diff options
-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; } |