Bug 1469714 - Part 2: Implement the Storage Access API to grant first-party storage access to 3rd party trackers; r=baku
☠☠ backed out by dc31941ced58 ☠ ☠
authorEhsan Akhgari <ehsan@mozilla.com>
Wed, 12 Sep 2018 19:20:28 -0400
changeset 436789 4db771422e75623a15a71c6a452cc51a2ba1f68f
parent 436788 fe750643da13ae1b7c7c196ee66683f8ed51d32e
child 436790 f90b4272f420ef8128305255fd53bd3dee82a94a
push id34660
push userbtara@mozilla.com
push dateMon, 17 Sep 2018 21:58:52 +0000
treeherdermozilla-central@87a95e1b7ec6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbaku
bugs1469714
milestone64.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 1469714 - Part 2: Implement the Storage Access API to grant first-party storage access to 3rd party trackers; r=baku Differential Revision: https://phabricator.services.mozilla.com/D5811
dom/base/nsDocument.cpp
dom/base/nsGlobalWindowOuter.cpp
dom/base/nsGlobalWindowOuter.h
dom/base/nsIDocument.h
dom/webidl/Document.webidl
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -112,16 +112,17 @@
 #include "nsIPermissionManager.h"
 #include "nsIPrincipal.h"
 #include "ExpandedPrincipal.h"
 #include "mozilla/NullPrincipal.h"
 
 #include "nsIDOMWindow.h"
 #include "nsPIDOMWindow.h"
 #include "nsFocusManager.h"
+#include "nsICookieService.h"
 
 // for radio group stuff
 #include "nsIRadioVisitor.h"
 #include "nsIFormControl.h"
 
 #include "nsBidiUtils.h"
 
 #include "nsContentCreatorFunctions.h"
@@ -13524,16 +13525,185 @@ nsIDocument::GetSelection(ErrorResult& a
 
   if (!window->IsCurrentInnerWindow()) {
     return nullptr;
   }
 
   return nsGlobalWindowInner::Cast(window)->GetSelection(aRv);
 }
 
+already_AddRefed<mozilla::dom::Promise>
+nsIDocument::HasStorageAccess(mozilla::ErrorResult& aRv)
+{
+  nsIGlobalObject* global = GetScopeObject();
+  if (!global) {
+    aRv.Throw(NS_ERROR_NOT_AVAILABLE);
+    return nullptr;
+  }
+
+  RefPtr<Promise> promise = Promise::Create(global, aRv);
+  if (aRv.Failed()) {
+    return nullptr;
+  }
+
+  if (NodePrincipal()->GetIsNullPrincipal()) {
+    promise->MaybeResolve(false);
+    return promise.forget();
+  }
+
+  if (IsTopLevelContentDocument()) {
+    promise->MaybeResolve(true);
+    return promise.forget();
+  }
+
+  nsCOMPtr<nsIDocument> topLevelDoc = GetTopLevelContentDocument();
+  if (!topLevelDoc) {
+    aRv.Throw(NS_ERROR_NOT_AVAILABLE);
+    return nullptr;
+  }
+  if (topLevelDoc->NodePrincipal()->Equals(NodePrincipal())) {
+    promise->MaybeResolve(true);
+    return promise.forget();
+  }
+
+  nsPIDOMWindowInner* inner = GetInnerWindow();
+  nsGlobalWindowOuter* outer = nullptr;
+  if (inner) {
+    outer = nsGlobalWindowOuter::Cast(inner->GetOuterWindow());
+    promise->MaybeResolve(outer->HasStorageAccess());
+  } else {
+    promise->MaybeRejectWithUndefined();
+  }
+  return promise.forget();
+}
+
+already_AddRefed<mozilla::dom::Promise>
+nsIDocument::RequestStorageAccess(mozilla::ErrorResult& aRv)
+{
+  nsIGlobalObject* global = GetScopeObject();
+  if (!global) {
+    aRv.Throw(NS_ERROR_NOT_AVAILABLE);
+    return nullptr;
+  }
+
+  RefPtr<Promise> promise = Promise::Create(global, aRv);
+  if (aRv.Failed()) {
+    return nullptr;
+  }
+
+  // Step 1. If the document already has been granted access, resolve.
+  nsPIDOMWindowInner* inner = GetInnerWindow();
+  nsGlobalWindowOuter* outer = nullptr;
+  if (inner) {
+    outer = nsGlobalWindowOuter::Cast(inner->GetOuterWindow());
+    if (outer->HasStorageAccess()) {
+      promise->MaybeResolveWithUndefined();
+      return promise.forget();
+    }
+  }
+
+  // Step 2. If the document has a null origin, reject.
+  if (NodePrincipal()->GetIsNullPrincipal()) {
+    promise->MaybeRejectWithUndefined();
+    return promise.forget();
+  }
+
+  // Only enforce third-party checks when there is a reason to enforce them.
+  if (StaticPrefs::network_cookie_cookieBehavior() !=
+        nsICookieService::BEHAVIOR_ACCEPT) {
+    // Step 3. If the document's frame is the main frame, resolve.
+    if (IsTopLevelContentDocument()) {
+      promise->MaybeResolveWithUndefined();
+      return promise.forget();
+    }
+
+    // Step 4. If the sub frame's origin is equal to the main frame's, resolve.
+    nsCOMPtr<nsIDocument> topLevelDoc = GetTopLevelContentDocument();
+    if (!topLevelDoc) {
+      aRv.Throw(NS_ERROR_NOT_AVAILABLE);
+      return nullptr;
+    }
+    if (topLevelDoc->NodePrincipal()->Equals(NodePrincipal())) {
+      promise->MaybeResolveWithUndefined();
+      return promise.forget();
+    }
+  }
+
+  // Step 5. If the sub frame is not sandboxed, skip to step 7.
+  // Step 6. If the sub frame doesn't have the token
+  //         "allow-storage-access-by-user-activation", reject.
+  if (mSandboxFlags & SANDBOXED_STORAGE_ACCESS) {
+    promise->MaybeRejectWithUndefined();
+    return promise.forget();
+  }
+
+  // Step 7. If the sub frame's parent frame is not the top frame, reject.
+  nsIDocument* parent = GetParentDocument();
+  if (parent && !parent->IsTopLevelContentDocument()) {
+    promise->MaybeRejectWithUndefined();
+    return promise.forget();
+  }
+
+  // Step 8. If the browser is not processing a user gesture, reject.
+  if (!EventStateManager::IsHandlingUserInput()) {
+    promise->MaybeRejectWithUndefined();
+    return promise.forget();
+  }
+
+  // Step 9. Check any additional rules that the browser has.
+  //         Examples: Whitelists, blacklists, on-device classification,
+  //         user settings, anti-clickjacking heuristics, or prompting the
+  //         user for explicit permission. Reject if some rule is not fulfilled.
+
+  bool granted = true;
+  bool isTrackingWindow = false;
+  if (StaticPrefs::browser_contentblocking_enabled() &&
+      StaticPrefs::network_cookie_cookieBehavior() ==
+        nsICookieService::BEHAVIOR_REJECT_TRACKER) {
+    // Only do something special for third-party tracking content.
+    if (nsContentUtils::StorageDisabledByAntiTracking(this, nullptr)) {
+      isTrackingWindow = true;
+      // TODO: prompt for permission
+    }
+  }
+
+  // Step 10. Grant the document access to cookies and store that fact for
+  //          the purposes of future calls to hasStorageAccess() and
+  //          requestStorageAccess().
+  if (granted && inner) {
+    outer->SetHasStorageAccess(true);
+    if (isTrackingWindow) {
+      nsCOMPtr<nsIURI> uri = GetDocumentURI();
+      if (NS_WARN_IF(!uri)) {
+        aRv.Throw(NS_ERROR_NOT_AVAILABLE);
+        return nullptr;
+      }
+      nsAutoString origin;
+      nsresult rv = nsContentUtils::GetUTFOrigin(uri, origin);
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        aRv.Throw(rv);
+        return nullptr;
+      }
+      AntiTrackingCommon::AddFirstPartyStorageAccessGrantedFor(origin,
+                                                               inner,
+                                                               AntiTrackingCommon::eStorageAccessAPI)
+        ->Then(GetCurrentThreadSerialEventTarget(), __func__,
+               [promise] (bool) {
+                 promise->MaybeResolveWithUndefined();
+               },
+               [promise] (bool) {
+                 promise->MaybeRejectWithUndefined();
+               });
+    } else {
+      promise->MaybeResolveWithUndefined();
+    }
+  }
+  return promise.forget();
+}
+
 void
 nsIDocument::RecordNavigationTiming(ReadyState aReadyState)
 {
   if (!XRE_IsContentProcess()) {
     return;
   }
   if (!IsTopLevelContentDocument()) {
     return;
--- a/dom/base/nsGlobalWindowOuter.cpp
+++ b/dom/base/nsGlobalWindowOuter.cpp
@@ -824,16 +824,17 @@ nsGlobalWindowOuter::nsGlobalWindowOuter
     mHadOriginalOpener(false),
     mIsPopupSpam(false),
     mBlockScriptedClosingFlag(false),
     mWasOffline(false),
     mCreatingInnerWindow(false),
     mIsChrome(false),
     mAllowScriptsToClose(false),
     mTopLevelOuterContentWindow(false),
+    mHasStorageAccess(false),
     mSerial(0),
 #ifdef DEBUG
     mSetOpenerWindowCalled(false),
 #endif
     mCleanedUp(false),
 #ifdef DEBUG
     mIsValidatingTabGroup(false),
 #endif
@@ -2010,16 +2011,18 @@ nsGlobalWindowOuter::SetNewDocument(nsID
 
   PreloadLocalStorage();
 
   // If we have a recorded interesting Large-Allocation header status, report it
   // to the newly attached document.
   ReportLargeAllocStatus();
   mLargeAllocStatus = LargeAllocStatus::NONE;
 
+  mHasStorageAccess = false;
+
   return NS_OK;
 }
 
 void
 nsGlobalWindowOuter::PreloadLocalStorage()
 {
   if (!Storage::StoragePrefIsEnabled()) {
     return;
--- a/dom/base/nsGlobalWindowOuter.h
+++ b/dom/base/nsGlobalWindowOuter.h
@@ -959,16 +959,25 @@ public:
   bool WindowExists(const nsAString& aName, bool aForceNoOpener,
                     bool aLookForCallerOnJSStack);
 
   already_AddRefed<nsIWidget> GetMainWidget();
   nsIWidget* GetNearestWidget() const;
 
   bool IsInModalState();
 
+  bool HasStorageAccess() const
+  {
+    return mHasStorageAccess;
+  }
+  void SetHasStorageAccess(bool aHasStorageAccess)
+  {
+    mHasStorageAccess = aHasStorageAccess;
+  }
+
   // Convenience functions for the many methods that need to scale
   // from device to CSS pixels or vice versa.  Note: if a presentation
   // context is not available, they will assume a 1:1 ratio.
   int32_t DevToCSSIntPixels(int32_t px);
   int32_t CSSToDevIntPixels(int32_t px);
   nsIntSize DevToCSSIntPixels(nsIntSize px);
   nsIntSize CSSToDevIntPixels(nsIntSize px);
 
@@ -1083,16 +1092,19 @@ protected:
   bool                          mIsChrome : 1;
 
   // whether scripts may close the window,
   // even if "dom.allow_scripts_to_close_windows" is false.
   bool                   mAllowScriptsToClose : 1;
 
   bool mTopLevelOuterContentWindow : 1;
 
+  // whether storage access has been granted to this frame.
+  bool mHasStorageAccess : 1;
+
   nsCOMPtr<nsIScriptContext>    mContext;
   nsWeakPtr                     mOpener;
   nsCOMPtr<nsIControllers>      mControllers;
 
   // For |window.arguments|, via |openDialog|.
   nsCOMPtr<nsIArray>            mArguments;
 
   RefPtr<nsDOMWindowList>     mFrames;
--- a/dom/base/nsIDocument.h
+++ b/dom/base/nsIDocument.h
@@ -1229,16 +1229,21 @@ public:
 
   /**
    * Return the root element for this document.
    */
   Element* GetRootElement() const;
 
   mozilla::dom::Selection* GetSelection(mozilla::ErrorResult& aRv);
 
+  already_AddRefed<mozilla::dom::Promise>
+    HasStorageAccess(mozilla::ErrorResult& aRv);
+  already_AddRefed<mozilla::dom::Promise>
+    RequestStorageAccess(mozilla::ErrorResult& aRv);
+
   /**
    * Gets the event target to dispatch key events to if there is no focused
    * content in the document.
    */
   virtual nsIContent* GetUnfocusedKeyEventTarget();
 
   /**
    * Retrieve information about the viewport as a data structure.
--- a/dom/webidl/Document.webidl
+++ b/dom/webidl/Document.webidl
@@ -478,16 +478,24 @@ partial interface Document {
 };
 
 // http://w3c.github.io/selection-api/#extensions-to-document-interface
 partial interface Document {
   [Throws]
   Selection? getSelection();
 };
 
+// https://github.com/whatwg/html/issues/3338
+partial interface Document {
+  [Pref="dom.storage_access.enabled", Throws]
+  Promise<boolean> hasStorageAccess();
+  [Pref="dom.storage_access.enabled", Throws]
+  Promise<void> requestStorageAccess();
+};
+
 // Extension to give chrome JS the ability to determine whether
 // the user has interacted with the document or not.
 partial interface Document {
   [ChromeOnly] readonly attribute boolean userHasInteracted;
 };
 
 // Extension to give chrome JS the ability to simulate activate the docuement
 // by user gesture.