dom-panel.js 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241
  1. /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
  2. /* This Source Code Form is subject to the terms of the Mozilla Public
  3. * License, v. 2.0. If a copy of the MPL was not distributed with this
  4. * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
  5. "use strict";
  6. const { Cu } = require("chrome");
  7. const defer = require("devtools/shared/defer");
  8. const { ObjectClient } = require("devtools/shared/client/main");
  9. const promise = require("promise");
  10. const EventEmitter = require("devtools/shared/event-emitter");
  11. const { Task } = require("devtools/shared/task");
  12. /**
  13. * This object represents DOM panel. It's responsibility is to
  14. * render Document Object Model of the current debugger target.
  15. */
  16. function DomPanel(iframeWindow, toolbox) {
  17. this.panelWin = iframeWindow;
  18. this._toolbox = toolbox;
  19. this.onTabNavigated = this.onTabNavigated.bind(this);
  20. this.onContentMessage = this.onContentMessage.bind(this);
  21. this.onPanelVisibilityChange = this.onPanelVisibilityChange.bind(this);
  22. this.pendingRequests = new Map();
  23. EventEmitter.decorate(this);
  24. }
  25. DomPanel.prototype = {
  26. /**
  27. * Open is effectively an asynchronous constructor.
  28. *
  29. * @return object
  30. * A promise that is resolved when the DOM panel completes opening.
  31. */
  32. open: Task.async(function* () {
  33. if (this._opening) {
  34. return this._opening;
  35. }
  36. let deferred = promise.defer();
  37. this._opening = deferred.promise;
  38. // Local monitoring needs to make the target remote.
  39. if (!this.target.isRemote) {
  40. yield this.target.makeRemote();
  41. }
  42. this.initialize();
  43. this.isReady = true;
  44. this.emit("ready");
  45. deferred.resolve(this);
  46. return this._opening;
  47. }),
  48. // Initialization
  49. initialize: function () {
  50. this.panelWin.addEventListener("devtools/content/message",
  51. this.onContentMessage, true);
  52. this.target.on("navigate", this.onTabNavigated);
  53. this._toolbox.on("select", this.onPanelVisibilityChange);
  54. let provider = {
  55. getPrototypeAndProperties: this.getPrototypeAndProperties.bind(this)
  56. };
  57. exportIntoContentScope(this.panelWin, provider, "DomProvider");
  58. this.shouldRefresh = true;
  59. },
  60. destroy: Task.async(function* () {
  61. if (this._destroying) {
  62. return this._destroying;
  63. }
  64. let deferred = promise.defer();
  65. this._destroying = deferred.promise;
  66. this.target.off("navigate", this.onTabNavigated);
  67. this._toolbox.off("select", this.onPanelVisibilityChange);
  68. this.emit("destroyed");
  69. deferred.resolve();
  70. return this._destroying;
  71. }),
  72. // Events
  73. refresh: function () {
  74. // Do not refresh if the panel isn't visible.
  75. if (!this.isPanelVisible()) {
  76. return;
  77. }
  78. // Do not refresh if it isn't necessary.
  79. if (!this.shouldRefresh) {
  80. return;
  81. }
  82. // Alright reset the flag we are about to refresh the panel.
  83. this.shouldRefresh = false;
  84. this.getRootGrip().then(rootGrip => {
  85. this.postContentMessage("initialize", rootGrip);
  86. });
  87. },
  88. /**
  89. * Make sure the panel is refreshed when the page is reloaded.
  90. * The panel is refreshed immediatelly if it's currently selected
  91. * or lazily when the user actually selects it.
  92. */
  93. onTabNavigated: function () {
  94. this.shouldRefresh = true;
  95. this.refresh();
  96. },
  97. /**
  98. * Make sure the panel is refreshed (if needed) when it's selected.
  99. */
  100. onPanelVisibilityChange: function () {
  101. this.refresh();
  102. },
  103. // Helpers
  104. /**
  105. * Return true if the DOM panel is currently selected.
  106. */
  107. isPanelVisible: function () {
  108. return this._toolbox.currentToolId === "dom";
  109. },
  110. getPrototypeAndProperties: function (grip) {
  111. let deferred = defer();
  112. if (!grip.actor) {
  113. console.error("No actor!", grip);
  114. deferred.reject(new Error("Failed to get actor from grip."));
  115. return deferred.promise;
  116. }
  117. // Bail out if target doesn't exist (toolbox maybe closed already).
  118. if (!this.target) {
  119. return deferred.promise;
  120. }
  121. // If a request for the grips is already in progress
  122. // use the same promise.
  123. let request = this.pendingRequests.get(grip.actor);
  124. if (request) {
  125. return request;
  126. }
  127. let client = new ObjectClient(this.target.client, grip);
  128. client.getPrototypeAndProperties(response => {
  129. this.pendingRequests.delete(grip.actor, deferred.promise);
  130. deferred.resolve(response);
  131. // Fire an event about not having any pending requests.
  132. if (!this.pendingRequests.size) {
  133. this.emit("no-pending-requests");
  134. }
  135. });
  136. this.pendingRequests.set(grip.actor, deferred.promise);
  137. return deferred.promise;
  138. },
  139. getRootGrip: function () {
  140. let deferred = defer();
  141. // Attach Console. It might involve RDP communication, so wait
  142. // asynchronously for the result
  143. this.target.activeConsole.evaluateJSAsync("window", res => {
  144. deferred.resolve(res.result);
  145. });
  146. return deferred.promise;
  147. },
  148. postContentMessage: function (type, args) {
  149. let data = {
  150. type: type,
  151. args: args,
  152. };
  153. let event = new this.panelWin.MessageEvent("devtools/chrome/message", {
  154. bubbles: true,
  155. cancelable: true,
  156. data: data,
  157. });
  158. this.panelWin.dispatchEvent(event);
  159. },
  160. onContentMessage: function (event) {
  161. let data = event.data;
  162. let method = data.type;
  163. if (typeof this[method] == "function") {
  164. this[method](data.args);
  165. }
  166. },
  167. get target() {
  168. return this._toolbox.target;
  169. },
  170. };
  171. // Helpers
  172. function exportIntoContentScope(win, obj, defineAs) {
  173. let clone = Cu.createObjectIn(win, {
  174. defineAs: defineAs
  175. });
  176. let props = Object.getOwnPropertyNames(obj);
  177. for (let i = 0; i < props.length; i++) {
  178. let propName = props[i];
  179. let propValue = obj[propName];
  180. if (typeof propValue == "function") {
  181. Cu.exportFunction(propValue, clone, {
  182. defineAs: propName
  183. });
  184. }
  185. }
  186. }
  187. // Exports from this module
  188. exports.DomPanel = DomPanel;