2016-01-05 10:33:31 +03:00
|
|
|
/* 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 promise = require("promise");
|
2018-03-27 16:46:41 +03:00
|
|
|
const EventEmitter = require("devtools/shared/event-emitter");
|
2016-01-05 10:33:31 +03:00
|
|
|
|
2019-09-11 00:36:07 +03:00
|
|
|
loader.lazyRequireGetter(this, "ResponsiveUI", "devtools/client/responsive/ui");
|
2019-08-07 18:24:45 +03:00
|
|
|
loader.lazyRequireGetter(
|
|
|
|
this,
|
|
|
|
"startup",
|
|
|
|
"devtools/client/responsive/utils/window",
|
2019-08-07 18:24:49 +03:00
|
|
|
true
|
2019-08-07 18:24:45 +03:00
|
|
|
);
|
|
|
|
loader.lazyRequireGetter(
|
|
|
|
this,
|
|
|
|
"showNotification",
|
|
|
|
"devtools/client/responsive/utils/notification",
|
|
|
|
true
|
2019-08-07 18:24:49 +03:00
|
|
|
);
|
2019-08-07 18:24:45 +03:00
|
|
|
loader.lazyRequireGetter(this, "l10n", "devtools/client/responsive/utils/l10n");
|
2018-02-09 03:43:23 +03:00
|
|
|
loader.lazyRequireGetter(
|
|
|
|
this,
|
|
|
|
"PriorityLevels",
|
|
|
|
"devtools/client/shared/components/NotificationBox",
|
2018-03-13 03:24:37 +03:00
|
|
|
true
|
|
|
|
);
|
|
|
|
loader.lazyRequireGetter(
|
|
|
|
this,
|
|
|
|
"TargetFactory",
|
|
|
|
"devtools/client/framework/target",
|
|
|
|
true
|
|
|
|
);
|
|
|
|
loader.lazyRequireGetter(
|
|
|
|
this,
|
|
|
|
"gDevTools",
|
|
|
|
"devtools/client/framework/devtools",
|
2019-08-07 18:24:49 +03:00
|
|
|
true
|
|
|
|
);
|
2018-04-23 15:33:39 +03:00
|
|
|
loader.lazyRequireGetter(this, "Telemetry", "devtools/client/shared/telemetry");
|
2017-10-14 04:07:24 +03:00
|
|
|
|
2016-01-05 10:33:31 +03:00
|
|
|
/**
|
|
|
|
* ResponsiveUIManager is the external API for the browser UI, etc. to use when
|
|
|
|
* opening and closing the responsive UI.
|
|
|
|
*/
|
2016-03-04 09:21:19 +03:00
|
|
|
const ResponsiveUIManager = (exports.ResponsiveUIManager = {
|
2018-04-23 15:33:39 +03:00
|
|
|
_telemetry: new Telemetry(),
|
|
|
|
|
2016-01-05 10:33:31 +03:00
|
|
|
activeTabs: new Map(),
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Toggle the responsive UI for a tab.
|
|
|
|
*
|
|
|
|
* @param window
|
|
|
|
* The main browser chrome window.
|
|
|
|
* @param tab
|
|
|
|
* The browser tab.
|
2016-08-13 01:09:29 +03:00
|
|
|
* @param options
|
|
|
|
* Other options associated with toggling. Currently includes:
|
2018-03-13 22:52:34 +03:00
|
|
|
* - `trigger`: String denoting the UI entry point, such as:
|
2018-09-06 14:42:57 +03:00
|
|
|
* - `toolbox`: Toolbox Button
|
2018-03-13 22:52:34 +03:00
|
|
|
* - `menu`: Web Developer menu item
|
|
|
|
* - `shortcut`: Keyboard shortcut
|
2016-01-05 10:33:31 +03:00
|
|
|
* @return Promise
|
2016-01-20 22:57:30 +03:00
|
|
|
* Resolved when the toggling has completed. If the UI has opened,
|
|
|
|
* it is resolved to the ResponsiveUI instance for this tab. If the
|
|
|
|
* the UI has closed, there is no resolution value.
|
2016-01-05 10:33:31 +03:00
|
|
|
*/
|
2018-03-13 22:52:34 +03:00
|
|
|
toggle(window, tab, options = {}) {
|
2018-06-01 13:36:09 +03:00
|
|
|
const action = this.isActiveForTab(tab) ? "close" : "open";
|
|
|
|
const completed = this[action + "IfNeeded"](window, tab, options);
|
2016-04-19 23:58:44 +03:00
|
|
|
completed.catch(console.error);
|
|
|
|
return completed;
|
2016-01-05 10:33:31 +03:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
2016-01-20 22:57:30 +03:00
|
|
|
* Opens the responsive UI, if not already open.
|
2016-01-05 10:33:31 +03:00
|
|
|
*
|
|
|
|
* @param window
|
|
|
|
* The main browser chrome window.
|
|
|
|
* @param tab
|
|
|
|
* The browser tab.
|
2016-08-13 01:09:29 +03:00
|
|
|
* @param options
|
|
|
|
* Other options associated with opening. Currently includes:
|
2018-03-13 22:52:34 +03:00
|
|
|
* - `trigger`: String denoting the UI entry point, such as:
|
2018-09-06 14:42:57 +03:00
|
|
|
* - `toolbox`: Toolbox Button
|
2018-03-13 22:52:34 +03:00
|
|
|
* - `menu`: Web Developer menu item
|
|
|
|
* - `shortcut`: Keyboard shortcut
|
2016-01-20 22:57:30 +03:00
|
|
|
* @return Promise
|
|
|
|
* Resolved to the ResponsiveUI instance for this tab when opening is
|
|
|
|
* complete.
|
2016-01-05 10:33:31 +03:00
|
|
|
*/
|
2018-03-13 22:52:34 +03:00
|
|
|
async openIfNeeded(window, tab, options = {}) {
|
2016-04-19 02:20:22 +03:00
|
|
|
if (!tab.linkedBrowser.isRemoteBrowser) {
|
2018-08-29 16:11:53 +03:00
|
|
|
await this.showRemoteOnlyNotification(window, tab, options);
|
2016-04-19 02:20:22 +03:00
|
|
|
return promise.reject(new Error("RDM only available for remote tabs."));
|
|
|
|
}
|
2016-01-05 10:33:31 +03:00
|
|
|
if (!this.isActiveForTab(tab)) {
|
2016-05-19 13:04:51 +03:00
|
|
|
this.initMenuCheckListenerFor(window);
|
|
|
|
|
2019-09-11 00:36:07 +03:00
|
|
|
const ui = new ResponsiveUI(this, window, tab);
|
2018-09-24 15:27:27 +03:00
|
|
|
this.activeTabs.set(tab, ui);
|
2018-08-29 16:11:53 +03:00
|
|
|
|
|
|
|
// Explicitly not await on telemetry to avoid delaying RDM opening
|
|
|
|
this.recordTelemetryOpen(window, tab, options);
|
|
|
|
|
2018-01-09 19:08:35 +03:00
|
|
|
await this.setMenuCheckFor(tab, window);
|
|
|
|
await ui.inited;
|
2016-01-20 22:57:30 +03:00
|
|
|
this.emit("on", { tab });
|
2016-01-05 10:33:31 +03:00
|
|
|
}
|
2016-05-19 13:04:51 +03:00
|
|
|
|
2016-01-20 22:57:30 +03:00
|
|
|
return this.getResponsiveUIForTab(tab);
|
2018-01-09 19:08:35 +03:00
|
|
|
},
|
2016-01-20 22:57:30 +03:00
|
|
|
|
2018-08-29 16:11:53 +03:00
|
|
|
/**
|
|
|
|
* Record all telemetry probes related to RDM opening.
|
|
|
|
*/
|
|
|
|
async recordTelemetryOpen(window, tab, options) {
|
|
|
|
// Track whether a toolbox was opened before RDM was opened.
|
|
|
|
const isKnownTab = TargetFactory.isKnownTab(tab);
|
|
|
|
let toolbox;
|
|
|
|
if (isKnownTab) {
|
|
|
|
const target = await TargetFactory.forTab(tab);
|
|
|
|
toolbox = gDevTools.getToolbox(target);
|
|
|
|
}
|
|
|
|
const hostType = toolbox ? toolbox.hostType : "none";
|
|
|
|
const hasToolbox = !!toolbox;
|
|
|
|
const tel = this._telemetry;
|
|
|
|
if (hasToolbox) {
|
|
|
|
tel.scalarAdd("devtools.responsive.toolbox_opened_first", 1);
|
|
|
|
}
|
|
|
|
|
2018-09-25 15:57:13 +03:00
|
|
|
tel.recordEvent("activate", "responsive_design", null, {
|
2018-08-29 16:11:53 +03:00
|
|
|
host: hostType,
|
|
|
|
width: Math.ceil(window.outerWidth / 50) * 50,
|
2018-10-19 15:55:39 +03:00
|
|
|
session_id: toolbox ? toolbox.sessionId : -1,
|
2018-08-29 16:11:53 +03:00
|
|
|
});
|
|
|
|
|
|
|
|
// Track opens keyed by the UI entry point used.
|
|
|
|
let { trigger } = options;
|
|
|
|
if (!trigger) {
|
|
|
|
trigger = "unknown";
|
|
|
|
}
|
|
|
|
tel.keyedScalarAdd("devtools.responsive.open_trigger", trigger, 1);
|
|
|
|
},
|
|
|
|
|
2016-01-20 22:57:30 +03:00
|
|
|
/**
|
|
|
|
* Closes the responsive UI, if not already closed.
|
|
|
|
*
|
|
|
|
* @param window
|
|
|
|
* The main browser chrome window.
|
|
|
|
* @param tab
|
|
|
|
* The browser tab.
|
2016-08-13 01:09:29 +03:00
|
|
|
* @param options
|
|
|
|
* Other options associated with closing. Currently includes:
|
2018-03-13 22:52:34 +03:00
|
|
|
* - `trigger`: String denoting the UI entry point, such as:
|
2018-09-06 14:42:57 +03:00
|
|
|
* - `toolbox`: Toolbox Button
|
2018-03-13 22:52:34 +03:00
|
|
|
* - `menu`: Web Developer menu item
|
|
|
|
* - `shortcut`: Keyboard shortcut
|
2016-08-13 01:09:29 +03:00
|
|
|
* - `reason`: String detailing the specific cause for closing
|
2016-01-20 22:57:30 +03:00
|
|
|
* @return Promise
|
|
|
|
* Resolved (with no value) when closing is complete.
|
|
|
|
*/
|
2018-03-13 22:52:34 +03:00
|
|
|
async closeIfNeeded(window, tab, options = {}) {
|
2016-01-20 22:57:30 +03:00
|
|
|
if (this.isActiveForTab(tab)) {
|
2018-04-23 15:50:53 +03:00
|
|
|
const ui = this.activeTabs.get(tab);
|
|
|
|
const destroyed = await ui.destroy(options);
|
2016-04-19 02:20:22 +03:00
|
|
|
if (!destroyed) {
|
|
|
|
// Already in the process of destroying, abort.
|
|
|
|
return;
|
|
|
|
}
|
2018-04-23 15:50:53 +03:00
|
|
|
|
2016-01-20 22:57:30 +03:00
|
|
|
this.activeTabs.delete(tab);
|
2016-05-19 13:04:51 +03:00
|
|
|
|
|
|
|
if (!this.isActiveForWindow(window)) {
|
|
|
|
this.removeMenuCheckListenerFor(window);
|
2016-04-24 15:54:08 +03:00
|
|
|
}
|
2016-01-20 22:57:30 +03:00
|
|
|
this.emit("off", { tab });
|
2018-01-09 19:08:35 +03:00
|
|
|
await this.setMenuCheckFor(tab, window);
|
2018-08-29 16:11:53 +03:00
|
|
|
|
|
|
|
// Explicitly not await on telemetry to avoid delaying RDM closing
|
|
|
|
this.recordTelemetryClose(window, tab);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
async recordTelemetryClose(window, tab) {
|
|
|
|
const isKnownTab = TargetFactory.isKnownTab(tab);
|
|
|
|
let toolbox;
|
|
|
|
if (isKnownTab) {
|
|
|
|
const target = await TargetFactory.forTab(tab);
|
|
|
|
toolbox = gDevTools.getToolbox(target);
|
2016-01-20 22:57:30 +03:00
|
|
|
}
|
2018-08-29 16:11:53 +03:00
|
|
|
|
|
|
|
const hostType = toolbox ? toolbox.hostType : "none";
|
|
|
|
const t = this._telemetry;
|
2018-09-25 15:57:13 +03:00
|
|
|
t.recordEvent("deactivate", "responsive_design", null, {
|
2018-08-29 16:11:53 +03:00
|
|
|
host: hostType,
|
|
|
|
width: Math.ceil(window.outerWidth / 50) * 50,
|
2018-10-19 15:55:39 +03:00
|
|
|
session_id: toolbox ? toolbox.sessionId : -1,
|
2018-08-29 16:11:53 +03:00
|
|
|
});
|
2018-01-09 19:08:35 +03:00
|
|
|
},
|
2016-01-05 10:33:31 +03:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns true if responsive UI is active for a given tab.
|
|
|
|
*
|
|
|
|
* @param tab
|
|
|
|
* The browser tab.
|
|
|
|
* @return boolean
|
|
|
|
*/
|
|
|
|
isActiveForTab(tab) {
|
|
|
|
return this.activeTabs.has(tab);
|
|
|
|
},
|
|
|
|
|
2016-05-19 13:04:51 +03:00
|
|
|
/**
|
|
|
|
* Returns true if responsive UI is active in any tab in the given window.
|
|
|
|
*
|
|
|
|
* @param window
|
|
|
|
* The main browser chrome window.
|
|
|
|
* @return boolean
|
|
|
|
*/
|
|
|
|
isActiveForWindow(window) {
|
2017-04-07 05:42:21 +03:00
|
|
|
return [...this.activeTabs.keys()].some(t => t.ownerGlobal === window);
|
2016-05-19 13:04:51 +03:00
|
|
|
},
|
|
|
|
|
2016-01-05 10:33:31 +03:00
|
|
|
/**
|
|
|
|
* Return the responsive UI controller for a tab.
|
|
|
|
*
|
|
|
|
* @param tab
|
|
|
|
* The browser tab.
|
2016-01-23 00:11:10 +03:00
|
|
|
* @return ResponsiveUI
|
|
|
|
* The UI instance for this tab.
|
2016-01-05 10:33:31 +03:00
|
|
|
*/
|
|
|
|
getResponsiveUIForTab(tab) {
|
|
|
|
return this.activeTabs.get(tab);
|
|
|
|
},
|
|
|
|
|
2016-05-19 13:04:51 +03:00
|
|
|
handleMenuCheck({ target }) {
|
|
|
|
ResponsiveUIManager.setMenuCheckFor(target);
|
|
|
|
},
|
|
|
|
|
|
|
|
initMenuCheckListenerFor(window) {
|
2018-06-01 13:36:09 +03:00
|
|
|
const { tabContainer } = window.gBrowser;
|
2016-05-19 13:04:51 +03:00
|
|
|
tabContainer.addEventListener("TabSelect", this.handleMenuCheck);
|
|
|
|
},
|
|
|
|
|
|
|
|
removeMenuCheckListenerFor(window) {
|
|
|
|
if (window && window.gBrowser && window.gBrowser.tabContainer) {
|
2018-06-01 13:36:09 +03:00
|
|
|
const { tabContainer } = window.gBrowser;
|
2016-05-19 13:04:51 +03:00
|
|
|
tabContainer.removeEventListener("TabSelect", this.handleMenuCheck);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2018-01-09 19:08:35 +03:00
|
|
|
async setMenuCheckFor(tab, window = tab.ownerGlobal) {
|
|
|
|
await startup(window);
|
2016-05-19 13:04:51 +03:00
|
|
|
|
2018-06-01 13:36:09 +03:00
|
|
|
const menu = window.document.getElementById("menu_responsiveUI");
|
2016-05-19 13:04:51 +03:00
|
|
|
if (menu) {
|
|
|
|
menu.setAttribute("checked", this.isActiveForTab(tab));
|
|
|
|
}
|
2018-01-09 19:08:35 +03:00
|
|
|
},
|
2016-08-13 01:09:29 +03:00
|
|
|
|
2018-03-13 22:52:34 +03:00
|
|
|
showRemoteOnlyNotification(window, tab, { trigger } = {}) {
|
2018-08-29 16:11:53 +03:00
|
|
|
return showNotification(window, tab, {
|
2018-09-06 14:42:57 +03:00
|
|
|
toolboxButton: trigger == "toolbox",
|
2018-02-09 03:43:23 +03:00
|
|
|
msg: l10n.getStr("responsive.remoteOnly"),
|
|
|
|
priority: PriorityLevels.PRIORITY_CRITICAL_MEDIUM,
|
|
|
|
});
|
2018-02-22 20:26:58 +03:00
|
|
|
},
|
2016-01-05 10:33:31 +03:00
|
|
|
});
|
|
|
|
|
2016-03-04 09:21:19 +03:00
|
|
|
EventEmitter.decorate(ResponsiveUIManager);
|