target.js 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826
  1. /* This Source Code Form is subject to the terms of the Mozilla Public
  2. * License, v. 2.0. If a copy of the MPL was not distributed with this
  3. * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
  4. "use strict";
  5. const { Ci } = require("chrome");
  6. const promise = require("promise");
  7. const defer = require("devtools/shared/defer");
  8. const EventEmitter = require("devtools/shared/event-emitter");
  9. const Services = require("Services");
  10. const { XPCOMUtils } = require("resource://gre/modules/XPCOMUtils.jsm");
  11. loader.lazyRequireGetter(this, "DebuggerServer", "devtools/server/main", true);
  12. loader.lazyRequireGetter(this, "DebuggerClient",
  13. "devtools/shared/client/main", true);
  14. loader.lazyRequireGetter(this, "gDevTools",
  15. "devtools/client/framework/devtools", true);
  16. const targets = new WeakMap();
  17. const promiseTargets = new WeakMap();
  18. /**
  19. * Functions for creating Targets
  20. */
  21. const TargetFactory = exports.TargetFactory = {
  22. /**
  23. * Construct a Target
  24. * @param {XULTab} tab
  25. * The tab to use in creating a new target.
  26. *
  27. * @return A target object
  28. */
  29. forTab: function (tab) {
  30. let target = targets.get(tab);
  31. if (target == null) {
  32. target = new TabTarget(tab);
  33. targets.set(tab, target);
  34. }
  35. return target;
  36. },
  37. /**
  38. * Return a promise of a Target for a remote tab.
  39. * @param {Object} options
  40. * The options object has the following properties:
  41. * {
  42. * form: the remote protocol form of a tab,
  43. * client: a DebuggerClient instance
  44. * (caller owns this and is responsible for closing),
  45. * chrome: true if the remote target is the whole process
  46. * }
  47. *
  48. * @return A promise of a target object
  49. */
  50. forRemoteTab: function (options) {
  51. let targetPromise = promiseTargets.get(options);
  52. if (targetPromise == null) {
  53. let target = new TabTarget(options);
  54. targetPromise = target.makeRemote().then(() => target);
  55. promiseTargets.set(options, targetPromise);
  56. }
  57. return targetPromise;
  58. },
  59. forWorker: function (workerClient) {
  60. let target = targets.get(workerClient);
  61. if (target == null) {
  62. target = new WorkerTarget(workerClient);
  63. targets.set(workerClient, target);
  64. }
  65. return target;
  66. },
  67. /**
  68. * Creating a target for a tab that is being closed is a problem because it
  69. * allows a leak as a result of coming after the close event which normally
  70. * clears things up. This function allows us to ask if there is a known
  71. * target for a tab without creating a target
  72. * @return true/false
  73. */
  74. isKnownTab: function (tab) {
  75. return targets.has(tab);
  76. },
  77. };
  78. /**
  79. * A Target represents something that we can debug. Targets are generally
  80. * read-only. Any changes that you wish to make to a target should be done via
  81. * a Tool that attaches to the target. i.e. a Target is just a pointer saying
  82. * "the thing to debug is over there".
  83. *
  84. * Providing a generalized abstraction of a web-page or web-browser (available
  85. * either locally or remotely) is beyond the scope of this class (and maybe
  86. * also beyond the scope of this universe) However Target does attempt to
  87. * abstract some common events and read-only properties common to many Tools.
  88. *
  89. * Supported read-only properties:
  90. * - name, isRemote, url
  91. *
  92. * Target extends EventEmitter and provides support for the following events:
  93. * - close: The target window has been closed. All tools attached to this
  94. * target should close. This event is not currently cancelable.
  95. * - navigate: The target window has navigated to a different URL
  96. *
  97. * Optional events:
  98. * - will-navigate: The target window will navigate to a different URL
  99. * - hidden: The target is not visible anymore (for TargetTab, another tab is
  100. * selected)
  101. * - visible: The target is visible (for TargetTab, tab is selected)
  102. *
  103. * Comparing Targets: 2 instances of a Target object can point at the same
  104. * thing, so t1 !== t2 and t1 != t2 even when they represent the same object.
  105. * To compare to targets use 't1.equals(t2)'.
  106. */
  107. /**
  108. * A TabTarget represents a page living in a browser tab. Generally these will
  109. * be web pages served over http(s), but they don't have to be.
  110. */
  111. function TabTarget(tab) {
  112. EventEmitter.decorate(this);
  113. this.destroy = this.destroy.bind(this);
  114. this.activeTab = this.activeConsole = null;
  115. // Only real tabs need initialization here. Placeholder objects for remote
  116. // targets will be initialized after a makeRemote method call.
  117. if (tab && !["client", "form", "chrome"].every(tab.hasOwnProperty, tab)) {
  118. this._tab = tab;
  119. this._setupListeners();
  120. } else {
  121. this._form = tab.form;
  122. this._url = this._form.url;
  123. this._title = this._form.title;
  124. this._client = tab.client;
  125. this._chrome = tab.chrome;
  126. }
  127. // Default isTabActor to true if not explicitly specified
  128. if (typeof tab.isTabActor == "boolean") {
  129. this._isTabActor = tab.isTabActor;
  130. } else {
  131. this._isTabActor = true;
  132. }
  133. }
  134. TabTarget.prototype = {
  135. _webProgressListener: null,
  136. /**
  137. * Returns a promise for the protocol description from the root actor. Used
  138. * internally with `target.actorHasMethod`. Takes advantage of caching if
  139. * definition was fetched previously with the corresponding actor information.
  140. * Actors are lazily loaded, so not only must the tool using a specific actor
  141. * be in use, the actors are only registered after invoking a method (for
  142. * performance reasons, added in bug 988237), so to use these actor detection
  143. * methods, one must already be communicating with a specific actor of that
  144. * type.
  145. *
  146. * Must be a remote target.
  147. *
  148. * @return {Promise}
  149. * {
  150. * "category": "actor",
  151. * "typeName": "longstractor",
  152. * "methods": [{
  153. * "name": "substring",
  154. * "request": {
  155. * "type": "substring",
  156. * "start": {
  157. * "_arg": 0,
  158. * "type": "primitive"
  159. * },
  160. * "end": {
  161. * "_arg": 1,
  162. * "type": "primitive"
  163. * }
  164. * },
  165. * "response": {
  166. * "substring": {
  167. * "_retval": "primitive"
  168. * }
  169. * }
  170. * }],
  171. * "events": {}
  172. * }
  173. */
  174. getActorDescription: function (actorName) {
  175. if (!this.client) {
  176. throw new Error("TabTarget#getActorDescription() can only be called on " +
  177. "remote tabs.");
  178. }
  179. let deferred = defer();
  180. if (this._protocolDescription &&
  181. this._protocolDescription.types[actorName]) {
  182. deferred.resolve(this._protocolDescription.types[actorName]);
  183. } else {
  184. this.client.mainRoot.protocolDescription(description => {
  185. this._protocolDescription = description;
  186. deferred.resolve(description.types[actorName]);
  187. });
  188. }
  189. return deferred.promise;
  190. },
  191. /**
  192. * Returns a boolean indicating whether or not the specific actor
  193. * type exists. Must be a remote target.
  194. *
  195. * @param {String} actorName
  196. * @return {Boolean}
  197. */
  198. hasActor: function (actorName) {
  199. if (!this.client) {
  200. throw new Error("TabTarget#hasActor() can only be called on remote " +
  201. "tabs.");
  202. }
  203. if (this.form) {
  204. return !!this.form[actorName + "Actor"];
  205. }
  206. return false;
  207. },
  208. /**
  209. * Queries the protocol description to see if an actor has
  210. * an available method. The actor must already be lazily-loaded (read
  211. * the restrictions in the `getActorDescription` comments),
  212. * so this is for use inside of tool. Returns a promise that
  213. * resolves to a boolean. Must be a remote target.
  214. *
  215. * @param {String} actorName
  216. * @param {String} methodName
  217. * @return {Promise}
  218. */
  219. actorHasMethod: function (actorName, methodName) {
  220. if (!this.client) {
  221. throw new Error("TabTarget#actorHasMethod() can only be called on " +
  222. "remote tabs.");
  223. }
  224. return this.getActorDescription(actorName).then(desc => {
  225. if (desc && desc.methods) {
  226. return !!desc.methods.find(method => method.name === methodName);
  227. }
  228. return false;
  229. });
  230. },
  231. /**
  232. * Returns a trait from the root actor.
  233. *
  234. * @param {String} traitName
  235. * @return {Mixed}
  236. */
  237. getTrait: function (traitName) {
  238. if (!this.client) {
  239. throw new Error("TabTarget#getTrait() can only be called on remote " +
  240. "tabs.");
  241. }
  242. // If the targeted actor exposes traits and has a defined value for this
  243. // traits, override the root actor traits
  244. if (this.form.traits && traitName in this.form.traits) {
  245. return this.form.traits[traitName];
  246. }
  247. return this.client.traits[traitName];
  248. },
  249. get tab() {
  250. return this._tab;
  251. },
  252. get form() {
  253. return this._form;
  254. },
  255. // Get a promise of the root form returned by a listTabs request. This promise
  256. // is cached.
  257. get root() {
  258. if (!this._root) {
  259. this._root = this._getRoot();
  260. }
  261. return this._root;
  262. },
  263. _getRoot: function () {
  264. return new Promise((resolve, reject) => {
  265. this.client.listTabs(response => {
  266. if (response.error) {
  267. reject(new Error(response.error + ": " + response.message));
  268. return;
  269. }
  270. resolve(response);
  271. });
  272. });
  273. },
  274. get client() {
  275. return this._client;
  276. },
  277. // Tells us if we are debugging content document
  278. // or if we are debugging chrome stuff.
  279. // Allows to controls which features are available against
  280. // a chrome or a content document.
  281. get chrome() {
  282. return this._chrome;
  283. },
  284. // Tells us if the related actor implements TabActor interface
  285. // and requires to call `attach` request before being used
  286. // and `detach` during cleanup
  287. get isTabActor() {
  288. return this._isTabActor;
  289. },
  290. get window() {
  291. // XXX - this is a footgun for e10s - there .contentWindow will be null,
  292. // and even though .contentWindowAsCPOW *might* work, it will not work
  293. // in all contexts. Consumers of .window need to be refactored to not
  294. // rely on this.
  295. if (Services.appinfo.processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT) {
  296. console.error("The .window getter on devtools' |target| object isn't " +
  297. "e10s friendly!\n" + Error().stack);
  298. }
  299. // Be extra careful here, since this may be called by HS_getHudByWindow
  300. // during shutdown.
  301. if (this._tab && this._tab.linkedBrowser) {
  302. return this._tab.linkedBrowser.contentWindow;
  303. }
  304. return null;
  305. },
  306. get name() {
  307. if (this.isAddon) {
  308. return this._form.name;
  309. }
  310. return this._title;
  311. },
  312. get url() {
  313. return this._url;
  314. },
  315. get isRemote() {
  316. return !this.isLocalTab;
  317. },
  318. get isAddon() {
  319. return !!(this._form && this._form.actor && (
  320. this._form.actor.match(/conn\d+\.addon\d+/) ||
  321. this._form.actor.match(/conn\d+\.webExtension\d+/)
  322. ));
  323. },
  324. get isWebExtension() {
  325. return !!(this._form && this._form.actor &&
  326. this._form.actor.match(/conn\d+\.webExtension\d+/));
  327. },
  328. get isLocalTab() {
  329. return !!this._tab;
  330. },
  331. get isMultiProcess() {
  332. return !this.window;
  333. },
  334. /**
  335. * Adds remote protocol capabilities to the target, so that it can be used
  336. * for tools that support the Remote Debugging Protocol even for local
  337. * connections.
  338. */
  339. makeRemote: function () {
  340. if (this._remote) {
  341. return this._remote.promise;
  342. }
  343. this._remote = defer();
  344. if (this.isLocalTab) {
  345. // Since a remote protocol connection will be made, let's start the
  346. // DebuggerServer here, once and for all tools.
  347. if (!DebuggerServer.initialized) {
  348. DebuggerServer.init();
  349. DebuggerServer.addBrowserActors();
  350. }
  351. this._client = new DebuggerClient(DebuggerServer.connectPipe());
  352. // A local TabTarget will never perform chrome debugging.
  353. this._chrome = false;
  354. }
  355. this._setupRemoteListeners();
  356. let attachTab = () => {
  357. this._client.attachTab(this._form.actor, (response, tabClient) => {
  358. if (!tabClient) {
  359. this._remote.reject("Unable to attach to the tab");
  360. return;
  361. }
  362. this.activeTab = tabClient;
  363. this.threadActor = response.threadActor;
  364. attachConsole();
  365. });
  366. };
  367. let onConsoleAttached = (response, consoleClient) => {
  368. if (!consoleClient) {
  369. this._remote.reject("Unable to attach to the console");
  370. return;
  371. }
  372. this.activeConsole = consoleClient;
  373. this._remote.resolve(null);
  374. };
  375. let attachConsole = () => {
  376. this._client.attachConsole(this._form.consoleActor,
  377. [ "NetworkActivity" ],
  378. onConsoleAttached);
  379. };
  380. if (this.isLocalTab) {
  381. this._client.connect()
  382. .then(() => this._client.getTab({ tab: this.tab }))
  383. .then(response => {
  384. this._form = response.tab;
  385. this._url = this._form.url;
  386. this._title = this._form.title;
  387. attachTab();
  388. }, e => this._remote.reject(e));
  389. } else if (this.isTabActor) {
  390. // In the remote debugging case, the protocol connection will have been
  391. // already initialized in the connection screen code.
  392. attachTab();
  393. } else {
  394. // AddonActor and chrome debugging on RootActor doesn't inherits from
  395. // TabActor and doesn't need to be attached.
  396. attachConsole();
  397. }
  398. return this._remote.promise;
  399. },
  400. /**
  401. * Listen to the different events.
  402. */
  403. _setupListeners: function () {
  404. this._webProgressListener = new TabWebProgressListener(this);
  405. this.tab.linkedBrowser.addProgressListener(this._webProgressListener);
  406. this.tab.addEventListener("TabClose", this);
  407. this.tab.parentNode.addEventListener("TabSelect", this);
  408. this.tab.ownerDocument.defaultView.addEventListener("unload", this);
  409. this.tab.addEventListener("TabRemotenessChange", this);
  410. },
  411. /**
  412. * Teardown event listeners.
  413. */
  414. _teardownListeners: function () {
  415. if (this._webProgressListener) {
  416. this._webProgressListener.destroy();
  417. }
  418. this._tab.ownerDocument.defaultView.removeEventListener("unload", this);
  419. this._tab.removeEventListener("TabClose", this);
  420. this._tab.parentNode.removeEventListener("TabSelect", this);
  421. this._tab.removeEventListener("TabRemotenessChange", this);
  422. },
  423. /**
  424. * Setup listeners for remote debugging, updating existing ones as necessary.
  425. */
  426. _setupRemoteListeners: function () {
  427. this.client.addListener("closed", this.destroy);
  428. this._onTabDetached = (aType, aPacket) => {
  429. // We have to filter message to ensure that this detach is for this tab
  430. if (aPacket.from == this._form.actor) {
  431. this.destroy();
  432. }
  433. };
  434. this.client.addListener("tabDetached", this._onTabDetached);
  435. this._onTabNavigated = (aType, aPacket) => {
  436. let event = Object.create(null);
  437. event.url = aPacket.url;
  438. event.title = aPacket.title;
  439. event.nativeConsoleAPI = aPacket.nativeConsoleAPI;
  440. event.isFrameSwitching = aPacket.isFrameSwitching;
  441. if (!aPacket.isFrameSwitching) {
  442. // Update the title and url unless this is a frame switch.
  443. this._url = aPacket.url;
  444. this._title = aPacket.title;
  445. }
  446. // Send any stored event payload (DOMWindow or nsIRequest) for backwards
  447. // compatibility with non-remotable tools.
  448. if (aPacket.state == "start") {
  449. event._navPayload = this._navRequest;
  450. this.emit("will-navigate", event);
  451. this._navRequest = null;
  452. } else {
  453. event._navPayload = this._navWindow;
  454. this.emit("navigate", event);
  455. this._navWindow = null;
  456. }
  457. };
  458. this.client.addListener("tabNavigated", this._onTabNavigated);
  459. this._onFrameUpdate = (aType, aPacket) => {
  460. this.emit("frame-update", aPacket);
  461. };
  462. this.client.addListener("frameUpdate", this._onFrameUpdate);
  463. this._onSourceUpdated = (event, packet) => this.emit("source-updated", packet);
  464. this.client.addListener("newSource", this._onSourceUpdated);
  465. this.client.addListener("updatedSource", this._onSourceUpdated);
  466. },
  467. /**
  468. * Teardown listeners for remote debugging.
  469. */
  470. _teardownRemoteListeners: function () {
  471. this.client.removeListener("closed", this.destroy);
  472. this.client.removeListener("tabNavigated", this._onTabNavigated);
  473. this.client.removeListener("tabDetached", this._onTabDetached);
  474. this.client.removeListener("frameUpdate", this._onFrameUpdate);
  475. this.client.removeListener("newSource", this._onSourceUpdated);
  476. this.client.removeListener("updatedSource", this._onSourceUpdated);
  477. },
  478. /**
  479. * Handle tabs events.
  480. */
  481. handleEvent: function (event) {
  482. switch (event.type) {
  483. case "TabClose":
  484. case "unload":
  485. this.destroy();
  486. break;
  487. case "TabSelect":
  488. if (this.tab.selected) {
  489. this.emit("visible", event);
  490. } else {
  491. this.emit("hidden", event);
  492. }
  493. break;
  494. case "TabRemotenessChange":
  495. this.onRemotenessChange();
  496. break;
  497. }
  498. },
  499. // Automatically respawn the toolbox when the tab changes between being
  500. // loaded within the parent process and loaded from a content process.
  501. // Process change can go in both ways.
  502. onRemotenessChange: function () {
  503. // Responsive design do a crazy dance around tabs and triggers
  504. // remotenesschange events. But we should ignore them as at the end
  505. // the content doesn't change its remoteness.
  506. if (this._tab.isResponsiveDesignMode) {
  507. return;
  508. }
  509. // Save a reference to the tab as it will be nullified on destroy
  510. let tab = this._tab;
  511. let onToolboxDestroyed = (event, target) => {
  512. if (target != this) {
  513. return;
  514. }
  515. gDevTools.off("toolbox-destroyed", target);
  516. // Recreate a fresh target instance as the current one is now destroyed
  517. let newTarget = TargetFactory.forTab(tab);
  518. gDevTools.showToolbox(newTarget);
  519. };
  520. gDevTools.on("toolbox-destroyed", onToolboxDestroyed);
  521. },
  522. /**
  523. * Target is not alive anymore.
  524. */
  525. destroy: function () {
  526. // If several things call destroy then we give them all the same
  527. // destruction promise so we're sure to destroy only once
  528. if (this._destroyer) {
  529. return this._destroyer.promise;
  530. }
  531. this._destroyer = defer();
  532. // Before taking any action, notify listeners that destruction is imminent.
  533. this.emit("close");
  534. if (this._tab) {
  535. this._teardownListeners();
  536. }
  537. let cleanupAndResolve = () => {
  538. this._cleanup();
  539. this._destroyer.resolve(null);
  540. };
  541. // If this target was not remoted, the promise will be resolved before the
  542. // function returns.
  543. if (this._tab && !this._client) {
  544. cleanupAndResolve();
  545. } else if (this._client) {
  546. // If, on the other hand, this target was remoted, the promise will be
  547. // resolved after the remote connection is closed.
  548. this._teardownRemoteListeners();
  549. if (this.isLocalTab) {
  550. // We started with a local tab and created the client ourselves, so we
  551. // should close it.
  552. this._client.close().then(cleanupAndResolve);
  553. } else if (this.activeTab) {
  554. // The client was handed to us, so we are not responsible for closing
  555. // it. We just need to detach from the tab, if already attached.
  556. // |detach| may fail if the connection is already dead, so proceed with
  557. // cleanup directly after this.
  558. this.activeTab.detach();
  559. cleanupAndResolve();
  560. } else {
  561. cleanupAndResolve();
  562. }
  563. }
  564. return this._destroyer.promise;
  565. },
  566. /**
  567. * Clean up references to what this target points to.
  568. */
  569. _cleanup: function () {
  570. if (this._tab) {
  571. targets.delete(this._tab);
  572. } else {
  573. promiseTargets.delete(this._form);
  574. }
  575. this.activeTab = null;
  576. this.activeConsole = null;
  577. this._client = null;
  578. this._tab = null;
  579. this._form = null;
  580. this._remote = null;
  581. this._root = null;
  582. this._title = null;
  583. this._url = null;
  584. this.threadActor = null;
  585. },
  586. toString: function () {
  587. let id = this._tab ? this._tab : (this._form && this._form.actor);
  588. return `TabTarget:${id}`;
  589. },
  590. /**
  591. * @see TabActor.prototype.onResolveLocation
  592. */
  593. resolveLocation(loc) {
  594. let deferred = defer();
  595. this.client.request(Object.assign({
  596. to: this._form.actor,
  597. type: "resolveLocation",
  598. }, loc), deferred.resolve);
  599. return deferred.promise;
  600. },
  601. };
  602. /**
  603. * WebProgressListener for TabTarget.
  604. *
  605. * @param object aTarget
  606. * The TabTarget instance to work with.
  607. */
  608. function TabWebProgressListener(aTarget) {
  609. this.target = aTarget;
  610. }
  611. TabWebProgressListener.prototype = {
  612. target: null,
  613. QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener,
  614. Ci.nsISupportsWeakReference]),
  615. onStateChange: function (progress, request, flag) {
  616. let isStart = flag & Ci.nsIWebProgressListener.STATE_START;
  617. let isDocument = flag & Ci.nsIWebProgressListener.STATE_IS_DOCUMENT;
  618. let isNetwork = flag & Ci.nsIWebProgressListener.STATE_IS_NETWORK;
  619. let isRequest = flag & Ci.nsIWebProgressListener.STATE_IS_REQUEST;
  620. // Skip non-interesting states.
  621. if (!isStart || !isDocument || !isRequest || !isNetwork) {
  622. return;
  623. }
  624. // emit event if the top frame is navigating
  625. if (progress.isTopLevel) {
  626. // Emit the event if the target is not remoted or store the payload for
  627. // later emission otherwise.
  628. if (this.target._client) {
  629. this.target._navRequest = request;
  630. } else {
  631. this.target.emit("will-navigate", request);
  632. }
  633. }
  634. },
  635. onProgressChange: function () {},
  636. onSecurityChange: function () {},
  637. onStatusChange: function () {},
  638. onLocationChange: function (webProgress, request, URI, flags) {
  639. if (this.target &&
  640. !(flags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT)) {
  641. let window = webProgress.DOMWindow;
  642. // Emit the event if the target is not remoted or store the payload for
  643. // later emission otherwise.
  644. if (this.target._client) {
  645. this.target._navWindow = window;
  646. } else {
  647. this.target.emit("navigate", window);
  648. }
  649. }
  650. },
  651. /**
  652. * Destroy the progress listener instance.
  653. */
  654. destroy: function () {
  655. if (this.target.tab) {
  656. try {
  657. this.target.tab.linkedBrowser.removeProgressListener(this);
  658. } catch (ex) {
  659. // This can throw when a tab crashes in e10s.
  660. }
  661. }
  662. this.target._webProgressListener = null;
  663. this.target._navRequest = null;
  664. this.target._navWindow = null;
  665. this.target = null;
  666. }
  667. };
  668. function WorkerTarget(workerClient) {
  669. EventEmitter.decorate(this);
  670. this._workerClient = workerClient;
  671. }
  672. /**
  673. * A WorkerTarget represents a worker. Unlike TabTarget, which can represent
  674. * either a local or remote tab, WorkerTarget always represents a remote worker.
  675. * Moreover, unlike TabTarget, which is constructed with a placeholder object
  676. * for remote tabs (from which a TabClient can then be lazily obtained),
  677. * WorkerTarget is constructed with a WorkerClient directly.
  678. *
  679. * WorkerClient is designed to mimic the interface of TabClient as closely as
  680. * possible. This allows us to debug workers as if they were ordinary tabs,
  681. * requiring only minimal changes to the rest of the frontend.
  682. */
  683. WorkerTarget.prototype = {
  684. get isRemote() {
  685. return true;
  686. },
  687. get isTabActor() {
  688. return true;
  689. },
  690. get name() {
  691. return "Worker";
  692. },
  693. get url() {
  694. return this._workerClient.url;
  695. },
  696. get isWorkerTarget() {
  697. return true;
  698. },
  699. get form() {
  700. return {
  701. consoleActor: this._workerClient.consoleActor
  702. };
  703. },
  704. get activeTab() {
  705. return this._workerClient;
  706. },
  707. get client() {
  708. return this._workerClient.client;
  709. },
  710. destroy: function () {
  711. this._workerClient.detach();
  712. },
  713. hasActor: function (name) {
  714. // console is the only one actor implemented by WorkerActor
  715. if (name == "console") {
  716. return true;
  717. }
  718. return false;
  719. },
  720. getTrait: function () {
  721. return undefined;
  722. },
  723. makeRemote: function () {
  724. return Promise.resolve();
  725. }
  726. };