diff --git a/moli-renderer-v8/src/native_bridge/context_host/host_environment.rs b/moli-renderer-v8/src/native_bridge/context_host/host_environment.rs index d7a56a2eeb..3983b54297 100644 --- a/moli-renderer-v8/src/native_bridge/context_host/host_environment.rs +++ b/moli-renderer-v8/src/native_bridge/context_host/host_environment.rs @@ -2,6 +2,7 @@ use std::collections::HashSet; use super::{ JsContextHost, + layout::LayoutInputChange, permissions::{permission_names_match, permission_override_name}, }; use crate::{ @@ -793,8 +794,7 @@ impl JsContextHost { overrides: &crate::protocol_types::EmulatedMediaOverrides, ) { if self.emulated_media != *overrides { - self.style_engine - .bump_target_context_epoch_for_document(self.document_handle()); + self.invalidate_layout_inputs(LayoutInputChange::StyleEnvironment); } self.emulated_media = overrides.clone(); } @@ -809,8 +809,7 @@ impl JsContextHost { ) -> bool { let changed = self.viewport_surface != viewport_surface; if changed { - self.style_engine - .bump_target_context_epoch_for_document(self.document_handle()); + self.invalidate_layout_inputs(LayoutInputChange::StyleEnvironment); } self.viewport_surface = viewport_surface; changed diff --git a/moli-renderer-v8/src/native_bridge/context_host/layout.rs b/moli-renderer-v8/src/native_bridge/context_host/layout.rs index 5100ea8adc..09d40a9c46 100644 --- a/moli-renderer-v8/src/native_bridge/context_host/layout.rs +++ b/moli-renderer-v8/src/native_bridge/context_host/layout.rs @@ -14,6 +14,16 @@ use crate::{ style_engine::{StyleViewport, StyloStyleEnvironment}, }; +/// Invalidation required by a changed native input. Publication is a separate +/// owner decision; these changes retain the last frozen layout for readers. +#[derive(Clone, Copy)] +pub(crate) enum LayoutInputChange { + /// Media and viewport changes invalidate computed styles through Stylo. + StyleEnvironment, + /// Scrollbar policy and interactions change future geometry/paint output. + UsedGeometry, +} + /// Resets the entry flag on every return path, including unwinding. /// /// This is a synchronous ownership guard, not a generation or a retry fence. @@ -57,13 +67,26 @@ impl JsContextHost { self.layout_policy } - pub(crate) fn set_scrollbars_hidden(&mut self, hidden: bool) { - if self.scrollbars_hidden != hidden { + pub(crate) fn set_scrollbars_hidden(&mut self, hidden: bool) -> bool { + let changed = self.scrollbars_hidden != hidden; + if changed { self.scrollbars_hidden = hidden; - self.clear_layout_rect_cache(); - self.document_layout_state - .get_mut() - .mark_visual_state_dirty(); + self.invalidate_layout_inputs(LayoutInputChange::UsedGeometry); + } + changed + } + + pub(crate) fn invalidate_layout_inputs(&self, change: LayoutInputChange) { + match change { + LayoutInputChange::StyleEnvironment => self + .style_engine + .bump_target_context_epoch_for_document(self.document_handle()), + LayoutInputChange::UsedGeometry => { + self.clear_layout_rect_cache(); + self.document_layout_state + .borrow_mut() + .mark_visual_state_dirty(); + } } } @@ -519,12 +542,7 @@ impl JsContextHost { } pub(crate) fn invalidate_layout_after_interaction_state_change(&self) { - self.clear_layout_rect_cache(); - // Interactions dirty future visual publication, while ordinary geometry - // and input keep consuming the last published frozen snapshot. - self.document_layout_state - .borrow_mut() - .mark_visual_state_dirty(); + self.invalidate_layout_inputs(LayoutInputChange::UsedGeometry); } pub(crate) fn document_web_font_resources_are_current( diff --git a/moli-renderer-v8/src/runtime/page_settings.rs b/moli-renderer-v8/src/runtime/page_settings.rs index 5927bc72e1..686374ec7d 100644 --- a/moli-renderer-v8/src/runtime/page_settings.rs +++ b/moli-renderer-v8/src/runtime/page_settings.rs @@ -4,7 +4,7 @@ use crate::script_vm::{DocumentSetting, DocumentSettingEffect, DocumentSettingsA impl PageVm { fn apply_document_setting(&mut self, setting: DocumentSetting<'_>) -> anyhow::Result<()> { let layout = self.layout_configuration; - let effect = self.vm_mut().apply_document_setting( + let effect: DocumentSettingEffect = self.vm_mut().apply_document_setting( setting, layout, DocumentSettingsApplication::Live, @@ -12,10 +12,8 @@ impl PageVm { // Retain the target value before publication. Even if publication // fails, a followed navigation must inherit the accepted native input. setting.retain_in(&mut self.document_settings); - if effect == DocumentSettingEffect::PublishLayout { - self.vm_mut().publish_layout()?; - } - Ok(()) + self.vm_mut() + .complete_document_settings_application(DocumentSettingsApplication::Live, effect) } pub(crate) fn set_script_execution_disabled(&mut self, disabled: bool) -> anyhow::Result<()> { diff --git a/moli-renderer-v8/src/script_vm/document_environment.rs b/moli-renderer-v8/src/script_vm/document_environment.rs index e450dddd60..dc29f888bc 100644 --- a/moli-renderer-v8/src/script_vm/document_environment.rs +++ b/moli-renderer-v8/src/script_vm/document_environment.rs @@ -198,10 +198,10 @@ impl ScriptVm { self._context_host.borrow_mut().set_layout_policy(policy); } - pub(crate) fn set_scrollbars_hidden(&mut self, hidden: bool) { + pub(crate) fn set_scrollbars_hidden(&mut self, hidden: bool) -> bool { self._context_host .borrow_mut() - .set_scrollbars_hidden(hidden); + .set_scrollbars_hidden(hidden) } pub(super) fn sync_document_fonts_for_environment(&mut self) { diff --git a/moli-renderer-v8/src/script_vm/document_settings.rs b/moli-renderer-v8/src/script_vm/document_settings.rs index 6bdd52f9ca..500331b693 100644 --- a/moli-renderer-v8/src/script_vm/document_settings.rs +++ b/moli-renderer-v8/src/script_vm/document_settings.rs @@ -12,7 +12,9 @@ pub(crate) enum DocumentSettingsApplication { #[derive(Clone, Copy, PartialEq, Eq)] pub(crate) enum DocumentSettingEffect { + /// Native values are installed; queued rendering updates publish later. Deferred, + /// Geometry must be current before a live setting command is acknowledged. PublishLayout, } @@ -75,7 +77,9 @@ impl ScriptVm { DocumentSetting::DocumentActivity(*document_activity), DocumentSetting::ScrollbarsHidden(*scrollbars_hidden), ] { - self.apply_document_setting(setting, layout, DocumentSettingsApplication::Bootstrap)?; + let application = DocumentSettingsApplication::Bootstrap; + let effect = self.apply_document_setting(setting, layout, application)?; + self.complete_document_settings_application(application, effect)?; } Ok(()) } @@ -134,13 +138,27 @@ impl ScriptVm { } DocumentSetting::ScrollbarsHidden(value) => { let hidden = layout.scrollbars_hidden_for(value); - let changed = self._context_host.borrow().scrollbars_hidden() != hidden; - self.set_scrollbars_hidden(hidden); - if live && changed { + if self.set_scrollbars_hidden(hidden) { effect = DocumentSettingEffect::PublishLayout; } } } Ok(effect) } + + /// Initialization never publishes layout or runs rendering notifications. + /// Live commands publish only settings that require synchronous geometry. + pub(crate) fn complete_document_settings_application( + &mut self, + application: DocumentSettingsApplication, + effect: DocumentSettingEffect, + ) -> anyhow::Result<()> { + match (application, effect) { + (DocumentSettingsApplication::Bootstrap, _) + | (DocumentSettingsApplication::Live, DocumentSettingEffect::Deferred) => Ok(()), + (DocumentSettingsApplication::Live, DocumentSettingEffect::PublishLayout) => { + self.publish_layout() + } + } + } }