| Index: third_party/WebKit/Source/core/editing/SelectionEditor.cpp
|
| diff --git a/third_party/WebKit/Source/core/editing/SelectionEditor.cpp b/third_party/WebKit/Source/core/editing/SelectionEditor.cpp
|
| index 8206ecf15d3debae364272601af153a1bff8911d..42d97add4e8eeae89b1c5123b1f75228b9663e69 100644
|
| --- a/third_party/WebKit/Source/core/editing/SelectionEditor.cpp
|
| +++ b/third_party/WebKit/Source/core/editing/SelectionEditor.cpp
|
| @@ -25,27 +25,56 @@
|
|
|
| #include "core/editing/SelectionEditor.h"
|
|
|
| +#include "core/dom/NodeWithIndex.h"
|
| +#include "core/dom/Text.h"
|
| #include "core/editing/EditingUtilities.h"
|
| #include "core/editing/Editor.h"
|
| #include "core/editing/SelectionAdjuster.h"
|
| #include "core/frame/LocalFrame.h"
|
| +#include <iterator>
|
| +#include <ostream> // NOLINT
|
|
|
| namespace blink {
|
|
|
| +#define FOR_EACH_STATE(V) \
|
| + V(VisibleSelectionMayBeValid) \
|
| + V(DOMTreeIsChanged) \
|
| + V(DOMTreeIsChanging) \
|
| + V(SelectionInDOMTreeIsSource)
|
| +
|
| +enum class SelectionEditor::CacheState {
|
| +#define V(name) name,
|
| + FOR_EACH_STATE(V)
|
| +#undef V
|
| +};
|
| +
|
| +std::ostream& operator<<(std::ostream& ostream,
|
| + SelectionEditor::CacheState state) {
|
| + static const char* names[] = {
|
| +#define V(name) #name,
|
| + FOR_EACH_STATE(V)
|
| +#undef V
|
| + };
|
| + const auto& it = std::begin(names) + static_cast<size_t>(state);
|
| + if (it < std::begin(names) || it >= std::end(names))
|
| + return ostream << "CacheState::Bad(" << static_cast<int>(state) << ')';
|
| + return ostream << *it;
|
| +}
|
| +
|
| SelectionEditor::SelectionEditor(LocalFrame& frame)
|
| - : m_frame(frame), m_observingVisibleSelection(false) {
|
| + : m_frame(frame), m_cacheState(CacheState::VisibleSelectionMayBeValid) {
|
| clearVisibleSelection();
|
| }
|
|
|
| SelectionEditor::~SelectionEditor() {}
|
|
|
| void SelectionEditor::clearVisibleSelection() {
|
| - m_selection = VisibleSelection();
|
| - m_selectionInFlatTree = VisibleSelectionInFlatTree();
|
| + m_selection = SelectionInDOMTree();
|
| + m_cachedVisibleSelectionInDOMTree = VisibleSelection();
|
| + m_cachedVisibleSelectionInFlatTree = VisibleSelectionInFlatTree();
|
| if (!shouldAlwaysUseDirectionalSelection())
|
| return;
|
| - m_selection.setIsDirectional(true);
|
| - m_selectionInFlatTree.setIsDirectional(true);
|
| + m_selection.m_isDirectional = true;
|
| }
|
|
|
| void SelectionEditor::dispose() {
|
| @@ -53,83 +82,356 @@ void SelectionEditor::dispose() {
|
| clearVisibleSelection();
|
| }
|
|
|
| -const Document& SelectionEditor::document() const {
|
| - DCHECK(m_document);
|
| - return *m_document;
|
| +Document& SelectionEditor::document() const {
|
| + DCHECK(lifecycleContext());
|
| + return *lifecycleContext();
|
| +}
|
| +
|
| +const SelectionInDOMTree& SelectionEditor::selectionInDOMTree() const {
|
| + m_selection.assertValidFor(document());
|
| + return m_selection;
|
| +}
|
| +
|
| +bool SelectionEditor::hasEditableStyle() const {
|
| + return computeVisibleSelectionInDOMTree().hasEditableStyle();
|
| +}
|
| +
|
| +bool SelectionEditor::isContentEditable() const {
|
| + return computeVisibleSelectionInDOMTree().isContentEditable();
|
| }
|
|
|
| -template <>
|
| -const VisibleSelection& SelectionEditor::visibleSelection<EditingStrategy>()
|
| +bool SelectionEditor::isContentRichlyEditable() const {
|
| + return computeVisibleSelectionInDOMTree().isContentRichlyEditable();
|
| +}
|
| +
|
| +const VisibleSelection& SelectionEditor::computeVisibleSelectionInDOMTree()
|
| const {
|
| DCHECK_EQ(frame()->document(), document());
|
| DCHECK_EQ(frame(), document().frame());
|
| - if (m_selection.isNone())
|
| - return m_selection;
|
| - DCHECK_EQ(m_selection.base().document(), document());
|
| - return m_selection;
|
| + const_cast<SelectionEditor*>(this)->updateCachedVisibleSelectionIfNeeded();
|
| + if (m_cachedVisibleSelectionInDOMTree.isNone())
|
| + return m_cachedVisibleSelectionInDOMTree;
|
| + DCHECK_EQ(m_cachedVisibleSelectionInDOMTree.base().document(), document());
|
| + return m_cachedVisibleSelectionInDOMTree;
|
| }
|
|
|
| -template <>
|
| const VisibleSelectionInFlatTree&
|
| -SelectionEditor::visibleSelection<EditingInFlatTreeStrategy>() const {
|
| +SelectionEditor::computeVisibleSelectionInFlatTree() const {
|
| DCHECK_EQ(frame()->document(), document());
|
| DCHECK_EQ(frame(), document().frame());
|
| - if (m_selectionInFlatTree.isNone())
|
| - return m_selectionInFlatTree;
|
| - DCHECK_EQ(m_selectionInFlatTree.base().document(), document());
|
| - return m_selectionInFlatTree;
|
| + const_cast<SelectionEditor*>(this)->updateCachedVisibleSelectionIfNeeded();
|
| + if (m_cachedVisibleSelectionInFlatTree.isNone())
|
| + return m_cachedVisibleSelectionInFlatTree;
|
| + DCHECK_EQ(m_cachedVisibleSelectionInFlatTree.base().document(), document());
|
| + return m_cachedVisibleSelectionInFlatTree;
|
| }
|
|
|
| -void SelectionEditor::setVisibleSelection(
|
| - const VisibleSelection& newSelection,
|
| - FrameSelection::SetSelectionOptions options) {
|
| - DCHECK(newSelection.isValidFor(document())) << newSelection;
|
| +void SelectionEditor::setSelection(const SelectionInDOMTree& newSelection) {
|
| + m_selection.assertValidFor(document());
|
| + newSelection.assertValidFor(document());
|
| + if (m_selection == newSelection)
|
| + return;
|
| resetLogicalRange();
|
| + m_cacheState = CacheState::SelectionInDOMTreeIsSource;
|
| + m_cachedVisibleSelectionInFlatTree = VisibleSelectionInFlatTree();
|
| + m_cachedVisibleSelectionInDOMTree = VisibleSelection();
|
| +
|
| m_selection = newSelection;
|
| - if (options & FrameSelection::DoNotAdjustInFlatTree) {
|
| - m_selectionInFlatTree.setWithoutValidation(
|
| - toPositionInFlatTree(m_selection.base()),
|
| - toPositionInFlatTree(m_selection.extent()));
|
| +
|
| + m_domTreeVersion = document().domTreeVersion();
|
| + m_styleVersion = document().styleVersion();
|
| +}
|
| +
|
| +void SelectionEditor::didChangeAttribute(const Element&) {
|
| + didFinishDOMMutation();
|
| +}
|
| +
|
| +void SelectionEditor::didChangeChildren(const ContainerNode&) {
|
| + didFinishDOMMutation();
|
| +}
|
| +
|
| +void SelectionEditor::didMoveTreeToNewDocument() {
|
| + didFinishDOMMutation();
|
| +}
|
| +
|
| +static Position computePositionForChildrenRemoval(const Position& position,
|
| + ContainerNode& container) {
|
| + Node* node = position.computeContainerNode();
|
| + if (container.containsIncludingHostElements(*node))
|
| + return Position::firstPositionInNode(&container);
|
| + return position;
|
| +}
|
| +
|
| +void SelectionEditor::nodeChildrenWillBeRemoved(ContainerNode& container) {
|
| + if (m_selection.isNone())
|
| + return;
|
| + const Position oldBase = m_selection.m_base;
|
| + const Position oldExtent = m_selection.m_extent;
|
| + const Position& newBase =
|
| + computePositionForChildrenRemoval(oldBase, container);
|
| + const Position& newExtent =
|
| + computePositionForChildrenRemoval(oldExtent, container);
|
| + if (newBase == oldBase && newExtent == oldExtent)
|
| return;
|
| + m_selection = SelectionInDOMTree::Builder()
|
| + .setBaseAndExtent(newBase, newExtent)
|
| + .build();
|
| + m_cachedVisibleSelectionInDOMTree = VisibleSelection();
|
| + m_cachedVisibleSelectionInFlatTree = VisibleSelectionInFlatTree();
|
| +}
|
| +
|
| +static Position positionAfterPreviousSiblingOf(const Node& node) {
|
| + Node* const previousSibling = NodeTraversal::previousSibling(node);
|
| + if (!previousSibling) {
|
| + // Returns |BeforeChildren| of a parent of |node| since |node| is the
|
| + // first child.
|
| + return Position::firstPositionInNode(NodeTraversal::parent(node));
|
| + }
|
| +#if 0
|
| + // When enable this code fragment,
|
| + // "editing/selection/mouse/drag-user-select-all-textarea.html" is failed.
|
| + //
|
| + // When disable this code fragment,
|
| + // "editing/selection/selection-after-text-normalize.html" is failed.
|
| + if (previousSibling->isCharacterDataNode()) {
|
| + return Position(previousSibling,
|
| + toCharacterData(previousSibling)->length());
|
| }
|
| +#endif
|
| + return Position::afterNode(previousSibling);
|
| +}
|
|
|
| - SelectionAdjuster::adjustSelectionInFlatTree(&m_selectionInFlatTree,
|
| - m_selection);
|
| +static Position computePositionForNodeRemoval(const Position& position,
|
| + Node& nodeToBeRemoved) {
|
| + Node* const anchorNode = position.anchorNode();
|
| + if (anchorNode == nodeToBeRemoved)
|
| + return positionAfterPreviousSiblingOf(nodeToBeRemoved);
|
| + if (position.isOffsetInAnchor() &&
|
| + anchorNode == nodeToBeRemoved.parentNode()) {
|
| + const int positionOffset = position.offsetInContainerNode();
|
| + if (positionOffset == 0)
|
| + return position;
|
| + // |nodeToBeRemoved| is sibling or itself of |position|.
|
| + int offset = 0;
|
| + for (Node& child :
|
| + NodeTraversal::childrenOf(*nodeToBeRemoved.parentNode())) {
|
| + if (nodeToBeRemoved == child) {
|
| + // |position| is after |nodeToBeRemoved|.
|
| + return Position(anchorNode, positionOffset - 1);
|
| + }
|
| + if (positionOffset == offset) {
|
| + // |position| is before |nodeToBeRemoved|.
|
| + return position;
|
| + }
|
| + ++offset;
|
| + }
|
| + NOTREACHED() << position << " should be handled.";
|
| + }
|
| + if (!nodeToBeRemoved.isContainerNode())
|
| + return position;
|
| + if (toContainerNode(nodeToBeRemoved)
|
| + .containsIncludingHostElements(*anchorNode))
|
| + return positionAfterPreviousSiblingOf(nodeToBeRemoved);
|
| + return position;
|
| }
|
|
|
| -void SelectionEditor::setVisibleSelection(
|
| - const VisibleSelectionInFlatTree& newSelection,
|
| - FrameSelection::SetSelectionOptions options) {
|
| - DCHECK(newSelection.isValidFor(document())) << newSelection;
|
| - DCHECK(!(options & FrameSelection::DoNotAdjustInFlatTree));
|
| - resetLogicalRange();
|
| - m_selectionInFlatTree = newSelection;
|
| - SelectionAdjuster::adjustSelectionInDOMTree(&m_selection,
|
| - m_selectionInFlatTree);
|
| +static Position updatePositionAfterAdoptingTextReplacement(
|
| + const Position& position,
|
| + CharacterData* node,
|
| + unsigned offset,
|
| + unsigned oldLength,
|
| + unsigned newLength) {
|
| + if (position.anchorNode() != node)
|
| + return position;
|
| +
|
| + if (position.isBeforeAnchor()) {
|
| + return updatePositionAfterAdoptingTextReplacement(
|
| + Position(node, 0), node, offset, oldLength, newLength);
|
| + }
|
| + if (position.isAfterAnchor()) {
|
| + return updatePositionAfterAdoptingTextReplacement(
|
| + Position(node, oldLength), node, offset, oldLength, newLength);
|
| + }
|
| +
|
| + // See:
|
| + // http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html#Level-2-Range-Mutation
|
| + DCHECK_GE(position.offsetInContainerNode(), 0);
|
| + unsigned positionOffset =
|
| + static_cast<unsigned>(position.offsetInContainerNode());
|
| + // Replacing text can be viewed as a deletion followed by insertion.
|
| + if (positionOffset >= offset && positionOffset <= offset + oldLength)
|
| + positionOffset = offset;
|
| +
|
| + // Adjust the offset if the position is after the end of the deleted contents
|
| + // (positionOffset > offset + oldLength) to avoid having a stale offset.
|
| + if (positionOffset > offset + oldLength)
|
| + positionOffset = positionOffset - oldLength + newLength;
|
| +
|
| + // Due to case folding
|
| + // (http://unicode.org/Public/UCD/latest/ucd/CaseFolding.txt),
|
| + // LayoutText length may be different from Text length. A correct
|
| + // implementation
|
| + // would translate the LayoutText offset to a Text offset; this is just a
|
| + // safety
|
| + // precaution to avoid offset values that run off the end of the Text.
|
| + if (positionOffset > node->length())
|
| + positionOffset = node->length();
|
| +
|
| + return Position(node, positionOffset);
|
| }
|
|
|
| -void SelectionEditor::setWithoutValidation(const Position& base,
|
| - const Position& extent) {
|
| - resetLogicalRange();
|
| - if (base.isNotNull())
|
| - DCHECK_EQ(base.document(), document());
|
| - if (extent.isNotNull())
|
| - DCHECK_EQ(extent.document(), document());
|
| - m_selection.setWithoutValidation(base, extent);
|
| - m_selectionInFlatTree.setWithoutValidation(toPositionInFlatTree(base),
|
| - toPositionInFlatTree(extent));
|
| +void SelectionEditor::didUpdateCharacterData(CharacterData* node,
|
| + unsigned offset,
|
| + unsigned oldLength,
|
| + unsigned newLength) {
|
| + // The fragment check is a performance optimization. See
|
| + // http://trac.webkit.org/changeset/30062.
|
| + if (m_selection.isNone() || !node || !node->isConnected()) {
|
| + didFinishDOMMutation();
|
| + return;
|
| + }
|
| + const Position& newBase = updatePositionAfterAdoptingTextReplacement(
|
| + m_selection.m_base, node, offset, oldLength, newLength);
|
| + const Position& newExtent = updatePositionAfterAdoptingTextReplacement(
|
| + m_selection.m_extent, node, offset, oldLength, newLength);
|
| + didFinishTextChange(newBase, newExtent);
|
| +}
|
| +
|
| +static Position updatePostionAfterAdoptingTextNodesMerged(
|
| + const Position& position,
|
| + const Text& mergedNode,
|
| + const NodeWithIndex& nodeToBeRemovedWithIndex,
|
| + unsigned oldLength) {
|
| + Node* const anchorNode = position.anchorNode();
|
| + const Node& nodeToBeRemoved = nodeToBeRemovedWithIndex.node();
|
| + switch (position.anchorType()) {
|
| + case PositionAnchorType::BeforeChildren:
|
| + case PositionAnchorType::AfterChildren:
|
| + return position;
|
| + case PositionAnchorType::BeforeAnchor:
|
| + if (anchorNode == nodeToBeRemoved)
|
| + return Position(mergedNode, mergedNode.length());
|
| + return position;
|
| + case PositionAnchorType::AfterAnchor:
|
| + if (anchorNode == nodeToBeRemoved)
|
| + return Position(mergedNode, mergedNode.length());
|
| + if (anchorNode == mergedNode)
|
| + return Position(mergedNode, oldLength);
|
| + return position;
|
| + case PositionAnchorType::OffsetInAnchor: {
|
| + const int offset = position.offsetInContainerNode();
|
| + if (anchorNode == nodeToBeRemoved)
|
| + return Position(mergedNode, oldLength + offset);
|
| + if (anchorNode == nodeToBeRemoved.parentNode() &&
|
| + offset == nodeToBeRemovedWithIndex.index()) {
|
| + return Position(mergedNode, oldLength);
|
| + }
|
| + return position;
|
| + }
|
| + }
|
| + NOTREACHED() << position;
|
| + return position;
|
| +}
|
| +
|
| +void SelectionEditor::didMergeTextNodes(
|
| + const Text& mergedNode,
|
| + const NodeWithIndex& nodeToBeRemovedWithIndex,
|
| + unsigned oldLength) {
|
| + if (m_selection.isNone()) {
|
| + didFinishDOMMutation();
|
| + return;
|
| + }
|
| + const Position& newBase = updatePostionAfterAdoptingTextNodesMerged(
|
| + m_selection.m_base, mergedNode, nodeToBeRemovedWithIndex, oldLength);
|
| + const Position& newExtent = updatePostionAfterAdoptingTextNodesMerged(
|
| + m_selection.m_extent, mergedNode, nodeToBeRemovedWithIndex, oldLength);
|
| + didFinishTextChange(newBase, newExtent);
|
| +}
|
| +
|
| +static Position updatePostionAfterAdoptingTextNodeSplit(
|
| + const Position& position,
|
| + const Text& oldNode) {
|
| + if (position.anchorNode() != &oldNode || !position.isOffsetInAnchor())
|
| + return position;
|
| + // See:
|
| + // http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html#Level-2-Range-Mutation
|
| + DCHECK_GE(position.offsetInContainerNode(), 0);
|
| + unsigned positionOffset =
|
| + static_cast<unsigned>(position.offsetInContainerNode());
|
| + unsigned oldLength = oldNode.length();
|
| + if (positionOffset <= oldLength)
|
| + return position;
|
| + return Position(toText(oldNode.nextSibling()), positionOffset - oldLength);
|
| +}
|
| +
|
| +void SelectionEditor::didSplitTextNode(const Text& oldNode) {
|
| + if (m_selection.isNone() || !oldNode.isConnected()) {
|
| + didFinishDOMMutation();
|
| + return;
|
| + }
|
| + const Position& newBase =
|
| + updatePostionAfterAdoptingTextNodeSplit(m_selection.m_base, oldNode);
|
| + const Position& newExtent =
|
| + updatePostionAfterAdoptingTextNodeSplit(m_selection.m_extent, oldNode);
|
| + didFinishTextChange(newBase, newExtent);
|
| +}
|
| +
|
| +void SelectionEditor::didFinishTextChange(const Position& newBase,
|
| + const Position& newExtent) {
|
| + if (newBase != m_selection.m_base || newExtent != m_selection.m_extent) {
|
| + m_selection.m_base = newBase;
|
| + m_selection.m_extent = newExtent;
|
| + m_cachedVisibleSelectionInDOMTree = VisibleSelection();
|
| + m_cachedVisibleSelectionInFlatTree = VisibleSelectionInFlatTree();
|
| + }
|
| + didFinishDOMMutation();
|
| +}
|
| +
|
| +void SelectionEditor::didFinishDOMMutation() {
|
| + m_domTreeVersion = document().domTreeVersion();
|
| +#if ENABLE(ASSERT)
|
| + m_selection.m_domTreeVersion = m_domTreeVersion;
|
| +#endif
|
| + m_cacheState = CacheState::DOMTreeIsChanged;
|
| + // TODO(yosin): Should we notify selection change?
|
| + m_selection.assertValidFor(document());
|
| +}
|
| +
|
| +void SelectionEditor::nodeWillBeRemoved(Node& nodeToBeRemoved) {
|
| + if (m_selection.isNone())
|
| + return;
|
| + const Position oldBase = m_selection.m_base;
|
| + const Position oldExtent = m_selection.m_extent;
|
| + const Position& newBase =
|
| + computePositionForNodeRemoval(oldBase, nodeToBeRemoved);
|
| + const Position& newExtent =
|
| + computePositionForNodeRemoval(oldExtent, nodeToBeRemoved);
|
| + if (newBase == oldBase && newExtent == oldExtent)
|
| + return;
|
| + m_cacheState = CacheState::DOMTreeIsChanging;
|
| + m_selection = SelectionInDOMTree::Builder()
|
| + .setBaseAndExtent(newBase, newExtent)
|
| + .build();
|
| + m_cachedVisibleSelectionInDOMTree = VisibleSelection();
|
| + m_cachedVisibleSelectionInFlatTree = VisibleSelectionInFlatTree();
|
| }
|
|
|
| void SelectionEditor::documentAttached(Document* document) {
|
| DCHECK(document);
|
| - DCHECK(!m_document) << m_document;
|
| - m_document = document;
|
| + DCHECK(!lifecycleContext()) << lifecycleContext();
|
| + m_domTreeVersion = document->domTreeVersion();
|
| + m_styleVersion = static_cast<uint64_t>(-1);
|
| + clearVisibleSelection();
|
| + setContext(document);
|
| }
|
|
|
| -void SelectionEditor::documentDetached(const Document& document) {
|
| - DCHECK_EQ(m_document, &document);
|
| +void SelectionEditor::contextDestroyed(Document*) {
|
| dispose();
|
| - m_document = nullptr;
|
| + m_domTreeVersion = 0;
|
| + m_styleVersion = static_cast<uint64_t>(-1);
|
| + m_selection = SelectionInDOMTree();
|
| + m_cachedVisibleSelectionInDOMTree = VisibleSelection();
|
| + m_cachedVisibleSelectionInFlatTree = VisibleSelectionInFlatTree();
|
| }
|
|
|
| void SelectionEditor::resetLogicalRange() {
|
| @@ -150,7 +452,7 @@ void SelectionEditor::setLogicalRange(Range* range) {
|
| Range* SelectionEditor::firstRange() const {
|
| if (m_logicalRange)
|
| return m_logicalRange->cloneRange();
|
| - return firstRangeOf(m_selection);
|
| + return firstRangeOf(computeVisibleSelectionInDOMTree());
|
| }
|
|
|
| bool SelectionEditor::shouldAlwaysUseDirectionalSelection() const {
|
| @@ -158,18 +460,107 @@ bool SelectionEditor::shouldAlwaysUseDirectionalSelection() const {
|
| }
|
|
|
| void SelectionEditor::updateIfNeeded() {
|
| - DCHECK(m_selection.isValidFor(document())) << m_selection;
|
| - DCHECK(m_selectionInFlatTree.isValidFor(document())) << m_selection;
|
| - m_selection.updateIfNeeded();
|
| - m_selectionInFlatTree.updateIfNeeded();
|
| + // TODO(yosin): We should unify |SelectionEditor::updateIfNeeded()| and
|
| + // |updateCachedVisibleSelectionIfNeeded()|
|
| + updateCachedVisibleSelectionIfNeeded();
|
| +}
|
| +
|
| +class SelectionEditor::CacheUpdateScope {
|
| + STACK_ALLOCATED();
|
| +
|
| + public:
|
| + CacheUpdateScope(SelectionEditor*);
|
| + ~CacheUpdateScope();
|
| +
|
| + private:
|
| + DEFINE_INLINE_TRACE() { visitor->trace(m_editor); }
|
| +
|
| + Member<SelectionEditor> m_editor;
|
| + DISALLOW_COPY_AND_ASSIGN(CacheUpdateScope);
|
| +};
|
| +
|
| +SelectionEditor::CacheUpdateScope::CacheUpdateScope(SelectionEditor* editor)
|
| + : m_editor(editor) {
|
| + m_editor->m_isUpdatingCachedSelection = true;
|
| +}
|
| +
|
| +SelectionEditor::CacheUpdateScope::~CacheUpdateScope() {
|
| + m_editor->m_styleVersion = m_editor->document().styleVersion();
|
| + m_editor->m_cacheState = CacheState::VisibleSelectionMayBeValid;
|
| + m_editor->m_isUpdatingCachedSelection = false;
|
| +}
|
| +
|
| +bool SelectionEditor::needsUpdateVisibleSelection() const {
|
| + DCHECK_EQ(m_domTreeVersion, document().domTreeVersion());
|
| + if (m_cacheState != CacheState::VisibleSelectionMayBeValid)
|
| + return true;
|
| + return m_styleVersion != document().styleVersion();
|
| +}
|
| +
|
| +void SelectionEditor::updateCachedVisibleSelectionIfNeeded() {
|
| + DCHECK(!m_isUpdatingCachedSelection)
|
| + << "Recursive update should not be happened.";
|
| + m_selection.assertValidFor(document());
|
| +
|
| + // We should use |DCHECK(!document().needsLayoutTreeUpdate())| but following
|
| + // tests are failed:
|
| + // - media/track/css-cue-for-video-in-shadow-2.html
|
| + // - media/track/css-cue-for-video-in-shadow.html
|
| + // - media/track/cue-style-invalidation.html
|
| + // - media/track/opera/track/webvtt/parsing/001.html
|
| + // - media/track/text-track-selection-menu-add-track.html
|
| + // - media/track/track-css-all-cues.html
|
| + // - media/track/track-css-cue-lifetime.html [ Crash
|
| + // Note: Since we |FrameCaret::updateApperance()| is called from
|
| + // |FrameView::performPostLayoutTasks()|, we check lifecycle against
|
| + // |AfterPerformLayout| instead of |LayoutClean|.
|
| + DCHECK_GE(document().lifecycle().state(),
|
| + DocumentLifecycle::AfterPerformLayout);
|
| + if (!needsUpdateVisibleSelection())
|
| + return;
|
| +
|
| + CacheUpdateScope updatingCache(this);
|
| + switch (m_cacheState) {
|
| + case CacheState::VisibleSelectionMayBeValid:
|
| + // TDOO(yosin): We should study why
|
| + // createVisibleSelection(m_cachedVisibleSelectionInDOMTree.asSelection())
|
| + // not
|
| + // work.
|
| + m_cachedVisibleSelectionInDOMTree.validate(
|
| + m_cachedVisibleSelectionInDOMTree.m_granularity);
|
| + m_cachedVisibleSelectionInFlatTree.validate(
|
| + m_cachedVisibleSelectionInFlatTree.m_granularity);
|
| + return;
|
| + case CacheState::DOMTreeIsChanged:
|
| + case CacheState::SelectionInDOMTreeIsSource: {
|
| + // TODO(yosin): We should get rid of |adjustSelectionInFlatTree| in
|
| + // |SelectionAdjuster|.
|
| + m_cachedVisibleSelectionInDOMTree = createVisibleSelection(m_selection);
|
| + m_cachedVisibleSelectionInFlatTree = createVisibleSelection(
|
| + SelectionInFlatTree::Builder()
|
| + .setBaseAndExtent(toPositionInFlatTree(m_selection.base()),
|
| + toPositionInFlatTree(m_selection.extent()))
|
| + .setAffinity(m_selection.affinity())
|
| + .setHasTrailingWhitespace(m_selection.hasTrailingWhitespace())
|
| + .setGranularity(m_selection.granularity())
|
| + .setIsDirectional(m_selection.isDirectional())
|
| + .build());
|
| + return;
|
| + }
|
| + case CacheState::DOMTreeIsChanging:
|
| + NOTREACHED() << "We should not update selection during DOM mutation";
|
| + return;
|
| + }
|
| + NOTREACHED() << "We should handle " << m_cacheState;
|
| }
|
|
|
| DEFINE_TRACE(SelectionEditor) {
|
| - visitor->trace(m_document);
|
| visitor->trace(m_frame);
|
| visitor->trace(m_selection);
|
| - visitor->trace(m_selectionInFlatTree);
|
| + visitor->trace(m_cachedVisibleSelectionInDOMTree);
|
| + visitor->trace(m_cachedVisibleSelectionInFlatTree);
|
| visitor->trace(m_logicalRange);
|
| + SynchronousMutationObserver::trace(visitor);
|
| }
|
|
|
| } // namespace blink
|
|
|