HeapVector<Member<Element>> TreeScope::elementsFromHitTestResult( HitTestResult& result) const { HeapVector<Member<Element>> elements; Node* lastNode = nullptr; for (const auto rectBasedNode : result.listBasedTestResult()) { Node* node = rectBasedNode.get(); if (!node || !node->isElementNode() || node->isDocumentNode()) continue; if (node->isPseudoElement() || node->isTextNode()) node = node->parentOrShadowHostNode(); node = ancestorInThisScope(node); // Prune duplicate entries. A pseduo ::before content above its parent // node should only result in a single entry. if (node == lastNode) continue; if (node && node->isElementNode()) { elements.append(toElement(node)); lastNode = node; } } if (rootNode().isDocumentNode()) { if (Element* rootElement = toDocument(rootNode()).documentElement()) { if (elements.isEmpty() || elements.last() != rootElement) elements.append(rootElement); } } return elements; }
const TreeScope* TreeScope::commonAncestorTreeScope(const TreeScope& other) const { HeapVector<Member<const TreeScope>, 16> thisChain; for (const TreeScope* tree = this; tree; tree = tree->parentTreeScope()) thisChain.append(tree); HeapVector<Member<const TreeScope>, 16> otherChain; for (const TreeScope* tree = &other; tree; tree = tree->parentTreeScope()) otherChain.append(tree); // Keep popping out the last elements of these chains until a mismatched pair is found. If |this| and |other| // belong to different documents, null will be returned. const TreeScope* lastAncestor = nullptr; while (!thisChain.isEmpty() && !otherChain.isEmpty() && thisChain.last() == otherChain.last()) { lastAncestor = thisChain.last(); thisChain.removeLast(); otherChain.removeLast(); } return lastAncestor; }
TEST_F(CustomElementRegistryTest, collectCandidates_shouldNotIncludeElementsRemovedFromDocument) { Element* element = CreateElement("a-a").inDocument(&document()); registry().addCandidate(element); HeapVector<Member<Element>> elements; collectCandidates(CustomElementDescriptor("a-a", "a-a"), &elements); EXPECT_TRUE(elements.isEmpty()) << "no candidates should have been found, but we have " << elements.size(); EXPECT_FALSE(elements.contains(element)) << "the out-of-document candidate should not have been found"; }
TEST_F(CustomElementRegistryTest, collectCandidates_shouldNotIncludeElementsInDifferentDocument) { Element* element = CreateElement("a-a").inDocument(&document()); registry().addCandidate(element); Document* otherDocument = HTMLDocument::create(); otherDocument->appendChild(element); EXPECT_EQ(otherDocument, element->ownerDocument()) << "sanity: another document should have adopted an element on append"; HeapVector<Member<Element>> elements; collectCandidates(CustomElementDescriptor("a-a", "a-a"), &elements); EXPECT_TRUE(elements.isEmpty()) << "no candidates should have been found, but we have " << elements.size(); EXPECT_FALSE(elements.contains(element)) << "the adopted-away candidate should not have been found"; }
void HTMLFormControlsCollection::namedGetter( const AtomicString& name, RadioNodeListOrElement& returnValue) { HeapVector<Member<Element>> namedItems; this->namedItems(name, namedItems); if (namedItems.isEmpty()) return; if (namedItems.size() == 1) { if (!isHTMLImageElement(*namedItems[0])) returnValue.setElement(namedItems.at(0)); return; } // This path never returns a RadioNodeList for <img> because // onlyMatchingImgElements flag is false by default. returnValue.setRadioNodeList(ownerNode().radioNodeList(name)); }
void HTMLFormattingElementList::ensureNoahsArkCondition( HTMLStackItem* newItem) { HeapVector<Member<HTMLStackItem>> candidates; tryToEnsureNoahsArkConditionQuickly(newItem, candidates); if (candidates.isEmpty()) return; // We pre-allocate and re-use this second vector to save one malloc per // attribute that we verify. HeapVector<Member<HTMLStackItem>> remainingCandidates; remainingCandidates.reserveInitialCapacity(candidates.size()); for (const auto& attribute : newItem->attributes()) { for (const auto& candidate : candidates) { // These properties should already have been checked by // tryToEnsureNoahsArkConditionQuickly. ASSERT(newItem->attributes().size() == candidate->attributes().size()); ASSERT(newItem->localName() == candidate->localName() && newItem->namespaceURI() == candidate->namespaceURI()); Attribute* candidateAttribute = candidate->getAttributeItem(attribute.name()); if (candidateAttribute && candidateAttribute->value() == attribute.value()) remainingCandidates.append(candidate); } if (remainingCandidates.size() < kNoahsArkCapacity) return; candidates.swap(remainingCandidates); remainingCandidates.shrink(0); } // Inductively, we shouldn't spin this loop very many times. It's possible, // however, that we wil spin the loop more than once because of how the // formatting element list gets permuted. for (size_t i = kNoahsArkCapacity - 1; i < candidates.size(); ++i) remove(candidates[i]->element()); }
MHTMLArchive* MHTMLArchive::create(const KURL& url, SharedBuffer* data) { // For security reasons we only load MHTML pages from local URLs. if (!SchemeRegistry::shouldTreatURLSchemeAsLocal(url.protocol())) return nullptr; MHTMLParser parser(data); HeapVector<Member<ArchiveResource>> resources = parser.parseArchive(); if (resources.isEmpty()) return nullptr; // Invalid MHTML file. MHTMLArchive* archive = new MHTMLArchive; // The first document suitable resource is the main resource of the top frame. for (size_t i = 0; i < resources.size(); ++i) { const AtomicString& mimeType = resources[i]->mimeType(); if (archive->mainResource() || !MIMETypeRegistry::isSupportedNonImageMIMEType(mimeType) || MIMETypeRegistry::isSupportedJavaScriptMIMEType(mimeType) || mimeType == "text/css") archive->addSubresource(resources[i].get()); else archive->setMainResource(resources[i].get()); } return archive; }
void HTMLFormattingElementList::tryToEnsureNoahsArkConditionQuickly( HTMLStackItem* newItem, HeapVector<Member<HTMLStackItem>>& remainingCandidates) { ASSERT(remainingCandidates.isEmpty()); if (m_entries.size() < kNoahsArkCapacity) return; // Use a vector with inline capacity to avoid a malloc in the common case of a // quickly ensuring the condition. HeapVector<Member<HTMLStackItem>, 10> candidates; size_t newItemAttributeCount = newItem->attributes().size(); for (size_t i = m_entries.size(); i;) { --i; Entry& entry = m_entries[i]; if (entry.isMarker()) break; // Quickly reject obviously non-matching candidates. HTMLStackItem* candidate = entry.stackItem(); if (newItem->localName() != candidate->localName() || newItem->namespaceURI() != candidate->namespaceURI()) continue; if (candidate->attributes().size() != newItemAttributeCount) continue; candidates.append(candidate); } // There's room for the new element in the ark. There's no need to copy out // the remainingCandidates. if (candidates.size() < kNoahsArkCapacity) return; remainingCandidates.appendVector(candidates); }
void BreakBlockquoteCommand::doApply(EditingState* editingState) { if (endingSelection().isNone()) return; // Delete the current selection. if (endingSelection().isRange()) { deleteSelection(editingState, false, false); if (editingState->isAborted()) return; } // This is a scenario that should never happen, but we want to // make sure we don't dereference a null pointer below. DCHECK(!endingSelection().isNone()); if (endingSelection().isNone()) return; VisiblePosition visiblePos = endingSelection().visibleStart(); // pos is a position equivalent to the caret. We use downstream() so that pos will // be in the first node that we need to move (there are a few exceptions to this, see below). Position pos = mostForwardCaretPosition(endingSelection().start()); // Find the top-most blockquote from the start. HTMLQuoteElement* topBlockquote = toHTMLQuoteElement(highestEnclosingNodeOfType(pos, isMailHTMLBlockquoteElement)); if (!topBlockquote || !topBlockquote->parentNode()) return; HTMLBRElement* breakElement = HTMLBRElement::create(document()); bool isLastVisPosInNode = isLastVisiblePositionInNode(visiblePos, topBlockquote); // If the position is at the beginning of the top quoted content, we don't need to break the quote. // Instead, insert the break before the blockquote, unless the position is as the end of the the quoted content. if (isFirstVisiblePositionInNode(visiblePos, topBlockquote) && !isLastVisPosInNode) { insertNodeBefore(breakElement, topBlockquote, editingState); if (editingState->isAborted()) return; setEndingSelection(VisibleSelection(Position::beforeNode(breakElement), TextAffinity::Downstream, endingSelection().isDirectional())); rebalanceWhitespace(); return; } // Insert a break after the top blockquote. insertNodeAfter(breakElement, topBlockquote, editingState); if (editingState->isAborted()) return; // If we're inserting the break at the end of the quoted content, we don't need to break the quote. if (isLastVisPosInNode) { setEndingSelection(VisibleSelection(Position::beforeNode(breakElement), TextAffinity::Downstream, endingSelection().isDirectional())); rebalanceWhitespace(); return; } // Don't move a line break just after the caret. Doing so would create an extra, empty paragraph // in the new blockquote. if (lineBreakExistsAtVisiblePosition(visiblePos)) { pos = nextPositionOf(pos, PositionMoveType::GraphemeCluster); } // Adjust the position so we don't split at the beginning of a quote. while (isFirstVisiblePositionInNode(createVisiblePosition(pos), toHTMLQuoteElement(enclosingNodeOfType(pos, isMailHTMLBlockquoteElement)))) { pos = previousPositionOf(pos, PositionMoveType::GraphemeCluster); } // startNode is the first node that we need to move to the new blockquote. Node* startNode = pos.anchorNode(); DCHECK(startNode); // Split at pos if in the middle of a text node. if (startNode->isTextNode()) { Text* textNode = toText(startNode); int textOffset = pos.computeOffsetInContainerNode(); if ((unsigned)textOffset >= textNode->length()) { startNode = NodeTraversal::next(*startNode); DCHECK(startNode); } else if (textOffset > 0) { splitTextNode(textNode, textOffset); } } else if (pos.computeEditingOffset() > 0) { Node* childAtOffset = NodeTraversal::childAt(*startNode, pos.computeEditingOffset()); startNode = childAtOffset ? childAtOffset : NodeTraversal::next(*startNode); DCHECK(startNode); } // If there's nothing inside topBlockquote to move, we're finished. if (!startNode->isDescendantOf(topBlockquote)) { setEndingSelection(VisibleSelection(createVisiblePosition(firstPositionInOrBeforeNode(startNode)), endingSelection().isDirectional())); return; } // Build up list of ancestors in between the start node and the top blockquote. HeapVector<Member<Element>> ancestors; for (Element* node = startNode->parentElement(); node && node != topBlockquote; node = node->parentElement()) ancestors.append(node); // Insert a clone of the top blockquote after the break. Element* clonedBlockquote = topBlockquote->cloneElementWithoutChildren(); insertNodeAfter(clonedBlockquote, breakElement, editingState); if (editingState->isAborted()) return; // Clone startNode's ancestors into the cloned blockquote. // On exiting this loop, clonedAncestor is the lowest ancestor // that was cloned (i.e. the clone of either ancestors.last() // or clonedBlockquote if ancestors is empty). Element* clonedAncestor = clonedBlockquote; for (size_t i = ancestors.size(); i != 0; --i) { Element* clonedChild = ancestors[i - 1]->cloneElementWithoutChildren(); // Preserve list item numbering in cloned lists. if (isHTMLOListElement(*clonedChild)) { Node* listChildNode = i > 1 ? ancestors[i - 2].get() : startNode; // The first child of the cloned list might not be a list item element, // find the first one so that we know where to start numbering. while (listChildNode && !isHTMLLIElement(*listChildNode)) listChildNode = listChildNode->nextSibling(); if (isListItem(listChildNode)) setNodeAttribute(clonedChild, startAttr, AtomicString::number(toLayoutListItem(listChildNode->layoutObject())->value())); } appendNode(clonedChild, clonedAncestor, editingState); if (editingState->isAborted()) return; clonedAncestor = clonedChild; } moveRemainingSiblingsToNewParent(startNode, 0, clonedAncestor, editingState); if (editingState->isAborted()) return; if (!ancestors.isEmpty()) { // Split the tree up the ancestor chain until the topBlockquote // Throughout this loop, clonedParent is the clone of ancestor's parent. // This is so we can clone ancestor's siblings and place the clones // into the clone corresponding to the ancestor's parent. Element* ancestor = nullptr; Element* clonedParent = nullptr; for (ancestor = ancestors.first(), clonedParent = clonedAncestor->parentElement(); ancestor && ancestor != topBlockquote; ancestor = ancestor->parentElement(), clonedParent = clonedParent->parentElement()) { moveRemainingSiblingsToNewParent(ancestor->nextSibling(), 0, clonedParent, editingState); if (editingState->isAborted()) return; } // If the startNode's original parent is now empty, remove it Element* originalParent = ancestors.first().get(); if (!originalParent->hasChildren()) { removeNode(originalParent, editingState); if (editingState->isAborted()) return; } } // Make sure the cloned block quote renders. addBlockPlaceholderIfNeeded(clonedBlockquote, editingState); if (editingState->isAborted()) return; // Put the selection right before the break. setEndingSelection(VisibleSelection(Position::beforeNode(breakElement), TextAffinity::Downstream, endingSelection().isDirectional())); rebalanceWhitespace(); }
Node* StyledMarkupTraverser<Strategy>::traverse(Node* startNode, Node* pastEnd) { HeapVector<Member<ContainerNode>> ancestorsToClose; Node* next; Node* lastClosed = nullptr; for (Node* n = startNode; n && n != pastEnd; n = next) { // If |n| is a selection boundary such as <input>, traverse the child // nodes in the DOM tree instead of the flat tree. if (handleSelectionBoundary<Strategy>(*n)) { lastClosed = StyledMarkupTraverser<EditingStrategy>(m_accumulator, m_lastClosed.get()).traverse(n, EditingStrategy::nextSkippingChildren(*n)); next = EditingInFlatTreeStrategy::nextSkippingChildren(*n); } else { next = Strategy::next(*n); if (isEnclosingBlock(n) && canHaveChildrenForEditing(n) && next == pastEnd) { // Don't write out empty block containers that aren't fully selected. continue; } if (!n->layoutObject() && !enclosingElementWithTag(firstPositionInOrBeforeNode(n), selectTag)) { next = Strategy::nextSkippingChildren(*n); // Don't skip over pastEnd. if (pastEnd && Strategy::isDescendantOf(*pastEnd, *n)) next = pastEnd; } else { // Add the node to the markup if we're not skipping the descendants appendStartMarkup(*n); // If node has no children, close the tag now. if (Strategy::hasChildren(*n)) { ancestorsToClose.append(toContainerNode(n)); continue; } appendEndMarkup(*n); lastClosed = n; } } // If we didn't insert open tag and there's no more siblings or we're at the end of the traversal, take care of ancestors. // FIXME: What happens if we just inserted open tag and reached the end? if (Strategy::nextSibling(*n) && next != pastEnd) continue; // Close up the ancestors. while (!ancestorsToClose.isEmpty()) { ContainerNode* ancestor = ancestorsToClose.last(); ASSERT(ancestor); if (next && next != pastEnd && Strategy::isDescendantOf(*next, *ancestor)) break; // Not at the end of the range, close ancestors up to sibling of next node. appendEndMarkup(*ancestor); lastClosed = ancestor; ancestorsToClose.removeLast(); } // Surround the currently accumulated markup with markup for ancestors we never opened as we leave the subtree(s) rooted at those ancestors. ContainerNode* nextParent = next ? Strategy::parent(*next) : nullptr; if (next == pastEnd || n == nextParent) continue; ASSERT(n); Node* lastAncestorClosedOrSelf = (lastClosed && Strategy::isDescendantOf(*n, *lastClosed)) ? lastClosed : n; for (ContainerNode* parent = Strategy::parent(*lastAncestorClosedOrSelf); parent && parent != nextParent; parent = Strategy::parent(*parent)) { // All ancestors that aren't in the ancestorsToClose list should either be a) unrendered: if (!parent->layoutObject()) continue; // or b) ancestors that we never encountered during a pre-order traversal starting at startNode: ASSERT(startNode); ASSERT(Strategy::isDescendantOf(*startNode, *parent)); RawPtr<EditingStyle> style = createInlineStyleIfNeeded(*parent); wrapWithNode(*parent, style); lastClosed = parent; } } return lastClosed; }
void VTTParser::getNewRegions(HeapVector<Member<VTTRegion>>& outputRegions) { ASSERT(outputRegions.isEmpty()); outputRegions.swap(m_regionList); }
void VTTParser::getNewCues(HeapVector<Member<TextTrackCue>>& outputCues) { ASSERT(outputCues.isEmpty()); outputCues.swap(m_cueList); }