1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 /* eslint-env browser */
9 const { BrowserLoader } = ChromeUtils.importESModule(
10 "resource://devtools/shared/loader/browser-loader.sys.mjs"
12 const { require } = BrowserLoader({
13 baseURI: "resource://devtools/client/responsive/",
16 const Telemetry = require("resource://devtools/client/shared/telemetry.js");
21 } = require("resource://devtools/client/shared/vendor/react.js");
22 const ReactDOM = require("resource://devtools/client/shared/vendor/react-dom.js");
25 } = require("resource://devtools/client/shared/vendor/react-redux.js");
27 const message = require("resource://devtools/client/responsive/utils/message.js");
28 const App = createFactory(
29 require("resource://devtools/client/responsive/components/App.js")
31 const Store = require("resource://devtools/client/responsive/store.js");
35 } = require("resource://devtools/client/responsive/actions/devices.js");
38 removeDeviceAssociation,
41 } = require("resource://devtools/client/responsive/actions/viewports.js");
43 changeDisplayPixelRatio,
44 } = require("resource://devtools/client/responsive/actions/ui.js");
46 // Exposed for use by tests
47 window.require = require;
49 // Tell the ResponsiveUIManager that the frame script has begun initializing.
50 message.post(window, "script-init");
53 telemetry: new Telemetry(),
58 this.telemetry.toolOpened("responsive", this);
60 const store = (this.store = Store());
61 const provider = createElement(Provider, { store }, App());
62 this._root = document.querySelector("#root");
63 ReactDOM.render(provider, this._root);
64 message.post(window, "init:done");
66 this.destroy = this.destroy.bind(this);
67 window.addEventListener("unload", this.destroy, { once: true });
71 window.removeEventListener("unload", this.destroy, { once: true });
73 // unmount to stop async action and renders after destroy
74 ReactDOM.unmountComponentAtNode(this._root);
78 this.telemetry.toolClosed("responsive", this);
79 this.telemetry = null;
83 * While most actions will be dispatched by React components, some external
84 * APIs that coordinate with the larger browser UI may also have actions to
85 * to dispatch. They can do so here.
89 // If actions are dispatched after store is destroyed, ignore them. This
90 // can happen in tests that close the tool quickly while async tasks like
91 // initDevices() below are still pending.
92 return Promise.resolve();
94 return this.store.dispatch(action);
98 // manager.js sends a message to signal init
99 message.wait(window, "init").then(() => bootstrap.init());
101 // manager.js sends a message to signal init is done, which can be used for delayed
102 // startup work that shouldn't block initial load
103 message.wait(window, "post-init").then(() => {
104 bootstrap.dispatch(loadDevices()).then(() => {
105 bootstrap.dispatch(restoreDeviceState());
109 window.destroy = () => bootstrap.destroy();
110 // Allows quick testing of actions from the console
111 window.dispatch = action => bootstrap.dispatch(action);
113 // Expose the store on window for testing
114 Object.defineProperty(window, "store", {
115 get: () => bootstrap.store,
119 // Dispatch a `changeDisplayPixelRatio` action when the browser's pixel ratio is changing.
120 // This is usually triggered when the user changes the monitor resolution, or when the
121 // browser's window is dragged to a different display with a different pixel ratio.
122 // TODO: It would be better to move this watching into the actor, so that it can be
123 // better synchronized with any overrides that might be applied. Also, reading a single
124 // value like this makes less sense with multiple viewports.
125 function onDevicePixelRatioChange() {
126 const dpr = window.devicePixelRatio;
127 const mql = window.matchMedia(`(resolution: ${dpr}dppx)`);
129 function listener() {
130 bootstrap.dispatch(changeDisplayPixelRatio(window.devicePixelRatio));
131 mql.removeListener(listener);
132 onDevicePixelRatioChange();
135 mql.addListener(listener);
139 * Called by manager.js to add the initial viewport based on the original page.
141 window.addInitialViewport = ({ userContextId }) => {
143 onDevicePixelRatioChange();
144 bootstrap.dispatch(changeDisplayPixelRatio(window.devicePixelRatio));
145 bootstrap.dispatch(addViewport(userContextId));
151 window.getAssociatedDevice = () => {
152 const { viewports } = bootstrap.store.getState();
153 if (!viewports.length) {
157 return viewports[0].device;
161 * Called by manager.js when tests want to check the viewport size.
163 window.getViewportSize = () => {
164 const { viewports } = bootstrap.store.getState();
165 if (!viewports.length) {
169 const { width, height } = viewports[0];
170 return { width, height };
174 * Called by manager.js to set viewport size from tests, etc.
176 window.setViewportSize = ({ width, height }) => {
178 bootstrap.dispatch(resizeViewport(0, width, height));
184 window.clearDeviceAssociation = () => {
186 bootstrap.dispatch(removeDeviceAssociation(0));
193 * Called by manager.js to access the viewport's browser, either for testing
194 * purposes or to reload it when touch simulation is enabled.
195 * A messageManager getter is added on the object to provide an easy access
196 * to the message manager without pulling the frame loader.
198 window.getViewportBrowser = () => {
199 const browser = document.querySelector("iframe.browser");
200 if (browser && !browser.messageManager) {
201 Object.defineProperty(browser, "messageManager", {
203 return this.frameLoader.messageManager;
213 * Called by manager.js to zoom the viewport.
215 window.setViewportZoom = zoom => {
217 bootstrap.dispatch(zoomViewport(0, zoom));