summaryrefslogtreecommitdiffstats
path: root/devtools/client/framework/browser-toolbox/test/browser_browser_toolbox_debugger.js
blob: edcba359e2bc981ff4d1550d82f044fb7b76ecb2 (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
221
222
/* Any copyright is dedicated to the Public Domain.
   http://creativecommons.org/publicdomain/zero/1.0/ */

// This test asserts that the new debugger works from the browser toolbox process

// 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/);

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

/* eslint-disable mozilla/no-arbitrary-setTimeout */

const { fetch } = require("resource://devtools/shared/DevToolsUtils.js");

const debuggerHeadURL =
  CHROME_URL_ROOT + "../../../debugger/test/mochitest/shared-head.js";

add_task(async function runTest() {
  let { content: debuggerHead } = await fetch(debuggerHeadURL);

  // We remove its import of shared-head, which isn't available in browser toolbox process
  // And isn't needed thanks to testHead's symbols
  debuggerHead = debuggerHead.replace(
    /Services.scriptloader.loadSubScript[^\)]*\);/g,
    ""
  );

  await pushPref("devtools.browsertoolbox.scope", "everything");

  const ToolboxTask = await initBrowserToolboxTask();
  await ToolboxTask.importFunctions({
    // head.js uses this method
    registerCleanupFunction: () => {},
    waitForDispatch,
    waitUntil,
  });
  await ToolboxTask.importScript(debuggerHead);

  info("### First test breakpoint in the parent process script");
  const s = Cu.Sandbox("http://mozilla.org");

  // Use a unique id for the fake script name in order to be able to run
  // this test more than once. That's because the Sandbox is not immediately
  // destroyed and so the debugger would display only one file but not necessarily
  // connected to the latest sandbox.
  const id = new Date().getTime();

  // Pass a fake URL to evalInSandbox. If we just pass a filename,
  // Debugger is going to fail and only display root folder (`/`) listing.
  // But it won't try to fetch this url and use sandbox content as expected.
  const testUrl = `http://mozilla.org/browser-toolbox-test-${id}.js`;
  Cu.evalInSandbox(
    `this.plop = function plop() {
  const foo = 1;
  return foo;
};`,
    s,
    "1.8",
    testUrl,
    0
  );

  // Execute the function every second in order to trigger the breakpoint
  const interval = setInterval(s.plop, 1000);

  await ToolboxTask.spawn(testUrl, async _testUrl => {
    /* global gToolbox, createDebuggerContext, waitForSources, waitForPaused,
          addBreakpoint, assertPausedAtSourceAndLine, stepIn, findSource,
          removeBreakpoint, resume, selectSource, assertNotPaused, assertBreakpoint,
          assertTextContentOnLine, waitForResumed */
    Services.prefs.clearUserPref("devtools.debugger.tabs");
    Services.prefs.clearUserPref("devtools.debugger.pending-selected-location");

    info("Waiting for debugger load");
    await gToolbox.selectTool("jsdebugger");
    const dbg = createDebuggerContext(gToolbox);

    await waitForSources(dbg, _testUrl);

    info("Loaded, selecting the test script to debug");
    const fileName = _testUrl.match(/browser-toolbox-test.*\.js/)[0];
    await selectSource(dbg, fileName);

    info("Add a breakpoint and wait to be paused");
    const onPaused = waitForPaused(dbg);
    await addBreakpoint(dbg, fileName, 2);
    await onPaused;

    const source = findSource(dbg, fileName);
    assertPausedAtSourceAndLine(dbg, source.id, 2);
    assertTextContentOnLine(dbg, 2, "const foo = 1;");
    is(
      dbg.selectors.getBreakpointCount(),
      1,
      "There is exactly one breakpoint"
    );

    await stepIn(dbg);

    assertPausedAtSourceAndLine(dbg, source.id, 3);
    assertTextContentOnLine(dbg, 3, "return foo;");
    is(
      dbg.selectors.getBreakpointCount(),
      1,
      "We still have only one breakpoint after step-in"
    );

    // Remove the breakpoint before resuming in order to prevent hitting the breakpoint
    // again during test closing.
    await removeBreakpoint(dbg, source.id, 2);

    await resume(dbg);

    // Let a change for the interval to re-execute
    await new Promise(r => setTimeout(r, 1000));

    is(dbg.selectors.getBreakpointCount(), 0, "There is no more breakpoints");

    assertNotPaused(dbg);
  });

  clearInterval(interval);

  info("### Now test breakpoint in a privileged content process script");
  const testUrl2 = `http://mozilla.org/content-process-test-${id}.js`;
  await SpecialPowers.spawn(gBrowser.selectedBrowser, [testUrl2], testUrl => {
    // Use a sandbox in order to have a URL to set a breakpoint
    const s = Cu.Sandbox("http://mozilla.org");
    Cu.evalInSandbox(
      `this.foo = function foo() {
  const plop = 1;
  return plop;
};`,
      s,
      "1.8",
      testUrl,
      0
    );
    content.interval = content.setInterval(s.foo, 1000);
  });
  await ToolboxTask.spawn(testUrl2, async _testUrl => {
    const dbg = createDebuggerContext(gToolbox);

    const fileName = _testUrl.match(/content-process-test.*\.js/)[0];
    await waitForSources(dbg, _testUrl);

    await selectSource(dbg, fileName);

    const onPaused = waitForPaused(dbg);
    await addBreakpoint(dbg, fileName, 2);
    await onPaused;

    const source = findSource(dbg, fileName);
    assertPausedAtSourceAndLine(dbg, source.id, 2);
    assertTextContentOnLine(dbg, 2, "const plop = 1;");
    await assertBreakpoint(dbg, 2);
    is(dbg.selectors.getBreakpointCount(), 1, "We have exactly one breakpoint");

    await stepIn(dbg);

    assertPausedAtSourceAndLine(dbg, source.id, 3);
    assertTextContentOnLine(dbg, 3, "return plop;");
    is(
      dbg.selectors.getBreakpointCount(),
      1,
      "We still have only one breakpoint after step-in"
    );

    // Remove the breakpoint before resuming in order to prevent hitting the breakpoint
    // again during test closing.
    await removeBreakpoint(dbg, source.id, 2);

    await resume(dbg);

    // Let a change for the interval to re-execute
    await new Promise(r => setTimeout(r, 1000));

    is(dbg.selectors.getBreakpointCount(), 0, "There is no more breakpoints");

    assertNotPaused(dbg);
  });

  await SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => {
    content.clearInterval(content.interval);
  });

  info("Trying pausing in a content process that crashes");

  const crashingUrl =
    "data:text/html,<script>setTimeout(()=>{debugger;})</script>";
  const crashingTab = await addTab(crashingUrl);
  await ToolboxTask.spawn(crashingUrl, async url => {
    const dbg = createDebuggerContext(gToolbox);
    await waitForPaused(dbg);
    const source = findSource(dbg, url);
    assertPausedAtSourceAndLine(dbg, source.id, 1);
    const thread = dbg.selectors.getThread(dbg.selectors.getCurrentThread());
    is(thread.isTopLevel, false, "The current thread is not the top level one");
    is(thread.targetType, "process", "The current thread is the tab one");
  });

  info(
    "Crash the tab and ensure the debugger resumes and switch to the main thread"
  );
  await BrowserTestUtils.crashFrame(crashingTab.linkedBrowser);

  await ToolboxTask.spawn(null, async () => {
    const dbg = createDebuggerContext(gToolbox);
    await waitForResumed(dbg);
    const thread = dbg.selectors.getThread(dbg.selectors.getCurrentThread());
    is(thread.isTopLevel, true, "The current thread is the top level one");
  });

  await removeTab(crashingTab);

  await ToolboxTask.destroy();
});