Merge fx-team to m-c
authorWes Kocher <wkocher@mozilla.com>
Tue, 12 Nov 2013 18:42:56 -0800
changeset 154662 7b014f0f3b031ad57e2995d1f9ee04095c82bda5
parent 154647 bb502bb5ed5fe7d48f1ace7cfff35bf71c72e7fd (current diff)
parent 154661 5fa603642231124db0336e2606a012cab554af6b (diff)
child 154783 319c3d822ae4707f699b71bc3914170d251a1516
push id25652
push userkwierso@gmail.com
push dateWed, 13 Nov 2013 02:43:23 +0000
treeherdermozilla-central@7b014f0f3b03 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone28.0a1
first release with
nightly linux32
7b014f0f3b03 / 28.0a1 / 20131113030205 / files
nightly linux64
7b014f0f3b03 / 28.0a1 / 20131113030205 / files
nightly mac
7b014f0f3b03 / 28.0a1 / 20131113030205 / files
nightly win32
7b014f0f3b03 / 28.0a1 / 20131113030205 / files
nightly win64
7b014f0f3b03 / 28.0a1 / 20131113030205 / 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/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1205,16 +1205,21 @@ pref("devtools.browserconsole.filter.sec
 // Text size in the Web Console. Use 0 for the system default size.
 pref("devtools.webconsole.fontSize", 0);
 
 // Persistent logging: |true| if you want the Web Console to keep all of the
 // logged messages after reloading the page, |false| if you want the output to
 // be cleared each time page navigation happens.
 pref("devtools.webconsole.persistlog", false);
 
+// Web Console timestamp: |true| if you want the logs and instructions
+// in the Web Console to display a timestamp, or |false| to not display
+// any timestamps.
+pref("devtools.webconsole.timestampMessages", false);
+
 // The number of lines that are displayed in the web console for the Net,
 // CSS, JS and Web Developer categories.
 pref("devtools.hud.loglimit.network", 200);
 pref("devtools.hud.loglimit.cssparser", 200);
 pref("devtools.hud.loglimit.exception", 200);
 pref("devtools.hud.loglimit.console", 200);
 
 // The developer tools editor configuration:
--- a/browser/base/content/browser-plugins.js
+++ b/browser/base/content/browser-plugins.js
@@ -726,20 +726,16 @@ var gPluginHandler = {
     }
 
     let browser = aNotification.browser;
     let contentWindow = browser.contentWindow;
     if (aNewState != "continue") {
       let principal = contentWindow.document.nodePrincipal;
       Services.perms.addFromPrincipal(principal, aPluginInfo.permissionString,
                                       permission, expireType, expireTime);
-
-      if (aNewState == "block") {
-        return;
-      }
     }
 
     // Manually activate the plugins that would have been automatically
     // activated.
     let cwu = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
                            .getInterface(Ci.nsIDOMWindowUtils);
     let plugins = cwu.plugins;
     let pluginHost = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost);
@@ -747,29 +743,33 @@ var gPluginHandler = {
     let pluginFound = false;
     for (let plugin of plugins) {
       plugin.QueryInterface(Ci.nsIObjectLoadingContent);
       if (!gPluginHandler.isKnownPlugin(plugin)) {
         continue;
       }
       if (aPluginInfo.permissionString == pluginHost.getPermissionStringForType(plugin.actualType)) {
         pluginFound = true;
-        if (gPluginHandler.canActivatePlugin(plugin)) {
-          let overlay = this.getPluginUI(plugin, "main");
-          if (overlay) {
-            overlay.removeEventListener("click", gPluginHandler._overlayClickListener, true);
+        if (aNewState == "block") {
+          plugin.reload(true);
+        } else {
+          if (gPluginHandler.canActivatePlugin(plugin)) {
+            let overlay = this.getPluginUI(plugin, "main");
+            if (overlay) {
+              overlay.removeEventListener("click", gPluginHandler._overlayClickListener, true);
+            }
+            plugin.playPlugin();
           }
-          plugin.playPlugin();
         }
       }
     }
 
     // If there are no instances of the plugin on the page any more, what the
     // user probably needs is for us to allow and then refresh.
-    if (!pluginFound) {
+    if (aNewState != "block" && !pluginFound) {
       browser.reload();
     }
   },
 
   _showClickToPlayNotification: function PH_showClickToPlayNotification(aBrowser, aPlugin, aShowNow) {
     let notification = PopupNotifications.getNotification("click-to-play-plugins", aBrowser);
     let plugins = [];
 
--- a/browser/base/content/browser-social.js
+++ b/browser/base/content/browser-social.js
@@ -1509,16 +1509,17 @@ SocialStatus = {
         notificationFrameId, /* frame name */
         panel, /* parent */
         {
           "type": "content",
           "mozbrowser": "true",
           "class": "social-panel-frame",
           "id": notificationFrameId,
           "tooltip": "aHTMLTooltip",
+          "context": "contentAreaContextMenu",
 
           // work around bug 793057 - by making the panel roughly the final size
           // we are more likely to have the anchor in the correct position.
           "style": "width: " + PANEL_MIN_WIDTH + "px;",
 
           "origin": provider.origin,
           "src": provider.statusURL
         }
--- a/browser/base/content/test/general/browser_CTP_data_urls.js
+++ b/browser/base/content/test/general/browser_CTP_data_urls.js
@@ -3,62 +3,16 @@ const gTestRoot = rootDir;
 const gHttpTestRoot = rootDir.replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
 
 var gTestBrowser = null;
 var gNextTest = null;
 var gPluginHost = Components.classes["@mozilla.org/plugin/host;1"].getService(Components.interfaces.nsIPluginHost);
 
 Components.utils.import("resource://gre/modules/Services.jsm");
 
-// This listens for the next opened tab and checks it is of the right url.
-// opencallback is called when the new tab is fully loaded
-// closecallback is called when the tab is closed
-function TabOpenListener(url, opencallback, closecallback) {
-  this.url = url;
-  this.opencallback = opencallback;
-  this.closecallback = closecallback;
-
-  gBrowser.tabContainer.addEventListener("TabOpen", this, false);
-}
-
-TabOpenListener.prototype = {
-  url: null,
-  opencallback: null,
-  closecallback: null,
-  tab: null,
-  browser: null,
-
-  handleEvent: function(event) {
-    if (event.type == "TabOpen") {
-      gBrowser.tabContainer.removeEventListener("TabOpen", this, false);
-      this.tab = event.originalTarget;
-      this.browser = this.tab.linkedBrowser;
-      gBrowser.addEventListener("pageshow", this, false);
-    } else if (event.type == "pageshow") {
-      if (event.target.location.href != this.url)
-        return;
-      gBrowser.removeEventListener("pageshow", this, false);
-      this.tab.addEventListener("TabClose", this, false);
-      var url = this.browser.contentDocument.location.href;
-      is(url, this.url, "Should have opened the correct tab");
-      this.opencallback(this.tab, this.browser.contentWindow);
-    } else if (event.type == "TabClose") {
-      if (event.originalTarget != this.tab)
-        return;
-      this.tab.removeEventListener("TabClose", this, false);
-      this.opencallback = null;
-      this.tab = null;
-      this.browser = null;
-      // Let the window close complete
-      executeSoon(this.closecallback);
-      this.closecallback = null;
-    }
-  }
-};
-
 function test() {
   waitForExplicitFinish();
   registerCleanupFunction(function() {
     clearAllPluginPermissions();
     Services.prefs.clearUserPref("extensions.blocklist.suppressUI");
   });
   Services.prefs.setBoolPref("extensions.blocklist.suppressUI", true);
 
--- a/browser/base/content/test/general/browser_CTP_nonplugins.js
+++ b/browser/base/content/test/general/browser_CTP_nonplugins.js
@@ -4,62 +4,16 @@ const gHttpTestRoot = rootDir.replace("c
 
 var gTestBrowser = null;
 var gNextTest = null;
 var gPluginHost = Components.classes["@mozilla.org/plugin/host;1"].getService(Components.interfaces.nsIPluginHost);
 var gRunNextTestAfterPluginRemoved = false;
 
 Components.utils.import("resource://gre/modules/Services.jsm");
 
-// This listens for the next opened tab and checks it is of the right url.
-// opencallback is called when the new tab is fully loaded
-// closecallback is called when the tab is closed
-function TabOpenListener(url, opencallback, closecallback) {
-  this.url = url;
-  this.opencallback = opencallback;
-  this.closecallback = closecallback;
-
-  gBrowser.tabContainer.addEventListener("TabOpen", this, false);
-}
-
-TabOpenListener.prototype = {
-  url: null,
-  opencallback: null,
-  closecallback: null,
-  tab: null,
-  browser: null,
-
-  handleEvent: function(event) {
-    if (event.type == "TabOpen") {
-      gBrowser.tabContainer.removeEventListener("TabOpen", this, false);
-      this.tab = event.originalTarget;
-      this.browser = this.tab.linkedBrowser;
-      gBrowser.addEventListener("pageshow", this, false);
-    } else if (event.type == "pageshow") {
-      if (event.target.location.href != this.url)
-        return;
-      gBrowser.removeEventListener("pageshow", this, false);
-      this.tab.addEventListener("TabClose", this, false);
-      var url = this.browser.contentDocument.location.href;
-      is(url, this.url, "Should have opened the correct tab");
-      this.opencallback(this.tab, this.browser.contentWindow);
-    } else if (event.type == "TabClose") {
-      if (event.originalTarget != this.tab)
-        return;
-      this.tab.removeEventListener("TabClose", this, false);
-      this.opencallback = null;
-      this.tab = null;
-      this.browser = null;
-      // Let the window close complete
-      executeSoon(this.closecallback);
-      this.closecallback = null;
-    }
-  }
-};
-
 function test() {
   waitForExplicitFinish();
   registerCleanupFunction(function() {
     clearAllPluginPermissions();
     Services.prefs.clearUserPref("extensions.blocklist.suppressUI");
     gTestBrowser.removeEventListener("PluginRemoved", handlePluginRemoved, true, true);
   });
   Services.prefs.setBoolPref("extensions.blocklist.suppressUI", true);
--- a/browser/base/content/test/general/browser_CTP_resize.js
+++ b/browser/base/content/test/general/browser_CTP_resize.js
@@ -3,62 +3,16 @@ const gTestRoot = rootDir;
 const gHttpTestRoot = rootDir.replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
 
 var gTestBrowser = null;
 var gNextTest = null;
 var gPluginHost = Components.classes["@mozilla.org/plugin/host;1"].getService(Components.interfaces.nsIPluginHost);
 
 Components.utils.import("resource://gre/modules/Services.jsm");
 
-// This listens for the next opened tab and checks it is of the right url.
-// opencallback is called when the new tab is fully loaded
-// closecallback is called when the tab is closed
-function TabOpenListener(url, opencallback, closecallback) {
-  this.url = url;
-  this.opencallback = opencallback;
-  this.closecallback = closecallback;
-
-  gBrowser.tabContainer.addEventListener("TabOpen", this, false);
-}
-
-TabOpenListener.prototype = {
-  url: null,
-  opencallback: null,
-  closecallback: null,
-  tab: null,
-  browser: null,
-
-  handleEvent: function(event) {
-    if (event.type == "TabOpen") {
-      gBrowser.tabContainer.removeEventListener("TabOpen", this, false);
-      this.tab = event.originalTarget;
-      this.browser = this.tab.linkedBrowser;
-      gBrowser.addEventListener("pageshow", this, false);
-    } else if (event.type == "pageshow") {
-      if (event.target.location.href != this.url)
-        return;
-      gBrowser.removeEventListener("pageshow", this, false);
-      this.tab.addEventListener("TabClose", this, false);
-      var url = this.browser.contentDocument.location.href;
-      is(url, this.url, "Should have opened the correct tab");
-      this.opencallback(this.tab, this.browser.contentWindow);
-    } else if (event.type == "TabClose") {
-      if (event.originalTarget != this.tab)
-        return;
-      this.tab.removeEventListener("TabClose", this, false);
-      this.opencallback = null;
-      this.tab = null;
-      this.browser = null;
-      // Let the window close complete
-      executeSoon(this.closecallback);
-      this.closecallback = null;
-    }
-  }
-};
-
 function test() {
   waitForExplicitFinish();
   registerCleanupFunction(function() {
     clearAllPluginPermissions();
     Services.prefs.clearUserPref("extensions.blocklist.suppressUI");
   });
   Services.prefs.setBoolPref("extensions.blocklist.suppressUI", true);
 
--- a/browser/devtools/framework/toolbox-options.xul
+++ b/browser/devtools/framework/toolbox-options.xul
@@ -49,16 +49,20 @@
             </menulist>
           </hbox>
         </vbox>
         <label value="&options.webconsole.label;"/>
         <vbox id="webconsole-options" class="options-groupbox">
           <checkbox label="&options.enablePersistentLogging.label;"
                     tooltiptext="&options.enablePersistentLogging.tooltip;"
                     data-pref="devtools.webconsole.persistlog"/>
+          <checkbox id="webconsole-timestamp-messages"
+                    label="&options.timestampMessages.label;"
+                    tooltiptext="&options.timestampMessages.tooltip;"
+                    data-pref="devtools.webconsole.timestampMessages"/>
         </vbox>
         <label value="&options.profiler.label;"/>
         <vbox id="profiler-options" class="options-groupbox">
           <checkbox label="&options.showPlatformData.label;"
                     tooltiptext="&options.showPlatformData.tooltip;"
                     data-pref="devtools.profiler.ui.show-platform-data"/>
         </vbox>
         <label value="&options.context.advancedSettings;"/>
--- a/browser/devtools/webconsole/test/browser.ini
+++ b/browser/devtools/webconsole/test/browser.ini
@@ -234,8 +234,9 @@ skip-if = os == "linux"
 [browser_webconsole_notifications.js]
 [browser_webconsole_output_copy_newlines.js]
 [browser_webconsole_output_order.js]
 [browser_webconsole_property_provider.js]
 [browser_webconsole_scratchpad_panel_link.js]
 [browser_webconsole_view_source.js]
 [browser_webconsole_reflow.js]
 [browser_webconsole_log_file_filter.js]
+[browser_webconsole_expandable_timestamps.js]
--- a/browser/devtools/webconsole/test/browser_webconsole_bug_601667_filter_buttons.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_601667_filter_buttons.js
@@ -17,16 +17,23 @@ function testFilterButtons(aHud) {
   hud = aHud;
   hudId = hud.hudId;
   hudBox = hud.ui.rootElement;
 
   testMenuFilterButton("net");
   testMenuFilterButton("css");
   testMenuFilterButton("js");
   testMenuFilterButton("logging");
+  testMenuFilterButton("security");
+
+  testIsolateFilterButton("net");
+  testIsolateFilterButton("css");
+  testIsolateFilterButton("js");
+  testIsolateFilterButton("logging");
+  testIsolateFilterButton("security");
 
   finishTest();
 }
 
 function testMenuFilterButton(aCategory) {
   let selector = ".webconsole-filter-button[category=\"" + aCategory + "\"]";
   let button = hudBox.querySelector(selector);
   ok(button, "we have the \"" + aCategory + "\" button");
@@ -67,25 +74,17 @@ function testMenuFilterButton(aCategory)
   ok(!isChecked(firstMenuItem), "the first menu item for category " +
      aCategory + " is no longer checked after clicking it");
   ok(!hud.ui.filterPrefs[prefKey], prefKey + " messages are " +
      "turned off after clicking the appropriate menu item");
   ok(isChecked(button), "the button for category " + aCategory + " is still " +
      "checked after turning off its first menu item");
 
   // Turn all the filters off by clicking the main part of the button.
-  let anonymousNodes = hud.ui.document.getAnonymousNodes(button);
-  let subbutton;
-  for (let i = 0; i < anonymousNodes.length; i++) {
-    let node = anonymousNodes[i];
-    if (node.classList.contains("toolbarbutton-menubutton-button")) {
-      subbutton = node;
-      break;
-    }
-  }
+  let subbutton = getMainButton(button);
   ok(subbutton, "we have the subbutton for category " + aCategory);
 
   clickButton(subbutton);
   ok(!isChecked(button), "the button for category " + aCategory + " is " +
      "no longer checked after clicking its main part");
 
   menuItem = firstMenuItem;
   while (menuItem) {
@@ -124,20 +123,91 @@ function testMenuFilterButton(aCategory)
 
   ok(!isChecked(button), "the button for category " + aCategory + " is " +
      "unchecked after unchecking all its filters");
 
   // Turn all the filters on again by clicking the button.
   clickButton(subbutton);
 }
 
+function testIsolateFilterButton(aCategory) {
+  let selector = ".webconsole-filter-button[category=\"" + aCategory + "\"]";
+  let targetButton = hudBox.querySelector(selector);
+  ok(targetButton, "we have the \"" + aCategory + "\" button");
+
+  // Get the main part of the filter button.
+  let subbutton = getMainButton(targetButton);
+  ok(subbutton, "we have the subbutton for category " + aCategory);
+
+  // Turn on all the filters by alt clicking the main part of the button.
+  altClickButton(subbutton);
+  ok(isChecked(targetButton), "the button for category " + aCategory +
+     " is checked after isolating for filter");
+
+  // Check if all the filters for the target button are on.
+  let menuItems = targetButton.querySelectorAll("menuitem");
+  Array.forEach(menuItems, (item) => {
+    let prefKey = item.getAttribute("prefKey");
+    ok(isChecked(item), "menu item " + prefKey + " for category " +
+      aCategory + " is checked after isolating for " + aCategory);
+    ok(hud.ui.filterPrefs[prefKey], prefKey + " messages are " +
+      "turned on after isolating for " + aCategory);
+  });
+
+  // Ensure all other filter buttons are toggled off and their
+  // associated filters are turned off
+  let buttons = hudBox.querySelectorAll(".webconsole-filter-button[category]");
+  Array.forEach(buttons, (filterButton) => {
+    if (filterButton !== targetButton) {
+      let category = filterButton.getAttribute("category");
+      ok(!isChecked(filterButton), "the button for category " +
+        category + " is unchecked after isolating for " + aCategory);
+
+      menuItems = filterButton.querySelectorAll("menuitem");
+      Array.forEach(menuItems, (item) => {
+        let prefKey = item.getAttribute("prefKey");
+        ok(!isChecked(item), "menu item " + prefKey + " for category " +
+          aCategory + " is unchecked after isolating for " + aCategory);
+        ok(!hud.ui.filterPrefs[prefKey], prefKey + " messages are " +
+          "turned off after isolating for " + aCategory);
+      });
+
+      // Turn all the filters on again by clicking the button.
+      let mainButton = getMainButton(filterButton);
+      clickButton(mainButton);
+    }
+  });
+}
+
+/**
+ * Return the main part of the target filter button.
+ */
+function getMainButton(aTargetButton) {
+  let anonymousNodes = hud.ui.document.getAnonymousNodes(aTargetButton);
+  let subbutton;
+
+  for (let i = 0; i < anonymousNodes.length; i++) {
+    let node = anonymousNodes[i];
+    if (node.classList.contains("toolbarbutton-menubutton-button")) {
+      subbutton = node;
+      break;
+    }
+  }
+
+  return subbutton;
+}
+
 function clickButton(aNode) {
   EventUtils.sendMouseEvent({ type: "click" }, aNode);
 }
 
+function altClickButton(aNode) {
+  EventUtils.sendMouseEvent({ type: "click", altKey: true }, aNode);
+}
+
 function chooseMenuItem(aNode) {
   let event = document.createEvent("XULCommandEvent");
   event.initCommandEvent("command", true, true, window, 0, false, false, false,
                          false, null);
   aNode.dispatchEvent(event);
 }
 
 function isChecked(aNode) {
new file mode 100644
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_expandable_timestamps.js
@@ -0,0 +1,57 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test for the message timestamps option: check if the preference toggles the
+// display of messages in the console output. See bug 722267.
+
+function test()
+{
+  const PREF_MESSAGE_TIMESTAMP = "devtools.webconsole.timestampMessages";
+  let hud;
+
+  registerCleanupFunction(() => {
+    Services.prefs.clearUserPref(PREF_MESSAGE_TIMESTAMP);
+  });
+
+  addTab("data:text/html;charset=utf-8,Web Console test for bug 722267 - " +
+         "preference for toggling timestamps in messages");
+
+  browser.addEventListener("load", function tabLoad() {
+    browser.removeEventListener("load", tabLoad, true);
+    openConsole(null, consoleOpened);
+  }, true);
+
+  function consoleOpened(aHud)
+  {
+    hud = aHud;
+
+    info("console opened");
+    let prefValue = Services.prefs.getBoolPref(PREF_MESSAGE_TIMESTAMP);
+    ok(!prefValue, "messages have no timestamp by default (pref check)");
+    ok(hud.outputNode.classList.contains("hideTimestamps"),
+       "messages have no timestamp (class name check)");
+
+    let toolbox = gDevTools.getToolbox(hud.target);
+    toolbox.selectTool("options").then(onOptionsPanelSelected);
+  }
+
+  function onOptionsPanelSelected(panel)
+  {
+    info("options panel opened");
+
+    gDevTools.once("pref-changed", onPrefChanged);
+
+    let checkbox = panel.panelDoc.getElementById("webconsole-timestamp-messages");
+    checkbox.click();
+  }
+
+  function onPrefChanged()
+  {
+    info("pref changed");
+    let prefValue = Services.prefs.getBoolPref(PREF_MESSAGE_TIMESTAMP);
+    ok(prefValue, "messages have timestamps (pref check)");
+    ok(!hud.outputNode.classList.contains("hideTimestamps"),
+       "messages have timestamps (class name check)");
+    finishTest();
+  }
+}
--- a/browser/devtools/webconsole/webconsole.js
+++ b/browser/devtools/webconsole/webconsole.js
@@ -166,16 +166,17 @@ const FILTER_PREFS_PREFIX = "devtools.we
 // The minimum font size.
 const MIN_FONT_SIZE = 10;
 
 // The maximum length of strings to be displayed by the Web Console.
 const MAX_LONG_STRING_LENGTH = 200000;
 
 const PREF_CONNECTION_TIMEOUT = "devtools.debugger.remote-timeout";
 const PREF_PERSISTLOG = "devtools.webconsole.persistlog";
+const PREF_MESSAGE_TIMESTAMP = "devtools.webconsole.timestampMessages";
 
 /**
  * A WebConsoleFrame instance is an interactive console initialized *per target*
  * that displays console log data as well as provides an interactive terminal to
  * manipulate the target's document content.
  *
  * The WebConsoleFrame is responsible for the actual Web Console UI
  * implementation.
@@ -196,16 +197,17 @@ function WebConsoleFrame(aWebConsoleOwne
   this._networkRequests = {};
   this.filterPrefs = {};
 
   this.output = new ConsoleOutput(this);
 
   this._toggleFilter = this._toggleFilter.bind(this);
   this._onPanelSelected = this._onPanelSelected.bind(this);
   this._flushMessageQueue = this._flushMessageQueue.bind(this);
+  this._onToolboxPrefChanged = this._onToolboxPrefChanged.bind(this);
 
   this._outputTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
   this._outputTimerInitialized = false;
 
   EventEmitter.decorate(this);
 }
 exports.WebConsoleFrame = WebConsoleFrame;
 
@@ -566,16 +568,23 @@ WebConsoleFrame.prototype = {
     this.jsterm = new JSTerm(this);
     this.jsterm.init();
     this.jsterm.inputNode.focus();
 
     let toolbox = gDevTools.getToolbox(this.owner.target);
     if (toolbox) {
       toolbox.on("webconsole-selected", this._onPanelSelected);
     }
+
+    // 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),
+    });
   },
 
   /**
    * Sets the focus to JavaScript input field when the web console tab is
    * selected.
    * @private
    */
   _onPanelSelected: function WCF__onPanelSelected()
@@ -796,28 +805,36 @@ WebConsoleFrame.prototype = {
         if (!classes.contains("toolbarbutton-menubutton-button") &&
             originalTarget.getAttribute("type") === "menu-button") {
           // This is a filter button with a drop-down. The user clicked the
           // drop-down, so do nothing. (The menu will automatically appear
           // without our intervention.)
           break;
         }
 
+        // Toggle on the targeted filter button, and if the user alt clicked,
+        // toggle off all other filter buttons and their associated filters.
         let state = target.getAttribute("checked") !== "true";
+        if (aEvent.getModifierState("Alt")) {
+          let buttons = this.document
+                        .querySelectorAll(".webconsole-filter-button");
+          Array.forEach(buttons, (button) => {
+            if (button !== target) {
+              button.setAttribute("checked", false);
+              this._setMenuState(button, false);
+            }
+          });
+          state = true;
+        }
         target.setAttribute("checked", state);
 
         // This is a filter button with a drop-down, and the user clicked the
         // main part of the button. Go through all the severities and toggle
         // their associated filters.
-        let menuItems = target.querySelectorAll("menuitem");
-        for (let i = 0; i < menuItems.length; i++) {
-          menuItems[i].setAttribute("checked", state);
-          let prefKey = menuItems[i].getAttribute("prefKey");
-          this.setFilterState(prefKey, state);
-        }
+        this._setMenuState(target, state);
         break;
       }
 
       case "menuitem": {
         let state = target.getAttribute("checked") !== "true";
         target.setAttribute("checked", state);
 
         let prefKey = target.getAttribute("prefKey");
@@ -847,16 +864,35 @@ WebConsoleFrame.prototype = {
         let toolbarButton = menuPopup.parentNode;
         toolbarButton.setAttribute("checked", someChecked);
         break;
       }
     }
   },
 
   /**
+   * Set the menu attributes for a specific toggle button.
+   *
+   * @private
+   * @param XULElement aTarget
+   *        Button with drop down items to be toggled.
+   * @param boolean aState
+   *        True if the menu item is being toggled on, and false otherwise.
+   */
+  _setMenuState: function WCF__setMenuState(aTarget, aState)
+  {
+    let menuItems = aTarget.querySelectorAll("menuitem");
+    Array.forEach(menuItems, (item) => {
+      item.setAttribute("checked", aState);
+      let prefKey = item.getAttribute("prefKey");
+      this.setFilterState(prefKey, aState);
+    });
+  },
+
+  /**
    * Set the filter state for a specific toggle button.
    *
    * @param string aToggleType
    * @param boolean aState
    * @returns void
    */
   setFilterState: function WCF_setFilterState(aToggleType, aState)
   {
@@ -2771,16 +2807,39 @@ WebConsoleFrame.prototype = {
         return;
       }
 
       aCallback(this, aEvent);
     }, false);
   },
 
   /**
+   * Handler for the pref-changed event coming from the toolbox.
+   * Currently this function only handles the timestamps preferences.
+   *
+   * @private
+   * @param object aEvent
+   *        This parameter is a string that holds the event name
+   *        pref-changed in this case.
+   * @param object aData
+   *        This is the pref-changed data object.
+  */
+  _onToolboxPrefChanged: function WCF__onToolboxPrefChanged(aEvent, aData)
+  {
+    if (aData.pref == PREF_MESSAGE_TIMESTAMP) {
+      if (aData.newValue) {
+        this.outputNode.classList.remove("hideTimestamps");
+      }
+      else {
+        this.outputNode.classList.add("hideTimestamps");
+      }
+    }
+  },
+
+  /**
    * Copies the selected items to the system clipboard.
    *
    * @param object aOptions
    *        - linkOnly:
    *        An optional flag to copy only URL without timestamp and
    *        other meta-information. Default is false.
    */
   copySelectedItems: function WCF_copySelectedItems(aOptions)
@@ -2880,16 +2939,18 @@ WebConsoleFrame.prototype = {
 
     this._destroyer = promise.defer();
 
     let toolbox = gDevTools.getToolbox(this.owner.target);
     if (toolbox) {
       toolbox.off("webconsole-selected", this._onPanelSelected);
     }
 
+    gDevTools.off("pref-changed", this._onToolboxPrefChanged);
+
     this._repeatNodes = {};
     this._outputQueue = [];
     this._pruneCategoriesQueue = {};
     this._networkRequests = {};
 
     if (this._outputTimerInitialized) {
       this._outputTimerInitialized = false;
       this._outputTimer.cancel();
--- a/browser/devtools/webconsole/webconsole.xul
+++ b/browser/devtools/webconsole/webconsole.xul
@@ -43,18 +43,18 @@ function goUpdateConsoleCommands() {
              oncommand="goDoCommand('consoleCmd_clearOutput');"/>
     <command id="cmd_find" oncommand="goDoCommand('cmd_find');"/>
     <command id="cmd_fullZoomEnlarge" oncommand="goDoCommand('cmd_fontSizeEnlarge');" disabled="true"/>
     <command id="cmd_fullZoomReduce" oncommand="goDoCommand('cmd_fontSizeReduce');" disabled="true"/>
     <command id="cmd_fullZoomReset" oncommand="goDoCommand('cmd_fontSizeReset');" disabled="true"/>
     <command id="cmd_close" oncommand="goDoCommand('cmd_close');" disabled="true"/>
   </commandset>
   <keyset id="consoleKeys">
-    <key id="key_fullZoomReduce"  key="&fullZoomReduceCmd.commandkey;" command="cmd_fullZoomReduce"  modifiers="accel"/>
-    <key key="&fullZoomReduceCmd.commandkey2;"  command="cmd_fullZoomReduce" modifiers="accel"/>
+    <key id="key_fullZoomReduce" key="&fullZoomReduceCmd.commandkey;" command="cmd_fullZoomReduce" modifiers="accel"/>
+    <key key="&fullZoomReduceCmd.commandkey2;" command="cmd_fullZoomReduce" modifiers="accel"/>
     <key id="key_fullZoomEnlarge" key="&fullZoomEnlargeCmd.commandkey;" command="cmd_fullZoomEnlarge" modifiers="accel"/>
     <key key="&fullZoomEnlargeCmd.commandkey2;" command="cmd_fullZoomEnlarge" modifiers="accel"/>
     <key key="&fullZoomEnlargeCmd.commandkey3;" command="cmd_fullZoomEnlarge" modifiers="accel"/>
     <key id="key_fullZoomReset" key="&fullZoomResetCmd.commandkey;" command="cmd_fullZoomReset" modifiers="accel"/>
     <key key="&fullZoomResetCmd.commandkey2;" command="cmd_fullZoomReset" modifiers="accel"/>
     <key key="&findCmd.key;" command="cmd_find" modifiers="accel"/>
     <key key="&closeCmd.key;" command="cmd_close" modifiers="accel"/>
     <key key="&clearOutputCtrl.key;" command="consoleCmd_clearOutput" modifiers="control"/>
--- a/browser/locales/en-US/chrome/browser/devtools/toolbox.dtd
+++ b/browser/locales/en-US/chrome/browser/devtools/toolbox.dtd
@@ -104,16 +104,21 @@
 
 <!-- LOCALIZATION NOTE (options.enablePersistentLogging.label): This is the
   -  label for the checkbox that toggles persistent logs in the Web Console,
   -  i.e. devtools.webconsole.persistlog a boolean preference in about:config,
   -  in the options panel. -->
 <!ENTITY options.enablePersistentLogging.label    "Enable persistent logs">
 <!ENTITY options.enablePersistentLogging.tooltip  "If you enable this option the Web Console will not clear the output each time you navigate to a new page">
 
+<!-- LOCALIZATION NOTE (options.timestampMessages.label): This is the
+   - label for the checkbox that toggles timestamps in the Web Console -->
+<!ENTITY options.timestampMessages.label      "Enable timestamps">
+<!ENTITY options.timestampMessages.tooltip    "If you enable this option commands and output in the Web Console will display a timestamp">
+
 <!-- LOCALIZATION NOTE (options.profiler.label): This is the label for the
   -  heading of the group of JavaScript Profiler preferences in the options
   -  panel. -->
 <!ENTITY options.profiler.label            "JavaScript Profiler">
 
 <!-- LOCALIZATION NOTE (options.showPlatformData.label): This is the
   -  label for the checkbox that toggles the display of the platform data in the,
   -  Profiler i.e. devtools.profiler.ui.show-platform-data a boolean preference
--- a/browser/metro/base/content/browser.js
+++ b/browser/metro/base/content/browser.js
@@ -1015,97 +1015,93 @@ Browser.MainDragger.prototype = {
     this._horizontalScrollbar.removeAttribute("width");
     this._verticalScrollbar.removeAttribute("height");
     this._horizontalScrollbar.style.MozTransform = "";
     this._verticalScrollbar.style.MozTransform = "";
   }
 };
 
 
-
-const OPEN_APPTAB = 100; // Hack until we get a real API
-
 function nsBrowserAccess() { }
 
 nsBrowserAccess.prototype = {
   QueryInterface: function(aIID) {
     if (aIID.equals(Ci.nsIBrowserDOMWindow) || aIID.equals(Ci.nsISupports))
       return this;
     throw Cr.NS_NOINTERFACE;
   },
 
+  _getOpenAction: function _getOpenAction(aURI, aOpener, aWhere, aContext) {
+    let where = aWhere;
+    /*
+     * aWhere: 
+     * OPEN_DEFAULTWINDOW: default action
+     * OPEN_CURRENTWINDOW: current window/tab
+     * OPEN_NEWWINDOW: not allowed, converted to newtab below
+     * OPEN_NEWTAB: open a new tab
+     * OPEN_SWITCHTAB: open in an existing tab if it matches, otherwise open
+     * a new tab. afaict we always open these in the current tab.
+     */
+    if (where == Ci.nsIBrowserDOMWindow.OPEN_DEFAULTWINDOW) {
+      // query standard browser prefs indicating what to do for default action
+      switch (aContext) {
+        // indicates this is an open request from a 3rd party app.
+        case Ci.nsIBrowserDOMWindow.OPEN_EXTERNAL :
+          where = Services.prefs.getIntPref("browser.link.open_external");
+          break;
+        // internal request
+        default :
+          where = Services.prefs.getIntPref("browser.link.open_newwindow");
+      }
+    }
+    if (where == Ci.nsIBrowserDOMWindow.OPEN_NEWWINDOW) {
+      Util.dumpLn("Invalid request - we can't open links in new windows.");
+      where = Ci.nsIBrowserDOMWindow.OPEN_NEWTAB;
+    }
+    return where;
+  },
+
   _getBrowser: function _getBrowser(aURI, aOpener, aWhere, aContext) {
     let isExternal = (aContext == Ci.nsIBrowserDOMWindow.OPEN_EXTERNAL);
+    // We don't allow externals apps opening chrome docs
     if (isExternal && aURI && aURI.schemeIs("chrome"))
       return null;
 
+    let location;
+    let browser;
     let loadflags = isExternal ?
                       Ci.nsIWebNavigation.LOAD_FLAGS_FROM_EXTERNAL :
                       Ci.nsIWebNavigation.LOAD_FLAGS_NONE;
-    let location;
-    if (aWhere == Ci.nsIBrowserDOMWindow.OPEN_DEFAULTWINDOW) {
-      switch (aContext) {
-        case Ci.nsIBrowserDOMWindow.OPEN_EXTERNAL :
-          aWhere = Services.prefs.getIntPref("browser.link.open_external");
-          break;
-        default : // OPEN_NEW or an illegal value
-          aWhere = Services.prefs.getIntPref("browser.link.open_newwindow");
-      }
-    }
+    let openAction = this._getOpenAction(aURI, aOpener, aWhere, aContext);
 
-    let browser;
-    if (aWhere == Ci.nsIBrowserDOMWindow.OPEN_NEWWINDOW) {
-      let url = aURI ? aURI.spec : "about:blank";
-      let newWindow = openDialog("chrome://browser/content/browser.xul", "_blank",
-                                 "all,dialog=no", url, null, null, null);
-      // since newWindow.Browser doesn't exist yet, just return null
-      return null;
-    } else if (aWhere == Ci.nsIBrowserDOMWindow.OPEN_NEWTAB) {
+    if (openAction == Ci.nsIBrowserDOMWindow.OPEN_NEWTAB) {
       let owner = isExternal ? null : Browser.selectedTab;
       let tab = Browser.addTab("about:blank", true, owner);
-      if (isExternal)
+      // Link clicks in content need to trigger peek tab functionality
+      ContextUI.peekTabs(kOpenInNewTabAnimationDelayMsec);
+      if (isExternal) {
         tab.closeOnExit = true;
+      }
       browser = tab.browser;
-    } else if (aWhere == OPEN_APPTAB) {
-      Browser.tabs.forEach(function(aTab) {
-        if ("appURI" in aTab.browser && aTab.browser.appURI.spec == aURI.spec) {
-          Browser.selectedTab = aTab;
-          browser = aTab.browser;
-        }
-      });
-
-      if (!browser) {
-        // Make a new tab to hold the app
-        let tab = Browser.addTab("about:blank", true);
-        browser = tab.browser;
-        browser.appURI = aURI;
-      } else {
-        // Just use the existing browser, but return null to keep the system from trying to load the URI again
-        browser = null;
-      }
-    } else { // OPEN_CURRENTWINDOW and illegal values
+    } else {
       browser = Browser.selectedBrowser;
     }
 
     try {
       let referrer;
       if (aURI && browser) {
         if (aOpener) {
           location = aOpener.location;
           referrer = Services.io.newURI(location, null, null);
         }
         browser.loadURIWithFlags(aURI.spec, loadflags, referrer, null, null);
       }
       browser.focus();
     } catch(e) { }
 
-    // We are loading web content into this window, so make sure content is visible
-    // XXX Can we remove this?  It seems to be reproduced in BrowserUI already.
-    BrowserUI.showContent();
-
     return browser;
   },
 
   openURI: function browser_openURI(aURI, aOpener, aWhere, aContext) {
     let browser = this._getBrowser(aURI, aOpener, aWhere, aContext);
     return browser ? browser.contentWindow : null;
   },
 
--- a/browser/themes/shared/devtools/webconsole.inc.css
+++ b/browser/themes/shared/devtools/webconsole.inc.css
@@ -107,16 +107,20 @@ a {
 #output-container {
   -moz-user-select: text;
   -moz-box-flex: 1;
   display: flex;
   flex-direction: column;
   align-items: flex-start;
 }
 
+#output-container.hideTimestamps > .message > .timestamp {
+  display: none;
+}
+
 .filtered-by-type,
 .filtered-by-string {
   display: none;
 }
 
 .hidden-message {
   display: block;
   visibility: hidden;
--- a/toolkit/mozapps/extensions/XPIProvider.jsm
+++ b/toolkit/mozapps/extensions/XPIProvider.jsm
@@ -1381,52 +1381,30 @@ function escapeAddonURI(aAddon, aUri, aU
     compatMode = "ignore";
   else if (AddonManager.strictCompatibility)
     compatMode = "strict";
   uri = uri.replace(/%COMPATIBILITY_MODE%/g, compatMode);
 
   return uri;
 }
 
-function recursiveRemoveAsync(aFile) {
+function removeAsync(aFile) {
   return Task.spawn(function () {
     let info = null;
     try {
       info = yield OS.File.stat(aFile.path);
+      if (info.isDir)
+        yield OS.File.removeDir(aFile.path);
+      else
+        yield OS.File.remove(aFile.path);
     }
     catch (e if e instanceof OS.File.Error && e.becauseNoSuchFile) {
       // The file has already gone away
       return;
     }
-
-    setFilePermissions(aFile, info.isDir ? FileUtils.PERMS_DIRECTORY
-                                         : FileUtils.PERMS_FILE);
-
-    // OS.File means we have to recurse into directories
-    if (info.isDir) {
-      let iterator = new OS.File.DirectoryIterator(aFile.path);
-      yield iterator.forEach(function(entry) {
-        let nextFile = aFile.clone();
-        nextFile.append(entry.name);
-        return recursiveRemoveAsync(nextFile);
-      });
-      yield iterator.close();
-    }
-
-    try {
-      yield info.isDir ? OS.File.removeEmptyDir(aFile.path)
-                       : OS.File.remove(aFile.path);
-    }
-    catch (e if e instanceof OS.File.Error && e.becauseNoSuchFile) {
-      // The file has already gone away
-    }
-    catch (e) {
-      ERROR("Failed to remove file " + aFile.path, e);
-      throw e;
-    }
   });
 }
 
 /**
  * Recursively removes a directory or file fixing permissions when necessary.
  *
  * @param  aFile
  *         The nsIFile to remove
@@ -5419,26 +5397,26 @@ AddonInstall.prototype = {
       let installedUnpacked = 0;
       yield this.installLocation.requestStagingDir();
 
       // First stage the file regardless of whether restarting is necessary
       if (this.addon.unpack || Prefs.getBoolPref(PREF_XPI_UNPACK, false)) {
         LOG("Addon " + this.addon.id + " will be installed as " +
             "an unpacked directory");
         stagedAddon.append(this.addon.id);
-        yield recursiveRemoveAsync(stagedAddon);
+        yield removeAsync(stagedAddon);
         yield OS.File.makeDir(stagedAddon.path);
         yield extractFilesAsync(this.file, stagedAddon);
         installedUnpacked = 1;
       }
       else {
         LOG("Addon " + this.addon.id + " will be installed as " +
             "a packed xpi");
         stagedAddon.append(this.addon.id + ".xpi");
-        yield recursiveRemoveAsync(stagedAddon);
+        yield removeAsync(stagedAddon);
         yield OS.File.copy(this.file.path, stagedAddon.path);
       }
 
       if (requiresRestart) {
         // Point the add-on to its extracted files as the xpi may get deleted
         this.addon._sourceBundle = stagedAddon;
 
         // Cache the AddonInternal as it may have updated compatibility info