Convert Background Fetch' input to a WebServiceWorkerRequest vector

Developers can pass in either an individual USVString or Request object,
or a sequence of either. Convert the input provided by the developer to
a sequence of WebServiceWorkerRequest objects, which can (soon) be send
over Mojo to the browser process.

BUG=692534, 692535

Review-Url: https://codereview.chromium.org/2762243002
Cr-Original-Commit-Position: refs/heads/master@{#459465}
Cr-Mirrored-From: https://chromium.googlesource.com/chromium/src
Cr-Mirrored-Commit: 9b34b21e479145817c93e1e4870ed2e87376eac0
diff --git a/WebKit/LayoutTests/http/tests/background_fetch/background-fetch-manager-fetch.https.html b/WebKit/LayoutTests/http/tests/background_fetch/background-fetch-manager-fetch.https.html
index 365995f..40286c4 100644
--- a/WebKit/LayoutTests/http/tests/background_fetch/background-fetch-manager-fetch.https.html
+++ b/WebKit/LayoutTests/http/tests/background_fetch/background-fetch-manager-fetch.https.html
@@ -8,13 +8,15 @@
 <h1>BackgroundFetchManager.fetch()</h1>
 <p>This test validates the behaviour of the fetch() method.</p>
 
-<!-- TODO(peter): Move this to the WPT directory when it's merged. -->
+<!-- TODO(peter): Move this to the WPT directory when it's merged and the
+     behaviour of fetch() for null and empty sequences is defined. -->
 
 <script>
 'use strict';
 
 const workerUrl = 'resources/empty-worker.js';
 const scope = 'resources/scope/' + location.pathname;
+const tag = 'my-background-fetch';
 
 promise_test(function(test) {
   return service_worker_unregister_and_register(test, workerUrl, scope)
@@ -29,7 +31,42 @@
 }, 'BackgroundFetchManager.fetch() requires an activated Service Worker.');
 
 promise_test(function(test) {
-  const tag = 'my-background-fetch';
+  let registration = null;
+  return service_worker_unregister_and_register(test, workerUrl, scope)
+    .then(r => {
+      registration = r;
+      return wait_for_state(test, r.installing, 'activated');
+    })
+    .then(() => registration.backgroundFetch.fetch(tag, null))
+    .then(() => unreached_fulfillment(test), () => true /* pass */);
+
+}, 'BackgroundFetchManager.fetch() throws when given a null request.');
+
+promise_test(function(test) {
+  let registration = null;
+  return service_worker_unregister_and_register(test, workerUrl, scope)
+    .then(r => {
+      registration = r;
+      return wait_for_state(test, r.installing, 'activated');
+    })
+    .then(() => registration.backgroundFetch.fetch(tag, []))
+    .then(() => unreached_fulfillment(test), () => true /* pass */);
+
+}, 'BackgroundFetchManager.fetch() throws when given an empty sequence.');
+
+promise_test(function(test) {
+  let registration = null;
+  return service_worker_unregister_and_register(test, workerUrl, scope)
+    .then(r => {
+      registration = r;
+      return wait_for_state(test, r.installing, 'activated');
+    })
+    .then(() => registration.backgroundFetch.fetch(tag, ['resources/non-existing-file.png', null]))
+    .then(() => unreached_fulfillment(test), () => true /* pass */);
+
+}, 'BackgroundFetchManager.fetch() throws when given a sequence with a null request.');
+
+promise_test(function(test) {
   const options = {
     icons: [
       {
diff --git a/WebKit/Source/modules/BUILD.gn b/WebKit/Source/modules/BUILD.gn
index 91e8181..59c50ca 100644
--- a/WebKit/Source/modules/BUILD.gn
+++ b/WebKit/Source/modules/BUILD.gn
@@ -234,6 +234,7 @@
 
   sources = [
     "accessibility/AXObjectTest.cpp",
+    "background_fetch/BackgroundFetchManagerTest.cpp",
     "cachestorage/CacheTest.cpp",
     "canvas/HTMLCanvasElementModuleTest.cpp",
     "canvas2d/CanvasRenderingContext2DAPITest.cpp",
diff --git a/WebKit/Source/modules/background_fetch/BackgroundFetchBridge.cpp b/WebKit/Source/modules/background_fetch/BackgroundFetchBridge.cpp
index c65ff59..a0b283e 100644
--- a/WebKit/Source/modules/background_fetch/BackgroundFetchBridge.cpp
+++ b/WebKit/Source/modules/background_fetch/BackgroundFetchBridge.cpp
@@ -12,6 +12,7 @@
 #include "public/platform/InterfaceProvider.h"
 #include "public/platform/Platform.h"
 #include "public/platform/modules/serviceworker/WebServiceWorkerRegistration.h"
+#include "public/platform/modules/serviceworker/WebServiceWorkerRequest.h"
 
 namespace blink {
 
@@ -46,8 +47,10 @@
 
 void BackgroundFetchBridge::fetch(
     const String& tag,
+    Vector<WebServiceWorkerRequest> requests,
     const BackgroundFetchOptions& options,
     std::unique_ptr<RegistrationCallback> callback) {
+  // TODO(peter): Include |requests| in the Mojo call.
   getService()->Fetch(
       supplementable()->webRegistration()->registrationId(), tag,
       mojom::blink::BackgroundFetchOptions::From(options),
diff --git a/WebKit/Source/modules/background_fetch/BackgroundFetchBridge.h b/WebKit/Source/modules/background_fetch/BackgroundFetchBridge.h
index c8c1067..4d67561 100644
--- a/WebKit/Source/modules/background_fetch/BackgroundFetchBridge.h
+++ b/WebKit/Source/modules/background_fetch/BackgroundFetchBridge.h
@@ -18,6 +18,7 @@
 
 class BackgroundFetchOptions;
 class BackgroundFetchRegistration;
+class WebServiceWorkerRequest;
 
 // The bridge is responsible for establishing and maintaining the Mojo
 // connection to the BackgroundFetchService. It's keyed on an active Service
@@ -45,6 +46,7 @@
   // given |options| for the sequence of |requests|. The |callback| will be
   // invoked when the registration has been created.
   void fetch(const String& tag,
+             Vector<WebServiceWorkerRequest> requests,
              const BackgroundFetchOptions&,
              std::unique_ptr<RegistrationCallback>);
 
diff --git a/WebKit/Source/modules/background_fetch/BackgroundFetchManager.cpp b/WebKit/Source/modules/background_fetch/BackgroundFetchManager.cpp
index d1018f8..66a39ed 100644
--- a/WebKit/Source/modules/background_fetch/BackgroundFetchManager.cpp
+++ b/WebKit/Source/modules/background_fetch/BackgroundFetchManager.cpp
@@ -7,15 +7,30 @@
 #include "bindings/core/v8/ScriptPromiseResolver.h"
 #include "bindings/core/v8/ScriptState.h"
 #include "bindings/core/v8/V8ThrowException.h"
+#include "bindings/modules/v8/RequestOrUSVString.h"
+#include "bindings/modules/v8/RequestOrUSVStringOrRequestOrUSVStringSequence.h"
 #include "core/dom/DOMException.h"
 #include "core/dom/ExceptionCode.h"
 #include "modules/background_fetch/BackgroundFetchBridge.h"
 #include "modules/background_fetch/BackgroundFetchOptions.h"
 #include "modules/background_fetch/BackgroundFetchRegistration.h"
+#include "modules/fetch/Request.h"
 #include "modules/serviceworkers/ServiceWorkerRegistration.h"
+#include "public/platform/modules/serviceworker/WebServiceWorkerRequest.h"
 
 namespace blink {
 
+namespace {
+
+// Message for the TypeError thrown when an empty request sequence is seen.
+const char kEmptyRequestSequenceErrorMessage[] =
+    "At least one request must be given.";
+
+// Message for the TypeError thrown when a null request is seen.
+const char kNullRequestErrorMessage[] = "Requests must not be null.";
+
+}  // namespace
+
 BackgroundFetchManager::BackgroundFetchManager(
     ServiceWorkerRegistration* registration)
     : m_registration(registration) {
@@ -27,7 +42,8 @@
     ScriptState* scriptState,
     const String& tag,
     const RequestOrUSVStringOrRequestOrUSVStringSequence& requests,
-    const BackgroundFetchOptions& options) {
+    const BackgroundFetchOptions& options,
+    ExceptionState& exceptionState) {
   if (!m_registration->active()) {
     return ScriptPromise::reject(
         scriptState,
@@ -36,12 +52,15 @@
                                           "the ServiceWorkerRegistration."));
   }
 
+  Vector<WebServiceWorkerRequest> webRequests =
+      createWebRequestVector(scriptState, requests, exceptionState);
+  if (exceptionState.hadException())
+    return ScriptPromise();
+
   ScriptPromiseResolver* resolver = ScriptPromiseResolver::create(scriptState);
   ScriptPromise promise = resolver->promise();
 
-  // TODO(peter): Include the |requests| in the Mojo call.
-
-  m_bridge->fetch(tag, options,
+  m_bridge->fetch(tag, std::move(webRequests), options,
                   WTF::bind(&BackgroundFetchManager::didFetch,
                             wrapPersistent(this), wrapPersistent(resolver)));
 
@@ -91,6 +110,65 @@
   return promise;
 }
 
+// static
+Vector<WebServiceWorkerRequest> BackgroundFetchManager::createWebRequestVector(
+    ScriptState* scriptState,
+    const RequestOrUSVStringOrRequestOrUSVStringSequence& requests,
+    ExceptionState& exceptionState) {
+  Vector<WebServiceWorkerRequest> webRequests;
+
+  if (requests.isRequestOrUSVStringSequence()) {
+    HeapVector<RequestOrUSVString> requestVector =
+        requests.getAsRequestOrUSVStringSequence();
+
+    // Throw a TypeError when the developer has passed an empty sequence.
+    if (!requestVector.size()) {
+      exceptionState.throwTypeError(kEmptyRequestSequenceErrorMessage);
+      return Vector<WebServiceWorkerRequest>();
+    }
+
+    webRequests.resize(requestVector.size());
+
+    for (size_t i = 0; i < requestVector.size(); ++i) {
+      const RequestOrUSVString& requestOrUrl = requestVector[i];
+
+      Request* request = nullptr;
+      if (requestOrUrl.isRequest()) {
+        request = requestOrUrl.getAsRequest();
+      } else if (requestOrUrl.isUSVString()) {
+        request = Request::create(scriptState, requestOrUrl.getAsUSVString(),
+                                  exceptionState);
+        if (exceptionState.hadException())
+          return Vector<WebServiceWorkerRequest>();
+      } else {
+        exceptionState.throwTypeError(kNullRequestErrorMessage);
+        return Vector<WebServiceWorkerRequest>();
+      }
+
+      DCHECK(request);
+      request->populateWebServiceWorkerRequest(webRequests[i]);
+    }
+  } else if (requests.isRequest()) {
+    DCHECK(requests.getAsRequest());
+    webRequests.resize(1);
+    requests.getAsRequest()->populateWebServiceWorkerRequest(webRequests[0]);
+  } else if (requests.isUSVString()) {
+    Request* request =
+        Request::create(scriptState, requests.getAsUSVString(), exceptionState);
+    if (exceptionState.hadException())
+      return Vector<WebServiceWorkerRequest>();
+
+    DCHECK(request);
+    webRequests.resize(1);
+    request->populateWebServiceWorkerRequest(webRequests[0]);
+  } else {
+    exceptionState.throwTypeError(kNullRequestErrorMessage);
+    return Vector<WebServiceWorkerRequest>();
+  }
+
+  return webRequests;
+}
+
 void BackgroundFetchManager::didGetRegistration(
     ScriptPromiseResolver* resolver,
     mojom::blink::BackgroundFetchError error,
diff --git a/WebKit/Source/modules/background_fetch/BackgroundFetchManager.h b/WebKit/Source/modules/background_fetch/BackgroundFetchManager.h
index b524fd5..ad1c4f4 100644
--- a/WebKit/Source/modules/background_fetch/BackgroundFetchManager.h
+++ b/WebKit/Source/modules/background_fetch/BackgroundFetchManager.h
@@ -7,6 +7,7 @@
 
 #include "bindings/core/v8/ScriptPromise.h"
 #include "bindings/core/v8/ScriptWrappable.h"
+#include "modules/ModulesExport.h"
 #include "platform/heap/GarbageCollected.h"
 #include "platform/heap/Handle.h"
 #include "public/platform/modules/background_fetch/background_fetch.mojom-blink.h"
@@ -16,14 +17,16 @@
 class BackgroundFetchBridge;
 class BackgroundFetchOptions;
 class BackgroundFetchRegistration;
+class ExceptionState;
 class RequestOrUSVStringOrRequestOrUSVStringSequence;
 class ScriptPromiseResolver;
 class ScriptState;
 class ServiceWorkerRegistration;
+class WebServiceWorkerRequest;
 
 // Implementation of the BackgroundFetchManager JavaScript object, accessible
 // by developers through ServiceWorkerRegistration.backgroundFetch.
-class BackgroundFetchManager final
+class MODULES_EXPORT BackgroundFetchManager final
     : public GarbageCollected<BackgroundFetchManager>,
       public ScriptWrappable {
   DEFINE_WRAPPERTYPEINFO();
@@ -39,15 +42,25 @@
       ScriptState*,
       const String& tag,
       const RequestOrUSVStringOrRequestOrUSVStringSequence& requests,
-      const BackgroundFetchOptions&);
+      const BackgroundFetchOptions&,
+      ExceptionState&);
   ScriptPromise get(ScriptState*, const String& tag);
   ScriptPromise getTags(ScriptState*);
 
   DECLARE_TRACE();
 
  private:
+  friend class BackgroundFetchManagerTest;
+
   explicit BackgroundFetchManager(ServiceWorkerRegistration*);
 
+  // Creates a vector of WebServiceWorkerRequest objects for the given set of
+  // |requests|, which can be either Request objects or URL strings.
+  static Vector<WebServiceWorkerRequest> createWebRequestVector(
+      ScriptState*,
+      const RequestOrUSVStringOrRequestOrUSVStringSequence& requests,
+      ExceptionState&);
+
   void didFetch(ScriptPromiseResolver*,
                 mojom::blink::BackgroundFetchError,
                 BackgroundFetchRegistration*);
diff --git a/WebKit/Source/modules/background_fetch/BackgroundFetchManager.idl b/WebKit/Source/modules/background_fetch/BackgroundFetchManager.idl
index a30ad00..855ab3b 100644
--- a/WebKit/Source/modules/background_fetch/BackgroundFetchManager.idl
+++ b/WebKit/Source/modules/background_fetch/BackgroundFetchManager.idl
@@ -8,7 +8,7 @@
     Exposed=(Window,Worker),
     RuntimeEnabled=BackgroundFetch
 ] interface BackgroundFetchManager {
-    [CallWith=ScriptState] Promise<BackgroundFetchRegistration> fetch(DOMString tag, (RequestInfo or sequence<RequestInfo>) requests, optional BackgroundFetchOptions options);
+    [CallWith=ScriptState, RaisesException] Promise<BackgroundFetchRegistration> fetch(DOMString tag, (RequestInfo or sequence<RequestInfo>) requests, optional BackgroundFetchOptions options);
     [CallWith=ScriptState] Promise<BackgroundFetchRegistration?> get(DOMString tag);
     [CallWith=ScriptState] Promise<FrozenArray<DOMString>> getTags();
 };
diff --git a/WebKit/Source/modules/background_fetch/BackgroundFetchManagerTest.cpp b/WebKit/Source/modules/background_fetch/BackgroundFetchManagerTest.cpp
new file mode 100644
index 0000000..7bd0a0d
--- /dev/null
+++ b/WebKit/Source/modules/background_fetch/BackgroundFetchManagerTest.cpp
@@ -0,0 +1,185 @@
+// 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 "modules/background_fetch/BackgroundFetchManager.h"
+
+#include "bindings/core/v8/Dictionary.h"
+#include "bindings/core/v8/ExceptionState.h"
+#include "bindings/core/v8/ScriptState.h"
+#include "bindings/core/v8/V8Binding.h"
+#include "bindings/core/v8/V8BindingForTesting.h"
+#include "bindings/modules/v8/RequestOrUSVString.h"
+#include "bindings/modules/v8/RequestOrUSVStringOrRequestOrUSVStringSequence.h"
+#include "core/dom/ExceptionCode.h"
+#include "modules/fetch/Request.h"
+#include "public/platform/modules/serviceworker/WebServiceWorkerRequest.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace blink {
+
+class BackgroundFetchManagerTest : public ::testing::Test {
+ protected:
+  // Creates a vector of WebServiceWorkerRequest entries for the given
+  // |requests| based on the |scope|. Proxied in the fixture to reduce the
+  // number of friend declarations necessary in the BackgroundFetchManager.
+  Vector<WebServiceWorkerRequest> createWebRequestVector(
+      V8TestingScope& scope,
+      const RequestOrUSVStringOrRequestOrUSVStringSequence& requests) {
+    return BackgroundFetchManager::createWebRequestVector(
+        scope.getScriptState(), requests, scope.getExceptionState());
+  }
+
+  // Returns a Dictionary object that represents a JavaScript dictionary with
+  // a single key-value pair, where the key always is "method" with the value
+  // set to |method|.
+  Dictionary getDictionaryForMethod(V8TestingScope& scope, const char* method) {
+    v8::Isolate* isolate = scope.isolate();
+    v8::Local<v8::Object> data = v8::Object::New(isolate);
+
+    data->Set(isolate->GetCurrentContext(), v8String(isolate, "method"),
+              v8String(isolate, method))
+        .ToChecked();
+
+    return Dictionary(scope.isolate(), data, scope.getExceptionState());
+  }
+};
+
+TEST_F(BackgroundFetchManagerTest, NullValue) {
+  V8TestingScope scope;
+
+  RequestOrUSVStringOrRequestOrUSVStringSequence requests;
+
+  Vector<WebServiceWorkerRequest> webRequests =
+      createWebRequestVector(scope, requests);
+  ASSERT_TRUE(scope.getExceptionState().hadException());
+  EXPECT_EQ(scope.getExceptionState().code(), V8TypeError);
+}
+
+TEST_F(BackgroundFetchManagerTest, SingleUSVString) {
+  V8TestingScope scope;
+
+  KURL imageUrl(ParsedURLString, "https://www.example.com/my_image.png");
+
+  RequestOrUSVStringOrRequestOrUSVStringSequence requests =
+      RequestOrUSVStringOrRequestOrUSVStringSequence::fromUSVString(
+          imageUrl.getString());
+
+  Vector<WebServiceWorkerRequest> webRequests =
+      createWebRequestVector(scope, requests);
+  ASSERT_FALSE(scope.getExceptionState().hadException());
+
+  ASSERT_EQ(webRequests.size(), 1u);
+
+  WebServiceWorkerRequest& webRequest = webRequests[0];
+  EXPECT_EQ(webRequest.url(), WebURL(imageUrl));
+  EXPECT_EQ(webRequest.method(), "GET");
+}
+
+TEST_F(BackgroundFetchManagerTest, SingleRequest) {
+  V8TestingScope scope;
+
+  KURL imageUrl(ParsedURLString, "https://www.example.com/my_image.png");
+
+  Request* request = Request::create(
+      scope.getScriptState(), imageUrl.getString(),
+      getDictionaryForMethod(scope, "POST"), scope.getExceptionState());
+  ASSERT_FALSE(scope.getExceptionState().hadException());
+  ASSERT_TRUE(request);
+
+  RequestOrUSVStringOrRequestOrUSVStringSequence requests =
+      RequestOrUSVStringOrRequestOrUSVStringSequence::fromRequest(request);
+
+  Vector<WebServiceWorkerRequest> webRequests =
+      createWebRequestVector(scope, requests);
+  ASSERT_FALSE(scope.getExceptionState().hadException());
+
+  ASSERT_EQ(webRequests.size(), 1u);
+
+  WebServiceWorkerRequest& webRequest = webRequests[0];
+  EXPECT_EQ(webRequest.url(), WebURL(imageUrl));
+  EXPECT_EQ(webRequest.method(), "POST");
+}
+
+TEST_F(BackgroundFetchManagerTest, Sequence) {
+  V8TestingScope scope;
+
+  KURL imageUrl(ParsedURLString, "https://www.example.com/my_image.png");
+  KURL iconUrl(ParsedURLString, "https://www.example.com/my_icon.jpg");
+  KURL catVideoUrl(ParsedURLString, "https://www.example.com/my_cat_video.avi");
+
+  RequestOrUSVString imageRequest =
+      RequestOrUSVString::fromUSVString(imageUrl.getString());
+  RequestOrUSVString iconRequest =
+      RequestOrUSVString::fromUSVString(iconUrl.getString());
+
+  Request* request = Request::create(
+      scope.getScriptState(), catVideoUrl.getString(),
+      getDictionaryForMethod(scope, "DELETE"), scope.getExceptionState());
+  ASSERT_FALSE(scope.getExceptionState().hadException());
+  ASSERT_TRUE(request);
+
+  RequestOrUSVString catVideoRequest = RequestOrUSVString::fromRequest(request);
+
+  HeapVector<RequestOrUSVString> requestSequence;
+  requestSequence.push_back(imageRequest);
+  requestSequence.push_back(iconRequest);
+  requestSequence.push_back(catVideoRequest);
+
+  RequestOrUSVStringOrRequestOrUSVStringSequence requests =
+      RequestOrUSVStringOrRequestOrUSVStringSequence::
+          fromRequestOrUSVStringSequence(requestSequence);
+
+  Vector<WebServiceWorkerRequest> webRequests =
+      createWebRequestVector(scope, requests);
+  ASSERT_FALSE(scope.getExceptionState().hadException());
+
+  ASSERT_EQ(webRequests.size(), 3u);
+  EXPECT_EQ(webRequests[0].url(), WebURL(imageUrl));
+  EXPECT_EQ(webRequests[0].method(), "GET");
+
+  EXPECT_EQ(webRequests[1].url(), WebURL(iconUrl));
+  EXPECT_EQ(webRequests[1].method(), "GET");
+
+  EXPECT_EQ(webRequests[2].url(), WebURL(catVideoUrl));
+  EXPECT_EQ(webRequests[2].method(), "DELETE");
+}
+
+TEST_F(BackgroundFetchManagerTest, SequenceEmpty) {
+  V8TestingScope scope;
+
+  HeapVector<RequestOrUSVString> requestSequence;
+  RequestOrUSVStringOrRequestOrUSVStringSequence requests =
+      RequestOrUSVStringOrRequestOrUSVStringSequence::
+          fromRequestOrUSVStringSequence(requestSequence);
+
+  Vector<WebServiceWorkerRequest> webRequests =
+      createWebRequestVector(scope, requests);
+  ASSERT_TRUE(scope.getExceptionState().hadException());
+  EXPECT_EQ(scope.getExceptionState().code(), V8TypeError);
+}
+
+TEST_F(BackgroundFetchManagerTest, SequenceWithNullValue) {
+  V8TestingScope scope;
+
+  KURL imageUrl(ParsedURLString, "https://www.example.com/my_image.png");
+
+  RequestOrUSVString nullRequest;
+  RequestOrUSVString imageRequest =
+      RequestOrUSVString::fromUSVString(imageUrl.getString());
+
+  HeapVector<RequestOrUSVString> requestSequence;
+  requestSequence.push_back(imageRequest);
+  requestSequence.push_back(nullRequest);
+
+  RequestOrUSVStringOrRequestOrUSVStringSequence requests =
+      RequestOrUSVStringOrRequestOrUSVStringSequence::
+          fromRequestOrUSVStringSequence(requestSequence);
+
+  Vector<WebServiceWorkerRequest> webRequests =
+      createWebRequestVector(scope, requests);
+  ASSERT_TRUE(scope.getExceptionState().hadException());
+  EXPECT_EQ(scope.getExceptionState().code(), V8TypeError);
+}
+
+}  // namespace blink