CSP3: Implement 'worker-src'.

As a drive-by, this also renames `allowChildFrameFromSource` to
`allowFrameFromSource`, and `allowChildContextFromSource` to
`allowWorkerFromSource` to reflect their usage.

Intent to Ship: https://groups.google.com/a/chromium.org/d/msg/blink-dev/1UkZE-vOROc/gEj7psewAAAJ

BUG=662930

Review-Url: https://codereview.chromium.org/2480303002
Cr-Commit-Position: refs/heads/master@{#431228}
diff --git a/third_party/WebKit/LayoutTests/http/tests/security/contentSecurityPolicy/1.1/child-src/worker-blocked-expected.txt b/third_party/WebKit/LayoutTests/http/tests/security/contentSecurityPolicy/1.1/child-src/worker-blocked-expected.txt
index dfd5cb3..f15c63e 100644
--- a/third_party/WebKit/LayoutTests/http/tests/security/contentSecurityPolicy/1.1/child-src/worker-blocked-expected.txt
+++ b/third_party/WebKit/LayoutTests/http/tests/security/contentSecurityPolicy/1.1/child-src/worker-blocked-expected.txt
@@ -1,4 +1,4 @@
-CONSOLE ERROR: line 1: Refused to create a child context containing 'http://127.0.0.1:8000/security/contentSecurityPolicy/resources/alert-fail.js' because it violates the following Content Security Policy directive: "child-src 'none'".
+CONSOLE ERROR: line 1: Refused to create a worker from 'http://127.0.0.1:8000/security/contentSecurityPolicy/resources/alert-fail.js' because it violates the following Content Security Policy directive: "child-src 'none'".
 
 Workers should be governed by 'child-src'.
 
diff --git a/third_party/WebKit/LayoutTests/http/tests/security/contentSecurityPolicy/1.1/child-src/worker-shared-blocked-expected.txt b/third_party/WebKit/LayoutTests/http/tests/security/contentSecurityPolicy/1.1/child-src/worker-shared-blocked-expected.txt
index 02e2545..df93a58 100644
--- a/third_party/WebKit/LayoutTests/http/tests/security/contentSecurityPolicy/1.1/child-src/worker-shared-blocked-expected.txt
+++ b/third_party/WebKit/LayoutTests/http/tests/security/contentSecurityPolicy/1.1/child-src/worker-shared-blocked-expected.txt
@@ -1,4 +1,4 @@
-CONSOLE ERROR: line 1: Refused to create a child context containing 'http://127.0.0.1:8000/security/contentSecurityPolicy/resources/alert-fail.js' because it violates the following Content Security Policy directive: "child-src 'none'".
+CONSOLE ERROR: line 1: Refused to create a worker from 'http://127.0.0.1:8000/security/contentSecurityPolicy/resources/alert-fail.js' because it violates the following Content Security Policy directive: "child-src 'none'".
 
 SharedWorkers should be governed by 'child-src'.
 
diff --git a/third_party/WebKit/LayoutTests/http/tests/security/contentSecurityPolicy/resources/ping.js b/third_party/WebKit/LayoutTests/http/tests/security/contentSecurityPolicy/resources/ping.js
new file mode 100644
index 0000000..750ae45f
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/http/tests/security/contentSecurityPolicy/resources/ping.js
@@ -0,0 +1,12 @@
+if (typeof ServiceWorkerGlobalScope === "function") {
+  self.onmessage = function (e) { e.source.postMessage("ping"); };
+} else if (typeof SharedWorkerGlobalScope === "function") {
+  onconnect = function (e) {
+    var port = e.ports[0];
+
+    port.onmessage = function () { port.postMessage("ping"); }
+    port.postMessage("ping");
+  };
+} else if (typeof DedicatedWorkerGlobalScope === "function") {
+  self.postMessage("ping");
+}
diff --git a/third_party/WebKit/LayoutTests/http/tests/security/contentSecurityPolicy/resources/testharness-helper.js b/third_party/WebKit/LayoutTests/http/tests/security/contentSecurityPolicy/resources/testharness-helper.js
new file mode 100644
index 0000000..a1eaa89
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/http/tests/security/contentSecurityPolicy/resources/testharness-helper.js
@@ -0,0 +1,131 @@
+function assert_no_csp_event_for_url(test, url) {
+  document.addEventListener("securitypolicyviolation", test.step_func(e => {
+    if (e.blockedURI !== url)
+      return;
+    assert_unreached("SecurityPolicyViolation event fired for " + url);
+  }));
+}
+
+function assert_no_event(test, obj, name) {
+  obj.addEventListener(name, test.unreached_func("The '" + name + "' event should not have fired."));
+}
+
+function waitUntilCSPEventForURL(test, url) {
+  return new Promise((resolve, reject) => {
+    document.addEventListener("securitypolicyviolation", test.step_func(e => {
+      if (e.blockedURI == url)
+        resolve(e);
+    }));
+  });
+}
+
+function waitUntilEvent(obj, name) {
+  return new Promise((resolve, reject) => {
+    obj.addEventListener(name, resolve);
+  });
+}
+
+// Given the URL of a worker that pings its opener upon load, this
+// function builds a test that asserts that the ping is received,
+// and that no CSP event fires.
+function assert_worker_is_loaded(url, description) {
+  async_test(t => {
+    assert_no_csp_event_for_url(t, url);
+    var w = new Worker(url);
+    assert_no_event(t, w, "error");
+    waitUntilEvent(w, "message")
+      .then(t.step_func_done(e => {
+        assert_equals(e.data, "ping");
+      }));
+  }, description);
+}
+
+function assert_shared_worker_is_loaded(url, description) {
+  async_test(t => {
+    assert_no_csp_event_for_url(t, url);
+    var w = new SharedWorker(url);
+    assert_no_event(t, w, "error");
+    waitUntilEvent(w.port, "message")
+      .then(t.step_func_done(e => {
+        assert_equals(e.data, "ping");
+      }));
+    w.port.start();
+  }, description);
+}
+
+function assert_service_worker_is_loaded(url, description) {
+  promise_test(t => {
+    assert_no_csp_event_for_url(t, url);
+    return Promise.all([
+      waitUntilEvent(navigator.serviceWorker, "message")
+        .then(e => {
+          assert_equals(e.data, "ping");
+        }),
+      navigator.serviceWorker.register(url, { scope: url })
+        .then(r => {
+          var sw = r.active || r.installing || r.waiting;
+          t.add_cleanup(_ => r.unregister());
+          sw.postMessage("pong?");
+        })
+    ]);
+  }, description);
+}
+
+// Given the URL of a worker that pings its opener upon load, this
+// function builds a test that asserts that the constructor throws
+// a SecurityError, and that a CSP event fires.
+function assert_worker_is_blocked(url, description) {
+  async_test(t => {
+    // If |url| is a blob, it will be stripped down to "blob" for reporting.
+    var reportedURL = new URL(url).protocol == "blob:" ? "blob" : url;
+    waitUntilCSPEventForURL(t, reportedURL)
+      .then(t.step_func_done(e => {
+        assert_equals(e.blockedURI, reportedURL);
+        assert_equals(e.violatedDirective, "worker-src");
+        assert_equals(e.effectiveDirective, "worker-src");
+      }));
+
+    // TODO(mkwst): We shouldn't be throwing here. We should be firing an
+    // `error` event on the Worker. https://crbug.com/663298
+    assert_throws("SecurityError", function () {
+      var w = new Worker(url);
+    });
+  }, description);
+}
+
+function assert_shared_worker_is_blocked(url, description) {
+  async_test(t => {
+    // If |url| is a blob, it will be stripped down to "blob" for reporting.
+    var reportedURL = new URL(url).protocol == "blob:" ? "blob" : url;
+    waitUntilCSPEventForURL(t, reportedURL)
+      .then(t.step_func_done(e => {
+        assert_equals(e.blockedURI, reportedURL);
+        assert_equals(e.violatedDirective, "worker-src");
+        assert_equals(e.effectiveDirective, "worker-src");
+      }));
+
+    // TODO(mkwst): We shouldn't be throwing here. We should be firing an
+    // `error` event on the SharedWorker. https://crbug.com/663298
+    assert_throws("SecurityError", function () {
+      var w = new SharedWorker(url);
+    });
+  }, description);
+}
+
+function assert_service_worker_is_blocked(url, description) {
+  promise_test(t => {
+    assert_no_event(t, navigator.serviceWorker, "message");
+    // If |url| is a blob, it will be stripped down to "blob" for reporting.
+    var reportedURL = new URL(url).protocol == "blob:" ? "blob" : url;
+    return Promise.all([
+      waitUntilCSPEventForURL(t, reportedURL)
+        .then(t.step_func_done(e => {
+          assert_equals(e.blockedURI, reportedURL);
+          assert_equals(e.violatedDirective, "worker-src");
+          assert_equals(e.effectiveDirective, "worker-src");
+        })),
+      promise_rejects(t, "SecurityError", navigator.serviceWorker.register(url, { scope: url }))
+    ]);
+  }, description);
+}
+
diff --git a/third_party/WebKit/LayoutTests/http/tests/security/contentSecurityPolicy/service-worker-blocked-expected.txt b/third_party/WebKit/LayoutTests/http/tests/security/contentSecurityPolicy/service-worker-blocked-expected.txt
index 212517a..935ff994 100644
--- a/third_party/WebKit/LayoutTests/http/tests/security/contentSecurityPolicy/service-worker-blocked-expected.txt
+++ b/third_party/WebKit/LayoutTests/http/tests/security/contentSecurityPolicy/service-worker-blocked-expected.txt
@@ -1,4 +1,4 @@
-CONSOLE ERROR: line 9: Refused to create a child context containing 'http://127.0.0.1:8000/security/contentSecurityPolicy/resources/service-worker.js' because it violates the following Content Security Policy directive: "child-src 'none'".
+CONSOLE ERROR: line 9: Refused to create a worker from 'http://127.0.0.1:8000/security/contentSecurityPolicy/resources/service-worker.js' because it violates the following Content Security Policy directive: "child-src 'none'".
 
 This is a testharness.js-based test.
 PASS Test that a service worker cannot be registered if the CSP does not allow it 
diff --git a/third_party/WebKit/LayoutTests/http/tests/security/contentSecurityPolicy/worker-src/dedicated-child.html b/third_party/WebKit/LayoutTests/http/tests/security/contentSecurityPolicy/worker-src/dedicated-child.html
new file mode 100644
index 0000000..da1fbc1
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/http/tests/security/contentSecurityPolicy/worker-src/dedicated-child.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/testharness-helper.js"></script>
+<meta http-equiv="Content-Security-Policy" content="child-src http://127.0.0.1:8000 blob:">
+<script>
+  var url = new URL("/security/contentSecurityPolicy/resources/ping.js", document.baseURI).toString();
+  assert_worker_is_loaded(url, "Same-origin dedicated worker allowed by host-source expression.");
+
+  var b = new Blob(["postMessage('ping');"], {type: "text/javascript"});
+  var url = URL.createObjectURL(b);
+  assert_worker_is_loaded(url, "blob: dedicated worker allowed by 'blob:'.");
+</script>
diff --git a/third_party/WebKit/LayoutTests/http/tests/security/contentSecurityPolicy/worker-src/dedicated-fallback.html b/third_party/WebKit/LayoutTests/http/tests/security/contentSecurityPolicy/worker-src/dedicated-fallback.html
new file mode 100644
index 0000000..6de1985
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/http/tests/security/contentSecurityPolicy/worker-src/dedicated-fallback.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/testharness-helper.js"></script>
+<meta http-equiv="Content-Security-Policy" content="worker-src http://127.0.0.1:8000 blob:; child-src 'none'">
+<script>
+  var url = new URL("/security/contentSecurityPolicy/resources/ping.js", document.baseURI).toString();
+  assert_worker_is_loaded(url, "Same-origin dedicated worker allowed by host-source expression.");
+
+  var b = new Blob(["postMessage('ping');"], {type: "text/javascript"});
+  var url = URL.createObjectURL(b);
+  assert_worker_is_loaded(url, "blob: dedicated worker allowed by 'blob:'.");
+</script>
diff --git a/third_party/WebKit/LayoutTests/http/tests/security/contentSecurityPolicy/worker-src/dedicated-list.html b/third_party/WebKit/LayoutTests/http/tests/security/contentSecurityPolicy/worker-src/dedicated-list.html
new file mode 100644
index 0000000..8b19f53
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/http/tests/security/contentSecurityPolicy/worker-src/dedicated-list.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/testharness-helper.js"></script>
+<meta http-equiv="Content-Security-Policy" content="worker-src http://127.0.0.1:8000 blob:">
+<script>
+  var url = new URL("/security/contentSecurityPolicy/resources/ping.js", document.baseURI).toString();
+  assert_worker_is_loaded(url, "Same-origin dedicated worker allowed by host-source expression.");
+
+  var b = new Blob(["postMessage('ping');"], {type: "text/javascript"});
+  var url = URL.createObjectURL(b);
+  assert_worker_is_loaded(url, "blob: dedicated worker allowed by 'blob:'.");
+</script>
diff --git a/third_party/WebKit/LayoutTests/http/tests/security/contentSecurityPolicy/worker-src/dedicated-none.html b/third_party/WebKit/LayoutTests/http/tests/security/contentSecurityPolicy/worker-src/dedicated-none.html
new file mode 100644
index 0000000..3377775
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/http/tests/security/contentSecurityPolicy/worker-src/dedicated-none.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/testharness-helper.js"></script>
+<meta http-equiv="Content-Security-Policy" content="worker-src 'none'">
+<script>
+  var url = new URL("/security/contentSecurityPolicy/resources/ping.js", document.baseURI).toString();
+  assert_worker_is_blocked(url, "Same-origin dedicated worker blocked by host-source expression.");
+
+  var b = new Blob(["postMessage('ping');"], {type: "text/javascript"});
+  var url = URL.createObjectURL(b);
+  assert_worker_is_blocked(url, "blob: dedicated worker blocked by 'blob:'.");
+</script>
diff --git a/third_party/WebKit/LayoutTests/http/tests/security/contentSecurityPolicy/worker-src/dedicated-self.html b/third_party/WebKit/LayoutTests/http/tests/security/contentSecurityPolicy/worker-src/dedicated-self.html
new file mode 100644
index 0000000..b504a6d
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/http/tests/security/contentSecurityPolicy/worker-src/dedicated-self.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/testharness-helper.js"></script>
+<meta http-equiv="Content-Security-Policy" content="worker-src 'self'">
+<script>
+  var url = new URL("/security/contentSecurityPolicy/resources/ping.js", document.baseURI).toString();
+  assert_worker_is_loaded(url, "Same-origin dedicated worker allowed by 'self'.");
+</script>
diff --git a/third_party/WebKit/LayoutTests/http/tests/security/contentSecurityPolicy/worker-src/service-child.html b/third_party/WebKit/LayoutTests/http/tests/security/contentSecurityPolicy/worker-src/service-child.html
new file mode 100644
index 0000000..9b897fc
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/http/tests/security/contentSecurityPolicy/worker-src/service-child.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/testharness-helper.js"></script>
+<meta http-equiv="Content-Security-Policy" content="child-src http://127.0.0.1:8000">
+<script>
+  var url = new URL("/security/contentSecurityPolicy/resources/ping.js", document.baseURI).toString();
+  assert_service_worker_is_loaded(url, "Same-origin service worker allowed by host-source expression.");
+</script>
+
diff --git a/third_party/WebKit/LayoutTests/http/tests/security/contentSecurityPolicy/worker-src/service-fallback.html b/third_party/WebKit/LayoutTests/http/tests/security/contentSecurityPolicy/worker-src/service-fallback.html
new file mode 100644
index 0000000..3ce8e54
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/http/tests/security/contentSecurityPolicy/worker-src/service-fallback.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/testharness-helper.js"></script>
+<meta http-equiv="Content-Security-Policy" content="worker-src http://127.0.0.1:8000; child-src 'none'">
+<script>
+  var url = new URL("/security/contentSecurityPolicy/resources/ping.js", document.baseURI).toString();
+  assert_service_worker_is_loaded(url, "Same-origin service worker allowed by host-source expression.");
+</script>
diff --git a/third_party/WebKit/LayoutTests/http/tests/security/contentSecurityPolicy/worker-src/service-list.html b/third_party/WebKit/LayoutTests/http/tests/security/contentSecurityPolicy/worker-src/service-list.html
new file mode 100644
index 0000000..6f71549
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/http/tests/security/contentSecurityPolicy/worker-src/service-list.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/testharness-helper.js"></script>
+<meta http-equiv="Content-Security-Policy" content="worker-src http://127.0.0.1:8000">
+<script>
+  var url = new URL("/security/contentSecurityPolicy/resources/ping.js", document.baseURI).toString();
+  assert_service_worker_is_loaded(url, "Same-origin service worker allowed by host-source expression.");
+</script>
diff --git a/third_party/WebKit/LayoutTests/http/tests/security/contentSecurityPolicy/worker-src/service-none.html b/third_party/WebKit/LayoutTests/http/tests/security/contentSecurityPolicy/worker-src/service-none.html
new file mode 100644
index 0000000..2b239e2
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/http/tests/security/contentSecurityPolicy/worker-src/service-none.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/testharness-helper.js"></script>
+<meta http-equiv="Content-Security-Policy" content="worker-src 'none'">
+<script>
+  var url = new URL("/security/contentSecurityPolicy/resources/ping.js", document.baseURI).toString();
+  assert_service_worker_is_blocked(url, "Same-origin service worker blocked by 'none'.");
+</script>
diff --git a/third_party/WebKit/LayoutTests/http/tests/security/contentSecurityPolicy/worker-src/service-self.html b/third_party/WebKit/LayoutTests/http/tests/security/contentSecurityPolicy/worker-src/service-self.html
new file mode 100644
index 0000000..5fdccae
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/http/tests/security/contentSecurityPolicy/worker-src/service-self.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/testharness-helper.js"></script>
+<meta http-equiv="Content-Security-Policy" content="worker-src 'self'">
+<script>
+  var url = new URL("/security/contentSecurityPolicy/resources/ping.js", document.baseURI).toString();
+  assert_service_worker_is_loaded(url, "Same-origin service worker allowed by 'self'.");
+</script>
diff --git a/third_party/WebKit/LayoutTests/http/tests/security/contentSecurityPolicy/worker-src/shared-child.html b/third_party/WebKit/LayoutTests/http/tests/security/contentSecurityPolicy/worker-src/shared-child.html
new file mode 100644
index 0000000..8376498
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/http/tests/security/contentSecurityPolicy/worker-src/shared-child.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/testharness-helper.js"></script>
+<meta http-equiv="Content-Security-Policy" content="child-src http://127.0.0.1:8000 blob:">
+<script>
+  var url = new URL("/security/contentSecurityPolicy/resources/ping.js", document.baseURI).toString();
+  assert_shared_worker_is_loaded(url, "Same-origin dedicated worker allowed by 'self'.");
+
+  var b = new Blob(["onconnect = e => { e.ports[0].postMessage('ping'); }"], {type: "text/javascript"});
+  var url = URL.createObjectURL(b);
+  assert_shared_worker_is_loaded(url, "blob: dedicated worker allowed by 'blob:'.");
+</script>
diff --git a/third_party/WebKit/LayoutTests/http/tests/security/contentSecurityPolicy/worker-src/shared-fallback.html b/third_party/WebKit/LayoutTests/http/tests/security/contentSecurityPolicy/worker-src/shared-fallback.html
new file mode 100644
index 0000000..2a8754b
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/http/tests/security/contentSecurityPolicy/worker-src/shared-fallback.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/testharness-helper.js"></script>
+<meta http-equiv="Content-Security-Policy" content="worker-src http://127.0.0.1:8000 blob:; child-src 'none'">
+<script>
+  var url = new URL("/security/contentSecurityPolicy/resources/ping.js", document.baseURI).toString();
+  assert_shared_worker_is_loaded(url, "Same-origin dedicated worker allowed by 'self'.");
+
+  var b = new Blob(["onconnect = e => { e.ports[0].postMessage('ping'); }"], {type: "text/javascript"});
+  var url = URL.createObjectURL(b);
+  assert_shared_worker_is_loaded(url, "blob: dedicated worker allowed by 'blob:'.");
+</script>
diff --git a/third_party/WebKit/LayoutTests/http/tests/security/contentSecurityPolicy/worker-src/shared-list.html b/third_party/WebKit/LayoutTests/http/tests/security/contentSecurityPolicy/worker-src/shared-list.html
new file mode 100644
index 0000000..43d7481
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/http/tests/security/contentSecurityPolicy/worker-src/shared-list.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/testharness-helper.js"></script>
+<meta http-equiv="Content-Security-Policy" content="worker-src http://127.0.0.1:8000 blob:">
+<script>
+  var url = new URL("/security/contentSecurityPolicy/resources/ping.js", document.baseURI).toString();
+  assert_shared_worker_is_loaded(url, "Same-origin dedicated worker allowed by 'self'.");
+
+  var b = new Blob(["onconnect = e => { e.ports[0].postMessage('ping'); }"], {type: "text/javascript"});
+  var url = URL.createObjectURL(b);
+  assert_shared_worker_is_loaded(url, "blob: dedicated worker allowed by 'blob:'.");
+</script>
diff --git a/third_party/WebKit/LayoutTests/http/tests/security/contentSecurityPolicy/worker-src/shared-none.html b/third_party/WebKit/LayoutTests/http/tests/security/contentSecurityPolicy/worker-src/shared-none.html
new file mode 100644
index 0000000..9a24cf7
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/http/tests/security/contentSecurityPolicy/worker-src/shared-none.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/testharness-helper.js"></script>
+<meta http-equiv="Content-Security-Policy" content="worker-src 'none'">
+<script>
+  var url = new URL("/security/contentSecurityPolicy/resources/ping.js", document.baseURI).toString();
+  assert_shared_worker_is_blocked(url, "Same-origin shared worker blocked by 'none'.");
+
+  var b = new Blob(["onconnect = e => { e.ports[0].postMessage('ping'); }"], {type: "text/javascript"});
+  var url = URL.createObjectURL(b);
+  assert_shared_worker_is_blocked(url, "blob: shared worker blocked by 'none'.");
+</script>
diff --git a/third_party/WebKit/LayoutTests/http/tests/security/contentSecurityPolicy/worker-src/shared-self.html b/third_party/WebKit/LayoutTests/http/tests/security/contentSecurityPolicy/worker-src/shared-self.html
new file mode 100644
index 0000000..42d3da2
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/http/tests/security/contentSecurityPolicy/worker-src/shared-self.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/testharness-helper.js"></script>
+<meta http-equiv="Content-Security-Policy" content="worker-src 'self'">
+<script>
+  var url = new URL("/security/contentSecurityPolicy/resources/ping.js", document.baseURI).toString();
+  assert_shared_worker_is_loaded(url, "Same-origin dedicated worker allowed by 'self'.");
+</script>
+
diff --git a/third_party/WebKit/Source/core/frame/csp/CSPDirectiveList.cpp b/third_party/WebKit/Source/core/frame/csp/CSPDirectiveList.cpp
index 9d232e9..4bdfefc 100644
--- a/third_party/WebKit/Source/core/frame/csp/CSPDirectiveList.cpp
+++ b/third_party/WebKit/Source/core/frame/csp/CSPDirectiveList.cpp
@@ -430,11 +430,17 @@
   if (checkSource(directive, url, redirectStatus) && !checkDynamic(directive))
     return true;
 
+  // We should never have a violation against `child-src` or `default-src`
+  // directly; the effective directive should always be one of the explicit
+  // fetch directives.
+  DCHECK_NE(ContentSecurityPolicy::ChildSrc, effectiveDirective);
+  DCHECK_NE(ContentSecurityPolicy::DefaultSrc, effectiveDirective);
+
   String prefix;
   if (ContentSecurityPolicy::BaseURI == effectiveDirective)
     prefix = "Refused to set the document's base URI to '";
-  else if (ContentSecurityPolicy::ChildSrc == effectiveDirective)
-    prefix = "Refused to create a child context containing '";
+  else if (ContentSecurityPolicy::WorkerSrc == effectiveDirective)
+    prefix = "Refused to create a worker from '";
   else if (ContentSecurityPolicy::ConnectSrc == effectiveDirective)
     prefix = "Refused to connect to '";
   else if (ContentSecurityPolicy::FontSrc == effectiveDirective)
@@ -626,15 +632,14 @@
                            redirectStatus);
 }
 
-bool CSPDirectiveList::allowChildFrameFromSource(
+bool CSPDirectiveList::allowFrameFromSource(
     const KURL& url,
     ResourceRequest::RedirectStatus redirectStatus,
     ContentSecurityPolicy::ReportingStatus reportingStatus) const {
   if (url.protocolIsAbout())
     return true;
 
-  // 'frame-src' is the only directive which overrides something other than the
-  // default sources.  It overrides 'child-src', which overrides the default
+  // 'frame-src' overrides 'child-src', which overrides the default
   // sources. So, we do this nested set of calls to 'operativeDirective()' to
   // grab 'frame-src' if it exists, 'child-src' if it doesn't, and 'defaut-src'
   // if neither are available.
@@ -745,16 +750,22 @@
              : checkSource(m_baseURI.get(), url, redirectStatus);
 }
 
-bool CSPDirectiveList::allowChildContextFromSource(
+bool CSPDirectiveList::allowWorkerFromSource(
     const KURL& url,
     ResourceRequest::RedirectStatus redirectStatus,
     ContentSecurityPolicy::ReportingStatus reportingStatus) const {
+  // 'worker-src' overrides 'child-src', which overrides the default
+  // sources. So, we do this nested set of calls to 'operativeDirective()' to
+  // grab 'worker-src' if it exists, 'child-src' if it doesn't, and 'defaut-src'
+  // if neither are available.
+  SourceListDirective* whichDirective = operativeDirective(
+      m_workerSrc.get(), operativeDirective(m_childSrc.get()));
+
   return reportingStatus == ContentSecurityPolicy::SendReport
-             ? checkSourceAndReportViolation(
-                   operativeDirective(m_childSrc.get()), url,
-                   ContentSecurityPolicy::ChildSrc, redirectStatus)
-             : checkSource(operativeDirective(m_childSrc.get()), url,
-                           redirectStatus);
+             ? checkSourceAndReportViolation(whichDirective, url,
+                                             ContentSecurityPolicy::WorkerSrc,
+                                             redirectStatus)
+             : checkSource(whichDirective, url, redirectStatus);
 }
 
 bool CSPDirectiveList::allowAncestors(
@@ -1106,6 +1117,8 @@
     setCSPDirective<SourceListDirective>(name, value, m_baseURI);
   } else if (equalIgnoringCase(name, ContentSecurityPolicy::ChildSrc)) {
     setCSPDirective<SourceListDirective>(name, value, m_childSrc);
+  } else if (equalIgnoringCase(name, ContentSecurityPolicy::WorkerSrc)) {
+    setCSPDirective<SourceListDirective>(name, value, m_workerSrc);
   } else if (equalIgnoringCase(name, ContentSecurityPolicy::FormAction)) {
     setCSPDirective<SourceListDirective>(name, value, m_formAction);
   } else if (equalIgnoringCase(name, ContentSecurityPolicy::PluginTypes)) {
@@ -1146,6 +1159,7 @@
   visitor->trace(m_objectSrc);
   visitor->trace(m_scriptSrc);
   visitor->trace(m_styleSrc);
+  visitor->trace(m_workerSrc);
 }
 
 }  // namespace blink
diff --git a/third_party/WebKit/Source/core/frame/csp/CSPDirectiveList.h b/third_party/WebKit/Source/core/frame/csp/CSPDirectiveList.h
index c4c9215..b305e5b 100644
--- a/third_party/WebKit/Source/core/frame/csp/CSPDirectiveList.h
+++ b/third_party/WebKit/Source/core/frame/csp/CSPDirectiveList.h
@@ -83,9 +83,9 @@
   bool allowObjectFromSource(const KURL&,
                              ResourceRequest::RedirectStatus,
                              ContentSecurityPolicy::ReportingStatus) const;
-  bool allowChildFrameFromSource(const KURL&,
-                                 ResourceRequest::RedirectStatus,
-                                 ContentSecurityPolicy::ReportingStatus) const;
+  bool allowFrameFromSource(const KURL&,
+                            ResourceRequest::RedirectStatus,
+                            ContentSecurityPolicy::ReportingStatus) const;
   bool allowImageFromSource(const KURL&,
                             ResourceRequest::RedirectStatus,
                             ContentSecurityPolicy::ReportingStatus) const;
@@ -107,10 +107,9 @@
   bool allowBaseURI(const KURL&,
                     ResourceRequest::RedirectStatus,
                     ContentSecurityPolicy::ReportingStatus) const;
-  bool allowChildContextFromSource(
-      const KURL&,
-      ResourceRequest::RedirectStatus,
-      ContentSecurityPolicy::ReportingStatus) const;
+  bool allowWorkerFromSource(const KURL&,
+                             ResourceRequest::RedirectStatus,
+                             ContentSecurityPolicy::ReportingStatus) const;
   // |allowAncestors| does not need to know whether the resource was a
   // result of a redirect. After a redirect, source paths are usually
   // ignored to stop a page from learning the path to which the
@@ -294,6 +293,7 @@
   Member<SourceListDirective> m_objectSrc;
   Member<SourceListDirective> m_scriptSrc;
   Member<SourceListDirective> m_styleSrc;
+  Member<SourceListDirective> m_workerSrc;
 
   uint8_t m_requireSRIFor;
 
diff --git a/third_party/WebKit/Source/core/frame/csp/CSPDirectiveListTest.cpp b/third_party/WebKit/Source/core/frame/csp/CSPDirectiveListTest.cpp
index de8284a..2fcde83 100644
--- a/third_party/WebKit/Source/core/frame/csp/CSPDirectiveListTest.cpp
+++ b/third_party/WebKit/Source/core/frame/csp/CSPDirectiveListTest.cpp
@@ -391,4 +391,48 @@
   }
 }
 
+TEST_F(CSPDirectiveListTest, workerSrc) {
+  struct TestCase {
+    const char* list;
+    bool allowed;
+  } cases[] = {
+      {"worker-src 'none'", false},
+      {"worker-src http://not.example.test", false},
+      {"worker-src https://example.test", true},
+      {"default-src *; worker-src 'none'", false},
+      {"default-src *; worker-src http://not.example.test", false},
+      {"default-src *; worker-src https://example.test", true},
+      {"child-src *; worker-src 'none'", false},
+      {"child-src *; worker-src http://not.example.test", false},
+      {"child-src *; worker-src https://example.test", true},
+      {"default-src *; child-src *; worker-src 'none'", false},
+      {"default-src *; child-src *; worker-src http://not.example.test", false},
+      {"default-src *; child-src *; worker-src https://example.test", true},
+
+      // Fallback to child-src.
+      {"child-src 'none'", false},
+      {"child-src http://not.example.test", false},
+      {"child-src https://example.test", true},
+      {"default-src *; child-src 'none'", false},
+      {"default-src *; child-src http://not.example.test", false},
+      {"default-src *; child-src https://example.test", true},
+
+      // Fallback to default-src.
+      {"default-src 'none'", false},
+      {"default-src http://not.example.test", false},
+      {"default-src https://example.test", true},
+  };
+
+  for (const auto& test : cases) {
+    SCOPED_TRACE(test.list);
+    KURL resource = KURL(KURL(), "https://example.test/worker.js");
+    Member<CSPDirectiveList> directiveList =
+        createList(test.list, ContentSecurityPolicyHeaderTypeEnforce);
+    EXPECT_EQ(test.allowed,
+              directiveList->allowWorkerFromSource(
+                  resource, ResourceRequest::RedirectStatus::NoRedirect,
+                  ContentSecurityPolicy::SuppressReport));
+  }
+}
+
 }  // namespace blink
diff --git a/third_party/WebKit/Source/core/frame/csp/ContentSecurityPolicy.cpp b/third_party/WebKit/Source/core/frame/csp/ContentSecurityPolicy.cpp
index 52ef1de..3450842 100644
--- a/third_party/WebKit/Source/core/frame/csp/ContentSecurityPolicy.cpp
+++ b/third_party/WebKit/Source/core/frame/csp/ContentSecurityPolicy.cpp
@@ -92,9 +92,9 @@
 const char ContentSecurityPolicy::FrameAncestors[] = "frame-ancestors";
 const char ContentSecurityPolicy::PluginTypes[] = "plugin-types";
 
-// CSP Editor's Draft:
-// https://w3c.github.io/webappsec/specs/content-security-policy
+// CSP Level 3 Directives
 const char ContentSecurityPolicy::ManifestSrc[] = "manifest-src";
+const char ContentSecurityPolicy::WorkerSrc[] = "worker-src";
 
 // Mixed Content Directive
 // https://w3c.github.io/webappsec/specs/mixedcontent/#strict-mode
@@ -125,6 +125,7 @@
       equalIgnoringCase(name, FrameAncestors) ||
       equalIgnoringCase(name, PluginTypes) ||
       equalIgnoringCase(name, ManifestSrc) ||
+      equalIgnoringCase(name, WorkerSrc) ||
       equalIgnoringCase(name, BlockAllMixedContent) ||
       equalIgnoringCase(name, UpgradeInsecureRequests) ||
       equalIgnoringCase(name, TreatAsPublicAddress) ||
@@ -844,7 +845,7 @@
       return allowFormAction(url, redirectStatus, reportingStatus);
     case WebURLRequest::RequestContextFrame:
     case WebURLRequest::RequestContextIframe:
-      return allowChildFrameFromSource(url, redirectStatus, reportingStatus);
+      return allowFrameFromSource(url, redirectStatus, reportingStatus);
     case WebURLRequest::RequestContextImport:
     case WebURLRequest::RequestContextScript:
       return allowScriptFromSource(url, nonce, parserDisposition,
@@ -892,11 +893,11 @@
       m_policies, url, redirectStatus, reportingStatus);
 }
 
-bool ContentSecurityPolicy::allowChildFrameFromSource(
+bool ContentSecurityPolicy::allowFrameFromSource(
     const KURL& url,
     RedirectStatus redirectStatus,
     ContentSecurityPolicy::ReportingStatus reportingStatus) const {
-  return isAllowedByAll<&CSPDirectiveList::allowChildFrameFromSource>(
+  return isAllowedByAll<&CSPDirectiveList::allowFrameFromSource>(
       m_policies, url, redirectStatus, reportingStatus);
 }
 
@@ -971,7 +972,7 @@
   // impact of this backwards-incompatible change.
   if (Document* document = this->document()) {
     UseCounter::count(*document, UseCounter::WorkerSubjectToCSP);
-    if (isAllowedByAll<&CSPDirectiveList::allowChildContextFromSource>(
+    if (isAllowedByAll<&CSPDirectiveList::allowWorkerFromSource>(
             m_policies, url, redirectStatus, SuppressReport) &&
         !isAllowedByAll<&CSPDirectiveList::allowScriptFromSource>(
             m_policies, url, AtomicString(), NotParserInserted, redirectStatus,
@@ -981,7 +982,7 @@
     }
   }
 
-  return isAllowedByAll<&CSPDirectiveList::allowChildContextFromSource>(
+  return isAllowedByAll<&CSPDirectiveList::allowWorkerFromSource>(
       m_policies, url, redirectStatus, reportingStatus);
 }
 
diff --git a/third_party/WebKit/Source/core/frame/csp/ContentSecurityPolicy.h b/third_party/WebKit/Source/core/frame/csp/ContentSecurityPolicy.h
index 2bb64ac..87ee3397 100644
--- a/third_party/WebKit/Source/core/frame/csp/ContentSecurityPolicy.h
+++ b/third_party/WebKit/Source/core/frame/csp/ContentSecurityPolicy.h
@@ -92,9 +92,9 @@
   static const char FrameAncestors[];
   static const char PluginTypes[];
 
-  // Manifest Directives (to be merged into CSP Level 2)
-  // https://w3c.github.io/manifest/#content-security-policy
+  // CSP Level 3 Directives
   static const char ManifestSrc[];
+  static const char WorkerSrc[];
 
   // Mixed Content Directive
   // https://w3c.github.io/webappsec/specs/mixedcontent/#strict-mode
@@ -182,9 +182,9 @@
   bool allowObjectFromSource(const KURL&,
                              RedirectStatus = RedirectStatus::NoRedirect,
                              ReportingStatus = SendReport) const;
-  bool allowChildFrameFromSource(const KURL&,
-                                 RedirectStatus = RedirectStatus::NoRedirect,
-                                 ReportingStatus = SendReport) const;
+  bool allowFrameFromSource(const KURL&,
+                            RedirectStatus = RedirectStatus::NoRedirect,
+                            ReportingStatus = SendReport) const;
   bool allowImageFromSource(const KURL&,
                             RedirectStatus = RedirectStatus::NoRedirect,
                             ReportingStatus = SendReport) const;
diff --git a/third_party/WebKit/Source/core/loader/FrameLoader.cpp b/third_party/WebKit/Source/core/loader/FrameLoader.cpp
index 23c0121c..d9ba74a 100644
--- a/third_party/WebKit/Source/core/loader/FrameLoader.cpp
+++ b/third_party/WebKit/Source/core/loader/FrameLoader.cpp
@@ -1596,8 +1596,8 @@
     if (parentFrame) {
       ContentSecurityPolicy* parentPolicy =
           parentFrame->securityContext()->contentSecurityPolicy();
-      if (!parentPolicy->allowChildFrameFromSource(request.url(),
-                                                   request.redirectStatus())) {
+      if (!parentPolicy->allowFrameFromSource(request.url(),
+                                              request.redirectStatus())) {
         // Fire a load event, as timing attacks would otherwise reveal that the
         // frame was blocked. This way, it looks like every other cross-origin
         // page load.