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
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;
 };