summaryrefslogtreecommitdiffstats
path: root/testing/specialpowers/content/AppTestDelegateParent.sys.mjs
blob: b271dea3df9aacf47627f7762ad2efef33c0d690 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * This module provides the bridge between the "AppTestDelegate" helper in
 * mochitests and the supporting implementations in AppUiTestDelegate.sys.mjs.
 *
 * "AppTestDelegate" is documented in AppTestDelegate.sys.mjs and enables
 * mochitests to invoke common functionality whose implementation is different
 * (e.g. in browser/ and mobile/ instead of toolkit/).
 * Tests can use this common interface after importing AppTestDelegate.sys.mjs:
 *
 *     // head.js, in the scope of a plain mochitest:
 *     var { AppTestDelegate } = SpecialPowers.ChromeUtils.importESModule(
 *       "resource://specialpowers/AppTestDelegate.sys.mjs"
 *     );
 *
 *     // test usage example: open and close a tab.
 *     let tab = await AppTestDelegate.openNewForegroundTab(window, url);
 *     await AppTestDelegate.removeTab(window, tab);
 *
 * ## Overview of files supporting "AppTestDelegate":
 *
 * MOZ_BUILD_APP-specific AppUiTestDelegate.sys.mjs implementations:
 * - browser/components/extensions/test/AppUiTestDelegate.jsm
 * - mobile/android/modules/test/AppUiTestDelegate.jsm
 * - mail/components/extensions/test/AppUiTestDelegate.sys.mjs (in comm-central)
 *
 * Glue between AppUiTestDelegate.sys.mjs in parent and test code in child:
 * - testing/specialpowers/content/AppTestDelegateParent.sys.mjs (this file)
 * - testing/specialpowers/content/AppTestDelegateChild.sys.mjs
 * - testing/specialpowers/content/AppTestDelegate.sys.mjs
 *
 * Setup for usage by test code in child (i.e. plain mochitests):
 * - Import AppTestDelegate.sys.mjs (e.g. in head.js or the test)
 *
 * Note: some browser-chrome tests import AppUiTestDelegate.sys.mjs directly,
 * but that is not part of this API contract. They merely reuse code.
 *
 * ## How to add new AppTestDelegate methods
 *
 * - Add the method to AppTestDelegate.sys.mjs
 * - Add a message forwarder in AppTestDelegateChild.sys.mjs
 * - Add a message handler in AppTestDelegateParent.sys.mjs
 * - Add an implementation in AppUiTestDelegate.sys.mjs for each MOZ_BUILD_APP,
 *   by defining the method on the exported AppUiTestDelegate object.
 *   All AppUiTestDelegate implementations must be kept in sync to have the
 *   same interface!
 *
 * You should use the same method name across all of these files for ease of
 * lookup and maintainability.
 */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  // Each app needs to implement this - see above comment.
  AppUiTestDelegate: "resource://testing-common/AppUiTestDelegate.sys.mjs",
});

export class AppTestDelegateParent extends JSWindowActorParent {
  constructor() {
    super();
    this._tabs = new Map();
  }

  get browser() {
    return this.browsingContext.top.embedderElement;
  }

  get window() {
    return this.browser.ownerGlobal;
  }

  async receiveMessage(message) {
    const { extensionId, url, waitForLoad, tabId } = message.data;
    switch (message.name) {
      case "DOMContentLoaded":
      case "load": {
        return this.browser?.dispatchEvent(
          new CustomEvent(`AppTestDelegate:${message.name}`, {
            detail: {
              browsingContext: this.browsingContext,
              ...message.data,
            },
          })
        );
      }
      case "clickPageAction":
        return lazy.AppUiTestDelegate.clickPageAction(this.window, extensionId);
      case "clickBrowserAction":
        return lazy.AppUiTestDelegate.clickBrowserAction(
          this.window,
          extensionId
        );
      case "closePageAction":
        return lazy.AppUiTestDelegate.closePageAction(this.window, extensionId);
      case "closeBrowserAction":
        return lazy.AppUiTestDelegate.closeBrowserAction(
          this.window,
          extensionId
        );
      case "awaitExtensionPanel":
        // The desktop delegate returns a <browser>, but that cannot be sent
        // over IPC, so just ignore it. The promise resolves when the panel and
        // its content is fully loaded.
        await lazy.AppUiTestDelegate.awaitExtensionPanel(
          this.window,
          extensionId
        );
        return null;
      case "openNewForegroundTab": {
        // We cannot send the tab object across process so let's store it with
        // a unique ID here.
        const uuid = Services.uuid.generateUUID().toString();
        const tab = await lazy.AppUiTestDelegate.openNewForegroundTab(
          this.window,
          url,
          waitForLoad
        );
        this._tabs.set(uuid, tab);
        return uuid;
      }
      case "removeTab": {
        const tab = this._tabs.get(tabId);
        this._tabs.delete(tabId);
        return lazy.AppUiTestDelegate.removeTab(tab);
      }

      default:
        throw new Error(`Unknown Test API: ${message.name}.`);
    }
  }
}