1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/browser/devtools/debugger/test/head.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,879 @@ 1.4 +/* Any copyright is dedicated to the Public Domain. 1.5 + http://creativecommons.org/publicdomain/zero/1.0/ */ 1.6 +"use strict"; 1.7 + 1.8 +const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components; 1.9 + 1.10 +let { Services } = Cu.import("resource://gre/modules/Services.jsm", {}); 1.11 + 1.12 +// Disable logging for faster test runs. Set this pref to true if you want to 1.13 +// debug a test in your try runs. Both the debugger server and frontend will 1.14 +// be affected by this pref. 1.15 +let gEnableLogging = Services.prefs.getBoolPref("devtools.debugger.log"); 1.16 +Services.prefs.setBoolPref("devtools.debugger.log", false); 1.17 + 1.18 +let { Task } = Cu.import("resource://gre/modules/Task.jsm", {}); 1.19 +let { Promise: promise } = Cu.import("resource://gre/modules/Promise.jsm", {}); 1.20 +let { gDevTools } = Cu.import("resource:///modules/devtools/gDevTools.jsm", {}); 1.21 +let { devtools } = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}); 1.22 +let { require } = devtools; 1.23 +let { DevToolsUtils } = Cu.import("resource://gre/modules/devtools/DevToolsUtils.jsm", {}); 1.24 +let { BrowserToolboxProcess } = Cu.import("resource:///modules/devtools/ToolboxProcess.jsm", {}); 1.25 +let { DebuggerServer } = Cu.import("resource://gre/modules/devtools/dbg-server.jsm", {}); 1.26 +let { DebuggerClient } = Cu.import("resource://gre/modules/devtools/dbg-client.jsm", {}); 1.27 +let { AddonManager } = Cu.import("resource://gre/modules/AddonManager.jsm", {}); 1.28 +const { promiseInvoke } = require("devtools/async-utils"); 1.29 +let TargetFactory = devtools.TargetFactory; 1.30 +let Toolbox = devtools.Toolbox; 1.31 + 1.32 +const EXAMPLE_URL = "http://example.com/browser/browser/devtools/debugger/test/"; 1.33 + 1.34 +gDevTools.testing = true; 1.35 +SimpleTest.registerCleanupFunction(() => { 1.36 + gDevTools.testing = false; 1.37 +}); 1.38 + 1.39 +// All tests are asynchronous. 1.40 +waitForExplicitFinish(); 1.41 + 1.42 +registerCleanupFunction(function() { 1.43 + info("finish() was called, cleaning up..."); 1.44 + Services.prefs.setBoolPref("devtools.debugger.log", gEnableLogging); 1.45 + 1.46 + // Properly shut down the server to avoid memory leaks. 1.47 + DebuggerServer.destroy(); 1.48 + 1.49 + // Debugger tests use a lot of memory, so force a GC to help fragmentation. 1.50 + info("Forcing GC after debugger test."); 1.51 + Cu.forceGC(); 1.52 +}); 1.53 + 1.54 +// Import the GCLI test helper 1.55 +let testDir = gTestPath.substr(0, gTestPath.lastIndexOf("/")); 1.56 +Services.scriptloader.loadSubScript(testDir + "../../../commandline/test/helpers.js", this); 1.57 + 1.58 +// Redeclare dbg_assert with a fatal behavior. 1.59 +function dbg_assert(cond, e) { 1.60 + if (!cond) { 1.61 + throw e; 1.62 + } 1.63 +} 1.64 + 1.65 +function addWindow(aUrl) { 1.66 + info("Adding window: " + aUrl); 1.67 + return promise.resolve(getDOMWindow(window.open(aUrl))); 1.68 +} 1.69 + 1.70 +function getDOMWindow(aReference) { 1.71 + return aReference 1.72 + .QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIWebNavigation) 1.73 + .QueryInterface(Ci.nsIDocShellTreeItem).rootTreeItem 1.74 + .QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindow); 1.75 +} 1.76 + 1.77 +function addTab(aUrl, aWindow) { 1.78 + info("Adding tab: " + aUrl); 1.79 + 1.80 + let deferred = promise.defer(); 1.81 + let targetWindow = aWindow || window; 1.82 + let targetBrowser = targetWindow.gBrowser; 1.83 + 1.84 + targetWindow.focus(); 1.85 + let tab = targetBrowser.selectedTab = targetBrowser.addTab(aUrl); 1.86 + let linkedBrowser = tab.linkedBrowser; 1.87 + 1.88 + linkedBrowser.addEventListener("load", function onLoad() { 1.89 + linkedBrowser.removeEventListener("load", onLoad, true); 1.90 + info("Tab added and finished loading: " + aUrl); 1.91 + deferred.resolve(tab); 1.92 + }, true); 1.93 + 1.94 + return deferred.promise; 1.95 +} 1.96 + 1.97 +function removeTab(aTab, aWindow) { 1.98 + info("Removing tab."); 1.99 + 1.100 + let deferred = promise.defer(); 1.101 + let targetWindow = aWindow || window; 1.102 + let targetBrowser = targetWindow.gBrowser; 1.103 + let tabContainer = targetBrowser.tabContainer; 1.104 + 1.105 + tabContainer.addEventListener("TabClose", function onClose(aEvent) { 1.106 + tabContainer.removeEventListener("TabClose", onClose, false); 1.107 + info("Tab removed and finished closing."); 1.108 + deferred.resolve(); 1.109 + }, false); 1.110 + 1.111 + targetBrowser.removeTab(aTab); 1.112 + return deferred.promise; 1.113 +} 1.114 + 1.115 +function addAddon(aUrl) { 1.116 + info("Installing addon: " + aUrl); 1.117 + 1.118 + let deferred = promise.defer(); 1.119 + 1.120 + AddonManager.getInstallForURL(aUrl, aInstaller => { 1.121 + aInstaller.install(); 1.122 + let listener = { 1.123 + onInstallEnded: function(aAddon, aAddonInstall) { 1.124 + aInstaller.removeListener(listener); 1.125 + 1.126 + // Wait for add-on's startup scripts to execute. See bug 997408 1.127 + executeSoon(function() { 1.128 + deferred.resolve(aAddonInstall); 1.129 + }); 1.130 + } 1.131 + }; 1.132 + aInstaller.addListener(listener); 1.133 + }, "application/x-xpinstall"); 1.134 + 1.135 + return deferred.promise; 1.136 +} 1.137 + 1.138 +function removeAddon(aAddon) { 1.139 + info("Removing addon."); 1.140 + 1.141 + let deferred = promise.defer(); 1.142 + 1.143 + let listener = { 1.144 + onUninstalled: function(aUninstalledAddon) { 1.145 + if (aUninstalledAddon != aAddon) { 1.146 + return; 1.147 + } 1.148 + AddonManager.removeAddonListener(listener); 1.149 + deferred.resolve(); 1.150 + } 1.151 + }; 1.152 + AddonManager.addAddonListener(listener); 1.153 + aAddon.uninstall(); 1.154 + 1.155 + return deferred.promise; 1.156 +} 1.157 + 1.158 +function getTabActorForUrl(aClient, aUrl) { 1.159 + let deferred = promise.defer(); 1.160 + 1.161 + aClient.listTabs(aResponse => { 1.162 + let tabActor = aResponse.tabs.filter(aGrip => aGrip.url == aUrl).pop(); 1.163 + deferred.resolve(tabActor); 1.164 + }); 1.165 + 1.166 + return deferred.promise; 1.167 +} 1.168 + 1.169 +function getAddonActorForUrl(aClient, aUrl) { 1.170 + info("Get addon actor for URL: " + aUrl); 1.171 + let deferred = promise.defer(); 1.172 + 1.173 + aClient.listAddons(aResponse => { 1.174 + let addonActor = aResponse.addons.filter(aGrip => aGrip.url == aUrl).pop(); 1.175 + info("got addon actor for URL: " + addonActor.actor); 1.176 + deferred.resolve(addonActor); 1.177 + }); 1.178 + 1.179 + return deferred.promise; 1.180 +} 1.181 + 1.182 +function attachTabActorForUrl(aClient, aUrl) { 1.183 + let deferred = promise.defer(); 1.184 + 1.185 + getTabActorForUrl(aClient, aUrl).then(aGrip => { 1.186 + aClient.attachTab(aGrip.actor, aResponse => { 1.187 + deferred.resolve([aGrip, aResponse]); 1.188 + }); 1.189 + }); 1.190 + 1.191 + return deferred.promise; 1.192 +} 1.193 + 1.194 +function attachThreadActorForUrl(aClient, aUrl) { 1.195 + let deferred = promise.defer(); 1.196 + 1.197 + attachTabActorForUrl(aClient, aUrl).then(([aGrip, aResponse]) => { 1.198 + aClient.attachThread(aResponse.threadActor, (aResponse, aThreadClient) => { 1.199 + aThreadClient.resume(aResponse => { 1.200 + deferred.resolve(aThreadClient); 1.201 + }); 1.202 + }); 1.203 + }); 1.204 + 1.205 + return deferred.promise; 1.206 +} 1.207 + 1.208 +function once(aTarget, aEventName, aUseCapture = false) { 1.209 + info("Waiting for event: '" + aEventName + "' on " + aTarget + "."); 1.210 + 1.211 + let deferred = promise.defer(); 1.212 + 1.213 + for (let [add, remove] of [ 1.214 + ["addEventListener", "removeEventListener"], 1.215 + ["addListener", "removeListener"], 1.216 + ["on", "off"] 1.217 + ]) { 1.218 + if ((add in aTarget) && (remove in aTarget)) { 1.219 + aTarget[add](aEventName, function onEvent(...aArgs) { 1.220 + aTarget[remove](aEventName, onEvent, aUseCapture); 1.221 + deferred.resolve.apply(deferred, aArgs); 1.222 + }, aUseCapture); 1.223 + break; 1.224 + } 1.225 + } 1.226 + 1.227 + return deferred.promise; 1.228 +} 1.229 + 1.230 +function waitForTick() { 1.231 + let deferred = promise.defer(); 1.232 + executeSoon(deferred.resolve); 1.233 + return deferred.promise; 1.234 +} 1.235 + 1.236 +function waitForTime(aDelay) { 1.237 + let deferred = promise.defer(); 1.238 + setTimeout(deferred.resolve, aDelay); 1.239 + return deferred.promise; 1.240 +} 1.241 + 1.242 +function waitForSourceShown(aPanel, aUrl) { 1.243 + return waitForDebuggerEvents(aPanel, aPanel.panelWin.EVENTS.SOURCE_SHOWN).then(aSource => { 1.244 + let sourceUrl = aSource.url; 1.245 + info("Source shown: " + sourceUrl); 1.246 + 1.247 + if (!sourceUrl.contains(aUrl)) { 1.248 + return waitForSourceShown(aPanel, aUrl); 1.249 + } else { 1.250 + ok(true, "The correct source has been shown."); 1.251 + } 1.252 + }); 1.253 +} 1.254 + 1.255 +function waitForEditorLocationSet(aPanel) { 1.256 + return waitForDebuggerEvents(aPanel, aPanel.panelWin.EVENTS.EDITOR_LOCATION_SET); 1.257 +} 1.258 + 1.259 +function ensureSourceIs(aPanel, aUrl, aWaitFlag = false) { 1.260 + if (aPanel.panelWin.DebuggerView.Sources.selectedValue.contains(aUrl)) { 1.261 + ok(true, "Expected source is shown: " + aUrl); 1.262 + return promise.resolve(null); 1.263 + } 1.264 + if (aWaitFlag) { 1.265 + return waitForSourceShown(aPanel, aUrl); 1.266 + } 1.267 + ok(false, "Expected source was not already shown: " + aUrl); 1.268 + return promise.reject(null); 1.269 +} 1.270 + 1.271 +function waitForCaretUpdated(aPanel, aLine, aCol = 1) { 1.272 + return waitForEditorEvents(aPanel, "cursorActivity").then(() => { 1.273 + let cursor = aPanel.panelWin.DebuggerView.editor.getCursor(); 1.274 + info("Caret updated: " + (cursor.line + 1) + ", " + (cursor.ch + 1)); 1.275 + 1.276 + if (!isCaretPos(aPanel, aLine, aCol)) { 1.277 + return waitForCaretUpdated(aPanel, aLine, aCol); 1.278 + } else { 1.279 + ok(true, "The correct caret position has been set."); 1.280 + } 1.281 + }); 1.282 +} 1.283 + 1.284 +function ensureCaretAt(aPanel, aLine, aCol = 1, aWaitFlag = false) { 1.285 + if (isCaretPos(aPanel, aLine, aCol)) { 1.286 + ok(true, "Expected caret position is set: " + aLine + "," + aCol); 1.287 + return promise.resolve(null); 1.288 + } 1.289 + if (aWaitFlag) { 1.290 + return waitForCaretUpdated(aPanel, aLine, aCol); 1.291 + } 1.292 + ok(false, "Expected caret position was not already set: " + aLine + "," + aCol); 1.293 + return promise.reject(null); 1.294 +} 1.295 + 1.296 +function isCaretPos(aPanel, aLine, aCol = 1) { 1.297 + let editor = aPanel.panelWin.DebuggerView.editor; 1.298 + let cursor = editor.getCursor(); 1.299 + 1.300 + // Source editor starts counting line and column numbers from 0. 1.301 + info("Current editor caret position: " + (cursor.line + 1) + ", " + (cursor.ch + 1)); 1.302 + return cursor.line == (aLine - 1) && cursor.ch == (aCol - 1); 1.303 +} 1.304 + 1.305 +function isEditorSel(aPanel, [start, end]) { 1.306 + let editor = aPanel.panelWin.DebuggerView.editor; 1.307 + let range = { 1.308 + start: editor.getOffset(editor.getCursor("start")), 1.309 + end: editor.getOffset(editor.getCursor()) 1.310 + }; 1.311 + 1.312 + // Source editor starts counting line and column numbers from 0. 1.313 + info("Current editor selection: " + (range.start + 1) + ", " + (range.end + 1)); 1.314 + return range.start == (start - 1) && range.end == (end - 1); 1.315 +} 1.316 + 1.317 +function waitForSourceAndCaret(aPanel, aUrl, aLine, aCol) { 1.318 + return promise.all([ 1.319 + waitForSourceShown(aPanel, aUrl), 1.320 + waitForCaretUpdated(aPanel, aLine, aCol) 1.321 + ]); 1.322 +} 1.323 + 1.324 +function waitForCaretAndScopes(aPanel, aLine, aCol) { 1.325 + return promise.all([ 1.326 + waitForCaretUpdated(aPanel, aLine, aCol), 1.327 + waitForDebuggerEvents(aPanel, aPanel.panelWin.EVENTS.FETCHED_SCOPES) 1.328 + ]); 1.329 +} 1.330 + 1.331 +function waitForSourceAndCaretAndScopes(aPanel, aUrl, aLine, aCol) { 1.332 + return promise.all([ 1.333 + waitForSourceAndCaret(aPanel, aUrl, aLine, aCol), 1.334 + waitForDebuggerEvents(aPanel, aPanel.panelWin.EVENTS.FETCHED_SCOPES) 1.335 + ]); 1.336 +} 1.337 + 1.338 +function waitForDebuggerEvents(aPanel, aEventName, aEventRepeat = 1) { 1.339 + info("Waiting for debugger event: '" + aEventName + "' to fire: " + aEventRepeat + " time(s)."); 1.340 + 1.341 + let deferred = promise.defer(); 1.342 + let panelWin = aPanel.panelWin; 1.343 + let count = 0; 1.344 + 1.345 + panelWin.on(aEventName, function onEvent(aEventName, ...aArgs) { 1.346 + info("Debugger event '" + aEventName + "' fired: " + (++count) + " time(s)."); 1.347 + 1.348 + if (count == aEventRepeat) { 1.349 + ok(true, "Enough '" + aEventName + "' panel events have been fired."); 1.350 + panelWin.off(aEventName, onEvent); 1.351 + deferred.resolve.apply(deferred, aArgs); 1.352 + } 1.353 + }); 1.354 + 1.355 + return deferred.promise; 1.356 +} 1.357 + 1.358 +function waitForEditorEvents(aPanel, aEventName, aEventRepeat = 1) { 1.359 + info("Waiting for editor event: '" + aEventName + "' to fire: " + aEventRepeat + " time(s)."); 1.360 + 1.361 + let deferred = promise.defer(); 1.362 + let editor = aPanel.panelWin.DebuggerView.editor; 1.363 + let count = 0; 1.364 + 1.365 + editor.on(aEventName, function onEvent(...aArgs) { 1.366 + info("Editor event '" + aEventName + "' fired: " + (++count) + " time(s)."); 1.367 + 1.368 + if (count == aEventRepeat) { 1.369 + ok(true, "Enough '" + aEventName + "' editor events have been fired."); 1.370 + editor.off(aEventName, onEvent); 1.371 + deferred.resolve.apply(deferred, aArgs); 1.372 + } 1.373 + }); 1.374 + 1.375 + return deferred.promise; 1.376 +} 1.377 + 1.378 +function waitForThreadEvents(aPanel, aEventName, aEventRepeat = 1) { 1.379 + info("Waiting for thread event: '" + aEventName + "' to fire: " + aEventRepeat + " time(s)."); 1.380 + 1.381 + let deferred = promise.defer(); 1.382 + let thread = aPanel.panelWin.gThreadClient; 1.383 + let count = 0; 1.384 + 1.385 + thread.addListener(aEventName, function onEvent(aEventName, ...aArgs) { 1.386 + info("Thread event '" + aEventName + "' fired: " + (++count) + " time(s)."); 1.387 + 1.388 + if (count == aEventRepeat) { 1.389 + ok(true, "Enough '" + aEventName + "' thread events have been fired."); 1.390 + thread.removeListener(aEventName, onEvent); 1.391 + deferred.resolve.apply(deferred, aArgs); 1.392 + } 1.393 + }); 1.394 + 1.395 + return deferred.promise; 1.396 +} 1.397 + 1.398 +function waitForClientEvents(aPanel, aEventName, aEventRepeat = 1) { 1.399 + info("Waiting for client event: '" + aEventName + "' to fire: " + aEventRepeat + " time(s)."); 1.400 + 1.401 + let deferred = promise.defer(); 1.402 + let client = aPanel.panelWin.gClient; 1.403 + let count = 0; 1.404 + 1.405 + client.addListener(aEventName, function onEvent(aEventName, ...aArgs) { 1.406 + info("Thread event '" + aEventName + "' fired: " + (++count) + " time(s)."); 1.407 + 1.408 + if (count == aEventRepeat) { 1.409 + ok(true, "Enough '" + aEventName + "' thread events have been fired."); 1.410 + client.removeListener(aEventName, onEvent); 1.411 + deferred.resolve.apply(deferred, aArgs); 1.412 + } 1.413 + }); 1.414 + 1.415 + return deferred.promise; 1.416 +} 1.417 + 1.418 +function ensureThreadClientState(aPanel, aState) { 1.419 + let thread = aPanel.panelWin.gThreadClient; 1.420 + let state = thread.state; 1.421 + 1.422 + info("Thread is: '" + state + "'."); 1.423 + 1.424 + if (state == aState) { 1.425 + return promise.resolve(null); 1.426 + } else { 1.427 + return waitForThreadEvents(aPanel, aState); 1.428 + } 1.429 +} 1.430 + 1.431 +function navigateActiveTabTo(aPanel, aUrl, aWaitForEventName, aEventRepeat) { 1.432 + let finished = waitForDebuggerEvents(aPanel, aWaitForEventName, aEventRepeat); 1.433 + let activeTab = aPanel.panelWin.DebuggerController._target.activeTab; 1.434 + aUrl ? activeTab.navigateTo(aUrl) : activeTab.reload(); 1.435 + return finished; 1.436 +} 1.437 + 1.438 +function navigateActiveTabInHistory(aPanel, aDirection, aWaitForEventName, aEventRepeat) { 1.439 + let finished = waitForDebuggerEvents(aPanel, aWaitForEventName, aEventRepeat); 1.440 + content.history[aDirection](); 1.441 + return finished; 1.442 +} 1.443 + 1.444 +function reloadActiveTab(aPanel, aWaitForEventName, aEventRepeat) { 1.445 + return navigateActiveTabTo(aPanel, null, aWaitForEventName, aEventRepeat); 1.446 +} 1.447 + 1.448 +function clearText(aElement) { 1.449 + info("Clearing text..."); 1.450 + aElement.focus(); 1.451 + aElement.value = ""; 1.452 +} 1.453 + 1.454 +function setText(aElement, aText) { 1.455 + clearText(aElement); 1.456 + info("Setting text: " + aText); 1.457 + aElement.value = aText; 1.458 +} 1.459 + 1.460 +function typeText(aElement, aText) { 1.461 + info("Typing text: " + aText); 1.462 + aElement.focus(); 1.463 + EventUtils.sendString(aText, aElement.ownerDocument.defaultView); 1.464 +} 1.465 + 1.466 +function backspaceText(aElement, aTimes) { 1.467 + info("Pressing backspace " + aTimes + " times."); 1.468 + for (let i = 0; i < aTimes; i++) { 1.469 + aElement.focus(); 1.470 + EventUtils.sendKey("BACK_SPACE", aElement.ownerDocument.defaultView); 1.471 + } 1.472 +} 1.473 + 1.474 +function getTab(aTarget, aWindow) { 1.475 + if (aTarget instanceof XULElement) { 1.476 + return promise.resolve(aTarget); 1.477 + } else { 1.478 + return addTab(aTarget, aWindow); 1.479 + } 1.480 +} 1.481 + 1.482 +function getSources(aClient) { 1.483 + let deferred = promise.defer(); 1.484 + 1.485 + aClient.getSources(({sources}) => deferred.resolve(sources)); 1.486 + 1.487 + return deferred.promise; 1.488 +} 1.489 + 1.490 +function initDebugger(aTarget, aWindow) { 1.491 + info("Initializing a debugger panel."); 1.492 + 1.493 + return getTab(aTarget, aWindow).then(aTab => { 1.494 + info("Debugee tab added successfully: " + aTarget); 1.495 + 1.496 + let deferred = promise.defer(); 1.497 + let debuggee = aTab.linkedBrowser.contentWindow.wrappedJSObject; 1.498 + let target = TargetFactory.forTab(aTab); 1.499 + 1.500 + gDevTools.showToolbox(target, "jsdebugger").then(aToolbox => { 1.501 + info("Debugger panel shown successfully."); 1.502 + 1.503 + let debuggerPanel = aToolbox.getCurrentPanel(); 1.504 + let panelWin = debuggerPanel.panelWin; 1.505 + 1.506 + // Wait for the initial resume... 1.507 + panelWin.gClient.addOneTimeListener("resumed", () => { 1.508 + info("Debugger client resumed successfully."); 1.509 + 1.510 + prepareDebugger(debuggerPanel); 1.511 + deferred.resolve([aTab, debuggee, debuggerPanel, aWindow]); 1.512 + }); 1.513 + }); 1.514 + 1.515 + return deferred.promise; 1.516 + }); 1.517 +} 1.518 + 1.519 +// Creates an add-on debugger for a given add-on. The returned AddonDebugger 1.520 +// object must be destroyed before finishing the test 1.521 +function initAddonDebugger(aUrl) { 1.522 + let addonDebugger = new AddonDebugger(); 1.523 + return addonDebugger.init(aUrl).then(() => addonDebugger); 1.524 +} 1.525 + 1.526 +function AddonDebugger() { 1.527 + this._onMessage = this._onMessage.bind(this); 1.528 +} 1.529 + 1.530 +AddonDebugger.prototype = { 1.531 + init: Task.async(function*(aUrl) { 1.532 + info("Initializing an addon debugger panel."); 1.533 + 1.534 + if (!DebuggerServer.initialized) { 1.535 + DebuggerServer.init(() => true); 1.536 + DebuggerServer.addBrowserActors(); 1.537 + } 1.538 + 1.539 + this.frame = document.createElement("iframe"); 1.540 + this.frame.setAttribute("height", 400); 1.541 + document.documentElement.appendChild(this.frame); 1.542 + window.addEventListener("message", this._onMessage); 1.543 + 1.544 + let transport = DebuggerServer.connectPipe(); 1.545 + this.client = new DebuggerClient(transport); 1.546 + 1.547 + let connected = promise.defer(); 1.548 + this.client.connect(connected.resolve); 1.549 + yield connected.promise; 1.550 + 1.551 + let addonActor = yield getAddonActorForUrl(this.client, aUrl); 1.552 + 1.553 + let targetOptions = { 1.554 + form: { addonActor: addonActor.actor, title: addonActor.name }, 1.555 + client: this.client, 1.556 + chrome: true 1.557 + }; 1.558 + 1.559 + let toolboxOptions = { 1.560 + customIframe: this.frame 1.561 + }; 1.562 + 1.563 + let target = devtools.TargetFactory.forTab(targetOptions); 1.564 + let toolbox = yield gDevTools.showToolbox(target, "jsdebugger", devtools.Toolbox.HostType.CUSTOM, toolboxOptions); 1.565 + 1.566 + info("Addon debugger panel shown successfully."); 1.567 + 1.568 + this.debuggerPanel = toolbox.getCurrentPanel(); 1.569 + 1.570 + // Wait for the initial resume... 1.571 + yield waitForClientEvents(this.debuggerPanel, "resumed"); 1.572 + yield prepareDebugger(this.debuggerPanel); 1.573 + }), 1.574 + 1.575 + destroy: Task.async(function*() { 1.576 + let deferred = promise.defer(); 1.577 + this.client.close(deferred.resolve); 1.578 + yield deferred.promise; 1.579 + yield this.debuggerPanel._toolbox.destroy(); 1.580 + this.frame.remove(); 1.581 + window.removeEventListener("message", this._onMessage); 1.582 + }), 1.583 + 1.584 + /** 1.585 + * Returns a list of the groups and sources in the UI. The returned array 1.586 + * contains objects for each group with properties name and sources. The 1.587 + * sources property contains an array with objects for each source for that 1.588 + * group with properties label and url. 1.589 + */ 1.590 + getSourceGroups: Task.async(function*() { 1.591 + let debuggerWin = this.debuggerPanel.panelWin; 1.592 + let sources = yield getSources(debuggerWin.gThreadClient); 1.593 + ok(sources.length, "retrieved sources"); 1.594 + 1.595 + // groups will be the return value, groupmap and the maps we put in it will 1.596 + // be used as quick lookups to add the url information in below 1.597 + let groups = []; 1.598 + let groupmap = new Map(); 1.599 + 1.600 + let uigroups = this.debuggerPanel.panelWin.document.querySelectorAll(".side-menu-widget-group"); 1.601 + for (let g of uigroups) { 1.602 + let name = g.querySelector(".side-menu-widget-group-title .name").value; 1.603 + let group = { 1.604 + name: name, 1.605 + sources: [] 1.606 + }; 1.607 + groups.push(group); 1.608 + let labelmap = new Map(); 1.609 + groupmap.set(name, labelmap); 1.610 + 1.611 + for (let l of g.querySelectorAll(".dbg-source-item")) { 1.612 + let source = { 1.613 + label: l.value, 1.614 + url: null 1.615 + }; 1.616 + 1.617 + labelmap.set(l.value, source); 1.618 + group.sources.push(source); 1.619 + } 1.620 + } 1.621 + 1.622 + for (let source of sources) { 1.623 + let { label, group } = debuggerWin.DebuggerView.Sources.getItemByValue(source.url).attachment; 1.624 + 1.625 + if (!groupmap.has(group)) { 1.626 + ok(false, "Saw a source group not in the UI: " + group); 1.627 + continue; 1.628 + } 1.629 + 1.630 + if (!groupmap.get(group).has(label)) { 1.631 + ok(false, "Saw a source label not in the UI: " + label); 1.632 + continue; 1.633 + } 1.634 + 1.635 + groupmap.get(group).get(label).url = source.url.split(" -> ").pop(); 1.636 + } 1.637 + 1.638 + return groups; 1.639 + }), 1.640 + 1.641 + _onMessage: function(event) { 1.642 + let json = JSON.parse(event.data); 1.643 + switch (json.name) { 1.644 + case "toolbox-title": 1.645 + this.title = json.data.value; 1.646 + break; 1.647 + } 1.648 + } 1.649 +} 1.650 + 1.651 +function initChromeDebugger(aOnClose) { 1.652 + info("Initializing a chrome debugger process."); 1.653 + 1.654 + let deferred = promise.defer(); 1.655 + 1.656 + // Wait for the toolbox process to start... 1.657 + BrowserToolboxProcess.init(aOnClose, (aEvent, aProcess) => { 1.658 + info("Browser toolbox process started successfully."); 1.659 + 1.660 + prepareDebugger(aProcess); 1.661 + deferred.resolve(aProcess); 1.662 + }); 1.663 + 1.664 + return deferred.promise; 1.665 +} 1.666 + 1.667 +function prepareDebugger(aDebugger) { 1.668 + if ("target" in aDebugger) { 1.669 + let view = aDebugger.panelWin.DebuggerView; 1.670 + view.Variables.lazyEmpty = false; 1.671 + view.Variables.lazySearch = false; 1.672 + view.FilteredSources._autoSelectFirstItem = true; 1.673 + view.FilteredFunctions._autoSelectFirstItem = true; 1.674 + } else { 1.675 + // Nothing to do here yet. 1.676 + } 1.677 +} 1.678 + 1.679 +function teardown(aPanel, aFlags = {}) { 1.680 + info("Destroying the specified debugger."); 1.681 + 1.682 + let toolbox = aPanel._toolbox; 1.683 + let tab = aPanel.target.tab; 1.684 + let debuggerRootActorDisconnected = once(window, "Debugger:Shutdown"); 1.685 + let debuggerPanelDestroyed = once(aPanel, "destroyed"); 1.686 + let devtoolsToolboxDestroyed = toolbox.destroy(); 1.687 + 1.688 + return promise.all([ 1.689 + debuggerRootActorDisconnected, 1.690 + debuggerPanelDestroyed, 1.691 + devtoolsToolboxDestroyed 1.692 + ]).then(() => aFlags.noTabRemoval ? null : removeTab(tab)); 1.693 +} 1.694 + 1.695 +function closeDebuggerAndFinish(aPanel, aFlags = {}) { 1.696 + let thread = aPanel.panelWin.gThreadClient; 1.697 + if (thread.state == "paused" && !aFlags.whilePaused) { 1.698 + ok(false, "You should use 'resumeDebuggerThenCloseAndFinish' instead, " + 1.699 + "unless you're absolutely sure about what you're doing."); 1.700 + } 1.701 + return teardown(aPanel, aFlags).then(finish); 1.702 +} 1.703 + 1.704 +function resumeDebuggerThenCloseAndFinish(aPanel, aFlags = {}) { 1.705 + let deferred = promise.defer(); 1.706 + let thread = aPanel.panelWin.gThreadClient; 1.707 + thread.resume(() => closeDebuggerAndFinish(aPanel, aFlags).then(deferred.resolve)); 1.708 + return deferred.promise; 1.709 +} 1.710 + 1.711 +// Blackboxing helpers 1.712 + 1.713 +function getBlackBoxButton(aPanel) { 1.714 + return aPanel.panelWin.document.getElementById("black-box"); 1.715 +} 1.716 + 1.717 +function toggleBlackBoxing(aPanel, aSource = null) { 1.718 + function clickBlackBoxButton() { 1.719 + getBlackBoxButton(aPanel).click(); 1.720 + } 1.721 + 1.722 + const blackBoxChanged = waitForThreadEvents(aPanel, "blackboxchange"); 1.723 + 1.724 + if (aSource) { 1.725 + aPanel.panelWin.DebuggerView.Sources.selectedValue = aSource; 1.726 + ensureSourceIs(aPanel, aSource, true).then(clickBlackBoxButton); 1.727 + } else { 1.728 + clickBlackBoxButton(); 1.729 + } 1.730 + 1.731 + return blackBoxChanged; 1.732 +} 1.733 + 1.734 +function selectSourceAndGetBlackBoxButton(aPanel, aSource) { 1.735 + function returnBlackboxButton() { 1.736 + return getBlackBoxButton(aPanel); 1.737 + } 1.738 + 1.739 + aPanel.panelWin.DebuggerView.Sources.selectedValue = aSource; 1.740 + return ensureSourceIs(aPanel, aSource, true).then(returnBlackboxButton); 1.741 +} 1.742 + 1.743 +// Variables view inspection popup helpers 1.744 + 1.745 +function openVarPopup(aPanel, aCoords, aWaitForFetchedProperties) { 1.746 + let events = aPanel.panelWin.EVENTS; 1.747 + let editor = aPanel.panelWin.DebuggerView.editor; 1.748 + let bubble = aPanel.panelWin.DebuggerView.VariableBubble; 1.749 + let tooltip = bubble._tooltip.panel; 1.750 + 1.751 + let popupShown = once(tooltip, "popupshown"); 1.752 + let fetchedProperties = aWaitForFetchedProperties 1.753 + ? waitForDebuggerEvents(aPanel, events.FETCHED_BUBBLE_PROPERTIES) 1.754 + : promise.resolve(null); 1.755 + 1.756 + let { left, top } = editor.getCoordsFromPosition(aCoords); 1.757 + bubble._findIdentifier(left, top); 1.758 + return promise.all([popupShown, fetchedProperties]).then(waitForTick); 1.759 +} 1.760 + 1.761 +// Simulates the mouse hovering a variable in the debugger 1.762 +// Takes in account the position of the cursor in the text, if the text is 1.763 +// selected and if a button is currently pushed (aButtonPushed > 0). 1.764 +// The function returns a promise which returns true if the popup opened or 1.765 +// false if it didn't 1.766 +function intendOpenVarPopup(aPanel, aPosition, aButtonPushed) { 1.767 + let bubble = aPanel.panelWin.DebuggerView.VariableBubble; 1.768 + let editor = aPanel.panelWin.DebuggerView.editor; 1.769 + let tooltip = bubble._tooltip; 1.770 + 1.771 + let { left, top } = editor.getCoordsFromPosition(aPosition); 1.772 + 1.773 + const eventDescriptor = { 1.774 + clientX: left, 1.775 + clientY: top, 1.776 + buttons: aButtonPushed 1.777 + }; 1.778 + 1.779 + bubble._onMouseMove(eventDescriptor); 1.780 + 1.781 + const deferred = promise.defer(); 1.782 + window.setTimeout( 1.783 + function() { 1.784 + if(tooltip.isEmpty()) { 1.785 + deferred.resolve(false); 1.786 + } else { 1.787 + deferred.resolve(true); 1.788 + } 1.789 + }, 1.790 + tooltip.defaultShowDelay + 1000 1.791 + ); 1.792 + 1.793 + return deferred.promise; 1.794 +} 1.795 + 1.796 +function hideVarPopup(aPanel) { 1.797 + let bubble = aPanel.panelWin.DebuggerView.VariableBubble; 1.798 + let tooltip = bubble._tooltip.panel; 1.799 + 1.800 + let popupHiding = once(tooltip, "popuphiding"); 1.801 + bubble.hideContents(); 1.802 + return popupHiding.then(waitForTick); 1.803 +} 1.804 + 1.805 +function hideVarPopupByScrollingEditor(aPanel) { 1.806 + let editor = aPanel.panelWin.DebuggerView.editor; 1.807 + let bubble = aPanel.panelWin.DebuggerView.VariableBubble; 1.808 + let tooltip = bubble._tooltip.panel; 1.809 + 1.810 + let popupHiding = once(tooltip, "popuphiding"); 1.811 + editor.setFirstVisibleLine(0); 1.812 + return popupHiding.then(waitForTick); 1.813 +} 1.814 + 1.815 +function reopenVarPopup(...aArgs) { 1.816 + return hideVarPopup.apply(this, aArgs).then(() => openVarPopup.apply(this, aArgs)); 1.817 +} 1.818 + 1.819 +// Tracing helpers 1.820 + 1.821 +function startTracing(aPanel) { 1.822 + const deferred = promise.defer(); 1.823 + aPanel.panelWin.DebuggerController.Tracer.startTracing(aResponse => { 1.824 + if (aResponse.error) { 1.825 + deferred.reject(aResponse); 1.826 + } else { 1.827 + deferred.resolve(aResponse); 1.828 + } 1.829 + }); 1.830 + return deferred.promise; 1.831 +} 1.832 + 1.833 +function stopTracing(aPanel) { 1.834 + const deferred = promise.defer(); 1.835 + aPanel.panelWin.DebuggerController.Tracer.stopTracing(aResponse => { 1.836 + if (aResponse.error) { 1.837 + deferred.reject(aResponse); 1.838 + } else { 1.839 + deferred.resolve(aResponse); 1.840 + } 1.841 + }); 1.842 + return deferred.promise; 1.843 +} 1.844 + 1.845 +function filterTraces(aPanel, f) { 1.846 + const traces = aPanel.panelWin.document 1.847 + .getElementById("tracer-traces") 1.848 + .querySelector("scrollbox") 1.849 + .children; 1.850 + return Array.filter(traces, f); 1.851 +} 1.852 +function attachAddonActorForUrl(aClient, aUrl) { 1.853 + let deferred = promise.defer(); 1.854 + 1.855 + getAddonActorForUrl(aClient, aUrl).then(aGrip => { 1.856 + aClient.attachAddon(aGrip.actor, aResponse => { 1.857 + deferred.resolve([aGrip, aResponse]); 1.858 + }); 1.859 + }); 1.860 + 1.861 + return deferred.promise; 1.862 +} 1.863 + 1.864 +function rdpInvoke(aClient, aMethod, ...args) { 1.865 + return promiseInvoke(aClient, aMethod, ...args) 1.866 + .then(({error, message }) => { 1.867 + if (error) { 1.868 + throw new Error(error + ": " + message); 1.869 + } 1.870 + }); 1.871 +} 1.872 + 1.873 +function doResume(aPanel) { 1.874 + const threadClient = aPanel.panelWin.gThreadClient; 1.875 + return rdpInvoke(threadClient, threadClient.resume); 1.876 +} 1.877 + 1.878 +function doInterrupt(aPanel) { 1.879 + const threadClient = aPanel.panelWin.gThreadClient; 1.880 + return rdpInvoke(threadClient, threadClient.interrupt); 1.881 +} 1.882 +