Bug 718543 - Preserve SpecialPowers wrapper identity with a WeakMap cache. r=mrbkap
authorBobby Holley <bobbyholley@gmail.com>
Sun, 19 Feb 2012 15:47:12 -0800
changeset 87194 f9145dab4be93acbc202f4c9515fd693bb3c8d0b
parent 87193 16c4770044a8e360c391e917c4f0e6be327664c5
child 87195 766fdf473acd96b76ca7c7669a0672df9cf1633b
push id22091
push userbmo@edmorley.co.uk
push dateMon, 20 Feb 2012 12:09:20 +0000
treeherdermozilla-central@b8e7474374d5 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmrbkap
bugs718543
milestone13.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 718543 - Preserve SpecialPowers wrapper identity with a WeakMap cache. r=mrbkap
testing/mochitest/tests/SimpleTest/specialpowersAPI.js
testing/mochitest/tests/test_SpecialPowersExtension.html
--- a/testing/mochitest/tests/SimpleTest/specialpowersAPI.js
+++ b/testing/mochitest/tests/SimpleTest/specialpowersAPI.js
@@ -151,30 +151,38 @@ function isXrayWrapper(x) {
 }
 
 // We can't call apply() directy on Xray-wrapped functions, so we have to be
 // clever.
 function doApply(fun, invocant, args) {
   return Function.prototype.apply.call(fun, invocant, args);
 }
 
+// Use a weak map to cache wrappers. This allows the wrappers to preserve identity.
+var wrapperCache = WeakMap();
+
 function wrapPrivileged(obj) {
 
   // Primitives pass straight through.
   if (!isWrappable(obj))
     return obj;
 
   // No double wrapping.
   if (isWrapper(obj))
     throw "Trying to double-wrap object!";
 
+  // Try the cache.
+  if (wrapperCache.has(obj))
+    return wrapperCache.get(obj);
+
   // Make our core wrapper object.
   var handler = new SpecialPowersHandler(obj);
 
   // If the object is callable, make a function proxy.
+  var wrapper;
   if (typeof obj === "function") {
     var callTrap = function() {
       // The invocant and arguments may or may not be wrappers. Unwrap them if necessary.
       var invocant = unwrapIfWrapped(this);
       var unwrappedArgs = Array.prototype.slice.call(arguments).map(unwrapIfWrapped);
 
       return wrapPrivileged(doApply(obj, invocant, unwrappedArgs));
     };
@@ -188,21 +196,26 @@ function wrapPrivileged(obj) {
       var FakeConstructor = function() {
         doApply(obj, this, unwrappedArgs);
       };
       FakeConstructor.prototype = obj.prototype;
 
       return wrapPrivileged(new FakeConstructor());
     };
 
-    return Proxy.createFunction(handler, callTrap, constructTrap);
+    wrapper = Proxy.createFunction(handler, callTrap, constructTrap);
+  }
+  // Otherwise, just make a regular object proxy.
+  else {
+    wrapper = Proxy.create(handler);
   }
 
-  // Otherwise, just make a regular object proxy.
-  return Proxy.create(handler);
+  // Cache the wrapper and return it.
+  wrapperCache.set(obj, wrapper);
+  return wrapper;
 };
 
 function unwrapPrivileged(x) {
 
   // We don't wrap primitives, so sometimes we have a primitive where we'd
   // expect to have a wrapper. The proxy pretends to be the type that it's
   // emulating, so we can just as easily check isWrappable() on a proxy as
   // we can on an unwrapped object.
@@ -403,19 +416,16 @@ SpecialPowersAPI.prototype = {
    * object containing a reference to the underlying object, where all method
    * calls and property accesses are transparently performed with the System
    * Principal. Moreover, objects obtained from the wrapper (including properties
    * and method return values) are wrapped automatically. Thus, after a single
    * call to SpecialPowers.wrap(), the wrapper layer is transitively maintained.
    *
    * Known Issues:
    *
-   *  - The wrapping function does not preserve identity, so
-   *    SpecialPowers.wrap(foo) !== SpecialPowers.wrap(foo). See bug 718543.
-   *
    *  - The wrapper cannot see expando properties on unprivileged DOM objects.
    *    That is to say, the wrapper uses Xray delegation.
    *
    *  - The wrapper sometimes guesses certain ES5 attributes for returned
    *    properties. This is explained in a comment in the wrapper code above,
    *    and shouldn't be a problem.
    */
   wrap: wrapPrivileged,
--- a/testing/mochitest/tests/test_SpecialPowersExtension.html
+++ b/testing/mochitest/tests/test_SpecialPowersExtension.html
@@ -129,16 +129,23 @@ function starttest(){
   noxray.__proto__ = noxray_proto;
   var noxray_wrapper = SpecialPowers.wrap(noxray);
   is(noxray_wrapper.c, 32, "Regular properties should work.");
   is(noxray_wrapper.a, 5, "Shadow properties should work.");
   is(noxray_wrapper.b, 12, "Proto properties should work.");
   noxray.b = 122;
   is(noxray_wrapper.b, 122, "Should be able to shadow.");
 
+  // Check that the wrapper preserves identity.
+  var someIdentityObject = {a: 2};
+  ok(SpecialPowers.wrap(someIdentityObject) === SpecialPowers.wrap(someIdentityObject),
+     "SpecialPowers wrapping should preserve identity!");
+  ok(webnav === SpecialPowers.wrap(SpecialPowers.unwrap(webnav)),
+     "SpecialPowers wrapping should preserve identity!");
+
   info("\nProfile::SpecialPowersRunTime: " + (new Date() - startTime) + "\n");
   SimpleTest.finish();
 }
 </script>
 </pre>
 </body>
 </html>