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.

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

mercurial