Bug 1412173 Part 1: Implement the Document ignore-opens-during-unload counter required by spec. r=bz
authorBrad Werth <bwerth@mozilla.com>
Thu, 02 Nov 2017 11:47:21 -0700
changeset 444066 3bc3d8b72947475088c68e358a70053b859e267c
parent 444065 1f5043322c8899aeef2b84f4ea76453f878caaf9
child 444067 315b2e8320b276ec5aef7fd413b50b413e2ce4fa
push id1618
push userCallek@gmail.com
push dateThu, 11 Jan 2018 17:45:48 +0000
treeherdermozilla-release@882ca853e05a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbz
bugs1412173
milestone58.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 1412173 Part 1: Implement the Document ignore-opens-during-unload counter required by spec. r=bz MozReview-Commit-ID: IWfT5c0H3t
dom/base/nsDocument.cpp
dom/base/nsIDocument.h
dom/html/nsHTMLDocument.cpp
layout/base/nsDocumentViewer.cpp
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -1593,17 +1593,18 @@ nsIDocument::nsIDocument()
     mInSyncOperationCount(0),
     mBlockDOMContentLoaded(0),
     mUseCounters(0),
     mChildDocumentUseCounters(0),
     mNotifiedPageForUseCounter(0),
     mIncCounters(),
     mUserHasInteracted(false),
     mServoRestyleRootDirtyBits(0),
-    mThrowOnDynamicMarkupInsertionCounter(0)
+    mThrowOnDynamicMarkupInsertionCounter(0),
+    mIgnoreOpensDuringUnloadCounter(0)
 {
   SetIsInDocument();
   for (auto& cnt : mIncCounters) {
     cnt = 0;
   }
 }
 
 nsDocument::nsDocument(const char* aContentType)
--- a/dom/base/nsIDocument.h
+++ b/dom/base/nsIDocument.h
@@ -3212,16 +3212,32 @@ public:
   }
 
   void DecrementThrowOnDynamicMarkupInsertionCounter()
   {
     MOZ_ASSERT(mThrowOnDynamicMarkupInsertionCounter);
     --mThrowOnDynamicMarkupInsertionCounter;
   }
 
+  bool ShouldIgnoreOpens() const
+  {
+    return mIgnoreOpensDuringUnloadCounter;
+  }
+
+  void IncrementIgnoreOpensDuringUnloadCounter()
+  {
+    ++mIgnoreOpensDuringUnloadCounter;
+  }
+
+  void DecrementIgnoreOpensDuringUnloadCounter()
+  {
+    MOZ_ASSERT(mIgnoreOpensDuringUnloadCounter);
+    --mIgnoreOpensDuringUnloadCounter;
+  }
+
   virtual bool AllowPaymentRequest() const = 0;
   virtual void SetAllowPaymentRequest(bool aAllowPaymentRequest) = 0;
 
 protected:
   bool GetUseCounter(mozilla::UseCounter aUseCounter)
   {
     return mUseCounters[aUseCounter];
   }
@@ -3763,16 +3779,19 @@ protected:
   // root corresponds to.
   nsCOMPtr<nsINode> mServoRestyleRoot;
   uint32_t mServoRestyleRootDirtyBits;
 
   // Used in conjunction with the create-an-element-for-the-token algorithm to
   // prevent custom element constructors from being able to use document.open(),
   // document.close(), and document.write() when they are invoked by the parser.
   uint32_t mThrowOnDynamicMarkupInsertionCounter;
+
+  // Count of unload/beforeunload/pagehide operations in progress.
+  uint32_t mIgnoreOpensDuringUnloadCounter;
 };
 
 NS_DEFINE_STATIC_IID_ACCESSOR(nsIDocument, NS_IDOCUMENT_IID)
 
 /**
  * mozAutoSubtreeModified batches DOM mutations so that a DOMSubtreeModified
  * event is dispatched, if necessary, when the outermost mozAutoSubtreeModified
  * object is deleted.
@@ -3836,16 +3855,33 @@ class MOZ_RAII AutoSetThrowOnDynamicMark
     ~AutoSetThrowOnDynamicMarkupInsertionCounter() {
       mDocument->DecrementThrowOnDynamicMarkupInsertionCounter();
     }
 
   private:
     nsIDocument* mDocument;
 };
 
+class MOZ_RAII IgnoreOpensDuringUnload final
+{
+public:
+  explicit IgnoreOpensDuringUnload(nsIDocument* aDoc)
+    : mDoc(aDoc)
+  {
+    mDoc->IncrementIgnoreOpensDuringUnloadCounter();
+  }
+
+  ~IgnoreOpensDuringUnload()
+  {
+    mDoc->DecrementIgnoreOpensDuringUnloadCounter();
+  }
+private:
+  nsIDocument* mDoc;
+};
+
 // XXX These belong somewhere else
 nsresult
 NS_NewHTMLDocument(nsIDocument** aInstancePtrResult, bool aLoadedAsData = false);
 
 nsresult
 NS_NewXMLDocument(nsIDocument** aInstancePtrResult, bool aLoadedAsData = false,
                   bool aIsPlainDocument = false);
 
--- a/dom/html/nsHTMLDocument.cpp
+++ b/dom/html/nsHTMLDocument.cpp
@@ -1529,16 +1529,23 @@ nsHTMLDocument::Open(JSContext* cx,
     // invoked."
     // Note that aborting a parser leaves the parser "active" with its
     // insertion point "not undefined". We track this using mParserAborted,
     // because aborting a parser nulls out mParser.
     nsCOMPtr<nsIDocument> ret = this;
     return ret.forget();
   }
 
+  // Implement Step 6 of:
+  // https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#document-open-steps
+  if (ShouldIgnoreOpens()) {
+    nsCOMPtr<nsIDocument> ret = this;
+    return ret.forget();
+  }
+
   // No calling document.open() without a script global object
   if (!mScriptGlobalObject) {
     nsCOMPtr<nsIDocument> ret = this;
     return ret.forget();
   }
 
   nsPIDOMWindowOuter* outer = GetWindow();
   if (!outer || (GetInnerWindow() != outer->GetCurrentInnerWindow())) {
@@ -1942,16 +1949,22 @@ nsHTMLDocument::WriteCommon(JSContext *c
 
   if (mParserAborted) {
     // Hixie says aborting the parser doesn't undefine the insertion point.
     // However, since we null out mParser in that case, we track the
     // theoretically defined insertion point using mParserAborted.
     return NS_OK;
   }
 
+  // Implement Step 4.1 of:
+  // https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#document-write-steps
+  if (ShouldIgnoreOpens()) {
+    return NS_OK;
+  }
+
   nsresult rv = NS_OK;
 
   void *key = GenerateParserKey();
   if (mParser && !mParser->IsInsertionPointDefined()) {
     if (mIgnoreDestructiveWritesCounter) {
       // Instead of implying a call to document.open(), ignore the call.
       nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
                                       NS_LITERAL_CSTRING("DOM Events"), this,
--- a/layout/base/nsDocumentViewer.cpp
+++ b/layout/base/nsDocumentViewer.cpp
@@ -1184,16 +1184,23 @@ nsDocumentViewer::PermitUnloadInternal(b
   if (!window) {
     // This is odd, but not fatal
     NS_WARNING("window not set for document!");
     return NS_OK;
   }
 
   NS_ASSERTION(nsContentUtils::IsSafeToRunScript(), "This is unsafe");
 
+  // https://html.spec.whatwg.org/multipage/browsing-the-web.html#prompt-to-unload-a-document
+  // Create an RAII object on mDocument that will increment the
+  // should-ignore-opens-during-unload counter on initialization
+  // and decrement it again when it goes out of score (regardless
+  // of how we exit this function).
+  IgnoreOpensDuringUnload ignoreOpens(mDocument);
+
   // Now, fire an BeforeUnload event to the document and see if it's ok
   // to unload...
   nsIPresShell* shell = mDocument->GetShell();
   nsPresContext* presContext = nullptr;
   if (shell) {
     presContext = shell->GetPresContext();
   }
   RefPtr<BeforeUnloadEvent> event =
@@ -1392,16 +1399,22 @@ nsDocumentViewer::PageHide(bool aIsUnloa
     nsPIDOMWindowOuter* window = mDocument->GetWindow();
 
     if (!window) {
       // Fail if no window is available...
       NS_WARNING("window not set for document!");
       return NS_ERROR_NULL_POINTER;
     }
 
+    // https://html.spec.whatwg.org/multipage/browsing-the-web.html#unload-a-document
+    // Create an RAII object on mDocument that will increment the
+    // should-ignore-opens-during-unload counter on initialization
+    // and decrement it again when it goes out of scope.
+    IgnoreOpensDuringUnload ignoreOpens(mDocument);
+
     // Now, fire an Unload event to the document...
     nsEventStatus status = nsEventStatus_eIgnore;
     WidgetEvent event(true, eUnload);
     event.mFlags.mBubbles = false;
     // XXX Dispatching to |window|, but using |document| as the target.
     event.mTarget = mDocument;
 
     // Never permit popups from the unload handler, no matter how we get