Bug 1251448 - Add support for RequestInit.referrer; r=jdm
authorEhsan Akhgari <ehsan@mozilla.com>
Thu, 25 Feb 2016 18:12:20 -0500
changeset 286523 3fd1ae10e2c81631325b0886452c115fc297d17a
parent 286522 1a1d1594492d7bfb40c961f0fe86c966809ab1c2
child 286524 29dec7e96a9c87e54b5ec1b169df10c94c299f53
push id72806
push usereakhgari@mozilla.com
push dateWed, 02 Mar 2016 23:18:39 +0000
treeherdermozilla-inbound@3fd1ae10e2c8 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjdm
bugs1251448
milestone47.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 1251448 - Add support for RequestInit.referrer; r=jdm
dom/bindings/Errors.msg
dom/fetch/Request.cpp
dom/webidl/Request.webidl
testing/web-platform/meta/fetch/api/policies/referrer-origin-worker.html.ini
testing/web-platform/meta/fetch/api/policies/referrer-origin.html.ini
testing/web-platform/meta/fetch/api/request/request-error.html.ini
testing/web-platform/meta/fetch/api/request/request-init-001.sub.html.ini
--- a/dom/bindings/Errors.msg
+++ b/dom/bindings/Errors.msg
@@ -56,16 +56,17 @@ MSG_DEF(MSG_INVALID_READ_SIZE, 0, JSEXN_
 MSG_DEF(MSG_HEADERS_IMMUTABLE, 0, JSEXN_TYPEERR, "Headers are immutable and cannot be modified.")
 MSG_DEF(MSG_INVALID_HEADER_NAME, 1, JSEXN_TYPEERR, "{0} is an invalid header name.")
 MSG_DEF(MSG_INVALID_HEADER_VALUE, 1, JSEXN_TYPEERR, "{0} is an invalid header value.")
 MSG_DEF(MSG_INVALID_HEADER_SEQUENCE, 0, JSEXN_TYPEERR, "Headers require name/value tuples when being initialized by a sequence.")
 MSG_DEF(MSG_PERMISSION_DENIED_TO_PASS_ARG, 1, JSEXN_TYPEERR, "Permission denied to pass cross-origin object as {0}.")
 MSG_DEF(MSG_MISSING_REQUIRED_DICTIONARY_MEMBER, 1, JSEXN_TYPEERR, "Missing required {0}.")
 MSG_DEF(MSG_INVALID_REQUEST_METHOD, 1, JSEXN_TYPEERR, "Invalid request method {0}.")
 MSG_DEF(MSG_INVALID_REQUEST_MODE, 1, JSEXN_TYPEERR, "Invalid request mode {0}.")
+MSG_DEF(MSG_INVALID_REFERRER_URL, 1, JSEXN_TYPEERR, "Invalid referrer URL {0}.")
 MSG_DEF(MSG_FETCH_BODY_CONSUMED_ERROR, 0, JSEXN_TYPEERR, "Body has already been consumed.")
 MSG_DEF(MSG_RESPONSE_INVALID_STATUSTEXT_ERROR, 0, JSEXN_TYPEERR, "Response statusText may not contain newline or carriage return.")
 MSG_DEF(MSG_FETCH_FAILED, 0, JSEXN_TYPEERR, "NetworkError when attempting to fetch resource.")
 MSG_DEF(MSG_NO_BODY_ALLOWED_FOR_GET_AND_HEAD, 0, JSEXN_TYPEERR, "HEAD or GET Request cannot have a body.")
 MSG_DEF(MSG_RESPONSE_NULL_STATUS_WITH_BODY, 0, JSEXN_TYPEERR, "Response body is given with a null body status.")
 MSG_DEF(MSG_DEFINE_NON_CONFIGURABLE_PROP_ON_WINDOW, 0, JSEXN_TYPEERR, "Not allowed to define a non-configurable property on the WindowProxy object")
 MSG_DEF(MSG_INVALID_ZOOMANDPAN_VALUE_ERROR, 0, JSEXN_RANGEERR, "Invalid zoom and pan value.")
 MSG_DEF(MSG_INVALID_TRANSFORM_ANGLE_ERROR, 0, JSEXN_RANGEERR, "Invalid transform angle.")
--- a/dom/fetch/Request.cpp
+++ b/dom/fetch/Request.cpp
@@ -86,28 +86,38 @@ Request::RequestCacheEnabled(JSContext* 
 already_AddRefed<InternalRequest>
 Request::GetInternalRequest()
 {
   RefPtr<InternalRequest> r = mRequest;
   return r.forget();
 }
 
 namespace {
-void
-GetRequestURLFromDocument(nsIDocument* aDocument, const nsAString& aInput,
-                          nsAString& aRequestURL, ErrorResult& aRv)
+already_AddRefed<nsIURI>
+ParseURLFromDocument(nsIDocument* aDocument, const nsAString& aInput,
+                     ErrorResult& aRv)
 {
   MOZ_ASSERT(aDocument);
   MOZ_ASSERT(NS_IsMainThread());
 
   nsCOMPtr<nsIURI> baseURI = aDocument->GetBaseURI();
   nsCOMPtr<nsIURI> resolvedURI;
   aRv = NS_NewURI(getter_AddRefs(resolvedURI), aInput, nullptr, baseURI);
   if (NS_WARN_IF(aRv.Failed())) {
     aRv.ThrowTypeError<MSG_INVALID_URL>(aInput);
+  }
+  return resolvedURI.forget();
+}
+
+void
+GetRequestURLFromDocument(nsIDocument* aDocument, const nsAString& aInput,
+                          nsAString& aRequestURL, ErrorResult& aRv)
+{
+  nsCOMPtr<nsIURI> resolvedURI = ParseURLFromDocument(aDocument, aInput, aRv);
+  if (aRv.Failed()) {
     return;
   }
 
   // This fails with URIs with weird protocols, even when they are valid,
   // so we ignore the failure
   nsAutoCString credentials;
   Unused << resolvedURI->GetUserPass(credentials);
   if (!credentials.IsEmpty()) {
@@ -127,26 +137,35 @@ GetRequestURLFromDocument(nsIDocument* a
   aRv = resolvedURIClone->GetSpec(spec);
   if (NS_WARN_IF(aRv.Failed())) {
     return;
   }
 
   CopyUTF8toUTF16(spec, aRequestURL);
 }
 
-void
-GetRequestURLFromChrome(const nsAString& aInput, nsAString& aRequestURL,
-                        ErrorResult& aRv)
+already_AddRefed<nsIURI>
+ParseURLFromChrome(const nsAString& aInput, ErrorResult& aRv)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   nsCOMPtr<nsIURI> uri;
   aRv = NS_NewURI(getter_AddRefs(uri), aInput, nullptr, nullptr);
   if (NS_WARN_IF(aRv.Failed())) {
     aRv.ThrowTypeError<MSG_INVALID_URL>(aInput);
+  }
+  return uri.forget();
+}
+
+void
+GetRequestURLFromChrome(const nsAString& aInput, nsAString& aRequestURL,
+                        ErrorResult& aRv)
+{
+  nsCOMPtr<nsIURI> uri = ParseURLFromChrome(aInput, aRv);
+  if (aRv.Failed()) {
     return;
   }
 
   // This fails with URIs with weird protocols, even when they are valid,
   // so we ignore the failure
   nsAutoCString credentials;
   Unused << uri->GetUserPass(credentials);
   if (!credentials.IsEmpty()) {
@@ -166,29 +185,39 @@ GetRequestURLFromChrome(const nsAString&
   aRv = uriClone->GetSpec(spec);
   if (NS_WARN_IF(aRv.Failed())) {
     return;
   }
 
   CopyUTF8toUTF16(spec, aRequestURL);
 }
 
-void
-GetRequestURLFromWorker(const GlobalObject& aGlobal, const nsAString& aInput,
-                        nsAString& aRequestURL, ErrorResult& aRv)
+already_AddRefed<workers::URL>
+ParseURLFromWorker(const GlobalObject& aGlobal, const nsAString& aInput,
+                   ErrorResult& aRv)
 {
   workers::WorkerPrivate* worker = workers::GetCurrentThreadWorkerPrivate();
   MOZ_ASSERT(worker);
   worker->AssertIsOnWorkerThread();
 
   NS_ConvertUTF8toUTF16 baseURL(worker->GetLocationInfo().mHref);
   RefPtr<workers::URL> url =
     workers::URL::Constructor(aGlobal, aInput, baseURL, aRv);
   if (NS_WARN_IF(aRv.Failed())) {
     aRv.ThrowTypeError<MSG_INVALID_URL>(aInput);
+  }
+  return url.forget();
+}
+
+void
+GetRequestURLFromWorker(const GlobalObject& aGlobal, const nsAString& aInput,
+                        nsAString& aRequestURL, ErrorResult& aRv)
+{
+  RefPtr<workers::URL> url = ParseURLFromWorker(aGlobal, aInput, aRv);
+  if (aRv.Failed()) {
     return;
   }
 
   nsString username;
   url->GetUsername(username, aRv);
   if (NS_WARN_IF(aRv.Failed())) {
     return;
   }
@@ -210,16 +239,48 @@ GetRequestURLFromWorker(const GlobalObje
   }
 
   url->Stringify(aRequestURL, aRv);
   if (NS_WARN_IF(aRv.Failed())) {
     return;
   }
 }
 
+class ReferrerSameOriginChecker final : public workers::WorkerMainThreadRunnable
+{
+public:
+  ReferrerSameOriginChecker(workers::WorkerPrivate* aWorkerPrivate,
+                            const nsAString& aReferrerURL,
+                            nsresult& aResult)
+    : workers::WorkerMainThreadRunnable(aWorkerPrivate),
+      mReferrerURL(aReferrerURL),
+      mResult(aResult)
+  {
+    mWorkerPrivate->AssertIsOnWorkerThread();
+  }
+
+  bool
+  MainThreadRun() override
+  {
+    nsCOMPtr<nsIURI> uri;
+    if (NS_SUCCEEDED(NS_NewURI(getter_AddRefs(uri), mReferrerURL))) {
+      nsCOMPtr<nsIPrincipal> principal = mWorkerPrivate->GetPrincipal();
+      if (principal) {
+        mResult = principal->CheckMayLoad(uri, /* report */ false,
+                                          /* allowIfInheritsPrincipal */ false);
+      }
+    }
+    return true;
+  }
+
+private:
+  const nsString mReferrerURL;
+  nsresult& mResult;
+};
+
 } // namespace
 
 /*static*/ already_AddRefed<Request>
 Request::Constructor(const GlobalObject& aGlobal,
                      const RequestOrUSVString& aInput,
                      const RequestInit& aInit, ErrorResult& aRv)
 {
   nsCOMPtr<nsIInputStream> temporaryBody;
@@ -285,16 +346,84 @@ Request::Constructor(const GlobalObject&
                                    : fallbackCredentials;
 
   if (mode == RequestMode::Navigate ||
       (aInit.IsAnyMemberPresent() && request->Mode() == RequestMode::Navigate)) {
     aRv.ThrowTypeError<MSG_INVALID_REQUEST_MODE>(NS_LITERAL_STRING("navigate"));
     return nullptr;
   }
 
+  if (aInit.IsAnyMemberPresent()) {
+    request->SetReferrer(NS_LITERAL_STRING(kFETCH_CLIENT_REFERRER_STR));
+  }
+  if (aInit.mReferrer.WasPassed()) {
+    const nsString& referrer = aInit.mReferrer.Value();
+    if (referrer.IsEmpty()) {
+      request->SetReferrer(NS_LITERAL_STRING(""));
+    } else {
+      nsAutoString referrerURL;
+      if (NS_IsMainThread()) {
+        nsIDocument* doc = GetEntryDocument();
+        nsCOMPtr<nsIURI> uri;
+        if (doc) {
+          uri = ParseURLFromDocument(doc, referrer, aRv);
+        } else {
+          // If we don't have a document, we must assume that this is a full URL.
+          uri = ParseURLFromChrome(referrer, aRv);
+        }
+        if (NS_WARN_IF(aRv.Failed())) {
+          aRv.ThrowTypeError<MSG_INVALID_REFERRER_URL>(referrer);
+          return nullptr;
+        }
+        nsAutoCString spec;
+        uri->GetSpec(spec);
+        CopyUTF8toUTF16(spec, referrerURL);
+        if (!referrerURL.EqualsLiteral(kFETCH_CLIENT_REFERRER_STR)) {
+          nsCOMPtr<nsIPrincipal> principal = global->PrincipalOrNull();
+          if (principal) {
+            nsresult rv = principal->CheckMayLoad(uri, /* report */ false,
+                                                  /* allowIfInheritsPrincipal */ false);
+            if (NS_FAILED(rv)) {
+              aRv.ThrowTypeError<MSG_INVALID_REFERRER_URL>(referrer);
+              return nullptr;
+            }
+          }
+        }
+      } else {
+        RefPtr<workers::URL> url = ParseURLFromWorker(aGlobal, referrer, aRv);
+        if (NS_WARN_IF(aRv.Failed())) {
+          aRv.ThrowTypeError<MSG_INVALID_REFERRER_URL>(referrer);
+          return nullptr;
+        }
+        url->Stringify(referrerURL, aRv);
+        if (NS_WARN_IF(aRv.Failed())) {
+          aRv.ThrowTypeError<MSG_INVALID_REFERRER_URL>(referrer);
+          return nullptr;
+        }
+        if (!referrerURL.EqualsLiteral(kFETCH_CLIENT_REFERRER_STR)) {
+          workers::WorkerPrivate* worker = workers::GetCurrentThreadWorkerPrivate();
+          nsresult rv = NS_OK;
+          // ReferrerSameOriginChecker uses a sync loop to get the main thread
+          // to perform the same-origin check.  Overall, on Workers this method
+          // can create 3 sync loops (two for constructing URLs and one here) so
+          // in the future we may want to optimize it all by off-loading all of
+          // this work in a single sync loop.
+          RefPtr<ReferrerSameOriginChecker> checker =
+            new ReferrerSameOriginChecker(worker, referrerURL, rv);
+          checker->Dispatch(aRv);
+          if (aRv.Failed() || NS_FAILED(rv)) {
+            aRv.ThrowTypeError<MSG_INVALID_REFERRER_URL>(referrer);
+            return nullptr;
+          }
+        }
+      }
+      request->SetReferrer(referrerURL);
+    }
+  }
+
   if (mode != RequestMode::EndGuard_) {
     request->ClearCreatedByFetchEvent();
     request->SetMode(mode);
   }
 
   if (credentials != RequestCredentials::EndGuard_) {
     request->ClearCreatedByFetchEvent();
     request->SetCredentialsMode(credentials);
--- a/dom/webidl/Request.webidl
+++ b/dom/webidl/Request.webidl
@@ -34,16 +34,17 @@ interface Request {
   void setContentPolicyType(nsContentPolicyType context);
 };
 Request implements Body;
 
 dictionary RequestInit {
   ByteString method;
   HeadersInit headers;
   BodyInit? body;
+  USVString referrer;
   RequestMode mode;
   RequestCredentials credentials;
   RequestCache cache;
   RequestRedirect redirect;
 };
 
 // Gecko currently does not ship RequestContext, so please don't use it in IDL
 // that is exposed to script.
--- a/testing/web-platform/meta/fetch/api/policies/referrer-origin-worker.html.ini
+++ b/testing/web-platform/meta/fetch/api/policies/referrer-origin-worker.html.ini
@@ -1,8 +1,6 @@
 [referrer-origin-worker.html]
   type: testharness
   [Request's referrer is origin]
     expected: FAIL
+    bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1251378
 
-  [Throw a TypeError referrer is not same-origin with origin]
-    expected: FAIL
-
deleted file mode 100644
--- a/testing/web-platform/meta/fetch/api/policies/referrer-origin.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[referrer-origin.html]
-  type: testharness
-  [Throw a TypeError referrer is not same-origin with origin]
-    expected: FAIL
-
--- a/testing/web-platform/meta/fetch/api/request/request-error.html.ini
+++ b/testing/web-platform/meta/fetch/api/request/request-error.html.ini
@@ -1,17 +1,11 @@
 [request-error.html]
   type: testharness
   [RequestInit's window is not null]
     expected: FAIL
 
-  [RequestInit's referrer is invalid]
-    expected: FAIL
-
-  [RequestInit's referrer has invalid origin]
-    expected: FAIL
-
   [RequestInit's mode is no-cors and integrity is not empty]
     expected: FAIL
 
   [Bad referrerPolicy init parameter value]
     expected: FAIL
 
--- a/testing/web-platform/meta/fetch/api/request/request-init-001.sub.html.ini
+++ b/testing/web-platform/meta/fetch/api/request/request-init-001.sub.html.ini
@@ -1,22 +1,10 @@
 [request-init-001.sub.html]
   type: testharness
-  [Check referrer init value of /relative/ressource and associated getter]
-    expected: FAIL
-
-  [Check referrer init value of http://web-platform.test:8000/relative/ressource?query=true#fragment and associated getter]
-    expected: FAIL
-
-  [Check referrer init value of http://web-platform.test:8000/ and associated getter]
-    expected: FAIL
-
-  [Check referrer init value of  and associated getter]
-    expected: FAIL
-
   [Check referrerPolicy init value of  and associated getter]
     expected: FAIL
 
   [Check referrerPolicy init value of no-referrer and associated getter]
     expected: FAIL
 
   [Check referrerPolicy init value of no-referrer-when-downgrade and associated getter]
     expected: FAIL