feat(midi): expose secure non-constructible MIDI owner interfaces

This commit is contained in:
ldm0
2026-10-03 03:00:38 +08:00
committed by Donough Liu
parent 0b7081a51a
commit 2f0d1d4848
5 changed files with 83 additions and 0 deletions
@@ -92,6 +92,12 @@ pub(in crate::context_bootstrap) const WORKER_SHARED_INTERFACE_NAMES: &[&str] =
];
const SECURE_CONTEXT_ONLY_INTERFACE_NAMES: &[&str] = &[
"MIDIPort",
"MIDIAccess",
"MIDIInput",
"MIDIOutput",
"MIDIInputMap",
"MIDIOutputMap",
"MediaDeviceInfo",
"InputDeviceInfo",
"DeviceMotionEvent",
@@ -392,6 +392,30 @@ const CONSTRUCTOR_SPECS_BEFORE_STREAMS: &[ConstructorSpec] = &[
interface: web_api_interfaces::MessageChannel::DESCRIPTOR,
kind: ConstructorKind::MessageChannel,
},
ConstructorSpec {
interface: web_api_interfaces::MIDIPort::DESCRIPTOR,
kind: ConstructorKind::Illegal,
},
ConstructorSpec {
interface: web_api_interfaces::MIDIAccess::DESCRIPTOR,
kind: ConstructorKind::Illegal,
},
ConstructorSpec {
interface: web_api_interfaces::MIDIInput::DESCRIPTOR,
kind: ConstructorKind::Illegal,
},
ConstructorSpec {
interface: web_api_interfaces::MIDIOutput::DESCRIPTOR,
kind: ConstructorKind::Illegal,
},
ConstructorSpec {
interface: web_api_interfaces::MIDIInputMap::DESCRIPTOR,
kind: ConstructorKind::Illegal,
},
ConstructorSpec {
interface: web_api_interfaces::MIDIOutputMap::DESCRIPTOR,
kind: ConstructorKind::Illegal,
},
ConstructorSpec {
interface: web_api_interfaces::MessagePort::DESCRIPTOR,
kind: ConstructorKind::MessagePort,
@@ -0,0 +1,46 @@
use super::*;
#[test]
fn midi_owner_interfaces_preserve_native_inheritance_and_realm_contracts() {
for url in [
"https://midi-owner-interfaces.test/",
"http://midi-owner-interfaces.test/",
"http://localhost/",
] {
let mut vm = new_storage_page_task_executor_test_vm(url);
vm.eval("document.body.innerHTML = '<iframe id=child></iframe>'")
.unwrap();
assert_eq!(vm.eval(r#"(() => {
const assert = (ok, message) => { if (!ok) throw Error(message); };
const popup = open();
assert(popup, 'popup test prerequisite');
try {
const realms = [window, document.getElementById('child').contentWindow, popup];
for (const w of realms) {
for (const [name, parentName] of [["MIDIPort","EventTarget"],["MIDIAccess","EventTarget"],["MIDIInput","MIDIPort"],["MIDIOutput","MIDIPort"],["MIDIInputMap","Object"],["MIDIOutputMap","Object"]]) {
if (true && !w.isSecureContext) {
assert(!(name in w), name + ' hidden in an insecure realm');
continue;
}
const C = w[name], parent = w[parentName];
assert(typeof C === 'function' && C.name === name && C.length === 0, name + ' interface object');
const descriptor = Object.getOwnPropertyDescriptor(w, name);
assert(descriptor.writable && descriptor.configurable && !descriptor.enumerable, name + ' global descriptor');
assert(Object.getPrototypeOf(C.prototype) === parent.prototype, name + ' prototype inheritance');
assert(Object.getPrototypeOf(C) === (parentName === 'Object' ? w.Function.prototype : parent), name + ' interface inheritance');
assert(C.prototype.constructor === C, name + ' prototype constructor');
const tag = Object.getOwnPropertyDescriptor(C.prototype, Symbol.toStringTag);
assert(tag.value === name && !tag.writable && !tag.enumerable && tag.configurable, name + ' tag');
for (const call of [() => C(), () => new C()]) {
let error;
try { call(); } catch (caught) { error = caught; }
assert(error instanceof w.TypeError, name + ' rejects direct construction in the callee realm');
if (w !== window) assert(!(error instanceof TypeError), name + ' foreign error realm');
}
}
}
return true;
} finally { popup.close(); }
})()"#).unwrap(), "true", "{url}");
}
}
@@ -2152,6 +2152,7 @@ mod indexed_db;
mod inspector_unwrap;
mod lazy_storage;
mod lazy_window_surfaces;
mod midi_owner_interfaces;
mod mouse_snapshot;
mod no_cors_header_fill;
mod observer_callbacks;
@@ -296,6 +296,12 @@ interfaces! {
MediaQueryListEvent: Event;
MediaSource: EventTarget;
MemoryInfo;
MIDIPort: EventTarget;
MIDIAccess: EventTarget;
MIDIInput: MIDIPort;
MIDIOutput: MIDIPort;
MIDIInputMap;
MIDIOutputMap;
MessageChannel;
MessageEvent: Event;
MessagePort: EventTarget;