diff --git a/DEPS b/DEPS
index 8f5c4ba5..6480161 100644
--- a/DEPS
+++ b/DEPS
@@ -40,7 +40,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling Skia
   # and whatever else without interference from each other.
-  'skia_revision': '4833950b754d076e725d592e53bfd2ed22d6d669',
+  'skia_revision': 'b5d9e85c028f937991efac755a6da02ec1977f80',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling V8
   # and whatever else without interference from each other.
diff --git a/components/exo/buffer.cc b/components/exo/buffer.cc
index d2141c9..94dd9d1 100644
--- a/components/exo/buffer.cc
+++ b/components/exo/buffer.cc
@@ -144,6 +144,7 @@
   void ScheduleWaitForRelease(base::TimeDelta delay);
   void WaitForRelease();
 
+  gfx::GpuMemoryBuffer* const gpu_memory_buffer_;
   ui::ContextFactory* context_factory_;
   scoped_refptr<cc::ContextProvider> context_provider_;
   const unsigned texture_target_;
@@ -163,7 +164,8 @@
 
 Buffer::Texture::Texture(ui::ContextFactory* context_factory,
                          cc::ContextProvider* context_provider)
-    : context_factory_(context_factory),
+    : gpu_memory_buffer_(nullptr),
+      context_factory_(context_factory),
       context_provider_(context_provider),
       texture_target_(GL_TEXTURE_2D),
       query_type_(GL_COMMANDS_COMPLETED_CHROMIUM),
@@ -182,7 +184,8 @@
                          gfx::GpuMemoryBuffer* gpu_memory_buffer,
                          unsigned texture_target,
                          unsigned query_type)
-    : context_factory_(context_factory),
+    : gpu_memory_buffer_(gpu_memory_buffer),
+      context_factory_(context_factory),
       context_provider_(context_provider),
       texture_target_(texture_target),
       query_type_(query_type),
@@ -250,6 +253,8 @@
     uint64_t fence_sync = gles2->InsertFenceSyncCHROMIUM();
     gles2->OrderingBarrierCHROMIUM();
     gles2->GenUnverifiedSyncTokenCHROMIUM(fence_sync, sync_token.GetData());
+    TRACE_EVENT_ASYNC_STEP_INTO0("exo", "BufferInUse", gpu_memory_buffer_,
+                                 "bound");
   }
   return sync_token;
 }
@@ -327,6 +332,8 @@
       base::TimeDelta::FromMilliseconds(kWaitForReleaseDelayMs);
   wait_for_release_time_ = base::TimeTicks::Now() + wait_for_release_delay;
   ScheduleWaitForRelease(wait_for_release_delay);
+  TRACE_EVENT_ASYNC_STEP_INTO0("exo", "BufferInUse", gpu_memory_buffer_,
+                               "pending_query");
   context_provider_->ContextSupport()->SignalQuery(
       query_id_,
       base::Bind(&Buffer::Texture::Released, weak_ptr_factory_.GetWeakPtr()));
@@ -406,6 +413,8 @@
     bool secure_output_only,
     bool client_usage,
     cc::TransferableResource* resource) {
+  TRACE_EVENT0("exo", "Buffer::ProduceTransferableResource");
+
   DCHECK(attach_count_);
   DLOG_IF(WARNING, !release_contents_callback_.IsCancelled() && client_usage)
       << "Producing a texture mailbox for a buffer that has not been released";
@@ -443,6 +452,9 @@
   }
   Texture* contents_texture = contents_texture_.get();
 
+  if (release_contents_callback_.IsCancelled())
+    TRACE_EVENT_ASYNC_BEGIN0("exo", "BufferInUse", gpu_memory_buffer_.get());
+
   // Cancel pending contents release callback.
   release_contents_callback_.Reset(
       base::Bind(&Buffer::ReleaseContents, base::Unretained(this)));
@@ -535,6 +547,8 @@
 // Buffer, private:
 
 void Buffer::Release() {
+  TRACE_EVENT_ASYNC_END0("exo", "BufferInUse", gpu_memory_buffer_.get());
+
   // Run release callback to notify the client that buffer has been released.
   if (!release_callback_.is_null())
     release_callback_.Run();
@@ -556,9 +570,13 @@
   // Cancel callback to indicate that buffer has been released.
   release_contents_callback_.Cancel();
 
-  // Release buffer if not attached to surface.
-  if (!attach_count_)
+  if (attach_count_) {
+    TRACE_EVENT_ASYNC_STEP_INTO0("exo", "BufferInUse", gpu_memory_buffer_.get(),
+                                 "attached");
+  } else {
+    // Release buffer if not attached to surface.
     Release();
+  }
 }
 
 }  // namespace exo
diff --git a/content/browser/BUILD.gn b/content/browser/BUILD.gn
index 6c88e40..fd5bcf6 100644
--- a/content/browser/BUILD.gn
+++ b/content/browser/BUILD.gn
@@ -391,6 +391,8 @@
     "blob_storage/blob_dispatcher_host.h",
     "blob_storage/blob_internals_url_loader.cc",
     "blob_storage/blob_internals_url_loader.h",
+    "blob_storage/blob_url_loader_factory.cc",
+    "blob_storage/blob_url_loader_factory.h",
     "blob_storage/chrome_blob_storage_context.cc",
     "blob_storage/chrome_blob_storage_context.h",
     "bluetooth/bluetooth_allowed_devices.cc",
diff --git a/content/browser/blob_storage/blob_url_loader_factory.cc b/content/browser/blob_storage/blob_url_loader_factory.cc
new file mode 100644
index 0000000..8267654e
--- /dev/null
+++ b/content/browser/blob_storage/blob_url_loader_factory.cc
@@ -0,0 +1,405 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "content/browser/blob_storage/blob_url_loader_factory.h"
+
+#include <stddef.h>
+#include "base/bind.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "content/browser/blob_storage/chrome_blob_storage_context.h"
+#include "content/browser/storage_partition_impl.h"
+#include "content/common/net_adapters.h"
+#include "content/common/url_loader.mojom.h"
+#include "content/public/browser/browser_thread.h"
+#include "mojo/public/cpp/system/simple_watcher.h"
+#include "net/base/io_buffer.h"
+#include "net/http/http_byte_range.h"
+#include "net/http/http_request_headers.h"
+#include "net/http/http_response_headers.h"
+#include "net/http/http_status_code.h"
+#include "net/http/http_util.h"
+#include "storage/browser/blob/blob_data_handle.h"
+#include "storage/browser/blob/blob_reader.h"
+#include "storage/browser/blob/blob_storage_context.h"
+#include "storage/browser/blob/blob_url_request_job.h"
+#include "storage/browser/fileapi/file_system_context.h"
+
+namespace content {
+
+namespace {
+constexpr size_t kDefaultAllocationSize = 512 * 1024;
+
+// This class handles a request for a blob:// url. It self-destructs (see
+// DeleteIfNeeded) when it has finished responding.
+// Note: some of this code is duplicated from storage::BlobURLRequestJob.
+class BlobURLLoader : public mojom::URLLoader {
+ public:
+  BlobURLLoader(mojom::URLLoaderAssociatedRequest url_loader_request,
+                const ResourceRequest& request,
+                mojom::URLLoaderClientPtr client,
+                storage::BlobStorageContext* blob_storage_context,
+                scoped_refptr<storage::FileSystemContext> file_system_context)
+      : binding_(this, std::move(url_loader_request)),
+        request_(request),
+        client_(std::move(client)),
+        writable_handle_watcher_(FROM_HERE,
+                                 mojo::SimpleWatcher::ArmingPolicy::MANUAL),
+        peer_closed_handle_watcher_(FROM_HERE,
+                                    mojo::SimpleWatcher::ArmingPolicy::MANUAL),
+        weak_factory_(this) {
+    DCHECK_CURRENTLY_ON(BrowserThread::IO);
+    if (blob_storage_context) {
+      blob_handle_ =
+          blob_storage_context->GetBlobDataFromPublicURL(request.url);
+    }
+
+    // PostTask since it might destruct.
+    base::ThreadTaskRunnerHandle::Get()->PostTask(
+        FROM_HERE, base::Bind(&BlobURLLoader::Start, weak_factory_.GetWeakPtr(),
+                              request, file_system_context));
+  }
+
+  void Start(const ResourceRequest& request,
+             scoped_refptr<storage::FileSystemContext> file_system_context) {
+    if (!blob_handle_) {
+      NotifyCompleted(net::ERR_FILE_NOT_FOUND);
+      return;
+    }
+
+    base::SequencedTaskRunner* file_task_runner =
+        BrowserThread::GetTaskRunnerForThread(BrowserThread::FILE).get();
+    blob_reader_ =
+        blob_handle_->CreateReader(file_system_context.get(), file_task_runner);
+
+    // We only support GET request per the spec.
+    if (request.method != "GET") {
+      NotifyCompleted(net::ERR_METHOD_NOT_SUPPORTED);
+      return;
+    }
+
+    if (blob_reader_->net_error()) {
+      NotifyCompleted(blob_reader_->net_error());
+      return;
+    }
+
+    net::HttpRequestHeaders request_headers;
+    request_headers.AddHeadersFromString(request.headers);
+    std::string range_header;
+    if (request_headers.GetHeader(net::HttpRequestHeaders::kRange,
+                                  &range_header)) {
+      // We only care about "Range" header here.
+      std::vector<net::HttpByteRange> ranges;
+      if (net::HttpUtil::ParseRangeHeader(range_header, &ranges)) {
+        if (ranges.size() == 1) {
+          byte_range_set_ = true;
+          byte_range_ = ranges[0];
+        } else {
+          // We don't support multiple range requests in one single URL request,
+          // because we need to do multipart encoding here.
+          // TODO(jianli): Support multipart byte range requests.
+          NotifyCompleted(net::ERR_REQUEST_RANGE_NOT_SATISFIABLE);
+        }
+      }
+    }
+
+    storage::BlobReader::Status size_status =
+        blob_reader_->CalculateSize(base::Bind(&BlobURLLoader::DidCalculateSize,
+                                               weak_factory_.GetWeakPtr()));
+    switch (size_status) {
+      case storage::BlobReader::Status::NET_ERROR:
+        NotifyCompleted(blob_reader_->net_error());
+        return;
+      case storage::BlobReader::Status::IO_PENDING:
+        return;
+      case storage::BlobReader::Status::DONE:
+        DidCalculateSize(net::OK);
+        return;
+    }
+
+    NOTREACHED();
+  }
+
+  ~BlobURLLoader() override {}
+
+ private:
+  // mojom::URLLoader implementation:
+  void FollowRedirect() override { NOTREACHED(); }
+
+  void SetPriority(net::RequestPriority priority,
+                   int32_t intra_priority_value) override {}
+
+  // Notifies the client that the request completed. Takes care of deleting this
+  // object now if possible (i.e. no outstanding data pipe), otherwise this
+  // object will be deleted when the data pipe is closed.
+  void NotifyCompleted(int error_code) {
+    if (error_code != net::OK && !sent_headers_) {
+      net::HttpStatusCode status_code =
+          storage::BlobURLRequestJob::NetErrorToHttpStatusCode(error_code);
+      ResourceResponseHead response;
+      response.headers = storage::BlobURLRequestJob::GenerateHeaders(
+          status_code, nullptr, nullptr, nullptr, nullptr);
+      client_->OnReceiveResponse(response, base::nullopt, nullptr);
+    }
+    ResourceRequestCompletionStatus request_complete_data;
+    client_->OnComplete(request_complete_data);
+
+    DeleteIfNeeded();
+  }
+
+  void DidCalculateSize(int result) {
+    if (result != net::OK) {
+      NotifyCompleted(result);
+      return;
+    }
+
+    // Apply the range requirement.
+    if (!byte_range_.ComputeBounds(blob_reader_->total_size())) {
+      NotifyCompleted(net::ERR_REQUEST_RANGE_NOT_SATISFIABLE);
+      return;
+    }
+
+    DCHECK_LE(byte_range_.first_byte_position(),
+              byte_range_.last_byte_position() + 1);
+    uint64_t length =
+        base::checked_cast<uint64_t>(byte_range_.last_byte_position() -
+                                     byte_range_.first_byte_position() + 1);
+
+    if (byte_range_set_)
+      blob_reader_->SetReadRange(byte_range_.first_byte_position(), length);
+
+    net::HttpStatusCode status_code = net::HTTP_OK;
+    if (byte_range_set_ && byte_range_.IsValid()) {
+      status_code = net::HTTP_PARTIAL_CONTENT;
+    } else {
+      // TODO(horo): When the requester doesn't need the side data
+      // (ex:FileReader) we should skip reading the side data.
+      if (blob_reader_->has_side_data() &&
+          blob_reader_->ReadSideData(base::Bind(&BlobURLLoader::DidReadMetadata,
+                                                weak_factory_.GetWeakPtr())) ==
+              storage::BlobReader::Status::IO_PENDING) {
+        return;
+      }
+    }
+
+    HeadersCompleted(status_code);
+  }
+
+  void DidReadMetadata(storage::BlobReader::Status result) {
+    if (result != storage::BlobReader::Status::DONE) {
+      NotifyCompleted(blob_reader_->net_error());
+      return;
+    }
+    HeadersCompleted(net::HTTP_OK);
+  }
+
+  void HeadersCompleted(net::HttpStatusCode status_code) {
+    ResourceResponseHead response;
+    response.content_length = 0;
+    response.headers = storage::BlobURLRequestJob::GenerateHeaders(
+        status_code, blob_handle_.get(), blob_reader_.get(), &byte_range_,
+        &response.content_length);
+
+    std::string mime_type;
+    response.headers->GetMimeType(&mime_type);
+    // Match logic in StreamURLRequestJob::HeadersCompleted.
+    if (mime_type.empty())
+      mime_type = "text/plain";
+    response.mime_type = mime_type;
+
+    // TODO(jam): some of this code can be shared with
+    // content/network/url_loader_impl.h
+    client_->OnReceiveResponse(response, base::nullopt, nullptr);
+    sent_headers_ = true;
+
+    net::IOBufferWithSize* metadata = blob_reader_->side_data();
+    if (metadata) {
+      const uint8_t* data = reinterpret_cast<const uint8_t*>(metadata->data());
+      client_->OnReceiveCachedMetadata(
+          std::vector<uint8_t>(data, data + metadata->size()));
+    }
+
+    mojo::DataPipe data_pipe(kDefaultAllocationSize);
+    response_body_stream_ = std::move(data_pipe.producer_handle);
+    response_body_consumer_handle_ = std::move(data_pipe.consumer_handle);
+    peer_closed_handle_watcher_.Watch(
+        response_body_stream_.get(), MOJO_HANDLE_SIGNAL_PEER_CLOSED,
+        base::Bind(&BlobURLLoader::OnResponseBodyStreamClosed,
+                   base::Unretained(this)));
+    peer_closed_handle_watcher_.ArmOrNotify();
+
+    writable_handle_watcher_.Watch(
+        response_body_stream_.get(), MOJO_HANDLE_SIGNAL_WRITABLE,
+        base::Bind(&BlobURLLoader::OnResponseBodyStreamReady,
+                   base::Unretained(this)));
+
+    // Start reading...
+    ReadMore();
+  }
+
+  void ReadMore() {
+    DCHECK(!pending_write_.get());
+
+    uint32_t num_bytes;
+    // TODO: we should use the abstractions in MojoAsyncResourceHandler.
+    MojoResult result = NetToMojoPendingBuffer::BeginWrite(
+        &response_body_stream_, &pending_write_, &num_bytes);
+    if (result == MOJO_RESULT_SHOULD_WAIT) {
+      // The pipe is full. We need to wait for it to have more space.
+      writable_handle_watcher_.ArmOrNotify();
+      return;
+    } else if (result != MOJO_RESULT_OK) {
+      // The response body stream is in a bad state. Bail.
+      writable_handle_watcher_.Cancel();
+      response_body_stream_.reset();
+      NotifyCompleted(net::ERR_UNEXPECTED);
+      return;
+    }
+
+    CHECK_GT(static_cast<uint32_t>(std::numeric_limits<int>::max()), num_bytes);
+    scoped_refptr<net::IOBuffer> buf(
+        new NetToMojoIOBuffer(pending_write_.get()));
+    int bytes_read;
+    storage::BlobReader::Status read_status = blob_reader_->Read(
+        buf.get(), static_cast<int>(num_bytes), &bytes_read,
+        base::Bind(&BlobURLLoader::DidRead, weak_factory_.GetWeakPtr(), false));
+    switch (read_status) {
+      case storage::BlobReader::Status::NET_ERROR:
+        NotifyCompleted(blob_reader_->net_error());
+        return;
+      case storage::BlobReader::Status::IO_PENDING:
+        // Wait for DidRead.
+        return;
+      case storage::BlobReader::Status::DONE:
+        if (bytes_read > 0) {
+          DidRead(true, bytes_read);
+        } else {
+          writable_handle_watcher_.Cancel();
+          pending_write_->Complete(0);
+          pending_write_ = nullptr;  // This closes the data pipe.
+          NotifyCompleted(net::OK);
+          return;
+        }
+    }
+  }
+
+  void DidRead(bool completed_synchronously, int num_bytes) {
+    if (response_body_consumer_handle_.is_valid()) {
+      // Send the data pipe on the first OnReadCompleted call.
+      client_->OnStartLoadingResponseBody(
+          std::move(response_body_consumer_handle_));
+    }
+    response_body_stream_ = pending_write_->Complete(num_bytes);
+    pending_write_ = nullptr;
+    if (completed_synchronously) {
+      base::ThreadTaskRunnerHandle::Get()->PostTask(
+          FROM_HERE,
+          base::Bind(&BlobURLLoader::ReadMore, weak_factory_.GetWeakPtr()));
+    } else {
+      ReadMore();
+    }
+  }
+
+  void OnResponseBodyStreamClosed(MojoResult result) {
+    response_body_stream_.reset();
+    pending_write_ = nullptr;
+    DeleteIfNeeded();
+  }
+
+  void OnResponseBodyStreamReady(MojoResult result) {
+    // TODO: Handle a bad |result| value.
+    DCHECK_EQ(result, MOJO_RESULT_OK);
+    ReadMore();
+  }
+
+  void DeleteIfNeeded() {
+    bool has_data_pipe =
+        pending_write_.get() || response_body_stream_.is_valid();
+    if (!has_data_pipe)
+      delete this;
+  }
+
+  mojo::AssociatedBinding<mojom::URLLoader> binding_;
+  ResourceRequest request_;
+  mojom::URLLoaderClientPtr client_;
+
+  bool byte_range_set_ = false;
+  net::HttpByteRange byte_range_;
+
+  bool sent_headers_ = false;
+
+  std::unique_ptr<storage::BlobDataHandle> blob_handle_;
+  std::unique_ptr<storage::BlobReader> blob_reader_;
+
+  // TODO(jam): share with URLLoaderImpl
+  mojo::ScopedDataPipeProducerHandle response_body_stream_;
+  mojo::ScopedDataPipeConsumerHandle response_body_consumer_handle_;
+  scoped_refptr<NetToMojoPendingBuffer> pending_write_;
+  mojo::SimpleWatcher writable_handle_watcher_;
+  mojo::SimpleWatcher peer_closed_handle_watcher_;
+
+  base::WeakPtrFactory<BlobURLLoader> weak_factory_;
+
+  DISALLOW_COPY_AND_ASSIGN(BlobURLLoader);
+};
+
+}  // namespace
+
+BlobURLLoaderFactory::BlobURLLoaderFactory(
+    BlobContextGetter blob_storage_context_getter,
+    scoped_refptr<storage::FileSystemContext> file_system_context)
+    : file_system_context_(file_system_context) {
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
+  BrowserThread::PostTask(
+      BrowserThread::IO, FROM_HERE,
+      base::BindOnce(&BlobURLLoaderFactory::InitializeOnIO, this,
+                     std::move(blob_storage_context_getter)));
+}
+
+mojom::URLLoaderFactoryPtr BlobURLLoaderFactory::CreateFactory() {
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
+  mojom::URLLoaderFactoryPtr factory;
+  mojom::URLLoaderFactoryRequest request = mojo::MakeRequest(&factory);
+  BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
+                          base::BindOnce(&BlobURLLoaderFactory::BindOnIO, this,
+                                         std::move(request)));
+
+  return factory;
+}
+
+BlobURLLoaderFactory::~BlobURLLoaderFactory() {}
+
+void BlobURLLoaderFactory::InitializeOnIO(
+    BlobContextGetter blob_storage_context_getter) {
+  blob_storage_context_ = std::move(blob_storage_context_getter).Run();
+}
+
+void BlobURLLoaderFactory::BindOnIO(mojom::URLLoaderFactoryRequest request) {
+  DCHECK_CURRENTLY_ON(BrowserThread::IO);
+
+  loader_factory_bindings_.AddBinding(this, std::move(request));
+}
+
+void BlobURLLoaderFactory::CreateLoaderAndStart(
+    mojom::URLLoaderAssociatedRequest loader,
+    int32_t routing_id,
+    int32_t request_id,
+    uint32_t options,
+    const ResourceRequest& request,
+    mojom::URLLoaderClientPtr client) {
+  DCHECK_CURRENTLY_ON(BrowserThread::IO);
+  new BlobURLLoader(std::move(loader), request, std::move(client),
+                    blob_storage_context_.get(), file_system_context_);
+}
+
+void BlobURLLoaderFactory::SyncLoad(int32_t routing_id,
+                                    int32_t request_id,
+                                    const ResourceRequest& request,
+                                    SyncLoadCallback callback) {
+  NOTREACHED();
+}
+
+}  // namespace content
diff --git a/content/browser/blob_storage/blob_url_loader_factory.h b/content/browser/blob_storage/blob_url_loader_factory.h
new file mode 100644
index 0000000..cea4f06
--- /dev/null
+++ b/content/browser/blob_storage/blob_url_loader_factory.h
@@ -0,0 +1,72 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CONTENT_BROWSER_BLOB_STORAGE_BLOB_URL_LOADER_FACTORY_H_
+#define CONTENT_BROWSER_BLOB_STORAGE_BLOB_URL_LOADER_FACTORY_H_
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "content/common/content_export.h"
+#include "content/common/url_loader_factory.mojom.h"
+#include "content/public/browser/browser_thread.h"
+#include "mojo/public/cpp/bindings/binding_set.h"
+
+namespace storage {
+class BlobStorageContext;
+class FileSystemContext;
+}
+
+namespace content {
+
+// A class for creating URLLoaderFactory for blob scheme.
+// There should be one owned per StoragePartition.
+class BlobURLLoaderFactory
+    : public base::RefCountedThreadSafe<BlobURLLoaderFactory,
+                                        BrowserThread::DeleteOnIOThread>,
+      public mojom::URLLoaderFactory {
+ public:
+  using BlobContextGetter =
+      base::OnceCallback<base::WeakPtr<storage::BlobStorageContext>()>;
+  CONTENT_EXPORT BlobURLLoaderFactory(
+      BlobContextGetter blob_storage_context_getter,
+      scoped_refptr<storage::FileSystemContext> file_system_context);
+
+  // Creates a URLLoaderFactory interface pointer for serving blob requests.
+  // Called on the UI thread.
+  mojom::URLLoaderFactoryPtr CreateFactory();
+
+  // mojom::URLLoaderFactory implementation:
+  void CreateLoaderAndStart(mojom::URLLoaderAssociatedRequest loader,
+                            int32_t routing_id,
+                            int32_t request_id,
+                            uint32_t options,
+                            const ResourceRequest& request,
+                            mojom::URLLoaderClientPtr client) override;
+  void SyncLoad(int32_t routing_id,
+                int32_t request_id,
+                const ResourceRequest& request,
+                SyncLoadCallback callback) override;
+
+ private:
+  friend class base::DeleteHelper<BlobURLLoaderFactory>;
+  friend struct BrowserThread::DeleteOnThread<BrowserThread::IO>;
+
+  ~BlobURLLoaderFactory() override;
+
+  void InitializeOnIO(BlobContextGetter blob_storage_context_getter);
+  void BindOnIO(mojom::URLLoaderFactoryRequest request);
+
+  base::WeakPtr<storage::BlobStorageContext> blob_storage_context_;
+  scoped_refptr<storage::FileSystemContext> file_system_context_;
+
+  // Used on the IO thread.
+  mojo::BindingSet<mojom::URLLoaderFactory> loader_factory_bindings_;
+
+  DISALLOW_COPY_AND_ASSIGN(BlobURLLoaderFactory);
+};
+
+}  // namespace content
+
+#endif  // CONTENT_BROWSER_BLOB_STORAGE_BLOB_URL_LOADER_FACTORY_H_
diff --git a/storage/browser/blob/blob_url_request_job_unittest.cc b/content/browser/blob_storage/blob_url_unittest.cc
similarity index 82%
rename from storage/browser/blob/blob_url_request_job_unittest.cc
rename to content/browser/blob_storage/blob_url_unittest.cc
index 353eaf08..fef6738 100644
--- a/storage/browser/blob/blob_url_request_job_unittest.cc
+++ b/content/browser/blob_storage/blob_url_unittest.cc
@@ -8,6 +8,7 @@
 #include <memory>
 
 #include "base/bind.h"
+#include "base/command_line.h"
 #include "base/files/file_path.h"
 #include "base/files/file_util.h"
 #include "base/files/scoped_temp_dir.h"
@@ -19,6 +20,12 @@
 #include "base/run_loop.h"
 #include "base/threading/thread_task_runner_handle.h"
 #include "base/time/time.h"
+#include "content/browser/blob_storage/blob_url_loader_factory.h"
+#include "content/browser/loader/test_url_loader_client.h"
+#include "content/browser/url_loader_factory_getter.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/test/test_browser_thread_bundle.h"
+#include "mojo/common/data_pipe_utils.h"
 #include "net/base/net_errors.h"
 #include "net/base/request_priority.h"
 #include "net/base/test_completion_callback.h"
@@ -148,7 +155,10 @@
   };
 
   BlobURLRequestJobTest()
-      : blob_data_(new BlobDataBuilder("uuid")), expected_status_code_(0) {}
+      : thread_bundle_(TestBrowserThreadBundle::IO_MAINLOOP),
+        blob_data_(new BlobDataBuilder("uuid")),
+        response_error_code_(net::OK),
+        expected_status_code_(0) {}
 
   void SetUp() override {
     ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
@@ -180,7 +190,6 @@
 
   void TearDown() override {
     blob_handle_.reset();
-    request_.reset();
     // Clean up for ASAN
     base::RunLoop run_loop;
     run_loop.RunUntilIdle();
@@ -250,34 +259,76 @@
     expected_status_code_ = 200;
     expected_response_ = expected_response;
     TestRequest("GET", net::HttpRequestHeaders());
-    EXPECT_EQ(expected_content_length,
-              request_->response_headers()->GetContentLength());
+    EXPECT_EQ(expected_content_length, response_headers_->GetContentLength());
   }
 
   void TestErrorRequest(int expected_status_code) {
     expected_status_code_ = expected_status_code;
     expected_response_ = "";
     TestRequest("GET", net::HttpRequestHeaders());
-    EXPECT_FALSE(request_->response_info().metadata);
+    EXPECT_TRUE(response_metadata_.empty());
   }
 
   void TestRequest(const std::string& method,
                    const net::HttpRequestHeaders& extra_headers) {
-    request_ = url_request_context_.CreateRequest(
-        GURL("blob:blah"), net::DEFAULT_PRIORITY, &url_request_delegate_,
-        TRAFFIC_ANNOTATION_FOR_TESTS);
-    request_->set_method(method);
-    if (!extra_headers.IsEmpty())
-      request_->SetExtraRequestHeaders(extra_headers);
-    request_->Start();
+    GURL url("blob:blah");
 
-    base::RunLoop().Run();
+    if (base::CommandLine::ForCurrentProcess()->HasSwitch(
+            switches::kEnableNetworkService)) {
+      GetHandleFromBuilder();  // To add to StorageContext.
+      const_cast<storage::BlobStorageRegistry&>(blob_context_.registry())
+          .CreateUrlMapping(url, blob_data_->uuid());
+      ResourceRequest request;
+      request.url = url;
+      request.method = method;
+      if (!extra_headers.IsEmpty())
+        request.headers = extra_headers.ToString();
+
+      mojom::URLLoaderAssociatedPtr url_loader;
+      TestURLLoaderClient url_loader_client;
+      scoped_refptr<BlobURLLoaderFactory> factory = new BlobURLLoaderFactory(
+          base::Bind(&BlobURLRequestJobTest::GetStorageContext,
+                     base::Unretained(this)),
+          file_system_context_);
+      base::RunLoop().RunUntilIdle();
+      factory->CreateLoaderAndStart(mojo::MakeRequest(&url_loader), 0, 0,
+                                    mojom::kURLLoadOptionNone, request,
+                                    url_loader_client.CreateInterfacePtr());
+      url_loader_client.RunUntilComplete();
+
+      if (url_loader_client.response_body().is_valid()) {
+        EXPECT_TRUE(mojo::common::BlockingCopyToString(
+            url_loader_client.response_body_release(), &response_));
+      }
+      response_headers_ = url_loader_client.response_head().headers;
+      response_metadata_ = url_loader_client.cached_metadata();
+      response_error_code_ = url_loader_client.completion_status().error_code;
+    } else {
+      std::unique_ptr<net::URLRequest> request =
+          url_request_context_.CreateRequest(url, net::DEFAULT_PRIORITY,
+                                             &url_request_delegate_,
+                                             TRAFFIC_ANNOTATION_FOR_TESTS);
+      request->set_method(method);
+      if (!extra_headers.IsEmpty())
+        request->SetExtraRequestHeaders(extra_headers);
+      request->Start();
+
+      base::RunLoop().Run();
+      response_ = url_request_delegate_.data_received();
+      response_headers_ = request->response_headers();
+      if (request->response_info().metadata) {
+        response_metadata_ =
+            std::string(request->response_info().metadata->data(),
+                        request->response_info().metadata->size());
+      }
+
+      response_error_code_ = url_request_delegate_.request_status();
+    }
 
     // Verify response.
-    EXPECT_EQ(net::OK, url_request_delegate_.request_status());
-    EXPECT_EQ(expected_status_code_,
-              request_->response_headers()->response_code());
-    EXPECT_EQ(expected_response_, url_request_delegate_.data_received());
+    EXPECT_EQ(net::OK, response_error_code_);
+    EXPECT_EQ(expected_status_code_, response_headers_->response_code());
+    EXPECT_EQ(expected_response_, response_);
   }
 
   void BuildComplicatedData(std::string* expected_result) {
@@ -330,6 +381,10 @@
   }
 
  protected:
+  base::WeakPtr<storage::BlobStorageContext> GetStorageContext() {
+    return blob_context_.AsWeakPtr();
+  }
+
   base::ScopedTempDir temp_dir_;
   base::FilePath temp_file1_;
   base::FilePath temp_file2_;
@@ -344,7 +399,7 @@
   std::unique_ptr<disk_cache::Backend> disk_cache_backend_;
   disk_cache::ScopedEntryPtr disk_cache_entry_;
 
-  base::MessageLoopForIO message_loop_;
+  TestBrowserThreadBundle thread_bundle_;
   scoped_refptr<storage::FileSystemContext> file_system_context_;
 
   storage::BlobStorageContext blob_context_;
@@ -354,7 +409,10 @@
   net::URLRequestJobFactoryImpl url_request_job_factory_;
   net::URLRequestContext url_request_context_;
   net::TestDelegate url_request_delegate_;
-  std::unique_ptr<net::URLRequest> request_;
+  std::string response_;
+  int response_error_code_;
+  scoped_refptr<net::HttpResponseHeaders> response_headers_;
+  std::string response_metadata_;
 
   int expected_status_code_;
   std::string expected_response_;
@@ -490,12 +548,11 @@
   expected_response_ = result.substr(5, 10 - 5 + 1);
   TestRequest("GET", extra_headers);
 
-  EXPECT_EQ(6, request_->response_headers()->GetContentLength());
-  EXPECT_FALSE(request_->response_info().metadata);
+  EXPECT_EQ(6, response_headers_->GetContentLength());
+  EXPECT_TRUE(response_metadata_.empty());
 
   int64_t first = 0, last = 0, length = 0;
-  EXPECT_TRUE(request_->response_headers()->GetContentRangeFor206(&first, &last,
-                                                                  &length));
+  EXPECT_TRUE(response_headers_->GetContentRangeFor206(&first, &last, &length));
   EXPECT_EQ(5, first);
   EXPECT_EQ(10, last);
   EXPECT_EQ(GetTotalBlobLength(), length);
@@ -512,13 +569,12 @@
   expected_response_ = result.substr(result.length() - 10);
   TestRequest("GET", extra_headers);
 
-  EXPECT_EQ(10, request_->response_headers()->GetContentLength());
-  EXPECT_FALSE(request_->response_info().metadata);
+  EXPECT_EQ(10, response_headers_->GetContentLength());
+  EXPECT_TRUE(response_metadata_.empty());
 
   int64_t total = GetTotalBlobLength();
   int64_t first = 0, last = 0, length = 0;
-  EXPECT_TRUE(request_->response_headers()->GetContentRangeFor206(&first, &last,
-                                                                  &length));
+  EXPECT_TRUE(response_headers_->GetContentRangeFor206(&first, &last, &length));
   EXPECT_EQ(total - 10, first);
   EXPECT_EQ(total - 1, last);
   EXPECT_EQ(total, length);
@@ -535,12 +591,11 @@
   expected_response_ = result.substr(0, 3);
   TestRequest("GET", extra_headers);
 
-  EXPECT_EQ(3, request_->response_headers()->GetContentLength());
-  EXPECT_FALSE(request_->response_info().metadata);
+  EXPECT_EQ(3, response_headers_->GetContentLength());
+  EXPECT_TRUE(response_metadata_.empty());
 
   int64_t first = 0, last = 0, length = 0;
-  EXPECT_TRUE(request_->response_headers()->GetContentRangeFor206(&first, &last,
-                                                                  &length));
+  EXPECT_TRUE(response_headers_->GetContentRangeFor206(&first, &last, &length));
   EXPECT_EQ(0, first);
   EXPECT_EQ(2, last);
   EXPECT_EQ(GetTotalBlobLength(), length);
@@ -555,13 +610,13 @@
   TestRequest("GET", net::HttpRequestHeaders());
 
   std::string content_type;
-  EXPECT_TRUE(request_->response_headers()->GetMimeType(&content_type));
+  EXPECT_TRUE(response_headers_->GetMimeType(&content_type));
   EXPECT_EQ(kTestContentType, content_type);
-  EXPECT_FALSE(request_->response_info().metadata);
+  EXPECT_TRUE(response_metadata_.empty());
   size_t iter = 0;
   std::string content_disposition;
-  EXPECT_TRUE(request_->response_headers()->EnumerateHeader(
-      &iter, "Content-Disposition", &content_disposition));
+  EXPECT_TRUE(response_headers_->EnumerateHeader(&iter, "Content-Disposition",
+                                                 &content_disposition));
   EXPECT_EQ(kTestContentDisposition, content_disposition);
 }
 
@@ -577,12 +632,9 @@
   expected_response_ = kTestDiskCacheData2;
   TestRequest("GET", net::HttpRequestHeaders());
   EXPECT_EQ(static_cast<int>(arraysize(kTestDiskCacheData2) - 1),
-            request_->response_headers()->GetContentLength());
+            response_headers_->GetContentLength());
 
-  ASSERT_TRUE(request_->response_info().metadata);
-  std::string metadata(request_->response_info().metadata->data(),
-                       request_->response_info().metadata->size());
-  EXPECT_EQ(std::string(kTestDiskCacheSideData), metadata);
+  EXPECT_EQ(std::string(kTestDiskCacheSideData), response_metadata_);
 }
 
 TEST_F(BlobURLRequestJobTest, TestZeroSizeSideData) {
@@ -597,9 +649,9 @@
   expected_response_ = kTestDiskCacheData2;
   TestRequest("GET", net::HttpRequestHeaders());
   EXPECT_EQ(static_cast<int>(arraysize(kTestDiskCacheData2) - 1),
-            request_->response_headers()->GetContentLength());
+            response_headers_->GetContentLength());
 
-  EXPECT_FALSE(request_->response_info().metadata);
+  EXPECT_TRUE(response_metadata_.empty());
 }
 
 TEST_F(BlobURLRequestJobTest, BrokenBlob) {
diff --git a/content/browser/frame_host/render_frame_host_impl.cc b/content/browser/frame_host/render_frame_host_impl.cc
index b4b4cc1..0bd003f 100644
--- a/content/browser/frame_host/render_frame_host_impl.cc
+++ b/content/browser/frame_host/render_frame_host_impl.cc
@@ -3151,7 +3151,7 @@
     const auto& schemes = URLDataManagerBackend::GetWebUISchemes();
     if (std::find(schemes.begin(), schemes.end(), common_params.url.scheme()) !=
         schemes.end()) {
-      commit_data.url_loader_factory = GetWebUIURLLoader(frame_tree_node_)
+      commit_data.url_loader_factory = CreateWebUIURLLoader(frame_tree_node_)
                                            .PassInterface()
                                            .PassHandle()
                                            .release();
diff --git a/content/browser/loader/navigation_url_loader_network_service.cc b/content/browser/loader/navigation_url_loader_network_service.cc
index 49b37ab1..4136c41c 100644
--- a/content/browser/loader/navigation_url_loader_network_service.cc
+++ b/content/browser/loader/navigation_url_loader_network_service.cc
@@ -66,11 +66,13 @@
 // redirects happen.
 class NavigationURLLoaderNetworkService::URLLoaderRequestController {
  public:
-  URLLoaderRequestController(std::unique_ptr<ResourceRequest> resource_request,
-                             ResourceContext* resource_context)
+  URLLoaderRequestController(
+      std::unique_ptr<ResourceRequest> resource_request,
+      ResourceContext* resource_context,
+      scoped_refptr<URLLoaderFactoryGetter> url_loader_factory_getter)
       : resource_request_(std::move(resource_request)),
         resource_context_(resource_context),
-        network_factory_(nullptr) {}
+        url_loader_factory_getter_(url_loader_factory_getter) {}
 
   virtual ~URLLoaderRequestController() {
     DCHECK_CURRENTLY_ON(BrowserThread::IO);
@@ -81,7 +83,6 @@
       AppCacheNavigationHandleCore* appcache_handle_core,
       std::unique_ptr<NavigationRequestInfo> request_info,
       mojom::URLLoaderFactoryPtrInfo factory_for_webui,
-      scoped_refptr<URLLoaderFactoryGetter> url_loader_factory_getter,
       const base::Callback<WebContents*(void)>& web_contents_getter,
       mojom::URLLoaderAssociatedRequest url_loader_request,
       mojom::URLLoaderClientPtr url_loader_client_ptr,
@@ -132,9 +133,6 @@
       // TODO: add appcache code here.
     }
 
-    DCHECK(!network_factory_);
-    network_factory_ = url_loader_factory_getter->GetNetworkFactory()->get();
-
     Restart(std::move(url_loader_request), std::move(url_loader_client_ptr));
   }
 
@@ -167,8 +165,12 @@
     }
 
     DCHECK_EQ(handlers_.size(), handler_index_);
-    DCHECK(network_factory_ != nullptr);
-    MaybeStartLoader(network_factory_);
+    if (resource_request_->url.SchemeIs(url::kBlobScheme)) {
+      factory = url_loader_factory_getter_->GetBlobFactory()->get();
+    } else {
+      factory = url_loader_factory_getter_->GetNetworkFactory()->get();
+    }
+    MaybeStartLoader(factory);
   }
 
  private:
@@ -178,9 +180,7 @@
   std::unique_ptr<ResourceRequest> resource_request_;
   ResourceContext* resource_context_;
 
-  // The factory for doing a vanilla network request, called when
-  // any of other request handlers handle the given request.
-  mojom::URLLoaderFactory* network_factory_;
+  scoped_refptr<URLLoaderFactoryGetter> url_loader_factory_getter_;
 
   // Kept around until we create a loader.
   mojom::URLLoaderAssociatedRequest url_loader_request_;
@@ -249,14 +249,16 @@
       schemes.end()) {
     FrameTreeNode* frame_tree_node =
         FrameTreeNode::GloballyFindByID(frame_tree_node_id);
-    factory_for_webui = GetWebUIURLLoader(frame_tree_node).PassInterface();
+    factory_for_webui = CreateWebUIURLLoader(frame_tree_node).PassInterface();
   }
 
   g_next_request_id--;
 
   DCHECK(!request_controller_);
   request_controller_ = base::MakeUnique<URLLoaderRequestController>(
-      std::move(new_request), resource_context);
+      std::move(new_request), resource_context,
+      static_cast<StoragePartitionImpl*>(storage_partition)
+          ->url_loader_factory_getter());
   BrowserThread::PostTask(
       BrowserThread::IO, FROM_HERE,
       base::Bind(
@@ -268,8 +270,6 @@
           appcache_handle ? appcache_handle->core() : nullptr,
           base::Passed(std::move(request_info)),
           base::Passed(std::move(factory_for_webui)),
-          static_cast<StoragePartitionImpl*>(storage_partition)
-              ->url_loader_factory_getter(),
           base::Bind(&GetWebContentsFromFrameTreeNodeID, frame_tree_node_id),
           base::Passed(std::move(loader_associated_request)),
           base::Passed(std::move(url_loader_client_ptr_to_pass)),
diff --git a/content/browser/loader/test_url_loader_client.h b/content/browser/loader/test_url_loader_client.h
index ba50bc7..3bec4de 100644
--- a/content/browser/loader/test_url_loader_client.h
+++ b/content/browser/loader/test_url_loader_client.h
@@ -61,6 +61,9 @@
     return cached_metadata_;
   }
   mojo::DataPipeConsumerHandle response_body() { return response_body_.get(); }
+  mojo::ScopedDataPipeConsumerHandle response_body_release() {
+    return std::move(response_body_);
+  }
   const ResourceRequestCompletionStatus& completion_status() const {
     return completion_status_;
   }
diff --git a/content/browser/storage_partition_impl.cc b/content/browser/storage_partition_impl.cc
index 01dc7068..dbea308 100644
--- a/content/browser/storage_partition_impl.cc
+++ b/content/browser/storage_partition_impl.cc
@@ -15,6 +15,7 @@
 #include "base/sequenced_task_runner.h"
 #include "base/single_thread_task_runner.h"
 #include "base/strings/utf_string_conversions.h"
+#include "content/browser/blob_storage/chrome_blob_storage_context.h"
 #include "content/browser/browser_main_loop.h"
 #include "content/browser/browsing_data/storage_partition_http_cache_data_remover.h"
 #include "content/browser/fileapi/browser_file_system_helper.h"
@@ -40,6 +41,7 @@
 #include "net/url_request/url_request_context_getter.h"
 #include "ppapi/features/features.h"
 #include "services/service_manager/public/cpp/connector.h"
+#include "storage/browser/blob/blob_storage_context.h"
 #include "storage/browser/database/database_tracker.h"
 #include "storage/browser/quota/quota_manager.h"
 
@@ -230,6 +232,12 @@
                  callback));
 }
 
+base::WeakPtr<storage::BlobStorageContext> BlobStorageContextGetter(
+    scoped_refptr<ChromeBlobStorageContext> blob_context) {
+  DCHECK_CURRENTLY_ON(BrowserThread::IO);
+  return blob_context->context()->AsWeakPtr();
+}
+
 }  // namespace
 
 // Static.
@@ -529,6 +537,13 @@
     network_service->CreateNetworkContext(
         MakeRequest(&partition->network_context_), std::move(context_params));
 
+    scoped_refptr<ChromeBlobStorageContext> blob_context =
+        ChromeBlobStorageContext::GetFor(context);
+    BlobURLLoaderFactory::BlobContextGetter blob_getter =
+        base::BindOnce(&BlobStorageContextGetter, blob_context);
+    partition->blob_url_loader_factory_ = new BlobURLLoaderFactory(
+        std::move(blob_getter), partition->filesystem_context_);
+
     partition->url_loader_factory_getter_ = new URLLoaderFactoryGetter();
     partition->url_loader_factory_getter_->Initialize(partition.get());
   }
@@ -623,6 +638,10 @@
   return bluetooth_allowed_devices_map_.get();
 }
 
+BlobURLLoaderFactory* StoragePartitionImpl::GetBlobURLLoaderFactory() {
+  return blob_url_loader_factory_.get();
+}
+
 void StoragePartitionImpl::OpenLocalStorage(
     const url::Origin& origin,
     mojo::InterfaceRequest<mojom::LevelDBWrapper> request) {
diff --git a/content/browser/storage_partition_impl.h b/content/browser/storage_partition_impl.h
index 58a6de1..cf6d28c 100644
--- a/content/browser/storage_partition_impl.h
+++ b/content/browser/storage_partition_impl.h
@@ -18,6 +18,7 @@
 #include "content/browser/appcache/chrome_appcache_service.h"
 #include "content/browser/background_fetch/background_fetch_context.h"
 #include "content/browser/background_sync/background_sync_context.h"
+#include "content/browser/blob_storage/blob_url_loader_factory.h"
 #include "content/browser/bluetooth/bluetooth_allowed_devices_map.h"
 #include "content/browser/broadcast_channel/broadcast_channel_provider.h"
 #include "content/browser/cache_storage/cache_storage_context_impl.h"
@@ -41,6 +42,7 @@
 #endif
 
 namespace content {
+class BlobURLLoaderFactory;
 
 class CONTENT_EXPORT  StoragePartitionImpl
     : public StoragePartition,
@@ -116,6 +118,7 @@
   PaymentAppContextImpl* GetPaymentAppContext();
   BroadcastChannelProvider* GetBroadcastChannelProvider();
   BluetoothAllowedDevicesMap* GetBluetoothAllowedDevicesMap();
+  BlobURLLoaderFactory* GetBlobURLLoaderFactory();
 
   // mojom::StoragePartitionService interface.
   void OpenLocalStorage(
@@ -247,6 +250,7 @@
   scoped_refptr<PaymentAppContextImpl> payment_app_context_;
   scoped_refptr<BroadcastChannelProvider> broadcast_channel_provider_;
   scoped_refptr<BluetoothAllowedDevicesMap> bluetooth_allowed_devices_map_;
+  scoped_refptr<BlobURLLoaderFactory> blob_url_loader_factory_;
 
   mojo::BindingSet<mojom::StoragePartitionService> bindings_;
   mojom::NetworkContextPtr network_context_;
diff --git a/content/browser/url_loader_factory_getter.cc b/content/browser/url_loader_factory_getter.cc
index 611cef7..9f16c054 100644
--- a/content/browser/url_loader_factory_getter.cc
+++ b/content/browser/url_loader_factory_getter.cc
@@ -18,10 +18,14 @@
   partition->network_context()->CreateURLLoaderFactory(
       MakeRequest(&network_factory), 0);
 
+  mojom::URLLoaderFactoryPtr blob_factory =
+      partition->GetBlobURLLoaderFactory()->CreateFactory();
+
   BrowserThread::PostTask(
       BrowserThread::IO, FROM_HERE,
       base::BindOnce(&URLLoaderFactoryGetter::InitializeOnIOThread, this,
                      network_factory.PassInterface(),
+                     blob_factory.PassInterface(),
                      scoped_refptr<ChromeAppCacheService>(
                          partition->GetAppCacheService())));
 }
@@ -31,6 +35,11 @@
   return test_factory_.is_bound() ? &test_factory_ : &network_factory_;
 }
 
+mojom::URLLoaderFactoryPtr* URLLoaderFactoryGetter::GetBlobFactory() {
+  DCHECK_CURRENTLY_ON(BrowserThread::IO);
+  return &blob_factory_;
+}
+
 void URLLoaderFactoryGetter::SetNetworkFactoryForTesting(
     mojom::URLLoaderFactoryPtr test_factory) {
   // Since the URLLoaderFactory pointers are bound on the IO thread, and this
@@ -51,8 +60,10 @@
 
 void URLLoaderFactoryGetter::InitializeOnIOThread(
     mojom::URLLoaderFactoryPtrInfo network_factory,
+    mojom::URLLoaderFactoryPtrInfo blob_factory,
     scoped_refptr<ChromeAppCacheService> appcache_service) {
   network_factory_.Bind(std::move(network_factory));
+  blob_factory_.Bind(std::move(blob_factory));
 
   AppCacheURLLoaderFactory::CreateURLLoaderFactory(
       mojo::MakeRequest(&appcache_factory_), appcache_service.get(), this);
diff --git a/content/browser/url_loader_factory_getter.h b/content/browser/url_loader_factory_getter.h
index 1b1fb72a..0af16fd 100644
--- a/content/browser/url_loader_factory_getter.h
+++ b/content/browser/url_loader_factory_getter.h
@@ -33,6 +33,10 @@
   // The pointer shouldn't be cached.
   mojom::URLLoaderFactoryPtr* GetNetworkFactory();
 
+  // Called on the IO thread to get the URLLoaderFactory to the blob service.
+  // The pointer shouldn't be cached.
+  CONTENT_EXPORT mojom::URLLoaderFactoryPtr* GetBlobFactory();
+
   // Overrides the network URLLoaderFactory for subsequent requests. Passing a
   // null pointer will restore the default behavior.
   // This is called on the UI thread.
@@ -50,14 +54,16 @@
   CONTENT_EXPORT ~URLLoaderFactoryGetter();
   void InitializeOnIOThread(
       mojom::URLLoaderFactoryPtrInfo network_factory,
+      mojom::URLLoaderFactoryPtrInfo blob_factory,
       scoped_refptr<ChromeAppCacheService> appcache_service);
   void SetTestNetworkFactoryOnIOThread(
       mojom::URLLoaderFactoryPtrInfo test_factory);
 
   // Only accessed on IO thread.
   mojom::URLLoaderFactoryPtr network_factory_;
-  mojom::URLLoaderFactoryPtr test_factory_;
   mojom::URLLoaderFactoryPtr appcache_factory_;
+  mojom::URLLoaderFactoryPtr blob_factory_;
+  mojom::URLLoaderFactoryPtr test_factory_;
 
   DISALLOW_COPY_AND_ASSIGN(URLLoaderFactoryGetter);
 };
diff --git a/content/browser/webui/web_ui_url_loader_factory.cc b/content/browser/webui/web_ui_url_loader_factory.cc
index e85627c..a730bba 100644
--- a/content/browser/webui/web_ui_url_loader_factory.cc
+++ b/content/browser/webui/web_ui_url_loader_factory.cc
@@ -286,7 +286,7 @@
 
 }  // namespace
 
-mojom::URLLoaderFactoryPtr GetWebUIURLLoader(FrameTreeNode* node) {
+mojom::URLLoaderFactoryPtr CreateWebUIURLLoader(FrameTreeNode* node) {
   int ftn_id = node->frame_tree_node_id();
   if (g_factories.Get()[ftn_id].get() == nullptr)
     g_factories.Get()[ftn_id] = base::MakeUnique<WebUIURLLoaderFactory>(node);
diff --git a/content/browser/webui/web_ui_url_loader_factory.h b/content/browser/webui/web_ui_url_loader_factory.h
index cf085f51..b52d653 100644
--- a/content/browser/webui/web_ui_url_loader_factory.h
+++ b/content/browser/webui/web_ui_url_loader_factory.h
@@ -10,8 +10,8 @@
 namespace content {
 class FrameTreeNode;
 
-// Returns a URLLoaderFactory interface pointer for serving WebUI requests.
-mojom::URLLoaderFactoryPtr GetWebUIURLLoader(FrameTreeNode* node);
+// Creates a URLLoaderFactory interface pointer for serving WebUI requests.
+mojom::URLLoaderFactoryPtr CreateWebUIURLLoader(FrameTreeNode* node);
 
 }  // namespace content
 
diff --git a/content/common/BUILD.gn b/content/common/BUILD.gn
index f1bcce28..d5a4acb 100644
--- a/content/common/BUILD.gn
+++ b/content/common/BUILD.gn
@@ -240,6 +240,8 @@
     "net/url_request_service_worker_data.h",
     "net/url_request_user_data.cc",
     "net/url_request_user_data.h",
+    "net_adapters.cc",
+    "net_adapters.h",
     "origin_trials/trial_token.cc",
     "origin_trials/trial_token.h",
     "origin_trials/trial_token_validator.cc",
diff --git a/content/network/net_adapters.cc b/content/common/net_adapters.cc
similarity index 96%
rename from content/network/net_adapters.cc
rename to content/common/net_adapters.cc
index ee0bc33..462659a8 100644
--- a/content/network/net_adapters.cc
+++ b/content/common/net_adapters.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "content/network/net_adapters.h"
+#include "content/common/net_adapters.h"
 
 #include "net/base/net_errors.h"
 
diff --git a/content/network/net_adapters.h b/content/common/net_adapters.h
similarity index 95%
rename from content/network/net_adapters.h
rename to content/common/net_adapters.h
index ab6a496..9da1b21 100644
--- a/content/network/net_adapters.h
+++ b/content/common/net_adapters.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CONTENT_NETWORK_NET_ADAPTERS_
-#define CONTENT_NETWORK_NET_ADAPTERS_
+#ifndef CONTENT_COMMON_NET_ADAPTERS_
+#define CONTENT_COMMON_NET_ADAPTERS_
 
 #include <stdint.h>
 
@@ -68,4 +68,4 @@
 
 }  // namespace content
 
-#endif  // CONTENT_NETWORK_NET_ADAPTERS_
+#endif  // CONTENT_COMMON_NET_ADAPTERS_
diff --git a/content/network/BUILD.gn b/content/network/BUILD.gn
index 4031986..8f87a56 100644
--- a/content/network/BUILD.gn
+++ b/content/network/BUILD.gn
@@ -30,8 +30,6 @@
   sources = [
     "cache_url_loader.cc",
     "cache_url_loader.h",
-    "net_adapters.cc",
-    "net_adapters.h",
     "network_context.cc",
     "network_context.h",
     "network_service.cc",
diff --git a/content/network/DEPS b/content/network/DEPS
index 1960e889..bbad7c3 100644
--- a/content/network/DEPS
+++ b/content/network/DEPS
@@ -3,6 +3,7 @@
   "-content",
   "+content/common/content_export.h",
   "+content/common/resource_request.h",
+  "+content/common/net_adapters.h",
   "+content/common/network_service.mojom.h",
   "+content/common/url_loader.mojom.h",
   "+content/common/url_loader_factory.mojom.h",
diff --git a/content/network/url_loader_impl.cc b/content/network/url_loader_impl.cc
index 8d2143e..7d015c0 100644
--- a/content/network/url_loader_impl.cc
+++ b/content/network/url_loader_impl.cc
@@ -7,8 +7,8 @@
 #include "base/task_scheduler/post_task.h"
 #include "base/threading/thread_task_runner_handle.h"
 #include "base/time/time.h"
+#include "content/common/net_adapters.h"
 #include "content/common/url_loader_factory.mojom.h"
-#include "content/network/net_adapters.h"
 #include "content/network/network_context.h"
 #include "content/public/common/referrer.h"
 #include "content/public/common/resource_response.h"
diff --git a/content/test/BUILD.gn b/content/test/BUILD.gn
index ea57ae5..f756353 100644
--- a/content/test/BUILD.gn
+++ b/content/test/BUILD.gn
@@ -1073,6 +1073,7 @@
     "../browser/background_sync/background_sync_service_impl_unittest.cc",
     "../browser/blob_storage/blob_dispatcher_host_unittest.cc",
     "../browser/blob_storage/blob_transport_host_unittest.cc",
+    "../browser/blob_storage/blob_url_unittest.cc",
     "../browser/bluetooth/bluetooth_allowed_devices_unittest.cc",
     "../browser/bluetooth/bluetooth_blocklist_unittest.cc",
     "../browser/bluetooth/bluetooth_device_chooser_controller_unittest.cc",
diff --git a/storage/browser/BUILD.gn b/storage/browser/BUILD.gn
index c02bbea..d0160bf 100644
--- a/storage/browser/BUILD.gn
+++ b/storage/browser/BUILD.gn
@@ -236,7 +236,6 @@
     "blob/blob_storage_context_unittest.cc",
     "blob/blob_storage_registry_unittest.cc",
     "blob/blob_transport_request_builder_unittest.cc",
-    "blob/blob_url_request_job_unittest.cc",
     "database/database_quota_client_unittest.cc",
     "database/database_tracker_unittest.cc",
     "database/database_util_unittest.cc",
diff --git a/storage/browser/blob/blob_url_request_job.cc b/storage/browser/blob/blob_url_request_job.cc
index 8d7aaea..35cc474 100644
--- a/storage/browser/blob/blob_url_request_job.cc
+++ b/storage/browser/blob/blob_url_request_job.cc
@@ -143,6 +143,85 @@
   }
 }
 
+scoped_refptr<net::HttpResponseHeaders> BlobURLRequestJob::GenerateHeaders(
+    net::HttpStatusCode status_code,
+    BlobDataHandle* blob_handle,
+    BlobReader* blob_reader,
+    net::HttpByteRange* byte_range,
+    int64_t* content_size) {
+  std::string status("HTTP/1.1 ");
+  status.append(base::IntToString(status_code));
+  status.append(" ");
+  status.append(net::GetHttpReasonPhrase(status_code));
+  status.append("\0\0", 2);
+  scoped_refptr<net::HttpResponseHeaders> headers =
+      new net::HttpResponseHeaders(status);
+
+  if (status_code == net::HTTP_OK || status_code == net::HTTP_PARTIAL_CONTENT) {
+    *content_size = blob_reader->remaining_bytes();
+    std::string content_length_header(net::HttpRequestHeaders::kContentLength);
+    content_length_header.append(": ");
+    content_length_header.append(base::Int64ToString(*content_size));
+    headers->AddHeader(content_length_header);
+    if (status_code == net::HTTP_PARTIAL_CONTENT) {
+      DCHECK(byte_range->IsValid());
+      std::string content_range_header(net::HttpResponseHeaders::kContentRange);
+      content_range_header.append(": bytes ");
+      content_range_header.append(base::StringPrintf(
+          "%" PRId64 "-%" PRId64, byte_range->first_byte_position(),
+          byte_range->last_byte_position()));
+      content_range_header.append("/");
+      content_range_header.append(
+          base::StringPrintf("%" PRId64, blob_reader->total_size()));
+      headers->AddHeader(content_range_header);
+    }
+    if (!blob_handle->content_type().empty()) {
+      std::string content_type_header(net::HttpRequestHeaders::kContentType);
+      content_type_header.append(": ");
+      content_type_header.append(blob_handle->content_type());
+      headers->AddHeader(content_type_header);
+    }
+    if (!blob_handle->content_disposition().empty()) {
+      std::string content_disposition_header("Content-Disposition: ");
+      content_disposition_header.append(blob_handle->content_disposition());
+      headers->AddHeader(content_disposition_header);
+    }
+  }
+
+  return headers;
+}
+
+net::HttpStatusCode BlobURLRequestJob::NetErrorToHttpStatusCode(
+    int error_code) {
+  net::HttpStatusCode status_code = net::HTTP_INTERNAL_SERVER_ERROR;
+  switch (error_code) {
+    case net::ERR_ACCESS_DENIED:
+      status_code = net::HTTP_FORBIDDEN;
+      break;
+    case net::ERR_FILE_NOT_FOUND:
+      status_code = net::HTTP_NOT_FOUND;
+      break;
+    case net::ERR_METHOD_NOT_SUPPORTED:
+      status_code = net::HTTP_METHOD_NOT_ALLOWED;
+      break;
+    case net::ERR_REQUEST_RANGE_NOT_SATISFIABLE:
+      status_code = net::HTTP_REQUESTED_RANGE_NOT_SATISFIABLE;
+      break;
+    case net::ERR_INVALID_ARGUMENT:
+      status_code = net::HTTP_BAD_REQUEST;
+      break;
+    case net::ERR_CACHE_READ_FAILURE:
+    case net::ERR_CACHE_CHECKSUM_READ_FAILURE:
+    case net::ERR_UNEXPECTED:
+    case net::ERR_FAILED:
+      break;
+    default:
+      DCHECK(false) << "Error code not supported: " << error_code;
+      break;
+  }
+  return status_code;
+}
+
 BlobURLRequestJob::~BlobURLRequestJob() {
   TRACE_EVENT_ASYNC_END1("Blob", "BlobRequest", this, "uuid",
                          blob_handle_ ? blob_handle_->uuid() : "NotFound");
@@ -244,80 +323,16 @@
   // now. Instead, we just error out.
   DCHECK(!response_info_) << "Cannot NotifyFailure after headers.";
 
-  net::HttpStatusCode status_code = net::HTTP_INTERNAL_SERVER_ERROR;
-  switch (error_code) {
-    case net::ERR_ACCESS_DENIED:
-      status_code = net::HTTP_FORBIDDEN;
-      break;
-    case net::ERR_FILE_NOT_FOUND:
-      status_code = net::HTTP_NOT_FOUND;
-      break;
-    case net::ERR_METHOD_NOT_SUPPORTED:
-      status_code = net::HTTP_METHOD_NOT_ALLOWED;
-      break;
-    case net::ERR_REQUEST_RANGE_NOT_SATISFIABLE:
-      status_code = net::HTTP_REQUESTED_RANGE_NOT_SATISFIABLE;
-      break;
-    case net::ERR_INVALID_ARGUMENT:
-      status_code = net::HTTP_BAD_REQUEST;
-      break;
-    case net::ERR_CACHE_READ_FAILURE:
-    case net::ERR_CACHE_CHECKSUM_READ_FAILURE:
-    case net::ERR_UNEXPECTED:
-    case net::ERR_FAILED:
-      break;
-    default:
-      DCHECK(false) << "Error code not supported: " << error_code;
-      break;
-  }
-  HeadersCompleted(status_code);
+  HeadersCompleted(NetErrorToHttpStatusCode(error_code));
 }
 
 void BlobURLRequestJob::HeadersCompleted(net::HttpStatusCode status_code) {
-  std::string status("HTTP/1.1 ");
-  status.append(base::IntToString(status_code));
-  status.append(" ");
-  status.append(net::GetHttpReasonPhrase(status_code));
-  status.append("\0\0", 2);
-  net::HttpResponseHeaders* headers = new net::HttpResponseHeaders(status);
-
-  set_expected_content_size(0);
-
-  if (status_code == net::HTTP_OK || status_code == net::HTTP_PARTIAL_CONTENT) {
-    set_expected_content_size(blob_reader_->remaining_bytes());
-    std::string content_length_header(net::HttpRequestHeaders::kContentLength);
-    content_length_header.append(": ");
-    content_length_header.append(
-        base::Int64ToString(blob_reader_->remaining_bytes()));
-    headers->AddHeader(content_length_header);
-    if (status_code == net::HTTP_PARTIAL_CONTENT) {
-      DCHECK(byte_range_set_);
-      DCHECK(byte_range_.IsValid());
-      std::string content_range_header(net::HttpResponseHeaders::kContentRange);
-      content_range_header.append(": bytes ");
-      content_range_header.append(base::StringPrintf(
-          "%" PRId64 "-%" PRId64, byte_range_.first_byte_position(),
-          byte_range_.last_byte_position()));
-      content_range_header.append("/");
-      content_range_header.append(
-          base::StringPrintf("%" PRId64, blob_reader_->total_size()));
-      headers->AddHeader(content_range_header);
-    }
-    if (!blob_handle_->content_type().empty()) {
-      std::string content_type_header(net::HttpRequestHeaders::kContentType);
-      content_type_header.append(": ");
-      content_type_header.append(blob_handle_->content_type());
-      headers->AddHeader(content_type_header);
-    }
-    if (!blob_handle_->content_disposition().empty()) {
-      std::string content_disposition_header("Content-Disposition: ");
-      content_disposition_header.append(blob_handle_->content_disposition());
-      headers->AddHeader(content_disposition_header);
-    }
-  }
-
+  int64_t content_size = 0;
   response_info_.reset(new net::HttpResponseInfo());
-  response_info_->headers = headers;
+  response_info_->headers =
+      GenerateHeaders(status_code, blob_handle_.get(), blob_reader_.get(),
+                      &byte_range_, &content_size);
+  set_expected_content_size(content_size);
   if (blob_reader_)
     response_info_->metadata = blob_reader_->side_data();
 
diff --git a/storage/browser/blob/blob_url_request_job.h b/storage/browser/blob/blob_url_request_job.h
index 4d83d60..3eb264c 100644
--- a/storage/browser/blob/blob_url_request_job.h
+++ b/storage/browser/blob/blob_url_request_job.h
@@ -24,6 +24,7 @@
 }
 
 namespace net {
+class HttpResponseHeaders;
 class IOBuffer;
 }
 
@@ -51,6 +52,19 @@
   void GetResponseInfo(net::HttpResponseInfo* info) override;
   void SetExtraRequestHeaders(const net::HttpRequestHeaders& headers) override;
 
+  // Helper method to create the HTTP headers for the response.
+  // |blob_handles|, |blob_reader|, |byte_range| and |content_size| are only
+  // used if status_code isn't an error.
+  static scoped_refptr<net::HttpResponseHeaders> GenerateHeaders(
+      net::HttpStatusCode status_code,
+      BlobDataHandle* blob_handle,
+      BlobReader* blob_reader,
+      net::HttpByteRange* byte_range,
+      int64_t* content_size);
+
+  // Helper method to map from a net error to an http status code.
+  static net::HttpStatusCode NetErrorToHttpStatusCode(int error_code);
+
  protected:
   ~BlobURLRequestJob() override;
 
diff --git a/testing/buildbot/filters/mojo.fyi.network_content_browsertests.filter b/testing/buildbot/filters/mojo.fyi.network_content_browsertests.filter
index 25c1dd8..20ca034 100644
--- a/testing/buildbot/filters/mojo.fyi.network_content_browsertests.filter
+++ b/testing/buildbot/filters/mojo.fyi.network_content_browsertests.filter
@@ -3,7 +3,8 @@
 -ServiceWorker*
 
 # http://crbug.com/715677
--Blob*
+# Needs blob fetching for subresources
+-BlobStorageBrowserTest.BlobCombinations
 
 # http://crbug.com/715630
 -DownloadContentTest.*