Bug 1769763: Part 2 - Add debug names to StructuredCloneHolder objects for about:memory. r=mccr8,devtools-reviewers

Differential Revision: https://phabricator.services.mozilla.com/D162251
This commit is contained in:
Kris Maglione 2023-05-23 23:13:36 +00:00
parent 020bf49219
commit f9100d622c
21 changed files with 189 additions and 48 deletions

View File

@ -74,7 +74,11 @@ ChildDebuggerTransport.prototype = {
*/ */
_canBeSerialized(object) { _canBeSerialized(object) {
try { try {
const holder = new StructuredCloneHolder(object); const holder = new StructuredCloneHolder(
"ChildDebuggerTransport._canBeSerialized",
null,
object
);
holder.deserialize(this); holder.deserialize(this);
} catch (e) { } catch (e) {
return false; return false;

View File

@ -12,7 +12,9 @@ add_task(async function test_BrowsingContext_structured_clone() {
let { browsingContext } = frame; let { browsingContext } = frame;
let sch = new StructuredCloneHolder({ browsingContext }); let sch = new StructuredCloneHolder("debug name", "<anonymized> debug name", {
browsingContext,
});
let deserialize = () => sch.deserialize({}, true); let deserialize = () => sch.deserialize({}, true);

View File

@ -22,6 +22,7 @@
#include "mozilla/dom/BlobImpl.h" #include "mozilla/dom/BlobImpl.h"
#include "mozilla/dom/StructuredCloneHolderBinding.h" #include "mozilla/dom/StructuredCloneHolderBinding.h"
#include "mozilla/dom/StructuredCloneTags.h" #include "mozilla/dom/StructuredCloneTags.h"
#include "nsPrintfCString.h"
#include "xpcpublic.h" #include "xpcpublic.h"
namespace mozilla::dom { namespace mozilla::dom {
@ -37,12 +38,16 @@ StructuredCloneBlob::~StructuredCloneBlob() {
/* static */ /* static */
already_AddRefed<StructuredCloneBlob> StructuredCloneBlob::Constructor( already_AddRefed<StructuredCloneBlob> StructuredCloneBlob::Constructor(
GlobalObject& aGlobal, JS::Handle<JS::Value> aValue, GlobalObject& aGlobal, const nsACString& aName,
const nsACString& aAnonymizedName, JS::Handle<JS::Value> aValue,
JS::Handle<JSObject*> aTargetGlobal, ErrorResult& aRv) { JS::Handle<JSObject*> aTargetGlobal, ErrorResult& aRv) {
JSContext* cx = aGlobal.Context(); JSContext* cx = aGlobal.Context();
RefPtr<StructuredCloneBlob> holder = StructuredCloneBlob::Create(); RefPtr<StructuredCloneBlob> holder = StructuredCloneBlob::Create();
holder->mName = aName;
holder->mAnonymizedName = aAnonymizedName.IsVoid() ? aName : aAnonymizedName;
Maybe<JSAutoRealm> ar; Maybe<JSAutoRealm> ar;
JS::Rooted<JS::Value> value(cx, aValue); JS::Rooted<JS::Value> value(cx, aValue);
@ -129,6 +134,14 @@ JSObject* StructuredCloneBlob::ReadStructuredClone(
{ {
RefPtr<StructuredCloneBlob> holder = StructuredCloneBlob::Create(); RefPtr<StructuredCloneBlob> holder = StructuredCloneBlob::Create();
if (!StructuredCloneHolder::ReadCString(aReader, holder->mName)) {
return nullptr;
}
if (!StructuredCloneHolder::ReadCString(aReader, holder->mAnonymizedName)) {
return nullptr;
}
if (!holder->mHolder->ReadStructuredCloneInternal(aCx, aReader, aHolder) || if (!holder->mHolder->ReadStructuredCloneInternal(aCx, aReader, aHolder) ||
!holder->WrapObject(aCx, nullptr, &obj)) { !holder->WrapObject(aCx, nullptr, &obj)) {
return nullptr; return nullptr;
@ -186,6 +199,13 @@ bool StructuredCloneBlob::WriteStructuredClone(JSContext* aCx,
if (mHolder.isNothing()) { if (mHolder.isNothing()) {
return false; return false;
} }
if (!JS_WriteUint32Pair(aWriter, SCTAG_DOM_STRUCTURED_CLONE_HOLDER, 0) ||
!StructuredCloneHolder::WriteCString(aWriter, mName) ||
!StructuredCloneHolder::WriteCString(aWriter, mAnonymizedName)) {
return false;
}
return mHolder->WriteStructuredClone(aCx, aWriter, aHolder); return mHolder->WriteStructuredClone(aCx, aWriter, aHolder);
} }
@ -193,8 +213,7 @@ bool StructuredCloneBlob::Holder::WriteStructuredClone(
JSContext* aCx, JSStructuredCloneWriter* aWriter, JSContext* aCx, JSStructuredCloneWriter* aWriter,
StructuredCloneHolder* aHolder) { StructuredCloneHolder* aHolder) {
auto& data = mBuffer->data(); auto& data = mBuffer->data();
if (!JS_WriteUint32Pair(aWriter, SCTAG_DOM_STRUCTURED_CLONE_HOLDER, 0) || if (!JS_WriteUint32Pair(aWriter, data.Size(), JS_STRUCTURED_CLONE_VERSION) ||
!JS_WriteUint32Pair(aWriter, data.Size(), JS_STRUCTURED_CLONE_VERSION) ||
!JS_WriteUint32Pair(aWriter, aHolder->BlobImpls().Length(), !JS_WriteUint32Pair(aWriter, aHolder->BlobImpls().Length(),
BlobImpls().Length())) { BlobImpls().Length())) {
return false; return false;
@ -221,9 +240,12 @@ StructuredCloneBlob::CollectReports(nsIHandleReportCallback* aHandleReport,
size += mHolder->SizeOfExcludingThis(MallocSizeOf); size += mHolder->SizeOfExcludingThis(MallocSizeOf);
} }
MOZ_COLLECT_REPORT("explicit/dom/structured-clone-holder", KIND_HEAP, aHandleReport->Callback(
UNITS_BYTES, size, ""_ns,
"Memory used by StructuredCloneHolder DOM objects."); nsPrintfCString("explicit/dom/structured-clone-holder/%s",
aAnonymize ? mAnonymizedName.get() : mName.get()),
KIND_HEAP, UNITS_BYTES, size,
"Memory used by StructuredCloneHolder DOM objects."_ns, aData);
return NS_OK; return NS_OK;
} }

View File

@ -40,7 +40,8 @@ class StructuredCloneBlob final : public nsIMemoryReporter {
StructuredCloneHolder* aHolder); StructuredCloneHolder* aHolder);
static already_AddRefed<StructuredCloneBlob> Constructor( static already_AddRefed<StructuredCloneBlob> Constructor(
GlobalObject& aGlobal, JS::Handle<JS::Value> aValue, GlobalObject& aGlobal, const nsACString& aName,
const nsACString& aAnonymizedName, JS::Handle<JS::Value> aValue,
JS::Handle<JSObject*> aTargetGlobal, ErrorResult& aRv); JS::Handle<JSObject*> aTargetGlobal, ErrorResult& aRv);
void Deserialize(JSContext* aCx, JS::Handle<JSObject*> aTargetScope, void Deserialize(JSContext* aCx, JS::Handle<JSObject*> aTargetScope,
@ -71,6 +72,8 @@ class StructuredCloneBlob final : public nsIMemoryReporter {
StructuredCloneHolder* aHolder); StructuredCloneHolder* aHolder);
}; };
nsCString mName;
nsCString mAnonymizedName;
Maybe<Holder> mHolder; Maybe<Holder> mHolder;
static already_AddRefed<StructuredCloneBlob> Create() { static already_AddRefed<StructuredCloneBlob> Create() {

View File

@ -12,7 +12,7 @@ add_task(async function test_structuredCloneHolder() {
const obj = { foo: [{ bar: "baz" }] }; const obj = { foo: [{ bar: "baz" }] };
let holder = new StructuredCloneHolder(obj); let holder = new StructuredCloneHolder("", "", obj);
// Test same-compartment deserialization // Test same-compartment deserialization
@ -39,7 +39,7 @@ add_task(async function test_structuredCloneHolder() {
// Test non-object-value round-trip. // Test non-object-value round-trip.
equal( equal(
new StructuredCloneHolder("foo").deserialize(global), new StructuredCloneHolder("", "", "foo").deserialize(global),
"foo", "foo",
"Round-tripping non-object values works as expected" "Round-tripping non-object values works as expected"
); );
@ -128,7 +128,7 @@ add_task(async function test_structuredCloneHolder_xray() {
let holder; let holder;
Cu.exportFunction( Cu.exportFunction(
function serialize(val) { function serialize(val) {
holder = new StructuredCloneHolder(val, sandbox1); holder = new StructuredCloneHolder("", "", val, sandbox1);
}, },
sandbox1, sandbox1,
{ defineAs: "serialize" } { defineAs: "serialize" }

View File

@ -16,9 +16,17 @@ interface StructuredCloneHolder {
* *
* The serialization happens in the compartment of the given global or, if no * The serialization happens in the compartment of the given global or, if no
* global is provided, the compartment of the data value. * global is provided, the compartment of the data value.
*
* The name argument is added to the path of the object in
* memory reports, to make it easier to determine the source of leaks. In
* anonymized memory reports, the anonymized name is used instead. If
* anonymizedName is null, name is used in anonymized reports as well.
* Anonymized names should not contain any potentially private information,
* such as web URLs or user-provided data.
*/ */
[Throws] [Throws]
constructor(any data, optional object? global = null); constructor(UTF8String name, UTF8String? anonymizedName,
any data, optional object? global = null);
/** /**
* Deserializes the structured clone data in the scope of the given global, * Deserializes the structured clone data in the scope of the given global,

View File

@ -1457,7 +1457,11 @@ export class RTCPeerConnection {
// Exceptions thrown by c++ code do not propagate. In most cases, that's // Exceptions thrown by c++ code do not propagate. In most cases, that's
// fine because we're using Promises, which can be copied. But this is // fine because we're using Promises, which can be copied. But this is
// not promise-based, so we have to do this sketchy stuff. // not promise-based, so we have to do this sketchy stuff.
const holder = new StructuredCloneHolder(new ClonedErrorHolder(e)); const holder = new StructuredCloneHolder(
"",
"",
new ClonedErrorHolder(e)
);
throw holder.deserialize(this._win); throw holder.deserialize(this._win);
} }
} }

View File

@ -42,7 +42,7 @@ add_task(async function test_unhandled_dom_exception() {
let messages = await getSandboxMessages( let messages = await getSandboxMessages(
sandbox, sandbox,
`new Promise(() => { `new Promise(() => {
new StructuredCloneHolder(() => {}); new StructuredCloneHolder("", "", () => {});
});` });`
); );

View File

@ -171,7 +171,11 @@ class EmbedderPort {
switch (aEvent) { switch (aEvent) {
case "GeckoView:WebExtension:PortMessageFromApp": { case "GeckoView:WebExtension:PortMessageFromApp": {
const holder = new StructuredCloneHolder(aData.message); const holder = new StructuredCloneHolder(
"GeckoView:WebExtension:PortMessageFromApp",
null,
aData.message
);
this.messenger.sendPortMessage(this.id, holder); this.messenger.sendPortMessage(this.id, holder);
break; break;
} }

View File

@ -613,9 +613,11 @@ export class SpecialPowersChild extends JSWindowActorChild {
let name = aMessage.json.name; let name = aMessage.json.name;
let message = aMessage.json.message; let message = aMessage.json.message;
if (this.contentWindow) { if (this.contentWindow) {
message = new StructuredCloneHolder(message).deserialize( message = new StructuredCloneHolder(
this.contentWindow `SpecialPowers/receiveMessage/${name}`,
); null,
message
).deserialize(this.contentWindow);
} }
// Ignore message from other chrome script // Ignore message from other chrome script
if (messageId != id) { if (messageId != id) {

View File

@ -243,11 +243,11 @@ class MessageEvent extends SimpleEventAPI {
} }
} }
function holdMessage(data, native = null) { function holdMessage(name, anonymizedName, data, native = null) {
if (native && AppConstants.platform !== "android") { if (native && AppConstants.platform !== "android") {
data = lazy.NativeApp.encodeMessage(native.context, data); data = lazy.NativeApp.encodeMessage(native.context, data);
} }
return new StructuredCloneHolder(data); return new StructuredCloneHolder(name, anonymizedName, data);
} }
// Implements the runtime.Port extension API object. // Implements the runtime.Port extension API object.
@ -263,7 +263,10 @@ class Port {
this.context = context; this.context = context;
this.name = name; this.name = name;
this.sender = sender; this.sender = sender;
this.holdMessage = native ? data => holdMessage(data, this) : holdMessage; this.holdMessage = native
? (name, anonymizedName, data) =>
holdMessage(name, anonymizedName, data, this)
: holdMessage;
this.conduit = context.openConduit(this, { this.conduit = context.openConduit(this, {
portId, portId,
native, native,
@ -307,7 +310,13 @@ class Port {
sendPortMessage(json) { sendPortMessage(json) {
if (this.conduit.actor) { if (this.conduit.actor) {
return this.conduit.sendPortMessage({ holder: this.holdMessage(json) }); return this.conduit.sendPortMessage({
holder: this.holdMessage(
`Port/${this.context.extension.id}/sendPortMessage/${this.name}`,
`Port/${this.context.extension.id}/sendPortMessage/<anonymized>`,
json
),
});
} }
throw new this.context.Error("Attempt to postMessage on disconnected port"); throw new this.context.Error("Attempt to postMessage on disconnected port");
} }
@ -342,14 +351,23 @@ class Messenger {
} }
sendNativeMessage(nativeApp, json) { sendNativeMessage(nativeApp, json) {
let holder = holdMessage(json, this); let holder = holdMessage(
`Messenger/${this.context.extension.id}/sendNativeMessage/${nativeApp}`,
null,
json,
this
);
return this.conduit.queryNativeMessage({ nativeApp, holder }); return this.conduit.queryNativeMessage({ nativeApp, holder });
} }
sendRuntimeMessage({ extensionId, message, callback, ...args }) { sendRuntimeMessage({ extensionId, message, callback, ...args }) {
let response = this.conduit.queryRuntimeMessage({ let response = this.conduit.queryRuntimeMessage({
extensionId: extensionId || this.context.extension.id, extensionId: extensionId || this.context.extension.id,
holder: holdMessage(message), holder: holdMessage(
`Messenger/${this.context.extension.id}/sendRuntimeMessage`,
null,
message
),
...args, ...args,
}); });
// If |response| is a rejected promise, the value will be sanitized by // If |response| is a rejected promise, the value will be sanitized by
@ -836,7 +854,12 @@ class ChildAPIManager {
: fire() : fire()
).then(result => { ).then(result => {
if (result !== undefined) { if (result !== undefined) {
return new StructuredCloneHolder(result, this.context.cloneScope); return new StructuredCloneHolder(
`ChildAPIManager/${this.context.extension.id}/${data.path}`,
null,
result,
this.context.cloneScope
);
} }
return result; return result;
}); });

View File

@ -1507,7 +1507,7 @@ class SchemaAPIManager extends EventEmitter {
this._modulesJSONLoaded = true; this._modulesJSONLoaded = true;
return new StructuredCloneHolder({ return new StructuredCloneHolder("SchemaAPIManager/initModuleJSON", null, {
modules: this.modules, modules: this.modules,
modulePaths: this.modulePaths, modulePaths: this.modulePaths,
manifestKeys: this.manifestKeys, manifestKeys: this.manifestKeys,

View File

@ -1168,7 +1168,11 @@ ParentAPIManager = {
result => { result => {
result = result instanceof SpreadArgs ? [...result] : [result]; result = result instanceof SpreadArgs ? [...result] : [result];
let holder = new StructuredCloneHolder(result); let holder = new StructuredCloneHolder(
`ExtensionParent/${context.extension.id}/recvAPICall/${data.path}`,
null,
result
);
reply({ result: holder }); reply({ result: holder });
}, },
@ -1213,7 +1217,11 @@ ParentAPIManager = {
path: data.path, path: data.path,
urgentSend, urgentSend,
get args() { get args() {
return new StructuredCloneHolder(listenerArgs); return new StructuredCloneHolder(
`ExtensionParent/${context.extension.id}/recvAddListener/${data.path}`,
null,
listenerArgs
);
}, },
}); });
context.trackRunListenerPromise(runListenerPromise); context.trackRunListenerPromise(runListenerPromise);

View File

@ -80,13 +80,19 @@ class SerializeableMap extends Map {
* sending a storage value across a message manager, before cloning it * sending a storage value across a message manager, before cloning it
* into an extension scope. * into an extension scope.
* *
* @param {string} name
* A debugging name for the value, which will appear in the
* StructuredCloneHolder's about:memory path.
* @param {string?} anonymizedName
* An anonymized version of `name`, to be used in anonymized memory
* reports. If `null`, then `name` will be used instead.
* @param {StructuredCloneHolder|*} value * @param {StructuredCloneHolder|*} value
* A value to serialize. * A value to serialize.
* @returns {*} * @returns {*}
*/ */
function serialize(value) { function serialize(name, anonymizedName, value) {
if (value && typeof value === "object" && !isStructuredCloneHolder(value)) { if (value && typeof value === "object" && !isStructuredCloneHolder(value)) {
return new StructuredCloneHolder(value); return new StructuredCloneHolder(name, anonymizedName, value);
} }
return value; return value;
} }
@ -219,8 +225,16 @@ var ExtensionStorage = {
for (let prop in items) { for (let prop in items) {
let item = items[prop]; let item = items[prop];
changes[prop] = { changes[prop] = {
oldValue: serialize(jsonFile.data.get(prop)), oldValue: serialize(
newValue: serialize(item), `set/${extensionId}/old/${prop}`,
`set/${extensionId}/old/<anonymized>`,
jsonFile.data.get(prop)
),
newValue: serialize(
`set/${extensionId}/new/${prop}`,
`set/${extensionId}/new/<anonymized>`,
item
),
}; };
jsonFile.data.set(prop, item); jsonFile.data.set(prop, item);
} }
@ -249,7 +263,13 @@ var ExtensionStorage = {
for (let prop of [].concat(items)) { for (let prop of [].concat(items)) {
if (jsonFile.data.has(prop)) { if (jsonFile.data.has(prop)) {
changes[prop] = { oldValue: serialize(jsonFile.data.get(prop)) }; changes[prop] = {
oldValue: serialize(
`remove/${extensionId}/${prop}`,
`remove/${extensionId}/<anonymized>`,
jsonFile.data.get(prop)
),
};
jsonFile.data.delete(prop); jsonFile.data.delete(prop);
changed = true; changed = true;
} }
@ -282,7 +302,13 @@ var ExtensionStorage = {
for (let [prop, oldValue] of jsonFile.data.entries()) { for (let [prop, oldValue] of jsonFile.data.entries()) {
if (shouldNotifyListeners) { if (shouldNotifyListeners) {
changes[prop] = { oldValue: serialize(oldValue) }; changes[prop] = {
oldValue: serialize(
`clear/${extensionId}/${prop}`,
`clear/${extensionId}/<anonymized>`,
oldValue
),
};
} }
jsonFile.data.delete(prop); jsonFile.data.delete(prop);
@ -319,17 +345,21 @@ var ExtensionStorage = {
*/ */
async get(extensionId, keys) { async get(extensionId, keys) {
let jsonFile = await this.getFile(extensionId); let jsonFile = await this.getFile(extensionId);
return this._filterProperties(jsonFile.data, keys); return this._filterProperties(extensionId, jsonFile.data, keys);
}, },
async _filterProperties(data, keys) { async _filterProperties(extensionId, data, keys) {
let result = {}; let result = {};
if (keys === null) { if (keys === null) {
Object.assign(result, data.toJSON()); Object.assign(result, data.toJSON());
} else if (typeof keys == "object" && !Array.isArray(keys)) { } else if (typeof keys == "object" && !Array.isArray(keys)) {
for (let prop in keys) { for (let prop in keys) {
if (data.has(prop)) { if (data.has(prop)) {
result[prop] = serialize(data.get(prop)); result[prop] = serialize(
`filterProperties/${extensionId}/${prop}`,
`filterProperties/${extensionId}/<anonymized>`,
data.get(prop)
);
} else { } else {
result[prop] = keys[prop]; result[prop] = keys[prop];
} }
@ -337,7 +367,11 @@ var ExtensionStorage = {
} else { } else {
for (let prop of [].concat(keys)) { for (let prop of [].concat(keys)) {
if (data.has(prop)) { if (data.has(prop)) {
result[prop] = serialize(data.get(prop)); result[prop] = serialize(
`filterProperties/${extensionId}/${prop}`,
`filterProperties/${extensionId}/<anonymized>`,
data.get(prop)
);
} }
} }
} }
@ -407,7 +441,12 @@ var ExtensionStorage = {
let result = {}; let result = {};
for (let [key, value] of Object.entries(items)) { for (let [key, value] of Object.entries(items)) {
try { try {
result[key] = new StructuredCloneHolder(value, context.cloneScope); result[key] = new StructuredCloneHolder(
`serializeForContext/${context.extension.id}`,
null,
value,
context.cloneScope
);
} catch (e) { } catch (e) {
throw new ExtensionError(String(e)); throw new ExtensionError(String(e));
} }

View File

@ -236,6 +236,10 @@ class ExtensionStorageLocalIDB extends IndexedDB {
); );
const transactionCompleted = transaction.promiseComplete(); const transactionCompleted = transaction.promiseComplete();
if (!serialize) {
serialize = (name, anonymizedName, value) => value;
}
for (let key of Object.keys(items)) { for (let key of Object.keys(items)) {
try { try {
let oldValue = await objectStore.get(key); let oldValue = await objectStore.get(key);
@ -243,8 +247,9 @@ class ExtensionStorageLocalIDB extends IndexedDB {
await objectStore.put(items[key], key); await objectStore.put(items[key], key);
changes[key] = { changes[key] = {
oldValue: oldValue && serialize ? serialize(oldValue) : oldValue, oldValue:
newValue: serialize ? serialize(items[key]) : items[key], oldValue && serialize(`old/${key}`, `old/<anonymized>`, oldValue),
newValue: serialize(`new/${key}`, `new/<anonymized>`, items[key]),
}; };
changed = true; changed = true;
} catch (err) { } catch (err) {
@ -701,6 +706,8 @@ ExtensionStorageIDB = {
// Serialize the nsIPrincipal object into a StructuredCloneHolder related to the privileged // Serialize the nsIPrincipal object into a StructuredCloneHolder related to the privileged
// js global, ready to be sent to the child processes. // js global, ready to be sent to the child processes.
const serializedPrincipal = new StructuredCloneHolder( const serializedPrincipal = new StructuredCloneHolder(
"ExtensionStorageIDB/selectBackend/serializedPrincipal",
null,
storagePrincipal, storagePrincipal,
this this
); );

View File

@ -138,7 +138,14 @@ var NativeApp = class extends EventEmitter {
onConnect(portId, port) { onConnect(portId, port) {
// eslint-disable-next-line // eslint-disable-next-line
this.on("message", (_, message) => { this.on("message", (_, message) => {
port.sendPortMessage(portId, new StructuredCloneHolder(message)); port.sendPortMessage(
portId,
new StructuredCloneHolder(
`NativeMessaging/onConnect/${this.name}`,
null,
message
)
);
}); });
this.once("disconnect", (_, error) => { this.once("disconnect", (_, error) => {
port.sendPortDisconnect(portId, error && new ClonedErrorHolder(error)); port.sendPortDisconnect(portId, error && new ClonedErrorHolder(error));

View File

@ -136,7 +136,7 @@ function blobbify(json) {
// blobbifying. // blobbifying.
json = stripDescriptions(json); json = stripDescriptions(json);
return new StructuredCloneHolder(json); return new StructuredCloneHolder("Schemas/blobbify", null, json);
} }
async function readJSONAndBlobbify(url) { async function readJSONAndBlobbify(url) {

View File

@ -109,13 +109,21 @@ this.storage = class extends ExtensionAPI {
); );
}, },
set(items) { set(items) {
function serialize(name, anonymizedName, value) {
return ExtensionStorage.serialize(
`set/${context.extension.id}/${name}`,
`set/${context.extension.id}/${anonymizedName}`,
value
);
}
return measureOp( return measureOp(
ExtensionTelemetry.storageLocalSetIDB, ExtensionTelemetry.storageLocalSetIDB,
context.extension, context.extension,
async () => { async () => {
const db = await getDB(); const db = await getDB();
const changes = await db.set(items, { const changes = await db.set(items, {
serialize: ExtensionStorage.serialize, serialize,
}); });
if (changes) { if (changes) {

View File

@ -259,7 +259,7 @@ this.test = class extends ExtensionAPI {
// throw if needed. // throw if needed.
v = ChromeUtils.waiveXrays(v); v = ChromeUtils.waiveXrays(v);
} }
new StructuredCloneHolder(v, globalThis); new StructuredCloneHolder("test.assertEq", null, v, globalThis);
} }
// When WebIDL bindings are used, the objects are already cloned // When WebIDL bindings are used, the objects are already cloned
// structurally, so we don't need to check again. // structurally, so we don't need to check again.

View File

@ -337,7 +337,7 @@ this.storage = class extends ExtensionAPIPersistent {
message: "Managed storage manifest not found", message: "Managed storage manifest not found",
}); });
} }
return ExtensionStorage._filterProperties(data, keys); return ExtensionStorage._filterProperties(extension.id, data, keys);
}, },
// managed storage is currently initialized once. // managed storage is currently initialized once.
onChanged: ignoreEvent(context, "storage.managed.onChanged"), onChanged: ignoreEvent(context, "storage.managed.onChanged"),

View File

@ -460,7 +460,7 @@ while True:
}); });
let buffer = NativeApp.encodeMessage(mockContext, MSG); let buffer = NativeApp.encodeMessage(mockContext, MSG);
app.send(new StructuredCloneHolder(buffer)); app.send(new StructuredCloneHolder("", null, buffer));
await recvPromise; await recvPromise;
app._cleanup(); app._cleanup();