Merge MC -> JM
authorBrian Hackett <bhackett1024@gmail.com>
Mon, 29 Aug 2011 18:41:32 -0700
changeset 76163 f35ac2fed8fcb37d608b451f12f8e360ccba32c2
parent 76162 6c8e0affe03e4b3eaa7482807998a44cf8e53e6a (current diff)
parent 76028 85d9c87ef45ae7d3d64b66ac00cc682245756792 (diff)
child 76164 b61af4d7dc7ca8c1ba5138876f969e36819a333f
push id3
push userfelipc@gmail.com
push dateFri, 30 Sep 2011 20:09:13 +0000
milestone9.0a1
Merge MC -> JM
dom/base/nsDOMClassInfo.cpp
dom/base/nsJSEnvironment.cpp
js/src/xpconnect/src/xpcprivate.h
--- a/browser/base/content/inspector.js
+++ b/browser/base/content/inspector.js
@@ -535,16 +535,17 @@ Highlighter.prototype = {
  */
 var InspectorUI = {
   browser: null,
   tools: {},
   showTextNodesWithWhitespace: false,
   inspecting: false,
   treeLoaded: false,
   prefEnabledName: "devtools.inspector.enabled",
+  isDirty: false,
 
   /**
    * Toggle the inspector interface elements on or off.
    *
    * @param aEvent
    *        The event that requested the UI change. Toolbar button or menu.
    */
   toggleInspectorUI: function IUI_toggleInspectorUI(aEvent)
@@ -580,17 +581,17 @@ var InspectorUI = {
   },
 
   /**
    * Return the default selection element for the inspected document.
    */
   get defaultSelection()
   {
     let doc = this.win.document;
-    return doc.documentElement.lastElementChild;
+    return doc.documentElement ? doc.documentElement.lastElementChild : null;
   },
 
   initializeTreePanel: function IUI_initializeTreePanel()
   {
     this.treeBrowserDocument = this.treeIFrame.contentDocument;
     this.treePanelDiv = this.treeBrowserDocument.createElement("div");
     this.treeBrowserDocument.body.appendChild(this.treePanelDiv);
     this.treePanelDiv.ownerPanel = this;
@@ -774,16 +775,18 @@ var InspectorUI = {
       Cu.import("resource:///modules/domplate.jsm", this);
       this.domplateUtils.setDOM(window);
     }
 
     this.openTreePanel();
 
     this.toolbar.hidden = false;
     this.inspectCmd.setAttribute("checked", true);
+
+    gBrowser.addProgressListener(InspectorProgressListener);
   },
 
   /**
    * Initialize highlighter.
    */
   initializeHighlighter: function IUI_initializeHighlighter()
   {
     this.highlighter = new Highlighter(this.browser);
@@ -833,16 +836,18 @@ var InspectorUI = {
 
     if (this.closing || !this.win || !this.browser) {
       return;
     }
 
     this.closing = true;
     this.toolbar.hidden = true;
 
+    gBrowser.removeProgressListener(InspectorProgressListener);
+
     if (!aKeepStore) {
       InspectorStore.deleteStore(this.winID);
       this.win.removeEventListener("pagehide", this, true);
     } else {
       // Update the store before closing.
       if (this.selection) {
         InspectorStore.setValue(this.winID, "selectedNode",
           this.selection);
@@ -1310,16 +1315,18 @@ var InspectorUI = {
 
     // set the new attribute value on the original target DOM element
     this.editingContext.repObj.setAttribute(this.editingContext.attrName, 
                                               editorInput.value);
 
     // update the HTML tree attribute value
     this.editingContext.attrObj.innerHTML = editorInput.value;
 
+    this.isDirty = true;
+
     // event notification    
     Services.obs.notifyObservers(null, INSPECTOR_NOTIFICATIONS.EDITOR_SAVED, 
                                   null);
     
     this.closeEditor();
   },
 
   /**
@@ -1736,14 +1743,127 @@ var InspectorStore = {
       delete this.store[aID][aKey];
       result = true;
     }
 
     return result;
   }
 };
 
+/**
+ * The InspectorProgressListener object is an nsIWebProgressListener which
+ * handles onStateChange events for the inspected browser. If the user makes
+ * changes to the web page and he tries to navigate away, he is prompted to
+ * confirm page navigation, such that he's given the chance to prevent the loss
+ * of edits.
+ */
+var InspectorProgressListener = {
+  onStateChange:
+  function IPL_onStateChange(aProgress, aRequest, aFlag, aStatus)
+  {
+    // Remove myself if the Inspector is no longer open.
+    if (!InspectorUI.isTreePanelOpen) {
+      gBrowser.removeProgressListener(InspectorProgressListener);
+      return;
+    }
+
+    // Skip non-start states.
+    if (!(aFlag & Ci.nsIWebProgressListener.STATE_START)) {
+      return;
+    }
+
+    // If the request is about to happen in a new window, we are not concerned
+    // about the request.
+    if (aProgress.DOMWindow != InspectorUI.win) {
+      return;
+    }
+
+    if (InspectorUI.isDirty) {
+      this.showNotification(aRequest);
+    } else {
+      InspectorUI.closeInspectorUI();
+    }
+  },
+
+  /**
+   * Show an asynchronous notification which asks the user to confirm or cancel
+   * the page navigation request.
+   *
+   * @param nsIRequest aRequest
+   *        The request initiated by the user or by the page itself.
+   * @returns void
+   */
+  showNotification: function IPL_showNotification(aRequest)
+  {
+    aRequest.suspend();
+
+    let notificationBox = gBrowser.getNotificationBox(InspectorUI.browser);
+    let notification = notificationBox.
+      getNotificationWithValue("inspector-page-navigation");
+
+    if (notification) {
+      notificationBox.removeNotification(notification, true);
+    }
+
+    let cancelRequest = function onCancelRequest() {
+      if (aRequest) {
+        aRequest.cancel(Cr.NS_BINDING_ABORTED);
+        aRequest.resume(); // needed to allow the connection to be cancelled.
+        aRequest = null;
+      }
+    };
+
+    let eventCallback = function onNotificationCallback(aEvent) {
+      if (aEvent == "removed") {
+        cancelRequest();
+      }
+    };
+
+    let buttons = [
+      {
+        id: "inspector.confirmNavigationAway.buttonLeave",
+        label: InspectorUI.strings.
+          GetStringFromName("confirmNavigationAway.buttonLeave"),
+        accessKey: InspectorUI.strings.
+          GetStringFromName("confirmNavigationAway.buttonLeaveAccesskey"),
+        callback: function onButtonLeave() {
+          if (aRequest) {
+            aRequest.resume();
+            aRequest = null;
+            InspectorUI.closeInspectorUI();
+          }
+        },
+      },
+      {
+        id: "inspector.confirmNavigationAway.buttonStay",
+        label: InspectorUI.strings.
+          GetStringFromName("confirmNavigationAway.buttonStay"),
+        accessKey: InspectorUI.strings.
+          GetStringFromName("confirmNavigationAway.buttonStayAccesskey"),
+        callback: cancelRequest
+      },
+    ];
+
+    let message = InspectorUI.strings.
+      GetStringFromName("confirmNavigationAway.message");
+
+    notification = notificationBox.appendNotification(message,
+      "inspector-page-navigation", "chrome://browser/skin/Info.png",
+      notificationBox.PRIORITY_WARNING_HIGH, buttons, eventCallback);
+
+    // Make sure this not a transient notification, to avoid the automatic
+    // transient notification removal.
+    notification.persistence = -1;
+  },
+};
+
 /////////////////////////////////////////////////////////////////////////
-//// Initializors
+//// Initializers
 
 XPCOMUtils.defineLazyGetter(InspectorUI, "inspectCmd", function () {
   return document.getElementById("Tools:Inspect");
 });
+
+XPCOMUtils.defineLazyGetter(InspectorUI, "strings", function () {
+  return Services.strings.
+         createBundle("chrome://browser/locale/inspector.properties");
+});
+
--- a/browser/base/content/test/inspector/Makefile.in
+++ b/browser/base/content/test/inspector/Makefile.in
@@ -52,12 +52,13 @@ include $(topsrcdir)/config/rules.mk
 		browser_inspector_store.js \
 		browser_inspector_tab_switch.js \
 		browser_inspector_treePanel_output.js \
 		browser_inspector_treePanel_input.html \
 		browser_inspector_treePanel_result.html \
 		browser_inspector_registertools.js \
 		browser_inspector_bug_665880.js \
 		browser_inspector_editor.js \
+		browser_inspector_bug_566084_location_changed.js \
 		$(NULL)
 
 libs::	$(_BROWSER_FILES)
 	$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/browser/$(relativesrcdir)
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/inspector/browser_inspector_bug_566084_location_changed.js
@@ -0,0 +1,121 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+let notificationBox = null;
+
+function startLocationTests() {
+  ok(window.InspectorUI, "InspectorUI variable exists");
+  Services.obs.addObserver(runInspectorTests, INSPECTOR_NOTIFICATIONS.OPENED, null);
+  InspectorUI.toggleInspectorUI();
+}
+
+function runInspectorTests() {
+  Services.obs.removeObserver(runInspectorTests, INSPECTOR_NOTIFICATIONS.OPENED, null);
+
+  let para = content.document.querySelector("p");
+  ok(para, "found the paragraph element");
+  is(para.textContent, "init", "paragraph content is correct");
+
+  ok(InspectorUI.inspecting, "Inspector is highlighting");
+  ok(InspectorUI.isTreePanelOpen, "Inspector Panel is open");
+
+  InspectorUI.isDirty = true;
+
+  notificationBox = gBrowser.getNotificationBox(gBrowser.selectedBrowser);
+  notificationBox.addEventListener("AlertActive", alertActive1, false);
+
+  gBrowser.selectedBrowser.addEventListener("load", onPageLoad, true);
+
+  content.location = "data:text/html,<div>location change test 1 for " +
+    "inspector</div><p>test1</p>";
+}
+
+function alertActive1() {
+  notificationBox.removeEventListener("AlertActive", alertActive1, false);
+
+  let notification = notificationBox.
+    getNotificationWithValue("inspector-page-navigation");
+  ok(notification, "found the inspector-page-navigation notification");
+
+  // By closing the notification it is expected that page navigation is
+  // canceled.
+  executeSoon(function() {
+    notification.close();
+    locationTest2();
+  });
+}
+
+function onPageLoad() {
+  gBrowser.selectedBrowser.removeEventListener("load", onPageLoad, true);
+
+  isnot(content.location.href.indexOf("test2"), -1,
+        "page navigated to the correct location");
+
+  let para = content.document.querySelector("p");
+  ok(para, "found the paragraph element, third time");
+  is(para.textContent, "test2", "paragraph content is correct");
+
+  ok(!InspectorUI.inspecting, "Inspector is not highlighting");
+  ok(!InspectorUI.isTreePanelOpen, "Inspector Panel is not open");
+
+  testEnd();
+}
+
+function locationTest2() {
+  // Location did not change.
+  let para = content.document.querySelector("p");
+  ok(para, "found the paragraph element, second time");
+  is(para.textContent, "init", "paragraph content is correct");
+
+  ok(InspectorUI.inspecting, "Inspector is highlighting");
+  ok(InspectorUI.isTreePanelOpen, "Inspector Panel is open");
+
+  notificationBox.addEventListener("AlertActive", alertActive2, false);
+
+  content.location = "data:text/html,<div>location change test 2 for " +
+    "inspector</div><p>test2</p>";
+}
+
+function alertActive2() {
+  notificationBox.removeEventListener("AlertActive", alertActive2, false);
+
+  let notification = notificationBox.
+    getNotificationWithValue("inspector-page-navigation");
+  ok(notification, "found the inspector-page-navigation notification");
+
+  let buttons = notification.querySelectorAll("button");
+  let buttonLeave = null;
+  for (let i = 0; i < buttons.length; i++) {
+    if (buttons[i].buttonInfo.id == "inspector.confirmNavigationAway.buttonLeave") {
+      buttonLeave = buttons[i];
+      break;
+    }
+  }
+
+  ok(buttonLeave, "the Leave page button was found");
+
+  // Accept page navigation.
+  executeSoon(function(){
+    buttonLeave.doCommand();
+  });
+}
+
+function testEnd() {
+  notificationBox = null;
+  InspectorUI.isDirty = false;
+  gBrowser.removeCurrentTab();
+  executeSoon(finish);
+}
+
+function test() {
+  waitForExplicitFinish();
+
+  gBrowser.selectedTab = gBrowser.addTab();
+  gBrowser.selectedBrowser.addEventListener("load", function onBrowserLoad() {
+    gBrowser.selectedBrowser.removeEventListener("load", onBrowserLoad, true);
+    waitForFocus(startLocationTests, content);
+  }, true);
+
+  content.location = "data:text/html,<div>location change tests for " +
+    "inspector.</div><p>init</p>";
+}
--- a/browser/devtools/scratchpad/scratchpad.js
+++ b/browser/devtools/scratchpad/scratchpad.js
@@ -275,17 +275,17 @@ var Scratchpad = {
       let contentWindow = this.gBrowser.selectedBrowser.contentWindow;
 
       let scriptError = Cc["@mozilla.org/scripterror;1"].
                         createInstance(Ci.nsIScriptError2);
 
       scriptError.initWithWindowID(ex.message + "\n" + ex.stack, ex.fileName,
                                    "", ex.lineNumber, 0, scriptError.errorFlag,
                                    "content javascript",
-                                   this.getWindowId(contentWindow));
+                                   this.getInnerWindowId(contentWindow));
 
       Services.console.logMessage(scriptError);
     }
 
     return result;
   },
 
   /**
@@ -628,26 +628,26 @@ var Scratchpad = {
     this._chromeSandbox = null;
     this._contentSandbox = null;
     this._previousWindow = null;
     this._previousBrowser = null;
     this._previousLocation = null;
   },
 
   /**
-   * Gets the ID of the outer window of the given DOM window object.
+   * Gets the ID of the inner window of the given DOM window object.
    *
    * @param nsIDOMWindow aWindow
    * @return integer
-   *         the outer window ID
+   *         the inner window ID
    */
-  getWindowId: function SP_getWindowId(aWindow)
+  getInnerWindowId: function SP_getInnerWindowId(aWindow)
   {
     return aWindow.QueryInterface(Ci.nsIInterfaceRequestor).
-           getInterface(Ci.nsIDOMWindowUtils).outerWindowID;
+           getInterface(Ci.nsIDOMWindowUtils).currentInnerWindowID;
   },
 
   /**
    * The Scratchpad window DOMContentLoaded event handler. This method
    * initializes the Scratchpad window and source editor.
    *
    * @param nsIDOMEvent aEvent
    */
--- a/browser/devtools/webconsole/HUDService.jsm
+++ b/browser/devtools/webconsole/HUDService.jsm
@@ -85,17 +85,17 @@ XPCOMUtils.defineLazyGetter(this, "Prope
     Cu.reportError(err);
   }
   return obj.PropertyPanel;
 });
 
 XPCOMUtils.defineLazyGetter(this, "AutocompletePopup", function () {
   var obj = {};
   try {
-    Cu.import("resource://gre/modules/AutocompletePopup.jsm", obj);
+    Cu.import("resource:///modules/AutocompletePopup.jsm", obj);
   }
   catch (err) {
     Cu.reportError(err);
   }
   return obj.AutocompletePopup;
 });
 
 XPCOMUtils.defineLazyGetter(this, "namesAndValuesOf", function () {
--- a/browser/devtools/webconsole/test/browser/browser_webconsole_bug_651501_document_body_autocomplete.js
+++ b/browser/devtools/webconsole/test/browser/browser_webconsole_bug_651501_document_body_autocomplete.js
@@ -1,17 +1,17 @@
 /* vim:set ts=2 sw=2 sts=2 et: */
 /*
  * Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/
  */
 
 // Tests that document.body autocompletes in the web console.
 
-Cu.import("resource://gre/modules/PropertyPanel.jsm");
+Cu.import("resource:///modules/PropertyPanel.jsm");
 
 function test() {
   addTab("data:text/html,Web Console autocompletion bug in document.body");
   browser.addEventListener("load", onLoad, true);
 }
 
 var gHUD;
 
--- a/browser/devtools/webconsole/test/browser/head.js
+++ b/browser/devtools/webconsole/test/browser/head.js
@@ -31,17 +31,17 @@
  * 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 ***** */
 
-Cu.import("resource://gre/modules/HUDService.jsm");
+Cu.import("resource:///modules/HUDService.jsm");
 
 function log(aMsg)
 {
   dump("*** WebConsoleTest: " + aMsg + "\n");
 }
 
 function pprint(aObj)
 {
new file mode 100644
--- /dev/null
+++ b/browser/locales/en-US/chrome/browser/inspector.properties
@@ -0,0 +1,9 @@
+# LOCALIZATION NOTE (confirmNavigationAway): Used in the Inspector tool, when
+# the user tries to navigate away from a web page, to confirm the change of
+# page.
+confirmNavigationAway.message=Leaving this page will close the Inspector and the changes you have made will be lost.
+confirmNavigationAway.buttonLeave=Leave Page
+confirmNavigationAway.buttonLeaveAccesskey=L
+confirmNavigationAway.buttonStay=Stay on Page
+confirmNavigationAway.buttonStayAccesskey=S
+
--- a/browser/locales/jar.mn
+++ b/browser/locales/jar.mn
@@ -11,16 +11,17 @@
 #ifdef MOZ_SERVICES_SYNC
     locale/browser/aboutSyncTabs.dtd               (%chrome/browser/aboutSyncTabs.dtd)
 #endif
 *   locale/browser/browser.dtd                     (%chrome/browser/browser.dtd)
     locale/browser/baseMenuOverlay.dtd             (%chrome/browser/baseMenuOverlay.dtd)
     locale/browser/browser.properties              (%chrome/browser/browser.properties)
     locale/browser/scratchpad.properties           (%chrome/browser/scratchpad.properties)
     locale/browser/scratchpad.dtd                  (%chrome/browser/scratchpad.dtd)
+    locale/browser/inspector.properties            (%chrome/browser/inspector.properties)
     locale/browser/openLocation.dtd                (%chrome/browser/openLocation.dtd)
     locale/browser/openLocation.properties         (%chrome/browser/openLocation.properties)
 *   locale/browser/pageInfo.dtd                    (%chrome/browser/pageInfo.dtd)
     locale/browser/pageInfo.properties             (%chrome/browser/pageInfo.properties)
     locale/browser/quitDialog.properties           (%chrome/browser/quitDialog.properties)
 *   locale/browser/safeMode.dtd                    (%chrome/browser/safeMode.dtd)
     locale/browser/sanitize.dtd                    (%chrome/browser/sanitize.dtd)
     locale/browser/search.properties               (%chrome/browser/search.properties)
--- a/content/base/public/nsContentUtils.h
+++ b/content/base/public/nsContentUtils.h
@@ -738,18 +738,18 @@ public:
    *   @param aParamsLength Length of aParams.
    *   @param aURI URI of resource containing error (may be null).
    *   @param aSourceLine The text of the line that contains the error (may be
               empty).
    *   @param aLineNumber Line number within resource containing error.
    *   @param aColumnNumber Column number within resource containing error.
    *   @param aErrorFlags See nsIScriptError.
    *   @param aCategory Name of module reporting error.
-   *   @param [aWindowId=0] (Optional) The window ID of the outer window the
-   *          message originates from.
+   *   @param [aInnerWindowId=0] (Optional) The window ID of the inner window
+   *          the message originates from.
    */
   enum PropertiesFile {
     eCSS_PROPERTIES,
     eXBL_PROPERTIES,
     eXUL_PROPERTIES,
     eLAYOUT_PROPERTIES,
     eFORMS_PROPERTIES,
     ePRINTING_PROPERTIES,
@@ -764,17 +764,17 @@ public:
                                   const PRUnichar **aParams,
                                   PRUint32 aParamsLength,
                                   nsIURI* aURI,
                                   const nsAFlatString& aSourceLine,
                                   PRUint32 aLineNumber,
                                   PRUint32 aColumnNumber,
                                   PRUint32 aErrorFlags,
                                   const char *aCategory,
-                                  PRUint64 aWindowId = 0);
+                                  PRUint64 aInnerWindowId = 0);
 
   /**
    * Report a localized error message to the error console.
    *   @param aFile Properties file containing localized message.
    *   @param aMessageName Name of localized message.
    *   @param aParams Parameters to be substituted into localized message.
    *   @param aParamsLength Length of aParams.
    *   @param aURI URI of resource containing error (may be null).
--- a/content/base/public/nsIDocument.h
+++ b/content/base/public/nsIDocument.h
@@ -716,16 +716,25 @@ public:
    */
   PRUint64 OuterWindowID() const
   {
     nsPIDOMWindow *window = GetWindow();
     return window ? window->WindowID() : 0;
   }
 
   /**
+   * Return the inner window ID.
+   */
+  PRUint64 InnerWindowID()
+  {
+    nsPIDOMWindow *window = GetInnerWindow();
+    return window ? window->WindowID() : 0;
+  }
+
+  /**
    * Get the script loader for this document
    */ 
   virtual nsScriptLoader* ScriptLoader() = 0;
 
   /**
    * Add/Remove an element to the document's id and name hashes
    */
   virtual void AddToIdTable(Element* aElement, nsIAtom* aId) = 0;
--- a/content/base/src/nsContentUtils.cpp
+++ b/content/base/src/nsContentUtils.cpp
@@ -2728,17 +2728,17 @@ nsContentUtils::ReportToConsole(Properti
                                 const PRUnichar **aParams,
                                 PRUint32 aParamsLength,
                                 nsIURI* aURI,
                                 const nsAFlatString& aSourceLine,
                                 PRUint32 aLineNumber,
                                 PRUint32 aColumnNumber,
                                 PRUint32 aErrorFlags,
                                 const char *aCategory,
-                                PRUint64 aWindowId)
+                                PRUint64 aInnerWindowId)
 {
   NS_ASSERTION((aParams && aParamsLength) || (!aParams && !aParamsLength),
                "Supply either both parameters and their number or no"
                "parameters and 0.");
 
   nsresult rv;
   if (!sConsoleService) { // only need to bother null-checking here
     rv = CallGetService(NS_CONSOLESERVICE_CONTRACTID, &sConsoleService);
@@ -2762,17 +2762,18 @@ nsContentUtils::ReportToConsole(Properti
   nsCOMPtr<nsIScriptError2> errorObject =
       do_CreateInstance(NS_SCRIPTERROR_CONTRACTID, &rv);
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = errorObject->InitWithWindowID(errorText.get(),
                                      NS_ConvertUTF8toUTF16(spec).get(), // file name
                                      aSourceLine.get(),
                                      aLineNumber, aColumnNumber,
-                                     aErrorFlags, aCategory, aWindowId);
+                                     aErrorFlags, aCategory,
+                                     aInnerWindowId);
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsCOMPtr<nsIScriptError> logError = do_QueryInterface(errorObject);
   return sConsoleService->LogMessage(logError);
 }
 
 /* static */ nsresult
 nsContentUtils::ReportToConsole(PropertiesFile aFile,
@@ -2783,27 +2784,27 @@ nsContentUtils::ReportToConsole(Properti
                                 const nsAFlatString& aSourceLine,
                                 PRUint32 aLineNumber,
                                 PRUint32 aColumnNumber,
                                 PRUint32 aErrorFlags,
                                 const char *aCategory,
                                 nsIDocument* aDocument)
 {
   nsIURI* uri = aURI;
-  PRUint64 windowID = 0;
+  PRUint64 innerWindowID = 0;
   if (aDocument) {
     if (!uri) {
       uri = aDocument->GetDocumentURI();
     }
-    windowID = aDocument->OuterWindowID();
+    innerWindowID = aDocument->InnerWindowID();
   }
 
   return ReportToConsole(aFile, aMessageName, aParams, aParamsLength, uri,
                          aSourceLine, aLineNumber, aColumnNumber, aErrorFlags,
-                         aCategory, windowID);
+                         aCategory, innerWindowID);
 }
 
 PRBool
 nsContentUtils::IsChromeDoc(nsIDocument *aDocument)
 {
   if (!aDocument) {
     return PR_FALSE;
   }
--- a/content/base/src/nsEventSource.cpp
+++ b/content/base/src/nsEventSource.cpp
@@ -80,17 +80,17 @@ using namespace mozilla;
 nsEventSource::nsEventSource() :
   mStatus(PARSE_STATE_OFF),
   mFrozen(PR_FALSE),
   mErrorLoadOnRedirect(PR_FALSE),
   mGoingToDispatchAllMessages(PR_FALSE),
   mLastConvertionResult(NS_OK),
   mReadyState(nsIEventSource::CONNECTING),
   mScriptLine(0),
-  mWindowID(0)
+  mInnerWindowID(0)
 {
 }
 
 nsEventSource::~nsEventSource()
 {
   Close();
 
   if (mListenerManager) {
@@ -248,17 +248,17 @@ nsEventSource::Init(nsIPrincipal* aPrinc
     do_GetService("@mozilla.org/js/xpc/ContextStack;1");
   JSContext* cx = nsnull;
   if (stack && NS_SUCCEEDED(stack->Peek(&cx)) && cx) {
     const char *filename;
     if (nsJSUtils::GetCallingLocation(cx, &filename, &mScriptLine)) {
       mScriptFile.AssignASCII(filename);
     }
 
-    mWindowID = nsJSUtils::GetCurrentlyRunningCodeWindowID(cx);
+    mInnerWindowID = nsJSUtils::GetCurrentlyRunningCodeInnerWindowID(cx);
   }
 
   // Get the load group for the page. When requesting we'll add ourselves to it.
   // This way any pending requests will be automatically aborted if the user
   // leaves the page.
   if (mScriptContext) {
     nsCOMPtr<nsIDocument> doc =
       nsContentUtils::GetDocumentFromScriptContext(mScriptContext);
@@ -1067,17 +1067,17 @@ nsEventSource::PrintErrorOnConsole(const
   }
   NS_ENSURE_SUCCESS(rv, rv);
 
   errObj->InitWithWindowID(message.get(),
                            mScriptFile.get(),
                            nsnull,
                            mScriptLine, 0,
                            nsIScriptError::errorFlag,
-                           "Event Source", mWindowID);
+                           "Event Source", mInnerWindowID);
 
   // print the error message directly to the JS console
   nsCOMPtr<nsIScriptError> logError = do_QueryInterface(errObj);
   rv = console->LogMessage(logError);
   NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
--- a/content/base/src/nsEventSource.h
+++ b/content/base/src/nsEventSource.h
@@ -247,21 +247,21 @@ protected:
 
   PRUint32 mRedirectFlags;
   nsCOMPtr<nsIAsyncVerifyRedirectCallback> mRedirectCallback;
   nsCOMPtr<nsIChannel> mNewRedirectChannel;
 
   // Event Source owner information:
   // - the script file name
   // - source code line number where the Event Source object was constructed.
-  // - the window ID of the outer window where the script lives. Note that this
-  // may not be the same as the Event Source owner window.
+  // - the ID of the inner window where the script lives. Note that this may not
+  //   be the same as the Event Source owner window.
   // These attributes are used for error reporting.
   nsString mScriptFile;
   PRUint32 mScriptLine;
-  PRUint64 mWindowID;
+  PRUint64 mInnerWindowID;
 
 private:
   nsEventSource(const nsEventSource& x);   // prevent bad usage
   nsEventSource& operator=(const nsEventSource& x);
 };
 
 #endif // nsEventSource_h__
--- a/content/base/src/nsWebSocket.cpp
+++ b/content/base/src/nsWebSocket.cpp
@@ -352,17 +352,17 @@ nsWebSocketEstablishedConnection::PrintE
   }
   NS_ENSURE_SUCCESS(rv, rv);
 
   errorObject->InitWithWindowID
     (message.get(),
      NS_ConvertUTF8toUTF16(mOwner->GetScriptFile()).get(),
      nsnull,
      mOwner->GetScriptLine(), 0, nsIScriptError::errorFlag,
-     "Web Socket", mOwner->WindowID()
+     "Web Socket", mOwner->InnerWindowID()
      );
   
   // print the error message directly to the JS console
   nsCOMPtr<nsIScriptError> logError(do_QueryInterface(errorObject));
   rv = console->LogMessage(logError);
   NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
@@ -626,17 +626,17 @@ nsWebSocketEstablishedConnection::GetInt
 nsWebSocket::nsWebSocket() : mKeepingAlive(PR_FALSE),
                              mCheckMustKeepAlive(PR_TRUE),
                              mTriggeredCloseEvent(PR_FALSE),
                              mClientReasonCode(0),
                              mServerReasonCode(nsIWebSocketChannel::CLOSE_ABNORMAL),
                              mReadyState(nsIMozWebSocket::CONNECTING),
                              mOutgoingBufferedAmount(0),
                              mScriptLine(0),
-                             mWindowID(0)
+                             mInnerWindowID(0)
 {
   NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
 }
 
 nsWebSocket::~nsWebSocket()
 {
   NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
   if (mConnection) {
@@ -1423,17 +1423,17 @@ nsWebSocket::Init(nsIPrincipal* aPrincip
       }
 
       jsbytecode *pc = JS_GetFramePC(cx, fp);
       if (script && pc) {
         mScriptLine = JS_PCToLineNumber(cx, script, pc);
       }
     }
 
-    mWindowID = nsJSUtils::GetCurrentlyRunningCodeWindowID(cx);
+    mInnerWindowID = nsJSUtils::GetCurrentlyRunningCodeInnerWindowID(cx);
   }
 
   // parses the url
   rv = ParseURL(PromiseFlatString(aURL));
   NS_ENSURE_SUCCESS(rv, rv);
 
   // Don't allow https:// to open ws://
   if (!mSecure && 
--- a/content/base/src/nsWebSocket.h
+++ b/content/base/src/nsWebSocket.h
@@ -94,17 +94,17 @@ public:
                               PRUint8 optional_argc);
   NS_IMETHOD RemoveEventListener(const nsAString& aType,
                                  nsIDOMEventListener* aListener,
                                  PRBool aUseCapture);
 
   // Determine if preferences allow WebSocket
   static PRBool PrefEnabled();
 
-  const PRUint64 WindowID() const { return mWindowID; }
+  const PRUint64 InnerWindowID() const { return mInnerWindowID; }
   const nsCString& GetScriptFile() const { return mScriptFile; }
   const PRUint32 GetScriptLine() const { return mScriptLine; }
 
 protected:
   nsresult ParseURL(const nsString& aURL);
   nsresult EstablishConnection();
 
   nsresult CreateAndDispatchSimpleEvent(const nsString& aName);
@@ -159,21 +159,21 @@ protected:
   nsRefPtr<nsWebSocketEstablishedConnection> mConnection;
   PRUint32 mOutgoingBufferedAmount; // actually, we get this value from
                                     // mConnection when we are connected,
                                     // but we need this one after disconnecting.
 
   // Web Socket owner information:
   // - the script file name, UTF8 encoded.
   // - source code line number where the Web Socket object was constructed.
-  // - the window ID of the outer window where the script lives. Note that this 
-  // may not be the same as the Web Socket owner window.
+  // - the ID of the inner window where the script lives. Note that this may not
+  //   be the same as the Web Socket owner window.
   // These attributes are used for error reporting.
   nsCString mScriptFile;
   PRUint32 mScriptLine;
-  PRUint64 mWindowID;
+  PRUint64 mInnerWindowID;
 
 private:
   nsWebSocket(const nsWebSocket& x);   // prevent bad usage
   nsWebSocket& operator=(const nsWebSocket& x);
 };
 
 #endif
--- a/content/xml/document/src/nsXMLDocument.cpp
+++ b/content/xml/document/src/nsXMLDocument.cpp
@@ -377,18 +377,18 @@ nsXMLDocument::Load(const nsAString& aUr
       nsCOMPtr<nsIScriptError2> errorObject =
           do_CreateInstance(NS_SCRIPTERROR_CONTRACTID, &rv);
       NS_ENSURE_SUCCESS(rv, rv);
 
       rv = errorObject->InitWithWindowID(error.get(), NS_ConvertUTF8toUTF16(spec).get(),
                                          nsnull, 0, 0, nsIScriptError::warningFlag,
                                          "DOM",
                                          callingDoc ?
-                                           callingDoc->OuterWindowID() :
-                                           this->OuterWindowID());
+                                           callingDoc->InnerWindowID() :
+                                           this->InnerWindowID());
 
       NS_ENSURE_SUCCESS(rv, rv);
 
       nsCOMPtr<nsIConsoleService> consoleService =
         do_GetService(NS_CONSOLESERVICE_CONTRACTID);
       nsCOMPtr<nsIScriptError> logError = do_QueryInterface(errorObject);
       if (consoleService && logError) {
         consoleService->LogMessage(logError);
--- a/dom/base/ConsoleAPI.js
+++ b/dom/base/ConsoleAPI.js
@@ -38,59 +38,64 @@
  * ***** END LICENSE BLOCK ***** */
 
 let Cu = Components.utils;
 let Ci = Components.interfaces;
 let Cc = Components.classes;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/ConsoleAPIStorage.jsm");
 
 function ConsoleAPI() {}
 ConsoleAPI.prototype = {
 
   classID: Components.ID("{b49c18f8-3379-4fc0-8c90-d7772c1a9ff3}"),
 
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIDOMGlobalPropertyInitializer]),
 
   // nsIDOMGlobalPropertyInitializer
   init: function CA_init(aWindow) {
-    let id;
+    let outerID;
+    let innerID;
     try {
-      id = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
-                  .getInterface(Ci.nsIDOMWindowUtils)
-                  .outerWindowID;
-    } catch (ex) {
+      let windowUtils = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
+                          .getInterface(Ci.nsIDOMWindowUtils);
+
+      outerID = windowUtils.outerWindowID;
+      innerID = windowUtils.currentInnerWindowID;
+    }
+    catch (ex) {
       Cu.reportError(ex);
     }
 
     let self = this;
     let chromeObject = {
       // window.console API
       log: function CA_log() {
-        self.notifyObservers(id, "log", arguments);
+        self.notifyObservers(outerID, innerID, "log", arguments);
       },
       info: function CA_info() {
-        self.notifyObservers(id, "info", arguments);
+        self.notifyObservers(outerID, innerID, "info", arguments);
       },
       warn: function CA_warn() {
-        self.notifyObservers(id, "warn", arguments);
+        self.notifyObservers(outerID, innerID, "warn", arguments);
       },
       error: function CA_error() {
-        self.notifyObservers(id, "error", arguments);
+        self.notifyObservers(outerID, innerID, "error", arguments);
       },
       debug: function CA_debug() {
-        self.notifyObservers(id, "log", arguments);
+        self.notifyObservers(outerID, innerID, "log", arguments);
       },
       trace: function CA_trace() {
-        self.notifyObservers(id, "trace", self.getStackTrace());
+        self.notifyObservers(outerID, innerID, "trace", self.getStackTrace());
       },
       // Displays an interactive listing of all the properties of an object.
       dir: function CA_dir() {
-        self.notifyObservers(id, "dir", arguments);
+        self.notifyObservers(outerID, innerID, "dir", arguments);
       },
       __exposedProps__: {
         log: "r",
         info: "r",
         warn: "r",
         error: "r",
         debug: "r",
         trace: "r",
@@ -120,38 +125,52 @@ ConsoleAPI.prototype = {
 
     Object.defineProperties(contentObj, properties);
     Cu.makeObjectPropsNormal(contentObj);
 
     return contentObj;
   },
 
   /**
-   * Notify all observers of any console API call
+   * Notify all observers of any console API call.
+   *
+   * @param number aOuterWindowID
+   *        The outer window ID from where the message came from.
+   * @param number aInnerWindowID
+   *        The inner window ID from where the message came from.
+   * @param string aLevel
+   *        The message level.
+   * @param mixed aArguments
+   *        The arguments given to the console API call.
    **/
-  notifyObservers: function CA_notifyObservers(aID, aLevel, aArguments) {
-    if (!aID)
+  notifyObservers:
+  function CA_notifyObservers(aOuterWindowID, aInnerWindowID, aLevel, aArguments) {
+    if (!aOuterWindowID) {
       return;
+    }
 
     let stack = this.getStackTrace();
     // Skip the first frame since it contains an internal call.
     let frame = stack[1];
     let consoleEvent = {
-      ID: aID,
+      ID: aOuterWindowID,
+      innerID: aInnerWindowID,
       level: aLevel,
       filename: frame.filename,
       lineNumber: frame.lineNumber,
       functionName: frame.functionName,
       arguments: aArguments
     };
 
     consoleEvent.wrappedJSObject = consoleEvent;
 
+    ConsoleAPIStorage.recordEvent(aInnerWindowID, consoleEvent);
+
     Services.obs.notifyObservers(consoleEvent,
-                                 "console-api-log-event", aID);
+                                 "console-api-log-event", aOuterWindowID);
   },
 
   /**
    * Build the stacktrace array for the console.trace() call.
    *
    * @return array
    *         Each element is a stack frame that holds the following properties:
    *         filename, lineNumber, functionName and language.
new file mode 100644
--- /dev/null
+++ b/dom/base/ConsoleAPIStorage.jsm
@@ -0,0 +1,180 @@
+/* ***** 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 ConsoleStorageService 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>  (Original Author)
+ *
+ * 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 ***** */
+
+let Cu = Components.utils;
+let Ci = Components.interfaces;
+let Cc = Components.classes;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+const STORAGE_MAX_EVENTS = 200;
+
+XPCOMUtils.defineLazyGetter(this, "gPrivBrowsing", function () {
+  // private browsing may not be available in some Gecko Apps
+  try {
+    return Cc["@mozilla.org/privatebrowsing;1"].getService(Ci.nsIPrivateBrowsingService);
+  }
+  catch (ex) {
+    return null;
+  }
+});
+
+var EXPORTED_SYMBOLS = ["ConsoleAPIStorage"];
+
+var _consoleStorage = {};
+
+/**
+ * The ConsoleAPIStorage is meant to cache window.console API calls for later
+ * reuse by other components when needed. For example, the Web Console code can
+ * display the cached messages when it opens for the active tab.
+ *
+ * ConsoleAPI messages are stored as they come from the ConsoleAPI code, with
+ * all their properties. They are kept around until the inner window object that
+ * created the messages is destroyed. Messages are indexed by the inner window
+ * ID.
+ *
+ * Usage:
+ *    Cu.import("resource://gre/modules/ConsoleAPIStorage.jsm");
+ *
+ *    // Get the cached events array for the window you want (use the inner
+ *    // window ID).
+ *    let events = ConsoleAPIStorage.getEvents(innerWindowID);
+ *    events.forEach(function(event) { ... });
+ *
+ *    // Clear the events for the given inner window ID.
+ *    ConsoleAPIStorage.clearEvents(innerWindowID);
+ */
+var ConsoleAPIStorage = {
+
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]),
+
+  /** @private */
+  observe: function CS_observe(aSubject, aTopic, aData)
+  {
+    if (aTopic == "xpcom-shutdown") {
+      Services.obs.removeObserver(this, "xpcom-shutdown");
+      Services.obs.removeObserver(this, "inner-window-destroyed");
+      Services.obs.removeObserver(this, "memory-pressure");
+      delete _consoleStorage;
+    }
+    else if (aTopic == "inner-window-destroyed") {
+      let innerWindowID = aSubject.QueryInterface(Ci.nsISupportsPRUint64).data;
+      this.clearEvents(innerWindowID);
+    }
+    else if (aTopic == "memory-pressure") {
+      if (aData == "low-memory") {
+        this.clearEvents();
+      }
+    }
+  },
+
+  /** @private */
+  init: function CS_init()
+  {
+    Services.obs.addObserver(this, "xpcom-shutdown", false);
+    Services.obs.addObserver(this, "inner-window-destroyed", false);
+    Services.obs.addObserver(this, "memory-pressure", false);
+  },
+
+  /**
+   * Get the events array by inner window ID.
+   *
+   * @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] || [];
+  },
+
+  /**
+   * 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 JavaScript object you want to store.
+   */
+  recordEvent: function CS_recordEvent(aWindowID, aEvent)
+  {
+    let ID = parseInt(aWindowID);
+    if (isNaN(ID)) {
+      throw new Error("Invalid window ID argument");
+    }
+
+    if (gPrivBrowsing && gPrivBrowsing.privateBrowsingEnabled) {
+      return;
+    }
+
+    if (!_consoleStorage[ID]) {
+      _consoleStorage[ID] = [];
+    }
+    let storage = _consoleStorage[ID];
+    storage.push(aEvent);
+
+    // truncate
+    if (storage.length > STORAGE_MAX_EVENTS) {
+      storage.shift();
+    }
+
+    Services.obs.notifyObservers(aEvent, "console-storage-cache-event", ID);
+  },
+
+  /**
+   * Clear storage data for the given window.
+   *
+   * @param string [aId]
+   *        Optional, the inner window ID for which you want to clear the
+   *        messages. If this is not specified all of the cached messages are
+   *        cleared, from all window objects.
+   */
+  clearEvents: function CS_clearEvents(aId)
+  {
+    if (aId != null) {
+      delete _consoleStorage[aId];
+    }
+    else {
+      _consoleStorage = {};
+      Services.obs.notifyObservers(null, "console-storage-reset", null);
+    }
+  },
+};
+
+ConsoleAPIStorage.init();
--- a/dom/base/Makefile.in
+++ b/dom/base/Makefile.in
@@ -47,16 +47,19 @@ LIBRARY_NAME	= jsdombase_s
 LIBXUL_LIBRARY	= 1
 FORCE_STATIC_LIB = 1
 
 EXTRA_PP_COMPONENTS = \
 		ConsoleAPI.js \
 		ConsoleAPI.manifest \
 		$(NULL)
 
+EXTRA_JS_MODULES = ConsoleAPIStorage.jsm \
+		$(NULL)
+
 XPIDLSRCS = \
   nsIEntropyCollector.idl \
   nsIScriptChannel.idl \
   $(NULL)
 
 EXPORTS = \
   nsDOMCID.h \
   nsDOMClassInfoClasses.h \
--- a/dom/base/nsDOMClassInfo.cpp
+++ b/dom/base/nsDOMClassInfo.cpp
@@ -1697,17 +1697,17 @@ PrintWarningOnConsole(JSContext *cx, con
 
   nsresult rv = scriptError->InitWithWindowID(msg.get(),
                                               sourcefile.get(),
                                               EmptyString().get(),
                                               lineno,
                                               0, // column for error is not available
                                               nsIScriptError::warningFlag,
                                               "DOM:HTML",
-                                              nsJSUtils::GetCurrentlyRunningCodeWindowID(cx));
+                                              nsJSUtils::GetCurrentlyRunningCodeInnerWindowID(cx));
 
   if (NS_SUCCEEDED(rv)){
     nsCOMPtr<nsIScriptError> logError = do_QueryInterface(scriptError);
     consoleService->LogMessage(logError);
   }
 }
 
 static inline JSString *
--- a/dom/base/nsGlobalWindow.h
+++ b/dom/base/nsGlobalWindow.h
@@ -535,16 +535,21 @@ public:
     return mSerial;
   }
 
   static nsGlobalWindow* GetOuterWindowWithId(PRUint64 aWindowID) {
     nsGlobalWindow* outerWindow = sWindowsById->Get(aWindowID);
     return outerWindow && !outerWindow->IsInnerWindow() ? outerWindow : nsnull;
   }
 
+  static nsGlobalWindow* GetInnerWindowWithId(PRUint64 aInnerWindowID) {
+    nsGlobalWindow* innerWindow = sWindowsById->Get(aInnerWindowID);
+    return innerWindow && innerWindow->IsInnerWindow() ? innerWindow : nsnull;
+  }
+
   static bool HasIndexedDBSupport();
 
   static bool HasPerformanceSupport();
 
   static WindowByIdTable* GetWindowsTable() {
     return sWindowsById;
   }
 
--- a/dom/base/nsJSEnvironment.cpp
+++ b/dom/base/nsJSEnvironment.cpp
@@ -262,21 +262,21 @@ class ScriptErrorEvent : public nsRunnab
 {
 public:
   ScriptErrorEvent(nsIScriptGlobalObject* aScriptGlobal,
                    PRUint32 aLineNr, PRUint32 aColumn, PRUint32 aFlags,
                    const nsAString& aErrorMsg,
                    const nsAString& aFileName,
                    const nsAString& aSourceLine,
                    PRBool aDispatchEvent,
-                   PRUint64 aWindowID)
+                   PRUint64 aInnerWindowID)
   : mScriptGlobal(aScriptGlobal), mLineNr(aLineNr), mColumn(aColumn),
     mFlags(aFlags), mErrorMsg(aErrorMsg), mFileName(aFileName),
     mSourceLine(aSourceLine), mDispatchEvent(aDispatchEvent),
-    mWindowID(aWindowID)
+    mInnerWindowID(aInnerWindowID)
   {}
 
   NS_IMETHOD Run()
   {
     nsEventStatus status = nsEventStatus_eIgnore;
     // First, notify the DOM that we have a script error.
     if (mDispatchEvent) {
       nsCOMPtr<nsPIDOMWindow> win(do_QueryInterface(mScriptGlobal));
@@ -359,17 +359,17 @@ public:
           ? "chrome javascript"
           : "content javascript";
 
         nsCOMPtr<nsIScriptError2> error2(do_QueryInterface(errorObject));
         if (error2) {
           rv = error2->InitWithWindowID(mErrorMsg.get(), mFileName.get(),
                                         mSourceLine.get(),
                                         mLineNr, mColumn, mFlags,
-                                        category, mWindowID);
+                                        category, mInnerWindowID);
         } else {
           rv = errorObject->Init(mErrorMsg.get(), mFileName.get(),
                                  mSourceLine.get(),
                                  mLineNr, mColumn, mFlags,
                                  category);
         }
 
         if (NS_SUCCEEDED(rv)) {
@@ -388,17 +388,17 @@ public:
   nsCOMPtr<nsIScriptGlobalObject> mScriptGlobal;
   PRUint32                        mLineNr;
   PRUint32                        mColumn;
   PRUint32                        mFlags;
   nsString                        mErrorMsg;
   nsString                        mFileName;
   nsString                        mSourceLine;
   PRBool                          mDispatchEvent;
-  PRUint64                        mWindowID;
+  PRUint64                        mInnerWindowID;
 
   static PRBool sHandlingScriptError;
 };
 
 PRBool ScriptErrorEvent::sHandlingScriptError = PR_FALSE;
 
 // NOTE: This function could be refactored to use the above.  The only reason
 // it has not been done is that the code below only fills the error event
@@ -469,23 +469,29 @@ NS_ScriptErrorReporter(JSContext *cx,
        * event because the dom event handler would encounter
        * an OOM exception trying to process the event, and
        * then we'd need to generate a new OOM event for that
        * new OOM instance -- this isn't pretty.
        */
       nsAutoString sourceLine;
       sourceLine.Assign(reinterpret_cast<const PRUnichar*>(report->uclinebuf));
       nsCOMPtr<nsPIDOMWindow> win = do_QueryInterface(globalObject);
-      PRUint64 windowID = win ? win->WindowID() : 0;
+      PRUint64 innerWindowID = 0;
+      if (win) {
+        nsCOMPtr<nsPIDOMWindow> innerWin = win->GetCurrentInnerWindow();
+        if (innerWin) {
+          innerWindowID = innerWin->WindowID();
+        }
+      }
       nsContentUtils::AddScriptRunner(
         new ScriptErrorEvent(globalObject, report->lineno,
                              report->uctokenptr - report->uclinebuf,
                              report->flags, msg, fileName, sourceLine,
                              report->errorNumber != JSMSG_OUT_OF_MEMORY,
-                             windowID));
+                             innerWindowID));
     }
   }
 
 #ifdef DEBUG
   // Print it to stderr as well, for the benefit of those invoking
   // mozilla with -console.
   nsCAutoString error;
   error.Assign("JavaScript ");
--- a/dom/base/nsJSUtils.cpp
+++ b/dom/base/nsJSUtils.cpp
@@ -158,29 +158,29 @@ nsJSUtils::GetDynamicScriptGlobal(JSCont
 
 nsIScriptContext *
 nsJSUtils::GetDynamicScriptContext(JSContext *aContext)
 {
   return GetScriptContextFromJSContext(aContext);
 }
 
 PRUint64
-nsJSUtils::GetCurrentlyRunningCodeWindowID(JSContext *aContext)
+nsJSUtils::GetCurrentlyRunningCodeInnerWindowID(JSContext *aContext)
 {
   if (!aContext)
     return 0;
 
-  PRUint64 windowID = 0;
+  PRUint64 innerWindowID = 0;
 
   JSObject *jsGlobal = JS_GetGlobalForScopeChain(aContext);
   if (jsGlobal) {
     nsIScriptGlobalObject *scriptGlobal = GetStaticScriptGlobal(aContext,
                                                                 jsGlobal);
     if (scriptGlobal) {
       nsCOMPtr<nsPIDOMWindow> win = do_QueryInterface(scriptGlobal);
       if (win)
-        windowID = win->GetOuterWindow()->WindowID();
+        innerWindowID = win->WindowID();
     }
   }
 
-  return windowID;
+  return innerWindowID;
 }
 
--- a/dom/base/nsJSUtils.h
+++ b/dom/base/nsJSUtils.h
@@ -66,24 +66,24 @@ public:
   static nsIScriptContext *GetStaticScriptContext(JSContext* aContext,
                                                   JSObject* aObj);
 
   static nsIScriptGlobalObject *GetDynamicScriptGlobal(JSContext *aContext);
 
   static nsIScriptContext *GetDynamicScriptContext(JSContext *aContext);
 
   /**
-   * Retrieve the outer window ID based on the given JSContext.
+   * Retrieve the inner window ID based on the given JSContext.
    *
    * @param JSContext aContext
-   *        The JSContext from which you want to find the outer window ID.
+   *        The JSContext from which you want to find the inner window ID.
    *
-   * @returns PRUint64 the outer window ID.
+   * @returns PRUint64 the inner window ID.
    */
-  static PRUint64 GetCurrentlyRunningCodeWindowID(JSContext *aContext);
+  static PRUint64 GetCurrentlyRunningCodeInnerWindowID(JSContext *aContext);
 };
 
 
 class nsDependentJSString : public nsDependentString
 {
 public:
   /**
    * In the case of string ids, getting the string's chars is infallible, so
--- a/dom/tests/browser/Makefile.in
+++ b/dom/tests/browser/Makefile.in
@@ -45,14 +45,16 @@ include $(DEPTH)/config/autoconf.mk
 include $(topsrcdir)/config/rules.mk
 
 _BROWSER_FILES = \
 		browser_focus_steal_from_chrome.js \
 		browser_focus_steal_from_chrome_during_mousedown.js \
 		browser_autofocus_background.js \
 		browser_ConsoleAPITests.js \
 		test-console-api.html \
+		browser_ConsoleStorageAPITests.js \
+		browser_ConsoleStoragePBTest.js \
 		browser_autofocus_preference.js \
 		browser_popup_blocker_save_open_panel.js \
 		$(NULL)
 
 libs::	$(_BROWSER_FILES)
 	$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/browser/$(relativesrcdir)
new file mode 100644
--- /dev/null
+++ b/dom/tests/browser/browser_ConsoleStorageAPITests.js
@@ -0,0 +1,104 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const TEST_URI = "http://example.com/browser/dom/tests/browser/test-console-api.html";
+const TEST_URI_NAV = "http://example.com/browser/dom/tests/browser/";
+
+Cu.import("resource://gre/modules/ConsoleAPIStorage.jsm");
+
+var apiCallCount;
+
+var ConsoleObserver = {
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]),
+
+  init: function CO_init()
+  {
+    Services.obs.addObserver(this, "console-storage-cache-event", false);
+    apiCallCount = 0;
+  },
+
+  observe: function CO_observe(aSubject, aTopic, aData)
+  {
+    if (aTopic == "console-storage-cache-event") {
+      apiCallCount ++;
+      if (apiCallCount == 4) {
+        try {
+                  var tab = gBrowser.selectedTab;
+        let browser = gBrowser.selectedBrowser;
+        let win = XPCNativeWrapper.unwrap(browser.contentWindow);
+        let windowID = getWindowId(win);
+        let messages = ConsoleAPIStorage.getEvents(windowID);
+
+        ok(messages.length >= 4, "Some messages found in the storage service");
+
+        ConsoleAPIStorage.clearEvents();
+        messages = ConsoleAPIStorage.getEvents(windowID);
+        ok(messages.length == 0, "Cleared Storage, no events found");
+
+        // remove the observer so we don't trigger this test again
+        Services.obs.removeObserver(this, "console-storage-cache-event");
+
+        // make sure a closed window's events are in fact removed from the
+        // storage cache
+        win.console.log("adding a new event");
+
+        // close the window - the storage cache should now be empty
+        gBrowser.removeTab(tab, {animate: false});
+
+        window.QueryInterface(Ci.nsIInterfaceRequestor)
+          .getInterface(Ci.nsIDOMWindowUtils).garbageCollect();
+        executeSoon(function (){
+          // use the old windowID again to see if we have any stray cached messages
+          messages = ConsoleAPIStorage.getEvents(windowID);
+          ok(messages.length == 0, "0 events found, tab close is clearing the cache");
+          finish();
+        });
+        } catch (ex) {
+          dump(ex + "\n\n\n");
+          dump(ex.stack + "\n\n\n");
+        }
+        }
+
+    }
+  }
+};
+
+function tearDown()
+{
+   while (gBrowser.tabs.length > 1) {
+    gBrowser.removeCurrentTab();
+  }
+}
+
+function test()
+{
+  registerCleanupFunction(tearDown);
+
+  ConsoleObserver.init();
+
+  waitForExplicitFinish();
+
+  var tab = gBrowser.addTab(TEST_URI);
+  gBrowser.selectedTab = tab;
+  var browser = gBrowser.selectedBrowser;
+  browser.addEventListener("DOMContentLoaded", function onLoad(event) {
+    browser.removeEventListener("DOMContentLoaded", onLoad, false);
+    executeSoon(function test_executeSoon() {
+      var contentWin = browser.contentWindow;
+
+      let win = XPCNativeWrapper.unwrap(contentWin);
+
+      win.console.log("this", "is", "a", "log message");
+      win.console.info("this", "is", "a", "info message");
+      win.console.warn("this", "is", "a", "warn message");
+      win.console.error("this", "is", "a", "error message");
+    });
+  }, false);
+}
+
+function getWindowId(aWindow)
+{
+  return aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
+                .getInterface(Ci.nsIDOMWindowUtils)
+                .currentInnerWindowID;
+}
new file mode 100644
--- /dev/null
+++ b/dom/tests/browser/browser_ConsoleStoragePBTest.js
@@ -0,0 +1,74 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function test() {
+  try {
+    var pb = Cc["@mozilla.org/privatebrowsing;1"].getService(Ci.nsIPrivateBrowsingService);
+  } catch (ex) {
+    ok(true, "nothing to do here, PB service doesn't exist");
+    return;
+  }
+
+  waitForExplicitFinish();
+
+  var CSS = {};
+  Cu.import("resource://gre/modules/ConsoleAPIStorage.jsm", CSS);
+
+  function checkStorageOccurs(shouldOccur) {
+    let win = XPCNativeWrapper.unwrap(browser.contentWindow);
+    let innerID = getInnerWindowId(win);
+
+    let beforeEvents = CSS.ConsoleAPIStorage.getEvents(innerID);
+    win.console.log("foo bar baz (private: " + !shouldOccur + ")");
+
+    let afterEvents = CSS.ConsoleAPIStorage.getEvents(innerID);
+
+    is(beforeEvents.length == afterEvents.length - 1,
+       shouldOccur, "storage should" + (shouldOccur ? "" : "n't") + " occur");
+  }
+
+  function pbObserver(aSubject, aTopic, aData) {
+    if (aData == "enter") {
+      checkStorageOccurs(false);
+
+      executeSoon(function () { pb.privateBrowsingEnabled = false; });
+    } else if (aData == "exit") {
+      executeSoon(finish);
+    }
+  }
+
+  const TEST_URI = "http://example.com/browser/dom/tests/browser/test-console-api.html";
+  var tab = gBrowser.selectedTab = gBrowser.addTab(TEST_URI);
+  var browser = gBrowser.selectedBrowser;
+
+  Services.obs.addObserver(pbObserver, "private-browsing", false);
+
+  const PB_KEEP_SESSION_PREF = "browser.privatebrowsing.keep_current_session";
+  Services.prefs.setBoolPref(PB_KEEP_SESSION_PREF, true);
+
+  registerCleanupFunction(function () {
+    gBrowser.removeTab(tab);
+
+    Services.obs.removeObserver(pbObserver, "private-browsing");
+
+    if (Services.prefs.prefHasUserValue(PB_KEEP_SESSION_PREF))
+      Services.prefs.clearUserPref(PB_KEEP_SESSION_PREF);
+  });
+
+  browser.addEventListener("DOMContentLoaded", function onLoad(event) {
+    if (browser.currentURI.spec != TEST_URI)
+      return;
+
+    browser.removeEventListener("DOMContentLoaded", onLoad, false);
+
+    checkStorageOccurs(true);
+
+    pb.privateBrowsingEnabled = true;
+  }, false);
+}
+
+function getInnerWindowId(aWindow) {
+  return aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
+                .getInterface(Ci.nsIDOMWindowUtils)
+                .currentInnerWindowID;
+}
--- a/dom/workers/WorkerPrivate.cpp
+++ b/dom/workers/WorkerPrivate.cpp
@@ -890,45 +890,45 @@ public:
   bool
   WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
   {
     JSObject* target = aWorkerPrivate->GetJSObject();
     if (target) {
       aWorkerPrivate->AssertInnerWindowIsCorrect();
     }
 
-    PRUint64 windowId;
+    PRUint64 innerWindowId;
 
     WorkerPrivate* parent = aWorkerPrivate->GetParent();
     if (parent) {
-      windowId = 0;
+      innerWindowId = 0;
     }
     else {
       AssertIsOnMainThread();
 
       if (aWorkerPrivate->IsSuspended()) {
         aWorkerPrivate->QueueRunnable(this);
         return true;
       }
 
-      windowId = aWorkerPrivate->GetOuterWindowId();
+      innerWindowId = aWorkerPrivate->GetInnerWindowId();
     }
 
     return ReportErrorRunnable::ReportError(aCx, parent, true, target, mMessage,
                                             mFilename, mLine, mLineNumber,
                                             mColumnNumber, mFlags,
-                                            mErrorNumber, windowId);
+                                            mErrorNumber, innerWindowId);
   }
 
   static bool
   ReportError(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
               bool aFireAtScope, JSObject* aTarget, const nsString& aMessage,
               const nsString& aFilename, const nsString& aLine,
               PRUint32 aLineNumber, PRUint32 aColumnNumber, PRUint32 aFlags,
-              PRUint32 aErrorNumber, PRUint64 aWindowId)
+              PRUint32 aErrorNumber, PRUint64 aInnerWindowId)
   {
     if (aWorkerPrivate) {
       aWorkerPrivate->AssertIsOnWorkerThread();
     }
     else {
       AssertIsOnMainThread();
     }
 
@@ -1025,17 +1025,17 @@ public:
     nsCOMPtr<nsIConsoleMessage> consoleMessage;
 
     if (scriptError) {
       if (NS_SUCCEEDED(scriptError->InitWithWindowID(aMessage.get(),
                                                      aFilename.get(),
                                                      aLine.get(), aLineNumber,
                                                      aColumnNumber, aFlags,
                                                      "Web Worker",
-                                                     aWindowId))) {
+                                                     aInnerWindowId))) {
         consoleMessage = do_QueryInterface(scriptError);
         NS_ASSERTION(consoleMessage, "This should never fail!");
       }
       else {
         NS_WARNING("Failed to init script error!");
       }
     }
 
@@ -1927,20 +1927,20 @@ WorkerPrivateParent<Derived>::PostMessag
     new MessageEventRunnable(ParentAsWorkerPrivate(),
                              WorkerRunnable::WorkerThread, buffer,
                              clonedObjects);
   return runnable->Dispatch(aCx);
 }
 
 template <class Derived>
 PRUint64
-WorkerPrivateParent<Derived>::GetOuterWindowId()
+WorkerPrivateParent<Derived>::GetInnerWindowId()
 {
   AssertIsOnMainThread();
-  return mDocument->OuterWindowID();
+  return mDocument->InnerWindowID();
 }
 
 template <class Derived>
 void
 WorkerPrivateParent<Derived>::UpdateJSContextOptions(JSContext* aCx,
                                                      PRUint32 aOptions)
 {
   AssertIsOnParentThread();
--- a/dom/workers/WorkerPrivate.h
+++ b/dom/workers/WorkerPrivate.h
@@ -294,17 +294,17 @@ public:
 
   void
   ForgetMainThreadObjects(nsTArray<nsCOMPtr<nsISupports> >& aDoomed);
 
   bool
   PostMessage(JSContext* aCx, jsval aMessage);
 
   PRUint64
-  GetOuterWindowId();
+  GetInnerWindowId();
 
   void
   UpdateJSContextOptions(JSContext* aCx, PRUint32 aOptions);
 
 #ifdef JS_GC_ZEAL
   void
   UpdateGCZeal(JSContext* aCx, PRUint8 aGCZeal);
 #endif
--- a/js/src/xpconnect/idl/nsIScriptError.idl
+++ b/js/src/xpconnect/idl/nsIScriptError.idl
@@ -91,33 +91,41 @@ interface nsIScriptError : nsIConsoleMes
 
     AUTF8String toString();
 };
 
 /**
  * An interface that nsIScriptError objects can implement to allow
  * them to be initialized with a window id.
  */
-[scriptable, uuid(35cd0f6a-f5bb-497a-ba83-9c8d089b52cd)]
+[scriptable, uuid(4472646b-c928-4d76-9e7c-6b91da7f24cc)]
 interface nsIScriptError2 : nsISupports {
     /* Get the window id this was initialized with.  Zero will be
-       returned if init() was used instead of initWithWindowId(). */
+       returned if init() was used instead of initWithWindowID(). */
     readonly attribute unsigned long long outerWindowID;
 
+    /* Get the inner window id this was initialized with.  Zero will be
+       returned if init() was used instead of initWithWindowID(). */
+    readonly attribute unsigned long long innerWindowID;
+
+    /* Elapsed time, in milliseconds, from a platform-specific zero time to the
+       time the message was created. */
+    readonly attribute long long timeStamp;
+
     /* This should be called instead of nsIScriptError.init to
        initialize with a window id.  The window id should be for the
-       outer window associated with this error. */
+       inner window associated with this error. */
     void initWithWindowID(in wstring message,
                           in wstring sourceName,
                           in wstring sourceLine,
                           in PRUint32 lineNumber,
                           in PRUint32 columnNumber,
                           in PRUint32 flags,
                           in string category,
-                          in unsigned long long windowID);
+                          in unsigned long long innerWindowID);
 };
 
 %{ C++
 #define NS_SCRIPTERROR_CLASSNAME "Script Error"
 
 #define NS_SCRIPTERROR_CID \
 { 0xe38e53b9, 0x5bb0, 0x456a, { 0xb5, 0x53, 0x57, 0x93, 0x70, 0xcb, 0x15, 0x67 }}
 
--- a/js/src/xpconnect/src/nsScriptError.cpp
+++ b/js/src/xpconnect/src/nsScriptError.cpp
@@ -38,29 +38,33 @@
  * ***** END LICENSE BLOCK ***** */
 
 /*
  * nsIScriptError implementation.  Defined here, lacking a JS-specific
  * place to put XPCOM things.
  */
 
 #include "xpcprivate.h"
+#include "nsGlobalWindow.h"
+#include "nsPIDOMWindow.h"
 
 NS_IMPL_THREADSAFE_ISUPPORTS3(nsScriptError, nsIConsoleMessage, nsIScriptError,
                               nsIScriptError2)
 
 nsScriptError::nsScriptError()
     :  mMessage(),
        mSourceName(),
        mLineNumber(0),
        mSourceLine(),
        mColumnNumber(0),
        mFlags(0),
        mCategory(),
-       mWindowID(0)
+       mOuterWindowID(0),
+       mInnerWindowID(0),
+       mTimeStamp(0)
 {
 }
 
 nsScriptError::~nsScriptError() {}
 
 // nsIConsoleMessage methods
 NS_IMETHODIMP
 nsScriptError::GetMessageMoz(PRUnichar **result) {
@@ -137,26 +141,37 @@ nsScriptError::Init(const PRUnichar *mes
 NS_IMETHODIMP
 nsScriptError::InitWithWindowID(const PRUnichar *message,
                                 const PRUnichar *sourceName,
                                 const PRUnichar *sourceLine,
                                 PRUint32 lineNumber,
                                 PRUint32 columnNumber,
                                 PRUint32 flags,
                                 const char *category,
-                                PRUint64 aWindowID)
+                                PRUint64 aInnerWindowID)
 {
     mMessage.Assign(message);
     mSourceName.Assign(sourceName);
     mLineNumber = lineNumber;
     mSourceLine.Assign(sourceLine);
     mColumnNumber = columnNumber;
     mFlags = flags;
     mCategory.Assign(category);
-    mWindowID = aWindowID;
+    mTimeStamp = PR_Now() / 1000;
+    mInnerWindowID = aInnerWindowID;
+
+    if(aInnerWindowID) {
+        nsGlobalWindow* window =
+          nsGlobalWindow::GetInnerWindowWithId(aInnerWindowID);
+        if(window) {
+            nsPIDOMWindow* outer = window->GetOuterWindow();
+            if(outer)
+                mOuterWindowID = outer->WindowID();
+        }
+    }
 
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsScriptError::ToString(nsACString& /*UTF8*/ aResult)
 {
     static const char format0[] =
@@ -213,13 +228,27 @@ nsScriptError::ToString(nsACString& /*UT
         return NS_ERROR_OUT_OF_MEMORY;
 
     aResult.Assign(temp);
     JS_smprintf_free(temp);
     return NS_OK;
 }
 
 NS_IMETHODIMP
-nsScriptError::GetOuterWindowID(PRUint64 *aWindowID)
+nsScriptError::GetOuterWindowID(PRUint64 *aOuterWindowID)
 {
-    *aWindowID = mWindowID;
+    *aOuterWindowID = mOuterWindowID;
     return NS_OK;
 }
+
+NS_IMETHODIMP
+nsScriptError::GetInnerWindowID(PRUint64 *aInnerWindowID)
+{
+    *aInnerWindowID = mInnerWindowID;
+    return NS_OK;
+}
+
+NS_IMETHODIMP
+nsScriptError::GetTimeStamp(PRInt64 *aTimeStamp)
+{
+    *aTimeStamp = mTimeStamp;
+    return NS_OK;
+}
--- a/js/src/xpconnect/src/xpccomponents.cpp
+++ b/js/src/xpconnect/src/xpccomponents.cpp
@@ -2875,17 +2875,17 @@ nsXPCComponents_Utils::ReportError()
     if(argc < 1)
         return NS_ERROR_XPC_NOT_ENOUGH_ARGS;
 
     jsval* argv;
     rv = cc->GetArgvPtr(&argv);
     if(NS_FAILED(rv) || !argv)
         return NS_OK;
 
-    const PRUint64 windowID = nsJSUtils::GetCurrentlyRunningCodeWindowID(cx);
+    const PRUint64 innerWindowID = nsJSUtils::GetCurrentlyRunningCodeInnerWindowID(cx);
 
     JSErrorReport* err = JS_ErrorFromException(cx, argv[0]);
     if(err)
     {
         // It's a proper JS Error
         nsAutoString fileUni;
         CopyUTF8toUTF16(err->filename, fileUni);
 
@@ -2894,17 +2894,17 @@ nsXPCComponents_Utils::ReportError()
         rv = scripterr->InitWithWindowID(reinterpret_cast<const PRUnichar*>
                                                        (err->ucmessage),
                                          fileUni.get(),
                                          reinterpret_cast<const PRUnichar*>
                                                          (err->uclinebuf),
                                          err->lineno,
                                          column,
                                          err->flags,
-                                         "XPConnect JavaScript", windowID);
+                                         "XPConnect JavaScript", innerWindowID);
         if(NS_FAILED(rv))
             return NS_OK;
 
         nsCOMPtr<nsIScriptError> logError = do_QueryInterface(scripterr);
         console->LogMessage(logError);
         return NS_OK;
     }
 
@@ -2930,18 +2930,18 @@ nsXPCComponents_Utils::ReportError()
 
         const jschar *msgchars = JS_GetStringCharsZ(cx, msgstr);
         if (!msgchars)
             return NS_OK;
 
         rv = scripterr->InitWithWindowID(reinterpret_cast<const PRUnichar *>(msgchars),
                                          NS_ConvertUTF8toUTF16(fileName).get(),
                                          nsnull,
-                                         lineNo, 0,
-                                         0, "XPConnect JavaScript", windowID);
+                                         lineNo, 0, 0,
+                                         "XPConnect JavaScript", innerWindowID);
         if(NS_SUCCEEDED(rv))
         {
             nsCOMPtr<nsIScriptError> logError = do_QueryInterface(scripterr);
             console->LogMessage(logError);
         }
     }
 
     return NS_OK;
--- a/js/src/xpconnect/src/xpcconvert.cpp
+++ b/js/src/xpconnect/src/xpcconvert.cpp
@@ -1822,17 +1822,17 @@ XPCConvert::JSErrorToXPCException(XPCCal
             return NS_ERROR_OUT_OF_MEMORY;
 
 
         data->InitWithWindowID(bestMessage.get(),
                                NS_ConvertASCIItoUTF16(report->filename).get(),
                                (const PRUnichar *)report->uclinebuf, report->lineno,
                                report->uctokenptr - report->uclinebuf, report->flags,
                                "XPConnect JavaScript",
-                               nsJSUtils::GetCurrentlyRunningCodeWindowID(ccx.GetJSContext()));
+                               nsJSUtils::GetCurrentlyRunningCodeInnerWindowID(ccx.GetJSContext()));
     }
 
     if(data)
     {
         nsCAutoString formattedMsg;
         data->ToString(formattedMsg);
 
         rv = ConstructException(NS_ERROR_XPC_JAVASCRIPT_ERROR_WITH_DETAILS,
--- a/js/src/xpconnect/src/xpcprivate.h
+++ b/js/src/xpconnect/src/xpcprivate.h
@@ -3880,17 +3880,19 @@ public:
 private:
     nsString mMessage;
     nsString mSourceName;
     PRUint32 mLineNumber;
     nsString mSourceLine;
     PRUint32 mColumnNumber;
     PRUint32 mFlags;
     nsCString mCategory;
-    PRUint64 mWindowID;
+    PRUint64 mOuterWindowID;
+    PRUint64 mInnerWindowID;
+    PRUint64 mTimeStamp;
 };
 
 /***************************************************************************/
 
 class NS_STACK_CLASS AutoJSErrorAndExceptionEater
 {
 public:
     AutoJSErrorAndExceptionEater(JSContext* aCX
--- a/js/src/xpconnect/src/xpcwrappedjsclass.cpp
+++ b/js/src/xpconnect/src/xpcwrappedjsclass.cpp
@@ -1213,17 +1213,17 @@ nsXPCWrappedJSClass::CheckForException(X
 
                                 nsCOMPtr<nsIScriptError2> scriptError2 =
                                     do_QueryInterface(scriptError);
                                 rv = scriptError2->InitWithWindowID(newMessage.get(),
                                                                     NS_ConvertASCIItoUTF16(sourceName).get(),
                                                                     nsnull,
                                                                     lineNumber, 0, 0,
                                                                     "XPConnect JavaScript",
-                                                                    nsJSUtils::GetCurrentlyRunningCodeWindowID(cx));
+                                                                    nsJSUtils::GetCurrentlyRunningCodeInnerWindowID(cx));
                                 if(NS_FAILED(rv))
                                     scriptError = nsnull;
                             }
                         }
                     }
                     if(nsnull != scriptError)
                         consoleService->LogMessage(scriptError);
                 }
--- a/layout/style/nsCSSScanner.cpp
+++ b/layout/style/nsCSSScanner.cpp
@@ -280,17 +280,17 @@ nsCSSToken::AppendToString(nsString& aBu
 
 nsCSSScanner::nsCSSScanner()
   : mInputStream(nsnull)
   , mReadPointer(nsnull)
   , mLowLevelError(NS_OK)
   , mSVGMode(PR_FALSE)
 #ifdef CSS_REPORT_PARSE_ERRORS
   , mError(mErrorBuf, NS_ARRAY_LENGTH(mErrorBuf), 0)
-  , mWindowID(0)
+  , mInnerWindowID(0)
   , mWindowIDCached(PR_FALSE)
   , mSheet(nsnull)
   , mLoader(nsnull)
 #endif
 {
   MOZ_COUNT_CTOR(nsCSSScanner);
   mPushback = mLocalPushback;
   mPushbackSize = NS_ARRAY_LENGTH(mLocalPushback);
@@ -444,39 +444,40 @@ nsCSSScanner::OutputError()
 {
   if (mError.IsEmpty()) return;
  
   // Log it to the Error console
 
   if (InitGlobals() && gReportErrors) {
     if (!mWindowIDCached) {
       if (mSheet) {
-        mWindowID = mSheet->FindOwningWindowID();
+        mInnerWindowID = mSheet->FindOwningWindowInnerID();
       }
-      if (mWindowID == 0 && mLoader) {
+      if (mInnerWindowID == 0 && mLoader) {
         nsIDocument* doc = mLoader->GetDocument();
         if (doc) {
-          mWindowID = doc->OuterWindowID();
+          mInnerWindowID = doc->InnerWindowID();
         }
       }
       mWindowIDCached = PR_TRUE;
     }
 
     nsresult rv;
     nsCOMPtr<nsIScriptError2> errorObject =
       do_CreateInstance(gScriptErrorFactory, &rv);
 
     if (NS_SUCCEEDED(rv)) {
       rv = errorObject->InitWithWindowID(mError.get(),
                                          NS_ConvertUTF8toUTF16(mFileName).get(),
                                          EmptyString().get(),
                                          mErrorLineNumber,
                                          mErrorColNumber,
                                          nsIScriptError::warningFlag,
-                                         "CSS Parser", mWindowID);
+                                         "CSS Parser",
+                                         mInnerWindowID);
       if (NS_SUCCEEDED(rv)) {
         nsCOMPtr<nsIScriptError> logError = do_QueryInterface(errorObject);
         gConsoleService->LogMessage(logError);
       }
     }
   }
   ClearError();
 }
@@ -617,17 +618,17 @@ nsCSSScanner::Close()
   mInputStream = nsnull;
   mReadPointer = nsnull;
 
   // Clean things up so we don't hold on to memory if our parser gets recycled.
 #ifdef CSS_REPORT_PARSE_ERRORS
   mFileName.Truncate();
   mURI = nsnull;
   mError.Truncate();
-  mWindowID = 0;
+  mInnerWindowID = 0;
   mWindowIDCached = PR_FALSE;
   mSheet = nsnull;
   mLoader = nsnull;
 #endif
   if (mPushback != mLocalPushback) {
     delete [] mPushback;
     mPushback = mLocalPushback;
     mPushbackSize = NS_ARRAY_LENGTH(mLocalPushback);
--- a/layout/style/nsCSSScanner.h
+++ b/layout/style/nsCSSScanner.h
@@ -243,16 +243,16 @@ protected:
   // True if we are in SVG mode; false in "normal" CSS
   PRPackedBool mSVGMode;
 #ifdef CSS_REPORT_PARSE_ERRORS
   nsXPIDLCString mFileName;
   nsCOMPtr<nsIURI> mURI;  // Cached so we know to not refetch mFileName
   PRUint32 mErrorLineNumber, mColNumber, mErrorColNumber;
   nsFixedString mError;
   PRUnichar mErrorBuf[200];
-  PRUint64 mWindowID;
+  PRUint64 mInnerWindowID;
   PRBool mWindowIDCached;
   nsCSSStyleSheet* mSheet;
   mozilla::css::Loader* mLoader;
 #endif
 };
 
 #endif /* nsCSSScanner_h___ */
--- a/layout/style/nsCSSStyleSheet.cpp
+++ b/layout/style/nsCSSStyleSheet.cpp
@@ -1247,46 +1247,46 @@ nsCSSStyleSheet::SetOwningDocument(nsIDo
   for (nsCSSStyleSheet* child = mInner->mFirstChild;
        child; child = child->mNext) {
     if (child->mParent == this) {
       child->SetOwningDocument(aDocument);
     }
   }
 }
 
-/* virtual */ PRUint64
-nsCSSStyleSheet::FindOwningWindowID() const
+PRUint64
+nsCSSStyleSheet::FindOwningWindowInnerID() const
 {
   PRUint64 windowID = 0;
   if (mDocument) {
-    windowID = mDocument->OuterWindowID();
+    windowID = mDocument->InnerWindowID();
   }
 
   if (windowID == 0 && mOwningNode) {
     nsCOMPtr<nsIContent> node = do_QueryInterface(mOwningNode);
     if (node) {
       nsIDocument* doc = node->GetOwnerDoc();
       if (doc) {
-        windowID = doc->OuterWindowID();
+        windowID = doc->InnerWindowID();
       }
     }
   }
 
   if (windowID == 0 && mOwnerRule) {
     nsCOMPtr<nsIStyleSheet> sheet = static_cast<css::Rule*>(mOwnerRule)->GetStyleSheet();
     if (sheet) {
       nsRefPtr<nsCSSStyleSheet> cssSheet = do_QueryObject(sheet);
       if (cssSheet) {
-        windowID = cssSheet->FindOwningWindowID();
+        windowID = cssSheet->FindOwningWindowInnerID();
       }
     }
   }
 
   if (windowID == 0 && mParent) {
-    windowID = mParent->FindOwningWindowID();
+    windowID = mParent->FindOwningWindowInnerID();
   }
 
   return windowID;
 }
 
 void
 nsCSSStyleSheet::AppendStyleSheet(nsCSSStyleSheet* aSheet)
 {
--- a/layout/style/nsCSSStyleSheet.h
+++ b/layout/style/nsCSSStyleSheet.h
@@ -150,18 +150,18 @@ public:
   virtual PRBool IsApplicable() const;
   virtual void SetEnabled(PRBool aEnabled);
   virtual PRBool IsComplete() const;
   virtual void SetComplete();
   virtual nsIStyleSheet* GetParentSheet() const;  // may be null
   virtual nsIDocument* GetOwningDocument() const;  // may be null
   virtual void SetOwningDocument(nsIDocument* aDocument);
 
-  // Find the ID of the owner outer window.
-  virtual PRUint64 FindOwningWindowID() const;
+  // Find the ID of the owner inner window.
+  PRUint64 FindOwningWindowInnerID() const;
 #ifdef DEBUG
   virtual void List(FILE* out = stdout, PRInt32 aIndent = 0) const;
 #endif
 
   void AppendStyleSheet(nsCSSStyleSheet* aSheet);
   void InsertStyleSheetAt(nsCSSStyleSheet* aSheet, PRInt32 aIndex);
 
   // XXX do these belong here or are they generic?
--- a/layout/style/nsFontFaceLoader.cpp
+++ b/layout/style/nsFontFaceLoader.cpp
@@ -793,24 +793,24 @@ nsUserFontSet::LogMessage(gfxProxyFontEn
     rv = sheet->GetHref(href);
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   nsCOMPtr<nsIScriptError2> scriptError =
     do_CreateInstance(NS_SCRIPTERROR_CONTRACTID, &rv);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  PRUint64 windowID = GetPresContext()->Document()->OuterWindowID();
+  PRUint64 innerWindowID = GetPresContext()->Document()->InnerWindowID();
   rv = scriptError->InitWithWindowID(NS_ConvertUTF8toUTF16(msg).get(),
                                      href.get(),   // file
                                      text.get(),   // src line
                                      0, 0,         // line & column number
                                      aFlags,       // flags
                                      "CSS Loader", // category (make separate?)
-                                     windowID);
+                                     innerWindowID);
   if (NS_SUCCEEDED(rv)){
     nsCOMPtr<nsIScriptError> logError = do_QueryInterface(scriptError);
     if (logError) {
       console->LogMessage(logError);
     }
   }
 
   return NS_OK;
--- a/modules/libpr0n/src/Decoder.cpp
+++ b/modules/libpr0n/src/Decoder.cpp
@@ -148,17 +148,17 @@ Decoder::Finish()
       nsAutoString msg(NS_LITERAL_STRING("Image corrupt or truncated: ") +
                        NS_ConvertASCIItoUTF16(mImage->GetURIString()));
 
       errorObject->InitWithWindowID
         (msg.get(),
          NS_ConvertUTF8toUTF16(mImage->GetURIString()).get(),
          nsnull,
          0, 0, nsIScriptError::errorFlag,
-         "Image", mImage->WindowID()
+         "Image", mImage->InnerWindowID()
          );
   
       nsCOMPtr<nsIScriptError> error = do_QueryInterface(errorObject);
       consoleService->LogMessage(error);
     }
 
     // If we only have a data error, see if things are worth salvaging
     bool salvage = !HasDecoderError() && mImage->GetNumFrames();
--- a/modules/libpr0n/src/Image.cpp
+++ b/modules/libpr0n/src/Image.cpp
@@ -37,17 +37,17 @@
 
 #include "Image.h"
 
 namespace mozilla {
 namespace imagelib {
 
 // Constructor
 Image::Image(imgStatusTracker* aStatusTracker) :
-  mWindowId(0),
+  mInnerWindowId(0),
   mAnimationConsumers(0),
   mAnimationMode(kNormalAnimMode),
   mInitialized(PR_FALSE),
   mAnimating(PR_FALSE),
   mError(PR_FALSE)
 {
   if (aStatusTracker) {
     mStatusTracker = aStatusTracker;
--- a/modules/libpr0n/src/Image.h
+++ b/modules/libpr0n/src/Image.h
@@ -120,36 +120,36 @@ public:
   static eDecoderType GetDecoderType(const char *aMimeType);
 
   void IncrementAnimationConsumers();
   void DecrementAnimationConsumers();
 #ifdef DEBUG
   PRUint32 GetAnimationConsumers() { return mAnimationConsumers; }
 #endif
 
-  void SetWindowID(PRUint64 aWindowId) {
-    mWindowId = aWindowId;
+  void SetInnerWindowID(PRUint64 aInnerWindowId) {
+    mInnerWindowId = aInnerWindowId;
   }
-  PRUint64 WindowID() const { return mWindowId; }
+  PRUint64 InnerWindowID() const { return mInnerWindowId; }
 
   PRBool HasError() { return mError; }
 
 protected:
   Image(imgStatusTracker* aStatusTracker);
 
   /**
    * Decides whether animation should or should not be happening,
    * and makes sure the right thing is being done.
    */
   virtual void EvaluateAnimation();
 
   virtual nsresult StartAnimation() = 0;
   virtual nsresult StopAnimation() = 0;
 
-  PRUint64 mWindowId;
+  PRUint64 mInnerWindowId;
 
   // Member data shared by all implementations of this abstract class
   nsAutoPtr<imgStatusTracker> mStatusTracker;
   PRUint32                    mAnimationConsumers;
   PRUint16                    mAnimationMode;   // Enum values in imgIContainer
   PRPackedBool                mInitialized:1;   // Have we been initalized?
   PRPackedBool                mAnimating:1;     // Are we currently animating?
   PRPackedBool                mError:1;         // Error handling
--- a/modules/libpr0n/src/imgLoader.cpp
+++ b/modules/libpr0n/src/imgLoader.cpp
@@ -1704,20 +1704,20 @@ NS_IMETHODIMP imgLoader::LoadImage(nsIUR
     nsCOMPtr<nsILoadGroup> loadGroup =
         do_CreateInstance(NS_LOADGROUP_CONTRACTID);
     newChannel->SetLoadGroup(loadGroup);
 
     void *cacheId = NS_GetCurrentThread();
     request->Init(aURI, aURI, loadGroup, newChannel, entry, cacheId, aCX,
                   aLoadingPrincipal, corsmode);
 
-    // Pass the windowID of the loading document, if possible.
+    // Pass the inner window ID of the loading document, if possible.
     nsCOMPtr<nsIDocument> doc = do_QueryInterface(aCX);
     if (doc) {
-      request->SetWindowID(doc->OuterWindowID());
+      request->SetInnerWindowID(doc->InnerWindowID());
     }
 
     // create the proxy listener
     nsCOMPtr<nsIStreamListener> pl = new ProxyListener(request.get());
 
     // See if we need to insert a CORS proxy between the proxy listener and the
     // request.
     nsCOMPtr<nsIStreamListener> listener = pl;
--- a/modules/libpr0n/src/imgRequest.cpp
+++ b/modules/libpr0n/src/imgRequest.cpp
@@ -179,18 +179,19 @@ NS_IMPL_ISUPPORTS8(imgRequest,
                    nsIStreamListener, nsIRequestObserver,
                    nsISupportsWeakReference,
                    nsIChannelEventSink,
                    nsIInterfaceRequestor,
                    nsIAsyncVerifyRedirectCallback)
 
 imgRequest::imgRequest() : 
   mCacheId(0), mValidator(nsnull), mImageSniffers("image-sniffing-services"),
-  mWindowId(0), mCORSMode(imgIRequest::CORS_NONE), mDecodeRequested(PR_FALSE),
-  mIsMultiPartChannel(PR_FALSE), mGotData(PR_FALSE), mIsInCache(PR_FALSE)
+  mInnerWindowId(0), mCORSMode(imgIRequest::CORS_NONE),
+  mDecodeRequested(PR_FALSE), mIsMultiPartChannel(PR_FALSE), mGotData(PR_FALSE),
+  mIsInCache(PR_FALSE)
 {}
 
 imgRequest::~imgRequest()
 {
   if (mURI) {
     nsCAutoString spec;
     mURI->GetSpec(spec);
     LOG_FUNC_WITH_PARAM(gImgLog, "imgRequest::~imgRequest()", "keyuri", spec.get());
@@ -1030,17 +1031,17 @@ NS_IMETHODIMP imgRequest::OnDataAvailabl
     }
 
     /* now we have mimetype, so we can infer the image type that we want */
     if (mContentType.EqualsLiteral(SVG_MIMETYPE)) {
       mImage = new VectorImage(mStatusTracker.forget());
     } else {
       mImage = new RasterImage(mStatusTracker.forget());
     }
-    mImage->SetWindowID(mWindowId);
+    mImage->SetInnerWindowID(mInnerWindowId);
     imageType = mImage->GetType();
 
     // Notify any imgRequestProxys that are observing us that we have an Image.
     nsTObserverArray<imgRequestProxy*>::ForwardIterator iter(mObservers);
     while (iter.HasMore()) {
       iter.GetNext()->SetImage(mImage);
     }
 
--- a/modules/libpr0n/src/imgRequest.h
+++ b/modules/libpr0n/src/imgRequest.h
@@ -118,22 +118,22 @@ public:
   void CancelAndAbort(nsresult aStatus);
 
   // Methods that get forwarded to the Image, or deferred until it's
   // instantiated.
   nsresult LockImage();
   nsresult UnlockImage();
   nsresult RequestDecode();
 
-  inline void SetWindowID(PRUint64 aWindowId) {
-    mWindowId = aWindowId;
+  inline void SetInnerWindowID(PRUint64 aInnerWindowId) {
+    mInnerWindowId = aInnerWindowId;
   }
 
-  inline PRUint64 WindowID() const {
-    return mWindowId;
+  inline PRUint64 InnerWindowID() const {
+    return mInnerWindowId;
   }
 
   // Set the cache validation information (expiry time, whether we must
   // validate, etc) on the cache entry based on the request information.
   // If this function is called multiple times, the information set earliest
   // wins.
   static void SetCacheValidation(imgCacheEntry* aEntry, nsIRequest* aRequest);
 
@@ -252,18 +252,18 @@ private:
 
   void *mLoadId;
 
   imgCacheValidator *mValidator;
   nsCategoryCache<nsIContentSniffer> mImageSniffers;
   nsCOMPtr<nsIAsyncVerifyRedirectCallback> mRedirectCallback;
   nsCOMPtr<nsIChannel> mNewRedirectChannel;
 
-  // Originating outer window ID. Used for error reporting.
-  PRUint64 mWindowId;
+  // The ID of the inner window origin, used for error reporting.
+  PRUint64 mInnerWindowId;
 
   // The CORS mode (defined in imgIRequest) this image was loaded with. By
   // default, imgIRequest::CORS_NONE.
   PRInt32 mCORSMode;
 
   // Sometimes consumers want to do things before the image is ready. Let them,
   // and apply the action when the image becomes available.
   PRPackedBool mDecodeRequested : 1;
--- a/parser/htmlparser/src/nsExpatDriver.cpp
+++ b/parser/htmlparser/src/nsExpatDriver.cpp
@@ -371,17 +371,17 @@ nsExpatDriver::nsExpatDriver()
     mInCData(PR_FALSE),
     mInInternalSubset(PR_FALSE),
     mInExternalDTD(PR_FALSE),
     mMadeFinalCallToExpat(PR_FALSE),
     mIsFinalChunk(PR_FALSE),
     mInternalState(NS_OK),
     mExpatBuffered(0),
     mCatalogData(nsnull),
-    mWindowID(0)
+    mInnerWindowID(0)
 {
 }
 
 nsExpatDriver::~nsExpatDriver()
 {
   if (mExpatParser) {
     XML_ParserFree(mExpatParser);
   }
@@ -948,17 +948,17 @@ nsExpatDriver::HandleError()
   nsresult rv = NS_ERROR_FAILURE;
   if (serr) {
     nsCOMPtr<nsIScriptError2> serr2(do_QueryInterface(serr));
     rv = serr2->InitWithWindowID(description.get(),
                                  mURISpec.get(),
                                  mLastLine.get(),
                                  lineNumber, colNumber,
                                  nsIScriptError::errorFlag, "malformed-xml",
-                                 mWindowID);
+                                 mInnerWindowID);
   }
 
   // If it didn't initialize, we can't do any logging.
   PRBool shouldReportError = NS_SUCCEEDED(rv);
 
   if (mSink && shouldReportError) {
     rv = mSink->ReportError(errorText.get(), 
                             sourceText.get(), 
@@ -1245,18 +1245,21 @@ nsExpatDriver::WillBuildModel(const CPar
     if (!win) {
       PRBool aHasHadScriptHandlingObject;
       nsIScriptGlobalObject *global =
         doc->GetScriptHandlingObject(aHasHadScriptHandlingObject);
       if (global) {
         win = do_QueryInterface(global);
       }
     }
+    if (win && !win->IsInnerWindow()) {
+      win = win->GetCurrentInnerWindow();
+    }
     if (win) {
-      mWindowID = win->GetOuterWindow()->WindowID();
+      mInnerWindowID = win->WindowID();
     }
   }
 
   // Set up the callbacks
   XML_SetXmlDeclHandler(mExpatParser, Driver_HandleXMLDeclaration); 
   XML_SetElementHandler(mExpatParser, Driver_HandleStartElement,
                         Driver_HandleEndElement);
   XML_SetCharacterDataHandler(mExpatParser, Driver_HandleCharacterData);
--- a/parser/htmlparser/src/nsExpatDriver.h
+++ b/parser/htmlparser/src/nsExpatDriver.h
@@ -164,12 +164,12 @@ private:
   nsCOMPtr<nsIContentSink> mOriginalSink;
   nsCOMPtr<nsIExpatSink> mSink;
   nsCOMPtr<nsIExtendedExpatSink> mExtendedSink;
 
   const nsCatalogData* mCatalogData; // weak
   nsString         mURISpec;
 
   // Used for error reporting.
-  PRUint64         mWindowID;
+  PRUint64         mInnerWindowID;
 };
 
 #endif
--- a/toolkit/content/tests/chrome/test_notificationbox.xul
+++ b/toolkit/content/tests/chrome/test_notificationbox.xul
@@ -44,16 +44,34 @@ function testtag_notificationbox(nb)
   ise(nb.removeAllNotifications(false), undefined, "initial removeAllNotifications");
   testtag_notificationbox_State(nb, "initial removeAllNotifications", null, 0);
   ise(nb.removeAllNotifications(true), undefined, "initial removeAllNotifications immediate");
   testtag_notificationbox_State(nb, "initial removeAllNotifications immediate", null, 0);
 
   runTimedTests(tests, -1, nb, null);
 }
 
+var notification_last_event, notification_last_event_item;
+function notification_eventCallback(event)
+{
+  notification_last_event = event;
+  notification_last_event_item = this;
+}
+
+function testtag_notification_eventCallback(expectedEvent, ntf, testName)
+{
+  SimpleTest.is(notification_last_event, expectedEvent,
+                testName + ": event name");
+  SimpleTest.is(notification_last_event_item, ntf,
+                testName + ": event item");
+
+  notification_last_event = null;
+  notification_last_event_item = null;
+}
+
 var tests =
 [
   {
     test: function(nb, ntf) {
       // append a new notification
       var ntf = nb.appendNotification("Notification", "note", "happy.png",
                                       nb.PRIORITY_INFO_LOW, testtag_notificationbox_buttons);
       ise(ntf && ntf.localName == "notification", true, "append notification");
@@ -85,16 +103,43 @@ var tests =
       // try removing the notification again to make sure an exception occurs
       var exh = false;
       try {
         nb.removeNotification(ntf);
       } catch (ex) { exh = true; }
       ise(exh, true, "removeNotification again");
       testtag_notificationbox_State(nb, "removeNotification again", null, 0);
 
+    }
+  },
+  {
+    test: function(nb, ntf) {
+      // append a new notification, but now with an event callback
+      var ntf = nb.appendNotification("Notification", "note", "happy.png",
+                                      nb.PRIORITY_INFO_LOW,
+                                      testtag_notificationbox_buttons,
+                                      notification_eventCallback);
+      ise(ntf && ntf.localName == "notification", true, "append notification with callback");
+      return ntf;
+    },
+    result: function(nb, ntf) {
+      testtag_notificationbox_State(nb, "append with callback", ntf, 1);
+      return ntf;
+    }
+  },
+  {
+    test: function(nb, ntf) {
+      nb.removeNotification(ntf);
+      return ntf;
+    },
+    result: function(nb, ntf) {
+      testtag_notificationbox_State(nb, "removeNotification with callback",
+                                    null, 0);
+
+      testtag_notification_eventCallback("removed", ntf, "removeNotification()");
       return [1, null];
     }
   },
   {
     repeat: true,
     test: function(nb, arr) {
       var idx = arr[0];
       var ntf = arr[1];
--- a/toolkit/content/widgets/notification.xml
+++ b/toolkit/content/widgets/notification.xml
@@ -72,16 +72,17 @@
       </method>
 
       <method name="appendNotification">
         <parameter name="aLabel"/>
         <parameter name="aValue"/>
         <parameter name="aImage"/>
         <parameter name="aPriority"/>
         <parameter name="aButtons"/>
+        <parameter name="aEventCallback"/>
         <body>
           <![CDATA[
             if (aPriority < this.PRIORITY_INFO_LOW ||
                 aPriority > this.PRIORITY_CRITICAL_BLOCK)
               throw "Invalid notification priority " + aPriority;
 
             // check for where the notification should be inserted according to
             // priority. If two are equal, the existing one appears on top.
@@ -94,16 +95,17 @@
             }
 
             const XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
             var newitem = document.createElementNS(XULNS, "notification");
             newitem.setAttribute("label", aLabel);
             newitem.setAttribute("value", aValue);
             if (aImage)
               newitem.setAttribute("image", aImage);
+            newitem.eventCallback = aEventCallback;
 
             if (aButtons) {
               for (var b = 0; b < aButtons.length; b++) {
                 var button = aButtons[b];
                 var buttonElem = document.createElementNS(XULNS, "button");
                 buttonElem.setAttribute("label", button.label);
                 buttonElem.setAttribute("accesskey", button.accessKey);
 
@@ -143,39 +145,50 @@
       <method name="removeNotification">
         <parameter name="aItem"/>
         <parameter name="aSkipAnimation"/>
         <body>
           <![CDATA[
             if (aItem == this.currentNotification)
               this.removeCurrentNotification(aSkipAnimation);
             else if (aItem != this._closedNotification)
-              this.removeChild(aItem);
+              this._removeNotificationElement(aItem);
             return aItem;
           ]]>
         </body>
       </method>
 
+      <method name="_removeNotificationElement">
+        <parameter name="aChild"/>
+        <body>
+          <![CDATA[
+            if (aChild.eventCallback)
+              aChild.eventCallback("removed");
+            this.removeChild(aChild);
+          ]]>
+        </body>
+      </method>
+
       <method name="removeCurrentNotification">
         <parameter name="aSkipAnimation"/>
         <body>
           <![CDATA[
             this._showNotification(this.currentNotification, false, aSkipAnimation);
           ]]>
         </body>
       </method>
 
       <method name="removeAllNotifications">
         <parameter name="aImmediate"/>
         <body>
           <![CDATA[
             var notifications = this.allNotifications;
             for (var n = notifications.length - 1; n >= 0; n--) {
               if (aImmediate)
-                this.removeChild(notifications[n]);
+                this._removeNotificationElement(notifications[n]);
               else
                 this.removeNotification(notifications[n]);
             }
             this.currentNotification = null;
 
             // Must clear up any currently animating notification
             if (aImmediate)
               this._finishAnimation();
@@ -223,17 +236,17 @@
             }
             else {
               this._closedNotification = aNotification;
               var notifications = this.allNotifications;
               var idx = notifications.length - 1;
               this.currentNotification = (idx >= 0) ? notifications[idx] : null;
 
               if (skipAnimation) {
-                this.removeChild(this._closedNotification);
+                this._removeNotificationElement(this._closedNotification);
                 this._closedNotification = null;
                 this._setBlockingState(this.currentNotification);
                 return;
               }
 
               aNotification.style.marginTop = -height + "px";
               aNotification.style.opacity = 0;
             }
@@ -243,17 +256,17 @@
         </body>
       </method>
 
       <method name="_finishAnimation">
         <body><![CDATA[
           if (this._animating) {
             this._animating = false;
             if (this._closedNotification) {
-              this.removeChild(this._closedNotification);
+              this._removeNotificationElement(this._closedNotification);
               this._closedNotification = null;
             }
             this._setBlockingState(this.currentNotification);
           }
         ]]></body>
       </method>
 
       <method name="_setBlockingState">