this._destroyed = true; this._commands.targetCommand.unwatchTargets({ types: [this._commands.targetCommand.TYPES.FRAME], onAvailable: this._onTargetAvailable, onSelected: this._onTargetSelected, }); this._commands.resourceCommand.unwatchResources( [this._commands.resourceCommand.TYPES.DOCUMENT_EVENT], { onAvailable: this.onResourceAvailable } ); this._toolbox.off("select", this.onPanelVisibilityChange); this.emit("destroyed"); } // Events refresh() { // 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, either when navigation occurs or when a frame is * selected in the iframe picker. * The panel is refreshed immediately if it's currently selected or lazily when the user * actually selects it. */ forceRefresh() { this.shouldRefresh = true; // This will end up calling scriptCommand execute method to retrieve the `window` grip // on targetCommand.selectedTargetFront. this.refresh(); } _onTargetSelected() { this.forceRefresh(); } onResourceAvailable(resources) { for (const resource of resources) { // Only consider top level document, and ignore remote iframes top document if ( resource.resourceType === this._commands.resourceCommand.TYPES.DOCUMENT_EVENT && resource.name === "dom-complete" && resource.targetFront.isTopLevel ) { this.forceRefresh(); } } } /** * Make sure the panel is refreshed (if needed) when it's selected. */ onPanelVisibilityChange() { this.refresh(); } // Helpers /** * Return true if the DOM panel is currently selected. */ isPanelVisible() { return this._toolbox.currentToolId === "dom"; } async getPrototypeAndProperties(objectFront) { if (!objectFront.actorID) { console.error("No actor!", objectFront); throw new Error("Failed to get object front."); } // Bail out if target doesn't exist (toolbox maybe closed already). if (!this.currentTarget) { return null; } // Check for a previously stored request for grip. let request = this.pendingRequests.get(objectFront.actorID); // If no request is in progress create a new one. if (!request) { request = objectFront.getPrototypeAndProperties(); this.pendingRequests.set(objectFront.actorID, request); } const response = await request; this.pendingRequests.delete(objectFront.actorID); // Fire an event about not having any pending requests. if (!this.pendingRequests.size) { this.emit("no-pending-requests"); } return response; } openLink(url) { openContentLink(url); } async getRootGrip() { const { result } = await this._toolbox.commands.scriptCommand.execute( "window", { disableBreaks: true, } ); return result; } postContentMessage(type, args) { const data = { type, args, }; const event = new this.panelWin.MessageEvent("devtools/chrome/message", { bubbles: true, cancelable: true, data, }); this.panelWin.dispatchEvent(event); } onContentMessage(event) { const data = event.data; const method = data.type; if (typeof this[method] == "function") { this[method](data.args); } } getToolbox() { return this._toolbox; } get currentTarget() { return this._toolbox.target; } } // Helpers function exportIntoContentScope(win, obj, defineAs) { const clone = Cu.createObjectIn(win, { defineAs, }); const props = Object.getOwnPropertyNames(obj); for (let i = 0; i < props.length; i++) { const propName = props[i]; const propValue = obj[propName]; if (typeof propValue == "function") { Cu.exportFunction(propValue, clone, { defineAs: propName, }); } } } // Exports from this module exports.DomPanel = DomPanel; PK