/* * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009 Apple Inc. All rights * reserved. * Copyright (C) 2008, 2009, 2010, 2011 Google Inc. All rights reserved. * Copyright (C) 2011 Igalia S.L. * Copyright (C) 2011 Motorola Mobility. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "third_party/blink/renderer/core/editing/serializers/styled_markup_serializer.h" #include "base/macros.h" #include "third_party/blink/renderer/core/css/css_property_value_set.h" #include "third_party/blink/renderer/core/dom/document.h" #include "third_party/blink/renderer/core/dom/element.h" #include "third_party/blink/renderer/core/dom/text.h" #include "third_party/blink/renderer/core/editing/editing_style.h" #include "third_party/blink/renderer/core/editing/editing_style_utilities.h" #include "third_party/blink/renderer/core/editing/editing_utilities.h" #include "third_party/blink/renderer/core/editing/ephemeral_range.h" #include "third_party/blink/renderer/core/editing/selection_template.h" #include "third_party/blink/renderer/core/editing/serializers/serialization.h" #include "third_party/blink/renderer/core/editing/visible_position.h" #include "third_party/blink/renderer/core/editing/visible_selection.h" #include "third_party/blink/renderer/core/editing/visible_units.h" #include "third_party/blink/renderer/core/html/html_body_element.h" #include "third_party/blink/renderer/core/html/html_element.h" #include "third_party/blink/renderer/platform/wtf/text/string_builder.h" namespace blink { namespace { template TextOffset ToTextOffset(const PositionTemplate& position) { if (position.IsNull()) return TextOffset(); auto* text_node = DynamicTo(position.ComputeContainerNode()); if (!text_node) return TextOffset(); return TextOffset(text_node, position.OffsetInContainerNode()); } template static bool HandleSelectionBoundary(const Node&); template <> bool HandleSelectionBoundary(const Node&) { return false; } template <> bool HandleSelectionBoundary(const Node& node) { ShadowRoot* root = node.GetShadowRoot(); return root && root->IsUserAgent(); } } // namespace using namespace html_names; template class StyledMarkupTraverser { STACK_ALLOCATED(); public: StyledMarkupTraverser(); StyledMarkupTraverser(StyledMarkupAccumulator*, Node*); Node* Traverse(Node*, Node*); void WrapWithNode(ContainerNode&, EditingStyle*); EditingStyle* CreateInlineStyleIfNeeded(Node&); private: bool ShouldAnnotate() const; bool ShouldConvertBlocksToInlines() const; void AppendStartMarkup(Node&); void AppendEndMarkup(Node&); EditingStyle* CreateInlineStyle(Element&); bool NeedsInlineStyle(const Element&); bool ShouldApplyWrappingStyle(const Node&) const; bool ContainsOnlyBRElement(const Element&) const; StyledMarkupAccumulator* accumulator_; Member last_closed_; Member wrapping_style_; DISALLOW_COPY_AND_ASSIGN(StyledMarkupTraverser); }; template bool StyledMarkupTraverser::ShouldAnnotate() const { return accumulator_->ShouldAnnotate(); } template bool StyledMarkupTraverser::ShouldConvertBlocksToInlines() const { return accumulator_->ShouldConvertBlocksToInlines(); } template StyledMarkupSerializer::StyledMarkupSerializer( const PositionTemplate& start, const PositionTemplate& end, Node* highest_node_to_be_serialized, const CreateMarkupOptions& options) : start_(start), end_(end), highest_node_to_be_serialized_(highest_node_to_be_serialized), options_(options), last_closed_(highest_node_to_be_serialized) {} template static bool NeedInterchangeNewlineAfter( const VisiblePositionTemplate& v) { const VisiblePositionTemplate next = NextPositionOf(v); Node* upstream_node = MostBackwardCaretPosition(next.DeepEquivalent()).AnchorNode(); Node* downstream_node = MostForwardCaretPosition(v.DeepEquivalent()).AnchorNode(); // Add an interchange newline if a paragraph break is selected and a br won't // already be added to the markup to represent it. return IsEndOfParagraph(v) && IsStartOfParagraph(next) && !(IsHTMLBRElement(*upstream_node) && upstream_node == downstream_node); } template static bool NeedInterchangeNewlineAt( const VisiblePositionTemplate& v) { return NeedInterchangeNewlineAfter(PreviousPositionOf(v)); } template static bool AreSameRanges(Node* node, const PositionTemplate& start_position, const PositionTemplate& end_position) { DCHECK(node); const EphemeralRange range = CreateVisibleSelection( SelectionInDOMTree::Builder().SelectAllChildren(*node).Build()) .ToNormalizedEphemeralRange(); return ToPositionInDOMTree(start_position) == range.StartPosition() && ToPositionInDOMTree(end_position) == range.EndPosition(); } static EditingStyle* StyleFromMatchedRulesAndInlineDecl( const HTMLElement* element) { EditingStyle* style = MakeGarbageCollected(element->InlineStyle()); // FIXME: Having to const_cast here is ugly, but it is quite a bit of work to // untangle the non-const-ness of styleFromMatchedRulesForElement. style->MergeStyleFromRules(const_cast(element)); return style; } template String StyledMarkupSerializer::CreateMarkup() { StyledMarkupAccumulator markup_accumulator( ToTextOffset(start_.ParentAnchoredEquivalent()), ToTextOffset(end_.ParentAnchoredEquivalent()), start_.GetDocument(), options_); Node* past_end = end_.NodeAsRangePastLastNode(); Node* first_node = start_.NodeAsRangeFirstNode(); const VisiblePositionTemplate visible_start = CreateVisiblePosition(start_); const VisiblePositionTemplate visible_end = CreateVisiblePosition(end_); if (ShouldAnnotate() && NeedInterchangeNewlineAfter(visible_start)) { markup_accumulator.AppendInterchangeNewline(); if (visible_start.DeepEquivalent() == PreviousPositionOf(visible_end).DeepEquivalent()) return markup_accumulator.TakeResults(); first_node = NextPositionOf(visible_start).DeepEquivalent().AnchorNode(); if (past_end && PositionTemplate::BeforeNode(*first_node) .CompareTo(PositionTemplate::BeforeNode( *past_end)) >= 0) { // This condition hits in editing/pasteboard/copy-display-none.html. return markup_accumulator.TakeResults(); } } // If there is no the highest node in the selected nodes, |last_closed_| can // be #text when its parent is a formatting tag. In this case, #text is // wrapped by tag, but this text should be wrapped by the formatting // tag. See http://crbug.com/634482 bool should_append_parent_tag = false; if (!last_closed_) { last_closed_ = StyledMarkupTraverser().Traverse(first_node, past_end); if (last_closed_ && last_closed_->IsTextNode() && IsPresentationalHTMLElement(last_closed_->parentNode())) { last_closed_ = last_closed_->parentElement(); should_append_parent_tag = true; } } StyledMarkupTraverser traverser(&markup_accumulator, last_closed_); Node* last_closed = traverser.Traverse(first_node, past_end); if (highest_node_to_be_serialized_ && last_closed) { // TODO(hajimehoshi): This is calculated at createMarkupInternal too. Node* common_ancestor = Strategy::CommonAncestor( *start_.ComputeContainerNode(), *end_.ComputeContainerNode()); DCHECK(common_ancestor); HTMLBodyElement* body = ToHTMLBodyElement(EnclosingElementWithTag( Position::FirstPositionInNode(*common_ancestor), kBodyTag)); HTMLBodyElement* fully_selected_root = nullptr; // FIXME: Do this for all fully selected blocks, not just the body. if (body && AreSameRanges(body, start_, end_)) fully_selected_root = body; // Also include all of the ancestors of lastClosed up to this special // ancestor. // FIXME: What is ancestor? for (ContainerNode* ancestor = Strategy::Parent(*last_closed); ancestor; ancestor = Strategy::Parent(*ancestor)) { if (ancestor == fully_selected_root && !markup_accumulator.ShouldConvertBlocksToInlines()) { EditingStyle* fully_selected_root_style = StyleFromMatchedRulesAndInlineDecl(fully_selected_root); // Bring the background attribute over, but not as an attribute because // a background attribute on a div appears to have no effect. if ((!fully_selected_root_style || !fully_selected_root_style->Style() || !fully_selected_root_style->Style()->GetPropertyCSSValue( CSSPropertyID::kBackgroundImage)) && fully_selected_root->hasAttribute(kBackgroundAttr)) { fully_selected_root_style->Style()->SetProperty( CSSPropertyID::kBackgroundImage, "url('" + fully_selected_root->getAttribute(kBackgroundAttr) + "')", /* important */ false, fully_selected_root->GetDocument().GetSecureContextMode()); } if (fully_selected_root_style->Style()) { // Reset the CSS properties to avoid an assertion error in // addStyleMarkup(). This assertion is caused at least when we select // all text of a element whose 'text-decoration' property is // "inherit", and copy it. if (!PropertyMissingOrEqualToNone(fully_selected_root_style->Style(), CSSPropertyID::kTextDecoration)) { fully_selected_root_style->Style()->SetProperty( CSSPropertyID::kTextDecoration, CSSValueID::kNone); } if (!PropertyMissingOrEqualToNone( fully_selected_root_style->Style(), CSSPropertyID::kWebkitTextDecorationsInEffect)) { fully_selected_root_style->Style()->SetProperty( CSSPropertyID::kWebkitTextDecorationsInEffect, CSSValueID::kNone); } markup_accumulator.WrapWithStyleNode( fully_selected_root_style->Style()); } } else { EditingStyle* style = traverser.CreateInlineStyleIfNeeded(*ancestor); // Since this node and all the other ancestors are not in the selection // we want styles that affect the exterior of the node not to be not // included. If the node is not fully selected by the range, then we // don't want to keep styles that affect its relationship to the nodes // around it only the ones that affect it and the nodes within it. if (style && style->Style()) style->Style()->RemoveProperty(CSSPropertyID::kFloat); traverser.WrapWithNode(*ancestor, style); } if (ancestor == highest_node_to_be_serialized_) break; } } else if (should_append_parent_tag) { EditingStyle* style = traverser.CreateInlineStyleIfNeeded(*last_closed_); traverser.WrapWithNode(To(*last_closed_), style); } // FIXME: The interchange newline should be placed in the block that it's in, // not after all of the content, unconditionally. if (!(last_closed && IsHTMLBRElement(*last_closed)) && ShouldAnnotate() && NeedInterchangeNewlineAt(visible_end)) markup_accumulator.AppendInterchangeNewline(); return markup_accumulator.TakeResults(); } template StyledMarkupTraverser::StyledMarkupTraverser() : StyledMarkupTraverser(nullptr, nullptr) {} template StyledMarkupTraverser::StyledMarkupTraverser( StyledMarkupAccumulator* accumulator, Node* last_closed) : accumulator_(accumulator), last_closed_(last_closed), wrapping_style_(nullptr) { if (!accumulator_) { DCHECK_EQ(last_closed_, static_cast(nullptr)); return; } if (!last_closed_) return; ContainerNode* parent = Strategy::Parent(*last_closed_); if (!parent) return; if (ShouldAnnotate()) { wrapping_style_ = EditingStyleUtilities::CreateWrappingStyleForAnnotatedSerialization( parent); return; } wrapping_style_ = EditingStyleUtilities::CreateWrappingStyleForSerialization(parent); } template Node* StyledMarkupTraverser::Traverse(Node* start_node, Node* past_end) { HeapVector> ancestors_to_close; Node* next; Node* last_closed = nullptr; for (Node* n = start_node; n && n != past_end; n = next) { // If |n| is a selection boundary such as , traverse the child // nodes in the DOM tree instead of the flat tree. if (HandleSelectionBoundary(*n)) { last_closed = StyledMarkupTraverser(accumulator_, last_closed_.Get()) .Traverse(n, EditingStrategy::NextSkippingChildren(*n)); next = EditingInFlatTreeStrategy::NextSkippingChildren(*n); } else { next = Strategy::Next(*n); if (IsEnclosingBlock(n) && CanHaveChildrenForEditing(n) && next == past_end && !ContainsOnlyBRElement(To(*n))) { // Don't write out empty block containers that aren't fully selected // unless the block container only contains br element. continue; } auto* element = DynamicTo(n); if (n->GetLayoutObject() || (element && element->HasDisplayContentsStyle()) || EnclosingElementWithTag(FirstPositionInOrBeforeNode(*n), kSelectTag)) { // 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)) { if (next == past_end && ContainsOnlyBRElement(*element)) { // node is not fully selected and node contains only one br element // as child. Close the br tag now. AppendStartMarkup(*next); AppendEndMarkup(*next); last_closed = next; } else { ancestors_to_close.push_back(To(n)); } continue; } AppendEndMarkup(*n); last_closed = n; } else { next = Strategy::NextSkippingChildren(*n); // Don't skip over pastEnd. if (past_end && Strategy::IsDescendantOf(*past_end, *n)) next = past_end; } } // 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 != past_end) continue; // Close up the ancestors. while (!ancestors_to_close.IsEmpty()) { ContainerNode* ancestor = ancestors_to_close.back(); DCHECK(ancestor); if (next && next != past_end && Strategy::IsDescendantOf(*next, *ancestor)) break; // Not at the end of the range, close ancestors up to sibling of next // node. AppendEndMarkup(*ancestor); last_closed = ancestor; ancestors_to_close.pop_back(); } // Surround the currently accumulated markup with markup for ancestors we // never opened as we leave the subtree(s) rooted at those ancestors. ContainerNode* next_parent = next ? Strategy::Parent(*next) : nullptr; if (next == past_end || n == next_parent) continue; DCHECK(n); Node* last_ancestor_closed_or_self = (last_closed && Strategy::IsDescendantOf(*n, *last_closed)) ? last_closed : n; for (ContainerNode* parent = Strategy::Parent(*last_ancestor_closed_or_self); parent && parent != next_parent; parent = Strategy::Parent(*parent)) { // All ancestors that aren't in the ancestorsToClose list should either be // a) unrendered: if (!parent->GetLayoutObject()) continue; // or b) ancestors that we never encountered during a pre-order traversal // starting at startNode: DCHECK(start_node); DCHECK(Strategy::IsDescendantOf(*start_node, *parent)); EditingStyle* style = CreateInlineStyleIfNeeded(*parent); WrapWithNode(*parent, style); last_closed = parent; } } return last_closed; } template bool StyledMarkupTraverser::NeedsInlineStyle(const Element& element) { if (!element.IsHTMLElement()) return false; if (ShouldAnnotate()) return true; return ShouldConvertBlocksToInlines() && IsEnclosingBlock(&element); } template void StyledMarkupTraverser::WrapWithNode(ContainerNode& node, EditingStyle* style) { if (!accumulator_) return; StringBuilder markup; if (auto* document = DynamicTo(node)) { MarkupFormatter::AppendXMLDeclaration(markup, *document); accumulator_->PushMarkup(markup.ToString()); return; } auto* element = DynamicTo(node); if (!element) return; if (ShouldApplyWrappingStyle(*element) || NeedsInlineStyle(*element)) accumulator_->AppendElementWithInlineStyle(markup, *element, style); else accumulator_->AppendElement(markup, *element); accumulator_->PushMarkup(markup.ToString()); accumulator_->AppendEndTag(*element); } template EditingStyle* StyledMarkupTraverser::CreateInlineStyleIfNeeded( Node& node) { if (!accumulator_) return nullptr; auto* element = DynamicTo(node); if (!element) return nullptr; EditingStyle* inline_style = CreateInlineStyle(*element); if (ShouldConvertBlocksToInlines() && IsEnclosingBlock(&node)) inline_style->ForceInline(); return inline_style; } template void StyledMarkupTraverser::AppendStartMarkup(Node& node) { if (!accumulator_) return; switch (node.getNodeType()) { case Node::kTextNode: { auto& text = To(node); if (text.parentElement() && IsHTMLTextAreaElement(text.parentElement())) { accumulator_->AppendText(text); break; } EditingStyle* inline_style = nullptr; if (ShouldApplyWrappingStyle(text)) { inline_style = wrapping_style_->Copy(); // FIXME: Style rules that match pasted content // can change its appearance. // Make sure spans are inline style in paste side e.g. span { display: // block }. inline_style->ForceInline(); // FIXME: Should this be included in forceInline? inline_style->Style()->SetProperty(CSSPropertyID::kFloat, CSSValueID::kNone); } accumulator_->AppendTextWithInlineStyle(text, inline_style); break; } case Node::kElementNode: { auto& element = To(node); if ((element.IsHTMLElement() && ShouldAnnotate()) || ShouldApplyWrappingStyle(element)) { EditingStyle* inline_style = CreateInlineStyle(element); accumulator_->AppendElementWithInlineStyle(element, inline_style); break; } accumulator_->AppendElement(element); break; } default: accumulator_->AppendStartMarkup(node); break; } } template void StyledMarkupTraverser::AppendEndMarkup(Node& node) { auto* element = DynamicTo(node); if (!accumulator_ || !element) return; accumulator_->AppendEndTag(*element); } template bool StyledMarkupTraverser::ShouldApplyWrappingStyle( const Node& node) const { return last_closed_ && Strategy::Parent(*last_closed_) == Strategy::Parent(node) && wrapping_style_ && wrapping_style_->Style(); } template EditingStyle* StyledMarkupTraverser::CreateInlineStyle( Element& element) { EditingStyle* inline_style = nullptr; if (ShouldApplyWrappingStyle(element)) { inline_style = wrapping_style_->Copy(); inline_style->RemovePropertiesInElementDefaultStyle(&element); inline_style->RemoveStyleConflictingWithStyleOfElement(&element); } else { inline_style = MakeGarbageCollected(); } if (element.IsStyledElement() && element.InlineStyle()) inline_style->OverrideWithStyle(element.InlineStyle()); auto* html_element = DynamicTo(element); if (html_element && ShouldAnnotate()) { inline_style->MergeStyleFromRulesForSerialization(html_element); } return inline_style; } template bool StyledMarkupTraverser::ContainsOnlyBRElement( const Element& element) const { auto* const first_child = element.firstChild(); if (!first_child) return false; return IsHTMLBRElement(first_child) && first_child == element.lastChild(); } template class StyledMarkupSerializer; template class StyledMarkupSerializer; } // namespace blink