index.js (6701B)
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/. */ 4 5 "use strict"; 6 7 const { BrowserLoader } = ChromeUtils.importESModule( 8 "resource://devtools/shared/loader/browser-loader.sys.mjs" 9 ); 10 const { require } = BrowserLoader({ 11 baseURI: "resource://devtools/client/responsive/", 12 window, 13 }); 14 const Telemetry = require("resource://devtools/client/shared/telemetry.js"); 15 16 const { 17 createFactory, 18 createElement, 19 } = require("resource://devtools/client/shared/vendor/react.mjs"); 20 const ReactDOM = require("resource://devtools/client/shared/vendor/react-dom.mjs"); 21 const { 22 Provider, 23 } = require("resource://devtools/client/shared/vendor/react-redux.js"); 24 const { 25 START_IGNORE_ACTION, 26 } = require("resource://devtools/client/shared/redux/middleware/ignore.js"); 27 28 const message = require("resource://devtools/client/responsive/utils/message.js"); 29 const App = createFactory( 30 require("resource://devtools/client/responsive/components/App.js") 31 ); 32 const Store = require("resource://devtools/client/responsive/store.js"); 33 const { 34 loadDevices, 35 restoreDeviceState, 36 } = require("resource://devtools/client/responsive/actions/devices.js"); 37 const { 38 addViewport, 39 removeDeviceAssociation, 40 resizeViewport, 41 zoomViewport, 42 } = require("resource://devtools/client/responsive/actions/viewports.js"); 43 const { 44 changeDisplayPixelRatio, 45 } = require("resource://devtools/client/responsive/actions/ui.js"); 46 47 // Exposed for use by tests 48 window.require = require; 49 50 // Tell the ResponsiveUIManager that the frame script has begun initializing. 51 message.post(window, "script-init"); 52 53 const bootstrap = { 54 telemetry: new Telemetry(), 55 56 store: null, 57 58 async init() { 59 this.telemetry.toolOpened("responsive", this); 60 61 const store = (this.store = Store()); 62 const provider = createElement(Provider, { store }, App()); 63 this._root = document.querySelector("#root"); 64 ReactDOM.render(provider, this._root); 65 message.post(window, "init:done"); 66 67 this.destroy = this.destroy.bind(this); 68 window.addEventListener("unload", this.destroy, { once: true }); 69 }, 70 71 destroy() { 72 window.removeEventListener("unload", this.destroy, { once: true }); 73 74 // Prevents any further action from being dispatched 75 this.store.dispatch(START_IGNORE_ACTION); 76 77 // unmount to stop async action and renders after destroy 78 ReactDOM.unmountComponentAtNode(this._root); 79 80 this.store = null; 81 82 this.telemetry.toolClosed("responsive", this); 83 this.telemetry = null; 84 }, 85 86 /** 87 * While most actions will be dispatched by React components, some external 88 * APIs that coordinate with the larger browser UI may also have actions to 89 * to dispatch. They can do so here. 90 */ 91 dispatch(action) { 92 if (!this.store) { 93 // If actions are dispatched after store is destroyed, ignore them. This 94 // can happen in tests that close the tool quickly while async tasks like 95 // initDevices() below are still pending. 96 return Promise.resolve(); 97 } 98 return this.store.dispatch(action); 99 }, 100 }; 101 102 // manager.js sends a message to signal init 103 message.wait(window, "init").then(() => bootstrap.init()); 104 105 // manager.js sends a message to signal init is done, which can be used for delayed 106 // startup work that shouldn't block initial load 107 message.wait(window, "post-init").then(() => { 108 bootstrap.dispatch(loadDevices()).then(() => { 109 bootstrap.dispatch(restoreDeviceState()); 110 }); 111 }); 112 113 window.destroy = () => bootstrap.destroy(); 114 // Allows quick testing of actions from the console 115 window.dispatch = action => bootstrap.dispatch(action); 116 117 // Expose the store on window for testing 118 Object.defineProperty(window, "store", { 119 get: () => bootstrap.store, 120 enumerable: true, 121 }); 122 123 // Dispatch a `changeDisplayPixelRatio` action when the browser's pixel ratio is changing. 124 // This is usually triggered when the user changes the monitor resolution, or when the 125 // browser's window is dragged to a different display with a different pixel ratio. 126 // TODO: It would be better to move this watching into the actor, so that it can be 127 // better synchronized with any overrides that might be applied. Also, reading a single 128 // value like this makes less sense with multiple viewports. 129 function onDevicePixelRatioChange() { 130 const dpr = window.devicePixelRatio; 131 const mql = window.matchMedia(`(resolution: ${dpr}dppx)`); 132 133 function listener() { 134 bootstrap.dispatch(changeDisplayPixelRatio(window.devicePixelRatio)); 135 mql.removeListener(listener); 136 onDevicePixelRatioChange(); 137 } 138 139 mql.addListener(listener); 140 } 141 142 /** 143 * Called by manager.js to add the initial viewport based on the original page. 144 */ 145 window.addInitialViewport = ({ userContextId }) => { 146 try { 147 onDevicePixelRatioChange(); 148 bootstrap.dispatch(changeDisplayPixelRatio(window.devicePixelRatio)); 149 bootstrap.dispatch(addViewport(userContextId)); 150 } catch (e) { 151 console.error(e); 152 } 153 }; 154 155 window.getAssociatedDevice = () => { 156 const { viewports } = bootstrap.store.getState(); 157 if (!viewports.length) { 158 return null; 159 } 160 161 return viewports[0].device; 162 }; 163 164 /** 165 * Called by manager.js when tests want to check the viewport size. 166 */ 167 window.getViewportSize = () => { 168 const { viewports } = bootstrap.store.getState(); 169 if (!viewports.length) { 170 return null; 171 } 172 173 const { width, height } = viewports[0]; 174 return { width, height }; 175 }; 176 177 /** 178 * Called by manager.js to set viewport size from tests, etc. 179 */ 180 window.setViewportSize = ({ width, height }) => { 181 try { 182 bootstrap.dispatch(resizeViewport(0, width, height)); 183 } catch (e) { 184 console.error(e); 185 } 186 }; 187 188 window.clearDeviceAssociation = () => { 189 try { 190 bootstrap.dispatch(removeDeviceAssociation(0)); 191 } catch (e) { 192 console.error(e); 193 } 194 }; 195 196 /** 197 * Called by manager.js to access the viewport's browser, either for testing 198 * purposes or to reload it when touch simulation is enabled. 199 * A messageManager getter is added on the object to provide an easy access 200 * to the message manager without pulling the frame loader. 201 */ 202 window.getViewportBrowser = () => { 203 const browser = document.querySelector("iframe.browser"); 204 if (browser && !browser.messageManager) { 205 Object.defineProperty(browser, "messageManager", { 206 get() { 207 return this.frameLoader.messageManager; 208 }, 209 configurable: true, 210 enumerable: true, 211 }); 212 } 213 return browser; 214 }; 215 216 /** 217 * Called by manager.js to zoom the viewport. 218 */ 219 window.setViewportZoom = zoom => { 220 try { 221 bootstrap.dispatch(zoomViewport(0, zoom)); 222 } catch (e) { 223 console.error(e); 224 } 225 };