--- original
+++ modified
@@ -14,12 +14,14 @@
use std::time::Duration;
use base::cross_process_instant::CrossProcessInstant;
-use base::generic_channel::GenericSend;
+use base::generic_channel::{GenericSend, GenericSharedMemory};
use base::id::WebViewId;
use base::{Epoch, generic_channel};
use bitflags::bitflags;
use chrono::Local;
-use constellation_traits::{NavigationHistoryBehavior, ScriptToConstellationMessage};
+use constellation_traits::{
+ EmbeddedWebViewEventType, NavigationHistoryBehavior, ScriptToConstellationMessage,
+};
use content_security_policy::sandboxing_directive::SandboxingFlagSet;
use content_security_policy::{CspList, Policy as CspPolicy, PolicyDisposition};
use cookie::Cookie;
@@ -31,6 +33,7 @@
Image, LoadStatus,
};
use encoding_rs::{Encoding, UTF_8};
+use euclid::Size2D;
use fonts::WebFontDocumentContext;
use html5ever::{LocalName, Namespace, QualName, local_name, ns};
use hyper_serde::Serde;
@@ -615,6 +618,9 @@
#[no_trace]
favicon: RefCell>,
+ /// The cached theme color for that document.
+ theme_color: RefCell >,
+
/// All websockets created that are associated with this document.
websockets: DOMTracker,
@@ -860,6 +866,12 @@
// Set the document's activity level, reflow if necessary, and suspend or resume timers.
self.activity.set(activity);
+
+ // When document becomes inactive, hide all embedder controls (keyboard, etc.)
+ if activity == DocumentActivity::Inactive {
+ self.embedder_controls().hide_all_controls();
+ }
+
let media = ServoMedia::get();
let pipeline_id = self.window().pipeline_id();
let client_context_id =
@@ -873,6 +885,7 @@
self.title_changed();
self.notify_embedder_favicon();
+ self.notify_embedder_theme_color();
self.dirty_all_nodes();
self.window().resume(CanGc::from_cx(cx));
media.resume(&client_context_id);
@@ -1277,6 +1290,9 @@
LoadStatus::Started,
));
self.send_to_embedder(EmbedderMsg::Status(self.webview_id(), None));
+ self.notify_embedded_webview_parent(
+ EmbeddedWebViewEventType::LoadStatusChanged(LoadStatus::Started),
+ );
}
},
DocumentReadyState::Complete => {
@@ -1285,6 +1301,9 @@
self.webview_id(),
LoadStatus::Complete,
));
+ self.notify_embedded_webview_parent(
+ EmbeddedWebViewEventType::LoadStatusChanged(LoadStatus::Complete),
+ );
}
update_with_current_instant(&self.dom_complete);
},
@@ -1691,7 +1710,13 @@
let window = self.window();
if window.is_top_level() {
let title = self.title().map(String::from);
- self.send_to_embedder(EmbedderMsg::ChangePageTitle(self.webview_id(), title));
+ self.send_to_embedder(EmbedderMsg::ChangePageTitle(
+ self.webview_id(),
+ title.clone(),
+ ));
+ // Also notify parent iframe if this is an embedded webview.
+ // The constellation will filter and only forward if this is actually an embedded webview.
+ self.notify_embedded_webview_parent(EmbeddedWebViewEventType::TitleChanged(title));
}
}
@@ -1700,6 +1725,18 @@
window.send_to_embedder(msg);
}
+ /// Sends a notification to the parent iframe element if this document is in an embedded webview.
+ /// The constellation checks if the webview is embedded and forwards the event to the parent pipeline.
+ pub(crate) fn notify_embedded_webview_parent(&self, event: EmbeddedWebViewEventType) {
+ let window = self.window();
+ // Only top-level windows can be embedded webviews
+ if window.is_top_level() {
+ window.send_to_constellation(
+ ScriptToConstellationMessage::EmbeddedWebViewNotification(event),
+ );
+ }
+ }
+
pub(crate) fn dirty_all_nodes(&self) {
let root = match self.GetDocumentElement() {
Some(root) => root,
@@ -3198,9 +3235,59 @@
current_rendering_epoch,
);
+ // After reflow, update embedded webview rects for input event routing
+ self.update_embedded_webview_rects();
+
(phases, statistics)
}
+ /// Update the rects of embedded webviews for input event routing.
+ /// This sends the current position/size of each embedded webview iframe
+ /// to the compositor so it can route input events to the correct webview.
+ /// Also updates visibility state when iframes have display:none.
+ fn update_embedded_webview_rects(&self) {
+ let parent_webview_id = self.webview_id();
+ let paint_api = self.window().paint_api();
+ let device_pixel_ratio = self.window().device_pixel_ratio().get();
+
+ // Collect embedded webview iframes first to avoid holding the borrow
+ // during border_box() calls which can trigger reflow and need iframes_mut().
+ let embedded_iframes: Vec<(DomRoot, WebViewId)> = self
+ .iframes()
+ .iter()
+ .filter(|iframe| iframe.is_embedded_webview())
+ .filter_map(|iframe| iframe.embedded_webview_id().map(|id| (iframe, id)))
+ .collect();
+
+ for (iframe, embedded_webview_id) in embedded_iframes {
+ // Get the iframe's border box (in CSS pixels relative to the initial containing block)
+ // This is equivalent to getBoundingClientRect() which is viewport-relative.
+ // If the iframe has display:none, border_box() returns None.
+ let Some(border_box) = iframe.upcast::().border_box() else {
+ // Iframe is not visible (display:none), notify compositor to hide it
+ paint_api.set_embedded_webview_hidden(embedded_webview_id, parent_webview_id, true);
+ continue;
+ };
+
+ // Convert to device pixels
+ // Note: border_box coordinates are viewport-relative (like getBoundingClientRect)
+ let rect = webrender_api::units::DeviceRect::from_origin_and_size(
+ webrender_api::units::DevicePoint::new(
+ border_box.origin.x.to_f32_px() * device_pixel_ratio,
+ border_box.origin.y.to_f32_px() * device_pixel_ratio,
+ ),
+ webrender_api::units::DeviceSize::new(
+ border_box.size.width.to_f32_px() * device_pixel_ratio,
+ border_box.size.height.to_f32_px() * device_pixel_ratio,
+ ),
+ );
+
+ // Iframe is visible, notify compositor to show it and update its rect
+ paint_api.set_embedded_webview_hidden(embedded_webview_id, parent_webview_id, false);
+ paint_api.update_embedded_webview_rect(embedded_webview_id, parent_webview_id, rect);
+ }
+ }
+
pub(crate) fn handle_no_longer_waiting_on_asynchronous_image_updates(&self) {
self.waiting_on_canvas_image_updates.set(false);
}
@@ -3943,6 +4030,7 @@
active_sandboxing_flag_set: Cell::new(SandboxingFlagSet::empty()),
creation_sandboxing_flag_set: Cell::new(creation_sandboxing_flag_set),
favicon: RefCell::new(None),
+ theme_color: RefCell::new(None),
websockets: DOMTracker::new(),
details_name_groups: Default::default(),
protocol_handler_automation_mode: Default::default(),
@@ -5047,6 +5135,36 @@
pub(crate) fn notify_embedder_favicon(&self) {
if let Some(ref image) = *self.favicon.borrow() {
+ // Encode the raw pixel data as PNG for the embedded webview event
+ let pixel_format = match image.format {
+ embedder_traits::PixelFormat::RGBA8 => pixels::SnapshotPixelFormat::RGBA,
+ embedder_traits::PixelFormat::BGRA8 => pixels::SnapshotPixelFormat::BGRA,
+ _ => pixels::SnapshotPixelFormat::RGBA, // Fallback
+ };
+
+ let mut snapshot = pixels::Snapshot::from_vec(
+ Size2D::new(image.width, image.height),
+ pixel_format,
+ pixels::SnapshotAlphaMode::Transparent {
+ premultiplied: false,
+ },
+ image.data().to_vec(),
+ );
+
+ let mut png_bytes = Vec::new();
+ if snapshot
+ .encode_for_mime_type(&pixels::EncodedImageType::Png, None, &mut png_bytes)
+ .is_ok()
+ {
+ // Notify parent iframe about favicon change (for embedded webviews)
+ self.notify_embedded_webview_parent(EmbeddedWebViewEventType::FaviconChanged {
+ bytes: GenericSharedMemory::from_bytes(&png_bytes),
+ width: image.width,
+ height: image.height,
+ });
+ }
+
+ // Notify embedder
self.send_to_embedder(EmbedderMsg::NewFavicon(self.webview_id(), image.clone()));
}
}
@@ -5092,6 +5210,20 @@
pub(crate) fn set_css_styling_flag(&self, value: bool) {
self.css_styling_flag.set(value)
}
+
+ pub(crate) fn notify_embedder_theme_color(&self) {
+ if let Some(ref theme_color) = *self.theme_color.borrow() {
+ // Notify parent iframe about theme color change (for embedded webviews)
+ self.notify_embedded_webview_parent(EmbeddedWebViewEventType::ThemeColorChanged(
+ theme_color.clone(),
+ ));
+ }
+ }
+
+ pub(crate) fn set_theme_color(&self, theme_color: String) {
+ *self.theme_color.borrow_mut() = Some(theme_color);
+ self.notify_embedder_theme_color();
+ }
}
impl DocumentMethods for Document {