diff options
Diffstat (limited to 'devtools/client/dom/dom-panel.js')
-rw-r--r-- | devtools/client/dom/dom-panel.js | 241 |
1 files changed, 241 insertions, 0 deletions
diff --git a/devtools/client/dom/dom-panel.js b/devtools/client/dom/dom-panel.js new file mode 100644 index 000000000..5cb6d0061 --- /dev/null +++ b/devtools/client/dom/dom-panel.js @@ -0,0 +1,241 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +"use strict"; + +const { Cu } = require("chrome"); +const defer = require("devtools/shared/defer"); +const { ObjectClient } = require("devtools/shared/client/main"); + +const promise = require("promise"); +const EventEmitter = require("devtools/shared/event-emitter"); +const { Task } = require("devtools/shared/task"); + +/** + * This object represents DOM panel. It's responsibility is to + * render Document Object Model of the current debugger target. + */ +function DomPanel(iframeWindow, toolbox) { + this.panelWin = iframeWindow; + this._toolbox = toolbox; + + this.onTabNavigated = this.onTabNavigated.bind(this); + this.onContentMessage = this.onContentMessage.bind(this); + this.onPanelVisibilityChange = this.onPanelVisibilityChange.bind(this); + + this.pendingRequests = new Map(); + + EventEmitter.decorate(this); +} + +DomPanel.prototype = { + /** + * Open is effectively an asynchronous constructor. + * + * @return object + * A promise that is resolved when the DOM panel completes opening. + */ + open: Task.async(function* () { + if (this._opening) { + return this._opening; + } + + let deferred = promise.defer(); + this._opening = deferred.promise; + + // Local monitoring needs to make the target remote. + if (!this.target.isRemote) { + yield this.target.makeRemote(); + } + + this.initialize(); + + this.isReady = true; + this.emit("ready"); + deferred.resolve(this); + + return this._opening; + }), + + // Initialization + + initialize: function () { + this.panelWin.addEventListener("devtools/content/message", + this.onContentMessage, true); + + this.target.on("navigate", this.onTabNavigated); + this._toolbox.on("select", this.onPanelVisibilityChange); + + let provider = { + getPrototypeAndProperties: this.getPrototypeAndProperties.bind(this) + }; + + exportIntoContentScope(this.panelWin, provider, "DomProvider"); + + this.shouldRefresh = true; + }, + + destroy: Task.async(function* () { + if (this._destroying) { + return this._destroying; + } + + let deferred = promise.defer(); + this._destroying = deferred.promise; + + this.target.off("navigate", this.onTabNavigated); + this._toolbox.off("select", this.onPanelVisibilityChange); + + this.emit("destroyed"); + + deferred.resolve(); + return this._destroying; + }), + + // Events + + refresh: function () { + // Do not refresh if the panel isn't visible. + if (!this.isPanelVisible()) { + return; + } + + // Do not refresh if it isn't necessary. + if (!this.shouldRefresh) { + return; + } + + // Alright reset the flag we are about to refresh the panel. + this.shouldRefresh = false; + + this.getRootGrip().then(rootGrip => { + this.postContentMessage("initialize", rootGrip); + }); + }, + + /** + * Make sure the panel is refreshed when the page is reloaded. + * The panel is refreshed immediatelly if it's currently selected + * or lazily when the user actually selects it. + */ + onTabNavigated: function () { + this.shouldRefresh = true; + this.refresh(); + }, + + /** + * Make sure the panel is refreshed (if needed) when it's selected. + */ + onPanelVisibilityChange: function () { + this.refresh(); + }, + + // Helpers + + /** + * Return true if the DOM panel is currently selected. + */ + isPanelVisible: function () { + return this._toolbox.currentToolId === "dom"; + }, + + getPrototypeAndProperties: function (grip) { + let deferred = defer(); + + if (!grip.actor) { + console.error("No actor!", grip); + deferred.reject(new Error("Failed to get actor from grip.")); + return deferred.promise; + } + + // Bail out if target doesn't exist (toolbox maybe closed already). + if (!this.target) { + return deferred.promise; + } + + // If a request for the grips is already in progress + // use the same promise. + let request = this.pendingRequests.get(grip.actor); + if (request) { + return request; + } + + let client = new ObjectClient(this.target.client, grip); + client.getPrototypeAndProperties(response => { + this.pendingRequests.delete(grip.actor, deferred.promise); + deferred.resolve(response); + + // Fire an event about not having any pending requests. + if (!this.pendingRequests.size) { + this.emit("no-pending-requests"); + } + }); + + this.pendingRequests.set(grip.actor, deferred.promise); + + return deferred.promise; + }, + + getRootGrip: function () { + let deferred = defer(); + + // Attach Console. It might involve RDP communication, so wait + // asynchronously for the result + this.target.activeConsole.evaluateJSAsync("window", res => { + deferred.resolve(res.result); + }); + + return deferred.promise; + }, + + postContentMessage: function (type, args) { + let data = { + type: type, + args: args, + }; + + let event = new this.panelWin.MessageEvent("devtools/chrome/message", { + bubbles: true, + cancelable: true, + data: data, + }); + + this.panelWin.dispatchEvent(event); + }, + + onContentMessage: function (event) { + let data = event.data; + let method = data.type; + if (typeof this[method] == "function") { + this[method](data.args); + } + }, + + get target() { + return this._toolbox.target; + }, +}; + +// Helpers + +function exportIntoContentScope(win, obj, defineAs) { + let clone = Cu.createObjectIn(win, { + defineAs: defineAs + }); + + let props = Object.getOwnPropertyNames(obj); + for (let i = 0; i < props.length; i++) { + let propName = props[i]; + let propValue = obj[propName]; + if (typeof propValue == "function") { + Cu.exportFunction(propValue, clone, { + defineAs: propName + }); + } + } +} + +// Exports from this module +exports.DomPanel = DomPanel; |