diff --git a/src/components/script/dom/document.rs b/src/components/script/dom/document.rs index 2ed144add2b..f970afa9aff 100644 --- a/src/components/script/dom/document.rs +++ b/src/components/script/dom/document.rs @@ -83,6 +83,7 @@ pub trait DocumentHelpers { fn wait_until_safe_to_modify_dom(&self); fn unregister_named_element(&mut self, to_unregister: &JSRef, id: DOMString); fn register_named_element(&mut self, element: &JSRef, id: DOMString); + fn load_anchor_href(&self, href: DOMString); } impl<'a> DocumentHelpers for JSRef<'a, Document> { @@ -176,6 +177,11 @@ impl<'a> DocumentHelpers for JSRef<'a, Document> { elements.push_unrooted(element); self.idmap.insert(id, elements); } + + fn load_anchor_href(&self, href: DOMString) { + let mut window = self.window.root(); + window.load_url(href); + } } impl Document { diff --git a/src/components/script/dom/eventdispatcher.rs b/src/components/script/dom/eventdispatcher.rs index 1bd90f93137..3e653b9ace3 100644 --- a/src/components/script/dom/eventdispatcher.rs +++ b/src/components/script/dom/eventdispatcher.rs @@ -4,10 +4,11 @@ use dom::bindings::callback::ReportExceptions; use dom::bindings::codegen::InheritTypes::{EventTargetCast, NodeCast, NodeDerived}; -use dom::bindings::js::{JSRef, OptionalSettable, Root}; +use dom::bindings::js::{JSRef, OptionalSettable, OptionalRootable, Root}; use dom::eventtarget::{Capturing, Bubbling, EventTarget}; use dom::event::{Event, PhaseAtTarget, PhaseNone, PhaseBubbling, PhaseCapturing, EventMethods}; use dom::node::{Node, NodeHelpers}; +use dom::virtualmethods::vtable_for; // See http://dom.spec.whatwg.org/#concept-event-dispatch for the full dispatch algorithm pub fn dispatch_event<'a, 'b>(target: &JSRef<'a, EventTarget>, @@ -115,6 +116,22 @@ pub fn dispatch_event<'a, 'b>(target: &JSRef<'a, EventTarget>, } } + /* default action */ + let target = event.GetTarget().root(); + match target { + Some(mut target) => { + let node: Option<&mut JSRef> = NodeCast::to_mut_ref(&mut *target); + match node { + Some(node) =>{ + let vtable = vtable_for(node); + vtable.handle_event(event); + } + None => {} + } + } + None => {} + } + // Root ordering restrictions mean we need to unroot the chain entries // in the same order they were rooted. while chain.len() > 0 { diff --git a/src/components/script/dom/htmlanchorelement.rs b/src/components/script/dom/htmlanchorelement.rs index fcc6f200203..b9de564237f 100644 --- a/src/components/script/dom/htmlanchorelement.rs +++ b/src/components/script/dom/htmlanchorelement.rs @@ -4,13 +4,18 @@ use dom::bindings::codegen::BindingDeclarations::HTMLAnchorElementBinding; use dom::bindings::codegen::InheritTypes::HTMLAnchorElementDerived; -use dom::bindings::js::{JSRef, Temporary}; +use dom::bindings::codegen::InheritTypes::{ElementCast, HTMLElementCast, NodeCast}; +use dom::bindings::js::{JSRef, Temporary, OptionalRootable}; use dom::bindings::error::ErrorResult; -use dom::document::Document; -use dom::element::HTMLAnchorElementTypeId; +use dom::document::{Document, DocumentHelpers}; +use dom::attr::AttrMethods; +use dom::element::{Element, AttributeHandlers, HTMLAnchorElementTypeId}; +use dom::event::{Event, EventMethods}; use dom::eventtarget::{EventTarget, NodeTargetTypeId}; use dom::htmlelement::HTMLElement; -use dom::node::{Node, ElementNodeTypeId}; +use dom::node::{Node, NodeHelpers, ElementNodeTypeId}; +use dom::virtualmethods::VirtualMethods; +use servo_util::namespace::Null; use servo_util::str::DOMString; #[deriving(Encodable)] @@ -171,3 +176,43 @@ impl<'a> HTMLAnchorElementMethods for JSRef<'a, HTMLAnchorElement> { Ok(()) } } + +trait PrivateHTMLAnchorElementHelpers { + fn handle_event_impl(&self, event: &JSRef); +} + +impl<'a> PrivateHTMLAnchorElementHelpers for JSRef<'a, HTMLAnchorElement> { + fn handle_event_impl(&self, event: &JSRef) { + if "click" == event.Type() && !event.DefaultPrevented() { + let element: &JSRef = ElementCast::from_ref(self); + let attr = element.get_attribute(Null, "href").root(); + match attr { + Some(ref href) => { + let value = href.Value(); + debug!("clicked on link to {:s}", value); + let node: &JSRef = NodeCast::from_ref(self); + let mut doc = node.owner_doc().root(); + doc.load_anchor_href(value); + } + None => () + } + } + } +} + +impl<'a> VirtualMethods for JSRef<'a, HTMLAnchorElement> { + fn super_type<'a>(&'a mut self) -> Option<&'a mut VirtualMethods:> { + let htmlelement: &mut JSRef = HTMLElementCast::from_mut_ref(self); + Some(htmlelement as &mut VirtualMethods:) + } + + fn handle_event(&mut self, event: &JSRef) { + match self.super_type() { + Some(s) => { + s.handle_event(event); + } + None => {} + } + self.handle_event_impl(event); + } +} diff --git a/src/components/script/dom/virtualmethods.rs b/src/components/script/dom/virtualmethods.rs index 2b0d9aabfe5..2c0dc358411 100644 --- a/src/components/script/dom/virtualmethods.rs +++ b/src/components/script/dom/virtualmethods.rs @@ -3,6 +3,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use dom::bindings::codegen::InheritTypes::ElementCast; +use dom::bindings::codegen::InheritTypes::HTMLAnchorElementCast; use dom::bindings::codegen::InheritTypes::HTMLElementCast; use dom::bindings::codegen::InheritTypes::HTMLIFrameElementCast; use dom::bindings::codegen::InheritTypes::HTMLImageElementCast; @@ -10,8 +11,10 @@ use dom::bindings::codegen::InheritTypes::HTMLObjectElementCast; use dom::bindings::codegen::InheritTypes::HTMLStyleElementCast; use dom::bindings::js::JSRef; use dom::element::Element; -use dom::element::{ElementTypeId, HTMLImageElementTypeId}; +use dom::element::{ElementTypeId, HTMLAnchorElementTypeId, HTMLImageElementTypeId}; use dom::element::{HTMLIFrameElementTypeId, HTMLObjectElementTypeId, HTMLStyleElementTypeId}; +use dom::event::Event; +use dom::htmlanchorelement::HTMLAnchorElement; use dom::htmlelement::HTMLElement; use dom::htmliframeelement::HTMLIFrameElement; use dom::htmlimageelement::HTMLImageElement; @@ -68,6 +71,16 @@ pub trait VirtualMethods { _ => (), } } + + /// Called during event dispatch after the bubbling phase completes. + fn handle_event(&mut self, event: &JSRef) { + match self.super_type() { + Some(s) => { + s.handle_event(event); + } + _ => (), + } + } } /// Obtain a VirtualMethods instance for a given Node-derived object. Any @@ -76,6 +89,10 @@ pub trait VirtualMethods { /// interrupted. pub fn vtable_for<'a>(node: &'a mut JSRef) -> &'a mut VirtualMethods: { match node.type_id() { + ElementNodeTypeId(HTMLAnchorElementTypeId) => { + let element: &mut JSRef = HTMLAnchorElementCast::to_mut_ref(node).unwrap(); + element as &mut VirtualMethods: + } ElementNodeTypeId(HTMLImageElementTypeId) => { let element: &mut JSRef = HTMLImageElementCast::to_mut_ref(node).unwrap(); element as &mut VirtualMethods: diff --git a/src/components/script/dom/window.rs b/src/components/script/dom/window.rs index e620714a9c4..e8d4a121dcd 100644 --- a/src/components/script/dom/window.rs +++ b/src/components/script/dom/window.rs @@ -16,11 +16,12 @@ use dom::navigator::Navigator; use dom::performance::Performance; use layout_interface::{ReflowForDisplay, DocumentDamageLevel}; -use script_task::{ExitWindowMsg, FireTimerMsg, Page, ScriptChan}; +use script_task::{ExitWindowMsg, FireTimerMsg, Page, ScriptChan, TriggerLoadMsg, TriggerFragmentMsg}; use servo_msg::compositor_msg::ScriptListener; use servo_net::image_cache_task::ImageCacheTask; use servo_util::str::DOMString; use servo_util::task::{spawn_named}; +use servo_util::url::parse_url; use js::jsapi::JSContext; use js::jsapi::{JS_GC, JS_GetRuntime}; @@ -292,6 +293,7 @@ pub trait WindowHelpers { fn damage_and_reflow(&self, damage: DocumentDamageLevel); fn wait_until_safe_to_modify_dom(&self); fn init_browser_context(&mut self, doc: &JSRef); + fn load_url(&self, href: DOMString); } trait PrivateWindowHelpers { @@ -316,6 +318,19 @@ impl<'a> WindowHelpers for JSRef<'a, Window> { fn init_browser_context(&mut self, doc: &JSRef) { self.browser_context = Some(BrowserContext::new(doc)); } + + /// Commence a new URL load which will either replace this window or scroll to a fragment. + fn load_url(&self, href: DOMString) { + let base_url = Some(self.page().get_url()); + debug!("current page url is {:?}", base_url); + let url = parse_url(href, base_url); + let ScriptChan(ref script_chan) = self.script_chan; + if href.starts_with("#") { + script_chan.send(TriggerFragmentMsg(self.page.id, url)); + } else { + script_chan.send(TriggerLoadMsg(self.page.id, url)); + } + } } impl<'a> PrivateWindowHelpers for JSRef<'a, Window> { diff --git a/src/components/script/script_task.rs b/src/components/script/script_task.rs index 05c58db96e6..5846be72fa8 100644 --- a/src/components/script/script_task.rs +++ b/src/components/script/script_task.rs @@ -5,7 +5,6 @@ //! The script task is the task that owns the DOM in memory, runs JavaScript, and spawns parsing //! and layout tasks. -use dom::attr::AttrMethods; use dom::bindings::codegen::RegisterBindings; use dom::bindings::codegen::InheritTypes::{EventTargetCast, NodeCast, ElementCast, EventCast}; use dom::bindings::js::{JS, JSRef, RootCollection, Temporary, OptionalSettable}; @@ -51,9 +50,9 @@ 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::url::parse_url; use servo_util::task::send_on_failure; use servo_util::namespace::Null; +use servo_util::str::DOMString; use std::cast; use std::cell::{Cell, RefCell, Ref, RefMut}; use std::comm::{channel, Sender, Receiver, Empty, Disconnected}; @@ -71,6 +70,10 @@ local_data_key!(pub StackRoots: *RootCollection) pub enum ScriptMsg { /// Loads a new URL on the specified pipeline. LoadMsg(PipelineId, Url), + /// Acts on a fragment URL load on the specified pipeline. + TriggerFragmentMsg(PipelineId, Url), + /// Begins a content-initiated load on the specified pipeline. + TriggerLoadMsg(PipelineId, Url), /// Gives a channel and ID to a layout task, as well as the ID of that layout's parent AttachLayoutMsg(NewLayoutInfo), /// Instructs the script task to send a navigate message to the constellation. @@ -440,7 +443,8 @@ impl Page { } } - fn find_fragment_node(&self, fragid: ~str) -> Option> { + /// Attempt to find a named element in this page's document. + fn find_fragment_node(&self, fragid: DOMString) -> Option> { let document = self.frame().get_ref().document.root(); match document.deref().GetElementById(fragid.to_owned()) { Some(node) => Some(node), @@ -774,6 +778,8 @@ impl ScriptTask { // TODO(tkuehn) need to handle auxiliary layouts for iframes AttachLayoutMsg(new_layout_info) => self.handle_new_layout(new_layout_info), LoadMsg(id, url) => self.load(id, url), + TriggerLoadMsg(id, url) => self.trigger_load(id, url), + TriggerFragmentMsg(id, url) => self.trigger_fragment(id, url), SendEventMsg(id, event) => self.handle_event(id, event), FireTimerMsg(id, timer_id) => self.handle_fire_timer_msg(id, timer_id), NavigateMsg(direction) => self.handle_navigate_msg(direction), @@ -1064,12 +1070,6 @@ impl ScriptTask { /// /// TODO: Actually perform DOM event dispatch. fn handle_event(&self, pipeline_id: PipelineId, event: Event_) { - fn get_page(page: &Rc, pipeline_id: PipelineId) -> Rc { - page.find(pipeline_id).expect("ScriptTask: received an event \ - message for a layout channel that is not associated with this script task.\ - This is a bug.") - } - match event { ResizeEvent(new_width, new_height) => { debug!("script got resize event: {:u}, {:u}", new_width, new_height); @@ -1130,12 +1130,20 @@ impl ScriptTask { node::from_untrusted_node_address( self.js_runtime.deref().ptr, node_address); - let maybe_node = temp_node.root().ancestors().find(|node| node.is_anchor_element()); + let maybe_node = temp_node.root().ancestors().find(|node| node.is_element()); match maybe_node { Some(node) => { debug!("clicked on {:s}", node.debug_str()); - let element: &JSRef = ElementCast::to_ref(&node).unwrap(); - self.load_url_from_element(&*page, element); + match *page.frame() { + Some(ref frame) => { + let window = frame.window.root(); + let mut event = Event::new(&*window).root(); + event.InitEvent("click".to_owned(), true, true); + let eventtarget: &JSRef = EventTargetCast::from_ref(&node); + eventtarget.dispatch_event_with_target(None, &mut *event); + } + None => {} + } } None => {} } @@ -1213,27 +1221,24 @@ impl ScriptTask { } } - fn load_url_from_element(&self, page: &Page, element: &JSRef) { - // if the node's element is "a," load url from href attr - let attr = element.get_attribute(Null, "href"); - for href in attr.root().iter() { - debug!("ScriptTask: clicked on link to {:s}", href.Value()); - let click_frag = href.deref().value_ref().starts_with("#"); - let base_url = Some(page.get_url()); - debug!("ScriptTask: current url is {:?}", base_url); - let url = parse_url(href.deref().value_ref(), base_url); - - if click_frag { - match page.find_fragment_node(url.fragment.unwrap()).root() { - Some(node) => self.scroll_fragment_point(page.id, &*node), - None => {} - } - } else { - let ConstellationChan(ref chan) = self.constellation_chan; - chan.send(LoadUrlMsg(page.id, url)); - } - } + /// The entry point for content to notify that a new load has been requested + /// for the given pipeline. + fn trigger_load(&self, pipeline_id: PipelineId, url: Url) { + let ConstellationChan(ref const_chan) = self.constellation_chan; + const_chan.send(LoadUrlMsg(pipeline_id, url)); } + + /// The entry point for content to notify that a fragment url has been requested + /// for the given pipeline. + fn trigger_fragment(&self, pipeline_id: PipelineId, url: Url) { + let page = get_page(&*self.page.borrow(), pipeline_id); + match page.find_fragment_node(url.fragment.unwrap()).root() { + Some(node) => { + self.scroll_fragment_point(pipeline_id, &*node); + } + None => {} + } + } } /// Shuts down layout for the given page tree. @@ -1271,3 +1276,10 @@ fn shut_down_layout(page_tree: &Rc, rt: *JSRuntime) { chan.send(layout_interface::ExitNowMsg); } } + + +fn get_page(page: &Rc, pipeline_id: PipelineId) -> Rc { + page.find(pipeline_id).expect("ScriptTask: received an event \ + message for a layout channel that is not associated with this script task.\ + This is a bug.") +} diff --git a/src/test/content/test_click_prevent.html b/src/test/content/test_click_prevent.html new file mode 100644 index 00000000000..1636080e0af --- /dev/null +++ b/src/test/content/test_click_prevent.html @@ -0,0 +1,18 @@ + + + +test link + + +