Worker: Return early in TaskWorklet/WorkerTaskQueue::postTask() after context destruction

Bug: 965283
Change-Id: Ia8a959a8ed2d5423231001ea59957d09652caf2d
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1624007
Reviewed-by: Matt Falkenhagen <falken@chromium.org>
Commit-Queue: Hiroki Nakagawa <nhiroki@chromium.org>
Cr-Commit-Position: refs/heads/master@{#662112}
diff --git a/third_party/blink/renderer/core/workers/experimental/task_worklet.cc b/third_party/blink/renderer/core/workers/experimental/task_worklet.cc
index aa76962..6d070db 100644
--- a/third_party/blink/renderer/core/workers/experimental/task_worklet.cc
+++ b/third_party/blink/renderer/core/workers/experimental/task_worklet.cc
@@ -16,7 +16,7 @@
 
 class TaskWorkletMessagingProxy final : public ThreadedWorkletMessagingProxy {
  public:
-  TaskWorkletMessagingProxy(ExecutionContext* context)
+  explicit TaskWorkletMessagingProxy(ExecutionContext* context)
       : ThreadedWorkletMessagingProxy(context) {}
   ~TaskWorkletMessagingProxy() override = default;
 
@@ -52,6 +52,12 @@
                             V8Function* function,
                             const Vector<ScriptValue>& arguments,
                             ExceptionState& exception_state) {
+  if (!GetExecutionContext() || GetExecutionContext()->IsContextDestroyed()) {
+    exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
+                                      "This frame is already detached");
+    return nullptr;
+  }
+
   // TODO(japhet): Here and below: it's unclear what task type should be used,
   // and whether the API should allow it to be configured. Using kIdleTask as a
   // placeholder for now.
@@ -67,6 +73,12 @@
                             const String& function_name,
                             const Vector<ScriptValue>& arguments,
                             ExceptionState& exception_state) {
+  if (!GetExecutionContext() || GetExecutionContext()->IsContextDestroyed()) {
+    exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
+                                      "This frame is already detached");
+    return nullptr;
+  }
+
   Task* task =
       MakeGarbageCollected<Task>(script_state, this, function_name, arguments,
                                  TaskType::kIdleTask, exception_state);
diff --git a/third_party/blink/renderer/core/workers/experimental/worker_task_queue.cc b/third_party/blink/renderer/core/workers/experimental/worker_task_queue.cc
index d7b2697..3c499bc 100644
--- a/third_party/blink/renderer/core/workers/experimental/worker_task_queue.cc
+++ b/third_party/blink/renderer/core/workers/experimental/worker_task_queue.cc
@@ -15,9 +15,11 @@
 WorkerTaskQueue* WorkerTaskQueue::Create(ExecutionContext* context,
                                          const String& type,
                                          ExceptionState& exception_state) {
+  DCHECK(context->IsContextThread());
+
   if (context->IsContextDestroyed()) {
-    exception_state.ThrowDOMException(DOMExceptionCode::kInvalidAccessError,
-                                      "The context provided is invalid.");
+    exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
+                                      "This frame is already detached.");
     return nullptr;
   }
 
@@ -45,6 +47,12 @@
     ExceptionState& exception_state) {
   DCHECK(document_->IsContextThread());
 
+  if (document_->IsContextDestroyed()) {
+    exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
+                                      "This frame is already detached");
+    return ScriptPromise();
+  }
+
   Task* task = MakeGarbageCollected<Task>(
       script_state, ThreadPool::From(*document_), function, arguments,
       task_type_, exception_state);
@@ -61,6 +69,13 @@
                                 const Vector<ScriptValue>& arguments,
                                 ExceptionState& exception_state) {
   DCHECK(document_->IsContextThread());
+
+  if (document_->IsContextDestroyed()) {
+    exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
+                                      "This frame is already detached");
+    return nullptr;
+  }
+
   Task* task = MakeGarbageCollected<Task>(
       script_state, ThreadPool::From(*document_), function, arguments,
       task_type_, exception_state);
diff --git a/third_party/blink/web_tests/fast/workers/taskqueue/taskWorklet-on-detached-frame.html b/third_party/blink/web_tests/fast/workers/taskqueue/taskWorklet-on-detached-frame.html
new file mode 100644
index 0000000..68b94313
--- /dev/null
+++ b/third_party/blink/web_tests/fast/workers/taskqueue/taskWorklet-on-detached-frame.html
@@ -0,0 +1,23 @@
+<body>
+<script src="../../../resources/testharness.js"></script>
+<script src="../../../resources/testharnessreport.js"></script>
+<script>
+
+test(() => {
+  const frame = document.createElement('iframe');
+  document.body.appendChild(frame);
+  const tw = frame.contentWindow.taskWorklet;
+  frame.remove();
+  assert_throws('InvalidStateError', () => tw.postTask(i => i, 2));
+}, 'taskWorklet.postTask() with a function on a detached frame.');
+
+test(() => {
+  const frame = document.createElement('iframe');
+  document.body.appendChild(frame);
+  const tw = frame.contentWindow.taskWorklet;
+  frame.remove();
+  assert_throws('InvalidStateError', () => tw.postTask('sum', 1, 2));
+}, 'taskWorklet.postTask() with a named task on a detached frame.');
+
+</script>
+</body>
diff --git a/third_party/blink/web_tests/fast/workers/taskqueue/taskqueue-on-detached-frame.html b/third_party/blink/web_tests/fast/workers/taskqueue/taskqueue-on-detached-frame.html
new file mode 100644
index 0000000..c436abb
--- /dev/null
+++ b/third_party/blink/web_tests/fast/workers/taskqueue/taskqueue-on-detached-frame.html
@@ -0,0 +1,23 @@
+<body>
+<script src="../../../resources/testharness.js"></script>
+<script src="../../../resources/testharnessreport.js"></script>
+<script>
+
+test(() => {
+  const frame = document.createElement('iframe');
+  document.body.appendChild(frame);
+  const wtq = frame.contentWindow.WorkerTaskQueue;
+  frame.remove();
+  assert_throws('InvalidStateError', () => new wtq('background'));
+}, 'WorkerTaskQueue construction on a detached frame.');
+
+test(() => {
+  const frame = document.createElement('iframe');
+  document.body.appendChild(frame);
+  const queue = new frame.contentWindow.WorkerTaskQueue('background');
+  frame.remove();
+  assert_throws('InvalidStateError', () => queue.postTask(i => i, 2));
+}, 'postTask() on a detached frame.');
+
+</script>
+</body>