Coverage Report

Created: 2025-03-04 07:22

/src/serenity/Userland/Libraries/LibWeb/WebIDL/AbstractOperations.cpp
Line
Count
Source (jump to first uncovered line)
1
/*
2
 * Copyright (c) 2021-2023, Luke Wilde <lukew@serenityos.org>
3
 * Copyright (c) 2021-2022, Linus Groh <linusg@serenityos.org>
4
 * Copyright (c) 2023, Shannon Booth <shannon@serenityos.org>
5
 *
6
 * SPDX-License-Identifier: BSD-2-Clause
7
 */
8
9
#include <AK/ByteBuffer.h>
10
#include <AK/Math.h>
11
#include <AK/NumericLimits.h>
12
#include <LibJS/Runtime/AbstractOperations.h>
13
#include <LibJS/Runtime/ArrayBuffer.h>
14
#include <LibJS/Runtime/DataView.h>
15
#include <LibJS/Runtime/PropertyKey.h>
16
#include <LibJS/Runtime/TypedArray.h>
17
#include <LibJS/Runtime/ValueInlines.h>
18
#include <LibWeb/Bindings/PlatformObject.h>
19
#include <LibWeb/HTML/Window.h>
20
#include <LibWeb/WebIDL/AbstractOperations.h>
21
#include <LibWeb/WebIDL/Promise.h>
22
#include <LibWeb/WebIDL/Types.h>
23
24
namespace Web::WebIDL {
25
26
// https://webidl.spec.whatwg.org/#dfn-get-buffer-source-copy
27
ErrorOr<ByteBuffer> get_buffer_source_copy(JS::Object const& buffer_source)
28
0
{
29
    // 1. Let esBufferSource be the result of converting bufferSource to an ECMAScript value.
30
31
    // 2. Let esArrayBuffer be esBufferSource.
32
0
    JS::ArrayBuffer* es_array_buffer;
33
34
    // 3. Let offset be 0.
35
0
    u32 offset = 0;
36
37
    // 4. Let length be 0.
38
0
    u32 length = 0;
39
40
    // 5. If esBufferSource has a [[ViewedArrayBuffer]] internal slot, then:
41
0
    if (is<JS::TypedArrayBase>(buffer_source)) {
42
0
        auto const& es_buffer_source = static_cast<JS::TypedArrayBase const&>(buffer_source);
43
44
0
        auto typed_array_record = JS::make_typed_array_with_buffer_witness_record(es_buffer_source, JS::ArrayBuffer::Order::SeqCst);
45
46
        // AD-HOC: The WebIDL spec has not been updated for resizable ArrayBuffer objects. This check follows the behavior of step 7.
47
0
        if (JS::is_typed_array_out_of_bounds(typed_array_record))
48
0
            return ByteBuffer {};
49
50
        // 1. Set esArrayBuffer to esBufferSource.[[ViewedArrayBuffer]].
51
0
        es_array_buffer = es_buffer_source.viewed_array_buffer();
52
53
        // 2. Set offset to esBufferSource.[[ByteOffset]].
54
0
        offset = es_buffer_source.byte_offset();
55
56
        // 3. Set length to esBufferSource.[[ByteLength]].
57
0
        length = JS::typed_array_byte_length(typed_array_record);
58
0
    } else if (is<JS::DataView>(buffer_source)) {
59
0
        auto const& es_buffer_source = static_cast<JS::DataView const&>(buffer_source);
60
61
0
        auto view_record = JS::make_data_view_with_buffer_witness_record(es_buffer_source, JS::ArrayBuffer::Order::SeqCst);
62
63
        // AD-HOC: The WebIDL spec has not been updated for resizable ArrayBuffer objects. This check follows the behavior of step 7.
64
0
        if (JS::is_view_out_of_bounds(view_record))
65
0
            return ByteBuffer {};
66
67
        // 1. Set esArrayBuffer to esBufferSource.[[ViewedArrayBuffer]].
68
0
        es_array_buffer = es_buffer_source.viewed_array_buffer();
69
70
        // 2. Set offset to esBufferSource.[[ByteOffset]].
71
0
        offset = es_buffer_source.byte_offset();
72
73
        // 3. Set length to esBufferSource.[[ByteLength]].
74
0
        length = JS::get_view_byte_length(view_record);
75
0
    }
76
    // 6. Otherwise:
77
0
    else {
78
        // 1. Assert: esBufferSource is an ArrayBuffer or SharedArrayBuffer object.
79
0
        auto const& es_buffer_source = static_cast<JS::ArrayBuffer const&>(buffer_source);
80
0
        es_array_buffer = &const_cast<JS::ArrayBuffer&>(es_buffer_source);
81
82
        // 2. Set length to esBufferSource.[[ArrayBufferByteLength]].
83
0
        length = es_buffer_source.byte_length();
84
0
    }
85
86
    // 7. If ! IsDetachedBuffer(esArrayBuffer) is true, then return the empty byte sequence.
87
0
    if (es_array_buffer->is_detached())
88
0
        return ByteBuffer {};
89
90
    // 8. Let bytes be a new byte sequence of length equal to length.
91
0
    auto bytes = TRY(ByteBuffer::create_zeroed(length));
92
93
    // 9. For i in the range offset to offset + length − 1, inclusive, set bytes[i − offset] to ! GetValueFromBuffer(esArrayBuffer, i, Uint8, true, Unordered).
94
0
    for (u64 i = offset; i < offset + length; ++i) {
95
0
        auto value = es_array_buffer->get_value<u8>(i, true, JS::ArrayBuffer::Unordered);
96
0
        bytes[i - offset] = static_cast<u8>(value.as_double());
97
0
    }
98
99
    // 10. Return bytes.
100
0
    return bytes;
101
0
}
102
103
// https://webidl.spec.whatwg.org/#call-user-object-operation-return
104
inline JS::Completion clean_up_on_return(HTML::EnvironmentSettingsObject& stored_settings, HTML::EnvironmentSettingsObject& relevant_settings, JS::Completion& completion, OperationReturnsPromise operation_returns_promise)
105
0
{
106
0
    auto& realm = stored_settings.realm();
107
108
    // Return: at this point completion will be set to an ECMAScript completion value.
109
110
    // 1. Clean up after running a callback with stored settings.
111
0
    stored_settings.clean_up_after_running_callback();
112
113
    // 2. Clean up after running script with relevant settings.
114
0
    relevant_settings.clean_up_after_running_script();
115
116
    // 3. If completion is a normal completion, return completion.
117
0
    if (completion.type() == JS::Completion::Type::Normal)
118
0
        return completion;
119
120
    // 4. If completion is an abrupt completion and the operation has a return type that is not a promise type, return completion.
121
0
    if (completion.is_abrupt() && operation_returns_promise == OperationReturnsPromise::No)
122
0
        return completion;
123
124
    // 5. Let rejectedPromise be ! Call(%Promise.reject%, %Promise%, «completion.[[Value]]»).
125
0
    auto rejected_promise = create_rejected_promise(realm, *completion.release_value());
126
127
    // 6. Return the result of converting rejectedPromise to the operation’s return type.
128
    // Note: The operation must return a promise, so no conversion is necessary
129
0
    return JS::Value { rejected_promise->promise() };
130
0
}
131
132
JS::Completion call_user_object_operation(WebIDL::CallbackType& callback, String const& operation_name, Optional<JS::Value> this_argument, JS::MarkedVector<JS::Value> args)
133
0
{
134
    // 1. Let completion be an uninitialized variable.
135
0
    JS::Completion completion;
136
137
    // 2. If thisArg was not given, let thisArg be undefined.
138
0
    if (!this_argument.has_value())
139
0
        this_argument = JS::js_undefined();
140
141
    // 3. Let O be the ECMAScript object corresponding to value.
142
0
    auto& object = callback.callback;
143
144
    // 4. Let realm be O’s associated Realm.
145
0
    auto& realm = object->shape().realm();
146
147
    // 5. Let relevant settings be realm’s settings object.
148
0
    auto& relevant_settings = Bindings::host_defined_environment_settings_object(realm);
149
150
    // 6. Let stored settings be value’s callback context.
151
0
    auto& stored_settings = callback.callback_context;
152
153
    // 7. Prepare to run script with relevant settings.
154
0
    relevant_settings.prepare_to_run_script();
155
156
    // 8. Prepare to run a callback with stored settings.
157
0
    stored_settings->prepare_to_run_callback();
158
159
    // 9. Let X be O.
160
0
    auto actual_function_object = object;
161
162
    // 10. If ! IsCallable(O) is false, then:
163
0
    if (!object->is_function()) {
164
        // 1. Let getResult be Get(O, opName).
165
0
        auto get_result = object->get(operation_name.to_byte_string());
166
167
        // 2. If getResult is an abrupt completion, set completion to getResult and jump to the step labeled return.
168
0
        if (get_result.is_throw_completion()) {
169
0
            completion = get_result.throw_completion();
170
0
            return clean_up_on_return(stored_settings, relevant_settings, completion, callback.operation_returns_promise);
171
0
        }
172
173
        // 4. If ! IsCallable(X) is false, then set completion to a new Completion{[[Type]]: throw, [[Value]]: a newly created TypeError object, [[Target]]: empty}, and jump to the step labeled return.
174
0
        if (!get_result.value().is_function()) {
175
0
            completion = realm.vm().template throw_completion<JS::TypeError>(JS::ErrorType::NotAFunction, get_result.value().to_string_without_side_effects());
176
0
            return clean_up_on_return(stored_settings, relevant_settings, completion, callback.operation_returns_promise);
177
0
        }
178
179
        // 3. Set X to getResult.[[Value]].
180
        // NOTE: This is done out of order because `actual_function_object` is of type JS::Object and we cannot assign to it until we know for sure getResult.[[Value]] is a JS::Object.
181
0
        actual_function_object = get_result.release_value().as_object();
182
183
        // 5. Set thisArg to O (overriding the provided value).
184
0
        this_argument = object;
185
0
    }
186
187
    // FIXME: 11. Let esArgs be the result of converting args to an ECMAScript arguments list. If this throws an exception, set completion to the completion value representing the thrown exception and jump to the step labeled return.
188
    //        For simplicity, we currently make the caller do this. However, this means we can't throw exceptions at this point like the spec wants us to.
189
190
    // 12. Let callResult be Call(X, thisArg, esArgs).
191
0
    VERIFY(actual_function_object);
192
0
    auto& vm = object->vm();
193
0
    auto call_result = JS::call(vm, verify_cast<JS::FunctionObject>(*actual_function_object), this_argument.value(), args.span());
194
195
    // 13. If callResult is an abrupt completion, set completion to callResult and jump to the step labeled return.
196
0
    if (call_result.is_throw_completion()) {
197
0
        completion = call_result.throw_completion();
198
0
        return clean_up_on_return(stored_settings, relevant_settings, completion, callback.operation_returns_promise);
199
0
    }
200
201
    // 14. Set completion to the result of converting callResult.[[Value]] to an IDL value of the same type as the operation’s return type.
202
    // FIXME: This does no conversion.
203
0
    completion = call_result.value();
204
205
0
    return clean_up_on_return(stored_settings, relevant_settings, completion, callback.operation_returns_promise);
206
0
}
207
208
// https://webidl.spec.whatwg.org/#invoke-a-callback-function
209
JS::Completion invoke_callback(WebIDL::CallbackType& callback, Optional<JS::Value> this_argument, JS::MarkedVector<JS::Value> args)
210
0
{
211
    // 1. Let completion be an uninitialized variable.
212
0
    JS::Completion completion;
213
214
    // 2. If thisArg was not given, let thisArg be undefined.
215
0
    if (!this_argument.has_value())
216
0
        this_argument = JS::js_undefined();
217
218
    // 3. Let F be the ECMAScript object corresponding to callable.
219
0
    auto& function_object = callback.callback;
220
221
    // 4. If ! IsCallable(F) is false:
222
0
    if (!function_object->is_function()) {
223
        // 1. Note: This is only possible when the callback function came from an attribute marked with [LegacyTreatNonObjectAsNull].
224
225
        // 2. Return the result of converting undefined to the callback function’s return type.
226
        // FIXME: This does no conversion.
227
0
        return { JS::js_undefined() };
228
0
    }
229
230
    // 5. Let realm be F’s associated Realm.
231
    // See the comment about associated realm on step 4 of call_user_object_operation.
232
0
    auto& realm = function_object->shape().realm();
233
234
    // 6. Let relevant settings be realm’s settings object.
235
0
    auto& relevant_settings = Bindings::host_defined_environment_settings_object(realm);
236
237
    // 7. Let stored settings be value’s callback context.
238
0
    auto& stored_settings = callback.callback_context;
239
240
    // 8. Prepare to run script with relevant settings.
241
0
    relevant_settings.prepare_to_run_script();
242
243
    // 9. Prepare to run a callback with stored settings.
244
0
    stored_settings->prepare_to_run_callback();
245
246
    // FIXME: 10. Let esArgs be the result of converting args to an ECMAScript arguments list. If this throws an exception, set completion to the completion value representing the thrown exception and jump to the step labeled return.
247
    //        For simplicity, we currently make the caller do this. However, this means we can't throw exceptions at this point like the spec wants us to.
248
249
    // 11. Let callResult be Call(F, thisArg, esArgs).
250
0
    auto& vm = function_object->vm();
251
0
    auto call_result = JS::call(vm, verify_cast<JS::FunctionObject>(*function_object), this_argument.value(), args.span());
252
253
    // 12. If callResult is an abrupt completion, set completion to callResult and jump to the step labeled return.
254
0
    if (call_result.is_throw_completion()) {
255
0
        completion = call_result.throw_completion();
256
0
        return clean_up_on_return(stored_settings, relevant_settings, completion, callback.operation_returns_promise);
257
0
    }
258
259
    // 13. Set completion to the result of converting callResult.[[Value]] to an IDL value of the same type as the operation’s return type.
260
    // FIXME: This does no conversion.
261
0
    completion = call_result.value();
262
263
0
    return clean_up_on_return(stored_settings, relevant_settings, completion, callback.operation_returns_promise);
264
0
}
265
266
JS::Completion construct(WebIDL::CallbackType& callback, JS::MarkedVector<JS::Value> args)
267
0
{
268
    // 1. Let completion be an uninitialized variable.
269
0
    JS::Completion completion;
270
271
    // 2. Let F be the ECMAScript object corresponding to callable.
272
0
    auto& function_object = callback.callback;
273
274
    // 4. Let realm be F’s associated Realm.
275
0
    auto& realm = function_object->shape().realm();
276
277
    // 3. If IsConstructor(F) is false, throw a TypeError exception.
278
0
    if (!JS::Value(function_object).is_constructor())
279
0
        return realm.vm().template throw_completion<JS::TypeError>(JS::ErrorType::NotAConstructor, JS::Value(function_object).to_string_without_side_effects());
280
281
    // 5. Let relevant settings be realm’s settings object.
282
0
    auto& relevant_settings = Bindings::host_defined_environment_settings_object(realm);
283
284
    // 6. Let stored settings be callable’s callback context.
285
0
    auto& stored_settings = callback.callback_context;
286
287
    // 7. Prepare to run script with relevant settings.
288
0
    relevant_settings.prepare_to_run_script();
289
290
    // 8. Prepare to run a callback with stored settings.
291
0
    stored_settings->prepare_to_run_callback();
292
293
    // FIXME: 9. Let esArgs be the result of converting args to an ECMAScript arguments list. If this throws an exception, set completion to the completion value representing the thrown exception and jump to the step labeled return.
294
    //        For simplicity, we currently make the caller do this. However, this means we can't throw exceptions at this point like the spec wants us to.
295
296
    // 10. Let callResult be Completion(Construct(F, esArgs)).
297
0
    auto& vm = function_object->vm();
298
0
    auto call_result = JS::construct(vm, verify_cast<JS::FunctionObject>(*function_object), args.span());
299
300
    // 11. If callResult is an abrupt completion, set completion to callResult and jump to the step labeled return.
301
0
    if (call_result.is_throw_completion()) {
302
0
        completion = call_result.throw_completion();
303
0
    }
304
305
    // 12. Set completion to the result of converting callResult.[[Value]] to an IDL value of the same type as the operation’s return type.
306
0
    else {
307
        // FIXME: This does no conversion.
308
0
        completion = JS::Value(call_result.value());
309
0
    }
310
311
    // 13. Return: at this point completion will be set to an ECMAScript completion value.
312
    // 1. Clean up after running a callback with stored settings.
313
0
    stored_settings->clean_up_after_running_callback();
314
315
    // 2. Clean up after running script with relevant settings.
316
0
    relevant_settings.clean_up_after_running_script();
317
318
    // 3. Return completion.
319
0
    return completion;
320
0
}
321
322
// https://webidl.spec.whatwg.org/#abstract-opdef-integerpart
323
double integer_part(double n)
324
0
{
325
    // 1. Let r be floor(abs(n)).
326
0
    auto r = floor(abs(n));
327
328
    // 2. If n < 0, then return -1 × r.
329
0
    if (n < 0)
330
0
        return -r;
331
332
    // 3. Otherwise, return r.
333
0
    return r;
334
0
}
335
336
// https://webidl.spec.whatwg.org/#abstract-opdef-converttoint
337
template<Integral T>
338
JS::ThrowCompletionOr<T> convert_to_int(JS::VM& vm, JS::Value value, EnforceRange enforce_range, Clamp clamp)
339
0
{
340
0
    double upper_bound = 0;
341
0
    double lower_bound = 0;
342
343
    // 1. If bitLength is 64, then:
344
0
    if constexpr (sizeof(T) == 8) {
345
        // 1. Let upperBound be 2^(53) − 1
346
0
        upper_bound = JS::MAX_ARRAY_LIKE_INDEX;
347
348
        // 2. If signedness is "unsigned", then let lowerBound be 0.
349
0
        if constexpr (IsUnsigned<T>) {
350
0
            lower_bound = 0;
351
        }
352
        // 3. Otherwise let lowerBound be −2^(53) + 1.
353
0
        else {
354
0
            lower_bound = -JS::MAX_ARRAY_LIKE_INDEX;
355
0
        }
356
357
        // Note: this ensures long long types associated with [EnforceRange] or [Clamp] extended attributes are representable in ECMAScript’s Number type as unambiguous integers.
358
0
    } else {
359
        // 2. Otherwise, if signedness is "unsigned", then:
360
        //     1. Let lowerBound be 0.
361
        //     2. Let upperBound be 2^(bitLength) − 1.
362
        // 3. Otherwise:
363
        //     1. Let lowerBound be -2^(bitLength − 1).
364
        //     2. Let upperBound be 2^(bitLength − 1) − 1.
365
0
        lower_bound = NumericLimits<T>::min();
366
0
        upper_bound = NumericLimits<T>::max();
367
0
    }
368
369
    // 4. Let x be ? ToNumber(V).
370
0
    auto x = TRY(value.to_number(vm)).as_double();
371
372
    // 5. If x is −0, then set x to +0.
373
0
    if (x == -0.)
374
0
        x = 0.;
375
376
    // 6. If the conversion is to an IDL type associated with the [EnforceRange] extended attribute, then:
377
0
    if (enforce_range == EnforceRange::Yes) {
378
        // 1. If x is NaN, +∞, or −∞, then throw a TypeError.
379
0
        if (isnan(x) || isinf(x))
380
0
            return vm.throw_completion<JS::TypeError>(JS::ErrorType::NumberIsNaNOrInfinity);
381
382
        // 2. Set x to IntegerPart(x).
383
0
        x = integer_part(x);
384
385
        // 3. If x < lowerBound or x > upperBound, then throw a TypeError.
386
0
        if (x < lower_bound || x > upper_bound)
387
0
            return vm.throw_completion<JS::RangeError>(MUST(String::formatted("Number '{}' is outside of allowed range of {} to {}", x, lower_bound, upper_bound)));
388
389
        // 4. Return x.
390
0
        return x;
391
0
    }
392
393
    // 7. If x is not NaN and the conversion is to an IDL type associated with the [Clamp] extended attribute, then:
394
0
    if (clamp == Clamp::Yes && !isnan(x)) {
395
        // 1. Set x to min(max(x, lowerBound), upperBound).
396
0
        x = min(max(x, lower_bound), upper_bound);
397
398
        // 2. Round x to the nearest integer, choosing the even integer if it lies halfway between two, and choosing +0 rather than −0.
399
        // 3. Return x.
400
0
        return round(x);
401
0
    }
402
403
    // 8. If x is NaN, +0, +∞, or −∞, then return +0.
404
0
    if (isnan(x) || x == 0.0 || isinf(x))
405
0
        return 0;
406
407
    // 9. Set x to IntegerPart(x).
408
0
    x = integer_part(x);
409
410
    // 10. Set x to x modulo 2^bitLength.
411
0
    auto constexpr two_pow_bitlength = NumericLimits<MakeUnsigned<T>>::max() + 1.0;
412
0
    x = JS::modulo(x, two_pow_bitlength);
413
414
    // 11. If signedness is "signed" and x ≥ 2^(bitLength − 1), then return x − 2^(bitLength).
415
0
    if (IsSigned<T> && x > NumericLimits<T>::max())
416
0
        return x - two_pow_bitlength;
417
418
    // 12. Otherwise, return x.
419
0
    return x;
420
0
}
Unexecuted instantiation: _ZN3Web6WebIDL14convert_to_intITkN2AK8Concepts8IntegralEaEEN2JS17ThrowCompletionOrIT_EERNS4_2VMENS4_5ValueENS0_12EnforceRangeENS0_5ClampE
Unexecuted instantiation: _ZN3Web6WebIDL14convert_to_intITkN2AK8Concepts8IntegralEhEEN2JS17ThrowCompletionOrIT_EERNS4_2VMENS4_5ValueENS0_12EnforceRangeENS0_5ClampE
Unexecuted instantiation: _ZN3Web6WebIDL14convert_to_intITkN2AK8Concepts8IntegralEsEEN2JS17ThrowCompletionOrIT_EERNS4_2VMENS4_5ValueENS0_12EnforceRangeENS0_5ClampE
Unexecuted instantiation: _ZN3Web6WebIDL14convert_to_intITkN2AK8Concepts8IntegralEtEEN2JS17ThrowCompletionOrIT_EERNS4_2VMENS4_5ValueENS0_12EnforceRangeENS0_5ClampE
Unexecuted instantiation: _ZN3Web6WebIDL14convert_to_intITkN2AK8Concepts8IntegralEiEEN2JS17ThrowCompletionOrIT_EERNS4_2VMENS4_5ValueENS0_12EnforceRangeENS0_5ClampE
Unexecuted instantiation: _ZN3Web6WebIDL14convert_to_intITkN2AK8Concepts8IntegralEjEEN2JS17ThrowCompletionOrIT_EERNS4_2VMENS4_5ValueENS0_12EnforceRangeENS0_5ClampE
Unexecuted instantiation: _ZN3Web6WebIDL14convert_to_intITkN2AK8Concepts8IntegralElEEN2JS17ThrowCompletionOrIT_EERNS4_2VMENS4_5ValueENS0_12EnforceRangeENS0_5ClampE
Unexecuted instantiation: _ZN3Web6WebIDL14convert_to_intITkN2AK8Concepts8IntegralEmEEN2JS17ThrowCompletionOrIT_EERNS4_2VMENS4_5ValueENS0_12EnforceRangeENS0_5ClampE
421
422
template JS::ThrowCompletionOr<Byte> convert_to_int(JS::VM& vm, JS::Value, EnforceRange, Clamp);
423
template JS::ThrowCompletionOr<Octet> convert_to_int(JS::VM& vm, JS::Value, EnforceRange, Clamp);
424
template JS::ThrowCompletionOr<Short> convert_to_int(JS::VM& vm, JS::Value, EnforceRange, Clamp);
425
template JS::ThrowCompletionOr<UnsignedShort> convert_to_int(JS::VM& vm, JS::Value, EnforceRange, Clamp);
426
template JS::ThrowCompletionOr<Long> convert_to_int(JS::VM& vm, JS::Value, EnforceRange, Clamp);
427
template JS::ThrowCompletionOr<UnsignedLong> convert_to_int(JS::VM& vm, JS::Value, EnforceRange, Clamp);
428
template JS::ThrowCompletionOr<LongLong> convert_to_int(JS::VM& vm, JS::Value, EnforceRange, Clamp);
429
template JS::ThrowCompletionOr<UnsignedLongLong> convert_to_int(JS::VM& vm, JS::Value, EnforceRange, Clamp);
430
431
}