Gmailの「スヌーズ期間…」メニューのアイテムをユーザスクリプトからクリックする

## 要約
Gmailの「スヌーズ期間…」メニューのアイテムをユーザースクリプトで自動クリックする時、`element.click()`では反応せず、`mousedown`イベントと`mouseup`イベントを連続で発火させたら反応しました。

```js
// 動かない例
targetItem.click();
```

```js
// 動く例
targetItem.dispatchEvent(
  new MouseEvent("mousedown", {
    bubbles: true,
    cancelable: true
  })
);
targetItem.dispatchEvent(
  new MouseEvent("mouseup", {
    bubbles: true,
    cancelable: true
  })
);
```

今回は「スヌーズ期間…」メニューしか調査していませんが、それ以外のポップアップメニューでも同じかもしれないのでメモとして残しておきます。また、今回はTampermonkeyでユーザースクリプトを記述していますが、拡張機能でもおそらく同じことだと思います。

※正直「何故こうなのか」は分かっておらず偶然上手く動いているだけかもしれません。将来的に動かなくなる可能性は十分にありますので参考にする際は自己責任でお願いします。


## 検証環境
OS: Windows 11 Home 23H2
ブラウザ： Chrome 132.0.6834.84（Official Build） （64 ビット） 日本語環境
Tampermonkey: 5.3.3 
Gmail: 2025-0116時点版

## 事の発端
Gmailで「スヌーズ」を選択すると「スヌーズ期間…」メニューがポップアップ表示されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/29682/28fc0238-9a4d-603a-2a2f-d3c085a3745f.png)
↓
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/29682/37cec12f-56f0-60a1-483c-244a14908504.png)


私はプリセットの時間を使わず毎回「日付と時間を選択」をクリックしていたため面倒臭くなり、これをスキップしたくなりました。つまり、「スヌーズ期間…」メニューが出現したら自動で即座に「日付と時間を選択」をクリック（したことに）させたかったのです。

## 「スヌーズ期間…」メニューのDOM構造

調べてみると「スヌーズ期間…」メニューの正体は次のようなDOM構造で、`body`配下にappendされることが分かりました（かなり簡略化しています。本当はもっと色々な属性があり、`div`タグの構造も深いです）。

```html
<div role="menu" aria-label="スヌーズ メニュー">
  <div>スヌーズ期間…</div>
  <div role="menuitem">今日中 木 18:00</div>
  <div role="menuitem">明日 金 8:00</div>
  <div role="separator"></div>
  <div role="menuitem">日付と時間を選択</div>
</div>
```

DOMの変化を監視して`ariaLabel === "スヌーズ メニュー"`のエレメントが現れたらその配下にある目的の`menuitem`をクリックすればやりたいことが実現できる気がしました。

## `click`版ユーザスクリプト（失敗）

早速、次のようなユーザスクリプトを書いてTampermonkeyで動かしてみました。

```js
// ==UserScript==
// @name         SkipGmailSnoozeMenu
// @description  Gmailの「スヌーズ期間…」メニューをスキップする(注意：これは動きません)
// @match        https://mail.google.com/*
// ==/UserScript==
(function () {
  "use strict";
  // body配下の変更を監視する。
  new MutationObserver(observeSnoozeMenu).observe(document.body, {
    childList: true
  });

  // 「スヌーズ期間…」メニューが出現したら即座に「日付と時間を選択」を選択する。
  function observeSnoozeMenu(mutations) {
    for (const mutation of mutations) {
      for (const node of mutation.addedNodes) {
        // 「スヌーズ期間…」メニューにだけ反応する
        const isSnoozeMenu =
          node instanceof Element && node.ariaLabel === "スヌーズ メニュー";
        if (!isSnoozeMenu) {
          continue;
        }

        // 「日付と時間を選択」アイテムのDOMを取得
        const targetItem = Array.from(
          node.querySelectorAll("[role=menuitem]")
        ).find((e) => e.textContent === "日付と時間を選択");
        if (!targetItem) {
          throw new Error("GmailのDOM構造が変わった可能性があります");
        }

        // 「日付と時間を選択」アイテムをクリックする(★注意：動かない)
        targetItem.click();
      }
    }
  }
})();
```

結果、動きませんでした。深追いはしませんが、どうやらGmailは単純にclickイベントハンドリングしているだけはないようです。

## `mousedown`,`mouseup`イベント版ユーザスクリプト（成功）

試行錯誤の結果、`menuitem`のDOMに対して`mousedown`イベントと`mouseup`イベントを連続で発火させるとクリックしたことになる事が判明しました。先程のコードの`targetItem.click()`の部分を次のように書き換えたら狙い通り動くようになりました。

```js
// 「日付と時間を選択」アイテムをクリックする
// `targetItem.click()`では動かないため`mousedown`イベントと`mouseup`イベントを連続で発火させる。
targetItem.dispatchEvent(
  new MouseEvent("mousedown", {
    bubbles: true,
    cancelable: true
  })
);
targetItem.dispatchEvent(
  new MouseEvent("mouseup", {
    bubbles: true,
    cancelable: true
  })
);
```

<details><summary>成功版コードの全量</summary>

```js
// ==UserScript==
// @name         SkipGmailSnoozeMenu
// @description  Gmailの「スヌーズ期間…」メニューをスキップする
// @match        https://mail.google.com/*
// ==/UserScript==
(function () {
  "use strict";
  // body配下の変更を監視する。
  new MutationObserver(observeSnoozeMenu).observe(document.body, {
    childList: true
  });

  // 「スヌーズ期間…」メニューが出現したら即座に「日付と時間を選択」を選択する。
  function observeSnoozeMenu(mutations) {
    for (const mutation of mutations) {
      for (const node of mutation.addedNodes) {
        // 「スヌーズ期間…」メニューにだけ反応する
        const isSnoozeMenu =
          node instanceof Element && node.ariaLabel === "スヌーズ メニュー";
        if (!isSnoozeMenu) {
          continue;
        }

        // 「日付と時間を選択」アイテムのDOMを取得
        const targetItem = Array.from(
          node.querySelectorAll("[role=menuitem]")
        ).find((e) => e.textContent === "日付と時間を選択");
        if (!targetItem) {
          throw new Error("GmailのDOM構造が変わった可能性があります");
        }

        // 「日付と時間を選択」アイテムをクリックする
        // `targetItem.click()`では動かないため`mousedown`イベントと`mouseup`イベントを連続で発火させる。
        targetItem.dispatchEvent(
          new MouseEvent("mousedown", {
            bubbles: true,
            cancelable: true
          })
        );
        targetItem.dispatchEvent(
          new MouseEvent("mouseup", {
            bubbles: true,
            cancelable: true
          })
        );
      }
    }
  }
})();
```
</details>

:::note warn
このユーザスクリプトは2025-0116現在のGmailの実装に強く依存しており、Gmailの実装変更に極めて弱いことに注意してください。特にariaLabelやtextContentは変更されやすい属性であり、長期的に見ると動かなくなる可能性が高いです。必要に応じてDOM構造の確認やスクリプトの更新を行ってください。
:::