Bug 1039846 - Patch 4: FetchDriver with about:, blob: and data: fetching. r=baku
authorNikhil Marathe <nsm.nikhil@gmail.com>
Mon, 06 Oct 2014 11:01:20 -0700
changeset 211356 9cea2a5955d7a9757ee19dce3ffcfe9824286d3e
parent 211355 0660519a81b7d5c02096ed044d85c8f3a2487cd8
child 211357 f5a4ac30f9993505e2b2de1a01dd4809cd16a5c9
push id27673
push userkwierso@gmail.com
push dateTue, 21 Oct 2014 01:57:45 +0000
treeherdermozilla-central@29fbfc1b31aa [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbaku
bugs1039846
milestone36.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 1039846 - Patch 4: FetchDriver with about:, blob: and data: fetching. r=baku
content/base/public/nsHostObjectProtocolHandler.h
content/base/src/nsHostObjectProtocolHandler.cpp
dom/base/nsGlobalWindow.cpp
dom/fetch/Fetch.cpp
dom/fetch/Fetch.h
dom/fetch/FetchDriver.cpp
dom/fetch/FetchDriver.h
dom/fetch/InternalHeaders.cpp
dom/fetch/InternalHeaders.h
dom/fetch/InternalRequest.h
dom/fetch/InternalResponse.cpp
dom/fetch/InternalResponse.h
dom/fetch/Request.cpp
dom/fetch/Response.h
dom/fetch/moz.build
dom/promise/PromiseWorkerProxy.h
dom/tests/mochitest/fetch/mochitest.ini
dom/tests/mochitest/fetch/test_fetch_basic.html
dom/tests/mochitest/fetch/worker_test_fetch_basic.js
dom/webidl/Request.webidl
dom/workers/WorkerScope.cpp
--- a/content/base/public/nsHostObjectProtocolHandler.h
+++ b/content/base/public/nsHostObjectProtocolHandler.h
@@ -18,16 +18,17 @@
 #define RTSPURI_SCHEME "rtsp"
 
 class nsIDOMBlob;
 class nsIPrincipal;
 
 namespace mozilla {
 class DOMMediaStream;
 namespace dom {
+class FileImpl;
 class MediaSource;
 }
 }
 
 class nsHostObjectProtocolHandler : public nsIProtocolHandler
 {
 public:
   nsHostObjectProtocolHandler();
@@ -114,16 +115,19 @@ inline bool IsMediaSourceURI(nsIURI* aUr
 
 inline bool IsFontTableURI(nsIURI* aUri)
 {
   bool isFont;
   return NS_SUCCEEDED(aUri->SchemeIs(FONTTABLEURI_SCHEME, &isFont)) && isFont;
 }
 
 extern nsresult
+NS_GetBlobForBlobURI(nsIURI* aURI, mozilla::dom::FileImpl** aBlob);
+
+extern nsresult
 NS_GetStreamForBlobURI(nsIURI* aURI, nsIInputStream** aStream);
 
 extern nsresult
 NS_GetStreamForMediaStreamURI(nsIURI* aURI, mozilla::DOMMediaStream** aStream);
 
 extern nsresult
 NS_GetSourceForMediaSourceURI(nsIURI* aURI, mozilla::dom::MediaSource** aSource);
 
--- a/content/base/src/nsHostObjectProtocolHandler.cpp
+++ b/content/base/src/nsHostObjectProtocolHandler.cpp
@@ -583,29 +583,42 @@ nsMediaSourceProtocolHandler::GetScheme(
 NS_IMETHODIMP
 nsFontTableProtocolHandler::GetScheme(nsACString &result)
 {
   result.AssignLiteral(FONTTABLEURI_SCHEME);
   return NS_OK;
 }
 
 nsresult
-NS_GetStreamForBlobURI(nsIURI* aURI, nsIInputStream** aStream)
+NS_GetBlobForBlobURI(nsIURI* aURI, FileImpl** aBlob)
 {
   NS_ASSERTION(IsBlobURI(aURI), "Only call this with blob URIs");
 
-  *aStream = nullptr;
+  *aBlob = nullptr;
 
   nsCOMPtr<PIFileImpl> blobImpl = do_QueryInterface(GetDataObject(aURI));
   if (!blobImpl) {
     return NS_ERROR_DOM_BAD_URI;
   }
 
-  FileImpl* blob = static_cast<FileImpl*>(blobImpl.get());
-  return blob->GetInternalStream(aStream);
+  nsRefPtr<FileImpl> blob = static_cast<FileImpl*>(blobImpl.get());
+  blob.forget(aBlob);
+  return NS_OK;
+}
+
+nsresult
+NS_GetStreamForBlobURI(nsIURI* aURI, nsIInputStream** aStream)
+{
+  nsRefPtr<FileImpl> blobImpl;
+  nsresult rv = NS_GetBlobForBlobURI(aURI, getter_AddRefs(blobImpl));
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  return blobImpl->GetInternalStream(aStream);
 }
 
 nsresult
 NS_GetStreamForMediaStreamURI(nsIURI* aURI, mozilla::DOMMediaStream** aStream)
 {
   NS_ASSERTION(IsMediaStreamURI(aURI), "Only call this with mediastream URIs");
 
   *aStream = nullptr;
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -201,16 +201,17 @@
 #include "mozilla/DOMEventTargetHelper.h"
 #include "prrng.h"
 #include "nsSandboxFlags.h"
 #include "TimeChangeObserver.h"
 #include "mozilla/dom/AudioContext.h"
 #include "mozilla/dom/BlobBinding.h"
 #include "mozilla/dom/BrowserElementDictionariesBinding.h"
 #include "mozilla/dom/Console.h"
+#include "mozilla/dom/Fetch.h"
 #include "mozilla/dom/FunctionBinding.h"
 #include "mozilla/dom/HashChangeEvent.h"
 #include "mozilla/dom/MozSelfSupportBinding.h"
 #include "mozilla/dom/PopStateEvent.h"
 #include "mozilla/dom/PopupBlockedEvent.h"
 #include "mozilla/dom/WindowBinding.h"
 #include "nsITabChild.h"
 #include "mozilla/dom/MediaQueryList.h"
@@ -6363,18 +6364,17 @@ nsGlobalWindow::Confirm(const nsAString&
 
   return rv.ErrorCode();
 }
 
 already_AddRefed<Promise>
 nsGlobalWindow::Fetch(const RequestOrScalarValueString& aInput,
                       const RequestInit& aInit, ErrorResult& aRv)
 {
-  aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);
-  return nullptr;
+  return FetchRequest(this, aInput, aInit, aRv);
 }
 
 void
 nsGlobalWindow::Prompt(const nsAString& aMessage, const nsAString& aInitial,
                        nsAString& aReturn, ErrorResult& aError)
 {
   // XXX This method is very similar to nsGlobalWindow::AlertOrConfirm, make
   // sure any modifications here don't need to happen over there!
--- a/dom/fetch/Fetch.cpp
+++ b/dom/fetch/Fetch.cpp
@@ -1,36 +1,283 @@
 /* -*- 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 "Fetch.h"
 
+#include "nsIDocument.h"
+#include "nsIGlobalObject.h"
 #include "nsIStringStream.h"
 #include "nsIUnicodeDecoder.h"
 #include "nsIUnicodeEncoder.h"
 
 #include "nsDOMString.h"
 #include "nsNetUtil.h"
 #include "nsStreamUtils.h"
 #include "nsStringStream.h"
 
 #include "mozilla/ErrorResult.h"
 #include "mozilla/dom/EncodingUtils.h"
+#include "mozilla/dom/FetchDriver.h"
 #include "mozilla/dom/File.h"
 #include "mozilla/dom/Headers.h"
 #include "mozilla/dom/Promise.h"
+#include "mozilla/dom/PromiseWorkerProxy.h"
 #include "mozilla/dom/Request.h"
 #include "mozilla/dom/Response.h"
+#include "mozilla/dom/ScriptSettings.h"
 #include "mozilla/dom/URLSearchParams.h"
+#include "mozilla/dom/WorkerScope.h"
+#include "mozilla/dom/workers/Workers.h"
+
+#include "InternalResponse.h"
+#include "WorkerPrivate.h"
+#include "WorkerRunnable.h"
 
 namespace mozilla {
 namespace dom {
 
+using namespace workers;
+
+class WorkerFetchResolver MOZ_FINAL : public FetchDriverObserver
+{
+  friend class WorkerFetchResponseRunnable;
+  friend class ResolveFetchWithBodyRunnable;
+
+  // This promise proxy is for the Promise returned by a call to fetch() that
+  // is resolved with a Response instance.
+  nsRefPtr<PromiseWorkerProxy> mPromiseProxy;
+  // Passed from main thread to worker thread after being initialized (except
+  // for the body.
+  nsRefPtr<InternalResponse> mInternalResponse;
+public:
+
+  WorkerFetchResolver(workers::WorkerPrivate* aWorkerPrivate, Promise* aPromise);
+
+  void
+  OnResponseAvailable(InternalResponse* aResponse) MOZ_OVERRIDE;
+
+  workers::WorkerPrivate*
+  GetWorkerPrivate() { return mPromiseProxy->GetWorkerPrivate(); }
+
+private:
+  ~WorkerFetchResolver();
+};
+
+class MainThreadFetchResolver MOZ_FINAL : public FetchDriverObserver
+{
+  nsRefPtr<Promise> mPromise;
+  nsRefPtr<InternalResponse> mInternalResponse;
+
+  NS_DECL_OWNINGTHREAD
+public:
+  MainThreadFetchResolver(Promise* aPromise);
+
+  void
+  OnResponseAvailable(InternalResponse* aResponse) MOZ_OVERRIDE;
+
+private:
+  ~MainThreadFetchResolver();
+};
+
+class MainThreadFetchRunnable : public nsRunnable
+{
+  nsRefPtr<WorkerFetchResolver> mResolver;
+  nsRefPtr<InternalRequest> mRequest;
+
+public:
+  MainThreadFetchRunnable(WorkerPrivate* aWorkerPrivate,
+                          Promise* aPromise,
+                          InternalRequest* aRequest)
+    : mResolver(new WorkerFetchResolver(aWorkerPrivate, aPromise))
+    , mRequest(aRequest)
+  {
+    MOZ_ASSERT(aWorkerPrivate);
+    aWorkerPrivate->AssertIsOnWorkerThread();
+  }
+
+  NS_IMETHODIMP
+  Run()
+  {
+    AssertIsOnMainThread();
+    nsRefPtr<FetchDriver> fetch = new FetchDriver(mRequest);
+    nsresult rv = fetch->Fetch(mResolver);
+    // Right now we only support async fetch, which should never directly fail.
+    MOZ_ASSERT(NS_SUCCEEDED(rv));
+    return NS_OK;
+  }
+};
+
+already_AddRefed<Promise>
+FetchRequest(nsIGlobalObject* aGlobal, const RequestOrScalarValueString& aInput,
+             const RequestInit& aInit, ErrorResult& aRv)
+{
+  nsRefPtr<Promise> p = Promise::Create(aGlobal, aRv);
+  if (NS_WARN_IF(aRv.Failed())) {
+    return nullptr;
+  }
+
+  AutoJSAPI jsapi;
+  jsapi.Init(aGlobal);
+  JSContext* cx = jsapi.cx();
+
+  JS::Rooted<JSObject*> jsGlobal(cx, aGlobal->GetGlobalJSObject());
+  GlobalObject global(cx, jsGlobal);
+
+  nsRefPtr<Request> request = Request::Constructor(global, aInput, aInit, aRv);
+  if (NS_WARN_IF(aRv.Failed())) {
+    return nullptr;
+  }
+
+  nsRefPtr<InternalRequest> r = request->GetInternalRequest();
+  if (!r->ReferrerIsNone()) {
+    nsAutoCString ref;
+    aRv = GetRequestReferrer(aGlobal, r, ref);
+    if (NS_WARN_IF(aRv.Failed())) {
+      return nullptr;
+    }
+    r->SetReferrer(ref);
+  }
+
+  if (NS_IsMainThread()) {
+    nsRefPtr<MainThreadFetchResolver> resolver = new MainThreadFetchResolver(p);
+    nsRefPtr<FetchDriver> fetch = new FetchDriver(r);
+    aRv = fetch->Fetch(resolver);
+    if (NS_WARN_IF(aRv.Failed())) {
+      return nullptr;
+    }
+  } else {
+    WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
+    MOZ_ASSERT(worker);
+    nsRefPtr<MainThreadFetchRunnable> run = new MainThreadFetchRunnable(worker, p, r);
+    if (NS_FAILED(NS_DispatchToMainThread(run))) {
+      NS_WARNING("MainThreadFetchRunnable dispatch failed!");
+    }
+  }
+
+  return p.forget();
+}
+
+MainThreadFetchResolver::MainThreadFetchResolver(Promise* aPromise)
+  : mPromise(aPromise)
+{
+}
+
+void
+MainThreadFetchResolver::OnResponseAvailable(InternalResponse* aResponse)
+{
+  NS_ASSERT_OWNINGTHREAD(MainThreadFetchResolver)
+  AssertIsOnMainThread();
+  mInternalResponse = aResponse;
+
+  nsCOMPtr<nsIGlobalObject> go = mPromise->GetParentObject();
+
+  nsRefPtr<Response> response = new Response(go, aResponse);
+  mPromise->MaybeResolve(response);
+}
+
+MainThreadFetchResolver::~MainThreadFetchResolver()
+{
+  NS_ASSERT_OWNINGTHREAD(MainThreadFetchResolver)
+}
+
+class WorkerFetchResponseRunnable : public WorkerRunnable
+{
+  nsRefPtr<WorkerFetchResolver> mResolver;
+public:
+  WorkerFetchResponseRunnable(WorkerFetchResolver* aResolver)
+    : WorkerRunnable(aResolver->GetWorkerPrivate(), WorkerThreadModifyBusyCount)
+    , mResolver(aResolver)
+  {
+  }
+
+  bool
+  WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) MOZ_OVERRIDE
+  {
+    MOZ_ASSERT(aWorkerPrivate);
+    aWorkerPrivate->AssertIsOnWorkerThread();
+    MOZ_ASSERT(aWorkerPrivate == mResolver->GetWorkerPrivate());
+
+    nsRefPtr<nsIGlobalObject> global = aWorkerPrivate->GlobalScope();
+    nsRefPtr<Response> response = new Response(global, mResolver->mInternalResponse);
+
+    nsRefPtr<Promise> promise = mResolver->mPromiseProxy->GetWorkerPromise();
+    MOZ_ASSERT(promise);
+    promise->MaybeResolve(response);
+
+    mResolver->mPromiseProxy->CleanUp(aCx);
+    return true;
+  }
+};
+
+WorkerFetchResolver::WorkerFetchResolver(WorkerPrivate* aWorkerPrivate, Promise* aPromise)
+  : mPromiseProxy(new PromiseWorkerProxy(aWorkerPrivate, aPromise))
+{
+}
+
+WorkerFetchResolver::~WorkerFetchResolver()
+{
+}
+
+void
+WorkerFetchResolver::OnResponseAvailable(InternalResponse* aResponse)
+{
+  AssertIsOnMainThread();
+  mInternalResponse = aResponse;
+
+  nsRefPtr<WorkerFetchResponseRunnable> r =
+    new WorkerFetchResponseRunnable(this);
+
+  AutoSafeJSContext cx;
+  if (!r->Dispatch(cx)) {
+    NS_WARNING("Could not dispatch fetch resolve");
+  }
+}
+
+// Empty string for no-referrer. FIXME(nsm): Does returning empty string
+// actually lead to no-referrer in the base channel?
+// The actual referrer policy and stripping is dealt with by HttpBaseChannel,
+// this always returns the full API referrer URL of the relevant global.
+nsresult
+GetRequestReferrer(nsIGlobalObject* aGlobal, const InternalRequest* aRequest, nsCString& aReferrer)
+{
+  if (aRequest->ReferrerIsURL()) {
+    aReferrer = aRequest->ReferrerAsURL();
+    return NS_OK;
+  }
+
+  nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aGlobal);
+  if (window) {
+    nsCOMPtr<nsIDocument> doc = window->GetExtantDoc();
+    if (doc) {
+      nsCOMPtr<nsIURI> docURI = doc->GetDocumentURI();
+      nsAutoCString origin;
+      nsresult rv = nsContentUtils::GetASCIIOrigin(docURI, origin);
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+      }
+
+      nsAutoString referrer;
+      doc->GetReferrer(referrer);
+      aReferrer = NS_ConvertUTF16toUTF8(referrer);
+    }
+  } else {
+    WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
+    MOZ_ASSERT(worker);
+    worker->AssertIsOnWorkerThread();
+    aReferrer = worker->GetLocationInfo().mHref;
+    // XXX(nsm): Algorithm says "If source is not a URL..." but when is it
+    // not a URL?
+  }
+
+  return NS_OK;
+}
+
 namespace {
 nsresult
 ExtractFromArrayBuffer(const ArrayBuffer& aBuffer,
                        nsIInputStream** aStream)
 {
   aBuffer.ComputeLengthAndData();
   //XXXnsm reinterpret_cast<> is used in DOMParser, should be ok.
   return NS_NewByteInputStream(aStream,
--- a/dom/fetch/Fetch.h
+++ b/dom/fetch/Fetch.h
@@ -5,25 +5,40 @@
 
 #ifndef mozilla_dom_Fetch_h
 #define mozilla_dom_Fetch_h
 
 #include "nsCOMPtr.h"
 #include "nsError.h"
 #include "nsString.h"
 #include "mozilla/ErrorResult.h"
+#include "mozilla/dom/RequestBinding.h"
 
 class nsIInputStream;
+class nsIGlobalObject;
 
 namespace mozilla {
 namespace dom {
 
 class ArrayBufferOrArrayBufferViewOrBlobOrScalarValueStringOrURLSearchParams;
+class InternalRequest;
 class OwningArrayBufferOrArrayBufferViewOrBlobOrScalarValueStringOrURLSearchParams;
 class Promise;
+class RequestOrScalarValueString;
+
+namespace workers {
+class WorkerPrivate;
+} // namespace workers
+
+already_AddRefed<Promise>
+FetchRequest(nsIGlobalObject* aGlobal, const RequestOrScalarValueString& aInput,
+             const RequestInit& aInit, ErrorResult& aRv);
+
+nsresult
+GetRequestReferrer(nsIGlobalObject* aGlobal, const InternalRequest* aRequest, nsCString& aReferrer);
 
 /*
  * Creates an nsIInputStream based on the fetch specifications 'extract a byte
  * stream algorithm' - http://fetch.spec.whatwg.org/#concept-bodyinit-extract.
  * Stores content type in out param aContentType.
  */
 nsresult
 ExtractByteStreamFromBody(const OwningArrayBufferOrArrayBufferViewOrBlobOrScalarValueStringOrURLSearchParams& aBodyInit,
@@ -100,12 +115,13 @@ private:
   }
 
   already_AddRefed<Promise>
   ConsumeBody(ConsumeType aType, ErrorResult& aRv);
 
   bool mBodyUsed;
   nsCString mMimeType;
 };
+
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_Fetch_h
new file mode 100644
--- /dev/null
+++ b/dom/fetch/FetchDriver.cpp
@@ -0,0 +1,304 @@
+/* -*- 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 "mozilla/dom/FetchDriver.h"
+
+#include "nsIScriptSecurityManager.h"
+
+#include "nsContentPolicyUtils.h"
+#include "nsDataHandler.h"
+#include "nsHostObjectProtocolHandler.h"
+#include "nsNetUtil.h"
+#include "nsStringStream.h"
+
+#include "mozilla/dom/File.h"
+#include "mozilla/dom/workers/Workers.h"
+
+#include "Fetch.h"
+#include "InternalRequest.h"
+#include "InternalResponse.h"
+
+namespace mozilla {
+namespace dom {
+
+FetchDriver::FetchDriver(InternalRequest* aRequest)
+  : mRequest(aRequest)
+  , mFetchRecursionCount(0)
+{
+}
+
+FetchDriver::~FetchDriver()
+{
+}
+
+nsresult
+FetchDriver::Fetch(FetchDriverObserver* aObserver)
+{
+  workers::AssertIsOnMainThread();
+  mObserver = aObserver;
+
+  return Fetch(false /* CORS flag */);
+}
+
+nsresult
+FetchDriver::Fetch(bool aCORSFlag)
+{
+  // We do not currently implement parts of the spec that lead to recursion.
+  MOZ_ASSERT(mFetchRecursionCount == 0);
+  mFetchRecursionCount++;
+
+  // FIXME(nsm): Deal with HSTS.
+
+  if (!mRequest->IsSynchronous() && mFetchRecursionCount <= 1) {
+    nsCOMPtr<nsIRunnable> r =
+      NS_NewRunnableMethodWithArg<bool>(this, &FetchDriver::ContinueFetch, aCORSFlag);
+    return NS_DispatchToCurrentThread(r);
+  }
+
+  MOZ_CRASH("Synchronous fetch not supported");
+}
+
+nsresult
+FetchDriver::ContinueFetch(bool aCORSFlag)
+{
+  workers::AssertIsOnMainThread();
+
+  nsAutoCString url;
+  mRequest->GetURL(url);
+  nsCOMPtr<nsIURI> requestURI;
+  // FIXME(nsm): Deal with relative URLs.
+  nsresult rv = NS_NewURI(getter_AddRefs(requestURI), url,
+                          nullptr, nullptr);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return FailWithNetworkError();
+  }
+
+  // FIXME(nsm): Bug 1039846: Add CSP checks
+
+  nsAutoCString scheme;
+  rv = requestURI->GetScheme(scheme);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return FailWithNetworkError();
+  }
+
+  nsAutoCString originURL;
+  mRequest->GetOrigin(originURL);
+  nsCOMPtr<nsIURI> originURI;
+  rv = NS_NewURI(getter_AddRefs(originURI), originURL, nullptr, nullptr);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return FailWithNetworkError();
+  }
+
+  nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
+  rv = ssm->CheckSameOriginURI(requestURI, originURI, false);
+  if ((!aCORSFlag && NS_SUCCEEDED(rv)) ||
+      (scheme.EqualsLiteral("data") && mRequest->SameOriginDataURL()) ||
+      scheme.EqualsLiteral("about")) {
+    return BasicFetch();
+  }
+
+  if (mRequest->Mode() == RequestMode::Same_origin) {
+    return FailWithNetworkError();
+  }
+
+  if (mRequest->Mode() == RequestMode::No_cors) {
+    mRequest->SetResponseTainting(InternalRequest::RESPONSETAINT_OPAQUE);
+    return BasicFetch();
+  }
+
+  if (!scheme.EqualsLiteral("http") && !scheme.EqualsLiteral("https")) {
+    return FailWithNetworkError();
+  }
+
+  bool corsPreflight = false;
+
+  if (mRequest->Mode() == RequestMode::Cors_with_forced_preflight ||
+      (mRequest->UnsafeRequest() && (mRequest->HasSimpleMethod() || !mRequest->Headers()->HasOnlySimpleHeaders()))) {
+    corsPreflight = true;
+  }
+
+  mRequest->SetResponseTainting(InternalRequest::RESPONSETAINT_CORS);
+  // FIXME(nsm): HttpFetch.
+  return FailWithNetworkError();
+}
+
+nsresult
+FetchDriver::BasicFetch()
+{
+  nsAutoCString url;
+  mRequest->GetURL(url);
+  nsCOMPtr<nsIURI> uri;
+  nsresult rv = NS_NewURI(getter_AddRefs(uri),
+                 url,
+                 nullptr,
+                 nullptr);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsCString scheme;
+  rv = uri->GetScheme(scheme);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  if (scheme.LowerCaseEqualsLiteral("about")) {
+    if (url.EqualsLiteral("about:blank")) {
+      nsRefPtr<InternalResponse> response =
+        new InternalResponse(200, NS_LITERAL_CSTRING("OK"));
+      ErrorResult result;
+      response->Headers()->Append(NS_LITERAL_CSTRING("content-type"),
+                                  NS_LITERAL_CSTRING("text/html;charset=utf-8"),
+                                  result);
+      MOZ_ASSERT(!result.Failed());
+      nsCOMPtr<nsIInputStream> body;
+      rv = NS_NewCStringInputStream(getter_AddRefs(body), EmptyCString());
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return FailWithNetworkError();
+      }
+
+      response->SetBody(body);
+      BeginResponse(response);
+      return SucceedWithResponse();
+    }
+    return FailWithNetworkError();
+  }
+
+  if (scheme.LowerCaseEqualsLiteral("blob")) {
+    nsRefPtr<FileImpl> blobImpl;
+    rv = NS_GetBlobForBlobURI(uri, getter_AddRefs(blobImpl));
+    FileImpl* blob = static_cast<FileImpl*>(blobImpl.get());
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return FailWithNetworkError();
+    }
+
+    nsRefPtr<InternalResponse> response = new InternalResponse(200, NS_LITERAL_CSTRING("OK"));
+    {
+      ErrorResult result;
+      uint64_t size = blob->GetSize(result);
+      if (NS_WARN_IF(result.Failed())) {
+        return FailWithNetworkError();
+      }
+
+      nsAutoString sizeStr;
+      sizeStr.AppendInt(size);
+      response->Headers()->Append(NS_LITERAL_CSTRING("Content-Length"), NS_ConvertUTF16toUTF8(sizeStr), result);
+      if (NS_WARN_IF(result.Failed())) {
+        return FailWithNetworkError();
+      }
+
+      nsAutoString type;
+      blob->GetType(type);
+      response->Headers()->Append(NS_LITERAL_CSTRING("Content-Type"), NS_ConvertUTF16toUTF8(type), result);
+      if (NS_WARN_IF(result.Failed())) {
+        return FailWithNetworkError();
+      }
+    }
+
+    nsCOMPtr<nsIInputStream> stream;
+    rv = blob->GetInternalStream(getter_AddRefs(stream));
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return FailWithNetworkError();
+    }
+
+    response->SetBody(stream);
+    BeginResponse(response);
+    return SucceedWithResponse();
+  }
+
+  if (scheme.LowerCaseEqualsLiteral("data")) {
+    nsAutoCString method;
+    mRequest->GetMethod(method);
+    if (method.LowerCaseEqualsASCII("get")) {
+      // Use nsDataHandler directly so that we can extract the content type.
+      // XXX(nsm): Is there a way to acquire the charset without such tight
+      // coupling with the DataHandler? nsIProtocolHandler does not provide
+      // anything similar.
+      nsAutoCString contentType, contentCharset, dataBuffer, hashRef;
+      bool isBase64;
+      rv = nsDataHandler::ParseURI(url,
+                                   contentType,
+                                   contentCharset,
+                                   isBase64,
+                                   dataBuffer,
+                                   hashRef);
+      if (NS_SUCCEEDED(rv)) {
+        ErrorResult result;
+        nsRefPtr<InternalResponse> response = new InternalResponse(200, NS_LITERAL_CSTRING("OK"));
+        if (!contentCharset.IsEmpty()) {
+          contentType.Append(";charset=");
+          contentType.Append(contentCharset);
+        }
+
+        response->Headers()->Append(NS_LITERAL_CSTRING("Content-Type"), contentType, result);
+        if (!result.Failed()) {
+          nsCOMPtr<nsIInputStream> stream;
+          rv = NS_NewCStringInputStream(getter_AddRefs(stream), dataBuffer);
+          if (NS_SUCCEEDED(rv)) {
+            response->SetBody(stream);
+            BeginResponse(response);
+            return SucceedWithResponse();
+          }
+        }
+      }
+    }
+
+    NS_WARN_IF(NS_FAILED(rv));
+    return FailWithNetworkError();
+  }
+
+  if (scheme.LowerCaseEqualsLiteral("file")) {
+  } else if (scheme.LowerCaseEqualsLiteral("http") ||
+             scheme.LowerCaseEqualsLiteral("https")) {
+    // FIXME(nsm): HttpFetch.
+    return FailWithNetworkError();
+  }
+
+  return FailWithNetworkError();
+}
+
+nsresult
+FetchDriver::BeginResponse(InternalResponse* aResponse)
+{
+  MOZ_ASSERT(aResponse);
+  nsAutoCString reqURL;
+  mRequest->GetURL(reqURL);
+  aResponse->SetUrl(reqURL);
+
+  // FIXME(nsm): Handle mixed content check, step 7 of fetch.
+
+  nsRefPtr<InternalResponse> filteredResponse;
+  switch (mRequest->GetResponseTainting()) {
+    case InternalRequest::RESPONSETAINT_BASIC:
+      filteredResponse = InternalResponse::BasicResponse(aResponse);
+      break;
+    case InternalRequest::RESPONSETAINT_CORS:
+      filteredResponse = InternalResponse::CORSResponse(aResponse);
+      break;
+    case InternalRequest::RESPONSETAINT_OPAQUE:
+      filteredResponse = InternalResponse::OpaqueResponse();
+      break;
+    default:
+      MOZ_CRASH("Unexpected case");
+  }
+
+  MOZ_ASSERT(filteredResponse);
+  mObserver->OnResponseAvailable(filteredResponse);
+  return NS_OK;
+}
+
+nsresult
+FetchDriver::SucceedWithResponse()
+{
+  return NS_OK;
+}
+
+nsresult
+FetchDriver::FailWithNetworkError()
+{
+  nsRefPtr<InternalResponse> error = InternalResponse::NetworkError();
+  mObserver->OnResponseAvailable(error);
+  // FIXME(nsm): Some sort of shutdown?
+  return NS_OK;
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/fetch/FetchDriver.h
@@ -0,0 +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/. */
+
+#ifndef mozilla_dom_FetchDriver_h
+#define mozilla_dom_FetchDriver_h
+
+#include "nsIStreamListener.h"
+#include "nsRefPtr.h"
+
+class nsPIDOMWindow;
+
+namespace mozilla {
+namespace dom {
+
+class InternalRequest;
+class InternalResponse;
+
+class FetchDriverObserver
+{
+public:
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(FetchDriverObserver);
+  virtual void OnResponseAvailable(InternalResponse* aResponse) = 0;
+
+protected:
+  virtual ~FetchDriverObserver()
+  { };
+};
+
+class FetchDriver MOZ_FINAL
+{
+  NS_INLINE_DECL_REFCOUNTING(FetchDriver)
+public:
+  explicit FetchDriver(InternalRequest* aRequest);
+  NS_IMETHOD Fetch(FetchDriverObserver* aObserver);
+
+private:
+  nsRefPtr<InternalRequest> mRequest;
+  nsRefPtr<FetchDriverObserver> mObserver;
+  uint32_t mFetchRecursionCount;
+
+  FetchDriver() MOZ_DELETE;
+  FetchDriver(const FetchDriver&) MOZ_DELETE;
+  FetchDriver& operator=(const FetchDriver&) MOZ_DELETE;
+  ~FetchDriver();
+
+  nsresult Fetch(bool aCORSFlag);
+  nsresult ContinueFetch(bool aCORSFlag);
+  nsresult BasicFetch();
+  nsresult FailWithNetworkError();
+  nsresult BeginResponse(InternalResponse* aResponse);
+  nsresult SucceedWithResponse();
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_FetchDriver_h
--- a/dom/fetch/InternalHeaders.cpp
+++ b/dom/fetch/InternalHeaders.cpp
@@ -263,10 +263,66 @@ void
 InternalHeaders::Fill(const MozMap<nsCString>& aInit, ErrorResult& aRv)
 {
   nsTArray<nsString> keys;
   aInit.GetKeys(keys);
   for (uint32_t i = 0; i < keys.Length() && !aRv.Failed(); ++i) {
     Append(NS_ConvertUTF16toUTF8(keys[i]), aInit.Get(keys[i]), aRv);
   }
 }
+
+bool
+InternalHeaders::HasOnlySimpleHeaders() const
+{
+  for (uint32_t i = 0; i < mList.Length(); ++i) {
+    if (!IsSimpleHeader(mList[i].mName, mList[i].mValue)) {
+      return false;
+    }
+  }
+
+  return true;
+}
+
+// static
+already_AddRefed<InternalHeaders>
+InternalHeaders::BasicHeaders(InternalHeaders* aHeaders)
+{
+  nsRefPtr<InternalHeaders> basic = new InternalHeaders(*aHeaders);
+  ErrorResult result;
+  // The Set-Cookie headers cannot be invalid mutable headers, so the Delete
+  // must succeed.
+  basic->Delete(NS_LITERAL_CSTRING("Set-Cookie"), result);
+  MOZ_ASSERT(!result.Failed());
+  basic->Delete(NS_LITERAL_CSTRING("Set-Cookie2"), result);
+  MOZ_ASSERT(!result.Failed());
+  return basic.forget();
+}
+
+// static
+already_AddRefed<InternalHeaders>
+InternalHeaders::CORSHeaders(InternalHeaders* aHeaders)
+{
+  nsRefPtr<InternalHeaders> cors = new InternalHeaders(aHeaders->mGuard);
+  ErrorResult result;
+
+  nsAutoTArray<nsCString, 1> acExposedNames;
+  aHeaders->GetAll(NS_LITERAL_CSTRING("Access-Control-Expose-Headers"), acExposedNames, result);
+  MOZ_ASSERT(!result.Failed());
+
+  nsCaseInsensitiveCStringArrayComparator comp;
+  for (uint32_t i = 0; i < aHeaders->mList.Length(); ++i) {
+    const Entry& entry = aHeaders->mList[i];
+    if (entry.mName.EqualsASCII("cache-control") ||
+        entry.mName.EqualsASCII("content-language") ||
+        entry.mName.EqualsASCII("content-type") ||
+        entry.mName.EqualsASCII("expires") ||
+        entry.mName.EqualsASCII("last-modified") ||
+        entry.mName.EqualsASCII("pragma") ||
+        acExposedNames.Contains(entry.mName, comp)) {
+      cors->Append(entry.mName, entry.mValue, result);
+      MOZ_ASSERT(!result.Failed());
+    }
+  }
+
+  return cors.forget();
+}
 } // namespace dom
 } // namespace mozilla
--- a/dom/fetch/InternalHeaders.h
+++ b/dom/fetch/InternalHeaders.h
@@ -72,16 +72,24 @@ public:
   void Clear();
 
   HeadersGuardEnum Guard() const { return mGuard; }
   void SetGuard(HeadersGuardEnum aGuard, ErrorResult& aRv);
 
   void Fill(const InternalHeaders& aInit, ErrorResult& aRv);
   void Fill(const Sequence<Sequence<nsCString>>& aInit, ErrorResult& aRv);
   void Fill(const MozMap<nsCString>& aInit, ErrorResult& aRv);
+
+  bool HasOnlySimpleHeaders() const;
+
+  static already_AddRefed<InternalHeaders>
+  BasicHeaders(InternalHeaders* aHeaders);
+
+  static already_AddRefed<InternalHeaders>
+  CORSHeaders(InternalHeaders* aHeaders);
 private:
   virtual ~InternalHeaders();
 
   static bool IsSimpleHeader(const nsACString& aName,
                              const nsACString& aValue);
   static bool IsInvalidName(const nsACString& aName, ErrorResult& aRv);
   static bool IsInvalidValue(const nsACString& aValue, ErrorResult& aRv);
   bool IsImmutable(ErrorResult& aRv) const;
--- a/dom/fetch/InternalRequest.h
+++ b/dom/fetch/InternalRequest.h
@@ -61,17 +61,21 @@ public:
     , mMode(RequestMode::No_cors)
     , mCredentialsMode(RequestCredentials::Omit)
     , mResponseTainting(RESPONSETAINT_BASIC)
     , mRedirectCount(0)
     , mAuthenticationFlag(false)
     , mForceOriginHeader(false)
     , mManualRedirect(false)
     , mPreserveContentCodings(false)
-    , mSameOriginDataURL(false)
+      // FIXME(nsm): This should be false by default, but will lead to the
+      // algorithm never loading data: URLs right now. See Bug 1018872 about
+      // how certain contexts will override it to set it to true. Fetch
+      // specification does not handle this yet.
+    , mSameOriginDataURL(true)
     , mSkipServiceWorker(false)
     , mSynchronous(false)
     , mUnsafeRequest(false)
     , mUseURLCredentials(false)
   {
   }
 
   explicit InternalRequest(const InternalRequest& aOther)
@@ -108,16 +112,24 @@ public:
   }
 
   void
   SetMethod(const nsACString& aMethod)
   {
     mMethod.Assign(aMethod);
   }
 
+  bool
+  HasSimpleMethod() const
+  {
+    return mMethod.LowerCaseEqualsASCII("get") ||
+           mMethod.LowerCaseEqualsASCII("post") ||
+           mMethod.LowerCaseEqualsASCII("head");
+  }
+
   void
   GetURL(nsCString& aURL) const
   {
     aURL.Assign(mURL);
   }
 
   bool
   ReferrerIsNone() const
@@ -172,22 +184,40 @@ public:
   }
 
   void
   SetCredentialsMode(RequestCredentials aCredentialsMode)
   {
     mCredentialsMode = aCredentialsMode;
   }
 
+  ResponseTainting
+  GetResponseTainting() const
+  {
+    return mResponseTainting;
+  }
+
+  void
+  SetResponseTainting(ResponseTainting aTainting)
+  {
+    mResponseTainting = aTainting;
+  }
+
   nsContentPolicyType
   GetContext() const
   {
     return mContext;
   }
 
+  bool
+  UnsafeRequest() const
+  {
+    return mUnsafeRequest;
+  }
+
   InternalHeaders*
   Headers()
   {
     return mHeaders;
   }
 
   bool
   ForceOriginHeader()
@@ -196,16 +226,22 @@ public:
   }
 
   void
   GetOrigin(nsCString& aOrigin) const
   {
     aOrigin.Assign(mOrigin);
   }
 
+  bool
+  SameOriginDataURL() const
+  {
+    return mSameOriginDataURL;
+  }
+
   void
   SetBody(nsIInputStream* aStream)
   {
     mBodyStream = aStream;
   }
 
   // Will return the original stream!
   // Use a tee or copy if you don't want to erase the original.
--- a/dom/fetch/InternalResponse.cpp
+++ b/dom/fetch/InternalResponse.cpp
@@ -15,10 +15,45 @@ namespace dom {
 InternalResponse::InternalResponse(uint16_t aStatus, const nsACString& aStatusText)
   : mType(ResponseType::Default)
   , mStatus(aStatus)
   , mStatusText(aStatusText)
   , mHeaders(new InternalHeaders(HeadersGuardEnum::Response))
 {
 }
 
+// Headers are not copied since BasicResponse and CORSResponse both need custom
+// header handling.
+InternalResponse::InternalResponse(const InternalResponse& aOther)
+  : mType(aOther.mType)
+  , mTerminationReason(aOther.mTerminationReason)
+  , mURL(aOther.mURL)
+  , mStatus(aOther.mStatus)
+  , mStatusText(aOther.mStatusText)
+  , mBody(aOther.mBody)
+  , mContentType(aOther.mContentType)
+{
+}
+
+// static
+already_AddRefed<InternalResponse>
+InternalResponse::BasicResponse(InternalResponse* aInner)
+{
+  MOZ_ASSERT(aInner);
+  nsRefPtr<InternalResponse> basic = new InternalResponse(*aInner);
+  basic->mType = ResponseType::Basic;
+  basic->mHeaders = InternalHeaders::BasicHeaders(aInner->mHeaders);
+  return basic.forget();
+}
+
+// static
+already_AddRefed<InternalResponse>
+InternalResponse::CORSResponse(InternalResponse* aInner)
+{
+  MOZ_ASSERT(aInner);
+  nsRefPtr<InternalResponse> cors = new InternalResponse(*aInner);
+  cors->mType = ResponseType::Cors;
+  cors->mHeaders = InternalHeaders::CORSHeaders(aInner->mHeaders);
+  return cors.forget();
+}
+
 } // namespace dom
 } // namespace mozilla
--- a/dom/fetch/InternalResponse.h
+++ b/dom/fetch/InternalResponse.h
@@ -20,43 +20,65 @@ class InternalResponse MOZ_FINAL
 {
   friend class FetchDriver;
 
 public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(InternalResponse)
 
   InternalResponse(uint16_t aStatus, const nsACString& aStatusText);
 
-  explicit InternalResponse(const InternalResponse& aOther) MOZ_DELETE;
-
   static already_AddRefed<InternalResponse>
   NetworkError()
   {
     nsRefPtr<InternalResponse> response = new InternalResponse(0, EmptyCString());
     response->mType = ResponseType::Error;
     return response.forget();
   }
 
+  static already_AddRefed<InternalResponse>
+  OpaqueResponse()
+  {
+    nsRefPtr<InternalResponse> response = new InternalResponse(0, EmptyCString());
+    response->mType = ResponseType::Opaque;
+    return response.forget();
+  }
+
+  // DO NOT use the inner response after filtering it since the filtered
+  // response will adopt the inner response's body.
+  static already_AddRefed<InternalResponse>
+  BasicResponse(InternalResponse* aInner);
+
+  // DO NOT use the inner response after filtering it since the filtered
+  // response will adopt the inner response's body.
+  static already_AddRefed<InternalResponse>
+  CORSResponse(InternalResponse* aInner);
+
   ResponseType
   Type() const
   {
     return mType;
   }
 
   bool
   IsError() const
   {
     return Type() == ResponseType::Error;
   }
 
   // FIXME(nsm): Return with exclude fragment.
-  nsCString&
-  GetUrl()
+  void
+  GetUrl(nsCString& aURL) const
   {
-    return mURL;
+    aURL.Assign(mURL);
+  }
+
+  void
+  SetUrl(const nsACString& aURL)
+  {
+    mURL.Assign(aURL);
   }
 
   uint16_t
   GetStatus() const
   {
     return mStatus;
   }
 
@@ -84,16 +106,20 @@ public:
   {
     mBody = aBody;
   }
 
 private:
   ~InternalResponse()
   { }
 
+  // Used to create filtered responses.
+  // Does not copy headers.
+  explicit InternalResponse(const InternalResponse& aOther);
+
   ResponseType mType;
   nsCString mTerminationReason;
   nsCString mURL;
   const uint16_t mStatus;
   const nsCString mStatusText;
   nsRefPtr<InternalHeaders> mHeaders;
   nsCOMPtr<nsIInputStream> mBody;
   nsCString mContentType;
--- a/dom/fetch/Request.cpp
+++ b/dom/fetch/Request.cpp
@@ -166,22 +166,19 @@ Request::Constructor(const GlobalObject&
     headers = h->GetInternalHeaders();
   } else {
     headers = new InternalHeaders(*requestHeaders);
   }
 
   requestHeaders->Clear();
 
   if (request->Mode() == RequestMode::No_cors) {
-    nsCString method;
-    request->GetMethod(method);
-    ToLowerCase(method);
-    if (!method.EqualsASCII("get") &&
-        !method.EqualsASCII("head") &&
-        !method.EqualsASCII("post")) {
+    if (!request->HasSimpleMethod()) {
+      nsAutoCString method;
+      request->GetMethod(method);
       NS_ConvertUTF8toUTF16 label(method);
       aRv.ThrowTypeError(MSG_INVALID_REQUEST_METHOD, &label);
       return nullptr;
     }
 
     requestHeaders->SetGuard(HeadersGuardEnum::Request_no_cors, aRv);
     if (aRv.Failed()) {
       return nullptr;
--- a/dom/fetch/Response.h
+++ b/dom/fetch/Response.h
@@ -46,17 +46,19 @@ public:
   Type() const
   {
     return mInternalResponse->Type();
   }
 
   void
   GetUrl(DOMString& aUrl) const
   {
-    aUrl.AsAString() = NS_ConvertUTF8toUTF16(mInternalResponse->GetUrl());
+    nsCString url;
+    mInternalResponse->GetUrl(url);
+    aUrl.AsAString() = NS_ConvertUTF8toUTF16(url);
   }
 
   uint16_t
   Status() const
   {
     return mInternalResponse->GetStatus();
   }
 
--- a/dom/fetch/moz.build
+++ b/dom/fetch/moz.build
@@ -1,33 +1,37 @@
 # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # 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/.
 
 EXPORTS.mozilla.dom += [
     'Fetch.h',
+    'FetchDriver.h',
     'Headers.h',
     'InternalHeaders.h',
     'InternalRequest.h',
     'InternalResponse.h',
     'Request.h',
     'Response.h',
 ]
 
 UNIFIED_SOURCES += [
     'Fetch.cpp',
+    'FetchDriver.cpp',
     'Headers.cpp',
     'InternalHeaders.cpp',
     'InternalRequest.cpp',
     'InternalResponse.cpp',
     'Request.cpp',
     'Response.cpp',
 ]
 
 LOCAL_INCLUDES += [
     '../workers',
+    # For nsDataHandler.h
+    '/netwerk/protocol/data',
 ]
 
 FAIL_ON_WARNINGS = True
 MSVC_ENABLE_PGO = True
 FINAL_LIBRARY = 'xul'
--- a/dom/promise/PromiseWorkerProxy.h
+++ b/dom/promise/PromiseWorkerProxy.h
@@ -1,15 +1,17 @@
 /* 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/. */
 
 #ifndef mozilla_dom_PromiseWorkerProxy_h
 #define mozilla_dom_PromiseWorkerProxy_h
 
+// Required for Promise::PromiseTaskSync.
+#include "mozilla/dom/Promise.h"
 #include "mozilla/dom/PromiseNativeHandler.h"
 #include "mozilla/dom/workers/bindings/WorkerFeature.h"
 #include "nsProxyRelease.h"
 
 namespace mozilla {
 namespace dom {
 
 class Promise;
@@ -38,16 +40,26 @@ class WorkerPrivate;
 //
 //   3. In your WorkerMainThreadRunnable::MainThreadRun(), obtain a Promise on
 //      the main thread and call its AppendNativeHandler(PromiseNativeHandler*)
 //      to bind the PromiseWorkerProxy created at #2.
 //
 //   4. Then the Promise results returned by ResolvedCallback/RejectedCallback
 //      will be dispatched as a WorkerRunnable to the worker thread to
 //      resolve/reject the Promise created at #1.
+//
+// PromiseWorkerProxy can also be used in situations where there is no main
+// thread Promise, or where special handling is required on the worker thread
+// for promise resolution. Create a PromiseWorkerProxy as in steps 1 and
+// 2 above. When the main thread is ready to resolve the worker thread promise,
+// dispatch a runnable to the worker. Use GetWorkerPrivate() to acquire the
+// worker. This might be null! In the WorkerRunnable's WorkerRun() use
+// GetWorkerPromise() to access the Promise and resolve/reject it. Then call
+// CleanUp() on the worker
+// thread.
 
 class PromiseWorkerProxy : public PromiseNativeHandler,
                            public workers::WorkerFeature
 {
   friend class PromiseWorkerProxyRunnable;
 
   // This overrides the non-threadsafe refcounting in PromiseNativeHandler.
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(PromiseWorkerProxy)
--- a/dom/tests/mochitest/fetch/mochitest.ini
+++ b/dom/tests/mochitest/fetch/mochitest.ini
@@ -1,7 +1,9 @@
 [DEFAULT]
 support-files =
   test_headers_common.js
   test_headers_mainthread.js
+  worker_test_fetch_basic.js
   worker_wrapper.js
 
 [test_headers.html]
+[test_fetch_basic.html]
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/fetch/test_fetch_basic.html
@@ -0,0 +1,57 @@
+<!--
+  Any copyright is dedicated to the Public Domain.
+  http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Bug 1039846 - Test fetch() function in worker</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test"></pre>
+<script type="text/javascript" src="worker_test_fetch_basic.js"> </script>
+<script class="testbody" type="text/javascript">
+SimpleTest.waitForExplicitFinish();
+
+function testOnWorker(done) {
+  ok(true, "=== Start Worker Tests ===");
+  var worker = new Worker("worker_test_fetch_basic.js");
+  worker.onmessage = function(event) {
+    if (event.data.type == "finish") {
+      ok(true, "=== Finish Worker Tests ===");
+      done();
+    } else if (event.data.type == "status") {
+      ok(event.data.status, event.data.msg);
+    }
+  }
+
+  worker.onerror = function(event) {
+    ok(false, "Worker had an error: " + event.data);
+    ok(true, "=== Finish Worker Tests ===");
+    done();
+  };
+
+  worker.postMessage("start");
+}
+
+//
+// Driver
+//
+
+SpecialPowers.pushPrefEnv({"set": [
+  ["dom.fetch.enabled", true]
+]}, function() {
+  testOnWorker(function() {
+    SimpleTest.finish();
+  });
+});
+</script>
+</script>
+</pre>
+</body>
+</html>
+
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/fetch/worker_test_fetch_basic.js
@@ -0,0 +1,94 @@
+if (typeof ok !== "function") {
+  function ok(a, msg) {
+    dump("OK: " + !!a + "  =>  " + a + " " + msg + "\n");
+    postMessage({type: 'status', status: !!a, msg: a + ": " + msg });
+  }
+}
+
+if (typeof is !== "function") {
+  function is(a, b, msg) {
+    dump("IS: " + (a===b) + "  =>  " + a + " | " + b + " " + msg + "\n");
+    postMessage({type: 'status', status: a === b, msg: a + " === " + b + ": " + msg });
+  }
+}
+
+function testAboutURL() {
+  var p1 = fetch('about:blank').then(function(res) {
+    is(res.status, 200, "about:blank should load a valid Response");
+    is(res.headers.get('content-type'), 'text/html;charset=utf-8',
+       "about:blank content-type should be text/html;charset=utf-8");
+    return res.text().then(function(v) {
+      is(v, "", "about:blank body should be empty");
+    });
+  });
+
+  var p2 = fetch('about:config').then(function(res) {
+    is(res.type, "error", "about:config should fail");
+  });
+
+  return Promise.all([p1, p2]);
+}
+
+function testDataURL() {
+  return fetch("data:text/plain;charset=UTF-8,Hello").then(function(res) {
+    ok(true, "Data URL fetch should resolve");
+    if (res.type == "error") {
+      ok(false, "Data URL fetch should not fail.");
+      return Promise.reject();
+    }
+    ok(res instanceof Response, "Fetch should resolve to a Response");
+    is(res.status, 200, "Data URL status should be 200");
+    is(res.statusText, "OK", "Data URL statusText should be OK");
+    ok(res.headers.has("content-type"), "Headers must have Content-Type header");
+    is(res.headers.get("content-type"), "text/plain;charset=UTF-8", "Content-Type header should match specified value");
+    return res.text().then(function(body) {
+      is(body, "Hello", "Data URL Body should match");
+    });
+  });
+}
+
+function testSameOriginBlobURL() {
+  var blob = new Blob(["english ", "sentence"], { type: "text/plain" });
+  var url = URL.createObjectURL(blob);
+  return fetch(url).then(function(res) {
+    ok(true, "Blob URL fetch should resolve");
+    if (res.type == "error") {
+      ok(false, "Blob URL fetch should not fail.");
+      return Promise.reject();
+    }
+    ok(res instanceof Response, "Fetch should resolve to a Response");
+    is(res.status, 200, "Blob fetch status should be 200");
+    is(res.statusText, "OK", "Blob fetch statusText should be OK");
+    ok(res.headers.has("content-type"), "Headers must have Content-Type header");
+    is(res.headers.get("content-type"), blob.type, "Content-Type header should match specified value");
+    ok(res.headers.has("content-length"), "Headers must have Content-Length header");
+    is(parseInt(res.headers.get("content-length")), 16, "Content-Length should match Blob's size");
+    return res.text().then(function(body) {
+      is(body, "english sentence", "Blob fetch body should match");
+    });
+  });
+}
+
+function runTest() {
+  var done = function() {
+    if (typeof SimpleTest === "object") {
+      SimpleTest.finish();
+    } else {
+      postMessage({ type: 'finish' });
+    }
+  }
+
+  Promise.resolve()
+    .then(testAboutURL)
+    .then(testDataURL)
+    .then(testSameOriginBlobURL)
+    //.then(testAboutURL)
+    // Put more promise based tests here.
+    .then(done)
+    .catch(function(e) {
+      ok(false, "Some Response tests failed " + e);
+      done();
+    })
+}
+
+onmessage = runTest;
--- a/dom/webidl/Request.webidl
+++ b/dom/webidl/Request.webidl
@@ -1,9 +1,9 @@
-/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* -*- Mode: IDL; tab-width: 1; 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/.
  *
  * The origin of this IDL file is
  * https://fetch.spec.whatwg.org/#request-class
  */
 
@@ -29,10 +29,10 @@ Request implements Body;
 dictionary RequestInit {
   ByteString method;
   HeadersInit headers;
   BodyInit body;
   RequestMode mode;
   RequestCredentials credentials;
 };
 
-enum RequestMode { "same-origin", "no-cors", "cors" };
+enum RequestMode { "same-origin", "no-cors", "cors", "cors-with-forced-preflight" };
 enum RequestCredentials { "omit", "same-origin", "include" };
--- a/dom/workers/WorkerScope.cpp
+++ b/dom/workers/WorkerScope.cpp
@@ -5,16 +5,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "WorkerScope.h"
 
 #include "jsapi.h"
 #include "mozilla/EventListenerManager.h"
 #include "mozilla/dom/Console.h"
 #include "mozilla/dom/DedicatedWorkerGlobalScopeBinding.h"
+#include "mozilla/dom/Fetch.h"
 #include "mozilla/dom/FunctionBinding.h"
 #include "mozilla/dom/Promise.h"
 #include "mozilla/dom/ServiceWorkerGlobalScopeBinding.h"
 #include "mozilla/dom/SharedWorkerGlobalScopeBinding.h"
 #include "mozilla/Services.h"
 #include "nsServiceManagerUtils.h"
 
 #include "nsIDocument.h"
@@ -300,18 +301,17 @@ WorkerGlobalScope::GetPerformance()
 
   return mPerformance;
 }
 
 already_AddRefed<Promise>
 WorkerGlobalScope::Fetch(const RequestOrScalarValueString& aInput,
                          const RequestInit& aInit, ErrorResult& aRv)
 {
-  aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);
-  return nullptr;
+  return FetchRequest(this, aInput, aInit, aRv);
 }
 
 DedicatedWorkerGlobalScope::DedicatedWorkerGlobalScope(WorkerPrivate* aWorkerPrivate)
 : WorkerGlobalScope(aWorkerPrivate)
 {
 }
 
 JSObject*