Coverage Report

Created: 2026-07-25 07:46

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