Exemple #1
0
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;
}
Exemple #3
0
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";
}
Exemple #4
0
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";
}
Exemple #5
0
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));
}
Exemple #6
0
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());
}
Exemple #7
0
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;
}
Exemple #8
0
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);
}