Bug 1369309 - Part 3: Making the media statistics reports a spoofed value when fingerprinting resistance is enabled. r=cpearce, r=arthuredelstein
authorTim Huang <tihuang@mozilla.com>
Mon, 17 Jul 2017 15:13:55 +0800
changeset 374026 9afd71d7438c5b7bbd01a317cf7490529b50fdc5
parent 374025 23efa91adf8a33e8e516b15db1a1031d8e997c18
child 374027 45ec9d0af14b0be23316c7d74f6d3df5e12b84eb
push id32312
push userarchaeopteryx@coole-files.de
push dateFri, 11 Aug 2017 09:55:13 +0000
treeherdermozilla-central@f74094603063 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerscpearce, arthuredelstein
bugs1369309
milestone57.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 1369309 - Part 3: Making the media statistics reports a spoofed value when fingerprinting resistance is enabled. r=cpearce, r=arthuredelstein This patch makes the media statistics report values with a fixed frames per second and a dynamic dropped ratio when resistance fingerprinting is enabled. The dropped rate is decided by the video resolution that it will report a fixed dropped rate when the video resolution is greater than 480p. And It will report a zero dropped rate if the video is below or equal to 480p. In addition, it adds three new prefs that allow us to change the value of frames per second, the dropped ratio and the threshold of target video resolution. The three prefs are 'privacy.resistFingerprinting.video_frames_per_sec', 'privacy.resistFingerprinting.video_dropped_ratio' and 'privacy.resistFingerprinting.target_video_res'. The default values of them are 30, 5 and 480, which means 30 frames per second, 5 percent dropped ratio and 480p. This also adds a new helper function 'nsContentUtils::ShouldResistFingerprinting(nsIDocument* aDoc)' for checking whether fingerprinting resistance is enabled for a given docuemnt. If it is a chrome document, this function will indicate that fingerprinting resistance is not enabled regardless of the pref 'privacy.resistFingerprinting'. If it is a content document, the result will depend on the pref. MozReview-Commit-ID: FbSuRq6Zdnn
dom/base/nsContentUtils.cpp
dom/base/nsContentUtils.h
dom/html/HTMLVideoElement.cpp
dom/html/HTMLVideoElement.h
toolkit/components/resistfingerprinting/nsRFPService.cpp
toolkit/components/resistfingerprinting/nsRFPService.h
--- a/dom/base/nsContentUtils.cpp
+++ b/dom/base/nsContentUtils.cpp
@@ -2400,17 +2400,26 @@ nsContentUtils::ShouldResistFingerprinti
 }
 
 bool
 nsContentUtils::ShouldResistFingerprinting(nsIDocShell* aDocShell)
 {
   if (!aDocShell) {
     return false;
   }
-  bool isChrome = nsContentUtils::IsChromeDoc(aDocShell->GetDocument());
+  return ShouldResistFingerprinting(aDocShell->GetDocument());
+}
+
+/* static */
+bool
+nsContentUtils::ShouldResistFingerprinting(nsIDocument* aDoc) {
+  if (!aDoc) {
+    return false;
+  }
+  bool isChrome = nsContentUtils::IsChromeDoc(aDoc);
   return !isChrome && ShouldResistFingerprinting();
 }
 
 /* static */
 void
 nsContentUtils::CalcRoundedWindowSizeForResistingFingerprinting(int32_t  aChromeWidth,
                                                                 int32_t  aChromeHeight,
                                                                 int32_t  aScreenWidth,
--- a/dom/base/nsContentUtils.h
+++ b/dom/base/nsContentUtils.h
@@ -271,16 +271,17 @@ public:
   static bool LookupBindingMember(JSContext* aCx, nsIContent *aContent,
                                   JS::Handle<jsid> aId,
                                   JS::MutableHandle<JS::PropertyDescriptor> aDesc);
 
   // Check whether we should avoid leaking distinguishing information to JS/CSS.
   // This function can be called both in the main thread and worker threads.
   static bool ShouldResistFingerprinting();
   static bool ShouldResistFingerprinting(nsIDocShell* aDocShell);
+  static bool ShouldResistFingerprinting(nsIDocument* aDoc);
 
   // A helper function to calculate the rounded window size for fingerprinting
   // resistance. The rounded size is based on the chrome UI size and available
   // screen size. If the inputWidth/Height is greater than the available content
   // size, this will report the available content size. Otherwise, it will
   // round the size to the nearest upper 200x100.
   static void CalcRoundedWindowSizeForResistingFingerprinting(int32_t  aChromeWidth,
                                                               int32_t  aChromeHeight,
--- a/dom/html/HTMLVideoElement.cpp
+++ b/dom/html/HTMLVideoElement.cpp
@@ -149,51 +149,73 @@ HTMLVideoElement::IsInteractiveHTMLConte
 }
 
 uint32_t HTMLVideoElement::MozParsedFrames() const
 {
   MOZ_ASSERT(NS_IsMainThread(), "Should be on main thread.");
   if (!IsVideoStatsEnabled()) {
     return 0;
   }
+
+  if (nsContentUtils::ShouldResistFingerprinting(OwnerDoc())) {
+    return nsRFPService::GetSpoofedTotalFrames(TotalPlayTime());
+  }
+
   return mDecoder ? mDecoder->GetFrameStatistics().GetParsedFrames() : 0;
 }
 
 uint32_t HTMLVideoElement::MozDecodedFrames() const
 {
   MOZ_ASSERT(NS_IsMainThread(), "Should be on main thread.");
   if (!IsVideoStatsEnabled()) {
     return 0;
   }
+
+  if (nsContentUtils::ShouldResistFingerprinting(OwnerDoc())) {
+    return nsRFPService::GetSpoofedTotalFrames(TotalPlayTime());
+  }
+
   return mDecoder ? mDecoder->GetFrameStatistics().GetDecodedFrames() : 0;
 }
 
 uint32_t HTMLVideoElement::MozPresentedFrames() const
 {
   MOZ_ASSERT(NS_IsMainThread(), "Should be on main thread.");
   if (!IsVideoStatsEnabled()) {
     return 0;
   }
+
+  if (nsContentUtils::ShouldResistFingerprinting(OwnerDoc())) {
+    return nsRFPService::GetSpoofedPresentedFrames(TotalPlayTime(), VideoWidth(), VideoHeight());
+  }
+
   return mDecoder ? mDecoder->GetFrameStatistics().GetPresentedFrames() : 0;
 }
 
 uint32_t HTMLVideoElement::MozPaintedFrames()
 {
   MOZ_ASSERT(NS_IsMainThread(), "Should be on main thread.");
   if (!IsVideoStatsEnabled()) {
     return 0;
   }
+
+  if (nsContentUtils::ShouldResistFingerprinting(OwnerDoc())) {
+    return nsRFPService::GetSpoofedPresentedFrames(TotalPlayTime(), VideoWidth(), VideoHeight());
+  }
+
   layers::ImageContainer* container = GetImageContainer();
   return container ? container->GetPaintCount() : 0;
 }
 
 double HTMLVideoElement::MozFrameDelay()
 {
   MOZ_ASSERT(NS_IsMainThread(), "Should be on main thread.");
-  if (!IsVideoStatsEnabled()) {
+
+  if (!IsVideoStatsEnabled() ||
+      nsContentUtils::ShouldResistFingerprinting(OwnerDoc())) {
     return 0.0;
   }
 
   VideoFrameContainer* container = GetVideoFrameContainer();
   // Hide negative delays. Frame timing tweaks in the compositor (e.g.
   // adding a bias value to prevent multiple dropped/duped frames when
   // frame times are aligned with composition times) may produce apparent
   // negative delay, but we shouldn't report that.
@@ -250,35 +272,43 @@ HTMLVideoElement::GetVideoPlaybackQualit
     if (nsPIDOMWindowInner* window = OwnerDoc()->GetInnerWindow()) {
       Performance* perf = window->GetPerformance();
       if (perf) {
         creationTime = perf->Now();
       }
     }
 
     if (mDecoder) {
-      FrameStatisticsData stats =
-        mDecoder->GetFrameStatistics().GetFrameStatisticsData();
-      if (sizeof(totalFrames) >= sizeof(stats.mParsedFrames)) {
-        totalFrames = stats.mPresentedFrames + stats.mDroppedFrames;
-        droppedFrames = stats.mDroppedFrames;
+      if (nsContentUtils::ShouldResistFingerprinting(OwnerDoc())) {
+        totalFrames = nsRFPService::GetSpoofedTotalFrames(TotalPlayTime());
+        droppedFrames = nsRFPService::GetSpoofedDroppedFrames(TotalPlayTime(),
+                                                              VideoWidth(),
+                                                              VideoHeight());
+        corruptedFrames = 0;
       } else {
-        uint64_t total = stats.mPresentedFrames + stats.mDroppedFrames;
-        const auto maxNumber = std::numeric_limits<uint32_t>::max();
-        if (total <= maxNumber) {
-          totalFrames = uint32_t(total);
-          droppedFrames = uint32_t(stats.mDroppedFrames);
+        FrameStatisticsData stats =
+          mDecoder->GetFrameStatistics().GetFrameStatisticsData();
+        if (sizeof(totalFrames) >= sizeof(stats.mParsedFrames)) {
+          totalFrames = stats.mPresentedFrames + stats.mDroppedFrames;
+          droppedFrames = stats.mDroppedFrames;
         } else {
-          // Too big number(s) -> Resize everything to fit in 32 bits.
-          double ratio = double(maxNumber) / double(total);
-          totalFrames = maxNumber; // === total * ratio
-          droppedFrames = uint32_t(double(stats.mDroppedFrames) * ratio);
+          uint64_t total = stats.mPresentedFrames + stats.mDroppedFrames;
+          const auto maxNumber = std::numeric_limits<uint32_t>::max();
+          if (total <= maxNumber) {
+            totalFrames = uint32_t(total);
+            droppedFrames = uint32_t(stats.mDroppedFrames);
+          } else {
+            // Too big number(s) -> Resize everything to fit in 32 bits.
+            double ratio = double(maxNumber) / double(total);
+            totalFrames = maxNumber; // === total * ratio
+            droppedFrames = uint32_t(double(stats.mDroppedFrames) * ratio);
+          }
         }
+        corruptedFrames = 0;
       }
-      corruptedFrames = 0;
     }
   }
 
   RefPtr<VideoPlaybackQuality> playbackQuality =
     new VideoPlaybackQuality(this, creationTime, totalFrames, droppedFrames,
                              corruptedFrames);
   return playbackQuality.forget();
 }
@@ -326,14 +356,43 @@ HTMLVideoElement::UpdateScreenWakeLock()
 void
 HTMLVideoElement::Init()
 {
   Preferences::AddBoolVarCache(&sVideoStatsEnabled, "media.video_stats.enabled");
 }
 
 /* static */
 bool
-HTMLVideoElement::IsVideoStatsEnabled() {
-  return sVideoStatsEnabled && !nsContentUtils::ShouldResistFingerprinting();
+HTMLVideoElement::IsVideoStatsEnabled()
+{
+  return sVideoStatsEnabled;
+}
+
+double
+HTMLVideoElement::TotalPlayTime() const
+{
+  double total = 0.0;
+
+  if (mPlayed) {
+    uint32_t timeRangeCount = 0;
+    mPlayed->GetLength(&timeRangeCount);
+
+    for (uint32_t i = 0; i < timeRangeCount; i++) {
+      double begin;
+      double end;
+      mPlayed->Start(i, &begin);
+      mPlayed->End(i, &end);
+      total += end - begin;
+    }
+
+    if (mCurrentPlayRangeStart != -1.0) {
+      double now = CurrentTime();
+      if (mCurrentPlayRangeStart != now) {
+        total += now - mCurrentPlayRangeStart;
+      }
+    }
+  }
+
+  return total;
 }
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/html/HTMLVideoElement.h
+++ b/dom/html/HTMLVideoElement.h
@@ -148,14 +148,15 @@ protected:
   bool mUseScreenWakeLock;
   RefPtr<WakeLock> mScreenWakeLock;
 
 private:
   static void MapAttributesIntoRule(const nsMappedAttributes* aAttributes,
                                     GenericSpecifiedValues* aGenericData);
 
   static bool IsVideoStatsEnabled();
+  double TotalPlayTime() const;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_HTMLVideoElement_h
--- a/toolkit/components/resistfingerprinting/nsRFPService.cpp
+++ b/toolkit/components/resistfingerprinting/nsRFPService.cpp
@@ -1,47 +1,59 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsRFPService.h"
 
+#include <algorithm>
 #include <time.h>
 
 #include "mozilla/ClearOnShutdown.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/Services.h"
 #include "mozilla/StaticPtr.h"
 
 #include "nsCOMPtr.h"
+#include "nsCoord.h"
 #include "nsServiceManagerUtils.h"
 #include "nsString.h"
 #include "nsXULAppAPI.h"
 
 #include "nsIObserverService.h"
 #include "nsIPrefBranch.h"
 #include "nsIPrefService.h"
 #include "nsJSUtils.h"
 
 #include "prenv.h"
 
 #include "js/Date.h"
 
 using namespace mozilla;
+using namespace std;
 
 #define RESIST_FINGERPRINTING_PREF "privacy.resistFingerprinting"
+#define RFP_SPOOFED_FRAMES_PER_SEC_PREF "privacy.resistFingerprinting.video_frames_per_sec"
+#define RFP_SPOOFED_DROPPED_RATIO_PREF  "privacy.resistFingerprinting.video_dropped_ratio"
+#define RFP_TARGET_VIDEO_RES_PREF "privacy.resistFingerprinting.target_video_res"
+#define RFP_SPOOFED_FRAMES_PER_SEC_DEFAULT 30
+#define RFP_SPOOFED_DROPPED_RATIO_DEFAULT  5
+#define RFP_TARGET_VIDEO_RES_DEFAULT 480
 #define PROFILE_INITIALIZED_TOPIC "profile-initial-state"
 
 NS_IMPL_ISUPPORTS(nsRFPService, nsIObserver)
 
 static StaticRefPtr<nsRFPService> sRFPService;
 static bool sInitialized = false;
 Atomic<bool, ReleaseAcquire> nsRFPService::sPrivacyResistFingerprinting;
 static uint32_t kResolutionUSec = 100000;
+static uint32_t sVideoFramesPerSec;
+static uint32_t sVideoDroppedRatio;
+static uint32_t sTargetVideoRes;
 
 /* static */
 nsRFPService*
 nsRFPService::GetOrCreate()
 {
   if (!sInitialized) {
     sRFPService = new nsRFPService();
     nsresult rv = sRFPService->Init();
@@ -75,32 +87,86 @@ nsRFPService::ReduceTimePrecisionAsUSecs
 {
   if (!IsResistFingerprintingEnabled()) {
     return aTime;
   }
   return floor(aTime / kResolutionUSec) * kResolutionUSec;
 }
 
 /* static */
+uint32_t
+nsRFPService::CalculateTargetVideoResolution(uint32_t aVideoQuality)
+{
+  return aVideoQuality * NSToIntCeil(aVideoQuality * 16 / 9.0);
+}
+
+/* static */
 double
 nsRFPService::ReduceTimePrecisionAsSecs(double aTime)
 {
   if (!IsResistFingerprintingEnabled()) {
     return aTime;
   }
   if (kResolutionUSec < 1000000) {
     // The resolution is smaller than one sec.  Use the reciprocal to avoid
     // floating point error.
     const double resolutionSecReciprocal = 1000000.0 / kResolutionUSec;
     return floor(aTime * resolutionSecReciprocal) / resolutionSecReciprocal;
   }
   const double resolutionSec = kResolutionUSec / 1000000.0;
   return floor(aTime / resolutionSec) * resolutionSec;
 }
 
+/* static */
+uint32_t
+nsRFPService::GetSpoofedTotalFrames(double aTime)
+{
+  double time = ReduceTimePrecisionAsSecs(aTime);
+
+  return NSToIntFloor(time * sVideoFramesPerSec);
+}
+
+/* static */
+uint32_t
+nsRFPService::GetSpoofedDroppedFrames(double aTime, uint32_t aWidth, uint32_t aHeight)
+{
+  uint32_t targetRes = CalculateTargetVideoResolution(sTargetVideoRes);
+
+  // The video resolution is less than or equal to the target resolution, we
+  // report a zero dropped rate for this case.
+  if (targetRes >= aWidth * aHeight) {
+    return 0;
+  }
+
+  double time = ReduceTimePrecisionAsSecs(aTime);
+  // Bound the dropped ratio from 0 to 100.
+  uint32_t boundedDroppedRatio = min(sVideoDroppedRatio, 100u);
+
+  return NSToIntFloor(time * sVideoFramesPerSec * (boundedDroppedRatio / 100.0));
+}
+
+/* static */
+uint32_t
+nsRFPService::GetSpoofedPresentedFrames(double aTime, uint32_t aWidth, uint32_t aHeight)
+{
+  uint32_t targetRes = CalculateTargetVideoResolution(sTargetVideoRes);
+
+  // The target resolution is greater than the current resolution. For this case,
+  // there will be no dropped frames, so we report total frames directly.
+  if (targetRes >= aWidth * aHeight) {
+    return GetSpoofedTotalFrames(aTime);
+  }
+
+  double time = ReduceTimePrecisionAsSecs(aTime);
+  // Bound the dropped ratio from 0 to 100.
+  uint32_t boundedDroppedRatio = min(sVideoDroppedRatio, 100u);
+
+  return NSToIntFloor(time * sVideoFramesPerSec * ((100 - boundedDroppedRatio) / 100.0));
+}
+
 nsresult
 nsRFPService::Init()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   nsresult rv;
 
   nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
@@ -115,16 +181,26 @@ nsRFPService::Init()
 #endif
 
   nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
   NS_ENSURE_TRUE(prefs, NS_ERROR_NOT_AVAILABLE);
 
   rv = prefs->AddObserver(RESIST_FINGERPRINTING_PREF, this, false);
   NS_ENSURE_SUCCESS(rv, rv);
 
+  Preferences::AddUintVarCache(&sVideoFramesPerSec,
+                               RFP_SPOOFED_FRAMES_PER_SEC_PREF,
+                               RFP_SPOOFED_FRAMES_PER_SEC_DEFAULT);
+  Preferences::AddUintVarCache(&sVideoDroppedRatio,
+                               RFP_SPOOFED_DROPPED_RATIO_PREF,
+                               RFP_SPOOFED_DROPPED_RATIO_DEFAULT);
+  Preferences::AddUintVarCache(&sTargetVideoRes,
+                               RFP_TARGET_VIDEO_RES_PREF,
+                               RFP_TARGET_VIDEO_RES_DEFAULT);
+
   // We backup the original TZ value here.
   const char* tzValue = PR_GetEnv("TZ");
   if (tzValue) {
     mInitialTZValue = nsCString(tzValue);
   }
 
   // Call UpdatePref() here to cache the value of 'privacy.resistFingerprinting'
   // and set the timezone.
--- a/toolkit/components/resistfingerprinting/nsRFPService.h
+++ b/toolkit/components/resistfingerprinting/nsRFPService.h
@@ -34,16 +34,26 @@ public:
     return sPrivacyResistFingerprinting;
   }
 
   // The following Reduce methods can be called off main thread.
   static double ReduceTimePrecisionAsMSecs(double aTime);
   static double ReduceTimePrecisionAsUSecs(double aTime);
   static double ReduceTimePrecisionAsSecs(double aTime);
 
+  // This method calculates the video resolution (i.e. height x width) based
+  // on the video quality (480p, 720p, etc).
+  static uint32_t CalculateTargetVideoResolution(uint32_t aVideoQuality);
+
+  // Methods for getting spoofed media statistics and the return value will
+  // depend on the video resolution.
+  static uint32_t GetSpoofedTotalFrames(double aTime);
+  static uint32_t GetSpoofedDroppedFrames(double aTime, uint32_t aWidth, uint32_t aHeight);
+  static uint32_t GetSpoofedPresentedFrames(double aTime, uint32_t aWidth, uint32_t aHeight);
+
 private:
   nsresult Init();
 
   nsRFPService() {}
 
   ~nsRFPService() {}
 
   void UpdatePref();