merge m-c to fx-team
authorTim 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 id23544
push userttaubert@mozilla.com
push dateThu, 27 Sep 2012 07:12:21 +0000
treeherdermozilla-central@b038e9e2023f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone18.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
nightly linux64
nightly mac
nightly win32
nightly win64
merge m-c to fx-team
build/valgrind/x86_64-redhat-linux-gnu.sup
--- 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>&lt;</span><span save="${tag}" class="tagname"></span><span save="${attrList}"></span><span save="${newAttr}" class="newattr" tabindex="0"></span>&gt;</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>=&quot;<span save="${val}" class="attrvalue"></span>&quot;</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>&lt;!--</span><pre save="${value}" style="display:inline-block;" tabindex="0"></pre><span>--&gt;</span>
     </span>
 
     <span id="template-elementClose" save="${closeElt}">&lt;/<span save="${closeTag}" class="tagname"></span>&gt;</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