browser/devtools/framework/target.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 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 2 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 4
michael@0 5 "use strict";
michael@0 6
michael@0 7 const {Cc, Ci, Cu} = require("chrome");
michael@0 8 const {Promise: promise} = require("resource://gre/modules/Promise.jsm");
michael@0 9 const EventEmitter = require("devtools/toolkit/event-emitter");
michael@0 10
michael@0 11 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
michael@0 12 XPCOMUtils.defineLazyModuleGetter(this, "DebuggerServer",
michael@0 13 "resource://gre/modules/devtools/dbg-server.jsm");
michael@0 14 XPCOMUtils.defineLazyModuleGetter(this, "DebuggerClient",
michael@0 15 "resource://gre/modules/devtools/dbg-client.jsm");
michael@0 16
michael@0 17 const targets = new WeakMap();
michael@0 18 const promiseTargets = new WeakMap();
michael@0 19
michael@0 20 /**
michael@0 21 * Functions for creating Targets
michael@0 22 */
michael@0 23 exports.TargetFactory = {
michael@0 24 /**
michael@0 25 * Construct a Target
michael@0 26 * @param {XULTab} tab
michael@0 27 * The tab to use in creating a new target.
michael@0 28 *
michael@0 29 * @return A target object
michael@0 30 */
michael@0 31 forTab: function TF_forTab(tab) {
michael@0 32 let target = targets.get(tab);
michael@0 33 if (target == null) {
michael@0 34 target = new TabTarget(tab);
michael@0 35 targets.set(tab, target);
michael@0 36 }
michael@0 37 return target;
michael@0 38 },
michael@0 39
michael@0 40 /**
michael@0 41 * Return a promise of a Target for a remote tab.
michael@0 42 * @param {Object} options
michael@0 43 * The options object has the following properties:
michael@0 44 * {
michael@0 45 * form: the remote protocol form of a tab,
michael@0 46 * client: a DebuggerClient instance
michael@0 47 * (caller owns this and is responsible for closing),
michael@0 48 * chrome: true if the remote target is the whole process
michael@0 49 * }
michael@0 50 *
michael@0 51 * @return A promise of a target object
michael@0 52 */
michael@0 53 forRemoteTab: function TF_forRemoteTab(options) {
michael@0 54 let targetPromise = promiseTargets.get(options);
michael@0 55 if (targetPromise == null) {
michael@0 56 let target = new TabTarget(options);
michael@0 57 targetPromise = target.makeRemote().then(() => target);
michael@0 58 promiseTargets.set(options, targetPromise);
michael@0 59 }
michael@0 60 return targetPromise;
michael@0 61 },
michael@0 62
michael@0 63 /**
michael@0 64 * Creating a target for a tab that is being closed is a problem because it
michael@0 65 * allows a leak as a result of coming after the close event which normally
michael@0 66 * clears things up. This function allows us to ask if there is a known
michael@0 67 * target for a tab without creating a target
michael@0 68 * @return true/false
michael@0 69 */
michael@0 70 isKnownTab: function TF_isKnownTab(tab) {
michael@0 71 return targets.has(tab);
michael@0 72 },
michael@0 73
michael@0 74 /**
michael@0 75 * Construct a Target
michael@0 76 * @param {nsIDOMWindow} window
michael@0 77 * The chromeWindow to use in creating a new target
michael@0 78 * @return A target object
michael@0 79 */
michael@0 80 forWindow: function TF_forWindow(window) {
michael@0 81 let target = targets.get(window);
michael@0 82 if (target == null) {
michael@0 83 target = new WindowTarget(window);
michael@0 84 targets.set(window, target);
michael@0 85 }
michael@0 86 return target;
michael@0 87 },
michael@0 88
michael@0 89 /**
michael@0 90 * Get all of the targets known to the local browser instance
michael@0 91 * @return An array of target objects
michael@0 92 */
michael@0 93 allTargets: function TF_allTargets() {
michael@0 94 let windows = [];
michael@0 95 let wm = Cc["@mozilla.org/appshell/window-mediator;1"]
michael@0 96 .getService(Ci.nsIWindowMediator);
michael@0 97 let en = wm.getXULWindowEnumerator(null);
michael@0 98 while (en.hasMoreElements()) {
michael@0 99 windows.push(en.getNext());
michael@0 100 }
michael@0 101
michael@0 102 return windows.map(function(window) {
michael@0 103 return TargetFactory.forWindow(window);
michael@0 104 });
michael@0 105 },
michael@0 106 };
michael@0 107
michael@0 108 /**
michael@0 109 * The 'version' property allows the developer tools equivalent of browser
michael@0 110 * detection. Browser detection is evil, however while we don't know what we
michael@0 111 * will need to detect in the future, it is an easy way to postpone work.
michael@0 112 * We should be looking to use 'supports()' in place of version where
michael@0 113 * possible.
michael@0 114 */
michael@0 115 function getVersion() {
michael@0 116 // FIXME: return something better
michael@0 117 return 20;
michael@0 118 }
michael@0 119
michael@0 120 /**
michael@0 121 * A better way to support feature detection, but we're not yet at a place
michael@0 122 * where we have the features well enough defined for this to make lots of
michael@0 123 * sense.
michael@0 124 */
michael@0 125 function supports(feature) {
michael@0 126 // FIXME: return something better
michael@0 127 return false;
michael@0 128 };
michael@0 129
michael@0 130 /**
michael@0 131 * A Target represents something that we can debug. Targets are generally
michael@0 132 * read-only. Any changes that you wish to make to a target should be done via
michael@0 133 * a Tool that attaches to the target. i.e. a Target is just a pointer saying
michael@0 134 * "the thing to debug is over there".
michael@0 135 *
michael@0 136 * Providing a generalized abstraction of a web-page or web-browser (available
michael@0 137 * either locally or remotely) is beyond the scope of this class (and maybe
michael@0 138 * also beyond the scope of this universe) However Target does attempt to
michael@0 139 * abstract some common events and read-only properties common to many Tools.
michael@0 140 *
michael@0 141 * Supported read-only properties:
michael@0 142 * - name, isRemote, url
michael@0 143 *
michael@0 144 * Target extends EventEmitter and provides support for the following events:
michael@0 145 * - close: The target window has been closed. All tools attached to this
michael@0 146 * target should close. This event is not currently cancelable.
michael@0 147 * - navigate: The target window has navigated to a different URL
michael@0 148 *
michael@0 149 * Optional events:
michael@0 150 * - will-navigate: The target window will navigate to a different URL
michael@0 151 * - hidden: The target is not visible anymore (for TargetTab, another tab is selected)
michael@0 152 * - visible: The target is visible (for TargetTab, tab is selected)
michael@0 153 *
michael@0 154 * Target also supports 2 functions to help allow 2 different versions of
michael@0 155 * Firefox debug each other. The 'version' property is the equivalent of
michael@0 156 * browser detection - simple and easy to implement but gets fragile when things
michael@0 157 * are not quite what they seem. The 'supports' property is the equivalent of
michael@0 158 * feature detection - harder to setup, but more robust long-term.
michael@0 159 *
michael@0 160 * Comparing Targets: 2 instances of a Target object can point at the same
michael@0 161 * thing, so t1 !== t2 and t1 != t2 even when they represent the same object.
michael@0 162 * To compare to targets use 't1.equals(t2)'.
michael@0 163 */
michael@0 164 function Target() {
michael@0 165 throw new Error("Use TargetFactory.newXXX or Target.getXXX to create a Target in place of 'new Target()'");
michael@0 166 }
michael@0 167
michael@0 168 Object.defineProperty(Target.prototype, "version", {
michael@0 169 get: getVersion,
michael@0 170 enumerable: true
michael@0 171 });
michael@0 172
michael@0 173
michael@0 174 /**
michael@0 175 * A TabTarget represents a page living in a browser tab. Generally these will
michael@0 176 * be web pages served over http(s), but they don't have to be.
michael@0 177 */
michael@0 178 function TabTarget(tab) {
michael@0 179 EventEmitter.decorate(this);
michael@0 180 this.destroy = this.destroy.bind(this);
michael@0 181 this._handleThreadState = this._handleThreadState.bind(this);
michael@0 182 this.on("thread-resumed", this._handleThreadState);
michael@0 183 this.on("thread-paused", this._handleThreadState);
michael@0 184 // Only real tabs need initialization here. Placeholder objects for remote
michael@0 185 // targets will be initialized after a makeRemote method call.
michael@0 186 if (tab && !["client", "form", "chrome"].every(tab.hasOwnProperty, tab)) {
michael@0 187 this._tab = tab;
michael@0 188 this._setupListeners();
michael@0 189 } else {
michael@0 190 this._form = tab.form;
michael@0 191 this._client = tab.client;
michael@0 192 this._chrome = tab.chrome;
michael@0 193 }
michael@0 194 }
michael@0 195
michael@0 196 TabTarget.prototype = {
michael@0 197 _webProgressListener: null,
michael@0 198
michael@0 199 supports: supports,
michael@0 200 get version() { return getVersion(); },
michael@0 201
michael@0 202 get tab() {
michael@0 203 return this._tab;
michael@0 204 },
michael@0 205
michael@0 206 get form() {
michael@0 207 return this._form;
michael@0 208 },
michael@0 209
michael@0 210 get root() {
michael@0 211 return this._root;
michael@0 212 },
michael@0 213
michael@0 214 get client() {
michael@0 215 return this._client;
michael@0 216 },
michael@0 217
michael@0 218 get chrome() {
michael@0 219 return this._chrome;
michael@0 220 },
michael@0 221
michael@0 222 get window() {
michael@0 223 // Be extra careful here, since this may be called by HS_getHudByWindow
michael@0 224 // during shutdown.
michael@0 225 if (this._tab && this._tab.linkedBrowser) {
michael@0 226 return this._tab.linkedBrowser.contentWindow;
michael@0 227 }
michael@0 228 return null;
michael@0 229 },
michael@0 230
michael@0 231 get name() {
michael@0 232 return this._tab ? this._tab.linkedBrowser.contentDocument.title :
michael@0 233 this._form.title;
michael@0 234 },
michael@0 235
michael@0 236 get url() {
michael@0 237 return this._tab ? this._tab.linkedBrowser.contentDocument.location.href :
michael@0 238 this._form.url;
michael@0 239 },
michael@0 240
michael@0 241 get isRemote() {
michael@0 242 return !this.isLocalTab;
michael@0 243 },
michael@0 244
michael@0 245 get isAddon() {
michael@0 246 return !!(this._form && this._form.addonActor);
michael@0 247 },
michael@0 248
michael@0 249 get isLocalTab() {
michael@0 250 return !!this._tab;
michael@0 251 },
michael@0 252
michael@0 253 get isThreadPaused() {
michael@0 254 return !!this._isThreadPaused;
michael@0 255 },
michael@0 256
michael@0 257 /**
michael@0 258 * Adds remote protocol capabilities to the target, so that it can be used
michael@0 259 * for tools that support the Remote Debugging Protocol even for local
michael@0 260 * connections.
michael@0 261 */
michael@0 262 makeRemote: function TabTarget_makeRemote() {
michael@0 263 if (this._remote) {
michael@0 264 return this._remote.promise;
michael@0 265 }
michael@0 266
michael@0 267 this._remote = promise.defer();
michael@0 268
michael@0 269 if (this.isLocalTab) {
michael@0 270 // Since a remote protocol connection will be made, let's start the
michael@0 271 // DebuggerServer here, once and for all tools.
michael@0 272 if (!DebuggerServer.initialized) {
michael@0 273 DebuggerServer.init();
michael@0 274 DebuggerServer.addBrowserActors();
michael@0 275 }
michael@0 276
michael@0 277 this._client = new DebuggerClient(DebuggerServer.connectPipe());
michael@0 278 // A local TabTarget will never perform chrome debugging.
michael@0 279 this._chrome = false;
michael@0 280 }
michael@0 281
michael@0 282 this._setupRemoteListeners();
michael@0 283
michael@0 284 let attachTab = () => {
michael@0 285 this._client.attachTab(this._form.actor, (aResponse, aTabClient) => {
michael@0 286 if (!aTabClient) {
michael@0 287 this._remote.reject("Unable to attach to the tab");
michael@0 288 return;
michael@0 289 }
michael@0 290 this.activeTab = aTabClient;
michael@0 291 this.threadActor = aResponse.threadActor;
michael@0 292 this._remote.resolve(null);
michael@0 293 });
michael@0 294 };
michael@0 295
michael@0 296 if (this.isLocalTab) {
michael@0 297 this._client.connect((aType, aTraits) => {
michael@0 298 this._client.listTabs(aResponse => {
michael@0 299 this._root = aResponse;
michael@0 300
michael@0 301 let windowUtils = this.window
michael@0 302 .QueryInterface(Ci.nsIInterfaceRequestor)
michael@0 303 .getInterface(Ci.nsIDOMWindowUtils);
michael@0 304 let outerWindow = windowUtils.outerWindowID;
michael@0 305 aResponse.tabs.some((tab) => {
michael@0 306 if (tab.outerWindowID === outerWindow) {
michael@0 307 this._form = tab;
michael@0 308 return true;
michael@0 309 }
michael@0 310 return false;
michael@0 311 });
michael@0 312 if (!this._form) {
michael@0 313 this._form = aResponse.tabs[aResponse.selected];
michael@0 314 }
michael@0 315 attachTab();
michael@0 316 });
michael@0 317 });
michael@0 318 } else if (!this.chrome) {
michael@0 319 // In the remote debugging case, the protocol connection will have been
michael@0 320 // already initialized in the connection screen code.
michael@0 321 attachTab();
michael@0 322 } else {
michael@0 323 // Remote chrome debugging doesn't need anything at this point.
michael@0 324 this._remote.resolve(null);
michael@0 325 }
michael@0 326
michael@0 327 return this._remote.promise;
michael@0 328 },
michael@0 329
michael@0 330 /**
michael@0 331 * Listen to the different events.
michael@0 332 */
michael@0 333 _setupListeners: function TabTarget__setupListeners() {
michael@0 334 this._webProgressListener = new TabWebProgressListener(this);
michael@0 335 this.tab.linkedBrowser.addProgressListener(this._webProgressListener);
michael@0 336 this.tab.addEventListener("TabClose", this);
michael@0 337 this.tab.parentNode.addEventListener("TabSelect", this);
michael@0 338 this.tab.ownerDocument.defaultView.addEventListener("unload", this);
michael@0 339 },
michael@0 340
michael@0 341 /**
michael@0 342 * Teardown event listeners.
michael@0 343 */
michael@0 344 _teardownListeners: function TabTarget__teardownListeners() {
michael@0 345 if (this._webProgressListener) {
michael@0 346 this._webProgressListener.destroy();
michael@0 347 }
michael@0 348
michael@0 349 this._tab.ownerDocument.defaultView.removeEventListener("unload", this);
michael@0 350 this._tab.removeEventListener("TabClose", this);
michael@0 351 this._tab.parentNode.removeEventListener("TabSelect", this);
michael@0 352 },
michael@0 353
michael@0 354 /**
michael@0 355 * Setup listeners for remote debugging, updating existing ones as necessary.
michael@0 356 */
michael@0 357 _setupRemoteListeners: function TabTarget__setupRemoteListeners() {
michael@0 358 this.client.addListener("closed", this.destroy);
michael@0 359
michael@0 360 this._onTabDetached = (aType, aPacket) => {
michael@0 361 // We have to filter message to ensure that this detach is for this tab
michael@0 362 if (aPacket.from == this._form.actor) {
michael@0 363 this.destroy();
michael@0 364 }
michael@0 365 };
michael@0 366 this.client.addListener("tabDetached", this._onTabDetached);
michael@0 367
michael@0 368 this._onTabNavigated = function onRemoteTabNavigated(aType, aPacket) {
michael@0 369 let event = Object.create(null);
michael@0 370 event.url = aPacket.url;
michael@0 371 event.title = aPacket.title;
michael@0 372 event.nativeConsoleAPI = aPacket.nativeConsoleAPI;
michael@0 373 // Send any stored event payload (DOMWindow or nsIRequest) for backwards
michael@0 374 // compatibility with non-remotable tools.
michael@0 375 if (aPacket.state == "start") {
michael@0 376 event._navPayload = this._navRequest;
michael@0 377 this.emit("will-navigate", event);
michael@0 378 this._navRequest = null;
michael@0 379 } else {
michael@0 380 event._navPayload = this._navWindow;
michael@0 381 this.emit("navigate", event);
michael@0 382 this._navWindow = null;
michael@0 383 }
michael@0 384 }.bind(this);
michael@0 385 this.client.addListener("tabNavigated", this._onTabNavigated);
michael@0 386 },
michael@0 387
michael@0 388 /**
michael@0 389 * Teardown listeners for remote debugging.
michael@0 390 */
michael@0 391 _teardownRemoteListeners: function TabTarget__teardownRemoteListeners() {
michael@0 392 this.client.removeListener("closed", this.destroy);
michael@0 393 this.client.removeListener("tabNavigated", this._onTabNavigated);
michael@0 394 this.client.removeListener("tabDetached", this._onTabDetached);
michael@0 395 },
michael@0 396
michael@0 397 /**
michael@0 398 * Handle tabs events.
michael@0 399 */
michael@0 400 handleEvent: function (event) {
michael@0 401 switch (event.type) {
michael@0 402 case "TabClose":
michael@0 403 case "unload":
michael@0 404 this.destroy();
michael@0 405 break;
michael@0 406 case "TabSelect":
michael@0 407 if (this.tab.selected) {
michael@0 408 this.emit("visible", event);
michael@0 409 } else {
michael@0 410 this.emit("hidden", event);
michael@0 411 }
michael@0 412 break;
michael@0 413 }
michael@0 414 },
michael@0 415
michael@0 416 /**
michael@0 417 * Handle script status.
michael@0 418 */
michael@0 419 _handleThreadState: function(event) {
michael@0 420 switch (event) {
michael@0 421 case "thread-resumed":
michael@0 422 this._isThreadPaused = false;
michael@0 423 break;
michael@0 424 case "thread-paused":
michael@0 425 this._isThreadPaused = true;
michael@0 426 break;
michael@0 427 }
michael@0 428 },
michael@0 429
michael@0 430 /**
michael@0 431 * Target is not alive anymore.
michael@0 432 */
michael@0 433 destroy: function() {
michael@0 434 // If several things call destroy then we give them all the same
michael@0 435 // destruction promise so we're sure to destroy only once
michael@0 436 if (this._destroyer) {
michael@0 437 return this._destroyer.promise;
michael@0 438 }
michael@0 439
michael@0 440 this._destroyer = promise.defer();
michael@0 441
michael@0 442 // Before taking any action, notify listeners that destruction is imminent.
michael@0 443 this.emit("close");
michael@0 444
michael@0 445 // First of all, do cleanup tasks that pertain to both remoted and
michael@0 446 // non-remoted targets.
michael@0 447 this.off("thread-resumed", this._handleThreadState);
michael@0 448 this.off("thread-paused", this._handleThreadState);
michael@0 449
michael@0 450 if (this._tab) {
michael@0 451 this._teardownListeners();
michael@0 452 }
michael@0 453
michael@0 454 let cleanupAndResolve = () => {
michael@0 455 this._cleanup();
michael@0 456 this._destroyer.resolve(null);
michael@0 457 };
michael@0 458 // If this target was not remoted, the promise will be resolved before the
michael@0 459 // function returns.
michael@0 460 if (this._tab && !this._client) {
michael@0 461 cleanupAndResolve();
michael@0 462 } else if (this._client) {
michael@0 463 // If, on the other hand, this target was remoted, the promise will be
michael@0 464 // resolved after the remote connection is closed.
michael@0 465 this._teardownRemoteListeners();
michael@0 466
michael@0 467 if (this.isLocalTab) {
michael@0 468 // We started with a local tab and created the client ourselves, so we
michael@0 469 // should close it.
michael@0 470 this._client.close(cleanupAndResolve);
michael@0 471 } else {
michael@0 472 // The client was handed to us, so we are not responsible for closing
michael@0 473 // it. We just need to detach from the tab, if already attached.
michael@0 474 if (this.activeTab) {
michael@0 475 this.activeTab.detach(cleanupAndResolve);
michael@0 476 } else {
michael@0 477 cleanupAndResolve();
michael@0 478 }
michael@0 479 }
michael@0 480 }
michael@0 481
michael@0 482 return this._destroyer.promise;
michael@0 483 },
michael@0 484
michael@0 485 /**
michael@0 486 * Clean up references to what this target points to.
michael@0 487 */
michael@0 488 _cleanup: function TabTarget__cleanup() {
michael@0 489 if (this._tab) {
michael@0 490 targets.delete(this._tab);
michael@0 491 } else {
michael@0 492 promiseTargets.delete(this._form);
michael@0 493 }
michael@0 494 this.activeTab = null;
michael@0 495 this._client = null;
michael@0 496 this._tab = null;
michael@0 497 this._form = null;
michael@0 498 this._remote = null;
michael@0 499 },
michael@0 500
michael@0 501 toString: function() {
michael@0 502 return 'TabTarget:' + (this._tab ? this._tab : (this._form && this._form.actor));
michael@0 503 },
michael@0 504 };
michael@0 505
michael@0 506
michael@0 507 /**
michael@0 508 * WebProgressListener for TabTarget.
michael@0 509 *
michael@0 510 * @param object aTarget
michael@0 511 * The TabTarget instance to work with.
michael@0 512 */
michael@0 513 function TabWebProgressListener(aTarget) {
michael@0 514 this.target = aTarget;
michael@0 515 }
michael@0 516
michael@0 517 TabWebProgressListener.prototype = {
michael@0 518 target: null,
michael@0 519
michael@0 520 QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener, Ci.nsISupportsWeakReference]),
michael@0 521
michael@0 522 onStateChange: function TWPL_onStateChange(progress, request, flag, status) {
michael@0 523 let isStart = flag & Ci.nsIWebProgressListener.STATE_START;
michael@0 524 let isDocument = flag & Ci.nsIWebProgressListener.STATE_IS_DOCUMENT;
michael@0 525 let isNetwork = flag & Ci.nsIWebProgressListener.STATE_IS_NETWORK;
michael@0 526 let isRequest = flag & Ci.nsIWebProgressListener.STATE_IS_REQUEST;
michael@0 527
michael@0 528 // Skip non-interesting states.
michael@0 529 if (!isStart || !isDocument || !isRequest || !isNetwork) {
michael@0 530 return;
michael@0 531 }
michael@0 532
michael@0 533 // emit event if the top frame is navigating
michael@0 534 if (this.target && this.target.window == progress.DOMWindow) {
michael@0 535 // Emit the event if the target is not remoted or store the payload for
michael@0 536 // later emission otherwise.
michael@0 537 if (this.target._client) {
michael@0 538 this.target._navRequest = request;
michael@0 539 } else {
michael@0 540 this.target.emit("will-navigate", request);
michael@0 541 }
michael@0 542 }
michael@0 543 },
michael@0 544
michael@0 545 onProgressChange: function() {},
michael@0 546 onSecurityChange: function() {},
michael@0 547 onStatusChange: function() {},
michael@0 548
michael@0 549 onLocationChange: function TWPL_onLocationChange(webProgress, request, URI, flags) {
michael@0 550 if (this.target &&
michael@0 551 !(flags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT)) {
michael@0 552 let window = webProgress.DOMWindow;
michael@0 553 // Emit the event if the target is not remoted or store the payload for
michael@0 554 // later emission otherwise.
michael@0 555 if (this.target._client) {
michael@0 556 this.target._navWindow = window;
michael@0 557 } else {
michael@0 558 this.target.emit("navigate", window);
michael@0 559 }
michael@0 560 }
michael@0 561 },
michael@0 562
michael@0 563 /**
michael@0 564 * Destroy the progress listener instance.
michael@0 565 */
michael@0 566 destroy: function TWPL_destroy() {
michael@0 567 if (this.target.tab) {
michael@0 568 try {
michael@0 569 this.target.tab.linkedBrowser.removeProgressListener(this);
michael@0 570 } catch (ex) {
michael@0 571 // This can throw when a tab crashes in e10s.
michael@0 572 }
michael@0 573 }
michael@0 574 this.target._webProgressListener = null;
michael@0 575 this.target._navRequest = null;
michael@0 576 this.target._navWindow = null;
michael@0 577 this.target = null;
michael@0 578 }
michael@0 579 };
michael@0 580
michael@0 581
michael@0 582 /**
michael@0 583 * A WindowTarget represents a page living in a xul window or panel. Generally
michael@0 584 * these will have a chrome: URL
michael@0 585 */
michael@0 586 function WindowTarget(window) {
michael@0 587 EventEmitter.decorate(this);
michael@0 588 this._window = window;
michael@0 589 this._setupListeners();
michael@0 590 }
michael@0 591
michael@0 592 WindowTarget.prototype = {
michael@0 593 supports: supports,
michael@0 594 get version() { return getVersion(); },
michael@0 595
michael@0 596 get window() {
michael@0 597 return this._window;
michael@0 598 },
michael@0 599
michael@0 600 get name() {
michael@0 601 return this._window.document.title;
michael@0 602 },
michael@0 603
michael@0 604 get url() {
michael@0 605 return this._window.document.location.href;
michael@0 606 },
michael@0 607
michael@0 608 get isRemote() {
michael@0 609 return false;
michael@0 610 },
michael@0 611
michael@0 612 get isLocalTab() {
michael@0 613 return false;
michael@0 614 },
michael@0 615
michael@0 616 get isThreadPaused() {
michael@0 617 return !!this._isThreadPaused;
michael@0 618 },
michael@0 619
michael@0 620 /**
michael@0 621 * Listen to the different events.
michael@0 622 */
michael@0 623 _setupListeners: function() {
michael@0 624 this._handleThreadState = this._handleThreadState.bind(this);
michael@0 625 this.on("thread-paused", this._handleThreadState);
michael@0 626 this.on("thread-resumed", this._handleThreadState);
michael@0 627 },
michael@0 628
michael@0 629 _handleThreadState: function(event) {
michael@0 630 switch (event) {
michael@0 631 case "thread-resumed":
michael@0 632 this._isThreadPaused = false;
michael@0 633 break;
michael@0 634 case "thread-paused":
michael@0 635 this._isThreadPaused = true;
michael@0 636 break;
michael@0 637 }
michael@0 638 },
michael@0 639
michael@0 640 /**
michael@0 641 * Target is not alive anymore.
michael@0 642 */
michael@0 643 destroy: function() {
michael@0 644 if (!this._destroyed) {
michael@0 645 this._destroyed = true;
michael@0 646
michael@0 647 this.off("thread-paused", this._handleThreadState);
michael@0 648 this.off("thread-resumed", this._handleThreadState);
michael@0 649 this.emit("close");
michael@0 650
michael@0 651 targets.delete(this._window);
michael@0 652 this._window = null;
michael@0 653 }
michael@0 654
michael@0 655 return promise.resolve(null);
michael@0 656 },
michael@0 657
michael@0 658 toString: function() {
michael@0 659 return 'WindowTarget:' + this.window;
michael@0 660 },
michael@0 661 };

mercurial