1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/browser/devtools/responsivedesign/responsivedesign.jsm Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,927 @@ 1.4 +/* -*- Mode: Javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ 1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.9 + 1.10 +const Ci = Components.interfaces; 1.11 +const Cu = Components.utils; 1.12 + 1.13 +Cu.import("resource://gre/modules/Services.jsm"); 1.14 +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); 1.15 +Cu.import("resource:///modules/devtools/gDevTools.jsm"); 1.16 +Cu.import("resource:///modules/devtools/FloatingScrollbars.jsm"); 1.17 +Cu.import("resource://gre/modules/devtools/event-emitter.js"); 1.18 + 1.19 +var require = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools.require; 1.20 +let Telemetry = require("devtools/shared/telemetry"); 1.21 +let {TouchEventHandler} = require("devtools/touch-events"); 1.22 + 1.23 +this.EXPORTED_SYMBOLS = ["ResponsiveUIManager"]; 1.24 + 1.25 +const MIN_WIDTH = 50; 1.26 +const MIN_HEIGHT = 50; 1.27 + 1.28 +const MAX_WIDTH = 10000; 1.29 +const MAX_HEIGHT = 10000; 1.30 + 1.31 +const SLOW_RATIO = 6; 1.32 +const ROUND_RATIO = 10; 1.33 + 1.34 +this.ResponsiveUIManager = { 1.35 + /** 1.36 + * Check if the a tab is in a responsive mode. 1.37 + * Leave the responsive mode if active, 1.38 + * active the responsive mode if not active. 1.39 + * 1.40 + * @param aWindow the main window. 1.41 + * @param aTab the tab targeted. 1.42 + */ 1.43 + toggle: function(aWindow, aTab) { 1.44 + if (aTab.__responsiveUI) { 1.45 + aTab.__responsiveUI.close(); 1.46 + } else { 1.47 + new ResponsiveUI(aWindow, aTab); 1.48 + } 1.49 + }, 1.50 + 1.51 + /** 1.52 + * Returns true if responsive view is active for the provided tab. 1.53 + * 1.54 + * @param aTab the tab targeted. 1.55 + */ 1.56 + isActiveForTab: function(aTab) { 1.57 + return !!aTab.__responsiveUI; 1.58 + }, 1.59 + 1.60 + /** 1.61 + * Handle gcli commands. 1.62 + * 1.63 + * @param aWindow the browser window. 1.64 + * @param aTab the tab targeted. 1.65 + * @param aCommand the command name. 1.66 + * @param aArgs command arguments. 1.67 + */ 1.68 + handleGcliCommand: function(aWindow, aTab, aCommand, aArgs) { 1.69 + switch (aCommand) { 1.70 + case "resize to": 1.71 + if (!aTab.__responsiveUI) { 1.72 + new ResponsiveUI(aWindow, aTab); 1.73 + } 1.74 + aTab.__responsiveUI.setSize(aArgs.width, aArgs.height); 1.75 + break; 1.76 + case "resize on": 1.77 + if (!aTab.__responsiveUI) { 1.78 + new ResponsiveUI(aWindow, aTab); 1.79 + } 1.80 + break; 1.81 + case "resize off": 1.82 + if (aTab.__responsiveUI) { 1.83 + aTab.__responsiveUI.close(); 1.84 + } 1.85 + break; 1.86 + case "resize toggle": 1.87 + this.toggle(aWindow, aTab); 1.88 + default: 1.89 + } 1.90 + } 1.91 +} 1.92 + 1.93 +EventEmitter.decorate(ResponsiveUIManager); 1.94 + 1.95 +let presets = [ 1.96 + // Phones 1.97 + {key: "320x480", width: 320, height: 480}, // iPhone, B2G, with <meta viewport> 1.98 + {key: "360x640", width: 360, height: 640}, // Android 4, phones, with <meta viewport> 1.99 + 1.100 + // Tablets 1.101 + {key: "768x1024", width: 768, height: 1024}, // iPad, with <meta viewport> 1.102 + {key: "800x1280", width: 800, height: 1280}, // Android 4, Tablet, with <meta viewport> 1.103 + 1.104 + // Default width for mobile browsers, no <meta viewport> 1.105 + {key: "980x1280", width: 980, height: 1280}, 1.106 + 1.107 + // Computer 1.108 + {key: "1280x600", width: 1280, height: 600}, 1.109 + {key: "1920x900", width: 1920, height: 900}, 1.110 +]; 1.111 + 1.112 +function ResponsiveUI(aWindow, aTab) 1.113 +{ 1.114 + this.mainWindow = aWindow; 1.115 + this.tab = aTab; 1.116 + this.tabContainer = aWindow.gBrowser.tabContainer; 1.117 + this.browser = aTab.linkedBrowser; 1.118 + this.chromeDoc = aWindow.document; 1.119 + this.container = aWindow.gBrowser.getBrowserContainer(this.browser); 1.120 + this.stack = this.container.querySelector(".browserStack"); 1.121 + this._telemetry = new Telemetry(); 1.122 + this._floatingScrollbars = !this.mainWindow.matchMedia("(-moz-overlay-scrollbars)").matches; 1.123 + 1.124 + 1.125 + // Try to load presets from prefs 1.126 + if (Services.prefs.prefHasUserValue("devtools.responsiveUI.presets")) { 1.127 + try { 1.128 + presets = JSON.parse(Services.prefs.getCharPref("devtools.responsiveUI.presets")); 1.129 + } catch(e) { 1.130 + // User pref is malformated. 1.131 + Cu.reportError("Could not parse pref `devtools.responsiveUI.presets`: " + e); 1.132 + } 1.133 + } 1.134 + 1.135 + this.customPreset = {key: "custom", custom: true}; 1.136 + 1.137 + if (Array.isArray(presets)) { 1.138 + this.presets = [this.customPreset].concat(presets); 1.139 + } else { 1.140 + Cu.reportError("Presets value (devtools.responsiveUI.presets) is malformated."); 1.141 + this.presets = [this.customPreset]; 1.142 + } 1.143 + 1.144 + try { 1.145 + let width = Services.prefs.getIntPref("devtools.responsiveUI.customWidth"); 1.146 + let height = Services.prefs.getIntPref("devtools.responsiveUI.customHeight"); 1.147 + this.customPreset.width = Math.min(MAX_WIDTH, width); 1.148 + this.customPreset.height = Math.min(MAX_HEIGHT, height); 1.149 + 1.150 + this.currentPresetKey = Services.prefs.getCharPref("devtools.responsiveUI.currentPreset"); 1.151 + } catch(e) { 1.152 + // Default size. The first preset (custom) is the one that will be used. 1.153 + let bbox = this.stack.getBoundingClientRect(); 1.154 + 1.155 + this.customPreset.width = bbox.width - 40; // horizontal padding of the container 1.156 + this.customPreset.height = bbox.height - 80; // vertical padding + toolbar height 1.157 + 1.158 + this.currentPresetKey = this.presets[1].key; // most common preset 1.159 + } 1.160 + 1.161 + this.container.setAttribute("responsivemode", "true"); 1.162 + this.stack.setAttribute("responsivemode", "true"); 1.163 + 1.164 + // Let's bind some callbacks. 1.165 + this.bound_onPageLoad = this.onPageLoad.bind(this); 1.166 + this.bound_onPageUnload = this.onPageUnload.bind(this); 1.167 + this.bound_presetSelected = this.presetSelected.bind(this); 1.168 + this.bound_addPreset = this.addPreset.bind(this); 1.169 + this.bound_removePreset = this.removePreset.bind(this); 1.170 + this.bound_rotate = this.rotate.bind(this); 1.171 + this.bound_screenshot = () => this.screenshot(); 1.172 + this.bound_touch = this.toggleTouch.bind(this); 1.173 + this.bound_close = this.close.bind(this); 1.174 + this.bound_startResizing = this.startResizing.bind(this); 1.175 + this.bound_stopResizing = this.stopResizing.bind(this); 1.176 + this.bound_onDrag = this.onDrag.bind(this); 1.177 + this.bound_onKeypress = this.onKeypress.bind(this); 1.178 + 1.179 + // Events 1.180 + this.tab.addEventListener("TabClose", this); 1.181 + this.tabContainer.addEventListener("TabSelect", this); 1.182 + this.mainWindow.document.addEventListener("keypress", this.bound_onKeypress, false); 1.183 + 1.184 + this.buildUI(); 1.185 + this.checkMenus(); 1.186 + 1.187 + this.docShell = this.browser.contentWindow.QueryInterface(Ci.nsIInterfaceRequestor) 1.188 + .getInterface(Ci.nsIWebNavigation) 1.189 + .QueryInterface(Ci.nsIDocShell); 1.190 + 1.191 + this._deviceSizeWasPageSize = this.docShell.deviceSizeIsPageSize; 1.192 + this.docShell.deviceSizeIsPageSize = true; 1.193 + 1.194 + try { 1.195 + if (Services.prefs.getBoolPref("devtools.responsiveUI.rotate")) { 1.196 + this.rotate(); 1.197 + } 1.198 + } catch(e) {} 1.199 + 1.200 + if (this._floatingScrollbars) 1.201 + switchToFloatingScrollbars(this.tab); 1.202 + 1.203 + this.tab.__responsiveUI = this; 1.204 + 1.205 + this._telemetry.toolOpened("responsive"); 1.206 + 1.207 + // Touch events support 1.208 + this.touchEnableBefore = false; 1.209 + this.touchEventHandler = new TouchEventHandler(this.browser); 1.210 + 1.211 + this.browser.addEventListener("load", this.bound_onPageLoad, true); 1.212 + this.browser.addEventListener("unload", this.bound_onPageUnload, true); 1.213 + 1.214 + if (this.browser.contentWindow.document && 1.215 + this.browser.contentWindow.document.readyState == "complete") { 1.216 + this.onPageLoad(); 1.217 + } 1.218 + 1.219 + ResponsiveUIManager.emit("on", this.tab, this); 1.220 +} 1.221 + 1.222 +ResponsiveUI.prototype = { 1.223 + _transitionsEnabled: true, 1.224 + get transitionsEnabled() this._transitionsEnabled, 1.225 + set transitionsEnabled(aValue) { 1.226 + this._transitionsEnabled = aValue; 1.227 + if (aValue && !this._resizing && this.stack.hasAttribute("responsivemode")) { 1.228 + this.stack.removeAttribute("notransition"); 1.229 + } else if (!aValue) { 1.230 + this.stack.setAttribute("notransition", "true"); 1.231 + } 1.232 + }, 1.233 + 1.234 + /** 1.235 + * Window onload / onunload 1.236 + */ 1.237 + onPageLoad: function() { 1.238 + this.touchEventHandler = new TouchEventHandler(this.browser); 1.239 + if (this.touchEnableBefore) { 1.240 + this.enableTouch(); 1.241 + } 1.242 + }, 1.243 + 1.244 + onPageUnload: function(evt) { 1.245 + // Ignore sub frames unload events 1.246 + if (evt.target != this.browser.contentDocument) 1.247 + return; 1.248 + if (this.closing) 1.249 + return; 1.250 + if (this.touchEventHandler) { 1.251 + this.touchEnableBefore = this.touchEventHandler.enabled; 1.252 + this.disableTouch(); 1.253 + delete this.touchEventHandler; 1.254 + } 1.255 + }, 1.256 + 1.257 + /** 1.258 + * Destroy the nodes. Remove listeners. Reset the style. 1.259 + */ 1.260 + close: function RUI_unload() { 1.261 + if (this.closing) 1.262 + return; 1.263 + this.closing = true; 1.264 + 1.265 + this.docShell.deviceSizeIsPageSize = this._deviceSizeWasPageSize; 1.266 + 1.267 + this.browser.removeEventListener("load", this.bound_onPageLoad, true); 1.268 + this.browser.removeEventListener("unload", this.bound_onPageUnload, true); 1.269 + 1.270 + if (this._floatingScrollbars) 1.271 + switchToNativeScrollbars(this.tab); 1.272 + 1.273 + this.unCheckMenus(); 1.274 + // Reset style of the stack. 1.275 + let style = "max-width: none;" + 1.276 + "min-width: 0;" + 1.277 + "max-height: none;" + 1.278 + "min-height: 0;"; 1.279 + this.stack.setAttribute("style", style); 1.280 + 1.281 + if (this.isResizing) 1.282 + this.stopResizing(); 1.283 + 1.284 + // Remove listeners. 1.285 + this.mainWindow.document.removeEventListener("keypress", this.bound_onKeypress, false); 1.286 + this.menulist.removeEventListener("select", this.bound_presetSelected, true); 1.287 + this.tab.removeEventListener("TabClose", this); 1.288 + this.tabContainer.removeEventListener("TabSelect", this); 1.289 + this.rotatebutton.removeEventListener("command", this.bound_rotate, true); 1.290 + this.screenshotbutton.removeEventListener("command", this.bound_screenshot, true); 1.291 + this.touchbutton.removeEventListener("command", this.bound_touch, true); 1.292 + this.closebutton.removeEventListener("command", this.bound_close, true); 1.293 + this.addbutton.removeEventListener("command", this.bound_addPreset, true); 1.294 + this.removebutton.removeEventListener("command", this.bound_removePreset, true); 1.295 + 1.296 + // Removed elements. 1.297 + this.container.removeChild(this.toolbar); 1.298 + this.stack.removeChild(this.resizer); 1.299 + this.stack.removeChild(this.resizeBarV); 1.300 + this.stack.removeChild(this.resizeBarH); 1.301 + 1.302 + // Unset the responsive mode. 1.303 + this.container.removeAttribute("responsivemode"); 1.304 + this.stack.removeAttribute("responsivemode"); 1.305 + 1.306 + delete this.docShell; 1.307 + delete this.tab.__responsiveUI; 1.308 + if (this.touchEventHandler) 1.309 + this.touchEventHandler.stop(); 1.310 + this._telemetry.toolClosed("responsive"); 1.311 + ResponsiveUIManager.emit("off", this.tab, this); 1.312 + }, 1.313 + 1.314 + /** 1.315 + * Handle keypressed. 1.316 + * 1.317 + * @param aEvent 1.318 + */ 1.319 + onKeypress: function RUI_onKeypress(aEvent) { 1.320 + if (aEvent.keyCode == this.mainWindow.KeyEvent.DOM_VK_ESCAPE && 1.321 + this.mainWindow.gBrowser.selectedBrowser == this.browser) { 1.322 + 1.323 + aEvent.preventDefault(); 1.324 + aEvent.stopPropagation(); 1.325 + this.close(); 1.326 + } 1.327 + }, 1.328 + 1.329 + /** 1.330 + * Handle events 1.331 + */ 1.332 + handleEvent: function (aEvent) { 1.333 + switch (aEvent.type) { 1.334 + case "TabClose": 1.335 + this.close(); 1.336 + break; 1.337 + case "TabSelect": 1.338 + if (this.tab.selected) { 1.339 + this.checkMenus(); 1.340 + } else if (!this.mainWindow.gBrowser.selectedTab.responsiveUI) { 1.341 + this.unCheckMenus(); 1.342 + } 1.343 + break; 1.344 + } 1.345 + }, 1.346 + 1.347 + /** 1.348 + * Check the menu items. 1.349 + */ 1.350 + checkMenus: function RUI_checkMenus() { 1.351 + this.chromeDoc.getElementById("Tools:ResponsiveUI").setAttribute("checked", "true"); 1.352 + }, 1.353 + 1.354 + /** 1.355 + * Uncheck the menu items. 1.356 + */ 1.357 + unCheckMenus: function RUI_unCheckMenus() { 1.358 + this.chromeDoc.getElementById("Tools:ResponsiveUI").setAttribute("checked", "false"); 1.359 + }, 1.360 + 1.361 + /** 1.362 + * Build the toolbar and the resizers. 1.363 + * 1.364 + * <vbox class="browserContainer"> From tabbrowser.xml 1.365 + * <toolbar class="devtools-responsiveui-toolbar"> 1.366 + * <menulist class="devtools-responsiveui-menulist"/> // presets 1.367 + * <toolbarbutton tabindex="0" class="devtools-responsiveui-toolbarbutton" tooltiptext="rotate"/> // rotate 1.368 + * <toolbarbutton tabindex="0" class="devtools-responsiveui-toolbarbutton" tooltiptext="screenshot"/> // screenshot 1.369 + * <toolbarbutton tabindex="0" class="devtools-responsiveui-toolbarbutton" tooltiptext="Leave Responsive Design View"/> // close 1.370 + * </toolbar> 1.371 + * <stack class="browserStack"> From tabbrowser.xml 1.372 + * <browser/> 1.373 + * <box class="devtools-responsiveui-resizehandle" bottom="0" right="0"/> 1.374 + * <box class="devtools-responsiveui-resizebarV" top="0" right="0"/> 1.375 + * <box class="devtools-responsiveui-resizebarH" bottom="0" left="0"/> 1.376 + * </stack> 1.377 + * </vbox> 1.378 + */ 1.379 + buildUI: function RUI_buildUI() { 1.380 + // Toolbar 1.381 + this.toolbar = this.chromeDoc.createElement("toolbar"); 1.382 + this.toolbar.className = "devtools-responsiveui-toolbar"; 1.383 + 1.384 + this.menulist = this.chromeDoc.createElement("menulist"); 1.385 + this.menulist.className = "devtools-responsiveui-menulist"; 1.386 + 1.387 + this.menulist.addEventListener("select", this.bound_presetSelected, true); 1.388 + 1.389 + this.menuitems = new Map(); 1.390 + 1.391 + let menupopup = this.chromeDoc.createElement("menupopup"); 1.392 + this.registerPresets(menupopup); 1.393 + this.menulist.appendChild(menupopup); 1.394 + 1.395 + this.addbutton = this.chromeDoc.createElement("menuitem"); 1.396 + this.addbutton.setAttribute("label", this.strings.GetStringFromName("responsiveUI.addPreset")); 1.397 + this.addbutton.addEventListener("command", this.bound_addPreset, true); 1.398 + 1.399 + this.removebutton = this.chromeDoc.createElement("menuitem"); 1.400 + this.removebutton.setAttribute("label", this.strings.GetStringFromName("responsiveUI.removePreset")); 1.401 + this.removebutton.addEventListener("command", this.bound_removePreset, true); 1.402 + 1.403 + menupopup.appendChild(this.chromeDoc.createElement("menuseparator")); 1.404 + menupopup.appendChild(this.addbutton); 1.405 + menupopup.appendChild(this.removebutton); 1.406 + 1.407 + this.rotatebutton = this.chromeDoc.createElement("toolbarbutton"); 1.408 + this.rotatebutton.setAttribute("tabindex", "0"); 1.409 + this.rotatebutton.setAttribute("tooltiptext", this.strings.GetStringFromName("responsiveUI.rotate2")); 1.410 + this.rotatebutton.className = "devtools-responsiveui-toolbarbutton devtools-responsiveui-rotate"; 1.411 + this.rotatebutton.addEventListener("command", this.bound_rotate, true); 1.412 + 1.413 + this.screenshotbutton = this.chromeDoc.createElement("toolbarbutton"); 1.414 + this.screenshotbutton.setAttribute("tabindex", "0"); 1.415 + this.screenshotbutton.setAttribute("tooltiptext", this.strings.GetStringFromName("responsiveUI.screenshot")); 1.416 + this.screenshotbutton.className = "devtools-responsiveui-toolbarbutton devtools-responsiveui-screenshot"; 1.417 + this.screenshotbutton.addEventListener("command", this.bound_screenshot, true); 1.418 + 1.419 + this.touchbutton = this.chromeDoc.createElement("toolbarbutton"); 1.420 + this.touchbutton.setAttribute("tabindex", "0"); 1.421 + this.touchbutton.setAttribute("tooltiptext", this.strings.GetStringFromName("responsiveUI.touch")); 1.422 + this.touchbutton.className = "devtools-responsiveui-toolbarbutton devtools-responsiveui-touch"; 1.423 + this.touchbutton.addEventListener("command", this.bound_touch, true); 1.424 + 1.425 + this.closebutton = this.chromeDoc.createElement("toolbarbutton"); 1.426 + this.closebutton.setAttribute("tabindex", "0"); 1.427 + this.closebutton.className = "devtools-responsiveui-toolbarbutton devtools-responsiveui-close"; 1.428 + this.closebutton.setAttribute("tooltiptext", this.strings.GetStringFromName("responsiveUI.close")); 1.429 + this.closebutton.addEventListener("command", this.bound_close, true); 1.430 + 1.431 + this.toolbar.appendChild(this.closebutton); 1.432 + this.toolbar.appendChild(this.menulist); 1.433 + this.toolbar.appendChild(this.rotatebutton); 1.434 + this.toolbar.appendChild(this.touchbutton); 1.435 + this.toolbar.appendChild(this.screenshotbutton); 1.436 + 1.437 + // Resizers 1.438 + let resizerTooltip = this.strings.GetStringFromName("responsiveUI.resizerTooltip"); 1.439 + this.resizer = this.chromeDoc.createElement("box"); 1.440 + this.resizer.className = "devtools-responsiveui-resizehandle"; 1.441 + this.resizer.setAttribute("right", "0"); 1.442 + this.resizer.setAttribute("bottom", "0"); 1.443 + this.resizer.setAttribute("tooltiptext", resizerTooltip); 1.444 + this.resizer.onmousedown = this.bound_startResizing; 1.445 + 1.446 + this.resizeBarV = this.chromeDoc.createElement("box"); 1.447 + this.resizeBarV.className = "devtools-responsiveui-resizebarV"; 1.448 + this.resizeBarV.setAttribute("top", "0"); 1.449 + this.resizeBarV.setAttribute("right", "0"); 1.450 + this.resizeBarV.setAttribute("tooltiptext", resizerTooltip); 1.451 + this.resizeBarV.onmousedown = this.bound_startResizing; 1.452 + 1.453 + this.resizeBarH = this.chromeDoc.createElement("box"); 1.454 + this.resizeBarH.className = "devtools-responsiveui-resizebarH"; 1.455 + this.resizeBarH.setAttribute("bottom", "0"); 1.456 + this.resizeBarH.setAttribute("left", "0"); 1.457 + this.resizeBarH.setAttribute("tooltiptext", resizerTooltip); 1.458 + this.resizeBarH.onmousedown = this.bound_startResizing; 1.459 + 1.460 + this.container.insertBefore(this.toolbar, this.stack); 1.461 + this.stack.appendChild(this.resizer); 1.462 + this.stack.appendChild(this.resizeBarV); 1.463 + this.stack.appendChild(this.resizeBarH); 1.464 + }, 1.465 + 1.466 + /** 1.467 + * Build the presets list and append it to the menupopup. 1.468 + * 1.469 + * @param aParent menupopup. 1.470 + */ 1.471 + registerPresets: function RUI_registerPresets(aParent) { 1.472 + let fragment = this.chromeDoc.createDocumentFragment(); 1.473 + let doc = this.chromeDoc; 1.474 + 1.475 + for (let preset of this.presets) { 1.476 + let menuitem = doc.createElement("menuitem"); 1.477 + menuitem.setAttribute("ispreset", true); 1.478 + this.menuitems.set(menuitem, preset); 1.479 + 1.480 + if (preset.key === this.currentPresetKey) { 1.481 + menuitem.setAttribute("selected", "true"); 1.482 + this.selectedItem = menuitem; 1.483 + } 1.484 + 1.485 + if (preset.custom) 1.486 + this.customMenuitem = menuitem; 1.487 + 1.488 + this.setMenuLabel(menuitem, preset); 1.489 + fragment.appendChild(menuitem); 1.490 + } 1.491 + aParent.appendChild(fragment); 1.492 + }, 1.493 + 1.494 + /** 1.495 + * Set the menuitem label of a preset. 1.496 + * 1.497 + * @param aMenuitem menuitem to edit. 1.498 + * @param aPreset associated preset. 1.499 + */ 1.500 + setMenuLabel: function RUI_setMenuLabel(aMenuitem, aPreset) { 1.501 + let size = Math.round(aPreset.width) + "x" + Math.round(aPreset.height); 1.502 + if (aPreset.custom) { 1.503 + let str = this.strings.formatStringFromName("responsiveUI.customResolution", [size], 1); 1.504 + aMenuitem.setAttribute("label", str); 1.505 + } else if (aPreset.name != null && aPreset.name !== "") { 1.506 + let str = this.strings.formatStringFromName("responsiveUI.namedResolution", [size, aPreset.name], 2); 1.507 + aMenuitem.setAttribute("label", str); 1.508 + } else { 1.509 + aMenuitem.setAttribute("label", size); 1.510 + } 1.511 + }, 1.512 + 1.513 + /** 1.514 + * When a preset is selected, apply it. 1.515 + */ 1.516 + presetSelected: function RUI_presetSelected() { 1.517 + if (this.menulist.selectedItem.getAttribute("ispreset") === "true") { 1.518 + this.selectedItem = this.menulist.selectedItem; 1.519 + 1.520 + this.rotateValue = false; 1.521 + let selectedPreset = this.menuitems.get(this.selectedItem); 1.522 + this.loadPreset(selectedPreset); 1.523 + this.currentPresetKey = selectedPreset.key; 1.524 + this.saveCurrentPreset(); 1.525 + 1.526 + // Update the buttons hidden status according to the new selected preset 1.527 + if (selectedPreset == this.customPreset) { 1.528 + this.addbutton.hidden = false; 1.529 + this.removebutton.hidden = true; 1.530 + } else { 1.531 + this.addbutton.hidden = true; 1.532 + this.removebutton.hidden = false; 1.533 + } 1.534 + } 1.535 + }, 1.536 + 1.537 + /** 1.538 + * Apply a preset. 1.539 + * 1.540 + * @param aPreset preset to apply. 1.541 + */ 1.542 + loadPreset: function RUI_loadPreset(aPreset) { 1.543 + this.setSize(aPreset.width, aPreset.height); 1.544 + }, 1.545 + 1.546 + /** 1.547 + * Add a preset to the list and the memory 1.548 + */ 1.549 + addPreset: function RUI_addPreset() { 1.550 + let w = this.customPreset.width; 1.551 + let h = this.customPreset.height; 1.552 + let newName = {}; 1.553 + 1.554 + let title = this.strings.GetStringFromName("responsiveUI.customNamePromptTitle"); 1.555 + let message = this.strings.formatStringFromName("responsiveUI.customNamePromptMsg", [w, h], 2); 1.556 + let promptOk = Services.prompt.prompt(null, title, message, newName, null, {}); 1.557 + 1.558 + if (!promptOk) { 1.559 + // Prompt has been cancelled 1.560 + let menuitem = this.customMenuitem; 1.561 + this.menulist.selectedItem = menuitem; 1.562 + this.currentPresetKey = this.customPreset.key; 1.563 + return; 1.564 + } 1.565 + 1.566 + let newPreset = { 1.567 + key: w + "x" + h, 1.568 + name: newName.value, 1.569 + width: w, 1.570 + height: h 1.571 + }; 1.572 + 1.573 + this.presets.push(newPreset); 1.574 + 1.575 + // Sort the presets according to width/height ascending order 1.576 + this.presets.sort(function RUI_sortPresets(aPresetA, aPresetB) { 1.577 + // We keep custom preset at first 1.578 + if (aPresetA.custom && !aPresetB.custom) { 1.579 + return 1; 1.580 + } 1.581 + if (!aPresetA.custom && aPresetB.custom) { 1.582 + return -1; 1.583 + } 1.584 + 1.585 + if (aPresetA.width === aPresetB.width) { 1.586 + if (aPresetA.height === aPresetB.height) { 1.587 + return 0; 1.588 + } else { 1.589 + return aPresetA.height > aPresetB.height; 1.590 + } 1.591 + } else { 1.592 + return aPresetA.width > aPresetB.width; 1.593 + } 1.594 + }); 1.595 + 1.596 + this.savePresets(); 1.597 + 1.598 + let newMenuitem = this.chromeDoc.createElement("menuitem"); 1.599 + newMenuitem.setAttribute("ispreset", true); 1.600 + this.setMenuLabel(newMenuitem, newPreset); 1.601 + 1.602 + this.menuitems.set(newMenuitem, newPreset); 1.603 + let idx = this.presets.indexOf(newPreset); 1.604 + let beforeMenuitem = this.menulist.firstChild.childNodes[idx + 1]; 1.605 + this.menulist.firstChild.insertBefore(newMenuitem, beforeMenuitem); 1.606 + 1.607 + this.menulist.selectedItem = newMenuitem; 1.608 + this.currentPresetKey = newPreset.key; 1.609 + this.saveCurrentPreset(); 1.610 + }, 1.611 + 1.612 + /** 1.613 + * remove a preset from the list and the memory 1.614 + */ 1.615 + removePreset: function RUI_removePreset() { 1.616 + let selectedPreset = this.menuitems.get(this.selectedItem); 1.617 + let w = selectedPreset.width; 1.618 + let h = selectedPreset.height; 1.619 + 1.620 + this.presets.splice(this.presets.indexOf(selectedPreset), 1); 1.621 + this.menulist.firstChild.removeChild(this.selectedItem); 1.622 + this.menuitems.delete(this.selectedItem); 1.623 + 1.624 + this.customPreset.width = w; 1.625 + this.customPreset.height = h; 1.626 + let menuitem = this.customMenuitem; 1.627 + this.setMenuLabel(menuitem, this.customPreset); 1.628 + this.menulist.selectedItem = menuitem; 1.629 + this.currentPresetKey = this.customPreset.key; 1.630 + 1.631 + this.setSize(w, h); 1.632 + 1.633 + this.savePresets(); 1.634 + }, 1.635 + 1.636 + /** 1.637 + * Swap width and height. 1.638 + */ 1.639 + rotate: function RUI_rotate() { 1.640 + let selectedPreset = this.menuitems.get(this.selectedItem); 1.641 + let width = this.rotateValue ? selectedPreset.height : selectedPreset.width; 1.642 + let height = this.rotateValue ? selectedPreset.width : selectedPreset.height; 1.643 + 1.644 + this.setSize(height, width); 1.645 + 1.646 + if (selectedPreset.custom) { 1.647 + this.saveCustomSize(); 1.648 + } else { 1.649 + this.rotateValue = !this.rotateValue; 1.650 + this.saveCurrentPreset(); 1.651 + } 1.652 + }, 1.653 + 1.654 + /** 1.655 + * Take a screenshot of the page. 1.656 + * 1.657 + * @param aFileName name of the screenshot file (used for tests). 1.658 + */ 1.659 + screenshot: function RUI_screenshot(aFileName) { 1.660 + let window = this.browser.contentWindow; 1.661 + let document = window.document; 1.662 + let canvas = this.chromeDoc.createElementNS("http://www.w3.org/1999/xhtml", "canvas"); 1.663 + 1.664 + let width = window.innerWidth; 1.665 + let height = window.innerHeight; 1.666 + 1.667 + canvas.width = width; 1.668 + canvas.height = height; 1.669 + 1.670 + let ctx = canvas.getContext("2d"); 1.671 + ctx.drawWindow(window, window.scrollX, window.scrollY, width, height, "#fff"); 1.672 + 1.673 + let filename = aFileName; 1.674 + 1.675 + if (!filename) { 1.676 + let date = new Date(); 1.677 + let month = ("0" + (date.getMonth() + 1)).substr(-2, 2); 1.678 + let day = ("0" + (date.getDay() + 1)).substr(-2, 2); 1.679 + let dateString = [date.getFullYear(), month, day].join("-"); 1.680 + let timeString = date.toTimeString().replace(/:/g, ".").split(" ")[0]; 1.681 + filename = this.strings.formatStringFromName("responsiveUI.screenshotGeneratedFilename", [dateString, timeString], 2); 1.682 + } 1.683 + 1.684 + canvas.toBlob(blob => { 1.685 + let chromeWindow = this.chromeDoc.defaultView; 1.686 + let url = chromeWindow.URL.createObjectURL(blob); 1.687 + chromeWindow.saveURL(url, filename + ".png", null, true, true, document.documentURIObject, document); 1.688 + }); 1.689 + }, 1.690 + 1.691 + /** 1.692 + * Enable/Disable mouse -> touch events translation. 1.693 + */ 1.694 + enableTouch: function RUI_enableTouch() { 1.695 + if (!this.touchEventHandler.enabled) { 1.696 + let isReloadNeeded = this.touchEventHandler.start(); 1.697 + this.touchbutton.setAttribute("checked", "true"); 1.698 + return isReloadNeeded; 1.699 + } 1.700 + return false; 1.701 + }, 1.702 + 1.703 + disableTouch: function RUI_disableTouch() { 1.704 + if (this.touchEventHandler.enabled) { 1.705 + this.touchEventHandler.stop(); 1.706 + this.touchbutton.removeAttribute("checked"); 1.707 + } 1.708 + }, 1.709 + 1.710 + hideTouchNotification: function RUI_hideTouchNotification() { 1.711 + let nbox = this.mainWindow.gBrowser.getNotificationBox(this.browser); 1.712 + let n = nbox.getNotificationWithValue("responsive-ui-need-reload"); 1.713 + if (n) { 1.714 + n.close(); 1.715 + } 1.716 + }, 1.717 + 1.718 + toggleTouch: function RUI_toggleTouch() { 1.719 + this.hideTouchNotification(); 1.720 + if (this.touchEventHandler.enabled) { 1.721 + this.disableTouch(); 1.722 + } else { 1.723 + let isReloadNeeded = this.enableTouch(); 1.724 + if (isReloadNeeded) { 1.725 + if (Services.prefs.getBoolPref("devtools.responsiveUI.no-reload-notification")) { 1.726 + return; 1.727 + } 1.728 + 1.729 + let nbox = this.mainWindow.gBrowser.getNotificationBox(this.browser); 1.730 + 1.731 + var buttons = [{ 1.732 + label: this.strings.GetStringFromName("responsiveUI.notificationReload"), 1.733 + callback: () => { 1.734 + this.browser.reload(); 1.735 + }, 1.736 + accessKey: this.strings.GetStringFromName("responsiveUI.notificationReload_accesskey"), 1.737 + }, { 1.738 + label: this.strings.GetStringFromName("responsiveUI.dontShowReloadNotification"), 1.739 + callback: function() { 1.740 + Services.prefs.setBoolPref("devtools.responsiveUI.no-reload-notification", true); 1.741 + }, 1.742 + accessKey: this.strings.GetStringFromName("responsiveUI.dontShowReloadNotification_accesskey"), 1.743 + }]; 1.744 + 1.745 + nbox.appendNotification( 1.746 + this.strings.GetStringFromName("responsiveUI.needReload"), 1.747 + "responsive-ui-need-reload", 1.748 + null, 1.749 + nbox.PRIORITY_INFO_LOW, 1.750 + buttons); 1.751 + } 1.752 + } 1.753 + }, 1.754 + 1.755 + /** 1.756 + * Change the size of the browser. 1.757 + * 1.758 + * @param aWidth width of the browser. 1.759 + * @param aHeight height of the browser. 1.760 + */ 1.761 + setSize: function RUI_setSize(aWidth, aHeight) { 1.762 + aWidth = Math.min(Math.max(aWidth, MIN_WIDTH), MAX_WIDTH); 1.763 + aHeight = Math.min(Math.max(aHeight, MIN_HEIGHT), MAX_HEIGHT); 1.764 + 1.765 + // We resize the containing stack. 1.766 + let style = "max-width: %width;" + 1.767 + "min-width: %width;" + 1.768 + "max-height: %height;" + 1.769 + "min-height: %height;"; 1.770 + 1.771 + style = style.replace(/%width/g, aWidth + "px"); 1.772 + style = style.replace(/%height/g, aHeight + "px"); 1.773 + 1.774 + this.stack.setAttribute("style", style); 1.775 + 1.776 + if (!this.ignoreY) 1.777 + this.resizeBarV.setAttribute("top", Math.round(aHeight / 2)); 1.778 + if (!this.ignoreX) 1.779 + this.resizeBarH.setAttribute("left", Math.round(aWidth / 2)); 1.780 + 1.781 + let selectedPreset = this.menuitems.get(this.selectedItem); 1.782 + 1.783 + // We uptate the custom menuitem if we are using it 1.784 + if (selectedPreset.custom) { 1.785 + selectedPreset.width = aWidth; 1.786 + selectedPreset.height = aHeight; 1.787 + 1.788 + this.setMenuLabel(this.selectedItem, selectedPreset); 1.789 + } 1.790 + }, 1.791 + 1.792 + /** 1.793 + * Start the process of resizing the browser. 1.794 + * 1.795 + * @param aEvent 1.796 + */ 1.797 + startResizing: function RUI_startResizing(aEvent) { 1.798 + let selectedPreset = this.menuitems.get(this.selectedItem); 1.799 + 1.800 + if (!selectedPreset.custom) { 1.801 + this.customPreset.width = this.rotateValue ? selectedPreset.height : selectedPreset.width; 1.802 + this.customPreset.height = this.rotateValue ? selectedPreset.width : selectedPreset.height; 1.803 + 1.804 + let menuitem = this.customMenuitem; 1.805 + this.setMenuLabel(menuitem, this.customPreset); 1.806 + 1.807 + this.currentPresetKey = this.customPreset.key; 1.808 + this.menulist.selectedItem = menuitem; 1.809 + } 1.810 + this.mainWindow.addEventListener("mouseup", this.bound_stopResizing, true); 1.811 + this.mainWindow.addEventListener("mousemove", this.bound_onDrag, true); 1.812 + this.container.style.pointerEvents = "none"; 1.813 + 1.814 + this._resizing = true; 1.815 + this.stack.setAttribute("notransition", "true"); 1.816 + 1.817 + this.lastScreenX = aEvent.screenX; 1.818 + this.lastScreenY = aEvent.screenY; 1.819 + 1.820 + this.ignoreY = (aEvent.target === this.resizeBarV); 1.821 + this.ignoreX = (aEvent.target === this.resizeBarH); 1.822 + 1.823 + this.isResizing = true; 1.824 + }, 1.825 + 1.826 + /** 1.827 + * Resizing on mouse move. 1.828 + * 1.829 + * @param aEvent 1.830 + */ 1.831 + onDrag: function RUI_onDrag(aEvent) { 1.832 + let shift = aEvent.shiftKey; 1.833 + let ctrl = !aEvent.shiftKey && aEvent.ctrlKey; 1.834 + 1.835 + let screenX = aEvent.screenX; 1.836 + let screenY = aEvent.screenY; 1.837 + 1.838 + let deltaX = screenX - this.lastScreenX; 1.839 + let deltaY = screenY - this.lastScreenY; 1.840 + 1.841 + if (this.ignoreY) 1.842 + deltaY = 0; 1.843 + if (this.ignoreX) 1.844 + deltaX = 0; 1.845 + 1.846 + if (ctrl) { 1.847 + deltaX /= SLOW_RATIO; 1.848 + deltaY /= SLOW_RATIO; 1.849 + } 1.850 + 1.851 + let width = this.customPreset.width + deltaX; 1.852 + let height = this.customPreset.height + deltaY; 1.853 + 1.854 + if (shift) { 1.855 + let roundedWidth, roundedHeight; 1.856 + roundedWidth = 10 * Math.floor(width / ROUND_RATIO); 1.857 + roundedHeight = 10 * Math.floor(height / ROUND_RATIO); 1.858 + screenX += roundedWidth - width; 1.859 + screenY += roundedHeight - height; 1.860 + width = roundedWidth; 1.861 + height = roundedHeight; 1.862 + } 1.863 + 1.864 + if (width < MIN_WIDTH) { 1.865 + width = MIN_WIDTH; 1.866 + } else { 1.867 + this.lastScreenX = screenX; 1.868 + } 1.869 + 1.870 + if (height < MIN_HEIGHT) { 1.871 + height = MIN_HEIGHT; 1.872 + } else { 1.873 + this.lastScreenY = screenY; 1.874 + } 1.875 + 1.876 + this.setSize(width, height); 1.877 + }, 1.878 + 1.879 + /** 1.880 + * Stop End resizing 1.881 + */ 1.882 + stopResizing: function RUI_stopResizing() { 1.883 + this.container.style.pointerEvents = "auto"; 1.884 + 1.885 + this.mainWindow.removeEventListener("mouseup", this.bound_stopResizing, true); 1.886 + this.mainWindow.removeEventListener("mousemove", this.bound_onDrag, true); 1.887 + 1.888 + this.saveCustomSize(); 1.889 + 1.890 + delete this._resizing; 1.891 + if (this.transitionsEnabled) { 1.892 + this.stack.removeAttribute("notransition"); 1.893 + } 1.894 + this.ignoreY = false; 1.895 + this.ignoreX = false; 1.896 + this.isResizing = false; 1.897 + }, 1.898 + 1.899 + /** 1.900 + * Store the custom size as a pref. 1.901 + */ 1.902 + saveCustomSize: function RUI_saveCustomSize() { 1.903 + Services.prefs.setIntPref("devtools.responsiveUI.customWidth", this.customPreset.width); 1.904 + Services.prefs.setIntPref("devtools.responsiveUI.customHeight", this.customPreset.height); 1.905 + }, 1.906 + 1.907 + /** 1.908 + * Store the current preset as a pref. 1.909 + */ 1.910 + saveCurrentPreset: function RUI_saveCurrentPreset() { 1.911 + Services.prefs.setCharPref("devtools.responsiveUI.currentPreset", this.currentPresetKey); 1.912 + Services.prefs.setBoolPref("devtools.responsiveUI.rotate", this.rotateValue); 1.913 + }, 1.914 + 1.915 + /** 1.916 + * Store the list of all registered presets as a pref. 1.917 + */ 1.918 + savePresets: function RUI_savePresets() { 1.919 + // We exclude the custom one 1.920 + let registeredPresets = this.presets.filter(function (aPreset) { 1.921 + return !aPreset.custom; 1.922 + }); 1.923 + 1.924 + Services.prefs.setCharPref("devtools.responsiveUI.presets", JSON.stringify(registeredPresets)); 1.925 + }, 1.926 +} 1.927 + 1.928 +XPCOMUtils.defineLazyGetter(ResponsiveUI.prototype, "strings", function () { 1.929 + return Services.strings.createBundle("chrome://browser/locale/devtools/responsiveUI.properties"); 1.930 +});