Merge fx-team to m-c
authorWes Kocher <wkocher@mozilla.com>
Tue, 21 Jan 2014 17:27:53 -0800
changeset 180543 8f4ecbf938cd15199f017b5b21ca3c3e167802a3
parent 180534 51d581fdf7832f891296ba5dce043aa8ebf3a55f (current diff)
parent 180542 de1374bf0c6edbc1e182078d15d2f99603f39dd6 (diff)
child 180549 93cec25bc798229509902e35c11cfea7503953e4
child 180577 9ab3440df7b8c2639ddbcbc7af0e071553ee2093
child 180611 1f03e8d6f26286f2373f5ddc62aab8ba00491545
child 180624 2cf3d715f52f0699a281840ad8abea45a12dc28e
push id3343
push userffxbld
push dateMon, 17 Mar 2014 21:55:32 +0000
treeherdermozilla-beta@2f7d3415f79f [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 =