123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241 |
- /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
- /* 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;
|