/src/serenity/Userland/Libraries/LibJS/Runtime/GlobalObject.cpp
Line | Count | Source |
1 | | /* |
2 | | * Copyright (c) 2020, Andreas Kling <kling@serenityos.org> |
3 | | * Copyright (c) 2020-2023, Linus Groh <linusg@serenityos.org> |
4 | | * |
5 | | * SPDX-License-Identifier: BSD-2-Clause |
6 | | */ |
7 | | |
8 | | #include <AK/BuiltinWrappers.h> |
9 | | #include <AK/CharacterTypes.h> |
10 | | #include <AK/FloatingPointStringConversions.h> |
11 | | #include <AK/Hex.h> |
12 | | #include <AK/UnicodeUtils.h> |
13 | | #include <AK/Utf16View.h> |
14 | | #include <AK/Utf8View.h> |
15 | | #include <LibJS/Heap/DeferGC.h> |
16 | | #include <LibJS/Runtime/AbstractOperations.h> |
17 | | #include <LibJS/Runtime/AggregateErrorConstructor.h> |
18 | | #include <LibJS/Runtime/ArrayBufferConstructor.h> |
19 | | #include <LibJS/Runtime/ArrayConstructor.h> |
20 | | #include <LibJS/Runtime/ArrayPrototype.h> |
21 | | #include <LibJS/Runtime/AsyncFunctionConstructor.h> |
22 | | #include <LibJS/Runtime/AsyncGeneratorFunctionConstructor.h> |
23 | | #include <LibJS/Runtime/AsyncGeneratorPrototype.h> |
24 | | #include <LibJS/Runtime/AtomicsObject.h> |
25 | | #include <LibJS/Runtime/BigIntConstructor.h> |
26 | | #include <LibJS/Runtime/BooleanConstructor.h> |
27 | | #include <LibJS/Runtime/ConsoleObject.h> |
28 | | #include <LibJS/Runtime/DataViewConstructor.h> |
29 | | #include <LibJS/Runtime/DateConstructor.h> |
30 | | #include <LibJS/Runtime/DisposableStackConstructor.h> |
31 | | #include <LibJS/Runtime/ErrorConstructor.h> |
32 | | #include <LibJS/Runtime/FinalizationRegistryConstructor.h> |
33 | | #include <LibJS/Runtime/FinalizationRegistryPrototype.h> |
34 | | #include <LibJS/Runtime/FunctionConstructor.h> |
35 | | #include <LibJS/Runtime/GeneratorFunctionConstructor.h> |
36 | | #include <LibJS/Runtime/GeneratorPrototype.h> |
37 | | #include <LibJS/Runtime/GlobalEnvironment.h> |
38 | | #include <LibJS/Runtime/GlobalObject.h> |
39 | | #include <LibJS/Runtime/Intl/CollatorConstructor.h> |
40 | | #include <LibJS/Runtime/Intl/DateTimeFormatConstructor.h> |
41 | | #include <LibJS/Runtime/Intl/DisplayNamesConstructor.h> |
42 | | #include <LibJS/Runtime/Intl/DurationFormatConstructor.h> |
43 | | #include <LibJS/Runtime/Intl/Intl.h> |
44 | | #include <LibJS/Runtime/Intl/ListFormatConstructor.h> |
45 | | #include <LibJS/Runtime/Intl/LocaleConstructor.h> |
46 | | #include <LibJS/Runtime/Intl/NumberFormatConstructor.h> |
47 | | #include <LibJS/Runtime/Intl/PluralRulesConstructor.h> |
48 | | #include <LibJS/Runtime/Intl/RelativeTimeFormatConstructor.h> |
49 | | #include <LibJS/Runtime/Intl/SegmenterConstructor.h> |
50 | | #include <LibJS/Runtime/IteratorConstructor.h> |
51 | | #include <LibJS/Runtime/JSONObject.h> |
52 | | #include <LibJS/Runtime/MapConstructor.h> |
53 | | #include <LibJS/Runtime/MathObject.h> |
54 | | #include <LibJS/Runtime/NumberConstructor.h> |
55 | | #include <LibJS/Runtime/Object.h> |
56 | | #include <LibJS/Runtime/ObjectConstructor.h> |
57 | | #include <LibJS/Runtime/PromiseConstructor.h> |
58 | | #include <LibJS/Runtime/ProxyConstructor.h> |
59 | | #include <LibJS/Runtime/Realm.h> |
60 | | #include <LibJS/Runtime/ReflectObject.h> |
61 | | #include <LibJS/Runtime/RegExpConstructor.h> |
62 | | #include <LibJS/Runtime/SetConstructor.h> |
63 | | #include <LibJS/Runtime/ShadowRealmConstructor.h> |
64 | | #include <LibJS/Runtime/Shape.h> |
65 | | #include <LibJS/Runtime/SharedArrayBufferConstructor.h> |
66 | | #include <LibJS/Runtime/StringConstructor.h> |
67 | | #include <LibJS/Runtime/StringPrototype.h> |
68 | | #include <LibJS/Runtime/SuppressedErrorConstructor.h> |
69 | | #include <LibJS/Runtime/SymbolConstructor.h> |
70 | | #include <LibJS/Runtime/Temporal/CalendarConstructor.h> |
71 | | #include <LibJS/Runtime/Temporal/DurationConstructor.h> |
72 | | #include <LibJS/Runtime/Temporal/InstantConstructor.h> |
73 | | #include <LibJS/Runtime/Temporal/PlainDateConstructor.h> |
74 | | #include <LibJS/Runtime/Temporal/PlainDateTimeConstructor.h> |
75 | | #include <LibJS/Runtime/Temporal/PlainMonthDayConstructor.h> |
76 | | #include <LibJS/Runtime/Temporal/PlainTimeConstructor.h> |
77 | | #include <LibJS/Runtime/Temporal/PlainYearMonthConstructor.h> |
78 | | #include <LibJS/Runtime/Temporal/Temporal.h> |
79 | | #include <LibJS/Runtime/Temporal/TimeZoneConstructor.h> |
80 | | #include <LibJS/Runtime/Temporal/ZonedDateTimeConstructor.h> |
81 | | #include <LibJS/Runtime/TypedArray.h> |
82 | | #include <LibJS/Runtime/Value.h> |
83 | | #include <LibJS/Runtime/ValueInlines.h> |
84 | | #include <LibJS/Runtime/WeakMapConstructor.h> |
85 | | #include <LibJS/Runtime/WeakRefConstructor.h> |
86 | | #include <LibJS/Runtime/WeakSetConstructor.h> |
87 | | |
88 | | namespace JS { |
89 | | |
90 | | JS_DEFINE_ALLOCATOR(GlobalObject); |
91 | | |
92 | | GlobalObject::GlobalObject(Realm& realm) |
93 | 0 | : Object(GlobalObjectTag::Tag, realm) |
94 | 0 | { |
95 | 0 | Object::set_prototype(realm.intrinsics().object_prototype()); |
96 | 0 | } |
97 | | |
98 | | // 9.3.3 SetDefaultGlobalBindings ( realmRec ), https://tc39.es/ecma262/#sec-setdefaultglobalbindings |
99 | | void set_default_global_bindings(Realm& realm) |
100 | 0 | { |
101 | 0 | auto& vm = realm.vm(); |
102 | | |
103 | | // 1. Let global be realmRec.[[GlobalObject]]. |
104 | 0 | auto& global = realm.global_object(); |
105 | | |
106 | | // 2. For each property of the Global Object specified in clause 19, do |
107 | | // a. Let name be the String value of the property name. |
108 | | // b. Let desc be the fully populated data Property Descriptor for the property, containing the specified attributes for the property. |
109 | | // For properties listed in 19.2, 19.3, or 19.4 the value of the [[Value]] attribute is the corresponding intrinsic object from realmRec. |
110 | | // c. Perform ? DefinePropertyOrThrow(global, name, desc). |
111 | | // NOTE: This function is infallible as we set properties directly; property clashes in global object construction are not expected. |
112 | |
|
113 | 0 | u8 attr = Attribute::Writable | Attribute::Configurable; |
114 | | |
115 | | // 19.2 Function Properties of the Global Object, https://tc39.es/ecma262/#sec-function-properties-of-the-global-object |
116 | 0 | global.define_direct_property(vm.names.eval, realm.intrinsics().eval_function(), attr); |
117 | 0 | global.define_direct_property(vm.names.isFinite, realm.intrinsics().is_finite_function(), attr); |
118 | 0 | global.define_direct_property(vm.names.isNaN, realm.intrinsics().is_nan_function(), attr); |
119 | 0 | global.define_direct_property(vm.names.parseFloat, realm.intrinsics().parse_float_function(), attr); |
120 | 0 | global.define_direct_property(vm.names.parseInt, realm.intrinsics().parse_int_function(), attr); |
121 | 0 | global.define_direct_property(vm.names.decodeURI, realm.intrinsics().decode_uri_function(), attr); |
122 | 0 | global.define_direct_property(vm.names.decodeURIComponent, realm.intrinsics().decode_uri_component_function(), attr); |
123 | 0 | global.define_direct_property(vm.names.encodeURI, realm.intrinsics().encode_uri_function(), attr); |
124 | 0 | global.define_direct_property(vm.names.encodeURIComponent, realm.intrinsics().encode_uri_component_function(), attr); |
125 | | |
126 | | // 19.1 Value Properties of the Global Object, https://tc39.es/ecma262/#sec-value-properties-of-the-global-object |
127 | 0 | global.define_direct_property(vm.names.globalThis, &global, attr); |
128 | 0 | global.define_direct_property(vm.names.Infinity, js_infinity(), 0); |
129 | 0 | global.define_direct_property(vm.names.NaN, js_nan(), 0); |
130 | 0 | global.define_direct_property(vm.names.undefined, js_undefined(), 0); |
131 | | |
132 | | // 19.3 Constructor Properties of the Global Object, https://tc39.es/ecma262/#sec-constructor-properties-of-the-global-object |
133 | 0 | global.define_intrinsic_accessor(vm.names.AggregateError, attr, [](auto& realm) -> Value { return realm.intrinsics().aggregate_error_constructor(); }); |
134 | 0 | global.define_intrinsic_accessor(vm.names.Array, attr, [](auto& realm) -> Value { return realm.intrinsics().array_constructor(); }); |
135 | 0 | global.define_intrinsic_accessor(vm.names.ArrayBuffer, attr, [](auto& realm) -> Value { return realm.intrinsics().array_buffer_constructor(); }); |
136 | 0 | global.define_intrinsic_accessor(vm.names.BigInt, attr, [](auto& realm) -> Value { return realm.intrinsics().bigint_constructor(); }); |
137 | 0 | global.define_intrinsic_accessor(vm.names.BigInt64Array, attr, [](auto& realm) -> Value { return realm.intrinsics().big_int64_array_constructor(); }); |
138 | 0 | global.define_intrinsic_accessor(vm.names.BigUint64Array, attr, [](auto& realm) -> Value { return realm.intrinsics().big_uint64_array_constructor(); }); |
139 | 0 | global.define_intrinsic_accessor(vm.names.Boolean, attr, [](auto& realm) -> Value { return realm.intrinsics().boolean_constructor(); }); |
140 | 0 | global.define_intrinsic_accessor(vm.names.DataView, attr, [](auto& realm) -> Value { return realm.intrinsics().data_view_constructor(); }); |
141 | 0 | global.define_intrinsic_accessor(vm.names.Date, attr, [](auto& realm) -> Value { return realm.intrinsics().date_constructor(); }); |
142 | 0 | global.define_intrinsic_accessor(vm.names.DisposableStack, attr, [](auto& realm) -> Value { return realm.intrinsics().disposable_stack_constructor(); }); |
143 | 0 | global.define_intrinsic_accessor(vm.names.Error, attr, [](auto& realm) -> Value { return realm.intrinsics().error_constructor(); }); |
144 | 0 | global.define_intrinsic_accessor(vm.names.EvalError, attr, [](auto& realm) -> Value { return realm.intrinsics().eval_error_constructor(); }); |
145 | 0 | global.define_intrinsic_accessor(vm.names.FinalizationRegistry, attr, [](auto& realm) -> Value { return realm.intrinsics().finalization_registry_constructor(); }); |
146 | 0 | global.define_intrinsic_accessor(vm.names.Float32Array, attr, [](auto& realm) -> Value { return realm.intrinsics().float32_array_constructor(); }); |
147 | 0 | global.define_intrinsic_accessor(vm.names.Float64Array, attr, [](auto& realm) -> Value { return realm.intrinsics().float64_array_constructor(); }); |
148 | 0 | global.define_intrinsic_accessor(vm.names.Function, attr, [](auto& realm) -> Value { return realm.intrinsics().function_constructor(); }); |
149 | 0 | global.define_intrinsic_accessor(vm.names.Int8Array, attr, [](auto& realm) -> Value { return realm.intrinsics().int8_array_constructor(); }); |
150 | 0 | global.define_intrinsic_accessor(vm.names.Int16Array, attr, [](auto& realm) -> Value { return realm.intrinsics().int16_array_constructor(); }); |
151 | 0 | global.define_intrinsic_accessor(vm.names.Int32Array, attr, [](auto& realm) -> Value { return realm.intrinsics().int32_array_constructor(); }); |
152 | 0 | global.define_intrinsic_accessor(vm.names.Iterator, attr, [](auto& realm) -> Value { return realm.intrinsics().iterator_constructor(); }); |
153 | 0 | global.define_intrinsic_accessor(vm.names.Map, attr, [](auto& realm) -> Value { return realm.intrinsics().map_constructor(); }); |
154 | 0 | global.define_intrinsic_accessor(vm.names.Number, attr, [](auto& realm) -> Value { return realm.intrinsics().number_constructor(); }); |
155 | 0 | global.define_intrinsic_accessor(vm.names.Object, attr, [](auto& realm) -> Value { return realm.intrinsics().object_constructor(); }); |
156 | 0 | global.define_intrinsic_accessor(vm.names.Promise, attr, [](auto& realm) -> Value { return realm.intrinsics().promise_constructor(); }); |
157 | 0 | global.define_intrinsic_accessor(vm.names.Proxy, attr, [](auto& realm) -> Value { return realm.intrinsics().proxy_constructor(); }); |
158 | 0 | global.define_intrinsic_accessor(vm.names.RangeError, attr, [](auto& realm) -> Value { return realm.intrinsics().range_error_constructor(); }); |
159 | 0 | global.define_intrinsic_accessor(vm.names.ReferenceError, attr, [](auto& realm) -> Value { return realm.intrinsics().reference_error_constructor(); }); |
160 | 0 | global.define_intrinsic_accessor(vm.names.RegExp, attr, [](auto& realm) -> Value { return realm.intrinsics().regexp_constructor(); }); |
161 | 0 | global.define_intrinsic_accessor(vm.names.Set, attr, [](auto& realm) -> Value { return realm.intrinsics().set_constructor(); }); |
162 | 0 | global.define_intrinsic_accessor(vm.names.ShadowRealm, attr, [](auto& realm) -> Value { return realm.intrinsics().shadow_realm_constructor(); }); |
163 | 0 | global.define_intrinsic_accessor(vm.names.SharedArrayBuffer, attr, [](auto& realm) -> Value { return realm.intrinsics().shared_array_buffer_constructor(); }); |
164 | 0 | global.define_intrinsic_accessor(vm.names.String, attr, [](auto& realm) -> Value { return realm.intrinsics().string_constructor(); }); |
165 | 0 | global.define_intrinsic_accessor(vm.names.SuppressedError, attr, [](auto& realm) -> Value { return realm.intrinsics().suppressed_error_constructor(); }); |
166 | 0 | global.define_intrinsic_accessor(vm.names.Symbol, attr, [](auto& realm) -> Value { return realm.intrinsics().symbol_constructor(); }); |
167 | 0 | global.define_intrinsic_accessor(vm.names.SyntaxError, attr, [](auto& realm) -> Value { return realm.intrinsics().syntax_error_constructor(); }); |
168 | 0 | global.define_intrinsic_accessor(vm.names.TypeError, attr, [](auto& realm) -> Value { return realm.intrinsics().type_error_constructor(); }); |
169 | 0 | global.define_intrinsic_accessor(vm.names.Uint8Array, attr, [](auto& realm) -> Value { return realm.intrinsics().uint8_array_constructor(); }); |
170 | 0 | global.define_intrinsic_accessor(vm.names.Uint8ClampedArray, attr, [](auto& realm) -> Value { return realm.intrinsics().uint8_clamped_array_constructor(); }); |
171 | 0 | global.define_intrinsic_accessor(vm.names.Uint16Array, attr, [](auto& realm) -> Value { return realm.intrinsics().uint16_array_constructor(); }); |
172 | 0 | global.define_intrinsic_accessor(vm.names.Uint32Array, attr, [](auto& realm) -> Value { return realm.intrinsics().uint32_array_constructor(); }); |
173 | 0 | global.define_intrinsic_accessor(vm.names.URIError, attr, [](auto& realm) -> Value { return realm.intrinsics().uri_error_constructor(); }); |
174 | 0 | global.define_intrinsic_accessor(vm.names.WeakMap, attr, [](auto& realm) -> Value { return realm.intrinsics().weak_map_constructor(); }); |
175 | 0 | global.define_intrinsic_accessor(vm.names.WeakRef, attr, [](auto& realm) -> Value { return realm.intrinsics().weak_ref_constructor(); }); |
176 | 0 | global.define_intrinsic_accessor(vm.names.WeakSet, attr, [](auto& realm) -> Value { return realm.intrinsics().weak_set_constructor(); }); |
177 | | |
178 | | // 19.4 Other Properties of the Global Object, https://tc39.es/ecma262/#sec-other-properties-of-the-global-object |
179 | 0 | global.define_intrinsic_accessor(vm.names.Atomics, attr, [](auto& realm) -> Value { return realm.intrinsics().atomics_object(); }); |
180 | 0 | global.define_intrinsic_accessor(vm.names.Intl, attr, [](auto& realm) -> Value { return realm.intrinsics().intl_object(); }); |
181 | 0 | global.define_intrinsic_accessor(vm.names.JSON, attr, [](auto& realm) -> Value { return realm.intrinsics().json_object(); }); |
182 | 0 | global.define_intrinsic_accessor(vm.names.Math, attr, [](auto& realm) -> Value { return realm.intrinsics().math_object(); }); |
183 | 0 | global.define_intrinsic_accessor(vm.names.Reflect, attr, [](auto& realm) -> Value { return realm.intrinsics().reflect_object(); }); |
184 | 0 | global.define_intrinsic_accessor(vm.names.Temporal, attr, [](auto& realm) -> Value { return realm.intrinsics().temporal_object(); }); |
185 | | |
186 | | // B.2.1 Additional Properties of the Global Object, https://tc39.es/ecma262/#sec-additional-properties-of-the-global-object |
187 | 0 | global.define_direct_property(vm.names.escape, realm.intrinsics().escape_function(), attr); |
188 | 0 | global.define_direct_property(vm.names.unescape, realm.intrinsics().unescape_function(), attr); |
189 | | |
190 | | // Non-standard |
191 | 0 | global.define_direct_property(vm.names.InternalError, realm.intrinsics().internal_error_constructor(), attr); |
192 | 0 | global.define_direct_property(vm.names.console, realm.intrinsics().console_object(), attr); |
193 | | |
194 | | // 3. Return unused. |
195 | 0 | } |
196 | | |
197 | | void GlobalObject::initialize(Realm& realm) |
198 | 0 | { |
199 | 0 | Base::initialize(realm); |
200 | |
|
201 | 0 | auto& vm = this->vm(); |
202 | | |
203 | | // Non-standard |
204 | 0 | u8 attr = Attribute::Writable | Attribute::Configurable; |
205 | 0 | define_native_function(realm, vm.names.gc, gc, 0, attr); |
206 | 0 | } |
207 | | |
208 | 0 | GlobalObject::~GlobalObject() = default; |
209 | | |
210 | | JS_DEFINE_NATIVE_FUNCTION(GlobalObject::gc) |
211 | 0 | { |
212 | | #ifdef AK_OS_SERENITY |
213 | | dbgln("Forced garbage collection requested!"); |
214 | | #endif |
215 | 0 | vm.heap().collect_garbage(); |
216 | 0 | return js_undefined(); |
217 | 0 | } |
218 | | |
219 | | // 19.2.1 eval ( x ), https://tc39.es/ecma262/#sec-eval-x |
220 | | JS_DEFINE_NATIVE_FUNCTION(GlobalObject::eval) |
221 | 0 | { |
222 | 0 | auto x = vm.argument(0); |
223 | | |
224 | | // 1. Return ? PerformEval(x, false, false). |
225 | 0 | return perform_eval(vm, x, CallerMode::NonStrict, EvalMode::Indirect); |
226 | 0 | } |
227 | | |
228 | | // 19.2.2 isFinite ( number ), https://tc39.es/ecma262/#sec-isfinite-number |
229 | | JS_DEFINE_NATIVE_FUNCTION(GlobalObject::is_finite) |
230 | 0 | { |
231 | 0 | auto number = vm.argument(0); |
232 | | |
233 | | // 1. Let num be ? ToNumber(number). |
234 | 0 | auto num = TRY(number.to_number(vm)); |
235 | | |
236 | | // 2. If num is not finite, return false. |
237 | | // 3. Otherwise, return true. |
238 | 0 | return Value(num.is_finite_number()); |
239 | 0 | } |
240 | | |
241 | | // 19.2.3 isNaN ( number ), https://tc39.es/ecma262/#sec-isnan-number |
242 | | JS_DEFINE_NATIVE_FUNCTION(GlobalObject::is_nan) |
243 | 0 | { |
244 | 0 | auto number = vm.argument(0); |
245 | | |
246 | | // 1. Let num be ? ToNumber(number). |
247 | 0 | auto num = TRY(number.to_number(vm)); |
248 | | |
249 | | // 2. If num is NaN, return true. |
250 | | // 3. Otherwise, return false. |
251 | 0 | return Value(num.is_nan()); |
252 | 0 | } |
253 | | |
254 | | // 19.2.4 parseFloat ( string ), https://tc39.es/ecma262/#sec-parsefloat-string |
255 | | JS_DEFINE_NATIVE_FUNCTION(GlobalObject::parse_float) |
256 | 0 | { |
257 | 0 | auto string = vm.argument(0); |
258 | | |
259 | | // OPTIMIZATION: We can skip the number-to-string-to-number round trip when the value is already a number. |
260 | 0 | if (string.is_number()) |
261 | 0 | return string; |
262 | | |
263 | | // 1. Let inputString be ? ToString(string). |
264 | 0 | auto input_string = TRY(string.to_string(vm)); |
265 | | |
266 | | // 2. Let trimmedString be ! TrimString(inputString, start). |
267 | 0 | auto trimmed_string = MUST(trim_string(vm, PrimitiveString::create(vm, move(input_string)), TrimMode::Left)); |
268 | 0 | if (trimmed_string.is_empty()) |
269 | 0 | return js_nan(); |
270 | | |
271 | | // 3. If neither trimmedString nor any prefix of trimmedString satisfies the syntax of a StrDecimalLiteral (see 7.1.4.1), return NaN. |
272 | | // 4. Let numberString be the longest prefix of trimmedString, which might be trimmedString itself, that satisfies the syntax of a StrDecimalLiteral. |
273 | | // 5. Let parsedNumber be ParseText(StringToCodePoints(numberString), StrDecimalLiteral). |
274 | | // 6. Assert: parsedNumber is a Parse Node. |
275 | | // 7. Return StringNumericValue of parsedNumber. |
276 | 0 | auto trimmed_string_view = trimmed_string.bytes_as_string_view(); |
277 | 0 | auto const* begin = trimmed_string_view.characters_without_null_termination(); |
278 | 0 | auto const* end = begin + trimmed_string_view.length(); |
279 | |
|
280 | 0 | auto parsed_number = parse_first_floating_point<double>(begin, end); |
281 | 0 | if (parsed_number.parsed_value()) |
282 | 0 | return parsed_number.value; |
283 | | |
284 | 0 | auto first_code_point = *trimmed_string.code_points().begin(); |
285 | 0 | if (first_code_point == '-' || first_code_point == '+') |
286 | 0 | trimmed_string_view = trimmed_string_view.substring_view(1); |
287 | |
|
288 | 0 | if (trimmed_string_view.starts_with("Infinity"sv, AK::CaseSensitivity::CaseSensitive)) { |
289 | | // Only an immediate - means we should return negative infinity |
290 | 0 | return first_code_point == '-' ? js_negative_infinity() : js_infinity(); |
291 | 0 | } |
292 | | |
293 | 0 | return js_nan(); |
294 | 0 | } |
295 | | |
296 | | // 19.2.5 parseInt ( string, radix ), https://tc39.es/ecma262/#sec-parseint-string-radix |
297 | | JS_DEFINE_NATIVE_FUNCTION(GlobalObject::parse_int) |
298 | 0 | { |
299 | 0 | auto string = vm.argument(0); |
300 | | |
301 | | // 1. Let inputString be ? ToString(string). |
302 | 0 | auto input_string = TRY(string.to_string(vm)); |
303 | | |
304 | | // 2. Let S be ! TrimString(inputString, start). |
305 | 0 | String trimmed_string; |
306 | | // OPTIMIZATION: We can skip the trimming step when the value already starts with an alphanumeric ASCII character. |
307 | 0 | if (input_string.is_empty() || is_ascii_alphanumeric(input_string.bytes_as_string_view()[0])) { |
308 | 0 | trimmed_string = input_string; |
309 | 0 | } else { |
310 | 0 | trimmed_string = MUST(trim_string(vm, PrimitiveString::create(vm, move(input_string)), TrimMode::Left)); |
311 | 0 | } |
312 | | |
313 | | // 3. Let sign be 1. |
314 | 0 | auto sign = 1; |
315 | | |
316 | | // 4. If S is not empty and the first code unit of S is the code unit 0x002D (HYPHEN-MINUS), set sign to -1. |
317 | 0 | auto first_code_point = trimmed_string.is_empty() ? OptionalNone {} : Optional<u32> { *trimmed_string.code_points().begin() }; |
318 | 0 | if (first_code_point == 0x2Du) |
319 | 0 | sign = -1; |
320 | | |
321 | | // 5. If S is not empty and the first code unit of S is the code unit 0x002B (PLUS SIGN) or the code unit 0x002D (HYPHEN-MINUS), remove the first code unit from S. |
322 | 0 | auto trimmed_view = trimmed_string.bytes_as_string_view(); |
323 | 0 | if (first_code_point == 0x2Bu || first_code_point == 0x2Du) |
324 | 0 | trimmed_view = trimmed_view.substring_view(1); |
325 | | |
326 | | // 6. Let R be ℝ(? ToInt32(radix)). |
327 | 0 | auto radix = TRY(vm.argument(1).to_i32(vm)); |
328 | | |
329 | | // 7. Let stripPrefix be true. |
330 | 0 | auto strip_prefix = true; |
331 | | |
332 | | // 8. If R ≠ 0, then |
333 | 0 | if (radix != 0) { |
334 | | // a. If R < 2 or R > 36, return NaN. |
335 | 0 | if (radix < 2 || radix > 36) |
336 | 0 | return js_nan(); |
337 | | |
338 | | // b. If R ≠ 16, set stripPrefix to false. |
339 | 0 | if (radix != 16) |
340 | 0 | strip_prefix = false; |
341 | 0 | } |
342 | | // 9. Else, |
343 | 0 | else { |
344 | | // a. Set R to 10. |
345 | 0 | radix = 10; |
346 | 0 | } |
347 | | |
348 | | // 10. If stripPrefix is true, then |
349 | 0 | if (strip_prefix) { |
350 | | // a. If the length of S is at least 2 and the first two code units of S are either "0x" or "0X", then |
351 | 0 | if (trimmed_view.length() >= 2 && trimmed_view.substring_view(0, 2).equals_ignoring_ascii_case("0x"sv)) { |
352 | | // i. Remove the first two code units from S. |
353 | 0 | trimmed_view = trimmed_view.substring_view(2); |
354 | | |
355 | | // ii. Set R to 16. |
356 | 0 | radix = 16; |
357 | 0 | } |
358 | 0 | } |
359 | | |
360 | | // 11. If S contains a code unit that is not a radix-R digit, let end be the index within S of the first such code unit; otherwise, let end be the length of S. |
361 | | // 12. Let Z be the substring of S from 0 to end. |
362 | | // 13. If Z is empty, return NaN. |
363 | | // 14. Let mathInt be the integer value that is represented by Z in radix-R notation, using the letters A-Z and a-z for digits with values 10 through 35. (However, if R is 10 and Z contains more than 20 significant digits, every significant digit after the 20th may be replaced by a 0 digit, at the option of the implementation; and if R is not 2, 4, 8, 10, 16, or 32, then mathInt may be an implementation-approximated integer representing the integer value denoted by Z in radix-R notation.) |
364 | 0 | auto parse_digit = [&](u32 code_point) -> Optional<u32> { |
365 | 0 | if (!is_ascii_alphanumeric(code_point)) |
366 | 0 | return {}; |
367 | 0 | auto digit = parse_ascii_base36_digit(code_point); |
368 | 0 | if (digit >= (u32)radix) |
369 | 0 | return {}; |
370 | 0 | return digit; |
371 | 0 | }; |
372 | |
|
373 | 0 | bool had_digits = false; |
374 | 0 | double number = 0; |
375 | 0 | for (auto code_point : Utf8View(trimmed_view)) { |
376 | 0 | auto digit = parse_digit(code_point); |
377 | 0 | if (!digit.has_value()) |
378 | 0 | break; |
379 | 0 | had_digits = true; |
380 | 0 | number *= radix; |
381 | 0 | number += digit.value(); |
382 | 0 | } |
383 | |
|
384 | 0 | if (!had_digits) |
385 | 0 | return js_nan(); |
386 | | |
387 | | // 15. If mathInt = 0, then |
388 | | // a. If sign = -1, return -0𝔽. |
389 | | // b. Return +0𝔽. |
390 | | // 16. Return 𝔽(sign × mathInt). |
391 | 0 | return Value(sign * number); |
392 | 0 | } |
393 | | |
394 | | // 19.2.6.5 Encode ( string, extraUnescaped ), https://tc39.es/ecma262/#sec-encode |
395 | | static ThrowCompletionOr<ByteString> encode(VM& vm, ByteString const& string, StringView unescaped_set) |
396 | 0 | { |
397 | 0 | auto utf16_string = Utf16String::create(string); |
398 | | |
399 | | // 1. Let strLen be the length of string. |
400 | 0 | auto string_length = utf16_string.length_in_code_units(); |
401 | | |
402 | | // 2. Let R be the empty String. |
403 | 0 | StringBuilder encoded_builder; |
404 | | |
405 | | // 3. Let alwaysUnescaped be the string-concatenation of the ASCII word characters and "-.!~*'()". |
406 | | // 4. Let unescapedSet be the string-concatenation of alwaysUnescaped and extraUnescaped. |
407 | | // OPTIMIZATION: We pass in the entire unescapedSet as a StringView to avoid an extra allocation. |
408 | | |
409 | | // 5. Let k be 0. |
410 | 0 | auto k = 0u; |
411 | | |
412 | | // 6. Repeat, |
413 | 0 | while (k < string_length) { |
414 | | // a. If k = strLen, return R. |
415 | | // Handled below |
416 | | |
417 | | // b. Let C be the code unit at index k within string. |
418 | 0 | auto code_unit = utf16_string.code_unit_at(k); |
419 | | // c. If C is in unescapedSet, then |
420 | | // NOTE: We assume the unescaped set only contains ascii characters as unescaped_set is a StringView. |
421 | 0 | if (code_unit < 0x80 && unescaped_set.contains(static_cast<char>(code_unit))) { |
422 | | // i. Set k to k + 1. |
423 | 0 | k++; |
424 | | |
425 | | // ii. Set R to the string-concatenation of R and C. |
426 | 0 | encoded_builder.append(code_unit); |
427 | 0 | } |
428 | | // d. Else, |
429 | 0 | else { |
430 | | // i. Let cp be CodePointAt(string, k). |
431 | 0 | auto code_point = code_point_at(utf16_string.view(), k); |
432 | | // ii. If cp.[[IsUnpairedSurrogate]] is true, throw a URIError exception. |
433 | 0 | if (code_point.is_unpaired_surrogate) |
434 | 0 | return vm.throw_completion<URIError>(ErrorType::URIMalformed); |
435 | | |
436 | | // iii. Set k to k + cp.[[CodeUnitCount]]. |
437 | 0 | k += code_point.code_unit_count; |
438 | | |
439 | | // iv. Let Octets be the List of octets resulting by applying the UTF-8 transformation to cp.[[CodePoint]]. |
440 | | // v. For each element octet of Octets, do |
441 | 0 | auto nwritten = AK::UnicodeUtils::code_point_to_utf8(code_point.code_point, [&encoded_builder](u8 octet) { |
442 | | // 1. Let hex be the String representation of octet, formatted as an uppercase hexadecimal number. |
443 | | // 2. Set R to the string-concatenation of R, "%", and ! StringPad(hex, 2𝔽, "0", start). |
444 | 0 | encoded_builder.appendff("%{:02X}", octet); |
445 | 0 | }); |
446 | 0 | VERIFY(nwritten > 0); |
447 | 0 | } |
448 | 0 | } |
449 | 0 | return encoded_builder.to_byte_string(); |
450 | 0 | } |
451 | | |
452 | | // 19.2.6.6 Decode ( string, preserveEscapeSet ), https://tc39.es/ecma262/#sec-decode |
453 | | // FIXME: Add spec comments to this implementation. It deviates a lot, so that's a bit tricky. |
454 | | static ThrowCompletionOr<ByteString> decode(VM& vm, ByteString const& string, StringView reserved_set) |
455 | 0 | { |
456 | 0 | StringBuilder decoded_builder; |
457 | 0 | auto code_point_start_offset = 0u; |
458 | 0 | auto expected_continuation_bytes = 0; |
459 | 0 | for (size_t k = 0; k < string.length(); k++) { |
460 | 0 | auto code_unit = string[k]; |
461 | 0 | if (code_unit != '%') { |
462 | 0 | if (expected_continuation_bytes > 0) |
463 | 0 | return vm.throw_completion<URIError>(ErrorType::URIMalformed); |
464 | | |
465 | 0 | decoded_builder.append(code_unit); |
466 | 0 | continue; |
467 | 0 | } |
468 | | |
469 | 0 | if (k + 2 >= string.length()) |
470 | 0 | return vm.throw_completion<URIError>(ErrorType::URIMalformed); |
471 | | |
472 | 0 | auto first_digit = decode_hex_digit(string[k + 1]); |
473 | 0 | if (first_digit >= 16) |
474 | 0 | return vm.throw_completion<URIError>(ErrorType::URIMalformed); |
475 | | |
476 | 0 | auto second_digit = decode_hex_digit(string[k + 2]); |
477 | 0 | if (second_digit >= 16) |
478 | 0 | return vm.throw_completion<URIError>(ErrorType::URIMalformed); |
479 | | |
480 | 0 | u8 decoded_code_unit = (first_digit << 4) | second_digit; |
481 | 0 | k += 2; |
482 | 0 | if (expected_continuation_bytes > 0) { |
483 | 0 | decoded_builder.append(decoded_code_unit); |
484 | 0 | expected_continuation_bytes--; |
485 | 0 | if (expected_continuation_bytes == 0 && !Utf8View(decoded_builder.string_view().substring_view(code_point_start_offset)).validate()) |
486 | 0 | return vm.throw_completion<URIError>(ErrorType::URIMalformed); |
487 | 0 | continue; |
488 | 0 | } |
489 | | |
490 | 0 | if (decoded_code_unit < 0x80) { |
491 | 0 | if (reserved_set.contains(static_cast<char>(decoded_code_unit))) |
492 | 0 | decoded_builder.append(string.substring_view(k - 2, 3)); |
493 | 0 | else |
494 | 0 | decoded_builder.append(decoded_code_unit); |
495 | 0 | continue; |
496 | 0 | } |
497 | | |
498 | 0 | auto leading_ones = count_leading_zeroes_safe(static_cast<u8>(~decoded_code_unit)); |
499 | 0 | if (leading_ones == 1 || leading_ones > 4) |
500 | 0 | return vm.throw_completion<URIError>(ErrorType::URIMalformed); |
501 | | |
502 | 0 | code_point_start_offset = decoded_builder.length(); |
503 | 0 | decoded_builder.append(decoded_code_unit); |
504 | 0 | expected_continuation_bytes = leading_ones - 1; |
505 | 0 | } |
506 | 0 | if (expected_continuation_bytes > 0) |
507 | 0 | return vm.throw_completion<URIError>(ErrorType::URIMalformed); |
508 | 0 | return decoded_builder.to_byte_string(); |
509 | 0 | } |
510 | | |
511 | | // 19.2.6.1 decodeURI ( encodedURI ), https://tc39.es/ecma262/#sec-decodeuri-encodeduri |
512 | | JS_DEFINE_NATIVE_FUNCTION(GlobalObject::decode_uri) |
513 | 0 | { |
514 | | // 1. Let uriString be ? ToString(encodedURI). |
515 | 0 | auto uri_string = TRY(vm.argument(0).to_byte_string(vm)); |
516 | | |
517 | | // 2. Let preserveEscapeSet be ";/?:@&=+$,#". |
518 | | // 3. Return ? Decode(uriString, preserveEscapeSet). |
519 | 0 | auto decoded = TRY(decode(vm, uri_string, ";/?:@&=+$,#"sv)); |
520 | 0 | return PrimitiveString::create(vm, move(decoded)); |
521 | 0 | } |
522 | | |
523 | | // 19.2.6.2 decodeURIComponent ( encodedURIComponent ), https://tc39.es/ecma262/#sec-decodeuricomponent-encodeduricomponent |
524 | | JS_DEFINE_NATIVE_FUNCTION(GlobalObject::decode_uri_component) |
525 | 0 | { |
526 | 0 | auto encoded_uri_component = vm.argument(0); |
527 | | |
528 | | // 1. Let componentString be ? ToString(encodedURIComponent). |
529 | 0 | auto uri_string = TRY(encoded_uri_component.to_byte_string(vm)); |
530 | | |
531 | | // 2. Let preserveEscapeSet be the empty String. |
532 | | // 3. Return ? Decode(componentString, preserveEscapeSet). |
533 | 0 | auto decoded = TRY(decode(vm, uri_string, ""sv)); |
534 | 0 | return PrimitiveString::create(vm, move(decoded)); |
535 | 0 | } |
536 | | |
537 | | // 19.2.6.3 encodeURI ( uri ), https://tc39.es/ecma262/#sec-encodeuri-uri |
538 | | JS_DEFINE_NATIVE_FUNCTION(GlobalObject::encode_uri) |
539 | 0 | { |
540 | 0 | auto uri = vm.argument(0); |
541 | | |
542 | | // 1. Let uriString be ? ToString(uri). |
543 | 0 | auto uri_string = TRY(uri.to_byte_string(vm)); |
544 | | |
545 | | // 2. Let extraUnescaped be ";/?:@&=+$,#". |
546 | | // 3. Return ? Encode(uriString, extraUnescaped). |
547 | 0 | auto encoded = TRY(encode(vm, uri_string, ";/?:@&=+$,abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.!~*'()#"sv)); |
548 | 0 | return PrimitiveString::create(vm, move(encoded)); |
549 | 0 | } |
550 | | |
551 | | // 19.2.6.4 encodeURIComponent ( uriComponent ), https://tc39.es/ecma262/#sec-encodeuricomponent-uricomponent |
552 | | JS_DEFINE_NATIVE_FUNCTION(GlobalObject::encode_uri_component) |
553 | 0 | { |
554 | 0 | auto uri_component = vm.argument(0); |
555 | | |
556 | | // 1. Let componentString be ? ToString(uriComponent). |
557 | 0 | auto uri_string = TRY(uri_component.to_byte_string(vm)); |
558 | | |
559 | | // 2. Let extraUnescaped be the empty String. |
560 | | // 3. Return ? Encode(componentString, extraUnescaped). |
561 | 0 | auto encoded = TRY(encode(vm, uri_string, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.!~*'()"sv)); |
562 | 0 | return PrimitiveString::create(vm, move(encoded)); |
563 | 0 | } |
564 | | |
565 | | // B.2.1.1 escape ( string ), https://tc39.es/ecma262/#sec-escape-string |
566 | | JS_DEFINE_NATIVE_FUNCTION(GlobalObject::escape) |
567 | 0 | { |
568 | | // 1. Set string to ? ToString(string). |
569 | 0 | auto string = TRY(vm.argument(0).to_byte_string(vm)); |
570 | | |
571 | | // 3. Let R be the empty String. |
572 | 0 | StringBuilder escaped; |
573 | | |
574 | | // 4. Let unescapedSet be the string-concatenation of the ASCII word characters and "@*+-./". |
575 | 0 | auto unescaped_set = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789@*_+-./"sv; |
576 | | |
577 | | // 2. Let length be the length of string. |
578 | | // 5. Let k be 0. |
579 | | // 6. Repeat, while k < length, |
580 | 0 | for (auto code_point : TRY_OR_THROW_OOM(vm, utf8_to_utf16(string))) { |
581 | | // a. Let char be the code unit at index k within string. |
582 | | |
583 | | // b. If unescapedSet contains char, then |
584 | | // NOTE: We know unescapedSet is ASCII-only, so ensure we have an ASCII codepoint before casting to char. |
585 | 0 | if (is_ascii(code_point) && unescaped_set.contains(static_cast<char>(code_point))) { |
586 | | // i. Let S be the String value containing the single code unit char. |
587 | 0 | escaped.append(code_point); |
588 | 0 | } |
589 | | // c. Else, |
590 | | // i. Let n be the numeric value of char. |
591 | | // ii. If n < 256, then |
592 | 0 | else if (code_point < 256) { |
593 | | // 1. Let hex be the String representation of n, formatted as an uppercase hexadecimal number. |
594 | | // 2. Let S be the string-concatenation of "%" and ! StringPad(hex, 2𝔽, "0", start). |
595 | 0 | escaped.appendff("%{:02X}", code_point); |
596 | 0 | } |
597 | | // iii. Else, |
598 | 0 | else { |
599 | | // 1. Let hex be the String representation of n, formatted as an uppercase hexadecimal number. |
600 | | // 2. Let S be the string-concatenation of "%u" and ! StringPad(hex, 4𝔽, "0", start). |
601 | 0 | escaped.appendff("%u{:04X}", code_point); |
602 | 0 | } |
603 | | |
604 | | // d. Set R to the string-concatenation of R and S. |
605 | | // e. Set k to k + 1. |
606 | 0 | } |
607 | | |
608 | | // 7. Return R. |
609 | 0 | return PrimitiveString::create(vm, escaped.to_byte_string()); |
610 | 0 | } |
611 | | |
612 | | // B.2.1.2 unescape ( string ), https://tc39.es/ecma262/#sec-unescape-string |
613 | | JS_DEFINE_NATIVE_FUNCTION(GlobalObject::unescape) |
614 | 0 | { |
615 | | // 1. Set string to ? ToString(string). |
616 | 0 | auto string = TRY(vm.argument(0).to_byte_string(vm)); |
617 | | |
618 | | // 2. Let length be the length of string. |
619 | 0 | ssize_t length = string.length(); |
620 | | |
621 | | // 3. Let R be the empty String. |
622 | 0 | StringBuilder unescaped(length); |
623 | | |
624 | | // 4. Let k be 0. |
625 | | // 5. Repeat, while k ≠ length, |
626 | 0 | for (auto k = 0; k < length; ++k) { |
627 | | // a. Let c be the code unit at index k within string. |
628 | 0 | u32 code_point = string[k]; |
629 | | |
630 | | // b. If c is the code unit 0x0025 (PERCENT SIGN), then |
631 | 0 | if (code_point == '%') { |
632 | | // i. Let hexEscape be the empty String. |
633 | | // ii. Let skip be 0. |
634 | | // iii. If k ≤ length - 6 and the code unit at index k + 1 within string is the code unit 0x0075 (LATIN SMALL LETTER U), then |
635 | 0 | if (k <= length - 6 && string[k + 1] == 'u' && is_ascii_hex_digit(string[k + 2]) && is_ascii_hex_digit(string[k + 3]) && is_ascii_hex_digit(string[k + 4]) && is_ascii_hex_digit(string[k + 5])) { |
636 | | // 1. Set hexEscape to the substring of string from k + 2 to k + 6. |
637 | 0 | code_point = (parse_ascii_hex_digit(string[k + 2]) << 12) | (parse_ascii_hex_digit(string[k + 3]) << 8) | (parse_ascii_hex_digit(string[k + 4]) << 4) | parse_ascii_hex_digit(string[k + 5]); |
638 | | |
639 | | // 2. Set skip to 5. |
640 | 0 | k += 5; |
641 | 0 | } |
642 | | // iv. Else if k ≤ length - 3, then |
643 | 0 | else if (k <= length - 3 && is_ascii_hex_digit(string[k + 1]) && is_ascii_hex_digit(string[k + 2])) { |
644 | | // 1. Set hexEscape to the substring of string from k + 1 to k + 3. |
645 | 0 | code_point = (parse_ascii_hex_digit(string[k + 1]) << 4) | parse_ascii_hex_digit(string[k + 2]); |
646 | | |
647 | | // 2. Set skip to 2. |
648 | 0 | k += 2; |
649 | 0 | } |
650 | | |
651 | | // v. If hexEscape can be interpreted as an expansion of HexDigits[~Sep], then |
652 | | // 1. Let hexIntegerLiteral be the string-concatenation of "0x" and hexEscape. |
653 | | // 2. Let n be ! ToNumber(hexIntegerLiteral). |
654 | | // 3. Set c to the code unit whose value is ℝ(n). |
655 | | // 4. Set k to k + skip. |
656 | | // NOTE: All of this is already done in the branches above. |
657 | 0 | } |
658 | | |
659 | | // c. Set R to the string-concatenation of R and c. |
660 | 0 | unescaped.append_code_point(code_point); |
661 | | |
662 | | // d. Set k to k + 1. |
663 | 0 | } |
664 | | |
665 | | // 6. Return R. |
666 | 0 | return PrimitiveString::create(vm, unescaped.to_byte_string()); |
667 | 0 | } |
668 | | |
669 | | } |