Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
mod clipboard;
mod collections;
mod gamepad;
mod geolocation;
mod media_capabilities;
mod media_devices;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
use super::super::context_host_ptr_from_global_bridge;
use super::navigator::navigator_receiver_branded;
use crate::{util::throw_type_error, webidl};

pub(super) fn navigator_get_gamepads_callback<'s>(
scope: &mut v8::PinScope<'s, '_>,
args: v8::FunctionCallbackArguments<'s>,
mut rv: v8::ReturnValue<'s, v8::Value>,
) {
if !navigator_receiver_branded(scope, args.this()) {
throw_type_error(scope, "Illegal invocation");
return;
}

// The Gamepad algorithm uses the current global's document, including
// when a method from one Window is borrowed by another Navigator.
let context = scope.get_current_context();
if let Some(host_ptr) = context_host_ptr_from_global_bridge(scope) {
// SAFETY: the bridge owns the host for the lifetime of this callback.
let host = unsafe { &*host_ptr };
if let Some(identity) =
host.window_execution_context_identity_for_v8_context(scope, context)
&& host.window_execution_context_identity_is_current(identity)
&& host
.document_permissions_policy_for_owner(identity.dispatch_scope())
.is_some_and(|policy| !policy.gamepad_enabled())
{
webidl::throw_dom_exception(
scope,
"SecurityError",
"Access to gamepads is disallowed by permissions policy.",
);
return;
}
}

// No gamepad backend is connected. An inactive document also returns an
// empty sequence before checking policy. Each call produces a new Array.
rv.set(v8::Array::new(scope, 0).into());
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ use super::clipboard::{build_clipboard_object, install_clipboard_template_bindin
use super::collections::{
build_navigator_plugin_collections, install_navigator_collection_template_bindings,
};
use super::gamepad::navigator_get_gamepads_callback;
use super::geolocation::{build_geolocation_object, install_geolocation_template_bindings};
use super::media_capabilities::{
build_media_capabilities_object, install_media_capabilities_template_bindings,
Expand Down Expand Up @@ -211,6 +212,9 @@ struct NavigatorRuntimeDataPrototypeDeclaration {
#[derive(Default, WebApiFunctionTemplate)]
#[webapi(name = "Navigator")]
struct NavigatorPrototypeMethodsDeclaration {
#[webapi(method, enumerable, length = 0, callback = navigator_get_gamepads_callback)]
get_gamepads: (),

#[webapi(method, enumerable, length = 0, callback = navigator_java_enabled_callback)]
java_enabled: (),

Expand Down
1 change: 1 addition & 0 deletions moli-renderer-v8/src/document_runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -687,6 +687,7 @@ pub(crate) struct DocumentPolicyContainer {
pub(crate) credentialless: bool,
pub(crate) credentialless_storage_nonce: Option<moli_storage_key::OpaqueOriginNonce>,
pub(crate) sandbox: DocumentSandboxPolicy,
pub(crate) permissions_policy: crate::permissions_policy::DocumentPermissionsPolicy,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
Expand Down
5 changes: 5 additions & 0 deletions moli-renderer-v8/src/document_runtime/security_policy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,11 @@ impl DocumentPolicyContainer {
headers,
final_url,
),
permissions_policy:
crate::permissions_policy::DocumentPermissionsPolicy::from_navigation_response_headers(
headers,
final_url,
),
..Self::default()
}
}
Expand Down
1 change: 1 addition & 0 deletions moli-renderer-v8/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ mod page_task_queue;
mod parser_module_evaluation;
mod parser_module_pending;
mod parser_script;
mod permissions_policy;
mod queue_microtask;
mod range_boundary;
mod referrer_policy;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,15 @@ impl JsContextHost {
.map(|entry| entry.document_policy_container_snapshot())
})
.unwrap_or_else(|| self.document_policy_container().clone());
if let Some(parent_document) = self
.dom_host()
.node(handle)
.and_then(crate::dom::native::Node::owner_document)
&& let Some(permissions_policy) =
self.document_permissions_policy_for_document_handle(parent_document)
{
policy_container.permissions_policy = permissions_policy;
}
policy_container.document_referrer =
self.document_url_for_child_context(handle).to_string();
policy_container
Expand Down
15 changes: 15 additions & 0 deletions moli-renderer-v8/src/native_bridge/context_host/child_frames.rs
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,13 @@ impl ChildBrowsingContextEntry {
self.document_policy_container.clone()
}

pub(super) fn set_document_permissions_policy(
&mut self,
policy: crate::permissions_policy::DocumentPermissionsPolicy,
) {
self.document_policy_container.permissions_policy = policy;
}

pub(super) fn owner_credentialless(&self) -> bool {
self.credentialless
}
Expand Down Expand Up @@ -329,6 +336,10 @@ impl ChildBrowsingContextEntry {
.content_security_reporting_endpoints = policy_container
.content_security_reporting_endpoints
.clone();
self.document_policy_container.permissions_policy = self
.document_policy_container
.permissions_policy
.intersect(policy_container.permissions_policy);
self.set_document_credentialless_state(credentialless, credentialless_storage_nonce);
self.set_document_sandbox_policy(sandbox);
}
Expand Down Expand Up @@ -367,6 +378,10 @@ impl ChildBrowsingContextEntry {
.policy_container
.content_security_reporting_endpoints
.clone();
self.document_policy_container.permissions_policy = self
.document_policy_container
.permissions_policy
.intersect(snapshot.policy_container.permissions_policy);
self.set_document_credentialless_state(credentialless, credentialless_storage_nonce);
self.set_document_sandbox_policy(sandbox);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -436,6 +436,8 @@ impl JsContextHost {
if !self.child_browsing_contexts.contains_key(&handle) {
return None;
};
let permissions_policy =
self.child_browsing_context_permissions_policy_for_navigation(handle, &bootstrap);
let Some(navigation) = self.replace_child_navigation_load(handle) else {
tracing::warn!(
?handle,
Expand All @@ -444,6 +446,7 @@ impl JsContextHost {
return None;
};
let entry = self.child_browsing_contexts.get_mut(&handle)?;
entry.set_document_permissions_policy(permissions_policy);
entry.set_pending_navigation(bootstrap, reflects_window_state);
self.note_child_frame_load_started_for_parent(handle);
Some(navigation)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,20 @@ impl JsContextHost {
&& child_browsing_context_bootstrap_uses_initial_empty_load(
&attribute_bootstrap,
);
let pending_attribute_bootstrap_commit =
ChildBrowsingContextEntry::pending_attribute_bootstrap_commit_for_refresh(
existing.as_ref(),
is_new,
attribute_bootstrap_changed,
initial_about_blank_document_is_complete,
);
let pending_attribute_permissions_policy =
pending_attribute_bootstrap_commit.then(|| {
self.child_browsing_context_permissions_policy_for_navigation(
handle,
&attribute_bootstrap,
)
});
if is_new || attribute_bootstrap_changed || frame_identity_changed {
self.note_child_frame_load_started_for_parent(handle);
}
Expand Down Expand Up @@ -324,6 +338,20 @@ impl JsContextHost {
content_security_reporting_endpoints: refresh_policy_source
.map(|policy| policy.content_security_reporting_endpoints.clone())
.unwrap_or_default(),
permissions_policy: if is_new {
// The synchronous initial about:blank Document is
// already subject to the iframe's container policy.
// Use its live inherited origin, not a pending target
// navigation's origin, when applying the allowlist.
self.child_browsing_context_permissions_policy_for_navigation(
handle,
&live_bootstrap,
)
} else {
refresh_policy_source
.map(|policy| policy.permissions_policy)
.unwrap_or_default()
},
};
let initial_empty_document_init: Option<ChildInitialEmptyDocumentInit> = is_new
.then(|| {
Expand All @@ -342,13 +370,7 @@ impl JsContextHost {
name,
id: id.filter(|value| !value.is_empty()),
attribute_bootstrap,
pending_attribute_bootstrap_commit:
ChildBrowsingContextEntry::pending_attribute_bootstrap_commit_for_refresh(
existing.as_ref(),
is_new,
attribute_bootstrap_changed,
initial_about_blank_document_is_complete,
),
pending_attribute_bootstrap_commit,
pending_live_navigation: existing.as_ref().and_then(|entry| {
entry.pending_live_navigation_for_refresh(attribute_bootstrap_changed)
}),
Expand All @@ -365,7 +387,8 @@ impl JsContextHost {
cached_snapshot,
document_policy_container,
completed_document_network: existing.as_ref().and_then(|entry| {
entry.completed_document_network_for_refresh(attribute_bootstrap_changed)
entry
.completed_document_network_for_refresh(attribute_bootstrap_changed)
}),
completed_frame_owner_resource_timing: existing.as_ref().and_then(
|entry| {
Expand Down Expand Up @@ -468,6 +491,11 @@ impl JsContextHost {
self.register_or_update_service_worker_child_client(handle);
}
}
if let Some(policy) = pending_attribute_permissions_policy
&& let Some(entry) = self.child_browsing_contexts.get_mut(&handle)
{
entry.set_document_permissions_policy(policy);
}
if attribute_bootstrap_changed {
self.cancel_child_meta_refresh_navigation(handle);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -683,6 +683,34 @@ impl JsContextHost {
credentialless.then(|| self.ensure_top_document_credentialless_storage_nonce())
}

pub(in crate::native_bridge::context_host) fn child_browsing_context_permissions_policy_for_navigation(
&self,
handle: DomHandle,
bootstrap: &ChildBrowsingContextBootstrap,
) -> crate::permissions_policy::DocumentPermissionsPolicy {
let parent_document = self
.dom_host()
.node(handle)
.and_then(crate::dom::native::Node::owner_document)
.unwrap_or_else(|| self.document_handle());
let parent_url = self.document_url_for_handle(parent_document);
let parent_policy = self
.document_permissions_policy_for_document_handle(parent_document)
.unwrap_or_else(|| self.document_policy_container().permissions_policy);
let child_url = Self::child_browsing_context_bootstrap_url(bootstrap)
.unwrap_or_else(|| parent_url.clone());
let is_iframe = self.dom_host().is_html_element_named(handle, "iframe");
let allow = is_iframe
.then(|| self.dom_host().get_attribute(handle, "allow"))
.flatten();
parent_policy.delegated_to_child(
&parent_url,
&child_url,
bootstrap.security_origin_inherited(),
allow.as_deref(),
)
}

pub(crate) fn child_browsing_context_is_same_origin_with_top(&self, handle: DomHandle) -> bool {
self.top_window_can_access_child(handle)
}
Expand Down
42 changes: 42 additions & 0 deletions moli-renderer-v8/src/native_bridge/context_host/security_policy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,48 @@ impl JsContextHost {
)
}

pub(crate) fn document_permissions_policy_for_owner(
&self,
owner: OwnerDispatchScope,
) -> Option<crate::permissions_policy::DocumentPermissionsPolicy> {
match owner {
OwnerDispatchScope::Top => Some(self.document_policy_container().permissions_policy),
OwnerDispatchScope::Child(handle) => self
.frame_owner_current_child_snapshot(handle)
.map(|snapshot| {
snapshot
.settings
.document_policy_container
.permissions_policy
}),
OwnerDispatchScope::LightweightPopup(popup_id) => self
.lightweight_popup_policy_container(popup_id)
.map(|policy| policy.permissions_policy),
}
}

pub(crate) fn document_permissions_policy_for_document_handle(
&self,
document: DomHandle,
) -> Option<crate::permissions_policy::DocumentPermissionsPolicy> {
if document == self.document_handle() {
return Some(self.document_policy_container().permissions_policy);
}
if let Some(popup_id) = self.lightweight_popup_id_for_document_handle(document) {
return self
.lightweight_popup_policy_container(popup_id)
.map(|policy| policy.permissions_policy);
}
let child_handle = self.child_browsing_context_host_for_document_handle(document)?;
let snapshot = self.frame_owner_current_child_snapshot(child_handle)?;
(snapshot.document_handle == document).then_some(
snapshot
.settings
.document_policy_container
.permissions_policy,
)
}

pub(crate) fn trusted_types_for_script_requirements_for_owner(
&self,
owner: OwnerDispatchScope,
Expand Down
7 changes: 7 additions & 0 deletions moli-renderer-v8/src/native_bridge/element.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3896,6 +3896,13 @@ struct HtmlIFrameElementPrototypeDeclaration {
setter = iframe_srcdoc_setter_function
)]
srcdoc: (),
#[webapi(
accessor_property,
getter = dom_string_reflection_getter_function,
setter = dom_string_reflection_setter_function,
data = DomStringReflection::IframeAllow
)]
allow: (),
#[webapi(
accessor_property,
getter = dom_string_reflection_getter_function,
Expand Down
5 changes: 5 additions & 0 deletions moli-renderer-v8/src/native_bridge/element/reflection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@ pub(super) enum DomStringReflection {
HrWidth,
HtmlTimeDateTime,
HtmlVersion,
IframeAllow,
IframeCsp,
IframeFrameBorder,
IframeHeight,
Expand Down Expand Up @@ -412,6 +413,10 @@ const DOM_STRING_REFLECTION_DESCRIPTORS: &[(DomStringReflection, DomStringReflec
DomStringReflection::HtmlVersion,
DomStringReflectionDescriptor::new("HTMLHtmlElement", "version", "version"),
),
(
DomStringReflection::IframeAllow,
DomStringReflectionDescriptor::new("HTMLIFrameElement", "allow", "allow"),
),
(
DomStringReflection::IframeCsp,
DomStringReflectionDescriptor::new_html_element(
Expand Down
Loading
Loading