mirror of
https://github.com/servo/servo.git
synced 2025-07-22 14:53:49 +01:00
feat(script): implement some of the non-ordinary internal methods of Location
<https://html.spec.whatwg.org/multipage/#the-location-interface> - `[[GetPrototypeOf]]`: not yet - `[[SetPrototypeOf]]`: not yet - `[[IsExtensible]]`: `proxyhandler::is_extensible` - `[[PreventExtensions]]`: `proxyhandler::prevent_extensions` - `[[GetOwnProperty]]`: `CGDOMJSProxyHandler_getOwnPropertyDescriptor` (updated) - `[[DefineOwnProperty]]`: `CGDOMJSProxyHandler_defineProperty` (updated) - `[[Get]]`: `CGDOMJSProxyHandler_get` (updated) - `[[Set]]`: not yet - `[[Delete]]`: `CGDOMJSProxyHandler_delete` (updated) - `[[OwnPropertyKeys]]`: `CGDOMJSProxyHandler_ownPropertyKeys` (updated)
This commit is contained in:
parent
1a033ba8a9
commit
41cce140bc
4 changed files with 557 additions and 30 deletions
|
@ -1656,10 +1656,15 @@ class MethodDefiner(PropertyDefiner):
|
|||
"""
|
||||
A class for defining methods on a prototype object.
|
||||
"""
|
||||
def __init__(self, descriptor, name, static, unforgeable):
|
||||
def __init__(self, descriptor, name, static, unforgeable, crossorigin=False):
|
||||
assert not (static and unforgeable)
|
||||
assert not (static and crossorigin)
|
||||
assert not (unforgeable and crossorigin)
|
||||
PropertyDefiner.__init__(self, descriptor, name)
|
||||
|
||||
# TODO: Separate the `(static, unforgeable, crossorigin) = (False, False, True)` case
|
||||
# to a separate class or something.
|
||||
|
||||
# FIXME https://bugzilla.mozilla.org/show_bug.cgi?id=772822
|
||||
# We should be able to check for special operations without an
|
||||
# identifier. For now we check if the name starts with __
|
||||
|
@ -1668,14 +1673,15 @@ class MethodDefiner(PropertyDefiner):
|
|||
if not descriptor.interface.isCallback() or static:
|
||||
methods = [m for m in descriptor.interface.members if
|
||||
m.isMethod() and m.isStatic() == static
|
||||
and (bool(m.getExtendedAttribute("CrossOriginCallable")) or not crossorigin)
|
||||
and not m.isIdentifierLess()
|
||||
and MemberIsUnforgeable(m, descriptor) == unforgeable]
|
||||
and (MemberIsUnforgeable(m, descriptor) == unforgeable or crossorigin)]
|
||||
else:
|
||||
methods = []
|
||||
self.regular = [{"name": m.identifier.name,
|
||||
"methodInfo": not m.isStatic(),
|
||||
"length": methodLength(m),
|
||||
"flags": "JSPROP_ENUMERATE",
|
||||
"flags": "JSPROP_READONLY" if crossorigin else "JSPROP_ENUMERATE",
|
||||
"condition": PropertyDefiner.getControllingCondition(m, descriptor)}
|
||||
for m in methods]
|
||||
|
||||
|
@ -1693,6 +1699,7 @@ class MethodDefiner(PropertyDefiner):
|
|||
# neither.
|
||||
if (not static
|
||||
and not unforgeable
|
||||
and not crossorigin
|
||||
and descriptor.supportsIndexedProperties()): # noqa
|
||||
if hasIterator(methods, self.regular): # noqa
|
||||
raise TypeError("Cannot have indexed getter/attr on "
|
||||
|
@ -1709,7 +1716,7 @@ class MethodDefiner(PropertyDefiner):
|
|||
|
||||
# Generate the keys/values/entries aliases for value iterables.
|
||||
maplikeOrSetlikeOrIterable = descriptor.interface.maplikeOrSetlikeOrIterable
|
||||
if (not static and not unforgeable
|
||||
if (not static and not unforgeable and not crossorigin
|
||||
and maplikeOrSetlikeOrIterable
|
||||
and maplikeOrSetlikeOrIterable.isIterable()
|
||||
and maplikeOrSetlikeOrIterable.isValueIterator()):
|
||||
|
@ -1754,7 +1761,7 @@ class MethodDefiner(PropertyDefiner):
|
|||
})
|
||||
|
||||
isUnforgeableInterface = bool(descriptor.interface.getExtendedAttribute("Unforgeable"))
|
||||
if not static and unforgeable == isUnforgeableInterface:
|
||||
if not static and unforgeable == isUnforgeableInterface and not crossorigin:
|
||||
stringifier = descriptor.operations['Stringifier']
|
||||
if stringifier:
|
||||
self.regular.append({
|
||||
|
@ -1765,6 +1772,7 @@ class MethodDefiner(PropertyDefiner):
|
|||
"condition": PropertyDefiner.getControllingCondition(stringifier, descriptor)
|
||||
})
|
||||
self.unforgeable = unforgeable
|
||||
self.crossorigin = crossorigin
|
||||
|
||||
def generateArray(self, array, name):
|
||||
if len(array) == 0:
|
||||
|
@ -1796,36 +1804,59 @@ class MethodDefiner(PropertyDefiner):
|
|||
jitinfo = "ptr::null()"
|
||||
accessor = 'Some(%s)' % m.get("nativeName", m["name"])
|
||||
if m["name"].startswith("@@"):
|
||||
assert not self.crossorigin
|
||||
name = 'JSPropertySpec_Name { symbol_: SymbolCode::%s as usize + 1 }' % m["name"][2:]
|
||||
else:
|
||||
name = ('JSPropertySpec_Name { string_: %s as *const u8 as *const libc::c_char }'
|
||||
% str_to_const_array(m["name"]))
|
||||
return (name, accessor, jitinfo, m["length"], flags, selfHostedName)
|
||||
|
||||
return self.generateGuardedArray(
|
||||
array, name,
|
||||
specTemplate = (
|
||||
' JSFunctionSpec {\n'
|
||||
' name: %s,\n'
|
||||
' call: JSNativeWrapper { op: %s, info: %s },\n'
|
||||
' nargs: %s,\n'
|
||||
' flags: (%s) as u16,\n'
|
||||
' selfHostedName: %s\n'
|
||||
' }',
|
||||
' }')
|
||||
specTerminator = (
|
||||
' JSFunctionSpec {\n'
|
||||
' name: JSPropertySpec_Name { string_: ptr::null() },\n'
|
||||
' call: JSNativeWrapper { op: None, info: ptr::null() },\n'
|
||||
' nargs: 0,\n'
|
||||
' flags: 0,\n'
|
||||
' selfHostedName: ptr::null()\n'
|
||||
' }',
|
||||
'JSFunctionSpec',
|
||||
condition, specData)
|
||||
' }')
|
||||
|
||||
if self.crossorigin:
|
||||
groups = groupby(array, lambda m: condition(m, self.descriptor))
|
||||
assert len(list(groups)) == 1 # can't handle mixed condition
|
||||
elems = [specTemplate % specData(m) for m in array]
|
||||
return dedent(
|
||||
"""
|
||||
const %s: &[JSFunctionSpec] = &[
|
||||
%s,
|
||||
%s,
|
||||
];
|
||||
""") % (name, ',\n'.join(elems), specTerminator)
|
||||
else:
|
||||
return self.generateGuardedArray(
|
||||
array, name,
|
||||
specTemplate, specTerminator,
|
||||
'JSFunctionSpec',
|
||||
condition, specData)
|
||||
|
||||
|
||||
class AttrDefiner(PropertyDefiner):
|
||||
def __init__(self, descriptor, name, static, unforgeable):
|
||||
def __init__(self, descriptor, name, static, unforgeable, crossorigin=False):
|
||||
assert not (static and unforgeable)
|
||||
assert not (static and crossorigin)
|
||||
assert not (unforgeable and crossorigin)
|
||||
PropertyDefiner.__init__(self, descriptor, name)
|
||||
|
||||
# TODO: Separate the `(static, unforgeable, crossorigin) = (False, False, True)` case
|
||||
# to a separate class or something.
|
||||
|
||||
self.name = name
|
||||
self.descriptor = descriptor
|
||||
self.regular = [
|
||||
|
@ -1837,12 +1868,16 @@ class AttrDefiner(PropertyDefiner):
|
|||
}
|
||||
for m in descriptor.interface.members if
|
||||
m.isAttr() and m.isStatic() == static
|
||||
and MemberIsUnforgeable(m, descriptor) == unforgeable
|
||||
and (MemberIsUnforgeable(m, descriptor) == unforgeable or crossorigin)
|
||||
and (not crossorigin
|
||||
or m.getExtendedAttribute("CrossOriginReadable")
|
||||
or m.getExtendedAttribute("CrossOriginWritable"))
|
||||
]
|
||||
self.static = static
|
||||
self.unforgeable = unforgeable
|
||||
self.crossorigin = crossorigin
|
||||
|
||||
if not static and not unforgeable and not (
|
||||
if not static and not unforgeable and not crossorigin and not (
|
||||
descriptor.interface.isNamespace() or descriptor.interface.isCallback()
|
||||
):
|
||||
self.regular.append({
|
||||
|
@ -1858,6 +1893,10 @@ class AttrDefiner(PropertyDefiner):
|
|||
|
||||
def getter(attr):
|
||||
attr = attr['attr']
|
||||
|
||||
if self.crossorigin and not attr.getExtendedAttribute("CrossOriginReadable"):
|
||||
return "JSNativeWrapper { op: None, info: 0 as *const JSJitInfo }"
|
||||
|
||||
if self.static:
|
||||
accessor = 'get_' + self.descriptor.internalNameFor(attr.identifier.name)
|
||||
jitinfo = "0 as *const JSJitInfo"
|
||||
|
@ -1874,8 +1913,10 @@ class AttrDefiner(PropertyDefiner):
|
|||
|
||||
def setter(attr):
|
||||
attr = attr['attr']
|
||||
if (attr.readonly and not attr.getExtendedAttribute("PutForwards")
|
||||
and not attr.getExtendedAttribute("Replaceable")):
|
||||
|
||||
if ((attr.readonly and not attr.getExtendedAttribute("PutForwards")
|
||||
and not attr.getExtendedAttribute("Replaceable"))
|
||||
or (self.crossorigin and not attr.getExtendedAttribute("CrossOriginReadable"))):
|
||||
return "JSNativeWrapper { op: None, info: 0 as *const JSJitInfo }"
|
||||
|
||||
if self.static:
|
||||
|
@ -1941,12 +1982,24 @@ class AttrDefiner(PropertyDefiner):
|
|||
}
|
||||
"""
|
||||
|
||||
return self.generateGuardedArray(
|
||||
array, name,
|
||||
template,
|
||||
' JSPropertySpec::ZERO',
|
||||
'JSPropertySpec',
|
||||
condition, specData)
|
||||
if self.crossorigin:
|
||||
groups = groupby(array, lambda m: condition(m, self.descriptor))
|
||||
assert len(list(groups)) == 1 # can't handle mixed condition
|
||||
elems = [template(m) % specData(m) for m in array]
|
||||
return dedent(
|
||||
"""
|
||||
const %s: &[JSPropertySpec] = &[
|
||||
%s,
|
||||
JSPropertySpec::ZERO,
|
||||
];
|
||||
""") % (name, ',\n'.join(elems))
|
||||
else:
|
||||
return self.generateGuardedArray(
|
||||
array, name,
|
||||
template,
|
||||
' JSPropertySpec::ZERO',
|
||||
'JSPropertySpec',
|
||||
condition, specData)
|
||||
|
||||
|
||||
class ConstDefiner(PropertyDefiner):
|
||||
|
@ -3071,6 +3124,25 @@ class PropertyArrays():
|
|||
return define
|
||||
|
||||
|
||||
class CGCrossOriginProperties(CGThing):
|
||||
def __init__(self, descriptor):
|
||||
self.methods = MethodDefiner(descriptor, "CrossOriginMethods", static=False,
|
||||
unforgeable=False, crossorigin=True)
|
||||
self.attributes = AttrDefiner(descriptor, "CrossOriginAttributes", static=False,
|
||||
unforgeable=False, crossorigin=True)
|
||||
|
||||
def define(self):
|
||||
return str(self.methods) + str(self.attributes) + dedent(
|
||||
"""
|
||||
const CROSS_ORIGIN_PROPERTIES: proxyhandler::CrossOriginProperties =
|
||||
proxyhandler::CrossOriginProperties {
|
||||
attributes: sCrossOriginAttributes,
|
||||
methods: sCrossOriginMethods,
|
||||
};
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
class CGCollectJSONAttributesMethod(CGAbstractMethod):
|
||||
"""
|
||||
Generate the CollectJSONAttributes method for an interface descriptor
|
||||
|
@ -3451,11 +3523,12 @@ class CGDefineProxyHandler(CGAbstractMethod):
|
|||
|
||||
def definition_body(self):
|
||||
customDefineProperty = 'proxyhandler::define_property'
|
||||
if self.descriptor.operations['IndexedSetter'] or self.descriptor.operations['NamedSetter']:
|
||||
if self.descriptor.isMaybeCrossOriginObject() or self.descriptor.operations['IndexedSetter'] or \
|
||||
self.descriptor.operations['NamedSetter']:
|
||||
customDefineProperty = 'defineProperty'
|
||||
|
||||
customDelete = 'proxyhandler::delete'
|
||||
if self.descriptor.operations['NamedDeleter']:
|
||||
if self.descriptor.isMaybeCrossOriginObject() or self.descriptor.operations['NamedDeleter']:
|
||||
customDelete = 'delete'
|
||||
|
||||
getOwnEnumerablePropertyKeys = "own_property_keys"
|
||||
|
@ -5353,6 +5426,26 @@ class CGDOMJSProxyHandler_getOwnPropertyDescriptor(CGAbstractExternMethod):
|
|||
indexedGetter = self.descriptor.operations['IndexedGetter']
|
||||
|
||||
get = "let cx = SafeJSContext::from_ptr(cx);\n"
|
||||
|
||||
if self.descriptor.isMaybeCrossOriginObject():
|
||||
get += dedent(
|
||||
"""
|
||||
if !proxyhandler::is_platform_object_same_origin(cx, proxy) {
|
||||
if !proxyhandler::cross_origin_get_own_property_helper(
|
||||
cx, proxy, &CROSS_ORIGIN_PROPERTIES, id, desc
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
if desc.obj.is_null() {
|
||||
return proxyhandler::cross_origin_property_fallback(cx, proxy, id, desc);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Safe to enter the Realm of proxy now.
|
||||
let _ac = JSAutoRealm::new(*cx, proxy.get());
|
||||
""")
|
||||
|
||||
if indexedGetter:
|
||||
get += "let index = get_array_index_from_id(*cx, Handle::from_raw(id));\n"
|
||||
|
||||
|
@ -5450,6 +5543,17 @@ class CGDOMJSProxyHandler_defineProperty(CGAbstractExternMethod):
|
|||
def getBody(self):
|
||||
set = "let cx = SafeJSContext::from_ptr(cx);\n"
|
||||
|
||||
if self.descriptor.isMaybeCrossOriginObject():
|
||||
set += dedent(
|
||||
"""
|
||||
if !proxyhandler::is_platform_object_same_origin(cx, proxy) {
|
||||
return proxyhandler::report_cross_origin_denial(cx, id, "define");
|
||||
}
|
||||
|
||||
// Safe to enter the Realm of proxy now.
|
||||
let _ac = JSAutoRealm::new(*cx, proxy.get());
|
||||
""")
|
||||
|
||||
indexedSetter = self.descriptor.operations['IndexedSetter']
|
||||
if indexedSetter:
|
||||
set += ("let index = get_array_index_from_id(*cx, Handle::from_raw(id));\n"
|
||||
|
@ -5497,6 +5601,18 @@ class CGDOMJSProxyHandler_delete(CGAbstractExternMethod):
|
|||
|
||||
def getBody(self):
|
||||
set = "let cx = SafeJSContext::from_ptr(cx);\n"
|
||||
|
||||
if self.descriptor.isMaybeCrossOriginObject():
|
||||
set += dedent(
|
||||
"""
|
||||
if !proxyhandler::is_platform_object_same_origin(cx, proxy) {
|
||||
return proxyhandler::report_cross_origin_denial(cx, id, "delete");
|
||||
}
|
||||
|
||||
// Safe to enter the Realm of proxy now.
|
||||
let _ac = JSAutoRealm::new(*cx, proxy.get());
|
||||
""")
|
||||
|
||||
if self.descriptor.operations['NamedDeleter']:
|
||||
if self.descriptor.hasUnforgeableMembers:
|
||||
raise TypeError("Can't handle a deleter on an interface that has "
|
||||
|
@ -5524,6 +5640,17 @@ class CGDOMJSProxyHandler_ownPropertyKeys(CGAbstractExternMethod):
|
|||
let unwrapped_proxy = UnwrapProxy(proxy);
|
||||
""")
|
||||
|
||||
if self.descriptor.isMaybeCrossOriginObject():
|
||||
body += dedent(
|
||||
"""
|
||||
if !proxyhandler::is_platform_object_same_origin(cx, proxy) {
|
||||
return proxyhandler::cross_origin_own_property_keys(cx, proxy, &CROSS_ORIGIN_PROPERTIES, props);
|
||||
}
|
||||
|
||||
// Safe to enter the Realm of proxy now.
|
||||
let _ac = JSAutoRealm::new(*cx, proxy.get());
|
||||
""")
|
||||
|
||||
if self.descriptor.operations['IndexedGetter']:
|
||||
body += dedent(
|
||||
"""
|
||||
|
@ -5583,6 +5710,18 @@ class CGDOMJSProxyHandler_getOwnEnumerablePropertyKeys(CGAbstractExternMethod):
|
|||
let unwrapped_proxy = UnwrapProxy(proxy);
|
||||
""")
|
||||
|
||||
if self.descriptor.isMaybeCrossOriginObject():
|
||||
body += dedent(
|
||||
"""
|
||||
if !proxyhandler::is_platform_object_same_origin(cx, proxy) {
|
||||
// There are no enumerable cross-origin props, so we're done.
|
||||
return true;
|
||||
}
|
||||
|
||||
// Safe to enter the Realm of proxy now.
|
||||
let _ac = JSAutoRealm::new(*cx, proxy.get());
|
||||
""")
|
||||
|
||||
if self.descriptor.operations['IndexedGetter']:
|
||||
body += dedent(
|
||||
"""
|
||||
|
@ -5621,6 +5760,18 @@ class CGDOMJSProxyHandler_hasOwn(CGAbstractExternMethod):
|
|||
def getBody(self):
|
||||
indexedGetter = self.descriptor.operations['IndexedGetter']
|
||||
indexed = "let cx = SafeJSContext::from_ptr(cx);\n"
|
||||
|
||||
if self.descriptor.isMaybeCrossOriginObject():
|
||||
indexed += dedent(
|
||||
"""
|
||||
if !proxyhandler::is_platform_object_same_origin(cx, proxy) {
|
||||
return proxyhandler::cross_origin_has_own(cx, proxy, &CROSS_ORIGIN_PROPERTIES, id, bp);
|
||||
}
|
||||
|
||||
// Safe to enter the Realm of proxy now.
|
||||
let _ac = JSAutoRealm::new(*cx, proxy.get());
|
||||
""")
|
||||
|
||||
if indexedGetter:
|
||||
indexed += ("let index = get_array_index_from_id(*cx, Handle::from_raw(id));\n"
|
||||
+ "if let Some(index) = index {\n"
|
||||
|
@ -5682,6 +5833,18 @@ class CGDOMJSProxyHandler_get(CGAbstractExternMethod):
|
|||
|
||||
# https://heycam.github.io/webidl/#LegacyPlatformObjectGetOwnProperty
|
||||
def getBody(self):
|
||||
if self.descriptor.isMaybeCrossOriginObject():
|
||||
maybeCrossOriginGet = dedent(
|
||||
"""
|
||||
if !proxyhandler::is_platform_object_same_origin(cx, proxy) {
|
||||
return proxyhandler::cross_origin_get(cx, proxy, receiver, id, vp);
|
||||
}
|
||||
|
||||
// Safe to enter the Realm of proxy now.
|
||||
let _ac = JSAutoRealm::new(*cx, proxy.get());
|
||||
""")
|
||||
else:
|
||||
maybeCrossOriginGet = ""
|
||||
getFromExpando = """\
|
||||
rooted!(in(*cx) let mut expando = ptr::null_mut::<JSObject>());
|
||||
get_expando_object(proxy, expando.handle_mut());
|
||||
|
@ -5737,6 +5900,9 @@ if !expando.is_null() {
|
|||
//MOZ_ASSERT(!xpc::WrapperFactory::IsXrayWrapper(proxy),
|
||||
//"Should not have a XrayWrapper here");
|
||||
let cx = SafeJSContext::from_ptr(cx);
|
||||
|
||||
%s
|
||||
|
||||
let proxy_lt = Handle::from_raw(proxy);
|
||||
let vp_lt = MutableHandle::from_raw(vp);
|
||||
let id_lt = Handle::from_raw(id);
|
||||
|
@ -5753,7 +5919,7 @@ if found {
|
|||
}
|
||||
%s
|
||||
vp.set(UndefinedValue());
|
||||
return true;""" % (getIndexedOrExpando, getNamed)
|
||||
return true;""" % (maybeCrossOriginGet, getIndexedOrExpando, getNamed)
|
||||
|
||||
def definition_body(self):
|
||||
return CGGeneric(self.getBody())
|
||||
|
@ -6392,6 +6558,9 @@ class CGDescriptor(CGThing):
|
|||
if descriptor.proxy:
|
||||
cgThings.append(CGDefineProxyHandler(descriptor))
|
||||
|
||||
if descriptor.isMaybeCrossOriginObject():
|
||||
cgThings.append(CGCrossOriginProperties(descriptor))
|
||||
|
||||
properties = PropertyArrays(descriptor)
|
||||
|
||||
if defaultToJSONMethod:
|
||||
|
@ -6410,15 +6579,24 @@ class CGDescriptor(CGThing):
|
|||
cgThings.append(CGDOMJSProxyHandler_get(descriptor))
|
||||
cgThings.append(CGDOMJSProxyHandler_hasOwn(descriptor))
|
||||
|
||||
if descriptor.operations['IndexedSetter'] or descriptor.operations['NamedSetter']:
|
||||
if descriptor.isMaybeCrossOriginObject() or descriptor.operations['IndexedSetter'] or \
|
||||
descriptor.operations['NamedSetter']:
|
||||
cgThings.append(CGDOMJSProxyHandler_defineProperty(descriptor))
|
||||
|
||||
# We want to prevent indexed deleters from compiling at all.
|
||||
assert not descriptor.operations['IndexedDeleter']
|
||||
|
||||
if descriptor.operations['NamedDeleter']:
|
||||
if descriptor.isMaybeCrossOriginObject() or descriptor.operations['NamedDeleter']:
|
||||
cgThings.append(CGDOMJSProxyHandler_delete(descriptor))
|
||||
|
||||
if descriptor.isMaybeCrossOriginObject():
|
||||
# TODO: CGDOMJSProxyHandler_getPrototype(descriptor),
|
||||
# TODO: CGDOMJSProxyHandler_getPrototypeIfOrdinary(descriptor),
|
||||
# TODO: CGDOMJSProxyHandler_setPrototype(descriptor),
|
||||
# TODO: CGDOMJSProxyHandler_setImmutablePrototype(descriptor),
|
||||
# TODO: CGDOMJSProxyHandler_set(descriptor),
|
||||
pass
|
||||
|
||||
# cgThings.append(CGDOMJSProxyHandler(descriptor))
|
||||
# cgThings.append(CGIsMethod(descriptor))
|
||||
pass
|
||||
|
|
|
@ -299,6 +299,9 @@ class Descriptor(DescriptorProvider):
|
|||
if iface:
|
||||
iface.setUserData('hasConcreteDescendant', True)
|
||||
|
||||
if self.isMaybeCrossOriginObject():
|
||||
self.proxy = True
|
||||
|
||||
if self.proxy:
|
||||
iface = self.interface
|
||||
while iface.parent:
|
||||
|
@ -404,6 +407,11 @@ class Descriptor(DescriptorProvider):
|
|||
def supportsIndexedProperties(self):
|
||||
return self.operations['IndexedGetter'] is not None
|
||||
|
||||
def isMaybeCrossOriginObject(self):
|
||||
# If we're isGlobal and have cross-origin members, we're a Window, and
|
||||
# that's not a cross-origin object. The WindowProxy is.
|
||||
return self.concrete and self.interface.hasCrossOriginMembers and not self.isGlobal()
|
||||
|
||||
def hasDescendants(self):
|
||||
return (self.interface.getUserData("hasConcreteDescendant", False)
|
||||
or self.interface.getUserData("hasProxyDescendant", False))
|
||||
|
|
|
@ -7,24 +7,43 @@
|
|||
#![deny(missing_docs)]
|
||||
|
||||
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::utils::delete_property_by_id;
|
||||
use js::glue::GetProxyHandlerFamily;
|
||||
use crate::dom::globalscope::GlobalScope;
|
||||
use crate::realms::{AlreadyInRealm, InRealm};
|
||||
use crate::script_runtime::JSContext as SafeJSContext;
|
||||
use js::conversions::ToJSValConvertible;
|
||||
use js::glue::{
|
||||
GetProxyHandler, GetProxyHandlerFamily, InvokeGetOwnPropertyDescriptor, RUST_SYMBOL_TO_JSID,
|
||||
};
|
||||
use js::glue::{GetProxyPrivate, SetProxyPrivate};
|
||||
use js::jsapi;
|
||||
use js::jsapi::GetStaticPrototype;
|
||||
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::JS_AtomizeAndPinString;
|
||||
use js::jsapi::JS_DefinePropertyById;
|
||||
use js::jsapi::JS_GetOwnPropertyDescriptorById;
|
||||
use js::jsapi::JS_IsExceptionPending;
|
||||
use js::jsapi::MutableHandle as RawMutableHandle;
|
||||
use js::jsapi::MutableHandleIdVector as RawMutableHandleIdVector;
|
||||
use js::jsapi::MutableHandleObject as RawMutableHandleObject;
|
||||
use js::jsapi::MutableHandleValue as RawMutableHandleValue;
|
||||
use js::jsapi::ObjectOpResult;
|
||||
use js::jsapi::{jsid, GetObjectRealmOrNull, GetRealmPrincipals, JSFunctionSpec, JSPropertySpec};
|
||||
use js::jsapi::{DOMProxyShadowsResult, JSContext, JSObject, PropertyDescriptor};
|
||||
use js::jsapi::{GetWellKnownSymbol, SymbolCode};
|
||||
use js::jsapi::{JSErrNum, SetDOMProxyInformation};
|
||||
use js::jsval::ObjectValue;
|
||||
use js::jsval::UndefinedValue;
|
||||
use js::rust::wrappers::JS_AlreadyHasOwnPropertyById;
|
||||
use js::rust::wrappers::JS_NewObjectWithGivenProto;
|
||||
use js::rust::{Handle, HandleObject, MutableHandle, MutableHandleObject};
|
||||
use std::ptr;
|
||||
use js::rust::wrappers::{AppendToIdVector, RUST_INTERNED_STRING_TO_JSID};
|
||||
use js::rust::{get_context_realm, Handle, HandleObject, MutableHandle, MutableHandleObject};
|
||||
use std::{ffi::CStr, ptr};
|
||||
|
||||
/// Determine if this id shadows any existing properties for this proxy.
|
||||
pub unsafe extern "C" fn shadow_check_callback(
|
||||
|
@ -177,3 +196,322 @@ pub fn fill_property_descriptor(
|
|||
desc.getter = None;
|
||||
desc.setter = None;
|
||||
}
|
||||
|
||||
/// <https://html.spec.whatwg.org/multipage/#isplatformobjectsameorigin-(-o-)>
|
||||
pub unsafe fn is_platform_object_same_origin(cx: SafeJSContext, obj: RawHandleObject) -> bool {
|
||||
let subject_realm = get_context_realm(*cx);
|
||||
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_origin = subject.origin();
|
||||
let obj_origin = obj.origin();
|
||||
|
||||
subject_origin.same_origin_domain(&obj_origin)
|
||||
}
|
||||
|
||||
/// Report a cross-origin denial for a property, Always returns `false`, so it
|
||||
/// can be used as `return report_cross_origin_denial(...);`.
|
||||
///
|
||||
/// 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 {
|
||||
let in_realm_proof = AlreadyInRealm::assert_for_cx(cx);
|
||||
if !JS_IsExceptionPending(*cx) {
|
||||
let global = GlobalScope::from_context(*cx, InRealm::Already(&in_realm_proof));
|
||||
// TODO: include `id` and `access` in the exception message
|
||||
throw_dom_exception(cx, &*global, Error::Security);
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
/// Property and method specs that correspond to the elements of
|
||||
/// [`CrossOriginProperties(O)`].
|
||||
///
|
||||
/// [`CrossOriginProperties(O)`]: https://html.spec.whatwg.org/multipage/#crossoriginproperties-(-o-)
|
||||
pub struct CrossOriginProperties {
|
||||
pub attributes: &'static [JSPropertySpec],
|
||||
pub methods: &'static [JSFunctionSpec],
|
||||
}
|
||||
|
||||
impl CrossOriginProperties {
|
||||
/// Enumerate the property keys defined by `self`.
|
||||
fn keys(&self) -> impl Iterator<Item = *const std::os::raw::c_char> + '_ {
|
||||
// Safety: All cross-origin property keys are strings, not symbols
|
||||
self.attributes
|
||||
.iter()
|
||||
.map(|spec| unsafe { spec.name.string_ })
|
||||
.chain(self.methods.iter().map(|spec| unsafe { spec.name.string_ }))
|
||||
.filter(|ptr| !ptr.is_null())
|
||||
}
|
||||
}
|
||||
|
||||
/// Implementation of [`CrossOriginOwnPropertyKeys`].
|
||||
///
|
||||
/// [`CrossOriginOwnPropertyKeys`]: https://html.spec.whatwg.org/multipage/#crossoriginownpropertykeys-(-o-)
|
||||
pub unsafe fn cross_origin_own_property_keys(
|
||||
cx: SafeJSContext,
|
||||
_proxy: RawHandleObject,
|
||||
cross_origin_properties: &'static CrossOriginProperties,
|
||||
props: RawMutableHandleIdVector,
|
||||
) -> bool {
|
||||
for key in cross_origin_properties.keys() {
|
||||
let jsstring = JS_AtomizeAndPinString(*cx, key);
|
||||
rooted!(in(*cx) let rooted = jsstring);
|
||||
rooted!(in(*cx) let mut rooted_jsid: jsid);
|
||||
RUST_INTERNED_STRING_TO_JSID(*cx, rooted.handle().get(), rooted_jsid.handle_mut());
|
||||
AppendToIdVector(props, rooted_jsid.handle());
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
/// Implementation of [`CrossOriginGet`].
|
||||
///
|
||||
/// [`CrossOriginGet`]: https://html.spec.whatwg.org/multipage/#crossoriginget-(-o,-p,-receiver-)
|
||||
pub unsafe fn cross_origin_get(
|
||||
cx: SafeJSContext,
|
||||
proxy: RawHandleObject,
|
||||
receiver: RawHandleValue,
|
||||
id: RawHandleId,
|
||||
vp: RawMutableHandleValue,
|
||||
) -> bool {
|
||||
// > 1. Let `desc` be `? O.[[GetOwnProperty]](P)`.
|
||||
rooted!(in(*cx) let mut descriptor = PropertyDescriptor::default());
|
||||
if !InvokeGetOwnPropertyDescriptor(
|
||||
GetProxyHandler(*proxy),
|
||||
*cx,
|
||||
proxy,
|
||||
id,
|
||||
descriptor.handle_mut().into(),
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
// let descriptor = descriptor.get();
|
||||
|
||||
// > 2. Assert: `desc` is not undefined.
|
||||
assert!(
|
||||
!descriptor.obj.is_null(),
|
||||
"Callees should throw in all cases when they are not finding \
|
||||
a property decriptor"
|
||||
);
|
||||
|
||||
// > 3. If `! IsDataDescriptor(desc)` is true, then return `desc.[[Value]]`.
|
||||
if is_data_descriptor(&descriptor) {
|
||||
vp.set(descriptor.value);
|
||||
return true;
|
||||
}
|
||||
|
||||
// > 4. Assert: `IsAccessorDescriptor(desc)` is `true`.
|
||||
assert!(is_accessor_descriptor(&descriptor));
|
||||
|
||||
// > 5. Let `getter` be `desc.[[Get]]`.
|
||||
// >
|
||||
// > 6. If `getter` is `undefined`, then throw a `SecurityError`
|
||||
// > `DOMException`.
|
||||
rooted!(in(*cx) let mut getter = ptr::null_mut::<JSObject>());
|
||||
get_getter_object(&descriptor, getter.handle_mut().into());
|
||||
if getter.get().is_null() {
|
||||
return report_cross_origin_denial(cx, id, "get");
|
||||
}
|
||||
|
||||
rooted!(in(*cx) let mut getter_jsval = UndefinedValue());
|
||||
getter.get().to_jsval(*cx, getter_jsval.handle_mut());
|
||||
|
||||
// > 7. Return `? Call(getter, Receiver)`.
|
||||
jsapi::Call(
|
||||
*cx,
|
||||
receiver,
|
||||
getter_jsval.handle().into(),
|
||||
&jsapi::HandleValueArray::new(),
|
||||
vp,
|
||||
)
|
||||
}
|
||||
|
||||
unsafe fn get_getter_object(d: &PropertyDescriptor, out: RawMutableHandleObject) {
|
||||
if (d.attrs & jsapi::JSPROP_GETTER as u32) != 0 {
|
||||
out.set(std::mem::transmute(d.getter));
|
||||
}
|
||||
}
|
||||
|
||||
/// <https://tc39.es/ecma262/#sec-isaccessordescriptor>
|
||||
fn is_accessor_descriptor(d: &PropertyDescriptor) -> bool {
|
||||
d.attrs & (jsapi::JSPROP_GETTER as u32 | jsapi::JSPROP_SETTER as u32) != 0
|
||||
}
|
||||
|
||||
/// <https://tc39.es/ecma262/#sec-isdatadescriptor>
|
||||
fn is_data_descriptor(d: &PropertyDescriptor) -> bool {
|
||||
let is_accessor = is_accessor_descriptor(d);
|
||||
let is_generic = d.attrs &
|
||||
(jsapi::JSPROP_GETTER as u32 |
|
||||
jsapi::JSPROP_SETTER as u32 |
|
||||
jsapi::JSPROP_IGNORE_READONLY |
|
||||
jsapi::JSPROP_IGNORE_VALUE) ==
|
||||
jsapi::JSPROP_IGNORE_READONLY | jsapi::JSPROP_IGNORE_VALUE;
|
||||
!is_accessor && !is_generic
|
||||
}
|
||||
|
||||
/// Evaluate `CrossOriginGetOwnPropertyHelper(proxy, id) != null`.
|
||||
pub unsafe fn cross_origin_has_own(
|
||||
cx: SafeJSContext,
|
||||
_proxy: RawHandleObject,
|
||||
cross_origin_properties: &'static CrossOriginProperties,
|
||||
id: RawHandleId,
|
||||
bp: *mut bool,
|
||||
) -> bool {
|
||||
// TODO: Once we have the slot for the holder, it'd be more efficient to
|
||||
// use `ensure_cross_origin_property_holder`.
|
||||
*bp = if let Some(key) =
|
||||
crate::dom::bindings::conversions::jsid_to_string(*cx, Handle::from_raw(id))
|
||||
{
|
||||
cross_origin_properties.keys().any(|defined_key| {
|
||||
let defined_key = CStr::from_ptr(defined_key);
|
||||
defined_key.to_bytes() == key.as_bytes()
|
||||
})
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
/// Implementation of [`CrossOriginGetOwnPropertyHelper`].
|
||||
///
|
||||
/// `cx` and `obj` are expected to be different-Realm here. `obj` can be a
|
||||
/// `WindowProxy` or a `Location` or a `DissimilarOrigin*` proxy for one of
|
||||
/// those.
|
||||
///
|
||||
/// [`CrossOriginGetOwnPropertyHelper`]: https://html.spec.whatwg.org/multipage/#crossorigingetownpropertyhelper-(-o,-p-)
|
||||
pub unsafe fn cross_origin_get_own_property_helper(
|
||||
cx: SafeJSContext,
|
||||
proxy: RawHandleObject,
|
||||
cross_origin_properties: &'static CrossOriginProperties,
|
||||
id: RawHandleId,
|
||||
mut desc: RawMutableHandle<PropertyDescriptor>,
|
||||
) -> bool {
|
||||
rooted!(in(*cx) let mut holder = ptr::null_mut::<JSObject>());
|
||||
|
||||
ensure_cross_origin_property_holder(
|
||||
cx,
|
||||
proxy,
|
||||
cross_origin_properties,
|
||||
holder.handle_mut().into(),
|
||||
);
|
||||
|
||||
if !JS_GetOwnPropertyDescriptorById(*cx, holder.handle().into(), id, desc) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if !desc.obj.is_null() {
|
||||
desc.obj = proxy.get();
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
/// Implementation of [`CrossOriginPropertyFallback`].
|
||||
///
|
||||
|
||||
/// [`CrossOriginPropertyFallback`]: https://html.spec.whatwg.org/multipage/#crossoriginpropertyfallback-(-p-)
|
||||
pub unsafe fn cross_origin_property_fallback(
|
||||
cx: SafeJSContext,
|
||||
proxy: RawHandleObject,
|
||||
id: RawHandleId,
|
||||
mut desc: RawMutableHandle<PropertyDescriptor>,
|
||||
) -> bool {
|
||||
assert!(desc.obj.is_null(), "why are we being called?");
|
||||
|
||||
// > 1. If P is `then`, `@@toStringTag`, `@@hasInstance`, or
|
||||
// > `@@isConcatSpreadable`, then return `PropertyDescriptor{ [[Value]]:
|
||||
// > undefined, [[Writable]]: false, [[Enumerable]]: false,
|
||||
// > [[Configurable]]: true }`.
|
||||
if is_cross_origin_allowlisted_prop(cx, id) {
|
||||
*desc = PropertyDescriptor {
|
||||
getter: None,
|
||||
setter: None,
|
||||
value: UndefinedValue(),
|
||||
attrs: jsapi::JSPROP_READONLY as u32,
|
||||
obj: proxy.get(),
|
||||
};
|
||||
return true;
|
||||
}
|
||||
|
||||
// > 2. Throw a `SecurityError` `DOMException`.
|
||||
report_cross_origin_denial(cx, id, "access")
|
||||
}
|
||||
|
||||
unsafe fn is_cross_origin_allowlisted_prop(cx: SafeJSContext, id: RawHandleId) -> bool {
|
||||
const ALLOWLISTED_SYMBOL_CODES: &[SymbolCode] = &[
|
||||
SymbolCode::toStringTag,
|
||||
SymbolCode::hasInstance,
|
||||
SymbolCode::isConcatSpreadable,
|
||||
];
|
||||
|
||||
crate::dom::bindings::conversions::jsid_to_string(*cx, Handle::from_raw(id))
|
||||
.filter(|st| st == "then")
|
||||
.is_some() ||
|
||||
{
|
||||
rooted!(in(*cx) let mut allowed_id: jsid);
|
||||
ALLOWLISTED_SYMBOL_CODES.iter().any(|&allowed_code| {
|
||||
RUST_SYMBOL_TO_JSID(
|
||||
GetWellKnownSymbol(*cx, allowed_code),
|
||||
allowed_id.handle_mut().into(),
|
||||
);
|
||||
// `jsid`s containing `JS::Symbol *` can be compared by
|
||||
// referential equality
|
||||
allowed_id.get().asBits == id.asBits
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the holder for cross-origin properties for the current global of the
|
||||
/// `JSContext`, creating one and storing it in a slot of the proxy object if it
|
||||
/// doesn't exist yet.
|
||||
///
|
||||
/// This essentially creates a cache of [`CrossOriginGetOwnPropertyHelper`]'s
|
||||
/// results for all property keys.
|
||||
///
|
||||
/// [`CrossOriginGetOwnPropertyHelper`]: https://html.spec.whatwg.org/multipage/#crossorigingetownpropertyhelper-(-o,-p-)
|
||||
unsafe fn ensure_cross_origin_property_holder(
|
||||
cx: SafeJSContext,
|
||||
_proxy: RawHandleObject,
|
||||
cross_origin_properties: &'static CrossOriginProperties,
|
||||
out_holder: RawMutableHandleObject,
|
||||
) -> bool {
|
||||
// TODO: We don't have the slot to store the holder yet. For now,
|
||||
// the holder is constructed every time this function is called,
|
||||
// which is not only inefficient but also deviates from the
|
||||
// specification in a subtle yet observable way.
|
||||
|
||||
// Create a holder for the current Realm
|
||||
out_holder.set(jsapi::JS_NewObjectWithGivenProto(
|
||||
*cx,
|
||||
ptr::null_mut(),
|
||||
RawHandleObject::null(),
|
||||
));
|
||||
|
||||
if out_holder.get().is_null() ||
|
||||
!jsapi::JS_DefineProperties(
|
||||
*cx,
|
||||
out_holder.handle(),
|
||||
cross_origin_properties.attributes.as_ptr(),
|
||||
) ||
|
||||
!jsapi::JS_DefineFunctions(
|
||||
*cx,
|
||||
out_holder.handle(),
|
||||
cross_origin_properties.methods.as_ptr(),
|
||||
)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO: Store the holder in the slot that we don't have yet.
|
||||
|
||||
true
|
||||
}
|
||||
|
|
|
@ -1084,6 +1084,9 @@ pub fn new_window_proxy_handler() -> WindowProxyHandler {
|
|||
// These traps often throw security errors, and only pass on calls to methods
|
||||
// defined in the DissimilarOriginWindow IDL.
|
||||
|
||||
// TODO: reuse the infrastructure in `proxyhandler.rs`. For starters, the calls
|
||||
// to this function should be replaced with those to
|
||||
// `report_cross_origin_denial`.
|
||||
#[allow(unsafe_code)]
|
||||
unsafe fn throw_security_error(cx: *mut JSContext, realm: InRealm) -> bool {
|
||||
if !JS_IsExceptionPending(cx) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue