| /* |
| * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2012 Apple Inc. All |
| * rights reserved. |
| * Copyright (C) 2005 Alexey Proskuryakov. |
| * |
| * 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/iterators/text_iterator.h" |
| |
| #include <unicode/utf16.h> |
| #include "third_party/blink/renderer/core/dom/document.h" |
| #include "third_party/blink/renderer/core/dom/shadow_root.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/position.h" |
| #include "third_party/blink/renderer/core/editing/visible_position.h" |
| #include "third_party/blink/renderer/core/editing/visible_units.h" |
| #include "third_party/blink/renderer/core/frame/local_frame_view.h" |
| #include "third_party/blink/renderer/core/frame/use_counter.h" |
| #include "third_party/blink/renderer/core/html/forms/html_input_element.h" |
| #include "third_party/blink/renderer/core/html/forms/text_control_element.h" |
| #include "third_party/blink/renderer/core/html/html_element.h" |
| #include "third_party/blink/renderer/core/html/html_image_element.h" |
| #include "third_party/blink/renderer/core/html_names.h" |
| #include "third_party/blink/renderer/core/input_type_names.h" |
| #include "third_party/blink/renderer/core/layout/layout_table_cell.h" |
| #include "third_party/blink/renderer/core/layout/layout_table_row.h" |
| #include "third_party/blink/renderer/platform/fonts/font.h" |
| #include "third_party/blink/renderer/platform/wtf/text/string_builder.h" |
| |
| namespace blink { |
| |
| using namespace html_names; |
| |
| namespace { |
| |
| template <typename Strategy> |
| TextIteratorBehavior AdjustBehaviorFlags(const TextIteratorBehavior&); |
| |
| template <> |
| TextIteratorBehavior AdjustBehaviorFlags<EditingStrategy>( |
| const TextIteratorBehavior& behavior) { |
| if (!behavior.ForSelectionToString()) |
| return behavior; |
| return TextIteratorBehavior::Builder(behavior) |
| .SetExcludeAutofilledValue(true) |
| .Build(); |
| } |
| |
| template <> |
| TextIteratorBehavior AdjustBehaviorFlags<EditingInFlatTreeStrategy>( |
| const TextIteratorBehavior& behavior) { |
| return TextIteratorBehavior::Builder(behavior) |
| .SetExcludeAutofilledValue(behavior.ForSelectionToString() || |
| behavior.ExcludeAutofilledValue()) |
| .SetEntersOpenShadowRoots(false) |
| .Build(); |
| } |
| |
| static inline bool HasDisplayContents(const Node& node) { |
| return node.IsElementNode() && ToElement(node).HasDisplayContentsStyle(); |
| } |
| |
| // Checks if |advance()| skips the descendants of |node|, which is the case if |
| // |node| is neither a shadow root nor the owner of a layout object. |
| static bool NotSkipping(const Node& node) { |
| return node.GetLayoutObject() || HasDisplayContents(node) || |
| (node.IsShadowRoot() && node.OwnerShadowHost()->GetLayoutObject()); |
| } |
| |
| template <typename Strategy> |
| const Node* StartNode(const Node* start_container, unsigned start_offset) { |
| if (start_container->IsCharacterDataNode()) |
| return start_container; |
| if (Node* child = Strategy::ChildAt(*start_container, start_offset)) |
| return child; |
| if (!start_offset) |
| return start_container; |
| return Strategy::NextSkippingChildren(*start_container); |
| } |
| |
| template <typename Strategy> |
| const Node* EndNode(const Node& end_container, unsigned end_offset) { |
| if (!end_container.IsCharacterDataNode() && end_offset) |
| return Strategy::ChildAt(end_container, end_offset - 1); |
| return nullptr; |
| } |
| |
| // This function is like Range::PastLastNode, except for the fact that it can |
| // climb up out of shadow trees and ignores all nodes that will be skipped in |
| // |advance()|. |
| template <typename Strategy> |
| const Node* PastLastNode(const Node& range_end_container, |
| unsigned range_end_offset) { |
| if (!range_end_container.IsCharacterDataNode() && |
| NotSkipping(range_end_container)) { |
| for (Node* next = Strategy::ChildAt(range_end_container, range_end_offset); |
| next; next = Strategy::NextSibling(*next)) { |
| if (NotSkipping(*next)) |
| return next; |
| } |
| } |
| for (const Node* node = &range_end_container; node;) { |
| const Node* parent = ParentCrossingShadowBoundaries<Strategy>(*node); |
| if (parent && NotSkipping(*parent)) { |
| if (Node* next = Strategy::NextSibling(*node)) |
| return next; |
| } |
| node = parent; |
| } |
| return nullptr; |
| } |
| |
| // Figure out the initial value of shadow_depth_: the depth of start_container's |
| // tree scope from the common ancestor tree scope. |
| template <typename Strategy> |
| unsigned ShadowDepthOf(const Node& start_container, const Node& end_container); |
| |
| template <> |
| unsigned ShadowDepthOf<EditingStrategy>(const Node& start_container, |
| const Node& end_container) { |
| const TreeScope* common_ancestor_tree_scope = |
| start_container.GetTreeScope().CommonAncestorTreeScope( |
| end_container.GetTreeScope()); |
| DCHECK(common_ancestor_tree_scope); |
| unsigned shadow_depth = 0; |
| for (const TreeScope* tree_scope = &start_container.GetTreeScope(); |
| tree_scope != common_ancestor_tree_scope; |
| tree_scope = tree_scope->ParentTreeScope()) |
| ++shadow_depth; |
| return shadow_depth; |
| } |
| |
| template <> |
| unsigned ShadowDepthOf<EditingInFlatTreeStrategy>(const Node& start_container, |
| const Node& end_container) { |
| return 0; |
| } |
| |
| bool IsRenderedAsTable(const Node* node) { |
| if (!node || !node->IsElementNode()) |
| return false; |
| LayoutObject* layout_object = node->GetLayoutObject(); |
| return layout_object && layout_object->IsTable(); |
| } |
| |
| bool ShouldHandleChildren(const Node& node, |
| const TextIteratorBehavior& behavior) { |
| // To support |TextIteratorEmitsImageAltText|, we don't traversal child |
| // nodes, in flat tree. |
| if (IsHTMLImageElement(node)) |
| return false; |
| // Traverse internals of text control elements in flat tree only when |
| // |EntersTextControls| flag is set. |
| if (!behavior.EntersTextControls() && IsTextControl(node)) |
| return false; |
| |
| if (node.IsElementNode()) { |
| if (auto* context = ToElement(node).GetDisplayLockContext()) |
| return context->IsActivatable(); |
| } |
| return true; |
| } |
| |
| } // namespace |
| |
| template <typename Strategy> |
| TextIteratorAlgorithm<Strategy>::TextIteratorAlgorithm( |
| const EphemeralRangeTemplate<Strategy>& range, |
| const TextIteratorBehavior& behavior) |
| : TextIteratorAlgorithm(range.StartPosition(), |
| range.EndPosition(), |
| behavior) {} |
| |
| template <typename Strategy> |
| TextIteratorAlgorithm<Strategy>::TextIteratorAlgorithm( |
| const PositionTemplate<Strategy>& start, |
| const PositionTemplate<Strategy>& end, |
| const TextIteratorBehavior& behavior) |
| : start_container_(start.ComputeContainerNode()), |
| start_offset_(start.ComputeOffsetInContainerNode()), |
| end_container_(end.ComputeContainerNode()), |
| end_offset_(end.ComputeOffsetInContainerNode()), |
| end_node_(EndNode<Strategy>(*end_container_, end_offset_)), |
| past_end_node_(PastLastNode<Strategy>(*end_container_, end_offset_)), |
| node_(StartNode<Strategy>(start_container_, start_offset_)), |
| iteration_progress_(kHandledNone), |
| shadow_depth_( |
| ShadowDepthOf<Strategy>(*start_container_, *end_container_)), |
| behavior_(AdjustBehaviorFlags<Strategy>(behavior)), |
| text_state_(behavior_), |
| text_node_handler_(behavior_, &text_state_) { |
| DCHECK(start_container_); |
| DCHECK(end_container_); |
| |
| // TODO(dglazkov): TextIterator should not be created for documents that don't |
| // have a frame, but it currently still happens in some cases. See |
| // http://crbug.com/591877 for details. |
| DCHECK(!start.GetDocument()->View() || |
| !start.GetDocument()->View()->NeedsLayout()); |
| DCHECK(!start.GetDocument()->NeedsLayoutTreeUpdate()); |
| // To avoid renderer hang, we use |CHECK_LE()| to catch the bad callers |
| // in release build. |
| CHECK_LE(start, end); |
| |
| if (!node_) |
| return; |
| |
| fully_clipped_stack_.SetUpFullyClippedStack(node_); |
| |
| // Identify the first run. |
| Advance(); |
| } |
| |
| template <typename Strategy> |
| TextIteratorAlgorithm<Strategy>::~TextIteratorAlgorithm() { |
| if (!handle_shadow_root_) |
| return; |
| const Document& document = OwnerDocument(); |
| if (behavior_.ForInnerText()) |
| UseCounter::Count(document, WebFeature::kInnerTextWithShadowTree); |
| if (behavior_.ForSelectionToString()) |
| UseCounter::Count(document, WebFeature::kSelectionToStringWithShadowTree); |
| if (behavior_.ForWindowFind()) |
| UseCounter::Count(document, WebFeature::kWindowFindWithShadowTree); |
| } |
| |
| template <typename Strategy> |
| bool TextIteratorAlgorithm<Strategy>::IsInsideAtomicInlineElement() const { |
| if (AtEnd() || length() != 1 || !node_) |
| return false; |
| |
| LayoutObject* layout_object = node_->GetLayoutObject(); |
| return layout_object && layout_object->IsAtomicInlineLevel(); |
| } |
| |
| template <typename Strategy> |
| bool TextIteratorAlgorithm<Strategy>::HandleRememberedProgress() { |
| // Handle remembered node that needed a newline after the text node's newline |
| if (needs_another_newline_) { |
| // Emit the extra newline, and position it *inside* node_, after node_'s |
| // contents, in case it's a block, in the same way that we position the |
| // first newline. The range for the emitted newline should start where the |
| // line break begins. |
| // FIXME: It would be cleaner if we emitted two newlines during the last |
| // iteration, instead of using needs_another_newline_. |
| Node* last_child = Strategy::LastChild(*node_); |
| const Node* base_node = last_child ? last_child : node_.Get(); |
| EmitChar16AfterNode('\n', *base_node); |
| needs_another_newline_ = false; |
| return true; |
| } |
| |
| if (needs_handle_replaced_element_) { |
| HandleReplacedElement(); |
| if (text_state_.PositionNode()) |
| return true; |
| } |
| |
| // Try to emit more text runs if we are handling a text node. |
| return text_node_handler_.HandleRemainingTextRuns(); |
| } |
| |
| template <typename Strategy> |
| void TextIteratorAlgorithm<Strategy>::Advance() { |
| if (should_stop_) |
| return; |
| |
| if (node_) |
| DCHECK(!node_->GetDocument().NeedsLayoutTreeUpdate()) << node_; |
| |
| text_state_.ResetRunInformation(); |
| |
| if (HandleRememberedProgress()) |
| return; |
| |
| while (node_ && (node_ != past_end_node_ || shadow_depth_)) { |
| if (!should_stop_ && StopsOnFormControls() && |
| HTMLFormControlElement::EnclosingFormControlElement(node_)) |
| should_stop_ = true; |
| |
| // if the range ends at offset 0 of an element, represent the |
| // position, but not the content, of that element e.g. if the |
| // node is a blockflow element, emit a newline that |
| // precedes the element |
| if (node_ == end_container_ && !end_offset_) { |
| RepresentNodeOffsetZero(); |
| node_ = nullptr; |
| return; |
| } |
| |
| LayoutObject* layout_object = node_->GetLayoutObject(); |
| if (!layout_object) { |
| if (node_->IsShadowRoot() || HasDisplayContents(*node_)) { |
| // Shadow roots or display: contents elements don't have LayoutObjects, |
| // but we want to visit children anyway. |
| iteration_progress_ = iteration_progress_ < kHandledNode |
| ? kHandledNode |
| : iteration_progress_; |
| handle_shadow_root_ = node_->IsShadowRoot(); |
| } else { |
| iteration_progress_ = kHandledChildren; |
| } |
| } else { |
| // Enter author shadow roots, from youngest, if any and if necessary. |
| if (iteration_progress_ < kHandledOpenShadowRoots) { |
| if (std::is_same<Strategy, EditingStrategy>::value && |
| EntersOpenShadowRoots() && node_->IsElementNode() && |
| ToElement(node_)->OpenShadowRoot()) { |
| ShadowRoot* youngest_shadow_root = ToElement(node_)->OpenShadowRoot(); |
| DCHECK(youngest_shadow_root->GetType() == ShadowRootType::V0 || |
| youngest_shadow_root->GetType() == ShadowRootType::kOpen); |
| node_ = youngest_shadow_root; |
| iteration_progress_ = kHandledNone; |
| ++shadow_depth_; |
| fully_clipped_stack_.PushFullyClippedState(node_); |
| continue; |
| } |
| |
| iteration_progress_ = kHandledOpenShadowRoots; |
| } |
| |
| // Enter user-agent shadow root, if necessary. |
| if (iteration_progress_ < kHandledUserAgentShadowRoot) { |
| if (std::is_same<Strategy, EditingStrategy>::value && |
| EntersTextControls() && layout_object->IsTextControl()) { |
| ShadowRoot* user_agent_shadow_root = |
| ToElement(node_)->UserAgentShadowRoot(); |
| DCHECK(user_agent_shadow_root->IsUserAgent()); |
| node_ = user_agent_shadow_root; |
| iteration_progress_ = kHandledNone; |
| ++shadow_depth_; |
| fully_clipped_stack_.PushFullyClippedState(node_); |
| continue; |
| } |
| iteration_progress_ = kHandledUserAgentShadowRoot; |
| } |
| |
| // Handle the current node according to its type. |
| if (iteration_progress_ < kHandledNode) { |
| if (!SkipsUnselectableContent() || layout_object->IsSelectable()) { |
| if (layout_object->IsText() && |
| node_->getNodeType() == |
| Node::kTextNode) { // FIXME: What about kCdataSectionNode? |
| if (!fully_clipped_stack_.Top() || IgnoresStyleVisibility()) |
| HandleTextNode(); |
| } else if (layout_object && |
| (layout_object->IsImage() || |
| layout_object->IsLayoutEmbeddedContent() || |
| (node_ && node_->IsHTMLElement() && |
| (IsHTMLFormControlElement(ToHTMLElement(*node_)) || |
| IsHTMLLegendElement(ToHTMLElement(*node_)) || |
| IsHTMLImageElement(ToHTMLElement(*node_)) || |
| IsHTMLMeterElement(ToHTMLElement(*node_)) || |
| IsHTMLProgressElement(ToHTMLElement(*node_)))))) { |
| HandleReplacedElement(); |
| } else { |
| HandleNonTextNode(); |
| } |
| } |
| iteration_progress_ = kHandledNode; |
| if (text_state_.PositionNode()) |
| return; |
| } |
| } |
| |
| // Find a new current node to handle in depth-first manner, |
| // calling exitNode() as we come back thru a parent node. |
| // |
| // 1. Iterate over child nodes, if we haven't done yet. |
| Node* next = iteration_progress_ < kHandledChildren && |
| ShouldHandleChildren(*node_, behavior_) |
| ? Strategy::FirstChild(*node_) |
| : nullptr; |
| if (!next) { |
| // 2. If we've already iterated children or they are not available, go to |
| // the next sibling node. |
| next = Strategy::NextSibling(*node_); |
| if (!next) { |
| // 3. If we are at the last child, go up the node tree until we find a |
| // next sibling. |
| ContainerNode* parent_node = Strategy::Parent(*node_); |
| while (!next && parent_node) { |
| if (node_ == end_node_ || |
| Strategy::IsDescendantOf(*end_container_, *parent_node)) |
| return; |
| bool have_layout_object = node_->GetLayoutObject(); |
| node_ = parent_node; |
| fully_clipped_stack_.Pop(); |
| parent_node = Strategy::Parent(*node_); |
| if (have_layout_object) |
| ExitNode(); |
| if (text_state_.PositionNode()) { |
| iteration_progress_ = kHandledChildren; |
| return; |
| } |
| next = Strategy::NextSibling(*node_); |
| } |
| |
| if (!next && !parent_node && shadow_depth_) { |
| // 4. Reached the top of a shadow root. If it's created by author, |
| // then try to visit the next |
| // sibling shadow root, if any. |
| if (!node_->IsShadowRoot()) { |
| NOTREACHED(); |
| should_stop_ = true; |
| return; |
| } |
| const ShadowRoot* shadow_root = ToShadowRoot(node_); |
| if (shadow_root->GetType() == ShadowRootType::V0 || |
| shadow_root->GetType() == ShadowRootType::kOpen) { |
| // We are the shadow root; exit from here and go back to |
| // where we were. |
| node_ = &shadow_root->host(); |
| iteration_progress_ = kHandledOpenShadowRoots; |
| --shadow_depth_; |
| fully_clipped_stack_.Pop(); |
| } else { |
| // If we are in a closed or user-agent shadow root, then go back to |
| // the host. |
| // TODO(kochi): Make sure we treat closed shadow as user agent |
| // shadow here. |
| DCHECK(shadow_root->GetType() == ShadowRootType::kClosed || |
| shadow_root->IsUserAgent()); |
| node_ = &shadow_root->host(); |
| iteration_progress_ = kHandledUserAgentShadowRoot; |
| --shadow_depth_; |
| fully_clipped_stack_.Pop(); |
| } |
| continue; |
| } |
| } |
| fully_clipped_stack_.Pop(); |
| } |
| |
| // set the new current node |
| node_ = next; |
| if (node_) |
| fully_clipped_stack_.PushFullyClippedState(node_); |
| iteration_progress_ = kHandledNone; |
| |
| // how would this ever be? |
| if (text_state_.PositionNode()) |
| return; |
| } |
| } |
| |
| template <typename Strategy> |
| void TextIteratorAlgorithm<Strategy>::HandleTextNode() { |
| if (ExcludesAutofilledValue()) { |
| TextControlElement* control = EnclosingTextControl(node_); |
| // For security reason, we don't expose suggested value if it is |
| // auto-filled. |
| if (control && control->IsAutofilled()) |
| return; |
| } |
| |
| DCHECK_NE(last_text_node_, node_) |
| << "We should never call HandleTextNode on the same node twice"; |
| const Text* text = ToText(node_); |
| last_text_node_ = text; |
| |
| // TODO(editing-dev): Introduce a |DOMOffsetRange| class so that we can pass |
| // an offset range with unbounded endpoint(s) in an easy but still clear way. |
| if (node_ != start_container_) { |
| if (node_ != end_container_) |
| text_node_handler_.HandleTextNodeWhole(text); |
| else |
| text_node_handler_.HandleTextNodeEndAt(text, end_offset_); |
| return; |
| } |
| if (node_ != end_container_) { |
| text_node_handler_.HandleTextNodeStartFrom(text, start_offset_); |
| return; |
| } |
| text_node_handler_.HandleTextNodeInRange(text, start_offset_, end_offset_); |
| } |
| |
| template <typename Strategy> |
| bool TextIteratorAlgorithm<Strategy>::SupportsAltText(const Node& node) { |
| if (!node.IsHTMLElement()) |
| return false; |
| const HTMLElement& element = ToHTMLElement(node); |
| |
| // FIXME: Add isSVGImageElement. |
| if (IsHTMLImageElement(element)) |
| return true; |
| if (IsHTMLInputElement(element) && |
| ToHTMLInputElement(node).type() == input_type_names::kImage) |
| return true; |
| return false; |
| } |
| |
| template <typename Strategy> |
| void TextIteratorAlgorithm<Strategy>::HandleReplacedElement() { |
| needs_handle_replaced_element_ = false; |
| |
| if (fully_clipped_stack_.Top()) |
| return; |
| |
| LayoutObject* layout_object = node_->GetLayoutObject(); |
| if (layout_object->Style()->Visibility() != EVisibility::kVisible && |
| !IgnoresStyleVisibility()) { |
| return; |
| } |
| |
| if (EmitsObjectReplacementCharacter()) { |
| EmitChar16AsNode(kObjectReplacementCharacter, *node_); |
| return; |
| } |
| |
| DCHECK_EQ(last_text_node_, text_node_handler_.GetNode()); |
| if (last_text_node_) { |
| if (text_node_handler_.FixLeadingWhiteSpaceForReplacedElement()) { |
| needs_handle_replaced_element_ = true; |
| return; |
| } |
| } |
| |
| if (EntersTextControls() && layout_object->IsTextControl()) { |
| // The shadow tree should be already visited. |
| return; |
| } |
| |
| if (EmitsCharactersBetweenAllVisiblePositions()) { |
| // We want replaced elements to behave like punctuation for boundary |
| // finding, and to simply take up space for the selection preservation |
| // code in moveParagraphs, so we use a comma. |
| EmitChar16AsNode(',', *node_); |
| return; |
| } |
| |
| if (EmitsImageAltText() && TextIterator::SupportsAltText(*node_)) { |
| text_state_.EmitAltText(ToHTMLElement(*node_)); |
| return; |
| } |
| // TODO(editing-dev): We can remove |UpdateForReplacedElement()| call when |
| // we address web test failures (text diff by newlines only) and unit |
| // tests, e.g. TextIteratorTest.IgnoreAltTextInTextControls. |
| text_state_.UpdateForReplacedElement(*node_); |
| } |
| |
| template <typename Strategy> |
| bool TextIteratorAlgorithm<Strategy>::ShouldEmitTabBeforeNode( |
| const Node& node) { |
| LayoutObject* r = node.GetLayoutObject(); |
| |
| // Table cells are delimited by tabs. |
| if (!r || !IsTableCell(&node)) |
| return false; |
| |
| // Want a tab before every cell other than the first one |
| LayoutTableCell* rc = ToLayoutTableCell(r); |
| LayoutTable* t = rc->Table(); |
| return t && (t->CellPreceding(*rc) || t->CellAbove(*rc)); |
| } |
| |
| template <typename Strategy> |
| bool TextIteratorAlgorithm<Strategy>::ShouldEmitNewlineForNode( |
| const Node& node, |
| bool emits_original_text) { |
| LayoutObject* layout_object = node.GetLayoutObject(); |
| |
| if (layout_object ? !layout_object->IsBR() : !IsHTMLBRElement(node)) |
| return false; |
| return emits_original_text || !(node.IsInShadowTree() && |
| IsHTMLInputElement(*node.OwnerShadowHost())); |
| } |
| |
| static bool ShouldEmitNewlinesBeforeAndAfterNode(const Node& node) { |
| // Block flow (versus inline flow) is represented by having |
| // a newline both before and after the element. |
| LayoutObject* r = node.GetLayoutObject(); |
| if (!r) { |
| if (HasDisplayContents(node)) |
| return false; |
| return (node.HasTagName(kBlockquoteTag) || node.HasTagName(kDdTag) || |
| node.HasTagName(kDivTag) || node.HasTagName(kDlTag) || |
| node.HasTagName(kDtTag) || node.HasTagName(kH1Tag) || |
| node.HasTagName(kH2Tag) || node.HasTagName(kH3Tag) || |
| node.HasTagName(kH4Tag) || node.HasTagName(kH5Tag) || |
| node.HasTagName(kH6Tag) || node.HasTagName(kHrTag) || |
| node.HasTagName(kLiTag) || node.HasTagName(kListingTag) || |
| node.HasTagName(kOlTag) || node.HasTagName(kPTag) || |
| node.HasTagName(kPreTag) || node.HasTagName(kTrTag) || |
| node.HasTagName(kUlTag)); |
| } |
| |
| // Need to make an exception for option and optgroup, because we want to |
| // keep the legacy behavior before we added layoutObjects to them. |
| if (IsHTMLOptionElement(node) || IsHTMLOptGroupElement(node)) |
| return false; |
| |
| // Need to make an exception for table cells, because they are blocks, but we |
| // want them tab-delimited rather than having newlines before and after. |
| if (IsTableCell(&node)) |
| return false; |
| |
| // Need to make an exception for table row elements, because they are neither |
| // "inline" or "LayoutBlock", but we want newlines for them. |
| if (r->IsTableRow()) { |
| LayoutTable* t = ToLayoutTableRow(r)->Table(); |
| if (t && !t->IsInline()) |
| return true; |
| } |
| |
| return !r->IsInline() && r->IsLayoutBlock() && |
| !r->IsFloatingOrOutOfFlowPositioned() && !r->IsBody() && |
| !r->IsRubyText(); |
| } |
| |
| template <typename Strategy> |
| bool TextIteratorAlgorithm<Strategy>::ShouldEmitNewlineAfterNode( |
| const Node& node) { |
| // FIXME: It should be better but slower to create a VisiblePosition here. |
| if (!ShouldEmitNewlinesBeforeAndAfterNode(node)) |
| return false; |
| // Check if this is the very last layoutObject in the document. |
| // If so, then we should not emit a newline. |
| const Node* next = &node; |
| do { |
| next = Strategy::NextSkippingChildren(*next); |
| if (next && next->GetLayoutObject()) |
| return true; |
| } while (next); |
| return false; |
| } |
| |
| template <typename Strategy> |
| bool TextIteratorAlgorithm<Strategy>::ShouldEmitNewlineBeforeNode( |
| const Node& node) { |
| return ShouldEmitNewlinesBeforeAndAfterNode(node); |
| } |
| |
| static bool ShouldEmitExtraNewlineForNode(const Node* node) { |
| // https://html.spec.whatwg.org/C/#the-innertext-idl-attribute |
| // Append two required linebreaks after a P element. |
| LayoutObject* r = node->GetLayoutObject(); |
| if (!r || !r->IsBox()) |
| return false; |
| |
| return node->HasTagName(kPTag); |
| } |
| |
| // Whether or not we should emit a character as we enter node_ (if it's a |
| // container) or as we hit it (if it's atomic). |
| template <typename Strategy> |
| bool TextIteratorAlgorithm<Strategy>::ShouldRepresentNodeOffsetZero() { |
| if (EmitsCharactersBetweenAllVisiblePositions() && IsRenderedAsTable(node_)) |
| return true; |
| |
| // Leave element positioned flush with start of a paragraph |
| // (e.g. do not insert tab before a table cell at the start of a paragraph) |
| if (text_state_.LastCharacter() == '\n') |
| return false; |
| |
| // Otherwise, show the position if we have emitted any characters |
| if (text_state_.HasEmitted()) |
| return true; |
| |
| // We've not emitted anything yet. Generally, there is no need for any |
| // positioning then. The only exception is when the element is visually not in |
| // the same line as the start of the range (e.g. the range starts at the end |
| // of the previous paragraph). |
| // NOTE: Creating VisiblePositions and comparing them is relatively expensive, |
| // so we make quicker checks to possibly avoid that. Another check that we |
| // could make is is whether the inline vs block flow changed since the |
| // previous visible element. I think we're already in a special enough case |
| // that that won't be needed, tho. |
| |
| // No character needed if this is the first node in the range. |
| if (node_ == start_container_) |
| return false; |
| |
| // If we are outside the start container's subtree, assume we need to emit. |
| // FIXME: start_container_ could be an inline block |
| if (!Strategy::IsDescendantOf(*node_, *start_container_)) |
| return true; |
| |
| // If we started as start_container_ offset 0 and the current node is a |
| // descendant of the start container, we already had enough context to |
| // correctly decide whether to emit after a preceding block. We chose not to |
| // emit (has_emitted_ is false), so don't second guess that now. |
| // NOTE: Is this really correct when node_ is not a leftmost descendant? |
| // Probably immaterial since we likely would have already emitted something by |
| // now. |
| if (!start_offset_) |
| return false; |
| |
| // If this node is unrendered or invisible the VisiblePosition checks below |
| // won't have much meaning. |
| // Additionally, if the range we are iterating over contains huge sections of |
| // unrendered content, we would create VisiblePositions on every call to this |
| // function without this check. |
| if (!node_->GetLayoutObject() || |
| node_->GetLayoutObject()->Style()->Visibility() != |
| EVisibility::kVisible || |
| (node_->GetLayoutObject()->IsLayoutBlockFlow() && |
| !ToLayoutBlock(node_->GetLayoutObject())->Size().Height() && |
| !IsHTMLBodyElement(*node_))) |
| return false; |
| |
| // The startPos.isNotNull() check is needed because the start could be before |
| // the body, and in that case we'll get null. We don't want to put in newlines |
| // at the start in that case. |
| // The currPos.isNotNull() check is needed because positions in non-HTML |
| // content (like SVG) do not have visible positions, and we don't want to emit |
| // for them either. |
| const VisiblePositionTemplate<Strategy> start_pos = CreateVisiblePosition( |
| PositionTemplate<Strategy>(start_container_, start_offset_)); |
| const VisiblePositionTemplate<Strategy> curr_pos = |
| VisiblePositionTemplate<Strategy>::BeforeNode(*node_); |
| return start_pos.IsNotNull() && curr_pos.IsNotNull() && |
| !InSameLine(start_pos, curr_pos); |
| } |
| |
| template <typename Strategy> |
| bool TextIteratorAlgorithm<Strategy>::ShouldEmitSpaceBeforeAndAfterNode( |
| const Node& node) { |
| return IsRenderedAsTable(&node) && |
| (node.GetLayoutObject()->IsInline() || |
| EmitsCharactersBetweenAllVisiblePositions()); |
| } |
| |
| template <typename Strategy> |
| void TextIteratorAlgorithm<Strategy>::RepresentNodeOffsetZero() { |
| // Emit a character to show the positioning of node_. |
| |
| // TODO(editing-dev): We should rewrite this below code fragment to utilize |
| // early-return style. |
| // When we haven't been emitting any characters, |
| // ShouldRepresentNodeOffsetZero() can create VisiblePositions, which is |
| // expensive. So, we perform the inexpensive checks on |node_| to see if it |
| // necessitates emitting a character first and will early return before |
| // encountering ShouldRepresentNodeOffsetZero()s worse case behavior. |
| if (ShouldEmitTabBeforeNode(*node_)) { |
| if (ShouldRepresentNodeOffsetZero()) |
| EmitChar16BeforeNode('\t', *node_); |
| } else if (ShouldEmitNewlineBeforeNode(*node_)) { |
| if (ShouldRepresentNodeOffsetZero()) |
| EmitChar16BeforeNode('\n', *node_); |
| } else if (ShouldEmitSpaceBeforeAndAfterNode(*node_)) { |
| if (ShouldRepresentNodeOffsetZero()) |
| EmitChar16BeforeNode(kSpaceCharacter, *node_); |
| } |
| } |
| |
| template <typename Strategy> |
| void TextIteratorAlgorithm<Strategy>::HandleNonTextNode() { |
| if (ShouldEmitNewlineForNode(*node_, EmitsOriginalText())) |
| EmitChar16AsNode('\n', *node_); |
| else if (EmitsCharactersBetweenAllVisiblePositions() && |
| node_->GetLayoutObject() && node_->GetLayoutObject()->IsHR()) |
| EmitChar16AsNode(kSpaceCharacter, *node_); |
| else |
| RepresentNodeOffsetZero(); |
| } |
| |
| template <typename Strategy> |
| void TextIteratorAlgorithm<Strategy>::ExitNode() { |
| // prevent emitting a newline when exiting a collapsed block at beginning of |
| // the range |
| // FIXME: !has_emitted_ does not necessarily mean there was a collapsed |
| // block... it could have been an hr (e.g.). Also, a collapsed block could |
| // have height (e.g. a table) and therefore look like a blank line. |
| if (!text_state_.HasEmitted()) |
| return; |
| |
| // Emit with a position *inside* node_, after node_'s contents, in |
| // case it is a block, because the run should start where the |
| // emitted character is positioned visually. |
| Node* last_child = Strategy::LastChild(*node_); |
| const Node* base_node = last_child ? last_child : node_.Get(); |
| // FIXME: This shouldn't require the last_text_node to be true, but we can't |
| // change that without making the logic in _web_attributedStringFromRange |
| // match. We'll get that for free when we switch to use TextIterator in |
| // _web_attributedStringFromRange. See <rdar://problem/5428427> for an example |
| // of how this mismatch will cause problems. |
| if (last_text_node_ && ShouldEmitNewlineAfterNode(*node_)) { |
| // use extra newline to represent margin bottom, as needed |
| const bool add_newline = !behavior_.SuppressesExtraNewlineEmission() && |
| ShouldEmitExtraNewlineForNode(node_); |
| |
| // FIXME: We need to emit a '\n' as we leave an empty block(s) that |
| // contain a VisiblePosition when doing selection preservation. |
| if (text_state_.LastCharacter() != '\n') { |
| // insert a newline with a position following this block's contents. |
| EmitChar16AfterNode(kNewlineCharacter, *base_node); |
| // remember whether to later add a newline for the current node |
| DCHECK(!needs_another_newline_); |
| needs_another_newline_ = add_newline; |
| } else if (add_newline) { |
| // insert a newline with a position following this block's contents. |
| EmitChar16AfterNode(kNewlineCharacter, *base_node); |
| } |
| } |
| |
| // If nothing was emitted, see if we need to emit a space. |
| if (!text_state_.PositionNode() && ShouldEmitSpaceBeforeAndAfterNode(*node_)) |
| EmitChar16AfterNode(kSpaceCharacter, *base_node); |
| } |
| |
| template <typename Strategy> |
| void TextIteratorAlgorithm<Strategy>::EmitChar16AfterNode(UChar code_unit, |
| const Node& node) { |
| text_state_.EmitChar16AfterNode(code_unit, node); |
| text_node_handler_.ResetCollapsedWhiteSpaceFixup(); |
| } |
| |
| template <typename Strategy> |
| void TextIteratorAlgorithm<Strategy>::EmitChar16AsNode(UChar code_unit, |
| const Node& node) { |
| text_state_.EmitChar16AsNode(code_unit, node); |
| text_node_handler_.ResetCollapsedWhiteSpaceFixup(); |
| } |
| |
| template <typename Strategy> |
| void TextIteratorAlgorithm<Strategy>::EmitChar16BeforeNode(UChar code_unit, |
| const Node& node) { |
| text_state_.EmitChar16BeforeNode(code_unit, node); |
| text_node_handler_.ResetCollapsedWhiteSpaceFixup(); |
| } |
| |
| template <typename Strategy> |
| EphemeralRangeTemplate<Strategy> TextIteratorAlgorithm<Strategy>::Range() |
| const { |
| // use the current run information, if we have it |
| if (text_state_.PositionNode()) { |
| return EphemeralRangeTemplate<Strategy>(StartPositionInCurrentContainer(), |
| EndPositionInCurrentContainer()); |
| } |
| |
| // otherwise, return the end of the overall range we were given |
| return EphemeralRangeTemplate<Strategy>( |
| PositionTemplate<Strategy>(end_container_, end_offset_)); |
| } |
| |
| template <typename Strategy> |
| const Document& TextIteratorAlgorithm<Strategy>::OwnerDocument() const { |
| return end_container_->GetDocument(); |
| } |
| |
| template <typename Strategy> |
| const Node* TextIteratorAlgorithm<Strategy>::GetNode() const { |
| const Node& node = CurrentContainer(); |
| if (node.IsCharacterDataNode()) |
| return &node; |
| return Strategy::ChildAt(node, StartOffsetInCurrentContainer()); |
| } |
| |
| template <typename Strategy> |
| int TextIteratorAlgorithm<Strategy>::StartOffsetInCurrentContainer() const { |
| if (!text_state_.PositionNode()) |
| return end_offset_; |
| EnsurePositionContainer(); |
| return text_state_.PositionStartOffset(); |
| } |
| |
| template <typename Strategy> |
| int TextIteratorAlgorithm<Strategy>::EndOffsetInCurrentContainer() const { |
| if (!text_state_.PositionNode()) |
| return end_offset_; |
| EnsurePositionContainer(); |
| return text_state_.PositionEndOffset(); |
| } |
| |
| template <typename Strategy> |
| const Node& TextIteratorAlgorithm<Strategy>::CurrentContainer() const { |
| if (!text_state_.PositionNode()) |
| return *end_container_; |
| EnsurePositionContainer(); |
| return *text_state_.PositionContainerNode(); |
| } |
| |
| template <typename Strategy> |
| void TextIteratorAlgorithm<Strategy>::EnsurePositionContainer() const { |
| DCHECK(text_state_.PositionNode()); |
| if (text_state_.PositionContainerNode()) |
| return; |
| const Node& node = *text_state_.PositionNode(); |
| const ContainerNode* parent = Strategy::Parent(node); |
| DCHECK(parent); |
| text_state_.UpdatePositionOffsets(*parent, Strategy::Index(node)); |
| } |
| |
| template <typename Strategy> |
| PositionTemplate<Strategy> TextIteratorAlgorithm<Strategy>::GetPositionBefore( |
| int char16_offset) const { |
| if (AtEnd()) { |
| DCHECK_EQ(char16_offset, 0); |
| return PositionTemplate<Strategy>(CurrentContainer(), |
| StartOffsetInCurrentContainer()); |
| } |
| DCHECK_GE(char16_offset, 0); |
| DCHECK_LT(char16_offset, length()); |
| DCHECK_GE(length(), 1); |
| const Node& node = *text_state_.PositionNode(); |
| if (text_state_.IsInTextNode() || text_state_.IsBeforeCharacter()) { |
| return PositionTemplate<Strategy>( |
| node, text_state_.PositionStartOffset() + char16_offset); |
| } |
| if (node.IsTextNode()) { |
| if (text_state_.IsAfterPositionNode()) |
| return PositionTemplate<Strategy>(node, ToText(node).length()); |
| return PositionTemplate<Strategy>(node, 0); |
| } |
| if (text_state_.IsAfterPositionNode()) |
| return PositionTemplate<Strategy>::AfterNode(node); |
| DCHECK(!text_state_.IsBeforeChildren()); |
| return PositionTemplate<Strategy>::BeforeNode(node); |
| } |
| |
| template <typename Strategy> |
| PositionTemplate<Strategy> TextIteratorAlgorithm<Strategy>::GetPositionAfter( |
| int char16_offset) const { |
| if (AtEnd()) { |
| DCHECK_EQ(char16_offset, 0); |
| return PositionTemplate<Strategy>(CurrentContainer(), |
| EndOffsetInCurrentContainer()); |
| } |
| DCHECK_GE(char16_offset, 0); |
| DCHECK_LT(char16_offset, length()); |
| DCHECK_GE(length(), 1); |
| const Node& node = *text_state_.PositionNode(); |
| if (text_state_.IsBeforeCharacter()) { |
| return PositionTemplate<Strategy>( |
| node, text_state_.PositionStartOffset() + char16_offset); |
| } |
| if (text_state_.IsInTextNode()) { |
| return PositionTemplate<Strategy>( |
| node, text_state_.PositionStartOffset() + char16_offset + 1); |
| } |
| if (node.IsTextNode()) { |
| if (text_state_.IsBeforePositionNode()) |
| return PositionTemplate<Strategy>(node, 0); |
| return PositionTemplate<Strategy>(node, ToText(node).length()); |
| } |
| if (text_state_.IsBeforePositionNode()) |
| return PositionTemplate<Strategy>::BeforeNode(node); |
| DCHECK(!text_state_.IsBeforeChildren()); |
| return PositionTemplate<Strategy>::AfterNode(node); |
| } |
| |
| template <typename Strategy> |
| PositionTemplate<Strategy> |
| TextIteratorAlgorithm<Strategy>::StartPositionInCurrentContainer() const { |
| return PositionTemplate<Strategy>::EditingPositionOf( |
| &CurrentContainer(), StartOffsetInCurrentContainer()); |
| } |
| |
| template <typename Strategy> |
| PositionTemplate<Strategy> |
| TextIteratorAlgorithm<Strategy>::EndPositionInCurrentContainer() const { |
| return PositionTemplate<Strategy>::EditingPositionOf( |
| &CurrentContainer(), EndOffsetInCurrentContainer()); |
| } |
| |
| template <typename Strategy> |
| int TextIteratorAlgorithm<Strategy>::RangeLength( |
| const PositionTemplate<Strategy>& start, |
| const PositionTemplate<Strategy>& end, |
| const TextIteratorBehavior& behavior) { |
| DCHECK(start.GetDocument()); |
| DocumentLifecycle::DisallowTransitionScope disallow_transition( |
| start.GetDocument()->Lifecycle()); |
| |
| int length = 0; |
| for (TextIteratorAlgorithm<Strategy> it(start, end, behavior); !it.AtEnd(); |
| it.Advance()) |
| length += it.length(); |
| |
| return length; |
| } |
| |
| template <typename Strategy> |
| int TextIteratorAlgorithm<Strategy>::RangeLength( |
| const EphemeralRangeTemplate<Strategy>& range, |
| const TextIteratorBehavior& behavior) { |
| return RangeLength(range.StartPosition(), range.EndPosition(), behavior); |
| } |
| |
| template <typename Strategy> |
| bool TextIteratorAlgorithm<Strategy>::IsBetweenSurrogatePair( |
| unsigned position) const { |
| return position > 0 && position < static_cast<unsigned>(length()) && |
| U16_IS_LEAD(CharacterAt(position - 1)) && |
| U16_IS_TRAIL(CharacterAt(position)); |
| } |
| |
| template <typename Strategy> |
| int TextIteratorAlgorithm<Strategy>::CopyTextTo(ForwardsTextBuffer* output, |
| int position, |
| int min_length) const { |
| unsigned end = std::min(length(), position + min_length); |
| if (IsBetweenSurrogatePair(end)) |
| ++end; |
| unsigned copied_length = end - position; |
| CopyCodeUnitsTo(output, position, copied_length); |
| return copied_length; |
| } |
| |
| template <typename Strategy> |
| int TextIteratorAlgorithm<Strategy>::CopyTextTo(ForwardsTextBuffer* output, |
| int position) const { |
| return CopyTextTo(output, position, length() - position); |
| } |
| |
| template <typename Strategy> |
| void TextIteratorAlgorithm<Strategy>::CopyCodeUnitsTo( |
| ForwardsTextBuffer* output, |
| unsigned position, |
| unsigned copy_length) const { |
| text_state_.AppendTextTo(output, position, copy_length); |
| } |
| |
| // -------- |
| |
| template <typename Strategy> |
| static String CreatePlainText(const EphemeralRangeTemplate<Strategy>& range, |
| const TextIteratorBehavior& behavior) { |
| if (range.IsNull()) |
| return g_empty_string; |
| |
| DocumentLifecycle::DisallowTransitionScope disallow_transition( |
| range.StartPosition().GetDocument()->Lifecycle()); |
| |
| TextIteratorAlgorithm<Strategy> it(range.StartPosition(), range.EndPosition(), |
| behavior); |
| |
| if (it.AtEnd()) |
| return g_empty_string; |
| |
| // The initial buffer size can be critical for performance: |
| // https://bugs.webkit.org/show_bug.cgi?id=81192 |
| static const unsigned kInitialCapacity = 1 << 15; |
| |
| StringBuilder builder; |
| builder.ReserveCapacity(kInitialCapacity); |
| |
| for (; !it.AtEnd(); it.Advance()) |
| it.GetText().AppendTextToStringBuilder(builder); |
| |
| if (builder.IsEmpty()) |
| return g_empty_string; |
| |
| return builder.ToString(); |
| } |
| |
| String PlainText(const EphemeralRange& range, |
| const TextIteratorBehavior& behavior) { |
| return CreatePlainText<EditingStrategy>(range, behavior); |
| } |
| |
| String PlainText(const EphemeralRangeInFlatTree& range, |
| const TextIteratorBehavior& behavior) { |
| return CreatePlainText<EditingInFlatTreeStrategy>(range, behavior); |
| } |
| |
| template class CORE_TEMPLATE_EXPORT TextIteratorAlgorithm<EditingStrategy>; |
| template class CORE_TEMPLATE_EXPORT |
| TextIteratorAlgorithm<EditingInFlatTreeStrategy>; |
| |
| } // namespace blink |