aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAlan Jeffrey <ajeffrey@mozilla.com>2017-04-03 14:35:57 -0500
committerAlan Jeffrey <ajeffrey@mozilla.com>2017-05-17 09:01:05 -0500
commitaf8436c9be4c69c07265ab1095f89982b48cdd00 (patch)
tree0a4dbfaeb69c7a1741840e94ffb88139317ef295
parentabb2985ffe96485b58f6b9e5f8b2dd3641d987b7 (diff)
downloadservo-af8436c9be4c69c07265ab1095f89982b48cdd00.tar.gz
servo-af8436c9be4c69c07265ab1095f89982b48cdd00.zip
Implemented Houdini worklets.
-rw-r--r--Cargo.lock46
-rw-r--r--components/profile/time.rs1
-rw-r--r--components/profile_traits/time.rs1
-rw-r--r--components/script/Cargo.toml1
-rw-r--r--components/script/dom/bindings/refcounted.rs39
-rw-r--r--components/script/dom/globalscope.rs38
-rw-r--r--components/script/dom/mod.rs4
-rw-r--r--components/script/dom/promise.rs1
-rw-r--r--components/script/dom/testworklet.rs61
-rw-r--r--components/script/dom/testworkletglobalscope.rs66
-rw-r--r--components/script/dom/webidls/Console.webidl2
-rw-r--r--components/script/dom/webidls/EventTarget.webidl2
-rw-r--r--components/script/dom/webidls/GlobalScope.webidl2
-rw-r--r--components/script/dom/webidls/TestWorklet.webidl12
-rw-r--r--components/script/dom/webidls/TestWorkletGlobalScope.webidl11
-rw-r--r--components/script/dom/webidls/VoidFunction.webidl13
-rw-r--r--components/script/dom/webidls/Window.webidl1
-rw-r--r--components/script/dom/webidls/Worklet.webidl13
-rw-r--r--components/script/dom/webidls/WorkletGlobalScope.webidl10
-rw-r--r--components/script/dom/window.rs5
-rw-r--r--components/script/dom/worklet.rs637
-rw-r--r--components/script/dom/workletglobalscope.rs143
-rw-r--r--components/script/lib.rs5
-rw-r--r--components/script/script_runtime.rs1
-rw-r--r--components/script/script_thread.rs28
-rw-r--r--components/script_traits/Cargo.toml1
-rw-r--r--python/tidy/servo_tidy/tidy.py1
-rw-r--r--tests/wpt/mozilla/meta/MANIFEST.json39
-rw-r--r--tests/wpt/mozilla/meta/mozilla/worklets/test_worklet.html.ini3
-rw-r--r--tests/wpt/mozilla/tests/mozilla/interfaces.html1
-rw-r--r--tests/wpt/mozilla/tests/mozilla/worklets/syntax_error.js1
-rw-r--r--tests/wpt/mozilla/tests/mozilla/worklets/test_worklet.html35
-rw-r--r--tests/wpt/mozilla/tests/mozilla/worklets/test_worklet.js1
-rw-r--r--tests/wpt/mozilla/tests/mozilla/worklets/throw_exception.js1
34 files changed, 1209 insertions, 17 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 3610f0cf2cb..d0eec9a4440 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -196,6 +196,11 @@ 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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2143,6 +2148,15 @@ dependencies = [
]
[[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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -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)",
@@ -2739,6 +2753,15 @@ 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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2883,6 +2906,14 @@ dependencies = [
]
[[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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2969,6 +3000,14 @@ dependencies = [
]
[[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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -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"
diff --git a/components/profile/time.rs b/components/profile/time.rs
index 928eca4a263..dfe5c37068d 100644
--- a/components/profile/time.rs
+++ b/components/profile/time.rs
@@ -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)
diff --git a/components/profile_traits/time.rs b/components/profile_traits/time.rs
index 7bbebf5f465..369c2fa366d 100644
--- a/components/profile_traits/time.rs
+++ b/components/profile_traits/time.rs
@@ -89,6 +89,7 @@ pub enum ProfilerCategory {
ScriptEnterFullscreen = 0x77,
ScriptExitFullscreen = 0x78,
ScriptWebVREvent = 0x79,
+ ScriptWorkletEvent = 0x7a,
ApplicationHeartbeat = 0x90,
}
diff --git a/components/script/Cargo.toml b/components/script/Cargo.toml
index 306dd2a748d..388801aa011 100644
--- a/components/script/Cargo.toml
+++ b/components/script/Cargo.toml
@@ -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"]}
diff --git a/components/script/dom/bindings/refcounted.rs b/components/script/dom/bindings/refcounted.rs
index dcf11626543..f22074d5fef 100644
--- a/components/script/dom/bindings/refcounted.rs
+++ b/components/script/dom/bindings/refcounted.rs
@@ -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
diff --git a/components/script/dom/globalscope.rs b/components/script/dom/globalscope.rs
index 931d1f576aa..ce81958a2cb 100644
--- a/components/script/dom/globalscope.rs
+++ b/components/script/dom/globalscope.rs
@@ -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(),
- code.len() as libc::size_t,
- rval) {
- debug!("error evaluating JS string");
- report_pending_exception(cx, true);
- }
+
+ debug!("evaluating JS string");
+ let result = unsafe {
+ Evaluate2(cx, options.ptr, code.as_ptr(),
+ code.len() as libc::size_t,
+ rval)
+ };
+
+ if !result {
+ debug!("error evaluating JS string");
+ 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!();
}
diff --git a/components/script/dom/mod.rs b/components/script/dom/mod.rs
index d155b8b6a47..62bffbdcbcd 100644
--- a/components/script/dom/mod.rs
+++ b/components/script/dom/mod.rs
@@ -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;
diff --git a/components/script/dom/promise.rs b/components/script/dom/promise.rs
index 13689023462..5595e999c9d 100644
--- a/components/script/dom/promise.rs
+++ b/components/script/dom/promise.rs
@@ -296,3 +296,4 @@ fn create_native_handler_function(cx: *mut JSContext,
obj.get()
}
}
+
diff --git a/components/script/dom/testworklet.rs b/components/script/dom/testworklet.rs
new file mode 100644
index 00000000000..ea032e66faa
--- /dev/null
+++ b/components/script/dom/testworklet.rs
@@ -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)
+ }
+}
diff --git a/components/script/dom/testworkletglobalscope.rs b/components/script/dom/testworkletglobalscope.rs
new file mode 100644
index 00000000000..dfd000ac5c1
--- /dev/null
+++ b/components/script/dom/testworkletglobalscope.rs
@@ -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>>),
+}
diff --git a/components/script/dom/webidls/Console.webidl b/components/script/dom/webidls/Console.webidl
index 90f9bb9f58e..7c4c6906a27 100644
--- a/components/script/dom/webidls/Console.webidl
+++ b/components/script/dom/webidls/Console.webidl
@@ -10,7 +10,7 @@
*/
[ClassString="Console",
- Exposed=(Window,Worker),
+ Exposed=(Window,Worker,Worklet),
ProtoObjectHack]
namespace console {
// These should be DOMString message, DOMString message2, ...
diff --git a/components/script/dom/webidls/EventTarget.webidl b/components/script/dom/webidls/EventTarget.webidl
index ee6e5d722a8..ad25712122a 100644
--- a/components/script/dom/webidls/EventTarget.webidl
+++ b/components/script/dom/webidls/EventTarget.webidl
@@ -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,
diff --git a/components/script/dom/webidls/GlobalScope.webidl b/components/script/dom/webidls/GlobalScope.webidl
index 7dab4f3afa7..2681d236dbc 100644
--- a/components/script/dom/webidls/GlobalScope.webidl
+++ b/components/script/dom/webidls/GlobalScope.webidl
@@ -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 {};
diff --git a/components/script/dom/webidls/TestWorklet.webidl b/components/script/dom/webidls/TestWorklet.webidl
new file mode 100644
index 00000000000..c1f1965a1e0
--- /dev/null
+++ b/components/script/dom/webidls/TestWorklet.webidl
@@ -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);
+};
diff --git a/components/script/dom/webidls/TestWorkletGlobalScope.webidl b/components/script/dom/webidls/TestWorkletGlobalScope.webidl
new file mode 100644
index 00000000000..44027ab8dc6
--- /dev/null
+++ b/components/script/dom/webidls/TestWorkletGlobalScope.webidl
@@ -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);
+};
diff --git a/components/script/dom/webidls/VoidFunction.webidl b/components/script/dom/webidls/VoidFunction.webidl
new file mode 100644
index 00000000000..82d4a666c51
--- /dev/null
+++ b/components/script/dom/webidls/VoidFunction.webidl
@@ -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 ();
diff --git a/components/script/dom/webidls/Window.webidl b/components/script/dom/webidls/Window.webidl
index 47c753f43b1..548821ac971 100644
--- a/components/script/dom/webidls/Window.webidl
+++ b/components/script/dom/webidls/Window.webidl
@@ -201,3 +201,4 @@ partial interface Window {
readonly attribute TestRunner testRunner;
//readonly attribute EventSender eventSender;
};
+
diff --git a/components/script/dom/webidls/Worklet.webidl b/components/script/dom/webidls/Worklet.webidl
new file mode 100644
index 00000000000..5bb39bebd96
--- /dev/null
+++ b/components/script/dom/webidls/Worklet.webidl
@@ -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";
+};
diff --git a/components/script/dom/webidls/WorkletGlobalScope.webidl b/components/script/dom/webidls/WorkletGlobalScope.webidl
new file mode 100644
index 00000000000..ca29296a10e
--- /dev/null
+++ b/components/script/dom/webidls/WorkletGlobalScope.webidl
@@ -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 {
+};
diff --git a/components/script/dom/window.rs b/components/script/dom/window.rs
index 65d1a591adc..097f136e29c 100644
--- a/components/script/dom/window.rs
+++ b/components/script/dom/window.rs
@@ -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 {
diff --git a/components/script/dom/worklet.rs b/components/script/dom/worklet.rs
new file mode 100644
index 00000000000..fa5b3950b51
--- /dev/null
+++ b/components/script/dom/worklet.rs
@@ -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.");
+ }
+}
diff --git a/components/script/dom/workletglobalscope.rs b/components/script/dom/workletglobalscope.rs
new file mode 100644
index 00000000000..a2e2463ca27
--- /dev/null
+++ b/components/script/dom/workletglobalscope.rs
@@ -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),
+}
+
diff --git a/components/script/lib.rs b/components/script/lib.rs
index c1bd886b927..703209604b0 100644
--- a/components/script/lib.rs
+++ b/components/script/lib.rs
@@ -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;
diff --git a/components/script/script_runtime.rs b/components/script/script_runtime.rs
index c4f90edf8ea..8c243b18695 100644
--- a/components/script/script_runtime.rs
+++ b/components/script/script_runtime.rs
@@ -74,6 +74,7 @@ pub enum ScriptThreadEventCategory {
UpdateReplacedElement,
WebSocketEvent,
WorkerEvent,
+ WorkletEvent,
ServiceWorkerEvent,
EnterFullscreen,
ExitFullscreen,
diff --git a/components/script/script_thread.rs b/components/script/script_thread.rs
index a8b20b6cf98..8a6efa946ad 100644
--- a/components/script/script_thread.rs
+++ b/components/script/script_thread.rs
@@ -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)) =>
diff --git a/components/script_traits/Cargo.toml b/components/script_traits/Cargo.toml
index da5fcc78704..993c14c43b0 100644
--- a/components/script_traits/Cargo.toml
+++ b/components/script_traits/Cargo.toml
@@ -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"
diff --git a/python/tidy/servo_tidy/tidy.py b/python/tidy/servo_tidy/tidy.py
index ccca233db9c..b8229e67d96 100644
--- a/python/tidy/servo_tidy/tidy.py
+++ b/python/tidy/servo_tidy/tidy.py
@@ -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",
diff --git a/tests/wpt/mozilla/meta/MANIFEST.json b/tests/wpt/mozilla/meta/MANIFEST.json
index ce611aee8f1..c94175fdce8 100644
--- a/tests/wpt/mozilla/meta/MANIFEST.json
+++ b/tests/wpt/mozilla/meta/MANIFEST.json
@@ -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/",
diff --git a/tests/wpt/mozilla/meta/mozilla/worklets/test_worklet.html.ini b/tests/wpt/mozilla/meta/mozilla/worklets/test_worklet.html.ini
new file mode 100644
index 00000000000..37762a513dd
--- /dev/null
+++ b/tests/wpt/mozilla/meta/mozilla/worklets/test_worklet.html.ini
@@ -0,0 +1,3 @@
+[test_worklet.html]
+ type: testharness
+ prefs: [dom.worklet.testing.enabled:true]
diff --git a/tests/wpt/mozilla/tests/mozilla/interfaces.html b/tests/wpt/mozilla/tests/mozilla/interfaces.html
index a29f439a7c6..ee987ae9322 100644
--- a/tests/wpt/mozilla/tests/mozilla/interfaces.html
+++ b/tests/wpt/mozilla/tests/mozilla/interfaces.html
@@ -200,6 +200,7 @@ test_interfaces([
"WebSocket",
"Window",
"Worker",
+ "Worklet",
"XMLDocument",
"XMLHttpRequest",
"XMLHttpRequestEventTarget",
diff --git a/tests/wpt/mozilla/tests/mozilla/worklets/syntax_error.js b/tests/wpt/mozilla/tests/mozilla/worklets/syntax_error.js
new file mode 100644
index 00000000000..4adade8939c
--- /dev/null
+++ b/tests/wpt/mozilla/tests/mozilla/worklets/syntax_error.js
@@ -0,0 +1 @@
+{];
diff --git a/tests/wpt/mozilla/tests/mozilla/worklets/test_worklet.html b/tests/wpt/mozilla/tests/mozilla/worklets/test_worklet.html
new file mode 100644
index 00000000000..d7a9efa04fe
--- /dev/null
+++ b/tests/wpt/mozilla/tests/mozilla/worklets/test_worklet.html
@@ -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>
diff --git a/tests/wpt/mozilla/tests/mozilla/worklets/test_worklet.js b/tests/wpt/mozilla/tests/mozilla/worklets/test_worklet.js
new file mode 100644
index 00000000000..9c0b392a6ab
--- /dev/null
+++ b/tests/wpt/mozilla/tests/mozilla/worklets/test_worklet.js
@@ -0,0 +1 @@
+registerKeyValue("hello", "world");
diff --git a/tests/wpt/mozilla/tests/mozilla/worklets/throw_exception.js b/tests/wpt/mozilla/tests/mozilla/worklets/throw_exception.js
new file mode 100644
index 00000000000..6ca4f80fc27
--- /dev/null
+++ b/tests/wpt/mozilla/tests/mozilla/worklets/throw_exception.js
@@ -0,0 +1 @@
+throw new TypeError();