
As outlined in the previous commit, this should be more reliable than Interpreter::realm(), as we don't always have access to an Interpreter.
203 lines
8.2 KiB
C++
203 lines
8.2 KiB
C++
/*
|
|
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
|
|
* Copyright (c) 2021, Linus Groh <linusg@serenityos.org>
|
|
*
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*/
|
|
|
|
#include <LibJS/Interpreter.h>
|
|
#include <LibJS/Runtime/FunctionEnvironment.h>
|
|
#include <LibJS/Runtime/GlobalObject.h>
|
|
#include <LibJS/Runtime/NativeFunction.h>
|
|
#include <LibJS/Runtime/Value.h>
|
|
|
|
namespace JS {
|
|
|
|
NativeFunction* NativeFunction::create(GlobalObject& global_object, const FlyString& name, Function<Value(VM&, GlobalObject&)> function)
|
|
{
|
|
return global_object.heap().allocate<NativeFunction>(global_object, name, move(function), *global_object.function_prototype());
|
|
}
|
|
|
|
// FIXME: m_realm is supposed to be the realm argument of CreateBuiltinFunction, or the current
|
|
// Realm Record. The former is not something that's commonly used or we support, the
|
|
// latter is impossible as no ExecutionContext exists when most NativeFunctions are created...
|
|
|
|
NativeFunction::NativeFunction(Object& prototype)
|
|
: FunctionObject(prototype)
|
|
, m_realm(global_object().associated_realm())
|
|
{
|
|
}
|
|
|
|
NativeFunction::NativeFunction(FlyString name, Function<Value(VM&, GlobalObject&)> native_function, Object& prototype)
|
|
: FunctionObject(prototype)
|
|
, m_name(move(name))
|
|
, m_native_function(move(native_function))
|
|
, m_realm(global_object().associated_realm())
|
|
{
|
|
}
|
|
|
|
NativeFunction::NativeFunction(FlyString name, Object& prototype)
|
|
: FunctionObject(prototype)
|
|
, m_name(move(name))
|
|
, m_realm(global_object().associated_realm())
|
|
{
|
|
}
|
|
|
|
NativeFunction::~NativeFunction()
|
|
{
|
|
}
|
|
|
|
// NOTE: Do not attempt to DRY these, it's not worth it. The difference in return types (Value vs Object*),
|
|
// called functions (call() vs construct(FunctionObject&)), and this value (passed vs uninitialized) make
|
|
// these good candidates for a bit of code duplication :^)
|
|
|
|
// 10.3.1 [[Call]] ( thisArgument, argumentsList ), https://tc39.es/ecma262/#sec-built-in-function-objects-call-thisargument-argumentslist
|
|
ThrowCompletionOr<Value> NativeFunction::internal_call(Value this_argument, MarkedValueList arguments_list)
|
|
{
|
|
auto& vm = this->vm();
|
|
auto& global_object = this->global_object();
|
|
|
|
// 1. Let callerContext be the running execution context.
|
|
auto& caller_context = vm.running_execution_context();
|
|
|
|
// 2. If callerContext is not already suspended, suspend callerContext.
|
|
// NOTE: We don't support this concept yet.
|
|
|
|
// 3. Let calleeContext be a new execution context.
|
|
ExecutionContext callee_context(heap());
|
|
|
|
// 4. Set the Function of calleeContext to F.
|
|
callee_context.function = this;
|
|
callee_context.function_name = m_name;
|
|
|
|
// 5. Let calleeRealm be F.[[Realm]].
|
|
auto* callee_realm = m_realm;
|
|
// NOTE: This non-standard fallback is needed until we can guarantee that literally
|
|
// every function has a realm - especially in LibWeb that's sometimes not the case
|
|
// when a function is created while no JS is running, as we currently need to rely on
|
|
// that (:acid2:, I know - see set_event_handler_attribute() for an example).
|
|
// If there's no 'current realm' either, we can't continue and crash.
|
|
if (!callee_realm)
|
|
callee_realm = vm.current_realm();
|
|
VERIFY(callee_realm);
|
|
|
|
// 6. Set the Realm of calleeContext to calleeRealm.
|
|
callee_context.realm = callee_realm;
|
|
|
|
// 7. Set the ScriptOrModule of calleeContext to null.
|
|
// FIXME: Our execution context struct currently does not track this item.
|
|
|
|
// 8. Perform any necessary implementation-defined initialization of calleeContext.
|
|
|
|
callee_context.this_value = this_argument;
|
|
callee_context.arguments.extend(move(arguments_list));
|
|
|
|
callee_context.lexical_environment = caller_context.lexical_environment;
|
|
callee_context.variable_environment = caller_context.variable_environment;
|
|
|
|
// NOTE: This is a LibJS specific hack for NativeFunction to inherit the strictness of its caller.
|
|
callee_context.is_strict_mode = vm.in_strict_mode();
|
|
|
|
if (auto* interpreter = vm.interpreter_if_exists())
|
|
callee_context.current_node = interpreter->current_node();
|
|
|
|
// </8.> --------------------------------------------------------------------------
|
|
|
|
// 9. Push calleeContext onto the execution context stack; calleeContext is now the running execution context.
|
|
vm.push_execution_context(callee_context, global_object);
|
|
|
|
// 10. Let result be the Completion Record that is the result of evaluating F in a manner that conforms to the specification of F. thisArgument is the this value, argumentsList provides the named parameters, and the NewTarget value is undefined.
|
|
auto result = call();
|
|
|
|
// 11. Remove calleeContext from the execution context stack and restore callerContext as the running execution context.
|
|
vm.pop_execution_context();
|
|
|
|
// 12. Return result.
|
|
if (auto* exception = vm.exception())
|
|
return throw_completion(exception->value());
|
|
return result;
|
|
}
|
|
|
|
// 10.3.2 [[Construct]] ( argumentsList, newTarget ), https://tc39.es/ecma262/#sec-built-in-function-objects-construct-argumentslist-newtarget
|
|
ThrowCompletionOr<Object*> NativeFunction::internal_construct(MarkedValueList arguments_list, FunctionObject& new_target)
|
|
{
|
|
auto& vm = this->vm();
|
|
auto& global_object = this->global_object();
|
|
|
|
// 1. Let callerContext be the running execution context.
|
|
auto& caller_context = vm.running_execution_context();
|
|
|
|
// 2. If callerContext is not already suspended, suspend callerContext.
|
|
// NOTE: We don't support this concept yet.
|
|
|
|
// 3. Let calleeContext be a new execution context.
|
|
ExecutionContext callee_context(heap());
|
|
|
|
// 4. Set the Function of calleeContext to F.
|
|
callee_context.function = this;
|
|
callee_context.function_name = m_name;
|
|
|
|
// 5. Let calleeRealm be F.[[Realm]].
|
|
auto* callee_realm = m_realm;
|
|
// NOTE: This non-standard fallback is needed until we can guarantee that literally
|
|
// every function has a realm - especially in LibWeb that's sometimes not the case
|
|
// when a function is created while no JS is running, as we currently need to rely on
|
|
// that (:acid2:, I know - see set_event_handler_attribute() for an example).
|
|
// If there's no 'current realm' either, we can't continue and crash.
|
|
if (!callee_realm)
|
|
callee_realm = vm.current_realm();
|
|
VERIFY(callee_realm);
|
|
|
|
// 6. Set the Realm of calleeContext to calleeRealm.
|
|
callee_context.realm = callee_realm;
|
|
|
|
// 7. Set the ScriptOrModule of calleeContext to null.
|
|
// FIXME: Our execution context struct currently does not track this item.
|
|
|
|
// 8. Perform any necessary implementation-defined initialization of calleeContext.
|
|
|
|
callee_context.arguments.extend(move(arguments_list));
|
|
|
|
callee_context.lexical_environment = caller_context.lexical_environment;
|
|
callee_context.variable_environment = caller_context.variable_environment;
|
|
|
|
// NOTE: This is a LibJS specific hack for NativeFunction to inherit the strictness of its caller.
|
|
callee_context.is_strict_mode = vm.in_strict_mode();
|
|
|
|
if (auto* interpreter = vm.interpreter_if_exists())
|
|
callee_context.current_node = interpreter->current_node();
|
|
|
|
// </8.> --------------------------------------------------------------------------
|
|
|
|
// 9. Push calleeContext onto the execution context stack; calleeContext is now the running execution context.
|
|
vm.push_execution_context(callee_context, global_object);
|
|
|
|
// 10. Let result be the Completion Record that is the result of evaluating F in a manner that conforms to the specification of F. The this value is uninitialized, argumentsList provides the named parameters, and newTarget provides the NewTarget value.
|
|
auto result = construct(new_target);
|
|
|
|
// 11. Remove calleeContext from the execution context stack and restore callerContext as the running execution context.
|
|
vm.pop_execution_context();
|
|
|
|
// 12. Return result.
|
|
if (auto* exception = vm.exception())
|
|
return throw_completion(exception->value());
|
|
return &result.as_object();
|
|
}
|
|
|
|
Value NativeFunction::call()
|
|
{
|
|
return m_native_function(vm(), global_object());
|
|
}
|
|
|
|
Value NativeFunction::construct(FunctionObject&)
|
|
{
|
|
// Needs to be overridden if [[Construct]] is needed.
|
|
VERIFY_NOT_REACHED();
|
|
}
|
|
|
|
bool NativeFunction::is_strict_mode() const
|
|
{
|
|
return true;
|
|
}
|
|
|
|
}
|