author | Tim Taubert <ttaubert@mozilla.com> |
Thu, 27 Sep 2012 09:11:54 +0200 | |
changeset 108200 | b038e9e2023f712021f36ff1a706fc106fb7a2af |
parent 108189 | 4c99e254b83138b087c9e2985d3cb418c9f4a9cb (current diff) |
parent 108199 | b56fa836e894675118171680ab693f69f6fc7180 (diff) |
child 108243 | aacf4867f83067e33f31f81ecaa14a7152a82d1c |
child 108248 | 225a4da889c5158601b9b46d91a900d7e6306ac2 |
push id | 23544 |
push user | ttaubert@mozilla.com |
push date | Thu, 27 Sep 2012 07:12:21 +0000 |
treeherder | mozilla-central@b038e9e2023f [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
milestone | 18.0a1 |
first release with | nightly linux32
b038e9e2023f
/
18.0a1
/
20120927030539
/
files
nightly linux64
b038e9e2023f
/
18.0a1
/
20120927030539
/
files
nightly mac
b038e9e2023f
/
18.0a1
/
20120927030539
/
files
nightly win32
b038e9e2023f
/
18.0a1
/
20120927030539
/
files
nightly win64
b038e9e2023f
/
18.0a1
/
20120927030539
/
files
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
releases | nightly linux32
18.0a1
/
20120927030539
/
pushlog to previous
nightly linux64
18.0a1
/
20120927030539
/
pushlog to previous
nightly mac
18.0a1
/
20120927030539
/
pushlog to previous
nightly win32
18.0a1
/
20120927030539
/
pushlog to previous
nightly win64
18.0a1
/
20120927030539
/
pushlog to previous
|
--- a/browser/app/profile/firefox.js +++ b/browser/app/profile/firefox.js @@ -1010,16 +1010,17 @@ pref("devtools.gcli.allowSet", false); pref("devtools.commands.dir", ""); // Enable the Inspector pref("devtools.inspector.enabled", true); pref("devtools.inspector.htmlHeight", 112); pref("devtools.inspector.htmlPanelOpen", false); pref("devtools.inspector.sidebarOpen", false); pref("devtools.inspector.activeSidebar", "ruleview"); +pref("devtools.inspector.markupPreview", true); // Enable the Layout View pref("devtools.layoutview.enabled", true); pref("devtools.layoutview.open", false); // Enable the Responsive UI tool pref("devtools.responsiveUI.enabled", true);
--- a/browser/base/content/browser-tabPreviews.js +++ b/browser/base/content/browser-tabPreviews.js @@ -81,17 +81,19 @@ var tabPreviews = { !this._pendingUpdate) { // Generate a thumbnail for the tab that was selected. // The timeout keeps the UI snappy and prevents us from generating thumbnails // for tabs that will be closed. During that timeout, don't generate other // thumbnails in case multiple TabSelect events occur fast in succession. this._pendingUpdate = true; setTimeout(function (self, aTab) { self._pendingUpdate = false; - if (aTab.parentNode && !aTab.hasAttribute("busy")) + if (aTab.parentNode && + !aTab.hasAttribute("busy") && + !aTab.hasAttribute("pending")) self.capture(aTab, true); }, 2000, this, this._selectedTab); } this._selectedTab = event.target; break; case "SSTabRestored": this.capture(event.target, true); break;
--- a/browser/components/sessionstore/test/browser_586068-cascaded_restore.js +++ b/browser/components/sessionstore/test/browser_586068-cascaded_restore.js @@ -5,18 +5,16 @@ let stateBackup = ss.getBrowserState(); const TAB_STATE_NEEDS_RESTORE = 1; const TAB_STATE_RESTORING = 2; function test() { /** Test for Bug 586068 - Cascade page loads when restoring **/ waitForExplicitFinish(); - // Too many uncaught exceptions, see bug 789003. - ignoreAllUncaughtExceptions(); // This test does a lot of window opening / closing and waiting for loads. // In order to prevent timeouts, we'll extend the default that mochitest uses. requestLongerTimeout(4); runNextTest(); } // test_reloadCascade, test_reloadReload are generated tests that are run out // of cycle (since they depend on current state). They're listed in [tests] here
--- a/browser/components/tabview/groupitems.js +++ b/browser/components/tabview/groupitems.js @@ -88,17 +88,17 @@ function GroupItem(listOfEls, options) { this.$resizer = iQ("<div>") .addClass('resizer') .appendTo($container) .hide(); // ___ Titlebar var html = "<div class='title-container'>" + - "<input class='name' placeholder='" + this.defaultName + "'/>" + + "<input class='name' />" + "<div class='title-shield' />" + "</div>"; this.$titlebar = iQ('<div>') .addClass('titlebar') .html(html) .appendTo($container); @@ -107,17 +107,17 @@ function GroupItem(listOfEls, options) { .click(function() { self.closeAll(); }) .attr("title", tabviewString("groupItem.closeGroup")) .appendTo($container); // ___ Title this.$titleContainer = iQ('.title-container', this.$titlebar); - this.$title = iQ('.name', this.$titlebar); + this.$title = iQ('.name', this.$titlebar).attr('placeholder', this.defaultName); this.$titleShield = iQ('.title-shield', this.$titlebar); this.setTitle(options.title); var handleKeyPress = function (e) { if (e.keyCode == KeyEvent.DOM_VK_ESCAPE || e.keyCode == KeyEvent.DOM_VK_RETURN || e.keyCode == KeyEvent.DOM_VK_ENTER) { (self.$title)[0].blur();
--- a/browser/devtools/commandline/CmdScreenshot.jsm +++ b/browser/devtools/commandline/CmdScreenshot.jsm @@ -141,19 +141,21 @@ gcli.addCommand({ imgTools.decodeImageData(input, channel.contentType, container); let wrapped = Cc["@mozilla.org/supports-interface-pointer;1"] .createInstance(Ci.nsISupportsInterfacePointer); wrapped.data = container.value; let trans = Cc["@mozilla.org/widget/transferable;1"] .createInstance(Ci.nsITransferable); - if ("init" in trans) { - trans.init(null); - } + let loadContext = document.defaultView + .QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIWebNavigation) + .QueryInterface(Ci.nsILoadContext); + trans.init(loadContext); trans.addDataFlavor(channel.contentType); trans.setTransferData(channel.contentType, wrapped, -1); let clipid = Ci.nsIClipboard; let clip = Cc["@mozilla.org/widget/clipboard;1"].getService(clipid); clip.setData(trans, null, clipid.kGlobalClipboard); div.textContent = gcli.lookup("screenshotCopied"); return div; @@ -209,25 +211,27 @@ gcli.addCommand({ let persist = Cc["@mozilla.org/embedding/browser/nsWebBrowserPersist;1"] .createInstance(Persist); persist.persistFlags = Persist.PERSIST_FLAGS_REPLACE_EXISTING_FILES | Persist.PERSIST_FLAGS_AUTODETECT_APPLY_CONVERSION; let source = ioService.newURI(data, "UTF8", null); persist.saveURI(source, null, null, null, null, file); - div.textContent = gcli.lookup("screenshotSavedToFile") + " " + filename; + div.textContent = gcli.lookup("screenshotSavedToFile") + " \"" + filename + + "\""; div.addEventListener("click", function openFile() { div.removeEventListener("click", openFile); file.reveal(); }); div.style.cursor = "pointer"; let image = document.createElement("div"); let previewHeight = parseInt(256*height/width); image.setAttribute("style", "width:256px; height:" + previewHeight + "px;" + + "max-height: 256px;" + "background-image: url('" + data + "');" + "background-size: 256px " + previewHeight + "px;" + "margin: 4px; display: block"); div.appendChild(image); return div; } }); \ No newline at end of file
--- a/browser/devtools/commandline/test/browser_cmd_screenshot.js +++ b/browser/devtools/commandline/test/browser_cmd_screenshot.js @@ -5,16 +5,18 @@ const TEST_URI = "http://example.com/browser/browser/devtools/commandline/" + "test/browser_cmd_screenshot.html"; const { classes: Cc, interfaces: Ci, utils: Cu } = Components; let tempScope = {}; Cu.import("resource://gre/modules/FileUtils.jsm", tempScope); let FileUtils = tempScope.FileUtils; +let pb = Cc["@mozilla.org/privatebrowsing;1"] + .getService(Ci.nsIPrivateBrowsingService); function test() { DeveloperToolbarTest.test(TEST_URI, [ testInput, testCapture ]); } function testInput() { helpers.setInput('screenshot'); helpers.check({ input: 'screenshot', @@ -74,75 +76,104 @@ function testCapture() { gFile.remove(false); return true; } else { return false; } } - function clearClipboard() { - let clipid = Ci.nsIClipboard; - let clip = Cc["@mozilla.org/widget/clipboard;1"].getService(clipid); - clip.emptyClipboard(clipid.kGlobalClipboard); - } - function checkClipboard() { try { let clipid = Ci.nsIClipboard; let clip = Cc["@mozilla.org/widget/clipboard;1"].getService(clipid); let trans = Cc["@mozilla.org/widget/transferable;1"] .createInstance(Ci.nsITransferable); - if ("init" in trans) { - trans.init(null); - } - let io = Cc["@mozilla.org/network/io-service;1"] - .getService(Ci.nsIIOService); - let contentType = io.newChannel("", null, null).contentType; - trans.addDataFlavor(contentType); + trans.init(null); + trans.addDataFlavor("image/png"); clip.getData(trans, clipid.kGlobalClipboard); let str = new Object(); let strLength = new Object(); - trans.getTransferData(contentType, str, strLength); - if (str && strLength > 0) { - clip.emptyClipboard(clipid.kGlobalClipboard); + trans.getTransferData("image/png", str, strLength); + if (str.value && strLength.value > 0) { return true; } } catch (ex) {} return false; } + let PBEntered = DeveloperToolbarTest.checkCalled(function() { + Services.obs.removeObserver(PBEntered, + "private-browsing-transition-complete", + false); + + Services.obs.addObserver(PBLeft, "last-pb-context-exited", false); + + DeveloperToolbarTest.exec({ + typed: "screenshot --clipboard", + args: { + delay: 0, + filename: " ", + fullpage: false, + clipboard: true, + node: null, + chrome: false, + }, + outputMatch: new RegExp("^Copied to clipboard.$"), + }); + + ok(checkClipboard(), "Screenshot present in clipboard in private browsing"); + + pb.privateBrowsingEnabled = false; + }); + + let PBLeft = DeveloperToolbarTest.checkCalled(function() { + Services.obs.removeObserver(PBLeft, "last-pb-context-exited", false); + executeSoon(function() { + ok(!checkClipboard(), "Screenshot taken in private browsing mode is not" + + " present outside of it in the clipboard"); + Services.prefs.clearUserPref("browser.privatebrowsing.keep_current_session"); + pb = null; + }); + }); + let path = FileUtils.getFile("TmpD", ["TestScreenshotFile.png"]).path; DeveloperToolbarTest.exec({ typed: "screenshot " + path, args: { delay: 0, filename: "" + path, fullpage: false, clipboard: false, node: null, chrome: false, }, outputMatch: new RegExp("^Saved to "), }); - ok(checkTemporaryFile, "Screenshot got created"); + Services.obs.addObserver(PBEntered, "private-browsing-transition-complete", + false); - clearClipboard(); + executeSoon(function() { + ok(checkTemporaryFile(), "Screenshot got created"); - DeveloperToolbarTest.exec({ - typed: "screenshot --fullpage --clipboard", - args: { - delay: 0, - filename: " ", - fullpage: true, - clipboard: true, - node: null, - chrome: false, - }, - outputMatch: new RegExp("^Copied to clipboard.$"), + DeveloperToolbarTest.exec({ + typed: "screenshot --fullpage --clipboard", + args: { + delay: 0, + filename: " ", + fullpage: true, + clipboard: true, + node: null, + chrome: false, + }, + outputMatch: new RegExp("^Copied to clipboard.$"), + }); + + ok(checkClipboard(), "Screenshot got created and copied"); + + Services.prefs.setBoolPref("browser.privatebrowsing.keep_current_session", true); + + pb.privateBrowsingEnabled = true; }); - - ok(checkClipboard, "Screenshot got created and copied"); } -
--- a/browser/devtools/markupview/MarkupView.jsm +++ b/browser/devtools/markupview/MarkupView.jsm @@ -6,22 +6,25 @@ const Cc = Components.classes; const Cu = Components.utils; const Ci = Components.interfaces; // Page size for pageup/pagedown const PAGE_SIZE = 10; +const PREVIEW_AREA = 700; + var EXPORTED_SYMBOLS = ["MarkupView"]; Cu.import("resource:///modules/devtools/LayoutHelpers.jsm"); Cu.import("resource:///modules/devtools/CssRuleView.jsm"); Cu.import("resource:///modules/devtools/Templater.jsm"); -Cu.import("resource:///modules/devtools/Undo.jsm") +Cu.import("resource:///modules/devtools/Undo.jsm"); +Cu.import("resource://gre/modules/Services.jsm"); /** * Vocabulary for the purposes of this file: * * MarkupContainer - the structure that holds an editor and its * immediate children in the markup panel. * Node - A content node. * object.elt - A UI element in the markup panel. @@ -54,16 +57,18 @@ function MarkupView(aInspector, aFrame) this._inspector.on("select", this._boundSelect); this._onSelect(); this._boundKeyDown = this._onKeyDown.bind(this); this._frame.addEventListener("keydown", this._boundKeyDown, false); this._boundFocus = this._onFocus.bind(this); this._frame.addEventListener("focus", this._boundFocus, false); + + this._initPreview(); } MarkupView.prototype = { _selectedContainer: null, /** * Return the selected node. */ @@ -468,31 +473,115 @@ MarkupView.prototype = { /** * Tear down the markup panel. */ destroy: function MT_destroy() { this.undo.destroy(); delete this.undo; - this._frame.addEventListener("focus", this._boundFocus, false); + this._frame.removeEventListener("focus", this._boundFocus, false); delete this._boundFocus; + this._frame.contentWindow.removeEventListener("scroll", this._boundUpdatePreview, true); + this._frame.contentWindow.removeEventListener("resize", this._boundUpdatePreview, true); + this._frame.contentWindow.removeEventListener("overflow", this._boundResizePreview, true); + this._frame.contentWindow.removeEventListener("underflow", this._boundResizePreview, true); + delete this._boundUpdatePreview; + this._frame.removeEventListener("keydown", this._boundKeyDown, true); delete this._boundKeyDown; this._inspector.off("select", this._boundSelect); delete this._boundSelect; delete this._elt; delete this._containers; this._observer.disconnect(); delete this._observer; - } + }, + + /** + * Initialize the preview panel. + */ + _initPreview: function MT_initPreview() + { + if (!Services.prefs.getBoolPref("devtools.inspector.markupPreview")) { + return; + } + + this._previewBar = this.doc.querySelector("#previewbar"); + this._preview = this.doc.querySelector("#preview"); + this._viewbox = this.doc.querySelector("#viewbox"); + + this._previewBar.classList.remove("disabled"); + + this._previewWidth = this._preview.getBoundingClientRect().width; + + this._boundResizePreview = this._resizePreview.bind(this); + this._frame.contentWindow.addEventListener("resize", this._boundResizePreview, true); + this._frame.contentWindow.addEventListener("overflow", this._boundResizePreview, true); + this._frame.contentWindow.addEventListener("underflow", this._boundResizePreview, true); + + this._boundUpdatePreview = this._updatePreview.bind(this); + this._frame.contentWindow.addEventListener("scroll", this._boundUpdatePreview, true); + this._updatePreview(); + }, + + + /** + * Move the preview viewbox. + */ + _updatePreview: function MT_updatePreview() + { + let win = this._frame.contentWindow; + + if (win.scrollMaxY == 0) { + this._previewBar.classList.add("disabled"); + return; + } + + this._previewBar.classList.remove("disabled"); + + let ratio = this._previewWidth / PREVIEW_AREA; + let width = ratio * win.innerWidth; + + let height = ratio * (win.scrollMaxY + win.innerHeight); + let scrollTo + if (height >= win.innerHeight) { + scrollTo = -(height - win.innerHeight) * (win.scrollY / win.scrollMaxY); + this._previewBar.setAttribute("style", "height:" + height + "px;transform:translateY(" + scrollTo + "px)"); + } else { + this._previewBar.setAttribute("style", "height:100%"); + } + + let bgSize = ~~width + "px " + ~~height + "px"; + this._preview.setAttribute("style", "background-size:" + bgSize); + + let height = ~~(win.innerHeight * ratio) + "px"; + let top = ~~(win.scrollY * ratio) + "px"; + this._viewbox.setAttribute("style", "height:" + height + ";transform: translateY(" + top + ")"); + }, + + /** + * Hide the preview while resizing, to avoid slowness. + */ + _resizePreview: function MT_resizePreview() + { + let win = this._frame.contentWindow; + this._previewBar.classList.add("hide"); + win.clearTimeout(this._resizePreviewTimeout); + + win.setTimeout(function() { + this._updatePreview(); + this._previewBar.classList.remove("hide"); + }.bind(this), 1000); + }, + }; /** * The main structure for storing a document node in the markup * tree. Manages creation of the editor for the node and * a <ul> for placing child elements, and expansion/collapsing * of the element.
--- a/browser/devtools/markupview/markup-view.css +++ b/browser/devtools/markupview/markup-view.css @@ -14,8 +14,50 @@ ul.children:not([expanded]) { display: inline-block; } .newattr { display: inline-block; width: 1em; height: 1ex; } + +#root { + margin-right: 80px; +} + +/* Preview */ + +#previewbar { + position: fixed; + top: 0; + right: 0; + width: 90px; + background: black; + border-left: 1px solid #333; + border-bottom: 1px solid #333; + overflow: hidden; +} + +#preview { + position: absolute; + top: 0; + right: 5px; + width: 80px; + height: 100%; + background-image: -moz-element(#root); + background-repeat: no-repeat; +} + +#previewbar.hide, +#previewbar.disabled { + display: none; +} + +#viewbox { + position: absolute; + top: 0; + right: 5px; + width: 80px; + border: 1px dashed #888; + background: rgba(205,205,255,0.2); + outline: 1px solid transparent; +}
--- a/browser/devtools/markupview/markup-view.xhtml +++ b/browser/devtools/markupview/markup-view.xhtml @@ -8,28 +8,32 @@ <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> <link rel="stylesheet" href="chrome://browser/content/devtools/markup-view.css" type="text/css"/> <link rel="stylesheet" href="chrome://browser/skin/devtools/markup-view.css" type="text/css"/> </head> <body role="application"> <div id="root"></div> <div id="templates" style="display:none"> - <ul> - <li id="template-container" save="${elt}" class="container"><span save="${expander}" class="expander"></span><span save="${codeBox}" class="codebox"><ul save="${children}" class="children"></ul></span></li> + <ul> + <li id="template-container" save="${elt}" class="container"><span save="${expander}" class="expander"></span><span save="${codeBox}" class="codebox"><ul save="${children}" class="children"></ul></span></li> </ul> <span id="template-element" save="${elt}" class="editor"><span><</span><span save="${tag}" class="tagname"></span><span save="${attrList}"></span><span save="${newAttr}" class="newattr" tabindex="0"></span>></span> <span id="template-attribute" save="${attr}" data-attr="${attrName}" class="attreditor" style="display:none"> <span class="editable" save="${inner}" tabindex="0"><span save="${name}" class="attrname"></span>="<span save="${val}" class="attrvalue"></span>"</span></span> <span id="template-text" save="${elt}" class="editor"> <pre save="${value}" style="display:inline-block;" tabindex="0"></pre> </span> <span id="template-comment" save="${elt}" class="editor comment"> <span><!--</span><pre save="${value}" style="display:inline-block;" tabindex="0"></pre><span>--></span> </span> <span id="template-elementClose" save="${closeElt}"></<span save="${closeTag}" class="tagname"></span>></span> </div> + <div id="previewbar" class="disabled"> + <div id="preview"/> + <div id="viewbox"/> + </div> </body> </html>
--- a/browser/devtools/responsivedesign/responsivedesign.jsm +++ b/browser/devtools/responsivedesign/responsivedesign.jsm @@ -100,46 +100,49 @@ function ResponsiveUI(aWindow, aTab) try { presets = JSON.parse(Services.prefs.getCharPref("devtools.responsiveUI.presets")); } catch(e) { // User pref is malformated. Cu.reportError("Could not parse pref `devtools.responsiveUI.presets`: " + e); } } + this.customPreset = {key: "custom", custom: true}; + if (Array.isArray(presets)) { - this.presets = [{key: "custom", custom: true}].concat(presets) + this.presets = [this.customPreset].concat(presets); } else { Cu.reportError("Presets value (devtools.responsiveUI.presets) is malformated."); - this.presets = [{key: "custom", custom: true}]; + this.presets = [this.customPreset]; } try { let width = Services.prefs.getIntPref("devtools.responsiveUI.customWidth"); let height = Services.prefs.getIntPref("devtools.responsiveUI.customHeight"); - this.presets[0].width = Math.min(MAX_WIDTH, width); - this.presets[0].height = Math.min(MAX_HEIGHT, height); + this.customPreset.width = Math.min(MAX_WIDTH, width); + this.customPreset.height = Math.min(MAX_HEIGHT, height); - let key = Services.prefs.getCharPref("devtools.responsiveUI.currentPreset"); - let idx = this.getPresetIdx(key); - this.currentPreset = (idx == -1 ? 0 : idx); + this.currentPresetKey = Services.prefs.getCharPref("devtools.responsiveUI.currentPreset"); } catch(e) { // Default size. The first preset (custom) is the one that will be used. let bbox = this.stack.getBoundingClientRect(); - this.presets[0].width = bbox.width - 40; // horizontal padding of the container - this.presets[0].height = bbox.height - 80; // vertical padding + toolbar height - this.currentPreset = 0; // Custom + this.customPreset.width = bbox.width - 40; // horizontal padding of the container + this.customPreset.height = bbox.height - 80; // vertical padding + toolbar height + + this.currentPresetKey = this.customPreset.key; } this.container.setAttribute("responsivemode", "true"); this.stack.setAttribute("responsivemode", "true"); // Let's bind some callbacks. this.bound_presetSelected = this.presetSelected.bind(this); + this.bound_addPreset = this.addPreset.bind(this); + this.bound_removePreset = this.removePreset.bind(this); this.bound_rotate = this.rotate.bind(this); this.bound_startResizing = this.startResizing.bind(this); this.bound_stopResizing = this.stopResizing.bind(this); this.bound_onDrag = this.onDrag.bind(this); this.bound_onKeypress = this.onKeypress.bind(this); // Events this.tab.addEventListener("TabClose", this); @@ -184,53 +187,38 @@ ResponsiveUI.prototype = { "min-width: 0;" + "max-height: none;" + "min-height: 0;"; this.stack.setAttribute("style", style); if (this.isResizing) this.stopResizing(); - this.saveCurrentPreset(); - // Remove listeners. this.mainWindow.document.removeEventListener("keypress", this.bound_onKeypress, false); this.menulist.removeEventListener("select", this.bound_presetSelected, true); this.tab.removeEventListener("TabClose", this); this.tabContainer.removeEventListener("TabSelect", this); this.rotatebutton.removeEventListener("command", this.bound_rotate, true); + this.addbutton.removeEventListener("command", this.bound_addPreset, true); + this.removebutton.removeEventListener("command", this.bound_removePreset, true); // Removed elements. this.container.removeChild(this.toolbar); this.stack.removeChild(this.resizer); this.stack.removeChild(this.resizeBar); // Unset the responsive mode. this.container.removeAttribute("responsivemode"); this.stack.removeAttribute("responsivemode"); delete this.tab.__responsiveUI; }, /** - * Retrieve a preset from its key. - * - * @param aKey preset's key. - * @returns the index of the preset, -1 if not found. - */ - getPresetIdx: function RUI_getPresetIdx(aKey) { - for (let i = 0; i < this.presets.length; i++) { - if (this.presets[i].key == aKey) { - return i; - } - } - return -1; - }, - - /** * Handle keypressed. * * @param aEvent */ onKeypress: function RUI_onKeypress(aEvent) { if (aEvent.keyCode == this.mainWindow.KeyEvent.DOM_VK_ESCAPE && this.mainWindow.gBrowser.selectedBrowser == this.browser) { @@ -299,20 +287,34 @@ ResponsiveUI.prototype = { this.toolbar = this.chromeDoc.createElement("toolbar"); this.toolbar.className = "devtools-toolbar devtools-responsiveui-toolbar"; this.menulist = this.chromeDoc.createElement("menulist"); this.menulist.className = "devtools-menulist"; this.menulist.addEventListener("select", this.bound_presetSelected, true); + this.menuitems = new Map(); + let menupopup = this.chromeDoc.createElement("menupopup"); this.registerPresets(menupopup); this.menulist.appendChild(menupopup); + this.addbutton = this.chromeDoc.createElement("menuitem"); + this.addbutton.setAttribute("label", this.strings.GetStringFromName("responsiveUI.addPreset")); + this.addbutton.addEventListener("command", this.bound_addPreset, true); + + this.removebutton = this.chromeDoc.createElement("menuitem"); + this.removebutton.setAttribute("label", this.strings.GetStringFromName("responsiveUI.removePreset")); + this.removebutton.addEventListener("command", this.bound_removePreset, true); + + menupopup.appendChild(this.chromeDoc.createElement("menuseparator")); + menupopup.appendChild(this.addbutton); + menupopup.appendChild(this.removebutton); + this.rotatebutton = this.chromeDoc.createElement("toolbarbutton"); this.rotatebutton.setAttribute("tabindex", "0"); this.rotatebutton.setAttribute("label", this.strings.GetStringFromName("responsiveUI.rotate")); this.rotatebutton.className = "devtools-toolbarbutton"; this.rotatebutton.addEventListener("command", this.bound_rotate, true); this.toolbar.appendChild(this.menulist); this.toolbar.appendChild(this.rotatebutton); @@ -339,123 +341,239 @@ ResponsiveUI.prototype = { * Build the presets list and append it to the menupopup. * * @param aParent menupopup. */ registerPresets: function RUI_registerPresets(aParent) { let fragment = this.chromeDoc.createDocumentFragment(); let doc = this.chromeDoc; - for (let i = 0; i < this.presets.length; i++) { + for (let preset of this.presets) { let menuitem = doc.createElement("menuitem"); - if (i == this.currentPreset) + menuitem.setAttribute("ispreset", true); + this.menuitems.set(menuitem, preset); + + if (preset.key === this.currentPresetKey) { menuitem.setAttribute("selected", "true"); - this.setMenuLabel(menuitem, this.presets[i]); + this.selectedItem = menuitem; + } + + if (preset.custom) + this.customMenuitem = menuitem; + + this.setMenuLabel(menuitem, preset); fragment.appendChild(menuitem); } aParent.appendChild(fragment); }, /** * Set the menuitem label of a preset. * * @param aMenuitem menuitem to edit. * @param aPreset associated preset. */ setMenuLabel: function RUI_setMenuLabel(aMenuitem, aPreset) { let size = Math.round(aPreset.width) + "x" + Math.round(aPreset.height); if (aPreset.custom) { let str = this.strings.formatStringFromName("responsiveUI.customResolution", [size], 1); aMenuitem.setAttribute("label", str); + } else if (aPreset.name != null && aPreset.name !== "") { + let str = this.strings.formatStringFromName("responsiveUI.namedResolution", [size, aPreset.name], 2); + aMenuitem.setAttribute("label", str); } else { aMenuitem.setAttribute("label", size); } }, /** * When a preset is selected, apply it. */ presetSelected: function RUI_presetSelected() { - this.rotateValue = false; - this.currentPreset = this.menulist.selectedIndex; - let preset = this.presets[this.currentPreset]; - this.loadPreset(preset); + if (this.menulist.selectedItem.getAttribute("ispreset") === "true") { + this.selectedItem = this.menulist.selectedItem; + + this.rotateValue = false; + let selectedPreset = this.menuitems.get(this.selectedItem); + this.loadPreset(selectedPreset); + this.currentPresetKey = selectedPreset.key; + this.saveCurrentPreset(); + + // Update the buttons hidden status according to the new selected preset + if (selectedPreset == this.customPreset) { + this.addbutton.hidden = false; + this.removebutton.hidden = true; + } else { + this.addbutton.hidden = true; + this.removebutton.hidden = false; + } + } }, /** * Apply a preset. * * @param aPreset preset to apply. */ loadPreset: function RUI_loadPreset(aPreset) { this.setSize(aPreset.width, aPreset.height); }, /** + * Add a preset to the list and the memory + */ + addPreset: function RUI_addPreset() { + let w = this.customPreset.width; + let h = this.customPreset.height; + let newName = {}; + + let title = this.strings.GetStringFromName("responsiveUI.customNamePromptTitle"); + let message = this.strings.formatStringFromName("responsiveUI.customNamePromptMsg", [w, h], 2); + Services.prompt.prompt(null, title, message, newName, null, {}); + + let newPreset = { + key: w + "x" + h, + name: newName.value, + width: w, + height: h + }; + + this.presets.push(newPreset); + + // Sort the presets according to width/height ascending order + this.presets.sort(function RUI_sortPresets(aPresetA, aPresetB) { + // We keep custom preset at first + if (aPresetA.custom && !aPresetB.custom) { + return 1; + } + if (!aPresetA.custom && aPresetB.custom) { + return -1; + } + + if (aPresetA.width === aPresetB.width) { + if (aPresetA.height === aPresetB.height) { + return 0; + } else { + return aPresetA.height > aPresetB.height; + } + } else { + return aPresetA.width > aPresetB.width; + } + }); + + this.savePresets(); + + let newMenuitem = this.chromeDoc.createElement("menuitem"); + newMenuitem.setAttribute("ispreset", true); + this.setMenuLabel(newMenuitem, newPreset); + + this.menuitems.set(newMenuitem, newPreset); + let idx = this.presets.indexOf(newPreset); + let beforeMenuitem = this.menulist.firstChild.childNodes[idx + 1]; + this.menulist.firstChild.insertBefore(newMenuitem, beforeMenuitem); + + this.menulist.selectedItem = newMenuitem; + this.currentPresetKey = newPreset.key; + this.saveCurrentPreset(); + }, + + /** + * remove a preset from the list and the memory + */ + removePreset: function RUI_removePreset() { + let selectedPreset = this.menuitems.get(this.selectedItem); + let w = selectedPreset.width; + let h = selectedPreset.height; + + this.presets.splice(this.presets.indexOf(selectedPreset), 1); + this.menulist.firstChild.removeChild(this.selectedItem); + this.menuitems.delete(this.selectedItem); + + this.customPreset.width = w; + this.customPreset.height = h; + let menuitem = this.customMenuitem; + this.setMenuLabel(menuitem, this.customPreset); + this.menulist.selectedItem = menuitem; + this.currentPresetKey = this.customPreset.key; + + this.setSize(w, h); + + this.savePresets(); + }, + + /** * Swap width and height. */ rotate: function RUI_rotate() { - this.setSize(this.currentHeight, this.currentWidth); - if (this.currentPreset == 0) { + let selectedPreset = this.menuitems.get(this.selectedItem); + let width = this.rotateValue ? selectedPreset.height : selectedPreset.width; + let height = this.rotateValue ? selectedPreset.width : selectedPreset.height; + + this.setSize(height, width); + + if (selectedPreset.custom) { this.saveCustomSize(); } else { this.rotateValue = !this.rotateValue; + this.saveCurrentPreset(); } }, /** * Change the size of the browser. * * @param aWidth width of the browser. * @param aHeight height of the browser. */ setSize: function RUI_setSize(aWidth, aHeight) { - this.currentWidth = Math.min(Math.max(aWidth, MIN_WIDTH), MAX_WIDTH); - this.currentHeight = Math.min(Math.max(aHeight, MIN_HEIGHT), MAX_WIDTH); + aWidth = Math.min(Math.max(aWidth, MIN_WIDTH), MAX_WIDTH); + aHeight = Math.min(Math.max(aHeight, MIN_HEIGHT), MAX_WIDTH); // We resize the containing stack. let style = "max-width: %width;" + "min-width: %width;" + "max-height: %height;" + "min-height: %height;"; - style = style.replace(/%width/g, this.currentWidth + "px"); - style = style.replace(/%height/g, this.currentHeight + "px"); + style = style.replace(/%width/g, aWidth + "px"); + style = style.replace(/%height/g, aHeight + "px"); this.stack.setAttribute("style", style); if (!this.ignoreY) - this.resizeBar.setAttribute("top", Math.round(this.currentHeight / 2)); + this.resizeBar.setAttribute("top", Math.round(aHeight / 2)); + + let selectedPreset = this.menuitems.get(this.selectedItem); - // We uptate the Custom menuitem if we are not using a preset. - if (this.presets[this.currentPreset].custom) { - let preset = this.presets[this.currentPreset]; - preset.width = this.currentWidth; - preset.height = this.currentHeight; + // We uptate the custom menuitem if we are using it + if (selectedPreset.custom) { + selectedPreset.width = aWidth; + selectedPreset.height = aHeight; - let menuitem = this.menulist.firstChild.childNodes[this.currentPreset]; - this.setMenuLabel(menuitem, preset); + this.setMenuLabel(this.selectedItem, selectedPreset); } }, /** * Start the process of resizing the browser. * * @param aEvent */ startResizing: function RUI_startResizing(aEvent) { - let preset = this.presets[this.currentPreset]; - if (!preset.custom) { - this.currentPreset = 0; - preset = this.presets[0]; - preset.width = this.currentWidth; - preset.height = this.currentHeight; - let menuitem = this.menulist.firstChild.childNodes[0]; - this.setMenuLabel(menuitem, preset); - this.menulist.selectedIndex = 0; + let selectedPreset = this.menuitems.get(this.selectedItem); + + if (!selectedPreset.custom) { + this.customPreset.width = this.rotateValue ? selectedPreset.height : selectedPreset.width; + this.customPreset.height = this.rotateValue ? selectedPreset.width : selectedPreset.height; + + let menuitem = this.customMenuitem; + this.setMenuLabel(menuitem, this.customPreset); + + this.currentPresetKey = this.customPreset.key; + this.menulist.selectedItem = menuitem; } this.mainWindow.addEventListener("mouseup", this.bound_stopResizing, true); this.mainWindow.addEventListener("mousemove", this.bound_onDrag, true); this.container.style.pointerEvents = "none"; this._resizing = true; this.stack.setAttribute("notransition", "true"); @@ -474,18 +592,18 @@ ResponsiveUI.prototype = { */ onDrag: function RUI_onDrag(aEvent) { let deltaX = aEvent.clientX - this.lastClientX; let deltaY = aEvent.clientY - this.lastClientY; if (this.ignoreY) deltaY = 0; - let width = this.currentWidth + deltaX; - let height = this.currentHeight + deltaY; + let width = this.customPreset.width + deltaX; + let height = this.customPreset.height + deltaY; if (width < MIN_WIDTH) { width = MIN_WIDTH; } else { this.lastClientX = aEvent.clientX; } if (height < MIN_HEIGHT) { @@ -515,25 +633,36 @@ ResponsiveUI.prototype = { this.ignoreY = false; this.isResizing = false; }, /** * Store the custom size as a pref. */ saveCustomSize: function RUI_saveCustomSize() { - Services.prefs.setIntPref("devtools.responsiveUI.customWidth", this.currentWidth); - Services.prefs.setIntPref("devtools.responsiveUI.customHeight", this.currentHeight); + Services.prefs.setIntPref("devtools.responsiveUI.customWidth", this.customPreset.width); + Services.prefs.setIntPref("devtools.responsiveUI.customHeight", this.customPreset.height); }, /** * Store the current preset as a pref. */ saveCurrentPreset: function RUI_saveCurrentPreset() { - let key = this.presets[this.currentPreset].key; - Services.prefs.setCharPref("devtools.responsiveUI.currentPreset", key); + Services.prefs.setCharPref("devtools.responsiveUI.currentPreset", this.currentPresetKey); Services.prefs.setBoolPref("devtools.responsiveUI.rotate", this.rotateValue); }, + + /** + * Store the list of all registered presets as a pref. + */ + savePresets: function RUI_savePresets() { + // We exclude the custom one + let registeredPresets = this.presets.filter(function (aPreset) { + return !aPreset.custom; + }); + + Services.prefs.setCharPref("devtools.responsiveUI.presets", JSON.stringify(registeredPresets)); + }, } XPCOMUtils.defineLazyGetter(ResponsiveUI.prototype, "strings", function () { return Services.strings.createBundle("chrome://browser/locale/devtools/responsiveUI.properties"); });
--- a/browser/devtools/responsivedesign/test/Makefile.in +++ b/browser/devtools/responsivedesign/test/Makefile.in @@ -44,15 +44,16 @@ relativesrcdir = @relativesrcdir@ include $(DEPTH)/config/autoconf.mk include $(topsrcdir)/config/rules.mk _BROWSER_FILES = \ browser_responsiveui.js \ browser_responsiveruleview.js \ browser_responsive_cmd.js \ browser_responsivecomputedview.js \ + browser_responsiveuiaddcustompreset.js \ head.js \ helpers.js \ $(NULL) libs:: $(_BROWSER_FILES) $(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/browser/$(relativesrcdir)
--- a/browser/devtools/responsivedesign/test/browser_responsiveui.js +++ b/browser/devtools/responsivedesign/test/browser_responsiveui.js @@ -45,17 +45,18 @@ function test() { instance.menulist.selectedIndex = c; let item = instance.menulist.firstChild.childNodes[c]; let [width, height] = extractSizeFromString(item.getAttribute("label")); is(content.innerWidth, width, "preset " + c + ": dimension valid (width)"); is(content.innerHeight, height, "preset " + c + ": dimension valid (height)"); testOnePreset(c - 1); } - testOnePreset(instance.menulist.firstChild.childNodes.length - 1); + // Starting from length - 4 because last 3 items are not presets : separator, addbutton and removebutton + testOnePreset(instance.menulist.firstChild.childNodes.length - 4); } function extractSizeFromString(str) { let numbers = str.match(/(\d+)[^\d]*(\d+)/); if (numbers) { return [numbers[1], numbers[2]]; } else { return [null, null];
new file mode 100644 --- /dev/null +++ b/browser/devtools/responsivedesign/test/browser_responsiveuiaddcustompreset.js @@ -0,0 +1,162 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +function test() { + let instance, deletedPresetA, deletedPresetB, oldPrompt; + + waitForExplicitFinish(); + + gBrowser.selectedTab = gBrowser.addTab(); + gBrowser.selectedBrowser.addEventListener("load", function onload() { + gBrowser.selectedBrowser.removeEventListener("load", onload, true); + waitForFocus(startTest, content); + }, true); + + content.location = "data:text/html,foo"; + + function startTest() { + // Mocking prompt + oldPrompt = Services.prompt; + Services.prompt = { + prompt: function(aParent, aDialogTitle, aText, aValue, aCheckMsg, aCheckState) { + aValue.value = "Testing preset"; + } + }; + + document.getElementById("Tools:ResponsiveUI").removeAttribute("disabled"); + synthesizeKeyFromKeyTag("key_responsiveUI"); + executeSoon(onUIOpen); + } + + function onUIOpen() { + // Is it open? + let container = gBrowser.getBrowserContainer(); + is(container.getAttribute("responsivemode"), "true", "In responsive mode."); + + instance = gBrowser.selectedTab.__responsiveUI; + ok(instance, "instance of the module is attached to the tab."); + + instance.transitionsEnabled = false; + + testAddCustomPreset(); + } + + function testAddCustomPreset() { + let customHeight = 123, customWidth = 456; + instance.setSize(customWidth, customHeight); + + // Adds the custom preset with "Testing preset" as label (look at mock upper) + instance.addbutton.doCommand(); + + instance.menulist.selectedIndex = 1; + + EventUtils.synthesizeKey("VK_ESCAPE", {}); + executeSoon(restart); + } + + function restart() { + synthesizeKeyFromKeyTag("key_responsiveUI"); + + let container = gBrowser.getBrowserContainer(); + is(container.getAttribute("responsivemode"), "true", "In responsive mode."); + + instance = gBrowser.selectedTab.__responsiveUI; + + testCustomPresetInList(); + } + + function testCustomPresetInList() { + let customPresetIndex = getPresetIndex("456x123 (Testing preset)"); + ok(customPresetIndex >= 0, "is the previously added preset (idx = " + customPresetIndex + ") in the list of items"); + + instance.menulist.selectedIndex = customPresetIndex; + + is(content.innerWidth, 456, "add preset, and selected in the list, dimension valid (width)"); + is(content.innerHeight, 123, "add preset, and selected in the list, dimension valid (height)"); + + testDeleteCustomPresets(); + } + + function testDeleteCustomPresets() { + instance.removebutton.doCommand(); + + instance.menulist.selectedIndex = 2; + deletedPresetA = instance.menulist.selectedItem.getAttribute("label"); + instance.removebutton.doCommand(); + + instance.menulist.selectedIndex = 2; + deletedPresetB = instance.menulist.selectedItem.getAttribute("label"); + instance.removebutton.doCommand(); + + EventUtils.synthesizeKey("VK_ESCAPE", {}); + executeSoon(restartAgain); + } + + function restartAgain() { + synthesizeKeyFromKeyTag("key_responsiveUI"); + instance = gBrowser.selectedTab.__responsiveUI; + executeSoon(testCustomPresetsNotInListAnymore); + } + + function testCustomPresetsNotInListAnymore() { + let customPresetIndex = getPresetIndex(deletedPresetA); + is(customPresetIndex, -1, "deleted preset " + deletedPresetA + " is not in the list anymore"); + + customPresetIndex = getPresetIndex(deletedPresetB); + is(customPresetIndex, -1, "deleted preset " + deletedPresetB + " is not in the list anymore"); + + executeSoon(finishUp); + } + + function finishUp() { + delete instance; + gBrowser.removeCurrentTab(); + + Services.prompt = oldPrompt; + + finish(); + } + + function getPresetIndex(presetLabel) { + function testOnePreset(c) { + if (c == 0) { + return -1; + } + instance.menulist.selectedIndex = c; + + let item = instance.menulist.firstChild.childNodes[c]; + if (item.getAttribute("label") === presetLabel) { + return c; + } else { + return testOnePreset(c - 1); + } + } + return testOnePreset(instance.menulist.firstChild.childNodes.length - 4); + } + + function synthesizeKeyFromKeyTag(aKeyId) { + let key = document.getElementById(aKeyId); + isnot(key, null, "Successfully retrieved the <key> node"); + + let modifiersAttr = key.getAttribute("modifiers"); + + let name = null; + + if (key.getAttribute("keycode")) + name = key.getAttribute("keycode"); + else if (key.getAttribute("key")) + name = key.getAttribute("key"); + + isnot(name, null, "Successfully retrieved keycode/key"); + + let modifiers = { + shiftKey: modifiersAttr.match("shift"), + ctrlKey: modifiersAttr.match("ctrl"), + altKey: modifiersAttr.match("alt"), + metaKey: modifiersAttr.match("meta"), + accelKey: modifiersAttr.match("accel") + } + + EventUtils.synthesizeKey(name, modifiers); + } +}
--- a/browser/devtools/scratchpad/scratchpad.xul +++ b/browser/devtools/scratchpad/scratchpad.xul @@ -12,16 +12,17 @@ <?xul-overlay href="chrome://global/content/editMenuOverlay.xul"?> <?xul-overlay href="chrome://browser/content/source-editor-overlay.xul"?> <window id="main-window" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" title="&window.title;" windowtype="devtools:scratchpad" macanimationtype="document" + fullscreenbutton="true" screenX="4" screenY="4" width="640" height="480" persist="screenX screenY width height sizemode"> <script type="application/javascript" src="chrome://global/content/globalOverlay.js"/> <script type="application/javascript" src="chrome://browser/content/scratchpad.js"/> <commandset id="editMenuCommands"/>
--- a/browser/locales/en-US/chrome/browser/devtools/responsiveUI.properties +++ b/browser/locales/en-US/chrome/browser/devtools/responsiveUI.properties @@ -6,12 +6,31 @@ # You want to make that choice consistent across the developer tools. # A good criteria is the language in which you'd find the best # documentation on web development on the web. # LOCALIZATION NOTE (responsiveUI.rotate): label of the rotate button. responsiveUI.rotate=rotate +# LOCALIZATION NOTE (responsiveUI.addPreset): label of the add preset button. +responsiveUI.addPreset=Add Preset + +# LOCALIZATION NOTE (responsiveUI.removePreset): label of the remove preset button. +responsiveUI.removePreset=Remove Preset + # LOCALIZATION NOTE (responsiveUI.customResolution): label of the first item # in the menulist at the beginning of the toolbar. For %S is replace with the # current size of the page. For example: "400x600". responsiveUI.customResolution=%S (custom) + +# LOCALIZATION NOTE (responsiveUI.namedResolution): label of custom items with a name +# in the menulist of the toolbar. +# For example: "320x480 (phone)". +responsiveUI.namedResolution=%S (%S) + +# LOCALIZATION NOTE (responsiveUI.customNamePromptTitle): prompt title when asking +# the user to specify a name for a new custom preset. +responsiveUI.customNamePromptTitle=Responsive Design View + +# LOCALIZATION NOTE (responsiveUI.customNamePromptMsg): prompt message when asking +# the user to specify a name for a new custom preset. +responsiveUI.customNamePromptMsg=Give a name to the %Sx%S preset \ No newline at end of file