Bug 1412173 Part 1: Implement the Document ignore-opens-during-unload counter required by spec. draft
authorBrad Werth <bwerth@mozilla.com>
Thu, 02 Nov 2017 11:47:21 -0700
changeset 694492 201a79d365a82d6978af02c2e13c6eb34c314f45
parent 694164 7851d6768dfd9fe5568d1315a98f142d9bb9234f
child 694493 6e77a278ecc7cb6262fa2cdb347b3b7f5646dde1
push id88137
push userbwerth@mozilla.com
push dateTue, 07 Nov 2017 20:45:08 +0000
bugs1412173
milestone58.0a1
Bug 1412173 Part 1: Implement the Document ignore-opens-during-unload counter required by spec. 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