void CSSVariableResolver::resolveAndApplyVariableReferences(StyleResolverState& state, CSSPropertyID id, const CSSVariableReferenceValue& value) { CSSVariableResolver resolver(state.style()->variables()); Vector<CSSParserToken> tokens; if (resolver.resolveTokenRange(value.variableDataValue()->tokens(), tokens)) { CSSParserContext context(HTMLStandardMode, 0); HeapVector<CSSProperty, 256> parsedProperties; // TODO: Non-shorthands should just call CSSPropertyParser::parseSingleValue if (CSSPropertyParser::parseValue(id, false, CSSParserTokenRange(tokens), context, parsedProperties, StyleRule::RuleType::Style)) { unsigned parsedPropertiesCount = parsedProperties.size(); for (unsigned i = 0; i < parsedPropertiesCount; ++i) StyleBuilder::applyProperty(parsedProperties[i].id(), state, parsedProperties[i].value()); return; } } RawPtr<CSSUnsetValue> unset = cssValuePool().createUnsetValue(); if (isShorthandProperty(id)) { StylePropertyShorthand shorthand = shorthandForProperty(id); for (unsigned i = 0; i < shorthand.length(); i++) StyleBuilder::applyProperty(shorthand.properties()[i], state, unset.get()); return; } StyleBuilder::applyProperty(id, state, unset.get()); }
RawPtr<HTMLElement> VTTElement::createEquivalentHTMLElement(Document& document) { RawPtr<HTMLElement> htmlElement = nullptr; switch (m_webVTTNodeType) { case VTTNodeTypeClass: case VTTNodeTypeLanguage: case VTTNodeTypeVoice: htmlElement = HTMLElementFactory::createHTMLElement(HTMLNames::spanTag.localName(), document); htmlElement.get()->setAttribute(HTMLNames::titleAttr, getAttribute(voiceAttributeName())); htmlElement.get()->setAttribute(HTMLNames::langAttr, getAttribute(langAttributeName())); break; case VTTNodeTypeItalic: htmlElement = HTMLElementFactory::createHTMLElement(HTMLNames::iTag.localName(), document); break; case VTTNodeTypeBold: htmlElement = HTMLElementFactory::createHTMLElement(HTMLNames::bTag.localName(), document); break; case VTTNodeTypeUnderline: htmlElement = HTMLElementFactory::createHTMLElement(HTMLNames::uTag.localName(), document); break; case VTTNodeTypeRuby: htmlElement = HTMLElementFactory::createHTMLElement(HTMLNames::rubyTag.localName(), document); break; case VTTNodeTypeRubyText: htmlElement = HTMLElementFactory::createHTMLElement(HTMLNames::rtTag.localName(), document); break; default: ASSERT_NOT_REACHED(); } htmlElement.get()->setAttribute(HTMLNames::classAttr, getAttribute(HTMLNames::classAttr)); return htmlElement; }
// Tests moving extent over to the other side of the vase and immediately // passing the word boundary and going into word granularity. TEST_F(GranularityStrategyTest, DirectionSwitchSideWordGranularityThenShrink) { dummyPageHolder().frame().settings()->setDefaultFontSize(12); String str = "ab cd efghijkl mnopqr iiin, abc"; RawPtr<Text> text = document().createTextNode(str); document().body()->appendChild(text); dummyPageHolder().frame().settings()->setSelectionStrategy(SelectionStrategy::Direction); parseText(text.get()); // "abcd efgh ijkl mno^pqr|> iiin, abc" (^ means base, | means extent, < means start, and > means end). selection().setSelection(VisibleSelection(Position(text, 18), Position(text, 21))); EXPECT_EQ_SELECTED_TEXT("pqr"); // Move to the middle of word #4 selecting it - this will set the offset to // be half the width of "iiin". selection().moveRangeSelectionExtent(m_wordMiddles[4]); EXPECT_EQ_SELECTED_TEXT("pqr iiin"); // Move to the middle of word #2 - extent will switch over to the other // side of the base, and we should enter word granularity since we pass // the word boundary. The offset should become negative since the width // of "efghjkkl" is greater than that of "iiin". int offset = m_letterPos[26].x() - m_wordMiddles[4].x(); IntPoint p = IntPoint(m_wordMiddles[2].x() - offset - 1, m_wordMiddles[2].y()); selection().moveRangeSelectionExtent(p); EXPECT_EQ_SELECTED_TEXT("efghijkl mno"); p.move(m_letterPos[7].x() - m_letterPos[6].x(), 0); selection().moveRangeSelectionExtent(p); EXPECT_EQ_SELECTED_TEXT("fghijkl mno"); }
RawPtr<Text> Text::splitText(unsigned offset, ExceptionState& exceptionState) { // IndexSizeError: Raised if the specified offset is negative or greater than // the number of 16-bit units in data. if (offset > length()) { exceptionState.throwDOMException(IndexSizeError, "The offset " + String::number(offset) + " is larger than the Text node's length."); return nullptr; } EventQueueScope scope; String oldStr = data(); RawPtr<Text> newText = cloneWithData(oldStr.substring(offset)); setDataWithoutUpdate(oldStr.substring(0, offset)); didModifyData(oldStr, CharacterData::UpdateFromNonParser); if (parentNode()) parentNode()->insertBefore(newText.get(), nextSibling(), exceptionState); if (exceptionState.hadException()) return nullptr; if (layoutObject()) layoutObject()->setTextWithOffset(dataImpl(), 0, oldStr.length()); if (parentNode()) document().didSplitTextNode(*this); return newText.release(); }
RawPtr<Text> GranularityStrategyTest::setupRotate(String str) { setInnerHTML( "<html>" "<head>" "<style>" "div {" "transform: translate(0px,600px) rotate(90deg);" "}" "</style>" "</head>" "<body>" "<div id='mytext'></div>" "</body>" "</html>"); RawPtr<Text> text = document().createTextNode(str); Element* div = document().getElementById("mytext"); div->appendChild(text); document().view()->updateAllLifecyclePhases(); parseText(text.get()); return text.release(); }
WebRemoteFrame* WebRemoteFrameImpl::createRemoteChild(WebTreeScopeType scope, const WebString& name, const WebString& uniqueName, WebSandboxFlags sandboxFlags, WebRemoteFrameClient* client, WebFrame* opener) { WebRemoteFrameImpl* child = WebRemoteFrameImpl::create(scope, client, opener); appendChild(child); RawPtr<RemoteFrameOwner> owner = RemoteFrameOwner::create(static_cast<SandboxFlags>(sandboxFlags), WebFrameOwnerProperties()); child->initializeCoreFrame(frame()->host(), owner.get(), name, uniqueName); return child; }
void BaseChooserOnlyDateAndTimeInputType::createShadowSubtree() { DEFINE_STATIC_LOCAL(AtomicString, valueContainerPseudo, ("-webkit-date-and-time-value")); RawPtr<HTMLDivElement> valueContainer = HTMLDivElement::create(element().document()); valueContainer->setShadowPseudoId(valueContainerPseudo); element().userAgentShadowRoot()->appendChild(valueContainer.get()); updateView(); }
TEST_F(LayoutPartTest, DestroyUpdatesImageQualityController) { RawPtr<Element> element = HTMLElement::create(HTMLNames::divTag, document()); LayoutObject* part = new OverriddenLayoutPart(element.get()); // The third and forth arguments are not important in this test. ImageQualityController::imageQualityController()->set(*part, 0, this, LayoutSize(1, 1), false); EXPECT_TRUE(ImageQualityController::has(*part)); part->destroy(); EXPECT_FALSE(ImageQualityController::has(*part)); }
TEST(MainThreadTaskRunnerTest, PostTask) { RawPtr<NullExecutionContext> context = new NullExecutionContext(); RawPtr<MainThreadTaskRunner> runner = MainThreadTaskRunner::create(context.get()); bool isMarked = false; runner->postTask(BLINK_FROM_HERE, createSameThreadTask(&markBoolean, &isMarked)); EXPECT_FALSE(isMarked); blink::testing::runPendingTasks(); EXPECT_TRUE(isMarked); }
void FileSystemCallbacksBase::handleEventOrScheduleCallback(RawPtr<CB> callback, CBArg* arg) { ASSERT(callback); InspectorInstrumentationCookie cookie = InspectorInstrumentation::traceAsyncOperationCompletedCallbackStarting(m_executionContext.get(), m_asyncOperationId); if (shouldScheduleCallback()) DOMFileSystem::scheduleCallback(m_executionContext.get(), callback.get(), arg); else if (callback) callback->handleEvent(arg); m_executionContext.clear(); InspectorInstrumentation::traceAsyncCallbackCompleted(cookie); }
TEST(MainThreadTaskRunnerTest, RemoveRunner) { RawPtr<NullExecutionContext> context = new NullExecutionContext(); RawPtr<MainThreadTaskRunner> runner = MainThreadTaskRunner::create(context.get()); bool isMarked = false; context->setTasksNeedSuspension(true); runner->postTask(BLINK_FROM_HERE, createSameThreadTask(&markBoolean, &isMarked)); runner.clear(); blink::testing::runPendingTasks(); EXPECT_FALSE(isMarked); }
void DeviceSingleWindowEventController::dispatchDeviceEvent(RawPtr<Event> prpEvent) { if (!document().domWindow() || document().activeDOMObjectsAreSuspended() || document().activeDOMObjectsAreStopped()) return; RawPtr<Event> event = prpEvent; document().domWindow()->dispatchEvent(event); if (m_needsCheckingNullEvents) { if (isNullEvent(event.get())) stopUpdating(); else m_needsCheckingNullEvents = false; } }
WebLocalFrame* WebRemoteFrameImpl::createLocalChild(WebTreeScopeType scope, const WebString& name, const WebString& uniqueName, WebSandboxFlags sandboxFlags, WebFrameClient* client, WebFrame* previousSibling, const WebFrameOwnerProperties& frameOwnerProperties, WebFrame* opener) { WebLocalFrameImpl* child = WebLocalFrameImpl::create(scope, client, opener); insertAfter(child, previousSibling); RawPtr<RemoteFrameOwner> owner = RemoteFrameOwner::create(static_cast<SandboxFlags>(sandboxFlags), frameOwnerProperties); // FIXME: currently this calls LocalFrame::init() on the created LocalFrame, which may // result in the browser observing two navigations to about:blank (one from the initial // frame creation, and one from swapping it into the remote process). FrameLoader might // need a special initialization function for this case to avoid that duplicate navigation. child->initializeCoreFrame(frame()->host(), owner.get(), name, uniqueName); // Partially related with the above FIXME--the init() call may trigger JS dispatch. However, // if the parent is remote, it should never be detached synchronously... DCHECK(child->frame()); return child; }
static void verifyCSSCalc(String text, double value, bool valid, unsigned fontSize, unsigned viewportWidth, unsigned viewportHeight) { CSSLengthArray lengthArray; initLengthArray(lengthArray); RawPtr<CSSValue> cssValue = CSSParser::parseSingleValue(CSSPropertyLeft, text); CSSPrimitiveValue* primitiveValue = toCSSPrimitiveValue(cssValue.get()); if (primitiveValue) primitiveValue->accumulateLengthArray(lengthArray); else ASSERT_EQ(valid, false); float length = lengthArray.at(CSSPrimitiveValue::UnitTypePixels); length += lengthArray.at(CSSPrimitiveValue::UnitTypeFontSize) * fontSize; length += lengthArray.at(CSSPrimitiveValue::UnitTypeViewportWidth) * viewportWidth / 100.0; length += lengthArray.at(CSSPrimitiveValue::UnitTypeViewportHeight) * viewportHeight / 100.0; ASSERT_EQ(value, length); }
void ConsoleMessageStorage::reportMessage(ExecutionContext* context, RawPtr<ConsoleMessage> prpMessage) { RawPtr<ConsoleMessage> message = prpMessage; message->collectCallStack(); if (message->type() == ClearMessageType) clear(context); InspectorInstrumentation::addMessageToConsole(context, message.get()); ASSERT(m_messages.size() <= maxConsoleMessageCount); if (m_messages.size() == maxConsoleMessageCount) { ++m_expiredCount; m_messages.removeFirst(); } m_messages.append(message); }
// Make sure we switch to word granularity right away when starting on a // word boundary and extending. TEST_F(GranularityStrategyTest, DirectionSwitchStartOnBoundary) { dummyPageHolder().frame().settings()->setDefaultFontSize(12); String str = "ab cd efghijkl mnopqr iiin, abc"; RawPtr<Text> text = document().createTextNode(str); document().body()->appendChild(text); dummyPageHolder().frame().settings()->setSelectionStrategy(SelectionStrategy::Direction); parseText(text.get()); // "ab cd efghijkl ^mnopqr |>stuvwi inm," (^ means base and | means extent, // > means end). selection().setSelection(VisibleSelection(Position(text, 15), Position(text, 22))); EXPECT_EQ_SELECTED_TEXT("mnopqr "); selection().moveRangeSelectionExtent(m_wordMiddles[4]); EXPECT_EQ_SELECTED_TEXT("mnopqr iiin"); }
WebDOMMessageEvent::WebDOMMessageEvent(const WebSerializedScriptValue& messageData, const WebString& origin, const WebFrame* sourceFrame, const WebDocument& targetDocument, const WebMessagePortChannelArray& channels) : WebDOMMessageEvent(MessageEvent::create()) { DOMWindow* window = nullptr; if (sourceFrame) window = sourceFrame->toImplBase()->frame()->domWindow(); MessagePortArray* ports = nullptr; if (!targetDocument.isNull()) { RawPtr<Document> coreDocument = RawPtr<Document>(targetDocument); ports = MessagePort::toMessagePortArray(coreDocument.get(), channels); } // Use an empty array for |ports| when it is null because this function // is used to implement postMessage(). if (!ports) ports = new MessagePortArray; // TODO(esprehn): Chromium always passes empty string for lastEventId, is that right? unwrap<MessageEvent>()->initMessageEvent("message", false, false, messageData, origin, ""/*lastEventId*/, window, ports); }
void InsertParagraphSeparatorCommand::doApply(EditingState* editingState) { if (!endingSelection().isNonOrphanedCaretOrRange()) return; Position insertionPosition = endingSelection().start(); TextAffinity affinity = endingSelection().affinity(); // Delete the current selection. if (endingSelection().isRange()) { calculateStyleBeforeInsertion(insertionPosition); deleteSelection(editingState, false, true); if (editingState->isAborted()) return; insertionPosition = endingSelection().start(); affinity = endingSelection().affinity(); } // FIXME: The parentAnchoredEquivalent conversion needs to be moved into enclosingBlock. RawPtr<Element> startBlock = enclosingBlock(insertionPosition.parentAnchoredEquivalent().computeContainerNode()); Node* listChildNode = enclosingListChild(insertionPosition.parentAnchoredEquivalent().computeContainerNode()); RawPtr<HTMLElement> listChild = listChildNode && listChildNode->isHTMLElement() ? toHTMLElement(listChildNode) : 0; Position canonicalPos = createVisiblePosition(insertionPosition).deepEquivalent(); if (!startBlock || !startBlock->nonShadowBoundaryParentNode() || isTableCell(startBlock.get()) || isHTMLFormElement(*startBlock) // FIXME: If the node is hidden, we don't have a canonical position so we will do the wrong thing for tables and <hr>. https://bugs.webkit.org/show_bug.cgi?id=40342 || (!canonicalPos.isNull() && isDisplayInsideTable(canonicalPos.anchorNode())) || (!canonicalPos.isNull() && isHTMLHRElement(*canonicalPos.anchorNode()))) { applyCommandToComposite(InsertLineBreakCommand::create(document()), editingState); return; } // Use the leftmost candidate. insertionPosition = mostBackwardCaretPosition(insertionPosition); if (!isVisuallyEquivalentCandidate(insertionPosition)) insertionPosition = mostForwardCaretPosition(insertionPosition); // Adjust the insertion position after the delete const Position originalInsertionPosition = insertionPosition; const Element* enclosingAnchor = enclosingAnchorElement(originalInsertionPosition); insertionPosition = positionAvoidingSpecialElementBoundary(insertionPosition, editingState); if (editingState->isAborted()) return; if (listChild == enclosingAnchor) { // |positionAvoidingSpecialElementBoundary()| creates new A element and // move to another place. listChild = toHTMLElement(enclosingAnchorElement(originalInsertionPosition)); } VisiblePosition visiblePos = createVisiblePosition(insertionPosition, affinity); calculateStyleBeforeInsertion(insertionPosition); //--------------------------------------------------------------------- // Handle special case of typing return on an empty list item if (breakOutOfEmptyListItem(editingState) || editingState->isAborted()) return; //--------------------------------------------------------------------- // Prepare for more general cases. bool isFirstInBlock = isStartOfBlock(visiblePos); bool isLastInBlock = isEndOfBlock(visiblePos); bool nestNewBlock = false; // Create block to be inserted. RawPtr<Element> blockToInsert = nullptr; if (startBlock->isRootEditableElement()) { blockToInsert = createDefaultParagraphElement(document()); nestNewBlock = true; } else if (shouldUseDefaultParagraphElement(startBlock.get())) { blockToInsert = createDefaultParagraphElement(document()); } else { blockToInsert = startBlock->cloneElementWithoutChildren(); } //--------------------------------------------------------------------- // Handle case when position is in the last visible position in its block, // including when the block is empty. if (isLastInBlock) { if (nestNewBlock) { if (isFirstInBlock && !lineBreakExistsAtVisiblePosition(visiblePos)) { // The block is empty. Create an empty block to // represent the paragraph that we're leaving. RawPtr<HTMLElement> extraBlock = createDefaultParagraphElement(document()); appendNode(extraBlock, startBlock, editingState); if (editingState->isAborted()) return; appendBlockPlaceholder(extraBlock, editingState); if (editingState->isAborted()) return; } appendNode(blockToInsert, startBlock, editingState); if (editingState->isAborted()) return; } else { // We can get here if we pasted a copied portion of a blockquote with a newline at the end and are trying to paste it // into an unquoted area. We then don't want the newline within the blockquote or else it will also be quoted. if (m_pasteBlockquoteIntoUnquotedArea) { if (HTMLQuoteElement* highestBlockquote = toHTMLQuoteElement(highestEnclosingNodeOfType(canonicalPos, &isMailHTMLBlockquoteElement))) startBlock = highestBlockquote; } if (listChild && listChild != startBlock) { RawPtr<Element> listChildToInsert = listChild->cloneElementWithoutChildren(); appendNode(blockToInsert, listChildToInsert.get(), editingState); if (editingState->isAborted()) return; insertNodeAfter(listChildToInsert.get(), listChild, editingState); } else { // Most of the time we want to stay at the nesting level of the startBlock (e.g., when nesting within lists). However, // for div nodes, this can result in nested div tags that are hard to break out of. Element* siblingElement = startBlock.get(); if (isHTMLDivElement(*blockToInsert)) siblingElement = highestVisuallyEquivalentDivBelowRoot(startBlock.get()); insertNodeAfter(blockToInsert, siblingElement, editingState); } if (editingState->isAborted()) return; } // Recreate the same structure in the new paragraph. HeapVector<Member<Element>> ancestors; getAncestorsInsideBlock(positionOutsideTabSpan(insertionPosition).anchorNode(), startBlock.get(), ancestors); RawPtr<Element> parent = cloneHierarchyUnderNewBlock(ancestors, blockToInsert, editingState); if (editingState->isAborted()) return; appendBlockPlaceholder(parent, editingState); if (editingState->isAborted()) return; setEndingSelection(VisibleSelection(firstPositionInNode(parent.get()), TextAffinity::Downstream, endingSelection().isDirectional())); return; } //--------------------------------------------------------------------- // Handle case when position is in the first visible position in its block, and // similar case where previous position is in another, presumeably nested, block. if (isFirstInBlock || !inSameBlock(visiblePos, previousPositionOf(visiblePos))) { Node* refNode = nullptr; insertionPosition = positionOutsideTabSpan(insertionPosition); if (isFirstInBlock && !nestNewBlock) { if (listChild && listChild != startBlock) { RawPtr<Element> listChildToInsert = listChild->cloneElementWithoutChildren(); appendNode(blockToInsert, listChildToInsert.get(), editingState); if (editingState->isAborted()) return; insertNodeBefore(listChildToInsert.get(), listChild, editingState); if (editingState->isAborted()) return; } else { refNode = startBlock.get(); } } else if (isFirstInBlock && nestNewBlock) { // startBlock should always have children, otherwise isLastInBlock would be true and it's handled above. ASSERT(startBlock->hasChildren()); refNode = startBlock->firstChild(); } else if (insertionPosition.anchorNode() == startBlock && nestNewBlock) { refNode = NodeTraversal::childAt(*startBlock, insertionPosition.computeEditingOffset()); ASSERT(refNode); // must be true or we'd be in the end of block case } else { refNode = insertionPosition.anchorNode(); } // find ending selection position easily before inserting the paragraph insertionPosition = mostForwardCaretPosition(insertionPosition); if (refNode) { insertNodeBefore(blockToInsert, refNode, editingState); if (editingState->isAborted()) return; } // Recreate the same structure in the new paragraph. HeapVector<Member<Element>> ancestors; insertionPosition = positionAvoidingSpecialElementBoundary(positionOutsideTabSpan(insertionPosition), editingState); if (editingState->isAborted()) return; getAncestorsInsideBlock(insertionPosition.anchorNode(), startBlock.get(), ancestors); RawPtr<Element> placeholder = cloneHierarchyUnderNewBlock(ancestors, blockToInsert, editingState); if (editingState->isAborted()) return; appendBlockPlaceholder(placeholder.release(), editingState); if (editingState->isAborted()) return; // In this case, we need to set the new ending selection. setEndingSelection(VisibleSelection(insertionPosition, TextAffinity::Downstream, endingSelection().isDirectional())); return; } //--------------------------------------------------------------------- // Handle the (more complicated) general case, // All of the content in the current block after visiblePos is // about to be wrapped in a new paragraph element. Add a br before // it if visiblePos is at the start of a paragraph so that the // content will move down a line. if (isStartOfParagraph(visiblePos)) { RawPtr<HTMLBRElement> br = HTMLBRElement::create(document()); insertNodeAt(br.get(), insertionPosition, editingState); if (editingState->isAborted()) return; insertionPosition = positionInParentAfterNode(*br); // If the insertion point is a break element, there is nothing else // we need to do. if (visiblePos.deepEquivalent().anchorNode()->layoutObject()->isBR()) { setEndingSelection(VisibleSelection(insertionPosition, TextAffinity::Downstream, endingSelection().isDirectional())); return; } } // Move downstream. Typing style code will take care of carrying along the // style of the upstream position. insertionPosition = mostForwardCaretPosition(insertionPosition); // At this point, the insertionPosition's node could be a container, and we want to make sure we include // all of the correct nodes when building the ancestor list. So this needs to be the deepest representation of the position // before we walk the DOM tree. insertionPosition = positionOutsideTabSpan(createVisiblePosition(insertionPosition).deepEquivalent()); // If the returned position lies either at the end or at the start of an element that is ignored by editing // we should move to its upstream or downstream position. if (editingIgnoresContent(insertionPosition.anchorNode())) { if (insertionPosition.atLastEditingPositionForNode()) insertionPosition = mostForwardCaretPosition(insertionPosition); else if (insertionPosition.atFirstEditingPositionForNode()) insertionPosition = mostBackwardCaretPosition(insertionPosition); } // Make sure we do not cause a rendered space to become unrendered. // FIXME: We need the affinity for pos, but mostForwardCaretPosition does not give it Position leadingWhitespace = leadingWhitespacePosition(insertionPosition, VP_DEFAULT_AFFINITY); // FIXME: leadingWhitespacePosition is returning the position before preserved newlines for positions // after the preserved newline, causing the newline to be turned into a nbsp. if (leadingWhitespace.isNotNull() && leadingWhitespace.anchorNode()->isTextNode()) { Text* textNode = toText(leadingWhitespace.anchorNode()); ASSERT(!textNode->layoutObject() || textNode->layoutObject()->style()->collapseWhiteSpace()); replaceTextInNodePreservingMarkers(textNode, leadingWhitespace.computeOffsetInContainerNode(), 1, nonBreakingSpaceString()); } // Split at pos if in the middle of a text node. Position positionAfterSplit; if (insertionPosition.isOffsetInAnchor() && insertionPosition.computeContainerNode()->isTextNode()) { RawPtr<Text> textNode = toText(insertionPosition.computeContainerNode()); int textOffset = insertionPosition.offsetInContainerNode(); bool atEnd = static_cast<unsigned>(textOffset) >= textNode->length(); if (textOffset > 0 && !atEnd) { splitTextNode(textNode, textOffset); positionAfterSplit = firstPositionInNode(textNode.get()); insertionPosition = Position(textNode->previousSibling(), textOffset); visiblePos = createVisiblePosition(insertionPosition); } } // If we got detached due to mutation events, just bail out. if (!startBlock->parentNode()) return; // Put the added block in the tree. if (nestNewBlock) { appendNode(blockToInsert.get(), startBlock, editingState); } else if (listChild && listChild != startBlock) { RawPtr<Element> listChildToInsert = listChild->cloneElementWithoutChildren(); appendNode(blockToInsert.get(), listChildToInsert.get(), editingState); if (editingState->isAborted()) return; insertNodeAfter(listChildToInsert.get(), listChild, editingState); } else { insertNodeAfter(blockToInsert.get(), startBlock, editingState); } if (editingState->isAborted()) return; document().updateLayoutIgnorePendingStylesheets(); // If the paragraph separator was inserted at the end of a paragraph, an empty line must be // created. All of the nodes, starting at visiblePos, are about to be added to the new paragraph // element. If the first node to be inserted won't be one that will hold an empty line open, add a br. if (isEndOfParagraph(visiblePos) && !lineBreakExistsAtVisiblePosition(visiblePos)) { appendNode(HTMLBRElement::create(document()).get(), blockToInsert.get(), editingState); if (editingState->isAborted()) return; } // Move the start node and the siblings of the start node. if (createVisiblePosition(insertionPosition).deepEquivalent() != createVisiblePosition(positionBeforeNode(blockToInsert.get())).deepEquivalent()) { Node* n; if (insertionPosition.computeContainerNode() == startBlock) { n = insertionPosition.computeNodeAfterPosition(); } else { Node* splitTo = insertionPosition.computeContainerNode(); if (splitTo->isTextNode() && insertionPosition.offsetInContainerNode() >= caretMaxOffset(splitTo)) splitTo = NodeTraversal::next(*splitTo, startBlock.get()); if (splitTo) splitTreeToNode(splitTo, startBlock.get()); for (n = startBlock->firstChild(); n; n = n->nextSibling()) { VisiblePosition beforeNodePosition = createVisiblePosition(positionBeforeNode(n)); if (!beforeNodePosition.isNull() && comparePositions(createVisiblePosition(insertionPosition), beforeNodePosition) <= 0) break; } } moveRemainingSiblingsToNewParent(n, blockToInsert.get(), blockToInsert, editingState); if (editingState->isAborted()) return; } // Handle whitespace that occurs after the split if (positionAfterSplit.isNotNull()) { document().updateLayoutIgnorePendingStylesheets(); // TODO(yosin) |isRenderedCharacter()| should be removed, and we should // use |VisiblePosition::characterAfter()|. if (!isRenderedCharacter(positionAfterSplit)) { // Clear out all whitespace and insert one non-breaking space ASSERT(!positionAfterSplit.computeContainerNode()->layoutObject() || positionAfterSplit.computeContainerNode()->layoutObject()->style()->collapseWhiteSpace()); deleteInsignificantTextDownstream(positionAfterSplit); if (positionAfterSplit.anchorNode()->isTextNode()) insertTextIntoNode(toText(positionAfterSplit.computeContainerNode()), 0, nonBreakingSpaceString()); } } setEndingSelection(VisibleSelection(firstPositionInNode(blockToInsert.get()), TextAffinity::Downstream, endingSelection().isDirectional())); applyStyleAfterInsertion(startBlock.get(), editingState); }
TEST_F(ContextMenuControllerTest, TestCustomMenu) { document().settings()->setScriptEnabled(true); // Load the the test page. setBodyInnerHTML( "<button id=\"button_id\" contextmenu=\"menu_id\" style=\"height: 100px; width: 100px;\">" "<menu type=\"context\" id=\"menu_id\">" "<menuitem label=\"Item1\" onclick='document.title = \"Title 1\";'>" "<menuitem label=\"Item2\" onclick='document.title = \"Title 2\";'>" "<menuitem label=\"Item3\" onclick='document.title = \"Title 3\";'>" "<menu label='Submenu'>" "<menuitem label=\"Item4\" onclick='document.title = \"Title 4\";'>" "<menuitem label=\"Item5\" onclick='document.title = \"Title 5\";'>" "<menuitem label=\"Item6\" onclick='document.title = \"Title 6\";'>" "</menu>" "<menuitem id=\"item7\" type=\"checkbox\" checked label=\"Item7\"" "onclick='if (document.getElementById(\"item7\").hasAttribute(\"checked\"))" "document.title = \"Title 7 checked\"; else document.title = \"Title 7 not checked\";'>" "<menuitem id=\"item8\" type=\"radio\" radiogroup=\"group\" label=\"Item8\"" "onclick='if (document.getElementById(\"item8\").hasAttribute(\"checked\"))" "document.title = \"Title 8 checked\"; else if (document.getElementById(\"item9\").hasAttribute(\"checked\"))" "document.title = \"Title 8 not checked and Title 9 checked\";'>" "<menuitem id=\"item9\" type=\"radio\" radiogroup=\"group\" checked label=\"Item9\"" "onclick='if (document.getElementById(\"item9\").hasAttribute(\"checked\"))" "document.title = \"Title 9 checked\"; else document.title = \"Title 9 not checked\";'>" "<menuitem id=\"item10\" type=\"radio\" radiogroup=\"group\" label=\"Item10\"" "onclick='if (document.getElementById(\"item10\").hasAttribute(\"checked\"))" "document.title = \"Title 10 checked\"; else if (document.getElementById(\"item8\").hasAttribute(\"checked\"))" "document.title = \"Title 10 not checked and Title 8 checked\";'>" "</menu>" "</button>"); // Create right button click event and pass it to context menu controller. RawPtr<Event> event = MouseEvent::create(EventTypeNames::click, false, false, document().domWindow(), 50, 50, 0, 0, 0, 0, 0, PlatformEvent::NoModifiers, 1, 0, nullptr, 0, PlatformMouseEvent::RealOrIndistinguishable, String()); document().getElementById("button_id")->focus(); event->setTarget(document().getElementById("button_id")); document().page()->contextMenuController().handleContextMenuEvent(event.get()); // Item 1 // Item 2 // Item 3 // Submenu > Item 4 // Item 5 // Item 6 // *Item 7 // Item 8 // *Item 9 // Item 10 const Vector<ContextMenuItem>& items = document().page()->contextMenuController().contextMenu()->items(); EXPECT_EQ(8u, items.size()); EXPECT_EQ(ActionType, items[0].type()); EXPECT_STREQ("Item1", items[0].title().utf8().data()); document().page()->contextMenuController().contextMenuItemSelected(&items[0]); EXPECT_STREQ("Title 1", document().title().utf8().data()); EXPECT_EQ(SubmenuType, items[3].type()); EXPECT_STREQ("Submenu", items[3].title().utf8().data()); const Vector<ContextMenuItem>& subMenuItems = items[3].subMenuItems(); EXPECT_EQ(3u, subMenuItems.size()); EXPECT_STREQ("Item6", subMenuItems[2].title().utf8().data()); document().page()->contextMenuController().contextMenuItemSelected(&subMenuItems[2]); EXPECT_STREQ("Title 6", document().title().utf8().data()); document().page()->contextMenuController().contextMenuItemSelected(&items[4]); EXPECT_STREQ("Title 7 checked", document().title().utf8().data()); document().page()->contextMenuController().contextMenuItemSelected(&items[4]); EXPECT_STREQ("Title 7 not checked", document().title().utf8().data()); document().page()->contextMenuController().contextMenuItemSelected(&items[5]); EXPECT_STREQ("Title 8 not checked and Title 9 checked", document().title().utf8().data()); document().page()->contextMenuController().contextMenuItemSelected(&items[7]); EXPECT_STREQ("Title 10 not checked and Title 8 checked", document().title().utf8().data()); }
void V8MutationObserver::constructorCustom(const v8::FunctionCallbackInfo<v8::Value>& info) { ExceptionState exceptionState(ExceptionState::ConstructionContext, "MutationObserver", info.Holder(), info.GetIsolate()); if (info.Length() < 1) { exceptionState.throwTypeError(ExceptionMessages::notEnoughArguments(1, info.Length())); exceptionState.throwIfNeeded(); return; } v8::Local<v8::Value> arg = info[0]; if (!arg->IsFunction()) { exceptionState.throwTypeError("Callback argument must be a function"); exceptionState.throwIfNeeded(); return; } v8::Local<v8::Object> wrapper = info.Holder(); RawPtr<MutationCallback> callback = V8MutationCallback::create(v8::Local<v8::Function>::Cast(arg), wrapper, ScriptState::current(info.GetIsolate())); RawPtr<MutationObserver> observer = MutationObserver::create(callback.release()); v8SetReturnValue(info, V8DOMWrapper::associateObjectWithWrapper(info.GetIsolate(), observer.get(), &wrapperTypeInfo, wrapper)); }
void InputMethodController::setComposition(const String& text, const Vector<CompositionUnderline>& underlines, unsigned selectionStart, unsigned selectionEnd) { Editor::RevealSelectionScope revealSelectionScope(&editor()); // Updates styles before setting selection for composition to prevent // inserting the previous composition text into text nodes oddly. // See https://bugs.webkit.org/show_bug.cgi?id=46868 frame().document()->updateLayoutTree(); selectComposition(); if (frame().selection().isNone()) return; if (Element* target = frame().document()->focusedElement()) { // Dispatch an appropriate composition event to the focused node. // We check the composition status and choose an appropriate composition event since this // function is used for three purposes: // 1. Starting a new composition. // Send a compositionstart and a compositionupdate event when this function creates // a new composition node, i.e. // !hasComposition() && !text.isEmpty(). // Sending a compositionupdate event at this time ensures that at least one // compositionupdate event is dispatched. // 2. Updating the existing composition node. // Send a compositionupdate event when this function updates the existing composition // node, i.e. hasComposition() && !text.isEmpty(). // 3. Canceling the ongoing composition. // Send a compositionend event when function deletes the existing composition node, i.e. // !hasComposition() && test.isEmpty(). RawPtr<CompositionEvent> event = nullptr; if (!hasComposition()) { // We should send a compositionstart event only when the given text is not empty because this // function doesn't create a composition node when the text is empty. if (!text.isEmpty()) { target->dispatchEvent(CompositionEvent::create(EventTypeNames::compositionstart, frame().domWindow(), frame().selectedText())); event = CompositionEvent::create(EventTypeNames::compositionupdate, frame().domWindow(), text); } } else { if (!text.isEmpty()) event = CompositionEvent::create(EventTypeNames::compositionupdate, frame().domWindow(), text); else event = CompositionEvent::create(EventTypeNames::compositionend, frame().domWindow(), text); } if (event.get()) target->dispatchEvent(event); } // If text is empty, then delete the old composition here. If text is non-empty, InsertTextCommand::input // will delete the old composition with an optimized replace operation. if (text.isEmpty()) { ASSERT(frame().document()); TypingCommand::deleteSelection(*frame().document(), TypingCommand::PreventSpellChecking); } clear(); if (text.isEmpty()) return; ASSERT(frame().document()); TypingCommand::insertText(*frame().document(), text, TypingCommand::SelectInsertedText | TypingCommand::PreventSpellChecking, TypingCommand::TextCompositionUpdate); // Find out what node has the composition now. Position base = mostForwardCaretPosition(frame().selection().base()); Node* baseNode = base.anchorNode(); if (!baseNode || !baseNode->isTextNode()) return; Position extent = frame().selection().extent(); Node* extentNode = extent.anchorNode(); if (baseNode != extentNode) return; unsigned extentOffset = extent.computeOffsetInContainerNode(); unsigned baseOffset = base.computeOffsetInContainerNode(); if (baseOffset + text.length() != extentOffset) return; m_isDirty = true; m_hasComposition = true; if (!m_compositionRange) m_compositionRange = Range::create(baseNode->document()); m_compositionRange->setStart(baseNode, baseOffset); m_compositionRange->setEnd(baseNode, extentOffset); if (baseNode->layoutObject()) baseNode->layoutObject()->setShouldDoFullPaintInvalidation(); unsigned start = std::min(baseOffset + selectionStart, extentOffset); unsigned end = std::min(std::max(start, baseOffset + selectionEnd), extentOffset); RawPtr<Range> selectedRange = Range::create(baseNode->document(), baseNode, start, baseNode, end); frame().selection().setSelectedRange(selectedRange.get(), TextAffinity::Downstream, SelectionDirectionalMode::NonDirectional, NotUserTriggered); if (underlines.isEmpty()) { frame().document()->markers().addCompositionMarker(m_compositionRange->startPosition(), m_compositionRange->endPosition(), Color::black, false, LayoutTheme::theme().platformDefaultCompositionBackgroundColor()); return; } for (const auto& underline : underlines) { unsigned underlineStart = baseOffset + underline.startOffset; unsigned underlineEnd = baseOffset + underline.endOffset; EphemeralRange ephemeralLineRange = EphemeralRange(Position(baseNode, underlineStart), Position(baseNode, underlineEnd)); if (ephemeralLineRange.isNull()) continue; frame().document()->markers().addCompositionMarker(ephemeralLineRange.startPosition(), ephemeralLineRange.endPosition(), underline.color, underline.thick, underline.backgroundColor); } }
FilterOperations FilterOperationResolver::createFilterOperations(StyleResolverState& state, const CSSValue& inValue) { FilterOperations operations; if (inValue.isPrimitiveValue()) { ASSERT(toCSSPrimitiveValue(inValue).getValueID() == CSSValueNone); return operations; } const CSSToLengthConversionData& conversionData = state.cssToLengthConversionData(); for (auto& currValue : toCSSValueList(inValue)) { CSSFunctionValue* filterValue = toCSSFunctionValue(currValue.get()); FilterOperation::OperationType operationType = filterOperationForType(filterValue->functionType()); countFilterUse(operationType, state.document()); ASSERT(filterValue->length() <= 1); if (operationType == FilterOperation::REFERENCE) { CSSSVGDocumentValue* svgDocumentValue = toCSSSVGDocumentValue(filterValue->item(0)); KURL url = state.document().completeURL(svgDocumentValue->url()); RawPtr<ReferenceFilterOperation> operation = ReferenceFilterOperation::create(svgDocumentValue->url(), AtomicString(url.fragmentIdentifier())); if (SVGURIReference::isExternalURIReference(svgDocumentValue->url(), state.document())) { if (!svgDocumentValue->loadRequested()) state.elementStyleResources().addPendingSVGDocument(operation.get(), svgDocumentValue); else if (svgDocumentValue->cachedSVGDocument()) ReferenceFilterBuilder::setDocumentResourceReference(operation.get(), adoptPtr(new DocumentResourceReference(svgDocumentValue->cachedSVGDocument()))); } operations.operations().append(operation); continue; } CSSPrimitiveValue* firstValue = filterValue->length() && filterValue->item(0)->isPrimitiveValue() ? toCSSPrimitiveValue(filterValue->item(0)) : nullptr; switch (filterValue->functionType()) { case CSSValueGrayscale: case CSSValueSepia: case CSSValueSaturate: { double amount = 1; if (filterValue->length() == 1) { amount = firstValue->getDoubleValue(); if (firstValue->isPercentage()) amount /= 100; } operations.operations().append(BasicColorMatrixFilterOperation::create(amount, operationType)); break; } case CSSValueHueRotate: { double angle = 0; if (filterValue->length() == 1) angle = firstValue->computeDegrees(); operations.operations().append(BasicColorMatrixFilterOperation::create(angle, operationType)); break; } case CSSValueInvert: case CSSValueBrightness: case CSSValueContrast: case CSSValueOpacity: { double amount = (filterValue->functionType() == CSSValueBrightness) ? 0 : 1; if (filterValue->length() == 1) { amount = firstValue->getDoubleValue(); if (firstValue->isPercentage()) amount /= 100; } operations.operations().append(BasicComponentTransferFilterOperation::create(amount, operationType)); break; } case CSSValueBlur: { Length stdDeviation = Length(0, Fixed); if (filterValue->length() >= 1) stdDeviation = firstValue->convertToLength(conversionData); operations.operations().append(BlurFilterOperation::create(stdDeviation)); break; } case CSSValueDropShadow: { CSSShadowValue* item = toCSSShadowValue(filterValue->item(0)); IntPoint location(item->x->computeLength<int>(conversionData), item->y->computeLength<int>(conversionData)); int blur = item->blur ? item->blur->computeLength<int>(conversionData) : 0; Color shadowColor = Color::black; if (item->color) shadowColor = state.document().textLinkColors().colorFromCSSValue(*item->color, state.style()->color()); operations.operations().append(DropShadowFilterOperation::create(location, blur, shadowColor)); break; } default: ASSERT_NOT_REACHED(); break; } } return operations; }