Bug 1148188 - part2: interposeCall. r=billm

This commit is contained in:
Gabor Krizsanits 2015-05-07 14:03:24 +02:00
parent 11fdde3a7d
commit 3ccaa7fa1e
13 changed files with 167 additions and 27 deletions

View File

@ -1261,6 +1261,13 @@ js::ForwardToNative(JSContext* cx, JSNative native, const CallArgs& args)
return native(cx, args.length(), args.base());
}
JS_FRIEND_API(JSObject*)
js::ConvertArgsToArray(JSContext* cx, const CallArgs& args)
{
RootedObject argsArray(cx, NewDenseCopiedArray(cx, args.length(), args.array()));
return argsArray;
}
JS_FRIEND_API(JSAtom*)
js::GetPropertyNameFromPC(JSScript* script, jsbytecode* pc)
{

View File

@ -2743,6 +2743,9 @@ extern JS_FRIEND_API(bool)
DefineOwnProperty(JSContext* cx, JSObject* objArg, jsid idArg,
JS::Handle<JSPropertyDescriptor> descriptor, JS::ObjectOpResult& result);
extern JS_FRIEND_API(JSObject*)
ConvertArgsToArray(JSContext* cx, const JS::CallArgs& args);
} /* namespace js */
extern JS_FRIEND_API(void)

View File

@ -20,7 +20,7 @@
* property, it should return a replacement property descriptor for it. If not,
* it should return null.
*/
[scriptable,uuid(e5453950-d95a-11e3-9c1a-0800200c9a66)]
[scriptable,uuid(215353cb-6e77-462f-a791-6891f42e593f)]
interface nsIAddonInterposition : nsISupports
{
/**
@ -35,8 +35,23 @@ interface nsIAddonInterposition : nsISupports
* @param prop The name of the property being accessed.
* @return A property descriptor or null.
*/
jsval interpose(in jsval addonId,
in jsval target,
in nsIIDPtr iface,
in jsval prop);
jsval interposeProperty(in jsval addonId,
in jsval target,
in nsIIDPtr iface,
in jsval prop);
/**
* We're intercepting calls from add-ons scopes into non-addon scopes.
*
* @param addonId The ID of the add-on accessing the property.
* @param originalFunc The function object being intercepted.
* @param originalThis The |this| value for the intercepted call.
* @param args The arguments of the original call in an array.
* @return The result of the call. NOTE: after the call interception,
* the original function will not be called automatically, so the
* implementer has to do that.
*/
jsval interposeCall(in jsval addonId,
in jsval originalFunc,
in jsval originalThis,
in jsval args);
};

View File

@ -123,7 +123,7 @@ interface ScheduledGCCallback : nsISupports
/**
* interface of Components.utils
*/
[scriptable, uuid(1a9bcb85-67a9-4a22-89a2-40eaf7fc31ec)]
[scriptable, uuid(e04a4a58-2b5e-4a74-941a-0d344b057c5a)]
interface nsIXPCComponents_Utils : nsISupports
{
@ -689,6 +689,12 @@ interface nsIXPCComponents_Utils : nsISupports
[implicit_jscontext]
void setAddonInterposition(in ACString addonId, in nsIAddonInterposition interposition);
/*
* Enables call interpositions from addon scopes to any functions in the scope of |target|.
*/
[implicit_jscontext]
void setAddonCallInterposition(in jsval target);
/*
* Return a fractional number of milliseconds from process
* startup, measured with a monotonic clock.

View File

@ -3489,6 +3489,21 @@ nsXPCComponents_Utils::SetAddonInterposition(const nsACString& addonIdStr,
return NS_OK;
}
NS_IMETHODIMP
nsXPCComponents_Utils::SetAddonCallInterposition(HandleValue target,
JSContext* cx)
{
NS_ENSURE_TRUE(target.isObject(), NS_ERROR_INVALID_ARG);
RootedObject targetObj(cx, &target.toObject());
targetObj = js::CheckedUnwrap(targetObj);
NS_ENSURE_TRUE(targetObj, NS_ERROR_INVALID_ARG);
XPCWrappedNativeScope* xpcScope = ObjectScope(targetObj);
NS_ENSURE_TRUE(xpcScope, NS_ERROR_INVALID_ARG);
xpcScope->SetAddonCallInterposition();
return NS_OK;
}
NS_IMETHODIMP
nsXPCComponents_Utils::Now(double* aRetval)
{

View File

@ -357,7 +357,7 @@ DefinePropertyIfFound(XPCCallContext& ccx,
if (scope->HasInterposition()) {
Rooted<JSPropertyDescriptor> desc(ccx);
if (!xpc::Interpose(ccx, obj, iface->GetIID(), id, &desc))
if (!xpc::InterposeProperty(ccx, obj, iface->GetIID(), id, &desc))
return false;
if (desc.object()) {

View File

@ -83,6 +83,7 @@ XPCWrappedNativeScope::XPCWrappedNativeScope(JSContext* cx,
mComponents(nullptr),
mNext(nullptr),
mGlobalJSObject(aGlobal),
mHasCallInterpositions(false),
mIsContentXBLScope(false),
mIsAddonScope(false)
{

View File

@ -1165,9 +1165,6 @@ public:
js::PointerHasher<JSAddonId*, 3>,
js::SystemAllocPolicy> InterpositionMap;
static bool SetAddonInterposition(JSAddonId* addonId,
nsIAddonInterposition* interp);
// Gets the appropriate scope object for XBL in this scope. The context
// must be same-compartment with the global upon entering, and the scope
// object is wrapped into the compartment of the global.
@ -1188,6 +1185,12 @@ public:
bool HasInterposition() { return mInterposition; }
nsCOMPtr<nsIAddonInterposition> GetInterposition();
static bool SetAddonInterposition(JSAddonId* addonId,
nsIAddonInterposition* interp);
void SetAddonCallInterposition() { mHasCallInterpositions = true; }
bool HasCallInterposition() { return mHasCallInterpositions; };
protected:
virtual ~XPCWrappedNativeScope();
@ -1226,10 +1229,14 @@ private:
// Lazily created sandboxes for addon code.
nsTArray<JS::ObjectPtr> mAddonScopes;
// This is a service that will be use to interpose on all calls out of this
// scope. If it's null, no interposition is done.
// This is a service that will be use to interpose on some property accesses on
// objects from other scope, for add-on compatibility reasons.
nsCOMPtr<nsIAddonInterposition> mInterposition;
// If this flag is set, we intercept function calls on vanilla JS function objects
// from this scope if the caller scope has mInterposition set.
bool mHasCallInterpositions;
nsAutoPtr<DOMExpandoSet> mDOMExpandoSet;
JS::WeakMapPtr<JSObject*, JSObject*> mXrayExpandos;

View File

@ -34,7 +34,7 @@ let TestInterposition = {
QueryInterface: XPCOMUtils.generateQI([Ci.nsIAddonInterposition,
Ci.nsISupportsWeakReference]),
interpose: function(addonId, target, iid, prop) {
interposeProperty: function(addonId, target, iid, prop) {
do_check_eq(addonId, ADDONID);
do_check_eq(gExpectedProp, prop);
gExpectedProp = undefined;
@ -56,6 +56,12 @@ let TestInterposition = {
}
return null;
},
interposeCall: function(addonId, originalFunc, originalThis, args) {
do_check_eq(addonId, ADDONID);
args.splice(0, 0, addonId);
return originalFunc.apply(originalThis, args);
}
};
@ -138,5 +144,17 @@ function run_test()
do_check_eq(desc.configurable, false);
});
let moduleScope = Cu.Sandbox(this);
moduleScope.ADDONID = ADDONID;
moduleScope.do_check_eq = do_check_eq;
function funToIntercept(addonId) {
do_check_eq(addonId, ADDONID);
counter++;
}
sandbox.moduleFunction = Cu.evalInSandbox(funToIntercept.toSource() + "; funToIntercept", moduleScope);
Cu.evalInSandbox("var counter = 0;", moduleScope);
Cu.evalInSandbox("Components.utils.setAddonCallInterposition(this);", moduleScope);
Cu.evalInSandbox("moduleFunction()", sandbox);
do_check_eq(Cu.evalInSandbox("counter", moduleScope), 1);
Cu.setAddonInterposition(ADDONID, null);
}

View File

@ -22,8 +22,8 @@ using namespace JS;
namespace xpc {
bool
Interpose(JSContext* cx, HandleObject target, const nsIID* iid, HandleId id,
MutableHandle<JSPropertyDescriptor> descriptor)
InterposeProperty(JSContext* cx, HandleObject target, const nsIID* iid, HandleId id,
MutableHandle<JSPropertyDescriptor> descriptor)
{
// We only want to do interpostion on DOM instances and
// wrapped natives.
@ -45,8 +45,8 @@ Interpose(JSContext* cx, HandleObject target, const nsIID* iid, HandleId id,
RootedValue prop(cx, IdToValue(id));
RootedValue targetValue(cx, ObjectValue(*target));
RootedValue descriptorVal(cx);
nsresult rv = interp->Interpose(addonIdValue, targetValue,
iid, prop, &descriptorVal);
nsresult rv = interp->InterposeProperty(addonIdValue, targetValue,
iid, prop, &descriptorVal);
if (NS_FAILED(rv)) {
xpc::Throw(cx, rv);
return false;
@ -79,12 +79,64 @@ Interpose(JSContext* cx, HandleObject target, const nsIID* iid, HandleId id,
return true;
}
bool
InterposeCall(JSContext* cx, JS::HandleObject target, const JS::CallArgs& args, bool* done)
{
*done = false;
XPCWrappedNativeScope* scope = ObjectScope(CurrentGlobalOrNull(cx));
MOZ_ASSERT(scope->HasInterposition());
nsCOMPtr<nsIAddonInterposition> interp = scope->GetInterposition();
RootedObject unwrappedTarget(cx, UncheckedUnwrap(target));
XPCWrappedNativeScope* targetScope = ObjectScope(unwrappedTarget);
bool hasInterpostion = targetScope->HasCallInterposition();
if (!hasInterpostion)
return true;
// If there is a call interpostion, we don't want to propogate the
// call to Base:
*done = true;
JSAddonId* addonId = AddonIdOfObject(target);
RootedValue addonIdValue(cx, StringValue(StringOfAddonId(addonId)));
RootedValue targetValue(cx, ObjectValue(*target));
RootedValue thisValue(cx, args.thisv());
RootedObject argsArray(cx, ConvertArgsToArray(cx, args));
if (!argsArray)
return false;
RootedValue argsVal(cx, ObjectValue(*argsArray));
RootedValue returnVal(cx);
nsresult rv = interp->InterposeCall(addonIdValue, targetValue,
thisValue, argsVal, args.rval());
if (NS_FAILED(rv)) {
xpc::Throw(cx, rv);
return false;
}
return true;
}
template<typename Base>
bool AddonWrapper<Base>::call(JSContext* cx, JS::Handle<JSObject*> wrapper,
const JS::CallArgs& args) const
{
bool done = false;
if (!InterposeCall(cx, wrapper, args, &done))
return false;
return done || Base::call(cx, wrapper, args);
}
template<typename Base>
bool
AddonWrapper<Base>::getPropertyDescriptor(JSContext* cx, HandleObject wrapper,
HandleId id, MutableHandle<JSPropertyDescriptor> desc) const
{
if (!Interpose(cx, wrapper, nullptr, id, desc))
if (!InterposeProperty(cx, wrapper, nullptr, id, desc))
return false;
if (desc.object())
@ -98,7 +150,7 @@ bool
AddonWrapper<Base>::getOwnPropertyDescriptor(JSContext* cx, HandleObject wrapper,
HandleId id, MutableHandle<JSPropertyDescriptor> desc) const
{
if (!Interpose(cx, wrapper, nullptr, id, desc))
if (!InterposeProperty(cx, wrapper, nullptr, id, desc))
return false;
if (desc.object())
@ -113,7 +165,7 @@ AddonWrapper<Base>::get(JSContext* cx, JS::Handle<JSObject*> wrapper, JS::Handle
JS::Handle<jsid> id, JS::MutableHandle<JS::Value> vp) const
{
Rooted<JSPropertyDescriptor> desc(cx);
if (!Interpose(cx, wrapper, nullptr, id, &desc))
if (!InterposeProperty(cx, wrapper, nullptr, id, &desc))
return false;
if (!desc.object())
@ -136,7 +188,7 @@ AddonWrapper<Base>::set(JSContext* cx, JS::HandleObject wrapper, JS::HandleId id
JS::HandleValue receiver, JS::ObjectOpResult& result) const
{
Rooted<JSPropertyDescriptor> desc(cx);
if (!Interpose(cx, wrapper, nullptr, id, &desc))
if (!InterposeProperty(cx, wrapper, nullptr, id, &desc))
return false;
if (!desc.object())
@ -164,7 +216,7 @@ AddonWrapper<Base>::defineProperty(JSContext* cx, HandleObject wrapper, HandleId
ObjectOpResult& result) const
{
Rooted<JSPropertyDescriptor> interpDesc(cx);
if (!Interpose(cx, wrapper, nullptr, id, &interpDesc))
if (!InterposeProperty(cx, wrapper, nullptr, id, &interpDesc))
return false;
if (!interpDesc.object())
@ -180,7 +232,7 @@ AddonWrapper<Base>::delete_(JSContext* cx, HandleObject wrapper, HandleId id,
ObjectOpResult& result) const
{
Rooted<JSPropertyDescriptor> desc(cx);
if (!Interpose(cx, wrapper, nullptr, id, &desc))
if (!InterposeProperty(cx, wrapper, nullptr, id, &desc))
return false;
if (!desc.object())

View File

@ -16,8 +16,12 @@
namespace xpc {
bool
Interpose(JSContext* cx, JS::HandleObject target, const nsIID* iid, JS::HandleId id,
JS::MutableHandle<JSPropertyDescriptor> descriptor);
InterposeProperty(JSContext* cx, JS::HandleObject target, const nsIID* iid, JS::HandleId id,
JS::MutableHandle<JSPropertyDescriptor> descriptor);
bool
InterposeCall(JSContext* cx, JS::HandleObject wrapper,
const JS::CallArgs& args, bool& done);
template<typename Base>
class AddonWrapper : public Base {
@ -36,6 +40,8 @@ class AddonWrapper : public Base {
JS::Handle<jsid> id, JS::MutableHandle<JS::Value> vp) const override;
virtual bool set(JSContext* cx, JS::HandleObject wrapper, JS::HandleId id, JS::HandleValue v,
JS::HandleValue receiver, JS::ObjectOpResult& result) const override;
virtual bool call(JSContext* cx, JS::Handle<JSObject*> wrapper,
const JS::CallArgs& args) const override;
virtual bool getPropertyDescriptor(JSContext* cx, JS::Handle<JSObject*> wrapper,
JS::Handle<jsid> id,

View File

@ -22,9 +22,14 @@ DefaultInterpositionService.prototype = {
classID: Components.ID("{50bc93ce-602a-4bef-bf3a-61fc749c4caf}"),
QueryInterface: XPCOMUtils.generateQI([Ci.nsIAddonInterposition, Ci.nsISupportsWeakReference]),
interpose: function(addon, target, iid, prop) {
interposeProperty: function(addon, target, iid, prop) {
return null;
},
interposeCall: function(addonId, originalFunc, originalThis, args) {
args.splice(0, 0, addonId);
return originalFunc.apply(originalThis, args);
},
};
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([DefaultInterpositionService]);

View File

@ -105,7 +105,7 @@ AddonInterpositionService.prototype = {
return "generic";
},
interpose: function(addon, target, iid, prop) {
interposeProperty: function(addon, target, iid, prop) {
let interp;
if (iid) {
interp = this._interfaceInterpositions[iid];
@ -143,6 +143,11 @@ AddonInterpositionService.prototype = {
return Prefetcher.lookupInCache(addon, target, prop);
},
interposeCall: function(addonId, originalFunc, originalThis, args) {
args.splice(0, 0, addonId);
return originalFunc.apply(originalThis, args);
},
};
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([AddonInterpositionService]);