diff --git a/components/constellation/pipeline.rs b/components/constellation/pipeline.rs index 77cc70f8df2d..e520583cd7fb 100644 --- a/components/constellation/pipeline.rs +++ b/components/constellation/pipeline.rs @@ -488,7 +488,8 @@ impl UnprivilegedPipelineContent { let image_cache = Arc::new(ImageCacheImpl::new(self.webrender_api_sender.create_api())); let paint_time_metrics = PaintTimeMetrics::new(self.id, self.time_profiler_chan.clone(), - self.layout_to_constellation_chan.clone()); + self.layout_to_constellation_chan.clone(), + self.script_chan.clone()); let layout_pair = STF::create(InitialScriptState { id: self.id, browsing_context_id: self.browsing_context_id, diff --git a/components/metrics/lib.rs b/components/metrics/lib.rs index eb37919f8720..6a1259ff173d 100644 --- a/components/metrics/lib.rs +++ b/components/metrics/lib.rs @@ -18,7 +18,7 @@ use ipc_channel::ipc::IpcSender; use msg::constellation_msg::PipelineId; use profile_traits::time::{ProfilerChan, ProfilerCategory, send_profile_data}; use profile_traits::time::TimerMetadata; -use script_traits::LayoutMsg; +use script_traits::{ConstellationControlMsg, LayoutMsg, PaintMetricType}; use servo_config::opts; use std::cell::{Cell, RefCell}; use std::collections::HashMap; @@ -28,8 +28,10 @@ pub trait ProfilerMetadataFactory { } macro_rules! make_time_setter( - ( $attr:ident, $func:ident, $category:ident, $label:expr ) => ( - fn $func(&self, profiler_metadata: Option, paint_time: f64) { + ( $attr:ident, $func:ident, $category:ident, $label:expr, $metric_type:path ) => ( + fn $func(&self, + profiler_metadata: Option, + paint_time: f64) { if self.$attr.get().is_some() { return; } @@ -45,6 +47,14 @@ macro_rules! make_time_setter( let time = paint_time - navigation_start; self.$attr.set(Some(time)); + // Queue performance observer notification. + let msg = ConstellationControlMsg::PaintMetric(self.pipeline_id, + $metric_type, + time); + if let Err(e) = self.script_chan.send(msg) { + warn!("Sending paint metric to script thread failed ({}).", e); + } + // Send the metric to the time profiler. send_profile_data(ProfilerCategory::$category, profiler_metadata, @@ -67,12 +77,14 @@ pub struct PaintTimeMetrics { pipeline_id: PipelineId, time_profiler_chan: ProfilerChan, constellation_chan: IpcSender, + script_chan: IpcSender, } impl PaintTimeMetrics { pub fn new(pipeline_id: PipelineId, time_profiler_chan: ProfilerChan, - constellation_chan: IpcSender) + constellation_chan: IpcSender, + script_chan: IpcSender) -> PaintTimeMetrics { PaintTimeMetrics { pending_metrics: RefCell::new(HashMap::new()), @@ -82,6 +94,7 @@ impl PaintTimeMetrics { pipeline_id, time_profiler_chan, constellation_chan, + script_chan, } } @@ -91,10 +104,12 @@ impl PaintTimeMetrics { make_time_setter!(first_paint, set_first_paint, TimeToFirstPaint, - "first-paint"); + "first-paint", + PaintMetricType::FirstPaint); make_time_setter!(first_contentful_paint, set_first_contentful_paint, TimeToFirstContentfulPaint, - "first-contentful-paint"); + "first-contentful-paint", + PaintMetricType::FirstContentfulPaint); pub fn maybe_observe_paint_time(&self, profiler_metadata_factory: &T, diff --git a/components/script/dom/mod.rs b/components/script/dom/mod.rs index 448d11ad1c56..db065419b2b0 100644 --- a/components/script/dom/mod.rs +++ b/components/script/dom/mod.rs @@ -401,6 +401,7 @@ pub mod performance; pub mod performanceentry; pub mod performanceobserver; pub mod performanceobserverentrylist; +pub mod performancepainttiming; pub mod performancetiming; pub mod permissions; pub mod permissionstatus; diff --git a/components/script/dom/performanceentry.rs b/components/script/dom/performanceentry.rs index 7de1116c320e..2c6fef87db01 100644 --- a/components/script/dom/performanceentry.rs +++ b/components/script/dom/performanceentry.rs @@ -21,10 +21,10 @@ pub struct PerformanceEntry { } impl PerformanceEntry { - fn new_inherited(name: DOMString, - entry_type: DOMString, - start_time: f64, - duration: f64) -> PerformanceEntry { + pub fn new_inherited(name: DOMString, + entry_type: DOMString, + start_time: f64, + duration: f64) -> PerformanceEntry { PerformanceEntry { reflector_: Reflector::new(), name, diff --git a/components/script/dom/performanceobserver.rs b/components/script/dom/performanceobserver.rs index e0d2e49a57b2..087cb44cd096 100644 --- a/components/script/dom/performanceobserver.rs +++ b/components/script/dom/performanceobserver.rs @@ -27,7 +27,7 @@ const VALID_ENTRY_TYPES: &'static [&'static str] = &[ // "measure", XXX User Timing API // "resource", XXX Resource Timing API // "server", XXX Server Timing API - // "paint", XXX Paint Timing API + "paint", // Paint Timing API ]; #[dom_struct] diff --git a/components/script/dom/performancepainttiming.rs b/components/script/dom/performancepainttiming.rs new file mode 100644 index 000000000000..a4e4369ccebc --- /dev/null +++ b/components/script/dom/performancepainttiming.rs @@ -0,0 +1,41 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use dom::bindings::codegen::Bindings::PerformancePaintTimingBinding; +use dom::bindings::js::Root; +use dom::bindings::reflector::reflect_dom_object; +use dom::bindings::str::DOMString; +use dom::globalscope::GlobalScope; +use dom::performanceentry::PerformanceEntry; +use dom_struct::dom_struct; +use script_traits::PaintMetricType; + +#[dom_struct] +pub struct PerformancePaintTiming { + entry: PerformanceEntry, +} + +impl PerformancePaintTiming { + fn new_inherited(metric_type: PaintMetricType, start_time: f64) + -> PerformancePaintTiming { + let name = match metric_type { + PaintMetricType::FirstPaint => DOMString::from("first-paint"), + PaintMetricType::FirstContentfulPaint => DOMString::from("first-contentful-paint"), + }; + PerformancePaintTiming { + entry: PerformanceEntry::new_inherited(name, + DOMString::from("paint"), + start_time, + 0.) + } + } + + #[allow(unrooted_must_root)] + pub fn new(global: &GlobalScope, + metric_type: PaintMetricType, + start_time: f64) -> Root { + let entry = PerformancePaintTiming::new_inherited(metric_type, start_time); + reflect_dom_object(box entry, global, PerformancePaintTimingBinding::Wrap) + } +} diff --git a/components/script/dom/webidls/PerformancePaintTiming.webidl b/components/script/dom/webidls/PerformancePaintTiming.webidl new file mode 100644 index 000000000000..6265dc7d292b --- /dev/null +++ b/components/script/dom/webidls/PerformancePaintTiming.webidl @@ -0,0 +1,11 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + * + * The origin of this IDL file is + * https://wicg.github.io/paint-timing/#sec-PerformancePaintTiming + */ + +[Exposed=(Window,Worker)] +interface PerformancePaintTiming : PerformanceEntry { +}; diff --git a/components/script/script_thread.rs b/components/script/script_thread.rs index d4911627a6f2..6bba0824270d 100644 --- a/components/script/script_thread.rs +++ b/components/script/script_thread.rs @@ -49,6 +49,8 @@ use dom::htmlanchorelement::HTMLAnchorElement; use dom::htmliframeelement::{HTMLIFrameElement, NavigationType}; use dom::mutationobserver::MutationObserver; use dom::node::{Node, NodeDamage, window_from_node, from_untrusted_node_address}; +use dom::performanceentry::PerformanceEntry; +use dom::performancepainttiming::PerformancePaintTiming; use dom::serviceworker::TrustedServiceWorkerAddress; use dom::serviceworkerregistration::ServiceWorkerRegistration; use dom::servoparser::{ParserContext, ServoParser}; @@ -85,7 +87,7 @@ use profile_traits::time::{self, ProfilerCategory, profile}; use script_layout_interface::message::{self, Msg, NewLayoutThreadInfo, ReflowQueryType}; use script_runtime::{CommonScriptMsg, ScriptChan, ScriptThreadEventCategory}; use script_runtime::{ScriptPort, StackRootTLS, get_reports, new_rt_and_cx}; -use script_traits::{CompositorEvent, ConstellationControlMsg}; +use script_traits::{CompositorEvent, ConstellationControlMsg, PaintMetricType}; use script_traits::{DocumentActivity, DiscardBrowsingContext, EventResult}; use script_traits::{InitialScriptState, LayoutMsg, LoadData, MouseButton, MouseEventType, MozBrowserEvent}; use script_traits::{NewLayoutInfo, ScriptToConstellationChan, ScriptMsg, UpdatePipelineIdReason}; @@ -1261,6 +1263,8 @@ impl ScriptThread { self.handle_exit_pipeline_msg(pipeline_id, discard_browsing_context), ConstellationControlMsg::WebVREvents(pipeline_id, events) => self.handle_webvr_events(pipeline_id, events), + ConstellationControlMsg::PaintMetric(pipeline_id, metric_type, metric_value) => + self.handle_paint_metric(pipeline_id, metric_type, metric_value), msg @ ConstellationControlMsg::AttachLayout(..) | msg @ ConstellationControlMsg::Viewport(..) | msg @ ConstellationControlMsg::SetScrollState(..) | @@ -1482,7 +1486,8 @@ impl ScriptThread { layout_threads: layout_threads, paint_time_metrics: PaintTimeMetrics::new(new_pipeline_id, self.time_profiler_chan.clone(), - self.layout_to_constellation_chan.clone()), + self.layout_to_constellation_chan.clone(), + self.control_chan.clone()), }); // Pick a layout thread, any layout thread @@ -2485,6 +2490,19 @@ impl ScriptThread { } } + fn handle_paint_metric(&self, + pipeline_id: PipelineId, + metric_type: PaintMetricType, + metric_value: f64) { + let window = self.documents.borrow().find_window(pipeline_id); + if let Some(window) = window { + let entry = PerformancePaintTiming::new(&window.upcast::(), + metric_type, metric_value); + window.Performance().queue_entry(&entry.upcast::(), + true /* buffer performance entry */); + } + } + pub fn enqueue_microtask(job: Microtask) { SCRIPT_THREAD_ROOT.with(|root| { let script_thread = unsafe { &*root.get().unwrap() }; diff --git a/components/script_traits/lib.rs b/components/script_traits/lib.rs index 3b5a47a3ef77..29d4b96d99eb 100644 --- a/components/script_traits/lib.rs +++ b/components/script_traits/lib.rs @@ -220,6 +220,15 @@ pub enum DocumentActivity { FullyActive, } +/// The type of recorded paint metric. +#[derive(Deserialize, Serialize)] +pub enum PaintMetricType { + /// Time to First Paint type. + FirstPaint, + /// Time to First Contentful Paint type. + FirstContentfulPaint, +} + /// The reason why the pipeline id of an iframe is being updated. #[derive(Copy, Clone, PartialEq, Eq, Hash, HeapSizeOf, Debug, Deserialize, Serialize)] pub enum UpdatePipelineIdReason { @@ -300,7 +309,9 @@ pub enum ConstellationControlMsg { /// Reload the given page. Reload(PipelineId), /// Notifies the script thread of WebVR events. - WebVREvents(PipelineId, Vec) + WebVREvents(PipelineId, Vec), + /// Notifies the script thread about a new recorded paint metric. + PaintMetric(PipelineId, PaintMetricType, f64), } impl fmt::Debug for ConstellationControlMsg { @@ -334,6 +345,7 @@ impl fmt::Debug for ConstellationControlMsg { ReportCSSError(..) => "ReportCSSError", Reload(..) => "Reload", WebVREvents(..) => "WebVREvents", + PaintMetric(..) => "PaintMetric", }; write!(formatter, "ConstellationMsg::{}", variant) } diff --git a/python/tidy/servo_tidy/tidy.py b/python/tidy/servo_tidy/tidy.py index 3bf6176de442..89e6cef12969 100644 --- a/python/tidy/servo_tidy/tidy.py +++ b/python/tidy/servo_tidy/tidy.py @@ -79,6 +79,7 @@ "//heycam.github.io/webidl", "//webbluetoothcg.github.io/web-bluetooth/", "//svgwg.org/svg2-draft", + "//wicg.github.io", # Not a URL "// This interface is entirely internal to Servo, and should not be" + " accessible to\n// web pages." diff --git a/tests/unit/metrics/paint_time.rs b/tests/unit/metrics/paint_time.rs index 72f293d97070..8fea5a32bb98 100644 --- a/tests/unit/metrics/paint_time.rs +++ b/tests/unit/metrics/paint_time.rs @@ -30,7 +30,8 @@ fn test_paint_metrics_construction() { let (sender, _) = ipc::channel().unwrap(); let profiler_chan = ProfilerChan(sender); let (layout_sender, _) = ipc::channel().unwrap(); - let paint_time_metrics = PaintTimeMetrics::new(pipeline_id, profiler_chan, layout_sender); + let (script_sender, _) = ipc::channel().unwrap(); + let paint_time_metrics = PaintTimeMetrics::new(pipeline_id, profiler_chan, layout_sender, script_sender); assert_eq!(paint_time_metrics.get_navigation_start(), None, "navigation start is None"); assert_eq!(paint_time_metrics.get_first_paint(), None, "first paint is None"); assert_eq!(paint_time_metrics.get_first_contentful_paint(), None, "first contentful paint is None"); @@ -44,7 +45,8 @@ fn test_common(display_list: &DisplayList, epoch: Epoch) -> PaintTimeMetrics { let (sender, _) = ipc::channel().unwrap(); let profiler_chan = ProfilerChan(sender); let (layout_sender, _) = ipc::channel().unwrap(); - let mut paint_time_metrics = PaintTimeMetrics::new(pipeline_id, profiler_chan, layout_sender); + let (script_sender, _) = ipc::channel().unwrap(); + let mut paint_time_metrics = PaintTimeMetrics::new(pipeline_id, profiler_chan, layout_sender, script_sender); let dummy_profiler_metadata_factory = DummyProfilerMetadataFactory {}; paint_time_metrics.maybe_observe_paint_time(&dummy_profiler_metadata_factory, diff --git a/tests/wpt/mozilla/meta/MANIFEST.json b/tests/wpt/mozilla/meta/MANIFEST.json index 7623a1a6a46c..91a2addff610 100644 --- a/tests/wpt/mozilla/meta/MANIFEST.json +++ b/tests/wpt/mozilla/meta/MANIFEST.json @@ -14444,6 +14444,12 @@ {} ] ], + "mozilla/paint_timing.html": [ + [ + "/_mozilla/mozilla/paint_timing.html", + {} + ] + ], "mozilla/parentNode_querySelector.html": [ [ "/_mozilla/mozilla/parentNode_querySelector.html", @@ -27286,7 +27292,7 @@ "testharness" ], "mozilla/interfaces.html": [ - "7ac46204fb780c96344f166d34d0fb888c9e25c4", + "48028c5a9b401bab52a155c8820140a65dbc2f11", "testharness" ], "mozilla/interfaces.js": [ @@ -27294,7 +27300,7 @@ "support" ], "mozilla/interfaces.worker.js": [ - "1474c6500ce1c4aef99d200dae5407324ddbdd4a", + "5fb0da8a22a5afe00d1232c700720c080f5dff44", "testharness" ], "mozilla/iterable.html": [ @@ -27525,6 +27531,10 @@ "1e0066636c757e68b13a4a5271d4184232c51e34", "testharness" ], + "mozilla/paint_timing.html": [ + "8cf0400c36168b57c386253d647b61013896d325", + "testharness" + ], "mozilla/parentNode_querySelector.html": [ "b9c5eee0b0c895109141a48676348a8a8ab5e3cc", "testharness" diff --git a/tests/wpt/mozilla/tests/mozilla/interfaces.html b/tests/wpt/mozilla/tests/mozilla/interfaces.html index 90c4531467bb..be135cacb653 100644 --- a/tests/wpt/mozilla/tests/mozilla/interfaces.html +++ b/tests/wpt/mozilla/tests/mozilla/interfaces.html @@ -162,6 +162,7 @@ "PerformanceEntry", "PerformanceObserver", "PerformanceObserverEntryList", + "PerformancePaintTiming", "PerformanceTiming", "Plugin", "PluginArray", diff --git a/tests/wpt/mozilla/tests/mozilla/interfaces.worker.js b/tests/wpt/mozilla/tests/mozilla/interfaces.worker.js index e410bb863b0d..b8f110130be6 100644 --- a/tests/wpt/mozilla/tests/mozilla/interfaces.worker.js +++ b/tests/wpt/mozilla/tests/mozilla/interfaces.worker.js @@ -33,10 +33,11 @@ test_interfaces([ "ImageData", "MessageEvent", "Performance", - "PerformanceTiming", "PerformanceEntry", "PerformanceObserver", "PerformanceObserverEntryList", + "PerformancePaintTiming", + "PerformanceTiming", "ProgressEvent", "Request", "Response", diff --git a/tests/wpt/mozilla/tests/mozilla/paint_timing.html b/tests/wpt/mozilla/tests/mozilla/paint_timing.html new file mode 100644 index 000000000000..6bf5e7aae45d --- /dev/null +++ b/tests/wpt/mozilla/tests/mozilla/paint_timing.html @@ -0,0 +1,18 @@ + + +Paint Timing API + + +