2

I am building an accessibility tool that helps people with limited motor control write emails, hence I need programmatic focus of the "To" field (the first field in Gmail's compose window).

I have tried various combinations of the following in the Chrome devtools panel, on the textarea element, and it's parent. Nothing seems to get it focused.

setTimeout(() => {
    let el = document.querySelector('*[name="to"]');
    el.dispatchEvent(new MouseEvent('mouseover', {bubbles: true}));
    el.dispatchEvent(new MouseEvent('mousedown', {bubbles: true}));
    el.focus();
    el.dispatchEvent(new MouseEvent('mouseup', {bubbles: true}));
    el.click();
    console.log('done')
}, 5000)

Here is a sample of the relevant markup:

<div class="wO nr l1" style="">
    <input class="wA" tabindex="-1" aria-hidden="true">
    <textarea rows="1" id=":bw" class="vO" name="to" spellcheck="false" autocomplete="false" autocapitalize="off" autocorrect="off" tabindex="1" dir="ltr" aria-label="To" role="combobox" aria-autocomplete="list" style="width: 380px;"></textarea>
    <div class="aA6">
        <span>
            <div tabindex="1" style="background-color: transparent; width: 1px; height: 1px; position: absolute;"></div>
            <div tabindex="1" style="background-color: transparent; width: 1px; height: 1px; position: absolute;"></div>
            <span><span id=":8p" class="aB gQ pE" role="link" tabindex="1" data-tooltip="Add Cc recipients ‪(Ctrl-Shift-C)‬" aria-label="Add Cc recipients ‪(Ctrl-Shift-C)‬" style="user-select: none;">Cc</span><span id=":8o" class="aB  gQ pB" role="link" tabindex="1" data-tooltip="Add Bcc recipients ‪(Ctrl-Shift-B)‬" aria-label="Add Bcc recipients ‪(Ctrl-Shift-B)‬" style="user-select: none;">Bcc</span><span id=":aw" role="button" tabindex="1" aria-hidden="false" class="bcV Sz" style="display:none" data-tooltip="Some recipients use services that don't support encryption (click for details)" aria-label="Some recipients use services that don't support encryption (click for details)"></span></span><div tabindex="1" style="background-color: transparent; width: 1px; height: 1px; position: absolute;">
            </div>
        </span>
    </div>
</div>
Salami
  • 2,849
  • 4
  • 24
  • 33

1 Answers1

3

When not focused, the "To", "Cc" and "Bcc" fields in Gmail's compose window are covered up by another field labelled "Recipients":

Screenshot

This "Recipients" field has a focus event handler that automatically hides it, shows the "To", "Cc" and "Bcc" fields and transfers focus to one of them. To activate the handler programmatically, you first need to locate the "Recipients" field and dispatch a focus event to it. The following code did the trick for me on the Chrome console, although I suspect that the ID I used might not be be stable:

let recipients = document.getElementById(':oa');  // not sure if this ID changes
recipients.dispatchEvent(new FocusEvent('focus'));

(Once at least one e-mail address has been entered in the "To" / "Cc" / "Bcc" fields, the appearence of the "Recipients" field changes, hiding the label and showing the recipient addresses instead. However, the same code seems to still work to activate the focus event handler.)


Ps. The ID of the node with the focus handler indeed seems to change. I'm not sure how to reliably locate it — the HTML code really doesn't offer much to hang a reliable selector onto:

<div id=":oa" class="aoD hl" tabindex="1" style="background-color: transparent;">
  <div id=":pm" class="oL aDm" style="">Recipients</div>
  <div id=":o7" class="bgW">
    <span id=":p0" role="button" tabindex="-1" aria-hidden="true" class="bcV Sz" style="display:none" data-tooltip="Some recipients use services that don't support encryption (click for details)" aria-label="Some recipients use services that don't support encryption (click for details)"></span>
  </div>
</div>

You might have to resort to XPath trickery to locate it using the label text, like this:

let recipients = document.evaluate("//div[text()='Recipients']/..", document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;

But even this will only work when no recipients have been entered yet (when there are any, the "Recipients" label gets replaced with the list of recipients), and it's also likely to break if the user's interface language isn't English. :(

Ilmari Karonen
  • 49,047
  • 9
  • 93
  • 153
  • very impressed. Would you mind letting me know how you figured out that the element with "Recipients" inside it was stealing and transferring focus? My best guess is you saw "Recipients" in the right place on the page, which you searched for in the DOM, then you used the "Event listeners" panel to look at the relevant JS? But the JS is compressed and very difficult to read. Am I missing something here? Would really appreciate "learning how to fish" so to speak. Thank you in advance! – Salami Sep 06 '20 at 16:15
  • Yes. I used the _Inspect_ item in the right-click context menu to find the DOM element corresponding to the "Recipients" field and then, with that element selected, used the "Event Listeners" tab in the dev tools to look for an event handler applying only to that specific element. It took me a minute to find because I was first looking for click or mousedown event handlers, but eventually I thought to look under "focus" instead. – Ilmari Karonen Sep 06 '20 at 17:07
  • 1
    And yes, the code shown for the event handler is pretty meaningless (`function(e){return c.call(d.src,d.listener,e)}`), but just seeing that the handler was there was enough of a hint to get me to try sending a focus event to the element to see what happens. – Ilmari Karonen Sep 06 '20 at 17:10