browser/devtools/responsivedesign/responsivedesign.jsm

changeset 0
6474c204b198
equal deleted inserted replaced
-1:000000000000 0:37c5a125ee9c
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/. */
6
7 const Ci = Components.interfaces;
8 const Cu = Components.utils;
9
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");
15
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");
19
20 this.EXPORTED_SYMBOLS = ["ResponsiveUIManager"];
21
22 const MIN_WIDTH = 50;
23 const MIN_HEIGHT = 50;
24
25 const MAX_WIDTH = 10000;
26 const MAX_HEIGHT = 10000;
27
28 const SLOW_RATIO = 6;
29 const ROUND_RATIO = 10;
30
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 },
47
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 },
56
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 }
89
90 EventEmitter.decorate(ResponsiveUIManager);
91
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>
96
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>
100
101 // Default width for mobile browsers, no <meta viewport>
102 {key: "980x1280", width: 980, height: 1280},
103
104 // Computer
105 {key: "1280x600", width: 1280, height: 600},
106 {key: "1920x900", width: 1920, height: 900},
107 ];
108
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;
120
121
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 }
131
132 this.customPreset = {key: "custom", custom: true};
133
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 }
140
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);
146
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();
151
152 this.customPreset.width = bbox.width - 40; // horizontal padding of the container
153 this.customPreset.height = bbox.height - 80; // vertical padding + toolbar height
154
155 this.currentPresetKey = this.presets[1].key; // most common preset
156 }
157
158 this.container.setAttribute("responsivemode", "true");
159 this.stack.setAttribute("responsivemode", "true");
160
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);
175
176 // Events
177 this.tab.addEventListener("TabClose", this);
178 this.tabContainer.addEventListener("TabSelect", this);
179 this.mainWindow.document.addEventListener("keypress", this.bound_onKeypress, false);
180
181 this.buildUI();
182 this.checkMenus();
183
184 this.docShell = this.browser.contentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
185 .getInterface(Ci.nsIWebNavigation)
186 .QueryInterface(Ci.nsIDocShell);
187
188 this._deviceSizeWasPageSize = this.docShell.deviceSizeIsPageSize;
189 this.docShell.deviceSizeIsPageSize = true;
190
191 try {
192 if (Services.prefs.getBoolPref("devtools.responsiveUI.rotate")) {
193 this.rotate();
194 }
195 } catch(e) {}
196
197 if (this._floatingScrollbars)
198 switchToFloatingScrollbars(this.tab);
199
200 this.tab.__responsiveUI = this;
201
202 this._telemetry.toolOpened("responsive");
203
204 // Touch events support
205 this.touchEnableBefore = false;
206 this.touchEventHandler = new TouchEventHandler(this.browser);
207
208 this.browser.addEventListener("load", this.bound_onPageLoad, true);
209 this.browser.addEventListener("unload", this.bound_onPageUnload, true);
210
211 if (this.browser.contentWindow.document &&
212 this.browser.contentWindow.document.readyState == "complete") {
213 this.onPageLoad();
214 }
215
216 ResponsiveUIManager.emit("on", this.tab, this);
217 }
218
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 },
230
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 },
240
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 },
253
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;
261
262 this.docShell.deviceSizeIsPageSize = this._deviceSizeWasPageSize;
263
264 this.browser.removeEventListener("load", this.bound_onPageLoad, true);
265 this.browser.removeEventListener("unload", this.bound_onPageUnload, true);
266
267 if (this._floatingScrollbars)
268 switchToNativeScrollbars(this.tab);
269
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);
277
278 if (this.isResizing)
279 this.stopResizing();
280
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);
292
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);
298
299 // Unset the responsive mode.
300 this.container.removeAttribute("responsivemode");
301 this.stack.removeAttribute("responsivemode");
302
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 },
310
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) {
319
320 aEvent.preventDefault();
321 aEvent.stopPropagation();
322 this.close();
323 }
324 },
325
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 },
343
344 /**
345 * Check the menu items.
346 */
347 checkMenus: function RUI_checkMenus() {
348 this.chromeDoc.getElementById("Tools:ResponsiveUI").setAttribute("checked", "true");
349 },
350
351 /**
352 * Uncheck the menu items.
353 */
354 unCheckMenus: function RUI_unCheckMenus() {
355 this.chromeDoc.getElementById("Tools:ResponsiveUI").setAttribute("checked", "false");
356 },
357
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";
380
381 this.menulist = this.chromeDoc.createElement("menulist");
382 this.menulist.className = "devtools-responsiveui-menulist";
383
384 this.menulist.addEventListener("select", this.bound_presetSelected, true);
385
386 this.menuitems = new Map();
387
388 let menupopup = this.chromeDoc.createElement("menupopup");
389 this.registerPresets(menupopup);
390 this.menulist.appendChild(menupopup);
391
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);
395
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);
399
400 menupopup.appendChild(this.chromeDoc.createElement("menuseparator"));
401 menupopup.appendChild(this.addbutton);
402 menupopup.appendChild(this.removebutton);
403
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);
409
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);
415
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);
421
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);
427
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);
433
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;
442
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;
449
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;
456
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 },
462
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;
471
472 for (let preset of this.presets) {
473 let menuitem = doc.createElement("menuitem");
474 menuitem.setAttribute("ispreset", true);
475 this.menuitems.set(menuitem, preset);
476
477 if (preset.key === this.currentPresetKey) {
478 menuitem.setAttribute("selected", "true");
479 this.selectedItem = menuitem;
480 }
481
482 if (preset.custom)
483 this.customMenuitem = menuitem;
484
485 this.setMenuLabel(menuitem, preset);
486 fragment.appendChild(menuitem);
487 }
488 aParent.appendChild(fragment);
489 },
490
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 },
509
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;
516
517 this.rotateValue = false;
518 let selectedPreset = this.menuitems.get(this.selectedItem);
519 this.loadPreset(selectedPreset);
520 this.currentPresetKey = selectedPreset.key;
521 this.saveCurrentPreset();
522
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 },
533
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 },
542
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 = {};
550
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, {});
554
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 }
562
563 let newPreset = {
564 key: w + "x" + h,
565 name: newName.value,
566 width: w,
567 height: h
568 };
569
570 this.presets.push(newPreset);
571
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 }
581
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 });
592
593 this.savePresets();
594
595 let newMenuitem = this.chromeDoc.createElement("menuitem");
596 newMenuitem.setAttribute("ispreset", true);
597 this.setMenuLabel(newMenuitem, newPreset);
598
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);
603
604 this.menulist.selectedItem = newMenuitem;
605 this.currentPresetKey = newPreset.key;
606 this.saveCurrentPreset();
607 },
608
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;
616
617 this.presets.splice(this.presets.indexOf(selectedPreset), 1);
618 this.menulist.firstChild.removeChild(this.selectedItem);
619 this.menuitems.delete(this.selectedItem);
620
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;
627
628 this.setSize(w, h);
629
630 this.savePresets();
631 },
632
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;
640
641 this.setSize(height, width);
642
643 if (selectedPreset.custom) {
644 this.saveCustomSize();
645 } else {
646 this.rotateValue = !this.rotateValue;
647 this.saveCurrentPreset();
648 }
649 },
650
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");
660
661 let width = window.innerWidth;
662 let height = window.innerHeight;
663
664 canvas.width = width;
665 canvas.height = height;
666
667 let ctx = canvas.getContext("2d");
668 ctx.drawWindow(window, window.scrollX, window.scrollY, width, height, "#fff");
669
670 let filename = aFileName;
671
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 }
680
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 },
687
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 },
699
700 disableTouch: function RUI_disableTouch() {
701 if (this.touchEventHandler.enabled) {
702 this.touchEventHandler.stop();
703 this.touchbutton.removeAttribute("checked");
704 }
705 },
706
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 },
714
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 }
725
726 let nbox = this.mainWindow.gBrowser.getNotificationBox(this.browser);
727
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 }];
741
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 },
751
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);
761
762 // We resize the containing stack.
763 let style = "max-width: %width;" +
764 "min-width: %width;" +
765 "max-height: %height;" +
766 "min-height: %height;";
767
768 style = style.replace(/%width/g, aWidth + "px");
769 style = style.replace(/%height/g, aHeight + "px");
770
771 this.stack.setAttribute("style", style);
772
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));
777
778 let selectedPreset = this.menuitems.get(this.selectedItem);
779
780 // We uptate the custom menuitem if we are using it
781 if (selectedPreset.custom) {
782 selectedPreset.width = aWidth;
783 selectedPreset.height = aHeight;
784
785 this.setMenuLabel(this.selectedItem, selectedPreset);
786 }
787 },
788
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);
796
797 if (!selectedPreset.custom) {
798 this.customPreset.width = this.rotateValue ? selectedPreset.height : selectedPreset.width;
799 this.customPreset.height = this.rotateValue ? selectedPreset.width : selectedPreset.height;
800
801 let menuitem = this.customMenuitem;
802 this.setMenuLabel(menuitem, this.customPreset);
803
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";
810
811 this._resizing = true;
812 this.stack.setAttribute("notransition", "true");
813
814 this.lastScreenX = aEvent.screenX;
815 this.lastScreenY = aEvent.screenY;
816
817 this.ignoreY = (aEvent.target === this.resizeBarV);
818 this.ignoreX = (aEvent.target === this.resizeBarH);
819
820 this.isResizing = true;
821 },
822
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;
831
832 let screenX = aEvent.screenX;
833 let screenY = aEvent.screenY;
834
835 let deltaX = screenX - this.lastScreenX;
836 let deltaY = screenY - this.lastScreenY;
837
838 if (this.ignoreY)
839 deltaY = 0;
840 if (this.ignoreX)
841 deltaX = 0;
842
843 if (ctrl) {
844 deltaX /= SLOW_RATIO;
845 deltaY /= SLOW_RATIO;
846 }
847
848 let width = this.customPreset.width + deltaX;
849 let height = this.customPreset.height + deltaY;
850
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 }
860
861 if (width < MIN_WIDTH) {
862 width = MIN_WIDTH;
863 } else {
864 this.lastScreenX = screenX;
865 }
866
867 if (height < MIN_HEIGHT) {
868 height = MIN_HEIGHT;
869 } else {
870 this.lastScreenY = screenY;
871 }
872
873 this.setSize(width, height);
874 },
875
876 /**
877 * Stop End resizing
878 */
879 stopResizing: function RUI_stopResizing() {
880 this.container.style.pointerEvents = "auto";
881
882 this.mainWindow.removeEventListener("mouseup", this.bound_stopResizing, true);
883 this.mainWindow.removeEventListener("mousemove", this.bound_onDrag, true);
884
885 this.saveCustomSize();
886
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 },
895
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 },
903
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 },
911
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 });
920
921 Services.prefs.setCharPref("devtools.responsiveUI.presets", JSON.stringify(registeredPresets));
922 },
923 }
924
925 XPCOMUtils.defineLazyGetter(ResponsiveUI.prototype, "strings", function () {
926 return Services.strings.createBundle("chrome://browser/locale/devtools/responsiveUI.properties");
927 });

mercurial