browser/devtools/responsivedesign/responsivedesign.jsm

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

michael@0 1 /* -*- Mode: Javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
michael@0 2 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
michael@0 3 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 4 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 6
michael@0 7 const Ci = Components.interfaces;
michael@0 8 const Cu = Components.utils;
michael@0 9
michael@0 10 Cu.import("resource://gre/modules/Services.jsm");
michael@0 11 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
michael@0 12 Cu.import("resource:///modules/devtools/gDevTools.jsm");
michael@0 13 Cu.import("resource:///modules/devtools/FloatingScrollbars.jsm");
michael@0 14 Cu.import("resource://gre/modules/devtools/event-emitter.js");
michael@0 15
michael@0 16 var require = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools.require;
michael@0 17 let Telemetry = require("devtools/shared/telemetry");
michael@0 18 let {TouchEventHandler} = require("devtools/touch-events");
michael@0 19
michael@0 20 this.EXPORTED_SYMBOLS = ["ResponsiveUIManager"];
michael@0 21
michael@0 22 const MIN_WIDTH = 50;
michael@0 23 const MIN_HEIGHT = 50;
michael@0 24
michael@0 25 const MAX_WIDTH = 10000;
michael@0 26 const MAX_HEIGHT = 10000;
michael@0 27
michael@0 28 const SLOW_RATIO = 6;
michael@0 29 const ROUND_RATIO = 10;
michael@0 30
michael@0 31 this.ResponsiveUIManager = {
michael@0 32 /**
michael@0 33 * Check if the a tab is in a responsive mode.
michael@0 34 * Leave the responsive mode if active,
michael@0 35 * active the responsive mode if not active.
michael@0 36 *
michael@0 37 * @param aWindow the main window.
michael@0 38 * @param aTab the tab targeted.
michael@0 39 */
michael@0 40 toggle: function(aWindow, aTab) {
michael@0 41 if (aTab.__responsiveUI) {
michael@0 42 aTab.__responsiveUI.close();
michael@0 43 } else {
michael@0 44 new ResponsiveUI(aWindow, aTab);
michael@0 45 }
michael@0 46 },
michael@0 47
michael@0 48 /**
michael@0 49 * Returns true if responsive view is active for the provided tab.
michael@0 50 *
michael@0 51 * @param aTab the tab targeted.
michael@0 52 */
michael@0 53 isActiveForTab: function(aTab) {
michael@0 54 return !!aTab.__responsiveUI;
michael@0 55 },
michael@0 56
michael@0 57 /**
michael@0 58 * Handle gcli commands.
michael@0 59 *
michael@0 60 * @param aWindow the browser window.
michael@0 61 * @param aTab the tab targeted.
michael@0 62 * @param aCommand the command name.
michael@0 63 * @param aArgs command arguments.
michael@0 64 */
michael@0 65 handleGcliCommand: function(aWindow, aTab, aCommand, aArgs) {
michael@0 66 switch (aCommand) {
michael@0 67 case "resize to":
michael@0 68 if (!aTab.__responsiveUI) {
michael@0 69 new ResponsiveUI(aWindow, aTab);
michael@0 70 }
michael@0 71 aTab.__responsiveUI.setSize(aArgs.width, aArgs.height);
michael@0 72 break;
michael@0 73 case "resize on":
michael@0 74 if (!aTab.__responsiveUI) {
michael@0 75 new ResponsiveUI(aWindow, aTab);
michael@0 76 }
michael@0 77 break;
michael@0 78 case "resize off":
michael@0 79 if (aTab.__responsiveUI) {
michael@0 80 aTab.__responsiveUI.close();
michael@0 81 }
michael@0 82 break;
michael@0 83 case "resize toggle":
michael@0 84 this.toggle(aWindow, aTab);
michael@0 85 default:
michael@0 86 }
michael@0 87 }
michael@0 88 }
michael@0 89
michael@0 90 EventEmitter.decorate(ResponsiveUIManager);
michael@0 91
michael@0 92 let presets = [
michael@0 93 // Phones
michael@0 94 {key: "320x480", width: 320, height: 480}, // iPhone, B2G, with <meta viewport>
michael@0 95 {key: "360x640", width: 360, height: 640}, // Android 4, phones, with <meta viewport>
michael@0 96
michael@0 97 // Tablets
michael@0 98 {key: "768x1024", width: 768, height: 1024}, // iPad, with <meta viewport>
michael@0 99 {key: "800x1280", width: 800, height: 1280}, // Android 4, Tablet, with <meta viewport>
michael@0 100
michael@0 101 // Default width for mobile browsers, no <meta viewport>
michael@0 102 {key: "980x1280", width: 980, height: 1280},
michael@0 103
michael@0 104 // Computer
michael@0 105 {key: "1280x600", width: 1280, height: 600},
michael@0 106 {key: "1920x900", width: 1920, height: 900},
michael@0 107 ];
michael@0 108
michael@0 109 function ResponsiveUI(aWindow, aTab)
michael@0 110 {
michael@0 111 this.mainWindow = aWindow;
michael@0 112 this.tab = aTab;
michael@0 113 this.tabContainer = aWindow.gBrowser.tabContainer;
michael@0 114 this.browser = aTab.linkedBrowser;
michael@0 115 this.chromeDoc = aWindow.document;
michael@0 116 this.container = aWindow.gBrowser.getBrowserContainer(this.browser);
michael@0 117 this.stack = this.container.querySelector(".browserStack");
michael@0 118 this._telemetry = new Telemetry();
michael@0 119 this._floatingScrollbars = !this.mainWindow.matchMedia("(-moz-overlay-scrollbars)").matches;
michael@0 120
michael@0 121
michael@0 122 // Try to load presets from prefs
michael@0 123 if (Services.prefs.prefHasUserValue("devtools.responsiveUI.presets")) {
michael@0 124 try {
michael@0 125 presets = JSON.parse(Services.prefs.getCharPref("devtools.responsiveUI.presets"));
michael@0 126 } catch(e) {
michael@0 127 // User pref is malformated.
michael@0 128 Cu.reportError("Could not parse pref `devtools.responsiveUI.presets`: " + e);
michael@0 129 }
michael@0 130 }
michael@0 131
michael@0 132 this.customPreset = {key: "custom", custom: true};
michael@0 133
michael@0 134 if (Array.isArray(presets)) {
michael@0 135 this.presets = [this.customPreset].concat(presets);
michael@0 136 } else {
michael@0 137 Cu.reportError("Presets value (devtools.responsiveUI.presets) is malformated.");
michael@0 138 this.presets = [this.customPreset];
michael@0 139 }
michael@0 140
michael@0 141 try {
michael@0 142 let width = Services.prefs.getIntPref("devtools.responsiveUI.customWidth");
michael@0 143 let height = Services.prefs.getIntPref("devtools.responsiveUI.customHeight");
michael@0 144 this.customPreset.width = Math.min(MAX_WIDTH, width);
michael@0 145 this.customPreset.height = Math.min(MAX_HEIGHT, height);
michael@0 146
michael@0 147 this.currentPresetKey = Services.prefs.getCharPref("devtools.responsiveUI.currentPreset");
michael@0 148 } catch(e) {
michael@0 149 // Default size. The first preset (custom) is the one that will be used.
michael@0 150 let bbox = this.stack.getBoundingClientRect();
michael@0 151
michael@0 152 this.customPreset.width = bbox.width - 40; // horizontal padding of the container
michael@0 153 this.customPreset.height = bbox.height - 80; // vertical padding + toolbar height
michael@0 154
michael@0 155 this.currentPresetKey = this.presets[1].key; // most common preset
michael@0 156 }
michael@0 157
michael@0 158 this.container.setAttribute("responsivemode", "true");
michael@0 159 this.stack.setAttribute("responsivemode", "true");
michael@0 160
michael@0 161 // Let's bind some callbacks.
michael@0 162 this.bound_onPageLoad = this.onPageLoad.bind(this);
michael@0 163 this.bound_onPageUnload = this.onPageUnload.bind(this);
michael@0 164 this.bound_presetSelected = this.presetSelected.bind(this);
michael@0 165 this.bound_addPreset = this.addPreset.bind(this);
michael@0 166 this.bound_removePreset = this.removePreset.bind(this);
michael@0 167 this.bound_rotate = this.rotate.bind(this);
michael@0 168 this.bound_screenshot = () => this.screenshot();
michael@0 169 this.bound_touch = this.toggleTouch.bind(this);
michael@0 170 this.bound_close = this.close.bind(this);
michael@0 171 this.bound_startResizing = this.startResizing.bind(this);
michael@0 172 this.bound_stopResizing = this.stopResizing.bind(this);
michael@0 173 this.bound_onDrag = this.onDrag.bind(this);
michael@0 174 this.bound_onKeypress = this.onKeypress.bind(this);
michael@0 175
michael@0 176 // Events
michael@0 177 this.tab.addEventListener("TabClose", this);
michael@0 178 this.tabContainer.addEventListener("TabSelect", this);
michael@0 179 this.mainWindow.document.addEventListener("keypress", this.bound_onKeypress, false);
michael@0 180
michael@0 181 this.buildUI();
michael@0 182 this.checkMenus();
michael@0 183
michael@0 184 this.docShell = this.browser.contentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
michael@0 185 .getInterface(Ci.nsIWebNavigation)
michael@0 186 .QueryInterface(Ci.nsIDocShell);
michael@0 187
michael@0 188 this._deviceSizeWasPageSize = this.docShell.deviceSizeIsPageSize;
michael@0 189 this.docShell.deviceSizeIsPageSize = true;
michael@0 190
michael@0 191 try {
michael@0 192 if (Services.prefs.getBoolPref("devtools.responsiveUI.rotate")) {
michael@0 193 this.rotate();
michael@0 194 }
michael@0 195 } catch(e) {}
michael@0 196
michael@0 197 if (this._floatingScrollbars)
michael@0 198 switchToFloatingScrollbars(this.tab);
michael@0 199
michael@0 200 this.tab.__responsiveUI = this;
michael@0 201
michael@0 202 this._telemetry.toolOpened("responsive");
michael@0 203
michael@0 204 // Touch events support
michael@0 205 this.touchEnableBefore = false;
michael@0 206 this.touchEventHandler = new TouchEventHandler(this.browser);
michael@0 207
michael@0 208 this.browser.addEventListener("load", this.bound_onPageLoad, true);
michael@0 209 this.browser.addEventListener("unload", this.bound_onPageUnload, true);
michael@0 210
michael@0 211 if (this.browser.contentWindow.document &&
michael@0 212 this.browser.contentWindow.document.readyState == "complete") {
michael@0 213 this.onPageLoad();
michael@0 214 }
michael@0 215
michael@0 216 ResponsiveUIManager.emit("on", this.tab, this);
michael@0 217 }
michael@0 218
michael@0 219 ResponsiveUI.prototype = {
michael@0 220 _transitionsEnabled: true,
michael@0 221 get transitionsEnabled() this._transitionsEnabled,
michael@0 222 set transitionsEnabled(aValue) {
michael@0 223 this._transitionsEnabled = aValue;
michael@0 224 if (aValue && !this._resizing && this.stack.hasAttribute("responsivemode")) {
michael@0 225 this.stack.removeAttribute("notransition");
michael@0 226 } else if (!aValue) {
michael@0 227 this.stack.setAttribute("notransition", "true");
michael@0 228 }
michael@0 229 },
michael@0 230
michael@0 231 /**
michael@0 232 * Window onload / onunload
michael@0 233 */
michael@0 234 onPageLoad: function() {
michael@0 235 this.touchEventHandler = new TouchEventHandler(this.browser);
michael@0 236 if (this.touchEnableBefore) {
michael@0 237 this.enableTouch();
michael@0 238 }
michael@0 239 },
michael@0 240
michael@0 241 onPageUnload: function(evt) {
michael@0 242 // Ignore sub frames unload events
michael@0 243 if (evt.target != this.browser.contentDocument)
michael@0 244 return;
michael@0 245 if (this.closing)
michael@0 246 return;
michael@0 247 if (this.touchEventHandler) {
michael@0 248 this.touchEnableBefore = this.touchEventHandler.enabled;
michael@0 249 this.disableTouch();
michael@0 250 delete this.touchEventHandler;
michael@0 251 }
michael@0 252 },
michael@0 253
michael@0 254 /**
michael@0 255 * Destroy the nodes. Remove listeners. Reset the style.
michael@0 256 */
michael@0 257 close: function RUI_unload() {
michael@0 258 if (this.closing)
michael@0 259 return;
michael@0 260 this.closing = true;
michael@0 261
michael@0 262 this.docShell.deviceSizeIsPageSize = this._deviceSizeWasPageSize;
michael@0 263
michael@0 264 this.browser.removeEventListener("load", this.bound_onPageLoad, true);
michael@0 265 this.browser.removeEventListener("unload", this.bound_onPageUnload, true);
michael@0 266
michael@0 267 if (this._floatingScrollbars)
michael@0 268 switchToNativeScrollbars(this.tab);
michael@0 269
michael@0 270 this.unCheckMenus();
michael@0 271 // Reset style of the stack.
michael@0 272 let style = "max-width: none;" +
michael@0 273 "min-width: 0;" +
michael@0 274 "max-height: none;" +
michael@0 275 "min-height: 0;";
michael@0 276 this.stack.setAttribute("style", style);
michael@0 277
michael@0 278 if (this.isResizing)
michael@0 279 this.stopResizing();
michael@0 280
michael@0 281 // Remove listeners.
michael@0 282 this.mainWindow.document.removeEventListener("keypress", this.bound_onKeypress, false);
michael@0 283 this.menulist.removeEventListener("select", this.bound_presetSelected, true);
michael@0 284 this.tab.removeEventListener("TabClose", this);
michael@0 285 this.tabContainer.removeEventListener("TabSelect", this);
michael@0 286 this.rotatebutton.removeEventListener("command", this.bound_rotate, true);
michael@0 287 this.screenshotbutton.removeEventListener("command", this.bound_screenshot, true);
michael@0 288 this.touchbutton.removeEventListener("command", this.bound_touch, true);
michael@0 289 this.closebutton.removeEventListener("command", this.bound_close, true);
michael@0 290 this.addbutton.removeEventListener("command", this.bound_addPreset, true);
michael@0 291 this.removebutton.removeEventListener("command", this.bound_removePreset, true);
michael@0 292
michael@0 293 // Removed elements.
michael@0 294 this.container.removeChild(this.toolbar);
michael@0 295 this.stack.removeChild(this.resizer);
michael@0 296 this.stack.removeChild(this.resizeBarV);
michael@0 297 this.stack.removeChild(this.resizeBarH);
michael@0 298
michael@0 299 // Unset the responsive mode.
michael@0 300 this.container.removeAttribute("responsivemode");
michael@0 301 this.stack.removeAttribute("responsivemode");
michael@0 302
michael@0 303 delete this.docShell;
michael@0 304 delete this.tab.__responsiveUI;
michael@0 305 if (this.touchEventHandler)
michael@0 306 this.touchEventHandler.stop();
michael@0 307 this._telemetry.toolClosed("responsive");
michael@0 308 ResponsiveUIManager.emit("off", this.tab, this);
michael@0 309 },
michael@0 310
michael@0 311 /**
michael@0 312 * Handle keypressed.
michael@0 313 *
michael@0 314 * @param aEvent
michael@0 315 */
michael@0 316 onKeypress: function RUI_onKeypress(aEvent) {
michael@0 317 if (aEvent.keyCode == this.mainWindow.KeyEvent.DOM_VK_ESCAPE &&
michael@0 318 this.mainWindow.gBrowser.selectedBrowser == this.browser) {
michael@0 319
michael@0 320 aEvent.preventDefault();
michael@0 321 aEvent.stopPropagation();
michael@0 322 this.close();
michael@0 323 }
michael@0 324 },
michael@0 325
michael@0 326 /**
michael@0 327 * Handle events
michael@0 328 */
michael@0 329 handleEvent: function (aEvent) {
michael@0 330 switch (aEvent.type) {
michael@0 331 case "TabClose":
michael@0 332 this.close();
michael@0 333 break;
michael@0 334 case "TabSelect":
michael@0 335 if (this.tab.selected) {
michael@0 336 this.checkMenus();
michael@0 337 } else if (!this.mainWindow.gBrowser.selectedTab.responsiveUI) {
michael@0 338 this.unCheckMenus();
michael@0 339 }
michael@0 340 break;
michael@0 341 }
michael@0 342 },
michael@0 343
michael@0 344 /**
michael@0 345 * Check the menu items.
michael@0 346 */
michael@0 347 checkMenus: function RUI_checkMenus() {
michael@0 348 this.chromeDoc.getElementById("Tools:ResponsiveUI").setAttribute("checked", "true");
michael@0 349 },
michael@0 350
michael@0 351 /**
michael@0 352 * Uncheck the menu items.
michael@0 353 */
michael@0 354 unCheckMenus: function RUI_unCheckMenus() {
michael@0 355 this.chromeDoc.getElementById("Tools:ResponsiveUI").setAttribute("checked", "false");
michael@0 356 },
michael@0 357
michael@0 358 /**
michael@0 359 * Build the toolbar and the resizers.
michael@0 360 *
michael@0 361 * <vbox class="browserContainer"> From tabbrowser.xml
michael@0 362 * <toolbar class="devtools-responsiveui-toolbar">
michael@0 363 * <menulist class="devtools-responsiveui-menulist"/> // presets
michael@0 364 * <toolbarbutton tabindex="0" class="devtools-responsiveui-toolbarbutton" tooltiptext="rotate"/> // rotate
michael@0 365 * <toolbarbutton tabindex="0" class="devtools-responsiveui-toolbarbutton" tooltiptext="screenshot"/> // screenshot
michael@0 366 * <toolbarbutton tabindex="0" class="devtools-responsiveui-toolbarbutton" tooltiptext="Leave Responsive Design View"/> // close
michael@0 367 * </toolbar>
michael@0 368 * <stack class="browserStack"> From tabbrowser.xml
michael@0 369 * <browser/>
michael@0 370 * <box class="devtools-responsiveui-resizehandle" bottom="0" right="0"/>
michael@0 371 * <box class="devtools-responsiveui-resizebarV" top="0" right="0"/>
michael@0 372 * <box class="devtools-responsiveui-resizebarH" bottom="0" left="0"/>
michael@0 373 * </stack>
michael@0 374 * </vbox>
michael@0 375 */
michael@0 376 buildUI: function RUI_buildUI() {
michael@0 377 // Toolbar
michael@0 378 this.toolbar = this.chromeDoc.createElement("toolbar");
michael@0 379 this.toolbar.className = "devtools-responsiveui-toolbar";
michael@0 380
michael@0 381 this.menulist = this.chromeDoc.createElement("menulist");
michael@0 382 this.menulist.className = "devtools-responsiveui-menulist";
michael@0 383
michael@0 384 this.menulist.addEventListener("select", this.bound_presetSelected, true);
michael@0 385
michael@0 386 this.menuitems = new Map();
michael@0 387
michael@0 388 let menupopup = this.chromeDoc.createElement("menupopup");
michael@0 389 this.registerPresets(menupopup);
michael@0 390 this.menulist.appendChild(menupopup);
michael@0 391
michael@0 392 this.addbutton = this.chromeDoc.createElement("menuitem");
michael@0 393 this.addbutton.setAttribute("label", this.strings.GetStringFromName("responsiveUI.addPreset"));
michael@0 394 this.addbutton.addEventListener("command", this.bound_addPreset, true);
michael@0 395
michael@0 396 this.removebutton = this.chromeDoc.createElement("menuitem");
michael@0 397 this.removebutton.setAttribute("label", this.strings.GetStringFromName("responsiveUI.removePreset"));
michael@0 398 this.removebutton.addEventListener("command", this.bound_removePreset, true);
michael@0 399
michael@0 400 menupopup.appendChild(this.chromeDoc.createElement("menuseparator"));
michael@0 401 menupopup.appendChild(this.addbutton);
michael@0 402 menupopup.appendChild(this.removebutton);
michael@0 403
michael@0 404 this.rotatebutton = this.chromeDoc.createElement("toolbarbutton");
michael@0 405 this.rotatebutton.setAttribute("tabindex", "0");
michael@0 406 this.rotatebutton.setAttribute("tooltiptext", this.strings.GetStringFromName("responsiveUI.rotate2"));
michael@0 407 this.rotatebutton.className = "devtools-responsiveui-toolbarbutton devtools-responsiveui-rotate";
michael@0 408 this.rotatebutton.addEventListener("command", this.bound_rotate, true);
michael@0 409
michael@0 410 this.screenshotbutton = this.chromeDoc.createElement("toolbarbutton");
michael@0 411 this.screenshotbutton.setAttribute("tabindex", "0");
michael@0 412 this.screenshotbutton.setAttribute("tooltiptext", this.strings.GetStringFromName("responsiveUI.screenshot"));
michael@0 413 this.screenshotbutton.className = "devtools-responsiveui-toolbarbutton devtools-responsiveui-screenshot";
michael@0 414 this.screenshotbutton.addEventListener("command", this.bound_screenshot, true);
michael@0 415
michael@0 416 this.touchbutton = this.chromeDoc.createElement("toolbarbutton");
michael@0 417 this.touchbutton.setAttribute("tabindex", "0");
michael@0 418 this.touchbutton.setAttribute("tooltiptext", this.strings.GetStringFromName("responsiveUI.touch"));
michael@0 419 this.touchbutton.className = "devtools-responsiveui-toolbarbutton devtools-responsiveui-touch";
michael@0 420 this.touchbutton.addEventListener("command", this.bound_touch, true);
michael@0 421
michael@0 422 this.closebutton = this.chromeDoc.createElement("toolbarbutton");
michael@0 423 this.closebutton.setAttribute("tabindex", "0");
michael@0 424 this.closebutton.className = "devtools-responsiveui-toolbarbutton devtools-responsiveui-close";
michael@0 425 this.closebutton.setAttribute("tooltiptext", this.strings.GetStringFromName("responsiveUI.close"));
michael@0 426 this.closebutton.addEventListener("command", this.bound_close, true);
michael@0 427
michael@0 428 this.toolbar.appendChild(this.closebutton);
michael@0 429 this.toolbar.appendChild(this.menulist);
michael@0 430 this.toolbar.appendChild(this.rotatebutton);
michael@0 431 this.toolbar.appendChild(this.touchbutton);
michael@0 432 this.toolbar.appendChild(this.screenshotbutton);
michael@0 433
michael@0 434 // Resizers
michael@0 435 let resizerTooltip = this.strings.GetStringFromName("responsiveUI.resizerTooltip");
michael@0 436 this.resizer = this.chromeDoc.createElement("box");
michael@0 437 this.resizer.className = "devtools-responsiveui-resizehandle";
michael@0 438 this.resizer.setAttribute("right", "0");
michael@0 439 this.resizer.setAttribute("bottom", "0");
michael@0 440 this.resizer.setAttribute("tooltiptext", resizerTooltip);
michael@0 441 this.resizer.onmousedown = this.bound_startResizing;
michael@0 442
michael@0 443 this.resizeBarV = this.chromeDoc.createElement("box");
michael@0 444 this.resizeBarV.className = "devtools-responsiveui-resizebarV";
michael@0 445 this.resizeBarV.setAttribute("top", "0");
michael@0 446 this.resizeBarV.setAttribute("right", "0");
michael@0 447 this.resizeBarV.setAttribute("tooltiptext", resizerTooltip);
michael@0 448 this.resizeBarV.onmousedown = this.bound_startResizing;
michael@0 449
michael@0 450 this.resizeBarH = this.chromeDoc.createElement("box");
michael@0 451 this.resizeBarH.className = "devtools-responsiveui-resizebarH";
michael@0 452 this.resizeBarH.setAttribute("bottom", "0");
michael@0 453 this.resizeBarH.setAttribute("left", "0");
michael@0 454 this.resizeBarH.setAttribute("tooltiptext", resizerTooltip);
michael@0 455 this.resizeBarH.onmousedown = this.bound_startResizing;
michael@0 456
michael@0 457 this.container.insertBefore(this.toolbar, this.stack);
michael@0 458 this.stack.appendChild(this.resizer);
michael@0 459 this.stack.appendChild(this.resizeBarV);
michael@0 460 this.stack.appendChild(this.resizeBarH);
michael@0 461 },
michael@0 462
michael@0 463 /**
michael@0 464 * Build the presets list and append it to the menupopup.
michael@0 465 *
michael@0 466 * @param aParent menupopup.
michael@0 467 */
michael@0 468 registerPresets: function RUI_registerPresets(aParent) {
michael@0 469 let fragment = this.chromeDoc.createDocumentFragment();
michael@0 470 let doc = this.chromeDoc;
michael@0 471
michael@0 472 for (let preset of this.presets) {
michael@0 473 let menuitem = doc.createElement("menuitem");
michael@0 474 menuitem.setAttribute("ispreset", true);
michael@0 475 this.menuitems.set(menuitem, preset);
michael@0 476
michael@0 477 if (preset.key === this.currentPresetKey) {
michael@0 478 menuitem.setAttribute("selected", "true");
michael@0 479 this.selectedItem = menuitem;
michael@0 480 }
michael@0 481
michael@0 482 if (preset.custom)
michael@0 483 this.customMenuitem = menuitem;
michael@0 484
michael@0 485 this.setMenuLabel(menuitem, preset);
michael@0 486 fragment.appendChild(menuitem);
michael@0 487 }
michael@0 488 aParent.appendChild(fragment);
michael@0 489 },
michael@0 490
michael@0 491 /**
michael@0 492 * Set the menuitem label of a preset.
michael@0 493 *
michael@0 494 * @param aMenuitem menuitem to edit.
michael@0 495 * @param aPreset associated preset.
michael@0 496 */
michael@0 497 setMenuLabel: function RUI_setMenuLabel(aMenuitem, aPreset) {
michael@0 498 let size = Math.round(aPreset.width) + "x" + Math.round(aPreset.height);
michael@0 499 if (aPreset.custom) {
michael@0 500 let str = this.strings.formatStringFromName("responsiveUI.customResolution", [size], 1);
michael@0 501 aMenuitem.setAttribute("label", str);
michael@0 502 } else if (aPreset.name != null && aPreset.name !== "") {
michael@0 503 let str = this.strings.formatStringFromName("responsiveUI.namedResolution", [size, aPreset.name], 2);
michael@0 504 aMenuitem.setAttribute("label", str);
michael@0 505 } else {
michael@0 506 aMenuitem.setAttribute("label", size);
michael@0 507 }
michael@0 508 },
michael@0 509
michael@0 510 /**
michael@0 511 * When a preset is selected, apply it.
michael@0 512 */
michael@0 513 presetSelected: function RUI_presetSelected() {
michael@0 514 if (this.menulist.selectedItem.getAttribute("ispreset") === "true") {
michael@0 515 this.selectedItem = this.menulist.selectedItem;
michael@0 516
michael@0 517 this.rotateValue = false;
michael@0 518 let selectedPreset = this.menuitems.get(this.selectedItem);
michael@0 519 this.loadPreset(selectedPreset);
michael@0 520 this.currentPresetKey = selectedPreset.key;
michael@0 521 this.saveCurrentPreset();
michael@0 522
michael@0 523 // Update the buttons hidden status according to the new selected preset
michael@0 524 if (selectedPreset == this.customPreset) {
michael@0 525 this.addbutton.hidden = false;
michael@0 526 this.removebutton.hidden = true;
michael@0 527 } else {
michael@0 528 this.addbutton.hidden = true;
michael@0 529 this.removebutton.hidden = false;
michael@0 530 }
michael@0 531 }
michael@0 532 },
michael@0 533
michael@0 534 /**
michael@0 535 * Apply a preset.
michael@0 536 *
michael@0 537 * @param aPreset preset to apply.
michael@0 538 */
michael@0 539 loadPreset: function RUI_loadPreset(aPreset) {
michael@0 540 this.setSize(aPreset.width, aPreset.height);
michael@0 541 },
michael@0 542
michael@0 543 /**
michael@0 544 * Add a preset to the list and the memory
michael@0 545 */
michael@0 546 addPreset: function RUI_addPreset() {
michael@0 547 let w = this.customPreset.width;
michael@0 548 let h = this.customPreset.height;
michael@0 549 let newName = {};
michael@0 550
michael@0 551 let title = this.strings.GetStringFromName("responsiveUI.customNamePromptTitle");
michael@0 552 let message = this.strings.formatStringFromName("responsiveUI.customNamePromptMsg", [w, h], 2);
michael@0 553 let promptOk = Services.prompt.prompt(null, title, message, newName, null, {});
michael@0 554
michael@0 555 if (!promptOk) {
michael@0 556 // Prompt has been cancelled
michael@0 557 let menuitem = this.customMenuitem;
michael@0 558 this.menulist.selectedItem = menuitem;
michael@0 559 this.currentPresetKey = this.customPreset.key;
michael@0 560 return;
michael@0 561 }
michael@0 562
michael@0 563 let newPreset = {
michael@0 564 key: w + "x" + h,
michael@0 565 name: newName.value,
michael@0 566 width: w,
michael@0 567 height: h
michael@0 568 };
michael@0 569
michael@0 570 this.presets.push(newPreset);
michael@0 571
michael@0 572 // Sort the presets according to width/height ascending order
michael@0 573 this.presets.sort(function RUI_sortPresets(aPresetA, aPresetB) {
michael@0 574 // We keep custom preset at first
michael@0 575 if (aPresetA.custom && !aPresetB.custom) {
michael@0 576 return 1;
michael@0 577 }
michael@0 578 if (!aPresetA.custom && aPresetB.custom) {
michael@0 579 return -1;
michael@0 580 }
michael@0 581
michael@0 582 if (aPresetA.width === aPresetB.width) {
michael@0 583 if (aPresetA.height === aPresetB.height) {
michael@0 584 return 0;
michael@0 585 } else {
michael@0 586 return aPresetA.height > aPresetB.height;
michael@0 587 }
michael@0 588 } else {
michael@0 589 return aPresetA.width > aPresetB.width;
michael@0 590 }
michael@0 591 });
michael@0 592
michael@0 593 this.savePresets();
michael@0 594
michael@0 595 let newMenuitem = this.chromeDoc.createElement("menuitem");
michael@0 596 newMenuitem.setAttribute("ispreset", true);
michael@0 597 this.setMenuLabel(newMenuitem, newPreset);
michael@0 598
michael@0 599 this.menuitems.set(newMenuitem, newPreset);
michael@0 600 let idx = this.presets.indexOf(newPreset);
michael@0 601 let beforeMenuitem = this.menulist.firstChild.childNodes[idx + 1];
michael@0 602 this.menulist.firstChild.insertBefore(newMenuitem, beforeMenuitem);
michael@0 603
michael@0 604 this.menulist.selectedItem = newMenuitem;
michael@0 605 this.currentPresetKey = newPreset.key;
michael@0 606 this.saveCurrentPreset();
michael@0 607 },
michael@0 608
michael@0 609 /**
michael@0 610 * remove a preset from the list and the memory
michael@0 611 */
michael@0 612 removePreset: function RUI_removePreset() {
michael@0 613 let selectedPreset = this.menuitems.get(this.selectedItem);
michael@0 614 let w = selectedPreset.width;
michael@0 615 let h = selectedPreset.height;
michael@0 616
michael@0 617 this.presets.splice(this.presets.indexOf(selectedPreset), 1);
michael@0 618 this.menulist.firstChild.removeChild(this.selectedItem);
michael@0 619 this.menuitems.delete(this.selectedItem);
michael@0 620
michael@0 621 this.customPreset.width = w;
michael@0 622 this.customPreset.height = h;
michael@0 623 let menuitem = this.customMenuitem;
michael@0 624 this.setMenuLabel(menuitem, this.customPreset);
michael@0 625 this.menulist.selectedItem = menuitem;
michael@0 626 this.currentPresetKey = this.customPreset.key;
michael@0 627
michael@0 628 this.setSize(w, h);
michael@0 629
michael@0 630 this.savePresets();
michael@0 631 },
michael@0 632
michael@0 633 /**
michael@0 634 * Swap width and height.
michael@0 635 */
michael@0 636 rotate: function RUI_rotate() {
michael@0 637 let selectedPreset = this.menuitems.get(this.selectedItem);
michael@0 638 let width = this.rotateValue ? selectedPreset.height : selectedPreset.width;
michael@0 639 let height = this.rotateValue ? selectedPreset.width : selectedPreset.height;
michael@0 640
michael@0 641 this.setSize(height, width);
michael@0 642
michael@0 643 if (selectedPreset.custom) {
michael@0 644 this.saveCustomSize();
michael@0 645 } else {
michael@0 646 this.rotateValue = !this.rotateValue;
michael@0 647 this.saveCurrentPreset();
michael@0 648 }
michael@0 649 },
michael@0 650
michael@0 651 /**
michael@0 652 * Take a screenshot of the page.
michael@0 653 *
michael@0 654 * @param aFileName name of the screenshot file (used for tests).
michael@0 655 */
michael@0 656 screenshot: function RUI_screenshot(aFileName) {
michael@0 657 let window = this.browser.contentWindow;
michael@0 658 let document = window.document;
michael@0 659 let canvas = this.chromeDoc.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
michael@0 660
michael@0 661 let width = window.innerWidth;
michael@0 662 let height = window.innerHeight;
michael@0 663
michael@0 664 canvas.width = width;
michael@0 665 canvas.height = height;
michael@0 666
michael@0 667 let ctx = canvas.getContext("2d");
michael@0 668 ctx.drawWindow(window, window.scrollX, window.scrollY, width, height, "#fff");
michael@0 669
michael@0 670 let filename = aFileName;
michael@0 671
michael@0 672 if (!filename) {
michael@0 673 let date = new Date();
michael@0 674 let month = ("0" + (date.getMonth() + 1)).substr(-2, 2);
michael@0 675 let day = ("0" + (date.getDay() + 1)).substr(-2, 2);
michael@0 676 let dateString = [date.getFullYear(), month, day].join("-");
michael@0 677 let timeString = date.toTimeString().replace(/:/g, ".").split(" ")[0];
michael@0 678 filename = this.strings.formatStringFromName("responsiveUI.screenshotGeneratedFilename", [dateString, timeString], 2);
michael@0 679 }
michael@0 680
michael@0 681 canvas.toBlob(blob => {
michael@0 682 let chromeWindow = this.chromeDoc.defaultView;
michael@0 683 let url = chromeWindow.URL.createObjectURL(blob);
michael@0 684 chromeWindow.saveURL(url, filename + ".png", null, true, true, document.documentURIObject, document);
michael@0 685 });
michael@0 686 },
michael@0 687
michael@0 688 /**
michael@0 689 * Enable/Disable mouse -> touch events translation.
michael@0 690 */
michael@0 691 enableTouch: function RUI_enableTouch() {
michael@0 692 if (!this.touchEventHandler.enabled) {
michael@0 693 let isReloadNeeded = this.touchEventHandler.start();
michael@0 694 this.touchbutton.setAttribute("checked", "true");
michael@0 695 return isReloadNeeded;
michael@0 696 }
michael@0 697 return false;
michael@0 698 },
michael@0 699
michael@0 700 disableTouch: function RUI_disableTouch() {
michael@0 701 if (this.touchEventHandler.enabled) {
michael@0 702 this.touchEventHandler.stop();
michael@0 703 this.touchbutton.removeAttribute("checked");
michael@0 704 }
michael@0 705 },
michael@0 706
michael@0 707 hideTouchNotification: function RUI_hideTouchNotification() {
michael@0 708 let nbox = this.mainWindow.gBrowser.getNotificationBox(this.browser);
michael@0 709 let n = nbox.getNotificationWithValue("responsive-ui-need-reload");
michael@0 710 if (n) {
michael@0 711 n.close();
michael@0 712 }
michael@0 713 },
michael@0 714
michael@0 715 toggleTouch: function RUI_toggleTouch() {
michael@0 716 this.hideTouchNotification();
michael@0 717 if (this.touchEventHandler.enabled) {
michael@0 718 this.disableTouch();
michael@0 719 } else {
michael@0 720 let isReloadNeeded = this.enableTouch();
michael@0 721 if (isReloadNeeded) {
michael@0 722 if (Services.prefs.getBoolPref("devtools.responsiveUI.no-reload-notification")) {
michael@0 723 return;
michael@0 724 }
michael@0 725
michael@0 726 let nbox = this.mainWindow.gBrowser.getNotificationBox(this.browser);
michael@0 727
michael@0 728 var buttons = [{
michael@0 729 label: this.strings.GetStringFromName("responsiveUI.notificationReload"),
michael@0 730 callback: () => {
michael@0 731 this.browser.reload();
michael@0 732 },
michael@0 733 accessKey: this.strings.GetStringFromName("responsiveUI.notificationReload_accesskey"),
michael@0 734 }, {
michael@0 735 label: this.strings.GetStringFromName("responsiveUI.dontShowReloadNotification"),
michael@0 736 callback: function() {
michael@0 737 Services.prefs.setBoolPref("devtools.responsiveUI.no-reload-notification", true);
michael@0 738 },
michael@0 739 accessKey: this.strings.GetStringFromName("responsiveUI.dontShowReloadNotification_accesskey"),
michael@0 740 }];
michael@0 741
michael@0 742 nbox.appendNotification(
michael@0 743 this.strings.GetStringFromName("responsiveUI.needReload"),
michael@0 744 "responsive-ui-need-reload",
michael@0 745 null,
michael@0 746 nbox.PRIORITY_INFO_LOW,
michael@0 747 buttons);
michael@0 748 }
michael@0 749 }
michael@0 750 },
michael@0 751
michael@0 752 /**
michael@0 753 * Change the size of the browser.
michael@0 754 *
michael@0 755 * @param aWidth width of the browser.
michael@0 756 * @param aHeight height of the browser.
michael@0 757 */
michael@0 758 setSize: function RUI_setSize(aWidth, aHeight) {
michael@0 759 aWidth = Math.min(Math.max(aWidth, MIN_WIDTH), MAX_WIDTH);
michael@0 760 aHeight = Math.min(Math.max(aHeight, MIN_HEIGHT), MAX_HEIGHT);
michael@0 761
michael@0 762 // We resize the containing stack.
michael@0 763 let style = "max-width: %width;" +
michael@0 764 "min-width: %width;" +
michael@0 765 "max-height: %height;" +
michael@0 766 "min-height: %height;";
michael@0 767
michael@0 768 style = style.replace(/%width/g, aWidth + "px");
michael@0 769 style = style.replace(/%height/g, aHeight + "px");
michael@0 770
michael@0 771 this.stack.setAttribute("style", style);
michael@0 772
michael@0 773 if (!this.ignoreY)
michael@0 774 this.resizeBarV.setAttribute("top", Math.round(aHeight / 2));
michael@0 775 if (!this.ignoreX)
michael@0 776 this.resizeBarH.setAttribute("left", Math.round(aWidth / 2));
michael@0 777
michael@0 778 let selectedPreset = this.menuitems.get(this.selectedItem);
michael@0 779
michael@0 780 // We uptate the custom menuitem if we are using it
michael@0 781 if (selectedPreset.custom) {
michael@0 782 selectedPreset.width = aWidth;
michael@0 783 selectedPreset.height = aHeight;
michael@0 784
michael@0 785 this.setMenuLabel(this.selectedItem, selectedPreset);
michael@0 786 }
michael@0 787 },
michael@0 788
michael@0 789 /**
michael@0 790 * Start the process of resizing the browser.
michael@0 791 *
michael@0 792 * @param aEvent
michael@0 793 */
michael@0 794 startResizing: function RUI_startResizing(aEvent) {
michael@0 795 let selectedPreset = this.menuitems.get(this.selectedItem);
michael@0 796
michael@0 797 if (!selectedPreset.custom) {
michael@0 798 this.customPreset.width = this.rotateValue ? selectedPreset.height : selectedPreset.width;
michael@0 799 this.customPreset.height = this.rotateValue ? selectedPreset.width : selectedPreset.height;
michael@0 800
michael@0 801 let menuitem = this.customMenuitem;
michael@0 802 this.setMenuLabel(menuitem, this.customPreset);
michael@0 803
michael@0 804 this.currentPresetKey = this.customPreset.key;
michael@0 805 this.menulist.selectedItem = menuitem;
michael@0 806 }
michael@0 807 this.mainWindow.addEventListener("mouseup", this.bound_stopResizing, true);
michael@0 808 this.mainWindow.addEventListener("mousemove", this.bound_onDrag, true);
michael@0 809 this.container.style.pointerEvents = "none";
michael@0 810
michael@0 811 this._resizing = true;
michael@0 812 this.stack.setAttribute("notransition", "true");
michael@0 813
michael@0 814 this.lastScreenX = aEvent.screenX;
michael@0 815 this.lastScreenY = aEvent.screenY;
michael@0 816
michael@0 817 this.ignoreY = (aEvent.target === this.resizeBarV);
michael@0 818 this.ignoreX = (aEvent.target === this.resizeBarH);
michael@0 819
michael@0 820 this.isResizing = true;
michael@0 821 },
michael@0 822
michael@0 823 /**
michael@0 824 * Resizing on mouse move.
michael@0 825 *
michael@0 826 * @param aEvent
michael@0 827 */
michael@0 828 onDrag: function RUI_onDrag(aEvent) {
michael@0 829 let shift = aEvent.shiftKey;
michael@0 830 let ctrl = !aEvent.shiftKey && aEvent.ctrlKey;
michael@0 831
michael@0 832 let screenX = aEvent.screenX;
michael@0 833 let screenY = aEvent.screenY;
michael@0 834
michael@0 835 let deltaX = screenX - this.lastScreenX;
michael@0 836 let deltaY = screenY - this.lastScreenY;
michael@0 837
michael@0 838 if (this.ignoreY)
michael@0 839 deltaY = 0;
michael@0 840 if (this.ignoreX)
michael@0 841 deltaX = 0;
michael@0 842
michael@0 843 if (ctrl) {
michael@0 844 deltaX /= SLOW_RATIO;
michael@0 845 deltaY /= SLOW_RATIO;
michael@0 846 }
michael@0 847
michael@0 848 let width = this.customPreset.width + deltaX;
michael@0 849 let height = this.customPreset.height + deltaY;
michael@0 850
michael@0 851 if (shift) {
michael@0 852 let roundedWidth, roundedHeight;
michael@0 853 roundedWidth = 10 * Math.floor(width / ROUND_RATIO);
michael@0 854 roundedHeight = 10 * Math.floor(height / ROUND_RATIO);
michael@0 855 screenX += roundedWidth - width;
michael@0 856 screenY += roundedHeight - height;
michael@0 857 width = roundedWidth;
michael@0 858 height = roundedHeight;
michael@0 859 }
michael@0 860
michael@0 861 if (width < MIN_WIDTH) {
michael@0 862 width = MIN_WIDTH;
michael@0 863 } else {
michael@0 864 this.lastScreenX = screenX;
michael@0 865 }
michael@0 866
michael@0 867 if (height < MIN_HEIGHT) {
michael@0 868 height = MIN_HEIGHT;
michael@0 869 } else {
michael@0 870 this.lastScreenY = screenY;
michael@0 871 }
michael@0 872
michael@0 873 this.setSize(width, height);
michael@0 874 },
michael@0 875
michael@0 876 /**
michael@0 877 * Stop End resizing
michael@0 878 */
michael@0 879 stopResizing: function RUI_stopResizing() {
michael@0 880 this.container.style.pointerEvents = "auto";
michael@0 881
michael@0 882 this.mainWindow.removeEventListener("mouseup", this.bound_stopResizing, true);
michael@0 883 this.mainWindow.removeEventListener("mousemove", this.bound_onDrag, true);
michael@0 884
michael@0 885 this.saveCustomSize();
michael@0 886
michael@0 887 delete this._resizing;
michael@0 888 if (this.transitionsEnabled) {
michael@0 889 this.stack.removeAttribute("notransition");
michael@0 890 }
michael@0 891 this.ignoreY = false;
michael@0 892 this.ignoreX = false;
michael@0 893 this.isResizing = false;
michael@0 894 },
michael@0 895
michael@0 896 /**
michael@0 897 * Store the custom size as a pref.
michael@0 898 */
michael@0 899 saveCustomSize: function RUI_saveCustomSize() {
michael@0 900 Services.prefs.setIntPref("devtools.responsiveUI.customWidth", this.customPreset.width);
michael@0 901 Services.prefs.setIntPref("devtools.responsiveUI.customHeight", this.customPreset.height);
michael@0 902 },
michael@0 903
michael@0 904 /**
michael@0 905 * Store the current preset as a pref.
michael@0 906 */
michael@0 907 saveCurrentPreset: function RUI_saveCurrentPreset() {
michael@0 908 Services.prefs.setCharPref("devtools.responsiveUI.currentPreset", this.currentPresetKey);
michael@0 909 Services.prefs.setBoolPref("devtools.responsiveUI.rotate", this.rotateValue);
michael@0 910 },
michael@0 911
michael@0 912 /**
michael@0 913 * Store the list of all registered presets as a pref.
michael@0 914 */
michael@0 915 savePresets: function RUI_savePresets() {
michael@0 916 // We exclude the custom one
michael@0 917 let registeredPresets = this.presets.filter(function (aPreset) {
michael@0 918 return !aPreset.custom;
michael@0 919 });
michael@0 920
michael@0 921 Services.prefs.setCharPref("devtools.responsiveUI.presets", JSON.stringify(registeredPresets));
michael@0 922 },
michael@0 923 }
michael@0 924
michael@0 925 XPCOMUtils.defineLazyGetter(ResponsiveUI.prototype, "strings", function () {
michael@0 926 return Services.strings.createBundle("chrome://browser/locale/devtools/responsiveUI.properties");
michael@0 927 });

mercurial