diff --git a/Libraries/LibWeb/HTML/WindowOrWorkerGlobalScope.cpp b/Libraries/LibWeb/HTML/WindowOrWorkerGlobalScope.cpp
index 862fe0971a..5fe40c7c8d 100644
--- a/Libraries/LibWeb/HTML/WindowOrWorkerGlobalScope.cpp
+++ b/Libraries/LibWeb/HTML/WindowOrWorkerGlobalScope.cpp
@@ -2,7 +2,7 @@
* Copyright (c) 2022, Andrew Kaster
* Copyright (c) 2023, Linus Groh
* Copyright (c) 2023, Luke Wilde
- * Copyright (c) 2025, Shannon Booth
+ * Copyright (c) 2025-2026, Shannon Booth
*
* SPDX-License-Identifier: BSD-2-Clause
*/
diff --git a/Libraries/LibWeb/Internals/Internals.cpp b/Libraries/LibWeb/Internals/Internals.cpp
index 11e7123295..2328b09630 100644
--- a/Libraries/LibWeb/Internals/Internals.cpp
+++ b/Libraries/LibWeb/Internals/Internals.cpp
@@ -9,6 +9,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -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);
+}
+
}
diff --git a/Libraries/LibWeb/Internals/Internals.h b/Libraries/LibWeb/Internals/Internals.h
index a299738b14..74d81f45e5 100644
--- a/Libraries/LibWeb/Internals/Internals.h
+++ b/Libraries/LibWeb/Internals/Internals.h
@@ -105,6 +105,7 @@ public:
void set_highlighted_node(GC::Ptr node);
void clear_element(HTML::HTMLElement&);
+ void set_environments_top_level_url(StringView url);
private:
explicit Internals(JS::Realm&);
diff --git a/Libraries/LibWeb/Internals/Internals.idl b/Libraries/LibWeb/Internals/Internals.idl
index 5ff01ad3ac..86115cc0ca 100644
--- a/Libraries/LibWeb/Internals/Internals.idl
+++ b/Libraries/LibWeb/Internals/Internals.idl
@@ -79,6 +79,8 @@ interface Internals {
undefined handleSDLInputEvents();
+ undefined setEnvironmentsTopLevelURL(USVString url);
+
InternalGamepad connectVirtualGamepad();
undefined setHighlightedNode(Node? node);
diff --git a/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/IDLGenerators.cpp b/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/IDLGenerators.cpp
index da7e523ddd..485e691c93 100644
--- a/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/IDLGenerators.cpp
+++ b/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/IDLGenerators.cpp
@@ -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
#include
#include
+#include
#include
#include
#include
@@ -5969,6 +5995,7 @@ void generate_global_mixin_implementation(IDL::Interface const& interface, Strin
#include
#include
#include
+#include
#include
#include
#include
diff --git a/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWindowOrWorkerInterfaces.cpp b/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWindowOrWorkerInterfaces.cpp
index 1542bcc084..6c982ab57a 100644
--- a/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWindowOrWorkerInterfaces.cpp
+++ b/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWindowOrWorkerInterfaces.cpp
@@ -120,6 +120,7 @@ static ErrorOr generate_intrinsic_definitions_implementation(StringView ou
#include
#include
#include
+#include
#include
#include
#include
@@ -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 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 generate_exposed_interface_implementation(StringView class_
#include
#include
#include
+#include
)~~~");
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) {
diff --git a/Tests/LibWeb/Text/expected/secure-context.txt b/Tests/LibWeb/Text/expected/secure-context.txt
new file mode 100644
index 0000000000..7d007925be
--- /dev/null
+++ b/Tests/LibWeb/Text/expected/secure-context.txt
@@ -0,0 +1,5 @@
+{
+ "SubtleCrypto": false,
+ "Clipboard": false,
+ "CookieChangeEvent": true
+}
diff --git a/Tests/LibWeb/Text/input/secure-context.html b/Tests/LibWeb/Text/input/secure-context.html
new file mode 100644
index 0000000000..60a5da9213
--- /dev/null
+++ b/Tests/LibWeb/Text/input/secure-context.html
@@ -0,0 +1,35 @@
+
+
+