LibWeb: Add a simple internals objects only available during testing

This object is available as `window.internals` (or just `internals`) and
is only accessible while running in "test mode".

This first version only has one API: gc(), which triggers a garbage
collection immediately.

In the future, we can add more APIs here to help us test parts of the
engine that are hard or impossible to reach via public web APIs.
This commit is contained in:
Andreas Kling 2023-07-21 11:59:49 +02:00
parent ba236e3f21
commit ec24d7555a
Notes: sideshowbarker 2024-07-18 00:34:07 +09:00
14 changed files with 109 additions and 5 deletions

View file

@ -23,6 +23,7 @@
#include <LibJS/Bytecode/Interpreter.h>
#include <LibMain/Main.h>
#include <LibWeb/Bindings/MainThreadVM.h>
#include <LibWeb/HTML/Window.h>
#include <LibWeb/Loader/ContentFilter.h>
#include <LibWeb/Loader/FrameLoader.h>
#include <LibWeb/Loader/ResourceLoader.h>
@ -89,6 +90,8 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
Web::WebSockets::WebSocketClientManager::initialize(Ladybird::WebSocketClientManagerQt::create());
}
Web::HTML::Window::set_internals_object_exposed(is_layout_test_mode);
JS::Bytecode::Interpreter::set_enabled(use_javascript_bytecode);
VERIFY(webcontent_fd_passing_socket >= 0);

View file

@ -3126,6 +3126,7 @@ using namespace Web::FileAPI;
using namespace Web::Geometry;
using namespace Web::HighResolutionTime;
using namespace Web::HTML;
using namespace Web::Internals;
using namespace Web::IntersectionObserver;
using namespace Web::RequestIdleCallback;
using namespace Web::ResizeObserver;
@ -3342,6 +3343,7 @@ using namespace Web::FileAPI;
using namespace Web::Geometry;
using namespace Web::HighResolutionTime;
using namespace Web::HTML;
using namespace Web::Internals;
using namespace Web::IntersectionObserver;
using namespace Web::PerformanceTimeline;
using namespace Web::RequestIdleCallback;
@ -3723,6 +3725,7 @@ using namespace Web::FileAPI;
using namespace Web::Geometry;
using namespace Web::HighResolutionTime;
using namespace Web::HTML;
using namespace Web::Internals;
using namespace Web::IntersectionObserver;
using namespace Web::NavigationTiming;
using namespace Web::PerformanceTimeline;
@ -3858,6 +3861,7 @@ using namespace Web::FileAPI;
using namespace Web::Geometry;
using namespace Web::HighResolutionTime;
using namespace Web::HTML;
using namespace Web::Internals;
using namespace Web::IntersectionObserver;
using namespace Web::NavigationTiming;
using namespace Web::PerformanceTimeline;
@ -3990,6 +3994,7 @@ using namespace Web::FileAPI;
using namespace Web::Geometry;
using namespace Web::HighResolutionTime;
using namespace Web::HTML;
using namespace Web::Internals;
using namespace Web::IntersectionObserver;
using namespace Web::NavigationTiming;
using namespace Web::PerformanceTimeline;

View file

@ -23,6 +23,7 @@ static constexpr Array libweb_interface_namespaces = {
"Geometry"sv,
"HTML"sv,
"HighResolutionTime"sv,
"Internals"sv,
"IntersectionObserver"sv,
"NavigationTiming"sv,
"RequestIdleCallback"sv,

View file

@ -178,7 +178,7 @@ void Intrinsics::create_web_namespace<@namespace_class@>(JS::Realm& realm)
)~~~");
};
auto add_interface = [&](SourceGenerator& gen, StringView name, StringView prototype_class, StringView constructor_class, Optional<LegacyConstructor> const& legacy_constructor) {
auto add_interface = [](SourceGenerator& gen, StringView name, StringView prototype_class, StringView constructor_class, Optional<LegacyConstructor> const& legacy_constructor) {
gen.set("interface_name", name);
gen.set("prototype_class", prototype_class);
gen.set("constructor_class", constructor_class);
@ -434,6 +434,8 @@ static ErrorOr<ExposedTo> parse_exposure_set(IDL::Interface& interface)
auto exposed = maybe_exposed.value().trim_whitespace();
if (exposed == "*"sv)
return ExposedTo::All;
if (exposed == "Nobody"sv)
return ExposedTo::Nobody;
if (exposed == "Window"sv)
return ExposedTo::Window;
if (exposed == "Worker"sv)
@ -477,10 +479,8 @@ ErrorOr<void> add_to_interface_sets(IDL::Interface& interface, Vector<IDL::Inter
{
// TODO: Add service worker exposed and audio worklet exposed
auto whom = TRY(parse_exposure_set(interface));
VERIFY(whom != ExposedTo::Nobody);
if ((whom & ExposedTo::Window) || (whom & ExposedTo::DedicatedWorker) || (whom & ExposedTo::SharedWorker))
intrinsics.append(interface);
intrinsics.append(interface);
if (whom & ExposedTo::Window)
window_exposed.append(interface);

View file

@ -0,0 +1,3 @@
[object Internals]
function gc() { [native code] }
OK

View file

@ -0,0 +1,9 @@
<script src="include.js"></script>
<script>
test(() => {
println(window.internals);
println(internals.gc);
internals.gc();
println("OK");
});
</script>

View file

@ -397,6 +397,7 @@ set(SOURCES
Infra/ByteSequences.cpp
Infra/JSON.cpp
Infra/Strings.cpp
Internals/Internals.cpp
IntersectionObserver/IntersectionObserver.cpp
IntersectionObserver/IntersectionObserverEntry.cpp
Layout/AudioBox.cpp

View file

@ -454,6 +454,10 @@ namespace Web::HighResolutionTime {
class Performance;
}
namespace Web::Internals {
class Internals;
}
namespace Web::IntersectionObserver {
class IntersectionObserver;
class IntersectionObserverEntry;

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020-2022, Andreas Kling <kling@serenityos.org>
* Copyright (c) 2020-2023, Andreas Kling <kling@serenityos.org>
* Copyright (c) 2021-2022, Sam Atkins <atkinssj@serenityos.org>
* Copyright (c) 2021-2023, Linus Groh <linusg@serenityos.org>
*
@ -51,6 +51,7 @@
#include <LibWeb/HighResolutionTime/TimeOrigin.h>
#include <LibWeb/Infra/Base64.h>
#include <LibWeb/Infra/CharacterTypes.h>
#include <LibWeb/Internals/Internals.h>
#include <LibWeb/Layout/Viewport.h>
#include <LibWeb/Page/Page.h>
#include <LibWeb/RequestIdleCallback/IdleDeadline.h>
@ -805,6 +806,13 @@ WebIDL::ExceptionOr<JS::NonnullGCPtr<WebIDL::CallbackType>> Window::byte_length_
return JS::NonnullGCPtr { *m_byte_length_queuing_strategy_size_function };
}
static bool s_internals_object_exposed = false;
void Window::set_internals_object_exposed(bool exposed)
{
s_internals_object_exposed = exposed;
}
WebIDL::ExceptionOr<void> Window::initialize_web_interfaces(Badge<WindowEnvironmentSettingsObject>)
{
auto& realm = this->realm();
@ -815,6 +823,9 @@ WebIDL::ExceptionOr<void> Window::initialize_web_interfaces(Badge<WindowEnvironm
MUST_OR_THROW_OOM(Bindings::WindowGlobalMixin::initialize(realm, *this));
MUST_OR_THROW_OOM(WindowOrWorkerGlobalScopeMixin::initialize(realm));
if (s_internals_object_exposed)
define_direct_property("internals", MUST_OR_THROW_OOM(heap().allocate<Internals::Internals>(realm, realm)), JS::default_attributes);
return {};
}

View file

@ -190,6 +190,8 @@ public:
HighResolutionTime::DOMHighResTimeStamp get_last_activation_timestamp() const { return m_last_activation_timestamp; }
void set_last_activation_timestamp(HighResolutionTime::DOMHighResTimeStamp timestamp) { m_last_activation_timestamp = timestamp; }
static void set_internals_object_exposed(bool);
private:
explicit Window(JS::Realm&);

View file

@ -0,0 +1,33 @@
/*
* Copyright (c) 2023, Andreas Kling <kling@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibJS/Runtime/VM.h>
#include <LibWeb/Bindings/InternalsPrototype.h>
#include <LibWeb/Bindings/Intrinsics.h>
#include <LibWeb/Internals/Internals.h>
namespace Web::Internals {
Internals::Internals(JS::Realm& realm)
: Bindings::PlatformObject(realm)
{
}
Internals::~Internals() = default;
JS::ThrowCompletionOr<void> Internals::initialize(JS::Realm& realm)
{
TRY(Base::initialize(realm));
Object::set_prototype(&Bindings::ensure_web_prototype<Bindings::InternalsPrototype>(realm, "Internals"));
return {};
}
void Internals::gc()
{
vm().heap().collect_garbage();
}
}

View file

@ -0,0 +1,26 @@
/*
* Copyright (c) 2023, Andreas Kling <kling@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <LibWeb/Bindings/PlatformObject.h>
namespace Web::Internals {
class Internals final : public Bindings::PlatformObject {
WEB_PLATFORM_OBJECT(Internals, Bindings::PlatformObject);
public:
virtual ~Internals() override;
void gc();
private:
explicit Internals(JS::Realm&);
virtual JS::ThrowCompletionOr<void> initialize(JS::Realm&) override;
};
}

View file

@ -0,0 +1,5 @@
[Exposed=Nobody] interface Internals {
undefined gc();
};

View file

@ -183,6 +183,7 @@ libweb_js_bindings(HTML/WorkerGlobalScope)
libweb_js_bindings(HTML/WorkerLocation)
libweb_js_bindings(HTML/WorkerNavigator)
libweb_js_bindings(HighResolutionTime/Performance)
libweb_js_bindings(Internals/Internals)
libweb_js_bindings(IntersectionObserver/IntersectionObserver)
libweb_js_bindings(IntersectionObserver/IntersectionObserverEntry)
libweb_js_bindings(NavigationTiming/PerformanceTiming)