Bug 690056. Implement a vendor-prefixed version of the visibility API. r=sicking
authorBoris Zbarsky <bzbarsky@mit.edu>
Tue, 11 Oct 2011 17:29:12 -0400
changeset 78583 c7b4452ef1d2
parent 78582 073b4ef6933f
child 78584 ff1d493bf113
push id21314
push usermak77@bonardo.net
push date2011-10-12 10:03 +0000
treeherdermozilla-central@866b2b1793cd [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssicking
bugs690056
milestone10.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 690056. Implement a vendor-prefixed version of the visibility API. r=sicking The idea is to fire the visibilitychange event synchronously during pageshow and pagehide, since we're already running script there for the pageshow/pagehide events. For docshell active state changes, we fire the event asynchronously. In all cases, the actual state changes just before the event fires.
content/base/public/nsIDocument.h
content/base/src/nsDocument.cpp
content/base/src/nsDocument.h
content/base/test/Makefile.in
content/base/test/test_bug690056.html
docshell/base/nsDocShell.cpp
docshell/test/chrome/Makefile.in
docshell/test/chrome/bug690056_window.xul
docshell/test/chrome/docshell_helpers.js
docshell/test/chrome/test_bug690056.xul
dom/base/nsPIDOMWindow.h
dom/interfaces/core/nsIDOMDocument.idl
dom/interfaces/core/nsIDOMXMLDocument.idl
dom/interfaces/html/nsIDOMHTMLDocument.idl
dom/interfaces/svg/nsIDOMSVGDocument.idl
--- a/content/base/public/nsIDocument.h
+++ b/content/base/public/nsIDocument.h
@@ -121,18 +121,18 @@ class Loader;
 
 namespace dom {
 class Link;
 class Element;
 } // namespace dom
 } // namespace mozilla
 
 #define NS_IDOCUMENT_IID      \
-{ 0xd76bcf5f, 0xd02f, 0x459a, \
- { 0xb1, 0x23, 0x8e, 0x2c, 0x9a, 0x0d, 0x84, 0x68 } }
+{ 0x448c396a, 0x013c, 0x47b8, \
+ { 0x95, 0xf4, 0x56, 0x68, 0x0f, 0x5f, 0x12, 0xf8 } }
 
 // Flag for AddStyleSheet().
 #define NS_STYLESHEET_FROM_CATALOG                (1 << 0)
 
 // Document states
 
 // RTL locale: specific to the XUL localedir attribute
 #define NS_DOCUMENT_STATE_RTL_LOCALE              NS_DEFINE_EVENT_STATE_MACRO(0)
@@ -1316,27 +1316,27 @@ public:
   virtual void EnumerateExternalResources(nsSubDocEnumFunc aCallback,
                                           void* aData) = 0;
 
   /**
    * Return whether the document is currently showing (in the sense of
    * OnPageShow() having been called already and OnPageHide() not having been
    * called yet.
    */
-  bool IsShowing() { return mIsShowing; }
+  bool IsShowing() const { return mIsShowing; }
   /**
    * Return whether the document is currently visible (in the sense of
    * OnPageHide having been called and OnPageShow not yet having been called)
    */
-  bool IsVisible() { return mVisible; }
+  bool IsVisible() const { return mVisible; }
   /**
    * Return true when this document is active, i.e., the active document
    * in a content viewer.
    */
-  bool IsActive() { return mDocumentContainer && !mRemovedFromDocShell; }
+  bool IsActive() const { return mDocumentContainer && !mRemovedFromDocShell; }
 
   void RegisterFreezableElement(nsIContent* aContent);
   bool UnregisterFreezableElement(nsIContent* aContent);
   typedef void (* FreezableElementEnumerator)(nsIContent*, void*);
   void EnumerateFreezableElements(FreezableElementEnumerator aEnumerator,
                                   void* aData);
 
 #ifdef MOZ_SMIL
@@ -1568,16 +1568,18 @@ public:
 #define DEPRECATED_OPERATION(_op) e##_op,
   enum DeprecatedOperations {
 #include "nsDeprecatedOperationList.h"
     eDeprecatedOperationCount
   };
 #undef DEPRECATED_OPERATION
   void WarnOnceAbout(DeprecatedOperations aOperation);
 
+  virtual void PostVisibilityUpdateEvent() = 0;
+
 private:
   PRUint64 mWarnedAbout;
 
 protected:
   ~nsIDocument()
   {
     // XXX The cleanup of mNodeInfoManager (calling DropDocumentReference and
     //     releasing it) happens in the nsDocument destructor. We'd prefer to
--- a/content/base/src/nsDocument.cpp
+++ b/content/base/src/nsDocument.cpp
@@ -1521,16 +1521,17 @@ nsDOMImplementation::CreateHTMLDocument(
 
   // NOTE! nsDocument::operator new() zeroes out all members, so don't
   // bother initializing members to 0.
 
 nsDocument::nsDocument(const char* aContentType)
   : nsIDocument()
   , mAnimatingImages(PR_TRUE)
   , mIsFullScreen(PR_FALSE)
+  , mVisibilityState(eHidden)
 {
   SetContentTypeInternal(nsDependentCString(aContentType));
   
 #ifdef PR_LOGGING
   if (!gDocumentLeakPRLog)
     gDocumentLeakPRLog = PR_NewLogModule("DocumentLeak");
 
   if (gDocumentLeakPRLog)
@@ -3832,16 +3833,23 @@ nsDocument::SetScriptGlobalObject(nsIScr
 
     MaybeRescheduleAnimationFrameNotifications();
   }
 
   // Remember the pointer to our window (or lack there of), to avoid
   // having to QI every time it's asked for.
   nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(mScriptGlobalObject);
   mWindow = window;
+
+  // Set our visibility state, but do not fire the event.  This is correct
+  // because either we're coming out of bfcache (in which case IsVisible() will
+  // still test false at this point and no state change will happen) or we're
+  // doing the initial document load and don't want to fire the event for this
+  // change.
+  mVisibilityState = GetVisibilityState();
 }
 
 nsIScriptGlobalObject*
 nsDocument::GetScriptHandlingObjectInternal() const
 {
   NS_ASSERTION(!mScriptGlobalObject,
                "Do not call this when mScriptGlobalObject is set!");
 
@@ -7317,16 +7325,18 @@ nsDocument::OnPageShow(bool aPersisted,
     mAnimationController->OnPageShow();
   }
 #endif
 
   if (aPersisted) {
     SetImagesNeedAnimating(PR_TRUE);
   }
 
+  UpdateVisibilityState();
+
   nsCOMPtr<nsIDOMEventTarget> target = aDispatchStartTarget;
   if (!target) {
     target = do_QueryInterface(GetWindow());
   }
   DispatchPageTransition(target, NS_LITERAL_STRING("pageshow"), aPersisted);
 }
 
 static bool
@@ -7378,16 +7388,19 @@ nsDocument::OnPageHide(bool aPersisted,
   // Now send out a PageHide event.
   nsCOMPtr<nsIDOMEventTarget> target = aDispatchStartTarget;
   if (!target) {
     target = do_QueryInterface(GetWindow());
   }
   DispatchPageTransition(target, NS_LITERAL_STRING("pagehide"), aPersisted);
 
   mVisible = PR_FALSE;
+
+  UpdateVisibilityState();
+  
   EnumerateExternalResources(NotifyPageHide, &aPersisted);
   EnumerateFreezableElements(NotifyActivityChanged, nsnull);
 }
 
 void
 nsDocument::WillDispatchMutationEvent(nsINode* aTarget)
 {
   NS_ASSERTION(mSubtreeModifiedDepth != 0 ||
@@ -8653,8 +8666,66 @@ nsDocument::SizeOf() const
     return nsINode::SetOn##name_(cx, v);                                  \
   }
 #define TOUCH_EVENT EVENT
 #define DOCUMENT_ONLY_EVENT EVENT
 #include "nsEventNameList.h"
 #undef DOCUMENT_ONLY_EVENT
 #undef TOUCH_EVENT
 #undef EVENT
+
+void
+nsDocument::UpdateVisibilityState()
+{
+  VisibilityState oldState = mVisibilityState;
+  mVisibilityState = GetVisibilityState();
+  if (oldState != mVisibilityState) {
+    nsContentUtils::DispatchTrustedEvent(this, static_cast<nsIDocument*>(this),
+                                         NS_LITERAL_STRING("mozvisibilitychange"),
+                                         false, false);
+  }
+}
+
+nsDocument::VisibilityState
+nsDocument::GetVisibilityState() const
+{
+  // We have to check a few pieces of information here:
+  // 1)  Are we in bfcache (!IsVisible())?  If so, nothing else matters.
+  // 2)  Do we have an outer window?  If not, we're hidden.  Note that we don't
+  //     want to use GetWindow here because it does weird groveling for windows
+  //     in some cases.
+  // 3)  Is our outer window background?  If so, we're hidden.
+  // Otherwise, we're visible.
+  if (!IsVisible() || !mWindow || !mWindow->GetOuterWindow() ||
+      mWindow->GetOuterWindow()->IsBackground()) {
+    return eHidden;
+  }
+
+  return eVisible;
+}
+
+/* virtual */ void
+nsDocument::PostVisibilityUpdateEvent()
+{
+  nsCOMPtr<nsIRunnable> event =
+    NS_NewRunnableMethod(this, &nsDocument::UpdateVisibilityState);
+  NS_DispatchToMainThread(event);
+}
+
+NS_IMETHODIMP
+nsDocument::GetMozHidden(bool* aHidden)
+{
+  *aHidden = mVisibilityState != eVisible;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocument::GetMozVisibilityState(nsAString& aState)
+{
+  // This needs to stay in sync with the VisibilityState enum.
+  static const char states[][8] = {
+    "hidden",
+    "visible"
+  };
+  PR_STATIC_ASSERT(NS_ARRAY_LENGTH(states) == eVisibilityStateCount);
+  aState.AssignASCII(states[mVisibilityState]);
+  return NS_OK;
+}
--- a/content/base/src/nsDocument.h
+++ b/content/base/src/nsDocument.h
@@ -943,16 +943,22 @@ public:
 
   virtual void ResetFullScreenElement();
   virtual Element* GetFullScreenElement();
   virtual void RequestFullScreen(Element* aElement);
   virtual void CancelFullScreen();
   virtual void UpdateFullScreenStatus(bool aIsFullScreen);
   virtual bool IsFullScreenDoc();
 
+  // This method may fire a DOM event; if it does so it will happen
+  // synchronously.
+  void UpdateVisibilityState();
+  // Posts an event to call UpdateVisibilityState
+  virtual void PostVisibilityUpdateEvent();
+
 protected:
   friend class nsNodeUtils;
 
   /**
    * Check that aId is not empty and log a message to the console
    * service if it is.
    * @returns PR_TRUE if aId looks correct, PR_FALSE otherwise.
    */
@@ -1145,16 +1151,24 @@ protected:
   nsCOMPtr<nsIContent> mFirstBaseNodeWithHref;
 
   nsEventStates mDocumentState;
   nsEventStates mGotDocumentState;
 
   nsRefPtr<nsDOMNavigationTiming> mTiming;
 private:
   friend class nsUnblockOnloadEvent;
+  // This needs to stay in sync with the list in GetMozVisibilityState.
+  enum VisibilityState {
+    eHidden = 0,
+    eVisible,
+    eVisibilityStateCount
+  };
+  // Recomputes the visibility state but doesn't set the new value.
+  VisibilityState GetVisibilityState() const;
 
   void PostUnblockOnloadEvent();
   void DoUnblockOnload();
 
   nsresult CheckFrameOptions();
   nsresult InitCSP();
 
   /**
@@ -1225,16 +1239,18 @@ private:
 
   nsCString mScrollToRef;
   PRUint8 mScrolledToRefAlready : 1;
   PRUint8 mChangeScrollPosWhenScrollingToRef : 1;
 
   // Tracking for images in the document.
   nsDataHashtable< nsPtrHashKey<imgIRequest>, PRUint32> mImageTracker;
 
+  VisibilityState mVisibilityState;
+
 #ifdef DEBUG
 protected:
   bool mWillReparent;
 #endif
 };
 
 #define NS_DOCUMENT_INTERFACE_TABLE_BEGIN(_class)                             \
   NS_NODE_OFFSET_AND_INTERFACE_TABLE_BEGIN(_class)                            \
--- a/content/base/test/Makefile.in
+++ b/content/base/test/Makefile.in
@@ -511,16 +511,17 @@ include $(topsrcdir)/config/rules.mk
 		test_bug675166.html \
 		test_bug682554.html \
 		test_bug682592.html \
 		bug682592-subframe.html \
 		bug682592-subframe-ref.html \
 		test_bug684671.html \
 		test_bug685798.html \
 		test_bug686449.xhtml \
+		test_bug690056.html \
 		test_bug692434.html \
 		file_bug692434.xml \
 		$(NULL)
 
 _CHROME_FILES =	\
 		test_bug357450.js \
 		$(NULL)
 
new file mode 100644
--- /dev/null
+++ b/content/base/test/test_bug690056.html
@@ -0,0 +1,54 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=690056
+-->
+<head>
+  <title>Test for Bug 690056</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=690056">Mozilla Bug 690056</a>
+<p id="display">
+  <iframe id="x"></iframe>
+  <iframe style="display: none" id="y"></iframe>
+</p>
+<div id="content" style="display: none">
+  
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 690056 **/
+SimpleTest.waitForExplicitFinish();
+is(document.mozHidden, false, "Document should not be hidden during load");
+is(document.mozVisibilityState, "visible",
+   "Document should be visible during load");
+
+addLoadEvent(function() {
+  var doc = document.implementation.createDocument("", "", null);
+  is(doc.mozHidden, true, "Data documents should be hidden");
+  is(doc.mozVisibilityState, "hidden", "Data documents really should be hidden");
+
+  is(document.mozHidden, false, "Document should not be hidden onload");
+  is(document.mozVisibilityState, "visible",
+     "Document should be visible onload");
+
+  is($("x").contentDocument.mozHidden, false,
+     "Subframe document should not be hidden onload");
+  is($("x").contentDocument.mozVisibilityState, "visible",
+     "Subframe document should be visible onload");
+  is($("y").contentDocument.mozHidden, false,
+     "display:none subframe document should not be hidden onload");
+  is($("y").contentDocument.mozVisibilityState, "visible",
+     "display:none subframe document should be visible onload");
+  
+  SimpleTest.finish();
+});
+
+
+</script>
+</pre>
+</body>
+</html>
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -4877,16 +4877,20 @@ nsDocShell::SetIsActive(bool aIsActive)
   GetPresShell(getter_AddRefs(pshell));
   if (pshell)
     pshell->SetIsActive(aIsActive);
 
   // Tell the window about it
   nsCOMPtr<nsPIDOMWindow> win = do_QueryInterface(mScriptGlobal);
   if (win) {
       win->SetIsBackground(!aIsActive);
+      nsCOMPtr<nsIDocument> doc = do_QueryInterface(win->GetExtantDocument());
+      if (doc) {
+          doc->PostVisibilityUpdateEvent();
+      }
   }
 
   // Recursively tell all of our children
   PRInt32 n = mChildList.Count();
   for (PRInt32 i = 0; i < n; ++i) {
       nsCOMPtr<nsIDocShell> docshell = do_QueryInterface(ChildAt(i));
       if (docshell)
         docshell->SetIsActive(aIsActive);
--- a/docshell/test/chrome/Makefile.in
+++ b/docshell/test/chrome/Makefile.in
@@ -115,16 +115,18 @@ include $(topsrcdir)/config/rules.mk
 		test_bug454235.xul \
 		bug454235-subframe.xul \
 		test_bug456980.xul \
 		test_bug662200.xul \
 		bug662200_window.xul \
 		662200a.html \
 		662200b.html \
 		662200c.html \
+		test_bug690056.xul \
+		bug690056_window.xul \
 		$(NULL)
 
 _DOCSHELL_SUBHARNESS = \
     docshell_helpers.js \
     generic.html \
     $(NULL)
 
 libs:: $(_HTTP_FILES)
new file mode 100644
--- /dev/null
+++ b/docshell/test/chrome/bug690056_window.xul
@@ -0,0 +1,176 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+
+<window id="690056Test"
+        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+        width="600"
+        height="600"
+        onload="setTimeout(nextTest,0);"
+        title="bug 6500056 test">
+
+  <script type="application/javascript" src= "chrome://mochikit/content/chrome-harness.js" />
+  <script type="application/javascript" src="docshell_helpers.js" />
+  <script type="application/javascript"><![CDATA[
+    var tests = testIterator();
+
+    function nextTest() {
+      tests.next();
+    }
+
+    // Makes sure that we fire the visibilitychange events
+    function testIterator() {
+      // Enable bfcache
+      enableBFCache(8);
+
+      // Load something for a start
+      doPageNavigation({
+        uri: 'data:text/html,<title>initial load</title>',
+        onNavComplete: nextTest
+      });
+      yield;
+
+      // Now load a new page
+      doPageNavigation({
+        uri: 'data:text/html,<title>new load</title>',
+        eventsToListenFor: [ "pageshow", "pagehide", "mozvisibilitychange" ],
+        expectedEvents: [ { type: "pagehide",
+                            title: "initial load",
+                            persisted: true },
+                          { type: "mozvisibilitychange",
+                            title: "initial load",
+                            visibilityState: "hidden",
+                            hidden: true },
+                          // No visibilitychange events fired for initial pageload
+                          { type: "pageshow",
+                            title: "new load",
+                            persisted: false }, // false on initial load
+                        ],
+        onNavComplete: nextTest
+      });
+      yield;
+
+      // Now go back
+      doPageNavigation({
+        back: true,
+        eventsToListenFor: [ "pageshow", "pagehide", "mozvisibilitychange" ],
+        expectedEvents: [ { type: "pagehide",
+                            title: "new load",
+                            persisted: true },
+                          { type: "mozvisibilitychange",
+                            title: "new load",
+                            visibilityState: "hidden",
+                            hidden: true },
+                          { type: "mozvisibilitychange",
+                            title: "initial load",
+                            visibilityState: "visible",
+                            hidden: false },
+                          { type: "pageshow",
+                            title: "initial load",
+                            persisted: true },
+                        ],
+        onNavComplete: nextTest
+      });
+      yield;
+
+      // And forward
+      doPageNavigation({
+        forward: true,
+        eventsToListenFor: [ "pageshow", "pagehide", "mozvisibilitychange" ],
+        expectedEvents: [ { type: "pagehide",
+                            title: "initial load",
+                            persisted: true },
+                          { type: "mozvisibilitychange",
+                            title: "initial load",
+                            visibilityState: "hidden",
+                            hidden: true },
+                          { type: "mozvisibilitychange",
+                            title: "new load",
+                            visibilityState: "visible",
+                            hidden: false },
+                          { type: "pageshow",
+                            title: "new load",
+                            persisted: true },
+                        ],
+        onNavComplete: nextTest
+      });
+      yield;
+
+      function generateDetector(state, hidden, title, name) {
+        var detector = function (event) {
+          is(event.target.mozHidden, hidden,
+             name + " hidden value does not match");
+          is(event.target.mozVisibilityState, state,
+             name + " state value does not match");
+          is(event.target.title, title,
+             name + " title value does not match");
+          document.getElementById("content")
+                  .removeEventListener("mozvisibilitychange",
+                                       detector,
+                                       true);
+          nextTest();
+        }
+
+        document.getElementById("content")
+                .addEventListener("mozvisibilitychange", detector, true);
+      }
+      
+      generateDetector("hidden", true, "new load", "Going hidden");
+        
+      // Now flip our docshell to not active
+      document.getElementById("content").docShellIsActive = false;
+      yield;
+
+      // And navigate back; there should be no visibility state transitions
+      doPageNavigation({
+        back: true,
+        eventsToListenFor: [ "pageshow", "pagehide", "mozvisibilitychange" ],
+        expectedEvents: [ { type: "pagehide",
+                            title: "new load",
+                            persisted: true },
+                          { type: "pageshow",
+                            title: "initial load",
+                            persisted: true },
+                        ],
+        unexpectedEvents: [ "mozvisibilitychange" ],
+        onNavComplete: nextTest
+      });
+      yield;
+
+      generateDetector("visible", false, "initial load", "Going visible");
+
+      // Now set the docshell active again
+      document.getElementById("content").docShellIsActive = true;
+      yield;
+
+      // And forward
+      doPageNavigation({
+        forward: true,
+        eventsToListenFor: [ "pageshow", "pagehide", "mozvisibilitychange" ],
+        expectedEvents: [ { type: "pagehide",
+                            title: "initial load",
+                            persisted: true },
+                          { type: "mozvisibilitychange",
+                            title: "initial load",
+                            visibilityState: "hidden",
+                            hidden: true },
+                          { type: "mozvisibilitychange",
+                            title: "new load",
+                            visibilityState: "visible",
+                            hidden: false },
+                          { type: "pageshow",
+                            title: "new load",
+                            persisted: true },
+                        ],
+        onNavComplete: nextTest
+      });
+      yield;
+
+      // Tell the framework the test is finished.  Include the final 'yield' 
+      // statement to prevent a StopIteration exception from being thrown.
+      finish();
+      yield;      
+    }
+  ]]></script>
+
+  <browser type="content-primary" flex="1" id="content" src="about:blank"/>
+</window>
\ No newline at end of file
--- a/docshell/test/chrome/docshell_helpers.js
+++ b/docshell/test/chrome/docshell_helpers.js
@@ -13,16 +13,18 @@ for each (var name in imports) {
 const NAV_NONE = 0;
 const NAV_BACK = 1;
 const NAV_FORWARD = 2;
 const NAV_URI = 3;
 const NAV_RELOAD = 4;
 
 var gExpectedEvents;          // an array of events which are expected to
                               // be triggered by this navigation
+var gUnexpectedEvents;        // an array of event names which are NOT expected
+                              // to be triggered by this navigation
 var gFinalEvent;              // true if the last expected event has fired
 var gUrisNotInBFCache = [];   // an array of uri's which shouldn't be stored
                               // in the bfcache
 var gNavType = NAV_NONE;      // defines the most recent navigation type
                               // executed by doPageNavigation
 var gOrigMaxTotalViewers =    // original value of max_total_viewers,
   undefined;                  // to be restored at end of test
 
@@ -90,16 +92,18 @@ function doPageNavigation(params) {
   let back = params.back ? params.back : false;
   let forward = params.forward ? params.forward : false;
   let reload = params.reload ? params.reload : false;
   let uri = params.uri ? params.uri : false;
   let eventsToListenFor = typeof(params.eventsToListenFor) != "undefined" ?
     params.eventsToListenFor : ["pageshow"];
   gExpectedEvents = typeof(params.eventsToListenFor) == "undefined" || 
     eventsToListenFor.length == 0 ? undefined : params.expectedEvents; 
+  gUnexpectedEvents = typeof(params.eventsToListenFor) == "undefined" || 
+    eventsToListenFor.length == 0 ? undefined : params.unexpectedEvents; 
   let preventBFCache = (typeof[params.preventBFCache] == "undefined") ? 
     false : params.preventBFCache;
   let waitOnly = (typeof(params.waitForEventsOnly) == "boolean" 
     && params.waitForEventsOnly);
   
   // Do some sanity checking on arguments.  
   if (back && forward)
     throw "Can't specify both back and forward";
@@ -124,16 +128,20 @@ function doPageNavigation(params) {
   for each (let anEventType in eventsToListenFor) {
     let eventFound = false;
     if ( (anEventType == "pageshow") && (!gExpectedEvents) )
       eventFound = true;
     for each (let anExpectedEvent in gExpectedEvents) {
       if (anExpectedEvent.type == anEventType)
         eventFound = true;
     }
+    for each (let anExpectedEventType in gUnexpectedEvents) {
+      if (anExpectedEventType == anEventType)
+        eventFound = true;
+    }
     if (!eventFound)
       throw "Event type " + anEventType + " is specified in " +
         "eventsToListenFor, but not in expectedEvents";
   }
   
   // If the test explicitly sets .eventsToListenFor to [], don't wait for any 
   // events.
   gFinalEvent = eventsToListenFor.length == 0 ? true : false;
@@ -255,17 +263,22 @@ function pageEventListener(event) {
   if ( (event.type == "pageshow") && 
     (gNavType == NAV_BACK || gNavType == NAV_FORWARD) ) {
     let uri = TestWindow.getBrowser().currentURI.spec;
     if (uri in gUrisNotInBFCache) {
       ok(!event.persisted, "pageshow event has .persisted = false, even " +
        "though it was loaded with .preventBFCache previously\n");
     }
   }
-  
+
+  if (typeof(gUnexpectedEvents) != "undefined") {
+    is(gUnexpectedEvents.indexOf(event.type), -1,
+       "Should not get unexpected event " + event.type);
+  }  
+
   // If no expected events were specified, mark the final event as having been 
   // triggered when a pageshow event is fired; this will allow 
   // doPageNavigation() to return.
   if ((typeof(gExpectedEvents) == "undefined") && event.type == "pageshow")
   {
     setTimeout(function() { gFinalEvent = true; }, 0);
     return;
   }
@@ -296,16 +309,28 @@ function pageEventListener(event) {
   }  
   
   if (typeof(expected.persisted) != "undefined") {
     is(event.persisted, expected.persisted, 
       "The persisted property of the " + event.type + " event on page " +
       event.originalTarget.location + " had an unexpected value"); 
   }
 
+  if ("visibilityState" in expected) {
+    is(event.originalTarget.mozVisibilityState, expected.visibilityState,
+       "The visibilityState property of the document on page " +
+       event.originalTarget.location + " had an unexpected value");
+  }
+
+  if ("hidden" in expected) {
+    is(event.originalTarget.mozHidden, expected.hidden,
+       "The hidden property of the document on page " +
+       event.originalTarget.location + " had an unexpected value");
+  }
+
   // If we're out of expected events, let doPageNavigation() return.
   if (gExpectedEvents.length == 0)
     setTimeout(function() { gFinalEvent = true; }, 0);
 }
 
 /**
  * End a test.  
  */
new file mode 100644
--- /dev/null
+++ b/docshell/test/chrome/test_bug690056.xul
@@ -0,0 +1,26 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=690056
+-->
+<window title="Mozilla Bug 690056"
+        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+
+  <!-- test results are displayed in the html:body -->
+  <body xmlns="http://www.w3.org/1999/xhtml">
+  <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=690056"
+     target="_blank">Mozilla Bug 690056</a>
+  </body>
+
+  <!-- test code goes here -->
+  <script type="application/javascript">
+  <![CDATA[
+  /** Test for Bug 690056 **/
+SimpleTest.waitForExplicitFinish();
+window.open("bug690056_window.xul", "bug690056",
+            "chrome,width=600,height=600");
+  ]]>
+  </script>
+</window>
--- a/dom/base/nsPIDOMWindow.h
+++ b/dom/base/nsPIDOMWindow.h
@@ -92,31 +92,39 @@ public:
 
   virtual void ActivateOrDeactivate(bool aActivate) = 0;
 
   // this is called GetTopWindowRoot to avoid conflicts with nsIDOMWindow::GetWindowRoot
   virtual already_AddRefed<nsPIWindowRoot> GetTopWindowRoot() = 0;
 
   virtual void SetActive(bool aActive)
   {
+    NS_PRECONDITION(IsOuterWindow(),
+                    "active state is only maintained on outer windows");
     mIsActive = aActive;
   }
 
   bool IsActive()
   {
+    NS_PRECONDITION(IsOuterWindow(),
+                    "active state is only maintained on outer windows");
     return mIsActive;
   }
 
   virtual void SetIsBackground(bool aIsBackground)
   {
+    NS_PRECONDITION(IsOuterWindow(),
+                    "background state is only maintained on outer windows");
     mIsBackground = aIsBackground;
   }
 
   bool IsBackground()
   {
+    NS_PRECONDITION(IsOuterWindow(),
+                    "background state is only maintained on outer windows");
     return mIsBackground;
   }
 
   nsIDOMEventTarget* GetChromeEventHandler() const
   {
     return mChromeEventHandler;
   }
 
--- a/dom/interfaces/core/nsIDOMDocument.idl
+++ b/dom/interfaces/core/nsIDOMDocument.idl
@@ -61,17 +61,17 @@ interface nsIDOMLocation;
  * cannot exist outside the context of a Document, the nsIDOMDocument 
  * interface also contains the factory methods needed to create these 
  * objects.
  *
  * For more information on this interface please see 
  * http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html
  */
 
-[scriptable, uuid(3f845f32-cb34-459c-8f79-2dfaa3088bbf)]
+[scriptable, uuid(CD4CD7C3-C688-4E50-9A72-4A00EABE66AB)]
 interface nsIDOMDocument : nsIDOMNode
 {
   readonly attribute nsIDOMDocumentType         doctype;
   readonly attribute nsIDOMDOMImplementation    implementation;
   readonly attribute nsIDOMElement              documentElement;
   nsIDOMElement                 createElement(in DOMString tagName)
                                   raises(DOMException);
   nsIDOMDocumentFragment        createDocumentFragment();
@@ -406,9 +406,15 @@ interface nsIDOMDocument : nsIDOMNode
 
   /**
    * Inline event handler for readystatechange events.
    */
   [implicit_jscontext] attribute jsval onreadystatechange;
 
   [implicit_jscontext] attribute jsval onmouseenter;
   [implicit_jscontext] attribute jsval onmouseleave;
+
+  /**
+   * Visibility API implementation.
+   */
+  readonly attribute boolean mozHidden;
+  readonly attribute DOMString mozVisibilityState;
 };
--- a/dom/interfaces/core/nsIDOMXMLDocument.idl
+++ b/dom/interfaces/core/nsIDOMXMLDocument.idl
@@ -33,17 +33,17 @@
  * 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 ***** */
 
 #include "nsIDOMDocument.idl"
 
-[scriptable, uuid(9f566fd8-8bd7-49eb-be8b-16fb50d00d32)]
+[scriptable, uuid(BB4D4D76-3802-4191-88E2-933BA609C4E1)]
 interface nsIDOMXMLDocument : nsIDOMDocument
 {
   // DOM Level 3 Load & Save, DocumentLS
   // http://www.w3.org/TR/DOM-Level-3-LS/load-save.html#LS-DocumentLS
   /**
    * Whether to load synchronously or asynchronously.
    * The default is async==true.
    */
--- a/dom/interfaces/html/nsIDOMHTMLDocument.idl
+++ b/dom/interfaces/html/nsIDOMHTMLDocument.idl
@@ -42,17 +42,17 @@
 /**
  * The nsIDOMHTMLDocument interface is the interface to a [X]HTML
  * document object.
  *
  * @see <http://www.whatwg.org/html/>
  */
 interface nsISelection;
 
-[scriptable, uuid(280857b8-c52c-455e-8b47-c56ad96614f7)]
+[scriptable, uuid(C94A5F14-F79F-4054-A93C-E8FF35623460)]
 interface nsIDOMHTMLDocument : nsIDOMDocument
 {
   readonly attribute DOMString            URL;
            attribute DOMString            domain;
            attribute DOMString            cookie;
   // returns "BackCompat" if we're in quirks mode,
   // or "CSS1Compat" if we're in strict mode
   readonly attribute DOMString            compatMode;
--- a/dom/interfaces/svg/nsIDOMSVGDocument.idl
+++ b/dom/interfaces/svg/nsIDOMSVGDocument.idl
@@ -34,15 +34,15 @@
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 #include "nsIDOMDocument.idl"
 
 interface nsIDOMSVGSVGElement;
 
-[scriptable, uuid(78ebe55f-631f-4b3b-9eba-c3689ff5ccbc)]
+[scriptable, uuid(B3806DF6-7ED4-4426-84E6-545EEFE5AA9A)]
 interface nsIDOMSVGDocument : nsIDOMDocument
 {
   readonly attribute DOMString domain;
   readonly attribute DOMString URL;
   readonly attribute nsIDOMSVGSVGElement rootElement;
 };