Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
michael@0 | 1 | /* -*- js2-basic-offset: 2; indent-tabs-mode: nil; -*- */ |
michael@0 | 2 | /* vim: set ft=javascript ts=2 et sw=2 tw=80: */ |
michael@0 | 3 | /* This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 4 | * License, v. 2.0. If a copy of the MPL was not distributed with this |
michael@0 | 5 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 6 | |
michael@0 | 7 | "use strict"; |
michael@0 | 8 | |
michael@0 | 9 | const {Cc, Ci, Cu} = require("chrome"); |
michael@0 | 10 | |
michael@0 | 11 | let WebConsoleUtils = require("devtools/toolkit/webconsole/utils").Utils; |
michael@0 | 12 | let Heritage = require("sdk/core/heritage"); |
michael@0 | 13 | |
michael@0 | 14 | loader.lazyGetter(this, "Telemetry", () => require("devtools/shared/telemetry")); |
michael@0 | 15 | loader.lazyGetter(this, "WebConsoleFrame", () => require("devtools/webconsole/webconsole").WebConsoleFrame); |
michael@0 | 16 | loader.lazyImporter(this, "promise", "resource://gre/modules/Promise.jsm", "Promise"); |
michael@0 | 17 | loader.lazyImporter(this, "gDevTools", "resource:///modules/devtools/gDevTools.jsm"); |
michael@0 | 18 | loader.lazyImporter(this, "devtools", "resource://gre/modules/devtools/Loader.jsm"); |
michael@0 | 19 | loader.lazyImporter(this, "Services", "resource://gre/modules/Services.jsm"); |
michael@0 | 20 | loader.lazyImporter(this, "DebuggerServer", "resource://gre/modules/devtools/dbg-server.jsm"); |
michael@0 | 21 | loader.lazyImporter(this, "DebuggerClient", "resource://gre/modules/devtools/dbg-client.jsm"); |
michael@0 | 22 | |
michael@0 | 23 | const STRINGS_URI = "chrome://browser/locale/devtools/webconsole.properties"; |
michael@0 | 24 | let l10n = new WebConsoleUtils.l10n(STRINGS_URI); |
michael@0 | 25 | |
michael@0 | 26 | const BROWSER_CONSOLE_WINDOW_FEATURES = "chrome,titlebar,toolbar,centerscreen,resizable,dialog=no"; |
michael@0 | 27 | |
michael@0 | 28 | // The preference prefix for all of the Browser Console filters. |
michael@0 | 29 | const BROWSER_CONSOLE_FILTER_PREFS_PREFIX = "devtools.browserconsole.filter."; |
michael@0 | 30 | |
michael@0 | 31 | let gHudId = 0; |
michael@0 | 32 | |
michael@0 | 33 | /////////////////////////////////////////////////////////////////////////// |
michael@0 | 34 | //// The HUD service |
michael@0 | 35 | |
michael@0 | 36 | function HUD_SERVICE() |
michael@0 | 37 | { |
michael@0 | 38 | this.consoles = new Map(); |
michael@0 | 39 | this.lastFinishedRequest = { callback: null }; |
michael@0 | 40 | } |
michael@0 | 41 | |
michael@0 | 42 | HUD_SERVICE.prototype = |
michael@0 | 43 | { |
michael@0 | 44 | _browserConsoleID: null, |
michael@0 | 45 | _browserConsoleDefer: null, |
michael@0 | 46 | |
michael@0 | 47 | /** |
michael@0 | 48 | * Keeps a reference for each Web Console / Browser Console that is created. |
michael@0 | 49 | * @type Map |
michael@0 | 50 | */ |
michael@0 | 51 | consoles: null, |
michael@0 | 52 | |
michael@0 | 53 | /** |
michael@0 | 54 | * Assign a function to this property to listen for every request that |
michael@0 | 55 | * completes. Used by unit tests. The callback takes one argument: the HTTP |
michael@0 | 56 | * activity object as received from the remote Web Console. |
michael@0 | 57 | * |
michael@0 | 58 | * @type object |
michael@0 | 59 | * Includes a property named |callback|. Assign the function to the |
michael@0 | 60 | * |callback| property of this object. |
michael@0 | 61 | */ |
michael@0 | 62 | lastFinishedRequest: null, |
michael@0 | 63 | |
michael@0 | 64 | /** |
michael@0 | 65 | * Firefox-specific current tab getter |
michael@0 | 66 | * |
michael@0 | 67 | * @returns nsIDOMWindow |
michael@0 | 68 | */ |
michael@0 | 69 | currentContext: function HS_currentContext() { |
michael@0 | 70 | return Services.wm.getMostRecentWindow("navigator:browser"); |
michael@0 | 71 | }, |
michael@0 | 72 | |
michael@0 | 73 | /** |
michael@0 | 74 | * Open a Web Console for the given target. |
michael@0 | 75 | * |
michael@0 | 76 | * @see devtools/framework/target.js for details about targets. |
michael@0 | 77 | * |
michael@0 | 78 | * @param object aTarget |
michael@0 | 79 | * The target that the web console will connect to. |
michael@0 | 80 | * @param nsIDOMWindow aIframeWindow |
michael@0 | 81 | * The window where the web console UI is already loaded. |
michael@0 | 82 | * @param nsIDOMWindow aChromeWindow |
michael@0 | 83 | * The window of the web console owner. |
michael@0 | 84 | * @return object |
michael@0 | 85 | * A promise object for the opening of the new WebConsole instance. |
michael@0 | 86 | */ |
michael@0 | 87 | openWebConsole: |
michael@0 | 88 | function HS_openWebConsole(aTarget, aIframeWindow, aChromeWindow) |
michael@0 | 89 | { |
michael@0 | 90 | let hud = new WebConsole(aTarget, aIframeWindow, aChromeWindow); |
michael@0 | 91 | this.consoles.set(hud.hudId, hud); |
michael@0 | 92 | return hud.init(); |
michael@0 | 93 | }, |
michael@0 | 94 | |
michael@0 | 95 | /** |
michael@0 | 96 | * Open a Browser Console for the given target. |
michael@0 | 97 | * |
michael@0 | 98 | * @see devtools/framework/target.js for details about targets. |
michael@0 | 99 | * |
michael@0 | 100 | * @param object aTarget |
michael@0 | 101 | * The target that the browser console will connect to. |
michael@0 | 102 | * @param nsIDOMWindow aIframeWindow |
michael@0 | 103 | * The window where the browser console UI is already loaded. |
michael@0 | 104 | * @param nsIDOMWindow aChromeWindow |
michael@0 | 105 | * The window of the browser console owner. |
michael@0 | 106 | * @return object |
michael@0 | 107 | * A promise object for the opening of the new BrowserConsole instance. |
michael@0 | 108 | */ |
michael@0 | 109 | openBrowserConsole: |
michael@0 | 110 | function HS_openBrowserConsole(aTarget, aIframeWindow, aChromeWindow) |
michael@0 | 111 | { |
michael@0 | 112 | let hud = new BrowserConsole(aTarget, aIframeWindow, aChromeWindow); |
michael@0 | 113 | this._browserConsoleID = hud.hudId; |
michael@0 | 114 | this.consoles.set(hud.hudId, hud); |
michael@0 | 115 | return hud.init(); |
michael@0 | 116 | }, |
michael@0 | 117 | |
michael@0 | 118 | /** |
michael@0 | 119 | * Returns the Web Console object associated to a content window. |
michael@0 | 120 | * |
michael@0 | 121 | * @param nsIDOMWindow aContentWindow |
michael@0 | 122 | * @returns object |
michael@0 | 123 | */ |
michael@0 | 124 | getHudByWindow: function HS_getHudByWindow(aContentWindow) |
michael@0 | 125 | { |
michael@0 | 126 | for (let [hudId, hud] of this.consoles) { |
michael@0 | 127 | let target = hud.target; |
michael@0 | 128 | if (target && target.tab && target.window === aContentWindow) { |
michael@0 | 129 | return hud; |
michael@0 | 130 | } |
michael@0 | 131 | } |
michael@0 | 132 | return null; |
michael@0 | 133 | }, |
michael@0 | 134 | |
michael@0 | 135 | /** |
michael@0 | 136 | * Returns the console instance for a given id. |
michael@0 | 137 | * |
michael@0 | 138 | * @param string aId |
michael@0 | 139 | * @returns Object |
michael@0 | 140 | */ |
michael@0 | 141 | getHudReferenceById: function HS_getHudReferenceById(aId) |
michael@0 | 142 | { |
michael@0 | 143 | return this.consoles.get(aId); |
michael@0 | 144 | }, |
michael@0 | 145 | |
michael@0 | 146 | /** |
michael@0 | 147 | * Find if there is a Web Console open for the current tab and return the |
michael@0 | 148 | * instance. |
michael@0 | 149 | * @return object|null |
michael@0 | 150 | * The WebConsole object or null if the active tab has no open Web |
michael@0 | 151 | * Console. |
michael@0 | 152 | */ |
michael@0 | 153 | getOpenWebConsole: function HS_getOpenWebConsole() |
michael@0 | 154 | { |
michael@0 | 155 | let tab = this.currentContext().gBrowser.selectedTab; |
michael@0 | 156 | if (!tab || !devtools.TargetFactory.isKnownTab(tab)) { |
michael@0 | 157 | return null; |
michael@0 | 158 | } |
michael@0 | 159 | let target = devtools.TargetFactory.forTab(tab); |
michael@0 | 160 | let toolbox = gDevTools.getToolbox(target); |
michael@0 | 161 | let panel = toolbox ? toolbox.getPanel("webconsole") : null; |
michael@0 | 162 | return panel ? panel.hud : null; |
michael@0 | 163 | }, |
michael@0 | 164 | |
michael@0 | 165 | /** |
michael@0 | 166 | * Toggle the Browser Console. |
michael@0 | 167 | */ |
michael@0 | 168 | toggleBrowserConsole: function HS_toggleBrowserConsole() |
michael@0 | 169 | { |
michael@0 | 170 | if (this._browserConsoleID) { |
michael@0 | 171 | let hud = this.getHudReferenceById(this._browserConsoleID); |
michael@0 | 172 | return hud.destroy(); |
michael@0 | 173 | } |
michael@0 | 174 | |
michael@0 | 175 | if (this._browserConsoleDefer) { |
michael@0 | 176 | return this._browserConsoleDefer.promise; |
michael@0 | 177 | } |
michael@0 | 178 | |
michael@0 | 179 | this._browserConsoleDefer = promise.defer(); |
michael@0 | 180 | |
michael@0 | 181 | function connect() |
michael@0 | 182 | { |
michael@0 | 183 | let deferred = promise.defer(); |
michael@0 | 184 | |
michael@0 | 185 | if (!DebuggerServer.initialized) { |
michael@0 | 186 | DebuggerServer.init(); |
michael@0 | 187 | DebuggerServer.addBrowserActors(); |
michael@0 | 188 | } |
michael@0 | 189 | |
michael@0 | 190 | let client = new DebuggerClient(DebuggerServer.connectPipe()); |
michael@0 | 191 | client.connect(() => |
michael@0 | 192 | client.listTabs((aResponse) => { |
michael@0 | 193 | // Add Global Process debugging... |
michael@0 | 194 | let globals = JSON.parse(JSON.stringify(aResponse)); |
michael@0 | 195 | delete globals.tabs; |
michael@0 | 196 | delete globals.selected; |
michael@0 | 197 | // ...only if there are appropriate actors (a 'from' property will |
michael@0 | 198 | // always be there). |
michael@0 | 199 | if (Object.keys(globals).length > 1) { |
michael@0 | 200 | deferred.resolve({ form: globals, client: client, chrome: true }); |
michael@0 | 201 | } else { |
michael@0 | 202 | deferred.reject("Global console not found!"); |
michael@0 | 203 | } |
michael@0 | 204 | })); |
michael@0 | 205 | |
michael@0 | 206 | return deferred.promise; |
michael@0 | 207 | } |
michael@0 | 208 | |
michael@0 | 209 | let target; |
michael@0 | 210 | function getTarget(aConnection) |
michael@0 | 211 | { |
michael@0 | 212 | let options = { |
michael@0 | 213 | form: aConnection.form, |
michael@0 | 214 | client: aConnection.client, |
michael@0 | 215 | chrome: true, |
michael@0 | 216 | }; |
michael@0 | 217 | |
michael@0 | 218 | return devtools.TargetFactory.forRemoteTab(options); |
michael@0 | 219 | } |
michael@0 | 220 | |
michael@0 | 221 | function openWindow(aTarget) |
michael@0 | 222 | { |
michael@0 | 223 | target = aTarget; |
michael@0 | 224 | |
michael@0 | 225 | let deferred = promise.defer(); |
michael@0 | 226 | |
michael@0 | 227 | let win = Services.ww.openWindow(null, devtools.Tools.webConsole.url, "_blank", |
michael@0 | 228 | BROWSER_CONSOLE_WINDOW_FEATURES, null); |
michael@0 | 229 | win.addEventListener("DOMContentLoaded", function onLoad() { |
michael@0 | 230 | win.removeEventListener("DOMContentLoaded", onLoad); |
michael@0 | 231 | |
michael@0 | 232 | // Set the correct Browser Console title. |
michael@0 | 233 | let root = win.document.documentElement; |
michael@0 | 234 | root.setAttribute("title", root.getAttribute("browserConsoleTitle")); |
michael@0 | 235 | |
michael@0 | 236 | deferred.resolve(win); |
michael@0 | 237 | }); |
michael@0 | 238 | |
michael@0 | 239 | return deferred.promise; |
michael@0 | 240 | } |
michael@0 | 241 | |
michael@0 | 242 | connect().then(getTarget).then(openWindow).then((aWindow) => { |
michael@0 | 243 | this.openBrowserConsole(target, aWindow, aWindow) |
michael@0 | 244 | .then((aBrowserConsole) => { |
michael@0 | 245 | this._browserConsoleDefer.resolve(aBrowserConsole); |
michael@0 | 246 | this._browserConsoleDefer = null; |
michael@0 | 247 | }) |
michael@0 | 248 | }, console.error); |
michael@0 | 249 | |
michael@0 | 250 | return this._browserConsoleDefer.promise; |
michael@0 | 251 | }, |
michael@0 | 252 | |
michael@0 | 253 | /** |
michael@0 | 254 | * Get the Browser Console instance, if open. |
michael@0 | 255 | * |
michael@0 | 256 | * @return object|null |
michael@0 | 257 | * A BrowserConsole instance or null if the Browser Console is not |
michael@0 | 258 | * open. |
michael@0 | 259 | */ |
michael@0 | 260 | getBrowserConsole: function HS_getBrowserConsole() |
michael@0 | 261 | { |
michael@0 | 262 | return this.getHudReferenceById(this._browserConsoleID); |
michael@0 | 263 | }, |
michael@0 | 264 | }; |
michael@0 | 265 | |
michael@0 | 266 | |
michael@0 | 267 | /** |
michael@0 | 268 | * A WebConsole instance is an interactive console initialized *per target* |
michael@0 | 269 | * that displays console log data as well as provides an interactive terminal to |
michael@0 | 270 | * manipulate the target's document content. |
michael@0 | 271 | * |
michael@0 | 272 | * This object only wraps the iframe that holds the Web Console UI. This is |
michael@0 | 273 | * meant to be an integration point between the Firefox UI and the Web Console |
michael@0 | 274 | * UI and features. |
michael@0 | 275 | * |
michael@0 | 276 | * @constructor |
michael@0 | 277 | * @param object aTarget |
michael@0 | 278 | * The target that the web console will connect to. |
michael@0 | 279 | * @param nsIDOMWindow aIframeWindow |
michael@0 | 280 | * The window where the web console UI is already loaded. |
michael@0 | 281 | * @param nsIDOMWindow aChromeWindow |
michael@0 | 282 | * The window of the web console owner. |
michael@0 | 283 | */ |
michael@0 | 284 | function WebConsole(aTarget, aIframeWindow, aChromeWindow) |
michael@0 | 285 | { |
michael@0 | 286 | this.iframeWindow = aIframeWindow; |
michael@0 | 287 | this.chromeWindow = aChromeWindow; |
michael@0 | 288 | this.hudId = "hud_" + ++gHudId; |
michael@0 | 289 | this.target = aTarget; |
michael@0 | 290 | |
michael@0 | 291 | this.browserWindow = this.chromeWindow.top; |
michael@0 | 292 | |
michael@0 | 293 | let element = this.browserWindow.document.documentElement; |
michael@0 | 294 | if (element.getAttribute("windowtype") != "navigator:browser") { |
michael@0 | 295 | this.browserWindow = HUDService.currentContext(); |
michael@0 | 296 | } |
michael@0 | 297 | |
michael@0 | 298 | this.ui = new WebConsoleFrame(this); |
michael@0 | 299 | } |
michael@0 | 300 | |
michael@0 | 301 | WebConsole.prototype = { |
michael@0 | 302 | iframeWindow: null, |
michael@0 | 303 | chromeWindow: null, |
michael@0 | 304 | browserWindow: null, |
michael@0 | 305 | hudId: null, |
michael@0 | 306 | target: null, |
michael@0 | 307 | ui: null, |
michael@0 | 308 | _browserConsole: false, |
michael@0 | 309 | _destroyer: null, |
michael@0 | 310 | |
michael@0 | 311 | /** |
michael@0 | 312 | * Getter for a function to to listen for every request that completes. Used |
michael@0 | 313 | * by unit tests. The callback takes one argument: the HTTP activity object as |
michael@0 | 314 | * received from the remote Web Console. |
michael@0 | 315 | * |
michael@0 | 316 | * @type function |
michael@0 | 317 | */ |
michael@0 | 318 | get lastFinishedRequestCallback() HUDService.lastFinishedRequest.callback, |
michael@0 | 319 | |
michael@0 | 320 | /** |
michael@0 | 321 | * Getter for the window that can provide various utilities that the web |
michael@0 | 322 | * console makes use of, like opening links, managing popups, etc. In |
michael@0 | 323 | * most cases, this will be |this.browserWindow|, but in some uses (such as |
michael@0 | 324 | * the Browser Toolbox), there is no browser window, so an alternative window |
michael@0 | 325 | * hosts the utilities there. |
michael@0 | 326 | * @type nsIDOMWindow |
michael@0 | 327 | */ |
michael@0 | 328 | get chromeUtilsWindow() |
michael@0 | 329 | { |
michael@0 | 330 | if (this.browserWindow) { |
michael@0 | 331 | return this.browserWindow; |
michael@0 | 332 | } |
michael@0 | 333 | return this.chromeWindow.top; |
michael@0 | 334 | }, |
michael@0 | 335 | |
michael@0 | 336 | /** |
michael@0 | 337 | * Getter for the xul:popupset that holds any popups we open. |
michael@0 | 338 | * @type nsIDOMElement |
michael@0 | 339 | */ |
michael@0 | 340 | get mainPopupSet() |
michael@0 | 341 | { |
michael@0 | 342 | return this.chromeUtilsWindow.document.getElementById("mainPopupSet"); |
michael@0 | 343 | }, |
michael@0 | 344 | |
michael@0 | 345 | /** |
michael@0 | 346 | * Getter for the output element that holds messages we display. |
michael@0 | 347 | * @type nsIDOMElement |
michael@0 | 348 | */ |
michael@0 | 349 | get outputNode() |
michael@0 | 350 | { |
michael@0 | 351 | return this.ui ? this.ui.outputNode : null; |
michael@0 | 352 | }, |
michael@0 | 353 | |
michael@0 | 354 | get gViewSourceUtils() |
michael@0 | 355 | { |
michael@0 | 356 | return this.chromeUtilsWindow.gViewSourceUtils; |
michael@0 | 357 | }, |
michael@0 | 358 | |
michael@0 | 359 | /** |
michael@0 | 360 | * Initialize the Web Console instance. |
michael@0 | 361 | * |
michael@0 | 362 | * @return object |
michael@0 | 363 | * A promise for the initialization. |
michael@0 | 364 | */ |
michael@0 | 365 | init: function WC_init() |
michael@0 | 366 | { |
michael@0 | 367 | return this.ui.init().then(() => this); |
michael@0 | 368 | }, |
michael@0 | 369 | |
michael@0 | 370 | /** |
michael@0 | 371 | * Retrieve the Web Console panel title. |
michael@0 | 372 | * |
michael@0 | 373 | * @return string |
michael@0 | 374 | * The Web Console panel title. |
michael@0 | 375 | */ |
michael@0 | 376 | getPanelTitle: function WC_getPanelTitle() |
michael@0 | 377 | { |
michael@0 | 378 | let url = this.ui ? this.ui.contentLocation : ""; |
michael@0 | 379 | return l10n.getFormatStr("webConsoleWindowTitleAndURL", [url]); |
michael@0 | 380 | }, |
michael@0 | 381 | |
michael@0 | 382 | /** |
michael@0 | 383 | * The JSTerm object that manages the console's input. |
michael@0 | 384 | * @see webconsole.js::JSTerm |
michael@0 | 385 | * @type object |
michael@0 | 386 | */ |
michael@0 | 387 | get jsterm() |
michael@0 | 388 | { |
michael@0 | 389 | return this.ui ? this.ui.jsterm : null; |
michael@0 | 390 | }, |
michael@0 | 391 | |
michael@0 | 392 | /** |
michael@0 | 393 | * The clear output button handler. |
michael@0 | 394 | * @private |
michael@0 | 395 | */ |
michael@0 | 396 | _onClearButton: function WC__onClearButton() |
michael@0 | 397 | { |
michael@0 | 398 | if (this.target.isLocalTab) { |
michael@0 | 399 | this.browserWindow.DeveloperToolbar.resetErrorsCount(this.target.tab); |
michael@0 | 400 | } |
michael@0 | 401 | }, |
michael@0 | 402 | |
michael@0 | 403 | /** |
michael@0 | 404 | * Alias for the WebConsoleFrame.setFilterState() method. |
michael@0 | 405 | * @see webconsole.js::WebConsoleFrame.setFilterState() |
michael@0 | 406 | */ |
michael@0 | 407 | setFilterState: function WC_setFilterState() |
michael@0 | 408 | { |
michael@0 | 409 | this.ui && this.ui.setFilterState.apply(this.ui, arguments); |
michael@0 | 410 | }, |
michael@0 | 411 | |
michael@0 | 412 | /** |
michael@0 | 413 | * Open a link in a new tab. |
michael@0 | 414 | * |
michael@0 | 415 | * @param string aLink |
michael@0 | 416 | * The URL you want to open in a new tab. |
michael@0 | 417 | */ |
michael@0 | 418 | openLink: function WC_openLink(aLink) |
michael@0 | 419 | { |
michael@0 | 420 | this.chromeUtilsWindow.openUILinkIn(aLink, "tab"); |
michael@0 | 421 | }, |
michael@0 | 422 | |
michael@0 | 423 | /** |
michael@0 | 424 | * Open a link in Firefox's view source. |
michael@0 | 425 | * |
michael@0 | 426 | * @param string aSourceURL |
michael@0 | 427 | * The URL of the file. |
michael@0 | 428 | * @param integer aSourceLine |
michael@0 | 429 | * The line number which should be highlighted. |
michael@0 | 430 | */ |
michael@0 | 431 | viewSource: function WC_viewSource(aSourceURL, aSourceLine) |
michael@0 | 432 | { |
michael@0 | 433 | this.gViewSourceUtils.viewSource(aSourceURL, null, |
michael@0 | 434 | this.iframeWindow.document, aSourceLine); |
michael@0 | 435 | }, |
michael@0 | 436 | |
michael@0 | 437 | /** |
michael@0 | 438 | * Tries to open a Stylesheet file related to the web page for the web console |
michael@0 | 439 | * instance in the Style Editor. If the file is not found, it is opened in |
michael@0 | 440 | * source view instead. |
michael@0 | 441 | * |
michael@0 | 442 | * @param string aSourceURL |
michael@0 | 443 | * The URL of the file. |
michael@0 | 444 | * @param integer aSourceLine |
michael@0 | 445 | * The line number which you want to place the caret. |
michael@0 | 446 | * TODO: This function breaks the client-server boundaries. |
michael@0 | 447 | * To be fixed in bug 793259. |
michael@0 | 448 | */ |
michael@0 | 449 | viewSourceInStyleEditor: |
michael@0 | 450 | function WC_viewSourceInStyleEditor(aSourceURL, aSourceLine) |
michael@0 | 451 | { |
michael@0 | 452 | let toolbox = gDevTools.getToolbox(this.target); |
michael@0 | 453 | if (!toolbox) { |
michael@0 | 454 | this.viewSource(aSourceURL, aSourceLine); |
michael@0 | 455 | return; |
michael@0 | 456 | } |
michael@0 | 457 | |
michael@0 | 458 | gDevTools.showToolbox(this.target, "styleeditor").then(function(toolbox) { |
michael@0 | 459 | try { |
michael@0 | 460 | toolbox.getCurrentPanel().selectStyleSheet(aSourceURL, aSourceLine); |
michael@0 | 461 | } catch(e) { |
michael@0 | 462 | // Open view source if style editor fails. |
michael@0 | 463 | this.viewSource(aSourceURL, aSourceLine); |
michael@0 | 464 | } |
michael@0 | 465 | }); |
michael@0 | 466 | }, |
michael@0 | 467 | |
michael@0 | 468 | /** |
michael@0 | 469 | * Tries to open a JavaScript file related to the web page for the web console |
michael@0 | 470 | * instance in the Script Debugger. If the file is not found, it is opened in |
michael@0 | 471 | * source view instead. |
michael@0 | 472 | * |
michael@0 | 473 | * @param string aSourceURL |
michael@0 | 474 | * The URL of the file. |
michael@0 | 475 | * @param integer aSourceLine |
michael@0 | 476 | * The line number which you want to place the caret. |
michael@0 | 477 | */ |
michael@0 | 478 | viewSourceInDebugger: |
michael@0 | 479 | function WC_viewSourceInDebugger(aSourceURL, aSourceLine) |
michael@0 | 480 | { |
michael@0 | 481 | let toolbox = gDevTools.getToolbox(this.target); |
michael@0 | 482 | if (!toolbox) { |
michael@0 | 483 | this.viewSource(aSourceURL, aSourceLine); |
michael@0 | 484 | return; |
michael@0 | 485 | } |
michael@0 | 486 | |
michael@0 | 487 | let showSource = ({ DebuggerView }) => { |
michael@0 | 488 | if (DebuggerView.Sources.containsValue(aSourceURL)) { |
michael@0 | 489 | DebuggerView.setEditorLocation(aSourceURL, aSourceLine, |
michael@0 | 490 | { noDebug: true }).then(() => { |
michael@0 | 491 | this.ui.emit("source-in-debugger-opened"); |
michael@0 | 492 | }); |
michael@0 | 493 | return; |
michael@0 | 494 | } |
michael@0 | 495 | toolbox.selectTool("webconsole"); |
michael@0 | 496 | this.viewSource(aSourceURL, aSourceLine); |
michael@0 | 497 | } |
michael@0 | 498 | |
michael@0 | 499 | // If the Debugger was already open, switch to it and try to show the |
michael@0 | 500 | // source immediately. Otherwise, initialize it and wait for the sources |
michael@0 | 501 | // to be added first. |
michael@0 | 502 | let debuggerAlreadyOpen = toolbox.getPanel("jsdebugger"); |
michael@0 | 503 | toolbox.selectTool("jsdebugger").then(({ panelWin: dbg }) => { |
michael@0 | 504 | if (debuggerAlreadyOpen) { |
michael@0 | 505 | showSource(dbg); |
michael@0 | 506 | } else { |
michael@0 | 507 | dbg.once(dbg.EVENTS.SOURCES_ADDED, () => showSource(dbg)); |
michael@0 | 508 | } |
michael@0 | 509 | }); |
michael@0 | 510 | }, |
michael@0 | 511 | |
michael@0 | 512 | |
michael@0 | 513 | /** |
michael@0 | 514 | * Tries to open a JavaScript file related to the web page for the web console |
michael@0 | 515 | * instance in the corresponding Scratchpad. |
michael@0 | 516 | * |
michael@0 | 517 | * @param string aSourceURL |
michael@0 | 518 | * The URL of the file which corresponds to a Scratchpad id. |
michael@0 | 519 | */ |
michael@0 | 520 | viewSourceInScratchpad: function WC_viewSourceInScratchpad(aSourceURL) |
michael@0 | 521 | { |
michael@0 | 522 | // Check for matching top level Scratchpad window. |
michael@0 | 523 | let wins = Services.wm.getEnumerator("devtools:scratchpad"); |
michael@0 | 524 | |
michael@0 | 525 | while (wins.hasMoreElements()) { |
michael@0 | 526 | let win = wins.getNext(); |
michael@0 | 527 | |
michael@0 | 528 | if (!win.closed && win.Scratchpad.uniqueName === aSourceURL) { |
michael@0 | 529 | win.focus(); |
michael@0 | 530 | return; |
michael@0 | 531 | } |
michael@0 | 532 | } |
michael@0 | 533 | |
michael@0 | 534 | // Check for matching Scratchpad toolbox tab. |
michael@0 | 535 | for (let [, toolbox] of gDevTools) { |
michael@0 | 536 | let scratchpadPanel = toolbox.getPanel("scratchpad"); |
michael@0 | 537 | if (scratchpadPanel) { |
michael@0 | 538 | let { scratchpad } = scratchpadPanel; |
michael@0 | 539 | if (scratchpad.uniqueName === aSourceURL) { |
michael@0 | 540 | toolbox.selectTool("scratchpad"); |
michael@0 | 541 | toolbox.raise(); |
michael@0 | 542 | scratchpad.editor.focus(); |
michael@0 | 543 | return; |
michael@0 | 544 | } |
michael@0 | 545 | } |
michael@0 | 546 | } |
michael@0 | 547 | }, |
michael@0 | 548 | |
michael@0 | 549 | /** |
michael@0 | 550 | * Retrieve information about the JavaScript debugger's stackframes list. This |
michael@0 | 551 | * is used to allow the Web Console to evaluate code in the selected |
michael@0 | 552 | * stackframe. |
michael@0 | 553 | * |
michael@0 | 554 | * @return object|null |
michael@0 | 555 | * An object which holds: |
michael@0 | 556 | * - frames: the active ThreadClient.cachedFrames array. |
michael@0 | 557 | * - selected: depth/index of the selected stackframe in the debugger |
michael@0 | 558 | * UI. |
michael@0 | 559 | * If the debugger is not open or if it's not paused, then |null| is |
michael@0 | 560 | * returned. |
michael@0 | 561 | */ |
michael@0 | 562 | getDebuggerFrames: function WC_getDebuggerFrames() |
michael@0 | 563 | { |
michael@0 | 564 | let toolbox = gDevTools.getToolbox(this.target); |
michael@0 | 565 | if (!toolbox) { |
michael@0 | 566 | return null; |
michael@0 | 567 | } |
michael@0 | 568 | let panel = toolbox.getPanel("jsdebugger"); |
michael@0 | 569 | if (!panel) { |
michael@0 | 570 | return null; |
michael@0 | 571 | } |
michael@0 | 572 | let framesController = panel.panelWin.DebuggerController.StackFrames; |
michael@0 | 573 | let thread = framesController.activeThread; |
michael@0 | 574 | if (thread && thread.paused) { |
michael@0 | 575 | return { |
michael@0 | 576 | frames: thread.cachedFrames, |
michael@0 | 577 | selected: framesController.currentFrameDepth, |
michael@0 | 578 | }; |
michael@0 | 579 | } |
michael@0 | 580 | return null; |
michael@0 | 581 | }, |
michael@0 | 582 | |
michael@0 | 583 | /** |
michael@0 | 584 | * Destroy the object. Call this method to avoid memory leaks when the Web |
michael@0 | 585 | * Console is closed. |
michael@0 | 586 | * |
michael@0 | 587 | * @return object |
michael@0 | 588 | * A promise object that is resolved once the Web Console is closed. |
michael@0 | 589 | */ |
michael@0 | 590 | destroy: function WC_destroy() |
michael@0 | 591 | { |
michael@0 | 592 | if (this._destroyer) { |
michael@0 | 593 | return this._destroyer.promise; |
michael@0 | 594 | } |
michael@0 | 595 | |
michael@0 | 596 | HUDService.consoles.delete(this.hudId); |
michael@0 | 597 | |
michael@0 | 598 | this._destroyer = promise.defer(); |
michael@0 | 599 | |
michael@0 | 600 | let popupset = this.mainPopupSet; |
michael@0 | 601 | let panels = popupset.querySelectorAll("panel[hudId=" + this.hudId + "]"); |
michael@0 | 602 | for (let panel of panels) { |
michael@0 | 603 | panel.hidePopup(); |
michael@0 | 604 | } |
michael@0 | 605 | |
michael@0 | 606 | let onDestroy = function WC_onDestroyUI() { |
michael@0 | 607 | try { |
michael@0 | 608 | let tabWindow = this.target.isLocalTab ? this.target.window : null; |
michael@0 | 609 | tabWindow && tabWindow.focus(); |
michael@0 | 610 | } |
michael@0 | 611 | catch (ex) { |
michael@0 | 612 | // Tab focus can fail if the tab or target is closed. |
michael@0 | 613 | } |
michael@0 | 614 | |
michael@0 | 615 | let id = WebConsoleUtils.supportsString(this.hudId); |
michael@0 | 616 | Services.obs.notifyObservers(id, "web-console-destroyed", null); |
michael@0 | 617 | this._destroyer.resolve(null); |
michael@0 | 618 | }.bind(this); |
michael@0 | 619 | |
michael@0 | 620 | if (this.ui) { |
michael@0 | 621 | this.ui.destroy().then(onDestroy); |
michael@0 | 622 | } |
michael@0 | 623 | else { |
michael@0 | 624 | onDestroy(); |
michael@0 | 625 | } |
michael@0 | 626 | |
michael@0 | 627 | return this._destroyer.promise; |
michael@0 | 628 | }, |
michael@0 | 629 | }; |
michael@0 | 630 | |
michael@0 | 631 | |
michael@0 | 632 | /** |
michael@0 | 633 | * A BrowserConsole instance is an interactive console initialized *per target* |
michael@0 | 634 | * that displays console log data as well as provides an interactive terminal to |
michael@0 | 635 | * manipulate the target's document content. |
michael@0 | 636 | * |
michael@0 | 637 | * This object only wraps the iframe that holds the Browser Console UI. This is |
michael@0 | 638 | * meant to be an integration point between the Firefox UI and the Browser Console |
michael@0 | 639 | * UI and features. |
michael@0 | 640 | * |
michael@0 | 641 | * @constructor |
michael@0 | 642 | * @param object aTarget |
michael@0 | 643 | * The target that the browser console will connect to. |
michael@0 | 644 | * @param nsIDOMWindow aIframeWindow |
michael@0 | 645 | * The window where the browser console UI is already loaded. |
michael@0 | 646 | * @param nsIDOMWindow aChromeWindow |
michael@0 | 647 | * The window of the browser console owner. |
michael@0 | 648 | */ |
michael@0 | 649 | function BrowserConsole() |
michael@0 | 650 | { |
michael@0 | 651 | WebConsole.apply(this, arguments); |
michael@0 | 652 | this._telemetry = new Telemetry(); |
michael@0 | 653 | } |
michael@0 | 654 | |
michael@0 | 655 | BrowserConsole.prototype = Heritage.extend(WebConsole.prototype, |
michael@0 | 656 | { |
michael@0 | 657 | _browserConsole: true, |
michael@0 | 658 | _bc_init: null, |
michael@0 | 659 | _bc_destroyer: null, |
michael@0 | 660 | |
michael@0 | 661 | $init: WebConsole.prototype.init, |
michael@0 | 662 | |
michael@0 | 663 | /** |
michael@0 | 664 | * Initialize the Browser Console instance. |
michael@0 | 665 | * |
michael@0 | 666 | * @return object |
michael@0 | 667 | * A promise for the initialization. |
michael@0 | 668 | */ |
michael@0 | 669 | init: function BC_init() |
michael@0 | 670 | { |
michael@0 | 671 | if (this._bc_init) { |
michael@0 | 672 | return this._bc_init; |
michael@0 | 673 | } |
michael@0 | 674 | |
michael@0 | 675 | this.ui._filterPrefsPrefix = BROWSER_CONSOLE_FILTER_PREFS_PREFIX; |
michael@0 | 676 | |
michael@0 | 677 | let window = this.iframeWindow; |
michael@0 | 678 | |
michael@0 | 679 | // Make sure that the closing of the Browser Console window destroys this |
michael@0 | 680 | // instance. |
michael@0 | 681 | let onClose = () => { |
michael@0 | 682 | window.removeEventListener("unload", onClose); |
michael@0 | 683 | this.destroy(); |
michael@0 | 684 | }; |
michael@0 | 685 | window.addEventListener("unload", onClose); |
michael@0 | 686 | |
michael@0 | 687 | // Make sure Ctrl-W closes the Browser Console window. |
michael@0 | 688 | window.document.getElementById("cmd_close").removeAttribute("disabled"); |
michael@0 | 689 | |
michael@0 | 690 | this._telemetry.toolOpened("browserconsole"); |
michael@0 | 691 | |
michael@0 | 692 | this._bc_init = this.$init(); |
michael@0 | 693 | return this._bc_init; |
michael@0 | 694 | }, |
michael@0 | 695 | |
michael@0 | 696 | $destroy: WebConsole.prototype.destroy, |
michael@0 | 697 | |
michael@0 | 698 | /** |
michael@0 | 699 | * Destroy the object. |
michael@0 | 700 | * |
michael@0 | 701 | * @return object |
michael@0 | 702 | * A promise object that is resolved once the Browser Console is closed. |
michael@0 | 703 | */ |
michael@0 | 704 | destroy: function BC_destroy() |
michael@0 | 705 | { |
michael@0 | 706 | if (this._bc_destroyer) { |
michael@0 | 707 | return this._bc_destroyer.promise; |
michael@0 | 708 | } |
michael@0 | 709 | |
michael@0 | 710 | this._telemetry.toolClosed("browserconsole"); |
michael@0 | 711 | |
michael@0 | 712 | this._bc_destroyer = promise.defer(); |
michael@0 | 713 | |
michael@0 | 714 | let chromeWindow = this.chromeWindow; |
michael@0 | 715 | this.$destroy().then(() => |
michael@0 | 716 | this.target.client.close(() => { |
michael@0 | 717 | HUDService._browserConsoleID = null; |
michael@0 | 718 | chromeWindow.close(); |
michael@0 | 719 | this._bc_destroyer.resolve(null); |
michael@0 | 720 | })); |
michael@0 | 721 | |
michael@0 | 722 | return this._bc_destroyer.promise; |
michael@0 | 723 | }, |
michael@0 | 724 | }); |
michael@0 | 725 | |
michael@0 | 726 | const HUDService = new HUD_SERVICE(); |
michael@0 | 727 | |
michael@0 | 728 | (() => { |
michael@0 | 729 | let methods = ["openWebConsole", "openBrowserConsole", |
michael@0 | 730 | "toggleBrowserConsole", "getOpenWebConsole", |
michael@0 | 731 | "getBrowserConsole", "getHudByWindow", "getHudReferenceById"]; |
michael@0 | 732 | for (let method of methods) { |
michael@0 | 733 | exports[method] = HUDService[method].bind(HUDService); |
michael@0 | 734 | } |
michael@0 | 735 | |
michael@0 | 736 | exports.consoles = HUDService.consoles; |
michael@0 | 737 | exports.lastFinishedRequest = HUDService.lastFinishedRequest; |
michael@0 | 738 | })(); |