Invert control flow, fix resizing, and improve checkerboarding

significantly by giving tiles some time to paint before we render
unrendered content.
This commit is contained in:
Patrick Walton 2014-10-19 09:23:18 -07:00
parent e483a189a3
commit 10f7b49cf7
27 changed files with 1195 additions and 678 deletions

4
Cargo.lock generated
View file

@ -221,14 +221,14 @@ name = "glfw"
version = "0.0.1"
source = "git+https://github.com/servo/glfw-rs?ref=servo#a15c2d04b8969aea653841d1d79e5fdf68de664b"
dependencies = [
"glfw-sys 3.0.4 (git+https://github.com/servo/glfw?ref=cargo-3.0.4#65a2b4721276589d9de24f6a9999a2db37286cae)",
"glfw-sys 3.0.4 (git+https://github.com/servo/glfw?ref=cargo-3.0.4#aa8e0d26cccdb4145f34c5a1724d7e48e0399674)",
"semver 0.0.1 (git+https://github.com/rust-lang/semver#d04583a173395b76c1eaa15cc630a5f6f8f0ae10)",
]
[[package]]
name = "glfw-sys"
version = "3.0.4"
source = "git+https://github.com/servo/glfw?ref=cargo-3.0.4#65a2b4721276589d9de24f6a9999a2db37286cae"
source = "git+https://github.com/servo/glfw?ref=cargo-3.0.4#aa8e0d26cccdb4145f34c5a1724d7e48e0399674"
[[package]]
name = "glfw_app"

View file

@ -7,21 +7,23 @@ use compositor_layer::{ScrollPositionChanged, WantsScrollEvents};
use compositor_task::{Msg, CompositorTask, Exit, ChangeReadyState, SetIds, LayerProperties};
use compositor_task::{GetGraphicsMetadata, CreateOrUpdateRootLayer, CreateOrUpdateDescendantLayer};
use compositor_task::{SetLayerOrigin, Paint, ScrollFragmentPoint, LoadComplete};
use compositor_task::{ShutdownComplete, ChangeRenderState, RenderMsgDiscarded};
use compositor_task::{ShutdownComplete, ChangeRenderState, RenderMsgDiscarded, ScrollTimeout};
use compositor_task::{CompositorEventListener, CompositorProxy, CompositorReceiver};
use constellation::SendableFrameTree;
use pipeline::CompositionPipeline;
use scrolling::ScrollingTimerProxy;
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 windowing::{PinchZoomWindowEvent};
use azure::azure_hl;
use std::cmp;
use std::mem;
use std::num::Zero;
use std::time::duration::Duration;
use geom::point::{Point2D, TypedPoint2D};
use geom::rect::{Rect, TypedRect};
use geom::size::TypedSize2D;
@ -45,20 +47,18 @@ use servo_util::memory::MemoryProfilerChan;
use servo_util::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 time::{precise_time_ns, precise_time_s};
use url::Url;
pub struct IOCompositor<Window: WindowMethods> {
/// The application window.
window: Rc<Window>,
/// The port on which we receive messages.
port: Receiver<Msg>,
port: Box<CompositorReceiver>,
/// The render context.
context: RenderContext,
@ -82,20 +82,23 @@ pub struct IOCompositor<Window: WindowMethods> {
/// 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,
/// A handle to the scrolling timer.
scrolling_timer: ScrollingTimerProxy,
/// Tracks whether we should composite this frame.
composition_request: CompositionRequest,
/// 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.
/// Tracks the last composite time.
last_composite_time: u64,
/// Tracks whether the zoom action has happened recently.
zoom_action: bool,
/// The time of the last zoom action has started.
@ -112,6 +115,9 @@ pub struct IOCompositor<Window: WindowMethods> {
/// many times for a single page.
got_load_complete_message: bool,
/// Whether we have gotten a `SetIds` message.
got_set_ids_message: bool,
/// The channel on which messages can be sent to the constellation.
constellation_chan: ConstellationChan,
@ -122,10 +128,25 @@ pub struct IOCompositor<Window: WindowMethods> {
memory_profiler_chan: MemoryProfilerChan,
/// Pending scroll to fragment event, if any
fragment_point: Option<Point2D<f32>>
fragment_point: Option<Point2D<f32>>,
/// Pending scroll events.
pending_scroll_events: Vec<ScrollEvent>,
}
pub struct ScrollEvent {
delta: TypedPoint2D<DevicePixel,f32>,
cursor: TypedPoint2D<DevicePixel,i32>,
}
#[deriving(PartialEq)]
enum CompositionRequest {
NoCompositingNecessary,
CompositeOnScrollTimeout(u64),
CompositeNow,
}
#[deriving(PartialEq, Show)]
enum ShutdownState {
NotShuttingDown,
ShuttingDown,
@ -139,11 +160,12 @@ struct HitTestResult {
impl<Window: WindowMethods> IOCompositor<Window> {
fn new(window: Rc<Window>,
port: Receiver<Msg>,
sender: Box<CompositorProxy+Send>,
receiver: Box<CompositorReceiver>,
constellation_chan: ConstellationChan,
time_profiler_chan: TimeProfilerChan,
memory_profiler_chan: MemoryProfilerChan) -> IOCompositor<Window> {
memory_profiler_chan: MemoryProfilerChan)
-> IOCompositor<Window> {
// Create an initial layer tree.
//
// TODO: There should be no initial layer tree until the renderer creates one from the
@ -155,7 +177,7 @@ impl<Window: WindowMethods> IOCompositor<Window> {
let show_debug_borders = opts::get().show_debug_borders;
IOCompositor {
window: window,
port: port,
port: receiver,
context: rendergl::RenderContext::new(context, show_debug_borders),
root_pipeline: None,
scene: Scene::new(Rect {
@ -164,9 +186,10 @@ impl<Window: WindowMethods> IOCompositor<Window> {
}),
window_size: window_size,
hidpi_factor: hidpi_factor,
composite_ready: false,
scrolling_timer: ScrollingTimerProxy::new(sender),
composition_request: NoCompositingNecessary,
pending_scroll_events: Vec::new(),
shutdown_state: NotShuttingDown,
recomposite: false,
page_zoom: ScaleFactor(1.0),
viewport_zoom: ScaleFactor(1.0),
zoom_action: false,
@ -174,99 +197,45 @@ impl<Window: WindowMethods> IOCompositor<Window> {
ready_states: HashMap::new(),
render_states: HashMap::new(),
got_load_complete_message: false,
got_set_ids_message: false,
constellation_chan: constellation_chan,
time_profiler_chan: time_profiler_chan,
memory_profiler_chan: memory_profiler_chan,
fragment_point: None,
outstanding_render_msgs: 0,
last_composite_time: 0,
}
}
pub fn create(window: Rc<Window>,
port: Receiver<Msg>,
sender: Box<CompositorProxy+Send>,
receiver: Box<CompositorReceiver>,
constellation_chan: ConstellationChan,
time_profiler_chan: TimeProfilerChan,
memory_profiler_chan: MemoryProfilerChan) {
memory_profiler_chan: MemoryProfilerChan)
-> IOCompositor<Window> {
let mut compositor = IOCompositor::new(window,
port,
sender,
receiver,
constellation_chan,
time_profiler_chan,
memory_profiler_chan);
// Set the size of the root layer.
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();
compositor.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;
compositor
}
// 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(Duration::milliseconds(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) => layer.forget_all_tiles(),
}
// 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) {
fn handle_browser_message(&mut self, msg: Msg) -> bool {
match (msg, self.shutdown_state) {
(_, FinishedShuttingDown) =>
fail!("compositor shouldn't be handling messages after shutting down"),
(Err(_), _) => break,
(Ok(Exit(chan)), _) => {
(Exit(chan), _) => {
debug!("shutting down the constellation");
let ConstellationChan(ref con_chan) = self.constellation_chan;
con_chan.send(ExitMsg);
@ -274,59 +243,74 @@ impl<Window: WindowMethods> IOCompositor<Window> {
self.shutdown_state = ShuttingDown;
}
(Ok(ShutdownComplete), _) => {
(ShutdownComplete, _) => {
debug!("constellation completed shutdown");
self.shutdown_state = FinishedShuttingDown;
break;
return false;
}
(Ok(ChangeReadyState(pipeline_id, ready_state)), NotShuttingDown) => {
(ChangeReadyState(pipeline_id, ready_state), NotShuttingDown) => {
self.change_ready_state(pipeline_id, ready_state);
}
(Ok(ChangeRenderState(pipeline_id, render_state)), NotShuttingDown) => {
(ChangeRenderState(pipeline_id, render_state), NotShuttingDown) => {
self.change_render_state(pipeline_id, render_state);
}
(Ok(RenderMsgDiscarded), NotShuttingDown) => {
(RenderMsgDiscarded, NotShuttingDown) => {
self.remove_outstanding_render_msg();
}
(Ok(SetIds(frame_tree, response_chan, new_constellation_chan)), _) => {
(SetIds(frame_tree, response_chan, new_constellation_chan), NotShuttingDown) => {
self.set_frame_tree(&frame_tree,
response_chan,
new_constellation_chan);
}
(Ok(GetGraphicsMetadata(chan)), NotShuttingDown) => {
chan.send(Some(self.window.native_metadata()));
}
(Ok(CreateOrUpdateRootLayer(layer_properties)), NotShuttingDown) => {
(CreateOrUpdateRootLayer(layer_properties), NotShuttingDown) => {
self.create_or_update_root_layer(layer_properties);
}
(Ok(CreateOrUpdateDescendantLayer(layer_properties)), NotShuttingDown) => {
(CreateOrUpdateDescendantLayer(layer_properties), NotShuttingDown) => {
self.create_or_update_descendant_layer(layer_properties);
}
(Ok(SetLayerOrigin(pipeline_id, layer_id, origin)), NotShuttingDown) => {
(GetGraphicsMetadata(chan), NotShuttingDown) => {
chan.send(Some(self.window.native_metadata()));
}
(SetLayerOrigin(pipeline_id, layer_id, origin), NotShuttingDown) => {
self.set_layer_origin(pipeline_id, layer_id, origin);
}
(Ok(Paint(pipeline_id, epoch, replies)), NotShuttingDown) => {
(Paint(pipeline_id, epoch, replies), NotShuttingDown) => {
for (layer_id, new_layer_buffer_set) in replies.into_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) => {
(ScrollFragmentPoint(pipeline_id, layer_id, point), NotShuttingDown) => {
self.scroll_fragment_to_point(pipeline_id, layer_id, point);
}
(Ok(LoadComplete(..)), NotShuttingDown) => {
(LoadComplete(..), NotShuttingDown) => {
self.got_load_complete_message = true;
// If we're rendering in headless mode, schedule a recomposite.
if opts::get().output_file.is_some() {
self.composite_if_necessary();
}
}
(ScrollTimeout(timestamp), NotShuttingDown) => {
debug!("scroll timeout, drawing unrendered content!");
match self.composition_request {
CompositeOnScrollTimeout(this_timestamp) if timestamp == this_timestamp => {
self.composition_request = CompositeNow
}
_ => {}
}
}
// When we are shutting_down, we need to avoid performing operations
@ -334,7 +318,8 @@ impl<Window: WindowMethods> IOCompositor<Window> {
// the rest of our resources.
(_, ShuttingDown) => { }
}
}
true
}
fn change_ready_state(&mut self, pipeline_id: PipelineId, ready_state: ReadyState) {
@ -342,6 +327,11 @@ impl<Window: WindowMethods> IOCompositor<Window> {
ready_state,
|_key, value| *value = ready_state);
self.window.set_ready_state(self.get_earliest_pipeline_ready_state());
// If we're rendering in headless mode, schedule a recomposite.
if opts::get().output_file.is_some() {
self.composite_if_necessary()
}
}
fn get_earliest_pipeline_ready_state(&self) -> ReadyState {
@ -357,9 +347,6 @@ impl<Window: WindowMethods> IOCompositor<Window> {
render_state,
|_key, value| *value = render_state);
self.window.set_render_state(render_state);
if render_state == IdleRenderState {
self.composite_ready = true;
}
}
fn all_pipelines_in_idle_render_state(&self) -> bool {
@ -417,6 +404,9 @@ impl<Window: WindowMethods> IOCompositor<Window> {
// Initialize the new constellation channel by sending it the root window size.
self.constellation_chan = new_constellation_chan;
self.send_window_size();
self.got_set_ids_message = true;
self.composite_if_necessary();
}
fn create_frame_tree_root_layers(&mut self,
@ -529,7 +519,6 @@ impl<Window: WindowMethods> IOCompositor<Window> {
}));
}
pub fn move_layer(&self,
pipeline_id: PipelineId,
layer_id: LayerId,
@ -555,10 +544,21 @@ impl<Window: WindowMethods> IOCompositor<Window> {
fail!("Compositor: Tried to scroll to fragment with unknown layer.");
}
self.recomposite = true;
self.start_scrolling_timer_if_necessary();
}
None => {}
};
}
}
fn start_scrolling_timer_if_necessary(&mut self) {
match self.composition_request {
CompositeNow | CompositeOnScrollTimeout(_) => return,
NoCompositingNecessary => {}
}
let timestamp = precise_time_ns();
self.scrolling_timer.scroll_event_processed(timestamp);
self.composition_request = CompositeOnScrollTimeout(timestamp);
}
fn set_layer_origin(&mut self,
@ -580,7 +580,9 @@ impl<Window: WindowMethods> IOCompositor<Window> {
layer_id: LayerId,
new_layer_buffer_set: Box<LayerBufferSet>,
epoch: Epoch) {
debug!("compositor received new frame");
debug!("compositor received new frame at size {}x{}",
self.window_size.width.get(),
self.window_size.height.get());
// From now on, if we destroy the buffers, they will leak.
let mut new_layer_buffer_set = new_layer_buffer_set;
@ -588,8 +590,10 @@ impl<Window: WindowMethods> IOCompositor<Window> {
match self.find_layer_with_pipeline_and_layer_id(pipeline_id, layer_id) {
Some(ref layer) => {
// FIXME(pcwalton): This is going to cause problems with inconsistent frames since
// we only composite one layer at a time.
assert!(layer.add_buffers(new_layer_buffer_set, epoch));
self.recomposite = true;
self.composite_if_necessary();
}
None => {
// FIXME: This may potentially be triggered by a race condition where a
@ -605,8 +609,9 @@ impl<Window: WindowMethods> IOCompositor<Window> {
layer_id: LayerId,
point: Point2D<f32>) {
if self.move_layer(pipeline_id, layer_id, Point2D::from_untyped(&point)) {
self.recomposite = true;
self.send_buffer_requests_for_all_layers();
if self.send_buffer_requests_for_all_layers() {
self.start_scrolling_timer_if_necessary();
}
} else {
self.fragment_point = Some(point);
}
@ -617,7 +622,7 @@ impl<Window: WindowMethods> IOCompositor<Window> {
IdleWindowEvent => {}
RefreshWindowEvent => {
self.recomposite = true;
self.composite_if_necessary()
}
ResizeWindowEvent(size) => {
@ -685,6 +690,7 @@ impl<Window: WindowMethods> IOCompositor<Window> {
debug!("osmain: window resized to {:?}", new_size);
self.window_size = new_size;
self.scene.set_root_layer_size(new_size.as_f32());
self.send_window_size();
}
@ -698,7 +704,8 @@ impl<Window: WindowMethods> IOCompositor<Window> {
layers"),
};
let msg = LoadUrlMsg(root_pipeline_id, LoadData::new(Url::parse(url_string.as_slice()).unwrap()));
let msg = LoadUrlMsg(root_pipeline_id,
LoadData::new(Url::parse(url_string.as_slice()).unwrap()));
let ConstellationChan(ref chan) = self.constellation_chan;
chan.send(msg);
}
@ -725,19 +732,30 @@ impl<Window: WindowMethods> IOCompositor<Window> {
fn on_scroll_window_event(&mut self,
delta: TypedPoint2D<DevicePixel, f32>,
cursor: TypedPoint2D<DevicePixel, i32>) {
let delta = delta / self.scene.scale;
let cursor = cursor.as_f32() / self.scene.scale;
self.pending_scroll_events.push(ScrollEvent {
delta: delta,
cursor: cursor,
});
let mut scroll = false;
match self.scene.root {
self.composite_if_necessary();
}
fn process_pending_scroll_events(&mut self) {
for scroll_event in mem::replace(&mut self.pending_scroll_events, Vec::new()).into_iter() {
let delta = scroll_event.delta / self.scene.scale;
let cursor = scroll_event.cursor.as_f32() / self.scene.scale;
let scrolled = match self.scene.root {
Some(ref mut layer) => {
scroll = layer.handle_scroll_event(delta, cursor) == ScrollPositionChanged;
layer.handle_scroll_event(delta, cursor) == ScrollPositionChanged
}
None => { }
}
self.recomposite_if(scroll);
None => false,
};
self.start_scrolling_timer_if_necessary();
self.send_buffer_requests_for_all_layers();
}
}
fn device_pixels_per_screen_px(&self) -> ScaleFactor<ScreenPx, DevicePixel, f32> {
match opts::get().device_pixels_per_px {
@ -768,6 +786,7 @@ impl<Window: WindowMethods> IOCompositor<Window> {
self.send_window_size();
}
// TODO(pcwalton): I think this should go through the same queuing as scroll events do.
fn on_pinch_zoom_window_event(&mut self, magnification: f32) {
self.zoom_action = true;
self.zoom_time = precise_time_s();
@ -792,7 +811,7 @@ impl<Window: WindowMethods> IOCompositor<Window> {
None => { }
}
self.recomposite = true;
self.composite_if_necessary();
}
fn on_navigation_window_event(&self, direction: WindowNavigateMsg) {
@ -840,9 +859,7 @@ impl<Window: WindowMethods> IOCompositor<Window> {
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 {
if unused_buffers.len() != 0 {
let message = UnusedBufferMsg(unused_buffers);
let _ = pipeline.render_chan.send_opt(message);
}
@ -851,7 +868,8 @@ impl<Window: WindowMethods> IOCompositor<Window> {
}
}
fn send_buffer_requests_for_all_layers(&mut self) {
/// Returns true if any buffer requests were sent or false otherwise.
fn send_buffer_requests_for_all_layers(&mut self) -> bool {
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()));
@ -860,7 +878,7 @@ impl<Window: WindowMethods> IOCompositor<Window> {
self.send_back_unused_buffers();
if layers_and_requests.len() == 0 {
return;
return false;
}
// We want to batch requests for each pipeline to avoid race conditions
@ -875,6 +893,7 @@ impl<Window: WindowMethods> IOCompositor<Window> {
}
self.add_outstanding_render_msg(num_render_msgs_sent);
true
}
fn is_ready_to_render_image_output(&self) -> bool {
@ -893,6 +912,11 @@ impl<Window: WindowMethods> IOCompositor<Window> {
if !self.all_pipelines_in_idle_render_state() {
return false;
}
if !self.got_set_ids_message {
return false;
}
return true;
}
@ -939,10 +963,11 @@ impl<Window: WindowMethods> IOCompositor<Window> {
});
if output_image {
let path = from_str::<Path>(opts::get().output_file.as_ref().unwrap().as_slice()).unwrap();
let path =
from_str::<Path>(opts::get().output_file.as_ref().unwrap().as_slice()).unwrap();
let mut pixels = gl::read_pixels(0, 0,
width as GLsizei,
height as GLsizei,
width as gl::GLsizei,
height as gl::GLsizei,
gl::RGB, gl::UNSIGNED_BYTE);
gl::bind_framebuffer(gl::FRAMEBUFFER, 0);
@ -976,18 +1001,26 @@ impl<Window: WindowMethods> IOCompositor<Window> {
self.shutdown_state = ShuttingDown;
}
// Perform the page flip. This will likely block for a while.
self.window.present();
self.last_composite_time = precise_time_ns();
let exit = opts::get().exit_after_load;
if exit {
debug!("shutting down the constellation for exit_after_load");
let ConstellationChan(ref chan) = self.constellation_chan;
chan.send(ExitMsg);
}
self.composition_request = NoCompositingNecessary;
self.process_pending_scroll_events();
}
fn recomposite_if(&mut self, result: bool) {
self.recomposite = result || self.recomposite;
fn composite_if_necessary(&mut self) {
if self.composition_request == NoCompositingNecessary {
self.composition_request = CompositeNow
}
}
fn find_topmost_layer_at_point_for_layer(&self,
@ -1054,3 +1087,87 @@ fn find_layer_with_pipeline_and_layer_id_for_layer(layer: Rc<Layer<CompositorDat
return None;
}
impl<Window> CompositorEventListener for IOCompositor<Window> where Window: WindowMethods {
fn handle_event(&mut self, msg: WindowEvent) -> bool {
// Check for new messages coming from the other tasks in the system.
loop {
match self.port.try_recv_compositor_msg() {
None => break,
Some(msg) => {
if !self.handle_browser_message(msg) {
break
}
}
}
}
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.");
return false;
}
// Handle the message coming from the windowing system.
self.handle_window_message(msg);
// 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();
}
match self.composition_request {
NoCompositingNecessary | CompositeOnScrollTimeout(_) => {}
CompositeNow => self.composite(),
}
self.shutdown_state != FinishedShuttingDown
}
/// Repaints and recomposites synchronously. You must be careful when calling this, as if a
/// paint is not scheduled the compositor will hang forever.
///
/// This is used when resizing the window.
fn repaint_synchronously(&mut self) {
while self.shutdown_state != ShuttingDown {
let msg = self.port.recv_compositor_msg();
let is_paint = match msg {
Paint(..) => true,
_ => false,
};
let keep_going = self.handle_browser_message(msg);
if is_paint {
self.composite();
break
}
if !keep_going {
break
}
}
}
fn shutdown(&mut self) {
// Clear out the compositor layers so that painting tasks can destroy the buffers.
match self.scene.root {
None => {}
Some(ref layer) => layer.forget_all_tiles(),
}
// Drain compositor port, sometimes messages contain channels that are blocking
// another task from finishing (i.e. SetIds)
while self.port.try_recv_compositor_msg().is_some() {}
// Tell the profiler, memory profiler, and scrolling timer 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);
self.scrolling_timer.shutdown();
}
}

View file

@ -2,12 +2,14 @@
* 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/. */
//! Communication with the compositor task.
pub use windowing;
pub use constellation::SendableFrameTree;
use compositor;
use headless;
pub use constellation::SendableFrameTree;
use windowing::WindowMethods;
use windowing::{WindowEvent, WindowMethods};
use azure::azure_hl::{SourceSurfaceMethods, Color};
use geom::point::Point2D;
@ -21,39 +23,64 @@ use servo_msg::constellation_msg::{ConstellationChan, PipelineId};
use servo_util::memory::MemoryProfilerChan;
use servo_util::time::TimeProfilerChan;
use std::comm::{channel, Sender, Receiver};
use std::fmt::{FormatError, Formatter, Show};
use std::rc::Rc;
use url::Url;
/// 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>,
/// Sends messages to the compositor. This is a trait supplied by the port because the method used
/// to communicate with the compositor may have to kick OS event loops awake, communicate cross-
/// process, and so forth.
pub trait CompositorProxy : 'static + Send {
/// Sends a message to the compositor.
fn send(&mut self, msg: Msg);
/// Clones the compositor proxy.
fn clone_compositor_proxy(&self) -> Box<CompositorProxy+'static+Send>;
}
/// The port that the compositor receives messages on. As above, this is a trait supplied by the
/// Servo port.
pub trait CompositorReceiver for Sized? : 'static {
/// Receives the next message inbound for the compositor. This must not block.
fn try_recv_compositor_msg(&mut self) -> Option<Msg>;
/// Synchronously waits for, and returns, the next message inbound for the compositor.
fn recv_compositor_msg(&mut self) -> Msg;
}
/// A convenience implementation of `CompositorReceiver` for a plain old Rust `Receiver`.
impl CompositorReceiver for Receiver<Msg> {
fn try_recv_compositor_msg(&mut self) -> Option<Msg> {
match self.try_recv() {
Ok(msg) => Some(msg),
Err(_) => None,
}
}
fn recv_compositor_msg(&mut self) -> Msg {
self.recv()
}
}
/// Implementation of the abstract `ScriptListener` interface.
impl ScriptListener for CompositorChan {
fn set_ready_state(&self, pipeline_id: PipelineId, ready_state: ReadyState) {
impl ScriptListener for Box<CompositorProxy+'static+Send> {
fn set_ready_state(&mut self, pipeline_id: PipelineId, ready_state: ReadyState) {
let msg = ChangeReadyState(pipeline_id, ready_state);
self.chan.send(msg);
self.send(msg);
}
fn scroll_fragment_point(&self,
fn scroll_fragment_point(&mut self,
pipeline_id: PipelineId,
layer_id: LayerId,
point: Point2D<f32>) {
self.chan.send(ScrollFragmentPoint(pipeline_id, layer_id, point));
self.send(ScrollFragmentPoint(pipeline_id, layer_id, point));
}
fn close(&self) {
fn close(&mut self) {
let (chan, port) = channel();
self.chan.send(Exit(chan));
self.send(Exit(chan));
port.recv();
}
fn dup(&self) -> Box<ScriptListener+'static> {
box self.clone() as Box<ScriptListener+'static>
fn dup(&mut self) -> Box<ScriptListener+'static> {
box self.clone_compositor_proxy() as Box<ScriptListener+'static>
}
}
@ -83,21 +110,21 @@ impl LayerProperties {
}
/// Implementation of the abstract `RenderListener` interface.
impl RenderListener for CompositorChan {
fn get_graphics_metadata(&self) -> Option<NativeGraphicsMetadata> {
impl RenderListener for Box<CompositorProxy+'static+Send> {
fn get_graphics_metadata(&mut self) -> Option<NativeGraphicsMetadata> {
let (chan, port) = channel();
self.chan.send(GetGraphicsMetadata(chan));
self.send(GetGraphicsMetadata(chan));
port.recv()
}
fn paint(&self,
fn paint(&mut self,
pipeline_id: PipelineId,
epoch: Epoch,
replies: Vec<(LayerId, Box<LayerBufferSet>)>) {
self.chan.send(Paint(pipeline_id, epoch, replies));
self.send(Paint(pipeline_id, epoch, replies));
}
fn initialize_layers_for_pipeline(&self,
fn initialize_layers_for_pipeline(&mut self,
pipeline_id: PipelineId,
metadata: Vec<LayerMetadata>,
epoch: Epoch) {
@ -108,36 +135,23 @@ impl RenderListener for CompositorChan {
for metadata in metadata.iter() {
let layer_properties = LayerProperties::new(pipeline_id, epoch, metadata);
if first {
self.chan.send(CreateOrUpdateRootLayer(layer_properties));
self.send(CreateOrUpdateRootLayer(layer_properties));
first = false
} else {
self.chan.send(CreateOrUpdateDescendantLayer(layer_properties));
self.send(CreateOrUpdateDescendantLayer(layer_properties));
}
}
}
fn render_msg_discarded(&self) {
self.chan.send(RenderMsgDiscarded);
fn render_msg_discarded(&mut self) {
self.send(RenderMsgDiscarded);
}
fn set_render_state(&self, pipeline_id: PipelineId, render_state: RenderState) {
self.chan.send(ChangeRenderState(pipeline_id, render_state))
fn set_render_state(&mut self, pipeline_id: PipelineId, render_state: RenderState) {
self.send(ChangeRenderState(pipeline_id, 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.
@ -177,6 +191,30 @@ pub enum Msg {
SetIds(SendableFrameTree, Sender<()>, ConstellationChan),
/// The load of a page for a given URL has completed.
LoadComplete(PipelineId, Url),
/// Indicates that the scrolling timeout with the given starting timestamp has happened and a
/// composite should happen. (See the `scrolling` module.)
ScrollTimeout(u64),
}
impl Show for Msg {
fn fmt(&self, f: &mut Formatter) -> Result<(),FormatError> {
match *self {
Exit(..) => write!(f, "Exit"),
ShutdownComplete(..) => write!(f, "ShutdownComplete"),
GetGraphicsMetadata(..) => write!(f, "GetGraphicsMetadata"),
CreateOrUpdateRootLayer(..) => write!(f, "CreateOrUpdateRootLayer"),
CreateOrUpdateDescendantLayer(..) => write!(f, "CreateOrUpdateDescendantLayer"),
SetLayerOrigin(..) => write!(f, "SetLayerOrigin"),
ScrollFragmentPoint(..) => write!(f, "ScrollFragmentPoint"),
Paint(..) => write!(f, "Paint"),
ChangeReadyState(..) => write!(f, "ChangeReadyState"),
ChangeRenderState(..) => write!(f, "ChangeRenderState"),
RenderMsgDiscarded(..) => write!(f, "RenderMsgDiscarded"),
SetIds(..) => write!(f, "SetIds"),
LoadComplete(..) => write!(f, "LoadComplete"),
ScrollTimeout(..) => write!(f, "ScrollTimeout"),
}
}
}
pub struct CompositorTask;
@ -196,27 +234,38 @@ impl CompositorTask {
NativeCompositingGraphicsContext::new()
}
pub fn create<Window: WindowMethods>(
window: Option<Rc<Window>>,
port: Receiver<Msg>,
pub fn create<Window>(window: Option<Rc<Window>>,
sender: Box<CompositorProxy+Send>,
receiver: Box<CompositorReceiver>,
constellation_chan: ConstellationChan,
time_profiler_chan: TimeProfilerChan,
memory_profiler_chan: MemoryProfilerChan) {
memory_profiler_chan: MemoryProfilerChan)
-> Box<CompositorEventListener + 'static>
where Window: WindowMethods + 'static {
match window {
Some(window) => {
compositor::IOCompositor::create(window,
port,
box compositor::IOCompositor::create(window,
sender,
receiver,
constellation_chan.clone(),
time_profiler_chan,
memory_profiler_chan)
as Box<CompositorEventListener>
}
None => {
headless::NullCompositor::create(port,
box headless::NullCompositor::create(receiver,
constellation_chan.clone(),
time_profiler_chan,
memory_profiler_chan)
}
};
as Box<CompositorEventListener>
}
}
}
}
pub trait CompositorEventListener {
fn handle_event(&mut self, event: WindowEvent) -> bool;
fn repaint_synchronously(&mut self);
fn shutdown(&mut self);
}

View file

@ -2,53 +2,79 @@
* 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, SetLayerOrigin, SetIds};
use pipeline::{Pipeline, CompositionPipeline};
use compositor_task::{CompositorProxy, LoadComplete, ShutdownComplete, SetLayerOrigin, SetIds};
use devtools_traits::DevtoolsControlChan;
use std::collections::hashmap::{HashMap, HashSet};
use geom::rect::{Rect, TypedRect};
use geom::scale_factor::ScaleFactor;
use gfx::font_cache_task::FontCacheTask;
use gfx::render_task;
use libc;
use pipeline::{Pipeline, CompositionPipeline};
use layers::geometry::DevicePixel;
use layout_traits::{LayoutControlChan, LayoutTaskFactory, ExitNowMsg};
use libc;
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::{LoadData, NavigationType, PipelineId, RendererReadyMsg, ResizedWindowMsg};
use servo_msg::constellation_msg::{SubpageId, WindowSizeData};
use servo_msg::constellation_msg::{LoadCompleteMsg, LoadIframeUrlMsg, LoadUrlMsg, Msg};
use servo_msg::constellation_msg::{LoadData, NavigateMsg, NavigationType, PipelineId};
use servo_msg::constellation_msg::{RendererReadyMsg, ResizedWindowMsg, 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::geometry::{PagePx, ViewportPx};
use servo_util::opts;
use servo_util::time::TimeProfilerChan;
use servo_util::task::spawn_named;
use servo_util::time::TimeProfilerChan;
use std::cell::RefCell;
use std::mem::replace;
use std::collections::hashmap::{HashMap, HashSet};
use std::io;
use std::mem::replace;
use std::rc::Rc;
use url::Url;
/// Maintains the pipelines and navigation context and grants permission to composite
/// Maintains the pipelines and navigation context and grants permission to composite.
pub struct Constellation<LTF, STF> {
/// A channel through which messages can be sent to this object.
pub chan: ConstellationChan,
/// Receives messages.
pub request_port: Receiver<Msg>,
pub compositor_chan: CompositorChan,
/// A channel (the implementation of which is port-specific) through which messages can be sent
/// to the compositor.
pub compositor_proxy: Box<CompositorProxy>,
/// A channel through which messages can be sent to the resource task.
pub resource_task: ResourceTask,
/// A channel through which messages can be sent to the image cache task.
pub image_cache_task: ImageCacheTask,
/// A channel through which messages can be sent to the developer tools.
devtools_chan: Option<DevtoolsControlChan>,
/// A list of all the pipelines. (See the `pipeline` module for more details.)
pipelines: HashMap<PipelineId, Rc<Pipeline>>,
/// A channel through which messages can be sent to the font cache.
font_cache_task: FontCacheTask,
navigation_context: NavigationContext,
/// The next free ID to assign to a pipeline.
next_pipeline_id: PipelineId,
pending_frames: Vec<FrameChange>,
pending_sizes: HashMap<(PipelineId, SubpageId), TypedRect<PagePx, f32>>,
/// A channel through which messages can be sent to the time profiler.
pub time_profiler_chan: TimeProfilerChan,
pub window_size: WindowSizeData,
}
@ -86,7 +112,11 @@ 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(),
children: self.children
.borrow()
.iter()
.map(|frame_tree| frame_tree.to_sendable())
.collect(),
};
sendable_frame_tree
}
@ -109,8 +139,8 @@ impl FrameTreeTraversal for 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.
/// 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();
@ -239,7 +269,7 @@ impl NavigationContext {
}
impl<LTF: LayoutTaskFactory, STF: ScriptTaskFactory> Constellation<LTF, STF> {
pub fn start(compositor_chan: CompositorChan,
pub fn start(compositor_proxy: Box<CompositorProxy+Send>,
resource_task: ResourceTask,
image_cache_task: ImageCacheTask,
font_cache_task: FontCacheTask,
@ -252,7 +282,7 @@ impl<LTF: LayoutTaskFactory, STF: ScriptTaskFactory> Constellation<LTF, STF> {
let mut constellation: Constellation<LTF, STF> = Constellation {
chan: constellation_chan_clone,
request_port: constellation_port,
compositor_chan: compositor_chan,
compositor_proxy: compositor_proxy,
devtools_chan: devtools_chan,
resource_task: resource_task,
image_cache_task: image_cache_task,
@ -293,7 +323,7 @@ impl<LTF: LayoutTaskFactory, STF: ScriptTaskFactory> Constellation<LTF, STF> {
let pipe = Pipeline::create::<LTF, STF>(id,
subpage_id,
self.chan.clone(),
self.compositor_chan.clone(),
self.compositor_proxy.clone_compositor_proxy(),
self.devtools_chan.clone(),
self.image_cache_task.clone(),
self.font_cache_task.clone(),
@ -367,7 +397,7 @@ impl<LTF: LayoutTaskFactory, STF: ScriptTaskFactory> Constellation<LTF, STF> {
// 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));
self.compositor_proxy.send(LoadComplete(pipeline_id, url));
}
// Handle a forward or back request
NavigateMsg(direction) => {
@ -387,14 +417,14 @@ impl<LTF: LayoutTaskFactory, STF: ScriptTaskFactory> Constellation<LTF, STF> {
true
}
fn handle_exit(&self) {
fn handle_exit(&mut 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);
self.compositor_proxy.send(ShutdownComplete);
}
fn handle_failure_msg(&mut self, pipeline_id: PipelineId, subpage_id: Option<SubpageId>) {
@ -491,10 +521,56 @@ impl<LTF: LayoutTaskFactory, STF: ScriptTaskFactory> Constellation<LTF, STF> {
let frames = self.find_all(pipeline_id);
{
// If the subframe is in the current frame tree, the compositor needs the new size
for current_frame in self.navigation_context.current.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();
match children.iter_mut().find(|child| subpage_eq(child)) {
None => {}
Some(child) => {
update_child_rect(child,
rect,
true,
&mut already_sent,
&mut self.compositor_proxy,
self.window_size.device_pixel_ratio)
}
}
}
}
// 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.iter_mut().find(|child| subpage_eq(child));
found_child.map(|child| {
update_child_rect(child,
rect,
false,
&mut already_sent,
&mut self.compositor_proxy,
self.window_size.device_pixel_ratio)
});
}
}
// 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);
}
// 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| {
fn update_child_rect(child_frame_tree: &mut ChildFrameTree,
rect: TypedRect<PagePx,f32>,
is_active: bool,
already_sent: &mut HashSet<PipelineId>,
compositor_proxy: &mut Box<CompositorProxy>,
device_pixel_ratio: ScaleFactor<ViewportPx,DevicePixel,f32>) {
child_frame_tree.rect = Some(rect);
// NOTE: work around borrowchk issues
let pipeline = &child_frame_tree.frame_tree.pipeline;
@ -504,42 +580,18 @@ impl<LTF: LayoutTaskFactory, STF: ScriptTaskFactory> Constellation<LTF, STF> {
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,
device_pixel_ratio: device_pixel_ratio,
}));
self.compositor_chan.send(SetLayerOrigin(pipeline.id,
compositor_proxy.send(SetLayerOrigin(pipeline.id,
LayerId::null(),
rect.to_untyped().origin));
} 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.iter_mut().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.iter_mut().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,
@ -851,10 +903,10 @@ impl<LTF: LayoutTaskFactory, STF: ScriptTaskFactory> Constellation<LTF, STF> {
}
}
fn set_ids(&self, frame_tree: &Rc<FrameTree>) {
fn set_ids(&mut 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()));
self.compositor_proxy.send(SetIds(frame_tree.to_sendable(), chan, self.chan.clone()));
match port.recv_opt() {
Ok(()) => {
let mut iter = frame_tree.iter();

View file

@ -2,10 +2,11 @@
* 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::{SetLayerOrigin, Paint, ScrollFragmentPoint, LoadComplete};
use compositor_task::{ShutdownComplete, ChangeRenderState, RenderMsgDiscarded};
use compositor_task::{Exit, ChangeReadyState, LoadComplete, Paint, ScrollFragmentPoint, SetIds};
use compositor_task::{SetLayerOrigin, ShutdownComplete, ChangeRenderState, RenderMsgDiscarded};
use compositor_task::{CompositorEventListener, CompositorReceiver, ScrollTimeout};
use windowing::WindowEvent;
use geom::scale_factor::ScaleFactor;
use geom::size::TypedSize2D;
@ -21,59 +22,66 @@ use servo_util::time;
/// It's intended for headless testing.
pub struct NullCompositor {
/// The port on which we receive messages.
pub port: Receiver<Msg>,
pub port: Box<CompositorReceiver>,
/// A channel to the constellation.
constellation_chan: ConstellationChan,
/// A channel to the time profiler.
time_profiler_chan: TimeProfilerChan,
/// A channel to the memory profiler.
memory_profiler_chan: MemoryProfilerChan,
}
impl NullCompositor {
fn new(port: Receiver<Msg>) -> NullCompositor {
fn new(port: Box<CompositorReceiver>,
constellation_chan: ConstellationChan,
time_profiler_chan: TimeProfilerChan,
memory_profiler_chan: MemoryProfilerChan)
-> NullCompositor {
NullCompositor {
port: port,
constellation_chan: constellation_chan,
time_profiler_chan: time_profiler_chan,
memory_profiler_chan: memory_profiler_chan,
}
}
pub fn create(port: Receiver<Msg>,
pub fn create(port: Box<CompositorReceiver>,
constellation_chan: ConstellationChan,
time_profiler_chan: TimeProfilerChan,
memory_profiler_chan: MemoryProfilerChan) {
let compositor = NullCompositor::new(port);
memory_profiler_chan: MemoryProfilerChan)
-> NullCompositor {
let compositor = NullCompositor::new(port,
constellation_chan,
time_profiler_chan,
memory_profiler_chan);
// Tell the constellation about the initial fake size.
{
let ConstellationChan(ref chan) = constellation_chan;
let ConstellationChan(ref chan) = compositor.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(_) => {},
compositor
}
}
time_profiler_chan.send(time::ExitMsg);
memory_profiler_chan.send(memory::ExitMsg);
}
fn handle_message(&self, constellation_chan: ConstellationChan) {
loop {
match self.port.recv() {
impl CompositorEventListener for NullCompositor {
fn handle_event(&mut self, _: WindowEvent) -> bool {
match self.port.recv_compositor_msg() {
Exit(chan) => {
debug!("shutting down the constellation");
let ConstellationChan(ref con_chan) = constellation_chan;
let ConstellationChan(ref con_chan) = self.constellation_chan;
con_chan.send(ExitMsg);
chan.send(());
}
ShutdownComplete => {
debug!("constellation completed shutdown");
break
return false
}
GetGraphicsMetadata(chan) => {
@ -92,8 +100,19 @@ impl NullCompositor {
CreateOrUpdateDescendantLayer(..) |
SetLayerOrigin(..) | Paint(..) |
ChangeReadyState(..) | ChangeRenderState(..) | ScrollFragmentPoint(..) |
LoadComplete(..) | RenderMsgDiscarded(..) => ()
}
}
LoadComplete(..) | RenderMsgDiscarded(..) | ScrollTimeout(..) => ()
}
true
}
fn repaint_synchronously(&mut self) {}
fn shutdown(&mut self) {
// Drain compositor port, sometimes messages contain channels that are blocking
// another task from finishing (i.e. SetIds)
while self.port.try_recv_compositor_msg().is_some() {}
self.time_profiler_chan.send(time::ExitMsg);
self.memory_profiler_chan.send(memory::ExitMsg);
}
}

View file

@ -30,6 +30,7 @@ extern crate "util" as servo_util;
extern crate gleam;
extern crate libc;
extern crate native;
extern crate time;
extern crate url;
@ -38,12 +39,14 @@ extern crate core_graphics;
#[cfg(target_os="macos")]
extern crate core_text;
pub use compositor_task::{CompositorChan, CompositorTask};
pub use compositor_task::{CompositorEventListener, CompositorProxy, CompositorTask};
pub use constellation::Constellation;
pub mod compositor_task;
mod compositor_layer;
mod scrolling;
mod compositor;
mod headless;

View file

@ -2,7 +2,7 @@
* 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 CompositorProxy;
use layout_traits::{LayoutTaskFactory, LayoutControlChan};
use script_traits::{ScriptControlChan, ScriptTaskFactory};
use script_traits::{AttachLayoutMsg, LoadMsg, NewLayoutInfo, ExitPipelineMsg};
@ -47,7 +47,7 @@ impl Pipeline {
id: PipelineId,
subpage_id: Option<SubpageId>,
constellation_chan: ConstellationChan,
compositor_chan: CompositorChan,
compositor_proxy: Box<CompositorProxy+'static+Send>,
devtools_chan: Option<DevtoolsControlChan>,
image_cache_task: ImageCacheTask,
font_cache_task: FontCacheTask,
@ -73,7 +73,7 @@ impl Pipeline {
let (script_chan, script_port) = channel();
ScriptTaskFactory::create(None::<&mut STF>,
id,
box compositor_chan.clone(),
compositor_proxy.clone_compositor_proxy(),
&layout_pair,
ScriptControlChan(script_chan.clone()),
script_port,
@ -101,7 +101,7 @@ impl Pipeline {
RenderTask::create(id,
render_port,
compositor_chan.clone(),
compositor_proxy,
constellation_chan.clone(),
font_cache_task.clone(),
failure.clone(),

View file

@ -0,0 +1,73 @@
/* 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 timer thread that gives the painting task a little time to catch up when the user scrolls.
use compositor_task::{CompositorProxy, ScrollTimeout};
use native::task::NativeTaskBuilder;
use std::io::timer;
use std::task::TaskBuilder;
use std::time::duration::Duration;
use time;
/// The amount of time in nanoseconds that we give to the painting thread to paint new tiles upon
/// processing a scroll event that caused new tiles to be revealed. When this expires, we give up
/// and composite anyway (showing a "checkerboard") to avoid dropping the frame.
static TIMEOUT: i64 = 12_000_000;
pub struct ScrollingTimerProxy {
sender: Sender<ToScrollingTimerMsg>,
}
pub struct ScrollingTimer {
compositor_proxy: Box<CompositorProxy>,
receiver: Receiver<ToScrollingTimerMsg>,
}
enum ToScrollingTimerMsg {
ExitMsg,
ScrollEventProcessedMsg(u64),
}
impl ScrollingTimerProxy {
pub fn new(compositor_proxy: Box<CompositorProxy+Send>) -> ScrollingTimerProxy {
let (to_scrolling_timer_sender, to_scrolling_timer_receiver) = channel();
TaskBuilder::new().native().spawn(proc() {
let mut scrolling_timer = ScrollingTimer {
compositor_proxy: compositor_proxy,
receiver: to_scrolling_timer_receiver,
};
scrolling_timer.run();
});
ScrollingTimerProxy {
sender: to_scrolling_timer_sender,
}
}
pub fn scroll_event_processed(&mut self, timestamp: u64) {
self.sender.send(ScrollEventProcessedMsg(timestamp))
}
pub fn shutdown(&mut self) {
self.sender.send(ExitMsg);
}
}
impl ScrollingTimer {
pub fn run(&mut self) {
loop {
match self.receiver.recv_opt() {
Ok(ScrollEventProcessedMsg(timestamp)) => {
let target = timestamp as i64 + TIMEOUT;
let delta = target - (time::precise_time_ns() as i64);
timer::sleep(Duration::nanoseconds(delta));
self.compositor_proxy.send(ScrollTimeout(timestamp));
}
Ok(ExitMsg) | Err(_) => break,
}
}
}
}

View file

@ -4,6 +4,8 @@
//! Abstract windowing methods. The concrete implementations of these can be found in `platform/`.
use compositor_task::{CompositorProxy, CompositorReceiver};
use geom::point::TypedPoint2D;
use geom::scale_factor::ScaleFactor;
use geom::size::TypedSize2D;
@ -11,6 +13,8 @@ use layers::geometry::DevicePixel;
use layers::platform::surface::NativeGraphicsMetadata;
use servo_msg::compositor_msg::{ReadyState, RenderState};
use servo_util::geometry::ScreenPx;
use std::fmt::{FormatError, Formatter, Show};
use std::rc::Rc;
pub enum MouseWindowEvent {
MouseWindowClickEvent(uint, TypedPoint2D<DevicePixel, f32>),
@ -25,10 +29,12 @@ pub enum WindowNavigateMsg {
/// Events that the windowing system sends to Servo.
pub enum WindowEvent {
/// Sent when no message has arrived.
/// Sent when no message has arrived, but the event loop was kicked for some reason (perhaps
/// by another Servo subsystem).
///
/// FIXME: This is a bogus event and is only used because we don't have the new
/// scheduler integrated with the platform event loop.
/// FIXME(pcwalton): This is kind of ugly and may not work well with multiprocess Servo.
/// It's possible that this should be something like
/// `CompositorMessageWindowEvent(compositor_task::Msg)` instead.
IdleWindowEvent,
/// Sent when part of the window is marked dirty and needs to be redrawn.
RefreshWindowEvent,
@ -54,6 +60,25 @@ pub enum WindowEvent {
QuitWindowEvent,
}
impl Show for WindowEvent {
fn fmt(&self, f: &mut Formatter) -> Result<(),FormatError> {
match *self {
IdleWindowEvent => write!(f, "Idle"),
RefreshWindowEvent => write!(f, "Refresh"),
ResizeWindowEvent(..) => write!(f, "Resize"),
LoadUrlWindowEvent(..) => write!(f, "LoadUrl"),
MouseWindowEventClass(..) => write!(f, "Mouse"),
MouseWindowMoveEventClass(..) => write!(f, "MouseMove"),
ScrollWindowEvent(..) => write!(f, "Scroll"),
ZoomWindowEvent(..) => write!(f, "Zoom"),
PinchZoomWindowEvent(..) => write!(f, "PinchZoom"),
NavigationWindowEvent(..) => write!(f, "Navigation"),
FinishedWindowEvent => write!(f, "Finished"),
QuitWindowEvent => write!(f, "Quit"),
}
}
}
pub trait WindowMethods {
/// Returns the size of the window in hardware pixels.
fn framebuffer_size(&self) -> TypedSize2D<DevicePixel, uint>;
@ -62,9 +87,6 @@ pub trait WindowMethods {
/// 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.
@ -75,5 +97,13 @@ pub trait WindowMethods {
/// Gets the OS native graphics information for this window.
fn native_metadata(&self) -> NativeGraphicsMetadata;
/// Creates a channel to the compositor. The dummy parameter is needed because we don't have
/// UFCS in Rust yet.
///
/// This is part of the windowing system because its implementation often involves OS-specific
/// magic to wake the up window's event loop.
fn create_compositor_channel(_: &Option<Rc<Self>>)
-> (Box<CompositorProxy+Send>, Box<CompositorReceiver>);
}

View file

@ -19,6 +19,7 @@ use render_context::RenderContext;
use text::glyph::CharIndex;
use text::TextRun;
use azure::azure::AzFloat;
use collections::Deque;
use collections::dlist::{mod, DList};
use geom::{Point2D, Rect, SideOffsets2D, Size2D, Matrix2D};
@ -26,18 +27,11 @@ use libc::uintptr_t;
use servo_net::image::base::Image;
use servo_util::dlist as servo_dlist;
use servo_util::geometry::Au;
use servo_util::opts;
use servo_util::range::Range;
use std::fmt;
use std::slice::Items;
use style::computed_values::border_style;
use sync::Arc;
use std::num::Zero;
use std::ptr;
use azure::AzFloat;
use azure::scaled_font::ScaledFont;
use azure::azure_hl::ColorPattern;
pub mod optimizer;
@ -58,88 +52,6 @@ impl OpaqueNode {
}
}
trait ScaledFontExtensionMethods {
fn draw_text_into_context(&self,
rctx: &RenderContext,
run: &Box<TextRun>,
range: &Range<CharIndex>,
baseline_origin: Point2D<Au>,
color: Color,
antialias: bool);
}
impl ScaledFontExtensionMethods for ScaledFont {
fn draw_text_into_context(&self,
rctx: &RenderContext,
run: &Box<TextRun>,
range: &Range<CharIndex>,
baseline_origin: Point2D<Au>,
color: Color,
antialias: bool) {
use libc::types::common::c99::uint32_t;
use azure::{struct__AzDrawOptions,
struct__AzGlyph,
struct__AzGlyphBuffer,
struct__AzPoint};
use azure::azure::{AzDrawTargetFillGlyphs};
let target = rctx.get_draw_target();
let pattern = ColorPattern::new(color);
let azure_pattern = pattern.azure_color_pattern;
assert!(azure_pattern.is_not_null());
let fields = if antialias {
0x0200
} else {
0
};
let mut options = struct__AzDrawOptions {
mAlpha: 1f64 as AzFloat,
fields: fields,
};
let mut origin = baseline_origin.clone();
let mut azglyphs = vec!();
azglyphs.reserve(range.length().to_uint());
for (glyphs, _offset, slice_range) in run.iter_slices_for_range(range) {
for (_i, glyph) in glyphs.iter_glyphs_for_char_range(&slice_range) {
let glyph_advance = glyph.advance();
let glyph_offset = glyph.offset().unwrap_or(Zero::zero());
let azglyph = struct__AzGlyph {
mIndex: glyph.id() as uint32_t,
mPosition: struct__AzPoint {
x: (origin.x + glyph_offset.x).to_subpx() as AzFloat,
y: (origin.y + glyph_offset.y).to_subpx() as AzFloat
}
};
origin = Point2D(origin.x + glyph_advance, origin.y);
azglyphs.push(azglyph)
};
}
let azglyph_buf_len = azglyphs.len();
if azglyph_buf_len == 0 { return; } // Otherwise the Quartz backend will assert.
let mut glyphbuf = struct__AzGlyphBuffer {
mGlyphs: azglyphs.as_mut_ptr(),
mNumGlyphs: azglyph_buf_len as uint32_t
};
unsafe {
// TODO(Issue #64): this call needs to move into azure_hl.rs
AzDrawTargetFillGlyphs(target.azure_draw_target,
self.get_ref(),
&mut glyphbuf,
azure_pattern,
&mut options,
ptr::null_mut());
}
}
}
/// "Steps" as defined by CSS 2.1 § E.2.
#[deriving(Clone, PartialEq, Show)]
pub enum StackingLevel {
@ -543,55 +455,7 @@ impl DisplayItem {
TextDisplayItemClass(ref text) => {
debug!("Drawing text at {}.", text.base.bounds);
// Optimization: Dont set a transform matrix for upright text,
// and pass a strart point to `draw_text_into_context`.
// For sideways text, its easier to do the rotation such that its center
// (the baselines start point) is at (0, 0) coordinates.
let baseline_origin = match text.orientation {
Upright => text.baseline_origin,
SidewaysLeft => {
let x = text.baseline_origin.x.to_nearest_px() as AzFloat;
let y = text.baseline_origin.y.to_nearest_px() as AzFloat;
render_context.draw_target.set_transform(&current_transform.mul(
&Matrix2D::new(
0., -1.,
1., 0.,
x, y
)
));
Zero::zero()
},
SidewaysRight => {
let x = text.baseline_origin.x.to_nearest_px() as AzFloat;
let y = text.baseline_origin.y.to_nearest_px() as AzFloat;
render_context.draw_target.set_transform(&current_transform.mul(
&Matrix2D::new(
0., 1.,
-1., 0.,
x, y
)
));
Zero::zero()
}
};
render_context.font_ctx.get_render_font_from_template(
&text.text_run.font_template,
text.text_run.actual_pt_size
).borrow().draw_text_into_context(
render_context,
&*text.text_run,
&text.range,
baseline_origin,
text.text_color,
opts::get().enable_text_antialiasing
);
// Undo the transform, only when we did one.
if text.orientation != Upright {
render_context.draw_target.set_transform(current_transform)
}
render_context.draw_text(&**text, current_transform);
}
ImageDisplayItemClass(ref image_item) => {

View file

@ -30,6 +30,7 @@ extern crate "util" as servo_util;
extern crate "msg" as servo_msg;
extern crate style;
extern crate sync;
extern crate time;
extern crate url;
// Eventually we would like the shaper to be pluggable, as many operating systems have their own

View file

@ -2,23 +2,32 @@
* 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 display_list::{SidewaysLeft, SidewaysRight, TextDisplayItem, Upright};
use font_context::FontContext;
use style::computed_values::border_style;
use azure::azure_hl::{B8G8R8A8, A8, Color, ColorPattern, ColorPatternRef, DrawOptions};
use azure::azure_hl::{DrawSurfaceOptions,DrawTarget, Linear, SourceOp, StrokeOptions};
use azure::AZ_CAP_BUTT;
use azure::AzFloat;
use azure::scaled_font::ScaledFont;
use azure::{AZ_CAP_BUTT, AzDrawTargetFillGlyphs, AzFloat, struct__AzDrawOptions, struct__AzGlyph};
use azure::{struct__AzGlyphBuffer, struct__AzPoint};
use geom::matrix2d::Matrix2D;
use geom::point::Point2D;
use geom::rect::Rect;
use geom::size::Size2D;
use geom::side_offsets::SideOffsets2D;
use libc::types::common::c99::uint16_t;
use libc::types::common::c99::{uint16_t, uint32_t};
use libc::size_t;
use png::{RGB8, RGBA8, K8, KA8};
use servo_net::image::base::Image;
use servo_util::geometry::Au;
use servo_util::opts;
use servo_util::range::Range;
use std::num::Zero;
use std::ptr;
use sync::Arc;
use text::glyph::CharIndex;
use text::TextRun;
pub struct RenderContext<'a> {
pub draw_target: &'a DrawTarget,
@ -390,6 +399,50 @@ impl<'a> RenderContext<'a> {
self.draw_border_path(original_bounds, direction, border, scaled_color);
}
pub fn draw_text(&mut self,
text: &TextDisplayItem,
current_transform: &Matrix2D<AzFloat>) {
// Optimization: Dont set a transform matrix for upright text, and pass a start point to
// `draw_text_into_context`.
//
// For sideways text, its easier to do the rotation such that its center (the baselines
// start point) is at (0, 0) coordinates.
let baseline_origin = match text.orientation {
Upright => text.baseline_origin,
SidewaysLeft => {
let x = text.baseline_origin.x.to_subpx() as AzFloat;
let y = text.baseline_origin.y.to_subpx() as AzFloat;
self.draw_target.set_transform(&current_transform.mul(&Matrix2D::new(0., -1.,
1., 0.,
x, y)));
Zero::zero()
}
SidewaysRight => {
let x = text.baseline_origin.x.to_subpx() as AzFloat;
let y = text.baseline_origin.y.to_subpx() as AzFloat;
self.draw_target.set_transform(&current_transform.mul(&Matrix2D::new(0., 1.,
-1., 0.,
x, y)));
Zero::zero()
}
};
self.font_ctx
.get_render_font_from_template(&text.text_run.font_template,
text.text_run.actual_pt_size)
.borrow()
.draw_text_into_context(self,
&*text.text_run,
&text.range,
baseline_origin,
text.text_color,
opts::get().enable_text_antialiasing);
// Undo the transform, only when we did one.
if text.orientation != Upright {
self.draw_target.set_transform(current_transform)
}
}
}
trait ToAzureRect {
@ -417,3 +470,78 @@ impl ToSideOffsetsPx for SideOffsets2D<Au> {
self.left.to_nearest_px() as AzFloat)
}
}
trait ScaledFontExtensionMethods {
fn draw_text_into_context(&self,
rctx: &RenderContext,
run: &Box<TextRun>,
range: &Range<CharIndex>,
baseline_origin: Point2D<Au>,
color: Color,
antialias: bool);
}
impl ScaledFontExtensionMethods for ScaledFont {
fn draw_text_into_context(&self,
rctx: &RenderContext,
run: &Box<TextRun>,
range: &Range<CharIndex>,
baseline_origin: Point2D<Au>,
color: Color,
antialias: bool) {
let target = rctx.get_draw_target();
let pattern = ColorPattern::new(color);
let azure_pattern = pattern.azure_color_pattern;
assert!(azure_pattern.is_not_null());
let fields = if antialias {
0x0200
} else {
0
};
let mut options = struct__AzDrawOptions {
mAlpha: 1f64 as AzFloat,
fields: fields,
};
let mut origin = baseline_origin.clone();
let mut azglyphs = vec!();
azglyphs.reserve(range.length().to_uint());
for (glyphs, _offset, slice_range) in run.iter_slices_for_range(range) {
for (_i, glyph) in glyphs.iter_glyphs_for_char_range(&slice_range) {
let glyph_advance = glyph.advance();
let glyph_offset = glyph.offset().unwrap_or(Zero::zero());
let azglyph = struct__AzGlyph {
mIndex: glyph.id() as uint32_t,
mPosition: struct__AzPoint {
x: (origin.x + glyph_offset.x).to_subpx() as AzFloat,
y: (origin.y + glyph_offset.y).to_subpx() as AzFloat
}
};
origin = Point2D(origin.x + glyph_advance, origin.y);
azglyphs.push(azglyph)
};
}
let azglyph_buf_len = azglyphs.len();
if azglyph_buf_len == 0 { return; } // Otherwise the Quartz backend will assert.
let mut glyphbuf = struct__AzGlyphBuffer {
mGlyphs: azglyphs.as_mut_ptr(),
mNumGlyphs: azglyph_buf_len as uint32_t
};
unsafe {
// TODO(Issue #64): this call needs to move into azure_hl.rs
AzDrawTargetFillGlyphs(target.azure_draw_target,
self.get_ref(),
&mut glyphbuf,
azure_pattern,
&mut options,
ptr::null_mut());
}
}
}

View file

@ -7,6 +7,7 @@
use buffer_map::BufferMap;
use display_list::optimizer::DisplayListOptimizer;
use display_list::DisplayList;
use font_cache_task::FontCacheTask;
use font_context::FontContext;
use render_context::RenderContext;
@ -37,7 +38,6 @@ use std::comm::{Receiver, Sender, channel};
use std::mem;
use std::task::TaskBuilder;
use sync::Arc;
use font_cache_task::FontCacheTask;
/// Information about a layer that layout sends to the painting task.
#[deriving(Clone)]
@ -153,7 +153,10 @@ impl<C> RenderTask<C> where C: RenderListener + Send {
shutdown_chan: Sender<()>) {
let ConstellationChan(c) = constellation_chan.clone();
spawn_named_with_send_on_failure("RenderTask", task_state::Render, proc() {
{ // Ensures RenderTask and graphics context are destroyed before shutdown msg
{
// Ensures that the render task and graphics context are destroyed before the
// shutdown message.
let mut compositor = compositor;
let native_graphics_context = compositor.get_graphics_metadata().map(
|md| NativePaintingGraphicsContext::from_metadata(&md));
let worker_threads = WorkerThreadProxy::spawn(compositor.get_graphics_metadata(),

View file

@ -84,36 +84,36 @@ pub struct LayerMetadata {
/// The interface used by the renderer to acquire draw targets for each render frame and
/// submit them to be drawn to the display.
pub trait RenderListener {
fn get_graphics_metadata(&self) -> Option<NativeGraphicsMetadata>;
pub trait RenderListener for Sized? {
fn get_graphics_metadata(&mut self) -> Option<NativeGraphicsMetadata>;
/// Informs the compositor of the layers for the given pipeline. The compositor responds by
/// creating and/or destroying render layers as necessary.
fn initialize_layers_for_pipeline(&self,
fn initialize_layers_for_pipeline(&mut self,
pipeline_id: PipelineId,
metadata: Vec<LayerMetadata>,
epoch: Epoch);
/// Sends new tiles for the given layer to the compositor.
fn paint(&self,
fn paint(&mut self,
pipeline_id: PipelineId,
epoch: Epoch,
replies: Vec<(LayerId, Box<LayerBufferSet>)>);
fn render_msg_discarded(&self);
fn set_render_state(&self, PipelineId, RenderState);
fn render_msg_discarded(&mut self);
fn set_render_state(&mut self, PipelineId, RenderState);
}
/// The interface used by the script task to tell the compositor to update its ready state,
/// which is used in displaying the appropriate message in the window's title.
pub trait ScriptListener : Clone {
fn set_ready_state(&self, PipelineId, ReadyState);
fn scroll_fragment_point(&self,
pub trait ScriptListener {
fn set_ready_state(&mut self, PipelineId, ReadyState);
fn scroll_fragment_point(&mut self,
pipeline_id: PipelineId,
layer_id: LayerId,
point: Point2D<f32>);
fn close(&self);
fn dup(&self) -> Box<ScriptListener+'static>;
fn close(&mut self);
fn dup(&mut self) -> Box<ScriptListener+'static>;
}
impl<E, S: Encoder<E>> Encodable<S, E> for Box<ScriptListener+'static> {

View file

@ -2,8 +2,8 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
//! The high-level interface from script to constellation. Using this abstract interface helps reduce
//! coupling between these two components
//! The high-level interface from script to constellation. Using this abstract interface helps
//! reduce coupling between these two components.
use geom::rect::Rect;
use geom::size::TypedSize2D;

View file

@ -2,7 +2,7 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use dom::bindings::cell::{DOMRefCell, Ref};
use dom::bindings::cell::{DOMRefCell, Ref, RefMut};
use dom::bindings::codegen::Bindings::EventHandlerBinding::{OnErrorEventHandlerNonNull, EventHandlerNonNull};
use dom::bindings::codegen::Bindings::WindowBinding;
use dom::bindings::codegen::Bindings::WindowBinding::WindowMethods;
@ -53,7 +53,7 @@ pub struct Window {
location: MutNullableJS<Location>,
navigator: MutNullableJS<Navigator>,
image_cache_task: ImageCacheTask,
compositor: Box<ScriptListener+'static>,
compositor: DOMRefCell<Box<ScriptListener+'static>>,
browser_context: DOMRefCell<Option<BrowserContext>>,
page: Rc<Page>,
performance: MutNullableJS<Performance>,
@ -81,8 +81,8 @@ impl Window {
&self.image_cache_task
}
pub fn compositor<'a>(&'a self) -> &'a ScriptListener+'static {
&*self.compositor
pub fn compositor(&self) -> RefMut<Box<ScriptListener+'static>> {
self.compositor.borrow_mut()
}
pub fn browser_context(&self) -> Ref<Option<BrowserContext>> {
@ -398,7 +398,7 @@ impl Window {
script_chan: script_chan,
control_chan: control_chan,
console: Default::default(),
compositor: compositor,
compositor: DOMRefCell::new(compositor),
page: page,
location: Default::default(),
navigator: Default::default(),

View file

@ -182,7 +182,7 @@ impl Page {
if force_reflow {
let frame = self.frame();
let window = frame.as_ref().unwrap().window.root();
self.reflow(reflow_goal, window.control_chan().clone(), window.compositor(), query);
self.reflow(reflow_goal, window.control_chan().clone(), &mut **window.compositor(), query);
} else {
self.avoided_reflows.set(self.avoided_reflows.get() + 1);
}
@ -328,9 +328,8 @@ impl Page {
pub fn reflow(&self,
goal: ReflowGoal,
script_chan: ScriptControlChan,
compositor: &ScriptListener,
compositor: &mut ScriptListener,
query_type: ReflowQueryType) {
let root = match *self.frame() {
None => return,
Some(ref frame) => {

View file

@ -168,7 +168,7 @@ pub struct ScriptTask {
/// For communicating load url messages to the constellation
constellation_chan: ConstellationChan,
/// A handle to the compositor for communicating ready state messages.
compositor: Box<ScriptListener+'static>,
compositor: DOMRefCell<Box<ScriptListener+'static>>,
/// For providing instructions to an optional devtools server.
devtools_chan: Option<DevtoolsControlChan>,
@ -248,10 +248,9 @@ impl ScriptTaskFactory for ScriptTask {
box pair.sender() as Box<Any+Send>
}
fn create<C:ScriptListener + Send + 'static>(
_phantom: Option<&mut ScriptTask>,
fn create<C>(_phantom: Option<&mut ScriptTask>,
id: PipelineId,
compositor: Box<C>,
compositor: C,
layout_chan: &OpaqueScriptLayoutChannel,
control_chan: ScriptControlChan,
control_port: Receiver<ConstellationControlMsg>,
@ -260,13 +259,14 @@ impl ScriptTaskFactory for ScriptTask {
resource_task: ResourceTask,
image_cache_task: ImageCacheTask,
devtools_chan: Option<DevtoolsControlChan>,
window_size: WindowSizeData) {
window_size: WindowSizeData)
where C: ScriptListener + Send + 'static {
let ConstellationChan(const_chan) = constellation_chan.clone();
let (script_chan, script_port) = channel();
let layout_chan = LayoutChan(layout_chan.sender());
spawn_named_with_send_on_failure("ScriptTask", task_state::Script, proc() {
let script_task = ScriptTask::new(id,
compositor as Box<ScriptListener>,
box compositor as Box<ScriptListener>,
layout_chan,
script_port,
ScriptChan(script_chan),
@ -350,7 +350,7 @@ impl ScriptTask {
control_chan: control_chan,
control_port: control_port,
constellation_chan: constellation_chan,
compositor: compositor,
compositor: DOMRefCell::new(compositor),
devtools_chan: devtools_chan,
devtools_port: devtools_receiver,
@ -671,7 +671,7 @@ impl ScriptTask {
*layout_join_port = None;
}
self.compositor.set_ready_state(pipeline_id, FinishedLoading);
self.compositor.borrow_mut().set_ready_state(pipeline_id, FinishedLoading);
if page.pending_reflows.get() > 0 {
page.pending_reflows.set(0);
@ -709,7 +709,7 @@ impl ScriptTask {
// TODO(tkuehn): currently there is only one window,
// so this can afford to be naive and just shut down the
// compositor. In the future it'll need to be smarter.
self.compositor.close();
self.compositor.borrow_mut().close();
}
/// Handles a request to exit the script task and shut down layout.
@ -771,7 +771,7 @@ impl ScriptTask {
page.clone(),
self.chan.clone(),
self.control_chan.clone(),
self.compositor.dup(),
self.compositor.borrow_mut().dup(),
self.image_cache_task.clone()).root();
let doc_url = if is_javascript {
let doc_url = last_url.unwrap_or_else(|| {
@ -787,7 +787,7 @@ impl ScriptTask {
window.init_browser_context(*document);
self.compositor.set_ready_state(pipeline_id, Loading);
self.compositor.borrow_mut().set_ready_state(pipeline_id, Loading);
let parser_input = if !is_javascript {
InputUrl(url.clone())
@ -858,7 +858,7 @@ impl ScriptTask {
// Really what needs to happen is that this needs to go through layout to ask which
// layer the element belongs to, and have it send the scroll message to the
// compositor.
self.compositor.scroll_fragment_point(pipeline_id, LayerId::null(), point);
self.compositor.borrow_mut().scroll_fragment_point(pipeline_id, LayerId::null(), point);
}
fn force_reflow(&self, page: &Page) {
@ -873,7 +873,10 @@ impl ScriptTask {
}
page.damage();
page.reflow(ReflowForDisplay, self.control_chan.clone(), &*self.compositor, NoQuery);
page.reflow(ReflowForDisplay,
self.control_chan.clone(),
&mut **self.compositor.borrow_mut(),
NoQuery);
}
/// This is the main entry point for receiving and dispatching DOM events.

View file

@ -89,9 +89,9 @@ impl<S: Encoder<E>, E> Encodable<S, E> for ScriptControlChan {
}
pub trait ScriptTaskFactory {
fn create<C: ScriptListener + Send>(_phantom: Option<&mut Self>,
fn create<C>(_phantom: Option<&mut Self>,
id: PipelineId,
compositor: Box<C>,
compositor: C,
layout_chan: &OpaqueScriptLayoutChannel,
control_chan: ScriptControlChan,
control_port: Receiver<ConstellationControlMsg>,
@ -100,7 +100,9 @@ pub trait ScriptTaskFactory {
resource_task: ResourceTask,
image_cache_task: ImageCacheTask,
devtools_chan: Option<DevtoolsControlChan>,
window_size: WindowSizeData);
window_size: WindowSizeData)
where C: ScriptListener + Send;
fn create_layout_channel(_phantom: Option<&mut Self>) -> OpaqueScriptLayoutChannel;
fn clone_layout_channel(_phantom: Option<&mut Self>, pair: &OpaqueScriptLayoutChannel) -> Box<Any+Send>;
fn clone_layout_channel(_phantom: Option<&mut Self>, pair: &OpaqueScriptLayoutChannel)
-> Box<Any+Send>;
}

View file

@ -4,13 +4,13 @@
//! Timing functions.
use std_time::precise_time_ns;
use collections::treemap::TreeMap;
use std::comm::{Sender, channel, Receiver};
use std::f64;
use std::iter::AdditiveIterator;
use std::io::timer::sleep;
use std::iter::AdditiveIterator;
use std::time::duration::Duration;
use std_time::precise_time_ns;
use task::{spawn_named};
use url::Url;

10
ports/cef/Cargo.lock generated
View file

@ -8,7 +8,7 @@ dependencies = [
"devtools 0.0.1",
"geom 0.1.0 (git+https://github.com/servo/rust-geom#b001a76e907befaae1d0d6dd259418a22092da86)",
"gfx 0.0.1",
"glfw 0.0.1 (git+https://github.com/servo/glfw-rs?ref=servo#a15c2d04b8969aea653841d1d79e5fdf68de664b)",
"glfw 0.0.1 (git+https://github.com/servo/glfw-rs?ref=servo#cec2861dd75eb721694b3176f94a276d77018bde)",
"glfw_app 0.0.1",
"js 0.1.0 (git+https://github.com/servo/rust-mozjs#1ec216a2577c03738fa11a78958bb2a0fd3f7fbd)",
"layers 0.1.0 (git+https://github.com/servo/rust-layers#3737b00270644594a95896a2cd37ffca36d5bc5f)",
@ -229,16 +229,16 @@ dependencies = [
[[package]]
name = "glfw"
version = "0.0.1"
source = "git+https://github.com/servo/glfw-rs?ref=servo#a15c2d04b8969aea653841d1d79e5fdf68de664b"
source = "git+https://github.com/servo/glfw-rs?ref=servo#cec2861dd75eb721694b3176f94a276d77018bde"
dependencies = [
"glfw-sys 3.0.4 (git+https://github.com/servo/glfw?ref=cargo-3.0.4#65a2b4721276589d9de24f6a9999a2db37286cae)",
"glfw-sys 3.0.4 (git+https://github.com/servo/glfw?ref=cargo-3.0.4#aa8e0d26cccdb4145f34c5a1724d7e48e0399674)",
"semver 0.0.1 (git+https://github.com/rust-lang/semver#d04583a173395b76c1eaa15cc630a5f6f8f0ae10)",
]
[[package]]
name = "glfw-sys"
version = "3.0.4"
source = "git+https://github.com/servo/glfw?ref=cargo-3.0.4#65a2b4721276589d9de24f6a9999a2db37286cae"
source = "git+https://github.com/servo/glfw?ref=cargo-3.0.4#aa8e0d26cccdb4145f34c5a1724d7e48e0399674"
[[package]]
name = "glfw_app"
@ -248,7 +248,7 @@ dependencies = [
"cgl 0.0.1 (git+https://github.com/servo/rust-cgl#698c6c5409c1049ba5a7e0f7bdddf97f91dc4cf5)",
"compositing 0.0.1",
"geom 0.1.0 (git+https://github.com/servo/rust-geom#b001a76e907befaae1d0d6dd259418a22092da86)",
"glfw 0.0.1 (git+https://github.com/servo/glfw-rs?ref=servo#a15c2d04b8969aea653841d1d79e5fdf68de664b)",
"glfw 0.0.1 (git+https://github.com/servo/glfw-rs?ref=servo#cec2861dd75eb721694b3176f94a276d77018bde)",
"layers 0.1.0 (git+https://github.com/servo/rust-layers#3737b00270644594a95896a2cd37ffca36d5bc5f)",
"msg 0.0.1",
"util 0.0.1",

View file

@ -9,12 +9,11 @@ use geom::size::TypedSize2D;
use glfw_app;
use libc::{c_int, c_void};
use native;
use servo;
use servo::Browser;
use servo_util::opts;
use std::mem;
use types::{cef_app_t, cef_main_args_t, cef_settings_t};
#[no_mangle]
pub extern "C" fn cef_initialize(args: *const cef_main_args_t,
_settings: *mut cef_settings_t,
@ -77,8 +76,10 @@ pub extern "C" fn cef_run_message_loop() {
validate_display_list_geometry: false,
});
native::start(0, 0 as *const *const u8, proc() {
let window = Some(glfw_app::create_window());
servo::run(window);
let window = glfw_app::create_window();
let mut browser = Browser::new(Some(window.clone()));
while browser.handle_event(window.wait_events()) {}
browser.shutdown()
});
}

View file

@ -20,12 +20,17 @@ extern crate msg;
extern crate time;
extern crate util;
use compositing::windowing::WindowEvent;
use geom::scale_factor::ScaleFactor;
use std::rc::Rc;
use window::Window;
use util::opts;
mod window;
pub mod window;
pub trait NestedEventLoopListener {
fn handle_event_from_nested_event_loop(&mut self, event: WindowEvent) -> bool;
}
pub fn create_window() -> Rc<Window> {
// Initialize GLFW.

View file

@ -4,14 +4,18 @@
//! A windowing implementation using GLFW.
use NestedEventLoopListener;
use alert::{Alert, AlertMethods};
use compositing::windowing::{WindowEvent, WindowMethods};
use compositing::compositor_task::{mod, CompositorProxy, CompositorReceiver};
use compositing::windowing::{Forward, Back};
use compositing::windowing::{IdleWindowEvent, ResizeWindowEvent, LoadUrlWindowEvent};
use compositing::windowing::{MouseWindowEventClass, MouseWindowMoveEventClass, ScrollWindowEvent};
use compositing::windowing::{ZoomWindowEvent, PinchZoomWindowEvent, NavigationWindowEvent};
use compositing::windowing::{FinishedWindowEvent, QuitWindowEvent, MouseWindowClickEvent};
use compositing::windowing::{MouseWindowMouseDownEvent, MouseWindowMouseUpEvent};
use compositing::windowing::{RefreshWindowEvent, Forward, Back};
use compositing::windowing::{MouseWindowClickEvent, MouseWindowMouseDownEvent};
use compositing::windowing::{MouseWindowEventClass, MouseWindowMoveEventClass};
use compositing::windowing::{MouseWindowMouseUpEvent, RefreshWindowEvent};
use compositing::windowing::{NavigationWindowEvent, ScrollWindowEvent, ZoomWindowEvent};
use compositing::windowing::{PinchZoomWindowEvent, QuitWindowEvent};
use compositing::windowing::{WindowEvent, WindowMethods, FinishedWindowEvent};
use geom::point::{Point2D, TypedPoint2D};
use geom::scale_factor::ScaleFactor;
use geom::size::TypedSize2D;
@ -19,8 +23,8 @@ use glfw::{mod, Context};
use layers::geometry::DevicePixel;
use layers::platform::surface::NativeGraphicsMetadata;
use libc::c_int;
use msg::compositor_msg::{IdleRenderState, RenderState, RenderingRenderState};
use msg::compositor_msg::{FinishedLoading, Blank, Loading, PerformingLayout, ReadyState};
use msg::compositor_msg::{IdleRenderState, RenderState, RenderingRenderState};
use std::cell::{Cell, RefCell};
use std::comm::Receiver;
use std::rc::Rc;
@ -84,12 +88,50 @@ impl Window {
window.glfw_window.set_cursor_pos_polling(true);
window.glfw_window.set_scroll_polling(true);
let wrapped_window = Rc::new(window);
glfw.set_swap_interval(1);
wrapped_window
Rc::new(window)
}
pub fn wait_events(&self) -> WindowEvent {
{
let mut event_queue = self.event_queue.borrow_mut();
if !event_queue.is_empty() {
return event_queue.remove(0).unwrap();
}
}
self.glfw.wait_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)
}
}
pub unsafe fn set_nested_event_loop_listener(
&self,
listener: *mut NestedEventLoopListener + 'static) {
self.glfw_window.set_refresh_polling(false);
glfw::ffi::glfwSetWindowRefreshCallback(self.glfw_window.ptr, Some(on_refresh));
glfw::ffi::glfwSetFramebufferSizeCallback(self.glfw_window.ptr, Some(on_framebuffer_size));
g_nested_event_loop_listener = Some(listener)
}
pub unsafe fn remove_nested_event_loop_listener(&self) {
glfw::ffi::glfwSetWindowRefreshCallback(self.glfw_window.ptr, None);
glfw::ffi::glfwSetFramebufferSizeCallback(self.glfw_window.ptr, None);
self.glfw_window.set_refresh_polling(true);
g_nested_event_loop_listener = None
}
}
static mut g_nested_event_loop_listener: Option<*mut NestedEventLoopListener + 'static> = None;
impl WindowMethods for Window {
/// Returns the size of the window in hardware pixels.
fn framebuffer_size(&self) -> TypedSize2D<DevicePixel, uint> {
@ -108,26 +150,6 @@ impl WindowMethods for Window {
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);
@ -169,6 +191,15 @@ impl WindowMethods for Window {
}
}
}
fn create_compositor_channel(_: &Option<Rc<Window>>)
-> (Box<CompositorProxy+Send>, Box<CompositorReceiver>) {
let (sender, receiver) = channel();
(box GlfwCompositorProxy {
sender: sender,
} as Box<CompositorProxy+Send>,
box receiver as Box<CompositorReceiver>)
}
}
impl Window {
@ -219,7 +250,6 @@ impl Window {
self.scroll_window(dx, dy);
}
}
},
_ => {}
}
@ -346,3 +376,45 @@ impl Window {
}
}
struct GlfwCompositorProxy {
sender: Sender<compositor_task::Msg>,
}
impl CompositorProxy for GlfwCompositorProxy {
fn send(&mut self, msg: compositor_task::Msg) {
// Send a message and kick the OS event loop awake.
self.sender.send(msg);
glfw::Glfw::post_empty_event()
}
fn clone_compositor_proxy(&self) -> Box<CompositorProxy+Send> {
box GlfwCompositorProxy {
sender: self.sender.clone(),
} as Box<CompositorProxy+Send>
}
}
extern "C" fn on_refresh(_glfw_window: *mut glfw::ffi::GLFWwindow) {
unsafe {
match g_nested_event_loop_listener {
None => {}
Some(listener) => {
(*listener).handle_event_from_nested_event_loop(RefreshWindowEvent);
}
}
}
}
extern "C" fn on_framebuffer_size(_glfw_window: *mut glfw::ffi::GLFWwindow,
width: c_int,
height: c_int) {
unsafe {
match g_nested_event_loop_listener {
None => {}
Some(listener) => {
let size = TypedSize2D(width as uint, height as uint);
(*listener).handle_event_from_nested_event_loop(ResizeWindowEvent(size));
}
}
}
}

View file

@ -30,10 +30,11 @@ extern crate native;
extern crate rustrt;
extern crate url;
use compositing::CompositorEventListener;
use compositing::windowing::{WindowEvent, WindowMethods};
#[cfg(not(test))]
use compositing::{CompositorChan, CompositorTask, Constellation};
#[cfg(not(test))]
use compositing::windowing::WindowMethods;
use compositing::{CompositorProxy, CompositorTask, Constellation};
#[cfg(not(test))]
use servo_msg::constellation_msg::{ConstellationChan, InitLoadUrlMsg};
#[cfg(not(test))]
@ -63,8 +64,14 @@ use std::rc::Rc;
#[cfg(not(test))]
use std::task::TaskBuilder;
pub struct Browser<Window> {
pool: green::SchedPool,
compositor: Box<CompositorEventListener + 'static>,
}
impl<Window> Browser<Window> where Window: WindowMethods + 'static {
#[cfg(not(test))]
pub fn run<Window: WindowMethods>(window: Option<Rc<Window>>) {
pub fn new(window: Option<Rc<Window>>) -> Browser<Window> {
::servo_util::opts::set_experimental_enabled(opts::get().enable_experimental);
let opts = opts::get();
RegisterBindings::RegisterProxyHandlers();
@ -72,21 +79,25 @@ pub fn run<Window: WindowMethods>(window: Option<Rc<Window>>) {
let mut pool_config = green::PoolConfig::new();
pool_config.event_loop_factory = rustuv::event_loop;
let mut pool = green::SchedPool::new(pool_config);
let shared_task_pool = TaskPool::new(8);
let (compositor_port, compositor_chan) = CompositorChan::new();
let (compositor_proxy, compositor_receiver) =
WindowMethods::create_compositor_channel(&window);
let time_profiler_chan = TimeProfiler::create(opts.time_profiler_period);
let memory_profiler_chan = MemoryProfiler::create(opts.memory_profiler_period);
let devtools_chan = opts.devtools_port.map(|port| {
devtools::start_server(port)
});
let opts_clone = opts.clone();
let time_profiler_chan_clone = time_profiler_chan.clone();
let shared_task_pool = TaskPool::new(8);
let (result_chan, result_port) = channel();
let compositor_proxy_for_constellation = compositor_proxy.clone_compositor_proxy();
TaskBuilder::new()
.green(&mut pool)
.spawn(proc() {
let opts = &opts_clone;
// Create a Servo instance.
let resource_task = new_resource_task(opts.user_agent.clone());
// If we are emitting an output file, then we need to block on
@ -100,7 +111,7 @@ pub fn run<Window: WindowMethods>(window: Option<Rc<Window>>) {
let font_cache_task = FontCacheTask::new(resource_task.clone());
let constellation_chan = Constellation::<layout::layout_task::LayoutTask,
script::script_task::ScriptTask>::start(
compositor_chan,
compositor_proxy_for_constellation,
resource_task,
image_cache_task,
font_cache_task,
@ -128,12 +139,30 @@ pub fn run<Window: WindowMethods>(window: Option<Rc<Window>>) {
let constellation_chan = result_port.recv();
debug!("preparing to enter main loop");
CompositorTask::create(window,
compositor_port,
let compositor = CompositorTask::create(window,
compositor_proxy,
compositor_receiver,
constellation_chan,
time_profiler_chan,
memory_profiler_chan);
pool.shutdown();
Browser {
pool: pool,
compositor: compositor,
}
}
pub fn handle_event(&mut self, event: WindowEvent) -> bool {
self.compositor.handle_event(event)
}
pub fn repaint_synchronously(&mut self) {
self.compositor.repaint_synchronously()
}
pub fn shutdown(mut self) {
self.compositor.shutdown();
self.pool.shutdown();
}
}

View file

@ -9,10 +9,13 @@
extern crate servo;
extern crate native;
extern crate time;
extern crate "util" as servo_util;
#[cfg(not(test),not(target_os="android"))]
extern crate glfw_app;
#[cfg(not(test),not(target_os="android"))]
extern crate compositing;
#[cfg(not(test),not(target_os="android"))]
use servo_util::opts;
@ -21,11 +24,18 @@ use servo_util::opts;
use servo_util::rtinstrument;
#[cfg(not(test),not(target_os="android"))]
use servo::run;
use servo::Browser;
#[cfg(not(test),not(target_os="android"))]
use compositing::windowing::{IdleWindowEvent, ResizeWindowEvent, WindowEvent};
#[cfg(not(test),not(target_os="android"))]
use std::os;
#[cfg(not(test),not(target_os="android"))]
struct BrowserWrapper {
browser: Browser<glfw_app::window::Window>,
}
#[cfg(not(test), not(target_os="android"))]
#[start]
#[allow(dead_code)]
@ -37,7 +47,46 @@ fn start(argc: int, argv: *const *const u8) -> int {
} else {
Some(glfw_app::create_window())
};
run(window);
let mut browser = BrowserWrapper {
browser: Browser::new(window.clone()),
};
match window {
None => {}
Some(ref window) => {
unsafe {
window.set_nested_event_loop_listener(&mut browser);
}
}
}
loop {
let should_continue = match window {
None => browser.browser.handle_event(IdleWindowEvent),
Some(ref window) => {
let event = window.wait_events();
browser.browser.handle_event(event)
}
};
if !should_continue {
break
}
}
match window {
None => {}
Some(ref window) => {
unsafe {
window.remove_nested_event_loop_listener();
}
}
}
let BrowserWrapper {
browser
} = browser;
browser.shutdown();
rtinstrument::teardown();
}
@ -46,3 +95,21 @@ fn start(argc: int, argv: *const *const u8) -> int {
#[cfg(not(test), target_os="android")]
fn main() {}
#[cfg(not(test),not(target_os="android"))]
impl glfw_app::NestedEventLoopListener for BrowserWrapper {
fn handle_event_from_nested_event_loop(&mut self, event: WindowEvent) -> bool {
let is_resize = match event {
ResizeWindowEvent(..) => true,
_ => false,
};
if !self.browser.handle_event(event) {
return false
}
if is_resize {
self.browser.repaint_synchronously()
}
true
}
}

@ -1 +1 @@
Subproject commit cec2861dd75eb721694b3176f94a276d77018bde
Subproject commit 8edf667fbf289d909c7d1bdde03acf6d78168674