diff --git a/moli-file-api/src/blob_store.rs b/moli-file-api/src/blob_store.rs index 9b8f67595b..b27c34990e 100644 --- a/moli-file-api/src/blob_store.rs +++ b/moli-file-api/src/blob_store.rs @@ -405,10 +405,22 @@ mod tests { let first_key = "first URL creator".to_owned(); let second_key = "second URL creator".to_owned(); let first = store - .create_object_url_with_access_key(Some(2), blob, "null", Some(first_key.clone())) + .create_object_url_with_lifetime_and_access_key( + Some(2), + Some(101), + blob, + "null", + Some(first_key.clone()), + ) .unwrap(); let second = store - .create_object_url_with_access_key(Some(3), blob, "null", Some(second_key.clone())) + .create_object_url_with_lifetime_and_access_key( + Some(3), + Some(101), + blob, + "null", + Some(second_key.clone()), + ) .unwrap(); let unkeyed = store.create_object_url(Some(1), blob, "null").unwrap(); store.release_blob_wrapper_ref(blob); @@ -427,7 +439,7 @@ mod tests { ); assert!(store.revoke_object_url(&unkeyed)); assert!(store.blob_bytes(blob).is_some()); - assert!(store.revoke_object_url_with_access_key(&second, &second_key)); + assert_eq!(store.cleanup_object_url_lifetime(3, 101), 1); assert!(store.blob_bytes(blob).is_none()); assert!(!store.revoke_object_url_with_access_key(&second, &second_key)); } diff --git a/moli-renderer-v8/src/blob.rs b/moli-renderer-v8/src/blob.rs index b0f545337f..75fc0fe523 100644 --- a/moli-renderer-v8/src/blob.rs +++ b/moli-renderer-v8/src/blob.rs @@ -384,7 +384,10 @@ pub(super) fn create_object_url_for_object<'s>( lifetime_id, blob_id, &origin, - Some(ObjectUrlAccessKey { partition, storage_key }), + Some(ObjectUrlAccessKey { + partition, + storage_key, + }), ) } diff --git a/moli-renderer-v8/src/runtime/page_vm/tests/fetch_xhr.rs b/moli-renderer-v8/src/runtime/page_vm/tests/fetch_xhr.rs index 69ac899ecb..98624e7361 100644 --- a/moli-renderer-v8/src/runtime/page_vm/tests/fetch_xhr.rs +++ b/moli-renderer-v8/src/runtime/page_vm/tests/fetch_xhr.rs @@ -4545,6 +4545,64 @@ async fn request_init_exceptions_preserve_identity_without_fetching_or_consuming }).await; } +#[tokio::test] +async fn blob_url_revocation_uses_window_and_worker_creator_storage_keys() { + run_page_vm_async_test(async move { + for document_url in ["https://example.com/", "data:text/html,opaque-parent"] { + let mut page_vm = test_page_vm_with_document_url(Url::parse(document_url).unwrap()); + let local_executor = page_vm.local_executor.clone(); + let result = local_executor.run(async move { + page_vm.vm_mut().eval(r#" + globalThis.__revocationResult = 'pending'; + (async () => { + const check = (value, message) => { if (!value) throw new Error(message); }; + const source = `onmessage = async event => { + const {action, url} = event.data; + if (action === 'create') postMessage(URL.createObjectURL(new Blob(['payload']))); + if (action === 'revoke') { URL.revokeObjectURL(url); postMessage('done'); } + if (action === 'read') { + try { postMessage(await (await fetch(url)).text()); } + catch (error) { postMessage(error.name); } + } + };`; + const sourceUrl = URL.createObjectURL(new Blob([source])); + const workers = [new Worker(sourceUrl), new Worker('data:text/javascript,' + encodeURIComponent(source))]; + const rpc = (worker, action, url) => new Promise((resolve, reject) => { + worker.onmessage = event => resolve(event.data); + worker.onerror = event => reject(new Error(event.message)); + worker.postMessage({action, url}); + }); + try { + for (let i = 0; i < workers.length; i++) { + const worker = workers[i]; + const url = URL.createObjectURL(new Blob(['payload'])); + await rpc(worker, 'revoke', url); + let body; + try { body = await (await fetch(url)).text(); } + catch (error) { body = error.name; } + check(body === (i === 0 ? 'TypeError' : 'payload'), 'worker revocation authority'); + URL.revokeObjectURL(url); + const childUrl = await rpc(worker, 'create'); + URL.revokeObjectURL(childUrl); + check(await rpc(worker, 'read', childUrl) === (i === 0 ? 'TypeError' : 'payload'), 'parent revocation authority'); + await rpc(worker, 'revoke', childUrl); + check(await rpc(worker, 'read', childUrl) === 'TypeError', 'worker can revoke its own opaque URL'); + } + } finally { + for (const worker of workers) worker.terminate(); + URL.revokeObjectURL(sourceUrl); + } + return 'ok'; + })().then(value => { globalThis.__revocationResult = value; }, error => { globalThis.__revocationResult = String(error); }); + "#)?; + drive_websocket_until_done(&mut page_vm, "String(globalThis.__revocationResult !== 'pending')", "revocation checks should finish").await?; + page_vm.vm_mut().eval("globalThis.__revocationResult") + }).await.expect("blob revocation checks should run on owner lane"); + assert_eq!(result, "ok", "document_url={document_url}"); + } + }).await; +} + #[tokio::test] async fn blob_url_entries_survive_request_cloning_and_xhr_open_in_window_and_worker() { run_page_vm_async_test(async move {