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: });