Bug 1017109 - Add construct hook for CPOWs (r=mrbkap)
authorBill McCloskey <wmccloskey@mozilla.com>
Mon, 30 Jun 2014 17:50:58 -0700
changeset 191638 032a08766a8c0ad779acf95d241d70de4fd1a14c
parent 191637 ba6b27e2b92a4c64262bcbb20c83814534e5e01e
child 191639 d0787b1eebc1f6cf8ba948a2283128cc96c0ca06
push id27055
push usercbook@mozilla.com
push dateTue, 01 Jul 2014 12:01:46 +0000
treeherdermozilla-central@4a9353b5762d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmrbkap
bugs1017109
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 1017109 - Add construct hook for CPOWs (r=mrbkap)
content/base/test/chrome/cpows_child.js
content/base/test/chrome/cpows_parent.xul
js/ipc/JavaScriptBase.h
js/ipc/PJavaScript.ipdl
js/ipc/WrapperAnswer.cpp
js/ipc/WrapperAnswer.h
js/ipc/WrapperOwner.cpp
js/ipc/WrapperOwner.h
js/src/jsapi.cpp
js/src/jsapi.h
js/src/vm/Interpreter.cpp
js/src/vm/Interpreter.h
--- a/content/base/test/chrome/cpows_child.js
+++ b/content/base/test/chrome/cpows_child.js
@@ -40,16 +40,17 @@ var async_obj;
 function make_object()
 {
   let o = { };
   o.i = 5;
   o.b = true;
   o.s = "hello";
   o.x = { i: 10 };
   o.f = function () { return 99; };
+  o.ctor = function() { this.a = 3; }
 
   // Doing anything with this Proxy will throw.
   var throwing = new Proxy({}, new Proxy({}, {
       get: function (trap) { throw trap; }
     }));
 
   let array = [1, 2, 3];
 
--- a/content/base/test/chrome/cpows_parent.xul
+++ b/content/base/test/chrome/cpows_parent.xul
@@ -43,16 +43,18 @@
         ok(Components.utils.isCrossProcessWrapper(data), "got a CPOW");
         ok(Components.utils.isCrossProcessWrapper(document), "got a CPOW");
       }
       ok(data.i === 5, "integer property");
       ok(data.b === true, "boolean property");
       ok(data.s === "hello", "string property");
       ok(data.x.i === 10, "nested property");
       ok(data.f() === 99, "function call");
+      let obj = new data.ctor();
+      ok(obj.a === 3, "constructor call");
       ok(document.title === "Hello, Kitty", "document node");
 
       data.i = 6;
       data.b = false;
       data.s = "bye";
       data.x = null;
       ok(data.i === 6, "integer property");
       ok(data.b === false, "boolean property");
--- a/js/ipc/JavaScriptBase.h
+++ b/js/ipc/JavaScriptBase.h
@@ -77,20 +77,20 @@ class JavaScriptBase : public WrapperOwn
                    const JSVariant &value, ReturnStatus *rs, JSVariant *result) {
         return Answer::AnswerSet(objId, receiverVar, id, strict, value, rs, result);
     }
 
     bool AnswerIsExtensible(const ObjectId &objId, ReturnStatus *rs,
                             bool *result) {
         return Answer::AnswerIsExtensible(objId, rs, result);
     }
-    bool AnswerCall(const ObjectId &objId, const nsTArray<JSParam> &argv,
-                    ReturnStatus *rs, JSVariant *result,
-                    nsTArray<JSParam> *outparams) {
-        return Answer::AnswerCall(objId, argv, rs, result, outparams);
+    bool AnswerCallOrConstruct(const ObjectId &objId, const nsTArray<JSParam> &argv,
+                               const bool &construct, ReturnStatus *rs, JSVariant *result,
+                               nsTArray<JSParam> *outparams) {
+        return Answer::AnswerCallOrConstruct(objId, argv, construct, rs, result, outparams);
     }
     bool AnswerObjectClassIs(const ObjectId &objId, const uint32_t &classValue,
                              bool *result) {
         return Answer::AnswerObjectClassIs(objId, classValue, result);
     }
     bool AnswerClassName(const ObjectId &objId, nsString *result) {
         return Answer::AnswerClassName(objId, result);
     }
@@ -159,20 +159,20 @@ class JavaScriptBase : public WrapperOwn
                  const JSVariant &value, ReturnStatus *rs, JSVariant *result) {
         return Base::CallSet(objId, receiverVar, id, strict, value, rs, result);
     }
 
     bool CallIsExtensible(const ObjectId &objId, ReturnStatus *rs,
                           bool *result) {
         return Base::CallIsExtensible(objId, rs, result);
     }
-    bool CallCall(const ObjectId &objId, const nsTArray<JSParam> &argv,
-                  ReturnStatus *rs, JSVariant *result,
-                  nsTArray<JSParam> *outparams) {
-        return Base::CallCall(objId, argv, rs, result, outparams);
+    bool CallCallOrConstruct(const ObjectId &objId, const nsTArray<JSParam> &argv,
+                             const bool &construct, ReturnStatus *rs, JSVariant *result,
+                             nsTArray<JSParam> *outparams) {
+        return Base::CallCallOrConstruct(objId, argv, construct, rs, result, outparams);
     }
     bool CallObjectClassIs(const ObjectId &objId, const uint32_t &classValue,
                            bool *result) {
         return Base::CallObjectClassIs(objId, classValue, result);
     }
     bool CallClassName(const ObjectId &objId, nsString *result) {
         return Base::CallClassName(objId, result);
     }
--- a/js/ipc/PJavaScript.ipdl
+++ b/js/ipc/PJavaScript.ipdl
@@ -31,17 +31,17 @@ both:
     rpc Delete(uint64_t objId, nsString id) returns (ReturnStatus rs, bool successful);
 
     rpc Has(uint64_t objId, nsString id) returns (ReturnStatus rs, bool has);
     rpc HasOwn(uint64_t objId, nsString id) returns (ReturnStatus rs, bool has);
     rpc Get(uint64_t objId, ObjectVariant receiver, nsString id) returns (ReturnStatus rs, JSVariant result);
     rpc Set(uint64_t objId, ObjectVariant receiver, nsString id, bool strict, JSVariant value) returns (ReturnStatus rs, JSVariant result);
 
     rpc IsExtensible(uint64_t objId) returns (ReturnStatus rs, bool result);
-    rpc Call(uint64_t objId, JSParam[] argv) returns (ReturnStatus rs, JSVariant result, JSParam[] outparams);
+    rpc CallOrConstruct(uint64_t objId, JSParam[] argv, bool construct) returns (ReturnStatus rs, JSVariant result, JSParam[] outparams);
     rpc ObjectClassIs(uint64_t objId, uint32_t classValue) returns (bool result);
     rpc ClassName(uint64_t objId) returns (nsString name);
 
     rpc GetPropertyNames(uint64_t objId, uint32_t flags) returns (ReturnStatus rs, nsString[] names);
     rpc InstanceOf(uint64_t objId, JSIID iid) returns (ReturnStatus rs, bool instanceof);
     rpc DOMInstanceOf(uint64_t objId, int prototypeID, int depth) returns (ReturnStatus rs, bool instanceof);
 
 parent:
--- a/js/ipc/WrapperAnswer.cpp
+++ b/js/ipc/WrapperAnswer.cpp
@@ -376,18 +376,22 @@ WrapperAnswer::AnswerIsExtensible(const 
     if (!JS_IsExtensible(cx, obj, &extensible))
         return fail(cx, rs);
 
     *result = !!extensible;
     return ok(rs);
 }
 
 bool
-WrapperAnswer::AnswerCall(const ObjectId &objId, const nsTArray<JSParam> &argv, ReturnStatus *rs,
-			  JSVariant *result, nsTArray<JSParam> *outparams)
+WrapperAnswer::AnswerCallOrConstruct(const ObjectId &objId,
+                                     const nsTArray<JSParam> &argv,
+                                     const bool &construct,
+                                     ReturnStatus *rs,
+                                     JSVariant *result,
+                                     nsTArray<JSParam> *outparams)
 {
     AutoSafeJSContext cx;
     JSAutoRequest request(cx);
 
     // The outparam will be written to the buffer, so it must be set even if
     // the parent won't read it.
     *result = UndefinedVariant();
 
@@ -429,17 +433,21 @@ WrapperAnswer::AnswerCall(const ObjectId
     }
 
     RootedValue rval(cx);
     {
         AutoSaveContextOptions asco(cx);
         ContextOptionsRef(cx).setDontReportUncaught(true);
 
         HandleValueArray args = HandleValueArray::subarray(vals, 2, vals.length() - 2);
-        bool success = JS::Call(cx, vals[1], vals[0], args, &rval);
+        bool success;
+        if (construct)
+            success = JS::Construct(cx, vals[0], args, &rval);
+        else
+            success = JS::Call(cx, vals[1], vals[0], args, &rval);
         if (!success)
             return fail(cx, rs);
     }
 
     if (!toVariant(cx, rval, result))
         return fail(cx, rs);
 
     // Prefill everything with a dummy jsval.
--- a/js/ipc/WrapperAnswer.h
+++ b/js/ipc/WrapperAnswer.h
@@ -40,19 +40,19 @@ class WrapperAnswer : public virtual Jav
                        const nsString &id,
                        ReturnStatus *rs, JSVariant *result);
     bool AnswerSet(const ObjectId &objId, const ObjectVariant &receiverVar,
                    const nsString &id, const bool &strict,
                    const JSVariant &value, ReturnStatus *rs, JSVariant *result);
 
     bool AnswerIsExtensible(const ObjectId &objId, ReturnStatus *rs,
                             bool *result);
-    bool AnswerCall(const ObjectId &objId, const nsTArray<JSParam> &argv,
-                    ReturnStatus *rs, JSVariant *result,
-                    nsTArray<JSParam> *outparams);
+    bool AnswerCallOrConstruct(const ObjectId &objId, const nsTArray<JSParam> &argv,
+                               const bool &construct, ReturnStatus *rs, JSVariant *result,
+                               nsTArray<JSParam> *outparams);
     bool AnswerObjectClassIs(const ObjectId &objId, const uint32_t &classValue,
                              bool *result);
     bool AnswerClassName(const ObjectId &objId, nsString *result);
 
     bool AnswerGetPropertyNames(const ObjectId &objId, const uint32_t &flags,
                                 ReturnStatus *rs, nsTArray<nsString> *names);
     bool AnswerInstanceOf(const ObjectId &objId, const JSIID &iid,
                           ReturnStatus *rs, bool *instanceof);
--- a/js/ipc/WrapperOwner.cpp
+++ b/js/ipc/WrapperOwner.cpp
@@ -75,16 +75,17 @@ class CPOWProxyHandler : public BaseProx
     virtual bool get(JSContext *cx, HandleObject proxy, HandleObject receiver,
                      HandleId id, MutableHandleValue vp) const MOZ_OVERRIDE;
     virtual bool set(JSContext *cx, JS::HandleObject proxy, JS::HandleObject receiver,
                      JS::HandleId id, bool strict, JS::MutableHandleValue vp) const MOZ_OVERRIDE;
     virtual bool keys(JSContext *cx, HandleObject proxy, AutoIdVector &props) const MOZ_OVERRIDE;
 
     virtual bool isExtensible(JSContext *cx, HandleObject proxy, bool *extensible) const MOZ_OVERRIDE;
     virtual bool call(JSContext *cx, HandleObject proxy, const CallArgs &args) const MOZ_OVERRIDE;
+    virtual bool construct(JSContext *cx, HandleObject proxy, const CallArgs &args) const MOZ_OVERRIDE;
     virtual bool objectClassIs(HandleObject obj, js::ESClassValue classValue,
                                JSContext *cx) const MOZ_OVERRIDE;
     virtual const char* className(JSContext *cx, HandleObject proxy) const MOZ_OVERRIDE;
     virtual void finalize(JSFreeOp *fop, JSObject *proxy) const MOZ_OVERRIDE;
 
     static const CPOWProxyHandler singleton;
 };
 
@@ -331,17 +332,17 @@ CPOWToString(JSContext *cx, unsigned arg
 }
 
 bool
 WrapperOwner::toString(JSContext *cx, HandleObject cpow, JS::CallArgs &args)
 {
     // Ask the other side to call its toString method. Update the callee so that
     // it points to the CPOW and not to the synthesized CPOWToString function.
     args.setCallee(ObjectValue(*cpow));
-    if (!call(cx, cpow, args))
+    if (!callOrConstruct(cx, cpow, args, false))
         return false;
 
     if (!args.rval().isString())
         return true;
 
     RootedString cpowResult(cx, args.rval().toString());
     nsDependentJSString toStringResult;
     if (!toStringResult.init(cx, cpowResult))
@@ -477,30 +478,42 @@ WrapperOwner::isExtensible(JSContext *cx
     LOG_STACK();
 
     return ok(cx, status);
 }
 
 bool
 CPOWProxyHandler::call(JSContext *cx, HandleObject proxy, const CallArgs &args) const
 {
-    FORWARD(call, (cx, proxy, args));
+    FORWARD(callOrConstruct, (cx, proxy, args, false));
 }
 
 bool
-WrapperOwner::call(JSContext *cx, HandleObject proxy, const CallArgs &args)
+CPOWProxyHandler::construct(JSContext *cx, HandleObject proxy, const CallArgs &args) const
+{
+    FORWARD(callOrConstruct, (cx, proxy, args, true));
+}
+
+bool
+WrapperOwner::callOrConstruct(JSContext *cx, HandleObject proxy, const CallArgs &args,
+                              bool construct)
 {
     ObjectId objId = idOf(proxy);
 
     InfallibleTArray<JSParam> vals;
     AutoValueVector outobjects(cx);
 
     RootedValue v(cx);
     for (size_t i = 0; i < args.length() + 2; i++) {
-        v = args.base()[i];
+        // The |this| value for constructors is a magic value that we won't be
+        // able to convert, so skip it.
+        if (i == 1 && construct)
+            v = UndefinedValue();
+        else
+            v = args.base()[i];
         if (v.isObject()) {
             RootedObject obj(cx, &v.toObject());
             if (xpc::IsOutObject(cx, obj)) {
                 // Make sure it is not an in-out object.
                 bool found;
                 if (!JS_HasProperty(cx, obj, "value", &found))
                     return false;
                 if (found) {
@@ -518,17 +531,17 @@ WrapperOwner::call(JSContext *cx, Handle
         if (!toVariant(cx, v, &val))
             return false;
         vals.AppendElement(JSParam(val));
     }
 
     JSVariant result;
     ReturnStatus status;
     InfallibleTArray<JSParam> outparams;
-    if (!CallCall(objId, vals, &status, &result, &outparams))
+    if (!CallCallOrConstruct(objId, vals, construct, &status, &result, &outparams))
         return ipcfail(cx);
 
     LOG_STACK();
 
     if (!ok(cx, status))
         return false;
 
     if (outparams.Length() != outobjects.length())
--- a/js/ipc/WrapperOwner.h
+++ b/js/ipc/WrapperOwner.h
@@ -50,17 +50,18 @@ class WrapperOwner : public virtual Java
              JS::HandleId id, JS::MutableHandleValue vp);
     bool set(JSContext *cx, JS::HandleObject proxy, JS::HandleObject receiver,
              JS::HandleId id, bool strict, JS::MutableHandleValue vp);
     bool keys(JSContext *cx, JS::HandleObject proxy, JS::AutoIdVector &props);
     // We use "iterate" provided by the base class here.
 
     // SpiderMonkey Extensions.
     bool isExtensible(JSContext *cx, JS::HandleObject proxy, bool *extensible);
-    bool call(JSContext *cx, JS::HandleObject proxy, const JS::CallArgs &args);
+    bool callOrConstruct(JSContext *cx, JS::HandleObject proxy, const JS::CallArgs &args,
+                         bool construct);
     bool objectClassIs(JSContext *cx, JS::HandleObject obj, js::ESClassValue classValue);
     const char* className(JSContext *cx, JS::HandleObject proxy);
 
     nsresult instanceOf(JSObject *obj, const nsID *id, bool *bp);
 
     bool toString(JSContext *cx, JS::HandleObject callee, JS::CallArgs &args);
 
     /*
@@ -120,19 +121,19 @@ class WrapperOwner : public virtual Java
                          const nsString &id,
                          ReturnStatus *rs, JSVariant *result) = 0;
     virtual bool CallSet(const ObjectId &objId, const ObjectVariant &receiverVar,
                          const nsString &id, const bool &strict,
                          const JSVariant &value, ReturnStatus *rs, JSVariant *result) = 0;
 
     virtual bool CallIsExtensible(const ObjectId &objId, ReturnStatus *rs,
                                   bool *result) = 0;
-    virtual bool CallCall(const ObjectId &objId, const nsTArray<JSParam> &argv,
-                          ReturnStatus *rs, JSVariant *result,
-                          nsTArray<JSParam> *outparams) = 0;
+    virtual bool CallCallOrConstruct(const ObjectId &objId, const nsTArray<JSParam> &argv,
+                                     const bool &construct, ReturnStatus *rs, JSVariant *result,
+                                     nsTArray<JSParam> *outparams) = 0;
     virtual bool CallObjectClassIs(const ObjectId &objId, const uint32_t &classValue,
                                    bool *result) = 0;
     virtual bool CallClassName(const ObjectId &objId, nsString *result) = 0;
 
     virtual bool CallGetPropertyNames(const ObjectId &objId, const uint32_t &flags,
                                       ReturnStatus *rs, nsTArray<nsString> *names) = 0;
     virtual bool CallInstanceOf(const ObjectId &objId, const JSIID &iid,
                                 ReturnStatus *rs, bool *instanceof) = 0;
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -5121,16 +5121,28 @@ JS::Call(JSContext *cx, HandleValue this
     AssertHeapIsIdle(cx);
     CHECK_REQUEST(cx);
     assertSameCompartment(cx, thisv, fval, args);
     AutoLastFrameCheck lfc(cx);
 
     return Invoke(cx, thisv, fval, args.length(), args.begin(), rval);
 }
 
+JS_PUBLIC_API(bool)
+JS::Construct(JSContext *cx, HandleValue fval, const JS::HandleValueArray& args,
+              MutableHandleValue rval)
+{
+    AssertHeapIsIdle(cx);
+    CHECK_REQUEST(cx);
+    assertSameCompartment(cx, fval, args);
+    AutoLastFrameCheck lfc(cx);
+
+    return InvokeConstructor(cx, fval, args.length(), args.begin(), rval.address());
+}
+
 JS_PUBLIC_API(JSObject *)
 JS_New(JSContext *cx, HandleObject ctor, const JS::HandleValueArray& inputArgs)
 {
     AssertHeapIsIdle(cx);
     CHECK_REQUEST(cx);
     assertSameCompartment(cx, ctor, inputArgs);
     AutoLastFrameCheck lfc(cx);
 
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -3991,16 +3991,21 @@ static inline bool
 Call(JSContext *cx, JS::HandleValue thisv, JS::HandleObject funObj, const JS::HandleValueArray& args,
      MutableHandleValue rval)
 {
     JS_ASSERT(funObj);
     JS::RootedValue fun(cx, JS::ObjectValue(*funObj));
     return Call(cx, thisv, fun, args, rval);
 }
 
+extern JS_PUBLIC_API(bool)
+Construct(JSContext *cx, JS::HandleValue fun,
+          const JS::HandleValueArray& args,
+          MutableHandleValue rval);
+
 } /* namespace JS */
 
 /*
  * These functions allow setting an interrupt callback that will be called
  * from the JS thread some time after any thread triggered the callback using
  * JS_RequestInterruptCallback(rt).
  *
  * To schedule the GC and for other activities the engine internally triggers
--- a/js/src/vm/Interpreter.cpp
+++ b/js/src/vm/Interpreter.cpp
@@ -548,17 +548,17 @@ js::InvokeConstructor(JSContext *cx, Cal
     const Class *clasp = callee.getClass();
     if (!clasp->construct)
         return ReportIsNotFunction(cx, args.calleev(), args.length() + 1, CONSTRUCT);
 
     return CallJSNativeConstructor(cx, clasp->construct, args);
 }
 
 bool
-js::InvokeConstructor(JSContext *cx, Value fval, unsigned argc, Value *argv, Value *rval)
+js::InvokeConstructor(JSContext *cx, Value fval, unsigned argc, const Value *argv, Value *rval)
 {
     InvokeArgs args(cx);
     if (!args.init(argc))
         return false;
 
     args.setCallee(fval);
     args.setThis(MagicValue(JS_THIS_POISON));
     PodCopy(args.array(), argv, argc);
--- a/js/src/vm/Interpreter.h
+++ b/js/src/vm/Interpreter.h
@@ -144,17 +144,17 @@ InvokeGetterOrSetter(JSContext *cx, JSOb
  * InvokeConstructor implement a function call from a constructor context
  * (e.g. 'new') handling the the creation of the new 'this' object.
  */
 extern bool
 InvokeConstructor(JSContext *cx, CallArgs args);
 
 /* See the fval overload of Invoke. */
 extern bool
-InvokeConstructor(JSContext *cx, Value fval, unsigned argc, Value *argv, Value *rval);
+InvokeConstructor(JSContext *cx, Value fval, unsigned argc, const Value *argv, Value *rval);
 
 /*
  * Executes a script with the given scopeChain/this. The 'type' indicates
  * whether this is eval code or global code. To support debugging, the
  * evalFrame parameter can point to an arbitrary frame in the context's call
  * stack to simulate executing an eval in that frame.
  */
 extern bool