Bug 1377587, part 1 - Always act like __exposedProps__ is missing. r=krizsa
authorAndrew McCreight <continuation@gmail.com>
Tue, 22 Aug 2017 14:24:11 -0700
changeset 429640 0a058c19c23507038bf2a1c830ddc34ca90fb578
parent 429639 db9a3beb83849e3ab2527df6630679f4a38e7c5a
child 429641 cdb4e0259eebe3db8f2abeb31aa4696656f3c2e2
push id7761
push userjlund@mozilla.com
push dateFri, 15 Sep 2017 00:19:52 +0000
treeherdermozilla-beta@c38455951db4 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskrizsa
bugs1377587, 1065185
milestone57.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 1377587, part 1 - Always act like __exposedProps__ is missing. r=krizsa This patch gently removes support for __exposedProps__ by changing ExposedPropertiesOnly::check() to always return false, while still failing silently in deny for some kinds of access. The tests that I changed all involve testing the behavior with __exposedProps__. I adjusted them to expect it to fail, or to adjust the error message they get when they fail. That seemed better than deleting them entirely. Note that test_bug1065185.html had a bug, so that it never executed the first case. I fixed that, and then fixed up the test to work when __exposedProps__ is not supported. This also removes various bits of the test framework that use __exposedProps__, but don't actually need to. MozReview-Commit-ID: 8fvkAmITmXY
addon-sdk/source/lib/sdk/console/plain-text.js
addon-sdk/source/lib/sdk/test/loader.js
addon-sdk/source/test/addons/e10s-content/lib/test-content-script.js
addon-sdk/source/test/test-content-script.js
devtools/server/tests/unit/test_objectgrips-17.js
dom/base/nsDeprecatedOperationList.h
dom/base/test/chrome/cpows_child.js
dom/locales/en-US/chrome/dom/dom.properties
js/xpconnect/src/XPCJSRuntime.cpp
js/xpconnect/src/xpcprivate.h
js/xpconnect/tests/chrome/test_bug1065185.html
js/xpconnect/tests/chrome/test_cows.xul
js/xpconnect/tests/chrome/test_exposeInDerived.xul
js/xpconnect/tests/unit/test_bug1082450.js
js/xpconnect/tests/unit/test_bug780370.js
js/xpconnect/tests/unit/test_bug813901.js
js/xpconnect/tests/unit/test_bug853709.js
js/xpconnect/tests/unit/test_bug854558.js
js/xpconnect/tests/unit/test_bug930091.js
js/xpconnect/wrappers/AccessCheck.cpp
js/xpconnect/wrappers/AccessCheck.h
js/xpconnect/wrappers/ChromeObjectWrapper.h
js/xpconnect/wrappers/WrapperFactory.cpp
js/xpconnect/wrappers/XrayWrapper.cpp
testing/mochitest/tests/SimpleTest/ChromePowers.js
testing/specialpowers/content/MockPermissionPrompt.jsm
toolkit/components/addoncompat/RemoteAddonsParent.jsm
--- a/addon-sdk/source/lib/sdk/console/plain-text.js
+++ b/addon-sdk/source/lib/sdk/console/plain-text.js
@@ -57,22 +57,12 @@ function PlainTextConsole(print, innerID
 
   // As we freeze the console object, we can't modify this property afterward
   Object.defineProperty(console, "maxLogLevel", {
     get: function() {
       return logLevel;
     }
   });
 
-  // We defined the `__exposedProps__` in our console chrome object.
-  //
-  // Meanwhile we're investigating with the platform team if `__exposedProps__`
-  // are needed, or are just a left-over.
-
-  console.__exposedProps__ = Object.keys(ConsoleAPI.prototype).reduce(function(exposed, prop) {
-    exposed[prop] = "r";
-    return exposed;
-  }, {});
-
   Object.freeze(console);
   return console;
 };
 exports.PlainTextConsole = PlainTextConsole;
--- a/addon-sdk/source/lib/sdk/test/loader.js
+++ b/addon-sdk/source/lib/sdk/test/loader.js
@@ -48,21 +48,16 @@ function HookedPlainTextConsole(hook, pr
   this.log = hook.bind(null, "log", innerID);
   this.info = hook.bind(null, "info", innerID);
   this.warn = hook.bind(null, "warn", innerID);
   this.error = hook.bind(null, "error", innerID);
   this.debug = hook.bind(null, "debug", innerID);
   this.exception = hook.bind(null, "exception", innerID);
   this.time = hook.bind(null, "time", innerID);
   this.timeEnd = hook.bind(null, "timeEnd", innerID);
-
-  this.__exposedProps__ = {
-    log: "rw", info: "rw", warn: "rw", error: "rw", debug: "rw",
-    exception: "rw", time: "rw", timeEnd: "rw"
-  };
 }
 
 // Creates a custom loader instance whose console module is hooked in order
 // to avoid printing messages to the console, and instead, expose them in the
 // returned `messages` array attribute
 exports.LoaderWithHookedConsole = function (module, callback) {
   let messages = [];
   function hook(type, innerID, msg) {
--- a/addon-sdk/source/test/addons/e10s-content/lib/test-content-script.js
+++ b/addon-sdk/source/test/addons/e10s-content/lib/test-content-script.js
@@ -439,17 +439,17 @@ exports["test Highlight toString Behavio
   function f() {};
   let funToString = Object.prototype.toString.call(f);
   assert.ok(/\[object Function.*\]/.test(funToString), "functions are functions 1");
 
   // This is how jquery call toString:
   let strToString = helper.rawWindow.Object.prototype.toString.call("");
   assert.ok(/\[object String.*\]/.test(strToString), "strings are strings");
 
-  let o = {__exposedProps__:{}};
+  let o = {};
   let objToString = helper.rawWindow.Object.prototype.toString.call(o);
   assert.ok(/\[object Object.*\]/.test(objToString), "objects are objects");
 
   // Make sure to pass a function from the same compartments
   // or toString will return [object Object] on FF8+
   let f2 = helper.rawWindow.eval("(function () {})");
   let funToString2 = helper.rawWindow.Object.prototype.toString.call(f2);
   assert.ok(/\[object Function.*\]/.test(funToString2), "functions are functions 2");
@@ -617,20 +617,16 @@ exports["test Prototype Inheritance"] = 
 exports["test Functions"] = createProxyTest("", function (helper) {
 
   helper.rawWindow.callFunction = function callFunction(f) {
     return f();
   };
   helper.rawWindow.isEqual = function isEqual(a, b) {
     return a == b;
   };
-  // bug 784116: workaround in order to allow proxy code to cache proxies on
-  // these functions:
-  helper.rawWindow.callFunction.__exposedProps__ = {__proxy: 'rw'};
-  helper.rawWindow.isEqual.__exposedProps__ = {__proxy: 'rw'};
 
   helper.createWorker(
     'new ' + function ContentScriptScope() {
       // Check basic usage of functions
       let closure2 = function () {return "ok";};
       assert(window.wrappedJSObject.callFunction(closure2) == "ok", "Function references work");
 
       // Ensure that functions are cached when being wrapped to native code
--- a/addon-sdk/source/test/test-content-script.js
+++ b/addon-sdk/source/test/test-content-script.js
@@ -439,17 +439,17 @@ exports["test Highlight toString Behavio
   function f() {};
   let funToString = Object.prototype.toString.call(f);
   assert.ok(/\[object Function.*\]/.test(funToString), "functions are functions 1");
 
   // This is how jquery call toString:
   let strToString = helper.rawWindow.Object.prototype.toString.call("");
   assert.ok(/\[object String.*\]/.test(strToString), "strings are strings");
 
-  let o = {__exposedProps__:{}};
+  let o = {};
   let objToString = helper.rawWindow.Object.prototype.toString.call(o);
   assert.ok(/\[object Object.*\]/.test(objToString), "objects are objects");
 
   // Make sure to pass a function from the same compartments
   // or toString will return [object Object] on FF8+
   let f2 = helper.rawWindow.eval("(function () {})");
   let funToString2 = helper.rawWindow.Object.prototype.toString.call(f2);
   assert.ok(/\[object Function.*\]/.test(funToString2), "functions are functions 2");
@@ -617,20 +617,16 @@ exports["test Prototype Inheritance"] = 
 exports["test Functions"] = createProxyTest("", function (helper) {
 
   helper.rawWindow.callFunction = function callFunction(f) {
     return f();
   };
   helper.rawWindow.isEqual = function isEqual(a, b) {
     return a == b;
   };
-  // bug 784116: workaround in order to allow proxy code to cache proxies on
-  // these functions:
-  helper.rawWindow.callFunction.__exposedProps__ = {__proxy: 'rw'};
-  helper.rawWindow.isEqual.__exposedProps__ = {__proxy: 'rw'};
 
   helper.createWorker(
     'new ' + function ContentScriptScope() {
       // Check basic usage of functions
       let closure2 = function () {return "ok";};
       assert(window.wrappedJSObject.callFunction(closure2) == "ok", "Function references work");
 
       // Ensure that functions are cached when being wrapped to native code
--- a/devtools/server/tests/unit/test_objectgrips-17.js
+++ b/devtools/server/tests/unit/test_objectgrips-17.js
@@ -101,20 +101,16 @@ function testPrincipal(principal, wantXr
     // kinds of security wrappers, or no wrapper at all.
     // To detect that no proxy trap runs, the proxy handler should define all possible
     // traps, but the list is long and may change. Therefore a second proxy is used as
     // the handler, so that a single `get` trap suffices.
     gGlobal.eval(`
       var trapDidRun = false;
       var proxy = new Proxy({}, new Proxy({}, {get: (_, trap) => {
         return function(_, arg) {
-          if (trap === "has" && arg === "__exposedProps__") {
-            // Tolerate this case until bug 1392026 is fixed.
-            return false;
-          }
           trapDidRun = true;
           throw new Error("proxy trap '" + trap + "' was called.");
         }
       }}));
       var inheritsProxy = Object.create(proxy, {x:{value:1}});
     `);
     let data = Cu.createObjectIn(gDebuggee, {defineAs: "data"});
     data.proxy = gGlobal.proxy;
--- a/dom/base/nsDeprecatedOperationList.h
+++ b/dom/base/nsDeprecatedOperationList.h
@@ -16,17 +16,16 @@ DEPRECATED_OPERATION(GetAttributeNodeNS)
 DEPRECATED_OPERATION(SetAttributeNodeNS)
 DEPRECATED_OPERATION(RemoveAttributeNode)
 DEPRECATED_OPERATION(CreateAttribute)
 DEPRECATED_OPERATION(CreateAttributeNS)
 DEPRECATED_OPERATION(NodeValue)
 DEPRECATED_OPERATION(TextContent)
 DEPRECATED_OPERATION(EnablePrivilege)
 DEPRECATED_OPERATION(DOMExceptionCode)
-DEPRECATED_OPERATION(NoExposedProps)
 DEPRECATED_OPERATION(MutationEvent)
 DEPRECATED_OPERATION(Components)
 DEPRECATED_OPERATION(PrefixedVisibilityAPI)
 DEPRECATED_OPERATION(NodeIteratorDetach)
 DEPRECATED_OPERATION(LenientThis)
 DEPRECATED_OPERATION(GetPreventDefault)
 DEPRECATED_OPERATION(GetSetUserData)
 DEPRECATED_OPERATION(MozGetAsFile)
--- a/dom/base/test/chrome/cpows_child.js
+++ b/dom/base/test/chrome/cpows_child.js
@@ -100,17 +100,23 @@ function parent_test(finish)
 
     let result = check_func(10);
     ok(result == 20, "calling function in parent worked");
     return result;
   }
 
   addMessageListener("cpows:from_parent", (msg) => {
     let obj = msg.objects.obj;
-    ok(obj.a == 1, "correct value from parent");
+    if (is_remote) {
+      ok(obj.a == undefined, "__exposedProps__ should not work");
+    } else {
+      // The same process test is not run as content, so the field can
+      // be accessed even though __exposedProps__ has been removed.
+      ok(obj.a == 1, "correct value from parent");
+    }
 
     // Test that a CPOW reference to a function in the chrome process
     // is callable from unprivileged content. Greasemonkey uses this
     // functionality.
     let func = msg.objects.func;
     let sb = Cu.Sandbox('http://www.example.com', {});
     sb.func = func;
     ok(sb.eval('func()') == 101, "can call parent's function in child");
@@ -255,21 +261,21 @@ function lifetime_test(finish)
     finish();
     return;
   }
 
   dump("beginning lifetime test\n");
   var obj = {"will_die": {"f": 1}};
   let [result] = sendRpcMessage("cpows:lifetime_test_1", {}, {obj: obj});
   ok(result == 10, "got sync result");
-  ok(obj.wont_die.f == 2, "got reverse CPOW");
+  ok(obj.wont_die.f == undefined, "got reverse CPOW");
   obj.will_die = null;
   Components.utils.schedulePreciseGC(function() {
     addMessageListener("cpows:lifetime_test_3", (msg) => {
-      ok(obj.wont_die.f == 2, "reverse CPOW still works");
+      ok(obj.wont_die.f == undefined, "reverse CPOW still works");
       finish();
     });
     sendRpcMessage("cpows:lifetime_test_2");
   });
 }
 
 function cancel_test(finish)
 {
--- a/dom/locales/en-US/chrome/dom/dom.properties
+++ b/dom/locales/en-US/chrome/dom/dom.properties
@@ -149,18 +149,16 @@ MediaStreamStopDeprecatedWarning=MediaSt
 # LOCALIZATION NOTE: %S is the URL of the web page which is not served on HTTPS and thus is not encrypted and considered insecure.
 MediaEMEInsecureContextDeprecatedWarning=Using Encrypted Media Extensions at %S on an insecure (i.e. non-HTTPS) context is deprecated and will soon be removed. You should consider switching to a secure origin such as HTTPS.
 # LOCALIZATION NOTE: %S is the URL of the web page which is calling web APIs without passing data (either an audioCapabilities or a videoCapabilities) that will soon be required. See https://bugzilla.mozilla.org/show_bug.cgi?id=1368583#c21 for explanation of this string.
 MediaEMENoCapabilitiesDeprecatedWarning=Calling navigator.requestMediaKeySystemAccess() (at %S) without passing a candidate MediaKeySystemConfiguration containing audioCapabilities or videoCapabilities is deprecated and will soon become unsupported.
 # LOCALIZATION NOTE: %S is the URL of the web page which is calling web APIs without passing data (a "codecs" string in the "contentType") that will soon be required. See https://bugzilla.mozilla.org/show_bug.cgi?id=1368583#c21 for explanation of this string.
 MediaEMENoCodecsDeprecatedWarning=Calling navigator.requestMediaKeySystemAccess() (at %S) passing a candidate MediaKeySystemConfiguration containing audioCapabilities or videoCapabilities without a contentType with a “codecs” string is deprecated and will soon become unsupported.
 # LOCALIZATION NOTE: Do not translate "DOMException", "code" and "name"
 DOMExceptionCodeWarning=Use of DOMException’s code attribute is deprecated. Use name instead.
-# LOCALIZATION NOTE: Do not translate "__exposedProps__"
-NoExposedPropsWarning=Exposing chrome JS objects to content without __exposedProps__ is insecure and deprecated. See https://developer.mozilla.org/en/XPConnect_wrappers for more information.
 # LOCALIZATION NOTE: Do not translate "Mutation Event" and "MutationObserver"
 MutationEventWarning=Use of Mutation Events is deprecated. Use MutationObserver instead.
 # LOCALIZATION NOTE: Do not translate "Components"
 ComponentsWarning=The Components object is deprecated. It will soon be removed.
 PluginHangUITitle=Warning: Unresponsive plugin
 PluginHangUIMessage=%S may be busy, or it may have stopped responding. You can stop the plugin now, or you can continue to see if the plugin will complete.
 PluginHangUIWaitButton=Continue
 PluginHangUIStopButton=Stop plugin
--- a/js/xpconnect/src/XPCJSRuntime.cpp
+++ b/js/xpconnect/src/XPCJSRuntime.cpp
@@ -87,17 +87,16 @@ const char* const XPCJSRuntime::mStrings
     "Components",           // IDX_COMPONENTS
     "wrappedJSObject",      // IDX_WRAPPED_JSOBJECT
     "Object",               // IDX_OBJECT
     "Function",             // IDX_FUNCTION
     "prototype",            // IDX_PROTOTYPE
     "createInstance",       // IDX_CREATE_INSTANCE
     "item",                 // IDX_ITEM
     "__proto__",            // IDX_PROTO
-    "__exposedProps__",     // IDX_EXPOSEDPROPS
     "eval",                 // IDX_EVAL
     "controllers",          // IDX_CONTROLLERS
     "Controllers",          // IDX_CONTROLLERS_CLASS
     "realFrameElement",     // IDX_REALFRAMEELEMENT
     "length",               // IDX_LENGTH
     "name",                 // IDX_NAME
     "undefined",            // IDX_UNDEFINED
     "",                     // IDX_EMPTYSTRING
--- a/js/xpconnect/src/xpcprivate.h
+++ b/js/xpconnect/src/xpcprivate.h
@@ -453,17 +453,16 @@ public:
         IDX_COMPONENTS              ,
         IDX_WRAPPED_JSOBJECT        ,
         IDX_OBJECT                  ,
         IDX_FUNCTION                ,
         IDX_PROTOTYPE               ,
         IDX_CREATE_INSTANCE         ,
         IDX_ITEM                    ,
         IDX_PROTO                   ,
-        IDX_EXPOSEDPROPS            ,
         IDX_EVAL                    ,
         IDX_CONTROLLERS             ,
         IDX_CONTROLLERS_CLASS       ,
         IDX_REALFRAMEELEMENT        ,
         IDX_LENGTH                  ,
         IDX_NAME                    ,
         IDX_UNDEFINED               ,
         IDX_EMPTYSTRING             ,
--- a/js/xpconnect/tests/chrome/test_bug1065185.html
+++ b/js/xpconnect/tests/chrome/test_bug1065185.html
@@ -20,21 +20,21 @@ https://bugzilla.mozilla.org/show_bug.cg
     SimpleTest.monitorConsole(() => SimpleTest.executeSoon(() => window[0].location.reload()), messages, /* forbidUnexpected = */ true);
   }
   function endMonitor() {
     SimpleTest.executeSoon(SimpleTest.endMonitorConsole.bind(SimpleTest));
   }
 
   var gLoadCount = 0;
   function loaded() {
-    switch(++gLoadCount) {
+    switch(gLoadCount++) {
       case 0:
-        doMonitor([]);
+        doMonitor([/access to property "a"/i]);
         window[0].wrappedJSObject.probe = { a: 2, __exposedProps__: { 'a': 'r' } };
-        is(window[0].eval('probe.a'), 2, "Accessed exposed prop");
+        is(window[0].eval('probe.a'), undefined, "Accessed exposed prop");
         endMonitor();
         break;
       case 1:
         doMonitor([/access to property "a"/i]);
         window[0].wrappedJSObject.probe = { a: 2 };
         is(window[0].eval('probe.a'), undefined, "Non-exposed prop undefined");
         is(window[0].eval('probe.a'), undefined, "Non-exposed prop undefined again");
         endMonitor();
--- a/js/xpconnect/tests/chrome/test_cows.xul
+++ b/js/xpconnect/tests/chrome/test_cows.xul
@@ -44,23 +44,16 @@ function getCOW(x) {
 
 // Give the sandbox a way to create ChromeObjectWrapped objects.
 sandbox.getCOW = getCOW;
 
 // Define test API functions in the sandbox.
 const TEST_API = ['is', 'isnot', 'ok', 'todo_is', 'todo_isnot', 'todo'];
 TEST_API.forEach(function(name) { sandbox[name] = window[name]; });
 
-sandbox.alienObject = {
-  __exposedProps__: {funProp: 'r'},
-  funProp: function foo(x) {
-    return x + 1;
-  }
-};
-
 sandbox.chromeGet = function (obj, prop) { return obj[prop]; };
 
 function COWTests() {
     function getNames(cow) {
         let names = [];
         for (let name in cow) {
             names.push(name);
         }
@@ -69,27 +62,16 @@ function COWTests() {
 
     // This function is actually decompiled and run inside a
     // sandbox with content privileges.
 
     // TODO: This could use some refactoring; creating helper
     // functions like assertIsWritable(myObj, 'someproperty') might
     // be useful.
 
-    function isProp(obj, propName, value, desc) {
-      try {
-          is(obj[propName], value, "getting " + propName + " on " + desc);
-          ok(propName in obj,
-             propName + " on " + desc + " should exist");
-          ok(Object.hasOwnProperty.call(obj, propName),
-             propName + " on " + desc + " should exist");
-      } catch (e) {
-          ok(false, "getting " + propName + " on " + desc + " threw " + e);
-      }
-    }
     function isPropHidden(obj, propName, desc) {
       try {
           is(obj[propName], undefined,
              "getting " + propName + " on " + desc + " should return undefined");
           ok(!(propName in obj),
              propName + " on " + desc + " should act as if it doesn't exist");
           ok(!Object.hasOwnProperty.call(obj, propName),
              propName + " on " + desc + " should act as if it doesn't exist");
@@ -98,17 +80,17 @@ function COWTests() {
       }
     }
 
     const PROPS_TO_TEST = ['foo', 'bar', 'prototype'];
 
     var empty = {};
     var nonempty = {foo: 42, bar: 33};
     is(getCOW(empty).foo, undefined,
-       "shouldn't throw when accessing exposed properties that doesn't exist");
+       "shouldn't throw when accessing exposed properties that don't exist");
 
     PROPS_TO_TEST.forEach(function(name) {
         isPropHidden(getCOW(nonempty), name, "object without exposedProps");
     });
 
     // Test function objects.
     var func = function(x) { return 42; };
     func.__exposedProps__ = { foo: "r" };
@@ -130,63 +112,50 @@ function COWTests() {
     });
     is(getNames(strictCOW).length, 0,
        "object with empty exposedProps shouldn't have any properties");
 
     // Test object with one exposed property
     var strict = { __exposedProps__: { foo: "r" }, foo: "foo property" };
     var strictCOWr = getCOW(strict);
     PROPS_TO_TEST.forEach(function(name) {
-        if (name == "foo") {
-            isProp(strictCOWr, name, "foo property",
-                   "object with exposed 'foo'");
-        }
-        else {
-            isPropHidden(strictCOW, name, "object with exposed 'foo'");
-        }
+        isPropHidden(strictCOW, name, "object with exposed 'foo'");
     });
-    is(getNames(strictCOWr).length, 1,
-       "object with exposedProps only enumerate exposed props");
-    is(getNames(strictCOWr)[0], "foo",
-       "object with exposedProps only enumerate exposed props");
+    is(getNames(strictCOWr).length, 0,
+       "exposed props does not enumerate anything");
+    is(getNames(strictCOWr)[0], undefined,
+       "exposed props does not enumerate anything");
 
     // Test writable property
     var writable = getCOW({ __exposedProps__: {foo: 'w'}});
     try {
         ok(!("foo" in writable),
            "non-existing write-only property shouldn't exist");
         writable.foo = 5;
-        is(chromeGet(writable, "foo"), 5, "writing to a write-only exposed prop works");
-        todo("foo" in writable,
-             "existing write-only property should exist");
+        ok(false, "writing to a write-only exposed prop should throw");
     } catch (e) {
-        ok(false, "writing to a write-only exposed prop shouldn't throw " + e);
+        ok(/Permission denied/.test(e),
+           "writing to a write-only exposed prop should throw the right error");
     }
-    try {
-        writable.foo;
-        todo(false, "reading from a write-only exposed prop should throw");
-    } catch (e) {
-        todo(/Permission denied/.test(e),
-             "reading from a write-only exposed prop should throw");
-    }
+    is(writable.foo, undefined,
+       "reading from a write-only exposed prop should return undefined");
     try {
         delete writable.foo;
-        is(chromeGet(writable, "foo"), undefined,
-           "deleting a write-only exposed prop works");
+        ok(false, "deleting a write-only exposed prop should throw");
     } catch (e) {
-        ok(false, "deleting a write-only exposed prop shouldn't throw " + e);
+        ok(true, "deleting a write-only exposed prop should throw " + e);
     }
 
     // Test readable property
     var readable = { __exposedProps__: {foo: 'r'},
                      foo: 5,
                      bar: 6 };
     try {
-        isProp(getCOW(readable), "foo", 5,
-               "reading from a readable exposed prop works");
+        isPropHidden(getCOW(readable), "foo", undefined,
+                     "reading from a readable exposed prop shouldn't work");
     } catch (e) {
         ok(false, "reading from a readable exposed prop shouldn't throw " + e);
     }
     try {
         getCOW(readable).foo = 1;
         ok(false, "writing to a read-only exposed prop should fail");
     } catch (e) {
         ok(/Permission denied/.test(e),
@@ -197,44 +166,39 @@ function COWTests() {
         ok(false, "deleting a read-only exposed prop shouldn't work");
     } catch (e) {
         ok(/Permission denied/.test(e),
            "deleting a read-only exposed prop should throw error");
     }
 
     try {
         var props = getNames(getCOW(readable));
-        is(props.length, 1, "COW w/ one exposed prop should enumerate once");
-        is(props[0], 'foo', "COW w/ one exposed prop should enumerate it");
+        is(props.length, 0, "COW w/ one exposed prop should not enumerate");
     } catch (e) {
         ok(false, "COW w/ a readable prop should not raise exc " +
                   "on enumeration: " + e);
     }
 
     // Test read/write property
     var readwrite = getCOW({ __exposedProps__: {foo: 'rw'}});
     try {
         ok(!("foo" in readwrite),
            "non-existing readwrite property shouldn't exist");
         readwrite.foo = 5;
-        is(readwrite.foo, 5, "writing to a readwrite exposed prop looks like it worked");
-        is(chromeGet(readwrite, "foo"), 5, "writing to a readwrite exposed prop works");
-        ok("foo" in readwrite,
-           "existing readwrite property should exist");
+        ok(false, "writing to a readwrite exposed prop should throw");
     } catch (e) {
-        ok(false, "writing to a readwrite exposed prop shouldn't throw " + e);
+        ok(/Permission denied/.test(e),
+           "writing to a readwrite exposed prop should throw the right error");
     }
     try {
         delete readwrite.foo;
-        is(readwrite.foo, undefined, "deleting readwrite prop looks like it worked");
-        ok(!("foo" in readwrite), "deleting readwrite prop looks like it really worked");
-        is(chromeGet(readwrite, "foo"), undefined,
-           "deleting a readwrite exposed prop works");
+        ok(false, "deleting a readwrite prop should throw");
     } catch (e) {
-        ok(false, "deleting a readwrite exposed prop shouldn't throw " + e);
+        ok(/Permission denied/.test(e),
+           "deleting a readwrite exposed prop should throw the right error");
     }
 
     // Readables and functions
     try {
         var COWFunc = getCOW((function() { return 5; }));
         is(COWFunc(), 5, "COWed functions should be callable");
     } catch (e) {
         todo(false, "COWed functions should not raise " + e);
--- a/js/xpconnect/tests/chrome/test_exposeInDerived.xul
+++ b/js/xpconnect/tests/chrome/test_exposeInDerived.xul
@@ -12,35 +12,36 @@ https://bugzilla.mozilla.org/show_bug.cg
   <body xmlns="http://www.w3.org/1999/xhtml">
   <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=804630"
      target="_blank">Mozilla Bug 804630</a>
   </body>
 
   <!-- test code goes here -->
   <script type="application/javascript">
   <![CDATA[
-  /** Test to make sure that COWed objects can expose properties from their prototypes. **/
+  /** Test to make sure that COWed objects can't expose properties from their prototypes. **/
   const Cu = Components.utils;
 
   // Set up the sandbox.
   var sb = new Cu.Sandbox('http://www.example.com');
   sb.ok = ok;
   sb.is = is;
 
-  // Make a chrome object that exposes objects off its prototype.
+  // Make a chrome object that tries to expose objects off its prototype.
   sb.proto = { read: 42, readWrite: 32, __exposedProps__: {} };
   sb.obj = { __exposedProps__: { read: 'r', readWrite: 'rw' } };
   sb.obj.__proto__ = sb.proto;
 
   // Make sure we can't access any of the properties on the prototype directly.
   Cu.evalInSandbox('is(proto.read, undefined, "proto.read inaccessible");', sb);
   Cu.evalInSandbox('var wrote = false; ' +
                    'try { proto.readWrite = 12; wrote = true; } catch(e) {} ' +
                    ' ok(!wrote, "Should not write proto property");', sb);
 
-  // Make sure we can access the exposed properties via the derived object.
-  Cu.evalInSandbox('is(obj.read, 42, "obj.read accessible");', sb);
-  Cu.evalInSandbox('is(obj.readWrite, 32, "obj.readWrite is readable");', sb);
-  Cu.evalInSandbox('obj.readWrite = 8; is(obj.readWrite, 8, "obj.readWrite is writable");', sb);
+  // Make sure we can't access the exposed properties via the derived object.
+  Cu.evalInSandbox('is(obj.read, undefined, "obj.read inaccessible");', sb);
+  Cu.evalInSandbox('is(obj.readWrite, undefined, "obj.readWrite is not readable");', sb);
+  Cu.evalInSandbox('try { obj.readWrite = 8; ok(false, "obj.readWrite is not writable"); } catch (e) {};',
+                   sb);
 
   ]]>
   </script>
 </window>
--- a/js/xpconnect/tests/unit/test_bug1082450.js
+++ b/js/xpconnect/tests/unit/test_bug1082450.js
@@ -1,18 +1,18 @@
 const Cu = Components.utils;
 function run_test() {
 
   var sb = new Cu.Sandbox('http://www.example.com');
   function checkThrows(str, rgxp) {
     try {
       sb.eval(str);
-      do_check_true(false);
+      do_check_true(false, "eval should have thrown");
     } catch (e) {
-      do_check_true(rgxp.test(e));
+      do_check_true(rgxp.test(e), "error message should match");
     }
   }
 
   sb.exposed = {
     get getterProp() { return 42; },
     set setterProp(x) { },
     get getterSetterProp() { return 42; },
     set getterSetterProp(x) { },
@@ -24,17 +24,17 @@ function run_test() {
                         setterProp : 'w',
                         getterSetterProp: 'rw',
                         simpleValueProp: 'r',
                         objectValueProp: 'r',
                         contentCallableValueProp: 'r',
                         chromeCallableValueProp: 'r' }
   };
 
-  do_check_eq(sb.eval('exposed.simpleValueProp'), 42);
-  do_check_eq(sb.eval('exposed.objectValueProp.val'), 42);
-  checkThrows('exposed.getterProp;', /privileged accessor/i);
-  checkThrows('exposed.setterProp = 42;', /privileged accessor/i);
-  checkThrows('exposed.getterSetterProp;', /privileged accessor/i);
-  checkThrows('exposed.getterSetterProp = 42;', /privileged accessor/i);
-  do_check_eq(sb.eval('exposed.contentCallableValueProp()'), 42);
-  checkThrows('exposed.chromeCallableValueProp();', /privileged or cross-origin callable/i);
+  do_check_eq(sb.eval('exposed.simpleValueProp'), undefined);
+  do_check_eq(sb.eval('exposed.objectValueProp'), undefined);
+  do_check_eq(sb.eval('exposed.getterProp;'), undefined);
+  do_check_eq(sb.eval('exposed.getterSetterProp;'), undefined);
+  checkThrows('exposed.setterProp = 42;', /Permission denied/i);
+  checkThrows('exposed.getterSetterProp = 42;', /Permission denied/i);
+  do_check_eq(sb.eval('exposed.contentCallableValueProp'), undefined);
+  checkThrows('exposed.chromeCallableValueProp();', /is not a function/i);
 }
--- a/js/xpconnect/tests/unit/test_bug780370.js
+++ b/js/xpconnect/tests/unit/test_bug780370.js
@@ -9,15 +9,10 @@ const Cu = Components.utils;
 // Use a COW to expose a function from a standard prototype, and make we deny
 // access to it.
 
 function run_test()
 {
   var sb = Cu.Sandbox("http://www.example.com");
   sb.obj = { foo: 42, __exposedProps__: { hasOwnProperty: 'r' } };
   do_check_eq(Cu.evalInSandbox('typeof obj.foo', sb), 'undefined', "COW works as expected");
-  try {
-    Cu.evalInSandbox('obj.hasOwnProperty', sb);
-    do_check_true(false);
-  } catch (e) {
-    do_check_true(/privileged or cross-origin callable/i.test(e));
-  }
+  do_check_eq(Cu.evalInSandbox('obj.hasOwnProperty', sb), undefined);
 }
--- a/js/xpconnect/tests/unit/test_bug813901.js
+++ b/js/xpconnect/tests/unit/test_bug813901.js
@@ -16,10 +16,10 @@ function checkThrows(expression, sb, reg
 
 function run_test() {
 
   var sb = new Cu.Sandbox('http://www.example.org');
   sb.obj = {foo: 2};
   checkThrows('obj.foo = 3;', sb, /denied/);
   Cu.evalInSandbox("var p = {__exposedProps__: {foo: 'rw'}};", sb);
   sb.obj.__proto__ = sb.p;
-  checkThrows('obj.foo = 4;', sb, /__exposedProps__/);
+  checkThrows('obj.foo = 4;', sb, /denied/);
 }
--- a/js/xpconnect/tests/unit/test_bug853709.js
+++ b/js/xpconnect/tests/unit/test_bug853709.js
@@ -3,28 +3,28 @@ const Cu = Components.utils;
 function setupChromeSandbox() {
   this.chromeObj = {a: 2, __exposedProps__: {a: "rw", b: "rw"} };
   this.chromeArr = [4, 2, 1];
 }
 
 function checkDefineThrows(sb, obj, prop, desc) {
   var result = Cu.evalInSandbox('(function() { try { Object.defineProperty(' + obj + ', "' + prop + '", ' + desc.toSource() + '); return "nothrow"; } catch (e) { return e.toString(); }})();', sb);
   do_check_neq(result, 'nothrow');
-  do_check_true(!!/denied/.exec(result));
+  do_check_true(!!/denied|prohibited/.exec(result));
   do_check_true(result.indexOf(prop) != -1); // Make sure the prop name is in the error message.
 }
 
 function run_test() {
   var chromeSB = new Cu.Sandbox(this);
   var contentSB = new Cu.Sandbox('http://www.example.org');
   Cu.evalInSandbox('(' + setupChromeSandbox.toSource() + ')()', chromeSB);
   contentSB.chromeObj = chromeSB.chromeObj;
   contentSB.chromeArr = chromeSB.chromeArr;
 
-  do_check_eq(Cu.evalInSandbox('chromeObj.a', contentSB), 2);
+  do_check_eq(Cu.evalInSandbox('chromeObj.a', contentSB), undefined);
   try {
     Cu.evalInSandbox('chromeArr[1]', contentSB);
     do_check_true(false);
   } catch (e) { do_check_true(/denied|insecure/.test(e)); }
 
   checkDefineThrows(contentSB, 'chromeObj', 'a', {get: function() { return 2; }});
   checkDefineThrows(contentSB, 'chromeObj', 'a', {configurable: true, get: function() { return 2; }});
   checkDefineThrows(contentSB, 'chromeObj', 'b', {configurable: true, get: function() { return 2; }, set: function() {}});
--- a/js/xpconnect/tests/unit/test_bug854558.js
+++ b/js/xpconnect/tests/unit/test_bug854558.js
@@ -2,10 +2,10 @@ const Cu = Components.utils;
 function run_test() {
 
   var chromeSB = new Cu.Sandbox(this);
   var contentSB = new Cu.Sandbox('http://www.example.com');
   Cu.evalInSandbox('this.foo = {a: 2}', chromeSB);
   contentSB.foo = chromeSB.foo;
   do_check_eq(Cu.evalInSandbox('foo.a', contentSB), undefined, "Default deny with no __exposedProps__");
   Cu.evalInSandbox('this.foo.__exposedProps__ = {a: "r"}', chromeSB);
-  do_check_eq(Cu.evalInSandbox('foo.a', contentSB), 2, "works with __exposedProps__");
+  do_check_eq(Cu.evalInSandbox('foo.a', contentSB), undefined, "Still not allowed with __exposedProps__");
 }
--- a/js/xpconnect/tests/unit/test_bug930091.js
+++ b/js/xpconnect/tests/unit/test_bug930091.js
@@ -1,16 +1,16 @@
 const Cu = Components.utils;
 
 function checkThrows(fn) {
   try {
     fn();
     ok(false, "Should have thrown");
   } catch (e) {
-    do_check_true(/denied|insecure/.test(e));
+    do_check_true(/denied|insecure|prohibited/.test(e));
   }
 }
 
 function run_test() {
   var xosb = new Cu.Sandbox('http://www.example.org');
   var sb = new Cu.Sandbox('http://www.example.com');
   sb.do_check_true = do_check_true;
   sb.fun = function() { ok(false, "Shouldn't ever reach me"); };
--- a/js/xpconnect/wrappers/AccessCheck.cpp
+++ b/js/xpconnect/wrappers/AccessCheck.cpp
@@ -247,30 +247,16 @@ AccessCheck::checkPassToPrivilegedCode(J
     // pass any objects at all to CPOWs.
     if (mozilla::jsipc::IsWrappedCPOW(obj) &&
         js::GetObjectCompartment(wrapper) == js::GetObjectCompartment(xpc::UnprivilegedJunkScope()) &&
         XRE_IsParentProcess())
     {
         return true;
     }
 
-    // COWs are fine to pass to chrome if and only if they have __exposedProps__,
-    // since presumably content should never have a reason to pass an opaque
-    // object back to chrome.
-    if (AccessCheck::isChrome(js::UncheckedUnwrap(wrapper)) && WrapperFactory::IsCOW(obj)) {
-        RootedObject target(cx, js::UncheckedUnwrap(obj));
-        JSAutoCompartment ac(cx, target);
-        RootedId id(cx, GetJSIDByIndex(cx, XPCJSContext::IDX_EXPOSEDPROPS));
-        bool found = false;
-        if (!JS_HasPropertyById(cx, target, id, &found))
-            return false;
-        if (found)
-            return true;
-    }
-
     // Same-origin wrappers are fine.
     if (AccessCheck::wrapperSubsumes(obj))
         return true;
 
     // Badness.
     JS_ReportErrorASCII(cx, "Permission denied to pass object to privileged code");
     return false;
 }
@@ -318,181 +304,16 @@ AccessCheck::reportCrossOriginDenial(JSC
                   NS_ConvertUTF16toUTF8(propName) +
                   NS_LITERAL_CSTRING(" on cross-origin object");
     }
     ErrorResult rv;
     rv.ThrowDOMException(NS_ERROR_DOM_SECURITY_ERR, message);
     MOZ_ALWAYS_TRUE(rv.MaybeSetPendingException(cx));
 }
 
-enum Access { READ = (1<<0), WRITE = (1<<1), NO_ACCESS = 0 };
-
-static void
-EnterAndThrowASCII(JSContext* cx, JSObject* wrapper, const char* msg)
-{
-    JSAutoCompartment ac(cx, wrapper);
-    JS_ReportErrorASCII(cx, "%s", msg);
-}
-
-bool
-ExposedPropertiesOnly::check(JSContext* cx, HandleObject wrapper, HandleId id, Wrapper::Action act)
-{
-    RootedObject wrappedObject(cx, Wrapper::wrappedObject(wrapper));
-
-    if (act == Wrapper::CALL)
-        return false;
-
-    // For the case of getting a property descriptor, we allow if either GET or SET
-    // is allowed, and rely on FilteringWrapper to filter out any disallowed accessors.
-    if (act == Wrapper::GET_PROPERTY_DESCRIPTOR) {
-        return check(cx, wrapper, id, Wrapper::GET) ||
-               check(cx, wrapper, id, Wrapper::SET);
-    }
-
-    RootedId exposedPropsId(cx, GetJSIDByIndex(cx, XPCJSContext::IDX_EXPOSEDPROPS));
-
-    // We need to enter the wrappee's compartment to look at __exposedProps__,
-    // but we want to be in the wrapper's compartment if we call Deny().
-    //
-    // Unfortunately, |cx| can be in either compartment when we call ::check. :-(
-    JSAutoCompartment ac(cx, wrappedObject);
-
-    bool found = false;
-    if (!JS_HasPropertyById(cx, wrappedObject, exposedPropsId, &found))
-        return false;
-
-    // If no __exposedProps__ existed, deny access.
-    if (!found) {
-        // Previously we automatically granted access to indexed properties and
-        // .length for Array COWs. We're not doing that anymore, so make sure to
-        // let people know what's going on.
-        bool isArray;
-        if (!JS_IsArrayObject(cx, wrappedObject, &isArray))
-            return false;
-        if (!isArray)
-            isArray = JS_IsTypedArrayObject(wrappedObject);
-        bool isIndexedAccessOnArray = isArray && JSID_IS_INT(id) && JSID_TO_INT(id) >= 0;
-        bool isLengthAccessOnArray = isArray && JSID_IS_STRING(id) &&
-                                     JS_FlatStringEqualsAscii(JSID_TO_FLAT_STRING(id), "length");
-        if (isIndexedAccessOnArray || isLengthAccessOnArray) {
-            JSAutoCompartment ac2(cx, wrapper);
-            ReportWrapperDenial(cx, id, WrapperDenialForCOW,
-                                "Access to elements and length of privileged Array not permitted");
-        }
-
-        return false;
-    }
-
-    if (id == JSID_VOID)
-        return true;
-
-    Rooted<PropertyDescriptor> desc(cx);
-    if (!JS_GetPropertyDescriptorById(cx, wrappedObject, exposedPropsId, &desc))
-        return false;
-
-    if (!desc.object())
-        return false;
-
-    if (desc.hasGetterOrSetter()) {
-        EnterAndThrowASCII(cx, wrapper, "__exposedProps__ must be a value property");
-        return false;
-    }
-
-    RootedValue exposedProps(cx, desc.value());
-    if (exposedProps.isNullOrUndefined())
-        return false;
-
-    if (!exposedProps.isObject()) {
-        EnterAndThrowASCII(cx, wrapper, "__exposedProps__ must be undefined, null, or an Object");
-        return false;
-    }
-
-    RootedObject hallpass(cx, &exposedProps.toObject());
-
-    if (!AccessCheck::subsumes(js::UncheckedUnwrap(hallpass), wrappedObject)) {
-        EnterAndThrowASCII(cx, wrapper, "Invalid __exposedProps__");
-        return false;
-    }
-
-    Access access = NO_ACCESS;
-
-    if (!JS_GetPropertyDescriptorById(cx, hallpass, id, &desc)) {
-        return false; // Error
-    }
-    if (!desc.object() || !desc.enumerable())
-        return false;
-
-    if (!desc.value().isString()) {
-        EnterAndThrowASCII(cx, wrapper, "property must be a string");
-        return false;
-    }
-
-    JSFlatString* flat = JS_FlattenString(cx, desc.value().toString());
-    if (!flat)
-        return false;
-
-    size_t length = JS_GetStringLength(JS_FORGET_STRING_FLATNESS(flat));
-
-    for (size_t i = 0; i < length; ++i) {
-        char16_t ch = JS_GetFlatStringCharAt(flat, i);
-        switch (ch) {
-        case 'r':
-            if (access & READ) {
-                EnterAndThrowASCII(cx, wrapper, "duplicate 'readable' property flag");
-                return false;
-            }
-            access = Access(access | READ);
-            break;
-
-        case 'w':
-            if (access & WRITE) {
-                EnterAndThrowASCII(cx, wrapper, "duplicate 'writable' property flag");
-                return false;
-            }
-            access = Access(access | WRITE);
-            break;
-
-        default:
-            EnterAndThrowASCII(cx, wrapper, "properties can only be readable or read and writable");
-            return false;
-        }
-    }
-
-    if (access == NO_ACCESS) {
-        EnterAndThrowASCII(cx, wrapper, "specified properties must have a permission bit set");
-        return false;
-    }
-
-    if ((act == Wrapper::SET && !(access & WRITE)) ||
-        (act != Wrapper::SET && !(access & READ))) {
-        return false;
-    }
-
-    // Inspect the property on the underlying object to check for red flags.
-    if (!JS_GetPropertyDescriptorById(cx, wrappedObject, id, &desc))
-        return false;
-
-    // Reject accessor properties.
-    if (desc.hasGetterOrSetter()) {
-        EnterAndThrowASCII(cx, wrapper, "Exposing privileged accessor properties is prohibited");
-        return false;
-    }
-
-    // Reject privileged or cross-origin callables.
-    if (desc.value().isObject()) {
-        RootedObject maybeCallable(cx, js::UncheckedUnwrap(&desc.value().toObject()));
-        if (JS::IsCallable(maybeCallable) && !AccessCheck::subsumes(wrapper, maybeCallable)) {
-            EnterAndThrowASCII(cx, wrapper, "Exposing privileged or cross-origin callable is prohibited");
-            return false;
-        }
-    }
-
-    return true;
-}
-
 bool
 ExposedPropertiesOnly::deny(JSContext* cx, js::Wrapper::Action act, HandleId id,
                             bool mayThrow)
 {
     // Fail silently for GET, ENUMERATE, and GET_PROPERTY_DESCRIPTOR.
     if (act == js::Wrapper::GET || act == js::Wrapper::ENUMERATE ||
         act == js::Wrapper::GET_PROPERTY_DESCRIPTOR)
     {
--- a/js/xpconnect/wrappers/AccessCheck.h
+++ b/js/xpconnect/wrappers/AccessCheck.h
@@ -99,20 +99,25 @@ struct CrossOriginAccessiblePropertiesOn
                                                  NS_LITERAL_CSTRING("access"));
         return false;
     }
     static bool allowNativeCall(JSContext* cx, JS::IsAcceptableThis test, JS::NativeImpl impl) {
         return false;
     }
 };
 
-// This policy only permits access to properties if they appear in the
-// objects exposed properties list.
+// This class used to support permitting access to properties if they
+// appeared in an access list on the object, but now it acts like an
+// Opaque wrapper, with the exception that it fails silently for GET,
+// ENUMERATE, and GET_PROPERTY_DESCRIPTOR. This is done for backwards
+// compatibility. See bug 1397513.
 struct ExposedPropertiesOnly : public Policy {
-    static bool check(JSContext* cx, JS::HandleObject wrapper, JS::HandleId id, js::Wrapper::Action act);
+    static bool check(JSContext* cx, JS::HandleObject wrapper, JS::HandleId id, js::Wrapper::Action act) {
+        return false;
+    }
 
     static bool deny(JSContext* cx, js::Wrapper::Action act, JS::HandleId id,
                      bool mayThrow);
     static bool allowNativeCall(JSContext* cx, JS::IsAcceptableThis test, JS::NativeImpl impl) {
         return false;
     }
 };
 
--- a/js/xpconnect/wrappers/ChromeObjectWrapper.h
+++ b/js/xpconnect/wrappers/ChromeObjectWrapper.h
@@ -11,19 +11,19 @@
 
 #include "FilteringWrapper.h"
 
 namespace xpc {
 
 struct ExposedPropertiesOnly;
 
 // When a vanilla chrome JS object is exposed to content, we use a wrapper that
-// supports __exposedProps__ for legacy reasons. For extra security, we override
-// the traps that allow content to pass an object to chrome, and perform extra
-// security checks on them.
+// fails silently on GET, ENUMERATE, and GET_PROPERTY_DESCRIPTOR for legacy
+// reasons. For extra security, we override the traps that allow content to pass
+// an object to chrome, and perform extra security checks on them.
 #define ChromeObjectWrapperBase \
   FilteringWrapper<js::CrossCompartmentSecurityWrapper, ExposedPropertiesOnly>
 
 class ChromeObjectWrapper : public ChromeObjectWrapperBase
 {
   public:
     constexpr ChromeObjectWrapper() : ChromeObjectWrapperBase(0) {}
 
--- a/js/xpconnect/wrappers/WrapperFactory.cpp
+++ b/js/xpconnect/wrappers/WrapperFactory.cpp
@@ -509,18 +509,18 @@ WrapperFactory::Rewrap(JSContext* cx, Ha
         // here, but only in the content process.
         if ((IdentifyStandardInstance(obj) == JSProto_Function ||
             (jsipc::IsCPOW(obj) && JS::IsCallable(obj) &&
              XRE_IsContentProcess())))
         {
             wrapper = &FilteringWrapper<CrossCompartmentSecurityWrapper, OpaqueWithCall>::singleton;
         }
 
-        // For Vanilla JSObjects exposed from chrome to content, we use a wrapper
-        // that supports __exposedProps__. We'd like to get rid of these eventually,
+        // For vanilla JSObjects exposed from chrome to content, we use a wrapper
+        // that fails silently in a few cases. We'd like to get rid of this eventually,
         // but in their current form they don't cause much trouble.
         else if (IdentifyStandardInstance(obj) == JSProto_Object) {
             wrapper = &ChromeObjectWrapper::singleton;
         }
 
         // Otherwise we get an opaque wrapper.
         else {
             wrapper = &FilteringWrapper<CrossCompartmentSecurityWrapper, Opaque>::singleton;
--- a/js/xpconnect/wrappers/XrayWrapper.cpp
+++ b/js/xpconnect/wrappers/XrayWrapper.cpp
@@ -286,17 +286,17 @@ ReportWrapperDenial(JSContext* cx, Handl
                              "for more information. Note that only the first denied "
                              "property access from a given global object will be reported.",
                              NS_LossyConvertUTF16toASCII(propertyName).get(),
                              reason);
     } else {
         MOZ_ASSERT(type == WrapperDenialForCOW);
         errorMessage.emplace("Security wrapper denied access to property %s on privileged "
                              "Javascript object. Support for exposing privileged objects "
-                             "to untrusted content via __exposedProps__ is being gradually "
+                             "to untrusted content via __exposedProps__ has been "
                              "removed - use WebIDL bindings or Components.utils.cloneInto "
                              "instead. Note that only the first denied property access from a "
                              "given global object will be reported.",
                              NS_LossyConvertUTF16toASCII(propertyName).get());
     }
     nsString filenameStr(NS_ConvertASCIItoUTF16(filename.get()));
     nsresult rv = errorObject->InitWithWindowID(NS_ConvertASCIItoUTF16(errorMessage.ref()),
                                                 filenameStr,
--- a/testing/mochitest/tests/SimpleTest/ChromePowers.js
+++ b/testing/mochitest/tests/SimpleTest/ChromePowers.js
@@ -99,25 +99,16 @@ ChromePowers.prototype.focus = function(
   if (aWindow)
     aWindow.focus();
 };
 
 ChromePowers.prototype.executeAfterFlushingMessageQueue = function(aCallback) {
   aCallback();
 };
 
-// Expose everything but internal APIs (starting with underscores) to
-// web content.  We cannot use Object.keys to view SpecialPowers.prototype since
-// we are using the functions from SpecialPowersAPI.prototype
-ChromePowers.prototype.__exposedProps__ = {};
-for (var i in ChromePowers.prototype) {
-  if (i.charAt(0) != "_")
-    ChromePowers.prototype.__exposedProps__[i] = "r";
-}
-
 if ((window.parent !== null) &&
     (window.parent !== undefined) &&
     (window.parent.wrappedJSObject.SpecialPowers) &&
     !(window.wrappedJSObject.SpecialPowers)) {
   window.wrappedJSObject.SpecialPowers = window.parent.SpecialPowers;
 } else {
   window.wrappedJSObject.SpecialPowers = new ChromePowers(window);
 }
--- a/testing/specialpowers/content/MockPermissionPrompt.jsm
+++ b/testing/specialpowers/content/MockPermissionPrompt.jsm
@@ -78,20 +78,8 @@ MockPermissionPromptInstance.prototype =
         request.cancel();
         return;
       }
     }
 
     request.allow();
   }
 };
-
-// Expose everything to content. We call reset() here so that all of the relevant
-// lazy expandos get added.
-MockPermissionPrompt.reset();
-function exposeAll(obj) {
-  var props = {};
-  for (var prop in obj)
-    props[prop] = "rw";
-  obj.__exposedProps__ = props;
-}
-exposeAll(MockPermissionPrompt);
-exposeAll(MockPermissionPromptInstance.prototype);
--- a/toolkit/components/addoncompat/RemoteAddonsParent.jsm
+++ b/toolkit/components/addoncompat/RemoteAddonsParent.jsm
@@ -730,17 +730,17 @@ var SandboxParent = {
   componentsMap: new WeakMap(),
 
   makeContentSandbox(addon, chromeGlobal, principals, ...rest) {
     CompatWarning.warn("This sandbox should be created from the child process.",
                        addon, CompatWarning.warnings.sandboxes);
     if (rest.length) {
       // Do a shallow copy of the options object into the child
       // process. This way we don't have to access it through a Chrome
-      // object wrapper, which would require __exposedProps__.
+      // object wrapper, which would not let us access any properties.
       //
       // The only object property here is sandboxPrototype. We assume
       // it's a child process object (since that's what Greasemonkey
       // does) and leave it alone.
       let options = rest[0];
       let optionsCopy = new chromeGlobal.Object();
       for (let prop in options) {
         optionsCopy[prop] = options[prop];