Search code examples
javascripthtmlgoogle-chromegmailgoogle-chrome-devtools

On Gmail, why can't the "To" field be clicked with javascript?


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>

Solution

  • 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. :(