summaryrefslogtreecommitdiffstats
path: root/browser/components/extensions/test/browser/head_devtools.js
blob: f46254d43591b0d86292a3a5e0cdf8193f01f6c4 (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
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
"use strict";

/* exported
     assertDevToolsExtensionEnabled,
     closeToolboxForTab,
     navigateToWithDevToolsOpen
     openToolboxForTab,
     registerBlankToolboxPanel,
     TOOLBOX_BLANK_PANEL_ID,
*/

ChromeUtils.defineESModuleGetters(this, {
  loader: "resource://devtools/shared/loader/Loader.sys.mjs",
  DevToolsShim: "chrome://devtools-startup/content/DevToolsShim.sys.mjs",
});
XPCOMUtils.defineLazyGetter(this, "gDevTools", () => {
  const { gDevTools } = loader.require("devtools/client/framework/devtools");
  return gDevTools;
});

const TOOLBOX_BLANK_PANEL_ID = "testBlankPanel";

// Register a blank custom tool so that we don't need to wait the webconsole
// to be fully loaded/unloaded to prevent intermittent failures (related
// to a webconsole that is still loading when the test has been completed).
async function registerBlankToolboxPanel() {
  const testBlankPanel = {
    id: TOOLBOX_BLANK_PANEL_ID,
    url: "about:blank",
    label: "Blank Tool",
    isToolSupported() {
      return true;
    },
    build(iframeWindow, toolbox) {
      return Promise.resolve({
        target: toolbox.target,
        toolbox: toolbox,
        isReady: true,
        panelDoc: iframeWindow.document,
        destroy() {},
      });
    },
  };

  registerCleanupFunction(() => {
    gDevTools.unregisterTool(testBlankPanel.id);
  });

  gDevTools.registerTool(testBlankPanel);
}

async function openToolboxForTab(tab, panelId = TOOLBOX_BLANK_PANEL_ID) {
  if (
    panelId == TOOLBOX_BLANK_PANEL_ID &&
    !gDevTools.getToolDefinition(panelId)
  ) {
    info(`Registering ${TOOLBOX_BLANK_PANEL_ID} tool to the developer tools`);
    registerBlankToolboxPanel();
  }

  const toolbox = await gDevTools.showToolboxForTab(tab, { toolId: panelId });
  const { url, outerWindowID } = toolbox.target.form;
  info(
    `Developer toolbox opened on panel "${panelId}" for target ${JSON.stringify(
      { url, outerWindowID }
    )}`
  );
  return toolbox;
}

async function closeToolboxForTab(tab) {
  await gDevTools.closeToolboxForTab(tab);
  const tabUrl = tab.linkedBrowser.currentURI.spec;
  info(`Developer toolbox closed for tab "${tabUrl}"`);
}

function assertDevToolsExtensionEnabled(uuid, enabled) {
  for (let toolbox of DevToolsShim.getToolboxes()) {
    is(
      enabled,
      !!toolbox.isWebExtensionEnabled(uuid),
      `extension is ${enabled ? "enabled" : "disabled"} on toolbox`
    );
  }
}

/**
 * Navigate the currently selected tab to a new URL and wait for it to load.
 * Also wait for the toolbox to attach to the new target, if we navigated
 * to a new process.
 *
 * @param {object} tab The tab to redirect.
 * @param {string} uri The url to be loaded in the current tab.
 * @param {boolean} isErrorPage You may pass `true` is the URL is an error
 *                    page. Otherwise BrowserTestUtils.browserLoaded will wait
 *                    for 'load' event, which never fires for error pages.
 *
 * @returns {Promise} A promise that resolves when the page has fully loaded.
 */
async function navigateToWithDevToolsOpen(tab, uri, isErrorPage = false) {
  const toolbox = await gDevTools.getToolboxForTab(tab);
  const target = toolbox.target;

  // If we're switching origins, we need to wait for the 'switched-target'
  // event to make sure everything is ready.
  // Navigating from/to pages loaded in the parent process, like about:robots,
  // also spawn new targets.
  // (If target switching is disabled, the toolbox will reboot)
  const onTargetSwitched =
    toolbox.commands.targetCommand.once("switched-target");
  // Otherwise, if we don't switch target, it is safe to wait for navigate event.
  const onNavigate = target.once("navigate");

  // If the current top-level target follows the window global lifecycle, a
  // target switch will occur regardless of process changes.
  const targetFollowsWindowLifecycle =
    target.targetForm.followWindowGlobalLifeCycle;

  info(`Load document "${uri}"`);
  const browser = gBrowser.selectedBrowser;
  const currentPID = browser.browsingContext.currentWindowGlobal.osPid;
  const currentBrowsingContextID = browser.browsingContext.id;
  const onBrowserLoaded = BrowserTestUtils.browserLoaded(
    browser,
    false,
    null,
    isErrorPage
  );
  BrowserTestUtils.loadURIString(browser, uri);

  info(`Waiting for page to be loaded…`);
  await onBrowserLoaded;
  info(`→ page loaded`);

  // Compare the PIDs (and not the toolbox's targets) as PIDs are updated also immediately,
  // while target may be updated slightly later.
  const switchedToAnotherProcess =
    currentPID !== browser.browsingContext.currentWindowGlobal.osPid;
  const switchedToAnotherBrowsingContext =
    currentBrowsingContextID !== browser.browsingContext.id;

  // If:
  // - the tab navigated to another process, or,
  // - the tab navigated to another browsing context, or,
  // - if the old target follows the window lifecycle
  // then, expect a target switching.
  if (
    switchedToAnotherProcess ||
    targetFollowsWindowLifecycle ||
    switchedToAnotherBrowsingContext
  ) {
    info(`Waiting for target switch…`);
    await onTargetSwitched;
    info(`→ switched-target emitted`);
  } else {
    info(`Waiting for target 'navigate' event…`);
    await onNavigate;
    info(`→ 'navigate' emitted`);
  }
}