mirror of
https://github.com/servo/servo.git
synced 2025-08-03 12:40:06 +01:00
auto merge of #2441 : saneyuki/servo/event, r=jdm
- Fix #2118 - Take over from #2293
This commit is contained in:
commit
e59557e3bb
7 changed files with 169 additions and 39 deletions
|
@ -83,6 +83,7 @@ pub trait DocumentHelpers {
|
|||
fn wait_until_safe_to_modify_dom(&self);
|
||||
fn unregister_named_element(&mut self, to_unregister: &JSRef<Element>, id: DOMString);
|
||||
fn register_named_element(&mut self, element: &JSRef<Element>, 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 {
|
||||
|
|
|
@ -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<Node>> = 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 {
|
||||
|
|
|
@ -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<Event>);
|
||||
}
|
||||
|
||||
impl<'a> PrivateHTMLAnchorElementHelpers for JSRef<'a, HTMLAnchorElement> {
|
||||
fn handle_event_impl(&self, event: &JSRef<Event>) {
|
||||
if "click" == event.Type() && !event.DefaultPrevented() {
|
||||
let element: &JSRef<Element> = 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<Node> = 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<HTMLElement> = HTMLElementCast::from_mut_ref(self);
|
||||
Some(htmlelement as &mut VirtualMethods:)
|
||||
}
|
||||
|
||||
fn handle_event(&mut self, event: &JSRef<Event>) {
|
||||
match self.super_type() {
|
||||
Some(s) => {
|
||||
s.handle_event(event);
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
self.handle_event_impl(event);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<Event>) {
|
||||
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<Node>) -> &'a mut VirtualMethods: {
|
||||
match node.type_id() {
|
||||
ElementNodeTypeId(HTMLAnchorElementTypeId) => {
|
||||
let element: &mut JSRef<HTMLAnchorElement> = HTMLAnchorElementCast::to_mut_ref(node).unwrap();
|
||||
element as &mut VirtualMethods:
|
||||
}
|
||||
ElementNodeTypeId(HTMLImageElementTypeId) => {
|
||||
let element: &mut JSRef<HTMLImageElement> = HTMLImageElementCast::to_mut_ref(node).unwrap();
|
||||
element as &mut VirtualMethods:
|
||||
|
|
|
@ -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<Document>);
|
||||
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<Document>) {
|
||||
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> {
|
||||
|
|
|
@ -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<Temporary<Element>> {
|
||||
/// Attempt to find a named element in this page's document.
|
||||
fn find_fragment_node(&self, fragid: DOMString) -> Option<Temporary<Element>> {
|
||||
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<Page>, pipeline_id: PipelineId) -> Rc<Page> {
|
||||
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<Element> = 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<EventTarget> = 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<Element>) {
|
||||
// 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<Page>, rt: *JSRuntime) {
|
|||
chan.send(layout_interface::ExitNowMsg);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fn get_page(page: &Rc<Page>, pipeline_id: PipelineId) -> Rc<Page> {
|
||||
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.")
|
||||
}
|
||||
|
|
18
src/test/content/test_click_prevent.html
Normal file
18
src/test/content/test_click_prevent.html
Normal file
|
@ -0,0 +1,18 @@
|
|||
<html>
|
||||
<head>
|
||||
<script src="./harness.js"></script>
|
||||
<a id="foo" href="/nonexistent">test link</a>
|
||||
<script>
|
||||
var link = document.getElementById('foo');
|
||||
link.addEventListener('click', function(ev) {
|
||||
ev.preventDefault();
|
||||
});
|
||||
var ev = new Event('click', {bubbles: true, cancelable: true});
|
||||
link.dispatchEvent(ev);
|
||||
setTimeout(function() {
|
||||
is(true, true, "load probably would have occurred by now");
|
||||
finish();
|
||||
}, 500);
|
||||
</script>
|
||||
</head>
|
||||
</html>
|
Loading…
Add table
Add a link
Reference in a new issue