diff options
author | bors-servo <metajack+bors@gmail.com> | 2014-12-18 00:24:49 -0700 |
---|---|---|
committer | bors-servo <metajack+bors@gmail.com> | 2014-12-18 00:24:49 -0700 |
commit | e2267e0a0749e27046ee8a26ba514cc6865e0345 (patch) | |
tree | 7b7b34c971ab6127c7f0748fc3fde10f6d3ca722 | |
parent | 6eb9ae1eff2d26c52ad2ac59eec703bd7e8ae867 (diff) | |
parent | 7371e0b8e38753ffbc977529359d8befb4f87481 (diff) | |
download | servo-e2267e0a0749e27046ee8a26ba514cc6865e0345.tar.gz servo-e2267e0a0749e27046ee8a26ba514cc6865e0345.zip |
auto merge of #4358 : pcwalton/servo/cursor, r=mbrubeck
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).
If you test this in the wild you will probably hit #4357 until that PR lands.
-rw-r--r-- | components/compositing/compositor.rs | 8 | ||||
-rw-r--r-- | components/compositing/compositor_task.rs | 6 | ||||
-rw-r--r-- | components/compositing/constellation.rs | 14 | ||||
-rw-r--r-- | components/compositing/headless.rs | 16 | ||||
-rw-r--r-- | components/compositing/windowing.rs | 4 | ||||
-rw-r--r-- | components/gfx/display_list/mod.rs | 105 | ||||
-rw-r--r-- | components/layout/display_list_builder.rs | 85 | ||||
-rw-r--r-- | components/layout/layout_task.rs | 156 | ||||
-rw-r--r-- | components/layout/parallel.rs | 16 | ||||
-rw-r--r-- | components/msg/Cargo.toml | 3 | ||||
-rw-r--r-- | components/msg/constellation_msg.rs | 3 | ||||
-rw-r--r-- | components/script/script_task.rs | 21 | ||||
-rw-r--r-- | components/servo/Cargo.lock | 1 | ||||
-rw-r--r-- | components/style/properties/mod.rs.mako | 135 | ||||
-rw-r--r-- | components/util/cursor.rs | 46 | ||||
-rw-r--r-- | components/util/lib.rs | 1 | ||||
-rw-r--r-- | components/util/time.rs | 4 | ||||
-rw-r--r-- | ports/cef/Cargo.lock | 1 | ||||
-rw-r--r-- | ports/cef/browser_host.rs | 7 | ||||
-rw-r--r-- | ports/cef/window.rs | 64 | ||||
-rw-r--r-- | ports/glfw/window.rs | 36 | ||||
-rw-r--r-- | resources/servo.css | 1 | ||||
-rw-r--r-- | resources/user-agent.css | 2 |
23 files changed, 564 insertions, 171 deletions
diff --git a/components/compositing/compositor.rs b/components/compositing/compositor.rs index 0b18124f53a..5b265d2ec15 100644 --- a/components/compositing/compositor.rs +++ b/components/compositing/compositor.rs @@ -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. diff --git a/components/compositing/compositor_task.rs b/components/compositing/compositor_task.rs index 0fc99cc1884..c8e8932b956 100644 --- a/components/compositing/compositor_task.rs +++ b/components/compositing/compositor_task.rs @@ -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"), } } } diff --git a/components/compositing/constellation.rs b/components/compositing/constellation.rs index fe756a5c7b9..118f33bc3f3 100644 --- a/components/compositing/constellation.rs +++ b/components/compositing/constellation.rs @@ -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); diff --git a/components/compositing/headless.rs b/components/compositing/headless.rs index 4a9ed6d5c56..9651ec7de78 100644 --- a/components/compositing/headless.rs +++ b/components/compositing/headless.rs @@ -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 } diff --git a/components/compositing/windowing.rs b/components/compositing/windowing.rs index e33a5f9fe75..54fc96897d0 100644 --- a/components/compositing/windowing.rs +++ b/components/compositing/windowing.rs @@ -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); } diff --git a/components/gfx/display_list/mod.rs b/components/gfx/display_list/mod.rs index b0cb1e609a6..1d81f39f612 100644 --- a/components/gfx/display_list/mod.rs +++ b/components/gfx/display_list/mod.rs @@ -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 - } - } -} diff --git a/components/layout/display_list_builder.rs b/components/layout/display_list_builder.rs index 33f40f8b188..27c59072191 100644 --- a/components/layout/display_list_builder.rs +++ b/components/layout/display_list_builder.rs @@ -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, diff --git a/components/layout/layout_task.rs b/components/layout/layout_task.rs index 4d8885cf8ed..48941a0d336 100644 --- a/components/layout/layout_task.rs +++ b/components/layout/layout_task.rs @@ -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)) } } } diff --git a/components/layout/parallel.rs b/components/layout/parallel.rs index 6d1ce6643c8..5537ba95998 100644 --- a/components/layout/parallel.rs +++ b/components/layout/parallel.rs @@ -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), diff --git a/components/msg/Cargo.toml b/components/msg/Cargo.toml index 21312f5d481..460a277a7a7 100644 --- a/components/msg/Cargo.toml +++ b/components/msg/Cargo.toml @@ -8,6 +8,9 @@ authors = ["The Servo Project Developers"] name = "msg" path = "lib.rs" +[dependencies.style] +path = "../style" + [dependencies.util] path = "../util" diff --git a/components/msg/constellation_msg.rs b/components/msg/constellation_msg.rs index 531b39360e5..78cda641aef 100644 --- a/components/msg/constellation_msg.rs +++ b/components/msg/constellation_msg.rs @@ -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 diff --git a/components/script/script_task.rs b/components/script/script_task.rs index 3fc455d588a..dfb4e7d267a 100644 --- a/components/script/script_task.rs +++ b/components/script/script_task.rs @@ -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); } diff --git a/components/servo/Cargo.lock b/components/servo/Cargo.lock index bca176f29cb..716539140c9 100644 --- a/components/servo/Cargo.lock +++ b/components/servo/Cargo.lock @@ -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", ] diff --git a/components/style/properties/mod.rs.mako b/components/style/properties/mod.rs.mako index 3541677e0d9..f82c821a8bb 100644 --- a/components/style/properties/mod.rs.mako +++ b/components/style/properties/mod.rs.mako @@ -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 @@ -1351,6 +1351,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)} diff --git a/components/util/cursor.rs b/components/util/cursor.rs new file mode 100644 index 00000000000..d295487ac9b --- /dev/null +++ b/components/util/cursor.rs @@ -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, +} + diff --git a/components/util/lib.rs b/components/util/lib.rs index b0e4cff8c62..02daa6be76f 100644 --- a/components/util/lib.rs +++ b/components/util/lib.rs @@ -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; diff --git a/components/util/time.rs b/components/util/time.rs index 16155710737..c4a994bc832 100644 --- a/components/util/time.rs +++ b/components/util/time.rs @@ -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 { diff --git a/ports/cef/Cargo.lock b/ports/cef/Cargo.lock index c79b3716831..9079fd27a9c 100644 --- a/ports/cef/Cargo.lock +++ b/ports/cef/Cargo.lock @@ -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", ] diff --git a/ports/cef/browser_host.rs b/ports/cef/browser_host.rs index 9fea6a6fa9c..e1248403e2f 100644 --- a/ports/cef/browser_host.rs +++ b/ports/cef/browser_host.rs @@ -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, diff --git a/ports/cef/window.rs b/ports/cef/window.rs index 56851533767..807b72b83cd 100644 --- a/ports/cef/window.rs +++ b/ports/cef/window.rs @@ -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,11 +25,23 @@ 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; /// The type of an off-screen window. @@ -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 { diff --git a/ports/glfw/window.rs b/ports/glfw/window.rs index 05a4b68a0e0..377bb2839b6 100644 --- a/ports/glfw/window.rs +++ b/ports/glfw/window.rs @@ -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 { diff --git a/resources/servo.css b/resources/servo.css index eec446505d4..e62ca9837bb 100644 --- a/resources/servo.css +++ b/resources/servo.css @@ -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; } + diff --git a/resources/user-agent.css b/resources/user-agent.css index 8ae8f5a5960..46c5787783a 100644 --- a/resources/user-agent.css +++ b/resources/user-agent.css @@ -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; } |