mirror of
https://github.com/servo/servo.git
synced 2025-07-23 07:13:52 +01:00
Implemented Houdini worklets.
This commit is contained in:
parent
abb2985ffe
commit
af8436c9be
34 changed files with 1209 additions and 17 deletions
46
Cargo.lock
generated
46
Cargo.lock
generated
|
@ -194,6 +194,11 @@ name = "bit-vec"
|
|||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "0.7.0"
|
||||
|
@ -2142,6 +2147,15 @@ dependencies = [
|
|||
"time 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pulldown-cmark"
|
||||
version = "0.0.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"bitflags 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"getopts 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quasi"
|
||||
version = "0.32.0"
|
||||
|
@ -2351,6 +2365,7 @@ dependencies = [
|
|||
"smallvec 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"style 0.0.1",
|
||||
"style_traits 0.0.1",
|
||||
"swapper 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"time 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"tinyfiledialogs 2.5.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"unicode-segmentation 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -2407,7 +2422,6 @@ dependencies = [
|
|||
name = "script_traits"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"app_units 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"bluetooth_traits 0.0.1",
|
||||
"canvas_traits 0.0.1",
|
||||
"cookie 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -2738,6 +2752,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
name = "size_of_test"
|
||||
version = "0.0.1"
|
||||
|
||||
[[package]]
|
||||
name = "skeptic"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"pulldown-cmark 0.0.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"tempdir 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
version = "0.3.0"
|
||||
|
@ -2882,6 +2905,14 @@ dependencies = [
|
|||
"style_traits 0.0.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "swapper"
|
||||
version = "0.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"skeptic 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "0.11.11"
|
||||
|
@ -2968,6 +2999,14 @@ dependencies = [
|
|||
"gcc 0.3.45 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tempdir"
|
||||
version = "0.3.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"rand 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tendril"
|
||||
version = "0.2.4"
|
||||
|
@ -3395,6 +3434,7 @@ dependencies = [
|
|||
"checksum bindgen 0.25.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ccaf8958532d7e570e905266ee2dc1094c3e5c3c3cfc2c299368747a30a5e654"
|
||||
"checksum bit-set 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d9bf6104718e80d7b26a68fdbacff3481cfc05df670821affc7e9cbc1884400c"
|
||||
"checksum bit-vec 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "5b97c2c8e8bbb4251754f559df8af22fb264853c7d009084a576cdf12565089d"
|
||||
"checksum bitflags 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4f67931368edf3a9a51d29886d245f1c3db2f1ef0dcc9e35ff70341b78c10d23"
|
||||
"checksum bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "aad18937a628ec6abcd26d1489012cc0e18c21798210f491af69ded9b881106d"
|
||||
"checksum bitflags 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1370e9fc2a6ae53aea8b7a5110edbd08836ed87c88736dfabccade1c2b44bff4"
|
||||
"checksum bitreader 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "80b13e2ab064ff3aa0bdbf1eff533f9822dc37899821f5f98c67f263eab51707"
|
||||
|
@ -3550,6 +3590,7 @@ dependencies = [
|
|||
"checksum png 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "3cb773e9a557edb568ce9935cf783e3cdcabe06a9449d41b3e5506d88e582c82"
|
||||
"checksum precomputed-hash 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "cdf1fc3616b3ef726a847f2cd2388c646ef6a1f1ba4835c2629004da48184150"
|
||||
"checksum procedural-masquerade 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9f566249236c6ca4340f7ca78968271f0ed2b0f234007a61b66f9ecd0af09260"
|
||||
"checksum pulldown-cmark 0.0.8 (registry+https://github.com/rust-lang/crates.io-index)" = "1058d7bb927ca067656537eec4e02c2b4b70eaaa129664c5b90c111e20326f41"
|
||||
"checksum quasi 0.32.0 (registry+https://github.com/rust-lang/crates.io-index)" = "18c45c4854d6d1cf5d531db97c75880feb91c958b0720f4ec1057135fec358b3"
|
||||
"checksum quasi_codegen 0.32.0 (registry+https://github.com/rust-lang/crates.io-index)" = "51b9e25fa23c044c1803f43ca59c98dac608976dd04ce799411edd58ece776d4"
|
||||
"checksum quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a"
|
||||
|
@ -3586,12 +3627,14 @@ dependencies = [
|
|||
"checksum signpost 0.1.0 (git+https://github.com/pcwalton/signpost.git)" = "<none>"
|
||||
"checksum simd 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7a94d14a2ae1f1f110937de5fb69e494372560181c7e1739a097fcc2cee37ba0"
|
||||
"checksum siphasher 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "0df90a788073e8d0235a67e50441d47db7c8ad9debd91cbf43736a2a92d36537"
|
||||
"checksum skeptic 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "dd7d8dc1315094150052d0ab767840376335a98ac66ef313ff911cdf439a5b69"
|
||||
"checksum slab 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "17b4fcaed89ab08ef143da37bc52adbcc04d4a69014f4c1208d6b51f0c47bc23"
|
||||
"checksum smallvec 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "4f8266519bc1d17d0b5b16f6c21295625d562841c708f6376f49028a43e9c11e"
|
||||
"checksum string_cache 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f55fba06c5e294108f22e8512eb598cb13388a117991e411a8df8f41a1219a75"
|
||||
"checksum string_cache_codegen 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "479cde50c3539481f33906a387f2bd17c8e87cb848c35b6021d41fb81ff9b4d7"
|
||||
"checksum string_cache_shared 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b1884d1bc09741d466d9b14e6d37ac89d6909cbcac41dd9ae982d4d063bbedfc"
|
||||
"checksum strsim 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b4d15c810519a91cf877e7e36e63fe068815c678181439f2f29e2562147c3694"
|
||||
"checksum swapper 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "3ca610b32bb8bfc5e7f705480c3a1edfeb70b6582495d343872c8bee0dcf758c"
|
||||
"checksum syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d3b891b9015c88c576343b9b3e41c2c11a51c219ef067b264bd9c8aa9b441dad"
|
||||
"checksum synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a393066ed9010ebaed60b9eafa373d4b1baac186dd7e008555b0f702b51945b6"
|
||||
"checksum synstructure 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5ccc9780bf1aa601943988c2876ab22413c01ad1739689aa6af18d0aa0b3f38b"
|
||||
|
@ -3600,6 +3643,7 @@ dependencies = [
|
|||
"checksum syntex_pos 0.58.1 (registry+https://github.com/rust-lang/crates.io-index)" = "13ad4762fe52abc9f4008e85c4fb1b1fe3aa91ccb99ff4826a439c7c598e1047"
|
||||
"checksum syntex_syntax 0.58.1 (registry+https://github.com/rust-lang/crates.io-index)" = "6e0e4dbae163dd98989464c23dd503161b338790640e11537686f2ef0f25c791"
|
||||
"checksum target_build_utils 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f42dc058080c19c6a58bdd1bf962904ee4f5ef1fe2a81b529f31dacc750c679f"
|
||||
"checksum tempdir 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "87974a6f5c1dfb344d733055601650059a3363de2a6104819293baff662132d6"
|
||||
"checksum tendril 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "4ce04c250d202db8004921e3d3bc95eaa4f2126c6937a428ae39d12d0e38df62"
|
||||
"checksum term 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "d168af3930b369cfe245132550579d47dfd873d69470755a19c2c6568dbbd989"
|
||||
"checksum term_size 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "07b6c1ac5b3fffd75073276bca1ceed01f67a28537097a2a9539e116e50fb21a"
|
||||
|
|
|
@ -150,6 +150,7 @@ impl Formattable for ProfilerCategory {
|
|||
ProfilerCategory::ScriptEnterFullscreen => "Script Enter Fullscreen",
|
||||
ProfilerCategory::ScriptExitFullscreen => "Script Exit Fullscreen",
|
||||
ProfilerCategory::ScriptWebVREvent => "Script WebVR Event",
|
||||
ProfilerCategory::ScriptWorkletEvent => "Script Worklet Event",
|
||||
ProfilerCategory::ApplicationHeartbeat => "Application Heartbeat",
|
||||
};
|
||||
format!("{}{}", padding, name)
|
||||
|
|
|
@ -89,6 +89,7 @@ pub enum ProfilerCategory {
|
|||
ScriptEnterFullscreen = 0x77,
|
||||
ScriptExitFullscreen = 0x78,
|
||||
ScriptWebVREvent = 0x79,
|
||||
ScriptWorkletEvent = 0x7a,
|
||||
ApplicationHeartbeat = 0x90,
|
||||
}
|
||||
|
||||
|
|
|
@ -83,6 +83,7 @@ servo_url = {path = "../url"}
|
|||
smallvec = "0.3"
|
||||
style = {path = "../style"}
|
||||
style_traits = {path = "../style_traits"}
|
||||
swapper = "0.0.4"
|
||||
time = "0.1.12"
|
||||
unicode-segmentation = "1.1.0"
|
||||
url = {version = "1.2", features = ["heap_size", "query_encoding"]}
|
||||
|
|
|
@ -23,12 +23,17 @@
|
|||
//! as JS roots.
|
||||
|
||||
use core::nonzero::NonZero;
|
||||
use dom::bindings::conversions::ToJSValConvertible;
|
||||
use dom::bindings::error::Error;
|
||||
use dom::bindings::js::Root;
|
||||
use dom::bindings::reflector::{DomObject, Reflector};
|
||||
use dom::bindings::trace::trace_reflector;
|
||||
use dom::promise::Promise;
|
||||
use js::jsapi::JSAutoCompartment;
|
||||
use js::jsapi::JSTracer;
|
||||
use libc;
|
||||
use script_thread::Runnable;
|
||||
use script_thread::ScriptThread;
|
||||
use std::cell::RefCell;
|
||||
use std::collections::hash_map::Entry::{Occupied, Vacant};
|
||||
use std::collections::hash_map::HashMap;
|
||||
|
@ -115,6 +120,40 @@ impl TrustedPromise {
|
|||
promise
|
||||
})
|
||||
}
|
||||
|
||||
/// A runnable which will reject the promise.
|
||||
#[allow(unrooted_must_root)]
|
||||
pub fn reject_runnable(self, error: Error) -> impl Runnable + Send {
|
||||
struct RejectPromise(TrustedPromise, Error);
|
||||
impl Runnable for RejectPromise {
|
||||
fn main_thread_handler(self: Box<Self>, script_thread: &ScriptThread) {
|
||||
let this = *self;
|
||||
let cx = script_thread.get_cx();
|
||||
let promise = this.0.root();
|
||||
let _ac = JSAutoCompartment::new(cx, promise.reflector().get_jsobject().get());
|
||||
promise.reject_error(cx, this.1);
|
||||
}
|
||||
}
|
||||
RejectPromise(self, error)
|
||||
}
|
||||
|
||||
/// A runnable which will resolve the promise.
|
||||
#[allow(unrooted_must_root)]
|
||||
pub fn resolve_runnable<T>(self, value: T) -> impl Runnable + Send where
|
||||
T: ToJSValConvertible + Send
|
||||
{
|
||||
struct ResolvePromise<T>(TrustedPromise, T);
|
||||
impl<T: ToJSValConvertible> Runnable for ResolvePromise<T> {
|
||||
fn main_thread_handler(self: Box<Self>, script_thread: &ScriptThread) {
|
||||
let this = *self;
|
||||
let cx = script_thread.get_cx();
|
||||
let promise = this.0.root();
|
||||
let _ac = JSAutoCompartment::new(cx, promise.reflector().get_jsobject().get());
|
||||
promise.resolve_native(cx, &this.1);
|
||||
}
|
||||
}
|
||||
ResolvePromise(self, value)
|
||||
}
|
||||
}
|
||||
|
||||
/// A safe wrapper around a raw pointer to a DOM object that can be
|
||||
|
|
|
@ -19,6 +19,7 @@ use dom::event::{Event, EventBubbles, EventCancelable, EventStatus};
|
|||
use dom::eventtarget::EventTarget;
|
||||
use dom::window::Window;
|
||||
use dom::workerglobalscope::WorkerGlobalScope;
|
||||
use dom::workletglobalscope::WorkletGlobalScope;
|
||||
use dom_struct::dom_struct;
|
||||
use ipc_channel::ipc::IpcSender;
|
||||
use js::{JSCLASS_IS_DOMJSCLASS, JSCLASS_IS_GLOBAL};
|
||||
|
@ -259,6 +260,10 @@ impl GlobalScope {
|
|||
// https://html.spec.whatwg.org/multipage/#script-settings-for-workers:api-base-url
|
||||
return worker.get_url().clone();
|
||||
}
|
||||
if let Some(worker) = self.downcast::<WorkletGlobalScope>() {
|
||||
// https://drafts.css-houdini.org/worklets/#script-settings-for-worklets
|
||||
return worker.base_url();
|
||||
}
|
||||
unreachable!();
|
||||
}
|
||||
|
||||
|
@ -270,6 +275,10 @@ impl GlobalScope {
|
|||
if let Some(worker) = self.downcast::<WorkerGlobalScope>() {
|
||||
return worker.get_url().clone();
|
||||
}
|
||||
if let Some(worker) = self.downcast::<WorkletGlobalScope>() {
|
||||
// TODO: is this the right URL to return?
|
||||
return worker.base_url();
|
||||
}
|
||||
unreachable!();
|
||||
}
|
||||
|
||||
|
@ -349,14 +358,14 @@ impl GlobalScope {
|
|||
|
||||
/// Evaluate JS code on this global scope.
|
||||
pub fn evaluate_js_on_global_with_result(
|
||||
&self, code: &str, rval: MutableHandleValue) {
|
||||
&self, code: &str, rval: MutableHandleValue) -> bool {
|
||||
self.evaluate_script_on_global_with_result(code, "", rval, 1)
|
||||
}
|
||||
|
||||
/// Evaluate a JS script on this global scope.
|
||||
#[allow(unsafe_code)]
|
||||
pub fn evaluate_script_on_global_with_result(
|
||||
&self, code: &str, filename: &str, rval: MutableHandleValue, line_number: u32) {
|
||||
&self, code: &str, filename: &str, rval: MutableHandleValue, line_number: u32) -> bool {
|
||||
let metadata = time::TimerMetadata {
|
||||
url: if filename.is_empty() {
|
||||
self.get_url().as_str().into()
|
||||
|
@ -379,16 +388,21 @@ impl GlobalScope {
|
|||
let _ac = JSAutoCompartment::new(cx, globalhandle.get());
|
||||
let _aes = AutoEntryScript::new(self);
|
||||
let options = CompileOptionsWrapper::new(cx, filename.as_ptr(), line_number);
|
||||
unsafe {
|
||||
if !Evaluate2(cx, options.ptr, code.as_ptr(),
|
||||
|
||||
debug!("evaluating JS string");
|
||||
let result = unsafe {
|
||||
Evaluate2(cx, options.ptr, code.as_ptr(),
|
||||
code.len() as libc::size_t,
|
||||
rval) {
|
||||
rval)
|
||||
};
|
||||
|
||||
if !result {
|
||||
debug!("error evaluating JS string");
|
||||
report_pending_exception(cx, true);
|
||||
}
|
||||
unsafe { report_pending_exception(cx, true) };
|
||||
}
|
||||
|
||||
maybe_resume_unwind();
|
||||
result
|
||||
}
|
||||
)
|
||||
}
|
||||
|
@ -468,6 +482,9 @@ impl GlobalScope {
|
|||
if let Some(worker) = self.downcast::<WorkerGlobalScope>() {
|
||||
return worker.perform_a_microtask_checkpoint();
|
||||
}
|
||||
if let Some(worker) = self.downcast::<WorkletGlobalScope>() {
|
||||
return worker.perform_a_microtask_checkpoint();
|
||||
}
|
||||
unreachable!();
|
||||
}
|
||||
|
||||
|
@ -479,6 +496,9 @@ impl GlobalScope {
|
|||
if let Some(worker) = self.downcast::<WorkerGlobalScope>() {
|
||||
return worker.enqueue_microtask(job);
|
||||
}
|
||||
if let Some(worker) = self.downcast::<WorkletGlobalScope>() {
|
||||
return worker.enqueue_microtask(job);
|
||||
}
|
||||
unreachable!();
|
||||
}
|
||||
|
||||
|
|
|
@ -423,6 +423,8 @@ pub mod testbindingiterable;
|
|||
pub mod testbindingpairiterable;
|
||||
pub mod testbindingproxy;
|
||||
pub mod testrunner;
|
||||
pub mod testworklet;
|
||||
pub mod testworkletglobalscope;
|
||||
pub mod text;
|
||||
pub mod textdecoder;
|
||||
pub mod textencoder;
|
||||
|
@ -469,6 +471,8 @@ pub mod worker;
|
|||
pub mod workerglobalscope;
|
||||
pub mod workerlocation;
|
||||
pub mod workernavigator;
|
||||
pub mod worklet;
|
||||
pub mod workletglobalscope;
|
||||
pub mod xmldocument;
|
||||
pub mod xmlhttprequest;
|
||||
pub mod xmlhttprequesteventtarget;
|
||||
|
|
|
@ -296,3 +296,4 @@ fn create_native_handler_function(cx: *mut JSContext,
|
|||
obj.get()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
61
components/script/dom/testworklet.rs
Normal file
61
components/script/dom/testworklet.rs
Normal file
|
@ -0,0 +1,61 @@
|
|||
/* 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/. */
|
||||
|
||||
// check-tidy: no specs after this line
|
||||
|
||||
use dom::bindings::codegen::Bindings::TestWorkletBinding::TestWorkletMethods;
|
||||
use dom::bindings::codegen::Bindings::TestWorkletBinding::Wrap;
|
||||
use dom::bindings::codegen::Bindings::WorkletBinding::WorkletBinding::WorkletMethods;
|
||||
use dom::bindings::codegen::Bindings::WorkletBinding::WorkletOptions;
|
||||
use dom::bindings::error::Fallible;
|
||||
use dom::bindings::js::JS;
|
||||
use dom::bindings::js::Root;
|
||||
use dom::bindings::reflector::Reflector;
|
||||
use dom::bindings::reflector::reflect_dom_object;
|
||||
use dom::bindings::str::DOMString;
|
||||
use dom::bindings::str::USVString;
|
||||
use dom::promise::Promise;
|
||||
use dom::window::Window;
|
||||
use dom::worklet::Worklet;
|
||||
use dom::workletglobalscope::WorkletGlobalScopeType;
|
||||
use dom_struct::dom_struct;
|
||||
use script_thread::ScriptThread;
|
||||
use std::rc::Rc;
|
||||
|
||||
#[dom_struct]
|
||||
pub struct TestWorklet {
|
||||
reflector: Reflector,
|
||||
worklet: JS<Worklet>,
|
||||
}
|
||||
|
||||
impl TestWorklet {
|
||||
fn new_inherited(worklet: &Worklet) -> TestWorklet {
|
||||
TestWorklet {
|
||||
reflector: Reflector::new(),
|
||||
worklet: JS::from_ref(worklet),
|
||||
}
|
||||
}
|
||||
|
||||
fn new(window: &Window) -> Root<TestWorklet> {
|
||||
let worklet = Worklet::new(window, WorkletGlobalScopeType::Test);
|
||||
reflect_dom_object(box TestWorklet::new_inherited(&*worklet), window, Wrap)
|
||||
}
|
||||
|
||||
pub fn Constructor(window: &Window) -> Fallible<Root<TestWorklet>> {
|
||||
Ok(TestWorklet::new(window))
|
||||
}
|
||||
}
|
||||
|
||||
impl TestWorkletMethods for TestWorklet {
|
||||
#[allow(unrooted_must_root)]
|
||||
fn AddModule(&self, moduleURL: USVString, options: &WorkletOptions) -> Rc<Promise> {
|
||||
self.worklet.AddModule(moduleURL, options)
|
||||
}
|
||||
|
||||
fn Lookup(&self, key: DOMString) -> Option<DOMString> {
|
||||
let id = self.worklet.worklet_id();
|
||||
let pool = ScriptThread::worklet_thread_pool();
|
||||
pool.test_worklet_lookup(id, String::from(key)).map(DOMString::from)
|
||||
}
|
||||
}
|
66
components/script/dom/testworkletglobalscope.rs
Normal file
66
components/script/dom/testworkletglobalscope.rs
Normal file
|
@ -0,0 +1,66 @@
|
|||
/* 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/. */
|
||||
|
||||
use dom::bindings::cell::DOMRefCell;
|
||||
use dom::bindings::codegen::Bindings::TestWorkletGlobalScopeBinding;
|
||||
use dom::bindings::codegen::Bindings::TestWorkletGlobalScopeBinding::TestWorkletGlobalScopeMethods;
|
||||
use dom::bindings::js::Root;
|
||||
use dom::bindings::str::DOMString;
|
||||
use dom::workletglobalscope::WorkletGlobalScope;
|
||||
use dom::workletglobalscope::WorkletGlobalScopeInit;
|
||||
use dom_struct::dom_struct;
|
||||
use js::rust::Runtime;
|
||||
use msg::constellation_msg::PipelineId;
|
||||
use servo_url::ServoUrl;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::mpsc::Sender;
|
||||
|
||||
// check-tidy: no specs after this line
|
||||
|
||||
#[dom_struct]
|
||||
pub struct TestWorkletGlobalScope {
|
||||
// The worklet global for this object
|
||||
worklet_global: WorkletGlobalScope,
|
||||
// The key/value pairs
|
||||
lookup_table: DOMRefCell<HashMap<String, String>>,
|
||||
}
|
||||
|
||||
impl TestWorkletGlobalScope {
|
||||
#[allow(unsafe_code)]
|
||||
pub fn new(runtime: &Runtime,
|
||||
pipeline_id: PipelineId,
|
||||
base_url: ServoUrl,
|
||||
init: &WorkletGlobalScopeInit)
|
||||
-> Root<TestWorkletGlobalScope>
|
||||
{
|
||||
debug!("Creating test worklet global scope for pipeline {}.", pipeline_id);
|
||||
let global = box TestWorkletGlobalScope {
|
||||
worklet_global: WorkletGlobalScope::new_inherited(pipeline_id, base_url, init),
|
||||
lookup_table: Default::default(),
|
||||
};
|
||||
unsafe { TestWorkletGlobalScopeBinding::Wrap(runtime.cx(), global) }
|
||||
}
|
||||
|
||||
pub fn perform_a_worklet_task(&self, task: TestWorkletTask) {
|
||||
match task {
|
||||
TestWorkletTask::Lookup(key, sender) => {
|
||||
debug!("Looking up key {}.", key);
|
||||
let result = self.lookup_table.borrow().get(&key).cloned();
|
||||
let _ = sender.send(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TestWorkletGlobalScopeMethods for TestWorkletGlobalScope {
|
||||
fn RegisterKeyValue(&self, key: DOMString, value: DOMString) {
|
||||
debug!("Registering test worklet key/value {}/{}.", key, value);
|
||||
self.lookup_table.borrow_mut().insert(String::from(key), String::from(value));
|
||||
}
|
||||
}
|
||||
|
||||
/// Tasks which can be performed by test worklets.
|
||||
pub enum TestWorkletTask {
|
||||
Lookup(String, Sender<Option<String>>),
|
||||
}
|
|
@ -10,7 +10,7 @@
|
|||
*/
|
||||
|
||||
[ClassString="Console",
|
||||
Exposed=(Window,Worker),
|
||||
Exposed=(Window,Worker,Worklet),
|
||||
ProtoObjectHack]
|
||||
namespace console {
|
||||
// These should be DOMString message, DOMString message2, ...
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* https://dom.spec.whatwg.org/#interface-eventtarget
|
||||
*/
|
||||
|
||||
[Abstract, Exposed=(Window,Worker)]
|
||||
[Abstract, Exposed=(Window,Worker,Worklet)]
|
||||
interface EventTarget {
|
||||
void addEventListener(DOMString type,
|
||||
EventListener? listener,
|
||||
|
|
|
@ -5,6 +5,6 @@
|
|||
// This interface is entirely internal to Servo, and should not be accessible to
|
||||
// web pages.
|
||||
|
||||
[Exposed=(Window,Worker),
|
||||
[Exposed=(Window,Worker,Worklet),
|
||||
Inline]
|
||||
interface GlobalScope : EventTarget {};
|
||||
|
|
12
components/script/dom/webidls/TestWorklet.webidl
Normal file
12
components/script/dom/webidls/TestWorklet.webidl
Normal file
|
@ -0,0 +1,12 @@
|
|||
/* 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/. */
|
||||
|
||||
// This interface is entirely internal to Servo, and should not be accessible to
|
||||
// web pages.
|
||||
|
||||
[Pref="dom.worklet.testing.enabled", Exposed=(Window), Constructor]
|
||||
interface TestWorklet {
|
||||
[NewObject] Promise<void> addModule(USVString moduleURL, optional WorkletOptions options);
|
||||
DOMString? lookup(DOMString key);
|
||||
};
|
11
components/script/dom/webidls/TestWorkletGlobalScope.webidl
Normal file
11
components/script/dom/webidls/TestWorkletGlobalScope.webidl
Normal file
|
@ -0,0 +1,11 @@
|
|||
/* 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/. */
|
||||
|
||||
// This interface is entirely internal to Servo, and should not be accessible to
|
||||
// web pages.
|
||||
|
||||
[Global=(Worklet,TestWorklet), Exposed=TestWorklet]
|
||||
interface TestWorkletGlobalScope : WorkletGlobalScope {
|
||||
void registerKeyValue(DOMString key, DOMString value);
|
||||
};
|
13
components/script/dom/webidls/VoidFunction.webidl
Normal file
13
components/script/dom/webidls/VoidFunction.webidl
Normal file
|
@ -0,0 +1,13 @@
|
|||
/* 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 origin of this IDL file is
|
||||
* https://heycam.github.io/webidl/#VoidFunction
|
||||
*
|
||||
* © Copyright 2004-2011 Apple Computer, Inc., Mozilla Foundation, and
|
||||
* Opera Software ASA. You are granted a license to use, reproduce
|
||||
* and create derivative works of this document.
|
||||
*/
|
||||
|
||||
callback VoidFunction = void ();
|
|
@ -201,3 +201,4 @@ partial interface Window {
|
|||
readonly attribute TestRunner testRunner;
|
||||
//readonly attribute EventSender eventSender;
|
||||
};
|
||||
|
||||
|
|
13
components/script/dom/webidls/Worklet.webidl
Normal file
13
components/script/dom/webidls/Worklet.webidl
Normal file
|
@ -0,0 +1,13 @@
|
|||
/* 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/. */
|
||||
|
||||
// https://drafts.css-houdini.org/worklets/#worklet
|
||||
[Exposed=(Window)]
|
||||
interface Worklet {
|
||||
[NewObject] Promise<void> addModule(USVString moduleURL, optional WorkletOptions options);
|
||||
};
|
||||
|
||||
dictionary WorkletOptions {
|
||||
RequestCredentials credentials = "omit";
|
||||
};
|
10
components/script/dom/webidls/WorkletGlobalScope.webidl
Normal file
10
components/script/dom/webidls/WorkletGlobalScope.webidl
Normal file
|
@ -0,0 +1,10 @@
|
|||
/* 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/. */
|
||||
|
||||
// https://drafts.css-houdini.org/worklets/#workletglobalscope
|
||||
// TODO: The spec IDL doesn't make this a subclass of EventTarget
|
||||
// https://github.com/whatwg/html/issues/2611
|
||||
[Exposed=Worklet]
|
||||
interface WorkletGlobalScope: GlobalScope {
|
||||
};
|
|
@ -49,6 +49,7 @@ use dom::screen::Screen;
|
|||
use dom::storage::Storage;
|
||||
use dom::testrunner::TestRunner;
|
||||
use dom::windowproxy::WindowProxy;
|
||||
use dom::worklet::Worklet;
|
||||
use dom_struct::dom_struct;
|
||||
use euclid::{Point2D, Rect, Size2D};
|
||||
use fetch;
|
||||
|
@ -273,6 +274,9 @@ pub struct Window {
|
|||
/// Directory to store unminified scripts for this window if unminify-js
|
||||
/// opt is enabled.
|
||||
unminified_js_dir: DOMRefCell<Option<String>>,
|
||||
|
||||
/// Worklets
|
||||
test_worklet: MutNullableJS<Worklet>,
|
||||
}
|
||||
|
||||
impl Window {
|
||||
|
@ -1830,6 +1834,7 @@ impl Window {
|
|||
permission_state_invocation_results: DOMRefCell::new(HashMap::new()),
|
||||
pending_layout_images: DOMRefCell::new(HashMap::new()),
|
||||
unminified_js_dir: DOMRefCell::new(None),
|
||||
test_worklet: Default::default(),
|
||||
};
|
||||
|
||||
unsafe {
|
||||
|
|
637
components/script/dom/worklet.rs
Normal file
637
components/script/dom/worklet.rs
Normal file
|
@ -0,0 +1,637 @@
|
|||
/* 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/. */
|
||||
|
||||
//! An implementation of Houdini worklets.
|
||||
//!
|
||||
//! The goal of this implementation is to maximize responsiveness of worklets,
|
||||
//! and in particular to ensure that the thread performing worklet tasks
|
||||
//! is never busy GCing or loading worklet code. We do this by providing a custom
|
||||
//! thread pool implementation, which only performs GC or code loading on
|
||||
//! a backup thread, not on the primary worklet thread.
|
||||
|
||||
use dom::bindings::codegen::Bindings::RequestBinding::RequestCredentials;
|
||||
use dom::bindings::codegen::Bindings::WindowBinding::WindowBinding::WindowMethods;
|
||||
use dom::bindings::codegen::Bindings::WorkletBinding::WorkletMethods;
|
||||
use dom::bindings::codegen::Bindings::WorkletBinding::WorkletOptions;
|
||||
use dom::bindings::codegen::Bindings::WorkletBinding::Wrap;
|
||||
use dom::bindings::error::Error;
|
||||
use dom::bindings::inheritance::Castable;
|
||||
use dom::bindings::js::JS;
|
||||
use dom::bindings::js::Root;
|
||||
use dom::bindings::js::RootCollection;
|
||||
use dom::bindings::refcounted::TrustedPromise;
|
||||
use dom::bindings::reflector::Reflector;
|
||||
use dom::bindings::reflector::reflect_dom_object;
|
||||
use dom::bindings::str::USVString;
|
||||
use dom::bindings::trace::JSTraceable;
|
||||
use dom::bindings::trace::RootedTraceableBox;
|
||||
use dom::globalscope::GlobalScope;
|
||||
use dom::promise::Promise;
|
||||
use dom::testworkletglobalscope::TestWorkletTask;
|
||||
use dom::window::Window;
|
||||
use dom::workletglobalscope::WorkletGlobalScope;
|
||||
use dom::workletglobalscope::WorkletGlobalScopeInit;
|
||||
use dom::workletglobalscope::WorkletGlobalScopeType;
|
||||
use dom::workletglobalscope::WorkletTask;
|
||||
use dom_struct::dom_struct;
|
||||
use js::jsapi::JSGCParamKey;
|
||||
use js::jsapi::JSTracer;
|
||||
use js::jsapi::JS_GC;
|
||||
use js::jsapi::JS_GetGCParameter;
|
||||
use js::rust::Runtime;
|
||||
use msg::constellation_msg::PipelineId;
|
||||
use net_traits::IpcSend;
|
||||
use net_traits::load_whole_resource;
|
||||
use net_traits::request::Destination;
|
||||
use net_traits::request::RequestInit;
|
||||
use net_traits::request::RequestMode;
|
||||
use net_traits::request::Type as RequestType;
|
||||
use script_runtime::CommonScriptMsg;
|
||||
use script_runtime::ScriptThreadEventCategory;
|
||||
use script_runtime::StackRootTLS;
|
||||
use script_runtime::new_rt_and_cx;
|
||||
use script_thread::MainThreadScriptMsg;
|
||||
use script_thread::Runnable;
|
||||
use script_thread::ScriptThread;
|
||||
use servo_rand;
|
||||
use servo_url::ImmutableOrigin;
|
||||
use servo_url::ServoUrl;
|
||||
use std::cmp::max;
|
||||
use std::collections::HashMap;
|
||||
use std::collections::hash_map;
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::AtomicIsize;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::sync::mpsc;
|
||||
use std::sync::mpsc::Receiver;
|
||||
use std::sync::mpsc::Sender;
|
||||
use std::thread;
|
||||
use style::thread_state;
|
||||
use swapper::Swapper;
|
||||
use swapper::swapper;
|
||||
use uuid::Uuid;
|
||||
|
||||
// Magic numbers
|
||||
const WORKLET_THREAD_POOL_SIZE: u32 = 3;
|
||||
const MIN_GC_THRESHOLD: u32 = 1_000_000;
|
||||
|
||||
#[dom_struct]
|
||||
/// https://drafts.css-houdini.org/worklets/#worklet
|
||||
pub struct Worklet {
|
||||
reflector: Reflector,
|
||||
window: JS<Window>,
|
||||
worklet_id: WorkletId,
|
||||
global_type: WorkletGlobalScopeType,
|
||||
}
|
||||
|
||||
impl Worklet {
|
||||
fn new_inherited(window: &Window, global_type: WorkletGlobalScopeType) -> Worklet {
|
||||
Worklet {
|
||||
reflector: Reflector::new(),
|
||||
window: JS::from_ref(window),
|
||||
worklet_id: WorkletId::new(),
|
||||
global_type: global_type,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new(window: &Window, global_type: WorkletGlobalScopeType) -> Root<Worklet> {
|
||||
debug!("Creating worklet {:?}.", global_type);
|
||||
reflect_dom_object(box Worklet::new_inherited(window, global_type), window, Wrap)
|
||||
}
|
||||
|
||||
pub fn worklet_id(&self) -> WorkletId {
|
||||
self.worklet_id
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn worklet_global_scope_type(&self) -> WorkletGlobalScopeType {
|
||||
self.global_type
|
||||
}
|
||||
}
|
||||
|
||||
impl WorkletMethods for Worklet {
|
||||
#[allow(unrooted_must_root)]
|
||||
/// https://drafts.css-houdini.org/worklets/#dom-worklet-addmodule
|
||||
fn AddModule(&self, module_url: USVString, options: &WorkletOptions) -> Rc<Promise> {
|
||||
// Step 1.
|
||||
let promise = Promise::new(self.window.upcast());
|
||||
|
||||
// Step 3.
|
||||
let module_url_record = match self.window.Document().base_url().join(&module_url.0) {
|
||||
Ok(url) => url,
|
||||
Err(err) => {
|
||||
// Step 4.
|
||||
debug!("URL {:?} parse error {:?}.", module_url.0, err);
|
||||
promise.reject_error(self.window.get_cx(), Error::Syntax);
|
||||
return promise;
|
||||
}
|
||||
};
|
||||
debug!("Adding Worklet module {}.", module_url_record);
|
||||
|
||||
// Steps 6-12 in parallel.
|
||||
let pending_tasks_struct = PendingTasksStruct::new();
|
||||
let global = self.window.upcast::<GlobalScope>();
|
||||
let pool = ScriptThread::worklet_thread_pool();
|
||||
|
||||
pool.fetch_and_invoke_a_worklet_script(global.pipeline_id(),
|
||||
self.worklet_id,
|
||||
self.global_type,
|
||||
self.window.origin().immutable().clone(),
|
||||
global.api_base_url(),
|
||||
module_url_record,
|
||||
options.credentials.clone(),
|
||||
pending_tasks_struct,
|
||||
&promise);
|
||||
|
||||
// Step 5.
|
||||
promise
|
||||
}
|
||||
}
|
||||
|
||||
/// A guid for worklets.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, JSTraceable)]
|
||||
pub struct WorkletId(Uuid);
|
||||
|
||||
known_heap_size!(0, WorkletId);
|
||||
|
||||
impl WorkletId {
|
||||
fn new() -> WorkletId {
|
||||
WorkletId(servo_rand::random())
|
||||
}
|
||||
}
|
||||
|
||||
/// https://drafts.css-houdini.org/worklets/#pending-tasks-struct
|
||||
#[derive(Clone, Debug)]
|
||||
struct PendingTasksStruct(Arc<AtomicIsize>);
|
||||
|
||||
impl PendingTasksStruct {
|
||||
fn new() -> PendingTasksStruct {
|
||||
PendingTasksStruct(Arc::new(AtomicIsize::new(WORKLET_THREAD_POOL_SIZE as isize)))
|
||||
}
|
||||
|
||||
fn set_counter_to(&self, value: isize) -> isize {
|
||||
self.0.swap(value, Ordering::AcqRel)
|
||||
}
|
||||
|
||||
fn decrement_counter_by(&self, offset: isize) -> isize {
|
||||
self.0.fetch_sub(offset, Ordering::AcqRel)
|
||||
}
|
||||
}
|
||||
|
||||
/// Worklets execute in a dedicated thread pool.
|
||||
///
|
||||
/// The goal is to ensure that there is a primary worklet thread,
|
||||
/// which is able to responsively execute worklet code. In particular,
|
||||
/// worklet execution should not be delayed by GC, or by script
|
||||
/// loading.
|
||||
///
|
||||
/// To achieve this, we implement a three-thread pool, with the
|
||||
/// threads cycling between three thread roles:
|
||||
///
|
||||
/// * The primary worklet thread is the one available to execute
|
||||
/// worklet code.
|
||||
///
|
||||
/// * The hot backup thread may peform GC, but otherwise is expected
|
||||
/// to take over the primary role.
|
||||
///
|
||||
/// * The cold backup thread may peform script loading and other
|
||||
/// long-running tasks.
|
||||
///
|
||||
/// In the implementation, we use two kinds of messages:
|
||||
///
|
||||
/// * Data messages are expected to be processed quickly, and include
|
||||
/// the worklet tasks to be performed by the primary thread, as
|
||||
/// well as requests to change role or quit execution.
|
||||
///
|
||||
/// * Control messages are expected to be processed more slowly, and
|
||||
/// include script loading.
|
||||
///
|
||||
/// Data messages are targeted at a role, for example, task execution
|
||||
/// is expected to be performed by whichever thread is currently
|
||||
/// primary. Control messages are targeted at a thread, for example
|
||||
/// adding a module is performed in every thread, even if they change roles
|
||||
/// in the middle of module loading.
|
||||
///
|
||||
/// The thread pool lives in the script thread, and is initialized
|
||||
/// when a worklet adds a module. It is dropped when the script thread
|
||||
/// is dropped, and asks each of the worklet threads to quit.
|
||||
|
||||
#[derive(Clone, JSTraceable)]
|
||||
pub struct WorkletThreadPool {
|
||||
// Channels to send data messages to the three roles.
|
||||
primary_sender: Sender<WorkletData>,
|
||||
hot_backup_sender: Sender<WorkletData>,
|
||||
cold_backup_sender: Sender<WorkletData>,
|
||||
// Channels to send control messages to the three threads.
|
||||
control_sender_0: Sender<WorkletControl>,
|
||||
control_sender_1: Sender<WorkletControl>,
|
||||
control_sender_2: Sender<WorkletControl>,
|
||||
}
|
||||
|
||||
impl Drop for WorkletThreadPool {
|
||||
fn drop(&mut self) {
|
||||
let _ = self.cold_backup_sender.send(WorkletData::Quit);
|
||||
let _ = self.hot_backup_sender.send(WorkletData::Quit);
|
||||
let _ = self.primary_sender.send(WorkletData::Quit);
|
||||
}
|
||||
}
|
||||
|
||||
impl WorkletThreadPool {
|
||||
/// Create a new thread pool and spawn the threads.
|
||||
/// When the thread pool is dropped, the threads will be asked to quit.
|
||||
pub fn spawn(script_sender: Sender<MainThreadScriptMsg>, global_init: WorkletGlobalScopeInit) -> WorkletThreadPool {
|
||||
let primary_role = WorkletThreadRole::new(false, false);
|
||||
let hot_backup_role = WorkletThreadRole::new(true, false);
|
||||
let cold_backup_role = WorkletThreadRole::new(false, true);
|
||||
let primary_sender = primary_role.sender.clone();
|
||||
let hot_backup_sender = hot_backup_role.sender.clone();
|
||||
let cold_backup_sender = cold_backup_role.sender.clone();
|
||||
let init = WorkletThreadInit {
|
||||
hot_backup_sender: hot_backup_sender.clone(),
|
||||
cold_backup_sender: cold_backup_sender.clone(),
|
||||
script_sender: script_sender.clone(),
|
||||
global_init: global_init,
|
||||
};
|
||||
WorkletThreadPool {
|
||||
primary_sender: primary_sender,
|
||||
hot_backup_sender: hot_backup_sender,
|
||||
cold_backup_sender: cold_backup_sender,
|
||||
control_sender_0: WorkletThread::spawn(primary_role, init.clone()),
|
||||
control_sender_1: WorkletThread::spawn(hot_backup_role, init.clone()),
|
||||
control_sender_2: WorkletThread::spawn(cold_backup_role, init),
|
||||
}
|
||||
}
|
||||
|
||||
/// Loads a worklet module into every worklet thread.
|
||||
/// If all of the threads load successfully, the promise is resolved.
|
||||
/// If any of the threads fails to load, the promise is rejected.
|
||||
/// https://drafts.css-houdini.org/worklets/#fetch-and-invoke-a-worklet-script
|
||||
fn fetch_and_invoke_a_worklet_script(&self,
|
||||
pipeline_id: PipelineId,
|
||||
worklet_id: WorkletId,
|
||||
global_type: WorkletGlobalScopeType,
|
||||
origin: ImmutableOrigin,
|
||||
base_url: ServoUrl,
|
||||
script_url: ServoUrl,
|
||||
credentials: RequestCredentials,
|
||||
pending_tasks_struct: PendingTasksStruct,
|
||||
promise: &Rc<Promise>)
|
||||
{
|
||||
// Send each thread a control message asking it to load the script.
|
||||
for sender in &[&self.control_sender_0, &self.control_sender_1, &self.control_sender_2] {
|
||||
let _ = sender.send(WorkletControl::FetchAndInvokeAWorkletScript {
|
||||
pipeline_id: pipeline_id,
|
||||
worklet_id: worklet_id,
|
||||
global_type: global_type,
|
||||
origin: origin.clone(),
|
||||
base_url: base_url.clone(),
|
||||
script_url: script_url.clone(),
|
||||
credentials: credentials,
|
||||
pending_tasks_struct: pending_tasks_struct.clone(),
|
||||
promise: TrustedPromise::new(promise.clone()),
|
||||
});
|
||||
}
|
||||
// If any of the threads are blocked waiting on data, wake them up.
|
||||
let _ = self.cold_backup_sender.send(WorkletData::WakeUp);
|
||||
let _ = self.hot_backup_sender.send(WorkletData::WakeUp);
|
||||
let _ = self.primary_sender.send(WorkletData::WakeUp);
|
||||
}
|
||||
|
||||
/// For testing.
|
||||
pub fn test_worklet_lookup(&self, id: WorkletId, key: String) -> Option<String> {
|
||||
let (sender, receiver) = mpsc::channel();
|
||||
let msg = WorkletData::Task(id, WorkletTask::Test(TestWorkletTask::Lookup(key, sender)));
|
||||
let _ = self.primary_sender.send(msg);
|
||||
receiver.recv().expect("Test worklet has died?")
|
||||
}
|
||||
}
|
||||
|
||||
/// The data messages sent to worklet threads
|
||||
enum WorkletData {
|
||||
Task(WorkletId, WorkletTask),
|
||||
StartSwapRoles(Sender<WorkletData>),
|
||||
FinishSwapRoles(Swapper<WorkletThreadRole>),
|
||||
WakeUp,
|
||||
Quit,
|
||||
}
|
||||
|
||||
/// The control message sent to worklet threads
|
||||
enum WorkletControl {
|
||||
FetchAndInvokeAWorkletScript {
|
||||
pipeline_id: PipelineId,
|
||||
worklet_id: WorkletId,
|
||||
global_type: WorkletGlobalScopeType,
|
||||
origin: ImmutableOrigin,
|
||||
base_url: ServoUrl,
|
||||
script_url: ServoUrl,
|
||||
credentials: RequestCredentials,
|
||||
pending_tasks_struct: PendingTasksStruct,
|
||||
promise: TrustedPromise,
|
||||
},
|
||||
}
|
||||
|
||||
/// A role that a worklet thread can be playing.
|
||||
///
|
||||
/// These roles are used as tokens or capabilities, we track unique
|
||||
/// ownership using Rust's types, and use atomic swapping to exchange
|
||||
/// them between worklet threads. This ensures that each thread pool has
|
||||
/// exactly one primary, one hot backup and one cold backup.
|
||||
struct WorkletThreadRole {
|
||||
receiver: Receiver<WorkletData>,
|
||||
sender: Sender<WorkletData>,
|
||||
is_hot_backup: bool,
|
||||
is_cold_backup: bool,
|
||||
}
|
||||
|
||||
impl WorkletThreadRole {
|
||||
fn new(is_hot_backup: bool, is_cold_backup: bool) -> WorkletThreadRole {
|
||||
let (sender, receiver) = mpsc::channel();
|
||||
WorkletThreadRole {
|
||||
sender: sender,
|
||||
receiver: receiver,
|
||||
is_hot_backup: is_hot_backup,
|
||||
is_cold_backup: is_cold_backup,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Data to initialize a worklet thread.
|
||||
#[derive(Clone)]
|
||||
struct WorkletThreadInit {
|
||||
/// Senders
|
||||
hot_backup_sender: Sender<WorkletData>,
|
||||
cold_backup_sender: Sender<WorkletData>,
|
||||
script_sender: Sender<MainThreadScriptMsg>,
|
||||
|
||||
/// Data for initializing new worklet global scopes
|
||||
global_init: WorkletGlobalScopeInit,
|
||||
}
|
||||
|
||||
/// A thread for executing worklets.
|
||||
#[must_root]
|
||||
struct WorkletThread {
|
||||
/// Which role the thread is currently playing
|
||||
role: WorkletThreadRole,
|
||||
|
||||
/// The thread's receiver for control messages
|
||||
control_receiver: Receiver<WorkletControl>,
|
||||
|
||||
/// Senders
|
||||
hot_backup_sender: Sender<WorkletData>,
|
||||
cold_backup_sender: Sender<WorkletData>,
|
||||
script_sender: Sender<MainThreadScriptMsg>,
|
||||
|
||||
/// Data for initializing new worklet global scopes
|
||||
global_init: WorkletGlobalScopeInit,
|
||||
|
||||
/// The global scopes created by this thread
|
||||
global_scopes: HashMap<WorkletId, JS<WorkletGlobalScope>>,
|
||||
|
||||
/// A one-place buffer for control messages
|
||||
control_buffer: Option<WorkletControl>,
|
||||
|
||||
/// The JS runtime
|
||||
runtime: Runtime,
|
||||
should_gc: bool,
|
||||
gc_threshold: u32,
|
||||
}
|
||||
|
||||
#[allow(unsafe_code)]
|
||||
unsafe impl JSTraceable for WorkletThread {
|
||||
unsafe fn trace(&self, trc: *mut JSTracer) {
|
||||
debug!("Tracing worklet thread.");
|
||||
self.global_scopes.trace(trc);
|
||||
}
|
||||
}
|
||||
|
||||
impl WorkletThread {
|
||||
/// Spawn a new worklet thread, returning the channel to send it control messages.
|
||||
#[allow(unsafe_code)]
|
||||
#[allow(unrooted_must_root)]
|
||||
fn spawn(role: WorkletThreadRole, init: WorkletThreadInit) -> Sender<WorkletControl> {
|
||||
let (control_sender, control_receiver) = mpsc::channel();
|
||||
// TODO: name this thread
|
||||
thread::spawn(move || {
|
||||
// TODO: add a new IN_WORKLET thread state?
|
||||
// TODO: set interrupt handler?
|
||||
// TODO: configure the JS runtime (e.g. discourage GC, encourage agressive JIT)
|
||||
debug!("Initializing worklet thread.");
|
||||
thread_state::initialize(thread_state::SCRIPT | thread_state::IN_WORKER);
|
||||
let roots = RootCollection::new();
|
||||
let _stack_roots_tls = StackRootTLS::new(&roots);
|
||||
let mut thread = RootedTraceableBox::new(WorkletThread {
|
||||
role: role,
|
||||
control_receiver: control_receiver,
|
||||
hot_backup_sender: init.hot_backup_sender,
|
||||
cold_backup_sender: init.cold_backup_sender,
|
||||
script_sender: init.script_sender,
|
||||
global_init: init.global_init,
|
||||
global_scopes: HashMap::new(),
|
||||
control_buffer: None,
|
||||
runtime: unsafe { new_rt_and_cx() },
|
||||
should_gc: false,
|
||||
gc_threshold: MIN_GC_THRESHOLD,
|
||||
});
|
||||
thread.run();
|
||||
});
|
||||
control_sender
|
||||
}
|
||||
|
||||
/// The main event loop for a worklet thread
|
||||
fn run(&mut self) {
|
||||
loop {
|
||||
// The handler for data messages
|
||||
let message = self.role.receiver.recv().unwrap();
|
||||
match message {
|
||||
// The whole point of this thread pool is to perform tasks!
|
||||
WorkletData::Task(id, task) => {
|
||||
self.perform_a_worklet_task(id, task);
|
||||
}
|
||||
// To start swapping roles, get ready to perform an atomic swap,
|
||||
// and block waiting for the other end to finish it.
|
||||
// NOTE: the cold backup can block on the primary or the hot backup;
|
||||
// the hot backup can block on the primary;
|
||||
// the primary can block on nothing;
|
||||
// this total ordering on thread roles is what guarantees deadlock-freedom.
|
||||
WorkletData::StartSwapRoles(sender) => {
|
||||
let (our_swapper, their_swapper) = swapper();
|
||||
sender.send(WorkletData::FinishSwapRoles(their_swapper)).unwrap();
|
||||
let _ = our_swapper.swap(&mut self.role);
|
||||
}
|
||||
// To finish swapping roles, perform the atomic swap.
|
||||
// The other end should have already started the swap, so this shouldn't block.
|
||||
WorkletData::FinishSwapRoles(swapper) => {
|
||||
let _ = swapper.swap(&mut self.role);
|
||||
}
|
||||
// Wake up! There may be control messages to process.
|
||||
WorkletData::WakeUp => {
|
||||
}
|
||||
// Quit!
|
||||
WorkletData::Quit => {
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Only process control messages if we're the cold backup,
|
||||
// otherwise if there are outstanding control messages,
|
||||
// try to become the cold backup.
|
||||
if self.role.is_cold_backup {
|
||||
if let Some(control) = self.control_buffer.take() {
|
||||
self.process_control(control);
|
||||
}
|
||||
while let Ok(control) = self.control_receiver.try_recv() {
|
||||
self.process_control(control);
|
||||
}
|
||||
self.gc();
|
||||
} else if self.control_buffer.is_none() {
|
||||
if let Ok(control) = self.control_receiver.try_recv() {
|
||||
self.control_buffer = Some(control);
|
||||
let msg = WorkletData::StartSwapRoles(self.role.sender.clone());
|
||||
let _ = self.cold_backup_sender.send(msg);
|
||||
}
|
||||
}
|
||||
// If we are tight on memory, and we're a backup then perform a gc.
|
||||
// If we are tight on memory, and we're the primary then try to become the hot backup.
|
||||
// Hopefully this happens soon!
|
||||
if self.current_memory_usage() > self.gc_threshold {
|
||||
if self.role.is_hot_backup || self.role.is_cold_backup {
|
||||
self.should_gc = false;
|
||||
self.gc();
|
||||
} else if !self.should_gc {
|
||||
self.should_gc = true;
|
||||
let msg = WorkletData::StartSwapRoles(self.role.sender.clone());
|
||||
let _ = self.hot_backup_sender.send(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The current memory usage of the thread
|
||||
#[allow(unsafe_code)]
|
||||
fn current_memory_usage(&self) -> u32 {
|
||||
unsafe { JS_GetGCParameter(self.runtime.rt(), JSGCParamKey::JSGC_BYTES) }
|
||||
}
|
||||
|
||||
/// Perform a GC.
|
||||
#[allow(unsafe_code)]
|
||||
fn gc(&mut self) {
|
||||
debug!("BEGIN GC (usage = {}, threshold = {}).", self.current_memory_usage(), self.gc_threshold);
|
||||
unsafe { JS_GC(self.runtime.rt()) };
|
||||
self.gc_threshold = max(MIN_GC_THRESHOLD, self.current_memory_usage() * 2);
|
||||
debug!("END GC (usage = {}, threshold = {}).", self.current_memory_usage(), self.gc_threshold);
|
||||
}
|
||||
|
||||
/// Get the worklet global scope for a given worklet.
|
||||
/// Creates the worklet global scope if it doesn't exist.
|
||||
fn get_worklet_global_scope(&mut self,
|
||||
pipeline_id: PipelineId,
|
||||
worklet_id: WorkletId,
|
||||
global_type: WorkletGlobalScopeType,
|
||||
base_url: ServoUrl)
|
||||
-> Root<WorkletGlobalScope>
|
||||
{
|
||||
match self.global_scopes.entry(worklet_id) {
|
||||
hash_map::Entry::Occupied(entry) => Root::from_ref(entry.get()),
|
||||
hash_map::Entry::Vacant(entry) => {
|
||||
let result = global_type.new(&self.runtime, pipeline_id, base_url, &self.global_init);
|
||||
entry.insert(JS::from_ref(&*result));
|
||||
result
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Fetch and invoke a worklet script.
|
||||
/// https://drafts.css-houdini.org/worklets/#fetch-and-invoke-a-worklet-script
|
||||
fn fetch_and_invoke_a_worklet_script(&self,
|
||||
global_scope: &WorkletGlobalScope,
|
||||
origin: ImmutableOrigin,
|
||||
script_url: ServoUrl,
|
||||
credentials: RequestCredentials,
|
||||
pending_tasks_struct: PendingTasksStruct,
|
||||
promise: TrustedPromise)
|
||||
{
|
||||
debug!("Fetching from {}.", script_url);
|
||||
// Step 1.
|
||||
// TODO: Settings object?
|
||||
|
||||
// Step 2.
|
||||
// TODO: Fetch a module graph, not just a single script.
|
||||
// TODO: Fetch the script asynchronously?
|
||||
// TODO: Caching.
|
||||
// TODO: Avoid re-parsing the origin as a URL.
|
||||
let resource_fetcher = self.global_init.resource_threads.sender();
|
||||
let origin_url = ServoUrl::parse(&*origin.unicode_serialization()).expect("Failed to parse origin as URL.");
|
||||
let request = RequestInit {
|
||||
url: script_url,
|
||||
type_: RequestType::Script,
|
||||
destination: Destination::Script,
|
||||
mode: RequestMode::CorsMode,
|
||||
origin: origin_url,
|
||||
credentials_mode: credentials.into(),
|
||||
.. RequestInit::default()
|
||||
};
|
||||
let script = load_whole_resource(request, &resource_fetcher).ok()
|
||||
.and_then(|(_, bytes)| String::from_utf8(bytes).ok());
|
||||
|
||||
// Step 4.
|
||||
// NOTE: the spec parses and executes the script in separate steps,
|
||||
// but our JS API doesn't separate these, so we do the steps out of order.
|
||||
let ok = script.map(|script| global_scope.evaluate_js(&*script)).unwrap_or(false);
|
||||
|
||||
if !ok {
|
||||
// Step 3.
|
||||
debug!("Failed to load script.");
|
||||
let old_counter = pending_tasks_struct.set_counter_to(-1);
|
||||
if old_counter > 0 {
|
||||
self.run_in_script_thread(promise.reject_runnable(Error::Abort));
|
||||
}
|
||||
} else {
|
||||
// Step 5.
|
||||
debug!("Finished adding script.");
|
||||
let old_counter = pending_tasks_struct.decrement_counter_by(1);
|
||||
if old_counter == 1 {
|
||||
// TODO: trigger a reflow?
|
||||
self.run_in_script_thread(promise.resolve_runnable(()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Perform a task.
|
||||
fn perform_a_worklet_task(&self, worklet_id: WorkletId, task: WorkletTask) {
|
||||
match self.global_scopes.get(&worklet_id) {
|
||||
Some(global) => global.perform_a_worklet_task(task),
|
||||
None => return warn!("No such worklet as {:?}.", worklet_id),
|
||||
}
|
||||
}
|
||||
|
||||
/// Process a control message.
|
||||
fn process_control(&mut self, control: WorkletControl) {
|
||||
match control {
|
||||
WorkletControl::FetchAndInvokeAWorkletScript {
|
||||
pipeline_id, worklet_id, global_type, origin, base_url,
|
||||
script_url, credentials, pending_tasks_struct, promise,
|
||||
} => {
|
||||
let global = self.get_worklet_global_scope(pipeline_id,
|
||||
worklet_id,
|
||||
global_type,
|
||||
base_url);
|
||||
self.fetch_and_invoke_a_worklet_script(&*global,
|
||||
origin,
|
||||
script_url,
|
||||
credentials,
|
||||
pending_tasks_struct,
|
||||
promise)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Run a runnable in the main script thread.
|
||||
fn run_in_script_thread<R>(&self, runnable: R) where
|
||||
R: 'static + Send + Runnable,
|
||||
{
|
||||
let msg = CommonScriptMsg::RunnableMsg(ScriptThreadEventCategory::WorkletEvent, box runnable);
|
||||
let msg = MainThreadScriptMsg::Common(msg);
|
||||
self.script_sender.send(msg).expect("Worklet thread outlived script thread.");
|
||||
}
|
||||
}
|
143
components/script/dom/workletglobalscope.rs
Normal file
143
components/script/dom/workletglobalscope.rs
Normal file
|
@ -0,0 +1,143 @@
|
|||
/* 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/. */
|
||||
|
||||
use devtools_traits::ScriptToDevtoolsControlMsg;
|
||||
use dom::bindings::inheritance::Castable;
|
||||
use dom::bindings::js::Root;
|
||||
use dom::globalscope::GlobalScope;
|
||||
use dom::testworkletglobalscope::TestWorkletGlobalScope;
|
||||
use dom::testworkletglobalscope::TestWorkletTask;
|
||||
use dom_struct::dom_struct;
|
||||
use ipc_channel::ipc;
|
||||
use ipc_channel::ipc::IpcSender;
|
||||
use js::jsval::UndefinedValue;
|
||||
use js::rust::Runtime;
|
||||
use microtask::Microtask;
|
||||
use microtask::MicrotaskQueue;
|
||||
use msg::constellation_msg::PipelineId;
|
||||
use net_traits::ResourceThreads;
|
||||
use profile_traits::mem;
|
||||
use profile_traits::time;
|
||||
use script_traits::ScriptMsg;
|
||||
use script_traits::TimerSchedulerMsg;
|
||||
use servo_url::ImmutableOrigin;
|
||||
use servo_url::MutableOrigin;
|
||||
use servo_url::ServoUrl;
|
||||
|
||||
#[dom_struct]
|
||||
/// https://drafts.css-houdini.org/worklets/#workletglobalscope
|
||||
pub struct WorkletGlobalScope {
|
||||
/// The global for this worklet.
|
||||
globalscope: GlobalScope,
|
||||
/// The base URL for this worklet.
|
||||
base_url: ServoUrl,
|
||||
/// The microtask queue for this worklet
|
||||
microtask_queue: MicrotaskQueue,
|
||||
}
|
||||
|
||||
impl WorkletGlobalScope {
|
||||
/// Create a new stack-allocated `WorkletGlobalScope`.
|
||||
pub fn new_inherited(pipeline_id: PipelineId,
|
||||
base_url: ServoUrl,
|
||||
init: &WorkletGlobalScopeInit)
|
||||
-> WorkletGlobalScope {
|
||||
// Any timer events fired on this global are ignored.
|
||||
let (timer_event_chan, _) = ipc::channel().unwrap();
|
||||
WorkletGlobalScope {
|
||||
globalscope: GlobalScope::new_inherited(pipeline_id,
|
||||
init.devtools_chan.clone(),
|
||||
init.mem_profiler_chan.clone(),
|
||||
init.time_profiler_chan.clone(),
|
||||
init.constellation_chan.clone(),
|
||||
init.scheduler_chan.clone(),
|
||||
init.resource_threads.clone(),
|
||||
timer_event_chan,
|
||||
MutableOrigin::new(ImmutableOrigin::new_opaque())),
|
||||
base_url: base_url,
|
||||
microtask_queue: MicrotaskQueue::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Evaluate a JS script in this global.
|
||||
pub fn evaluate_js(&self, script: &str) -> bool {
|
||||
debug!("Evaluating JS.");
|
||||
rooted!(in (self.globalscope.get_cx()) let mut rval = UndefinedValue());
|
||||
self.globalscope.evaluate_js_on_global_with_result(&*script, rval.handle_mut())
|
||||
}
|
||||
|
||||
/// The base URL of this global.
|
||||
pub fn base_url(&self) -> ServoUrl {
|
||||
self.base_url.clone()
|
||||
}
|
||||
|
||||
/// Queue up a microtask to be executed in this global.
|
||||
pub fn enqueue_microtask(&self, job: Microtask) {
|
||||
self.microtask_queue.enqueue(job);
|
||||
}
|
||||
|
||||
/// Perform any queued microtasks.
|
||||
pub fn perform_a_microtask_checkpoint(&self) {
|
||||
self.microtask_queue.checkpoint(|id| {
|
||||
let global = self.upcast::<GlobalScope>();
|
||||
assert_eq!(global.pipeline_id(), id);
|
||||
Some(Root::from_ref(global))
|
||||
});
|
||||
}
|
||||
|
||||
/// Perform a worklet task
|
||||
pub fn perform_a_worklet_task(&self, task: WorkletTask) {
|
||||
match task {
|
||||
WorkletTask::Test(task) => match self.downcast::<TestWorkletGlobalScope>() {
|
||||
Some(global) => global.perform_a_worklet_task(task),
|
||||
None => warn!("This is not a test worklet."),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Resources required by workletglobalscopes
|
||||
#[derive(Clone)]
|
||||
pub struct WorkletGlobalScopeInit {
|
||||
/// Channel to a resource thread
|
||||
pub resource_threads: ResourceThreads,
|
||||
/// Channel to the memory profiler
|
||||
pub mem_profiler_chan: mem::ProfilerChan,
|
||||
/// Channel to the time profiler
|
||||
pub time_profiler_chan: time::ProfilerChan,
|
||||
/// Channel to devtools
|
||||
pub devtools_chan: Option<IpcSender<ScriptToDevtoolsControlMsg>>,
|
||||
/// Messages to send to constellation
|
||||
pub constellation_chan: IpcSender<ScriptMsg>,
|
||||
/// Message to send to the scheduler
|
||||
pub scheduler_chan: IpcSender<TimerSchedulerMsg>,
|
||||
}
|
||||
|
||||
/// https://drafts.css-houdini.org/worklets/#worklet-global-scope-type
|
||||
#[derive(Clone, Copy, Debug, HeapSizeOf, JSTraceable)]
|
||||
pub enum WorkletGlobalScopeType {
|
||||
/// https://drafts.css-houdini.org/worklets/#examples
|
||||
Test,
|
||||
}
|
||||
|
||||
impl WorkletGlobalScopeType {
|
||||
/// Create a new heap-allocated `WorkletGlobalScope`.
|
||||
pub fn new(&self,
|
||||
runtime: &Runtime,
|
||||
pipeline_id: PipelineId,
|
||||
base_url: ServoUrl,
|
||||
init: &WorkletGlobalScopeInit)
|
||||
-> Root<WorkletGlobalScope>
|
||||
{
|
||||
match *self {
|
||||
WorkletGlobalScopeType::Test =>
|
||||
Root::upcast(TestWorkletGlobalScope::new(runtime, pipeline_id, base_url, init)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A task which can be performed in the context of a worklet global.
|
||||
pub enum WorkletTask {
|
||||
Test(TestWorkletTask),
|
||||
}
|
||||
|
|
@ -10,10 +10,12 @@
|
|||
#![feature(nonzero)]
|
||||
#![feature(on_unimplemented)]
|
||||
#![feature(optin_builtin_traits)]
|
||||
#![feature(option_entry)]
|
||||
#![feature(plugin)]
|
||||
#![feature(proc_macro)]
|
||||
#![feature(stmt_expr_attributes)]
|
||||
#![feature(try_from)]
|
||||
#![feature(unboxed_closures)]
|
||||
#![feature(untagged_unions)]
|
||||
|
||||
#![deny(unsafe_code)]
|
||||
|
@ -46,7 +48,7 @@ extern crate encoding;
|
|||
extern crate euclid;
|
||||
extern crate fnv;
|
||||
extern crate gfx_traits;
|
||||
extern crate heapsize;
|
||||
#[macro_use] extern crate heapsize;
|
||||
#[macro_use] extern crate heapsize_derive;
|
||||
#[macro_use] extern crate html5ever;
|
||||
#[macro_use]
|
||||
|
@ -92,6 +94,7 @@ extern crate smallvec;
|
|||
#[macro_use]
|
||||
extern crate style;
|
||||
extern crate style_traits;
|
||||
extern crate swapper;
|
||||
extern crate time;
|
||||
#[cfg(any(target_os = "macos", target_os = "linux", target_os = "windows"))]
|
||||
extern crate tinyfiledialogs;
|
||||
|
|
|
@ -74,6 +74,7 @@ pub enum ScriptThreadEventCategory {
|
|||
UpdateReplacedElement,
|
||||
WebSocketEvent,
|
||||
WorkerEvent,
|
||||
WorkletEvent,
|
||||
ServiceWorkerEvent,
|
||||
EnterFullscreen,
|
||||
ExitFullscreen,
|
||||
|
|
|
@ -56,6 +56,8 @@ use dom::uievent::UIEvent;
|
|||
use dom::window::{ReflowReason, Window};
|
||||
use dom::windowproxy::WindowProxy;
|
||||
use dom::worker::TrustedWorkerAddress;
|
||||
use dom::worklet::WorkletThreadPool;
|
||||
use dom::workletglobalscope::WorkletGlobalScopeInit;
|
||||
use euclid::Rect;
|
||||
use euclid::point::Point2D;
|
||||
use hyper::header::{ContentType, HttpDate, LastModified, Headers};
|
||||
|
@ -490,6 +492,9 @@ pub struct ScriptThread {
|
|||
/// A handle to the webvr thread, if available
|
||||
webvr_thread: Option<IpcSender<WebVRMsg>>,
|
||||
|
||||
/// The worklet thread pool
|
||||
worklet_thread_pool: DOMRefCell<Option<Rc<WorkletThreadPool>>>,
|
||||
|
||||
/// A list of pipelines containing documents that finished loading all their blocking
|
||||
/// resources during a turn of the event loop.
|
||||
docs_with_no_blocking_loads: DOMRefCell<HashSet<JS<Document>>>,
|
||||
|
@ -703,6 +708,24 @@ impl ScriptThread {
|
|||
}))
|
||||
}
|
||||
|
||||
pub fn worklet_thread_pool() -> Rc<WorkletThreadPool> {
|
||||
SCRIPT_THREAD_ROOT.with(|root| {
|
||||
let script_thread = unsafe { &*root.get().unwrap() };
|
||||
script_thread.worklet_thread_pool.borrow_mut().get_or_insert_with(|| {
|
||||
let chan = script_thread.chan.0.clone();
|
||||
let init = WorkletGlobalScopeInit {
|
||||
resource_threads: script_thread.resource_threads.clone(),
|
||||
mem_profiler_chan: script_thread.mem_profiler_chan.clone(),
|
||||
time_profiler_chan: script_thread.time_profiler_chan.clone(),
|
||||
devtools_chan: script_thread.devtools_chan.clone(),
|
||||
constellation_chan: script_thread.constellation_chan.clone(),
|
||||
scheduler_chan: script_thread.scheduler_chan.clone(),
|
||||
};
|
||||
Rc::new(WorkletThreadPool::spawn(chan, init))
|
||||
}).clone()
|
||||
})
|
||||
}
|
||||
|
||||
/// Creates a new script thread.
|
||||
pub fn new(state: InitialScriptState,
|
||||
port: Receiver<MainThreadScriptMsg>,
|
||||
|
@ -782,6 +805,8 @@ impl ScriptThread {
|
|||
|
||||
webvr_thread: state.webvr_thread,
|
||||
|
||||
worklet_thread_pool: Default::default(),
|
||||
|
||||
docs_with_no_blocking_loads: Default::default(),
|
||||
|
||||
transitioning_nodes: Default::default(),
|
||||
|
@ -1065,6 +1090,7 @@ impl ScriptThread {
|
|||
ScriptThreadEventCategory::WebSocketEvent => ProfilerCategory::ScriptWebSocketEvent,
|
||||
ScriptThreadEventCategory::WebVREvent => ProfilerCategory::ScriptWebVREvent,
|
||||
ScriptThreadEventCategory::WorkerEvent => ProfilerCategory::ScriptWorkerEvent,
|
||||
ScriptThreadEventCategory::WorkletEvent => ProfilerCategory::ScriptWorkletEvent,
|
||||
ScriptThreadEventCategory::ServiceWorkerEvent => ProfilerCategory::ScriptServiceWorkerEvent,
|
||||
ScriptThreadEventCategory::EnterFullscreen => ProfilerCategory::ScriptEnterFullscreen,
|
||||
ScriptThreadEventCategory::ExitFullscreen => ProfilerCategory::ScriptExitFullscreen,
|
||||
|
@ -1149,7 +1175,7 @@ impl ScriptThread {
|
|||
// 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()
|
||||
runnable.main_thread_handler(self)
|
||||
}
|
||||
}
|
||||
MainThreadScriptMsg::Common(CommonScriptMsg::CollectReports(reports_chan)) =>
|
||||
|
|
|
@ -10,7 +10,6 @@ name = "script_traits"
|
|||
path = "lib.rs"
|
||||
|
||||
[dependencies]
|
||||
app_units = "0.4"
|
||||
bluetooth_traits = {path = "../bluetooth_traits"}
|
||||
canvas_traits = {path = "../canvas_traits"}
|
||||
cookie = "0.6"
|
||||
|
|
|
@ -67,6 +67,7 @@ WEBIDL_STANDARDS = [
|
|||
"//dom.spec.whatwg.org",
|
||||
"//domparsing.spec.whatwg.org",
|
||||
"//drafts.csswg.org",
|
||||
"//drafts.css-houdini.org",
|
||||
"//drafts.fxtf.org",
|
||||
"//encoding.spec.whatwg.org",
|
||||
"//fetch.spec.whatwg.org",
|
||||
|
|
|
@ -11185,6 +11185,21 @@
|
|||
[
|
||||
{}
|
||||
]
|
||||
],
|
||||
"mozilla/worklets/syntax_error.js": [
|
||||
[
|
||||
{}
|
||||
]
|
||||
],
|
||||
"mozilla/worklets/test_worklet.js": [
|
||||
[
|
||||
{}
|
||||
]
|
||||
],
|
||||
"mozilla/worklets/throw_exception.js": [
|
||||
[
|
||||
{}
|
||||
]
|
||||
]
|
||||
},
|
||||
"testharness": {
|
||||
|
@ -20027,6 +20042,12 @@
|
|||
"/_mozilla/mozilla/windowproxy.html",
|
||||
{}
|
||||
]
|
||||
],
|
||||
"mozilla/worklets/test_worklet.html": [
|
||||
[
|
||||
"/_mozilla/mozilla/worklets/test_worklet.html",
|
||||
{}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
|
@ -25796,7 +25817,7 @@
|
|||
"testharness"
|
||||
],
|
||||
"mozilla/interfaces.html": [
|
||||
"21e18bafdbfe5f3aa0ee71766bdc3b6a7e334226",
|
||||
"49dd9f6ef449813f2ce943d4c9fac351357e5c74",
|
||||
"testharness"
|
||||
],
|
||||
"mozilla/interfaces.js": [
|
||||
|
@ -31714,6 +31735,22 @@
|
|||
"mozilla/windowproxy.html": [
|
||||
"128cd0aa5cf80f60078979039036d32b470b0616",
|
||||
"testharness"
|
||||
],
|
||||
"mozilla/worklets/syntax_error.js": [
|
||||
"f3a9b8c78346507bc0b3190c8000ccf80cc133f6",
|
||||
"support"
|
||||
],
|
||||
"mozilla/worklets/test_worklet.html": [
|
||||
"fe9c93a5307c616f878b6623155e1b04c86dd994",
|
||||
"testharness"
|
||||
],
|
||||
"mozilla/worklets/test_worklet.js": [
|
||||
"9d5f8a07cd62a10f4f5ff93744672e5a6fdbc2b0",
|
||||
"support"
|
||||
],
|
||||
"mozilla/worklets/throw_exception.js": [
|
||||
"ebfdae19db68fed8e69142ef73842ac9921e4463",
|
||||
"support"
|
||||
]
|
||||
},
|
||||
"url_base": "/_mozilla/",
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
[test_worklet.html]
|
||||
type: testharness
|
||||
prefs: [dom.worklet.testing.enabled:true]
|
|
@ -200,6 +200,7 @@ test_interfaces([
|
|||
"WebSocket",
|
||||
"Window",
|
||||
"Worker",
|
||||
"Worklet",
|
||||
"XMLDocument",
|
||||
"XMLHttpRequest",
|
||||
"XMLHttpRequestEventTarget",
|
||||
|
|
1
tests/wpt/mozilla/tests/mozilla/worklets/syntax_error.js
Normal file
1
tests/wpt/mozilla/tests/mozilla/worklets/syntax_error.js
Normal file
|
@ -0,0 +1 @@
|
|||
{];
|
35
tests/wpt/mozilla/tests/mozilla/worklets/test_worklet.html
Normal file
35
tests/wpt/mozilla/tests/mozilla/worklets/test_worklet.html
Normal file
|
@ -0,0 +1,35 @@
|
|||
<!doctype html>
|
||||
<meta charset="utf-8">
|
||||
<title>Test worklet loading</title>
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<script src="/common/get-host-info.sub.js"></script>
|
||||
<script>
|
||||
var testWorklet = new TestWorklet();
|
||||
var host_info = get_host_info();
|
||||
|
||||
promise_test(function() {
|
||||
return testWorklet.addModule("test_worklet.js")
|
||||
.then(function () {
|
||||
assert_equals(testWorklet.lookup("hello"), "world");
|
||||
});
|
||||
}, "Loading a test worklet.");
|
||||
|
||||
promise_test(function(t) {
|
||||
var path = new URL("test_worklet.js", document.location).pathname;
|
||||
var url = new URL(path, host_info.HTTP_REMOTE_ORIGIN);
|
||||
return promise_rejects(t, "AbortError", testWorklet.addModule(url));
|
||||
}, "Loading a cross-origin test worklet.");
|
||||
|
||||
promise_test(function(t) {
|
||||
return promise_rejects(t, "AbortError", testWorklet.addModule("nonexistent_worklet.js"));
|
||||
}, "Loading a nonexistent test worklet.");
|
||||
|
||||
promise_test(function(t) {
|
||||
return promise_rejects(t, "AbortError", testWorklet.addModule("syntax_error.js"));
|
||||
}, "Loading a syntactically incorrect test worklet.");
|
||||
|
||||
promise_test(function(t) {
|
||||
return promise_rejects(t, "AbortError", testWorklet.addModule("throw_exception.js"));
|
||||
}, "Loading an exception-throwing test worklet.");
|
||||
</script>
|
1
tests/wpt/mozilla/tests/mozilla/worklets/test_worklet.js
Normal file
1
tests/wpt/mozilla/tests/mozilla/worklets/test_worklet.js
Normal file
|
@ -0,0 +1 @@
|
|||
registerKeyValue("hello", "world");
|
|
@ -0,0 +1 @@
|
|||
throw new TypeError();
|
Loading…
Add table
Add a link
Reference in a new issue