示例#1
0
SmartClipData SmartClip::dataForRect(const IntRect& cropRect)
{
    IntRect resizedCropRect = applyScaleWithoutCollapsingToZero(cropRect, 1 / pageScaleFactor());

    Node* bestNode = findBestOverlappingNode(m_frame->document(), resizedCropRect);
    if (!bestNode)
        return SmartClipData();

    if (Node* nodeFromFrame = nodeInsideFrame(bestNode)) {
        // FIXME: This code only hit-tests a single iframe. It seems like we ought support nested frames.
        if (Node* bestNodeInFrame = findBestOverlappingNode(nodeFromFrame, resizedCropRect))
            bestNode = bestNodeInFrame;
    }

    WillBeHeapVector<RawPtrWillBeMember<Node> > hitNodes;
    collectOverlappingChildNodes(bestNode, resizedCropRect, hitNodes);

    if (hitNodes.isEmpty() || hitNodes.size() == bestNode->countChildren()) {
        hitNodes.clear();
        hitNodes.append(bestNode);
    }

    // Unite won't work with the empty rect, so we initialize to the first rect.
    IntRect unitedRects = hitNodes[0]->pixelSnappedBoundingBox();
    StringBuilder collectedText;
    for (size_t i = 0; i < hitNodes.size(); ++i) {
        collectedText.append(extractTextFromNode(hitNodes[i]));
        unitedRects.unite(hitNodes[i]->pixelSnappedBoundingBox());
    }

    return SmartClipData(bestNode, convertRectToWindow(unitedRects), collectedText.toString());
}
void HTMLFormattingElementList::tryToEnsureNoahsArkConditionQuickly(HTMLStackItem* newItem, WillBeHeapVector<RawPtrWillBeMember<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.
    WillBeHeapVector<RawPtrWillBeMember<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().get();
        if (newItem->localName() != candidate->localName() || newItem->namespaceURI() != candidate->namespaceURI())
            continue;
        if (candidate->attributes().size() != newItemAttributeCount)
            continue;

        candidates.append(candidate);
    }

    if (candidates.size() < kNoahsArkCapacity)
        return; // There's room for the new element in the ark. There's no need to copy out the remainingCandidates.

    remainingCandidates.appendVector(candidates);
}
TreeScopeStyleSheetCollection::StyleResolverUpdateType TreeScopeStyleSheetCollection::compareStyleSheets(const WillBeHeapVector<RefPtrWillBeMember<CSSStyleSheet>>& oldStyleSheets, const WillBeHeapVector<RefPtrWillBeMember<CSSStyleSheet>>& newStylesheets, WillBeHeapVector<RawPtrWillBeMember<StyleSheetContents>>& addedSheets)
{
    unsigned newStyleSheetCount = newStylesheets.size();
    unsigned oldStyleSheetCount = oldStyleSheets.size();
    ASSERT(newStyleSheetCount >= oldStyleSheetCount);

    if (!newStyleSheetCount)
        return Reconstruct;

    unsigned newIndex = 0;
    for (unsigned oldIndex = 0; oldIndex < oldStyleSheetCount; ++oldIndex) {
        while (oldStyleSheets[oldIndex] != newStylesheets[newIndex]) {
            addedSheets.append(newStylesheets[newIndex]->contents());
            if (++newIndex == newStyleSheetCount)
                return Reconstruct;
        }
        if (++newIndex == newStyleSheetCount)
            return Reconstruct;
    }
    bool hasInsertions = !addedSheets.isEmpty();
    while (newIndex < newStyleSheetCount) {
        addedSheets.append(newStylesheets[newIndex]->contents());
        ++newIndex;
    }
    // If all new sheets were added at the end of the list we can just add them to existing StyleResolver.
    // If there were insertions we need to re-add all the stylesheets so rules are ordered correctly.
    return hasInsertions ? Reset : Additive;
}
bool AnimatableRepeatable::interpolateLists(const WillBeHeapVector<RefPtrWillBeMember<AnimatableValue>>& fromValues, const WillBeHeapVector<RefPtrWillBeMember<AnimatableValue>>& toValues, double fraction, WillBeHeapVector<RefPtrWillBeMember<AnimatableValue>>& interpolatedValues)
{
    // Interpolation behaviour spec: http://www.w3.org/TR/css3-transitions/#animtype-repeatable-list
    ASSERT(interpolatedValues.isEmpty());
    ASSERT(!fromValues.isEmpty() && !toValues.isEmpty());
    size_t size = lowestCommonMultiple(fromValues.size(), toValues.size());
    ASSERT(size > 0);
    for (size_t i = 0; i < size; ++i) {
        const AnimatableValue* from = fromValues[i % fromValues.size()].get();
        const AnimatableValue* to = toValues[i % toValues.size()].get();
        // Spec: If a pair of values cannot be interpolated, then the lists are not interpolable.
        if (AnimatableValue::usesDefaultInterpolation(from, to))
            return false;
        interpolatedValues.append(interpolate(from, to, fraction));
    }
    return true;
}
示例#5
0
void EventPath::calculatePath()
{
    ASSERT(m_node);
    ASSERT(m_nodeEventContexts.isEmpty());
    m_node->updateDistribution();

    // For performance and memory usage reasons we want to store the
    // path using as few bytes as possible and with as few allocations
    // as possible which is why we gather the data on the stack before
    // storing it in a perfectly sized m_nodeEventContexts Vector.
    WillBeHeapVector<RawPtrWillBeMember<Node>, 64> nodesInPath;
    Node* current = m_node;
    nodesInPath.append(current);
    while (current) {
        if (m_event && current->keepEventInNode(m_event))
            break;
        WillBeHeapVector<RawPtrWillBeMember<InsertionPoint>, 8> insertionPoints;
        collectDestinationInsertionPoints(*current, insertionPoints);
        if (!insertionPoints.isEmpty()) {
            for (const auto& insertionPoint : insertionPoints) {
                if (insertionPoint->isShadowInsertionPoint()) {
                    ShadowRoot* containingShadowRoot = insertionPoint->containingShadowRoot();
                    ASSERT(containingShadowRoot);
                    if (!containingShadowRoot->isOldest())
                        nodesInPath.append(containingShadowRoot->olderShadowRoot());
                }
                nodesInPath.append(insertionPoint);
            }
            current = insertionPoints.last();
            continue;
        }
        if (current->isShadowRoot()) {
            if (m_event && shouldStopAtShadowRoot(*m_event, *toShadowRoot(current), *m_node))
                break;
            current = current->shadowHost();
#if !ENABLE(OILPAN)
            // TODO(kochi): crbug.com/507413 This check is necessary when some asynchronous event
            // is queued while its shadow host is removed and the shadow root gets the event
            // immediately after it.  When Oilpan is enabled, this situation does not happen.
            // Except this case, shadow root's host is assumed to be non-null.
            if (current)
                nodesInPath.append(current);
#else
            nodesInPath.append(current);
#endif
        } else {
            current = current->parentNode();
            if (current)
                nodesInPath.append(current);
        }
    }

    m_nodeEventContexts.reserveCapacity(nodesInPath.size());
    for (Node* nodeInPath : nodesInPath) {
        m_nodeEventContexts.append(NodeEventContext(nodeInPath, eventTargetRespectingTargetRules(*nodeInPath)));
    }
}
const TreeScope* TreeScope::commonAncestorTreeScope(const TreeScope& other) const
{
    WillBeHeapVector<RawPtrWillBeMember<const TreeScope>, 16> thisChain;
    for (const TreeScope* tree = this; tree; tree = tree->parentTreeScope())
        thisChain.append(tree);

    WillBeHeapVector<RawPtrWillBeMember<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;
}
Element* HTMLCollection::namedItem(const AtomicString& name) const
{
    // http://msdn.microsoft.com/workshop/author/dhtml/reference/methods/nameditem.asp
    // This method first searches for an object with a matching id
    // attribute. If a match is not found, the method then searches for an
    // object with a matching name attribute, but only on those elements
    // that are allowed a name attribute.
    updateIdNameCache();

    const NamedItemCache& cache = namedItemCache();
    WillBeHeapVector<RawPtrWillBeMember<Element>>* idResults = cache.getElementsById(name);
    if (idResults && !idResults->isEmpty())
        return idResults->first();

    WillBeHeapVector<RawPtrWillBeMember<Element>>* nameResults = cache.getElementsByName(name);
    if (nameResults && !nameResults->isEmpty())
        return nameResults->first();

    return nullptr;
}
PassRefPtrWillBeRawPtr<AnimatableValue> AnimatableStrokeDasharrayList::interpolateTo(const AnimatableValue* value, double fraction) const
{
    WillBeHeapVector<RefPtrWillBeMember<AnimatableValue> > from = m_values;
    WillBeHeapVector<RefPtrWillBeMember<AnimatableValue> > to = toAnimatableStrokeDasharrayList(value)->m_values;

    // The spec states that if the sum of all values is zero, this should be
    // treated like a value of 'none', which means that a solid line is drawn.
    // Since we animate to and from values of zero, treat a value of 'none' the
    // same. If both the two and from values are 'none', we return 'none'
    // rather than '0 0'.
    if (from.isEmpty() && to.isEmpty())
        return takeConstRef(this);
    if (from.isEmpty() || to.isEmpty()) {
        DEFINE_STATIC_REF_WILL_BE_PERSISTENT(AnimatableSVGLength, zeroPixels, (AnimatableSVGLength::create(SVGLength::create())));
        if (from.isEmpty()) {
            from.append(zeroPixels);
            from.append(zeroPixels);
        }
        if (to.isEmpty()) {
            to.append(zeroPixels);
            to.append(zeroPixels);
        }
    }

    WillBeHeapVector<RefPtrWillBeMember<AnimatableValue> > interpolatedValues;
    bool success = interpolateLists(from, to, fraction, interpolatedValues);
    ASSERT_UNUSED(success, success);
    return adoptRefWillBeNoop(new AnimatableStrokeDasharrayList(interpolatedValues));
}
void HTMLFormElement::getNamedElements(const AtomicString& name, WillBeHeapVector<RefPtrWillBeMember<Element>>& namedItems)
{
    // http://www.whatwg.org/specs/web-apps/current-work/multipage/forms.html#dom-form-nameditem
    elements()->namedItems(name, namedItems);

    Element* elementFromPast = elementFromPastNamesMap(name);
    if (namedItems.size() && namedItems.first() != elementFromPast) {
        addToPastNamesMap(namedItems.first().get(), name);
    } else if (elementFromPast && namedItems.isEmpty()) {
        namedItems.append(elementFromPast);
        UseCounter::count(document(), UseCounter::FormNameAccessForPastNamesMap);
    }
}
void HTMLFormElement::anonymousNamedGetter(const AtomicString& name, RadioNodeListOrElement& returnValue)
{
    // Call getNamedElements twice, first time check if it has a value
    // and let HTMLFormElement update its cache.
    // See issue: 867404
    {
        WillBeHeapVector<RefPtrWillBeMember<Element>> elements;
        getNamedElements(name, elements);
        if (elements.isEmpty())
            return;
    }

    // Second call may return different results from the first call,
    // but if the first the size cannot be zero.
    WillBeHeapVector<RefPtrWillBeMember<Element>> elements;
    getNamedElements(name, elements);
    ASSERT(!elements.isEmpty());

    bool onlyMatchImg = !elements.isEmpty() && isHTMLImageElement(*elements.first());
    if (onlyMatchImg) {
        UseCounter::count(document(), UseCounter::FormNameAccessForImageElement);
        // The following code has performance impact, but it should be small
        // because <img> access via <form> name getter is rarely used.
        for (auto& element : elements) {
            if (isHTMLImageElement(*element) && !element->isDescendantOf(this)) {
                UseCounter::count(document(), UseCounter::FormNameAccessForNonDescendantImageElement);
                break;
            }
        }
    }
    if (elements.size() == 1) {
        returnValue.setElement(elements.at(0));
        return;
    }

    returnValue.setRadioNodeList(radioNodeList(name, onlyMatchImg));
}
示例#11
0
void PageRuleCollector::matchPageRules(RuleSet* rules)
{
    if (!rules)
        return;

    rules->compactRulesIfNeeded();
    WillBeHeapVector<RawPtrWillBeMember<StyleRulePage> > matchedPageRules;
    matchPageRulesForList(matchedPageRules, rules->pageRules(), m_isLeftPage, m_isFirstPage, m_pageName);
    if (matchedPageRules.isEmpty())
        return;

    std::stable_sort(matchedPageRules.begin(), matchedPageRules.end(), comparePageRules);

    for (unsigned i = 0; i < matchedPageRules.size(); i++)
        m_result.addMatchedProperties(&matchedPageRules[i]->properties());
}
void HTMLCollection::namedItems(const AtomicString& name, WillBeHeapVector<RefPtrWillBeMember<Element>>& result) const
{
    ASSERT(result.isEmpty());
    if (name.isEmpty())
        return;

    updateIdNameCache();

    const NamedItemCache& cache = namedItemCache();
    if (WillBeHeapVector<RawPtrWillBeMember<Element>>* idResults = cache.getElementsById(name)) {
        for (unsigned i = 0; i < idResults->size(); ++i)
            result.append(idResults->at(i));
    }
    if (WillBeHeapVector<RawPtrWillBeMember<Element>>* nameResults = cache.getElementsByName(name)) {
        for (unsigned i = 0; i < nameResults->size(); ++i)
            result.append(nameResults->at(i));
    }
}
WillBeHeapVector<RawPtrWillBeMember<Element>> TreeScope::elementsFromPoint(int x, int y) const
{
    WillBeHeapVector<RawPtrWillBeMember<Element>> elements;

    Document& document = rootNode().document();
    IntPoint hitPoint(x, y);
    if (!pointWithScrollAndZoomIfPossible(document, hitPoint))
        return elements;

    HitTestRequest request(HitTestRequest::ReadOnly | HitTestRequest::Active | HitTestRequest::ListBased | HitTestRequest::PenetratingList);
    HitTestResult result(request, hitPoint);
    document.layoutView()->hitTest(result);

    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;
}
void HTMLFormControlsCollection::namedGetter(const AtomicString& name, RadioNodeListOrElement& returnValue)
{
    WillBeHeapVector<RefPtrWillBeMember<Element>> namedItems;
    this->namedItems(name, namedItems);

    if (namedItems.isEmpty())
        return;

    if (namedItems.size() == 1) {
        if (isHTMLImageElement(*namedItems[0]))
            UseCounter::count(document(), UseCounter::FormControlsCollectionNameAccessForImageElement);
        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));
}
示例#15
0
const StyleRuleKeyframes* CSSAnimations::matchScopedKeyframesRule(StyleResolver* resolver, const Element* element, const StringImpl* animationName)
{
    // FIXME: This is all implementation detail of style resolver, CSSAnimations shouldn't be reaching into any of it.
    if (element->document().styleEngine()->onlyDocumentHasStyles()) {
        if (ScopedStyleResolver* resolver = element->document().scopedStyleResolver())
            return resolver->keyframeStylesForAnimation(animationName);
        return nullptr;
    }

    WillBeHeapVector<RawPtrWillBeMember<ScopedStyleResolver>, 8> stack;
    resolver->styleTreeResolveScopedKeyframesRules(element, stack);
    if (stack.isEmpty())
        return nullptr;

    for (size_t i = 0; i < stack.size(); ++i) {
        if (const StyleRuleKeyframes* keyframesRule = stack.at(i)->keyframeStylesForAnimation(animationName))
            return keyframesRule;
    }
    return nullptr;
}
void HTMLFormattingElementList::ensureNoahsArkCondition(HTMLStackItem* newItem)
{
    WillBeHeapVector<RawPtrWillBeMember<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.
    WillBeHeapVector<RawPtrWillBeMember<HTMLStackItem> > remainingCandidates;
    remainingCandidates.reserveInitialCapacity(candidates.size());

    const Vector<Attribute>& attributes = newItem->attributes();
    for (size_t i = 0; i < attributes.size(); ++i) {
        const Attribute& attribute = attributes[i];

        for (size_t j = 0; j < candidates.size(); ++j) {
            HTMLStackItem* candidate = candidates[j];

            // 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());
}
示例#17
0
void EventPath::calculatePath()
{
    ASSERT(m_node);
    ASSERT(m_nodeEventContexts.isEmpty());
    m_node->document().updateDistributionForNodeIfNeeded(const_cast<Node*>(m_node.get()));

    Node* current = m_node;
    addNodeEventContext(*current);
    if (!m_node->inDocument())
        return;
    while (current) {
        if (m_event && current->keepEventInNode(m_event))
            break;
        WillBeHeapVector<RawPtrWillBeMember<InsertionPoint>, 8> insertionPoints;
        collectDestinationInsertionPoints(*current, insertionPoints);
        if (!insertionPoints.isEmpty()) {
            for (const auto& insertionPoint : insertionPoints) {
                if (insertionPoint->isShadowInsertionPoint()) {
                    ShadowRoot* containingShadowRoot = insertionPoint->containingShadowRoot();
                    ASSERT(containingShadowRoot);
                    if (!containingShadowRoot->isOldest())
                        addNodeEventContext(*containingShadowRoot->olderShadowRoot());
                }
                addNodeEventContext(*insertionPoint);
            }
            current = insertionPoints.last();
            continue;
        }
        if (current->isShadowRoot()) {
            if (m_event && shouldStopAtShadowRoot(*m_event, *toShadowRoot(current), *m_node))
                break;
            current = current->shadowHost();
            addNodeEventContext(*current);
        } else {
            current = current->parentNode();
            if (current)
                addNodeEventContext(*current);
        }
    }
}
PassRefPtrWillBeRawPtr<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);
    WillBeHeapVector<RefPtrWillBeMember<ArchiveResource>> resources = parser.parseArchive();
    if (resources.isEmpty())
        return nullptr; // Invalid MHTML file.

    RefPtrWillBeRawPtr<MHTMLArchive> archive = adoptRefWillBeNoop(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.release();
}
bool HTMLFormControlElement::reportValidity()
{
    WillBeHeapVector<RefPtrWillBeMember<HTMLFormControlElement>> unhandledInvalidControls;
    bool isValid = checkValidity(&unhandledInvalidControls, CheckValidityDispatchInvalidEvent);
    if (isValid || unhandledInvalidControls.isEmpty())
        return isValid;
    ASSERT(unhandledInvalidControls.size() == 1);
    ASSERT(unhandledInvalidControls[0].get() == this);
    // Update layout now before calling isFocusable(), which has
    // !layoutObject()->needsLayout() assertion.
    document().updateLayoutIgnorePendingStylesheets();
    if (isFocusable()) {
        showValidationMessage();
        return false;
    }
    if (document().frame()) {
        String message("An invalid form control with name='%name' is not focusable.");
        message.replace("%name", name());
        document().addConsoleMessage(ConsoleMessage::create(RenderingMessageSource, ErrorMessageLevel, message));
    }
    return false;
}
示例#20
0
void SVGAnimateElement::resetAnimatedType()
{
    SVGAnimatedTypeAnimator* animator = ensureAnimator();

    SVGElement* targetElement = this->targetElement();
    const QualifiedName& attributeName = this->attributeName();
    ShouldApplyAnimation shouldApply = shouldApplyAnimation(targetElement, attributeName);

    if (shouldApply == DontApplyAnimation)
        return;

    if (shouldApply == ApplyXMLAnimation) {
        // SVG DOM animVal animation code-path.
        WillBeHeapVector<RawPtrWillBeMember<SVGElement> > animatedElements = findElementInstances(targetElement);
        ASSERT(!animatedElements.isEmpty());

        WillBeHeapVector<RawPtrWillBeMember<SVGElement> >::const_iterator end = animatedElements.end();
        for (WillBeHeapVector<RawPtrWillBeMember<SVGElement> >::const_iterator it = animatedElements.begin(); it != end; ++it)
            document().accessSVGExtensions().addElementReferencingTarget(this, *it);

        if (!m_animatedProperty)
            m_animatedProperty = animator->startAnimValAnimation(animatedElements);
        else
            m_animatedProperty = animator->resetAnimValToBaseVal(animatedElements);

        return;
    }

    // CSS properties animation code-path.
    String baseValue;

    if (shouldApply == ApplyCSSAnimation) {
        ASSERT(SVGAnimationElement::isTargetAttributeCSSProperty(targetElement, attributeName));
        computeCSSPropertyValue(targetElement, cssPropertyID(attributeName.localName()), baseValue);
    }

    m_animatedProperty = animator->constructFromString(baseValue);
}
示例#21
0
void Step::optimize()
{
    // Evaluate predicates as part of node test if possible to avoid building
    // unnecessary NodeSets.
    // E.g., there is no need to build a set of all "foo" nodes to evaluate
    // "foo[@bar]", we can check the predicate while enumerating.
    // This optimization can be applied to predicates that are not context node
    // list sensitive, or to first predicate that is only context position
    // sensitive, e.g. foo[position() mod 2 = 0].
    WillBeHeapVector<OwnPtrWillBeMember<Predicate> > remainingPredicates;
    for (size_t i = 0; i < m_predicates.size(); ++i) {
        OwnPtrWillBeRawPtr<Predicate> predicate(m_predicates[i].release());
        if ((!predicate->isContextPositionSensitive() || nodeTest().mergedPredicates().isEmpty()) && !predicate->isContextSizeSensitive() && remainingPredicates.isEmpty()) {
            nodeTest().mergedPredicates().append(predicate.release());
        } else {
            remainingPredicates.append(predicate.release());
        }
    }
    swap(remainingPredicates, m_predicates);
}
void BreakBlockquoteCommand::doApply()
{
    if (endingSelection().isNone())
        return;

    // Delete the current selection.
    if (endingSelection().isRange())
        deleteSelection(false, false);

    // This is a scenario that should never happen, but we want to
    // make sure we don't dereference a null pointer below.

    ASSERT(!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;

    RefPtrWillBeRawPtr<HTMLBRElement> breakElement = createBreakElement(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.get(), topBlockquote);
        setEndingSelection(VisibleSelection(positionBeforeNode(breakElement.get()), TextAffinity::Downstream, endingSelection().isDirectional()));
        rebalanceWhitespace();
        return;
    }

    // Insert a break after the top blockquote.
    insertNodeAfter(breakElement.get(), topBlockquote);

    // 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(positionBeforeNode(breakElement.get()), 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)) {
        // TODO(yosin) We should use |PositionMoveType::Character| for
        // |nextPositionOf()| to avoid editing middle of character.
        pos = nextPositionOf(pos, PositionMoveType::CodePoint);
    }

    // Adjust the position so we don't split at the beginning of a quote.
    while (isFirstVisiblePositionInNode(createVisiblePosition(pos), toHTMLQuoteElement(enclosingNodeOfType(pos, isMailHTMLBlockquoteElement)))) {
        // TODO(yosin) We should use |PositionMoveType::Character| for
        // |previousPositionOf()| to avoid editing middle character.
        pos = previousPositionOf(pos, PositionMoveType::CodePoint);
    }

    // startNode is the first node that we need to move to the new blockquote.
    Node* startNode = pos.anchorNode();
    ASSERT(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);
            ASSERT(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);
        ASSERT(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.
    WillBeHeapVector<RefPtrWillBeMember<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.
    RefPtrWillBeRawPtr<Element> clonedBlockquote = topBlockquote->cloneElementWithoutChildren();
    insertNodeAfter(clonedBlockquote.get(), breakElement.get());

    // 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).
    RefPtrWillBeRawPtr<Element> clonedAncestor = clonedBlockquote;
    for (size_t i = ancestors.size(); i != 0; --i) {
        RefPtrWillBeRawPtr<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.get(), clonedAncestor.get());
        clonedAncestor = clonedChild;
    }

    moveRemainingSiblingsToNewParent(startNode, 0, clonedAncestor);

    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.
        RefPtrWillBeRawPtr<Element> ancestor = nullptr;
        RefPtrWillBeRawPtr<Element> clonedParent = nullptr;
        for (ancestor = ancestors.first(), clonedParent = clonedAncestor->parentElement();
            ancestor && ancestor != topBlockquote;
            ancestor = ancestor->parentElement(), clonedParent = clonedParent->parentElement())
            moveRemainingSiblingsToNewParent(ancestor->nextSibling(), 0, clonedParent);

        // If the startNode's original parent is now empty, remove it
        Element* originalParent = ancestors.first().get();
        if (!originalParent->hasChildren())
            removeNode(originalParent);
    }

    // Make sure the cloned block quote renders.
    addBlockPlaceholderIfNeeded(clonedBlockquote.get());

    // Put the selection right before the break.
    setEndingSelection(VisibleSelection(positionBeforeNode(breakElement.get()), TextAffinity::Downstream, endingSelection().isDirectional()));
    rebalanceWhitespace();
}