browser/devtools/debugger/test/head.js

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

michael@0 1 /* Any copyright is dedicated to the Public Domain.
michael@0 2 http://creativecommons.org/publicdomain/zero/1.0/ */
michael@0 3 "use strict";
michael@0 4
michael@0 5 const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
michael@0 6
michael@0 7 let { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
michael@0 8
michael@0 9 // Disable logging for faster test runs. Set this pref to true if you want to
michael@0 10 // debug a test in your try runs. Both the debugger server and frontend will
michael@0 11 // be affected by this pref.
michael@0 12 let gEnableLogging = Services.prefs.getBoolPref("devtools.debugger.log");
michael@0 13 Services.prefs.setBoolPref("devtools.debugger.log", false);
michael@0 14
michael@0 15 let { Task } = Cu.import("resource://gre/modules/Task.jsm", {});
michael@0 16 let { Promise: promise } = Cu.import("resource://gre/modules/Promise.jsm", {});
michael@0 17 let { gDevTools } = Cu.import("resource:///modules/devtools/gDevTools.jsm", {});
michael@0 18 let { devtools } = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
michael@0 19 let { require } = devtools;
michael@0 20 let { DevToolsUtils } = Cu.import("resource://gre/modules/devtools/DevToolsUtils.jsm", {});
michael@0 21 let { BrowserToolboxProcess } = Cu.import("resource:///modules/devtools/ToolboxProcess.jsm", {});
michael@0 22 let { DebuggerServer } = Cu.import("resource://gre/modules/devtools/dbg-server.jsm", {});
michael@0 23 let { DebuggerClient } = Cu.import("resource://gre/modules/devtools/dbg-client.jsm", {});
michael@0 24 let { AddonManager } = Cu.import("resource://gre/modules/AddonManager.jsm", {});
michael@0 25 const { promiseInvoke } = require("devtools/async-utils");
michael@0 26 let TargetFactory = devtools.TargetFactory;
michael@0 27 let Toolbox = devtools.Toolbox;
michael@0 28
michael@0 29 const EXAMPLE_URL = "http://example.com/browser/browser/devtools/debugger/test/";
michael@0 30
michael@0 31 gDevTools.testing = true;
michael@0 32 SimpleTest.registerCleanupFunction(() => {
michael@0 33 gDevTools.testing = false;
michael@0 34 });
michael@0 35
michael@0 36 // All tests are asynchronous.
michael@0 37 waitForExplicitFinish();
michael@0 38
michael@0 39 registerCleanupFunction(function() {
michael@0 40 info("finish() was called, cleaning up...");
michael@0 41 Services.prefs.setBoolPref("devtools.debugger.log", gEnableLogging);
michael@0 42
michael@0 43 // Properly shut down the server to avoid memory leaks.
michael@0 44 DebuggerServer.destroy();
michael@0 45
michael@0 46 // Debugger tests use a lot of memory, so force a GC to help fragmentation.
michael@0 47 info("Forcing GC after debugger test.");
michael@0 48 Cu.forceGC();
michael@0 49 });
michael@0 50
michael@0 51 // Import the GCLI test helper
michael@0 52 let testDir = gTestPath.substr(0, gTestPath.lastIndexOf("/"));
michael@0 53 Services.scriptloader.loadSubScript(testDir + "../../../commandline/test/helpers.js", this);
michael@0 54
michael@0 55 // Redeclare dbg_assert with a fatal behavior.
michael@0 56 function dbg_assert(cond, e) {
michael@0 57 if (!cond) {
michael@0 58 throw e;
michael@0 59 }
michael@0 60 }
michael@0 61
michael@0 62 function addWindow(aUrl) {
michael@0 63 info("Adding window: " + aUrl);
michael@0 64 return promise.resolve(getDOMWindow(window.open(aUrl)));
michael@0 65 }
michael@0 66
michael@0 67 function getDOMWindow(aReference) {
michael@0 68 return aReference
michael@0 69 .QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIWebNavigation)
michael@0 70 .QueryInterface(Ci.nsIDocShellTreeItem).rootTreeItem
michael@0 71 .QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindow);
michael@0 72 }
michael@0 73
michael@0 74 function addTab(aUrl, aWindow) {
michael@0 75 info("Adding tab: " + aUrl);
michael@0 76
michael@0 77 let deferred = promise.defer();
michael@0 78 let targetWindow = aWindow || window;
michael@0 79 let targetBrowser = targetWindow.gBrowser;
michael@0 80
michael@0 81 targetWindow.focus();
michael@0 82 let tab = targetBrowser.selectedTab = targetBrowser.addTab(aUrl);
michael@0 83 let linkedBrowser = tab.linkedBrowser;
michael@0 84
michael@0 85 linkedBrowser.addEventListener("load", function onLoad() {
michael@0 86 linkedBrowser.removeEventListener("load", onLoad, true);
michael@0 87 info("Tab added and finished loading: " + aUrl);
michael@0 88 deferred.resolve(tab);
michael@0 89 }, true);
michael@0 90
michael@0 91 return deferred.promise;
michael@0 92 }
michael@0 93
michael@0 94 function removeTab(aTab, aWindow) {
michael@0 95 info("Removing tab.");
michael@0 96
michael@0 97 let deferred = promise.defer();
michael@0 98 let targetWindow = aWindow || window;
michael@0 99 let targetBrowser = targetWindow.gBrowser;
michael@0 100 let tabContainer = targetBrowser.tabContainer;
michael@0 101
michael@0 102 tabContainer.addEventListener("TabClose", function onClose(aEvent) {
michael@0 103 tabContainer.removeEventListener("TabClose", onClose, false);
michael@0 104 info("Tab removed and finished closing.");
michael@0 105 deferred.resolve();
michael@0 106 }, false);
michael@0 107
michael@0 108 targetBrowser.removeTab(aTab);
michael@0 109 return deferred.promise;
michael@0 110 }
michael@0 111
michael@0 112 function addAddon(aUrl) {
michael@0 113 info("Installing addon: " + aUrl);
michael@0 114
michael@0 115 let deferred = promise.defer();
michael@0 116
michael@0 117 AddonManager.getInstallForURL(aUrl, aInstaller => {
michael@0 118 aInstaller.install();
michael@0 119 let listener = {
michael@0 120 onInstallEnded: function(aAddon, aAddonInstall) {
michael@0 121 aInstaller.removeListener(listener);
michael@0 122
michael@0 123 // Wait for add-on's startup scripts to execute. See bug 997408
michael@0 124 executeSoon(function() {
michael@0 125 deferred.resolve(aAddonInstall);
michael@0 126 });
michael@0 127 }
michael@0 128 };
michael@0 129 aInstaller.addListener(listener);
michael@0 130 }, "application/x-xpinstall");
michael@0 131
michael@0 132 return deferred.promise;
michael@0 133 }
michael@0 134
michael@0 135 function removeAddon(aAddon) {
michael@0 136 info("Removing addon.");
michael@0 137
michael@0 138 let deferred = promise.defer();
michael@0 139
michael@0 140 let listener = {
michael@0 141 onUninstalled: function(aUninstalledAddon) {
michael@0 142 if (aUninstalledAddon != aAddon) {
michael@0 143 return;
michael@0 144 }
michael@0 145 AddonManager.removeAddonListener(listener);
michael@0 146 deferred.resolve();
michael@0 147 }
michael@0 148 };
michael@0 149 AddonManager.addAddonListener(listener);
michael@0 150 aAddon.uninstall();
michael@0 151
michael@0 152 return deferred.promise;
michael@0 153 }
michael@0 154
michael@0 155 function getTabActorForUrl(aClient, aUrl) {
michael@0 156 let deferred = promise.defer();
michael@0 157
michael@0 158 aClient.listTabs(aResponse => {
michael@0 159 let tabActor = aResponse.tabs.filter(aGrip => aGrip.url == aUrl).pop();
michael@0 160 deferred.resolve(tabActor);
michael@0 161 });
michael@0 162
michael@0 163 return deferred.promise;
michael@0 164 }
michael@0 165
michael@0 166 function getAddonActorForUrl(aClient, aUrl) {
michael@0 167 info("Get addon actor for URL: " + aUrl);
michael@0 168 let deferred = promise.defer();
michael@0 169
michael@0 170 aClient.listAddons(aResponse => {
michael@0 171 let addonActor = aResponse.addons.filter(aGrip => aGrip.url == aUrl).pop();
michael@0 172 info("got addon actor for URL: " + addonActor.actor);
michael@0 173 deferred.resolve(addonActor);
michael@0 174 });
michael@0 175
michael@0 176 return deferred.promise;
michael@0 177 }
michael@0 178
michael@0 179 function attachTabActorForUrl(aClient, aUrl) {
michael@0 180 let deferred = promise.defer();
michael@0 181
michael@0 182 getTabActorForUrl(aClient, aUrl).then(aGrip => {
michael@0 183 aClient.attachTab(aGrip.actor, aResponse => {
michael@0 184 deferred.resolve([aGrip, aResponse]);
michael@0 185 });
michael@0 186 });
michael@0 187
michael@0 188 return deferred.promise;
michael@0 189 }
michael@0 190
michael@0 191 function attachThreadActorForUrl(aClient, aUrl) {
michael@0 192 let deferred = promise.defer();
michael@0 193
michael@0 194 attachTabActorForUrl(aClient, aUrl).then(([aGrip, aResponse]) => {
michael@0 195 aClient.attachThread(aResponse.threadActor, (aResponse, aThreadClient) => {
michael@0 196 aThreadClient.resume(aResponse => {
michael@0 197 deferred.resolve(aThreadClient);
michael@0 198 });
michael@0 199 });
michael@0 200 });
michael@0 201
michael@0 202 return deferred.promise;
michael@0 203 }
michael@0 204
michael@0 205 function once(aTarget, aEventName, aUseCapture = false) {
michael@0 206 info("Waiting for event: '" + aEventName + "' on " + aTarget + ".");
michael@0 207
michael@0 208 let deferred = promise.defer();
michael@0 209
michael@0 210 for (let [add, remove] of [
michael@0 211 ["addEventListener", "removeEventListener"],
michael@0 212 ["addListener", "removeListener"],
michael@0 213 ["on", "off"]
michael@0 214 ]) {
michael@0 215 if ((add in aTarget) && (remove in aTarget)) {
michael@0 216 aTarget[add](aEventName, function onEvent(...aArgs) {
michael@0 217 aTarget[remove](aEventName, onEvent, aUseCapture);
michael@0 218 deferred.resolve.apply(deferred, aArgs);
michael@0 219 }, aUseCapture);
michael@0 220 break;
michael@0 221 }
michael@0 222 }
michael@0 223
michael@0 224 return deferred.promise;
michael@0 225 }
michael@0 226
michael@0 227 function waitForTick() {
michael@0 228 let deferred = promise.defer();
michael@0 229 executeSoon(deferred.resolve);
michael@0 230 return deferred.promise;
michael@0 231 }
michael@0 232
michael@0 233 function waitForTime(aDelay) {
michael@0 234 let deferred = promise.defer();
michael@0 235 setTimeout(deferred.resolve, aDelay);
michael@0 236 return deferred.promise;
michael@0 237 }
michael@0 238
michael@0 239 function waitForSourceShown(aPanel, aUrl) {
michael@0 240 return waitForDebuggerEvents(aPanel, aPanel.panelWin.EVENTS.SOURCE_SHOWN).then(aSource => {
michael@0 241 let sourceUrl = aSource.url;
michael@0 242 info("Source shown: " + sourceUrl);
michael@0 243
michael@0 244 if (!sourceUrl.contains(aUrl)) {
michael@0 245 return waitForSourceShown(aPanel, aUrl);
michael@0 246 } else {
michael@0 247 ok(true, "The correct source has been shown.");
michael@0 248 }
michael@0 249 });
michael@0 250 }
michael@0 251
michael@0 252 function waitForEditorLocationSet(aPanel) {
michael@0 253 return waitForDebuggerEvents(aPanel, aPanel.panelWin.EVENTS.EDITOR_LOCATION_SET);
michael@0 254 }
michael@0 255
michael@0 256 function ensureSourceIs(aPanel, aUrl, aWaitFlag = false) {
michael@0 257 if (aPanel.panelWin.DebuggerView.Sources.selectedValue.contains(aUrl)) {
michael@0 258 ok(true, "Expected source is shown: " + aUrl);
michael@0 259 return promise.resolve(null);
michael@0 260 }
michael@0 261 if (aWaitFlag) {
michael@0 262 return waitForSourceShown(aPanel, aUrl);
michael@0 263 }
michael@0 264 ok(false, "Expected source was not already shown: " + aUrl);
michael@0 265 return promise.reject(null);
michael@0 266 }
michael@0 267
michael@0 268 function waitForCaretUpdated(aPanel, aLine, aCol = 1) {
michael@0 269 return waitForEditorEvents(aPanel, "cursorActivity").then(() => {
michael@0 270 let cursor = aPanel.panelWin.DebuggerView.editor.getCursor();
michael@0 271 info("Caret updated: " + (cursor.line + 1) + ", " + (cursor.ch + 1));
michael@0 272
michael@0 273 if (!isCaretPos(aPanel, aLine, aCol)) {
michael@0 274 return waitForCaretUpdated(aPanel, aLine, aCol);
michael@0 275 } else {
michael@0 276 ok(true, "The correct caret position has been set.");
michael@0 277 }
michael@0 278 });
michael@0 279 }
michael@0 280
michael@0 281 function ensureCaretAt(aPanel, aLine, aCol = 1, aWaitFlag = false) {
michael@0 282 if (isCaretPos(aPanel, aLine, aCol)) {
michael@0 283 ok(true, "Expected caret position is set: " + aLine + "," + aCol);
michael@0 284 return promise.resolve(null);
michael@0 285 }
michael@0 286 if (aWaitFlag) {
michael@0 287 return waitForCaretUpdated(aPanel, aLine, aCol);
michael@0 288 }
michael@0 289 ok(false, "Expected caret position was not already set: " + aLine + "," + aCol);
michael@0 290 return promise.reject(null);
michael@0 291 }
michael@0 292
michael@0 293 function isCaretPos(aPanel, aLine, aCol = 1) {
michael@0 294 let editor = aPanel.panelWin.DebuggerView.editor;
michael@0 295 let cursor = editor.getCursor();
michael@0 296
michael@0 297 // Source editor starts counting line and column numbers from 0.
michael@0 298 info("Current editor caret position: " + (cursor.line + 1) + ", " + (cursor.ch + 1));
michael@0 299 return cursor.line == (aLine - 1) && cursor.ch == (aCol - 1);
michael@0 300 }
michael@0 301
michael@0 302 function isEditorSel(aPanel, [start, end]) {
michael@0 303 let editor = aPanel.panelWin.DebuggerView.editor;
michael@0 304 let range = {
michael@0 305 start: editor.getOffset(editor.getCursor("start")),
michael@0 306 end: editor.getOffset(editor.getCursor())
michael@0 307 };
michael@0 308
michael@0 309 // Source editor starts counting line and column numbers from 0.
michael@0 310 info("Current editor selection: " + (range.start + 1) + ", " + (range.end + 1));
michael@0 311 return range.start == (start - 1) && range.end == (end - 1);
michael@0 312 }
michael@0 313
michael@0 314 function waitForSourceAndCaret(aPanel, aUrl, aLine, aCol) {
michael@0 315 return promise.all([
michael@0 316 waitForSourceShown(aPanel, aUrl),
michael@0 317 waitForCaretUpdated(aPanel, aLine, aCol)
michael@0 318 ]);
michael@0 319 }
michael@0 320
michael@0 321 function waitForCaretAndScopes(aPanel, aLine, aCol) {
michael@0 322 return promise.all([
michael@0 323 waitForCaretUpdated(aPanel, aLine, aCol),
michael@0 324 waitForDebuggerEvents(aPanel, aPanel.panelWin.EVENTS.FETCHED_SCOPES)
michael@0 325 ]);
michael@0 326 }
michael@0 327
michael@0 328 function waitForSourceAndCaretAndScopes(aPanel, aUrl, aLine, aCol) {
michael@0 329 return promise.all([
michael@0 330 waitForSourceAndCaret(aPanel, aUrl, aLine, aCol),
michael@0 331 waitForDebuggerEvents(aPanel, aPanel.panelWin.EVENTS.FETCHED_SCOPES)
michael@0 332 ]);
michael@0 333 }
michael@0 334
michael@0 335 function waitForDebuggerEvents(aPanel, aEventName, aEventRepeat = 1) {
michael@0 336 info("Waiting for debugger event: '" + aEventName + "' to fire: " + aEventRepeat + " time(s).");
michael@0 337
michael@0 338 let deferred = promise.defer();
michael@0 339 let panelWin = aPanel.panelWin;
michael@0 340 let count = 0;
michael@0 341
michael@0 342 panelWin.on(aEventName, function onEvent(aEventName, ...aArgs) {
michael@0 343 info("Debugger event '" + aEventName + "' fired: " + (++count) + " time(s).");
michael@0 344
michael@0 345 if (count == aEventRepeat) {
michael@0 346 ok(true, "Enough '" + aEventName + "' panel events have been fired.");
michael@0 347 panelWin.off(aEventName, onEvent);
michael@0 348 deferred.resolve.apply(deferred, aArgs);
michael@0 349 }
michael@0 350 });
michael@0 351
michael@0 352 return deferred.promise;
michael@0 353 }
michael@0 354
michael@0 355 function waitForEditorEvents(aPanel, aEventName, aEventRepeat = 1) {
michael@0 356 info("Waiting for editor event: '" + aEventName + "' to fire: " + aEventRepeat + " time(s).");
michael@0 357
michael@0 358 let deferred = promise.defer();
michael@0 359 let editor = aPanel.panelWin.DebuggerView.editor;
michael@0 360 let count = 0;
michael@0 361
michael@0 362 editor.on(aEventName, function onEvent(...aArgs) {
michael@0 363 info("Editor event '" + aEventName + "' fired: " + (++count) + " time(s).");
michael@0 364
michael@0 365 if (count == aEventRepeat) {
michael@0 366 ok(true, "Enough '" + aEventName + "' editor events have been fired.");
michael@0 367 editor.off(aEventName, onEvent);
michael@0 368 deferred.resolve.apply(deferred, aArgs);
michael@0 369 }
michael@0 370 });
michael@0 371
michael@0 372 return deferred.promise;
michael@0 373 }
michael@0 374
michael@0 375 function waitForThreadEvents(aPanel, aEventName, aEventRepeat = 1) {
michael@0 376 info("Waiting for thread event: '" + aEventName + "' to fire: " + aEventRepeat + " time(s).");
michael@0 377
michael@0 378 let deferred = promise.defer();
michael@0 379 let thread = aPanel.panelWin.gThreadClient;
michael@0 380 let count = 0;
michael@0 381
michael@0 382 thread.addListener(aEventName, function onEvent(aEventName, ...aArgs) {
michael@0 383 info("Thread event '" + aEventName + "' fired: " + (++count) + " time(s).");
michael@0 384
michael@0 385 if (count == aEventRepeat) {
michael@0 386 ok(true, "Enough '" + aEventName + "' thread events have been fired.");
michael@0 387 thread.removeListener(aEventName, onEvent);
michael@0 388 deferred.resolve.apply(deferred, aArgs);
michael@0 389 }
michael@0 390 });
michael@0 391
michael@0 392 return deferred.promise;
michael@0 393 }
michael@0 394
michael@0 395 function waitForClientEvents(aPanel, aEventName, aEventRepeat = 1) {
michael@0 396 info("Waiting for client event: '" + aEventName + "' to fire: " + aEventRepeat + " time(s).");
michael@0 397
michael@0 398 let deferred = promise.defer();
michael@0 399 let client = aPanel.panelWin.gClient;
michael@0 400 let count = 0;
michael@0 401
michael@0 402 client.addListener(aEventName, function onEvent(aEventName, ...aArgs) {
michael@0 403 info("Thread event '" + aEventName + "' fired: " + (++count) + " time(s).");
michael@0 404
michael@0 405 if (count == aEventRepeat) {
michael@0 406 ok(true, "Enough '" + aEventName + "' thread events have been fired.");
michael@0 407 client.removeListener(aEventName, onEvent);
michael@0 408 deferred.resolve.apply(deferred, aArgs);
michael@0 409 }
michael@0 410 });
michael@0 411
michael@0 412 return deferred.promise;
michael@0 413 }
michael@0 414
michael@0 415 function ensureThreadClientState(aPanel, aState) {
michael@0 416 let thread = aPanel.panelWin.gThreadClient;
michael@0 417 let state = thread.state;
michael@0 418
michael@0 419 info("Thread is: '" + state + "'.");
michael@0 420
michael@0 421 if (state == aState) {
michael@0 422 return promise.resolve(null);
michael@0 423 } else {
michael@0 424 return waitForThreadEvents(aPanel, aState);
michael@0 425 }
michael@0 426 }
michael@0 427
michael@0 428 function navigateActiveTabTo(aPanel, aUrl, aWaitForEventName, aEventRepeat) {
michael@0 429 let finished = waitForDebuggerEvents(aPanel, aWaitForEventName, aEventRepeat);
michael@0 430 let activeTab = aPanel.panelWin.DebuggerController._target.activeTab;
michael@0 431 aUrl ? activeTab.navigateTo(aUrl) : activeTab.reload();
michael@0 432 return finished;
michael@0 433 }
michael@0 434
michael@0 435 function navigateActiveTabInHistory(aPanel, aDirection, aWaitForEventName, aEventRepeat) {
michael@0 436 let finished = waitForDebuggerEvents(aPanel, aWaitForEventName, aEventRepeat);
michael@0 437 content.history[aDirection]();
michael@0 438 return finished;
michael@0 439 }
michael@0 440
michael@0 441 function reloadActiveTab(aPanel, aWaitForEventName, aEventRepeat) {
michael@0 442 return navigateActiveTabTo(aPanel, null, aWaitForEventName, aEventRepeat);
michael@0 443 }
michael@0 444
michael@0 445 function clearText(aElement) {
michael@0 446 info("Clearing text...");
michael@0 447 aElement.focus();
michael@0 448 aElement.value = "";
michael@0 449 }
michael@0 450
michael@0 451 function setText(aElement, aText) {
michael@0 452 clearText(aElement);
michael@0 453 info("Setting text: " + aText);
michael@0 454 aElement.value = aText;
michael@0 455 }
michael@0 456
michael@0 457 function typeText(aElement, aText) {
michael@0 458 info("Typing text: " + aText);
michael@0 459 aElement.focus();
michael@0 460 EventUtils.sendString(aText, aElement.ownerDocument.defaultView);
michael@0 461 }
michael@0 462
michael@0 463 function backspaceText(aElement, aTimes) {
michael@0 464 info("Pressing backspace " + aTimes + " times.");
michael@0 465 for (let i = 0; i < aTimes; i++) {
michael@0 466 aElement.focus();
michael@0 467 EventUtils.sendKey("BACK_SPACE", aElement.ownerDocument.defaultView);
michael@0 468 }
michael@0 469 }
michael@0 470
michael@0 471 function getTab(aTarget, aWindow) {
michael@0 472 if (aTarget instanceof XULElement) {
michael@0 473 return promise.resolve(aTarget);
michael@0 474 } else {
michael@0 475 return addTab(aTarget, aWindow);
michael@0 476 }
michael@0 477 }
michael@0 478
michael@0 479 function getSources(aClient) {
michael@0 480 let deferred = promise.defer();
michael@0 481
michael@0 482 aClient.getSources(({sources}) => deferred.resolve(sources));
michael@0 483
michael@0 484 return deferred.promise;
michael@0 485 }
michael@0 486
michael@0 487 function initDebugger(aTarget, aWindow) {
michael@0 488 info("Initializing a debugger panel.");
michael@0 489
michael@0 490 return getTab(aTarget, aWindow).then(aTab => {
michael@0 491 info("Debugee tab added successfully: " + aTarget);
michael@0 492
michael@0 493 let deferred = promise.defer();
michael@0 494 let debuggee = aTab.linkedBrowser.contentWindow.wrappedJSObject;
michael@0 495 let target = TargetFactory.forTab(aTab);
michael@0 496
michael@0 497 gDevTools.showToolbox(target, "jsdebugger").then(aToolbox => {
michael@0 498 info("Debugger panel shown successfully.");
michael@0 499
michael@0 500 let debuggerPanel = aToolbox.getCurrentPanel();
michael@0 501 let panelWin = debuggerPanel.panelWin;
michael@0 502
michael@0 503 // Wait for the initial resume...
michael@0 504 panelWin.gClient.addOneTimeListener("resumed", () => {
michael@0 505 info("Debugger client resumed successfully.");
michael@0 506
michael@0 507 prepareDebugger(debuggerPanel);
michael@0 508 deferred.resolve([aTab, debuggee, debuggerPanel, aWindow]);
michael@0 509 });
michael@0 510 });
michael@0 511
michael@0 512 return deferred.promise;
michael@0 513 });
michael@0 514 }
michael@0 515
michael@0 516 // Creates an add-on debugger for a given add-on. The returned AddonDebugger
michael@0 517 // object must be destroyed before finishing the test
michael@0 518 function initAddonDebugger(aUrl) {
michael@0 519 let addonDebugger = new AddonDebugger();
michael@0 520 return addonDebugger.init(aUrl).then(() => addonDebugger);
michael@0 521 }
michael@0 522
michael@0 523 function AddonDebugger() {
michael@0 524 this._onMessage = this._onMessage.bind(this);
michael@0 525 }
michael@0 526
michael@0 527 AddonDebugger.prototype = {
michael@0 528 init: Task.async(function*(aUrl) {
michael@0 529 info("Initializing an addon debugger panel.");
michael@0 530
michael@0 531 if (!DebuggerServer.initialized) {
michael@0 532 DebuggerServer.init(() => true);
michael@0 533 DebuggerServer.addBrowserActors();
michael@0 534 }
michael@0 535
michael@0 536 this.frame = document.createElement("iframe");
michael@0 537 this.frame.setAttribute("height", 400);
michael@0 538 document.documentElement.appendChild(this.frame);
michael@0 539 window.addEventListener("message", this._onMessage);
michael@0 540
michael@0 541 let transport = DebuggerServer.connectPipe();
michael@0 542 this.client = new DebuggerClient(transport);
michael@0 543
michael@0 544 let connected = promise.defer();
michael@0 545 this.client.connect(connected.resolve);
michael@0 546 yield connected.promise;
michael@0 547
michael@0 548 let addonActor = yield getAddonActorForUrl(this.client, aUrl);
michael@0 549
michael@0 550 let targetOptions = {
michael@0 551 form: { addonActor: addonActor.actor, title: addonActor.name },
michael@0 552 client: this.client,
michael@0 553 chrome: true
michael@0 554 };
michael@0 555
michael@0 556 let toolboxOptions = {
michael@0 557 customIframe: this.frame
michael@0 558 };
michael@0 559
michael@0 560 let target = devtools.TargetFactory.forTab(targetOptions);
michael@0 561 let toolbox = yield gDevTools.showToolbox(target, "jsdebugger", devtools.Toolbox.HostType.CUSTOM, toolboxOptions);
michael@0 562
michael@0 563 info("Addon debugger panel shown successfully.");
michael@0 564
michael@0 565 this.debuggerPanel = toolbox.getCurrentPanel();
michael@0 566
michael@0 567 // Wait for the initial resume...
michael@0 568 yield waitForClientEvents(this.debuggerPanel, "resumed");
michael@0 569 yield prepareDebugger(this.debuggerPanel);
michael@0 570 }),
michael@0 571
michael@0 572 destroy: Task.async(function*() {
michael@0 573 let deferred = promise.defer();
michael@0 574 this.client.close(deferred.resolve);
michael@0 575 yield deferred.promise;
michael@0 576 yield this.debuggerPanel._toolbox.destroy();
michael@0 577 this.frame.remove();
michael@0 578 window.removeEventListener("message", this._onMessage);
michael@0 579 }),
michael@0 580
michael@0 581 /**
michael@0 582 * Returns a list of the groups and sources in the UI. The returned array
michael@0 583 * contains objects for each group with properties name and sources. The
michael@0 584 * sources property contains an array with objects for each source for that
michael@0 585 * group with properties label and url.
michael@0 586 */
michael@0 587 getSourceGroups: Task.async(function*() {
michael@0 588 let debuggerWin = this.debuggerPanel.panelWin;
michael@0 589 let sources = yield getSources(debuggerWin.gThreadClient);
michael@0 590 ok(sources.length, "retrieved sources");
michael@0 591
michael@0 592 // groups will be the return value, groupmap and the maps we put in it will
michael@0 593 // be used as quick lookups to add the url information in below
michael@0 594 let groups = [];
michael@0 595 let groupmap = new Map();
michael@0 596
michael@0 597 let uigroups = this.debuggerPanel.panelWin.document.querySelectorAll(".side-menu-widget-group");
michael@0 598 for (let g of uigroups) {
michael@0 599 let name = g.querySelector(".side-menu-widget-group-title .name").value;
michael@0 600 let group = {
michael@0 601 name: name,
michael@0 602 sources: []
michael@0 603 };
michael@0 604 groups.push(group);
michael@0 605 let labelmap = new Map();
michael@0 606 groupmap.set(name, labelmap);
michael@0 607
michael@0 608 for (let l of g.querySelectorAll(".dbg-source-item")) {
michael@0 609 let source = {
michael@0 610 label: l.value,
michael@0 611 url: null
michael@0 612 };
michael@0 613
michael@0 614 labelmap.set(l.value, source);
michael@0 615 group.sources.push(source);
michael@0 616 }
michael@0 617 }
michael@0 618
michael@0 619 for (let source of sources) {
michael@0 620 let { label, group } = debuggerWin.DebuggerView.Sources.getItemByValue(source.url).attachment;
michael@0 621
michael@0 622 if (!groupmap.has(group)) {
michael@0 623 ok(false, "Saw a source group not in the UI: " + group);
michael@0 624 continue;
michael@0 625 }
michael@0 626
michael@0 627 if (!groupmap.get(group).has(label)) {
michael@0 628 ok(false, "Saw a source label not in the UI: " + label);
michael@0 629 continue;
michael@0 630 }
michael@0 631
michael@0 632 groupmap.get(group).get(label).url = source.url.split(" -> ").pop();
michael@0 633 }
michael@0 634
michael@0 635 return groups;
michael@0 636 }),
michael@0 637
michael@0 638 _onMessage: function(event) {
michael@0 639 let json = JSON.parse(event.data);
michael@0 640 switch (json.name) {
michael@0 641 case "toolbox-title":
michael@0 642 this.title = json.data.value;
michael@0 643 break;
michael@0 644 }
michael@0 645 }
michael@0 646 }
michael@0 647
michael@0 648 function initChromeDebugger(aOnClose) {
michael@0 649 info("Initializing a chrome debugger process.");
michael@0 650
michael@0 651 let deferred = promise.defer();
michael@0 652
michael@0 653 // Wait for the toolbox process to start...
michael@0 654 BrowserToolboxProcess.init(aOnClose, (aEvent, aProcess) => {
michael@0 655 info("Browser toolbox process started successfully.");
michael@0 656
michael@0 657 prepareDebugger(aProcess);
michael@0 658 deferred.resolve(aProcess);
michael@0 659 });
michael@0 660
michael@0 661 return deferred.promise;
michael@0 662 }
michael@0 663
michael@0 664 function prepareDebugger(aDebugger) {
michael@0 665 if ("target" in aDebugger) {
michael@0 666 let view = aDebugger.panelWin.DebuggerView;
michael@0 667 view.Variables.lazyEmpty = false;
michael@0 668 view.Variables.lazySearch = false;
michael@0 669 view.FilteredSources._autoSelectFirstItem = true;
michael@0 670 view.FilteredFunctions._autoSelectFirstItem = true;
michael@0 671 } else {
michael@0 672 // Nothing to do here yet.
michael@0 673 }
michael@0 674 }
michael@0 675
michael@0 676 function teardown(aPanel, aFlags = {}) {
michael@0 677 info("Destroying the specified debugger.");
michael@0 678
michael@0 679 let toolbox = aPanel._toolbox;
michael@0 680 let tab = aPanel.target.tab;
michael@0 681 let debuggerRootActorDisconnected = once(window, "Debugger:Shutdown");
michael@0 682 let debuggerPanelDestroyed = once(aPanel, "destroyed");
michael@0 683 let devtoolsToolboxDestroyed = toolbox.destroy();
michael@0 684
michael@0 685 return promise.all([
michael@0 686 debuggerRootActorDisconnected,
michael@0 687 debuggerPanelDestroyed,
michael@0 688 devtoolsToolboxDestroyed
michael@0 689 ]).then(() => aFlags.noTabRemoval ? null : removeTab(tab));
michael@0 690 }
michael@0 691
michael@0 692 function closeDebuggerAndFinish(aPanel, aFlags = {}) {
michael@0 693 let thread = aPanel.panelWin.gThreadClient;
michael@0 694 if (thread.state == "paused" && !aFlags.whilePaused) {
michael@0 695 ok(false, "You should use 'resumeDebuggerThenCloseAndFinish' instead, " +
michael@0 696 "unless you're absolutely sure about what you're doing.");
michael@0 697 }
michael@0 698 return teardown(aPanel, aFlags).then(finish);
michael@0 699 }
michael@0 700
michael@0 701 function resumeDebuggerThenCloseAndFinish(aPanel, aFlags = {}) {
michael@0 702 let deferred = promise.defer();
michael@0 703 let thread = aPanel.panelWin.gThreadClient;
michael@0 704 thread.resume(() => closeDebuggerAndFinish(aPanel, aFlags).then(deferred.resolve));
michael@0 705 return deferred.promise;
michael@0 706 }
michael@0 707
michael@0 708 // Blackboxing helpers
michael@0 709
michael@0 710 function getBlackBoxButton(aPanel) {
michael@0 711 return aPanel.panelWin.document.getElementById("black-box");
michael@0 712 }
michael@0 713
michael@0 714 function toggleBlackBoxing(aPanel, aSource = null) {
michael@0 715 function clickBlackBoxButton() {
michael@0 716 getBlackBoxButton(aPanel).click();
michael@0 717 }
michael@0 718
michael@0 719 const blackBoxChanged = waitForThreadEvents(aPanel, "blackboxchange");
michael@0 720
michael@0 721 if (aSource) {
michael@0 722 aPanel.panelWin.DebuggerView.Sources.selectedValue = aSource;
michael@0 723 ensureSourceIs(aPanel, aSource, true).then(clickBlackBoxButton);
michael@0 724 } else {
michael@0 725 clickBlackBoxButton();
michael@0 726 }
michael@0 727
michael@0 728 return blackBoxChanged;
michael@0 729 }
michael@0 730
michael@0 731 function selectSourceAndGetBlackBoxButton(aPanel, aSource) {
michael@0 732 function returnBlackboxButton() {
michael@0 733 return getBlackBoxButton(aPanel);
michael@0 734 }
michael@0 735
michael@0 736 aPanel.panelWin.DebuggerView.Sources.selectedValue = aSource;
michael@0 737 return ensureSourceIs(aPanel, aSource, true).then(returnBlackboxButton);
michael@0 738 }
michael@0 739
michael@0 740 // Variables view inspection popup helpers
michael@0 741
michael@0 742 function openVarPopup(aPanel, aCoords, aWaitForFetchedProperties) {
michael@0 743 let events = aPanel.panelWin.EVENTS;
michael@0 744 let editor = aPanel.panelWin.DebuggerView.editor;
michael@0 745 let bubble = aPanel.panelWin.DebuggerView.VariableBubble;
michael@0 746 let tooltip = bubble._tooltip.panel;
michael@0 747
michael@0 748 let popupShown = once(tooltip, "popupshown");
michael@0 749 let fetchedProperties = aWaitForFetchedProperties
michael@0 750 ? waitForDebuggerEvents(aPanel, events.FETCHED_BUBBLE_PROPERTIES)
michael@0 751 : promise.resolve(null);
michael@0 752
michael@0 753 let { left, top } = editor.getCoordsFromPosition(aCoords);
michael@0 754 bubble._findIdentifier(left, top);
michael@0 755 return promise.all([popupShown, fetchedProperties]).then(waitForTick);
michael@0 756 }
michael@0 757
michael@0 758 // Simulates the mouse hovering a variable in the debugger
michael@0 759 // Takes in account the position of the cursor in the text, if the text is
michael@0 760 // selected and if a button is currently pushed (aButtonPushed > 0).
michael@0 761 // The function returns a promise which returns true if the popup opened or
michael@0 762 // false if it didn't
michael@0 763 function intendOpenVarPopup(aPanel, aPosition, aButtonPushed) {
michael@0 764 let bubble = aPanel.panelWin.DebuggerView.VariableBubble;
michael@0 765 let editor = aPanel.panelWin.DebuggerView.editor;
michael@0 766 let tooltip = bubble._tooltip;
michael@0 767
michael@0 768 let { left, top } = editor.getCoordsFromPosition(aPosition);
michael@0 769
michael@0 770 const eventDescriptor = {
michael@0 771 clientX: left,
michael@0 772 clientY: top,
michael@0 773 buttons: aButtonPushed
michael@0 774 };
michael@0 775
michael@0 776 bubble._onMouseMove(eventDescriptor);
michael@0 777
michael@0 778 const deferred = promise.defer();
michael@0 779 window.setTimeout(
michael@0 780 function() {
michael@0 781 if(tooltip.isEmpty()) {
michael@0 782 deferred.resolve(false);
michael@0 783 } else {
michael@0 784 deferred.resolve(true);
michael@0 785 }
michael@0 786 },
michael@0 787 tooltip.defaultShowDelay + 1000
michael@0 788 );
michael@0 789
michael@0 790 return deferred.promise;
michael@0 791 }
michael@0 792
michael@0 793 function hideVarPopup(aPanel) {
michael@0 794 let bubble = aPanel.panelWin.DebuggerView.VariableBubble;
michael@0 795 let tooltip = bubble._tooltip.panel;
michael@0 796
michael@0 797 let popupHiding = once(tooltip, "popuphiding");
michael@0 798 bubble.hideContents();
michael@0 799 return popupHiding.then(waitForTick);
michael@0 800 }
michael@0 801
michael@0 802 function hideVarPopupByScrollingEditor(aPanel) {
michael@0 803 let editor = aPanel.panelWin.DebuggerView.editor;
michael@0 804 let bubble = aPanel.panelWin.DebuggerView.VariableBubble;
michael@0 805 let tooltip = bubble._tooltip.panel;
michael@0 806
michael@0 807 let popupHiding = once(tooltip, "popuphiding");
michael@0 808 editor.setFirstVisibleLine(0);
michael@0 809 return popupHiding.then(waitForTick);
michael@0 810 }
michael@0 811
michael@0 812 function reopenVarPopup(...aArgs) {
michael@0 813 return hideVarPopup.apply(this, aArgs).then(() => openVarPopup.apply(this, aArgs));
michael@0 814 }
michael@0 815
michael@0 816 // Tracing helpers
michael@0 817
michael@0 818 function startTracing(aPanel) {
michael@0 819 const deferred = promise.defer();
michael@0 820 aPanel.panelWin.DebuggerController.Tracer.startTracing(aResponse => {
michael@0 821 if (aResponse.error) {
michael@0 822 deferred.reject(aResponse);
michael@0 823 } else {
michael@0 824 deferred.resolve(aResponse);
michael@0 825 }
michael@0 826 });
michael@0 827 return deferred.promise;
michael@0 828 }
michael@0 829
michael@0 830 function stopTracing(aPanel) {
michael@0 831 const deferred = promise.defer();
michael@0 832 aPanel.panelWin.DebuggerController.Tracer.stopTracing(aResponse => {
michael@0 833 if (aResponse.error) {
michael@0 834 deferred.reject(aResponse);
michael@0 835 } else {
michael@0 836 deferred.resolve(aResponse);
michael@0 837 }
michael@0 838 });
michael@0 839 return deferred.promise;
michael@0 840 }
michael@0 841
michael@0 842 function filterTraces(aPanel, f) {
michael@0 843 const traces = aPanel.panelWin.document
michael@0 844 .getElementById("tracer-traces")
michael@0 845 .querySelector("scrollbox")
michael@0 846 .children;
michael@0 847 return Array.filter(traces, f);
michael@0 848 }
michael@0 849 function attachAddonActorForUrl(aClient, aUrl) {
michael@0 850 let deferred = promise.defer();
michael@0 851
michael@0 852 getAddonActorForUrl(aClient, aUrl).then(aGrip => {
michael@0 853 aClient.attachAddon(aGrip.actor, aResponse => {
michael@0 854 deferred.resolve([aGrip, aResponse]);
michael@0 855 });
michael@0 856 });
michael@0 857
michael@0 858 return deferred.promise;
michael@0 859 }
michael@0 860
michael@0 861 function rdpInvoke(aClient, aMethod, ...args) {
michael@0 862 return promiseInvoke(aClient, aMethod, ...args)
michael@0 863 .then(({error, message }) => {
michael@0 864 if (error) {
michael@0 865 throw new Error(error + ": " + message);
michael@0 866 }
michael@0 867 });
michael@0 868 }
michael@0 869
michael@0 870 function doResume(aPanel) {
michael@0 871 const threadClient = aPanel.panelWin.gThreadClient;
michael@0 872 return rdpInvoke(threadClient, threadClient.resume);
michael@0 873 }
michael@0 874
michael@0 875 function doInterrupt(aPanel) {
michael@0 876 const threadClient = aPanel.panelWin.gThreadClient;
michael@0 877 return rdpInvoke(threadClient, threadClient.interrupt);
michael@0 878 }
michael@0 879

mercurial