/* 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 { windowGlobalTargetSpec, } = require("resource://devtools/shared/specs/targets/window-global.js"); const { FrontClassWithSpec, registerFront, } = require("resource://devtools/shared/protocol.js"); const { TargetMixin, } = require("resource://devtools/client/fronts/targets/target-mixin.js"); class WindowGlobalTargetFront extends TargetMixin( FrontClassWithSpec(windowGlobalTargetSpec) ) { constructor(client, targetFront, parentFront) { super(client, targetFront, parentFront); // For targets which support the Watcher and configuration actor, the status // for the `javascriptEnabled` setting will be available on the configuration // front, and the target will only be used to read the initial value from older // servers. // Note: this property is marked as private but is accessed by the // TargetCommand to provide the "isJavascriptEnabled" wrapper. It should NOT be // used anywhere else. this._javascriptEnabled = null; // If this target was retrieved via NodeFront connectToFrame, keep a // reference to the parent NodeFront. this._parentNodeFront = null; this._onTabNavigated = this._onTabNavigated.bind(this); this._onFrameUpdate = this._onFrameUpdate.bind(this); this.on("tabNavigated", this._onTabNavigated); this.on("frameUpdate", this._onFrameUpdate); } form(json) { this.actorID = json.actor; this.browsingContextID = json.browsingContextID; this.innerWindowId = json.innerWindowId; this.processID = json.processID; // Save the full form for Target class usage. // Do not use `form` name to avoid colliding with protocol.js's `form` method this.targetForm = json; this.outerWindowID = json.outerWindowID; this.favicon = json.favicon; // Initial value for the page title and url. Since the WindowGlobalTargetActor can // be created very early, those might not represent the actual value we'd want to // display for the user (e.g. the might not have been parsed yet, and the // url could still be about:blank, which is what the platform uses at the very start // of a navigation to a new location). // Those values are set again from the targetCommand when receiving DOCUMENT_EVENT // resource, at which point both values should be in their expected form. this.setTitle(json.title); this.setUrl(json.url); } /** * Event listener for `frameUpdate` event. */ _onFrameUpdate(packet) { this.emit("frame-update", packet); } /** * Event listener for `tabNavigated` event. */ _onTabNavigated(packet) { const event = Object.create(null); event.url = packet.url; event.title = packet.title; event.isFrameSwitching = packet.isFrameSwitching; // Keep the title unmodified when a developer toolbox switches frame // for a tab (Bug 1261687), but always update the title when the target // is a WebExtension (where the addon name is always included in the title // and the url is supposed to be updated every time the selected frame changes). if (!packet.isFrameSwitching || this.isWebExtension) { this.setTitle(packet.title); this.setUrl(packet.url); } // Send any stored event payload (DOMWindow or nsIRequest) for backwards // compatibility with non-remotable tools. if (packet.state == "start") { this.emit("will-navigate", event); } else { this.emit("navigate", event); } } getParentNodeFront() { return this._parentNodeFront; } setParentNodeFront(nodeFront) { this._parentNodeFront = nodeFront; } /** * Set the targetFront url. * * @param {string} url */ setUrl(url) { this._url = url; } /** * Set the targetFront title. * * @param {string} title */ setTitle(title) { this._title = title; } async detach() { // When calling this.destroy() at the end of this method, // we will end up calling detach again from TargetMixin.destroy. // Avoid invalid loops and do not try to resolve only once the previous call to detach // is done as it would do async infinite loop that never resolves. if (this._isDetaching) { return; } this._isDetaching = true; // Remove listeners set in constructor this.off("tabNavigated", this._onTabNavigated); this.off("frameUpdate", this._onFrameUpdate); try { await super.detach(); } catch (e) { this.logDetachError(e, "browsing context"); } // Detach will destroy the target actor, but the server won't emit any // target-destroyed-form in such manual, client side destruction. // So that we have to manually destroy the associated front on the client // // If detach was called by TargetFrontMixin.destroy, avoid recalling it from it // as it would do an async infinite loop which would never resolve. if (!this.isDestroyedOrBeingDestroyed()) { this.destroy(); } } destroy() { const promise = super.destroy(); this._parentNodeFront = null; // As detach isn't necessarily called on target's destroy // (it isn't for local tabs), ensure removing listeners set in constructor. this.off("tabNavigated", this._onTabNavigated); this.off("frameUpdate", this._onFrameUpdate); return promise; } } exports.WindowGlobalTargetFront = WindowGlobalTargetFront; registerFront(exports.WindowGlobalTargetFront);