summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Tests/LibWeb/test-web.cpp55
-rw-r--r--Userland/Libraries/LibJS/Forward.h1
-rw-r--r--Userland/Libraries/LibJS/Module.h1
-rw-r--r--Userland/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.h5
-rw-r--r--Userland/Libraries/LibJS/Runtime/ExecutionContext.h5
-rw-r--r--Userland/Libraries/LibJS/Runtime/Promise.h2
-rw-r--r--Userland/Libraries/LibJS/Runtime/Reference.h1
-rw-r--r--Userland/Libraries/LibTest/JavaScriptTestRunner.h11
-rw-r--r--Userland/Libraries/LibTest/JavaScriptTestRunnerMain.cpp1
-rw-r--r--Userland/Libraries/LibWeb/Bindings/CallbackType.h28
-rw-r--r--Userland/Libraries/LibWeb/Bindings/WindowObject.cpp7
-rw-r--r--Userland/Libraries/LibWeb/Bindings/WindowObject.h2
-rw-r--r--Userland/Libraries/LibWeb/CMakeLists.txt2
-rw-r--r--Userland/Libraries/LibWeb/DOM/Document.cpp46
-rw-r--r--Userland/Libraries/LibWeb/DOM/Document.h4
-rw-r--r--Userland/Libraries/LibWeb/DOM/Window.cpp10
-rw-r--r--Userland/Libraries/LibWeb/Forward.h4
-rw-r--r--Userland/Libraries/LibWeb/HTML/EventLoop/EventLoop.cpp59
-rw-r--r--Userland/Libraries/LibWeb/HTML/EventLoop/EventLoop.h19
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLScriptElement.cpp9
-rw-r--r--Userland/Libraries/LibWeb/HTML/Parser/HTMLParser.cpp4
-rw-r--r--Userland/Libraries/LibWeb/HTML/Scripting/ClassicScript.cpp107
-rw-r--r--Userland/Libraries/LibWeb/HTML/Scripting/ClassicScript.h11
-rw-r--r--Userland/Libraries/LibWeb/HTML/Scripting/Environments.cpp273
-rw-r--r--Userland/Libraries/LibWeb/HTML/Scripting/Environments.h121
-rw-r--r--Userland/Libraries/LibWeb/HTML/Scripting/WindowEnvironmentSettingsObject.cpp86
-rw-r--r--Userland/Libraries/LibWeb/HTML/Scripting/WindowEnvironmentSettingsObject.h32
-rw-r--r--Userland/Services/WebContent/ClientConnection.cpp26
-rw-r--r--Userland/Services/WebContent/WebContentConsoleClient.cpp37
29 files changed, 883 insertions, 86 deletions
diff --git a/Tests/LibWeb/test-web.cpp b/Tests/LibWeb/test-web.cpp
index d778cd1bc3..14819e41c1 100644
--- a/Tests/LibWeb/test-web.cpp
+++ b/Tests/LibWeb/test-web.cpp
@@ -11,7 +11,9 @@
#include <LibGUI/Window.h>
#include <LibTest/JavaScriptTestRunner.h>
#include <LibWeb/Bindings/MainThreadVM.h>
+#include <LibWeb/Bindings/WindowObject.h>
#include <LibWeb/HTML/Parser/HTMLParser.h>
+#include <LibWeb/HTML/Scripting/WindowEnvironmentSettingsObject.h>
#include <LibWeb/InProcessWebView.h>
#include <LibWeb/Loader/ResourceLoader.h>
@@ -24,6 +26,7 @@ RefPtr<GUI::Application> g_app;
Optional<URL> next_page_to_load;
Vector<Function<JS::ThrowCompletionOr<void>(JS::Object&)>> after_initial_load_hooks;
Vector<Function<JS::ThrowCompletionOr<void>(JS::Object&)>> before_initial_load_hooks;
+RefPtr<Web::DOM::Document> g_current_interpreter_document;
TESTJS_MAIN_HOOK()
{
@@ -38,7 +41,51 @@ TESTJS_MAIN_HOOK()
g_page_view = view;
}
-TESTJS_GLOBAL_FUNCTION(load_local_page, loadLocalPage)
+struct TestWebGlobalObject : public Web::Bindings::WindowObject {
+ JS_OBJECT(TestWebGlobalObject, Web::Bindings::WindowObject);
+
+public:
+ TestWebGlobalObject(Web::DOM::Window& window)
+ : Web::Bindings::WindowObject(window)
+ {
+ }
+
+ virtual ~TestWebGlobalObject() override = default;
+
+ virtual void initialize_global_object() override;
+
+ JS_DECLARE_NATIVE_FUNCTION(load_local_page);
+ JS_DECLARE_NATIVE_FUNCTION(after_initial_page_load);
+ JS_DECLARE_NATIVE_FUNCTION(before_initial_page_load);
+ JS_DECLARE_NATIVE_FUNCTION(wait_for_page_to_load);
+};
+
+void TestWebGlobalObject::initialize_global_object()
+{
+ Base::initialize_global_object();
+
+ define_native_function("loadLocalPage", load_local_page, 1, JS::default_attributes);
+ define_native_function("afterInitialPageLoad", after_initial_page_load, 1, JS::default_attributes);
+ define_native_function("beforeInitialPageLoad", before_initial_page_load, 1, JS::default_attributes);
+ define_native_function("waitForPageToLoad", wait_for_page_to_load, 0, JS::default_attributes);
+}
+
+TESTJS_CREATE_INTERPRETER_HOOK()
+{
+ // FIXME: This is a hack as the document we create needs to stay alive the entire time and we don't have insight into JavaScriptTestRUnner from here to work out the lifetime from here.
+ g_current_interpreter_document = Web::DOM::Document::create();
+
+ // FIXME: Use WindowProxy as the globalThis value.
+ auto interpreter = JS::Interpreter::create<TestWebGlobalObject>(*g_vm, g_current_interpreter_document->window());
+
+ // FIXME: Work out the creation URL.
+ AK::URL creation_url;
+
+ Web::HTML::WindowEnvironmentSettingsObject::setup(creation_url, g_vm->running_execution_context());
+ return interpreter;
+}
+
+JS_DEFINE_NATIVE_FUNCTION(TestWebGlobalObject::load_local_page)
{
auto name = TRY(vm.argument(0).to_string(global_object));
@@ -54,7 +101,7 @@ TESTJS_GLOBAL_FUNCTION(load_local_page, loadLocalPage)
return JS::js_undefined();
}
-TESTJS_GLOBAL_FUNCTION(after_initial_page_load, afterInitialPageLoad)
+JS_DEFINE_NATIVE_FUNCTION(TestWebGlobalObject::after_initial_page_load)
{
auto function = vm.argument(0);
if (!function.is_function()) {
@@ -69,7 +116,7 @@ TESTJS_GLOBAL_FUNCTION(after_initial_page_load, afterInitialPageLoad)
return JS::js_undefined();
}
-TESTJS_GLOBAL_FUNCTION(before_initial_page_load, beforeInitialPageLoad)
+JS_DEFINE_NATIVE_FUNCTION(TestWebGlobalObject::before_initial_page_load)
{
auto function = vm.argument(0);
if (!function.is_function()) {
@@ -84,7 +131,7 @@ TESTJS_GLOBAL_FUNCTION(before_initial_page_load, beforeInitialPageLoad)
return JS::js_undefined();
}
-TESTJS_GLOBAL_FUNCTION(wait_for_page_to_load, waitForPageToLoad)
+JS_DEFINE_NATIVE_FUNCTION(TestWebGlobalObject::wait_for_page_to_load)
{
// Create a new parser and immediately get its document to replace the old interpreter.
auto document = Web::DOM::Document::create();
diff --git a/Userland/Libraries/LibJS/Forward.h b/Userland/Libraries/LibJS/Forward.h
index ead0c0ebcb..9a35729af8 100644
--- a/Userland/Libraries/LibJS/Forward.h
+++ b/Userland/Libraries/LibJS/Forward.h
@@ -147,6 +147,7 @@ class ECMAScriptFunctionObject;
class Environment;
class Error;
class ErrorType;
+struct ExecutionContext;
class Expression;
class FunctionEnvironment;
class FunctionNode;
diff --git a/Userland/Libraries/LibJS/Module.h b/Userland/Libraries/LibJS/Module.h
index 6286442283..c846cc8303 100644
--- a/Userland/Libraries/LibJS/Module.h
+++ b/Userland/Libraries/LibJS/Module.h
@@ -9,6 +9,7 @@
#include <AK/FlyString.h>
#include <LibJS/Heap/Handle.h>
+#include <LibJS/Runtime/Environment.h>
#include <LibJS/Runtime/Realm.h>
namespace JS {
diff --git a/Userland/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.h b/Userland/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.h
index e54fe6e944..ebd17ebec5 100644
--- a/Userland/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.h
+++ b/Userland/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.h
@@ -9,6 +9,7 @@
#include <LibJS/AST.h>
#include <LibJS/Bytecode/Generator.h>
+#include <LibJS/Runtime/ExecutionContext.h>
#include <LibJS/Runtime/FunctionObject.h>
namespace JS {
@@ -86,6 +87,10 @@ public:
FunctionKind kind() const { return m_kind; }
+ // This is used by LibWeb to disassociate event handler attribute callback functions from the nearest script on the call stack.
+ // https://html.spec.whatwg.org/multipage/webappapis.html#getting-the-current-value-of-the-event-handler Step 3.11
+ void set_script_or_module(ScriptOrModule script_or_module) { m_script_or_module = move(script_or_module); }
+
protected:
virtual bool is_strict_mode() const final { return m_strict; }
diff --git a/Userland/Libraries/LibJS/Runtime/ExecutionContext.h b/Userland/Libraries/LibJS/Runtime/ExecutionContext.h
index 6cf0c1e7b1..753b6690ab 100644
--- a/Userland/Libraries/LibJS/Runtime/ExecutionContext.h
+++ b/Userland/Libraries/LibJS/Runtime/ExecutionContext.h
@@ -10,6 +10,7 @@
#include <AK/FlyString.h>
#include <AK/WeakPtr.h>
#include <LibJS/Forward.h>
+#include <LibJS/Module.h>
#include <LibJS/Runtime/MarkedValueList.h>
#include <LibJS/Runtime/PrivateEnvironment.h>
#include <LibJS/Runtime/Value.h>
@@ -62,6 +63,10 @@ public:
Value this_value;
MarkedValueList arguments;
bool is_strict_mode { false };
+
+ // https://html.spec.whatwg.org/multipage/webappapis.html#skip-when-determining-incumbent-counter
+ // FIXME: Move this out of LibJS (e.g. by using the CustomData concept), as it's used exclusively by LibWeb.
+ size_t skip_when_determining_incumbent_counter { 0 };
};
}
diff --git a/Userland/Libraries/LibJS/Runtime/Promise.h b/Userland/Libraries/LibJS/Runtime/Promise.h
index 73176d6fb7..ca29dde6d7 100644
--- a/Userland/Libraries/LibJS/Runtime/Promise.h
+++ b/Userland/Libraries/LibJS/Runtime/Promise.h
@@ -45,6 +45,8 @@ public:
Value reject(Value reason);
Value perform_then(Value on_fulfilled, Value on_rejected, Optional<PromiseCapability> result_capability);
+ bool is_handled() const { return m_is_handled; }
+
protected:
virtual void visit_edges(Visitor&) override;
diff --git a/Userland/Libraries/LibJS/Runtime/Reference.h b/Userland/Libraries/LibJS/Runtime/Reference.h
index b022dafc58..ff527d154f 100644
--- a/Userland/Libraries/LibJS/Runtime/Reference.h
+++ b/Userland/Libraries/LibJS/Runtime/Reference.h
@@ -9,7 +9,6 @@
#include <AK/String.h>
#include <LibJS/Runtime/Environment.h>
#include <LibJS/Runtime/EnvironmentCoordinate.h>
-#include <LibJS/Runtime/ExecutionContext.h>
#include <LibJS/Runtime/PropertyKey.h>
#include <LibJS/Runtime/Value.h>
diff --git a/Userland/Libraries/LibTest/JavaScriptTestRunner.h b/Userland/Libraries/LibTest/JavaScriptTestRunner.h
index a83d250d11..459063b8c0 100644
--- a/Userland/Libraries/LibTest/JavaScriptTestRunner.h
+++ b/Userland/Libraries/LibTest/JavaScriptTestRunner.h
@@ -99,6 +99,16 @@
} __testjs_common_run_file {}; \
::Test::JS::IntermediateRunFileResult __TestJS_run_file::hook(__VA_ARGS__)
+#define TESTJS_CREATE_INTERPRETER_HOOK(...) \
+ struct __TestJS_create_interpreter_hook { \
+ __TestJS_create_interpreter_hook() \
+ { \
+ ::Test::JS::g_create_interpreter_hook = hook; \
+ } \
+ static NonnullOwnPtr<JS::Interpreter> hook(); \
+ } __testjs_create_interpreter_hook {}; \
+ NonnullOwnPtr<JS::Interpreter> __TestJS_create_interpreter_hook::hook(__VA_ARGS__)
+
namespace Test::JS {
namespace JS = ::JS;
@@ -128,6 +138,7 @@ extern String g_test_root;
extern int g_test_argc;
extern char** g_test_argv;
extern Function<void()> g_main_hook;
+extern Function<NonnullOwnPtr<JS::Interpreter>()> g_create_interpreter_hook;
extern HashMap<bool*, Tuple<String, String, char>> g_extra_args;
struct ParserError {
diff --git a/Userland/Libraries/LibTest/JavaScriptTestRunnerMain.cpp b/Userland/Libraries/LibTest/JavaScriptTestRunnerMain.cpp
index 0910486bb2..108a8ba21e 100644
--- a/Userland/Libraries/LibTest/JavaScriptTestRunnerMain.cpp
+++ b/Userland/Libraries/LibTest/JavaScriptTestRunnerMain.cpp
@@ -23,6 +23,7 @@ bool g_run_bytecode = false;
String g_currently_running_test;
HashMap<String, FunctionWithLength> s_exposed_global_functions;
Function<void()> g_main_hook;
+Function<NonnullOwnPtr<JS::Interpreter>()> g_create_interpreter_hook;
HashMap<bool*, Tuple<String, String, char>> g_extra_args;
IntermediateRunFileResult (*g_run_file)(const String&, JS::Interpreter&) = nullptr;
String g_test_root;
diff --git a/Userland/Libraries/LibWeb/Bindings/CallbackType.h b/Userland/Libraries/LibWeb/Bindings/CallbackType.h
new file mode 100644
index 0000000000..8b4d0cd538
--- /dev/null
+++ b/Userland/Libraries/LibWeb/Bindings/CallbackType.h
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2021, Luke Wilde <lukew@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+#include <LibJS/Heap/Handle.h>
+#include <LibWeb/Forward.h>
+
+namespace Web::Bindings {
+
+// https://heycam.github.io/webidl/#idl-callback-interface
+struct CallbackType {
+ CallbackType(JS::Handle<JS::Object> callback, HTML::EnvironmentSettingsObject& callback_context)
+ : callback(move(callback))
+ , callback_context(callback_context)
+ {
+ }
+
+ JS::Handle<JS::Object> callback;
+
+ // https://heycam.github.io/webidl/#dfn-callback-context
+ HTML::EnvironmentSettingsObject& callback_context;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/Bindings/WindowObject.cpp b/Userland/Libraries/LibWeb/Bindings/WindowObject.cpp
index 88a20ab02a..254a9a15de 100644
--- a/Userland/Libraries/LibWeb/Bindings/WindowObject.cpp
+++ b/Userland/Libraries/LibWeb/Bindings/WindowObject.cpp
@@ -41,6 +41,7 @@
#include <LibWeb/HTML/BrowsingContext.h>
#include <LibWeb/HTML/EventHandler.h>
#include <LibWeb/HTML/Scripting/ClassicScript.h>
+#include <LibWeb/HTML/Scripting/Environments.h>
#include <LibWeb/Origin.h>
#include <LibWeb/Page/Page.h>
#include <LibWeb/WebAssembly/WebAssemblyObject.h>
@@ -232,7 +233,8 @@ JS_DEFINE_NATIVE_FUNCTION(WindowObject::set_interval)
// using a NativeFunction for the latter is a workaround so that we can reuse the
// DOM::Timer API unaltered (always expects a JS::FunctionObject).
callback = JS::NativeFunction::create(global_object, "", [impl, script_source = move(script_source)](auto&, auto&) mutable {
- auto script = HTML::ClassicScript::create(impl->associated_document().url().to_string(), script_source, impl->associated_document().realm(), AK::URL());
+ auto& settings_object = verify_cast<HTML::EnvironmentSettingsObject>(*impl->associated_document().realm().host_defined());
+ auto script = HTML::ClassicScript::create(impl->associated_document().url().to_string(), script_source, settings_object, AK::URL());
return script->run();
});
}
@@ -265,7 +267,8 @@ JS_DEFINE_NATIVE_FUNCTION(WindowObject::set_timeout)
// using a NativeFunction for the latter is a workaround so that we can reuse the
// DOM::Timer API unaltered (always expects a JS::FunctionObject).
callback = JS::NativeFunction::create(global_object, "", [impl, script_source = move(script_source)](auto&, auto&) mutable {
- auto script = HTML::ClassicScript::create(impl->associated_document().url().to_string(), script_source, impl->associated_document().realm(), AK::URL());
+ auto& settings_object = verify_cast<HTML::EnvironmentSettingsObject>(*impl->associated_document().realm().host_defined());
+ auto script = HTML::ClassicScript::create(impl->associated_document().url().to_string(), script_source, settings_object, AK::URL());
return script->run();
});
}
diff --git a/Userland/Libraries/LibWeb/Bindings/WindowObject.h b/Userland/Libraries/LibWeb/Bindings/WindowObject.h
index c86a89de5e..b89a450dcf 100644
--- a/Userland/Libraries/LibWeb/Bindings/WindowObject.h
+++ b/Userland/Libraries/LibWeb/Bindings/WindowObject.h
@@ -17,7 +17,7 @@
namespace Web {
namespace Bindings {
-class WindowObject final
+class WindowObject
: public JS::GlobalObject
, public Weakable<WindowObject> {
JS_OBJECT(WindowObject, JS::GlobalObject);
diff --git a/Userland/Libraries/LibWeb/CMakeLists.txt b/Userland/Libraries/LibWeb/CMakeLists.txt
index e5c31dea25..dad66cc02d 100644
--- a/Userland/Libraries/LibWeb/CMakeLists.txt
+++ b/Userland/Libraries/LibWeb/CMakeLists.txt
@@ -194,8 +194,10 @@ set(SOURCES
HTML/Parser/ListOfActiveFormattingElements.cpp
HTML/Parser/StackOfOpenElements.cpp
HTML/Scripting/ClassicScript.cpp
+ HTML/Scripting/Environments.cpp
HTML/Scripting/ExceptionReporter.cpp
HTML/Scripting/Script.cpp
+ HTML/Scripting/WindowEnvironmentSettingsObject.cpp
HTML/SyntaxHighlighter/SyntaxHighlighter.cpp
HTML/TagNames.cpp
HTML/TextMetrics.cpp
diff --git a/Userland/Libraries/LibWeb/DOM/Document.cpp b/Userland/Libraries/LibWeb/DOM/Document.cpp
index 3e2421c6fc..e42d46cda9 100644
--- a/Userland/Libraries/LibWeb/DOM/Document.cpp
+++ b/Userland/Libraries/LibWeb/DOM/Document.cpp
@@ -54,6 +54,7 @@
#include <LibWeb/HTML/HTMLTitleElement.h>
#include <LibWeb/HTML/MessageEvent.h>
#include <LibWeb/HTML/Scripting/ExceptionReporter.h>
+#include <LibWeb/HTML/Scripting/WindowEnvironmentSettingsObject.h>
#include <LibWeb/Layout/BlockFormattingContext.h>
#include <LibWeb/Layout/InitialContainingBlock.h>
#include <LibWeb/Layout/TreeBuilder.h>
@@ -618,6 +619,13 @@ Color Document::visited_link_color() const
return page()->palette().visited_link();
}
+// https://html.spec.whatwg.org/multipage/webappapis.html#relevant-settings-object
+HTML::EnvironmentSettingsObject& Document::relevant_settings_object()
+{
+ // Then, the relevant settings object for a platform object o is the environment settings object of the relevant Realm for o.
+ return verify_cast<HTML::EnvironmentSettingsObject>(*realm().host_defined());
+}
+
JS::Realm& Document::realm()
{
return interpreter().realm();
@@ -626,9 +634,47 @@ JS::Realm& Document::realm()
JS::Interpreter& Document::interpreter()
{
if (!m_interpreter) {
+ // FIXME: This is all ad-hoc. It loosely follows steps 6.4 to 6.9 of https://html.spec.whatwg.org/#initialise-the-document-object
auto& vm = Bindings::main_thread_vm();
+
+ // https://html.spec.whatwg.org/multipage/webappapis.html#creating-a-new-javascript-realm
+ // FIXME: Put all this into it's own function that can be used outside of Document.
+
+ // 1. Perform InitializeHostDefinedRealm() with the provided customizations for creating the global object and the global this binding.
+ // FIXME: Use WindowProxy as the global this value.
m_interpreter = JS::Interpreter::create<Bindings::WindowObject>(vm, *m_window);
+ // 2. Let realm execution context be the running JavaScript execution context.
+ auto& realm_execution_context = vm.running_execution_context();
+
+ // 3. Remove realm execution context from the JavaScript execution context stack.
+ vm.execution_context_stack().remove_first_matching([&realm_execution_context](auto* execution_context) {
+ return execution_context == &realm_execution_context;
+ });
+
+ // FIXME: 4. Let realm be realm execution context's Realm component.
+ // FIXME: 5. Set realm's agent to agent.
+
+ // FIXME: 6. If agent's agent cluster's cross-origin isolation mode is "none", then:
+ // 1. Let global be realm's global object.
+ // 2. Let status be ! global.[[Delete]]("SharedArrayBuffer").
+ // 3. Assert: status is true.
+
+ // FIXME: 7. Return realm execution context. (Requires being in it's own function as mentioned above)
+
+ // == End of "create a JavaScript realm" ==
+
+ // FIXME: 6. Let topLevelCreationURL be creationURL.
+ // FIXME: 7. Let topLevelOrigin be navigationParams's origin.
+ // FIXME: 8. If browsingContext is not a top-level browsing context, then:
+ // 1. Let parentEnvironment be browsingContext's container's relevant settings object.
+ // 2. Set topLevelCreationURL to parentEnvironment's top-level creation URL.
+ // 3. Set topLevelOrigin to parentEnvironment's top-level origin.
+
+ // FIXME: 9. Set up a window environment settings object with creationURL, realm execution context, navigationParams's reserved environment, topLevelCreationURL, and topLevelOrigin.
+ // (This is missing reserved environment, topLevelCreationURL and topLevelOrigin. It also assumes creationURL is the document's URL, when it's really "navigationParams's response's URL.")
+ HTML::WindowEnvironmentSettingsObject::setup(m_url, realm_execution_context);
+
// NOTE: We must hook `on_call_stack_emptied` after the interpreter was created, as the initialization of the
// WindowsObject can invoke some internal calls, which will eventually lead to this hook being called without
// `m_interpreter` being fully initialized yet.
diff --git a/Userland/Libraries/LibWeb/DOM/Document.h b/Userland/Libraries/LibWeb/DOM/Document.h
index 95d70bc1be..8a3740bdde 100644
--- a/Userland/Libraries/LibWeb/DOM/Document.h
+++ b/Userland/Libraries/LibWeb/DOM/Document.h
@@ -28,6 +28,7 @@
#include <LibWeb/HTML/DocumentReadyState.h>
#include <LibWeb/HTML/HTMLScriptElement.h>
#include <LibWeb/HTML/History.h>
+#include <LibWeb/HTML/Scripting/Environments.h>
namespace Web::DOM {
@@ -171,7 +172,8 @@ public:
const String& source() const { return m_source; }
void set_source(const String& source) { m_source = source; }
- virtual JS::Realm& realm() override;
+ HTML::EnvironmentSettingsObject& relevant_settings_object();
+ JS::Realm& realm();
JS::Interpreter& interpreter();
JS::Value run_javascript(StringView source, StringView filename = "(unknown)");
diff --git a/Userland/Libraries/LibWeb/DOM/Window.cpp b/Userland/Libraries/LibWeb/DOM/Window.cpp
index 072e77560b..389088a634 100644
--- a/Userland/Libraries/LibWeb/DOM/Window.cpp
+++ b/Userland/Libraries/LibWeb/DOM/Window.cpp
@@ -168,9 +168,10 @@ void Window::timer_did_fire(Badge<Timer>, Timer& timer)
m_timers.remove(timer.id());
}
- HTML::queue_global_task(HTML::Task::Source::TimerTask, associated_document(), [this, strong_this = NonnullRefPtr(*this), strong_timer = NonnullRefPtr(timer)]() mutable {
- // We should not be here if there's no JS wrapper for the Window object.
- VERIFY(wrapper());
+ // We should not be here if there's no JS wrapper for the Window object.
+ VERIFY(wrapper());
+
+ HTML::queue_global_task(HTML::Task::Source::TimerTask, *wrapper(), [this, strong_this = NonnullRefPtr(*this), strong_timer = NonnullRefPtr(timer)]() mutable {
auto result = JS::call(wrapper()->global_object(), strong_timer->callback(), wrapper());
if (result.is_error())
HTML::report_exception(result);
@@ -400,8 +401,7 @@ void Window::fire_a_page_transition_event(FlyString const& event_name, bool pers
// https://html.spec.whatwg.org/#dom-queuemicrotask
void Window::queue_microtask(JS::FunctionObject& callback)
{
- // The queueMicrotask(callback) method must queue a microtask to invoke callback,
- HTML::queue_a_microtask(associated_document(), [&callback, handle = JS::make_handle(&callback)]() {
+ HTML::queue_a_microtask(&associated_document(), [&callback, handle = JS::make_handle(&callback)]() {
auto result = JS::call(callback.global_object(), callback, JS::js_null());
// and if callback throws an exception, report the exception.
if (result.is_error())
diff --git a/Userland/Libraries/LibWeb/Forward.h b/Userland/Libraries/LibWeb/Forward.h
index 1c188c7be3..39d50aa604 100644
--- a/Userland/Libraries/LibWeb/Forward.h
+++ b/Userland/Libraries/LibWeb/Forward.h
@@ -125,9 +125,12 @@ namespace Web::HTML {
class BrowsingContext;
class BrowsingContextContainer;
class CanvasRenderingContext2D;
+class ClassicScript;
class CloseEvent;
class DOMParser;
class DOMStringMap;
+struct Environment;
+struct EnvironmentSettingsObject;
class ErrorEvent;
struct EventHandler;
class EventLoop;
@@ -213,6 +216,7 @@ class PromiseRejectionEvent;
class SubmitEvent;
class TextMetrics;
class WebSocket;
+class WindowEnvironmentSettingsObject;
}
namespace Web::HighResolutionTime {
diff --git a/Userland/Libraries/LibWeb/HTML/EventLoop/EventLoop.cpp b/Userland/Libraries/LibWeb/HTML/EventLoop/EventLoop.cpp
index 4bdd50c33b..b4d8e57c78 100644
--- a/Userland/Libraries/LibWeb/HTML/EventLoop/EventLoop.cpp
+++ b/Userland/Libraries/LibWeb/HTML/EventLoop/EventLoop.cpp
@@ -12,6 +12,7 @@
#include <LibWeb/DOM/Window.h>
#include <LibWeb/HTML/BrowsingContext.h>
#include <LibWeb/HTML/EventLoop/EventLoop.h>
+#include <LibWeb/HTML/Scripting/Environments.h>
#include <LibWeb/HighResolutionTime/Performance.h>
namespace Web::HTML {
@@ -207,15 +208,33 @@ void EventLoop::process()
schedule();
}
-// https://html.spec.whatwg.org/multipage/webappapis.html#queue-a-global-task
-void queue_global_task(HTML::Task::Source source, DOM::Document& document, Function<void()> steps)
+// FIXME: This is here to paper over an issue in the HTML parser where it'll create new interpreters (and thus ESOs) on temporary documents created for innerHTML if it uses Document::realm() to get the global object.
+// Use queue_global_task instead.
+void old_queue_global_task_with_document(HTML::Task::Source source, DOM::Document& document, Function<void()> steps)
{
- // FIXME: This should take a global object as input and find the relevant document for it.
main_thread_event_loop().task_queue().add(HTML::Task::create(source, &document, move(steps)));
}
+// https://html.spec.whatwg.org/multipage/webappapis.html#queue-a-global-task
+void queue_global_task(HTML::Task::Source source, JS::GlobalObject& global_object, Function<void()> steps)
+{
+ // 1. Let event loop be global's relevant agent's event loop.
+ auto& global_custom_data = verify_cast<Bindings::WebEngineCustomData>(*global_object.vm().custom_data());
+ auto& event_loop = global_custom_data.event_loop;
+
+ // 2. Let document be global's associated Document, if global is a Window object; otherwise null.
+ DOM::Document* document { nullptr };
+ if (is<Bindings::WindowObject>(global_object)) {
+ auto& window_object = verify_cast<Bindings::WindowObject>(global_object);
+ document = &window_object.impl().associated_document();
+ }
+
+ // 3. Queue a task given source, event loop, document, and steps.
+ event_loop.task_queue().add(HTML::Task::create(source, document, move(steps)));
+}
+
// https://html.spec.whatwg.org/#queue-a-microtask
-void queue_a_microtask(DOM::Document& document, Function<void()> steps)
+void queue_a_microtask(DOM::Document* document, Function<void()> steps)
{
// 1. If event loop was not given, set event loop to the implied event loop.
auto& event_loop = HTML::main_thread_event_loop();
@@ -226,7 +245,7 @@ void queue_a_microtask(DOM::Document& document, Function<void()> steps)
// 4. Set microtask's steps to steps.
// 5. Set microtask's source to the microtask task source.
// 6. Set microtask's document to document.
- auto microtask = HTML::Task::create(HTML::Task::Source::Microtask, &document, move(steps));
+ auto microtask = HTML::Task::create(HTML::Task::Source::Microtask, document, move(steps));
// FIXME: 7. Set microtask's script evaluation environment settings object set to an empty set.
@@ -259,7 +278,9 @@ void EventLoop::perform_a_microtask_checkpoint()
m_currently_running_task = nullptr;
}
- // FIXME: 4. For each environment settings object whose responsible event loop is this event loop, notify about rejected promises on that environment settings object.
+ // 4. For each environment settings object whose responsible event loop is this event loop, notify about rejected promises on that environment settings object.
+ for (auto& environment_settings_object : m_related_environment_settings_objects)
+ environment_settings_object.notify_about_rejected_promises({});
// FIXME: 5. Cleanup Indexed Database transactions.
@@ -290,4 +311,30 @@ void EventLoop::unregister_document(Badge<DOM::Document>, DOM::Document& documen
VERIFY(did_remove);
}
+void EventLoop::push_onto_backup_incumbent_settings_object_stack(Badge<EnvironmentSettingsObject>, EnvironmentSettingsObject& environment_settings_object)
+{
+ m_backup_incumbent_settings_object_stack.append(environment_settings_object);
+}
+
+void EventLoop::pop_backup_incumbent_settings_object_stack(Badge<EnvironmentSettingsObject>)
+{
+ m_backup_incumbent_settings_object_stack.take_last();
+}
+
+EnvironmentSettingsObject& EventLoop::top_of_backup_incumbent_settings_object_stack()
+{
+ return m_backup_incumbent_settings_object_stack.last();
+}
+
+void EventLoop::register_environment_settings_object(Badge<EnvironmentSettingsObject>, EnvironmentSettingsObject& environment_settings_object)
+{
+ m_related_environment_settings_objects.append(environment_settings_object);
+}
+
+void EventLoop::unregister_environment_settings_object(Badge<EnvironmentSettingsObject>, EnvironmentSettingsObject& environment_settings_object)
+{
+ bool did_remove = m_related_environment_settings_objects.remove_first_matching([&](auto& entry) { return &entry == &environment_settings_object; });
+ VERIFY(did_remove);
+}
+
}
diff --git a/Userland/Libraries/LibWeb/HTML/EventLoop/EventLoop.h b/Userland/Libraries/LibWeb/HTML/EventLoop/EventLoop.h
index b3ebdd5e07..658ff67dae 100644
--- a/Userland/Libraries/LibWeb/HTML/EventLoop/EventLoop.h
+++ b/Userland/Libraries/LibWeb/HTML/EventLoop/EventLoop.h
@@ -55,6 +55,14 @@ public:
NonnullRefPtrVector<DOM::Document> documents_in_this_event_loop() const;
+ void push_onto_backup_incumbent_settings_object_stack(Badge<EnvironmentSettingsObject>, EnvironmentSettingsObject& environment_settings_object);
+ void pop_backup_incumbent_settings_object_stack(Badge<EnvironmentSettingsObject>);
+ EnvironmentSettingsObject& top_of_backup_incumbent_settings_object_stack();
+ bool is_backup_incumbent_settings_object_stack_empty() const { return m_backup_incumbent_settings_object_stack.is_empty(); }
+
+ void register_environment_settings_object(Badge<EnvironmentSettingsObject>, EnvironmentSettingsObject&);
+ void unregister_environment_settings_object(Badge<EnvironmentSettingsObject>, EnvironmentSettingsObject&);
+
private:
Type m_type { Type::Window };
@@ -72,11 +80,18 @@ private:
bool m_performing_a_microtask_checkpoint { false };
Vector<WeakPtr<DOM::Document>> m_documents;
+
+ // Used to implement step 4 of "perform a microtask checkpoint".
+ Vector<EnvironmentSettingsObject&> m_related_environment_settings_objects;
+
+ // https://html.spec.whatwg.org/multipage/webappapis.html#backup-incumbent-settings-object-stack
+ Vector<EnvironmentSettingsObject&> m_backup_incumbent_settings_object_stack;
};
EventLoop& main_thread_event_loop();
-void queue_global_task(HTML::Task::Source, DOM::Document&, Function<void()> steps);
-void queue_a_microtask(DOM::Document&, Function<void()> steps);
+void old_queue_global_task_with_document(HTML::Task::Source, DOM::Document&, Function<void()> steps);
+void queue_global_task(HTML::Task::Source, JS::GlobalObject&, Function<void()> steps);
+void queue_a_microtask(DOM::Document*, Function<void()> steps);
void perform_a_microtask_checkpoint();
}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLScriptElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLScriptElement.cpp
index 65caedc22c..6ecae73b9f 100644
--- a/Userland/Libraries/LibWeb/HTML/HTMLScriptElement.cpp
+++ b/Userland/Libraries/LibWeb/HTML/HTMLScriptElement.cpp
@@ -85,7 +85,7 @@ void HTMLScriptElement::execute_script()
dbgln_if(HTML_SCRIPT_DEBUG, "HTMLScriptElement: Running inline script");
// 3. Run the classic script given by the script's script for scriptElement.
- verify_cast<ClassicScript>(*m_script).run();
+ (void)verify_cast<ClassicScript>(*m_script).run();
// 4. Set document's currentScript attribute to oldCurrentScript.
node_document->set_current_script({}, old_current_script);
@@ -259,7 +259,8 @@ void HTMLScriptElement::prepare_script()
// FIXME: 24. Let options be a script fetch options whose cryptographic nonce is cryptographic nonce, integrity metadata is integrity metadata, parser metadata is parser metadata,
// credentials mode is module script credentials mode, and referrer policy is referrer policy.
- // FIXME: 25. Let settings object be the element's node document's relevant settings object.
+ // 25. Let settings object be the element's node document's relevant settings object.
+ // NOTE: This will be done manually when this is required, as two of the use cases are inside lambdas that get executed later and thus a reference cannot be taken.
// 26. If the element has a src content attribute, then:
if (has_attribute(HTML::AttributeNames::src)) {
@@ -302,7 +303,7 @@ void HTMLScriptElement::prepare_script()
}
// FIXME: This is all ad-hoc and needs work.
- auto script = ClassicScript::create(url.to_string(), data, document().realm(), AK::URL());
+ auto script = ClassicScript::create(url.to_string(), data, document().relevant_settings_object(), AK::URL());
// When the chosen algorithm asynchronously completes, set the script's script to the result. At that time, the script is ready.
m_script = script;
@@ -328,7 +329,7 @@ void HTMLScriptElement::prepare_script()
// 1. Let script be the result of creating a classic script using source text, settings object, base URL, and options.
// FIXME: Pass settings, base URL and options.
- auto script = ClassicScript::create(m_document->url().to_string(), source_text, document().realm(), AK::URL());
+ auto script = ClassicScript::create(m_document->url().to_string(), source_text, document().relevant_settings_object(), AK::URL());
// 2. Set the script's script to script.
m_script = script;
diff --git a/Userland/Libraries/LibWeb/HTML/Parser/HTMLParser.cpp b/Userland/Libraries/LibWeb/HTML/Parser/HTMLParser.cpp
index 8797cb1588..e6f95179d9 100644
--- a/Userland/Libraries/LibWeb/HTML/Parser/HTMLParser.cpp
+++ b/Userland/Libraries/LibWeb/HTML/Parser/HTMLParser.cpp
@@ -222,7 +222,7 @@ void HTMLParser::the_end()
}
// 6. Queue a global task on the DOM manipulation task source given the Document's relevant global object to run the following substeps:
- queue_global_task(HTML::Task::Source::DOMManipulation, *m_document, [document = m_document]() mutable {
+ old_queue_global_task_with_document(HTML::Task::Source::DOMManipulation, m_document, [document = m_document]() mutable {
// FIXME: 1. Set the Document's load timing info's DOM content loaded event start time to the current high resolution time given the Document's relevant global object.
// 2. Fire an event named DOMContentLoaded at the Document object, with its bubbles attribute initialized to true.
@@ -249,7 +249,7 @@ void HTMLParser::the_end()
});
// 9. Queue a global task on the DOM manipulation task source given the Document's relevant global object to run the following steps:
- queue_global_task(HTML::Task::Source::DOMManipulation, *m_document, [document = m_document]() mutable {
+ old_queue_global_task_with_document(HTML::Task::Source::DOMManipulation, m_document, [document = m_document]() mutable {
// 1. Update the current document readiness to "complete".
document->update_readiness(HTML::DocumentReadyState::Complete);
diff --git a/Userland/Libraries/LibWeb/HTML/Scripting/ClassicScript.cpp b/Userland/Libraries/LibWeb/HTML/Scripting/ClassicScript.cpp
index 4d43864e83..8749048c74 100644
--- a/Userland/Libraries/LibWeb/HTML/Scripting/ClassicScript.cpp
+++ b/Userland/Libraries/LibWeb/HTML/Scripting/ClassicScript.cpp
@@ -7,13 +7,16 @@
#include <AK/Debug.h>
#include <LibCore/ElapsedTimer.h>
#include <LibJS/Interpreter.h>
+#include <LibWeb/Bindings/ExceptionOrUtils.h>
+#include <LibWeb/DOM/DOMException.h>
#include <LibWeb/HTML/Scripting/ClassicScript.h>
+#include <LibWeb/HTML/Scripting/Environments.h>
#include <LibWeb/HTML/Scripting/ExceptionReporter.h>
namespace Web::HTML {
// https://html.spec.whatwg.org/multipage/webappapis.html#creating-a-classic-script
-NonnullRefPtr<ClassicScript> ClassicScript::create(String filename, StringView source, JS::Realm& realm, AK::URL base_url, MutedErrors muted_errors)
+NonnullRefPtr<ClassicScript> ClassicScript::create(String filename, StringView source, EnvironmentSettingsObject& environment_settings_object, AK::URL base_url, MutedErrors muted_errors)
{
// 1. If muted errors was not provided, let it be false. (NOTE: This is taken care of by the default argument.)
@@ -24,9 +27,9 @@ NonnullRefPtr<ClassicScript> ClassicScript::create(String filename, StringView s
// FIXME: 3. If scripting is disabled for settings, then set source to the empty string.
// 4. Let script be a new classic script that this algorithm will subsequently initialize.
- auto script = adopt_ref(*new ClassicScript(move(base_url), move(filename)));
+ auto script = adopt_ref(*new ClassicScript(move(base_url), move(filename), environment_settings_object));
- // FIXME: 5. Set script's settings object to settings.
+ // 5. Set script's settings object to settings. (NOTE: This was already done when constructing.)
// 6. Set script's base URL to baseURL. (NOTE: This was already done when constructing.)
@@ -36,16 +39,21 @@ NonnullRefPtr<ClassicScript> ClassicScript::create(String filename, StringView s
script->m_muted_errors = muted_errors;
// FIXME: 9. Set script's parse error and error to rethrow to null.
+ // NOTE: Error to rethrow was set to null in the construction of ClassicScript. We do not have parse error as it would currently go unused.
// 10. Let result be ParseScript(source, settings's Realm, script).
auto parse_timer = Core::ElapsedTimer::start_new();
- auto result = JS::Script::parse(source, realm, script->filename());
+ auto result = JS::Script::parse(source, environment_settings_object.realm(), script->filename());
dbgln_if(HTML_SCRIPT_DEBUG, "ClassicScript: Parsed {} in {}ms", script->filename(), parse_timer.elapsed());
// 11. If result is a list of errors, then:
-
if (result.is_error()) {
- // FIXME: 1. Set script's parse error and its error to rethrow to result[0].
+ auto& parse_error = result.error().first();
+ dbgln_if(HTML_SCRIPT_DEBUG, "ClassicScript: Failed to parse: {}", parse_error.to_string());
+
+ // FIXME: 1. Set script's parse error and its error to rethrow to result[0].
+ // We do not have parse error as it would currently go unused.
+ script->m_error_to_rethrow = parse_error;
// 2. Return script.
return script;
@@ -59,37 +67,88 @@ NonnullRefPtr<ClassicScript> ClassicScript::create(String filename, StringView s
}
// https://html.spec.whatwg.org/multipage/webappapis.html#run-a-classic-script
-JS::Value ClassicScript::run(RethrowErrors rethrow_errors)
+JS::Completion ClassicScript::run(RethrowErrors rethrow_errors)
{
- if (!m_script_record) {
- // FIXME: Throw a SyntaxError per the spec.
- dbgln("ClassicScript: Unable to run script {}", filename());
- return {};
+ auto& global_object = m_settings_object.global_object();
+ auto& vm = global_object.vm();
+
+ // 1. Let settings be the settings object of script. (NOTE: Not necessary)
+
+ // 2. Check if we can run script with settings. If this returns "do not run" then return NormalCompletion(empty).
+ if (m_settings_object.can_run_script() == RunScriptDecision::DoNotRun)
+ return JS::normal_completion({});
+
+ // 3. Prepare to run script given settings.
+ m_settings_object.prepare_to_run_script();
+
+ // 4. Let evaluationStatus be null.
+ JS::Completion evaluation_status;
+
+ // 5. If script's error to rethrow is not null, then set evaluationStatus to Completion { [[Type]]: throw, [[Value]]: script's error to rethrow, [[Target]]: empty }.
+ if (m_error_to_rethrow.has_value()) {
+ evaluation_status = vm.throw_completion<JS::SyntaxError>(global_object, m_error_to_rethrow.value().to_string());
+ } else {
+ auto timer = Core::ElapsedTimer::start_new();
+
+ // 6. Otherwise, set evaluationStatus to ScriptEvaluation(script's record).
+ auto interpreter = JS::Interpreter::create_with_existing_realm(m_script_record->realm());
+
+ evaluation_status = interpreter->run(*m_script_record);
+
+ // FIXME: If ScriptEvaluation does not complete because the user agent has aborted the running script, leave evaluationStatus as null.
+
+ dbgln_if(HTML_SCRIPT_DEBUG, "ClassicScript: Finished running script {}, Duration: {}ms", filename(), timer.elapsed());
}
- dbgln_if(HTML_SCRIPT_DEBUG, "ClassicScript: Running script {}", filename());
- (void)rethrow_errors;
+ // 7. If evaluationStatus is an abrupt completion, then:
+ if (evaluation_status.is_abrupt()) {
+ // 1. If rethrow errors is true and script's muted errors is false, then:
+ if (rethrow_errors == RethrowErrors::Yes && m_muted_errors == MutedErrors::No) {
+ // 1. Clean up after running script with settings.
+ m_settings_object.clean_up_after_running_script();
- auto timer = Core::ElapsedTimer::start_new();
+ // 2. Rethrow evaluationStatus.[[Value]].
+ return JS::throw_completion(*evaluation_status.value());
+ }
- // 6. Otherwise, set evaluationStatus to ScriptEvaluation(script's record).
- auto interpreter = JS::Interpreter::create_with_existing_realm(m_script_record->realm());
+ // 2. If rethrow errors is true and script's muted errors is true, then:
+ if (rethrow_errors == RethrowErrors::Yes && m_muted_errors == MutedErrors::Yes) {
+ // 1. Clean up after running script with settings.
+ m_settings_object.clean_up_after_running_script();
- auto evaluation_status = interpreter->run(*m_script_record);
+ // 2. Throw a "NetworkError" DOMException.
+ return Bindings::throw_dom_exception_if_needed(global_object, [] {
+ return DOM::NetworkError::create("Script error.");
+ }).release_error();
+ }
- // FIXME: If ScriptEvaluation does not complete because the user agent has aborted the running script, leave evaluationStatus as null.
+ // 3. Otherwise, rethrow errors is false. Perform the following steps:
+ VERIFY(rethrow_errors == RethrowErrors::No);
- dbgln_if(HTML_SCRIPT_DEBUG, "ClassicScript: Finished running script {}, Duration: {}ms", filename(), timer.elapsed());
- if (evaluation_status.is_error()) {
- // FIXME: Propagate error according to the spec.
+ // 1. Report the exception given by evaluationStatus.[[Value]] for script.
report_exception(evaluation_status);
- return {};
+
+ // 2. Clean up after running script with settings.
+ m_settings_object.clean_up_after_running_script();
+
+ // 3. Return evaluationStatus.
+ return evaluation_status;
}
- return evaluation_status.value();
+
+ // 8. Clean up after running script with settings.
+ m_settings_object.clean_up_after_running_script();
+
+ // 9. If evaluationStatus is a normal completion, then return evaluationStatus.
+ VERIFY(!evaluation_status.is_abrupt());
+ return evaluation_status;
+
+ // FIXME: 10. If we've reached this point, evaluationStatus was left as null because the script was aborted prematurely during evaluation.
+ // Return Completion { [[Type]]: throw, [[Value]]: a new "QuotaExceededError" DOMException, [[Target]]: empty }.
}
-ClassicScript::ClassicScript(AK::URL base_url, String filename)
+ClassicScript::ClassicScript(AK::URL base_url, String filename, EnvironmentSettingsObject& environment_settings_object)
: Script(move(base_url), move(filename))
+ , m_settings_object(environment_settings_object)
{
}
diff --git a/Userland/Libraries/LibWeb/HTML/Scripting/ClassicScript.h b/Userland/Libraries/LibWeb/HTML/Scripting/ClassicScript.h
index 34af98d50c..80affbec8f 100644
--- a/Userland/Libraries/LibWeb/HTML/Scripting/ClassicScript.h
+++ b/Userland/Libraries/LibWeb/HTML/Scripting/ClassicScript.h
@@ -7,6 +7,7 @@
#pragma once
#include <LibJS/Script.h>
+#include <LibWeb/Forward.h>
#include <LibWeb/HTML/Scripting/Script.h>
namespace Web::HTML {
@@ -20,22 +21,26 @@ public:
No,
Yes,
};
- static NonnullRefPtr<ClassicScript> create(String filename, StringView source, JS::Realm&, AK::URL base_url, MutedErrors = MutedErrors::No);
+ static NonnullRefPtr<ClassicScript> create(String filename, StringView source, EnvironmentSettingsObject&, AK::URL base_url, MutedErrors = MutedErrors::No);
JS::Script* script_record() { return m_script_record; }
JS::Script const* script_record() const { return m_script_record; }
+ EnvironmentSettingsObject& settings_object() { return m_settings_object; }
+
enum class RethrowErrors {
No,
Yes,
};
- JS::Value run(RethrowErrors = RethrowErrors::No);
+ JS::Completion run(RethrowErrors = RethrowErrors::No);
private:
- ClassicScript(AK::URL base_url, String filename);
+ ClassicScript(AK::URL base_url, String filename, EnvironmentSettingsObject& environment_settings_object);
+ EnvironmentSettingsObject& m_settings_object;
RefPtr<JS::Script> m_script_record;
MutedErrors m_muted_errors { MutedErrors::No };
+ Optional<JS::Parser::Error> m_error_to_rethrow;
};
}
diff --git a/Userland/Libraries/LibWeb/HTML/Scripting/Environments.cpp b/Userland/Libraries/LibWeb/HTML/Scripting/Environments.cpp
new file mode 100644
index 0000000000..7f46a11ff6
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/Scripting/Environments.cpp
@@ -0,0 +1,273 @@
+/*
+ * Copyright (c) 2021, Luke Wilde <lukew@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <LibWeb/Bindings/MainThreadVM.h>
+#include <LibWeb/Bindings/WindowObject.h>
+#include <LibWeb/DOM/Document.h>
+#include <LibWeb/DOM/Window.h>
+#include <LibWeb/HTML/PromiseRejectionEvent.h>
+#include <LibWeb/HTML/Scripting/Environments.h>
+
+namespace Web::HTML {
+
+EnvironmentSettingsObject::EnvironmentSettingsObject(JS::ExecutionContext& realm_execution_context)
+ : m_realm_execution_context(realm_execution_context)
+{
+ // Register with the responsible event loop so we can perform step 4 of "perform a microtask checkpoint".
+ responsible_event_loop().register_environment_settings_object({}, *this);
+}
+
+EnvironmentSettingsObject::~EnvironmentSettingsObject()
+{
+ responsible_event_loop().unregister_environment_settings_object({}, *this);
+}
+
+JS::ExecutionContext& EnvironmentSettingsObject::realm_execution_context()
+{
+ // NOTE: All environment settings objects are created with a realm execution context, so it's stored and returned here in the base class.
+ return m_realm_execution_context;
+}
+
+// https://html.spec.whatwg.org/multipage/webappapis.html#environment-settings-object%27s-realm
+JS::Realm& EnvironmentSettingsObject::realm()
+{
+ // An environment settings object's realm execution context's Realm component is the environment settings object's Realm.
+ return *realm_execution_context().realm;
+}
+
+// https://html.spec.whatwg.org/multipage/webappapis.html#concept-settings-object-global
+JS::GlobalObject& EnvironmentSettingsObject::global_object()
+{
+ // An environment settings object's Realm then has a [[GlobalObject]] field, which contains the environment settings object's global object.
+ return realm().global_object();
+}
+
+// https://html.spec.whatwg.org/multipage/webappapis.html#responsible-event-loop
+EventLoop& EnvironmentSettingsObject::responsible_event_loop()
+{
+ // An environment settings object's responsible event loop is its global object's relevant agent's event loop.
+ // This is here in case the realm that is holding onto this ESO is destroyed before the ESO is. The responsible event loop pointer is needed in the ESO destructor to deregister from the event loop.
+ // FIXME: Figure out why the realm can be destroyed before the ESO, as the realm is holding onto this with an OwnPtr, but the heap block deallocator calls the ESO destructor directly instead of through the realm destructor.
+ if (m_responsible_event_loop)
+ return *m_responsible_event_loop;
+
+ auto& vm = global_object().vm();
+ auto& event_loop = verify_cast<Bindings::WebEngineCustomData>(vm.custom_data())->event_loop;
+ m_responsible_event_loop = &event_loop;
+ return event_loop;
+}
+
+// https://html.spec.whatwg.org/multipage/webappapis.html#check-if-we-can-run-script
+RunScriptDecision EnvironmentSettingsObject::can_run_script()
+{
+ // 1. If the global object specified by settings is a Window object whose Document object is not fully active, then return "do not run".
+ if (is<Bindings::WindowObject>(global_object()) && !verify_cast<Bindings::WindowObject>(global_object()).impl().associated_document().is_fully_active())
+ return RunScriptDecision::DoNotRun;
+
+ // FIXME: 2. If scripting is disabled for settings, then return "do not run".
+
+ // 3. Return "run".
+ return RunScriptDecision::Run;
+}
+
+// https://html.spec.whatwg.org/multipage/webappapis.html#prepare-to-run-script
+void EnvironmentSettingsObject::prepare_to_run_script()
+{
+ // 1. Push settings's realm execution context onto the JavaScript execution context stack; it is now the running JavaScript execution context.
+ global_object().vm().push_execution_context(realm_execution_context(), global_object());
+
+ // FIXME: 2. Add settings to the currently running task's script evaluation environment settings object set.
+}
+
+// https://html.spec.whatwg.org/multipage/webappapis.html#clean-up-after-running-script
+void EnvironmentSettingsObject::clean_up_after_running_script()
+{
+ auto& vm = global_object().vm();
+
+ // 1. Assert: settings's realm execution context is the running JavaScript execution context.
+ VERIFY(&realm_execution_context() == &vm.running_execution_context());
+
+ // 2. Remove settings's realm execution context from the JavaScript execution context stack.
+ vm.pop_execution_context();
+
+ // 3. If the JavaScript execution context stack is now empty, perform a microtask checkpoint. (If this runs scripts, these algorithms will be invoked reentrantly.)
+ if (vm.execution_context_stack().is_empty())
+ responsible_event_loop().perform_a_microtask_checkpoint();
+}
+
+static JS::ExecutionContext* top_most_script_having_execution_context(JS::VM& vm)
+{
+ // Here, the topmost script-having execution context is the topmost entry of the JavaScript execution context stack that has a non-null ScriptOrModule component,
+ // or null if there is no such entry in the JavaScript execution context stack.
+ auto execution_context = vm.execution_context_stack().last_matching([&](JS::ExecutionContext* context) {
+ return !context->script_or_module.has<Empty>();
+ });
+
+ if (!execution_context.has_value())
+ return nullptr;
+
+ return execution_context.value();
+}
+
+// https://html.spec.whatwg.org/multipage/webappapis.html#prepare-to-run-a-callback
+void EnvironmentSettingsObject::prepare_to_run_callback()
+{
+ auto& vm = global_object().vm();
+
+ // 1. Push settings onto the backup incumbent settings object stack.
+ // NOTE: The spec doesn't say which event loop's stack to put this on. However, all the examples of the incumbent settings object use iframes and cross browsing context communication to demonstrate the concept.
+ // This means that it must rely on some global state that can be accessed by all browsing contexts, which is the main thread event loop.
+ HTML::main_thread_event_loop().push_onto_backup_incumbent_settings_object_stack({}, *this);
+
+ // 2. Let context be the topmost script-having execution context.
+ auto* context = top_most_script_having_execution_context(vm);
+
+ // 3. If context is not null, increment context's skip-when-determining-incumbent counter.
+ if (context)
+ context->skip_when_determining_incumbent_counter++;
+}
+
+// https://html.spec.whatwg.org/multipage/webappapis.html#clean-up-after-running-a-callback
+void EnvironmentSettingsObject::clean_up_after_running_callback()
+{
+ auto& vm = global_object().vm();
+
+ // 1. Let context be the topmost script-having execution context.
+ auto* context = top_most_script_having_execution_context(vm);
+
+ // 2. If context is not null, decrement context's skip-when-determining-incumbent counter.
+ if (context)
+ context->skip_when_determining_incumbent_counter--;
+
+ // 3. Assert: the topmost entry of the backup incumbent settings object stack is settings.
+ auto& event_loop = HTML::main_thread_event_loop();
+ VERIFY(&event_loop.top_of_backup_incumbent_settings_object_stack() == this);
+
+ // 4. Remove settings from the backup incumbent settings object stack.
+ event_loop.pop_backup_incumbent_settings_object_stack({});
+}
+
+void EnvironmentSettingsObject::push_onto_outstanding_rejected_promises_weak_set(JS::Promise* promise)
+{
+ m_outstanding_rejected_promises_weak_set.append(promise);
+}
+
+bool EnvironmentSettingsObject::remove_from_outstanding_rejected_promises_weak_set(JS::Promise* promise)
+{
+ return m_outstanding_rejected_promises_weak_set.remove_first_matching([&](JS::Promise* promise_in_set) {
+ return promise == promise_in_set;
+ });
+}
+
+void EnvironmentSettingsObject::push_onto_about_to_be_notified_rejected_promises_list(JS::Handle<JS::Promise> promise)
+{
+ m_about_to_be_notified_rejected_promises_list.append(move(promise));
+}
+
+bool EnvironmentSettingsObject::remove_from_about_to_be_notified_rejected_promises_list(JS::Promise* promise)
+{
+ return m_about_to_be_notified_rejected_promises_list.remove_first_matching([&](JS::Handle<JS::Promise> promise_in_list) {
+ return promise == promise_in_list.cell();
+ });
+}
+
+// https://html.spec.whatwg.org/multipage/webappapis.html#notify-about-rejected-promises
+void EnvironmentSettingsObject::notify_about_rejected_promises(Badge<EventLoop>)
+{
+ // 1. Let list be a copy of settings object's about-to-be-notified rejected promises list.
+ auto list = m_about_to_be_notified_rejected_promises_list;
+
+ // 2. If list is empty, return.
+ if (list.is_empty())
+ return;
+
+ // 3. Clear settings object's about-to-be-notified rejected promises list.
+ m_about_to_be_notified_rejected_promises_list.clear();
+
+ // 4. Let global be settings object's global object.
+ auto& global = global_object();
+
+ // 5. Queue a global task on the DOM manipulation task source given global to run the following substep:
+ queue_global_task(Task::Source::DOMManipulation, global, [this, global = JS::make_handle(&global), list = move(list)]() mutable {
+ // 1. For each promise p in list:
+ for (auto promise_handle : list) {
+ auto& promise = *promise_handle.cell();
+
+ // 1. If p's [[PromiseIsHandled]] internal slot is true, continue to the next iteration of the loop.
+ if (promise.is_handled())
+ continue;
+
+ // 2. Let notHandled be the result of firing an event named unhandledrejection at global, using PromiseRejectionEvent, with the cancelable attribute initialized to true,
+ // the promise attribute initialized to p, and the reason attribute initialized to the value of p's [[PromiseResult]] internal slot.
+ PromiseRejectionEventInit event_init {
+ {
+ .bubbles = false,
+ .cancelable = true,
+ .composed = false,
+ },
+ // Sadly we can't use .promise and .reason here, as we can't use the designator on the initialization of DOM::EventInit above.
+ /* .promise = */ promise_handle,
+ /* .reason = */ promise.result(),
+ };
+ auto promise_rejection_event = PromiseRejectionEvent::create(HTML::EventNames::unhandledrejection, event_init);
+
+ // FIXME: This currently assumes that global is a WindowObject.
+ auto& window = verify_cast<Bindings::WindowObject>(*global.cell());
+
+ bool not_handled = window.impl().dispatch_event(move(promise_rejection_event));
+
+ // 3. If notHandled is false, then the promise rejection is handled. Otherwise, the promise rejection is not handled.
+
+ // 4. If p's [[PromiseIsHandled]] internal slot is false, add p to settings object's outstanding rejected promises weak set.
+ if (!promise.is_handled())
+ m_outstanding_rejected_promises_weak_set.append(&promise);
+
+ // This algorithm results in promise rejections being marked as handled or not handled. These concepts parallel handled and not handled script errors.
+ // If a rejection is still not handled after this, then the rejection may be reported to a developer console.
+ if (not_handled)
+ dbgln("WARNING: A promise was rejected without any handlers. promise={:p}, result={}", &promise, promise.result().to_string_without_side_effects());
+ }
+ });
+}
+
+// https://html.spec.whatwg.org/multipage/webappapis.html#incumbent-settings-object
+EnvironmentSettingsObject& incumbent_settings_object()
+{
+ auto& event_loop = HTML::main_thread_event_loop();
+ auto& vm = event_loop.vm();
+
+ // 1. Let context be the topmost script-having execution context.
+ auto* context = top_most_script_having_execution_context(vm);
+
+ // 2. If context is null, or if context's skip-when-determining-incumbent counter is greater than zero, then:
+ if (!context || context->skip_when_determining_incumbent_counter > 0) {
+ // 1. Assert: the backup incumbent settings object stack is not empty.
+ // NOTE: If this assertion fails, it's because the incumbent settings object was used with no involvement of JavaScript.
+ VERIFY(!event_loop.is_backup_incumbent_settings_object_stack_empty());
+
+ // 2. Return the topmost entry of the backup incumbent settings object stack.
+ return event_loop.top_of_backup_incumbent_settings_object_stack();
+ }
+
+ // 3. Return context's Realm component's settings object.
+ return verify_cast<EnvironmentSettingsObject>(*context->realm->host_defined());
+}
+
+// https://html.spec.whatwg.org/multipage/webappapis.html#concept-incumbent-realm
+JS::Realm& incumbent_realm()
+{
+ // Then, the incumbent Realm is the Realm of the incumbent settings object.
+ return incumbent_settings_object().realm();
+}
+
+// https://html.spec.whatwg.org/multipage/webappapis.html#concept-incumbent-global
+JS::GlobalObject& incumbent_global_object()
+{
+ // Similarly, the incumbent global object is the global object of the incumbent settings object.
+ return incumbent_settings_object().global_object();
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/Scripting/Environments.h b/Userland/Libraries/LibWeb/HTML/Scripting/Environments.h
new file mode 100644
index 0000000000..a006b80257
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/Scripting/Environments.h
@@ -0,0 +1,121 @@
+/*
+ * Copyright (c) 2021, Luke Wilde <lukew@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+#include <AK/URL.h>
+#include <LibJS/Runtime/ExecutionContext.h>
+#include <LibJS/Runtime/GlobalObject.h>
+#include <LibJS/Runtime/Realm.h>
+#include <LibWeb/HTML/BrowsingContext.h>
+#include <LibWeb/HTML/EventLoop/EventLoop.h>
+#include <LibWeb/Origin.h>
+
+namespace Web::HTML {
+
+// https://html.spec.whatwg.org/multipage/webappapis.html#environment
+struct Environment {
+ // FIXME: An id https://html.spec.whatwg.org/multipage/webappapis.html#concept-environment-id
+
+ // https://html.spec.whatwg.org/multipage/webappapis.html#concept-environment-creation-url
+ AK::URL creation_url;
+
+ // https://html.spec.whatwg.org/multipage/webappapis.html#concept-environment-top-level-creation-url
+ AK::URL top_level_creation_url;
+
+ // https://html.spec.whatwg.org/multipage/webappapis.html#concept-environment-top-level-origin
+ Origin top_level_origin;
+
+ // https://html.spec.whatwg.org/multipage/webappapis.html#concept-environment-target-browsing-context
+ RefPtr<BrowsingContext> target_browsing_context;
+
+ // FIXME: An active service worker https://html.spec.whatwg.org/multipage/webappapis.html#concept-environment-active-service-worker
+
+ // https://html.spec.whatwg.org/multipage/webappapis.html#concept-environment-execution-ready-flag
+ bool execution_ready { false };
+};
+
+enum class CanUseCrossOriginIsolatedAPIs {
+ No,
+ Yes,
+};
+
+enum class RunScriptDecision {
+ Run,
+ DoNotRun,
+};
+
+// https://html.spec.whatwg.org/multipage/webappapis.html#environment-settings-object
+struct EnvironmentSettingsObject
+ : public Environment
+ , public JS::Realm::HostDefined {
+ virtual ~EnvironmentSettingsObject() override;
+
+ // https://html.spec.whatwg.org/multipage/webappapis.html#concept-environment-target-browsing-context
+ JS::ExecutionContext& realm_execution_context();
+
+ // FIXME: A module map https://html.spec.whatwg.org/multipage/webappapis.html#concept-settings-object-module-map
+
+ // https://html.spec.whatwg.org/multipage/webappapis.html#responsible-document
+ virtual RefPtr<DOM::Document> responsible_document() = 0;
+
+ // https://html.spec.whatwg.org/multipage/webappapis.html#api-url-character-encoding
+ virtual String api_url_character_encoding() = 0;
+
+ // https://html.spec.whatwg.org/multipage/webappapis.html#api-base-url
+ virtual AK::URL api_base_url() = 0;
+
+ // https://html.spec.whatwg.org/multipage/webappapis.html#concept-settings-object-origin
+ virtual Origin origin() = 0;
+
+ // FIXME: A policy container https://html.spec.whatwg.org/multipage/webappapis.html#concept-settings-object-policy-container
+
+ // https://html.spec.whatwg.org/multipage/webappapis.html#concept-settings-object-cross-origin-isolated-capability
+ virtual CanUseCrossOriginIsolatedAPIs cross_origin_isolated_capability() = 0;
+
+ JS::Realm& realm();
+ JS::GlobalObject& global_object();
+ EventLoop& responsible_event_loop();
+
+ RunScriptDecision can_run_script();
+ void prepare_to_run_script();
+ void clean_up_after_running_script();
+
+ void prepare_to_run_callback();
+ void clean_up_after_running_callback();
+
+ void push_onto_outstanding_rejected_promises_weak_set(JS::Promise*);
+
+ // Returns true if removed, false otherwise.
+ bool remove_from_outstanding_rejected_promises_weak_set(JS::Promise*);
+
+ void push_onto_about_to_be_notified_rejected_promises_list(JS::Handle<JS::Promise>);
+
+ // Returns true if removed, false otherwise.
+ bool remove_from_about_to_be_notified_rejected_promises_list(JS::Promise*);
+
+ void notify_about_rejected_promises(Badge<EventLoop>);
+
+protected:
+ explicit EnvironmentSettingsObject(JS::ExecutionContext& realm_execution_context);
+
+private:
+ JS::ExecutionContext& m_realm_execution_context;
+ EventLoop* m_responsible_event_loop { nullptr };
+
+ // https://html.spec.whatwg.org/multipage/webappapis.html#outstanding-rejected-promises-weak-set
+ // The outstanding rejected promises weak set must not create strong references to any of its members, and implementations are free to limit its size, e.g. by removing old entries from it when new ones are added.
+ Vector<JS::Promise*> m_outstanding_rejected_promises_weak_set;
+
+ // https://html.spec.whatwg.org/multipage/webappapis.html#about-to-be-notified-rejected-promises-list
+ Vector<JS::Handle<JS::Promise>> m_about_to_be_notified_rejected_promises_list;
+};
+
+EnvironmentSettingsObject& incumbent_settings_object();
+JS::Realm& incumbent_realm();
+JS::GlobalObject& incumbent_global_object();
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/Scripting/WindowEnvironmentSettingsObject.cpp b/Userland/Libraries/LibWeb/HTML/Scripting/WindowEnvironmentSettingsObject.cpp
new file mode 100644
index 0000000000..089aaed189
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/Scripting/WindowEnvironmentSettingsObject.cpp
@@ -0,0 +1,86 @@
+/*
+ * Copyright (c) 2021, Luke Wilde <lukew@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <LibWeb/Bindings/WindowObject.h>
+#include <LibWeb/DOM/Document.h>
+#include <LibWeb/HTML/Scripting/WindowEnvironmentSettingsObject.h>
+
+namespace Web::HTML {
+
+WindowEnvironmentSettingsObject::WindowEnvironmentSettingsObject(DOM::Window& window, JS::ExecutionContext& execution_context)
+ : EnvironmentSettingsObject(execution_context)
+ , m_window(window)
+{
+}
+
+// https://html.spec.whatwg.org/multipage/window-object.html#set-up-a-window-environment-settings-object
+void WindowEnvironmentSettingsObject::setup(AK::URL& creation_url, JS::ExecutionContext& execution_context)
+{
+ // 1. Let realm be the value of execution context's Realm component.
+ auto* realm = execution_context.realm;
+ VERIFY(realm);
+
+ // 2. Let window be realm's global object.
+ // NOTE: We want to store the Window impl rather than the WindowObject.
+ auto& window = verify_cast<Bindings::WindowObject>(realm->global_object()).impl();
+
+ // 3. Let settings object be a new environment settings object whose algorithms are defined as follows:
+ // NOTE: See the functions defined for this class.
+ auto settings_object = adopt_own(*new WindowEnvironmentSettingsObject(window, execution_context));
+
+ // FIXME: 4. If reservedEnvironment is non-null, then:
+ // FIXME: 1. Set settings object's id to reservedEnvironment's id, target browsing context to reservedEnvironment's target browsing context, and active service worker to reservedEnvironment's active service worker.
+ // FIXME: 2. Set reservedEnvironment's id to the empty string.
+
+ // FIXME: 5. Otherwise, set settings object's id to a new unique opaque string, settings object's target browsing context to null, and settings object's active service worker to null.
+ settings_object->target_browsing_context = nullptr;
+
+ // FIXME: 6. Set settings object's creation URL to creationURL, settings object's top-level creation URL to topLevelCreationURL, and settings object's top-level origin to topLevelOrigin.
+ settings_object->creation_url = creation_url;
+
+ // 7. Set realm's [[HostDefined]] field to settings object.
+ realm->set_host_defined(move(settings_object));
+}
+
+// https://html.spec.whatwg.org/multipage/window-object.html#script-settings-for-window-objects:responsible-document
+RefPtr<DOM::Document> WindowEnvironmentSettingsObject::responsible_document()
+{
+ // Return window's associated Document.
+ return m_window->associated_document();
+}
+
+// https://html.spec.whatwg.org/multipage/window-object.html#script-settings-for-window-objects:api-url-character-encoding
+String WindowEnvironmentSettingsObject::api_url_character_encoding()
+{
+ // Return the current character encoding of window's associated Document.
+ return m_window->associated_document().encoding_or_default();
+}
+
+// https://html.spec.whatwg.org/multipage/window-object.html#script-settings-for-window-objects:api-base-url
+AK::URL WindowEnvironmentSettingsObject::api_base_url()
+{
+ // FIXME: Return the current base URL of window's associated Document.
+ // (This currently just returns the current document URL, not accounting for <base> elements and such)
+ return m_window->associated_document().url();
+}
+
+// https://html.spec.whatwg.org/multipage/window-object.html#script-settings-for-window-objects:concept-settings-object-origin
+Origin WindowEnvironmentSettingsObject::origin()
+{
+ // Return the origin of window's associated Document.
+ return m_window->associated_document().origin();
+}
+
+// https://html.spec.whatwg.org/multipage/window-object.html#script-settings-for-window-objects:concept-settings-object-cross-origin-isolated-capability
+CanUseCrossOriginIsolatedAPIs WindowEnvironmentSettingsObject::cross_origin_isolated_capability()
+{
+ // FIXME: Return true if both of the following hold, and false otherwise:
+ // 1. realm's agent cluster's cross-origin-isolation mode is "concrete", and
+ // 2. window's associated Document is allowed to use the "cross-origin-isolated" feature.
+ TODO();
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/Scripting/WindowEnvironmentSettingsObject.h b/Userland/Libraries/LibWeb/HTML/Scripting/WindowEnvironmentSettingsObject.h
new file mode 100644
index 0000000000..8577f82e04
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/Scripting/WindowEnvironmentSettingsObject.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2021, Luke Wilde <lukew@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+#include <LibWeb/DOM/Window.h>
+#include <LibWeb/HTML/Scripting/Environments.h>
+
+namespace Web::HTML {
+
+class WindowEnvironmentSettingsObject final : public EnvironmentSettingsObject {
+public:
+ static void setup(AK::URL& creation_url, JS::ExecutionContext& execution_context /* FIXME: null or an environment reservedEnvironment, a URL topLevelCreationURL, and an origin topLevelOrigin */);
+
+ virtual ~WindowEnvironmentSettingsObject() override = default;
+
+ virtual RefPtr<DOM::Document> responsible_document() override;
+ virtual String api_url_character_encoding() override;
+ virtual AK::URL api_base_url() override;
+ virtual Origin origin() override;
+ virtual CanUseCrossOriginIsolatedAPIs cross_origin_isolated_capability() override;
+
+private:
+ WindowEnvironmentSettingsObject(DOM::Window&, JS::ExecutionContext& execution_context);
+
+ NonnullRefPtr<DOM::Window> m_window;
+};
+
+}
diff --git a/Userland/Services/WebContent/ClientConnection.cpp b/Userland/Services/WebContent/ClientConnection.cpp
index 843d14ff7a..fcefebfbaf 100644
--- a/Userland/Services/WebContent/ClientConnection.cpp
+++ b/Userland/Services/WebContent/ClientConnection.cpp
@@ -19,6 +19,7 @@
#include <LibWeb/DOM/Document.h>
#include <LibWeb/Dump.h>
#include <LibWeb/HTML/BrowsingContext.h>
+#include <LibWeb/HTML/Scripting/ClassicScript.h>
#include <LibWeb/Layout/InitialContainingBlock.h>
#include <LibWeb/Loader/ContentFilter.h>
#include <LibWeb/Loader/ResourceLoader.h>
@@ -333,20 +334,29 @@ void ClientConnection::js_console_input(const String& js_source)
void ClientConnection::run_javascript(String const& js_source)
{
- if (!page().top_level_browsing_context().active_document())
+ auto* active_document = page().top_level_browsing_context().active_document();
+
+ if (!active_document)
return;
- auto& interpreter = page().top_level_browsing_context().active_document()->interpreter();
+ // This is partially based on "execute a javascript: URL request" https://html.spec.whatwg.org/multipage/browsing-the-web.html#javascript-protocol
- auto script_or_error = JS::Script::parse(js_source, interpreter.realm(), "");
- if (script_or_error.is_error())
- return;
+ // Let settings be browsingContext's active document's relevant settings object.
+ auto& settings = active_document->relevant_settings_object();
+
+ // Let baseURL be settings's API base URL.
+ auto base_url = settings.api_base_url();
- auto result = interpreter.run(script_or_error.value());
+ // Let script be the result of creating a classic script given scriptSource, settings, baseURL, and the default classic script fetch options.
+ // FIXME: This doesn't pass in "default classic script fetch options"
+ // FIXME: What should the filename be here?
+ auto script = Web::HTML::ClassicScript::create("(client connection run_javascript)", js_source, settings, move(base_url));
- if (result.is_error()) {
+ // Let evaluationStatus be the result of running the classic script script.
+ auto evaluation_status = script->run();
+
+ if (evaluation_status.is_error())
dbgln("Exception :(");
- }
}
void ClientConnection::js_console_request_messages(i32 start_index)
diff --git a/Userland/Services/WebContent/WebContentConsoleClient.cpp b/Userland/Services/WebContent/WebContentConsoleClient.cpp
index 6bbbfe3478..9ae9986c3d 100644
--- a/Userland/Services/WebContent/WebContentConsoleClient.cpp
+++ b/Userland/Services/WebContent/WebContentConsoleClient.cpp
@@ -9,8 +9,11 @@
#include "WebContentConsoleClient.h"
#include <LibJS/Interpreter.h>
#include <LibJS/MarkupGenerator.h>
+#include <LibJS/Parser.h>
#include <LibJS/Script.h>
#include <LibWeb/Bindings/WindowObject.h>
+#include <LibWeb/HTML/Scripting/ClassicScript.h>
+#include <LibWeb/HTML/Scripting/Environments.h>
#include <WebContent/ConsoleGlobalObject.h>
namespace WebContent {
@@ -28,31 +31,18 @@ WebContentConsoleClient::WebContentConsoleClient(JS::Console& console, WeakPtr<J
void WebContentConsoleClient::handle_input(String const& js_source)
{
- auto script_or_error = JS::Script::parse(js_source, m_interpreter->realm(), "");
+ auto& settings = verify_cast<Web::HTML::EnvironmentSettingsObject>(*m_interpreter->realm().host_defined());
+ auto script = Web::HTML::ClassicScript::create("(console)", js_source, settings, settings.api_base_url());
+
+ // FIXME: Add parse error printouts back once ClassicScript can report parse errors.
+
+ auto result = script->run();
+
StringBuilder output_html;
- auto result = JS::ThrowCompletionOr<JS::Value> { JS::js_undefined() };
- if (script_or_error.is_error()) {
- auto error = script_or_error.error()[0];
- auto hint = error.source_location_hint(js_source);
- if (!hint.is_empty())
- output_html.append(String::formatted("<pre>{}</pre>", escape_html_entities(hint)));
- result = m_interpreter->vm().throw_completion<JS::SyntaxError>(*m_console_global_object.cell(), error.to_string());
- } else {
- // FIXME: This is not the correct way to do this, we probably want to have
- // multiple execution contexts we switch between.
- auto& global_object_before = m_interpreter->realm().global_object();
- VERIFY(is<Web::Bindings::WindowObject>(global_object_before));
- auto& this_value_before = m_interpreter->realm().global_environment().global_this_value();
- m_interpreter->realm().set_global_object(*m_console_global_object.cell(), &global_object_before);
-
- result = m_interpreter->run(script_or_error.value());
-
- m_interpreter->realm().set_global_object(global_object_before, &this_value_before);
- }
- if (result.is_error()) {
+ if (result.is_abrupt()) {
output_html.append("Uncaught exception: ");
- auto error = *result.throw_completion().value();
+ auto error = *result.release_error().value();
if (error.is_object())
output_html.append(JS::MarkupGenerator::html_from_error(error.as_object()));
else
@@ -61,7 +51,8 @@ void WebContentConsoleClient::handle_input(String const& js_source)
return;
}
- print_html(JS::MarkupGenerator::html_from_value(result.value()));
+ if (result.value().has_value())
+ print_html(JS::MarkupGenerator::html_from_value(*result.value()));
}
void WebContentConsoleClient::print_html(String const& line)