aboutsummaryrefslogtreecommitdiffstats
path: root/components/script/script_task.rs
diff options
context:
space:
mode:
Diffstat (limited to 'components/script/script_task.rs')
-rw-r--r--components/script/script_task.rs400
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();
- }
- }
- }
-}