summaryrefslogtreecommitdiffstats
path: root/devtools/client/framework/browser-toolbox/test/browser_browser_toolbox_fission_inspector.js
blob: 24c7fa79180b095597d65b1eebba3a8cb9c99cb4 (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
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
/* Any copyright is dedicated to the Public Domain.
   http://creativecommons.org/publicdomain/zero/1.0/ */

// There are shutdown issues for which multiple rejections are left uncaught.
// See bug 1018184 for resolving these issues.
const { PromiseTestUtils } = ChromeUtils.importESModule(
  "resource://testing-common/PromiseTestUtils.sys.mjs"
);
PromiseTestUtils.allowMatchingRejectionsGlobally(/File closed/);

Services.scriptloader.loadSubScript(
  "chrome://mochitests/content/browser/devtools/client/inspector/test/shared-head.js",
  this
);

// On debug test machine, it takes about 50s to run the test.
requestLongerTimeout(4);

// This test is used to test fission-like features via the Browser Toolbox:
// - computed view is correct when selecting an element in a remote frame

add_task(async function () {
  // Forces the Browser Toolbox to open on the inspector by default
  await pushPref("devtools.browsertoolbox.panel", "inspector");
  // Enable Multiprocess Browser Toolbox
  await pushPref("devtools.browsertoolbox.scope", "everything");

  const ToolboxTask = await initBrowserToolboxTask();
  await ToolboxTask.importFunctions({
    getNodeFront,
    getNodeFrontInFrames,
    selectNode,
    // selectNodeInFrames depends on selectNode, getNodeFront, getNodeFrontInFrames.
    selectNodeInFrames,
  });

  // Open the tab *after* opening the Browser Toolbox in order to force creating the remote frames
  // late and exercise frame target watching code.
  const tab = await addTab(
    `data:text/html,<div id="my-div" style="color: red">Foo</div><div id="second-div" style="color: blue">Foo</div>`
  );
  // Set a custom attribute on the tab's browser, in order to easily select it in the markup view
  tab.linkedBrowser.setAttribute("test-tab", "true");

  const color = await ToolboxTask.spawn(null, async () => {
    /* global gToolbox */
    const inspector = gToolbox.getPanel("inspector");
    const onSidebarSelect = inspector.sidebar.once("select");
    inspector.sidebar.select("computedview");
    await onSidebarSelect;

    await selectNodeInFrames(
      ['browser[remote="true"][test-tab]', "#my-div"],
      inspector
    );

    const view = inspector.getPanel("computedview").computedView;
    function getProperty(name) {
      const propertyViews = view.propertyViews;
      for (const propView of propertyViews) {
        if (propView.name == name) {
          return propView;
        }
      }
      return null;
    }
    const prop = getProperty("color");
    return prop.valueNode.textContent;
  });

  is(
    color,
    "rgb(255, 0, 0)",
    "The color property of the <div> within a tab isn't red"
  );

  info("Check that the node picker can be used on element in the content page");
  await pickNodeInContentPage(
    ToolboxTask,
    tab,
    "browser[test-tab]",
    "#second-div"
  );
  const secondColor = await ToolboxTask.spawn(null, async () => {
    const inspector = gToolbox.getPanel("inspector");

    is(
      inspector.selection.nodeFront.id,
      "second-div",
      "The expected element is selected in the inspector"
    );

    const view = inspector.getPanel("computedview").computedView;
    function getProperty(name) {
      const propertyViews = view.propertyViews;
      for (const propView of propertyViews) {
        if (propView.name == name) {
          return propView;
        }
      }
      return null;
    }
    const prop = getProperty("color");
    return prop.valueNode.textContent;
  });

  is(
    secondColor,
    "rgb(0, 0, 255)",
    "The color property of the <div> within a tab isn't blue"
  );

  info(
    "Check that the node picker can be used for element in non-remote <browser>"
  );
  const nonRemoteUrl = "about:robots";
  const nonRemoteTab = await addTab(nonRemoteUrl);
  // Set a custom attribute on the tab's browser, in order to target it
  nonRemoteTab.linkedBrowser.setAttribute("test-tab-non-remote", "");

  // check that the browser element is indeed not remote. If that changes for about:robots,
  // this should be replaced with another page
  is(
    nonRemoteTab.linkedBrowser.hasAttribute("remote"),
    false,
    "The <browser> element for about:robots is not remote"
  );

  await pickNodeInContentPage(
    ToolboxTask,
    nonRemoteTab,
    "browser[test-tab-non-remote]",
    "#errorTryAgain"
  );

  await ToolboxTask.spawn(null, async () => {
    const inspector = gToolbox.getPanel("inspector");
    is(
      inspector.selection.nodeFront.id,
      "errorTryAgain",
      "The element inside a non-remote <browser> element is selected in the inspector"
    );
  });

  await ToolboxTask.destroy();
});

async function pickNodeInContentPage(
  ToolboxTask,
  tab,
  browserElementSelector,
  contentElementSelector
) {
  await ToolboxTask.spawn(contentElementSelector, async _selector => {
    const onPickerStarted = gToolbox.nodePicker.once("picker-started");

    // Wait until the inspector front was initialized in the target that
    // contains the element we want to pick.
    // Otherwise, even if the picker is "started", the corresponding WalkerActor
    // might not be listening to the correct pick events (WalkerActor::pick)
    const onPickerReady = new Promise(resolve => {
      gToolbox.nodePicker.on(
        "inspector-front-ready-for-picker",
        async function onFrontReady(walker) {
          if (await walker.querySelector(walker.rootNode, _selector)) {
            gToolbox.nodePicker.off(
              "inspector-front-ready-for-picker",
              onFrontReady
            );
            resolve();
          }
        }
      );
    });

    gToolbox.nodePicker.start();
    await onPickerStarted;
    await onPickerReady;

    const inspector = gToolbox.getPanel("inspector");

    // Save the promises for later tasks, in order to start listening
    // *before* hovering the element and wait for resolution *after* hovering.
    this.onPickerStopped = gToolbox.nodePicker.once("picker-stopped");
    this.onInspectorUpdated = inspector.once("inspector-updated");
  });

  // Retrieve the position of the element we want to pick in the content page
  const { x, y } = await SpecialPowers.spawn(
    tab.linkedBrowser,
    [contentElementSelector],
    _selector => {
      const rect = content.document
        .querySelector(_selector)
        .getBoundingClientRect();
      return { x: rect.x, y: rect.y };
    }
  );

  // Synthesize the mouse event in the top level browsing context, but on the <browser>
  // element containing the tab we're looking at, at the position where should be the
  // content element.
  // We need to do this to mimick what's actually done in node-picker.js
  await EventUtils.synthesizeMouse(
    document.querySelector(browserElementSelector),
    x + 5,
    y + 5,
    {}
  );

  await ToolboxTask.spawn(null, async () => {
    info(" # Waiting for picker stop");
    await this.onPickerStopped;
    info(" # Waiting for inspector-updated");
    await this.onInspectorUpdated;

    delete this.onPickerStopped;
    delete this.onInspectorUpdated;
  });
}