Merge fx-team to m-c
authorWes Kocher <wkocher@mozilla.com>
Tue, 21 Jan 2014 17:27:53 -0800
changeset 164533 8f4ecbf938cd15199f017b5b21ca3c3e167802a3
parent 164524 51d581fdf7832f891296ba5dce043aa8ebf3a55f (current diff)
parent 164532 de1374bf0c6edbc1e182078d15d2f99603f39dd6 (diff)
child 164539 93cec25bc798229509902e35c11cfea7503953e4
child 164567 9ab3440df7b8c2639ddbcbc7af0e071553ee2093
child 164601 1f03e8d6f26286f2373f5ddc62aab8ba00491545
child 164614 2cf3d715f52f0699a281840ad8abea45a12dc28e
push id26050
push userkwierso@gmail.com
push dateWed, 22 Jan 2014 01:28:12 +0000
treeherdermozilla-central@8f4ecbf938cd [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone29.0a1
first release with
nightly linux32
8f4ecbf938cd / 29.0a1 / 20140122030521 / files
nightly linux64
8f4ecbf938cd / 29.0a1 / 20140122030521 / files
nightly mac
8f4ecbf938cd / 29.0a1 / 20140122030521 / files
nightly win32
8f4ecbf938cd / 29.0a1 / 20140122030521 / files
nightly win64
8f4ecbf938cd / 29.0a1 / 20140122030521 / 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 fx-team to m-c
--- a/browser/base/content/browser-places.js
+++ b/browser/base/content/browser-places.js
@@ -994,17 +994,17 @@ let BookmarkingUI = {
     if (event.target != event.currentTarget)
       return;
 
     let widget = CustomizableUI.getWidget("bookmarks-menu-button")
                                .forWindow(window);
     if (widget.overflowed) {
       // Don't open a popup in the overflow popup, rather just open the Library.
       event.preventDefault();
-      widget.node.removeAttribute("noautoclose");
+      widget.node.removeAttribute("closemenu");
       PlacesCommandHook.showPlacesOrganizer("BookmarksMenu");
       return;
     }
 
     if (!this._popupNeedsUpdate)
       return;
     this._popupNeedsUpdate = false;
 
@@ -1167,28 +1167,28 @@ let BookmarkingUI = {
 
     // Handle special case when the button is in the panel.
     let widgetGroup = CustomizableUI.getWidget("bookmarks-menu-button");
     let widget = widgetGroup.forWindow(window);
     if (widgetGroup.areaType == CustomizableUI.TYPE_MENU_PANEL) {
       let view = document.getElementById("PanelUI-bookmarks");
       view.addEventListener("ViewShowing", this.onPanelMenuViewShowing);
       view.addEventListener("ViewHiding", this.onPanelMenuViewHiding);
-      widget.node.setAttribute("noautoclose", "true");
+      widget.node.setAttribute("closemenu", "none");
       PanelUI.showSubView("PanelUI-bookmarks", widget.node,
                           CustomizableUI.AREA_PANEL);
       return;
     }
     else if (widget.overflowed) {
       // Allow to close the panel if the page is already bookmarked, cause
       // we are going to open the edit bookmark panel.
       if (this._itemIds.length > 0)
-        widget.node.removeAttribute("noautoclose");
+        widget.node.removeAttribute("closemenu");
       else
-        widget.node.setAttribute("noautoclose", "true");
+        widget.node.setAttribute("closemenu", "none");
     }
 
     // Ignore clicks on the star if we are updating its state.
     if (!this._pendingStmt) {
       PlacesCommandHook.bookmarkCurrentPage(this._itemIds.length > 0);
     }
   },
 
--- a/browser/base/content/browser-social.js
+++ b/browser/base/content/browser-social.js
@@ -1166,17 +1166,17 @@ SocialStatus = {
     // if we're a slice in the hamburger, use that panel instead
     let widgetGroup = CustomizableUI.getWidget(aToolbarButton.getAttribute("id"));
     let widget = widgetGroup.forWindow(window);
     let panel, showingEvent, hidingEvent;
     let inMenuPanel = widgetGroup.areaType == CustomizableUI.TYPE_MENU_PANEL;
     if (inMenuPanel) {
       panel = document.getElementById("PanelUI-socialapi");
       this._attachNotificatonPanel(panel, aToolbarButton, provider);
-      widget.node.setAttribute("noautoclose", "true");
+      widget.node.setAttribute("closemenu", "none");
       showingEvent = "ViewShowing";
       hidingEvent = "ViewHiding";
     } else {
       panel = document.getElementById("social-notification-panel");
       this._attachNotificatonPanel(panel, aToolbarButton, provider);
       showingEvent = "popupshown";
       hidingEvent = "popuphidden";
     }
--- a/browser/base/content/socialmarks.xml
+++ b/browser/base/content/socialmarks.xml
@@ -18,17 +18,17 @@
       <field name="inMenuPanel">false</field>
 
       <property name="panel">
         <getter>
           let widgetGroup = CustomizableUI.getWidget(this.getAttribute("id"));
           let widget = widgetGroup.forWindow(window);
           this.inMenuPanel = widgetGroup.areaType == CustomizableUI.TYPE_MENU_PANEL;
           if (this.inMenuPanel) {
-            widget.node.setAttribute("noautoclose", "true");
+            widget.node.setAttribute("closemenu", "none");
             return document.getElementById("PanelUI-socialapi");
           }
           return document.getAnonymousElementByAttribute(this, "anonid", "panel");
         </getter>
       </property>
 
       <property name="content">
         <getter><![CDATA[
--- a/browser/components/customizableui/content/customizeMode.inc.xul
+++ b/browser/components/customizableui/content/customizeMode.inc.xul
@@ -2,18 +2,27 @@
    - License, v. 2.0. If a copy of the MPL was not distributed with this
    - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
 
 <hbox id="customization-container" flex="1" hidden="true">
   <vbox flex="1" id="customization-palette-container">
     <label id="customization-header">
       &customizeMode.menuAndToolbars.header;
     </label>
+    <hbox id="customization-empty" hidden="true">
+      <label>&customizeMode.menuAndToolbars.empty;</label>
+      <label onclick="BrowserOpenAddonsMgr('addons://discovery/');"
+             onkeypress="BrowserOpenAddonsMgr('addons://discovery/');"
+             id="customization-more-tools"
+             class="text-link">
+        &customizeMode.menuAndToolbars.emptyLink;
+      </label>
+    </hbox>
     <vbox id="customization-palette" flex="1"/>
-    <spacer flex="1"/>
+    <spacer id="customization-spacer" flex="1"/>
     <hbox>
       <button id="customization-toolbar-visibility-button" label="&customizeMode.toolbars;" class="customizationmode-button" type="menu">
         <menupopup id="customization-toolbar-menu" onpopupshowing="onViewToolbarsPopupShowing(event)"/>
       </button>
       <spacer flex="1"/>
       <button id="customization-reset-button" oncommand="gCustomizeMode.reset();" label="&customizeMode.restoreDefaults;" class="customizationmode-button"/>
     </hbox>
   </vbox>
--- a/browser/components/customizableui/content/panelUI.js
+++ b/browser/components/customizableui/content/panelUI.js
@@ -325,19 +325,25 @@ const PanelUI = {
     }
   },
 
   /**
    * This function can be used as a command event listener for subviews
    * so that the panel knows if and when to close itself.
    */
   onCommandHandler: function(aEvent) {
-    if (!aEvent.originalTarget.hasAttribute("noautoclose")) {
-      PanelUI.hide();
+    let closemenu = aEvent.originalTarget.getAttribute("closemenu");
+    if (closemenu == "none") {
+      return;
     }
+    if (closemenu == "single") {
+      this.showMainView();
+      return;
+    }
+    this.hide();
   },
 
   /**
    * Open a dialog window that allow the user to customize listed character sets.
    */
   onCharsetCustomizeCommand: function() {
     this.hide();
     window.openDialog("chrome://global/content/customizeCharset.xul",
--- a/browser/components/customizableui/src/CustomizableUI.jsm
+++ b/browser/components/customizableui/src/CustomizableUI.jsm
@@ -1225,30 +1225,31 @@ let CustomizableUIInternal = {
   maybeAutoHidePanel: function(aEvent) {
     if (aEvent.type == "keypress") {
       if (aEvent.keyCode != aEvent.DOM_VK_ENTER &&
           aEvent.keyCode != aEvent.DOM_VK_RETURN) {
         return;
       }
       // If the user hit enter/return, we don't check preventDefault - it makes sense
       // that this was prevented, but we probably still want to close the panel.
-      // If consumers don't want this to happen, they should specify noautoclose.
+      // If consumers don't want this to happen, they should specify the closemenu
+      // attribute.
 
     } else if (aEvent.type != "command") { // mouse events:
       if (aEvent.defaultPrevented || aEvent.button != 0) {
         return;
       }
       let isInteractive = this._isOnInteractiveElement(aEvent);
       LOG("maybeAutoHidePanel: interactive ? " + isInteractive);
       if (isInteractive) {
         return;
       }
     }
 
-    if (aEvent.target.getAttribute("noautoclose") == "true" ||
+    if (aEvent.target.getAttribute("closemenu") == "none" ||
         aEvent.target.getAttribute("widget-type") == "view") {
       return;
     }
 
     // If we get here, we can actually hide the popup:
     this.hidePanelForNode(aEvent.target);
   },
 
--- a/browser/components/customizableui/src/CustomizableWidgets.jsm
+++ b/browser/components/customizableui/src/CustomizableWidgets.jsm
@@ -39,22 +39,22 @@ function setAttributes(aNode, aAttrs) {
     } else {
       if (name == "label" || name == "tooltiptext")
         value = CustomizableUI.getLocalizedProperty({id: aAttrs.id}, name);
       aNode.setAttribute(name, value);
     }
   }
 }
 
-function updateCombinedWidgetStyle(aNode, aArea, aModifyAutoclose) {
+function updateCombinedWidgetStyle(aNode, aArea, aModifyCloseMenu) {
   let inPanel = (aArea == CustomizableUI.AREA_PANEL);
   let cls = inPanel ? "panel-combined-button" : "toolbarbutton-1";
   let attrs = {class: cls};
-  if (aModifyAutoclose) {
-    attrs.noautoclose = inPanel ? true : null;
+  if (aModifyCloseMenu) {
+    attrs.closemenu = inPanel ? "none" : null;
   }
   for (let i = 0, l = aNode.childNodes.length; i < l; ++i) {
     if (aNode.childNodes[i].localName == "separator")
       continue;
     setAttributes(aNode.childNodes[i], attrs);
   }
 }
 
@@ -306,38 +306,38 @@ const CustomizableWidgets = [{
     id: "zoom-controls",
     type: "custom",
     defaultArea: CustomizableUI.AREA_PANEL,
     onBuild: function(aDocument) {
       const kPanelId = "PanelUI-popup";
       let areaType = CustomizableUI.getAreaType(this.currentArea);
       let inPanel = areaType == CustomizableUI.TYPE_MENU_PANEL;
       let inToolbar = areaType == CustomizableUI.TYPE_TOOLBAR;
-      let noautoclose = inPanel ? "true" : null;
+      let closeMenu = inPanel ? "none" : null;
       let cls = inPanel ? "panel-combined-button" : "toolbarbutton-1";
 
       if (!this.currentArea)
         cls = null;
 
       let buttons = [{
         id: "zoom-out-button",
-        noautoclose: noautoclose,
+        closemenu: closeMenu,
         command: "cmd_fullZoomReduce",
         class: cls,
         label: true,
         tooltiptext: true
       }, {
         id: "zoom-reset-button",
-        noautoclose: noautoclose,
+        closemenu: closeMenu,
         command: "cmd_fullZoomReset",
         class: cls,
         tooltiptext: true
       }, {
         id: "zoom-in-button",
-        noautoclose: noautoclose,
+        closemenu: closeMenu,
         command: "cmd_fullZoomEnlarge",
         class: cls,
         label: true,
         tooltiptext: true
       }];
 
       let node = aDocument.createElementNS(kNSXUL, "toolbaritem");
       node.setAttribute("id", "zoom-controls");
--- a/browser/components/customizableui/src/CustomizeMode.jsm
+++ b/browser/components/customizableui/src/CustomizeMode.jsm
@@ -43,16 +43,18 @@ function CustomizeMode(aWindow) {
   this.document = aWindow.document;
   this.browser = aWindow.gBrowser;
 
   // There are two palettes - there's the palette that can be overlayed with
   // toolbar items in browser.xul. This is invisible, and never seen by the
   // user. Then there's the visible palette, which gets populated and displayed
   // to the user when in customizing mode.
   this.visiblePalette = this.document.getElementById(kPaletteId);
+  this.paletteEmptyNotice = this.document.getElementById("customization-empty");
+  this.paletteSpacer = this.document.getElementById("customization-spacer");
 };
 
 CustomizeMode.prototype = {
   _changed: false,
   _transitioning: false,
   window: null,
   document: null,
   // areas is used to cache the customizable areas when in customization mode.
@@ -223,16 +225,18 @@ CustomizeMode.prototype = {
 
       CustomizableUI.addListener(this);
       window.PanelUI.endBatchUpdate();
       this._customizing = true;
       this._transitioning = false;
 
       // Show the palette now that the transition has finished.
       this.visiblePalette.hidden = false;
+      this.paletteSpacer.hidden = true;
+      this._updateEmptyPaletteNotice();
 
       this._handler.isEnteringCustomizeMode = false;
       this.dispatchToolboxEvent("customizationready");
       if (!this._wantToBeInCustomizeMode) {
         this.exit();
       }
     }.bind(this)).then(null, function(e) {
       ERROR(e);
@@ -268,17 +272,19 @@ CustomizeMode.prototype = {
 
     this._removePanelCustomizationPlaceholders();
 
     let window = this.window;
     let document = this.document;
     let documentElement = document.documentElement;
 
     // Hide the palette before starting the transition for increased perf.
+    this.paletteSpacer.hidden = false;
     this.visiblePalette.hidden = true;
+    this.paletteEmptyNotice.hidden = true;
 
     this._transitioning = true;
 
     Task.spawn(function() {
       yield this.depopulatePalette();
 
       yield this._doTransition(false);
 
@@ -908,19 +914,25 @@ CustomizeMode.prototype = {
         this.visiblePalette.appendChild(this.makePaletteItem(widget, "palette"));
       }
     }
   },
 
   _onUIChange: function() {
     this._changed = true;
     this._updateResetButton();
+    this._updateEmptyPaletteNotice();
     this.dispatchToolboxEvent("customizationchange");
   },
 
+  _updateEmptyPaletteNotice: function() {
+    let paletteItems = this.visiblePalette.getElementsByTagName("toolbarpaletteitem");
+    this.paletteEmptyNotice.hidden = !!paletteItems.length;
+  },
+
   _updateResetButton: function() {
     let btn = this.document.getElementById("customization-reset-button");
     btn.disabled = CustomizableUI.inDefaultState;
   },
 
   handleEvent: function(aEvent) {
     switch(aEvent.type) {
       case "toolbarvisibilitychange":
--- a/browser/components/customizableui/test/browser.ini
+++ b/browser/components/customizableui/test/browser.ini
@@ -32,16 +32,17 @@ skip-if = os == "mac"
 # Because of the specific widths, this test is fragile and has been disabled.
 # NB: it was designed for mac only, but started randomly failing there.
 skip-if = true
 
 [browser_914863_disabled_help_quit_buttons.js]
 [browser_918049_skipintoolbarset_dnd.js]
 [browser_923857_customize_mode_event_wrapping_during_reset.js]
 [browser_927717_customize_drag_empty_toolbar.js]
+[browser_932928_show_notice_when_palette_empty.js]
 
 [browser_934113_menubar_removable.js]
 # Because this test is about the menubar, it can't be run on mac
 skip-if = os == "mac"
 
 [browser_946320_tabs_from_other_computers.js]
 skip-if = os == "linux"
 
new file mode 100644
--- /dev/null
+++ b/browser/components/customizableui/test/browser_932928_show_notice_when_palette_empty.js
@@ -0,0 +1,35 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+// There should be an advert to get more addons when the palette is empty.
+add_task(function() {
+  yield startCustomizing();
+  let visiblePalette = document.getElementById("customization-palette");
+  let emptyPaletteNotice = document.getElementById("customization-empty");
+  is(emptyPaletteNotice.hidden, true, "The empty palette notice should not be shown when there are items in the palette.");
+
+  while (visiblePalette.childElementCount) {
+    gCustomizeMode.addToToolbar(visiblePalette.children[0]);
+  }
+  is(visiblePalette.childElementCount, 0, "There shouldn't be any items remaining in the visible palette.");
+  is(emptyPaletteNotice.hidden, false, "The empty palette notice should be shown when there are no items in the palette.");
+
+  yield endCustomizing();
+  yield startCustomizing();
+  visiblePalette = document.getElementById("customization-palette");
+  emptyPaletteNotice = document.getElementById("customization-empty");
+  is(emptyPaletteNotice.hidden, false,
+     "The empty palette notice should be shown when there are no items in the palette and cust. mode is re-entered.");
+
+  gCustomizeMode.removeFromArea(document.getElementById("wrapper-home-button"));
+  is(emptyPaletteNotice.hidden, true,
+     "The empty palette notice should not be shown when there is at least one item in the palette.");
+});
+
+add_task(function asyncCleanup() {
+  yield endCustomizing();
+  yield resetCustomization();
+});
--- a/browser/devtools/scratchpad/test/browser_scratchpad_wrong_window_focus.js
+++ b/browser/devtools/scratchpad/test/browser_scratchpad_wrong_window_focus.js
@@ -1,13 +1,14 @@
 /* vim:set ts=2 sw=2 sts=2 et: */
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 /* Bug 661762 */
 
+
 function test()
 {
   waitForExplicitFinish();
 
   // To test for this bug we open a Scratchpad window, save its
   // reference and then open another one. This way the first window
   // loses its focus.
   //
@@ -20,32 +21,25 @@ function test()
   // is currently active (it should be the older one).
 
   gBrowser.selectedTab = gBrowser.addTab();
   gBrowser.selectedBrowser.addEventListener("load", function onLoad() {
     gBrowser.selectedBrowser.removeEventListener("load", onLoad, true);
 
     openScratchpad(function () {
       let sw = gScratchpadWindow;
+      let {devtools} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
 
       openScratchpad(function () {
-        function onWebConsoleOpen(subj) {
-          Services.obs.removeObserver(onWebConsoleOpen,
-            "web-console-created");
-          subj.QueryInterface(Ci.nsISupportsString);
-
-          let hud = HUDService.getHudReferenceById(subj.data);
+        let target = devtools.TargetFactory.forTab(gBrowser.selectedTab);
+        gDevTools.showToolbox(target, "webconsole").then((toolbox) => {
+          let hud = toolbox.getCurrentPanel().hud;
           hud.jsterm.clearOutput(true);
-          executeSoon(testFocus.bind(null, sw, hud));
-        }
-
-        Services.obs.
-          addObserver(onWebConsoleOpen, "web-console-created", false);
-
-        HUDService.toggleWebConsole();
+          testFocus(sw, hud);
+        });
       });
     });
   }, true);
 
   content.location = "data:text/html;charset=utf8,<p>test window focus for Scratchpad.";
 }
 
 function testFocus(sw, hud) {
--- a/browser/devtools/webconsole/hudservice.js
+++ b/browser/devtools/webconsole/hudservice.js
@@ -138,34 +138,16 @@ HUD_SERVICE.prototype =
    * @returns Object
    */
   getHudReferenceById: function HS_getHudReferenceById(aId)
   {
     return this.consoles.get(aId);
   },
 
   /**
-   * Toggle the Web Console for the current tab.
-   *
-   * @return object
-   *         A promise for either the opening of the toolbox that holds the Web
-   *         Console, or a Promise for the closing of the toolbox.
-   */
-  toggleWebConsole: function HS_toggleWebConsole()
-  {
-    let window = this.currentContext();
-    let target = devtools.TargetFactory.forTab(window.gBrowser.selectedTab);
-    let toolbox = gDevTools.getToolbox(target);
-
-    return toolbox && toolbox.currentToolId == "webconsole" ?
-        toolbox.destroy() :
-        gDevTools.showToolbox(target, "webconsole");
-  },
-
-  /**
    * Find if there is a Web Console open for the current tab and return the
    * instance.
    * @return object|null
    *         The WebConsole object or null if the active tab has no open Web
    *         Console.
    */
   getOpenWebConsole: function HS_getOpenWebConsole()
   {
@@ -739,17 +721,17 @@ BrowserConsole.prototype = Heritage.exte
 
     return this._bc_destroyer.promise;
   },
 });
 
 const HUDService = new HUD_SERVICE();
 
 (() => {
-  let methods = ["openWebConsole", "openBrowserConsole", "toggleWebConsole",
+  let methods = ["openWebConsole", "openBrowserConsole",
                  "toggleBrowserConsole", "getOpenWebConsole",
                  "getBrowserConsole", "getHudByWindow", "getHudReferenceById"];
   for (let method of methods) {
     exports[method] = HUDService[method].bind(HUDService);
   }
 
   exports.consoles = HUDService.consoles;
   exports.lastFinishedRequest = HUDService.lastFinishedRequest;
--- a/browser/devtools/webconsole/test/browser.ini
+++ b/browser/devtools/webconsole/test/browser.ini
@@ -110,16 +110,17 @@ support-files =
 [browser_bug_865288_repeat_different_objects.js]
 [browser_bug_865871_variables_view_close_on_esc_key.js]
 [browser_bug_869003_inspect_cross_domain_object.js]
 [browser_bug_871156_ctrlw_close_tab.js]
 [browser_cached_messages.js]
 [browser_console.js]
 [browser_console_addonsdk_loader_exception.js]
 [browser_console_clear_on_reload.js]
+[browser_console_click_focus.js]
 [browser_console_consolejsm_output.js]
 [browser_console_dead_objects.js]
 [browser_console_error_source_click.js]
 [browser_console_filters.js]
 [browser_console_iframe_messages.js]
 [browser_console_keyboard_accessibility.js]
 [browser_console_log_inspectable_object.js]
 [browser_console_native_getters.js]
new file mode 100644
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_console_click_focus.js
@@ -0,0 +1,39 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Tests that the input field is focused when the console is opened.
+
+const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-console.html";
+
+function test() {
+  addTab(TEST_URI);
+  browser.addEventListener("DOMContentLoaded", testInputFocus, false);
+}
+
+function testInputFocus() {
+  browser.removeEventListener("DOMContentLoaded", testInputFocus, false);
+
+  openConsole().then((hud) => {
+    let inputNode = hud.jsterm.inputNode;
+    ok(inputNode.getAttribute("focused"), "input node is focused");
+
+    let lostFocus = () => {
+      inputNode.removeEventListener("blur", lostFocus);
+      info("input node lost focus");
+    }
+
+   	inputNode.addEventListener("blur", lostFocus);
+
+   	browser.ownerDocument.getElementById("urlbar").click();
+
+  	ok(!inputNode.getAttribute("focused"), "input node is not focused");
+
+   	hud.outputNode.click();
+
+   	ok(inputNode.getAttribute("focused"), "input node is focused");
+
+      finishTest();
+  });
+}
--- a/browser/devtools/webconsole/webconsole.js
+++ b/browser/devtools/webconsole/webconsole.js
@@ -564,16 +564,29 @@ WebConsoleFrame.prototype = {
     this.jsterm.init();
     this.jsterm.inputNode.focus();
 
     let toolbox = gDevTools.getToolbox(this.owner.target);
     if (toolbox) {
       toolbox.on("webconsole-selected", this._onPanelSelected);
     }
 
+    /*
+     * Focus input line whenever the output area is clicked.
+     * Only focus when the target node (or parent, as in source links) is
+     * not an anchor.
+     */
+    this.outputNode.addEventListener("click", (e) => {
+      if ((e.button == 0) &&
+          (e.target.nodeName.toLowerCase() != "a") &&
+          (e.target.parentNode.nodeName.toLowerCase() != "a")) {
+        this.jsterm.inputNode.focus();
+      }
+    });
+
     // Toggle the timestamp on preference change
     gDevTools.on("pref-changed", this._onToolboxPrefChanged);
     this._onToolboxPrefChanged("pref-changed", {
       pref: PREF_MESSAGE_TIMESTAMP,
       newValue: Services.prefs.getBoolPref(PREF_MESSAGE_TIMESTAMP),
     });
   },
 
--- a/browser/locales/en-US/chrome/browser/browser.dtd
+++ b/browser/locales/en-US/chrome/browser/browser.dtd
@@ -672,16 +672,18 @@ just addresses the organization to follo
 <!ENTITY social.learnMore.accesskey "l">
 <!ENTITY social.closeNotificationItem.label "Not Now">
 
 
 
 <!ENTITY customizeMode.tabTitle "Customize &brandShortName;">
 <!ENTITY customizeMode.menuAndToolbars.label "Menu and toolbars">
 <!ENTITY customizeMode.menuAndToolbars.header "More Tools to Add to the Menu and Toolbar">
+<!ENTITY customizeMode.menuAndToolbars.empty "Want more tools?">
+<!ENTITY customizeMode.menuAndToolbars.emptyLink "Choose from thousands of add-ons">
 <!ENTITY customizeMode.restoreDefaults "Restore Defaults">
 <!ENTITY customizeMode.toolbars "Show / Hide Toolbars">
 
 <!ENTITY social.chatBar.commandkey "c">
 <!ENTITY social.chatBar.label "Focus chats">
 <!ENTITY social.chatBar.accesskey "c">
 
 <!ENTITY social.markpageMenu.accesskey "P">
--- a/browser/metro/base/content/Util.js
+++ b/browser/metro/base/content/Util.js
@@ -78,40 +78,54 @@ let Util = {
   },
 
   isTextInput: function isTextInput(aElement) {
     return ((aElement instanceof Ci.nsIDOMHTMLInputElement &&
              aElement.mozIsTextField(false)) ||
             aElement instanceof Ci.nsIDOMHTMLTextAreaElement);
   },
 
+  /**
+   * Checks whether aElement's content can be edited either if it(or any of its
+   * parents) has "contenteditable" attribute set to "true" or aElement's
+   * ownerDocument is in design mode.
+   */
   isEditableContent: function isEditableContent(aElement) {
-    if (!aElement)
-      return false;
-    if (aElement.isContentEditable || aElement.designMode == "on")
-      return true;
-    return false;
+    return !!aElement && (aElement.isContentEditable ||
+                          this.isOwnerDocumentInDesignMode(aElement));
+
   },
 
   isEditable: function isEditable(aElement) {
-    if (!aElement)
+    if (!aElement) {
       return false;
-    if (this.isTextInput(aElement) || this.isEditableContent(aElement))
+    }
+
+    if (this.isTextInput(aElement) || this.isEditableContent(aElement)) {
       return true;
+    }
 
     // If a body element is editable and the body is the child of an
     // iframe or div we can assume this is an advanced HTML editor
     if ((aElement instanceof Ci.nsIDOMHTMLIFrameElement ||
          aElement instanceof Ci.nsIDOMHTMLDivElement) &&
         aElement.contentDocument &&
         this.isEditableContent(aElement.contentDocument.body)) {
       return true;
     }
 
-    return aElement.ownerDocument && aElement.ownerDocument.designMode == "on";
+    return false;
+  },
+
+  /**
+   * Checks whether aElement's owner document has design mode turned on.
+   */
+  isOwnerDocumentInDesignMode: function(aElement) {
+    return !!aElement && !!aElement.ownerDocument &&
+           aElement.ownerDocument.designMode == "on";
   },
 
   isMultilineInput: function isMultilineInput(aElement) {
     return (aElement instanceof Ci.nsIDOMHTMLTextAreaElement);
   },
 
   isLink: function isLink(aElement) {
     return ((aElement instanceof Ci.nsIDOMHTMLAnchorElement && aElement.href) ||
--- a/browser/metro/base/content/contenthandlers/ContextMenuHandler.js
+++ b/browser/metro/base/content/contenthandlers/ContextMenuHandler.js
@@ -100,33 +100,35 @@ var ContextMenuHandler = {
   /******************************************************
    * ContextCommand handlers
    */
 
   _onSelectAll: function _onSelectAll() {
     if (Util.isTextInput(this._target)) {
       // select all text in the input control
       this._target.select();
+    } else if (Util.isEditableContent(this._target)) {
+      this._target.ownerDocument.execCommand("selectAll", false);
     } else {
       // select the entire document
       content.getSelection().selectAllChildren(content.document);
     }
     this.reset();
   },
 
   _onPaste: function _onPaste() {
     // paste text if this is an input control
     if (Util.isTextInput(this._target)) {
       let edit = this._target.QueryInterface(Ci.nsIDOMNSEditableElement);
       if (edit) {
         edit.editor.paste(Ci.nsIClipboard.kGlobalClipboard);
       } else {
         Util.dumpLn("error: target element does not support nsIDOMNSEditableElement");
       }
-    } else if (this._target.isContentEditable) {
+    } else if (Util.isEditableContent(this._target)) {
       try {
         this._target.ownerDocument.execCommand("paste",
                                                false,
                                                Ci.nsIClipboard.kGlobalClipboard);
       } catch (ex) {
         dump("ContextMenuHandler: exception pasting into contentEditable: " + ex.message + "\n");
       }
     }
@@ -140,17 +142,17 @@ var ContextMenuHandler = {
   _onCut: function _onCut() {
     if (Util.isTextInput(this._target)) {
       let edit = this._target.QueryInterface(Ci.nsIDOMNSEditableElement);
       if (edit) {
         edit.editor.cut();
       } else {
         Util.dumpLn("error: target element does not support nsIDOMNSEditableElement");
       }
-    } else if (this._target.isContentEditable) {
+    } else if (Util.isEditableContent(this._target)) {
       try {
         this._target.ownerDocument.execCommand("cut", false);
       } catch (ex) {
         dump("ContextMenuHandler: exception cutting from contentEditable: " + ex.message + "\n");
       }
     }
     this.reset();
   },
@@ -254,17 +256,19 @@ var ContextMenuHandler = {
           linkUrl = state.linkURL;
           state.linkTitle = popupNode.textContent || popupNode.title;
           state.linkProtocol = this._getProtocol(this._getURI(state.linkURL));
           // mark as text so we can pickup on selection below
           isText = true;
           break;
         }
         // is the target contentEditable (not just inheriting contentEditable)
-        else if (elem.contentEditable == "true") {
+        // or the entire document in designer mode.
+        else if (elem.contentEditable == "true" ||
+                 Util.isOwnerDocumentInDesignMode(elem)) {
           this._target = elem;
           isEditableText = true;
           isText = true;
           uniqueStateTypes.add("input-text");
 
           if (elem.textContent.length) {
             uniqueStateTypes.add("selectable");
           } else {
--- a/browser/metro/base/tests/mochitest/browser_context_menu_tests.js
+++ b/browser/metro/base/tests/mochitest/browser_context_menu_tests.js
@@ -704,12 +704,90 @@ gTests.push({
 
 gTests.push({
   desc: "bug 856264 - touch - context menu should reopen on other links",
   setUp: reopenSetUp,
   tearDown: reopenTearDown,
   run: getReopenTest(sendContextMenuClickToElement, sendTap)
 });
 
+gTests.push({
+  desc: "Bug 947505 - Right-click in a designMode document should display a " +
+        "context menu",
+  run: function test() {
+    info(chromeRoot + "browser_context_menu_tests_02.html");
+    yield addTab(chromeRoot + "browser_context_menu_tests_02.html");
+
+    purgeEventQueue();
+    emptyClipboard();
+    ContextUI.dismiss();
+
+    yield waitForCondition(() => !ContextUI.navbarVisible);
+
+    let tabWindow = Browser.selectedTab.browser.contentWindow;
+    let testSpan = tabWindow.document.getElementById("text1");
+
+    // Case #1: Document isn't in design mode and nothing is selected.
+    tabWindow.document.designMode = "off";
+
+    // Simulate right mouse click to reproduce the same step as noted in the
+    // appropriate bug. It's valid for non-touch case only.
+    synthesizeNativeMouseRDown(Browser.selectedTab.browser, 10, 10);
+    synthesizeNativeMouseRUp(Browser.selectedTab.browser, 10, 10);
+
+    yield waitForCondition(() => ContextUI.navbarVisible);
+
+    ok(ContextUI.navbarVisible, "Navbar is visible on context menu action.");
+    ok(ContextUI.tabbarVisible, "Tabbar is visible on context menu action.");
+
+    ContextUI.dismiss();
+    yield waitForCondition(() => !ContextUI.navbarVisible);
+
+    // Case #2: Document isn't in design mode and text is selected.
+    tabWindow.getSelection().selectAllChildren(testSpan);
+
+    let promise = waitForEvent(tabWindow.document, "popupshown");
+    sendContextMenuClickToSelection(tabWindow);
+    yield promise;
+
+    checkContextUIMenuItemVisibility(["context-copy", "context-search"]);
+
+    promise = waitForEvent(document, "popuphidden");
+    ContextMenuUI.hide();
+    yield promise;
+
+    // Case #3: Document is in design mode and nothing is selected.
+    tabWindow.document.designMode = "on";
+    tabWindow.getSelection().removeAllRanges();
+
+    promise = waitForEvent(tabWindow.document, "popupshown");
+    sendContextMenuClickToElement(tabWindow, testSpan);
+    yield promise;
+
+    checkContextUIMenuItemVisibility(["context-select-all", "context-select"]);
+
+    promise = waitForEvent(document, "popuphidden");
+    ContextMenuUI.hide();
+    yield promise;
+
+    // Case #4: Document is in design mode and text is selected.
+    tabWindow.getSelection().selectAllChildren(testSpan);
+
+    promise = waitForEvent(tabWindow.document, "popupshown");
+    sendContextMenuClickToSelection(tabWindow);
+    yield promise;
+
+    checkContextUIMenuItemVisibility(["context-cut", "context-copy",
+                                      "context-select-all", "context-select",
+                                      "context-search"]);
+
+    promise = waitForEvent(document, "popuphidden");
+    ContextMenuUI.hide();
+    yield promise;
+
+    Browser.closeTab(Browser.selectedTab, { forceClose: true });
+  }
+});
+
 function test() {
   setDevPixelEqualToPx();
   runTests();
 }
--- a/browser/themes/osx/browser.css
+++ b/browser/themes/osx/browser.css
@@ -3955,17 +3955,17 @@ window > chatbox {
   border-bottom-left-radius: @toolbarbuttonCornerRadius@;
   border-bottom-right-radius: @toolbarbuttonCornerRadius@;
 }
 
 /* Customization mode */
 
 %include ../shared/customizableui/customizeMode.inc.css
 
-#main-window[customizing] #titlebar {
+#main-window[customize-entered] #titlebar {
   padding-top: 0;
 }
 
 #main-window:-moz-any([customize-entering],[customize-entered]) #tab-view-deck {
   padding: 0 2em 2em;
 }
 
 #main-window[customize-entered] #tab-view-deck {
--- a/mobile/android/base/FormAssistPopup.java
+++ b/mobile/android/base/FormAssistPopup.java
@@ -46,17 +46,22 @@ public class FormAssistPopup extends Rel
     private TextView mValidationMessageText;
     private ImageView mValidationMessageArrow;
     private ImageView mValidationMessageArrowInverted;
 
     private double mX;
     private double mY;
     private double mW;
     private double mH;
-    private boolean mIsAutoComplete = true;
+
+    private enum PopupType {
+        AUTOCOMPLETE,
+        VALIDATIONMESSAGE;
+    }
+    private PopupType mPopupType;
 
     private static int sAutoCompleteMinWidth = 0;
     private static int sAutoCompleteRowHeight = 0;
     private static int sValidationMessageHeight = 0;
     private static int sValidationTextMarginTop = 0;
     private static RelativeLayout.LayoutParams sValidationTextLayoutNormal;
     private static RelativeLayout.LayoutParams sValidationTextLayoutInverted;
 
@@ -203,17 +208,18 @@ public class FormAssistPopup extends Rel
             mW = rect.getDouble("w");
             mH = rect.getDouble("h");
         } catch (JSONException e) {
             // Bail if we can't get the correct dimensions for the popup.
             Log.e(LOGTAG, "Error getting FormAssistPopup dimensions", e);
             return false;
         }
 
-        mIsAutoComplete = isAutoComplete;
+        mPopupType = (isAutoComplete ?
+                      PopupType.AUTOCOMPLETE : PopupType.VALIDATIONMESSAGE);
         return true;
     }
 
     private void positionAndShowPopup() {
         positionAndShowPopup(GeckoAppShell.getLayerView().getViewportMetrics());
     }
 
     private void positionAndShowPopup(ImmutableViewportMetrics aMetrics) {
@@ -222,19 +228,19 @@ public class FormAssistPopup extends Rel
         // Don't show the form assist popup when using fullscreen VKB
         InputMethodManager imm =
                 (InputMethodManager) GeckoAppShell.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
         if (imm.isFullscreenMode())
             return;
 
         // Hide/show the appropriate popup contents
         if (mAutoCompleteList != null)
-            mAutoCompleteList.setVisibility(mIsAutoComplete ? VISIBLE : GONE);
+            mAutoCompleteList.setVisibility((mPopupType == PopupType.AUTOCOMPLETE) ? VISIBLE : GONE);
         if (mValidationMessage != null)
-            mValidationMessage.setVisibility(mIsAutoComplete ? GONE : VISIBLE);
+            mValidationMessage.setVisibility((mPopupType == PopupType.AUTOCOMPLETE) ? GONE : VISIBLE);
 
         if (sAutoCompleteMinWidth == 0) {
             Resources res = mContext.getResources();
             sAutoCompleteMinWidth = (int) (res.getDimension(R.dimen.autocomplete_min_width));
             sAutoCompleteRowHeight = (int) (res.getDimension(R.dimen.autocomplete_row_height));
             sValidationMessageHeight = (int) (res.getDimension(R.dimen.validation_message_height));
         }
 
@@ -250,38 +256,38 @@ public class FormAssistPopup extends Rel
 
         int popupWidth = RelativeLayout.LayoutParams.FILL_PARENT;
         int popupLeft = left < 0 ? 0 : left;
 
         FloatSize viewport = aMetrics.getSize();
 
         // For autocomplete suggestions, if the input is smaller than the screen-width,
         // shrink the popup's width. Otherwise, keep it as FILL_PARENT.
-        if (mIsAutoComplete && (left + width) < viewport.width) {
+        if ((mPopupType == PopupType.AUTOCOMPLETE) && (left + width) < viewport.width) {
             popupWidth = left < 0 ? left + width : width;
 
             // Ensure the popup has a minimum width.
             if (popupWidth < sAutoCompleteMinWidth) {
                 popupWidth = sAutoCompleteMinWidth;
 
                 // Move the popup to the left if there isn't enough room for it.
                 if ((popupLeft + popupWidth) > viewport.width)
                     popupLeft = (int) (viewport.width - popupWidth);
             }
         }
 
         int popupHeight;
-        if (mIsAutoComplete)
+        if (mPopupType == PopupType.AUTOCOMPLETE)
             popupHeight = sAutoCompleteRowHeight * mAutoCompleteList.getAdapter().getCount();
         else
             popupHeight = sValidationMessageHeight;
 
         int popupTop = top + height;
 
-        if (!mIsAutoComplete) {
+        if (mPopupType == PopupType.VALIDATIONMESSAGE) {
             mValidationMessageText.setLayoutParams(sValidationTextLayoutNormal);
             mValidationMessageArrow.setVisibility(VISIBLE);
             mValidationMessageArrowInverted.setVisibility(GONE);
         }
 
         // If the popup doesn't fit below the input box, shrink its height, or
         // see if we can place it above the input instead.
         if ((popupTop + popupHeight) > viewport.height) {
@@ -294,17 +300,17 @@ public class FormAssistPopup extends Rel
                     // No shrinking needed to fit on top.
                     popupTop = (top - popupHeight);
                 } else {
                     // Shrink to available space on top.
                     popupTop = 0;
                     popupHeight = top;
                 }
 
-                if (!mIsAutoComplete) {
+                if (mPopupType == PopupType.VALIDATIONMESSAGE) {
                     mValidationMessageText.setLayoutParams(sValidationTextLayoutInverted);
                     mValidationMessageArrow.setVisibility(GONE);
                     mValidationMessageArrowInverted.setVisibility(VISIBLE);
                 }
            }
         }
 
         RelativeLayout.LayoutParams layoutParams =