diff options
Diffstat (limited to 'components/script/script_task.rs')
-rw-r--r-- | components/script/script_task.rs | 400 |
1 files changed, 101 insertions, 299 deletions
diff --git a/components/script/script_task.rs b/components/script/script_task.rs index fa397113c9b..b59bef57468 100644 --- a/components/script/script_task.rs +++ b/components/script/script_task.rs @@ -15,23 +15,20 @@ use dom::bindings::codegen::Bindings::WindowBinding::WindowMethods; use dom::bindings::codegen::InheritTypes::{ElementCast, EventTargetCast, HTMLIFrameElementCast, NodeCast, EventCast}; use dom::bindings::conversions::FromJSValConvertible; use dom::bindings::conversions::StringificationBehavior; -use dom::bindings::global::GlobalRef; use dom::bindings::js::{JS, JSRef, Temporary, OptionalRootable, RootedReference}; use dom::bindings::js::{RootCollection, RootCollectionPtr}; use dom::bindings::refcounted::{LiveDOMReferences, Trusted, TrustedReference}; use dom::bindings::structuredclone::StructuredCloneData; use dom::bindings::trace::JSTraceable; use dom::bindings::utils::{wrap_for_same_compartment, pre_wrap}; -use dom::document::{Document, IsHTMLDocument, DocumentHelpers, DocumentSource}; -use dom::element::{Element, ElementTypeId, ActivationElementHelpers}; -use dom::event::{Event, EventHelpers, EventBubbles, EventCancelable}; +use dom::document::{Document, IsHTMLDocument, DocumentHelpers, DocumentProgressHandler, DocumentProgressTask, DocumentSource}; +use dom::element::{Element, ActivationElementHelpers}; +use dom::event::{Event, EventHelpers}; use dom::uievent::UIEvent; -use dom::eventtarget::{EventTarget, EventTargetHelpers}; -use dom::htmlelement::HTMLElementTypeId; -use dom::htmliframeelement::HTMLIFrameElement; +use dom::eventtarget::EventTarget; use dom::keyboardevent::KeyboardEvent; use dom::mouseevent::MouseEvent; -use dom::node::{self, Node, NodeHelpers, NodeDamage, NodeTypeId, window_from_node}; +use dom::node::{self, Node, NodeHelpers, NodeDamage}; use dom::window::{Window, WindowHelpers, ScriptHelpers}; use dom::worker::{Worker, TrustedWorkerAddress}; use parse::html::{HTMLInput, parse_html}; @@ -69,8 +66,7 @@ use util::task::spawn_named_with_send_on_failure; use util::task_state; use geom::point::Point2D; -use hyper::header::{Header, Headers, HeaderFormat}; -use hyper::header::parsing as header_parsing; +use hyper::header::{LastModified, Headers}; use js::jsapi::{JS_SetWrapObjectCallbacks, JS_SetGCZeal, JS_DEFAULT_ZEAL_FREQ, JS_GC}; use js::jsapi::{JSContext, JSRuntime, JSObject}; use js::jsapi::{JS_SetGCParameter, JSGC_MAX_BYTES}; @@ -83,13 +79,13 @@ use libc; use std::any::Any; use std::borrow::ToOwned; use std::cell::Cell; -use std::fmt::{self, Display}; use std::mem::replace; use std::num::ToPrimitive; use std::rc::Rc; +use std::result::Result; use std::sync::mpsc::{channel, Sender, Receiver, Select}; use std::u32; -use time::{Tm, strptime}; +use time::Tm; thread_local!(pub static STACK_ROOTS: Cell<Option<RootCollectionPtr>> = Cell::new(None)); @@ -136,7 +132,7 @@ pub enum ScriptMsg { /// A cloneable interface for communicating with an event loop. pub trait ScriptChan { /// Send a message to the associated event loop. - fn send(&self, msg: ScriptMsg); + fn send(&self, msg: ScriptMsg) -> Result<(), ()>; /// Clone this handle. fn clone(&self) -> Box<ScriptChan+Send>; } @@ -146,9 +142,9 @@ pub trait ScriptChan { pub struct NonWorkerScriptChan(pub Sender<ScriptMsg>); impl ScriptChan for NonWorkerScriptChan { - fn send(&self, msg: ScriptMsg) { + fn send(&self, msg: ScriptMsg) -> Result<(), ()> { let NonWorkerScriptChan(ref chan) = *self; - chan.send(msg).unwrap(); + return chan.send(msg).map_err(|_| ()); } fn clone(&self) -> Box<ScriptChan+Send> { @@ -224,7 +220,7 @@ pub struct ScriptTask { /// The JSContext. js_context: DOMRefCell<Option<Rc<Cx>>>, - mouse_over_targets: DOMRefCell<Option<Vec<JS<Node>>>> + mouse_over_targets: DOMRefCell<Vec<JS<Node>>> } /// In the event of task failure, all data on the stack runs its destructor. However, there @@ -265,24 +261,6 @@ impl<'a> Drop for ScriptMemoryFailsafe<'a> { } } -trait PrivateScriptTaskHelpers { - fn click_event_filter_by_disabled_state(&self) -> bool; -} - -impl<'a> PrivateScriptTaskHelpers for JSRef<'a, Node> { - fn click_event_filter_by_disabled_state(&self) -> bool { - match self.type_id() { - NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLButtonElement)) | - NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLInputElement)) | - // NodeTypeId::Element(ElementTypeId::HTMLKeygenElement) | - NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLOptionElement)) | - NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLSelectElement)) | - NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLTextAreaElement)) if self.get_disabled_state() => true, - _ => false - } - } -} - impl ScriptTaskFactory for ScriptTask { fn create_layout_channel(_phantom: Option<&mut ScriptTask>) -> OpaqueScriptLayoutChannel { let (chan, port) = channel(); @@ -405,7 +383,7 @@ impl ScriptTask { js_runtime: js_runtime, js_context: DOMRefCell::new(Some(js_context)), - mouse_over_targets: DOMRefCell::new(None) + mouse_over_targets: DOMRefCell::new(vec!()) } } @@ -795,31 +773,28 @@ impl ScriptTask { fn load(&self, pipeline_id: PipelineId, parent: Option<(PipelineId, SubpageId)>, load_data: LoadData) { let url = load_data.url.clone(); - debug!("ScriptTask: loading {:?} on page {:?}", url, pipeline_id); + debug!("ScriptTask: loading {} on page {:?}", url.serialize(), pipeline_id); let borrowed_page = self.page.borrow_mut(); let frame_element = parent.and_then(|(parent_id, subpage_id)| { - // In the case a parent id exists but the matching page - // cannot be found, this means the page exists in a different - // script task (due to origin) so it shouldn't be returned. - // TODO: window.parent will continue to return self in that - // case, which is wrong. We should be returning an object that - // denies access to most properties (per - // https://github.com/servo/servo/issues/3939#issuecomment-62287025). - borrowed_page.find(parent_id).and_then(|page| { - let match_iframe = |&:&node: &JSRef<HTMLIFrameElement>| { - node.subpage_id().map_or(false, |id| id == subpage_id) - }; - - let doc = page.frame().as_ref().unwrap().document.root(); - let doc: JSRef<Node> = NodeCast::from_ref(doc.r()); - - doc.traverse_preorder() - .filter_map(|node| HTMLIFrameElementCast::to_ref(node)) - .find(match_iframe) - .map(|node| Temporary::from_rooted(ElementCast::from_ref(node))) - }) + // In the case a parent id exists but the matching page + // cannot be found, this means the page exists in a different + // script task (due to origin) so it shouldn't be returned. + // TODO: window.parent will continue to return self in that + // case, which is wrong. We should be returning an object that + // denies access to most properties (per + // https://github.com/servo/servo/issues/3939#issuecomment-62287025). + borrowed_page.find(parent_id).and_then(|page| { + let doc = page.frame().as_ref().unwrap().document.root(); + let doc: JSRef<Node> = NodeCast::from_ref(doc.r()); + + doc.traverse_preorder() + .filter_map(HTMLIFrameElementCast::to_ref) + .find(|node| node.subpage_id() == Some(subpage_id)) + .map(ElementCast::from_ref) + .map(Temporary::from_rooted) + }) }).root(); let page = borrowed_page.find(pipeline_id).expect("ScriptTask: received a load @@ -940,22 +915,16 @@ impl ScriptTask { // https://html.spec.whatwg.org/multipage/#the-end step 4 let addr: Trusted<Document> = Trusted::new(self.get_cx(), document.r(), self.chan.clone()); - - self.chan.send(ScriptMsg::RunnableMsg(box DocumentProgressHandler { - addr: addr.clone(), - task: DocumentProgressTask::DOMContentLoaded, - })); + let handler = Box::new(DocumentProgressHandler::new(addr.clone(), DocumentProgressTask::DOMContentLoaded)); + self.chan.send(ScriptMsg::RunnableMsg(handler)).unwrap(); // We have no concept of a document loader right now, so just dispatch the // "load" event as soon as we've finished executing all scripts parsed during // the initial load. // https://html.spec.whatwg.org/multipage/#the-end step 7 - self.chan.send(ScriptMsg::RunnableMsg(box DocumentProgressHandler { - addr: addr, - task: DocumentProgressTask::Load, - })); - + let handler = Box::new(DocumentProgressHandler::new(addr, DocumentProgressTask::Load)); + self.chan.send(ScriptMsg::RunnableMsg(handler)).unwrap(); *page.fragment_name.borrow_mut() = final_url.fragment.clone(); @@ -1032,7 +1001,10 @@ impl ScriptTask { } ClickEvent(_button, point) => { - self.handle_click_event(pipeline_id, _button, point); + let page = get_page(&*self.page.borrow(), pipeline_id); + let frame = page.frame(); + let document = frame.as_ref().unwrap().document.root(); + document.r().handle_click_event(self.js_runtime.ptr, _button, point); } MouseDownEvent(..) => {} @@ -1198,143 +1170,75 @@ impl ScriptTask { } } - fn handle_click_event(&self, pipeline_id: PipelineId, _button: uint, point: Point2D<f32>) { - debug!("ClickEvent: clicked at {:?}", point); - let page = get_page(&*self.page.borrow(), pipeline_id); - match page.hit_test(&point) { - Some(node_address) => { - debug!("node address is {:?}", node_address.0); - - let temp_node = - node::from_untrusted_node_address( - self.js_runtime.ptr, node_address).root(); - - let maybe_elem: Option<JSRef<Element>> = ElementCast::to_ref(temp_node.r()); - let maybe_node = match maybe_elem { - Some(element) => Some(element), - None => temp_node.r().ancestors().filter_map(ElementCast::to_ref).next(), - }; - - match maybe_node { - Some(el) => { - let node: JSRef<Node> = NodeCast::from_ref(el); - debug!("clicked on {:?}", node.debug_str()); - // Prevent click event if form control element is disabled. - if node.click_event_filter_by_disabled_state() { return; } - match *page.frame() { - Some(ref frame) => { - let window = frame.window.root(); - let doc = window.r().Document().root(); - doc.r().begin_focus_transaction(); - - let event = - Event::new(GlobalRef::Window(window.r()), - "click".to_owned(), - EventBubbles::Bubbles, - EventCancelable::Cancelable).root(); - // https://dvcs.w3.org/hg/dom3events/raw-file/tip/html/DOM3-Events.html#trusted-events - event.r().set_trusted(true); - // https://html.spec.whatwg.org/multipage/interaction.html#run-authentic-click-activation-steps - el.authentic_click_activation(event.r()); - - doc.r().commit_focus_transaction(); - window.r().flush_layout(ReflowGoal::ForDisplay, ReflowQueryType::NoQuery); - } - None => {} - } - } - None => {} - } - } - - None => {} - } - } - fn handle_mouse_move_event(&self, pipeline_id: PipelineId, point: Point2D<f32>) { 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; - - let mouse_over_targets = &mut *self.mouse_over_targets.borrow_mut(); - match *mouse_over_targets { - Some(ref mut mouse_over_targets) => { - for node in mouse_over_targets.iter_mut() { - let node = node.root(); - node.r().set_hover_state(false); - } - } - None => {} - } - - if node_address.len() > 0 { - let top_most_node = - node::from_untrusted_node_address(self.js_runtime.ptr, node_address[0]).root(); - - if let Some(ref frame) = *page.frame() { - let window = frame.window.root(); - - let x = point.x.to_i32().unwrap_or(0); - let y = point.y.to_i32().unwrap_or(0); - - let mouse_event = MouseEvent::new(window.r(), - "mousemove".to_owned(), - true, - true, - Some(window.r()), - 0i32, - x, y, x, y, - false, false, false, false, - 0i16, - None).root(); - - let event: JSRef<Event> = EventCast::from_ref(mouse_event.r()); - let target: JSRef<EventTarget> = EventTargetCast::from_ref(top_most_node.r()); - event.fire(target); - } - } + let mut needs_reflow = false; + + // Build a list of elements that are currently under the mouse. + let mouse_over_addresses = page.get_nodes_under_mouse(&point); + let mouse_over_targets: Vec<JS<Node>> = mouse_over_addresses.iter() + .filter_map(|node_address| { + let node = node::from_untrusted_node_address(self.js_runtime.ptr, *node_address); + node.root().r().inclusive_ancestors().find(|node| node.is_element()).map(JS::from_rooted) + }).collect(); + + // Remove hover from any elements in the previous list that are no longer + // under the mouse. + let prev_mouse_over_targets = &mut *self.mouse_over_targets.borrow_mut(); + for target in prev_mouse_over_targets.iter() { + if !mouse_over_targets.contains(target) { + target.root().r().set_hover_state(false); + needs_reflow = true; + } + } - for node_address in node_address.iter() { - let temp_node = - node::from_untrusted_node_address(self.js_runtime.ptr, *node_address).root(); - - let maybe_node = temp_node.r().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)); - } - _ => {} - } - target_list.push(JS::from_rooted(node)); - } - None => {} - } - } - match *mouse_over_targets { - Some(ref mouse_over_targets) => { - if mouse_over_targets.len() != target_list.len() { - target_compare = true - } - } - None => target_compare = true, - } + // Set hover state for any elements in the current mouse over list. + // Check if any of them changed state to determine whether to + // force a reflow below. + for target in mouse_over_targets.iter() { + let target = target.root(); + let target_ref = target.r(); + if !target_ref.get_hover_state() { + target_ref.set_hover_state(true); + needs_reflow = true; + } + } - if target_compare { - if mouse_over_targets.is_some() { - self.force_reflow(&*page) - } - *mouse_over_targets = Some(target_list); - } + // Send mousemove event to topmost target + if mouse_over_addresses.len() > 0 { + let top_most_node = + node::from_untrusted_node_address(self.js_runtime.ptr, mouse_over_addresses[0]).root(); + + if let Some(ref frame) = *page.frame() { + let window = frame.window.root(); + + let x = point.x.to_i32().unwrap_or(0); + let y = point.y.to_i32().unwrap_or(0); + + let mouse_event = MouseEvent::new(window.r(), + "mousemove".to_owned(), + true, + true, + Some(window.r()), + 0i32, + x, y, x, y, + false, false, false, false, + 0i16, + None).root(); + + let event: JSRef<Event> = EventCast::from_ref(mouse_event.r()); + let target: JSRef<EventTarget> = EventTargetCast::from_ref(top_most_node.r()); + event.fire(target); } + } - None => {} + // Store the current mouse over targets for next frame + *prev_mouse_over_targets = mouse_over_targets; + + // Reflow if hover state changed + if needs_reflow { + self.force_reflow(&*page); } } } @@ -1381,108 +1285,6 @@ pub fn get_page(page: &Rc<Page>, pipeline_id: PipelineId) -> Rc<Page> { This is a bug.") } -//FIXME(seanmonstar): uplift to Hyper -#[derive(Clone)] -struct LastModified(pub Tm); - -impl Header for LastModified { - #[inline] - fn header_name() -> &'static str { - "Last-Modified" - } - - // Parses an RFC 2616 compliant date/time string, - fn parse_header(raw: &[Vec<u8>]) -> Option<LastModified> { - header_parsing::from_one_raw_str(raw).and_then(|s: String| { - let s = s.as_slice(); - strptime(s, "%a, %d %b %Y %T %Z").or_else(|_| { - strptime(s, "%A, %d-%b-%y %T %Z") - }).or_else(|_| { - strptime(s, "%c") - }).ok().map(|tm| LastModified(tm)) - }) - } -} - -impl HeaderFormat for LastModified { - // a localized date/time string in a format suitable - // for document.lastModified. - fn fmt_header(&self, f: &mut fmt::Formatter) -> fmt::Result { - let LastModified(ref tm) = *self; - match tm.tm_utcoff { - 0 => <_ as Display>::fmt(&tm.rfc822(), f), - _ => <_ as Display>::fmt(&tm.to_utc().rfc822(), f) - } - } -} - fn dom_last_modified(tm: &Tm) -> String { format!("{}", tm.to_local().strftime("%m/%d/%Y %H:%M:%S").unwrap()) } - -enum DocumentProgressTask { - DOMContentLoaded, - Load, -} - -struct DocumentProgressHandler { - addr: Trusted<Document>, - task: DocumentProgressTask, -} - -impl DocumentProgressHandler { - fn dispatch_dom_content_loaded(&self) { - let document = self.addr.to_temporary().root(); - let window = document.r().window().root(); - let event = Event::new(GlobalRef::Window(window.r()), "DOMContentLoaded".to_owned(), - EventBubbles::DoesNotBubble, - EventCancelable::NotCancelable).root(); - let doctarget: JSRef<EventTarget> = EventTargetCast::from_ref(document.r()); - let _ = doctarget.DispatchEvent(event.r()); - } - - fn set_ready_state_complete(&self) { - let document = self.addr.to_temporary().root(); - document.r().set_ready_state(DocumentReadyState::Complete); - } - - fn dispatch_load(&self) { - let document = self.addr.to_temporary().root(); - let window = document.r().window().root(); - let event = Event::new(GlobalRef::Window(window.r()), "load".to_owned(), - EventBubbles::DoesNotBubble, - EventCancelable::NotCancelable).root(); - let wintarget: JSRef<EventTarget> = EventTargetCast::from_ref(window.r()); - let doctarget: JSRef<EventTarget> = EventTargetCast::from_ref(document.r()); - event.r().set_trusted(true); - let _ = wintarget.dispatch_event_with_target(doctarget, event.r()); - - let window_ref = window.r(); - let browser_context = window_ref.browser_context(); - let browser_context = browser_context.as_ref().unwrap(); - - browser_context.frame_element().map(|frame_element| { - let frame_element = frame_element.root(); - let frame_window = window_from_node(frame_element.r()).root(); - let event = Event::new(GlobalRef::Window(frame_window.r()), "load".to_owned(), - EventBubbles::DoesNotBubble, - EventCancelable::NotCancelable).root(); - let target: JSRef<EventTarget> = EventTargetCast::from_ref(frame_element.r()); - event.r().fire(target); - }); - } -} - -impl Runnable for DocumentProgressHandler { - fn handler(self: Box<DocumentProgressHandler>) { - match self.task { - DocumentProgressTask::DOMContentLoaded => { - self.dispatch_dom_content_loaded(); - } - DocumentProgressTask::Load => { - self.set_ready_state_complete(); - self.dispatch_load(); - } - } - } -} |