1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5 use CompositionPipeline;
6 use SendableFrameTree;
7 use compositor_thread::{CompositorProxy, CompositorReceiver};
8 use compositor_thread::{InitialCompositorState, Msg};
9 use euclid::{TypedPoint2D, TypedVector2D, TypedScale};
10 use gfx_traits::Epoch;
11 use gleam::gl;
12 use image::{DynamicImage, ImageFormat, RgbImage};
13 use ipc_channel::ipc::{self, IpcSharedMemory};
14 use libc::c_void;
15 use msg::constellation_msg::{PipelineId, PipelineIndex, PipelineNamespaceId};
16 use net_traits::image::base::{Image, PixelFormat};
17 use nonzero::NonZero;
18 use profile_traits::time::{self, ProfilerCategory, profile};
19 use script_traits::{AnimationState, AnimationTickType, ConstellationMsg, LayoutControlMsg};
20 use script_traits::{MouseButton, MouseEventType, ScrollState, TouchEventType, TouchId};
21 use script_traits::{UntrustedNodeAddress, WindowSizeData, WindowSizeType};
22 use script_traits::CompositorEvent::{MouseMoveEvent, MouseButtonEvent, TouchEvent};
23 use servo_config::opts;
24 use servo_geometry::DeviceIndependentPixel;
25 use std::collections::HashMap;
26 use std::fs::File;
27 use std::rc::Rc;
28 use std::sync::mpsc::Sender;
29 use std::time::{Duration, Instant};
30 use style_traits::{CSSPixel, DevicePixel, PinchZoomFactor};
31 use style_traits::cursor::CursorKind;
32 use style_traits::viewport::ViewportConstraints;
33 use time::{precise_time_ns, precise_time_s};
34 use touch::{TouchHandler, TouchAction};
35 use webrender;
36 use webrender_api::{self, DeviceUintRect, DeviceUintSize, HitTestFlags, HitTestResult};
37 use webrender_api::{LayoutVector2D, ScrollEventPhase, ScrollLocation};
38 use windowing::{self, MouseWindowEvent, WebRenderDebugOption, WindowMethods};
39
40 #[derive(Debug, PartialEq)]
41 enum UnableToComposite {
42 WindowUnprepared,
43 NotReadyToPaintImage(NotReadyToPaint),
44 }
45
46 #[derive(Debug, PartialEq)]
47 enum NotReadyToPaint {
48 AnimationsActive,
49 JustNotifiedConstellation,
50 WaitingOnConstellation,
51 }
52
53 // Default viewport constraints
54 const MAX_ZOOM: f32 = 8.0;
55 const MIN_ZOOM: f32 = 0.1;
56
57 trait ConvertPipelineIdFromWebRender {
from_webrender(&self) -> PipelineId58 fn from_webrender(&self) -> PipelineId;
59 }
60
61 impl ConvertPipelineIdFromWebRender for webrender_api::PipelineId {
from_webrender(&self) -> PipelineId62 fn from_webrender(&self) -> PipelineId {
63 PipelineId {
64 namespace_id: PipelineNamespaceId(self.0),
65 index: PipelineIndex(NonZero::new(self.1).expect("Webrender pipeline zero?")),
66 }
67 }
68 }
69
70 /// Holds the state when running reftests that determines when it is
71 /// safe to save the output image.
72 #[derive(Clone, Copy, Debug, PartialEq)]
73 enum ReadyState {
74 Unknown,
75 WaitingForConstellationReply,
76 ReadyToSaveImage,
77 }
78
79 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
80 struct FrameTreeId(u32);
81
82 impl FrameTreeId {
next(&mut self)83 pub fn next(&mut self) {
84 self.0 += 1;
85 }
86 }
87
88 /// One pixel in layer coordinate space.
89 ///
90 /// This unit corresponds to a "pixel" in layer coordinate space, which after scaling and
91 /// transformation becomes a device pixel.
92 #[derive(Clone, Copy, Debug)]
93 enum LayerPixel {}
94
95 /// NB: Never block on the constellation, because sometimes the constellation blocks on us.
96 pub struct IOCompositor<Window: WindowMethods> {
97 /// The application window.
98 pub window: Rc<Window>,
99
100 /// The port on which we receive messages.
101 port: CompositorReceiver,
102
103 /// The root pipeline.
104 root_pipeline: Option<CompositionPipeline>,
105
106 /// Tracks details about each active pipeline that the compositor knows about.
107 pipeline_details: HashMap<PipelineId, PipelineDetails>,
108
109 /// The scene scale, to allow for zooming and high-resolution painting.
110 scale: TypedScale<f32, LayerPixel, DevicePixel>,
111
112 /// The size of the rendering area.
113 frame_size: DeviceUintSize,
114
115 /// The position and size of the window within the rendering area.
116 window_rect: DeviceUintRect,
117
118 /// "Mobile-style" zoom that does not reflow the page.
119 viewport_zoom: PinchZoomFactor,
120
121 /// Viewport zoom constraints provided by @viewport.
122 min_viewport_zoom: Option<PinchZoomFactor>,
123 max_viewport_zoom: Option<PinchZoomFactor>,
124
125 /// "Desktop-style" zoom that resizes the viewport to fit the window.
126 page_zoom: TypedScale<f32, CSSPixel, DeviceIndependentPixel>,
127
128 /// The device pixel ratio for this window.
129 scale_factor: TypedScale<f32, DeviceIndependentPixel, DevicePixel>,
130
131 /// The type of composition to perform
132 composite_target: CompositeTarget,
133
134 /// Tracks whether we should composite this frame.
135 composition_request: CompositionRequest,
136
137 /// Tracks whether we are in the process of shutting down, or have shut down and should close
138 /// the compositor.
139 pub shutdown_state: ShutdownState,
140
141 /// Tracks the last composite time.
142 last_composite_time: u64,
143
144 /// Tracks whether the zoom action has happened recently.
145 zoom_action: bool,
146
147 /// The time of the last zoom action has started.
148 zoom_time: f64,
149
150 /// The current frame tree ID (used to reject old paint buffers)
151 frame_tree_id: FrameTreeId,
152
153 /// The channel on which messages can be sent to the constellation.
154 constellation_chan: Sender<ConstellationMsg>,
155
156 /// The channel on which messages can be sent to the time profiler.
157 time_profiler_chan: time::ProfilerChan,
158
159 /// Touch input state machine
160 touch_handler: TouchHandler,
161
162 /// Pending scroll/zoom events.
163 pending_scroll_zoom_events: Vec<ScrollZoomEvent>,
164
165 /// Whether we're waiting on a recomposite after dispatching a scroll.
166 waiting_for_results_of_scroll: bool,
167
168 /// Used by the logic that determines when it is safe to output an
169 /// image for the reftest framework.
170 ready_to_save_state: ReadyState,
171
172 /// Whether a scroll is in progress; i.e. whether the user's fingers are down.
173 scroll_in_progress: bool,
174
175 in_scroll_transaction: Option<Instant>,
176
177 /// The webrender renderer.
178 webrender: webrender::Renderer,
179
180 /// The active webrender document.
181 webrender_document: webrender_api::DocumentId,
182
183 /// The webrender interface, if enabled.
184 webrender_api: webrender_api::RenderApi,
185
186 /// GL functions interface (may be GL or GLES)
187 gl: Rc<gl::Gl>,
188
189 /// Map of the pending paint metrics per layout thread.
190 /// The layout thread for each specific pipeline expects the compositor to
191 /// paint frames with specific given IDs (epoch). Once the compositor paints
192 /// these frames, it records the paint time for each of them and sends the
193 /// metric to the corresponding layout thread.
194 pending_paint_metrics: HashMap<PipelineId, Epoch>,
195 }
196
197 #[derive(Clone, Copy)]
198 struct ScrollZoomEvent {
199 /// Change the pinch zoom level by this factor
200 magnification: f32,
201 /// Scroll by this offset, or to Start or End
202 scroll_location: ScrollLocation,
203 /// Apply changes to the frame at this location
204 cursor: TypedPoint2D<i32, DevicePixel>,
205 /// The scroll event phase.
206 phase: ScrollEventPhase,
207 /// The number of OS events that have been coalesced together into this one event.
208 event_count: u32,
209 }
210
211 #[derive(Debug, PartialEq)]
212 enum CompositionRequest {
213 NoCompositingNecessary,
214 CompositeNow(CompositingReason),
215 }
216
217 #[derive(Clone, Copy, Debug, PartialEq)]
218 pub enum ShutdownState {
219 NotShuttingDown,
220 ShuttingDown,
221 FinishedShuttingDown,
222 }
223
224 struct PipelineDetails {
225 /// The pipeline associated with this PipelineDetails object.
226 pipeline: Option<CompositionPipeline>,
227
228 /// Whether animations are running
229 animations_running: bool,
230
231 /// Whether there are animation callbacks
232 animation_callbacks_running: bool,
233
234 /// Whether this pipeline is visible
235 visible: bool,
236 }
237
238 impl PipelineDetails {
new() -> PipelineDetails239 fn new() -> PipelineDetails {
240 PipelineDetails {
241 pipeline: None,
242 animations_running: false,
243 animation_callbacks_running: false,
244 visible: true,
245 }
246 }
247 }
248
249 #[derive(Clone, Copy, Debug, PartialEq)]
250 enum CompositeTarget {
251 /// Normal composition to a window
252 Window,
253
254 /// Compose as normal, but also return a PNG of the composed output
255 WindowAndPng,
256
257 /// Compose to a PNG, write it to disk, and then exit the browser (used for reftests)
258 PngFile
259 }
260
261 struct RenderTargetInfo {
262 framebuffer_ids: Vec<gl::GLuint>,
263 renderbuffer_ids: Vec<gl::GLuint>,
264 texture_ids: Vec<gl::GLuint>,
265 }
266
267 impl RenderTargetInfo {
empty() -> RenderTargetInfo268 fn empty() -> RenderTargetInfo {
269 RenderTargetInfo {
270 framebuffer_ids: Vec::new(),
271 renderbuffer_ids: Vec::new(),
272 texture_ids: Vec::new(),
273 }
274 }
275 }
276
initialize_png(gl: &gl::Gl, width: usize, height: usize) -> RenderTargetInfo277 fn initialize_png(gl: &gl::Gl, width: usize, height: usize) -> RenderTargetInfo {
278 let framebuffer_ids = gl.gen_framebuffers(1);
279 gl.bind_framebuffer(gl::FRAMEBUFFER, framebuffer_ids[0]);
280
281 let texture_ids = gl.gen_textures(1);
282 gl.bind_texture(gl::TEXTURE_2D, texture_ids[0]);
283
284 gl.tex_image_2d(gl::TEXTURE_2D, 0, gl::RGB as gl::GLint, width as gl::GLsizei,
285 height as gl::GLsizei, 0, gl::RGB, gl::UNSIGNED_BYTE, None);
286 gl.tex_parameter_i(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::NEAREST as gl::GLint);
287 gl.tex_parameter_i(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::NEAREST as gl::GLint);
288
289 gl.framebuffer_texture_2d(gl::FRAMEBUFFER, gl::COLOR_ATTACHMENT0, gl::TEXTURE_2D,
290 texture_ids[0], 0);
291
292 gl.bind_texture(gl::TEXTURE_2D, 0);
293
294 let renderbuffer_ids = gl.gen_renderbuffers(1);
295 let depth_rb = renderbuffer_ids[0];
296 gl.bind_renderbuffer(gl::RENDERBUFFER, depth_rb);
297 gl.renderbuffer_storage(gl::RENDERBUFFER,
298 gl::DEPTH_COMPONENT24,
299 width as gl::GLsizei,
300 height as gl::GLsizei);
301 gl.framebuffer_renderbuffer(gl::FRAMEBUFFER,
302 gl::DEPTH_ATTACHMENT,
303 gl::RENDERBUFFER,
304 depth_rb);
305
306 RenderTargetInfo {
307 framebuffer_ids: framebuffer_ids,
308 renderbuffer_ids: renderbuffer_ids,
309 texture_ids: texture_ids,
310 }
311 }
312
313 #[derive(Clone)]
314 pub struct RenderNotifier {
315 compositor_proxy: CompositorProxy,
316 }
317
318 impl RenderNotifier {
new(compositor_proxy: CompositorProxy) -> RenderNotifier319 pub fn new(compositor_proxy: CompositorProxy) -> RenderNotifier {
320 RenderNotifier {
321 compositor_proxy: compositor_proxy,
322 }
323 }
324 }
325
326 impl webrender_api::RenderNotifier for RenderNotifier {
clone(&self) -> Box<webrender_api::RenderNotifier>327 fn clone(&self) -> Box<webrender_api::RenderNotifier> {
328 Box::new(RenderNotifier::new(self.compositor_proxy.clone()))
329 }
330
wake_up(&self)331 fn wake_up(&self) {
332 self.compositor_proxy.recomposite(CompositingReason::NewWebRenderFrame);
333 }
334
new_document_ready( &self, _document_id: webrender_api::DocumentId, scrolled: bool, composite_needed: bool, )335 fn new_document_ready(
336 &self,
337 _document_id: webrender_api::DocumentId,
338 scrolled: bool,
339 composite_needed: bool,
340 ) {
341 if scrolled {
342 self.compositor_proxy.send(Msg::NewScrollFrameReady(composite_needed));
343 } else {
344 self.wake_up();
345 }
346 }
347 }
348
349 impl<Window: WindowMethods> IOCompositor<Window> {
new(window: Rc<Window>, state: InitialCompositorState) -> IOCompositor<Window>350 fn new(window: Rc<Window>, state: InitialCompositorState)
351 -> IOCompositor<Window> {
352 let frame_size = window.framebuffer_size();
353 let window_rect = window.window_rect();
354 let scale_factor = window.hidpi_factor();
355 let composite_target = match opts::get().output_file {
356 Some(_) => CompositeTarget::PngFile,
357 None => CompositeTarget::Window
358 };
359
360 IOCompositor {
361 gl: window.gl(),
362 window: window,
363 port: state.receiver,
364 root_pipeline: None,
365 pipeline_details: HashMap::new(),
366 frame_size: frame_size,
367 window_rect: window_rect,
368 scale: TypedScale::new(1.0),
369 scale_factor: scale_factor,
370 composition_request: CompositionRequest::NoCompositingNecessary,
371 touch_handler: TouchHandler::new(),
372 pending_scroll_zoom_events: Vec::new(),
373 waiting_for_results_of_scroll: false,
374 composite_target: composite_target,
375 shutdown_state: ShutdownState::NotShuttingDown,
376 page_zoom: TypedScale::new(1.0),
377 viewport_zoom: PinchZoomFactor::new(1.0),
378 min_viewport_zoom: None,
379 max_viewport_zoom: None,
380 zoom_action: false,
381 zoom_time: 0f64,
382 frame_tree_id: FrameTreeId(0),
383 constellation_chan: state.constellation_chan,
384 time_profiler_chan: state.time_profiler_chan,
385 last_composite_time: 0,
386 ready_to_save_state: ReadyState::Unknown,
387 scroll_in_progress: false,
388 in_scroll_transaction: None,
389 webrender: state.webrender,
390 webrender_document: state.webrender_document,
391 webrender_api: state.webrender_api,
392 pending_paint_metrics: HashMap::new(),
393 }
394 }
395
create(window: Rc<Window>, state: InitialCompositorState) -> IOCompositor<Window>396 pub fn create(window: Rc<Window>, state: InitialCompositorState) -> IOCompositor<Window> {
397 let mut compositor = IOCompositor::new(window, state);
398
399 // Set the size of the root layer.
400 compositor.update_zoom_transform();
401
402 // Tell the constellation about the initial window size.
403 compositor.send_window_size(WindowSizeType::Initial);
404
405 compositor
406 }
407
deinit(self)408 pub fn deinit(self) {
409 self.webrender.deinit();
410 }
411
maybe_start_shutting_down(&mut self)412 pub fn maybe_start_shutting_down(&mut self) {
413 if self.shutdown_state == ShutdownState::NotShuttingDown {
414 debug!("Shutting down the constellation for WindowEvent::Quit");
415 self.start_shutting_down();
416 }
417 }
418
start_shutting_down(&mut self)419 fn start_shutting_down(&mut self) {
420 debug!("Compositor sending Exit message to Constellation");
421 if let Err(e) = self.constellation_chan.send(ConstellationMsg::Exit) {
422 warn!("Sending exit message to constellation failed ({}).", e);
423 }
424
425 self.shutdown_state = ShutdownState::ShuttingDown;
426 }
427
finish_shutting_down(&mut self)428 fn finish_shutting_down(&mut self) {
429 debug!("Compositor received message that constellation shutdown is complete");
430
431 // Drain compositor port, sometimes messages contain channels that are blocking
432 // another thread from finishing (i.e. SetFrameTree).
433 while self.port.try_recv_compositor_msg().is_some() {}
434
435 // Tell the profiler, memory profiler, and scrolling timer to shut down.
436 if let Ok((sender, receiver)) = ipc::channel() {
437 self.time_profiler_chan.send(time::ProfilerMsg::Exit(sender));
438 let _ = receiver.recv();
439 }
440
441 self.shutdown_state = ShutdownState::FinishedShuttingDown;
442 }
443
handle_browser_message(&mut self, msg: Msg) -> bool444 fn handle_browser_message(&mut self, msg: Msg) -> bool {
445 match (msg, self.shutdown_state) {
446 (_, ShutdownState::FinishedShuttingDown) => {
447 error!("compositor shouldn't be handling messages after shutting down");
448 return false
449 }
450
451 (Msg::Exit, _) => {
452 self.start_shutting_down();
453 }
454
455 (Msg::ShutdownComplete, _) => {
456 self.finish_shutting_down();
457 return false;
458 }
459
460 (Msg::ChangeRunningAnimationsState(pipeline_id, animation_state),
461 ShutdownState::NotShuttingDown) => {
462 self.change_running_animations_state(pipeline_id, animation_state);
463 }
464
465 (Msg::SetFrameTree(frame_tree),
466 ShutdownState::NotShuttingDown) => {
467 self.set_frame_tree(&frame_tree);
468 self.send_viewport_rects();
469 }
470
471 (Msg::Recomposite(reason), ShutdownState::NotShuttingDown) => {
472 self.composition_request = CompositionRequest::CompositeNow(reason)
473 }
474
475
476 (Msg::TouchEventProcessed(result), ShutdownState::NotShuttingDown) => {
477 self.touch_handler.on_event_processed(result);
478 }
479
480 (Msg::CreatePng(reply), ShutdownState::NotShuttingDown) => {
481 let res = self.composite_specific_target(CompositeTarget::WindowAndPng);
482 if let Err(ref e) = res {
483 info!("Error retrieving PNG: {:?}", e);
484 }
485 let img = res.unwrap_or(None);
486 if let Err(e) = reply.send(img) {
487 warn!("Sending reply to create png failed ({}).", e);
488 }
489 }
490
491 (Msg::ViewportConstrained(pipeline_id, constraints),
492 ShutdownState::NotShuttingDown) => {
493 self.constrain_viewport(pipeline_id, constraints);
494 }
495
496 (Msg::IsReadyToSaveImageReply(is_ready), ShutdownState::NotShuttingDown) => {
497 assert_eq!(self.ready_to_save_state, ReadyState::WaitingForConstellationReply);
498 if is_ready {
499 self.ready_to_save_state = ReadyState::ReadyToSaveImage;
500 if opts::get().is_running_problem_test {
501 println!("ready to save image!");
502 }
503 } else {
504 self.ready_to_save_state = ReadyState::Unknown;
505 if opts::get().is_running_problem_test {
506 println!("resetting ready_to_save_state!");
507 }
508 }
509 self.composite_if_necessary(CompositingReason::Headless);
510 }
511
512 (Msg::PipelineVisibilityChanged(pipeline_id, visible), ShutdownState::NotShuttingDown) => {
513 self.pipeline_details(pipeline_id).visible = visible;
514 if visible {
515 self.process_animations();
516 }
517 }
518
519 (Msg::PipelineExited(pipeline_id, sender), _) => {
520 debug!("Compositor got pipeline exited: {:?}", pipeline_id);
521 self.remove_pipeline_root_layer(pipeline_id);
522 let _ = sender.send(());
523 }
524
525 (Msg::NewScrollFrameReady(recomposite_needed), ShutdownState::NotShuttingDown) => {
526 self.waiting_for_results_of_scroll = false;
527 if recomposite_needed {
528 self.composition_request = CompositionRequest::CompositeNow(
529 CompositingReason::NewWebRenderScrollFrame);
530 }
531 }
532
533 (Msg::Dispatch(func), ShutdownState::NotShuttingDown) => {
534 // The functions sent here right now are really dumb, so they can't panic.
535 // But if we start running more complex code here, we should really catch panic here.
536 func();
537 }
538
539 (Msg::LoadComplete(_), ShutdownState::NotShuttingDown) => {
540 // If we're painting in headless mode, schedule a recomposite.
541 if opts::get().output_file.is_some() || opts::get().exit_after_load {
542 self.composite_if_necessary(CompositingReason::Headless);
543 }
544 },
545
546 (Msg::PendingPaintMetric(pipeline_id, epoch), _) => {
547 self.pending_paint_metrics.insert(pipeline_id, epoch);
548 }
549
550 // When we are shutting_down, we need to avoid performing operations
551 // such as Paint that may crash because we have begun tearing down
552 // the rest of our resources.
553 (_, ShutdownState::ShuttingDown) => {}
554 }
555
556 true
557 }
558
559 /// Sets or unsets the animations-running flag for the given pipeline, and schedules a
560 /// recomposite if necessary.
change_running_animations_state(&mut self, pipeline_id: PipelineId, animation_state: AnimationState)561 fn change_running_animations_state(&mut self,
562 pipeline_id: PipelineId,
563 animation_state: AnimationState) {
564 match animation_state {
565 AnimationState::AnimationsPresent => {
566 let visible = self.pipeline_details(pipeline_id).visible;
567 self.pipeline_details(pipeline_id).animations_running = true;
568 if visible {
569 self.composite_if_necessary(CompositingReason::Animation);
570 }
571 }
572 AnimationState::AnimationCallbacksPresent => {
573 let visible = self.pipeline_details(pipeline_id).visible;
574 self.pipeline_details(pipeline_id).animation_callbacks_running = true;
575 if visible {
576 self.tick_animations_for_pipeline(pipeline_id);
577 }
578 }
579 AnimationState::NoAnimationsPresent => {
580 self.pipeline_details(pipeline_id).animations_running = false;
581 }
582 AnimationState::NoAnimationCallbacksPresent => {
583 self.pipeline_details(pipeline_id).animation_callbacks_running = false;
584 }
585 }
586 }
587
pipeline_details(&mut self, pipeline_id: PipelineId) -> &mut PipelineDetails588 fn pipeline_details(&mut self, pipeline_id: PipelineId) -> &mut PipelineDetails {
589 if !self.pipeline_details.contains_key(&pipeline_id) {
590 self.pipeline_details.insert(pipeline_id, PipelineDetails::new());
591 }
592 self.pipeline_details.get_mut(&pipeline_id).expect("Insert then get failed!")
593 }
594
pipeline(&self, pipeline_id: PipelineId) -> Option<&CompositionPipeline>595 pub fn pipeline(&self, pipeline_id: PipelineId) -> Option<&CompositionPipeline> {
596 match self.pipeline_details.get(&pipeline_id) {
597 Some(ref details) => details.pipeline.as_ref(),
598 None => {
599 warn!("Compositor layer has an unknown pipeline ({:?}).", pipeline_id);
600 None
601 }
602 }
603 }
604
set_frame_tree(&mut self, frame_tree: &SendableFrameTree)605 fn set_frame_tree(&mut self, frame_tree: &SendableFrameTree) {
606 debug!("Setting the frame tree for pipeline {}", frame_tree.pipeline.id);
607
608 self.root_pipeline = Some(frame_tree.pipeline.clone());
609
610 let pipeline_id = frame_tree.pipeline.id.to_webrender();
611 let mut txn = webrender_api::Transaction::new();
612 txn.set_root_pipeline(pipeline_id);
613 txn.generate_frame();
614 self.webrender_api.send_transaction(self.webrender_document, txn);
615
616 self.create_pipeline_details_for_frame_tree(&frame_tree);
617
618 self.send_window_size(WindowSizeType::Initial);
619
620 self.frame_tree_id.next();
621 }
622
create_pipeline_details_for_frame_tree(&mut self, frame_tree: &SendableFrameTree)623 fn create_pipeline_details_for_frame_tree(&mut self, frame_tree: &SendableFrameTree) {
624 self.pipeline_details(frame_tree.pipeline.id).pipeline = Some(frame_tree.pipeline.clone());
625
626 for kid in &frame_tree.children {
627 self.create_pipeline_details_for_frame_tree(kid);
628 }
629 }
630
remove_pipeline_root_layer(&mut self, pipeline_id: PipelineId)631 fn remove_pipeline_root_layer(&mut self, pipeline_id: PipelineId) {
632 self.pipeline_details.remove(&pipeline_id);
633 }
634
send_window_size(&self, size_type: WindowSizeType)635 fn send_window_size(&self, size_type: WindowSizeType) {
636 let dppx = self.page_zoom * self.hidpi_factor();
637
638 self.webrender_api.set_window_parameters(self.webrender_document,
639 self.frame_size,
640 self.window_rect,
641 self.hidpi_factor().get());
642
643 let initial_viewport = self.window_rect.size.to_f32() / dppx;
644
645 let data = WindowSizeData {
646 device_pixel_ratio: dppx,
647 initial_viewport: initial_viewport,
648 };
649 let top_level_browsing_context_id = self.root_pipeline.as_ref().map(|pipeline| {
650 pipeline.top_level_browsing_context_id
651 });
652 let msg = ConstellationMsg::WindowSize(top_level_browsing_context_id, data, size_type);
653
654 if let Err(e) = self.constellation_chan.send(msg) {
655 warn!("Sending window resize to constellation failed ({}).", e);
656 }
657 }
658
on_resize_window_event(&mut self)659 pub fn on_resize_window_event(&mut self) {
660 debug!("compositor resize requested");
661
662 // A size change could also mean a resolution change.
663 let new_scale_factor = self.window.hidpi_factor();
664 if self.scale_factor != new_scale_factor {
665 self.scale_factor = new_scale_factor;
666 self.update_zoom_transform();
667 }
668
669 let new_window_rect = self.window.window_rect();
670 let new_frame_size = self.window.framebuffer_size();
671
672 if self.window_rect == new_window_rect &&
673 self.frame_size == new_frame_size {
674 return;
675 }
676
677 self.frame_size = new_frame_size;
678 self.window_rect = new_window_rect;
679
680 self.send_window_size(WindowSizeType::Resize);
681 }
682
on_mouse_window_event_class(&mut self, mouse_window_event: MouseWindowEvent)683 pub fn on_mouse_window_event_class(&mut self, mouse_window_event: MouseWindowEvent) {
684 if opts::get().convert_mouse_to_touch {
685 match mouse_window_event {
686 MouseWindowEvent::Click(_, _) => {}
687 MouseWindowEvent::MouseDown(_, p) => self.on_touch_down(TouchId(0), p),
688 MouseWindowEvent::MouseUp(_, p) => self.on_touch_up(TouchId(0), p),
689 }
690 return
691 }
692
693 self.dispatch_mouse_window_event_class(mouse_window_event);
694 }
695
dispatch_mouse_window_event_class(&mut self, mouse_window_event: MouseWindowEvent)696 fn dispatch_mouse_window_event_class(&mut self, mouse_window_event: MouseWindowEvent) {
697 let point = match mouse_window_event {
698 MouseWindowEvent::Click(_, p) => p,
699 MouseWindowEvent::MouseDown(_, p) => p,
700 MouseWindowEvent::MouseUp(_, p) => p,
701 };
702
703 let results = self.hit_test_at_point(point);
704 let result = match results.items.first() {
705 Some(result) => result,
706 None => return,
707 };
708
709 let (button, event_type) = match mouse_window_event {
710 MouseWindowEvent::Click(button, _) => (button, MouseEventType::Click),
711 MouseWindowEvent::MouseDown(button, _) => (button, MouseEventType::MouseDown),
712 MouseWindowEvent::MouseUp(button, _) => (button, MouseEventType::MouseUp),
713 };
714
715 let event_to_send = MouseButtonEvent(
716 event_type,
717 button,
718 result.point_in_viewport.to_untyped(),
719 Some(UntrustedNodeAddress(result.tag.0 as *const c_void)),
720 Some(result.point_relative_to_item.to_untyped()),
721 );
722
723 let pipeline_id = PipelineId::from_webrender(result.pipeline);
724 let msg = ConstellationMsg::ForwardEvent(pipeline_id, event_to_send);
725 if let Err(e) = self.constellation_chan.send(msg) {
726 warn!("Sending event to constellation failed ({}).", e);
727 }
728 }
729
hit_test_at_point(&self, point: TypedPoint2D<f32, DevicePixel>) -> HitTestResult730 fn hit_test_at_point(&self, point: TypedPoint2D<f32, DevicePixel>) -> HitTestResult {
731 let dppx = self.page_zoom * self.hidpi_factor();
732 let scaled_point = (point / dppx).to_untyped();
733
734 let world_cursor = webrender_api::WorldPoint::from_untyped(&scaled_point);
735 self.webrender_api.hit_test(
736 self.webrender_document,
737 None,
738 world_cursor,
739 HitTestFlags::empty()
740 )
741
742 }
743
on_mouse_window_move_event_class(&mut self, cursor: TypedPoint2D<f32, DevicePixel>)744 pub fn on_mouse_window_move_event_class(&mut self, cursor: TypedPoint2D<f32, DevicePixel>) {
745 if opts::get().convert_mouse_to_touch {
746 self.on_touch_move(TouchId(0), cursor);
747 return
748 }
749
750 self.dispatch_mouse_window_move_event_class(cursor);
751 }
752
dispatch_mouse_window_move_event_class(&mut self, cursor: TypedPoint2D<f32, DevicePixel>)753 fn dispatch_mouse_window_move_event_class(&mut self, cursor: TypedPoint2D<f32, DevicePixel>) {
754 let root_pipeline_id = match self.get_root_pipeline_id() {
755 Some(root_pipeline_id) => root_pipeline_id,
756 None => return,
757 };
758 if self.pipeline(root_pipeline_id).is_none() {
759 return;
760 }
761
762 let results = self.hit_test_at_point(cursor);
763 if let Some(item) = results.items.first() {
764 let node_address = Some(UntrustedNodeAddress(item.tag.0 as *const c_void));
765 let event = MouseMoveEvent(Some(item.point_in_viewport.to_untyped()), node_address);
766 let pipeline_id = PipelineId::from_webrender(item.pipeline);
767 let msg = ConstellationMsg::ForwardEvent(pipeline_id, event);
768 if let Err(e) = self.constellation_chan.send(msg) {
769 warn!("Sending event to constellation failed ({}).", e);
770 }
771
772 if let Some(cursor) = CursorKind::from_u8(item.tag.1 as _).ok() {
773 let msg = ConstellationMsg::SetCursor(cursor);
774 if let Err(e) = self.constellation_chan.send(msg) {
775 warn!("Sending event to constellation failed ({}).", e);
776 }
777 }
778 }
779 }
780
send_touch_event( &self, event_type: TouchEventType, identifier: TouchId, point: TypedPoint2D<f32, DevicePixel>)781 fn send_touch_event(
782 &self,
783 event_type: TouchEventType,
784 identifier: TouchId,
785 point: TypedPoint2D<f32, DevicePixel>)
786 {
787 let results = self.hit_test_at_point(point);
788 if let Some(item) = results.items.first() {
789 let event = TouchEvent(
790 event_type,
791 identifier,
792 item.point_in_viewport.to_untyped(),
793 Some(UntrustedNodeAddress(item.tag.0 as *const c_void)),
794 );
795 let pipeline_id = PipelineId::from_webrender(item.pipeline);
796 let msg = ConstellationMsg::ForwardEvent(pipeline_id, event);
797 if let Err(e) = self.constellation_chan.send(msg) {
798 warn!("Sending event to constellation failed ({}).", e);
799 }
800 }
801 }
802
on_touch_event(&mut self, event_type: TouchEventType, identifier: TouchId, location: TypedPoint2D<f32, DevicePixel>)803 pub fn on_touch_event(&mut self,
804 event_type: TouchEventType,
805 identifier: TouchId,
806 location: TypedPoint2D<f32, DevicePixel>) {
807 match event_type {
808 TouchEventType::Down => self.on_touch_down(identifier, location),
809 TouchEventType::Move => self.on_touch_move(identifier, location),
810 TouchEventType::Up => self.on_touch_up(identifier, location),
811 TouchEventType::Cancel => self.on_touch_cancel(identifier, location),
812 }
813 }
814
on_touch_down(&mut self, identifier: TouchId, point: TypedPoint2D<f32, DevicePixel>)815 fn on_touch_down(&mut self, identifier: TouchId, point: TypedPoint2D<f32, DevicePixel>) {
816 self.touch_handler.on_touch_down(identifier, point);
817 self.send_touch_event(TouchEventType::Down, identifier, point);
818 }
819
on_touch_move(&mut self, identifier: TouchId, point: TypedPoint2D<f32, DevicePixel>)820 fn on_touch_move(&mut self, identifier: TouchId, point: TypedPoint2D<f32, DevicePixel>) {
821 match self.touch_handler.on_touch_move(identifier, point) {
822 TouchAction::Scroll(delta) => {
823 match point.cast() {
824 Some(point) => self.on_scroll_window_event(
825 ScrollLocation::Delta(
826 LayoutVector2D::from_untyped(&delta.to_untyped())
827 ),
828 point
829 ),
830 None => error!("Point cast failed."),
831 }
832 }
833 TouchAction::Zoom(magnification, scroll_delta) => {
834 let cursor = TypedPoint2D::new(-1, -1); // Make sure this hits the base layer.
835 self.pending_scroll_zoom_events.push(ScrollZoomEvent {
836 magnification: magnification,
837 scroll_location: ScrollLocation::Delta(webrender_api::LayoutVector2D::from_untyped(
838 &scroll_delta.to_untyped())),
839 cursor: cursor,
840 phase: ScrollEventPhase::Move(true),
841 event_count: 1,
842 });
843 }
844 TouchAction::DispatchEvent => {
845 self.send_touch_event(TouchEventType::Move, identifier, point);
846 }
847 _ => {}
848 }
849 }
850
on_touch_up(&mut self, identifier: TouchId, point: TypedPoint2D<f32, DevicePixel>)851 fn on_touch_up(&mut self, identifier: TouchId, point: TypedPoint2D<f32, DevicePixel>) {
852 self.send_touch_event(TouchEventType::Up, identifier, point);
853
854 if let TouchAction::Click = self.touch_handler.on_touch_up(identifier, point) {
855 self.simulate_mouse_click(point);
856 }
857 }
858
on_touch_cancel(&mut self, identifier: TouchId, point: TypedPoint2D<f32, DevicePixel>)859 fn on_touch_cancel(&mut self, identifier: TouchId, point: TypedPoint2D<f32, DevicePixel>) {
860 // Send the event to script.
861 self.touch_handler.on_touch_cancel(identifier, point);
862 self.send_touch_event(TouchEventType::Cancel, identifier, point);
863 }
864
865 /// <http://w3c.github.io/touch-events/#mouse-events>
simulate_mouse_click(&mut self, p: TypedPoint2D<f32, DevicePixel>)866 fn simulate_mouse_click(&mut self, p: TypedPoint2D<f32, DevicePixel>) {
867 let button = MouseButton::Left;
868 self.dispatch_mouse_window_move_event_class(p);
869 self.dispatch_mouse_window_event_class(MouseWindowEvent::MouseDown(button, p));
870 self.dispatch_mouse_window_event_class(MouseWindowEvent::MouseUp(button, p));
871 self.dispatch_mouse_window_event_class(MouseWindowEvent::Click(button, p));
872 }
873
on_scroll_event(&mut self, delta: ScrollLocation, cursor: TypedPoint2D<i32, DevicePixel>, phase: TouchEventType)874 pub fn on_scroll_event(&mut self,
875 delta: ScrollLocation,
876 cursor: TypedPoint2D<i32, DevicePixel>,
877 phase: TouchEventType) {
878 match phase {
879 TouchEventType::Move => self.on_scroll_window_event(delta, cursor),
880 TouchEventType::Up | TouchEventType::Cancel => {
881 self.on_scroll_end_window_event(delta, cursor);
882 }
883 TouchEventType::Down => {
884 self.on_scroll_start_window_event(delta, cursor);
885 }
886 }
887 }
888
on_scroll_window_event(&mut self, scroll_location: ScrollLocation, cursor: TypedPoint2D<i32, DevicePixel>)889 fn on_scroll_window_event(&mut self,
890 scroll_location: ScrollLocation,
891 cursor: TypedPoint2D<i32, DevicePixel>) {
892 let event_phase = match (self.scroll_in_progress, self.in_scroll_transaction) {
893 (false, None) => ScrollEventPhase::Start,
894 (false, Some(last_scroll)) if last_scroll.elapsed() > Duration::from_millis(80) =>
895 ScrollEventPhase::Start,
896 (_, _) => ScrollEventPhase::Move(self.scroll_in_progress),
897 };
898 self.in_scroll_transaction = Some(Instant::now());
899 self.pending_scroll_zoom_events.push(ScrollZoomEvent {
900 magnification: 1.0,
901 scroll_location: scroll_location,
902 cursor: cursor,
903 phase: event_phase,
904 event_count: 1,
905 });
906 }
907
on_scroll_start_window_event(&mut self, scroll_location: ScrollLocation, cursor: TypedPoint2D<i32, DevicePixel>)908 fn on_scroll_start_window_event(&mut self,
909 scroll_location: ScrollLocation,
910 cursor: TypedPoint2D<i32, DevicePixel>) {
911 self.scroll_in_progress = true;
912 self.pending_scroll_zoom_events.push(ScrollZoomEvent {
913 magnification: 1.0,
914 scroll_location: scroll_location,
915 cursor: cursor,
916 phase: ScrollEventPhase::Start,
917 event_count: 1,
918 });
919 }
920
on_scroll_end_window_event(&mut self, scroll_location: ScrollLocation, cursor: TypedPoint2D<i32, DevicePixel>)921 fn on_scroll_end_window_event(&mut self,
922 scroll_location: ScrollLocation,
923 cursor: TypedPoint2D<i32, DevicePixel>) {
924 self.scroll_in_progress = false;
925 self.pending_scroll_zoom_events.push(ScrollZoomEvent {
926 magnification: 1.0,
927 scroll_location: scroll_location,
928 cursor: cursor,
929 phase: ScrollEventPhase::End,
930 event_count: 1,
931 });
932 }
933
process_pending_scroll_events(&mut self)934 fn process_pending_scroll_events(&mut self) {
935 let had_events = self.pending_scroll_zoom_events.len() > 0;
936
937 // Batch up all scroll events into one, or else we'll do way too much painting.
938 let mut last_combined_event: Option<ScrollZoomEvent> = None;
939 for scroll_event in self.pending_scroll_zoom_events.drain(..) {
940 let this_cursor = scroll_event.cursor;
941
942 let this_delta = match scroll_event.scroll_location {
943 ScrollLocation::Delta(delta) => delta,
944 ScrollLocation::Start | ScrollLocation::End => {
945 // If this is an event which is scrolling to the start or end of the page,
946 // disregard other pending events and exit the loop.
947 last_combined_event = Some(scroll_event);
948 break;
949 }
950 };
951
952 if let Some(combined_event) = last_combined_event {
953 if combined_event.phase != scroll_event.phase {
954 let combined_delta = match combined_event.scroll_location {
955 ScrollLocation::Delta(delta) => delta,
956 ScrollLocation::Start | ScrollLocation::End => {
957 // If this is an event which is scrolling to the start or end of the page,
958 // disregard other pending events and exit the loop.
959 last_combined_event = Some(scroll_event);
960 break;
961 }
962 };
963 // TODO: units don't match!
964 let delta = combined_delta / self.scale.get();
965
966 let cursor =
967 (combined_event.cursor.to_f32() / self.scale).to_untyped();
968 let location = webrender_api::ScrollLocation::Delta(delta);
969 let cursor = webrender_api::WorldPoint::from_untyped(&cursor);
970 let mut txn = webrender_api::Transaction::new();
971 txn.scroll(location, cursor, combined_event.phase);
972 self.webrender_api.send_transaction(self.webrender_document, txn);
973 last_combined_event = None
974 }
975 }
976
977 match (&mut last_combined_event, scroll_event.phase) {
978 (last_combined_event @ &mut None, _) => {
979 *last_combined_event = Some(ScrollZoomEvent {
980 magnification: scroll_event.magnification,
981 scroll_location: ScrollLocation::Delta(webrender_api::LayoutVector2D::from_untyped(
982 &this_delta.to_untyped())),
983 cursor: this_cursor,
984 phase: scroll_event.phase,
985 event_count: 1,
986 })
987 }
988 (&mut Some(ref mut last_combined_event),
989 ScrollEventPhase::Move(false)) => {
990 // Mac OS X sometimes delivers scroll events out of vsync during a
991 // fling. This causes events to get bunched up occasionally, causing
992 // nasty-looking "pops". To mitigate this, during a fling we average
993 // deltas instead of summing them.
994 if let ScrollLocation::Delta(delta) = last_combined_event.scroll_location {
995 let old_event_count =
996 TypedScale::new(last_combined_event.event_count as f32);
997 last_combined_event.event_count += 1;
998 let new_event_count =
999 TypedScale::new(last_combined_event.event_count as f32);
1000 last_combined_event.scroll_location = ScrollLocation::Delta(
1001 (delta * old_event_count + this_delta) /
1002 new_event_count);
1003 }
1004 }
1005 (&mut Some(ref mut last_combined_event), _) => {
1006 if let ScrollLocation::Delta(delta) = last_combined_event.scroll_location {
1007 last_combined_event.scroll_location = ScrollLocation::Delta(delta + this_delta);
1008 last_combined_event.event_count += 1
1009 }
1010 }
1011 }
1012 }
1013
1014 if let Some(combined_event) = last_combined_event {
1015 let scroll_location = match combined_event.scroll_location {
1016 ScrollLocation::Delta(delta) => {
1017 let scaled_delta = (TypedVector2D::from_untyped(&delta.to_untyped()) / self.scale)
1018 .to_untyped();
1019 let calculated_delta = webrender_api::LayoutVector2D::from_untyped(&scaled_delta);
1020 ScrollLocation::Delta(calculated_delta)
1021 },
1022 // Leave ScrollLocation unchanged if it is Start or End location.
1023 sl @ ScrollLocation::Start | sl @ ScrollLocation::End => sl,
1024 };
1025 let cursor = (combined_event.cursor.to_f32() / self.scale).to_untyped();
1026 let cursor = webrender_api::WorldPoint::from_untyped(&cursor);
1027 let mut txn = webrender_api::Transaction::new();
1028 txn.scroll(scroll_location, cursor, combined_event.phase);
1029 self.webrender_api.send_transaction(self.webrender_document, txn);
1030 self.waiting_for_results_of_scroll = true
1031 }
1032
1033 if had_events {
1034 self.send_viewport_rects();
1035 }
1036 }
1037
1038 /// If there are any animations running, dispatches appropriate messages to the constellation.
process_animations(&mut self)1039 fn process_animations(&mut self) {
1040 let mut pipeline_ids = vec![];
1041 for (pipeline_id, pipeline_details) in &self.pipeline_details {
1042 if (pipeline_details.animations_running ||
1043 pipeline_details.animation_callbacks_running) &&
1044 pipeline_details.visible {
1045 pipeline_ids.push(*pipeline_id);
1046 }
1047 }
1048 let animation_state = if pipeline_ids.is_empty() {
1049 windowing::AnimationState::Idle
1050 } else {
1051 windowing::AnimationState::Animating
1052 };
1053 self.window.set_animation_state(animation_state);
1054 for pipeline_id in &pipeline_ids {
1055 self.tick_animations_for_pipeline(*pipeline_id)
1056 }
1057 }
1058
tick_animations_for_pipeline(&mut self, pipeline_id: PipelineId)1059 fn tick_animations_for_pipeline(&mut self, pipeline_id: PipelineId) {
1060 let animation_callbacks_running = self.pipeline_details(pipeline_id).animation_callbacks_running;
1061 if animation_callbacks_running {
1062 let msg = ConstellationMsg::TickAnimation(pipeline_id, AnimationTickType::Script);
1063 if let Err(e) = self.constellation_chan.send(msg) {
1064 warn!("Sending tick to constellation failed ({}).", e);
1065 }
1066 }
1067
1068 // We may need to tick animations in layout. (See #12749.)
1069 let animations_running = self.pipeline_details(pipeline_id).animations_running;
1070 if animations_running {
1071 let msg = ConstellationMsg::TickAnimation(pipeline_id, AnimationTickType::Layout);
1072 if let Err(e) = self.constellation_chan.send(msg) {
1073 warn!("Sending tick to constellation failed ({}).", e);
1074 }
1075 }
1076 }
1077
constrain_viewport(&mut self, pipeline_id: PipelineId, constraints: ViewportConstraints)1078 fn constrain_viewport(&mut self, pipeline_id: PipelineId, constraints: ViewportConstraints) {
1079 let is_root = self.root_pipeline.as_ref().map_or(false, |root_pipeline| {
1080 root_pipeline.id == pipeline_id
1081 });
1082
1083 if is_root {
1084 self.viewport_zoom = constraints.initial_zoom;
1085 self.min_viewport_zoom = constraints.min_zoom;
1086 self.max_viewport_zoom = constraints.max_zoom;
1087 self.update_zoom_transform();
1088 }
1089 }
1090
hidpi_factor(&self) -> TypedScale<f32, DeviceIndependentPixel, DevicePixel>1091 fn hidpi_factor(&self) -> TypedScale<f32, DeviceIndependentPixel, DevicePixel> {
1092 match opts::get().device_pixels_per_px {
1093 Some(device_pixels_per_px) => TypedScale::new(device_pixels_per_px),
1094 None => match opts::get().output_file {
1095 Some(_) => TypedScale::new(1.0),
1096 None => self.scale_factor
1097 }
1098 }
1099 }
1100
device_pixels_per_page_px(&self) -> TypedScale<f32, CSSPixel, DevicePixel>1101 fn device_pixels_per_page_px(&self) -> TypedScale<f32, CSSPixel, DevicePixel> {
1102 self.page_zoom * self.hidpi_factor()
1103 }
1104
update_zoom_transform(&mut self)1105 fn update_zoom_transform(&mut self) {
1106 let scale = self.device_pixels_per_page_px();
1107 self.scale = TypedScale::new(scale.get());
1108 }
1109
on_zoom_reset_window_event(&mut self)1110 pub fn on_zoom_reset_window_event(&mut self) {
1111 self.page_zoom = TypedScale::new(1.0);
1112 self.update_zoom_transform();
1113 self.send_window_size(WindowSizeType::Resize);
1114 self.update_page_zoom_for_webrender();
1115 }
1116
on_zoom_window_event(&mut self, magnification: f32)1117 pub fn on_zoom_window_event(&mut self, magnification: f32) {
1118 self.page_zoom = TypedScale::new((self.page_zoom.get() * magnification)
1119 .max(MIN_ZOOM).min(MAX_ZOOM));
1120 self.update_zoom_transform();
1121 self.send_window_size(WindowSizeType::Resize);
1122 self.update_page_zoom_for_webrender();
1123 }
1124
update_page_zoom_for_webrender(&mut self)1125 fn update_page_zoom_for_webrender(&mut self) {
1126 let page_zoom = webrender_api::ZoomFactor::new(self.page_zoom.get());
1127
1128 let mut txn = webrender_api::Transaction::new();
1129 txn.set_page_zoom(page_zoom);
1130 self.webrender_api.send_transaction(self.webrender_document, txn);
1131 }
1132
1133 /// Simulate a pinch zoom
on_pinch_zoom_window_event(&mut self, magnification: f32)1134 pub fn on_pinch_zoom_window_event(&mut self, magnification: f32) {
1135 self.pending_scroll_zoom_events.push(ScrollZoomEvent {
1136 magnification: magnification,
1137 scroll_location: ScrollLocation::Delta(TypedVector2D::zero()), // TODO: Scroll to keep the center in view?
1138 cursor: TypedPoint2D::new(-1, -1), // Make sure this hits the base layer.
1139 phase: ScrollEventPhase::Move(true),
1140 event_count: 1,
1141 });
1142 }
1143
send_viewport_rects(&self)1144 fn send_viewport_rects(&self) {
1145 let mut scroll_states_per_pipeline = HashMap::new();
1146 for scroll_layer_state in self.webrender_api.get_scroll_node_state(self.webrender_document) {
1147 let scroll_state = ScrollState {
1148 scroll_id: scroll_layer_state.id,
1149 scroll_offset: scroll_layer_state.scroll_offset.to_untyped(),
1150 };
1151
1152 scroll_states_per_pipeline
1153 .entry(scroll_layer_state.id.pipeline_id())
1154 .or_insert(vec![])
1155 .push(scroll_state);
1156 }
1157
1158 for (pipeline_id, scroll_states) in scroll_states_per_pipeline {
1159 if let Some(pipeline) = self.pipeline(pipeline_id.from_webrender()) {
1160 let msg = LayoutControlMsg::SetScrollStates(scroll_states);
1161 let _ = pipeline.layout_chan.send(msg);
1162 }
1163 }
1164 }
1165
1166 // Check if any pipelines currently have active animations or animation callbacks.
animations_active(&self) -> bool1167 fn animations_active(&self) -> bool {
1168 for (_, details) in &self.pipeline_details {
1169 // If animations are currently running, then don't bother checking
1170 // with the constellation if the output image is stable.
1171 if details.animations_running {
1172 return true;
1173 }
1174 if details.animation_callbacks_running {
1175 return true;
1176 }
1177 }
1178
1179 false
1180 }
1181
1182 /// Query the constellation to see if the current compositor
1183 /// output matches the current frame tree output, and if the
1184 /// associated script threads are idle.
is_ready_to_paint_image_output(&mut self) -> Result<(), NotReadyToPaint>1185 fn is_ready_to_paint_image_output(&mut self) -> Result<(), NotReadyToPaint> {
1186 match self.ready_to_save_state {
1187 ReadyState::Unknown => {
1188 // Unsure if the output image is stable.
1189
1190 // Collect the currently painted epoch of each pipeline that is
1191 // complete (i.e. has *all* layers painted to the requested epoch).
1192 // This gets sent to the constellation for comparison with the current
1193 // frame tree.
1194 let mut pipeline_epochs = HashMap::new();
1195 for (id, _) in &self.pipeline_details {
1196 let webrender_pipeline_id = id.to_webrender();
1197 if let Some(webrender_api::Epoch(epoch)) = self.webrender
1198 .current_epoch(webrender_pipeline_id) {
1199 let epoch = Epoch(epoch);
1200 pipeline_epochs.insert(*id, epoch);
1201 }
1202 }
1203
1204 // Pass the pipeline/epoch states to the constellation and check
1205 // if it's safe to output the image.
1206 let msg = ConstellationMsg::IsReadyToSaveImage(pipeline_epochs);
1207 if let Err(e) = self.constellation_chan.send(msg) {
1208 warn!("Sending ready to save to constellation failed ({}).", e);
1209 }
1210 self.ready_to_save_state = ReadyState::WaitingForConstellationReply;
1211 Err(NotReadyToPaint::JustNotifiedConstellation)
1212 }
1213 ReadyState::WaitingForConstellationReply => {
1214 // If waiting on a reply from the constellation to the last
1215 // query if the image is stable, then assume not ready yet.
1216 Err(NotReadyToPaint::WaitingOnConstellation)
1217 }
1218 ReadyState::ReadyToSaveImage => {
1219 // Constellation has replied at some point in the past
1220 // that the current output image is stable and ready
1221 // for saving.
1222 // Reset the flag so that we check again in the future
1223 // TODO: only reset this if we load a new document?
1224 if opts::get().is_running_problem_test {
1225 println!("was ready to save, resetting ready_to_save_state");
1226 }
1227 self.ready_to_save_state = ReadyState::Unknown;
1228 Ok(())
1229 }
1230 }
1231 }
1232
composite(&mut self)1233 pub fn composite(&mut self) {
1234 let target = self.composite_target;
1235 match self.composite_specific_target(target) {
1236 Ok(_) => if opts::get().output_file.is_some() || opts::get().exit_after_load {
1237 println!("Shutting down the Constellation after generating an output file or exit flag specified");
1238 self.start_shutting_down();
1239 },
1240 Err(e) => if opts::get().is_running_problem_test {
1241 if e != UnableToComposite::NotReadyToPaintImage(NotReadyToPaint::WaitingOnConstellation) {
1242 println!("not ready to composite: {:?}", e);
1243 }
1244 },
1245 }
1246 }
1247
1248 /// Composite either to the screen or to a png image or both.
1249 /// Returns Ok if composition was performed or Err if it was not possible to composite
1250 /// for some reason. If CompositeTarget is Window or Png no image data is returned;
1251 /// in the latter case the image is written directly to a file. If CompositeTarget
1252 /// is WindowAndPng Ok(Some(png::Image)) is returned.
composite_specific_target(&mut self, target: CompositeTarget) -> Result<Option<Image>, UnableToComposite>1253 fn composite_specific_target(&mut self,
1254 target: CompositeTarget)
1255 -> Result<Option<Image>, UnableToComposite> {
1256 let (width, height) =
1257 (self.frame_size.width as usize, self.frame_size.height as usize);
1258 if !self.window.prepare_for_composite(width, height) {
1259 return Err(UnableToComposite::WindowUnprepared)
1260 }
1261
1262 self.webrender.update();
1263
1264 let wait_for_stable_image = match target {
1265 CompositeTarget::WindowAndPng | CompositeTarget::PngFile => true,
1266 CompositeTarget::Window => opts::get().exit_after_load,
1267 };
1268
1269 if wait_for_stable_image {
1270 // The current image may be ready to output. However, if there are animations active,
1271 // tick those instead and continue waiting for the image output to be stable AND
1272 // all active animations to complete.
1273 if self.animations_active() {
1274 self.process_animations();
1275 return Err(UnableToComposite::NotReadyToPaintImage(NotReadyToPaint::AnimationsActive));
1276 }
1277 if let Err(result) = self.is_ready_to_paint_image_output() {
1278 return Err(UnableToComposite::NotReadyToPaintImage(result))
1279 }
1280 }
1281
1282 let render_target_info = match target {
1283 CompositeTarget::Window => RenderTargetInfo::empty(),
1284 _ => initialize_png(&*self.gl, width, height)
1285 };
1286
1287 profile(ProfilerCategory::Compositing, None, self.time_profiler_chan.clone(), || {
1288 debug!("compositor: compositing");
1289
1290 // Paint the scene.
1291 // TODO(gw): Take notice of any errors the renderer returns!
1292 self.webrender.render(self.frame_size).ok();
1293 });
1294
1295 // If there are pending paint metrics, we check if any of the painted epochs is
1296 // one of the ones that the paint metrics recorder is expecting . In that case,
1297 // we get the current time, inform the layout thread about it and remove the
1298 // pending metric from the list.
1299 if !self.pending_paint_metrics.is_empty() {
1300 let paint_time = precise_time_ns();
1301 let mut to_remove = Vec::new();
1302 // For each pending paint metrics pipeline id
1303 for (id, pending_epoch) in &self.pending_paint_metrics {
1304 // we get the last painted frame id from webrender
1305 if let Some(webrender_api::Epoch(epoch)) = self.webrender.current_epoch(id.to_webrender()) {
1306 // and check if it is the one the layout thread is expecting,
1307 let epoch = Epoch(epoch);
1308 if *pending_epoch != epoch {
1309 continue;
1310 }
1311 // in which case, we remove it from the list of pending metrics,
1312 to_remove.push(id.clone());
1313 if let Some(pipeline) = self.pipeline(*id) {
1314 // and inform the layout thread with the measured paint time.
1315 let msg = LayoutControlMsg::PaintMetric(epoch, paint_time);
1316 if let Err(e) = pipeline.layout_chan.send(msg) {
1317 warn!("Sending PaintMetric message to layout failed ({}).", e);
1318 }
1319 }
1320 }
1321 }
1322 for id in to_remove.iter() {
1323 self.pending_paint_metrics.remove(id);
1324 }
1325 }
1326
1327 let rv = match target {
1328 CompositeTarget::Window => None,
1329 CompositeTarget::WindowAndPng => {
1330 let img = self.draw_img(render_target_info,
1331 width,
1332 height);
1333 Some(Image {
1334 width: img.width(),
1335 height: img.height(),
1336 format: PixelFormat::RGB8,
1337 bytes: IpcSharedMemory::from_bytes(&*img),
1338 id: None,
1339 })
1340 }
1341 CompositeTarget::PngFile => {
1342 profile(ProfilerCategory::ImageSaving, None, self.time_profiler_chan.clone(), || {
1343 match opts::get().output_file.as_ref() {
1344 Some(path) => match File::create(path) {
1345 Ok(mut file) => {
1346 let img = self.draw_img(render_target_info, width, height);
1347 let dynamic_image = DynamicImage::ImageRgb8(img);
1348 if let Err(e) = dynamic_image.save(&mut file, ImageFormat::PNG) {
1349 error!("Failed to save {} ({}).", path, e);
1350 }
1351 },
1352 Err(e) => error!("Failed to create {} ({}).", path, e),
1353 },
1354 None => error!("No file specified."),
1355 }
1356 });
1357 None
1358 }
1359 };
1360
1361 // Perform the page flip. This will likely block for a while.
1362 self.window.present();
1363
1364 self.last_composite_time = precise_time_ns();
1365
1366 self.composition_request = CompositionRequest::NoCompositingNecessary;
1367
1368 self.process_animations();
1369 self.start_scrolling_bounce_if_necessary();
1370 self.waiting_for_results_of_scroll = false;
1371
1372 Ok(rv)
1373 }
1374
draw_img(&self, render_target_info: RenderTargetInfo, width: usize, height: usize) -> RgbImage1375 fn draw_img(&self,
1376 render_target_info: RenderTargetInfo,
1377 width: usize,
1378 height: usize)
1379 -> RgbImage {
1380 // For some reason, OSMesa fails to render on the 3rd
1381 // attempt in headless mode, under some conditions.
1382 // I think this can only be some kind of synchronization
1383 // bug in OSMesa, but explicitly un-binding any vertex
1384 // array here seems to work around that bug.
1385 // See https://github.com/servo/servo/issues/18606.
1386 self.gl.bind_vertex_array(0);
1387
1388 let mut pixels = self.gl.read_pixels(0, 0,
1389 width as gl::GLsizei,
1390 height as gl::GLsizei,
1391 gl::RGB, gl::UNSIGNED_BYTE);
1392
1393 self.gl.bind_framebuffer(gl::FRAMEBUFFER, 0);
1394
1395 self.gl.delete_buffers(&render_target_info.texture_ids);
1396 self.gl.delete_renderbuffers(&render_target_info.renderbuffer_ids);
1397 self.gl.delete_framebuffers(&render_target_info.framebuffer_ids);
1398
1399 // flip image vertically (texture is upside down)
1400 let orig_pixels = pixels.clone();
1401 let stride = width * 3;
1402 for y in 0..height {
1403 let dst_start = y * stride;
1404 let src_start = (height - y - 1) * stride;
1405 let src_slice = &orig_pixels[src_start .. src_start + stride];
1406 (&mut pixels[dst_start .. dst_start + stride]).clone_from_slice(&src_slice[..stride]);
1407 }
1408 RgbImage::from_raw(width as u32, height as u32, pixels).expect("Flipping image failed!")
1409 }
1410
composite_if_necessary(&mut self, reason: CompositingReason)1411 fn composite_if_necessary(&mut self, reason: CompositingReason) {
1412 if self.composition_request == CompositionRequest::NoCompositingNecessary {
1413 if opts::get().is_running_problem_test {
1414 println!("updating composition_request ({:?})", reason);
1415 }
1416 self.composition_request = CompositionRequest::CompositeNow(reason)
1417 } else if opts::get().is_running_problem_test {
1418 println!("composition_request is already {:?}", self.composition_request);
1419 }
1420 }
1421
get_root_pipeline_id(&self) -> Option<PipelineId>1422 fn get_root_pipeline_id(&self) -> Option<PipelineId> {
1423 self.root_pipeline.as_ref().map(|pipeline| pipeline.id)
1424 }
1425
start_scrolling_bounce_if_necessary(&mut self)1426 fn start_scrolling_bounce_if_necessary(&mut self) {
1427 if self.scroll_in_progress {
1428 return
1429 }
1430
1431 if self.webrender.layers_are_bouncing_back() {
1432 let mut txn = webrender_api::Transaction::new();
1433 txn.tick_scrolling_bounce_animations();
1434 self.webrender_api.send_transaction(self.webrender_document, txn);
1435 self.send_viewport_rects()
1436 }
1437 }
1438
receive_messages(&mut self) -> bool1439 pub fn receive_messages(&mut self) -> bool {
1440 // Check for new messages coming from the other threads in the system.
1441 let mut compositor_messages = vec![];
1442 let mut found_recomposite_msg = false;
1443 while let Some(msg) = self.port.try_recv_compositor_msg() {
1444 match msg {
1445 Msg::Recomposite(_) if found_recomposite_msg => {}
1446 Msg::Recomposite(_) => {
1447 found_recomposite_msg = true;
1448 compositor_messages.push(msg)
1449 }
1450 _ => compositor_messages.push(msg),
1451 }
1452 }
1453 for msg in compositor_messages {
1454 if !self.handle_browser_message(msg) {
1455 return false
1456 }
1457 }
1458 true
1459 }
1460
perform_updates(&mut self) -> bool1461 pub fn perform_updates(&mut self) -> bool {
1462 if self.shutdown_state == ShutdownState::FinishedShuttingDown {
1463 return false;
1464 }
1465
1466 // If a pinch-zoom happened recently, ask for tiles at the new resolution
1467 if self.zoom_action && precise_time_s() - self.zoom_time > 0.3 {
1468 self.zoom_action = false;
1469 }
1470
1471 match self.composition_request {
1472 CompositionRequest::NoCompositingNecessary => {}
1473 CompositionRequest::CompositeNow(_) => {
1474 self.composite()
1475 }
1476 }
1477
1478 if !self.pending_scroll_zoom_events.is_empty() && !self.waiting_for_results_of_scroll {
1479 self.process_pending_scroll_events()
1480 }
1481 self.shutdown_state != ShutdownState::FinishedShuttingDown
1482 }
1483
1484 /// Repaints and recomposites synchronously. You must be careful when calling this, as if a
1485 /// paint is not scheduled the compositor will hang forever.
1486 ///
1487 /// This is used when resizing the window.
repaint_synchronously(&mut self)1488 pub fn repaint_synchronously(&mut self) {
1489 while self.shutdown_state != ShutdownState::ShuttingDown {
1490 let msg = self.port.recv_compositor_msg();
1491 let need_recomposite = match msg {
1492 Msg::Recomposite(_) => true,
1493 _ => false,
1494 };
1495 let keep_going = self.handle_browser_message(msg);
1496 if need_recomposite {
1497 self.composite();
1498 break
1499 }
1500 if !keep_going {
1501 break
1502 }
1503 }
1504 }
1505
pinch_zoom_level(&self) -> f321506 pub fn pinch_zoom_level(&self) -> f32 {
1507 // TODO(gw): Access via WR.
1508 1.0
1509 }
1510
toggle_webrender_debug(&mut self, option: WebRenderDebugOption)1511 pub fn toggle_webrender_debug(&mut self, option: WebRenderDebugOption) {
1512 let mut flags = self.webrender.get_debug_flags();
1513 let flag = match option {
1514 WebRenderDebugOption::Profiler => {
1515 webrender::DebugFlags::PROFILER_DBG |
1516 webrender::DebugFlags::GPU_TIME_QUERIES |
1517 webrender::DebugFlags::GPU_SAMPLE_QUERIES
1518 }
1519 WebRenderDebugOption::TextureCacheDebug => {
1520 webrender::DebugFlags::TEXTURE_CACHE_DBG
1521 }
1522 WebRenderDebugOption::RenderTargetDebug => {
1523 webrender::DebugFlags::RENDER_TARGET_DBG
1524 }
1525 };
1526 flags.toggle(flag);
1527 self.webrender.set_debug_flags(flags);
1528
1529 let mut txn = webrender_api::Transaction::new();
1530 txn.generate_frame();
1531 self.webrender_api.send_transaction(self.webrender_document, txn);
1532 }
1533 }
1534
1535 /// Why we performed a composite. This is used for debugging.
1536 #[derive(Clone, Copy, Debug, PartialEq)]
1537 pub enum CompositingReason {
1538 /// We hit the delayed composition timeout. (See `delayed_composition.rs`.)
1539 DelayedCompositeTimeout,
1540 /// The window has been scrolled and we're starting the first recomposite.
1541 Scroll,
1542 /// A scroll has continued and we need to recomposite again.
1543 ContinueScroll,
1544 /// We're performing the single composite in headless mode.
1545 Headless,
1546 /// We're performing a composite to run an animation.
1547 Animation,
1548 /// A new frame tree has been loaded.
1549 NewFrameTree,
1550 /// New painted buffers have been received.
1551 NewPaintedBuffers,
1552 /// The window has been zoomed.
1553 Zoom,
1554 /// A new WebRender frame has arrived.
1555 NewWebRenderFrame,
1556 /// WebRender has processed a scroll event and has generated a new frame.
1557 NewWebRenderScrollFrame,
1558 }
1559