mirror of
https://github.com/LadybirdBrowser/ladybird
synced 2026-04-25 17:25:08 +02:00
LibWeb/Bindings: Implement [SecureContext] extended attribute
Unfortunately this is a bit of a pain to test as it is surprisingly difficult to create a non secure context in our test harness. This is because both file scheme URLs and localhost are considered secure contexts. To test this, add a very specific internals setter to change the top level origin of the environment for the current realm.
This commit is contained in:
committed by
Shannon Booth
parent
6bdced0014
commit
bc93ba4530
Notes:
github-actions[bot]
2026-02-14 19:35:46 +00:00
Author: https://github.com/shannonbooth Commit: https://github.com/LadybirdBrowser/ladybird/commit/bc93ba45306 Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/7518
@@ -2,7 +2,7 @@
|
||||
* Copyright (c) 2022, Andrew Kaster <akaster@serenityos.org>
|
||||
* Copyright (c) 2023, Linus Groh <linusg@serenityos.org>
|
||||
* Copyright (c) 2023, Luke Wilde <lukew@serenityos.org>
|
||||
* Copyright (c) 2025, Shannon Booth <shannon@serenityos.org>
|
||||
* Copyright (c) 2025-2026, Shannon Booth <shannon@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <LibGfx/Cursor.h>
|
||||
#include <LibJS/Runtime/Date.h>
|
||||
#include <LibJS/Runtime/VM.h>
|
||||
#include <LibURL/Parser.h>
|
||||
#include <LibUnicode/TimeZone.h>
|
||||
#include <LibWeb/ARIA/AriaData.h>
|
||||
#include <LibWeb/ARIA/StateAndProperties.h>
|
||||
@@ -532,4 +533,10 @@ void Internals::clear_element(HTML::HTMLElement& element)
|
||||
form_associated_element.clear_algorithm();
|
||||
}
|
||||
|
||||
void Internals::set_environments_top_level_url(StringView url)
|
||||
{
|
||||
auto& realm = *vm().current_realm();
|
||||
HTML::principal_realm_settings_object(realm).top_level_creation_url = URL::Parser::basic_parse(url);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -105,6 +105,7 @@ public:
|
||||
void set_highlighted_node(GC::Ptr<DOM::Node> node);
|
||||
|
||||
void clear_element(HTML::HTMLElement&);
|
||||
void set_environments_top_level_url(StringView url);
|
||||
|
||||
private:
|
||||
explicit Internals(JS::Realm&);
|
||||
|
||||
@@ -79,6 +79,8 @@ interface Internals {
|
||||
|
||||
undefined handleSDLInputEvents();
|
||||
|
||||
undefined setEnvironmentsTopLevelURL(USVString url);
|
||||
|
||||
InternalGamepad connectVirtualGamepad();
|
||||
|
||||
undefined setHighlightedNode(Node? node);
|
||||
|
||||
@@ -3731,11 +3731,21 @@ void @class_name@::initialize(JS::Realm& realm)
|
||||
continue;
|
||||
|
||||
auto attribute_generator = generator_for_member(attribute.name, attribute.extended_attributes);
|
||||
|
||||
if (attribute.extended_attributes.contains("SecureContext")) {
|
||||
attribute_generator.append(R"~~~(
|
||||
if (HTML::is_secure_context(Bindings::principal_host_defined_environment_settings_object(realm))) {)~~~");
|
||||
}
|
||||
|
||||
if (attribute.extended_attributes.contains("FIXME")) {
|
||||
attribute_generator.set("attribute.name", attribute.name);
|
||||
attribute_generator.append(R"~~~(
|
||||
@define_direct_property@("@attribute.name@"_utf16_fly_string, JS::js_undefined(), default_attributes | JS::Attribute::Unimplemented);
|
||||
)~~~");
|
||||
if (attribute.extended_attributes.contains("SecureContext")) {
|
||||
attribute_generator.append(R"~~~(
|
||||
})~~~");
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -3778,6 +3788,11 @@ void @class_name@::initialize(JS::Realm& realm)
|
||||
attribute_generator.append(R"~~~(
|
||||
@define_direct_accessor@("@attribute.name@"_utf16_fly_string, native_@attribute.getter_callback@, native_@attribute.setter_callback@, default_attributes);
|
||||
)~~~");
|
||||
|
||||
if (attribute.extended_attributes.contains("SecureContext")) {
|
||||
attribute_generator.append(R"~~~(
|
||||
})~~~");
|
||||
}
|
||||
}
|
||||
|
||||
for (auto& function : interface.functions) {
|
||||
@@ -3823,6 +3838,11 @@ void @class_name@::initialize(JS::Realm& realm)
|
||||
function_generator.set("function.name:snakecase", make_input_acceptable_cpp(overload_set.key.to_snakecase()));
|
||||
function_generator.set("function.length", ByteString::number(get_shortest_function_length(overload_set.value)));
|
||||
|
||||
if (function.extended_attributes.contains("SecureContext")) {
|
||||
function_generator.append(R"~~~(
|
||||
if (HTML::is_secure_context(Bindings::principal_host_defined_environment_settings_object(realm))) {)~~~");
|
||||
}
|
||||
|
||||
if (any_of(overload_set.value, [](auto const& function) { return function.extended_attributes.contains("Unscopable"); })) {
|
||||
VERIFY(all_of(overload_set.value, [](auto const& function) { return function.extended_attributes.contains("Unscopable"); }));
|
||||
function_generator.append(R"~~~(
|
||||
@@ -3833,6 +3853,11 @@ void @class_name@::initialize(JS::Realm& realm)
|
||||
function_generator.append(R"~~~(
|
||||
@define_native_function@(realm, "@function.name@"_utf16_fly_string, @function.name:snakecase@, @function.length@, default_attributes);
|
||||
)~~~");
|
||||
|
||||
if (function.extended_attributes.contains("SecureContext")) {
|
||||
function_generator.append(R"~~~(
|
||||
})~~~");
|
||||
}
|
||||
}
|
||||
|
||||
bool should_generate_stringifier = true;
|
||||
@@ -5548,6 +5573,7 @@ void generate_prototype_implementation(IDL::Interface const& interface, StringBu
|
||||
#include <LibURL/Origin.h>
|
||||
#include <LibWeb/Bindings/@prototype_class@.h>
|
||||
#include <LibWeb/Bindings/ExceptionOrUtils.h>
|
||||
#include <LibWeb/Bindings/PrincipalHostDefined.h>
|
||||
#include <LibWeb/Bindings/Intrinsics.h>
|
||||
#include <LibWeb/DOM/Element.h>
|
||||
#include <LibWeb/DOM/Event.h>
|
||||
@@ -5969,6 +5995,7 @@ void generate_global_mixin_implementation(IDL::Interface const& interface, Strin
|
||||
#include <LibWeb/Bindings/@prototype_name@.h>
|
||||
#include <LibWeb/Bindings/ExceptionOrUtils.h>
|
||||
#include <LibWeb/Bindings/Intrinsics.h>
|
||||
#include <LibWeb/Bindings/PrincipalHostDefined.h>
|
||||
#include <LibWeb/DOM/Element.h>
|
||||
#include <LibWeb/DOM/Event.h>
|
||||
#include <LibWeb/DOM/IDLEventListener.h>
|
||||
|
||||
@@ -120,6 +120,7 @@ static ErrorOr<void> generate_intrinsic_definitions_implementation(StringView ou
|
||||
#include <LibGC/DeferGC.h>
|
||||
#include <LibJS/Runtime/Object.h>
|
||||
#include <LibWeb/Bindings/Intrinsics.h>
|
||||
#include <LibWeb/Bindings/PrincipalHostDefined.h>
|
||||
#include <LibWeb/Export.h>
|
||||
#include <LibWeb/HTML/Window.h>
|
||||
#include <LibWeb/HTML/DedicatedWorkerGlobalScope.h>
|
||||
@@ -182,6 +183,27 @@ void Intrinsics::create_web_namespace<@namespace_class@>(JS::Realm& realm)
|
||||
)~~~");
|
||||
};
|
||||
|
||||
generator.append(R"~~~(
|
||||
static bool is_secure_context_interface(InterfaceName name)
|
||||
{
|
||||
switch (name) {
|
||||
)~~~");
|
||||
for (auto const& interface : interface_sets.intrinsics) {
|
||||
if (!interface.extended_attributes.contains("SecureContext"))
|
||||
continue;
|
||||
|
||||
generator.set("secure_context_interface_name", interface.name);
|
||||
generator.append(R"~~~(
|
||||
case InterfaceName::@secure_context_interface_name@:)~~~");
|
||||
}
|
||||
generator.append(R"~~~(
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
)~~~");
|
||||
|
||||
auto generate_global_exposed = [&generator](StringView global_name, Vector<IDL::Interface&> const& interface_set) {
|
||||
generator.set("global_name", global_name);
|
||||
generator.append(R"~~~(
|
||||
@@ -235,8 +257,11 @@ bool is_exposed(InterfaceName name, JS::Realm& realm)
|
||||
TODO(); // FIXME: ServiceWorkerGlobalScope and WorkletGlobalScope.
|
||||
}
|
||||
|
||||
// FIXME: 2. If realm’s settings object is not a secure context, and construct is conditionally exposed on
|
||||
// [SecureContext], then return false.
|
||||
// 2. If realm’s settings object is not a secure context, and construct is conditionally exposed on
|
||||
// [SecureContext], then return false.
|
||||
if (is_secure_context_interface(name) && HTML::is_non_secure_context(principal_host_defined_environment_settings_object(realm)))
|
||||
return false;
|
||||
|
||||
// FIXME: 3. If realm’s settings object’s cross-origin isolated capability is false, and construct is
|
||||
// conditionally exposed on [CrossOriginIsolated], then return false.
|
||||
|
||||
@@ -352,6 +377,7 @@ static ErrorOr<void> generate_exposed_interface_implementation(StringView class_
|
||||
#include <LibJS/Runtime/Object.h>
|
||||
#include <LibWeb/Bindings/Intrinsics.h>
|
||||
#include <LibWeb/Bindings/@global_object_name@ExposedInterfaces.h>
|
||||
#include <LibWeb/HTML/Scripting/Environments.h>
|
||||
)~~~");
|
||||
for (auto& interface : exposed_interfaces) {
|
||||
auto gen = generator.fork();
|
||||
@@ -382,6 +408,7 @@ namespace Web::Bindings {
|
||||
void add_@global_object_snake_name@_exposed_interfaces(JS::Object& global)
|
||||
{
|
||||
static constexpr u8 attr = JS::Attribute::Writable | JS::Attribute::Configurable;
|
||||
[[maybe_unused]] bool is_secure_context = HTML::is_secure_context(HTML::relevant_principal_settings_object(global));
|
||||
)~~~");
|
||||
|
||||
auto add_interface = [class_name](SourceGenerator& gen, IDL::Interface const& interface) {
|
||||
@@ -393,6 +420,11 @@ void add_@global_object_snake_name@_exposed_interfaces(JS::Object& global)
|
||||
gen.set("interface_name", interface.namespaced_name);
|
||||
gen.set("prototype_class", interface.prototype_class);
|
||||
|
||||
if (interface.extended_attributes.contains("SecureContext")) {
|
||||
gen.append(R"~~~(
|
||||
if (is_secure_context) {)~~~");
|
||||
}
|
||||
|
||||
gen.append(R"~~~(
|
||||
global.define_intrinsic_accessor("@interface_name@"_utf16_fly_string, attr, [](auto& realm) -> JS::Value { return &ensure_web_constructor<@prototype_class@>(realm, "@interface_name@"_fly_string); });)~~~");
|
||||
|
||||
@@ -417,6 +449,11 @@ void add_@global_object_snake_name@_exposed_interfaces(JS::Object& global)
|
||||
gen.append(R"~~~(
|
||||
global.define_intrinsic_accessor("@legacy_interface_name@"_utf16_fly_string, attr, [](auto& realm) -> JS::Value { return &ensure_web_constructor<@prototype_class@>(realm, "@legacy_interface_name@"_fly_string); });)~~~");
|
||||
}
|
||||
|
||||
if (interface.extended_attributes.contains("SecureContext")) {
|
||||
gen.append(R"~~~(
|
||||
})~~~");
|
||||
}
|
||||
};
|
||||
|
||||
auto add_namespace = [](SourceGenerator& gen, StringView name, StringView namespace_class) {
|
||||
|
||||
5
Tests/LibWeb/Text/expected/secure-context.txt
Normal file
5
Tests/LibWeb/Text/expected/secure-context.txt
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"SubtleCrypto": false,
|
||||
"Clipboard": false,
|
||||
"CookieChangeEvent": true
|
||||
}
|
||||
35
Tests/LibWeb/Text/input/secure-context.html
Normal file
35
Tests/LibWeb/Text/input/secure-context.html
Normal file
@@ -0,0 +1,35 @@
|
||||
<!DOCTYPE html>
|
||||
<script src="include.js"></script>
|
||||
<script>
|
||||
asyncTest(async (done) => {
|
||||
const httpServer = httpTestServer();
|
||||
const url = await httpServer.createEcho("GET", "/secure-context-idl", {
|
||||
status: 200,
|
||||
headers: {
|
||||
"Access-Control-Allow-Origin": "*",
|
||||
"Content-Type": "text/html",
|
||||
},
|
||||
body: `
|
||||
<script>
|
||||
// HTTP is not a secure context.
|
||||
internals.setEnvironmentsTopLevelURL('http://ladybird.org');
|
||||
|
||||
parent.postMessage({
|
||||
SubtleCrypto: !!crypto?.subtle,
|
||||
Clipboard: typeof navigator.clipboard === "object",
|
||||
CookieChangeEvent: typeof CookieChangeEvent === "function",
|
||||
}, "*");
|
||||
<\/script>`,
|
||||
});
|
||||
|
||||
const frame = document.createElement('iframe');
|
||||
frame.src = url;
|
||||
|
||||
addEventListener("message", (event) => {
|
||||
println(JSON.stringify(event.data, null, 2));
|
||||
done();
|
||||
}, false);
|
||||
|
||||
document.body.appendChild(frame);
|
||||
});
|
||||
</script>
|
||||
Reference in New Issue
Block a user