ServiceWorkerContainer.cpp 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  1. /*
  2. * Copyright (c) 2024, Tim Ledbetter <tim.ledbetter@ladybird.org>
  3. * Copyright (c) 2024, Jamie Mansfield <jmansfield@cadixdev.org>
  4. *
  5. * SPDX-License-Identifier: BSD-2-Clause
  6. */
  7. #include <LibJS/Runtime/Realm.h>
  8. #include <LibWeb/Bindings/Intrinsics.h>
  9. #include <LibWeb/Bindings/ServiceWorkerContainerPrototype.h>
  10. #include <LibWeb/DOMURL/DOMURL.h>
  11. #include <LibWeb/HTML/EventNames.h>
  12. #include <LibWeb/ServiceWorker/Job.h>
  13. #include <LibWeb/ServiceWorker/ServiceWorker.h>
  14. #include <LibWeb/ServiceWorker/ServiceWorkerContainer.h>
  15. #include <LibWeb/StorageAPI/StorageKey.h>
  16. namespace Web::ServiceWorker {
  17. GC_DEFINE_ALLOCATOR(ServiceWorkerContainer);
  18. ServiceWorkerContainer::ServiceWorkerContainer(JS::Realm& realm)
  19. : DOM::EventTarget(realm)
  20. , m_service_worker_client(HTML::relevant_settings_object(*this))
  21. {
  22. }
  23. ServiceWorkerContainer::~ServiceWorkerContainer() = default;
  24. void ServiceWorkerContainer::initialize(JS::Realm& realm)
  25. {
  26. Base::initialize(realm);
  27. WEB_SET_PROTOTYPE_FOR_INTERFACE(ServiceWorkerContainer);
  28. }
  29. void ServiceWorkerContainer::visit_edges(Cell::Visitor& visitor)
  30. {
  31. Base::visit_edges(visitor);
  32. visitor.visit(m_service_worker_client);
  33. }
  34. GC::Ref<ServiceWorkerContainer> ServiceWorkerContainer::create(JS::Realm& realm)
  35. {
  36. return realm.create<ServiceWorkerContainer>(realm);
  37. }
  38. // https://w3c.github.io/ServiceWorker/#navigator-service-worker-register
  39. GC::Ref<WebIDL::Promise> ServiceWorkerContainer::register_(String script_url, RegistrationOptions const& options)
  40. {
  41. auto& realm = this->realm();
  42. // Note: The register(scriptURL, options) method creates or updates a service worker registration for the given scope url.
  43. // If successful, a service worker registration ties the provided scriptURL to a scope url,
  44. // which is subsequently used for navigation matching.
  45. // 1. Let p be a promise.
  46. auto p = WebIDL::create_promise(realm);
  47. // FIXME: 2. Set scriptURL to the result of invoking Get Trusted Type compliant string with TrustedScriptURL,
  48. // this's relevant global object, scriptURL, "ServiceWorkerContainer register", and "script".
  49. // 3 Let client be this's service worker client.
  50. auto client = m_service_worker_client;
  51. // 4. Let scriptURL be the result of parsing scriptURL with this's relevant settings object’s API base URL.
  52. auto base_url = HTML::relevant_settings_object(*this).api_base_url();
  53. auto parsed_script_url = DOMURL::parse(script_url, base_url);
  54. // 5. Let scopeURL be null.
  55. Optional<URL::URL> scope_url;
  56. // 6. If options["scope"] exists, set scopeURL to the result of parsing options["scope"] with this's relevant settings object’s API base URL.
  57. if (options.scope.has_value()) {
  58. scope_url = DOMURL::parse(options.scope.value(), base_url);
  59. }
  60. // 7. Invoke Start Register with scopeURL, scriptURL, p, client, client’s creation URL, options["type"], and options["updateViaCache"].
  61. start_register(scope_url, parsed_script_url, p, client, client->creation_url, options.type, options.update_via_cache);
  62. // 8. Return p.
  63. return p;
  64. }
  65. // https://w3c.github.io/ServiceWorker/#start-register-algorithm
  66. void ServiceWorkerContainer::start_register(Optional<URL::URL> scope_url, URL::URL script_url, GC::Ref<WebIDL::Promise> promise, HTML::EnvironmentSettingsObject& client, URL::URL referrer, Bindings::WorkerType worker_type, Bindings::ServiceWorkerUpdateViaCache update_via_cache)
  67. {
  68. auto& realm = this->realm();
  69. auto& vm = realm.vm();
  70. // 1. If scriptURL is failure, reject promise with a TypeError and abort these steps.
  71. if (!script_url.is_valid()) {
  72. WebIDL::reject_promise(realm, promise, JS::TypeError::create(realm, "scriptURL is not a valid URL"sv));
  73. return;
  74. }
  75. // 2. Set scriptURL’s fragment to null.
  76. // Note: The user agent does not store the fragment of the script’s url.
  77. // This means that the fragment does not have an effect on identifying service workers.
  78. script_url.set_fragment({});
  79. // 3. If scriptURL’s scheme is not one of "http" and "https", reject promise with a TypeError and abort these steps.
  80. if (!script_url.scheme().is_one_of("http"sv, "https"sv)) {
  81. WebIDL::reject_promise(realm, promise, JS::TypeError::create(realm, "scriptURL must have a scheme of 'http' or 'https'"sv));
  82. return;
  83. }
  84. // 4. If any of the strings in scriptURL’s path contains either ASCII case-insensitive "%2f" or ASCII case-insensitive "%5c",
  85. // reject promise with a TypeError and abort these steps.
  86. auto invalid_path = script_url.paths().first_matching([&](auto& path) {
  87. return path.contains("%2f"sv, CaseSensitivity::CaseInsensitive) || path.contains("%5c"sv, CaseSensitivity::CaseInsensitive);
  88. });
  89. if (invalid_path.has_value()) {
  90. WebIDL::reject_promise(realm, promise, JS::TypeError::create(realm, "scriptURL path must not contain '%2f' or '%5c'"sv));
  91. return;
  92. }
  93. // 5. If scopeURL is null, set scopeURL to the result of parsing the string "./" with scriptURL.
  94. // Note: The scope url for the registration is set to the location of the service worker script by default.
  95. if (!scope_url.has_value()) {
  96. scope_url = DOMURL::parse("./"sv, script_url);
  97. }
  98. // 6. If scopeURL is failure, reject promise with a TypeError and abort these steps.
  99. if (!scope_url->is_valid()) {
  100. WebIDL::reject_promise(realm, promise, JS::TypeError::create(realm, "scopeURL is not a valid URL"sv));
  101. return;
  102. }
  103. // 7. Set scopeURL’s fragment to null.
  104. // Note: The user agent does not store the fragment of the scope url.
  105. // This means that the fragment does not have an effect on identifying service worker registrations.
  106. scope_url->set_fragment({});
  107. // 8. If scopeURL’s scheme is not one of "http" and "https", reject promise with a TypeError and abort these steps.
  108. if (!scope_url->scheme().is_one_of("http"sv, "https"sv)) {
  109. WebIDL::reject_promise(realm, promise, JS::TypeError::create(realm, "scopeURL must have a scheme of 'http' or 'https'"sv));
  110. return;
  111. }
  112. // 9. If any of the strings in scopeURL’s path contains either ASCII case-insensitive "%2f" or ASCII case-insensitive "%5c",
  113. // reject promise with a TypeError and abort these steps.
  114. invalid_path = scope_url->paths().first_matching([&](auto& path) {
  115. return path.contains("%2f"sv, CaseSensitivity::CaseInsensitive) || path.contains("%5c"sv, CaseSensitivity::CaseInsensitive);
  116. });
  117. if (invalid_path.has_value()) {
  118. WebIDL::reject_promise(realm, promise, JS::TypeError::create(realm, "scopeURL path must not contain '%2f' or '%5c'"sv));
  119. return;
  120. }
  121. // 10. Let storage key be the result of running obtain a storage key given client.
  122. auto storage_key = StorageAPI::obtain_a_storage_key(client);
  123. // FIXME: Ad-Hoc. Spec should handle this failure here, or earlier.
  124. if (!storage_key.has_value()) {
  125. WebIDL::reject_promise(realm, promise, JS::TypeError::create(realm, "Failed to obtain a storage key"sv));
  126. return;
  127. }
  128. // 11. Let job be the result of running Create Job with register, storage key, scopeURL, scriptURL, promise, and client.
  129. auto job = Job::create(vm, Job::Type::Register, storage_key.value(), scope_url.value(), script_url, promise, client);
  130. // 12. Set job’s worker type to workerType.
  131. job->worker_type = worker_type;
  132. // 13. Set job’s update via cache to updateViaCache.
  133. job->update_via_cache = update_via_cache;
  134. // 14. Set job’s referrer to referrer.
  135. job->referrer = move(referrer);
  136. // 15. Invoke Schedule Job with job.
  137. schedule_job(vm, job);
  138. }
  139. #undef __ENUMERATE
  140. #define __ENUMERATE(attribute_name, event_name) \
  141. void ServiceWorkerContainer::set_##attribute_name(WebIDL::CallbackType* value) \
  142. { \
  143. set_event_handler_attribute(event_name, move(value)); \
  144. } \
  145. WebIDL::CallbackType* ServiceWorkerContainer::attribute_name() \
  146. { \
  147. return event_handler_attribute(event_name); \
  148. }
  149. ENUMERATE_SERVICE_WORKER_CONTAINER_EVENT_HANDLERS(__ENUMERATE)
  150. #undef __ENUMERATE
  151. }