diff options
Diffstat (limited to 'components')
-rw-r--r-- | components/compositing/compositor.rs | 81 | ||||
-rw-r--r-- | components/compositing/webview_renderer.rs | 82 | ||||
-rw-r--r-- | components/constellation/constellation.rs | 42 | ||||
-rw-r--r-- | components/constellation/event_loop.rs | 21 | ||||
-rw-r--r-- | components/script/dom/document.rs | 30 | ||||
-rw-r--r-- | components/script/messaging.rs | 2 | ||||
-rw-r--r-- | components/script/script_thread.rs | 23 | ||||
-rw-r--r-- | components/shared/constellation/lib.rs | 17 | ||||
-rw-r--r-- | components/shared/script/lib.rs | 4 |
9 files changed, 132 insertions, 170 deletions
diff --git a/components/compositing/compositor.rs b/components/compositing/compositor.rs index 4550188a7fa..41286a2760a 100644 --- a/components/compositing/compositor.rs +++ b/components/compositing/compositor.rs @@ -21,12 +21,12 @@ use compositing_traits::rendering_context::RenderingContext; use compositing_traits::{ CompositionPipeline, CompositorMsg, ImageUpdate, SendableFrameTree, WebViewTrait, }; -use constellation_traits::{AnimationTickType, EmbedderToConstellationMessage, PaintMetricEvent}; +use constellation_traits::{EmbedderToConstellationMessage, PaintMetricEvent}; use crossbeam_channel::{Receiver, Sender}; use dpi::PhysicalSize; use embedder_traits::{ - AnimationState, CompositorHitTestResult, Cursor, InputEvent, MouseButtonEvent, MouseMoveEvent, - ShutdownState, TouchEventType, UntrustedNodeAddress, ViewportDetails, + CompositorHitTestResult, Cursor, InputEvent, MouseButtonEvent, MouseMoveEvent, ShutdownState, + TouchEventType, UntrustedNodeAddress, ViewportDetails, }; use euclid::{Point2D, Rect, Scale, Size2D, Transform3D}; use fnv::FnvHashMap; @@ -197,9 +197,6 @@ pub(crate) struct PipelineDetails { /// The pipeline associated with this PipelineDetails object. pub pipeline: Option<CompositionPipeline>, - /// The [`PipelineId`] of this pipeline. - pub id: PipelineId, - /// The id of the parent pipeline, if any. pub parent_pipeline_id: Option<PipelineId>, @@ -243,32 +240,12 @@ impl PipelineDetails { pub(crate) fn animating(&self) -> bool { !self.throttled && (self.animation_callbacks_running || self.animations_running) } - - pub(crate) fn tick_animations(&self, compositor: &IOCompositor) { - if !self.animating() { - return; - } - - let mut tick_type = AnimationTickType::empty(); - if self.animations_running { - tick_type.insert(AnimationTickType::CSS_ANIMATIONS_AND_TRANSITIONS); - } - if self.animation_callbacks_running { - tick_type.insert(AnimationTickType::REQUEST_ANIMATION_FRAME); - } - - let msg = EmbedderToConstellationMessage::TickAnimation(self.id, tick_type); - if let Err(e) = compositor.global.borrow().constellation_sender.send(msg) { - warn!("Sending tick to constellation failed ({:?}).", e); - } - } } impl PipelineDetails { - pub(crate) fn new(id: PipelineId) -> PipelineDetails { + pub(crate) fn new() -> PipelineDetails { PipelineDetails { pipeline: None, - id, parent_pipeline_id: None, most_recent_display_list_epoch: None, animations_running: false, @@ -543,22 +520,14 @@ impl IOCompositor { pipeline_id, animation_state, ) => { - let mut throttled = true; if let Some(webview_renderer) = self.webview_renderers.get_mut(webview_id) { - throttled = webview_renderer - .change_running_animations_state(pipeline_id, animation_state); - } - - // These operations should eventually happen per-WebView, but they are global now as rendering - // is still global to all WebViews. - if !throttled && animation_state == AnimationState::AnimationsPresent { - self.set_needs_repaint(RepaintReason::ChangedAnimationState); - } - - if !throttled && animation_state == AnimationState::AnimationCallbacksPresent { - // We need to fetch the WebView again in order to avoid a double borrow. - if let Some(webview_renderer) = self.webview_renderers.get(webview_id) { - webview_renderer.tick_animations_for_pipeline(pipeline_id, self); + if webview_renderer + .change_pipeline_running_animations_state(pipeline_id, animation_state) && + webview_renderer.animating() + { + // These operations should eventually happen per-WebView, but they are + // global now as rendering is still global to all WebViews. + self.process_animations(true); } } }, @@ -605,8 +574,13 @@ impl IOCompositor { CompositorMsg::SetThrottled(webview_id, pipeline_id, throttled) => { if let Some(webview_renderer) = self.webview_renderers.get_mut(webview_id) { - webview_renderer.set_throttled(pipeline_id, throttled); - self.process_animations(true); + if webview_renderer.set_throttled(pipeline_id, throttled) && + webview_renderer.animating() + { + // These operations should eventually happen per-WebView, but they are + // global now as rendering is still global to all WebViews. + self.process_animations(true); + } } }, @@ -1283,8 +1257,23 @@ impl IOCompositor { } self.last_animation_tick = Instant::now(); - for webview_renderer in self.webview_renderers.iter() { - webview_renderer.tick_all_animations(self); + let animating_webviews: Vec<_> = self + .webview_renderers + .iter() + .filter_map(|webview_renderer| { + if webview_renderer.animating() { + Some(webview_renderer.id) + } else { + None + } + }) + .collect(); + if !animating_webviews.is_empty() { + if let Err(error) = self.global.borrow().constellation_sender.send( + EmbedderToConstellationMessage::TickAnimation(animating_webviews), + ) { + warn!("Sending tick to constellation failed ({error:?})."); + } } } diff --git a/components/compositing/webview_renderer.rs b/components/compositing/webview_renderer.rs index 6ad77d46043..614ef0ff4c3 100644 --- a/components/compositing/webview_renderer.rs +++ b/components/compositing/webview_renderer.rs @@ -86,6 +86,9 @@ pub(crate) struct WebViewRenderer { /// The HiDPI scale factor for the `WebView` associated with this renderer. This is controlled /// by the embedding layer. hidpi_scale_factor: Scale<f32, DeviceIndependentPixel, DevicePixel>, + /// Whether or not this [`WebViewRenderer`] isn't throttled and has a pipeline with + /// active animations or animation frame callbacks. + animating: bool, } impl Drop for WebViewRenderer { @@ -119,6 +122,7 @@ impl WebViewRenderer { min_viewport_zoom: Some(PinchZoomFactor::new(1.0)), max_viewport_zoom: None, hidpi_scale_factor: Scale::new(hidpi_scale_factor.0), + animating: false, } } @@ -138,6 +142,10 @@ impl WebViewRenderer { self.pipelines.keys() } + pub(crate) fn animating(&self) -> bool { + self.animating + } + /// Returns the [`PipelineDetails`] for the given [`PipelineId`], creating it if needed. pub(crate) fn ensure_pipeline_details( &mut self, @@ -148,14 +156,10 @@ impl WebViewRenderer { .borrow_mut() .pipeline_to_webview_map .insert(pipeline_id, self.id); - PipelineDetails::new(pipeline_id) + PipelineDetails::new() }) } - pub(crate) fn set_throttled(&mut self, pipeline_id: PipelineId, throttled: bool) { - self.ensure_pipeline_details(pipeline_id).throttled = throttled; - } - pub(crate) fn remove_pipeline(&mut self, pipeline_id: PipelineId) { self.global .borrow_mut() @@ -245,51 +249,45 @@ impl WebViewRenderer { }) } - /// Sets or unsets the animations-running flag for the given pipeline. Returns true if - /// the pipeline is throttled. - pub(crate) fn change_running_animations_state( + /// Sets or unsets the animations-running flag for the given pipeline. Returns + /// true if the [`WebViewRenderer`]'s overall animating state changed. + pub(crate) fn change_pipeline_running_animations_state( &mut self, pipeline_id: PipelineId, animation_state: AnimationState, ) -> bool { - let throttled = { - let pipeline_details = self.ensure_pipeline_details(pipeline_id); - match animation_state { - AnimationState::AnimationsPresent => { - pipeline_details.animations_running = true; - }, - AnimationState::AnimationCallbacksPresent => { - pipeline_details.animation_callbacks_running = true; - }, - AnimationState::NoAnimationsPresent => { - pipeline_details.animations_running = false; - }, - AnimationState::NoAnimationCallbacksPresent => { - pipeline_details.animation_callbacks_running = false; - }, - } - pipeline_details.throttled - }; - - let animating = self.pipelines.values().any(PipelineDetails::animating); - self.webview.set_animating(animating); - throttled + let pipeline_details = self.ensure_pipeline_details(pipeline_id); + match animation_state { + AnimationState::AnimationsPresent => { + pipeline_details.animations_running = true; + }, + AnimationState::AnimationCallbacksPresent => { + pipeline_details.animation_callbacks_running = true; + }, + AnimationState::NoAnimationsPresent => { + pipeline_details.animations_running = false; + }, + AnimationState::NoAnimationCallbacksPresent => { + pipeline_details.animation_callbacks_running = false; + }, + } + self.update_animation_state() } - pub(crate) fn tick_all_animations(&self, compositor: &IOCompositor) { - for pipeline_details in self.pipelines.values() { - pipeline_details.tick_animations(compositor) - } + /// Sets or unsets the throttled flag for the given pipeline. Returns + /// true if the [`WebViewRenderer`]'s overall animating state changed. + pub(crate) fn set_throttled(&mut self, pipeline_id: PipelineId, throttled: bool) -> bool { + self.ensure_pipeline_details(pipeline_id).throttled = throttled; + + // Throttling a pipeline can cause it to be taken into the "not-animating" state. + self.update_animation_state() } - pub(crate) fn tick_animations_for_pipeline( - &self, - pipeline_id: PipelineId, - compositor: &IOCompositor, - ) { - if let Some(pipeline_details) = self.pipelines.get(&pipeline_id) { - pipeline_details.tick_animations(compositor); - } + pub(crate) fn update_animation_state(&mut self) -> bool { + let animating = self.pipelines.values().any(PipelineDetails::animating); + let old_animating = std::mem::replace(&mut self.animating, animating); + self.webview.set_animating(self.animating); + old_animating != self.animating } /// On a Window refresh tick (e.g. vsync) diff --git a/components/constellation/constellation.rs b/components/constellation/constellation.rs index 3f70b0abb89..05081fe0ba7 100644 --- a/components/constellation/constellation.rs +++ b/components/constellation/constellation.rs @@ -112,13 +112,12 @@ use compositing_traits::{ CompositorMsg, CompositorProxy, SendableFrameTree, WebrenderExternalImageRegistry, }; use constellation_traits::{ - AnimationTickType, AuxiliaryWebViewCreationRequest, AuxiliaryWebViewCreationResponse, - BroadcastMsg, DocumentState, EmbedderToConstellationMessage, IFrameLoadInfo, - IFrameLoadInfoWithData, IFrameSandboxState, IFrameSizeMsg, Job, LoadData, LoadOrigin, LogEntry, - MessagePortMsg, NavigationHistoryBehavior, PaintMetricEvent, PortMessageTask, SWManagerMsg, - SWManagerSenders, ScriptToConstellationChan, ScriptToConstellationMessage, ScrollState, - ServiceWorkerManagerFactory, ServiceWorkerMsg, StructuredSerializedData, TraversalDirection, - WindowSizeType, + AuxiliaryWebViewCreationRequest, AuxiliaryWebViewCreationResponse, BroadcastMsg, DocumentState, + EmbedderToConstellationMessage, IFrameLoadInfo, IFrameLoadInfoWithData, IFrameSandboxState, + IFrameSizeMsg, Job, LoadData, LoadOrigin, LogEntry, MessagePortMsg, NavigationHistoryBehavior, + PaintMetricEvent, PortMessageTask, SWManagerMsg, SWManagerSenders, ScriptToConstellationChan, + ScriptToConstellationMessage, ScrollState, ServiceWorkerManagerFactory, ServiceWorkerMsg, + StructuredSerializedData, TraversalDirection, WindowSizeType, }; use crossbeam_channel::{Receiver, Select, Sender, unbounded}; use devtools_traits::{ @@ -1398,8 +1397,8 @@ where EmbedderToConstellationMessage::ThemeChange(theme) => { self.handle_theme_change(theme); }, - EmbedderToConstellationMessage::TickAnimation(pipeline_id, tick_type) => { - self.handle_tick_animation(pipeline_id, tick_type) + EmbedderToConstellationMessage::TickAnimation(webview_ids) => { + self.handle_tick_animation(webview_ids) }, EmbedderToConstellationMessage::WebDriverCommand(command) => { self.handle_webdriver_msg(command); @@ -3528,15 +3527,24 @@ where feature = "tracing", tracing::instrument(skip_all, fields(servo_profiling = true), level = "trace") )] - fn handle_tick_animation(&mut self, pipeline_id: PipelineId, tick_type: AnimationTickType) { - let pipeline = match self.pipelines.get(&pipeline_id) { - Some(pipeline) => pipeline, - None => return warn!("{}: Got script tick after closure", pipeline_id), - }; + fn handle_tick_animation(&mut self, webview_ids: Vec<WebViewId>) { + let mut animating_event_loops = HashSet::new(); - let message = ScriptThreadMessage::TickAllAnimations(pipeline_id, tick_type); - if let Err(e) = pipeline.event_loop.send(message) { - self.handle_send_error(pipeline_id, e); + for webview_id in webview_ids.iter() { + for browsing_context in self.fully_active_browsing_contexts_iter(*webview_id) { + let Some(pipeline) = self.pipelines.get(&browsing_context.pipeline_id) else { + continue; + }; + animating_event_loops.insert(pipeline.event_loop.clone()); + } + } + + for event_loop in animating_event_loops { + // No error handling here. It's unclear what to do when this fails as the error isn't associated + // with a particular pipeline. In addition, the danger of not progressing animations is pretty + // low, so it's probably safe to ignore this error and handle the crashed ScriptThread on + // some other message. + let _ = event_loop.send(ScriptThreadMessage::TickAllAnimations(webview_ids.clone())); } } diff --git a/components/constellation/event_loop.rs b/components/constellation/event_loop.rs index 362960eba64..46542e7212f 100644 --- a/components/constellation/event_loop.rs +++ b/components/constellation/event_loop.rs @@ -6,17 +6,36 @@ //! view of a script thread. When an `EventLoop` is dropped, an `ExitScriptThread` //! message is sent to the script thread, asking it to shut down. +use std::hash::Hash; use std::marker::PhantomData; use std::rc::Rc; +use std::sync::atomic::{AtomicUsize, Ordering}; use ipc_channel::Error; use ipc_channel::ipc::IpcSender; use script_traits::ScriptThreadMessage; +static CURRENT_EVENT_LOOP_ID: AtomicUsize = AtomicUsize::new(0); + /// <https://html.spec.whatwg.org/multipage/#event-loop> pub struct EventLoop { script_chan: IpcSender<ScriptThreadMessage>, dont_send_or_sync: PhantomData<Rc<()>>, + id: usize, +} + +impl PartialEq for EventLoop { + fn eq(&self, other: &Self) -> bool { + self.id == other.id + } +} + +impl Eq for EventLoop {} + +impl Hash for EventLoop { + fn hash<H: std::hash::Hasher>(&self, state: &mut H) { + self.id.hash(state); + } } impl Drop for EventLoop { @@ -28,9 +47,11 @@ impl Drop for EventLoop { impl EventLoop { /// Create a new event loop from the channel to its script thread. pub fn new(script_chan: IpcSender<ScriptThreadMessage>) -> Rc<EventLoop> { + let id = CURRENT_EVENT_LOOP_ID.fetch_add(1, Ordering::Relaxed); Rc::new(EventLoop { script_chan, dont_send_or_sync: PhantomData, + id, }) } diff --git a/components/script/dom/document.rs b/components/script/dom/document.rs index 852a12fc7c5..02bdd343d89 100644 --- a/components/script/dom/document.rs +++ b/components/script/dom/document.rs @@ -21,9 +21,7 @@ use base::id::WebViewId; use canvas_traits::canvas::CanvasId; use canvas_traits::webgl::{self, WebGLContextId, WebGLMsg}; use chrono::Local; -use constellation_traits::{ - AnimationTickType, NavigationHistoryBehavior, ScriptToConstellationMessage, -}; +use constellation_traits::{NavigationHistoryBehavior, ScriptToConstellationMessage}; use content_security_policy::{self as csp, CspList, PolicyDisposition}; use cookie::Cookie; use cssparser::match_ignore_ascii_case; @@ -516,10 +514,6 @@ pub(crate) struct Document { pending_input_events: DomRefCell<Vec<ConstellationInputEvent>>, /// The index of the last mouse move event in the pending compositor events queue. mouse_move_event_index: DomRefCell<Option<usize>>, - /// Pending animation ticks, to be handled at the next rendering opportunity. - #[no_trace] - #[ignore_malloc_size_of = "AnimationTickType contains data from an outside crate"] - pending_animation_ticks: DomRefCell<AnimationTickType>, /// <https://drafts.csswg.org/resize-observer/#dom-document-resizeobservers-slot> /// /// Note: we are storing, but never removing, resize observers. @@ -2397,10 +2391,6 @@ impl Document { pub(crate) fn run_the_animation_frame_callbacks(&self, can_gc: CanGc) { let _realm = enter_realm(self); - self.pending_animation_ticks - .borrow_mut() - .remove(AnimationTickType::REQUEST_ANIMATION_FRAME); - self.running_animation_callbacks.set(true); let was_faking_animation_frames = self.is_faking_animation_frames(); let timing = self.global().performance().Now(); @@ -3916,7 +3906,6 @@ impl Document { image_animation_manager: DomRefCell::new(ImageAnimationManager::new()), dirty_root: Default::default(), declarative_refresh: Default::default(), - pending_animation_ticks: Default::default(), pending_input_events: Default::default(), mouse_move_event_index: Default::default(), resize_observers: Default::default(), @@ -4689,18 +4678,6 @@ impl Document { .collect() } - /// Note a pending animation tick, to be processed at the next `update_the_rendering` task. - pub(crate) fn note_pending_animation_tick(&self, tick_type: AnimationTickType) { - self.pending_animation_ticks.borrow_mut().extend(tick_type); - } - - /// Whether this document has received an animation tick for rafs. - pub(crate) fn has_received_raf_tick(&self) -> bool { - self.pending_animation_ticks - .borrow() - .contains(AnimationTickType::REQUEST_ANIMATION_FRAME) - } - pub(crate) fn advance_animation_timeline_for_testing(&self, delta: f64) { self.animation_timeline.borrow_mut().advance_specific(delta); let current_timeline_value = self.current_animation_timeline_value(); @@ -6437,10 +6414,7 @@ impl FakeRequestAnimationFrameCallback { pub(crate) fn invoke(self, can_gc: CanGc) { // TODO: Once there is a more generic mechanism to trigger `update_the_rendering` when // not driven by the compositor, it should be used here. - self.document - .root() - .note_pending_animation_tick(AnimationTickType::REQUEST_ANIMATION_FRAME); - with_script_thread(|script_thread| script_thread.update_the_rendering(false, can_gc)) + with_script_thread(|script_thread| script_thread.update_the_rendering(true, can_gc)) } } diff --git a/components/script/messaging.rs b/components/script/messaging.rs index 808b338e709..7d0b7aabe05 100644 --- a/components/script/messaging.rs +++ b/components/script/messaging.rs @@ -73,7 +73,7 @@ impl MixedMessage { ScriptThreadMessage::RemoveHistoryStates(id, ..) => Some(*id), ScriptThreadMessage::FocusIFrame(id, ..) => Some(*id), ScriptThreadMessage::WebDriverScriptCommand(id, ..) => Some(*id), - ScriptThreadMessage::TickAllAnimations(id, ..) => Some(*id), + ScriptThreadMessage::TickAllAnimations(..) => None, ScriptThreadMessage::WebFontLoaded(id, ..) => Some(*id), ScriptThreadMessage::DispatchIFrameLoadEvent { target: _, diff --git a/components/script/script_thread.rs b/components/script/script_thread.rs index 231de76df1d..07310073949 100644 --- a/components/script/script_thread.rs +++ b/components/script/script_thread.rs @@ -1147,14 +1147,6 @@ impl ScriptThread { return; } - // Run rafs for all pipeline, if a raf tick was received for any. - // This ensures relative ordering of rafs between parent doc and iframes. - let should_run_rafs = self - .documents - .borrow() - .iter() - .any(|(_, doc)| doc.is_fully_active() && doc.has_received_raf_tick()); - let any_animations_running = self.documents.borrow().iter().any(|(_, document)| { document.is_fully_active() && document.animations().running_animation_count() != 0 }); @@ -1242,7 +1234,7 @@ impl ScriptThread { // > 14. For each doc of docs, run the animation frame callbacks for doc, passing // > in the relative high resolution time given frameTimestamp and doc's // > relevant global object as the timestamp. - if should_run_rafs { + if requested_by_compositor { document.run_the_animation_frame_callbacks(can_gc); } @@ -1421,18 +1413,9 @@ impl ScriptThread { self.handle_viewport(id, rect); }), MixedMessage::FromConstellation(ScriptThreadMessage::TickAllAnimations( - pipeline_id, - tick_type, + _webviews, )) => { - if let Some(document) = self.documents.borrow().find_document(pipeline_id) { - document.note_pending_animation_tick(tick_type); - compositor_requested_update_the_rendering = true; - } else { - warn!( - "Trying to note pending animation tick for closed pipeline {}.", - pipeline_id - ) - } + compositor_requested_update_the_rendering = true; }, MixedMessage::FromConstellation(ScriptThreadMessage::SendInputEvent(id, event)) => { self.handle_input_event(id, event) diff --git a/components/shared/constellation/lib.rs b/components/shared/constellation/lib.rs index 548e17b532c..b3d4fe525a1 100644 --- a/components/shared/constellation/lib.rs +++ b/components/shared/constellation/lib.rs @@ -18,7 +18,6 @@ use std::time::Duration; use base::Epoch; use base::cross_process_instant::CrossProcessInstant; use base::id::{MessagePortId, PipelineId, WebViewId}; -use bitflags::bitflags; use embedder_traits::{ CompositorHitTestResult, Cursor, InputEvent, MediaSessionActionType, Theme, ViewportDetails, WebDriverCommandMsg, @@ -57,8 +56,9 @@ pub enum EmbedderToConstellationMessage { ChangeViewportDetails(WebViewId, ViewportDetails, WindowSizeType), /// Inform the constellation of a theme change. ThemeChange(Theme), - /// Requests that the constellation instruct layout to begin a new tick of the animation. - TickAnimation(PipelineId, AnimationTickType), + /// Requests that the constellation instruct script/layout to try to layout again and tick + /// animations. + TickAnimation(Vec<WebViewId>), /// Dispatch a webdriver command WebDriverCommand(WebDriverCommandMsg), /// Reload a top-level browsing context. @@ -130,17 +130,6 @@ pub enum WindowSizeType { Resize, } -bitflags! { - #[derive(Debug, Default, Deserialize, Serialize)] - /// Specifies if rAF should be triggered and/or CSS Animations and Transitions. - pub struct AnimationTickType: u8 { - /// Trigger a call to requestAnimationFrame. - const REQUEST_ANIMATION_FRAME = 0b001; - /// Trigger restyles for CSS Animations and Transitions. - const CSS_ANIMATIONS_AND_TRANSITIONS = 0b010; - } -} - /// The scroll state of a stacking context. #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)] pub struct ScrollState { diff --git a/components/shared/script/lib.rs b/components/shared/script/lib.rs index a39be739fd5..7323907cba3 100644 --- a/components/shared/script/lib.rs +++ b/components/shared/script/lib.rs @@ -20,7 +20,7 @@ use bluetooth_traits::BluetoothRequest; use canvas_traits::webgl::WebGLPipeline; use compositing_traits::CrossProcessCompositorApi; use constellation_traits::{ - AnimationTickType, LoadData, NavigationHistoryBehavior, ScriptToConstellationChan, ScrollState, + LoadData, NavigationHistoryBehavior, ScriptToConstellationChan, ScrollState, StructuredSerializedData, WindowSizeType, }; use crossbeam_channel::{RecvTimeoutError, Sender}; @@ -195,7 +195,7 @@ pub enum ScriptThreadMessage { /// Passes a webdriver command to the script thread for execution WebDriverScriptCommand(PipelineId, WebDriverScriptCommand), /// Notifies script thread that all animations are done - TickAllAnimations(PipelineId, AnimationTickType), + TickAllAnimations(Vec<WebViewId>), /// Notifies the script thread that a new Web font has been loaded, and thus the page should be /// reflowed. WebFontLoaded(PipelineId, bool /* success */), |