aboutsummaryrefslogtreecommitdiffstats
path: root/components/script/script_thread.rs
diff options
context:
space:
mode:
authorrohan.prinja <rohan.prinja@samsung.com>2015-11-14 05:07:55 +0900
committerRohan Prinja <rohan.prinja@gmail.com>2016-01-10 17:58:13 +0900
commit1f02c4ebbb7d5ea49051f4391f1418f20c15d7a9 (patch)
tree7d61e58e746ddca93074ca6bca6849360a098b8c /components/script/script_thread.rs
parentf00532bab0382d1c24e6086314f26497fb6ffe0f (diff)
downloadservo-1f02c4ebbb7d5ea49051f4391f1418f20c15d7a9.tar.gz
servo-1f02c4ebbb7d5ea49051f4391f1418f20c15d7a9.zip
task -> thread
Diffstat (limited to 'components/script/script_thread.rs')
-rw-r--r--components/script/script_thread.rs2247
1 files changed, 2247 insertions, 0 deletions
diff --git a/components/script/script_thread.rs b/components/script/script_thread.rs
new file mode 100644
index 00000000000..f0ed7ac22d6
--- /dev/null
+++ b/components/script/script_thread.rs
@@ -0,0 +1,2247 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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/. */
+
+//! The script thread is the thread that owns the DOM in memory, runs JavaScript, and spawns parsing
+//! and layout threads. It's in charge of processing events for all same-origin pages in a frame
+//! tree, and manages the entire lifetime of pages in the frame tree from initial request to
+//! teardown.
+//!
+//! Page loads follow a two-step process. When a request for a new page load is received, the
+//! network request is initiated and the relevant data pertaining to the new page is stashed.
+//! While the non-blocking request is ongoing, the script thread is free to process further events,
+//! noting when they pertain to ongoing loads (such as resizes/viewport adjustments). When the
+//! initial response is received for an ongoing load, the second phase starts - the frame tree
+//! entry is created, along with the Window and Document objects, and the appropriate parser
+//! takes over the response body. Once parsing is complete, the document lifecycle for loading
+//! a page runs its course and the script thread returns to processing events in the main event
+//! loop.
+
+use devtools;
+use devtools_traits::{DevtoolScriptControlMsg, DevtoolsPageInfo};
+use devtools_traits::{ScriptToDevtoolsControlMsg, WorkerId};
+use document_loader::DocumentLoader;
+use dom::bindings::cell::DOMRefCell;
+use dom::bindings::codegen::Bindings::DocumentBinding::{DocumentMethods, DocumentReadyState};
+use dom::bindings::conversions::{FromJSValConvertible, StringificationBehavior};
+use dom::bindings::global::GlobalRef;
+use dom::bindings::inheritance::Castable;
+use dom::bindings::js::{JS, RootCollection, trace_roots};
+use dom::bindings::js::{RootCollectionPtr, RootedReference};
+use dom::bindings::refcounted::{LiveDOMReferences, Trusted, TrustedReference, trace_refcounted_objects};
+use dom::bindings::trace::{JSTraceable, RootedVec, trace_traceables};
+use dom::bindings::utils::{DOM_CALLBACKS, WRAP_CALLBACKS};
+use dom::document::{Document, DocumentProgressHandler, DocumentSource, FocusType, IsHTMLDocument};
+use dom::element::Element;
+use dom::event::{Event, EventBubbles, EventCancelable};
+use dom::htmlanchorelement::HTMLAnchorElement;
+use dom::node::{Node, NodeDamage, window_from_node};
+use dom::servohtmlparser::{ParserContext, ParserRoot};
+use dom::uievent::UIEvent;
+use dom::window::{ReflowReason, ScriptHelpers, Window};
+use dom::worker::TrustedWorkerAddress;
+use euclid::Rect;
+use euclid::point::Point2D;
+use gfx_traits::LayerId;
+use hyper::header::{ContentType, HttpDate};
+use hyper::header::{Headers, LastModified};
+use hyper::method::Method;
+use hyper::mime::{Mime, SubLevel, TopLevel};
+use ipc_channel::ipc::{self, IpcSender};
+use ipc_channel::router::ROUTER;
+use js::glue::CollectServoSizes;
+use js::jsapi::{DOMProxyShadowsResult, HandleId, HandleObject, RootedValue};
+use js::jsapi::{DisableIncrementalGC, JS_AddExtraGCRootsTracer, JS_SetWrapObjectCallbacks};
+use js::jsapi::{GCDescription, GCProgress, JSGCInvocationKind, SetGCSliceCallback};
+use js::jsapi::{JSAutoRequest, JSGCStatus, JS_GetRuntime, JS_SetGCCallback, SetDOMCallbacks};
+use js::jsapi::{JSContext, JSRuntime, JSTracer};
+use js::jsapi::{JSObject, SetPreserveWrapperCallback};
+use js::jsval::UndefinedValue;
+use js::rust::Runtime;
+use layout_interface::{ReflowQueryType};
+use layout_interface::{self, LayoutChan, NewLayoutThreadInfo, ReflowGoal, ScriptLayoutChan};
+use libc;
+use mem::heap_size_of_self_and_children;
+use msg::constellation_msg::{ConstellationChan, LoadData};
+use msg::constellation_msg::{MouseButton, MouseEventType, MozBrowserEvent, PipelineId};
+use msg::constellation_msg::{PipelineNamespace};
+use msg::constellation_msg::{SubpageId, WindowSizeData};
+use msg::webdriver_msg::WebDriverScriptCommand;
+use net_traits::LoadData as NetLoadData;
+use net_traits::image_cache_thread::{ImageCacheChan, ImageCacheResult, ImageCacheThread};
+use net_traits::storage_thread::StorageThread;
+use net_traits::{AsyncResponseTarget, ControlMsg, LoadConsumer, LoadContext, Metadata, ResourceThread};
+use network_listener::NetworkListener;
+use page::{Frame, IterablePage, Page};
+use parse::html::{ParseContext, parse_html};
+use parse::xml::{self, parse_xml};
+use profile_traits::mem::{self, OpaqueSender, Report, ReportKind, ReportsChan};
+use profile_traits::time::{self, ProfilerCategory, profile};
+use script_traits::CompositorEvent::{KeyEvent, MouseButtonEvent, MouseMoveEvent, ResizeEvent};
+use script_traits::CompositorEvent::{TouchEvent};
+use script_traits::{CompositorEvent, ConstellationControlMsg, EventResult, InitialScriptState, NewLayoutInfo};
+use script_traits::{LayoutMsg, OpaqueScriptLayoutChannel, ScriptMsg as ConstellationMsg};
+use script_traits::{ScriptThreadFactory, ScriptToCompositorMsg, TimerEvent, TimerEventRequest, TimerSource};
+use script_traits::{TouchEventType, TouchId};
+use std::any::Any;
+use std::borrow::ToOwned;
+use std::cell::{Cell, RefCell};
+use std::collections::HashSet;
+use std::io::{Write, stdout};
+use std::marker::PhantomData;
+use std::mem as std_mem;
+use std::option::Option;
+use std::ptr;
+use std::rc::Rc;
+use std::result::Result;
+use std::sync::atomic::{Ordering, AtomicBool};
+use std::sync::mpsc::{Receiver, Select, Sender, channel};
+use std::sync::{Arc, Mutex};
+use time::{Tm, now};
+use url::Url;
+use util::opts;
+use util::str::DOMString;
+use util::thread;
+use util::thread_state;
+use webdriver_handlers;
+
+thread_local!(pub static STACK_ROOTS: Cell<Option<RootCollectionPtr>> = Cell::new(None));
+thread_local!(static SCRIPT_TASK_ROOT: RefCell<Option<*const ScriptThread>> = RefCell::new(None));
+
+unsafe extern fn trace_rust_roots(tr: *mut JSTracer, _data: *mut libc::c_void) {
+ SCRIPT_TASK_ROOT.with(|root| {
+ if let Some(script_thread) = *root.borrow() {
+ (*script_thread).trace(tr);
+ }
+ });
+
+ trace_traceables(tr);
+ trace_roots(tr);
+}
+
+/// A document load that is in the process of fetching the requested resource. Contains
+/// data that will need to be present when the document and frame tree entry are created,
+/// but is only easily available at initiation of the load and on a push basis (so some
+/// data will be updated according to future resize events, viewport changes, etc.)
+#[derive(JSTraceable)]
+struct InProgressLoad {
+ /// The pipeline which requested this load.
+ pipeline_id: PipelineId,
+ /// The parent pipeline and child subpage associated with this load, if any.
+ parent_info: Option<(PipelineId, SubpageId)>,
+ /// The current window size associated with this pipeline.
+ window_size: Option<WindowSizeData>,
+ /// Channel to the layout thread associated with this pipeline.
+ layout_chan: LayoutChan,
+ /// The current viewport clipping rectangle applying to this pipeline, if any.
+ clip_rect: Option<Rect<f32>>,
+ /// Window is frozen (navigated away while loading for example).
+ is_frozen: bool,
+ /// The requested URL of the load.
+ url: Url,
+}
+
+impl InProgressLoad {
+ /// Create a new InProgressLoad object.
+ fn new(id: PipelineId,
+ parent_info: Option<(PipelineId, SubpageId)>,
+ layout_chan: LayoutChan,
+ window_size: Option<WindowSizeData>,
+ url: Url) -> InProgressLoad {
+ InProgressLoad {
+ pipeline_id: id,
+ parent_info: parent_info,
+ layout_chan: layout_chan,
+ window_size: window_size,
+ clip_rect: None,
+ is_frozen: false,
+ url: url,
+ }
+ }
+}
+
+/// Encapsulated state required to create cancellable runnables from non-script threads.
+pub struct RunnableWrapper {
+ pub cancelled: Arc<AtomicBool>,
+}
+
+impl RunnableWrapper {
+ pub fn wrap_runnable<T: Runnable + Send + 'static>(&self, runnable: T) -> Box<Runnable + Send> {
+ box CancellableRunnable {
+ cancelled: self.cancelled.clone(),
+ inner: box runnable,
+ }
+ }
+}
+
+/// A runnable that can be discarded by toggling a shared flag.
+pub struct CancellableRunnable<T: Runnable + Send> {
+ cancelled: Arc<AtomicBool>,
+ inner: Box<T>,
+}
+
+impl<T: Runnable + Send> Runnable for CancellableRunnable<T> {
+ fn is_cancelled(&self) -> bool {
+ self.cancelled.load(Ordering::Relaxed)
+ }
+
+ fn handler(self: Box<CancellableRunnable<T>>) {
+ self.inner.handler()
+ }
+}
+
+pub trait Runnable {
+ fn is_cancelled(&self) -> bool { false }
+ fn handler(self: Box<Self>);
+}
+
+pub trait MainThreadRunnable {
+ fn handler(self: Box<Self>, script_thread: &ScriptThread);
+}
+
+enum MixedMessage {
+ FromConstellation(ConstellationControlMsg),
+ FromScript(MainThreadScriptMsg),
+ FromDevtools(DevtoolScriptControlMsg),
+ FromImageCache(ImageCacheResult),
+ FromScheduler(TimerEvent),
+}
+
+/// Common messages used to control the event loops in both the script and the worker
+pub enum CommonScriptMsg {
+ /// Requests that the script thread measure its memory usage. The results are sent back via the
+ /// supplied channel.
+ CollectReports(ReportsChan),
+ /// A DOM object's last pinned reference was removed (dispatched to all threads).
+ RefcountCleanup(TrustedReference),
+ /// Generic message that encapsulates event handling.
+ RunnableMsg(ScriptThreadEventCategory, Box<Runnable + Send>),
+}
+
+#[derive(Clone, Copy, Debug, Eq, Hash, JSTraceable, PartialEq)]
+pub enum ScriptThreadEventCategory {
+ AttachLayout,
+ ConstellationMsg,
+ DevtoolsMsg,
+ DocumentEvent,
+ DomEvent,
+ FileRead,
+ ImageCacheMsg,
+ InputEvent,
+ NetworkEvent,
+ Resize,
+ ScriptEvent,
+ TimerEvent,
+ SetViewport,
+ StylesheetLoad,
+ UpdateReplacedElement,
+ WebSocketEvent,
+ WorkerEvent,
+}
+
+/// Messages used to control the script event loop
+pub enum MainThreadScriptMsg {
+ /// Common variants associated with the script messages
+ Common(CommonScriptMsg),
+ /// Notify a document that all pending loads are complete.
+ DocumentLoadsComplete(PipelineId),
+ /// Notifies the script that a window associated with a particular pipeline
+ /// should be closed (only dispatched to ScriptThread).
+ ExitWindow(PipelineId),
+ /// Generic message for running threads in the ScriptThread
+ MainThreadRunnableMsg(Box<MainThreadRunnable + Send>),
+ /// Begins a content-initiated load on the specified pipeline (only
+ /// dispatched to ScriptThread).
+ Navigate(PipelineId, LoadData),
+}
+
+/// A cloneable interface for communicating with an event loop.
+pub trait ScriptChan {
+ /// Send a message to the associated event loop.
+ fn send(&self, msg: CommonScriptMsg) -> Result<(), ()>;
+ /// Clone this handle.
+ fn clone(&self) -> Box<ScriptChan + Send>;
+}
+
+impl OpaqueSender<CommonScriptMsg> for Box<ScriptChan + Send> {
+ fn send(&self, msg: CommonScriptMsg) {
+ ScriptChan::send(&**self, msg).unwrap();
+ }
+}
+
+/// An interface for receiving ScriptMsg values in an event loop. Used for synchronous DOM
+/// APIs that need to abstract over multiple kinds of event loops (worker/main thread) with
+/// different Receiver interfaces.
+pub trait ScriptPort {
+ fn recv(&self) -> CommonScriptMsg;
+}
+
+impl ScriptPort for Receiver<CommonScriptMsg> {
+ fn recv(&self) -> CommonScriptMsg {
+ self.recv().unwrap()
+ }
+}
+
+impl ScriptPort for Receiver<MainThreadScriptMsg> {
+ fn recv(&self) -> CommonScriptMsg {
+ match self.recv().unwrap() {
+ MainThreadScriptMsg::Common(script_msg) => script_msg,
+ _ => panic!("unexpected main thread event message!")
+ }
+ }
+}
+
+impl ScriptPort for Receiver<(TrustedWorkerAddress, CommonScriptMsg)> {
+ fn recv(&self) -> CommonScriptMsg {
+ self.recv().unwrap().1
+ }
+}
+
+impl ScriptPort for Receiver<(TrustedWorkerAddress, MainThreadScriptMsg)> {
+ fn recv(&self) -> CommonScriptMsg {
+ match self.recv().unwrap().1 {
+ MainThreadScriptMsg::Common(script_msg) => script_msg,
+ _ => panic!("unexpected main thread event message!")
+ }
+ }
+}
+
+/// Encapsulates internal communication of shared messages within the script thread.
+#[derive(JSTraceable)]
+pub struct SendableMainThreadScriptChan(pub Sender<CommonScriptMsg>);
+
+impl ScriptChan for SendableMainThreadScriptChan {
+ fn send(&self, msg: CommonScriptMsg) -> Result<(), ()> {
+ let SendableMainThreadScriptChan(ref chan) = *self;
+ chan.send(msg).map_err(|_| ())
+ }
+
+ fn clone(&self) -> Box<ScriptChan + Send> {
+ let SendableMainThreadScriptChan(ref chan) = *self;
+ box SendableMainThreadScriptChan((*chan).clone())
+ }
+}
+
+impl SendableMainThreadScriptChan {
+ /// Creates a new script chan.
+ pub fn new() -> (Receiver<CommonScriptMsg>, Box<SendableMainThreadScriptChan>) {
+ let (chan, port) = channel();
+ (port, box SendableMainThreadScriptChan(chan))
+ }
+}
+
+/// Encapsulates internal communication of main thread messages within the script thread.
+#[derive(JSTraceable)]
+pub struct MainThreadScriptChan(pub Sender<MainThreadScriptMsg>);
+
+impl ScriptChan for MainThreadScriptChan {
+ fn send(&self, msg: CommonScriptMsg) -> Result<(), ()> {
+ let MainThreadScriptChan(ref chan) = *self;
+ chan.send(MainThreadScriptMsg::Common(msg)).map_err(|_| ())
+ }
+
+ fn clone(&self) -> Box<ScriptChan + Send> {
+ let MainThreadScriptChan(ref chan) = *self;
+ box MainThreadScriptChan((*chan).clone())
+ }
+}
+
+impl MainThreadScriptChan {
+ /// Creates a new script chan.
+ pub fn new() -> (Receiver<MainThreadScriptMsg>, Box<MainThreadScriptChan>) {
+ let (chan, port) = channel();
+ (port, box MainThreadScriptChan(chan))
+ }
+}
+
+// FIXME: Use a thread source specific message instead of MainThreadScriptMsg
+#[derive(JSTraceable)]
+pub struct DOMManipulationThreadSource(pub Sender<MainThreadScriptMsg>);
+
+impl ScriptChan for DOMManipulationThreadSource {
+ fn send(&self, msg: CommonScriptMsg) -> Result<(), ()> {
+ let DOMManipulationThreadSource(ref chan) = *self;
+ chan.send(MainThreadScriptMsg::Common(msg)).map_err(|_| ())
+ }
+
+ fn clone(&self) -> Box<ScriptChan + Send> {
+ let DOMManipulationThreadSource(ref chan) = *self;
+ box DOMManipulationThreadSource((*chan).clone())
+ }
+}
+
+// FIXME: Use a thread source specific message instead of MainThreadScriptMsg
+#[derive(JSTraceable)]
+pub struct UserInteractionThreadSource(pub Sender<MainThreadScriptMsg>);
+
+impl ScriptChan for UserInteractionThreadSource {
+ fn send(&self, msg: CommonScriptMsg) -> Result<(), ()> {
+ let UserInteractionThreadSource(ref chan) = *self;
+ chan.send(MainThreadScriptMsg::Common(msg)).map_err(|_| ())
+ }
+
+ fn clone(&self) -> Box<ScriptChan + Send> {
+ let UserInteractionThreadSource(ref chan) = *self;
+ box UserInteractionThreadSource((*chan).clone())
+ }
+}
+
+// FIXME: Use a thread source specific message instead of MainThreadScriptMsg
+#[derive(JSTraceable)]
+pub struct NetworkingThreadSource(pub Sender<MainThreadScriptMsg>);
+
+impl ScriptChan for NetworkingThreadSource {
+ fn send(&self, msg: CommonScriptMsg) -> Result<(), ()> {
+ let NetworkingThreadSource(ref chan) = *self;
+ chan.send(MainThreadScriptMsg::Common(msg)).map_err(|_| ())
+ }
+
+ fn clone(&self) -> Box<ScriptChan + Send> {
+ let NetworkingThreadSource(ref chan) = *self;
+ box NetworkingThreadSource((*chan).clone())
+ }
+}
+
+// FIXME: Use a thread source specific message instead of MainThreadScriptMsg
+#[derive(JSTraceable)]
+pub struct HistoryTraversalThreadSource(pub Sender<MainThreadScriptMsg>);
+
+impl ScriptChan for HistoryTraversalThreadSource {
+ fn send(&self, msg: CommonScriptMsg) -> Result<(), ()> {
+ let HistoryTraversalThreadSource(ref chan) = *self;
+ chan.send(MainThreadScriptMsg::Common(msg)).map_err(|_| ())
+ }
+
+ fn clone(&self) -> Box<ScriptChan + Send> {
+ let HistoryTraversalThreadSource(ref chan) = *self;
+ box HistoryTraversalThreadSource((*chan).clone())
+ }
+}
+
+// FIXME: Use a thread source specific message instead of MainThreadScriptMsg
+#[derive(JSTraceable)]
+pub struct FileReadingThreadSource(pub Sender<MainThreadScriptMsg>);
+
+impl ScriptChan for FileReadingThreadSource {
+ fn send(&self, msg: CommonScriptMsg) -> Result<(), ()> {
+ let FileReadingThreadSource(ref chan) = *self;
+ chan.send(MainThreadScriptMsg::Common(msg)).map_err(|_| ())
+ }
+
+ fn clone(&self) -> Box<ScriptChan + Send> {
+ let FileReadingThreadSource(ref chan) = *self;
+ box FileReadingThreadSource((*chan).clone())
+ }
+}
+
+// FIXME: Use a thread source specific message instead of MainThreadScriptMsg
+#[derive(JSTraceable)]
+pub struct ProfilerThreadSource(pub Sender<MainThreadScriptMsg>);
+
+impl ScriptChan for ProfilerThreadSource {
+ fn send(&self, msg: CommonScriptMsg) -> Result<(), ()> {
+ let ProfilerThreadSource(ref chan) = *self;
+ chan.send(MainThreadScriptMsg::Common(msg)).map_err(|_| ())
+ }
+
+ fn clone(&self) -> Box<ScriptChan + Send> {
+ let ProfilerThreadSource(ref chan) = *self;
+ box ProfilerThreadSource((*chan).clone())
+ }
+}
+
+pub struct StackRootTLS<'a>(PhantomData<&'a u32>);
+
+impl<'a> StackRootTLS<'a> {
+ pub fn new(roots: &'a RootCollection) -> StackRootTLS<'a> {
+ STACK_ROOTS.with(|ref r| {
+ r.set(Some(RootCollectionPtr(roots as *const _)))
+ });
+ StackRootTLS(PhantomData)
+ }
+}
+
+impl<'a> Drop for StackRootTLS<'a> {
+ fn drop(&mut self) {
+ STACK_ROOTS.with(|ref r| r.set(None));
+ }
+}
+
+
+/// Information for an entire page. Pages are top-level browsing contexts and can contain multiple
+/// frames.
+#[derive(JSTraceable)]
+// ScriptThread instances are rooted on creation, so this is okay
+#[allow(unrooted_must_root)]
+pub struct ScriptThread {
+ /// A handle to the information pertaining to page layout
+ page: DOMRefCell<Option<Rc<Page>>>,
+ /// A list of data pertaining to loads that have not yet received a network response
+ incomplete_loads: DOMRefCell<Vec<InProgressLoad>>,
+ /// A handle to the image cache thread.
+ image_cache_thread: ImageCacheThread,
+ /// A handle to the resource thread. This is an `Arc` to avoid running out of file descriptors if
+ /// there are many iframes.
+ resource_thread: Arc<ResourceThread>,
+ /// A handle to the storage thread.
+ storage_thread: StorageThread,
+
+ /// The port on which the script thread receives messages (load URL, exit, etc.)
+ port: Receiver<MainThreadScriptMsg>,
+ /// A channel to hand out to script thread-based entities that need to be able to enqueue
+ /// events in the event queue.
+ chan: MainThreadScriptChan,
+ dom_manipulation_thread_source: DOMManipulationThreadSource,
+
+ user_interaction_thread_source: UserInteractionThreadSource,
+
+ networking_thread_source: NetworkingThreadSource,
+
+ history_traversal_thread_source: HistoryTraversalThreadSource,
+
+ file_reading_thread_source: FileReadingThreadSource,
+
+ /// A channel to hand out to threads that need to respond to a message from the script thread.
+ control_chan: IpcSender<ConstellationControlMsg>,
+
+ /// The port on which the constellation and layout threads can communicate with the
+ /// script thread.
+ control_port: Receiver<ConstellationControlMsg>,
+
+ /// For communicating load url messages to the constellation
+ constellation_chan: ConstellationChan<ConstellationMsg>,
+
+ /// For communicating layout messages to the constellation
+ layout_to_constellation_chan: ConstellationChan<LayoutMsg>,
+
+ /// A handle to the compositor for communicating ready state messages.
+ compositor: DOMRefCell<IpcSender<ScriptToCompositorMsg>>,
+
+ /// The port on which we receive messages from the image cache
+ image_cache_port: Receiver<ImageCacheResult>,
+
+ /// The channel on which the image cache can send messages to ourself.
+ image_cache_channel: ImageCacheChan,
+
+ /// For providing contact with the time profiler.
+ time_profiler_chan: time::ProfilerChan,
+
+ /// For providing contact with the memory profiler.
+ mem_profiler_chan: mem::ProfilerChan,
+
+ /// For providing instructions to an optional devtools server.
+ devtools_chan: Option<IpcSender<ScriptToDevtoolsControlMsg>>,
+ /// For receiving commands from an optional devtools server. Will be ignored if
+ /// no such server exists.
+ devtools_port: Receiver<DevtoolScriptControlMsg>,
+ devtools_sender: IpcSender<DevtoolScriptControlMsg>,
+
+ /// The JavaScript runtime.
+ js_runtime: Rc<Runtime>,
+
+ mouse_over_targets: DOMRefCell<Vec<JS<Element>>>,
+
+ /// List of pipelines that have been owned and closed by this script thread.
+ closed_pipelines: DOMRefCell<HashSet<PipelineId>>,
+
+ scheduler_chan: IpcSender<TimerEventRequest>,
+ timer_event_chan: Sender<TimerEvent>,
+ timer_event_port: Receiver<TimerEvent>,
+
+ content_process_shutdown_chan: IpcSender<()>,
+}
+
+/// In the event of thread failure, all data on the stack runs its destructor. However, there
+/// are no reachable, owning pointers to the DOM memory, so it never gets freed by default
+/// when the script thread fails. The ScriptMemoryFailsafe uses the destructor bomb pattern
+/// to forcibly tear down the JS compartments for pages associated with the failing ScriptThread.
+struct ScriptMemoryFailsafe<'a> {
+ owner: Option<&'a ScriptThread>,
+}
+
+impl<'a> ScriptMemoryFailsafe<'a> {
+ fn neuter(&mut self) {
+ self.owner = None;
+ }
+
+ fn new(owner: &'a ScriptThread) -> ScriptMemoryFailsafe<'a> {
+ ScriptMemoryFailsafe {
+ owner: Some(owner),
+ }
+ }
+}
+
+impl<'a> Drop for ScriptMemoryFailsafe<'a> {
+ #[allow(unrooted_must_root)]
+ fn drop(&mut self) {
+ match self.owner {
+ Some(owner) => {
+ unsafe {
+ let page = owner.page.borrow_for_script_deallocation();
+ for page in page.iter() {
+ let window = page.window();
+ window.clear_js_runtime_for_script_deallocation();
+ }
+ }
+ }
+ None => (),
+ }
+ }
+}
+
+impl ScriptThreadFactory for ScriptThread {
+ fn create_layout_channel(_phantom: Option<&mut ScriptThread>) -> OpaqueScriptLayoutChannel {
+ let (chan, port) = channel();
+ ScriptLayoutChan::new(chan, port)
+ }
+
+ fn clone_layout_channel(_phantom: Option<&mut ScriptThread>, pair: &OpaqueScriptLayoutChannel)
+ -> Box<Any + Send> {
+ box pair.sender() as Box<Any + Send>
+ }
+
+ fn create(_phantom: Option<&mut ScriptThread>,
+ state: InitialScriptState,
+ layout_chan: &OpaqueScriptLayoutChannel,
+ load_data: LoadData) {
+ let ConstellationChan(const_chan) = state.constellation_chan.clone();
+ let (script_chan, script_port) = channel();
+ let layout_chan = LayoutChan(layout_chan.sender());
+ let failure_info = state.failure_info;
+ thread::spawn_named_with_send_on_failure(format!("ScriptThread {:?}", state.id),
+ thread_state::SCRIPT,
+ move || {
+ PipelineNamespace::install(state.pipeline_namespace_id);
+ let roots = RootCollection::new();
+ let _stack_roots_tls = StackRootTLS::new(&roots);
+ let chan = MainThreadScriptChan(script_chan.clone());
+ let channel_for_reporter = chan.clone();
+ let id = state.id;
+ let parent_info = state.parent_info;
+ let mem_profiler_chan = state.mem_profiler_chan.clone();
+ let window_size = state.window_size;
+ let script_thread = ScriptThread::new(state,
+ script_port,
+ script_chan);
+
+ SCRIPT_TASK_ROOT.with(|root| {
+ *root.borrow_mut() = Some(&script_thread as *const _);
+ });
+
+ let mut failsafe = ScriptMemoryFailsafe::new(&script_thread);
+
+ let new_load = InProgressLoad::new(id, parent_info, layout_chan, window_size,
+ load_data.url.clone());
+ script_thread.start_page_load(new_load, load_data);
+
+ let reporter_name = format!("script-reporter-{}", id);
+ mem_profiler_chan.run_with_memory_reporting(|| {
+ script_thread.start();
+ let _ = script_thread.content_process_shutdown_chan.send(());
+ }, reporter_name, channel_for_reporter, CommonScriptMsg::CollectReports);
+
+ // This must always be the very last operation performed before the thread completes
+ failsafe.neuter();
+ }, ConstellationMsg::Failure(failure_info), const_chan);
+ }
+}
+
+thread_local!(static GC_CYCLE_START: Cell<Option<Tm>> = Cell::new(None));
+thread_local!(static GC_SLICE_START: Cell<Option<Tm>> = Cell::new(None));
+
+unsafe extern "C" fn gc_slice_callback(_rt: *mut JSRuntime, progress: GCProgress, desc: *const GCDescription) {
+ match progress {
+ GCProgress::GC_CYCLE_BEGIN => {
+ GC_CYCLE_START.with(|start| {
+ start.set(Some(now()));
+ println!("GC cycle began");
+ })
+ },
+ GCProgress::GC_SLICE_BEGIN => {
+ GC_SLICE_START.with(|start| {
+ start.set(Some(now()));
+ println!("GC slice began");
+ })
+ },
+ GCProgress::GC_SLICE_END => {
+ GC_SLICE_START.with(|start| {
+ let dur = now() - start.get().unwrap();
+ start.set(None);
+ println!("GC slice ended: duration={}", dur);
+ })
+ },
+ GCProgress::GC_CYCLE_END => {
+ GC_CYCLE_START.with(|start| {
+ let dur = now() - start.get().unwrap();
+ start.set(None);
+ println!("GC cycle ended: duration={}", dur);
+ })
+ },
+ };
+ if !desc.is_null() {
+ let desc: &GCDescription = &*desc;
+ let invocationKind = match desc.invocationKind_ {
+ JSGCInvocationKind::GC_NORMAL => "GC_NORMAL",
+ JSGCInvocationKind::GC_SHRINK => "GC_SHRINK",
+ };
+ println!(" isCompartment={}, invocationKind={}", desc.isCompartment_, invocationKind);
+ }
+ let _ = stdout().flush();
+}
+
+unsafe extern "C" fn debug_gc_callback(_rt: *mut JSRuntime, status: JSGCStatus, _data: *mut libc::c_void) {
+ match status {
+ JSGCStatus::JSGC_BEGIN => thread_state::enter(thread_state::IN_GC),
+ JSGCStatus::JSGC_END => thread_state::exit(thread_state::IN_GC),
+ }
+}
+
+pub unsafe extern "C" fn shadow_check_callback(_cx: *mut JSContext,
+ _object: HandleObject, _id: HandleId) -> DOMProxyShadowsResult {
+ // XXX implement me
+ DOMProxyShadowsResult::ShadowCheckFailed
+}
+
+#[derive(JSTraceable, HeapSizeOf)]
+pub struct CSSError {
+ filename: String,
+ line: usize,
+ column: usize,
+ msg: String
+}
+
+impl ScriptThread {
+ pub fn page_fetch_complete(id: PipelineId, subpage: Option<SubpageId>, metadata: Metadata)
+ -> Option<ParserRoot> {
+ SCRIPT_TASK_ROOT.with(|root| {
+ let script_thread = unsafe { &*root.borrow().unwrap() };
+ script_thread.handle_page_fetch_complete(id, subpage, metadata)
+ })
+ }
+
+ pub fn parsing_complete(id: PipelineId) {
+ SCRIPT_TASK_ROOT.with(|root| {
+ let script_thread = unsafe { &*root.borrow().unwrap() };
+ script_thread.handle_parsing_complete(id);
+ });
+ }
+
+ pub fn process_event(msg: CommonScriptMsg) {
+ SCRIPT_TASK_ROOT.with(|root| {
+ if let Some(script_thread) = *root.borrow() {
+ let script_thread = unsafe { &*script_thread };
+ script_thread.handle_msg_from_script(MainThreadScriptMsg::Common(msg));
+ }
+ });
+ }
+
+ /// Creates a new script thread.
+ pub fn new(state: InitialScriptState,
+ port: Receiver<MainThreadScriptMsg>,
+ chan: Sender<MainThreadScriptMsg>)
+ -> ScriptThread {
+ let runtime = ScriptThread::new_rt_and_cx();
+
+ unsafe {
+ JS_SetWrapObjectCallbacks(runtime.rt(),
+ &WRAP_CALLBACKS);
+ }
+
+ // Ask the router to proxy IPC messages from the devtools to us.
+ let (ipc_devtools_sender, ipc_devtools_receiver) = ipc::channel().unwrap();
+ let devtools_port = ROUTER.route_ipc_receiver_to_new_mpsc_receiver(ipc_devtools_receiver);
+
+ // Ask the router to proxy IPC messages from the image cache thread to us.
+ let (ipc_image_cache_channel, ipc_image_cache_port) = ipc::channel().unwrap();
+ let image_cache_port =
+ ROUTER.route_ipc_receiver_to_new_mpsc_receiver(ipc_image_cache_port);
+
+ let (timer_event_chan, timer_event_port) = channel();
+
+ // Ask the router to proxy IPC messages from the control port to us.
+ let control_port = ROUTER.route_ipc_receiver_to_new_mpsc_receiver(state.control_port);
+
+ ScriptThread {
+ page: DOMRefCell::new(None),
+ incomplete_loads: DOMRefCell::new(vec!()),
+
+ image_cache_thread: state.image_cache_thread,
+ image_cache_channel: ImageCacheChan(ipc_image_cache_channel),
+ image_cache_port: image_cache_port,
+
+ resource_thread: Arc::new(state.resource_thread),
+ storage_thread: state.storage_thread,
+
+ port: port,
+ chan: MainThreadScriptChan(chan.clone()),
+ dom_manipulation_thread_source: DOMManipulationThreadSource(chan.clone()),
+ user_interaction_thread_source: UserInteractionThreadSource(chan.clone()),
+ networking_thread_source: NetworkingThreadSource(chan.clone()),
+ history_traversal_thread_source: HistoryTraversalThreadSource(chan.clone()),
+ file_reading_thread_source: FileReadingThreadSource(chan),
+
+ control_chan: state.control_chan,
+ control_port: control_port,
+ constellation_chan: state.constellation_chan,
+ layout_to_constellation_chan: state.layout_to_constellation_chan,
+ compositor: DOMRefCell::new(state.compositor),
+ time_profiler_chan: state.time_profiler_chan,
+ mem_profiler_chan: state.mem_profiler_chan,
+
+ devtools_chan: state.devtools_chan,
+ devtools_port: devtools_port,
+ devtools_sender: ipc_devtools_sender,
+
+ js_runtime: Rc::new(runtime),
+ mouse_over_targets: DOMRefCell::new(vec!()),
+ closed_pipelines: DOMRefCell::new(HashSet::new()),
+
+ scheduler_chan: state.scheduler_chan,
+ timer_event_chan: timer_event_chan,
+ timer_event_port: timer_event_port,
+
+ content_process_shutdown_chan: state.content_process_shutdown_chan,
+ }
+ }
+
+ pub fn new_rt_and_cx() -> Runtime {
+ LiveDOMReferences::initialize();
+ let runtime = Runtime::new();
+
+ unsafe {
+ JS_AddExtraGCRootsTracer(runtime.rt(), Some(trace_rust_roots), ptr::null_mut());
+ JS_AddExtraGCRootsTracer(runtime.rt(), Some(trace_refcounted_objects), ptr::null_mut());
+ }
+
+ // Needed for debug assertions about whether GC is running.
+ if cfg!(debug_assertions) {
+ unsafe {
+ JS_SetGCCallback(runtime.rt(), Some(debug_gc_callback), ptr::null_mut());
+ }
+ }
+ if opts::get().gc_profile {
+ unsafe {
+ SetGCSliceCallback(runtime.rt(), Some(gc_slice_callback));
+ }
+ }
+
+ unsafe {
+ unsafe extern "C" fn empty_wrapper_callback(_: *mut JSContext, _: *mut JSObject) -> bool { true }
+ SetDOMCallbacks(runtime.rt(), &DOM_CALLBACKS);
+ SetPreserveWrapperCallback(runtime.rt(), Some(empty_wrapper_callback));
+ // Pre barriers aren't working correctly at the moment
+ DisableIncrementalGC(runtime.rt());
+ }
+
+ runtime
+ }
+
+ // Return the root page in the frame tree. Panics if it doesn't exist.
+ pub fn root_page(&self) -> Rc<Page> {
+ self.page.borrow().as_ref().unwrap().clone()
+ }
+
+ /// Find a child page of the root page by pipeline id. Returns `None` if the root page does
+ /// not exist or the subpage cannot be found.
+ fn find_subpage(&self, pipeline_id: PipelineId) -> Option<Rc<Page>> {
+ self.page.borrow().as_ref().and_then(|page| page.find(pipeline_id))
+ }
+
+ pub fn get_cx(&self) -> *mut JSContext {
+ self.js_runtime.cx()
+ }
+
+ /// Starts the script thread. After calling this method, the script thread will loop receiving
+ /// messages on its port.
+ pub fn start(&self) {
+ while self.handle_msgs() {
+ // Go on...
+ }
+ }
+
+ /// Handle incoming control messages.
+ fn handle_msgs(&self) -> bool {
+ use self::MixedMessage::{FromScript, FromConstellation, FromScheduler, FromDevtools, FromImageCache};
+
+ // Handle pending resize events.
+ // Gather them first to avoid a double mut borrow on self.
+ let mut resizes = vec!();
+
+ {
+ let page = self.page.borrow();
+ if let Some(page) = page.as_ref() {
+ for page in page.iter() {
+ // Only process a resize if layout is idle.
+ let window = page.window();
+ let resize_event = window.steal_resize_event();
+ match resize_event {
+ Some(size) => resizes.push((window.pipeline(), size)),
+ None => ()
+ }
+ }
+ }
+ }
+
+ for (id, size) in resizes {
+ self.handle_event(id, ResizeEvent(size));
+ }
+
+ // Store new resizes, and gather all other events.
+ let mut sequential = vec!();
+
+ // Receive at least one message so we don't spinloop.
+ let mut event = {
+ let sel = Select::new();
+ let mut script_port = sel.handle(&self.port);
+ let mut control_port = sel.handle(&self.control_port);
+ let mut timer_event_port = sel.handle(&self.timer_event_port);
+ let mut devtools_port = sel.handle(&self.devtools_port);
+ let mut image_cache_port = sel.handle(&self.image_cache_port);
+ unsafe {
+ script_port.add();
+ control_port.add();
+ timer_event_port.add();
+ if self.devtools_chan.is_some() {
+ devtools_port.add();
+ }
+ image_cache_port.add();
+ }
+ let ret = sel.wait();
+ if ret == script_port.id() {
+ FromScript(self.port.recv().unwrap())
+ } else if ret == control_port.id() {
+ FromConstellation(self.control_port.recv().unwrap())
+ } else if ret == timer_event_port.id() {
+ FromScheduler(self.timer_event_port.recv().unwrap())
+ } else if ret == devtools_port.id() {
+ FromDevtools(self.devtools_port.recv().unwrap())
+ } else if ret == image_cache_port.id() {
+ FromImageCache(self.image_cache_port.recv().unwrap())
+ } else {
+ panic!("unexpected select result")
+ }
+ };
+
+ // Squash any pending resize, reflow, animation tick, and mouse-move events in the queue.
+ let mut mouse_move_event_index = None;
+ let mut animation_ticks = HashSet::new();
+ loop {
+ match event {
+ // This has to be handled before the ResizeMsg below,
+ // otherwise the page may not have been added to the
+ // child list yet, causing the find() to fail.
+ FromConstellation(ConstellationControlMsg::AttachLayout(
+ new_layout_info)) => {
+ self.profile_event(ScriptThreadEventCategory::AttachLayout, || {
+ self.handle_new_layout(new_layout_info);
+ })
+ }
+ FromConstellation(ConstellationControlMsg::Resize(id, size)) => {
+ self.profile_event(ScriptThreadEventCategory::Resize, || {
+ self.handle_resize(id, size);
+ })
+ }
+ FromConstellation(ConstellationControlMsg::Viewport(id, rect)) => {
+ self.profile_event(ScriptThreadEventCategory::SetViewport, || {
+ self.handle_viewport(id, rect);
+ })
+ }
+ FromConstellation(ConstellationControlMsg::TickAllAnimations(
+ pipeline_id)) => {
+ if !animation_ticks.contains(&pipeline_id) {
+ animation_ticks.insert(pipeline_id);
+ sequential.push(event);
+ }
+ }
+ FromConstellation(ConstellationControlMsg::SendEvent(
+ _,
+ MouseMoveEvent(_))) => {
+ match mouse_move_event_index {
+ None => {
+ mouse_move_event_index = Some(sequential.len());
+ sequential.push(event);
+ }
+ Some(index) => {
+ sequential[index] = event
+ }
+ }
+ }
+ _ => {
+ sequential.push(event);
+ }
+ }
+
+ // If any of our input sources has an event pending, we'll perform another iteration
+ // and check for more resize events. If there are no events pending, we'll move
+ // on and execute the sequential non-resize events we've seen.
+ match self.control_port.try_recv() {
+ Err(_) => match self.port.try_recv() {
+ Err(_) => match self.timer_event_port.try_recv() {
+ Err(_) => match self.devtools_port.try_recv() {
+ Err(_) => match self.image_cache_port.try_recv() {
+ Err(_) => break,
+ Ok(ev) => event = FromImageCache(ev),
+ },
+ Ok(ev) => event = FromDevtools(ev),
+ },
+ Ok(ev) => event = FromScheduler(ev),
+ },
+ Ok(ev) => event = FromScript(ev),
+ },
+ Ok(ev) => event = FromConstellation(ev),
+ }
+ }
+
+ // Process the gathered events.
+ for msg in sequential {
+ let category = self.categorize_msg(&msg);
+
+ let result = self.profile_event(category, move || {
+ match msg {
+ FromConstellation(ConstellationControlMsg::ExitPipeline(id)) => {
+ if self.handle_exit_pipeline_msg(id) {
+ return Some(false)
+ }
+ },
+ FromConstellation(inner_msg) => self.handle_msg_from_constellation(inner_msg),
+ FromScript(inner_msg) => self.handle_msg_from_script(inner_msg),
+ FromScheduler(inner_msg) => self.handle_timer_event(inner_msg),
+ FromDevtools(inner_msg) => self.handle_msg_from_devtools(inner_msg),
+ FromImageCache(inner_msg) => self.handle_msg_from_image_cache(inner_msg),
+ }
+
+ None
+ });
+
+ if let Some(retval) = result {
+ return retval
+ }
+ }
+
+ // Issue batched reflows on any pages that require it (e.g. if images loaded)
+ // TODO(gw): In the future we could probably batch other types of reflows
+ // into this loop too, but for now it's only images.
+ let page = self.page.borrow();
+ if let Some(page) = page.as_ref() {
+ for page in page.iter() {
+ let window = page.window();
+ let pending_reflows = window.get_pending_reflow_count();
+ if pending_reflows > 0 {
+ window.reflow(ReflowGoal::ForDisplay,
+ ReflowQueryType::NoQuery,
+ ReflowReason::ImageLoaded);
+ }
+ }
+ }
+
+ true
+ }
+
+ fn categorize_msg(&self, msg: &MixedMessage) -> ScriptThreadEventCategory {
+ match *msg {
+ MixedMessage::FromConstellation(ref inner_msg) => {
+ match *inner_msg {
+ ConstellationControlMsg::SendEvent(_, _) =>
+ ScriptThreadEventCategory::DomEvent,
+ _ => ScriptThreadEventCategory::ConstellationMsg
+ }
+ },
+ MixedMessage::FromDevtools(_) => ScriptThreadEventCategory::DevtoolsMsg,
+ MixedMessage::FromImageCache(_) => ScriptThreadEventCategory::ImageCacheMsg,
+ MixedMessage::FromScript(ref inner_msg) => {
+ match *inner_msg {
+ MainThreadScriptMsg::Common(CommonScriptMsg::RunnableMsg(ref category, _)) =>
+ *category,
+ _ => ScriptThreadEventCategory::ScriptEvent
+ }
+ },
+ MixedMessage::FromScheduler(_) => ScriptThreadEventCategory::TimerEvent,
+ }
+ }
+
+ fn profile_event<F, R>(&self, category: ScriptThreadEventCategory, f: F) -> R
+ where F: FnOnce() -> R {
+
+ if opts::get().profile_script_events {
+ let profiler_cat = match category {
+ ScriptThreadEventCategory::AttachLayout => ProfilerCategory::ScriptAttachLayout,
+ ScriptThreadEventCategory::ConstellationMsg => ProfilerCategory::ScriptConstellationMsg,
+ ScriptThreadEventCategory::DevtoolsMsg => ProfilerCategory::ScriptDevtoolsMsg,
+ ScriptThreadEventCategory::DocumentEvent => ProfilerCategory::ScriptDocumentEvent,
+ ScriptThreadEventCategory::DomEvent => ProfilerCategory::ScriptDomEvent,
+ ScriptThreadEventCategory::FileRead => ProfilerCategory::ScriptFileRead,
+ ScriptThreadEventCategory::ImageCacheMsg => ProfilerCategory::ScriptImageCacheMsg,
+ ScriptThreadEventCategory::InputEvent => ProfilerCategory::ScriptInputEvent,
+ ScriptThreadEventCategory::NetworkEvent => ProfilerCategory::ScriptNetworkEvent,
+ ScriptThreadEventCategory::Resize => ProfilerCategory::ScriptResize,
+ ScriptThreadEventCategory::ScriptEvent => ProfilerCategory::ScriptEvent,
+ ScriptThreadEventCategory::UpdateReplacedElement => {
+ ProfilerCategory::ScriptUpdateReplacedElement
+ }
+ ScriptThreadEventCategory::StylesheetLoad => ProfilerCategory::ScriptStylesheetLoad,
+ ScriptThreadEventCategory::SetViewport => ProfilerCategory::ScriptSetViewport,
+ ScriptThreadEventCategory::TimerEvent => ProfilerCategory::ScriptTimerEvent,
+ ScriptThreadEventCategory::WebSocketEvent => ProfilerCategory::ScriptWebSocketEvent,
+ ScriptThreadEventCategory::WorkerEvent => ProfilerCategory::ScriptWorkerEvent,
+ };
+ profile(profiler_cat, None, self.time_profiler_chan.clone(), f)
+ } else {
+ f()
+ }
+ }
+
+ fn handle_msg_from_constellation(&self, msg: ConstellationControlMsg) {
+ match msg {
+ ConstellationControlMsg::AttachLayout(_) =>
+ panic!("should have handled AttachLayout already"),
+ ConstellationControlMsg::Navigate(pipeline_id, subpage_id, load_data) =>
+ self.handle_navigate(pipeline_id, Some(subpage_id), load_data),
+ ConstellationControlMsg::SendEvent(id, event) =>
+ self.handle_event(id, event),
+ ConstellationControlMsg::ResizeInactive(id, new_size) =>
+ self.handle_resize_inactive_msg(id, new_size),
+ ConstellationControlMsg::Viewport(..) =>
+ panic!("should have handled Viewport already"),
+ ConstellationControlMsg::Resize(..) =>
+ panic!("should have handled Resize already"),
+ ConstellationControlMsg::ExitPipeline(..) =>
+ panic!("should have handled ExitPipeline already"),
+ ConstellationControlMsg::GetTitle(pipeline_id) =>
+ self.handle_get_title_msg(pipeline_id),
+ ConstellationControlMsg::Freeze(pipeline_id) =>
+ self.handle_freeze_msg(pipeline_id),
+ ConstellationControlMsg::Thaw(pipeline_id) =>
+ self.handle_thaw_msg(pipeline_id),
+ ConstellationControlMsg::MozBrowserEvent(parent_pipeline_id,
+ subpage_id,
+ event) =>
+ self.handle_mozbrowser_event_msg(parent_pipeline_id,
+ subpage_id,
+ event),
+ ConstellationControlMsg::UpdateSubpageId(containing_pipeline_id,
+ old_subpage_id,
+ new_subpage_id) =>
+ self.handle_update_subpage_id(containing_pipeline_id, old_subpage_id, new_subpage_id),
+ ConstellationControlMsg::FocusIFrame(containing_pipeline_id, subpage_id) =>
+ self.handle_focus_iframe_msg(containing_pipeline_id, subpage_id),
+ ConstellationControlMsg::WebDriverScriptCommand(pipeline_id, msg) =>
+ self.handle_webdriver_msg(pipeline_id, msg),
+ ConstellationControlMsg::TickAllAnimations(pipeline_id) =>
+ self.handle_tick_all_animations(pipeline_id),
+ ConstellationControlMsg::WebFontLoaded(pipeline_id) =>
+ self.handle_web_font_loaded(pipeline_id),
+ ConstellationControlMsg::DispatchFrameLoadEvent {
+ target: pipeline_id, parent: containing_id } =>
+ self.handle_frame_load_event(containing_id, pipeline_id),
+ ConstellationControlMsg::ReportCSSError(pipeline_id, filename, line, column, msg) =>
+ self.handle_css_error_reporting(pipeline_id, filename, line, column, msg),
+ }
+ }
+
+ fn handle_msg_from_script(&self, msg: MainThreadScriptMsg) {
+ match msg {
+ MainThreadScriptMsg::Navigate(id, load_data) =>
+ self.handle_navigate(id, None, load_data),
+ MainThreadScriptMsg::ExitWindow(id) =>
+ self.handle_exit_window_msg(id),
+ MainThreadScriptMsg::MainThreadRunnableMsg(runnable) =>
+ runnable.handler(self),
+ MainThreadScriptMsg::DocumentLoadsComplete(id) =>
+ self.handle_loads_complete(id),
+ MainThreadScriptMsg::Common(CommonScriptMsg::RunnableMsg(_, runnable)) => {
+ // The category of the runnable is ignored by the pattern, however
+ // it is still respected by profiling (see categorize_msg).
+ if !runnable.is_cancelled() {
+ runnable.handler()
+ }
+ }
+ MainThreadScriptMsg::Common(CommonScriptMsg::RefcountCleanup(addr)) =>
+ LiveDOMReferences::cleanup(addr),
+ MainThreadScriptMsg::Common(CommonScriptMsg::CollectReports(reports_chan)) =>
+ self.collect_reports(reports_chan),
+ }
+ }
+
+ fn handle_timer_event(&self, timer_event: TimerEvent) {
+ let TimerEvent(source, id) = timer_event;
+
+ let pipeline_id = match source {
+ TimerSource::FromWindow(pipeline_id) => pipeline_id,
+ TimerSource::FromWorker => panic!("Worker timeouts must not be sent to script thread"),
+ };
+
+ let page = self.root_page();
+ let page = page.find(pipeline_id).expect("ScriptThread: received fire timer msg for a
+ pipeline ID not associated with this script thread. This is a bug.");
+ let window = page.window();
+
+ window.handle_fire_timer(id);
+ }
+
+ fn handle_msg_from_devtools(&self, msg: DevtoolScriptControlMsg) {
+ let page = self.root_page();
+ match msg {
+ DevtoolScriptControlMsg::EvaluateJS(id, s, reply) => {
+ let window = get_page(&page, id).window();
+ let global_ref = GlobalRef::Window(window.r());
+ devtools::handle_evaluate_js(&global_ref, s, reply)
+ },
+ DevtoolScriptControlMsg::GetRootNode(id, reply) =>
+ devtools::handle_get_root_node(&page, id, reply),
+ DevtoolScriptControlMsg::GetDocumentElement(id, reply) =>
+ devtools::handle_get_document_element(&page, id, reply),
+ DevtoolScriptControlMsg::GetChildren(id, node_id, reply) =>
+ devtools::handle_get_children(&page, id, node_id, reply),
+ DevtoolScriptControlMsg::GetLayout(id, node_id, reply) =>
+ devtools::handle_get_layout(&page, id, node_id, reply),
+ DevtoolScriptControlMsg::GetCachedMessages(pipeline_id, message_types, reply) =>
+ devtools::handle_get_cached_messages(pipeline_id, message_types, reply),
+ DevtoolScriptControlMsg::ModifyAttribute(id, node_id, modifications) =>
+ devtools::handle_modify_attribute(&page, id, node_id, modifications),
+ DevtoolScriptControlMsg::WantsLiveNotifications(id, to_send) => {
+ let window = get_page(&page, id).window();
+ let global_ref = GlobalRef::Window(window.r());
+ devtools::handle_wants_live_notifications(&global_ref, to_send)
+ },
+ DevtoolScriptControlMsg::SetTimelineMarkers(_pipeline_id, marker_types, reply) =>
+ devtools::handle_set_timeline_markers(&page, marker_types, reply),
+ DevtoolScriptControlMsg::DropTimelineMarkers(_pipeline_id, marker_types) =>
+ devtools::handle_drop_timeline_markers(&page, marker_types),
+ DevtoolScriptControlMsg::RequestAnimationFrame(pipeline_id, name) =>
+ devtools::handle_request_animation_frame(&page, pipeline_id, name),
+ }
+ }
+
+ fn handle_msg_from_image_cache(&self, msg: ImageCacheResult) {
+ msg.responder.unwrap().respond(msg.image_response);
+ }
+
+ fn handle_webdriver_msg(&self, pipeline_id: PipelineId, msg: WebDriverScriptCommand) {
+ let page = self.root_page();
+ match msg {
+ WebDriverScriptCommand::ExecuteScript(script, reply) =>
+ webdriver_handlers::handle_execute_script(&page, pipeline_id, script, reply),
+ WebDriverScriptCommand::FindElementCSS(selector, reply) =>
+ webdriver_handlers::handle_find_element_css(&page, pipeline_id, selector, reply),
+ WebDriverScriptCommand::FindElementsCSS(selector, reply) =>
+ webdriver_handlers::handle_find_elements_css(&page, pipeline_id, selector, reply),
+ WebDriverScriptCommand::FocusElement(element_id, reply) =>
+ webdriver_handlers::handle_focus_element(&page, pipeline_id, element_id, reply),
+ WebDriverScriptCommand::GetActiveElement(reply) =>
+ webdriver_handlers::handle_get_active_element(&page, pipeline_id, reply),
+ WebDriverScriptCommand::GetElementTagName(node_id, reply) =>
+ webdriver_handlers::handle_get_name(&page, pipeline_id, node_id, reply),
+ WebDriverScriptCommand::GetElementAttribute(node_id, name, reply) =>
+ webdriver_handlers::handle_get_attribute(&page, pipeline_id, node_id, name, reply),
+ WebDriverScriptCommand::GetElementCSS(node_id, name, reply) =>
+ webdriver_handlers::handle_get_css(&page, pipeline_id, node_id, name, reply),
+ WebDriverScriptCommand::GetElementText(node_id, reply) =>
+ webdriver_handlers::handle_get_text(&page, pipeline_id, node_id, reply),
+ WebDriverScriptCommand::GetFrameId(frame_id, reply) =>
+ webdriver_handlers::handle_get_frame_id(&page, pipeline_id, frame_id, reply),
+ WebDriverScriptCommand::GetUrl(reply) =>
+ webdriver_handlers::handle_get_url(&page, pipeline_id, reply),
+ WebDriverScriptCommand::GetWindowSize(reply) =>
+ webdriver_handlers::handle_get_window_size(&page, pipeline_id, reply),
+ WebDriverScriptCommand::IsEnabled(element_id, reply) =>
+ webdriver_handlers::handle_is_enabled(&page, pipeline_id, element_id, reply),
+ WebDriverScriptCommand::IsSelected(element_id, reply) =>
+ webdriver_handlers::handle_is_selected(&page, pipeline_id, element_id, reply),
+ WebDriverScriptCommand::GetTitle(reply) =>
+ webdriver_handlers::handle_get_title(&page, pipeline_id, reply),
+ WebDriverScriptCommand::ExecuteAsyncScript(script, reply) =>
+ webdriver_handlers::handle_execute_async_script(&page, pipeline_id, script, reply),
+ }
+ }
+
+ fn handle_resize(&self, id: PipelineId, size: WindowSizeData) {
+ if let Some(ref page) = self.find_subpage(id) {
+ let window = page.window();
+ window.set_resize_event(size);
+ return;
+ }
+ let mut loads = self.incomplete_loads.borrow_mut();
+ if let Some(ref mut load) = loads.iter_mut().find(|load| load.pipeline_id == id) {
+ load.window_size = Some(size);
+ return;
+ }
+ panic!("resize sent to nonexistent pipeline");
+ }
+
+ fn handle_viewport(&self, id: PipelineId, rect: Rect<f32>) {
+ let page = self.page.borrow();
+ if let Some(page) = page.as_ref() {
+ if let Some(ref inner_page) = page.find(id) {
+ let window = inner_page.window();
+ if window.set_page_clip_rect_with_new_viewport(rect) {
+ let page = get_page(page, id);
+ self.rebuild_and_force_reflow(&*page, ReflowReason::Viewport);
+ }
+ return;
+ }
+ }
+ let mut loads = self.incomplete_loads.borrow_mut();
+ if let Some(ref mut load) = loads.iter_mut().find(|load| load.pipeline_id == id) {
+ load.clip_rect = Some(rect);
+ return;
+ }
+ panic!("Page rect message sent to nonexistent pipeline");
+ }
+
+ fn handle_new_layout(&self, new_layout_info: NewLayoutInfo) {
+ let NewLayoutInfo {
+ containing_pipeline_id,
+ new_pipeline_id,
+ subpage_id,
+ load_data,
+ paint_chan,
+ failure,
+ pipeline_port,
+ layout_shutdown_chan,
+ content_process_shutdown_chan,
+ } = new_layout_info;
+
+ let layout_pair = ScriptThread::create_layout_channel(None::<&mut ScriptThread>);
+ let layout_chan = LayoutChan(*ScriptThread::clone_layout_channel(
+ None::<&mut ScriptThread>,
+ &layout_pair).downcast::<Sender<layout_interface::Msg>>().unwrap());
+
+ let layout_creation_info = NewLayoutThreadInfo {
+ id: new_pipeline_id,
+ url: load_data.url.clone(),
+ is_parent: false,
+ layout_pair: layout_pair,
+ pipeline_port: pipeline_port,
+ constellation_chan: self.layout_to_constellation_chan.clone(),
+ failure: failure,
+ paint_chan: paint_chan,
+ script_chan: self.control_chan.clone(),
+ image_cache_thread: self.image_cache_thread.clone(),
+ layout_shutdown_chan: layout_shutdown_chan,
+ content_process_shutdown_chan: content_process_shutdown_chan,
+ };
+
+ let page = self.root_page();
+ let parent_page = page.find(containing_pipeline_id).expect("ScriptThread: received a layout
+ whose parent has a PipelineId which does not correspond to a pipeline in the script
+ thread's page tree. This is a bug.");
+ let parent_window = parent_page.window();
+
+ // Tell layout to actually spawn the thread.
+ parent_window.layout_chan()
+ .0
+ .send(layout_interface::Msg::CreateLayoutThread(layout_creation_info))
+ .unwrap();
+
+ // Kick off the fetch for the new resource.
+ let new_load = InProgressLoad::new(new_pipeline_id, Some((containing_pipeline_id, subpage_id)),
+ layout_chan, parent_window.window_size(),
+ load_data.url.clone());
+ self.start_page_load(new_load, load_data);
+ }
+
+ fn handle_loads_complete(&self, pipeline: PipelineId) {
+ let page = get_page(&self.root_page(), pipeline);
+ let doc = page.document();
+ let doc = doc.r();
+ if doc.loader().is_blocked() {
+ return;
+ }
+
+ doc.mut_loader().inhibit_events();
+
+ // https://html.spec.whatwg.org/multipage/#the-end step 7
+ let addr: Trusted<Document> = Trusted::new(doc, self.chan.clone());
+ let handler = box DocumentProgressHandler::new(addr.clone());
+ self.chan.send(CommonScriptMsg::RunnableMsg(ScriptThreadEventCategory::DocumentEvent, handler)).unwrap();
+
+ let ConstellationChan(ref chan) = self.constellation_chan;
+ chan.send(ConstellationMsg::LoadComplete(pipeline)).unwrap();
+ }
+
+ pub fn get_reports(cx: *mut JSContext, path_seg: String) -> Vec<Report> {
+ let mut reports = vec![];
+
+ unsafe {
+ let rt = JS_GetRuntime(cx);
+ let mut stats = ::std::mem::zeroed();
+ if CollectServoSizes(rt, &mut stats) {
+ let mut report = |mut path_suffix, kind, size| {
+ let mut path = path![path_seg, "js"];
+ path.append(&mut path_suffix);
+ reports.push(Report {
+ path: path,
+ kind: kind,
+ size: size as usize,
+ })
+ };
+
+ // A note about possibly confusing terminology: the JS GC "heap" is allocated via
+ // mmap/VirtualAlloc, which means it's not on the malloc "heap", so we use
+ // `ExplicitNonHeapSize` as its kind.
+
+ report(path!["gc-heap", "used"],
+ ReportKind::ExplicitNonHeapSize,
+ stats.gcHeapUsed);
+
+ report(path!["gc-heap", "unused"],
+ ReportKind::ExplicitNonHeapSize,
+ stats.gcHeapUnused);
+
+ report(path!["gc-heap", "admin"],
+ ReportKind::ExplicitNonHeapSize,
+ stats.gcHeapAdmin);
+
+ report(path!["gc-heap", "decommitted"],
+ ReportKind::ExplicitNonHeapSize,
+ stats.gcHeapDecommitted);
+
+ // SpiderMonkey uses the system heap, not jemalloc.
+ report(path!["malloc-heap"],
+ ReportKind::ExplicitSystemHeapSize,
+ stats.mallocHeap);
+
+ report(path!["non-heap"],
+ ReportKind::ExplicitNonHeapSize,
+ stats.nonHeap);
+ }
+ }
+ reports
+ }
+
+ fn collect_reports(&self, reports_chan: ReportsChan) {
+ let mut urls = vec![];
+ let mut dom_tree_size = 0;
+ let mut reports = vec![];
+
+ if let Some(root_page) = self.page.borrow().as_ref() {
+ for it_page in root_page.iter() {
+ let current_url = it_page.document().url().serialize();
+ urls.push(current_url.clone());
+
+ for child in it_page.document().upcast::<Node>().traverse_preorder() {
+ dom_tree_size += heap_size_of_self_and_children(&*child);
+ }
+ let window = it_page.window();
+ dom_tree_size += heap_size_of_self_and_children(&*window);
+
+ reports.push(Report {
+ path: path![format!("url({})", current_url), "dom-tree"],
+ kind: ReportKind::ExplicitJemallocHeapSize,
+ size: dom_tree_size,
+ })
+ }
+ }
+ let path_seg = format!("url({})", urls.join(", "));
+ reports.extend(ScriptThread::get_reports(self.get_cx(), path_seg));
+ reports_chan.send(reports);
+ }
+
+ /// Handles freeze message
+ fn handle_freeze_msg(&self, id: PipelineId) {
+ if let Some(root_page) = self.page.borrow().as_ref() {
+ if let Some(ref inner_page) = root_page.find(id) {
+ let window = inner_page.window();
+ window.freeze();
+ return;
+ }
+ }
+ let mut loads = self.incomplete_loads.borrow_mut();
+ if let Some(ref mut load) = loads.iter_mut().find(|load| load.pipeline_id == id) {
+ load.is_frozen = true;
+ return;
+ }
+ panic!("freeze sent to nonexistent pipeline");
+ }
+
+ /// Handles thaw message
+ fn handle_thaw_msg(&self, id: PipelineId) {
+ if let Some(ref inner_page) = self.root_page().find(id) {
+ let needed_reflow = inner_page.set_reflow_status(false);
+ if needed_reflow {
+ self.rebuild_and_force_reflow(&*inner_page, ReflowReason::CachedPageNeededReflow);
+ }
+ let window = inner_page.window();
+ window.thaw();
+ return;
+ }
+ let mut loads = self.incomplete_loads.borrow_mut();
+ if let Some(ref mut load) = loads.iter_mut().find(|load| load.pipeline_id == id) {
+ load.is_frozen = false;
+ return;
+ }
+ panic!("thaw sent to nonexistent pipeline");
+ }
+
+ fn handle_focus_iframe_msg(&self,
+ parent_pipeline_id: PipelineId,
+ subpage_id: SubpageId) {
+ let borrowed_page = self.root_page();
+ let page = borrowed_page.find(parent_pipeline_id).unwrap();
+
+ let doc = page.document();
+ let frame_element = doc.find_iframe(subpage_id);
+
+ if let Some(ref frame_element) = frame_element {
+ doc.begin_focus_transaction();
+ doc.request_focus(frame_element.upcast());
+ doc.commit_focus_transaction(FocusType::Parent);
+ }
+ }
+
+ /// Handles a mozbrowser event, for example see:
+ /// https://developer.mozilla.org/en-US/docs/Web/Events/mozbrowserloadstart
+ fn handle_mozbrowser_event_msg(&self,
+ parent_pipeline_id: PipelineId,
+ subpage_id: SubpageId,
+ event: MozBrowserEvent) {
+ let borrowed_page = self.root_page();
+
+ let frame_element = borrowed_page.find(parent_pipeline_id).and_then(|page| {
+ let doc = page.document();
+ doc.find_iframe(subpage_id)
+ });
+
+ if let Some(ref frame_element) = frame_element {
+ frame_element.dispatch_mozbrowser_event(event);
+ }
+ }
+
+ fn handle_update_subpage_id(&self,
+ containing_pipeline_id: PipelineId,
+ old_subpage_id: SubpageId,
+ new_subpage_id: SubpageId) {
+ let borrowed_page = self.root_page();
+
+ let frame_element = borrowed_page.find(containing_pipeline_id).and_then(|page| {
+ let doc = page.document();
+ doc.find_iframe(old_subpage_id)
+ });
+
+ frame_element.unwrap().update_subpage_id(new_subpage_id);
+ }
+
+ /// Window was resized, but this script was not active, so don't reflow yet
+ fn handle_resize_inactive_msg(&self, id: PipelineId, new_size: WindowSizeData) {
+ let page = self.root_page();
+ let page = page.find(id).expect("Received resize message for PipelineId not associated
+ with a page in the page tree. This is a bug.");
+ let window = page.window();
+ window.set_window_size(new_size);
+ page.set_reflow_status(true);
+ }
+
+ /// We have gotten a window.close from script, which we pass on to the compositor.
+ /// We do not shut down the script thread now, because the compositor will ask the
+ /// constellation to shut down the pipeline, which will clean everything up
+ /// normally. If we do exit, we will tear down the DOM nodes, possibly at a point
+ /// where layout is still accessing them.
+ fn handle_exit_window_msg(&self, _: PipelineId) {
+ debug!("script thread handling exit window msg");
+
+ // TODO(tkuehn): currently there is only one window,
+ // so this can afford to be naive and just shut down the
+ // compositor. In the future it'll need to be smarter.
+ self.compositor.borrow_mut().send(ScriptToCompositorMsg::Exit).unwrap();
+ }
+
+ /// We have received notification that the response associated with a load has completed.
+ /// Kick off the document and frame tree creation process using the result.
+ fn handle_page_fetch_complete(&self, id: PipelineId, subpage: Option<SubpageId>,
+ metadata: Metadata) -> Option<ParserRoot> {
+ let idx = self.incomplete_loads.borrow().iter().position(|load| {
+ load.pipeline_id == id && load.parent_info.map(|info| info.1) == subpage
+ });
+ // The matching in progress load structure may not exist if
+ // the pipeline exited before the page load completed.
+ match idx {
+ Some(idx) => {
+ let load = self.incomplete_loads.borrow_mut().remove(idx);
+ Some(self.load(metadata, load))
+ }
+ None => {
+ assert!(self.closed_pipelines.borrow().contains(&id));
+ None
+ }
+ }
+ }
+
+ /// Handles a request for the window title.
+ fn handle_get_title_msg(&self, pipeline_id: PipelineId) {
+ let page = get_page(&self.root_page(), pipeline_id);
+ let document = page.document();
+ document.send_title_to_compositor();
+ }
+
+ /// Handles a request to exit the script thread and shut down layout.
+ /// Returns true if the script thread should shut down and false otherwise.
+ fn handle_exit_pipeline_msg(&self, id: PipelineId) -> bool {
+ self.closed_pipelines.borrow_mut().insert(id);
+
+ // Check if the exit message is for an in progress load.
+ let idx = self.incomplete_loads.borrow().iter().position(|load| {
+ load.pipeline_id == id
+ });
+
+ if let Some(idx) = idx {
+ let load = self.incomplete_loads.borrow_mut().remove(idx);
+
+ // Tell the layout thread to begin shutting down, and wait until it
+ // processed this message.
+ let (response_chan, response_port) = channel();
+ let LayoutChan(chan) = load.layout_chan;
+ if chan.send(layout_interface::Msg::PrepareToExit(response_chan)).is_ok() {
+ debug!("shutting down layout for page {:?}", id);
+ response_port.recv().unwrap();
+ chan.send(layout_interface::Msg::ExitNow).ok();
+ }
+
+ let has_pending_loads = self.incomplete_loads.borrow().len() > 0;
+ let has_root_page = self.page.borrow().is_some();
+
+ // Exit if no pending loads and no root page
+ return !has_pending_loads && !has_root_page;
+ }
+
+ // If root is being exited, shut down all pages
+ let page = self.root_page();
+ let window = page.window();
+ if window.pipeline() == id {
+ debug!("shutting down layout for root page {:?}", id);
+ shut_down_layout(&page);
+ return true
+ }
+
+ // otherwise find just the matching page and exit all sub-pages
+ if let Some(ref mut child_page) = page.remove(id) {
+ shut_down_layout(&*child_page);
+ }
+ false
+ }
+
+ /// Handles when layout thread finishes all animation in one tick
+ fn handle_tick_all_animations(&self, id: PipelineId) {
+ let page = get_page(&self.root_page(), id);
+ let document = page.document();
+ document.run_the_animation_frame_callbacks();
+ }
+
+ /// Handles a Web font being loaded. Does nothing if the page no longer exists.
+ fn handle_web_font_loaded(&self, pipeline_id: PipelineId) {
+ if let Some(ref page) = self.find_subpage(pipeline_id) {
+ self.rebuild_and_force_reflow(page, ReflowReason::WebFontLoaded);
+ }
+ }
+
+ /// Notify the containing document of a child frame that has completed loading.
+ fn handle_frame_load_event(&self, containing_pipeline: PipelineId, id: PipelineId) {
+ let page = get_page(&self.root_page(), containing_pipeline);
+ let document = page.document();
+ if let Some(iframe) = document.find_iframe_by_pipeline(id) {
+ iframe.iframe_load_event_steps();
+ }
+ }
+
+ /// The entry point to document loading. Defines bindings, sets up the window and document
+ /// objects, parses HTML and CSS, and kicks off initial layout.
+ fn load(&self, metadata: Metadata, incomplete: InProgressLoad) -> ParserRoot {
+ let final_url = metadata.final_url.clone();
+ {
+ // send the final url to the layout thread.
+ let LayoutChan(ref chan) = incomplete.layout_chan;
+ chan.send(layout_interface::Msg::SetFinalUrl(final_url.clone())).unwrap();
+
+ // update the pipeline url
+ let ConstellationChan(ref chan) = self.constellation_chan;
+ chan.send(ConstellationMsg::SetFinalUrl(incomplete.pipeline_id, final_url.clone())).unwrap();
+ }
+ debug!("ScriptThread: loading {} on page {:?}", incomplete.url.serialize(), incomplete.pipeline_id);
+
+ // We should either be initializing a root page or loading a child page of an
+ // existing one.
+ let root_page_exists = self.page.borrow().is_some();
+
+ let frame_element = incomplete.parent_info.and_then(|(parent_id, subpage_id)| {
+ // The root page may not exist yet, if the parent of this frame
+ // exists in a different script thread.
+ let borrowed_page = self.page.borrow();
+
+ // In the case a parent id exists but the matching page
+ // cannot be found, this means the page exists in a different
+ // script thread (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.as_ref().and_then(|borrowed_page| {
+ borrowed_page.find(parent_id).and_then(|page| {
+ let doc = page.document();
+ doc.find_iframe(subpage_id)
+ })
+ })
+ });
+
+ // Create a new frame tree entry.
+ let page = Rc::new(Page::new(incomplete.pipeline_id));
+ if !root_page_exists {
+ // We have a new root frame tree.
+ *self.page.borrow_mut() = Some(page.clone());
+ } else if let Some((parent, _)) = incomplete.parent_info {
+ // We have a new child frame.
+ let parent_page = self.root_page();
+ // TODO(gw): This find will fail when we are sharing script threads
+ // between cross origin iframes in the same TLD.
+ let parent_page = parent_page.find(parent)
+ .expect("received load for subpage with missing parent");
+ parent_page.children.borrow_mut().push(page.clone());
+ }
+
+ enum PageToRemove {
+ Root,
+ Child(PipelineId),
+ }
+ struct AutoPageRemover<'a> {
+ page: PageToRemove,
+ script_thread: &'a ScriptThread,
+ neutered: bool,
+ }
+ impl<'a> AutoPageRemover<'a> {
+ fn new(script_thread: &'a ScriptThread, page: PageToRemove) -> AutoPageRemover<'a> {
+ AutoPageRemover {
+ page: page,
+ script_thread: script_thread,
+ neutered: false,
+ }
+ }
+
+ fn neuter(&mut self) {
+ self.neutered = true;
+ }
+ }
+ impl<'a> Drop for AutoPageRemover<'a> {
+ fn drop(&mut self) {
+ if !self.neutered {
+ match self.page {
+ PageToRemove::Root => *self.script_thread.page.borrow_mut() = None,
+ PageToRemove::Child(id) => {
+ self.script_thread.root_page().remove(id).unwrap();
+ }
+ }
+ }
+ }
+ }
+
+ let page_to_remove = if !root_page_exists {
+ PageToRemove::Root
+ } else {
+ PageToRemove::Child(incomplete.pipeline_id)
+ };
+ let mut page_remover = AutoPageRemover::new(self, page_to_remove);
+ let MainThreadScriptChan(ref sender) = self.chan;
+ let DOMManipulationThreadSource(ref dom_sender) = self.dom_manipulation_thread_source;
+ let UserInteractionThreadSource(ref user_sender) = self.user_interaction_thread_source;
+ let NetworkingThreadSource(ref network_sender) = self.networking_thread_source;
+ let HistoryTraversalThreadSource(ref history_sender) = self.history_traversal_thread_source;
+ let FileReadingThreadSource(ref file_sender) = self.file_reading_thread_source;
+
+ let (ipc_timer_event_chan, ipc_timer_event_port) = ipc::channel().unwrap();
+ ROUTER.route_ipc_receiver_to_mpsc_sender(ipc_timer_event_port,
+ self.timer_event_chan.clone());
+
+ // Create the window and document objects.
+ let window = Window::new(self.js_runtime.clone(),
+ page.clone(),
+ MainThreadScriptChan(sender.clone()),
+ DOMManipulationThreadSource(dom_sender.clone()),
+ UserInteractionThreadSource(user_sender.clone()),
+ NetworkingThreadSource(network_sender.clone()),
+ HistoryTraversalThreadSource(history_sender.clone()),
+ FileReadingThreadSource(file_sender.clone()),
+ self.image_cache_channel.clone(),
+ self.compositor.borrow_mut().clone(),
+ self.image_cache_thread.clone(),
+ self.resource_thread.clone(),
+ self.storage_thread.clone(),
+ self.mem_profiler_chan.clone(),
+ self.devtools_chan.clone(),
+ self.constellation_chan.clone(),
+ self.scheduler_chan.clone(),
+ ipc_timer_event_chan,
+ incomplete.layout_chan,
+ incomplete.pipeline_id,
+ incomplete.parent_info,
+ incomplete.window_size);
+
+ let last_modified = metadata.headers.as_ref().and_then(|headers| {
+ headers.get().map(|&LastModified(HttpDate(ref tm))| dom_last_modified(tm))
+ });
+
+ let content_type = match metadata.content_type {
+ Some(ContentType(Mime(TopLevel::Text, SubLevel::Xml, _))) => {
+ Some(DOMString::from("text/xml"))
+ }
+
+ Some(ContentType(Mime(TopLevel::Text, SubLevel::Plain, _))) => {
+ Some(DOMString::from("text/plain"))
+ }
+
+ _ => None
+ };
+
+ let loader = DocumentLoader::new_with_thread(self.resource_thread.clone(),
+ Some(page.pipeline()),
+ Some(incomplete.url.clone()));
+
+ let is_html_document = match metadata.content_type {
+ Some(ContentType(Mime(TopLevel::Text, SubLevel::Xml, _))) =>
+ IsHTMLDocument::NonHTMLDocument,
+ _ => IsHTMLDocument::HTMLDocument,
+ };
+
+ let document = Document::new(window.r(),
+ Some(final_url.clone()),
+ is_html_document,
+ content_type,
+ last_modified,
+ DocumentSource::FromParser,
+ loader);
+
+ let frame_element = frame_element.r().map(Castable::upcast);
+ window.init_browsing_context(document.r(), frame_element);
+
+ document.set_ready_state(DocumentReadyState::Loading);
+
+ // Create the root frame
+ page.set_frame(Some(Frame {
+ document: JS::from_rooted(&document),
+ window: JS::from_rooted(&window),
+ }));
+
+ let ConstellationChan(ref chan) = self.constellation_chan;
+ chan.send(ConstellationMsg::ActivateDocument(incomplete.pipeline_id)).unwrap();
+
+ let is_javascript = incomplete.url.scheme == "javascript";
+ let parse_input = if is_javascript {
+ use url::percent_encoding::percent_decode_to;
+
+ // Turn javascript: URL into JS code to eval, according to the steps in
+ // https://html.spec.whatwg.org/multipage/#javascript-protocol
+ let _ar = JSAutoRequest::new(self.get_cx());
+ let mut script_source_bytes = Vec::new();
+ // Start with the scheme data of the parsed URL (5.), while percent-decoding (8.)
+ percent_decode_to(incomplete.url.non_relative_scheme_data().unwrap().as_bytes(),
+ &mut script_source_bytes);
+ // Append question mark and query component, if any (6.), while percent-decoding (8.)
+ if let Some(ref query) = incomplete.url.query {
+ script_source_bytes.push(b'?');
+ percent_decode_to(query.as_bytes(), &mut script_source_bytes);
+ }
+ // Append number sign and fragment component if any (7.), while percent-decoding (8.)
+ if let Some(ref fragment) = incomplete.url.fragment {
+ script_source_bytes.push(b'#');
+ percent_decode_to(fragment.as_bytes(), &mut script_source_bytes);
+ }
+
+ // UTF-8 decode (9.)
+ let script_source = String::from_utf8_lossy(&script_source_bytes);
+
+ // Script source is ready to be evaluated (11.)
+ unsafe {
+ let mut jsval = RootedValue::new(self.get_cx(), UndefinedValue());
+ window.evaluate_js_on_global_with_result(&script_source, jsval.handle_mut());
+ let strval = DOMString::from_jsval(self.get_cx(),
+ jsval.handle(),
+ StringificationBehavior::Empty);
+ strval.unwrap_or(DOMString::new())
+ }
+ } else {
+ DOMString::new()
+ };
+
+ match metadata.content_type {
+ Some(ContentType(Mime(TopLevel::Text, SubLevel::Xml, _))) => {
+ parse_xml(document.r(),
+ parse_input,
+ final_url,
+ xml::ParseContext::Owner(Some(incomplete.pipeline_id)));
+ }
+ _ => {
+ parse_html(document.r(),
+ parse_input,
+ final_url,
+ ParseContext::Owner(Some(incomplete.pipeline_id)));
+ }
+ }
+
+ if incomplete.is_frozen {
+ window.freeze();
+ }
+
+ page_remover.neuter();
+
+ document.get_current_parser().unwrap()
+ }
+
+ fn notify_devtools(&self, title: DOMString, url: Url, ids: (PipelineId, Option<WorkerId>)) {
+ if let Some(ref chan) = self.devtools_chan {
+ let page_info = DevtoolsPageInfo {
+ title: String::from(title),
+ url: url,
+ };
+ chan.send(ScriptToDevtoolsControlMsg::NewGlobal(
+ ids,
+ self.devtools_sender.clone(),
+ page_info)).unwrap();
+ }
+ }
+
+ fn scroll_fragment_point(&self, pipeline_id: PipelineId, element: &Element) {
+ // FIXME(#8275, pcwalton): This is pretty bogus when multiple layers are involved.
+ // Really what needs to happen is that this needs to go through layout to ask which
+ // layer the element belongs to, and have it send the scroll message to the
+ // compositor.
+ let rect = element.upcast::<Node>().get_bounding_content_box();
+
+ // In order to align with element edges, we snap to unscaled pixel boundaries, since the
+ // paint thread currently does the same for drawing elements. This is important for pages
+ // that require pixel perfect scroll positioning for proper display (like Acid2). Since we
+ // don't have the device pixel ratio here, this might not be accurate, but should work as
+ // long as the ratio is a whole number. Once #8275 is fixed this should actually take into
+ // account the real device pixel ratio.
+ let point = Point2D::new(rect.origin.x.to_nearest_px() as f32,
+ rect.origin.y.to_nearest_px() as f32);
+
+ self.compositor.borrow_mut().send(ScriptToCompositorMsg::ScrollFragmentPoint(
+ pipeline_id, LayerId::null(), point, false)).unwrap();
+ }
+
+ /// Reflows non-incrementally, rebuilding the entire layout tree in the process.
+ fn rebuild_and_force_reflow(&self, page: &Page, reason: ReflowReason) {
+ let document = page.document();
+ document.dirty_all_nodes();
+ let window = window_from_node(document.r());
+ window.reflow(ReflowGoal::ForDisplay, ReflowQueryType::NoQuery, reason);
+ }
+
+ /// This is the main entry point for receiving and dispatching DOM events.
+ ///
+ /// TODO: Actually perform DOM event dispatch.
+ fn handle_event(&self, pipeline_id: PipelineId, event: CompositorEvent) {
+
+ match event {
+ ResizeEvent(new_size) => {
+ self.handle_resize_event(pipeline_id, new_size);
+ }
+
+ MouseButtonEvent(event_type, button, point) => {
+ self.handle_mouse_event(pipeline_id, event_type, button, point);
+ }
+
+ MouseMoveEvent(point) => {
+ let page = get_page(&self.root_page(), pipeline_id);
+ let document = page.document();
+
+ let mut prev_mouse_over_targets: RootedVec<JS<Element>> = RootedVec::new();
+ for target in &*self.mouse_over_targets.borrow_mut() {
+ prev_mouse_over_targets.push(target.clone());
+ }
+
+ // We temporarily steal the list of targets over which the mouse is to pass it to
+ // handle_mouse_move_event() in a safe RootedVec container.
+ let mut mouse_over_targets = RootedVec::new();
+ std_mem::swap(&mut *self.mouse_over_targets.borrow_mut(), &mut *mouse_over_targets);
+ document.handle_mouse_move_event(self.js_runtime.rt(), point, &mut mouse_over_targets);
+
+ // Notify Constellation about anchors that are no longer mouse over targets.
+ for target in &*prev_mouse_over_targets {
+ if !mouse_over_targets.contains(target) && target.is::<HTMLAnchorElement>() {
+ let event = ConstellationMsg::NodeStatus(None);
+ let ConstellationChan(ref chan) = self.constellation_chan;
+ chan.send(event).unwrap();
+ break;
+ }
+ }
+
+ // Notify Constellation about the topmost anchor mouse over target.
+ for target in &*mouse_over_targets {
+ if target.is::<HTMLAnchorElement>() {
+ let status = target.get_attribute(&ns!(), &atom!("href"))
+ .and_then(|href| {
+ let value = href.value();
+ let url = document.url();
+ url.join(&value).map(|url| url.serialize()).ok()
+ });
+ let event = ConstellationMsg::NodeStatus(status);
+ let ConstellationChan(ref chan) = self.constellation_chan;
+ chan.send(event).unwrap();
+ break;
+ }
+ }
+
+ std_mem::swap(&mut *self.mouse_over_targets.borrow_mut(), &mut *mouse_over_targets);
+ }
+
+ TouchEvent(event_type, identifier, point) => {
+ let handled = self.handle_touch_event(pipeline_id, event_type, identifier, point);
+ match event_type {
+ TouchEventType::Down => {
+ if handled {
+ // TODO: Wait to see if preventDefault is called on the first touchmove event.
+ self.compositor.borrow_mut()
+ .send(ScriptToCompositorMsg::TouchEventProcessed(
+ EventResult::DefaultAllowed)).unwrap();
+ } else {
+ self.compositor.borrow_mut()
+ .send(ScriptToCompositorMsg::TouchEventProcessed(
+ EventResult::DefaultPrevented)).unwrap();
+ }
+ }
+ _ => {
+ // TODO: Calling preventDefault on a touchup event should prevent clicks.
+ }
+ }
+ }
+
+ KeyEvent(key, state, modifiers) => {
+ let page = get_page(&self.root_page(), pipeline_id);
+ let document = page.document();
+ document.dispatch_key_event(
+ key, state, modifiers, &mut self.compositor.borrow_mut());
+ }
+ }
+ }
+
+ fn handle_mouse_event(&self,
+ pipeline_id: PipelineId,
+ mouse_event_type: MouseEventType,
+ button: MouseButton,
+ point: Point2D<f32>) {
+ let page = get_page(&self.root_page(), pipeline_id);
+ let document = page.document();
+ document.handle_mouse_event(self.js_runtime.rt(), button, point, mouse_event_type);
+ }
+
+ fn handle_touch_event(&self,
+ pipeline_id: PipelineId,
+ event_type: TouchEventType,
+ identifier: TouchId,
+ point: Point2D<f32>)
+ -> bool {
+ let page = get_page(&self.root_page(), pipeline_id);
+ let document = page.document();
+ document.handle_touch_event(self.js_runtime.rt(), event_type, identifier, point)
+ }
+
+ /// https://html.spec.whatwg.org/multipage/#navigating-across-documents
+ /// The entry point for content to notify that a new load has been requested
+ /// for the given pipeline (specifically the "navigate" algorithm).
+ fn handle_navigate(&self, pipeline_id: PipelineId, subpage_id: Option<SubpageId>, load_data: LoadData) {
+ // Step 8.
+ {
+ let nurl = &load_data.url;
+ if let Some(ref fragment) = nurl.fragment {
+ let page = get_page(&self.root_page(), pipeline_id);
+ let document = page.document();
+ let document = document.r();
+ let url = document.url();
+ if url.scheme == nurl.scheme && url.scheme_data == nurl.scheme_data &&
+ url.query == nurl.query && load_data.method == Method::Get {
+ match document.find_fragment_node(&*fragment) {
+ Some(ref node) => {
+ self.scroll_fragment_point(pipeline_id, node.r());
+ }
+ None => {}
+ }
+ return;
+ }
+ }
+ }
+
+ match subpage_id {
+ Some(subpage_id) => {
+ let borrowed_page = self.root_page();
+ let iframe = borrowed_page.find(pipeline_id).and_then(|page| {
+ let doc = page.document();
+ doc.find_iframe(subpage_id)
+ });
+ if let Some(iframe) = iframe.r() {
+ iframe.navigate_or_reload_child_browsing_context(Some(load_data.url));
+ }
+ }
+ None => {
+ let ConstellationChan(ref const_chan) = self.constellation_chan;
+ const_chan.send(ConstellationMsg::LoadUrl(pipeline_id, load_data)).unwrap();
+ }
+ }
+ }
+
+ fn handle_resize_event(&self, pipeline_id: PipelineId, new_size: WindowSizeData) {
+ let page = get_page(&self.root_page(), pipeline_id);
+ let window = page.window();
+ window.set_window_size(new_size);
+ window.force_reflow(ReflowGoal::ForDisplay,
+ ReflowQueryType::NoQuery,
+ ReflowReason::WindowResize);
+
+ let document = page.document();
+ let fragment_node = window.steal_fragment_name()
+ .and_then(|name| document.find_fragment_node(&*name));
+ match fragment_node {
+ Some(ref node) => self.scroll_fragment_point(pipeline_id, node.r()),
+ None => {}
+ }
+
+ // http://dev.w3.org/csswg/cssom-view/#resizing-viewports
+ // https://dvcs.w3.org/hg/dom3events/raw-file/tip/html/DOM3-Events.html#event-type-resize
+ let uievent = UIEvent::new(window.r(),
+ DOMString::from("resize"), EventBubbles::DoesNotBubble,
+ EventCancelable::NotCancelable, Some(window.r()),
+ 0i32);
+ uievent.upcast::<Event>().fire(window.upcast());
+ }
+
+ /// Initiate a non-blocking fetch for a specified resource. Stores the InProgressLoad
+ /// argument until a notification is received that the fetch is complete.
+ fn start_page_load(&self, incomplete: InProgressLoad, mut load_data: LoadData) {
+ let id = incomplete.pipeline_id.clone();
+ let subpage = incomplete.parent_info.clone().map(|p| p.1);
+
+ let script_chan = self.chan.clone();
+ let resource_thread = self.resource_thread.clone();
+
+ let context = Arc::new(Mutex::new(ParserContext::new(id, subpage, script_chan.clone(),
+ load_data.url.clone())));
+ let (action_sender, action_receiver) = ipc::channel().unwrap();
+ let listener = NetworkListener {
+ context: context,
+ script_chan: script_chan.clone(),
+ };
+ ROUTER.add_route(action_receiver.to_opaque(), box move |message| {
+ listener.notify(message.to().unwrap());
+ });
+ let response_target = AsyncResponseTarget {
+ sender: action_sender,
+ };
+
+ if load_data.url.scheme == "javascript" {
+ load_data.url = url!("about:blank");
+ }
+
+ resource_thread.send(ControlMsg::Load(NetLoadData {
+ context: LoadContext::Browsing,
+ url: load_data.url,
+ method: load_data.method,
+ headers: Headers::new(),
+ preserved_headers: load_data.headers,
+ data: load_data.data,
+ cors: None,
+ pipeline_id: Some(id),
+ credentials_flag: true,
+ }, LoadConsumer::Listener(response_target), None)).unwrap();
+
+ self.incomplete_loads.borrow_mut().push(incomplete);
+ }
+
+ fn handle_parsing_complete(&self, id: PipelineId) {
+ let parent_page = self.root_page();
+ let page = match parent_page.find(id) {
+ Some(page) => page,
+ None => return,
+ };
+
+ let document = page.document();
+ let final_url = document.url();
+
+ // https://html.spec.whatwg.org/multipage/#the-end step 1
+ document.set_ready_state(DocumentReadyState::Interactive);
+
+ // TODO: Execute step 2 here.
+
+ // Kick off the initial reflow of the page.
+ debug!("kicking off initial reflow of {:?}", final_url);
+ document.disarm_reflow_timeout();
+ document.content_changed(document.upcast(),
+ NodeDamage::OtherNodeDamage);
+ let window = window_from_node(document.r());
+ window.reflow(ReflowGoal::ForDisplay, ReflowQueryType::NoQuery, ReflowReason::FirstLoad);
+
+ // No more reflow required
+ page.set_reflow_status(false);
+
+ // https://html.spec.whatwg.org/multipage/#the-end steps 3-4.
+ document.process_deferred_scripts();
+
+ window.set_fragment_name(final_url.fragment.clone());
+
+ // Notify devtools that a new script global exists.
+ //TODO: should this happen as soon as the global is created, or at least once the first
+ // script runs?
+ self.notify_devtools(document.Title(), (*final_url).clone(), (id, None));
+ }
+
+ fn handle_css_error_reporting(&self, pipeline_id: PipelineId, filename: String,
+ line: usize, column: usize, msg: String) {
+ let parent_page = self.root_page();
+ let page = match parent_page.find(pipeline_id) {
+ Some(page) => page,
+ None => return,
+ };
+
+ let document = page.document();
+ let css_error = CSSError {
+ filename: filename,
+ line: line,
+ column: column,
+ msg: msg
+ };
+ document.report_css_error(css_error);
+ }
+}
+
+impl Drop for ScriptThread {
+ fn drop(&mut self) {
+ SCRIPT_TASK_ROOT.with(|root| {
+ *root.borrow_mut() = None;
+ });
+ }
+}
+
+/// Shuts down layout for the given page tree.
+fn shut_down_layout(page_tree: &Rc<Page>) {
+ let mut channels = vec!();
+
+ for page in page_tree.iter() {
+ // Tell the layout thread to begin shutting down, and wait until it
+ // processed this message.
+ let (response_chan, response_port) = channel();
+ let window = page.window();
+ let LayoutChan(chan) = window.layout_chan();
+ if chan.send(layout_interface::Msg::PrepareToExit(response_chan)).is_ok() {
+ channels.push(chan);
+ response_port.recv().unwrap();
+ }
+ }
+
+ // Drop our references to the JSContext and DOM objects.
+ for page in page_tree.iter() {
+ let window = page.window();
+ window.clear_js_runtime();
+ // Sever the connection between the global and the DOM tree
+ page.set_frame(None);
+ }
+
+ // Destroy the layout thread. If there were node leaks, layout will now crash safely.
+ for chan in channels {
+ chan.send(layout_interface::Msg::ExitNow).ok();
+ }
+}
+
+pub fn get_page(page: &Rc<Page>, pipeline_id: PipelineId) -> Rc<Page> {
+ page.find(pipeline_id).expect("ScriptThread: received an event \
+ message for a layout channel that is not associated with this script thread.\
+ This is a bug.")
+}
+
+fn dom_last_modified(tm: &Tm) -> String {
+ tm.to_local().strftime("%m/%d/%Y %H:%M:%S").unwrap().to_string()
+}