mirror of
https://github.com/servo/servo
synced 2026-04-25 17:15:48 +02:00
script: registration mechanism for selective broadcast from constellation (#43124)
This introduces a mechanism that let script threads register and unregister themselves from receiving messages from the constellation. This is useful when only globals with event listeners or callbacks will process some message types. The first migrated API is the webstorage 'storage' event. This patch ensures that we only send IPC to same origin globals that have an event handler set. While we use it for an event here, this is also usable for DOM callbacks such as the ones used in the Geolocation API. Testing: optimization with no tests regression: https://github.com/webbeef/servo/actions/runs/22880845745 --------- Signed-off-by: webbeef <me@webbeef.org>
This commit is contained in:
@@ -159,13 +159,13 @@ use servo_canvas_traits::canvas::{CanvasId, CanvasMsg};
|
||||
use servo_canvas_traits::webgl::WebGLThreads;
|
||||
use servo_config::{opts, pref};
|
||||
use servo_constellation_traits::{
|
||||
AuxiliaryWebViewCreationRequest, AuxiliaryWebViewCreationResponse, DocumentState,
|
||||
EmbedderToConstellationMessage, IFrameLoadInfo, IFrameLoadInfoWithData, IFrameSizeMsg, Job,
|
||||
LoadData, LogEntry, MessagePortMsg, NavigationHistoryBehavior, PaintMetricEvent,
|
||||
PortMessageTask, PortTransferInfo, SWManagerMsg, SWManagerSenders, ScreenshotReadinessResponse,
|
||||
ScriptToConstellationMessage, ScrollStateUpdate, ServiceWorkerManagerFactory, ServiceWorkerMsg,
|
||||
StructuredSerializedData, TargetSnapshotParams, TraversalDirection, UserContentManagerAction,
|
||||
WindowSizeType,
|
||||
AuxiliaryWebViewCreationRequest, AuxiliaryWebViewCreationResponse, ConstellationInterest,
|
||||
DocumentState, EmbedderToConstellationMessage, IFrameLoadInfo, IFrameLoadInfoWithData,
|
||||
IFrameSizeMsg, Job, LoadData, LogEntry, MessagePortMsg, NavigationHistoryBehavior,
|
||||
PaintMetricEvent, PortMessageTask, PortTransferInfo, SWManagerMsg, SWManagerSenders,
|
||||
ScreenshotReadinessResponse, ScriptToConstellationMessage, ScrollStateUpdate,
|
||||
ServiceWorkerManagerFactory, ServiceWorkerMsg, StructuredSerializedData, TargetSnapshotParams,
|
||||
TraversalDirection, UserContentManagerAction, WindowSizeType,
|
||||
};
|
||||
use servo_url::{Host, ImmutableOrigin, ServoUrl};
|
||||
use servo_wakelock::{WakeLockProvider, WakeLockType};
|
||||
@@ -414,6 +414,9 @@ pub struct Constellation<STF, SWF> {
|
||||
/// Bookkeeping for BroadcastChannel functionnality.
|
||||
broadcast_channels: BroadcastChannels,
|
||||
|
||||
/// Tracks which pipelines have registered interest in each notification category.
|
||||
pipeline_interests: FxHashMap<ConstellationInterest, FxHashSet<PipelineId>>,
|
||||
|
||||
/// The set of all the pipelines in the browser. (See the `pipeline` module
|
||||
/// for more details.)
|
||||
pipelines: FxHashMap<PipelineId, Pipeline>,
|
||||
@@ -714,6 +717,7 @@ where
|
||||
message_ports: Default::default(),
|
||||
message_port_routers: Default::default(),
|
||||
broadcast_channels: Default::default(),
|
||||
pipeline_interests: Default::default(),
|
||||
pipelines: Default::default(),
|
||||
browsing_contexts: Default::default(),
|
||||
pending_changes: vec![],
|
||||
@@ -2012,6 +2016,20 @@ where
|
||||
warn!("Unable to forward DOMMessage for postMessage call");
|
||||
}
|
||||
},
|
||||
ScriptToConstellationMessage::RegisterInterest(interest) => {
|
||||
self.pipeline_interests
|
||||
.entry(interest)
|
||||
.or_default()
|
||||
.insert(source_pipeline_id);
|
||||
},
|
||||
ScriptToConstellationMessage::UnregisterInterest(interest) => {
|
||||
if let Some(set) = self.pipeline_interests.get_mut(&interest) {
|
||||
set.remove(&source_pipeline_id);
|
||||
if set.is_empty() {
|
||||
self.pipeline_interests.remove(&interest);
|
||||
}
|
||||
}
|
||||
},
|
||||
ScriptToConstellationMessage::BroadcastStorageEvent(
|
||||
storage,
|
||||
url,
|
||||
@@ -2646,7 +2664,17 @@ where
|
||||
);
|
||||
}
|
||||
|
||||
for pipeline in self.pipelines.values() {
|
||||
let interested = match self
|
||||
.pipeline_interests
|
||||
.get(&ConstellationInterest::StorageEvent)
|
||||
{
|
||||
Some(set) => set,
|
||||
None => return,
|
||||
}
|
||||
.iter()
|
||||
.filter_map(|interested_id| self.pipelines.get(interested_id));
|
||||
|
||||
for pipeline in interested {
|
||||
if pipeline.id == pipeline_id || pipeline.url.origin() != origin {
|
||||
continue;
|
||||
}
|
||||
@@ -2954,6 +2982,12 @@ where
|
||||
return;
|
||||
};
|
||||
|
||||
// Clean up any registered interests for this pipeline.
|
||||
self.pipeline_interests.retain(|_, set| {
|
||||
set.remove(&pipeline_id);
|
||||
!set.is_empty()
|
||||
});
|
||||
|
||||
// Now that the Script and Constellation parts of Servo no longer have a reference to
|
||||
// this pipeline, tell `Paint` that it has shut down. This is delayed until the
|
||||
// last moment.
|
||||
|
||||
@@ -141,6 +141,8 @@ mod from_script {
|
||||
target!("RemoveBroadcastChannelNameInRouter")
|
||||
},
|
||||
Self::ScheduleBroadcast(..) => target!("ScheduleBroadcast"),
|
||||
Self::RegisterInterest(..) => target!("RegisterInterest"),
|
||||
Self::UnregisterInterest(..) => target!("UnregisterInterest"),
|
||||
Self::BroadcastStorageEvent(..) => target!("BroadcastStorageEvent"),
|
||||
Self::ChangeRunningAnimationsState(..) => target!("ChangeRunningAnimationsState"),
|
||||
Self::CreateCanvasPaintThread(..) => target!("CreateCanvasPaintThread"),
|
||||
|
||||
@@ -21,6 +21,7 @@ use js::rust::{CompileOptionsWrapper, HandleObject, transform_u16_to_source_text
|
||||
use libc::c_char;
|
||||
use rustc_hash::{FxBuildHasher, FxHashSet};
|
||||
use script_bindings::cformat;
|
||||
use servo_constellation_traits::ConstellationInterest;
|
||||
use servo_url::ServoUrl;
|
||||
use style::str::HTML_SPACE_CHARACTERS;
|
||||
use stylo_atoms::Atom;
|
||||
@@ -435,6 +436,30 @@ impl EventTarget {
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns the [`ConstellationInterest`] associated with a given event type
|
||||
/// on this specific target, if any. The mapping depends on the concrete type
|
||||
/// of the EventTarget since the same event name can be used in multiple contexts.
|
||||
fn interest_for_event_type(&self, ty: &Atom) -> Option<ConstellationInterest> {
|
||||
if self.is::<Window>() && *ty == atom!("storage") {
|
||||
return Some(ConstellationInterest::StorageEvent);
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Notify the global about a listener being added for a given event type.
|
||||
fn notify_listener_added(&self, ty: &Atom) {
|
||||
if let Some(interest) = self.interest_for_event_type(ty) {
|
||||
self.global().register_interest(interest);
|
||||
}
|
||||
}
|
||||
|
||||
/// Notify the global about a listener being removed for a given event type.
|
||||
fn notify_listener_removed(&self, ty: &Atom) {
|
||||
if let Some(interest) = self.interest_for_event_type(ty) {
|
||||
self.global().unregister_interest(interest);
|
||||
}
|
||||
}
|
||||
|
||||
/// Determine if there are any listeners for a given event type.
|
||||
/// See <https://github.com/whatwg/dom/issues/453>.
|
||||
pub(crate) fn has_listeners_for(&self, type_: &Atom) -> bool {
|
||||
@@ -457,10 +482,11 @@ impl EventTarget {
|
||||
|
||||
pub(crate) fn remove_all_listeners(&self) {
|
||||
let mut handlers = self.handlers.borrow_mut();
|
||||
for (_, entries) in handlers.iter() {
|
||||
for (ty, entries) in handlers.iter() {
|
||||
entries
|
||||
.iter()
|
||||
.for_each(|entry| entry.borrow_mut().removed = true);
|
||||
self.notify_listener_removed(ty);
|
||||
}
|
||||
|
||||
*handlers = Default::default();
|
||||
@@ -523,6 +549,7 @@ impl EventTarget {
|
||||
},
|
||||
None => {
|
||||
entries.remove(idx).borrow_mut().removed = true;
|
||||
self.notify_listener_removed(&ty);
|
||||
},
|
||||
},
|
||||
None => {
|
||||
@@ -534,6 +561,7 @@ impl EventTarget {
|
||||
passive: self.default_passive_value(&ty),
|
||||
removed: false,
|
||||
})));
|
||||
self.notify_listener_added(&ty)
|
||||
}
|
||||
},
|
||||
}
|
||||
@@ -545,6 +573,7 @@ impl EventTarget {
|
||||
if let Some(entries) = handlers.get_mut(ty) {
|
||||
if let Some(position) = entries.iter().position(|e| *e == *entry) {
|
||||
entries.remove(position).borrow_mut().removed = true;
|
||||
self.notify_listener_removed(ty);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -866,8 +895,8 @@ impl EventTarget {
|
||||
Some(l) => l,
|
||||
None => return,
|
||||
};
|
||||
let mut handlers = self.handlers.borrow_mut();
|
||||
let ty = Atom::from(ty);
|
||||
let mut handlers = self.handlers.borrow_mut();
|
||||
let entries = match handlers.entry(ty.clone()) {
|
||||
Occupied(entry) => entry.into_mut(),
|
||||
Vacant(entry) => entry.insert(EventListeners(vec![])),
|
||||
@@ -892,6 +921,7 @@ impl EventTarget {
|
||||
// and capture is listener’s capture, then append listener to eventTarget’s event listener list.
|
||||
if !entries.contains(&new_entry) {
|
||||
entries.push(new_entry);
|
||||
self.notify_listener_added(&ty);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -906,8 +936,9 @@ impl EventTarget {
|
||||
let Some(listener) = listener else {
|
||||
return;
|
||||
};
|
||||
let ty_atom = Atom::from(ty);
|
||||
let mut handlers = self.handlers.borrow_mut();
|
||||
if let Some(entries) = handlers.get_mut(&Atom::from(ty)) {
|
||||
if let Some(entries) = handlers.get_mut(&ty_atom) {
|
||||
let phase = if options.capture {
|
||||
ListenerPhase::Capturing
|
||||
} else {
|
||||
@@ -920,6 +951,7 @@ impl EventTarget {
|
||||
{
|
||||
// Step 2. Set listener’s removed to true and remove listener from eventTarget’s event listener list.
|
||||
entries.remove(position).borrow_mut().removed = true;
|
||||
self.notify_listener_removed(&ty_atom);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,8 +65,8 @@ use servo_base::id::{
|
||||
ServiceWorkerId, ServiceWorkerRegistrationId, WebViewId,
|
||||
};
|
||||
use servo_constellation_traits::{
|
||||
BlobData, BlobImpl, BroadcastChannelMsg, FileBlob, MessagePortImpl, MessagePortMsg,
|
||||
PortMessageTask, ScriptToConstellationChan, ScriptToConstellationMessage,
|
||||
BlobData, BlobImpl, BroadcastChannelMsg, ConstellationInterest, FileBlob, MessagePortImpl,
|
||||
MessagePortMsg, PortMessageTask, ScriptToConstellationChan, ScriptToConstellationMessage,
|
||||
};
|
||||
use servo_url::{ImmutableOrigin, MutableOrigin, ServoUrl};
|
||||
use storage_traits::StorageThreads;
|
||||
@@ -218,6 +218,12 @@ pub(crate) struct GlobalScope {
|
||||
/// The broadcast channels state this global, if it is managing any.
|
||||
broadcast_channel_state: DomRefCell<BroadcastChannelState>,
|
||||
|
||||
/// Tracks the number of active listeners per constellation interest category.
|
||||
/// When the count transitions from 0 to 1, a RegisterInterest message is sent.
|
||||
/// When it transitions from 1 to 0, an UnregisterInterest message is sent.
|
||||
#[no_trace]
|
||||
constellation_interest_counts: RefCell<HashMap<ConstellationInterest, usize>>,
|
||||
|
||||
/// The blobs managed by this global, if any.
|
||||
blob_state: DomRefCell<HashMapTracedValues<BlobId, BlobInfo, FxBuildHasher>>,
|
||||
|
||||
@@ -769,6 +775,7 @@ impl GlobalScope {
|
||||
task_manager: Default::default(),
|
||||
message_port_state: DomRefCell::new(MessagePortState::UnManaged),
|
||||
broadcast_channel_state: DomRefCell::new(BroadcastChannelState::UnManaged),
|
||||
constellation_interest_counts: RefCell::new(HashMap::new()),
|
||||
blob_state: Default::default(),
|
||||
eventtarget: EventTarget::new_inherited(),
|
||||
registration_map: DomRefCell::new(HashMapTracedValues::new_fx()),
|
||||
@@ -2476,6 +2483,34 @@ impl GlobalScope {
|
||||
self.pipeline_id
|
||||
}
|
||||
|
||||
/// Register interest in a notification category. Sends a `RegisterInterest`
|
||||
/// message to the constellation when the first listener is registered.
|
||||
pub(crate) fn register_interest(&self, interest: ConstellationInterest) {
|
||||
let mut counts = self.constellation_interest_counts.borrow_mut();
|
||||
let count = counts.entry(interest).or_insert(0);
|
||||
*count += 1;
|
||||
if *count == 1 {
|
||||
let _ = self
|
||||
.script_to_constellation_chan()
|
||||
.send(ScriptToConstellationMessage::RegisterInterest(interest));
|
||||
}
|
||||
}
|
||||
|
||||
/// Unregister interest in a notification category. Sends an `UnregisterInterest`
|
||||
/// message to the constellation when the last listener is removed.
|
||||
pub(crate) fn unregister_interest(&self, interest: ConstellationInterest) {
|
||||
let mut counts = self.constellation_interest_counts.borrow_mut();
|
||||
if let Some(count) = counts.get_mut(&interest) {
|
||||
*count = count.saturating_sub(1);
|
||||
if *count == 0 {
|
||||
counts.remove(&interest);
|
||||
let _ = self
|
||||
.script_to_constellation_chan()
|
||||
.send(ScriptToConstellationMessage::UnregisterInterest(interest));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the origin for this global scope
|
||||
pub(crate) fn origin(&self) -> &MutableOrigin {
|
||||
&self.origin
|
||||
|
||||
@@ -543,6 +543,41 @@ macro_rules! event_handler(
|
||||
)
|
||||
);
|
||||
|
||||
/// Similar to `event_handler!`, but also registers/unregisters a [`ConstellationInterest`]
|
||||
/// with the global scope when the handler is set or cleared.
|
||||
/// Use this macro for event handlers whose corresponding events are sent by the constellation
|
||||
/// only to interested pipelines.
|
||||
macro_rules! registered_event_handler(
|
||||
($interest:expr, $event_type: ident, $getter: ident, $setter: ident) => (
|
||||
fn $getter(&self) -> Option<::std::rc::Rc<
|
||||
crate::dom::bindings::codegen::Bindings::EventHandlerBinding::EventHandlerNonNull,
|
||||
>> {
|
||||
use crate::dom::bindings::inheritance::Castable;
|
||||
use crate::dom::eventtarget::EventTarget;
|
||||
use crate::script_runtime::CanGc;
|
||||
let eventtarget = self.upcast::<EventTarget>();
|
||||
eventtarget.get_event_handler_common(stringify!($event_type), CanGc::deprecated_note())
|
||||
}
|
||||
|
||||
fn $setter(&self, listener: Option<::std::rc::Rc<
|
||||
crate::dom::bindings::codegen::Bindings::EventHandlerBinding::EventHandlerNonNull,
|
||||
>>) {
|
||||
use crate::dom::bindings::inheritance::Castable;
|
||||
use crate::dom::bindings::reflector::DomGlobal;
|
||||
use crate::dom::eventtarget::EventTarget;
|
||||
let had_handler = self.$getter().is_some();
|
||||
let has_handler = listener.is_some();
|
||||
let eventtarget = self.upcast::<EventTarget>();
|
||||
eventtarget.set_event_handler_common(stringify!($event_type), listener);
|
||||
if !had_handler && has_handler {
|
||||
self.global().register_interest($interest);
|
||||
} else if had_handler && !has_handler {
|
||||
self.global().unregister_interest($interest);
|
||||
}
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
macro_rules! error_event_handler(
|
||||
($event_type: ident, $getter: ident, $setter: ident) => (
|
||||
define_event_handler!(
|
||||
@@ -718,7 +753,10 @@ macro_rules! window_event_handlers(
|
||||
event_handler!(popstate, GetOnpopstate, SetOnpopstate);
|
||||
event_handler!(rejectionhandled, GetOnrejectionhandled,
|
||||
SetOnrejectionhandled);
|
||||
event_handler!(storage, GetOnstorage, SetOnstorage);
|
||||
registered_event_handler!(
|
||||
servo_constellation_traits::ConstellationInterest::StorageEvent,
|
||||
storage, GetOnstorage, SetOnstorage
|
||||
);
|
||||
event_handler!(unhandledrejection, GetOnunhandledrejection,
|
||||
SetOnunhandledrejection);
|
||||
event_handler!(unload, GetOnunload, SetOnunload);
|
||||
|
||||
@@ -542,6 +542,16 @@ pub enum ScreenshotReadinessResponse {
|
||||
NoLongerActive,
|
||||
}
|
||||
|
||||
/// Identifies a category of events/notifications that a pipeline can register
|
||||
/// interest in with the constellation. When a pipeline has active listeners for
|
||||
/// events in a given category, it registers interest so the constellation only
|
||||
/// sends notifications to pipelines that care.
|
||||
#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)]
|
||||
pub enum ConstellationInterest {
|
||||
/// Interest in `storage` events (fired when another same-origin pipeline modifies storage).
|
||||
StorageEvent,
|
||||
}
|
||||
|
||||
/// Messages from the script to the constellation.
|
||||
#[derive(Deserialize, IntoStaticStr, Serialize)]
|
||||
pub enum ScriptToConstellationMessage {
|
||||
@@ -589,6 +599,12 @@ pub enum ScriptToConstellationMessage {
|
||||
/// Broadcast a message to all same-origin broadcast channels,
|
||||
/// excluding the source of the broadcast.
|
||||
ScheduleBroadcast(BroadcastChannelRouterId, BroadcastChannelMsg),
|
||||
/// Register this pipeline's interest in a category of notifications.
|
||||
/// The constellation will only send notifications in this category to
|
||||
/// pipelines that have registered interest.
|
||||
RegisterInterest(ConstellationInterest),
|
||||
/// Unregister this pipeline's interest in a category of notifications.
|
||||
UnregisterInterest(ConstellationInterest),
|
||||
/// Broadcast a storage event to every same-origin pipeline.
|
||||
/// The strings are key, old value and new value.
|
||||
BroadcastStorageEvent(
|
||||
|
||||
Reference in New Issue
Block a user