Bug 1768060 - Add a wrapper for Cu.import return value that supports lexical variable. r=jonco,Standard8

This wrapper exposes all lexical variables in Cu.import return value,
that allows us removing `this.foo = foo;` hack, in bug 1610653 patches,
without affecting the not-in-tree consumer.

Differential Revision: https://phabricator.services.mozilla.com/D145938
This commit is contained in:
Tooru Fujisawa
2022-05-13 22:02:40 +00:00
parent 4da20cce0e
commit 2191390bed
5 changed files with 319 additions and 48 deletions

View File

@@ -0,0 +1,260 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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 "JSMEnvironmentProxy.h"
#include "mozilla/Assertions.h" // MOZ_ASSERT
#include "mozilla/Maybe.h" // mozilla::Maybe
#include <stddef.h> // size_t
#include "jsapi.h" // JS_HasExtensibleLexicalEnvironment, JS_ExtensibleLexicalEnvironment
#include "js/Class.h" // JS::ObjectOpResult
#include "js/ErrorReport.h" // JS_ReportOutOfMemory
#include "js/GCVector.h" // JS::RootedVector
#include "js/Id.h" // JS::PropertyKey
#include "js/PropertyAndElement.h" // JS::IdVector, JS_HasPropertyById, JS_HasOwnPropertyById, JS_GetPropertyById, JS_Enumerate
#include "js/PropertyDescriptor.h" // JS::PropertyDescriptor, JS_GetOwnPropertyDescriptorById
#include "js/PropertyDescriptor.h" // JS::PropertyDescriptor, JS_GetOwnPropertyDescriptorById
#include "js/Proxy.h" // js::ProxyOptions, js::NewProxyObject, js::GetProxyPrivate
#include "js/RootingAPI.h" // JS::Rooted, JS::Handle, JS::MutableHandle
#include "js/TypeDecls.h" // JSContext, JSObject, JS::MutableHandleVector
#include "js/Value.h" // JS::Value, JS::UndefinedValue, JS_UNINITIALIZED_LEXICAL
#include "js/friend/ErrorMessages.h" // JSMSG_*
namespace mozilla {
namespace loader {
struct JSMEnvironmentProxyHandler : public js::BaseProxyHandler {
JSMEnvironmentProxyHandler() : BaseProxyHandler(&gFamily, false) {}
bool defineProperty(JSContext* aCx, JS::Handle<JSObject*> aProxy,
JS::Handle<JS::PropertyKey> aId,
JS::Handle<JS::PropertyDescriptor> aDesc,
JS::ObjectOpResult& aResult) const override {
return aResult.fail(JSMSG_CANT_DEFINE_PROP_OBJECT_NOT_EXTENSIBLE);
}
bool getPrototype(JSContext* aCx, JS::Handle<JSObject*> aProxy,
JS::MutableHandle<JSObject*> aProtop) const override {
aProtop.set(nullptr);
return true;
}
bool setPrototype(JSContext* aCx, JS::Handle<JSObject*> aProxy,
JS::Handle<JSObject*> aProto,
JS::ObjectOpResult& aResult) const override {
if (!aProto) {
return aResult.succeed();
}
return aResult.failCantSetProto();
}
bool getPrototypeIfOrdinary(
JSContext* aCx, JS::Handle<JSObject*> aProxy, bool* aIsOrdinary,
JS::MutableHandle<JSObject*> aProtop) const override {
*aIsOrdinary = false;
return true;
}
bool setImmutablePrototype(JSContext* aCx, JS::Handle<JSObject*> aProxy,
bool* aSucceeded) const override {
*aSucceeded = true;
return true;
}
bool preventExtensions(JSContext* aCx, JS::Handle<JSObject*> aProxy,
JS::ObjectOpResult& aResult) const override {
aResult.succeed();
return true;
}
bool isExtensible(JSContext* aCx, JS::Handle<JSObject*> aProxy,
bool* aExtensible) const override {
*aExtensible = false;
return true;
}
bool set(JSContext* aCx, JS::Handle<JSObject*> aProxy,
JS::Handle<JS::PropertyKey> aId, JS::Handle<JS::Value> aValue,
JS::Handle<JS::Value> aReceiver,
JS::ObjectOpResult& aResult) const override {
return aResult.failReadOnly();
}
bool delete_(JSContext* aCx, JS::Handle<JSObject*> aProxy,
JS::Handle<JS::PropertyKey> aId,
JS::ObjectOpResult& aResult) const override {
return aResult.failCantDelete();
}
bool getOwnPropertyDescriptor(
JSContext* aCx, JS::Handle<JSObject*> aProxy,
JS::Handle<JS::PropertyKey> aId,
JS::MutableHandle<mozilla::Maybe<JS::PropertyDescriptor>> aDesc)
const override;
bool has(JSContext* aCx, JS::Handle<JSObject*> aProxy,
JS::Handle<JS::PropertyKey> aId, bool* aBp) const override;
bool get(JSContext* aCx, JS::Handle<JSObject*> aProxy,
JS::Handle<JS::Value> aReceiver, JS::Handle<JS::PropertyKey> aId,
JS::MutableHandle<JS::Value> aVp) const override;
bool ownPropertyKeys(
JSContext* aCx, JS::Handle<JSObject*> aProxy,
JS::MutableHandleVector<JS::PropertyKey> aProps) const override;
private:
static JSObject* getGlobal(JSContext* aCx, JS::Handle<JSObject*> aProxy) {
JS::Rooted<JSObject*> globalObj(aCx,
&js::GetProxyPrivate(aProxy).toObject());
return globalObj;
}
public:
static const char gFamily;
static const JSMEnvironmentProxyHandler gHandler;
};
const JSMEnvironmentProxyHandler JSMEnvironmentProxyHandler::gHandler;
const char JSMEnvironmentProxyHandler::gFamily = 0;
JSObject* ResolveModuleObjectPropertyById(JSContext* aCx,
JS::Handle<JSObject*> aModObj,
JS::Handle<JS::PropertyKey> aId) {
if (JS_HasExtensibleLexicalEnvironment(aModObj)) {
JS::Rooted<JSObject*> lexical(aCx,
JS_ExtensibleLexicalEnvironment(aModObj));
bool found;
if (!JS_HasOwnPropertyById(aCx, lexical, aId, &found)) {
return nullptr;
}
if (found) {
return lexical;
}
}
return aModObj;
}
JSObject* ResolveModuleObjectProperty(JSContext* aCx,
JS::Handle<JSObject*> aModObj,
const char* aName) {
if (JS_HasExtensibleLexicalEnvironment(aModObj)) {
RootedObject lexical(aCx, JS_ExtensibleLexicalEnvironment(aModObj));
bool found;
if (!JS_HasOwnProperty(aCx, lexical, aName, &found)) {
return nullptr;
}
if (found) {
return lexical;
}
}
return aModObj;
}
bool JSMEnvironmentProxyHandler::getOwnPropertyDescriptor(
JSContext* aCx, JS::Handle<JSObject*> aProxy,
JS::Handle<JS::PropertyKey> aId,
JS::MutableHandle<mozilla::Maybe<JS::PropertyDescriptor>> aDesc) const {
JS::Rooted<JSObject*> globalObj(aCx, getGlobal(aCx, aProxy));
JS::Rooted<JSObject*> holder(
aCx, ResolveModuleObjectPropertyById(aCx, globalObj, aId));
if (!JS_GetOwnPropertyDescriptorById(aCx, holder, aId, aDesc)) {
return false;
}
if (aDesc.get().isNothing()) {
return true;
}
JS::PropertyDescriptor& desc = *aDesc.get();
if (desc.hasValue()) {
if (desc.value().isMagic(JS_UNINITIALIZED_LEXICAL)) {
desc.setValue(JS::UndefinedValue());
}
}
desc.setConfigurable(false);
desc.setEnumerable(true);
if (!desc.isAccessorDescriptor()) {
desc.setWritable(false);
}
return true;
}
bool JSMEnvironmentProxyHandler::has(JSContext* aCx,
JS::Handle<JSObject*> aProxy,
JS::Handle<JS::PropertyKey> aId,
bool* aBp) const {
JS::Rooted<JSObject*> globalObj(aCx, getGlobal(aCx, aProxy));
JS::Rooted<JSObject*> holder(
aCx, ResolveModuleObjectPropertyById(aCx, globalObj, aId));
return JS_HasPropertyById(aCx, holder, aId, aBp);
}
bool JSMEnvironmentProxyHandler::get(JSContext* aCx,
JS::Handle<JSObject*> aProxy,
JS::Handle<JS::Value> aReceiver,
JS::Handle<JS::PropertyKey> aId,
JS::MutableHandle<JS::Value> aVp) const {
JS::Rooted<JSObject*> globalObj(aCx, getGlobal(aCx, aProxy));
JS::Rooted<JSObject*> holder(
aCx, ResolveModuleObjectPropertyById(aCx, globalObj, aId));
if (!JS_GetPropertyById(aCx, holder, aId, aVp)) {
return false;
}
if (aVp.isMagic(JS_UNINITIALIZED_LEXICAL)) {
aVp.setUndefined();
}
return true;
}
bool JSMEnvironmentProxyHandler::ownPropertyKeys(
JSContext* aCx, JS::Handle<JSObject*> aProxy,
JS::MutableHandleVector<JS::PropertyKey> aProps) const {
JS::Rooted<JSObject*> globalObj(aCx, getGlobal(aCx, aProxy));
JS::Rooted<JS::IdVector> globalIds(aCx, JS::IdVector(aCx));
if (!JS_Enumerate(aCx, globalObj, &globalIds)) {
return false;
}
for (size_t i = 0; i < globalIds.length(); i++) {
if (!aProps.append(globalIds[i])) {
JS_ReportOutOfMemory(aCx);
return false;
}
}
JS::RootedObject lexicalEnv(aCx, JS_ExtensibleLexicalEnvironment(globalObj));
JS::Rooted<JS::IdVector> lexicalIds(aCx, JS::IdVector(aCx));
if (!JS_Enumerate(aCx, lexicalEnv, &lexicalIds)) {
return false;
}
for (size_t i = 0; i < lexicalIds.length(); i++) {
if (!aProps.append(lexicalIds[i])) {
JS_ReportOutOfMemory(aCx);
return false;
}
}
return true;
}
JSObject* CreateJSMEnvironmentProxy(JSContext* aCx,
JS::Handle<JSObject*> aGlobalObj) {
js::ProxyOptions options;
options.setLazyProto(true);
JS::Rooted<JS::Value> globalVal(aCx, JS::ObjectValue(*aGlobalObj));
return NewProxyObject(aCx, &JSMEnvironmentProxyHandler::gHandler, globalVal,
nullptr, options);
}
} // namespace loader
} // namespace mozilla

View File

@@ -0,0 +1,31 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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 mozilla_loader_JSMEnvironmentProxy_h
#define mozilla_loader_JSMEnvironmentProxy_h
#include "js/Id.h" // JS::PropertyKey
#include "js/TypeDecls.h" // JSContext, JSObject
#include "js/RootingAPI.h" // JS::Handle
namespace mozilla {
namespace loader {
JSObject* ResolveModuleObjectPropertyById(JSContext* aCx,
JS::Handle<JSObject*> aModObj,
JS::Handle<JS::PropertyKey> aId);
JSObject* ResolveModuleObjectProperty(JSContext* aCx,
JS::Handle<JSObject*> aModObj,
const char* aName);
JSObject* CreateJSMEnvironmentProxy(JSContext* aCx,
JS::Handle<JSObject*> aGlobalObj);
} // namespace loader
} // namespace mozilla
#endif // mozilla_loader_JSMEnvironmentProxy_h

View File

@@ -8,6 +8,7 @@ UNIFIED_SOURCES += [
"AutoMemMap.cpp", "AutoMemMap.cpp",
"ChromeScriptLoader.cpp", "ChromeScriptLoader.cpp",
"ComponentModuleLoader.cpp", "ComponentModuleLoader.cpp",
"JSMEnvironmentProxy.cpp",
"ModuleEnvironmentProxy.cpp", "ModuleEnvironmentProxy.cpp",
"mozJSLoaderUtils.cpp", "mozJSLoaderUtils.cpp",
"mozJSSubScriptLoader.cpp", "mozJSSubScriptLoader.cpp",

View File

@@ -49,6 +49,7 @@
#include "nsReadableUtils.h" #include "nsReadableUtils.h"
#include "nsXULAppAPI.h" #include "nsXULAppAPI.h"
#include "WrapperFactory.h" #include "WrapperFactory.h"
#include "JSMEnvironmentProxy.h"
#include "ModuleEnvironmentProxy.h" #include "ModuleEnvironmentProxy.h"
#include "AutoMemMap.h" #include "AutoMemMap.h"
@@ -344,25 +345,6 @@ mozJSComponentLoader::~mozJSComponentLoader() {
StaticRefPtr<mozJSComponentLoader> mozJSComponentLoader::sSelf; StaticRefPtr<mozJSComponentLoader> mozJSComponentLoader::sSelf;
// For terrible compatibility reasons, we need to consider both the global
// lexical environment and the global of modules when searching for exported
// symbols.
static JSObject* ResolveModuleObjectProperty(JSContext* aCx,
HandleObject aModObj,
const char* name) {
if (JS_HasExtensibleLexicalEnvironment(aModObj)) {
RootedObject lexical(aCx, JS_ExtensibleLexicalEnvironment(aModObj));
bool found;
if (!JS_HasOwnProperty(aCx, lexical, name, &found)) {
return nullptr;
}
if (found) {
return lexical;
}
}
return aModObj;
}
const mozilla::Module* mozJSComponentLoader::LoadModule(FileLocation& aFile) { const mozilla::Module* mozJSComponentLoader::LoadModule(FileLocation& aFile) {
if (!NS_IsMainThread()) { if (!NS_IsMainThread()) {
MOZ_ASSERT(false, "Don't use JS components off the main thread"); MOZ_ASSERT(false, "Don't use JS components off the main thread");
@@ -1187,22 +1169,6 @@ nsresult mozJSComponentLoader::GetComponentLoadStack(
#endif #endif
} }
static JSObject* ResolveModuleObjectPropertyById(JSContext* aCx,
HandleObject aModObj,
HandleId id) {
if (JS_HasExtensibleLexicalEnvironment(aModObj)) {
RootedObject lexical(aCx, JS_ExtensibleLexicalEnvironment(aModObj));
bool found;
if (!JS_HasOwnPropertyById(aCx, lexical, id, &found)) {
return nullptr;
}
if (found) {
return lexical;
}
}
return aModObj;
}
nsresult mozJSComponentLoader::ImportInto(const nsACString& aLocation, nsresult mozJSComponentLoader::ImportInto(const nsACString& aLocation,
HandleObject targetObj, JSContext* cx, HandleObject targetObj, JSContext* cx,
MutableHandleObject vp) { MutableHandleObject vp) {
@@ -1454,7 +1420,19 @@ nsresult mozJSComponentLoader::Import(JSContext* aCx,
} }
MOZ_ASSERT(mod->obj, "Import table contains entry with no object"); MOZ_ASSERT(mod->obj, "Import table contains entry with no object");
aModuleGlobal.set(mod->obj); JS::RootedObject globalProxy(aCx);
{
JSAutoRealm ar(aCx, mod->obj);
globalProxy = CreateJSMEnvironmentProxy(aCx, mod->obj);
if (!globalProxy) {
return NS_ERROR_FAILURE;
}
}
if (!JS_WrapObject(aCx, &globalProxy)) {
return NS_ERROR_FAILURE;
}
aModuleGlobal.set(globalProxy);
JS::RootedObject exports(aCx, mod->exports); JS::RootedObject exports(aCx, mod->exports);
if (!exports && !aIgnoreExports) { if (!exports && !aIgnoreExports) {

View File

@@ -200,7 +200,7 @@ function testWriteProxyOps(global, expectedNames) {
add_task(function test_Cu_import_not_exported_no_shim_JSM() { add_task(function test_Cu_import_not_exported_no_shim_JSM() {
// `exports` and `global` properties for not-ESM-ified case. // `exports` and `global` properties for not-ESM-ified case.
// Not-exported non-lexical variables should be visible in `global`. // Not-exported variables should be visible in `global`.
const exports = {}; const exports = {};
const global = Components.utils.import( const global = Components.utils.import(
@@ -210,25 +210,31 @@ add_task(function test_Cu_import_not_exported_no_shim_JSM() {
Assert.equal(global.exportedVar, "exported var"); Assert.equal(global.exportedVar, "exported var");
Assert.equal(global.exportedFunction(), "exported function"); Assert.equal(global.exportedFunction(), "exported function");
Assert.equal(global.exportedLet, undefined); Assert.equal(global.exportedLet, "exported let");
Assert.equal(global.exportedConst, undefined); Assert.equal(global.exportedConst, "exported const");
Assert.equal(global.notExportedVar, "not exported var"); Assert.equal(global.notExportedVar, "not exported var");
Assert.equal(global.notExportedFunction(), "not exported function"); Assert.equal(global.notExportedFunction(), "not exported function");
Assert.equal(global.notExportedLet, undefined); Assert.equal(global.notExportedLet, "not exported let");
Assert.equal(global.notExportedConst, undefined); Assert.equal(global.notExportedConst, "not exported const");
const expectedNames = [ const expectedNames = [
"EXPORTED_SYMBOLS",
"exportedVar", "exportedVar",
"exportedFunction", "exportedFunction",
"exportedLet",
"exportedConst",
"notExportedVar", "notExportedVar",
"notExportedFunction", "notExportedFunction",
"notExportedLet",
"notExportedConst",
]; ];
testReadProxyOps(global, expectedNames, { testReadProxyOps(global, expectedNames, {
writable: true, writable: false,
enumerable: true, enumerable: true,
configurable: false, configurable: false,
}); });
testWriteProxyOps(global, expectedNames);
Assert.equal(exports.exportedVar, "exported var"); Assert.equal(exports.exportedVar, "exported var");
Assert.equal(exports.exportedFunction(), "exported function"); Assert.equal(exports.exportedFunction(), "exported function");
@@ -252,17 +258,11 @@ add_task(function test_Cu_import_not_exported_shim() {
Assert.equal(global.exportedVar, "exported var"); Assert.equal(global.exportedVar, "exported var");
Assert.equal(global.exportedFunction(), "exported function"); Assert.equal(global.exportedFunction(), "exported function");
// This is different than no-shim case.
// Lexical variables are visible in the shim's global.
Assert.equal(global.exportedLet, "exported let"); Assert.equal(global.exportedLet, "exported let");
Assert.equal(global.exportedConst, "exported const"); Assert.equal(global.exportedConst, "exported const");
Assert.equal(global.notExportedVar, "not exported var"); Assert.equal(global.notExportedVar, "not exported var");
Assert.equal(global.notExportedFunction(), "not exported function"); Assert.equal(global.notExportedFunction(), "not exported function");
// This is different than no-shim case.
// Lexical variables are visible in the shim's global.
Assert.equal(global.notExportedLet, "not exported let"); Assert.equal(global.notExportedLet, "not exported let");
Assert.equal(global.notExportedConst, "not exported const"); Assert.equal(global.notExportedConst, "not exported const");
@@ -276,6 +276,7 @@ add_task(function test_Cu_import_not_exported_shim() {
"notExportedLet", "notExportedLet",
"notExportedConst", "notExportedConst",
]; ];
testReadProxyOps(global, expectedNames, { testReadProxyOps(global, expectedNames, {
writable: false, writable: false,
enumerable: true, enumerable: true,