1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-11-21 15:04:11 -05:00

feat(runtime): support classic workers for internal testing (#11338)

This commit implements classic workers, but only when the `--enable-testing-features-do-not-use` flag is provided. This change is not user facing. Classic workers are used extensively in WPT tests. The classic workers do not support loading from disk, and do not support TypeScript.

Co-authored-by: Luca Casonato <hello@lcas.dev>
This commit is contained in:
Andreu Botella 2021-08-16 14:29:54 +02:00 committed by GitHub
parent d1d2388d7f
commit ddbb7b83f2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 485 additions and 15 deletions

View file

@ -109,6 +109,7 @@ fn create_web_worker_callback(
.log_level
.map_or(false, |l| l == log::Level::Debug),
unstable: program_state.flags.unstable,
enable_testing_features: program_state.flags.enable_testing_features,
unsafely_ignore_certificate_errors: program_state
.flags
.unsafely_ignore_certificate_errors
@ -120,6 +121,7 @@ fn create_web_worker_callback(
create_web_worker_cb,
js_error_create_fn: Some(js_error_create_fn),
use_deno_namespace: args.use_deno_namespace,
worker_type: args.worker_type,
maybe_inspector_server,
runtime_version: version::deno(),
ts_version: version::TYPESCRIPT.to_string(),
@ -193,6 +195,7 @@ pub fn create_main_worker(
.log_level
.map_or(false, |l| l == log::Level::Debug),
unstable: program_state.flags.unstable,
enable_testing_features: program_state.flags.enable_testing_features,
unsafely_ignore_certificate_errors: program_state
.flags
.unsafely_ignore_certificate_errors

View file

@ -253,6 +253,7 @@ pub async fn run(
debug_flag: metadata.log_level.map_or(false, |l| l == log::Level::Debug),
user_agent: version::get_user_agent(),
unstable: metadata.unstable,
enable_testing_features: false,
unsafely_ignore_certificate_errors: metadata
.unsafely_ignore_certificate_errors,
root_cert_store: Some(root_cert_store),

View file

@ -52,7 +52,9 @@ use tokio::sync::mpsc;
use tokio_stream::wrappers::ReceiverStream;
use tokio_util::io::StreamReader;
pub use reqwest; // Re-export reqwest
// Re-export reqwest and data_url
pub use data_url;
pub use reqwest;
pub fn init<P: FetchPermissions + 'static>(
user_agent: String,

View file

@ -27,6 +27,7 @@ async fn main() -> Result<(), AnyError> {
args: vec![],
debug_flag: false,
unstable: false,
enable_testing_features: false,
unsafely_ignore_certificate_errors: None,
root_cert_store: None,
user_agent: "hello_runtime".to_string(),

View file

@ -7,7 +7,6 @@
ArrayIsArray,
ArrayPrototypeMap,
Error,
Uint8Array,
StringPrototypeStartsWith,
String,
SymbolIterator,
@ -28,6 +27,7 @@
useDenoNamespace,
permissions,
name,
workerType,
) {
return core.opSync("op_create_worker", {
hasSourceCode,
@ -36,6 +36,7 @@
sourceCode,
specifier,
useDenoNamespace,
workerType,
});
}
@ -183,20 +184,12 @@
}
}
if (type !== "module") {
throw new Error(
'Not yet implemented: only "module" type workers are supported',
);
}
this.#name = name;
const hasSourceCode = false;
const sourceCode = core.decode(new Uint8Array());
const workerType = webidl.converters["WorkerType"](type);
if (
StringPrototypeStartsWith(specifier, "./") ||
StringPrototypeStartsWith(specifier, "../") ||
StringPrototypeStartsWith(specifier, "/") || type == "classic"
StringPrototypeStartsWith(specifier, "/") || workerType === "classic"
) {
const baseUrl = getLocationHref();
if (baseUrl != null) {
@ -204,6 +197,16 @@
}
}
this.#name = name;
let hasSourceCode, sourceCode;
if (workerType === "classic") {
hasSourceCode = true;
sourceCode = `importScripts("#");`;
} else {
hasSourceCode = false;
sourceCode = "";
}
const id = createWorker(
specifier,
hasSourceCode,
@ -213,6 +216,7 @@
? null
: parsePermissions(workerDenoAttributes.permissions),
options?.name,
workerType,
);
this.#id = id;
this.#pollControl();
@ -344,6 +348,11 @@
defineEventHandler(Worker.prototype, "message");
defineEventHandler(Worker.prototype, "messageerror");
webidl.converters["WorkerType"] = webidl.createEnumConverter("WorkerType", [
"classic",
"module",
]);
window.__bootstrap.worker = {
parsePermissions,
Worker,

View file

@ -8,6 +8,7 @@ delete Object.prototype.__proto__;
((window) => {
const core = Deno.core;
const {
ArrayPrototypeMap,
Error,
FunctionPrototypeCall,
FunctionPrototypeBind,
@ -164,6 +165,44 @@ delete Object.prototype.__proto__;
}
}
let loadedMainWorkerScript = false;
function importScripts(...urls) {
if (core.opSync("op_worker_get_type") === "module") {
throw new TypeError("Can't import scripts in a module worker.");
}
const baseUrl = location.getLocationHref();
const parsedUrls = ArrayPrototypeMap(urls, (scriptUrl) => {
try {
return new url.URL(scriptUrl, baseUrl ?? undefined).href;
} catch {
throw new domException.DOMException(
"Failed to parse URL.",
"SyntaxError",
);
}
});
// A classic worker's main script has looser MIME type checks than any
// imported scripts, so we use `loadedMainWorkerScript` to distinguish them.
// TODO(andreubotella) Refactor worker creation so the main script isn't
// loaded with `importScripts()`.
const scripts = core.opSync(
"op_worker_sync_fetch",
parsedUrls,
!loadedMainWorkerScript,
);
loadedMainWorkerScript = true;
for (const { url, script } of scripts) {
const err = core.evalContext(script, url)[1];
if (err !== null) {
throw err.thrown;
}
}
}
function opMainModule() {
return core.opSync("op_main_module");
}
@ -597,6 +636,13 @@ delete Object.prototype.__proto__;
}
ObjectDefineProperties(globalThis, workerRuntimeGlobalProperties);
ObjectDefineProperties(globalThis, { name: util.readOnly(name) });
if (runtimeOptions.enableTestingFeaturesFlag) {
ObjectDefineProperty(
globalThis,
"importScripts",
util.writable(importScripts),
);
}
ObjectSetPrototypeOf(globalThis, DedicatedWorkerGlobalScope.prototype);
const consoleFromDeno = globalThis.console;

View file

@ -84,3 +84,5 @@ pub fn check_unstable2(state: &Rc<RefCell<OpState>>, api_name: &str) {
let state = state.borrow();
state.borrow::<UnstableChecker>().check_unstable(api_name)
}
pub struct TestingFeaturesEnabled(pub bool);

View file

@ -1,6 +1,9 @@
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
mod sync_fetch;
use crate::web_worker::WebWorkerInternalHandle;
use crate::web_worker::WebWorkerType;
use crate::web_worker::WorkerControlEvent;
use deno_core::error::generic_error;
use deno_core::error::AnyError;
@ -13,6 +16,8 @@ use deno_web::JsMessageData;
use std::cell::RefCell;
use std::rc::Rc;
use self::sync_fetch::op_worker_sync_fetch;
pub fn init() -> Extension {
Extension::builder()
.ops(vec![
@ -25,6 +30,8 @@ pub fn init() -> Extension {
"op_worker_unhandled_error",
op_sync(op_worker_unhandled_error),
),
("op_worker_get_type", op_sync(op_worker_get_type)),
("op_worker_sync_fetch", op_sync(op_worker_sync_fetch)),
])
.build()
}
@ -79,3 +86,12 @@ fn op_worker_unhandled_error(
.expect("Failed to propagate error event to parent worker");
Ok(())
}
fn op_worker_get_type(
state: &mut OpState,
_: (),
_: (),
) -> Result<WebWorkerType, AnyError> {
let handle = state.borrow::<WebWorkerInternalHandle>().clone();
Ok(handle.worker_type)
}

View file

@ -0,0 +1,155 @@
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
use crate::web_worker::WebWorkerInternalHandle;
use crate::web_worker::WebWorkerType;
use deno_core::error::type_error;
use deno_core::error::AnyError;
use deno_core::url::Url;
use deno_core::OpState;
use deno_fetch::data_url::DataUrl;
use deno_fetch::reqwest;
use deno_web::BlobStore;
use deno_websocket::DomExceptionNetworkError;
use hyper::body::Bytes;
use serde::{Deserialize, Serialize};
use tokio::task::JoinHandle;
// TODO(andreubotella) Properly parse the MIME type
fn mime_type_essence(mime_type: &str) -> String {
let essence = match mime_type.split_once(";") {
Some((essence, _)) => essence,
None => mime_type,
};
essence.trim().to_ascii_lowercase()
}
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SyncFetchScript {
url: String,
script: String,
}
pub fn op_worker_sync_fetch(
state: &mut OpState,
scripts: Vec<String>,
mut loose_mime_checks: bool,
) -> Result<Vec<SyncFetchScript>, AnyError> {
let handle = state.borrow::<WebWorkerInternalHandle>().clone();
assert_eq!(handle.worker_type, WebWorkerType::Classic);
// TODO(andreubotella) Make the runtime into a resource and add a new op to
// block on each request, so a script can run while the next loads.
let runtime = tokio::runtime::Builder::new_current_thread()
.enable_io()
.enable_time()
.build()
.unwrap();
// TODO(andreubotella) It's not good to throw an exception related to blob
// URLs when none of the script URLs use the blob scheme.
// Also, in which contexts are blob URLs not supported?
let blob_store = state.try_borrow::<BlobStore>().ok_or_else(|| {
type_error("Blob URLs are not supported in this context.")
})?;
let handles: Vec<_> = scripts
.into_iter()
.map(|script| -> JoinHandle<Result<SyncFetchScript, AnyError>> {
let blob_store = blob_store.clone();
runtime.spawn(async move {
let script_url =
Url::parse(&script).map_err(|_| type_error("Invalid script URL"))?;
let (body, mime_type, res_url) = match script_url.scheme() {
"http" | "https" => {
let resp = reqwest::get(script_url).await?.error_for_status()?;
let res_url = resp.url().to_string();
// TODO(andreubotella) Properly run fetch's "extract a MIME type".
let mime_type = resp
.headers()
.get("Content-Type")
.and_then(|v| v.to_str().ok())
.map(mime_type_essence);
// Always check the MIME type with HTTP(S).
loose_mime_checks = false;
let body = resp.bytes().await?;
(body, mime_type, res_url)
}
"data" => {
let data_url = DataUrl::process(&script)
.map_err(|e| type_error(format!("{:?}", e)))?;
let mime_type = {
let mime = data_url.mime_type();
format!("{}/{}", mime.type_, mime.subtype)
};
let (body, _) = data_url
.decode_to_vec()
.map_err(|e| type_error(format!("{:?}", e)))?;
(Bytes::from(body), Some(mime_type), script)
}
"blob" => {
let blob = blob_store
.get_object_url(script_url)?
.ok_or_else(|| type_error("Blob for the given URL not found."))?;
let mime_type = mime_type_essence(&blob.media_type);
let body = blob.read_all().await?;
(Bytes::from(body), Some(mime_type), script)
}
_ => {
return Err(type_error(format!(
"Classic scripts with scheme {}: are not supported in workers.",
script_url.scheme()
)))
}
};
if !loose_mime_checks {
// TODO(andreubotella) Check properly for a Javascript MIME type.
match mime_type.as_deref() {
Some("application/javascript" | "text/javascript") => {}
Some(mime_type) => {
return Err(
DomExceptionNetworkError {
msg: format!("Invalid MIME type {:?}.", mime_type),
}
.into(),
)
}
None => {
return Err(
DomExceptionNetworkError::new("Missing MIME type.").into(),
)
}
}
}
let (text, _) = encoding_rs::UTF_8.decode_with_bom_removal(&body);
Ok(SyncFetchScript {
url: res_url,
script: text.into_owned(),
})
})
})
.collect();
let mut ret = Vec::with_capacity(handles.len());
for handle in handles {
let script = runtime.block_on(handle)??;
ret.push(script);
}
Ok(ret)
}

View file

@ -1,5 +1,6 @@
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
use crate::ops::TestingFeaturesEnabled;
use crate::permissions::resolve_read_allowlist;
use crate::permissions::resolve_write_allowlist;
use crate::permissions::EnvDescriptor;
@ -16,6 +17,7 @@ use crate::web_worker::run_web_worker;
use crate::web_worker::SendableWebWorkerHandle;
use crate::web_worker::WebWorker;
use crate::web_worker::WebWorkerHandle;
use crate::web_worker::WebWorkerType;
use crate::web_worker::WorkerControlEvent;
use crate::web_worker::WorkerId;
use deno_core::error::custom_error;
@ -48,6 +50,7 @@ pub struct CreateWebWorkerArgs {
pub permissions: Permissions,
pub main_module: ModuleSpecifier,
pub use_deno_namespace: bool,
pub worker_type: WebWorkerType,
}
pub type CreateWebWorkerCb = dyn Fn(CreateWebWorkerArgs) -> (WebWorker, SendableWebWorkerHandle)
@ -460,6 +463,7 @@ pub struct CreateWorkerArgs {
source_code: String,
specifier: String,
use_deno_namespace: bool,
worker_type: WebWorkerType,
}
/// Create worker as the host
@ -479,6 +483,17 @@ fn op_create_worker(
if use_deno_namespace {
super::check_unstable(state, "Worker.deno.namespace");
}
let worker_type = args.worker_type;
if let WebWorkerType::Classic = worker_type {
if let TestingFeaturesEnabled(false) = state.borrow() {
return Err(
deno_webstorage::DomExceptionNotSupportedError::new(
"Classic workers are not supported.",
)
.into(),
);
}
}
let parent_permissions = state.borrow::<Permissions>().clone();
let worker_permissions = if let Some(permissions) = args.permissions {
super::check_unstable(state, "Worker.deno.permissions");
@ -518,6 +533,7 @@ fn op_create_worker(
permissions: worker_permissions,
main_module: module_specifier.clone(),
use_deno_namespace,
worker_type,
});
// Send thread safe handle from newly created worker to host thread

View file

@ -44,6 +44,13 @@ use std::sync::Arc;
use std::task::Context;
use std::task::Poll;
#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum WebWorkerType {
Classic,
Module,
}
#[derive(
Debug, Default, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize,
)]
@ -110,6 +117,7 @@ pub struct WebWorkerInternalHandle {
pub cancel: Rc<CancelHandle>,
terminated: Arc<AtomicBool>,
isolate_handle: v8::IsolateHandle,
pub worker_type: WebWorkerType,
}
impl WebWorkerInternalHandle {
@ -215,6 +223,7 @@ impl WebWorkerHandle {
fn create_handles(
isolate_handle: v8::IsolateHandle,
worker_type: WebWorkerType,
) -> (WebWorkerInternalHandle, SendableWebWorkerHandle) {
let (parent_port, worker_port) = create_entangled_message_port();
let (ctrl_tx, ctrl_rx) = mpsc::channel::<WorkerControlEvent>(1);
@ -225,6 +234,7 @@ fn create_handles(
terminated: terminated.clone(),
isolate_handle: isolate_handle.clone(),
cancel: CancelHandle::new_rc(),
worker_type,
};
let external_handle = SendableWebWorkerHandle {
receiver: ctrl_rx,
@ -245,6 +255,7 @@ pub struct WebWorker {
pub name: String,
internal_handle: WebWorkerInternalHandle,
pub use_deno_namespace: bool,
pub worker_type: WebWorkerType,
pub main_module: ModuleSpecifier,
}
@ -253,6 +264,7 @@ pub struct WebWorkerOptions {
pub args: Vec<String>,
pub debug_flag: bool,
pub unstable: bool,
pub enable_testing_features: bool,
pub unsafely_ignore_certificate_errors: Option<Vec<String>>,
pub root_cert_store: Option<RootCertStore>,
pub user_agent: String,
@ -261,6 +273,7 @@ pub struct WebWorkerOptions {
pub create_web_worker_cb: Arc<ops::worker_host::CreateWebWorkerCb>,
pub js_error_create_fn: Option<Rc<JsErrorCreateFn>>,
pub use_deno_namespace: bool,
pub worker_type: WebWorkerType,
pub maybe_inspector_server: Option<Arc<InspectorServer>>,
pub apply_source_maps: bool,
/// Sets `Deno.version.deno` in JS runtime.
@ -286,10 +299,12 @@ impl WebWorker {
) -> (Self, SendableWebWorkerHandle) {
// Permissions: many ops depend on this
let unstable = options.unstable;
let enable_testing_features = options.enable_testing_features;
let perm_ext = Extension::builder()
.state(move |state| {
state.put::<Permissions>(permissions.clone());
state.put(ops::UnstableChecker { unstable });
state.put(ops::TestingFeaturesEnabled(enable_testing_features));
Ok(())
})
.build();
@ -386,7 +401,8 @@ impl WebWorker {
let (internal_handle, external_handle) = {
let handle = js_runtime.v8_isolate().thread_safe_handle();
let (internal_handle, external_handle) = create_handles(handle);
let (internal_handle, external_handle) =
create_handles(handle, options.worker_type);
let op_state = js_runtime.op_state();
let mut op_state = op_state.borrow_mut();
op_state.put(internal_handle.clone());
@ -400,6 +416,7 @@ impl WebWorker {
name,
internal_handle,
use_deno_namespace: options.use_deno_namespace,
worker_type: options.worker_type,
main_module,
},
external_handle,
@ -418,6 +435,7 @@ impl WebWorker {
"target": env!("TARGET"),
"tsVersion": options.ts_version,
"unstableFlag": options.unstable,
"enableTestingFeaturesFlag": options.enable_testing_features,
"v8Version": deno_core::v8_version(),
"location": self.main_module,
"cpuCount": options.cpu_count,

View file

@ -50,6 +50,7 @@ pub struct WorkerOptions {
pub args: Vec<String>,
pub debug_flag: bool,
pub unstable: bool,
pub enable_testing_features: bool,
pub unsafely_ignore_certificate_errors: Option<Vec<String>>,
pub root_cert_store: Option<RootCertStore>,
pub user_agent: String,
@ -84,10 +85,12 @@ impl MainWorker {
) -> Self {
// Permissions: many ops depend on this
let unstable = options.unstable;
let enable_testing_features = options.enable_testing_features;
let perm_ext = Extension::builder()
.state(move |state| {
state.put::<Permissions>(permissions.clone());
state.put(ops::UnstableChecker { unstable });
state.put(ops::TestingFeaturesEnabled(enable_testing_features));
Ok(())
})
.build();
@ -304,6 +307,7 @@ mod tests {
args: vec![],
debug_flag: false,
unstable: false,
enable_testing_features: false,
unsafely_ignore_certificate_errors: None,
root_cert_store: None,
seed: None,

View file

@ -647,7 +647,8 @@ function discoverTestsToRun(
const url = new URL(path, "http://web-platform.test:8000");
if (
!url.pathname.endsWith(".any.html") &&
!url.pathname.endsWith(".window.html")
!url.pathname.endsWith(".window.html") &&
!url.pathname.endsWith(".worker.html")
) {
continue;
}

View file

@ -16611,5 +16611,200 @@
"constructor.any.html?wss": true
}
}
},
"workers": {
"Worker-base64.any.worker.html": true,
"Worker-call.worker.html": true,
"Worker-constructor-proto.any.worker.html": true,
"Worker-custom-event.any.worker.html": true,
"Worker-formdata.any.worker.html": [
"Test FormData interface object"
],
"Worker-location.sub.any.worker.html": [
"Test WorkerLocation properties."
],
"Worker-replace-event-handler.any.worker.html": true,
"Worker-replace-global-constructor.any.worker.html": true,
"Worker-replace-self.any.worker.html": true,
"WorkerGlobalScope_requestAnimationFrame.tentative.worker.html": [
"WorkerGlobalScope_requestAnimationFrame"
],
"WorkerLocation-origin.sub.window.html": [
"workerLocation.origin must use ASCII code points"
],
"WorkerNavigator-hardware-concurrency.any.worker.html": [
"Test worker navigator hardware concurrency."
],
"WorkerNavigator.any.worker.html": [
"Testing Navigator properties on workers."
],
"constructors": {
"Worker": {
"DedicatedWorkerGlobalScope-members.worker.html": [
"existence of onoffline",
"existence of ononline"
],
"expected-self-properties.worker.html": [
"existence of XMLHttpRequest",
"existence of EventSource",
"existence of SharedWorker"
],
"unexpected-self-properties.worker.html": true
}
},
"dedicated-worker-from-blob-url.window.html": [
"Creating a dedicated worker from a blob URL works immediately before revoking."
],
"dedicated-worker-in-data-url-context.window.html": [
"Create a dedicated worker in a data url frame",
"Create a dedicated worker in a data url dedicated worker",
"Create a data url dedicated worker in a data url frame"
],
"examples": {
"general.any.worker.html": true,
"general.worker.html": true
},
"importscripts_mime.any.worker.html": [
"importScripts() requires scripty MIME types: text/ecmascript is allowed."
],
"interfaces": {
"DedicatedWorkerGlobalScope": {
"EventTarget.worker.html": true,
"onmessage.worker.html": [
"Setting onmessage to 1",
"Setting onmessage to 1 (again)"
],
"postMessage": {
"return-value.worker.html": true
}
},
"WorkerGlobalScope": {
"location": {
"returns-same-object.any.worker.html": true
},
"self.any.worker.html": true
},
"WorkerUtils": {
"importScripts": {
"001.worker.html": true,
"002.worker.html": true,
"catch.sub.any.worker.html": [
"Cross-origin syntax error",
"Cross-origin throw",
"Redirect-to-cross-origin syntax error",
"Redirect-to-Cross-origin throw"
],
"report-error-cross-origin.sub.any.worker.html": false,
"report-error-redirect-to-cross-origin.sub.any.worker.html": false,
"report-error-same-origin.sub.any.worker.html": false,
"report-error-setTimeout-cross-origin.sub.any.worker.html": false,
"report-error-setTimeout-redirect-to-cross-origin.sub.any.worker.html": false,
"report-error-setTimeout-same-origin.sub.any.worker.html": false
}
}
},
"modules": {
"dedicated-worker-import-blob-url.any.html": true,
"dedicated-worker-import-blob-url.any.worker.html": true,
"dedicated-worker-import-data-url.any.html": true,
"dedicated-worker-import-data-url.any.worker.html": true,
"dedicated-worker-import.any.html": true,
"dedicated-worker-import.any.worker.html": true,
"shared-worker-import-blob-url.window.html": [
"Static import.",
"Static import (cross-origin).",
"Static import (redirect).",
"Nested static import.",
"Static import and then dynamic import.",
"Dynamic import.",
"Nested dynamic import.",
"Dynamic import and then static import.",
"eval(import())."
],
"shared-worker-import-data-url.window.html": [
"Static import.",
"Static import (cross-origin).",
"Static import (redirect).",
"Nested static import.",
"Static import and then dynamic import.",
"Dynamic import.",
"Nested dynamic import.",
"Dynamic import and then static import.",
"eval(import())."
],
"shared-worker-import.window.html": [
"Static import.",
"Static import (cross-origin).",
"Static import (redirect).",
"Nested static import.",
"Static import and then dynamic import.",
"Dynamic import.",
"Nested dynamic import.",
"Dynamic import and then static import.",
"eval(import())."
]
},
"nested_worker.worker.html": true,
"nested_worker_close_self.worker.html": true,
"nested_worker_importScripts.worker.html": true,
"nested_worker_sync_xhr.worker.html": [
"Nested worker that issues a sync XHR"
],
"semantics": {
"encodings": {
"004.worker.html": true
},
"interface-objects": {
"001.worker.html": [
"The SharedWorker interface object should be exposed.",
"The ImageData interface object should be exposed.",
"The ImageBitmap interface object should be exposed.",
"The CanvasGradient interface object should be exposed.",
"The CanvasPattern interface object should be exposed.",
"The CanvasPath interface object should be exposed.",
"The TextMetrics interface object should be exposed.",
"The Path2D interface object should be exposed.",
"The PromiseRejectionEvent interface object should be exposed.",
"The EventSource interface object should be exposed.",
"The XMLHttpRequestEventTarget interface object should be exposed.",
"The XMLHttpRequestUpload interface object should be exposed.",
"The XMLHttpRequest interface object should be exposed.",
"The FileList interface object should be exposed.",
"The FileReaderSync interface object should be exposed.",
"The IDBRequest interface object should be exposed.",
"The IDBOpenDBRequest interface object should be exposed.",
"The IDBVersionChangeEvent interface object should be exposed.",
"The IDBFactory interface object should be exposed.",
"The IDBDatabase interface object should be exposed.",
"The IDBObjectStore interface object should be exposed.",
"The IDBIndex interface object should be exposed.",
"The IDBKeyRange interface object should be exposed.",
"The IDBCursor interface object should be exposed.",
"The IDBCursorWithValue interface object should be exposed.",
"The IDBTransaction interface object should be exposed."
],
"002.worker.html": true
},
"multiple-workers": {
"exposure.any.html": [
"SharedWorker exposure"
],
"exposure.any.worker.html": true
}
},
"shared-worker-from-blob-url.window.html": [
"Creating a shared worker from a blob URL works.",
"Creating a shared worker from a blob URL works immediately before revoking.",
"Connecting to a shared worker on a revoked blob URL works."
],
"shared-worker-in-data-url-context.window.html": [
"Create a shared worker in a data url frame",
"Create a data url shared worker in a data url frame"
],
"worker-performance.worker.html": [
"Resource timing seems to work in workers",
"performance.clearResourceTimings in workers",
"performance.setResourceTimingBufferSize in workers"
]
}
}
}

View file

@ -94,6 +94,7 @@ export async function runSingleTest(
"run",
"-A",
"--unstable",
"--enable-testing-features-do-not-use",
"--location",
url.toString(),
"--cert",