Bug 943418. Check for active documents, not current inners, in History APIs. r=bholley
authorBoris Zbarsky <bzbarsky@mit.edu>
Wed, 27 Nov 2013 11:16:07 -0500
changeset 157829 200590163c1f20fc9af1373fc54f67306d7ce668
parent 157828 c4883720cfb3191da8c79b2605abe6e4bda09bcb
child 157830 63a7d0d3dd83862dede3ad9b770dd4077ee3fd16
push id25726
push usercbook@mozilla.com
push dateThu, 28 Nov 2013 10:47:25 +0000
treeherdermozilla-central@cdca43b7657d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbholley
bugs943418
milestone28.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 943418. Check for active documents, not current inners, in History APIs. r=bholley
content/base/public/nsIDocument.h
dom/base/nsHistory.cpp
dom/base/nsPIDOMWindow.h
dom/base/test/mochitest.ini
dom/base/test/test_history_document_open.html
--- a/content/base/public/nsIDocument.h
+++ b/content/base/public/nsIDocument.h
@@ -902,17 +902,17 @@ public:
     return outer && outer->IsBackground();
   }
   
   /**
    * Return the inner window used as the script compilation scope for
    * this document. If you're not absolutely sure you need this, use
    * GetWindow().
    */
-  nsPIDOMWindow* GetInnerWindow()
+  nsPIDOMWindow* GetInnerWindow() const
   {
     return mRemovedFromDocShell ? nullptr : mWindow;
   }
 
   /**
    * Return the outer window ID.
    */
   uint64_t OuterWindowID() const
@@ -1608,17 +1608,20 @@ public:
   /**
    * Return whether the document and all its ancestors are visible in the sense of
    * pageshow / hide.
    */
   bool IsVisibleConsideringAncestors() const;
 
   /**
    * Return true when this document is active, i.e., the active document
-   * in a content viewer.
+   * in a content viewer.  Note that this will return true for bfcached
+   * documents, so this does NOT match the "active document" concept in
+   * the WHATWG spec.  That would correspond to GetInnerWindow() &&
+   * GetInnerWindow()->IsCurrentInnerWindow().
    */
   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);
--- a/dom/base/nsHistory.cpp
+++ b/dom/base/nsHistory.cpp
@@ -65,17 +65,17 @@ nsHistory::WrapObject(JSContext* aCx, JS
 {
   return HistoryBinding::Wrap(aCx, aScope, this);
 }
 
 uint32_t
 nsHistory::GetLength(ErrorResult& aRv) const
 {
   nsCOMPtr<nsPIDOMWindow> win(do_QueryReferent(mInnerWindow));
-  if (!win || !win->IsCurrentInnerWindow()) {
+  if (!win || !win->HasActiveDocument()) {
     aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
 
     return 0;
   }
 
   // Get session History from docshell
   nsCOMPtr<nsISHistory> sHistory = GetSessionHistory();
   if (!sHistory) {
@@ -101,17 +101,17 @@ nsHistory::GetState(JSContext* aCx, Erro
 {
   nsCOMPtr<nsPIDOMWindow> win(do_QueryReferent(mInnerWindow));
   if (!win) {
     aRv.Throw(NS_ERROR_NOT_AVAILABLE);
 
     return JS::UndefinedValue();
   }
 
-  if (!win->IsCurrentInnerWindow()) {
+  if (!win->HasActiveDocument()) {
     aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
 
     return JS::UndefinedValue();
   }
 
   nsCOMPtr<nsIDocument> doc =
     do_QueryInterface(win->GetExtantDoc());
   if (!doc) {
@@ -141,17 +141,17 @@ nsHistory::GetState(JSContext* aCx, Erro
 
   return JS::UndefinedValue();
 }
 
 void
 nsHistory::Go(int32_t aDelta, ErrorResult& aRv)
 {
   nsCOMPtr<nsPIDOMWindow> win(do_QueryReferent(mInnerWindow));
-  if (!win || !win->IsCurrentInnerWindow()) {
+  if (!win || !win->HasActiveDocument()) {
     aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
 
     return;
   }
 
   if (!aDelta) {
     nsCOMPtr<nsPIDOMWindow> window(do_GetInterface(GetDocShell()));
 
@@ -197,17 +197,17 @@ nsHistory::Go(int32_t aDelta, ErrorResul
   // from GotoIndex() can lead to exceptions and a possible leak
   // of history length
 }
 
 void
 nsHistory::Back(ErrorResult& aRv)
 {
   nsCOMPtr<nsPIDOMWindow> win(do_QueryReferent(mInnerWindow));
-  if (!win || !win->IsCurrentInnerWindow()) {
+  if (!win || !win->HasActiveDocument()) {
     aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
 
     return;
   }
 
   nsCOMPtr<nsISHistory> sHistory = GetSessionHistory();
   nsCOMPtr<nsIWebNavigation> webNav(do_QueryInterface(sHistory));
   if (!webNav) {
@@ -218,17 +218,17 @@ nsHistory::Back(ErrorResult& aRv)
 
   webNav->GoBack();
 }
 
 void
 nsHistory::Forward(ErrorResult& aRv)
 {
   nsCOMPtr<nsPIDOMWindow> win(do_QueryReferent(mInnerWindow));
-  if (!win || !win->IsCurrentInnerWindow()) {
+  if (!win || !win->HasActiveDocument()) {
     aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
 
     return;
   }
 
   nsCOMPtr<nsISHistory> sHistory = GetSessionHistory();
   nsCOMPtr<nsIWebNavigation> webNav(do_QueryInterface(sHistory));
   if (!webNav) {
@@ -263,17 +263,17 @@ nsHistory::PushOrReplaceState(JSContext*
 {
   nsCOMPtr<nsPIDOMWindow> win(do_QueryReferent(mInnerWindow));
   if (!win) {
     aRv.Throw(NS_ERROR_NOT_AVAILABLE);
 
     return;
   }
 
-  if (!win->IsCurrentInnerWindow()) {
+  if (!win->HasActiveDocument()) {
     aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
 
     return;
   }
 
   // Check that PushState hasn't been pref'ed off.
   if (!Preferences::GetBool(aReplace ? sAllowReplaceStatePrefStr :
                             sAllowPushStatePrefStr, false)) {
--- a/dom/base/nsPIDOMWindow.h
+++ b/dom/base/nsPIDOMWindow.h
@@ -331,16 +331,27 @@ public:
   // window of that outer. Only call this on inner windows.
   bool IsCurrentInnerWindow() const
   {
     MOZ_ASSERT(IsInnerWindow(),
                "It doesn't make sense to call this on outer windows.");
     return mOuterWindow && mOuterWindow->GetCurrentInnerWindow() == this;
   }
 
+  // Returns true if the document of this window is the active document.  This
+  // is not identical to IsCurrentInnerWindow() because document.open() will
+  // keep the same document active but create a new window.
+  bool HasActiveDocument()
+  {
+    return IsCurrentInnerWindow() ||
+      (GetOuterWindow() &&
+       GetOuterWindow()->GetCurrentInnerWindow() &&
+       GetOuterWindow()->GetCurrentInnerWindow()->GetDoc() == mDoc);
+  }
+
   bool IsOuterWindow() const
   {
     return !IsInnerWindow();
   }
 
   virtual bool WouldReuseInnerWindow(nsIDocument *aNewDocument) = 0;
 
   /**
--- a/dom/base/test/mochitest.ini
+++ b/dom/base/test/mochitest.ini
@@ -13,16 +13,17 @@ support-files =
 [test_document.all_unqualified.html]
 [test_domcursor.html]
 [test_domrequest.html]
 [test_e4x_for_each.html]
 [test_error.html]
 [test_gsp-qualified.html]
 [test_gsp-quirks.html]
 [test_gsp-standards.html]
+[test_history_document_open.html]
 [test_innersize_scrollport.html]
 [test_messageChannel.html]
 [test_messageChannel_cloning.html]
 [test_messageChannel_pingpong.html]
 [test_messageChannel_post.html]
 [test_messageChannel_pref.html]
 [test_messageChannel_start.html]
 [test_messageChannel_transferable.html]
new file mode 100644
--- /dev/null
+++ b/dom/base/test/test_history_document_open.html
@@ -0,0 +1,37 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=943418
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Test for Bug 943418</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+  <script type="application/javascript">
+
+  /** Test for Bug 943418 **/
+  SimpleTest.waitForExplicitFinish();
+
+  function continueTest(f) {
+    // Make sure we're the entry script so errors get reported here
+    setTimeout(finishTest, 0, f);
+  }
+
+  function finishTest(f) {
+    f();
+    ok(true, "Got here");
+    SimpleTest.finish();
+  }
+  </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=943418">Mozilla Bug 943418</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+<iframe src="data:text/html,<script>function f() { history.length; } window.onload = function() { var f = window.f; document.open(); document.close(); parent.continueTest(f); }</script>" </script>
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>