merge m-c to fx-team
authorTim Taubert <tim.taubert@gmx.de>
Thu, 12 Jan 2012 21:26:09 +0100
changeset 84393 1798c955b58a34cd143e7dcbfca1fed7eb5b1786
parent 84335 c98283f80ae7d02a408e21169144adcf13be08e0 (current diff)
parent 84392 85c42ab04ac38bc60367c0c0030195e2d52f52f4 (diff)
child 84394 ed19305f89e68a735422c82dd81ff7c10ec3687d
push id21843
push usertim.taubert@gmx.de
push dateFri, 13 Jan 2012 10:45:53 +0000
treeherdermozilla-central@9b5f1ccdb021 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone12.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 m-c to fx-team
browser/app/profile/firefox.js
browser/devtools/webconsole/HUDService.jsm
--- 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
@@ -3902,23 +3902,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) {
@@ -3969,20 +3966,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);
@@ -4117,29 +4111,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/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