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; }
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)); }
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)); }
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()); }
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; }
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); }
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(); }