⚠ Archived content — this site is no longer maintained.   Current WebKit documentation is at docs.webkit.org.

Changeset 267329 in webkit


Ignore:
Timestamp:
Sep 20, 2020, 12:37:43 PM (6 years ago)
Author:
Darin Adler
Message:

Selection API: Further improvements to VisibleSelection, FrameSelection, and DOMSelection to preserve anchor and focus
https://bugs.webkit.org/show_bug.cgi?id=216739

Reviewed by Ryosuke Niwa.

Source/WebCore:

If done correctly, these changes should have almost no effect unless live range selection
is enabled. This adds a feature where VisibleSelection tracks the original points,
before the VisiblePosition-style canonicalization. It also starts a transition from
the base/extent terminology to the slightly-easier-to-understand anchor/focus terminology
that is used in the selection specification. New functions name them anchor and focus,
even though we still have some old ones that call them base and extent. In addition,
made some other small related improvements to live range selection mode, without changing
behavior when not in that mode. Have been testing these locally by turning on that mode
and running various tests.

  • editing/FrameSelection.cpp:

(WebCore::FrameSelection::isInDocumentTree const): Added. Used by DOMSelection to
correctly handle cases where the selection is in a shadow tree without requiring it
actually create the associated live range just to query.
(WebCore::FrameSelection::associatedLiveRange): Use the new VisibleSelection::range,
which gives us non-canonicalized points, rather than the deprecated
VisibleSelection::firstRange, which we will eventually be renaming or deleting.
(WebCore::FrameSelection::updateFromAssociatedLiveRange): Use
makeContainerOffsetPosition instead of implicitly using makeDeprecatedLegacyPosition.
Also removed the FIXME about normalization of endpoints, which is what this patch
mostly addresses; no need to track with a comment now.
(WebCore::FrameSelection::updateAssociatedLiveRange): Use VisibleSelection::range
for the same reason as above.

  • editing/FrameSelection.h: Added isInDocumentTree.
  • editing/VisibleSelection.cpp:

(WebCore::VisibleSelection::VisibleSelection): Updated the primary constructor to set
m_anchor/focus and let the validate function set m_base/extent/start/end/baseIsFirst.
Changed most other constructors to call that primary one.
(WebCore::VisibleSelection::anchor const): Added. This is a non-canonicalized position.
(WebCore::VisibleSelection::focus const): Ditto.
(WebCore::VisibleSelection::uncanonicalizedStart const): Added. This non-canonicalized
version of start is used by new DOMSelection code, but eventually should be used
elsewhere as well, and we may eventually eliminate the canonicalized version.
(WebCore::VisibleSelection::uncanonicalizedEnd const): Ditto.
(WebCore::VisibleSelection::range const): Added. This non-canonicalized version of
firstRange has many of the same considerations as the other four functions above.
(WebCore::VisibleSelection::setBase): Set m_anchor instead of m_base. Eventually we
will want to rename this function, too, but there is no need to do that now.
(WebCore::VisibleSelection::setExtent): Ditto.
(WebCore::VisibleSelection::setBaseAndExtentToDeepEquivalents): Rewrote to take
m_anchor and m_focus as inputs and now m_base and m_extent are pure outputs.
May want to rename this later, because "deep equivalents" is a funny way to say
that this function sets base and extent to canonicalized values.
(WebCore::VisibleSelection::adjustSelectionRespectingGranularity): Renamed from
setStartAndEndFromBaseAndExtentRespectingGranularity. This now takes m_start/end
as input as well as output, because the validate function needs to check
if the function changes m_start/end so it can update m_anchor/focus. Also did a tiny
bit of refactoring to use std::swap.
(WebCore::VisibleSelection::validate): Refined the code to handle canonicalization
differently from expanding due to granularity. Canonicalization is done preserving
the original m_anchor/focus as it mostly always has been. But expansion due to
granularity now changes m_anchor/focus/base/extent rather than just m_start/end.
This makes sense because granularity changes actually affect what is selected,
while canonicalization just changes how the selection is expressed and tracked.
(WebCore::VisibleSelection::setWithoutValidation): Set m_anchor/focus. In the future,
we can probably get rid of this eventually, because the "validation" talked about
here is the unwanted canonicalization. But that's a refinement for the future.
(WebCore::VisibleSelection::adjustSelectionToAvoidCrossingShadowBoundaries):
Update m_focus, not just m_extent, if we have to adjust. Also removed an unnnecessary
null check from the start of the function.
(WebCore::VisibleSelection::adjustSelectionToAvoidCrossingEditingBoundaries): Ditto.
Also removed the assertions because these situations can indeed arise and it's not
necessarily an indication of a bug in editing code.

  • editing/VisibleSelection.h: Rearranged constructors to make it slightly more clear

which is the main one and that the others are simply convenience shorthands. Might
want to come back later and remove some unused ones. Added uncanonicalizedStart,
uncanonicalizedEnd, anchor, focus, and range. Renamed the private function called
setStartAndEndFromBaseAndExtentRespectingGranularity to the new name
adjustSelectionRespectingGranularity. Added m_anchor and m_focus and refined the
comments about the position data members. We should eventually be able to cut down
on the number of these again, but it's fine to have two more for now.

  • page/DOMSelection.cpp:

(WebCore::DOMSelection::range const): Use range rather than firstRange when live
range selection is enabled, for proper round-tripping of selection endpoints,
unaffected by canonicalization. The other changes below are for the same reason.
(WebCore::DOMSelection::anchorPosition const): Use anchor.
(WebCore::DOMSelection::focusPosition const): Use focus.
(WebCore::DOMSelection::basePosition const): Use anchor.
(WebCore::DOMSelection::extentPosition const): Use focus.
(WebCore::DOMSelection::type const): Use isInDocumentTree and range rather than
isNone/isCaret/isRange when live range selection is enabled, because this needs
to report "Caret" when the range is not collapsed, even if the canonicalized
range is collapsed and "None" when the selection is in a shadow tree.
(WebCore::DOMSelection::rangeCount const): Use isInDocumentTree to check if
there is a selection rather than isNone to correctly handle the shadow tree case.
(WebCore::DOMSelection::collapseToEnd): Use uncanonicalizedEnd.
(WebCore::DOMSelection::collapseToStart): Use uncanonicalizedStart.
(WebCore::DOMSelection::containsNode const): Updated the comment for clarity.

LayoutTests:

  • editing/execCommand/insert-list-nested-with-orphaned-expected.txt: This test

result reflected incorrrect behavior where the selection changed from a range
to a caret in a certain unusual case. The new behavior of leaving the text
selected is consistent with how the command works in normal cases, and was an
accidental progression from the improvements to the selection code. Was able
to reproduce this all in Safari and after experimenting with different
selections, it's clear this is an improvement.

Location:
trunk
Files:
8 edited

Legend:

Unmodified
Added
Removed
  • trunk/LayoutTests/ChangeLog

    r267325 r267329  
     12020-09-20  Darin Adler  <darin@apple.com>
     2
     3        Selection API: Further improvements to VisibleSelection, FrameSelection, and DOMSelection to preserve anchor and focus
     4        https://bugs.webkit.org/show_bug.cgi?id=216739
     5
     6        Reviewed by Ryosuke Niwa.
     7
     8        * editing/execCommand/insert-list-nested-with-orphaned-expected.txt: This test
     9        result reflected incorrrect behavior where the selection changed from a range
     10        to a caret in a certain unusual case. The new behavior of leaving the text
     11        selected is consistent with how the command works in normal cases, and was an
     12        accidental progression from the improvements to the selection code. Was able
     13        to reproduce this all in Safari and after experimenting with different
     14        selections, it's clear this is an improvement.
     15
    1162020-09-20  Zalan Bujtas  <zalan@apple.com>
    217
  • trunk/LayoutTests/editing/execCommand/insert-list-nested-with-orphaned-expected.txt

    r66048 r267329  
    1818|     <ol>
    1919|       <li>
    20 |         "<#selection-caret>because of you"
     20|         "because of you"
    2121|   "
    2222    "
  • trunk/Source/WebCore/ChangeLog

    r267328 r267329  
     12020-09-20  Darin Adler  <darin@apple.com>
     2
     3        Selection API: Further improvements to VisibleSelection, FrameSelection, and DOMSelection to preserve anchor and focus
     4        https://bugs.webkit.org/show_bug.cgi?id=216739
     5
     6        Reviewed by Ryosuke Niwa.
     7
     8        If done correctly, these changes should have almost no effect unless live range selection
     9        is enabled. This adds a feature where VisibleSelection tracks the original points,
     10        before the VisiblePosition-style canonicalization. It also starts a transition from
     11        the base/extent terminology to the slightly-easier-to-understand anchor/focus terminology
     12        that is used in the selection specification. New functions name them anchor and focus,
     13        even though we still have some old ones that call them base and extent. In addition,
     14        made some other small related improvements to live range selection mode, without changing
     15        behavior when not in that mode. Have been testing these locally by turning on that mode
     16        and running various tests.
     17
     18        * editing/FrameSelection.cpp:
     19        (WebCore::FrameSelection::isInDocumentTree const): Added. Used by DOMSelection to
     20        correctly handle cases where the selection is in a shadow tree without requiring it
     21        actually create the associated live range just to query.
     22        (WebCore::FrameSelection::associatedLiveRange): Use the new VisibleSelection::range,
     23        which gives us non-canonicalized points, rather than the deprecated
     24        VisibleSelection::firstRange, which we will eventually be renaming or deleting.
     25        (WebCore::FrameSelection::updateFromAssociatedLiveRange): Use
     26        makeContainerOffsetPosition instead of implicitly using makeDeprecatedLegacyPosition.
     27        Also removed the FIXME about normalization of endpoints, which is what this patch
     28        mostly addresses; no need to track with a comment now.
     29        (WebCore::FrameSelection::updateAssociatedLiveRange): Use VisibleSelection::range
     30        for the same reason as above.
     31
     32        * editing/FrameSelection.h: Added isInDocumentTree.
     33
     34        * editing/VisibleSelection.cpp:
     35        (WebCore::VisibleSelection::VisibleSelection): Updated the primary constructor to set
     36        m_anchor/focus and let the validate function set m_base/extent/start/end/baseIsFirst.
     37        Changed most other constructors to call that primary one.
     38        (WebCore::VisibleSelection::anchor const): Added. This is a non-canonicalized position.
     39        (WebCore::VisibleSelection::focus const): Ditto.
     40        (WebCore::VisibleSelection::uncanonicalizedStart const): Added. This non-canonicalized
     41        version of start is used by new DOMSelection code, but eventually should be used
     42        elsewhere as well, and we may eventually eliminate the canonicalized version.
     43        (WebCore::VisibleSelection::uncanonicalizedEnd const): Ditto.
     44        (WebCore::VisibleSelection::range const): Added. This non-canonicalized version of
     45        firstRange has many of the same considerations as the other four functions above.
     46        (WebCore::VisibleSelection::setBase): Set m_anchor instead of m_base. Eventually we
     47        will want to rename this function, too, but there is no need to do that now.
     48        (WebCore::VisibleSelection::setExtent): Ditto.
     49        (WebCore::VisibleSelection::setBaseAndExtentToDeepEquivalents): Rewrote to take
     50        m_anchor and m_focus as inputs and now m_base and m_extent are pure outputs.
     51        May want to rename this later, because "deep equivalents" is a funny way to say
     52        that this function sets base and extent to canonicalized values.
     53        (WebCore::VisibleSelection::adjustSelectionRespectingGranularity): Renamed from
     54        setStartAndEndFromBaseAndExtentRespectingGranularity. This now takes m_start/end
     55        as input as well as output, because the validate function needs to check
     56        if the function changes m_start/end so it can update m_anchor/focus. Also did a tiny
     57        bit of refactoring to use std::swap.
     58        (WebCore::VisibleSelection::validate): Refined the code to handle canonicalization
     59        differently from expanding due to granularity. Canonicalization is done preserving
     60        the original m_anchor/focus as it mostly always has been. But expansion due to
     61        granularity now changes m_anchor/focus/base/extent rather than just m_start/end.
     62        This makes sense because granularity changes actually affect what is selected,
     63        while canonicalization just changes how the selection is expressed and tracked.
     64        (WebCore::VisibleSelection::setWithoutValidation): Set m_anchor/focus. In the future,
     65        we can probably get rid of this eventually, because the "validation" talked about
     66        here is the unwanted canonicalization. But that's a refinement for the future.
     67        (WebCore::VisibleSelection::adjustSelectionToAvoidCrossingShadowBoundaries):
     68        Update m_focus, not just m_extent, if we have to adjust. Also removed an unnnecessary
     69        null check from the start of the function.
     70        (WebCore::VisibleSelection::adjustSelectionToAvoidCrossingEditingBoundaries): Ditto.
     71        Also removed the assertions because these situations can indeed arise and it's not
     72        necessarily an indication of a bug in editing code.
     73
     74        * editing/VisibleSelection.h: Rearranged constructors to make it slightly more clear
     75        which is the main one and that the others are simply convenience shorthands. Might
     76        want to come back later and remove some unused ones. Added uncanonicalizedStart,
     77        uncanonicalizedEnd, anchor, focus, and range. Renamed the private function called
     78        setStartAndEndFromBaseAndExtentRespectingGranularity to the new name
     79        adjustSelectionRespectingGranularity. Added m_anchor and m_focus and refined the
     80        comments about the position data members. We should eventually be able to cut down
     81        on the number of these again, but it's fine to have two more for now.
     82
     83        * page/DOMSelection.cpp:
     84        (WebCore::DOMSelection::range const): Use range rather than firstRange when live
     85        range selection is enabled, for proper round-tripping of selection endpoints,
     86        unaffected by canonicalization. The other changes below are for the same reason.
     87        (WebCore::DOMSelection::anchorPosition const): Use anchor.
     88        (WebCore::DOMSelection::focusPosition const): Use focus.
     89        (WebCore::DOMSelection::basePosition const): Use anchor.
     90        (WebCore::DOMSelection::extentPosition const): Use focus.
     91        (WebCore::DOMSelection::type const): Use isInDocumentTree and range rather than
     92        isNone/isCaret/isRange when live range selection is enabled, because this needs
     93        to report "Caret" when the range is not collapsed, even if the canonicalized
     94        range is collapsed and "None" when the selection is in a shadow tree.
     95        (WebCore::DOMSelection::rangeCount const): Use isInDocumentTree to check if
     96        there is a selection rather than isNone to correctly handle the shadow tree case.
     97        (WebCore::DOMSelection::collapseToEnd): Use uncanonicalizedEnd.
     98        (WebCore::DOMSelection::collapseToStart): Use uncanonicalizedStart.
     99        (WebCore::DOMSelection::containsNode const): Updated the comment for clarity.
     100
    11012020-09-20  Simon Fraser  <simon.fraser@apple.com>
    2102
  • trunk/Source/WebCore/editing/FrameSelection.cpp

    r267313 r267329  
    27872787}
    27882788
     2789bool FrameSelection::isInDocumentTree() const
     2790{
     2791    return containsEndpoints(m_document, m_selection.range());
     2792}
     2793
    27892794RefPtr<Range> FrameSelection::associatedLiveRange()
    27902795{
    27912796    if (!m_associatedLiveRange) {
    2792         if (auto range = m_selection.firstRange(); containsEndpoints(m_document, range)) {
     2797        if (auto range = m_selection.range(); containsEndpoints(m_document, range)) {
    27932798            m_associatedLiveRange = createLiveRange(*range);
    27942799            m_associatedLiveRange->didAssociateWithSelection();
     
    28172822    if (!containsEndpoints(m_document, *m_associatedLiveRange))
    28182823        disassociateLiveRange();
    2819     else
    2820         setSelection(makeSimpleRange(*m_associatedLiveRange));
    2821     // FIXME: Normalization done by setSelection will be visible next time updateAssociatedLiveRange is called. Instead the Selection API specification calls for allowing non-normalized selection ranges.
     2824    else {
     2825        // Don't use VisibleSelection's constructor that takes a SimpleRange, because it uses makeDeprecatedLegacyPosition instead of makeContainerOffsetPosition.
     2826        auto start = makeContainerOffsetPosition(&m_associatedLiveRange->startContainer(), m_associatedLiveRange->startOffset());
     2827        auto end = makeContainerOffsetPosition(&m_associatedLiveRange->endContainer(), m_associatedLiveRange->endOffset());
     2828        setSelection({ start, end });
     2829    }
    28222830}
    28232831
    28242832void FrameSelection::updateAssociatedLiveRange()
    28252833{
    2826     auto range = m_selection.firstRange();
     2834    auto range = m_selection.range();
    28272835    if (!containsEndpoints(m_document, range)) {
    28282836        // The selection was cleared or is now within a shadow tree.
  • trunk/Source/WebCore/editing/FrameSelection.h

    r267220 r267329  
    252252    void setShouldShowBlockCursor(bool);
    253253
     254    bool isInDocumentTree() const;
    254255    RefPtr<Range> associatedLiveRange();
    255256    void associateLiveRange(Range&);
  • trunk/Source/WebCore/editing/VisibleSelection.cpp

    r266986 r267329  
    5050}
    5151
    52 VisibleSelection::VisibleSelection(const Position& position, Affinity affinity, bool isDirectional)
    53     : m_base(position)
    54     , m_extent(position)
     52VisibleSelection::VisibleSelection(const Position& anchor, const Position& focus, Affinity affinity, bool isDirectional)
     53    : m_anchor(anchor)
     54    , m_focus(focus)
    5555    , m_affinity(affinity)
    5656    , m_isDirectional(isDirectional)
     
    5959}
    6060
    61 VisibleSelection::VisibleSelection(const Position& base, const Position& extent, Affinity affinity, bool isDirectional)
    62     : m_base(base)
    63     , m_extent(extent)
    64     , m_affinity(affinity)
    65     , m_isDirectional(isDirectional)
    66 {
    67     validate();
     61VisibleSelection::VisibleSelection(const Position& position, Affinity affinity, bool isDirectional)
     62    : VisibleSelection(position, position, affinity, isDirectional)
     63{
    6864}
    6965
    7066VisibleSelection::VisibleSelection(const VisiblePosition& position, bool isDirectional)
    71     : m_base(position.deepEquivalent())
    72     , m_extent(position.deepEquivalent())
    73     , m_affinity(position.affinity())
    74     , m_isDirectional(isDirectional)
    75 {
    76     validate();
    77 }
    78 
    79 VisibleSelection::VisibleSelection(const VisiblePosition& base, const VisiblePosition& extent, bool isDirectional)
    80     : m_base(base.deepEquivalent())
    81     , m_extent(extent.deepEquivalent())
    82     , m_affinity(base.affinity())
    83     , m_isDirectional(isDirectional)
    84 {
    85     validate();
     67    : VisibleSelection(position.deepEquivalent(), position.affinity(), isDirectional)
     68{
     69    // FIXME: Wasteful that this re-canonicalizes, but risky to change since the VisiblePosition object could be from before a mutation and its position may no longer be canonical.
     70}
     71
     72VisibleSelection::VisibleSelection(const VisiblePosition& anchor, const VisiblePosition& focus, bool isDirectional)
     73    : VisibleSelection(anchor.deepEquivalent(), focus.deepEquivalent(), anchor.affinity(), isDirectional)
     74{
     75    // FIXME: Wasteful that this re-canonicalizes, but risky to change since the VisiblePosition objects could be from before a mutation and their positions may no longer be canonical.
    8676}
    8777
    8878VisibleSelection::VisibleSelection(const SimpleRange& range, Affinity affinity, bool isDirectional)
    89     : m_base(createLegacyEditingPosition(&range.startContainer(), range.startOffset()))
    90     , m_extent(createLegacyEditingPosition(&range.endContainer(), range.endOffset()))
    91     , m_affinity(affinity)
    92     , m_isDirectional(isDirectional)
    93 {
    94     ASSERT(&range.startContainer().treeScope() == &range.endContainer().treeScope());
    95     validate();
     79    : VisibleSelection(makeDeprecatedLegacyPosition(range.start), makeDeprecatedLegacyPosition(range.end), affinity, isDirectional)
     80{
    9681}
    9782
     
    10287}
    10388
     89Position VisibleSelection::anchor() const
     90{
     91    return m_anchor;
     92}
     93
     94Position VisibleSelection::focus() const
     95{
     96    return m_focus;
     97}
     98
     99Position VisibleSelection::uncanonicalizedStart() const
     100{
     101    return m_baseIsFirst ? m_anchor : m_focus;
     102}
     103
     104Position VisibleSelection::uncanonicalizedEnd() const
     105{
     106    return m_baseIsFirst ? m_focus : m_anchor;
     107}
     108
     109Optional<SimpleRange> VisibleSelection::range() const
     110{
     111    return makeSimpleRange(uncanonicalizedStart().parentAnchoredEquivalent(), uncanonicalizedEnd().parentAnchoredEquivalent());
     112}
     113
    104114void VisibleSelection::setBase(const Position& position)
    105115{
    106     m_base = position;
     116    m_anchor = position;
    107117    validate();
    108118}
     
    110120void VisibleSelection::setBase(const VisiblePosition& visiblePosition)
    111121{
    112     m_base = visiblePosition.deepEquivalent();
     122    setBase(visiblePosition.deepEquivalent());
     123}
     124
     125void VisibleSelection::setExtent(const Position& position)
     126{
     127    m_focus = position;
    113128    validate();
    114129}
    115130
    116 void VisibleSelection::setExtent(const Position& position)
    117 {
    118     m_extent = position;
    119     validate();
    120 }
    121 
    122131void VisibleSelection::setExtent(const VisiblePosition& visiblePosition)
    123132{
    124     m_extent = visiblePosition.deepEquivalent();
    125     validate();
     133    setExtent(visiblePosition.deepEquivalent());
    126134}
    127135
     
    211219void VisibleSelection::setBaseAndExtentToDeepEquivalents()
    212220{
    213     // Move the selection to rendered positions, if possible.
    214     bool baseAndExtentEqual = m_base == m_extent;
    215     if (m_base.isNotNull()) {
    216         m_base = VisiblePosition(m_base, m_affinity).deepEquivalent();
    217         if (baseAndExtentEqual)
    218             m_extent = m_base;
    219     }
    220     if (m_extent.isNotNull() && !baseAndExtentEqual)
    221         m_extent = VisiblePosition(m_extent, m_affinity).deepEquivalent();
    222 
    223     // Make sure we do not have a dangling base or extent.
    224     if (m_base.isNull() && m_extent.isNull())
    225         m_baseIsFirst = true;
    226     else if (m_base.isNull()) {
    227         m_base = m_extent;
    228         m_baseIsFirst = true;
    229     } else if (m_extent.isNull()) {
     221    // If only one of anchor and focus is null, convert to a caret selection.
     222    // FIXME: Seems like a better rule would be to convert to no selection.
     223    if (m_anchor.isNull())
     224        m_anchor = m_focus;
     225    if (m_focus.isNull())
     226        m_focus = m_anchor;
     227
     228    m_base = VisiblePosition(m_anchor, m_affinity).deepEquivalent();
     229    if (m_anchor == m_focus)
    230230        m_extent = m_base;
    231         m_baseIsFirst = true;
    232     } else
    233         m_baseIsFirst = m_base <= m_extent;
    234 }
    235 
    236 void VisibleSelection::setStartAndEndFromBaseAndExtentRespectingGranularity(TextGranularity granularity)
    237 {
    238     if (m_baseIsFirst) {
    239         m_start = m_base;
    240         m_end = m_extent;
    241     } else {
    242         m_start = m_extent;
    243         m_end = m_base;
    244     }
    245 
     231    else
     232        m_extent = VisiblePosition(m_focus, m_affinity).deepEquivalent();
     233
     234    m_baseIsFirst = m_base <= m_extent;
     235}
     236
     237void VisibleSelection::adjustSelectionRespectingGranularity(TextGranularity granularity)
     238{
    246239    switch (granularity) {
    247240        case TextGranularity::CharacterGranularity:
     
    287280               
    288281            m_end = end.deepEquivalent();
     282
    289283            // End must not be before start.
    290             if (m_start.deprecatedNode() == m_end.deprecatedNode() && m_start.deprecatedEditingOffset() > m_end.deprecatedEditingOffset()) {
    291                 Position swap(m_start);
    292                 m_start = m_end;   
    293                 m_end = swap;   
    294             }
     284            if (m_start.deprecatedNode() == m_end.deprecatedNode() && m_start.deprecatedEditingOffset() > m_end.deprecatedEditingOffset())
     285                std::swap(m_start, m_end);
    295286            break;
    296287        }
    297         case TextGranularity::SentenceGranularity: {
     288        case TextGranularity::SentenceGranularity:
    298289            m_start = startOfSentence(VisiblePosition(m_start, m_affinity)).deepEquivalent();
    299290            m_end = endOfSentence(VisiblePosition(m_end, m_affinity)).deepEquivalent();
    300291            break;
    301         }
    302292        case TextGranularity::LineGranularity: {
    303293            m_start = startOfLine(VisiblePosition(m_start, m_affinity)).deepEquivalent();
     
    385375{
    386376    setBaseAndExtentToDeepEquivalents();
    387     setStartAndEndFromBaseAndExtentRespectingGranularity(granularity);
     377
     378    m_start = m_baseIsFirst ? m_base : m_extent;
     379    m_end = m_baseIsFirst ? m_extent : m_base;
     380
     381    auto startBeforeAdjustments = m_start;
     382    auto endBeforeAdjustments = m_end;
     383
     384    adjustSelectionRespectingGranularity(granularity);
    388385    adjustSelectionToAvoidCrossingShadowBoundaries();
    389386    adjustSelectionToAvoidCrossingEditingBoundaries();
    390387    updateSelectionType();
     388
     389    bool shouldUpdateAnchor = m_start != startBeforeAdjustments;
     390    bool shouldUpdateFocus = m_end != endBeforeAdjustments;
    391391
    392392    if (isRange()) {
     
    395395        // useful to make to make the selection "canonical" (if only for
    396396        // purposes of comparing selections). This is an ideal point of the code
    397         // to do this operation, since all selection changes that result in a RANGE 
     397        // to do this operation, since all selection changes that result in a RANGE
    398398        // come through here before anyone uses it.
    399399        // FIXME: Canonicalizing is good, but haven't we already done it (when we
     
    402402        m_end = m_end.upstream();
    403403
    404         // FIXME: Position::downstream() or Position::upStream() might violate editing boundaries
    405         // if an anchor node has a Shadow DOM. So we adjust selection to avoid crossing editing
    406         // boundaries again. See https://bugs.webkit.org/show_bug.cgi?id=87463
     404        // Position::downstream() or Position::upstream() might violate editing boundaries
     405        // if an anchor node has a Shadow DOM even though they should not. But because this
     406        // happens in practice, adjust selection to avoid crossing editing boundaries again.
     407        // See https://bugs.webkit.org/show_bug.cgi?id=87463.
    407408        adjustSelectionToAvoidCrossingEditingBoundaries();
     409    }
     410
     411    if (shouldUpdateAnchor) {
     412        m_anchor = m_baseIsFirst ? m_start : m_end;
     413        m_base = m_anchor;
     414    }
     415    if (shouldUpdateFocus) {
     416        m_focus = m_baseIsFirst ? m_end : m_start;
     417        m_extent = m_focus;
    408418    }
    409419}
     
    417427void VisibleSelection::setWithoutValidation(const Position& base, const Position& extent)
    418428{
    419     ASSERT(!base.isNull());
    420     ASSERT(!extent.isNull());
     429    ASSERT(base.isNull() == extent.isNull());
    421430    ASSERT(m_affinity == Affinity::Downstream);
     431    m_anchor = base;
     432    m_focus = extent;
    422433    m_base = base;
    423434    m_extent = extent;
     
    487498void VisibleSelection::adjustSelectionToAvoidCrossingShadowBoundaries()
    488499{
    489     if (m_base.isNull() || m_start.isNull() || m_end.isNull())
     500    if (m_start.isNull() || m_end.isNull())
    490501        return;
    491502
     
    501512    }
    502513
     514    // Correct the focus if necessary.
    503515    if (m_baseIsFirst) {
    504516        m_extent = adjustPositionForEnd(m_end, m_start.containerNode());
     
    508520        m_start = m_extent;
    509521    }
     522    m_focus = m_extent;
    510523}
    511524
    512525void VisibleSelection::adjustSelectionToAvoidCrossingEditingBoundaries()
    513526{
    514     if (m_base.isNull() || m_start.isNull() || m_end.isNull())
     527    if (m_start.isNull() || m_end.isNull())
    515528        return;
    516529
     
    576589
    577590            if (previous.isNull()) {
    578                 // The selection crosses an Editing boundary.  This is a
    579                 // programmer error in the editing code.  Happy debugging!
    580                 ASSERT_NOT_REACHED();
    581                 m_base = Position();
    582                 m_extent = Position();
    583                 validate();
     591                *this = { };
    584592                return;
    585593            }
     
    605613           
    606614            if (next.isNull()) {
    607                 // The selection crosses an Editing boundary.  This is a
    608                 // programmer error in the editing code.  Happy debugging!
    609                 ASSERT_NOT_REACHED();
    610                 m_base = Position();
    611                 m_extent = Position();
    612                 validate();
     615                *this = { };
    613616                return;
    614617            }
     
    617620    }
    618621   
    619     // Correct the extent if necessary.
    620     if (baseEditableAncestor != lowestEditableAncestor(m_extent.containerNode()))
     622    // Correct the focus if necessary.
     623    if (baseEditableAncestor != lowestEditableAncestor(m_extent.containerNode())) {
    621624        m_extent = m_baseIsFirst ? m_end : m_start;
     625        m_focus = m_extent;
     626    }
    622627}
    623628
  • trunk/Source/WebCore/editing/VisibleSelection.h

    r266986 r267329  
    4040    static constexpr auto defaultAffinity = VisiblePosition::defaultAffinity;
    4141
     42    VisibleSelection(const Position& anchor, const Position& focus, Affinity = defaultAffinity, bool isDirectional = false);
     43
    4244    VisibleSelection(const Position&, Affinity, bool isDirectional = false);
    43     VisibleSelection(const Position&, const Position&, Affinity = defaultAffinity, bool isDirectional = false);
    4445    WEBCORE_EXPORT VisibleSelection(const SimpleRange&, Affinity = defaultAffinity, bool isDirectional = false);
    4546    WEBCORE_EXPORT VisibleSelection(const VisiblePosition&, bool isDirectional = false);
    46     WEBCORE_EXPORT VisibleSelection(const VisiblePosition&, const VisiblePosition&, bool isDirectional = false);
     47    WEBCORE_EXPORT VisibleSelection(const VisiblePosition& anchor, const VisiblePosition& focus, bool isDirectional = false);
    4748
    4849    WEBCORE_EXPORT static VisibleSelection selectionFromContentsOfNode(Node*);
     
    5152    Affinity affinity() const { return m_affinity; }
    5253
     54    // FIXME: Move to the terms "focus" and "anchor" instead of "base" and "extent".
     55
    5356    void setBase(const Position&);
    5457    void setBase(const VisiblePosition&);
     
    5659    void setExtent(const VisiblePosition&);
    5760
     61    // These functions return the values that were passed in, without the canonicalization done by VisiblePosition.
     62    // FIXME: When we expand granularity, we canonicalize as a side effect, so expanded values have been made canonical.
     63    // FIXME: Replace start/range/base/end/firstRange with these, renaming these to the shorter names.
     64    Position uncanonicalizedStart() const;
     65    Position uncanonicalizedEnd() const;
     66    Position anchor() const;
     67    Position focus() const;
     68    WEBCORE_EXPORT Optional<SimpleRange> range() const;
     69
     70    // FIXME: Rename these to include the word "canonical" or remove.
    5871    Position base() const { return m_base; }
    5972    Position extent() const { return m_extent; }
    6073    Position start() const { return m_start; }
    6174    Position end() const { return m_end; }
    62    
     75
    6376    VisiblePosition visibleStart() const { return VisiblePosition(m_start, isRange() ? Affinity::Downstream : affinity()); }
    6477    VisiblePosition visibleEnd() const { return VisiblePosition(m_end, isRange() ? Affinity::Upstream : affinity()); }
     
    8598    WEBCORE_EXPORT bool expandUsingGranularity(TextGranularity granularity);
    8699
     100    // FIXME: Rename to include the word "canonical" and remove the word "first" or remove.
    87101    // We don't yet support multi-range selections, so we only ever have one range to return.
    88102    WEBCORE_EXPORT Optional<SimpleRange> firstRange() const;
    89103
    90     // FIXME: Most callers probably don't want this function, and should use firstRange instead.
    91     // toNormalizedRange is like firstRange, but contracts the range around text and moves the caret upstream before returning the range.
     104    // FIXME: Most callers probably don't want this function, and should use range instead.
     105    // This function contracts the range around text and moves the caret upstream before returning the range.
    92106    WEBCORE_EXPORT Optional<SimpleRange> toNormalizedRange() const;
    93107
     
    96110    WEBCORE_EXPORT bool hasEditableStyle() const;
    97111    WEBCORE_EXPORT bool isContentRichlyEditable() const;
     112
    98113    // Returns a shadow tree node for legacy shadow trees, a child of the
    99114    // ShadowRoot node for new shadow trees, or 0 for non-shadow trees.
     
    118133    // Support methods for validate()
    119134    void setBaseAndExtentToDeepEquivalents();
    120     void setStartAndEndFromBaseAndExtentRespectingGranularity(TextGranularity);
     135    void adjustSelectionRespectingGranularity(TextGranularity);
    121136    void adjustSelectionToAvoidCrossingShadowBoundaries();
    122137    void adjustSelectionToAvoidCrossingEditingBoundaries();
    123138    void updateSelectionType();
    124139
    125     // We store these as Positions because VisibleSelection is used to store values in
     140    // We store only Positions because VisibleSelection is used to store values in
    126141    // editing commands for use when undoing the command. We need to be able to store
    127142    // a selection that, while currently invalid, will be valid once the changes are undone.
    128143
    129     Position m_base; // Where the first click happened.
    130     Position m_extent; // Where the end click happened.
    131 
    132     Position m_start; // Leftmost position when expanded to respect granularity.
    133     Position m_end; // Rightmost position when expanded to respect granularity.
     144    // FIXME: Consider doing canonicalization only as part of editing operations, and keeping all selection endpoints non-canonical outside of that code.
     145    // FIXME: Rename m_base to m_canonicalAnchor.
     146    // FIXME: Rename m_extent to m_canonicalFocus.
     147    Position m_anchor; // Where the first click happened.
     148    Position m_focus; // Where the end click/release happened.
     149    Position m_base; // Anchor, canonical, not yet expended to respect granularity.
     150    Position m_extent; // Focus, canonical, but not expended to respect granularity.
     151    Position m_start; // First of anchor and focus, caonicalized and expanded to respect granularity.
     152    Position m_end; // Last of anchor and focus, caonicalized and expanded to respect granularity.
    134153
    135154    Affinity m_affinity { defaultAffinity };
  • trunk/Source/WebCore/page/DOMSelection.cpp

    r267327 r267329  
    7070    if (!frame)
    7171        return WTF::nullopt;
    72     auto range = frame->selection().selection().firstRange();
     72    auto range = frame->settings().liveRangeSelectionEnabled()
     73        ? frame->selection().selection().range()
     74        : frame->selection().selection().firstRange();
    7375    if (!range || range->start.container->isInShadowTree())
    7476        return WTF::nullopt;
     
    8183    if (!frame)
    8284        return { };
     85    if (frame->settings().liveRangeSelectionEnabled())
     86        return frame->selection().selection().anchor().parentAnchoredEquivalent();
    8387    auto& selection = frame->selection().selection();
    8488    return (selection.isBaseFirst() ? selection.start() : selection.end()).parentAnchoredEquivalent();
     
    9094    if (!frame)
    9195        return { };
     96    if (frame->settings().liveRangeSelectionEnabled())
     97        return frame->selection().selection().focus().parentAnchoredEquivalent();
    9298    auto& selection = frame->selection().selection();
    9399    return (selection.isBaseFirst() ? selection.end() : selection.start()).parentAnchoredEquivalent();
     
    96102Position DOMSelection::basePosition() const
    97103{
    98     auto frame = this->frame();
    99     if (!frame)
    100         return { };
     104    // FIXME: Remove this once liveRangeSelectionEnabled is always on, since base and anchor should be the same thing.
     105    auto frame = this->frame();
     106    if (!frame)
     107        return { };
     108    if (frame->settings().liveRangeSelectionEnabled())
     109        return frame->selection().selection().anchor().parentAnchoredEquivalent();
    101110    return frame->selection().selection().base().parentAnchoredEquivalent();
    102111}
     
    104113Position DOMSelection::extentPosition() const
    105114{
    106     auto frame = this->frame();
    107     if (!frame)
    108         return { };
     115    // FIXME: Remove this once liveRangeSelectionEnabled is always on, since extent and focus should be the same thing.
     116    auto frame = this->frame();
     117    if (!frame)
     118        return { };
     119    if (frame->settings().liveRangeSelectionEnabled())
     120        return frame->selection().selection().focus().parentAnchoredEquivalent();
    109121    return frame->selection().selection().extent().parentAnchoredEquivalent();
    110122}
     
    165177        return "None"_s;
    166178    auto& selection = frame->selection();
     179    if (frame->settings().liveRangeSelectionEnabled())
     180        return !selection.isInDocumentTree() ? "None"_s : range()->collapsed() ? "Caret"_s : "Range"_s;
    167181    if (selection.isNone())
    168182        return "None"_s;
     
    175189{
    176190    auto frame = this->frame();
     191    if (frame->settings().liveRangeSelectionEnabled())
     192        return frame && frame->selection().isInDocumentTree();
    177193    return !frame || frame->selection().isNone() ? 0 : 1;
    178194}
     
    211227    if (selection.isNone())
    212228        return Exception { InvalidStateError };
    213     selection.disassociateLiveRange();
    214     selection.moveTo(selection.selection().end(), Affinity::Downstream);
     229    if (frame->settings().liveRangeSelectionEnabled()) {
     230        selection.disassociateLiveRange();
     231        selection.moveTo(selection.selection().uncanonicalizedEnd(), Affinity::Downstream);
     232    } else
     233        selection.moveTo(selection.selection().end(), Affinity::Downstream);
    215234    return { };
    216235}
     
    224243    if (selection.isNone())
    225244        return Exception { InvalidStateError };
    226     selection.disassociateLiveRange();
    227     selection.moveTo(selection.selection().start(), Affinity::Downstream);
     245    if (frame->settings().liveRangeSelectionEnabled()) {
     246        selection.disassociateLiveRange();
     247        selection.moveTo(selection.selection().uncanonicalizedStart(), Affinity::Downstream);
     248    } else
     249        selection.moveTo(selection.selection().start(), Affinity::Downstream);
    228250    return { };
    229251}
     
    406428bool DOMSelection::containsNode(Node& node, bool allowPartial) const
    407429{
    408     // FIXME: This is wrong, and was added to work around anomalies caused when we canonicalize selection endpoints. We should fix that and remove this.
     430    // FIXME: The rule implemented here for text nodes is wrong, and was added to work around anomalies caused when we canonicalize selection endpoints.
    409431    if (node.isTextNode() && !node.document().settings().liveRangeSelectionEnabled())
    410432        allowPartial = true;
Note: See TracChangeset for help on using the changeset viewer.