Bug 1017323 - Add-on interposition (r=bholley)
authorBill McCloskey <wmccloskey@mozilla.com>
Mon, 14 Jul 2014 22:10:05 -0700
changeset 215893 3fadc02e2e841c3bb5f6fd7db6fc4fa9593817fa
parent 215892 2b29602d985075f16e4386b306e28173f1ad0dc6
child 215894 5ea30521f56bfa7f0b4e8361b40a702a90088f56
push id515
push userraliiev@mozilla.com
push dateMon, 06 Oct 2014 12:51:51 +0000
treeherdermozilla-release@267c7a481bef [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbholley
bugs1017323
milestone33.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1017323 - Add-on interposition (r=bholley)
js/src/jsapi.cpp
js/src/jsapi.h
js/src/jsfriendapi.cpp
js/src/jsfriendapi.h
js/src/jsproxy.cpp
js/xpconnect/idl/moz.build
js/xpconnect/idl/nsIAddonInterposition.idl
js/xpconnect/idl/xpccomponents.idl
js/xpconnect/src/XPCComponents.cpp
js/xpconnect/src/XPCWrappedNativeJSOps.cpp
js/xpconnect/src/XPCWrappedNativeScope.cpp
js/xpconnect/src/nsXPConnect.cpp
js/xpconnect/src/xpcprivate.h
js/xpconnect/src/xpcpublic.h
js/xpconnect/tests/unit/test_interposition.js
js/xpconnect/tests/unit/xpcshell.ini
js/xpconnect/wrappers/AddonWrapper.cpp
js/xpconnect/wrappers/AddonWrapper.h
js/xpconnect/wrappers/WrapperFactory.cpp
js/xpconnect/wrappers/moz.build
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -3205,26 +3205,16 @@ JS_DefineUCProperty(JSContext *cx, Handl
                     double valueArg, unsigned attrs,
                     JSPropertyOp getter, JSStrictPropertyOp setter)
 {
     Value value = NumberValue(valueArg);
     return DefineUCProperty(cx, obj, name, namelen, HandleValue::fromMarkedLocation(&value),
                             getter, setter, attrs, 0);
 }
 
-JS_PUBLIC_API(bool)
-JS_DefineOwnProperty(JSContext *cx, HandleObject obj, HandleId id, HandleValue descriptor, bool *bp)
-{
-    AssertHeapIsIdle(cx);
-    CHECK_REQUEST(cx);
-    assertSameCompartment(cx, obj, id, descriptor);
-
-    return DefineOwnProperty(cx, obj, id, descriptor, bp);
-}
-
 JS_PUBLIC_API(JSObject *)
 JS_DefineObject(JSContext *cx, HandleObject obj, const char *name, const JSClass *jsclasp,
                 HandleObject proto, unsigned attrs)
 {
     AssertHeapIsIdle(cx);
     CHECK_REQUEST(cx);
     assertSameCompartment(cx, obj, proto);
 
@@ -3304,16 +3294,29 @@ JS_DefineProperties(JSContext *cx, Handl
                                           ps->flags, 0);
         }
         if (!ok)
             break;
     }
     return ok;
 }
 
+JS_PUBLIC_API(bool)
+JS::ParsePropertyDescriptorObject(JSContext *cx,
+                                  HandleObject obj,
+                                  HandleValue descObj,
+                                  MutableHandle<JSPropertyDescriptor> desc)
+{
+    Rooted<PropDesc> d(cx);
+    if (!d.initialize(cx, descObj))
+        return false;
+    d.populatePropertyDescriptor(obj, desc);
+    return true;
+}
+
 static bool
 GetPropertyDescriptorById(JSContext *cx, HandleObject obj, HandleId id,
                           MutableHandle<PropertyDescriptor> desc)
 {
     RootedObject obj2(cx);
     RootedShape shape(cx);
 
     if (!LookupPropertyById(cx, obj, id, &obj2, &shape))
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -2833,20 +2833,16 @@ JS_DefinePropertyById(JSContext *cx, JS:
                       JSPropertyOp getter = nullptr, JSStrictPropertyOp setter = nullptr);
 
 extern JS_PUBLIC_API(bool)
 JS_DefinePropertyById(JSContext *cx, JS::HandleObject obj, JS::HandleId id, double value,
                       unsigned attrs,
                       JSPropertyOp getter = nullptr, JSStrictPropertyOp setter = nullptr);
 
 extern JS_PUBLIC_API(bool)
-JS_DefineOwnProperty(JSContext *cx, JS::HandleObject obj, JS::HandleId id,
-                     JS::HandleValue descriptor, bool *bp);
-
-extern JS_PUBLIC_API(bool)
 JS_AlreadyHasOwnProperty(JSContext *cx, JS::HandleObject obj, const char *name,
                          bool *foundp);
 
 extern JS_PUBLIC_API(bool)
 JS_AlreadyHasOwnPropertyById(JSContext *cx, JS::HandleObject obj, JS::HandleId id,
                              bool *foundp);
 
 extern JS_PUBLIC_API(bool)
@@ -2954,16 +2950,27 @@ class MutablePropertyDescriptorOperation
 
     void setEnumerable() { desc()->attrs |= JSPROP_ENUMERATE; }
     void setAttributes(unsigned attrs) { desc()->attrs = attrs; }
 
     void setGetter(JSPropertyOp op) { desc()->getter = op; }
     void setSetter(JSStrictPropertyOp op) { desc()->setter = op; }
     void setGetterObject(JSObject *obj) { desc()->getter = reinterpret_cast<JSPropertyOp>(obj); }
     void setSetterObject(JSObject *obj) { desc()->setter = reinterpret_cast<JSStrictPropertyOp>(obj); }
+
+    JS::MutableHandleObject getterObject() {
+        MOZ_ASSERT(this->hasGetterObject());
+        return JS::MutableHandleObject::fromMarkedLocation(
+                reinterpret_cast<JSObject **>(&desc()->getter));
+    }
+    JS::MutableHandleObject setterObject() {
+        MOZ_ASSERT(this->hasSetterObject());
+        return JS::MutableHandleObject::fromMarkedLocation(
+                reinterpret_cast<JSObject **>(&desc()->setter));
+    }
 };
 
 } /* namespace JS */
 
 namespace js {
 
 template <>
 struct GCMethods<JSPropertyDescriptor> {
@@ -3011,16 +3018,26 @@ class MutableHandleBase<JSPropertyDescri
     }
     JSPropertyDescriptor *extractMutable() {
         return static_cast<JS::MutableHandle<JSPropertyDescriptor>*>(this)->address();
     }
 };
 
 } /* namespace js */
 
+namespace JS {
+
+extern JS_PUBLIC_API(bool)
+ParsePropertyDescriptorObject(JSContext *cx,
+                              JS::HandleObject obj,
+                              JS::HandleValue descriptor,
+                              JS::MutableHandle<JSPropertyDescriptor> desc);
+
+} // namespace JS
+
 extern JS_PUBLIC_API(bool)
 JS_GetOwnPropertyDescriptorById(JSContext *cx, JS::HandleObject obj, JS::HandleId id,
                                 JS::MutableHandle<JSPropertyDescriptor> desc);
 
 extern JS_PUBLIC_API(bool)
 JS_GetOwnPropertyDescriptor(JSContext *cx, JS::HandleObject obj, const char *name,
                             JS::MutableHandle<JSPropertyDescriptor> desc);
 
--- a/js/src/jsfriendapi.cpp
+++ b/js/src/jsfriendapi.cpp
@@ -1177,16 +1177,31 @@ js_DefineOwnProperty(JSContext *cx, JSOb
 }
 
 JS_FRIEND_API(bool)
 js_ReportIsNotFunction(JSContext *cx, JS::HandleValue v)
 {
     return ReportIsNotFunction(cx, v);
 }
 
+JS_FRIEND_API(void)
+js::ReportErrorWithId(JSContext *cx, const char *msg, HandleId id)
+{
+    RootedValue idv(cx);
+    if (!JS_IdToValue(cx, id, &idv))
+        return;
+    JSString *idstr = JS::ToString(cx, idv);
+    if (!idstr)
+        return;
+    JSAutoByteString bytes(cx, idstr);
+    if (!bytes)
+        return;
+    JS_ReportError(cx, msg, bytes.ptr());
+}
+
 #ifdef DEBUG
 JS_PUBLIC_API(bool)
 js::IsInRequest(JSContext *cx)
 {
 #ifdef JS_THREADSAFE
     return !!cx->runtime()->requestDepth;
 #else
     return true;
--- a/js/src/jsfriendapi.h
+++ b/js/src/jsfriendapi.h
@@ -2323,16 +2323,19 @@ DefaultValue(JSContext *cx, JS::HandleOb
  * a sort of extension point, but there is no hook in js::Class,
  * js::ProxyHandler, or the JSAPI with precisely the right semantics for it.
  */
 extern JS_FRIEND_API(bool)
 CheckDefineProperty(JSContext *cx, JS::HandleObject obj, JS::HandleId id, JS::HandleValue value,
                     unsigned attrs,
                     JSPropertyOp getter = nullptr, JSStrictPropertyOp setter = nullptr);
 
+JS_FRIEND_API(void)
+ReportErrorWithId(JSContext *cx, const char *msg, JS::HandleId id);
+
 } /* namespace js */
 
 extern JS_FRIEND_API(bool)
 js_DefineOwnProperty(JSContext *cx, JSObject *objArg, jsid idArg,
                      JS::Handle<JSPropertyDescriptor> descriptor, bool *bp);
 
 extern JS_FRIEND_API(bool)
 js_ReportIsNotFunction(JSContext *cx, JS::HandleValue v);
--- a/js/src/jsproxy.cpp
+++ b/js/src/jsproxy.cpp
@@ -675,29 +675,16 @@ Trap2(JSContext *cx, HandleObject handle
         return false;
     JS::AutoValueArray<2> argv(cx);
     argv[0].set(rval);
     argv[1].set(v);
     return Trap(cx, handler, fval, 2, argv.begin(), rval);
 }
 
 static bool
-ParsePropertyDescriptorObject(JSContext *cx, HandleObject obj, const Value &v,
-                              MutableHandle<PropertyDescriptor> desc, bool complete = false)
-{
-    Rooted<PropDesc> d(cx);
-    if (!d.initialize(cx, v))
-        return false;
-    if (complete)
-        d.complete();
-    d.populatePropertyDescriptor(obj, desc);
-    return true;
-}
-
-static bool
 IndicatePropertyNotFound(MutableHandle<PropertyDescriptor> desc)
 {
     desc.object().set(nullptr);
     return true;
 }
 
 static bool
 ValueToBool(HandleValue v, bool *bp)
--- a/js/xpconnect/idl/moz.build
+++ b/js/xpconnect/idl/moz.build
@@ -1,16 +1,17 @@
 # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 XPIDL_SOURCES += [
     'mozIJSSubScriptLoader.idl',
+    'nsIAddonInterposition.idl',
     'nsIJSRuntimeService.idl',
     'nsIScriptError.idl',
     'nsIXPConnect.idl',
     'nsIXPCScriptable.idl',
     'xpccomponents.idl',
     'xpcexception.idl',
     'xpcIJSGetFactory.idl',
     'xpcIJSModuleLoader.idl',
new file mode 100644
--- /dev/null
+++ b/js/xpconnect/idl/nsIAddonInterposition.idl
@@ -0,0 +1,42 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+/**
+ * This interface allows Firefox to expose different implementations of its own
+ * classes to add-ons. Once an interposition is created, it must be assigned to
+ * an add-on using Cu.setAddonInterposition (JS) or xpc::SetAddonInterposition
+ * (C++). In both cases, the arguments should be the add-on ID and the
+ * interposition object (which must be an nsIAddonInterposition). This must
+ * happen before any compartments are created for the given add-on.
+ *
+ * Every time the add-on accesses a property on any object outside its own set
+ * of compartments, XPConnect will call the interposition's
+ * interpose method. If the interposition wants to replace the given
+ * property, it should return a replacement property descriptor for it. If not,
+ * it should return null.
+ */
+[scriptable,uuid(e5453950-d95a-11e3-9c1a-0800200c9a66)]
+interface nsIAddonInterposition : nsISupports
+{
+    /**
+     * Returns a replacement property descriptor for a browser object.
+     *
+     * @param addonId The ID of the add-on accessing the property.
+     * @param target The browser object being accessed.
+     * @param iface The IID of the interface the property is associated with. This
+     *              parameter is only available for XPCWrappedNative targets. As
+     *              such, it's only useful as an optimization to avoid
+     *              instanceof checks on the target.
+     * @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);
+};
--- a/js/xpconnect/idl/xpccomponents.idl
+++ b/js/xpconnect/idl/xpccomponents.idl
@@ -5,16 +5,17 @@
 
 #include "nsISupports.idl"
 
 %{C++
 #include "jspubtd.h"
 %}
 
 interface xpcIJSWeakReference;
+interface nsIAddonInterposition;
 interface nsIClassInfo;
 interface nsIComponentManager;
 interface nsIJSCID;
 interface nsIJSIID;
 interface nsIPrincipal;
 interface nsIStackFrame;
 
 /**
@@ -635,16 +636,19 @@ interface nsIXPCComponents_Utils : nsISu
     nsIPrincipal getWebIDLCallerPrincipal();
 
     /*
      * Gets the principal of a script object, after unwrapping any cross-
      * compartment wrappers.
      */
     [implicit_jscontext]
     nsIPrincipal getObjectPrincipal(in jsval obj);
+
+    [implicit_jscontext]
+    void setAddonInterposition(in ACString addonId, in nsIAddonInterposition interposition);
 };
 
 /**
 * Interface for the 'Components' object.
 *
 * The first interface contains things that are available to non-chrome XBL code
 * that runs in a scope with an nsExpandedPrincipal. The second interface
 * includes members that are only exposed to chrome.
--- a/js/xpconnect/src/XPCComponents.cpp
+++ b/js/xpconnect/src/XPCComponents.cpp
@@ -3536,16 +3536,29 @@ nsXPCComponents_Utils::GetObjectPrincipa
     obj = js::CheckedUnwrap(obj);
     MOZ_ASSERT(obj);
 
     nsCOMPtr<nsIPrincipal> prin = nsContentUtils::ObjectPrincipal(obj);
     prin.forget(result);
     return NS_OK;
 }
 
+NS_IMETHODIMP
+nsXPCComponents_Utils::SetAddonInterposition(const nsACString &addonIdStr,
+                                             nsIAddonInterposition *interposition,
+                                             JSContext *cx)
+{
+    JSAddonId *addonId = xpc::NewAddonId(cx, addonIdStr);
+    if (!addonId)
+        return NS_ERROR_FAILURE;
+    if (!XPCWrappedNativeScope::SetAddonInterposition(addonId, interposition))
+        return NS_ERROR_FAILURE;
+    return NS_OK;
+}
+
 /***************************************************************************/
 /***************************************************************************/
 /***************************************************************************/
 
 
 nsXPCComponentsBase::nsXPCComponentsBase(XPCWrappedNativeScope* aScope)
     :   mScope(aScope)
 {
--- a/js/xpconnect/src/XPCWrappedNativeJSOps.cpp
+++ b/js/xpconnect/src/XPCWrappedNativeJSOps.cpp
@@ -5,16 +5,18 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /* JavaScript JSClasses and JSOps for our Wrapped Native JS Objects. */
 
 #include "xpcprivate.h"
 #include "jsprf.h"
 #include "mozilla/dom/BindingUtils.h"
 #include "mozilla/Preferences.h"
+#include "nsIAddonInterposition.h"
+#include "AddonWrapper.h"
 
 using namespace mozilla;
 using namespace JS;
 
 /***************************************************************************/
 
 // All of the exceptions thrown into JS from this file go through here.
 // That makes this a nice place to set a breakpoint.
@@ -347,16 +349,30 @@ DefinePropertyIfFound(XPCCallContext& cc
         RootedValue val(ccx);
         AutoResolveName arn(ccx, id);
         if (resolved)
             *resolved = true;
         return member->GetConstantValue(ccx, iface, val.address()) &&
                JS_DefinePropertyById(ccx, obj, id, val, propFlags);
     }
 
+    if (scope->HasInterposition()) {
+        Rooted<JSPropertyDescriptor> desc(ccx);
+        if (!xpc::Interpose(ccx, obj, iface->GetIID(), id, &desc))
+            return false;
+
+        if (desc.object()) {
+            AutoResolveName arn(ccx, id);
+            if (resolved)
+                *resolved = true;
+            return JS_DefinePropertyById(ccx, obj, id, desc.value(), desc.attributes(),
+                                         desc.getter(), desc.setter());
+        }
+    }
+
     if (id == rt->GetStringID(XPCJSRuntime::IDX_TO_STRING) ||
         id == rt->GetStringID(XPCJSRuntime::IDX_TO_SOURCE) ||
         (scriptableInfo &&
          scriptableInfo->GetFlags().DontEnumQueryInterface() &&
          id == rt->GetStringID(XPCJSRuntime::IDX_QUERY_INTERFACE)))
         propFlags &= ~JSPROP_ENUMERATE;
 
     RootedValue funval(ccx);
--- a/js/xpconnect/src/XPCWrappedNativeScope.cpp
+++ b/js/xpconnect/src/XPCWrappedNativeScope.cpp
@@ -19,16 +19,17 @@
 using namespace mozilla;
 using namespace xpc;
 using namespace JS;
 
 /***************************************************************************/
 
 XPCWrappedNativeScope* XPCWrappedNativeScope::gScopes = nullptr;
 XPCWrappedNativeScope* XPCWrappedNativeScope::gDyingScopes = nullptr;
+XPCWrappedNativeScope::InterpositionMap* XPCWrappedNativeScope::gInterpositionMap = nullptr;
 
 static bool
 RemoteXULForbidsXBLScope(nsIPrincipal *aPrincipal, HandleObject aGlobal)
 {
   MOZ_ASSERT(aPrincipal);
 
   // The SafeJSContext is lazily created, and tends to be created at really
   // weird times, at least for xpcshell (often very early in startup or late
@@ -102,16 +103,24 @@ XPCWrappedNativeScope::XPCWrappedNativeS
       const js::Class *clasp = js::GetObjectClass(mGlobalJSObject);
       mUseContentXBLScope = !strcmp(clasp->name, "Window") ||
                             !strcmp(clasp->name, "ChromeWindow") ||
                             !strcmp(clasp->name, "ModalContentWindow");
     }
     if (mUseContentXBLScope) {
       mUseContentXBLScope = principal && !nsContentUtils::IsSystemPrincipal(principal);
     }
+
+    JSAddonId *addonId = JS::AddonIdOfObject(aGlobal);
+    if (gInterpositionMap) {
+        if (InterpositionMap::Ptr p = gInterpositionMap->lookup(addonId)) {
+            MOZ_RELEASE_ASSERT(nsContentUtils::IsSystemPrincipal(principal));
+            mInterposition = p->value();
+        }
+    }
 }
 
 // static
 bool
 XPCWrappedNativeScope::IsDyingScope(XPCWrappedNativeScope *scope)
 {
     for (XPCWrappedNativeScope *cur = gDyingScopes; cur; cur = cur->mNext) {
         if (scope == cur)
@@ -661,16 +670,19 @@ XPCWrappedNativeScope::SystemIsBeingShut
         cur->mWrappedNativeProtoMap->
                 Enumerate(WrappedNativeProtoShutdownEnumerator,  &data);
         cur->mWrappedNativeMap->
                 Enumerate(WrappedNativeShutdownEnumerator,  &data);
     }
 
     // Now it is safe to kill all the scopes.
     KillDyingScopes();
+
+    if (gInterpositionMap)
+        delete gInterpositionMap;
 }
 
 
 /***************************************************************************/
 
 JSObject *
 XPCWrappedNativeScope::GetExpandoChain(HandleObject target)
 {
@@ -687,16 +699,38 @@ XPCWrappedNativeScope::SetExpandoChain(J
     MOZ_ASSERT(ObjectScope(target) == this);
     MOZ_ASSERT(js::IsObjectInContextCompartment(target, cx));
     MOZ_ASSERT_IF(chain, ObjectScope(chain) == this);
     if (!mXrayExpandos.initialized() && !mXrayExpandos.init(cx))
         return false;
     return mXrayExpandos.put(cx, target, chain);
 }
 
+/* static */ bool
+XPCWrappedNativeScope::SetAddonInterposition(JSAddonId *addonId,
+                                             nsIAddonInterposition *interp)
+{
+    if (!gInterpositionMap) {
+        gInterpositionMap = new InterpositionMap();
+        gInterpositionMap->init();
+    }
+    if (interp) {
+        return gInterpositionMap->put(addonId, interp);
+    } else {
+        gInterpositionMap->remove(addonId);
+        return true;
+    }
+}
+
+nsCOMPtr<nsIAddonInterposition>
+XPCWrappedNativeScope::GetInterposition()
+{
+    return mInterposition;
+}
+
 /***************************************************************************/
 
 // static
 void
 XPCWrappedNativeScope::DebugDumpAllScopes(int16_t depth)
 {
 #ifdef DEBUG
     depth-- ;
--- a/js/xpconnect/src/nsXPConnect.cpp
+++ b/js/xpconnect/src/nsXPConnect.cpp
@@ -1448,16 +1448,42 @@ Btoa(JSContext *cx, unsigned argc, Value
 }
 
 bool
 IsXrayWrapper(JSObject *obj)
 {
     return WrapperFactory::IsXrayWrapper(obj);
 }
 
+JSAddonId *
+NewAddonId(JSContext *cx, const nsACString &id)
+{
+    JS::RootedString str(cx, JS_NewStringCopyN(cx, id.BeginReading(), id.Length()));
+    if (!str)
+        return nullptr;
+    return JS::NewAddonId(cx, str);
+}
+
+bool
+SetAddonInterposition(const nsACString &addonIdStr, nsIAddonInterposition *interposition)
+{
+    JSAddonId *addonId;
+    {
+        // We enter the junk scope just to allocate a string, which actually will go
+        // in the system zone.
+        AutoJSAPI jsapi;
+        jsapi.Init(xpc::GetJunkScopeGlobal());
+        addonId = NewAddonId(jsapi.cx(), addonIdStr);
+        if (!addonId)
+            return nullptr;
+    }
+
+    return XPCWrappedNativeScope::SetAddonInterposition(addonId, interposition);
+}
+
 } // namespace xpc
 
 namespace mozilla {
 namespace dom {
 
 bool
 IsChromeOrXBL(JSContext* cx, JSObject* /* unused */)
 {
--- a/js/xpconnect/src/xpcprivate.h
+++ b/js/xpconnect/src/xpcprivate.h
@@ -958,16 +958,17 @@ static inline bool IS_PROTO_CLASS(const 
            clazz == &XPC_WN_NoMods_NoCall_Proto_JSClass ||
            clazz == &XPC_WN_ModsAllowed_WithCall_Proto_JSClass ||
            clazz == &XPC_WN_ModsAllowed_NoCall_Proto_JSClass;
 }
 
 /***************************************************************************/
 // XPCWrappedNativeScope is one-to-one with a JS global object.
 
+class nsIAddonInterposition;
 class nsXPCComponentsBase;
 class XPCWrappedNativeScope : public PRCList
 {
 public:
 
     XPCJSRuntime*
     GetRuntime() const {return XPCJSRuntime::Get();}
 
@@ -1092,16 +1093,24 @@ public:
         }
         return mDOMExpandoSet->put(expando);
     }
     void RemoveDOMExpandoObject(JSObject *expando) {
         if (mDOMExpandoSet)
             mDOMExpandoSet->remove(expando);
     }
 
+    typedef js::HashMap<JSAddonId *,
+                        nsCOMPtr<nsIAddonInterposition>,
+                        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.
     JSObject *EnsureContentXBLScope(JSContext *cx);
 
     JSObject *EnsureAddonScope(JSContext *cx, JSAddonId *addonId);
 
     XPCWrappedNativeScope(JSContext *cx, JS::HandleObject aGlobal);
@@ -1109,27 +1118,32 @@ public:
     nsAutoPtr<JSObject2JSObjectMap> mWaiverWrapperMap;
 
     bool IsContentXBLScope() { return mIsContentXBLScope; }
     bool AllowContentXBLScope();
     bool UseContentXBLScope() { return mUseContentXBLScope; }
 
     bool IsAddonScope() { return mIsAddonScope; }
 
+    bool HasInterposition() { return mInterposition; }
+    nsCOMPtr<nsIAddonInterposition> GetInterposition();
+
 protected:
     virtual ~XPCWrappedNativeScope();
 
     static void KillDyingScopes();
 
     XPCWrappedNativeScope(); // not implemented
 
 private:
     static XPCWrappedNativeScope* gScopes;
     static XPCWrappedNativeScope* gDyingScopes;
 
+    static InterpositionMap*         gInterpositionMap;
+
     XPCJSRuntime*                    mRuntime;
     Native2WrappedNativeMap*         mWrappedNativeMap;
     ClassInfo2WrappedNativeProtoMap* mWrappedNativeProtoMap;
     nsRefPtr<nsXPCComponentsBase>    mComponents;
     XPCWrappedNativeScope*           mNext;
     // The JS global object for this scope.  If non-null, this will be the
     // default parent for the XPCWrappedNatives that have us as the scope,
     // unless a PreCreate hook overrides it.  Note that this _may_ be null (see
@@ -1139,16 +1153,20 @@ private:
     // XBL Scope. This is is a lazily-created sandbox for non-system scopes.
     // EnsureContentXBLScope() decides whether it needs to be created or not.
     // This reference is wrapped into the compartment of mGlobalJSObject.
     JS::ObjectPtr                    mContentXBLScope;
 
     // 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.
+    nsCOMPtr<nsIAddonInterposition>  mInterposition;
+
     nsAutoPtr<DOMExpandoSet> mDOMExpandoSet;
 
     JS::WeakMapPtr<JSObject*, JSObject*> mXrayExpandos;
 
     bool mIsContentXBLScope;
     bool mIsAddonScope;
 
     // For remote XUL domains, we run all XBL in the content scope for compat
@@ -3275,16 +3293,19 @@ xpc_GetJSPrivate(JSObject *obj)
 inline JSContext *
 xpc_GetSafeJSContext()
 {
     return XPCJSRuntime::Get()->GetJSContextStack()->GetSafeJSContext();
 }
 
 namespace xpc {
 
+JSAddonId *
+NewAddonId(JSContext *cx, const nsACString &id);
+
 // JSNatives to expose atob and btoa in various non-DOM XPConnect scopes.
 bool
 Atob(JSContext *cx, unsigned argc, jsval *vp);
 
 bool
 Btoa(JSContext *cx, unsigned argc, jsval *vp);
 
 class FunctionForwarderOptions;
--- a/js/xpconnect/src/xpcpublic.h
+++ b/js/xpconnect/src/xpcpublic.h
@@ -282,16 +282,18 @@ private:
 
     static void FinalizeLiteral(const JSStringFinalizer *fin, jschar *chars);
 
     static void FinalizeDOMString(const JSStringFinalizer *fin, jschar *chars);
 
     XPCStringConvert();         // not implemented
 };
 
+class nsIAddonInterposition;
+
 namespace xpc {
 
 // If these functions return false, then an exception will be set on cx.
 bool Base64Encode(JSContext *cx, JS::HandleValue val, JS::MutableHandleValue out);
 bool Base64Decode(JSContext *cx, JS::HandleValue val, JS::MutableHandleValue out);
 
 /**
  * Convert an nsString to jsval, returning true on success.
@@ -495,16 +497,19 @@ RecordAdoptedNode(JSCompartment *c);
 void
 RecordDonatedNode(JSCompartment *c);
 
 // This function may be used off-main-thread, in which case it is benignly
 // racey.
 bool
 ShouldDiscardSystemSource();
 
+bool
+SetAddonInterposition(const nsACString &addonId, nsIAddonInterposition *interposition);
+
 } // namespace xpc
 
 namespace mozilla {
 namespace dom {
 
 typedef JSObject*
 (*DefineInterface)(JSContext *cx, JS::Handle<JSObject*> global,
                    JS::Handle<jsid> id, bool defineOnGlobal);
new file mode 100644
--- /dev/null
+++ b/js/xpconnect/tests/unit/test_interposition.js
@@ -0,0 +1,140 @@
+const Cu = Components.utils;
+const Ci = Components.interfaces;
+const Cc = Components.classes;
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+const ADDONID = "bogus-addon@mozilla.org";
+
+let gExpectedProp;
+function expectAccess(prop, f)
+{
+  gExpectedProp = prop;
+  f();
+  do_check_eq(gExpectedProp, undefined);
+}
+
+let getter_run = false;
+function test_getter()
+{
+  do_check_eq(getter_run, false);
+  getter_run = true;
+  return 200;
+}
+
+let setter_run = false;
+function test_setter(v)
+{
+  do_check_eq(setter_run, false);
+  do_check_eq(v, 300);
+  setter_run = true;
+}
+
+let TestInterposition = {
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIAddonInterposition,
+                                         Ci.nsISupportsWeakReference]),
+
+  interpose: function(addonId, target, iid, prop) {
+    do_check_eq(addonId, ADDONID);
+    do_check_eq(gExpectedProp, prop);
+    gExpectedProp = undefined;
+
+    if (prop == "dataprop") {
+      return { configurable: false, enumerable: true, writable: false, value: 100 };
+    } else if (prop == "getterprop") {
+      return { configurable: false, enumerable: true, get: test_getter };
+    } else if (prop == "setterprop") {
+      return { configurable: false, enumerable: true, get: test_getter, set: test_setter };
+    } else if (prop == "utils") {
+      do_check_eq(iid, Ci.nsIXPCComponents.number);
+      return null;
+    } else if (prop == "objprop") {
+      gExpectedProp = "objprop"; // allow recursive access here
+      return { configurable: false, enumerable: true, writable: false, value: { objprop: 1 } };
+    } else if (prop == "configurableprop") {
+      return { configurable: true, enumerable: true, writable: false, value: 10 };
+    }
+
+    return null;
+  }
+};
+
+function run_test()
+{
+  Cu.setAddonInterposition(ADDONID, TestInterposition);
+
+  let sandbox = Cu.Sandbox(this, {addonId: ADDONID});
+  sandbox.outerObj = {};
+
+  expectAccess("abcxyz", () => {
+    Cu.evalInSandbox("outerObj.abcxyz = 12;", sandbox);
+  });
+
+  expectAccess("utils", () => {
+    Cu.evalInSandbox("Components.utils;", sandbox);
+  });
+
+  expectAccess("dataprop", () => {
+    do_check_eq(Cu.evalInSandbox("outerObj.dataprop;", sandbox), 100);
+  });
+
+  expectAccess("dataprop", () => {
+    try {
+      Cu.evalInSandbox("'use strict'; outerObj.dataprop = 400;", sandbox);
+      do_check_true(false); // it should throw
+    } catch (e) {}
+  });
+
+  expectAccess("objprop", () => {
+    Cu.evalInSandbox("outerObj.objprop.objprop;", sandbox);
+    gExpectedProp = undefined;
+  });
+
+  expectAccess("getterprop", () => {
+    do_check_eq(Cu.evalInSandbox("outerObj.getterprop;", sandbox), 200);
+    do_check_eq(getter_run, true);
+    getter_run = false;
+  });
+
+  expectAccess("getterprop", () => {
+    try {
+      Cu.evalInSandbox("'use strict'; outerObj.getterprop = 400;", sandbox);
+      do_check_true(false); // it should throw
+    } catch (e) {}
+    do_check_eq(getter_run, false);
+  });
+
+  expectAccess("setterprop", () => {
+    do_check_eq(Cu.evalInSandbox("outerObj.setterprop;", sandbox), 200);
+    do_check_eq(getter_run, true);
+    getter_run = false;
+    do_check_eq(setter_run, false);
+  });
+
+  expectAccess("setterprop", () => {
+    Cu.evalInSandbox("'use strict'; outerObj.setterprop = 300;", sandbox);
+    do_check_eq(getter_run, false);
+    do_check_eq(setter_run, true);
+    setter_run = false;
+  });
+
+  // Make sure that calling Object.defineProperty succeeds as long as
+  // we're not interposing on that property.
+  expectAccess("defineprop", () => {
+    Cu.evalInSandbox("'use strict'; Object.defineProperty(outerObj, 'defineprop', {configurable:true, enumerable:true, writable:true, value:10});", sandbox);
+  });
+
+  // Related to above: make sure we can delete those properties too.
+  expectAccess("defineprop", () => {
+    Cu.evalInSandbox("'use strict'; delete outerObj.defineprop;", sandbox);
+  });
+
+  // Make sure we get configurable=false even if the interposition
+  // sets it to true.
+  expectAccess("configurableprop", () => {
+    let desc = Cu.evalInSandbox("Object.getOwnPropertyDescriptor(outerObj, 'configurableprop')", sandbox);
+    do_check_eq(desc.configurable, false);
+  });
+
+  Cu.setAddonInterposition(ADDONID, null);
+}
--- a/js/xpconnect/tests/unit/xpcshell.ini
+++ b/js/xpconnect/tests/unit/xpcshell.ini
@@ -50,16 +50,17 @@ support-files =
 [test_bug1034262.js]
 [test_bug_442086.js]
 [test_file.js]
 [test_blob.js]
 [test_blob2.js]
 [test_file2.js]
 [test_import.js]
 [test_import_fail.js]
+[test_interposition.js]
 [test_isModuleLoaded.js]
 [test_js_weak_references.js]
 [test_reflect_parse.js]
 [test_localeCompare.js]
 # Bug 676965: test fails consistently on Android
 fail-if = os == "android"
 [test_recursive_import.js]
 [test_xpcomutils.js]
new file mode 100644
--- /dev/null
+++ b/js/xpconnect/wrappers/AddonWrapper.cpp
@@ -0,0 +1,202 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set ts=8 sts=4 et sw=4 tw=99: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "AddonWrapper.h"
+#include "WrapperFactory.h"
+#include "XrayWrapper.h"
+#include "jsapi.h"
+#include "jsfriendapi.h"
+#include "nsIAddonInterposition.h"
+
+using namespace js;
+using namespace JS;
+
+namespace xpc {
+
+bool
+Interpose(JSContext *cx, HandleObject target, const nsIID *iid, HandleId id,
+          MutableHandle<JSPropertyDescriptor> descriptor)
+{
+    XPCWrappedNativeScope *scope = ObjectScope(CurrentGlobalOrNull(cx));
+    MOZ_ASSERT(scope->HasInterposition());
+
+    nsCOMPtr<nsIAddonInterposition> interp = scope->GetInterposition();
+    JSAddonId *addonId = AddonIdOfObject(target);
+    RootedValue addonIdValue(cx, StringValue(StringOfAddonId(addonId)));
+    RootedValue prop(cx, IdToValue(id));
+    RootedValue targetValue(cx, ObjectValue(*target));
+    RootedValue descriptorVal(cx);
+    nsresult rv = interp->Interpose(addonIdValue, targetValue,
+                                    iid, prop, &descriptorVal);
+    if (NS_FAILED(rv)) {
+        xpc::Throw(cx, rv);
+        return false;
+    }
+
+    if (!descriptorVal.isObject())
+        return true;
+
+    // We need to be careful parsing descriptorVal. |cx| is in the compartment
+    // of the add-on and the descriptor is in the compartment of the
+    // interposition. We could wrap the descriptor in the add-on's compartment
+    // and then parse it. However, parsing the descriptor fetches properties
+    // from it, and we would try to interpose on those property accesses. So
+    // instead we parse in the interposition's compartment and then wrap the
+    // descriptor.
+
+    {
+        JSAutoCompartment ac(cx, &descriptorVal.toObject());
+        if (!JS::ParsePropertyDescriptorObject(cx, target, descriptorVal, descriptor))
+            return false;
+    }
+
+    // Always make the property non-configurable regardless of what the
+    // interposition wants.
+    descriptor.setAttributes(descriptor.attributes() | JSPROP_PERMANENT);
+
+    if (!JS_WrapPropertyDescriptor(cx, descriptor))
+        return false;
+
+    return true;
+}
+
+template<typename Base>
+AddonWrapper<Base>::AddonWrapper(unsigned flags) : Base(flags)
+{
+}
+
+template<typename Base>
+AddonWrapper<Base>::~AddonWrapper()
+{
+}
+
+template<typename Base>
+bool
+AddonWrapper<Base>::getPropertyDescriptor(JSContext *cx, HandleObject wrapper,
+                                          HandleId id, MutableHandle<JSPropertyDescriptor> desc) const
+{
+    if (!Interpose(cx, wrapper, nullptr, id, desc))
+        return false;
+
+    if (desc.object())
+        return true;
+
+    return Base::getPropertyDescriptor(cx, wrapper, id, desc);
+}
+
+template<typename Base>
+bool
+AddonWrapper<Base>::getOwnPropertyDescriptor(JSContext *cx, HandleObject wrapper,
+                                             HandleId id, MutableHandle<JSPropertyDescriptor> desc) const
+{
+    if (!Interpose(cx, wrapper, nullptr, id, desc))
+        return false;
+
+    if (desc.object())
+        return true;
+
+    return Base::getOwnPropertyDescriptor(cx, wrapper, id, desc);
+}
+
+template<typename Base>
+bool
+AddonWrapper<Base>::get(JSContext *cx, JS::Handle<JSObject*> wrapper, JS::Handle<JSObject*> receiver,
+                        JS::Handle<jsid> id, JS::MutableHandle<JS::Value> vp) const
+{
+    Rooted<JSPropertyDescriptor> desc(cx);
+    if (!Interpose(cx, wrapper, nullptr, id, &desc))
+        return false;
+
+    if (!desc.object())
+        return Base::get(cx, wrapper, receiver, id, vp);
+
+    if (desc.getter()) {
+        MOZ_ASSERT(desc.hasGetterObject());
+        AutoValueVector args(cx);
+        RootedValue fval(cx, ObjectValue(*desc.getterObject()));
+        return JS_CallFunctionValue(cx, receiver, fval, args, vp);
+    } else {
+        vp.set(desc.value());
+        return true;
+    }
+}
+
+template<typename Base>
+bool
+AddonWrapper<Base>::set(JSContext *cx, JS::HandleObject wrapper, JS::HandleObject receiver,
+                        JS::HandleId id, bool strict, JS::MutableHandleValue vp) const
+{
+    Rooted<JSPropertyDescriptor> desc(cx);
+    if (!Interpose(cx, wrapper, nullptr, id, &desc))
+        return false;
+
+    if (!desc.object())
+        return Base::set(cx, wrapper, receiver, id, strict, vp);
+
+    if (desc.setter()) {
+        MOZ_ASSERT(desc.hasSetterObject());
+        MOZ_ASSERT(!desc.isReadonly());
+        JS::AutoValueVector args(cx);
+        args.append(vp);
+        RootedValue fval(cx, ObjectValue(*desc.setterObject()));
+        return JS_CallFunctionValue(cx, receiver, fval, args, vp);
+    } else {
+        if (!strict)
+            return true;
+
+        js::ReportErrorWithId(cx, "unable to set interposed data property %s", id);
+        return false;
+    }
+}
+
+template<typename Base>
+bool
+AddonWrapper<Base>::defineProperty(JSContext *cx, HandleObject wrapper, HandleId id,
+                                   MutableHandle<JSPropertyDescriptor> desc) const
+{
+    Rooted<JSPropertyDescriptor> interpDesc(cx);
+    if (!Interpose(cx, wrapper, nullptr, id, &interpDesc))
+        return false;
+
+    if (!interpDesc.object())
+        return Base::defineProperty(cx, wrapper, id, desc);
+
+    js::ReportErrorWithId(cx, "unable to modify interposed property %s", id);
+    return false;
+}
+
+template<typename Base>
+bool
+AddonWrapper<Base>::delete_(JSContext *cx, HandleObject wrapper, HandleId id, bool *bp) const
+{
+    Rooted<JSPropertyDescriptor> desc(cx);
+    if (!Interpose(cx, wrapper, nullptr, id, &desc))
+        return false;
+
+    if (!desc.object())
+        return Base::delete_(cx, wrapper, id, bp);
+
+    js::ReportErrorWithId(cx, "unable to delete interposed property %s", id);
+    return false;
+}
+
+#define AddonWrapperCC AddonWrapper<CrossCompartmentWrapper>
+#define AddonWrapperXrayXPCWN AddonWrapper<PermissiveXrayXPCWN>
+#define AddonWrapperXrayDOM AddonWrapper<PermissiveXrayDOM>
+
+template<> AddonWrapperCC AddonWrapperCC::singleton(0);
+template<> AddonWrapperXrayXPCWN AddonWrapperXrayXPCWN::singleton(0);
+template<> AddonWrapperXrayDOM AddonWrapperXrayDOM::singleton(0);
+
+template class AddonWrapperCC;
+template class AddonWrapperXrayXPCWN;
+template class AddonWrapperXrayDOM;
+
+#undef AddonWrapperCC
+#undef AddonWrapperXrayXPCWN
+#undef AddonWrapperXrayDOM
+
+} // namespace xpc
new file mode 100644
--- /dev/null
+++ b/js/xpconnect/wrappers/AddonWrapper.h
@@ -0,0 +1,47 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set ts=8 sts=4 et sw=4 tw=99: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef AddonWrapper_h
+#define AddonWrapper_h
+
+#include "mozilla/Attributes.h"
+
+#include "jswrapper.h"
+
+namespace xpc {
+
+bool
+Interpose(JSContext *cx, HandleObject target, const nsIID *iid, HandleId id,
+          MutableHandle<JSPropertyDescriptor> descriptor);
+
+template<typename Base>
+class AddonWrapper : public Base {
+  public:
+    AddonWrapper(unsigned flags);
+    virtual ~AddonWrapper();
+
+    virtual bool getPropertyDescriptor(JSContext *cx, JS::Handle<JSObject*> wrapper,
+                                       JS::Handle<jsid> id,
+                                       JS::MutableHandle<JSPropertyDescriptor> desc) const MOZ_OVERRIDE;
+    virtual bool getOwnPropertyDescriptor(JSContext *cx, JS::Handle<JSObject*> wrapper,
+                                          JS::Handle<jsid> id,
+                                          JS::MutableHandle<JSPropertyDescriptor> desc) const MOZ_OVERRIDE;
+
+    virtual bool get(JSContext *cx, JS::Handle<JSObject*> wrapper, JS::Handle<JSObject*> receiver,
+                     JS::Handle<jsid> id, JS::MutableHandle<JS::Value> vp) const MOZ_OVERRIDE;
+    virtual bool set(JSContext *cx, JS::HandleObject wrapper, JS::HandleObject receiver,
+                     JS::HandleId id, bool strict, JS::MutableHandleValue vp) const MOZ_OVERRIDE;
+
+    virtual bool defineProperty(JSContext *cx, HandleObject proxy, HandleId id,
+                                MutableHandle<JSPropertyDescriptor> desc) const MOZ_OVERRIDE;
+    virtual bool delete_(JSContext *cx, HandleObject proxy, HandleId id, bool *bp) const MOZ_OVERRIDE;
+
+    static AddonWrapper singleton;
+};
+
+} // namespace xpc
+
+#endif // AddonWrapper_h
--- a/js/xpconnect/wrappers/WrapperFactory.cpp
+++ b/js/xpconnect/wrappers/WrapperFactory.cpp
@@ -392,16 +392,41 @@ SelectWrapper(bool securityWrapper, bool
     // if needed, by using ExportFunction to generate the content-side
     // representations of XBL methods.
     MOZ_ASSERT(xrayType == XrayForJSObject || xrayType == XrayForOpaqueObject);
     if (originIsXBLScope)
         return &FilteringWrapper<CrossCompartmentSecurityWrapper, OpaqueWithCall>::singleton;
     return &FilteringWrapper<CrossCompartmentSecurityWrapper, Opaque>::singleton;
 }
 
+static const Wrapper *
+SelectAddonWrapper(JSContext *cx, HandleObject obj, const Wrapper *wrapper)
+{
+    JSAddonId *originAddon = JS::AddonIdOfObject(obj);
+    JSAddonId *targetAddon = JS::AddonIdOfObject(JS::CurrentGlobalOrNull(cx));
+
+    MOZ_ASSERT(AccessCheck::isChrome(JS::CurrentGlobalOrNull(cx)));
+    MOZ_ASSERT(targetAddon);
+
+    if (targetAddon == originAddon)
+        return wrapper;
+
+    // Add-on interposition only supports certain wrapper types, so we check if
+    // we would have used one of the supported ones.
+    if (wrapper == &CrossCompartmentWrapper::singleton)
+        return &AddonWrapper<CrossCompartmentWrapper>::singleton;
+    else if (wrapper == &PermissiveXrayXPCWN::singleton)
+        return &AddonWrapper<PermissiveXrayXPCWN>::singleton;
+    else if (wrapper == &PermissiveXrayDOM::singleton)
+        return &AddonWrapper<PermissiveXrayDOM>::singleton;
+
+    // |wrapper| is not supported for interposition, so we don't do it.
+    return wrapper;
+}
+
 JSObject *
 WrapperFactory::Rewrap(JSContext *cx, HandleObject existing, HandleObject obj,
                        HandleObject parent, unsigned flags)
 {
     MOZ_ASSERT(!IsWrapper(obj) ||
                GetProxyHandler(obj) == &XrayWaiver ||
                js::GetObjectClass(obj)->ext.innerObject,
                "wrapped object passed to rewrap");
@@ -472,16 +497,21 @@ WrapperFactory::Rewrap(JSContext *cx, Ha
         bool waiveXrays = wantXrays && !securityWrapper && waiveXrayFlag;
 
         // We have slightly different behavior for the case when the object
         // being wrapped is in an XBL scope.
         bool originIsContentXBLScope = IsContentXBLScope(origin);
 
         wrapper = SelectWrapper(securityWrapper, wantXrays, xrayType, waiveXrays,
                                 originIsContentXBLScope);
+
+        // If we want to apply add-on interposition in the target compartment,
+        // then we try to "upgrade" the wrapper to an interposing one.
+        if (CompartmentPrivate::Get(target)->scope->HasInterposition())
+            wrapper = SelectAddonWrapper(cx, obj, wrapper);
     }
 
     if (!targetSubsumesOrigin) {
         // Do a belt-and-suspenders check against exposing eval()/Function() to
         // non-subsuming content.
         JSFunction *fun = JS_GetObjectFunction(obj);
         if (fun) {
             if (JS_IsBuiltinEvalFunction(fun) || JS_IsBuiltinFunctionConstructor(fun)) {
--- a/js/xpconnect/wrappers/moz.build
+++ b/js/xpconnect/wrappers/moz.build
@@ -5,16 +5,17 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 EXPORTS += [
     'WrapperFactory.h',
 ]
 
 UNIFIED_SOURCES += [
     'AccessCheck.cpp',
+    'AddonWrapper.cpp',
     'ChromeObjectWrapper.cpp',
     'FilteringWrapper.cpp',
     'WaiveXrayWrapper.cpp',
     'WrapperFactory.cpp',
 ]
 
 # XrayWrapper needs to be built separately becaue of template instantiations.
 SOURCES += [