mirror of
https://github.com/servo/servo.git
synced 2025-08-04 13:10:20 +01:00
Implement local bookmark(hashtag) for Acid2. With @joonwonlee
Support hashtag through link (e.g. <a href="#top">)
This commit is contained in:
parent
c7ad220178
commit
266b551aa4
8 changed files with 129 additions and 5 deletions
31
src/components/main/compositing/compositor_layer.rs
Normal file → Executable file
31
src/components/main/compositing/compositor_layer.rs
Normal file → Executable file
|
@ -381,6 +381,37 @@ impl CompositorLayer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn move(&mut self, origin: Point2D<f32>, window_size: Size2D<f32>) -> 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.
|
// Returns whether the layer should be vertically flipped.
|
||||||
#[cfg(target_os="macos")]
|
#[cfg(target_os="macos")]
|
||||||
fn texture_flip_and_target(cpu_painting: bool, size: Size2D<uint>) -> (Flip, TextureTarget) {
|
fn texture_flip_and_target(cpu_painting: bool, size: Size2D<uint>) -> (Flip, TextureTarget) {
|
||||||
|
|
7
src/components/main/compositing/mod.rs
Normal file → Executable file
7
src/components/main/compositing/mod.rs
Normal file → Executable file
|
@ -49,6 +49,10 @@ impl ScriptListener for CompositorChan {
|
||||||
self.chan.send(InvalidateRect(id, rect));
|
self.chan.send(InvalidateRect(id, rect));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn scroll_fragment_point(&self, id: PipelineId, point: Point2D<f32>) {
|
||||||
|
self.chan.send(ScrollFragmentPoint(id, point));
|
||||||
|
}
|
||||||
|
|
||||||
fn close(&self) {
|
fn close(&self) {
|
||||||
self.chan.send(Exit);
|
self.chan.send(Exit);
|
||||||
}
|
}
|
||||||
|
@ -125,7 +129,8 @@ pub enum Msg {
|
||||||
DeleteLayer(PipelineId),
|
DeleteLayer(PipelineId),
|
||||||
/// Invalidate a rect for a given layer
|
/// Invalidate a rect for a given layer
|
||||||
InvalidateRect(PipelineId, Rect<uint>),
|
InvalidateRect(PipelineId, Rect<uint>),
|
||||||
|
/// Scroll a page in a window
|
||||||
|
ScrollFragmentPoint(PipelineId, Point2D<f32>),
|
||||||
/// Requests that the compositor paint the given layer buffer set for the given page size.
|
/// Requests that the compositor paint the given layer buffer set for the given page size.
|
||||||
Paint(PipelineId, ~LayerBufferSet, Epoch),
|
Paint(PipelineId, ~LayerBufferSet, Epoch),
|
||||||
/// Alerts the compositor to the current status of page loading.
|
/// Alerts the compositor to the current status of page loading.
|
||||||
|
|
12
src/components/main/compositing/run.rs
Normal file → Executable file
12
src/components/main/compositing/run.rs
Normal file → Executable file
|
@ -227,6 +227,18 @@ pub fn run_compositor(compositor: &CompositorTask) {
|
||||||
None => {} // Nothing to do
|
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 => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
2
src/components/main/compositing/run_headless.rs
Normal file → Executable file
2
src/components/main/compositing/run_headless.rs
Normal file → Executable file
|
@ -31,7 +31,7 @@ pub fn run_compositor(compositor: &CompositorTask) {
|
||||||
|
|
||||||
NewLayer(*) | SetLayerPageSize(*) | SetLayerClipRect(*) | DeleteLayer(*) |
|
NewLayer(*) | SetLayerPageSize(*) | SetLayerClipRect(*) | DeleteLayer(*) |
|
||||||
Paint(*) | InvalidateRect(*) | ChangeReadyState(*) | ChangeRenderState(*)|
|
Paint(*) | InvalidateRect(*) | ChangeReadyState(*) | ChangeRenderState(*)|
|
||||||
SetUnRenderedColor(*)
|
ScrollFragmentPoint(*) | SetUnRenderedColor(*)
|
||||||
=> ()
|
=> ()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
2
src/components/msg/compositor_msg.rs
Normal file → Executable file
2
src/components/msg/compositor_msg.rs
Normal file → Executable file
|
@ -2,6 +2,7 @@
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* 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/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
use geom::point::Point2D;
|
||||||
use geom::rect::Rect;
|
use geom::rect::Rect;
|
||||||
use geom::size::Size2D;
|
use geom::size::Size2D;
|
||||||
use azure::azure_hl::Color;
|
use azure::azure_hl::Color;
|
||||||
|
@ -89,6 +90,7 @@ pub trait RenderListener {
|
||||||
pub trait ScriptListener : Clone {
|
pub trait ScriptListener : Clone {
|
||||||
fn set_ready_state(&self, ReadyState);
|
fn set_ready_state(&self, ReadyState);
|
||||||
fn invalidate_rect(&self, PipelineId, Rect<uint>);
|
fn invalidate_rect(&self, PipelineId, Rect<uint>);
|
||||||
|
fn scroll_fragment_point(&self, PipelineId, Point2D<f32>);
|
||||||
fn close(&self);
|
fn close(&self);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,7 @@ use dom::characterdata::CharacterData;
|
||||||
use dom::document::{AbstractDocument, DocumentTypeId};
|
use dom::document::{AbstractDocument, DocumentTypeId};
|
||||||
use dom::documenttype::DocumentType;
|
use dom::documenttype::DocumentType;
|
||||||
use dom::element::{Element, ElementTypeId, HTMLImageElementTypeId, HTMLIframeElementTypeId};
|
use dom::element::{Element, ElementTypeId, HTMLImageElementTypeId, HTMLIframeElementTypeId};
|
||||||
use dom::element::{HTMLStyleElementTypeId};
|
use dom::element::{HTMLAnchorElementTypeId, HTMLStyleElementTypeId};
|
||||||
use dom::eventtarget::{AbstractEventTarget, EventTarget, NodeTypeId};
|
use dom::eventtarget::{AbstractEventTarget, EventTarget, NodeTypeId};
|
||||||
use dom::nodelist::{NodeList};
|
use dom::nodelist::{NodeList};
|
||||||
use dom::htmlimageelement::HTMLImageElement;
|
use dom::htmlimageelement::HTMLImageElement;
|
||||||
|
@ -492,6 +492,10 @@ impl<'self, View> AbstractNode<View> {
|
||||||
self.type_id() == ElementNodeTypeId(HTMLStyleElementTypeId)
|
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<Node<View>> {
|
pub unsafe fn raw_object(self) -> *mut Box<Node<View>> {
|
||||||
self.obj
|
self.obj
|
||||||
}
|
}
|
||||||
|
|
58
src/components/script/script_task.rs
Normal file → Executable file
58
src/components/script/script_task.rs
Normal file → Executable file
|
@ -19,14 +19,18 @@ use html::hubbub_html_parser::HtmlParserResult;
|
||||||
use html::hubbub_html_parser::{HtmlDiscoveredStyle, HtmlDiscoveredIFrame, HtmlDiscoveredScript};
|
use html::hubbub_html_parser::{HtmlDiscoveredStyle, HtmlDiscoveredIFrame, HtmlDiscoveredScript};
|
||||||
use html::hubbub_html_parser;
|
use html::hubbub_html_parser;
|
||||||
use layout_interface::{AddStylesheetMsg, DocumentDamage};
|
use layout_interface::{AddStylesheetMsg, DocumentDamage};
|
||||||
|
use layout_interface::{ContentBoxQuery, ContentBoxResponse};
|
||||||
use layout_interface::{DocumentDamageLevel, HitTestQuery, HitTestResponse, LayoutQuery};
|
use layout_interface::{DocumentDamageLevel, HitTestQuery, HitTestResponse, LayoutQuery};
|
||||||
use layout_interface::{LayoutChan, MatchSelectorsDocumentDamage, QueryMsg, ReapLayoutDataMsg};
|
use layout_interface::{LayoutChan, MatchSelectorsDocumentDamage, QueryMsg, ReapLayoutDataMsg};
|
||||||
use layout_interface::{Reflow, ReflowDocumentDamage, ReflowForDisplay, ReflowGoal, ReflowMsg};
|
use layout_interface::{Reflow, ReflowDocumentDamage, ReflowForDisplay, ReflowGoal, ReflowMsg};
|
||||||
use layout_interface;
|
use layout_interface;
|
||||||
|
|
||||||
|
use dom::node::ScriptView;
|
||||||
use extra::future::Future;
|
use extra::future::Future;
|
||||||
use extra::url::Url;
|
use extra::url::Url;
|
||||||
|
use std::str::eq_slice;
|
||||||
use geom::size::Size2D;
|
use geom::size::Size2D;
|
||||||
|
use geom::point::Point2D;
|
||||||
use js::JSVAL_NULL;
|
use js::JSVAL_NULL;
|
||||||
use js::global::debug_fns;
|
use js::global::debug_fns;
|
||||||
use js::glue::RUST_JSVAL_TO_OBJECT;
|
use js::glue::RUST_JSVAL_TO_OBJECT;
|
||||||
|
@ -41,6 +45,7 @@ use servo_msg::constellation_msg::{SubpageId};
|
||||||
use servo_msg::constellation_msg;
|
use servo_msg::constellation_msg;
|
||||||
use servo_net::image_cache_task::ImageCacheTask;
|
use servo_net::image_cache_task::ImageCacheTask;
|
||||||
use servo_net::resource_task::ResourceTask;
|
use servo_net::resource_task::ResourceTask;
|
||||||
|
use servo_util::geometry::to_frac_px;
|
||||||
use servo_util::tree::{TreeNode, TreeNodeRef, ElementLike};
|
use servo_util::tree::{TreeNode, TreeNodeRef, ElementLike};
|
||||||
use servo_util::url::make_url;
|
use servo_util::url::make_url;
|
||||||
use std::cell::Cell;
|
use std::cell::Cell;
|
||||||
|
@ -126,7 +131,10 @@ pub struct Page {
|
||||||
next_subpage_id: SubpageId,
|
next_subpage_id: SubpageId,
|
||||||
|
|
||||||
/// Pending resize event, if any.
|
/// Pending resize event, if any.
|
||||||
resize_event: Option<Size2D<uint>>
|
resize_event: Option<Size2D<uint>>,
|
||||||
|
|
||||||
|
/// Pending scroll to fragment event, if any
|
||||||
|
fragment_node: Option<AbstractNode<ScriptView>>
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct PageTree {
|
pub struct PageTree {
|
||||||
|
@ -152,6 +160,7 @@ impl PageTree {
|
||||||
url: None,
|
url: None,
|
||||||
next_subpage_id: SubpageId(0),
|
next_subpage_id: SubpageId(0),
|
||||||
resize_event: None,
|
resize_event: None,
|
||||||
|
fragment_node: None,
|
||||||
last_reflow_id: 0
|
last_reflow_id: 0
|
||||||
},
|
},
|
||||||
inner: ~[],
|
inner: ~[],
|
||||||
|
@ -748,6 +757,8 @@ impl ScriptTask {
|
||||||
// Kick off the initial reflow of the page.
|
// Kick off the initial reflow of the page.
|
||||||
document.document().content_changed();
|
document.document().content_changed();
|
||||||
|
|
||||||
|
let fragment = url.fragment.as_ref().map(|ref fragment| fragment.to_owned());
|
||||||
|
|
||||||
// No more reflow required
|
// No more reflow required
|
||||||
page.url = Some((url, false));
|
page.url = Some((url, false));
|
||||||
|
|
||||||
|
@ -777,6 +788,38 @@ impl ScriptTask {
|
||||||
let doctarget = AbstractEventTarget::from_document(document);
|
let doctarget = AbstractEventTarget::from_document(document);
|
||||||
let wintarget = AbstractEventTarget::from_window(window);
|
let wintarget = AbstractEventTarget::from_window(window);
|
||||||
window.eventtarget.dispatch_event_with_target(wintarget, Some(doctarget), event);
|
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<AbstractNode<ScriptView>> {
|
||||||
|
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<ScriptView>) {
|
||||||
|
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.
|
/// This is the main entry point for receiving and dispatching DOM events.
|
||||||
|
@ -797,6 +840,10 @@ impl ScriptTask {
|
||||||
page.damage(ReflowDocumentDamage);
|
page.damage(ReflowDocumentDamage);
|
||||||
page.reflow(ReflowForDisplay, self.chan.clone(), self.compositor)
|
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.
|
// FIXME(pcwalton): This reflows the entire document and is not incremental-y.
|
||||||
|
@ -856,14 +903,23 @@ impl ScriptTask {
|
||||||
let attr = element.get_attr("href");
|
let attr = element.get_attr("href");
|
||||||
for href in attr.iter() {
|
for href in attr.iter() {
|
||||||
debug!("ScriptTask: clicked on link to {:s}", *href);
|
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, _)| {
|
let current_url = do page.url.as_ref().map |&(ref url, _)| {
|
||||||
url.clone()
|
url.clone()
|
||||||
};
|
};
|
||||||
debug!("ScriptTask: current url is {:?}", current_url);
|
debug!("ScriptTask: current url is {:?}", current_url);
|
||||||
let url = make_url(href.to_owned(), current_url);
|
let url = make_url(href.to_owned(), current_url);
|
||||||
|
|
||||||
|
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())));
|
self.constellation_chan.send(LoadUrlMsg(page.id, url, Future::from_value(page.window_size.get())));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Shuts down layout for the given page.
|
/// Shuts down layout for the given page.
|
||||||
|
|
|
@ -42,6 +42,8 @@ pub fn make_url(str_url: ~str, current_url: Option<Url>) -> Url {
|
||||||
current_url.scheme + "://" +
|
current_url.scheme + "://" +
|
||||||
current_url.host + "/" +
|
current_url.host + "/" +
|
||||||
str_url.trim_left_chars(&'/')
|
str_url.trim_left_chars(&'/')
|
||||||
|
} else if str_url.starts_with("#") {
|
||||||
|
current_url.scheme + "://" + current_url.host + current_url.path + str_url
|
||||||
} else {
|
} else {
|
||||||
let mut path = ~[];
|
let mut path = ~[];
|
||||||
for p in current_url.path.split_iter('/') {
|
for p in current_url.path.split_iter('/') {
|
||||||
|
@ -145,6 +147,18 @@ mod make_url_tests {
|
||||||
assert!(new_url.path == ~"/snarf/crumpet.html");
|
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<T> = HashMap<Url, T>;
|
pub type UrlMap<T> = HashMap<Url, T>;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue