<html>
    <head>
      <base href="https://bugs.webkit.org/" />
    </head>
    <body>
      <p>
        <div>
            <b><a class="bz_bug_link 
          bz_status_NEW "
   title="NEW - AX: new lines in content editable elements don't notify accessibility"
   href="https://bugs.webkit.org/show_bug.cgi?id=153361#c36">Comment # 36</a>
              on <a class="bz_bug_link 
          bz_status_NEW "
   title="NEW - AX: new lines in content editable elements don't notify accessibility"
   href="https://bugs.webkit.org/show_bug.cgi?id=153361">bug 153361</a>
              from <span class="vcard"><a class="email" href="mailto:d_russell&#64;apple.com" title="Doug Russell &lt;d_russell&#64;apple.com&gt;"> <span class="fn">Doug Russell</span></a>
</span></b>
        <pre>(In reply to <a href="show_bug.cgi?id=153361#c32">comment #32</a>)
<span class="quote">&gt; Comment on <span class="bz_obsolete"><a href="attachment.cgi?id=272624&amp;action=diff" name="attach_272624" title="Patch">attachment 272624</a> <a href="attachment.cgi?id=272624&amp;action=edit" title="Patch">[details]</a></span>
&gt; Patch
&gt; 
&gt; View in context:
&gt; <a href="https://bugs.webkit.org/attachment.cgi?id=272624&amp;action=review">https://bugs.webkit.org/attachment.cgi?id=272624&amp;action=review</a>
&gt; 
&gt; New approach looks a LOT better!
&gt; 
&gt; &gt; Source/WebCore/accessibility/AXObjectCache.cpp:135
&gt; &gt; +        cache-&gt;postTextReplacementNotification(start.deepEquivalent().anchorNode(), AXTextEditTypeDelete, m_replacedText, type, text, selection.start());
&gt; &gt; +    else
&gt; &gt; +        cache-&gt;postTextStateChangeNotification(start.deepEquivalent().anchorNode(), type, text, selection.start());
&gt; 
&gt; Using anchorNode is really bad idea because position could be before/after a
&gt; position.
&gt; e.g. getting any data out of these text nodes might result in very
&gt; unexpected results.
&gt; What are you using these nodes for?</span >

To look up the relevant accessibilityObject. I could probably use highestEditableRoot() instead if that makes more sense.

<span class="quote">&gt; 
&gt; &gt; Source/WebCore/editing/CompositeEditCommand.cpp:101
&gt; &gt; +    Node* node = start.deepEquivalent().anchorNode();
&gt; &gt; +    if (!node)
&gt; &gt; +        return;
&gt; &gt; +    if (selection.isRange())
&gt; &gt; +        m_endIndexes.startIndex = indexForVisiblePosition(node, start);
&gt; &gt; +    m_endIndexes.endIndex = indexForVisiblePosition(node, end);
&gt; 
&gt; We should really use the version of indexForVisiblePosition the rest of
&gt; editing code uses.
&gt; The one that takes VisiblePosition as the first argument and the scope as
&gt; the second argument.
&gt; The variant you're using here won't work in many cases.</span >

Good to know, this was some of the code I was hoping to get feedback on.

This version? int indexForVisiblePosition(const VisiblePosition&amp; visiblePosition, RefPtr&lt;ContainerNode&gt;&amp; scope)

How do I determine what to pass to scope?

<span class="quote">&gt; 
&gt; &gt; Source/WebCore/editing/CompositeEditCommand.cpp:112
&gt; &gt; +    Node* node = start.deepEquivalent().anchorNode();
&gt; &gt; +    if (!node)
&gt; &gt; +        return;
&gt; &gt; +    m_endIndexes.startIndex = indexForVisiblePosition(node, start);
&gt; 
&gt; Ditto.
&gt; 
&gt; &gt; Source/WebCore/editing/CompositeEditCommand.cpp:131
&gt; &gt; +    VisiblePosition start = visiblePositionForIndexUsingCharacterIterator(node, m_endIndexes.startIndex);
&gt; &gt; +    VisiblePosition end = visiblePositionForIndexUsingCharacterIterator(node, m_endIndexes.endIndex);
&gt; 
&gt; Please use visiblePositionForIndex instead.</span >

Ok.

<span class="quote">&gt; 
&gt; &gt; Source/WebCore/editing/CompositeEditCommand.cpp:176
&gt; &gt; +    m_replacedText.setStartingSelection(currentSelection);
&gt; 
&gt; This doesn't make sense. &quot;startingSelection&quot; in editing usually refers to
&gt; the selection before the editing command is being applied.
&gt; What we have here instead is the current frame selection.</span >

The naming there is definitely confusing. I'll try to improve it.

<span class="quote">&gt; 
&gt; &gt; Source/WebCore/editing/CompositeEditCommand.cpp:321
&gt; &gt; -        command-&gt;m_composition = EditCommandComposition::create(document(), startingSelection(), endingSelection(), editingAction());
&gt; &gt; +        command-&gt;m_composition = EditCommandComposition::create(document(), startingSelection(), endingSelection(), frame().selection().selection(), editingAction());
&gt; 
&gt; Why do we need to store the frame selection's visible selection here?
&gt; This should certainly needs to be explained in the change log.</span >

I think that was a holdover I hadn't meant to leave in. I'll check and likely remove it.

<span class="quote">&gt; 
&gt; &gt; Source/WebCore/editing/CompositeEditCommand.cpp:516
&gt; &gt; -        nodesToRemove.append(*node);
&gt; &gt; +        nodesToRemove.append(*node); 
&gt; 
&gt; Superflous change here.</span >

Ok.

<span class="quote">&gt; 
&gt; &gt; Source/WebCore/editing/CompositeEditCommand.h:53
&gt; &gt; +    String m_undoText = String();
&gt; &gt; +    String m_replacedText = String();
&gt; 
&gt; Here's no need to initialize strings. They're null by default.
&gt; I don't really like these member variable names though.
&gt; It's not clear at all to me what undoText and replacedText refer to.</span >

I'll work out clearer names. This logic is quite recent and reflects the flux in it's inconsistent naming.

<span class="quote">&gt; 
&gt; Is undoText the text we're going to re-insert or remove?</span >

Re-insert.

<span class="quote">&gt; 
&gt; &gt; Source/WebCore/editing/CompositeEditCommand.h:58
&gt; &gt; +    struct {
&gt; &gt; +        int startIndex = -1;
&gt; &gt; +        int endIndex = -1;
&gt; &gt; +    } m_endIndexes;
&gt; 
&gt; It's strange for a variable named m_endIndexes to contain endIndex. We
&gt; should probably call this selectionRange or something.
&gt; By the way, we shouldn't have to be storing these indices in the object
&gt; since they're only needed to figure out what got deleted / inserted so they
&gt; should be temporary variable
&gt; that only exist during ReplaceSelectionCommand exists.</span >

These are owned by the EditCommandComposition which I don't think ends up recreating a ReplaceSelectionCommand (could be wrong) to perform the undo.

<span class="quote">&gt; 
&gt; &gt; Source/WebCore/editing/CompositeEditCommand.h:92
&gt; &gt; +    AccessibilityUndoReplacedText m_replacedText;
&gt; 
&gt; We shouldn't always allocate this object.
&gt; We try to keep EditCommandComposition small because it'll be kept in
&gt; NSUndoManager for a long time.
&gt; We should either put this into std::unique_ptr or create a subclass of
&gt; EditCommandComposition which has this field
&gt; and make ReplaceSelectionCommand create that variant instead.</span >

I'll have to explore doing that. I don't think it can be unique since we need 1 per undo step. I can see about avoiding creating it if AX isn't on. I don't think it's specific to ReplaceSelection either, this logic is used to post all the notifications for unapply and reapply.

<span class="quote">&gt; 
&gt; &gt; Source/WebCore/editing/DictationCommand.cpp:112
&gt; &gt; +    // TODO: Test this in the ReplaceSelection case
&gt; &gt; +    // TODO: How do I write a test for this?
&gt; 
&gt; We don't use TODO.  Please use FIXME instead.</span >

Will do. Those were just meant as notes before I finalized the patch.

<span class="quote">&gt; 
&gt; &gt; Source/WebCore/editing/ReplaceSelectionCommand.cpp:929
&gt; &gt; -    if ((selection.start().deprecatedNode()-&gt;renderer() &amp;&amp; selection.start().deprecatedNode()-&gt;renderer()-&gt;style().userModify() == READ_WRITE_PLAINTEXT_ONLY)
&gt; &gt; -        &amp;&amp; (selection.end().deprecatedNode()-&gt;renderer() &amp;&amp; selection.end().deprecatedNode()-&gt;renderer()-&gt;style().userModify() == READ_WRITE_PLAINTEXT_ONLY))
&gt; &gt; +    if ((selection.start().deprecatedNode()-&gt;renderer() &amp;&amp; selection.start().deprecatedNode()-&gt;renderer()-&gt;style().userModify() == READ_WRITE_PLAINTEXT_ONLY))
&gt; &gt; +        m_matchStyle = false;
&gt; &gt; +    else if (selection.end().deprecatedNode()-&gt;renderer() &amp;&amp; selection.end().deprecatedNode()-&gt;renderer()-&gt;style().userModify() == READ_WRITE_PLAINTEXT_ONLY)
&gt; &gt;          m_matchStyle = false;
&gt; 
&gt; Why are you changing this code?
&gt; It's not great to touch random lines of code like this because it would make
&gt; it harder for us to track changes on svg/git blame.</span >

Didn't mean to check that in.

<span class="quote">&gt; 
&gt; &gt; Source/WebCore/editing/ReplaceSelectionCommand.h:56
&gt; &gt; +    // Only populated when (editingAction() == EditActionPaste &amp;&amp; AXObjectCache::accessibilityEnabled) == true
&gt; 
&gt; We don't like what comments like this.</span >

Will remove.

<span class="quote">&gt; 
&gt; &gt; Source/WebCore/editing/ReplaceSelectionCommand.h:57
&gt; &gt; +    String insertedText() { return m_insertedText; }
&gt; 
&gt; Instead, rename this member function as well as the instance variable to
&gt; something more descriptive.
&gt; e.g. m_pastedTextForAccessibilityNotification.</span >

Will do.

<span class="quote">&gt; 
&gt; &gt; Source/WebCore/editing/ReplaceSelectionCommand.h:116
&gt; &gt; +    String m_insertedText = String();
&gt; 
&gt; Again, there's no need to initialize String like this.</span >

Ok.

<span class="quote">&gt; 
&gt; &gt; Source/WebCore/editing/TextInsertionBaseCommand.cpp:39
&gt; &gt; -TextInsertionBaseCommand::TextInsertionBaseCommand(Document&amp; document)
&gt; &gt; -    : CompositeEditCommand(document)
&gt; &gt; +TextInsertionBaseCommand::TextInsertionBaseCommand(Document&amp; document, EditAction editingAction)
&gt; &gt; +    : CompositeEditCommand(document, editingAction)
&gt; 
&gt; Is this ever used?
&gt; If so, it should be explained in the change log.</span >

Will add an explanation. Correct editingAction() will primarily effect what we do in undo.

<span class="quote">&gt; 
&gt; &gt; Source/WebCore/editing/TypingCommand.cpp:280
&gt; &gt; +        // TODO: Should call postTextStateChangeNotificationForDeletion()
&gt; &gt;          deleteSelection(m_smartDelete);
&gt; &gt;          return;
&gt; &gt;      case DeleteKey:
&gt; &gt; +        // Calls postTextStateChangeNotificationForDeletion()
&gt; &gt;          deleteKeyPressed(m_granularity, m_shouldAddToKillRing);
&gt; &gt;          return;
&gt; &gt;      case ForwardDeleteKey:
&gt; &gt; +        // Calls postTextStateChangeNotificationForDeletion()
&gt; &gt;          forwardDeleteKeyPressed(m_granularity, m_shouldAddToKillRing);
&gt; 
&gt; Please use FIXME instead.</span >

Will do.</pre>
        </div>
      </p>
      <hr>
      <span>You are receiving this mail because:</span>
      
      <ul>
          <li>You are the assignee for the bug.</li>
      </ul>
    </body>
</html>