mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-24 13:21:05 +00:00
Bug 946906 part 8. When getting a cacheable property off a DOM Xray, cache it on the Xray's expando object. r=bholley
This commit is contained in:
parent
13a81b596c
commit
13514334ba
@ -1907,6 +1907,21 @@ XrayGetExpandoClass(JSContext* cx, JS::Handle<JSObject*> obj)
|
||||
return nativePropertyHooks->mXrayExpandoClass;
|
||||
}
|
||||
|
||||
JSObject*
|
||||
GetCachedSlotStorageObjectSlow(JSContext* cx, JS::Handle<JSObject*> obj,
|
||||
bool* isXray)
|
||||
{
|
||||
if (!xpc::WrapperFactory::IsXrayWrapper(obj)) {
|
||||
JSObject* retval = js::UncheckedUnwrap(obj, /* stopAtWindowProxy = */ false);
|
||||
MOZ_ASSERT(IsDOMObject(retval));
|
||||
*isXray = false;
|
||||
return retval;
|
||||
}
|
||||
|
||||
*isXray = true;
|
||||
return xpc::EnsureXrayExpandoObject(cx, obj);;
|
||||
}
|
||||
|
||||
DEFINE_XRAY_EXPANDO_CLASS(, DefaultXrayExpandoObjectClass, 0);
|
||||
|
||||
NativePropertyHooks sEmptyNativePropertyHooks = {
|
||||
|
@ -2406,6 +2406,35 @@ XrayGetNativeProto(JSContext* cx, JS::Handle<JSObject*> obj,
|
||||
const JSClass*
|
||||
XrayGetExpandoClass(JSContext* cx, JS::Handle<JSObject*> obj);
|
||||
|
||||
/**
|
||||
* Get the object which should be used to cache the return value of a property
|
||||
* getter in the case of a [Cached] or [StoreInSlot] property. `obj` is the
|
||||
* `this` value for our property getter that we're working with.
|
||||
*
|
||||
* This function can return null on failure to allocate the object, throwing on
|
||||
* the JSContext in the process.
|
||||
*
|
||||
* The isXray outparam will be set to true if obj is an Xray and false
|
||||
* otherwise.
|
||||
*
|
||||
* Note that the Slow version should only be called from
|
||||
* GetCachedSlotStorageObject.
|
||||
*/
|
||||
JSObject*
|
||||
GetCachedSlotStorageObjectSlow(JSContext* cx, JS::Handle<JSObject*> obj,
|
||||
bool* isXray);
|
||||
|
||||
inline JSObject*
|
||||
GetCachedSlotStorageObject(JSContext* cx, JS::Handle<JSObject*> obj,
|
||||
bool* isXray) {
|
||||
if (IsDOMObject(obj)) {
|
||||
*isXray = false;
|
||||
return obj;
|
||||
}
|
||||
|
||||
return GetCachedSlotStorageObjectSlow(cx, obj, isXray);
|
||||
}
|
||||
|
||||
extern NativePropertyHooks sEmptyNativePropertyHooks;
|
||||
|
||||
extern const js::ClassOps sBoringInterfaceObjectClassClassOps;
|
||||
|
@ -41,6 +41,13 @@ def memberXrayExpandoReservedSlot(member, descriptor):
|
||||
member.slotIndices[descriptor.interface.identifier.name])
|
||||
|
||||
|
||||
def mayUseXrayExpandoSlots(attr):
|
||||
assert not attr.getExtendedAttribute("NewObject")
|
||||
# For attributes whose type is a Gecko interface we always use
|
||||
# slots on the reflector for caching.
|
||||
return not attr.type.isGeckoInterface()
|
||||
|
||||
|
||||
def toStringBool(arg):
|
||||
return str(not not arg).lower()
|
||||
|
||||
@ -7523,7 +7530,11 @@ class CGPerSignatureCall(CGThing):
|
||||
'returnsNewObject': returnsNewObject,
|
||||
'isConstructorRetval': self.isConstructor,
|
||||
'successCode': successCode,
|
||||
'obj': "reflector" if setSlot else "obj"
|
||||
# 'obj' in this dictionary is the thing whose compartment we are
|
||||
# trying to do the to-JS conversion in. We're going to put that
|
||||
# thing in a variable named "conversionScope" if setSlot is true.
|
||||
# Otherwise, just use "obj" for lack of anything better.
|
||||
'obj': "conversionScope" if setSlot else "obj"
|
||||
}
|
||||
try:
|
||||
wrapCode += wrapForType(self.returnType, self.descriptor, resultTemplateValues)
|
||||
@ -7534,16 +7545,22 @@ class CGPerSignatureCall(CGThing):
|
||||
self.descriptor.interface.identifier.name,
|
||||
self.idlNode.identifier.name))
|
||||
if setSlot:
|
||||
# We need to make sure that our initial wrapping is done in the
|
||||
# reflector compartment, but that we finally set args.rval() in the
|
||||
# caller compartment. We also need to make sure that the actual
|
||||
# wrapping steps happen inside a do/while that they can break out
|
||||
# of.
|
||||
# When using a slot on the Xray expando, we need to make sure that
|
||||
# our initial conversion to a JS::Value is done in the caller
|
||||
# compartment. When using a slot on our reflector, we want to do
|
||||
# the conversion in the compartment of that reflector (that is,
|
||||
# slotStorage). In both cases we want to make sure that we finally
|
||||
# set up args.rval() to be in the caller compartment. We also need
|
||||
# to make sure that the conversion steps happen inside a do/while
|
||||
# that they can break out of on success.
|
||||
#
|
||||
# Of course we always have to wrap the value into the slotStorage
|
||||
# compartment before we store it in slotStorage.
|
||||
|
||||
# postSteps are the steps that run while we're still in the
|
||||
# reflector compartment but after we've finished the initial
|
||||
# wrapping into args.rval().
|
||||
postSteps = ""
|
||||
# postConversionSteps are the steps that run while we're still in
|
||||
# the compartment we do our conversion in but after we've finished
|
||||
# the initial conversion into args.rval().
|
||||
postConversionSteps = ""
|
||||
if self.idlNode.getExtendedAttribute("Frozen"):
|
||||
assert self.idlNode.type.isSequence() or self.idlNode.type.isDictionary()
|
||||
freezeValue = CGGeneric(
|
||||
@ -7554,9 +7571,23 @@ class CGPerSignatureCall(CGThing):
|
||||
if self.idlNode.type.nullable():
|
||||
freezeValue = CGIfWrapper(freezeValue,
|
||||
"args.rval().isObject()")
|
||||
postSteps += freezeValue.define()
|
||||
postSteps += ("js::SetReservedSlot(reflector, %s, args.rval());\n" %
|
||||
memberReservedSlot(self.idlNode, self.descriptor))
|
||||
postConversionSteps += freezeValue.define()
|
||||
|
||||
# slotStorageSteps are steps that run once we have entered the
|
||||
# slotStorage compartment.
|
||||
slotStorageSteps= fill(
|
||||
"""
|
||||
// Make a copy so that we don't do unnecessary wrapping on args.rval().
|
||||
JS::Rooted<JS::Value> storedVal(cx, args.rval());
|
||||
if (!${maybeWrap}(cx, &storedVal)) {
|
||||
return false;
|
||||
}
|
||||
js::SetReservedSlot(slotStorage, slotIndex, storedVal);
|
||||
""",
|
||||
maybeWrap=getMaybeWrapValueFuncForType(self.idlNode.type))
|
||||
|
||||
checkForXray = mayUseXrayExpandoSlots(self.idlNode)
|
||||
|
||||
# For the case of Cached attributes, go ahead and preserve our
|
||||
# wrapper if needed. We need to do this because otherwise the
|
||||
# wrapper could get garbage-collected and the cached value would
|
||||
@ -7567,22 +7598,48 @@ class CGPerSignatureCall(CGThing):
|
||||
# already-preserved wrapper.
|
||||
if (self.idlNode.getExtendedAttribute("Cached") and
|
||||
self.descriptor.wrapperCache):
|
||||
postSteps += "PreserveWrapper(self);\n"
|
||||
preserveWrapper = dedent(
|
||||
"""
|
||||
PreserveWrapper(self);
|
||||
""")
|
||||
if checkForXray:
|
||||
preserveWrapper = fill(
|
||||
"""
|
||||
if (!isXray) {
|
||||
// In the Xray case we don't need to do this, because getting the
|
||||
// expando object already preserved our wrapper.
|
||||
$*{preserveWrapper}
|
||||
}
|
||||
""",
|
||||
preserveWrapper=preserveWrapper)
|
||||
slotStorageSteps += preserveWrapper
|
||||
|
||||
if checkForXray:
|
||||
conversionScope = "isXray ? obj : slotStorage"
|
||||
else:
|
||||
conversionScope = "slotStorage"
|
||||
|
||||
wrapCode = fill(
|
||||
"""
|
||||
{ // Make sure we wrap and store in the slot in reflector's compartment
|
||||
JSAutoCompartment ac(cx, reflector);
|
||||
{
|
||||
JS::Rooted<JSObject*> conversionScope(cx, ${conversionScope});
|
||||
JSAutoCompartment ac(cx, conversionScope);
|
||||
do { // block we break out of when done wrapping
|
||||
$*{wrapCode}
|
||||
} while (0);
|
||||
$*{postSteps}
|
||||
$*{postConversionSteps}
|
||||
}
|
||||
{ // And now store things in the compartment of our slotStorage.
|
||||
JSAutoCompartment ac(cx, slotStorage);
|
||||
$*{slotStorageSteps}
|
||||
}
|
||||
// And now make sure args.rval() is in the caller compartment
|
||||
return ${maybeWrap}(cx, args.rval());
|
||||
""",
|
||||
conversionScope=conversionScope,
|
||||
wrapCode=wrapCode,
|
||||
postSteps=postSteps,
|
||||
postConversionSteps=postConversionSteps,
|
||||
slotStorageSteps=slotStorageSteps,
|
||||
maybeWrap=getMaybeWrapValueFuncForType(self.idlNode.type))
|
||||
return wrapCode
|
||||
|
||||
@ -8076,7 +8133,13 @@ class CGNavigatorGetterCall(CGPerSignatureCall):
|
||||
True, descriptor, attr, getter=True)
|
||||
|
||||
def getArguments(self):
|
||||
return [(FakeArgument(BuiltinTypes[IDLBuiltinType.Types.object], self.idlNode), "reflector")]
|
||||
# The navigator object should be associated with the global of
|
||||
# the navigator it's coming from, which will be the global of
|
||||
# the object whose slot it gets cached in. That's stored in
|
||||
# "slotStorage".
|
||||
return [(FakeArgument(BuiltinTypes[IDLBuiltinType.Types.object],
|
||||
self.idlNode),
|
||||
"slotStorage")]
|
||||
|
||||
|
||||
class FakeIdentifier():
|
||||
@ -8690,27 +8753,64 @@ class CGSpecializedGetter(CGAbstractStaticMethod):
|
||||
"can't use our slot for property '%s'!" %
|
||||
(self.descriptor.interface.identifier.name,
|
||||
self.attr.identifier.name))
|
||||
prefix = fill(
|
||||
|
||||
# We're going to store this return value in a slot on some object,
|
||||
# to cache it. The question is, which object? For dictionary and
|
||||
# sequence return values, we want to use a slot on the Xray expando
|
||||
# if we're called via Xrays, and a slot on our reflector otherwise.
|
||||
# On the other hand, when dealing with some interfacce types
|
||||
# (navigator properties, window.document) we want to avoid calling
|
||||
# the getter more than once. In the case of navigator properties
|
||||
# that's because the getter actually creates a new object each time.
|
||||
# In the case of window.document, it's because the getter can start
|
||||
# returning null, which would get hidden in he non-Xray case by the
|
||||
# fact that it's [StoreOnSlot], so the cached version is always
|
||||
# around.
|
||||
#
|
||||
# The upshot is that we use the reflector slot for any getter whose
|
||||
# type is a gecko interface, whether we're called via Xrays or not.
|
||||
# Since [Cached] and [StoreInSlot] cannot be used with "NewObject",
|
||||
# we know that in the interface type case the returned object is
|
||||
# wrappercached. So creating Xrays to it is reasonable.
|
||||
if mayUseXrayExpandoSlots(self.attr):
|
||||
prefix = fill(
|
||||
"""
|
||||
// Have to either root across the getter call or reget after.
|
||||
bool isXray;
|
||||
JS::Rooted<JSObject*> slotStorage(cx, GetCachedSlotStorageObject(cx, obj, &isXray));
|
||||
if (!slotStorage) {
|
||||
return false;
|
||||
}
|
||||
const size_t slotIndex = isXray ? ${xraySlotIndex} : ${slotIndex};
|
||||
""",
|
||||
xraySlotIndex=memberXrayExpandoReservedSlot(self.attr,
|
||||
self.descriptor),
|
||||
slotIndex=memberReservedSlot(self.attr, self.descriptor))
|
||||
else:
|
||||
prefix = fill(
|
||||
"""
|
||||
// Have to either root across the getter call or reget after.
|
||||
JS::Rooted<JSObject*> slotStorage(cx, js::UncheckedUnwrap(obj, /* stopAtWindowProxy = */ false));
|
||||
MOZ_ASSERT(IsDOMObject(slotStorage));
|
||||
const size_t slotIndex = ${slotIndex};
|
||||
""",
|
||||
slotIndex=memberReservedSlot(self.attr, self.descriptor))
|
||||
|
||||
prefix += fill(
|
||||
"""
|
||||
// Have to either root across the getter call or reget after.
|
||||
JS::Rooted<JSObject*> reflector(cx);
|
||||
// Safe to do an unchecked unwrap, since we've gotten this far.
|
||||
// Also make sure to unwrap outer windows, since we want the
|
||||
// real DOM object.
|
||||
reflector = IsDOMObject(obj) ? obj : js::UncheckedUnwrap(obj, /* stopAtWindowProxy = */ false);
|
||||
MOZ_ASSERT(JSCLASS_RESERVED_SLOTS(js::GetObjectClass(slotStorage)) > slotIndex);
|
||||
{
|
||||
// Scope for cachedVal
|
||||
JS::Value cachedVal = js::GetReservedSlot(reflector, ${slot});
|
||||
JS::Value cachedVal = js::GetReservedSlot(slotStorage, slotIndex);
|
||||
if (!cachedVal.isUndefined()) {
|
||||
args.rval().set(cachedVal);
|
||||
// The cached value is in the compartment of reflector,
|
||||
// The cached value is in the compartment of slotStorage,
|
||||
// so wrap into the caller compartment as needed.
|
||||
return ${maybeWrap}(cx, args.rval());
|
||||
}
|
||||
}
|
||||
|
||||
""",
|
||||
slot=memberReservedSlot(self.attr, self.descriptor),
|
||||
maybeWrap=getMaybeWrapValueFuncForType(self.attr.type))
|
||||
else:
|
||||
prefix = ""
|
||||
|
@ -1270,6 +1270,17 @@ ClearXrayExpandoSlots(JSObject* target, size_t slotIndex)
|
||||
}
|
||||
}
|
||||
|
||||
JSObject*
|
||||
EnsureXrayExpandoObject(JSContext* cx, JS::HandleObject wrapper)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MOZ_ASSERT(GetXrayTraits(wrapper) == &DOMXrayTraits::singleton);
|
||||
MOZ_ASSERT(IsXrayWrapper(wrapper));
|
||||
|
||||
RootedObject target(cx, DOMXrayTraits::singleton.getTargetObject(wrapper));
|
||||
return DOMXrayTraits::singleton.ensureExpandoObject(cx, wrapper, target);
|
||||
}
|
||||
|
||||
const JSClass*
|
||||
XrayTraits::getExpandoClass(JSContext* cx, HandleObject target) const
|
||||
{
|
||||
|
@ -604,6 +604,14 @@ extern const JSClassOps XrayExpandoObjectClassOps;
|
||||
void
|
||||
ClearXrayExpandoSlots(JSObject* target, size_t slotIndex);
|
||||
|
||||
/*
|
||||
* Ensure the given wrapper has an expando object and return it. This can
|
||||
* return null on failure. Will only be called when "wrapper" is an Xray for a
|
||||
* DOM object.
|
||||
*/
|
||||
JSObject*
|
||||
EnsureXrayExpandoObject(JSContext* cx, JS::HandleObject wrapper);
|
||||
|
||||
} // namespace xpc
|
||||
|
||||
#endif
|
||||
|
Loading…
Reference in New Issue
Block a user