mirror of
https://github.com/servo/servo.git
synced 2025-08-05 21:50:18 +01:00
Merge onto mozilla/servo master
This commit is contained in:
commit
ec4ba07109
52 changed files with 1157 additions and 580 deletions
13
configure
vendored
13
configure
vendored
|
@ -503,14 +503,17 @@ do
|
|||
# needed because Azure's configure wants "--enable-skia"
|
||||
CONFIGURE_ARGS=""
|
||||
ENV_VARS=""
|
||||
if [ $i = "rust-azure" ]; then
|
||||
if [ $i = "support/azure/rust-azure" ]; then
|
||||
CONFIGURE_ARGS="--enable-skia"
|
||||
fi
|
||||
if [ $i = mozjs ]; then
|
||||
if [ ! -z $CFG_ENABLE_DEBUG ]; then
|
||||
CONFIGURE_ARGS="--enable-debug"
|
||||
fi
|
||||
if [ $i = "support/nss/nspr" ]; then
|
||||
CONFIGURE_ARGS="--enable-64bit"
|
||||
fi
|
||||
if [ $i = "support/spidermonkey/mozjs" ]; then
|
||||
if [ ! -z $CFG_ENABLE_DEBUG ]; then
|
||||
CONFIGURE_ARGS="--enable-debug"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -f ${CONFIGURE_SCRIPT} ]
|
||||
then
|
||||
|
|
|
@ -26,8 +26,10 @@ reftest: $(S)src/test/harness/reftest/reftest.rs servo
|
|||
contenttest: $(S)src/test/harness/contenttest/contenttest.rs servo
|
||||
$(RUSTC) $(RFLAGS_servo) -o $@ $< -L .
|
||||
|
||||
DEPS_CHECK_TARGETS_ALL = $(addprefix check-,$(DEPS_CHECK_ALL))
|
||||
DEPS_CHECK_TARGETS_FAST = $(addprefix check-,$(filter-out $(SLOW_TESTS),$(DEPS_CHECK_ALL)))
|
||||
|
||||
DEPS_CHECK_TESTABLE = $(filter-out $(NO_TESTS),$(DEPS_CHECK_ALL))
|
||||
DEPS_CHECK_TARGETS_ALL = $(addprefix check-,$(DEPS_CHECK_TESTABLE))
|
||||
DEPS_CHECK_TARGETS_FAST = $(addprefix check-,$(filter-out $(SLOW_TESTS),$(DEPS_CHECK_TESTABLE)))
|
||||
|
||||
.PHONY: check $(DEPS_CHECK_TARGETS_ALL)
|
||||
|
||||
|
|
15
mk/sub.mk
15
mk/sub.mk
|
@ -3,6 +3,12 @@ SLOW_TESTS += \
|
|||
mozjs \
|
||||
$(NULL)
|
||||
|
||||
# Tests for these submodules do not exist.
|
||||
NO_TESTS += \
|
||||
nspr \
|
||||
nss \
|
||||
$(NULL)
|
||||
|
||||
# These submodules will not be cleaned by the `make clean-fast` target.
|
||||
SLOW_BUILDS += \
|
||||
libcss \
|
||||
|
@ -21,8 +27,8 @@ NATIVE_BUILDS += \
|
|||
libwapcaplet \
|
||||
mozjs \
|
||||
skia \
|
||||
nss \
|
||||
nspr \
|
||||
nss \
|
||||
nspr \
|
||||
$(NULL)
|
||||
|
||||
# NOTE: the make magic can only compute transitive build dependencies,
|
||||
|
@ -42,6 +48,7 @@ DEPS_rust-glut += \
|
|||
|
||||
DEPS_rust-layers += \
|
||||
rust-azure \
|
||||
rust-cocoa \
|
||||
rust-geom \
|
||||
rust-glut \
|
||||
rust-opengles \
|
||||
|
@ -130,6 +137,10 @@ DEPS_rust-layers += \
|
|||
rust-core-text \
|
||||
$(NULL)
|
||||
|
||||
DEPS_rust-glut += \
|
||||
rust-cocoa \
|
||||
$(NULL)
|
||||
|
||||
endif
|
||||
|
||||
ifeq ($(CFG_OSTYPE),unknown-linux-gnu)
|
||||
|
|
|
@ -12,6 +12,8 @@ pub struct LayerBuffer {
|
|||
// The rect in the containing RenderLayer that this represents.
|
||||
rect: Rect<uint>,
|
||||
|
||||
screen_pos: Rect<uint>,
|
||||
|
||||
// NB: stride is in pixels, like OpenGL GL_UNPACK_ROW_LENGTH.
|
||||
stride: uint
|
||||
}
|
||||
|
@ -22,9 +24,17 @@ pub struct LayerBufferSet {
|
|||
buffers: ~[LayerBuffer]
|
||||
}
|
||||
|
||||
/// The status of the renderer.
|
||||
#[deriving(Eq)]
|
||||
pub enum RenderState {
|
||||
IdleRenderState,
|
||||
RenderingRenderState,
|
||||
}
|
||||
|
||||
/// The interface used to by the renderer to acquire draw targets for each rendered frame and
|
||||
/// submit them to be drawn to the display.
|
||||
pub trait Compositor {
|
||||
fn paint(&self, layer_buffer_set: LayerBufferSet, new_size: Size2D<uint>);
|
||||
fn set_render_state(&self, render_state: RenderState);
|
||||
}
|
||||
|
||||
|
|
|
@ -18,6 +18,8 @@ use azure::scaled_font::ScaledFont;
|
|||
use azure::azure_hl::{BackendType, ColorPattern};
|
||||
use geom::{Point2D, Rect, Size2D};
|
||||
|
||||
use servo_util::time::ProfilerChan;
|
||||
|
||||
// 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
|
||||
|
@ -210,13 +212,15 @@ pub struct Font {
|
|||
style: UsedFontStyle,
|
||||
metrics: FontMetrics,
|
||||
backend: BackendType,
|
||||
profiler_chan: ProfilerChan,
|
||||
}
|
||||
|
||||
pub impl Font {
|
||||
fn new_from_buffer(ctx: &FontContext,
|
||||
buffer: ~[u8],
|
||||
style: &SpecifiedFontStyle,
|
||||
backend: BackendType)
|
||||
backend: BackendType,
|
||||
profiler_chan: ProfilerChan)
|
||||
-> Result<@mut Font, ()> {
|
||||
let handle = FontHandleMethods::new_from_buffer(&ctx.handle, buffer, style);
|
||||
let handle: FontHandle = if handle.is_ok() {
|
||||
|
@ -235,11 +239,13 @@ pub impl Font {
|
|||
style: copy *style,
|
||||
metrics: metrics,
|
||||
backend: backend,
|
||||
profiler_chan: profiler_chan,
|
||||
});
|
||||
}
|
||||
|
||||
fn new_from_adopted_handle(_fctx: &FontContext, handle: FontHandle,
|
||||
style: &SpecifiedFontStyle, backend: BackendType) -> @mut Font {
|
||||
style: &SpecifiedFontStyle, backend: BackendType,
|
||||
profiler_chan: ProfilerChan) -> @mut Font {
|
||||
let metrics = handle.get_metrics();
|
||||
|
||||
@mut Font {
|
||||
|
@ -249,11 +255,13 @@ pub impl Font {
|
|||
style: copy *style,
|
||||
metrics: metrics,
|
||||
backend: backend,
|
||||
profiler_chan: profiler_chan,
|
||||
}
|
||||
}
|
||||
|
||||
fn new_from_existing_handle(fctx: &FontContext, handle: &FontHandle,
|
||||
style: &SpecifiedFontStyle, backend: BackendType) -> Result<@mut Font,()> {
|
||||
style: &SpecifiedFontStyle, backend: BackendType,
|
||||
profiler_chan: ProfilerChan) -> Result<@mut Font,()> {
|
||||
|
||||
// TODO(Issue #179): convert between specified and used font style here?
|
||||
let styled_handle = match handle.clone_with_style(&fctx.handle, style) {
|
||||
|
@ -261,7 +269,7 @@ pub impl Font {
|
|||
Err(()) => return Err(())
|
||||
};
|
||||
|
||||
return Ok(Font::new_from_adopted_handle(fctx, styled_handle, style, backend));
|
||||
return Ok(Font::new_from_adopted_handle(fctx, styled_handle, style, backend, profiler_chan));
|
||||
}
|
||||
|
||||
priv fn get_shaper(@mut self) -> @Shaper {
|
||||
|
|
|
@ -40,17 +40,18 @@ pub struct FontContext {
|
|||
handle: FontContextHandle,
|
||||
backend: BackendType,
|
||||
generic_fonts: HashMap<~str,~str>,
|
||||
profiler_chan: ProfilerChan,
|
||||
}
|
||||
|
||||
#[allow(non_implicitly_copyable_typarams)]
|
||||
pub impl<'self> FontContext {
|
||||
fn new(backend: BackendType,
|
||||
needs_font_list: bool,
|
||||
prof_chan: ProfilerChan)
|
||||
profiler_chan: ProfilerChan)
|
||||
-> FontContext {
|
||||
let handle = FontContextHandle::new();
|
||||
let font_list = if needs_font_list {
|
||||
Some(FontList::new(&handle, prof_chan.clone())) }
|
||||
Some(FontList::new(&handle, profiler_chan.clone())) }
|
||||
else { None };
|
||||
|
||||
// TODO: Allow users to specify these.
|
||||
|
@ -69,6 +70,7 @@ pub impl<'self> FontContext {
|
|||
handle: handle,
|
||||
backend: backend,
|
||||
generic_fonts: generic_fonts,
|
||||
profiler_chan: profiler_chan,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -112,20 +114,21 @@ pub impl<'self> FontContext {
|
|||
|
||||
debug!("(create font group) --- starting ---");
|
||||
|
||||
let list = self.get_font_list();
|
||||
|
||||
// TODO(Issue #193): make iteration over 'font-family' more robust.
|
||||
for str::each_split_char(style.families, ',') |family| {
|
||||
let family_name = str::trim(family);
|
||||
let transformed_family_name = self.transform_family(family_name);
|
||||
debug!("(create font group) transformed family is `%s`", transformed_family_name);
|
||||
|
||||
let list = self.get_font_list();
|
||||
|
||||
let result = list.find_font_in_family(transformed_family_name, style);
|
||||
let mut found = false;
|
||||
for result.each |font_entry| {
|
||||
found = true;
|
||||
// TODO(Issue #203): route this instantion through FontContext's Font instance cache.
|
||||
let instance = Font::new_from_existing_handle(self, &font_entry.handle, style, self.backend);
|
||||
let instance = Font::new_from_existing_handle(self, &font_entry.handle, style, self.backend,
|
||||
self.profiler_chan.clone());
|
||||
do result::iter(&instance) |font: &@mut Font| { fonts.push(*font); }
|
||||
};
|
||||
|
||||
|
@ -134,6 +137,22 @@ pub impl<'self> FontContext {
|
|||
}
|
||||
}
|
||||
|
||||
let last_resort = FontList::get_last_resort_font_families();
|
||||
|
||||
for last_resort.each |family| {
|
||||
let result = list.find_font_in_family(*family,style);
|
||||
for result.each |font_entry| {
|
||||
let instance = Font::new_from_existing_handle(self,
|
||||
&font_entry.handle,
|
||||
style,
|
||||
self.backend,
|
||||
self.profiler_chan.clone());
|
||||
do result::iter(&instance) |font: &@mut Font| {
|
||||
fonts.push(*font);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assert!(fonts.len() > 0);
|
||||
// TODO(Issue #179): Split FontStyle into specified and used styles
|
||||
let used_style = copy *style;
|
||||
|
@ -153,7 +172,8 @@ pub impl<'self> FontContext {
|
|||
Ok(Font::new_from_adopted_handle(self,
|
||||
handle,
|
||||
&desc.style,
|
||||
self.backend))
|
||||
self.backend,
|
||||
self.profiler_chan.clone()))
|
||||
})
|
||||
}
|
||||
};
|
||||
|
|
|
@ -19,6 +19,7 @@ pub type FontFamilyMap = HashMap<~str, @mut FontFamily>;
|
|||
trait FontListHandleMethods {
|
||||
fn get_available_families(&self, fctx: &FontContextHandle) -> FontFamilyMap;
|
||||
fn load_variations_for_family(&self, family: @mut FontFamily);
|
||||
fn get_last_resort_font_families() -> ~[~str];
|
||||
}
|
||||
|
||||
/// The platform-independent font list abstraction.
|
||||
|
@ -86,6 +87,11 @@ pub impl FontList {
|
|||
// TODO(Issue #188): look up localized font family names if canonical name not found
|
||||
family.map(|f| **f)
|
||||
}
|
||||
|
||||
pub fn get_last_resort_font_families() -> ~[~str] {
|
||||
let last_resort = FontListHandle::get_last_resort_font_families();
|
||||
last_resort
|
||||
}
|
||||
}
|
||||
|
||||
// Holds a specific font family, and the various
|
||||
|
|
|
@ -13,6 +13,11 @@ pub struct Opts {
|
|||
render_backend: BackendType,
|
||||
n_render_threads: uint,
|
||||
tile_size: uint,
|
||||
profiler_period: Option<f64>,
|
||||
|
||||
/// A scale factor to apply to tiles, to allow rendering tiles at higher resolutions for
|
||||
/// testing pan and zoom code.
|
||||
zoom: uint,
|
||||
}
|
||||
|
||||
#[allow(non_implicitly_copyable_typarams)]
|
||||
|
@ -26,13 +31,14 @@ pub fn from_cmdline_args(args: &[~str]) -> Opts {
|
|||
getopts::optopt(~"r"), // rendering backend
|
||||
getopts::optopt(~"s"), // size of tiles
|
||||
getopts::optopt(~"t"), // threads to render with
|
||||
getopts::optflagopt(~"p"), // profiler flag and output interval
|
||||
getopts::optopt(~"z"), // zoom level
|
||||
];
|
||||
|
||||
let opt_match = match getopts::getopts(args, opts) {
|
||||
result::Ok(m) => { copy m }
|
||||
result::Err(f) => { fail!(getopts::fail_str(copy f)) }
|
||||
};
|
||||
|
||||
let urls = if opt_match.free.is_empty() {
|
||||
fail!(~"servo asks that you provide 1 or more URLs")
|
||||
} else {
|
||||
|
@ -68,10 +74,24 @@ pub fn from_cmdline_args(args: &[~str]) -> Opts {
|
|||
None => 1, // FIXME: Number of cores.
|
||||
};
|
||||
|
||||
let profiler_period: Option<f64> =
|
||||
// if only flag is present, default to 5 second period
|
||||
match getopts::opt_default(&opt_match, ~"p", ~"5") {
|
||||
Some(period) => Some(f64::from_str(period).get()),
|
||||
None => None,
|
||||
};
|
||||
|
||||
let zoom: uint = match getopts::opt_maybe_str(&opt_match, ~"z") {
|
||||
Some(zoom_str) => uint::from_str(zoom_str).get(),
|
||||
None => 1,
|
||||
};
|
||||
|
||||
Opts {
|
||||
urls: urls,
|
||||
render_backend: render_backend,
|
||||
n_render_threads: n_render_threads,
|
||||
tile_size: tile_size,
|
||||
profiler_period: profiler_period,
|
||||
zoom: zoom,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -125,6 +125,10 @@ pub impl FontListHandle {
|
|||
FcObjectSetDestroy(object_set);
|
||||
}
|
||||
}
|
||||
|
||||
fn get_last_resort_font_families() -> ~[~str] {
|
||||
~[~"Arial"]
|
||||
}
|
||||
}
|
||||
|
||||
struct AutoPattern {
|
||||
|
|
|
@ -55,4 +55,8 @@ pub impl FontListHandle {
|
|||
family.entries.push(entry)
|
||||
}
|
||||
}
|
||||
|
||||
fn get_last_resort_font_families() -> ~[~str] {
|
||||
~[~"Arial Unicode MS",~"Arial"]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,6 +37,7 @@ pub fn render_layers(layer_ref: *RenderLayer,
|
|||
f: RenderFn)
|
||||
-> LayerBufferSet {
|
||||
let tile_size = opts.tile_size;
|
||||
let scale = opts.zoom;
|
||||
|
||||
// FIXME: Try not to create a new array here.
|
||||
let mut new_buffer_ports = ~[];
|
||||
|
@ -45,12 +46,12 @@ pub fn render_layers(layer_ref: *RenderLayer,
|
|||
do time::profile(time::RenderingPrepBuffCategory, prof_chan.clone()) {
|
||||
let layer: &RenderLayer = unsafe { cast::transmute(layer_ref) };
|
||||
let mut y = 0;
|
||||
while y < layer.size.height {
|
||||
while y < layer.size.height * scale {
|
||||
let mut x = 0;
|
||||
while x < layer.size.width {
|
||||
while x < layer.size.width * scale {
|
||||
// Figure out the dimension of this tile.
|
||||
let right = uint::min(x + tile_size, layer.size.width);
|
||||
let bottom = uint::min(y + tile_size, layer.size.height);
|
||||
let right = uint::min(x + tile_size, layer.size.width * scale);
|
||||
let bottom = uint::min(y + tile_size, layer.size.height * scale);
|
||||
let width = right - x;
|
||||
let height = bottom - y;
|
||||
|
||||
|
@ -65,7 +66,8 @@ pub fn render_layers(layer_ref: *RenderLayer,
|
|||
|
||||
debug!("tile aligned_width %u", aligned_width);
|
||||
|
||||
let tile_rect = Rect(Point2D(x, y), Size2D(aligned_width, height));
|
||||
let tile_rect = Rect(Point2D(x / scale, y / scale), Size2D(aligned_width, height)); //change this
|
||||
let screen_rect = Rect(Point2D(x, y), Size2D(aligned_width, height)); //change this
|
||||
|
||||
let buffer;
|
||||
// FIXME: Try harder to search for a matching tile.
|
||||
|
@ -112,6 +114,7 @@ pub fn render_layers(layer_ref: *RenderLayer,
|
|||
stride,
|
||||
B8G8R8A8),
|
||||
rect: tile_rect,
|
||||
screen_pos: screen_rect,
|
||||
stride: stride as uint
|
||||
};
|
||||
//}
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
// The task that handles all rendering/painting.
|
||||
|
||||
use azure::AzFloat;
|
||||
use compositor::Compositor;
|
||||
use compositor::{Compositor, IdleRenderState, RenderingRenderState};
|
||||
use font_context::FontContext;
|
||||
use geom::matrix2d::Matrix2D;
|
||||
use opts::Opts;
|
||||
|
@ -18,9 +18,7 @@ use core::task::SingleThreaded;
|
|||
use std::task_pool::TaskPool;
|
||||
use servo_net::util::spawn_listener;
|
||||
|
||||
use servo_util::time::ProfilerChan;
|
||||
use servo_util::time::profile;
|
||||
use servo_util::time::time;
|
||||
use servo_util::time::{ProfilerChan, profile};
|
||||
use servo_util::time;
|
||||
|
||||
pub enum Msg {
|
||||
|
@ -124,7 +122,8 @@ impl<C: Compositor + Owned> Renderer<C> {
|
|||
|
||||
fn render(&mut self, render_layer: RenderLayer) {
|
||||
debug!("renderer: rendering");
|
||||
do time("rendering") {
|
||||
self.compositor.set_render_state(RenderingRenderState);
|
||||
do profile(time::RenderingCategory, self.profiler_chan.clone()) {
|
||||
let layer_buffer_set = do render_layers(&render_layer,
|
||||
&self.opts,
|
||||
self.profiler_chan.clone()) |render_layer_ref,
|
||||
|
@ -142,17 +141,24 @@ impl<C: Compositor + Owned> Renderer<C> {
|
|||
|
||||
// Apply the translation to render the tile we want.
|
||||
let matrix: Matrix2D<AzFloat> = Matrix2D::identity();
|
||||
let matrix = matrix.translate(&-(layer_buffer.rect.origin.x as AzFloat),
|
||||
&-(layer_buffer.rect.origin.y as AzFloat));
|
||||
let scale = thread_render_context.opts.zoom as f32;
|
||||
|
||||
let matrix = matrix.scale(scale as AzFloat, scale as AzFloat);
|
||||
let matrix = matrix.translate(-(layer_buffer.rect.origin.x as f32) as AzFloat,
|
||||
-(layer_buffer.rect.origin.y as f32) as AzFloat);
|
||||
|
||||
|
||||
layer_buffer.draw_target.set_transform(&matrix);
|
||||
|
||||
// Clear the buffer.
|
||||
ctx.clear();
|
||||
|
||||
|
||||
// Draw the display list.
|
||||
let render_layer: &RenderLayer = unsafe {
|
||||
cast::transmute(render_layer_ref)
|
||||
};
|
||||
|
||||
render_layer.display_list.draw_into_context(&ctx);
|
||||
}
|
||||
|
||||
|
@ -163,6 +169,7 @@ impl<C: Compositor + Owned> Renderer<C> {
|
|||
|
||||
debug!("renderer: returning surface");
|
||||
self.compositor.paint(layer_buffer_set, render_layer.size);
|
||||
self.compositor.set_render_state(IdleRenderState);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,8 @@ use font_context::FontContext;
|
|||
use geometry::Au;
|
||||
use text::glyph::{BreakTypeNormal, GlyphStore};
|
||||
use font::{Font, FontDescriptor, RunMetrics};
|
||||
use servo_util::time;
|
||||
use servo_util::time::profile;
|
||||
use servo_util::range::Range;
|
||||
|
||||
/// A text run.
|
||||
|
@ -44,7 +46,9 @@ pub impl<'self> TextRun {
|
|||
fn new(font: @mut Font, text: ~str, underline: bool) -> TextRun {
|
||||
let mut glyph_store = GlyphStore::new(str::char_len(text));
|
||||
TextRun::compute_potential_breaks(text, &mut glyph_store);
|
||||
font.shape_text(text, &mut glyph_store);
|
||||
do profile(time::LayoutShapingCategory, font.profiler_chan.clone()) {
|
||||
font.shape_text(text, &mut glyph_store);
|
||||
}
|
||||
|
||||
let run = TextRun {
|
||||
text: text,
|
||||
|
|
|
@ -5,8 +5,13 @@
|
|||
use compositing::resize_rate_limiter::ResizeRateLimiter;
|
||||
use platform::{Application, Window};
|
||||
use script::script_task::{LoadMsg, ScriptMsg, SendEventMsg};
|
||||
use windowing::{ApplicationMethods, WindowMethods};
|
||||
use script::dom::event::ClickEvent;
|
||||
use windowing::{ApplicationMethods, WindowMethods, WindowMouseEvent, WindowClickEvent};
|
||||
use windowing::{WindowMouseDownEvent, WindowMouseUpEvent};
|
||||
|
||||
use gfx::compositor::RenderState;
|
||||
use script::dom::event::{Event, ClickEvent, MouseDownEvent, MouseUpEvent};
|
||||
use script::compositor_interface::{ReadyState, CompositorInterface};
|
||||
use script::compositor_interface;
|
||||
|
||||
use azure::azure_hl::{DataSourceSurface, DrawTarget, SourceSurfaceMethods};
|
||||
use core::cell::Cell;
|
||||
|
@ -16,7 +21,7 @@ use core::util;
|
|||
use geom::matrix::identity;
|
||||
use geom::point::Point2D;
|
||||
use geom::size::Size2D;
|
||||
use gfx::compositor::{Compositor, LayerBufferSet};
|
||||
use gfx::compositor::{Compositor, LayerBufferSet, RenderState};
|
||||
use layers::layers::{ARGB32Format, BasicImageData, ContainerLayer, ContainerLayerKind, Format};
|
||||
use layers::layers::{Image, ImageData, ImageLayer, ImageLayerKind, RGB24Format, WithDataFn};
|
||||
use layers::rendergl;
|
||||
|
@ -34,11 +39,17 @@ pub struct CompositorTask {
|
|||
chan: SharedChan<Msg>,
|
||||
}
|
||||
|
||||
impl CompositorInterface for CompositorTask {
|
||||
fn set_ready_state(&self, ready_state: ReadyState) {
|
||||
let msg = ChangeReadyState(ready_state);
|
||||
self.chan.send(msg);
|
||||
}
|
||||
}
|
||||
|
||||
impl CompositorTask {
|
||||
/// Starts the compositor. Returns an interface that can be used to communicate with the
|
||||
/// compositor and a port which allows notification when the compositor shuts down.
|
||||
pub fn new(script_chan: SharedChan<ScriptMsg>,
|
||||
profiler_chan: ProfilerChan)
|
||||
pub fn new(script_chan: SharedChan<ScriptMsg>, profiler_chan: ProfilerChan)
|
||||
-> (CompositorTask, Port<()>) {
|
||||
let script_chan = Cell(script_chan);
|
||||
let (shutdown_port, shutdown_chan) = stream();
|
||||
|
@ -61,10 +72,14 @@ impl CompositorTask {
|
|||
|
||||
/// Messages to the compositor.
|
||||
pub enum Msg {
|
||||
/// Requests that the compositor paint the given layer buffer set for the given page size.
|
||||
Paint(LayerBufferSet, Size2D<uint>),
|
||||
/// Requests that the compositor shut down.
|
||||
Exit,
|
||||
/// Requests that the compositor paint the given layer buffer set for the given page size.
|
||||
Paint(LayerBufferSet, Size2D<uint>),
|
||||
/// Alerts the compositor to the current status of page loading.
|
||||
ChangeReadyState(ReadyState),
|
||||
/// Alerts the compositor to the current status of rendering.
|
||||
ChangeRenderState(RenderState),
|
||||
}
|
||||
|
||||
/// Azure surface wrapping to work with the layers infrastructure.
|
||||
|
@ -132,12 +147,14 @@ fn run_main_loop(port: Port<Msg>,
|
|||
let check_for_messages: @fn() = || {
|
||||
// Periodically check if the script task responded to our last resize event
|
||||
resize_rate_limiter.check_resize_response();
|
||||
|
||||
// Handle messages
|
||||
while port.peek() {
|
||||
match port.recv() {
|
||||
Exit => *done = true,
|
||||
|
||||
ChangeReadyState(ready_state) => window.set_ready_state(ready_state),
|
||||
ChangeRenderState(render_state) => window.set_render_state(render_state),
|
||||
|
||||
Paint(new_layer_buffer_set, new_size) => {
|
||||
debug!("osmain: received new frame");
|
||||
|
||||
|
@ -186,7 +203,7 @@ fn run_main_loop(port: Port<Msg>,
|
|||
Some(_) => fail!(~"found unexpected layer kind"),
|
||||
};
|
||||
|
||||
let origin = buffer.rect.origin;
|
||||
let origin = buffer.screen_pos.origin;
|
||||
let origin = Point2D(origin.x as f32, origin.y as f32);
|
||||
|
||||
// Set the layer's transform.
|
||||
|
@ -236,12 +253,24 @@ fn run_main_loop(port: Port<Msg>,
|
|||
|
||||
let script_chan_clone = script_chan.clone();
|
||||
|
||||
// When the user clicks, perform hit testing
|
||||
do window.set_click_callback |layer_click_point| {
|
||||
let world_click_point = layer_click_point + *world_offset;
|
||||
debug!("osmain: clicked at %?", world_click_point);
|
||||
|
||||
script_chan_clone.send(SendEventMsg(ClickEvent(world_click_point)));
|
||||
// When the user triggers a mouse event, perform appropriate hit testing
|
||||
do window.set_mouse_callback |window_mouse_event: WindowMouseEvent| {
|
||||
let event: Event;
|
||||
let world_mouse_point = |layer_mouse_point: Point2D<f32>| {
|
||||
layer_mouse_point + *world_offset
|
||||
};
|
||||
match window_mouse_event {
|
||||
WindowClickEvent(button, layer_mouse_point) => {
|
||||
event = ClickEvent(button, world_mouse_point(layer_mouse_point));
|
||||
}
|
||||
WindowMouseDownEvent(button, layer_mouse_point) => {
|
||||
event = MouseDownEvent(button, world_mouse_point(layer_mouse_point));
|
||||
}
|
||||
WindowMouseUpEvent(button, layer_mouse_point) => {
|
||||
event = MouseUpEvent(button, world_mouse_point(layer_mouse_point));
|
||||
}
|
||||
}
|
||||
script_chan_clone.send(SendEventMsg(event));
|
||||
}
|
||||
|
||||
// When the user scrolls, move the layer around.
|
||||
|
@ -276,12 +305,11 @@ fn run_main_loop(port: Port<Msg>,
|
|||
|
||||
|
||||
// When the user pinch-zooms, scale the layer
|
||||
do window.set_zoom_callback |delta| {
|
||||
let zoom_const = 0.01;
|
||||
do window.set_zoom_callback |magnification| {
|
||||
let old_world_zoom = *world_zoom;
|
||||
|
||||
// Determine zoom amount
|
||||
*world_zoom = (*world_zoom + delta.y * zoom_const).max(&1.0);
|
||||
*world_zoom = (*world_zoom * magnification).max(&1.0);
|
||||
|
||||
// Update world offset
|
||||
let corner_to_center_x = world_offset.x + window_size.width as f32 / 2f32;
|
||||
|
@ -331,6 +359,9 @@ impl Compositor for CompositorTask {
|
|||
fn paint(&self, layer_buffer_set: LayerBufferSet, new_size: Size2D<uint>) {
|
||||
self.chan.send(Paint(layer_buffer_set, new_size))
|
||||
}
|
||||
fn set_render_state(&self, render_state: RenderState) {
|
||||
self.chan.send(ChangeRenderState(render_state))
|
||||
}
|
||||
}
|
||||
|
||||
/// A function for spawning into the platform's main thread.
|
||||
|
|
|
@ -124,6 +124,8 @@ ul, ol, dl { page-break-before: avoid }
|
|||
|
||||
/* Servo additions */
|
||||
:link { color: blue }
|
||||
script { display: none }
|
||||
style { display: none }
|
||||
"
|
||||
}
|
||||
|
||||
|
|
|
@ -86,6 +86,36 @@ impl SelectHandler<AbstractNode<LayoutView>> for NodeSelectHandler {
|
|||
}
|
||||
}
|
||||
|
||||
fn with_node_classes<R>(&self, node: &AbstractNode<LayoutView>, f: &fn(Option<&str>) -> R) -> R {
|
||||
if !node.is_element() {
|
||||
fail!(~"attempting to style non-element node");
|
||||
}
|
||||
do node.with_imm_element() |element_n| {
|
||||
f(element_n.get_attr("class"))
|
||||
}
|
||||
}
|
||||
|
||||
fn node_has_class(&self, node: &AbstractNode<LayoutView>, class: &str) -> bool {
|
||||
if !node.is_element() {
|
||||
fail!(~"attempting to style non-element node");
|
||||
}
|
||||
do node.with_imm_element |element_n| {
|
||||
match element_n.get_attr("class") {
|
||||
None => false,
|
||||
Some(existing_classes) => {
|
||||
let mut ret = false;
|
||||
for str::each_split_char(existing_classes, ' ') |s| {
|
||||
if s == class {
|
||||
ret = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
ret
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn with_node_id<R>(&self, node: &AbstractNode<LayoutView>, f: &fn(Option<&str>) -> R) -> R {
|
||||
if !node.is_element() {
|
||||
fail!(~"attempting to style non-element node");
|
||||
|
|
|
@ -4,13 +4,14 @@
|
|||
|
||||
use compositing::CompositorTask;
|
||||
use layout::layout_task;
|
||||
use util::task::spawn_listener;
|
||||
|
||||
use core::cell::Cell;
|
||||
use core::comm::{Chan, Port, SharedChan};
|
||||
use core::comm::{Port, SharedChan};
|
||||
use gfx::opts::Opts;
|
||||
use gfx::render_task::RenderTask;
|
||||
use gfx::render_task;
|
||||
use script::compositor_interface::{CompositorInterface, ReadyState};
|
||||
use script::engine_interface::{EngineTask, ExitMsg, LoadUrlMsg, Msg};
|
||||
use script::layout_interface::LayoutTask;
|
||||
use script::layout_interface;
|
||||
use script::script_task::{ExecuteMsg, LoadMsg, ScriptMsg, ScriptTask};
|
||||
|
@ -18,15 +19,7 @@ use script::script_task;
|
|||
use servo_net::image_cache_task::{ImageCacheTask, ImageCacheTaskClient};
|
||||
use servo_net::resource_task::ResourceTask;
|
||||
use servo_net::resource_task;
|
||||
use servo_util::time::{ProfilerChan, ProfilerPort, ProfilerTask};
|
||||
use std::net::url::Url;
|
||||
|
||||
pub type EngineTask = Chan<Msg>;
|
||||
|
||||
pub enum Msg {
|
||||
LoadUrlMsg(Url),
|
||||
ExitMsg(Chan<()>),
|
||||
}
|
||||
use servo_util::time::{ProfilerChan, ProfilerPort, ProfilerTask, ForcePrintMsg};
|
||||
|
||||
pub struct Engine {
|
||||
request_port: Port<Msg>,
|
||||
|
@ -39,6 +32,12 @@ pub struct Engine {
|
|||
profiler_task: ProfilerTask,
|
||||
}
|
||||
|
||||
impl Drop for Engine {
|
||||
fn finalize(&self) {
|
||||
self.profiler_task.chan.send(ForcePrintMsg);
|
||||
}
|
||||
}
|
||||
|
||||
impl Engine {
|
||||
pub fn start(compositor: CompositorTask,
|
||||
opts: &Opts,
|
||||
|
@ -50,31 +49,44 @@ impl Engine {
|
|||
profiler_chan: ProfilerChan)
|
||||
-> EngineTask {
|
||||
let (script_port, script_chan) = (Cell(script_port), Cell(script_chan));
|
||||
let (engine_port, engine_chan) = comm::stream();
|
||||
let (engine_port, engine_chan) = (Cell(engine_port), SharedChan::new(engine_chan));
|
||||
let engine_chan_clone = engine_chan.clone();
|
||||
let compositor = Cell(compositor);
|
||||
let profiler_port = Cell(profiler_port);
|
||||
let opts = Cell(copy *opts);
|
||||
|
||||
do spawn_listener::<Msg> |request| {
|
||||
do task::spawn {
|
||||
let compositor = compositor.take();
|
||||
let render_task = RenderTask::new(compositor.clone(),
|
||||
opts.with_ref(|o| copy *o),
|
||||
profiler_chan.clone());
|
||||
|
||||
let profiler_task = ProfilerTask::new(profiler_port.take(), profiler_chan.clone());
|
||||
|
||||
let opts = opts.take();
|
||||
|
||||
let profiler_task = ProfilerTask::new(profiler_port.take(),
|
||||
profiler_chan.clone(),
|
||||
opts.profiler_period);
|
||||
|
||||
let layout_task = layout_task::create_layout_task(render_task.clone(),
|
||||
image_cache_task.clone(),
|
||||
opts,
|
||||
profiler_task.chan.clone());
|
||||
|
||||
let compositor_clone = compositor.clone();
|
||||
let script_task = ScriptTask::new(script_port.take(),
|
||||
script_chan.take(),
|
||||
engine_chan_clone.clone(),
|
||||
|msg: ReadyState| {
|
||||
compositor_clone.set_ready_state(msg)
|
||||
},
|
||||
layout_task.clone(),
|
||||
resource_task.clone(),
|
||||
image_cache_task.clone());
|
||||
|
||||
|
||||
Engine {
|
||||
request_port: request,
|
||||
request_port: engine_port.take(),
|
||||
compositor: compositor.clone(),
|
||||
render_task: render_task,
|
||||
resource_task: resource_task.clone(),
|
||||
|
@ -82,8 +94,9 @@ impl Engine {
|
|||
layout_task: layout_task,
|
||||
script_task: script_task,
|
||||
profiler_task: profiler_task,
|
||||
}.run()
|
||||
}.run();
|
||||
}
|
||||
engine_chan.clone()
|
||||
}
|
||||
|
||||
fn run(&self) {
|
||||
|
|
|
@ -102,39 +102,38 @@ impl BlockFlowData {
|
|||
}
|
||||
}
|
||||
|
||||
// If not an anonymous block context, add in the block box's widths. These widths will not
|
||||
// include child elements, just padding etc.
|
||||
for self.box.each |&box| {
|
||||
// Can compute border width here since it doesn't depend on anything.
|
||||
/* if not an anonymous block context, add in block box's widths.
|
||||
these widths will not include child elements, just padding etc. */
|
||||
self.box.map(|&box| {
|
||||
//Can compute border width here since it doesn't depend on anything
|
||||
let style = box.style();
|
||||
do box.with_model |model| {
|
||||
model.compute_borders(style)
|
||||
}
|
||||
min_width = min_width.add(&box.get_min_width(ctx));
|
||||
pref_width = pref_width.add(&box.get_pref_width(ctx));
|
||||
}
|
||||
});
|
||||
|
||||
self.common.min_width = min_width;
|
||||
self.common.pref_width = pref_width;
|
||||
}
|
||||
|
||||
/// Computes left and right margins and width based on CSS 2.1 secion 10.3.3.
|
||||
/// Requires borders and padding to already be computed.
|
||||
fn compute_horiz(&self,
|
||||
width: MaybeAuto,
|
||||
left_margin: MaybeAuto,
|
||||
right_margin: MaybeAuto,
|
||||
available_width: Au)
|
||||
-> (Au, Au, Au) {
|
||||
// If width is not 'auto', and width + margins > available_width, all 'auto' margins are
|
||||
// treated as '0'.
|
||||
let (left_margin, right_margin) = match width {
|
||||
/// Requires borders and padding to already be computed
|
||||
priv fn compute_horiz( &self,
|
||||
width: MaybeAuto,
|
||||
left_margin: MaybeAuto,
|
||||
right_margin: MaybeAuto,
|
||||
available_width: Au) -> (Au, Au, Au) {
|
||||
|
||||
//If width is not 'auto', and width + margins > available_width, all 'auto' margins are treated as '0'
|
||||
let (left_margin, right_margin) = match width{
|
||||
Auto => (left_margin, right_margin),
|
||||
Specified(width) => {
|
||||
let left = left_margin.spec_or_default(Au(0));
|
||||
let right = right_margin.spec_or_default(Au(0));
|
||||
|
||||
if (left + right + width) > available_width {
|
||||
if((left + right + width) > available_width) {
|
||||
(Specified(left), Specified(right))
|
||||
} else {
|
||||
(left_margin, right_margin)
|
||||
|
@ -142,43 +141,31 @@ impl BlockFlowData {
|
|||
}
|
||||
};
|
||||
|
||||
// Invariant: left_margin_Au + width_Au + right_margin_Au == available_width
|
||||
//Invariant: left_margin_Au + width_Au + right_margin_Au == available_width
|
||||
let (left_margin_Au, width_Au, right_margin_Au) = match (left_margin, width, right_margin) {
|
||||
// If all have a computed value other than 'auto', the system is over-constrained and
|
||||
// we need to discard a margin. If direction is ltr, ignore the specified right margin
|
||||
// and solve for it. If it is rtl, ignore the specified left margin.
|
||||
//
|
||||
// FIXME(eatkinson): this assumes the direction is ltr
|
||||
(Specified(margin_l), Specified(width), Specified(_)) => {
|
||||
(margin_l, width, available_width - (margin_l + width))
|
||||
}
|
||||
//If all have a computed value other than 'auto', the system is over-constrained and we need to discard a margin.
|
||||
//if direction is ltr, ignore the specified right margin and solve for it. If it is rtl, ignore the specified
|
||||
//left margin. FIXME(eatkinson): this assumes the direction is ltr
|
||||
(Specified(margin_l), Specified(width), Specified(_margin_r)) => (margin_l, width, available_width - (margin_l + width )),
|
||||
|
||||
// If exactly one value is 'auto', solve for it
|
||||
(Auto, Specified(width), Specified(margin_r)) => {
|
||||
(available_width - (width + margin_r), width, margin_r)
|
||||
}
|
||||
(Specified(margin_l), Auto, Specified(margin_r)) => {
|
||||
(margin_l, available_width - (margin_l + margin_r), margin_r)
|
||||
}
|
||||
(Specified(margin_l), Specified(width), Auto) => {
|
||||
(margin_l, width, available_width - (margin_l + width))
|
||||
}
|
||||
//If exactly one value is 'auto', solve for it
|
||||
(Auto, Specified(width), Specified(margin_r)) => (available_width - (width + margin_r), width, margin_r),
|
||||
(Specified(margin_l), Auto, Specified(margin_r)) => (margin_l, available_width - (margin_l + margin_r), margin_r),
|
||||
(Specified(margin_l), Specified(width), Auto) => (margin_l, width, available_width - (margin_l + width)),
|
||||
|
||||
// If width is set to 'auto', any other 'auto' value becomes '0', and width is solved
|
||||
// for.
|
||||
//If width is set to 'auto', any other 'auto' value becomes '0', and width is solved for
|
||||
(Auto, Auto, Specified(margin_r)) => (Au(0), available_width - margin_r, margin_r),
|
||||
(Specified(margin_l), Auto, Auto) => (margin_l, available_width - margin_l, Au(0)),
|
||||
(Auto, Auto, Auto) => (Au(0), available_width, Au(0)),
|
||||
|
||||
// If left and right margins are auto, they become equal.
|
||||
//If left and right margins are auto, they become equal
|
||||
(Auto, Specified(width), Auto) => {
|
||||
let margin = (available_width - width).scale_by(0.5);
|
||||
(margin, width, margin)
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
// Return values in same order as params.
|
||||
//return values in same order as params
|
||||
(width_Au, left_margin_Au, right_margin_Au)
|
||||
}
|
||||
|
||||
|
@ -188,7 +175,9 @@ impl BlockFlowData {
|
|||
/// Dual boxes consume some width first, and the remainder is assigned to all child (block)
|
||||
/// contexts.
|
||||
pub fn assign_widths_block(@mut self, ctx: &LayoutContext) {
|
||||
debug!("assign_widths_block: assigning width for flow %?", self.common.id);
|
||||
if self.is_root {
|
||||
debug!("Setting root position");
|
||||
self.common.position.origin = Au::zero_point();
|
||||
self.common.position.size.width = ctx.screen_size.size.width;
|
||||
}
|
||||
|
@ -207,21 +196,20 @@ impl BlockFlowData {
|
|||
let available_width = remaining_width - model.noncontent_width();
|
||||
|
||||
// Top and bottom margins for blocks are 0 if auto.
|
||||
let margin_top = MaybeAuto::from_margin(style.margin_top());
|
||||
let margin_top = margin_top.spec_or_default(Au(0));
|
||||
let margin_bottom = MaybeAuto::from_margin(style.margin_bottom());
|
||||
let margin_bottom = margin_bottom.spec_or_default(Au(0));
|
||||
let margin_top = MaybeAuto::from_margin(style.margin_top(),
|
||||
remaining_width).spec_or_default(Au(0));
|
||||
let margin_bottom = MaybeAuto::from_margin(style.margin_bottom(),
|
||||
remaining_width).spec_or_default(Au(0));
|
||||
|
||||
let (width, margin_left, margin_right) =
|
||||
(MaybeAuto::from_width(style.width()),
|
||||
MaybeAuto::from_margin(style.margin_left()),
|
||||
MaybeAuto::from_margin(style.margin_right()));
|
||||
(MaybeAuto::from_width(style.width(), remaining_width),
|
||||
MaybeAuto::from_margin(style.margin_left(), remaining_width),
|
||||
MaybeAuto::from_margin(style.margin_right(), remaining_width));
|
||||
|
||||
// FIXME(pcwalton): We discard the width here. Is that correct?
|
||||
let (_, margin_left, margin_right) = self.compute_horiz(width,
|
||||
margin_left,
|
||||
margin_right,
|
||||
available_width);
|
||||
let (width, margin_left, margin_right) = self.compute_horiz(width,
|
||||
margin_left,
|
||||
margin_right,
|
||||
available_width);
|
||||
|
||||
model.margin.top = margin_top;
|
||||
model.margin.right = margin_right;
|
||||
|
@ -229,7 +217,7 @@ impl BlockFlowData {
|
|||
model.margin.left = margin_left;
|
||||
|
||||
x_offset = model.offset();
|
||||
remaining_width = remaining_width - model.noncontent_width();
|
||||
remaining_width = width;
|
||||
}
|
||||
|
||||
do box.with_mut_base |base| {
|
||||
|
@ -254,12 +242,14 @@ impl BlockFlowData {
|
|||
|
||||
pub fn assign_height_block(@mut self, ctx: &LayoutContext) {
|
||||
let mut cur_y = Au(0);
|
||||
let mut top_offset = Au(0);
|
||||
|
||||
self.box.map(|&box| {
|
||||
for self.box.each |&box| {
|
||||
do box.with_model |model| {
|
||||
cur_y += model.margin.top + model.border.top + model.padding.top;
|
||||
top_offset = model.margin.top + model.border.top + model.padding.top;
|
||||
cur_y += top_offset;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
for BlockFlow(self).each_child |kid| {
|
||||
do kid.with_mut_base |child_node| {
|
||||
|
@ -271,22 +261,26 @@ impl BlockFlowData {
|
|||
let height = if self.is_root {
|
||||
Au::max(ctx.screen_size.size.height, cur_y)
|
||||
} else {
|
||||
cur_y
|
||||
cur_y - top_offset
|
||||
};
|
||||
|
||||
//TODO(eatkinson): compute heights using the 'height' property.
|
||||
self.common.position.size.height = height;
|
||||
|
||||
|
||||
let mut noncontent_height = Au(0);
|
||||
self.box.map(|&box| {
|
||||
do box.with_mut_base |base| {
|
||||
//The associated box is the border box of this flow
|
||||
base.position.origin.y = base.model.margin.top;
|
||||
|
||||
let pb = base.model.padding.top + base.model.padding.bottom +
|
||||
noncontent_height = base.model.padding.top + base.model.padding.bottom +
|
||||
base.model.border.top + base.model.border.bottom;
|
||||
base.position.size.height = height + pb;
|
||||
base.position.size.height = height + noncontent_height;
|
||||
|
||||
noncontent_height += base.model.margin.top + base.model.margin.bottom;
|
||||
}
|
||||
});
|
||||
|
||||
//TODO(eatkinson): compute heights using the 'height' property.
|
||||
self.common.position.size.height = height + noncontent_height;
|
||||
|
||||
}
|
||||
|
||||
pub fn build_display_list_block<E:ExtraDisplayListData>(@mut self,
|
||||
|
|
|
@ -626,7 +626,23 @@ pub impl RenderBox {
|
|||
});
|
||||
},
|
||||
|
||||
GenericRenderBoxClass(_) => {}
|
||||
GenericRenderBoxClass(_) => {
|
||||
// FIXME(pcwalton): This is somewhat of an abuse of the logging system.
|
||||
debug!("%?", {
|
||||
// Compute the text box bounds and draw a border surrounding them.
|
||||
do list.with_mut_ref |list| {
|
||||
let border_display_item = ~BorderDisplayItem {
|
||||
base: BaseDisplayItem {
|
||||
bounds: absolute_box_bounds,
|
||||
extra: ExtraDisplayListData::new(*self),
|
||||
},
|
||||
width: Au::from_px(1),
|
||||
color: rgb(0, 0, 0).to_gfx_color(),
|
||||
};
|
||||
list.append_item(BorderDisplayItemClass(border_display_item))
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ImageRenderBoxClass(image_box) => {
|
||||
match image_box.image.get_image() {
|
||||
|
|
|
@ -14,8 +14,13 @@ use layout::flow::{AbsoluteFlow, BlockFlow, FloatFlow, Flow_Absolute, Flow_Block
|
|||
use layout::flow::{Flow_Inline, Flow_InlineBlock, Flow_Root, Flow_Table, FlowContext};
|
||||
use layout::flow::{FlowContextType, FlowData, InlineBlockFlow, InlineFlow, TableFlow};
|
||||
use layout::inline::{InlineFlowData, InlineLayout};
|
||||
use css::node_style::StyledNode;
|
||||
|
||||
use newcss::values::{CSSDisplay, CSSDisplayBlock, CSSDisplayInline, CSSDisplayInlineBlock};
|
||||
use newcss::values::{CSSDisplayTable, CSSDisplayInlineTable, CSSDisplayListItem};
|
||||
use newcss::values::{CSSDisplayTableRowGroup, CSSDisplayTableHeaderGroup, CSSDisplayTableFooterGroup};
|
||||
use newcss::values::{CSSDisplayTableRow, CSSDisplayTableColumnGroup, CSSDisplayTableColumn};
|
||||
use newcss::values::{CSSDisplayTableCell, CSSDisplayTableCaption};
|
||||
use newcss::values::{CSSDisplayNone};
|
||||
use script::dom::element::*;
|
||||
use script::dom::node::{AbstractNode, CommentNodeTypeId, DoctypeNodeTypeId};
|
||||
|
@ -25,16 +30,16 @@ use servo_util::tree::{TreeNodeRef, TreeUtils};
|
|||
|
||||
pub struct LayoutTreeBuilder {
|
||||
root_flow: Option<FlowContext>,
|
||||
next_cid: int,
|
||||
next_bid: int,
|
||||
next_cid: int
|
||||
}
|
||||
|
||||
pub impl LayoutTreeBuilder {
|
||||
fn new() -> LayoutTreeBuilder {
|
||||
LayoutTreeBuilder {
|
||||
root_flow: None,
|
||||
next_cid: -1,
|
||||
next_bid: -1,
|
||||
next_cid: -1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -86,6 +91,8 @@ priv fn simulate_UA_display_rules(node: AbstractNode<LayoutView>) -> CSSDisplay
|
|||
}
|
||||
|
||||
impl BoxGenerator {
|
||||
/* Debug ids only */
|
||||
|
||||
fn new(flow: FlowContext) -> BoxGenerator {
|
||||
debug!("Creating box generator for flow: %s", flow.debug_str());
|
||||
BoxGenerator {
|
||||
|
@ -110,14 +117,14 @@ impl BoxGenerator {
|
|||
|
||||
pub fn push_node(&mut self,
|
||||
ctx: &LayoutContext,
|
||||
builder: &mut LayoutTreeBuilder,
|
||||
node: AbstractNode<LayoutView>) {
|
||||
node: AbstractNode<LayoutView>,
|
||||
builder: &mut LayoutTreeBuilder) {
|
||||
debug!("BoxGenerator[f%d]: pushing node: %s", self.flow.id(), node.debug_str());
|
||||
|
||||
// first, determine the box type, based on node characteristics
|
||||
let simulated_display = simulate_UA_display_rules(node);
|
||||
// TODO: remove this once UA styles work
|
||||
let box_type = builder.decide_box_type(node, simulated_display);
|
||||
let box_type = self.decide_box_type(node, simulated_display);
|
||||
|
||||
debug!("BoxGenerator[f%d]: point a", self.flow.id());
|
||||
|
||||
|
@ -129,7 +136,7 @@ impl BoxGenerator {
|
|||
|
||||
// if a leaf, make a box.
|
||||
if node.is_leaf() {
|
||||
let new_box = builder.make_box(ctx, box_type, node, self.flow);
|
||||
let new_box = self.make_box(ctx, box_type, node, self.flow, builder);
|
||||
inline.boxes.push(new_box);
|
||||
} else if self.inline_spacers_needed_for_node(node) {
|
||||
// else, maybe make a spacer for "left" margin, border, padding
|
||||
|
@ -142,7 +149,7 @@ impl BoxGenerator {
|
|||
},
|
||||
BlockFlow(block) => {
|
||||
debug!("BoxGenerator[f%d]: point b", block.common.id);
|
||||
let new_box = builder.make_box(ctx, box_type, node, self.flow);
|
||||
let new_box = self.make_box(ctx, box_type, node, self.flow, builder);
|
||||
|
||||
debug!("BoxGenerator[f%d]: attaching box[b%d] to block flow (node: %s)",
|
||||
block.common.id,
|
||||
|
@ -158,7 +165,6 @@ impl BoxGenerator {
|
|||
|
||||
pub fn pop_node(&mut self,
|
||||
ctx: &LayoutContext,
|
||||
_builder: &LayoutTreeBuilder,
|
||||
node: AbstractNode<LayoutView>) {
|
||||
debug!("BoxGenerator[f%d]: popping node: %s", self.flow.id(), node.debug_str());
|
||||
|
||||
|
@ -177,7 +183,10 @@ impl BoxGenerator {
|
|||
}
|
||||
let mut node_range: Range = Range::new(self.range_stack.pop(), 0);
|
||||
node_range.extend_to(inline.boxes.len());
|
||||
assert!(node_range.length() > 0);
|
||||
|
||||
if node_range.length() == 0 {
|
||||
warn!("node range length is zero?!")
|
||||
}
|
||||
|
||||
debug!("BoxGenerator: adding element range=%?", node_range);
|
||||
inline.elems.add_mapping(node, &node_range);
|
||||
|
@ -186,273 +195,6 @@ impl BoxGenerator {
|
|||
_ => warn!("pop_node() not implemented for flow %?", self.flow.id()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct BuilderContext {
|
||||
default_collector: @mut BoxGenerator,
|
||||
priv inline_collector: Option<@mut BoxGenerator>
|
||||
}
|
||||
|
||||
impl BuilderContext {
|
||||
fn new(collector: @mut BoxGenerator) -> BuilderContext {
|
||||
{
|
||||
let collector = &mut *collector;
|
||||
debug!("Creating new BuilderContext for flow: %s", collector.flow.debug_str());
|
||||
}
|
||||
|
||||
BuilderContext {
|
||||
default_collector: collector,
|
||||
inline_collector: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn clone(self) -> BuilderContext {
|
||||
debug!("BuilderContext: cloning context");
|
||||
copy self
|
||||
}
|
||||
|
||||
priv fn attach_child_flow(&self, child: FlowContext) {
|
||||
let default_collector = &mut *self.default_collector;
|
||||
debug!("BuilderContext: Adding child flow f%? of f%?",
|
||||
default_collector.flow.id(),
|
||||
child.id());
|
||||
default_collector.flow.add_child(child);
|
||||
}
|
||||
|
||||
priv fn create_child_flow_of_type(&self,
|
||||
flow_type: FlowContextType,
|
||||
builder: &mut LayoutTreeBuilder,
|
||||
node: AbstractNode<LayoutView>) -> BuilderContext {
|
||||
let new_flow = builder.make_flow(flow_type, node);
|
||||
self.attach_child_flow(new_flow);
|
||||
|
||||
BuilderContext::new(@mut BoxGenerator::new(new_flow))
|
||||
}
|
||||
|
||||
priv fn make_inline_collector(&mut self,
|
||||
builder: &mut LayoutTreeBuilder,
|
||||
node: AbstractNode<LayoutView>)
|
||||
-> BuilderContext {
|
||||
debug!("BuilderContext: making new inline collector flow");
|
||||
let new_flow = builder.make_flow(Flow_Inline, node);
|
||||
let new_generator = @mut BoxGenerator::new(new_flow);
|
||||
|
||||
self.inline_collector = Some(new_generator);
|
||||
self.attach_child_flow(new_flow);
|
||||
|
||||
BuilderContext::new(new_generator)
|
||||
}
|
||||
|
||||
priv fn get_inline_collector(&mut self,
|
||||
builder: &mut LayoutTreeBuilder,
|
||||
node: AbstractNode<LayoutView>)
|
||||
-> BuilderContext {
|
||||
match copy self.inline_collector {
|
||||
Some(collector) => BuilderContext::new(collector),
|
||||
None => self.make_inline_collector(builder, node)
|
||||
}
|
||||
}
|
||||
|
||||
priv fn clear_inline_collector(&mut self) {
|
||||
self.inline_collector = None;
|
||||
}
|
||||
|
||||
// returns a context for the current node, or None if the document subtree rooted
|
||||
// by the node should not generate a layout tree. For example, nodes with style 'display:none'
|
||||
// should just not generate any flows or boxes.
|
||||
fn containing_context_for_node(&mut self,
|
||||
node: AbstractNode<LayoutView>,
|
||||
builder: &mut LayoutTreeBuilder)
|
||||
-> Option<BuilderContext> {
|
||||
// TODO: remove this once UA styles work
|
||||
// TODO: handle interactions with 'float', 'position' (CSS 2.1, Section 9.7)
|
||||
let simulated_display = match simulate_UA_display_rules(node) {
|
||||
CSSDisplayNone => return None, // tree ends here if 'display: none'
|
||||
v => v
|
||||
};
|
||||
|
||||
let containing_context = match (simulated_display, self.default_collector.flow) {
|
||||
(CSSDisplayBlock, BlockFlow(info)) => match (info.is_root, node.parent_node()) {
|
||||
// If this is the root node, then use the root flow's
|
||||
// context. Otherwise, make a child block context.
|
||||
(true, Some(_)) => { self.create_child_flow_of_type(Flow_Block, builder, node) }
|
||||
(true, None) => { self.clone() }
|
||||
(false, _) => {
|
||||
self.clear_inline_collector();
|
||||
self.create_child_flow_of_type(Flow_Block, builder, node)
|
||||
}
|
||||
},
|
||||
(CSSDisplayInline, InlineFlow(*)) => self.clone(),
|
||||
(CSSDisplayInlineBlock, InlineFlow(*)) => self.clone(),
|
||||
(CSSDisplayInline, BlockFlow(*)) => self.get_inline_collector(builder, node),
|
||||
(CSSDisplayInlineBlock, BlockFlow(*)) => self.get_inline_collector(builder, node),
|
||||
_ => self.clone()
|
||||
};
|
||||
|
||||
Some(containing_context)
|
||||
}
|
||||
}
|
||||
|
||||
pub impl LayoutTreeBuilder {
|
||||
/* Debug-only ids */
|
||||
fn next_box_id(&mut self) -> int { self.next_bid += 1; self.next_bid }
|
||||
fn next_flow_id(&mut self) -> int { self.next_cid += 1; self.next_cid }
|
||||
|
||||
/// Creates necessary box(es) and flow context(s) for the current DOM node,
|
||||
/// and recurses on its children.
|
||||
fn construct_recursively(&mut self,
|
||||
layout_ctx: &LayoutContext,
|
||||
cur_node: AbstractNode<LayoutView>,
|
||||
parent_ctx: &mut BuilderContext) {
|
||||
debug!("Considering node: %s", cur_node.debug_str());
|
||||
|
||||
let mut this_ctx = match parent_ctx.containing_context_for_node(cur_node, self) {
|
||||
Some(ctx) => ctx,
|
||||
None => { return; } // no context because of display: none. Stop building subtree.
|
||||
};
|
||||
debug!("point a: %s", cur_node.debug_str());
|
||||
this_ctx.default_collector.push_node(layout_ctx, self, cur_node);
|
||||
debug!("point b: %s", cur_node.debug_str());
|
||||
|
||||
// recurse on child nodes.
|
||||
for cur_node.each_child |child_node| {
|
||||
self.construct_recursively(layout_ctx, child_node, &mut this_ctx);
|
||||
}
|
||||
|
||||
this_ctx.default_collector.pop_node(layout_ctx, self, cur_node);
|
||||
self.simplify_children_of_flow(layout_ctx, &this_ctx);
|
||||
|
||||
// store reference to the flow context which contains any
|
||||
// boxes that correspond to child_flow.node. These boxes may
|
||||
// eventually be elided or split, but the mapping between
|
||||
// nodes and FlowContexts should not change during layout.
|
||||
let flow = &mut this_ctx.default_collector.flow;
|
||||
let flow: &FlowContext = flow;
|
||||
for flow.each_child |child_flow| {
|
||||
do child_flow.with_base |child_node| {
|
||||
let dom_node = child_node.node;
|
||||
assert!(dom_node.has_layout_data());
|
||||
dom_node.layout_data().flow = Some(child_flow);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Fix up any irregularities such as:
|
||||
///
|
||||
/// * split inlines (CSS 2.1 Section 9.2.1.1)
|
||||
/// * elide non-preformatted whitespace-only text boxes and their flows (CSS 2.1 Section
|
||||
/// 9.2.2.1).
|
||||
///
|
||||
/// The latter can only be done immediately adjacent to, or at the beginning or end of a block
|
||||
/// flow. Otherwise, the whitespace might affect whitespace collapsing with adjacent text.
|
||||
fn simplify_children_of_flow(&self, _: &LayoutContext, parent_ctx: &BuilderContext) {
|
||||
match parent_ctx.default_collector.flow {
|
||||
InlineFlow(*) => {
|
||||
let mut found_child_inline = false;
|
||||
let mut found_child_block = false;
|
||||
|
||||
let flow = &mut parent_ctx.default_collector.flow;
|
||||
let flow: &FlowContext = flow;
|
||||
for flow.each_child |child_ctx| {
|
||||
match child_ctx {
|
||||
InlineFlow(*) | InlineBlockFlow(*) => found_child_inline = true,
|
||||
BlockFlow(*) => found_child_block = true,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
if found_child_block && found_child_inline {
|
||||
self.fixup_split_inline(parent_ctx.default_collector.flow)
|
||||
}
|
||||
},
|
||||
BlockFlow(*) => {
|
||||
// FIXME: this will create refcounted cycles between the removed flow and any
|
||||
// of its RenderBox or FlowContext children, and possibly keep alive other junk
|
||||
let parent_flow = parent_ctx.default_collector.flow;
|
||||
|
||||
// check first/last child for whitespace-ness
|
||||
let first_child = do parent_flow.with_base |parent_node| {
|
||||
parent_node.first_child
|
||||
};
|
||||
for first_child.each |first_flow| {
|
||||
if first_flow.starts_inline_flow() {
|
||||
// FIXME: workaround for rust#6393
|
||||
let mut do_remove = false;
|
||||
{
|
||||
let boxes = &first_flow.inline().boxes;
|
||||
if boxes.len() == 1 && boxes[0].is_whitespace_only() {
|
||||
debug!("LayoutTreeBuilder: pruning whitespace-only first child \
|
||||
flow f%d from parent f%d",
|
||||
first_flow.id(),
|
||||
parent_flow.id());
|
||||
do_remove = true;
|
||||
}
|
||||
}
|
||||
if (do_remove) {
|
||||
parent_flow.remove_child(*first_flow);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let last_child = do parent_flow.with_base |parent_node| {
|
||||
parent_node.last_child
|
||||
};
|
||||
for last_child.each |last_flow| {
|
||||
if last_flow.starts_inline_flow() {
|
||||
// FIXME: workaround for rust#6393
|
||||
let mut do_remove = false;
|
||||
{
|
||||
let boxes = &last_flow.inline().boxes;
|
||||
if boxes.len() == 1 && boxes.last().is_whitespace_only() {
|
||||
debug!("LayoutTreeBuilder: pruning whitespace-only last child \
|
||||
flow f%d from parent f%d",
|
||||
last_flow.id(),
|
||||
parent_flow.id());
|
||||
do_remove = true;
|
||||
}
|
||||
}
|
||||
if (do_remove) {
|
||||
parent_flow.remove_child(*last_flow);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn fixup_split_inline(&self, _: FlowContext) {
|
||||
// TODO: finish me.
|
||||
fail!(~"TODO: handle case where an inline is split by a block")
|
||||
}
|
||||
|
||||
/// Entry point for box creation. Should only be called on the root DOM element.
|
||||
fn construct_trees(&mut self, layout_ctx: &LayoutContext, root: AbstractNode<LayoutView>)
|
||||
-> Result<FlowContext, ()> {
|
||||
let new_flow = self.make_flow(Flow_Root, root);
|
||||
let new_generator = @mut BoxGenerator::new(new_flow);
|
||||
let mut root_ctx = BuilderContext::new(new_generator);
|
||||
|
||||
self.root_flow = Some(new_flow);
|
||||
self.construct_recursively(layout_ctx, root, &mut root_ctx);
|
||||
return Ok(new_flow)
|
||||
}
|
||||
|
||||
/// Creates a flow of the given type for the supplied node.
|
||||
fn make_flow(&mut self, ty: FlowContextType, node: AbstractNode<LayoutView>) -> FlowContext {
|
||||
let info = FlowData::new(self.next_flow_id(), node);
|
||||
let result = match ty {
|
||||
Flow_Absolute => AbsoluteFlow(@mut info),
|
||||
Flow_Block => BlockFlow(@mut BlockFlowData::new(info)),
|
||||
Flow_Float => FloatFlow(@mut info),
|
||||
Flow_InlineBlock => InlineBlockFlow(@mut info),
|
||||
Flow_Inline => InlineFlow(@mut InlineFlowData::new(info)),
|
||||
Flow_Root => BlockFlow(@mut BlockFlowData::new_root(info)),
|
||||
Flow_Table => TableFlow(@mut info),
|
||||
};
|
||||
debug!("LayoutTreeBuilder: created flow: %s", result.debug_str());
|
||||
result
|
||||
}
|
||||
|
||||
/// Disambiguate between different methods here instead of inlining, since each case has very
|
||||
/// different complexity.
|
||||
|
@ -460,15 +202,16 @@ pub impl LayoutTreeBuilder {
|
|||
layout_ctx: &LayoutContext,
|
||||
ty: RenderBoxType,
|
||||
node: AbstractNode<LayoutView>,
|
||||
flow_context: FlowContext)
|
||||
flow_context: FlowContext,
|
||||
builder: &mut LayoutTreeBuilder)
|
||||
-> RenderBox {
|
||||
let base = RenderBoxBase::new(node, flow_context, self.next_box_id());
|
||||
let base = RenderBoxBase::new(node, flow_context, builder.next_box_id());
|
||||
let result = match ty {
|
||||
RenderBox_Generic => GenericRenderBoxClass(@mut base),
|
||||
RenderBox_Text => UnscannedTextRenderBoxClass(@mut UnscannedTextRenderBox::new(base)),
|
||||
RenderBox_Image => self.make_image_box(layout_ctx, node, base),
|
||||
};
|
||||
debug!("LayoutTreeBuilder: created box: %s", result.debug_str());
|
||||
debug!("BoxGenerator: created box: %s", result.debug_str());
|
||||
result
|
||||
}
|
||||
|
||||
|
@ -508,4 +251,282 @@ pub impl LayoutTreeBuilder {
|
|||
fail!(~"Hey, doctypes and comments shouldn't get here! They are display:none!")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
pub impl LayoutTreeBuilder {
|
||||
/* Debug-only ids */
|
||||
fn next_flow_id(&mut self) -> int { self.next_cid += 1; self.next_cid }
|
||||
fn next_box_id(&mut self) -> int { self.next_bid += 1; self.next_bid }
|
||||
|
||||
/// Creates necessary box(es) and flow context(s) for the current DOM node,
|
||||
/// and recurses on its children.
|
||||
fn construct_recursively(&mut self,
|
||||
layout_ctx: &LayoutContext,
|
||||
cur_node: AbstractNode<LayoutView>,
|
||||
parent_generator: @mut BoxGenerator,
|
||||
prev_sibling_generator: Option<@mut BoxGenerator>)
|
||||
-> Option<@mut BoxGenerator> {
|
||||
debug!("Considering node: %s", cur_node.debug_str());
|
||||
|
||||
let this_generator = match self.box_generator_for_node(cur_node,
|
||||
parent_generator,
|
||||
prev_sibling_generator) {
|
||||
Some(gen) => gen,
|
||||
None => { return prev_sibling_generator; }
|
||||
};
|
||||
|
||||
debug!("point a: %s", cur_node.debug_str());
|
||||
this_generator.push_node(layout_ctx, cur_node, self);
|
||||
debug!("point b: %s", cur_node.debug_str());
|
||||
|
||||
// recurse on child nodes.
|
||||
let mut prev_generator: Option<@mut BoxGenerator> = None;
|
||||
for cur_node.each_child |child_node| {
|
||||
prev_generator = self.construct_recursively(layout_ctx, child_node, this_generator, prev_generator);
|
||||
}
|
||||
|
||||
this_generator.pop_node(layout_ctx, cur_node);
|
||||
self.simplify_children_of_flow(layout_ctx, &mut this_generator.flow);
|
||||
|
||||
// store reference to the flow context which contains any
|
||||
// boxes that correspond to child_flow.node. These boxes may
|
||||
// eventually be elided or split, but the mapping between
|
||||
// nodes and FlowContexts should not change during layout.
|
||||
let flow: &FlowContext = &this_generator.flow;
|
||||
for flow.each_child |child_flow| {
|
||||
do child_flow.with_base |child_node| {
|
||||
let dom_node = child_node.node;
|
||||
assert!(dom_node.has_layout_data());
|
||||
dom_node.layout_data().flow = Some(child_flow);
|
||||
}
|
||||
}
|
||||
Some(this_generator)
|
||||
}
|
||||
|
||||
fn box_generator_for_node(&mut self,
|
||||
node: AbstractNode<LayoutView>,
|
||||
parent_generator: @mut BoxGenerator,
|
||||
sibling_generator: Option<@mut BoxGenerator>)
|
||||
-> Option<@mut BoxGenerator> {
|
||||
|
||||
fn is_root(node: AbstractNode<LayoutView>) -> bool {
|
||||
match node.parent_node() {
|
||||
None => true,
|
||||
Some(_) => false
|
||||
}
|
||||
}
|
||||
let display = if (node.is_element()) {
|
||||
match node.style().display(is_root(node)) {
|
||||
CSSDisplayNone => return None, // tree ends here if 'display: none'
|
||||
// TODO(eatkinson) these are hacks so that the code doesn't crash
|
||||
// when unsupported display values are used. They should be deleted
|
||||
// as they are implemented.
|
||||
CSSDisplayListItem => CSSDisplayBlock,
|
||||
CSSDisplayTable => CSSDisplayBlock,
|
||||
CSSDisplayInlineTable => CSSDisplayInlineBlock,
|
||||
CSSDisplayTableRowGroup => CSSDisplayBlock,
|
||||
CSSDisplayTableHeaderGroup => CSSDisplayBlock,
|
||||
CSSDisplayTableFooterGroup => CSSDisplayBlock,
|
||||
CSSDisplayTableRow => CSSDisplayBlock,
|
||||
CSSDisplayTableColumnGroup => return None,
|
||||
CSSDisplayTableColumn => return None,
|
||||
CSSDisplayTableCell => CSSDisplayBlock,
|
||||
CSSDisplayTableCaption => CSSDisplayBlock,
|
||||
v => v
|
||||
}
|
||||
} else {
|
||||
match node.type_id() {
|
||||
|
||||
ElementNodeTypeId(_) => CSSDisplayInline,
|
||||
TextNodeTypeId => CSSDisplayInline,
|
||||
DoctypeNodeTypeId | CommentNodeTypeId => return None,
|
||||
}
|
||||
};
|
||||
|
||||
let sibling_flow: Option<FlowContext> = match sibling_generator {
|
||||
None => None,
|
||||
Some(gen) => Some(gen.flow)
|
||||
};
|
||||
|
||||
let new_generator = match (display, parent_generator.flow, sibling_flow) {
|
||||
(CSSDisplayBlock, BlockFlow(info), _) => match (info.is_root, node.parent_node()) {
|
||||
// If this is the root node, then use the root flow's
|
||||
// context. Otherwise, make a child block context.
|
||||
(true, Some(_)) => { self.create_child_generator(node, parent_generator, Flow_Block) }
|
||||
(true, None) => { parent_generator }
|
||||
(false, _) => {
|
||||
self.create_child_generator(node, parent_generator, Flow_Block)
|
||||
}
|
||||
},
|
||||
// Inlines that are children of inlines are part of the same flow
|
||||
(CSSDisplayInline, InlineFlow(*), _) => parent_generator,
|
||||
(CSSDisplayInlineBlock, InlineFlow(*), _) => parent_generator,
|
||||
|
||||
// Inlines that are children of blocks create new flows if their
|
||||
// previous sibling was a block.
|
||||
(CSSDisplayInline, BlockFlow(*), Some(BlockFlow(*))) |
|
||||
(CSSDisplayInlineBlock, BlockFlow(*), Some(BlockFlow(*))) => {
|
||||
self.create_child_generator(node, parent_generator, Flow_Inline)
|
||||
}
|
||||
|
||||
// Inlines whose previous sibling was not a block try to use their
|
||||
// sibling's flow context.
|
||||
(CSSDisplayInline, BlockFlow(*), _) |
|
||||
(CSSDisplayInlineBlock, BlockFlow(*), _) => {
|
||||
self.create_child_generator_if_needed(node,
|
||||
parent_generator,
|
||||
sibling_generator,
|
||||
Flow_Inline)
|
||||
}
|
||||
|
||||
// TODO(eatkinson): blocks that are children of inlines need
|
||||
// to split their parent flows.
|
||||
//
|
||||
// TODO(eatkinson): floats and positioned elements.
|
||||
_ => parent_generator
|
||||
};
|
||||
|
||||
Some(new_generator)
|
||||
}
|
||||
|
||||
fn create_child_generator(&mut self,
|
||||
node: AbstractNode<LayoutView>,
|
||||
parent_generator: @mut BoxGenerator,
|
||||
ty: FlowContextType)
|
||||
-> @mut BoxGenerator {
|
||||
|
||||
let new_flow = self.make_flow(ty, node);
|
||||
parent_generator.flow.add_child(new_flow);
|
||||
|
||||
@mut BoxGenerator::new(new_flow)
|
||||
}
|
||||
|
||||
fn create_child_generator_if_needed(&mut self,
|
||||
node: AbstractNode<LayoutView>,
|
||||
parent_generator: @mut BoxGenerator,
|
||||
maybe_generator: Option<@mut BoxGenerator>,
|
||||
ty: FlowContextType)
|
||||
-> @mut BoxGenerator {
|
||||
|
||||
match maybe_generator {
|
||||
None => self.create_child_generator(node, parent_generator, ty),
|
||||
Some(gen) => gen
|
||||
}
|
||||
}
|
||||
|
||||
/// Fix up any irregularities such as:
|
||||
///
|
||||
/// * split inlines (CSS 2.1 Section 9.2.1.1)
|
||||
/// * elide non-preformatted whitespace-only text boxes and their flows (CSS 2.1 Section
|
||||
/// 9.2.2.1).
|
||||
///
|
||||
/// The latter can only be done immediately adjacent to, or at the beginning or end of a block
|
||||
/// flow. Otherwise, the whitespace might affect whitespace collapsing with adjacent text.
|
||||
fn simplify_children_of_flow(&self, _: &LayoutContext, parent_flow: &mut FlowContext) {
|
||||
match *parent_flow {
|
||||
InlineFlow(*) => {
|
||||
let mut found_child_inline = false;
|
||||
let mut found_child_block = false;
|
||||
|
||||
let flow = *parent_flow;
|
||||
for flow.each_child |child_ctx: FlowContext| {
|
||||
match child_ctx {
|
||||
InlineFlow(*) | InlineBlockFlow(*) => found_child_inline = true,
|
||||
BlockFlow(*) => found_child_block = true,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
if found_child_block && found_child_inline {
|
||||
self.fixup_split_inline(*parent_flow)
|
||||
}
|
||||
},
|
||||
BlockFlow(*) => {
|
||||
// FIXME: this will create refcounted cycles between the removed flow and any
|
||||
// of its RenderBox or FlowContext children, and possibly keep alive other junk
|
||||
|
||||
// check first/last child for whitespace-ness
|
||||
let first_child = do parent_flow.with_base |parent_node| {
|
||||
parent_node.first_child
|
||||
};
|
||||
for first_child.each |&first_flow| {
|
||||
if first_flow.starts_inline_flow() {
|
||||
// FIXME: workaround for rust#6393
|
||||
let mut do_remove = false;
|
||||
{
|
||||
let boxes = &first_flow.inline().boxes;
|
||||
if boxes.len() == 1 && boxes[0].is_whitespace_only() {
|
||||
debug!("LayoutTreeBuilder: pruning whitespace-only first child \
|
||||
flow f%d from parent f%d",
|
||||
first_flow.id(),
|
||||
parent_flow.id());
|
||||
do_remove = true;
|
||||
}
|
||||
}
|
||||
if (do_remove) {
|
||||
(*parent_flow).remove_child(first_flow);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let last_child = do parent_flow.with_base |parent_node| {
|
||||
parent_node.last_child
|
||||
};
|
||||
for last_child.each |&last_flow| {
|
||||
if last_flow.starts_inline_flow() {
|
||||
// FIXME: workaround for rust#6393
|
||||
let mut do_remove = false;
|
||||
{
|
||||
let boxes = &last_flow.inline().boxes;
|
||||
if boxes.len() == 1 && boxes.last().is_whitespace_only() {
|
||||
debug!("LayoutTreeBuilder: pruning whitespace-only last child \
|
||||
flow f%d from parent f%d",
|
||||
last_flow.id(),
|
||||
parent_flow.id());
|
||||
do_remove = true;
|
||||
}
|
||||
}
|
||||
if (do_remove) {
|
||||
(*parent_flow).remove_child(last_flow);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn fixup_split_inline(&self, _: FlowContext) {
|
||||
// TODO: finish me.
|
||||
fail!(~"TODO: handle case where an inline is split by a block")
|
||||
}
|
||||
|
||||
/// Entry point for box creation. Should only be called on the root DOM element.
|
||||
fn construct_trees(&mut self, layout_ctx: &LayoutContext, root: AbstractNode<LayoutView>)
|
||||
-> Result<FlowContext, ()> {
|
||||
let new_flow = self.make_flow(Flow_Root, root);
|
||||
let new_generator = @mut BoxGenerator::new(new_flow);
|
||||
|
||||
self.root_flow = Some(new_flow);
|
||||
self.construct_recursively(layout_ctx, root, new_generator, None);
|
||||
return Ok(new_flow)
|
||||
}
|
||||
|
||||
/// Creates a flow of the given type for the supplied node.
|
||||
fn make_flow(&mut self, ty: FlowContextType, node: AbstractNode<LayoutView>) -> FlowContext {
|
||||
let info = FlowData::new(self.next_flow_id(), node);
|
||||
let result = match ty {
|
||||
Flow_Absolute => AbsoluteFlow(@mut info),
|
||||
Flow_Block => BlockFlow(@mut BlockFlowData::new(info)),
|
||||
Flow_Float => FloatFlow(@mut info),
|
||||
Flow_InlineBlock => InlineBlockFlow(@mut info),
|
||||
Flow_Inline => InlineFlow(@mut InlineFlowData::new(info)),
|
||||
Flow_Root => BlockFlow(@mut BlockFlowData::new_root(info)),
|
||||
Flow_Table => TableFlow(@mut info),
|
||||
};
|
||||
debug!("LayoutTreeBuilder: created flow: %s", result.debug_str());
|
||||
result
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,5 +13,5 @@ use servo_net::local_image_cache::LocalImageCache;
|
|||
pub struct LayoutContext {
|
||||
font_ctx: @mut FontContext,
|
||||
image_cache: @mut LocalImageCache,
|
||||
screen_size: Rect<Au>,
|
||||
screen_size: Rect<Au>
|
||||
}
|
||||
|
|
|
@ -412,7 +412,7 @@ impl<'self> FlowContext {
|
|||
};
|
||||
|
||||
do self.with_base |base| {
|
||||
fmt!("f%? %?", base.id, repr)
|
||||
fmt!("f%? %? size %?", base.id, repr, base.position)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -38,7 +38,7 @@ use script::layout_interface::{ContentBoxesQuery, ContentBoxesResponse, ExitMsg,
|
|||
use script::layout_interface::{LayoutResponse, LayoutTask, MatchSelectorsDocumentDamage, Msg};
|
||||
use script::layout_interface::{QueryMsg, Reflow, ReflowDocumentDamage, ReflowForDisplay};
|
||||
use script::layout_interface::{ReflowMsg};
|
||||
use script::script_task::{ScriptMsg, SendEventMsg};
|
||||
use script::script_task::{ReflowCompleteMsg, ScriptMsg, SendEventMsg};
|
||||
use servo_net::image_cache_task::{ImageCacheTask, ImageResponseMsg};
|
||||
use servo_net::local_image_cache::LocalImageCache;
|
||||
use servo_util::tree::{TreeNodeRef, TreeUtils};
|
||||
|
@ -252,8 +252,14 @@ impl Layout {
|
|||
} // time(layout: display list building)
|
||||
}
|
||||
|
||||
debug!("%?", layout_root.dump());
|
||||
|
||||
// Tell script that we're done.
|
||||
//
|
||||
// FIXME(pcwalton): This should probably be *one* channel, but we can't fix this without
|
||||
// either select or a filtered recv() that only looks for messages of a given type.
|
||||
data.script_join_chan.send(());
|
||||
data.script_chan.send(ReflowCompleteMsg);
|
||||
}
|
||||
|
||||
/// Handles a query from the script task. This is the main routine that DOM functions like
|
||||
|
@ -340,19 +346,18 @@ impl Layout {
|
|||
flow.build_display_list(&builder,
|
||||
&flow.position(),
|
||||
display_list);
|
||||
// iterate in reverse to ensure we have the most recently painted render box
|
||||
let (x, y) = (Au::from_frac_px(point.x as float),
|
||||
Au::from_frac_px(point.y as float));
|
||||
let mut resp = Err(());
|
||||
let display_list = &display_list.take().list;
|
||||
// iterate in reverse to ensure we have the most recently painted render box
|
||||
for display_list.each_reverse |display_item| {
|
||||
let bounds = display_item.bounds();
|
||||
|
||||
// FIXME(pcwalton): Move this to be a method on Rect.
|
||||
// TODO this check should really be performed by a method of DisplayItem
|
||||
if x <= bounds.origin.x + bounds.size.width &&
|
||||
x >= bounds.origin.x &&
|
||||
y < bounds.origin.y + bounds.size.height &&
|
||||
y >= bounds.origin.y {
|
||||
bounds.origin.x <= x &&
|
||||
y < bounds.origin.y + bounds.size.height &&
|
||||
bounds.origin.y < y {
|
||||
resp = Ok(HitTestResponse(display_item.base().extra.node()));
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -22,14 +22,10 @@ use newcss::values::{CSSBorderWidthThick, CSSBorderWidthThin};
|
|||
use newcss::values::{CSSWidth, CSSWidthLength, CSSWidthPercentage, CSSWidthAuto};
|
||||
use newcss::values::{CSSMargin, CSSMarginLength, CSSMarginPercentage, CSSMarginAuto};
|
||||
use newcss::values::{CSSPadding, CSSPaddingLength, CSSPaddingPercentage};
|
||||
|
||||
/// Encapsulates the borders, padding, and margins, which we collectively call the "box model".
|
||||
pub struct BoxModel {
|
||||
/// The size of the borders.
|
||||
border: SideOffsets2D<Au>,
|
||||
/// The size of the padding.
|
||||
padding: SideOffsets2D<Au>,
|
||||
/// The size of the margins.
|
||||
margin: SideOffsets2D<Au>,
|
||||
/// The width of the content box.
|
||||
content_box_width: Au,
|
||||
|
@ -41,12 +37,12 @@ pub enum MaybeAuto {
|
|||
Specified(Au),
|
||||
}
|
||||
|
||||
impl MaybeAuto {
|
||||
pub fn from_margin(margin: CSSMargin) -> MaybeAuto{
|
||||
impl MaybeAuto{
|
||||
pub fn from_margin(margin: CSSMargin, cb_width: Au) -> MaybeAuto{
|
||||
match margin {
|
||||
CSSMarginAuto => Auto,
|
||||
//FIXME(eatkinson): Compute percents properly
|
||||
CSSMarginPercentage(_) => Specified(Au(0)),
|
||||
CSSMarginPercentage(percent) => Specified(cb_width.scale_by(percent/100.0)),
|
||||
//FIXME(eatkinson): Compute pt and em values properly
|
||||
CSSMarginLength(Px(v)) |
|
||||
CSSMarginLength(Pt(v)) |
|
||||
|
@ -54,11 +50,10 @@ impl MaybeAuto {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn from_width(width: CSSWidth) -> MaybeAuto{
|
||||
pub fn from_width(width: CSSWidth, cb_width: Au) -> MaybeAuto{
|
||||
match width{
|
||||
CSSWidthAuto => Auto,
|
||||
//FIXME(eatkinson): Compute percents properly
|
||||
CSSWidthPercentage(_) => Specified(Au(0)),
|
||||
CSSWidthPercentage(percent) => Specified(cb_width.scale_by(percent/100.0)),
|
||||
//FIXME(eatkinson): Compute pt and em values properly
|
||||
CSSWidthLength(Px(v)) |
|
||||
CSSWidthLength(Pt(v)) |
|
||||
|
@ -90,7 +85,7 @@ impl Zero for BoxModel {
|
|||
}
|
||||
|
||||
impl BoxModel {
|
||||
/// Populates the box model border parameters from the given computed style.
|
||||
/// Populates the box model parameters from the given computed style.
|
||||
pub fn compute_borders(&mut self, style: CompleteStyle) {
|
||||
// Compute the borders.
|
||||
self.border.top = self.compute_border_width(style.border_top_width());
|
||||
|
@ -99,16 +94,11 @@ impl BoxModel {
|
|||
self.border.left = self.compute_border_width(style.border_left_width());
|
||||
}
|
||||
|
||||
/// Populates the box model padding parameters from the given computed style.
|
||||
pub fn compute_padding(&mut self, style: CompleteStyle, content_box_width: Au) {
|
||||
self.padding.top = self.compute_padding_length(style.padding_top(),
|
||||
content_box_width);
|
||||
self.padding.right = self.compute_padding_length(style.padding_right(),
|
||||
content_box_width);
|
||||
self.padding.bottom = self.compute_padding_length(style.padding_bottom(),
|
||||
content_box_width);
|
||||
self.padding.left = self.compute_padding_length(style.padding_left(),
|
||||
content_box_width);
|
||||
pub fn compute_padding(&mut self, style: CompleteStyle, cb_width: Au){
|
||||
self.padding.top = self.compute_padding_length(style.padding_top(), cb_width);
|
||||
self.padding.right = self.compute_padding_length(style.padding_right(), cb_width);
|
||||
self.padding.bottom = self.compute_padding_length(style.padding_bottom(), cb_width);
|
||||
self.padding.left = self.compute_padding_length(style.padding_left(), cb_width);
|
||||
}
|
||||
|
||||
pub fn noncontent_width(&self) -> Au {
|
||||
|
@ -122,7 +112,7 @@ impl BoxModel {
|
|||
}
|
||||
|
||||
/// Helper function to compute the border width in app units from the CSS border width.
|
||||
fn compute_border_width(&self, width: CSSBorderWidth) -> Au {
|
||||
priv fn compute_border_width(&self, width: CSSBorderWidth) -> Au {
|
||||
match width {
|
||||
CSSBorderWidthLength(Px(v)) |
|
||||
CSSBorderWidthLength(Em(v)) |
|
||||
|
@ -144,7 +134,7 @@ impl BoxModel {
|
|||
// FIXME(eatkinson): Handle 'em' and 'pt' correctly
|
||||
Au::from_frac_px(v)
|
||||
}
|
||||
CSSPaddingPercentage(p) => content_box_width.scale_by(p)
|
||||
CSSPaddingPercentage(p) => content_box_width.scale_by(p/100.0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -174,12 +164,12 @@ impl RenderBox {
|
|||
let border_width = border.top;
|
||||
let bounds = Rect {
|
||||
origin: Point2D {
|
||||
x: abs_bounds.origin.x,
|
||||
y: abs_bounds.origin.y,
|
||||
x: abs_bounds.origin.x + border_width.scale_by(0.5),
|
||||
y: abs_bounds.origin.y + border_width.scale_by(0.5),
|
||||
},
|
||||
size: Size2D {
|
||||
width: abs_bounds.size.width,
|
||||
height: abs_bounds.size.height
|
||||
width: abs_bounds.size.width - border_width,
|
||||
height: abs_bounds.size.height - border_width
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -7,15 +7,22 @@
|
|||
/// GLUT is a very old and bare-bones toolkit. However, it has good cross-platform support, at
|
||||
/// least on desktops. It is designed for testing Servo without the need of a UI.
|
||||
|
||||
use windowing::{ApplicationMethods, CompositeCallback, LoadUrlCallback, ClickCallback};
|
||||
use windowing::{ResizeCallback, ScrollCallback, ZoomCallback, WindowMethods};
|
||||
use windowing::{ApplicationMethods, CompositeCallback, LoadUrlCallback, MouseCallback};
|
||||
use windowing::{ResizeCallback, ScrollCallback, WindowMethods, WindowMouseEvent, WindowClickEvent};
|
||||
use windowing::{WindowMouseDownEvent, WindowMouseUpEvent, ZoomCallback};
|
||||
|
||||
use alert::{Alert, AlertMethods};
|
||||
use core::cell::Cell;
|
||||
use core::libc::c_int;
|
||||
use geom::point::Point2D;
|
||||
use geom::size::Size2D;
|
||||
use glut::glut::{DOUBLE, WindowHeight, WindowWidth};
|
||||
use gfx::compositor::{IdleRenderState, RenderState, RenderingRenderState};
|
||||
use glut::glut::{ACTIVE_CTRL, DOUBLE, HAVE_PRECISE_MOUSE_WHEEL, WindowHeight, WindowWidth};
|
||||
use glut::glut;
|
||||
use glut::machack;
|
||||
use script::compositor_interface::{FinishedLoading, Loading, PerformingLayout, ReadyState};
|
||||
|
||||
static THROBBER: [char, ..8] = [ '⣾', '⣽', '⣻', '⢿', '⡿', '⣟', '⣯', '⣷' ];
|
||||
|
||||
/// A structure responsible for setting up and tearing down the entire windowing system.
|
||||
pub struct Application;
|
||||
|
@ -35,12 +42,18 @@ pub struct Window {
|
|||
composite_callback: Option<CompositeCallback>,
|
||||
resize_callback: Option<ResizeCallback>,
|
||||
load_url_callback: Option<LoadUrlCallback>,
|
||||
click_callback: Option<ClickCallback>,
|
||||
mouse_callback: Option<MouseCallback>,
|
||||
scroll_callback: Option<ScrollCallback>,
|
||||
zoom_callback: Option<ZoomCallback>,
|
||||
|
||||
drag_origin: Point2D<c_int>,
|
||||
down_button: c_int
|
||||
|
||||
mouse_down_button: @mut c_int,
|
||||
mouse_down_point: @mut Point2D<c_int>,
|
||||
|
||||
ready_state: ReadyState,
|
||||
render_state: RenderState,
|
||||
throbber_frame: u8,
|
||||
}
|
||||
|
||||
impl WindowMethods<Application> for Window {
|
||||
|
@ -57,15 +70,31 @@ impl WindowMethods<Application> for Window {
|
|||
composite_callback: None,
|
||||
resize_callback: None,
|
||||
load_url_callback: None,
|
||||
click_callback: None,
|
||||
mouse_callback: None,
|
||||
scroll_callback: None,
|
||||
zoom_callback: None,
|
||||
|
||||
drag_origin: Point2D(0, 0),
|
||||
down_button: 0 // FIXME: Hacky solution to 2 button mouse.
|
||||
// Replace with tkuehn's code.
|
||||
|
||||
mouse_down_button: @mut 0,
|
||||
mouse_down_point: @mut Point2D(0, 0),
|
||||
|
||||
ready_state: FinishedLoading,
|
||||
render_state: IdleRenderState,
|
||||
throbber_frame: 0,
|
||||
};
|
||||
|
||||
// Spin the event loop every 50 ms to allow the Rust channels to be polled.
|
||||
//
|
||||
// This requirement is pretty much the nail in the coffin for GLUT's usefulness.
|
||||
//
|
||||
// FIXME(pcwalton): What a mess.
|
||||
let register_timer_callback: @mut @fn() = @mut ||{};
|
||||
*register_timer_callback = || {
|
||||
glut::timer_func(50, *register_timer_callback);
|
||||
window.throbber_frame = (window.throbber_frame + 1) % (THROBBER.len() as u8);
|
||||
window.update_window_title()
|
||||
};
|
||||
|
||||
// Register event handlers.
|
||||
do glut::reshape_func(window.glut_window) |width, height| {
|
||||
|
@ -84,14 +113,27 @@ impl WindowMethods<Application> for Window {
|
|||
do glut::keyboard_func |key, _, _| {
|
||||
window.handle_key(key)
|
||||
}
|
||||
do glut::mouse_func |button, _, x, y| {
|
||||
window.handle_click(x, y);
|
||||
window.down_button = button;
|
||||
window.start_drag(x, y)
|
||||
do glut::mouse_func |button, state, x, y| {
|
||||
if button < 3 {
|
||||
window.handle_mouse(button, state, x, y);
|
||||
}
|
||||
}
|
||||
do glut::motion_func |x, y| {
|
||||
window.continue_drag(x, y)
|
||||
do glut::mouse_wheel_func |wheel, direction, x, y| {
|
||||
let delta = if HAVE_PRECISE_MOUSE_WHEEL {
|
||||
(direction as f32) / 10000.0
|
||||
} else {
|
||||
(direction as f32) * 30.0
|
||||
};
|
||||
|
||||
match wheel {
|
||||
1 => window.handle_scroll(Point2D(delta, 0.0)),
|
||||
2 => window.handle_zoom(delta),
|
||||
_ => window.handle_scroll(Point2D(0.0, delta)),
|
||||
}
|
||||
}
|
||||
(*register_timer_callback)();
|
||||
|
||||
machack::perform_scroll_wheel_hack();
|
||||
|
||||
window
|
||||
}
|
||||
|
@ -121,9 +163,9 @@ impl WindowMethods<Application> for Window {
|
|||
self.load_url_callback = Some(new_load_url_callback)
|
||||
}
|
||||
|
||||
/// Registers a callback to be run when a click event occurs.
|
||||
pub fn set_click_callback(&mut self, new_click_callback: ClickCallback) {
|
||||
self.click_callback = Some(new_click_callback)
|
||||
/// Registers a callback to be run when a mouse event occurs.
|
||||
pub fn set_mouse_callback(&mut self, new_mouse_callback: MouseCallback) {
|
||||
self.mouse_callback = Some(new_mouse_callback)
|
||||
}
|
||||
|
||||
/// Registers a callback to be run when the user scrolls.
|
||||
|
@ -145,46 +187,110 @@ impl WindowMethods<Application> for Window {
|
|||
pub fn set_needs_display(@mut self) {
|
||||
glut::post_redisplay()
|
||||
}
|
||||
|
||||
/// Sets the ready state.
|
||||
pub fn set_ready_state(@mut self, ready_state: ReadyState) {
|
||||
self.ready_state = ready_state;
|
||||
self.update_window_title()
|
||||
}
|
||||
|
||||
/// Sets the render state.
|
||||
pub fn set_render_state(@mut self, render_state: RenderState) {
|
||||
self.render_state = render_state;
|
||||
self.update_window_title()
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
Loading => {
|
||||
glut::set_window_title(self.glut_window, fmt!("%c Loading — Servo", throbber))
|
||||
}
|
||||
PerformingLayout => {
|
||||
glut::set_window_title(self.glut_window,
|
||||
fmt!("%c Performing Layout — Servo", throbber))
|
||||
}
|
||||
FinishedLoading => {
|
||||
match self.render_state {
|
||||
RenderingRenderState => {
|
||||
glut::set_window_title(self.glut_window,
|
||||
fmt!("%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: %d", key as int);
|
||||
if key == 12 { // ^L
|
||||
self.load_url()
|
||||
match key {
|
||||
12 => self.load_url(), // Ctrl+L
|
||||
k if k == ('=' as u8) && (glut::get_modifiers() & ACTIVE_CTRL) != 0 => { // Ctrl++
|
||||
for self.zoom_callback.each |&callback| {
|
||||
callback(0.1);
|
||||
}
|
||||
}
|
||||
k if k == 31 && (glut::get_modifiers() & ACTIVE_CTRL) != 0 => { // Ctrl+-
|
||||
for self.zoom_callback.each |&callback| {
|
||||
callback(-0.1);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper function to handle a click
|
||||
fn handle_click(&self, x: c_int, y: c_int) {
|
||||
match self.click_callback {
|
||||
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 = 10f;
|
||||
match self.mouse_callback {
|
||||
None => {}
|
||||
Some(callback) => callback(Point2D(x as f32, y as f32)),
|
||||
Some(callback) => {
|
||||
let event: WindowMouseEvent;
|
||||
match state {
|
||||
glut::MOUSE_DOWN => {
|
||||
event = WindowMouseDownEvent(button as uint, Point2D(x as f32, y as f32));
|
||||
*self.mouse_down_point = Point2D(x, y);
|
||||
*self.mouse_down_button = button;
|
||||
}
|
||||
glut::MOUSE_UP => {
|
||||
event = WindowMouseUpEvent(button as uint, Point2D(x as f32, y as f32));
|
||||
if *self.mouse_down_button == button {
|
||||
let pixel_dist = *self.mouse_down_point - Point2D(x, y);
|
||||
let pixel_dist = ((pixel_dist.x * pixel_dist.x +
|
||||
pixel_dist.y * pixel_dist.y) as float).sqrt();
|
||||
if pixel_dist < max_pixel_dist {
|
||||
let click_event = WindowClickEvent(button as uint,
|
||||
Point2D(x as f32, y as f32));
|
||||
callback(click_event);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => fail!("I cannot recognize the type of mouse action that occured. :-(")
|
||||
};
|
||||
callback(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper function to start a drag.
|
||||
fn start_drag(&mut self, x: c_int, y: c_int) {
|
||||
self.drag_origin = Point2D(x, y)
|
||||
/// Helper function to handle a scroll.
|
||||
fn handle_scroll(&mut self, delta: Point2D<f32>) {
|
||||
match self.scroll_callback {
|
||||
None => {}
|
||||
Some(callback) => callback(delta),
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper function to continue a drag.
|
||||
fn continue_drag(&mut self, x: c_int, y: c_int) {
|
||||
let new_point = Point2D(x, y);
|
||||
let delta = new_point - self.drag_origin;
|
||||
self.drag_origin = new_point;
|
||||
|
||||
if self.down_button == 2 {
|
||||
match self.zoom_callback {
|
||||
None => {}
|
||||
Some(callback) => callback(Point2D(delta.x as f32, delta.y as f32)),
|
||||
}
|
||||
} else {
|
||||
match self.scroll_callback {
|
||||
None => {}
|
||||
Some(callback) => callback(Point2D(delta.x as f32, delta.y as f32)),
|
||||
}
|
||||
/// Helper function to handle a zoom.
|
||||
fn handle_zoom(&mut self, magnification: f32) {
|
||||
match self.zoom_callback {
|
||||
None => {}
|
||||
Some(callback) => callback(magnification),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -34,7 +34,8 @@ extern mod core_graphics;
|
|||
extern mod core_text;
|
||||
|
||||
use compositing::CompositorTask;
|
||||
use engine::{Engine, LoadUrlMsg};
|
||||
use engine::Engine;
|
||||
use script::engine_interface::{ExitMsg, LoadUrlMsg};
|
||||
|
||||
use core::comm::SharedChan;
|
||||
use gfx::opts;
|
||||
|
@ -121,7 +122,7 @@ fn run(opts: &Opts) {
|
|||
// Shut the engine down.
|
||||
debug!("master: Shut down");
|
||||
let (exit_response_from_engine, exit_chan) = comm::stream();
|
||||
engine_task.send(engine::ExitMsg(exit_chan));
|
||||
engine_task.send(ExitMsg(exit_chan));
|
||||
exit_response_from_engine.recv();
|
||||
}
|
||||
|
||||
|
|
|
@ -6,6 +6,14 @@
|
|||
|
||||
use geom::point::Point2D;
|
||||
use geom::size::Size2D;
|
||||
use gfx::compositor::RenderState;
|
||||
use script::compositor_interface::ReadyState;
|
||||
|
||||
pub enum WindowMouseEvent {
|
||||
WindowClickEvent(uint, Point2D<f32>),
|
||||
WindowMouseDownEvent(uint, Point2D<f32>),
|
||||
WindowMouseUpEvent(uint, Point2D<f32>),
|
||||
}
|
||||
|
||||
/// Type of the function that is called when the screen is to be redisplayed.
|
||||
pub type CompositeCallback = @fn();
|
||||
|
@ -16,15 +24,14 @@ pub type ResizeCallback = @fn(uint, uint);
|
|||
/// Type of the function that is called when a new URL is to be loaded.
|
||||
pub type LoadUrlCallback = @fn(&str);
|
||||
|
||||
/// Type of the function that is called when hit testing is to be performed.
|
||||
/// FIXME this currently does not discriminate between left and right clicks or any modifiers
|
||||
pub type ClickCallback = @fn(Point2D<f32>);
|
||||
/// Type of the function that is called when a mouse hit test is to be performed.
|
||||
pub type MouseCallback = @fn(WindowMouseEvent);
|
||||
|
||||
/// Type of the function that is called when the user scrolls.
|
||||
pub type ScrollCallback = @fn(Point2D<f32>);
|
||||
|
||||
///Type of the function that is called when the user zooms.
|
||||
pub type ZoomCallback = @fn(Point2D<f32>);
|
||||
pub type ZoomCallback = @fn(f32);
|
||||
|
||||
/// Methods for an abstract Application.
|
||||
pub trait ApplicationMethods {
|
||||
|
@ -46,7 +53,7 @@ pub trait WindowMethods<A> {
|
|||
/// Registers a callback to run when a new URL is to be loaded.
|
||||
pub fn set_load_url_callback(&mut self, new_load_url_callback: LoadUrlCallback);
|
||||
/// Registers a callback to run when the user clicks.
|
||||
pub fn set_click_callback(&mut self, new_click_callback: ClickCallback);
|
||||
pub fn set_mouse_callback(&mut self, new_mouse_callback: MouseCallback);
|
||||
/// Registers a callback to run when the user scrolls.
|
||||
pub fn set_scroll_callback(&mut self, new_scroll_callback: ScrollCallback);
|
||||
/// Registers a callback to run when the user zooms.
|
||||
|
@ -56,5 +63,9 @@ pub trait WindowMethods<A> {
|
|||
pub fn check_loop(@mut self);
|
||||
/// Schedules a redisplay at the next turn of the event loop.
|
||||
pub fn set_needs_display(@mut self);
|
||||
/// Sets the ready state of the current page.
|
||||
pub fn set_ready_state(@mut self, ready_state: ReadyState);
|
||||
/// Sets the render state of the current page.
|
||||
pub fn set_render_state(@mut self, render_state: RenderState);
|
||||
}
|
||||
|
||||
|
|
|
@ -5,11 +5,11 @@
|
|||
use core::comm::{Chan, Port};
|
||||
|
||||
pub fn spawn_listener<A: Owned>(f: ~fn(Port<A>)) -> Chan<A> {
|
||||
let (setup_po, setup_ch) = comm::stream();
|
||||
let (setup_port, setup_chan) = comm::stream();
|
||||
do task::spawn {
|
||||
let (po, ch) = comm::stream();
|
||||
setup_ch.send(ch);
|
||||
f(po);
|
||||
let (port, chan) = comm::stream();
|
||||
setup_chan.send(chan);
|
||||
f(port);
|
||||
}
|
||||
setup_po.recv()
|
||||
setup_port.recv()
|
||||
}
|
||||
|
|
19
src/components/script/compositor_interface.rs
Normal file
19
src/components/script/compositor_interface.rs
Normal 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/. */
|
||||
|
||||
//! The high-level interface from script to compositor. Using this abstract interface helps reduce
|
||||
/// coupling between these two components
|
||||
|
||||
pub enum ReadyState {
|
||||
/// 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,
|
||||
}
|
||||
|
||||
pub trait CompositorInterface : Clone {
|
||||
fn set_ready_state(&self, ReadyState);
|
||||
}
|
|
@ -12,7 +12,9 @@ use geom::point::Point2D;
|
|||
pub enum Event {
|
||||
ResizeEvent(uint, uint, comm::Chan<()>),
|
||||
ReflowEvent,
|
||||
ClickEvent(Point2D<f32>),
|
||||
ClickEvent(uint, Point2D<f32>),
|
||||
MouseDownEvent(uint, Point2D<f32>),
|
||||
MouseUpEvent(uint, Point2D<f32>),
|
||||
}
|
||||
|
||||
pub struct Event_ {
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
use dom::bindings::utils::WrapperCache;
|
||||
use dom::bindings::window;
|
||||
|
||||
use layout_interface::ReflowForScriptQuery;
|
||||
use script_task::{ExitMsg, FireTimerMsg, ScriptMsg, ScriptContext};
|
||||
|
||||
|
|
17
src/components/script/engine_interface.rs
Normal file
17
src/components/script/engine_interface.rs
Normal file
|
@ -0,0 +1,17 @@
|
|||
/* 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 engine. Using this abstract interface helps reduce
|
||||
/// coupling between these two components
|
||||
|
||||
use core::comm::{Chan, SharedChan};
|
||||
use std::net::url::Url;
|
||||
|
||||
pub type EngineTask = SharedChan<Msg>;
|
||||
|
||||
pub enum Msg {
|
||||
LoadUrlMsg(Url),
|
||||
ExitMsg(Chan<()>),
|
||||
}
|
||||
|
|
@ -43,6 +43,7 @@ pub fn spawn_css_parser(provenance: StylesheetProvenance,
|
|||
fn data_stream(provenance: StylesheetProvenance, resource_task: ResourceTask) -> DataStream {
|
||||
match provenance {
|
||||
UrlProvenance(url) => {
|
||||
debug!("cssparse: loading style sheet at %s", url.to_str());
|
||||
let (input_port, input_chan) = comm::stream();
|
||||
resource_task.send(Load(url, input_chan));
|
||||
resource_port_to_data_stream(input_port)
|
||||
|
|
|
@ -129,11 +129,13 @@ fn js_script_listener(to_parent: Chan<~[~[u8]]>,
|
|||
buf += data;
|
||||
}
|
||||
Done(Ok(*)) => {
|
||||
result_chan.send(buf);
|
||||
result_chan.send(Some(buf));
|
||||
break;
|
||||
}
|
||||
Done(Err(*)) => {
|
||||
error!("error loading script %s", url.to_str());
|
||||
result_chan.send(None);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -146,7 +148,7 @@ fn js_script_listener(to_parent: Chan<~[~[u8]]>,
|
|||
}
|
||||
}
|
||||
|
||||
let js_scripts = vec::map(result_vec, |result_port| result_port.recv());
|
||||
let js_scripts = vec::filter_map(result_vec, |result_port| result_port.recv());
|
||||
to_parent.send(js_scripts);
|
||||
}
|
||||
|
||||
|
|
|
@ -64,6 +64,8 @@ pub mod html {
|
|||
pub mod hubbub_html_parser;
|
||||
}
|
||||
|
||||
pub mod compositor_interface;
|
||||
pub mod engine_interface;
|
||||
pub mod layout_interface;
|
||||
pub mod script_task;
|
||||
|
||||
|
|
|
@ -5,9 +5,11 @@
|
|||
/// The script task is the task that owns the DOM in memory, runs JavaScript, and spawns parsing
|
||||
/// and layout tasks.
|
||||
|
||||
use compositor_interface::{ReadyState, Loading, PerformingLayout, FinishedLoading};
|
||||
use dom::bindings::utils::GlobalStaticData;
|
||||
use dom::document::Document;
|
||||
use dom::event::{Event, ResizeEvent, ReflowEvent, ClickEvent};
|
||||
use dom::element::Element;
|
||||
use dom::event::{Event, ResizeEvent, ReflowEvent, ClickEvent, MouseDownEvent, MouseUpEvent};
|
||||
use dom::node::{AbstractNode, ScriptView, define_bindings};
|
||||
use dom::window::Window;
|
||||
use layout_interface::{AddStylesheetMsg, DocumentDamage, DocumentDamageLevel, HitTestQuery};
|
||||
|
@ -15,6 +17,7 @@ use layout_interface::{HitTestResponse, LayoutQuery, LayoutResponse, LayoutTask}
|
|||
use layout_interface::{MatchSelectorsDocumentDamage, QueryMsg, Reflow, ReflowDocumentDamage};
|
||||
use layout_interface::{ReflowForDisplay, ReflowForScriptQuery, ReflowGoal, ReflowMsg};
|
||||
use layout_interface;
|
||||
use engine_interface::{EngineTask, LoadUrlMsg};
|
||||
|
||||
use core::cast::transmute;
|
||||
use core::cell::Cell;
|
||||
|
@ -37,6 +40,7 @@ use js;
|
|||
use servo_net::image_cache_task::ImageCacheTask;
|
||||
use servo_net::resource_task::ResourceTask;
|
||||
use servo_util::tree::TreeNodeRef;
|
||||
use servo_util::url::make_url;
|
||||
use std::net::url::Url;
|
||||
use std::net::url;
|
||||
|
||||
|
@ -50,6 +54,8 @@ pub enum ScriptMsg {
|
|||
SendEventMsg(Event),
|
||||
/// Fires a JavaScript timeout.
|
||||
FireTimerMsg(~TimerData),
|
||||
/// Notifies script that reflow is finished.
|
||||
ReflowCompleteMsg,
|
||||
/// Exits the engine.
|
||||
ExitMsg,
|
||||
}
|
||||
|
@ -64,12 +70,15 @@ impl ScriptTask {
|
|||
/// Creates a new script task.
|
||||
pub fn new(script_port: Port<ScriptMsg>,
|
||||
script_chan: SharedChan<ScriptMsg>,
|
||||
engine_task: EngineTask,
|
||||
//FIXME(rust #5192): workaround for lack of working ~Trait
|
||||
compositor_task: ~fn(ReadyState),
|
||||
layout_task: LayoutTask,
|
||||
resource_task: ResourceTask,
|
||||
image_cache_task: ImageCacheTask)
|
||||
-> ScriptTask {
|
||||
let (script_chan_copy, script_port) = (script_chan.clone(), Cell(script_port));
|
||||
|
||||
let compositor_task = Cell(compositor_task);
|
||||
// FIXME: rust#6399
|
||||
let mut the_task = task();
|
||||
the_task.sched_mode(SingleThreaded);
|
||||
|
@ -77,6 +86,8 @@ impl ScriptTask {
|
|||
let script_context = ScriptContext::new(layout_task.clone(),
|
||||
script_port.take(),
|
||||
script_chan_copy.clone(),
|
||||
engine_task.clone(),
|
||||
compositor_task.take(),
|
||||
resource_task.clone(),
|
||||
image_cache_task.clone());
|
||||
script_context.start();
|
||||
|
@ -116,6 +127,11 @@ pub struct ScriptContext {
|
|||
/// messages.
|
||||
script_chan: SharedChan<ScriptMsg>,
|
||||
|
||||
/// For communicating load url messages to the engine
|
||||
engine_task: EngineTask,
|
||||
/// For communicating loading messages to the compositor
|
||||
compositor_task: ~fn(ReadyState),
|
||||
|
||||
/// The JavaScript runtime.
|
||||
js_runtime: js::rust::rt,
|
||||
/// The JavaScript context.
|
||||
|
@ -167,6 +183,8 @@ impl ScriptContext {
|
|||
pub fn new(layout_task: LayoutTask,
|
||||
script_port: Port<ScriptMsg>,
|
||||
script_chan: SharedChan<ScriptMsg>,
|
||||
engine_task: EngineTask,
|
||||
compositor_task: ~fn(ReadyState),
|
||||
resource_task: ResourceTask,
|
||||
img_cache_task: ImageCacheTask)
|
||||
-> @mut ScriptContext {
|
||||
|
@ -190,6 +208,9 @@ impl ScriptContext {
|
|||
script_port: script_port,
|
||||
script_chan: script_chan,
|
||||
|
||||
engine_task: engine_task,
|
||||
compositor_task: compositor_task,
|
||||
|
||||
js_runtime: js_runtime,
|
||||
js_context: js_context,
|
||||
js_compartment: compartment,
|
||||
|
@ -243,6 +264,10 @@ impl ScriptContext {
|
|||
self.handle_fire_timer_msg(timer_data);
|
||||
true
|
||||
}
|
||||
ReflowCompleteMsg => {
|
||||
self.handle_reflow_complete_msg();
|
||||
true
|
||||
}
|
||||
ExitMsg => {
|
||||
self.handle_exit_msg();
|
||||
false
|
||||
|
@ -287,6 +312,12 @@ impl ScriptContext {
|
|||
self.reflow(ReflowForScriptQuery)
|
||||
}
|
||||
|
||||
/// Handles a notification that reflow completed.
|
||||
fn handle_reflow_complete_msg(&mut self) {
|
||||
self.layout_join_port = None;
|
||||
self.set_ready_state(FinishedLoading)
|
||||
}
|
||||
|
||||
/// Handles a request to exit the script task and shut down layout.
|
||||
fn handle_exit_msg(&mut self) {
|
||||
self.join_layout();
|
||||
|
@ -297,6 +328,12 @@ impl ScriptContext {
|
|||
self.layout_task.chan.send(layout_interface::ExitMsg)
|
||||
}
|
||||
|
||||
// tells the compositor when loading starts and finishes
|
||||
// FIXME ~compositor_interface doesn't work right now, which is why this is necessary
|
||||
fn set_ready_state(&self, msg: ReadyState) {
|
||||
(self.compositor_task)(msg);
|
||||
}
|
||||
|
||||
/// The entry point to document loading. Defines bindings, sets up the window and document
|
||||
/// objects, parses HTML and CSS, and kicks off initial layout.
|
||||
fn load(&mut self, url: Url) {
|
||||
|
@ -308,6 +345,7 @@ impl ScriptContext {
|
|||
self.bindings_initialized = true
|
||||
}
|
||||
|
||||
self.set_ready_state(Loading);
|
||||
// Parse HTML.
|
||||
//
|
||||
// Note: We can parse the next document in parallel with any previous documents.
|
||||
|
@ -398,6 +436,9 @@ impl ScriptContext {
|
|||
// Now, join the layout so that they will see the latest changes we have made.
|
||||
self.join_layout();
|
||||
|
||||
// Tell the user that we're performing layout.
|
||||
self.set_ready_state(PerformingLayout);
|
||||
|
||||
// Layout will let us know when it's done.
|
||||
let (join_port, join_chan) = comm::stream();
|
||||
self.layout_join_port = Some(join_port);
|
||||
|
@ -410,8 +451,8 @@ impl ScriptContext {
|
|||
document_root: root_frame.document.root,
|
||||
url: copy root_frame.url,
|
||||
goal: goal,
|
||||
script_chan: self.script_chan.clone(),
|
||||
window_size: self.window_size,
|
||||
script_chan: self.script_chan.clone(),
|
||||
script_join_chan: join_chan,
|
||||
damage: replace(&mut self.damage, None).unwrap(),
|
||||
};
|
||||
|
@ -503,7 +544,7 @@ impl ScriptContext {
|
|||
}
|
||||
}
|
||||
|
||||
ClickEvent(point) => {
|
||||
ClickEvent(button, point) => {
|
||||
debug!("ClickEvent: clicked at %?", point);
|
||||
let root = match self.root_frame {
|
||||
Some(ref frame) => frame.document.root,
|
||||
|
@ -511,14 +552,51 @@ impl ScriptContext {
|
|||
};
|
||||
match self.query_layout(HitTestQuery(root, point)) {
|
||||
Ok(node) => match node {
|
||||
HitTestResponse(node) => debug!("clicked on %?", node.debug_str()),
|
||||
HitTestResponse(node) => {
|
||||
debug!("clicked on %?", node.debug_str());
|
||||
let mut node = node;
|
||||
// traverse node generations until a node that is an element is found
|
||||
while !node.is_element() {
|
||||
match node.parent_node() {
|
||||
Some(parent) => {
|
||||
node = parent;
|
||||
}
|
||||
None => break
|
||||
}
|
||||
}
|
||||
if node.is_element() {
|
||||
do node.with_imm_element |element| {
|
||||
match element.tag_name {
|
||||
~"a" => self.load_url_from_element(element),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => fail!(~"unexpected layout reply")
|
||||
},
|
||||
Err(()) => {
|
||||
println(fmt!("layout query error"));
|
||||
debug!(fmt!("layout query error"));
|
||||
}
|
||||
};
|
||||
}
|
||||
MouseDownEvent(*) => {}
|
||||
MouseUpEvent(*) => {}
|
||||
}
|
||||
}
|
||||
|
||||
priv fn load_url_from_element(&self, element: &Element) {
|
||||
// if the node's element is "a," load url from href attr
|
||||
for element.attrs.each |attr| {
|
||||
if attr.name == ~"href" {
|
||||
debug!("clicked on link to %?", attr.value);
|
||||
let current_url = match self.root_frame {
|
||||
Some(ref frame) => Some(frame.url.clone()),
|
||||
None => None
|
||||
};
|
||||
let url = make_url(attr.value.clone(), current_url);
|
||||
self.engine_task.send(LoadUrlMsg(url));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,62 +6,133 @@
|
|||
use std::time::precise_time_ns;
|
||||
use core::cell::Cell;
|
||||
use core::comm::{Port, SharedChan};
|
||||
use core::os::getenv;
|
||||
use std::sort::tim_sort;
|
||||
|
||||
pub type ProfilerChan = SharedChan<ProfilerMsg>;
|
||||
pub type ProfilerPort = Port<ProfilerMsg>;
|
||||
|
||||
#[deriving(Eq)]
|
||||
pub enum ProfilerCategory {
|
||||
CompositingCategory,
|
||||
LayoutPerformCategory,
|
||||
LayoutQueryCategory,
|
||||
LayoutPerformCategory,
|
||||
LayoutAuxInitCategory,
|
||||
LayoutSelectorMatchCategory,
|
||||
LayoutTreeBuilderCategory,
|
||||
LayoutMainCategory,
|
||||
LayoutShapingCategory,
|
||||
LayoutDispListBuildCategory,
|
||||
GfxRegenAvailableFontsCategory,
|
||||
RenderingPrepBuffCategory,
|
||||
RenderingWaitSubtasksCategory,
|
||||
RenderingCategory,
|
||||
// hackish but helps prevent errors when adding new categories
|
||||
NUM_BUCKETS,
|
||||
}
|
||||
// change this whenever buckets are added/rm'd
|
||||
static NUM_BUCKETS: uint = 12;
|
||||
// FIXME(#5873) this should be initialized by a NUM_BUCKETS cast,
|
||||
static BUCKETS: uint = 13;
|
||||
|
||||
pub type ProfilerChan = SharedChan<(ProfilerCategory, uint)>;
|
||||
pub type ProfilerPort = Port<(ProfilerCategory, uint)>;
|
||||
pub enum ProfilerMsg {
|
||||
// Normal message used for reporting time
|
||||
TimeMsg(ProfilerCategory, f64),
|
||||
// Message used to force print the profiling metrics
|
||||
ForcePrintMsg,
|
||||
}
|
||||
|
||||
// front-end representation of the profiler used to communicate with the profiler context
|
||||
pub struct ProfilerTask {
|
||||
chan: ProfilerChan,
|
||||
}
|
||||
|
||||
// back end of the profiler that handles data aggregation and performance metrics
|
||||
pub struct ProfilerContext {
|
||||
port: ProfilerPort,
|
||||
buckets: ~[(ProfilerCategory, ~[f64])],
|
||||
verbose: bool,
|
||||
period: f64,
|
||||
last_print: f64,
|
||||
}
|
||||
|
||||
impl ProfilerCategory {
|
||||
|
||||
// convenience function to not have to cast every time
|
||||
pub fn num_buckets() -> uint {
|
||||
NUM_BUCKETS as uint
|
||||
}
|
||||
|
||||
// enumeration of all ProfilerCategory types
|
||||
// FIXME(tkuehn): this is ugly and error-prone,
|
||||
// but currently we lack better alternatives without an enum enumeration
|
||||
priv fn empty_buckets() -> ~[(ProfilerCategory, ~[f64])] {
|
||||
let mut vec = ~[];
|
||||
vec.push((CompositingCategory, ~[]));
|
||||
vec.push((LayoutQueryCategory, ~[]));
|
||||
vec.push((LayoutPerformCategory, ~[]));
|
||||
vec.push((LayoutAuxInitCategory, ~[]));
|
||||
vec.push((LayoutSelectorMatchCategory, ~[]));
|
||||
vec.push((LayoutTreeBuilderCategory, ~[]));
|
||||
vec.push((LayoutMainCategory, ~[]));
|
||||
vec.push((LayoutShapingCategory, ~[]));
|
||||
vec.push((LayoutDispListBuildCategory, ~[]));
|
||||
vec.push((GfxRegenAvailableFontsCategory, ~[]));
|
||||
vec.push((RenderingPrepBuffCategory, ~[]));
|
||||
vec.push((RenderingWaitSubtasksCategory, ~[]));
|
||||
vec.push((RenderingCategory, ~[]));
|
||||
|
||||
ProfilerCategory::check_order(vec);
|
||||
vec
|
||||
}
|
||||
|
||||
priv fn check_order(vec: &[(ProfilerCategory, ~[f64])]) {
|
||||
for vec.each |&(category, _)| {
|
||||
if category != vec[category as uint].first() {
|
||||
fail!("Enum category does not match bucket index. This is a bug.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// some categories are subcategories of LayoutPerformCategory
|
||||
// and should be printed to indicate this
|
||||
pub fn format(self) -> ~str {
|
||||
let padding = match self {
|
||||
LayoutAuxInitCategory | LayoutSelectorMatchCategory | LayoutTreeBuilderCategory |
|
||||
LayoutMainCategory | LayoutDispListBuildCategory | LayoutShapingCategory=> " - ",
|
||||
_ => ""
|
||||
};
|
||||
fmt!("%s%?", padding, self)
|
||||
}
|
||||
}
|
||||
|
||||
impl ProfilerTask {
|
||||
pub fn new(prof_port: ProfilerPort,
|
||||
prof_chan: ProfilerChan)
|
||||
pub fn new(profiler_port: ProfilerPort,
|
||||
profiler_chan: ProfilerChan,
|
||||
period: Option<f64>)
|
||||
-> ProfilerTask {
|
||||
let prof_port = Cell(prof_port);
|
||||
let profiler_port = Cell(profiler_port);
|
||||
|
||||
do spawn {
|
||||
let mut profiler_context = ProfilerContext::new(prof_port.take());
|
||||
let mut profiler_context = ProfilerContext::new(profiler_port.take(), period);
|
||||
profiler_context.start();
|
||||
}
|
||||
|
||||
ProfilerTask {
|
||||
chan: prof_chan
|
||||
chan: profiler_chan
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ProfilerContext {
|
||||
port: ProfilerPort,
|
||||
buckets: [~[uint], ..NUM_BUCKETS],
|
||||
verbose: Option<~str>,
|
||||
mut last_print: u64,
|
||||
}
|
||||
|
||||
impl ProfilerContext {
|
||||
pub fn new(port: ProfilerPort) -> ProfilerContext {
|
||||
pub fn new(port: ProfilerPort, period: Option<f64>) -> ProfilerContext {
|
||||
let (verbose, period) = match period {
|
||||
Some(period) => (true, period),
|
||||
None => (false, 0f64)
|
||||
};
|
||||
ProfilerContext {
|
||||
port: port,
|
||||
buckets: [~[], ..NUM_BUCKETS],
|
||||
verbose: getenv("SERVO_PROFILER"),
|
||||
last_print: 0,
|
||||
buckets: ProfilerCategory::empty_buckets(),
|
||||
verbose: verbose,
|
||||
period: period,
|
||||
last_print: 0f64,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -72,52 +143,64 @@ impl ProfilerContext {
|
|||
}
|
||||
}
|
||||
|
||||
priv fn handle_msg(&mut self, msg: (ProfilerCategory, uint)) {
|
||||
let (prof_msg, t) = msg;
|
||||
self.buckets[prof_msg as uint].push(t);
|
||||
if self.verbose.is_some() {
|
||||
let cur_time = precise_time_ns() / 1000000000u64;
|
||||
if cur_time - self.last_print > 5 {
|
||||
self.last_print = cur_time;
|
||||
let mut i = 0;
|
||||
for self.buckets.each |bucket| {
|
||||
let prof_msg = match i {
|
||||
// must be in same order as ProfilerCategory
|
||||
0 => CompositingCategory,
|
||||
1 => LayoutPerformCategory,
|
||||
2 => LayoutQueryCategory,
|
||||
3 => LayoutAuxInitCategory,
|
||||
4 => LayoutSelectorMatchCategory,
|
||||
5 => LayoutTreeBuilderCategory,
|
||||
6 => LayoutMainCategory,
|
||||
7 => LayoutDispListBuildCategory,
|
||||
8 => GfxRegenAvailableFontsCategory,
|
||||
9 => RenderingPrepBuffCategory,
|
||||
10 => RenderingWaitSubtasksCategory,
|
||||
11 => RenderingCategory,
|
||||
_ => fail!()
|
||||
};
|
||||
io::println(fmt!("%?: %f", prof_msg,
|
||||
(bucket.foldl(0 as uint, |a, b| a + *b) as float) /
|
||||
(bucket.len() as float)));
|
||||
i += 1;
|
||||
priv fn handle_msg(&mut self, msg: ProfilerMsg) {
|
||||
match msg {
|
||||
TimeMsg(category, t) => {
|
||||
// FIXME(#3874): this should be a let (cat, ref mut bucket) = ...,
|
||||
// not a match
|
||||
match self.buckets[category as uint] {
|
||||
(_, ref mut data) => {
|
||||
data.push(t);
|
||||
}
|
||||
}
|
||||
|
||||
if self.verbose {
|
||||
let cur_time = precise_time_ns() as f64 / 1000000000f64;
|
||||
if cur_time - self.last_print > self.period {
|
||||
self.last_print = cur_time;
|
||||
self.print_buckets();
|
||||
}
|
||||
}
|
||||
}
|
||||
ForcePrintMsg => self.print_buckets(),
|
||||
};
|
||||
}
|
||||
|
||||
priv fn print_buckets(&mut self) {
|
||||
println(fmt!("%31s %15s %15s %15s %15s %15s",
|
||||
"_category (ms)_", "_mean (ms)_", "_median (ms)_",
|
||||
"_min (ms)_", "_max (ms)_", "_bucket size_"));
|
||||
for vec::each_mut(self.buckets) |bucket| {
|
||||
match *bucket {
|
||||
(category, ref mut data) => {
|
||||
tim_sort(*data);
|
||||
let data_len = data.len();
|
||||
if data_len > 0 {
|
||||
let (mean, median, min, max) =
|
||||
(data.foldl(0f64, |a, b| a + *b) / (data_len as f64),
|
||||
data[data_len / 2],
|
||||
data.min(),
|
||||
data.max());
|
||||
println(fmt!("%-30s: %15.4? %15.4? %15.4? %15.4? %15u",
|
||||
category.format(), mean, median, min, max, data_len));
|
||||
}
|
||||
}
|
||||
io::println("");
|
||||
}
|
||||
}
|
||||
|
||||
println("");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn profile<T>(cat: ProfilerCategory,
|
||||
prof_chan: ProfilerChan,
|
||||
|
||||
pub fn profile<T>(category: ProfilerCategory,
|
||||
profiler_chan: ProfilerChan,
|
||||
callback: &fn() -> T)
|
||||
-> T {
|
||||
let start_time = precise_time_ns();
|
||||
let val = callback();
|
||||
let end_time = precise_time_ns();
|
||||
let ms = ((end_time - start_time) / 1000000u64) as uint;
|
||||
prof_chan.send((cat, ms));
|
||||
let ms = ((end_time - start_time) as f64 / 1000000f64);
|
||||
profiler_chan.send(TimeMsg(category, ms));
|
||||
return val;
|
||||
}
|
||||
|
||||
|
@ -125,9 +208,9 @@ pub fn time<T>(msg: &str, callback: &fn() -> T) -> T{
|
|||
let start_time = precise_time_ns();
|
||||
let val = callback();
|
||||
let end_time = precise_time_ns();
|
||||
let ms = ((end_time - start_time) / 1000000u64) as uint;
|
||||
if ms >= 5 {
|
||||
debug!("%s took %u ms", msg, ms);
|
||||
let ms = ((end_time - start_time) as f64 / 1000000f64);
|
||||
if ms >= 5f64 {
|
||||
debug!("%s took %? ms", msg, ms);
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
|
|
@ -30,8 +30,13 @@ pub fn make_url(str_url: ~str, current_url: Option<Url>) -> Url {
|
|||
} else {
|
||||
let current_url = current_url.get();
|
||||
debug!("make_url: current_url: %?", current_url);
|
||||
if current_url.path.is_empty() || current_url.path.ends_with("/") {
|
||||
current_url.scheme + "://" + current_url.host + "/" + str_url
|
||||
if str_url.starts_with("//") {
|
||||
current_url.scheme + ":" + str_url
|
||||
} else if current_url.path.is_empty() ||
|
||||
str_url.starts_with("/") {
|
||||
current_url.scheme + "://" +
|
||||
current_url.host + "/" +
|
||||
str_url.trim_left_chars([ '/' ])
|
||||
} else {
|
||||
let mut path = ~[];
|
||||
for str::each_split_char(current_url.path, '/') |p| {
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit 61c99b4b1aa8653b8ee8511b6c15099de441ac44
|
||||
Subproject commit d916b9796668d4bb46a3b1b4976c95ab7da79837
|
|
@ -1 +1 @@
|
|||
Subproject commit 2f3f03cbafb88608d4f89216da7172d3b754fbef
|
||||
Subproject commit 865f539114383a021822583801e8362faf916699
|
|
@ -1 +1 @@
|
|||
Subproject commit ebf1cc8f2e8a39f31da2575981fad966aa7da904
|
||||
Subproject commit efba52c020945e08f00bebf8ff54ec292e95eced
|
|
@ -1 +1 @@
|
|||
Subproject commit eb35e3957834264e99f42274f9c5c442e747ed3b
|
||||
Subproject commit 453bf81e021008f5eba29b135f07f4529e6c8b2e
|
|
@ -1 +1 @@
|
|||
Subproject commit 74c01163b8712dcfc2b567cc71b6819ff9a2bd9f
|
||||
Subproject commit f4ec5a4590a39804ef38faaab4b6749e5c7eb4c6
|
|
@ -1 +1 @@
|
|||
Subproject commit d722188de3876ed748382965eb4f300fc1b78bf8
|
||||
Subproject commit da248d3f5b3ed6d9e804c543563be8e34baf1673
|
|
@ -1 +1 @@
|
|||
Subproject commit 3565b32ba3d15d31b02cc76bdf76d6b13fc88451
|
||||
Subproject commit 325cd5197ed953f5c7c9317111b20ec1599eaffe
|
|
@ -1 +1 @@
|
|||
Subproject commit 725cf233061c9598fc7c27cdc1d6278742396a48
|
||||
Subproject commit 8ba903a1af20ce461d4f4033ec1092a229bc7482
|
25
src/test/html/box-model-smoketest.html
Normal file
25
src/test/html/box-model-smoketest.html
Normal file
|
@ -0,0 +1,25 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>box model smoketest</title>
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
#outer {
|
||||
background-color: red;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
#inner {
|
||||
margin: 16px;
|
||||
border: solid green 16px;
|
||||
padding: 16px;
|
||||
background-color: blue;
|
||||
color: white;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body><div id=outer><div id=inner>Ten points for Gryffindor</div></div></body>
|
||||
</html>
|
||||
|
20
src/test/html/test_class_helloworld.html
Normal file
20
src/test/html/test_class_helloworld.html
Normal file
|
@ -0,0 +1,20 @@
|
|||
<html>
|
||||
<head>
|
||||
<style type="text/css">
|
||||
.c1 { color: red; }
|
||||
.c2 { background: blue; }
|
||||
#i1 { color: green; }
|
||||
</style>
|
||||
<script>
|
||||
document.write("ok");
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div>
|
||||
<div id='i1'>Hello</div>
|
||||
<div class='c1'>World</div>
|
||||
<div class='c2'>Hello</div>
|
||||
<div class='c1 c2'>World</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -12,3 +12,4 @@ for (var i = 0; i < count; i++) {
|
|||
}
|
||||
var stop = new Date();
|
||||
window.alert((stop - start) / count * 1e6 + " ns/layout");
|
||||
window.close();
|
||||
|
|
|
@ -9,8 +9,7 @@
|
|||
<body>
|
||||
|
||||
<div>
|
||||
<div>Hello</div>
|
||||
|
||||
<div>Hello!</div>
|
||||
<div>World</div>
|
||||
</div>
|
||||
</body>
|
Loading…
Add table
Add a link
Reference in a new issue