mirror of
https://github.com/servo/servo.git
synced 2025-08-03 20:50:07 +01:00
Cargoify servo
This commit is contained in:
parent
db2f642c32
commit
c6ab60dbfc
1761 changed files with 8423 additions and 2294 deletions
61
components/compositing/Cargo.toml
Normal file
61
components/compositing/Cargo.toml
Normal file
|
@ -0,0 +1,61 @@
|
|||
[package]
|
||||
name = "compositing"
|
||||
version = "0.0.1"
|
||||
authors = ["The Servo Project Developers"]
|
||||
|
||||
[lib]
|
||||
name = "compositing"
|
||||
path = "lib.rs"
|
||||
|
||||
[dependencies.gfx]
|
||||
path = "../gfx"
|
||||
|
||||
[dependencies.layout_traits]
|
||||
path = "../layout_traits"
|
||||
|
||||
[dependencies.script_traits]
|
||||
path = "../script_traits"
|
||||
|
||||
[dependencies.msg]
|
||||
path = "../msg"
|
||||
|
||||
[dependencies.net]
|
||||
path = "../net"
|
||||
|
||||
[dependencies.util]
|
||||
path = "../util"
|
||||
|
||||
[dependencies.alert]
|
||||
git = "https://github.com/servo/rust-alert"
|
||||
|
||||
[dependencies.azure]
|
||||
git = "https://github.com/servo/rust-azure"
|
||||
|
||||
[dependencies.geom]
|
||||
git = "https://github.com/servo/rust-geom"
|
||||
|
||||
[dependencies.glfw]
|
||||
git = "https://github.com/servo/glfw-rs"
|
||||
branch = "servo"
|
||||
|
||||
[dependencies.layers]
|
||||
git = "https://github.com/servo/rust-layers"
|
||||
|
||||
[dependencies.opengles]
|
||||
git = "https://github.com/servo/rust-opengles"
|
||||
|
||||
[dependencies.png]
|
||||
git = "https://github.com/servo/rust-png"
|
||||
|
||||
[dependencies.url]
|
||||
git = "https://github.com/servo/rust-url"
|
||||
|
||||
[dependencies.core_graphics]
|
||||
git = "https://github.com/servo/rust-core-graphics"
|
||||
|
||||
[dependencies.core_text]
|
||||
git = "https://github.com/servo/rust-core-text"
|
||||
|
||||
[dependencies.glut]
|
||||
git = "https://github.com/servo/rust-glut"
|
||||
|
924
components/compositing/compositor.rs
Normal file
924
components/compositing/compositor.rs
Normal file
|
@ -0,0 +1,924 @@
|
|||
/* 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 compositor_data::{CompositorData, DoesntWantScrollEvents, WantsScrollEvents};
|
||||
use compositor_task::{Msg, CompositorTask, Exit, ChangeReadyState, SetIds, LayerProperties};
|
||||
use compositor_task::{GetGraphicsMetadata, CreateOrUpdateRootLayer, CreateOrUpdateDescendantLayer};
|
||||
use compositor_task::{SetLayerClipRect, Paint, ScrollFragmentPoint, LoadComplete};
|
||||
use compositor_task::{ShutdownComplete, ChangeRenderState, RenderMsgDiscarded};
|
||||
use constellation::SendableFrameTree;
|
||||
use events;
|
||||
use pipeline::CompositionPipeline;
|
||||
use platform::{Application, Window};
|
||||
use windowing;
|
||||
use windowing::{FinishedWindowEvent, IdleWindowEvent, LoadUrlWindowEvent, MouseWindowClickEvent};
|
||||
use windowing::{MouseWindowEvent, MouseWindowEventClass, MouseWindowMouseDownEvent};
|
||||
use windowing::{MouseWindowMouseUpEvent, MouseWindowMoveEventClass, NavigationWindowEvent};
|
||||
use windowing::{QuitWindowEvent, RefreshWindowEvent, ResizeWindowEvent, ScrollWindowEvent};
|
||||
use windowing::{WindowEvent, WindowMethods, WindowNavigateMsg, ZoomWindowEvent};
|
||||
use windowing::PinchZoomWindowEvent;
|
||||
|
||||
use azure::azure_hl::SourceSurfaceMethods;
|
||||
use azure::azure_hl;
|
||||
use geom::matrix::identity;
|
||||
use geom::point::{Point2D, TypedPoint2D};
|
||||
use geom::rect::Rect;
|
||||
use geom::size::TypedSize2D;
|
||||
use geom::scale_factor::ScaleFactor;
|
||||
use gfx::render_task::{RenderChan, RenderMsg, RenderRequest, UnusedBufferMsg};
|
||||
use layers::geometry::DevicePixel;
|
||||
use layers::layers::{BufferRequest, Layer, LayerBufferSet};
|
||||
use layers::rendergl;
|
||||
use layers::rendergl::RenderContext;
|
||||
use layers::scene::Scene;
|
||||
use opengles::gl2;
|
||||
use png;
|
||||
use servo_msg::compositor_msg::{Blank, Epoch, FixedPosition, FinishedLoading, IdleRenderState};
|
||||
use servo_msg::compositor_msg::{LayerId, ReadyState, RenderState};
|
||||
use servo_msg::constellation_msg::{ConstellationChan, ExitMsg, LoadUrlMsg, NavigateMsg};
|
||||
use servo_msg::constellation_msg::{PipelineId, ResizedWindowMsg, WindowSizeData};
|
||||
use servo_msg::constellation_msg;
|
||||
use servo_util::geometry::{PagePx, ScreenPx, ViewportPx};
|
||||
use servo_util::memory::MemoryProfilerChan;
|
||||
use servo_util::opts::Opts;
|
||||
use servo_util::time::{profile, TimeProfilerChan};
|
||||
use servo_util::{memory, time};
|
||||
use std::io::timer::sleep;
|
||||
use std::collections::hashmap::HashMap;
|
||||
use std::path::Path;
|
||||
use std::rc::Rc;
|
||||
use time::precise_time_s;
|
||||
use url::Url;
|
||||
|
||||
|
||||
pub struct IOCompositor {
|
||||
/// The application window.
|
||||
window: Rc<Window>,
|
||||
|
||||
/// The port on which we receive messages.
|
||||
port: Receiver<Msg>,
|
||||
|
||||
/// The render context.
|
||||
context: RenderContext,
|
||||
|
||||
/// The root pipeline.
|
||||
root_pipeline: Option<CompositionPipeline>,
|
||||
|
||||
/// The canvas to paint a page.
|
||||
scene: Scene<CompositorData>,
|
||||
|
||||
/// The application window size.
|
||||
window_size: TypedSize2D<DevicePixel, uint>,
|
||||
|
||||
/// "Mobile-style" zoom that does not reflow the page.
|
||||
viewport_zoom: ScaleFactor<PagePx, ViewportPx, f32>,
|
||||
|
||||
/// "Desktop-style" zoom that resizes the viewport to fit the window.
|
||||
/// See `ViewportPx` docs in util/geom.rs for details.
|
||||
page_zoom: ScaleFactor<ViewportPx, ScreenPx, f32>,
|
||||
|
||||
/// The device pixel ratio for this window.
|
||||
hidpi_factor: ScaleFactor<ScreenPx, DevicePixel, f32>,
|
||||
|
||||
/// Tracks whether the renderer has finished its first rendering
|
||||
composite_ready: bool,
|
||||
|
||||
/// Tracks whether we are in the process of shutting down, or have shut down and should close
|
||||
/// the compositor.
|
||||
shutdown_state: ShutdownState,
|
||||
|
||||
/// Tracks whether we need to re-composite a page.
|
||||
recomposite: bool,
|
||||
|
||||
/// Tracks outstanding render_msg's sent to the render tasks.
|
||||
outstanding_render_msgs: uint,
|
||||
|
||||
/// Tracks whether the zoom action has happend recently.
|
||||
zoom_action: bool,
|
||||
|
||||
/// The time of the last zoom action has started.
|
||||
zoom_time: f64,
|
||||
|
||||
/// Current display/reflow status of the page
|
||||
ready_state: ReadyState,
|
||||
|
||||
/// Whether the page being rendered has loaded completely.
|
||||
/// Differs from ReadyState because we can finish loading (ready)
|
||||
/// many times for a single page.
|
||||
load_complete: bool,
|
||||
|
||||
/// The command line option flags.
|
||||
opts: Opts,
|
||||
|
||||
/// The channel on which messages can be sent to the constellation.
|
||||
constellation_chan: ConstellationChan,
|
||||
|
||||
/// The channel on which messages can be sent to the time profiler.
|
||||
time_profiler_chan: TimeProfilerChan,
|
||||
|
||||
/// The channel on which messages can be sent to the memory profiler.
|
||||
memory_profiler_chan: MemoryProfilerChan,
|
||||
|
||||
/// Pending scroll to fragment event, if any
|
||||
fragment_point: Option<Point2D<f32>>
|
||||
}
|
||||
|
||||
#[deriving(PartialEq)]
|
||||
enum ShutdownState {
|
||||
NotShuttingDown,
|
||||
ShuttingDown,
|
||||
FinishedShuttingDown,
|
||||
}
|
||||
|
||||
impl IOCompositor {
|
||||
fn new(app: &Application,
|
||||
opts: Opts,
|
||||
port: Receiver<Msg>,
|
||||
constellation_chan: ConstellationChan,
|
||||
time_profiler_chan: TimeProfilerChan,
|
||||
memory_profiler_chan: MemoryProfilerChan) -> IOCompositor {
|
||||
let window: Rc<Window> = WindowMethods::new(app, opts.output_file.is_none());
|
||||
|
||||
// Create an initial layer tree.
|
||||
//
|
||||
// TODO: There should be no initial layer tree until the renderer creates one from the
|
||||
// display list. This is only here because we don't have that logic in the renderer yet.
|
||||
let window_size = window.framebuffer_size();
|
||||
let hidpi_factor = window.hidpi_factor();
|
||||
|
||||
let show_debug_borders = opts.show_debug_borders;
|
||||
IOCompositor {
|
||||
window: window,
|
||||
port: port,
|
||||
opts: opts,
|
||||
context: rendergl::RenderContext::new(CompositorTask::create_graphics_context(),
|
||||
show_debug_borders),
|
||||
root_pipeline: None,
|
||||
scene: Scene::new(window_size.as_f32().to_untyped(), identity()),
|
||||
window_size: window_size,
|
||||
hidpi_factor: hidpi_factor,
|
||||
composite_ready: false,
|
||||
shutdown_state: NotShuttingDown,
|
||||
recomposite: false,
|
||||
page_zoom: ScaleFactor(1.0),
|
||||
viewport_zoom: ScaleFactor(1.0),
|
||||
zoom_action: false,
|
||||
zoom_time: 0f64,
|
||||
ready_state: Blank,
|
||||
load_complete: false,
|
||||
constellation_chan: constellation_chan,
|
||||
time_profiler_chan: time_profiler_chan,
|
||||
memory_profiler_chan: memory_profiler_chan,
|
||||
fragment_point: None,
|
||||
outstanding_render_msgs: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create(app: &Application,
|
||||
opts: Opts,
|
||||
port: Receiver<Msg>,
|
||||
constellation_chan: ConstellationChan,
|
||||
time_profiler_chan: TimeProfilerChan,
|
||||
memory_profiler_chan: MemoryProfilerChan) {
|
||||
let mut compositor = IOCompositor::new(app,
|
||||
opts,
|
||||
port,
|
||||
constellation_chan,
|
||||
time_profiler_chan,
|
||||
memory_profiler_chan);
|
||||
compositor.update_zoom_transform();
|
||||
|
||||
// Starts the compositor, which listens for messages on the specified port.
|
||||
compositor.run();
|
||||
}
|
||||
|
||||
fn run (&mut self) {
|
||||
// Tell the constellation about the initial window size.
|
||||
self.send_window_size();
|
||||
|
||||
// Enter the main event loop.
|
||||
while self.shutdown_state != FinishedShuttingDown {
|
||||
// Check for new messages coming from the rendering task.
|
||||
self.handle_message();
|
||||
|
||||
if self.shutdown_state == FinishedShuttingDown {
|
||||
// We have exited the compositor and passing window
|
||||
// messages to script may crash.
|
||||
debug!("Exiting the compositor due to a request from script.");
|
||||
break;
|
||||
}
|
||||
|
||||
// Check for messages coming from the windowing system.
|
||||
let msg = self.window.recv();
|
||||
self.handle_window_message(msg);
|
||||
|
||||
// If asked to recomposite and renderer has run at least once
|
||||
if self.recomposite && self.composite_ready {
|
||||
self.recomposite = false;
|
||||
self.composite();
|
||||
}
|
||||
|
||||
sleep(10);
|
||||
|
||||
// If a pinch-zoom happened recently, ask for tiles at the new resolution
|
||||
if self.zoom_action && precise_time_s() - self.zoom_time > 0.3 {
|
||||
self.zoom_action = false;
|
||||
self.scene.mark_layer_contents_as_changed_recursively();
|
||||
self.send_buffer_requests_for_all_layers();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Clear out the compositor layers so that painting tasks can destroy the buffers.
|
||||
match self.scene.root {
|
||||
None => {}
|
||||
Some(ref layer) => CompositorData::forget_all_tiles(layer.clone()),
|
||||
}
|
||||
|
||||
// Drain compositor port, sometimes messages contain channels that are blocking
|
||||
// another task from finishing (i.e. SetIds)
|
||||
loop {
|
||||
match self.port.try_recv() {
|
||||
Err(_) => break,
|
||||
Ok(_) => {},
|
||||
}
|
||||
}
|
||||
|
||||
// Tell the profiler and memory profiler to shut down.
|
||||
let TimeProfilerChan(ref time_profiler_chan) = self.time_profiler_chan;
|
||||
time_profiler_chan.send(time::ExitMsg);
|
||||
|
||||
let MemoryProfilerChan(ref memory_profiler_chan) = self.memory_profiler_chan;
|
||||
memory_profiler_chan.send(memory::ExitMsg);
|
||||
}
|
||||
|
||||
fn handle_message(&mut self) {
|
||||
loop {
|
||||
match (self.port.try_recv(), self.shutdown_state) {
|
||||
(_, FinishedShuttingDown) =>
|
||||
fail!("compositor shouldn't be handling messages after shutting down"),
|
||||
|
||||
(Err(_), _) => break,
|
||||
|
||||
(Ok(Exit(chan)), _) => {
|
||||
debug!("shutting down the constellation");
|
||||
let ConstellationChan(ref con_chan) = self.constellation_chan;
|
||||
con_chan.send(ExitMsg);
|
||||
chan.send(());
|
||||
self.shutdown_state = ShuttingDown;
|
||||
}
|
||||
|
||||
(Ok(ShutdownComplete), _) => {
|
||||
debug!("constellation completed shutdown");
|
||||
self.shutdown_state = FinishedShuttingDown;
|
||||
break;
|
||||
}
|
||||
|
||||
(Ok(ChangeReadyState(ready_state)), NotShuttingDown) => {
|
||||
self.window.set_ready_state(ready_state);
|
||||
self.ready_state = ready_state;
|
||||
}
|
||||
|
||||
(Ok(ChangeRenderState(render_state)), NotShuttingDown) => {
|
||||
self.change_render_state(render_state);
|
||||
}
|
||||
|
||||
(Ok(RenderMsgDiscarded), NotShuttingDown) => {
|
||||
self.remove_outstanding_render_msg();
|
||||
}
|
||||
|
||||
(Ok(SetIds(frame_tree, response_chan, new_constellation_chan)), _) => {
|
||||
self.set_ids(frame_tree, response_chan, new_constellation_chan);
|
||||
}
|
||||
|
||||
(Ok(GetGraphicsMetadata(chan)), NotShuttingDown) => {
|
||||
chan.send(Some(azure_hl::current_graphics_metadata()));
|
||||
}
|
||||
|
||||
(Ok(CreateOrUpdateRootLayer(layer_properties)),
|
||||
NotShuttingDown) => {
|
||||
self.create_or_update_root_layer(layer_properties);
|
||||
}
|
||||
|
||||
(Ok(CreateOrUpdateDescendantLayer(layer_properties)),
|
||||
NotShuttingDown) => {
|
||||
self.create_or_update_descendant_layer(layer_properties);
|
||||
}
|
||||
|
||||
(Ok(SetLayerClipRect(pipeline_id, layer_id, new_rect)), NotShuttingDown) => {
|
||||
self.set_layer_clip_rect(pipeline_id, layer_id, new_rect);
|
||||
}
|
||||
|
||||
(Ok(Paint(pipeline_id, epoch, replies)), NotShuttingDown) => {
|
||||
for (layer_id, new_layer_buffer_set) in replies.move_iter() {
|
||||
self.paint(pipeline_id, layer_id, new_layer_buffer_set, epoch);
|
||||
}
|
||||
self.remove_outstanding_render_msg();
|
||||
}
|
||||
|
||||
(Ok(ScrollFragmentPoint(pipeline_id, layer_id, point)), NotShuttingDown) => {
|
||||
self.scroll_fragment_to_point(pipeline_id, layer_id, point);
|
||||
}
|
||||
|
||||
(Ok(LoadComplete(..)), NotShuttingDown) => {
|
||||
self.load_complete = true;
|
||||
}
|
||||
|
||||
// When we are shutting_down, we need to avoid performing operations
|
||||
// such as Paint that may crash because we have begun tearing down
|
||||
// the rest of our resources.
|
||||
(_, ShuttingDown) => { }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn change_render_state(&mut self, render_state: RenderState) {
|
||||
self.window.set_render_state(render_state);
|
||||
if render_state == IdleRenderState {
|
||||
self.composite_ready = true;
|
||||
}
|
||||
}
|
||||
|
||||
fn has_render_msg_tracking(&self) -> bool {
|
||||
// only track RenderMsg's if the compositor outputs to a file.
|
||||
self.opts.output_file.is_some()
|
||||
}
|
||||
|
||||
fn has_outstanding_render_msgs(&self) -> bool {
|
||||
self.has_render_msg_tracking() && self.outstanding_render_msgs > 0
|
||||
}
|
||||
|
||||
fn add_outstanding_render_msg(&mut self, count: uint) {
|
||||
// return early if not tracking render_msg's
|
||||
if !self.has_render_msg_tracking() {
|
||||
return;
|
||||
}
|
||||
debug!("add_outstanding_render_msg {}", self.outstanding_render_msgs);
|
||||
self.outstanding_render_msgs += count;
|
||||
}
|
||||
|
||||
fn remove_outstanding_render_msg(&mut self) {
|
||||
if !self.has_render_msg_tracking() {
|
||||
return;
|
||||
}
|
||||
if self.outstanding_render_msgs > 0 {
|
||||
self.outstanding_render_msgs -= 1;
|
||||
} else {
|
||||
debug!("too many rerender msgs completed");
|
||||
}
|
||||
}
|
||||
|
||||
fn set_ids(&mut self,
|
||||
frame_tree: SendableFrameTree,
|
||||
response_chan: Sender<()>,
|
||||
new_constellation_chan: ConstellationChan) {
|
||||
response_chan.send(());
|
||||
|
||||
self.root_pipeline = Some(frame_tree.pipeline.clone());
|
||||
|
||||
// Initialize the new constellation channel by sending it the root window size.
|
||||
self.constellation_chan = new_constellation_chan;
|
||||
self.send_window_size();
|
||||
}
|
||||
|
||||
fn find_layer_with_pipeline_and_layer_id(&self,
|
||||
pipeline_id: PipelineId,
|
||||
layer_id: LayerId)
|
||||
-> Option<Rc<Layer<CompositorData>>> {
|
||||
match self.scene.root {
|
||||
Some(ref root_layer) => {
|
||||
CompositorData::find_layer_with_pipeline_and_layer_id(root_layer.clone(),
|
||||
pipeline_id,
|
||||
layer_id)
|
||||
}
|
||||
None => None,
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fn update_layer_if_exists(&mut self, properties: LayerProperties) -> bool {
|
||||
match self.find_layer_with_pipeline_and_layer_id(properties.pipeline_id, properties.id) {
|
||||
Some(existing_layer) => {
|
||||
CompositorData::update_layer(existing_layer.clone(), properties);
|
||||
true
|
||||
}
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
|
||||
// rust-layers keeps everything in layer coordinates, so we must convert all rectangles
|
||||
// from page coordinates into layer coordinates based on our current scale.
|
||||
fn convert_page_rect_to_layer_coordinates(&self, page_rect: Rect<f32>) -> Rect<f32> {
|
||||
page_rect * self.device_pixels_per_page_px().get()
|
||||
}
|
||||
|
||||
fn create_or_update_root_layer(&mut self, mut layer_properties: LayerProperties) {
|
||||
layer_properties.rect = self.convert_page_rect_to_layer_coordinates(layer_properties.rect);
|
||||
|
||||
let need_new_root_layer = !self.update_layer_if_exists(layer_properties);
|
||||
if need_new_root_layer {
|
||||
let root_pipeline = match self.root_pipeline {
|
||||
Some(ref root_pipeline) => root_pipeline.clone(),
|
||||
None => fail!("Compositor: Making new layer without initialized pipeline"),
|
||||
};
|
||||
|
||||
let root_properties = LayerProperties {
|
||||
pipeline_id: root_pipeline.id,
|
||||
epoch: layer_properties.epoch,
|
||||
id: LayerId::null(),
|
||||
rect: layer_properties.rect,
|
||||
background_color: layer_properties.background_color,
|
||||
scroll_policy: FixedPosition,
|
||||
};
|
||||
let new_root = CompositorData::new_layer(root_pipeline.clone(),
|
||||
root_properties,
|
||||
WantsScrollEvents,
|
||||
self.opts.tile_size);
|
||||
let first_chid = CompositorData::new_layer(root_pipeline.clone(),
|
||||
layer_properties,
|
||||
DoesntWantScrollEvents,
|
||||
self.opts.tile_size);
|
||||
new_root.add_child(first_chid);
|
||||
|
||||
// Release all tiles from the layer before dropping it.
|
||||
match self.scene.root {
|
||||
Some(ref mut layer) => CompositorData::clear_all_tiles(layer.clone()),
|
||||
None => { }
|
||||
}
|
||||
self.scene.root = Some(new_root);
|
||||
}
|
||||
|
||||
self.scroll_layer_to_fragment_point_if_necessary(layer_properties.pipeline_id,
|
||||
layer_properties.id);
|
||||
self.send_buffer_requests_for_all_layers();
|
||||
}
|
||||
|
||||
fn create_or_update_descendant_layer(&mut self, mut layer_properties: LayerProperties) {
|
||||
layer_properties.rect = self.convert_page_rect_to_layer_coordinates(layer_properties.rect);
|
||||
if !self.update_layer_if_exists(layer_properties) {
|
||||
self.create_descendant_layer(layer_properties);
|
||||
}
|
||||
self.scroll_layer_to_fragment_point_if_necessary(layer_properties.pipeline_id,
|
||||
layer_properties.id);
|
||||
self.send_buffer_requests_for_all_layers();
|
||||
}
|
||||
|
||||
fn create_descendant_layer(&self, layer_properties: LayerProperties) {
|
||||
match self.scene.root {
|
||||
Some(ref root_layer) => {
|
||||
let root_layer_pipeline = root_layer.extra_data.borrow().pipeline.clone();
|
||||
if root_layer_pipeline.id != layer_properties.pipeline_id {
|
||||
fail!("Compositor: New layer pipeline does not match root layer pipeline");
|
||||
}
|
||||
|
||||
let new_layer = CompositorData::new_layer(root_layer_pipeline,
|
||||
layer_properties,
|
||||
DoesntWantScrollEvents,
|
||||
root_layer.tile_size);
|
||||
root_layer.add_child(new_layer);
|
||||
}
|
||||
None => fail!("Compositor: Received new layer without root layer")
|
||||
}
|
||||
}
|
||||
|
||||
fn send_window_size(&self) {
|
||||
let dppx = self.page_zoom * self.device_pixels_per_screen_px();
|
||||
let initial_viewport = self.window_size.as_f32() / dppx;
|
||||
let visible_viewport = initial_viewport / self.viewport_zoom;
|
||||
|
||||
let ConstellationChan(ref chan) = self.constellation_chan;
|
||||
chan.send(ResizedWindowMsg(WindowSizeData {
|
||||
device_pixel_ratio: dppx,
|
||||
initial_viewport: initial_viewport,
|
||||
visible_viewport: visible_viewport,
|
||||
}));
|
||||
}
|
||||
|
||||
fn scroll_layer_to_fragment_point_if_necessary(&mut self,
|
||||
pipeline_id: PipelineId,
|
||||
layer_id: LayerId) {
|
||||
let device_pixels_per_page_px = self.device_pixels_per_page_px();
|
||||
let window_size = self.window_size.as_f32();
|
||||
let needs_recomposite = match self.scene.root {
|
||||
Some(ref mut root_layer) => {
|
||||
self.fragment_point.take().map_or(false, |fragment_point| {
|
||||
let fragment_point = fragment_point * device_pixels_per_page_px.get();
|
||||
events::move(root_layer.clone(),
|
||||
pipeline_id,
|
||||
layer_id,
|
||||
Point2D::from_untyped(&fragment_point),
|
||||
window_size)
|
||||
})
|
||||
}
|
||||
None => fail!("Compositor: Tried to scroll to fragment without root layer."),
|
||||
};
|
||||
|
||||
self.recomposite_if(needs_recomposite);
|
||||
}
|
||||
|
||||
fn set_layer_clip_rect(&mut self,
|
||||
pipeline_id: PipelineId,
|
||||
layer_id: LayerId,
|
||||
new_rect_in_page_coordinates: Rect<f32>) {
|
||||
let new_rect_in_layer_coordinates =
|
||||
self.convert_page_rect_to_layer_coordinates(new_rect_in_page_coordinates);
|
||||
let new_rect_in_layer_coordinates = Rect::from_untyped(&new_rect_in_layer_coordinates);
|
||||
|
||||
match self.find_layer_with_pipeline_and_layer_id(pipeline_id, layer_id) {
|
||||
Some(ref layer) => *layer.bounds.borrow_mut() = new_rect_in_layer_coordinates,
|
||||
None => fail!("compositor received SetLayerClipRect for nonexistent layer"),
|
||||
};
|
||||
|
||||
self.send_buffer_requests_for_all_layers();
|
||||
}
|
||||
|
||||
fn paint(&mut self,
|
||||
pipeline_id: PipelineId,
|
||||
layer_id: LayerId,
|
||||
new_layer_buffer_set: Box<LayerBufferSet>,
|
||||
epoch: Epoch) {
|
||||
debug!("compositor received new frame");
|
||||
|
||||
// From now on, if we destroy the buffers, they will leak.
|
||||
let mut new_layer_buffer_set = new_layer_buffer_set;
|
||||
new_layer_buffer_set.mark_will_leak();
|
||||
|
||||
match self.find_layer_with_pipeline_and_layer_id(pipeline_id, layer_id) {
|
||||
Some(ref layer) => {
|
||||
assert!(CompositorData::add_buffers(layer.clone(), new_layer_buffer_set, epoch));
|
||||
self.recomposite = true;
|
||||
}
|
||||
None => {
|
||||
// FIXME: This may potentially be triggered by a race condition where a
|
||||
// buffers are being rendered but the layer is removed before rendering
|
||||
// completes.
|
||||
fail!("compositor given paint command for non-existent layer");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn scroll_fragment_to_point(&mut self,
|
||||
pipeline_id: PipelineId,
|
||||
layer_id: LayerId,
|
||||
point: Point2D<f32>) {
|
||||
|
||||
let device_pixels_per_page_px = self.device_pixels_per_page_px();
|
||||
let device_point = point * device_pixels_per_page_px.get();
|
||||
let window_size = self.window_size.as_f32();
|
||||
|
||||
let (ask, move): (bool, bool) = match self.scene.root {
|
||||
Some(ref layer) if layer.extra_data.borrow().pipeline.id == pipeline_id => {
|
||||
(true,
|
||||
events::move(layer.clone(),
|
||||
pipeline_id,
|
||||
layer_id,
|
||||
Point2D::from_untyped(&device_point),
|
||||
window_size))
|
||||
}
|
||||
Some(_) | None => {
|
||||
self.fragment_point = Some(point);
|
||||
|
||||
(false, false)
|
||||
}
|
||||
};
|
||||
|
||||
if ask {
|
||||
self.recomposite_if(move);
|
||||
self.send_buffer_requests_for_all_layers();
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_window_message(&mut self, event: WindowEvent) {
|
||||
match event {
|
||||
IdleWindowEvent => {}
|
||||
|
||||
RefreshWindowEvent => {
|
||||
self.recomposite = true;
|
||||
}
|
||||
|
||||
ResizeWindowEvent(size) => {
|
||||
self.on_resize_window_event(size);
|
||||
}
|
||||
|
||||
LoadUrlWindowEvent(url_string) => {
|
||||
self.on_load_url_window_event(url_string);
|
||||
}
|
||||
|
||||
MouseWindowEventClass(mouse_window_event) => {
|
||||
self.on_mouse_window_event_class(mouse_window_event);
|
||||
}
|
||||
|
||||
MouseWindowMoveEventClass(cursor) => {
|
||||
self.on_mouse_window_move_event_class(cursor);
|
||||
}
|
||||
|
||||
ScrollWindowEvent(delta, cursor) => {
|
||||
self.on_scroll_window_event(delta, cursor);
|
||||
}
|
||||
|
||||
ZoomWindowEvent(magnification) => {
|
||||
self.on_zoom_window_event(magnification);
|
||||
}
|
||||
|
||||
PinchZoomWindowEvent(magnification) => {
|
||||
self.on_pinch_zoom_window_event(magnification);
|
||||
}
|
||||
|
||||
NavigationWindowEvent(direction) => {
|
||||
self.on_navigation_window_event(direction);
|
||||
}
|
||||
|
||||
FinishedWindowEvent => {
|
||||
let exit = self.opts.exit_after_load;
|
||||
if exit {
|
||||
debug!("shutting down the constellation for FinishedWindowEvent");
|
||||
let ConstellationChan(ref chan) = self.constellation_chan;
|
||||
chan.send(ExitMsg);
|
||||
self.shutdown_state = ShuttingDown;
|
||||
}
|
||||
}
|
||||
|
||||
QuitWindowEvent => {
|
||||
debug!("shutting down the constellation for QuitWindowEvent");
|
||||
let ConstellationChan(ref chan) = self.constellation_chan;
|
||||
chan.send(ExitMsg);
|
||||
self.shutdown_state = ShuttingDown;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn on_resize_window_event(&mut self, new_size: TypedSize2D<DevicePixel, uint>) {
|
||||
// A size change could also mean a resolution change.
|
||||
let new_hidpi_factor = self.window.hidpi_factor();
|
||||
if self.hidpi_factor != new_hidpi_factor {
|
||||
self.hidpi_factor = new_hidpi_factor;
|
||||
self.update_zoom_transform();
|
||||
}
|
||||
if self.window_size != new_size {
|
||||
debug!("osmain: window resized to {:?}", new_size);
|
||||
self.window_size = new_size;
|
||||
self.send_window_size();
|
||||
} else {
|
||||
debug!("osmain: dropping window resize since size is still {:?}", new_size);
|
||||
}
|
||||
}
|
||||
|
||||
fn on_load_url_window_event(&mut self, url_string: String) {
|
||||
debug!("osmain: loading URL `{:s}`", url_string);
|
||||
self.load_complete = false;
|
||||
let root_pipeline_id = match self.scene.root {
|
||||
Some(ref layer) => layer.extra_data.borrow().pipeline.id.clone(),
|
||||
None => fail!("Compositor: Received LoadUrlWindowEvent without initialized compositor \
|
||||
layers"),
|
||||
};
|
||||
|
||||
let msg = LoadUrlMsg(root_pipeline_id, Url::parse(url_string.as_slice()).unwrap());
|
||||
let ConstellationChan(ref chan) = self.constellation_chan;
|
||||
chan.send(msg);
|
||||
}
|
||||
|
||||
fn on_mouse_window_event_class(&self, mouse_window_event: MouseWindowEvent) {
|
||||
let scale = self.device_pixels_per_page_px();
|
||||
let point = match mouse_window_event {
|
||||
MouseWindowClickEvent(_, p) => p,
|
||||
MouseWindowMouseDownEvent(_, p) => p,
|
||||
MouseWindowMouseUpEvent(_, p) => p,
|
||||
};
|
||||
for layer in self.scene.root.iter() {
|
||||
events::send_mouse_event(layer.clone(), mouse_window_event, point, scale);
|
||||
}
|
||||
}
|
||||
|
||||
fn on_mouse_window_move_event_class(&self, cursor: TypedPoint2D<DevicePixel, f32>) {
|
||||
let scale = self.device_pixels_per_page_px();
|
||||
for layer in self.scene.root.iter() {
|
||||
events::send_mouse_move_event(layer.clone(), cursor / scale);
|
||||
}
|
||||
}
|
||||
|
||||
fn on_scroll_window_event(&mut self,
|
||||
delta: TypedPoint2D<DevicePixel, f32>,
|
||||
cursor: TypedPoint2D<DevicePixel, i32>) {
|
||||
let mut scroll = false;
|
||||
let window_size = self.window_size.as_f32();
|
||||
match self.scene.root {
|
||||
Some(ref mut layer) => {
|
||||
scroll = events::handle_scroll_event(layer.clone(),
|
||||
delta,
|
||||
cursor.as_f32(),
|
||||
window_size) || scroll;
|
||||
}
|
||||
None => { }
|
||||
}
|
||||
self.recomposite_if(scroll);
|
||||
self.send_buffer_requests_for_all_layers();
|
||||
}
|
||||
|
||||
fn device_pixels_per_screen_px(&self) -> ScaleFactor<ScreenPx, DevicePixel, f32> {
|
||||
match self.opts.device_pixels_per_px {
|
||||
Some(device_pixels_per_px) => device_pixels_per_px,
|
||||
None => match self.opts.output_file {
|
||||
Some(_) => ScaleFactor(1.0),
|
||||
None => self.hidpi_factor
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn device_pixels_per_page_px(&self) -> ScaleFactor<PagePx, DevicePixel, f32> {
|
||||
self.viewport_zoom * self.page_zoom * self.device_pixels_per_screen_px()
|
||||
}
|
||||
|
||||
fn update_zoom_transform(&mut self) {
|
||||
let scale = self.device_pixels_per_page_px();
|
||||
self.scene.transform = identity().scale(scale.get(), scale.get(), 1f32);
|
||||
}
|
||||
|
||||
fn on_zoom_window_event(&mut self, magnification: f32) {
|
||||
self.page_zoom = ScaleFactor((self.page_zoom.get() * magnification).max(1.0));
|
||||
self.update_zoom_transform();
|
||||
self.send_window_size();
|
||||
}
|
||||
|
||||
fn on_pinch_zoom_window_event(&mut self, magnification: f32) {
|
||||
self.zoom_action = true;
|
||||
self.zoom_time = precise_time_s();
|
||||
let old_viewport_zoom = self.viewport_zoom;
|
||||
|
||||
self.viewport_zoom = ScaleFactor((self.viewport_zoom.get() * magnification).max(1.0));
|
||||
let viewport_zoom = self.viewport_zoom;
|
||||
|
||||
self.update_zoom_transform();
|
||||
|
||||
// Scroll as needed
|
||||
let window_size = self.window_size.as_f32();
|
||||
let page_delta: TypedPoint2D<PagePx, f32> = TypedPoint2D(
|
||||
window_size.width.get() * (viewport_zoom.inv() - old_viewport_zoom.inv()).get() * 0.5,
|
||||
window_size.height.get() * (viewport_zoom.inv() - old_viewport_zoom.inv()).get() * 0.5);
|
||||
|
||||
let delta = page_delta * self.device_pixels_per_page_px();
|
||||
let cursor = TypedPoint2D(-1f32, -1f32); // Make sure this hits the base layer.
|
||||
match self.scene.root {
|
||||
Some(ref mut layer) => {
|
||||
events::handle_scroll_event(layer.clone(),
|
||||
delta,
|
||||
cursor,
|
||||
window_size);
|
||||
}
|
||||
None => { }
|
||||
}
|
||||
|
||||
self.recomposite = true;
|
||||
}
|
||||
|
||||
fn on_navigation_window_event(&self, direction: WindowNavigateMsg) {
|
||||
let direction = match direction {
|
||||
windowing::Forward => constellation_msg::Forward,
|
||||
windowing::Back => constellation_msg::Back,
|
||||
};
|
||||
let ConstellationChan(ref chan) = self.constellation_chan;
|
||||
chan.send(NavigateMsg(direction))
|
||||
}
|
||||
|
||||
fn convert_buffer_requests_to_pipeline_requests_map(&self,
|
||||
requests: Vec<(Rc<Layer<CompositorData>>,
|
||||
Vec<BufferRequest>)>) ->
|
||||
HashMap<PipelineId, (RenderChan,
|
||||
Vec<RenderRequest>)> {
|
||||
let scale = self.device_pixels_per_page_px();
|
||||
let mut results:
|
||||
HashMap<PipelineId, (RenderChan, Vec<RenderRequest>)> = HashMap::new();
|
||||
|
||||
for (layer, mut layer_requests) in requests.move_iter() {
|
||||
let pipeline_id = layer.extra_data.borrow().pipeline.id;
|
||||
let &(_, ref mut vec) = results.find_or_insert_with(pipeline_id, |_| {
|
||||
(layer.extra_data.borrow().pipeline.render_chan.clone(), Vec::new())
|
||||
});
|
||||
|
||||
// All the BufferRequests are in layer/device coordinates, but the render task
|
||||
// wants to know the page coordinates. We scale them before sending them.
|
||||
for request in layer_requests.mut_iter() {
|
||||
request.page_rect = request.page_rect / scale.get();
|
||||
}
|
||||
|
||||
vec.push(RenderRequest {
|
||||
buffer_requests: layer_requests,
|
||||
scale: scale.get(),
|
||||
layer_id: layer.extra_data.borrow().id,
|
||||
epoch: layer.extra_data.borrow().epoch,
|
||||
});
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
fn send_back_unused_buffers(&mut self) {
|
||||
match self.root_pipeline {
|
||||
Some(ref pipeline) => {
|
||||
let unused_buffers = self.scene.collect_unused_buffers();
|
||||
let have_unused_buffers = unused_buffers.len() > 0;
|
||||
self.recomposite = self.recomposite || have_unused_buffers;
|
||||
if have_unused_buffers {
|
||||
let message = UnusedBufferMsg(unused_buffers);
|
||||
let _ = pipeline.render_chan.send_opt(message);
|
||||
}
|
||||
},
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn send_buffer_requests_for_all_layers(&mut self) {
|
||||
let mut layers_and_requests = Vec::new();
|
||||
self.scene.get_buffer_requests(&mut layers_and_requests,
|
||||
Rect(TypedPoint2D(0f32, 0f32), self.window_size.as_f32()));
|
||||
|
||||
// Return unused tiles first, so that they can be reused by any new BufferRequests.
|
||||
self.send_back_unused_buffers();
|
||||
|
||||
if layers_and_requests.len() == 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
// We want to batch requests for each pipeline to avoid race conditions
|
||||
// when handling the resulting BufferRequest responses.
|
||||
let pipeline_requests =
|
||||
self.convert_buffer_requests_to_pipeline_requests_map(layers_and_requests);
|
||||
|
||||
let mut num_render_msgs_sent = 0;
|
||||
for (_pipeline_id, (chan, requests)) in pipeline_requests.move_iter() {
|
||||
num_render_msgs_sent += 1;
|
||||
let _ = chan.send_opt(RenderMsg(requests));
|
||||
}
|
||||
|
||||
self.add_outstanding_render_msg(num_render_msgs_sent);
|
||||
}
|
||||
|
||||
fn composite(&mut self) {
|
||||
profile(time::CompositingCategory, self.time_profiler_chan.clone(), || {
|
||||
debug!("compositor: compositing");
|
||||
// Adjust the layer dimensions as necessary to correspond to the size of the window.
|
||||
self.scene.size = self.window_size.as_f32().to_untyped();
|
||||
// Render the scene.
|
||||
match self.scene.root {
|
||||
Some(ref layer) => {
|
||||
self.scene.background_color.r = layer.extra_data.borrow().background_color.r;
|
||||
self.scene.background_color.g = layer.extra_data.borrow().background_color.g;
|
||||
self.scene.background_color.b = layer.extra_data.borrow().background_color.b;
|
||||
self.scene.background_color.a = layer.extra_data.borrow().background_color.a;
|
||||
rendergl::render_scene(layer.clone(), self.context, &self.scene);
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
});
|
||||
|
||||
// Render to PNG. We must read from the back buffer (ie, before
|
||||
// self.window.present()) as OpenGL ES 2 does not have glReadBuffer().
|
||||
if self.load_complete && self.ready_state == FinishedLoading
|
||||
&& self.opts.output_file.is_some() && !self.has_outstanding_render_msgs() {
|
||||
let (width, height) = (self.window_size.width.get(), self.window_size.height.get());
|
||||
let path = from_str::<Path>(self.opts.output_file.get_ref().as_slice()).unwrap();
|
||||
let mut pixels = gl2::read_pixels(0, 0,
|
||||
width as gl2::GLsizei,
|
||||
height as gl2::GLsizei,
|
||||
gl2::RGB, gl2::UNSIGNED_BYTE);
|
||||
// flip image vertically (texture is upside down)
|
||||
let orig_pixels = pixels.clone();
|
||||
let stride = width * 3;
|
||||
for y in range(0, height) {
|
||||
let dst_start = y * stride;
|
||||
let src_start = (height - y - 1) * stride;
|
||||
unsafe {
|
||||
let src_slice = orig_pixels.slice(src_start, src_start + stride);
|
||||
pixels.mut_slice(dst_start, dst_start + stride)
|
||||
.copy_memory(src_slice.slice_to(stride));
|
||||
}
|
||||
}
|
||||
let mut img = png::Image {
|
||||
width: width as u32,
|
||||
height: height as u32,
|
||||
pixels: png::RGB8(pixels),
|
||||
};
|
||||
let res = png::store_png(&mut img, &path);
|
||||
assert!(res.is_ok());
|
||||
|
||||
debug!("shutting down the constellation after generating an output file");
|
||||
let ConstellationChan(ref chan) = self.constellation_chan;
|
||||
chan.send(ExitMsg);
|
||||
self.shutdown_state = ShuttingDown;
|
||||
}
|
||||
|
||||
self.window.present();
|
||||
|
||||
let exit = self.opts.exit_after_load;
|
||||
if exit {
|
||||
debug!("shutting down the constellation for exit_after_load");
|
||||
let ConstellationChan(ref chan) = self.constellation_chan;
|
||||
chan.send(ExitMsg);
|
||||
}
|
||||
}
|
||||
|
||||
fn recomposite_if(&mut self, result: bool) {
|
||||
self.recomposite = result || self.recomposite;
|
||||
}
|
||||
}
|
||||
|
183
components/compositing/compositor_data.rs
Normal file
183
components/compositing/compositor_data.rs
Normal file
|
@ -0,0 +1,183 @@
|
|||
/* 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 compositor_task::LayerProperties;
|
||||
use events;
|
||||
use pipeline::CompositionPipeline;
|
||||
|
||||
use azure::azure_hl::Color;
|
||||
use geom::point::TypedPoint2D;
|
||||
use geom::size::{Size2D, TypedSize2D};
|
||||
use geom::rect::Rect;
|
||||
use gfx::render_task::UnusedBufferMsg;
|
||||
use layers::geometry::DevicePixel;
|
||||
use layers::layers::{Layer, LayerBufferSet};
|
||||
use layers::platform::surface::NativeSurfaceMethods;
|
||||
use servo_msg::compositor_msg::{Epoch, LayerId};
|
||||
use servo_msg::compositor_msg::ScrollPolicy;
|
||||
use servo_msg::constellation_msg::PipelineId;
|
||||
use std::rc::Rc;
|
||||
|
||||
pub struct CompositorData {
|
||||
/// This layer's pipeline. BufferRequests and mouse events will be sent through this.
|
||||
pub pipeline: CompositionPipeline,
|
||||
|
||||
/// The ID of this layer within the pipeline.
|
||||
pub id: LayerId,
|
||||
|
||||
/// The behavior of this layer when a scroll message is received.
|
||||
pub wants_scroll_events: WantsScrollEventsFlag,
|
||||
|
||||
/// Whether an ancestor layer that receives scroll events moves this layer.
|
||||
pub scroll_policy: ScrollPolicy,
|
||||
|
||||
/// The color to use for the unrendered-content void
|
||||
pub background_color: Color,
|
||||
|
||||
/// A monotonically increasing counter that keeps track of the current epoch.
|
||||
/// add_buffer() calls that don't match the current epoch will be ignored.
|
||||
pub epoch: Epoch,
|
||||
}
|
||||
|
||||
#[deriving(PartialEq, Clone)]
|
||||
pub enum WantsScrollEventsFlag {
|
||||
WantsScrollEvents,
|
||||
DoesntWantScrollEvents,
|
||||
}
|
||||
|
||||
impl CompositorData {
|
||||
pub fn new_layer(pipeline: CompositionPipeline,
|
||||
layer_properties: LayerProperties,
|
||||
wants_scroll_events: WantsScrollEventsFlag,
|
||||
tile_size: uint)
|
||||
-> Rc<Layer<CompositorData>> {
|
||||
let new_compositor_data = CompositorData {
|
||||
pipeline: pipeline,
|
||||
id: layer_properties.id,
|
||||
wants_scroll_events: wants_scroll_events,
|
||||
scroll_policy: layer_properties.scroll_policy,
|
||||
background_color: layer_properties.background_color,
|
||||
epoch: layer_properties.epoch,
|
||||
};
|
||||
|
||||
Rc::new(Layer::new(Rect::from_untyped(&layer_properties.rect),
|
||||
tile_size, new_compositor_data))
|
||||
}
|
||||
|
||||
pub fn update_layer(layer: Rc<Layer<CompositorData>>, layer_properties: LayerProperties) {
|
||||
layer.extra_data.borrow_mut().epoch = layer_properties.epoch;
|
||||
layer.extra_data.borrow_mut().background_color = layer_properties.background_color;
|
||||
|
||||
let size: TypedSize2D<DevicePixel, f32> = Size2D::from_untyped(&layer_properties.rect.size);
|
||||
layer.resize(size);
|
||||
layer.contents_changed();
|
||||
|
||||
// Call scroll for bounds checking if the page shrunk. Use (-1, -1) as the
|
||||
// cursor position to make sure the scroll isn't propagated downwards.
|
||||
events::handle_scroll_event(layer.clone(),
|
||||
TypedPoint2D(0f32, 0f32),
|
||||
TypedPoint2D(-1f32, -1f32),
|
||||
size);
|
||||
}
|
||||
|
||||
pub fn find_layer_with_pipeline_and_layer_id(layer: Rc<Layer<CompositorData>>,
|
||||
pipeline_id: PipelineId,
|
||||
layer_id: LayerId)
|
||||
-> Option<Rc<Layer<CompositorData>>> {
|
||||
if layer.extra_data.borrow().pipeline.id == pipeline_id &&
|
||||
layer.extra_data.borrow().id == layer_id {
|
||||
return Some(layer.clone());
|
||||
}
|
||||
|
||||
for kid in layer.children().iter() {
|
||||
match CompositorData::find_layer_with_pipeline_and_layer_id(kid.clone(),
|
||||
pipeline_id,
|
||||
layer_id) {
|
||||
v @ Some(_) => { return v; }
|
||||
None => { }
|
||||
}
|
||||
}
|
||||
|
||||
return None;
|
||||
}
|
||||
|
||||
// Add LayerBuffers to the specified layer. Returns the layer buffer set back if the layer that
|
||||
// matches the given pipeline ID was not found; otherwise returns None and consumes the layer
|
||||
// buffer set.
|
||||
//
|
||||
// If the epoch of the message does not match the layer's epoch, the message is ignored, the
|
||||
// layer buffer set is consumed, and None is returned.
|
||||
pub fn add_buffers(layer: Rc<Layer<CompositorData>>,
|
||||
new_buffers: Box<LayerBufferSet>,
|
||||
epoch: Epoch)
|
||||
-> bool {
|
||||
if layer.extra_data.borrow().epoch != epoch {
|
||||
debug!("add_buffers: compositor epoch mismatch: {:?} != {:?}, id: {:?}",
|
||||
layer.extra_data.borrow().epoch,
|
||||
epoch,
|
||||
layer.extra_data.borrow().pipeline.id);
|
||||
let msg = UnusedBufferMsg(new_buffers.buffers);
|
||||
let _ = layer.extra_data.borrow().pipeline.render_chan.send_opt(msg);
|
||||
return false;
|
||||
}
|
||||
|
||||
{
|
||||
for buffer in new_buffers.buffers.move_iter().rev() {
|
||||
layer.add_buffer(buffer);
|
||||
}
|
||||
|
||||
let unused_buffers = layer.collect_unused_buffers();
|
||||
if !unused_buffers.is_empty() { // send back unused buffers
|
||||
let msg = UnusedBufferMsg(unused_buffers);
|
||||
let _ = layer.extra_data.borrow().pipeline.render_chan.send_opt(msg);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Destroys all layer tiles, sending the buffers back to the renderer to be destroyed or
|
||||
/// reused.
|
||||
fn clear(layer: Rc<Layer<CompositorData>>) {
|
||||
let mut buffers = layer.collect_buffers();
|
||||
|
||||
if !buffers.is_empty() {
|
||||
// We have no way of knowing without a race whether the render task is even up and
|
||||
// running, but mark the buffers as not leaking. If the render task died, then the
|
||||
// buffers are going to be cleaned up.
|
||||
for buffer in buffers.mut_iter() {
|
||||
buffer.mark_wont_leak()
|
||||
}
|
||||
|
||||
let _ = layer.extra_data.borrow().pipeline.render_chan.send_opt(UnusedBufferMsg(buffers));
|
||||
}
|
||||
}
|
||||
|
||||
/// Destroys tiles for this layer and all descendent layers, sending the buffers back to the
|
||||
/// renderer to be destroyed or reused.
|
||||
pub fn clear_all_tiles(layer: Rc<Layer<CompositorData>>) {
|
||||
CompositorData::clear(layer.clone());
|
||||
for kid in layer.children().iter() {
|
||||
CompositorData::clear_all_tiles(kid.clone());
|
||||
}
|
||||
}
|
||||
|
||||
/// Destroys all tiles of all layers, including children, *without* sending them back to the
|
||||
/// renderer. You must call this only when the render task is destined to be going down;
|
||||
/// otherwise, you will leak tiles.
|
||||
///
|
||||
/// This is used during shutdown, when we know the render task is going away.
|
||||
pub fn forget_all_tiles(layer: Rc<Layer<CompositorData>>) {
|
||||
let tiles = layer.collect_buffers();
|
||||
for tile in tiles.move_iter() {
|
||||
let mut tile = tile;
|
||||
tile.mark_wont_leak()
|
||||
}
|
||||
|
||||
for kid in layer.children().iter() {
|
||||
CompositorData::forget_all_tiles(kid.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
247
components/compositing/compositor_task.rs
Normal file
247
components/compositing/compositor_task.rs
Normal file
|
@ -0,0 +1,247 @@
|
|||
/* 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/. */
|
||||
|
||||
pub use windowing;
|
||||
|
||||
use compositor;
|
||||
use headless;
|
||||
pub use constellation::SendableFrameTree;
|
||||
use windowing::{ApplicationMethods, WindowMethods};
|
||||
use platform::Application;
|
||||
|
||||
use azure::azure_hl::{SourceSurfaceMethods, Color};
|
||||
use geom::point::Point2D;
|
||||
use geom::rect::Rect;
|
||||
use geom::size::Size2D;
|
||||
use layers::platform::surface::{NativeCompositingGraphicsContext, NativeGraphicsMetadata};
|
||||
use layers::layers::LayerBufferSet;
|
||||
use servo_msg::compositor_msg::{Epoch, LayerId, LayerMetadata, ReadyState};
|
||||
use servo_msg::compositor_msg::{RenderListener, RenderState, ScriptListener, ScrollPolicy};
|
||||
use servo_msg::constellation_msg::{ConstellationChan, PipelineId};
|
||||
use servo_util::memory::MemoryProfilerChan;
|
||||
use servo_util::opts::Opts;
|
||||
use servo_util::time::TimeProfilerChan;
|
||||
use std::comm::{channel, Sender, Receiver};
|
||||
|
||||
use url::Url;
|
||||
|
||||
#[cfg(target_os="linux")]
|
||||
use azure::azure_hl;
|
||||
|
||||
/// The implementation of the layers-based compositor.
|
||||
#[deriving(Clone)]
|
||||
pub struct CompositorChan {
|
||||
/// A channel on which messages can be sent to the compositor.
|
||||
pub chan: Sender<Msg>,
|
||||
}
|
||||
|
||||
/// Implementation of the abstract `ScriptListener` interface.
|
||||
impl ScriptListener for CompositorChan {
|
||||
fn set_ready_state(&self, ready_state: ReadyState) {
|
||||
let msg = ChangeReadyState(ready_state);
|
||||
self.chan.send(msg);
|
||||
}
|
||||
|
||||
fn scroll_fragment_point(&self,
|
||||
pipeline_id: PipelineId,
|
||||
layer_id: LayerId,
|
||||
point: Point2D<f32>) {
|
||||
self.chan.send(ScrollFragmentPoint(pipeline_id, layer_id, point));
|
||||
}
|
||||
|
||||
fn close(&self) {
|
||||
let (chan, port) = channel();
|
||||
self.chan.send(Exit(chan));
|
||||
port.recv();
|
||||
}
|
||||
|
||||
fn dup(&self) -> Box<ScriptListener> {
|
||||
box self.clone() as Box<ScriptListener>
|
||||
}
|
||||
}
|
||||
|
||||
pub struct LayerProperties {
|
||||
pub pipeline_id: PipelineId,
|
||||
pub epoch: Epoch,
|
||||
pub id: LayerId,
|
||||
pub rect: Rect<f32>,
|
||||
pub background_color: Color,
|
||||
pub scroll_policy: ScrollPolicy,
|
||||
}
|
||||
|
||||
impl LayerProperties {
|
||||
fn new(pipeline_id: PipelineId, epoch: Epoch, metadata: &LayerMetadata) -> LayerProperties {
|
||||
LayerProperties {
|
||||
pipeline_id: pipeline_id,
|
||||
epoch: epoch,
|
||||
id: metadata.id,
|
||||
rect: Rect(Point2D(metadata.position.origin.x as f32,
|
||||
metadata.position.origin.y as f32),
|
||||
Size2D(metadata.position.size.width as f32,
|
||||
metadata.position.size.height as f32)),
|
||||
background_color: metadata.background_color,
|
||||
scroll_policy: metadata.scroll_policy,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Implementation of the abstract `RenderListener` interface.
|
||||
impl RenderListener for CompositorChan {
|
||||
fn get_graphics_metadata(&self) -> Option<NativeGraphicsMetadata> {
|
||||
let (chan, port) = channel();
|
||||
self.chan.send(GetGraphicsMetadata(chan));
|
||||
port.recv()
|
||||
}
|
||||
|
||||
fn paint(&self,
|
||||
pipeline_id: PipelineId,
|
||||
epoch: Epoch,
|
||||
replies: Vec<(LayerId, Box<LayerBufferSet>)>) {
|
||||
self.chan.send(Paint(pipeline_id, epoch, replies));
|
||||
}
|
||||
|
||||
fn initialize_layers_for_pipeline(&self,
|
||||
pipeline_id: PipelineId,
|
||||
metadata: Vec<LayerMetadata>,
|
||||
epoch: Epoch) {
|
||||
// FIXME(#2004, pcwalton): This assumes that the first layer determines the page size, and
|
||||
// that all other layers are immediate children of it. This is sufficient to handle
|
||||
// `position: fixed` but will not be sufficient to handle `overflow: scroll` or transforms.
|
||||
let mut first = true;
|
||||
for metadata in metadata.iter() {
|
||||
let layer_properties = LayerProperties::new(pipeline_id, epoch, metadata);
|
||||
if first {
|
||||
self.chan.send(CreateOrUpdateRootLayer(layer_properties));
|
||||
first = false
|
||||
} else {
|
||||
self.chan.send(CreateOrUpdateDescendantLayer(layer_properties));
|
||||
}
|
||||
|
||||
self.chan.send(SetLayerClipRect(pipeline_id, metadata.id, layer_properties.rect));
|
||||
}
|
||||
}
|
||||
|
||||
fn render_msg_discarded(&self) {
|
||||
self.chan.send(RenderMsgDiscarded);
|
||||
}
|
||||
|
||||
fn set_render_state(&self, render_state: RenderState) {
|
||||
self.chan.send(ChangeRenderState(render_state))
|
||||
}
|
||||
}
|
||||
|
||||
impl CompositorChan {
|
||||
pub fn new() -> (Receiver<Msg>, CompositorChan) {
|
||||
let (chan, port) = channel();
|
||||
let compositor_chan = CompositorChan {
|
||||
chan: chan,
|
||||
};
|
||||
(port, compositor_chan)
|
||||
}
|
||||
|
||||
pub fn send(&self, msg: Msg) {
|
||||
self.chan.send(msg);
|
||||
}
|
||||
}
|
||||
/// Messages from the painting task and the constellation task to the compositor task.
|
||||
pub enum Msg {
|
||||
/// Requests that the compositor shut down.
|
||||
Exit(Sender<()>),
|
||||
|
||||
/// Informs the compositor that the constellation has completed shutdown.
|
||||
/// Required because the constellation can have pending calls to make (e.g. SetIds)
|
||||
/// at the time that we send it an ExitMsg.
|
||||
ShutdownComplete,
|
||||
|
||||
/// Requests the compositor's graphics metadata. Graphics metadata is what the renderer needs
|
||||
/// to create surfaces that the compositor can see. On Linux this is the X display; on Mac this
|
||||
/// is the pixel format.
|
||||
///
|
||||
/// The headless compositor returns `None`.
|
||||
GetGraphicsMetadata(Sender<Option<NativeGraphicsMetadata>>),
|
||||
|
||||
/// Tells the compositor to create the root layer for a pipeline if necessary (i.e. if no layer
|
||||
/// with that ID exists).
|
||||
CreateOrUpdateRootLayer(LayerProperties),
|
||||
/// Tells the compositor to create a descendant layer for a pipeline if necessary (i.e. if no
|
||||
/// layer with that ID exists).
|
||||
CreateOrUpdateDescendantLayer(LayerProperties),
|
||||
/// Alerts the compositor that the specified layer's clipping rect has changed.
|
||||
SetLayerClipRect(PipelineId, LayerId, Rect<f32>),
|
||||
/// Scroll a page in a window
|
||||
ScrollFragmentPoint(PipelineId, LayerId, Point2D<f32>),
|
||||
/// Requests that the compositor paint the given layer buffer set for the given page size.
|
||||
Paint(PipelineId, Epoch, Vec<(LayerId, Box<LayerBufferSet>)>),
|
||||
/// Alerts the compositor to the current status of page loading.
|
||||
ChangeReadyState(ReadyState),
|
||||
/// Alerts the compositor to the current status of rendering.
|
||||
ChangeRenderState(RenderState),
|
||||
/// Alerts the compositor that the RenderMsg has been discarded.
|
||||
RenderMsgDiscarded,
|
||||
/// Sets the channel to the current layout and render tasks, along with their id
|
||||
SetIds(SendableFrameTree, Sender<()>, ConstellationChan),
|
||||
/// The load of a page for a given URL has completed.
|
||||
LoadComplete(PipelineId, Url),
|
||||
}
|
||||
|
||||
pub enum CompositorMode {
|
||||
Windowed(Application),
|
||||
Headless
|
||||
}
|
||||
|
||||
pub struct CompositorTask {
|
||||
pub mode: CompositorMode,
|
||||
}
|
||||
|
||||
impl CompositorTask {
|
||||
fn new(is_headless: bool) -> CompositorTask {
|
||||
let mode: CompositorMode = if is_headless {
|
||||
Headless
|
||||
} else {
|
||||
Windowed(ApplicationMethods::new())
|
||||
};
|
||||
|
||||
CompositorTask {
|
||||
mode: mode
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a graphics context. Platform-specific.
|
||||
///
|
||||
/// FIXME(pcwalton): Probably could be less platform-specific, using the metadata abstraction.
|
||||
#[cfg(target_os="linux")]
|
||||
pub fn create_graphics_context() -> NativeCompositingGraphicsContext {
|
||||
NativeCompositingGraphicsContext::from_display(azure_hl::current_display())
|
||||
}
|
||||
#[cfg(not(target_os="linux"))]
|
||||
pub fn create_graphics_context() -> NativeCompositingGraphicsContext {
|
||||
NativeCompositingGraphicsContext::new()
|
||||
}
|
||||
|
||||
pub fn create(opts: Opts,
|
||||
port: Receiver<Msg>,
|
||||
constellation_chan: ConstellationChan,
|
||||
time_profiler_chan: TimeProfilerChan,
|
||||
memory_profiler_chan: MemoryProfilerChan) {
|
||||
|
||||
let compositor = CompositorTask::new(opts.headless);
|
||||
|
||||
match compositor.mode {
|
||||
Windowed(ref app) => {
|
||||
compositor::IOCompositor::create(app,
|
||||
opts,
|
||||
port,
|
||||
constellation_chan.clone(),
|
||||
time_profiler_chan,
|
||||
memory_profiler_chan)
|
||||
}
|
||||
Headless => {
|
||||
headless::NullCompositor::create(port,
|
||||
constellation_chan.clone(),
|
||||
time_profiler_chan,
|
||||
memory_profiler_chan)
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
870
components/compositing/constellation.rs
Normal file
870
components/compositing/constellation.rs
Normal file
|
@ -0,0 +1,870 @@
|
|||
/* 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 compositor_task::{CompositorChan, LoadComplete, ShutdownComplete, SetLayerClipRect, SetIds};
|
||||
use std::collections::hashmap::{HashMap, HashSet};
|
||||
use geom::rect::{Rect, TypedRect};
|
||||
use geom::scale_factor::ScaleFactor;
|
||||
use geom::size::TypedSize2D;
|
||||
use gfx::render_task;
|
||||
use libc;
|
||||
use pipeline::{Pipeline, CompositionPipeline};
|
||||
use layout_traits::{LayoutControlChan, LayoutTaskFactory, ExitNowMsg};
|
||||
use script_traits::{ResizeMsg, ResizeInactiveMsg, ExitPipelineMsg};
|
||||
use script_traits::{ScriptControlChan, ScriptTaskFactory};
|
||||
use servo_msg::compositor_msg::LayerId;
|
||||
use servo_msg::constellation_msg::{ConstellationChan, ExitMsg, FailureMsg, Failure, FrameRectMsg};
|
||||
use servo_msg::constellation_msg::{IFrameSandboxState, IFrameUnsandboxed, InitLoadUrlMsg};
|
||||
use servo_msg::constellation_msg::{LoadCompleteMsg, LoadIframeUrlMsg, LoadUrlMsg, Msg, NavigateMsg};
|
||||
use servo_msg::constellation_msg::{NavigationType, PipelineId, RendererReadyMsg, ResizedWindowMsg};
|
||||
use servo_msg::constellation_msg::{SubpageId, WindowSizeData};
|
||||
use servo_msg::constellation_msg;
|
||||
use servo_net::image_cache_task::{ImageCacheTask, ImageCacheTaskClient};
|
||||
use gfx::font_cache_task::FontCacheTask;
|
||||
use servo_net::resource_task::ResourceTask;
|
||||
use servo_net::resource_task;
|
||||
use servo_util::geometry::PagePx;
|
||||
use servo_util::opts::Opts;
|
||||
use servo_util::time::TimeProfilerChan;
|
||||
use servo_util::task::spawn_named;
|
||||
use std::cell::RefCell;
|
||||
use std::mem::replace;
|
||||
use std::io;
|
||||
use std::rc::Rc;
|
||||
use url::Url;
|
||||
|
||||
/// Maintains the pipelines and navigation context and grants permission to composite
|
||||
pub struct Constellation<LTF, STF> {
|
||||
pub chan: ConstellationChan,
|
||||
pub request_port: Receiver<Msg>,
|
||||
pub compositor_chan: CompositorChan,
|
||||
pub resource_task: ResourceTask,
|
||||
pub image_cache_task: ImageCacheTask,
|
||||
pipelines: HashMap<PipelineId, Rc<Pipeline>>,
|
||||
font_cache_task: FontCacheTask,
|
||||
navigation_context: NavigationContext,
|
||||
next_pipeline_id: PipelineId,
|
||||
pending_frames: Vec<FrameChange>,
|
||||
pending_sizes: HashMap<(PipelineId, SubpageId), TypedRect<PagePx, f32>>,
|
||||
pub time_profiler_chan: TimeProfilerChan,
|
||||
pub window_size: WindowSizeData,
|
||||
pub opts: Opts,
|
||||
}
|
||||
|
||||
/// Stores the Id of the outermost frame's pipeline, along with a vector of children frames
|
||||
struct FrameTree {
|
||||
pub pipeline: Rc<Pipeline>,
|
||||
pub parent: RefCell<Option<Rc<Pipeline>>>,
|
||||
pub children: RefCell<Vec<ChildFrameTree>>,
|
||||
}
|
||||
|
||||
#[deriving(Clone)]
|
||||
struct ChildFrameTree {
|
||||
frame_tree: Rc<FrameTree>,
|
||||
/// Clipping rect representing the size and position, in page coordinates, of the visible
|
||||
/// region of the child frame relative to the parent.
|
||||
pub rect: Option<TypedRect<PagePx, f32>>,
|
||||
}
|
||||
|
||||
pub struct SendableFrameTree {
|
||||
pub pipeline: CompositionPipeline,
|
||||
pub children: Vec<SendableChildFrameTree>,
|
||||
}
|
||||
|
||||
pub struct SendableChildFrameTree {
|
||||
pub frame_tree: SendableFrameTree,
|
||||
pub rect: Option<TypedRect<PagePx, f32>>,
|
||||
}
|
||||
|
||||
enum ReplaceResult {
|
||||
ReplacedNode(Rc<FrameTree>),
|
||||
OriginalNode(Rc<FrameTree>),
|
||||
}
|
||||
|
||||
impl FrameTree {
|
||||
fn to_sendable(&self) -> SendableFrameTree {
|
||||
let sendable_frame_tree = SendableFrameTree {
|
||||
pipeline: self.pipeline.to_sendable(),
|
||||
children: self.children.borrow().iter().map(|frame_tree| frame_tree.to_sendable()).collect(),
|
||||
};
|
||||
sendable_frame_tree
|
||||
}
|
||||
}
|
||||
|
||||
trait FrameTreeTraversal {
|
||||
fn contains(&self, id: PipelineId) -> bool;
|
||||
fn find(&self, id: PipelineId) -> Option<Self>;
|
||||
fn replace_child(&self, id: PipelineId, new_child: Self) -> ReplaceResult;
|
||||
fn iter(&self) -> FrameTreeIterator;
|
||||
}
|
||||
|
||||
impl FrameTreeTraversal for Rc<FrameTree> {
|
||||
fn contains(&self, id: PipelineId) -> bool {
|
||||
self.iter().any(|frame_tree| id == frame_tree.pipeline.id)
|
||||
}
|
||||
|
||||
/// Returns the frame tree whose key is id
|
||||
fn find(&self, id: PipelineId) -> Option<Rc<FrameTree>> {
|
||||
self.iter().find(|frame_tree| id == frame_tree.pipeline.id)
|
||||
}
|
||||
|
||||
/// Replaces a node of the frame tree in place. Returns the node that was removed or the original node
|
||||
/// if the node to replace could not be found.
|
||||
fn replace_child(&self, id: PipelineId, new_child: Rc<FrameTree>) -> ReplaceResult {
|
||||
for frame_tree in self.iter() {
|
||||
let mut children = frame_tree.children.borrow_mut();
|
||||
let mut child = children.mut_iter()
|
||||
.find(|child| child.frame_tree.pipeline.id == id);
|
||||
for child in child.mut_iter() {
|
||||
*new_child.parent.borrow_mut() = child.frame_tree.parent.borrow().clone();
|
||||
return ReplacedNode(replace(&mut child.frame_tree, new_child));
|
||||
}
|
||||
}
|
||||
OriginalNode(new_child)
|
||||
}
|
||||
|
||||
fn iter(&self) -> FrameTreeIterator {
|
||||
FrameTreeIterator {
|
||||
stack: vec!(self.clone()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ChildFrameTree {
|
||||
fn to_sendable(&self) -> SendableChildFrameTree {
|
||||
SendableChildFrameTree {
|
||||
frame_tree: self.frame_tree.to_sendable(),
|
||||
rect: self.rect,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An iterator over a frame tree, returning nodes in depth-first order.
|
||||
/// Note that this iterator should _not_ be used to mutate nodes _during_
|
||||
/// iteration. Mutating nodes once the iterator is out of scope is OK.
|
||||
struct FrameTreeIterator {
|
||||
stack: Vec<Rc<FrameTree>>,
|
||||
}
|
||||
|
||||
impl Iterator<Rc<FrameTree>> for FrameTreeIterator {
|
||||
fn next(&mut self) -> Option<Rc<FrameTree>> {
|
||||
if !self.stack.is_empty() {
|
||||
let next = self.stack.pop();
|
||||
for cft in next.get_ref().children.borrow().iter() {
|
||||
self.stack.push(cft.frame_tree.clone());
|
||||
}
|
||||
Some(next.unwrap())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents the portion of a page that is changing in navigating.
|
||||
struct FrameChange {
|
||||
pub before: Option<PipelineId>,
|
||||
pub after: Rc<FrameTree>,
|
||||
pub navigation_type: NavigationType,
|
||||
}
|
||||
|
||||
/// Stores the Id's of the pipelines previous and next in the browser's history
|
||||
struct NavigationContext {
|
||||
pub previous: Vec<Rc<FrameTree>>,
|
||||
pub next: Vec<Rc<FrameTree>>,
|
||||
pub current: Option<Rc<FrameTree>>,
|
||||
}
|
||||
|
||||
impl NavigationContext {
|
||||
fn new() -> NavigationContext {
|
||||
NavigationContext {
|
||||
previous: vec!(),
|
||||
next: vec!(),
|
||||
current: None,
|
||||
}
|
||||
}
|
||||
|
||||
/* Note that the following two methods can fail. They should only be called *
|
||||
* when it is known that there exists either a previous page or a next page. */
|
||||
|
||||
fn back(&mut self) -> Rc<FrameTree> {
|
||||
self.next.push(self.current.take_unwrap());
|
||||
let prev = self.previous.pop().unwrap();
|
||||
self.current = Some(prev.clone());
|
||||
prev
|
||||
}
|
||||
|
||||
fn forward(&mut self) -> Rc<FrameTree> {
|
||||
self.previous.push(self.current.take_unwrap());
|
||||
let next = self.next.pop().unwrap();
|
||||
self.current = Some(next.clone());
|
||||
next
|
||||
}
|
||||
|
||||
/// Loads a new set of page frames, returning all evicted frame trees
|
||||
fn load(&mut self, frame_tree: Rc<FrameTree>) -> Vec<Rc<FrameTree>> {
|
||||
debug!("navigating to {:?}", frame_tree.pipeline.id);
|
||||
let evicted = replace(&mut self.next, vec!());
|
||||
if self.current.is_some() {
|
||||
self.previous.push(self.current.take_unwrap());
|
||||
}
|
||||
self.current = Some(frame_tree.clone());
|
||||
evicted
|
||||
}
|
||||
|
||||
/// Returns the frame trees whose keys are pipeline_id.
|
||||
fn find_all(&mut self, pipeline_id: PipelineId) -> Vec<Rc<FrameTree>> {
|
||||
let from_current = self.current.iter().filter_map(|frame_tree| {
|
||||
frame_tree.find(pipeline_id)
|
||||
});
|
||||
let from_next = self.next.iter().filter_map(|frame_tree| {
|
||||
frame_tree.find(pipeline_id)
|
||||
});
|
||||
let from_prev = self.previous.iter().filter_map(|frame_tree| {
|
||||
frame_tree.find(pipeline_id)
|
||||
});
|
||||
from_prev.chain(from_current).chain(from_next).collect()
|
||||
}
|
||||
|
||||
fn contains(&mut self, pipeline_id: PipelineId) -> bool {
|
||||
let from_current = self.current.iter();
|
||||
let from_next = self.next.iter();
|
||||
let from_prev = self.previous.iter();
|
||||
|
||||
let mut all_contained = from_prev.chain(from_current).chain(from_next);
|
||||
all_contained.any(|frame_tree| {
|
||||
frame_tree.contains(pipeline_id)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<LTF: LayoutTaskFactory, STF: ScriptTaskFactory> Constellation<LTF, STF> {
|
||||
pub fn start(compositor_chan: CompositorChan,
|
||||
opts: &Opts,
|
||||
resource_task: ResourceTask,
|
||||
image_cache_task: ImageCacheTask,
|
||||
font_cache_task: FontCacheTask,
|
||||
time_profiler_chan: TimeProfilerChan)
|
||||
-> ConstellationChan {
|
||||
let (constellation_port, constellation_chan) = ConstellationChan::new();
|
||||
let constellation_chan_clone = constellation_chan.clone();
|
||||
let opts_clone = opts.clone();
|
||||
spawn_named("Constellation", proc() {
|
||||
let mut constellation : Constellation<LTF, STF> = Constellation {
|
||||
chan: constellation_chan_clone,
|
||||
request_port: constellation_port,
|
||||
compositor_chan: compositor_chan,
|
||||
resource_task: resource_task,
|
||||
image_cache_task: image_cache_task,
|
||||
font_cache_task: font_cache_task,
|
||||
pipelines: HashMap::new(),
|
||||
navigation_context: NavigationContext::new(),
|
||||
next_pipeline_id: PipelineId(0),
|
||||
pending_frames: vec!(),
|
||||
pending_sizes: HashMap::new(),
|
||||
time_profiler_chan: time_profiler_chan,
|
||||
window_size: WindowSizeData {
|
||||
visible_viewport: TypedSize2D(800_f32, 600_f32),
|
||||
initial_viewport: TypedSize2D(800_f32, 600_f32),
|
||||
device_pixel_ratio: ScaleFactor(1.0),
|
||||
},
|
||||
opts: opts_clone,
|
||||
};
|
||||
constellation.run();
|
||||
});
|
||||
constellation_chan
|
||||
}
|
||||
|
||||
fn run(&mut self) {
|
||||
loop {
|
||||
let request = self.request_port.recv();
|
||||
if !self.handle_request(request) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper function for creating a pipeline
|
||||
fn new_pipeline(&self,
|
||||
id: PipelineId,
|
||||
subpage_id: Option<SubpageId>,
|
||||
script_pipeline: Option<Rc<Pipeline>>,
|
||||
url: Url)
|
||||
-> Rc<Pipeline> {
|
||||
let pipe = Pipeline::create::<LTF, STF>(id,
|
||||
subpage_id,
|
||||
self.chan.clone(),
|
||||
self.compositor_chan.clone(),
|
||||
self.image_cache_task.clone(),
|
||||
self.font_cache_task.clone(),
|
||||
self.resource_task.clone(),
|
||||
self.time_profiler_chan.clone(),
|
||||
self.window_size,
|
||||
self.opts.clone(),
|
||||
script_pipeline,
|
||||
url);
|
||||
pipe.load();
|
||||
Rc::new(pipe)
|
||||
}
|
||||
|
||||
|
||||
/// Helper function for getting a unique pipeline Id
|
||||
fn get_next_pipeline_id(&mut self) -> PipelineId {
|
||||
let id = self.next_pipeline_id;
|
||||
let PipelineId(ref mut i) = self.next_pipeline_id;
|
||||
*i += 1;
|
||||
id
|
||||
}
|
||||
|
||||
/// Convenience function for getting the currently active frame tree.
|
||||
/// The currently active frame tree should always be the current painter
|
||||
fn current_frame<'a>(&'a self) -> &'a Option<Rc<FrameTree>> {
|
||||
&self.navigation_context.current
|
||||
}
|
||||
|
||||
/// Returns both the navigation context and pending frame trees whose keys are pipeline_id.
|
||||
fn find_all(&mut self, pipeline_id: PipelineId) -> Vec<Rc<FrameTree>> {
|
||||
let matching_navi_frames = self.navigation_context.find_all(pipeline_id);
|
||||
let matching_pending_frames = self.pending_frames.iter().filter_map(|frame_change| {
|
||||
frame_change.after.find(pipeline_id)
|
||||
});
|
||||
matching_navi_frames.move_iter().chain(matching_pending_frames).collect()
|
||||
}
|
||||
|
||||
/// Handles loading pages, navigation, and granting access to the compositor
|
||||
fn handle_request(&mut self, request: Msg) -> bool {
|
||||
match request {
|
||||
ExitMsg => {
|
||||
debug!("constellation exiting");
|
||||
self.handle_exit();
|
||||
return false;
|
||||
}
|
||||
FailureMsg(Failure { pipeline_id, subpage_id }) => {
|
||||
self.handle_failure_msg(pipeline_id, subpage_id);
|
||||
}
|
||||
// This should only be called once per constellation, and only by the browser
|
||||
InitLoadUrlMsg(url) => {
|
||||
debug!("constellation got init load URL message");
|
||||
self.handle_init_load(url);
|
||||
}
|
||||
// A layout assigned a size and position to a subframe. This needs to be reflected by
|
||||
// all frame trees in the navigation context containing the subframe.
|
||||
FrameRectMsg(pipeline_id, subpage_id, rect) => {
|
||||
debug!("constellation got frame rect message");
|
||||
self.handle_frame_rect_msg(pipeline_id, subpage_id, Rect::from_untyped(&rect));
|
||||
}
|
||||
LoadIframeUrlMsg(url, source_pipeline_id, subpage_id, sandbox) => {
|
||||
debug!("constellation got iframe URL load message");
|
||||
self.handle_load_iframe_url_msg(url, source_pipeline_id, subpage_id, sandbox);
|
||||
}
|
||||
// Load a new page, usually -- but not always -- from a mouse click or typed url
|
||||
// If there is already a pending page (self.pending_frames), it will not be overridden;
|
||||
// However, if the id is not encompassed by another change, it will be.
|
||||
LoadUrlMsg(source_id, url) => {
|
||||
debug!("constellation got URL load message");
|
||||
self.handle_load_url_msg(source_id, url);
|
||||
}
|
||||
// A page loaded through one of several methods above has completed all parsing,
|
||||
// script, and reflow messages have been sent.
|
||||
LoadCompleteMsg(pipeline_id, url) => {
|
||||
debug!("constellation got load complete message");
|
||||
self.compositor_chan.send(LoadComplete(pipeline_id, url));
|
||||
}
|
||||
// Handle a forward or back request
|
||||
NavigateMsg(direction) => {
|
||||
debug!("constellation got navigation message");
|
||||
self.handle_navigate_msg(direction);
|
||||
}
|
||||
// Notification that rendering has finished and is requesting permission to paint.
|
||||
RendererReadyMsg(pipeline_id) => {
|
||||
debug!("constellation got renderer ready message");
|
||||
self.handle_renderer_ready_msg(pipeline_id);
|
||||
}
|
||||
ResizedWindowMsg(new_size) => {
|
||||
debug!("constellation got window resize message");
|
||||
self.handle_resized_window_msg(new_size);
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
fn handle_exit(&self) {
|
||||
for (_id, ref pipeline) in self.pipelines.iter() {
|
||||
pipeline.exit();
|
||||
}
|
||||
self.image_cache_task.exit();
|
||||
self.resource_task.send(resource_task::Exit);
|
||||
self.font_cache_task.exit();
|
||||
self.compositor_chan.send(ShutdownComplete);
|
||||
}
|
||||
|
||||
fn handle_failure_msg(&mut self, pipeline_id: PipelineId, subpage_id: Option<SubpageId>) {
|
||||
debug!("handling failure message from pipeline {:?}, {:?}", pipeline_id, subpage_id);
|
||||
|
||||
if self.opts.hard_fail {
|
||||
// It's quite difficult to make Servo exit cleanly if some tasks have failed.
|
||||
// Hard fail exists for test runners so we crash and that's good enough.
|
||||
let mut stderr = io::stderr();
|
||||
stderr.write_str("Pipeline failed in hard-fail mode. Crashing!\n").unwrap();
|
||||
stderr.flush().unwrap();
|
||||
unsafe { libc::exit(1); }
|
||||
}
|
||||
|
||||
let old_pipeline = match self.pipelines.find(&pipeline_id) {
|
||||
None => {
|
||||
debug!("no existing pipeline found; bailing out of failure recovery.");
|
||||
return; // already failed?
|
||||
}
|
||||
Some(pipeline) => pipeline.clone()
|
||||
};
|
||||
|
||||
fn force_pipeline_exit(old_pipeline: &Rc<Pipeline>) {
|
||||
let ScriptControlChan(ref old_script) = old_pipeline.script_chan;
|
||||
let _ = old_script.send_opt(ExitPipelineMsg(old_pipeline.id));
|
||||
let _ = old_pipeline.render_chan.send_opt(render_task::ExitMsg(None));
|
||||
let LayoutControlChan(ref old_layout) = old_pipeline.layout_chan;
|
||||
let _ = old_layout.send_opt(ExitNowMsg);
|
||||
}
|
||||
force_pipeline_exit(&old_pipeline);
|
||||
self.pipelines.remove(&pipeline_id);
|
||||
|
||||
loop {
|
||||
let idx = self.pending_frames.iter().position(|pending| {
|
||||
pending.after.pipeline.id == pipeline_id
|
||||
});
|
||||
idx.map(|idx| {
|
||||
debug!("removing pending frame change for failed pipeline");
|
||||
force_pipeline_exit(&self.pending_frames[idx].after.pipeline);
|
||||
self.pending_frames.remove(idx)
|
||||
});
|
||||
if idx.is_none() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
debug!("creating replacement pipeline for about:failure");
|
||||
|
||||
let new_id = self.get_next_pipeline_id();
|
||||
let pipeline = self.new_pipeline(new_id, subpage_id, None,
|
||||
Url::parse("about:failure").unwrap());
|
||||
|
||||
self.pending_frames.push(FrameChange{
|
||||
before: Some(pipeline_id),
|
||||
after: Rc::new(FrameTree {
|
||||
pipeline: pipeline.clone(),
|
||||
parent: RefCell::new(None),
|
||||
children: RefCell::new(vec!()),
|
||||
}),
|
||||
navigation_type: constellation_msg::Load,
|
||||
});
|
||||
|
||||
self.pipelines.insert(new_id, pipeline);
|
||||
}
|
||||
|
||||
fn handle_init_load(&mut self, url: Url) {
|
||||
let next_pipeline_id = self.get_next_pipeline_id();
|
||||
let pipeline = self.new_pipeline(next_pipeline_id, None, None, url);
|
||||
|
||||
self.pending_frames.push(FrameChange {
|
||||
before: None,
|
||||
after: Rc::new(FrameTree {
|
||||
pipeline: pipeline.clone(),
|
||||
parent: RefCell::new(None),
|
||||
children: RefCell::new(vec!()),
|
||||
}),
|
||||
navigation_type: constellation_msg::Load,
|
||||
});
|
||||
self.pipelines.insert(pipeline.id, pipeline);
|
||||
}
|
||||
|
||||
fn handle_frame_rect_msg(&mut self, pipeline_id: PipelineId, subpage_id: SubpageId,
|
||||
rect: TypedRect<PagePx, f32>) {
|
||||
debug!("Received frame rect {:?} from {:?}, {:?}", rect, pipeline_id, subpage_id);
|
||||
let mut already_sent = HashSet::new();
|
||||
|
||||
// Returns true if a child frame tree's subpage id matches the given subpage id
|
||||
let subpage_eq = |child_frame_tree: & &mut ChildFrameTree| {
|
||||
child_frame_tree.frame_tree.pipeline.
|
||||
subpage_id.expect("Constellation:
|
||||
child frame does not have a subpage id. This should not be possible.")
|
||||
== subpage_id
|
||||
};
|
||||
|
||||
let frames = self.find_all(pipeline_id);
|
||||
|
||||
{
|
||||
// Update a child's frame rect and inform its script task of the change,
|
||||
// if it hasn't been already. Optionally inform the compositor if
|
||||
// resize happens immediately.
|
||||
let update_child_rect = |child_frame_tree: &mut ChildFrameTree, is_active: bool| {
|
||||
child_frame_tree.rect = Some(rect);
|
||||
// NOTE: work around borrowchk issues
|
||||
let pipeline = &child_frame_tree.frame_tree.pipeline;
|
||||
if !already_sent.contains(&pipeline.id) {
|
||||
if is_active {
|
||||
let ScriptControlChan(ref script_chan) = pipeline.script_chan;
|
||||
script_chan.send(ResizeMsg(pipeline.id, WindowSizeData {
|
||||
visible_viewport: rect.size,
|
||||
initial_viewport: rect.size * ScaleFactor(1.0),
|
||||
device_pixel_ratio: self.window_size.device_pixel_ratio,
|
||||
}));
|
||||
self.compositor_chan.send(SetLayerClipRect(pipeline.id,
|
||||
LayerId::null(),
|
||||
rect.to_untyped()));
|
||||
} else {
|
||||
already_sent.insert(pipeline.id);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
// If the subframe is in the current frame tree, the compositor needs the new size
|
||||
for current_frame in self.current_frame().iter() {
|
||||
debug!("Constellation: Sending size for frame in current frame tree.");
|
||||
let source_frame = current_frame.find(pipeline_id);
|
||||
for source_frame in source_frame.iter() {
|
||||
let mut children = source_frame.children.borrow_mut();
|
||||
let found_child = children.mut_iter().find(|child| subpage_eq(child));
|
||||
found_child.map(|child| update_child_rect(child, true));
|
||||
}
|
||||
}
|
||||
|
||||
// Update all frames with matching pipeline- and subpage-ids
|
||||
for frame_tree in frames.iter() {
|
||||
let mut children = frame_tree.children.borrow_mut();
|
||||
let found_child = children.mut_iter().find(|child| subpage_eq(child));
|
||||
found_child.map(|child| update_child_rect(child, false));
|
||||
}
|
||||
}
|
||||
|
||||
// At this point, if no pipelines were sent a resize msg, then this subpage id
|
||||
// should be added to pending sizes
|
||||
if already_sent.len() == 0 {
|
||||
self.pending_sizes.insert((pipeline_id, subpage_id), rect);
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_load_iframe_url_msg(&mut self,
|
||||
url: Url,
|
||||
source_pipeline_id: PipelineId,
|
||||
subpage_id: SubpageId,
|
||||
sandbox: IFrameSandboxState) {
|
||||
// A message from the script associated with pipeline_id that it has
|
||||
// parsed an iframe during html parsing. This iframe will result in a
|
||||
// new pipeline being spawned and a frame tree being added to pipeline_id's
|
||||
// frame tree's children. This message is never the result of a link clicked
|
||||
// or a new url entered.
|
||||
// Start by finding the frame trees matching the pipeline id,
|
||||
// and add the new pipeline to their sub frames.
|
||||
let frame_trees = self.find_all(source_pipeline_id);
|
||||
if frame_trees.is_empty() {
|
||||
fail!("Constellation: source pipeline id of LoadIframeUrlMsg is not in
|
||||
navigation context, nor is it in a pending frame. This should be
|
||||
impossible.");
|
||||
}
|
||||
|
||||
let next_pipeline_id = self.get_next_pipeline_id();
|
||||
|
||||
// Compare the pipeline's url to the new url. If the origin is the same,
|
||||
// then reuse the script task in creating the new pipeline
|
||||
let source_pipeline = self.pipelines.find(&source_pipeline_id).expect("Constellation:
|
||||
source Id of LoadIframeUrlMsg does have an associated pipeline in
|
||||
constellation. This should be impossible.").clone();
|
||||
|
||||
let source_url = source_pipeline.url.clone();
|
||||
|
||||
let same_script = (source_url.host() == url.host() &&
|
||||
source_url.port() == url.port()) && sandbox == IFrameUnsandboxed;
|
||||
// FIXME(tkuehn): Need to follow the standardized spec for checking same-origin
|
||||
// Reuse the script task if the URL is same-origin
|
||||
let new_pipeline = if same_script {
|
||||
debug!("Constellation: loading same-origin iframe at {:?}", url);
|
||||
Some(source_pipeline.clone())
|
||||
} else {
|
||||
debug!("Constellation: loading cross-origin iframe at {:?}", url);
|
||||
None
|
||||
};
|
||||
|
||||
let pipeline = self.new_pipeline(
|
||||
next_pipeline_id,
|
||||
Some(subpage_id),
|
||||
new_pipeline,
|
||||
url
|
||||
);
|
||||
|
||||
let rect = self.pending_sizes.pop(&(source_pipeline_id, subpage_id));
|
||||
for frame_tree in frame_trees.iter() {
|
||||
frame_tree.children.borrow_mut().push(ChildFrameTree {
|
||||
frame_tree: Rc::new(FrameTree {
|
||||
pipeline: pipeline.clone(),
|
||||
parent: RefCell::new(Some(source_pipeline.clone())),
|
||||
children: RefCell::new(vec!()),
|
||||
}),
|
||||
rect: rect,
|
||||
});
|
||||
}
|
||||
self.pipelines.insert(pipeline.id, pipeline);
|
||||
}
|
||||
|
||||
fn handle_load_url_msg(&mut self, source_id: PipelineId, url: Url) {
|
||||
debug!("Constellation: received message to load {:s}", url.to_string());
|
||||
// Make sure no pending page would be overridden.
|
||||
let source_frame = self.current_frame().get_ref().find(source_id).expect(
|
||||
"Constellation: received a LoadUrlMsg from a pipeline_id associated
|
||||
with a pipeline not in the active frame tree. This should be
|
||||
impossible.");
|
||||
|
||||
for frame_change in self.pending_frames.iter() {
|
||||
let old_id = frame_change.before.expect("Constellation: Received load msg
|
||||
from pipeline, but there is no currently active page. This should
|
||||
be impossible.");
|
||||
let changing_frame = self.current_frame().get_ref().find(old_id).expect("Constellation:
|
||||
Pending change has non-active source pipeline. This should be
|
||||
impossible.");
|
||||
if changing_frame.contains(source_id) || source_frame.contains(old_id) {
|
||||
// id that sent load msg is being changed already; abort
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Being here means either there are no pending frames, or none of the pending
|
||||
// changes would be overriden by changing the subframe associated with source_id.
|
||||
|
||||
let parent = source_frame.parent.clone();
|
||||
let subpage_id = source_frame.pipeline.subpage_id;
|
||||
let next_pipeline_id = self.get_next_pipeline_id();
|
||||
|
||||
let pipeline = self.new_pipeline(next_pipeline_id, subpage_id, None, url);
|
||||
|
||||
self.pending_frames.push(FrameChange{
|
||||
before: Some(source_id),
|
||||
after: Rc::new(FrameTree {
|
||||
pipeline: pipeline.clone(),
|
||||
parent: parent,
|
||||
children: RefCell::new(vec!()),
|
||||
}),
|
||||
navigation_type: constellation_msg::Load,
|
||||
});
|
||||
self.pipelines.insert(pipeline.id, pipeline);
|
||||
}
|
||||
|
||||
fn handle_navigate_msg(&mut self, direction: constellation_msg::NavigationDirection) {
|
||||
debug!("received message to navigate {:?}", direction);
|
||||
|
||||
// TODO(tkuehn): what is the "critical point" beyond which pending frames
|
||||
// should not be cleared? Currently, the behavior is that forward/back
|
||||
// navigation always has navigation priority, and after that new page loading is
|
||||
// first come, first served.
|
||||
let destination_frame = match direction {
|
||||
constellation_msg::Forward => {
|
||||
if self.navigation_context.next.is_empty() {
|
||||
debug!("no next page to navigate to");
|
||||
return;
|
||||
} else {
|
||||
let old = self.current_frame().get_ref();
|
||||
for frame in old.iter() {
|
||||
frame.pipeline.revoke_paint_permission();
|
||||
}
|
||||
}
|
||||
self.navigation_context.forward()
|
||||
}
|
||||
constellation_msg::Back => {
|
||||
if self.navigation_context.previous.is_empty() {
|
||||
debug!("no previous page to navigate to");
|
||||
return;
|
||||
} else {
|
||||
let old = self.current_frame().get_ref();
|
||||
for frame in old.iter() {
|
||||
frame.pipeline.revoke_paint_permission();
|
||||
}
|
||||
}
|
||||
self.navigation_context.back()
|
||||
}
|
||||
};
|
||||
|
||||
for frame in destination_frame.iter() {
|
||||
frame.pipeline.load();
|
||||
}
|
||||
self.grant_paint_permission(destination_frame, constellation_msg::Navigate);
|
||||
|
||||
}
|
||||
|
||||
fn handle_renderer_ready_msg(&mut self, pipeline_id: PipelineId) {
|
||||
debug!("Renderer {:?} ready to send paint msg", pipeline_id);
|
||||
// This message could originate from a pipeline in the navigation context or
|
||||
// from a pending frame. The only time that we will grant paint permission is
|
||||
// when the message originates from a pending frame or the current frame.
|
||||
|
||||
for current_frame in self.current_frame().iter() {
|
||||
// Messages originating in the current frame are not navigations;
|
||||
// they may come from a page load in a subframe.
|
||||
if current_frame.contains(pipeline_id) {
|
||||
for frame in current_frame.iter() {
|
||||
frame.pipeline.grant_paint_permission();
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Find the pending frame change whose new pipeline id is pipeline_id.
|
||||
// If it is not found, it simply means that this pipeline will not receive
|
||||
// permission to paint.
|
||||
let pending_index = self.pending_frames.iter().rposition(|frame_change| {
|
||||
frame_change.after.pipeline.id == pipeline_id
|
||||
});
|
||||
for &pending_index in pending_index.iter() {
|
||||
let frame_change = self.pending_frames.swap_remove(pending_index).unwrap();
|
||||
let to_add = frame_change.after.clone();
|
||||
|
||||
// Create the next frame tree that will be given to the compositor
|
||||
let next_frame_tree = if to_add.parent.borrow().is_some() {
|
||||
// NOTE: work around borrowchk issues
|
||||
self.current_frame().get_ref().clone()
|
||||
} else {
|
||||
to_add.clone()
|
||||
};
|
||||
|
||||
// If there are frames to revoke permission from, do so now.
|
||||
match frame_change.before {
|
||||
Some(revoke_id) if self.current_frame().is_some() => {
|
||||
debug!("Constellation: revoking permission from {:?}", revoke_id);
|
||||
let current_frame = self.current_frame().get_ref();
|
||||
|
||||
let to_revoke = current_frame.find(revoke_id).expect(
|
||||
"Constellation: pending frame change refers to an old \
|
||||
frame not contained in the current frame. This is a bug");
|
||||
|
||||
for frame in to_revoke.iter() {
|
||||
frame.pipeline.revoke_paint_permission();
|
||||
}
|
||||
|
||||
// If to_add is not the root frame, then replace revoked_frame with it.
|
||||
// This conveniently keeps scissor rect size intact.
|
||||
// NOTE: work around borrowchk issue
|
||||
let mut flag = false;
|
||||
{
|
||||
if to_add.parent.borrow().is_some() {
|
||||
debug!("Constellation: replacing {:?} with {:?} in {:?}",
|
||||
revoke_id, to_add.pipeline.id,
|
||||
next_frame_tree.pipeline.id);
|
||||
flag = true;
|
||||
}
|
||||
}
|
||||
if flag {
|
||||
next_frame_tree.replace_child(revoke_id, to_add);
|
||||
}
|
||||
}
|
||||
|
||||
_ => {
|
||||
// Add to_add to parent's children, if it is not the root
|
||||
let parent = &to_add.parent;
|
||||
for parent in parent.borrow().iter() {
|
||||
let subpage_id = to_add.pipeline.subpage_id
|
||||
.expect("Constellation:
|
||||
Child frame's subpage id is None. This should be impossible.");
|
||||
let rect = self.pending_sizes.pop(&(parent.id, subpage_id));
|
||||
let parent = next_frame_tree.find(parent.id).expect(
|
||||
"Constellation: pending frame has a parent frame that is not
|
||||
active. This is a bug.");
|
||||
parent.children.borrow_mut().push(ChildFrameTree {
|
||||
frame_tree: to_add.clone(),
|
||||
rect: rect,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.grant_paint_permission(next_frame_tree, frame_change.navigation_type);
|
||||
}
|
||||
}
|
||||
|
||||
/// Called when the window is resized.
|
||||
fn handle_resized_window_msg(&mut self, new_size: WindowSizeData) {
|
||||
let mut already_seen = HashSet::new();
|
||||
for frame_tree in self.current_frame().iter() {
|
||||
debug!("constellation sending resize message to active frame");
|
||||
let pipeline = &frame_tree.pipeline;
|
||||
let ScriptControlChan(ref chan) = pipeline.script_chan;
|
||||
let _ = chan.send_opt(ResizeMsg(pipeline.id, new_size));
|
||||
already_seen.insert(pipeline.id);
|
||||
}
|
||||
for frame_tree in self.navigation_context.previous.iter()
|
||||
.chain(self.navigation_context.next.iter()) {
|
||||
let pipeline = &frame_tree.pipeline;
|
||||
if !already_seen.contains(&pipeline.id) {
|
||||
debug!("constellation sending resize message to inactive frame");
|
||||
let ScriptControlChan(ref chan) = pipeline.script_chan;
|
||||
let _ = chan.send_opt(ResizeInactiveMsg(pipeline.id, new_size));
|
||||
already_seen.insert(pipeline.id);
|
||||
}
|
||||
}
|
||||
|
||||
// If there are any pending outermost frames, then tell them to resize. (This is how the
|
||||
// initial window size gets sent to the first page loaded, giving it permission to reflow.)
|
||||
for change in self.pending_frames.iter() {
|
||||
let frame_tree = &change.after;
|
||||
if frame_tree.parent.borrow().is_none() {
|
||||
debug!("constellation sending resize message to pending outer frame ({:?})",
|
||||
frame_tree.pipeline.id);
|
||||
let ScriptControlChan(ref chan) = frame_tree.pipeline.script_chan;
|
||||
let _ = chan.send_opt(ResizeMsg(frame_tree.pipeline.id, new_size));
|
||||
}
|
||||
}
|
||||
|
||||
self.window_size = new_size;
|
||||
}
|
||||
|
||||
// Close all pipelines at and beneath a given frame
|
||||
fn close_pipelines(&mut self, frame_tree: Rc<FrameTree>) {
|
||||
// TODO(tkuehn): should only exit once per unique script task,
|
||||
// and then that script task will handle sub-exits
|
||||
for frame_tree in frame_tree.iter() {
|
||||
frame_tree.pipeline.exit();
|
||||
self.pipelines.remove(&frame_tree.pipeline.id);
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_evicted_frames(&mut self, evicted: Vec<Rc<FrameTree>>) {
|
||||
for frame_tree in evicted.iter() {
|
||||
if !self.navigation_context.contains(frame_tree.pipeline.id) {
|
||||
self.close_pipelines(frame_tree.clone());
|
||||
} else {
|
||||
let frames = frame_tree.children.borrow().iter()
|
||||
.map(|child| child.frame_tree.clone()).collect();
|
||||
self.handle_evicted_frames(frames);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Grants a frame tree permission to paint; optionally updates navigation to reflect a new page
|
||||
fn grant_paint_permission(&mut self, frame_tree: Rc<FrameTree>, navigation_type: NavigationType) {
|
||||
// Give permission to paint to the new frame and all child frames
|
||||
self.set_ids(&frame_tree);
|
||||
|
||||
// Don't call navigation_context.load() on a Navigate type (or None, as in the case of
|
||||
// parsed iframes that finish loading)
|
||||
match navigation_type {
|
||||
constellation_msg::Load => {
|
||||
debug!("evicting old frames due to load");
|
||||
let evicted = self.navigation_context.load(frame_tree);
|
||||
self.handle_evicted_frames(evicted);
|
||||
}
|
||||
_ => {
|
||||
debug!("ignoring non-load navigation type");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn set_ids(&self, frame_tree: &Rc<FrameTree>) {
|
||||
let (chan, port) = channel();
|
||||
debug!("Constellation sending SetIds");
|
||||
self.compositor_chan.send(SetIds(frame_tree.to_sendable(), chan, self.chan.clone()));
|
||||
match port.recv_opt() {
|
||||
Ok(()) => {
|
||||
let mut iter = frame_tree.iter();
|
||||
for frame in iter {
|
||||
frame.pipeline.grant_paint_permission();
|
||||
}
|
||||
}
|
||||
Err(()) => {} // message has been discarded, probably shutting down
|
||||
}
|
||||
}
|
||||
}
|
||||
|
178
components/compositing/events.rs
Normal file
178
components/compositing/events.rs
Normal file
|
@ -0,0 +1,178 @@
|
|||
/* 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 compositor_data::{CompositorData, WantsScrollEvents};
|
||||
use windowing::{MouseWindowEvent, MouseWindowClickEvent, MouseWindowMouseDownEvent};
|
||||
use windowing::MouseWindowMouseUpEvent;
|
||||
|
||||
use geom::length::Length;
|
||||
use geom::point::TypedPoint2D;
|
||||
use geom::scale_factor::ScaleFactor;
|
||||
use geom::size::TypedSize2D;
|
||||
use layers::geometry::DevicePixel;
|
||||
use layers::layers::Layer;
|
||||
use script_traits::{ClickEvent, MouseDownEvent, MouseMoveEvent, MouseUpEvent, SendEventMsg};
|
||||
use script_traits::{ScriptControlChan};
|
||||
use servo_msg::compositor_msg::{FixedPosition, LayerId};
|
||||
use servo_msg::constellation_msg::PipelineId;
|
||||
use servo_util::geometry::PagePx;
|
||||
use std::rc::Rc;
|
||||
|
||||
|
||||
use geom::matrix::identity;
|
||||
|
||||
trait Clampable {
|
||||
fn clamp(&self, mn: &Self, mx: &Self) -> Self;
|
||||
}
|
||||
|
||||
impl Clampable for f32 {
|
||||
/// Returns the number constrained within the range `mn <= self <= mx`.
|
||||
/// If any of the numbers are `NAN` then `NAN` is returned.
|
||||
#[inline]
|
||||
fn clamp(&self, mn: &f32, mx: &f32) -> f32 {
|
||||
match () {
|
||||
_ if self.is_nan() => *self,
|
||||
_ if !(*self <= *mx) => *mx,
|
||||
_ if !(*self >= *mn) => *mn,
|
||||
_ => *self,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Move the layer's descendants that don't want scroll events and scroll by a relative
|
||||
/// specified amount in page coordinates. This also takes in a cursor position to see if the
|
||||
/// mouse is over child layers first. If a layer successfully scrolled, returns true; otherwise
|
||||
/// returns false, so a parent layer can scroll instead.
|
||||
pub fn handle_scroll_event(layer: Rc<Layer<CompositorData>>,
|
||||
delta: TypedPoint2D<DevicePixel, f32>,
|
||||
cursor: TypedPoint2D<DevicePixel, f32>,
|
||||
window_size: TypedSize2D<DevicePixel, f32>)
|
||||
-> bool {
|
||||
// If this layer doesn't want scroll events, neither it nor its children can handle scroll
|
||||
// events.
|
||||
if layer.extra_data.borrow().wants_scroll_events != WantsScrollEvents {
|
||||
return false
|
||||
}
|
||||
|
||||
// Allow children to scroll.
|
||||
let content_offset = layer.content_offset.borrow().clone();
|
||||
let cursor = cursor - content_offset;
|
||||
for child in layer.children().iter() {
|
||||
let child_bounds = child.bounds.borrow();
|
||||
if child_bounds.contains(&cursor) &&
|
||||
handle_scroll_event(child.clone(),
|
||||
delta,
|
||||
cursor - child_bounds.origin,
|
||||
child_bounds.size) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
clamp_scroll_offset_and_scroll_layer(layer, content_offset + delta, window_size)
|
||||
|
||||
}
|
||||
|
||||
pub fn clamp_scroll_offset_and_scroll_layer(layer: Rc<Layer<CompositorData>>,
|
||||
mut new_offset: TypedPoint2D<DevicePixel, f32>,
|
||||
window_size: TypedSize2D<DevicePixel, f32>)
|
||||
-> bool {
|
||||
let layer_size = layer.bounds.borrow().size;
|
||||
let min_x = (window_size.width - layer_size.width).get().min(0.0);
|
||||
new_offset.x = Length(new_offset.x.get().clamp(&min_x, &0.0));
|
||||
|
||||
let min_y = (window_size.height - layer_size.height).get().min(0.0);
|
||||
new_offset.y = Length(new_offset.y.get().clamp(&min_y, &0.0));
|
||||
|
||||
if *layer.content_offset.borrow() == new_offset {
|
||||
return false
|
||||
}
|
||||
|
||||
// FIXME: This allows the base layer to record the current content offset without
|
||||
// updating its transform. This should be replaced with something less strange.
|
||||
*layer.content_offset.borrow_mut() = new_offset;
|
||||
scroll_layer_and_all_child_layers(layer.clone(), new_offset)
|
||||
}
|
||||
|
||||
fn scroll_layer_and_all_child_layers(layer: Rc<Layer<CompositorData>>,
|
||||
new_offset: TypedPoint2D<DevicePixel, f32>)
|
||||
-> bool {
|
||||
let mut result = false;
|
||||
|
||||
// Only scroll this layer if it's not fixed-positioned.
|
||||
if layer.extra_data.borrow().scroll_policy != FixedPosition {
|
||||
*layer.transform.borrow_mut() = identity().translate(new_offset.x.get(),
|
||||
new_offset.y.get(),
|
||||
0.0);
|
||||
*layer.content_offset.borrow_mut() = new_offset;
|
||||
result = true
|
||||
}
|
||||
|
||||
for child in layer.children().iter() {
|
||||
result |= scroll_layer_and_all_child_layers(child.clone(), new_offset);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Takes in a MouseWindowEvent, determines if it should be passed to children, and
|
||||
// sends the event off to the appropriate pipeline. NB: the cursor position is in
|
||||
// page coordinates.
|
||||
pub fn send_mouse_event(layer: Rc<Layer<CompositorData>>,
|
||||
event: MouseWindowEvent,
|
||||
cursor: TypedPoint2D<DevicePixel, f32>,
|
||||
device_pixels_per_page_px: ScaleFactor<PagePx, DevicePixel, f32>) {
|
||||
let cursor = cursor - *layer.content_offset.borrow();
|
||||
for child in layer.children().iter() {
|
||||
let child_bounds = child.bounds.borrow();
|
||||
if child_bounds.contains(&cursor) {
|
||||
send_mouse_event(child.clone(),
|
||||
event,
|
||||
cursor - child_bounds.origin,
|
||||
device_pixels_per_page_px);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// This mouse event is mine!
|
||||
let cursor = cursor / device_pixels_per_page_px;
|
||||
let message = match event {
|
||||
MouseWindowClickEvent(button, _) => ClickEvent(button, cursor.to_untyped()),
|
||||
MouseWindowMouseDownEvent(button, _) => MouseDownEvent(button, cursor.to_untyped()),
|
||||
MouseWindowMouseUpEvent(button, _) => MouseUpEvent(button, cursor.to_untyped()),
|
||||
};
|
||||
let ScriptControlChan(ref chan) = layer.extra_data.borrow().pipeline.script_chan;
|
||||
let _ = chan.send_opt(SendEventMsg(layer.extra_data.borrow().pipeline.id.clone(), message));
|
||||
}
|
||||
|
||||
pub fn send_mouse_move_event(layer: Rc<Layer<CompositorData>>,
|
||||
cursor: TypedPoint2D<PagePx, f32>) {
|
||||
let message = MouseMoveEvent(cursor.to_untyped());
|
||||
let ScriptControlChan(ref chan) = layer.extra_data.borrow().pipeline.script_chan;
|
||||
let _ = chan.send_opt(SendEventMsg(layer.extra_data.borrow().pipeline.id.clone(), message));
|
||||
}
|
||||
|
||||
pub fn move(layer: Rc<Layer<CompositorData>>,
|
||||
pipeline_id: PipelineId,
|
||||
layer_id: LayerId,
|
||||
origin: TypedPoint2D<DevicePixel, f32>,
|
||||
window_size: TypedSize2D<DevicePixel, f32>)
|
||||
-> bool {
|
||||
// Search children for the right layer to move.
|
||||
if layer.extra_data.borrow().pipeline.id != pipeline_id ||
|
||||
layer.extra_data.borrow().id != layer_id {
|
||||
return layer.children().iter().any(|kid| {
|
||||
move(kid.clone(),
|
||||
pipeline_id,
|
||||
layer_id,
|
||||
origin,
|
||||
window_size)
|
||||
});
|
||||
}
|
||||
|
||||
if layer.extra_data.borrow().wants_scroll_events != WantsScrollEvents {
|
||||
return false
|
||||
}
|
||||
|
||||
clamp_scroll_offset_and_scroll_layer(layer, TypedPoint2D(0f32, 0f32) - origin, window_size)
|
||||
}
|
99
components/compositing/headless.rs
Normal file
99
components/compositing/headless.rs
Normal file
|
@ -0,0 +1,99 @@
|
|||
/* 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 compositor_task::{Msg, Exit, ChangeReadyState, SetIds};
|
||||
use compositor_task::{GetGraphicsMetadata, CreateOrUpdateRootLayer, CreateOrUpdateDescendantLayer};
|
||||
use compositor_task::{SetLayerClipRect, Paint, ScrollFragmentPoint, LoadComplete};
|
||||
use compositor_task::{ShutdownComplete, ChangeRenderState, RenderMsgDiscarded};
|
||||
|
||||
use geom::scale_factor::ScaleFactor;
|
||||
use geom::size::TypedSize2D;
|
||||
use servo_msg::constellation_msg::{ConstellationChan, ExitMsg, ResizedWindowMsg, WindowSizeData};
|
||||
use servo_util::memory::MemoryProfilerChan;
|
||||
use servo_util::memory;
|
||||
use servo_util::time::TimeProfilerChan;
|
||||
use servo_util::time;
|
||||
|
||||
/// Starts the compositor, which listens for messages on the specified port.
|
||||
///
|
||||
/// This is the null compositor which doesn't draw anything to the screen.
|
||||
/// It's intended for headless testing.
|
||||
pub struct NullCompositor {
|
||||
/// The port on which we receive messages.
|
||||
pub port: Receiver<Msg>,
|
||||
}
|
||||
|
||||
impl NullCompositor {
|
||||
fn new(port: Receiver<Msg>) -> NullCompositor {
|
||||
NullCompositor {
|
||||
port: port,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create(port: Receiver<Msg>,
|
||||
constellation_chan: ConstellationChan,
|
||||
time_profiler_chan: TimeProfilerChan,
|
||||
memory_profiler_chan: MemoryProfilerChan) {
|
||||
let compositor = NullCompositor::new(port);
|
||||
|
||||
// Tell the constellation about the initial fake size.
|
||||
{
|
||||
let ConstellationChan(ref chan) = constellation_chan;
|
||||
chan.send(ResizedWindowMsg(WindowSizeData {
|
||||
initial_viewport: TypedSize2D(640_f32, 480_f32),
|
||||
visible_viewport: TypedSize2D(640_f32, 480_f32),
|
||||
device_pixel_ratio: ScaleFactor(1.0),
|
||||
}));
|
||||
}
|
||||
compositor.handle_message(constellation_chan);
|
||||
|
||||
// Drain compositor port, sometimes messages contain channels that are blocking
|
||||
// another task from finishing (i.e. SetIds)
|
||||
loop {
|
||||
match compositor.port.try_recv() {
|
||||
Err(_) => break,
|
||||
Ok(_) => {},
|
||||
}
|
||||
}
|
||||
|
||||
time_profiler_chan.send(time::ExitMsg);
|
||||
memory_profiler_chan.send(memory::ExitMsg);
|
||||
}
|
||||
|
||||
fn handle_message(&self, constellation_chan: ConstellationChan) {
|
||||
loop {
|
||||
match self.port.recv() {
|
||||
Exit(chan) => {
|
||||
debug!("shutting down the constellation");
|
||||
let ConstellationChan(ref con_chan) = constellation_chan;
|
||||
con_chan.send(ExitMsg);
|
||||
chan.send(());
|
||||
}
|
||||
|
||||
ShutdownComplete => {
|
||||
debug!("constellation completed shutdown");
|
||||
break
|
||||
}
|
||||
|
||||
GetGraphicsMetadata(chan) => {
|
||||
chan.send(None);
|
||||
}
|
||||
|
||||
SetIds(_, response_chan, _) => {
|
||||
response_chan.send(());
|
||||
}
|
||||
|
||||
// Explicitly list ignored messages so that when we add a new one,
|
||||
// we'll notice and think about whether it needs a response, like
|
||||
// SetIds.
|
||||
|
||||
CreateOrUpdateRootLayer(..) |
|
||||
CreateOrUpdateDescendantLayer(..) |
|
||||
SetLayerClipRect(..) | Paint(..) |
|
||||
ChangeReadyState(..) | ChangeRenderState(..) | ScrollFragmentPoint(..) |
|
||||
LoadComplete(..) | RenderMsgDiscarded(..) => ()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
59
components/compositing/lib.rs
Normal file
59
components/compositing/lib.rs
Normal file
|
@ -0,0 +1,59 @@
|
|||
/* 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/. */
|
||||
|
||||
#![comment = "The Servo Parallel Browser Project"]
|
||||
#![license = "MPL"]
|
||||
|
||||
#![feature(globs, phase, macro_rules)]
|
||||
|
||||
#[phase(plugin, link)]
|
||||
extern crate log;
|
||||
|
||||
extern crate debug;
|
||||
|
||||
extern crate alert;
|
||||
extern crate azure;
|
||||
extern crate geom;
|
||||
extern crate gfx;
|
||||
#[cfg(not(target_os="android"))]
|
||||
extern crate glfw;
|
||||
#[cfg(target_os="android")]
|
||||
extern crate glut;
|
||||
extern crate layers;
|
||||
extern crate layout_traits;
|
||||
extern crate opengles;
|
||||
extern crate png;
|
||||
extern crate script_traits;
|
||||
extern crate servo_msg = "msg";
|
||||
extern crate servo_net = "net";
|
||||
#[phase(plugin, link)]
|
||||
extern crate servo_util = "util";
|
||||
|
||||
extern crate libc;
|
||||
extern crate time;
|
||||
extern crate url;
|
||||
|
||||
#[cfg(target_os="macos")]
|
||||
extern crate core_graphics;
|
||||
#[cfg(target_os="macos")]
|
||||
extern crate core_text;
|
||||
|
||||
pub use compositor_task::{CompositorChan, CompositorTask};
|
||||
pub use constellation::Constellation;
|
||||
|
||||
pub mod compositor_task;
|
||||
|
||||
mod compositor_data;
|
||||
mod events;
|
||||
|
||||
mod compositor;
|
||||
mod headless;
|
||||
|
||||
pub mod pipeline;
|
||||
pub mod constellation;
|
||||
|
||||
mod windowing;
|
||||
|
||||
#[path="platform/mod.rs"]
|
||||
pub mod platform;
|
193
components/compositing/pipeline.rs
Normal file
193
components/compositing/pipeline.rs
Normal file
|
@ -0,0 +1,193 @@
|
|||
/* 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 CompositorChan;
|
||||
use layout_traits::{LayoutTaskFactory, LayoutControlChan};
|
||||
use script_traits::{ScriptControlChan, ScriptTaskFactory};
|
||||
use script_traits::{AttachLayoutMsg, LoadMsg, NewLayoutInfo, ExitPipelineMsg};
|
||||
|
||||
use gfx::render_task::{PaintPermissionGranted, PaintPermissionRevoked};
|
||||
use gfx::render_task::{RenderChan, RenderTask};
|
||||
use servo_msg::constellation_msg::{ConstellationChan, Failure, PipelineId, SubpageId};
|
||||
use servo_msg::constellation_msg::WindowSizeData;
|
||||
use servo_net::image_cache_task::ImageCacheTask;
|
||||
use gfx::font_cache_task::FontCacheTask;
|
||||
use servo_net::resource_task::ResourceTask;
|
||||
use servo_util::opts::Opts;
|
||||
use servo_util::time::TimeProfilerChan;
|
||||
use std::rc::Rc;
|
||||
use url::Url;
|
||||
|
||||
/// A uniquely-identifiable pipeline of script task, layout task, and render task.
|
||||
pub struct Pipeline {
|
||||
pub id: PipelineId,
|
||||
pub subpage_id: Option<SubpageId>,
|
||||
pub script_chan: ScriptControlChan,
|
||||
pub layout_chan: LayoutControlChan,
|
||||
pub render_chan: RenderChan,
|
||||
pub layout_shutdown_port: Receiver<()>,
|
||||
pub render_shutdown_port: Receiver<()>,
|
||||
/// The most recently loaded url
|
||||
pub url: Url,
|
||||
}
|
||||
|
||||
/// The subset of the pipeline that is needed for layer composition.
|
||||
#[deriving(Clone)]
|
||||
pub struct CompositionPipeline {
|
||||
pub id: PipelineId,
|
||||
pub script_chan: ScriptControlChan,
|
||||
pub render_chan: RenderChan,
|
||||
}
|
||||
|
||||
impl Pipeline {
|
||||
/// Starts a render task, layout task, and possibly a script task.
|
||||
/// Returns the channels wrapped in a struct.
|
||||
/// If script_pipeline is not None, then subpage_id must also be not None.
|
||||
pub fn create<LTF:LayoutTaskFactory, STF:ScriptTaskFactory>(
|
||||
id: PipelineId,
|
||||
subpage_id: Option<SubpageId>,
|
||||
constellation_chan: ConstellationChan,
|
||||
compositor_chan: CompositorChan,
|
||||
image_cache_task: ImageCacheTask,
|
||||
font_cache_task: FontCacheTask,
|
||||
resource_task: ResourceTask,
|
||||
time_profiler_chan: TimeProfilerChan,
|
||||
window_size: WindowSizeData,
|
||||
opts: Opts,
|
||||
script_pipeline: Option<Rc<Pipeline>>,
|
||||
url: Url)
|
||||
-> Pipeline {
|
||||
let layout_pair = ScriptTaskFactory::create_layout_channel(None::<&mut STF>);
|
||||
let (render_port, render_chan) = RenderChan::new();
|
||||
let (render_shutdown_chan, render_shutdown_port) = channel();
|
||||
let (layout_shutdown_chan, layout_shutdown_port) = channel();
|
||||
let (pipeline_chan, pipeline_port) = channel();
|
||||
|
||||
let failure = Failure {
|
||||
pipeline_id: id,
|
||||
subpage_id: subpage_id,
|
||||
};
|
||||
|
||||
let script_chan = match script_pipeline {
|
||||
None => {
|
||||
let (script_chan, script_port) = channel();
|
||||
ScriptTaskFactory::create(None::<&mut STF>,
|
||||
id,
|
||||
box compositor_chan.clone(),
|
||||
&layout_pair,
|
||||
ScriptControlChan(script_chan.clone()),
|
||||
script_port,
|
||||
constellation_chan.clone(),
|
||||
failure.clone(),
|
||||
resource_task,
|
||||
image_cache_task.clone(),
|
||||
window_size);
|
||||
ScriptControlChan(script_chan)
|
||||
}
|
||||
Some(spipe) => {
|
||||
let new_layout_info = NewLayoutInfo {
|
||||
old_pipeline_id: spipe.id.clone(),
|
||||
new_pipeline_id: id,
|
||||
subpage_id: subpage_id.expect("script_pipeline != None but subpage_id == None"),
|
||||
layout_chan: ScriptTaskFactory::clone_layout_channel(None::<&mut STF>, &layout_pair),
|
||||
};
|
||||
|
||||
let ScriptControlChan(ref chan) = spipe.script_chan;
|
||||
chan.send(AttachLayoutMsg(new_layout_info));
|
||||
spipe.script_chan.clone()
|
||||
}
|
||||
};
|
||||
|
||||
RenderTask::create(id,
|
||||
render_port,
|
||||
compositor_chan.clone(),
|
||||
constellation_chan.clone(),
|
||||
font_cache_task.clone(),
|
||||
failure.clone(),
|
||||
opts.clone(),
|
||||
time_profiler_chan.clone(),
|
||||
render_shutdown_chan);
|
||||
|
||||
LayoutTaskFactory::create(None::<&mut LTF>,
|
||||
id,
|
||||
layout_pair,
|
||||
pipeline_port,
|
||||
constellation_chan,
|
||||
failure,
|
||||
script_chan.clone(),
|
||||
render_chan.clone(),
|
||||
image_cache_task,
|
||||
font_cache_task,
|
||||
opts.clone(),
|
||||
time_profiler_chan,
|
||||
layout_shutdown_chan);
|
||||
|
||||
Pipeline::new(id,
|
||||
subpage_id,
|
||||
script_chan,
|
||||
LayoutControlChan(pipeline_chan),
|
||||
render_chan,
|
||||
layout_shutdown_port,
|
||||
render_shutdown_port,
|
||||
url)
|
||||
}
|
||||
|
||||
pub fn new(id: PipelineId,
|
||||
subpage_id: Option<SubpageId>,
|
||||
script_chan: ScriptControlChan,
|
||||
layout_chan: LayoutControlChan,
|
||||
render_chan: RenderChan,
|
||||
layout_shutdown_port: Receiver<()>,
|
||||
render_shutdown_port: Receiver<()>,
|
||||
url: Url)
|
||||
-> Pipeline {
|
||||
Pipeline {
|
||||
id: id,
|
||||
subpage_id: subpage_id,
|
||||
script_chan: script_chan,
|
||||
layout_chan: layout_chan,
|
||||
render_chan: render_chan,
|
||||
layout_shutdown_port: layout_shutdown_port,
|
||||
render_shutdown_port: render_shutdown_port,
|
||||
url: url,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load(&self) {
|
||||
let ScriptControlChan(ref chan) = self.script_chan;
|
||||
chan.send(LoadMsg(self.id, self.url.clone()));
|
||||
}
|
||||
|
||||
pub fn grant_paint_permission(&self) {
|
||||
let _ = self.render_chan.send_opt(PaintPermissionGranted);
|
||||
}
|
||||
|
||||
pub fn revoke_paint_permission(&self) {
|
||||
debug!("pipeline revoking render channel paint permission");
|
||||
let _ = self.render_chan.send_opt(PaintPermissionRevoked);
|
||||
}
|
||||
|
||||
pub fn exit(&self) {
|
||||
debug!("pipeline {:?} exiting", self.id);
|
||||
|
||||
// Script task handles shutting down layout, and layout handles shutting down the renderer.
|
||||
// For now, if the script task has failed, we give up on clean shutdown.
|
||||
let ScriptControlChan(ref chan) = self.script_chan;
|
||||
if chan.send_opt(ExitPipelineMsg(self.id)).is_ok() {
|
||||
// Wait until all slave tasks have terminated and run destructors
|
||||
// NOTE: We don't wait for script task as we don't always own it
|
||||
let _ = self.render_shutdown_port.recv_opt();
|
||||
let _ = self.layout_shutdown_port.recv_opt();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_sendable(&self) -> CompositionPipeline {
|
||||
CompositionPipeline {
|
||||
id: self.id.clone(),
|
||||
script_chan: self.script_chan.clone(),
|
||||
render_chan: self.render_chan.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
380
components/compositing/platform/common/glfw_windowing.rs
Normal file
380
components/compositing/platform/common/glfw_windowing.rs
Normal file
|
@ -0,0 +1,380 @@
|
|||
/* 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/. */
|
||||
|
||||
//! A windowing implementation using GLFW.
|
||||
|
||||
use windowing::{ApplicationMethods, WindowEvent, WindowMethods};
|
||||
use windowing::{IdleWindowEvent, ResizeWindowEvent, LoadUrlWindowEvent, MouseWindowEventClass, MouseWindowMoveEventClass};
|
||||
use windowing::{ScrollWindowEvent, ZoomWindowEvent, PinchZoomWindowEvent, NavigationWindowEvent, FinishedWindowEvent};
|
||||
use windowing::{QuitWindowEvent, MouseWindowClickEvent, MouseWindowMouseDownEvent, MouseWindowMouseUpEvent};
|
||||
use windowing::RefreshWindowEvent;
|
||||
use windowing::{Forward, Back};
|
||||
|
||||
use alert::{Alert, AlertMethods};
|
||||
use libc::{exit, c_int};
|
||||
use time;
|
||||
use time::Timespec;
|
||||
use std::cell::{Cell, RefCell};
|
||||
use std::comm::Receiver;
|
||||
use std::rc::Rc;
|
||||
|
||||
use geom::point::{Point2D, TypedPoint2D};
|
||||
use geom::scale_factor::ScaleFactor;
|
||||
use geom::size::TypedSize2D;
|
||||
use layers::geometry::DevicePixel;
|
||||
use servo_msg::compositor_msg::{IdleRenderState, RenderState, RenderingRenderState};
|
||||
use servo_msg::compositor_msg::{FinishedLoading, Blank, Loading, PerformingLayout, ReadyState};
|
||||
use servo_util::geometry::ScreenPx;
|
||||
|
||||
use glfw;
|
||||
use glfw::Context;
|
||||
|
||||
/// A structure responsible for setting up and tearing down the entire windowing system.
|
||||
pub struct Application {
|
||||
pub glfw: glfw::Glfw,
|
||||
}
|
||||
|
||||
impl ApplicationMethods for Application {
|
||||
fn new() -> Application {
|
||||
let app = glfw::init(glfw::LOG_ERRORS);
|
||||
match app {
|
||||
Err(_) => {
|
||||
// handles things like inability to connect to X
|
||||
// cannot simply fail, since the runtime isn't up yet (causes a nasty abort)
|
||||
println!("GLFW initialization failed");
|
||||
unsafe { exit(1); }
|
||||
}
|
||||
Ok(app) => {
|
||||
Application { glfw: app }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! glfw_callback(
|
||||
(
|
||||
$callback:path ($($arg:ident: $arg_ty:ty),*) $block:expr
|
||||
) => ({
|
||||
struct GlfwCallback;
|
||||
impl $callback for GlfwCallback {
|
||||
fn call(&self $(, $arg: $arg_ty)*) {
|
||||
$block
|
||||
}
|
||||
}
|
||||
~GlfwCallback
|
||||
});
|
||||
|
||||
(
|
||||
[$($state:ident: $state_ty:ty),*],
|
||||
$callback:path ($($arg:ident: $arg_ty:ty),*) $block:expr
|
||||
) => ({
|
||||
struct GlfwCallback {
|
||||
$($state: $state_ty,)*
|
||||
}
|
||||
impl $callback for GlfwCallback {
|
||||
fn call(&self $(, $arg: $arg_ty)*) {
|
||||
$block
|
||||
}
|
||||
}
|
||||
~GlfwCallback {
|
||||
$($state: $state,)*
|
||||
}
|
||||
});
|
||||
)
|
||||
|
||||
|
||||
/// The type of a window.
|
||||
pub struct Window {
|
||||
glfw: glfw::Glfw,
|
||||
|
||||
glfw_window: glfw::Window,
|
||||
events: Receiver<(f64, glfw::WindowEvent)>,
|
||||
|
||||
event_queue: RefCell<Vec<WindowEvent>>,
|
||||
|
||||
mouse_down_button: Cell<Option<glfw::MouseButton>>,
|
||||
mouse_down_point: Cell<Point2D<c_int>>,
|
||||
|
||||
ready_state: Cell<ReadyState>,
|
||||
render_state: Cell<RenderState>,
|
||||
|
||||
last_title_set_time: Cell<Timespec>,
|
||||
}
|
||||
|
||||
impl WindowMethods<Application> for Window {
|
||||
/// Creates a new window.
|
||||
fn new(app: &Application, is_foreground: bool) -> Rc<Window> {
|
||||
// Create the GLFW window.
|
||||
app.glfw.window_hint(glfw::Visible(is_foreground));
|
||||
let (glfw_window, events) = app.glfw.create_window(800, 600, "Servo", glfw::Windowed)
|
||||
.expect("Failed to create GLFW window");
|
||||
glfw_window.make_current();
|
||||
|
||||
// Create our window object.
|
||||
let window = Window {
|
||||
glfw: app.glfw,
|
||||
|
||||
glfw_window: glfw_window,
|
||||
events: events,
|
||||
|
||||
event_queue: RefCell::new(vec!()),
|
||||
|
||||
mouse_down_button: Cell::new(None),
|
||||
mouse_down_point: Cell::new(Point2D(0 as c_int, 0)),
|
||||
|
||||
ready_state: Cell::new(Blank),
|
||||
render_state: Cell::new(IdleRenderState),
|
||||
|
||||
last_title_set_time: Cell::new(Timespec::new(0, 0)),
|
||||
};
|
||||
|
||||
// Register event handlers.
|
||||
window.glfw_window.set_framebuffer_size_polling(true);
|
||||
window.glfw_window.set_refresh_polling(true);
|
||||
window.glfw_window.set_key_polling(true);
|
||||
window.glfw_window.set_mouse_button_polling(true);
|
||||
window.glfw_window.set_cursor_pos_polling(true);
|
||||
window.glfw_window.set_scroll_polling(true);
|
||||
|
||||
let wrapped_window = Rc::new(window);
|
||||
|
||||
wrapped_window
|
||||
}
|
||||
|
||||
/// Returns the size of the window in hardware pixels.
|
||||
fn framebuffer_size(&self) -> TypedSize2D<DevicePixel, uint> {
|
||||
let (width, height) = self.glfw_window.get_framebuffer_size();
|
||||
TypedSize2D(width as uint, height as uint)
|
||||
}
|
||||
|
||||
/// Returns the size of the window in density-independent "px" units.
|
||||
fn size(&self) -> TypedSize2D<ScreenPx, f32> {
|
||||
let (width, height) = self.glfw_window.get_size();
|
||||
TypedSize2D(width as f32, height as f32)
|
||||
}
|
||||
|
||||
/// Presents the window to the screen (perhaps by page flipping).
|
||||
fn present(&self) {
|
||||
self.glfw_window.swap_buffers();
|
||||
}
|
||||
|
||||
fn recv(&self) -> WindowEvent {
|
||||
{
|
||||
let mut event_queue = self.event_queue.borrow_mut();
|
||||
if !event_queue.is_empty() {
|
||||
return event_queue.remove(0).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
self.glfw.poll_events();
|
||||
for (_, event) in glfw::flush_messages(&self.events) {
|
||||
self.handle_window_event(&self.glfw_window, event);
|
||||
}
|
||||
|
||||
if self.glfw_window.should_close() {
|
||||
QuitWindowEvent
|
||||
} else {
|
||||
self.event_queue.borrow_mut().remove(0).unwrap_or(IdleWindowEvent)
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the ready state.
|
||||
fn set_ready_state(&self, ready_state: ReadyState) {
|
||||
self.ready_state.set(ready_state);
|
||||
self.update_window_title()
|
||||
}
|
||||
|
||||
/// Sets the render state.
|
||||
fn set_render_state(&self, render_state: RenderState) {
|
||||
if self.ready_state.get() == FinishedLoading &&
|
||||
self.render_state.get() == RenderingRenderState &&
|
||||
render_state == IdleRenderState {
|
||||
// page loaded
|
||||
self.event_queue.borrow_mut().push(FinishedWindowEvent);
|
||||
}
|
||||
|
||||
self.render_state.set(render_state);
|
||||
self.update_window_title()
|
||||
}
|
||||
|
||||
fn hidpi_factor(&self) -> ScaleFactor<ScreenPx, DevicePixel, f32> {
|
||||
let backing_size = self.framebuffer_size().width.get();
|
||||
let window_size = self.size().width.get();
|
||||
ScaleFactor((backing_size as f32) / window_size)
|
||||
}
|
||||
}
|
||||
|
||||
impl Window {
|
||||
fn handle_window_event(&self, window: &glfw::Window, event: glfw::WindowEvent) {
|
||||
match event {
|
||||
glfw::KeyEvent(key, _, action, mods) => {
|
||||
if action == glfw::Press {
|
||||
self.handle_key(key, mods)
|
||||
}
|
||||
},
|
||||
glfw::FramebufferSizeEvent(width, height) => {
|
||||
self.event_queue.borrow_mut().push(
|
||||
ResizeWindowEvent(TypedSize2D(width as uint, height as uint)));
|
||||
},
|
||||
glfw::RefreshEvent => {
|
||||
self.event_queue.borrow_mut().push(RefreshWindowEvent);
|
||||
},
|
||||
glfw::MouseButtonEvent(button, action, _mods) => {
|
||||
let (x, y) = window.get_cursor_pos();
|
||||
//handle hidpi displays, since GLFW returns non-hi-def coordinates.
|
||||
let (backing_size, _) = window.get_framebuffer_size();
|
||||
let (window_size, _) = window.get_size();
|
||||
let hidpi = (backing_size as f32) / (window_size as f32);
|
||||
let x = x as f32 * hidpi;
|
||||
let y = y as f32 * hidpi;
|
||||
if button == glfw::MouseButtonLeft || button == glfw::MouseButtonRight {
|
||||
self.handle_mouse(button, action, x as i32, y as i32);
|
||||
}
|
||||
},
|
||||
glfw::CursorPosEvent(xpos, ypos) => {
|
||||
self.event_queue.borrow_mut().push(
|
||||
MouseWindowMoveEventClass(TypedPoint2D(xpos as f32, ypos as f32)));
|
||||
},
|
||||
glfw::ScrollEvent(xpos, ypos) => {
|
||||
match (window.get_key(glfw::KeyLeftControl),
|
||||
window.get_key(glfw::KeyRightControl)) {
|
||||
(glfw::Press, _) | (_, glfw::Press) => {
|
||||
// Ctrl-Scrollwheel simulates a "pinch zoom" gesture.
|
||||
if ypos < 0.0 {
|
||||
self.event_queue.borrow_mut().push(PinchZoomWindowEvent(1.0/1.1));
|
||||
} else if ypos > 0.0 {
|
||||
self.event_queue.borrow_mut().push(PinchZoomWindowEvent(1.1));
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
let dx = (xpos as f32) * 30.0;
|
||||
let dy = (ypos as f32) * 30.0;
|
||||
self.scroll_window(dx, dy);
|
||||
}
|
||||
}
|
||||
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper function to send a scroll event.
|
||||
fn scroll_window(&self, dx: f32, dy: f32) {
|
||||
let (x, y) = self.glfw_window.get_cursor_pos();
|
||||
//handle hidpi displays, since GLFW returns non-hi-def coordinates.
|
||||
let (backing_size, _) = self.glfw_window.get_framebuffer_size();
|
||||
let (window_size, _) = self.glfw_window.get_size();
|
||||
let hidpi = (backing_size as f32) / (window_size as f32);
|
||||
let x = x as f32 * hidpi;
|
||||
let y = y as f32 * hidpi;
|
||||
|
||||
self.event_queue.borrow_mut().push(ScrollWindowEvent(TypedPoint2D(dx, dy),
|
||||
TypedPoint2D(x as i32, y as i32)));
|
||||
}
|
||||
|
||||
/// Helper function to set the window title in accordance with the ready state.
|
||||
fn update_window_title(&self) {
|
||||
let now = time::get_time();
|
||||
if now.sec == self.last_title_set_time.get().sec {
|
||||
return
|
||||
}
|
||||
self.last_title_set_time.set(now);
|
||||
|
||||
match self.ready_state.get() {
|
||||
Blank => {
|
||||
self.glfw_window.set_title("blank — Servo")
|
||||
}
|
||||
Loading => {
|
||||
self.glfw_window.set_title("Loading — Servo")
|
||||
}
|
||||
PerformingLayout => {
|
||||
self.glfw_window.set_title("Performing Layout — Servo")
|
||||
}
|
||||
FinishedLoading => {
|
||||
match self.render_state.get() {
|
||||
RenderingRenderState => {
|
||||
self.glfw_window.set_title("Rendering — Servo")
|
||||
}
|
||||
IdleRenderState => {
|
||||
self.glfw_window.set_title("Servo")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper function to handle keyboard events.
|
||||
fn handle_key(&self, key: glfw::Key, mods: glfw::Modifiers) {
|
||||
match key {
|
||||
glfw::KeyEscape => self.glfw_window.set_should_close(true),
|
||||
glfw::KeyL if mods.contains(glfw::Control) => self.load_url(), // Ctrl+L
|
||||
glfw::KeyEqual if mods.contains(glfw::Control) => { // Ctrl-+
|
||||
self.event_queue.borrow_mut().push(ZoomWindowEvent(1.1));
|
||||
}
|
||||
glfw::KeyMinus if mods.contains(glfw::Control) => { // Ctrl--
|
||||
self.event_queue.borrow_mut().push(ZoomWindowEvent(1.0/1.1));
|
||||
}
|
||||
glfw::KeyBackspace if mods.contains(glfw::Shift) => { // Shift-Backspace
|
||||
self.event_queue.borrow_mut().push(NavigationWindowEvent(Forward));
|
||||
}
|
||||
glfw::KeyBackspace => { // Backspace
|
||||
self.event_queue.borrow_mut().push(NavigationWindowEvent(Back));
|
||||
}
|
||||
glfw::KeyPageDown => {
|
||||
let (_, height) = self.glfw_window.get_size();
|
||||
self.scroll_window(0.0, -height as f32);
|
||||
}
|
||||
glfw::KeyPageUp => {
|
||||
let (_, height) = self.glfw_window.get_size();
|
||||
self.scroll_window(0.0, height as f32);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper function to handle a click
|
||||
fn handle_mouse(&self, button: glfw::MouseButton, action: glfw::Action, x: c_int, y: c_int) {
|
||||
// FIXME(tkuehn): max pixel dist should be based on pixel density
|
||||
let max_pixel_dist = 10f64;
|
||||
let event = match action {
|
||||
glfw::Press => {
|
||||
self.mouse_down_point.set(Point2D(x, y));
|
||||
self.mouse_down_button.set(Some(button));
|
||||
MouseWindowMouseDownEvent(button as uint, TypedPoint2D(x as f32, y as f32))
|
||||
}
|
||||
glfw::Release => {
|
||||
match self.mouse_down_button.get() {
|
||||
None => (),
|
||||
Some(but) if button == but => {
|
||||
let pixel_dist = self.mouse_down_point.get() - Point2D(x, y);
|
||||
let pixel_dist = ((pixel_dist.x * pixel_dist.x +
|
||||
pixel_dist.y * pixel_dist.y) as f64).sqrt();
|
||||
if pixel_dist < max_pixel_dist {
|
||||
let click_event = MouseWindowClickEvent(button as uint,
|
||||
TypedPoint2D(x as f32, y as f32));
|
||||
self.event_queue.borrow_mut().push(MouseWindowEventClass(click_event));
|
||||
}
|
||||
}
|
||||
Some(_) => (),
|
||||
}
|
||||
MouseWindowMouseUpEvent(button as uint, TypedPoint2D(x as f32, y as f32))
|
||||
}
|
||||
_ => fail!("I cannot recognize the type of mouse action that occured. :-(")
|
||||
};
|
||||
self.event_queue.borrow_mut().push(MouseWindowEventClass(event));
|
||||
}
|
||||
|
||||
/// Helper function to pop up an alert box prompting the user to load a URL.
|
||||
fn load_url(&self) {
|
||||
let mut alert: Alert = AlertMethods::new("Navigate to:");
|
||||
alert.add_prompt();
|
||||
alert.run();
|
||||
let value = alert.prompt_value();
|
||||
if "" == value.as_slice() { // To avoid crashing on Linux.
|
||||
self.event_queue.borrow_mut().push(LoadUrlWindowEvent("http://purple.com/".to_string()))
|
||||
} else {
|
||||
self.event_queue.borrow_mut().push(LoadUrlWindowEvent(value.clone()))
|
||||
}
|
||||
}
|
||||
}
|
303
components/compositing/platform/common/glut_windowing.rs
Normal file
303
components/compositing/platform/common/glut_windowing.rs
Normal file
|
@ -0,0 +1,303 @@
|
|||
/* 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/. */
|
||||
|
||||
//! A windowing implementation using GLUT.
|
||||
|
||||
use windowing::{ApplicationMethods, WindowEvent, WindowMethods};
|
||||
use windowing::{IdleWindowEvent, ResizeWindowEvent, LoadUrlWindowEvent, MouseWindowEventClass};
|
||||
use windowing::{ScrollWindowEvent, ZoomWindowEvent, NavigationWindowEvent, FinishedWindowEvent};
|
||||
use windowing::{MouseWindowClickEvent, MouseWindowMouseDownEvent, MouseWindowMouseUpEvent};
|
||||
use windowing::{Forward, Back};
|
||||
|
||||
use alert::{Alert, AlertMethods};
|
||||
use libc::{c_int, c_uchar};
|
||||
use std::cell::{Cell, RefCell};
|
||||
use std::rc::Rc;
|
||||
use geom::point::{Point2D, TypedPoint2D};
|
||||
use geom::scale_factor::ScaleFactor;
|
||||
use geom::size::TypedSize2D;
|
||||
use layers::geometry::DevicePixel;
|
||||
use servo_msg::compositor_msg::{IdleRenderState, RenderState, RenderingRenderState};
|
||||
use servo_msg::compositor_msg::{FinishedLoading, Blank, ReadyState};
|
||||
use servo_util::geometry::ScreenPx;
|
||||
|
||||
use glut::glut::{ACTIVE_SHIFT, DOUBLE, WindowHeight};
|
||||
use glut::glut::WindowWidth;
|
||||
use glut::glut;
|
||||
|
||||
// static THROBBER: [char, ..8] = [ '⣾', '⣽', '⣻', '⢿', '⡿', '⣟', '⣯', '⣷' ];
|
||||
|
||||
/// A structure responsible for setting up and tearing down the entire windowing system.
|
||||
pub struct Application;
|
||||
|
||||
impl ApplicationMethods for Application {
|
||||
fn new() -> Application {
|
||||
glut::init();
|
||||
glut::init_display_mode(DOUBLE);
|
||||
Application
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Application {
|
||||
fn drop(&mut self) {
|
||||
drop_local_window();
|
||||
}
|
||||
}
|
||||
|
||||
/// The type of a window.
|
||||
pub struct Window {
|
||||
pub glut_window: glut::Window,
|
||||
|
||||
pub event_queue: RefCell<Vec<WindowEvent>>,
|
||||
|
||||
pub drag_origin: Point2D<c_int>,
|
||||
|
||||
pub mouse_down_button: Cell<c_int>,
|
||||
pub mouse_down_point: Cell<Point2D<c_int>>,
|
||||
|
||||
pub ready_state: Cell<ReadyState>,
|
||||
pub render_state: Cell<RenderState>,
|
||||
pub throbber_frame: Cell<u8>,
|
||||
}
|
||||
|
||||
impl WindowMethods<Application> for Window {
|
||||
/// Creates a new window.
|
||||
fn new(_: &Application, _: bool) -> Rc<Window> {
|
||||
// Create the GLUT window.
|
||||
glut::init_window_size(800, 600);
|
||||
let glut_window = glut::create_window("Servo".to_string());
|
||||
|
||||
// Create our window object.
|
||||
let window = Window {
|
||||
glut_window: glut_window,
|
||||
|
||||
event_queue: RefCell::new(vec!()),
|
||||
|
||||
drag_origin: Point2D(0 as c_int, 0),
|
||||
|
||||
mouse_down_button: Cell::new(0),
|
||||
mouse_down_point: Cell::new(Point2D(0 as c_int, 0)),
|
||||
|
||||
ready_state: Cell::new(Blank),
|
||||
render_state: Cell::new(IdleRenderState),
|
||||
throbber_frame: Cell::new(0),
|
||||
};
|
||||
|
||||
// Register event handlers.
|
||||
|
||||
//Added dummy display callback to freeglut. According to freeglut ref, we should register some kind of display callback after freeglut 3.0.
|
||||
|
||||
struct DisplayCallbackState;
|
||||
impl glut::DisplayCallback for DisplayCallbackState {
|
||||
fn call(&self) {
|
||||
debug!("GLUT display func registgered");
|
||||
}
|
||||
}
|
||||
glut::display_func(box DisplayCallbackState);
|
||||
struct ReshapeCallbackState;
|
||||
impl glut::ReshapeCallback for ReshapeCallbackState {
|
||||
fn call(&self, width: c_int, height: c_int) {
|
||||
let tmp = local_window();
|
||||
tmp.event_queue.borrow_mut().push(ResizeWindowEvent(TypedSize2D(width as uint, height as uint)))
|
||||
}
|
||||
}
|
||||
glut::reshape_func(glut_window, box ReshapeCallbackState);
|
||||
struct KeyboardCallbackState;
|
||||
impl glut::KeyboardCallback for KeyboardCallbackState {
|
||||
fn call(&self, key: c_uchar, _x: c_int, _y: c_int) {
|
||||
let tmp = local_window();
|
||||
tmp.handle_key(key)
|
||||
}
|
||||
}
|
||||
glut::keyboard_func(box KeyboardCallbackState);
|
||||
struct MouseCallbackState;
|
||||
impl glut::MouseCallback for MouseCallbackState {
|
||||
fn call(&self, button: c_int, state: c_int, x: c_int, y: c_int) {
|
||||
if button < 3 {
|
||||
let tmp = local_window();
|
||||
tmp.handle_mouse(button, state, x, y);
|
||||
} else {
|
||||
match button {
|
||||
3 => {
|
||||
let tmp = local_window();
|
||||
tmp.event_queue.borrow_mut().push(ScrollWindowEvent(
|
||||
TypedPoint2D(0.0f32, 5.0f32),
|
||||
TypedPoint2D(0i32, 5i32)));
|
||||
},
|
||||
4 => {
|
||||
let tmp = local_window();
|
||||
tmp.event_queue.borrow_mut().push(ScrollWindowEvent(
|
||||
TypedPoint2D(0.0f32, -5.0f32),
|
||||
TypedPoint2D(0i32, -5i32)));
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
glut::mouse_func(box MouseCallbackState);
|
||||
|
||||
let wrapped_window = Rc::new(window);
|
||||
|
||||
install_local_window(wrapped_window.clone());
|
||||
|
||||
wrapped_window
|
||||
}
|
||||
|
||||
/// Returns the size of the window in hardware pixels.
|
||||
fn framebuffer_size(&self) -> TypedSize2D<DevicePixel, uint> {
|
||||
TypedSize2D(glut::get(WindowWidth) as uint, glut::get(WindowHeight) as uint)
|
||||
}
|
||||
|
||||
/// Returns the size of the window in density-independent "px" units.
|
||||
fn size(&self) -> TypedSize2D<ScreenPx, f32> {
|
||||
self.framebuffer_size().as_f32() / self.hidpi_factor()
|
||||
}
|
||||
|
||||
/// Presents the window to the screen (perhaps by page flipping).
|
||||
fn present(&self) {
|
||||
glut::swap_buffers();
|
||||
}
|
||||
|
||||
fn recv(&self) -> WindowEvent {
|
||||
if !self.event_queue.borrow_mut().is_empty() {
|
||||
return self.event_queue.borrow_mut().remove(0).unwrap();
|
||||
}
|
||||
|
||||
glut::check_loop();
|
||||
|
||||
self.event_queue.borrow_mut().remove(0).unwrap_or(IdleWindowEvent)
|
||||
}
|
||||
|
||||
/// Sets the ready state.
|
||||
fn set_ready_state(&self, ready_state: ReadyState) {
|
||||
self.ready_state.set(ready_state);
|
||||
//FIXME: set_window_title causes crash with Android version of freeGLUT. Temporarily blocked.
|
||||
//self.update_window_title()
|
||||
}
|
||||
|
||||
/// Sets the render state.
|
||||
fn set_render_state(&self, render_state: RenderState) {
|
||||
if self.ready_state.get() == FinishedLoading &&
|
||||
self.render_state.get() == RenderingRenderState &&
|
||||
render_state == IdleRenderState {
|
||||
// page loaded
|
||||
self.event_queue.borrow_mut().push(FinishedWindowEvent);
|
||||
}
|
||||
|
||||
self.render_state.set(render_state);
|
||||
//FIXME: set_window_title causes crash with Android version of freeGLUT. Temporarily blocked.
|
||||
//self.update_window_title()
|
||||
}
|
||||
|
||||
fn hidpi_factor(&self) -> ScaleFactor<ScreenPx, DevicePixel, f32> {
|
||||
//FIXME: Do nothing in GLUT now.
|
||||
ScaleFactor(1.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Window {
|
||||
/// Helper function to set the window title in accordance with the ready state.
|
||||
// fn update_window_title(&self) {
|
||||
// let throbber = THROBBER[self.throbber_frame];
|
||||
// match self.ready_state {
|
||||
// Blank => {
|
||||
// glut::set_window_title(self.glut_window, "Blank")
|
||||
// }
|
||||
// Loading => {
|
||||
// glut::set_window_title(self.glut_window, format!("{:c} Loading . Servo", throbber))
|
||||
// }
|
||||
// PerformingLayout => {
|
||||
// glut::set_window_title(self.glut_window, format!("{:c} Performing Layout . Servo", throbber))
|
||||
// }
|
||||
// FinishedLoading => {
|
||||
// match self.render_state {
|
||||
// RenderingRenderState => {
|
||||
// glut::set_window_title(self.glut_window, format!("{:c} Rendering . Servo", throbber))
|
||||
// }
|
||||
// IdleRenderState => glut::set_window_title(self.glut_window, "Servo"),
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
/// Helper function to handle keyboard events.
|
||||
fn handle_key(&self, key: u8) {
|
||||
debug!("got key: {}", key);
|
||||
let modifiers = glut::get_modifiers();
|
||||
match key {
|
||||
42 => self.load_url(),
|
||||
43 => self.event_queue.borrow_mut().push(ZoomWindowEvent(1.1)),
|
||||
45 => self.event_queue.borrow_mut().push(ZoomWindowEvent(0.909090909)),
|
||||
56 => self.event_queue.borrow_mut().push(ScrollWindowEvent(TypedPoint2D(0.0f32, 5.0f32),
|
||||
TypedPoint2D(0i32, 5i32))),
|
||||
50 => self.event_queue.borrow_mut().push(ScrollWindowEvent(TypedPoint2D(0.0f32, -5.0f32),
|
||||
TypedPoint2D(0i32, -5i32))),
|
||||
127 => {
|
||||
if (modifiers & ACTIVE_SHIFT) != 0 {
|
||||
self.event_queue.borrow_mut().push(NavigationWindowEvent(Forward));
|
||||
}
|
||||
else {
|
||||
self.event_queue.borrow_mut().push(NavigationWindowEvent(Back));
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper function to handle a click
|
||||
fn handle_mouse(&self, button: c_int, state: c_int, x: c_int, y: c_int) {
|
||||
// FIXME(tkuehn): max pixel dist should be based on pixel density
|
||||
let max_pixel_dist = 10f32;
|
||||
let event = match state {
|
||||
glut::MOUSE_DOWN => {
|
||||
self.mouse_down_point.set(Point2D(x, y));
|
||||
self.mouse_down_button.set(button);
|
||||
MouseWindowMouseDownEvent(button as uint, TypedPoint2D(x as f32, y as f32))
|
||||
}
|
||||
glut::MOUSE_UP => {
|
||||
if self.mouse_down_button.get() == button {
|
||||
let pixel_dist = self.mouse_down_point.get() - Point2D(x, y);
|
||||
let pixel_dist = ((pixel_dist.x * pixel_dist.x +
|
||||
pixel_dist.y * pixel_dist.y) as f32).sqrt();
|
||||
if pixel_dist < max_pixel_dist {
|
||||
let click_event = MouseWindowClickEvent(button as uint,
|
||||
TypedPoint2D(x as f32, y as f32));
|
||||
self.event_queue.borrow_mut().push(MouseWindowEventClass(click_event));
|
||||
}
|
||||
}
|
||||
MouseWindowMouseUpEvent(button as uint, TypedPoint2D(x as f32, y as f32))
|
||||
}
|
||||
_ => fail!("I cannot recognize the type of mouse action that occured. :-(")
|
||||
};
|
||||
self.event_queue.borrow_mut().push(MouseWindowEventClass(event));
|
||||
}
|
||||
|
||||
/// Helper function to pop up an alert box prompting the user to load a URL.
|
||||
fn load_url(&self) {
|
||||
let mut alert: Alert = AlertMethods::new("Navigate to:");
|
||||
alert.add_prompt();
|
||||
alert.run();
|
||||
let value = alert.prompt_value();
|
||||
if "" == value.as_slice() { // To avoid crashing on Linux.
|
||||
self.event_queue.borrow_mut().push(LoadUrlWindowEvent("http://purple.com/".to_string()))
|
||||
} else {
|
||||
self.event_queue.borrow_mut().push(LoadUrlWindowEvent(value.clone()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
local_data_key!(TLS_KEY: Rc<Window>)
|
||||
|
||||
fn install_local_window(window: Rc<Window>) {
|
||||
TLS_KEY.replace(Some(window));
|
||||
}
|
||||
|
||||
fn drop_local_window() {
|
||||
TLS_KEY.replace(None);
|
||||
}
|
||||
|
||||
fn local_window() -> Rc<Window> {
|
||||
TLS_KEY.get().unwrap().clone()
|
||||
}
|
18
components/compositing/platform/mod.rs
Normal file
18
components/compositing/platform/mod.rs
Normal file
|
@ -0,0 +1,18 @@
|
|||
/* 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/. */
|
||||
|
||||
//! Platform-specific functionality for Servo.
|
||||
|
||||
#[cfg(target_os="android")]
|
||||
pub use platform::common::glut_windowing::{Application, Window};
|
||||
#[cfg(not(target_os="android"))]
|
||||
pub use platform::common::glfw_windowing::{Application, Window};
|
||||
|
||||
pub mod common {
|
||||
#[cfg(target_os="android")]
|
||||
pub mod glut_windowing;
|
||||
#[cfg(not(target_os="android"))]
|
||||
pub mod glfw_windowing;
|
||||
}
|
||||
|
83
components/compositing/windowing.rs
Normal file
83
components/compositing/windowing.rs
Normal file
|
@ -0,0 +1,83 @@
|
|||
/* 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/. */
|
||||
|
||||
//! Abstract windowing methods. The concrete implementations of these can be found in `platform/`.
|
||||
|
||||
use geom::point::TypedPoint2D;
|
||||
use geom::scale_factor::ScaleFactor;
|
||||
use geom::size::TypedSize2D;
|
||||
use layers::geometry::DevicePixel;
|
||||
use servo_msg::compositor_msg::{ReadyState, RenderState};
|
||||
use servo_util::geometry::ScreenPx;
|
||||
use std::rc::Rc;
|
||||
|
||||
pub enum MouseWindowEvent {
|
||||
MouseWindowClickEvent(uint, TypedPoint2D<DevicePixel, f32>),
|
||||
MouseWindowMouseDownEvent(uint, TypedPoint2D<DevicePixel, f32>),
|
||||
MouseWindowMouseUpEvent(uint, TypedPoint2D<DevicePixel, f32>),
|
||||
}
|
||||
|
||||
pub enum WindowNavigateMsg {
|
||||
Forward,
|
||||
Back,
|
||||
}
|
||||
|
||||
/// Events that the windowing system sends to Servo.
|
||||
pub enum WindowEvent {
|
||||
/// Sent when no message has arrived.
|
||||
///
|
||||
/// FIXME: This is a bogus event and is only used because we don't have the new
|
||||
/// scheduler integrated with the platform event loop.
|
||||
IdleWindowEvent,
|
||||
/// Sent when part of the window is marked dirty and needs to be redrawn.
|
||||
RefreshWindowEvent,
|
||||
/// Sent when the window is resized.
|
||||
ResizeWindowEvent(TypedSize2D<DevicePixel, uint>),
|
||||
/// Sent when a new URL is to be loaded.
|
||||
LoadUrlWindowEvent(String),
|
||||
/// Sent when a mouse hit test is to be performed.
|
||||
MouseWindowEventClass(MouseWindowEvent),
|
||||
/// Sent when a mouse move.
|
||||
MouseWindowMoveEventClass(TypedPoint2D<DevicePixel, f32>),
|
||||
/// Sent when the user scrolls. Includes the current cursor position.
|
||||
ScrollWindowEvent(TypedPoint2D<DevicePixel, f32>, TypedPoint2D<DevicePixel, i32>),
|
||||
/// Sent when the user zooms.
|
||||
ZoomWindowEvent(f32),
|
||||
/// Simulated "pinch zoom" gesture for non-touch platforms (e.g. ctrl-scrollwheel).
|
||||
PinchZoomWindowEvent(f32),
|
||||
/// Sent when the user uses chrome navigation (i.e. backspace or shift-backspace).
|
||||
NavigationWindowEvent(WindowNavigateMsg),
|
||||
/// Sent when rendering is finished.
|
||||
FinishedWindowEvent,
|
||||
/// Sent when the user quits the application
|
||||
QuitWindowEvent,
|
||||
}
|
||||
|
||||
/// Methods for an abstract Application.
|
||||
pub trait ApplicationMethods {
|
||||
fn new() -> Self;
|
||||
}
|
||||
|
||||
pub trait WindowMethods<A> {
|
||||
/// Creates a new window.
|
||||
fn new(app: &A, is_foreground: bool) -> Rc<Self>;
|
||||
/// Returns the size of the window in hardware pixels.
|
||||
fn framebuffer_size(&self) -> TypedSize2D<DevicePixel, uint>;
|
||||
/// Returns the size of the window in density-independent "px" units.
|
||||
fn size(&self) -> TypedSize2D<ScreenPx, f32>;
|
||||
/// Presents the window to the screen (perhaps by page flipping).
|
||||
fn present(&self);
|
||||
|
||||
/// Spins the event loop and returns the next event.
|
||||
fn recv(&self) -> WindowEvent;
|
||||
|
||||
/// Sets the ready state of the current page.
|
||||
fn set_ready_state(&self, ready_state: ReadyState);
|
||||
/// Sets the render state of the current page.
|
||||
fn set_render_state(&self, render_state: RenderState);
|
||||
|
||||
/// Returns the hidpi factor of the monitor.
|
||||
fn hidpi_factor(&self) -> ScaleFactor<ScreenPx, DevicePixel, f32>;
|
||||
}
|
||||
|
Loading…
Add table
Add a link
Reference in a new issue