feat(script): Implement [[{Get,Set}PrototypeOf]] for Location

This commit is contained in:
yvt 2021-07-17 01:44:11 +09:00
parent 41cce140bc
commit 722a239715
2 changed files with 150 additions and 18 deletions

View file

@ -3531,6 +3531,17 @@ class CGDefineProxyHandler(CGAbstractMethod):
if self.descriptor.isMaybeCrossOriginObject() or self.descriptor.operations['NamedDeleter']:
customDelete = 'delete'
customGetPrototypeIfOrdinary = 'Some(proxyhandler::get_prototype_if_ordinary)'
customGetPrototype = 'None'
customSetPrototype = 'None'
if self.descriptor.isMaybeCrossOriginObject():
customGetPrototypeIfOrdinary = 'Some(proxyhandler::maybe_cross_origin_get_prototype_if_ordinary_rawcx)'
customGetPrototype = 'Some(getPrototype)'
customSetPrototype = 'Some(proxyhandler::maybe_cross_origin_set_prototype_rawcx)'
# The base class `BaseProxyHandler`'s `setImmutablePrototype` (not to be
# confused with ECMAScript's `[[SetImmutablePrototype]]`) always fails.
# This is the desired behavior, so we don't override it.
getOwnEnumerablePropertyKeys = "own_property_keys"
if self.descriptor.interface.getExtendedAttribute("LegacyUnenumerableNamedProperties"):
getOwnEnumerablePropertyKeys = "getOwnEnumerablePropertyKeys"
@ -3538,6 +3549,9 @@ class CGDefineProxyHandler(CGAbstractMethod):
args = {
"defineProperty": customDefineProperty,
"delete": customDelete,
"getPrototypeIfOrdinary": customGetPrototypeIfOrdinary,
"getPrototype": customGetPrototype,
"setPrototype": customSetPrototype,
"getOwnEnumerablePropertyKeys": getOwnEnumerablePropertyKeys,
"trace": TRACE_HOOK_NAME,
"finalize": FINALIZE_HOOK_NAME,
@ -3551,9 +3565,9 @@ let traps = ProxyTraps {
ownPropertyKeys: Some(own_property_keys),
delete_: Some(%(delete)s),
enumerate: None,
getPrototypeIfOrdinary: Some(proxyhandler::get_prototype_if_ordinary),
getPrototype: None,
setPrototype: None,
getPrototypeIfOrdinary: %(getPrototypeIfOrdinary)s,
getPrototype: %(getPrototype)s,
setPrototype: %(setPrototype)s,
setImmutablePrototype: None,
preventExtensions: Some(proxyhandler::prevent_extensions),
isExtensible: Some(proxyhandler::is_extensible),
@ -5925,6 +5939,25 @@ return true;""" % (maybeCrossOriginGet, getIndexedOrExpando, getNamed)
return CGGeneric(self.getBody())
class CGDOMJSProxyHandler_getPrototype(CGAbstractExternMethod):
def __init__(self, descriptor):
args = [Argument('*mut JSContext', 'cx'), Argument('RawHandleObject', 'proxy'),
Argument('RawMutableHandleObject', 'proto')]
CGAbstractExternMethod.__init__(self, descriptor, "getPrototype", "bool", args)
assert self.descriptor.isMaybeCrossOriginObject()
self.descriptor = descriptor
def getBody(self):
return dedent(
"""
let cx = SafeJSContext::from_ptr(cx);
proxyhandler::maybe_cross_origin_get_prototype(cx, proxy, GetProtoObject, proto)
""")
def definition_body(self):
return CGGeneric(self.getBody())
class CGDOMJSProxyHandler_className(CGAbstractExternMethod):
def __init__(self, descriptor):
args = [Argument('*mut JSContext', 'cx'), Argument('RawHandleObject', '_proxy')]
@ -6590,10 +6623,7 @@ class CGDescriptor(CGThing):
cgThings.append(CGDOMJSProxyHandler_delete(descriptor))
if descriptor.isMaybeCrossOriginObject():
# TODO: CGDOMJSProxyHandler_getPrototype(descriptor),
# TODO: CGDOMJSProxyHandler_getPrototypeIfOrdinary(descriptor),
# TODO: CGDOMJSProxyHandler_setPrototype(descriptor),
# TODO: CGDOMJSProxyHandler_setImmutablePrototype(descriptor),
cgThings.append(CGDOMJSProxyHandler_getPrototype(descriptor))
# TODO: CGDOMJSProxyHandler_set(descriptor),
pass

View file

@ -9,6 +9,8 @@
use crate::dom::bindings::conversions::is_dom_proxy;
use crate::dom::bindings::error::{throw_dom_exception, Error};
use crate::dom::bindings::principals::ServoJSPrincipalsRef;
use crate::dom::bindings::reflector::DomObject;
use crate::dom::bindings::str::DOMString;
use crate::dom::bindings::utils::delete_property_by_id;
use crate::dom::globalscope::GlobalScope;
use crate::realms::{AlreadyInRealm, InRealm};
@ -24,6 +26,7 @@ use js::jsapi::Handle as RawHandle;
use js::jsapi::HandleId as RawHandleId;
use js::jsapi::HandleObject as RawHandleObject;
use js::jsapi::HandleValue as RawHandleValue;
use js::jsapi::JSAutoRealm;
use js::jsapi::JS_AtomizeAndPinString;
use js::jsapi::JS_DefinePropertyById;
use js::jsapi::JS_GetOwnPropertyDescriptorById;
@ -139,7 +142,7 @@ pub unsafe extern "C" fn is_extensible(
///
/// This implementation always handles the case of the ordinary
/// `[[GetPrototypeOf]]` behavior. An alternative implementation will be
/// necessary for the Location object.
/// necessary for maybe-cross-origin objects.
pub unsafe extern "C" fn get_prototype_if_ordinary(
_: *mut JSContext,
proxy: RawHandleObject,
@ -203,13 +206,29 @@ pub unsafe fn is_platform_object_same_origin(cx: SafeJSContext, obj: RawHandleOb
let obj_realm = GetObjectRealmOrNull(*obj);
assert!(!obj_realm.is_null());
let subject = ServoJSPrincipalsRef::from_raw_unchecked(GetRealmPrincipals(subject_realm));
let obj = ServoJSPrincipalsRef::from_raw_unchecked(GetRealmPrincipals(obj_realm));
let subject_principals =
ServoJSPrincipalsRef::from_raw_unchecked(GetRealmPrincipals(subject_realm));
let obj_principals = ServoJSPrincipalsRef::from_raw_unchecked(GetRealmPrincipals(obj_realm));
let subject_origin = subject.origin();
let obj_origin = obj.origin();
let subject_origin = subject_principals.origin();
let obj_origin = obj_principals.origin();
subject_origin.same_origin_domain(&obj_origin)
let result = subject_origin.same_origin_domain(&obj_origin);
log::trace!(
"object {:p} (realm = {:p}, principalls = {:p}, origin = {:?}) is {} \
with reference to the current Realm (realm = {:p}, principals = {:p}, \
origin = {:?})",
obj.get(),
obj_realm,
obj_principals.as_raw(),
obj_origin.immutable(),
["NOT same domain-origin", "same domain-origin"][result as usize],
subject_realm,
subject_principals.as_raw(),
subject_origin.immutable()
);
result
}
/// Report a cross-origin denial for a property, Always returns `false`, so it
@ -218,11 +237,12 @@ pub unsafe fn is_platform_object_same_origin(cx: SafeJSContext, obj: RawHandleOb
/// What this function does corresponds to the operations in
/// <https://html.spec.whatwg.org/multipage/#the-location-interface> denoted as
/// "Throw a `SecurityError` DOMException".
pub unsafe fn report_cross_origin_denial(
cx: SafeJSContext,
_id: RawHandleId,
_access: &str,
) -> bool {
pub unsafe fn report_cross_origin_denial(cx: SafeJSContext, id: RawHandleId, access: &str) -> bool {
debug!(
"permission denied to {} property {} on cross-origin object",
access,
id_to_source(cx, id).as_deref().unwrap_or("< error >"),
);
let in_realm_proof = AlreadyInRealm::assert_for_cx(cx);
if !JS_IsExceptionPending(*cx) {
let global = GlobalScope::from_context(*cx, InRealm::Already(&in_realm_proof));
@ -232,6 +252,18 @@ pub unsafe fn report_cross_origin_denial(
false
}
unsafe fn id_to_source(cx: SafeJSContext, id: RawHandleId) -> Option<DOMString> {
rooted!(in(*cx) let mut value = UndefinedValue());
rooted!(in(*cx) let mut jsstr = ptr::null_mut::<jsapi::JSString>());
jsapi::JS_IdToValue(*cx, id.get(), value.handle_mut().into())
.then(|| {
jsstr.set(jsapi::JS_ValueToSource(*cx, value.handle().into()));
jsstr.get()
})
.filter(|jsstr| !jsstr.is_null())
.map(|jsstr| crate::dom::bindings::conversions::jsstring_to_str(*cx, jsstr))
}
/// Property and method specs that correspond to the elements of
/// [`CrossOriginProperties(O)`].
///
@ -272,6 +304,76 @@ pub unsafe fn cross_origin_own_property_keys(
true
}
pub unsafe extern "C" fn maybe_cross_origin_get_prototype_if_ordinary_rawcx(
_: *mut JSContext,
_proxy: RawHandleObject,
is_ordinary: *mut bool,
_proto: RawMutableHandleObject,
) -> bool {
// We have a custom `[[GetPrototypeOf]]`, so return `false`
*is_ordinary = false;
true
}
/// Implementation of `[[GetPrototypeOf]]` for [`History`].
///
/// [`History`]: https://html.spec.whatwg.org/multipage/#location-getprototypeof
pub unsafe fn maybe_cross_origin_get_prototype(
cx: SafeJSContext,
proxy: RawHandleObject,
get_proto_object: unsafe fn(cx: SafeJSContext, global: HandleObject, rval: MutableHandleObject),
proto: RawMutableHandleObject,
) -> bool {
// > 1. If ! IsPlatformObjectSameOrigin(this) is true, then return ! OrdinaryGetPrototypeOf(this).
if is_platform_object_same_origin(cx, proxy) {
let ac = JSAutoRealm::new(*cx, proxy.get());
let global = GlobalScope::from_context(*cx, InRealm::Entered(&ac));
get_proto_object(
cx,
global.reflector().get_jsobject(),
MutableHandleObject::from_raw(proto),
);
return !proto.is_null();
}
// > 2. Return null.
proto.set(ptr::null_mut());
true
}
/// Implementation of `[[SetPrototypeOf]]` for [`History`] and [`WindowProxy`].
///
/// [`History`]: https://html.spec.whatwg.org/multipage/#location-setprototypeof
/// [`WindowProxy`]: https://html.spec.whatwg.org/multipage/window-object.html#windowproxy-setprototypeof
pub unsafe extern "C" fn maybe_cross_origin_set_prototype_rawcx(
cx: *mut JSContext,
proxy: RawHandleObject,
proto: RawHandleObject,
result: *mut ObjectOpResult,
) -> bool {
// > 1. Return `! SetImmutablePrototype(this, V)`.
//
// <https://tc39.es/ecma262/#sec-set-immutable-prototype>
//
// > 1. Assert: Either `Type(V)` is Object or `Type(V)` is Null.
//
// > 2. Let current be `? O.[[GetPrototypeOf]]()`.
rooted!(in(cx) let mut current = ptr::null_mut::<JSObject>());
if !jsapi::GetObjectProto(cx, proxy, current.handle_mut().into()) {
return false;
}
// > 3. If `SameValue(V, current)` is true, return true.
if proto.get() == current.get() {
(*result).code_ = 0 /* OkCode */;
return true;
}
// > 4. Return false.
(*result).code_ = JSErrNum::JSMSG_CANT_SET_PROTO as usize;
true
}
/// Implementation of [`CrossOriginGet`].
///
/// [`CrossOriginGet`]: https://html.spec.whatwg.org/multipage/#crossoriginget-(-o,-p,-receiver-)