Bug 956704 - Introduce a mechanism for only suppressing animation frames. r=smaug
authorBlake Kaplan <mrbkap@gmail.com>
Wed, 26 Feb 2014 15:58:21 -0800
changeset 171190 d5f53cea0e183f54c467e81c75383a1b1ae8ab9b
parent 171189 3070f00e7833006dfaf5a18708becc43c1708ab8
child 171191 e0284b21846cd062351064208957c822f4641cea
push id270
push userpvanderbeken@mozilla.com
push dateThu, 06 Mar 2014 09:24:21 +0000
reviewerssmaug
bugs956704
milestone30.0a1
Bug 956704 - Introduce a mechanism for only suppressing animation frames. r=smaug
content/base/public/nsIDocument.h
content/base/src/nsDocument.cpp
content/base/src/nsDocument.h
content/base/src/nsXMLHttpRequest.cpp
docshell/base/nsDocShell.cpp
dom/base/nsDOMWindowUtils.cpp
dom/base/nsGlobalWindow.cpp
layout/base/nsDocumentViewer.cpp
--- a/content/base/public/nsIDocument.h
+++ b/content/base/public/nsIDocument.h
@@ -1638,30 +1638,41 @@ public:
   // mAnimationController isn't yet initialized.
   virtual nsSMILAnimationController* GetAnimationController() = 0;
 
   // Makes the images on this document capable of having their animation
   // active or suspended. An Image will animate as long as at least one of its
   // owning Documents needs it to animate; otherwise it can suspend.
   virtual void SetImagesNeedAnimating(bool aAnimating) = 0;
 
+  enum SuppressionType {
+    eAnimationsOnly = 0x1,
+
+    // Note that suppressing events also suppresses animation frames, so
+    // there's no need to split out events in its own bitmask.
+    eEvents = 0x3,
+  };
+
   /**
    * Prevents user initiated events from being dispatched to the document and
    * subdocuments.
    */
-  virtual void SuppressEventHandling(uint32_t aIncrease = 1) = 0;
+  virtual void SuppressEventHandling(SuppressionType aWhat,
+                                     uint32_t aIncrease = 1) = 0;
 
   /**
    * Unsuppress event handling.
    * @param aFireEvents If true, delayed events (focus/blur) will be fired
    *                    asynchronously.
    */
-  virtual void UnsuppressEventHandlingAndFireEvents(bool aFireEvents) = 0;
+  virtual void UnsuppressEventHandlingAndFireEvents(SuppressionType aWhat,
+                                                    bool aFireEvents) = 0;
 
   uint32_t EventHandlingSuppressed() const { return mEventsSuppressed; }
+  uint32_t AnimationsPaused() const { return mAnimationsPaused; }
 
   bool IsEventHandlingEnabled() {
     return !EventHandlingSuppressed() && mScriptGlobalObject;
   }
 
   /**
    * Increment the number of external scripts being evaluated.
    */
@@ -2482,16 +2493,18 @@ protected:
 
   // If we're an external resource document, this will be non-null and will
   // point to our "display document": the one that all resource lookups should
   // go to.
   nsCOMPtr<nsIDocument> mDisplayDocument;
 
   uint32_t mEventsSuppressed;
 
+  uint32_t mAnimationsPaused;
+
   /**
    * The number number of external scripts (ones with the src attribute) that
    * have this document as their owner and that are being evaluated right now.
    */
   uint32_t mExternalScriptsBeingEvaluated;
 
   /**
    * The current frame request callback handle
--- a/content/base/src/nsDocument.cpp
+++ b/content/base/src/nsDocument.cpp
@@ -9140,32 +9140,51 @@ nsIDocument::GetReadyState(nsAString& aR
   case READYSTATE_COMPLETE :
     aReadyState.Assign(NS_LITERAL_STRING("complete"));
     break;
   default:
     aReadyState.Assign(NS_LITERAL_STRING("uninitialized"));
   }
 }
 
+namespace {
+
+struct SuppressArgs
+{
+  nsIDocument::SuppressionType mWhat;
+  uint32_t mIncrease;
+};
+
+}
+
 static bool
 SuppressEventHandlingInDocument(nsIDocument* aDocument, void* aData)
 {
-  aDocument->SuppressEventHandling(*static_cast<uint32_t*>(aData));
+  SuppressArgs* args = static_cast<SuppressArgs*>(aData);
+  aDocument->SuppressEventHandling(args->mWhat, args->mIncrease);
   return true;
 }
 
 void
-nsDocument::SuppressEventHandling(uint32_t aIncrease)
-{
-  if (mEventsSuppressed == 0 && aIncrease != 0 && mPresShell &&
-      mScriptGlobalObject) {
+nsDocument::SuppressEventHandling(nsIDocument::SuppressionType aWhat,
+                                  uint32_t aIncrease)
+{
+  if (mEventsSuppressed == 0 && mAnimationsPaused == 0 &&
+      aIncrease != 0 && mPresShell && mScriptGlobalObject) {
     RevokeAnimationFrameNotifications();
   }
-  mEventsSuppressed += aIncrease;
-  EnumerateSubDocuments(SuppressEventHandlingInDocument, &aIncrease);
+
+  if (aWhat == eAnimationsOnly) {
+    mAnimationsPaused += aIncrease;
+  } else {
+    mEventsSuppressed += aIncrease;
+  }
+
+  SuppressArgs args = { aWhat, aIncrease };
+  EnumerateSubDocuments(SuppressEventHandlingInDocument, &args);
 }
 
 static void
 FireOrClearDelayedEvents(nsTArray<nsCOMPtr<nsIDocument> >& aDocuments,
                          bool aFireEvents)
 {
   nsIFocusManager* fm = nsFocusManager::GetFocusManager();
   if (!fm)
@@ -9309,40 +9328,69 @@ public:
     FireOrClearDelayedEvents(mDocuments, true);
     return NS_OK;
   }
 
 private:
   nsTArray<nsCOMPtr<nsIDocument> > mDocuments;
 };
 
+namespace {
+
+struct UnsuppressArgs
+{
+  UnsuppressArgs(nsIDocument::SuppressionType aWhat)
+    : mWhat(aWhat)
+  {
+  }
+
+  nsIDocument::SuppressionType mWhat;
+  nsTArray<nsCOMPtr<nsIDocument>> mDocs;
+};
+
+}
+
 static bool
-GetAndUnsuppressSubDocuments(nsIDocument* aDocument, void* aData)
-{
-  uint32_t suppression = aDocument->EventHandlingSuppressed();
-  if (suppression > 0) {
+GetAndUnsuppressSubDocuments(nsIDocument* aDocument,
+                             void* aData)
+{
+  UnsuppressArgs* args = static_cast<UnsuppressArgs*>(aData);
+  if (args->mWhat != nsIDocument::eAnimationsOnly &&
+      aDocument->EventHandlingSuppressed() > 0) {
     static_cast<nsDocument*>(aDocument)->DecreaseEventSuppression();
-  }
-  nsTArray<nsCOMPtr<nsIDocument> >* docs =
-    static_cast<nsTArray<nsCOMPtr<nsIDocument> >* >(aData);
-  docs->AppendElement(aDocument);
-  aDocument->EnumerateSubDocuments(GetAndUnsuppressSubDocuments, docs);
+  } else if (args->mWhat == nsIDocument::eAnimationsOnly &&
+             aDocument->AnimationsPaused()) {
+    static_cast<nsDocument*>(aDocument)->ResumeAnimations();
+  }
+
+  if (args->mWhat != nsIDocument::eAnimationsOnly) {
+    // No need to remember documents if we only care about animation frames.
+    args->mDocs.AppendElement(aDocument);
+  }
+
+  aDocument->EnumerateSubDocuments(GetAndUnsuppressSubDocuments, aData);
   return true;
 }
 
 void
-nsDocument::UnsuppressEventHandlingAndFireEvents(bool aFireEvents)
-{
-  nsTArray<nsCOMPtr<nsIDocument> > documents;
-  GetAndUnsuppressSubDocuments(this, &documents);
+nsDocument::UnsuppressEventHandlingAndFireEvents(nsIDocument::SuppressionType aWhat,
+                                                 bool aFireEvents)
+{
+  UnsuppressArgs args(aWhat);
+  GetAndUnsuppressSubDocuments(this, &args);
+
+  if (aWhat == nsIDocument::eAnimationsOnly) {
+    // No need to fire events if we only care about animations here.
+    return;
+  }
 
   if (aFireEvents) {
-    NS_DispatchToCurrentThread(new nsDelayedEventDispatcher(documents));
+    NS_DispatchToCurrentThread(new nsDelayedEventDispatcher(args.mDocs));
   } else {
-    FireOrClearDelayedEvents(documents, false);
+    FireOrClearDelayedEvents(args.mDocs, false);
   }
 }
 
 nsISupports*
 nsDocument::GetCurrentContentSink()
 {
   return mParser ? mParser->GetContentSink() : nullptr;
 }
--- a/content/base/src/nsDocument.h
+++ b/content/base/src/nsDocument.h
@@ -975,25 +975,34 @@ public:
   nsTArray<nsCString> mHostObjectURIs;
 
   // Returns our (lazily-initialized) animation controller.
   // If HasAnimationController is true, this is guaranteed to return non-null.
   nsSMILAnimationController* GetAnimationController() MOZ_OVERRIDE;
 
   void SetImagesNeedAnimating(bool aAnimating) MOZ_OVERRIDE;
 
-  virtual void SuppressEventHandling(uint32_t aIncrease) MOZ_OVERRIDE;
+  virtual void SuppressEventHandling(SuppressionType aWhat,
+                                     uint32_t aIncrease) MOZ_OVERRIDE;
+
+  virtual void UnsuppressEventHandlingAndFireEvents(SuppressionType aWhat,
+                                                    bool aFireEvents) MOZ_OVERRIDE;
 
-  virtual void UnsuppressEventHandlingAndFireEvents(bool aFireEvents) MOZ_OVERRIDE;
-  
   void DecreaseEventSuppression() {
+    MOZ_ASSERT(mEventsSuppressed);
     --mEventsSuppressed;
     MaybeRescheduleAnimationFrameNotifications();
   }
 
+  void ResumeAnimations() {
+    MOZ_ASSERT(mAnimationsPaused);
+    --mAnimationsPaused;
+    MaybeRescheduleAnimationFrameNotifications();
+  }
+
   virtual nsIDocument* GetTemplateContentsOwner() MOZ_OVERRIDE;
 
   NS_DECL_CYCLE_COLLECTION_SKIPPABLE_SCRIPT_HOLDER_CLASS_AMBIGUOUS(nsDocument,
                                                                    nsIDocument)
 
   void DoNotifyPossibleTitleChange();
 
   nsExternalResourceMap& ExternalResourceMap()
--- a/content/base/src/nsXMLHttpRequest.cpp
+++ b/content/base/src/nsXMLHttpRequest.cpp
@@ -2867,17 +2867,17 @@ nsXMLHttpRequest::Send(nsIVariant* aVari
     if (GetOwner()) {
       nsCOMPtr<nsIDOMWindow> topWindow;
       if (NS_SUCCEEDED(GetOwner()->GetTop(getter_AddRefs(topWindow)))) {
         nsCOMPtr<nsPIDOMWindow> suspendedWindow(do_QueryInterface(topWindow));
         if (suspendedWindow &&
             (suspendedWindow = suspendedWindow->GetCurrentInnerWindow())) {
           suspendedDoc = suspendedWindow->GetExtantDoc();
           if (suspendedDoc) {
-            suspendedDoc->SuppressEventHandling();
+            suspendedDoc->SuppressEventHandling(nsIDocument::eEvents);
           }
           suspendedWindow->SuspendTimeouts(1, false);
           resumeTimeoutRunnable = new nsResumeTimeoutsEvent(suspendedWindow);
         }
       }
     }
 
     ChangeState(XML_HTTP_REQUEST_SENT);
@@ -2891,17 +2891,18 @@ nsXMLHttpRequest::Send(nsIVariant* aVari
         if (!NS_ProcessNextEvent(thread)) {
           rv = NS_ERROR_UNEXPECTED;
           break;
         }
       }
     }
 
     if (suspendedDoc) {
-      suspendedDoc->UnsuppressEventHandlingAndFireEvents(true);
+      suspendedDoc->UnsuppressEventHandlingAndFireEvents(nsIDocument::eEvents,
+                                                         true);
     }
 
     if (resumeTimeoutRunnable) {
       NS_DispatchToCurrentThread(resumeTimeoutRunnable);
     }
   } else {
     // Now that we've successfully opened the channel, we can change state.  Note
     // that this needs to come after the AsyncOpen() and rv check, because this
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -7974,18 +7974,26 @@ nsDocShell::RestoreFromHistory()
     nsCOMPtr<nsIDocument> document = do_QueryInterface(domDoc);
     uint32_t parentSuspendCount = 0;
     if (document) {
         nsCOMPtr<nsIDocShellTreeItem> parent;
         GetParent(getter_AddRefs(parent));
         nsCOMPtr<nsIDocument> d = do_GetInterface(parent);
         if (d) {
             if (d->EventHandlingSuppressed()) {
-                document->SuppressEventHandling(d->EventHandlingSuppressed());
-            }
+                document->SuppressEventHandling(nsIDocument::eEvents,
+                                                d->EventHandlingSuppressed());
+            }
+
+            // Ick, it'd be nicer to not rewalk all of the subdocs here.
+            if (d->AnimationsPaused()) {
+                document->SuppressEventHandling(nsIDocument::eAnimationsOnly,
+                                                d->AnimationsPaused());
+            }
+
             nsCOMPtr<nsPIDOMWindow> parentWindow = d->GetWindow();
             if (parentWindow) {
                 parentSuspendCount = parentWindow->TimeoutSuspendCount();
             }
         }
 
         // Use the uri from the mLSHE we had when we entered this function
         // (which need not match the document's URI if anchors are involved),
--- a/dom/base/nsDOMWindowUtils.cpp
+++ b/dom/base/nsDOMWindowUtils.cpp
@@ -1678,19 +1678,19 @@ nsDOMWindowUtils::SuppressEventHandling(
 
   nsCOMPtr<nsPIDOMWindow> window = do_QueryReferent(mWindow);
   NS_ENSURE_STATE(window);
 
   nsCOMPtr<nsIDocument> doc = window->GetExtantDoc();
   NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE);
 
   if (aSuppress) {
-    doc->SuppressEventHandling();
+    doc->SuppressEventHandling(nsIDocument::eEvents);
   } else {
-    doc->UnsuppressEventHandlingAndFireEvents(true);
+    doc->UnsuppressEventHandlingAndFireEvents(nsIDocument::eEvents, true);
   }
 
   return NS_OK;
 }
 
 static nsresult
 getScrollXYAppUnits(nsWeakPtr mWindow, bool aFlushLayout, nsPoint& aScrollPos) {
   if (!nsContentUtils::IsCallerChrome()) {
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -1563,17 +1563,17 @@ nsGlobalWindow::FreeInnerObjects()
 
   if (mDoc) {
     // Remember the document's principal and URI.
     mDocumentPrincipal = mDoc->NodePrincipal();
     mDocumentURI = mDoc->GetDocumentURI();
     mDocBaseURI = mDoc->GetDocBaseURI();
 
     while (mDoc->EventHandlingSuppressed()) {
-      mDoc->UnsuppressEventHandlingAndFireEvents(false);
+      mDoc->UnsuppressEventHandlingAndFireEvents(nsIDocument::eEvents, false);
     }
   }
 
   // Remove our reference to the document and the document principal.
   mFocusedNode = nullptr;
 
   if (mApplicationCache) {
     static_cast<nsDOMOfflineResourceList*>(mApplicationCache.get())->Disconnect();
@@ -8277,17 +8277,17 @@ nsGlobalWindow::EnterModalState()
     nsIPresShell::SetCapturingContent(nullptr, 0);
   }
 
   if (topWin->mModalStateDepth == 0) {
     NS_ASSERTION(!mSuspendedDoc, "Shouldn't have mSuspendedDoc here!");
 
     mSuspendedDoc = topDoc;
     if (mSuspendedDoc) {
-      mSuspendedDoc->SuppressEventHandling();
+      mSuspendedDoc->SuppressEventHandling(nsIDocument::eEvents);
     }
   }
   topWin->mModalStateDepth++;
 }
 
 // static
 void
 nsGlobalWindow::RunPendingTimeoutsRecursive(nsGlobalWindow *aTopWindow,
@@ -8374,17 +8374,18 @@ nsGlobalWindow::LeaveModalState()
 
   if (topWin->mModalStateDepth == 0) {
     nsCOMPtr<nsIRunnable> runner = new nsPendingTimeoutRunner(topWin);
     if (NS_FAILED(NS_DispatchToCurrentThread(runner)))
       NS_WARNING("failed to dispatch pending timeout runnable");
 
     if (mSuspendedDoc) {
       nsCOMPtr<nsIDocument> currentDoc = topWin->GetExtantDoc();
-      mSuspendedDoc->UnsuppressEventHandlingAndFireEvents(currentDoc == mSuspendedDoc);
+      mSuspendedDoc->UnsuppressEventHandlingAndFireEvents(nsIDocument::eEvents,
+                                                          currentDoc == mSuspendedDoc);
       mSuspendedDoc = nullptr;
     }
   }
 
   // Remember the time of the last dialog quit.
   nsGlobalWindow *inner = topWin->GetCurrentInnerWindowInternal();
   if (inner)
     inner->mLastDialogQuitTime = TimeStamp::Now();
--- a/layout/base/nsDocumentViewer.cpp
+++ b/layout/base/nsDocumentViewer.cpp
@@ -587,17 +587,18 @@ nsDocumentViewer::SyncParentSubDocMap()
       nsCOMPtr<nsIDOMDocument> dom_doc;
       parent_win->GetDocument(getter_AddRefs(dom_doc));
 
       nsCOMPtr<nsIDocument> parent_doc(do_QueryInterface(dom_doc));
 
       if (parent_doc) {
         if (mDocument &&
             parent_doc->GetSubDocumentFor(content) != mDocument) {
-          mDocument->SuppressEventHandling(parent_doc->EventHandlingSuppressed());
+          mDocument->SuppressEventHandling(nsIDocument::eEvents,
+                                           parent_doc->EventHandlingSuppressed());
         }
         return parent_doc->SetSubDocumentFor(content->AsElement(), mDocument);
       }
     }
   }
 
   return NS_OK;
 }