Merge mozilla-central and mozilla-inbound
authorMarco Bonardo <mbonardo@mozilla.com>
Sat, 14 Jan 2012 10:07:48 +0100
changeset 85716 c7511462f15fad0e0e7955e5073fa13c9737c771
parent 85715 754c87545661b2ee5bea6ad32b3982ee9b7c07fa (current diff)
parent 85671 27a7f197c6fc99b941d061490dd3bccd2ae5dadf (diff)
child 85717 256c575375073285c36b73eca8db37142a2322ba
push id805
push userakeybl@mozilla.com
push dateWed, 01 Feb 2012 18:17:35 +0000
treeherdermozilla-aurora@6fb3bf232436 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone12.0a1
Merge mozilla-central and mozilla-inbound
--- a/accessible/tests/mochitest/events/test_focus_browserui.xul
+++ b/accessible/tests/mochitest/events/test_focus_browserui.xul
@@ -17,16 +17,18 @@
           src="../role.js"></script>
   <script type="application/javascript"
           src="../states.js"></script>
   <script type="application/javascript"
           src="../events.js"></script>
 
   <script type="application/javascript">
   <![CDATA[
+    Components.utils.import("resource://gre/modules/Services.jsm");
+
     ////////////////////////////////////////////////////////////////////////////
     // Helpers
 
     function tabBrowser()
     {
       return gBrowserWnd.gBrowser;
     }
 
@@ -92,18 +94,18 @@
     // Testing
 
     var gInputDocURI = "data:text/html,<html><input id='input'></html>";
     var gButtonDocURI = "data:text/html,<html><input id='input' type='button' value='button'></html>";
 
     var gBrowserWnd = null;
     function loadBrowser()
     {
-      gBrowserWnd = window.openDialog("chrome://browser/content/", "_blank",
-                                      "chrome,all,dialog=no", gInputDocURI);
+      gBrowserWnd = window.openDialog(Services.prefs.getCharPref("browser.chromeURL"),
+                                      "_blank", "chrome,all,dialog=no", gInputDocURI);
 
       addA11yLoadEvent(startTests, gBrowserWnd);
     }
 
     function startTests()
     {
       // Wait for tab load.
       var browser = gBrowserWnd.gBrowser.selectedBrowser;
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -867,17 +867,17 @@ pref("browser.zoom.siteSpecific", true);
 
 // Whether or not to update background tabs to the current zoom level.
 pref("browser.zoom.updateBackgroundTabs", true);
 
 // The breakpad report server to link to in about:crashes
 pref("breakpad.reportURL", "http://crash-stats.mozilla.com/report/index/");
 
 // base URL for web-based support pages
-pref("app.support.baseURL", "http://support.mozilla.com/1/firefox/%VERSION%/%OS%/%LOCALE%/");
+pref("app.support.baseURL", "http://support.mozilla.org/1/firefox/%VERSION%/%OS%/%LOCALE%/");
 
 // Name of alternate about: page for certificate errors (when undefined, defaults to about:neterror)
 pref("security.alternate_certificate_error_page", "certerror");
 
 // Whether to start the private browsing mode at application startup
 pref("browser.privatebrowsing.autostart", false);
 
 // Whether we should skip prompting before starting the private browsing mode
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -3930,23 +3930,20 @@ var FullScreen = {
       this._shouldAnimate = !document.mozFullScreen;
       this.mouseoverToggle(false);
 
       // Autohide prefs
       gPrefService.addObserver("browser.fullscreen", this, false);
     }
     else {
       // The user may quit fullscreen during an animation
-      window.mozCancelAnimationFrame(this._animationHandle);
-      this._animationHandle = 0;
-      clearTimeout(this._animationTimeout);
+      this._cancelAnimation();
       gNavToolbox.style.marginTop = "";
       if (this._isChromeCollapsed)
         this.mouseoverToggle(true);
-      this._isAnimating = false;
       // This is needed if they use the context menu to quit fullscreen
       this._isPopupOpen = false;
 
       this.cleanup();
     }
   },
 
   exitDomFullScreen : function(e) {
@@ -3997,20 +3994,17 @@ var FullScreen = {
     gBrowser.tabContainer.addEventListener("TabClose", this.exitDomFullScreen);
     gBrowser.tabContainer.addEventListener("TabSelect", this.exitDomFullScreen);
 
     // Exit DOM full-screen mode when the browser window loses focus (ALT+TAB, etc).
     window.addEventListener("deactivate", this.exitDomFullScreen, true);
 
     // Cancel any "hide the toolbar" animation which is in progress, and make
     // the toolbar hide immediately.
-    clearInterval(this._animationInterval);
-    clearTimeout(this._animationTimeout);
-    this._isAnimating = false;
-    this._shouldAnimate = false;
+    this._cancelAnimation();
     this.mouseoverToggle(false);
 
     // If there's a full-screen toggler, remove its listeners, so that mouseover
     // the top of the screen will not cause the toolbar to re-appear.
     let fullScrToggler = document.getElementById("fullscr-toggler");
     if (fullScrToggler) {
       fullScrToggler.removeEventListener("mouseover", this._expandCallback, false);
       fullScrToggler.removeEventListener("dragenter", this._expandCallback, false);
@@ -4145,29 +4139,34 @@ var FullScreen = {
   sample: function (timeStamp) {
     const duration = 1500;
     const timePassed = timeStamp - this._animateStartTime;
     const pos = timePassed >= duration ? 1 :
                 1 - Math.pow(1 - timePassed / duration, 4);
 
     if (pos >= 1) {
       // We've animated enough
-      window.mozCancelAnimationFrame(this._animationHandle);
+      this._cancelAnimation();
       gNavToolbox.style.marginTop = "";
-      this._animationHandle = 0;
-      this._isAnimating = false;
-      this._shouldAnimate = false; // Just to make sure
       this.mouseoverToggle(false);
       return;
     }
 
     gNavToolbox.style.marginTop = (gNavToolbox.boxObject.height * pos * -1) + "px";
     this._animationHandle = window.mozRequestAnimationFrame(this);
   },
 
+  _cancelAnimation: function() {
+    window.mozCancelAnimationFrame(this._animationHandle);
+    this._animationHandle = 0;
+    clearTimeout(this._animationTimeout);
+    this._isAnimating = false;
+    this._shouldAnimate = false;
+  },
+
   cancelWarning: function(event) {
     if (!this.warningBox) {
       return;
     }
     if (this.onWarningHidden) {
       this.warningBox.removeEventListener("transitionend", this.onWarningHidden, false);
       this.onWarningHidden = null;
     }
--- a/browser/devtools/webconsole/HUDService.jsm
+++ b/browser/devtools/webconsole/HUDService.jsm
@@ -114,22 +114,39 @@ XPCOMUtils.defineLazyGetter(this, "Autoc
     Cu.import("resource:///modules/AutocompletePopup.jsm", obj);
   }
   catch (err) {
     Cu.reportError(err);
   }
   return obj.AutocompletePopup;
 });
 
+XPCOMUtils.defineLazyGetter(this, "ScratchpadManager", function () {
+  var obj = {};
+  try {
+    Cu.import("resource:///modules/devtools/scratchpad-manager.jsm", obj);
+  }
+  catch (err) {
+    Cu.reportError(err);
+  }
+  return obj.ScratchpadManager;
+});
+
 XPCOMUtils.defineLazyGetter(this, "namesAndValuesOf", function () {
   var obj = {};
   Cu.import("resource:///modules/PropertyPanel.jsm", obj);
   return obj.namesAndValuesOf;
 });
 
+XPCOMUtils.defineLazyGetter(this, "gConsoleStorage", function () {
+  let obj = {};
+  Cu.import("resource://gre/modules/ConsoleAPIStorage.jsm", obj);
+  return obj.ConsoleAPIStorage;
+});
+
 function LogFactory(aMessagePrefix)
 {
   function log(aMessage) {
     var _msg = aMessagePrefix + " " + aMessage + "\n";
     dump(_msg);
   }
   return log;
 }
@@ -1410,16 +1427,28 @@ HUD_SERVICE.prototype =
    * @returns integer
    */
   getWindowId: function HS_getWindowId(aWindow)
   {
     return aWindow.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils).outerWindowID;
   },
 
   /**
+   * Gets the ID of the inner window of this DOM window
+   *
+   * @param nsIDOMWindow aWindow
+   * @returns integer
+   */
+  getInnerWindowId: function HS_getInnerWindowId(aWindow)
+  {
+    return aWindow.QueryInterface(Ci.nsIInterfaceRequestor).
+           getInterface(Ci.nsIDOMWindowUtils).currentInnerWindowID;
+  },
+
+  /**
    * Gets the top level content window that has an outer window with
    * the given ID or returns null if no such content window exists
    *
    * @param integer aId
    * @returns nsIDOMWindow
    */
   getWindowByWindowId: function HS_getWindowByWindowId(aId)
   {
@@ -2140,17 +2169,18 @@ HUD_SERVICE.prototype =
     let node = ConsoleUtils.createMessageNode(hud.outputNode.ownerDocument,
                                               CATEGORY_WEBDEV,
                                               LEVELS[level],
                                               body,
                                               aHUDId,
                                               sourceURL,
                                               sourceLine,
                                               clipboardText,
-                                              level);
+                                              level,
+                                              aMessage.timeStamp);
 
     // Make the node bring up the property panel, to allow the user to inspect
     // the stack trace.
     if (level == "trace") {
       node._stacktrace = args;
 
       let linkNode = node.querySelector(".webconsole-msg-body");
       linkNode.classList.add("hud-clickable");
@@ -2207,28 +2237,48 @@ HUD_SERVICE.prototype =
                                               SEVERITY_WARNING, message,
                                               aHUDId);
     ConsoleUtils.outputMessageNode(node, aHUDId);
   },
 
   /**
    * Reports an error in the page source, either JavaScript or CSS.
    *
-   * @param number aCategory
-   *        The category of the message; either CATEGORY_CSS or CATEGORY_JS.
    * @param nsIScriptError aScriptError
    *        The error message to report.
    * @return void
    */
-  reportPageError: function HS_reportPageError(aCategory, aScriptError)
-  {
-    if (aCategory != CATEGORY_CSS && aCategory != CATEGORY_JS) {
-      throw Components.Exception("Unsupported category (must be one of CSS " +
-                                 "or JS)", Cr.NS_ERROR_INVALID_ARG,
-                                 Components.stack.caller);
+  reportPageError: function HS_reportPageError(aScriptError)
+  {
+    if (!aScriptError.outerWindowID) {
+      return;
+    }
+
+    let category;
+
+    switch (aScriptError.category) {
+      // We ignore chrome-originating errors as we only care about content.
+      case "XPConnect JavaScript":
+      case "component javascript":
+      case "chrome javascript":
+      case "chrome registration":
+      case "XBL":
+      case "XBL Prototype Handler":
+      case "XBL Content Sink":
+      case "xbl javascript":
+        return;
+
+      case "CSS Parser":
+      case "CSS Loader":
+        category = CATEGORY_CSS;
+        break;
+
+      default:
+        category = CATEGORY_JS;
+        break;
     }
 
     // Warnings and legacy strict errors become warnings; other types become
     // errors.
     let severity = SEVERITY_ERROR;
     if ((aScriptError.flags & aScriptError.warningFlag) ||
         (aScriptError.flags & aScriptError.strictFlag)) {
       severity = SEVERITY_WARNING;
@@ -2237,22 +2287,24 @@ HUD_SERVICE.prototype =
     let window = HUDService.getWindowByWindowId(aScriptError.outerWindowID);
     if (window) {
       let hudId = HUDService.getHudIdByWindow(window.top);
       if (hudId) {
         let outputNode = this.hudReferences[hudId].outputNode;
         let chromeDocument = outputNode.ownerDocument;
 
         let node = ConsoleUtils.createMessageNode(chromeDocument,
-                                                  aCategory,
+                                                  category,
                                                   severity,
                                                   aScriptError.errorMessage,
                                                   hudId,
                                                   aScriptError.sourceName,
-                                                  aScriptError.lineNumber);
+                                                  aScriptError.lineNumber,
+                                                  null, null,
+                                                  aScriptError.timeStamp);
 
         ConsoleUtils.outputMessageNode(node, hudId);
       }
     }
   },
 
   /**
    * Register a Gecko app's specialized ApplicationHooks object
@@ -2890,29 +2942,31 @@ HUD_SERVICE.prototype =
       }
     }
 
     let hud;
     // If there is no HUD for this tab create a new one.
     if (!hudNode) {
       // get nBox object and call new HUD
       let config = { parentNode: nBox,
-                     contentWindow: aContentWindow
+                     contentWindow: aContentWindow.top
                    };
 
       hud = new HeadsUpDisplay(config);
 
       HUDService.registerHUDReference(hud);
       let windowId = this.getWindowId(aContentWindow.top);
       this.windowIds[windowId] = hudId;
 
       hud.progressListener = new ConsoleProgressListener(hudId);
 
       _browser.webProgress.addProgressListener(hud.progressListener,
         Ci.nsIWebProgress.NOTIFY_STATE_ALL);
+
+      hud.displayCachedConsoleMessages();
     }
     else {
       hud = this.hudReferences[hudId];
       if (aContentWindow == aContentWindow.top) {
         // TODO: name change?? doesn't actually re-attach the console
         hud.reattachConsole(aContentWindow);
       }
     }
@@ -3572,16 +3626,68 @@ HeadsUpDisplay.prototype = {
       }
     }
     else {
       throw new Error("Unsupported Gecko Application");
     }
   },
 
   /**
+   * Display cached messages that may have been collected before the UI is
+   * displayed.
+   *
+   * @returns void
+   */
+  displayCachedConsoleMessages: function HUD_displayCachedConsoleMessages()
+  {
+    let innerWindowId = HUDService.getInnerWindowId(this.contentWindow);
+
+    let messages = gConsoleStorage.getEvents(innerWindowId);
+
+    let errors = {};
+    Services.console.getMessageArray(errors, {});
+
+    // Filter the errors to find only those we should display.
+    let filteredErrors = (errors.value || []).filter(function(aError) {
+      return aError instanceof Ci.nsIScriptError &&
+             aError.innerWindowID == innerWindowId;
+    }, this);
+
+    messages.push.apply(messages, filteredErrors);
+    messages.sort(function(a, b) { return a.timeStamp - b.timeStamp; });
+
+    // Turn off scrolling for the moment.
+    ConsoleUtils.scroll = false;
+    this.outputNode.hidden = true;
+
+    // Display all messages.
+    messages.forEach(function(aMessage) {
+      if (aMessage instanceof Ci.nsIScriptError) {
+        HUDService.reportPageError(aMessage);
+      }
+      else {
+        // In this case the cached message is a console message generated
+        // by the ConsoleAPI, not an nsIScriptError
+        HUDService.logConsoleAPIMessage(this.hudId, aMessage);
+      }
+    }, this);
+
+    this.outputNode.hidden = false;
+    ConsoleUtils.scroll = true;
+
+    // Scroll to bottom.
+    let numChildren = this.outputNode.childNodes.length;
+    if (numChildren && this.outputNode.clientHeight) {
+      // We also check the clientHeight to force a reflow, otherwise
+      // ensureIndexIsVisible() does not work after outputNode.hidden = false.
+      this.outputNode.ensureIndexIsVisible(numChildren - 1);
+    }
+  },
+
+  /**
    * Re-attaches a console when the contentWindow is recreated
    *
    * @param nsIDOMWindow aContentWindow
    * @returns void
    */
   reattachConsole: function HUD_reattachConsole(aContentWindow)
   {
     this.contentWindow = aContentWindow;
@@ -4003,17 +4109,17 @@ HeadsUpDisplay.prototype = {
    * @return void
    */
   makeClearConsoleButton: function HUD_makeClearConsoleButton(aToolbar)
   {
     let hudId = this.hudId;
     function HUD_clearButton_onCommand() {
       let hud = HUDService.getHudReferenceById(hudId);
       if (hud.jsterm) {
-        hud.jsterm.clearOutput();
+        hud.jsterm.clearOutput(true);
       }
       if (hud.gcliterm) {
         hud.gcliterm.clearOutput();
       }
     }
 
     let clearButton = this.makeXULNode("toolbarbutton");
     clearButton.setAttribute("label", this.getStr("btnClear"));
@@ -4521,17 +4627,17 @@ function JSTermHelper(aJSTerm)
   });
 
   /**
    * Clears the output of the JSTerm.
    */
   aJSTerm.sandbox.clear = function JSTH_clear()
   {
     aJSTerm.helperEvaluated = true;
-    aJSTerm.clearOutput();
+    aJSTerm.clearOutput(true);
   };
 
   /**
    * Returns the result of Object.keys(aObject).
    *
    * @param object aObject
    *        Object to return the property names from.
    * @returns array of string
@@ -5113,17 +5219,24 @@ JSTerm.prototype = {
     let type = aResult === null ? "null" : typeof aResult;
     if (type == "object" && aResult.constructor && aResult.constructor.name) {
       type = aResult.constructor.name;
     }
 
     return type.toLowerCase();
   },
 
-  clearOutput: function JST_clearOutput()
+  /**
+   * Clear the Web Console output.
+   *
+   * @param boolean aClearStorage
+   *        True if you want to clear the console messages storage associated to
+   *        this Web Console.
+   */
+  clearOutput: function JST_clearOutput(aClearStorage)
   {
     let hud = HUDService.getHudReferenceById(this.hudId);
     hud.cssNodes = {};
 
     let node = hud.outputNode;
     while (node.firstChild) {
       if (node.firstChild.classList &&
           node.firstChild.classList.contains("webconsole-msg-inspector")) {
@@ -5131,16 +5244,21 @@ JSTerm.prototype = {
       }
       else {
         hud.outputNode.removeChild(node.firstChild);
       }
     }
 
     hud.HUDBox.lastTimestamp = 0;
     hud.groupDepth = 0;
+
+    if (aClearStorage) {
+      let windowId = HUDService.getInnerWindowId(hud.contentWindow);
+      gConsoleStorage.clearEvents(windowId);
+    }
   },
 
   /**
    * Updates the size of the input field (command line) to fit its contents.
    *
    * @returns void
    */
   resizeInput: function JST_resizeInput()
@@ -5661,16 +5779,21 @@ FirefoxApplicationHooks.prototype = {
 //////////////////////////////////////////////////////////////////////////////
 
 /**
  * ConsoleUtils: a collection of globally used functions
  *
  */
 
 ConsoleUtils = {
+  /**
+   * Flag to turn on and off scrolling.
+   */
+  scroll: true,
+
   supString: function ConsoleUtils_supString(aString)
   {
     let str = Cc["@mozilla.org/supports-string;1"].
       createInstance(Ci.nsISupportsString);
     str.data = aString;
     return str;
   },
 
@@ -5704,16 +5827,20 @@ ConsoleUtils = {
    * Scrolls a node so that it's visible in its containing XUL "scrollbox"
    * element.
    *
    * @param nsIDOMNode aNode
    *        The node to make visible.
    * @returns void
    */
   scrollToVisible: function ConsoleUtils_scrollToVisible(aNode) {
+    if (!this.scroll) {
+      return;
+    }
+
     // Find the enclosing richlistbox node.
     let richListBoxNode = aNode.parentNode;
     while (richListBoxNode.tagName != "richlistbox") {
       richListBoxNode = richListBoxNode.parentNode;
     }
 
     // Use the scroll box object interface to ensure the element is visible.
     let boxObject = richListBoxNode.scrollBoxObject;
@@ -5741,24 +5868,28 @@ ConsoleUtils = {
    *        The line number on which the error occurred. If zero or omitted,
    *        there is no line number associated with this message.
    * @param string aClipboardText [optional]
    *        The text that should be copied to the clipboard when this node is
    *        copied. If omitted, defaults to the body text. If `aBody` is not
    *        a string, then the clipboard text must be supplied.
    * @param number aLevel [optional]
    *        The level of the console API message.
+   * @param number aTimeStamp [optional]
+   *        The timestamp to use for this message node. If omitted, the current
+   *        date and time is used.
    * @return nsIDOMNode
    *         The message node: a XUL richlistitem ready to be inserted into
    *         the Web Console output node.
    */
   createMessageNode:
   function ConsoleUtils_createMessageNode(aDocument, aCategory, aSeverity,
                                           aBody, aHUDId, aSourceURL,
-                                          aSourceLine, aClipboardText, aLevel) {
+                                          aSourceLine, aClipboardText, aLevel,
+                                          aTimeStamp) {
     if (typeof aBody != "string" && aClipboardText == null && aBody.innerText) {
       aClipboardText = aBody.innerText;
     }
 
     // Make the icon container, which is a vertical box. Its purpose is to
     // ensure that the icon stays anchored at the top of the message even for
     // long multi-line messages.
     let iconContainer = aDocument.createElementNS(XUL_NS, "vbox");
@@ -5808,17 +5939,17 @@ ConsoleUtils = {
     let repeatNode = aDocument.createElementNS(XUL_NS, "label");
     repeatNode.setAttribute("value", "1");
     repeatNode.classList.add("webconsole-msg-repeat");
     repeatContainer.appendChild(repeatNode);
 
     // Create the timestamp.
     let timestampNode = aDocument.createElementNS(XUL_NS, "label");
     timestampNode.classList.add("webconsole-timestamp");
-    let timestamp = ConsoleUtils.timestamp();
+    let timestamp = aTimeStamp || ConsoleUtils.timestamp();
     let timestampString = ConsoleUtils.timestampString(timestamp);
     timestampNode.setAttribute("value", timestampString);
 
     // Create the source location (e.g. www.example.com:6) that sits on the
     // right side of the message, if applicable.
     let locationNode;
     if (aSourceURL) {
       locationNode = this.createLocationNode(aDocument, aSourceURL,
@@ -6514,58 +6645,32 @@ CommandController.prototype = {
 HUDConsoleObserver = {
   QueryInterface: XPCOMUtils.generateQI(
     [Ci.nsIObserver]
   ),
 
   init: function HCO_init()
   {
     Services.console.registerListener(this);
-    Services.obs.addObserver(this, "xpcom-shutdown", false);
+    Services.obs.addObserver(this, "quit-application-granted", false);
   },
 
   uninit: function HCO_uninit()
   {
     Services.console.unregisterListener(this);
-    Services.obs.removeObserver(this, "xpcom-shutdown");
+    Services.obs.removeObserver(this, "quit-application-granted");
   },
 
   observe: function HCO_observe(aSubject, aTopic, aData)
   {
-    if (aTopic == "xpcom-shutdown") {
+    if (aTopic == "quit-application-granted") {
       this.uninit();
-      return;
-    }
-
-    if (!(aSubject instanceof Ci.nsIScriptError) ||
-        !aSubject.outerWindowID) {
-      return;
-    }
-
-    switch (aSubject.category) {
-      // We ignore chrome-originating errors as we only
-      // care about content.
-      case "XPConnect JavaScript":
-      case "component javascript":
-      case "chrome javascript":
-      case "chrome registration":
-      case "XBL":
-      case "XBL Prototype Handler":
-      case "XBL Content Sink":
-      case "xbl javascript":
-        return;
-
-      case "CSS Parser":
-      case "CSS Loader":
-        HUDService.reportPageError(CATEGORY_CSS, aSubject);
-        return;
-
-      default:
-        HUDService.reportPageError(CATEGORY_JS, aSubject);
-        return;
+    }
+    else if (aSubject instanceof Ci.nsIScriptError) {
+      HUDService.reportPageError(aSubject);
     }
   }
 };
 
 /**
  * A WebProgressListener that listens for location changes, to update HUDService
  * state information on page navigation.
  *
@@ -6670,31 +6775,24 @@ function appName()
   let name = APP_ID_TABLE[APP_ID];
 
   if (name){
     return name;
   }
   throw new Error("appName: UNSUPPORTED APPLICATION UUID");
 }
 
-///////////////////////////////////////////////////////////////////////////
-// HUDService (exported symbol)
-///////////////////////////////////////////////////////////////////////////
-
-try {
-  // start the HUDService
-  // This is in a try block because we want to kill everything if
-  // *any* of this fails
-  var HUDService = new HUD_SERVICE();
-}
-catch (ex) {
-  Cu.reportError("HUDService failed initialization.\n" + ex);
-  // TODO: kill anything that may have started up
-  // see bug 568665
-}
+XPCOMUtils.defineLazyGetter(this, "HUDService", function () {
+  try {
+    return new HUD_SERVICE();
+  }
+  catch (ex) {
+    Cu.reportError(ex);
+  }
+});
 
 ///////////////////////////////////////////////////////////////////////////
 // GcliTerm
 ///////////////////////////////////////////////////////////////////////////
 
 /**
  * Some commands need customization - this is how we get at them.
  */
@@ -6727,29 +6825,44 @@ function GcliTerm(aContentWindow, aHudId
   this.hintNode = aHintNode;
 
   this.createUI();
   this.createSandbox();
 
   this.show = this.show.bind(this);
   this.hide = this.hide.bind(this);
 
+  // Allow GCLI:Inputter to decide how and when to open a scratchpad window
+  let scratchpad = {
+    shouldActivate: function Scratchpad_shouldActivate(aEvent) {
+      return aEvent.shiftKey &&
+          aEvent.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_RETURN;
+    },
+    activate: function Scratchpad_activate(aValue) {
+      aValue = aValue.replace(/^\s*{\s*/, '');
+      ScratchpadManager.openScratchpad({ text: aValue });
+      return true;
+    },
+    linkText: stringBundle.GetStringFromName('scratchpad.linkText')
+  };
+
   this.opts = {
     environment: { hudId: this.hudId },
     chromeDocument: this.document,
     contentDocument: aContentWindow.document,
     jsEnvironment: {
       globalObject: unwrap(aContentWindow),
       evalFunction: this.evalInSandbox.bind(this)
     },
     inputElement: this.inputNode,
     completeElement: this.completeNode,
     inputBackgroundElement: this.inputStack,
     hintElement: this.hintNode,
     consoleWrap: aConsoleWrap,
+    scratchpad: scratchpad,
     gcliTerm: this
   };
 
   gcli._internal.commandOutputManager.addListener(this.onCommandOutput, this);
   gcli._internal.createView(this.opts);
 
   if (!commandExports) {
     commandExports = loadCommands();
--- a/browser/devtools/webconsole/gcli.jsm
+++ b/browser/devtools/webconsole/gcli.jsm
@@ -5319,17 +5319,18 @@ function getManTemplateData(command, con
 
 define('gcli/ui/domtemplate', ['require', 'exports', 'module' ], function(require, exports, module) {
 
   var obj = {};
   Components.utils.import('resource:///modules/devtools/Templater.jsm', obj);
   exports.template = obj.template;
 
 });
-define("text!gcli/commands/help.css", [], void 0);
+define("text!gcli/commands/help.css", [], "");
+
 define("text!gcli/commands/help_intro.html", [], "\n" +
   "<h2>${l10n.introHeader}</h2>\n" +
   "\n" +
   "<p>\n" +
   "</p>\n" +
   "");
 
 define("text!gcli/commands/help_list.html", [], "\n" +
@@ -5410,17 +5411,18 @@ function Console(options) {
 
   this.inputter = new Inputter({
     document: options.chromeDocument,
     requisition: options.requisition,
     inputElement: options.inputElement,
     completeElement: options.completeElement,
     completionPrompt: '',
     backgroundElement: options.backgroundElement,
-    focusManager: this.focusManager
+    focusManager: this.focusManager,
+    scratchpad: options.scratchpad
   });
 
   this.menu = new CommandMenu({
     document: options.chromeDocument,
     requisition: options.requisition,
     menuClass: 'gcliterm-menu'
   });
   this.hintElement.appendChild(this.menu.element);
@@ -5561,34 +5563,36 @@ exports.Console = Console;
 
 });
 /*
  * Copyright 2009-2011 Mozilla Foundation and contributors
  * Licensed under the New BSD license. See LICENSE.txt or:
  * http://opensource.org/licenses/BSD-3-Clause
  */
 
-define('gcli/ui/inputter', ['require', 'exports', 'module' , 'gcli/util', 'gcli/types', 'gcli/history', 'text!gcli/ui/inputter.css'], function(require, exports, module) {
+define('gcli/ui/inputter', ['require', 'exports', 'module' , 'gcli/util', 'gcli/l10n', 'gcli/types', 'gcli/history', 'text!gcli/ui/inputter.css'], function(require, exports, module) {
 var cliView = exports;
 
 
 var KeyEvent = require('gcli/util').event.KeyEvent;
 var dom = require('gcli/util').dom;
+var l10n = require('gcli/l10n');
 
 var Status = require('gcli/types').Status;
 var History = require('gcli/history').History;
 
 var inputterCss = require('text!gcli/ui/inputter.css');
 
 
 /**
  * A wrapper to take care of the functions concerning an input element
  */
 function Inputter(options) {
   this.requisition = options.requisition;
+  this.scratchpad = options.scratchpad;
 
   // Suss out where the input element is
   this.element = options.inputElement || 'gcli-input';
   if (typeof this.element === 'string') {
     this.document = options.document || document;
     var name = this.element;
     this.element = this.document.getElementById(name);
     if (!this.element) {
@@ -5860,16 +5864,24 @@ Inputter.prototype.onKeyDown = function(
     }
   }
 };
 
 /**
  * The main keyboard processing loop
  */
 Inputter.prototype.onKeyUp = function(ev) {
+  // Give the scratchpad (if enabled) a chance to activate
+  if (this.scratchpad && this.scratchpad.shouldActivate(ev)) {
+    if (this.scratchpad.activate(this.element.value)) {
+      this._setInputInternal('', true);
+    }
+    return;
+  }
+
   // RETURN does a special exec/highlight thing
   if (ev.keyCode === KeyEvent.DOM_VK_RETURN) {
     var worst = this.requisition.getStatus();
     // Deny RETURN unless the command might work
     if (worst === Status.VALID) {
       this._scrollingThroughHistory = false;
       this.history.add(this.element.value);
       this.requisition.exec();
@@ -5958,16 +5970,21 @@ Inputter.prototype.getInputState = funct
 
   // Workaround for potential XUL bug 676520 where textbox gives incorrect
   // values for its content
   if (input.typed == null) {
     input = { typed: '', cursor: { start: 0, end: 0 } };
     console.log('fixing input.typed=""', input);
   }
 
+  // Workaround for a Bug 717268 (which is really a jsdom bug)
+  if (input.cursor.start == null) {
+    input.cursor.start = 0;
+  }
+
   return input;
 };
 
 cliView.Inputter = Inputter;
 
 
 /**
  * Completer is an 'input-like' element that sits  an input element annotating
@@ -5981,16 +5998,17 @@ cliView.Inputter = Inputter;
  * - completionPrompt (optional) The prompt - defaults to '\u00bb'
  *   (double greater-than, a.k.a right guillemet). The prompt is used directly
  *   in a TextNode, so HTML entities are not allowed.
  */
 function Completer(options) {
   this.document = options.document || document;
   this.requisition = options.requisition;
   this.elementCreated = false;
+  this.scratchpad = options.scratchpad;
 
   this.element = options.completeElement || 'gcli-row-complete';
   if (typeof this.element === 'string') {
     var name = this.element;
     this.element = this.document.getElementById(name);
 
     if (!this.element) {
       this.elementCreated = true;
@@ -6074,16 +6092,21 @@ Completer.prototype.decorate = function(
     this.resizer();
   }
 };
 
 /**
  * Ensure that the completion element is the same size and the inputter element
  */
 Completer.prototype.resizer = function() {
+  // Remove this when jsdom does getBoundingClientRect(). See Bug 717269
+  if (!this.inputter.element.getBoundingClientRect) {
+    return;
+  }
+
   var rect = this.inputter.element.getBoundingClientRect();
   // -4 to line up with 1px of padding and border, top and bottom
   var height = rect.bottom - rect.top - 4;
 
   this.element.style.top = rect.top + 'px';
   this.element.style.height = height + 'px';
   this.element.style.lineHeight = height + 'px';
   this.element.style.left = rect.left + 'px';
@@ -6118,16 +6141,17 @@ Completer.prototype.update = function(in
   // which is complex due to a need to merge spans.
   // Bug 707131 questions if we couldn't simplify this to use a template.
   //
   // <span class="gcli-prompt">${completionPrompt}</span>
   // ${appendMarkupStatus()}
   // ${prefix}
   // <span class="gcli-in-ontab">${contents}</span>
   // <span class="gcli-in-closebrace" if="${unclosedJs}">}<span>
+  // <div class="gcli-in-scratchlink">${scratchLink}</div>
 
   var document = this.element.ownerDocument;
   var prompt = dom.createElement(document, 'span');
   prompt.classList.add('gcli-prompt');
   prompt.appendChild(document.createTextNode(this.completionPrompt + ' '));
   this.element.appendChild(prompt);
 
   if (input.typed.length > 0) {
@@ -6161,24 +6185,34 @@ Completer.prototype.update = function(in
     suffix.classList.add('gcli-in-ontab');
     suffix.appendChild(document.createTextNode(contents));
     this.element.appendChild(suffix);
   }
 
   // Add a grey '}' to the end of the command line when we've opened
   // with a { but haven't closed it
   var command = this.requisition.commandAssignment.getValue();
-  var unclosedJs = command && command.name === '{' &&
+  var isJsCommand = (command && command.name === '{');
+  var isUnclosedJs = isJsCommand &&
           this.requisition.getAssignment(0).getArg().suffix.indexOf('}') === -1;
-  if (unclosedJs) {
+  if (isUnclosedJs) {
     var close = dom.createElement(document, 'span');
     close.classList.add('gcli-in-closebrace');
     close.appendChild(document.createTextNode(' }'));
     this.element.appendChild(close);
   }
+
+  // Create a scratchpad link if it's a JS command and we have a function to
+  // actually perform the request
+  if (isJsCommand && this.scratchpad) {
+    var hint = dom.createElement(document, 'div');
+    hint.classList.add('gcli-in-scratchlink');
+    hint.appendChild(document.createTextNode(this.scratchpad.linkText));
+    this.element.appendChild(hint);
+  }
 };
 
 /**
  * Mark-up an array of Status values with spans
  */
 Completer.prototype.appendMarkupStatus = function(element, scores, input) {
   if (scores.length === 0) {
     return;
@@ -6281,17 +6315,18 @@ History.prototype.backward = function() 
   if (this._current < this._buffer.length - 1) {
     this._current++;
   }
   return this._buffer[this._current];
 };
 
 exports.History = History;
 
-});define("text!gcli/ui/inputter.css", [], void 0);
+});define("text!gcli/ui/inputter.css", [], "");
+
 /*
  * Copyright 2009-2011 Mozilla Foundation and contributors
  * Licensed under the New BSD license. See LICENSE.txt or:
  * http://opensource.org/licenses/BSD-3-Clause
  */
 
 define('gcli/ui/arg_fetch', ['require', 'exports', 'module' , 'gcli/util', 'gcli/types', 'gcli/ui/field', 'gcli/ui/domtemplate', 'text!gcli/ui/arg_fetch.css', 'text!gcli/ui/arg_fetch.html'], function(require, exports, module) {
 var argFetch = exports;
@@ -7414,31 +7449,33 @@ CommandMenu.prototype.onCommandChange = 
     this.hide();
   }
 };
 
 exports.CommandMenu = CommandMenu;
 
 
 });
-define("text!gcli/ui/menu.css", [], void 0);
+define("text!gcli/ui/menu.css", [], "");
+
 define("text!gcli/ui/menu.html", [], "\n" +
   "<table class=\"gcli-menu-template\" aria-live=\"polite\">\n" +
   "  <tr class=\"gcli-menu-option\" foreach=\"item in ${items}\"\n" +
   "      onclick=\"${onItemClick}\" title=\"${item.manual || ''}\">\n" +
   "    <td class=\"gcli-menu-name\">${item.name}</td>\n" +
   "    <td class=\"gcli-menu-desc\">${item.description}</td>\n" +
   "  </tr>\n" +
   "  <tr if=\"${error}\">\n" +
   "    <td class=\"gcli-menu-error\" colspan=\"2\">${error}</td>\n" +
   "  </tr>\n" +
   "</table>\n" +
   "");
 
-define("text!gcli/ui/arg_fetch.css", [], void 0);
+define("text!gcli/ui/arg_fetch.css", [], "");
+
 define("text!gcli/ui/arg_fetch.html", [], "\n" +
   "<!--\n" +
   "Template for an Assignment.\n" +
   "Evaluated each time the commandAssignment changes\n" +
   "-->\n" +
   "<div class=\"gcli-af-template\" aria-live=\"polite\">\n" +
   "  <div>\n" +
   "    <div class=\"gcli-af-cmddesc\">\n" +
--- a/browser/devtools/webconsole/test/Makefile.in
+++ b/browser/devtools/webconsole/test/Makefile.in
@@ -145,16 +145,17 @@ include $(topsrcdir)/config/rules.mk
 	browser_webconsole_bug_704295.js \
 	browser_gcli_inspect.js \
 	browser_gcli_integrate.js \
 	browser_gcli_require.js \
 	browser_gcli_web.js \
 	browser_webconsole_bug_658368_time_methods.js \
 	browser_webconsole_bug_622303_persistent_filters.js \
 	browser_webconsole_window_zombie.js \
+	browser_cached_messages.js \
 	head.js \
 	$(NULL)
 
 _BROWSER_TEST_PAGES = \
 	test-console.html \
 	test-network.html \
 	test-network-request.html \
 	test-mutation.html \
@@ -217,15 +218,16 @@ include $(topsrcdir)/config/rules.mk
 	test-bug-585956-console-trace.html \
 	test-bug-644419-log-limits.html \
 	test-bug-632275-getters.html \
 	test-bug-646025-console-file-location.html \
 	test-bug-678816-content.js \
 	test-file-location.js \
 	browser_gcli_inspect.html \
 	test-bug-658368-time-methods.html \
+	test-webconsole-error-observer.html \
 	$(NULL)
 
 libs:: $(_BROWSER_TEST_FILES)
 	$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/browser/$(relativesrcdir)
 
 libs:: $(_BROWSER_TEST_PAGES)
 	$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/browser/$(relativesrcdir)
new file mode 100644
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_cached_messages.js
@@ -0,0 +1,83 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is DevTools test code.
+ *
+ * The Initial Developer of the Original Code is Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   David Dahl <ddahl@mozilla.com>
+ *   Mihai Sucan <mihai.sucan@gmail.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-webconsole-error-observer.html";
+
+function test()
+{
+  waitForExplicitFinish();
+
+  expectUncaughtException();
+
+  gBrowser.selectedTab = gBrowser.addTab(TEST_URI);
+
+  gBrowser.selectedBrowser.addEventListener("load", function onLoad() {
+    gBrowser.selectedBrowser.removeEventListener("load", onLoad, true);
+    testOpenUI(true);
+  }, true);
+}
+
+function testOpenUI(aTestReopen)
+{
+  // test to see if the messages are
+  // displayed when the console UI is opened
+
+  HUDService.activateHUDForContext(gBrowser.selectedTab);
+  let hudId = HUDService.getHudIdByWindow(content);
+  let hud = HUDService.getHudReferenceById(hudId);
+
+  testLogEntry(hud.outputNode, "log Bazzle",
+               "Find a console log entry from before console UI is opened",
+               false, null);
+
+  testLogEntry(hud.outputNode, "error Bazzle",
+               "Find a console error entry from before console UI is opened",
+               false, null);
+
+  testLogEntry(hud.outputNode, "bazBug611032", "Found the JavaScript error");
+  testLogEntry(hud.outputNode, "cssColorBug611032", "Found the CSS error");
+
+  HUDService.deactivateHUDForContext(gBrowser.selectedTab);
+
+  if (aTestReopen) {
+    HUDService.deactivateHUDForContext(gBrowser.selectedTab);
+    executeSoon(testOpenUI);
+  } else {
+    executeSoon(finish);
+  }
+}
--- a/browser/devtools/webconsole/test/browser_gcli_web.js
+++ b/browser/devtools/webconsole/test/browser_gcli_web.js
@@ -49,39 +49,83 @@ var define = obj.gcli._internal.define;
 var console = obj.gcli._internal.console;
 var Node = Components.interfaces.nsIDOMNode;
 /*
  * Copyright 2009-2011 Mozilla Foundation and contributors
  * Licensed under the New BSD license. See LICENSE.txt or:
  * http://opensource.org/licenses/BSD-3-Clause
  */
 
-define('gclitest/suite', ['require', 'exports', 'module' , 'gcli/index', 'test/examiner', 'gclitest/testTokenize', 'gclitest/testSplit', 'gclitest/testCli', 'gclitest/testExec', 'gclitest/testKeyboard', 'gclitest/testHistory', 'gclitest/testRequire', 'gclitest/testJs'], function(require, exports, module) {
+define('gclitest/index', ['require', 'exports', 'module' , 'gclitest/suite', 'gcli/types/javascript'], function(require, exports, module) {
+
+  var examiner = require('gclitest/suite').examiner;
+  var javascript = require('gcli/types/javascript');
+
+  /**
+   * Run the tests defined in the test suite
+   * @param options How the tests are run. Properties include:
+   * - window: The browser window object to run the tests against
+   * - useFakeWindow: Use a test subset and a fake DOM to avoid a real document
+   * - detailedResultLog: console.log test passes and failures in more detail
+   */
+  exports.run = function(options) {
+    options = options || {};
+
+    if (options.useFakeWindow) {
+      // A minimum fake dom to get us through the JS tests
+      var doc = { title: 'Fake DOM' };
+      var fakeWindow = {
+        window: { document: doc },
+        document: doc
+      };
+
+      options.window = fakeWindow;
+    }
+
+    if (options.window) {
+      javascript.setGlobalObject(options.window);
+    }
+
+    examiner.run(options);
+
+    if (options.detailedResultLog) {
+      examiner.log();
+    }
+    else {
+      console.log('Completed test suite');
+    }
+  };
+});
+/*
+ * Copyright 2009-2011 Mozilla Foundation and contributors
+ * Licensed under the New BSD license. See LICENSE.txt or:
+ * http://opensource.org/licenses/BSD-3-Clause
+ */
+
+define('gclitest/suite', ['require', 'exports', 'module' , 'gcli/index', 'test/examiner', 'gclitest/testTokenize', 'gclitest/testSplit', 'gclitest/testCli', 'gclitest/testExec', 'gclitest/testKeyboard', 'gclitest/testScratchpad', 'gclitest/testHistory', 'gclitest/testRequire', 'gclitest/testJs'], function(require, exports, module) {
 
   // We need to make sure GCLI is initialized before we begin testing it
   require('gcli/index');
 
   var examiner = require('test/examiner');
 
   // It's tempting to want to unify these strings and make addSuite() do the
   // call to require(), however that breaks the build system which looks for
   // the strings passed to require
   examiner.addSuite('gclitest/testTokenize', require('gclitest/testTokenize'));
   examiner.addSuite('gclitest/testSplit', require('gclitest/testSplit'));
   examiner.addSuite('gclitest/testCli', require('gclitest/testCli'));
   examiner.addSuite('gclitest/testExec', require('gclitest/testExec'));
   examiner.addSuite('gclitest/testKeyboard', require('gclitest/testKeyboard'));
+  examiner.addSuite('gclitest/testScratchpad', require('gclitest/testScratchpad'));
   examiner.addSuite('gclitest/testHistory', require('gclitest/testHistory'));
   examiner.addSuite('gclitest/testRequire', require('gclitest/testRequire'));
   examiner.addSuite('gclitest/testJs', require('gclitest/testJs'));
 
-  examiner.run();
-  console.log('Completed test suite');
-  // examiner.log();
-
+  exports.examiner = examiner;
 });
 /*
  * Copyright 2009-2011 Mozilla Foundation and contributors
  * Licensed under the New BSD license. See LICENSE.txt or:
  * http://opensource.org/licenses/BSD-3-Clause
  */
 
 define('test/examiner', ['require', 'exports', 'module' ], function(require, exports, module) {
@@ -114,46 +158,46 @@ var stati = {
  */
 examiner.addSuite = function(name, suite) {
   examiner.suites[name] = new Suite(name, suite);
 };
 
 /**
  * Run all the tests synchronously
  */
-examiner.run = function() {
+examiner.run = function(options) {
   Object.keys(examiner.suites).forEach(function(suiteName) {
     var suite = examiner.suites[suiteName];
-    suite.run();
+    suite.run(options);
   }.bind(this));
   return examiner.suites;
 };
 
 /**
  * Run all the tests asynchronously
  */
-examiner.runAsync = function(callback) {
-  this.runAsyncInternal(0, callback);
+examiner.runAsync = function(options, callback) {
+  this.runAsyncInternal(0, options, callback);
 };
 
 /**
  * Run all the test suits asynchronously
  */
-examiner.runAsyncInternal = function(i, callback) {
+examiner.runAsyncInternal = function(i, options, callback) {
   if (i >= Object.keys(examiner.suites).length) {
     if (typeof callback === 'function') {
       callback();
     }
     return;
   }
 
   var suiteName = Object.keys(examiner.suites)[i];
-  examiner.suites[suiteName].runAsync(function() {
+  examiner.suites[suiteName].runAsync(options, function() {
     setTimeout(function() {
-      examiner.runAsyncInternal(i + 1, callback);
+      examiner.runAsyncInternal(i + 1, options, callback);
     }.bind(this), delay);
   }.bind(this));
 };
 
 /**
  *
  */
 examiner.reportToText = function() {
@@ -217,65 +261,65 @@ function Suite(suiteName, suite) {
       this.tests[testName] = test;
     }
   }.bind(this));
 }
 
 /**
  * Run all the tests in this suite synchronously
  */
-Suite.prototype.run = function() {
+Suite.prototype.run = function(options) {
   if (typeof this.suite.setup == "function") {
-    this.suite.setup();
+    this.suite.setup(options);
   }
 
   Object.keys(this.tests).forEach(function(testName) {
     var test = this.tests[testName];
-    test.run();
+    test.run(options);
   }.bind(this));
 
   if (typeof this.suite.shutdown == "function") {
-    this.suite.shutdown();
+    this.suite.shutdown(options);
   }
 };
 
 /**
  * Run all the tests in this suite asynchronously
  */
-Suite.prototype.runAsync = function(callback) {
+Suite.prototype.runAsync = function(options, callback) {
   if (typeof this.suite.setup == "function") {
     this.suite.setup();
   }
 
-  this.runAsyncInternal(0, function() {
+  this.runAsyncInternal(0, options, function() {
     if (typeof this.suite.shutdown == "function") {
       this.suite.shutdown();
     }
 
     if (typeof callback === 'function') {
       callback();
     }
   }.bind(this));
 };
 
 /**
  * Function used by the async runners that can handle async recursion.
  */
-Suite.prototype.runAsyncInternal = function(i, callback) {
+Suite.prototype.runAsyncInternal = function(i, options, callback) {
   if (i >= Object.keys(this.tests).length) {
     if (typeof callback === 'function') {
       callback();
     }
     return;
   }
 
   var testName = Object.keys(this.tests)[i];
-  this.tests[testName].runAsync(function() {
+  this.tests[testName].runAsync(options, function() {
     setTimeout(function() {
-      this.runAsyncInternal(i + 1, callback);
+      this.runAsyncInternal(i + 1, options, callback);
     }.bind(this), delay);
   }.bind(this));
 };
 
 /**
  * Create a JSON object suitable for serialization
  */
 Suite.prototype.toRemote = function() {
@@ -299,23 +343,23 @@ function Test(suite, name, func) {
 
   this.messages = [];
   this.status = stati.notrun;
 }
 
 /**
  * Run just a single test
  */
-Test.prototype.run = function() {
+Test.prototype.run = function(options) {
   currentTest = this;
   this.status = stati.executing;
   this.messages = [];
 
   try {
-    this.func.apply(this.suite);
+    this.func.apply(this.suite, [ options ]);
   }
   catch (ex) {
     this.status = stati.fail;
     this.messages.push('' + ex);
     console.error(ex);
     if (ex.stack) {
       console.error(ex.stack);
     }
@@ -326,17 +370,17 @@ Test.prototype.run = function() {
   }
 
   currentTest = null;
 };
 
 /**
  * Run all the tests in this suite asynchronously
  */
-Test.prototype.runAsync = function(callback) {
+Test.prototype.runAsync = function(options, callback) {
   setTimeout(function() {
     this.run();
     if (typeof callback === 'function') {
       callback();
     }
   }.bind(this), delay);
 };
 
@@ -1505,24 +1549,28 @@ function check(initial, action, after) {
     case KEY_DOWNS_TO:
       assignment.decrement();
       break;
   }
 
   test.is(after, requisition.toString(), initial + ' + ' + action + ' -> ' + after);
 }
 
-exports.testComplete = function() {
+exports.testComplete = function(options) {
   check('tsela', COMPLETES_TO, 'tselarr ');
   check('tsn di', COMPLETES_TO, 'tsn dif ');
   check('tsg a', COMPLETES_TO, 'tsg aaa ');
 
   check('{ wind', COMPLETES_TO, '{ window');
   check('{ window.docum', COMPLETES_TO, '{ window.document');
-  check('{ window.document.titl', COMPLETES_TO, '{ window.document.title ');
+
+  // Bug 717228: This fails under node
+  if (!options.isNode) {
+    check('{ window.document.titl', COMPLETES_TO, '{ window.document.title ');
+  }
 };
 
 exports.testIncrDecr = function() {
   check('tsu -70', KEY_UPS_TO, 'tsu -5');
   check('tsu -7', KEY_UPS_TO, 'tsu -5');
   check('tsu -6', KEY_UPS_TO, 'tsu -5');
   check('tsu -5', KEY_UPS_TO, 'tsu -3');
   check('tsu -4', KEY_UPS_TO, 'tsu -3');
@@ -1574,16 +1622,69 @@ exports.testIncrDecr = function() {
 
 });
 /*
  * Copyright 2009-2011 Mozilla Foundation and contributors
  * Licensed under the New BSD license. See LICENSE.txt or:
  * http://opensource.org/licenses/BSD-3-Clause
  */
 
+define('gclitest/testScratchpad', ['require', 'exports', 'module' , 'test/assert'], function(require, exports, module) {
+
+
+var test = require('test/assert');
+
+var origScratchpad;
+
+exports.setup = function(options) {
+  if (options.inputter) {
+    origScratchpad = options.inputter.scratchpad;
+    options.inputter.scratchpad = stubScratchpad;
+  }
+};
+
+exports.shutdown = function(options) {
+  if (options.inputter) {
+    options.inputter.scratchpad = origScratchpad;
+  }
+};
+
+var stubScratchpad = {
+  shouldActivate: function(ev) {
+    return true;
+  },
+  activatedCount: 0,
+  linkText: 'scratchpad.linkText'
+};
+stubScratchpad.activate = function(value) {
+  stubScratchpad.activatedCount++;
+  return true;
+};
+
+
+exports.testActivate = function(options) {
+  if (options.inputter) {
+    var ev = {};
+    stubScratchpad.activatedCount = 0;
+    options.inputter.onKeyUp(ev);
+    test.is(1, stubScratchpad.activatedCount, 'scratchpad is activated');
+  }
+  else {
+    console.log('Skipping scratchpad tests');
+  }
+};
+
+
+});
+/*
+ * Copyright 2009-2011 Mozilla Foundation and contributors
+ * Licensed under the New BSD license. See LICENSE.txt or:
+ * http://opensource.org/licenses/BSD-3-Clause
+ */
+
 define('gclitest/testHistory', ['require', 'exports', 'module' , 'test/assert', 'gcli/history'], function(require, exports, module) {
 
 var test = require('test/assert');
 var History = require('gcli/history').History;
 
 exports.setup = function() {
 };
 
@@ -1823,16 +1924,21 @@ function check(expStatuses, expStatus, e
       expPredict.forEach(function(p) {
         contains = predictionsHas(p);
         test.ok(contains, 'missing prediction ' + p);
       });
     }
     else if (typeof expPredict === 'number') {
       contains = true;
       test.is(assign.getPredictions().length, expPredict, 'prediction count');
+      if (assign.getPredictions().length !== expPredict) {
+        assign.getPredictions().forEach(function(prediction) {
+          console.log('actual prediction: ', prediction);
+        });
+      }
     }
     else {
       contains = predictionsHas(expPredict);
       test.ok(contains, 'missing prediction ' + expPredict);
     }
 
     if (!contains) {
       console.log('Predictions: ' + assign.getPredictions().map(function(p) {
@@ -1851,17 +1957,17 @@ exports.testBasic = function() {
 
   input('{ w');
   check('VVI', Status.ERROR, 'w', 'window');
 
   input('{ windo');
   check('VVIIIII', Status.ERROR, 'windo', 'window');
 
   input('{ window');
-  check('VVVVVVVV', Status.VALID, 'window', 0);
+  check('VVVVVVVV', Status.VALID, 'window');
 
   input('{ window.d');
   check('VVIIIIIIII', Status.ERROR, 'window.d', 'window.document');
 
   input('{ window.document.title');
   check('VVVVVVVVVVVVVVVVVVVVVVV', Status.VALID, 'window.document.title', 0);
 
   input('{ d');
@@ -1893,39 +1999,43 @@ exports.testBasic = function() {
   input('{ donteval.xxx');
   check('VVVVVVVVVVVVVV', Status.VALID, 'donteval.xxx', 0);
 };
 
 
 });
 
 function undefine() {
+  delete define.modules['gclitest/index'];
   delete define.modules['gclitest/suite'];
   delete define.modules['test/examiner'];
   delete define.modules['gclitest/testTokenize'];
   delete define.modules['test/assert'];
   delete define.modules['gclitest/testSplit'];
   delete define.modules['gclitest/commands'];
   delete define.modules['gclitest/testCli'];
   delete define.modules['gclitest/testExec'];
   delete define.modules['gclitest/testKeyboard'];
+  delete define.modules['gclitest/testScratchpad'];
   delete define.modules['gclitest/testHistory'];
   delete define.modules['gclitest/testRequire'];
   delete define.modules['gclitest/requirable'];
   delete define.modules['gclitest/testJs'];
 
+  delete define.globalDomain.modules['gclitest/index'];
   delete define.globalDomain.modules['gclitest/suite'];
   delete define.globalDomain.modules['test/examiner'];
   delete define.globalDomain.modules['gclitest/testTokenize'];
   delete define.globalDomain.modules['test/assert'];
   delete define.globalDomain.modules['gclitest/testSplit'];
   delete define.globalDomain.modules['gclitest/commands'];
   delete define.globalDomain.modules['gclitest/testCli'];
   delete define.globalDomain.modules['gclitest/testExec'];
   delete define.globalDomain.modules['gclitest/testKeyboard'];
+  delete define.globalDomain.modules['gclitest/testScratchpad'];
   delete define.globalDomain.modules['gclitest/testHistory'];
   delete define.globalDomain.modules['gclitest/testRequire'];
   delete define.globalDomain.modules['gclitest/requirable'];
   delete define.globalDomain.modules['gclitest/testJs'];
 }
 
 registerCleanupFunction(function() {
   Services.prefs.clearUserPref("devtools.gcli.enable");
@@ -1943,22 +2053,30 @@ function test() {
 }
 
 function onLoad() {
   browser.removeEventListener("DOMContentLoaded", onLoad, false);
   var failed = false;
 
   try {
     openConsole();
-    define.globalDomain.require("gclitest/index");
+
+    var gcliterm = HUDService.getHudByWindow(content).gcliterm;
+
+    var gclitest = define.globalDomain.require("gclitest/index");
+    gclitest.run({
+      window: gcliterm.document.defaultView,
+      inputter: gcliterm.opts.console.inputter,
+      requisition: gcliterm.opts.requistion
+    });
   }
   catch (ex) {
     failed = ex;
-    console.error('Test Failure', ex);
-    ok(false, '' + ex);
+    console.error("Test Failure", ex);
+    ok(false, "" + ex);
   }
   finally {
     closeConsole();
     finish();
   }
 
   if (failed) {
     throw failed;
--- a/browser/devtools/webconsole/test/browser_webconsole_bug_611795.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_611795.js
@@ -43,12 +43,15 @@ registerCleanupFunction(function() {
 function test()
 {
   Services.prefs.setBoolPref("devtools.gcli.enable", false);
   addTab(TEST_URI);
   browser.addEventListener("load", function() {
     browser.removeEventListener("load", arguments.callee, true);
 
     openConsole();
+    // Clear cached messages that are shown once the Web Console opens.
+    HUDService.getHudByWindow(content).jsterm.clearOutput(true);
+
     browser.addEventListener("load", onContentLoaded, true);
     content.location.reload();
   }, true);
 }
--- a/browser/devtools/webconsole/test/browser_webconsole_notifications.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_notifications.js
@@ -14,63 +14,61 @@
  *
  * The Original Code is DevTools test code.
  *
  * The Initial Developer of the Original Code is Mozilla Foundation.
  * Portions created by the Initial Developer are Copyright (C) 2010
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
- *  David Dahl <ddahl@mozilla.com>
+ *   David Dahl <ddahl@mozilla.com>
+ *   Mihai Sucan <mihai.sucan@gmail.com>
  *
  * Alternatively, the contents of this file may be used under the terms of
  * either the GNU General Public License Version 2 or later (the "GPL"), or
  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  * in which case the provisions of the GPL or the LGPL are applicable instead
  * of those above. If you wish to allow use of your version of this file only
  * under the terms of either the GPL or the LGPL, and not to allow others to
  * use your version of this file under the terms of the MPL, indicate your
  * decision by deleting the provisions above and replace them with the notice
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
-const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test//test-console.html";
+const TEST_URI = "data:text/html,<p>Web Console test for notifications";
 
 function test() {
   observer.init();
   addTab(TEST_URI);
-  browser.addEventListener("DOMContentLoaded", onLoad, false);
+  browser.addEventListener("load", onLoad, true);
 }
 
 function webConsoleCreated(aID)
 {
   Services.obs.removeObserver(observer, "web-console-created");
-  executeSoon(function (){
-    ok(HUDService.hudReferences[aID], "We have a hud reference");
-    let console = browser.contentWindow.wrappedJSObject.console;
-    console.log("adding a log message");
-  });
+  ok(HUDService.hudReferences[aID], "We have a hud reference");
+  content.wrappedJSObject.console.log("adding a log message");
 }
 
 function webConsoleDestroyed(aID)
 {
   Services.obs.removeObserver(observer, "web-console-destroyed");
   ok(!HUDService.hudReferences[aID], "We do not have a hud reference");
-  finishTest();
+  executeSoon(finishTest);
 }
 
 function webConsoleMessage(aID, aNodeID)
 {
   Services.obs.removeObserver(observer, "web-console-message-created");
   ok(aID, "we have a console ID");
-  ok(typeof aNodeID == 'string', "message node id is not null");
-  closeConsole();
+  is(typeof aNodeID, "string", "message node id is a string");
+  executeSoon(closeConsole);
 }
 
 let observer = {
 
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]),
 
   observe: function observe(aSubject, aTopic, aData)
   {
@@ -95,11 +93,11 @@ let observer = {
   {
     Services.obs.addObserver(this, "web-console-created", false);
     Services.obs.addObserver(this, "web-console-destroyed", false);
     Services.obs.addObserver(this, "web-console-message-created", false);
   }
 };
 
 function onLoad() {
-  browser.removeEventListener("DOMContentLoaded", onLoad, false);
+  browser.removeEventListener("load", onLoad, true);
   openConsole();
 }
new file mode 100644
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-webconsole-error-observer.html
@@ -0,0 +1,24 @@
+<!DOCTYPE HTML>
+<html dir="ltr" xml:lang="en-US" lang="en-US">
+  <head>
+    <title>WebConsoleErrorObserver test - bug 611032</title>
+    <!-- Any copyright is dedicated to the Public Domain.
+         http://creativecommons.org/publicdomain/zero/1.0/ -->
+    <script type="text/javascript">
+      console.log("log Bazzle");
+      console.info("info Bazzle");
+      console.warn("warn Bazzle");
+      console.error("error Bazzle");
+
+      var foo = {};
+      foo.bazBug611032();
+    </script>
+    <style type="text/css">
+      .foo { color: cssColorBug611032; }
+    </style>
+  </head>
+  <body>
+    <h1>WebConsoleErrorObserver test</h1>
+  </body>
+</html>
+
--- a/browser/locales/en-US/chrome/browser/devtools/gcli.properties
+++ b/browser/locales/en-US/chrome/browser/devtools/gcli.properties
@@ -15,17 +15,17 @@ canonDescNone=(No description)
 # of JavaScript like traditional developer tool command lines. This describes
 # the '{' command.
 cliEvalJavascript=Enter JavaScript directly
 
 # LOCALIZATION NOTE (fieldSelectionSelect): When a command has a parameter
 # that has a number of pre-defined options the user interface presents these
 # in a drop-down menu, where the first 'option' is an indicator that a
 # selection should be made. This string describes that first option.
-fieldSelectionSelect=Select a %S …
+fieldSelectionSelect=Select a %S…
 
 # LOCALIZATION NOTE (fieldArrayAdd): When a command has a parameter that can
 # be repeated a number of times (e.g. like the 'cat a.txt b.txt' command) the
 # user interface presents buttons to add and remove arguments. This string is
 # used to add arguments.
 fieldArrayAdd=Add
 
 # LOCALIZATION NOTE (fieldArrayDel): When a command has a parameter that can
--- a/browser/locales/en-US/chrome/browser/devtools/webconsole.properties
+++ b/browser/locales/en-US/chrome/browser/devtools/webconsole.properties
@@ -141,16 +141,21 @@ webConsolePositionBelow=Below
 webConsolePositionWindow=Window
 
 # LOCALIZATION NOTE (webConsoleWindowTitleAndURL): The Web Console floating
 # panel title, followed by the web page URL.
 # For RTL languages you need to set the LRM in the string to give the URL
 # the correct direction.
 webConsoleWindowTitleAndURL=Web Console - %S
 
+# LOCALIZATION NOTE (scratchpad.linkText):
+# The text used in the right hand side of the web console command line when
+# Javascript is being entered, to indicate how to jump into scratchpad mode
+scratchpad.linkText=Shift+RETURN - Open in Scratchpad
+
 # LOCALIZATION NOTE (Autocomplete.label):
 # The autocomplete popup panel label/title.
 Autocomplete.label=Autocomplete popup
 
 # LOCALIZATION NOTE (stacktrace.anonymousFunction):
 # This string is used to display JavaScript functions that have no given name -
 # they are said to be anonymous. See stacktrace.outputMessage.
 stacktrace.anonymousFunction=<anonymous>
--- a/browser/themes/gnomestripe/devtools/gcli.css
+++ b/browser/themes/gnomestripe/devtools/gcli.css
@@ -168,31 +168,29 @@
 .gcli-out-shortcut {
   font-family: "DejaVu Sans Mono", monospace;
 }
 
 /* From: $GCLI/lib/gcli/ui/arg_fetch.css */
 
 .gcli-argfetch {
   width: 100%;
-  box-sizing: border-box;
   -moz-box-sizing: border-box;
 }
 
 .gcli-af-cmddesc {
   font-weight: bold;
   text-align: center;
   margin-bottom: 5px;
   padding: 3px 10px 0;
 }
 
 .gcli-af-params {
   padding: 0 10px;
   width: 100%;
-  box-sizing: border-box;
   -moz-box-sizing: border-box;
 }
 
 .gcli-af-paramname {
   text-align: right;
   font-size: 90%;
 }
 
@@ -302,16 +300,23 @@
   color: #999;
 }
 
 .gcli-prompt {
   color: #66F;
   font-weight: bold;
 }
 
+.gcli-in-scratchlink {
+  float: right;
+  font-size: 85%;
+  color: #888;
+  padding-right: 10px;
+}
+
 /* From: $GCLI/lib/gcli/commands/help.css */
 
 .gcli-help-name {
   text-align: end;
 }
 
 .gcli-help-arrow {
   font-size: 70%;
--- a/browser/themes/pinstripe/devtools/gcli.css
+++ b/browser/themes/pinstripe/devtools/gcli.css
@@ -172,31 +172,29 @@
 .gcli-out-shortcut {
   font-family: Menlo, Monaco, monospace;
 }
 
 /* From: $GCLI/lib/gcli/ui/arg_fetch.css */
 
 .gcli-argfetch {
   width: 100%;
-  box-sizing: border-box;
   -moz-box-sizing: border-box;
 }
 
 .gcli-af-cmddesc {
   font-weight: bold;
   text-align: center;
   margin-bottom: 5px;
   padding: 3px 10px 0;
 }
 
 .gcli-af-params {
   padding: 0 10px;
   width: 100%;
-  box-sizing: border-box;
   -moz-box-sizing: border-box;
 }
 
 .gcli-af-paramname {
   text-align: right;
   font-size: 90%;
 }
 
@@ -306,16 +304,23 @@
   color: #999;
 }
 
 .gcli-prompt {
   color: #66F;
   font-weight: bold;
 }
 
+.gcli-in-scratchlink {
+  float: right;
+  font-size: 85%;
+  color: #888;
+  padding-right: 10px;
+}
+
 /* From: $GCLI/lib/gcli/commands/help.css */
 
 .gcli-help-name {
   text-align: end;
 }
 
 .gcli-help-arrow {
   font-size: 70%;
--- a/browser/themes/winstripe/devtools/gcli.css
+++ b/browser/themes/winstripe/devtools/gcli.css
@@ -168,31 +168,29 @@
 .gcli-out-shortcut {
   font-family: Consolas, Inconsolata, "Courier New", monospace;
 }
 
 /* From: $GCLI/lib/gcli/ui/arg_fetch.css */
 
 .gcli-argfetch {
   width: 100%;
-  box-sizing: border-box;
   -moz-box-sizing: border-box;
 }
 
 .gcli-af-cmddesc {
   font-weight: bold;
   text-align: center;
   margin-bottom: 5px;
   padding: 3px 10px 0;
 }
 
 .gcli-af-params {
   padding: 0 10px;
   width: 100%;
-  box-sizing: border-box;
   -moz-box-sizing: border-box;
 }
 
 .gcli-af-paramname {
   text-align: right;
   font-size: 90%;
 }
 
@@ -302,16 +300,23 @@
   color: #999;
 }
 
 .gcli-prompt {
   color: #66F;
   font-weight: bold;
 }
 
+.gcli-in-scratchlink {
+  float: right;
+  font-size: 85%;
+  color: #888;
+  padding-right: 10px;
+}
+
 /* From: $GCLI/lib/gcli/commands/help.css */
 
 .gcli-help-name {
   text-align: end;
 }
 
 .gcli-help-arrow {
   font-size: 70%;
--- a/content/xbl/src/nsXBLDocumentInfo.cpp
+++ b/content/xbl/src/nsXBLDocumentInfo.cpp
@@ -53,16 +53,17 @@
 #include "nsIPrincipal.h"
 #include "nsIScriptSecurityManager.h"
 #include "nsContentUtils.h"
 #include "nsDOMJSUtils.h"
 #include "mozilla/Services.h"
 #include "xpcpublic.h"
 #include "mozilla/scache/StartupCache.h"
 #include "mozilla/scache/StartupCacheUtils.h"
+#include "nsCCUncollectableMarker.h"
 
 using namespace mozilla::scache;
 
 static const char kXBLCachePrefix[] = "xblcache";
 
 static NS_DEFINE_CID(kDOMScriptObjectFactoryCID, NS_DOM_SCRIPT_OBJECT_FACTORY_CID);
 
 // An XBLDocumentInfo object has a special context associated with it which we can use to pre-compile 
@@ -461,16 +462,21 @@ NS_IMPL_CYCLE_COLLECTION_CLASS(nsXBLDocu
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsXBLDocumentInfo)
   if (tmp->mBindingTable) {
     tmp->mBindingTable->Enumerate(UnlinkProtoJSObjects, nsnull);
   }
   NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mDocument)
   NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mGlobalObject)
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsXBLDocumentInfo)
+  if (tmp->mDocument &&
+      nsCCUncollectableMarker::InGeneration(cb, tmp->mDocument->GetMarkedCCGeneration())) {
+    NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
+    return NS_SUCCESS_INTERRUPTED_TRAVERSE;
+  }
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mDocument)
   if (tmp->mBindingTable) {
     tmp->mBindingTable->Enumerate(TraverseProtos, &cb);
   }
   cb.NoteXPCOMChild(static_cast<nsIScriptGlobalObject*>(tmp->mGlobalObject));
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(nsXBLDocumentInfo)
--- a/dom/base/ConsoleAPI.js
+++ b/dom/base/ConsoleAPI.js
@@ -197,17 +197,18 @@ ConsoleAPI.prototype = {
     let frame = stack[1];
     let consoleEvent = {
       ID: aOuterWindowID,
       innerID: aInnerWindowID,
       level: aLevel,
       filename: frame.filename,
       lineNumber: frame.lineNumber,
       functionName: frame.functionName,
-      arguments: aArguments
+      arguments: aArguments,
+      timeStamp: Date.now(),
     };
 
     consoleEvent.wrappedJSObject = consoleEvent;
 
     ConsoleAPIStorage.recordEvent(aInnerWindowID, consoleEvent);
 
     Services.obs.notifyObservers(consoleEvent,
                                  "console-api-log-event", aOuterWindowID);
--- a/dom/base/ConsoleAPIStorage.jsm
+++ b/dom/base/ConsoleAPIStorage.jsm
@@ -116,17 +116,17 @@ var ConsoleAPIStorage = {
    * @param string aId
    *        The inner window ID for which you want to get the array of cached
    *        events.
    * @returns array
    *          The array of cached events for the given window.
    */
   getEvents: function CS_getEvents(aId)
   {
-    return _consoleStorage[aId] || [];
+    return (_consoleStorage[aId] || []).slice(0);
   },
 
   /**
    * Record an event associated with the given window ID.
    *
    * @param string aWindowID
    *        The ID of the inner window for which the event occurred.
    * @param object aEvent
--- a/dom/base/nsFocusManager.cpp
+++ b/dom/base/nsFocusManager.cpp
@@ -156,21 +156,23 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mFocusedContent)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mFirstBlurEvent)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mFirstFocusEvent)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mWindowBeingLowered)
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 nsFocusManager* nsFocusManager::sInstance = nsnull;
 bool nsFocusManager::sMouseFocusesFormControl = false;
+bool nsFocusManager::sTestMode = false;
 
 static const char* kObservedPrefs[] = {
   "accessibility.browsewithcaret",
   "accessibility.tabfocus_applies_to_xul",
   "accessibility.mouse_focuses_formcontrol",
+  "focusmanager.testmode",
   NULL
 };
 
 nsFocusManager::nsFocusManager()
 { }
 
 nsFocusManager::~nsFocusManager()
 {
@@ -193,16 +195,18 @@ nsFocusManager::Init()
 
   nsIContent::sTabFocusModelAppliesToXUL =
     Preferences::GetBool("accessibility.tabfocus_applies_to_xul",
                          nsIContent::sTabFocusModelAppliesToXUL);
 
   sMouseFocusesFormControl =
     Preferences::GetBool("accessibility.mouse_focuses_formcontrol", false);
 
+  sTestMode = Preferences::GetBool("focusmanager.testmode", false);
+
   Preferences::AddWeakObservers(fm, kObservedPrefs);
 
   nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
   if (obs) {
     obs->AddObserver(fm, "xpcom-shutdown", true);
   }
 
   return NS_OK;
@@ -230,16 +234,19 @@ nsFocusManager::Observe(nsISupports *aSu
         Preferences::GetBool("accessibility.tabfocus_applies_to_xul",
                              nsIContent::sTabFocusModelAppliesToXUL);
     }
     else if (data.EqualsLiteral("accessibility.mouse_focuses_formcontrol")) {
       sMouseFocusesFormControl =
         Preferences::GetBool("accessibility.mouse_focuses_formcontrol",
                              false);
     }
+    else if (data.EqualsLiteral("focusmanager.testmode")) {
+      sTestMode = Preferences::GetBool("focusmanager.testmode", false);
+    }
   } else if (!nsCRT::strcmp(aTopic, "xpcom-shutdown")) {
     mActiveWindow = nsnull;
     mFocusedWindow = nsnull;
     mFocusedContent = nsnull;
     mFirstBlurEvent = nsnull;
     mFirstFocusEvent = nsnull;
     mWindowBeingLowered = nsnull;
     mDelayedBlurFocusEvents.Clear();
@@ -1059,17 +1066,17 @@ nsFocusManager::NotifyFocusStateChange(n
     aContent->AsElement()->RemoveStates(eventState);
   }
 }
 
 // static
 void
 nsFocusManager::EnsureCurrentWidgetFocused()
 {
-  if (!mFocusedWindow)
+  if (!mFocusedWindow || sTestMode)
     return;
 
   // get the main child widget for the focused window and ensure that the
   // platform knows that this widget is focused.
   nsCOMPtr<nsIDocShell> docShell = mFocusedWindow->GetDocShell();
   if (docShell) {
     nsCOMPtr<nsIPresShell> presShell;
     docShell->GetPresShell(getter_AddRefs(presShell));
@@ -1565,17 +1572,17 @@ nsFocusManager::Blur(nsPIDOMWindow* aWin
 
     // if an object/plug-in/remote browser is being blurred, move the system focus
     // to the parent window, otherwise events will still get fired at the plugin.
     // But don't do this if we are blurring due to the window being lowered,
     // otherwise, the parent window can get raised again.
     if (mActiveWindow) {
       nsIFrame* contentFrame = content->GetPrimaryFrame();
       nsIObjectFrame* objectFrame = do_QueryFrame(contentFrame);
-      if (aAdjustWidgets && objectFrame) {
+      if (aAdjustWidgets && objectFrame && !sTestMode) {
         // note that the presshell's widget is being retrieved here, not the one
         // for the object frame.
         nsIViewManager* vm = presShell->GetViewManager();
         if (vm) {
           nsCOMPtr<nsIWidget> widget;
           vm->GetRootWidget(getter_AddRefs(widget));
           if (widget)
             widget->SetFocus(false);
@@ -1739,17 +1746,17 @@ nsFocusManager::Focus(nsPIDOMWindow* aWi
   // own widget and is either already focused or is about to be focused.
   nsCOMPtr<nsIWidget> objectFrameWidget;
   if (aContent) {
     nsIFrame* contentFrame = aContent->GetPrimaryFrame();
     nsIObjectFrame* objectFrame = do_QueryFrame(contentFrame);
     if (objectFrame)
       objectFrameWidget = objectFrame->GetWidget();
   }
-  if (aAdjustWidgets && !objectFrameWidget) {
+  if (aAdjustWidgets && !objectFrameWidget && !sTestMode) {
     nsIViewManager* vm = presShell->GetViewManager();
     if (vm) {
       nsCOMPtr<nsIWidget> widget;
       vm->GetRootWidget(getter_AddRefs(widget));
       if (widget)
         widget->SetFocus(false);
     }
   }
@@ -1786,17 +1793,17 @@ nsFocusManager::Focus(nsPIDOMWindow* aWi
         ScrollIntoView(presShell, aContent, aFlags);
 
       NotifyFocusStateChange(aContent, aWindow->ShouldShowFocusRing(), true);
 
       // if this is an object/plug-in/remote browser, focus its widget.  Note that we might
       // no longer be in the same document, due to the events we fired above when
       // aIsNewDocument.
       if (presShell->GetDocument() == aContent->GetDocument()) {
-        if (aAdjustWidgets && objectFrameWidget)
+        if (aAdjustWidgets && objectFrameWidget && !sTestMode)
           objectFrameWidget->SetFocus(false);
 
         // if the object being focused is a remote browser, activate remote content
         TabParent* remote = GetRemoteForContent(aContent);
         if (remote) {
           remote->Activate();
 #ifdef DEBUG_FOCUS
           printf("*Remote browser activated\n");
@@ -1828,17 +1835,18 @@ nsFocusManager::Focus(nsPIDOMWindow* aWi
       }
     }
   }
   else {
     // If the window focus event (fired above when aIsNewDocument) caused
     // the plugin not to be focusable, update the system focus by focusing
     // the root widget.
     if (aAdjustWidgets && objectFrameWidget &&
-        mFocusedWindow == aWindow && mFocusedContent == nsnull) {
+        mFocusedWindow == aWindow && mFocusedContent == nsnull &&
+        !sTestMode) {
       nsIViewManager* vm = presShell->GetViewManager();
       if (vm) {
         nsCOMPtr<nsIWidget> widget;
         vm->GetRootWidget(getter_AddRefs(widget));
         if (widget)
           widget->SetFocus(false);
       }
     }
@@ -1960,16 +1968,25 @@ nsFocusManager::ScrollIntoView(nsIPresSh
 void
 nsFocusManager::RaiseWindow(nsPIDOMWindow* aWindow)
 {
   // don't raise windows that are already raised or are in the process of
   // being lowered
   if (!aWindow || aWindow == mActiveWindow || aWindow == mWindowBeingLowered)
     return;
 
+  if (sTestMode) {
+    // In test mode, emulate the existing window being lowered and the new
+    // window being raised.
+    if (mActiveWindow)
+      WindowLowered(mActiveWindow);
+    WindowRaised(aWindow);
+    return;
+  }
+
 #if defined(XP_WIN) || defined(XP_OS2)
   // Windows would rather we focus the child widget, otherwise, the toplevel
   // widget will always end up being focused. Fortunately, focusing the child
   // widget will also have the effect of raising the window this widget is in.
   // But on other platforms, we can just focus the toplevel widget to raise
   // the window.
   nsCOMPtr<nsPIDOMWindow> childWindow;
   GetFocusedDescendant(aWindow, true, getter_AddRefs(childWindow));
--- a/dom/base/nsFocusManager.h
+++ b/dom/base/nsFocusManager.h
@@ -522,16 +522,18 @@ private:
   // A document which is handling a mouse button down event.
   // When a mouse down event process is finished, ESM sets focus to the target
   // content.  Therefore, while DOM event handlers are handling mouse down
   // events, the handlers should be able to steal focus from any elements even
   // if focus is in chrome content.  So, if this isn't NULL and the caller
   // can access the document node, the caller should succeed in moving focus.
   nsCOMPtr<nsIDocument> mMouseDownEventHandlingDocument;
 
+  static bool sTestMode;
+
   // the single focus manager
   static nsFocusManager* sInstance;
 };
 
 nsresult
 NS_NewFocusManager(nsIFocusManager** aResult);
 
 #endif
--- a/layout/base/tests/test_reftests_with_caret.html
+++ b/layout/base/tests/test_reftests_with_caret.html
@@ -9,16 +9,18 @@
   <style>
     iframe {
       width: 600px;
       height: 600px;
     }
   </style>
 <script type="text/javascript">
 
+SimpleTest.waitForExplicitFinish();
+
 var canvases = [];
 function callbackTestCanvas(canvas)
 {
   canvases.push(canvas);
 
   if (canvases.length != 2)
     return;
 
@@ -58,17 +60,17 @@ function createIframe(url,next) {
   iframe.remotePageLoaded = remotePageLoaded;
   var me = this;
   var currentIteration = 0;
   function iframeLoadCompleted() {
     var docEl = iframe.contentDocument.documentElement;
     if (docEl.className.indexOf("reftest-wait") >= 0) {
       if (currentIteration++ > MAX_ITERATIONS) {
         ok(false, "iframe load for " + url + " timed out");
-        SimpleTest.finish();
+        endTest();
       } else {
         setTimeout(iframeLoadCompleted, 10);
       }
       return;
     }
     iframe.remotePageLoaded();
     if (next) setTimeout(function(){createIframe(next,null);}, 0)
   }
@@ -77,25 +79,27 @@ function createIframe(url,next) {
 };
 
 function refTest(test,ref) {
   createIframe(test,ref);
 };
 
 var caretBlinkTime = null;
 function endTest() {
-  SimpleTest.finish();
   netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
   var prefs = Components.classes["@mozilla.org/preferences-service;1"]
                         .getService(Components.interfaces.nsIPrefBranch);
   if (caretBlinkTime !== null) {
     prefs.setIntPref("ui.caretBlinkTime", caretBlinkTime);
   } else {
     prefs.clearUserPref("ui.caretBlinkTime");
   }
+
+  // finish(), yet let the test actually end first, to be safe.
+  SimpleTest.executeSoon(SimpleTest.finish);
 }
 
 var isWindows = /WINNT/.test(SpecialPowers.OS);
 
 var tests = [
     [ 'bug389321-2.html' , 'bug389321-2-ref.html' ] ,
     [ 'bug389321-3.html' , 'bug389321-3-ref.html' ] ,
     [ 'bug482484.html'   , 'bug482484-ref.html'   ] ,
@@ -153,18 +157,16 @@ function nextTest() {
     }
     ++testIndex;
   } else {
     endTest();
   }
 }
 function runTests() {
   try {
-    SimpleTest.waitForExplicitFinish();
-
     netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
     var prefs = Components.classes["@mozilla.org/preferences-service;1"]
                           .getService(Components.interfaces.nsIPrefBranch);
     try {
       caretBlinkTime = prefs.getIntPref("ui.caretBlinkTime");
     } catch (e) {}
     prefs.setIntPref("ui.caretBlinkTime", -1);
 
--- a/modules/libpref/src/init/all.js
+++ b/modules/libpref/src/init/all.js
@@ -254,16 +254,18 @@ pref("accessibility.win32.force_disabled
 // unless this preference was set manually
 pref("ui.scrollToClick", 0);
 
 #else
 // Only on mac tabfocus is expected to handle UI widgets as well as web content
 pref("accessibility.tabfocus_applies_to_xul", true);
 #endif
 
+pref("focusmanager.testmode", false);
+
 pref("accessibility.usetexttospeech", "");
 pref("accessibility.usebrailledisplay", "");
 pref("accessibility.accesskeycausesactivation", true);
 pref("accessibility.mouse_focuses_formcontrol", false);
 
 // Type Ahead Find
 pref("accessibility.typeaheadfind", true);
 pref("accessibility.typeaheadfind.autostart", true);
--- a/toolkit/mozapps/update/updater/bspatch.cpp
+++ b/toolkit/mozapps/update/updater/bspatch.cpp
@@ -98,28 +98,25 @@ MBS_ApplyPatch(const MBSPatchHeader *hea
   if (!buf)
     return MEM_ERROR;
 
   int rv = OK;
 
   size_t r = header->cblen + header->difflen + header->extralen;
   unsigned char *wb = buf;
   while (r) {
-    size_t c = fread(wb, 1, (r > SSIZE_MAX) ? SSIZE_MAX : r, patchFile);
-    if (c < 0) {
+    const size_t count = (r > SSIZE_MAX) ? SSIZE_MAX : r;
+    size_t c = fread(wb, 1, count, patchFile);
+    if (c != count) {
       rv = READ_ERROR;
       goto end;
     }
 
     r -= c;
-
-    if (c == 0 && r) {
-      rv = UNEXPECTED_ERROR;
-      goto end;
-    }
+    wb += c;
   }
 
   {
     MBSPatchTriple *ctrlsrc = (MBSPatchTriple*) buf;
     unsigned char *diffsrc = buf + header->cblen;
     unsigned char *extrasrc = diffsrc + header->difflen;
 
     MBSPatchTriple *ctrlend = (MBSPatchTriple*) diffsrc;
--- a/toolkit/mozapps/update/updater/updater.cpp
+++ b/toolkit/mozapps/update/updater/updater.cpp
@@ -1033,30 +1033,26 @@ PatchFile::LoadSourceFile(FILE* ofile)
 
   buf = (unsigned char *) malloc(header.slen);
   if (!buf)
     return MEM_ERROR;
 
   size_t r = header.slen;
   unsigned char *rb = buf;
   while (r) {
-    size_t c = fread(rb, 1, r, ofile);
-    if (c < 0) {
+    const size_t count = mmin(SSIZE_MAX, r);
+    size_t c = fread(rb, 1, count, ofile);
+    if (c != count) {
       LOG(("LoadSourceFile: error reading destination file: " LOG_S "\n",
            mFile));
       return READ_ERROR;
     }
 
     r -= c;
     rb += c;
-
-    if (c == 0 && r) {
-      LOG(("LoadSourceFile: expected %d more bytes in destination file\n", r));
-      return UNEXPECTED_ERROR;
-    }
   }
 
   // Verify that the contents of the source file correspond to what we expect.
 
   unsigned int crc = crc32(buf, header.slen);
 
   if (crc != header.scrc32) {
     LOG(("LoadSourceFile: destination file crc %d does not match expected " \
@@ -2406,27 +2402,25 @@ GetManifestContents(const NS_tchar *mani
 
   char *mbuf = (char *) malloc(ms.st_size + 1);
   if (!mbuf)
     return NULL;
 
   size_t r = ms.st_size;
   char *rb = mbuf;
   while (r) {
-    size_t c = fread(rb, 1, mmin(SSIZE_MAX, r), mfile);
-    if (c < 0) {
+    const size_t count = mmin(SSIZE_MAX, r);
+    size_t c = fread(rb, 1, count, mfile);
+    if (c != count) {
       LOG(("GetManifestContents: error reading manifest file: " LOG_S "\n", manifest));
       return NULL;
     }
 
     r -= c;
     rb += c;
-
-    if (c == 0 && r)
-      return NULL;
   }
   mbuf[ms.st_size] = '\0';
   rb = mbuf;
 
 #ifndef XP_WIN
   return rb;
 #else
   NS_tchar *wrb = (NS_tchar *) malloc((ms.st_size + 1) * sizeof(NS_tchar));