/src/serenity/Userland/Libraries/LibWeb/HTML/Scripting/ModuleScript.cpp
Line | Count | Source |
1 | | /* |
2 | | * Copyright (c) 2022, networkException <networkexception@serenityos.org> |
3 | | * |
4 | | * SPDX-License-Identifier: BSD-2-Clause |
5 | | */ |
6 | | |
7 | | #include <LibJS/Runtime/ModuleRequest.h> |
8 | | #include <LibWeb/HTML/Scripting/Environments.h> |
9 | | #include <LibWeb/HTML/Scripting/Fetching.h> |
10 | | #include <LibWeb/HTML/Scripting/ModuleScript.h> |
11 | | #include <LibWeb/WebIDL/DOMException.h> |
12 | | #include <LibWeb/WebIDL/ExceptionOr.h> |
13 | | |
14 | | namespace Web::HTML { |
15 | | |
16 | | JS_DEFINE_ALLOCATOR(JavaScriptModuleScript); |
17 | | |
18 | 0 | ModuleScript::~ModuleScript() = default; |
19 | | |
20 | | ModuleScript::ModuleScript(URL::URL base_url, ByteString filename, EnvironmentSettingsObject& environment_settings_object) |
21 | 0 | : Script(move(base_url), move(filename), environment_settings_object) |
22 | 0 | { |
23 | 0 | } |
24 | | |
25 | 0 | JavaScriptModuleScript::~JavaScriptModuleScript() = default; |
26 | | |
27 | | JavaScriptModuleScript::JavaScriptModuleScript(URL::URL base_url, ByteString filename, EnvironmentSettingsObject& environment_settings_object) |
28 | 0 | : ModuleScript(move(base_url), move(filename), environment_settings_object) |
29 | 0 | { |
30 | 0 | } |
31 | | |
32 | | // https://html.spec.whatwg.org/multipage/webappapis.html#creating-a-javascript-module-script |
33 | | WebIDL::ExceptionOr<JS::GCPtr<JavaScriptModuleScript>> JavaScriptModuleScript::create(ByteString const& filename, StringView source, EnvironmentSettingsObject& settings_object, URL::URL base_url) |
34 | 0 | { |
35 | | // 1. If scripting is disabled for settings, then set source to the empty string. |
36 | 0 | if (settings_object.is_scripting_disabled()) |
37 | 0 | source = ""sv; |
38 | |
|
39 | 0 | auto& realm = settings_object.realm(); |
40 | | |
41 | | // 2. Let script be a new module script that this algorithm will subsequently initialize. |
42 | 0 | auto script = realm.heap().allocate<JavaScriptModuleScript>(realm, move(base_url), filename, settings_object); |
43 | | |
44 | | // 3. Set script's settings object to settings. |
45 | | // NOTE: This was already done when constructing. |
46 | | |
47 | | // 4. Set script's base URL to baseURL. |
48 | | // NOTE: This was already done when constructing. |
49 | | |
50 | | // FIXME: 5. Set script's fetch options to options. |
51 | | |
52 | | // 6. Set script's parse error and error to rethrow to null. |
53 | 0 | script->set_parse_error(JS::js_null()); |
54 | 0 | script->set_error_to_rethrow(JS::js_null()); |
55 | | |
56 | | // 7. Let result be ParseModule(source, settings's Realm, script). |
57 | 0 | auto result = JS::SourceTextModule::parse(source, settings_object.realm(), filename.view(), script); |
58 | | |
59 | | // 8. If result is a list of errors, then: |
60 | 0 | if (result.is_error()) { |
61 | 0 | auto& parse_error = result.error().first(); |
62 | 0 | dbgln("JavaScriptModuleScript: Failed to parse: {}", parse_error.to_string()); |
63 | | |
64 | | // 1. Set script's parse error to result[0]. |
65 | 0 | script->set_parse_error(JS::SyntaxError::create(settings_object.realm(), parse_error.to_string())); |
66 | | |
67 | | // 2. Return script. |
68 | 0 | return script; |
69 | 0 | } |
70 | | |
71 | | // 9. For each ModuleRequest record requested of result.[[RequestedModules]]: |
72 | 0 | for (auto const& requested : result.value()->requested_modules()) { |
73 | | // FIXME: Clarify if this should be checked for all requested before running the steps below. |
74 | | // 1. If requested.[[Attributes]] contains a Record entry such that entry.[[Key]] is not "type", then: |
75 | 0 | for (auto const& attribute : requested.attributes) { |
76 | 0 | if (attribute.key != "type"sv) { |
77 | | // 1. Let error be a new SyntaxError exception. |
78 | 0 | auto error = JS::SyntaxError::create(settings_object.realm(), "Module request attributes must only contain a type attribute"_string); |
79 | | |
80 | | // 2. Set script's parse error to error. |
81 | 0 | script->set_parse_error(error); |
82 | | |
83 | | // 3. Return script. |
84 | 0 | return script; |
85 | 0 | } |
86 | 0 | } |
87 | | |
88 | | // 2. Let url be the result of resolving a module specifier given script and requested.[[Specifier]], catching any exceptions. |
89 | 0 | auto url = resolve_module_specifier(*script, requested.module_specifier); |
90 | | |
91 | | // 3. If the previous step threw an exception, then: |
92 | 0 | if (url.is_exception()) { |
93 | | // FIXME: 1. Set script's parse error to that exception. |
94 | | |
95 | | // 2. Return script. |
96 | 0 | return script; |
97 | 0 | } |
98 | | |
99 | | // 4. Let moduleType be the result of running the module type from module request steps given requested. |
100 | 0 | auto module_type = module_type_from_module_request(requested); |
101 | | |
102 | | // 5. If the result of running the module type allowed steps given moduleType and settings is false, then: |
103 | 0 | if (!settings_object.module_type_allowed(module_type)) { |
104 | | // FIXME: 1. Let error be a new TypeError exception. |
105 | | |
106 | | // FIXME: 2. Set script's parse error to error. |
107 | | |
108 | | // 3. Return script. |
109 | 0 | return script; |
110 | 0 | } |
111 | 0 | } |
112 | | |
113 | | // 10. Set script's record to result. |
114 | 0 | script->m_record = result.value(); |
115 | | |
116 | | // 11. Return script. |
117 | 0 | return script; |
118 | 0 | } |
119 | | |
120 | | // https://html.spec.whatwg.org/multipage/webappapis.html#run-a-module-script |
121 | | JS::Promise* JavaScriptModuleScript::run(PreventErrorReporting) |
122 | 0 | { |
123 | | // 1. Let settings be the settings object of script. |
124 | 0 | auto& settings = settings_object(); |
125 | | |
126 | | // 2. Check if we can run script with settings. If this returns "do not run", then return a promise resolved with undefined. |
127 | 0 | if (settings.can_run_script() == RunScriptDecision::DoNotRun) { |
128 | 0 | auto promise = JS::Promise::create(settings.realm()); |
129 | 0 | promise->fulfill(JS::js_undefined()); |
130 | 0 | return promise; |
131 | 0 | } |
132 | | |
133 | | // 3. Prepare to run script given settings. |
134 | 0 | settings.prepare_to_run_script(); |
135 | | |
136 | | // 4. Let evaluationPromise be null. |
137 | 0 | JS::Promise* evaluation_promise = nullptr; |
138 | | |
139 | | // 5. If script's error to rethrow is not null, then set evaluationPromise to a promise rejected with script's error to rethrow. |
140 | 0 | if (!error_to_rethrow().is_null()) { |
141 | 0 | evaluation_promise = JS::Promise::create(settings.realm()); |
142 | 0 | evaluation_promise->reject(error_to_rethrow()); |
143 | 0 | } |
144 | | // 6. Otherwise: |
145 | 0 | else { |
146 | | // 1. Let record be script's record. |
147 | 0 | auto record = m_record; |
148 | 0 | VERIFY(record); |
149 | | |
150 | | // NON-STANDARD: To ensure that LibJS can find the module on the stack, we push a new execution context. |
151 | 0 | auto module_execution_context = JS::ExecutionContext::create(); |
152 | 0 | module_execution_context->realm = &settings.realm(); |
153 | 0 | module_execution_context->script_or_module = JS::NonnullGCPtr<JS::Module> { *record }; |
154 | 0 | vm().push_execution_context(*module_execution_context); |
155 | | |
156 | | // 2. Set evaluationPromise to record.Evaluate(). |
157 | 0 | auto elevation_promise_or_error = record->evaluate(vm()); |
158 | | |
159 | | // NOTE: This step will recursively evaluate all of the module's dependencies. |
160 | | // If Evaluate fails to complete as a result of the user agent aborting the running script, |
161 | | // then set evaluationPromise to a promise rejected with a new "QuotaExceededError" DOMException. |
162 | 0 | if (elevation_promise_or_error.is_error()) { |
163 | 0 | auto promise = JS::Promise::create(settings_object().realm()); |
164 | 0 | promise->reject(WebIDL::QuotaExceededError::create(settings_object().realm(), "Failed to evaluate module script"_string).ptr()); |
165 | |
|
166 | 0 | evaluation_promise = promise; |
167 | 0 | } else { |
168 | 0 | evaluation_promise = elevation_promise_or_error.value(); |
169 | 0 | } |
170 | | |
171 | | // NON-STANDARD: Pop the execution context mentioned above. |
172 | 0 | vm().pop_execution_context(); |
173 | 0 | } |
174 | | |
175 | | // FIXME: 7. If preventErrorReporting is false, then upon rejection of evaluationPromise with reason, report the exception given by reason for script. |
176 | | |
177 | | // 8. Clean up after running script with settings. |
178 | 0 | settings.clean_up_after_running_script(); |
179 | | |
180 | | // 9. Return evaluationPromise. |
181 | 0 | return evaluation_promise; |
182 | 0 | } |
183 | | |
184 | | void JavaScriptModuleScript::visit_edges(Cell::Visitor& visitor) |
185 | 0 | { |
186 | 0 | Base::visit_edges(visitor); |
187 | 0 | visitor.visit(m_record); |
188 | 0 | } |
189 | | |
190 | | } |