michael@0: /* -*- Mode: Javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim: set ft=javascript ts=2 et sw=2 tw=80: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: const Ci = Components.interfaces; michael@0: const Cu = Components.utils; michael@0: michael@0: Cu.import("resource://gre/modules/Services.jsm"); michael@0: Cu.import("resource://gre/modules/XPCOMUtils.jsm"); michael@0: Cu.import("resource:///modules/devtools/gDevTools.jsm"); michael@0: Cu.import("resource:///modules/devtools/FloatingScrollbars.jsm"); michael@0: Cu.import("resource://gre/modules/devtools/event-emitter.js"); michael@0: michael@0: var require = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools.require; michael@0: let Telemetry = require("devtools/shared/telemetry"); michael@0: let {TouchEventHandler} = require("devtools/touch-events"); michael@0: michael@0: this.EXPORTED_SYMBOLS = ["ResponsiveUIManager"]; michael@0: michael@0: const MIN_WIDTH = 50; michael@0: const MIN_HEIGHT = 50; michael@0: michael@0: const MAX_WIDTH = 10000; michael@0: const MAX_HEIGHT = 10000; michael@0: michael@0: const SLOW_RATIO = 6; michael@0: const ROUND_RATIO = 10; michael@0: michael@0: this.ResponsiveUIManager = { michael@0: /** michael@0: * Check if the a tab is in a responsive mode. michael@0: * Leave the responsive mode if active, michael@0: * active the responsive mode if not active. michael@0: * michael@0: * @param aWindow the main window. michael@0: * @param aTab the tab targeted. michael@0: */ michael@0: toggle: function(aWindow, aTab) { michael@0: if (aTab.__responsiveUI) { michael@0: aTab.__responsiveUI.close(); michael@0: } else { michael@0: new ResponsiveUI(aWindow, aTab); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Returns true if responsive view is active for the provided tab. michael@0: * michael@0: * @param aTab the tab targeted. michael@0: */ michael@0: isActiveForTab: function(aTab) { michael@0: return !!aTab.__responsiveUI; michael@0: }, michael@0: michael@0: /** michael@0: * Handle gcli commands. michael@0: * michael@0: * @param aWindow the browser window. michael@0: * @param aTab the tab targeted. michael@0: * @param aCommand the command name. michael@0: * @param aArgs command arguments. michael@0: */ michael@0: handleGcliCommand: function(aWindow, aTab, aCommand, aArgs) { michael@0: switch (aCommand) { michael@0: case "resize to": michael@0: if (!aTab.__responsiveUI) { michael@0: new ResponsiveUI(aWindow, aTab); michael@0: } michael@0: aTab.__responsiveUI.setSize(aArgs.width, aArgs.height); michael@0: break; michael@0: case "resize on": michael@0: if (!aTab.__responsiveUI) { michael@0: new ResponsiveUI(aWindow, aTab); michael@0: } michael@0: break; michael@0: case "resize off": michael@0: if (aTab.__responsiveUI) { michael@0: aTab.__responsiveUI.close(); michael@0: } michael@0: break; michael@0: case "resize toggle": michael@0: this.toggle(aWindow, aTab); michael@0: default: michael@0: } michael@0: } michael@0: } michael@0: michael@0: EventEmitter.decorate(ResponsiveUIManager); michael@0: michael@0: let presets = [ michael@0: // Phones michael@0: {key: "320x480", width: 320, height: 480}, // iPhone, B2G, with michael@0: {key: "360x640", width: 360, height: 640}, // Android 4, phones, with michael@0: michael@0: // Tablets michael@0: {key: "768x1024", width: 768, height: 1024}, // iPad, with michael@0: {key: "800x1280", width: 800, height: 1280}, // Android 4, Tablet, with michael@0: michael@0: // Default width for mobile browsers, no michael@0: {key: "980x1280", width: 980, height: 1280}, michael@0: michael@0: // Computer michael@0: {key: "1280x600", width: 1280, height: 600}, michael@0: {key: "1920x900", width: 1920, height: 900}, michael@0: ]; michael@0: michael@0: function ResponsiveUI(aWindow, aTab) michael@0: { michael@0: this.mainWindow = aWindow; michael@0: this.tab = aTab; michael@0: this.tabContainer = aWindow.gBrowser.tabContainer; michael@0: this.browser = aTab.linkedBrowser; michael@0: this.chromeDoc = aWindow.document; michael@0: this.container = aWindow.gBrowser.getBrowserContainer(this.browser); michael@0: this.stack = this.container.querySelector(".browserStack"); michael@0: this._telemetry = new Telemetry(); michael@0: this._floatingScrollbars = !this.mainWindow.matchMedia("(-moz-overlay-scrollbars)").matches; michael@0: michael@0: michael@0: // Try to load presets from prefs michael@0: if (Services.prefs.prefHasUserValue("devtools.responsiveUI.presets")) { michael@0: try { michael@0: presets = JSON.parse(Services.prefs.getCharPref("devtools.responsiveUI.presets")); michael@0: } catch(e) { michael@0: // User pref is malformated. michael@0: Cu.reportError("Could not parse pref `devtools.responsiveUI.presets`: " + e); michael@0: } michael@0: } michael@0: michael@0: this.customPreset = {key: "custom", custom: true}; michael@0: michael@0: if (Array.isArray(presets)) { michael@0: this.presets = [this.customPreset].concat(presets); michael@0: } else { michael@0: Cu.reportError("Presets value (devtools.responsiveUI.presets) is malformated."); michael@0: this.presets = [this.customPreset]; michael@0: } michael@0: michael@0: try { michael@0: let width = Services.prefs.getIntPref("devtools.responsiveUI.customWidth"); michael@0: let height = Services.prefs.getIntPref("devtools.responsiveUI.customHeight"); michael@0: this.customPreset.width = Math.min(MAX_WIDTH, width); michael@0: this.customPreset.height = Math.min(MAX_HEIGHT, height); michael@0: michael@0: this.currentPresetKey = Services.prefs.getCharPref("devtools.responsiveUI.currentPreset"); michael@0: } catch(e) { michael@0: // Default size. The first preset (custom) is the one that will be used. michael@0: let bbox = this.stack.getBoundingClientRect(); michael@0: michael@0: this.customPreset.width = bbox.width - 40; // horizontal padding of the container michael@0: this.customPreset.height = bbox.height - 80; // vertical padding + toolbar height michael@0: michael@0: this.currentPresetKey = this.presets[1].key; // most common preset michael@0: } michael@0: michael@0: this.container.setAttribute("responsivemode", "true"); michael@0: this.stack.setAttribute("responsivemode", "true"); michael@0: michael@0: // Let's bind some callbacks. michael@0: this.bound_onPageLoad = this.onPageLoad.bind(this); michael@0: this.bound_onPageUnload = this.onPageUnload.bind(this); michael@0: this.bound_presetSelected = this.presetSelected.bind(this); michael@0: this.bound_addPreset = this.addPreset.bind(this); michael@0: this.bound_removePreset = this.removePreset.bind(this); michael@0: this.bound_rotate = this.rotate.bind(this); michael@0: this.bound_screenshot = () => this.screenshot(); michael@0: this.bound_touch = this.toggleTouch.bind(this); michael@0: this.bound_close = this.close.bind(this); michael@0: this.bound_startResizing = this.startResizing.bind(this); michael@0: this.bound_stopResizing = this.stopResizing.bind(this); michael@0: this.bound_onDrag = this.onDrag.bind(this); michael@0: this.bound_onKeypress = this.onKeypress.bind(this); michael@0: michael@0: // Events michael@0: this.tab.addEventListener("TabClose", this); michael@0: this.tabContainer.addEventListener("TabSelect", this); michael@0: this.mainWindow.document.addEventListener("keypress", this.bound_onKeypress, false); michael@0: michael@0: this.buildUI(); michael@0: this.checkMenus(); michael@0: michael@0: this.docShell = this.browser.contentWindow.QueryInterface(Ci.nsIInterfaceRequestor) michael@0: .getInterface(Ci.nsIWebNavigation) michael@0: .QueryInterface(Ci.nsIDocShell); michael@0: michael@0: this._deviceSizeWasPageSize = this.docShell.deviceSizeIsPageSize; michael@0: this.docShell.deviceSizeIsPageSize = true; michael@0: michael@0: try { michael@0: if (Services.prefs.getBoolPref("devtools.responsiveUI.rotate")) { michael@0: this.rotate(); michael@0: } michael@0: } catch(e) {} michael@0: michael@0: if (this._floatingScrollbars) michael@0: switchToFloatingScrollbars(this.tab); michael@0: michael@0: this.tab.__responsiveUI = this; michael@0: michael@0: this._telemetry.toolOpened("responsive"); michael@0: michael@0: // Touch events support michael@0: this.touchEnableBefore = false; michael@0: this.touchEventHandler = new TouchEventHandler(this.browser); michael@0: michael@0: this.browser.addEventListener("load", this.bound_onPageLoad, true); michael@0: this.browser.addEventListener("unload", this.bound_onPageUnload, true); michael@0: michael@0: if (this.browser.contentWindow.document && michael@0: this.browser.contentWindow.document.readyState == "complete") { michael@0: this.onPageLoad(); michael@0: } michael@0: michael@0: ResponsiveUIManager.emit("on", this.tab, this); michael@0: } michael@0: michael@0: ResponsiveUI.prototype = { michael@0: _transitionsEnabled: true, michael@0: get transitionsEnabled() this._transitionsEnabled, michael@0: set transitionsEnabled(aValue) { michael@0: this._transitionsEnabled = aValue; michael@0: if (aValue && !this._resizing && this.stack.hasAttribute("responsivemode")) { michael@0: this.stack.removeAttribute("notransition"); michael@0: } else if (!aValue) { michael@0: this.stack.setAttribute("notransition", "true"); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Window onload / onunload michael@0: */ michael@0: onPageLoad: function() { michael@0: this.touchEventHandler = new TouchEventHandler(this.browser); michael@0: if (this.touchEnableBefore) { michael@0: this.enableTouch(); michael@0: } michael@0: }, michael@0: michael@0: onPageUnload: function(evt) { michael@0: // Ignore sub frames unload events michael@0: if (evt.target != this.browser.contentDocument) michael@0: return; michael@0: if (this.closing) michael@0: return; michael@0: if (this.touchEventHandler) { michael@0: this.touchEnableBefore = this.touchEventHandler.enabled; michael@0: this.disableTouch(); michael@0: delete this.touchEventHandler; michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Destroy the nodes. Remove listeners. Reset the style. michael@0: */ michael@0: close: function RUI_unload() { michael@0: if (this.closing) michael@0: return; michael@0: this.closing = true; michael@0: michael@0: this.docShell.deviceSizeIsPageSize = this._deviceSizeWasPageSize; michael@0: michael@0: this.browser.removeEventListener("load", this.bound_onPageLoad, true); michael@0: this.browser.removeEventListener("unload", this.bound_onPageUnload, true); michael@0: michael@0: if (this._floatingScrollbars) michael@0: switchToNativeScrollbars(this.tab); michael@0: michael@0: this.unCheckMenus(); michael@0: // Reset style of the stack. michael@0: let style = "max-width: none;" + michael@0: "min-width: 0;" + michael@0: "max-height: none;" + michael@0: "min-height: 0;"; michael@0: this.stack.setAttribute("style", style); michael@0: michael@0: if (this.isResizing) michael@0: this.stopResizing(); michael@0: michael@0: // Remove listeners. michael@0: this.mainWindow.document.removeEventListener("keypress", this.bound_onKeypress, false); michael@0: this.menulist.removeEventListener("select", this.bound_presetSelected, true); michael@0: this.tab.removeEventListener("TabClose", this); michael@0: this.tabContainer.removeEventListener("TabSelect", this); michael@0: this.rotatebutton.removeEventListener("command", this.bound_rotate, true); michael@0: this.screenshotbutton.removeEventListener("command", this.bound_screenshot, true); michael@0: this.touchbutton.removeEventListener("command", this.bound_touch, true); michael@0: this.closebutton.removeEventListener("command", this.bound_close, true); michael@0: this.addbutton.removeEventListener("command", this.bound_addPreset, true); michael@0: this.removebutton.removeEventListener("command", this.bound_removePreset, true); michael@0: michael@0: // Removed elements. michael@0: this.container.removeChild(this.toolbar); michael@0: this.stack.removeChild(this.resizer); michael@0: this.stack.removeChild(this.resizeBarV); michael@0: this.stack.removeChild(this.resizeBarH); michael@0: michael@0: // Unset the responsive mode. michael@0: this.container.removeAttribute("responsivemode"); michael@0: this.stack.removeAttribute("responsivemode"); michael@0: michael@0: delete this.docShell; michael@0: delete this.tab.__responsiveUI; michael@0: if (this.touchEventHandler) michael@0: this.touchEventHandler.stop(); michael@0: this._telemetry.toolClosed("responsive"); michael@0: ResponsiveUIManager.emit("off", this.tab, this); michael@0: }, michael@0: michael@0: /** michael@0: * Handle keypressed. michael@0: * michael@0: * @param aEvent michael@0: */ michael@0: onKeypress: function RUI_onKeypress(aEvent) { michael@0: if (aEvent.keyCode == this.mainWindow.KeyEvent.DOM_VK_ESCAPE && michael@0: this.mainWindow.gBrowser.selectedBrowser == this.browser) { michael@0: michael@0: aEvent.preventDefault(); michael@0: aEvent.stopPropagation(); michael@0: this.close(); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Handle events michael@0: */ michael@0: handleEvent: function (aEvent) { michael@0: switch (aEvent.type) { michael@0: case "TabClose": michael@0: this.close(); michael@0: break; michael@0: case "TabSelect": michael@0: if (this.tab.selected) { michael@0: this.checkMenus(); michael@0: } else if (!this.mainWindow.gBrowser.selectedTab.responsiveUI) { michael@0: this.unCheckMenus(); michael@0: } michael@0: break; michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Check the menu items. michael@0: */ michael@0: checkMenus: function RUI_checkMenus() { michael@0: this.chromeDoc.getElementById("Tools:ResponsiveUI").setAttribute("checked", "true"); michael@0: }, michael@0: michael@0: /** michael@0: * Uncheck the menu items. michael@0: */ michael@0: unCheckMenus: function RUI_unCheckMenus() { michael@0: this.chromeDoc.getElementById("Tools:ResponsiveUI").setAttribute("checked", "false"); michael@0: }, michael@0: michael@0: /** michael@0: * Build the toolbar and the resizers. michael@0: * michael@0: * From tabbrowser.xml michael@0: * michael@0: * // presets michael@0: * // rotate michael@0: * // screenshot michael@0: * // close michael@0: * michael@0: * From tabbrowser.xml michael@0: * michael@0: * michael@0: * michael@0: * michael@0: * michael@0: * michael@0: */ michael@0: buildUI: function RUI_buildUI() { michael@0: // Toolbar michael@0: this.toolbar = this.chromeDoc.createElement("toolbar"); michael@0: this.toolbar.className = "devtools-responsiveui-toolbar"; michael@0: michael@0: this.menulist = this.chromeDoc.createElement("menulist"); michael@0: this.menulist.className = "devtools-responsiveui-menulist"; michael@0: michael@0: this.menulist.addEventListener("select", this.bound_presetSelected, true); michael@0: michael@0: this.menuitems = new Map(); michael@0: michael@0: let menupopup = this.chromeDoc.createElement("menupopup"); michael@0: this.registerPresets(menupopup); michael@0: this.menulist.appendChild(menupopup); michael@0: michael@0: this.addbutton = this.chromeDoc.createElement("menuitem"); michael@0: this.addbutton.setAttribute("label", this.strings.GetStringFromName("responsiveUI.addPreset")); michael@0: this.addbutton.addEventListener("command", this.bound_addPreset, true); michael@0: michael@0: this.removebutton = this.chromeDoc.createElement("menuitem"); michael@0: this.removebutton.setAttribute("label", this.strings.GetStringFromName("responsiveUI.removePreset")); michael@0: this.removebutton.addEventListener("command", this.bound_removePreset, true); michael@0: michael@0: menupopup.appendChild(this.chromeDoc.createElement("menuseparator")); michael@0: menupopup.appendChild(this.addbutton); michael@0: menupopup.appendChild(this.removebutton); michael@0: michael@0: this.rotatebutton = this.chromeDoc.createElement("toolbarbutton"); michael@0: this.rotatebutton.setAttribute("tabindex", "0"); michael@0: this.rotatebutton.setAttribute("tooltiptext", this.strings.GetStringFromName("responsiveUI.rotate2")); michael@0: this.rotatebutton.className = "devtools-responsiveui-toolbarbutton devtools-responsiveui-rotate"; michael@0: this.rotatebutton.addEventListener("command", this.bound_rotate, true); michael@0: michael@0: this.screenshotbutton = this.chromeDoc.createElement("toolbarbutton"); michael@0: this.screenshotbutton.setAttribute("tabindex", "0"); michael@0: this.screenshotbutton.setAttribute("tooltiptext", this.strings.GetStringFromName("responsiveUI.screenshot")); michael@0: this.screenshotbutton.className = "devtools-responsiveui-toolbarbutton devtools-responsiveui-screenshot"; michael@0: this.screenshotbutton.addEventListener("command", this.bound_screenshot, true); michael@0: michael@0: this.touchbutton = this.chromeDoc.createElement("toolbarbutton"); michael@0: this.touchbutton.setAttribute("tabindex", "0"); michael@0: this.touchbutton.setAttribute("tooltiptext", this.strings.GetStringFromName("responsiveUI.touch")); michael@0: this.touchbutton.className = "devtools-responsiveui-toolbarbutton devtools-responsiveui-touch"; michael@0: this.touchbutton.addEventListener("command", this.bound_touch, true); michael@0: michael@0: this.closebutton = this.chromeDoc.createElement("toolbarbutton"); michael@0: this.closebutton.setAttribute("tabindex", "0"); michael@0: this.closebutton.className = "devtools-responsiveui-toolbarbutton devtools-responsiveui-close"; michael@0: this.closebutton.setAttribute("tooltiptext", this.strings.GetStringFromName("responsiveUI.close")); michael@0: this.closebutton.addEventListener("command", this.bound_close, true); michael@0: michael@0: this.toolbar.appendChild(this.closebutton); michael@0: this.toolbar.appendChild(this.menulist); michael@0: this.toolbar.appendChild(this.rotatebutton); michael@0: this.toolbar.appendChild(this.touchbutton); michael@0: this.toolbar.appendChild(this.screenshotbutton); michael@0: michael@0: // Resizers michael@0: let resizerTooltip = this.strings.GetStringFromName("responsiveUI.resizerTooltip"); michael@0: this.resizer = this.chromeDoc.createElement("box"); michael@0: this.resizer.className = "devtools-responsiveui-resizehandle"; michael@0: this.resizer.setAttribute("right", "0"); michael@0: this.resizer.setAttribute("bottom", "0"); michael@0: this.resizer.setAttribute("tooltiptext", resizerTooltip); michael@0: this.resizer.onmousedown = this.bound_startResizing; michael@0: michael@0: this.resizeBarV = this.chromeDoc.createElement("box"); michael@0: this.resizeBarV.className = "devtools-responsiveui-resizebarV"; michael@0: this.resizeBarV.setAttribute("top", "0"); michael@0: this.resizeBarV.setAttribute("right", "0"); michael@0: this.resizeBarV.setAttribute("tooltiptext", resizerTooltip); michael@0: this.resizeBarV.onmousedown = this.bound_startResizing; michael@0: michael@0: this.resizeBarH = this.chromeDoc.createElement("box"); michael@0: this.resizeBarH.className = "devtools-responsiveui-resizebarH"; michael@0: this.resizeBarH.setAttribute("bottom", "0"); michael@0: this.resizeBarH.setAttribute("left", "0"); michael@0: this.resizeBarH.setAttribute("tooltiptext", resizerTooltip); michael@0: this.resizeBarH.onmousedown = this.bound_startResizing; michael@0: michael@0: this.container.insertBefore(this.toolbar, this.stack); michael@0: this.stack.appendChild(this.resizer); michael@0: this.stack.appendChild(this.resizeBarV); michael@0: this.stack.appendChild(this.resizeBarH); michael@0: }, michael@0: michael@0: /** michael@0: * Build the presets list and append it to the menupopup. michael@0: * michael@0: * @param aParent menupopup. michael@0: */ michael@0: registerPresets: function RUI_registerPresets(aParent) { michael@0: let fragment = this.chromeDoc.createDocumentFragment(); michael@0: let doc = this.chromeDoc; michael@0: michael@0: for (let preset of this.presets) { michael@0: let menuitem = doc.createElement("menuitem"); michael@0: menuitem.setAttribute("ispreset", true); michael@0: this.menuitems.set(menuitem, preset); michael@0: michael@0: if (preset.key === this.currentPresetKey) { michael@0: menuitem.setAttribute("selected", "true"); michael@0: this.selectedItem = menuitem; michael@0: } michael@0: michael@0: if (preset.custom) michael@0: this.customMenuitem = menuitem; michael@0: michael@0: this.setMenuLabel(menuitem, preset); michael@0: fragment.appendChild(menuitem); michael@0: } michael@0: aParent.appendChild(fragment); michael@0: }, michael@0: michael@0: /** michael@0: * Set the menuitem label of a preset. michael@0: * michael@0: * @param aMenuitem menuitem to edit. michael@0: * @param aPreset associated preset. michael@0: */ michael@0: setMenuLabel: function RUI_setMenuLabel(aMenuitem, aPreset) { michael@0: let size = Math.round(aPreset.width) + "x" + Math.round(aPreset.height); michael@0: if (aPreset.custom) { michael@0: let str = this.strings.formatStringFromName("responsiveUI.customResolution", [size], 1); michael@0: aMenuitem.setAttribute("label", str); michael@0: } else if (aPreset.name != null && aPreset.name !== "") { michael@0: let str = this.strings.formatStringFromName("responsiveUI.namedResolution", [size, aPreset.name], 2); michael@0: aMenuitem.setAttribute("label", str); michael@0: } else { michael@0: aMenuitem.setAttribute("label", size); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * When a preset is selected, apply it. michael@0: */ michael@0: presetSelected: function RUI_presetSelected() { michael@0: if (this.menulist.selectedItem.getAttribute("ispreset") === "true") { michael@0: this.selectedItem = this.menulist.selectedItem; michael@0: michael@0: this.rotateValue = false; michael@0: let selectedPreset = this.menuitems.get(this.selectedItem); michael@0: this.loadPreset(selectedPreset); michael@0: this.currentPresetKey = selectedPreset.key; michael@0: this.saveCurrentPreset(); michael@0: michael@0: // Update the buttons hidden status according to the new selected preset michael@0: if (selectedPreset == this.customPreset) { michael@0: this.addbutton.hidden = false; michael@0: this.removebutton.hidden = true; michael@0: } else { michael@0: this.addbutton.hidden = true; michael@0: this.removebutton.hidden = false; michael@0: } michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Apply a preset. michael@0: * michael@0: * @param aPreset preset to apply. michael@0: */ michael@0: loadPreset: function RUI_loadPreset(aPreset) { michael@0: this.setSize(aPreset.width, aPreset.height); michael@0: }, michael@0: michael@0: /** michael@0: * Add a preset to the list and the memory michael@0: */ michael@0: addPreset: function RUI_addPreset() { michael@0: let w = this.customPreset.width; michael@0: let h = this.customPreset.height; michael@0: let newName = {}; michael@0: michael@0: let title = this.strings.GetStringFromName("responsiveUI.customNamePromptTitle"); michael@0: let message = this.strings.formatStringFromName("responsiveUI.customNamePromptMsg", [w, h], 2); michael@0: let promptOk = Services.prompt.prompt(null, title, message, newName, null, {}); michael@0: michael@0: if (!promptOk) { michael@0: // Prompt has been cancelled michael@0: let menuitem = this.customMenuitem; michael@0: this.menulist.selectedItem = menuitem; michael@0: this.currentPresetKey = this.customPreset.key; michael@0: return; michael@0: } michael@0: michael@0: let newPreset = { michael@0: key: w + "x" + h, michael@0: name: newName.value, michael@0: width: w, michael@0: height: h michael@0: }; michael@0: michael@0: this.presets.push(newPreset); michael@0: michael@0: // Sort the presets according to width/height ascending order michael@0: this.presets.sort(function RUI_sortPresets(aPresetA, aPresetB) { michael@0: // We keep custom preset at first michael@0: if (aPresetA.custom && !aPresetB.custom) { michael@0: return 1; michael@0: } michael@0: if (!aPresetA.custom && aPresetB.custom) { michael@0: return -1; michael@0: } michael@0: michael@0: if (aPresetA.width === aPresetB.width) { michael@0: if (aPresetA.height === aPresetB.height) { michael@0: return 0; michael@0: } else { michael@0: return aPresetA.height > aPresetB.height; michael@0: } michael@0: } else { michael@0: return aPresetA.width > aPresetB.width; michael@0: } michael@0: }); michael@0: michael@0: this.savePresets(); michael@0: michael@0: let newMenuitem = this.chromeDoc.createElement("menuitem"); michael@0: newMenuitem.setAttribute("ispreset", true); michael@0: this.setMenuLabel(newMenuitem, newPreset); michael@0: michael@0: this.menuitems.set(newMenuitem, newPreset); michael@0: let idx = this.presets.indexOf(newPreset); michael@0: let beforeMenuitem = this.menulist.firstChild.childNodes[idx + 1]; michael@0: this.menulist.firstChild.insertBefore(newMenuitem, beforeMenuitem); michael@0: michael@0: this.menulist.selectedItem = newMenuitem; michael@0: this.currentPresetKey = newPreset.key; michael@0: this.saveCurrentPreset(); michael@0: }, michael@0: michael@0: /** michael@0: * remove a preset from the list and the memory michael@0: */ michael@0: removePreset: function RUI_removePreset() { michael@0: let selectedPreset = this.menuitems.get(this.selectedItem); michael@0: let w = selectedPreset.width; michael@0: let h = selectedPreset.height; michael@0: michael@0: this.presets.splice(this.presets.indexOf(selectedPreset), 1); michael@0: this.menulist.firstChild.removeChild(this.selectedItem); michael@0: this.menuitems.delete(this.selectedItem); michael@0: michael@0: this.customPreset.width = w; michael@0: this.customPreset.height = h; michael@0: let menuitem = this.customMenuitem; michael@0: this.setMenuLabel(menuitem, this.customPreset); michael@0: this.menulist.selectedItem = menuitem; michael@0: this.currentPresetKey = this.customPreset.key; michael@0: michael@0: this.setSize(w, h); michael@0: michael@0: this.savePresets(); michael@0: }, michael@0: michael@0: /** michael@0: * Swap width and height. michael@0: */ michael@0: rotate: function RUI_rotate() { michael@0: let selectedPreset = this.menuitems.get(this.selectedItem); michael@0: let width = this.rotateValue ? selectedPreset.height : selectedPreset.width; michael@0: let height = this.rotateValue ? selectedPreset.width : selectedPreset.height; michael@0: michael@0: this.setSize(height, width); michael@0: michael@0: if (selectedPreset.custom) { michael@0: this.saveCustomSize(); michael@0: } else { michael@0: this.rotateValue = !this.rotateValue; michael@0: this.saveCurrentPreset(); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Take a screenshot of the page. michael@0: * michael@0: * @param aFileName name of the screenshot file (used for tests). michael@0: */ michael@0: screenshot: function RUI_screenshot(aFileName) { michael@0: let window = this.browser.contentWindow; michael@0: let document = window.document; michael@0: let canvas = this.chromeDoc.createElementNS("http://www.w3.org/1999/xhtml", "canvas"); michael@0: michael@0: let width = window.innerWidth; michael@0: let height = window.innerHeight; michael@0: michael@0: canvas.width = width; michael@0: canvas.height = height; michael@0: michael@0: let ctx = canvas.getContext("2d"); michael@0: ctx.drawWindow(window, window.scrollX, window.scrollY, width, height, "#fff"); michael@0: michael@0: let filename = aFileName; michael@0: michael@0: if (!filename) { michael@0: let date = new Date(); michael@0: let month = ("0" + (date.getMonth() + 1)).substr(-2, 2); michael@0: let day = ("0" + (date.getDay() + 1)).substr(-2, 2); michael@0: let dateString = [date.getFullYear(), month, day].join("-"); michael@0: let timeString = date.toTimeString().replace(/:/g, ".").split(" ")[0]; michael@0: filename = this.strings.formatStringFromName("responsiveUI.screenshotGeneratedFilename", [dateString, timeString], 2); michael@0: } michael@0: michael@0: canvas.toBlob(blob => { michael@0: let chromeWindow = this.chromeDoc.defaultView; michael@0: let url = chromeWindow.URL.createObjectURL(blob); michael@0: chromeWindow.saveURL(url, filename + ".png", null, true, true, document.documentURIObject, document); michael@0: }); michael@0: }, michael@0: michael@0: /** michael@0: * Enable/Disable mouse -> touch events translation. michael@0: */ michael@0: enableTouch: function RUI_enableTouch() { michael@0: if (!this.touchEventHandler.enabled) { michael@0: let isReloadNeeded = this.touchEventHandler.start(); michael@0: this.touchbutton.setAttribute("checked", "true"); michael@0: return isReloadNeeded; michael@0: } michael@0: return false; michael@0: }, michael@0: michael@0: disableTouch: function RUI_disableTouch() { michael@0: if (this.touchEventHandler.enabled) { michael@0: this.touchEventHandler.stop(); michael@0: this.touchbutton.removeAttribute("checked"); michael@0: } michael@0: }, michael@0: michael@0: hideTouchNotification: function RUI_hideTouchNotification() { michael@0: let nbox = this.mainWindow.gBrowser.getNotificationBox(this.browser); michael@0: let n = nbox.getNotificationWithValue("responsive-ui-need-reload"); michael@0: if (n) { michael@0: n.close(); michael@0: } michael@0: }, michael@0: michael@0: toggleTouch: function RUI_toggleTouch() { michael@0: this.hideTouchNotification(); michael@0: if (this.touchEventHandler.enabled) { michael@0: this.disableTouch(); michael@0: } else { michael@0: let isReloadNeeded = this.enableTouch(); michael@0: if (isReloadNeeded) { michael@0: if (Services.prefs.getBoolPref("devtools.responsiveUI.no-reload-notification")) { michael@0: return; michael@0: } michael@0: michael@0: let nbox = this.mainWindow.gBrowser.getNotificationBox(this.browser); michael@0: michael@0: var buttons = [{ michael@0: label: this.strings.GetStringFromName("responsiveUI.notificationReload"), michael@0: callback: () => { michael@0: this.browser.reload(); michael@0: }, michael@0: accessKey: this.strings.GetStringFromName("responsiveUI.notificationReload_accesskey"), michael@0: }, { michael@0: label: this.strings.GetStringFromName("responsiveUI.dontShowReloadNotification"), michael@0: callback: function() { michael@0: Services.prefs.setBoolPref("devtools.responsiveUI.no-reload-notification", true); michael@0: }, michael@0: accessKey: this.strings.GetStringFromName("responsiveUI.dontShowReloadNotification_accesskey"), michael@0: }]; michael@0: michael@0: nbox.appendNotification( michael@0: this.strings.GetStringFromName("responsiveUI.needReload"), michael@0: "responsive-ui-need-reload", michael@0: null, michael@0: nbox.PRIORITY_INFO_LOW, michael@0: buttons); michael@0: } michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Change the size of the browser. michael@0: * michael@0: * @param aWidth width of the browser. michael@0: * @param aHeight height of the browser. michael@0: */ michael@0: setSize: function RUI_setSize(aWidth, aHeight) { michael@0: aWidth = Math.min(Math.max(aWidth, MIN_WIDTH), MAX_WIDTH); michael@0: aHeight = Math.min(Math.max(aHeight, MIN_HEIGHT), MAX_HEIGHT); michael@0: michael@0: // We resize the containing stack. michael@0: let style = "max-width: %width;" + michael@0: "min-width: %width;" + michael@0: "max-height: %height;" + michael@0: "min-height: %height;"; michael@0: michael@0: style = style.replace(/%width/g, aWidth + "px"); michael@0: style = style.replace(/%height/g, aHeight + "px"); michael@0: michael@0: this.stack.setAttribute("style", style); michael@0: michael@0: if (!this.ignoreY) michael@0: this.resizeBarV.setAttribute("top", Math.round(aHeight / 2)); michael@0: if (!this.ignoreX) michael@0: this.resizeBarH.setAttribute("left", Math.round(aWidth / 2)); michael@0: michael@0: let selectedPreset = this.menuitems.get(this.selectedItem); michael@0: michael@0: // We uptate the custom menuitem if we are using it michael@0: if (selectedPreset.custom) { michael@0: selectedPreset.width = aWidth; michael@0: selectedPreset.height = aHeight; michael@0: michael@0: this.setMenuLabel(this.selectedItem, selectedPreset); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Start the process of resizing the browser. michael@0: * michael@0: * @param aEvent michael@0: */ michael@0: startResizing: function RUI_startResizing(aEvent) { michael@0: let selectedPreset = this.menuitems.get(this.selectedItem); michael@0: michael@0: if (!selectedPreset.custom) { michael@0: this.customPreset.width = this.rotateValue ? selectedPreset.height : selectedPreset.width; michael@0: this.customPreset.height = this.rotateValue ? selectedPreset.width : selectedPreset.height; michael@0: michael@0: let menuitem = this.customMenuitem; michael@0: this.setMenuLabel(menuitem, this.customPreset); michael@0: michael@0: this.currentPresetKey = this.customPreset.key; michael@0: this.menulist.selectedItem = menuitem; michael@0: } michael@0: this.mainWindow.addEventListener("mouseup", this.bound_stopResizing, true); michael@0: this.mainWindow.addEventListener("mousemove", this.bound_onDrag, true); michael@0: this.container.style.pointerEvents = "none"; michael@0: michael@0: this._resizing = true; michael@0: this.stack.setAttribute("notransition", "true"); michael@0: michael@0: this.lastScreenX = aEvent.screenX; michael@0: this.lastScreenY = aEvent.screenY; michael@0: michael@0: this.ignoreY = (aEvent.target === this.resizeBarV); michael@0: this.ignoreX = (aEvent.target === this.resizeBarH); michael@0: michael@0: this.isResizing = true; michael@0: }, michael@0: michael@0: /** michael@0: * Resizing on mouse move. michael@0: * michael@0: * @param aEvent michael@0: */ michael@0: onDrag: function RUI_onDrag(aEvent) { michael@0: let shift = aEvent.shiftKey; michael@0: let ctrl = !aEvent.shiftKey && aEvent.ctrlKey; michael@0: michael@0: let screenX = aEvent.screenX; michael@0: let screenY = aEvent.screenY; michael@0: michael@0: let deltaX = screenX - this.lastScreenX; michael@0: let deltaY = screenY - this.lastScreenY; michael@0: michael@0: if (this.ignoreY) michael@0: deltaY = 0; michael@0: if (this.ignoreX) michael@0: deltaX = 0; michael@0: michael@0: if (ctrl) { michael@0: deltaX /= SLOW_RATIO; michael@0: deltaY /= SLOW_RATIO; michael@0: } michael@0: michael@0: let width = this.customPreset.width + deltaX; michael@0: let height = this.customPreset.height + deltaY; michael@0: michael@0: if (shift) { michael@0: let roundedWidth, roundedHeight; michael@0: roundedWidth = 10 * Math.floor(width / ROUND_RATIO); michael@0: roundedHeight = 10 * Math.floor(height / ROUND_RATIO); michael@0: screenX += roundedWidth - width; michael@0: screenY += roundedHeight - height; michael@0: width = roundedWidth; michael@0: height = roundedHeight; michael@0: } michael@0: michael@0: if (width < MIN_WIDTH) { michael@0: width = MIN_WIDTH; michael@0: } else { michael@0: this.lastScreenX = screenX; michael@0: } michael@0: michael@0: if (height < MIN_HEIGHT) { michael@0: height = MIN_HEIGHT; michael@0: } else { michael@0: this.lastScreenY = screenY; michael@0: } michael@0: michael@0: this.setSize(width, height); michael@0: }, michael@0: michael@0: /** michael@0: * Stop End resizing michael@0: */ michael@0: stopResizing: function RUI_stopResizing() { michael@0: this.container.style.pointerEvents = "auto"; michael@0: michael@0: this.mainWindow.removeEventListener("mouseup", this.bound_stopResizing, true); michael@0: this.mainWindow.removeEventListener("mousemove", this.bound_onDrag, true); michael@0: michael@0: this.saveCustomSize(); michael@0: michael@0: delete this._resizing; michael@0: if (this.transitionsEnabled) { michael@0: this.stack.removeAttribute("notransition"); michael@0: } michael@0: this.ignoreY = false; michael@0: this.ignoreX = false; michael@0: this.isResizing = false; michael@0: }, michael@0: michael@0: /** michael@0: * Store the custom size as a pref. michael@0: */ michael@0: saveCustomSize: function RUI_saveCustomSize() { michael@0: Services.prefs.setIntPref("devtools.responsiveUI.customWidth", this.customPreset.width); michael@0: Services.prefs.setIntPref("devtools.responsiveUI.customHeight", this.customPreset.height); michael@0: }, michael@0: michael@0: /** michael@0: * Store the current preset as a pref. michael@0: */ michael@0: saveCurrentPreset: function RUI_saveCurrentPreset() { michael@0: Services.prefs.setCharPref("devtools.responsiveUI.currentPreset", this.currentPresetKey); michael@0: Services.prefs.setBoolPref("devtools.responsiveUI.rotate", this.rotateValue); michael@0: }, michael@0: michael@0: /** michael@0: * Store the list of all registered presets as a pref. michael@0: */ michael@0: savePresets: function RUI_savePresets() { michael@0: // We exclude the custom one michael@0: let registeredPresets = this.presets.filter(function (aPreset) { michael@0: return !aPreset.custom; michael@0: }); michael@0: michael@0: Services.prefs.setCharPref("devtools.responsiveUI.presets", JSON.stringify(registeredPresets)); michael@0: }, michael@0: } michael@0: michael@0: XPCOMUtils.defineLazyGetter(ResponsiveUI.prototype, "strings", function () { michael@0: return Services.strings.createBundle("chrome://browser/locale/devtools/responsiveUI.properties"); michael@0: });