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