PlainTextRange PlainTextRange::create(const ContainerNode& scope, const Range& range) { if (!range.startContainer()) return PlainTextRange(); // The critical assumption is that this only gets called with ranges that // concentrate on a given area containing the selection root. This is done // because of text fields and textareas. The DOM for those is not // directly in the document DOM, so ensure that the range does not cross a // boundary of one of those. if (range.startContainer() != &scope && !range.startContainer()->isDescendantOf(&scope)) return PlainTextRange(); if (range.endContainer() != scope && !range.endContainer()->isDescendantOf(&scope)) return PlainTextRange(); RefPtrWillBeRawPtr<Range> testRange = Range::create(scope.document(), const_cast<ContainerNode*>(&scope), 0, range.startContainer(), range.startOffset()); ASSERT(testRange->startContainer() == &scope); size_t start = TextIterator::rangeLength(testRange->startPosition(), testRange->endPosition()); testRange->setEnd(range.endContainer(), range.endOffset(), IGNORE_EXCEPTION); ASSERT(testRange->startContainer() == &scope); size_t end = TextIterator::rangeLength(testRange->startPosition(), testRange->endPosition()); return PlainTextRange(start, end); }
void SelectorDataList::findTraverseRootsAndExecute(ContainerNode& rootNode, typename SelectorQueryTrait::OutputType& output) const { // We need to return the matches in document order. To use id lookup while there is possiblity of multiple matches // we would need to sort the results. For now, just traverse the document in that case. ASSERT(m_selectors.size() == 1); bool isRightmostSelector = true; bool startFromParent = false; Element* singleMatchingElement = 0; for (const CSSSelector* selector = &m_selectors[0].selector; selector; selector = selector->tagHistory()) { if (selector->m_match == CSSSelector::Id && (rootNode.document().getNumberOfElementsWithId(selector->value(), singleMatchingElement) <= 1)) { ContainerNode* adjustedNode = &rootNode; if (singleMatchingElement && (isTreeScopeRoot(rootNode) || singleMatchingElement->isDescendantOf(&rootNode))) adjustedNode = singleMatchingElement; else if (!singleMatchingElement || isRightmostSelector) adjustedNode = 0; if (isRightmostSelector) { executeForTraverseRoot<SelectorQueryTrait>(m_selectors[0], adjustedNode, MatchesTraverseRoots, rootNode, output); return; } if (startFromParent && adjustedNode) adjustedNode = adjustedNode->parentNode(); executeForTraverseRoot<SelectorQueryTrait>(m_selectors[0], adjustedNode, DoesNotMatchTraverseRoots, rootNode, output); return; } // If we have both CSSSelector::Id and CSSSelector::Class at the same time, we should use Id // to find traverse root. if (!SelectorQueryTrait::shouldOnlyMatchFirstElement && !startFromParent && selector->m_match == CSSSelector::Class) { if (isRightmostSelector) { ClassElementList<AllElements> traverseRoots(rootNode, selector->value()); executeForTraverseRoots<SelectorQueryTrait>(m_selectors[0], traverseRoots, MatchesTraverseRoots, rootNode, output); return; } // Since there exists some ancestor element which has the class name, we need to see all children of rootNode. if (ancestorHasClassName(rootNode, selector->value())) { executeForTraverseRoot<SelectorQueryTrait>(m_selectors[0], &rootNode, DoesNotMatchTraverseRoots, rootNode, output); return; } ClassElementList<OnlyRoots> traverseRoots(rootNode, selector->value()); executeForTraverseRoots<SelectorQueryTrait>(m_selectors[0], traverseRoots, DoesNotMatchTraverseRoots, rootNode, output); return; } if (selector->relation() == CSSSelector::SubSelector) continue; isRightmostSelector = false; if (selector->relation() == CSSSelector::DirectAdjacent || selector->relation() == CSSSelector::IndirectAdjacent) startFromParent = true; else startFromParent = false; } executeForTraverseRoot<SelectorQueryTrait>(m_selectors[0], &rootNode, DoesNotMatchTraverseRoots, rootNode, output); }
void SelectorDataList::execute(ContainerNode& rootNode, typename SelectorQueryTrait::OutputType& output) const { if (!canUseFastQuery(rootNode)) { if (m_crossesTreeBoundary) { rootNode.document().updateDistributionForNodeIfNeeded(&rootNode); executeSlowTraversingShadowTree<SelectorQueryTrait>(rootNode, output); } else { executeSlow<SelectorQueryTrait>(rootNode, output); } return; } ASSERT(m_selectors.size() == 1); const CSSSelector& selector = *m_selectors[0]; const CSSSelector& firstSelector = selector; // Fast path for querySelector*('#id'), querySelector*('tag#id'). if (const CSSSelector* idSelector = selectorForIdLookup(firstSelector)) { const AtomicString& idToMatch = idSelector->value(); if (rootNode.treeScope().containsMultipleElementsWithId(idToMatch)) { const Vector<Element*>& elements = rootNode.treeScope().getAllElementsById(idToMatch); size_t count = elements.size(); for (size_t i = 0; i < count; ++i) { Element& element = *elements[i]; if (!(isTreeScopeRoot(rootNode) || element.isDescendantOf(&rootNode))) continue; if (selectorMatches(selector, element, rootNode)) { SelectorQueryTrait::appendElement(output, element); if (SelectorQueryTrait::shouldOnlyMatchFirstElement) return; } } return; } Element* element = rootNode.treeScope().getElementById(idToMatch); if (!element || !(isTreeScopeRoot(rootNode) || element->isDescendantOf(&rootNode))) return; if (selectorMatches(selector, *element, rootNode)) SelectorQueryTrait::appendElement(output, *element); return; } if (!firstSelector.tagHistory()) { // Fast path for querySelector*('.foo'), and querySelector*('div'). switch (firstSelector.match()) { case CSSSelector::Class: collectElementsByClassName<SelectorQueryTrait>(rootNode, firstSelector.value(), output); return; case CSSSelector::Tag: collectElementsByTagName<SelectorQueryTrait>(rootNode, firstSelector.tagQName(), output); return; default: break; // If we need another fast path, add here. } } findTraverseRootsAndExecute<SelectorQueryTrait>(rootNode, output); }
bool SelectorDataList::compileSelector(const SelectorData& selectorData, const ContainerNode& rootNode) { if (selectorData.compilationStatus != SelectorCompilationStatus::NotCompiled) return isCompiledSelector(selectorData.compilationStatus); JSC::VM& vm = rootNode.document().scriptExecutionContext()->vm(); selectorData.compilationStatus = SelectorCompiler::compileSelector(selectorData.selector, &vm, SelectorCompiler::SelectorContext::QuerySelector, selectorData.compiledSelectorCodeRef); return isCompiledSelector(selectorData.compilationStatus); }
PassRefPtr<JSLazyEventListener> JSLazyEventListener::createForNode(ContainerNode& node, const QualifiedName& attributeName, const AtomicString& attributeValue) { if (attributeValue.isNull()) return nullptr; TextPosition position = TextPosition::minimumPosition(); String sourceURL; // FIXME: We should be able to provide source information for frameless documents too (e.g. for importing nodes from XMLHttpRequest.responseXML). if (Frame* frame = node.document().frame()) { if (!frame->script().canExecuteScripts(AboutToExecuteScript)) return nullptr; position = frame->script().eventHandlerPosition(); sourceURL = node.document().url().string(); } return adoptRef(new JSLazyEventListener(attributeName.localName().string(), eventParameterName(node.isSVGElement()), attributeValue, &node, sourceURL, position, nullptr, mainThreadNormalWorld())); }
inline bool SelectorDataList::canUseFastQuery(const ContainerNode& rootNode) const { if (m_crossesTreeBoundary) return false; if (m_needsUpdatedDistribution) return false; if (rootNode.document().inQuirksMode()) return false; if (!rootNode.inDocument()) return false; return m_selectors.size() == 1; }
inline bool SelectorDataList::canUseFastQuery(const ContainerNode& rootNode) const { if (m_usesDeepCombinatorOrShadowPseudo) return false; if (m_needsUpdatedDistribution) return false; if (rootNode.document().inQuirksMode()) return false; if (!rootNode.inDocument()) return false; return m_selectors.size() == 1; }
static void willRemoveChildren(ContainerNode& container) { NodeVector children; getChildNodes(container, children); ChildListMutationScope mutation(container); for (auto it = children.begin(); it != children.end(); ++it) { Node& child = it->get(); mutation.willRemoveChild(child); child.notifyMutationObserversNodeWillDetach(); // fire removed from document mutation events. dispatchChildRemovalEvents(child); } container.document().nodeChildrenWillBeRemoved(container); disconnectSubframesIfNeeded(container, DescendantsOnly); }
static void fillContainerFromString(ContainerNode& paragraph, const String& string) { Document& document = paragraph.document(); if (string.isEmpty()) { paragraph.appendChild(createBlockPlaceholderElement(document), ASSERT_NO_EXCEPTION); return; } ASSERT(string.find('\n') == notFound); Vector<String> tabList; string.split('\t', true, tabList); String tabText = emptyString(); bool first = true; size_t numEntries = tabList.size(); for (size_t i = 0; i < numEntries; ++i) { const String& s = tabList[i]; // append the non-tab textual part if (!s.isEmpty()) { if (!tabText.isEmpty()) { paragraph.appendChild(createTabSpanElement(document, tabText), ASSERT_NO_EXCEPTION); tabText = emptyString(); } Ref<Node> textNode = document.createTextNode(stringWithRebalancedWhitespace(s, first, i + 1 == numEntries)); paragraph.appendChild(WTF::move(textNode), ASSERT_NO_EXCEPTION); } // there is a tab after every entry, except the last entry // (if the last character is a tab, the list gets an extra empty entry) if (i + 1 != numEntries) tabText.append('\t'); else if (!tabText.isEmpty()) paragraph.appendChild(createTabSpanElement(document, tabText), ASSERT_NO_EXCEPTION); first = false; } }
inline bool SelectorDataList::canUseFastQuery(const ContainerNode& rootNode) const { return m_selectors.size() == 1 && rootNode.inDocument() && !rootNode.document().inQuirksMode(); }
HTMLTagCollection::HTMLTagCollection(ContainerNode& rootNode, const AtomicString& localName) : TagCollection(rootNode, HTMLTagCollectionType, starAtom, localName) , m_loweredLocalName(localName.lower()) { ASSERT(rootNode.document().isHTMLDocument()); }
void HTMLConstructionSite::insertCommentOnHTMLHtmlElement(AtomicHTMLToken* token) { ASSERT(token->type() == HTMLTokenTypes::Comment); ContainerNode* parent = m_openElements.rootNode(); attachLater(parent, Comment::create(parent->document(), token->comment())); }
ALWAYS_INLINE void SelectorDataList::execute(ContainerNode& rootNode, typename SelectorQueryTrait::OutputType& output) const { ContainerNode* searchRootNode = &rootNode; switch (m_matchType) { case RightMostWithIdMatch: { const SelectorData& selectorData = m_selectors.first(); if (const CSSSelector* idSelector = selectorForIdLookup(*searchRootNode, *selectorData.selector)) { executeFastPathForIdSelector<SelectorQueryTrait>(*searchRootNode, m_selectors.first(), idSelector, output); break; } #if ENABLE(CSS_SELECTOR_JIT) if (selectorData.compilationStatus == SelectorCompilationStatus::SimpleSelectorChecker) goto CompiledSingleCase; #endif } FALLTHROUGH; case CompilableSingleWithRootFilter: case CompilableSingle: { #if ENABLE(CSS_SELECTOR_JIT) const SelectorData& selectorData = m_selectors.first(); ASSERT(m_matchType == RightMostWithIdMatch || selectorData.compilationStatus == SelectorCompilationStatus::NotCompiled); JSC::VM& vm = searchRootNode->document().scriptExecutionContext()->vm(); selectorData.compilationStatus = SelectorCompiler::compileSelector(selectorData.selector, &vm, SelectorCompiler::SelectorContext::QuerySelector, selectorData.compiledSelectorCodeRef); RELEASE_ASSERT(selectorData.compilationStatus != SelectorCompilationStatus::SelectorCheckerWithCheckingContext); if (selectorData.compilationStatus == SelectorCompilationStatus::SimpleSelectorChecker) { if (m_matchType == CompilableSingle) { m_matchType = CompiledSingle; goto CompiledSingleCase; } if (m_matchType == CompilableSingleWithRootFilter) { m_matchType = CompiledSingleWithRootFilter; goto CompiledSingleWithRootFilterCase; } goto CompiledSingleCase; } if (m_matchType != RightMostWithIdMatch) m_matchType = SingleSelector; goto SingleSelectorCase; ASSERT_NOT_REACHED(); break; #else FALLTHROUGH; #endif // ENABLE(CSS_SELECTOR_JIT) } case CompiledSingleWithRootFilter: #if ENABLE(CSS_SELECTOR_JIT) CompiledSingleWithRootFilterCase: searchRootNode = &filterRootById(*searchRootNode, *m_selectors.first().selector); #endif // ENABLE(CSS_SELECTOR_JIT) FALLTHROUGH; case CompiledSingle: #if ENABLE(CSS_SELECTOR_JIT) { CompiledSingleCase: const SelectorData& selectorData = m_selectors.first(); void* compiledSelectorChecker = selectorData.compiledSelectorCodeRef.code().executableAddress(); SelectorCompiler::SimpleSelectorChecker selectorChecker = SelectorCompiler::simpleSelectorCheckerFunction(compiledSelectorChecker, selectorData.compilationStatus); executeCompiledSimpleSelectorChecker<SelectorQueryTrait>(*searchRootNode, selectorChecker, output); break; } #else FALLTHROUGH; #endif // ENABLE(CSS_SELECTOR_JIT) case SingleSelector: #if ENABLE(CSS_SELECTOR_JIT) SingleSelectorCase: #endif executeSingleSelectorData<SelectorQueryTrait>(*searchRootNode, m_selectors.first(), output); break; case TagNameMatch: executeSingleTagNameSelectorData<SelectorQueryTrait>(*searchRootNode, m_selectors.first(), output); break; case ClassNameMatch: executeSingleClassNameSelectorData<SelectorQueryTrait>(*searchRootNode, m_selectors.first(), output); break; case MultipleSelectorMatch: executeSingleMultiSelectorData<SelectorQueryTrait>(*searchRootNode, output); break; } }
EphemeralRange PlainTextRange::createRangeFor(const ContainerNode& scope, GetRangeFor getRangeFor) const { ASSERT(isNotNull()); size_t docTextPosition = 0; bool startRangeFound = false; Position textRunStartPosition; Position textRunEndPosition; TextIteratorBehaviorFlags behaviorFlags = TextIteratorEmitsObjectReplacementCharacter; if (getRangeFor == ForSelection) behaviorFlags |= TextIteratorEmitsCharactersBetweenAllVisiblePositions; auto range = EphemeralRange::rangeOfContents(scope); TextIterator it(range.startPosition(), range.endPosition(), behaviorFlags); // FIXME: the atEnd() check shouldn't be necessary, workaround for // <http://bugs.webkit.org/show_bug.cgi?id=6289>. if (!start() && !length() && it.atEnd()) return EphemeralRange(Position(it.currentContainer(), 0)); Position resultStart = Position(&scope.document(), 0); Position resultEnd = resultStart; for (; !it.atEnd(); it.advance()) { int len = it.length(); textRunStartPosition = it.startPositionInCurrentContainer(); textRunEndPosition = it.endPositionInCurrentContainer(); bool foundStart = start() >= docTextPosition && start() <= docTextPosition + len; bool foundEnd = end() >= docTextPosition && end() <= docTextPosition + len; // Fix textRunRange->endPosition(), but only if foundStart || foundEnd, because it is only // in those cases that textRunRange is used. if (foundEnd) { // FIXME: This is a workaround for the fact that the end of a run // is often at the wrong position for emitted '\n's or if the // layoutObject of the current node is a replaced element. if (len == 1 && (it.characterAt(0) == '\n' || it.isInsideAtomicInlineElement())) { it.advance(); if (!it.atEnd()) { textRunEndPosition = it.startPositionInCurrentContainer(); } else { Position runEnd = nextPositionOf(createVisiblePosition(textRunStartPosition)).deepEquivalent(); if (runEnd.isNotNull()) textRunEndPosition = runEnd; } } } if (foundStart) { startRangeFound = true; if (textRunStartPosition.computeContainerNode()->isTextNode()) { int offset = start() - docTextPosition; resultStart = Position(textRunStartPosition.computeContainerNode(), offset + textRunStartPosition.offsetInContainerNode()); } else { if (start() == docTextPosition) resultStart = textRunStartPosition; else resultStart = textRunEndPosition; } } if (foundEnd) { if (textRunStartPosition.computeContainerNode()->isTextNode()) { int offset = end() - docTextPosition; resultEnd = Position(textRunStartPosition.computeContainerNode(), offset + textRunStartPosition.offsetInContainerNode()); } else { if (end() == docTextPosition) resultEnd = textRunStartPosition; else resultEnd = textRunEndPosition; } docTextPosition += len; break; } docTextPosition += len; } if (!startRangeFound) return EphemeralRange(); if (length() && end() > docTextPosition) { // end() is out of bounds resultEnd = textRunEndPosition; } return EphemeralRange(resultStart.toOffsetInAnchor(), resultEnd.toOffsetInAnchor()); }
PassRefPtrWillBeRawPtr<Range> PlainTextRange::createRangeFor(const ContainerNode& scope, GetRangeFor getRangeFor) const { ASSERT(isNotNull()); RefPtrWillBeRawPtr<Range> resultRange = scope.document().createRange(); size_t docTextPosition = 0; bool startRangeFound = false; Position textRunStartPosition; Position textRunEndPosition; TextIteratorBehaviorFlags behaviorFlags = TextIteratorEmitsObjectReplacementCharacter; if (getRangeFor == ForSelection) behaviorFlags |= TextIteratorEmitsCharactersBetweenAllVisiblePositions; auto range = rangeOfContents(const_cast<ContainerNode*>(&scope)); TextIterator it(range->startPosition(), range->endPosition(), behaviorFlags); // FIXME: the atEnd() check shouldn't be necessary, workaround for <http://bugs.webkit.org/show_bug.cgi?id=6289>. if (!start() && !length() && it.atEnd()) { resultRange->setStart(it.currentContainer(), 0, ASSERT_NO_EXCEPTION); resultRange->setEnd(it.currentContainer(), 0, ASSERT_NO_EXCEPTION); return resultRange.release(); } for (; !it.atEnd(); it.advance()) { int len = it.length(); textRunStartPosition = it.startPositionInCurrentContainer(); textRunEndPosition = it.endPositionInCurrentContainer(); bool foundStart = start() >= docTextPosition && start() <= docTextPosition + len; bool foundEnd = end() >= docTextPosition && end() <= docTextPosition + len; // Fix textRunRange->endPosition(), but only if foundStart || foundEnd, because it is only // in those cases that textRunRange is used. if (foundEnd) { // FIXME: This is a workaround for the fact that the end of a run // is often at the wrong position for emitted '\n's or if the // renderer of the current node is a replaced element. if (len == 1 && (it.characterAt(0) == '\n' || it.isInsideReplacedElement())) { it.advance(); if (!it.atEnd()) { textRunEndPosition = it.startPositionInCurrentContainer(); } else { Position runEnd = VisiblePosition(textRunStartPosition).next().deepEquivalent(); if (runEnd.isNotNull()) textRunEndPosition = createLegacyEditingPosition(runEnd.containerNode(), runEnd.computeOffsetInContainerNode()); } } } if (foundStart) { startRangeFound = true; if (textRunStartPosition.containerNode()->isTextNode()) { int offset = start() - docTextPosition; resultRange->setStart(textRunStartPosition.containerNode(), offset + textRunStartPosition.offsetInContainerNode(), IGNORE_EXCEPTION); } else { if (start() == docTextPosition) resultRange->setStart(textRunStartPosition.containerNode(), textRunStartPosition.offsetInContainerNode(), IGNORE_EXCEPTION); else resultRange->setStart(textRunEndPosition.containerNode(), textRunEndPosition.offsetInContainerNode(), IGNORE_EXCEPTION); } } if (foundEnd) { if (textRunStartPosition.containerNode()->isTextNode()) { int offset = end() - docTextPosition; resultRange->setEnd(textRunStartPosition.containerNode(), offset + textRunStartPosition.offsetInContainerNode(), IGNORE_EXCEPTION); } else { if (end() == docTextPosition) resultRange->setEnd(textRunStartPosition.containerNode(), textRunStartPosition.offsetInContainerNode(), IGNORE_EXCEPTION); else resultRange->setEnd(textRunEndPosition.containerNode(), textRunEndPosition.offsetInContainerNode(), IGNORE_EXCEPTION); } docTextPosition += len; break; } docTextPosition += len; } if (!startRangeFound) return nullptr; if (length() && end() > docTextPosition) { // end() is out of bounds resultRange->setEnd(textRunEndPosition.containerNode(), textRunEndPosition.offsetInContainerNode(), IGNORE_EXCEPTION); } return resultRange.release(); }