diff --git a/src/components/main/compositing/compositor_layer.rs b/src/components/main/compositing/compositor_layer.rs old mode 100644 new mode 100755 index cae6a9e8057b..02169abeea75 --- a/src/components/main/compositing/compositor_layer.rs +++ b/src/components/main/compositing/compositor_layer.rs @@ -381,6 +381,37 @@ impl CompositorLayer { } } + pub fn move(&mut self, origin: Point2D, window_size: Size2D) -> bool { + match self.scroll_behavior { + Scroll => { + // Scroll this layer! + let old_origin = self.scroll_offset; + self.scroll_offset = Point2D(0f32, 0f32) - origin; + + // bounds checking + let page_size = match self.page_size { + Some(size) => size, + None => fail!("CompositorLayer: tried to scroll with no page size set"), + }; + let min_x = (window_size.width - page_size.width).min(&0.0); + self.scroll_offset.x = self.scroll_offset.x.clamp(&min_x, &0.0); + let min_y = (window_size.height - page_size.height).min(&0.0); + self.scroll_offset.y = self.scroll_offset.y.clamp(&min_y, &0.0); + + // check to see if we scrolled + if old_origin - self.scroll_offset == Point2D(0f32, 0f32) { + return false; + } + + self.root_layer.common.set_transform(identity().translate(self.scroll_offset.x, + self.scroll_offset.y, + 0.0)); + true + } + FixedPosition => false // Ignore this scroll event. + } + } + // Returns whether the layer should be vertically flipped. #[cfg(target_os="macos")] fn texture_flip_and_target(cpu_painting: bool, size: Size2D) -> (Flip, TextureTarget) { diff --git a/src/components/main/compositing/mod.rs b/src/components/main/compositing/mod.rs old mode 100644 new mode 100755 index d38e0b0f30c9..4f915cf82e71 --- a/src/components/main/compositing/mod.rs +++ b/src/components/main/compositing/mod.rs @@ -49,6 +49,10 @@ impl ScriptListener for CompositorChan { self.chan.send(InvalidateRect(id, rect)); } + fn scroll_fragment_point(&self, id: PipelineId, point: Point2D) { + self.chan.send(ScrollFragmentPoint(id, point)); + } + fn close(&self) { self.chan.send(Exit); } @@ -125,7 +129,8 @@ pub enum Msg { DeleteLayer(PipelineId), /// Invalidate a rect for a given layer InvalidateRect(PipelineId, Rect), - + /// Scroll a page in a window + ScrollFragmentPoint(PipelineId, Point2D), /// Requests that the compositor paint the given layer buffer set for the given page size. Paint(PipelineId, ~LayerBufferSet, Epoch), /// Alerts the compositor to the current status of page loading. diff --git a/src/components/main/compositing/run.rs b/src/components/main/compositing/run.rs old mode 100644 new mode 100755 index f833aa5d4ffd..b1b1a3bf7832 --- a/src/components/main/compositing/run.rs +++ b/src/components/main/compositing/run.rs @@ -227,6 +227,18 @@ pub fn run_compositor(compositor: &CompositorTask) { None => {} // Nothing to do } } + + ScrollFragmentPoint(id, point) => { + let page_window = Size2D(window_size.width as f32 / world_zoom, + window_size.height as f32 / world_zoom); + match compositor_layer { + Some(ref mut layer) if layer.pipeline.id == id => { + recomposite = layer.move(point, page_window) | recomposite; + ask_for_tiles(); + } + Some(_) | None => {} + } + } } } }; diff --git a/src/components/main/compositing/run_headless.rs b/src/components/main/compositing/run_headless.rs old mode 100644 new mode 100755 index c6be88ac36d8..914891a124fa --- a/src/components/main/compositing/run_headless.rs +++ b/src/components/main/compositing/run_headless.rs @@ -31,7 +31,7 @@ pub fn run_compositor(compositor: &CompositorTask) { NewLayer(*) | SetLayerPageSize(*) | SetLayerClipRect(*) | DeleteLayer(*) | Paint(*) | InvalidateRect(*) | ChangeReadyState(*) | ChangeRenderState(*)| - SetUnRenderedColor(*) + ScrollFragmentPoint(*) | SetUnRenderedColor(*) => () } } diff --git a/src/components/msg/compositor_msg.rs b/src/components/msg/compositor_msg.rs old mode 100644 new mode 100755 index 55a247eab974..c1d84cfa062a --- a/src/components/msg/compositor_msg.rs +++ b/src/components/msg/compositor_msg.rs @@ -2,6 +2,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +use geom::point::Point2D; use geom::rect::Rect; use geom::size::Size2D; use azure::azure_hl::Color; @@ -89,6 +90,7 @@ pub trait RenderListener { pub trait ScriptListener : Clone { fn set_ready_state(&self, ReadyState); fn invalidate_rect(&self, PipelineId, Rect); + fn scroll_fragment_point(&self, PipelineId, Point2D); fn close(&self); } diff --git a/src/components/script/dom/node.rs b/src/components/script/dom/node.rs index d21c994cb977..d8b3aae253d4 100644 --- a/src/components/script/dom/node.rs +++ b/src/components/script/dom/node.rs @@ -12,7 +12,7 @@ use dom::characterdata::CharacterData; use dom::document::{AbstractDocument, DocumentTypeId}; use dom::documenttype::DocumentType; use dom::element::{Element, ElementTypeId, HTMLImageElementTypeId, HTMLIframeElementTypeId}; -use dom::element::{HTMLStyleElementTypeId}; +use dom::element::{HTMLAnchorElementTypeId, HTMLStyleElementTypeId}; use dom::eventtarget::{AbstractEventTarget, EventTarget, NodeTypeId}; use dom::nodelist::{NodeList}; use dom::htmlimageelement::HTMLImageElement; @@ -492,6 +492,10 @@ impl<'self, View> AbstractNode { self.type_id() == ElementNodeTypeId(HTMLStyleElementTypeId) } + pub fn is_anchor_element(self) -> bool { + self.type_id() == ElementNodeTypeId(HTMLAnchorElementTypeId) + } + pub unsafe fn raw_object(self) -> *mut Box> { self.obj } diff --git a/src/components/script/script_task.rs b/src/components/script/script_task.rs old mode 100644 new mode 100755 index 438f5429f06a..dc49ca493b82 --- a/src/components/script/script_task.rs +++ b/src/components/script/script_task.rs @@ -19,14 +19,18 @@ use html::hubbub_html_parser::HtmlParserResult; use html::hubbub_html_parser::{HtmlDiscoveredStyle, HtmlDiscoveredIFrame, HtmlDiscoveredScript}; use html::hubbub_html_parser; use layout_interface::{AddStylesheetMsg, DocumentDamage}; +use layout_interface::{ContentBoxQuery, ContentBoxResponse}; use layout_interface::{DocumentDamageLevel, HitTestQuery, HitTestResponse, LayoutQuery}; use layout_interface::{LayoutChan, MatchSelectorsDocumentDamage, QueryMsg, ReapLayoutDataMsg}; use layout_interface::{Reflow, ReflowDocumentDamage, ReflowForDisplay, ReflowGoal, ReflowMsg}; use layout_interface; +use dom::node::ScriptView; use extra::future::Future; use extra::url::Url; +use std::str::eq_slice; use geom::size::Size2D; +use geom::point::Point2D; use js::JSVAL_NULL; use js::global::debug_fns; use js::glue::RUST_JSVAL_TO_OBJECT; @@ -41,6 +45,7 @@ use servo_msg::constellation_msg::{SubpageId}; use servo_msg::constellation_msg; use servo_net::image_cache_task::ImageCacheTask; use servo_net::resource_task::ResourceTask; +use servo_util::geometry::to_frac_px; use servo_util::tree::{TreeNode, TreeNodeRef, ElementLike}; use servo_util::url::make_url; use std::cell::Cell; @@ -126,7 +131,10 @@ pub struct Page { next_subpage_id: SubpageId, /// Pending resize event, if any. - resize_event: Option> + resize_event: Option>, + + /// Pending scroll to fragment event, if any + fragment_node: Option> } pub struct PageTree { @@ -152,6 +160,7 @@ impl PageTree { url: None, next_subpage_id: SubpageId(0), resize_event: None, + fragment_node: None, last_reflow_id: 0 }, inner: ~[], @@ -748,6 +757,8 @@ impl ScriptTask { // Kick off the initial reflow of the page. document.document().content_changed(); + let fragment = url.fragment.as_ref().map(|ref fragment| fragment.to_owned()); + // No more reflow required page.url = Some((url, false)); @@ -777,6 +788,38 @@ impl ScriptTask { let doctarget = AbstractEventTarget::from_document(document); let wintarget = AbstractEventTarget::from_window(window); window.eventtarget.dispatch_event_with_target(wintarget, Some(doctarget), event); + + page.fragment_node = fragment.map_default(None, |fragid| self.find_fragment_node(page, fragid)); + } + + fn find_fragment_node(&self, page: &mut Page, fragid: ~str) -> Option> { + let document = page.frame.expect("root frame is None").document; + match document.document().GetElementById(fragid.to_owned()) { + Some(node) => Some(node), + None => { + let doc_node = AbstractNode::from_document(document); + let mut anchors = doc_node.traverse_preorder().filter(|node| node.is_anchor_element()); + do anchors.find |node| { + do node.with_imm_element |elem| { + match elem.get_attr("name") { + Some(name) => eq_slice(name, fragid), + None => false + } + } + } + } + } + } + + fn scroll_fragment_point(&self, pipeline_id: PipelineId, page: &mut Page, node: AbstractNode) { + let (port, chan) = comm::stream(); + match page.query_layout(ContentBoxQuery(node, chan), port) { + ContentBoxResponse(rect) => { + let point = Point2D(to_frac_px(rect.origin.x).to_f32().unwrap(), + to_frac_px(rect.origin.y).to_f32().unwrap()); + self.compositor.scroll_fragment_point(pipeline_id, point); + } + } } /// This is the main entry point for receiving and dispatching DOM events. @@ -797,6 +840,10 @@ impl ScriptTask { page.damage(ReflowDocumentDamage); page.reflow(ReflowForDisplay, self.chan.clone(), self.compositor) } + match page.fragment_node.take() { + Some(node) => self.scroll_fragment_point(pipeline_id, page, node), + None => {} + } } // FIXME(pcwalton): This reflows the entire document and is not incremental-y. @@ -856,12 +903,21 @@ impl ScriptTask { let attr = element.get_attr("href"); for href in attr.iter() { debug!("ScriptTask: clicked on link to {:s}", *href); + let click_frag = href.starts_with("#"); let current_url = do page.url.as_ref().map |&(ref url, _)| { url.clone() }; debug!("ScriptTask: current url is {:?}", current_url); let url = make_url(href.to_owned(), current_url); - self.constellation_chan.send(LoadUrlMsg(page.id, url, Future::from_value(page.window_size.get()))); + + if click_frag { + match self.find_fragment_node(page, url.fragment.unwrap()) { + Some(node) => self.scroll_fragment_point(page.id, page, node), + None => {} + } + } else { + self.constellation_chan.send(LoadUrlMsg(page.id, url, Future::from_value(page.window_size.get()))); + } } } } diff --git a/src/components/util/url.rs b/src/components/util/url.rs index bf796c02c19e..aeb5245f9fa7 100644 --- a/src/components/util/url.rs +++ b/src/components/util/url.rs @@ -42,6 +42,8 @@ pub fn make_url(str_url: ~str, current_url: Option) -> Url { current_url.scheme + "://" + current_url.host + "/" + str_url.trim_left_chars(&'/') + } else if str_url.starts_with("#") { + current_url.scheme + "://" + current_url.host + current_url.path + str_url } else { let mut path = ~[]; for p in current_url.path.split_iter('/') { @@ -145,6 +147,18 @@ mod make_url_tests { assert!(new_url.path == ~"/snarf/crumpet.html"); } + #[test] + fn should_create_url_based_on_old_url_5() { + let old_str = ~"http://example.com/index.html"; + let old_url = make_url(old_str, None); + let new_str = ~"#top"; + let new_url = make_url(new_str, Some(old_url)); + + assert!(new_url.scheme == ~"http"); + assert!(new_url.host == ~"example.com"); + assert!(new_url.path == ~"/index.html"); + assert!(new_url.fragment == Some(~"top")); + } } pub type UrlMap = HashMap;