Job.cpp 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  1. /*
  2. * Copyright (c) 2024, Andrew Kaster <akaster@ladybird.org>
  3. *
  4. * SPDX-License-Identifier: BSD-2-Clause
  5. */
  6. #include <LibJS/Heap/Heap.h>
  7. #include <LibJS/Runtime/VM.h>
  8. #include <LibWeb/HTML/Scripting/Environments.h>
  9. #include <LibWeb/HTML/Scripting/TemporaryExecutionContext.h>
  10. #include <LibWeb/ServiceWorker/Job.h>
  11. #include <LibWeb/WebIDL/Promise.h>
  12. namespace Web::ServiceWorker {
  13. JS_DEFINE_ALLOCATOR(Job);
  14. // https://w3c.github.io/ServiceWorker/#create-job
  15. JS::NonnullGCPtr<Job> Job::create(JS::VM& vm, Job::Type type, StorageAPI::StorageKey storage_key, URL::URL scope_url, URL::URL script_url, JS::GCPtr<WebIDL::Promise> promise, JS::GCPtr<HTML::EnvironmentSettingsObject> client)
  16. {
  17. return vm.heap().allocate_without_realm<Job>(type, move(storage_key), move(scope_url), move(script_url), promise, client);
  18. }
  19. Job::Job(Job::Type type, StorageAPI::StorageKey storage_key, URL::URL scope_url, URL::URL script_url, JS::GCPtr<WebIDL::Promise> promise, JS::GCPtr<HTML::EnvironmentSettingsObject> client)
  20. : job_type(type)
  21. , storage_key(move(storage_key))
  22. , scope_url(move(scope_url))
  23. , script_url(move(script_url))
  24. , client(client)
  25. , job_promise(promise)
  26. {
  27. // 8. If client is not null, set job’s referrer to client’s creation URL.
  28. if (client)
  29. referrer = client->creation_url;
  30. }
  31. Job::~Job() = default;
  32. void Job::visit_edges(JS::Cell::Visitor& visitor)
  33. {
  34. Base::visit_edges(visitor);
  35. visitor.visit(client);
  36. visitor.visit(job_promise);
  37. for (auto& job : list_of_equivalent_jobs)
  38. visitor.visit(job);
  39. }
  40. // FIXME: Does this need to be a 'user agent' level thing? Or can we have one per renderer process?
  41. // https://w3c.github.io/ServiceWorker/#dfn-scope-to-job-queue-map
  42. static HashMap<ByteString, JobQueue>& scope_to_job_queue_map()
  43. {
  44. static HashMap<ByteString, JobQueue> map;
  45. return map;
  46. }
  47. static void register_(JS::VM& vm, JS::NonnullGCPtr<Job> job)
  48. {
  49. // If there's no client, there won't be any promises to resolve
  50. if (job->client) {
  51. auto context = HTML::TemporaryExecutionContext(*job->client, HTML::TemporaryExecutionContext::CallbacksEnabled::Yes);
  52. auto& realm = *vm.current_realm();
  53. WebIDL::reject_promise(realm, *job->job_promise, *vm.throw_completion<JS::InternalError>(JS::ErrorType::NotImplemented, "Service Worker registration"sv).value());
  54. }
  55. }
  56. static void update(JS::VM& vm, JS::NonnullGCPtr<Job> job)
  57. {
  58. // If there's no client, there won't be any promises to resolve
  59. if (job->client) {
  60. auto context = HTML::TemporaryExecutionContext(*job->client, HTML::TemporaryExecutionContext::CallbacksEnabled::Yes);
  61. auto& realm = *vm.current_realm();
  62. WebIDL::reject_promise(realm, *job->job_promise, *vm.throw_completion<JS::InternalError>(JS::ErrorType::NotImplemented, "Service Worker update"sv).value());
  63. }
  64. }
  65. static void unregister(JS::VM& vm, JS::NonnullGCPtr<Job> job)
  66. {
  67. // If there's no client, there won't be any promises to resolve
  68. if (job->client) {
  69. auto context = HTML::TemporaryExecutionContext(*job->client, HTML::TemporaryExecutionContext::CallbacksEnabled::Yes);
  70. auto& realm = *vm.current_realm();
  71. WebIDL::reject_promise(realm, *job->job_promise, *vm.throw_completion<JS::InternalError>(JS::ErrorType::NotImplemented, "Service Worker unregistration"sv).value());
  72. }
  73. }
  74. // https://w3c.github.io/ServiceWorker/#run-job
  75. static void run_job(JS::VM& vm, JobQueue& job_queue)
  76. {
  77. // 1. Assert: jobQueue is not empty.
  78. VERIFY(!job_queue.is_empty());
  79. // 2. Queue a task to run these steps:
  80. auto job_run_steps = JS::create_heap_function(vm.heap(), [&vm, &job_queue] {
  81. // 1. Let job be the first item in jobQueue.
  82. auto& job = job_queue.first();
  83. // FIXME: Do these really need to be in parallel to the HTML event loop? Sounds fishy
  84. switch (job->job_type) {
  85. case Job::Type::Register:
  86. // 2. If job’s job type is register, run Register with job in parallel.
  87. register_(vm, job);
  88. break;
  89. case Job::Type::Update:
  90. // 3. If job’s job type is update, run Update with job in parallel.
  91. update(vm, job);
  92. break;
  93. case Job::Type::Unregister:
  94. // 4. If job’s job type is unregister, run Unregister with job in parallel.
  95. unregister(vm, job);
  96. break;
  97. }
  98. });
  99. // FIXME: How does the user agent ensure this happens? Is this a normative note?
  100. // Spec-Note:
  101. // For a register job and an update job, the user agent delays queuing a task for running the job
  102. // until after a DOMContentLoaded event has been dispatched to the document that initiated the job.
  103. // FIXME: Spec should be updated to avoid 'queue a task' and use 'queue a global task' instead
  104. // FIXME: On which task source? On which event loop? On behalf of which document?
  105. HTML::queue_a_task(HTML::Task::Source::Unspecified, nullptr, nullptr, job_run_steps);
  106. }
  107. // https://w3c.github.io/ServiceWorker/#schedule-job
  108. void schedule_job(JS::VM& vm, JS::NonnullGCPtr<Job> job)
  109. {
  110. // 1. Let jobQueue be null.
  111. // Note: See below for how we ensure job queue
  112. // 2. Let jobScope be job’s scope url, serialized.
  113. auto job_scope = job->scope_url.serialize();
  114. // 3. If scope to job queue map[jobScope] does not exist, set scope to job queue map[jobScope] to a new job queue.
  115. // 4. Set jobQueue to scope to job queue map[jobScope].
  116. auto& job_queue = scope_to_job_queue_map().ensure(job_scope, [&vm] {
  117. return JobQueue(vm.heap());
  118. });
  119. // 5. If jobQueue is empty, then:
  120. if (job_queue.is_empty()) {
  121. // 2. Set job’s containing job queue to jobQueue, and enqueue job to jobQueue.
  122. job->containing_job_queue = &job_queue;
  123. job_queue.append(job);
  124. run_job(vm, job_queue);
  125. }
  126. // 6. Else:
  127. else {
  128. // 1. Let lastJob be the element at the back of jobQueue.
  129. auto& last_job = job_queue.last();
  130. // 2. If job is equivalent to lastJob and lastJob’s job promise has not settled, append job to lastJob’s list of equivalent jobs.
  131. // FIXME: There's no WebIDL AO that corresponds to checking if an ECMAScript promise has settled
  132. if (job == last_job && !verify_cast<JS::Promise>(*job->job_promise->promise()).is_handled()) {
  133. last_job->list_of_equivalent_jobs.append(job);
  134. }
  135. // 3. Else, set job’s containing job queue to jobQueue, and enqueue job to jobQueue.
  136. else {
  137. job->containing_job_queue = &job_queue;
  138. job_queue.append(job);
  139. }
  140. }
  141. }
  142. }