Browse Source

feat: migrate webRequest module to NetworkService (#19979)

* feat: associate InProgressRequest with requests (#19648)

* feat: migrate webRequest module to NetworkService (Part 4) (#19679)

* chore: use gin in WebRequest

* Add stubs for APIs

* feat: migrate webRequest module to NetworkService (Part 5) (#19714)

* Pass WebRequest to ProxyingURLLoaderFactory

* Call WebRequestAPI in InProgressRequest

* Store the listeners

* Pass the request and response

* Add stub to handle the events

* Use extensions::WebRequestInfo

* Make sure webRequest is managed by Session

* chore: make creation of WebRequestNS more clear

* fix: check WebContents for service workers

* feat: migrate webRequest module to NetworkService (Part 6) (#19752)

* Implement OnBeforeSendHeaders

* Pass the request

* Handle simple listeners

* Handle response listeners

* Read responses from listener

* feat: migrate webRequest module to NetworkService (Part 7) (#19820)

* fix: gin treats Function as Dictionary when doing convertions

* fix: check if listener exists

* fix: listener callback should be executed in next tick

* feat: make InProgressRequest work

* test: re-enable protocol test that relies on webRequest

* chore: merge conditions

* feat: migrate webRequest module to NetworkService (Part 8) (#19841)

* fix: fill uploadData property

* fix: requestHeaders in onBeforeSendHeaders

* fix: responseHeaders in onHeadersReceived

* fix: header keys should not be lowercased

* fix: gin::Dictionary::Get succeeds even though key does not exist...

* fix: throw for invalid filters

* test: re-enable api-web-request-spec

* chore: do not use deprecated base::Value API

* feat: migrate webRequest module to NetworkService (Part 9) (#19976)

* no need to get WebContents for URLLoaderFactory

* consult embedder for network_factory created in net module

* set disable_web_security to false

* re-enable webRequest tests in net module
Robo 5 years ago
parent
commit
d3e0c461f6

+ 5 - 0
filenames.gni

@@ -512,6 +512,11 @@ filenames = {
     "shell/common/crash_reporter/linux/crash_dump_handler.h",
     "shell/common/crash_reporter/win/crash_service_main.cc",
     "shell/common/crash_reporter/win/crash_service_main.h",
+    "shell/common/gin_converters/callback_converter_gin_adapter.h",
+    "shell/common/gin_converters/gurl_converter_gin_adapter.h",
+    "shell/common/gin_converters/net_converter_gin_adapter.h",
+    "shell/common/gin_converters/std_converter.h",
+    "shell/common/gin_converters/value_converter_gin_adapter.h",
     "shell/common/gin_util.h",
     "shell/common/heap_snapshot.cc",
     "shell/common/heap_snapshot.h",

+ 3 - 1
shell/browser/api/atom_api_session.cc

@@ -244,8 +244,10 @@ Session::Session(v8::Isolate* isolate, AtomBrowserContext* browser_context)
 Session::~Session() {
   content::BrowserContext::GetDownloadManager(browser_context())
       ->RemoveObserver(this);
+  // TODO(zcbenz): Now since URLRequestContextGetter is gone, is this still
+  // needed?
+  // Refs https://github.com/electron/electron/pull/12305.
   DestroyGlobalHandle(isolate(), cookies_);
-  DestroyGlobalHandle(isolate(), web_request_);
   DestroyGlobalHandle(isolate(), protocol_);
   DestroyGlobalHandle(isolate(), net_log_);
   g_sessions.erase(weak_map_id());

+ 4 - 2
shell/browser/api/atom_api_session.h

@@ -101,12 +101,14 @@ class Session : public mate::TrackableObject<Session>,
                          download::DownloadItem* item) override;
 
  private:
-  // Cached object.
+  // Cached mate::Wrappable objects.
   v8::Global<v8::Value> cookies_;
   v8::Global<v8::Value> protocol_;
-  v8::Global<v8::Value> web_request_;
   v8::Global<v8::Value> net_log_;
 
+  // Cached object.
+  v8::Global<v8::Value> web_request_;
+
   // The client id to enable the network throttler.
   base::UnguessableToken network_emulation_token_;
 

+ 30 - 2
shell/browser/api/atom_api_url_request_ns.cc

@@ -10,7 +10,9 @@
 #include "mojo/public/cpp/system/string_data_source.h"
 #include "native_mate/dictionary.h"
 #include "native_mate/object_template_builder.h"
+#include "services/network/public/cpp/wrapper_shared_url_loader_factory.h"
 #include "shell/browser/api/atom_api_session.h"
+#include "shell/browser/atom_browser_client.h"
 #include "shell/browser/atom_browser_context.h"
 #include "shell/common/native_mate_converters/gurl_converter.h"
 #include "shell/common/native_mate_converters/net_converter.h"
@@ -178,9 +180,35 @@ URLRequestNS::URLRequestNS(mate::Arguments* args) : weak_factory_(this) {
   }
 
   auto* browser_context = session->browser_context();
+  auto* storage_partition =
+      content::BrowserContext::GetDefaultStoragePartition(browser_context);
+
+  network::mojom::URLLoaderFactoryPtr network_factory;
+  mojo::PendingReceiver<network::mojom::URLLoaderFactory> factory_request =
+      mojo::MakeRequest(&network_factory);
+
+  // Consult the embedder.
+  network::mojom::TrustedURLLoaderHeaderClientPtrInfo header_client;
+  static_cast<content::ContentBrowserClient*>(AtomBrowserClient::Get())
+      ->WillCreateURLLoaderFactory(browser_context, nullptr, -1,
+                                   true /* is_navigation */,
+                                   false /* is_download */, url::Origin(),
+                                   &factory_request, &header_client, nullptr);
+
+  network::mojom::URLLoaderFactoryParamsPtr params =
+      network::mojom::URLLoaderFactoryParams::New();
+  params->header_client = std::move(header_client);
+  params->process_id = network::mojom::kBrowserProcessId;
+  params->is_corb_enabled = false;
+  // The tests of net module would fail if this setting is true, it seems that
+  // the non-NetworkService implementation always has web security enabled.
+  params->disable_web_security = false;
+
+  storage_partition->GetNetworkContext()->CreateURLLoaderFactory(
+      std::move(factory_request), std::move(params));
   url_loader_factory_ =
-      content::BrowserContext::GetDefaultStoragePartition(browser_context)
-          ->GetURLLoaderFactoryForBrowserProcess();
+      base::MakeRefCounted<network::WrapperSharedURLLoaderFactory>(
+          std::move(network_factory));
 
   InitWith(args->isolate(), args->GetThis());
 }

+ 485 - 12
shell/browser/api/atom_api_web_request_ns.cc

@@ -4,33 +4,506 @@
 
 #include "shell/browser/api/atom_api_web_request_ns.h"
 
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "base/stl_util.h"
+#include "base/values.h"
+#include "gin/converter.h"
+#include "gin/dictionary.h"
+#include "gin/object_template_builder.h"
+#include "shell/browser/api/atom_api_session.h"
+#include "shell/browser/api/atom_api_web_contents.h"
 #include "shell/browser/atom_browser_context.h"
+#include "shell/common/gin_converters/callback_converter_gin_adapter.h"
+#include "shell/common/gin_converters/gurl_converter_gin_adapter.h"
+#include "shell/common/gin_converters/net_converter_gin_adapter.h"
+#include "shell/common/gin_converters/std_converter.h"
+#include "shell/common/gin_converters/value_converter_gin_adapter.h"
+
+namespace gin {
+
+template <>
+struct Converter<URLPattern> {
+  static bool FromV8(v8::Isolate* isolate,
+                     v8::Local<v8::Value> val,
+                     URLPattern* out) {
+    std::string pattern;
+    if (!ConvertFromV8(isolate, val, &pattern))
+      return false;
+    *out = URLPattern(URLPattern::SCHEME_ALL);
+    return out->Parse(pattern) == URLPattern::ParseResult::kSuccess;
+  }
+};
+
+template <>
+struct Converter<content::ResourceType> {
+  static v8::Local<v8::Value> ToV8(v8::Isolate* isolate,
+                                   content::ResourceType type) {
+    const char* result;
+    switch (type) {
+      case content::ResourceType::kMainFrame:
+        result = "mainFrame";
+        break;
+      case content::ResourceType::kSubFrame:
+        result = "subFrame";
+        break;
+      case content::ResourceType::kStylesheet:
+        result = "stylesheet";
+        break;
+      case content::ResourceType::kScript:
+        result = "script";
+        break;
+      case content::ResourceType::kImage:
+        result = "image";
+        break;
+      case content::ResourceType::kObject:
+        result = "object";
+        break;
+      case content::ResourceType::kXhr:
+        result = "xhr";
+        break;
+      default:
+        result = "other";
+    }
+    return StringToV8(isolate, result);
+  }
+};
+
+}  // namespace gin
 
 namespace electron {
 
 namespace api {
 
+namespace {
+
+const char* kUserDataKey = "WebRequestNS";
+
+// BrowserContext <=> WebRequestNS relationship.
+struct UserData : public base::SupportsUserData::Data {
+  explicit UserData(WebRequestNS* data) : data(data) {}
+  WebRequestNS* data;
+};
+
+// Test whether the URL of |request| matches |patterns|.
+bool MatchesFilterCondition(extensions::WebRequestInfo* info,
+                            const std::set<URLPattern>& patterns) {
+  if (patterns.empty())
+    return true;
+
+  for (const auto& pattern : patterns) {
+    if (pattern.MatchesURL(info->url))
+      return true;
+  }
+  return false;
+}
+
+// Convert HttpResponseHeaders to V8.
+//
+// Note that while we already have converters for HttpResponseHeaders, we can
+// not use it because it lowercases the header keys, while the webRequest has
+// to pass the original keys.
+v8::Local<v8::Value> HttpResponseHeadersToV8(
+    net::HttpResponseHeaders* headers) {
+  base::DictionaryValue response_headers;
+  if (headers) {
+    size_t iter = 0;
+    std::string key;
+    std::string value;
+    while (headers->EnumerateHeaderLines(&iter, &key, &value)) {
+      base::Value* values = response_headers.FindListKey(key);
+      if (!values)
+        values = response_headers.SetKey(key, base::ListValue());
+      values->GetList().emplace_back(value);
+    }
+  }
+  return gin::ConvertToV8(v8::Isolate::GetCurrent(), response_headers);
+}
+
+// Overloaded by multiple types to fill the |details| object.
+void ToDictionary(gin::Dictionary* details, extensions::WebRequestInfo* info) {
+  details->Set("id", info->id);
+  details->Set("url", info->url);
+  details->Set("method", info->method);
+  details->Set("timestamp", base::Time::Now().ToDoubleT() * 1000);
+  if (!info->response_ip.empty())
+    details->Set("ip", info->response_ip);
+  if (info->type.has_value())
+    details->Set("resourceType", info->type.value());
+  else
+    details->Set("resourceType", base::StringPiece("other"));
+  if (info->response_headers) {
+    details->Set("fromCache", info->response_from_cache);
+    details->Set("statusLine", info->response_headers->GetStatusLine());
+    details->Set("statusCode", info->response_headers->response_code());
+    details->Set("responseHeaders",
+                 HttpResponseHeadersToV8(info->response_headers.get()));
+  }
+
+  auto* web_contents = content::WebContents::FromRenderFrameHost(
+      content::RenderFrameHost::FromID(info->render_process_id,
+                                       info->frame_id));
+  int32_t id = api::WebContents::GetIDFromWrappedClass(web_contents);
+  // id must be greater than zero.
+  if (id > 0)
+    details->Set("webContentsId", id);
+}
+
+void ToDictionary(gin::Dictionary* details,
+                  const network::ResourceRequest& request) {
+  details->Set("referrer", request.referrer);
+  if (request.request_body)
+    details->Set("uploadData", *request.request_body);
+}
+
+void ToDictionary(gin::Dictionary* details,
+                  const net::HttpRequestHeaders& headers) {
+  details->Set("requestHeaders", headers);
+}
+
+void ToDictionary(gin::Dictionary* details, const GURL& location) {
+  details->Set("redirectURL", location);
+}
+
+void ToDictionary(gin::Dictionary* details, int net_error) {
+  details->Set("error", net::ErrorToString(net_error));
+}
+
+// Helper function to fill |details| with arbitrary |args|.
+template <typename Arg>
+void FillDetails(gin::Dictionary* details, Arg arg) {
+  ToDictionary(details, arg);
+}
+
+template <typename Arg, typename... Args>
+void FillDetails(gin::Dictionary* details, Arg arg, Args... args) {
+  ToDictionary(details, arg);
+  FillDetails(details, args...);
+}
+
+// Fill the native types with the result from the response object.
+void ReadFromResponse(v8::Isolate* isolate,
+                      gin::Dictionary* response,
+                      GURL* new_location) {
+  response->Get("redirectURL", new_location);
+}
+
+void ReadFromResponse(v8::Isolate* isolate,
+                      gin::Dictionary* response,
+                      net::HttpRequestHeaders* headers) {
+  headers->Clear();
+  response->Get("requestHeaders", headers);
+}
+
+void ReadFromResponse(v8::Isolate* isolate,
+                      gin::Dictionary* response,
+                      const std::pair<scoped_refptr<net::HttpResponseHeaders>*,
+                                      const std::string>& headers) {
+  std::string status_line;
+  if (!response->Get("statusLine", &status_line))
+    status_line = headers.second;
+  v8::Local<v8::Value> value;
+  if (response->Get("responseHeaders", &value) && value->IsObject()) {
+    *headers.first = new net::HttpResponseHeaders("");
+    (*headers.first)->ReplaceStatusLine(status_line);
+    mate::Converter<net::HttpResponseHeaders*>::FromV8(isolate, value,
+                                                       (*headers.first).get());
+  }
+}
+
+}  // namespace
+
+gin::WrapperInfo WebRequestNS::kWrapperInfo = {gin::kEmbedderNativeGin};
+
+WebRequestNS::SimpleListenerInfo::SimpleListenerInfo(
+    std::set<URLPattern> patterns_,
+    SimpleListener listener_)
+    : url_patterns(std::move(patterns_)), listener(listener_) {}
+WebRequestNS::SimpleListenerInfo::SimpleListenerInfo() = default;
+WebRequestNS::SimpleListenerInfo::~SimpleListenerInfo() = default;
+
+WebRequestNS::ResponseListenerInfo::ResponseListenerInfo(
+    std::set<URLPattern> patterns_,
+    ResponseListener listener_)
+    : url_patterns(std::move(patterns_)), listener(listener_) {}
+WebRequestNS::ResponseListenerInfo::ResponseListenerInfo() = default;
+WebRequestNS::ResponseListenerInfo::~ResponseListenerInfo() = default;
+
 WebRequestNS::WebRequestNS(v8::Isolate* isolate,
-                           AtomBrowserContext* browser_context) {
-  Init(isolate);
-  AttachAsUserData(browser_context);
+                           content::BrowserContext* browser_context)
+    : browser_context_(browser_context) {
+  browser_context_->SetUserData(kUserDataKey, std::make_unique<UserData>(this));
+}
+
+WebRequestNS::~WebRequestNS() {
+  browser_context_->RemoveUserData(kUserDataKey);
+}
+
+gin::ObjectTemplateBuilder WebRequestNS::GetObjectTemplateBuilder(
+    v8::Isolate* isolate) {
+  return gin::Wrappable<WebRequestNS>::GetObjectTemplateBuilder(isolate)
+      .SetMethod("onBeforeRequest",
+                 &WebRequestNS::SetResponseListener<kOnBeforeRequest>)
+      .SetMethod("onBeforeSendHeaders",
+                 &WebRequestNS::SetResponseListener<kOnBeforeSendHeaders>)
+      .SetMethod("onHeadersReceived",
+                 &WebRequestNS::SetResponseListener<kOnHeadersReceived>)
+      .SetMethod("onSendHeaders",
+                 &WebRequestNS::SetSimpleListener<kOnSendHeaders>)
+      .SetMethod("onBeforeRedirect",
+                 &WebRequestNS::SetSimpleListener<kOnBeforeRedirect>)
+      .SetMethod("onResponseStarted",
+                 &WebRequestNS::SetSimpleListener<kOnResponseStarted>)
+      .SetMethod("onErrorOccurred",
+                 &WebRequestNS::SetSimpleListener<kOnErrorOccurred>)
+      .SetMethod("onCompleted", &WebRequestNS::SetSimpleListener<kOnCompleted>);
 }
 
-WebRequestNS::~WebRequestNS() = default;
+const char* WebRequestNS::GetTypeName() {
+  return "WebRequest";
+}
+
+bool WebRequestNS::HasListener() const {
+  return !(simple_listeners_.empty() && response_listeners_.empty());
+}
+
+int WebRequestNS::OnBeforeRequest(extensions::WebRequestInfo* info,
+                                  const network::ResourceRequest& request,
+                                  net::CompletionOnceCallback callback,
+                                  GURL* new_url) {
+  return HandleResponseEvent(kOnBeforeRequest, info, std::move(callback),
+                             new_url, request);
+}
+
+int WebRequestNS::OnBeforeSendHeaders(extensions::WebRequestInfo* info,
+                                      const network::ResourceRequest& request,
+                                      BeforeSendHeadersCallback callback,
+                                      net::HttpRequestHeaders* headers) {
+  return HandleResponseEvent(
+      kOnBeforeSendHeaders, info,
+      base::BindOnce(std::move(callback), std::set<std::string>(),
+                     std::set<std::string>()),
+      headers, request, *headers);
+}
+
+int WebRequestNS::OnHeadersReceived(
+    extensions::WebRequestInfo* info,
+    const network::ResourceRequest& request,
+    net::CompletionOnceCallback callback,
+    const net::HttpResponseHeaders* original_response_headers,
+    scoped_refptr<net::HttpResponseHeaders>* override_response_headers,
+    GURL* allowed_unsafe_redirect_url) {
+  return HandleResponseEvent(
+      kOnHeadersReceived, info, std::move(callback),
+      std::make_pair(override_response_headers,
+                     original_response_headers->GetStatusLine()),
+      request);
+}
+
+void WebRequestNS::OnSendHeaders(extensions::WebRequestInfo* info,
+                                 const network::ResourceRequest& request,
+                                 const net::HttpRequestHeaders& headers) {
+  HandleSimpleEvent(kOnSendHeaders, info, request, headers);
+}
+
+void WebRequestNS::OnBeforeRedirect(extensions::WebRequestInfo* info,
+                                    const network::ResourceRequest& request,
+                                    const GURL& new_location) {
+  HandleSimpleEvent(kOnBeforeRedirect, info, request, new_location);
+}
+
+void WebRequestNS::OnResponseStarted(extensions::WebRequestInfo* info,
+                                     const network::ResourceRequest& request) {
+  HandleSimpleEvent(kOnResponseStarted, info, request);
+}
+
+void WebRequestNS::OnErrorOccurred(extensions::WebRequestInfo* info,
+                                   const network::ResourceRequest& request,
+                                   int net_error) {
+  callbacks_.erase(info->id);
+
+  HandleSimpleEvent(kOnErrorOccurred, info, request, net_error);
+}
+
+void WebRequestNS::OnCompleted(extensions::WebRequestInfo* info,
+                               const network::ResourceRequest& request,
+                               int net_error) {
+  callbacks_.erase(info->id);
+
+  HandleSimpleEvent(kOnCompleted, info, request, net_error);
+}
+
+template <WebRequestNS::SimpleEvent event>
+void WebRequestNS::SetSimpleListener(gin::Arguments* args) {
+  SetListener<SimpleListener>(event, &simple_listeners_, args);
+}
+
+template <WebRequestNS::ResponseEvent event>
+void WebRequestNS::SetResponseListener(gin::Arguments* args) {
+  SetListener<ResponseListener>(event, &response_listeners_, args);
+}
+
+template <typename Listener, typename Listeners, typename Event>
+void WebRequestNS::SetListener(Event event,
+                               Listeners* listeners,
+                               gin::Arguments* args) {
+  v8::Local<v8::Value> arg;
+
+  // { urls }.
+  std::set<std::string> filter_patterns;
+  gin::Dictionary dict(args->isolate());
+  if (args->GetNext(&arg) && !arg->IsFunction()) {
+    // Note that gin treats Function as Dictionary when doing convertions, so we
+    // have to explicitly check if the argument is Function before trying to
+    // convert it to Dictionary.
+    if (gin::ConvertFromV8(args->isolate(), arg, &dict)) {
+      if (!dict.Get("urls", &filter_patterns)) {
+        args->ThrowTypeError("Parameter 'filter' must have property 'urls'.");
+        return;
+      }
+      args->GetNext(&arg);
+    }
+  }
+
+  std::set<URLPattern> patterns;
+  for (const std::string& filter_pattern : filter_patterns) {
+    URLPattern pattern(URLPattern::SCHEME_ALL);
+    const URLPattern::ParseResult result = pattern.Parse(filter_pattern);
+    if (result == URLPattern::ParseResult::kSuccess) {
+      patterns.insert(pattern);
+    } else {
+      const char* error_type = URLPattern::GetParseResultString(result);
+      args->ThrowTypeError("Invalid url pattern " + filter_pattern + ": " +
+                           error_type);
+      return;
+    }
+  }
+
+  // Function or null.
+  Listener listener;
+  if (arg.IsEmpty() ||
+      !(gin::ConvertFromV8(args->isolate(), arg, &listener) || arg->IsNull())) {
+    args->ThrowTypeError("Must pass null or a Function");
+    return;
+  }
+
+  if (listener.is_null())
+    listeners->erase(event);
+  else
+    (*listeners)[event] = {std::move(patterns), std::move(listener)};
+}
+
+template <typename... Args>
+void WebRequestNS::HandleSimpleEvent(SimpleEvent event,
+                                     extensions::WebRequestInfo* request_info,
+                                     Args... args) {
+  if (!base::Contains(simple_listeners_, event))
+    return;
+
+  const auto& info = simple_listeners_[event];
+  if (!MatchesFilterCondition(request_info, info.url_patterns))
+    return;
+
+  v8::Isolate* isolate = v8::Isolate::GetCurrent();
+  v8::HandleScope handle_scope(isolate);
+  gin::Dictionary details(isolate, v8::Object::New(isolate));
+  FillDetails(&details, request_info, args...);
+  info.listener.Run(gin::ConvertToV8(isolate, details));
+}
+
+template <typename Out, typename... Args>
+int WebRequestNS::HandleResponseEvent(ResponseEvent event,
+                                      extensions::WebRequestInfo* request_info,
+                                      net::CompletionOnceCallback callback,
+                                      Out out,
+                                      Args... args) {
+  if (!base::Contains(response_listeners_, event))
+    return net::OK;
+
+  const auto& info = response_listeners_[event];
+  if (!MatchesFilterCondition(request_info, info.url_patterns))
+    return net::OK;
+
+  callbacks_[request_info->id] = std::move(callback);
+
+  v8::Isolate* isolate = v8::Isolate::GetCurrent();
+  v8::HandleScope handle_scope(isolate);
+  gin::Dictionary details(isolate, v8::Object::New(isolate));
+  FillDetails(&details, request_info, args...);
+
+  ResponseCallback response =
+      base::BindOnce(&WebRequestNS::OnListenerResult<Out>,
+                     base::Unretained(this), request_info->id, out);
+  info.listener.Run(gin::ConvertToV8(isolate, details), std::move(response));
+  return net::ERR_IO_PENDING;
+}
+
+template <typename T>
+void WebRequestNS::OnListenerResult(uint64_t id,
+                                    T out,
+                                    v8::Local<v8::Value> response) {
+  if (!base::Contains(callbacks_, id))
+    return;
+
+  int result = net::OK;
+  if (response->IsObject()) {
+    v8::Isolate* isolate = v8::Isolate::GetCurrent();
+    gin::Dictionary dict(isolate, response.As<v8::Object>());
+
+    bool cancel = false;
+    dict.Get("cancel", &cancel);
+    if (cancel)
+      result = net::ERR_BLOCKED_BY_CLIENT;
+    else
+      ReadFromResponse(isolate, &dict, out);
+  }
+
+  // The ProxyingURLLoaderFactory expects the callback to be executed
+  // asynchronously, because it used to work on IO thread before NetworkService.
+  base::SequencedTaskRunnerHandle::Get()->PostTask(
+      FROM_HERE, base::BindOnce(std::move(callbacks_[id]), result));
+  callbacks_.erase(id);
+}
 
 // static
-mate::Handle<WebRequestNS> WebRequestNS::Create(
+gin::Handle<WebRequestNS> WebRequestNS::FromOrCreate(
     v8::Isolate* isolate,
-    AtomBrowserContext* browser_context) {
-  return mate::CreateHandle(isolate,
-                            new WebRequestNS(isolate, browser_context));
+    content::BrowserContext* browser_context) {
+  gin::Handle<WebRequestNS> handle = From(isolate, browser_context);
+  if (handle.IsEmpty()) {
+    // Make sure the |Session| object has the |webRequest| property created.
+    v8::Local<v8::Value> web_request =
+        Session::CreateFrom(isolate,
+                            static_cast<AtomBrowserContext*>(browser_context))
+            ->WebRequest(isolate);
+    gin::ConvertFromV8(isolate, web_request, &handle);
+  }
+  DCHECK(!handle.IsEmpty());
+  return handle;
 }
 
 // static
-void WebRequestNS::BuildPrototype(v8::Isolate* isolate,
-                                  v8::Local<v8::FunctionTemplate> prototype) {
-  prototype->SetClassName(mate::StringToV8(isolate, "WebRequest"));
-  mate::ObjectTemplateBuilder(isolate, prototype->PrototypeTemplate());
+gin::Handle<WebRequestNS> WebRequestNS::Create(
+    v8::Isolate* isolate,
+    content::BrowserContext* browser_context) {
+  DCHECK(From(isolate, browser_context).IsEmpty())
+      << "WebRequestNS already created";
+  return gin::CreateHandle(isolate, new WebRequestNS(isolate, browser_context));
+}
+
+// static
+gin::Handle<WebRequestNS> WebRequestNS::From(
+    v8::Isolate* isolate,
+    content::BrowserContext* browser_context) {
+  if (!browser_context)
+    return gin::Handle<WebRequestNS>();
+  auto* user_data =
+      static_cast<UserData*>(browser_context->GetUserData(kUserDataKey));
+  if (!user_data)
+    return gin::Handle<WebRequestNS>();
+  return gin::CreateHandle(isolate, user_data->data);
 }
 
 }  // namespace api

+ 134 - 9
shell/browser/api/atom_api_web_request_ns.h

@@ -5,27 +5,152 @@
 #ifndef SHELL_BROWSER_API_ATOM_API_WEB_REQUEST_NS_H_
 #define SHELL_BROWSER_API_ATOM_API_WEB_REQUEST_NS_H_
 
+#include <map>
+#include <set>
+
+#include "base/values.h"
+#include "extensions/common/url_pattern.h"
+#include "gin/arguments.h"
+#include "gin/handle.h"
+#include "gin/wrappable.h"
 #include "native_mate/dictionary.h"
 #include "native_mate/handle.h"
-#include "shell/browser/api/trackable_object.h"
+#include "shell/browser/net/proxying_url_loader_factory.h"
 
-namespace electron {
+namespace content {
+class BrowserContext;
+}
 
-class AtomBrowserContext;
+namespace electron {
 
 namespace api {
 
-class WebRequestNS : public mate::TrackableObject<WebRequestNS> {
+class WebRequestNS : public gin::Wrappable<WebRequestNS>, public WebRequestAPI {
  public:
-  static mate::Handle<WebRequestNS> Create(v8::Isolate* isolate,
-                                           AtomBrowserContext* browser_context);
+  static gin::WrapperInfo kWrapperInfo;
+
+  // Return the WebRequest object attached to |browser_context|, create if there
+  // is no one.
+  // Note that the lifetime of WebRequest object is managed by Session, instead
+  // of the caller.
+  static gin::Handle<WebRequestNS> FromOrCreate(
+      v8::Isolate* isolate,
+      content::BrowserContext* browser_context);
+
+  // Return a new WebRequest object, this should only be called by Session.
+  static gin::Handle<WebRequestNS> Create(
+      v8::Isolate* isolate,
+      content::BrowserContext* browser_context);
 
-  static void BuildPrototype(v8::Isolate* isolate,
-                             v8::Local<v8::FunctionTemplate> prototype);
+  // Find the WebRequest object attached to |browser_context|.
+  static gin::Handle<WebRequestNS> From(
+      v8::Isolate* isolate,
+      content::BrowserContext* browser_context);
+
+  // gin::Wrappable:
+  gin::ObjectTemplateBuilder GetObjectTemplateBuilder(
+      v8::Isolate* isolate) override;
+  const char* GetTypeName() override;
 
  private:
-  WebRequestNS(v8::Isolate* isolate, AtomBrowserContext* browser_context);
+  WebRequestNS(v8::Isolate* isolate, content::BrowserContext* browser_context);
   ~WebRequestNS() override;
+
+  // WebRequestAPI:
+  bool HasListener() const override;
+  int OnBeforeRequest(extensions::WebRequestInfo* info,
+                      const network::ResourceRequest& request,
+                      net::CompletionOnceCallback callback,
+                      GURL* new_url) override;
+  int OnBeforeSendHeaders(extensions::WebRequestInfo* info,
+                          const network::ResourceRequest& request,
+                          BeforeSendHeadersCallback callback,
+                          net::HttpRequestHeaders* headers) override;
+  int OnHeadersReceived(
+      extensions::WebRequestInfo* info,
+      const network::ResourceRequest& request,
+      net::CompletionOnceCallback callback,
+      const net::HttpResponseHeaders* original_response_headers,
+      scoped_refptr<net::HttpResponseHeaders>* override_response_headers,
+      GURL* allowed_unsafe_redirect_url) override;
+  void OnSendHeaders(extensions::WebRequestInfo* info,
+                     const network::ResourceRequest& request,
+                     const net::HttpRequestHeaders& headers) override;
+  void OnBeforeRedirect(extensions::WebRequestInfo* info,
+                        const network::ResourceRequest& request,
+                        const GURL& new_location) override;
+  void OnResponseStarted(extensions::WebRequestInfo* info,
+                         const network::ResourceRequest& request) override;
+  void OnErrorOccurred(extensions::WebRequestInfo* info,
+                       const network::ResourceRequest& request,
+                       int net_error) override;
+  void OnCompleted(extensions::WebRequestInfo* info,
+                   const network::ResourceRequest& request,
+                   int net_error) override;
+
+  enum SimpleEvent {
+    kOnSendHeaders,
+    kOnBeforeRedirect,
+    kOnResponseStarted,
+    kOnCompleted,
+    kOnErrorOccurred,
+  };
+  enum ResponseEvent {
+    kOnBeforeRequest,
+    kOnBeforeSendHeaders,
+    kOnHeadersReceived,
+  };
+
+  using SimpleListener = base::RepeatingCallback<void(v8::Local<v8::Value>)>;
+  using ResponseCallback = base::OnceCallback<void(v8::Local<v8::Value>)>;
+  using ResponseListener =
+      base::RepeatingCallback<void(v8::Local<v8::Value>, ResponseCallback)>;
+
+  template <SimpleEvent event>
+  void SetSimpleListener(gin::Arguments* args);
+  template <ResponseEvent event>
+  void SetResponseListener(gin::Arguments* args);
+  template <typename Listener, typename Listeners, typename Event>
+  void SetListener(Event event, Listeners* listeners, gin::Arguments* args);
+
+  template <typename... Args>
+  void HandleSimpleEvent(SimpleEvent event,
+                         extensions::WebRequestInfo* info,
+                         Args... args);
+  template <typename Out, typename... Args>
+  int HandleResponseEvent(ResponseEvent event,
+                          extensions::WebRequestInfo* info,
+                          net::CompletionOnceCallback callback,
+                          Out out,
+                          Args... args);
+
+  template <typename T>
+  void OnListenerResult(uint64_t id, T out, v8::Local<v8::Value> response);
+
+  struct SimpleListenerInfo {
+    std::set<URLPattern> url_patterns;
+    SimpleListener listener;
+
+    SimpleListenerInfo(std::set<URLPattern>, SimpleListener);
+    SimpleListenerInfo();
+    ~SimpleListenerInfo();
+  };
+
+  struct ResponseListenerInfo {
+    std::set<URLPattern> url_patterns;
+    ResponseListener listener;
+
+    ResponseListenerInfo(std::set<URLPattern>, ResponseListener);
+    ResponseListenerInfo();
+    ~ResponseListenerInfo();
+  };
+
+  std::map<SimpleEvent, SimpleListenerInfo> simple_listeners_;
+  std::map<ResponseEvent, ResponseListenerInfo> response_listeners_;
+  std::map<uint64_t, net::CompletionOnceCallback> callbacks_;
+
+  // Weak-ref, it manages us.
+  content::BrowserContext* browser_context_;
 };
 
 }  // namespace api

+ 10 - 8
shell/browser/atom_browser_client.cc

@@ -54,6 +54,7 @@
 #include "shell/browser/api/atom_api_protocol_ns.h"
 #include "shell/browser/api/atom_api_session.h"
 #include "shell/browser/api/atom_api_web_contents.h"
+#include "shell/browser/api/atom_api_web_request_ns.h"
 #include "shell/browser/atom_browser_context.h"
 #include "shell/browser/atom_browser_main_parts.h"
 #include "shell/browser/atom_navigation_throttle.h"
@@ -1014,12 +1015,12 @@ bool AtomBrowserClient::WillCreateURLLoaderFactory(
     mojo::PendingReceiver<network::mojom::URLLoaderFactory>* factory_receiver,
     network::mojom::TrustedURLLoaderHeaderClientPtrInfo* header_client,
     bool* bypass_redirect_checks) {
-  content::WebContents* web_contents =
-      content::WebContents::FromRenderFrameHost(frame_host);
-  api::ProtocolNS* protocol = api::ProtocolNS::FromWrappedClass(
-      v8::Isolate::GetCurrent(), web_contents->GetBrowserContext());
-  if (!protocol)
-    return false;
+  v8::Isolate* isolate = v8::Isolate::GetCurrent();
+  api::ProtocolNS* protocol =
+      api::ProtocolNS::FromWrappedClass(isolate, browser_context);
+  DCHECK(protocol);
+  auto web_request = api::WebRequestNS::FromOrCreate(isolate, browser_context);
+  DCHECK(web_request.get());
 
   auto proxied_receiver = std::move(*factory_receiver);
   network::mojom::URLLoaderFactoryPtrInfo target_factory_info;
@@ -1030,8 +1031,9 @@ bool AtomBrowserClient::WillCreateURLLoaderFactory(
     header_client_request = mojo::MakeRequest(header_client);
 
   new ProxyingURLLoaderFactory(
-      protocol->intercept_handlers(), std::move(proxied_receiver),
-      std::move(target_factory_info), std::move(header_client_request));
+      web_request.get(), protocol->intercept_handlers(), render_process_id,
+      std::move(proxied_receiver), std::move(target_factory_info),
+      std::move(header_client_request));
 
   *bypass_redirect_checks = true;
   return true;

+ 112 - 26
shell/browser/net/proxying_url_loader_factory.cc

@@ -6,6 +6,7 @@
 
 #include <utility>
 
+#include "extensions/browser/extension_navigation_ui_data.h"
 #include "mojo/public/cpp/bindings/binding.h"
 #include "net/base/completion_repeating_callback.h"
 #include "net/http/http_util.h"
@@ -14,6 +15,12 @@
 
 namespace electron {
 
+namespace {
+
+int64_t g_request_id = 0;
+
+}  // namespace
+
 ProxyingURLLoaderFactory::InProgressRequest::FollowRedirectParams::
     FollowRedirectParams() = default;
 ProxyingURLLoaderFactory::InProgressRequest::FollowRedirectParams::
@@ -21,6 +28,7 @@ ProxyingURLLoaderFactory::InProgressRequest::FollowRedirectParams::
 
 ProxyingURLLoaderFactory::InProgressRequest::InProgressRequest(
     ProxyingURLLoaderFactory* factory,
+    int64_t web_request_id,
     int32_t routing_id,
     int32_t network_service_request_id,
     uint32_t options,
@@ -31,6 +39,7 @@ ProxyingURLLoaderFactory::InProgressRequest::InProgressRequest(
     : factory_(factory),
       request_(request),
       original_initiator_(request.request_initiator),
+      request_id_(web_request_id),
       routing_id_(routing_id),
       network_service_request_id_(network_service_request_id),
       options_(options),
@@ -49,7 +58,9 @@ ProxyingURLLoaderFactory::InProgressRequest::InProgressRequest(
       network::URLLoaderCompletionStatus(net::ERR_ABORTED)));
 }
 
-ProxyingURLLoaderFactory::InProgressRequest::~InProgressRequest() {}
+ProxyingURLLoaderFactory::InProgressRequest::~InProgressRequest() {
+  // TODO(zcbenz): Do cleanup here.
+}
 
 void ProxyingURLLoaderFactory::InProgressRequest::Restart() {
   UpdateRequestInfo();
@@ -57,13 +68,27 @@ void ProxyingURLLoaderFactory::InProgressRequest::Restart() {
 }
 
 void ProxyingURLLoaderFactory::InProgressRequest::UpdateRequestInfo() {
+  // Derive a new WebRequestInfo value any time |Restart()| is called, because
+  // the details in |request_| may have changed e.g. if we've been redirected.
+  // |request_initiator| can be modified on redirects, but we keep the original
+  // for |initiator| in the event. See also
+  // https://developer.chrome.com/extensions/webRequest#event-onBeforeRequest.
+  network::ResourceRequest request_for_info = request_;
+  request_for_info.request_initiator = original_initiator_;
+  info_.emplace(extensions::WebRequestInfoInitParams(
+      request_id_, factory_->render_process_id_, request_.render_frame_id,
+      nullptr, routing_id_, request_for_info, false,
+      !(options_ & network::mojom::kURLLoadOptionSynchronous)));
+
   current_request_uses_header_client_ =
       factory_->url_loader_header_client_binding_ &&
       network_service_request_id_ != 0 &&
-      false /* HasExtraHeadersListenerForRequest */;
+      false /* TODO(zcbenz): HasExtraHeadersListenerForRequest */;
 }
 
 void ProxyingURLLoaderFactory::InProgressRequest::RestartInternal() {
+  DCHECK_EQ(info_->url, request_.url)
+      << "UpdateRequestInfo must have been called first";
   request_completed_ = false;
 
   // If the header client will be used, we start the request immediately, and
@@ -79,8 +104,8 @@ void ProxyingURLLoaderFactory::InProgressRequest::RestartInternal() {
                             weak_factory_.GetWeakPtr());
   }
   redirect_url_ = GURL();
-  // TODO(zcbenz): Call webRequest.onBeforeRequest.
-  int result = net::OK;
+  int result = factory_->web_request_api()->OnBeforeRequest(
+      &info_.value(), request_, continuation, &redirect_url_);
   if (result == net::ERR_BLOCKED_BY_CLIENT) {
     // The request was cancelled synchronously. Dispatch an error notification
     // and terminate the request.
@@ -239,10 +264,11 @@ void ProxyingURLLoaderFactory::InProgressRequest::OnComplete(
   }
 
   target_client_->OnComplete(status);
-  // TODO(zcbenz): Call webRequest.onCompleted.
+  factory_->web_request_api()->OnCompleted(&info_.value(), request_,
+                                           status.error_code);
 
-  // TODO(zcbenz): Disassociate from factory.
-  delete this;
+  // Deletes |this|.
+  factory_->RemoveRequest(network_service_request_id_, request_id_);
 }
 
 void ProxyingURLLoaderFactory::InProgressRequest::OnLoaderCreated(
@@ -297,8 +323,8 @@ void ProxyingURLLoaderFactory::InProgressRequest::ContinueToBeforeSendHeaders(
   auto continuation = base::BindRepeating(
       &InProgressRequest::ContinueToSendHeaders, weak_factory_.GetWeakPtr());
   // Note: In Electron onBeforeSendHeaders is called for all protocols.
-  // TODO(zcbenz): Call webRequest.onBeforeSendHeaders.
-  int result = net::OK;
+  int result = factory_->web_request_api()->OnBeforeSendHeaders(
+      &info_.value(), request_, continuation, &request_.headers);
 
   if (result == net::ERR_BLOCKED_BY_CLIENT) {
     // The request was cancelled synchronously. Dispatch an error notification
@@ -365,7 +391,8 @@ void ProxyingURLLoaderFactory::InProgressRequest::ContinueToSendHeaders(
     proxied_client_binding_.ResumeIncomingMethodCallProcessing();
 
   // Note: In Electron onSendHeaders is called for all protocols.
-  // TODO(zcbenz): Call webRequest.onSendHeaders.
+  factory_->web_request_api()->OnSendHeaders(&info_.value(), request_,
+                                             request_.headers);
 
   if (!current_request_uses_header_client_)
     ContinueToStartRequest(net::OK);
@@ -474,9 +501,11 @@ void ProxyingURLLoaderFactory::InProgressRequest::ContinueToResponseStarted(
     return;
   }
 
+  info_->AddResponseInfoFromResourceResponse(current_response_);
+
   proxied_client_binding_.ResumeIncomingMethodCallProcessing();
 
-  // TODO(zcbenz): Call webRequest.onResponseStarted.
+  factory_->web_request_api()->OnResponseStarted(&info_.value(), request_);
   target_client_->OnReceiveResponse(current_response_);
 }
 
@@ -488,10 +517,13 @@ void ProxyingURLLoaderFactory::InProgressRequest::ContinueToBeforeRedirect(
     return;
   }
 
+  info_->AddResponseInfoFromResourceResponse(current_response_);
+
   if (proxied_client_binding_.is_bound())
     proxied_client_binding_.ResumeIncomingMethodCallProcessing();
 
-  // TODO(zcbenz): Call WebRequest.onBeforeRedirect.
+  factory_->web_request_api()->OnBeforeRedirect(&info_.value(), request_,
+                                                redirect_info.new_url);
   target_client_->OnReceiveRedirect(redirect_info, current_response_);
   request_.url = redirect_info.new_url;
   request_.method = redirect_info.new_method;
@@ -581,10 +613,14 @@ void ProxyingURLLoaderFactory::InProgressRequest::
   override_headers_ = nullptr;
   redirect_url_ = GURL();
 
+  info_->AddResponseInfoFromResourceResponse(current_response_);
+
   net::CompletionRepeatingCallback copyable_callback =
       base::AdaptCallbackForRepeating(std::move(continuation));
-  // TODO(zcbenz): Call webRequest.onHeadersReceived.
-  int result = net::OK;
+  DCHECK(info_.has_value());
+  int result = factory_->web_request_api()->OnHeadersReceived(
+      &info_.value(), request_, copyable_callback,
+      current_response_.headers.get(), &override_headers_, &redirect_url_);
   if (result == net::ERR_BLOCKED_BY_CLIENT) {
     OnRequestError(network::URLLoaderCompletionStatus(result));
     return;
@@ -609,19 +645,24 @@ void ProxyingURLLoaderFactory::InProgressRequest::OnRequestError(
     const network::URLLoaderCompletionStatus& status) {
   if (!request_completed_) {
     target_client_->OnComplete(status);
-    // TODO(zcbenz): Call webRequest.onErrorOccurred.
+    factory_->web_request_api()->OnErrorOccurred(&info_.value(), request_,
+                                                 status.error_code);
   }
 
-  // TODO(zcbenz): Disassociate from factory.
-  delete this;
+  // Deletes |this|.
+  factory_->RemoveRequest(network_service_request_id_, request_id_);
 }
 
 ProxyingURLLoaderFactory::ProxyingURLLoaderFactory(
+    WebRequestAPI* web_request_api,
     const HandlersMap& intercepted_handlers,
+    int render_process_id,
     network::mojom::URLLoaderFactoryRequest loader_request,
     network::mojom::URLLoaderFactoryPtrInfo target_factory_info,
     network::mojom::TrustedURLLoaderHeaderClientRequest header_client_request)
-    : intercepted_handlers_(intercepted_handlers),
+    : web_request_api_(web_request_api),
+      intercepted_handlers_(intercepted_handlers),
+      render_process_id_(render_process_id),
       url_loader_header_client_binding_(this) {
   target_factory_.Bind(std::move(target_factory_info));
   target_factory_.set_connection_error_handler(base::BindOnce(
@@ -663,12 +704,29 @@ void ProxyingURLLoaderFactory::CreateLoaderAndStart(
     return;
   }
 
-  // Pass-through to the original factory.
-  target_factory_->CreateLoaderAndStart(std::move(loader), routing_id,
-                                        request_id, options, request,
-                                        std::move(client), traffic_annotation);
+  if (!web_request_api()->HasListener()) {
+    // Pass-through to the original factory.
+    target_factory_->CreateLoaderAndStart(
+        std::move(loader), routing_id, request_id, options, request,
+        std::move(client), traffic_annotation);
+    return;
+  }
+
+  // The request ID doesn't really matter. It just needs to be unique
+  // per-BrowserContext so extensions can make sense of it.  Note that
+  // |network_service_request_id_| by contrast is not necessarily unique, so we
+  // don't use it for identity here.
+  const uint64_t web_request_id = ++g_request_id;
+
+  if (request_id)
+    network_request_id_to_web_request_id_.emplace(request_id, web_request_id);
 
-  // TODO(zcbenz): Create InProgressRequest.
+  auto result = requests_.emplace(
+      web_request_id,
+      std::make_unique<InProgressRequest>(
+          this, web_request_id, routing_id, request_id, options, request,
+          traffic_annotation, std::move(loader), std::move(client)));
+  result.first->second->Restart();
 }
 
 void ProxyingURLLoaderFactory::Clone(
@@ -679,16 +737,44 @@ void ProxyingURLLoaderFactory::Clone(
 void ProxyingURLLoaderFactory::OnLoaderCreated(
     int32_t request_id,
     network::mojom::TrustedHeaderClientRequest request) {
-  // TODO(zcbenz): Call |OnLoaderCreated| for |InProgressRequest|.
+  auto it = network_request_id_to_web_request_id_.find(request_id);
+  if (it == network_request_id_to_web_request_id_.end())
+    return;
+
+  auto request_it = requests_.find(it->second);
+  DCHECK(request_it != requests_.end());
+  request_it->second->OnLoaderCreated(std::move(request));
 }
 
 void ProxyingURLLoaderFactory::OnTargetFactoryError() {
-  delete this;
+  target_factory_.reset();
+  proxy_bindings_.CloseAllBindings();
+
+  MaybeDeleteThis();
 }
 
 void ProxyingURLLoaderFactory::OnProxyBindingError() {
   if (proxy_bindings_.empty())
-    delete this;
+    target_factory_.reset();
+
+  MaybeDeleteThis();
+}
+
+void ProxyingURLLoaderFactory::RemoveRequest(int32_t network_service_request_id,
+                                             uint64_t request_id) {
+  network_request_id_to_web_request_id_.erase(network_service_request_id);
+  requests_.erase(request_id);
+
+  MaybeDeleteThis();
+}
+
+void ProxyingURLLoaderFactory::MaybeDeleteThis() {
+  // Even if all URLLoaderFactory pipes connected to this object have been
+  // closed it has to stay alive until all active requests have completed.
+  if (target_factory_.is_bound() || !requests_.empty())
+    return;
+
+  delete this;
 }
 
 }  // namespace electron

+ 66 - 0
shell/browser/net/proxying_url_loader_factory.h

@@ -5,12 +5,14 @@
 #ifndef SHELL_BROWSER_NET_PROXYING_URL_LOADER_FACTORY_H_
 #define SHELL_BROWSER_NET_PROXYING_URL_LOADER_FACTORY_H_
 
+#include <map>
 #include <memory>
 #include <set>
 #include <string>
 #include <vector>
 
 #include "base/optional.h"
+#include "extensions/browser/api/web_request/web_request_info.h"
 #include "services/network/public/cpp/resource_request.h"
 #include "services/network/public/cpp/resource_response.h"
 #include "services/network/public/mojom/network_context.mojom.h"
@@ -19,6 +21,48 @@
 
 namespace electron {
 
+// Defines the interface for WebRequest API, implemented by api::WebRequestNS.
+class WebRequestAPI {
+ public:
+  virtual ~WebRequestAPI() {}
+
+  using BeforeSendHeadersCallback =
+      base::OnceCallback<void(const std::set<std::string>& removed_headers,
+                              const std::set<std::string>& set_headers,
+                              int error_code)>;
+
+  virtual bool HasListener() const = 0;
+  virtual int OnBeforeRequest(extensions::WebRequestInfo* info,
+                              const network::ResourceRequest& request,
+                              net::CompletionOnceCallback callback,
+                              GURL* new_url) = 0;
+  virtual int OnBeforeSendHeaders(extensions::WebRequestInfo* info,
+                                  const network::ResourceRequest& request,
+                                  BeforeSendHeadersCallback callback,
+                                  net::HttpRequestHeaders* headers) = 0;
+  virtual int OnHeadersReceived(
+      extensions::WebRequestInfo* info,
+      const network::ResourceRequest& request,
+      net::CompletionOnceCallback callback,
+      const net::HttpResponseHeaders* original_response_headers,
+      scoped_refptr<net::HttpResponseHeaders>* override_response_headers,
+      GURL* allowed_unsafe_redirect_url) = 0;
+  virtual void OnSendHeaders(extensions::WebRequestInfo* info,
+                             const network::ResourceRequest& request,
+                             const net::HttpRequestHeaders& headers) = 0;
+  virtual void OnBeforeRedirect(extensions::WebRequestInfo* info,
+                                const network::ResourceRequest& request,
+                                const GURL& new_location) = 0;
+  virtual void OnResponseStarted(extensions::WebRequestInfo* info,
+                                 const network::ResourceRequest& request) = 0;
+  virtual void OnErrorOccurred(extensions::WebRequestInfo* info,
+                               const network::ResourceRequest& request,
+                               int net_error) = 0;
+  virtual void OnCompleted(extensions::WebRequestInfo* info,
+                           const network::ResourceRequest& request,
+                           int net_error) = 0;
+};
+
 // This class is responsible for following tasks when NetworkService is enabled:
 // 1. handling intercepted protocols;
 // 2. implementing webRequest module;
@@ -35,6 +79,7 @@ class ProxyingURLLoaderFactory
    public:
     InProgressRequest(
         ProxyingURLLoaderFactory* factory,
+        int64_t web_request_id,
         int32_t routing_id,
         int32_t network_service_request_id,
         uint32_t options,
@@ -99,6 +144,7 @@ class ProxyingURLLoaderFactory
     ProxyingURLLoaderFactory* factory_;
     network::ResourceRequest request_;
     const base::Optional<url::Origin> original_initiator_;
+    const uint64_t request_id_;
     const int32_t routing_id_;
     const int32_t network_service_request_id_;
     const uint32_t options_;
@@ -106,6 +152,8 @@ class ProxyingURLLoaderFactory
     mojo::Binding<network::mojom::URLLoader> proxied_loader_binding_;
     network::mojom::URLLoaderClientPtr target_client_;
 
+    base::Optional<extensions::WebRequestInfo> info_;
+
     network::ResourceResponseHead current_response_;
     scoped_refptr<net::HttpResponseHeaders> override_headers_;
     GURL redirect_url_;
@@ -148,7 +196,9 @@ class ProxyingURLLoaderFactory
   };
 
   ProxyingURLLoaderFactory(
+      WebRequestAPI* web_request_api,
       const HandlersMap& intercepted_handlers,
+      int render_process_id,
       network::mojom::URLLoaderFactoryRequest loader_request,
       network::mojom::URLLoaderFactoryPtrInfo target_factory_info,
       network::mojom::TrustedURLLoaderHeaderClientRequest
@@ -171,9 +221,16 @@ class ProxyingURLLoaderFactory
       int32_t request_id,
       network::mojom::TrustedHeaderClientRequest request) override;
 
+  WebRequestAPI* web_request_api() { return web_request_api_; }
+
  private:
   void OnTargetFactoryError();
   void OnProxyBindingError();
+  void RemoveRequest(int32_t network_service_request_id, uint64_t request_id);
+  void MaybeDeleteThis();
+
+  // Passed from api::WebRequestNS.
+  WebRequestAPI* web_request_api_;
 
   // This is passed from api::ProtocolNS.
   //
@@ -184,11 +241,20 @@ class ProxyingURLLoaderFactory
   // In this way we can avoid using code from api namespace in this file.
   const HandlersMap& intercepted_handlers_;
 
+  const int render_process_id_;
   mojo::BindingSet<network::mojom::URLLoaderFactory> proxy_bindings_;
   network::mojom::URLLoaderFactoryPtr target_factory_;
   mojo::Binding<network::mojom::TrustedURLLoaderHeaderClient>
       url_loader_header_client_binding_;
 
+  // Mapping from our own internally generated request ID to an
+  // InProgressRequest instance.
+  std::map<uint64_t, std::unique_ptr<InProgressRequest>> requests_;
+
+  // A mapping from the network stack's notion of request ID to our own
+  // internally generated request ID for the same request.
+  std::map<int32_t, uint64_t> network_request_id_to_web_request_id_;
+
   DISALLOW_COPY_AND_ASSIGN(ProxyingURLLoaderFactory);
 };
 

+ 44 - 0
shell/common/gin_converters/callback_converter_gin_adapter.h

@@ -0,0 +1,44 @@
+// Copyright (c) 2019 GitHub, Inc.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+#ifndef SHELL_COMMON_GIN_CONVERTERS_CALLBACK_CONVERTER_GIN_ADAPTER_H_
+#define SHELL_COMMON_GIN_CONVERTERS_CALLBACK_CONVERTER_GIN_ADAPTER_H_
+
+#include "gin/converter.h"
+#include "shell/common/native_mate_converters/once_callback.h"
+
+// TODO(zcbenz): Move the implementations from native_mate_converters to here.
+
+namespace gin {
+
+template <typename Sig>
+struct Converter<base::RepeatingCallback<Sig>> {
+  static v8::Local<v8::Value> ToV8(v8::Isolate* isolate,
+                                   const base::RepeatingCallback<Sig>& in) {
+    return mate::ConvertToV8(isolate, in);
+  }
+  static bool FromV8(v8::Isolate* isolate,
+                     v8::Local<v8::Value> val,
+                     base::RepeatingCallback<Sig>* out) {
+    return mate::ConvertFromV8(isolate, val, out);
+  }
+};
+
+template <typename Sig>
+struct Converter<base::OnceCallback<Sig>> {
+  static v8::Local<v8::Value> ToV8(v8::Isolate* isolate,
+                                   base::OnceCallback<Sig> in) {
+    return mate::ConvertToV8(isolate, in);
+  }
+
+  static bool FromV8(v8::Isolate* isolate,
+                     v8::Local<v8::Value> val,
+                     base::OnceCallback<Sig>* out) {
+    return mate::ConvertFromV8(isolate, val, out);
+  }
+};
+
+}  // namespace gin
+
+#endif  // SHELL_COMMON_GIN_CONVERTERS_CALLBACK_CONVERTER_GIN_ADAPTER_H_

+ 27 - 0
shell/common/gin_converters/gurl_converter_gin_adapter.h

@@ -0,0 +1,27 @@
+// Copyright (c) 2019 GitHub, Inc.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+#ifndef SHELL_COMMON_GIN_CONVERTERS_GURL_CONVERTER_GIN_ADAPTER_H_
+#define SHELL_COMMON_GIN_CONVERTERS_GURL_CONVERTER_GIN_ADAPTER_H_
+
+#include "gin/converter.h"
+#include "shell/common/native_mate_converters/gurl_converter.h"
+
+namespace gin {
+
+template <>
+struct Converter<GURL> {
+  static v8::Local<v8::Value> ToV8(v8::Isolate* isolate, const GURL& in) {
+    return mate::ConvertToV8(isolate, in);
+  }
+  static bool FromV8(v8::Isolate* isolate,
+                     v8::Local<v8::Value> val,
+                     GURL* out) {
+    return mate::ConvertFromV8(isolate, val, out);
+  }
+};
+
+}  // namespace gin
+
+#endif  // SHELL_COMMON_GIN_CONVERTERS_GURL_CONVERTER_GIN_ADAPTER_H_

+ 64 - 0
shell/common/gin_converters/net_converter_gin_adapter.h

@@ -0,0 +1,64 @@
+// Copyright (c) 2019 GitHub, Inc.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+#ifndef SHELL_COMMON_GIN_CONVERTERS_NET_CONVERTER_GIN_ADAPTER_H_
+#define SHELL_COMMON_GIN_CONVERTERS_NET_CONVERTER_GIN_ADAPTER_H_
+
+#include <string>
+
+#include "gin/converter.h"
+#include "shell/common/native_mate_converters/net_converter.h"
+
+namespace gin {
+
+template <>
+struct Converter<net::HttpResponseHeaders*> {
+  static v8::Local<v8::Value> ToV8(v8::Isolate* isolate,
+                                   net::HttpResponseHeaders* headers) {
+    return mate::ConvertToV8(isolate, headers);
+  }
+};
+
+template <>
+struct Converter<network::ResourceRequest> {
+  static v8::Local<v8::Value> ToV8(v8::Isolate* isolate,
+                                   const network::ResourceRequest& val) {
+    return mate::ConvertToV8(isolate, val);
+  }
+};
+
+template <>
+struct Converter<network::ResourceRequestBody> {
+  static v8::Local<v8::Value> ToV8(v8::Isolate* isolate,
+                                   const network::ResourceRequestBody& val) {
+    return mate::ConvertToV8(isolate, val);
+  }
+};
+
+template <>
+struct Converter<net::HttpRequestHeaders> {
+  static v8::Local<v8::Value> ToV8(v8::Isolate* isolate,
+                                   const net::HttpRequestHeaders& val) {
+    return mate::ConvertToV8(isolate, val);
+  }
+  static bool FromV8(v8::Isolate* isolate,
+                     v8::Local<v8::Value> val,
+                     net::HttpRequestHeaders* out) {
+    base::DictionaryValue dict;
+    if (!ConvertFromV8(isolate, val, &dict))
+      return false;
+    for (base::DictionaryValue::Iterator it(dict); !it.IsAtEnd();
+         it.Advance()) {
+      if (it.value().is_string()) {
+        std::string value = it.value().GetString();
+        out->SetHeader(it.key(), value);
+      }
+    }
+    return true;
+  }
+};
+
+}  // namespace gin
+
+#endif  // SHELL_COMMON_GIN_CONVERTERS_NET_CONVERTER_GIN_ADAPTER_H_

+ 52 - 0
shell/common/gin_converters/std_converter.h

@@ -0,0 +1,52 @@
+// Copyright (c) 2019 GitHub, Inc.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+#ifndef SHELL_COMMON_GIN_CONVERTERS_STD_CONVERTER_H_
+#define SHELL_COMMON_GIN_CONVERTERS_STD_CONVERTER_H_
+
+#include <set>
+
+#include "gin/converter.h"
+
+namespace gin {
+
+template <typename T>
+struct Converter<std::set<T>> {
+  static v8::Local<v8::Value> ToV8(v8::Isolate* isolate,
+                                   const std::set<T>& val) {
+    v8::Local<v8::Array> result(
+        v8::Array::New(isolate, static_cast<int>(val.size())));
+    auto context = isolate->GetCurrentContext();
+    typename std::set<T>::const_iterator it;
+    int i;
+    for (i = 0, it = val.begin(); it != val.end(); ++it, ++i)
+      result->Set(context, i, Converter<T>::ToV8(isolate, *it)).Check();
+    return result;
+  }
+  static bool FromV8(v8::Isolate* isolate,
+                     v8::Local<v8::Value> val,
+                     std::set<T>* out) {
+    if (!val->IsArray())
+      return false;
+
+    auto context = isolate->GetCurrentContext();
+    std::set<T> result;
+    v8::Local<v8::Array> array(v8::Local<v8::Array>::Cast(val));
+    uint32_t length = array->Length();
+    for (uint32_t i = 0; i < length; ++i) {
+      T item;
+      if (!Converter<T>::FromV8(isolate,
+                                array->Get(context, i).ToLocalChecked(), &item))
+        return false;
+      result.insert(item);
+    }
+
+    out->swap(result);
+    return true;
+  }
+};
+
+}  // namespace gin
+
+#endif  // SHELL_COMMON_GIN_CONVERTERS_STD_CONVERTER_H_

+ 43 - 0
shell/common/gin_converters/value_converter_gin_adapter.h

@@ -0,0 +1,43 @@
+// Copyright (c) 2019 GitHub, Inc.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+#ifndef SHELL_COMMON_GIN_CONVERTERS_VALUE_CONVERTER_GIN_ADAPTER_H_
+#define SHELL_COMMON_GIN_CONVERTERS_VALUE_CONVERTER_GIN_ADAPTER_H_
+
+#include "gin/converter.h"
+#include "shell/common/native_mate_converters/value_converter.h"
+
+// TODO(zcbenz): Move the implementations from native_mate_converters to here.
+
+namespace gin {
+
+template <>
+struct Converter<base::DictionaryValue> {
+  static bool FromV8(v8::Isolate* isolate,
+                     v8::Local<v8::Value> val,
+                     base::DictionaryValue* out) {
+    return mate::ConvertFromV8(isolate, val, out);
+  }
+  static v8::Local<v8::Value> ToV8(v8::Isolate* isolate,
+                                   const base::DictionaryValue& val) {
+    return mate::ConvertToV8(isolate, val);
+  }
+};
+
+template <>
+struct Converter<base::Value> {
+  static bool FromV8(v8::Isolate* isolate,
+                     v8::Local<v8::Value> val,
+                     base::Value* out) {
+    return mate::ConvertFromV8(isolate, val, out);
+  }
+  static v8::Local<v8::Value> ToV8(v8::Isolate* isolate,
+                                   const base::Value& in) {
+    return mate::ConvertToV8(isolate, in);
+  }
+};
+
+}  // namespace gin
+
+#endif  // SHELL_COMMON_GIN_CONVERTERS_VALUE_CONVERTER_GIN_ADAPTER_H_

+ 49 - 41
shell/common/native_mate_converters/net_converter.cc

@@ -161,15 +161,10 @@ v8::Local<v8::Value> Converter<net::HttpResponseHeaders*>::ToV8(
     std::string value;
     while (headers->EnumerateHeaderLines(&iter, &key, &value)) {
       key = base::ToLowerASCII(key);
-      if (response_headers.FindKey(key)) {
-        base::ListValue* values = nullptr;
-        if (response_headers.GetList(key, &values))
-          values->AppendString(value);
-      } else {
-        auto values = std::make_unique<base::ListValue>();
-        values->AppendString(value);
-        response_headers.Set(key, std::move(values));
-      }
+      base::Value* values = response_headers.FindListKey(key);
+      if (!values)
+        values = response_headers.SetKey(key, base::ListValue());
+      values->GetList().emplace_back(value);
     }
   }
   return ConvertToV8(isolate, response_headers);
@@ -226,6 +221,47 @@ bool Converter<net::HttpResponseHeaders*>::FromV8(
   return true;
 }
 
+// static
+v8::Local<v8::Value> Converter<net::HttpRequestHeaders>::ToV8(
+    v8::Isolate* isolate,
+    const net::HttpRequestHeaders& val) {
+  mate::Dictionary headers(isolate, v8::Object::New(isolate));
+  for (net::HttpRequestHeaders::Iterator it(val); it.GetNext();)
+    headers.Set(it.name(), it.value());
+  return ConvertToV8(isolate, headers);
+}
+
+// static
+v8::Local<v8::Value> Converter<network::ResourceRequestBody>::ToV8(
+    v8::Isolate* isolate,
+    const network::ResourceRequestBody& val) {
+  const auto& elements = *val.elements();
+  v8::Local<v8::Array> arr = v8::Array::New(isolate, elements.size());
+  for (size_t i = 0; i < elements.size(); ++i) {
+    const auto& element = elements[i];
+    mate::Dictionary upload_data(isolate, v8::Object::New(isolate));
+    switch (element.type()) {
+      case network::mojom::DataElementType::kFile:
+        upload_data.Set("file", element.path().value());
+        break;
+      case network::mojom::DataElementType::kBytes:
+        upload_data.Set("bytes", node::Buffer::Copy(isolate, element.bytes(),
+                                                    element.length())
+                                     .ToLocalChecked());
+        break;
+      case network::mojom::DataElementType::kBlob:
+        upload_data.Set("blobUUID", element.blob_uuid());
+        break;
+      default:
+        NOTREACHED() << "Found unsupported data element";
+    }
+    arr->Set(isolate->GetCurrentContext(), static_cast<uint32_t>(i),
+             ConvertToV8(isolate, upload_data))
+        .Check();
+  }
+  return arr;
+}
+
 // static
 v8::Local<v8::Value> Converter<network::ResourceRequest>::ToV8(
     v8::Isolate* isolate,
@@ -234,38 +270,10 @@ v8::Local<v8::Value> Converter<network::ResourceRequest>::ToV8(
   dict.Set("method", val.method);
   dict.Set("url", val.url.spec());
   dict.Set("referrer", val.referrer.spec());
-  mate::Dictionary headers(isolate, v8::Object::New(isolate));
-  for (net::HttpRequestHeaders::Iterator it(val.headers); it.GetNext();)
-    headers.Set(it.name(), it.value());
-  dict.Set("headers", headers);
-  if (val.request_body) {
-    const auto& elements = *val.request_body->elements();
-    v8::Local<v8::Array> arr = v8::Array::New(isolate, elements.size());
-    for (size_t i = 0; i < elements.size(); ++i) {
-      const auto& element = elements[i];
-      mate::Dictionary upload_data(isolate, v8::Object::New(isolate));
-      switch (element.type()) {
-        case network::mojom::DataElementType::kFile:
-          upload_data.Set("file", element.path().value());
-          break;
-        case network::mojom::DataElementType::kBytes:
-          upload_data.Set("bytes", node::Buffer::Copy(isolate, element.bytes(),
-                                                      element.length())
-                                       .ToLocalChecked());
-          break;
-        case network::mojom::DataElementType::kBlob:
-          upload_data.Set("blobUUID", element.blob_uuid());
-          break;
-        default:
-          NOTREACHED() << "Found unsupported data element";
-      }
-      arr->Set(isolate->GetCurrentContext(), static_cast<uint32_t>(i),
-               upload_data.GetHandle())
-          .Check();
-    }
-    dict.Set("uploadData", arr);
-  }
-  return dict.GetHandle();
+  dict.Set("headers", val.headers);
+  if (val.request_body)
+    dict.Set("uploadData", ConvertToV8(isolate, *val.request_body));
+  return ConvertToV8(isolate, dict);
 }
 
 }  // namespace mate

+ 15 - 1
shell/common/native_mate_converters/net_converter.h

@@ -18,12 +18,14 @@ class AuthChallengeInfo;
 class URLRequest;
 class X509Certificate;
 class HttpResponseHeaders;
+class HttpRequestHeaders;
 struct CertPrincipal;
 }  // namespace net
 
 namespace network {
+class ResourceRequestBody;
 struct ResourceRequest;
-}
+}  // namespace network
 
 namespace mate {
 
@@ -59,6 +61,18 @@ struct Converter<net::HttpResponseHeaders*> {
                      net::HttpResponseHeaders* out);
 };
 
+template <>
+struct Converter<net::HttpRequestHeaders> {
+  static v8::Local<v8::Value> ToV8(v8::Isolate* isolate,
+                                   const net::HttpRequestHeaders& headers);
+};
+
+template <>
+struct Converter<network::ResourceRequestBody> {
+  static v8::Local<v8::Value> ToV8(v8::Isolate* isolate,
+                                   const network::ResourceRequestBody& val);
+};
+
 template <>
 struct Converter<network::ResourceRequest> {
   static v8::Local<v8::Value> ToV8(v8::Isolate* isolate,