Merge latest green inbound changeset and mozilla-central
authorEd Morley <emorley@mozilla.com>
Wed, 05 Jun 2013 11:39:27 +0100
changeset 134069 b925d7cdd09acc8a8b1ad1f8e4ab997857a60d0a
parent 134056 c6c656a65a817a05482d1ddf77009a5e92ca3106 (current diff)
parent 134068 8f9ba85eb61c673f18dfca106240c9e72c751ccc (diff)
child 134078 ddb7b23166ef3ddafad31b326fd97bd8a2a9def4
push id29035
push useremorley@mozilla.com
push dateWed, 05 Jun 2013 11:20:37 +0000
treeherdermozilla-inbound@627af04199a5 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone24.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge latest green inbound changeset and mozilla-central
--- a/accessible/tests/mochitest/events/test_docload.html
+++ b/accessible/tests/mochitest/events/test_docload.html
@@ -10,16 +10,35 @@
           src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
 
   <script type="application/javascript"
           src="../common.js"></script>
   <script type="application/javascript"
           src="../role.js"></script>
   <script type="application/javascript"
           src="../states.js"></script>
+
+  <script type="application/javascript">
+    // Front end stuff sometimes likes to stuff things in the hidden window(s)
+    // in which case there's accessibles for that content.
+    Components.utils.import("resource://gre/modules/Services.jsm");
+
+    // Force the creation of an accessible for the hidden window's document.
+    var doc = Services.appShell.hiddenDOMWindow.document;
+    gAccRetrieval.getAccessibleFor(doc);
+
+    // The private hidden window will be lazily created that's why we need to do
+    // it here *before* loading '../events.js' or else we'll have a duplicate
+    // reorder event.
+    var privateDoc = Services.appShell.hiddenPrivateDOMWindow.document;
+
+    // Force the creation of an accessible for the private hidden window's doc.
+    gAccRetrieval.getAccessibleFor(privateDoc);
+  </script>
+
   <script type="application/javascript"
           src="../events.js"></script>
 
   <script type="application/javascript">
     ////////////////////////////////////////////////////////////////////////////
     // Invokers
 
     function changeIframeSrc(aIdentifier, aURL)
@@ -174,16 +193,22 @@
         var accTree = {
           role: ROLE_APP_ROOT,
           children: [
             {
               role: ROLE_CHROME_WINDOW
             },
             {
               role: ROLE_CHROME_WINDOW
+            },
+            {
+              role: ROLE_CHROME_WINDOW
+            },
+            {
+              role: ROLE_CHROME_WINDOW
             }
           ]
         };
 
         testAccessibleTree(this.mRootAcc, accTree);
 
         var dlgDoc = this.mDialog.document;
         ok(isAccessibleInCache(dlgDoc),
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1129,16 +1129,21 @@ pref("devtools.webconsole.filter.warn", 
 pref("devtools.webconsole.filter.info", true);
 pref("devtools.webconsole.filter.log", true);
 pref("devtools.webconsole.filter.secerror", true);
 pref("devtools.webconsole.filter.secwarn", true);
 
 // 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);
+
 // 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:
@@ -1170,17 +1175,17 @@ pref("browser.menu.showCharacterEncoding
 // Allow using tab-modal prompts when possible.
 pref("prompts.tab_modal.enabled", true);
 // Whether the Panorama should animate going in/out of tabs
 pref("browser.panorama.animate_zoom", true);
 
 // Defines the url to be used for new tabs.
 pref("browser.newtab.url", "about:newtab");
 // Activates preloading of the new tab url.
-pref("browser.newtab.preload", false);
+pref("browser.newtab.preload", true);
 
 // Toggles the content of 'about:newtab'. Shows the grid when enabled.
 pref("browser.newtabpage.enabled", true);
 
 // number of rows of newtab grid
 pref("browser.newtabpage.rows", 3);
 
 // number of columns of newtab grid
--- a/browser/base/content/newtab/grid.js
+++ b/browser/base/content/newtab/grid.js
@@ -25,16 +25,19 @@ let gGrid = {
   _cells: null,
   get cells() this._cells,
 
   /**
    * All sites contained in the grid's cells. Sites may be empty.
    */
   get sites() [cell.site for each (cell in this.cells)],
 
+  // Tells whether the grid has already been initialized.
+  get ready() !!this._node,
+
   /**
    * Initializes the grid.
    * @param aSelector The query selector of the grid.
    */
   init: function Grid_init() {
     this._node = document.getElementById("newtab-grid");
     this._createSiteFragment();
     this._render();
--- a/browser/base/content/newtab/page.js
+++ b/browser/base/content/newtab/page.js
@@ -45,17 +45,20 @@ let gPage = {
       gUndoDialog.hide();
     }
   },
 
   /**
    * Updates the whole page and the grid when the storage has changed.
    */
   update: function Page_update() {
-    gGrid.refresh();
+    // The grid might not be ready yet as we initialize it asynchronously.
+    if (gGrid.ready) {
+      gGrid.refresh();
+    }
   },
 
   /**
    * Internally initializes the page. This runs only when/if the feature
    * is/gets enabled.
    */
   _init: function Page_init() {
     if (this._initialized)
--- a/browser/base/content/test/browser_bug763468_perwindowpb.js
+++ b/browser/base/content/test/browser_bug763468_perwindowpb.js
@@ -8,33 +8,31 @@ function test() {
   waitForExplicitFinish();
   let windowsToClose = [];
   let newTab;
   let newTabPrefName = "browser.newtab.url";
   let newTabURL;
   let mode;
 
   function doTest(aIsPrivateMode, aWindow, aCallback) {
-    aWindow.BrowserOpenTab();
-    aWindow.gBrowser.selectedTab.linkedBrowser.addEventListener("load", function onLoad() {
-      aWindow.gBrowser.selectedTab.linkedBrowser.removeEventListener("load", onLoad, true);
+    whenNewTabLoaded(aWindow, function () {
       if (aIsPrivateMode) {
         mode = "per window private browsing";
         newTabURL = "about:privatebrowsing";
       } else {
         mode = "normal";
         newTabURL = Services.prefs.getCharPref(newTabPrefName) || "about:blank";
       }
 
       is(aWindow.gBrowser.currentURI.spec, newTabURL,
         "URL of NewTab should be " + newTabURL + " in " + mode +  " mode");
 
       aWindow.gBrowser.removeTab(aWindow.gBrowser.selectedTab);
       aCallback()
-    }, true);
+    });
   };
 
   function testOnWindow(aOptions, aCallback) {
     whenNewWindowLoaded(aOptions, function(aWin) {
       windowsToClose.push(aWin);
       // execute should only be called when need, like when you are opening
       // web pages on the test. If calling executeSoon() is not necesary, then
       // call whenNewWindowLoaded() instead of testOnWindow() on your test.
--- a/browser/base/content/test/browser_tabopen_reflows.js
+++ b/browser/base/content/test/browser_tabopen_reflows.js
@@ -28,23 +28,28 @@ const EXPECTED_REFLOWS = [
 
   // accessing element.scrollPosition in _fillTrailingGap() flushes layout
   "get_scrollPosition@chrome://global/content/bindings/scrollbox.xml|" +
     "_fillTrailingGap@chrome://browser/content/tabbrowser.xml|" +
     "_handleNewTab@chrome://browser/content/tabbrowser.xml|" +
     "onxbltransitionend@chrome://browser/content/tabbrowser.xml|"
 ];
 
+const PREF_PRELOAD = "browser.newtab.preload";
+
 /*
  * This test ensures that there are no unexpected
  * uninterruptible reflows when opening new tabs.
  */
 function test() {
   waitForExplicitFinish();
 
+  Services.prefs.setBoolPref(PREF_PRELOAD, false);
+  registerCleanupFunction(() => Services.prefs.clearUserPref(PREF_PRELOAD));
+
   // Add a reflow observer and open a new tab.
   docShell.addWeakReflowObserver(observer);
   BrowserOpenTab();
 
   // Wait until the tabopen animation has finished.
   waitForTransitionEnd(function () {
     // Remove reflow observer and clean up.
     docShell.removeWeakReflowObserver(observer);
--- a/browser/base/content/test/head.js
+++ b/browser/base/content/test/head.js
@@ -192,16 +192,31 @@ function promiseIsURIVisited(aURI, aExpe
   let deferred = Promise.defer();
   PlacesUtils.asyncHistory.isURIVisited(aURI, function(aURI, aIsVisited) {
     deferred.resolve(aIsVisited);
   });
 
   return deferred.promise;
 }
 
+function whenNewTabLoaded(aWindow, aCallback) {
+  aWindow.BrowserOpenTab();
+
+  let browser = aWindow.gBrowser.selectedBrowser;
+  if (browser.contentDocument.readyState === "complete") {
+    aCallback();
+    return;
+  }
+
+  browser.addEventListener("load", function onLoad() {
+    browser.removeEventListener("load", onLoad, true);
+    aCallback();
+  }, true);
+}
+
 function addVisits(aPlaceInfo, aCallback) {
   let places = [];
   if (aPlaceInfo instanceof Ci.nsIURI) {
     places.push({ uri: aPlaceInfo });
   } else if (Array.isArray(aPlaceInfo)) {
     places = places.concat(aPlaceInfo);
   } else {
     places.push(aPlaceInfo);
--- a/browser/base/content/test/newtab/browser_newtab_bug723102.js
+++ b/browser/base/content/test/newtab/browser_newtab_bug723102.js
@@ -10,9 +10,10 @@ function runTests() {
   let firstTab = gBrowser.selectedTab;
 
   yield addNewTabPageTab();
   gBrowser.removeTab(firstTab);
 
   ok(NewTabUtils.allPages.enabled, "page is enabled");
   NewTabUtils.allPages.enabled = false;
   ok(getGrid().node.hasAttribute("page-disabled"), "page is disabled");
+  NewTabUtils.allPages.enabled = true;
 }
--- a/browser/base/content/test/newtab/browser_newtab_focus.js
+++ b/browser/base/content/test/newtab/browser_newtab_focus.js
@@ -23,16 +23,17 @@ function runTests() {
   // Count the focus with the enabled page.
   yield countFocus(FOCUS_COUNT);
 
   // Disable page and count the focus with the disabled page.
   NewTabUtils.allPages.enabled = false;
   yield countFocus(1);
 
   Services.prefs.clearUserPref("accessibility.tabfocus");
+  NewTabUtils.allPages.enabled = true;
 }
 
 /**
  * Focus the urlbar and count how many focus stops to return again to the urlbar.
  */
 function countFocus(aExpectedCount) {
   let focusCount = 0;
   let contentDoc = getContentDocument();
--- a/browser/base/content/test/newtab/head.js
+++ b/browser/base/content/test/newtab/head.js
@@ -222,17 +222,20 @@ function addNewTabPageTab() {
 
   function whenNewTabLoaded() {
     if (NewTabUtils.allPages.enabled) {
       // Continue when the link cache has been populated.
       NewTabUtils.links.populateCache(function () {
         executeSoon(TestRunner.next);
       });
     } else {
-      TestRunner.next();
+      // It's important that we call next() asynchronously.
+      // 'yield addNewTabPageTab()' would fail if next() is called
+      // synchronously because the iterator is already executing.
+      executeSoon(TestRunner.next);
     }
   }
 
   // The new tab page might have been preloaded in the background.
   if (browser.contentDocument.readyState == "complete") {
     whenNewTabLoaded();
     return;
   }
--- a/browser/devtools/framework/test/browser_toolbox_options.js
+++ b/browser/devtools/framework/test/browser_toolbox_options.js
@@ -9,17 +9,17 @@ function test() {
   gBrowser.selectedTab = gBrowser.addTab();
   let target = TargetFactory.forTab(gBrowser.selectedTab);
 
   gBrowser.selectedBrowser.addEventListener("load", function onLoad(evt) {
     gBrowser.selectedBrowser.removeEventListener(evt.type, onLoad, true);
     gDevTools.showToolbox(target).then(testSelectTool);
   }, true);
 
-  content.location = "data:text/html,test for dynamically registering and unregistering tools";
+  content.location = "data:text/html;charset=utf8,test for dynamically registering and unregistering tools";
 }
 
 function testSelectTool(aToolbox) {
   toolbox = aToolbox;
   doc = toolbox.doc;
   toolbox.once("options-selected", testOptionsShortcut);
   toolbox.selectTool("options");
 }
@@ -90,25 +90,28 @@ function checkTools() {
 
   // Wait for the next turn of the event loop to avoid stack overflow errors.
   executeSoon(toggleTools);
 }
 
 function toggleTools() {
   if (index < prefNodes.length) {
     gDevTools.once("tool-unregistered", checkUnregistered);
-    EventUtils.synthesizeMouseAtCenter(prefNodes[index], {}, panelWin);
+    let node = prefNodes[index];
+    node.scrollIntoView();
+    EventUtils.synthesizeMouseAtCenter(node, {}, panelWin);
   }
   else if (index < 2*prefNodes.length) {
     gDevTools.once("tool-registered", checkRegistered);
-    EventUtils.synthesizeMouseAtCenter(prefNodes[index - prefNodes.length], {}, panelWin);
+    let node = prefNodes[index - prefNodes.length];
+    node.scrollIntoView();
+    EventUtils.synthesizeMouseAtCenter(node, {}, panelWin);
   }
   else {
     cleanup();
-    return;
   }
 }
 
 function checkUnregistered(event, data) {
   if (data.id == prefNodes[index].getAttribute("id")) {
     ok(true, "Correct tool removed");
     // checking tab on the toolbox
     ok(!doc.getElementById("toolbox-tab-" + data.id), "Tab removed for " +
--- a/browser/devtools/framework/toolbox-options.xul
+++ b/browser/devtools/framework/toolbox-options.xul
@@ -23,16 +23,22 @@
         <label value="&options.selectDevToolsTheme.label;"/>
         <radiogroup id="devtools-theme-box"
                     class="options-groupbox"
                     data-pref="devtools.theme"
                     orient="horizontal">
           <radio value="light" label="&options.lightTheme.label;"/>
           <radio value="dark" label="&options.darkTheme.label;"/>
         </radiogroup>
+        <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"/>
+        </vbox>
         <label value="&options.context.label;"/>
         <vbox id="context-options" class="options-groupbox">
           <checkbox label="&options.enableChrome.label;"
                     tooltiptext="&options.enableChrome.tooltip;"
                     data-pref="devtools.chrome.enabled"/>
           <checkbox label="&options.enableRemote.label;"
                     tooltiptext="&options.enableRemote.tooltip;"
                     data-pref="devtools.debugger.remote-enabled"/>
--- a/browser/devtools/netmonitor/netmonitor-view.js
+++ b/browser/devtools/netmonitor/netmonitor-view.js
@@ -410,49 +410,42 @@ create({ constructor: RequestsMenuView, 
 
     this.refreshSummary();
   },
 
   /**
    * Sorts all network requests in this container by a specified detail.
    *
    * @param string aType
-   *        Either null, "status", "method", "file", "domain", "type" or "size".
+   *        Either "status", "method", "file", "domain", "type", "size" or "waterfall".
    */
-  sortBy: function(aType) {
+  sortBy: function(aType = "waterfall") {
     let target = $("#requests-menu-" + aType + "-button");
     let headers = document.querySelectorAll(".requests-menu-header-button");
 
     for (let header of headers) {
       if (header != target) {
         header.removeAttribute("sorted");
         header.removeAttribute("tooltiptext");
       }
     }
 
     let direction = "";
     if (target) {
-      if (!target.hasAttribute("sorted")) {
-        target.setAttribute("sorted", direction = "ascending");
-        target.setAttribute("tooltiptext", L10N.getStr("networkMenu.sortedAsc"));
-      } else if (target.getAttribute("sorted") == "ascending") {
+      if (target.getAttribute("sorted") == "ascending") {
         target.setAttribute("sorted", direction = "descending");
         target.setAttribute("tooltiptext", L10N.getStr("networkMenu.sortedDesc"));
       } else {
-        target.removeAttribute("sorted");
-        target.removeAttribute("tooltiptext");
+        target.setAttribute("sorted", direction = "ascending");
+        target.setAttribute("tooltiptext", L10N.getStr("networkMenu.sortedAsc"));
       }
     }
 
-    // Sort by timing.
-    if (!target || !direction) {
-      this.sortContents(this._byTiming);
-    }
     // Sort by whatever was requested.
-    else switch (aType) {
+    switch (aType) {
       case "status":
         if (direction == "ascending") {
           this.sortContents(this._byStatus);
         } else {
           this.sortContents((a, b) => !this._byStatus(a, b));
         }
         break;
       case "method":
@@ -485,16 +478,23 @@ create({ constructor: RequestsMenuView, 
         break;
       case "size":
         if (direction == "ascending") {
           this.sortContents(this._bySize);
         } else {
           this.sortContents((a, b) => !this._bySize(a, b));
         }
         break;
+      case "waterfall":
+        if (direction == "ascending") {
+          this.sortContents(this._byTiming);
+        } else {
+          this.sortContents((a, b) => !this._byTiming(a, b));
+        }
+        break;
     }
   },
 
   /**
    * Predicates used when filtering items.
    *
    * @param MenuItem aItem
    *        The filtered menu item.
@@ -919,17 +919,17 @@ create({ constructor: RequestsMenuView, 
 
   /**
    * Creates the labels displayed on the waterfall header in this container.
    *
    * @param number aScale
    *        The current waterfall scale.
    */
   _showWaterfallDivisionLabels: function(aScale) {
-    let container = $("#requests-menu-waterfall-header-box");
+    let container = $("#requests-menu-waterfall-button");
     let availableWidth = this._waterfallWidth - REQUESTS_WATERFALL_SAFE_BOUNDS;
 
     // Nuke all existing labels.
     while (container.hasChildNodes()) {
       container.firstChild.remove();
     }
 
     // Build new millisecond tick labels...
--- a/browser/devtools/netmonitor/netmonitor.xul
+++ b/browser/devtools/netmonitor/netmonitor.xul
@@ -77,23 +77,29 @@
                     class="requests-menu-header-button requests-menu-size"
                     onclick="NetMonitorView.RequestsMenu.sortBy('size')"
                     label="&netmonitorUI.toolbar.size;"
                     flex="1">
             </button>
           </hbox>
           <hbox id="requests-menu-waterfall-header-box"
                 class="requests-menu-header requests-menu-waterfall"
-                align="center">
-            <label id="requests-menu-waterfall-label"
-                   class="plain requests-menu-waterfall"
-                   value="&netmonitorUI.toolbar.waterfall;"/>
+                align="center"
+                flex="1">
+            <button id="requests-menu-waterfall-button"
+                    class="requests-menu-header-button requests-menu-waterfall"
+                    onclick="NetMonitorView.RequestsMenu.sortBy('waterfall')"
+                    pack="start"
+                    flex="1">
+              <label id="requests-menu-waterfall-label"
+                     class="plain requests-menu-waterfall"
+                     value="&netmonitorUI.toolbar.waterfall;"/>
+            </button>
           </hbox>
         </hbox>
-        <spacer id="toolbar-spacer" flex="1"/>
         <toolbarbutton id="details-pane-toggle"
                        class="devtools-toolbarbutton"
                        tooltiptext="&netmonitorUI.panesButton.tooltip;"
                        disabled="true"
                        tabindex="0"/>
       </toolbar>
       <label id="requests-menu-empty-notice"
              value="&netmonitorUI.emptyNotice;"/>
--- a/browser/devtools/netmonitor/test/browser_net_filter-03.js
+++ b/browser/devtools/netmonitor/test/browser_net_filter-03.js
@@ -63,19 +63,18 @@ function test() {
         })
         .then(() => {
           return teardown(aMonitor);
         })
         .then(finish);
     });
 
     function resetSorting() {
-      for (let i = 0; i < 3; i++) {
-        EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-size-button"));
-      }
+      EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-waterfall-button"));
+      EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-size-button"));
     }
 
     function testButtons(aFilterType) {
       let doc = aMonitor.panelWin.document;
       let target = doc.querySelector("#requests-menu-filter-" + aFilterType + "-button");
       let buttons = doc.querySelectorAll(".requests-menu-footer-button");
 
       for (let button of buttons) {
--- a/browser/devtools/netmonitor/test/browser_net_sort-01.js
+++ b/browser/devtools/netmonitor/test/browser_net_sort-01.js
@@ -138,17 +138,17 @@ function test() {
         })
         .then(() => {
           info("Testing swap(4, 4)");
           RequestsMenu.swapItemsAtIndices(4, 4);
           return testContents([3, 4, 0, 1, 2]);
         })
         .then(() => {
           info("Clearing sort.");
-          RequestsMenu.sortBy(null);
+          RequestsMenu.sortBy();
           return testContents([0, 1, 2, 3, 4]);
         })
         .then(() => {
           return teardown(aMonitor);
         })
         .then(finish);
     });
 
--- a/browser/devtools/netmonitor/test/browser_net_sort-02.js
+++ b/browser/devtools/netmonitor/test/browser_net_sort-02.js
@@ -38,91 +38,109 @@ function test() {
         })
         .then(() => {
           info("Testing status sort, descending.");
           EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-status-button"));
           testHeaders("status", "descending");
           return testContents([4, 3, 2, 1, 0]);
         })
         .then(() => {
-          info("Clearing status sort.");
+          info("Testing status sort, ascending. Checking sort loops correctly.");
           EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-status-button"));
-          testHeaders();
-          return testContents([0, 2, 4, 3, 1]);
+          testHeaders("status", "ascending");
+          return testContents([0, 1, 2, 3, 4]);
         })
         .then(() => {
           info("Testing method sort, ascending.");
           EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-method-button"));
           testHeaders("method", "ascending");
           return testContents([0, 1, 2, 3, 4]);
         })
         .then(() => {
           info("Testing method sort, descending.");
           EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-method-button"));
           testHeaders("method", "descending");
           return testContents([4, 3, 2, 1, 0]);
         })
         .then(() => {
-          info("Clearing method sort.");
+          info("Testing method sort, ascending. Checking sort loops correctly.");
           EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-method-button"));
-          testHeaders();
-          return testContents([0, 2, 4, 3, 1]);
+          testHeaders("method", "ascending");
+          return testContents([0, 1, 2, 3, 4]);
         })
         .then(() => {
           info("Testing file sort, ascending.");
           EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-file-button"));
           testHeaders("file", "ascending");
           return testContents([0, 1, 2, 3, 4]);
         })
         .then(() => {
           info("Testing file sort, descending.");
           EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-file-button"));
           testHeaders("file", "descending");
           return testContents([4, 3, 2, 1, 0]);
         })
         .then(() => {
-          info("Clearing file sort.");
+          info("Testing file sort, ascending. Checking sort loops correctly.");
           EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-file-button"));
-          testHeaders();
-          return testContents([0, 2, 4, 3, 1]);
+          testHeaders("file", "ascending");
+          return testContents([0, 1, 2, 3, 4]);
         })
         .then(() => {
           info("Testing type sort, ascending.");
           EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-type-button"));
           testHeaders("type", "ascending");
           return testContents([0, 1, 2, 3, 4]);
         })
         .then(() => {
           info("Testing type sort, descending.");
           EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-type-button"));
           testHeaders("type", "descending");
           return testContents([4, 3, 2, 1, 0]);
         })
         .then(() => {
-          info("Clearing type sort.");
+          info("Testing type sort, ascending. Checking sort loops correctly.");
           EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-type-button"));
-          testHeaders();
-          return testContents([0, 2, 4, 3, 1]);
+          testHeaders("type", "ascending");
+          return testContents([0, 1, 2, 3, 4]);
         })
         .then(() => {
           info("Testing size sort, ascending.");
           EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-size-button"));
           testHeaders("size", "ascending");
           return testContents([0, 1, 2, 3, 4]);
         })
         .then(() => {
           info("Testing size sort, descending.");
           EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-size-button"));
           testHeaders("size", "descending");
           return testContents([4, 3, 2, 1, 0]);
         })
         .then(() => {
-          info("Clearing size sort.");
+          info("Testing size sort, ascending. Checking sort loops correctly.");
           EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-size-button"));
-          testHeaders();
+          testHeaders("size", "ascending");
+          return testContents([0, 1, 2, 3, 4]);
+        })
+        .then(() => {
+          info("Testing waterfall sort, ascending.");
+          EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-waterfall-button"));
+          testHeaders("waterfall", "ascending");
+          return testContents([0, 2, 4, 3, 1]);
+        })
+        .then(() => {
+          info("Testing waterfall sort, descending.");
+          EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-waterfall-button"));
+          testHeaders("waterfall", "descending");
+          return testContents([4, 2, 0, 1, 3]);
+        })
+        .then(() => {
+          info("Testing waterfall sort, ascending. Checking sort loops correctly.");
+          EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-waterfall-button"));
+          testHeaders("waterfall", "ascending");
           return testContents([0, 2, 4, 3, 1]);
         })
         .then(() => {
           return teardown(aMonitor);
         })
         .then(finish);
     });
 
--- a/browser/devtools/webconsole/test/Makefile.in
+++ b/browser/devtools/webconsole/test/Makefile.in
@@ -130,16 +130,17 @@ MOCHITEST_BROWSER_FILES = \
 	browser_bug_862916_console_dir_and_filter_off.js \
 	browser_console_native_getters.js \
 	browser_bug_871156_ctrlw_close_tab.js \
 	browser_console_private_browsing.js \
 	browser_console_nsiconsolemessage.js \
 	browser_webconsole_bug_817834_add_edited_input_to_history.js \
 	browser_console_addonsdk_loader_exception.js \
 	browser_console_error_source_click.js \
+	browser_console_clear_on_reload.js \
 	head.js \
 	$(NULL)
 
 ifeq ($(OS_ARCH), Darwin)
 MOCHITEST_BROWSER_FILES += \
 	browser_webconsole_bug_804845_ctrl_key_nav.js \
         $(NULL)
 endif
new file mode 100644
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_console_clear_on_reload.js
@@ -0,0 +1,73 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// Check that clear output on page reload works - bug 705921.
+
+function test()
+{
+  const PREF = "devtools.webconsole.persistlog";
+  const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-console.html";
+  let hud = null;
+
+  Services.prefs.setBoolPref(PREF, false);
+  registerCleanupFunction(() => Services.prefs.clearUserPref(PREF));
+
+  addTab(TEST_URI);
+
+  browser.addEventListener("load", function onLoad() {
+    browser.removeEventListener("load", onLoad, true);
+    openConsole(null, consoleOpened);
+  }, true);
+
+  function consoleOpened(aHud)
+  {
+    hud = aHud;
+    ok(hud, "Web Console opened");
+
+    hud.jsterm.clearOutput();
+    content.console.log("foobarz1");
+    waitForMessages({
+      webconsole: hud,
+      messages: [{
+        text: "foobarz1",
+        category: CATEGORY_WEBDEV,
+        severity: SEVERITY_LOG,
+      }],
+    }).then(onConsoleMessage);
+  }
+
+  function onConsoleMessage()
+  {
+    browser.addEventListener("load", onReload, true);
+    content.location.reload();
+  }
+
+  function onReload()
+  {
+    browser.removeEventListener("load", onReload, true);
+
+    content.console.log("foobarz2");
+
+    waitForMessages({
+      webconsole: hud,
+      messages: [{
+        text: "test-console.html",
+        category: CATEGORY_NETWORK,
+      },
+      {
+        text: "foobarz2",
+        category: CATEGORY_WEBDEV,
+        severity: SEVERITY_LOG,
+      }],
+    }).then(onConsoleMessageAfterReload);
+  }
+
+  function onConsoleMessageAfterReload()
+  {
+    is(hud.outputNode.textContent.indexOf("foobarz1"), -1,
+       "foobarz1 has been removed from output");
+    finishTest();
+  }
+}
--- a/browser/devtools/webconsole/test/browser_repeated_messages_accuracy.js
+++ b/browser/devtools/webconsole/test/browser_repeated_messages_accuracy.js
@@ -6,16 +6,20 @@
 
 // Tests that makes sure messages are not considered repeated when coming from
 // different lines of code, or from different severities, etc.
 // See bugs 720180 and 800510.
 
 const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-repeated-messages.html";
 
 function test() {
+  const PREF = "devtools.webconsole.persistlog";
+  Services.prefs.setBoolPref(PREF, true);
+  registerCleanupFunction(() => Services.prefs.clearUserPref(PREF));
+
   addTab(TEST_URI);
   browser.addEventListener("load", function onLoad() {
     browser.removeEventListener("load", onLoad, true);
     openConsole(null, consoleOpened);
   }, true);
 }
 
 function consoleOpened(hud) {
--- a/browser/devtools/webconsole/test/browser_warn_user_about_replaced_api.js
+++ b/browser/devtools/webconsole/test/browser_warn_user_about_replaced_api.js
@@ -4,16 +4,20 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 const TEST_REPLACED_API_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-console-replaced-api.html";
 const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/testscript.js";
 
 function test() {
   waitForExplicitFinish();
 
+  const PREF = "devtools.webconsole.persistlog";
+  Services.prefs.setBoolPref(PREF, true);
+  registerCleanupFunction(() => Services.prefs.clearUserPref(PREF));
+
   // First test that the warning does not appear on a page that doesn't override
   // the window.console object.
   addTab(TEST_URI);
   browser.addEventListener("load", function onLoad() {
     browser.removeEventListener("load", onLoad, true);
     openConsole(null, testWarningNotPresent);
   }, true);
 
--- a/browser/devtools/webconsole/test/browser_webconsole_bug_595223_file_uri.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_595223_file_uri.js
@@ -32,16 +32,20 @@ function test() {
   let jar = getJar(getRootDirectory(gTestPath));
   let dir = jar ?
             extractJarToTmp(jar) :
             getChromeDir(getResolvedURI(gTestPath));
   dir.append(TEST_FILE);
 
   let uri = Services.io.newFileURI(dir);
 
+  const PREF = "devtools.webconsole.persistlog";
+  Services.prefs.setBoolPref(PREF, true);
+  registerCleanupFunction(() => Services.prefs.clearUserPref(PREF));
+
   addTab("data:text/html;charset=utf8,<p>test file URI");
   browser.addEventListener("load", function tabLoad() {
     browser.removeEventListener("load", tabLoad, true);
     openConsole(null, function(aHud) {
       hud = aHud;
       hud.jsterm.clearOutput();
       browser.addEventListener("load", tabReload, true);
       content.location = uri.spec;
--- a/browser/devtools/webconsole/test/browser_webconsole_bug_597460_filter_scroll.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_597460_filter_scroll.js
@@ -18,41 +18,36 @@ function consoleOpened(aHud) {
   }
 
   hud.setFilterState("network", false);
   hud.setFilterState("networkinfo", false);
 
   hud.ui.filterBox.value = "test message";
   hud.ui.adjustVisibilityOnSearchStringChange();
 
-  let waitForNetwork = {
-    name: "network message",
-    validatorFn: function()
-    {
-      return hud.outputNode.querySelector(".webconsole-msg-network");
-    },
-    successFn: testScroll,
-    failureFn: finishTest,
-  };
-
-  waitForSuccess({
-    name: "console messages displayed",
-    validatorFn: function()
-    {
-      return hud.outputNode.textContent.indexOf("test message 199") > -1;
-    },
-    successFn: function()
-    {
-      browser.addEventListener("load", function onReload() {
-        browser.removeEventListener("load", onReload, true);
-        waitForSuccess(waitForNetwork);
-      }, true);
-      content.location.reload();
-    },
-    failureFn: finishTest,
+  waitForMessages({
+    webconsole: hud,
+    messages: [{
+      name: "console messages displayed",
+      text: "test message 199",
+      category: CATEGORY_WEBDEV,
+      severity: SEVERITY_LOG,
+    }],
+  }).then(() => {
+    waitForMessages({
+      webconsole: hud,
+      messages: [{
+        text: "test-network.html",
+        category: CATEGORY_NETWORK,
+        severity: SEVERITY_LOG,
+        successFn: testScroll,
+        failureFn: finishTest,
+      }],
+    }).then(testScroll);
+    content.location.reload();
   });
 }
 
 function testScroll() {
   let msgNode = hud.outputNode.querySelector(".webconsole-msg-network");
   ok(msgNode.classList.contains("hud-filtered-by-type"),
     "network message is filtered by type");
   ok(msgNode.classList.contains("hud-filtered-by-string"),
@@ -69,15 +64,18 @@ function testScroll() {
 
   hud.setFilterState("network", true);
   hud.setFilterState("networkinfo", true);
 
   executeSoon(finishTest);
 }
 
 function test() {
+  const PREF = "devtools.webconsole.persistlog";
+  Services.prefs.setBoolPref(PREF, true);
+  registerCleanupFunction(() => Services.prefs.clearUserPref(PREF));
+
   addTab(TEST_URI);
   browser.addEventListener("load", function onLoad() {
     browser.removeEventListener("load", onLoad, true);
     openConsole(null, consoleOpened);
   }, true);
 }
-
--- a/browser/devtools/webconsole/test/browser_webconsole_bug_632817.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_632817.js
@@ -11,16 +11,20 @@ const TEST_IMG = "http://example.com/bro
 const TEST_DATA_JSON_CONTENT =
   '{ id: "test JSON data", myArray: [ "foo", "bar", "baz", "biff" ] }';
 
 let lastRequest = null;
 let requestCallback = null;
 
 function test()
 {
+  const PREF = "devtools.webconsole.persistlog";
+  Services.prefs.setBoolPref(PREF, true);
+  registerCleanupFunction(() => Services.prefs.clearUserPref(PREF));
+
   addTab("data:text/html;charset=utf-8,Web Console network logging tests");
 
   browser.addEventListener("load", function onLoad() {
     browser.removeEventListener("load", onLoad, true);
 
     openConsole(null, function(aHud) {
       hud = aHud;
 
@@ -111,29 +115,27 @@ function testXhrPost()
 
 function testFormSubmission()
 {
   // Start the form submission test. As the form is submitted, the page is
   // loaded again. Bind to the load event to catch when this is done.
   requestCallback = function() {
     ok(lastRequest, "testFormSubmission() was logged");
     is(lastRequest.request.method, "POST", "Method is correct");
-    waitForSuccess({
-      name: "all network request displayed",
-      validatorFn: function() {
-        return hud.outputNode.querySelectorAll(".webconsole-msg-network")
-               .length == 5;
-      },
-      successFn: testLiveFilteringOnSearchStrings,
-      failureFn: function() {
-        let nodes = hud.outputNode.querySelectorAll(".webconsole-msg-network");
-        info("nodes: " + nodes.length + "\n");
-        finishTest();
-      },
-    });
+
+    // There should be 3 network requests pointing to the HTML file.
+    waitForMessages({
+      webconsole: hud,
+      messages: [{
+        text: "test-network-request.html",
+        category: CATEGORY_NETWORK,
+        severity: SEVERITY_LOG,
+        count: 3,
+      }],
+    }).then(testLiveFilteringOnSearchStrings);
   };
 
   let form = content.document.querySelector("form");
   ok(form, "we have the HTML form");
   form.submit();
 }
 
 function testLiveFilteringOnSearchStrings() {
--- a/browser/devtools/webconsole/webconsole.js
+++ b/browser/devtools/webconsole/webconsole.js
@@ -173,16 +173,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";
 
 /**
  * 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.
@@ -366,16 +367,30 @@ WebConsoleFrame.prototype = {
 
     this.webConsoleClient.setPreferences(preferences, function(aResponse) {
       if (!aResponse.error) {
         this._saveRequestAndResponseBodies = newValue;
       }
     }.bind(this));
   },
 
+  _persistLog: null,
+
+  /**
+   * Getter for the persistent logging preference. This value is cached per
+   * instance to avoid reading the pref too often.
+   * @type boolean
+   */
+  get persistLog() {
+    if (this._persistLog === null) {
+      this._persistLog = Services.prefs.getBoolPref(PREF_PERSISTLOG);
+    }
+    return this._persistLog;
+  },
+
   /**
    * Initialize the WebConsoleFrame instance.
    * @return object
    *         A Promise object for the initialization.
    */
   init: function WCF_init()
   {
     this._initUI();
@@ -4900,16 +4915,20 @@ WebConsoleConnectionProxy.prototype = {
    *        The message received from the server.
    */
   _onTabNavigated: function WCCP__onTabNavigated(aEvent, aPacket)
   {
     if (!this.owner) {
       return;
     }
 
+    if (aEvent == "will-navigate" && !this.owner.persistLog) {
+      this.owner.jsterm.clearOutput();
+    }
+
     if (aPacket.url) {
       this.owner.onLocationChange(aPacket.url, aPacket.title);
     }
 
     if (aEvent == "navigate" && !aPacket.nativeConsoleAPI) {
       this.owner.logWarningAboutReplacedAPI();
     }
   },
--- a/browser/locales/en-US/chrome/browser/devtools/toolbox.dtd
+++ b/browser/locales/en-US/chrome/browser/devtools/toolbox.dtd
@@ -39,8 +39,19 @@
 <!ENTITY options.selectAdditionalTools.label  "Developer Tools installed by add-ons">
 
 <!-- LOCALIZATION NOTE (options.selectDevToolsTheme.label): This is the label for
   -  the heading of the radiobox corresponding to the theme of the developer
   -  tools. -->
 <!ENTITY options.selectDevToolsTheme.label   "Choose DevTools theme:">
 <!ENTITY options.darkTheme.label             "Dark theme">
 <!ENTITY options.lightTheme.label            "Light theme">
+
+<!-- LOCALIZATION NOTE (options.webconsole.label): This is the label for the
+  -  heading of the group of Web Console preferences in the options panel. -->
+<!ENTITY options.webconsole.label            "Web Console">
+
+<!-- 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">