summaryrefslogtreecommitdiff
path: root/runtime
diff options
context:
space:
mode:
Diffstat (limited to 'runtime')
-rw-r--r--runtime/examples/hello_runtime.rs1
-rw-r--r--runtime/js/11_workers.js31
-rw-r--r--runtime/js/99_main.js46
-rw-r--r--runtime/ops/mod.rs2
-rw-r--r--runtime/ops/web_worker.rs16
-rw-r--r--runtime/ops/web_worker/sync_fetch.rs155
-rw-r--r--runtime/ops/worker_host.rs16
-rw-r--r--runtime/web_worker.rs20
-rw-r--r--runtime/worker.rs4
9 files changed, 279 insertions, 12 deletions
diff --git a/runtime/examples/hello_runtime.rs b/runtime/examples/hello_runtime.rs
index 776dc23c3..9ac1d0a27 100644
--- a/runtime/examples/hello_runtime.rs
+++ b/runtime/examples/hello_runtime.rs
@@ -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(),
diff --git a/runtime/js/11_workers.js b/runtime/js/11_workers.js
index b5a8a9d0c..2f9413119 100644
--- a/runtime/js/11_workers.js
+++ b/runtime/js/11_workers.js
@@ -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,
diff --git a/runtime/js/99_main.js b/runtime/js/99_main.js
index 6d5599e71..b1f7d1473 100644
--- a/runtime/js/99_main.js
+++ b/runtime/js/99_main.js
@@ -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;
diff --git a/runtime/ops/mod.rs b/runtime/ops/mod.rs
index e08ddd1c0..ee2bc0a1e 100644
--- a/runtime/ops/mod.rs
+++ b/runtime/ops/mod.rs
@@ -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);
diff --git a/runtime/ops/web_worker.rs b/runtime/ops/web_worker.rs
index 026e38157..8439e4384 100644
--- a/runtime/ops/web_worker.rs
+++ b/runtime/ops/web_worker.rs
@@ -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)
+}
diff --git a/runtime/ops/web_worker/sync_fetch.rs b/runtime/ops/web_worker/sync_fetch.rs
new file mode 100644
index 000000000..6ad6edba7
--- /dev/null
+++ b/runtime/ops/web_worker/sync_fetch.rs
@@ -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)
+}
diff --git a/runtime/ops/worker_host.rs b/runtime/ops/worker_host.rs
index 5315ff5c7..d80a39502 100644
--- a/runtime/ops/worker_host.rs
+++ b/runtime/ops/worker_host.rs
@@ -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
diff --git a/runtime/web_worker.rs b/runtime/web_worker.rs
index 495fedb81..240d79d1f 100644
--- a/runtime/web_worker.rs
+++ b/runtime/web_worker.rs
@@ -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,
diff --git a/runtime/worker.rs b/runtime/worker.rs
index b200ef08e..b1096024b 100644
--- a/runtime/worker.rs
+++ b/runtime/worker.rs
@@ -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,