Bug 1350858 - Only allow aOpener to be non-null at the first call to SetOpenerWindow(). r?bz draft
authorSamael Wang <freesamael@gmail.com>
Fri, 05 May 2017 16:40:55 +0800
changeset 573058 7359b3f68dc7f7487dad4017861742b808cf026d
parent 572425 4a6a71f4aa22e4dc3961884ce505ce34bdd799a2
child 627218 c0ceb65480f7759c8f41e9ebfeaa2d4c79c1450e
push id57293
push userbmo:sawang@mozilla.com
push dateFri, 05 May 2017 09:18:42 +0000
reviewersbz
bugs1350858
milestone55.0a1
Bug 1350858 - Only allow aOpener to be non-null at the first call to SetOpenerWindow(). r?bz Currently gecko changes the opener of an existing window if it's the target of a window.open() call or link click. However the spec reads window.opener "must return the WindowProxy object of the browsing context from which the current browsing context was created"; not the browsing contexts triggering subsequent loads. This patch fixes the behavior by not setting window.opener if it's not the first load causing window to be created, and not allowing aOpener to be non-null after first SetOpenerWindow call, with an exception of setting opener from chrome script. MozReview-Commit-ID: 6cMmdlSjQ5J
dom/base/nsFrameLoader.cpp
dom/base/nsGlobalWindow.cpp
dom/base/nsGlobalWindow.h
dom/base/nsPIDOMWindow.h
dom/ipc/ContentChild.cpp
testing/web-platform/meta/html/browsers/windows/browsing-context-names/browsing-context-choose-parent-003.html.ini
toolkit/components/windowwatcher/nsWindowWatcher.cpp
xpfe/appshell/nsWebShellWindow.cpp
--- a/dom/base/nsFrameLoader.cpp
+++ b/dom/base/nsFrameLoader.cpp
@@ -2395,17 +2395,17 @@ nsFrameLoader::MaybeCreateDocShell()
 
   nsCOMPtr<nsPIDOMWindowOuter> win_private(mDocShell->GetWindow());
   nsCOMPtr<nsIBaseWindow> base_win(do_QueryInterface(mDocShell));
   if (win_private) {
     win_private->SetFrameElementInternal(frame_element);
 
     // Set the opener window if we have one provided here
     if (mOpener) {
-      win_private->SetOpenerWindow(mOpener, true);
+      win_private->SetOpenerWindow(mOpener);
       mOpener = nullptr;
     }
   }
 
   // This is kinda whacky, this call doesn't really create anything,
   // but it must be called to make sure things are properly
   // initialized.
   if (NS_FAILED(base_win->Create()) || !win_private) {
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -1556,19 +1556,16 @@ nsGlobalWindow::nsGlobalWindow(nsGlobalW
     mAllowScriptsToClose(false),
     mTopLevelOuterContentWindow(false),
     mSuspendDepth(0),
     mFreezeDepth(0),
     mFocusMethod(0),
     mSerial(0),
     mIdleRequestCallbackCounter(1),
     mIdleRequestExecutor(nullptr),
-#ifdef DEBUG
-    mSetOpenerWindowCalled(false),
-#endif
 #ifdef MOZ_B2G
     mNetworkUploadObserverEnabled(false),
     mNetworkDownloadObserverEnabled(false),
 #endif
     mCleanedUp(false),
     mDialogAbuseCount(0),
     mAreDialogsEnabled(true),
 #ifdef DEBUG
@@ -3576,56 +3573,59 @@ nsGlobalWindow::DetachFromDocShell()
     mFrames->SetDocShell(nullptr);
   }
 
   MaybeForgiveSpamCount();
   CleanUp();
 }
 
 void
-nsGlobalWindow::SetOpenerWindow(nsPIDOMWindowOuter* aOpener,
-                                bool aOriginalOpener)
-{
-  FORWARD_TO_OUTER_VOID(SetOpenerWindow, (aOpener, aOriginalOpener));
+nsGlobalWindow::SetOpenerWindow(nsPIDOMWindowOuter* aOpener)
+{
+  SetOpenerWindowInternal(aOpener, /* aForceOverride */ false);
+}
+
+void
+nsGlobalWindow::SetOpenerWindowInternal(nsPIDOMWindowOuter* aOpener,
+                                        bool aForceOverride)
+{
+  FORWARD_TO_OUTER_VOID(SetOpenerWindowInternal, (aOpener, aForceOverride));
 
   nsWeakPtr opener = do_GetWeakReference(aOpener);
+  NS_ASSERTION(opener || !aOpener, "Opener must support weak references!");
   if (opener == mOpener) {
     return;
   }
 
-  NS_ASSERTION(!aOriginalOpener || !mSetOpenerWindowCalled,
-               "aOriginalOpener is true, but not first call to "
-               "SetOpenerWindow!");
-  NS_ASSERTION(aOpener || !aOriginalOpener,
-               "Shouldn't set mHadOriginalOpener if aOpener is null");
+  MOZ_ASSERT(!aOpener || aForceOverride || !mHadOriginalOpener,
+             "Trying to set opener to a non-null value while aForceOverride "
+             "is false and not first call to SetOpenerWindow!");
 
   mOpener = opener.forget();
-  NS_ASSERTION(mOpener || !aOpener, "Opener must support weak references!");
 
   // Check that the js visible opener matches! We currently don't depend on this
   // being true outside of nightly, so we disable the assertion in optimized
   // release / beta builds.
   nsPIDOMWindowOuter* contentOpener = GetSanitizedOpener(aOpener);
 
   // contentOpener is not used when the DIAGNOSTIC_ASSERT is compiled out.
   mozilla::Unused << contentOpener;
   MOZ_DIAGNOSTIC_ASSERT(!contentOpener || !mTabGroup ||
     mTabGroup == Cast(contentOpener)->mTabGroup);
 
-  if (aOriginalOpener) {
-    MOZ_ASSERT(!mHadOriginalOpener,
-               "Probably too late to call ComputeIsSecureContext again");
+  if (!mHadOriginalOpener) {
+    // Whether mOpener is set to a non-null value or not, we consider the
+    // opener has been set and prevent subsequent calls to change it to any
+    // non-null values (unless aForceOverride is set).
     mHadOriginalOpener = true;
-    mOriginalOpenerWasSecureContext =
-      aOpener->GetCurrentInnerWindow()->IsSecureContext();
-  }
-
-#ifdef DEBUG
-  mSetOpenerWindowCalled = true;
-#endif
+    if (aOpener) {
+      mOriginalOpenerWasSecureContext =
+        aOpener->GetCurrentInnerWindow()->IsSecureContext();
+    }
+  }
 }
 
 static
 already_AddRefed<EventTarget>
 TryGetTabChildGlobalAsEventTarget(nsISupports *aFrom)
 {
   nsCOMPtr<nsIFrameLoaderOwner> frameLoaderOwner = do_QueryInterface(aFrom);
   if (!frameLoaderOwner) {
@@ -5427,17 +5427,17 @@ nsGlobalWindow::SetOpener(JSContext* aCx
   if (win) {
     if (!win->IsCurrentInnerWindow()) {
       aError.Throw(NS_ERROR_FAILURE);
       return;
     }
     outer = win->GetOuterWindow();
   }
 
-  SetOpenerWindow(outer, false);
+  SetOpenerWindowInternal(outer, /* aForceOverride */ true);
 }
 
 void
 nsGlobalWindow::GetStatusOuter(nsAString& aStatus)
 {
   MOZ_RELEASE_ASSERT(IsOuterWindow());
 
   aStatus = mStatus;
--- a/dom/base/nsGlobalWindow.h
+++ b/dom/base/nsGlobalWindow.h
@@ -415,18 +415,17 @@ public:
   virtual void DetachFromDocShell() override;
   virtual nsresult SetNewDocument(nsIDocument *aDocument,
                                   nsISupports *aState,
                                   bool aForceReuseInnerWindow) override;
 
   // Outer windows only.
   void DispatchDOMWindowCreated();
 
-  virtual void SetOpenerWindow(nsPIDOMWindowOuter* aOpener,
-                               bool aOriginalOpener) override;
+  virtual void SetOpenerWindow(nsPIDOMWindowOuter* aOpener) override;
 
   // Outer windows only.
   virtual void EnsureSizeAndPositionUpToDate() override;
 
   virtual void EnterModalState() override;
   virtual void LeaveModalState() override;
 
   // Outer windows only.
@@ -1778,16 +1777,19 @@ protected:
   void CheckForDPIChange();
 
 private:
   // Fire the JS engine's onNewGlobalObject hook.  Only used on inner windows.
   void FireOnNewGlobalObject();
 
   void DisconnectEventTargetObjects();
 
+  // Set the opener window.  It can only be set to a non-null value at the first
+  // call unless aForceOverride is true.
+  void SetOpenerWindowInternal(nsPIDOMWindowOuter* aOpener, bool aForceOverride);
 
   enum class SecureContextFlags {
     eDefault,
     eIgnoreOpener
   };
   // Called only on outer windows to compute the value that will be returned by
   // IsSecureContext() for the inner window that corresponds to aDocument.
   bool ComputeIsSecureContext(nsIDocument* aDocument,
@@ -1969,17 +1971,16 @@ protected:
   uint32_t mSerial;
 
   // The current idle request callback handle
   uint32_t mIdleRequestCallbackCounter;
   IdleRequests mIdleRequestCallbacks;
   RefPtr<IdleRequestExecutor> mIdleRequestExecutor;
 
 #ifdef DEBUG
-  bool mSetOpenerWindowCalled;
   nsCOMPtr<nsIURI> mLastOpenedURI;
 #endif
 
 #ifdef MOZ_B2G
   bool mNetworkUploadObserverEnabled;
   bool mNetworkDownloadObserverEnabled;
 #endif // MOZ_B2G
 
--- a/dom/base/nsPIDOMWindow.h
+++ b/dom/base/nsPIDOMWindow.h
@@ -299,24 +299,20 @@ public:
    *
    * aDocument must not be null.
    */
   virtual nsresult SetNewDocument(nsIDocument *aDocument,
                                   nsISupports *aState,
                                   bool aForceReuseInnerWindow) = 0;
 
   /**
-   * Set the opener window.  aOriginalOpener is true if and only if this is the
-   * original opener for the window.  That is, it can only be true at most once
-   * during the life cycle of a window, and then only the first time
-   * SetOpenerWindow is called.  It might never be true, of course, if the
-   * window does not have an opener when it's created.
+   * Set the opener window.  It can only be set to a non-null value at the first
+   * time SetOpenerWindow is called.
    */
-  virtual void SetOpenerWindow(nsPIDOMWindowOuter* aOpener,
-                               bool aOriginalOpener) = 0;
+  virtual void SetOpenerWindow(nsPIDOMWindowOuter* aOpener) = 0;
 
   /**
    * Ensure the size and position of this window are up-to-date by doing
    * a layout flush in the parent (which will in turn, do a layout flush
    * in its parent, etc.).
    */
   virtual void EnsureSizeAndPositionUpToDate() = 0;
 
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -888,20 +888,20 @@ ContentChild::ProvideWindowCommon(TabChi
                         aTabOpener->mDefaultScale);
   }
 
   // Set the opener window for this window before we start loading the document
   // inside of it. We have to do this before loading the remote scripts, because
   // they can poke at the document and cause the nsDocument to be created before
   // the openerwindow
   nsCOMPtr<mozIDOMWindowProxy> windowProxy = do_GetInterface(newChild->WebNavigation());
-  if (!aForceNoOpener && windowProxy && aParent) {
+  if (!aForceNoOpener && windowProxy && aParent && *aWindowIsNew) {
     nsPIDOMWindowOuter* outer = nsPIDOMWindowOuter::From(windowProxy);
     nsPIDOMWindowOuter* parent = nsPIDOMWindowOuter::From(aParent);
-    outer->SetOpenerWindow(parent, *aWindowIsNew);
+    outer->SetOpenerWindow(parent);
   }
 
   // Unfortunately we don't get a window unless we've shown the frame.  That's
   // pretty bogus; see bug 763602.
   newChild->DoFakeShow(textureFactoryIdentifier, layersId, compositorOptions,
                        renderFrame, showInfo);
 
   for (size_t i = 0; i < frameScripts.Length(); i++) {
deleted file mode 100644
--- a/testing/web-platform/meta/html/browsers/windows/browsing-context-names/browsing-context-choose-parent-003.html.ini
+++ /dev/null
@@ -1,6 +0,0 @@
-[browsing-context-choose-parent-003.html]
-  type: testharness
-  expected: TIMEOUT
-  [_parent should reuse window.parent context]
-    expected: TIMEOUT
-
--- a/toolkit/components/windowwatcher/nsWindowWatcher.cpp
+++ b/toolkit/components/windowwatcher/nsWindowWatcher.cpp
@@ -2157,17 +2157,19 @@ nsWindowWatcher::ReadyOpenedDocShellItem
   nsresult rv = NS_ERROR_FAILURE;
 
   NS_ENSURE_ARG(aOpenedWindow);
 
   *aOpenedWindow = 0;
   nsCOMPtr<nsPIDOMWindowOuter> piOpenedWindow = aOpenedItem->GetWindow();
   if (piOpenedWindow) {
     if (!aForceNoOpener) {
-      piOpenedWindow->SetOpenerWindow(aParent, aWindowIsNew); // damnit
+      if (aWindowIsNew) {
+        piOpenedWindow->SetOpenerWindow(aParent);
+      }
     } else if (aParent && aParent != piOpenedWindow) {
       MOZ_ASSERT(piOpenedWindow->TabGroup() != aParent->TabGroup(),
                  "If we're forcing no opener, they should be in different tab groups");
     }
 
     if (aWindowIsNew) {
 #ifdef DEBUG
       // Assert that we're not loading things right now.  If we are, when
--- a/xpfe/appshell/nsWebShellWindow.cpp
+++ b/xpfe/appshell/nsWebShellWindow.cpp
@@ -216,17 +216,17 @@ nsresult nsWebShellWindow::Initialize(ns
   nsCOMPtr<nsIWebProgress> webProgress(do_GetInterface(mDocShell, &rv));
   if (webProgress) {
     webProgress->AddProgressListener(this, nsIWebProgress::NOTIFY_STATE_NETWORK);
   }
 
   if (aOpenerWindow) {
     nsPIDOMWindowOuter* window = mDocShell->GetWindow();
     MOZ_ASSERT(window);
-    window->SetOpenerWindow(nsPIDOMWindowOuter::From(aOpenerWindow), true);
+    window->SetOpenerWindow(nsPIDOMWindowOuter::From(aOpenerWindow));
   }
 
   // Eagerly create an about:blank content viewer with the right principal here,
   // rather than letting it happening in the upcoming call to
   // SetInitialPrincipalToSubject. This avoids creating the about:blank document
   // and then blowing it away with a second one, which can cause problems for the
   // top-level chrome window case. See bug 789773.
   // Note that we don't accept expanded principals here, similar to