/src/serenity/Userland/Libraries/LibWeb/HTML/Scripting/ClassicScript.cpp
Line | Count | Source |
1 | | /* |
2 | | * Copyright (c) 2021-2023, Andreas Kling <kling@serenityos.org> |
3 | | * |
4 | | * SPDX-License-Identifier: BSD-2-Clause |
5 | | */ |
6 | | |
7 | | #include <AK/Debug.h> |
8 | | #include <LibCore/ElapsedTimer.h> |
9 | | #include <LibJS/Bytecode/Interpreter.h> |
10 | | #include <LibWeb/Bindings/ExceptionOrUtils.h> |
11 | | #include <LibWeb/HTML/Scripting/ClassicScript.h> |
12 | | #include <LibWeb/HTML/Scripting/Environments.h> |
13 | | #include <LibWeb/HTML/Scripting/ExceptionReporter.h> |
14 | | #include <LibWeb/HTML/WindowOrWorkerGlobalScope.h> |
15 | | #include <LibWeb/WebIDL/DOMException.h> |
16 | | |
17 | | namespace Web::HTML { |
18 | | |
19 | | JS_DEFINE_ALLOCATOR(ClassicScript); |
20 | | |
21 | | // https://html.spec.whatwg.org/multipage/webappapis.html#creating-a-classic-script |
22 | | JS::NonnullGCPtr<ClassicScript> ClassicScript::create(ByteString filename, StringView source, EnvironmentSettingsObject& environment_settings_object, URL::URL base_url, size_t source_line_number, MutedErrors muted_errors) |
23 | 0 | { |
24 | 0 | auto& vm = environment_settings_object.realm().vm(); |
25 | | |
26 | | // 1. If muted errors was not provided, let it be false. (NOTE: This is taken care of by the default argument.) |
27 | | |
28 | | // 2. If muted errors is true, then set baseURL to about:blank. |
29 | 0 | if (muted_errors == MutedErrors::Yes) |
30 | 0 | base_url = "about:blank"sv; |
31 | | |
32 | | // 3. If scripting is disabled for settings, then set source to the empty string. |
33 | 0 | if (environment_settings_object.is_scripting_disabled()) |
34 | 0 | source = ""sv; |
35 | | |
36 | | // 4. Let script be a new classic script that this algorithm will subsequently initialize. |
37 | 0 | auto script = vm.heap().allocate_without_realm<ClassicScript>(move(base_url), move(filename), environment_settings_object); |
38 | | |
39 | | // 5. Set script's settings object to settings. (NOTE: This was already done when constructing.) |
40 | | |
41 | | // 6. Set script's base URL to baseURL. (NOTE: This was already done when constructing.) |
42 | | |
43 | | // FIXME: 7. Set script's fetch options to options. |
44 | | |
45 | | // 8. Set script's muted errors to muted errors. |
46 | 0 | script->m_muted_errors = muted_errors; |
47 | | |
48 | | // 9. Set script's parse error and error to rethrow to null. |
49 | 0 | script->set_parse_error(JS::js_null()); |
50 | 0 | script->set_error_to_rethrow(JS::js_null()); |
51 | | |
52 | | // 10. Let result be ParseScript(source, settings's Realm, script). |
53 | 0 | auto parse_timer = Core::ElapsedTimer::start_new(); |
54 | 0 | auto result = JS::Script::parse(source, environment_settings_object.realm(), script->filename(), script, source_line_number); |
55 | 0 | dbgln_if(HTML_SCRIPT_DEBUG, "ClassicScript: Parsed {} in {}ms", script->filename(), parse_timer.elapsed()); |
56 | | |
57 | | // 11. If result is a list of errors, then: |
58 | 0 | if (result.is_error()) { |
59 | 0 | auto& parse_error = result.error().first(); |
60 | 0 | dbgln_if(HTML_SCRIPT_DEBUG, "ClassicScript: Failed to parse: {}", parse_error.to_string()); |
61 | | |
62 | | // 1. Set script's parse error and its error to rethrow to result[0]. |
63 | 0 | script->set_parse_error(JS::SyntaxError::create(environment_settings_object.realm(), parse_error.to_string())); |
64 | 0 | script->set_error_to_rethrow(script->parse_error()); |
65 | | |
66 | | // 2. Return script. |
67 | 0 | return script; |
68 | 0 | } |
69 | | |
70 | | // 12. Set script's record to result. |
71 | 0 | script->m_script_record = *result.release_value(); |
72 | | |
73 | | // 13. Return script. |
74 | 0 | return script; |
75 | 0 | } |
76 | | |
77 | | // https://html.spec.whatwg.org/multipage/webappapis.html#run-a-classic-script |
78 | | JS::Completion ClassicScript::run(RethrowErrors rethrow_errors, JS::GCPtr<JS::Environment> lexical_environment_override) |
79 | 0 | { |
80 | | // 1. Let settings be the settings object of script. |
81 | 0 | auto& settings = settings_object(); |
82 | | |
83 | | // 2. Check if we can run script with settings. If this returns "do not run" then return NormalCompletion(empty). |
84 | 0 | if (settings.can_run_script() == RunScriptDecision::DoNotRun) |
85 | 0 | return JS::normal_completion({}); |
86 | | |
87 | | // 3. Prepare to run script given settings. |
88 | 0 | settings.prepare_to_run_script(); |
89 | | |
90 | | // 4. Let evaluationStatus be null. |
91 | 0 | JS::Completion evaluation_status; |
92 | | |
93 | | // 5. If script's error to rethrow is not null, then set evaluationStatus to Completion { [[Type]]: throw, [[Value]]: script's error to rethrow, [[Target]]: empty }. |
94 | 0 | if (!error_to_rethrow().is_null()) { |
95 | 0 | evaluation_status = JS::Completion { JS::Completion::Type::Throw, error_to_rethrow() }; |
96 | 0 | } else { |
97 | 0 | auto timer = Core::ElapsedTimer::start_new(); |
98 | | |
99 | | // 6. Otherwise, set evaluationStatus to ScriptEvaluation(script's record). |
100 | 0 | evaluation_status = vm().bytecode_interpreter().run(*m_script_record, lexical_environment_override); |
101 | | |
102 | | // FIXME: If ScriptEvaluation does not complete because the user agent has aborted the running script, leave evaluationStatus as null. |
103 | |
|
104 | 0 | dbgln_if(HTML_SCRIPT_DEBUG, "ClassicScript: Finished running script {}, Duration: {}ms", filename(), timer.elapsed()); |
105 | 0 | } |
106 | | |
107 | | // 7. If evaluationStatus is an abrupt completion, then: |
108 | 0 | if (evaluation_status.is_abrupt()) { |
109 | | // 1. If rethrow errors is true and script's muted errors is false, then: |
110 | 0 | if (rethrow_errors == RethrowErrors::Yes && m_muted_errors == MutedErrors::No) { |
111 | | // 1. Clean up after running script with settings. |
112 | 0 | settings.clean_up_after_running_script(); |
113 | | |
114 | | // 2. Rethrow evaluationStatus.[[Value]]. |
115 | 0 | return JS::throw_completion(*evaluation_status.value()); |
116 | 0 | } |
117 | | |
118 | | // 2. If rethrow errors is true and script's muted errors is true, then: |
119 | 0 | if (rethrow_errors == RethrowErrors::Yes && m_muted_errors == MutedErrors::Yes) { |
120 | | // 1. Clean up after running script with settings. |
121 | 0 | settings.clean_up_after_running_script(); |
122 | | |
123 | | // 2. Throw a "NetworkError" DOMException. |
124 | 0 | return throw_completion(WebIDL::NetworkError::create(settings.realm(), "Script error."_string)); |
125 | 0 | } |
126 | | |
127 | | // 3. Otherwise, rethrow errors is false. Perform the following steps: |
128 | 0 | VERIFY(rethrow_errors == RethrowErrors::No); |
129 | | |
130 | | // 1. Report an exception given by evaluationStatus.[[Value]] for script's settings object's global object. |
131 | 0 | auto* window_or_worker = dynamic_cast<WindowOrWorkerGlobalScopeMixin*>(&settings.global_object()); |
132 | 0 | VERIFY(window_or_worker); |
133 | 0 | window_or_worker->report_an_exception(*evaluation_status.value()); |
134 | | |
135 | | // 2. Clean up after running script with settings. |
136 | 0 | settings.clean_up_after_running_script(); |
137 | | |
138 | | // 3. Return evaluationStatus. |
139 | 0 | return evaluation_status; |
140 | 0 | } |
141 | | |
142 | | // 8. Clean up after running script with settings. |
143 | 0 | settings.clean_up_after_running_script(); |
144 | | |
145 | | // 9. If evaluationStatus is a normal completion, then return evaluationStatus. |
146 | 0 | VERIFY(!evaluation_status.is_abrupt()); |
147 | 0 | return evaluation_status; |
148 | | |
149 | | // FIXME: 10. If we've reached this point, evaluationStatus was left as null because the script was aborted prematurely during evaluation. |
150 | | // Return Completion { [[Type]]: throw, [[Value]]: a new "QuotaExceededError" DOMException, [[Target]]: empty }. |
151 | 0 | } |
152 | | |
153 | | ClassicScript::ClassicScript(URL::URL base_url, ByteString filename, EnvironmentSettingsObject& environment_settings_object) |
154 | 0 | : Script(move(base_url), move(filename), environment_settings_object) |
155 | 0 | { |
156 | 0 | } |
157 | | |
158 | 0 | ClassicScript::~ClassicScript() = default; |
159 | | |
160 | | void ClassicScript::visit_edges(Cell::Visitor& visitor) |
161 | 0 | { |
162 | 0 | Base::visit_edges(visitor); |
163 | 0 | visitor.visit(m_script_record); |
164 | 0 | } |
165 | | |
166 | | } |