Cargoify servo

This commit is contained in:
Jack Moffitt 2014-08-28 09:34:23 -06:00
parent db2f642c32
commit c6ab60dbfc
1761 changed files with 8423 additions and 2294 deletions

View file

@ -0,0 +1,14 @@
[package]
name = "canvas"
version = "0.0.1"
authors = ["The Servo Project Developers"]
[lib]
name = "canvas"
path = "lib.rs"
[dependencies.azure]
git = "https://github.com/servo/rust-azure"
[dependencies.geom]
git = "https://github.com/servo/rust-geom"

View file

@ -0,0 +1,78 @@
/* 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 azure::azure_hl::{DrawTarget, Color, B8G8R8A8, SkiaBackend, StrokeOptions, DrawOptions};
use azure::azure_hl::ColorPattern;
use geom::rect::Rect;
use geom::size::Size2D;
use std::comm;
use std::task::TaskBuilder;
pub enum CanvasMsg {
FillRect(Rect<f32>),
ClearRect(Rect<f32>),
StrokeRect(Rect<f32>),
Recreate(Size2D<i32>),
Close,
}
pub struct CanvasRenderTask {
drawtarget: DrawTarget,
fill_color: ColorPattern,
stroke_color: ColorPattern,
stroke_opts: StrokeOptions,
}
impl CanvasRenderTask {
fn new(size: Size2D<i32>) -> CanvasRenderTask {
CanvasRenderTask {
drawtarget: CanvasRenderTask::create(size),
fill_color: ColorPattern::new(Color::new(0., 0., 0., 1.)),
stroke_color: ColorPattern::new(Color::new(0., 0., 0., 1.)),
stroke_opts: StrokeOptions::new(1.0, 1.0),
}
}
pub fn start(size: Size2D<i32>) -> Sender<CanvasMsg> {
let (chan, port) = comm::channel::<CanvasMsg>();
let builder = TaskBuilder::new().named("CanvasTask");
builder.spawn(proc() {
let mut renderer = CanvasRenderTask::new(size);
loop {
match port.recv() {
FillRect(ref rect) => renderer.fill_rect(rect),
StrokeRect(ref rect) => renderer.stroke_rect(rect),
ClearRect(ref rect) => renderer.clear_rect(rect),
Recreate(size) => renderer.recreate(size),
Close => break,
}
}
});
chan
}
fn fill_rect(&self, rect: &Rect<f32>) {
let drawopts = DrawOptions::new(1.0, 0);
self.drawtarget.fill_rect(rect, &self.fill_color, Some(&drawopts));
}
fn clear_rect(&self, rect: &Rect<f32>) {
self.drawtarget.clear_rect(rect);
}
fn stroke_rect(&self, rect: &Rect<f32>) {
let drawopts = DrawOptions::new(1.0, 0);
self.drawtarget.stroke_rect(rect, &self.stroke_color, &self.stroke_opts, &drawopts);
}
fn create(size: Size2D<i32>) -> DrawTarget {
DrawTarget::new(SkiaBackend, size, B8G8R8A8)
}
fn recreate(&mut self, size: Size2D<i32>) {
self.drawtarget = CanvasRenderTask::create(size);
}
}

8
components/canvas/lib.rs Normal file
View file

@ -0,0 +1,8 @@
/* 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/. */
extern crate azure;
extern crate geom;
pub mod canvas_render_task;

View 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"

View 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;
}
}

View 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());
}
}
}

View 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)
}
};
}
}

View 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
}
}
}

View 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)
}

View 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(..) => ()
}
}
}
}

View 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;

View 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(),
}
}
}

View 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()))
}
}
}

View 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()
}

View 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;
}

View 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>;
}

62
components/gfx/Cargo.toml Normal file
View file

@ -0,0 +1,62 @@
[package]
name = "gfx"
version = "0.0.1"
authors = ["The Servo Project Developers"]
[lib]
name = "gfx"
path = "lib.rs"
[dependencies.macros]
path = "../macros"
[dependencies.net]
path = "../net"
[dependencies.util]
path = "../util"
[dependencies.msg]
path = "../msg"
[dependencies.style]
path = "../style"
[dependencies.azure]
git = "https://github.com/servo/rust-azure"
[dependencies.geom]
git = "https://github.com/servo/rust-geom"
[dependencies.layers]
git = "https://github.com/servo/rust-layers"
[dependencies.stb_image]
git = "https://github.com/servo/rust-stb-image"
[dependencies.png]
git = "https://github.com/servo/rust-png"
[dependencies.url]
git = "https://github.com/servo/rust-url"
[dependencies.harfbuzz]
git = "https://github.com/servo/rust-harfbuzz"
[dependencies.fontconfig]
git = "https://github.com/servo/rust-fontconfig"
[dependencies.freetype]
git = "https://github.com/servo/rust-freetype"
[dependencies.core_foundation]
git = "http://github.com/servo/rust-core-foundation"
[dependencies.core_graphics]
git = "http://github.com/servo/rust-core-graphics"
[dependencies.core_text]
git = "http://github.com/servo/rust-core-text"

View file

@ -0,0 +1,156 @@
/* 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 std::collections::hashmap::HashMap;
use geom::size::Size2D;
use layers::platform::surface::NativePaintingGraphicsContext;
use layers::layers::LayerBuffer;
use std::hash::Hash;
use std::hash::sip::SipState;
use std::mem;
/// This is a struct used to store buffers when they are not in use.
/// The render task can quickly query for a particular size of buffer when it
/// needs it.
pub struct BufferMap {
/// A HashMap that stores the Buffers.
map: HashMap<BufferKey, BufferValue>,
/// The current amount of memory stored by the BufferMap's buffers.
mem: uint,
/// The maximum allowed memory. Unused buffers will be deleted
/// when this threshold is exceeded.
max_mem: uint,
/// A monotonically increasing counter to track how recently tile sizes were used.
counter: uint,
}
/// A key with which to store buffers. It is based on the size of the buffer.
#[deriving(Eq)]
struct BufferKey([uint, ..2]);
impl Hash for BufferKey {
fn hash(&self, state: &mut SipState) {
let BufferKey(ref bytes) = *self;
bytes.as_slice().hash(state);
}
}
impl PartialEq for BufferKey {
fn eq(&self, other: &BufferKey) -> bool {
let BufferKey(s) = *self;
let BufferKey(o) = *other;
s[0] == o[0] && s[1] == o[1]
}
}
/// Create a key from a given size
impl BufferKey {
fn get(input: Size2D<uint>) -> BufferKey {
BufferKey([input.width, input.height])
}
}
/// A helper struct to keep track of buffers in the HashMap
struct BufferValue {
/// An array of buffers, all the same size
buffers: Vec<Box<LayerBuffer>>,
/// The counter when this size was last requested
last_action: uint,
}
impl BufferMap {
// Creates a new BufferMap with a given buffer limit.
pub fn new(max_mem: uint) -> BufferMap {
BufferMap {
map: HashMap::new(),
mem: 0u,
max_mem: max_mem,
counter: 0u,
}
}
/// Insert a new buffer into the map.
pub fn insert(&mut self, graphics_context: &NativePaintingGraphicsContext, new_buffer: Box<LayerBuffer>) {
let new_key = BufferKey::get(new_buffer.get_size_2d());
// If all our buffers are the same size and we're already at our
// memory limit, no need to store this new buffer; just let it drop.
if self.mem + new_buffer.get_mem() > self.max_mem && self.map.len() == 1 &&
self.map.contains_key(&new_key) {
new_buffer.destroy(graphics_context);
return;
}
self.mem += new_buffer.get_mem();
// use lazy insertion function to prevent unnecessary allocation
let counter = &self.counter;
self.map.find_or_insert_with(new_key, |_| BufferValue {
buffers: vec!(),
last_action: *counter
}).buffers.push(new_buffer);
let mut opt_key: Option<BufferKey> = None;
while self.mem > self.max_mem {
let old_key = match opt_key {
Some(key) => key,
None => {
match self.map.iter().min_by(|&(_, x)| x.last_action) {
Some((k, _)) => *k,
None => fail!("BufferMap: tried to delete with no elements in map"),
}
}
};
if {
let list = &mut self.map.get_mut(&old_key).buffers;
let condemned_buffer = list.pop().take_unwrap();
self.mem -= condemned_buffer.get_mem();
condemned_buffer.destroy(graphics_context);
list.is_empty()
}
{ // then
self.map.pop(&old_key); // Don't store empty vectors!
opt_key = None;
} else {
opt_key = Some(old_key);
}
}
}
// Try to find a buffer for the given size.
pub fn find(&mut self, size: Size2D<uint>) -> Option<Box<LayerBuffer>> {
let mut flag = false; // True if key needs to be popped after retrieval.
let key = BufferKey::get(size);
let ret = match self.map.find_mut(&key) {
Some(ref mut buffer_val) => {
buffer_val.last_action = self.counter;
self.counter += 1;
let buffer = buffer_val.buffers.pop().take_unwrap();
self.mem -= buffer.get_mem();
if buffer_val.buffers.is_empty() {
flag = true;
}
Some(buffer)
}
None => None,
};
if flag {
self.map.pop(&key); // Don't store empty vectors!
}
ret
}
/// Destroys all buffers.
pub fn clear(&mut self, graphics_context: &NativePaintingGraphicsContext) {
let map = mem::replace(&mut self.map, HashMap::new());
for (_, value) in map.move_iter() {
for tile in value.buffers.move_iter() {
tile.destroy(graphics_context)
}
}
self.mem = 0
}
}

21
components/gfx/color.rs Normal file
View file

@ -0,0 +1,21 @@
/* 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 azure::AzFloat;
use AzColor = azure::azure_hl::Color;
pub type Color = AzColor;
pub fn rgb(r: u8, g: u8, b: u8) -> AzColor {
AzColor {
r: (r as AzFloat) / (255.0 as AzFloat),
g: (g as AzFloat) / (255.0 as AzFloat),
b: (b as AzFloat) / (255.0 as AzFloat),
a: 1.0 as AzFloat
}
}
pub fn rgba(r: AzFloat, g: AzFloat, b: AzFloat, a: AzFloat) -> AzColor {
AzColor { r: r, g: g, b: b, a: a }
}

View file

@ -0,0 +1,773 @@
/* 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/. */
//! Servo heavily uses display lists, which are retained-mode lists of rendering commands to
//! perform. Using a list instead of rendering elements in immediate mode allows transforms, hit
//! testing, and invalidation to be performed using the same primitives as painting. It also allows
//! Servo to aggressively cull invisible and out-of-bounds rendering elements, to reduce overdraw.
//! Finally, display lists allow tiles to be farmed out onto multiple CPUs and rendered in
//! parallel (although this benefit does not apply to GPU-based rendering).
//!
//! Display items describe relatively high-level drawing operations (for example, entire borders
//! and shadows instead of lines and blur operations), to reduce the amount of allocation required.
//! They are therefore not exactly analogous to constructs like Skia pictures, which consist of
//! low-level drawing primitives.
use color::Color;
use render_context::RenderContext;
use text::glyph::CharIndex;
use text::TextRun;
use collections::dlist::DList;
use collections::dlist;
use geom::{Point2D, Rect, SideOffsets2D, Size2D, Matrix2D};
use libc::uintptr_t;
use servo_net::image::base::Image;
use servo_util::geometry::Au;
use servo_util::range::Range;
use std::fmt;
use std::mem;
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;
/// An opaque handle to a node. The only safe operation that can be performed on this node is to
/// compare it to another opaque handle or to another node.
///
/// Because the script task's GC does not trace layout, node data cannot be safely stored in layout
/// data structures. Also, layout code tends to be faster when the DOM is not being accessed, for
/// locality reasons. Using `OpaqueNode` enforces this invariant.
#[deriving(Clone, PartialEq)]
pub struct OpaqueNode(pub uintptr_t);
impl OpaqueNode {
/// Returns the address of this node, for debugging purposes.
pub fn id(&self) -> uintptr_t {
let OpaqueNode(pointer) = *self;
pointer
}
}
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_nearest_px() as AzFloat,
y: (origin.y + glyph_offset.y).to_nearest_px() 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::mut_null());
}
}
}
/// "Steps" as defined by CSS 2.1 § E.2.
#[deriving(Clone, PartialEq)]
pub enum StackingLevel {
/// The border and backgrounds for the root of this stacking context: steps 1 and 2.
BackgroundAndBordersStackingLevel,
/// Borders and backgrounds for block-level descendants: step 4.
BlockBackgroundsAndBordersStackingLevel,
/// Floats: step 5. These are treated as pseudo-stacking contexts.
FloatStackingLevel,
/// All other content.
ContentStackingLevel,
/// Positioned descendant stacking contexts, along with their `z-index` levels.
///
/// TODO(pcwalton): `z-index` should be the actual CSS property value in order to handle
/// `auto`, not just an integer.
PositionedDescendantStackingLevel(i32)
}
impl StackingLevel {
pub fn from_background_and_border_level(level: BackgroundAndBorderLevel) -> StackingLevel {
match level {
RootOfStackingContextLevel => BackgroundAndBordersStackingLevel,
BlockLevel => BlockBackgroundsAndBordersStackingLevel,
ContentLevel => ContentStackingLevel,
}
}
}
struct StackingContext {
/// The border and backgrounds for the root of this stacking context: steps 1 and 2.
pub background_and_borders: DisplayList,
/// Borders and backgrounds for block-level descendants: step 4.
pub block_backgrounds_and_borders: DisplayList,
/// Floats: step 5. These are treated as pseudo-stacking contexts.
pub floats: DisplayList,
/// All other content.
pub content: DisplayList,
/// Positioned descendant stacking contexts, along with their `z-index` levels.
///
/// TODO(pcwalton): `z-index` should be the actual CSS property value in order to handle
/// `auto`, not just an integer.
pub positioned_descendants: Vec<(i32, DisplayList)>,
}
impl StackingContext {
/// Creates a stacking context from a display list.
fn new(list: DisplayList) -> StackingContext {
let DisplayList {
list: list
} = list;
let mut stacking_context = StackingContext {
background_and_borders: DisplayList::new(),
block_backgrounds_and_borders: DisplayList::new(),
floats: DisplayList::new(),
content: DisplayList::new(),
positioned_descendants: Vec::new(),
};
for item in list.move_iter() {
match item {
ClipDisplayItemClass(box ClipDisplayItem {
base: base,
children: sublist
}) => {
let sub_stacking_context = StackingContext::new(sublist);
stacking_context.merge_with_clip(sub_stacking_context, &base.bounds, base.node)
}
item => {
match item.base().level {
BackgroundAndBordersStackingLevel => {
stacking_context.background_and_borders.push(item)
}
BlockBackgroundsAndBordersStackingLevel => {
stacking_context.block_backgrounds_and_borders.push(item)
}
FloatStackingLevel => stacking_context.floats.push(item),
ContentStackingLevel => stacking_context.content.push(item),
PositionedDescendantStackingLevel(z_index) => {
match stacking_context.positioned_descendants
.mut_iter()
.find(|& &(z, _)| z_index == z) {
Some(&(_, ref mut my_list)) => {
my_list.push(item);
continue
}
None => {}
}
let mut new_list = DisplayList::new();
new_list.list.push(item);
stacking_context.positioned_descendants.push((z_index, new_list))
}
}
}
}
}
stacking_context
}
/// Merges another stacking context into this one, with the given clipping rectangle and DOM
/// node that supplies it.
fn merge_with_clip(&mut self,
other: StackingContext,
clip_rect: &Rect<Au>,
clipping_dom_node: OpaqueNode) {
let StackingContext {
background_and_borders,
block_backgrounds_and_borders,
floats,
content,
positioned_descendants: positioned_descendants
} = other;
let push = |destination: &mut DisplayList, source: DisplayList, level| {
if !source.is_empty() {
let base = BaseDisplayItem::new(*clip_rect, clipping_dom_node, level);
destination.push(ClipDisplayItemClass(box ClipDisplayItem::new(base, source)))
}
};
push(&mut self.background_and_borders,
background_and_borders,
BackgroundAndBordersStackingLevel);
push(&mut self.block_backgrounds_and_borders,
block_backgrounds_and_borders,
BlockBackgroundsAndBordersStackingLevel);
push(&mut self.floats, floats, FloatStackingLevel);
push(&mut self.content, content, ContentStackingLevel);
for (z_index, list) in positioned_descendants.move_iter() {
match self.positioned_descendants
.mut_iter()
.find(|& &(existing_z_index, _)| z_index == existing_z_index) {
Some(&(_, ref mut existing_list)) => {
push(existing_list, list, PositionedDescendantStackingLevel(z_index));
continue
}
None => {}
}
let mut new_list = DisplayList::new();
push(&mut new_list, list, PositionedDescendantStackingLevel(z_index));
self.positioned_descendants.push((z_index, new_list));
}
}
}
/// Which level to place backgrounds and borders in.
pub enum BackgroundAndBorderLevel {
RootOfStackingContextLevel,
BlockLevel,
ContentLevel,
}
/// A list of rendering operations to be performed.
#[deriving(Clone)]
pub struct DisplayList {
pub list: DList<DisplayItem>,
}
pub enum DisplayListIterator<'a> {
EmptyDisplayListIterator,
ParentDisplayListIterator(Items<'a,DisplayList>),
}
impl<'a> Iterator<&'a DisplayList> for DisplayListIterator<'a> {
#[inline]
fn next(&mut self) -> Option<&'a DisplayList> {
match *self {
EmptyDisplayListIterator => None,
ParentDisplayListIterator(ref mut subiterator) => subiterator.next(),
}
}
}
impl DisplayList {
/// Creates a new display list.
pub fn new() -> DisplayList {
DisplayList {
list: DList::new(),
}
}
/// Appends the given item to the display list.
pub fn push(&mut self, item: DisplayItem) {
self.list.push(item)
}
/// Appends the given display list to this display list, consuming the other display list in
/// the process.
pub fn push_all_move(&mut self, other: DisplayList) {
self.list.append(other.list)
}
pub fn debug(&self) {
if log_enabled!(::log::DEBUG) {
for item in self.list.iter() {
item.debug_with_level(0);
}
}
}
/// Draws the display list into the given render context. The display list must be flattened
/// first for correct painting.
pub fn draw_into_context(&self, render_context: &mut RenderContext,
current_transform: &Matrix2D<AzFloat>) {
debug!("Beginning display list.");
for item in self.list.iter() {
item.draw_into_context(render_context, current_transform)
}
debug!("Ending display list.");
}
/// Returns a preorder iterator over the given display list.
pub fn iter<'a>(&'a self) -> DisplayItemIterator<'a> {
ParentDisplayItemIterator(self.list.iter())
}
/// Returns true if this list is empty and false otherwise.
fn is_empty(&self) -> bool {
self.list.len() == 0
}
/// Flattens a display list into a display list with a single stacking level according to the
/// steps in CSS 2.1 § E.2.
///
/// This must be called before `draw_into_context()` is for correct results.
pub fn flatten(self, resulting_level: StackingLevel) -> DisplayList {
// TODO(pcwalton): Sort positioned children according to z-index.
let mut result = DisplayList::new();
let StackingContext {
background_and_borders,
block_backgrounds_and_borders,
floats,
content,
positioned_descendants: mut positioned_descendants
} = StackingContext::new(self);
// Steps 1 and 2: Borders and background for the root.
result.push_all_move(background_and_borders);
// TODO(pcwalton): Sort positioned children according to z-index.
// Step 3: Positioned descendants with negative z-indices.
for &(ref mut z_index, ref mut list) in positioned_descendants.mut_iter() {
if *z_index < 0 {
result.push_all_move(mem::replace(list, DisplayList::new()))
}
}
// Step 4: Block backgrounds and borders.
result.push_all_move(block_backgrounds_and_borders);
// Step 5: Floats.
result.push_all_move(floats);
// TODO(pcwalton): Step 6: Inlines that generate stacking contexts.
// Step 7: Content.
result.push_all_move(content);
// Steps 8 and 9: Positioned descendants with nonnegative z-indices.
for &(ref mut z_index, ref mut list) in positioned_descendants.mut_iter() {
if *z_index >= 0 {
result.push_all_move(mem::replace(list, DisplayList::new()))
}
}
// TODO(pcwalton): Step 10: Outlines.
result.set_stacking_level(resulting_level);
result
}
/// Sets the stacking level for this display list and all its subitems.
fn set_stacking_level(&mut self, new_level: StackingLevel) {
for item in self.list.mut_iter() {
item.mut_base().level = new_level;
match item.mut_sublist() {
None => {}
Some(sublist) => sublist.set_stacking_level(new_level),
}
}
}
}
/// One drawing command in the list.
#[deriving(Clone)]
pub enum DisplayItem {
SolidColorDisplayItemClass(Box<SolidColorDisplayItem>),
TextDisplayItemClass(Box<TextDisplayItem>),
ImageDisplayItemClass(Box<ImageDisplayItem>),
BorderDisplayItemClass(Box<BorderDisplayItem>),
LineDisplayItemClass(Box<LineDisplayItem>),
ClipDisplayItemClass(Box<ClipDisplayItem>),
/// A pseudo-display item that exists only so that queries like `ContentBoxQuery` and
/// `ContentBoxesQuery` can be answered.
///
/// FIXME(pcwalton): This is really bogus. Those queries should not consult the display list
/// but should instead consult the flow/box tree.
PseudoDisplayItemClass(Box<BaseDisplayItem>),
}
/// Information common to all display items.
#[deriving(Clone)]
pub struct BaseDisplayItem {
/// The boundaries of the display item.
///
/// TODO: Which coordinate system should this use?
pub bounds: Rect<Au>,
/// The originating DOM node.
pub node: OpaqueNode,
/// The stacking level in which this display item lives.
pub level: StackingLevel,
}
impl BaseDisplayItem {
pub fn new(bounds: Rect<Au>, node: OpaqueNode, level: StackingLevel) -> BaseDisplayItem {
BaseDisplayItem {
bounds: bounds,
node: node,
level: level,
}
}
}
/// Renders a solid color.
#[deriving(Clone)]
pub struct SolidColorDisplayItem {
pub base: BaseDisplayItem,
pub color: Color,
}
/// Renders text.
#[deriving(Clone)]
pub struct TextDisplayItem {
/// Fields common to all display items.
pub base: BaseDisplayItem,
/// The text run.
pub text_run: Arc<Box<TextRun>>,
/// The range of text within the text run.
pub range: Range<CharIndex>,
/// The color of the text.
pub text_color: Color,
pub baseline_origin: Point2D<Au>,
pub orientation: TextOrientation,
}
#[deriving(Clone, Eq, PartialEq)]
pub enum TextOrientation {
Upright,
SidewaysLeft,
SidewaysRight,
}
/// Renders an image.
#[deriving(Clone)]
pub struct ImageDisplayItem {
pub base: BaseDisplayItem,
pub image: Arc<Box<Image>>,
/// The dimensions to which the image display item should be stretched. If this is smaller than
/// the bounds of this display item, then the image will be repeated in the appropriate
/// direction to tile the entire bounds.
pub stretch_size: Size2D<Au>,
}
/// Renders a border.
#[deriving(Clone)]
pub struct BorderDisplayItem {
pub base: BaseDisplayItem,
/// The border widths
pub border: SideOffsets2D<Au>,
/// The border colors.
pub color: SideOffsets2D<Color>,
/// The border styles.
pub style: SideOffsets2D<border_style::T>
}
/// Renders a line segment.
#[deriving(Clone)]
pub struct LineDisplayItem {
pub base: BaseDisplayItem,
/// The line segment color.
pub color: Color,
/// The line segment style.
pub style: border_style::T
}
/// Clips a list of child display items to this display item's boundaries.
#[deriving(Clone)]
pub struct ClipDisplayItem {
/// The base information.
pub base: BaseDisplayItem,
/// The child nodes.
pub children: DisplayList,
}
impl ClipDisplayItem {
pub fn new(base: BaseDisplayItem, children: DisplayList) -> ClipDisplayItem {
ClipDisplayItem {
base: base,
children: children,
}
}
}
pub enum DisplayItemIterator<'a> {
EmptyDisplayItemIterator,
ParentDisplayItemIterator(dlist::Items<'a,DisplayItem>),
}
impl<'a> Iterator<&'a DisplayItem> for DisplayItemIterator<'a> {
#[inline]
fn next(&mut self) -> Option<&'a DisplayItem> {
match *self {
EmptyDisplayItemIterator => None,
ParentDisplayItemIterator(ref mut subiterator) => subiterator.next(),
}
}
}
impl DisplayItem {
/// Renders this display item into the given render context.
fn draw_into_context(&self, render_context: &mut RenderContext,
current_transform: &Matrix2D<AzFloat>) {
// This should have been flattened to the content stacking level first.
assert!(self.base().level == ContentStackingLevel);
match *self {
SolidColorDisplayItemClass(ref solid_color) => {
render_context.draw_solid_color(&solid_color.base.bounds, solid_color.color)
}
ClipDisplayItemClass(ref clip) => {
render_context.draw_push_clip(&clip.base.bounds);
for item in clip.children.iter() {
(*item).draw_into_context(render_context, current_transform);
}
render_context.draw_pop_clip();
}
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.pt_size,
render_context.opts.render_backend
).borrow().draw_text_into_context(
render_context,
&*text.text_run,
&text.range,
baseline_origin,
text.text_color,
render_context.opts.enable_text_antialiasing
);
// Undo the transform, only when we did one.
if text.orientation != Upright {
render_context.draw_target.set_transform(current_transform)
}
}
ImageDisplayItemClass(ref image_item) => {
debug!("Drawing image at {:?}.", image_item.base.bounds);
let mut y_offset = Au(0);
while y_offset < image_item.base.bounds.size.height {
let mut x_offset = Au(0);
while x_offset < image_item.base.bounds.size.width {
let mut bounds = image_item.base.bounds;
bounds.origin.x = bounds.origin.x + x_offset;
bounds.origin.y = bounds.origin.y + y_offset;
bounds.size = image_item.stretch_size;
render_context.draw_image(bounds, image_item.image.clone());
x_offset = x_offset + image_item.stretch_size.width;
}
y_offset = y_offset + image_item.stretch_size.height;
}
}
BorderDisplayItemClass(ref border) => {
render_context.draw_border(&border.base.bounds,
border.border,
border.color,
border.style)
}
LineDisplayItemClass(ref line) => {
render_context.draw_line(&line.base.bounds,
line.color,
line.style)
}
PseudoDisplayItemClass(_) => {}
}
}
pub fn base<'a>(&'a self) -> &'a BaseDisplayItem {
match *self {
SolidColorDisplayItemClass(ref solid_color) => &solid_color.base,
TextDisplayItemClass(ref text) => &text.base,
ImageDisplayItemClass(ref image_item) => &image_item.base,
BorderDisplayItemClass(ref border) => &border.base,
LineDisplayItemClass(ref line) => &line.base,
ClipDisplayItemClass(ref clip) => &clip.base,
PseudoDisplayItemClass(ref base) => &**base,
}
}
pub fn mut_base<'a>(&'a mut self) -> &'a mut BaseDisplayItem {
match *self {
SolidColorDisplayItemClass(ref mut solid_color) => &mut solid_color.base,
TextDisplayItemClass(ref mut text) => &mut text.base,
ImageDisplayItemClass(ref mut image_item) => &mut image_item.base,
BorderDisplayItemClass(ref mut border) => &mut border.base,
LineDisplayItemClass(ref mut line) => &mut line.base,
ClipDisplayItemClass(ref mut clip) => &mut clip.base,
PseudoDisplayItemClass(ref mut base) => &mut **base,
}
}
pub fn bounds(&self) -> Rect<Au> {
self.base().bounds
}
pub fn children<'a>(&'a self) -> DisplayItemIterator<'a> {
match *self {
ClipDisplayItemClass(ref clip) => ParentDisplayItemIterator(clip.children.list.iter()),
SolidColorDisplayItemClass(..) |
TextDisplayItemClass(..) |
ImageDisplayItemClass(..) |
BorderDisplayItemClass(..) |
LineDisplayItemClass(..) |
PseudoDisplayItemClass(..) => EmptyDisplayItemIterator,
}
}
/// Returns a mutable reference to the sublist contained within this display list item, if any.
fn mut_sublist<'a>(&'a mut self) -> Option<&'a mut DisplayList> {
match *self {
ClipDisplayItemClass(ref mut clip) => Some(&mut clip.children),
SolidColorDisplayItemClass(..) |
TextDisplayItemClass(..) |
ImageDisplayItemClass(..) |
BorderDisplayItemClass(..) |
LineDisplayItemClass(..) |
PseudoDisplayItemClass(..) => None,
}
}
pub fn debug_with_level(&self, level: uint) {
let mut indent = String::new();
for _ in range(0, level) {
indent.push_str("| ")
}
debug!("{}+ {}", indent, self);
for child in self.children() {
child.debug_with_level(level + 1);
}
}
}
impl fmt::Show for DisplayItem {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{} @ {} ({:x})",
match *self {
SolidColorDisplayItemClass(_) => "SolidColor",
TextDisplayItemClass(_) => "Text",
ImageDisplayItemClass(_) => "Image",
BorderDisplayItemClass(_) => "Border",
LineDisplayItemClass(_) => "Line",
ClipDisplayItemClass(_) => "Clip",
PseudoDisplayItemClass(_) => "Pseudo",
},
self.base().bounds,
self.base().node.id(),
)
}
}

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/. */
use display_list::{BorderDisplayItemClass, ClipDisplayItem, ClipDisplayItemClass, DisplayItem};
use display_list::{DisplayList, ImageDisplayItemClass, LineDisplayItemClass};
use display_list::{PseudoDisplayItemClass, SolidColorDisplayItemClass, TextDisplayItemClass};
use collections::dlist::DList;
use geom::rect::Rect;
use servo_util::geometry::Au;
use sync::Arc;
pub struct DisplayListOptimizer {
display_list: Arc<DisplayList>,
/// The visible rect in page coordinates.
visible_rect: Rect<Au>,
}
impl DisplayListOptimizer {
/// `visible_rect` specifies the visible rect in page coordinates.
pub fn new(display_list: Arc<DisplayList>, visible_rect: Rect<Au>) -> DisplayListOptimizer {
DisplayListOptimizer {
display_list: display_list,
visible_rect: visible_rect,
}
}
pub fn optimize(self) -> DisplayList {
self.process_display_list(&*self.display_list)
}
fn process_display_list(&self, display_list: &DisplayList) -> DisplayList {
let mut result = DList::new();
for item in display_list.iter() {
match self.process_display_item(item) {
None => {}
Some(display_item) => result.push(display_item),
}
}
DisplayList {
list: result,
}
}
fn process_display_item(&self, display_item: &DisplayItem) -> Option<DisplayItem> {
// Eliminate display items outside the visible region.
if !self.visible_rect.intersects(&display_item.base().bounds) {
return None
}
// Recur.
match *display_item {
ClipDisplayItemClass(ref clip) => {
let new_children = self.process_display_list(&clip.children);
if new_children.is_empty() {
return None
}
Some(ClipDisplayItemClass(box ClipDisplayItem {
base: clip.base.clone(),
children: new_children,
}))
}
BorderDisplayItemClass(_) | ImageDisplayItemClass(_) | LineDisplayItemClass(_) |
PseudoDisplayItemClass(_) | SolidColorDisplayItemClass(_) |
TextDisplayItemClass(_) => {
Some((*display_item).clone())
}
}
}
}

213
components/gfx/font.rs Normal file
View file

@ -0,0 +1,213 @@
/* 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 geom::{Point2D, Rect, Size2D};
use std::mem;
use std::string;
use std::rc::Rc;
use std::cell::RefCell;
use servo_util::cache::{Cache, HashCache};
use style::computed_values::{font_weight, font_style};
use sync::Arc;
use servo_util::geometry::Au;
use platform::font_context::FontContextHandle;
use platform::font::{FontHandle, FontTable};
use text::glyph::{GlyphStore, GlyphId};
use text::shaping::ShaperMethods;
use text::{Shaper, TextRun};
use font_template::FontTemplateDescriptor;
use platform::font_template::FontTemplateData;
// FontHandle encapsulates access to the platform's font API,
// e.g. quartz, FreeType. It provides access to metrics and tables
// needed by the text shaper as well as access to the underlying font
// resources needed by the graphics layer to draw glyphs.
pub trait FontHandleMethods {
fn new_from_template(fctx: &FontContextHandle, template: Arc<FontTemplateData>, pt_size: Option<f64>)
-> Result<Self,()>;
fn get_template(&self) -> Arc<FontTemplateData>;
fn family_name(&self) -> String;
fn face_name(&self) -> String;
fn is_italic(&self) -> bool;
fn boldness(&self) -> font_weight::T;
fn glyph_index(&self, codepoint: char) -> Option<GlyphId>;
fn glyph_h_advance(&self, GlyphId) -> Option<FractionalPixel>;
fn glyph_h_kerning(&self, GlyphId, GlyphId) -> FractionalPixel;
fn get_metrics(&self) -> FontMetrics;
fn get_table_for_tag(&self, FontTableTag) -> Option<FontTable>;
}
// Used to abstract over the shaper's choice of fixed int representation.
pub type FractionalPixel = f64;
pub type FontTableTag = u32;
pub trait FontTableTagConversions {
fn tag_to_str(&self) -> String;
}
impl FontTableTagConversions for FontTableTag {
fn tag_to_str(&self) -> String {
unsafe {
let reversed = string::raw::from_buf_len(mem::transmute(self), 4);
return String::from_chars([reversed.as_slice().char_at(3),
reversed.as_slice().char_at(2),
reversed.as_slice().char_at(1),
reversed.as_slice().char_at(0)]);
}
}
}
pub trait FontTableMethods {
fn with_buffer(&self, |*const u8, uint|);
}
#[deriving(Clone)]
pub struct FontMetrics {
pub underline_size: Au,
pub underline_offset: Au,
pub strikeout_size: Au,
pub strikeout_offset: Au,
pub leading: Au,
pub x_height: Au,
pub em_size: Au,
pub ascent: Au,
pub descent: Au,
pub max_advance: Au,
pub line_gap: Au,
}
// TODO(Issue #179): eventually this will be split into the specified
// and used font styles. specified contains uninterpreted CSS font
// property values, while 'used' is attached to gfx::Font to descript
// the instance's properties.
//
// For now, the cases are differentiated with a typedef
#[deriving(Clone, PartialEq)]
pub struct FontStyle {
pub pt_size: f64,
pub weight: font_weight::T,
pub style: font_style::T,
pub families: Vec<String>,
// TODO(Issue #198): font-stretch, text-decoration, font-variant, size-adjust
}
pub type SpecifiedFontStyle = FontStyle;
pub type UsedFontStyle = FontStyle;
pub struct Font {
pub handle: FontHandle,
pub metrics: FontMetrics,
pub descriptor: FontTemplateDescriptor,
pub pt_size: f64,
pub shaper: Option<Shaper>,
pub shape_cache: HashCache<String, Arc<GlyphStore>>,
pub glyph_advance_cache: HashCache<u32, FractionalPixel>,
}
impl Font {
pub fn shape_text(&mut self, text: String, is_whitespace: bool) -> Arc<GlyphStore> {
self.make_shaper();
let shaper = &self.shaper;
self.shape_cache.find_or_create(&text, |txt| {
let mut glyphs = GlyphStore::new(text.as_slice().char_len() as int, is_whitespace);
shaper.get_ref().shape_text(txt.as_slice(), &mut glyphs);
Arc::new(glyphs)
})
}
fn make_shaper<'a>(&'a mut self) -> &'a Shaper {
// fast path: already created a shaper
match self.shaper {
Some(ref shaper) => {
let s: &'a Shaper = shaper;
return s;
},
None => {}
}
let shaper = Shaper::new(self);
self.shaper = Some(shaper);
self.shaper.get_ref()
}
pub fn get_table_for_tag(&self, tag: FontTableTag) -> Option<FontTable> {
let result = self.handle.get_table_for_tag(tag);
let status = if result.is_some() { "Found" } else { "Didn't find" };
debug!("{:s} font table[{:s}] with family={}, face={}",
status, tag.tag_to_str(),
self.handle.family_name(), self.handle.face_name());
return result;
}
pub fn glyph_index(&self, codepoint: char) -> Option<GlyphId> {
self.handle.glyph_index(codepoint)
}
pub fn glyph_h_kerning(&mut self, first_glyph: GlyphId, second_glyph: GlyphId) -> FractionalPixel {
self.handle.glyph_h_kerning(first_glyph, second_glyph)
}
pub fn glyph_h_advance(&mut self, glyph: GlyphId) -> FractionalPixel {
let handle = &self.handle;
self.glyph_advance_cache.find_or_create(&glyph, |glyph| {
match handle.glyph_h_advance(*glyph) {
Some(adv) => adv,
None => 10f64 as FractionalPixel // FIXME: Need fallback strategy
}
})
}
}
pub struct FontGroup {
pub fonts: Vec<Rc<RefCell<Font>>>,
}
impl FontGroup {
pub fn new(fonts: Vec<Rc<RefCell<Font>>>) -> FontGroup {
FontGroup {
fonts: fonts
}
}
pub fn create_textrun(&self, text: String) -> TextRun {
assert!(self.fonts.len() > 0);
// TODO(Issue #177): Actually fall back through the FontGroup when a font is unsuitable.
TextRun::new(&mut *self.fonts[0].borrow_mut(), text.clone())
}
}
pub struct RunMetrics {
// may be negative due to negative width (i.e., kerning of '.' in 'P.T.')
pub advance_width: Au,
pub ascent: Au, // nonzero
pub descent: Au, // nonzero
// this bounding box is relative to the left origin baseline.
// so, bounding_box.position.y = -ascent
pub bounding_box: Rect<Au>
}
impl RunMetrics {
pub fn new(advance: Au, ascent: Au, descent: Au) -> RunMetrics {
let bounds = Rect(Point2D(Au(0), -ascent),
Size2D(advance, ascent + descent));
// TODO(Issue #125): support loose and tight bounding boxes; using the
// ascent+descent and advance is sometimes too generous and
// looking at actual glyph extents can yield a tighter box.
RunMetrics {
advance_width: advance,
bounding_box: bounds,
ascent: ascent,
descent: descent,
}
}
}

View file

@ -0,0 +1,276 @@
/* 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 platform::font_list::get_available_families;
use platform::font_list::get_variations_for_family;
use platform::font_list::get_last_resort_font_families;
use platform::font_context::FontContextHandle;
use std::collections::HashMap;
use sync::Arc;
use font_template::{FontTemplate, FontTemplateDescriptor};
use platform::font_template::FontTemplateData;
use servo_net::resource_task::{ResourceTask, load_whole_resource};
use url::Url;
/// A list of font templates that make up a given font family.
struct FontFamily {
templates: Vec<FontTemplate>,
}
impl FontFamily {
fn new() -> FontFamily {
FontFamily {
templates: vec!(),
}
}
/// Find a font in this family that matches a given desriptor.
fn find_font_for_style<'a>(&'a mut self, desc: &FontTemplateDescriptor, fctx: &FontContextHandle)
-> Option<Arc<FontTemplateData>> {
// TODO(Issue #189): optimize lookup for
// regular/bold/italic/bolditalic with fixed offsets and a
// static decision table for fallback between these values.
// TODO(Issue #190): if not in the fast path above, do
// expensive matching of weights, etc.
for template in self.templates.mut_iter() {
let maybe_template = template.get_if_matches(fctx, desc);
if maybe_template.is_some() {
return maybe_template;
}
}
// If a request is made for a font family that exists,
// pick the first valid font in the family if we failed
// to find an exact match for the descriptor.
for template in self.templates.mut_iter() {
let maybe_template = template.get();
if maybe_template.is_some() {
return maybe_template;
}
}
None
}
fn add_template(&mut self, identifier: &str, maybe_data: Option<Vec<u8>>) {
for template in self.templates.iter() {
if template.identifier() == identifier {
return;
}
}
let template = FontTemplate::new(identifier, maybe_data);
self.templates.push(template);
}
}
/// Commands that the FontContext sends to the font cache task.
pub enum Command {
GetFontTemplate(String, FontTemplateDescriptor, Sender<Reply>),
AddWebFont(String, Url, Sender<()>),
Exit(Sender<()>),
}
/// Reply messages sent from the font cache task to the FontContext caller.
pub enum Reply {
GetFontTemplateReply(Arc<FontTemplateData>),
}
/// The font cache task itself. It maintains a list of reference counted
/// font templates that are currently in use.
struct FontCache {
port: Receiver<Command>,
generic_fonts: HashMap<String, String>,
local_families: HashMap<String, FontFamily>,
web_families: HashMap<String, FontFamily>,
font_context: FontContextHandle,
resource_task: ResourceTask,
}
impl FontCache {
fn run(&mut self) {
loop {
let msg = self.port.recv();
match msg {
GetFontTemplate(family, descriptor, result) => {
let maybe_font_template = self.get_font_template(&family, &descriptor);
let font_template = match maybe_font_template {
Some(font_template) => font_template,
None => self.get_last_resort_template(&descriptor),
};
result.send(GetFontTemplateReply(font_template));
}
AddWebFont(family_name, url, result) => {
let maybe_resource = load_whole_resource(&self.resource_task, url.clone());
match maybe_resource {
Ok((_, bytes)) => {
if !self.web_families.contains_key(&family_name) {
let family = FontFamily::new();
self.web_families.insert(family_name.clone(), family);
}
let family = self.web_families.get_mut(&family_name);
family.add_template(format!("{}", url).as_slice(), Some(bytes));
},
Err(msg) => {
fail!("{}: url={}", msg, url);
}
}
result.send(());
}
Exit(result) => {
result.send(());
break;
}
}
}
}
fn refresh_local_families(&mut self) {
self.local_families.clear();
get_available_families(|family_name| {
if !self.local_families.contains_key(&family_name) {
let family = FontFamily::new();
self.local_families.insert(family_name, family);
}
});
}
fn transform_family(&self, family: &String) -> String {
match self.generic_fonts.find(family) {
None => family.to_string(),
Some(mapped_family) => (*mapped_family).clone()
}
}
fn find_font_in_local_family<'a>(&'a mut self, family_name: &String, desc: &FontTemplateDescriptor)
-> Option<Arc<FontTemplateData>> {
// TODO(Issue #188): look up localized font family names if canonical name not found
// look up canonical name
if self.local_families.contains_key(family_name) {
debug!("FontList: Found font family with name={:s}", family_name.to_string());
let s = self.local_families.get_mut(family_name);
if s.templates.len() == 0 {
get_variations_for_family(family_name.as_slice(), |path| {
s.add_template(path.as_slice(), None);
});
}
// TODO(Issue #192: handle generic font families, like 'serif' and 'sans-serif'.
// if such family exists, try to match style to a font
let result = s.find_font_for_style(desc, &self.font_context);
if result.is_some() {
return result;
}
None
} else {
debug!("FontList: Couldn't find font family with name={:s}", family_name.to_string());
None
}
}
fn find_font_in_web_family<'a>(&'a mut self, family_name: &String, desc: &FontTemplateDescriptor)
-> Option<Arc<FontTemplateData>> {
if self.web_families.contains_key(family_name) {
let family = self.web_families.get_mut(family_name);
let maybe_font = family.find_font_for_style(desc, &self.font_context);
maybe_font
} else {
None
}
}
fn get_font_template(&mut self, family: &String, desc: &FontTemplateDescriptor) -> Option<Arc<FontTemplateData>> {
let transformed_family_name = self.transform_family(family);
let mut maybe_template = self.find_font_in_web_family(&transformed_family_name, desc);
if maybe_template.is_none() {
maybe_template = self.find_font_in_local_family(&transformed_family_name, desc);
}
maybe_template
}
fn get_last_resort_template(&mut self, desc: &FontTemplateDescriptor) -> Arc<FontTemplateData> {
let last_resort = get_last_resort_font_families();
for family in last_resort.iter() {
let maybe_font_in_family = self.find_font_in_local_family(family, desc);
if maybe_font_in_family.is_some() {
return maybe_font_in_family.unwrap();
}
}
fail!("Unable to find any fonts that match (do you have fallback fonts installed?)");
}
}
/// The public interface to the font cache task, used exclusively by
/// the per-thread/task FontContext structures.
#[deriving(Clone)]
pub struct FontCacheTask {
chan: Sender<Command>,
}
impl FontCacheTask {
pub fn new(resource_task: ResourceTask) -> FontCacheTask {
let (chan, port) = channel();
spawn(proc() {
// TODO: Allow users to specify these.
let mut generic_fonts = HashMap::with_capacity(5);
generic_fonts.insert("serif".to_string(), "Times New Roman".to_string());
generic_fonts.insert("sans-serif".to_string(), "Arial".to_string());
generic_fonts.insert("cursive".to_string(), "Apple Chancery".to_string());
generic_fonts.insert("fantasy".to_string(), "Papyrus".to_string());
generic_fonts.insert("monospace".to_string(), "Menlo".to_string());
let mut cache = FontCache {
port: port,
generic_fonts: generic_fonts,
local_families: HashMap::new(),
web_families: HashMap::new(),
font_context: FontContextHandle::new(),
resource_task: resource_task,
};
cache.refresh_local_families();
cache.run();
});
FontCacheTask {
chan: chan,
}
}
pub fn get_font_template(&self, family: String, desc: FontTemplateDescriptor)
-> Arc<FontTemplateData> {
let (response_chan, response_port) = channel();
self.chan.send(GetFontTemplate(family, desc, response_chan));
let reply = response_port.recv();
match reply {
GetFontTemplateReply(data) => {
data
}
}
}
pub fn add_web_font(&self, family: String, url: Url) {
let (response_chan, response_port) = channel();
self.chan.send(AddWebFont(family, url, response_chan));
response_port.recv();
}
pub fn exit(&self) {
let (response_chan, response_port) = channel();
self.chan.send(Exit(response_chan));
response_port.recv();
}
}

View file

@ -0,0 +1,148 @@
/* 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 font::{Font, FontGroup};
use font::SpecifiedFontStyle;
use platform::font_context::FontContextHandle;
use style::computed_values::font_style;
use font_cache_task::FontCacheTask;
use font_template::FontTemplateDescriptor;
use platform::font_template::FontTemplateData;
use font::FontHandleMethods;
use platform::font::FontHandle;
use servo_util::cache::HashCache;
use std::rc::{Rc, Weak};
use std::cell::RefCell;
use sync::Arc;
use azure::AzFloat;
use azure::azure_hl::BackendType;
use azure::scaled_font::ScaledFont;
#[cfg(target_os="linux")]
#[cfg(target_os="android")]
use azure::scaled_font::FontData;
#[cfg(target_os="linux")]
#[cfg(target_os="android")]
fn create_scaled_font(backend: BackendType, template: &Arc<FontTemplateData>, pt_size: f64) -> ScaledFont {
ScaledFont::new(backend, FontData(&template.bytes), pt_size as AzFloat)
}
#[cfg(target_os="macos")]
fn create_scaled_font(backend: BackendType, template: &Arc<FontTemplateData>, pt_size: f64) -> ScaledFont {
let cgfont = template.ctfont.get_ref().copy_to_CGFont();
ScaledFont::new(backend, &cgfont, pt_size as AzFloat)
}
/// A cached azure font (per render task) that
/// can be shared by multiple text runs.
struct RenderFontCacheEntry {
pt_size: f64,
identifier: String,
font: Rc<RefCell<ScaledFont>>,
}
/// The FontContext represents the per-thread/task state necessary for
/// working with fonts. It is the public API used by the layout and
/// render code. It talks directly to the font cache task where
/// required.
pub struct FontContext {
platform_handle: FontContextHandle,
font_cache_task: FontCacheTask,
/// Weak reference as the layout FontContext is persistent.
layout_font_cache: Vec<Weak<RefCell<Font>>>,
/// Strong reference as the render FontContext is (for now) recycled
/// per frame. TODO: Make this weak when incremental redraw is done.
render_font_cache: Vec<RenderFontCacheEntry>,
}
impl FontContext {
pub fn new(font_cache_task: FontCacheTask) -> FontContext {
let handle = FontContextHandle::new();
FontContext {
platform_handle: handle,
font_cache_task: font_cache_task,
layout_font_cache: vec!(),
render_font_cache: vec!(),
}
}
/// Create a font for use in layout calculations.
fn create_layout_font(&self, template: Arc<FontTemplateData>,
descriptor: FontTemplateDescriptor, pt_size: f64) -> Font {
let handle: FontHandle = FontHandleMethods::new_from_template(&self.platform_handle, template, Some(pt_size)).unwrap();
let metrics = handle.get_metrics();
Font {
handle: handle,
shaper: None,
descriptor: descriptor,
pt_size: pt_size,
metrics: metrics,
shape_cache: HashCache::new(),
glyph_advance_cache: HashCache::new(),
}
}
/// Create a group of fonts for use in layout calculations. May return
/// a cached font if this font instance has already been used by
/// this context.
pub fn get_layout_font_group_for_style(&mut self, style: &SpecifiedFontStyle) -> FontGroup {
// Remove all weak pointers that have been dropped.
self.layout_font_cache.retain(|maybe_font| {
maybe_font.upgrade().is_some()
});
let mut fonts: Vec<Rc<RefCell<Font>>> = vec!();
for family in style.families.iter() {
let desc = FontTemplateDescriptor::new(style.weight, style.style == font_style::italic);
// GWTODO: Check on real pages if this is faster as Vec() or HashMap().
let mut cache_hit = false;
for maybe_cached_font in self.layout_font_cache.iter() {
let cached_font = maybe_cached_font.upgrade().unwrap();
if cached_font.borrow().descriptor == desc {
fonts.push(cached_font.clone());
cache_hit = true;
break;
}
}
if !cache_hit {
let font_template = self.font_cache_task.get_font_template(family.clone(), desc.clone());
let layout_font = Rc::new(RefCell::new(self.create_layout_font(font_template, desc.clone(), style.pt_size)));
self.layout_font_cache.push(layout_font.downgrade());
fonts.push(layout_font);
}
}
FontGroup::new(fonts)
}
/// Create a render font for use with azure. May return a cached
/// reference if already used by this font context.
pub fn get_render_font_from_template(&mut self, template: &Arc<FontTemplateData>, pt_size: f64, backend: BackendType) -> Rc<RefCell<ScaledFont>> {
for cached_font in self.render_font_cache.iter() {
if cached_font.pt_size == pt_size &&
cached_font.identifier == template.identifier {
return cached_font.font.clone();
}
}
let render_font = Rc::new(RefCell::new(create_scaled_font(backend, template, pt_size)));
self.render_font_cache.push(RenderFontCacheEntry{
font: render_font.clone(),
pt_size: pt_size,
identifier: template.identifier.clone(),
});
render_font
}
}

View file

@ -0,0 +1,157 @@
/* 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 style::computed_values::font_weight;
use platform::font_context::FontContextHandle;
use platform::font::FontHandle;
use platform::font_template::FontTemplateData;
use sync::{Arc, Weak};
use font::FontHandleMethods;
/// Describes how to select a font from a given family.
/// This is very basic at the moment and needs to be
/// expanded or refactored when we support more of the
/// font styling parameters.
#[deriving(Clone)]
pub struct FontTemplateDescriptor {
pub weight: font_weight::T,
pub italic: bool,
}
impl FontTemplateDescriptor {
pub fn new(weight: font_weight::T, italic: bool) -> FontTemplateDescriptor {
FontTemplateDescriptor {
weight: weight,
italic: italic,
}
}
}
impl PartialEq for FontTemplateDescriptor {
fn eq(&self, other: &FontTemplateDescriptor) -> bool {
self.weight.is_bold() == other.weight.is_bold() &&
self.italic == other.italic
}
}
/// This describes all the information needed to create
/// font instance handles. It contains a unique
/// FontTemplateData structure that is platform specific.
pub struct FontTemplate {
identifier: String,
descriptor: Option<FontTemplateDescriptor>,
weak_ref: Option<Weak<FontTemplateData>>,
strong_ref: Option<Arc<FontTemplateData>>, // GWTODO: Add code path to unset the strong_ref for web fonts!
is_valid: bool,
}
/// Holds all of the template information for a font that
/// is common, regardless of the number of instances of
/// this font handle per thread.
impl FontTemplate {
pub fn new(identifier: &str, maybe_bytes: Option<Vec<u8>>) -> FontTemplate {
let maybe_data = match maybe_bytes {
Some(_) => Some(FontTemplateData::new(identifier, maybe_bytes)),
None => None,
};
let maybe_strong_ref = match maybe_data {
Some(data) => Some(Arc::new(data)),
None => None,
};
let maybe_weak_ref = match maybe_strong_ref {
Some(ref strong_ref) => Some(strong_ref.downgrade()),
None => None,
};
FontTemplate {
identifier: identifier.to_string(),
descriptor: None,
weak_ref: maybe_weak_ref,
strong_ref: maybe_strong_ref,
is_valid: true,
}
}
pub fn identifier<'a>(&'a self) -> &'a str {
self.identifier.as_slice()
}
/// Get the data for creating a font if it matches a given descriptor.
pub fn get_if_matches(&mut self, fctx: &FontContextHandle,
requested_desc: &FontTemplateDescriptor) -> Option<Arc<FontTemplateData>> {
// The font template data can be unloaded when nothing is referencing
// it (via the Weak reference to the Arc above). However, if we have
// already loaded a font, store the style information about it separately,
// so that we can do font matching against it again in the future
// without having to reload the font (unless it is an actual match).
match self.descriptor {
Some(actual_desc) => {
if *requested_desc == actual_desc {
Some(self.get_data())
} else {
None
}
},
None => {
if self.is_valid {
let data = self.get_data();
let handle: Result<FontHandle, ()> = FontHandleMethods::new_from_template(fctx, data.clone(), None);
match handle {
Ok(handle) => {
let actual_desc = FontTemplateDescriptor::new(handle.boldness(),
handle.is_italic());
let desc_match = actual_desc == *requested_desc;
self.descriptor = Some(actual_desc);
self.is_valid = true;
if desc_match {
Some(data)
} else {
None
}
}
Err(()) => {
self.is_valid = false;
debug!("Unable to create a font from template {}", self.identifier);
None
}
}
} else {
None
}
}
}
}
/// Get the data for creating a font.
pub fn get(&mut self) -> Option<Arc<FontTemplateData>> {
match self.is_valid {
true => Some(self.get_data()),
false => None
}
}
/// Get the font template data. If any strong references still
/// exist, it will return a clone, otherwise it will load the
/// font data and store a weak reference to it internally.
pub fn get_data(&mut self) -> Arc<FontTemplateData> {
let maybe_data = match self.weak_ref {
Some(ref data) => data.upgrade(),
None => None,
};
match maybe_data {
Some(data) => data,
None => {
assert!(self.strong_ref.is_none());
let template_data = Arc::new(FontTemplateData::new(self.identifier.as_slice(), None));
self.weak_ref = Some(template_data.downgrade());
template_data
}
}
}
}

72
components/gfx/lib.rs Normal file
View file

@ -0,0 +1,72 @@
/* 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/. */
#![feature(globs, macro_rules, phase, unsafe_destructor)]
#![feature(phase)]
#[phase(plugin, link)]
extern crate log;
extern crate debug;
extern crate azure;
extern crate collections;
extern crate geom;
extern crate layers;
extern crate libc;
extern crate native;
extern crate rustrt;
extern crate stb_image;
extern crate png;
extern crate serialize;
#[phase(plugin)]
extern crate servo_macros = "macros";
extern crate servo_net = "net";
#[phase(plugin, link)]
extern crate servo_util = "util";
extern crate servo_msg = "msg";
extern crate style;
extern crate sync;
extern crate url;
// Eventually we would like the shaper to be pluggable, as many operating systems have their own
// shapers. For now, however, this is a hard dependency.
extern crate harfbuzz;
// Linux and Android-specific library dependencies
#[cfg(target_os="linux")] #[cfg(target_os="android")] extern crate fontconfig;
#[cfg(target_os="linux")] #[cfg(target_os="android")] extern crate freetype;
// Mac OS-specific library dependencies
#[cfg(target_os="macos")] extern crate core_foundation;
#[cfg(target_os="macos")] extern crate core_graphics;
#[cfg(target_os="macos")] extern crate core_text;
pub use render_context::RenderContext;
// Private rendering modules
mod render_context;
// Rendering
pub mod color;
#[path="display_list/mod.rs"]
pub mod display_list;
pub mod render_task;
// Fonts
pub mod font;
pub mod font_context;
pub mod font_cache_task;
pub mod font_template;
// Misc.
mod buffer_map;
// Platform-specific implementations.
#[path="platform/mod.rs"]
pub mod platform;
// Text
#[path = "text/mod.rs"]
pub mod text;

View file

@ -0,0 +1,297 @@
/* 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/. */
extern crate freetype;
use font::{FontHandleMethods, FontMetrics, FontTableMethods};
use font::{FontTableTag, FractionalPixel};
use servo_util::geometry::Au;
use servo_util::geometry;
use platform::font_context::FontContextHandle;
use text::glyph::GlyphId;
use text::util::{float_to_fixed, fixed_to_float};
use style::computed_values::font_weight;
use platform::font_template::FontTemplateData;
use freetype::freetype::{FT_Get_Char_Index, FT_Get_Postscript_Name};
use freetype::freetype::{FT_Load_Glyph, FT_Set_Char_Size};
use freetype::freetype::{FT_Get_Kerning, FT_Get_Sfnt_Table};
use freetype::freetype::{FT_New_Memory_Face, FT_Done_Face};
use freetype::freetype::{FTErrorMethods, FT_F26Dot6, FT_Face, FT_FaceRec};
use freetype::freetype::{FT_GlyphSlot, FT_Library, FT_Long, FT_ULong};
use freetype::freetype::{FT_KERNING_DEFAULT, FT_STYLE_FLAG_ITALIC, FT_STYLE_FLAG_BOLD};
use freetype::freetype::{FT_SizeRec, FT_UInt, FT_Size_Metrics, struct_FT_Vector_};
use freetype::freetype::{ft_sfnt_os2};
use freetype::tt_os2::TT_OS2;
use std::mem;
use std::ptr;
use std::string;
use sync::Arc;
fn float_to_fixed_ft(f: f64) -> i32 {
float_to_fixed(6, f)
}
fn fixed_to_float_ft(f: i32) -> f64 {
fixed_to_float(6, f)
}
pub struct FontTable;
impl FontTableMethods for FontTable {
fn with_buffer(&self, _blk: |*const u8, uint|) {
fail!()
}
}
pub struct FontHandle {
// The font binary. This must stay valid for the lifetime of the font,
// if the font is created using FT_Memory_Face.
pub font_data: Arc<FontTemplateData>,
pub face: FT_Face,
pub handle: FontContextHandle
}
#[unsafe_destructor]
impl Drop for FontHandle {
fn drop(&mut self) {
assert!(self.face.is_not_null());
unsafe {
if !FT_Done_Face(self.face).succeeded() {
fail!("FT_Done_Face failed");
}
}
}
}
impl FontHandleMethods for FontHandle {
fn new_from_template(fctx: &FontContextHandle,
template: Arc<FontTemplateData>,
pt_size: Option<f64>)
-> Result<FontHandle, ()> {
let ft_ctx: FT_Library = fctx.ctx.ctx;
if ft_ctx.is_null() { return Err(()); }
let bytes = &template.deref().bytes;
let face_result = create_face_from_buffer(ft_ctx, bytes.as_ptr(), bytes.len(), pt_size);
// TODO: this could be more simply written as result::chain
// and moving buf into the struct ctor, but cant' move out of
// captured binding.
return match face_result {
Ok(face) => {
let handle = FontHandle {
face: face,
font_data: template.clone(),
handle: fctx.clone()
};
Ok(handle)
}
Err(()) => Err(())
};
fn create_face_from_buffer(lib: FT_Library, cbuf: *const u8, cbuflen: uint, pt_size: Option<f64>)
-> Result<FT_Face, ()> {
unsafe {
let mut face: FT_Face = ptr::mut_null();
let face_index = 0 as FT_Long;
let result = FT_New_Memory_Face(lib, cbuf, cbuflen as FT_Long,
face_index, &mut face);
if !result.succeeded() || face.is_null() {
return Err(());
}
match pt_size {
Some(s) => {
match FontHandle::set_char_size(face, s) {
Ok(_) => Ok(face),
Err(_) => Err(()),
}
}
None => Ok(face),
}
}
}
}
fn get_template(&self) -> Arc<FontTemplateData> {
self.font_data.clone()
}
fn family_name(&self) -> String {
unsafe { string::raw::from_buf(&*(*self.face).family_name as *const i8 as *const u8) }
}
fn face_name(&self) -> String {
unsafe { string::raw::from_buf(&*FT_Get_Postscript_Name(self.face) as *const i8 as *const u8) }
}
fn is_italic(&self) -> bool {
unsafe { (*self.face).style_flags & FT_STYLE_FLAG_ITALIC != 0 }
}
fn boldness(&self) -> font_weight::T {
let default_weight = font_weight::Weight400;
if unsafe { (*self.face).style_flags & FT_STYLE_FLAG_BOLD == 0 } {
default_weight
} else {
unsafe {
let os2 = FT_Get_Sfnt_Table(self.face, ft_sfnt_os2) as *mut TT_OS2;
let valid = os2.is_not_null() && (*os2).version != 0xffff;
if valid {
let weight =(*os2).usWeightClass;
match weight {
1 | 100..199 => font_weight::Weight100,
2 | 200..299 => font_weight::Weight200,
3 | 300..399 => font_weight::Weight300,
4 | 400..499 => font_weight::Weight400,
5 | 500..599 => font_weight::Weight500,
6 | 600..699 => font_weight::Weight600,
7 | 700..799 => font_weight::Weight700,
8 | 800..899 => font_weight::Weight800,
9 | 900..999 => font_weight::Weight900,
_ => default_weight
}
} else {
default_weight
}
}
}
}
fn glyph_index(&self,
codepoint: char) -> Option<GlyphId> {
assert!(self.face.is_not_null());
unsafe {
let idx = FT_Get_Char_Index(self.face, codepoint as FT_ULong);
return if idx != 0 as FT_UInt {
Some(idx as GlyphId)
} else {
debug!("Invalid codepoint: {}", codepoint);
None
};
}
}
fn glyph_h_kerning(&self, first_glyph: GlyphId, second_glyph: GlyphId)
-> FractionalPixel {
assert!(self.face.is_not_null());
let mut delta = struct_FT_Vector_ { x: 0, y: 0 };
unsafe {
FT_Get_Kerning(self.face, first_glyph, second_glyph, FT_KERNING_DEFAULT, &mut delta);
}
fixed_to_float_ft(delta.x as i32)
}
fn glyph_h_advance(&self,
glyph: GlyphId) -> Option<FractionalPixel> {
assert!(self.face.is_not_null());
unsafe {
let res = FT_Load_Glyph(self.face, glyph as FT_UInt, 0);
if res.succeeded() {
let void_glyph = (*self.face).glyph;
let slot: FT_GlyphSlot = mem::transmute(void_glyph);
assert!(slot.is_not_null());
debug!("metrics: {:?}", (*slot).metrics);
let advance = (*slot).metrics.horiAdvance;
debug!("h_advance for {} is {}", glyph, advance);
let advance = advance as i32;
return Some(fixed_to_float_ft(advance) as FractionalPixel);
} else {
debug!("Unable to load glyph {}. reason: {}", glyph, res);
return None;
}
}
}
fn get_metrics(&self) -> FontMetrics {
/* TODO(Issue #76): complete me */
let face = self.get_face_rec();
let underline_size = self.font_units_to_au(face.underline_thickness as f64);
let underline_offset = self.font_units_to_au(face.underline_position as f64);
let em_size = self.font_units_to_au(face.units_per_EM as f64);
let ascent = self.font_units_to_au(face.ascender as f64);
let descent = self.font_units_to_au(face.descender as f64);
let max_advance = self.font_units_to_au(face.max_advance_width as f64);
// 'leading' is supposed to be the vertical distance between two baselines,
// reflected by the height attibute in freetype. On OS X (w/ CTFont),
// leading represents the distance between the bottom of a line descent to
// the top of the next line's ascent or: (line_height - ascent - descent),
// see http://stackoverflow.com/a/5635981 for CTFont implementation.
// Convert using a formular similar to what CTFont returns for consistency.
let height = self.font_units_to_au(face.height as f64);
let leading = height - (ascent + descent);
let mut strikeout_size = geometry::from_pt(0.0);
let mut strikeout_offset = geometry::from_pt(0.0);
let mut x_height = geometry::from_pt(0.0);
unsafe {
let os2 = FT_Get_Sfnt_Table(face, ft_sfnt_os2) as *mut TT_OS2;
let valid = os2.is_not_null() && (*os2).version != 0xffff;
if valid {
strikeout_size = self.font_units_to_au((*os2).yStrikeoutSize as f64);
strikeout_offset = self.font_units_to_au((*os2).yStrikeoutPosition as f64);
x_height = self.font_units_to_au((*os2).sxHeight as f64);
}
}
let metrics = FontMetrics {
underline_size: underline_size,
underline_offset: underline_offset,
strikeout_size: strikeout_size,
strikeout_offset: strikeout_offset,
leading: leading,
x_height: x_height,
em_size: em_size,
ascent: ascent,
descent: -descent, // linux font's seem to use the opposite sign from mac
max_advance: max_advance,
line_gap: height,
};
debug!("Font metrics (@{:f} pt): {:?}", geometry::to_pt(em_size), metrics);
return metrics;
}
fn get_table_for_tag(&self, _: FontTableTag) -> Option<FontTable> {
None
}
}
impl<'a> FontHandle {
fn set_char_size(face: FT_Face, pt_size: f64) -> Result<(), ()>{
let char_width = float_to_fixed_ft(pt_size) as FT_F26Dot6;
let char_height = float_to_fixed_ft(pt_size) as FT_F26Dot6;
let h_dpi = 72;
let v_dpi = 72;
unsafe {
let result = FT_Set_Char_Size(face, char_width, char_height, h_dpi, v_dpi);
if result.succeeded() { Ok(()) } else { Err(()) }
}
}
fn get_face_rec(&'a self) -> &'a mut FT_FaceRec {
unsafe {
&mut (*self.face)
}
}
fn font_units_to_au(&self, value: f64) -> Au {
let face = self.get_face_rec();
// face.size is a *c_void in the bindings, presumably to avoid
// recursive structural types
let size: &FT_SizeRec = unsafe { mem::transmute(&(*face.size)) };
let metrics: &FT_Size_Metrics = &(*size).metrics;
let em_size = face.units_per_EM as f64;
let x_scale = (metrics.x_ppem as f64) / em_size as f64;
// If this isn't true then we're scaling one of the axes wrong
assert!(metrics.x_ppem == metrics.y_ppem);
return geometry::from_frac_px(value * x_scale);
}
}

View file

@ -0,0 +1,82 @@
/* 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 freetype::freetype::FTErrorMethods;
use freetype::freetype::FT_Add_Default_Modules;
use freetype::freetype::FT_Done_FreeType;
use freetype::freetype::FT_Library;
use freetype::freetype::FT_Memory;
use freetype::freetype::FT_New_Library;
use freetype::freetype::struct_FT_MemoryRec_;
use std::ptr;
use std::rc::Rc;
use libc;
use libc::{c_void, c_long, size_t, malloc};
use std::mem;
extern fn ft_alloc(_mem: FT_Memory, size: c_long) -> *mut c_void {
unsafe {
let ptr = libc::malloc(size as size_t);
ptr as *mut c_void
}
}
extern fn ft_free(_mem: FT_Memory, block: *mut c_void) {
unsafe {
libc::free(block);
}
}
extern fn ft_realloc(_mem: FT_Memory, _cur_size: c_long, new_size: c_long, block: *mut c_void) -> *mut c_void {
unsafe {
let ptr = libc::realloc(block, new_size as size_t);
ptr as *mut c_void
}
}
#[deriving(Clone)]
pub struct FreeTypeLibraryHandle {
pub ctx: FT_Library,
}
#[deriving(Clone)]
pub struct FontContextHandle {
pub ctx: Rc<FreeTypeLibraryHandle>,
}
impl Drop for FreeTypeLibraryHandle {
fn drop(&mut self) {
assert!(self.ctx.is_not_null());
unsafe { FT_Done_FreeType(self.ctx) };
}
}
impl FontContextHandle {
pub fn new() -> FontContextHandle {
unsafe {
let ptr = libc::malloc(mem::size_of::<struct_FT_MemoryRec_>() as size_t);
let allocator: &mut struct_FT_MemoryRec_ = mem::transmute(ptr);
ptr::write(allocator, struct_FT_MemoryRec_ {
user: ptr::mut_null(),
alloc: ft_alloc,
free: ft_free,
realloc: ft_realloc,
});
let mut ctx: FT_Library = ptr::mut_null();
let result = FT_New_Library(ptr as FT_Memory, &mut ctx);
if !result.succeeded() { fail!("Unable to initialize FreeType library"); }
FT_Add_Default_Modules(ctx);
FontContextHandle {
ctx: Rc::new(FreeTypeLibraryHandle { ctx: ctx }),
}
}
}
}

View file

@ -0,0 +1,115 @@
/* 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/. */
#![allow(uppercase_variables)]
extern crate freetype;
extern crate fontconfig;
use fontconfig::fontconfig::{FcChar8, FcResultMatch, FcSetSystem};
use fontconfig::fontconfig::{
FcConfigGetCurrent, FcConfigGetFonts, FcPatternGetString,
FcPatternDestroy, FcFontSetDestroy,
FcPatternCreate, FcPatternAddString,
FcFontSetList, FcObjectSetCreate, FcObjectSetDestroy,
FcObjectSetAdd, FcPatternGetInteger
};
use libc;
use libc::c_int;
use std::ptr;
use std::string;
pub fn get_available_families(callback: |String|) {
unsafe {
let config = FcConfigGetCurrent();
let fontSet = FcConfigGetFonts(config, FcSetSystem);
for i in range(0, (*fontSet).nfont as int) {
let font = (*fontSet).fonts.offset(i);
let mut family: *mut FcChar8 = ptr::mut_null();
let mut v: c_int = 0;
let mut FC_FAMILY_C = "family".to_c_str();
let FC_FAMILY = FC_FAMILY_C.as_mut_ptr();
while FcPatternGetString(*font, FC_FAMILY, v, &mut family) == FcResultMatch {
let family_name = string::raw::from_buf(family as *const i8 as *const u8);
callback(family_name);
v += 1;
}
}
}
}
pub fn get_variations_for_family(family_name: &str, callback: |String|) {
debug!("getting variations for {}", family_name);
unsafe {
let config = FcConfigGetCurrent();
let mut font_set = FcConfigGetFonts(config, FcSetSystem);
let font_set_array_ptr = &mut font_set;
let pattern = FcPatternCreate();
assert!(pattern.is_not_null());
let mut FC_FAMILY_C = "family".to_c_str();
let FC_FAMILY = FC_FAMILY_C.as_mut_ptr();
let mut family_name_c = family_name.to_c_str();
let family_name = family_name_c.as_mut_ptr();
let ok = FcPatternAddString(pattern, FC_FAMILY, family_name as *mut FcChar8);
assert!(ok != 0);
let object_set = FcObjectSetCreate();
assert!(object_set.is_not_null());
let mut FC_FILE_C = "file".to_c_str();
let FC_FILE = FC_FILE_C.as_mut_ptr();
FcObjectSetAdd(object_set, FC_FILE);
let mut FC_INDEX_C = "index".to_c_str();
let FC_INDEX = FC_INDEX_C.as_mut_ptr();
FcObjectSetAdd(object_set, FC_INDEX);
let matches = FcFontSetList(config, font_set_array_ptr, 1, pattern, object_set);
debug!("found {} variations", (*matches).nfont);
for i in range(0, (*matches).nfont as int) {
let font = (*matches).fonts.offset(i);
let mut FC_FILE_C = "file".to_c_str();
let FC_FILE = FC_FILE_C.as_mut_ptr();
let mut file: *mut FcChar8 = ptr::mut_null();
let file = if FcPatternGetString(*font, FC_FILE, 0, &mut file) == FcResultMatch {
string::raw::from_buf(file as *const i8 as *const u8)
} else {
fail!();
};
let mut FC_INDEX_C = "index".to_c_str();
let FC_INDEX = FC_INDEX_C.as_mut_ptr();
let mut index: libc::c_int = 0;
let index = if FcPatternGetInteger(*font, FC_INDEX, 0, &mut index) == FcResultMatch {
index
} else {
fail!();
};
debug!("variation file: {}", file);
debug!("variation index: {}", index);
callback(file);
}
FcFontSetDestroy(matches);
FcPatternDestroy(pattern);
FcObjectSetDestroy(object_set);
}
}
#[cfg(target_os="linux")]
pub fn get_last_resort_font_families() -> Vec<String> {
vec!(
"Fira Sans".to_string(),
"DejaVu Sans".to_string(),
"Arial".to_string()
)
}
#[cfg(target_os="android")]
pub fn get_last_resort_font_families() -> Vec<String> {
vec!("Roboto".to_string())
}

View file

@ -0,0 +1,35 @@
/* 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 std::io;
use std::io::File;
/// Platform specific font representation for Linux.
/// The identifier is an absolute path, and the bytes
/// field is the loaded data that can be passed to
/// freetype and azure directly.
pub struct FontTemplateData {
pub bytes: Vec<u8>,
pub identifier: String,
}
impl FontTemplateData {
pub fn new(identifier: &str, font_data: Option<Vec<u8>>) -> FontTemplateData {
let bytes = match font_data {
Some(bytes) => {
bytes
},
None => {
// TODO: Handle file load failure!
let mut file = File::open_mode(&Path::new(identifier), io::Open, io::Read).unwrap();
file.read_to_end().unwrap()
},
};
FontTemplateData {
bytes: bytes,
identifier: identifier.to_string(),
}
}
}

View file

@ -0,0 +1,185 @@
/* 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/. */
/// Implementation of Quartz (CoreGraphics) fonts.
extern crate core_foundation;
extern crate core_graphics;
extern crate core_text;
use font::{FontHandleMethods, FontMetrics, FontTableMethods};
use font::FontTableTag;
use font::FractionalPixel;
use servo_util::geometry::{Au, px_to_pt};
use servo_util::geometry;
use platform::macos::font_context::FontContextHandle;
use text::glyph::GlyphId;
use style::computed_values::font_weight;
use platform::font_template::FontTemplateData;
use core_foundation::base::CFIndex;
use core_foundation::data::CFData;
use core_foundation::string::UniChar;
use core_graphics::font::CGGlyph;
use core_graphics::geometry::CGRect;
use core_text::font::CTFont;
use core_text::font_descriptor::{SymbolicTraitAccessors, TraitAccessors};
use core_text::font_descriptor::{kCTFontDefaultOrientation};
use std::ptr;
use sync::Arc;
pub struct FontTable {
data: CFData,
}
// Noncopyable.
impl Drop for FontTable {
fn drop(&mut self) {}
}
impl FontTable {
pub fn wrap(data: CFData) -> FontTable {
FontTable { data: data }
}
}
impl FontTableMethods for FontTable {
fn with_buffer(&self, blk: |*const u8, uint|) {
blk(self.data.bytes().as_ptr(), self.data.len() as uint);
}
}
pub struct FontHandle {
pub font_data: Arc<FontTemplateData>,
pub ctfont: CTFont,
}
impl FontHandleMethods for FontHandle {
fn new_from_template(_fctx: &FontContextHandle,
template: Arc<FontTemplateData>,
pt_size: Option<f64>)
-> Result<FontHandle, ()> {
let size = match pt_size {
Some(s) => s,
None => 0.0
};
match template.ctfont {
Some(ref ctfont) => {
Ok(FontHandle {
font_data: template.clone(),
ctfont: ctfont.clone_with_font_size(size),
})
}
None => {
Err(())
}
}
}
fn get_template(&self) -> Arc<FontTemplateData> {
self.font_data.clone()
}
fn family_name(&self) -> String {
self.ctfont.family_name()
}
fn face_name(&self) -> String {
self.ctfont.face_name()
}
fn is_italic(&self) -> bool {
self.ctfont.symbolic_traits().is_italic()
}
fn boldness(&self) -> font_weight::T {
// -1.0 to 1.0
let normalized = self.ctfont.all_traits().normalized_weight();
// 0.0 to 9.0
let normalized = (normalized + 1.0) / 2.0 * 9.0;
if normalized < 1.0 { return font_weight::Weight100; }
if normalized < 2.0 { return font_weight::Weight200; }
if normalized < 3.0 { return font_weight::Weight300; }
if normalized < 4.0 { return font_weight::Weight400; }
if normalized < 5.0 { return font_weight::Weight500; }
if normalized < 6.0 { return font_weight::Weight600; }
if normalized < 7.0 { return font_weight::Weight700; }
if normalized < 8.0 { return font_weight::Weight800; }
return font_weight::Weight900;
}
fn glyph_index(&self, codepoint: char) -> Option<GlyphId> {
let characters: [UniChar, ..1] = [codepoint as UniChar];
let mut glyphs: [CGGlyph, ..1] = [0 as CGGlyph];
let count: CFIndex = 1;
let result = self.ctfont.get_glyphs_for_characters(&characters[0],
&mut glyphs[0],
count);
if !result {
// No glyph for this character
return None;
}
assert!(glyphs[0] != 0); // FIXME: error handling
return Some(glyphs[0] as GlyphId);
}
fn glyph_h_kerning(&self, _first_glyph: GlyphId, _second_glyph: GlyphId)
-> FractionalPixel {
// TODO: Implement on mac
0.0
}
fn glyph_h_advance(&self, glyph: GlyphId) -> Option<FractionalPixel> {
let glyphs = [glyph as CGGlyph];
let advance = self.ctfont.get_advances_for_glyphs(kCTFontDefaultOrientation,
&glyphs[0],
ptr::mut_null(),
1);
Some(advance as FractionalPixel)
}
fn get_metrics(&self) -> FontMetrics {
let bounding_rect: CGRect = self.ctfont.bounding_box();
let ascent = self.ctfont.ascent() as f64;
let descent = self.ctfont.descent() as f64;
let em_size = Au::from_frac_px(self.ctfont.pt_size() as f64);
let leading = self.ctfont.leading() as f64;
let scale = px_to_pt(self.ctfont.pt_size() as f64) / (ascent + descent);
let line_gap = (ascent + descent + leading + 0.5).floor();
let metrics = FontMetrics {
underline_size: Au::from_pt(self.ctfont.underline_thickness() as f64),
// TODO(Issue #201): underline metrics are not reliable. Have to pull out of font table
// directly.
//
// see also: https://bugs.webkit.org/show_bug.cgi?id=16768
// see also: https://bugreports.qt-project.org/browse/QTBUG-13364
underline_offset: Au::from_pt(self.ctfont.underline_position() as f64),
strikeout_size: geometry::from_pt(0.0), // FIXME(Issue #942)
strikeout_offset: geometry::from_pt(0.0), // FIXME(Issue #942)
leading: Au::from_pt(leading),
x_height: Au::from_pt(self.ctfont.x_height() as f64),
em_size: em_size,
ascent: Au::from_pt(ascent * scale),
descent: Au::from_pt(descent * scale),
max_advance: Au::from_pt(bounding_rect.size.width as f64),
line_gap: Au::from_frac_px(line_gap),
};
debug!("Font metrics (@{:f} pt): {:?}", self.ctfont.pt_size() as f64, metrics);
return metrics;
}
fn get_table_for_tag(&self, tag: FontTableTag) -> Option<FontTable> {
let result: Option<CFData> = self.ctfont.get_font_table(tag);
result.and_then(|data| {
Some(FontTable::wrap(data))
})
}
}

View file

@ -0,0 +1,16 @@
/* 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/. */
#[deriving(Clone)]
pub struct FontContextHandle {
ctx: ()
}
#[deriving(Clone)]
impl FontContextHandle {
// this is a placeholder until NSFontManager or whatever is bound in here.
pub fn new() -> FontContextHandle {
FontContextHandle { ctx: () }
}
}

View file

@ -0,0 +1,37 @@
/* 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 core_foundation::base::TCFType;
use core_foundation::string::{CFString, CFStringRef};
use core_text::font_descriptor::{CTFontDescriptor, CTFontDescriptorRef};
use core_text;
use std::mem;
pub fn get_available_families(callback: |String|) {
let family_names = core_text::font_collection::get_family_names();
for strref in family_names.iter() {
let family_name_ref: CFStringRef = unsafe { mem::transmute(strref) };
let family_name_cf: CFString = unsafe { TCFType::wrap_under_get_rule(family_name_ref) };
let family_name = family_name_cf.to_string();
callback(family_name);
}
}
pub fn get_variations_for_family(family_name: &str, callback: |String|) {
debug!("Looking for faces of family: {:s}", family_name);
let family_collection =
core_text::font_collection::create_for_family(family_name.as_slice());
let family_descriptors = family_collection.get_descriptors();
for descref in family_descriptors.iter() {
let descref: CTFontDescriptorRef = unsafe { mem::transmute(descref) };
let desc: CTFontDescriptor = unsafe { TCFType::wrap_under_get_rule(descref) };
let postscript_name = desc.font_name();
callback(postscript_name);
}
}
pub fn get_last_resort_font_families() -> Vec<String> {
vec!("Arial Unicode MS".to_string(), "Arial".to_string())
}

View file

@ -0,0 +1,40 @@
/* 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 core_graphics::data_provider::CGDataProvider;
use core_graphics::font::CGFont;
use core_text::font::CTFont;
use core_text;
/// Platform specific font representation for mac.
/// The identifier is a PostScript font name. The
/// CTFont object is cached here for use by the
/// render functions that create CGFont references.
pub struct FontTemplateData {
pub ctfont: Option<CTFont>,
pub identifier: String,
}
impl FontTemplateData {
pub fn new(identifier: &str, font_data: Option<Vec<u8>>) -> FontTemplateData {
let ctfont = match font_data {
Some(bytes) => {
let fontprov = CGDataProvider::from_buffer(bytes.as_slice());
let cgfont_result = CGFont::from_data_provider(fontprov);
match cgfont_result {
Ok(cgfont) => Some(core_text::font::new_from_CGFont(&cgfont, 0.0)),
Err(_) => None
}
},
None => {
Some(core_text::font::new_from_name(identifier.as_slice(), 0.0).unwrap())
}
};
FontTemplateData {
ctfont: ctfont,
identifier: identifier.to_string(),
}
}
}

View file

@ -0,0 +1,27 @@
/* 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/. */
#[cfg(target_os="linux")]
#[cfg(target_os="android")]
pub use platform::freetype::{font, font_context, font_list, font_template};
#[cfg(target_os="macos")]
pub use platform::macos::{font, font_context, font_list, font_template};
#[cfg(target_os="linux")]
#[cfg(target_os="android")]
pub mod freetype {
pub mod font;
pub mod font_context;
pub mod font_list;
pub mod font_template;
}
#[cfg(target_os="macos")]
pub mod macos {
pub mod font;
pub mod font_context;
pub mod font_list;
pub mod font_template;
}

View file

@ -0,0 +1,419 @@
/* 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 font_context::FontContext;
use style::computed_values::border_style;
use azure::azure_hl::{B8G8R8A8, A8, Color, ColorPattern, DrawOptions, DrawSurfaceOptions, DrawTarget};
use azure::azure_hl::{Linear, SourceOp, StrokeOptions};
use azure::AZ_CAP_BUTT;
use azure::AzFloat;
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::size_t;
use png::{RGB8, RGBA8, K8, KA8};
use servo_net::image::base::Image;
use servo_util::geometry::Au;
use servo_util::opts::Opts;
use sync::Arc;
pub struct RenderContext<'a> {
pub draw_target: &'a DrawTarget,
pub font_ctx: &'a mut Box<FontContext>,
pub opts: &'a Opts,
/// The rectangle that this context encompasses in page coordinates.
pub page_rect: Rect<f32>,
/// The rectangle that this context encompasses in screen coordinates (pixels).
pub screen_rect: Rect<uint>,
}
enum Direction {
Top,
Left,
Right,
Bottom
}
enum DashSize {
DottedBorder = 1,
DashedBorder = 3
}
impl<'a> RenderContext<'a> {
pub fn get_draw_target(&self) -> &'a DrawTarget {
self.draw_target
}
pub fn draw_solid_color(&self, bounds: &Rect<Au>, color: Color) {
self.draw_target.make_current();
self.draw_target.fill_rect(&bounds.to_azure_rect(), &ColorPattern::new(color), None);
}
pub fn draw_border(&self,
bounds: &Rect<Au>,
border: SideOffsets2D<Au>,
color: SideOffsets2D<Color>,
style: SideOffsets2D<border_style::T>) {
let border = border.to_float_px();
self.draw_target.make_current();
self.draw_border_segment(Top, bounds, border, color, style);
self.draw_border_segment(Right, bounds, border, color, style);
self.draw_border_segment(Bottom, bounds, border, color, style);
self.draw_border_segment(Left, bounds, border, color, style);
}
pub fn draw_line(&self,
bounds: &Rect<Au>,
color: Color,
style: border_style::T) {
self.draw_target.make_current();
self.draw_line_segment(bounds, color, style);
}
pub fn draw_push_clip(&self, bounds: &Rect<Au>) {
let rect = bounds.to_azure_rect();
let path_builder = self.draw_target.create_path_builder();
let left_top = Point2D(rect.origin.x, rect.origin.y);
let right_top = Point2D(rect.origin.x + rect.size.width, rect.origin.y);
let left_bottom = Point2D(rect.origin.x, rect.origin.y + rect.size.height);
let right_bottom = Point2D(rect.origin.x + rect.size.width, rect.origin.y + rect.size.height);
path_builder.move_to(left_top);
path_builder.line_to(right_top);
path_builder.line_to(right_bottom);
path_builder.line_to(left_bottom);
let path = path_builder.finish();
self.draw_target.push_clip(&path);
}
pub fn draw_pop_clip(&self) {
self.draw_target.pop_clip();
}
pub fn draw_image(&self, bounds: Rect<Au>, image: Arc<Box<Image>>) {
let size = Size2D(image.width as i32, image.height as i32);
let (pixel_width, pixels, source_format) = match image.pixels {
RGBA8(ref pixels) => (4, pixels.as_slice(), B8G8R8A8),
K8(ref pixels) => (1, pixels.as_slice(), A8),
RGB8(_) => fail!("RGB8 color type not supported"),
KA8(_) => fail!("KA8 color type not supported"),
};
let stride = image.width * pixel_width;
self.draw_target.make_current();
let draw_target_ref = &self.draw_target;
let azure_surface = draw_target_ref.create_source_surface_from_data(pixels,
size,
stride as i32,
source_format);
let source_rect = Rect(Point2D(0u as AzFloat, 0u as AzFloat),
Size2D(image.width as AzFloat, image.height as AzFloat));
let dest_rect = bounds.to_azure_rect();
let draw_surface_options = DrawSurfaceOptions::new(Linear, true);
let draw_options = DrawOptions::new(1.0f64 as AzFloat, 0);
draw_target_ref.draw_surface(azure_surface,
dest_rect,
source_rect,
draw_surface_options,
draw_options);
}
pub fn clear(&self) {
let pattern = ColorPattern::new(Color::new(0.0, 0.0, 0.0, 0.0));
let rect = Rect(Point2D(self.page_rect.origin.x as AzFloat,
self.page_rect.origin.y as AzFloat),
Size2D(self.screen_rect.size.width as AzFloat,
self.screen_rect.size.height as AzFloat));
let mut draw_options = DrawOptions::new(1.0, 0);
draw_options.set_composition_op(SourceOp);
self.draw_target.make_current();
self.draw_target.fill_rect(&rect, &pattern, Some(&draw_options));
}
fn draw_border_segment(&self, direction: Direction, bounds: &Rect<Au>, border: SideOffsets2D<f32>, color: SideOffsets2D<Color>, style: SideOffsets2D<border_style::T>) {
let (style_select, color_select) = match direction {
Top => (style.top, color.top),
Left => (style.left, color.left),
Right => (style.right, color.right),
Bottom => (style.bottom, color.bottom)
};
match style_select{
border_style::none => {
}
border_style::hidden => {
}
//FIXME(sammykim): This doesn't work with dash_pattern and cap_style well. I referred firefox code.
border_style::dotted => {
self.draw_dashed_border_segment(direction, bounds, border, color_select, DottedBorder);
}
border_style::dashed => {
self.draw_dashed_border_segment(direction, bounds, border, color_select, DashedBorder);
}
border_style::solid => {
self.draw_solid_border_segment(direction,bounds,border,color_select);
}
border_style::double => {
self.draw_double_border_segment(direction, bounds, border, color_select);
}
border_style::groove | border_style::ridge => {
self.draw_groove_ridge_border_segment(direction, bounds, border, color_select, style_select);
}
border_style::inset | border_style::outset => {
self.draw_inset_outset_border_segment(direction, bounds, border, style_select, color_select);
}
}
}
fn draw_line_segment(&self, bounds: &Rect<Au>, color: Color, style: border_style::T) {
let border = SideOffsets2D::new_all_same(bounds.size.width).to_float_px();
match style{
border_style::none | border_style::hidden => {}
border_style::dotted => {
self.draw_dashed_border_segment(Right, bounds, border, color, DottedBorder);
}
border_style::dashed => {
self.draw_dashed_border_segment(Right, bounds, border, color, DashedBorder);
}
border_style::solid => {
self.draw_solid_border_segment(Right,bounds,border,color);
}
border_style::double => {
self.draw_double_border_segment(Right, bounds, border, color);
}
border_style::groove | border_style::ridge => {
self.draw_groove_ridge_border_segment(Right, bounds, border, color, style);
}
border_style::inset | border_style::outset => {
self.draw_inset_outset_border_segment(Right, bounds, border, style, color);
}
}
}
fn draw_border_path(&self,
bounds: Rect<f32>,
direction: Direction,
border: SideOffsets2D<f32>,
color: Color) {
let left_top = bounds.origin;
let right_top = left_top + Point2D(bounds.size.width, 0.0);
let left_bottom = left_top + Point2D(0.0, bounds.size.height);
let right_bottom = left_top + Point2D(bounds.size.width, bounds.size.height);
let draw_opts = DrawOptions::new(1.0, 0);
let path_builder = self.draw_target.create_path_builder();
match direction {
Top => {
path_builder.move_to(left_top);
path_builder.line_to(right_top);
path_builder.line_to(right_top + Point2D(-border.right, border.top));
path_builder.line_to(left_top + Point2D(border.left, border.top));
}
Left => {
path_builder.move_to(left_top);
path_builder.line_to(left_top + Point2D(border.left, border.top));
path_builder.line_to(left_bottom + Point2D(border.left, -border.bottom));
path_builder.line_to(left_bottom);
}
Right => {
path_builder.move_to(right_top);
path_builder.line_to(right_bottom);
path_builder.line_to(right_bottom + Point2D(-border.right, -border.bottom));
path_builder.line_to(right_top + Point2D(-border.right, border.top));
}
Bottom => {
path_builder.move_to(left_bottom);
path_builder.line_to(left_bottom + Point2D(border.left, -border.bottom));
path_builder.line_to(right_bottom + Point2D(-border.right, -border.bottom));
path_builder.line_to(right_bottom);
}
}
let path = path_builder.finish();
self.draw_target.fill(&path, &ColorPattern::new(color), &draw_opts);
}
fn draw_dashed_border_segment(&self,
direction: Direction,
bounds: &Rect<Au>,
border: SideOffsets2D<f32>,
color: Color,
dash_size: DashSize) {
let rect = bounds.to_azure_rect();
let draw_opts = DrawOptions::new(1u as AzFloat, 0 as uint16_t);
let mut stroke_opts = StrokeOptions::new(0u as AzFloat, 10u as AzFloat);
let mut dash: [AzFloat, ..2] = [0u as AzFloat, 0u as AzFloat];
stroke_opts.set_cap_style(AZ_CAP_BUTT as u8);
let border_width = match direction {
Top => border.top,
Left => border.left,
Right => border.right,
Bottom => border.bottom
};
stroke_opts.line_width = border_width;
dash[0] = border_width * (dash_size as int) as AzFloat;
dash[1] = border_width * (dash_size as int) as AzFloat;
stroke_opts.mDashPattern = dash.as_mut_ptr();
stroke_opts.mDashLength = dash.len() as size_t;
let (start, end) = match direction {
Top => {
let y = rect.origin.y + border.top * 0.5;
let start = Point2D(rect.origin.x, y);
let end = Point2D(rect.origin.x + rect.size.width, y);
(start, end)
}
Left => {
let x = rect.origin.x + border.left * 0.5;
let start = Point2D(x, rect.origin.y + rect.size.height);
let end = Point2D(x, rect.origin.y + border.top);
(start, end)
}
Right => {
let x = rect.origin.x + rect.size.width - border.right * 0.5;
let start = Point2D(x, rect.origin.y);
let end = Point2D(x, rect.origin.y + rect.size.height);
(start, end)
}
Bottom => {
let y = rect.origin.y + rect.size.height - border.bottom * 0.5;
let start = Point2D(rect.origin.x + rect.size.width, y);
let end = Point2D(rect.origin.x + border.left, y);
(start, end)
}
};
self.draw_target.stroke_line(start,
end,
&ColorPattern::new(color),
&stroke_opts,
&draw_opts);
}
fn draw_solid_border_segment(&self, direction: Direction, bounds: &Rect<Au>, border: SideOffsets2D<f32>, color: Color) {
let rect = bounds.to_azure_rect();
self.draw_border_path(rect, direction, border, color);
}
fn get_scaled_bounds(&self,
bounds: &Rect<Au>,
border: SideOffsets2D<f32>,
shrink_factor: f32) -> Rect<f32> {
let rect = bounds.to_azure_rect();
let scaled_border = SideOffsets2D::new(shrink_factor * border.top,
shrink_factor * border.right,
shrink_factor * border.bottom,
shrink_factor * border.left);
let left_top = Point2D(rect.origin.x, rect.origin.y);
let scaled_left_top = left_top + Point2D(scaled_border.left,
scaled_border.top);
return Rect(scaled_left_top,
Size2D(rect.size.width - 2.0 * scaled_border.right, rect.size.height - 2.0 * scaled_border.bottom));
}
fn scale_color(&self, color: Color, scale_factor: f32) -> Color {
return Color::new(color.r * scale_factor, color.g * scale_factor, color.b * scale_factor, color.a);
}
fn draw_double_border_segment(&self, direction: Direction, bounds: &Rect<Au>, border: SideOffsets2D<f32>, color: Color) {
let scaled_border = SideOffsets2D::new((1.0/3.0) * border.top,
(1.0/3.0) * border.right,
(1.0/3.0) * border.bottom,
(1.0/3.0) * border.left);
let inner_scaled_bounds = self.get_scaled_bounds(bounds, border, 2.0/3.0);
// draw the outer portion of the double border.
self.draw_solid_border_segment(direction, bounds, scaled_border, color);
// draw the inner portion of the double border.
self.draw_border_path(inner_scaled_bounds, direction, scaled_border, color);
}
fn draw_groove_ridge_border_segment(&self,
direction: Direction,
bounds: &Rect<Au>,
border: SideOffsets2D<f32>,
color: Color,
style: border_style::T) {
// original bounds as a Rect<f32>, with no scaling.
let original_bounds = self.get_scaled_bounds(bounds, border, 0.0);
// shrink the bounds by 1/2 of the border, leaving the innermost 1/2 of the border
let inner_scaled_bounds = self.get_scaled_bounds(bounds, border, 0.5);
let scaled_border = SideOffsets2D::new(0.5 * border.top,
0.5 * border.right,
0.5 * border.bottom,
0.5 * border.left);
let is_groove = match style {
border_style::groove => true,
border_style::ridge => false,
_ => fail!("invalid border style")
};
let darker_color = self.scale_color(color, if is_groove { 1.0/3.0 } else { 2.0/3.0 });
let (outer_color, inner_color) = match (direction, is_groove) {
(Top, true) | (Left, true) | (Right, false) | (Bottom, false) => (darker_color, color),
(Top, false) | (Left, false) | (Right, true) | (Bottom, true) => (color, darker_color)
};
// outer portion of the border
self.draw_border_path(original_bounds, direction, scaled_border, outer_color);
// inner portion of the border
self.draw_border_path(inner_scaled_bounds, direction, scaled_border, inner_color);
}
fn draw_inset_outset_border_segment(&self,
direction: Direction,
bounds: &Rect<Au>,
border: SideOffsets2D<f32>,
style: border_style::T,
color: Color) {
let is_inset = match style {
border_style::inset => true,
border_style::outset => false,
_ => fail!("invalid border style")
};
// original bounds as a Rect<f32>
let original_bounds = self.get_scaled_bounds(bounds, border, 0.0);
// select and scale the color appropriately.
let scaled_color = match direction {
Top => self.scale_color(color, if is_inset { 2.0/3.0 } else { 1.0 }),
Left => self.scale_color(color, if is_inset { 1.0/6.0 } else { 0.5 }),
Right | Bottom => self.scale_color(color, if is_inset { 1.0 } else { 2.0/3.0 })
};
self.draw_border_path(original_bounds, direction, border, scaled_color);
}
}
trait ToAzureRect {
fn to_azure_rect(&self) -> Rect<AzFloat>;
}
impl ToAzureRect for Rect<Au> {
fn to_azure_rect(&self) -> Rect<AzFloat> {
Rect(Point2D(self.origin.x.to_nearest_px() as AzFloat,
self.origin.y.to_nearest_px() as AzFloat),
Size2D(self.size.width.to_nearest_px() as AzFloat,
self.size.height.to_nearest_px() as AzFloat))
}
}
trait ToSideOffsetsPx {
fn to_float_px(&self) -> SideOffsets2D<AzFloat>;
}
impl ToSideOffsetsPx for SideOffsets2D<Au> {
fn to_float_px(&self) -> SideOffsets2D<AzFloat> {
SideOffsets2D::new(self.top.to_nearest_px() as AzFloat,
self.right.to_nearest_px() as AzFloat,
self.bottom.to_nearest_px() as AzFloat,
self.left.to_nearest_px() as AzFloat)
}
}

View file

@ -0,0 +1,443 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
//! The task that handles all rendering/painting.
use buffer_map::BufferMap;
use display_list::optimizer::DisplayListOptimizer;
use display_list::DisplayList;
use font_context::FontContext;
use render_context::RenderContext;
use azure::azure_hl::{B8G8R8A8, Color, DrawTarget, StolenGLResources};
use azure::AzFloat;
use geom::matrix2d::Matrix2D;
use geom::rect::Rect;
use geom::size::Size2D;
use layers::platform::surface::{NativePaintingGraphicsContext, NativeSurface};
use layers::platform::surface::{NativeSurfaceMethods};
use layers::layers::{BufferRequest, LayerBuffer, LayerBufferSet};
use layers;
use servo_msg::compositor_msg::{Epoch, IdleRenderState, LayerId};
use servo_msg::compositor_msg::{LayerMetadata, RenderListener, RenderingRenderState, ScrollPolicy};
use servo_msg::constellation_msg::{ConstellationChan, Failure, FailureMsg, PipelineId};
use servo_msg::constellation_msg::{RendererReadyMsg};
use servo_msg::platform::surface::NativeSurfaceAzureMethods;
use servo_util::geometry;
use servo_util::opts::Opts;
use servo_util::smallvec::{SmallVec, SmallVec1};
use servo_util::task::spawn_named_with_send_on_failure;
use servo_util::time::{TimeProfilerChan, profile};
use servo_util::time;
use std::comm::{Receiver, Sender, channel};
use sync::Arc;
use font_cache_task::FontCacheTask;
/// Information about a layer that layout sends to the painting task.
pub struct RenderLayer {
/// A per-pipeline ID describing this layer that should be stable across reflows.
pub id: LayerId,
/// The display list describing the contents of this layer.
pub display_list: Arc<DisplayList>,
/// The position of the layer in pixels.
pub position: Rect<uint>,
/// The color of the background in this layer. Used for unrendered content.
pub background_color: Color,
/// The scrolling policy of this layer.
pub scroll_policy: ScrollPolicy,
}
pub struct RenderRequest {
pub buffer_requests: Vec<BufferRequest>,
pub scale: f32,
pub layer_id: LayerId,
pub epoch: Epoch,
}
pub enum Msg {
RenderInitMsg(SmallVec1<RenderLayer>),
RenderMsg(Vec<RenderRequest>),
UnusedBufferMsg(Vec<Box<LayerBuffer>>),
PaintPermissionGranted,
PaintPermissionRevoked,
ExitMsg(Option<Sender<()>>),
}
#[deriving(Clone)]
pub struct RenderChan(Sender<Msg>);
impl RenderChan {
pub fn new() -> (Receiver<Msg>, RenderChan) {
let (chan, port) = channel();
(port, RenderChan(chan))
}
pub fn send(&self, msg: Msg) {
let &RenderChan(ref chan) = self;
assert!(chan.send_opt(msg).is_ok(), "RenderChan.send: render port closed")
}
pub fn send_opt(&self, msg: Msg) -> Result<(), Msg> {
let &RenderChan(ref chan) = self;
chan.send_opt(msg)
}
}
/// If we're using GPU rendering, this provides the metadata needed to create a GL context that
/// is compatible with that of the main thread.
pub enum GraphicsContext {
CpuGraphicsContext,
GpuGraphicsContext,
}
pub struct RenderTask<C> {
id: PipelineId,
port: Receiver<Msg>,
compositor: C,
constellation_chan: ConstellationChan,
font_ctx: Box<FontContext>,
opts: Opts,
/// A channel to the time profiler.
time_profiler_chan: TimeProfilerChan,
/// The graphics context to use.
graphics_context: GraphicsContext,
/// The native graphics context.
native_graphics_context: Option<NativePaintingGraphicsContext>,
/// The layers to be rendered.
render_layers: SmallVec1<RenderLayer>,
/// Permission to send paint messages to the compositor
paint_permission: bool,
/// A counter for epoch messages
epoch: Epoch,
/// A data structure to store unused LayerBuffers
buffer_map: BufferMap,
}
// If we implement this as a function, we get borrowck errors from borrowing
// the whole RenderTask struct.
macro_rules! native_graphics_context(
($task:expr) => (
$task.native_graphics_context.as_ref().expect("Need a graphics context to do rendering")
)
)
fn initialize_layers<C:RenderListener>(
compositor: &mut C,
pipeline_id: PipelineId,
epoch: Epoch,
render_layers: &[RenderLayer]) {
let metadata = render_layers.iter().map(|render_layer| {
LayerMetadata {
id: render_layer.id,
position: render_layer.position,
background_color: render_layer.background_color,
scroll_policy: render_layer.scroll_policy,
}
}).collect();
compositor.initialize_layers_for_pipeline(pipeline_id, metadata, epoch);
}
impl<C:RenderListener + Send> RenderTask<C> {
pub fn create(id: PipelineId,
port: Receiver<Msg>,
compositor: C,
constellation_chan: ConstellationChan,
font_cache_task: FontCacheTask,
failure_msg: Failure,
opts: Opts,
time_profiler_chan: TimeProfilerChan,
shutdown_chan: Sender<()>) {
let ConstellationChan(c) = constellation_chan.clone();
let fc = font_cache_task.clone();
spawn_named_with_send_on_failure("RenderTask", proc() {
{ // Ensures RenderTask and graphics context are destroyed before shutdown msg
let native_graphics_context = compositor.get_graphics_metadata().map(
|md| NativePaintingGraphicsContext::from_metadata(&md));
let cpu_painting = opts.cpu_painting;
// FIXME: rust/#5967
let mut render_task = RenderTask {
id: id,
port: port,
compositor: compositor,
constellation_chan: constellation_chan,
font_ctx: box FontContext::new(fc.clone()),
opts: opts,
time_profiler_chan: time_profiler_chan,
graphics_context: if cpu_painting {
CpuGraphicsContext
} else {
GpuGraphicsContext
},
native_graphics_context: native_graphics_context,
render_layers: SmallVec1::new(),
paint_permission: false,
epoch: Epoch(0),
buffer_map: BufferMap::new(10000000),
};
render_task.start();
// Destroy all the buffers.
match render_task.native_graphics_context.as_ref() {
Some(ctx) => render_task.buffer_map.clear(ctx),
None => (),
}
}
debug!("render_task: shutdown_chan send");
shutdown_chan.send(());
}, FailureMsg(failure_msg), c, true);
}
fn start(&mut self) {
debug!("render_task: beginning rendering loop");
loop {
match self.port.recv() {
RenderInitMsg(render_layers) => {
self.epoch.next();
self.render_layers = render_layers;
if !self.paint_permission {
debug!("render_task: render ready msg");
let ConstellationChan(ref mut c) = self.constellation_chan;
c.send(RendererReadyMsg(self.id));
continue;
}
initialize_layers(&mut self.compositor,
self.id,
self.epoch,
self.render_layers.as_slice());
}
RenderMsg(requests) => {
if !self.paint_permission {
debug!("render_task: render ready msg");
let ConstellationChan(ref mut c) = self.constellation_chan;
c.send(RendererReadyMsg(self.id));
self.compositor.render_msg_discarded();
continue;
}
self.compositor.set_render_state(RenderingRenderState);
let mut replies = Vec::new();
for RenderRequest { buffer_requests, scale, layer_id, epoch }
in requests.move_iter() {
if self.epoch == epoch {
self.render(&mut replies, buffer_requests, scale, layer_id);
} else {
debug!("renderer epoch mismatch: {:?} != {:?}", self.epoch, epoch);
}
}
self.compositor.set_render_state(IdleRenderState);
debug!("render_task: returning surfaces");
self.compositor.paint(self.id, self.epoch, replies);
}
UnusedBufferMsg(unused_buffers) => {
for buffer in unused_buffers.move_iter().rev() {
self.buffer_map.insert(native_graphics_context!(self), buffer);
}
}
PaintPermissionGranted => {
self.paint_permission = true;
// Here we assume that the main layer—the layer responsible for the page size—
// is the first layer. This is a pretty fragile assumption. It will be fixed
// once we use the layers-based scrolling infrastructure for all scrolling.
if self.render_layers.len() > 1 {
self.epoch.next();
initialize_layers(&mut self.compositor,
self.id,
self.epoch,
self.render_layers.as_slice());
}
}
PaintPermissionRevoked => {
self.paint_permission = false;
}
ExitMsg(response_ch) => {
debug!("render_task: exitmsg response send");
response_ch.map(|ch| ch.send(()));
break;
}
}
}
}
/// Renders one layer and sends the tiles back to the layer.
fn render(&mut self,
replies: &mut Vec<(LayerId, Box<LayerBufferSet>)>,
tiles: Vec<BufferRequest>,
scale: f32,
layer_id: LayerId) {
time::profile(time::RenderingCategory, self.time_profiler_chan.clone(), || {
// FIXME: Try not to create a new array here.
let mut new_buffers = vec!();
// Find the appropriate render layer.
let render_layer = match self.render_layers.iter().find(|layer| layer.id == layer_id) {
Some(render_layer) => render_layer,
None => return,
};
// Divide up the layer into tiles.
for tile in tiles.iter() {
// Optimize the display list for this tile.
let page_rect_au = geometry::f32_rect_to_au_rect(tile.page_rect);
let optimizer = DisplayListOptimizer::new(render_layer.display_list.clone(),
page_rect_au);
let display_list = optimizer.optimize();
let width = tile.screen_rect.size.width;
let height = tile.screen_rect.size.height;
let size = Size2D(width as i32, height as i32);
let draw_target = match self.graphics_context {
CpuGraphicsContext => {
DrawTarget::new(self.opts.render_backend, size, B8G8R8A8)
}
GpuGraphicsContext => {
// FIXME(pcwalton): Cache the components of draw targets
// (texture color buffer, renderbuffers) instead of recreating them.
let draw_target =
DrawTarget::new_with_fbo(self.opts.render_backend,
native_graphics_context!(self),
size,
B8G8R8A8);
draw_target.make_current();
draw_target
}
};
{
// Build the render context.
let mut ctx = RenderContext {
draw_target: &draw_target,
font_ctx: &mut self.font_ctx,
opts: &self.opts,
page_rect: tile.page_rect,
screen_rect: tile.screen_rect,
};
// Apply the translation to render the tile we want.
let matrix: Matrix2D<AzFloat> = Matrix2D::identity();
let matrix = matrix.scale(scale as AzFloat, scale as AzFloat);
let matrix = matrix.translate(-(tile.page_rect.origin.x) as AzFloat,
-(tile.page_rect.origin.y) as AzFloat);
let matrix = matrix.translate(-(render_layer.position.origin.x as AzFloat),
-(render_layer.position.origin.y as AzFloat));
ctx.draw_target.set_transform(&matrix);
// Clear the buffer.
ctx.clear();
// Draw the display list.
profile(time::RenderingDrawingCategory, self.time_profiler_chan.clone(), || {
display_list.draw_into_context(&mut ctx, &matrix);
ctx.draw_target.flush();
});
}
// Extract the texture from the draw target and place it into its slot in the
// buffer. If using CPU rendering, upload it first.
//
// FIXME(pcwalton): We should supply the texture and native surface *to* the
// draw target in GPU rendering mode, so that it doesn't have to recreate it.
let buffer = match self.graphics_context {
CpuGraphicsContext => {
let mut buffer = match self.buffer_map.find(tile.screen_rect.size) {
Some(buffer) => {
let mut buffer = buffer;
buffer.rect = tile.page_rect;
buffer.screen_pos = tile.screen_rect;
buffer.resolution = scale;
buffer.native_surface.mark_wont_leak();
buffer.painted_with_cpu = true;
buffer.content_age = tile.content_age;
buffer
}
None => {
// Create an empty native surface. We mark it as not leaking
// in case it dies in transit to the compositor task.
let mut native_surface: NativeSurface =
layers::platform::surface::NativeSurfaceMethods::new(
native_graphics_context!(self),
Size2D(width as i32, height as i32),
width as i32 * 4);
native_surface.mark_wont_leak();
box LayerBuffer {
native_surface: native_surface,
rect: tile.page_rect,
screen_pos: tile.screen_rect,
resolution: scale,
stride: (width * 4) as uint,
painted_with_cpu: true,
content_age: tile.content_age,
}
}
};
draw_target.snapshot().get_data_surface().with_data(|data| {
buffer.native_surface.upload(native_graphics_context!(self), data);
debug!("RENDERER uploading to native surface {:d}",
buffer.native_surface.get_id() as int);
});
buffer
}
GpuGraphicsContext => {
draw_target.make_current();
let StolenGLResources {
surface: native_surface
} = draw_target.steal_gl_resources().unwrap();
// We mark the native surface as not leaking in case the surfaces
// die on their way to the compositor task.
let mut native_surface: NativeSurface =
NativeSurfaceAzureMethods::from_azure_surface(native_surface);
native_surface.mark_wont_leak();
box LayerBuffer {
native_surface: native_surface,
rect: tile.page_rect,
screen_pos: tile.screen_rect,
resolution: scale,
stride: (width * 4) as uint,
painted_with_cpu: false,
content_age: tile.content_age,
}
}
};
new_buffers.push(buffer);
}
let layer_buffer_set = box LayerBufferSet {
buffers: new_buffers,
};
replies.push((render_layer.id, layer_buffer_set));
})
}
}

View file

@ -0,0 +1,752 @@
/* 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 servo_util::vec::*;
use servo_util::range;
use servo_util::range::{Range, RangeIndex, IntRangeIndex, EachIndex};
use servo_util::geometry::Au;
use std::cmp::{PartialOrd, PartialEq};
use std::num::{NumCast, Zero};
use std::mem;
use std::u16;
use std::vec::Vec;
use geom::point::Point2D;
/// GlyphEntry is a port of Gecko's CompressedGlyph scheme for storing glyph data compactly.
///
/// In the common case (reasonable glyph advances, no offsets from the font em-box, and one glyph
/// per character), we pack glyph advance, glyph id, and some flags into a single u32.
///
/// In the uncommon case (multiple glyphs per unicode character, large glyph index/advance, or
/// glyph offsets), we pack the glyph count into GlyphEntry, and store the other glyph information
/// in DetailedGlyphStore.
#[deriving(Clone)]
struct GlyphEntry {
value: u32,
}
impl GlyphEntry {
fn new(value: u32) -> GlyphEntry {
GlyphEntry {
value: value,
}
}
fn initial() -> GlyphEntry {
GlyphEntry::new(0)
}
// Creates a GlyphEntry for the common case
fn simple(id: GlyphId, advance: Au) -> GlyphEntry {
assert!(is_simple_glyph_id(id));
assert!(is_simple_advance(advance));
let id_mask = id as u32;
let Au(advance) = advance;
let advance_mask = (advance as u32) << GLYPH_ADVANCE_SHIFT as uint;
GlyphEntry::new(id_mask | advance_mask | FLAG_IS_SIMPLE_GLYPH)
}
// Create a GlyphEntry for uncommon case; should be accompanied by
// initialization of the actual DetailedGlyph data in DetailedGlyphStore
fn complex(starts_cluster: bool, starts_ligature: bool, glyph_count: int) -> GlyphEntry {
assert!(glyph_count <= u16::MAX as int);
debug!("creating complex glyph entry: starts_cluster={}, starts_ligature={}, \
glyph_count={}",
starts_cluster,
starts_ligature,
glyph_count);
let mut val = FLAG_NOT_MISSING;
if !starts_cluster {
val |= FLAG_NOT_CLUSTER_START;
}
if !starts_ligature {
val |= FLAG_NOT_LIGATURE_GROUP_START;
}
val |= (glyph_count as u32) << GLYPH_COUNT_SHIFT as uint;
GlyphEntry::new(val)
}
/// Create a GlyphEntry for the case where glyphs couldn't be found for the specified
/// character.
fn missing(glyph_count: int) -> GlyphEntry {
assert!(glyph_count <= u16::MAX as int);
GlyphEntry::new((glyph_count as u32) << GLYPH_COUNT_SHIFT as uint)
}
}
/// The id of a particular glyph within a font
pub type GlyphId = u32;
// TODO: unify with bit flags?
#[deriving(PartialEq)]
pub enum BreakType {
BreakTypeNone,
BreakTypeNormal,
BreakTypeHyphen,
}
static BREAK_TYPE_NONE: u8 = 0x0;
static BREAK_TYPE_NORMAL: u8 = 0x1;
static BREAK_TYPE_HYPHEN: u8 = 0x2;
fn break_flag_to_enum(flag: u8) -> BreakType {
if (flag & BREAK_TYPE_NORMAL) != 0 {
BreakTypeNormal
} else if (flag & BREAK_TYPE_HYPHEN) != 0 {
BreakTypeHyphen
} else {
BreakTypeNone
}
}
fn break_enum_to_flag(e: BreakType) -> u8 {
match e {
BreakTypeNone => BREAK_TYPE_NONE,
BreakTypeNormal => BREAK_TYPE_NORMAL,
BreakTypeHyphen => BREAK_TYPE_HYPHEN,
}
}
// TODO: make this more type-safe.
static FLAG_CHAR_IS_SPACE: u32 = 0x10000000;
// These two bits store some BREAK_TYPE_* flags
static FLAG_CAN_BREAK_MASK: u32 = 0x60000000;
static FLAG_CAN_BREAK_SHIFT: u32 = 29;
static FLAG_IS_SIMPLE_GLYPH: u32 = 0x80000000;
// glyph advance; in Au's.
static GLYPH_ADVANCE_MASK: u32 = 0x0FFF0000;
static GLYPH_ADVANCE_SHIFT: u32 = 16;
static GLYPH_ID_MASK: u32 = 0x0000FFFF;
// Non-simple glyphs (more than one glyph per char; missing glyph,
// newline, tab, large advance, or nonzero x/y offsets) may have one
// or more detailed glyphs associated with them. They are stored in a
// side array so that there is a 1:1 mapping of GlyphEntry to
// unicode char.
// The number of detailed glyphs for this char. If the char couldn't
// be mapped to a glyph (!FLAG_NOT_MISSING), then this actually holds
// the UTF8 code point instead.
static GLYPH_COUNT_MASK: u32 = 0x00FFFF00;
static GLYPH_COUNT_SHIFT: u32 = 8;
// N.B. following Gecko, these are all inverted so that a lot of
// missing chars can be memset with zeros in one fell swoop.
static FLAG_NOT_MISSING: u32 = 0x00000001;
static FLAG_NOT_CLUSTER_START: u32 = 0x00000002;
static FLAG_NOT_LIGATURE_GROUP_START: u32 = 0x00000004;
static FLAG_CHAR_IS_TAB: u32 = 0x00000008;
static FLAG_CHAR_IS_NEWLINE: u32 = 0x00000010;
//static FLAG_CHAR_IS_LOW_SURROGATE: u32 = 0x00000020;
//static CHAR_IDENTITY_FLAGS_MASK: u32 = 0x00000038;
fn is_simple_glyph_id(id: GlyphId) -> bool {
((id as u32) & GLYPH_ID_MASK) == id
}
fn is_simple_advance(advance: Au) -> bool {
let unsignedAu = advance.to_u32().unwrap();
(unsignedAu & (GLYPH_ADVANCE_MASK >> GLYPH_ADVANCE_SHIFT as uint)) == unsignedAu
}
type DetailedGlyphCount = u16;
// Getters and setters for GlyphEntry. Setter methods are functional,
// because GlyphEntry is immutable and only a u32 in size.
impl GlyphEntry {
// getter methods
#[inline(always)]
fn advance(&self) -> Au {
NumCast::from((self.value & GLYPH_ADVANCE_MASK) >> GLYPH_ADVANCE_SHIFT as uint).unwrap()
}
fn id(&self) -> GlyphId {
self.value & GLYPH_ID_MASK
}
fn is_ligature_start(&self) -> bool {
self.has_flag(!FLAG_NOT_LIGATURE_GROUP_START)
}
fn is_cluster_start(&self) -> bool {
self.has_flag(!FLAG_NOT_CLUSTER_START)
}
// True if original char was normal (U+0020) space. Other chars may
// map to space glyph, but this does not account for them.
fn char_is_space(&self) -> bool {
self.has_flag(FLAG_CHAR_IS_SPACE)
}
fn char_is_tab(&self) -> bool {
!self.is_simple() && self.has_flag(FLAG_CHAR_IS_TAB)
}
fn char_is_newline(&self) -> bool {
!self.is_simple() && self.has_flag(FLAG_CHAR_IS_NEWLINE)
}
fn can_break_before(&self) -> BreakType {
let flag = ((self.value & FLAG_CAN_BREAK_MASK) >> FLAG_CAN_BREAK_SHIFT as uint) as u8;
break_flag_to_enum(flag)
}
// setter methods
#[inline(always)]
fn set_char_is_space(&self) -> GlyphEntry {
GlyphEntry::new(self.value | FLAG_CHAR_IS_SPACE)
}
#[inline(always)]
fn set_char_is_tab(&self) -> GlyphEntry {
assert!(!self.is_simple());
GlyphEntry::new(self.value | FLAG_CHAR_IS_TAB)
}
#[inline(always)]
fn set_char_is_newline(&self) -> GlyphEntry {
assert!(!self.is_simple());
GlyphEntry::new(self.value | FLAG_CHAR_IS_NEWLINE)
}
#[inline(always)]
fn set_can_break_before(&self, e: BreakType) -> GlyphEntry {
let flag = (break_enum_to_flag(e) as u32) << FLAG_CAN_BREAK_SHIFT as uint;
GlyphEntry::new(self.value | flag)
}
// helper methods
fn glyph_count(&self) -> u16 {
assert!(!self.is_simple());
((self.value & GLYPH_COUNT_MASK) >> GLYPH_COUNT_SHIFT as uint) as u16
}
#[inline(always)]
fn is_simple(&self) -> bool {
self.has_flag(FLAG_IS_SIMPLE_GLYPH)
}
#[inline(always)]
fn has_flag(&self, flag: u32) -> bool {
(self.value & flag) != 0
}
#[inline(always)]
fn adapt_character_flags_of_entry(&self, other: GlyphEntry) -> GlyphEntry {
GlyphEntry { value: self.value | other.value }
}
}
// Stores data for a detailed glyph, in the case that several glyphs
// correspond to one character, or the glyph's data couldn't be packed.
#[deriving(Clone)]
struct DetailedGlyph {
id: GlyphId,
// glyph's advance, in the text's direction (RTL or RTL)
advance: Au,
// glyph's offset from the font's em-box (from top-left)
offset: Point2D<Au>,
}
impl DetailedGlyph {
fn new(id: GlyphId, advance: Au, offset: Point2D<Au>) -> DetailedGlyph {
DetailedGlyph {
id: id,
advance: advance,
offset: offset,
}
}
}
#[deriving(PartialEq, Clone, Eq)]
struct DetailedGlyphRecord {
// source string offset/GlyphEntry offset in the TextRun
entry_offset: CharIndex,
// offset into the detailed glyphs buffer
detail_offset: int,
}
impl PartialOrd for DetailedGlyphRecord {
fn partial_cmp(&self, other: &DetailedGlyphRecord) -> Option<Ordering> {
self.entry_offset.partial_cmp(&other.entry_offset)
}
}
impl Ord for DetailedGlyphRecord {
fn cmp(&self, other: &DetailedGlyphRecord) -> Ordering {
self.entry_offset.cmp(&other.entry_offset)
}
}
// Manages the lookup table for detailed glyphs. Sorting is deferred
// until a lookup is actually performed; this matches the expected
// usage pattern of setting/appending all the detailed glyphs, and
// then querying without setting.
struct DetailedGlyphStore {
// TODO(pcwalton): Allocation of this buffer is expensive. Consider a small-vector
// optimization.
detail_buffer: Vec<DetailedGlyph>,
// TODO(pcwalton): Allocation of this buffer is expensive. Consider a small-vector
// optimization.
detail_lookup: Vec<DetailedGlyphRecord>,
lookup_is_sorted: bool,
}
impl<'a> DetailedGlyphStore {
fn new() -> DetailedGlyphStore {
DetailedGlyphStore {
detail_buffer: vec!(), // TODO: default size?
detail_lookup: vec!(),
lookup_is_sorted: false,
}
}
fn add_detailed_glyphs_for_entry(&mut self, entry_offset: CharIndex, glyphs: &[DetailedGlyph]) {
let entry = DetailedGlyphRecord {
entry_offset: entry_offset,
detail_offset: self.detail_buffer.len() as int,
};
debug!("Adding entry[off={}] for detailed glyphs: {:?}", entry_offset, glyphs);
/* TODO: don't actually assert this until asserts are compiled
in/out based on severity, debug/release, etc. This assertion
would wreck the complexity of the lookup.
See Rust Issue #3647, #2228, #3627 for related information.
do self.detail_lookup.borrow |arr| {
assert !arr.contains(entry)
}
*/
self.detail_lookup.push(entry);
self.detail_buffer.push_all(glyphs);
self.lookup_is_sorted = false;
}
fn get_detailed_glyphs_for_entry(&'a self, entry_offset: CharIndex, count: u16)
-> &'a [DetailedGlyph] {
debug!("Requesting detailed glyphs[n={}] for entry[off={}]", count, entry_offset);
// FIXME: Is this right? --pcwalton
// TODO: should fix this somewhere else
if count == 0 {
return self.detail_buffer.slice(0, 0);
}
assert!((count as uint) <= self.detail_buffer.len());
assert!(self.lookup_is_sorted);
let key = DetailedGlyphRecord {
entry_offset: entry_offset,
detail_offset: 0, // unused
};
let i = self.detail_lookup.as_slice().binary_search_index(&key)
.expect("Invalid index not found in detailed glyph lookup table!");
assert!(i + (count as uint) <= self.detail_buffer.len());
// return a slice into the buffer
self.detail_buffer.slice(i, i + count as uint)
}
fn get_detailed_glyph_with_index(&'a self,
entry_offset: CharIndex,
detail_offset: u16)
-> &'a DetailedGlyph {
assert!((detail_offset as uint) <= self.detail_buffer.len());
assert!(self.lookup_is_sorted);
let key = DetailedGlyphRecord {
entry_offset: entry_offset,
detail_offset: 0, // unused
};
let i = self.detail_lookup.as_slice().binary_search_index(&key)
.expect("Invalid index not found in detailed glyph lookup table!");
assert!(i + (detail_offset as uint) < self.detail_buffer.len());
&self.detail_buffer[i + (detail_offset as uint)]
}
fn ensure_sorted(&mut self) {
if self.lookup_is_sorted {
return;
}
// Sorting a unique vector is surprisingly hard. The follwing
// code is a good argument for using DVecs, but they require
// immutable locations thus don't play well with freezing.
// Thar be dragons here. You have been warned. (Tips accepted.)
let mut unsorted_records: Vec<DetailedGlyphRecord> = vec!();
mem::swap(&mut self.detail_lookup, &mut unsorted_records);
let mut mut_records : Vec<DetailedGlyphRecord> = unsorted_records;
mut_records.sort_by(|a, b| {
if a < b {
Less
} else {
Greater
}
});
let mut sorted_records = mut_records;
mem::swap(&mut self.detail_lookup, &mut sorted_records);
self.lookup_is_sorted = true;
}
}
// This struct is used by GlyphStore clients to provide new glyph data.
// It should be allocated on the stack and passed by reference to GlyphStore.
pub struct GlyphData {
id: GlyphId,
advance: Au,
offset: Point2D<Au>,
is_missing: bool,
cluster_start: bool,
ligature_start: bool,
}
impl GlyphData {
pub fn new(id: GlyphId,
advance: Au,
offset: Option<Point2D<Au>>,
is_missing: bool,
cluster_start: bool,
ligature_start: bool)
-> GlyphData {
GlyphData {
id: id,
advance: advance,
offset: offset.unwrap_or(Zero::zero()),
is_missing: is_missing,
cluster_start: cluster_start,
ligature_start: ligature_start,
}
}
}
// This enum is a proxy that's provided to GlyphStore clients when iterating
// through glyphs (either for a particular TextRun offset, or all glyphs).
// Rather than eagerly assembling and copying glyph data, it only retrieves
// values as they are needed from the GlyphStore, using provided offsets.
pub enum GlyphInfo<'a> {
SimpleGlyphInfo(&'a GlyphStore, CharIndex),
DetailGlyphInfo(&'a GlyphStore, CharIndex, u16),
}
impl<'a> GlyphInfo<'a> {
pub fn id(self) -> GlyphId {
match self {
SimpleGlyphInfo(store, entry_i) => store.entry_buffer[entry_i.to_uint()].id(),
DetailGlyphInfo(store, entry_i, detail_j) => {
store.detail_store.get_detailed_glyph_with_index(entry_i, detail_j).id
}
}
}
#[inline(always)]
// FIXME: Resolution conflicts with IteratorUtil trait so adding trailing _
pub fn advance(self) -> Au {
match self {
SimpleGlyphInfo(store, entry_i) => store.entry_buffer[entry_i.to_uint()].advance(),
DetailGlyphInfo(store, entry_i, detail_j) => {
store.detail_store.get_detailed_glyph_with_index(entry_i, detail_j).advance
}
}
}
pub fn offset(self) -> Option<Point2D<Au>> {
match self {
SimpleGlyphInfo(_, _) => None,
DetailGlyphInfo(store, entry_i, detail_j) => {
Some(store.detail_store.get_detailed_glyph_with_index(entry_i, detail_j).offset)
}
}
}
}
/// Stores the glyph data belonging to a text run.
///
/// Simple glyphs are stored inline in the `entry_buffer`, detailed glyphs are
/// stored as pointers into the `detail_store`.
///
/// ~~~
/// +- GlyphStore --------------------------------+
/// | +---+---+---+---+---+---+---+ |
/// | entry_buffer: | | s | | s | | s | s | | d = detailed
/// | +-|-+---+-|-+---+-|-+---+---+ | s = simple
/// | | | | |
/// | | +---+-------+ |
/// | | | |
/// | +-V-+-V-+ |
/// | detail_store: | d | d | |
/// | +---+---+ |
/// +---------------------------------------------+
/// ~~~
pub struct GlyphStore {
// TODO(pcwalton): Allocation of this buffer is expensive. Consider a small-vector
// optimization.
/// A buffer of glyphs within the text run, in the order in which they
/// appear in the input text
entry_buffer: Vec<GlyphEntry>,
/// A store of the detailed glyph data. Detailed glyphs contained in the
/// `entry_buffer` point to locations in this data structure.
detail_store: DetailedGlyphStore,
is_whitespace: bool,
}
int_range_index! {
#[deriving(Encodable)]
#[doc = "An index that refers to a character in a text run. This could \
point to the middle of a glyph."]
struct CharIndex(int)
}
impl<'a> GlyphStore {
// Initializes the glyph store, but doesn't actually shape anything.
// Use the set_glyph, set_glyphs() methods to store glyph data.
pub fn new(length: int, is_whitespace: bool) -> GlyphStore {
assert!(length > 0);
GlyphStore {
entry_buffer: Vec::from_elem(length as uint, GlyphEntry::initial()),
detail_store: DetailedGlyphStore::new(),
is_whitespace: is_whitespace,
}
}
pub fn char_len(&self) -> CharIndex {
CharIndex(self.entry_buffer.len() as int)
}
pub fn is_whitespace(&self) -> bool {
self.is_whitespace
}
pub fn finalize_changes(&mut self) {
self.detail_store.ensure_sorted();
}
pub fn add_glyph_for_char_index(&mut self, i: CharIndex, data: &GlyphData) {
fn glyph_is_compressible(data: &GlyphData) -> bool {
is_simple_glyph_id(data.id)
&& is_simple_advance(data.advance)
&& data.offset.is_zero()
&& data.cluster_start // others are stored in detail buffer
}
assert!(data.ligature_start); // can't compress ligature continuation glyphs.
assert!(i < self.char_len());
let entry = match (data.is_missing, glyph_is_compressible(data)) {
(true, _) => GlyphEntry::missing(1),
(false, true) => GlyphEntry::simple(data.id, data.advance),
(false, false) => {
let glyph = [DetailedGlyph::new(data.id, data.advance, data.offset)];
self.detail_store.add_detailed_glyphs_for_entry(i, glyph);
GlyphEntry::complex(data.cluster_start, data.ligature_start, 1)
}
}.adapt_character_flags_of_entry(self.entry_buffer[i.to_uint()]);
*self.entry_buffer.get_mut(i.to_uint()) = entry;
}
pub fn add_glyphs_for_char_index(&mut self, i: CharIndex, data_for_glyphs: &[GlyphData]) {
assert!(i < self.char_len());
assert!(data_for_glyphs.len() > 0);
let glyph_count = data_for_glyphs.len() as int;
let first_glyph_data = data_for_glyphs[0];
let entry = match first_glyph_data.is_missing {
true => GlyphEntry::missing(glyph_count),
false => {
let glyphs_vec = Vec::from_fn(glyph_count as uint, |i| {
DetailedGlyph::new(data_for_glyphs[i].id,
data_for_glyphs[i].advance,
data_for_glyphs[i].offset)
});
self.detail_store.add_detailed_glyphs_for_entry(i, glyphs_vec.as_slice());
GlyphEntry::complex(first_glyph_data.cluster_start,
first_glyph_data.ligature_start,
glyph_count)
}
}.adapt_character_flags_of_entry(self.entry_buffer[i.to_uint()]);
debug!("Adding multiple glyphs[idx={}, count={}]: {:?}", i, glyph_count, entry);
*self.entry_buffer.get_mut(i.to_uint()) = entry;
}
// used when a character index has no associated glyph---for example, a ligature continuation.
pub fn add_nonglyph_for_char_index(&mut self, i: CharIndex, cluster_start: bool, ligature_start: bool) {
assert!(i < self.char_len());
let entry = GlyphEntry::complex(cluster_start, ligature_start, 0);
debug!("adding spacer for chracter without associated glyph[idx={}]", i);
*self.entry_buffer.get_mut(i.to_uint()) = entry;
}
pub fn iter_glyphs_for_char_index(&'a self, i: CharIndex) -> GlyphIterator<'a> {
self.iter_glyphs_for_char_range(&Range::new(i, CharIndex(1)))
}
#[inline]
pub fn iter_glyphs_for_char_range(&'a self, rang: &Range<CharIndex>) -> GlyphIterator<'a> {
if rang.begin() >= self.char_len() {
fail!("iter_glyphs_for_range: range.begin beyond length!");
}
if rang.end() > self.char_len() {
fail!("iter_glyphs_for_range: range.end beyond length!");
}
GlyphIterator {
store: self,
char_index: rang.begin(),
char_range: rang.each_index(),
glyph_range: None,
}
}
#[inline]
pub fn advance_for_char_range(&self, rang: &Range<CharIndex>) -> Au {
self.iter_glyphs_for_char_range(rang)
.fold(Au(0), |advance, (_, glyph)| advance + glyph.advance())
}
// getter methods
pub fn char_is_space(&self, i: CharIndex) -> bool {
assert!(i < self.char_len());
self.entry_buffer[i.to_uint()].char_is_space()
}
pub fn char_is_tab(&self, i: CharIndex) -> bool {
assert!(i < self.char_len());
self.entry_buffer[i.to_uint()].char_is_tab()
}
pub fn char_is_newline(&self, i: CharIndex) -> bool {
assert!(i < self.char_len());
self.entry_buffer[i.to_uint()].char_is_newline()
}
pub fn is_ligature_start(&self, i: CharIndex) -> bool {
assert!(i < self.char_len());
self.entry_buffer[i.to_uint()].is_ligature_start()
}
pub fn is_cluster_start(&self, i: CharIndex) -> bool {
assert!(i < self.char_len());
self.entry_buffer[i.to_uint()].is_cluster_start()
}
pub fn can_break_before(&self, i: CharIndex) -> BreakType {
assert!(i < self.char_len());
self.entry_buffer[i.to_uint()].can_break_before()
}
// setter methods
pub fn set_char_is_space(&mut self, i: CharIndex) {
assert!(i < self.char_len());
let entry = self.entry_buffer[i.to_uint()];
*self.entry_buffer.get_mut(i.to_uint()) = entry.set_char_is_space();
}
pub fn set_char_is_tab(&mut self, i: CharIndex) {
assert!(i < self.char_len());
let entry = self.entry_buffer[i.to_uint()];
*self.entry_buffer.get_mut(i.to_uint()) = entry.set_char_is_tab();
}
pub fn set_char_is_newline(&mut self, i: CharIndex) {
assert!(i < self.char_len());
let entry = self.entry_buffer[i.to_uint()];
*self.entry_buffer.get_mut(i.to_uint()) = entry.set_char_is_newline();
}
pub fn set_can_break_before(&mut self, i: CharIndex, t: BreakType) {
assert!(i < self.char_len());
let entry = self.entry_buffer[i.to_uint()];
*self.entry_buffer.get_mut(i.to_uint()) = entry.set_can_break_before(t);
}
}
/// An iterator over the glyphs in a character range in a `GlyphStore`.
pub struct GlyphIterator<'a> {
store: &'a GlyphStore,
char_index: CharIndex,
char_range: EachIndex<int, CharIndex>,
glyph_range: Option<EachIndex<int, CharIndex>>,
}
impl<'a> GlyphIterator<'a> {
// Slow path when there is a glyph range.
#[inline(never)]
fn next_glyph_range(&mut self) -> Option<(CharIndex, GlyphInfo<'a>)> {
match self.glyph_range.get_mut_ref().next() {
Some(j) => Some((self.char_index,
DetailGlyphInfo(self.store, self.char_index, j.get() as u16 /* ??? */))),
None => {
// No more glyphs for current character. Try to get another.
self.glyph_range = None;
self.next()
}
}
}
// Slow path when there is a complex glyph.
#[inline(never)]
fn next_complex_glyph(&mut self, entry: &GlyphEntry, i: CharIndex)
-> Option<(CharIndex, GlyphInfo<'a>)> {
let glyphs = self.store.detail_store.get_detailed_glyphs_for_entry(i, entry.glyph_count());
self.glyph_range = Some(range::each_index(CharIndex(0), CharIndex(glyphs.len() as int)));
self.next()
}
}
impl<'a> Iterator<(CharIndex, GlyphInfo<'a>)> for GlyphIterator<'a> {
// I tried to start with something simpler and apply FlatMap, but the
// inability to store free variables in the FlatMap struct was problematic.
//
// This function consists of the fast path and is designed to be inlined into its caller. The
// slow paths, which should not be inlined, are `next_glyph_range()` and
// `next_complex_glyph()`.
#[inline(always)]
fn next(&mut self) -> Option<(CharIndex, GlyphInfo<'a>)> {
// Would use 'match' here but it borrows contents in a way that
// interferes with mutation.
if self.glyph_range.is_some() {
self.next_glyph_range()
} else {
// No glyph range. Look at next character.
self.char_range.next().and_then(|i| {
self.char_index = i;
assert!(i < self.store.char_len());
let entry = self.store.entry_buffer[i.to_uint()];
if entry.is_simple() {
Some((self.char_index, SimpleGlyphInfo(self.store, i)))
} else {
// Fall back to the slow path.
self.next_complex_glyph(&entry, i)
}
})
}
}
}

View 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/. */
/* This file exists just to make it easier to import things inside of
./text/ without specifying the file they came out of imports.
Note that you still must define each of the files as a module in
servo.rc. This is not ideal and may be changed in the future. */
pub use text::shaping::Shaper;
pub use text::text_run::TextRun;
pub mod glyph;
#[path="shaping/mod.rs"] pub mod shaping;
pub mod text_run;
pub mod util;

View file

@ -0,0 +1,541 @@
/* 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/. */
extern crate harfbuzz;
use font::{Font, FontHandleMethods, FontTableMethods, FontTableTag};
use platform::font::FontTable;
use text::glyph::{CharIndex, GlyphStore, GlyphId, GlyphData};
use text::shaping::ShaperMethods;
use text::util::{float_to_fixed, fixed_to_float};
use geom::Point2D;
use harfbuzz::{HB_MEMORY_MODE_READONLY, HB_DIRECTION_LTR};
use harfbuzz::{hb_blob_create, hb_face_create_for_tables};
use harfbuzz::{hb_blob_t};
use harfbuzz::{hb_bool_t};
use harfbuzz::{hb_buffer_add_utf8};
use harfbuzz::{hb_buffer_destroy};
use harfbuzz::{hb_buffer_get_glyph_positions};
use harfbuzz::{hb_buffer_set_direction};
use harfbuzz::{hb_face_destroy};
use harfbuzz::{hb_face_t, hb_font_t};
use harfbuzz::{hb_font_create};
use harfbuzz::{hb_font_destroy, hb_buffer_create};
use harfbuzz::{hb_font_funcs_create};
use harfbuzz::{hb_font_funcs_destroy};
use harfbuzz::{hb_font_funcs_set_glyph_func};
use harfbuzz::{hb_font_funcs_set_glyph_h_advance_func};
use harfbuzz::{hb_font_funcs_set_glyph_h_kerning_func};
use harfbuzz::{hb_font_funcs_t, hb_buffer_t, hb_codepoint_t};
use harfbuzz::{hb_font_set_funcs};
use harfbuzz::{hb_font_set_ppem};
use harfbuzz::{hb_font_set_scale};
use harfbuzz::{hb_glyph_info_t};
use harfbuzz::{hb_glyph_position_t};
use harfbuzz::{hb_position_t, hb_tag_t};
use harfbuzz::{hb_shape, hb_buffer_get_glyph_infos};
use libc::{c_uint, c_int, c_void, c_char};
use servo_util::geometry::Au;
use servo_util::range::Range;
use std::mem;
use std::char;
use std::cmp;
use std::ptr;
static NO_GLYPH: i32 = -1;
static CONTINUATION_BYTE: i32 = -2;
pub struct ShapedGlyphData {
count: int,
glyph_infos: *mut hb_glyph_info_t,
pos_infos: *mut hb_glyph_position_t,
}
pub struct ShapedGlyphEntry {
codepoint: GlyphId,
advance: Au,
offset: Option<Point2D<Au>>,
}
impl ShapedGlyphData {
pub fn new(buffer: *mut hb_buffer_t) -> ShapedGlyphData {
unsafe {
let mut glyph_count = 0;
let glyph_infos = hb_buffer_get_glyph_infos(buffer, &mut glyph_count);
let glyph_count = glyph_count as int;
assert!(glyph_infos.is_not_null());
let mut pos_count = 0;
let pos_infos = hb_buffer_get_glyph_positions(buffer, &mut pos_count);
let pos_count = pos_count as int;
assert!(pos_infos.is_not_null());
assert!(glyph_count == pos_count);
ShapedGlyphData {
count: glyph_count,
glyph_infos: glyph_infos,
pos_infos: pos_infos,
}
}
}
#[inline(always)]
fn byte_offset_of_glyph(&self, i: int) -> int {
assert!(i < self.count);
unsafe {
let glyph_info_i = self.glyph_infos.offset(i);
(*glyph_info_i).cluster as int
}
}
pub fn len(&self) -> int {
self.count
}
/// Returns shaped glyph data for one glyph, and updates the y-position of the pen.
pub fn get_entry_for_glyph(&self, i: int, y_pos: &mut Au) -> ShapedGlyphEntry {
assert!(i < self.count);
unsafe {
let glyph_info_i = self.glyph_infos.offset(i);
let pos_info_i = self.pos_infos.offset(i);
let x_offset = Shaper::fixed_to_float((*pos_info_i).x_offset);
let y_offset = Shaper::fixed_to_float((*pos_info_i).y_offset);
let x_advance = Shaper::fixed_to_float((*pos_info_i).x_advance);
let y_advance = Shaper::fixed_to_float((*pos_info_i).y_advance);
let x_offset = Au::from_frac_px(x_offset);
let y_offset = Au::from_frac_px(y_offset);
let x_advance = Au::from_frac_px(x_advance);
let y_advance = Au::from_frac_px(y_advance);
let offset = if x_offset == Au(0) && y_offset == Au(0) && y_advance == Au(0) {
None
} else {
// adjust the pen..
if y_advance > Au(0) {
*y_pos = *y_pos - y_advance;
}
Some(Point2D(x_offset, *y_pos - y_offset))
};
ShapedGlyphEntry {
codepoint: (*glyph_info_i).codepoint as GlyphId,
advance: x_advance,
offset: offset,
}
}
}
}
pub struct Shaper {
hb_face: *mut hb_face_t,
hb_font: *mut hb_font_t,
hb_funcs: *mut hb_font_funcs_t,
}
#[unsafe_destructor]
impl Drop for Shaper {
fn drop(&mut self) {
unsafe {
assert!(self.hb_face.is_not_null());
hb_face_destroy(self.hb_face);
assert!(self.hb_font.is_not_null());
hb_font_destroy(self.hb_font);
assert!(self.hb_funcs.is_not_null());
hb_font_funcs_destroy(self.hb_funcs);
}
}
}
impl Shaper {
pub fn new(font: &mut Font) -> Shaper {
unsafe {
// Indirection for Rust Issue #6248, dynamic freeze scope artifically extended
let font_ptr = font as *mut Font;
let hb_face: *mut hb_face_t = hb_face_create_for_tables(get_font_table_func,
font_ptr as *mut c_void,
None);
let hb_font: *mut hb_font_t = hb_font_create(hb_face);
// Set points-per-em. if zero, performs no hinting in that direction.
let pt_size = font.pt_size;
hb_font_set_ppem(hb_font, pt_size as c_uint, pt_size as c_uint);
// Set scaling. Note that this takes 16.16 fixed point.
hb_font_set_scale(hb_font,
Shaper::float_to_fixed(pt_size) as c_int,
Shaper::float_to_fixed(pt_size) as c_int);
// configure static function callbacks.
// NB. This funcs structure could be reused globally, as it never changes.
let hb_funcs: *mut hb_font_funcs_t = hb_font_funcs_create();
hb_font_funcs_set_glyph_func(hb_funcs, glyph_func, ptr::mut_null(), None);
hb_font_funcs_set_glyph_h_advance_func(hb_funcs, glyph_h_advance_func, ptr::mut_null(), None);
hb_font_funcs_set_glyph_h_kerning_func(hb_funcs, glyph_h_kerning_func, ptr::mut_null(), ptr::mut_null());
hb_font_set_funcs(hb_font, hb_funcs, font_ptr as *mut c_void, None);
Shaper {
hb_face: hb_face,
hb_font: hb_font,
hb_funcs: hb_funcs,
}
}
}
fn float_to_fixed(f: f64) -> i32 {
float_to_fixed(16, f)
}
fn fixed_to_float(i: hb_position_t) -> f64 {
fixed_to_float(16, i)
}
}
impl ShaperMethods for Shaper {
/// Calculate the layout metrics associated with the given text when rendered in a specific
/// font.
fn shape_text(&self, text: &str, glyphs: &mut GlyphStore) {
unsafe {
let hb_buffer: *mut hb_buffer_t = hb_buffer_create();
hb_buffer_set_direction(hb_buffer, HB_DIRECTION_LTR);
hb_buffer_add_utf8(hb_buffer,
text.as_ptr() as *const c_char,
text.len() as c_int,
0,
text.len() as c_int);
hb_shape(self.hb_font, hb_buffer, ptr::mut_null(), 0);
self.save_glyph_results(text, glyphs, hb_buffer);
hb_buffer_destroy(hb_buffer);
}
}
}
impl Shaper {
fn save_glyph_results(&self, text: &str, glyphs: &mut GlyphStore, buffer: *mut hb_buffer_t) {
let glyph_data = ShapedGlyphData::new(buffer);
let glyph_count = glyph_data.len();
let byte_max = text.len() as int;
let char_max = text.char_len() as int;
// GlyphStore records are indexed by character, not byte offset.
// so, we must be careful to increment this when saving glyph entries.
let mut char_idx = CharIndex(0);
assert!(glyph_count <= char_max);
debug!("Shaped text[char count={}], got back {} glyph info records.",
char_max,
glyph_count);
if char_max != glyph_count {
debug!("NOTE: Since these are not equal, we probably have been given some complex \
glyphs.");
}
// make map of what chars have glyphs
let mut byteToGlyph: Vec<i32>;
// fast path: all chars are single-byte.
if byte_max == char_max {
byteToGlyph = Vec::from_elem(byte_max as uint, NO_GLYPH);
} else {
byteToGlyph = Vec::from_elem(byte_max as uint, CONTINUATION_BYTE);
for (i, _) in text.char_indices() {
*byteToGlyph.get_mut(i) = NO_GLYPH;
}
}
debug!("(glyph idx) -> (text byte offset)");
for i in range(0, glyph_data.len()) {
// loc refers to a *byte* offset within the utf8 string.
let loc = glyph_data.byte_offset_of_glyph(i);
if loc < byte_max {
assert!(*byteToGlyph.get(loc as uint) != CONTINUATION_BYTE);
*byteToGlyph.get_mut(loc as uint) = i as i32;
} else {
debug!("ERROR: tried to set out of range byteToGlyph: idx={}, glyph idx={}",
loc,
i);
}
debug!("{} -> {}", i, loc);
}
debug!("text: {:s}", text);
debug!("(char idx): char->(glyph index):");
for (i, ch) in text.char_indices() {
debug!("{}: {} --> {:d}", i, ch, *byteToGlyph.get(i) as int);
}
// some helpers
let mut glyph_span: Range<int> = Range::empty();
// this span contains first byte of first char, to last byte of last char in range.
// so, end() points to first byte of last+1 char, if it's less than byte_max.
let mut char_byte_span: Range<int> = Range::empty();
let mut y_pos = Au(0);
// main loop over each glyph. each iteration usually processes 1 glyph and 1+ chars.
// in cases with complex glyph-character assocations, 2+ glyphs and 1+ chars can be
// processed.
while glyph_span.begin() < glyph_count {
// start by looking at just one glyph.
glyph_span.extend_by(1);
debug!("Processing glyph at idx={}", glyph_span.begin());
let char_byte_start = glyph_data.byte_offset_of_glyph(glyph_span.begin());
char_byte_span.reset(char_byte_start, 0);
// find a range of chars corresponding to this glyph, plus
// any trailing chars that do not have associated glyphs.
while char_byte_span.end() < byte_max {
let range = text.char_range_at(char_byte_span.end() as uint);
drop(range.ch);
char_byte_span.extend_to(range.next as int);
debug!("Processing char byte span: off={}, len={} for glyph idx={}",
char_byte_span.begin(), char_byte_span.length(), glyph_span.begin());
while char_byte_span.end() != byte_max &&
byteToGlyph[char_byte_span.end() as uint] == NO_GLYPH {
debug!("Extending char byte span to include byte offset={} with no associated \
glyph", char_byte_span.end());
let range = text.char_range_at(char_byte_span.end() as uint);
drop(range.ch);
char_byte_span.extend_to(range.next as int);
}
// extend glyph range to max glyph index covered by char_span,
// in cases where one char made several glyphs and left some unassociated chars.
let mut max_glyph_idx = glyph_span.end();
for i in char_byte_span.each_index() {
if byteToGlyph[i as uint] > NO_GLYPH {
max_glyph_idx = cmp::max(byteToGlyph[i as uint] as int + 1, max_glyph_idx);
}
}
if max_glyph_idx > glyph_span.end() {
glyph_span.extend_to(max_glyph_idx);
debug!("Extended glyph span (off={}, len={}) to cover char byte span's max \
glyph index",
glyph_span.begin(), glyph_span.length());
}
// if there's just one glyph, then we don't need further checks.
if glyph_span.length() == 1 { break; }
// if no glyphs were found yet, extend the char byte range more.
if glyph_span.length() == 0 { continue; }
debug!("Complex (multi-glyph to multi-char) association found. This case \
probably doesn't work.");
let mut all_glyphs_are_within_cluster: bool = true;
for j in glyph_span.each_index() {
let loc = glyph_data.byte_offset_of_glyph(j);
if !char_byte_span.contains(loc) {
all_glyphs_are_within_cluster = false;
break
}
}
debug!("All glyphs within char_byte_span cluster?: {}",
all_glyphs_are_within_cluster);
// found a valid range; stop extending char_span.
if all_glyphs_are_within_cluster {
break
}
}
// character/glyph clump must contain characters.
assert!(char_byte_span.length() > 0);
// character/glyph clump must contain glyphs.
assert!(glyph_span.length() > 0);
// now char_span is a ligature clump, formed by the glyphs in glyph_span.
// we need to find the chars that correspond to actual glyphs (char_extended_span),
//and set glyph info for those and empty infos for the chars that are continuations.
// a simple example:
// chars: 'f' 't' 't'
// glyphs: 'ftt' '' ''
// cgmap: t f f
// gspan: [-]
// cspan: [-]
// covsp: [---------------]
let mut covered_byte_span = char_byte_span.clone();
// extend, clipping at end of text range.
while covered_byte_span.end() < byte_max
&& byteToGlyph[covered_byte_span.end() as uint] == NO_GLYPH {
let range = text.char_range_at(covered_byte_span.end() as uint);
drop(range.ch);
covered_byte_span.extend_to(range.next as int);
}
if covered_byte_span.begin() >= byte_max {
// oops, out of range. clip and forget this clump.
let end = glyph_span.end(); // FIXME: borrow checker workaround
glyph_span.reset(end, 0);
let end = char_byte_span.end(); // FIXME: borrow checker workaround
char_byte_span.reset(end, 0);
}
// clamp to end of text. (I don't think this will be necessary, but..)
let end = covered_byte_span.end(); // FIXME: borrow checker workaround
covered_byte_span.extend_to(cmp::min(end, byte_max));
// fast path: 1-to-1 mapping of single char and single glyph.
if glyph_span.length() == 1 {
// TODO(Issue #214): cluster ranges need to be computed before
// shaping, and then consulted here.
// for now, just pretend that every character is a cluster start.
// (i.e., pretend there are no combining character sequences).
// 1-to-1 mapping of character to glyph also treated as ligature start.
let shape = glyph_data.get_entry_for_glyph(glyph_span.begin(), &mut y_pos);
let data = GlyphData::new(shape.codepoint,
shape.advance,
shape.offset,
false,
true,
true);
glyphs.add_glyph_for_char_index(char_idx, &data);
} else {
// collect all glyphs to be assigned to the first character.
let mut datas = vec!();
for glyph_i in glyph_span.each_index() {
let shape = glyph_data.get_entry_for_glyph(glyph_i, &mut y_pos);
datas.push(GlyphData::new(shape.codepoint,
shape.advance,
shape.offset,
false, // not missing
true, // treat as cluster start
glyph_i > glyph_span.begin()));
// all but first are ligature continuations
}
// now add the detailed glyph entry.
glyphs.add_glyphs_for_char_index(char_idx, datas.as_slice());
// set the other chars, who have no glyphs
let mut i = covered_byte_span.begin();
loop {
let range = text.char_range_at(i as uint);
drop(range.ch);
i = range.next as int;
if i >= covered_byte_span.end() { break; }
char_idx = char_idx + CharIndex(1);
glyphs.add_nonglyph_for_char_index(char_idx, false, false);
}
}
// shift up our working spans past things we just handled.
let end = glyph_span.end(); // FIXME: borrow checker workaround
glyph_span.reset(end, 0);
let end = char_byte_span.end();; // FIXME: borrow checker workaround
char_byte_span.reset(end, 0);
char_idx = char_idx + CharIndex(1);
}
// this must be called after adding all glyph data; it sorts the
// lookup table for finding detailed glyphs by associated char index.
glyphs.finalize_changes();
}
}
/// Callbacks from Harfbuzz when font map and glyph advance lookup needed.
extern fn glyph_func(_: *mut hb_font_t,
font_data: *mut c_void,
unicode: hb_codepoint_t,
_: hb_codepoint_t,
glyph: *mut hb_codepoint_t,
_: *mut c_void)
-> hb_bool_t {
let font: *const Font = font_data as *const Font;
assert!(font.is_not_null());
unsafe {
match (*font).glyph_index(char::from_u32(unicode).unwrap()) {
Some(g) => {
*glyph = g as hb_codepoint_t;
true as hb_bool_t
}
None => false as hb_bool_t
}
}
}
extern fn glyph_h_advance_func(_: *mut hb_font_t,
font_data: *mut c_void,
glyph: hb_codepoint_t,
_: *mut c_void)
-> hb_position_t {
let font: *mut Font = font_data as *mut Font;
assert!(font.is_not_null());
unsafe {
let advance = (*font).glyph_h_advance(glyph as GlyphId);
Shaper::float_to_fixed(advance)
}
}
extern fn glyph_h_kerning_func(_: *mut hb_font_t,
font_data: *mut c_void,
first_glyph: hb_codepoint_t,
second_glyph: hb_codepoint_t,
_: *mut c_void)
-> hb_position_t {
let font: *mut Font = font_data as *mut Font;
assert!(font.is_not_null());
unsafe {
let advance = (*font).glyph_h_kerning(first_glyph as GlyphId, second_glyph as GlyphId);
Shaper::float_to_fixed(advance)
}
}
// Callback to get a font table out of a font.
extern fn get_font_table_func(_: *mut hb_face_t, tag: hb_tag_t, user_data: *mut c_void) -> *mut hb_blob_t {
unsafe {
let font: *const Font = user_data as *const Font;
assert!(font.is_not_null());
// TODO(Issue #197): reuse font table data, which will change the unsound trickery here.
match (*font).get_table_for_tag(tag as FontTableTag) {
None => ptr::mut_null(),
Some(ref font_table) => {
let skinny_font_table_ptr: *const FontTable = font_table; // private context
let mut blob: *mut hb_blob_t = ptr::mut_null();
(*skinny_font_table_ptr).with_buffer(|buf: *const u8, len: uint| {
// HarfBuzz calls `destroy_blob_func` when the buffer is no longer needed.
blob = hb_blob_create(buf as *const c_char,
len as c_uint,
HB_MEMORY_MODE_READONLY,
mem::transmute(skinny_font_table_ptr),
destroy_blob_func);
});
assert!(blob.is_not_null());
blob
}
}
}
}
// TODO(Issue #197): reuse font table data, which will change the unsound trickery here.
// In particular, we'll need to cast to a boxed, rather than owned, FontTable.
// even better, should cache the harfbuzz blobs directly instead of recreating a lot.
extern fn destroy_blob_func(_: *mut c_void) {
// TODO: Previous code here was broken. Rewrite.
}

View file

@ -0,0 +1,19 @@
/* 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/. */
//! Shaper encapsulates a specific shaper, such as Harfbuzz,
//! Uniscribe, Pango, or Coretext.
//!
//! Currently, only harfbuzz bindings are implemented.
use text::glyph::GlyphStore;
pub use Shaper = text::shaping::harfbuzz::Shaper;
pub mod harfbuzz;
pub trait ShaperMethods {
fn shape_text(&self, text: &str, glyphs: &mut GlyphStore);
}

View file

@ -0,0 +1,271 @@
/* 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 font::{Font, RunMetrics, FontMetrics};
use servo_util::geometry::Au;
use servo_util::range::Range;
use servo_util::vec::{Comparator, FullBinarySearchMethods};
use std::slice::Items;
use sync::Arc;
use text::glyph::{CharIndex, GlyphStore};
use font::FontHandleMethods;
use platform::font_template::FontTemplateData;
/// A single "paragraph" of text in one font size and style.
#[deriving(Clone)]
pub struct TextRun {
pub text: Arc<String>,
pub font_template: Arc<FontTemplateData>,
pub pt_size: f64,
pub font_metrics: FontMetrics,
/// The glyph runs that make up this text run.
pub glyphs: Arc<Vec<GlyphRun>>,
}
/// A single series of glyphs within a text run.
#[deriving(Clone)]
pub struct GlyphRun {
/// The glyphs.
glyph_store: Arc<GlyphStore>,
/// The range of characters in the containing run.
range: Range<CharIndex>,
}
pub struct SliceIterator<'a> {
glyph_iter: Items<'a, GlyphRun>,
range: Range<CharIndex>,
}
struct CharIndexComparator;
impl Comparator<CharIndex,GlyphRun> for CharIndexComparator {
fn compare(&self, key: &CharIndex, value: &GlyphRun) -> Ordering {
if *key < value.range.begin() {
Less
} else if *key >= value.range.end() {
Greater
} else {
Equal
}
}
}
impl<'a> Iterator<(&'a GlyphStore, CharIndex, Range<CharIndex>)> for SliceIterator<'a> {
// inline(always) due to the inefficient rt failures messing up inline heuristics, I think.
#[inline(always)]
fn next(&mut self) -> Option<(&'a GlyphStore, CharIndex, Range<CharIndex>)> {
let slice_glyphs = self.glyph_iter.next();
if slice_glyphs.is_none() {
return None;
}
let slice_glyphs = slice_glyphs.unwrap();
let mut char_range = self.range.intersect(&slice_glyphs.range);
let slice_range_begin = slice_glyphs.range.begin();
char_range.shift_by(-slice_range_begin);
if !char_range.is_empty() {
return Some((&*slice_glyphs.glyph_store, slice_range_begin, char_range))
}
return None;
}
}
pub struct LineIterator<'a> {
range: Range<CharIndex>,
clump: Option<Range<CharIndex>>,
slices: SliceIterator<'a>,
}
impl<'a> Iterator<Range<CharIndex>> for LineIterator<'a> {
fn next(&mut self) -> Option<Range<CharIndex>> {
// Loop until we hit whitespace and are in a clump.
loop {
match self.slices.next() {
Some((glyphs, offset, slice_range)) => {
match (glyphs.is_whitespace(), self.clump) {
(false, Some(ref mut c)) => {
c.extend_by(slice_range.length());
}
(false, None) => {
let mut c = slice_range;
c.shift_by(offset);
self.clump = Some(c);
}
(true, None) => { /* chomp whitespace */ }
(true, Some(c)) => {
self.clump = None;
// The final whitespace clump is not included.
return Some(c);
}
}
},
None => {
// flush any remaining chars as a line
if self.clump.is_some() {
let mut c = self.clump.take_unwrap();
c.extend_to(self.range.end());
return Some(c);
} else {
return None;
}
}
}
}
}
}
impl<'a> TextRun {
pub fn new(font: &mut Font, text: String) -> TextRun {
let glyphs = TextRun::break_and_shape(font, text.as_slice());
let run = TextRun {
text: Arc::new(text),
font_metrics: font.metrics.clone(),
font_template: font.handle.get_template(),
pt_size: font.pt_size,
glyphs: Arc::new(glyphs),
};
return run;
}
pub fn break_and_shape(font: &mut Font, text: &str) -> Vec<GlyphRun> {
// TODO(Issue #230): do a better job. See Gecko's LineBreaker.
let mut glyphs = vec!();
let (mut byte_i, mut char_i) = (0u, CharIndex(0));
let mut cur_slice_is_whitespace = false;
let (mut byte_last_boundary, mut char_last_boundary) = (0, CharIndex(0));
while byte_i < text.len() {
let range = text.char_range_at(byte_i);
let ch = range.ch;
let next = range.next;
// Slices alternate between whitespace and non-whitespace,
// representing line break opportunities.
let can_break_before = if cur_slice_is_whitespace {
match ch {
' ' | '\t' | '\n' => false,
_ => {
cur_slice_is_whitespace = false;
true
}
}
} else {
match ch {
' ' | '\t' | '\n' => {
cur_slice_is_whitespace = true;
true
},
_ => false
}
};
// Create a glyph store for this slice if it's nonempty.
if can_break_before && byte_i > byte_last_boundary {
let slice = text.slice(byte_last_boundary, byte_i).to_string();
debug!("creating glyph store for slice {} (ws? {}), {} - {} in run {}",
slice, !cur_slice_is_whitespace, byte_last_boundary, byte_i, text);
glyphs.push(GlyphRun {
glyph_store: font.shape_text(slice, !cur_slice_is_whitespace),
range: Range::new(char_last_boundary, char_i - char_last_boundary),
});
byte_last_boundary = byte_i;
char_last_boundary = char_i;
}
byte_i = next;
char_i = char_i + CharIndex(1);
}
// Create a glyph store for the final slice if it's nonempty.
if byte_i > byte_last_boundary {
let slice = text.slice_from(byte_last_boundary).to_string();
debug!("creating glyph store for final slice {} (ws? {}), {} - {} in run {}",
slice, cur_slice_is_whitespace, byte_last_boundary, text.len(), text);
glyphs.push(GlyphRun {
glyph_store: font.shape_text(slice, cur_slice_is_whitespace),
range: Range::new(char_last_boundary, char_i - char_last_boundary),
});
}
glyphs
}
pub fn char_len(&self) -> CharIndex {
match self.glyphs.last() {
None => CharIndex(0),
Some(ref glyph_run) => glyph_run.range.end(),
}
}
pub fn glyphs(&'a self) -> &'a Vec<GlyphRun> {
&*self.glyphs
}
pub fn range_is_trimmable_whitespace(&self, range: &Range<CharIndex>) -> bool {
self.iter_slices_for_range(range).all(|(slice_glyphs, _, _)| {
slice_glyphs.is_whitespace()
})
}
pub fn ascent(&self) -> Au {
self.font_metrics.ascent
}
pub fn descent(&self) -> Au {
self.font_metrics.descent
}
pub fn advance_for_range(&self, range: &Range<CharIndex>) -> Au {
// TODO(Issue #199): alter advance direction for RTL
// TODO(Issue #98): using inter-char and inter-word spacing settings when measuring text
self.iter_slices_for_range(range)
.fold(Au(0), |advance, (glyphs, _, slice_range)| {
advance + glyphs.advance_for_char_range(&slice_range)
})
}
pub fn metrics_for_range(&self, range: &Range<CharIndex>) -> RunMetrics {
RunMetrics::new(self.advance_for_range(range),
self.font_metrics.ascent,
self.font_metrics.descent)
}
pub fn metrics_for_slice(&self, glyphs: &GlyphStore, slice_range: &Range<CharIndex>) -> RunMetrics {
RunMetrics::new(glyphs.advance_for_char_range(slice_range),
self.font_metrics.ascent,
self.font_metrics.descent)
}
pub fn min_width_for_range(&self, range: &Range<CharIndex>) -> Au {
debug!("iterating outer range {:?}", range);
self.iter_slices_for_range(range).fold(Au(0), |max_piece_width, (_, offset, slice_range)| {
debug!("iterated on {:?}[{:?}]", offset, slice_range);
Au::max(max_piece_width, self.advance_for_range(&slice_range))
})
}
/// Returns the index of the first glyph run containing the given character index.
fn index_of_first_glyph_run_containing(&self, index: CharIndex) -> Option<uint> {
self.glyphs.as_slice().binary_search_index_by(&index, CharIndexComparator)
}
pub fn iter_slices_for_range(&'a self, range: &Range<CharIndex>) -> SliceIterator<'a> {
let index = match self.index_of_first_glyph_run_containing(range.begin()) {
None => self.glyphs.len(),
Some(index) => index,
};
SliceIterator {
glyph_iter: self.glyphs.slice_from(index).iter(),
range: *range,
}
}
pub fn iter_natural_lines_for_range(&'a self, range: &Range<CharIndex>) -> LineIterator<'a> {
LineIterator {
range: *range,
clump: None,
slices: self.iter_slices_for_range(range),
}
}
}

285
components/gfx/text/util.rs Normal file
View file

@ -0,0 +1,285 @@
/* 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 text::glyph::CharIndex;
#[deriving(PartialEq)]
pub enum CompressionMode {
CompressNone,
CompressWhitespace,
CompressWhitespaceNewline,
DiscardNewline
}
// ported from Gecko's nsTextFrameUtils::TransformText.
//
// High level TODOs:
//
// * Issue #113: consider incoming text state (arabic, etc)
// and propogate outgoing text state (dual of above)
//
// * Issue #114: record skipped and kept chars for mapping original to new text
//
// * Untracked: various edge cases for bidi, CJK, etc.
pub fn transform_text(text: &str, mode: CompressionMode,
incoming_whitespace: bool,
new_line_pos: &mut Vec<CharIndex>) -> (String, bool) {
let mut out_str = String::new();
let out_whitespace = match mode {
CompressNone | DiscardNewline => {
let mut new_line_index = CharIndex(0);
for ch in text.chars() {
if is_discardable_char(ch, mode) {
// TODO: record skipped char
} else {
// TODO: record kept char
if ch == '\t' {
// TODO: set "has tab" flag
} else if ch == '\n' {
// Save new-line's position for line-break
// This value is relative(not absolute)
new_line_pos.push(new_line_index);
new_line_index = CharIndex(0);
}
if ch != '\n' {
new_line_index = new_line_index + CharIndex(1);
}
out_str.push_char(ch);
}
}
text.len() > 0 && is_in_whitespace(text.char_at_reverse(0), mode)
},
CompressWhitespace | CompressWhitespaceNewline => {
let mut in_whitespace: bool = incoming_whitespace;
for ch in text.chars() {
// TODO: discard newlines between CJK chars
let mut next_in_whitespace: bool = is_in_whitespace(ch, mode);
if !next_in_whitespace {
if is_always_discardable_char(ch) {
// revert whitespace setting, since this char was discarded
next_in_whitespace = in_whitespace;
// TODO: record skipped char
} else {
// TODO: record kept char
out_str.push_char(ch);
}
} else { /* next_in_whitespace; possibly add a space char */
if in_whitespace {
// TODO: record skipped char
} else {
// TODO: record kept char
out_str.push_char(' ');
}
}
// save whitespace context for next char
in_whitespace = next_in_whitespace;
} /* /for str::each_char */
in_whitespace
}
};
return (out_str.into_string(), out_whitespace);
fn is_in_whitespace(ch: char, mode: CompressionMode) -> bool {
match (ch, mode) {
(' ', _) => true,
('\t', _) => true,
('\n', CompressWhitespaceNewline) => true,
(_, _) => false
}
}
fn is_discardable_char(ch: char, mode: CompressionMode) -> bool {
if is_always_discardable_char(ch) {
return true;
}
match mode {
DiscardNewline | CompressWhitespaceNewline => ch == '\n',
_ => false
}
}
fn is_always_discardable_char(_ch: char) -> bool {
// TODO: check for bidi control chars, soft hyphens.
false
}
}
pub fn float_to_fixed(before: int, f: f64) -> i32 {
(1i32 << before as uint) * (f as i32)
}
pub fn fixed_to_float(before: int, f: i32) -> f64 {
f as f64 * 1.0f64 / ((1i32 << before as uint) as f64)
}
pub fn fixed_to_rounded_int(before: int, f: i32) -> int {
let half = 1i32 << (before-1) as uint;
if f > 0i32 {
((half + f) >> before as uint) as int
} else {
-((half - f) >> before as uint) as int
}
}
/* Generate a 32-bit TrueType tag from its 4 characters */
pub fn true_type_tag(a: char, b: char, c: char, d: char) -> u32 {
let a = a as u32;
let b = b as u32;
let c = c as u32;
let d = d as u32;
(a << 24 | b << 16 | c << 8 | d) as u32
}
#[test]
fn test_true_type_tag() {
assert_eq!(true_type_tag('c', 'm', 'a', 'p'), 0x_63_6D_61_70_u32);
}
#[test]
fn test_transform_compress_none() {
let test_strs = vec!(
" foo bar",
"foo bar ",
"foo\n bar",
"foo \nbar",
" foo bar \nbaz",
"foo bar baz",
"foobarbaz\n\n"
);
let mode = CompressNone;
for test in test_strs.iter() {
let mut new_line_pos = vec!();
let (trimmed_str, _out) = transform_text(*test, mode, true, &mut new_line_pos);
assert_eq!(trimmed_str.as_slice(), *test)
}
}
#[test]
fn test_transform_discard_newline() {
let test_strs = vec!(
" foo bar",
"foo bar ",
"foo\n bar",
"foo \nbar",
" foo bar \nbaz",
"foo bar baz",
"foobarbaz\n\n"
);
let oracle_strs = vec!(
" foo bar",
"foo bar ",
"foo bar",
"foo bar",
" foo bar baz",
"foo bar baz",
"foobarbaz"
);
assert_eq!(test_strs.len(), oracle_strs.len());
let mode = DiscardNewline;
for (test, oracle) in test_strs.iter().zip(oracle_strs.iter()) {
let mut new_line_pos = vec!();
let (trimmed_str, _out) = transform_text(*test, mode, true, &mut new_line_pos);
assert_eq!(trimmed_str.as_slice(), *oracle)
}
}
/* FIXME: Fix and re-enable
#[test]
fn test_transform_compress_whitespace() {
let test_strs : ~[String] = ~[" foo bar".to_string(),
"foo bar ".to_string(),
"foo\n bar".to_string(),
"foo \nbar".to_string(),
" foo bar \nbaz".to_string(),
"foo bar baz".to_string(),
"foobarbaz\n\n".to_string()];
let oracle_strs : ~[String] = ~[" foo bar".to_string(),
"foo bar ".to_string(),
"foo\n bar".to_string(),
"foo \nbar".to_string(),
" foo bar \nbaz".to_string(),
"foo bar baz".to_string(),
"foobarbaz\n\n".to_string()];
assert_eq!(test_strs.len(), oracle_strs.len());
let mode = CompressWhitespace;
for i in range(0, test_strs.len()) {
let mut new_line_pos = ~[];
let (trimmed_str, _out) = transform_text(test_strs[i], mode, true, &mut new_line_pos);
assert_eq!(&trimmed_str, &oracle_strs[i])
}
}
#[test]
fn test_transform_compress_whitespace_newline() {
let test_strs : ~[String] = ~[" foo bar".to_string(),
"foo bar ".to_string(),
"foo\n bar".to_string(),
"foo \nbar".to_string(),
" foo bar \nbaz".to_string(),
"foo bar baz".to_string(),
"foobarbaz\n\n".to_string()];
let oracle_strs : ~[String] = ~["foo bar".to_string(),
"foo bar ".to_string(),
"foo bar".to_string(),
"foo bar".to_string(),
" foo bar baz".to_string(),
"foo bar baz".to_string(),
"foobarbaz ".to_string()];
assert_eq!(test_strs.len(), oracle_strs.len());
let mode = CompressWhitespaceNewline;
for i in range(0, test_strs.len()) {
let mut new_line_pos = ~[];
let (trimmed_str, _out) = transform_text(test_strs[i], mode, true, &mut new_line_pos);
assert_eq!(&trimmed_str, &oracle_strs[i])
}
}
*/
#[test]
fn test_transform_compress_whitespace_newline_no_incoming() {
let test_strs = vec!(
" foo bar",
"\nfoo bar",
"foo bar ",
"foo\n bar",
"foo \nbar",
" foo bar \nbaz",
"foo bar baz",
"foobarbaz\n\n"
);
let oracle_strs = vec!(
" foo bar",
" foo bar",
"foo bar ",
"foo bar",
"foo bar",
" foo bar baz",
"foo bar baz",
"foobarbaz "
);
assert_eq!(test_strs.len(), oracle_strs.len());
let mode = CompressWhitespaceNewline;
for (test, oracle) in test_strs.iter().zip(oracle_strs.iter()) {
let mut new_line_pos = vec!();
let (trimmed_str, _out) = transform_text(*test, mode, false, &mut new_line_pos);
assert_eq!(trimmed_str.as_slice(), *oracle)
}
}

View file

@ -0,0 +1,38 @@
[package]
name = "layout"
version = "0.0.1"
authors = ["The Servo Project Developers"]
[lib]
name = "layout"
path = "lib.rs"
[dependencies.gfx]
path = "../gfx"
[dependencies.script]
path = "../script"
[dependencies.layout_traits]
path = "../layout_traits"
[dependencies.script_traits]
path = "../script_traits"
[dependencies.style]
path = "../style"
[dependencies.macros]
path = "../macros"
[dependencies.net]
path = "../net"
[dependencies.util]
path = "../util"
[dependencies.geom]
git = "https://github.com/servo/rust-geom"
[dependencies.url]
git = "https://github.com/servo/rust-url"

2428
components/layout/block.rs Normal file

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,123 @@
/* 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/. */
//! Data needed by the layout task.
use css::matching::{ApplicableDeclarationsCache, StyleSharingCandidateCache};
use geom::{Rect, Size2D};
use gfx::display_list::OpaqueNode;
use gfx::font_context::FontContext;
use gfx::font_cache_task::FontCacheTask;
use script::layout_interface::LayoutChan;
use servo_msg::constellation_msg::ConstellationChan;
use servo_net::local_image_cache::LocalImageCache;
use servo_util::geometry::Au;
use servo_util::opts::Opts;
use sync::{Arc, Mutex};
use std::mem;
use style::Stylist;
use url::Url;
struct LocalLayoutContext {
font_context: FontContext,
applicable_declarations_cache: ApplicableDeclarationsCache,
style_sharing_candidate_cache: StyleSharingCandidateCache,
}
local_data_key!(local_context_key: *mut LocalLayoutContext)
fn create_or_get_local_context(shared_layout_context: &SharedLayoutContext) -> *mut LocalLayoutContext {
let maybe_context = local_context_key.get();
let context = match maybe_context {
None => {
let context = box LocalLayoutContext {
font_context: FontContext::new(shared_layout_context.font_cache_task.clone()),
applicable_declarations_cache: ApplicableDeclarationsCache::new(),
style_sharing_candidate_cache: StyleSharingCandidateCache::new(),
};
local_context_key.replace(Some(unsafe { mem::transmute(context) }));
local_context_key.get().unwrap()
},
Some(context) => context
};
*context
}
pub struct SharedLayoutContext {
/// The local image cache.
pub image_cache: Arc<Mutex<LocalImageCache>>,
/// The current screen size.
pub screen_size: Size2D<Au>,
/// A channel up to the constellation.
pub constellation_chan: ConstellationChan,
/// A channel up to the layout task.
pub layout_chan: LayoutChan,
/// Interface to the font cache task.
pub font_cache_task: FontCacheTask,
/// The CSS selector stylist.
///
/// FIXME(#2604): Make this no longer an unsafe pointer once we have fast `RWArc`s.
pub stylist: *const Stylist,
/// The root node at which we're starting the layout.
pub reflow_root: OpaqueNode,
/// The URL.
pub url: Url,
/// The command line options.
pub opts: Opts,
/// The dirty rectangle, used during display list building.
pub dirty: Rect<Au>,
}
pub struct LayoutContext<'a> {
pub shared: &'a SharedLayoutContext,
cached_local_layout_context: *mut LocalLayoutContext,
}
impl<'a> LayoutContext<'a> {
pub fn new(shared_layout_context: &'a SharedLayoutContext) -> LayoutContext<'a> {
let local_context = create_or_get_local_context(shared_layout_context);
LayoutContext {
shared: shared_layout_context,
cached_local_layout_context: local_context,
}
}
#[inline(always)]
pub fn font_context<'a>(&'a self) -> &'a mut FontContext {
unsafe {
let cached_context = &*self.cached_local_layout_context;
mem::transmute(&cached_context.font_context)
}
}
#[inline(always)]
pub fn applicable_declarations_cache<'a>(&'a self) -> &'a mut ApplicableDeclarationsCache {
unsafe {
let cached_context = &*self.cached_local_layout_context;
mem::transmute(&cached_context.applicable_declarations_cache)
}
}
#[inline(always)]
pub fn style_sharing_candidate_cache<'a>(&'a self) -> &'a mut StyleSharingCandidateCache {
unsafe {
let cached_context = &*self.cached_local_layout_context;
mem::transmute(&cached_context.style_sharing_candidate_cache)
}
}
}

View file

@ -0,0 +1,558 @@
/* 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/. */
// High-level interface to CSS selector matching.
use css::node_style::StyledNode;
use construct::FlowConstructor;
use context::LayoutContext;
use extra::LayoutAuxMethods;
use util::{LayoutDataAccess, LayoutDataWrapper};
use wrapper::{LayoutElement, LayoutNode, PostorderNodeMutTraversal, ThreadSafeLayoutNode};
use servo_util::atom::Atom;
use servo_util::cache::{Cache, LRUCache, SimpleHashCache};
use servo_util::namespace::Null;
use servo_util::smallvec::{SmallVec, SmallVec16};
use servo_util::str::DOMString;
use std::mem;
use std::hash::{Hash, sip};
use std::slice::Items;
use style::{After, Before, ComputedValues, DeclarationBlock, Stylist, TElement, TNode, cascade};
use sync::Arc;
pub struct ApplicableDeclarations {
pub normal: SmallVec16<DeclarationBlock>,
pub before: Vec<DeclarationBlock>,
pub after: Vec<DeclarationBlock>,
/// Whether the `normal` declarations are shareable with other nodes.
pub normal_shareable: bool,
}
impl ApplicableDeclarations {
pub fn new() -> ApplicableDeclarations {
ApplicableDeclarations {
normal: SmallVec16::new(),
before: Vec::new(),
after: Vec::new(),
normal_shareable: false,
}
}
pub fn clear(&mut self) {
self.normal = SmallVec16::new();
self.before = Vec::new();
self.after = Vec::new();
self.normal_shareable = false;
}
}
#[deriving(Clone)]
pub struct ApplicableDeclarationsCacheEntry {
pub declarations: Vec<DeclarationBlock>,
}
impl ApplicableDeclarationsCacheEntry {
fn new(slice: &[DeclarationBlock]) -> ApplicableDeclarationsCacheEntry {
let mut entry_declarations = Vec::new();
for declarations in slice.iter() {
entry_declarations.push(declarations.clone());
}
ApplicableDeclarationsCacheEntry {
declarations: entry_declarations,
}
}
}
impl PartialEq for ApplicableDeclarationsCacheEntry {
fn eq(&self, other: &ApplicableDeclarationsCacheEntry) -> bool {
let this_as_query = ApplicableDeclarationsCacheQuery::new(self.declarations.as_slice());
this_as_query.equiv(other)
}
}
impl Hash for ApplicableDeclarationsCacheEntry {
fn hash(&self, state: &mut sip::SipState) {
let tmp = ApplicableDeclarationsCacheQuery::new(self.declarations.as_slice());
tmp.hash(state);
}
}
struct ApplicableDeclarationsCacheQuery<'a> {
declarations: &'a [DeclarationBlock],
}
impl<'a> ApplicableDeclarationsCacheQuery<'a> {
fn new(declarations: &'a [DeclarationBlock]) -> ApplicableDeclarationsCacheQuery<'a> {
ApplicableDeclarationsCacheQuery {
declarations: declarations,
}
}
}
// Workaround for lack of `ptr_eq` on Arcs...
#[inline]
fn arc_ptr_eq<T>(a: &Arc<T>, b: &Arc<T>) -> bool {
unsafe {
let a: uint = mem::transmute_copy(a);
let b: uint = mem::transmute_copy(b);
a == b
}
}
impl<'a> Equiv<ApplicableDeclarationsCacheEntry> for ApplicableDeclarationsCacheQuery<'a> {
fn equiv(&self, other: &ApplicableDeclarationsCacheEntry) -> bool {
if self.declarations.len() != other.declarations.len() {
return false
}
for (this, other) in self.declarations.iter().zip(other.declarations.iter()) {
if !arc_ptr_eq(&this.declarations, &other.declarations) {
return false
}
}
return true
}
}
impl<'a> Hash for ApplicableDeclarationsCacheQuery<'a> {
fn hash(&self, state: &mut sip::SipState) {
for declaration in self.declarations.iter() {
let ptr: uint = unsafe {
mem::transmute_copy(declaration)
};
ptr.hash(state);
}
}
}
static APPLICABLE_DECLARATIONS_CACHE_SIZE: uint = 32;
pub struct ApplicableDeclarationsCache {
cache: SimpleHashCache<ApplicableDeclarationsCacheEntry,Arc<ComputedValues>>,
}
impl ApplicableDeclarationsCache {
pub fn new() -> ApplicableDeclarationsCache {
ApplicableDeclarationsCache {
cache: SimpleHashCache::new(APPLICABLE_DECLARATIONS_CACHE_SIZE),
}
}
fn find(&self, declarations: &[DeclarationBlock]) -> Option<Arc<ComputedValues>> {
match self.cache.find_equiv(&ApplicableDeclarationsCacheQuery::new(declarations)) {
None => None,
Some(ref values) => Some((*values).clone()),
}
}
fn insert(&mut self, declarations: &[DeclarationBlock], style: Arc<ComputedValues>) {
self.cache.insert(ApplicableDeclarationsCacheEntry::new(declarations), style)
}
}
/// An LRU cache of the last few nodes seen, so that we can aggressively try to reuse their styles.
pub struct StyleSharingCandidateCache {
cache: LRUCache<StyleSharingCandidate,()>,
}
#[deriving(Clone)]
pub struct StyleSharingCandidate {
pub style: Arc<ComputedValues>,
pub parent_style: Arc<ComputedValues>,
pub local_name: Atom,
pub class: Option<DOMString>,
}
impl PartialEq for StyleSharingCandidate {
fn eq(&self, other: &StyleSharingCandidate) -> bool {
arc_ptr_eq(&self.style, &other.style) &&
arc_ptr_eq(&self.parent_style, &other.parent_style) &&
self.local_name == other.local_name &&
self.class == other.class
}
}
impl StyleSharingCandidate {
/// Attempts to create a style sharing candidate from this node. Returns
/// the style sharing candidate or `None` if this node is ineligible for
/// style sharing.
fn new(node: &LayoutNode) -> Option<StyleSharingCandidate> {
let parent_node = match node.parent_node() {
None => return None,
Some(parent_node) => parent_node,
};
if !parent_node.is_element() {
return None
}
let style = unsafe {
match *node.borrow_layout_data_unchecked() {
None => return None,
Some(ref layout_data_ref) => {
match layout_data_ref.shared_data.style {
None => return None,
Some(ref data) => (*data).clone(),
}
}
}
};
let parent_style = unsafe {
match *parent_node.borrow_layout_data_unchecked() {
None => return None,
Some(ref parent_layout_data_ref) => {
match parent_layout_data_ref.shared_data.style {
None => return None,
Some(ref data) => (*data).clone(),
}
}
}
};
let mut style = Some(style);
let mut parent_style = Some(parent_style);
let element = node.as_element();
if element.style_attribute().is_some() {
return None
}
Some(StyleSharingCandidate {
style: style.take_unwrap(),
parent_style: parent_style.take_unwrap(),
local_name: element.get_local_name().clone(),
class: element.get_attr(&Null, "class")
.map(|string| string.to_string()),
})
}
fn can_share_style_with(&self, element: &LayoutElement) -> bool {
if *element.get_local_name() != self.local_name {
return false
}
match (&self.class, element.get_attr(&Null, "class")) {
(&None, Some(_)) | (&Some(_), None) => return false,
(&Some(ref this_class), Some(element_class)) if element_class != this_class.as_slice() => {
return false
}
(&Some(_), Some(_)) | (&None, None) => {}
}
true
}
}
static STYLE_SHARING_CANDIDATE_CACHE_SIZE: uint = 40;
impl StyleSharingCandidateCache {
pub fn new() -> StyleSharingCandidateCache {
StyleSharingCandidateCache {
cache: LRUCache::new(STYLE_SHARING_CANDIDATE_CACHE_SIZE),
}
}
pub fn iter<'a>(&'a self) -> Items<'a,(StyleSharingCandidate,())> {
self.cache.iter()
}
pub fn insert_if_possible(&mut self, node: &LayoutNode) {
match StyleSharingCandidate::new(node) {
None => {}
Some(candidate) => self.cache.insert(candidate, ())
}
}
pub fn touch(&mut self, index: uint) {
self.cache.touch(index)
}
}
/// The results of attempting to share a style.
pub enum StyleSharingResult<'ln> {
/// We didn't find anybody to share the style with. The boolean indicates whether the style
/// is shareable at all.
CannotShare(bool),
/// The node's style can be shared. The integer specifies the index in the LRU cache that was
/// hit.
StyleWasShared(uint),
}
pub trait MatchMethods {
/// Performs aux initialization, selector matching, cascading, and flow construction
/// sequentially.
fn recalc_style_for_subtree(&self,
stylist: &Stylist,
layout_context: &LayoutContext,
applicable_declarations: &mut ApplicableDeclarations,
parent: Option<LayoutNode>);
fn match_node(&self,
stylist: &Stylist,
applicable_declarations: &mut ApplicableDeclarations,
shareable: &mut bool);
/// Attempts to share a style with another node. This method is unsafe because it depends on
/// the `style_sharing_candidate_cache` having only live nodes in it, and we have no way to
/// guarantee that at the type system level yet.
unsafe fn share_style_if_possible(&self,
style_sharing_candidate_cache:
&mut StyleSharingCandidateCache,
parent: Option<LayoutNode>)
-> StyleSharingResult;
unsafe fn cascade_node(&self,
parent: Option<LayoutNode>,
applicable_declarations: &ApplicableDeclarations,
applicable_declarations_cache: &mut ApplicableDeclarationsCache);
}
trait PrivateMatchMethods {
fn cascade_node_pseudo_element(&self,
parent_style: Option<&Arc<ComputedValues>>,
applicable_declarations: &[DeclarationBlock],
style: &mut Option<Arc<ComputedValues>>,
applicable_declarations_cache: &mut
ApplicableDeclarationsCache,
shareable: bool);
fn share_style_with_candidate_if_possible(&self,
parent_node: Option<LayoutNode>,
candidate: &StyleSharingCandidate)
-> Option<Arc<ComputedValues>>;
}
impl<'ln> PrivateMatchMethods for LayoutNode<'ln> {
fn cascade_node_pseudo_element(&self,
parent_style: Option<&Arc<ComputedValues>>,
applicable_declarations: &[DeclarationBlock],
style: &mut Option<Arc<ComputedValues>>,
applicable_declarations_cache: &mut
ApplicableDeclarationsCache,
shareable: bool) {
let this_style;
let cacheable;
match parent_style {
Some(ref parent_style) => {
let cache_entry = applicable_declarations_cache.find(applicable_declarations);
let cached_computed_values = match cache_entry {
None => None,
Some(ref style) => Some(&**style),
};
let (the_style, is_cacheable) = cascade(applicable_declarations,
shareable,
Some(&***parent_style),
cached_computed_values);
cacheable = is_cacheable;
this_style = Arc::new(the_style);
}
None => {
let (the_style, is_cacheable) = cascade(applicable_declarations,
shareable,
None,
None);
cacheable = is_cacheable;
this_style = Arc::new(the_style);
}
};
// Cache the resolved style if it was cacheable.
if cacheable {
applicable_declarations_cache.insert(applicable_declarations, this_style.clone());
}
*style = Some(this_style);
}
fn share_style_with_candidate_if_possible(&self,
parent_node: Option<LayoutNode>,
candidate: &StyleSharingCandidate)
-> Option<Arc<ComputedValues>> {
assert!(self.is_element());
let parent_node = match parent_node {
Some(ref parent_node) if parent_node.is_element() => parent_node,
Some(_) | None => return None,
};
let parent_layout_data: &Option<LayoutDataWrapper> = unsafe {
mem::transmute(parent_node.borrow_layout_data_unchecked())
};
match parent_layout_data {
&Some(ref parent_layout_data_ref) => {
// Check parent style.
let parent_style = parent_layout_data_ref.shared_data.style.as_ref().unwrap();
if !arc_ptr_eq(parent_style, &candidate.parent_style) {
return None
}
// Check tag names, classes, etc.
if !candidate.can_share_style_with(&self.as_element()) {
return None
}
return Some(candidate.style.clone())
}
_ => {}
}
None
}
}
impl<'ln> MatchMethods for LayoutNode<'ln> {
fn match_node(&self,
stylist: &Stylist,
applicable_declarations: &mut ApplicableDeclarations,
shareable: &mut bool) {
let style_attribute = self.as_element().style_attribute().as_ref();
applicable_declarations.normal_shareable =
stylist.push_applicable_declarations(self,
style_attribute,
None,
&mut applicable_declarations.normal);
stylist.push_applicable_declarations(self,
None,
Some(Before),
&mut applicable_declarations.before);
stylist.push_applicable_declarations(self,
None,
Some(After),
&mut applicable_declarations.after);
*shareable = applicable_declarations.normal_shareable
}
unsafe fn share_style_if_possible(&self,
style_sharing_candidate_cache:
&mut StyleSharingCandidateCache,
parent: Option<LayoutNode>)
-> StyleSharingResult {
if !self.is_element() {
return CannotShare(false)
}
let ok = {
let element = self.as_element();
element.style_attribute().is_none() && element.get_attr(&Null, "id").is_none()
};
if !ok {
return CannotShare(false)
}
for (i, &(ref candidate, ())) in style_sharing_candidate_cache.iter().enumerate() {
match self.share_style_with_candidate_if_possible(parent.clone(), candidate) {
Some(shared_style) => {
// Yay, cache hit. Share the style.
let mut layout_data_ref = self.mutate_layout_data();
layout_data_ref.get_mut_ref().shared_data.style = Some(shared_style);
return StyleWasShared(i)
}
None => {}
}
}
CannotShare(true)
}
fn recalc_style_for_subtree(&self,
stylist: &Stylist,
layout_context: &LayoutContext,
applicable_declarations: &mut ApplicableDeclarations,
parent: Option<LayoutNode>) {
self.initialize_layout_data(layout_context.shared.layout_chan.clone());
// First, check to see whether we can share a style with someone.
let sharing_result = unsafe {
self.share_style_if_possible(layout_context.style_sharing_candidate_cache(), parent.clone())
};
// Otherwise, match and cascade selectors.
match sharing_result {
CannotShare(mut shareable) => {
if self.is_element() {
self.match_node(stylist, applicable_declarations, &mut shareable)
}
unsafe {
self.cascade_node(parent,
applicable_declarations,
layout_context.applicable_declarations_cache())
}
applicable_declarations.clear();
// Add ourselves to the LRU cache.
if shareable {
layout_context.style_sharing_candidate_cache().insert_if_possible(self)
}
}
StyleWasShared(index) => layout_context.style_sharing_candidate_cache().touch(index),
}
for kid in self.children() {
kid.recalc_style_for_subtree(stylist,
layout_context,
applicable_declarations,
Some(self.clone()))
}
// Construct flows.
let layout_node = ThreadSafeLayoutNode::new(self);
let mut flow_constructor = FlowConstructor::new(layout_context);
flow_constructor.process(&layout_node);
}
unsafe fn cascade_node(&self,
parent: Option<LayoutNode>,
applicable_declarations: &ApplicableDeclarations,
applicable_declarations_cache: &mut ApplicableDeclarationsCache) {
// Get our parent's style. This must be unsafe so that we don't touch the parent's
// borrow flags.
//
// FIXME(pcwalton): Isolate this unsafety into the `wrapper` module to allow
// enforced safe, race-free access to the parent style.
let parent_style = match parent {
None => None,
Some(parent_node) => {
let parent_layout_data = parent_node.borrow_layout_data_unchecked();
match *parent_layout_data {
None => fail!("no parent data?!"),
Some(ref parent_layout_data) => {
match parent_layout_data.shared_data.style {
None => fail!("parent hasn't been styled yet?!"),
Some(ref style) => Some(style),
}
}
}
}
};
let mut layout_data_ref = self.mutate_layout_data();
match &mut *layout_data_ref {
&None => fail!("no layout data"),
&Some(ref mut layout_data) => {
self.cascade_node_pseudo_element(parent_style,
applicable_declarations.normal.as_slice(),
&mut layout_data.shared_data.style,
applicable_declarations_cache,
applicable_declarations.normal_shareable);
if applicable_declarations.before.len() > 0 {
self.cascade_node_pseudo_element(Some(layout_data.shared_data.style.get_ref()),
applicable_declarations.before.as_slice(),
&mut layout_data.data.before_style,
applicable_declarations_cache,
false);
}
if applicable_declarations.after.len() > 0 {
self.cascade_node_pseudo_element(Some(layout_data.shared_data.style.get_ref()),
applicable_declarations.after.as_slice(),
&mut layout_data.data.after_style,
applicable_declarations_cache,
false);
}
}
}
}
}

View file

@ -0,0 +1,30 @@
/* 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/. */
// Style retrieval from DOM elements.
use css::node_util::NodeUtil;
use incremental::RestyleDamage;
use wrapper::ThreadSafeLayoutNode;
use style::ComputedValues;
use sync::Arc;
/// Node mixin providing `style` method that returns a `NodeStyle`
pub trait StyledNode {
fn style<'a>(&'a self) -> &'a Arc<ComputedValues>;
fn restyle_damage(&self) -> RestyleDamage;
}
impl<'ln> StyledNode for ThreadSafeLayoutNode<'ln> {
#[inline]
fn style<'a>(&'a self) -> &'a Arc<ComputedValues> {
self.get_css_select_results()
}
fn restyle_damage(&self) -> RestyleDamage {
self.get_restyle_damage()
}
}

View file

@ -0,0 +1,90 @@
/* 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 incremental::RestyleDamage;
use util::LayoutDataAccess;
use wrapper::{TLayoutNode, ThreadSafeLayoutNode};
use wrapper::{After, AfterBlock, Before, BeforeBlock, Normal};
use std::mem;
use style::ComputedValues;
use sync::Arc;
pub trait NodeUtil {
fn get_css_select_results<'a>(&'a self) -> &'a Arc<ComputedValues>;
fn have_css_select_results(&self) -> bool;
fn get_restyle_damage(&self) -> RestyleDamage;
fn set_restyle_damage(&self, damage: RestyleDamage);
}
impl<'ln> NodeUtil for ThreadSafeLayoutNode<'ln> {
/// Returns the style results for the given node. If CSS selector
/// matching has not yet been performed, fails.
#[inline]
fn get_css_select_results<'a>(&'a self) -> &'a Arc<ComputedValues> {
unsafe {
let layout_data_ref = self.borrow_layout_data();
match self.get_pseudo_element_type() {
Before | BeforeBlock => {
mem::transmute(layout_data_ref.as_ref()
.unwrap()
.data
.before_style
.as_ref()
.unwrap())
}
After | AfterBlock => {
mem::transmute(layout_data_ref.as_ref()
.unwrap()
.data
.after_style
.as_ref()
.unwrap())
}
Normal => {
mem::transmute(layout_data_ref.as_ref()
.unwrap()
.shared_data
.style
.as_ref()
.unwrap())
}
}
}
}
/// Does this node have a computed style yet?
fn have_css_select_results(&self) -> bool {
let layout_data_ref = self.borrow_layout_data();
layout_data_ref.get_ref().shared_data.style.is_some()
}
/// Get the description of how to account for recent style changes.
/// This is a simple bitfield and fine to copy by value.
fn get_restyle_damage(&self) -> RestyleDamage {
// For DOM elements, if we haven't computed damage yet, assume the worst.
// Other nodes don't have styles.
let default = if self.node_is_element() {
RestyleDamage::all()
} else {
RestyleDamage::empty()
};
let layout_data_ref = self.borrow_layout_data();
layout_data_ref
.get_ref()
.data
.restyle_damage
.unwrap_or(default)
}
/// Set the restyle damage field.
fn set_restyle_damage(&self, damage: RestyleDamage) {
let mut layout_data_ref = self.mutate_layout_data();
match &mut *layout_data_ref {
&Some(ref mut layout_data) => layout_data.data.restyle_damage = Some(damage),
_ => fail!("no layout data for this node"),
}
}
}

View file

@ -0,0 +1,44 @@
/* 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/. */
//! Code for managing the layout data in the DOM.
use util::{PrivateLayoutData, LayoutDataAccess, LayoutDataWrapper};
use wrapper::LayoutNode;
use script::dom::node::SharedLayoutData;
use script::layout_interface::LayoutChan;
/// Functionality useful for querying the layout-specific data on DOM nodes.
pub trait LayoutAuxMethods {
fn initialize_layout_data(&self, chan: LayoutChan);
fn initialize_style_for_subtree(&self, chan: LayoutChan);
}
impl<'ln> LayoutAuxMethods for LayoutNode<'ln> {
/// Resets layout data and styles for the node.
///
/// FIXME(pcwalton): Do this as part of fragment building instead of in a traversal.
fn initialize_layout_data(&self, chan: LayoutChan) {
let mut layout_data_ref = self.mutate_layout_data();
match *layout_data_ref {
None => {
*layout_data_ref = Some(LayoutDataWrapper {
chan: Some(chan),
shared_data: SharedLayoutData { style: None },
data: box PrivateLayoutData::new(),
});
}
Some(_) => {}
}
}
/// Resets layout data and styles for a Node tree.
///
/// FIXME(pcwalton): Do this as part of fragment building instead of in a traversal.
fn initialize_style_for_subtree(&self, chan: LayoutChan) {
for n in self.traverse_preorder() {
n.initialize_layout_data(chan.clone());
}
}
}

439
components/layout/floats.rs Normal file
View file

@ -0,0 +1,439 @@
/* 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 servo_util::geometry::{Au, max, min};
use servo_util::logical_geometry::WritingMode;
use servo_util::logical_geometry::{LogicalPoint, LogicalRect, LogicalSize};
use std::i32;
use std::fmt;
use style::computed_values::float;
use sync::Arc;
/// The kind of float: left or right.
#[deriving(Clone, Encodable)]
pub enum FloatKind {
FloatLeft,
FloatRight
}
impl FloatKind {
pub fn from_property(property: float::T) -> FloatKind {
match property {
float::none => fail!("can't create a float type from an unfloated property"),
float::left => FloatLeft,
float::right => FloatRight,
}
}
}
/// The kind of clearance: left, right, or both.
pub enum ClearType {
ClearLeft,
ClearRight,
ClearBoth,
}
/// Information about a single float.
#[deriving(Clone)]
struct Float {
/// The boundaries of this float.
bounds: LogicalRect<Au>,
/// The kind of float: left or right.
kind: FloatKind,
}
impl fmt::Show for Float {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "bounds={} kind={:?}", self.bounds, self.kind)
}
}
/// Information about the floats next to a flow.
///
/// FIXME(pcwalton): When we have fast `MutexArc`s, try removing `#[deriving(Clone)]` and wrap in a
/// mutex.
#[deriving(Clone)]
struct FloatList {
/// Information about each of the floats here.
floats: Vec<Float>,
/// Cached copy of the maximum block-start offset of the float.
max_block_start: Au,
}
impl FloatList {
fn new() -> FloatList {
FloatList {
floats: vec!(),
max_block_start: Au(0),
}
}
}
impl fmt::Show for FloatList {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "max_block_start={} floats={:?}", self.max_block_start, self.floats)
}
}
/// Wraps a `FloatList` to avoid allocation in the common case of no floats.
///
/// FIXME(pcwalton): When we have fast `MutexArc`s, try removing `CowArc` and use a mutex instead.
#[deriving(Clone)]
struct FloatListRef {
list: Option<Arc<FloatList>>,
}
impl FloatListRef {
fn new() -> FloatListRef {
FloatListRef {
list: None,
}
}
/// Returns true if the list is allocated and false otherwise. If false, there are guaranteed
/// not to be any floats.
fn is_present(&self) -> bool {
self.list.is_some()
}
#[inline]
fn get<'a>(&'a self) -> Option<&'a FloatList> {
match self.list {
None => None,
Some(ref list) => Some(&**list),
}
}
#[allow(experimental)]
#[inline]
fn get_mut<'a>(&'a mut self) -> &'a mut FloatList {
if self.list.is_none() {
self.list = Some(Arc::new(FloatList::new()))
}
self.list.as_mut().unwrap().make_unique()
}
}
/// All the information necessary to place a float.
pub struct PlacementInfo {
/// The dimensions of the float.
pub size: LogicalSize<Au>,
/// The minimum block-start of the float, as determined by earlier elements.
pub ceiling: Au,
/// The maximum inline-end position of the float, generally determined by the containing block.
pub max_inline_size: Au,
/// The kind of float.
pub kind: FloatKind
}
impl fmt::Show for PlacementInfo {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "size={} ceiling={} max_inline_size={} kind={:?}", self.size, self.ceiling, self.max_inline_size, self.kind)
}
}
fn range_intersect(block_start_1: Au, block_end_1: Au, block_start_2: Au, block_end_2: Au) -> (Au, Au) {
(max(block_start_1, block_start_2), min(block_end_1, block_end_2))
}
/// Encapsulates information about floats. This is optimized to avoid allocation if there are
/// no floats, and to avoid copying when translating the list of floats downward.
#[deriving(Clone)]
pub struct Floats {
/// The list of floats.
list: FloatListRef,
/// The offset of the flow relative to the first float.
offset: LogicalSize<Au>,
pub writing_mode: WritingMode,
}
impl fmt::Show for Floats {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self.list.get() {
None => {
write!(f, "[empty]")
}
Some(list) => {
write!(f, "offset={} floats={}", self.offset, list)
}
}
}
}
impl Floats {
/// Creates a new `Floats` object.
pub fn new(writing_mode: WritingMode) -> Floats {
Floats {
list: FloatListRef::new(),
offset: LogicalSize::zero(writing_mode),
writing_mode: writing_mode,
}
}
/// Adjusts the recorded offset of the flow relative to the first float.
pub fn translate(&mut self, delta: LogicalSize<Au>) {
self.offset = self.offset + delta
}
/// Returns the position of the last float in flow coordinates.
pub fn last_float_pos(&self) -> Option<LogicalPoint<Au>> {
match self.list.get() {
None => None,
Some(list) => {
match list.floats.last() {
None => None,
Some(float) => Some(float.bounds.start + self.offset),
}
}
}
}
/// Returns a rectangle that encloses the region from block-start to block-start + block-size, with inline-size small
/// enough that it doesn't collide with any floats. max_x is the x-coordinate beyond which
/// floats have no effect. (Generally this is the containing block inline-size.)
pub fn available_rect(&self, block_start: Au, block_size: Au, max_x: Au) -> Option<LogicalRect<Au>> {
let list = match self.list.get() {
None => return None,
Some(list) => list,
};
let block_start = block_start - self.offset.block;
debug!("available_rect: trying to find space at {}", block_start);
// Relevant dimensions for the inline-end-most inline-start float
let mut max_inline_start = Au(0) - self.offset.inline;
let mut l_block_start = None;
let mut l_block_end = None;
// Relevant dimensions for the inline-start-most inline-end float
let mut min_inline_end = max_x - self.offset.inline;
let mut r_block_start = None;
let mut r_block_end = None;
// Find the float collisions for the given vertical range.
for float in list.floats.iter() {
debug!("available_rect: Checking for collision against float");
let float_pos = float.bounds.start;
let float_size = float.bounds.size;
debug!("float_pos: {}, float_size: {}", float_pos, float_size);
match float.kind {
FloatLeft if float_pos.i + float_size.inline > max_inline_start &&
float_pos.b + float_size.block > block_start && float_pos.b < block_start + block_size => {
max_inline_start = float_pos.i + float_size.inline;
l_block_start = Some(float_pos.b);
l_block_end = Some(float_pos.b + float_size.block);
debug!("available_rect: collision with inline_start float: new max_inline_start is {}",
max_inline_start);
}
FloatRight if float_pos.i < min_inline_end &&
float_pos.b + float_size.block > block_start && float_pos.b < block_start + block_size => {
min_inline_end = float_pos.i;
r_block_start = Some(float_pos.b);
r_block_end = Some(float_pos.b + float_size.block);
debug!("available_rect: collision with inline_end float: new min_inline_end is {}",
min_inline_end);
}
FloatLeft | FloatRight => {}
}
}
// Extend the vertical range of the rectangle to the closest floats.
// If there are floats on both sides, take the intersection of the
// two areas. Also make sure we never return a block-start smaller than the
// given upper bound.
let (block_start, block_end) = match (r_block_start, r_block_end, l_block_start, l_block_end) {
(Some(r_block_start), Some(r_block_end), Some(l_block_start), Some(l_block_end)) =>
range_intersect(max(block_start, r_block_start), r_block_end, max(block_start, l_block_start), l_block_end),
(None, None, Some(l_block_start), Some(l_block_end)) => (max(block_start, l_block_start), l_block_end),
(Some(r_block_start), Some(r_block_end), None, None) => (max(block_start, r_block_start), r_block_end),
(None, None, None, None) => return None,
_ => fail!("Reached unreachable state when computing float area")
};
// FIXME(eatkinson): This assertion is too strong and fails in some cases. It is OK to
// return negative inline-sizes since we check against that inline-end away, but we should still
// undersrtand why they occur and add a stronger assertion here.
// assert!(max_inline-start < min_inline-end);
assert!(block_start <= block_end, "Float position error");
Some(LogicalRect::new(
self.writing_mode, max_inline_start + self.offset.inline, block_start + self.offset.block,
min_inline_end - max_inline_start, block_end - block_start
))
}
/// Adds a new float to the list.
pub fn add_float(&mut self, info: &PlacementInfo) {
let new_info;
{
let list = self.list.get_mut();
new_info = PlacementInfo {
size: info.size,
ceiling: max(info.ceiling, list.max_block_start + self.offset.block),
max_inline_size: info.max_inline_size,
kind: info.kind
}
}
debug!("add_float: added float with info {:?}", new_info);
let new_float = Float {
bounds: LogicalRect::from_point_size(
self.writing_mode,
self.place_between_floats(&new_info).start - self.offset,
info.size,
),
kind: info.kind
};
let list = self.list.get_mut();
list.floats.push(new_float);
list.max_block_start = max(list.max_block_start, new_float.bounds.start.b);
}
/// Given the block-start 3 sides of the rectangle, finds the largest block-size that will result in the
/// rectangle not colliding with any floats. Returns None if that block-size is infinite.
fn max_block_size_for_bounds(&self, inline_start: Au, block_start: Au, inline_size: Au) -> Option<Au> {
let list = match self.list.get() {
None => return None,
Some(list) => list,
};
let block_start = block_start - self.offset.block;
let inline_start = inline_start - self.offset.inline;
let mut max_block_size = None;
for float in list.floats.iter() {
if float.bounds.start.b + float.bounds.size.block > block_start &&
float.bounds.start.i + float.bounds.size.inline > inline_start &&
float.bounds.start.i < inline_start + inline_size {
let new_y = float.bounds.start.b;
max_block_size = Some(min(max_block_size.unwrap_or(new_y), new_y));
}
}
max_block_size.map(|h| h + self.offset.block)
}
/// Given placement information, finds the closest place a fragment can be positioned without
/// colliding with any floats.
pub fn place_between_floats(&self, info: &PlacementInfo) -> LogicalRect<Au> {
debug!("place_between_floats: Placing object with {}", info.size);
// If no floats, use this fast path.
if !self.list.is_present() {
match info.kind {
FloatLeft => {
return LogicalRect::new(
self.writing_mode,
Au(0),
info.ceiling,
info.max_inline_size,
Au(i32::MAX))
}
FloatRight => {
return LogicalRect::new(
self.writing_mode,
info.max_inline_size - info.size.inline,
info.ceiling,
info.max_inline_size,
Au(i32::MAX))
}
}
}
// Can't go any higher than previous floats or previous elements in the document.
let mut float_b = info.ceiling;
loop {
let maybe_location = self.available_rect(float_b, info.size.block, info.max_inline_size);
debug!("place_float: Got available rect: {:?} for y-pos: {}", maybe_location, float_b);
match maybe_location {
// If there are no floats blocking us, return the current location
// TODO(eatkinson): integrate with overflow
None => {
return match info.kind {
FloatLeft => {
LogicalRect::new(
self.writing_mode,
Au(0),
float_b,
info.max_inline_size,
Au(i32::MAX))
}
FloatRight => {
LogicalRect::new(
self.writing_mode,
info.max_inline_size - info.size.inline,
float_b,
info.max_inline_size,
Au(i32::MAX))
}
}
}
Some(rect) => {
assert!(rect.start.b + rect.size.block != float_b,
"Non-terminating float placement");
// Place here if there is enough room
if rect.size.inline >= info.size.inline {
let block_size = self.max_block_size_for_bounds(rect.start.i,
rect.start.b,
rect.size.inline);
let block_size = block_size.unwrap_or(Au(i32::MAX));
return match info.kind {
FloatLeft => {
LogicalRect::new(
self.writing_mode,
rect.start.i,
float_b,
rect.size.inline,
block_size)
}
FloatRight => {
LogicalRect::new(
self.writing_mode,
rect.start.i + rect.size.inline - info.size.inline,
float_b,
rect.size.inline,
block_size)
}
}
}
// Try to place at the next-lowest location.
// Need to be careful of fencepost errors.
float_b = rect.start.b + rect.size.block;
}
}
}
}
pub fn clearance(&self, clear: ClearType) -> Au {
let list = match self.list.get() {
None => return Au(0),
Some(list) => list,
};
let mut clearance = Au(0);
for float in list.floats.iter() {
match (clear, float.kind) {
(ClearLeft, FloatLeft) |
(ClearRight, FloatRight) |
(ClearBoth, _) => {
let b = self.offset.block + float.bounds.start.b + float.bounds.size.block;
clearance = max(clearance, b);
}
_ => {}
}
}
clearance
}
}

1138
components/layout/flow.rs Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,296 @@
/* 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 variant of `DList` specialized to store `Flow`s without an extra
//! indirection.
use flow::{Flow, base, mut_base};
use flow_ref::FlowRef;
use std::kinds::marker::ContravariantLifetime;
use std::mem;
use std::ptr;
use std::raw;
pub type Link = Option<FlowRef>;
#[allow(raw_pointer_deriving)]
pub struct Rawlink<'a> {
object: raw::TraitObject,
marker: ContravariantLifetime<'a>,
}
/// Doubly-linked list of Flows.
///
/// The forward links are strong references.
/// The backward links are weak references.
pub struct FlowList {
length: uint,
list_head: Link,
list_tail: Link,
}
/// Double-ended FlowList iterator
pub struct FlowListIterator<'a> {
head: &'a Link,
nelem: uint,
}
/// Double-ended mutable FlowList iterator
pub struct MutFlowListIterator<'a> {
head: Rawlink<'a>,
nelem: uint,
}
impl<'a> Rawlink<'a> {
/// Like Option::None for Rawlink
pub fn none() -> Rawlink<'static> {
Rawlink {
object: raw::TraitObject {
vtable: ptr::mut_null(),
data: ptr::mut_null(),
},
marker: ContravariantLifetime,
}
}
/// Like Option::Some for Rawlink
pub fn some(n: &Flow) -> Rawlink {
unsafe {
Rawlink {
object: mem::transmute::<&Flow, raw::TraitObject>(n),
marker: ContravariantLifetime,
}
}
}
pub unsafe fn resolve_mut(&self) -> Option<&'a mut Flow> {
if self.object.data.is_null() {
None
} else {
Some(mem::transmute_copy::<raw::TraitObject, &mut Flow>(&self.object))
}
}
}
/// Set the .prev field on `next`, then return `Some(next)`
unsafe fn link_with_prev(mut next: FlowRef, prev: Option<FlowRef>) -> Link {
mut_base(next.get_mut()).prev_sibling = prev;
Some(next)
}
impl Collection for FlowList {
/// O(1)
#[inline]
fn is_empty(&self) -> bool {
self.list_head.is_none()
}
/// O(1)
#[inline]
fn len(&self) -> uint {
self.length
}
}
// This doesn't quite fit the Deque trait because of the need to switch between
// &Flow and ~Flow.
impl FlowList {
/// Provide a reference to the front element, or None if the list is empty
#[inline]
pub fn front<'a>(&'a self) -> Option<&'a Flow> {
self.list_head.as_ref().map(|head| head.get())
}
/// Provide a mutable reference to the front element, or None if the list is empty
#[inline]
pub unsafe fn front_mut<'a>(&'a mut self) -> Option<&'a mut Flow> {
self.list_head.as_mut().map(|head| head.get_mut())
}
/// Provide a reference to the back element, or None if the list is empty
#[inline]
pub fn back<'a>(&'a self) -> Option<&'a Flow> {
match self.list_tail {
None => None,
Some(ref list_tail) => Some(list_tail.get())
}
}
/// Provide a mutable reference to the back element, or None if the list is empty
#[inline]
pub unsafe fn back_mut<'a>(&'a mut self) -> Option<&'a mut Flow> {
// Can't use map() due to error:
// lifetime of `tail` is too short to guarantee its contents can be safely reborrowed
match self.list_tail {
None => None,
Some(ref mut tail) => {
let x: &mut Flow = tail.get_mut();
Some(mem::transmute_copy(&x))
}
}
}
/// Add an element first in the list
///
/// O(1)
pub fn push_front(&mut self, mut new_head: FlowRef) {
unsafe {
match self.list_head {
None => {
self.list_tail = Some(new_head.clone());
self.list_head = link_with_prev(new_head, None);
}
Some(ref mut head) => {
mut_base(new_head.get_mut()).prev_sibling = None;
mut_base(head.get_mut()).prev_sibling = Some(new_head.clone());
mem::swap(head, &mut new_head);
mut_base(head.get_mut()).next_sibling = Some(new_head);
}
}
self.length += 1;
}
}
/// Remove the first element and return it, or None if the list is empty
///
/// O(1)
pub fn pop_front(&mut self) -> Option<FlowRef> {
self.list_head.take().map(|mut front_node| {
self.length -= 1;
unsafe {
match mut_base(front_node.get_mut()).next_sibling.take() {
Some(node) => self.list_head = link_with_prev(node, None),
None => self.list_tail = None,
}
}
front_node
})
}
/// Add an element last in the list
///
/// O(1)
pub fn push_back(&mut self, new_tail: FlowRef) {
if self.list_tail.is_none() {
return self.push_front(new_tail);
}
let old_tail = self.list_tail.clone();
self.list_tail = Some(new_tail.clone());
let mut tail = (*old_tail.as_ref().unwrap()).clone();
let tail_clone = Some(tail.clone());
unsafe {
mut_base(tail.get_mut()).next_sibling = link_with_prev(new_tail, tail_clone);
}
self.length += 1;
}
/// Create an empty list
#[inline]
pub fn new() -> FlowList {
FlowList {
list_head: None,
list_tail: None,
length: 0,
}
}
/// Provide a forward iterator
#[inline]
pub fn iter<'a>(&'a self) -> FlowListIterator<'a> {
FlowListIterator {
nelem: self.len(),
head: &self.list_head,
}
}
/// Provide a forward iterator with mutable references
#[inline]
pub fn mut_iter<'a>(&'a mut self) -> MutFlowListIterator<'a> {
let len = self.len();
let head_raw = match self.list_head {
Some(ref mut h) => Rawlink::some(h.get()),
None => Rawlink::none(),
};
MutFlowListIterator {
nelem: len,
head: head_raw,
}
}
}
#[unsafe_destructor]
impl Drop for FlowList {
fn drop(&mut self) {
// Dissolve the list in backwards direction
// Just dropping the list_head can lead to stack exhaustion
// when length is >> 1_000_000
let mut tail = mem::replace(&mut self.list_tail, None);
loop {
let new_tail = match tail {
None => break,
Some(ref mut prev) => {
let prev_base = mut_base(prev.get_mut());
prev_base.next_sibling.take();
prev_base.prev_sibling.clone()
}
};
tail = new_tail
}
self.length = 0;
self.list_head = None;
}
}
impl<'a> Iterator<&'a Flow> for FlowListIterator<'a> {
#[inline]
fn next(&mut self) -> Option<&'a Flow> {
if self.nelem == 0 {
return None;
}
self.head.as_ref().map(|head| {
let head_base = base(head.get());
self.nelem -= 1;
self.head = &head_base.next_sibling;
let ret: &Flow = head.get();
ret
})
}
#[inline]
fn size_hint(&self) -> (uint, Option<uint>) {
(self.nelem, Some(self.nelem))
}
}
impl<'a> Iterator<&'a mut Flow> for MutFlowListIterator<'a> {
#[inline]
fn next(&mut self) -> Option<&'a mut Flow> {
if self.nelem == 0 {
return None;
}
unsafe {
self.head.resolve_mut().map(|next| {
self.nelem -= 1;
self.head = match mut_base(next).next_sibling {
Some(ref mut node) => {
let x: &mut Flow = node.get_mut();
// NOTE: transmute needed here to break the link
// between x and next so that it is no longer
// borrowed.
mem::transmute(Rawlink::some(x))
}
None => Rawlink::none(),
};
next
})
}
}
#[inline]
fn size_hint(&self) -> (uint, Option<uint>) {
(self.nelem, Some(self.nelem))
}
}

View file

@ -0,0 +1,84 @@
/* 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/. */
/// Reference-counted pointers to flows.
///
/// Eventually, with dynamically sized types in Rust, much of this code will be superfluous.
use flow::Flow;
use flow;
use std::mem;
use std::ptr;
use std::raw;
use std::sync::atomics::SeqCst;
#[unsafe_no_drop_flag]
pub struct FlowRef {
object: raw::TraitObject,
}
impl FlowRef {
pub fn new(mut flow: Box<Flow>) -> FlowRef {
unsafe {
let result = {
let flow_ref: &mut Flow = flow;
let object = mem::transmute::<&mut Flow, raw::TraitObject>(flow_ref);
FlowRef { object: object }
};
mem::forget(flow);
result
}
}
pub fn get<'a>(&'a self) -> &'a Flow {
unsafe {
mem::transmute_copy::<raw::TraitObject, &'a Flow>(&self.object)
}
}
pub fn get_mut<'a>(&'a mut self) -> &'a mut Flow {
unsafe {
mem::transmute_copy::<raw::TraitObject, &'a mut Flow>(&self.object)
}
}
}
impl Drop for FlowRef {
fn drop(&mut self) {
unsafe {
if self.object.vtable.is_null() {
return
}
if flow::base(self.get()).ref_count().fetch_sub(1, SeqCst) > 1 {
return
}
let flow_ref: FlowRef = mem::replace(self, FlowRef {
object: raw::TraitObject {
vtable: ptr::mut_null(),
data: ptr::mut_null(),
}
});
drop(mem::transmute::<raw::TraitObject, Box<Flow>>(flow_ref.object));
mem::forget(flow_ref);
self.object.vtable = ptr::mut_null();
self.object.data = ptr::mut_null();
}
}
}
impl Clone for FlowRef {
fn clone(&self) -> FlowRef {
unsafe {
drop(flow::base(self.get()).ref_count().fetch_add(1, SeqCst));
FlowRef {
object: raw::TraitObject {
vtable: self.object.vtable,
data: self.object.data,
}
}
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,78 @@
/* 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 style::ComputedValues;
bitflags! {
#[doc = "Individual layout actions that may be necessary after restyling."]
flags RestyleDamage: int {
#[doc = "Repaint the node itself."]
#[doc = "Currently unused; need to decide how this propagates."]
static Repaint = 0x01,
#[doc = "Recompute intrinsic inline_sizes (minimum and preferred)."]
#[doc = "Propagates down the flow tree because the computation is"]
#[doc = "bottom-up."]
static BubbleISizes = 0x02,
#[doc = "Recompute actual inline_sizes and block_sizes."]
#[doc = "Propagates up the flow tree because the computation is"]
#[doc = "top-down."]
static Reflow = 0x04
}
}
impl RestyleDamage {
/// Elements of self which should also get set on any ancestor flow.
pub fn propagate_up(self) -> RestyleDamage {
self & Reflow
}
/// Elements of self which should also get set on any child flows.
pub fn propagate_down(self) -> RestyleDamage {
self & BubbleISizes
}
}
// NB: We need the braces inside the RHS due to Rust #8012. This particular
// version of this macro might be safe anyway, but we want to avoid silent
// breakage on modifications.
macro_rules! add_if_not_equal(
($old:ident, $new:ident, $damage:ident,
[ $($effect:ident),* ], [ $($style_struct_getter:ident.$name:ident),* ]) => ({
if $( ($old.$style_struct_getter().$name != $new.$style_struct_getter().$name) )||* {
$damage.insert($($effect)|*);
}
})
)
pub fn compute_damage(old: &ComputedValues, new: &ComputedValues) -> RestyleDamage {
let mut damage = RestyleDamage::empty();
// This checks every CSS property, as enumerated in
// impl<'self> CssComputedStyle<'self>
// in src/support/netsurfcss/rust-netsurfcss/netsurfcss.rc.
// FIXME: We can short-circuit more of this.
add_if_not_equal!(old, new, damage, [ Repaint ],
[ get_color.color, get_background.background_color,
get_border.border_top_color, get_border.border_right_color,
get_border.border_bottom_color, get_border.border_left_color ]);
add_if_not_equal!(old, new, damage, [ Repaint, BubbleISizes, Reflow ],
[ get_border.border_top_width, get_border.border_right_width,
get_border.border_bottom_width, get_border.border_left_width,
get_margin.margin_top, get_margin.margin_right,
get_margin.margin_bottom, get_margin.margin_left,
get_padding.padding_top, get_padding.padding_right,
get_padding.padding_bottom, get_padding.padding_left,
get_box.position, get_box.width, get_box.height, get_box.float, get_box.display,
get_font.font_family, get_font.font_size, get_font.font_style, get_font.font_weight,
get_inheritedtext.text_align, get_text.text_decoration, get_inheritedbox.line_height ]);
// FIXME: test somehow that we checked every CSS property
damage
}

1170
components/layout/inline.rs Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,126 @@
/* 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/. */
//! Supports writing a trace file created during each layout scope
//! that can be viewed by an external tool to make layout debugging easier.
#![macro_escape]
use flow_ref::FlowRef;
use serialize::json;
use std::cell::RefCell;
use std::io::File;
use std::sync::atomics::{AtomicUint, SeqCst, INIT_ATOMIC_UINT};
local_data_key!(state_key: RefCell<State>)
static mut DEBUG_ID_COUNTER: AtomicUint = INIT_ATOMIC_UINT;
pub struct Scope;
#[macro_export]
macro_rules! layout_debug_scope(
($($arg:tt)*) => (
if cfg!(not(ndebug)) {
layout_debug::Scope::new(format!($($arg)*))
} else {
layout_debug::Scope
}
)
)
#[deriving(Encodable)]
struct ScopeData {
name: String,
pre: String,
post: String,
children: Vec<Box<ScopeData>>,
}
impl ScopeData {
fn new(name: String, pre: String) -> ScopeData {
ScopeData {
name: name,
pre: pre,
post: String::new(),
children: vec!(),
}
}
}
struct State {
flow_root: FlowRef,
scope_stack: Vec<Box<ScopeData>>,
}
/// A layout debugging scope. The entire state of the flow tree
/// will be output at the beginning and end of this scope.
impl Scope {
pub fn new(name: String) -> Scope {
let maybe_refcell = state_key.get();
match maybe_refcell {
Some(refcell) => {
let mut state = refcell.borrow_mut();
let flow_trace = json::encode(&state.flow_root.get());
let data = box ScopeData::new(name, flow_trace);
state.scope_stack.push(data);
}
None => {}
}
Scope
}
}
#[cfg(not(ndebug))]
impl Drop for Scope {
fn drop(&mut self) {
let maybe_refcell = state_key.get();
match maybe_refcell {
Some(refcell) => {
let mut state = refcell.borrow_mut();
let mut current_scope = state.scope_stack.pop().unwrap();
current_scope.post = json::encode(&state.flow_root.get());
let previous_scope = state.scope_stack.mut_last().unwrap();
previous_scope.children.push(current_scope);
}
None => {}
}
}
}
/// Generate a unique ID. This is used for items such as Fragment
/// which are often reallocated but represent essentially the
/// same data.
pub fn generate_unique_debug_id() -> uint {
unsafe { DEBUG_ID_COUNTER.fetch_add(1, SeqCst) }
}
/// Begin a layout debug trace. If this has not been called,
/// creating debug scopes has no effect.
pub fn begin_trace(flow_root: FlowRef) {
assert!(state_key.get().is_none());
let flow_trace = json::encode(&flow_root.get());
let state = State {
scope_stack: vec![box ScopeData::new("root".to_string(), flow_trace)],
flow_root: flow_root,
};
state_key.replace(Some(RefCell::new(state)));
}
/// End the debug layout trace. This will write the layout
/// trace to disk in the current directory. The output
/// file can then be viewed with an external tool.
pub fn end_trace() {
let task_state_cell = state_key.replace(None).unwrap();
let mut task_state = task_state_cell.borrow_mut();
assert!(task_state.scope_stack.len() == 1);
let mut root_scope = task_state.scope_stack.pop().unwrap();
root_scope.post = json::encode(&task_state.flow_root.get());
let result = json::encode(&root_scope);
let path = Path::new("layout_trace.json");
let mut file = File::create(&path).unwrap();
file.write_str(result.as_slice()).unwrap();
}

File diff suppressed because it is too large Load diff

68
components/layout/lib.rs Normal file
View file

@ -0,0 +1,68 @@
/* 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, macro_rules, phase, thread_local, unsafe_destructor)]
#[phase(plugin, link)]
extern crate log;
extern crate debug;
extern crate geom;
extern crate gfx;
extern crate layout_traits;
extern crate script;
extern crate script_traits;
extern crate serialize;
extern crate style;
#[phase(plugin)]
extern crate servo_macros = "macros";
extern crate servo_net = "net";
extern crate servo_msg = "msg";
#[phase(plugin, link)]
extern crate servo_util = "util";
extern crate collections;
extern crate green;
extern crate libc;
extern crate sync;
extern crate url;
// Listed first because of macro definitions
pub mod layout_debug;
pub mod block;
pub mod construct;
pub mod context;
pub mod floats;
pub mod flow;
pub mod flow_list;
pub mod flow_ref;
pub mod fragment;
pub mod layout_task;
pub mod inline;
pub mod model;
pub mod parallel;
pub mod table_wrapper;
pub mod table;
pub mod table_caption;
pub mod table_colgroup;
pub mod table_rowgroup;
pub mod table_row;
pub mod table_cell;
pub mod text;
pub mod util;
pub mod incremental;
pub mod wrapper;
pub mod extra;
pub mod css {
mod node_util;
pub mod matching;
pub mod node_style;
}

337
components/layout/model.rs Normal file
View file

@ -0,0 +1,337 @@
/* 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/. */
//! Borders, padding, and margins.
#![deny(unsafe_block)]
use fragment::Fragment;
use computed = style::computed_values;
use geom::SideOffsets2D;
use style::computed_values::{LPA_Auto, LPA_Length, LPA_Percentage, LP_Length, LP_Percentage};
use style::ComputedValues;
use servo_util::geometry::Au;
use servo_util::geometry;
use servo_util::logical_geometry::LogicalMargin;
use std::fmt;
/// A collapsible margin. See CSS 2.1 § 8.3.1.
pub struct AdjoiningMargins {
/// The value of the greatest positive margin.
pub most_positive: Au,
/// The actual value (not the absolute value) of the negative margin with the largest absolute
/// value. Since this is not the absolute value, this is always zero or negative.
pub most_negative: Au,
}
impl AdjoiningMargins {
pub fn new() -> AdjoiningMargins {
AdjoiningMargins {
most_positive: Au(0),
most_negative: Au(0),
}
}
pub fn from_margin(margin_value: Au) -> AdjoiningMargins {
if margin_value >= Au(0) {
AdjoiningMargins {
most_positive: margin_value,
most_negative: Au(0),
}
} else {
AdjoiningMargins {
most_positive: Au(0),
most_negative: margin_value,
}
}
}
pub fn union(&mut self, other: AdjoiningMargins) {
self.most_positive = geometry::max(self.most_positive, other.most_positive);
self.most_negative = geometry::min(self.most_negative, other.most_negative)
}
pub fn collapse(&self) -> Au {
self.most_positive + self.most_negative
}
}
/// Represents the block-start and block-end margins of a flow with collapsible margins. See CSS 2.1 § 8.3.1.
pub enum CollapsibleMargins {
/// Margins may not collapse with this flow.
NoCollapsibleMargins(Au, Au),
/// Both the block-start and block-end margins (specified here in that order) may collapse, but the
/// margins do not collapse through this flow.
MarginsCollapse(AdjoiningMargins, AdjoiningMargins),
/// Margins collapse *through* this flow. This means, essentially, that the flow doesnt
/// have any border, padding, or out-of-flow (floating or positioned) content
MarginsCollapseThrough(AdjoiningMargins),
}
impl CollapsibleMargins {
pub fn new() -> CollapsibleMargins {
NoCollapsibleMargins(Au(0), Au(0))
}
}
enum FinalMarginState {
MarginsCollapseThroughFinalMarginState,
BottomMarginCollapsesFinalMarginState,
}
pub struct MarginCollapseInfo {
pub state: MarginCollapseState,
pub block_start_margin: AdjoiningMargins,
pub margin_in: AdjoiningMargins,
}
impl MarginCollapseInfo {
/// TODO(#2012, pcwalton): Remove this method once `fragment` is not an `Option`.
pub fn new() -> MarginCollapseInfo {
MarginCollapseInfo {
state: AccumulatingCollapsibleTopMargin,
block_start_margin: AdjoiningMargins::new(),
margin_in: AdjoiningMargins::new(),
}
}
pub fn initialize_block_start_margin(&mut self,
fragment: &Fragment,
can_collapse_block_start_margin_with_kids: bool) {
if !can_collapse_block_start_margin_with_kids {
self.state = AccumulatingMarginIn
}
self.block_start_margin = AdjoiningMargins::from_margin(fragment.margin.block_start)
}
pub fn finish_and_compute_collapsible_margins(mut self,
fragment: &Fragment,
can_collapse_block_end_margin_with_kids: bool)
-> (CollapsibleMargins, Au) {
let state = match self.state {
AccumulatingCollapsibleTopMargin => {
match fragment.style().content_block_size() {
LPA_Auto | LPA_Length(Au(0)) | LPA_Percentage(0.) => {
match fragment.style().min_block_size() {
LP_Length(Au(0)) | LP_Percentage(0.) => {
MarginsCollapseThroughFinalMarginState
},
_ => {
// If the fragment has non-zero min-block-size, margins may not
// collapse through it.
BottomMarginCollapsesFinalMarginState
}
}
},
_ => {
// If the fragment has an explicitly specified block-size, margins may not
// collapse through it.
BottomMarginCollapsesFinalMarginState
}
}
}
AccumulatingMarginIn => BottomMarginCollapsesFinalMarginState,
};
// Different logic is needed here depending on whether this flow can collapse its block-end
// margin with its children.
let block_end_margin = fragment.margin.block_end;
if !can_collapse_block_end_margin_with_kids {
match state {
MarginsCollapseThroughFinalMarginState => {
let advance = self.block_start_margin.collapse();
self.margin_in.union(AdjoiningMargins::from_margin(block_end_margin));
(MarginsCollapse(self.block_start_margin, self.margin_in), advance)
}
BottomMarginCollapsesFinalMarginState => {
let advance = self.margin_in.collapse();
self.margin_in.union(AdjoiningMargins::from_margin(block_end_margin));
(MarginsCollapse(self.block_start_margin, self.margin_in), advance)
}
}
} else {
match state {
MarginsCollapseThroughFinalMarginState => {
self.block_start_margin.union(AdjoiningMargins::from_margin(block_end_margin));
(MarginsCollapseThrough(self.block_start_margin), Au(0))
}
BottomMarginCollapsesFinalMarginState => {
self.margin_in.union(AdjoiningMargins::from_margin(block_end_margin));
(MarginsCollapse(self.block_start_margin, self.margin_in), Au(0))
}
}
}
}
pub fn current_float_ceiling(&mut self) -> Au {
match self.state {
AccumulatingCollapsibleTopMargin => self.block_start_margin.collapse(),
AccumulatingMarginIn => self.margin_in.collapse(),
}
}
/// Adds the child's potentially collapsible block-start margin to the current margin state and
/// advances the Y offset by the appropriate amount to handle that margin. Returns the amount
/// that should be added to the Y offset during block layout.
pub fn advance_block_start_margin(&mut self, child_collapsible_margins: &CollapsibleMargins) -> Au {
match (self.state, *child_collapsible_margins) {
(AccumulatingCollapsibleTopMargin, NoCollapsibleMargins(block_start, _)) => {
self.state = AccumulatingMarginIn;
block_start
}
(AccumulatingCollapsibleTopMargin, MarginsCollapse(block_start, _)) => {
self.block_start_margin.union(block_start);
self.state = AccumulatingMarginIn;
Au(0)
}
(AccumulatingMarginIn, NoCollapsibleMargins(block_start, _)) => {
let previous_margin_value = self.margin_in.collapse();
self.margin_in = AdjoiningMargins::new();
previous_margin_value + block_start
}
(AccumulatingMarginIn, MarginsCollapse(block_start, _)) => {
self.margin_in.union(block_start);
let margin_value = self.margin_in.collapse();
self.margin_in = AdjoiningMargins::new();
margin_value
}
(_, MarginsCollapseThrough(_)) => {
// For now, we ignore this; this will be handled by `advance_block-end_margin` below.
Au(0)
}
}
}
/// Adds the child's potentially collapsible block-end margin to the current margin state and
/// advances the Y offset by the appropriate amount to handle that margin. Returns the amount
/// that should be added to the Y offset during block layout.
pub fn advance_block_end_margin(&mut self, child_collapsible_margins: &CollapsibleMargins) -> Au {
match (self.state, *child_collapsible_margins) {
(AccumulatingCollapsibleTopMargin, NoCollapsibleMargins(..)) |
(AccumulatingCollapsibleTopMargin, MarginsCollapse(..)) => {
// Can't happen because the state will have been replaced with
// `AccumulatingMarginIn` above.
fail!("should not be accumulating collapsible block_start margins anymore!")
}
(AccumulatingCollapsibleTopMargin, MarginsCollapseThrough(margin)) => {
self.block_start_margin.union(margin);
Au(0)
}
(AccumulatingMarginIn, NoCollapsibleMargins(_, block_end)) => {
assert_eq!(self.margin_in.most_positive, Au(0));
assert_eq!(self.margin_in.most_negative, Au(0));
block_end
}
(AccumulatingMarginIn, MarginsCollapse(_, block_end)) |
(AccumulatingMarginIn, MarginsCollapseThrough(block_end)) => {
self.margin_in.union(block_end);
Au(0)
}
}
}
}
pub enum MarginCollapseState {
AccumulatingCollapsibleTopMargin,
AccumulatingMarginIn,
}
/// Intrinsic inline-sizes, which consist of minimum and preferred.
#[deriving(Encodable)]
pub struct IntrinsicISizes {
/// The *minimum inline-size* of the content.
pub minimum_inline_size: Au,
/// The *preferred inline-size* of the content.
pub preferred_inline_size: Au,
/// The estimated sum of borders, padding, and margins. Some calculations use this information
/// when computing intrinsic inline-sizes.
pub surround_inline_size: Au,
}
impl fmt::Show for IntrinsicISizes {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "min={}, pref={}, surr={}", self.minimum_inline_size, self.preferred_inline_size, self.surround_inline_size)
}
}
impl IntrinsicISizes {
pub fn new() -> IntrinsicISizes {
IntrinsicISizes {
minimum_inline_size: Au(0),
preferred_inline_size: Au(0),
surround_inline_size: Au(0),
}
}
pub fn total_minimum_inline_size(&self) -> Au {
self.minimum_inline_size + self.surround_inline_size
}
pub fn total_preferred_inline_size(&self) -> Au {
self.preferred_inline_size + self.surround_inline_size
}
}
/// Useful helper data type when computing values for blocks and positioned elements.
pub enum MaybeAuto {
Auto,
Specified(Au),
}
impl MaybeAuto {
#[inline]
pub fn from_style(length: computed::LengthOrPercentageOrAuto, containing_length: Au)
-> MaybeAuto {
match length {
computed::LPA_Auto => Auto,
computed::LPA_Percentage(percent) => Specified(containing_length.scale_by(percent)),
computed::LPA_Length(length) => Specified(length)
}
}
#[inline]
pub fn specified_or_default(&self, default: Au) -> Au {
match *self {
Auto => default,
Specified(value) => value,
}
}
#[inline]
pub fn specified_or_zero(&self) -> Au {
self.specified_or_default(Au::new(0))
}
}
pub fn specified_or_none(length: computed::LengthOrPercentageOrNone, containing_length: Au) -> Option<Au> {
match length {
computed::LPN_None => None,
computed::LPN_Percentage(percent) => Some(containing_length.scale_by(percent)),
computed::LPN_Length(length) => Some(length),
}
}
pub fn specified(length: computed::LengthOrPercentage, containing_length: Au) -> Au {
match length {
computed::LP_Length(length) => length,
computed::LP_Percentage(p) => containing_length.scale_by(p)
}
}
#[inline]
pub fn padding_from_style(style: &ComputedValues, containing_block_inline_size: Au)
-> LogicalMargin<Au> {
let padding_style = style.get_padding();
LogicalMargin::from_physical(style.writing_mode, SideOffsets2D::new(
specified(padding_style.padding_top, containing_block_inline_size),
specified(padding_style.padding_right, containing_block_inline_size),
specified(padding_style.padding_bottom, containing_block_inline_size),
specified(padding_style.padding_left, containing_block_inline_size)))
}

View file

@ -0,0 +1,561 @@
/* 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/. */
//! Implements parallel traversals over the DOM and flow trees.
//!
//! This code is highly unsafe. Keep this file small and easy to audit.
use css::matching::{ApplicableDeclarations, CannotShare, MatchMethods, StyleWasShared};
use construct::FlowConstructor;
use context::{LayoutContext, SharedLayoutContext};
use extra::LayoutAuxMethods;
use flow::{Flow, MutableFlowUtils, PreorderFlowTraversal, PostorderFlowTraversal};
use flow;
use flow_ref::FlowRef;
use layout_task::{AssignBSizesAndStoreOverflowTraversal, AssignISizesTraversal};
use layout_task::{BubbleISizesTraversal};
use util::{LayoutDataAccess, LayoutDataWrapper, OpaqueNodeMethods};
use wrapper::{layout_node_to_unsafe_layout_node, layout_node_from_unsafe_layout_node, LayoutNode, PostorderNodeMutTraversal};
use wrapper::{ThreadSafeLayoutNode, UnsafeLayoutNode};
use gfx::display_list::OpaqueNode;
use servo_util::time::{TimeProfilerChan, profile};
use servo_util::time;
use servo_util::workqueue::{WorkQueue, WorkUnit, WorkerProxy};
use std::mem;
use std::ptr;
use std::sync::atomics::{AtomicInt, Relaxed, SeqCst};
use style::TNode;
#[allow(dead_code)]
fn static_assertion(node: UnsafeLayoutNode) {
unsafe {
let _: UnsafeFlow = ::std::intrinsics::transmute(node);
}
}
/// Vtable + pointer representation of a Flow trait object.
pub type UnsafeFlow = (uint, uint);
fn null_unsafe_flow() -> UnsafeFlow {
(0, 0)
}
pub fn owned_flow_to_unsafe_flow(flow: *const FlowRef) -> UnsafeFlow {
unsafe {
mem::transmute_copy(&*flow)
}
}
pub fn mut_owned_flow_to_unsafe_flow(flow: *mut FlowRef) -> UnsafeFlow {
unsafe {
mem::transmute_copy(&*flow)
}
}
pub fn borrowed_flow_to_unsafe_flow(flow: &Flow) -> UnsafeFlow {
unsafe {
mem::transmute_copy(&flow)
}
}
pub fn mut_borrowed_flow_to_unsafe_flow(flow: &mut Flow) -> UnsafeFlow {
unsafe {
mem::transmute_copy(&flow)
}
}
/// Information that we need stored in each DOM node.
pub struct DomParallelInfo {
/// The number of children that still need work done.
pub children_count: AtomicInt,
}
impl DomParallelInfo {
pub fn new() -> DomParallelInfo {
DomParallelInfo {
children_count: AtomicInt::new(0),
}
}
}
/// Information that we need stored in each flow.
pub struct FlowParallelInfo {
/// The number of children that still need work done.
pub children_count: AtomicInt,
/// The number of children and absolute descendants that still need work done.
pub children_and_absolute_descendant_count: AtomicInt,
/// The address of the parent flow.
pub parent: UnsafeFlow,
}
impl FlowParallelInfo {
pub fn new() -> FlowParallelInfo {
FlowParallelInfo {
children_count: AtomicInt::new(0),
children_and_absolute_descendant_count: AtomicInt::new(0),
parent: null_unsafe_flow(),
}
}
}
/// A parallel bottom-up flow traversal.
trait ParallelPostorderFlowTraversal : PostorderFlowTraversal {
/// Process current flow and potentially traverse its ancestors.
///
/// If we are the last child that finished processing, recursively process
/// our parent. Else, stop.
/// Also, stop at the root (obviously :P).
///
/// Thus, if we start with all the leaves of a tree, we end up traversing
/// the whole tree bottom-up because each parent will be processed exactly
/// once (by the last child that finishes processing).
///
/// The only communication between siblings is that they both
/// fetch-and-subtract the parent's children count.
fn run_parallel(&mut self,
mut unsafe_flow: UnsafeFlow,
_: &mut WorkerProxy<*const SharedLayoutContext,UnsafeFlow>) {
loop {
unsafe {
// Get a real flow.
let flow: &mut FlowRef = mem::transmute(&unsafe_flow);
// Perform the appropriate traversal.
if self.should_process(flow.get_mut()) {
self.process(flow.get_mut());
}
let base = flow::mut_base(flow.get_mut());
// Reset the count of children for the next layout traversal.
base.parallel.children_count.store(base.children.len() as int, Relaxed);
// Possibly enqueue the parent.
let unsafe_parent = base.parallel.parent;
if unsafe_parent == null_unsafe_flow() {
// We're done!
break
}
// No, we're not at the root yet. Then are we the last child
// of our parent to finish processing? If so, we can continue
// on with our parent; otherwise, we've gotta wait.
let parent: &mut FlowRef = mem::transmute(&unsafe_parent);
let parent_base = flow::mut_base(parent.get_mut());
if parent_base.parallel.children_count.fetch_sub(1, SeqCst) == 1 {
// We were the last child of our parent. Reflow our parent.
unsafe_flow = unsafe_parent
} else {
// Stop.
break
}
}
}
}
}
/// A parallel top-down flow traversal.
trait ParallelPreorderFlowTraversal : PreorderFlowTraversal {
fn run_parallel(&mut self,
unsafe_flow: UnsafeFlow,
proxy: &mut WorkerProxy<*const SharedLayoutContext,UnsafeFlow>);
#[inline(always)]
fn run_parallel_helper(&mut self,
unsafe_flow: UnsafeFlow,
proxy: &mut WorkerProxy<*const SharedLayoutContext,UnsafeFlow>,
top_down_func: extern "Rust" fn(UnsafeFlow,
&mut WorkerProxy<*const SharedLayoutContext,
UnsafeFlow>),
bottom_up_func: extern "Rust" fn(UnsafeFlow,
&mut WorkerProxy<*const SharedLayoutContext,
UnsafeFlow>)) {
let mut had_children = false;
unsafe {
// Get a real flow.
let flow: &mut FlowRef = mem::transmute(&unsafe_flow);
// Perform the appropriate traversal.
self.process(flow.get_mut());
// Possibly enqueue the children.
for kid in flow::child_iter(flow.get_mut()) {
had_children = true;
proxy.push(WorkUnit {
fun: top_down_func,
data: borrowed_flow_to_unsafe_flow(kid),
});
}
}
// If there were no more children, start assigning block-sizes.
if !had_children {
bottom_up_func(unsafe_flow, proxy)
}
}
}
impl<'a> ParallelPostorderFlowTraversal for BubbleISizesTraversal<'a> {}
impl<'a> ParallelPreorderFlowTraversal for AssignISizesTraversal<'a> {
fn run_parallel(&mut self,
unsafe_flow: UnsafeFlow,
proxy: &mut WorkerProxy<*const SharedLayoutContext,UnsafeFlow>) {
self.run_parallel_helper(unsafe_flow,
proxy,
assign_inline_sizes,
assign_block_sizes_and_store_overflow)
}
}
impl<'a> ParallelPostorderFlowTraversal for AssignBSizesAndStoreOverflowTraversal<'a> {}
fn recalc_style_for_node(unsafe_layout_node: UnsafeLayoutNode,
proxy: &mut WorkerProxy<*const SharedLayoutContext,UnsafeLayoutNode>) {
let shared_layout_context = unsafe { &**proxy.user_data() };
let layout_context = LayoutContext::new(shared_layout_context);
// Get a real layout node.
let node: LayoutNode = unsafe {
layout_node_from_unsafe_layout_node(&unsafe_layout_node)
};
// Initialize layout data.
//
// FIXME(pcwalton): Stop allocating here. Ideally this should just be done by the HTML
// parser.
node.initialize_layout_data(layout_context.shared.layout_chan.clone());
// Get the parent node.
let opaque_node: OpaqueNode = OpaqueNodeMethods::from_layout_node(&node);
let parent_opt = if opaque_node == layout_context.shared.reflow_root {
None
} else {
node.parent_node()
};
// First, check to see whether we can share a style with someone.
let style_sharing_candidate_cache = layout_context.style_sharing_candidate_cache();
let sharing_result = unsafe {
node.share_style_if_possible(style_sharing_candidate_cache,
parent_opt.clone())
};
// Otherwise, match and cascade selectors.
match sharing_result {
CannotShare(mut shareable) => {
let mut applicable_declarations = ApplicableDeclarations::new();
if node.is_element() {
// Perform the CSS selector matching.
let stylist = unsafe { &*layout_context.shared.stylist };
node.match_node(stylist, &mut applicable_declarations, &mut shareable);
}
// Perform the CSS cascade.
unsafe {
node.cascade_node(parent_opt,
&applicable_declarations,
layout_context.applicable_declarations_cache());
}
// Add ourselves to the LRU cache.
if shareable {
style_sharing_candidate_cache.insert_if_possible(&node);
}
}
StyleWasShared(index) => style_sharing_candidate_cache.touch(index),
}
// Prepare for flow construction by counting the node's children and storing that count.
let mut child_count = 0u;
for _ in node.children() {
child_count += 1;
}
if child_count != 0 {
let mut layout_data_ref = node.mutate_layout_data();
match &mut *layout_data_ref {
&Some(ref mut layout_data) => {
layout_data.data.parallel.children_count.store(child_count as int, Relaxed)
}
&None => fail!("no layout data"),
}
}
// It's *very* important that this block is in a separate scope to the block above,
// to avoid a data race that can occur (github issue #2308). The block above issues
// a borrow on the node layout data. That borrow must be dropped before the child
// nodes are actually pushed into the work queue. Otherwise, it's possible for a child
// node to get into construct_flows() and move up it's parent hierarchy, which can call
// borrow on the layout data before it is dropped from the block above.
if child_count != 0 {
// Enqueue kids.
for kid in node.children() {
proxy.push(WorkUnit {
fun: recalc_style_for_node,
data: layout_node_to_unsafe_layout_node(&kid),
});
}
return
}
// If we got here, we're a leaf. Start construction of flows for this node.
construct_flows(unsafe_layout_node, &layout_context)
}
fn construct_flows(mut unsafe_layout_node: UnsafeLayoutNode,
layout_context: &LayoutContext) {
loop {
// Get a real layout node.
let node: LayoutNode = unsafe {
layout_node_from_unsafe_layout_node(&unsafe_layout_node)
};
// Construct flows for this node.
{
let mut flow_constructor = FlowConstructor::new(layout_context);
flow_constructor.process(&ThreadSafeLayoutNode::new(&node));
}
// Reset the count of children for the next traversal.
//
// FIXME(pcwalton): Use children().len() when the implementation of that is efficient.
let mut child_count = 0u;
for _ in node.children() {
child_count += 1
}
{
let mut layout_data_ref = node.mutate_layout_data();
match &mut *layout_data_ref {
&Some(ref mut layout_data) => {
layout_data.data.parallel.children_count.store(child_count as int, Relaxed)
}
&None => fail!("no layout data"),
}
}
// If this is the reflow root, we're done.
let opaque_node: OpaqueNode = OpaqueNodeMethods::from_layout_node(&node);
if layout_context.shared.reflow_root == opaque_node {
break
}
// Otherwise, enqueue the parent.
match node.parent_node() {
Some(parent) => {
// No, we're not at the root yet. Then are we the last sibling of our parent?
// If so, we can continue on with our parent; otherwise, we've gotta wait.
unsafe {
match *parent.borrow_layout_data_unchecked() {
Some(ref parent_layout_data) => {
let parent_layout_data: &mut LayoutDataWrapper = mem::transmute(parent_layout_data);
if parent_layout_data.data
.parallel
.children_count
.fetch_sub(1, SeqCst) == 1 {
// We were the last child of our parent. Construct flows for our
// parent.
unsafe_layout_node = layout_node_to_unsafe_layout_node(&parent)
} else {
// Get out of here and find another node to work on.
break
}
}
None => fail!("no layout data for parent?!"),
}
}
}
None => fail!("no parent and weren't at reflow root?!"),
}
}
}
fn assign_inline_sizes(unsafe_flow: UnsafeFlow,
proxy: &mut WorkerProxy<*const SharedLayoutContext,UnsafeFlow>) {
let shared_layout_context = unsafe { &**proxy.user_data() };
let layout_context = LayoutContext::new(shared_layout_context);
let mut assign_inline_sizes_traversal = AssignISizesTraversal {
layout_context: &layout_context,
};
assign_inline_sizes_traversal.run_parallel(unsafe_flow, proxy)
}
fn assign_block_sizes_and_store_overflow(unsafe_flow: UnsafeFlow,
proxy: &mut WorkerProxy<*const SharedLayoutContext,UnsafeFlow>) {
let shared_layout_context = unsafe { &**proxy.user_data() };
let layout_context = LayoutContext::new(shared_layout_context);
let mut assign_block_sizes_traversal = AssignBSizesAndStoreOverflowTraversal {
layout_context: &layout_context,
};
assign_block_sizes_traversal.run_parallel(unsafe_flow, proxy)
}
fn compute_absolute_position(unsafe_flow: UnsafeFlow,
proxy: &mut WorkerProxy<*const SharedLayoutContext,UnsafeFlow>) {
let mut had_descendants = false;
unsafe {
// Get a real flow.
let flow: &mut FlowRef = mem::transmute(&unsafe_flow);
// Compute the absolute position for the flow.
flow.get_mut().compute_absolute_position();
// Count the number of absolutely-positioned children, so that we can subtract it from
// from `children_and_absolute_descendant_count` to get the number of real children.
let mut absolutely_positioned_child_count = 0u;
for kid in flow::child_iter(flow.get_mut()) {
if kid.is_absolutely_positioned() {
absolutely_positioned_child_count += 1;
}
}
// Don't enqueue absolutely positioned children.
drop(flow::mut_base(flow.get_mut()).parallel
.children_and_absolute_descendant_count
.fetch_sub(absolutely_positioned_child_count as int,
SeqCst));
// Possibly enqueue the children.
for kid in flow::child_iter(flow.get_mut()) {
if !kid.is_absolutely_positioned() {
had_descendants = true;
proxy.push(WorkUnit {
fun: compute_absolute_position,
data: borrowed_flow_to_unsafe_flow(kid),
});
}
}
// Possibly enqueue absolute descendants.
for absolute_descendant_link in flow::mut_base(flow.get_mut()).abs_descendants.iter() {
had_descendants = true;
let descendant = absolute_descendant_link;
proxy.push(WorkUnit {
fun: compute_absolute_position,
data: borrowed_flow_to_unsafe_flow(descendant),
});
}
// If there were no more descendants, start building the display list.
if !had_descendants {
build_display_list(mut_owned_flow_to_unsafe_flow(flow),
proxy)
}
}
}
fn build_display_list(mut unsafe_flow: UnsafeFlow,
proxy: &mut WorkerProxy<*const SharedLayoutContext,UnsafeFlow>) {
let shared_layout_context = unsafe { &**proxy.user_data() };
let layout_context = LayoutContext::new(shared_layout_context);
loop {
unsafe {
// Get a real flow.
let flow: &mut FlowRef = mem::transmute(&unsafe_flow);
// Build display lists.
flow.get_mut().build_display_list(&layout_context);
{
let base = flow::mut_base(flow.get_mut());
// Reset the count of children and absolute descendants for the next layout
// traversal.
let children_and_absolute_descendant_count = base.children.len() +
base.abs_descendants.len();
base.parallel
.children_and_absolute_descendant_count
.store(children_and_absolute_descendant_count as int, Relaxed);
}
// Possibly enqueue the parent.
let unsafe_parent = if flow.get().is_absolutely_positioned() {
match *flow::mut_base(flow.get_mut()).absolute_cb.get() {
None => fail!("no absolute containing block for absolutely positioned?!"),
Some(ref mut absolute_cb) => {
mut_borrowed_flow_to_unsafe_flow(absolute_cb.get_mut())
}
}
} else {
flow::mut_base(flow.get_mut()).parallel.parent
};
if unsafe_parent == null_unsafe_flow() {
// We're done!
break
}
// No, we're not at the root yet. Then are we the last child
// of our parent to finish processing? If so, we can continue
// on with our parent; otherwise, we've gotta wait.
let parent: &mut FlowRef = mem::transmute(&unsafe_parent);
let parent_base = flow::mut_base(parent.get_mut());
if parent_base.parallel
.children_and_absolute_descendant_count
.fetch_sub(1, SeqCst) == 1 {
// We were the last child of our parent. Build display lists for our parent.
unsafe_flow = unsafe_parent
} else {
// Stop.
break
}
}
}
}
pub fn recalc_style_for_subtree(root_node: &LayoutNode,
shared_layout_context: &SharedLayoutContext,
queue: &mut WorkQueue<*const SharedLayoutContext,UnsafeLayoutNode>) {
queue.data = shared_layout_context as *const _;
// Enqueue the root node.
queue.push(WorkUnit {
fun: recalc_style_for_node,
data: layout_node_to_unsafe_layout_node(root_node),
});
queue.run();
queue.data = ptr::null()
}
pub fn traverse_flow_tree_preorder(root: &mut FlowRef,
time_profiler_chan: TimeProfilerChan,
shared_layout_context: &SharedLayoutContext,
queue: &mut WorkQueue<*const SharedLayoutContext,UnsafeFlow>) {
queue.data = shared_layout_context as *const _;
profile(time::LayoutParallelWarmupCategory, time_profiler_chan, || {
queue.push(WorkUnit {
fun: assign_inline_sizes,
data: mut_owned_flow_to_unsafe_flow(root),
})
});
queue.run();
queue.data = ptr::null()
}
pub fn build_display_list_for_subtree(root: &mut FlowRef,
time_profiler_chan: TimeProfilerChan,
shared_layout_context: &SharedLayoutContext,
queue: &mut WorkQueue<*const SharedLayoutContext,UnsafeFlow>) {
queue.data = shared_layout_context as *const _;
profile(time::LayoutParallelWarmupCategory, time_profiler_chan, || {
queue.push(WorkUnit {
fun: compute_absolute_position,
data: mut_owned_flow_to_unsafe_flow(root),
})
});
queue.run();
queue.data = ptr::null()
}

324
components/layout/table.rs Normal file
View file

@ -0,0 +1,324 @@
/* 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/. */
//! CSS table formatting contexts.
#![deny(unsafe_block)]
use block::{BlockFlow, MarginsMayNotCollapse, ISizeAndMarginsComputer};
use block::{ISizeConstraintInput, ISizeConstraintSolution};
use construct::FlowConstructor;
use context::LayoutContext;
use floats::FloatKind;
use flow::{TableFlowClass, FlowClass, Flow, ImmutableFlowUtils};
use fragment::Fragment;
use table_wrapper::{TableLayout, FixedLayout, AutoLayout};
use wrapper::ThreadSafeLayoutNode;
use servo_util::geometry::Au;
use servo_util::geometry;
use std::fmt;
use style::computed_values::table_layout;
/// A table flow corresponded to the table's internal table fragment under a table wrapper flow.
/// The properties `position`, `float`, and `margin-*` are used on the table wrapper fragment,
/// not table fragment per CSS 2.1 § 10.5.
pub struct TableFlow {
pub block_flow: BlockFlow,
/// Column inline-sizes
pub col_inline_sizes: Vec<Au>,
/// Column min inline-sizes.
pub col_min_inline_sizes: Vec<Au>,
/// Column pref inline-sizes.
pub col_pref_inline_sizes: Vec<Au>,
/// Table-layout property
pub table_layout: TableLayout,
}
impl TableFlow {
pub fn from_node_and_fragment(node: &ThreadSafeLayoutNode,
fragment: Fragment)
-> TableFlow {
let mut block_flow = BlockFlow::from_node_and_fragment(node, fragment);
let table_layout = if block_flow.fragment().style().get_table().table_layout ==
table_layout::fixed {
FixedLayout
} else {
AutoLayout
};
TableFlow {
block_flow: block_flow,
col_inline_sizes: vec!(),
col_min_inline_sizes: vec!(),
col_pref_inline_sizes: vec!(),
table_layout: table_layout
}
}
pub fn from_node(constructor: &mut FlowConstructor,
node: &ThreadSafeLayoutNode)
-> TableFlow {
let mut block_flow = BlockFlow::from_node(constructor, node);
let table_layout = if block_flow.fragment().style().get_table().table_layout ==
table_layout::fixed {
FixedLayout
} else {
AutoLayout
};
TableFlow {
block_flow: block_flow,
col_inline_sizes: vec!(),
col_min_inline_sizes: vec!(),
col_pref_inline_sizes: vec!(),
table_layout: table_layout
}
}
pub fn float_from_node(constructor: &mut FlowConstructor,
node: &ThreadSafeLayoutNode,
float_kind: FloatKind)
-> TableFlow {
let mut block_flow = BlockFlow::float_from_node(constructor, node, float_kind);
let table_layout = if block_flow.fragment().style().get_table().table_layout ==
table_layout::fixed {
FixedLayout
} else {
AutoLayout
};
TableFlow {
block_flow: block_flow,
col_inline_sizes: vec!(),
col_min_inline_sizes: vec!(),
col_pref_inline_sizes: vec!(),
table_layout: table_layout
}
}
/// Update the corresponding value of self_inline-sizes if a value of kid_inline-sizes has larger value
/// than one of self_inline-sizes.
pub fn update_col_inline_sizes(self_inline_sizes: &mut Vec<Au>, kid_inline_sizes: &Vec<Au>) -> Au {
let mut sum_inline_sizes = Au(0);
let mut kid_inline_sizes_it = kid_inline_sizes.iter();
for self_inline_size in self_inline_sizes.mut_iter() {
match kid_inline_sizes_it.next() {
Some(kid_inline_size) => {
if *self_inline_size < *kid_inline_size {
*self_inline_size = *kid_inline_size;
}
},
None => {}
}
sum_inline_sizes = sum_inline_sizes + *self_inline_size;
}
sum_inline_sizes
}
/// Assign block-size for table flow.
///
/// TODO(#2014, pcwalton): This probably doesn't handle margin collapse right.
///
/// inline(always) because this is only ever called by in-order or non-in-order top-level
/// methods
#[inline(always)]
fn assign_block_size_table_base<'a>(&mut self, layout_context: &'a LayoutContext<'a>) {
self.block_flow.assign_block_size_block_base(layout_context, MarginsMayNotCollapse);
}
pub fn build_display_list_table(&mut self, layout_context: &LayoutContext) {
debug!("build_display_list_table: same process as block flow");
self.block_flow.build_display_list_block(layout_context);
}
}
impl Flow for TableFlow {
fn class(&self) -> FlowClass {
TableFlowClass
}
fn as_table<'a>(&'a mut self) -> &'a mut TableFlow {
self
}
fn as_block<'a>(&'a mut self) -> &'a mut BlockFlow {
&mut self.block_flow
}
fn col_inline_sizes<'a>(&'a mut self) -> &'a mut Vec<Au> {
&mut self.col_inline_sizes
}
fn col_min_inline_sizes<'a>(&'a self) -> &'a Vec<Au> {
&self.col_min_inline_sizes
}
fn col_pref_inline_sizes<'a>(&'a self) -> &'a Vec<Au> {
&self.col_pref_inline_sizes
}
/// The specified column inline-sizes are set from column group and the first row for the fixed
/// table layout calculation.
/// The maximum min/pref inline-sizes of each column are set from the rows for the automatic
/// table layout calculation.
fn bubble_inline_sizes(&mut self, _: &LayoutContext) {
let mut min_inline_size = Au(0);
let mut pref_inline_size = Au(0);
let mut did_first_row = false;
for kid in self.block_flow.base.child_iter() {
assert!(kid.is_proper_table_child());
if kid.is_table_colgroup() {
self.col_inline_sizes.push_all(kid.as_table_colgroup().inline_sizes.as_slice());
self.col_min_inline_sizes = self.col_inline_sizes.clone();
self.col_pref_inline_sizes = self.col_inline_sizes.clone();
} else if kid.is_table_rowgroup() || kid.is_table_row() {
// read column inline-sizes from table-row-group/table-row, and assign
// inline-size=0 for the columns not defined in column-group
// FIXME: need to read inline-sizes from either table-header-group OR
// first table-row
match self.table_layout {
FixedLayout => {
let kid_col_inline_sizes = kid.col_inline_sizes();
if !did_first_row {
did_first_row = true;
let mut child_inline_sizes = kid_col_inline_sizes.iter();
for col_inline_size in self.col_inline_sizes.mut_iter() {
match child_inline_sizes.next() {
Some(child_inline_size) => {
if *col_inline_size == Au::new(0) {
*col_inline_size = *child_inline_size;
}
},
None => break
}
}
}
let num_child_cols = kid_col_inline_sizes.len();
let num_cols = self.col_inline_sizes.len();
debug!("table until the previous row has {} column(s) and this row has {} column(s)",
num_cols, num_child_cols);
for i in range(num_cols, num_child_cols) {
self.col_inline_sizes.push((*kid_col_inline_sizes)[i]);
}
},
AutoLayout => {
min_inline_size = TableFlow::update_col_inline_sizes(&mut self.col_min_inline_sizes, kid.col_min_inline_sizes());
pref_inline_size = TableFlow::update_col_inline_sizes(&mut self.col_pref_inline_sizes, kid.col_pref_inline_sizes());
// update the number of column inline-sizes from table-rows.
let num_cols = self.col_min_inline_sizes.len();
let num_child_cols = kid.col_min_inline_sizes().len();
debug!("table until the previous row has {} column(s) and this row has {} column(s)",
num_cols, num_child_cols);
for i in range(num_cols, num_child_cols) {
self.col_inline_sizes.push(Au::new(0));
let new_kid_min = kid.col_min_inline_sizes()[i];
self.col_min_inline_sizes.push( new_kid_min );
let new_kid_pref = kid.col_pref_inline_sizes()[i];
self.col_pref_inline_sizes.push( new_kid_pref );
min_inline_size = min_inline_size + new_kid_min;
pref_inline_size = pref_inline_size + new_kid_pref;
}
}
}
}
}
self.block_flow.base.intrinsic_inline_sizes.minimum_inline_size = min_inline_size;
self.block_flow.base.intrinsic_inline_sizes.preferred_inline_size =
geometry::max(min_inline_size, pref_inline_size);
}
/// Recursively (top-down) determines the actual inline-size of child contexts and fragments. When
/// called on this context, the context has had its inline-size set by the parent context.
fn assign_inline_sizes(&mut self, ctx: &LayoutContext) {
debug!("assign_inline_sizes({}): assigning inline_size for flow", "table");
// The position was set to the containing block by the flow's parent.
let containing_block_inline_size = self.block_flow.base.position.size.inline;
let mut num_unspecified_inline_sizes = 0;
let mut total_column_inline_size = Au::new(0);
for col_inline_size in self.col_inline_sizes.iter() {
if *col_inline_size == Au::new(0) {
num_unspecified_inline_sizes += 1;
} else {
total_column_inline_size = total_column_inline_size.add(col_inline_size);
}
}
let inline_size_computer = InternalTable;
inline_size_computer.compute_used_inline_size(&mut self.block_flow, ctx, containing_block_inline_size);
let inline_start_content_edge = self.block_flow.fragment.border_padding.inline_start;
let padding_and_borders = self.block_flow.fragment.border_padding.inline_start_end();
let content_inline_size = self.block_flow.fragment.border_box.size.inline - padding_and_borders;
match self.table_layout {
FixedLayout => {
// In fixed table layout, we distribute extra space among the unspecified columns if there are
// any, or among all the columns if all are specified.
if (total_column_inline_size < content_inline_size) && (num_unspecified_inline_sizes == 0) {
let ratio = content_inline_size.to_f64().unwrap() / total_column_inline_size.to_f64().unwrap();
for col_inline_size in self.col_inline_sizes.mut_iter() {
*col_inline_size = (*col_inline_size).scale_by(ratio);
}
} else if num_unspecified_inline_sizes != 0 {
let extra_column_inline_size = (content_inline_size - total_column_inline_size) / Au::new(num_unspecified_inline_sizes);
for col_inline_size in self.col_inline_sizes.mut_iter() {
if *col_inline_size == Au(0) {
*col_inline_size = extra_column_inline_size;
}
}
}
}
_ => {}
}
self.block_flow.propagate_assigned_inline_size_to_children(inline_start_content_edge, content_inline_size, Some(self.col_inline_sizes.clone()));
}
fn assign_block_size<'a>(&mut self, ctx: &'a LayoutContext<'a>) {
debug!("assign_block_size: assigning block_size for table");
self.assign_block_size_table_base(ctx);
}
fn compute_absolute_position(&mut self) {
self.block_flow.compute_absolute_position()
}
}
impl fmt::Show for TableFlow {
/// Outputs a debugging string describing this table flow.
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "TableFlow: {}", self.block_flow)
}
}
/// Table, TableRowGroup, TableRow, TableCell types.
/// Their inline-sizes are calculated in the same way and do not have margins.
pub struct InternalTable;
impl ISizeAndMarginsComputer for InternalTable {
/// Compute the used value of inline-size, taking care of min-inline-size and max-inline-size.
///
/// CSS Section 10.4: Minimum and Maximum inline-sizes
fn compute_used_inline_size(&self,
block: &mut BlockFlow,
ctx: &LayoutContext,
parent_flow_inline_size: Au) {
let input = self.compute_inline_size_constraint_inputs(block, parent_flow_inline_size, ctx);
let solution = self.solve_inline_size_constraints(block, &input);
self.set_inline_size_constraint_solutions(block, solution);
}
/// Solve the inline-size and margins constraints for this block flow.
fn solve_inline_size_constraints(&self, _: &mut BlockFlow, input: &ISizeConstraintInput)
-> ISizeConstraintSolution {
ISizeConstraintSolution::new(input.available_inline_size, Au::new(0), Au::new(0))
}
}

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/. */
//! CSS table formatting contexts.
#![deny(unsafe_block)]
use block::BlockFlow;
use construct::FlowConstructor;
use context::LayoutContext;
use flow::{TableCaptionFlowClass, FlowClass, Flow};
use wrapper::ThreadSafeLayoutNode;
use std::fmt;
/// A table formatting context.
pub struct TableCaptionFlow {
pub block_flow: BlockFlow,
}
impl TableCaptionFlow {
pub fn from_node(constructor: &mut FlowConstructor,
node: &ThreadSafeLayoutNode)
-> TableCaptionFlow {
TableCaptionFlow {
block_flow: BlockFlow::from_node(constructor, node)
}
}
pub fn build_display_list_table_caption(&mut self, layout_context: &LayoutContext) {
debug!("build_display_list_table_caption: same process as block flow");
self.block_flow.build_display_list_block(layout_context)
}
}
impl Flow for TableCaptionFlow {
fn class(&self) -> FlowClass {
TableCaptionFlowClass
}
fn as_table_caption<'a>(&'a mut self) -> &'a mut TableCaptionFlow {
self
}
fn as_block<'a>(&'a mut self) -> &'a mut BlockFlow {
&mut self.block_flow
}
fn bubble_inline_sizes(&mut self, ctx: &LayoutContext) {
self.block_flow.bubble_inline_sizes(ctx);
}
fn assign_inline_sizes(&mut self, ctx: &LayoutContext) {
debug!("assign_inline_sizes({}): assigning inline_size for flow", "table_caption");
self.block_flow.assign_inline_sizes(ctx);
}
fn assign_block_size<'a>(&mut self, ctx: &'a LayoutContext<'a>) {
debug!("assign_block_size: assigning block_size for table_caption");
self.block_flow.assign_block_size(ctx);
}
fn compute_absolute_position(&mut self) {
self.block_flow.compute_absolute_position()
}
}
impl fmt::Show for TableCaptionFlow {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "TableCaptionFlow: {}", self.block_flow)
}
}

View file

@ -0,0 +1,121 @@
/* 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/. */
//! CSS table formatting contexts.
#![deny(unsafe_block)]
use block::{BlockFlow, MarginsMayNotCollapse, ISizeAndMarginsComputer};
use context::LayoutContext;
use flow::{TableCellFlowClass, FlowClass, Flow};
use fragment::Fragment;
use model::{MaybeAuto};
use table::InternalTable;
use wrapper::ThreadSafeLayoutNode;
use servo_util::geometry::Au;
use std::fmt;
/// A table formatting context.
pub struct TableCellFlow {
/// Data common to all flows.
pub block_flow: BlockFlow,
}
impl TableCellFlow {
pub fn from_node_and_fragment(node: &ThreadSafeLayoutNode, fragment: Fragment) -> TableCellFlow {
TableCellFlow {
block_flow: BlockFlow::from_node_and_fragment(node, fragment)
}
}
pub fn fragment<'a>(&'a mut self) -> &'a Fragment {
&self.block_flow.fragment
}
pub fn mut_fragment<'a>(&'a mut self) -> &'a mut Fragment {
&mut self.block_flow.fragment
}
/// Assign block-size for table-cell flow.
///
/// TODO(#2015, pcwalton): This doesn't handle floats right.
///
/// inline(always) because this is only ever called by in-order or non-in-order top-level
/// methods
#[inline(always)]
fn assign_block_size_table_cell_base<'a>(&mut self, layout_context: &'a LayoutContext<'a>) {
self.block_flow.assign_block_size_block_base(layout_context, MarginsMayNotCollapse)
}
pub fn build_display_list_table_cell(&mut self, layout_context: &LayoutContext) {
debug!("build_display_list_table: same process as block flow");
self.block_flow.build_display_list_block(layout_context)
}
}
impl Flow for TableCellFlow {
fn class(&self) -> FlowClass {
TableCellFlowClass
}
fn as_table_cell<'a>(&'a mut self) -> &'a mut TableCellFlow {
self
}
fn as_block<'a>(&'a mut self) -> &'a mut BlockFlow {
&mut self.block_flow
}
/// Minimum/preferred inline-sizes set by this function are used in automatic table layout calculation.
fn bubble_inline_sizes(&mut self, ctx: &LayoutContext) {
self.block_flow.bubble_inline_sizes(ctx);
let specified_inline_size = MaybeAuto::from_style(self.block_flow.fragment.style().content_inline_size(),
Au::new(0)).specified_or_zero();
if self.block_flow.base.intrinsic_inline_sizes.minimum_inline_size < specified_inline_size {
self.block_flow.base.intrinsic_inline_sizes.minimum_inline_size = specified_inline_size;
}
if self.block_flow.base.intrinsic_inline_sizes.preferred_inline_size <
self.block_flow.base.intrinsic_inline_sizes.minimum_inline_size {
self.block_flow.base.intrinsic_inline_sizes.preferred_inline_size =
self.block_flow.base.intrinsic_inline_sizes.minimum_inline_size;
}
}
/// Recursively (top-down) determines the actual inline-size of child contexts and fragments. When
/// called on this context, the context has had its inline-size set by the parent table row.
fn assign_inline_sizes(&mut self, ctx: &LayoutContext) {
debug!("assign_inline_sizes({}): assigning inline_size for flow", "table_cell");
// The position was set to the column inline-size by the parent flow, table row flow.
let containing_block_inline_size = self.block_flow.base.position.size.inline;
let inline_size_computer = InternalTable;
inline_size_computer.compute_used_inline_size(&mut self.block_flow, ctx, containing_block_inline_size);
let inline_start_content_edge = self.block_flow.fragment.border_box.start.i +
self.block_flow.fragment.border_padding.inline_start;
let padding_and_borders = self.block_flow.fragment.border_padding.inline_start_end();
let content_inline_size = self.block_flow.fragment.border_box.size.inline - padding_and_borders;
self.block_flow.propagate_assigned_inline_size_to_children(inline_start_content_edge,
content_inline_size,
None);
}
fn assign_block_size<'a>(&mut self, ctx: &'a LayoutContext<'a>) {
debug!("assign_block_size: assigning block_size for table_cell");
self.assign_block_size_table_cell_base(ctx);
}
fn compute_absolute_position(&mut self) {
self.block_flow.compute_absolute_position()
}
}
impl fmt::Show for TableCellFlow {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "TableCellFlow: {}", self.block_flow)
}
}

View file

@ -0,0 +1,88 @@
/* 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/. */
//! CSS table formatting contexts.
#![deny(unsafe_block)]
use context::LayoutContext;
use flow::{BaseFlow, TableColGroupFlowClass, FlowClass, Flow};
use fragment::{Fragment, TableColumnFragment};
use model::{MaybeAuto};
use wrapper::ThreadSafeLayoutNode;
use servo_util::geometry::Au;
use std::fmt;
/// A table formatting context.
pub struct TableColGroupFlow {
/// Data common to all flows.
pub base: BaseFlow,
/// The associated fragment.
pub fragment: Option<Fragment>,
/// The table column fragments
pub cols: Vec<Fragment>,
/// The specified inline-sizes of table columns
pub inline_sizes: Vec<Au>,
}
impl TableColGroupFlow {
pub fn from_node_and_fragments(node: &ThreadSafeLayoutNode,
fragment: Fragment,
fragments: Vec<Fragment>) -> TableColGroupFlow {
TableColGroupFlow {
base: BaseFlow::new((*node).clone()),
fragment: Some(fragment),
cols: fragments,
inline_sizes: vec!(),
}
}
}
impl Flow for TableColGroupFlow {
fn class(&self) -> FlowClass {
TableColGroupFlowClass
}
fn as_table_colgroup<'a>(&'a mut self) -> &'a mut TableColGroupFlow {
self
}
fn bubble_inline_sizes(&mut self, _: &LayoutContext) {
for fragment in self.cols.iter() {
// get the specified value from inline-size property
let inline_size = MaybeAuto::from_style(fragment.style().content_inline_size(),
Au::new(0)).specified_or_zero();
let span: int = match fragment.specific {
TableColumnFragment(col_fragment) => col_fragment.span.unwrap_or(1),
_ => fail!("Other fragment come out in TableColGroupFlow. {:?}", fragment.specific)
};
for _ in range(0, span) {
self.inline_sizes.push(inline_size);
}
}
}
/// Table column inline-sizes are assigned in table flow and propagated to table row or rowgroup flow.
/// Therefore, table colgroup flow does not need to assign its inline-size.
fn assign_inline_sizes(&mut self, _ctx: &LayoutContext) {
}
/// Table column do not have block-size.
fn assign_block_size(&mut self, _ctx: &LayoutContext) {
}
}
impl fmt::Show for TableColGroupFlow {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self.fragment {
Some(ref rb) => write!(f, "TableColGroupFlow: {}", rb),
None => write!(f, "TableColGroupFlow"),
}
}
}

View file

@ -0,0 +1,225 @@
/* 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/. */
//! CSS table formatting contexts.
#![deny(unsafe_block)]
use block::BlockFlow;
use block::ISizeAndMarginsComputer;
use construct::FlowConstructor;
use context::LayoutContext;
use flow::{TableRowFlowClass, FlowClass, Flow, ImmutableFlowUtils};
use flow;
use fragment::Fragment;
use table::InternalTable;
use model::{MaybeAuto, Specified, Auto};
use wrapper::ThreadSafeLayoutNode;
use servo_util::geometry::Au;
use servo_util::geometry;
use std::fmt;
/// A table formatting context.
pub struct TableRowFlow {
pub block_flow: BlockFlow,
/// Column inline-sizes.
pub col_inline_sizes: Vec<Au>,
/// Column min inline-sizes.
pub col_min_inline_sizes: Vec<Au>,
/// Column pref inline-sizes.
pub col_pref_inline_sizes: Vec<Au>,
}
impl TableRowFlow {
pub fn from_node_and_fragment(node: &ThreadSafeLayoutNode,
fragment: Fragment)
-> TableRowFlow {
TableRowFlow {
block_flow: BlockFlow::from_node_and_fragment(node, fragment),
col_inline_sizes: vec!(),
col_min_inline_sizes: vec!(),
col_pref_inline_sizes: vec!(),
}
}
pub fn from_node(constructor: &mut FlowConstructor,
node: &ThreadSafeLayoutNode)
-> TableRowFlow {
TableRowFlow {
block_flow: BlockFlow::from_node(constructor, node),
col_inline_sizes: vec!(),
col_min_inline_sizes: vec!(),
col_pref_inline_sizes: vec!(),
}
}
pub fn fragment<'a>(&'a mut self) -> &'a Fragment {
&self.block_flow.fragment
}
fn initialize_offsets(&mut self) -> (Au, Au, Au) {
// TODO: If border-collapse: collapse, block-start_offset, block-end_offset, and inline-start_offset
// should be updated. Currently, they are set as Au(0).
(Au(0), Au(0), Au(0))
}
/// Assign block-size for table-row flow.
///
/// TODO(pcwalton): This doesn't handle floats and positioned elements right.
///
/// inline(always) because this is only ever called by in-order or non-in-order top-level
/// methods
#[inline(always)]
fn assign_block_size_table_row_base<'a>(&mut self, layout_context: &'a LayoutContext<'a>) {
let (block_start_offset, _, _) = self.initialize_offsets();
let /* mut */ cur_y = block_start_offset;
// Per CSS 2.1 § 17.5.3, find max_y = max( computed `block-size`, minimum block-size of all cells )
let mut max_y = Au::new(0);
for kid in self.block_flow.base.child_iter() {
kid.assign_block_size_for_inorder_child_if_necessary(layout_context);
{
let child_fragment = kid.as_table_cell().fragment();
// TODO: Percentage block-size
let child_specified_block_size = MaybeAuto::from_style(child_fragment.style().content_block_size(),
Au::new(0)).specified_or_zero();
max_y =
geometry::max(max_y,
child_specified_block_size + child_fragment.border_padding.block_start_end());
}
let child_node = flow::mut_base(kid);
child_node.position.start.b = cur_y;
max_y = geometry::max(max_y, child_node.position.size.block);
}
let mut block_size = max_y;
// TODO: Percentage block-size
block_size = match MaybeAuto::from_style(self.block_flow.fragment.style().content_block_size(), Au(0)) {
Auto => block_size,
Specified(value) => geometry::max(value, block_size)
};
// cur_y = cur_y + block-size;
// Assign the block-size of own fragment
//
// FIXME(pcwalton): Take `cur_y` into account.
let mut position = self.block_flow.fragment.border_box;
position.size.block = block_size;
self.block_flow.fragment.border_box = position;
self.block_flow.base.position.size.block = block_size;
// Assign the block-size of kid fragments, which is the same value as own block-size.
for kid in self.block_flow.base.child_iter() {
{
let kid_fragment = kid.as_table_cell().mut_fragment();
let mut position = kid_fragment.border_box;
position.size.block = block_size;
kid_fragment.border_box = position;
}
let child_node = flow::mut_base(kid);
child_node.position.size.block = block_size;
}
}
pub fn build_display_list_table_row(&mut self, layout_context: &LayoutContext) {
debug!("build_display_list_table_row: same process as block flow");
self.block_flow.build_display_list_block(layout_context)
}
}
impl Flow for TableRowFlow {
fn class(&self) -> FlowClass {
TableRowFlowClass
}
fn as_table_row<'a>(&'a mut self) -> &'a mut TableRowFlow {
self
}
fn as_block<'a>(&'a mut self) -> &'a mut BlockFlow {
&mut self.block_flow
}
fn col_inline_sizes<'a>(&'a mut self) -> &'a mut Vec<Au> {
&mut self.col_inline_sizes
}
fn col_min_inline_sizes<'a>(&'a self) -> &'a Vec<Au> {
&self.col_min_inline_sizes
}
fn col_pref_inline_sizes<'a>(&'a self) -> &'a Vec<Au> {
&self.col_pref_inline_sizes
}
/// Recursively (bottom-up) determines the context's preferred and minimum inline-sizes. When called
/// on this context, all child contexts have had their min/pref inline-sizes set. This function must
/// decide min/pref inline-sizes based on child context inline-sizes and dimensions of any fragments it is
/// responsible for flowing.
/// Min/pref inline-sizes set by this function are used in automatic table layout calculation.
/// The specified column inline-sizes of children cells are used in fixed table layout calculation.
fn bubble_inline_sizes(&mut self, _: &LayoutContext) {
let mut min_inline_size = Au(0);
let mut pref_inline_size = Au(0);
/* find the specified inline_sizes from child table-cell contexts */
for kid in self.block_flow.base.child_iter() {
assert!(kid.is_table_cell());
// collect the specified column inline-sizes of cells. These are used in fixed table layout calculation.
{
let child_fragment = kid.as_table_cell().fragment();
let child_specified_inline_size = MaybeAuto::from_style(child_fragment.style().content_inline_size(),
Au::new(0)).specified_or_zero();
self.col_inline_sizes.push(child_specified_inline_size);
}
// collect min_inline-size & pref_inline-size of children cells for automatic table layout calculation.
let child_base = flow::mut_base(kid);
self.col_min_inline_sizes.push(child_base.intrinsic_inline_sizes.minimum_inline_size);
self.col_pref_inline_sizes.push(child_base.intrinsic_inline_sizes.preferred_inline_size);
min_inline_size = min_inline_size + child_base.intrinsic_inline_sizes.minimum_inline_size;
pref_inline_size = pref_inline_size + child_base.intrinsic_inline_sizes.preferred_inline_size;
}
self.block_flow.base.intrinsic_inline_sizes.minimum_inline_size = min_inline_size;
self.block_flow.base.intrinsic_inline_sizes.preferred_inline_size = geometry::max(min_inline_size,
pref_inline_size);
}
/// Recursively (top-down) determines the actual inline-size of child contexts and fragments. When called
/// on this context, the context has had its inline-size set by the parent context.
fn assign_inline_sizes(&mut self, ctx: &LayoutContext) {
debug!("assign_inline_sizes({}): assigning inline_size for flow", "table_row");
// The position was set to the containing block by the flow's parent.
let containing_block_inline_size = self.block_flow.base.position.size.inline;
// FIXME: In case of border-collapse: collapse, inline-start_content_edge should be border-inline-start
let inline_start_content_edge = Au::new(0);
let inline_size_computer = InternalTable;
inline_size_computer.compute_used_inline_size(&mut self.block_flow, ctx, containing_block_inline_size);
self.block_flow.propagate_assigned_inline_size_to_children(inline_start_content_edge, Au(0), Some(self.col_inline_sizes.clone()));
}
fn assign_block_size<'a>(&mut self, ctx: &'a LayoutContext<'a>) {
debug!("assign_block_size: assigning block_size for table_row");
self.assign_block_size_table_row_base(ctx);
}
fn compute_absolute_position(&mut self) {
self.block_flow.compute_absolute_position()
}
}
impl fmt::Show for TableRowFlow {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "TableRowFlow: {}", self.block_flow.fragment)
}
}

View file

@ -0,0 +1,208 @@
/* 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/. */
//! CSS table formatting contexts.
#![deny(unsafe_block)]
use block::BlockFlow;
use block::ISizeAndMarginsComputer;
use construct::FlowConstructor;
use context::LayoutContext;
use flow::{TableRowGroupFlowClass, FlowClass, Flow, ImmutableFlowUtils};
use flow;
use fragment::Fragment;
use table::{InternalTable, TableFlow};
use wrapper::ThreadSafeLayoutNode;
use servo_util::geometry::Au;
use servo_util::geometry;
use std::fmt;
/// A table formatting context.
pub struct TableRowGroupFlow {
pub block_flow: BlockFlow,
/// Column inline-sizes
pub col_inline_sizes: Vec<Au>,
/// Column min inline-sizes.
pub col_min_inline_sizes: Vec<Au>,
/// Column pref inline-sizes.
pub col_pref_inline_sizes: Vec<Au>,
}
impl TableRowGroupFlow {
pub fn from_node_and_fragment(node: &ThreadSafeLayoutNode,
fragment: Fragment)
-> TableRowGroupFlow {
TableRowGroupFlow {
block_flow: BlockFlow::from_node_and_fragment(node, fragment),
col_inline_sizes: vec!(),
col_min_inline_sizes: vec!(),
col_pref_inline_sizes: vec!(),
}
}
pub fn from_node(constructor: &mut FlowConstructor,
node: &ThreadSafeLayoutNode)
-> TableRowGroupFlow {
TableRowGroupFlow {
block_flow: BlockFlow::from_node(constructor, node),
col_inline_sizes: vec!(),
col_min_inline_sizes: vec!(),
col_pref_inline_sizes: vec!(),
}
}
pub fn fragment<'a>(&'a mut self) -> &'a Fragment {
&self.block_flow.fragment
}
fn initialize_offsets(&mut self) -> (Au, Au, Au) {
// TODO: If border-collapse: collapse, block-start_offset, block-end_offset, and inline-start_offset
// should be updated. Currently, they are set as Au(0).
(Au(0), Au(0), Au(0))
}
/// Assign block-size for table-rowgroup flow.
///
/// FIXME(pcwalton): This doesn't handle floats right.
///
/// inline(always) because this is only ever called by in-order or non-in-order top-level
/// methods
#[inline(always)]
fn assign_block_size_table_rowgroup_base<'a>(&mut self, layout_context: &'a LayoutContext<'a>) {
let (block_start_offset, _, _) = self.initialize_offsets();
let mut cur_y = block_start_offset;
for kid in self.block_flow.base.child_iter() {
kid.assign_block_size_for_inorder_child_if_necessary(layout_context);
let child_node = flow::mut_base(kid);
child_node.position.start.b = cur_y;
cur_y = cur_y + child_node.position.size.block;
}
let block_size = cur_y - block_start_offset;
let mut position = self.block_flow.fragment.border_box;
position.size.block = block_size;
self.block_flow.fragment.border_box = position;
self.block_flow.base.position.size.block = block_size;
}
pub fn build_display_list_table_rowgroup(&mut self, layout_context: &LayoutContext) {
debug!("build_display_list_table_rowgroup: same process as block flow");
self.block_flow.build_display_list_block(layout_context)
}
}
impl Flow for TableRowGroupFlow {
fn class(&self) -> FlowClass {
TableRowGroupFlowClass
}
fn as_table_rowgroup<'a>(&'a mut self) -> &'a mut TableRowGroupFlow {
self
}
fn as_block<'a>(&'a mut self) -> &'a mut BlockFlow {
&mut self.block_flow
}
fn col_inline_sizes<'a>(&'a mut self) -> &'a mut Vec<Au> {
&mut self.col_inline_sizes
}
fn col_min_inline_sizes<'a>(&'a self) -> &'a Vec<Au> {
&self.col_min_inline_sizes
}
fn col_pref_inline_sizes<'a>(&'a self) -> &'a Vec<Au> {
&self.col_pref_inline_sizes
}
/// Recursively (bottom-up) determines the context's preferred and minimum inline-sizes. When called
/// on this context, all child contexts have had their min/pref inline-sizes set. This function must
/// decide min/pref inline-sizes based on child context inline-sizes and dimensions of any fragments it is
/// responsible for flowing.
/// Min/pref inline-sizes set by this function are used in automatic table layout calculation.
/// Also, this function finds the specified column inline-sizes from the first row.
/// Those are used in fixed table layout calculation
fn bubble_inline_sizes(&mut self, _: &LayoutContext) {
let mut min_inline_size = Au(0);
let mut pref_inline_size = Au(0);
for kid in self.block_flow.base.child_iter() {
assert!(kid.is_table_row());
// calculate min_inline-size & pref_inline-size for automatic table layout calculation
// 'self.col_min_inline-sizes' collects the maximum value of cells' min-inline-sizes for each column.
// 'self.col_pref_inline-sizes' collects the maximum value of cells' pref-inline-sizes for each column.
if self.col_inline_sizes.is_empty() { // First Row
assert!(self.col_min_inline_sizes.is_empty() && self.col_pref_inline_sizes.is_empty());
// 'self.col_inline-sizes' collects the specified column inline-sizes from the first table-row for fixed table layout calculation.
self.col_inline_sizes = kid.col_inline_sizes().clone();
self.col_min_inline_sizes = kid.col_min_inline_sizes().clone();
self.col_pref_inline_sizes = kid.col_pref_inline_sizes().clone();
} else {
min_inline_size = TableFlow::update_col_inline_sizes(&mut self.col_min_inline_sizes, kid.col_min_inline_sizes());
pref_inline_size = TableFlow::update_col_inline_sizes(&mut self.col_pref_inline_sizes, kid.col_pref_inline_sizes());
// update the number of column inline-sizes from table-rows.
let num_cols = self.col_inline_sizes.len();
let num_child_cols = kid.col_min_inline_sizes().len();
for i in range(num_cols, num_child_cols) {
self.col_inline_sizes.push(Au::new(0));
let new_kid_min = kid.col_min_inline_sizes()[i];
self.col_min_inline_sizes.push(kid.col_min_inline_sizes()[i]);
let new_kid_pref = kid.col_pref_inline_sizes()[i];
self.col_pref_inline_sizes.push(kid.col_pref_inline_sizes()[i]);
min_inline_size = min_inline_size + new_kid_min;
pref_inline_size = pref_inline_size + new_kid_pref;
}
}
}
self.block_flow.base.intrinsic_inline_sizes.minimum_inline_size = min_inline_size;
self.block_flow.base.intrinsic_inline_sizes.preferred_inline_size = geometry::max(min_inline_size,
pref_inline_size);
}
/// Recursively (top-down) determines the actual inline-size of child contexts and fragments. When
/// called on this context, the context has had its inline-size set by the parent context.
fn assign_inline_sizes(&mut self, ctx: &LayoutContext) {
debug!("assign_inline_sizes({}): assigning inline_size for flow", "table_rowgroup");
// The position was set to the containing block by the flow's parent.
let containing_block_inline_size = self.block_flow.base.position.size.inline;
// FIXME: In case of border-collapse: collapse, inline-start_content_edge should be
// the border width on the inline-start side.
let inline_start_content_edge = Au::new(0);
let content_inline_size = containing_block_inline_size;
let inline_size_computer = InternalTable;
inline_size_computer.compute_used_inline_size(&mut self.block_flow, ctx, containing_block_inline_size);
self.block_flow.propagate_assigned_inline_size_to_children(inline_start_content_edge, content_inline_size, Some(self.col_inline_sizes.clone()));
}
fn assign_block_size<'a>(&mut self, ctx: &'a LayoutContext<'a>) {
debug!("assign_block_size: assigning block_size for table_rowgroup");
self.assign_block_size_table_rowgroup_base(ctx);
}
fn compute_absolute_position(&mut self) {
self.block_flow.compute_absolute_position()
}
}
impl fmt::Show for TableRowGroupFlow {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "TableRowGroupFlow: {}", self.block_flow.fragment)
}
}

View file

@ -0,0 +1,325 @@
/* 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/. */
//! CSS table formatting contexts.
#![deny(unsafe_block)]
use block::{BlockFlow, MarginsMayNotCollapse, ISizeAndMarginsComputer};
use block::{ISizeConstraintInput, ISizeConstraintSolution};
use construct::FlowConstructor;
use context::LayoutContext;
use floats::FloatKind;
use flow::{TableWrapperFlowClass, FlowClass, Flow, ImmutableFlowUtils};
use fragment::Fragment;
use model::{Specified, Auto, specified};
use wrapper::ThreadSafeLayoutNode;
use servo_util::geometry::Au;
use servo_util::geometry;
use std::fmt;
use style::computed_values::table_layout;
pub enum TableLayout {
FixedLayout,
AutoLayout
}
/// A table wrapper flow based on a block formatting context.
pub struct TableWrapperFlow {
pub block_flow: BlockFlow,
/// Column inline-sizes
pub col_inline_sizes: Vec<Au>,
/// Table-layout property
pub table_layout: TableLayout,
}
impl TableWrapperFlow {
pub fn from_node_and_fragment(node: &ThreadSafeLayoutNode,
fragment: Fragment)
-> TableWrapperFlow {
let mut block_flow = BlockFlow::from_node_and_fragment(node, fragment);
let table_layout = if block_flow.fragment().style().get_table().table_layout ==
table_layout::fixed {
FixedLayout
} else {
AutoLayout
};
TableWrapperFlow {
block_flow: block_flow,
col_inline_sizes: vec!(),
table_layout: table_layout
}
}
pub fn from_node(constructor: &mut FlowConstructor,
node: &ThreadSafeLayoutNode)
-> TableWrapperFlow {
let mut block_flow = BlockFlow::from_node(constructor, node);
let table_layout = if block_flow.fragment().style().get_table().table_layout ==
table_layout::fixed {
FixedLayout
} else {
AutoLayout
};
TableWrapperFlow {
block_flow: block_flow,
col_inline_sizes: vec!(),
table_layout: table_layout
}
}
pub fn float_from_node(constructor: &mut FlowConstructor,
node: &ThreadSafeLayoutNode,
float_kind: FloatKind)
-> TableWrapperFlow {
let mut block_flow = BlockFlow::float_from_node(constructor, node, float_kind);
let table_layout = if block_flow.fragment().style().get_table().table_layout ==
table_layout::fixed {
FixedLayout
} else {
AutoLayout
};
TableWrapperFlow {
block_flow: block_flow,
col_inline_sizes: vec!(),
table_layout: table_layout
}
}
pub fn is_float(&self) -> bool {
self.block_flow.float.is_some()
}
/// Assign block-size for table-wrapper flow.
/// `Assign block-size` of table-wrapper flow follows a similar process to that of block flow.
///
/// inline(always) because this is only ever called by in-order or non-in-order top-level
/// methods
#[inline(always)]
fn assign_block_size_table_wrapper_base<'a>(&mut self, layout_context: &'a LayoutContext<'a>) {
self.block_flow.assign_block_size_block_base(layout_context, MarginsMayNotCollapse);
}
pub fn build_display_list_table_wrapper(&mut self, layout_context: &LayoutContext) {
debug!("build_display_list_table_wrapper: same process as block flow");
self.block_flow.build_display_list_block(layout_context);
}
}
impl Flow for TableWrapperFlow {
fn class(&self) -> FlowClass {
TableWrapperFlowClass
}
fn as_table_wrapper<'a>(&'a mut self) -> &'a mut TableWrapperFlow {
self
}
fn as_block<'a>(&'a mut self) -> &'a mut BlockFlow {
&mut self.block_flow
}
/* Recursively (bottom-up) determine the context's preferred and
minimum inline_sizes. When called on this context, all child contexts
have had their min/pref inline_sizes set. This function must decide
min/pref inline_sizes based on child context inline_sizes and dimensions of
any fragments it is responsible for flowing. */
fn bubble_inline_sizes(&mut self, ctx: &LayoutContext) {
// get column inline-sizes info from table flow
for kid in self.block_flow.base.child_iter() {
assert!(kid.is_table_caption() || kid.is_table());
if kid.is_table() {
self.col_inline_sizes.push_all(kid.as_table().col_inline_sizes.as_slice());
}
}
self.block_flow.bubble_inline_sizes(ctx);
}
/// Recursively (top-down) determines the actual inline-size of child contexts and fragments. When
/// called on this context, the context has had its inline-size set by the parent context.
///
/// Dual fragments consume some inline-size first, and the remainder is assigned to all child (block)
/// contexts.
fn assign_inline_sizes(&mut self, ctx: &LayoutContext) {
debug!("assign_inline_sizes({}): assigning inline_size for flow",
if self.is_float() {
"floated table_wrapper"
} else {
"table_wrapper"
});
// The position was set to the containing block by the flow's parent.
let containing_block_inline_size = self.block_flow.base.position.size.inline;
let inline_size_computer = TableWrapper;
inline_size_computer.compute_used_inline_size_table_wrapper(self, ctx, containing_block_inline_size);
let inline_start_content_edge = self.block_flow.fragment.border_box.start.i;
let content_inline_size = self.block_flow.fragment.border_box.size.inline;
match self.table_layout {
FixedLayout | _ if self.is_float() =>
self.block_flow.base.position.size.inline = content_inline_size,
_ => {}
}
// In case of fixed layout, column inline-sizes are calculated in table flow.
let assigned_col_inline_sizes = match self.table_layout {
FixedLayout => None,
AutoLayout => Some(self.col_inline_sizes.clone())
};
self.block_flow.propagate_assigned_inline_size_to_children(inline_start_content_edge, content_inline_size, assigned_col_inline_sizes);
}
fn assign_block_size<'a>(&mut self, ctx: &'a LayoutContext<'a>) {
if self.is_float() {
debug!("assign_block_size_float: assigning block_size for floated table_wrapper");
self.block_flow.assign_block_size_float(ctx);
} else {
debug!("assign_block_size: assigning block_size for table_wrapper");
self.assign_block_size_table_wrapper_base(ctx);
}
}
fn compute_absolute_position(&mut self) {
self.block_flow.compute_absolute_position()
}
}
impl fmt::Show for TableWrapperFlow {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if self.is_float() {
write!(f, "TableWrapperFlow(Float): {}", self.block_flow.fragment)
} else {
write!(f, "TableWrapperFlow: {}", self.block_flow.fragment)
}
}
}
struct TableWrapper;
impl TableWrapper {
fn compute_used_inline_size_table_wrapper(&self,
table_wrapper: &mut TableWrapperFlow,
ctx: &LayoutContext,
parent_flow_inline_size: Au) {
let input = self.compute_inline_size_constraint_inputs_table_wrapper(table_wrapper,
parent_flow_inline_size,
ctx);
let solution = self.solve_inline_size_constraints(&mut table_wrapper.block_flow, &input);
self.set_inline_size_constraint_solutions(&mut table_wrapper.block_flow, solution);
self.set_flow_x_coord_if_necessary(&mut table_wrapper.block_flow, solution);
}
fn compute_inline_size_constraint_inputs_table_wrapper(&self,
table_wrapper: &mut TableWrapperFlow,
parent_flow_inline_size: Au,
ctx: &LayoutContext)
-> ISizeConstraintInput {
let mut input = self.compute_inline_size_constraint_inputs(&mut table_wrapper.block_flow,
parent_flow_inline_size,
ctx);
let computed_inline_size = match table_wrapper.table_layout {
FixedLayout => {
let fixed_cells_inline_size = table_wrapper.col_inline_sizes.iter().fold(Au(0),
|sum, inline_size| sum.add(inline_size));
let mut computed_inline_size = input.computed_inline_size.specified_or_zero();
let style = table_wrapper.block_flow.fragment.style();
// Get inline-start and inline-end paddings, borders for table.
// We get these values from the fragment's style since table_wrapper doesn't have it's own border or padding.
// input.available_inline-size is same as containing_block_inline-size in table_wrapper.
let padding = style.logical_padding();
let border = style.logical_border_width();
let padding_and_borders =
specified(padding.inline_start, input.available_inline_size) +
specified(padding.inline_end, input.available_inline_size) +
border.inline_start +
border.inline_end;
// Compare border-edge inline-sizes. Because fixed_cells_inline-size indicates content-inline-size,
// padding and border values are added to fixed_cells_inline-size.
computed_inline_size = geometry::max(
fixed_cells_inline_size + padding_and_borders, computed_inline_size);
computed_inline_size
},
AutoLayout => {
// Automatic table layout is calculated according to CSS 2.1 § 17.5.2.2.
let mut cap_min = Au(0);
let mut cols_min = Au(0);
let mut cols_max = Au(0);
let mut col_min_inline_sizes = &vec!();
let mut col_pref_inline_sizes = &vec!();
for kid in table_wrapper.block_flow.base.child_iter() {
if kid.is_table_caption() {
cap_min = kid.as_block().base.intrinsic_inline_sizes.minimum_inline_size;
} else {
assert!(kid.is_table());
cols_min = kid.as_block().base.intrinsic_inline_sizes.minimum_inline_size;
cols_max = kid.as_block().base.intrinsic_inline_sizes.preferred_inline_size;
col_min_inline_sizes = kid.col_min_inline_sizes();
col_pref_inline_sizes = kid.col_pref_inline_sizes();
}
}
// 'extra_inline-size': difference between the calculated table inline-size and minimum inline-size
// required by all columns. It will be distributed over the columns.
let (inline_size, extra_inline_size) = match input.computed_inline_size {
Auto => {
if input.available_inline_size > geometry::max(cols_max, cap_min) {
if cols_max > cap_min {
table_wrapper.col_inline_sizes = col_pref_inline_sizes.clone();
(cols_max, Au(0))
} else {
(cap_min, cap_min - cols_min)
}
} else {
let max = if cols_min >= input.available_inline_size && cols_min >= cap_min {
table_wrapper.col_inline_sizes = col_min_inline_sizes.clone();
cols_min
} else {
geometry::max(input.available_inline_size, cap_min)
};
(max, max - cols_min)
}
},
Specified(inline_size) => {
let max = if cols_min >= inline_size && cols_min >= cap_min {
table_wrapper.col_inline_sizes = col_min_inline_sizes.clone();
cols_min
} else {
geometry::max(inline_size, cap_min)
};
(max, max - cols_min)
}
};
// The extra inline-size is distributed over the columns
if extra_inline_size > Au(0) {
let cell_len = table_wrapper.col_inline_sizes.len() as f64;
table_wrapper.col_inline_sizes = col_min_inline_sizes.iter().map(|inline_size| {
inline_size + extra_inline_size.scale_by(1.0 / cell_len)
}).collect();
}
inline_size
}
};
input.computed_inline_size = Specified(computed_inline_size);
input
}
}
impl ISizeAndMarginsComputer for TableWrapper {
/// Solve the inline-size and margins constraints for this block flow.
fn solve_inline_size_constraints(&self, block: &mut BlockFlow, input: &ISizeConstraintInput)
-> ISizeConstraintSolution {
self.solve_block_inline_size_constraints(block, input)
}
}

327
components/layout/text.rs Normal file
View file

@ -0,0 +1,327 @@
/* 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/. */
//! Text layout.
#![deny(unsafe_block)]
use flow::Flow;
use fragment::{Fragment, ScannedTextFragment, ScannedTextFragmentInfo, UnscannedTextFragment};
use gfx::font::{FontMetrics, FontStyle, RunMetrics};
use gfx::font_context::FontContext;
use gfx::text::glyph::CharIndex;
use gfx::text::text_run::TextRun;
use gfx::text::util::{CompressWhitespaceNewline, transform_text, CompressNone};
use servo_util::geometry::Au;
use servo_util::logical_geometry::{LogicalSize, WritingMode};
use servo_util::range::Range;
use style::ComputedValues;
use style::computed_values::{font_family, line_height, text_orientation, white_space};
use sync::Arc;
struct NewLinePositions {
new_line_pos: Vec<CharIndex>,
}
// A helper function.
fn can_coalesce_text_nodes(fragments: &[Fragment], left_i: uint, right_i: uint) -> bool {
assert!(left_i != right_i);
fragments[left_i].can_merge_with_fragment(&fragments[right_i])
}
/// A stack-allocated object for scanning an inline flow into `TextRun`-containing `TextFragment`s.
pub struct TextRunScanner {
pub clump: Range<CharIndex>,
}
impl TextRunScanner {
pub fn new() -> TextRunScanner {
TextRunScanner {
clump: Range::empty(),
}
}
pub fn scan_for_runs(&mut self, font_context: &mut FontContext, flow: &mut Flow) {
{
let inline = flow.as_immutable_inline();
debug!("TextRunScanner: scanning {:u} fragments for text runs...", inline.fragments.len());
}
let fragments = &mut flow.as_inline().fragments;
let mut last_whitespace = true;
let mut new_fragments = Vec::new();
for fragment_i in range(0, fragments.fragments.len()) {
debug!("TextRunScanner: considering fragment: {:u}", fragment_i);
if fragment_i > 0 && !can_coalesce_text_nodes(fragments.fragments.as_slice(), fragment_i - 1, fragment_i) {
last_whitespace = self.flush_clump_to_list(font_context,
fragments.fragments.as_slice(),
&mut new_fragments,
last_whitespace);
}
self.clump.extend_by(CharIndex(1));
}
// Handle remaining clumps.
if self.clump.length() > CharIndex(0) {
drop(self.flush_clump_to_list(font_context,
fragments.fragments.as_slice(),
&mut new_fragments,
last_whitespace))
}
debug!("TextRunScanner: swapping out fragments.");
fragments.fragments = new_fragments;
}
/// A "clump" is a range of inline flow leaves that can be merged together into a single
/// fragment. Adjacent text with the same style can be merged, and nothing else can.
///
/// The flow keeps track of the fragments contained by all non-leaf DOM nodes. This is necessary
/// for correct painting order. Since we compress several leaf fragments here, the mapping must
/// be adjusted.
///
/// FIXME(#2267, pcwalton): Stop cloning fragments. Instead we will need to replace each
/// `in_fragment` with some smaller stub.
fn flush_clump_to_list(&mut self,
font_context: &mut FontContext,
in_fragments: &[Fragment],
out_fragments: &mut Vec<Fragment>,
last_whitespace: bool)
-> bool {
assert!(self.clump.length() > CharIndex(0));
debug!("TextRunScanner: flushing fragments in range={}", self.clump);
let is_singleton = self.clump.length() == CharIndex(1);
let is_text_clump = match in_fragments[self.clump.begin().to_uint()].specific {
UnscannedTextFragment(_) => true,
_ => false,
};
let mut new_whitespace = last_whitespace;
match (is_singleton, is_text_clump) {
(false, false) => {
fail!("WAT: can't coalesce non-text nodes in flush_clump_to_list()!")
}
(true, false) => {
// FIXME(pcwalton): Stop cloning fragments, as above.
debug!("TextRunScanner: pushing single non-text fragment in range: {}", self.clump);
let new_fragment = in_fragments[self.clump.begin().to_uint()].clone();
out_fragments.push(new_fragment)
},
(true, true) => {
let old_fragment = &in_fragments[self.clump.begin().to_uint()];
let text = match old_fragment.specific {
UnscannedTextFragment(ref text_fragment_info) => &text_fragment_info.text,
_ => fail!("Expected an unscanned text fragment!"),
};
let font_style = old_fragment.font_style();
let compression = match old_fragment.white_space() {
white_space::normal => CompressWhitespaceNewline,
white_space::pre => CompressNone,
};
let mut new_line_pos = vec![];
let (transformed_text, whitespace) = transform_text(text.as_slice(),
compression,
last_whitespace,
&mut new_line_pos);
new_whitespace = whitespace;
if transformed_text.len() > 0 {
// TODO(#177): Text run creation must account for the renderability of text by
// font group fonts. This is probably achieved by creating the font group above
// and then letting `FontGroup` decide which `Font` to stick into the text run.
let fontgroup = font_context.get_layout_font_group_for_style(&font_style);
let run = box fontgroup.create_textrun(
transformed_text.clone());
debug!("TextRunScanner: pushing single text fragment in range: {} ({})",
self.clump,
*text);
let range = Range::new(CharIndex(0), run.char_len());
let new_metrics = run.metrics_for_range(&range);
let bounding_box_size = bounding_box_for_run_metrics(
&new_metrics, old_fragment.style.writing_mode);
let new_text_fragment_info = ScannedTextFragmentInfo::new(Arc::new(run), range);
let mut new_fragment = old_fragment.transform(
bounding_box_size, ScannedTextFragment(new_text_fragment_info));
new_fragment.new_line_pos = new_line_pos;
out_fragments.push(new_fragment)
}
},
(false, true) => {
// TODO(#177): Text run creation must account for the renderability of text by
// font group fonts. This is probably achieved by creating the font group above
// and then letting `FontGroup` decide which `Font` to stick into the text run.
let in_fragment = &in_fragments[self.clump.begin().to_uint()];
let font_style = in_fragment.font_style();
let fontgroup = font_context.get_layout_font_group_for_style(&font_style);
let compression = match in_fragment.white_space() {
white_space::normal => CompressWhitespaceNewline,
white_space::pre => CompressNone,
};
let mut new_line_positions: Vec<NewLinePositions> = vec![];
// First, transform/compress text of all the nodes.
let mut last_whitespace_in_clump = new_whitespace;
let transformed_strs: Vec<String> = Vec::from_fn(self.clump.length().to_uint(), |i| {
let idx = CharIndex(i as int) + self.clump.begin();
let in_fragment = match in_fragments[idx.to_uint()].specific {
UnscannedTextFragment(ref text_fragment_info) => &text_fragment_info.text,
_ => fail!("Expected an unscanned text fragment!"),
};
let mut new_line_pos = vec![];
let (new_str, new_whitespace) = transform_text(in_fragment.as_slice(),
compression,
last_whitespace_in_clump,
&mut new_line_pos);
new_line_positions.push(NewLinePositions { new_line_pos: new_line_pos });
last_whitespace_in_clump = new_whitespace;
new_str
});
new_whitespace = last_whitespace_in_clump;
// Next, concatenate all of the transformed strings together, saving the new
// character indices.
let mut run_str = String::new();
let mut new_ranges: Vec<Range<CharIndex>> = vec![];
let mut char_total = CharIndex(0);
for i in range(0, transformed_strs.len() as int) {
let added_chars = CharIndex(transformed_strs[i as uint].as_slice().char_len() as int);
new_ranges.push(Range::new(char_total, added_chars));
run_str.push_str(transformed_strs[i as uint].as_slice());
char_total = char_total + added_chars;
}
// Now create the run.
// TextRuns contain a cycle which is usually resolved by the teardown
// sequence. If no clump takes ownership, however, it will leak.
let clump = self.clump;
let run = if clump.length() != CharIndex(0) && run_str.len() > 0 {
Some(Arc::new(box TextRun::new(
&mut *fontgroup.fonts[0].borrow_mut(),
run_str.to_string())))
} else {
None
};
// Make new fragments with the run and adjusted text indices.
debug!("TextRunScanner: pushing fragment(s) in range: {}", self.clump);
for i in clump.each_index() {
let logical_offset = i - self.clump.begin();
let range = new_ranges[logical_offset.to_uint()];
if range.length() == CharIndex(0) {
debug!("Elided an `UnscannedTextFragment` because it was zero-length after \
compression; {}", in_fragments[i.to_uint()]);
continue
}
let new_text_fragment_info = ScannedTextFragmentInfo::new(run.get_ref().clone(), range);
let old_fragment = &in_fragments[i.to_uint()];
let new_metrics = new_text_fragment_info.run.metrics_for_range(&range);
let bounding_box_size = bounding_box_for_run_metrics(
&new_metrics, old_fragment.style.writing_mode);
let mut new_fragment = old_fragment.transform(
bounding_box_size, ScannedTextFragment(new_text_fragment_info));
new_fragment.new_line_pos = new_line_positions[logical_offset.to_uint()].new_line_pos.clone();
out_fragments.push(new_fragment)
}
}
} // End of match.
let end = self.clump.end(); // FIXME: borrow checker workaround
self.clump.reset(end, CharIndex(0));
new_whitespace
} // End of `flush_clump_to_list`.
}
#[inline]
fn bounding_box_for_run_metrics(metrics: &RunMetrics, writing_mode: WritingMode)
-> LogicalSize<Au> {
// This does nothing, but it will fail to build
// when more values are added to the `text-orientation` CSS property.
// This will be a reminder to update the code below.
let dummy: Option<text_orientation::T> = None;
match dummy {
Some(text_orientation::sideways_right) |
Some(text_orientation::sideways_left) |
Some(text_orientation::sideways) |
None => {}
}
// In vertical sideways or horizontal upgright text,
// the "width" of text metrics is always inline
// This will need to be updated when other text orientations are supported.
LogicalSize::new(
writing_mode,
metrics.bounding_box.size.width,
metrics.bounding_box.size.height)
}
/// Returns the metrics of the font represented by the given `FontStyle`, respectively.
///
/// `#[inline]` because often the caller only needs a few fields from the font metrics.
#[inline]
pub fn font_metrics_for_style(font_context: &mut FontContext, font_style: &FontStyle)
-> FontMetrics {
let fontgroup = font_context.get_layout_font_group_for_style(font_style);
fontgroup.fonts[0].borrow().metrics.clone()
}
/// Converts a computed style to a font style used for rendering.
///
/// FIXME(pcwalton): This should not be necessary; just make the font part of the style sharable
/// with the display list somehow. (Perhaps we should use an ARC.)
pub fn computed_style_to_font_style(style: &ComputedValues) -> FontStyle {
debug!("(font style) start");
// FIXME: Too much allocation here.
let mut font_families = style.get_font().font_family.iter().map(|family| {
match *family {
font_family::FamilyName(ref name) => (*name).clone(),
}
});
debug!("(font style) font families: `{:?}`", font_families);
let font_size = style.get_font().font_size.to_f64().unwrap() / 60.0;
debug!("(font style) font size: `{:f}px`", font_size);
FontStyle {
pt_size: font_size,
weight: style.get_font().font_weight,
style: style.get_font().font_style,
families: font_families.collect(),
}
}
/// Returns the line block-size needed by the given computed style and font size.
pub fn line_height_from_style(style: &ComputedValues, metrics: &FontMetrics) -> Au {
let font_size = style.get_font().font_size;
let from_inline = match style.get_inheritedbox().line_height {
line_height::Normal => metrics.line_gap,
line_height::Number(l) => font_size.scale_by(l),
line_height::Length(l) => l
};
let minimum = style.get_inheritedbox()._servo_minimum_line_height;
Au::max(from_inline, minimum)
}

164
components/layout/util.rs Normal file
View file

@ -0,0 +1,164 @@
/* 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 construct::{ConstructionResult, NoConstructionResult};
use incremental::RestyleDamage;
use parallel::DomParallelInfo;
use wrapper::{LayoutNode, TLayoutNode, ThreadSafeLayoutNode};
use gfx::display_list::OpaqueNode;
use gfx;
use libc::uintptr_t;
use script::dom::bindings::js::JS;
use script::dom::bindings::utils::Reflectable;
use script::dom::node::{Node, SharedLayoutData};
use script::layout_interface::{LayoutChan, UntrustedNodeAddress, TrustedNodeAddress};
use std::mem;
use std::cell::{Ref, RefMut};
use style::ComputedValues;
use style;
use sync::Arc;
/// Data that layout associates with a node.
pub struct PrivateLayoutData {
/// The results of CSS styling for this node's `before` pseudo-element, if any.
pub before_style: Option<Arc<ComputedValues>>,
/// The results of CSS styling for this node's `after` pseudo-element, if any.
pub after_style: Option<Arc<ComputedValues>>,
/// Description of how to account for recent style changes.
pub restyle_damage: Option<RestyleDamage>,
/// The current results of flow construction for this node. This is either a flow or a
/// `ConstructionItem`. See comments in `construct.rs` for more details.
pub flow_construction_result: ConstructionResult,
pub before_flow_construction_result: ConstructionResult,
pub after_flow_construction_result: ConstructionResult,
/// Information needed during parallel traversals.
pub parallel: DomParallelInfo,
}
impl PrivateLayoutData {
/// Creates new layout data.
pub fn new() -> PrivateLayoutData {
PrivateLayoutData {
before_style: None,
after_style: None,
restyle_damage: None,
flow_construction_result: NoConstructionResult,
before_flow_construction_result: NoConstructionResult,
after_flow_construction_result: NoConstructionResult,
parallel: DomParallelInfo::new(),
}
}
}
pub struct LayoutDataWrapper {
pub chan: Option<LayoutChan>,
pub shared_data: SharedLayoutData,
pub data: Box<PrivateLayoutData>,
}
/// A trait that allows access to the layout data of a DOM node.
pub trait LayoutDataAccess {
/// Borrows the layout data without checks.
unsafe fn borrow_layout_data_unchecked(&self) -> *const Option<LayoutDataWrapper>;
/// Borrows the layout data immutably. Fails on a conflicting borrow.
fn borrow_layout_data<'a>(&'a self) -> Ref<'a,Option<LayoutDataWrapper>>;
/// Borrows the layout data mutably. Fails on a conflicting borrow.
fn mutate_layout_data<'a>(&'a self) -> RefMut<'a,Option<LayoutDataWrapper>>;
}
impl<'ln> LayoutDataAccess for LayoutNode<'ln> {
#[inline(always)]
unsafe fn borrow_layout_data_unchecked(&self) -> *const Option<LayoutDataWrapper> {
mem::transmute(self.get().layout_data.borrow_unchecked())
}
#[inline(always)]
fn borrow_layout_data<'a>(&'a self) -> Ref<'a,Option<LayoutDataWrapper>> {
unsafe {
mem::transmute(self.get().layout_data.borrow())
}
}
#[inline(always)]
fn mutate_layout_data<'a>(&'a self) -> RefMut<'a,Option<LayoutDataWrapper>> {
unsafe {
mem::transmute(self.get().layout_data.borrow_mut())
}
}
}
pub trait OpaqueNodeMethods {
/// Converts a DOM node (layout view) to an `OpaqueNode`.
fn from_layout_node(node: &LayoutNode) -> Self;
/// Converts a thread-safe DOM node (layout view) to an `OpaqueNode`.
fn from_thread_safe_layout_node(node: &ThreadSafeLayoutNode) -> Self;
/// Converts a DOM node (script view) to an `OpaqueNode`.
fn from_script_node(node: TrustedNodeAddress) -> Self;
/// Converts a DOM node to an `OpaqueNode'.
fn from_jsmanaged(node: &JS<Node>) -> Self;
/// Converts this node to an `UntrustedNodeAddress`. An `UntrustedNodeAddress` is just the type
/// of node that script expects to receive in a hit test.
fn to_untrusted_node_address(&self) -> UntrustedNodeAddress;
}
impl OpaqueNodeMethods for OpaqueNode {
fn from_layout_node(node: &LayoutNode) -> OpaqueNode {
unsafe {
OpaqueNodeMethods::from_jsmanaged(node.get_jsmanaged())
}
}
fn from_thread_safe_layout_node(node: &ThreadSafeLayoutNode) -> OpaqueNode {
unsafe {
let abstract_node = node.get_jsmanaged();
let ptr: uintptr_t = abstract_node.reflector().get_jsobject() as uint;
OpaqueNode(ptr)
}
}
fn from_script_node(node: TrustedNodeAddress) -> OpaqueNode {
unsafe {
OpaqueNodeMethods::from_jsmanaged(&JS::from_trusted_node_address(node))
}
}
fn from_jsmanaged(node: &JS<Node>) -> OpaqueNode {
unsafe {
let ptr: uintptr_t = mem::transmute(node.reflector().get_jsobject());
OpaqueNode(ptr)
}
}
fn to_untrusted_node_address(&self) -> UntrustedNodeAddress {
unsafe {
let OpaqueNode(addr) = *self;
let addr: UntrustedNodeAddress = mem::transmute(addr);
addr
}
}
}
/// Allows a CSS color to be converted into a graphics color.
pub trait ToGfxColor {
/// Converts a CSS color to a graphics color.
fn to_gfx_color(&self) -> gfx::color::Color;
}
impl ToGfxColor for style::computed_values::RGBA {
fn to_gfx_color(&self) -> gfx::color::Color {
gfx::color::rgba(self.red, self.green, self.blue, self.alpha)
}
}

View file

@ -0,0 +1,783 @@
/* 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 safe wrapper for DOM nodes that prevents layout from mutating the DOM, from letting DOM nodes
//! escape, and from generally doing anything that it isn't supposed to. This is accomplished via
//! a simple whitelist of allowed operations, along with some lifetime magic to prevent nodes from
//! escaping.
//!
//! As a security wrapper is only as good as its whitelist, be careful when adding operations to
//! this list. The cardinal rules are:
//!
//! 1. Layout is not allowed to mutate the DOM.
//!
//! 2. Layout is not allowed to see anything with `JS` in the name, because it could hang
//! onto these objects and cause use-after-free.
//!
//! When implementing wrapper functions, be careful that you do not touch the borrow flags, or you
//! will race and cause spurious task failure. (Note that I do not believe these races are
//! exploitable, but they'll result in brokenness nonetheless.)
//!
//! Rules of the road for this file:
//!
//! * In general, you must not use the `Cast` functions; use explicit checks and `transmute_copy`
//! instead.
//!
//! * You must also not use `.get()`; instead, use `.unsafe_get()`.
//!
//! * Do not call any methods on DOM nodes without checking to see whether they use borrow flags.
//!
//! o Instead of `get_attr()`, use `.get_attr_val_for_layout()`.
//!
//! o Instead of `html_element_in_html_document()`, use
//! `html_element_in_html_document_for_layout()`.
use css::node_style::StyledNode;
use util::LayoutDataWrapper;
use script::dom::bindings::codegen::InheritTypes::{HTMLIFrameElementDerived};
use script::dom::bindings::codegen::InheritTypes::{HTMLImageElementDerived, TextDerived};
use script::dom::bindings::js::JS;
use script::dom::element::{Element, HTMLAreaElementTypeId, HTMLAnchorElementTypeId};
use script::dom::element::{HTMLLinkElementTypeId, LayoutElementHelpers, RawLayoutElementHelpers};
use script::dom::htmliframeelement::HTMLIFrameElement;
use script::dom::htmlimageelement::{HTMLImageElement, LayoutHTMLImageElementHelpers};
use script::dom::node::{DocumentNodeTypeId, ElementNodeTypeId, Node, NodeTypeId};
use script::dom::node::{LayoutNodeHelpers, RawLayoutNodeHelpers, TextNodeTypeId};
use script::dom::text::Text;
use servo_msg::constellation_msg::{PipelineId, SubpageId};
use servo_util::atom::Atom;
use servo_util::namespace::Namespace;
use servo_util::namespace;
use servo_util::str::is_whitespace;
use std::cell::{RefCell, Ref, RefMut};
use std::kinds::marker::ContravariantLifetime;
use std::mem;
use style::computed_values::{content, display, white_space};
use style::{AnyNamespace, AttrSelector, PropertyDeclarationBlock, SpecificNamespace, TElement};
use style::{TNode};
use url::Url;
/// Allows some convenience methods on generic layout nodes.
pub trait TLayoutNode {
/// Creates a new layout node with the same lifetime as this layout node.
unsafe fn new_with_this_lifetime(&self, node: &JS<Node>) -> Self;
/// Returns the type ID of this node. Fails if this node is borrowed mutably. Returns `None`
/// if this is a pseudo-element; otherwise, returns `Some`.
fn type_id(&self) -> Option<NodeTypeId>;
/// Returns the interior of this node as a `JS`. This is highly unsafe for layout to
/// call and as such is marked `unsafe`.
unsafe fn get_jsmanaged<'a>(&'a self) -> &'a JS<Node>;
/// Returns the interior of this node as a `Node`. This is highly unsafe for layout to call
/// and as such is marked `unsafe`.
unsafe fn get<'a>(&'a self) -> &'a Node {
&*self.get_jsmanaged().unsafe_get()
}
fn node_is_element(&self) -> bool {
match self.type_id() {
Some(ElementNodeTypeId(..)) => true,
_ => false
}
}
fn node_is_document(&self) -> bool {
match self.type_id() {
Some(DocumentNodeTypeId(..)) => true,
_ => false
}
}
/// If this is an image element, returns its URL. If this is not an image element, fails.
///
/// FIXME(pcwalton): Don't copy URLs.
fn image_url(&self) -> Option<Url> {
unsafe {
if !self.get().is_htmlimageelement() {
fail!("not an image!")
}
let image_element: JS<HTMLImageElement> = self.get_jsmanaged().transmute_copy();
image_element.image().as_ref().map(|url| (*url).clone())
}
}
/// If this node is an iframe element, returns its pipeline and subpage IDs. If this node is
/// not an iframe element, fails.
fn iframe_pipeline_and_subpage_ids(&self) -> (PipelineId, SubpageId) {
unsafe {
if !self.get().is_htmliframeelement() {
fail!("not an iframe element!")
}
let iframe_element: JS<HTMLIFrameElement> = self.get_jsmanaged().transmute_copy();
let size = (*iframe_element.unsafe_get()).size.deref().get().unwrap();
(size.pipeline_id, size.subpage_id)
}
}
/// If this is a text node, copies out the text. If this is not a text node, fails.
///
/// FIXME(pcwalton): Don't copy text. Atomically reference count instead.
fn text(&self) -> String;
/// Returns the first child of this node.
fn first_child(&self) -> Option<Self>;
/// Dumps this node tree, for debugging.
fn dump(&self) {
// TODO(pcwalton): Reimplement this in a way that's safe for layout to call.
}
}
/// A wrapper so that layout can access only the methods that it should have access to. Layout must
/// only ever see these and must never see instances of `JS`.
pub struct LayoutNode<'a> {
/// The wrapped node.
node: JS<Node>,
/// Being chained to a ContravariantLifetime prevents `LayoutNode`s from escaping.
pub chain: ContravariantLifetime<'a>,
}
impl<'ln> Clone for LayoutNode<'ln> {
fn clone(&self) -> LayoutNode<'ln> {
LayoutNode {
node: self.node.clone(),
chain: self.chain,
}
}
}
impl<'a> PartialEq for LayoutNode<'a> {
#[inline]
fn eq(&self, other: &LayoutNode) -> bool {
self.node == other.node
}
}
impl<'ln> TLayoutNode for LayoutNode<'ln> {
unsafe fn new_with_this_lifetime(&self, node: &JS<Node>) -> LayoutNode<'ln> {
LayoutNode {
node: node.transmute_copy(),
chain: self.chain,
}
}
fn type_id(&self) -> Option<NodeTypeId> {
unsafe {
Some(self.node.type_id_for_layout())
}
}
unsafe fn get_jsmanaged<'a>(&'a self) -> &'a JS<Node> {
&self.node
}
fn first_child(&self) -> Option<LayoutNode<'ln>> {
unsafe {
self.get_jsmanaged().first_child_ref().map(|node| self.new_with_this_lifetime(&node))
}
}
fn text(&self) -> String {
unsafe {
if !self.get().is_text() {
fail!("not text!")
}
let text: JS<Text> = self.get_jsmanaged().transmute_copy();
(*text.unsafe_get()).characterdata.data.deref().borrow().clone()
}
}
}
impl<'ln> LayoutNode<'ln> {
/// Creates a new layout node, scoped to the given closure.
pub unsafe fn with_layout_node<R>(node: JS<Node>, f: <'a> |LayoutNode<'a>| -> R) -> R {
f(LayoutNode {
node: node,
chain: ContravariantLifetime,
})
}
/// Iterates over this node and all its descendants, in preorder.
///
/// FIXME(pcwalton): Terribly inefficient. We should use parallelism.
pub fn traverse_preorder(&self) -> LayoutTreeIterator<'ln> {
let mut nodes = vec!();
gather_layout_nodes(self, &mut nodes, false);
LayoutTreeIterator::new(nodes)
}
/// Returns an iterator over this node's children.
pub fn children(&self) -> LayoutNodeChildrenIterator<'ln> {
LayoutNodeChildrenIterator {
current_node: self.first_child(),
}
}
pub unsafe fn get_jsmanaged<'a>(&'a self) -> &'a JS<Node> {
&self.node
}
}
impl<'ln> TNode<LayoutElement<'ln>> for LayoutNode<'ln> {
fn parent_node(&self) -> Option<LayoutNode<'ln>> {
unsafe {
self.node.parent_node_ref().map(|node| self.new_with_this_lifetime(&node))
}
}
fn prev_sibling(&self) -> Option<LayoutNode<'ln>> {
unsafe {
self.node.prev_sibling_ref().map(|node| self.new_with_this_lifetime(&node))
}
}
fn next_sibling(&self) -> Option<LayoutNode<'ln>> {
unsafe {
self.node.next_sibling_ref().map(|node| self.new_with_this_lifetime(&node))
}
}
/// If this is an element, accesses the element data. Fails if this is not an element node.
#[inline]
fn as_element(&self) -> LayoutElement<'ln> {
unsafe {
assert!(self.node.is_element_for_layout());
let elem: JS<Element> = self.node.transmute_copy();
let element = &*elem.unsafe_get();
LayoutElement {
element: mem::transmute(element),
}
}
}
fn is_element(&self) -> bool {
self.node_is_element()
}
fn is_document(&self) -> bool {
self.node_is_document()
}
fn match_attr(&self, attr: &AttrSelector, test: |&str| -> bool) -> bool {
assert!(self.is_element())
let name = if self.is_html_element_in_html_document() {
attr.lower_name.as_slice()
} else {
attr.name.as_slice()
};
match attr.namespace {
SpecificNamespace(ref ns) => {
let element = self.as_element();
element.get_attr(ns, name)
.map_or(false, |attr| test(attr))
},
// FIXME: https://github.com/mozilla/servo/issues/1558
AnyNamespace => false,
}
}
fn is_html_element_in_html_document(&self) -> bool {
unsafe {
self.is_element() && {
let element: JS<Element> = self.node.transmute_copy();
element.html_element_in_html_document_for_layout()
}
}
}
}
pub struct LayoutNodeChildrenIterator<'a> {
current_node: Option<LayoutNode<'a>>,
}
impl<'a> Iterator<LayoutNode<'a>> for LayoutNodeChildrenIterator<'a> {
fn next(&mut self) -> Option<LayoutNode<'a>> {
let node = self.current_node.clone();
self.current_node = node.clone().and_then(|node| {
node.next_sibling()
});
node
}
}
// FIXME: Do this without precomputing a vector of refs.
// Easy for preorder; harder for postorder.
//
// FIXME(pcwalton): Parallelism! Eventually this should just be nuked.
pub struct LayoutTreeIterator<'a> {
nodes: Vec<LayoutNode<'a>>,
index: uint,
}
impl<'a> LayoutTreeIterator<'a> {
fn new(nodes: Vec<LayoutNode<'a>>) -> LayoutTreeIterator<'a> {
LayoutTreeIterator {
nodes: nodes,
index: 0,
}
}
}
impl<'a> Iterator<LayoutNode<'a>> for LayoutTreeIterator<'a> {
fn next(&mut self) -> Option<LayoutNode<'a>> {
if self.index >= self.nodes.len() {
None
} else {
let v = self.nodes[self.index].clone();
self.index += 1;
Some(v)
}
}
}
/// FIXME(pcwalton): This is super inefficient.
fn gather_layout_nodes<'a>(cur: &LayoutNode<'a>, refs: &mut Vec<LayoutNode<'a>>, postorder: bool) {
if !postorder {
refs.push(cur.clone());
}
for kid in cur.children() {
gather_layout_nodes(&kid, refs, postorder)
}
if postorder {
refs.push(cur.clone());
}
}
/// A wrapper around elements that ensures layout can only ever access safe properties.
pub struct LayoutElement<'le> {
element: &'le Element,
}
impl<'le> LayoutElement<'le> {
pub fn style_attribute(&self) -> &'le Option<PropertyDeclarationBlock> {
let style: &Option<PropertyDeclarationBlock> = unsafe {
let style: &RefCell<Option<PropertyDeclarationBlock>> = self.element.style_attribute.deref();
// cast to the direct reference to T placed on the head of RefCell<T>
mem::transmute(style)
};
style
}
}
impl<'le> TElement for LayoutElement<'le> {
#[inline]
fn get_local_name<'a>(&'a self) -> &'a Atom {
&self.element.local_name
}
#[inline]
fn get_namespace<'a>(&'a self) -> &'a Namespace {
&self.element.namespace
}
#[inline]
fn get_attr(&self, namespace: &Namespace, name: &str) -> Option<&'static str> {
unsafe { self.element.get_attr_val_for_layout(namespace, name) }
}
fn get_link(&self) -> Option<&'static str> {
// FIXME: This is HTML only.
match self.element.node.type_id_for_layout() {
// http://www.whatwg.org/specs/web-apps/current-work/multipage/selectors.html#
// selector-link
ElementNodeTypeId(HTMLAnchorElementTypeId) |
ElementNodeTypeId(HTMLAreaElementTypeId) |
ElementNodeTypeId(HTMLLinkElementTypeId) => {
unsafe { self.element.get_attr_val_for_layout(&namespace::Null, "href") }
}
_ => None,
}
}
fn get_hover_state(&self) -> bool {
unsafe {
self.element.node.get_hover_state_for_layout()
}
}
#[inline]
fn get_id(&self) -> Option<Atom> {
unsafe { self.element.get_attr_atom_for_layout(&namespace::Null, "id") }
}
fn get_disabled_state(&self) -> bool {
unsafe {
self.element.node.get_disabled_state_for_layout()
}
}
fn get_enabled_state(&self) -> bool {
unsafe {
self.element.node.get_enabled_state_for_layout()
}
}
}
fn get_content(content_list: &content::T) -> String {
match *content_list {
content::Content(ref value) => {
let iter = &mut value.clone().move_iter().peekable();
match iter.next() {
Some(content::StringContent(content)) => content,
_ => "".to_string(),
}
}
_ => "".to_string(),
}
}
#[deriving(PartialEq, Clone)]
pub enum PseudoElementType {
Normal,
Before,
After,
BeforeBlock,
AfterBlock,
}
/// A thread-safe version of `LayoutNode`, used during flow construction. This type of layout
/// node does not allow any parents or siblings of nodes to be accessed, to avoid races.
#[deriving(Clone)]
pub struct ThreadSafeLayoutNode<'ln> {
/// The wrapped node.
node: LayoutNode<'ln>,
pseudo: PseudoElementType,
}
impl<'ln> TLayoutNode for ThreadSafeLayoutNode<'ln> {
/// Creates a new layout node with the same lifetime as this layout node.
unsafe fn new_with_this_lifetime(&self, node: &JS<Node>) -> ThreadSafeLayoutNode<'ln> {
ThreadSafeLayoutNode {
node: LayoutNode {
node: node.transmute_copy(),
chain: self.node.chain,
},
pseudo: Normal,
}
}
/// Returns `None` if this is a pseudo-element.
fn type_id(&self) -> Option<NodeTypeId> {
if self.pseudo == Before || self.pseudo == After {
return None
}
self.node.type_id()
}
unsafe fn get_jsmanaged<'a>(&'a self) -> &'a JS<Node> {
self.node.get_jsmanaged()
}
unsafe fn get<'a>(&'a self) -> &'a Node { // this change.
mem::transmute::<*mut Node,&'a Node>(self.get_jsmanaged().unsafe_get())
}
fn first_child(&self) -> Option<ThreadSafeLayoutNode<'ln>> {
if self.pseudo == Before || self.pseudo == After {
return None
}
if self.has_before_pseudo() {
if self.is_block(Before) && self.pseudo == Normal {
let pseudo_before_node = self.with_pseudo(BeforeBlock);
return Some(pseudo_before_node)
} else if self.pseudo == Normal || self.pseudo == BeforeBlock {
let pseudo_before_node = self.with_pseudo(Before);
return Some(pseudo_before_node)
}
}
unsafe {
self.get_jsmanaged().first_child_ref().map(|node| self.new_with_this_lifetime(&node))
}
}
fn text(&self) -> String {
if self.pseudo == Before || self.pseudo == After {
let layout_data_ref = self.borrow_layout_data();
let node_layout_data_wrapper = layout_data_ref.get_ref();
if self.pseudo == Before {
let before_style = node_layout_data_wrapper.data.before_style.get_ref();
return get_content(&before_style.get_box().content)
} else {
let after_style = node_layout_data_wrapper.data.after_style.get_ref();
return get_content(&after_style.get_box().content)
}
}
unsafe {
if !self.get().is_text() {
fail!("not text!")
}
let text: JS<Text> = self.get_jsmanaged().transmute_copy();
(*text.unsafe_get()).characterdata.data.deref().borrow().clone()
}
}
}
impl<'ln> ThreadSafeLayoutNode<'ln> {
/// Creates a new `ThreadSafeLayoutNode` from the given `LayoutNode`.
pub fn new<'a>(node: &LayoutNode<'a>) -> ThreadSafeLayoutNode<'a> {
ThreadSafeLayoutNode {
node: node.clone(),
pseudo: Normal,
}
}
/// Creates a new `ThreadSafeLayoutNode` for the same `LayoutNode`
/// with a different pseudo-element type.
fn with_pseudo(&self, pseudo: PseudoElementType) -> ThreadSafeLayoutNode<'ln> {
ThreadSafeLayoutNode {
node: self.node.clone(),
pseudo: pseudo,
}
}
/// Returns the next sibling of this node. Unsafe and private because this can lead to races.
unsafe fn next_sibling(&self) -> Option<ThreadSafeLayoutNode<'ln>> {
if self.pseudo == Before || self.pseudo == BeforeBlock {
return self.get_jsmanaged().first_child_ref().map(|node| self.new_with_this_lifetime(&node))
}
self.get_jsmanaged().next_sibling_ref().map(|node| self.new_with_this_lifetime(&node))
}
/// Returns an iterator over this node's children.
pub fn children(&self) -> ThreadSafeLayoutNodeChildrenIterator<'ln> {
ThreadSafeLayoutNodeChildrenIterator {
current_node: self.first_child(),
parent_node: Some(self.clone()),
}
}
/// If this is an element, accesses the element data. Fails if this is not an element node.
#[inline]
pub fn as_element(&self) -> ThreadSafeLayoutElement {
unsafe {
assert!(self.get_jsmanaged().is_element_for_layout());
let elem: JS<Element> = self.get_jsmanaged().transmute_copy();
let element = elem.unsafe_get();
// FIXME(pcwalton): Workaround until Rust gets multiple lifetime parameters on
// implementations.
ThreadSafeLayoutElement {
element: &mut *element,
}
}
}
pub fn get_pseudo_element_type(&self) -> PseudoElementType {
self.pseudo
}
pub fn is_block(&self, kind: PseudoElementType) -> bool {
let mut layout_data_ref = self.mutate_layout_data();
let node_layout_data_wrapper = layout_data_ref.get_mut_ref();
let display = match kind {
Before | BeforeBlock => {
let before_style = node_layout_data_wrapper.data.before_style.get_ref();
before_style.get_box().display
}
After | AfterBlock => {
let after_style = node_layout_data_wrapper.data.after_style.get_ref();
after_style.get_box().display
}
Normal => {
let after_style = node_layout_data_wrapper.shared_data.style.get_ref();
after_style.get_box().display
}
};
display == display::block
}
pub fn has_before_pseudo(&self) -> bool {
let layout_data_wrapper = self.borrow_layout_data();
let layout_data_wrapper_ref = layout_data_wrapper.get_ref();
layout_data_wrapper_ref.data.before_style.is_some()
}
pub fn has_after_pseudo(&self) -> bool {
let layout_data_wrapper = self.borrow_layout_data();
let layout_data_wrapper_ref = layout_data_wrapper.get_ref();
layout_data_wrapper_ref.data.after_style.is_some()
}
/// Borrows the layout data immutably. Fails on a conflicting borrow.
#[inline(always)]
pub fn borrow_layout_data<'a>(&'a self) -> Ref<'a,Option<LayoutDataWrapper>> {
unsafe {
mem::transmute(self.get().layout_data.borrow())
}
}
/// Borrows the layout data mutably. Fails on a conflicting borrow.
#[inline(always)]
pub fn mutate_layout_data<'a>(&'a self) -> RefMut<'a,Option<LayoutDataWrapper>> {
unsafe {
mem::transmute(self.get().layout_data.borrow_mut())
}
}
/// Traverses the tree in postorder.
///
/// TODO(pcwalton): Offer a parallel version with a compatible API.
pub fn traverse_postorder_mut<T:PostorderNodeMutTraversal>(&mut self, traversal: &mut T)
-> bool {
if traversal.should_prune(self) {
return true
}
let mut opt_kid = self.first_child();
loop {
match opt_kid {
None => break,
Some(mut kid) => {
if !kid.traverse_postorder_mut(traversal) {
return false
}
unsafe {
opt_kid = kid.next_sibling()
}
}
}
}
traversal.process(self)
}
pub fn is_ignorable_whitespace(&self) -> bool {
match self.type_id() {
Some(TextNodeTypeId) => {
unsafe {
let text: JS<Text> = self.get_jsmanaged().transmute_copy();
if !is_whitespace((*text.unsafe_get()).characterdata.data.deref().borrow().as_slice()) {
return false
}
// NB: See the rules for `white-space` here:
//
// http://www.w3.org/TR/CSS21/text.html#propdef-white-space
//
// If you implement other values for this property, you will almost certainly
// want to update this check.
match self.style().get_inheritedtext().white_space {
white_space::normal => true,
_ => false,
}
}
}
_ => false
}
}
}
pub struct ThreadSafeLayoutNodeChildrenIterator<'a> {
current_node: Option<ThreadSafeLayoutNode<'a>>,
parent_node: Option<ThreadSafeLayoutNode<'a>>,
}
impl<'a> Iterator<ThreadSafeLayoutNode<'a>> for ThreadSafeLayoutNodeChildrenIterator<'a> {
fn next(&mut self) -> Option<ThreadSafeLayoutNode<'a>> {
let node = self.current_node.clone();
match node {
Some(ref node) => {
if node.pseudo == After || node.pseudo == AfterBlock {
return None
}
match self.parent_node {
Some(ref parent_node) => {
if parent_node.pseudo == Normal {
self.current_node = self.current_node.clone().and_then(|node| {
unsafe {
node.next_sibling()
}
});
} else {
self.current_node = None;
}
}
None => {}
}
}
None => {
match self.parent_node {
Some(ref parent_node) => {
if parent_node.has_after_pseudo() {
let pseudo_after_node = if parent_node.is_block(After) && parent_node.pseudo == Normal {
let pseudo_after_node = parent_node.with_pseudo(AfterBlock);
Some(pseudo_after_node)
} else if parent_node.pseudo == Normal || parent_node.pseudo == AfterBlock {
let pseudo_after_node = parent_node.with_pseudo(After);
Some(pseudo_after_node)
} else {
None
};
self.current_node = pseudo_after_node;
return self.current_node.clone()
}
}
None => {}
}
}
}
node
}
}
/// A wrapper around elements that ensures layout can only ever access safe properties and cannot
/// race on elements.
pub struct ThreadSafeLayoutElement<'le> {
element: &'le Element,
}
impl<'le> ThreadSafeLayoutElement<'le> {
#[inline]
pub fn get_attr(&self, namespace: &Namespace, name: &str) -> Option<&'static str> {
unsafe { self.element.get_attr_val_for_layout(namespace, name) }
}
}
/// A bottom-up, parallelizable traversal.
pub trait PostorderNodeMutTraversal {
/// The operation to perform. Return true to continue or false to stop.
fn process<'a>(&'a mut self, node: &ThreadSafeLayoutNode<'a>) -> bool;
/// Returns true if this node should be pruned. If this returns true, we skip the operation
/// entirely and do not process any descendant nodes. This is called *before* child nodes are
/// visited. The default implementation never prunes any nodes.
fn should_prune<'a>(&'a self, _node: &ThreadSafeLayoutNode<'a>) -> bool {
false
}
}
/// Opaque type stored in type-unsafe work queues for parallel layout.
/// Must be transmutable to and from LayoutNode/ThreadSafeLayoutNode.
pub type UnsafeLayoutNode = (uint, uint);
pub fn layout_node_to_unsafe_layout_node(node: &LayoutNode) -> UnsafeLayoutNode {
unsafe {
let ptr: uint = mem::transmute_copy(node);
(ptr, 0)
}
}
// FIXME(#3044): This should be updated to use a real lifetime instead of
// faking one.
pub unsafe fn layout_node_from_unsafe_layout_node(node: &UnsafeLayoutNode) -> LayoutNode<'static> {
let (node, _) = *node;
mem::transmute(node)
}

View file

@ -0,0 +1,23 @@
[package]
name = "layout_traits"
version = "0.0.1"
authors = ["The Servo Project Developers"]
[lib]
name = "layout_traits"
path = "lib.rs"
[dependencies.gfx]
path = "../gfx"
[dependencies.script_traits]
path = "../script_traits"
[dependencies.msg]
path = "../msg"
[dependencies.net]
path = "../net"
[dependencies.util]
path = "../util"

View file

@ -0,0 +1,54 @@
/* 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"]
extern crate gfx;
extern crate script_traits;
extern crate servo_msg = "msg";
extern crate servo_net = "net";
extern crate servo_util = "util";
// This module contains traits in layout used generically
// in the rest of Servo.
// The traits are here instead of in layout so
// that these modules won't have to depend on layout.
use gfx::font_cache_task::FontCacheTask;
use gfx::render_task::RenderChan;
use servo_msg::constellation_msg::{ConstellationChan, PipelineId};
use servo_msg::constellation_msg::Failure;
use servo_net::image_cache_task::ImageCacheTask;
use servo_util::opts::Opts;
use servo_util::time::TimeProfilerChan;
use script_traits::{ScriptControlChan, OpaqueScriptLayoutChannel};
use std::comm::Sender;
/// Messages sent to the layout task from the constellation
pub enum LayoutControlMsg {
ExitNowMsg,
}
/// A channel wrapper for constellation messages
pub struct LayoutControlChan(pub Sender<LayoutControlMsg>);
// A static method creating a layout task
// Here to remove the compositor -> layout dependency
pub trait LayoutTaskFactory {
// FIXME: use a proper static method
fn create(_phantom: Option<&mut Self>,
id: PipelineId,
chan: OpaqueScriptLayoutChannel,
pipeline_port: Receiver<LayoutControlMsg>,
constellation_chan: ConstellationChan,
failure_msg: Failure,
script_chan: ScriptControlChan,
render_chan: RenderChan,
img_cache_task: ImageCacheTask,
font_cache_task: FontCacheTask,
opts: Opts,
time_profiler_chan: TimeProfilerChan,
shutdown_chan: Sender<()>);
}

View file

@ -0,0 +1,9 @@
[package]
name = "macros"
version = "0.0.1"
authors = ["The Servo Project Developers"]
[lib]
name = "macros"
path = "lib.rs"
plugin = true

93
components/macros/lib.rs Normal file
View file

@ -0,0 +1,93 @@
/* 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/. */
#![feature(macro_rules, plugin_registrar, quote, phase)]
//! Exports macros for use in other Servo crates.
#[cfg(test)]
extern crate sync;
#[macro_export]
macro_rules! bitfield(
($bitfieldname:ident, $getter:ident, $setter:ident, $value:expr) => (
impl $bitfieldname {
#[inline]
pub fn $getter(self) -> bool {
let $bitfieldname(this) = self;
(this & $value) != 0
}
#[inline]
pub fn $setter(&mut self, value: bool) {
let $bitfieldname(this) = *self;
*self = $bitfieldname((this & !$value) | (if value { $value } else { 0 }))
}
}
)
)
#[macro_export]
macro_rules! lazy_init(
($(static ref $N:ident : $T:ty = $e:expr;)*) => (
$(
#[allow(non_camel_case_types)]
struct $N {__unit__: ()}
static $N: $N = $N {__unit__: ()};
impl Deref<$T> for $N {
fn deref<'a>(&'a self) -> &'a $T {
unsafe {
static mut s: *const $T = 0 as *const $T;
static mut ONCE: ::sync::one::Once = ::sync::one::ONCE_INIT;
ONCE.doit(|| {
s = ::std::mem::transmute::<Box<$T>, *const $T>(box () ($e));
});
&*s
}
}
}
)*
)
)
#[cfg(test)]
mod tests {
use std::collections::hashmap::HashMap;
lazy_init! {
static ref NUMBER: uint = times_two(3);
static ref VEC: [Box<uint>, ..3] = [box 1, box 2, box 3];
static ref OWNED_STRING: String = "hello".to_string();
static ref HASHMAP: HashMap<uint, &'static str> = {
let mut m = HashMap::new();
m.insert(0u, "abc");
m.insert(1, "def");
m.insert(2, "ghi");
m
};
}
fn times_two(n: uint) -> uint {
n * 2
}
#[test]
fn test_basic() {
assert_eq!(*OWNED_STRING, "hello".to_string());
assert_eq!(*NUMBER, 6);
assert!(HASHMAP.find(&1).is_some());
assert!(HASHMAP.find(&3).is_none());
assert_eq!(VEC.as_slice(), &[box 1, box 2, box 3]);
}
#[test]
fn test_repeat() {
assert_eq!(*NUMBER, 6);
assert_eq!(*NUMBER, 6);
assert_eq!(*NUMBER, 6);
}
}

30
components/msg/Cargo.toml Normal file
View file

@ -0,0 +1,30 @@
[package]
name = "msg"
version = "0.0.1"
authors = ["The Servo Project Developers"]
[lib]
name = "msg"
path = "lib.rs"
[dependencies.util]
path = "../util"
[dependencies.azure]
git = "http://github.com/servo/rust-azure"
[dependencies.geom]
git = "http://github.com/servo/rust-geom"
[dependencies.layers]
git = "http://github.com/servo/rust-layers"
[dependencies.core_foundation]
git = "http://github.com/servo/rust-core-foundation"
[dependencies.io_surface]
git = "http://github.com/servo/rust-io-surface"
[dependencies.url]
git = "http://github.com/servo/rust-url"

View file

@ -0,0 +1,123 @@
/* 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 azure::azure_hl::Color;
use geom::point::Point2D;
use geom::rect::Rect;
use layers::platform::surface::NativeGraphicsMetadata;
use layers::layers::LayerBufferSet;
use serialize::{Encoder, Encodable};
use std::fmt::{Formatter, Show};
use std::fmt;
use constellation_msg::PipelineId;
/// The status of the renderer.
#[deriving(PartialEq, Clone)]
pub enum RenderState {
IdleRenderState,
RenderingRenderState,
}
#[deriving(PartialEq, Clone)]
pub enum ReadyState {
/// Informs the compositor that nothing has been done yet. Used for setting status
Blank,
/// Informs the compositor that a page is loading. Used for setting status
Loading,
/// Informs the compositor that a page is performing layout. Used for setting status
PerformingLayout,
/// Informs the compositor that a page is finished loading. Used for setting status
FinishedLoading,
}
/// A newtype struct for denoting the age of messages; prevents race conditions.
#[deriving(PartialEq)]
pub struct Epoch(pub uint);
impl Epoch {
pub fn next(&mut self) {
let Epoch(ref mut u) = *self;
*u += 1;
}
}
#[deriving(Clone, PartialEq)]
pub struct LayerId(pub uint, pub uint);
impl Show for LayerId {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
let LayerId(a, b) = *self;
write!(f, "Layer({}, {})", a, b)
}
}
impl LayerId {
/// FIXME(#2011, pcwalton): This is unfortunate. Maybe remove this in the future.
pub fn null() -> LayerId {
LayerId(0, 0)
}
}
/// The scrolling policy of a layer.
#[deriving(PartialEq)]
pub enum ScrollPolicy {
/// These layers scroll when the parent receives a scrolling message.
Scrollable,
/// These layers do not scroll when the parent receives a scrolling message.
FixedPosition,
}
/// All layer-specific information that the painting task sends to the compositor other than the
/// buffer contents of the layer itself.
pub struct LayerMetadata {
/// An opaque ID. This is usually the address of the flow and index of the box within it.
pub id: LayerId,
/// The position and size of the layer in pixels.
pub position: Rect<uint>,
/// The background color of the layer.
pub background_color: Color,
/// The scrolling policy of this layer.
pub scroll_policy: ScrollPolicy,
}
/// 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>;
/// 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,
pipeline_id: PipelineId,
metadata: Vec<LayerMetadata>,
epoch: Epoch);
/// Sends new tiles for the given layer to the compositor.
fn paint(&self,
pipeline_id: PipelineId,
epoch: Epoch,
replies: Vec<(LayerId, Box<LayerBufferSet>)>);
fn render_msg_discarded(&self);
fn set_render_state(&self, render_state: 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, ReadyState);
fn scroll_fragment_point(&self,
pipeline_id: PipelineId,
layer_id: LayerId,
point: Point2D<f32>);
fn close(&self);
fn dup(&self) -> Box<ScriptListener>;
}
impl<E, S: Encoder<E>> Encodable<S, E> for Box<ScriptListener> {
fn encode(&self, _s: &mut S) -> Result<(), E> {
Ok(())
}
}

View file

@ -0,0 +1,84 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
//! The 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;
use geom::scale_factor::ScaleFactor;
use layers::geometry::DevicePixel;
use serialize::Encodable;
use servo_util::geometry::{PagePx, ViewportPx};
use std::comm::{channel, Sender, Receiver};
use url::Url;
#[deriving(Clone)]
pub struct ConstellationChan(pub Sender<Msg>);
impl ConstellationChan {
pub fn new() -> (Receiver<Msg>, ConstellationChan) {
let (chan, port) = channel();
(port, ConstellationChan(chan))
}
}
#[deriving(PartialEq)]
pub enum IFrameSandboxState {
IFrameSandboxed,
IFrameUnsandboxed
}
// We pass this info to various tasks, so it lives in a separate, cloneable struct.
#[deriving(Clone)]
pub struct Failure {
pub pipeline_id: PipelineId,
pub subpage_id: Option<SubpageId>,
}
#[deriving(Encodable)]
pub struct WindowSizeData {
/// The size of the initial layout viewport, before parsing an
/// http://www.w3.org/TR/css-device-adapt/#initial-viewport
pub initial_viewport: TypedSize2D<ViewportPx, f32>,
/// The "viewing area" in page px. See `PagePx` documentation for details.
pub visible_viewport: TypedSize2D<PagePx, f32>,
/// The resolution of the window in dppx, not including any "pinch zoom" factor.
pub device_pixel_ratio: ScaleFactor<ViewportPx, DevicePixel, f32>,
}
/// Messages from the compositor and script to the constellation.
pub enum Msg {
ExitMsg,
FailureMsg(Failure),
InitLoadUrlMsg(Url),
LoadCompleteMsg(PipelineId, Url),
FrameRectMsg(PipelineId, SubpageId, Rect<f32>),
LoadUrlMsg(PipelineId, Url),
LoadIframeUrlMsg(Url, PipelineId, SubpageId, IFrameSandboxState),
NavigateMsg(NavigationDirection),
RendererReadyMsg(PipelineId),
ResizedWindowMsg(WindowSizeData),
}
/// Represents the two different ways to which a page can be navigated
#[deriving(Clone, PartialEq, Hash)]
pub enum NavigationType {
Load, // entered or clicked on a url
Navigate, // browser forward/back buttons
}
#[deriving(Clone, PartialEq, Hash)]
pub enum NavigationDirection {
Forward,
Back,
}
#[deriving(Clone, PartialEq, Eq, Hash, Encodable)]
pub struct PipelineId(pub uint);
#[deriving(Clone, PartialEq, Eq, Hash, Encodable)]
pub struct SubpageId(pub uint);

43
components/msg/lib.rs Normal file
View file

@ -0,0 +1,43 @@
/* 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/. */
extern crate azure;
extern crate geom;
extern crate layers;
extern crate serialize;
extern crate servo_util = "util";
extern crate std;
extern crate url;
#[cfg(target_os="macos")]
extern crate core_foundation;
#[cfg(target_os="macos")]
extern crate io_surface;
pub mod compositor_msg;
pub mod constellation_msg;
pub mod platform {
#[cfg(target_os="macos")]
pub mod macos {
#[cfg(target_os="macos")]
pub mod surface;
}
#[cfg(target_os="linux")]
pub mod linux {
#[cfg(target_os="linux")]
pub mod surface;
}
#[cfg(target_os="android")]
pub mod android {
#[cfg(target_os="android")]
pub mod surface;
}
pub mod surface;
}

View file

@ -0,0 +1,20 @@
/* 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/. */
//! EGL-specific implementation of cross-process surfaces. This uses EGL surfaces.
use platform::surface::NativeSurfaceAzureMethods;
use azure::AzSkiaGrGLSharedSurfaceRef;
use layers::platform::surface::NativeSurface;
use std::mem;
impl NativeSurfaceAzureMethods for NativeSurface {
fn from_azure_surface(surface: AzSkiaGrGLSharedSurfaceRef) -> NativeSurface {
unsafe {
NativeSurface::from_image_khr(mem::transmute(surface))
}
}
}

View file

@ -0,0 +1,20 @@
/* 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/. */
//! X11-specific implementation of cross-process surfaces. This uses X pixmaps.
use platform::surface::NativeSurfaceAzureMethods;
use azure::AzSkiaGrGLSharedSurfaceRef;
use layers::platform::surface::NativeSurface;
use std::mem;
impl NativeSurfaceAzureMethods for NativeSurface {
fn from_azure_surface(surface: AzSkiaGrGLSharedSurfaceRef) -> NativeSurface {
unsafe {
NativeSurface::from_pixmap(mem::transmute(surface))
}
}
}

View file

@ -0,0 +1,25 @@
/* 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/. */
//! Mac OS-specific implementation of cross-process surfaces. This uses `IOSurface`, introduced
//! in Mac OS X 10.6 Snow Leopard.
use platform::surface::NativeSurfaceAzureMethods;
use azure::AzSkiaGrGLSharedSurfaceRef;
use io_surface::IOSurface;
use layers::platform::surface::NativeSurface;
use std::mem;
impl NativeSurfaceAzureMethods for NativeSurface {
fn from_azure_surface(surface: AzSkiaGrGLSharedSurfaceRef) -> NativeSurface {
unsafe {
let io_surface = IOSurface {
obj: mem::transmute(surface),
};
NativeSurface::from_io_surface(io_surface)
}
}
}

View file

@ -0,0 +1,12 @@
/* 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/. */
//! Declarations of types for cross-process surfaces.
use azure::AzSkiaGrGLSharedSurfaceRef;
pub trait NativeSurfaceAzureMethods {
fn from_azure_surface(surface: AzSkiaGrGLSharedSurfaceRef) -> Self;
}

27
components/net/Cargo.toml Normal file
View file

@ -0,0 +1,27 @@
[package]
name = "net"
version = "0.0.1"
authors = ["The Servo Project Developers"]
[lib]
name = "net"
path = "lib.rs"
[dependencies.util]
path = "../util"
[dependencies.geom]
git = "https://github.com/servo/rust-geom"
[dependencies.http]
git = "https://github.com/servo/rust-http"
branch = "servo"
[dependencies.png]
git = "https://github.com/servo/rust-png"
[dependencies.stb_image]
git = "https://github.com/servo/rust-stb-image"
[dependencies.url]
git = "https://github.com/servo/rust-url"

View file

@ -0,0 +1,154 @@
/* 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 std::str;
use resource_task::{Done, Payload, Metadata, LoadData, LoadResponse, LoaderTask, start_sending};
use serialize::base64::FromBase64;
use http::headers::test_utils::from_stream_with_str;
use http::headers::content_type::MediaType;
use url::{percent_decode, NonRelativeSchemeData};
pub fn factory() -> LoaderTask {
proc(url, start_chan) {
// NB: we don't spawn a new task.
// Hypothesis: data URLs are too small for parallel base64 etc. to be worth it.
// Should be tested at some point.
load(url, start_chan)
}
}
fn load(load_data: LoadData, start_chan: Sender<LoadResponse>) {
let url = load_data.url;
assert!("data" == url.scheme.as_slice());
let mut metadata = Metadata::default(url.clone());
// Split out content type and data.
let mut scheme_data = match url.scheme_data {
NonRelativeSchemeData(scheme_data) => scheme_data,
_ => fail!("Expected a non-relative scheme URL.")
};
match url.query {
Some(query) => {
scheme_data.push_str("?");
scheme_data.push_str(query.as_slice());
},
None => ()
}
let parts: Vec<&str> = scheme_data.as_slice().splitn(',', 1).collect();
if parts.len() != 2 {
start_sending(start_chan, metadata).send(Done(Err("invalid data uri".to_string())));
return;
}
// ";base64" must come at the end of the content type, per RFC 2397.
// rust-http will fail to parse it because there's no =value part.
let mut is_base64 = false;
let mut ct_str = parts[0];
if ct_str.ends_with(";base64") {
is_base64 = true;
ct_str = ct_str.slice_to(ct_str.as_bytes().len() - 7);
}
// Parse the content type using rust-http.
// FIXME: this can go into an infinite loop! (rust-http #25)
let content_type: Option<MediaType> = from_stream_with_str(ct_str);
metadata.set_content_type(&content_type);
let progress_chan = start_sending(start_chan, metadata);
let bytes = percent_decode(parts[1].as_bytes());
if is_base64 {
// FIXME(#2909): Its unclear what to do with non-alphabet characters,
// but Acid 3 apparently depends on spaces being ignored.
let bytes = bytes.move_iter().filter(|&b| b != ' ' as u8).collect::<Vec<u8>>();
// FIXME(#2877): use bytes.as_slice().from_base64() when we upgrade to a Rust version
// that includes https://github.com/rust-lang/rust/pull/15810
let fake_utf8 = unsafe { str::raw::from_utf8(bytes.as_slice()) };
match fake_utf8.from_base64() {
Err(..) => {
progress_chan.send(Done(Err("non-base64 data uri".to_string())));
}
Ok(data) => {
progress_chan.send(Payload(data));
progress_chan.send(Done(Ok(())));
}
}
} else {
progress_chan.send(Payload(bytes));
progress_chan.send(Done(Ok(())));
}
}
#[cfg(test)]
fn assert_parse(url: &'static str,
content_type: Option<(String, String)>,
charset: Option<String>,
data: Option<Vec<u8>>) {
use std::comm;
use url::Url;
let (start_chan, start_port) = comm::channel();
load(LoadData::new(Url::parse(url).unwrap()), start_chan);
let response = start_port.recv();
assert_eq!(&response.metadata.content_type, &content_type);
assert_eq!(&response.metadata.charset, &charset);
let progress = response.progress_port.recv();
match data {
None => {
assert_eq!(progress, Done(Err("invalid data uri".to_string())));
}
Some(dat) => {
assert_eq!(progress, Payload(dat));
assert_eq!(response.progress_port.recv(), Done(Ok(())));
}
}
}
#[test]
fn empty_invalid() {
assert_parse("data:", None, None, None);
}
#[test]
fn plain() {
assert_parse("data:,hello%20world", None, None, Some(b"hello world".iter().map(|&x| x).collect()));
}
#[test]
fn plain_ct() {
assert_parse("data:text/plain,hello",
Some(("text".to_string(), "plain".to_string())), None, Some(b"hello".iter().map(|&x| x).collect()));
}
#[test]
fn plain_charset() {
assert_parse("data:text/plain;charset=latin1,hello",
Some(("text".to_string(), "plain".to_string())), Some("latin1".to_string()), Some(b"hello".iter().map(|&x| x).collect()));
}
#[test]
fn base64() {
assert_parse("data:;base64,C62+7w==", None, None, Some(vec!(0x0B, 0xAD, 0xBE, 0xEF)));
}
#[test]
fn base64_ct() {
assert_parse("data:application/octet-stream;base64,C62+7w==",
Some(("application".to_string(), "octet-stream".to_string())), None, Some(vec!(0x0B, 0xAD, 0xBE, 0xEF)));
}
#[test]
fn base64_charset() {
assert_parse("data:text/plain;charset=koi8-r;base64,8PLl9+XkIO3l5Pfl5A==",
Some(("text".to_string(), "plain".to_string())), Some("koi8-r".to_string()),
Some(vec!(0xF0, 0xF2, 0xE5, 0xF7, 0xE5, 0xE4, 0x20, 0xED, 0xE5, 0xE4, 0xF7, 0xE5, 0xE4)));
}

View file

@ -0,0 +1,316 @@
/* 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/. */
//! An implementation of the [CORS preflight cache](http://fetch.spec.whatwg.org/#cors-preflight-cache)
//! For now this library is XHR-specific.
//! For stuff involving `<img>`, `<iframe>`, `<form>`, etc please check what
//! the request mode should be and compare with the fetch spec
//! This library will eventually become the core of the Fetch crate
//! with CORSRequest being expanded into FetchRequest (etc)
use http::method::Method;
use std::ascii::StrAsciiExt;
use std::comm::{Sender, Receiver, channel};
use time;
use time::{now, Timespec};
use url::Url;
/// Union type for CORS cache entries
///
/// Each entry might pertain to a header or method
#[deriving(Clone)]
pub enum HeaderOrMethod {
HeaderData(String),
MethodData(Method)
}
impl HeaderOrMethod {
fn match_header(&self, header_name: &str) -> bool {
match *self {
HeaderData(ref s) => s.as_slice().eq_ignore_ascii_case(header_name),
_ => false
}
}
fn match_method(&self, method: &Method) -> bool {
match *self {
MethodData(ref m) => m == method,
_ => false
}
}
}
/// An entry in the CORS cache
#[deriving(Clone)]
pub struct CORSCacheEntry {
pub origin: Url,
pub url: Url,
pub max_age: uint,
pub credentials: bool,
pub header_or_method: HeaderOrMethod,
created: Timespec
}
impl CORSCacheEntry {
fn new (origin:Url, url: Url, max_age: uint, credentials: bool, header_or_method: HeaderOrMethod) -> CORSCacheEntry {
CORSCacheEntry {
origin: origin,
url: url,
max_age: max_age,
credentials: credentials,
header_or_method: header_or_method,
created: time::now().to_timespec()
}
}
}
/// Properties of Request required to cache match.
pub struct CacheRequestDetails {
pub origin: Url,
pub destination: Url,
pub credentials: bool
}
/// Trait for a generic CORS Cache
pub trait CORSCache {
/// [Clear the cache](http://fetch.spec.whatwg.org/#concept-cache-clear)
fn clear (&mut self, request: CacheRequestDetails);
/// Remove old entries
fn cleanup(&mut self);
/// Returns true if an entry with a [matching header](http://fetch.spec.whatwg.org/#concept-cache-match-header) is found
fn match_header(&mut self, request: CacheRequestDetails, header_name: &str) -> bool;
/// Updates max age if an entry for a [matching header](http://fetch.spec.whatwg.org/#concept-cache-match-header) is found.
///
/// If not, it will insert an equivalent entry
fn match_header_and_update(&mut self, request: CacheRequestDetails, header_name: &str, new_max_age: uint) -> bool;
/// Returns true if an entry with a [matching method](http://fetch.spec.whatwg.org/#concept-cache-match-method) is found
fn match_method(&mut self, request: CacheRequestDetails, method: Method) -> bool;
/// Updates max age if an entry for [a matching method](http://fetch.spec.whatwg.org/#concept-cache-match-method) is found.
///
/// If not, it will insert an equivalent entry
fn match_method_and_update(&mut self, request: CacheRequestDetails, method: Method, new_max_age: uint) -> bool;
/// Insert an entry
fn insert(&mut self, entry: CORSCacheEntry);
}
/// A simple, vector-based CORS Cache
#[deriving(Clone)]
#[unstable = "This might later be replaced with a HashMap-like entity, though that requires a separate Origin struct"]
pub struct BasicCORSCache(Vec<CORSCacheEntry>);
impl BasicCORSCache {
fn find_entry_by_header<'a>(&'a mut self, request: &CacheRequestDetails, header_name: &str) -> Option<&'a mut CORSCacheEntry> {
self.cleanup();
let BasicCORSCache(ref mut buf) = *self;
let entry = buf.mut_iter().find(|e| e.origin.scheme == request.origin.scheme &&
e.origin.host() == request.origin.host() &&
e.origin.port() == request.origin.port() &&
e.url == request.destination &&
e.credentials == request.credentials &&
e.header_or_method.match_header(header_name));
entry
}
fn find_entry_by_method<'a>(&'a mut self, request: &CacheRequestDetails, method: Method) -> Option<&'a mut CORSCacheEntry> {
// we can take the method from CORSRequest itself
self.cleanup();
let BasicCORSCache(ref mut buf) = *self;
let entry = buf.mut_iter().find(|e| e.origin.scheme == request.origin.scheme &&
e.origin.host() == request.origin.host() &&
e.origin.port() == request.origin.port() &&
e.url == request.destination &&
e.credentials == request.credentials &&
e.header_or_method.match_method(&method));
entry
}
}
impl CORSCache for BasicCORSCache {
/// http://fetch.spec.whatwg.org/#concept-cache-clear
#[allow(dead_code)]
fn clear (&mut self, request: CacheRequestDetails) {
let BasicCORSCache(buf) = self.clone();
let new_buf: Vec<CORSCacheEntry> = buf.move_iter().filter(|e| e.origin == request.origin && request.destination == e.url).collect();
*self = BasicCORSCache(new_buf);
}
// Remove old entries
fn cleanup(&mut self) {
let BasicCORSCache(buf) = self.clone();
let now = time::now().to_timespec();
let new_buf: Vec<CORSCacheEntry> = buf.move_iter().filter(|e| now.sec > e.created.sec + e.max_age as i64).collect();
*self = BasicCORSCache(new_buf);
}
fn match_header(&mut self, request: CacheRequestDetails, header_name: &str) -> bool {
self.find_entry_by_header(&request, header_name).is_some()
}
fn match_header_and_update(&mut self, request: CacheRequestDetails, header_name: &str, new_max_age: uint) -> bool {
match self.find_entry_by_header(&request, header_name).map(|e| e.max_age = new_max_age) {
Some(_) => true,
None => {
self.insert(CORSCacheEntry::new(request.origin, request.destination, new_max_age,
request.credentials, HeaderData(header_name.to_string())));
false
}
}
}
fn match_method(&mut self, request: CacheRequestDetails, method: Method) -> bool {
self.find_entry_by_method(&request, method).is_some()
}
fn match_method_and_update(&mut self, request: CacheRequestDetails, method: Method, new_max_age: uint) -> bool {
match self.find_entry_by_method(&request, method.clone()).map(|e| e.max_age = new_max_age) {
Some(_) => true,
None => {
self.insert(CORSCacheEntry::new(request.origin, request.destination, new_max_age,
request.credentials, MethodData(method)));
false
}
}
}
fn insert(&mut self, entry: CORSCacheEntry) {
self.cleanup();
let BasicCORSCache(ref mut buf) = *self;
buf.push(entry);
}
}
/// Various messages that can be sent to a CORSCacheTask
pub enum CORSCacheTaskMsg {
Clear(CacheRequestDetails, Sender<()>),
Cleanup(Sender<()>),
MatchHeader(CacheRequestDetails, String, Sender<bool>),
MatchHeaderUpdate(CacheRequestDetails, String, uint, Sender<bool>),
MatchMethod(CacheRequestDetails, Method, Sender<bool>),
MatchMethodUpdate(CacheRequestDetails, Method, uint, Sender<bool>),
Insert(CORSCacheEntry, Sender<()>),
ExitMsg
}
/// A Sender to a CORSCacheTask
///
/// This can be used as a CORS Cache.
/// The methods on this type block until they can run, and it behaves similar to a mutex
pub type CORSCacheSender = Sender<CORSCacheTaskMsg>;
impl CORSCache for CORSCacheSender {
fn clear (&mut self, request: CacheRequestDetails) {
let (tx, rx) = channel();
self.send(Clear(request, tx));
let _ = rx.recv_opt();
}
fn cleanup(&mut self) {
let (tx, rx) = channel();
self.send(Cleanup(tx));
let _ = rx.recv_opt();
}
fn match_header(&mut self, request: CacheRequestDetails, header_name: &str) -> bool {
let (tx, rx) = channel();
self.send(MatchHeader(request, header_name.to_string(), tx));
rx.recv_opt().unwrap_or(false)
}
fn match_header_and_update(&mut self, request: CacheRequestDetails, header_name: &str, new_max_age: uint) -> bool {
let (tx, rx) = channel();
self.send(MatchHeaderUpdate(request, header_name.to_string(), new_max_age, tx));
rx.recv_opt().unwrap_or(false)
}
fn match_method(&mut self, request: CacheRequestDetails, method: Method) -> bool {
let (tx, rx) = channel();
self.send(MatchMethod(request, method, tx));
rx.recv_opt().unwrap_or(false)
}
fn match_method_and_update(&mut self, request: CacheRequestDetails, method: Method, new_max_age: uint) -> bool {
let (tx, rx) = channel();
self.send(MatchMethodUpdate(request, method, new_max_age, tx));
rx.recv_opt().unwrap_or(false)
}
fn insert(&mut self, entry: CORSCacheEntry) {
let (tx, rx) = channel();
self.send(Insert(entry, tx));
let _ = rx.recv_opt();
}
}
/// A simple task-based CORS Cache that can be sent messages
///
/// #Example
/// ```
/// let task = CORSCacheTask::new();
/// let builder = TaskBuilder::new().named("XHRTask");
/// let mut sender = task.get_sender();
/// builder.spawn(proc() { task.run() });
/// sender.insert(CORSCacheEntry::new(/* parameters here */));
/// ```
pub struct CORSCacheTask {
receiver: Receiver<CORSCacheTaskMsg>,
cache: BasicCORSCache,
sender: CORSCacheSender
}
impl CORSCacheTask {
pub fn new() -> CORSCacheTask {
let (tx, rx) = channel();
CORSCacheTask {
receiver: rx,
cache: BasicCORSCache(vec![]),
sender: tx
}
}
/// Provides a sender to the cache task
pub fn get_sender(&self) -> CORSCacheSender {
self.sender.clone()
}
/// Runs the cache task
/// This blocks the current task, so it is advised
/// to spawn a new task for this
/// Send ExitMsg to the associated Sender to exit
pub fn run(&mut self) {
loop {
match self.receiver.recv() {
Clear(request, tx) => {
self.cache.clear(request);
tx.send(());
},
Cleanup(tx) => {
self.cache.cleanup();
tx.send(());
},
MatchHeader(request, header, tx) => {
tx.send(self.cache.match_header(request, header.as_slice()));
},
MatchHeaderUpdate(request, header, new_max_age, tx) => {
tx.send(self.cache.match_header_and_update(request, header.as_slice(), new_max_age));
},
MatchMethod(request, method, tx) => {
tx.send(self.cache.match_method(request, method));
},
MatchMethodUpdate(request, method, new_max_age, tx) => {
tx.send(self.cache.match_method_and_update(request, method, new_max_age));
},
Insert(entry, tx) => {
self.cache.insert(entry);
tx.send(());
},
ExitMsg => break
}
}
}
}

View file

@ -0,0 +1,149 @@
/* 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 url::Url;
use http::method::{Get, Method};
use http::headers::request::HeaderCollection;
use fetch::cors_cache::CORSCache;
use fetch::response::Response;
/// A [request context](http://fetch.spec.whatwg.org/#concept-request-context)
pub enum Context {
Audio, Beacon, CSPreport, Download, Embed, Eventsource,
Favicon, Fetch, Font, Form, Frame, Hyperlink, IFrame, Image,
ImageSet, Import, Internal, Location, Manifest, Object, Ping,
Plugin, Prefetch, Script, ServiceWorker, SharedWorker, Subresource,
Style, Track, Video, Worker, XMLHttpRequest, XSLT
}
/// A [request context frame type](http://fetch.spec.whatwg.org/#concept-request-context-frame-type)
pub enum ContextFrameType {
Auxiliary,
TopLevel,
Nested,
ContextNone
}
/// A [referer](http://fetch.spec.whatwg.org/#concept-request-referrer)
pub enum Referer {
RefererNone,
Client,
RefererUrl(Url)
}
/// A [request mode](http://fetch.spec.whatwg.org/#concept-request-mode)
pub enum RequestMode {
SameOrigin,
NoCORS,
CORSMode,
ForcedPreflightMode
}
/// Request [credentials mode](http://fetch.spec.whatwg.org/#concept-request-credentials-mode)
pub enum CredentialsMode {
Omit,
CredentialsSameOrigin,
Include
}
/// [Response tainting](http://fetch.spec.whatwg.org/#concept-request-response-tainting)
pub enum ResponseTainting {
Basic,
CORSTainting,
Opaque
}
/// A [Request](http://fetch.spec.whatwg.org/#requests) as defined by the Fetch spec
pub struct Request {
pub method: Method,
pub url: Url,
pub headers: HeaderCollection,
pub unsafe_request: bool,
pub body: Option<Vec<u8>>,
pub preserve_content_codings: bool,
// pub client: GlobalRef, // XXXManishearth copy over only the relevant fields of the global scope,
// not the entire scope to avoid the libscript dependency
pub skip_service_worker: bool,
pub context: Context,
pub context_frame_type: ContextFrameType,
pub origin: Option<Url>,
pub force_origin_header: bool,
pub same_origin_data: bool,
pub referer: Referer,
pub authentication: bool,
pub sync: bool,
pub mode: RequestMode,
pub credentials_mode: CredentialsMode,
pub use_url_credentials: bool,
pub manual_redirect: bool,
pub redirect_count: uint,
pub response_tainting: ResponseTainting,
pub cache: Option<Box<CORSCache>>
}
impl Request {
pub fn new(url: Url, context: Context) -> Request {
Request {
method: Get,
url: url,
headers: HeaderCollection::new(),
unsafe_request: false,
body: None,
preserve_content_codings: false,
skip_service_worker: false,
context: context,
context_frame_type: ContextNone,
origin: None,
force_origin_header: false,
same_origin_data: false,
referer: Client,
authentication: false,
sync: false,
mode: NoCORS,
credentials_mode: Omit,
use_url_credentials: false,
manual_redirect: false,
redirect_count: 0,
response_tainting: Basic,
cache: None
}
}
/// [Basic fetch](http://fetch.spec.whatwg.org#basic-fetch)
pub fn basic_fetch(&mut self) -> Response {
match self.url.scheme.as_slice() {
"about" => match self.url.non_relative_scheme_data() {
Some(s) if s.as_slice() == "blank" => {
let mut response = Response::new();
let _ = response.headers.insert_raw("Content-Type".to_string(), b"text/html;charset=utf-8");
response
},
_ => Response::network_error()
},
"http" | "https" => {
self.http_fetch(false, false, false)
},
"blob" | "data" | "file" | "ftp" => {
// XXXManishearth handle these
fail!("Unimplemented scheme for Fetch")
},
_ => Response::network_error()
}
}
// [HTTP fetch](http://fetch.spec.whatwg.org#http-fetch)
pub fn http_fetch(&mut self, _cors_flag: bool, cors_preflight_flag: bool, _authentication_fetch_flag: bool) -> Response {
let response = Response::new();
// TODO: Service worker fetch
// Step 3
// Substep 1
self.skip_service_worker = true;
// Substep 2
if cors_preflight_flag {
// XXXManishearth stuff goes here
}
response
}
}

View file

@ -0,0 +1,144 @@
/* 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 url::Url;
use http::status::{Status, UnregisteredStatus};
use StatusOk = http::status::Ok;
use http::headers::HeaderEnum;
use http::headers::response::HeaderCollection;
use std::ascii::OwnedStrAsciiExt;
use std::comm::Receiver;
/// [Response type](http://fetch.spec.whatwg.org/#concept-response-type)
#[deriving(Clone, PartialEq)]
pub enum ResponseType {
Basic,
CORS,
Default,
Error,
Opaque
}
/// [Response termination reason](http://fetch.spec.whatwg.org/#concept-response-termination-reason)
#[deriving(Clone)]
pub enum TerminationReason {
EndUserAbort,
Fatal,
Timeout
}
/// The response body can still be pushed to after fetch
/// This provides a way to store unfinished response bodies
#[unstable = "I haven't yet decided exactly how the interface for this will be"]
#[deriving(Clone)]
pub enum ResponseBody {
Empty, // XXXManishearth is this necessary, or is Done(vec![]) enough?
Receiving(Vec<u8>),
Done(Vec<u8>),
}
#[unstable = "I haven't yet decided exactly how the interface for this will be"]
pub enum ResponseMsg {
Chunk(Vec<u8>),
Finished,
Errored
}
#[unstable = "I haven't yet decided exactly how the interface for this will be"]
pub struct ResponseLoader {
response: Response,
chan: Receiver<ResponseMsg>
}
/// A [Response](http://fetch.spec.whatwg.org/#concept-response) as defined by the Fetch spec
#[deriving(Clone)]
pub struct Response {
pub response_type: ResponseType,
pub termination_reason: Option<TerminationReason>,
pub url: Option<Url>,
pub status: Status,
pub headers: HeaderCollection,
pub body: ResponseBody,
/// [Internal response](http://fetch.spec.whatwg.org/#concept-internal-response), only used if the Response is a filtered response
pub internal_response: Option<Box<Response>>,
}
impl Response {
pub fn new() -> Response {
Response {
response_type: Default,
termination_reason: None,
url: None,
status: StatusOk,
headers: HeaderCollection::new(),
body: Empty,
internal_response: None
}
}
pub fn network_error() -> Response {
Response {
response_type: Error,
termination_reason: None,
url: None,
status: UnregisteredStatus(0, "".to_string()),
headers: HeaderCollection::new(),
body: Empty,
internal_response: None
}
}
pub fn is_network_error(&self) -> bool {
match self.response_type {
Error => true,
_ => false
}
}
/// Convert to a filtered response, of type `filter_type`.
/// Do not use with type Error or Default
pub fn to_filtered(self, filter_type: ResponseType) -> Response {
assert!(filter_type != Error);
assert!(filter_type != Default);
if self.is_network_error() {
return self;
}
let old_headers = self.headers.clone();
let mut response = self.clone();
response.internal_response = Some(box self);
match filter_type {
Default | Error => unreachable!(),
Basic => {
let mut headers = HeaderCollection::new();
for h in old_headers.iter() {
match h.header_name().into_ascii_lower().as_slice() {
"set-cookie" | "set-cookie2" => {},
_ => headers.insert(h)
}
}
response.headers = headers;
response.response_type = filter_type;
},
CORS => {
let mut headers = HeaderCollection::new();
for h in old_headers.iter() {
match h.header_name().into_ascii_lower().as_slice() {
"cache-control" | "content-language" |
"content-type" | "expires" | "last-modified" | "Pragma" => {},
// XXXManishearth handle Access-Control-Expose-Headers
_ => headers.insert(h)
}
}
response.headers = headers;
response.response_type = filter_type;
},
Opaque => {
response.headers = HeaderCollection::new();
response.status = UnregisteredStatus(0, "".to_string());
response.body = Empty;
}
}
response
}
}

View file

@ -0,0 +1,50 @@
/* 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 resource_task::{ProgressMsg, Metadata, Payload, Done, LoaderTask, start_sending};
use std::io;
use std::io::File;
use servo_util::task::spawn_named;
static READ_SIZE: uint = 8192;
fn read_all(reader: &mut io::Stream, progress_chan: &Sender<ProgressMsg>)
-> Result<(), String> {
loop {
let mut buf = vec!();
match reader.push_at_least(READ_SIZE, READ_SIZE, &mut buf) {
Ok(_) => progress_chan.send(Payload(buf)),
Err(e) => match e.kind {
io::EndOfFile => {
if buf.len() > 0 {
progress_chan.send(Payload(buf));
}
return Ok(());
}
_ => return Err(e.desc.to_string()),
}
}
}
}
pub fn factory() -> LoaderTask {
let f: LoaderTask = proc(load_data, start_chan) {
let url = load_data.url;
assert!("file" == url.scheme.as_slice());
let progress_chan = start_sending(start_chan, Metadata::default(url.clone()));
spawn_named("file_loader", proc() {
match File::open_mode(&Path::new(url.serialize_path().unwrap()), io::Open, io::Read) {
Ok(ref mut reader) => {
let res = read_all(reader as &mut io::Stream, &progress_chan);
progress_chan.send(Done(res));
}
Err(e) => {
progress_chan.send(Done(Err(e.desc.to_string())));
}
};
});
};
f
}

View file

@ -0,0 +1,167 @@
/* 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 resource_task::{Metadata, Payload, Done, LoadResponse, LoadData, LoaderTask, start_sending_opt};
use std::collections::hashmap::HashSet;
use http::client::{RequestWriter, NetworkStream};
use http::headers::HeaderEnum;
use std::io::Reader;
use servo_util::task::spawn_named;
use url::Url;
pub fn factory() -> LoaderTask {
let f: LoaderTask = proc(url, start_chan) {
spawn_named("http_loader", proc() load(url, start_chan))
};
f
}
fn send_error(url: Url, err: String, start_chan: Sender<LoadResponse>) {
match start_sending_opt(start_chan, Metadata::default(url)) {
Ok(p) => p.send(Done(Err(err))),
_ => {}
};
}
fn load(load_data: LoadData, start_chan: Sender<LoadResponse>) {
// FIXME: At the time of writing this FIXME, servo didn't have any central
// location for configuration. If you're reading this and such a
// repository DOES exist, please update this constant to use it.
let max_redirects = 50u;
let mut iters = 0u;
let mut url = load_data.url.clone();
let mut redirected_to = HashSet::new();
// Loop to handle redirects.
loop {
iters = iters + 1;
if iters > max_redirects {
send_error(url, "too many redirects".to_string(), start_chan);
return;
}
if redirected_to.contains(&url) {
send_error(url, "redirect loop".to_string(), start_chan);
return;
}
redirected_to.insert(url.clone());
match url.scheme.as_slice() {
"http" | "https" => {}
_ => {
let s = format!("{:s} request, but we don't support that scheme", url.scheme);
send_error(url, s, start_chan);
return;
}
}
info!("requesting {:s}", url.serialize());
let request = RequestWriter::<NetworkStream>::new(load_data.method.clone(), url.clone());
let mut writer = match request {
Ok(w) => box w,
Err(e) => {
send_error(url, e.desc.to_string(), start_chan);
return;
}
};
// Preserve the `host` header set automatically by RequestWriter.
let host = writer.headers.host.clone();
writer.headers = box load_data.headers.clone();
writer.headers.host = host;
if writer.headers.accept_encoding.is_none() {
// We currently don't support HTTP Compression (FIXME #2587)
writer.headers.accept_encoding = Some(String::from_str("identity".as_slice()))
}
match load_data.data {
Some(ref data) => {
writer.headers.content_length = Some(data.len());
match writer.write(data.as_slice()) {
Err(e) => {
send_error(url, e.desc.to_string(), start_chan);
return;
}
_ => {}
}
},
_ => {}
}
let mut response = match writer.read_response() {
Ok(r) => r,
Err((_, e)) => {
send_error(url, e.desc.to_string(), start_chan);
return;
}
};
// Dump headers, but only do the iteration if info!() is enabled.
info!("got HTTP response {:s}, headers:", response.status.to_string());
info!("{:?}",
for header in response.headers.iter() {
info!(" - {:s}: {:s}", header.header_name(), header.header_value());
});
if 3 == (response.status.code() / 100) {
match response.headers.location {
Some(new_url) => {
// CORS (http://fetch.spec.whatwg.org/#http-fetch, status section, point 9, 10)
match load_data.cors {
Some(ref c) => {
if c.preflight {
// The preflight lied
send_error(url, "Preflight fetch inconsistent with main fetch".to_string(), start_chan);
return;
} else {
// XXXManishearth There are some CORS-related steps here,
// but they don't seem necessary until credentials are implemented
}
}
_ => {}
}
info!("redirecting to {:s}", new_url.serialize());
url = new_url;
continue;
}
None => ()
}
}
let mut metadata = Metadata::default(url);
metadata.set_content_type(&response.headers.content_type);
metadata.headers = Some(*response.headers.clone());
metadata.status = response.status.clone();
let progress_chan = match start_sending_opt(start_chan, metadata) {
Ok(p) => p,
_ => return
};
loop {
let mut buf = Vec::with_capacity(1024);
unsafe { buf.set_len(1024); }
match response.read(buf.as_mut_slice()) {
Ok(len) => {
unsafe { buf.set_len(len); }
if progress_chan.send_opt(Payload(buf)).is_err() {
// The send errors when the receiver is out of scope,
// which will happen if the fetch has timed out (or has been aborted)
// so we don't need to continue with the loading of the file here.
return;
}
}
Err(_) => {
let _ = progress_chan.send_opt(Done(Ok(())));
break;
}
}
}
// We didn't get redirected.
break;
}
}

View file

@ -0,0 +1,67 @@
/* 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 std::iter::range_step;
use stb_image = stb_image::image;
use png;
// FIXME: Images must not be copied every frame. Instead we should atomically
// reference count them.
pub type Image = png::Image;
static TEST_IMAGE: &'static [u8] = include_bin!("test.jpeg");
pub fn test_image_bin() -> Vec<u8> {
TEST_IMAGE.iter().map(|&x| x).collect()
}
// TODO(pcwalton): Speed up with SIMD, or better yet, find some way to not do this.
fn byte_swap(data: &mut [u8]) {
let length = data.len();
for i in range_step(0, length, 4) {
let r = data[i + 2];
data[i + 2] = data[i + 0];
data[i + 0] = r;
}
}
pub fn load_from_memory(buffer: &[u8]) -> Option<Image> {
if buffer.len() == 0 {
return None;
}
if png::is_png(buffer) {
match png::load_png_from_memory(buffer) {
Ok(mut png_image) => {
match png_image.pixels {
png::RGB8(ref mut data) | png::RGBA8(ref mut data) => {
byte_swap(data.as_mut_slice());
}
_ => {}
}
Some(png_image)
}
Err(_err) => None,
}
} else {
// For non-png images, we use stb_image
// Can't remember why we do this. Maybe it's what cairo wants
static FORCE_DEPTH: uint = 4;
match stb_image::load_from_memory_with_depth(buffer, FORCE_DEPTH, true) {
stb_image::ImageU8(mut image) => {
assert!(image.depth == 4);
byte_swap(image.data.as_mut_slice());
Some(png::Image {
width: image.width as u32,
height: image.height as u32,
pixels: png::RGBA8(image.data)
})
}
stb_image::ImageF32(_image) => fail!("HDR images not implemented"),
stb_image::Error(_) => None
}
}
}

View file

@ -0,0 +1,109 @@
/* 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 image::base::Image;
use image_cache_task::{ImageReady, ImageNotReady, ImageFailed};
use local_image_cache::LocalImageCache;
use geom::size::Size2D;
use std::mem;
use sync::{Arc, Mutex};
use url::Url;
// FIXME: Nasty coupling here This will be a problem if we want to factor out image handling from
// the network stack. This should probably be factored out into an interface and use dependency
// injection.
/// A struct to store image data. The image will be loaded once the first time it is requested,
/// and an Arc will be stored. Clones of this Arc are given out on demand.
#[deriving(Clone)]
pub struct ImageHolder {
url: Url,
image: Option<Arc<Box<Image>>>,
cached_size: Size2D<int>,
local_image_cache: Arc<Mutex<LocalImageCache>>,
}
impl ImageHolder {
pub fn new(url: Url, local_image_cache: Arc<Mutex<LocalImageCache>>) -> ImageHolder {
debug!("ImageHolder::new() {}", url.serialize());
let holder = ImageHolder {
url: url,
image: None,
cached_size: Size2D(0,0),
local_image_cache: local_image_cache.clone(),
};
// Tell the image cache we're going to be interested in this url
// FIXME: These two messages must be sent to prep an image for use
// but they are intended to be spread out in time. Ideally prefetch
// should be done as early as possible and decode only once we
// are sure that the image will be used.
{
let val = holder.local_image_cache.lock();
let mut local_image_cache = val;
local_image_cache.prefetch(&holder.url);
local_image_cache.decode(&holder.url);
}
holder
}
/// This version doesn't perform any computation, but may be stale w.r.t. newly-available image
/// data that determines size.
///
/// The intent is that the impure version is used during layout when dimensions are used for
/// computing layout.
pub fn size(&self) -> Size2D<int> {
self.cached_size
}
/// Query and update the current image size.
pub fn get_size(&mut self) -> Option<Size2D<int>> {
debug!("get_size() {}", self.url.serialize());
self.get_image().map(|img| {
self.cached_size = Size2D(img.width as int,
img.height as int);
self.cached_size.clone()
})
}
pub fn get_image_if_present(&self) -> Option<Arc<Box<Image>>> {
debug!("get_image_if_present() {}", self.url.serialize());
self.image.clone()
}
pub fn get_image(&mut self) -> Option<Arc<Box<Image>>> {
debug!("get_image() {}", self.url.serialize());
// If this is the first time we've called this function, load
// the image and store it for the future
if self.image.is_none() {
let port = {
let val = self.local_image_cache.lock();
let mut local_image_cache = val;
local_image_cache.get_image(&self.url)
};
match port.recv() {
ImageReady(image) => {
self.image = Some(image);
}
ImageNotReady => {
debug!("image not ready for {:s}", self.url.serialize());
}
ImageFailed => {
debug!("image decoding failed for {:s}", self.url.serialize());
}
}
}
// Clone isn't pure so we have to swap out the mutable image option
let image = mem::replace(&mut self.image, None);
let result = image.clone();
mem::replace(&mut self.image, image);
return result;
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

View file

@ -0,0 +1,993 @@
/* 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 image::base::{Image, load_from_memory};
use resource_task;
use resource_task::{LoadData, ResourceTask};
use std::comm::{channel, Receiver, Sender};
use std::collections::hashmap::HashMap;
use std::mem::replace;
use std::task::spawn;
use std::result;
use sync::{Arc, Mutex};
use serialize::{Encoder, Encodable};
use url::Url;
pub enum Msg {
/// Tell the cache that we may need a particular image soon. Must be posted
/// before Decode
Prefetch(Url),
/// Tell the cache to decode an image. Must be posted before GetImage/WaitForImage
Decode(Url),
/// Request an Image object for a URL. If the image is not is not immediately
/// available then ImageNotReady is returned.
GetImage(Url, Sender<ImageResponseMsg>),
/// Wait for an image to become available (or fail to load).
WaitForImage(Url, Sender<ImageResponseMsg>),
/// Clients must wait for a response before shutting down the ResourceTask
Exit(Sender<()>),
/// Used by the prefetch tasks to post back image binaries
StorePrefetchedImageData(Url, Result<Vec<u8>, ()>),
/// Used by the decoder tasks to post decoded images back to the cache
StoreImage(Url, Option<Arc<Box<Image>>>),
/// For testing
WaitForStore(Sender<()>),
/// For testing
WaitForStorePrefetched(Sender<()>),
}
#[deriving(Clone)]
pub enum ImageResponseMsg {
ImageReady(Arc<Box<Image>>),
ImageNotReady,
ImageFailed
}
impl PartialEq for ImageResponseMsg {
fn eq(&self, other: &ImageResponseMsg) -> bool {
match (self, other) {
(&ImageReady(..), &ImageReady(..)) => fail!("unimplemented comparison"),
(&ImageNotReady, &ImageNotReady) => true,
(&ImageFailed, &ImageFailed) => true,
(&ImageReady(..), _) | (&ImageNotReady, _) | (&ImageFailed, _) => false
}
}
}
#[deriving(Clone)]
pub struct ImageCacheTask {
chan: Sender<Msg>,
}
impl<E, S: Encoder<E>> Encodable<S, E> for ImageCacheTask {
fn encode(&self, _: &mut S) -> Result<(), E> {
Ok(())
}
}
type DecoderFactory = fn() -> proc(&[u8]) -> Option<Image>;
impl ImageCacheTask {
pub fn new(resource_task: ResourceTask) -> ImageCacheTask {
let (chan, port) = channel();
let chan_clone = chan.clone();
spawn(proc() {
let mut cache = ImageCache {
resource_task: resource_task,
port: port,
chan: chan_clone,
state_map: HashMap::new(),
wait_map: HashMap::new(),
need_exit: None
};
cache.run();
});
ImageCacheTask {
chan: chan,
}
}
pub fn new_sync(resource_task: ResourceTask) -> ImageCacheTask {
let (chan, port) = channel();
spawn(proc() {
let inner_cache = ImageCacheTask::new(resource_task);
loop {
let msg: Msg = port.recv();
match msg {
GetImage(url, response) => {
inner_cache.send(WaitForImage(url, response));
}
Exit(response) => {
inner_cache.send(Exit(response));
break;
}
msg => inner_cache.send(msg)
}
}
});
ImageCacheTask {
chan: chan,
}
}
}
struct ImageCache {
/// A handle to the resource task for fetching the image binaries
resource_task: ResourceTask,
/// The port on which we'll receive client requests
port: Receiver<Msg>,
/// A copy of the shared chan to give to child tasks
chan: Sender<Msg>,
/// The state of processsing an image for a URL
state_map: HashMap<Url, ImageState>,
/// List of clients waiting on a WaitForImage response
wait_map: HashMap<Url, Arc<Mutex<Vec<Sender<ImageResponseMsg>>>>>,
need_exit: Option<Sender<()>>,
}
#[deriving(Clone)]
enum ImageState {
Init,
Prefetching(AfterPrefetch),
Prefetched(Vec<u8>),
Decoding,
Decoded(Arc<Box<Image>>),
Failed
}
#[deriving(Clone)]
enum AfterPrefetch {
DoDecode,
DoNotDecode
}
impl ImageCache {
pub fn run(&mut self) {
let mut store_chan: Option<Sender<()>> = None;
let mut store_prefetched_chan: Option<Sender<()>> = None;
loop {
let msg = self.port.recv();
debug!("image_cache_task: received: {:?}", msg);
match msg {
Prefetch(url) => self.prefetch(url),
StorePrefetchedImageData(url, data) => {
store_prefetched_chan.map(|chan| {
chan.send(());
});
store_prefetched_chan = None;
self.store_prefetched_image_data(url, data);
}
Decode(url) => self.decode(url),
StoreImage(url, image) => {
store_chan.map(|chan| {
chan.send(());
});
store_chan = None;
self.store_image(url, image)
}
GetImage(url, response) => self.get_image(url, response),
WaitForImage(url, response) => {
self.wait_for_image(url, response)
}
WaitForStore(chan) => store_chan = Some(chan),
WaitForStorePrefetched(chan) => store_prefetched_chan = Some(chan),
Exit(response) => {
assert!(self.need_exit.is_none());
self.need_exit = Some(response);
}
}
let need_exit = replace(&mut self.need_exit, None);
match need_exit {
Some(response) => {
// Wait until we have no outstanding requests and subtasks
// before exiting
let mut can_exit = true;
for (_, state) in self.state_map.iter() {
match *state {
Prefetching(..) => can_exit = false,
Decoding => can_exit = false,
Init | Prefetched(..) | Decoded(..) | Failed => ()
}
}
if can_exit {
response.send(());
break;
} else {
self.need_exit = Some(response);
}
}
None => ()
}
}
}
fn get_state(&self, url: Url) -> ImageState {
match self.state_map.find(&url) {
Some(state) => state.clone(),
None => Init
}
}
fn set_state(&mut self, url: Url, state: ImageState) {
self.state_map.insert(url, state);
}
fn prefetch(&mut self, url: Url) {
match self.get_state(url.clone()) {
Init => {
let to_cache = self.chan.clone();
let resource_task = self.resource_task.clone();
let url_clone = url.clone();
spawn(proc() {
let url = url_clone;
debug!("image_cache_task: started fetch for {:s}", url.serialize());
let image = load_image_data(url.clone(), resource_task.clone());
let result = if image.is_ok() {
Ok(image.unwrap())
} else {
Err(())
};
to_cache.send(StorePrefetchedImageData(url.clone(), result));
debug!("image_cache_task: ended fetch for {:s}", url.serialize());
});
self.set_state(url, Prefetching(DoNotDecode));
}
Prefetching(..) | Prefetched(..) | Decoding | Decoded(..) | Failed => {
// We've already begun working on this image
}
}
}
fn store_prefetched_image_data(&mut self, url: Url, data: Result<Vec<u8>, ()>) {
match self.get_state(url.clone()) {
Prefetching(next_step) => {
match data {
Ok(data) => {
self.set_state(url.clone(), Prefetched(data));
match next_step {
DoDecode => self.decode(url),
_ => ()
}
}
Err(..) => {
self.set_state(url.clone(), Failed);
self.purge_waiters(url, || ImageFailed);
}
}
}
Init
| Prefetched(..)
| Decoding
| Decoded(..)
| Failed => {
fail!("wrong state for storing prefetched image")
}
}
}
fn decode(&mut self, url: Url) {
match self.get_state(url.clone()) {
Init => fail!("decoding image before prefetch"),
Prefetching(DoNotDecode) => {
// We don't have the data yet, queue up the decode
self.set_state(url, Prefetching(DoDecode))
}
Prefetching(DoDecode) => {
// We don't have the data yet, but the decode request is queued up
}
Prefetched(data) => {
let to_cache = self.chan.clone();
let url_clone = url.clone();
spawn(proc() {
let url = url_clone;
debug!("image_cache_task: started image decode for {:s}", url.serialize());
let image = load_from_memory(data.as_slice());
let image = if image.is_some() {
Some(Arc::new(box image.unwrap()))
} else {
None
};
to_cache.send(StoreImage(url.clone(), image));
debug!("image_cache_task: ended image decode for {:s}", url.serialize());
});
self.set_state(url, Decoding);
}
Decoding | Decoded(..) | Failed => {
// We've already begun decoding
}
}
}
fn store_image(&mut self, url: Url, image: Option<Arc<Box<Image>>>) {
match self.get_state(url.clone()) {
Decoding => {
match image {
Some(image) => {
self.set_state(url.clone(), Decoded(image.clone()));
self.purge_waiters(url, || ImageReady(image.clone()) );
}
None => {
self.set_state(url.clone(), Failed);
self.purge_waiters(url, || ImageFailed );
}
}
}
Init
| Prefetching(..)
| Prefetched(..)
| Decoded(..)
| Failed => {
fail!("incorrect state in store_image")
}
}
}
fn purge_waiters(&mut self, url: Url, f: || -> ImageResponseMsg) {
match self.wait_map.pop(&url) {
Some(waiters) => {
let mut items = waiters.lock();
for response in items.iter() {
response.send(f());
}
}
None => ()
}
}
fn get_image(&self, url: Url, response: Sender<ImageResponseMsg>) {
match self.get_state(url.clone()) {
Init => fail!("request for image before prefetch"),
Prefetching(DoDecode) => response.send(ImageNotReady),
Prefetching(DoNotDecode) | Prefetched(..) => fail!("request for image before decode"),
Decoding => response.send(ImageNotReady),
Decoded(image) => response.send(ImageReady(image.clone())),
Failed => response.send(ImageFailed),
}
}
fn wait_for_image(&mut self, url: Url, response: Sender<ImageResponseMsg>) {
match self.get_state(url.clone()) {
Init => fail!("request for image before prefetch"),
Prefetching(DoNotDecode) | Prefetched(..) => fail!("request for image before decode"),
Prefetching(DoDecode) | Decoding => {
// We don't have this image yet
if self.wait_map.contains_key(&url) {
let waiters = self.wait_map.find_mut(&url).unwrap();
let mut response = Some(response);
let mut items = waiters.lock();
items.push(response.take().unwrap());
} else {
let response = vec!(response);
let wrapped = Arc::new(Mutex::new(response));
self.wait_map.insert(url, wrapped);
}
}
Decoded(image) => {
response.send(ImageReady(image.clone()));
}
Failed => {
response.send(ImageFailed);
}
}
}
}
pub trait ImageCacheTaskClient {
fn exit(&self);
}
impl ImageCacheTaskClient for ImageCacheTask {
fn exit(&self) {
let (response_chan, response_port) = channel();
self.send(Exit(response_chan));
response_port.recv();
}
}
impl ImageCacheTask {
pub fn send(&self, msg: Msg) {
self.chan.send(msg);
}
#[cfg(test)]
fn wait_for_store(&self) -> Receiver<()> {
let (chan, port) = channel();
self.send(WaitForStore(chan));
port
}
#[cfg(test)]
fn wait_for_store_prefetched(&self) -> Receiver<()> {
let (chan, port) = channel();
self.send(WaitForStorePrefetched(chan));
port
}
}
fn load_image_data(url: Url, resource_task: ResourceTask) -> Result<Vec<u8>, ()> {
let (response_chan, response_port) = channel();
resource_task.send(resource_task::Load(LoadData::new(url), response_chan));
let mut image_data = vec!();
let progress_port = response_port.recv().progress_port;
loop {
match progress_port.recv() {
resource_task::Payload(data) => {
image_data.push_all(data.as_slice());
}
resource_task::Done(result::Ok(..)) => {
return Ok(image_data.move_iter().collect());
}
resource_task::Done(result::Err(..)) => {
return Err(());
}
}
}
}
pub fn spawn_listener<A: Send>(f: proc(Receiver<A>):Send) -> Sender<A> {
let (setup_chan, setup_port) = channel();
spawn(proc() {
let (chan, port) = channel();
setup_chan.send(chan);
f(port);
});
setup_port.recv()
}
#[cfg(test)]
mod tests {
use super::*;
use resource_task;
use resource_task::{ResourceTask, Metadata, start_sending};
use image::base::test_image_bin;
use std::comm;
use url::Url;
trait Closure {
fn invoke(&self, _response: Sender<resource_task::ProgressMsg>) { }
}
struct DoesNothing;
impl Closure for DoesNothing { }
struct JustSendOK {
url_requested_chan: Sender<()>,
}
impl Closure for JustSendOK {
fn invoke(&self, response: Sender<resource_task::ProgressMsg>) {
self.url_requested_chan.send(());
response.send(resource_task::Done(Ok(())));
}
}
struct SendTestImage;
impl Closure for SendTestImage {
fn invoke(&self, response: Sender<resource_task::ProgressMsg>) {
response.send(resource_task::Payload(test_image_bin()));
response.send(resource_task::Done(Ok(())));
}
}
struct SendBogusImage;
impl Closure for SendBogusImage {
fn invoke(&self, response: Sender<resource_task::ProgressMsg>) {
response.send(resource_task::Payload(vec!()));
response.send(resource_task::Done(Ok(())));
}
}
struct SendTestImageErr;
impl Closure for SendTestImageErr {
fn invoke(&self, response: Sender<resource_task::ProgressMsg>) {
response.send(resource_task::Payload(test_image_bin()));
response.send(resource_task::Done(Err("".to_string())));
}
}
struct WaitSendTestImage {
wait_port: Receiver<()>,
}
impl Closure for WaitSendTestImage {
fn invoke(&self, response: Sender<resource_task::ProgressMsg>) {
// Don't send the data until after the client requests
// the image
self.wait_port.recv();
response.send(resource_task::Payload(test_image_bin()));
response.send(resource_task::Done(Ok(())));
}
}
struct WaitSendTestImageErr {
wait_port: Receiver<()>,
}
impl Closure for WaitSendTestImageErr {
fn invoke(&self, response: Sender<resource_task::ProgressMsg>) {
// Don't send the data until after the client requests
// the image
self.wait_port.recv();
response.send(resource_task::Payload(test_image_bin()));
response.send(resource_task::Done(Err("".to_string())));
}
}
fn mock_resource_task<T: Closure+Send>(on_load: Box<T>) -> ResourceTask {
spawn_listener(proc(port: Receiver<resource_task::ControlMsg>) {
loop {
match port.recv() {
resource_task::Load(_, response) => {
let chan = start_sending(response, Metadata::default(
Url::parse("file:///fake").unwrap()));
on_load.invoke(chan);
}
resource_task::Exit => break
}
}
})
}
#[test]
fn should_exit_on_request() {
let mock_resource_task = mock_resource_task(box DoesNothing);
let image_cache_task = ImageCacheTask::new(mock_resource_task.clone());
image_cache_task.exit();
mock_resource_task.send(resource_task::Exit);
}
#[test]
#[should_fail]
fn should_fail_if_unprefetched_image_is_requested() {
let mock_resource_task = mock_resource_task(box DoesNothing);
let image_cache_task = ImageCacheTask::new(mock_resource_task.clone());
let url = Url::parse("file:///").unwrap();
let (chan, port) = channel();
image_cache_task.send(GetImage(url, chan));
port.recv();
}
#[test]
fn should_request_url_from_resource_task_on_prefetch() {
let (url_requested_chan, url_requested) = channel();
let mock_resource_task = mock_resource_task(box JustSendOK { url_requested_chan: url_requested_chan});
let image_cache_task = ImageCacheTask::new(mock_resource_task.clone());
let url = Url::parse("file:///").unwrap();
image_cache_task.send(Prefetch(url));
url_requested.recv();
image_cache_task.exit();
mock_resource_task.send(resource_task::Exit);
}
#[test]
fn should_not_request_url_from_resource_task_on_multiple_prefetches() {
let (url_requested_chan, url_requested) = comm::channel();
let mock_resource_task = mock_resource_task(box JustSendOK { url_requested_chan: url_requested_chan});
let image_cache_task = ImageCacheTask::new(mock_resource_task.clone());
let url = Url::parse("file:///").unwrap();
image_cache_task.send(Prefetch(url.clone()));
image_cache_task.send(Prefetch(url));
url_requested.recv();
image_cache_task.exit();
mock_resource_task.send(resource_task::Exit);
match url_requested.try_recv() {
Err(_) => (),
Ok(_) => fail!(),
};
}
#[test]
fn should_return_image_not_ready_if_data_has_not_arrived() {
let (wait_chan, wait_port) = comm::channel();
let mock_resource_task = mock_resource_task(box WaitSendTestImage{wait_port: wait_port});
let image_cache_task = ImageCacheTask::new(mock_resource_task.clone());
let url = Url::parse("file:///").unwrap();
image_cache_task.send(Prefetch(url.clone()));
image_cache_task.send(Decode(url.clone()));
let (response_chan, response_port) = comm::channel();
image_cache_task.send(GetImage(url, response_chan));
assert!(response_port.recv() == ImageNotReady);
wait_chan.send(());
image_cache_task.exit();
mock_resource_task.send(resource_task::Exit);
}
#[test]
fn should_return_decoded_image_data_if_data_has_arrived() {
let mock_resource_task = mock_resource_task(box SendTestImage);
let image_cache_task = ImageCacheTask::new(mock_resource_task.clone());
let url = Url::parse("file:///").unwrap();
let join_port = image_cache_task.wait_for_store();
image_cache_task.send(Prefetch(url.clone()));
image_cache_task.send(Decode(url.clone()));
// Wait until our mock resource task has sent the image to the image cache
join_port.recv();
let (response_chan, response_port) = comm::channel();
image_cache_task.send(GetImage(url, response_chan));
match response_port.recv() {
ImageReady(_) => (),
_ => fail!("bleh")
}
image_cache_task.exit();
mock_resource_task.send(resource_task::Exit);
}
#[test]
fn should_return_decoded_image_data_for_multiple_requests() {
let mock_resource_task = mock_resource_task(box SendTestImage);
let image_cache_task = ImageCacheTask::new(mock_resource_task.clone());
let url = Url::parse("file:///").unwrap();
let join_port = image_cache_task.wait_for_store();
image_cache_task.send(Prefetch(url.clone()));
image_cache_task.send(Decode(url.clone()));
// Wait until our mock resource task has sent the image to the image cache
join_port.recv();
for _ in range(0u32, 2u32) {
let (response_chan, response_port) = comm::channel();
image_cache_task.send(GetImage(url.clone(), response_chan));
match response_port.recv() {
ImageReady(_) => (),
_ => fail!("bleh")
}
}
image_cache_task.exit();
mock_resource_task.send(resource_task::Exit);
}
#[test]
fn should_not_request_image_from_resource_task_if_image_is_already_available() {
let (image_bin_sent_chan, image_bin_sent) = comm::channel();
let (resource_task_exited_chan, resource_task_exited) = comm::channel();
let mock_resource_task = spawn_listener(proc(port: Receiver<resource_task::ControlMsg>) {
loop {
match port.recv() {
resource_task::Load(_, response) => {
let chan = start_sending(response, Metadata::default(
Url::parse("file:///fake").unwrap()));
chan.send(resource_task::Payload(test_image_bin()));
chan.send(resource_task::Done(Ok(())));
image_bin_sent_chan.send(());
}
resource_task::Exit => {
resource_task_exited_chan.send(());
break
}
}
}
});
let image_cache_task = ImageCacheTask::new(mock_resource_task.clone());
let url = Url::parse("file:///").unwrap();
image_cache_task.send(Prefetch(url.clone()));
// Wait until our mock resource task has sent the image to the image cache
image_bin_sent.recv();
image_cache_task.send(Prefetch(url.clone()));
image_cache_task.exit();
mock_resource_task.send(resource_task::Exit);
resource_task_exited.recv();
// Our resource task should not have received another request for the image
// because it's already cached
match image_bin_sent.try_recv() {
Err(_) => (),
Ok(_) => fail!(),
}
}
#[test]
fn should_not_request_image_from_resource_task_if_image_fetch_already_failed() {
let (image_bin_sent_chan, image_bin_sent) = comm::channel();
let (resource_task_exited_chan, resource_task_exited) = comm::channel();
let mock_resource_task = spawn_listener(proc(port: Receiver<resource_task::ControlMsg>) {
loop {
match port.recv() {
resource_task::Load(_, response) => {
let chan = start_sending(response, Metadata::default(
Url::parse("file:///fake").unwrap()));
chan.send(resource_task::Payload(test_image_bin()));
chan.send(resource_task::Done(Err("".to_string())));
image_bin_sent_chan.send(());
}
resource_task::Exit => {
resource_task_exited_chan.send(());
break
}
}
}
});
let image_cache_task = ImageCacheTask::new(mock_resource_task.clone());
let url = Url::parse("file:///").unwrap();
image_cache_task.send(Prefetch(url.clone()));
image_cache_task.send(Decode(url.clone()));
// Wait until our mock resource task has sent the image to the image cache
image_bin_sent.recv();
image_cache_task.send(Prefetch(url.clone()));
image_cache_task.send(Decode(url.clone()));
image_cache_task.exit();
mock_resource_task.send(resource_task::Exit);
resource_task_exited.recv();
// Our resource task should not have received another request for the image
// because it's already cached
match image_bin_sent.try_recv() {
Err(_) => (),
Ok(_) => fail!(),
}
}
#[test]
fn should_return_failed_if_image_bin_cannot_be_fetched() {
let mock_resource_task = mock_resource_task(box SendTestImageErr);
let image_cache_task = ImageCacheTask::new(mock_resource_task.clone());
let url = Url::parse("file:///").unwrap();
let join_port = image_cache_task.wait_for_store_prefetched();
image_cache_task.send(Prefetch(url.clone()));
image_cache_task.send(Decode(url.clone()));
// Wait until our mock resource task has sent the image to the image cache
join_port.recv();
let (response_chan, response_port) = comm::channel();
image_cache_task.send(GetImage(url, response_chan));
match response_port.recv() {
ImageFailed => (),
_ => fail!("bleh")
}
image_cache_task.exit();
mock_resource_task.send(resource_task::Exit);
}
#[test]
fn should_return_failed_for_multiple_get_image_requests_if_image_bin_cannot_be_fetched() {
let mock_resource_task = mock_resource_task(box SendTestImageErr);
let image_cache_task = ImageCacheTask::new(mock_resource_task.clone());
let url = Url::parse("file:///").unwrap();
let join_port = image_cache_task.wait_for_store_prefetched();
image_cache_task.send(Prefetch(url.clone()));
image_cache_task.send(Decode(url.clone()));
// Wait until our mock resource task has sent the image to the image cache
join_port.recv();
let (response_chan, response_port) = comm::channel();
image_cache_task.send(GetImage(url.clone(), response_chan));
match response_port.recv() {
ImageFailed => (),
_ => fail!("bleh")
}
// And ask again, we should get the same response
let (response_chan, response_port) = comm::channel();
image_cache_task.send(GetImage(url, response_chan));
match response_port.recv() {
ImageFailed => (),
_ => fail!("bleh")
}
image_cache_task.exit();
mock_resource_task.send(resource_task::Exit);
}
#[test]
fn should_return_failed_if_image_decode_fails() {
let mock_resource_task = mock_resource_task(box SendBogusImage);
let image_cache_task = ImageCacheTask::new(mock_resource_task.clone());
let url = Url::parse("file:///").unwrap();
let join_port = image_cache_task.wait_for_store();
image_cache_task.send(Prefetch(url.clone()));
image_cache_task.send(Decode(url.clone()));
// Wait until our mock resource task has sent the image to the image cache
join_port.recv();
// Make the request
let (response_chan, response_port) = comm::channel();
image_cache_task.send(GetImage(url, response_chan));
match response_port.recv() {
ImageFailed => (),
_ => fail!("bleh")
}
image_cache_task.exit();
mock_resource_task.send(resource_task::Exit);
}
#[test]
fn should_return_image_on_wait_if_image_is_already_loaded() {
let mock_resource_task = mock_resource_task(box SendTestImage);
let image_cache_task = ImageCacheTask::new(mock_resource_task.clone());
let url = Url::parse("file:///").unwrap();
let join_port = image_cache_task.wait_for_store();
image_cache_task.send(Prefetch(url.clone()));
image_cache_task.send(Decode(url.clone()));
// Wait until our mock resource task has sent the image to the image cache
join_port.recv();
let (response_chan, response_port) = comm::channel();
image_cache_task.send(WaitForImage(url, response_chan));
match response_port.recv() {
ImageReady(..) => (),
_ => fail!("bleh")
}
image_cache_task.exit();
mock_resource_task.send(resource_task::Exit);
}
#[test]
fn should_return_image_on_wait_if_image_is_not_yet_loaded() {
let (wait_chan, wait_port) = comm::channel();
let mock_resource_task = mock_resource_task(box WaitSendTestImage {wait_port: wait_port});
let image_cache_task = ImageCacheTask::new(mock_resource_task.clone());
let url = Url::parse("file:///").unwrap();
image_cache_task.send(Prefetch(url.clone()));
image_cache_task.send(Decode(url.clone()));
let (response_chan, response_port) = comm::channel();
image_cache_task.send(WaitForImage(url, response_chan));
wait_chan.send(());
match response_port.recv() {
ImageReady(..) => (),
_ => fail!("bleh")
}
image_cache_task.exit();
mock_resource_task.send(resource_task::Exit);
}
#[test]
fn should_return_image_failed_on_wait_if_image_fails_to_load() {
let (wait_chan, wait_port) = comm::channel();
let mock_resource_task = mock_resource_task(box WaitSendTestImageErr{wait_port: wait_port});
let image_cache_task = ImageCacheTask::new(mock_resource_task.clone());
let url = Url::parse("file:///").unwrap();
image_cache_task.send(Prefetch(url.clone()));
image_cache_task.send(Decode(url.clone()));
let (response_chan, response_port) = comm::channel();
image_cache_task.send(WaitForImage(url, response_chan));
wait_chan.send(());
match response_port.recv() {
ImageFailed => (),
_ => fail!("bleh")
}
image_cache_task.exit();
mock_resource_task.send(resource_task::Exit);
}
#[test]
fn sync_cache_should_wait_for_images() {
let mock_resource_task = mock_resource_task(box SendTestImage);
let image_cache_task = ImageCacheTask::new_sync(mock_resource_task.clone());
let url = Url::parse("file:///").unwrap();
image_cache_task.send(Prefetch(url.clone()));
image_cache_task.send(Decode(url.clone()));
let (response_chan, response_port) = comm::channel();
image_cache_task.send(GetImage(url, response_chan));
match response_port.recv() {
ImageReady(_) => (),
_ => fail!("bleh")
}
image_cache_task.exit();
mock_resource_task.send(resource_task::Exit);
}
}

44
components/net/lib.rs Normal file
View file

@ -0,0 +1,44 @@
/* 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/. */
#![feature(default_type_params, globs, managed_boxes, phase)]
extern crate debug;
extern crate collections;
extern crate geom;
extern crate http;
extern crate png;
#[phase(plugin, link)]
extern crate log;
extern crate serialize;
extern crate servo_util = "util";
extern crate stb_image;
extern crate sync;
extern crate time;
extern crate url;
/// Image handling.
///
/// It may be surprising that this goes in the network crate as opposed to the graphics crate.
/// However, image handling is generally very integrated with the network stack (especially where
/// caching is involved) and as a result it must live in here.
pub mod image {
pub mod base;
pub mod holder;
}
pub mod file_loader;
pub mod http_loader;
pub mod data_loader;
pub mod image_cache_task;
pub mod local_image_cache;
pub mod resource_task;
/// An implementation of the [Fetch spec](http://fetch.spec.whatwg.org/)
pub mod fetch {
#![allow(dead_code)] // XXXManishearth this is only temporary until the Fetch mod starts being used
pub mod request;
pub mod response;
pub mod cors_cache;
}

View file

@ -0,0 +1,166 @@
/* 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/. */
/*!
An adapter for ImageCacheTask that does local caching to avoid
extra message traffic, it also avoids waiting on the same image
multiple times and thus triggering reflows multiple times.
*/
use image_cache_task::{Decode, GetImage, ImageCacheTask, ImageFailed, ImageNotReady, ImageReady};
use image_cache_task::{ImageResponseMsg, Prefetch, WaitForImage};
use std::comm::{Receiver, channel};
use std::collections::hashmap::HashMap;
use servo_util::task::spawn_named;
use url::Url;
pub trait ImageResponder {
fn respond(&self) -> proc(ImageResponseMsg):Send;
}
pub struct LocalImageCache {
image_cache_task: ImageCacheTask,
round_number: uint,
on_image_available: Option<Box<ImageResponder+Send>>,
state_map: HashMap<Url, ImageState>
}
impl LocalImageCache {
pub fn new(image_cache_task: ImageCacheTask) -> LocalImageCache {
LocalImageCache {
image_cache_task: image_cache_task,
round_number: 1,
on_image_available: None,
state_map: HashMap::new()
}
}
}
#[deriving(Clone)]
struct ImageState {
prefetched: bool,
decoded: bool,
last_request_round: uint,
last_response: ImageResponseMsg
}
impl LocalImageCache {
/// The local cache will only do a single remote request for a given
/// URL in each 'round'. Layout should call this each time it begins
pub fn next_round(&mut self, on_image_available: Box<ImageResponder+Send>) {
self.round_number += 1;
self.on_image_available = Some(on_image_available);
}
pub fn prefetch(&mut self, url: &Url) {
{
let state = self.get_state(url);
if state.prefetched {
return
}
state.prefetched = true;
}
self.image_cache_task.send(Prefetch((*url).clone()));
}
pub fn decode(&mut self, url: &Url) {
{
let state = self.get_state(url);
if state.decoded {
return
}
state.decoded = true;
}
self.image_cache_task.send(Decode((*url).clone()));
}
// FIXME: Should return a Future
pub fn get_image(&mut self, url: &Url) -> Receiver<ImageResponseMsg> {
{
let round_number = self.round_number;
let state = self.get_state(url);
// Save the previous round number for comparison
let last_round = state.last_request_round;
// Set the current round number for this image
state.last_request_round = round_number;
match state.last_response {
ImageReady(ref image) => {
let (chan, port) = channel();
chan.send(ImageReady(image.clone()));
return port;
}
ImageNotReady => {
if last_round == round_number {
let (chan, port) = channel();
chan.send(ImageNotReady);
return port;
} else {
// We haven't requested the image from the
// remote cache this round
}
}
ImageFailed => {
let (chan, port) = channel();
chan.send(ImageFailed);
return port;
}
}
}
let (response_chan, response_port) = channel();
self.image_cache_task.send(GetImage((*url).clone(), response_chan));
let response = response_port.recv();
match response {
ImageNotReady => {
// Need to reflow when the image is available
// FIXME: Instead we should be just passing a Future
// to the caller, then to the display list. Finally,
// the compositor should be resonsible for waiting
// on the image to load and triggering layout
let image_cache_task = self.image_cache_task.clone();
assert!(self.on_image_available.is_some());
let on_image_available: proc(ImageResponseMsg):Send = self.on_image_available.as_ref().unwrap().respond();
let url = (*url).clone();
spawn_named("LocalImageCache", proc() {
let (response_chan, response_port) = channel();
image_cache_task.send(WaitForImage(url.clone(), response_chan));
on_image_available(response_port.recv());
});
}
_ => ()
}
// Put a copy of the response in the cache
let response_copy = match response {
ImageReady(ref image) => ImageReady(image.clone()),
ImageNotReady => ImageNotReady,
ImageFailed => ImageFailed
};
self.get_state(url).last_response = response_copy;
let (chan, port) = channel();
chan.send(response);
return port;
}
fn get_state<'a>(&'a mut self, url: &Url) -> &'a mut ImageState {
let state = self.state_map.find_or_insert_with(url.clone(), |_| {
let new_state = ImageState {
prefetched: false,
decoded: false,
last_request_round: 0,
last_response: ImageNotReady
};
new_state
});
state
}
}

View file

@ -0,0 +1,267 @@
/* 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 task that takes a URL and streams back the binary data.
use file_loader;
use http_loader;
use data_loader;
use std::comm::{channel, Receiver, Sender};
use std::task::TaskBuilder;
use std::os;
use http::headers::content_type::MediaType;
use ResponseHeaderCollection = http::headers::response::HeaderCollection;
use RequestHeaderCollection = http::headers::request::HeaderCollection;
use http::method::{Method, Get};
use url::Url;
use StatusOk = http::status::Ok;
use http::status::Status;
pub enum ControlMsg {
/// Request the data associated with a particular URL
Load(LoadData, Sender<LoadResponse>),
Exit
}
#[deriving(Clone)]
pub struct LoadData {
pub url: Url,
pub method: Method,
pub headers: RequestHeaderCollection,
pub data: Option<Vec<u8>>,
pub cors: Option<ResourceCORSData>
}
impl LoadData {
pub fn new(url: Url) -> LoadData {
LoadData {
url: url,
method: Get,
headers: RequestHeaderCollection::new(),
data: None,
cors: None
}
}
}
#[deriving(Clone)]
pub struct ResourceCORSData {
/// CORS Preflight flag
pub preflight: bool,
/// Origin of CORS Request
pub origin: Url
}
/// Metadata about a loaded resource, such as is obtained from HTTP headers.
pub struct Metadata {
/// Final URL after redirects.
pub final_url: Url,
/// MIME type / subtype.
pub content_type: Option<(String, String)>,
/// Character set.
pub charset: Option<String>,
/// Headers
pub headers: Option<ResponseHeaderCollection>,
/// HTTP Status
pub status: Status
}
impl Metadata {
/// Metadata with defaults for everything optional.
pub fn default(url: Url) -> Metadata {
Metadata {
final_url: url,
content_type: None,
charset: None,
headers: None,
status: StatusOk // http://fetch.spec.whatwg.org/#concept-response-status-message
}
}
/// Extract the parts of a MediaType that we care about.
pub fn set_content_type(&mut self, content_type: &Option<MediaType>) {
match *content_type {
None => (),
Some(MediaType { type_: ref type_,
subtype: ref subtype,
parameters: ref parameters }) => {
self.content_type = Some((type_.clone(), subtype.clone()));
for &(ref k, ref v) in parameters.iter() {
if "charset" == k.as_slice() {
self.charset = Some(v.clone());
}
}
}
}
}
}
/// Message sent in response to `Load`. Contains metadata, and a port
/// for receiving the data.
///
/// Even if loading fails immediately, we send one of these and the
/// progress_port will provide the error.
pub struct LoadResponse {
/// Metadata, such as from HTTP headers.
pub metadata: Metadata,
/// Port for reading data.
pub progress_port: Receiver<ProgressMsg>,
}
/// Messages sent in response to a `Load` message
#[deriving(PartialEq,Show)]
pub enum ProgressMsg {
/// Binary data - there may be multiple of these
Payload(Vec<u8>),
/// Indicates loading is complete, either successfully or not
Done(Result<(), String>)
}
/// For use by loaders in responding to a Load message.
pub fn start_sending(start_chan: Sender<LoadResponse>, metadata: Metadata) -> Sender<ProgressMsg> {
start_sending_opt(start_chan, metadata).ok().unwrap()
}
/// For use by loaders in responding to a Load message.
pub fn start_sending_opt(start_chan: Sender<LoadResponse>, metadata: Metadata) -> Result<Sender<ProgressMsg>, ()> {
let (progress_chan, progress_port) = channel();
let result = start_chan.send_opt(LoadResponse {
metadata: metadata,
progress_port: progress_port,
});
match result {
Ok(_) => Ok(progress_chan),
Err(_) => Err(())
}
}
/// Convenience function for synchronously loading a whole resource.
pub fn load_whole_resource(resource_task: &ResourceTask, url: Url)
-> Result<(Metadata, Vec<u8>), String> {
let (start_chan, start_port) = channel();
resource_task.send(Load(LoadData::new(url), start_chan));
let response = start_port.recv();
let mut buf = vec!();
loop {
match response.progress_port.recv() {
Payload(data) => buf.push_all(data.as_slice()),
Done(Ok(())) => return Ok((response.metadata, buf)),
Done(Err(e)) => return Err(e)
}
}
}
/// Handle to a resource task
pub type ResourceTask = Sender<ControlMsg>;
pub type LoaderTask = proc(load_data: LoadData, Sender<LoadResponse>);
/**
Creates a task to load a specific resource
The ResourceManager delegates loading to a different type of loader task for
each URL scheme
*/
type LoaderTaskFactory = extern "Rust" fn() -> LoaderTask;
/// Create a ResourceTask
pub fn new_resource_task() -> ResourceTask {
let (setup_chan, setup_port) = channel();
let builder = TaskBuilder::new().named("ResourceManager");
builder.spawn(proc() {
ResourceManager::new(setup_port).start();
});
setup_chan
}
struct ResourceManager {
from_client: Receiver<ControlMsg>,
}
impl ResourceManager {
fn new(from_client: Receiver<ControlMsg>) -> ResourceManager {
ResourceManager {
from_client : from_client,
}
}
}
impl ResourceManager {
fn start(&self) {
loop {
match self.from_client.recv() {
Load(load_data, start_chan) => {
self.load(load_data, start_chan)
}
Exit => {
break
}
}
}
}
fn load(&self, mut load_data: LoadData, start_chan: Sender<LoadResponse>) {
let loader = match load_data.url.scheme.as_slice() {
"file" => file_loader::factory(),
"http" | "https" => http_loader::factory(),
"data" => data_loader::factory(),
"about" => {
match load_data.url.non_relative_scheme_data().unwrap() {
"crash" => fail!("Loading the about:crash URL."),
"failure" => {
// FIXME: Find a way to load this without relying on the `../src` directory.
let mut path = os::self_exe_path().expect("can't get exe path");
path.pop();
path.push_many(["src", "test", "html", "failure.html"]);
load_data.url = Url::from_file_path(&path).unwrap();
file_loader::factory()
}
_ => {
start_sending(start_chan, Metadata::default(load_data.url))
.send(Done(Err("Unknown about: URL.".to_string())));
return
}
}
},
_ => {
debug!("resource_task: no loader for scheme {:s}", load_data.url.scheme);
start_sending(start_chan, Metadata::default(load_data.url))
.send(Done(Err("no loader for scheme".to_string())));
return
}
};
debug!("resource_task: loading url: {:s}", load_data.url.serialize());
loader(load_data, start_chan);
}
}
#[test]
fn test_exit() {
let resource_task = new_resource_task();
resource_task.send(Exit);
}
#[test]
fn test_bad_scheme() {
let resource_task = new_resource_task();
let (start_chan, start) = channel();
let url = Url::parse("bogus://whatever").unwrap();
resource_task.send(Load(LoadData::new(url), start_chan));
let response = start.recv();
match response.progress_port.recv() {
Done(result) => { assert!(result.is_err()) }
_ => fail!("bleh")
}
resource_task.send(Exit);
}

View file

@ -0,0 +1,56 @@
[package]
name = "script"
version = "0.0.1"
authors = ["The Servo Project Developers"]
build = "make -f makefile.cargo"
[lib]
name = "script"
path = "lib.rs"
[dependencies.macros]
path = "../macros"
[dependencies.util]
path = "../util"
[dependencies.msg]
path = "../msg"
[dependencies.net]
path = "../net"
[dependencies.script_traits]
path = "../script_traits"
[dependencies.style]
path = "../style"
[dependencies.gfx]
path = "../gfx"
[dependencies.canvas]
path = "../canvas"
[dependencies.cssparser]
git = "https://github.com/servo/rust-cssparser"
[dependencies.geom]
git = "https://github.com/servo/rust-geom"
[dependencies.hubbub]
git = "https://github.com/servo/rust-hubbub"
[dependencies.encoding]
git = "https://github.com/lifthrasiir/rust-encoding"
[dependencies.http]
git = "https://github.com/servo/rust-http"
branch = "servo"
[dependencies.js]
git = "https://github.com/servo/rust-mozjs"
[dependencies.url]
git = "https://github.com/servo/rust-url"

Some files were not shown because too many files have changed in this diff Show more