Bug 1921551 - React to sync sign in flow correctly r=android-reviewers,matt-tighe
[gecko.git] / devtools / client / responsive / index.js
blobf6e110cff75da8fc7a41e1049b964d96d3880c1b
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 */
7 "use strict";
9 const { BrowserLoader } = ChromeUtils.importESModule(
10   "resource://devtools/shared/loader/browser-loader.sys.mjs"
12 const { require } = BrowserLoader({
13   baseURI: "resource://devtools/client/responsive/",
14   window,
15 });
16 const Telemetry = require("resource://devtools/client/shared/telemetry.js");
18 const {
19   createFactory,
20   createElement,
21 } = require("resource://devtools/client/shared/vendor/react.js");
22 const ReactDOM = require("resource://devtools/client/shared/vendor/react-dom.js");
23 const {
24   Provider,
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");
32 const {
33   loadDevices,
34   restoreDeviceState,
35 } = require("resource://devtools/client/responsive/actions/devices.js");
36 const {
37   addViewport,
38   removeDeviceAssociation,
39   resizeViewport,
40   zoomViewport,
41 } = require("resource://devtools/client/responsive/actions/viewports.js");
42 const {
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");
52 const bootstrap = {
53   telemetry: new Telemetry(),
55   store: null,
57   async init() {
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 });
68   },
70   destroy() {
71     window.removeEventListener("unload", this.destroy, { once: true });
73     // unmount to stop async action and renders after destroy
74     ReactDOM.unmountComponentAtNode(this._root);
76     this.store = null;
78     this.telemetry.toolClosed("responsive", this);
79     this.telemetry = null;
80   },
82   /**
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.
86    */
87   dispatch(action) {
88     if (!this.store) {
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();
93     }
94     return this.store.dispatch(action);
95   },
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());
106   });
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,
116   enumerable: true,
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();
133   }
135   mql.addListener(listener);
139  * Called by manager.js to add the initial viewport based on the original page.
140  */
141 window.addInitialViewport = ({ userContextId }) => {
142   try {
143     onDevicePixelRatioChange();
144     bootstrap.dispatch(changeDisplayPixelRatio(window.devicePixelRatio));
145     bootstrap.dispatch(addViewport(userContextId));
146   } catch (e) {
147     console.error(e);
148   }
151 window.getAssociatedDevice = () => {
152   const { viewports } = bootstrap.store.getState();
153   if (!viewports.length) {
154     return null;
155   }
157   return viewports[0].device;
161  * Called by manager.js when tests want to check the viewport size.
162  */
163 window.getViewportSize = () => {
164   const { viewports } = bootstrap.store.getState();
165   if (!viewports.length) {
166     return null;
167   }
169   const { width, height } = viewports[0];
170   return { width, height };
174  * Called by manager.js to set viewport size from tests, etc.
175  */
176 window.setViewportSize = ({ width, height }) => {
177   try {
178     bootstrap.dispatch(resizeViewport(0, width, height));
179   } catch (e) {
180     console.error(e);
181   }
184 window.clearDeviceAssociation = () => {
185   try {
186     bootstrap.dispatch(removeDeviceAssociation(0));
187   } catch (e) {
188     console.error(e);
189   }
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.
197  */
198 window.getViewportBrowser = () => {
199   const browser = document.querySelector("iframe.browser");
200   if (browser && !browser.messageManager) {
201     Object.defineProperty(browser, "messageManager", {
202       get() {
203         return this.frameLoader.messageManager;
204       },
205       configurable: true,
206       enumerable: true,
207     });
208   }
209   return browser;
213  * Called by manager.js to zoom the viewport.
214  */
215 window.setViewportZoom = zoom => {
216   try {
217     bootstrap.dispatch(zoomViewport(0, zoom));
218   } catch (e) {
219     console.error(e);
220   }