compositing: Implement cursor per CSS3-UI § 8.1.1 in the CEF/Mac port.

I'm not sure how we want to handle Linux cursors, and GLFW has no
ability to set cursors (short of disabling it and managing it yourself).
This commit is contained in:
Patrick Walton 2014-12-12 07:18:16 -08:00
parent 636641f905
commit 7371e0b8e3
23 changed files with 564 additions and 171 deletions

View file

@ -3,8 +3,8 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use compositor_layer::{CompositorData, CompositorLayer, WantsScrollEventsFlag};
use compositor_task::{CompositorEventListener, CompositorProxy, CompositorReceiver, CompositorTask};
use compositor_task::{LayerProperties, Msg};
use compositor_task::{CompositorEventListener, CompositorProxy, CompositorReceiver};
use compositor_task::{CompositorTask, LayerProperties, Msg};
use constellation::{FrameId, FrameTreeDiff, SendableFrameTree};
use pipeline::CompositionPipeline;
use scrolling::ScrollingTimerProxy;
@ -338,6 +338,10 @@ impl<Window: WindowMethods> IOCompositor<Window> {
self.window.handle_key(key, modified);
}
(Msg::SetCursor(cursor), ShutdownState::NotShuttingDown) => {
self.window.set_cursor(cursor)
}
// When we are shutting_down, we need to avoid performing operations
// such as Paint that may crash because we have begun tearing down
// the rest of our resources.

View file

@ -21,6 +21,7 @@ use servo_msg::compositor_msg::{Epoch, LayerId, LayerMetadata, ReadyState};
use servo_msg::compositor_msg::{PaintListener, PaintState, ScriptListener, ScrollPolicy};
use servo_msg::constellation_msg::{ConstellationChan, LoadData, PipelineId};
use servo_msg::constellation_msg::{Key, KeyState, KeyModifiers, Pressed};
use servo_util::cursor::Cursor;
use servo_util::memory::MemoryProfilerChan;
use servo_util::time::TimeProfilerChan;
use std::comm::{channel, Sender, Receiver};
@ -213,6 +214,8 @@ pub enum Msg {
ScrollTimeout(u64),
/// Sends an unconsumed key event back to the compositor.
KeyEvent(Key, KeyModifiers),
/// Changes the cursor.
SetCursor(Cursor),
}
impl Show for Msg {
@ -231,11 +234,12 @@ impl Show for Msg {
Msg::ChangePageTitle(..) => write!(f, "ChangePageTitle"),
Msg::ChangePageLoadData(..) => write!(f, "ChangePageLoadData"),
Msg::PaintMsgDiscarded(..) => write!(f, "PaintMsgDiscarded"),
Msg::FrameTreeUpdate(..) => write!(f, "FrameTreeUpdate"),
Msg::SetIds(..) => write!(f, "SetIds"),
Msg::FrameTreeUpdate(..) => write!(f, "FrameTreeUpdateMsg"),
Msg::LoadComplete => write!(f, "LoadComplete"),
Msg::ScrollTimeout(..) => write!(f, "ScrollTimeout"),
Msg::KeyEvent(..) => write!(f, "KeyEvent"),
Msg::SetCursor(..) => write!(f, "SetCursor"),
}
}
}

View file

@ -18,20 +18,21 @@ use libc;
use script_traits::{mod, GetTitleMsg, ResizeMsg, ResizeInactiveMsg, ExitPipelineMsg, SendEventMsg};
use script_traits::{ScriptControlChan, ScriptTaskFactory};
use servo_msg::compositor_msg::LayerId;
use servo_msg::constellation_msg::{ConstellationChan, ExitMsg, FailureMsg, Failure, FrameRectMsg};
use servo_msg::constellation_msg::{GetPipelineTitleMsg};
use servo_msg::constellation_msg::{mod, ConstellationChan, ExitMsg, FailureMsg, Failure};
use servo_msg::constellation_msg::{FrameRectMsg, GetPipelineTitleMsg};
use servo_msg::constellation_msg::{IFrameSandboxState, IFrameUnsandboxed, InitLoadUrlMsg};
use servo_msg::constellation_msg::{KeyEvent, Key, KeyState, KeyModifiers, LoadCompleteMsg};
use servo_msg::constellation_msg::{LoadData, LoadUrlMsg, NavigateMsg, NavigationType};
use servo_msg::constellation_msg::{PainterReadyMsg, PipelineId, ResizedWindowMsg};
use servo_msg::constellation_msg::{ScriptLoadedURLInIFrameMsg, SubpageId, WindowSizeData};
use servo_msg::constellation_msg::{ScriptLoadedURLInIFrameMsg, SetCursorMsg, SubpageId};
use servo_msg::constellation_msg::{WindowSizeData};
use servo_msg::constellation_msg::Msg as ConstellationMsg;
use servo_msg::constellation_msg;
use servo_net::image_cache_task::{ImageCacheTask, ImageCacheTaskClient};
use servo_net::resource_task::ResourceTask;
use servo_net::resource_task;
use servo_net::storage_task::StorageTask;
use servo_net::storage_task;
use servo_util::cursor::Cursor;
use servo_util::geometry::{PagePx, ViewportPx};
use servo_util::opts;
use servo_util::task::spawn_named;
@ -472,6 +473,7 @@ impl<LTF: LayoutTaskFactory, STF: ScriptTaskFactory> Constellation<LTF, STF> {
subpage_id,
sandbox);
}
SetCursorMsg(cursor) => self.handle_set_cursor_msg(cursor),
// Load a new page, usually -- but not always -- from a mouse click or typed url
// If there is already a pending page (self.pending_frames), it will not be overridden;
// However, if the id is not encompassed by another change, it will be.
@ -754,6 +756,10 @@ impl<LTF: LayoutTaskFactory, STF: ScriptTaskFactory> Constellation<LTF, STF> {
self.pipelines.insert(pipeline.id, pipeline);
}
fn handle_set_cursor_msg(&mut self, cursor: Cursor) {
self.compositor_proxy.send(CompositorMsg::SetCursor(cursor))
}
fn handle_load_url_msg(&mut self, source_id: PipelineId, load_data: LoadData) {
let url = load_data.url.to_string();
debug!("Constellation: received message to load {:s}", url);

View file

@ -99,10 +99,18 @@ impl CompositorEventListener for NullCompositor {
Msg::CreateOrUpdateRootLayer(..) |
Msg::CreateOrUpdateDescendantLayer(..) |
Msg::SetLayerOrigin(..) | Msg::Paint(..) |
Msg::ChangeReadyState(..) | Msg::ChangePaintState(..) | Msg::ScrollFragmentPoint(..) |
Msg::LoadComplete | Msg::PaintMsgDiscarded(..) | Msg::ScrollTimeout(..) | Msg::ChangePageTitle(..) |
Msg::ChangePageLoadData(..) | Msg::KeyEvent(..) => ()
Msg::SetLayerOrigin(..) |
Msg::Paint(..) |
Msg::ChangeReadyState(..) |
Msg::ChangePaintState(..) |
Msg::ScrollFragmentPoint(..) |
Msg::LoadComplete |
Msg::PaintMsgDiscarded(..) |
Msg::ScrollTimeout(..) |
Msg::ChangePageTitle(..) |
Msg::ChangePageLoadData(..) |
Msg::KeyEvent(..) |
Msg::SetCursor(..) => {}
}
true
}

View file

@ -13,6 +13,7 @@ use layers::geometry::DevicePixel;
use layers::platform::surface::NativeGraphicsMetadata;
use servo_msg::compositor_msg::{PaintState, ReadyState};
use servo_msg::constellation_msg::{Key, KeyState, KeyModifiers, LoadData};
use servo_util::cursor::Cursor;
use servo_util::geometry::ScreenPx;
use std::fmt::{FormatError, Formatter, Show};
use std::rc::Rc;
@ -124,6 +125,9 @@ pub trait WindowMethods {
/// proceed and false if it should not.
fn prepare_for_composite(&self) -> bool;
/// Sets the cursor to be used in the window.
fn set_cursor(&self, cursor: Cursor);
/// Process a key event.
fn handle_key(&self, key: Key, mods: KeyModifiers);
}

View file

@ -14,12 +14,13 @@
//! They are therefore not exactly analogous to constructs like Skia pictures, which consist of
//! low-level drawing primitives.
use self::DisplayItem::*;
use self::DisplayItemIterator::*;
#![deny(unsafe_blocks)]
use color::Color;
use display_list::optimizer::DisplayListOptimizer;
use paint_context::{PaintContext, ToAzureRect};
use self::DisplayItem::*;
use self::DisplayItemIterator::*;
use text::glyph::CharIndex;
use text::TextRun;
@ -28,17 +29,18 @@ use collections::dlist::{mod, DList};
use geom::{Point2D, Rect, SideOffsets2D, Size2D, Matrix2D};
use libc::uintptr_t;
use paint_task::PaintLayer;
use script_traits::UntrustedNodeAddress;
use servo_msg::compositor_msg::LayerId;
use servo_net::image::base::Image;
use servo_util::cursor::Cursor;
use servo_util::dlist as servo_dlist;
use servo_util::geometry::{mod, Au, ZERO_POINT};
use servo_util::range::Range;
use servo_util::smallvec::{SmallVec, SmallVec8};
use std::fmt;
use std::mem;
use std::slice::Items;
use style::ComputedValues;
use style::computed_values::border_style;
use style::computed_values::cursor::{AutoCursor, SpecifiedCursor};
use sync::Arc;
// It seems cleaner to have layout code not mention Azure directly, so let's just reexport this for
@ -338,20 +340,45 @@ impl StackingContext {
/// upon entry to this function.
pub fn hit_test(&self,
point: Point2D<Au>,
result: &mut Vec<UntrustedNodeAddress>,
result: &mut Vec<DisplayItemMetadata>,
topmost_only: bool) {
fn hit_test_in_list<'a,I>(point: Point2D<Au>,
result: &mut Vec<UntrustedNodeAddress>,
result: &mut Vec<DisplayItemMetadata>,
topmost_only: bool,
mut iterator: I)
where I: Iterator<&'a DisplayItem> {
for item in iterator {
if geometry::rect_contains_point(item.base().clip_rect, point) &&
geometry::rect_contains_point(item.bounds(), point) {
result.push(item.base().node.to_untrusted_node_address());
if topmost_only {
return
if !geometry::rect_contains_point(item.base().clip_rect, point) {
// Clipped out.
continue
}
if !geometry::rect_contains_point(item.bounds(), point) {
// Can't possibly hit.
continue
}
match *item {
BorderDisplayItemClass(ref border) => {
// If the point is inside the border, it didn't hit the border!
let interior_rect =
Rect(Point2D(border.base.bounds.origin.x + border.border_widths.left,
border.base.bounds.origin.y + border.border_widths.top),
Size2D(border.base.bounds.size.width -
(border.border_widths.left +
border.border_widths.right),
border.base.bounds.size.height -
(border.border_widths.top +
border.border_widths.bottom)));
if geometry::rect_contains_point(interior_rect, point) {
continue
}
}
_ => {}
}
// We found a hit!
result.push(item.base().metadata);
if topmost_only {
return
}
}
}
@ -447,8 +474,8 @@ pub struct BaseDisplayItem {
/// The boundaries of the display item, in layer coordinates.
pub bounds: Rect<Au>,
/// The originating DOM node.
pub node: OpaqueNode,
/// Metadata attached to this display item.
pub metadata: DisplayItemMetadata,
/// The rectangle to clip to.
///
@ -459,15 +486,45 @@ pub struct BaseDisplayItem {
impl BaseDisplayItem {
#[inline(always)]
pub fn new(bounds: Rect<Au>, node: OpaqueNode, clip_rect: Rect<Au>) -> BaseDisplayItem {
pub fn new(bounds: Rect<Au>, metadata: DisplayItemMetadata, clip_rect: Rect<Au>)
-> BaseDisplayItem {
BaseDisplayItem {
bounds: bounds,
node: node,
metadata: metadata,
clip_rect: clip_rect,
}
}
}
/// Metadata attached to each display item. This is useful for performing auxiliary tasks with
/// the display list involving hit testing: finding the originating DOM node and determining the
/// cursor to use when the element is hovered over.
#[deriving(Clone)]
pub struct DisplayItemMetadata {
/// The DOM node from which this display item originated.
pub node: OpaqueNode,
/// The value of the `cursor` property when the mouse hovers over this display item.
pub cursor: Cursor,
}
impl DisplayItemMetadata {
/// Creates a new set of display metadata for a display item constributed by a DOM node.
/// `default_cursor` specifies the cursor to use if `cursor` is `auto`. Typically, this will
/// be `PointerCursor`, but for text display items it may be `TextCursor` or
/// `VerticalTextCursor`.
#[inline]
pub fn new(node: OpaqueNode, style: &ComputedValues, default_cursor: Cursor)
-> DisplayItemMetadata {
DisplayItemMetadata {
node: node,
cursor: match style.get_pointing().cursor {
AutoCursor => default_cursor,
SpecifiedCursor(cursor) => cursor,
},
}
}
}
/// Paints a solid color.
#[deriving(Clone)]
pub struct SolidColorDisplayItem {
@ -738,24 +795,8 @@ impl fmt::Show for DisplayItem {
BoxShadowDisplayItemClass(_) => "BoxShadow",
},
self.base().bounds,
self.base().node.id()
self.base().metadata.node.id()
)
}
}
pub trait OpaqueNodeMethods {
/// Converts this node to an `UntrustedNodeAddress`. An `UntrustedNodeAddress` is just the type
/// of node that script expects to receive in a hit test.
fn to_untrusted_node_address(&self) -> UntrustedNodeAddress;
}
impl OpaqueNodeMethods for OpaqueNode {
fn to_untrusted_node_address(&self) -> UntrustedNodeAddress {
unsafe {
let OpaqueNode(addr) = *self;
let addr: UntrustedNodeAddress = mem::transmute(addr);
addr
}
}
}

View file

@ -24,7 +24,7 @@ use geom::{Point2D, Rect, Size2D, SideOffsets2D};
use gfx::color;
use gfx::display_list::{BOX_SHADOW_INFLATION_FACTOR, BaseDisplayItem, BorderDisplayItem};
use gfx::display_list::{BorderDisplayItemClass, BorderRadii, BoxShadowDisplayItem};
use gfx::display_list::{BoxShadowDisplayItemClass, DisplayItem, DisplayList};
use gfx::display_list::{BoxShadowDisplayItemClass, DisplayItem, DisplayItemMetadata, DisplayList};
use gfx::display_list::{GradientDisplayItem, GradientDisplayItemClass};
use gfx::display_list::{GradientStop, ImageDisplayItem, ImageDisplayItemClass, LineDisplayItem};
use gfx::display_list::{LineDisplayItemClass, SidewaysLeft};
@ -34,6 +34,7 @@ use gfx::paint_task::PaintLayer;
use servo_msg::compositor_msg::{FixedPosition, Scrollable};
use servo_msg::constellation_msg::{ConstellationChan, FrameRectMsg};
use servo_net::image::holder::ImageHolder;
use servo_util::cursor::{DefaultCursor, TextCursor, VerticalTextCursor};
use servo_util::geometry::{mod, Au, ZERO_POINT, ZERO_RECT};
use servo_util::logical_geometry::{LogicalRect, WritingMode};
use servo_util::opts;
@ -132,6 +133,7 @@ pub trait FragmentDisplayListBuilding {
clip_rect: &Rect<Au>);
fn build_debug_borders_around_text_fragments(&self,
style: &ComputedValues,
display_list: &mut DisplayList,
flow_origin: Point2D<Au>,
text_fragment: &ScannedTextFragmentInfo,
@ -197,7 +199,11 @@ impl FragmentDisplayListBuilding for Fragment {
let background_color = style.resolve_color(style.get_background().background_color);
if !background_color.alpha.approx_eq(&0.0) {
display_list.push(SolidColorDisplayItemClass(box SolidColorDisplayItem {
base: BaseDisplayItem::new(*absolute_bounds, self.node, *clip_rect),
base: BaseDisplayItem::new(*absolute_bounds,
DisplayItemMetadata::new(self.node,
style,
DefaultCursor),
*clip_rect),
color: background_color.to_gfx_color(),
}), level);
}
@ -309,7 +315,9 @@ impl FragmentDisplayListBuilding for Fragment {
// Create the image display item.
display_list.push(ImageDisplayItemClass(box ImageDisplayItem {
base: BaseDisplayItem::new(bounds, self.node, clip_rect),
base: BaseDisplayItem::new(bounds,
DisplayItemMetadata::new(self.node, style, DefaultCursor),
clip_rect),
image: image.clone(),
stretch_size: Size2D(Au::from_px(image.width as int),
Au::from_px(image.height as int)),
@ -417,7 +425,9 @@ impl FragmentDisplayListBuilding for Fragment {
absolute_bounds.origin.y + absolute_bounds.size.height / 2);
let gradient_display_item = GradientDisplayItemClass(box GradientDisplayItem {
base: BaseDisplayItem::new(*absolute_bounds, self.node, clip_rect),
base: BaseDisplayItem::new(*absolute_bounds,
DisplayItemMetadata::new(self.node, style, DefaultCursor),
clip_rect),
start_point: center - delta,
end_point: center + delta,
stops: stops,
@ -441,7 +451,11 @@ impl FragmentDisplayListBuilding for Fragment {
absolute_bounds.translate(&Point2D(box_shadow.offset_x, box_shadow.offset_y))
.inflate(inflation, inflation);
list.push(BoxShadowDisplayItemClass(box BoxShadowDisplayItem {
base: BaseDisplayItem::new(bounds, self.node, *clip_rect),
base: BaseDisplayItem::new(bounds,
DisplayItemMetadata::new(self.node,
style,
DefaultCursor),
*clip_rect),
box_bounds: *absolute_bounds,
color: style.resolve_color(box_shadow.color).to_gfx_color(),
offset: Point2D(box_shadow.offset_x, box_shadow.offset_y),
@ -470,7 +484,9 @@ impl FragmentDisplayListBuilding for Fragment {
// Append the border to the display list.
display_list.push(BorderDisplayItemClass(box BorderDisplayItem {
base: BaseDisplayItem::new(*abs_bounds, self.node, *clip_rect),
base: BaseDisplayItem::new(*abs_bounds,
DisplayItemMetadata::new(self.node, style, DefaultCursor),
*clip_rect),
border_widths: border.to_physical(style.writing_mode),
color: SideOffsets2D::new(top_color.to_gfx_color(),
right_color.to_gfx_color(),
@ -510,7 +526,9 @@ impl FragmentDisplayListBuilding for Fragment {
// Append the outline to the display list.
let color = style.resolve_color(style.get_outline().outline_color).to_gfx_color();
display_list.outlines.push_back(BorderDisplayItemClass(box BorderDisplayItem {
base: BaseDisplayItem::new(bounds, self.node, *clip_rect),
base: BaseDisplayItem::new(bounds,
DisplayItemMetadata::new(self.node, style, DefaultCursor),
*clip_rect),
border_widths: SideOffsets2D::new_all_same(width),
color: SideOffsets2D::new_all_same(color),
style: SideOffsets2D::new_all_same(outline_style),
@ -519,6 +537,7 @@ impl FragmentDisplayListBuilding for Fragment {
}
fn build_debug_borders_around_text_fragments(&self,
style: &ComputedValues,
display_list: &mut DisplayList,
flow_origin: Point2D<Au>,
text_fragment: &ScannedTextFragmentInfo,
@ -533,7 +552,9 @@ impl FragmentDisplayListBuilding for Fragment {
// Compute the text fragment bounds and draw a border surrounding them.
display_list.content.push_back(BorderDisplayItemClass(box BorderDisplayItem {
base: BaseDisplayItem::new(absolute_fragment_bounds, self.node, *clip_rect),
base: BaseDisplayItem::new(absolute_fragment_bounds,
DisplayItemMetadata::new(self.node, style, DefaultCursor),
*clip_rect),
border_widths: SideOffsets2D::new_all_same(Au::from_px(1)),
color: SideOffsets2D::new_all_same(color::rgb(0, 0, 200)),
style: SideOffsets2D::new_all_same(border_style::solid),
@ -549,7 +570,9 @@ impl FragmentDisplayListBuilding for Fragment {
baseline.origin = baseline.origin + flow_origin;
let line_display_item = box LineDisplayItem {
base: BaseDisplayItem::new(baseline, self.node, *clip_rect),
base: BaseDisplayItem::new(baseline,
DisplayItemMetadata::new(self.node, style, DefaultCursor),
*clip_rect),
color: color::rgb(0, 200, 0),
style: border_style::dashed,
};
@ -570,7 +593,11 @@ impl FragmentDisplayListBuilding for Fragment {
// This prints a debug border around the border of this fragment.
display_list.content.push_back(BorderDisplayItemClass(box BorderDisplayItem {
base: BaseDisplayItem::new(absolute_fragment_bounds, self.node, *clip_rect),
base: BaseDisplayItem::new(absolute_fragment_bounds,
DisplayItemMetadata::new(self.node,
&*self.style,
DefaultCursor),
*clip_rect),
border_widths: SideOffsets2D::new_all_same(Au::from_px(1)),
color: SideOffsets2D::new_all_same(color::rgb(0, 0, 200)),
style: SideOffsets2D::new_all_same(border_style::solid),
@ -731,14 +758,14 @@ impl FragmentDisplayListBuilding for Fragment {
SpecificFragmentInfo::TableColumn(_) => panic!("Shouldn't see table column fragments here."),
SpecificFragmentInfo::ScannedText(ref text_fragment) => {
// Create the text display item.
let orientation = if self.style.writing_mode.is_vertical() {
let (orientation, cursor) = if self.style.writing_mode.is_vertical() {
if self.style.writing_mode.is_sideways_left() {
SidewaysLeft
(SidewaysLeft, VerticalTextCursor)
} else {
SidewaysRight
(SidewaysRight, VerticalTextCursor)
}
} else {
Upright
(Upright, TextCursor)
};
let metrics = &text_fragment.run.font_metrics;
@ -750,7 +777,11 @@ impl FragmentDisplayListBuilding for Fragment {
};
display_list.content.push_back(TextDisplayItemClass(box TextDisplayItem {
base: BaseDisplayItem::new(absolute_content_box, self.node, *clip_rect),
base: BaseDisplayItem::new(absolute_content_box,
DisplayItemMetadata::new(self.node,
self.style(),
cursor),
*clip_rect),
text_run: text_fragment.run.clone(),
range: text_fragment.range,
text_color: self.style().get_color().color.to_gfx_color(),
@ -760,14 +791,21 @@ impl FragmentDisplayListBuilding for Fragment {
// Create display items for text decoration
{
let line = |maybe_color: Option<RGBA>, rect: || -> LogicalRect<Au>| {
let line = |maybe_color: Option<RGBA>,
style: &ComputedValues,
rect: || -> LogicalRect<Au>| {
match maybe_color {
None => {}
Some(color) => {
let bounds = rect_to_absolute(self.style.writing_mode, rect());
display_list.content.push_back(SolidColorDisplayItemClass(
box SolidColorDisplayItem {
base: BaseDisplayItem::new(bounds, self.node, *clip_rect),
base: BaseDisplayItem::new(
bounds,
DisplayItemMetadata::new(self.node,
style,
DefaultCursor),
*clip_rect),
color: color.to_gfx_color(),
}))
}
@ -776,20 +814,20 @@ impl FragmentDisplayListBuilding for Fragment {
let text_decorations =
self.style().get_inheritedtext()._servo_text_decorations_in_effect;
line(text_decorations.underline, || {
line(text_decorations.underline, self.style(), || {
let mut rect = content_box.clone();
rect.start.b = rect.start.b + metrics.ascent - metrics.underline_offset;
rect.size.block = metrics.underline_size;
rect
});
line(text_decorations.overline, || {
line(text_decorations.overline, self.style(), || {
let mut rect = content_box.clone();
rect.size.block = metrics.underline_size;
rect
});
line(text_decorations.line_through, || {
line(text_decorations.line_through, self.style(), || {
let mut rect = content_box.clone();
rect.start.b = rect.start.b + metrics.ascent - metrics.strikeout_offset;
rect.size.block = metrics.strikeout_size;
@ -798,7 +836,8 @@ impl FragmentDisplayListBuilding for Fragment {
}
if opts::get().show_debug_fragment_borders {
self.build_debug_borders_around_text_fragments(display_list,
self.build_debug_borders_around_text_fragments(self.style(),
display_list,
flow_origin,
&**text_fragment,
clip_rect);
@ -822,7 +861,9 @@ impl FragmentDisplayListBuilding for Fragment {
// Place the image into the display list.
display_list.content.push_back(ImageDisplayItemClass(box ImageDisplayItem {
base: BaseDisplayItem::new(absolute_content_box,
self.node,
DisplayItemMetadata::new(self.node,
&*self.style,
DefaultCursor),
*clip_rect),
image: image.clone(),
stretch_size: absolute_content_box.size,

View file

@ -13,8 +13,7 @@ use flow_ref::FlowRef;
use fragment::{Fragment, FragmentBoundsIterator};
use incremental::{LayoutDamageComputation, REFLOW, REFLOW_ENTIRE_DOCUMENT, REPAINT};
use layout_debug;
use parallel::UnsafeFlow;
use parallel;
use parallel::{mod, UnsafeFlow};
use sequential;
use util::{LayoutDataAccess, LayoutDataWrapper, OpaqueNodeMethods, ToGfxColor};
use wrapper::{LayoutNode, TLayoutNode, ThreadSafeLayoutNode};
@ -26,7 +25,7 @@ use geom::rect::Rect;
use geom::size::Size2D;
use geom::scale_factor::ScaleFactor;
use gfx::color;
use gfx::display_list::{DisplayList, OpaqueNode, StackingContext};
use gfx::display_list::{DisplayItemMetadata, DisplayList, OpaqueNode, StackingContext};
use gfx::font_cache_task::FontCacheTask;
use gfx::paint_task::{mod, PaintInitMsg, PaintChan, PaintLayer};
use layout_traits;
@ -40,22 +39,25 @@ use script::layout_interface::{ContentBoxesQuery, ContentBoxQuery, ExitNowMsg, G
use script::layout_interface::{HitTestResponse, LayoutChan, LayoutRPC, LoadStylesheetMsg};
use script::layout_interface::{MouseOverResponse, Msg, NoQuery, PrepareToExitMsg};
use script::layout_interface::{ReapLayoutDataMsg, Reflow, ReflowForDisplay, ReflowMsg};
use script::layout_interface::{ScriptLayoutChan, SetQuirksModeMsg, TrustedNodeAddress};
use script::layout_interface::{ReflowForScriptQuery, ScriptLayoutChan, SetQuirksModeMsg};
use script::layout_interface::{TrustedNodeAddress};
use script_traits::{SendEventMsg, ReflowEvent, ReflowCompleteMsg, OpaqueScriptLayoutChannel};
use script_traits::{ScriptControlChan, UntrustedNodeAddress};
use servo_msg::compositor_msg::Scrollable;
use servo_msg::constellation_msg::{ConstellationChan, PipelineId, Failure, FailureMsg};
use servo_msg::constellation_msg::{SetCursorMsg};
use servo_net::image_cache_task::{ImageCacheTask, ImageResponseMsg};
use servo_net::local_image_cache::{ImageResponder, LocalImageCache};
use servo_net::resource_task::{ResourceTask, load_bytes_iter};
use servo_util::cursor::DefaultCursor;
use servo_util::geometry::Au;
use servo_util::logical_geometry::LogicalPoint;
use servo_util::opts;
use servo_util::smallvec::{SmallVec, SmallVec1, VecLike};
use servo_util::task::spawn_named_with_send_on_failure;
use servo_util::task_state;
use servo_util::time::{TimeProfilerChan, profile, TimerMetadataFrameType, TimerMetadataReflowType};
use servo_util::time;
use servo_util::time::{mod, ProfilerMetadata, TimeProfilerChan, TimerMetadataFrameType};
use servo_util::time::{TimerMetadataReflowType, profile};
use servo_util::workqueue::WorkQueue;
use std::cell::Cell;
use std::comm::{channel, Sender, Receiver, Select};
@ -73,6 +75,9 @@ pub struct LayoutTaskData {
/// The local image cache.
pub local_image_cache: Arc<Mutex<LocalImageCache<UntrustedNodeAddress>>>,
/// The channel on which messages can be sent to the constellation.
pub constellation_chan: ConstellationChan,
/// The size of the viewport.
pub screen_size: Size2D<Au>,
@ -112,9 +117,6 @@ pub struct LayoutTask {
//// The channel to send messages to ourself.
pub chan: LayoutChan,
/// The channel on which messages can be sent to the constellation.
pub constellation_chan: ConstellationChan,
/// The channel on which messages can be sent to the script task.
pub script_chan: ScriptControlChan,
@ -262,7 +264,6 @@ impl LayoutTask {
port: port,
pipeline_port: pipeline_port,
chan: chan,
constellation_chan: constellation_chan,
script_chan: script_chan,
paint_chan: paint_chan,
time_profiler_chan: time_profiler_chan,
@ -273,6 +274,7 @@ impl LayoutTask {
rw_data: Arc::new(Mutex::new(
LayoutTaskData {
local_image_cache: local_image_cache,
constellation_chan: constellation_chan,
screen_size: screen_size,
stacking_context: None,
stylist: box Stylist::new(device),
@ -302,7 +304,7 @@ impl LayoutTask {
SharedLayoutContext {
image_cache: rw_data.local_image_cache.clone(),
screen_size: rw_data.screen_size.clone(),
constellation_chan: self.constellation_chan.clone(),
constellation_chan: rw_data.constellation_chan.clone(),
layout_chan: self.chan.clone(),
font_cache_task: self.font_cache_task.clone(),
stylist: &*rw_data.stylist,
@ -397,9 +399,7 @@ impl LayoutTask {
},
ReflowMsg(data) => {
profile(time::LayoutPerformCategory,
Some((&data.url,
if data.iframe { TimerMetadataFrameType::IFrame } else { TimerMetadataFrameType::RootWindow },
if self.first_reflow.get() { TimerMetadataReflowType::FirstReflow } else { TimerMetadataReflowType::Incremental })),
self.profiler_metadata(&*data),
self.time_profiler_chan.clone(),
|| self.handle_reflow(&*data, possibly_locked_rw_data));
},
@ -573,9 +573,7 @@ impl LayoutTask {
// NOTE: this currently computes borders, so any pruning should separate that
// operation out.
parallel::traverse_flow_tree_preorder(layout_root,
&data.url,
if data.iframe { TimerMetadataFrameType::IFrame } else { TimerMetadataFrameType::RootWindow },
if self.first_reflow.get() { TimerMetadataReflowType::FirstReflow } else { TimerMetadataReflowType::Incremental },
self.profiler_metadata(data),
self.time_profiler_chan.clone(),
shared_layout_context,
traversal);
@ -624,16 +622,14 @@ impl LayoutTask {
data: &Reflow,
node: &mut LayoutNode,
layout_root: &mut FlowRef,
shared_layout_ctx: &mut SharedLayoutContext,
shared_layout_context: &mut SharedLayoutContext,
rw_data: &mut RWGuard<'a>) {
let writing_mode = flow::base(&**layout_root).writing_mode;
profile(time::LayoutDispListBuildCategory,
Some((&data.url,
if data.iframe { TimerMetadataFrameType::IFrame } else { TimerMetadataFrameType::RootWindow },
if self.first_reflow.get() { TimerMetadataReflowType::FirstReflow } else { TimerMetadataReflowType::Incremental })),
self.time_profiler_chan.clone(),
|| {
shared_layout_ctx.dirty =
self.profiler_metadata(data),
self.time_profiler_chan.clone(),
|| {
shared_layout_context.dirty =
flow::base(&**layout_root).position.to_physical(writing_mode,
rw_data.screen_size);
flow::mut_base(&mut **layout_root).stacking_relative_position =
@ -645,15 +641,13 @@ impl LayoutTask {
let rw_data = rw_data.deref_mut();
match rw_data.parallel_traversal {
None => {
sequential::build_display_list_for_subtree(layout_root, shared_layout_ctx);
sequential::build_display_list_for_subtree(layout_root, shared_layout_context);
}
Some(ref mut traversal) => {
parallel::build_display_list_for_subtree(layout_root,
&data.url,
if data.iframe { TimerMetadataFrameType::IFrame } else { TimerMetadataFrameType::RootWindow },
if self.first_reflow.get() { TimerMetadataReflowType::FirstReflow } else { TimerMetadataReflowType::Incremental },
self.profiler_metadata(data),
self.time_profiler_chan.clone(),
shared_layout_ctx,
shared_layout_context,
traversal);
}
}
@ -746,9 +740,9 @@ impl LayoutTask {
rw_data.screen_size = current_screen_size;
// Create a layout context for use throughout the following passes.
let mut shared_layout_ctx = self.build_shared_layout_context(rw_data.deref(),
node,
&data.url);
let mut shared_layout_context = self.build_shared_layout_context(rw_data.deref(),
node,
&data.url);
// Handle conditions where the entire flow tree is invalid.
let screen_size_changed = current_screen_size != old_screen_size;
@ -775,19 +769,17 @@ impl LayoutTask {
}
let mut layout_root = profile(time::LayoutStyleRecalcCategory,
Some((&data.url,
if data.iframe { TimerMetadataFrameType::IFrame } else { TimerMetadataFrameType::RootWindow },
if self.first_reflow.get() { TimerMetadataReflowType::FirstReflow } else { TimerMetadataReflowType::Incremental })),
self.profiler_metadata(data),
self.time_profiler_chan.clone(),
|| {
// Perform CSS selector matching and flow construction.
let rw_data = rw_data.deref_mut();
match rw_data.parallel_traversal {
None => {
sequential::traverse_dom_preorder(*node, &shared_layout_ctx);
sequential::traverse_dom_preorder(*node, &shared_layout_context);
}
Some(ref mut traversal) => {
parallel::traverse_dom_preorder(*node, &shared_layout_ctx, traversal)
parallel::traverse_dom_preorder(*node, &shared_layout_context, traversal)
}
}
@ -795,13 +787,12 @@ impl LayoutTask {
});
profile(time::LayoutRestyleDamagePropagation,
Some((&data.url,
if data.iframe { TimerMetadataFrameType::IFrame } else { TimerMetadataFrameType::RootWindow },
if self.first_reflow.get() { TimerMetadataReflowType::FirstReflow } else { TimerMetadataReflowType::Incremental })),
self.profiler_metadata(data),
self.time_profiler_chan.clone(),
|| {
if opts::get().nonincremental_layout ||
layout_root.deref_mut().compute_layout_damage().contains(REFLOW_ENTIRE_DOCUMENT) {
if opts::get().nonincremental_layout || layout_root.deref_mut()
.compute_layout_damage()
.contains(REFLOW_ENTIRE_DOCUMENT) {
layout_root.deref_mut().reflow_entire_document()
}
});
@ -818,42 +809,45 @@ impl LayoutTask {
// Perform the primary layout passes over the flow tree to compute the locations of all
// the boxes.
profile(time::LayoutMainCategory,
Some((&data.url,
if data.iframe { TimerMetadataFrameType::IFrame } else { TimerMetadataFrameType::RootWindow },
if self.first_reflow.get() { TimerMetadataReflowType::FirstReflow } else { TimerMetadataReflowType::Incremental })),
self.profiler_metadata(data),
self.time_profiler_chan.clone(),
|| {
let rw_data = rw_data.deref_mut();
match rw_data.parallel_traversal {
None => {
// Sequential mode.
self.solve_constraints(&mut layout_root, &shared_layout_ctx)
self.solve_constraints(&mut layout_root, &shared_layout_context)
}
Some(_) => {
// Parallel mode.
self.solve_constraints_parallel(data,
rw_data,
&mut layout_root,
&mut shared_layout_ctx);
&mut shared_layout_context);
}
}
});
// Build the display list if necessary, and send it to the painter.
if data.goal == ReflowForDisplay {
self.build_display_list_for_reflow(data,
node,
&mut layout_root,
&mut shared_layout_ctx,
&mut rw_data);
match data.goal {
ReflowForDisplay => {
self.build_display_list_for_reflow(data,
node,
&mut layout_root,
&mut shared_layout_context,
&mut rw_data);
}
ReflowForScriptQuery => {}
}
match data.query_type {
ContentBoxQuery(node) =>
self.process_content_box_request(node, &mut layout_root, &mut rw_data),
ContentBoxesQuery(node) =>
self.process_content_boxes_request(node, &mut layout_root, &mut rw_data),
NoQuery => {},
ContentBoxQuery(node) => {
self.process_content_box_request(node, &mut layout_root, &mut rw_data)
}
ContentBoxesQuery(node) => {
self.process_content_boxes_request(node, &mut layout_root, &mut rw_data)
}
NoQuery => {}
}
self.first_reflow.set(false);
@ -896,11 +890,13 @@ impl LayoutTask {
}
}
// When images can't be loaded in time to display they trigger
// this callback in some task somewhere. This will send a message
// to the script task, and ultimately cause the image to be
// re-requested. We probably don't need to go all the way back to
// the script task for this.
/// When images can't be loaded in time to display they trigger
/// this callback in some task somewhere. This will send a message
/// to the script task, and ultimately cause the image to be
/// re-requested. We probably don't need to go all the way back to
/// the script task for this.
///
/// FIXME(pcwalton): Rewrite all of this.
fn make_on_image_available_cb(&self) -> Box<ImageResponder<UntrustedNodeAddress>+Send> {
// This has a crazy signature because the image cache needs to
// make multiple copies of the callback, and the dom event
@ -919,6 +915,21 @@ impl LayoutTask {
let _: Option<LayoutDataWrapper> = mem::transmute(
mem::replace(&mut *layout_data_ref, None));
}
/// Returns profiling information which is passed to the time profiler.
fn profiler_metadata<'a>(&self, data: &'a Reflow) -> ProfilerMetadata<'a> {
Some((&data.url,
if data.iframe {
TimerMetadataFrameType::IFrame
} else {
TimerMetadataFrameType::RootWindow
},
if self.first_reflow.get() {
TimerMetadataReflowType::FirstReflow
} else {
TimerMetadataReflowType::Incremental
}))
}
}
struct LayoutRPCImpl(Arc<Mutex<LayoutTaskData>>);
@ -951,7 +962,7 @@ impl LayoutRPC for LayoutRPCImpl {
let mut result = Vec::new();
stacking_context.hit_test(point, &mut result, true);
if !result.is_empty() {
Some(HitTestResponse(result[0]))
Some(HitTestResponse(result[0].node.to_untrusted_node_address()))
} else {
None
}
@ -967,7 +978,7 @@ impl LayoutRPC for LayoutRPCImpl {
fn mouse_over(&self, _: TrustedNodeAddress, point: Point2D<f32>)
-> Result<MouseOverResponse, ()> {
let mut mouse_over_list: Vec<UntrustedNodeAddress> = vec!();
let mut mouse_over_list: Vec<DisplayItemMetadata> = vec!();
let point = Point2D(Au::from_frac_px(point.x as f64), Au::from_frac_px(point.y as f64));
{
let &LayoutRPCImpl(ref rw_data) = self;
@ -978,12 +989,25 @@ impl LayoutRPC for LayoutRPCImpl {
stacking_context.hit_test(point, &mut mouse_over_list, false);
}
}
// Compute the new cursor.
let cursor = if !mouse_over_list.is_empty() {
mouse_over_list[0].cursor
} else {
DefaultCursor
};
let ConstellationChan(ref constellation_chan) = rw_data.constellation_chan;
constellation_chan.send(SetCursorMsg(cursor));
}
if mouse_over_list.is_empty() {
Err(())
} else {
Ok(MouseOverResponse(mouse_over_list))
let response_list =
mouse_over_list.iter()
.map(|metadata| metadata.node.to_untrusted_node_address())
.collect();
Ok(MouseOverResponse(response_list))
}
}
}

View file

@ -13,15 +13,13 @@ use flow_ref::FlowRef;
use traversal::{RecalcStyleForNode, ConstructFlows};
use traversal::{BubbleISizes, AssignISizes, AssignBSizesAndStoreOverflow};
use traversal::{ComputeAbsolutePositions, BuildDisplayList};
use url::Url;
use util::{LayoutDataAccess, LayoutDataWrapper};
use wrapper::{layout_node_to_unsafe_layout_node, layout_node_from_unsafe_layout_node, LayoutNode};
use wrapper::{PostorderNodeMutTraversal, UnsafeLayoutNode};
use wrapper::{PreorderDomTraversal, PostorderDomTraversal};
use servo_util::opts;
use servo_util::time::{TimeProfilerChan, profile, TimerMetadataFrameType, TimerMetadataReflowType};
use servo_util::time;
use servo_util::time::{mod, ProfilerMetadata, TimeProfilerChan, profile};
use servo_util::workqueue::{WorkQueue, WorkUnit, WorkerProxy};
use std::mem;
use std::ptr;
@ -422,9 +420,7 @@ pub fn traverse_dom_preorder(root: LayoutNode,
}
pub fn traverse_flow_tree_preorder(root: &mut FlowRef,
url: &Url,
iframe: TimerMetadataFrameType,
reflow_type: TimerMetadataReflowType,
profiler_metadata: ProfilerMetadata,
time_profiler_chan: TimeProfilerChan,
shared_layout_context: &SharedLayoutContext,
queue: &mut WorkQueue<*const SharedLayoutContext,UnsafeFlow>) {
@ -436,7 +432,7 @@ pub fn traverse_flow_tree_preorder(root: &mut FlowRef,
queue.data = shared_layout_context as *const _;
profile(time::LayoutParallelWarmupCategory, Some((url, iframe, reflow_type)), time_profiler_chan, || {
profile(time::LayoutParallelWarmupCategory, profiler_metadata, time_profiler_chan, || {
queue.push(WorkUnit {
fun: assign_inline_sizes,
data: mut_owned_flow_to_unsafe_flow(root),
@ -449,15 +445,13 @@ pub fn traverse_flow_tree_preorder(root: &mut FlowRef,
}
pub fn build_display_list_for_subtree(root: &mut FlowRef,
url: &Url,
iframe: TimerMetadataFrameType,
reflow_type: TimerMetadataReflowType,
profiler_metadata: ProfilerMetadata,
time_profiler_chan: TimeProfilerChan,
shared_layout_context: &SharedLayoutContext,
queue: &mut WorkQueue<*const SharedLayoutContext,UnsafeFlow>) {
queue.data = shared_layout_context as *const _;
profile(time::LayoutParallelWarmupCategory, Some((url, iframe, reflow_type)), time_profiler_chan, || {
profile(time::LayoutParallelWarmupCategory, profiler_metadata, time_profiler_chan, || {
queue.push(WorkUnit {
fun: compute_absolute_positions,
data: mut_owned_flow_to_unsafe_flow(root),

View file

@ -8,6 +8,9 @@ authors = ["The Servo Project Developers"]
name = "msg"
path = "lib.rs"
[dependencies.style]
path = "../style"
[dependencies.util]
path = "../util"

View file

@ -11,6 +11,7 @@ use geom::scale_factor::ScaleFactor;
use hyper::header::Headers;
use hyper::method::{Method, Get};
use layers::geometry::DevicePixel;
use servo_util::cursor::Cursor;
use servo_util::geometry::{PagePx, ViewportPx};
use std::comm::{channel, Sender, Receiver};
use url::Url;
@ -208,6 +209,8 @@ pub enum Msg {
/// Requests that the constellation inform the compositor of the title of the pipeline
/// immediately.
GetPipelineTitleMsg(PipelineId),
/// Requests that the constellation inform the compositor of the a cursor change.
SetCursorMsg(Cursor),
}
/// Similar to net::resource_task::LoadData

View file

@ -1150,7 +1150,6 @@ impl ScriptTask {
let page = get_page(&*self.page.borrow(), pipeline_id);
match page.get_nodes_under_mouse(&point) {
Some(node_address) => {
let mut target_list = vec!();
let mut target_compare = false;
@ -1166,23 +1165,19 @@ impl ScriptTask {
}
for node_address in node_address.iter() {
let temp_node =
node::from_untrusted_node_address(
self.js_runtime.ptr, *node_address);
node::from_untrusted_node_address(self.js_runtime.ptr, *node_address);
let maybe_node = temp_node.root().ancestors().find(|node| node.is_element());
match maybe_node {
Some(node) => {
node.set_hover_state(true);
match *mouse_over_targets {
Some(ref mouse_over_targets) => {
if !target_compare {
target_compare = !mouse_over_targets.contains(&JS::from_rooted(node));
}
Some(ref mouse_over_targets) if !target_compare => {
target_compare =
!mouse_over_targets.contains(&JS::from_rooted(node));
}
None => {}
_ => {}
}
target_list.push(JS::from_rooted(node));
}
@ -1192,15 +1187,15 @@ impl ScriptTask {
match *mouse_over_targets {
Some(ref mouse_over_targets) => {
if mouse_over_targets.len() != target_list.len() {
target_compare = true;
target_compare = true
}
}
None => { target_compare = true; }
None => target_compare = true,
}
if target_compare {
if mouse_over_targets.is_some() {
self.force_reflow(&*page);
self.force_reflow(&*page)
}
*mouse_over_targets = Some(target_list);
}

View file

@ -501,6 +501,7 @@ dependencies = [
"hyper 0.0.1 (git+https://github.com/servo/hyper?ref=servo)",
"io_surface 0.1.0 (git+https://github.com/servo/rust-io-surface)",
"layers 0.1.0 (git+https://github.com/servo/rust-layers)",
"style 0.0.1",
"url 0.1.0 (git+https://github.com/servo/rust-url)",
"util 0.0.1",
]

View file

@ -32,7 +32,7 @@ import re
def to_rust_ident(name):
name = name.replace("-", "_")
if name in ["static", "super", "box"]: # Rust keywords
if name in ["static", "super", "box", "move"]: # Rust keywords
name += "_"
return name
@ -1349,6 +1349,139 @@ pub mod longhands {
${single_keyword("box-sizing", "content-box border-box")}
${new_style_struct("Pointing", is_inherited=True)}
<%self:single_component_value name="cursor">
use servo_util::cursor as util_cursor;
pub use super::computed_as_specified as to_computed_value;
pub mod computed_value {
use servo_util::cursor::Cursor;
#[deriving(Clone, PartialEq, Show)]
pub enum T {
AutoCursor,
SpecifiedCursor(Cursor),
}
}
pub type SpecifiedValue = computed_value::T;
#[inline]
pub fn get_initial_value() -> computed_value::T {
computed_value::T::AutoCursor
}
pub fn from_component_value(value: &ComponentValue, _: &Url)
-> Result<SpecifiedValue,()> {
match value {
&Ident(ref value) if value.eq_ignore_ascii_case("auto") => Ok(T::AutoCursor),
&Ident(ref value) if value.eq_ignore_ascii_case("none") => {
Ok(T::SpecifiedCursor(util_cursor::NoCursor))
}
&Ident(ref value) if value.eq_ignore_ascii_case("default") => {
Ok(T::SpecifiedCursor(util_cursor::DefaultCursor))
}
&Ident(ref value) if value.eq_ignore_ascii_case("pointer") => {
Ok(T::SpecifiedCursor(util_cursor::PointerCursor))
}
&Ident(ref value) if value.eq_ignore_ascii_case("context-menu") => {
Ok(T::SpecifiedCursor(util_cursor::ContextMenuCursor))
}
&Ident(ref value) if value.eq_ignore_ascii_case("help") => {
Ok(T::SpecifiedCursor(util_cursor::HelpCursor))
}
&Ident(ref value) if value.eq_ignore_ascii_case("progress") => {
Ok(T::SpecifiedCursor(util_cursor::ProgressCursor))
}
&Ident(ref value) if value.eq_ignore_ascii_case("wait") => {
Ok(T::SpecifiedCursor(util_cursor::WaitCursor))
}
&Ident(ref value) if value.eq_ignore_ascii_case("cell") => {
Ok(T::SpecifiedCursor(util_cursor::CellCursor))
}
&Ident(ref value) if value.eq_ignore_ascii_case("crosshair") => {
Ok(T::SpecifiedCursor(util_cursor::CrosshairCursor))
}
&Ident(ref value) if value.eq_ignore_ascii_case("text") => {
Ok(T::SpecifiedCursor(util_cursor::TextCursor))
}
&Ident(ref value) if value.eq_ignore_ascii_case("vertical-text") => {
Ok(T::SpecifiedCursor(util_cursor::VerticalTextCursor))
}
&Ident(ref value) if value.eq_ignore_ascii_case("alias") => {
Ok(T::SpecifiedCursor(util_cursor::AliasCursor))
}
&Ident(ref value) if value.eq_ignore_ascii_case("copy") => {
Ok(T::SpecifiedCursor(util_cursor::CopyCursor))
}
&Ident(ref value) if value.eq_ignore_ascii_case("move") => {
Ok(T::SpecifiedCursor(util_cursor::MoveCursor))
}
&Ident(ref value) if value.eq_ignore_ascii_case("no-drop") => {
Ok(T::SpecifiedCursor(util_cursor::NoDropCursor))
}
&Ident(ref value) if value.eq_ignore_ascii_case("not-allowed") => {
Ok(T::SpecifiedCursor(util_cursor::NotAllowedCursor))
}
&Ident(ref value) if value.eq_ignore_ascii_case("grab") => {
Ok(T::SpecifiedCursor(util_cursor::GrabCursor))
}
&Ident(ref value) if value.eq_ignore_ascii_case("grabbing") => {
Ok(T::SpecifiedCursor(util_cursor::GrabbingCursor))
}
&Ident(ref value) if value.eq_ignore_ascii_case("e-resize") => {
Ok(T::SpecifiedCursor(util_cursor::EResizeCursor))
}
&Ident(ref value) if value.eq_ignore_ascii_case("n-resize") => {
Ok(T::SpecifiedCursor(util_cursor::NResizeCursor))
}
&Ident(ref value) if value.eq_ignore_ascii_case("ne-resize") => {
Ok(T::SpecifiedCursor(util_cursor::NeResizeCursor))
}
&Ident(ref value) if value.eq_ignore_ascii_case("nw-resize") => {
Ok(T::SpecifiedCursor(util_cursor::NwResizeCursor))
}
&Ident(ref value) if value.eq_ignore_ascii_case("s-resize") => {
Ok(T::SpecifiedCursor(util_cursor::SResizeCursor))
}
&Ident(ref value) if value.eq_ignore_ascii_case("se-resize") => {
Ok(T::SpecifiedCursor(util_cursor::SeResizeCursor))
}
&Ident(ref value) if value.eq_ignore_ascii_case("sw-resize") => {
Ok(T::SpecifiedCursor(util_cursor::SwResizeCursor))
}
&Ident(ref value) if value.eq_ignore_ascii_case("w-resize") => {
Ok(T::SpecifiedCursor(util_cursor::WResizeCursor))
}
&Ident(ref value) if value.eq_ignore_ascii_case("ew-resize") => {
Ok(T::SpecifiedCursor(util_cursor::EwResizeCursor))
}
&Ident(ref value) if value.eq_ignore_ascii_case("ns-resize") => {
Ok(T::SpecifiedCursor(util_cursor::NsResizeCursor))
}
&Ident(ref value) if value.eq_ignore_ascii_case("nesw-resize") => {
Ok(T::SpecifiedCursor(util_cursor::NeswResizeCursor))
}
&Ident(ref value) if value.eq_ignore_ascii_case("nwse-resize") => {
Ok(T::SpecifiedCursor(util_cursor::NwseResizeCursor))
}
&Ident(ref value) if value.eq_ignore_ascii_case("col-resize") => {
Ok(T::SpecifiedCursor(util_cursor::ColResizeCursor))
}
&Ident(ref value) if value.eq_ignore_ascii_case("row-resize") => {
Ok(T::SpecifiedCursor(util_cursor::RowResizeCursor))
}
&Ident(ref value) if value.eq_ignore_ascii_case("all-scroll") => {
Ok(T::SpecifiedCursor(util_cursor::AllScrollCursor))
}
&Ident(ref value) if value.eq_ignore_ascii_case("zoom-in") => {
Ok(T::SpecifiedCursor(util_cursor::ZoomInCursor))
}
&Ident(ref value) if value.eq_ignore_ascii_case("zoom-out") => {
Ok(T::SpecifiedCursor(util_cursor::ZoomOutCursor))
}
_ => Err(())
}
}
</%self:single_component_value>
// Box-shadow, etc.
${new_style_struct("Effects", is_inherited=False)}

46
components/util/cursor.rs Normal file
View file

@ -0,0 +1,46 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
//! A list of common mouse cursors per CSS3-UI § 8.1.1.
#[deriving(Clone, PartialEq, FromPrimitive, Show)]
#[repr(u8)]
pub enum Cursor {
NoCursor = 0,
DefaultCursor = 1,
PointerCursor = 2,
ContextMenuCursor = 3,
HelpCursor = 4,
ProgressCursor = 5,
WaitCursor = 6,
CellCursor = 7,
CrosshairCursor = 8,
TextCursor = 9,
VerticalTextCursor = 10,
AliasCursor = 11,
CopyCursor = 12,
MoveCursor = 13,
NoDropCursor = 14,
NotAllowedCursor = 15,
GrabCursor = 16,
GrabbingCursor = 17,
EResizeCursor = 18,
NResizeCursor = 19,
NeResizeCursor = 20,
NwResizeCursor = 21,
SResizeCursor = 22,
SeResizeCursor = 23,
SwResizeCursor = 24,
WResizeCursor = 25,
EwResizeCursor = 26,
NsResizeCursor = 27,
NeswResizeCursor = 28,
NwseResizeCursor = 29,
ColResizeCursor = 30,
RowResizeCursor = 31,
AllScrollCursor = 32,
ZoomInCursor = 33,
ZoomOutCursor = 34,
}

View file

@ -39,6 +39,7 @@ use std::sync::Arc;
pub mod bloom;
pub mod cache;
pub mod cursor;
pub mod debug_utils;
pub mod dlist;
pub mod fnv;

View file

@ -259,8 +259,10 @@ pub enum TimerMetadataReflowType {
FirstReflow,
}
pub type ProfilerMetadata<'a> = Option<(&'a Url, TimerMetadataFrameType, TimerMetadataReflowType)>;
pub fn profile<T>(category: TimeProfilerCategory,
meta: Option<(&Url, TimerMetadataFrameType, TimerMetadataReflowType)>,
meta: ProfilerMetadata,
time_profiler_chan: TimeProfilerChan,
callback: || -> T)
-> T {

1
ports/cef/Cargo.lock generated
View file

@ -469,6 +469,7 @@ dependencies = [
"hyper 0.0.1 (git+https://github.com/servo/hyper?ref=servo)",
"io_surface 0.1.0 (git+https://github.com/servo/rust-io-surface)",
"layers 0.1.0 (git+https://github.com/servo/rust-layers)",
"style 0.0.1",
"url 0.1.0 (git+https://github.com/servo/rust-url)",
"util 0.0.1",
]

View file

@ -109,6 +109,13 @@ cef_class_impl! {
}
}
fn send_mouse_move_event(&_this, event: *const cef_mouse_event, _mouse_exited: c_int)
-> () {
let event: &cef_mouse_event = event;
let point = TypedPoint2D((*event).x as f32, (*event).y as f32);
core::send_window_event(WindowEvent::MouseWindowMoveEventClass(point))
}
fn send_mouse_wheel_event(&_this,
event: *const cef_mouse_event,
delta_x: c_int,

View file

@ -10,7 +10,7 @@
use eutil::Downcast;
use interfaces::CefBrowser;
use render_handler::CefRenderHandlerExtensions;
use types::cef_rect_t;
use types::{cef_cursor_handle_t, cef_rect_t};
use wrappers::Utf16Encoder;
use compositing::compositor_task::{mod, CompositorProxy, CompositorReceiver};
@ -25,10 +25,22 @@ use servo_msg::constellation_msg::{Key, KeyModifiers};
use servo_msg::compositor_msg::{Blank, FinishedLoading, Loading, PerformingLayout, PaintState};
use servo_msg::compositor_msg::{ReadyState};
use servo_msg::constellation_msg::LoadData;
use servo_util::cursor::Cursor;
use servo_util::geometry::ScreenPx;
use std::cell::RefCell;
use std::rc::Rc;
#[cfg(target_os="macos")]
use servo_util::cursor::{AliasCursor, AllScrollCursor, ColResizeCursor, ContextMenuCursor};
#[cfg(target_os="macos")]
use servo_util::cursor::{CopyCursor, CrosshairCursor, EResizeCursor, EwResizeCursor};
#[cfg(target_os="macos")]
use servo_util::cursor::{GrabCursor, GrabbingCursor, NResizeCursor, NoCursor, NoDropCursor};
#[cfg(target_os="macos")]
use servo_util::cursor::{NsResizeCursor, NotAllowedCursor, PointerCursor, RowResizeCursor};
#[cfg(target_os="macos")]
use servo_util::cursor::{SResizeCursor, TextCursor, VerticalTextCursor, WResizeCursor};
#[cfg(target_os="macos")]
use std::ptr;
@ -87,6 +99,42 @@ impl Window {
pub fn wait_events(&self) -> WindowEvent {
WindowEvent::Idle
}
/// Returns the Cocoa cursor for a CSS cursor. These match Firefox, except where Firefox
/// bundles custom resources (which we don't yet do).
#[cfg(target_os="macos")]
fn cursor_handle_for_cursor(&self, cursor: Cursor) -> cef_cursor_handle_t {
use cocoa::base::{class, msg_send, selector};
let cocoa_name = match cursor {
NoCursor => return 0 as cef_cursor_handle_t,
ContextMenuCursor => "contextualMenuCursor",
GrabbingCursor => "closedHandCursor",
CrosshairCursor => "crosshairCursor",
CopyCursor => "dragCopyCursor",
AliasCursor => "dragLinkCursor",
TextCursor => "IBeamCursor",
GrabCursor | AllScrollCursor => "openHandCursor",
NoDropCursor | NotAllowedCursor => "operationNotAllowedCursor",
PointerCursor => "pointingHandCursor",
SResizeCursor => "resizeDownCursor",
WResizeCursor => "resizeLeftCursor",
EwResizeCursor | ColResizeCursor => "resizeLeftRightCursor",
EResizeCursor => "resizeRightCursor",
NResizeCursor => "resizeUpCursor",
NsResizeCursor | RowResizeCursor => "resizeUpDownCursor",
VerticalTextCursor => "IBeamCursorForVerticalLayout",
_ => "arrowCursor",
};
unsafe {
msg_send()(class("NSCursor"), selector(cocoa_name))
}
}
#[cfg(not(target_os="macos"))]
fn cursor_handle_for_cursor(&self, _: Cursor) -> cef_cursor_handle_t {
0
}
}
impl WindowMethods for Window {
@ -265,6 +313,20 @@ impl WindowMethods for Window {
fn handle_key(&self, _: Key, _: KeyModifiers) {
// TODO(negge)
}
fn set_cursor(&self, cursor: Cursor) {
let browser = self.cef_browser.borrow();
match *browser {
None => {}
Some(ref browser) => {
let cursor_handle = self.cursor_handle_for_cursor(cursor);
browser.get_host()
.get_client()
.get_render_handler()
.on_cursor_change(browser.clone(), cursor_handle)
}
}
}
}
struct CefCompositorProxy {

View file

@ -32,6 +32,7 @@ use std::comm::Receiver;
use std::num::Float;
use std::rc::Rc;
use time::{mod, Timespec};
use util::cursor::Cursor;
use util::geometry::ScreenPx;
/// The type of a window.
@ -241,6 +242,12 @@ impl WindowMethods for Window {
true
}
fn set_cursor(&self, _: Cursor) {
// No-op. We could take over mouse handling ourselves and draw the cursor as an extra
// layer with our own custom bitmaps or something, but it doesn't seem worth the
// trouble.
}
fn load_end(&self) {}
fn set_page_title(&self, _: Option<String>) {}
@ -269,14 +276,7 @@ impl Window {
self.event_queue.borrow_mut().push(Refresh);
},
glfw::MouseButtonEvent(button, action, _mods) => {
let (x, y) = window.get_cursor_pos();
//handle hidpi displays, since GLFW returns non-hi-def coordinates.
let (backing_size, _) = window.get_framebuffer_size();
let (window_size, _) = window.get_size();
let hidpi = (backing_size as f32) / (window_size as f32);
let x = x as f32 * hidpi;
let y = y as f32 * hidpi;
let cursor_position = self.cursor_position();
match button {
glfw::MouseButton::Button5 => { // Back button (might be different per platform)
self.event_queue.borrow_mut().push(Navigation(Back));
@ -285,14 +285,18 @@ impl Window {
self.event_queue.borrow_mut().push(Navigation(Forward));
},
glfw::MouseButtonLeft | glfw::MouseButtonRight => {
self.handle_mouse(button, action, x as i32, y as i32);
self.handle_mouse(button,
action,
cursor_position.x.get() as i32,
cursor_position.y.get() as i32);
}
_ => {}
}
},
glfw::CursorPosEvent(xpos, ypos) => {
self.event_queue.borrow_mut().push(
MouseWindowMoveEventClass(TypedPoint2D(xpos as f32, ypos as f32)));
glfw::CursorPosEvent(..) => {
self.event_queue
.borrow_mut()
.push(MouseWindowMoveEventClass(self.cursor_position()));
},
glfw::ScrollEvent(xpos, ypos) => {
match (window.get_key(glfw::Key::LeftControl),
@ -393,6 +397,14 @@ impl Window {
};
self.event_queue.borrow_mut().push(MouseWindowEventClass(event));
}
/// Returns the cursor position, properly accounting for HiDPI.
fn cursor_position(&self) -> TypedPoint2D<DevicePixel,f32> {
// Handle hidpi displays, since GLFW returns non-hi-def coordinates.
let (x, y) = self.glfw_window.get_cursor_pos();
let hidpi_factor = self.hidpi_factor();
Point2D::from_untyped(&Point2D(x as f32, y as f32)) * hidpi_factor
}
}
struct GlfwCompositorProxy {

View file

@ -18,3 +18,4 @@ input[type="radio"]:checked::before { content: "(●)"; }
td[align="left"] { text-align: left; }
td[align="center"] { text-align: center; }
td[align="right"] { text-align: right; }

View file

@ -89,7 +89,7 @@ rt { display: ruby-text; }
:link { color: #0000EE; }
:visited { color: #551A8B; }
:link, :visited { text-decoration: underline; }
:link, :visited { text-decoration: underline; cursor: pointer; }
a:link[rel~=help], a:visited[rel~=help],
area:link[rel~=help], area:visited[rel~=help] { cursor: help; }