summaryrefslogtreecommitdiffstats
path: root/devtools/server/actors/descriptors/tab.js
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/server/actors/descriptors/tab.js')
-rw-r--r--devtools/server/actors/descriptors/tab.js253
1 files changed, 253 insertions, 0 deletions
diff --git a/devtools/server/actors/descriptors/tab.js b/devtools/server/actors/descriptors/tab.js
new file mode 100644
index 0000000000..ea20d3fb36
--- /dev/null
+++ b/devtools/server/actors/descriptors/tab.js
@@ -0,0 +1,253 @@
+/* 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";
+
+/*
+ * Descriptor Actor that represents a Tab in the parent process. It
+ * launches a WindowGlobalTargetActor in the content process to do the real work and tunnels the
+ * data.
+ *
+ * See devtools/docs/backend/actor-hierarchy.md for more details.
+ */
+
+const { Actor } = require("resource://devtools/shared/protocol.js");
+const {
+ tabDescriptorSpec,
+} = require("resource://devtools/shared/specs/descriptors/tab.js");
+
+const {
+ connectToFrame,
+} = require("resource://devtools/server/connectors/frame-connector.js");
+const lazy = {};
+ChromeUtils.defineESModuleGetters(lazy, {
+ PlacesUtils: "resource://gre/modules/PlacesUtils.sys.mjs",
+});
+
+const { AppConstants } = ChromeUtils.importESModule(
+ "resource://gre/modules/AppConstants.sys.mjs"
+);
+const {
+ createBrowserElementSessionContext,
+} = require("resource://devtools/server/actors/watcher/session-context.js");
+
+loader.lazyRequireGetter(
+ this,
+ "WatcherActor",
+ "resource://devtools/server/actors/watcher.js",
+ true
+);
+
+/**
+ * Creates a target actor proxy for handling requests to a single browser frame.
+ * Both <xul:browser> and <iframe mozbrowser> are supported.
+ * This actor is a shim that connects to a WindowGlobalTargetActor in a remote browser process.
+ * All RDP packets get forwarded using the message manager.
+ *
+ * @param connection The main RDP connection.
+ * @param browser <xul:browser> or <iframe mozbrowser> element to connect to.
+ */
+class TabDescriptorActor extends Actor {
+ constructor(connection, browser) {
+ super(connection, tabDescriptorSpec);
+ this._browser = browser;
+ }
+
+ form() {
+ const form = {
+ actor: this.actorID,
+ browserId: this._browser.browserId,
+ browsingContextID:
+ this._browser && this._browser.browsingContext
+ ? this._browser.browsingContext.id
+ : null,
+ isZombieTab: this._isZombieTab(),
+ outerWindowID: this._getOuterWindowId(),
+ selected: this.selected,
+ title: this._getTitle(),
+ traits: {
+ // Supports the Watcher actor. Can be removed as part of Bug 1680280.
+ watcher: true,
+ supportsReloadDescriptor: true,
+ },
+ url: this._getUrl(),
+ };
+
+ return form;
+ }
+
+ _getTitle() {
+ // If the content already provides a title, use it.
+ if (this._browser.contentTitle) {
+ return this._browser.contentTitle;
+ }
+
+ // For zombie or lazy tabs (tab created, but content has not been loaded),
+ // try to retrieve the title from the XUL Tab itself.
+ // Note: this only works on Firefox desktop.
+ if (this._tabbrowser) {
+ const tab = this._tabbrowser.getTabForBrowser(this._browser);
+ if (tab) {
+ return tab.label;
+ }
+ }
+
+ // No title available.
+ return null;
+ }
+
+ _getUrl() {
+ if (!this._browser || !this._browser.browsingContext) {
+ return "";
+ }
+
+ const { browsingContext } = this._browser;
+ return browsingContext.currentWindowGlobal.documentURI.spec;
+ }
+
+ _getOuterWindowId() {
+ if (!this._browser || !this._browser.browsingContext) {
+ return "";
+ }
+
+ const { browsingContext } = this._browser;
+ return browsingContext.currentWindowGlobal.outerWindowId;
+ }
+
+ get selected() {
+ // getMostRecentBrowserWindow will find the appropriate window on Firefox
+ // Desktop and on GeckoView.
+ const topAppWindow = Services.wm.getMostRecentBrowserWindow();
+
+ const selectedBrowser = topAppWindow?.gBrowser?.selectedBrowser;
+ if (!selectedBrowser) {
+ // Note: gBrowser is not available on GeckoView.
+ // We should find another way to know if this browser is the selected
+ // browser. See Bug 1631020.
+ return false;
+ }
+
+ return this._browser === selectedBrowser;
+ }
+
+ async getTarget() {
+ if (!this.conn) {
+ return {
+ error: "tabDestroyed",
+ message: "Tab destroyed while performing a TabDescriptorActor update",
+ };
+ }
+
+ /* eslint-disable-next-line no-async-promise-executor */
+ return new Promise(async (resolve, reject) => {
+ const onDestroy = () => {
+ // Reject the update promise if the tab was destroyed while requesting an update
+ reject({
+ error: "tabDestroyed",
+ message: "Tab destroyed while performing a TabDescriptorActor update",
+ });
+
+ // Targets created from the TabDescriptor are not created via JSWindowActors and
+ // we need to notify the watcher manually about their destruction.
+ // TabDescriptor's targets are created via TabDescriptor.getTarget and are still using
+ // message manager instead of JSWindowActors.
+ if (this.watcher && this.targetActorForm) {
+ this.watcher.notifyTargetDestroyed(this.targetActorForm);
+ }
+ };
+
+ try {
+ // Check if the browser is still connected before calling connectToFrame
+ if (!this._browser.isConnected) {
+ onDestroy();
+ return;
+ }
+
+ const connectForm = await connectToFrame(
+ this.conn,
+ this._browser,
+ onDestroy
+ );
+ this.targetActorForm = connectForm;
+ resolve(connectForm);
+ } catch (e) {
+ reject({
+ error: "tabDestroyed",
+ message: "Tab destroyed while connecting to the frame",
+ });
+ }
+ });
+ }
+
+ /**
+ * Return a Watcher actor, allowing to keep track of targets which
+ * already exists or will be created. It also helps knowing when they
+ * are destroyed.
+ */
+ getWatcher(config) {
+ if (!this.watcher) {
+ this.watcher = new WatcherActor(
+ this.conn,
+ createBrowserElementSessionContext(this._browser, {
+ isServerTargetSwitchingEnabled: config.isServerTargetSwitchingEnabled,
+ isPopupDebuggingEnabled: config.isPopupDebuggingEnabled,
+ })
+ );
+ this.manage(this.watcher);
+ }
+ return this.watcher;
+ }
+
+ get _tabbrowser() {
+ if (this._browser && typeof this._browser.getTabBrowser == "function") {
+ return this._browser.getTabBrowser();
+ }
+ return null;
+ }
+
+ async getFavicon() {
+ if (!AppConstants.MOZ_PLACES) {
+ // PlacesUtils is not supported
+ return null;
+ }
+
+ try {
+ const { data } = await lazy.PlacesUtils.promiseFaviconData(
+ this._getUrl()
+ );
+ return data;
+ } catch (e) {
+ // Favicon unavailable for this url.
+ return null;
+ }
+ }
+
+ _isZombieTab() {
+ // Note: GeckoView doesn't support zombie tabs
+ const tabbrowser = this._tabbrowser;
+ const tab = tabbrowser ? tabbrowser.getTabForBrowser(this._browser) : null;
+ return tab?.hasAttribute && tab.hasAttribute("pending");
+ }
+
+ reloadDescriptor({ bypassCache }) {
+ if (!this._browser || !this._browser.browsingContext) {
+ return;
+ }
+
+ this._browser.browsingContext.reload(
+ bypassCache
+ ? Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE
+ : Ci.nsIWebNavigation.LOAD_FLAGS_NONE
+ );
+ }
+
+ destroy() {
+ this.emit("descriptor-destroyed");
+ this._browser = null;
+
+ super.destroy();
+ }
+}
+
+exports.TabDescriptorActor = TabDescriptorActor;