Bug 945416 part 3. Make GlobalResolve fill in a JSPropertyDescriptor that nsWindowSH::NewResolve then uses to actually define the property. r=bholley,peterv

This commit is contained in:
Boris Zbarsky 2014-01-29 22:33:23 -08:00
parent 2cc31b028d
commit 3ecc0d422f
3 changed files with 181 additions and 113 deletions

View File

@ -1630,8 +1630,8 @@ ResolvePrototype(nsIXPConnect *aXPConnect, nsGlobalWindow *aWin, JSContext *cx,
const nsDOMClassInfoData *ci_data,
const nsGlobalNameStruct *name_struct,
nsScriptNameSpaceManager *nameSpaceManager,
JSObject *dot_prototype, bool install, bool *did_resolve);
JSObject *dot_prototype,
JS::MutableHandle<JSPropertyDescriptor> ctorDesc);
NS_IMETHODIMP
nsDOMClassInfo::PostCreatePrototype(JSContext * cx, JSObject * aProto)
@ -1727,19 +1727,30 @@ nsDOMClassInfo::PostCreatePrototype(JSContext * cx, JSObject * aProto)
}
// Don't overwrite a property set by content.
bool found;
bool contentDefinedProperty;
if (!::JS_AlreadyHasOwnUCProperty(cx, global, reinterpret_cast<const jschar*>(mData->mNameUTF16),
NS_strlen(mData->mNameUTF16), &found)) {
NS_strlen(mData->mNameUTF16),
&contentDefinedProperty)) {
return NS_ERROR_FAILURE;
}
nsScriptNameSpaceManager *nameSpaceManager = GetNameSpaceManager();
NS_ENSURE_TRUE(nameSpaceManager, NS_OK);
bool unused;
return ResolvePrototype(sXPConnect, win, cx, global, mData->mNameUTF16,
mData, nullptr, nameSpaceManager, proto, !found,
&unused);
JS::Rooted<JSPropertyDescriptor> desc(cx);
nsresult rv = ResolvePrototype(sXPConnect, win, cx, global, mData->mNameUTF16,
mData, nullptr, nameSpaceManager, proto,
&desc);
NS_ENSURE_SUCCESS(rv, rv);
if (!contentDefinedProperty && desc.object() && !desc.value().isUndefined() &&
!JS_DefineUCProperty(cx, global, mData->mNameUTF16,
NS_strlen(mData->mNameUTF16),
desc.value(), desc.getter(), desc.setter(),
desc.attributes())) {
return NS_ERROR_UNEXPECTED;
}
return NS_OK;
}
// static
@ -2181,20 +2192,6 @@ public:
JS::Handle<JSObject*> obj, const jsval &val, bool *bp,
bool *_retval);
nsresult Install(JSContext *cx, JS::Handle<JSObject*> target,
JS::Handle<JS::Value> aThisAsVal)
{
JS::Rooted<JS::Value> thisAsVal(cx, aThisAsVal);
// The 'attrs' argument used to be JSPROP_PERMANENT. See bug 628612.
bool ok = JS_WrapValue(cx, &thisAsVal) &&
::JS_DefineUCProperty(cx, target,
reinterpret_cast<const jschar *>(mClassName),
NS_strlen(mClassName), thisAsVal, JS_PropertyStub,
JS_StrictPropertyStub, 0);
return ok ? NS_OK : NS_ERROR_UNEXPECTED;
}
nsresult ResolveInterfaceConstants(JSContext *cx, JS::Handle<JSObject*> obj);
private:
@ -2616,7 +2613,8 @@ ResolvePrototype(nsIXPConnect *aXPConnect, nsGlobalWindow *aWin, JSContext *cx,
const nsDOMClassInfoData *ci_data,
const nsGlobalNameStruct *name_struct,
nsScriptNameSpaceManager *nameSpaceManager,
JSObject* aDot_prototype, bool install, bool *did_resolve)
JSObject* aDot_prototype,
JS::MutableHandle<JSPropertyDescriptor> ctorDesc)
{
JS::Rooted<JSObject*> dot_prototype(cx, aDot_prototype);
NS_ASSERTION(ci_data ||
@ -2635,9 +2633,12 @@ ResolvePrototype(nsIXPConnect *aXPConnect, nsGlobalWindow *aWin, JSContext *cx,
false, &v);
NS_ENSURE_SUCCESS(rv, rv);
if (install) {
rv = constructor->Install(cx, obj, v);
NS_ENSURE_SUCCESS(rv, rv);
FillPropertyDescriptor(ctorDesc, obj, 0, v);
// And make sure we wrap the value into the right compartment. Note that we
// do this with ctorDesc.value(), not with v, because we need v to be in the
// right compartment (that of the reflector of |constructor|) below.
if (!JS_WrapValue(cx, ctorDesc.value())) {
return NS_ERROR_UNEXPECTED;
}
JS::Rooted<JSObject*> class_obj(cx, &v.toObject());
@ -2766,8 +2767,6 @@ ResolvePrototype(nsIXPConnect *aXPConnect, nsGlobalWindow *aWin, JSContext *cx,
return NS_ERROR_UNEXPECTED;
}
*did_resolve = true;
return NS_OK;
}
@ -2801,21 +2800,20 @@ OldBindingConstructorEnabled(const nsGlobalNameStruct *aStruct,
}
static nsresult
DefineComponentsShim(JSContext *cx, JS::Handle<JSObject*> global, nsPIDOMWindow *win);
LookupComponentsShim(JSContext *cx, JS::Handle<JSObject*> global,
nsPIDOMWindow *win,
JS::MutableHandle<JSPropertyDescriptor> desc);
// static
nsresult
nsWindowSH::GlobalResolve(nsGlobalWindow *aWin, JSContext *cx,
JS::Handle<JSObject*> obj, JS::Handle<jsid> id,
bool *did_resolve)
JS::MutableHandle<JSPropertyDescriptor> desc)
{
if (id == XPCJSRuntime::Get()->GetStringID(XPCJSRuntime::IDX_COMPONENTS)) {
*did_resolve = true;
return DefineComponentsShim(cx, obj, aWin);
return LookupComponentsShim(cx, obj, aWin, desc);
}
*did_resolve = false;
nsScriptNameSpaceManager *nameSpaceManager = GetNameSpaceManager();
NS_ENSURE_TRUE(nameSpaceManager, NS_ERROR_NOT_INITIALIZED);
@ -2829,6 +2827,9 @@ nsWindowSH::GlobalResolve(nsGlobalWindow *aWin, JSContext *cx,
return NS_OK;
}
// The class_name had better match our name
MOZ_ASSERT(name.Equals(class_name));
NS_ENSURE_TRUE(class_name, NS_ERROR_UNEXPECTED);
nsresult rv = NS_OK;
@ -2859,39 +2860,73 @@ nsWindowSH::GlobalResolve(nsGlobalWindow *aWin, JSContext *cx,
return NS_OK;
}
Maybe<JSAutoCompartment> ac;
JS::Rooted<JSObject*> global(cx);
bool isXray = xpc::WrapperFactory::IsXrayWrapper(obj);
if (isXray) {
global = js::CheckedUnwrap(obj, /* stopAtOuter = */ false);
// The DOM constructor resolve machinery interacts with Xrays in tricky
// ways, and there are some asymmetries that are important to understand.
//
// In the regular (non-Xray) case, we only want to resolve constructors
// once (so that if they're deleted, they don't reappear). We do this by
// stashing the constructor in a slot on the global, such that we can see
// during resolve whether we've created it already. This is rather
// memory-intensive, so we don't try to maintain these semantics when
// manipulating a global over Xray (so the properties just re-resolve if
// they've been deleted).
//
// Unfortunately, there's a bit of an impedance-mismatch between the Xray
// and non-Xray machinery. The Xray machinery wants an API that returns a
// JSPropertyDescriptor, so that the resolve hook doesn't have to get
// snared up with trying to define a property on the Xray holder. At the
// same time, the DefineInterface callbacks are set up to define things
// directly on the global. And re-jiggering them to return property
// descriptors is tricky, because some DefineInterface callbacks define
// multiple things (like the Image() alias for HTMLImageElement).
//
// So the setup is as-follows:
//
// * The resolve function takes a JSPropertyDescriptor, but in the
// non-Xray case, callees may define things directly on the global, and
// set the value on the property descriptor to |undefined| to indicate
// that there's nothing more for the caller to do. We assert against
// this behavior in the Xray case.
//
// * We make sure that we do a non-Xray resolve first, so that all the
// slots are set up. In the Xray case, this means unwrapping and doing
// a non-Xray resolve before doing the Xray resolve.
//
// This all could use some grand refactoring, but for now we just limp
// along.
if (xpc::WrapperFactory::IsXrayWrapper(obj)) {
JS::Rooted<JSObject*> global(cx,
js::CheckedUnwrap(obj, /* stopAtOuter = */ false));
if (!global) {
return NS_ERROR_DOM_SECURITY_ERR;
}
ac.construct(cx, global);
} else {
global = obj;
}
JS::Rooted<JSObject*> interfaceObject(cx,
getOrCreateInterfaceObject(cx, global, id, !isXray));
if (!interfaceObject) {
return NS_ERROR_FAILURE;
}
if (isXray) {
// This really should be handled by the Xray for the window.
ac.destroy();
if (!JS_WrapObject(cx, &interfaceObject) ||
!JS_DefinePropertyById(cx, obj, id,
JS::ObjectValue(*interfaceObject), JS_PropertyStub,
JS_StrictPropertyStub, 0)) {
JS::Rooted<JSObject*> interfaceObject(cx);
{
JSAutoCompartment ac(cx, global);
interfaceObject = getOrCreateInterfaceObject(cx, global, id, false);
}
if (NS_WARN_IF(!interfaceObject)) {
return NS_ERROR_FAILURE;
}
if (!JS_WrapObject(cx, &interfaceObject)) {
return NS_ERROR_FAILURE;
}
FillPropertyDescriptor(desc, obj, 0, JS::ObjectValue(*interfaceObject));
} else {
JS::Rooted<JSObject*> interfaceObject(cx,
getOrCreateInterfaceObject(cx, obj, id, true));
if (NS_WARN_IF(!interfaceObject)) {
return NS_ERROR_FAILURE;
}
// We've already defined the property. We indicate this to the caller
// by filling a property descriptor with JS::UndefinedValue() as the
// value. We still have to fill in a property descriptor, though, so
// that the caller knows the property is in fact on this object. It
// doesn't matter what we pass for the "readonly" argument here.
FillPropertyDescriptor(desc, obj, JS::UndefinedValue(), false);
}
*did_resolve = true;
return NS_OK;
}
}
@ -2912,20 +2947,22 @@ nsWindowSH::GlobalResolve(nsGlobalWindow *aWin, JSContext *cx,
false, &v);
NS_ENSURE_SUCCESS(rv, rv);
rv = constructor->Install(cx, obj, v);
NS_ENSURE_SUCCESS(rv, rv);
JS::Rooted<JSObject*> class_obj(cx, &v.toObject());
// ... and define the constants from the DOM interface on that
// constructor object.
JSAutoCompartment ac(cx, class_obj);
rv = DefineInterfaceConstants(cx, class_obj, &name_struct->mIID);
NS_ENSURE_SUCCESS(rv, rv);
{
JSAutoCompartment ac(cx, class_obj);
rv = DefineInterfaceConstants(cx, class_obj, &name_struct->mIID);
NS_ENSURE_SUCCESS(rv, rv);
}
*did_resolve = true;
if (!JS_WrapValue(cx, &v)) {
return NS_ERROR_UNEXPECTED;
}
FillPropertyDescriptor(desc, obj, 0, v);
return NS_OK;
}
@ -2936,38 +2973,42 @@ nsWindowSH::GlobalResolve(nsGlobalWindow *aWin, JSContext *cx,
}
// Create the XPConnect prototype for our classinfo, PostCreateProto will
// set up the prototype chain.
// set up the prototype chain. This will go ahead and define things on the
// actual window's global.
nsCOMPtr<nsIXPConnectJSObjectHolder> proto_holder;
rv = GetXPCProto(sXPConnect, cx, aWin, name_struct,
getter_AddRefs(proto_holder));
if (NS_SUCCEEDED(rv) && obj != aWin->GetGlobalJSObject()) {
JS::Rooted<JSObject*> dot_prototype(cx, proto_holder->GetJSObject());
NS_ENSURE_STATE(dot_prototype);
const nsDOMClassInfoData *ci_data;
if (name_struct->mType == nsGlobalNameStruct::eTypeClassConstructor) {
ci_data = &sClassInfoData[name_struct->mDOMClassInfoID];
} else {
ci_data = name_struct->mData;
}
return ResolvePrototype(sXPConnect, aWin, cx, obj, class_name, ci_data,
name_struct, nameSpaceManager, dot_prototype,
true, did_resolve);
NS_ENSURE_SUCCESS(rv, rv);
bool isXray = xpc::WrapperFactory::IsXrayWrapper(obj);
MOZ_ASSERT_IF(obj != aWin->GetGlobalJSObject(), isXray);
if (!isXray) {
// GetXPCProto already defined the property for us
FillPropertyDescriptor(desc, obj, JS::UndefinedValue(), false);
return NS_OK;
}
*did_resolve = NS_SUCCEEDED(rv);
// This is the Xray case. Look up the constructor object for this
// prototype.
JS::Rooted<JSObject*> dot_prototype(cx, proto_holder->GetJSObject());
NS_ENSURE_STATE(dot_prototype);
return rv;
const nsDOMClassInfoData *ci_data;
if (name_struct->mType == nsGlobalNameStruct::eTypeClassConstructor) {
ci_data = &sClassInfoData[name_struct->mDOMClassInfoID];
} else {
ci_data = name_struct->mData;
}
return ResolvePrototype(sXPConnect, aWin, cx, obj, class_name, ci_data,
name_struct, nameSpaceManager, dot_prototype,
desc);
}
if (name_struct->mType == nsGlobalNameStruct::eTypeClassProto) {
// We don't have a XPConnect prototype object, let ResolvePrototype create
// one.
return ResolvePrototype(sXPConnect, aWin, cx, obj, class_name, nullptr,
name_struct, nameSpaceManager, nullptr, true,
did_resolve);
name_struct, nameSpaceManager, nullptr, desc);
}
if (name_struct->mType == nsGlobalNameStruct::eTypeExternalConstructorAlias) {
@ -2996,8 +3037,7 @@ nsWindowSH::GlobalResolve(nsGlobalWindow *aWin, JSContext *cx,
}
return ResolvePrototype(sXPConnect, aWin, cx, obj, class_name, ci_data,
name_struct, nameSpaceManager, nullptr, true,
did_resolve);
name_struct, nameSpaceManager, nullptr, desc);
}
if (name_struct->mType == nsGlobalNameStruct::eTypeExternalConstructor) {
@ -3009,15 +3049,12 @@ nsWindowSH::GlobalResolve(nsGlobalWindow *aWin, JSContext *cx,
JS::Rooted<JS::Value> val(cx);
rv = WrapNative(cx, obj, constructor, &NS_GET_IID(nsIDOMDOMConstructor),
false, &val);
NS_ENSURE_SUCCESS(rv, rv);
rv = constructor->Install(cx, obj, val);
true, &val);
NS_ENSURE_SUCCESS(rv, rv);
NS_ASSERTION(val.isObject(), "Why didn't we get a JSObject?");
*did_resolve = true;
FillPropertyDescriptor(desc, obj, 0, val);
return NS_OK;
}
@ -3066,13 +3103,9 @@ nsWindowSH::GlobalResolve(nsGlobalWindow *aWin, JSContext *cx,
return NS_ERROR_UNEXPECTED;
}
bool ok = ::JS_DefinePropertyById(cx, obj, id, prop_val,
JS_PropertyStub, JS_StrictPropertyStub,
JSPROP_ENUMERATE);
FillPropertyDescriptor(desc, obj, prop_val, false);
*did_resolve = true;
return ok ? NS_OK : NS_ERROR_FAILURE;
return NS_OK;
}
return rv;
@ -3184,7 +3217,9 @@ const InterfaceShimEntry kInterfaceShimMap[] =
{ "nsIDOMXPathResult", "XPathResult" } };
static nsresult
DefineComponentsShim(JSContext *cx, JS::Handle<JSObject*> global, nsPIDOMWindow *win)
LookupComponentsShim(JSContext *cx, JS::Handle<JSObject*> global,
nsPIDOMWindow *win,
JS::MutableHandle<JSPropertyDescriptor> desc)
{
// Keep track of how often this happens.
Telemetry::Accumulate(Telemetry::COMPONENTS_SHIM_ACCESSED_BY_CONTENT, true);
@ -3198,16 +3233,14 @@ DefineComponentsShim(JSContext *cx, JS::Handle<JSObject*> global, nsPIDOMWindow
// Create a fake Components object.
JS::Rooted<JSObject*> components(cx, JS_NewObject(cx, nullptr, JS::NullPtr(), global));
NS_ENSURE_TRUE(components, NS_ERROR_OUT_OF_MEMORY);
bool ok = JS_DefineProperty(cx, global, "Components", JS::ObjectValue(*components),
JS_PropertyStub, JS_StrictPropertyStub, JSPROP_ENUMERATE);
NS_ENSURE_TRUE(ok, NS_ERROR_OUT_OF_MEMORY);
// Create a fake interfaces object.
JS::Rooted<JSObject*> interfaces(cx, JS_NewObject(cx, nullptr, JS::NullPtr(), global));
NS_ENSURE_TRUE(interfaces, NS_ERROR_OUT_OF_MEMORY);
ok = JS_DefineProperty(cx, components, "interfaces", JS::ObjectValue(*interfaces),
JS_PropertyStub, JS_StrictPropertyStub,
JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_READONLY);
bool ok =
JS_DefineProperty(cx, components, "interfaces", JS::ObjectValue(*interfaces),
JS_PropertyStub, JS_StrictPropertyStub,
JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_READONLY);
NS_ENSURE_TRUE(ok, NS_ERROR_OUT_OF_MEMORY);
// Define a bunch of shims from the Ci.nsIDOMFoo to window.Foo for DOM
@ -3234,6 +3267,8 @@ DefineComponentsShim(JSContext *cx, JS::Handle<JSObject*> global, nsPIDOMWindow
NS_ENSURE_TRUE(ok, NS_ERROR_OUT_OF_MEMORY);
}
FillPropertyDescriptor(desc, global, JS::ObjectValue(*components), false);
return NS_OK;
}
@ -3339,18 +3374,39 @@ nsWindowSH::NewResolve(nsIXPConnectWrappedNative *wrapper, JSContext *cx,
// (and any relevant named constructor names) has been resolved before;
// this allows us to avoid re-resolving in the Xray case if the property is
// deleted by page script.
bool ignored;
JS::Rooted<JSObject*> global(cx,
js::UncheckedUnwrap(obj, /* stopAtOuter = */ false));
JSAutoCompartment ac(cx, global);
nsresult rv = GlobalResolve(win, cx, global, id, &ignored);
JS::Rooted<JSPropertyDescriptor> desc(cx);
nsresult rv = GlobalResolve(win, cx, global, id, &desc);
NS_ENSURE_SUCCESS(rv, rv);
// If we have an object here, that means we resolved the property.
// But if the value is undefined, that means that GlobalResolve
// also already defined it, so we don't have to.
if (desc.object() && !desc.value().isUndefined() &&
!JS_DefinePropertyById(cx, global, id, desc.value(),
desc.getter(), desc.setter(),
desc.attributes())) {
return NS_ERROR_FAILURE;
}
}
bool did_resolve = false;
nsresult rv = GlobalResolve(win, cx, obj, id, &did_resolve);
NS_ENSURE_SUCCESS(rv, rv);
if (did_resolve) {
JS::Rooted<JSPropertyDescriptor> desc(cx);
nsresult rv = GlobalResolve(win, cx, obj, id, &desc);
NS_ENSURE_SUCCESS(rv, rv);
if (desc.object()) {
// If we have an object here, that means we resolved the property.
// But if the value is undefined, that means that GlobalResolve
// also already defined it, so we don't have to. Note that in the
// Xray case we should never see undefined.
MOZ_ASSERT_IF(isXray, !desc.value().isUndefined());
if (!desc.value().isUndefined() &&
!JS_DefinePropertyById(cx, obj, id, desc.value(),
desc.getter(), desc.setter(),
desc.attributes())) {
return NS_ERROR_FAILURE;
}
*objp = obj;
return NS_OK;
}

View File

@ -256,7 +256,7 @@ protected:
static nsresult GlobalResolve(nsGlobalWindow *aWin, JSContext *cx,
JS::Handle<JSObject*> obj, JS::Handle<jsid> id,
bool *did_resolve);
JS::MutableHandle<JSPropertyDescriptor> desc);
public:
NS_IMETHOD PreCreate(nsISupports *nativeObj, JSContext *cx,

View File

@ -165,6 +165,18 @@ FillPropertyDescriptor(JS::MutableHandle<JSPropertyDescriptor> desc, JSObject* o
FillPropertyDescriptor(desc, obj, readonly);
}
inline void
FillPropertyDescriptor(JS::MutableHandle<JSPropertyDescriptor> desc,
JSObject* obj, unsigned attributes, JS::Value v)
{
desc.object().set(obj);
desc.value().set(v);
desc.setAttributes(attributes);
desc.setGetter(nullptr);
desc.setSetter(nullptr);
desc.setShortId(0);
}
} // namespace dom
} // namespace mozilla