Bug 1476456 - Add telemetry to report whether autoplay would be not allowed if autoplay was disabled. r=baku,r=francois draft
authorChris Pearce <cpearce@mozilla.com>
Wed, 18 Jul 2018 15:34:04 +1200
changeset 821753 45cbdaafa5b7e482cea4cf8c275de031b308caa4
parent 821326 54c01f0092e1f1edcede61fd08042e99d76d5c30
push id117189
push userbmo:cpearce@mozilla.com
push dateTue, 24 Jul 2018 01:10:45 +0000
reviewersbaku, francois
bugs1476456
milestone63.0a1
Bug 1476456 - Add telemetry to report whether autoplay would be not allowed if autoplay was disabled. r=baku,r=francois We'd like to add telemetry to help inform the decision as to how enabling block autoplay will affect video playback in the wild. Our data science team would also like some input to help them estimate the rate at which our shield study would receive pings, and the telemetry collected here will help them estimate that. We'd like to collect the following, on a per session basis: * Count of the number of top level content documents loaded, as denominator for other stats collected here. * Count of the number of top level content documents which contained (directly or in a descendant document) playback of an audible media element. * Count of the number of top level content documents which contained (directly or in a descendant document) a muted media element that was paused by the autoplay policy because it tried to unmute and it wasn't allowed to autoplay audibly. * Count of the total number of audible autoplay videos that would have not been allowed to play if block autoplay was enabled. We'd either prompt for permission on these videos, or block outright depending on user's settings. * Count of the total number of audible autoplay videos that would have been allowed to play if block autoplay was enabled. MozReview-Commit-ID: vHWJPyqHjT
dom/base/nsDocument.cpp
dom/base/nsIDocument.h
dom/html/HTMLMediaElement.cpp
dom/html/HTMLMediaElement.h
dom/media/AutoplayPolicy.cpp
dom/media/AutoplayPolicy.h
toolkit/components/telemetry/Scalars.yaml
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -1454,16 +1454,18 @@ nsIDocument::nsIDocument()
     mAllowDoubleTapZoom(false),
     mValidScaleFloat(false),
     mValidMaxScale(false),
     mScaleStrEmpty(false),
     mWidthStrEmpty(false),
     mParserAborted(false),
     mReportedUseCounters(false),
     mHasReportedShadowDOMUsage(false),
+    mDocTreeHadAudibleMedia(false),
+    mDocTreeHadPlayRevoked(false),
 #ifdef DEBUG
     mWillReparent(false),
 #endif
     mPendingFullscreenRequests(0),
     mXMLDeclarationBits(0),
     mOnloadBlockCount(0),
     mAsyncOnloadBlockCount(0),
     mCompatMode(eCompatibility_FullStandards),
@@ -1631,16 +1633,24 @@ nsDocument::~nsDocument()
       }
       if (mHasUnsafeEvalCSP) {
         Accumulate(Telemetry::CSP_UNSAFE_EVAL_DOCUMENTS_COUNT, 1);
       }
 
       if (MOZ_UNLIKELY(mMathMLEnabled)) {
         ScalarAdd(Telemetry::ScalarID::MATHML_DOC_COUNT, 1);
       }
+
+      ScalarAdd(Telemetry::ScalarID::MEDIA_PAGE_COUNT, 1);
+      if (mDocTreeHadAudibleMedia) {
+        ScalarAdd(Telemetry::ScalarID::MEDIA_PAGE_HAD_MEDIA_COUNT, 1);
+      }
+      if (mDocTreeHadPlayRevoked) {
+        ScalarAdd(Telemetry::ScalarID::MEDIA_PAGE_HAD_PLAY_REVOKED_COUNT, 1);
+      }
     }
   }
 
   ReportUseCounters();
 
   mInDestructor = true;
   mInUnlinkOrDeletion = true;
 
@@ -12485,16 +12495,34 @@ nsIDocument::NotifyUserGestureActivation
             LogLevel::Debug,
             ("Document %p has been activated by user.", this));
     doc->mUserGestureActivated = true;
     doc = doc->GetSameTypeParentDocument();
   }
 }
 
 void
+nsIDocument::SetDocTreeHadAudibleMedia()
+{
+  nsIDocument* topLevelDoc = GetTopLevelContentDocument();
+  if (topLevelDoc) {
+    topLevelDoc->mDocTreeHadAudibleMedia = true;
+  }
+}
+
+void
+nsIDocument::SetDocTreeHadPlayRevoked()
+{
+  nsIDocument* topLevelDoc = GetTopLevelContentDocument();
+  if (topLevelDoc) {
+    topLevelDoc->mDocTreeHadPlayRevoked = true;
+  }
+}
+
+void
 nsIDocument::MaybeAllowStorageForOpener()
 {
   if (!StaticPrefs::privacy_restrict3rdpartystorage_enabled()) {
     return;
   }
 
   // This will probably change for project fission, but currently this document
   // and the opener are on the same process. In the future, we should make this
--- a/dom/base/nsIDocument.h
+++ b/dom/base/nsIDocument.h
@@ -3570,16 +3570,20 @@ public:
    * element containing the subdocument containing aFrame, and/or find the
    * nearest non-anonymous ancestor in this document.
    * Returns null if there is no such element.
    */
   nsIContent* GetContentInThisDocument(nsIFrame* aFrame) const;
 
   void ReportShadowDOMUsage();
 
+  // Sets flags for media autoplay telemetry.
+  void SetDocTreeHadAudibleMedia();
+  void SetDocTreeHadPlayRevoked();
+
 protected:
   void DoUpdateSVGUseElementShadowTrees();
 
   already_AddRefed<nsIPrincipal> MaybeDowngradePrincipal(nsIPrincipal* aPrincipal);
 
   void EnsureOnloadBlocker();
 
   void SendToConsole(nsCOMArray<nsISecurityConsoleMessage>& aMessages);
@@ -4091,16 +4095,26 @@ protected:
   // documents (SVG documents) that are not guaranteed to be destroyed, we
   // report use counters when the image cache no longer has any imgRequestProxys
   // pointing to them.  We track whether we ever reported use counters so
   // that we only report them once for the document.
   bool mReportedUseCounters: 1;
 
   bool mHasReportedShadowDOMUsage: 1;
 
+  // True if this document contained, either directly or in a subdocument,
+  // an HTMLMediaElement that played audibly. This should only be set on
+  // top level content documents.
+  bool mDocTreeHadAudibleMedia: 1;
+  // True if this document contained, either directly or in a subdocument,
+  // an HTMLMediaElement that was playing inaudibly and became audible and we
+  // paused the HTMLMediaElement because it wasn't allowed to autoplay audibly.
+  // This should only be set on top level content documents.
+  bool mDocTreeHadPlayRevoked: 1;
+
 #ifdef DEBUG
 public:
   bool mWillReparent: 1;
 protected:
 #endif
 
   uint8_t mPendingFullscreenRequests;
 
--- a/dom/html/HTMLMediaElement.cpp
+++ b/dom/html/HTMLMediaElement.cpp
@@ -3052,16 +3052,17 @@ void
 HTMLMediaElement::PauseIfShouldNotBePlaying()
 {
   if (GetPaused()) {
     return;
   }
   if (AutoplayPolicy::IsAllowedToPlay(*this) != nsIAutoplay::ALLOWED) {
     ErrorResult rv;
     Pause(rv);
+    OwnerDoc()->SetDocTreeHadPlayRevoked();
   }
 }
 
 void
 HTMLMediaElement::SetVolumeInternal()
 {
   float effectiveVolume = ComputedVolume();
 
@@ -3999,16 +4000,31 @@ HTMLMediaElement::NotifyXPCOMShutdown()
 }
 
 bool
 HTMLMediaElement::AudioChannelAgentDelayingPlayback()
 {
   return mAudioChannelWrapper && mAudioChannelWrapper->IsPlaybackBlocked();
 }
 
+void
+HTMLMediaElement::ReportAutoplayTelemetry() const
+{
+  // If we're audible, and autoplaying...
+  if ((Volume() > 0.0 && !Muted()) &&
+      (!OwnerDoc()->HasBeenUserGestureActivated() || Autoplay())) {
+    OwnerDoc()->SetDocTreeHadAudibleMedia();
+    if (AutoplayPolicy::WouldBeAllowedToPlayIfAutoplayDisabled(*this)) {
+      ScalarAdd(Telemetry::ScalarID::MEDIA_AUTOPLAY_WOULD_BE_ALLOWED_COUNT, 1);
+    } else {
+      ScalarAdd(Telemetry::ScalarID::MEDIA_AUTOPLAY_WOULD_NOT_BE_ALLOWED_COUNT, 1);
+    }
+  }
+}
+
 already_AddRefed<Promise>
 HTMLMediaElement::Play(ErrorResult& aRv)
 {
   LOG(LogLevel::Debug,
       ("%p Play() called by JS readyState=%d", this, mReadyState));
 
   // 4.8.12.8
   // When the play() method on a media element is invoked, the user agent must
@@ -4058,16 +4074,18 @@ HTMLMediaElement::Play(ErrorResult& aRv)
     LOG(LogLevel::Debug, ("%p play blocked by AudioChannelAgent.", this));
     promise->MaybeReject(NS_ERROR_DOM_MEDIA_NOT_ALLOWED_ERR);
     if (StaticPrefs::MediaBlockEventEnabled()) {
       DispatchAsyncEvent(NS_LITERAL_STRING("blocked"));
     }
     return promise.forget();
   }
 
+  ReportAutoplayTelemetry();
+
   const bool handlingUserInput = EventStateManager::IsHandlingUserInput();
   switch (AutoplayPolicy::IsAllowedToPlay(*this)) {
     case nsIAutoplay::ALLOWED: {
       mPendingPlayPromises.AppendElement(promise);
       PlayInternal(handlingUserInput);
       UpdateCustomPolicyAfterPlayed();
       break;
     }
@@ -6216,16 +6234,17 @@ HTMLMediaElement::CanActivateAutoplay()
 
 void
 HTMLMediaElement::CheckAutoplayDataReady()
 {
   if (!CanActivateAutoplay()) {
     return;
   }
 
+  ReportAutoplayTelemetry();
   switch (AutoplayPolicy::IsAllowedToPlay(*this)) {
     case nsIAutoplay::BLOCKED:
       return;
     case nsIAutoplay::PROMPT:
       EnsureAutoplayRequested(false);
       return;
     case nsIAutoplay::ALLOWED:
       break;
--- a/dom/html/HTMLMediaElement.h
+++ b/dom/html/HTMLMediaElement.h
@@ -1776,16 +1776,18 @@ public:
     TimeStamp mStartTime;
     TimeDuration mSum;
     uint32_t mCount;
   };
 private:
 
   already_AddRefed<PlayPromise> CreatePlayPromise(ErrorResult& aRv) const;
 
+  void ReportAutoplayTelemetry() const;
+
   /**
    * This function is called by AfterSetAttr and OnAttrSetButNotChanged.
    * It will not be called if the value is being unset.
    *
    * @param aNamespaceID the namespace of the attr being set
    * @param aName the localname of the attribute being set
    * @param aNotify Whether we plan to notify document observers.
    */
--- a/dom/media/AutoplayPolicy.cpp
+++ b/dom/media/AutoplayPolicy.cpp
@@ -95,37 +95,45 @@ DefaultAutoplayBehaviour()
   int prefValue = Preferences::GetInt("media.autoplay.default", nsIAutoplay::ALLOWED);
   if (prefValue < nsIAutoplay::ALLOWED || prefValue > nsIAutoplay::PROMPT) {
     // Invalid pref values are just converted to ALLOWED.
     return nsIAutoplay::ALLOWED;
   }
   return prefValue;
 }
 
+static bool
+IsMediaElementAllowedToPlay(const HTMLMediaElement& aElement)
+{
+  return ((aElement.Volume() == 0.0 || aElement.Muted()) &&
+          Preferences::GetBool("media.autoplay.allow-muted", true)) ||
+         IsWindowAllowedToPlay(aElement.OwnerDoc()->GetInnerWindow());
+}
+
+/* static */ bool
+AutoplayPolicy::WouldBeAllowedToPlayIfAutoplayDisabled(const HTMLMediaElement& aElement)
+{
+  return IsMediaElementAllowedToPlay(aElement);
+}
+
 /* static */ uint32_t
 AutoplayPolicy::IsAllowedToPlay(const HTMLMediaElement& aElement)
 {
   const uint32_t autoplayDefault = DefaultAutoplayBehaviour();
   // TODO : this old way would be removed when user-gestures-needed becomes
   // as a default option to block autoplay.
   if (!Preferences::GetBool("media.autoplay.enabled.user-gestures-needed", false)) {
     // If element is blessed, it would always be allowed to play().
     return (autoplayDefault == nsIAutoplay::ALLOWED ||
             aElement.IsBlessed() ||
             EventStateManager::IsHandlingUserInput())
               ? nsIAutoplay::ALLOWED : nsIAutoplay::BLOCKED;
   }
 
-  // Muted content
-  if ((aElement.Volume() == 0.0 || aElement.Muted()) &&
-      Preferences::GetBool("media.autoplay.allow-muted", true)) {
-    return nsIAutoplay::ALLOWED;
-  }
-
-  if (IsWindowAllowedToPlay(aElement.OwnerDoc()->GetInnerWindow())) {
+  if (IsMediaElementAllowedToPlay(aElement)) {
     return nsIAutoplay::ALLOWED;
   }
 
   return autoplayDefault;
 }
 
 /* static */ bool
 AutoplayPolicy::IsAudioContextAllowedToPlay(NotNull<AudioContext*> aContext)
--- a/dom/media/AutoplayPolicy.h
+++ b/dom/media/AutoplayPolicy.h
@@ -33,16 +33,23 @@ class AudioContext;
  * 3) Document's origin has the "autoplay-media" permission.
  */
 class AutoplayPolicy
 {
 public:
   // Returns whether a given media element is allowed to play.
   static uint32_t IsAllowedToPlay(const HTMLMediaElement& aElement);
 
+  // Returns true if a given media element would be allowed to play
+  // if block autoplay was enabled. If this returns false, it means we would
+  // either block or ask for permission.
+  // Note: this is for telemetry purposes, and doesn't check the prefs
+  // which enable/disable block autoplay. Do not use for blocking logic!
+  static bool WouldBeAllowedToPlayIfAutoplayDisabled(const HTMLMediaElement& aElement);
+
   // Returns whether a given AudioContext is allowed to play.
   static bool IsAudioContextAllowedToPlay(NotNull<AudioContext*> aContext);
 
   // Returns the AutoplayPermissionManager that a given document must request on
   // for autoplay permission.
   static already_AddRefed<AutoplayPermissionManager> RequestFor(
     const nsIDocument& aDocument);
 };
--- a/toolkit/components/telemetry/Scalars.yaml
+++ b/toolkit/components/telemetry/Scalars.yaml
@@ -752,16 +752,97 @@ mediarecorder:
     kind: uint
     notification_emails:
       - bvandyk@mozilla.com
     release_channel_collection: opt-in
     record_in_processes:
       - main
       - content
 
+media:
+  page_count:
+    bug_numbers:
+      - 1476456
+    description: >
+      The number of times a top level document is loaded.
+    expires: "68"
+    kind: uint
+    notification_emails:
+      - cpearce@mozilla.com
+      - alwu@mozilla.com
+      - nohlmeier@mozilla.com
+    release_channel_collection: opt-in
+    record_in_processes:
+      - main
+      - content
+
+  page_had_media_count:
+    bug_numbers:
+      - 1476456
+    description: >
+      The number of times a document hierarchy contained at least one audible HTMLMediaElement that had play() called upon it.
+    expires: "68"
+    kind: uint
+    notification_emails:
+      - cpearce@mozilla.com
+      - alwu@mozilla.com
+      - nohlmeier@mozilla.com
+    release_channel_collection: opt-in
+    record_in_processes:
+      - main
+      - content
+
+  page_had_play_revoked_count:
+    bug_numbers:
+      - 1476456
+    description: >
+      The number of times a document hierarchy contained at least one muted playing HTMLMediaElement that was paused due to becoming unmuted while not being allowed to autoplay.
+    expires: "68"
+    kind: uint
+    notification_emails:
+      - cpearce@mozilla.com
+      - alwu@mozilla.com
+      - nohlmeier@mozilla.com
+    release_channel_collection: opt-in
+    record_in_processes:
+      - main
+      - content
+
+  autoplay_would_not_be_allowed_count:
+    bug_numbers:
+      - 1476456
+    description: >
+      The number of HTMLMediaElement autoplays on audible HTMLMediaElements which would not be allowed to play if block autoplay was enabled; we'd either prompt for permission to play or block outright depending on user preferences.
+    expires: "68"
+    kind: uint
+    notification_emails:
+      - cpearce@mozilla.com
+      - alwu@mozilla.com
+      - nohlmeier@mozilla.com
+    release_channel_collection: opt-in
+    record_in_processes:
+      - main
+      - content
+
+  autoplay_would_be_allowed_count:
+    bug_numbers:
+      - 1476456
+    description: >
+      The number of HTMLMediaElement autoplays on audible HTMLMediaElements that would have been allowed (not blocked) by the autoplay policy if block autoplay was enabled.
+    expires: "68"
+    kind: uint
+    notification_emails:
+      - cpearce@mozilla.com
+      - alwu@mozilla.com
+      - nohlmeier@mozilla.com
+    release_channel_collection: opt-in
+    record_in_processes:
+      - main
+      - content
+
 # The following section contains content process base counters.
 dom.contentprocess:
   troubled_due_to_memory:
     bug_numbers:
       - 1305091
     description: >
       The number of content processes that were marked as troubled because
       it was running low on virtual memory.