зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1356546: Part 1 - Add a StructuredCloneHolder JS helper to hold opaque structured clone blobs. r=billm
There are several places in the WebExtensions framework where we currently need to repeatedly serialize and deserialize structured clone data as it passes through message managers, which can lead to significant performance issues. This helper class lets us serialize a value directly from the source extension context into an opaque blob, and then directly deserialize it into the target context on the other end, with no X-ray overhead or clones into privileged scopes in-between. MozReview-Commit-ID: 4QzHi89onxc --HG-- extra : rebase_source : 2ec196ca9ce9be90b7eadf136c938373ac7d3fdd
This commit is contained in:
Родитель
f47b467c52
Коммит
761a458cda
|
@ -0,0 +1,159 @@
|
|||
/* -*- 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 "mozilla/dom/StructuredCloneBlob.h"
|
||||
|
||||
#include "js/StructuredClone.h"
|
||||
#include "js/Utility.h"
|
||||
#include "jswrapper.h"
|
||||
|
||||
#include "xpcpublic.h"
|
||||
|
||||
#include "mozilla/Maybe.h"
|
||||
#include "mozilla/UniquePtr.h"
|
||||
#include "mozilla/dom/StructuredCloneTags.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
||||
StructuredCloneBlob::StructuredCloneBlob()
|
||||
: StructuredCloneHolder(CloningSupported, TransferringNotSupported,
|
||||
StructuredCloneScope::DifferentProcess)
|
||||
{};
|
||||
|
||||
|
||||
/* static */ already_AddRefed<StructuredCloneBlob>
|
||||
StructuredCloneBlob::Constructor(GlobalObject& aGlobal, JS::HandleValue aValue,
|
||||
JS::HandleObject aTargetGlobal,
|
||||
ErrorResult& aRv)
|
||||
{
|
||||
JSContext* cx = aGlobal.Context();
|
||||
|
||||
RefPtr<StructuredCloneBlob> holder = new StructuredCloneBlob();
|
||||
|
||||
Maybe<JSAutoCompartment> ac;
|
||||
JS::RootedValue value(cx, aValue);
|
||||
|
||||
if (aTargetGlobal) {
|
||||
ac.emplace(cx, aTargetGlobal);
|
||||
|
||||
if (!JS_WrapValue(cx, &value)) {
|
||||
aRv.NoteJSContextException(cx);
|
||||
return nullptr;
|
||||
}
|
||||
} else if (value.isObject()) {
|
||||
JS::RootedObject obj(cx, js::CheckedUnwrap(&value.toObject()));
|
||||
if (!obj) {
|
||||
js::ReportAccessDenied(cx);
|
||||
aRv.NoteJSContextException(cx);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
ac.emplace(cx, obj);
|
||||
value = JS::ObjectValue(*obj);
|
||||
}
|
||||
|
||||
holder->Write(cx, value, aRv);
|
||||
if (aRv.Failed()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return holder.forget();
|
||||
}
|
||||
|
||||
void
|
||||
StructuredCloneBlob::Deserialize(JSContext* aCx, JS::HandleObject aTargetScope,
|
||||
JS::MutableHandleValue aResult, ErrorResult& aRv)
|
||||
{
|
||||
JS::RootedObject scope(aCx, js::CheckedUnwrap(aTargetScope));
|
||||
if (!scope) {
|
||||
js::ReportAccessDenied(aCx);
|
||||
aRv.NoteJSContextException(aCx);
|
||||
return;
|
||||
}
|
||||
|
||||
{
|
||||
JSAutoCompartment ac(aCx, scope);
|
||||
|
||||
Read(xpc::NativeGlobal(scope), aCx, aResult, aRv);
|
||||
if (aRv.Failed()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!JS_WrapValue(aCx, aResult)) {
|
||||
aResult.set(JS::UndefinedValue());
|
||||
aRv.NoteJSContextException(aCx);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* static */ JSObject*
|
||||
StructuredCloneBlob::ReadStructuredClone(JSContext* aCx, JSStructuredCloneReader* aReader)
|
||||
{
|
||||
RefPtr<StructuredCloneBlob> holder = new StructuredCloneBlob();
|
||||
|
||||
if (!holder->ReadStructuredCloneInternal(aCx, aReader)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
JS::RootedObject obj(aCx);
|
||||
if (holder->WrapObject(aCx, nullptr, &obj)) {
|
||||
return obj.get();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool
|
||||
StructuredCloneBlob::ReadStructuredCloneInternal(JSContext* aCx, JSStructuredCloneReader* aReader)
|
||||
{
|
||||
uint32_t length;
|
||||
uint32_t version;
|
||||
if (!JS_ReadUint32Pair(aReader, &length, &version)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
JSStructuredCloneData data(length, length, 4096);
|
||||
if (!JS_ReadBytes(aReader, data.Start(), length)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
mBuffer = MakeUnique<JSAutoStructuredCloneBuffer>(mStructuredCloneScope,
|
||||
&StructuredCloneHolder::sCallbacks,
|
||||
this);
|
||||
mBuffer->adopt(Move(data), version, &StructuredCloneHolder::sCallbacks);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
StructuredCloneBlob::WriteStructuredClone(JSContext* aCx, JSStructuredCloneWriter* aWriter)
|
||||
{
|
||||
auto& data = mBuffer->data();
|
||||
if (!JS_WriteUint32Pair(aWriter, SCTAG_DOM_STRUCTURED_CLONE_HOLDER, 0) ||
|
||||
!JS_WriteUint32Pair(aWriter, data.Size(), JS_STRUCTURED_CLONE_VERSION)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto iter = data.Iter();
|
||||
while (!iter.Done()) {
|
||||
if (!JS_WriteBytes(aWriter, iter.Data(), iter.RemainingInSegment())) {
|
||||
return false;
|
||||
}
|
||||
iter.Advance(data, iter.RemainingInSegment());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
StructuredCloneBlob::WrapObject(JSContext* aCx, JS::HandleObject aGivenProto, JS::MutableHandleObject aResult)
|
||||
{
|
||||
return StructuredCloneHolderBinding::Wrap(aCx, this, aGivenProto, aResult);
|
||||
}
|
||||
|
||||
} // namespace dom
|
||||
} // namespace mozilla
|
|
@ -0,0 +1,55 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
|
||||
/* 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_dom_StructuredCloneBlob_h
|
||||
#define mozilla_dom_StructuredCloneBlob_h
|
||||
|
||||
#include "mozilla/dom/BindingDeclarations.h"
|
||||
#include "mozilla/dom/StructuredCloneHolder.h"
|
||||
#include "mozilla/dom/StructuredCloneHolderBinding.h"
|
||||
#include "mozilla/RefCounted.h"
|
||||
|
||||
#include "jsapi.h"
|
||||
|
||||
#include "nsISupports.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
||||
class StructuredCloneBlob : public StructuredCloneHolder
|
||||
, public RefCounted<StructuredCloneBlob>
|
||||
{
|
||||
public:
|
||||
explicit StructuredCloneBlob();
|
||||
|
||||
static JSObject* ReadStructuredClone(JSContext* aCx, JSStructuredCloneReader* aReader);
|
||||
bool WriteStructuredClone(JSContext* aCx, JSStructuredCloneWriter* aWriter);
|
||||
|
||||
static already_AddRefed<StructuredCloneBlob>
|
||||
Constructor(GlobalObject& aGlobal, JS::HandleValue aValue, JS::HandleObject aTargetGlobal, ErrorResult& aRv);
|
||||
|
||||
void Deserialize(JSContext* aCx, JS::HandleObject aTargetScope,
|
||||
JS::MutableHandleValue aResult, ErrorResult& aRv);
|
||||
|
||||
nsISupports* GetParentObject() const { return nullptr; }
|
||||
JSObject* GetWrapper() const { return nullptr; }
|
||||
|
||||
bool WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto, JS::MutableHandleObject aResult);
|
||||
|
||||
protected:
|
||||
template <typename T, detail::RefCountAtomicity>
|
||||
friend class detail::RefCounted;
|
||||
|
||||
~StructuredCloneBlob() = default;
|
||||
|
||||
private:
|
||||
bool ReadStructuredCloneInternal(JSContext* aCx, JSStructuredCloneReader* aReader);
|
||||
};
|
||||
|
||||
} // namespace dom
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_dom_StructuredCloneBlob_h
|
||||
|
|
@ -10,6 +10,7 @@
|
|||
#include "mozilla/AutoRestore.h"
|
||||
#include "mozilla/dom/BlobBinding.h"
|
||||
#include "mozilla/dom/CryptoKey.h"
|
||||
#include "mozilla/dom/StructuredCloneBlob.h"
|
||||
#include "mozilla/dom/Directory.h"
|
||||
#include "mozilla/dom/DirectoryBinding.h"
|
||||
#include "mozilla/dom/File.h"
|
||||
|
@ -357,6 +358,10 @@ StructuredCloneHolder::ReadFullySerializableObjects(JSContext* aCx,
|
|||
return ReadStructuredCloneImageData(aCx, aReader);
|
||||
}
|
||||
|
||||
if (aTag == SCTAG_DOM_STRUCTURED_CLONE_HOLDER) {
|
||||
return StructuredCloneBlob::ReadStructuredClone(aCx, aReader);
|
||||
}
|
||||
|
||||
if (aTag == SCTAG_DOM_WEBCRYPTO_KEY || aTag == SCTAG_DOM_URLSEARCHPARAMS) {
|
||||
nsIGlobalObject *global = xpc::NativeGlobal(JS::CurrentGlobalOrNull(aCx));
|
||||
if (!global) {
|
||||
|
@ -453,6 +458,14 @@ StructuredCloneHolder::WriteFullySerializableObjects(JSContext* aCx,
|
|||
}
|
||||
}
|
||||
|
||||
// See if this is a StructuredCloneBlob object.
|
||||
{
|
||||
StructuredCloneBlob* holder = nullptr;
|
||||
if (NS_SUCCEEDED(UNWRAP_OBJECT(StructuredCloneHolder, aObj, holder))) {
|
||||
return holder->WriteStructuredClone(aCx, aWriter);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle URLSearchParams cloning
|
||||
{
|
||||
URLSearchParams* usp = nullptr;
|
||||
|
|
|
@ -62,6 +62,8 @@ enum StructuredCloneTags {
|
|||
|
||||
SCTAG_DOM_INPUTSTREAM,
|
||||
|
||||
SCTAG_DOM_STRUCTURED_CLONE_HOLDER,
|
||||
|
||||
// When adding a new tag for IDB, please don't add it to the end of the list!
|
||||
// Tags that are supported by IDB must not ever change. See the static assert
|
||||
// in IDBObjectStore.cpp, method CommonStructuredCloneReadCallback.
|
||||
|
|
|
@ -196,6 +196,7 @@ EXPORTS.mozilla.dom += [
|
|||
'SameProcessMessageQueue.h',
|
||||
'ScreenOrientation.h',
|
||||
'ShadowRoot.h',
|
||||
'StructuredCloneBlob.h',
|
||||
'StructuredCloneHolder.h',
|
||||
'StructuredCloneTags.h',
|
||||
'StyleSheetList.h',
|
||||
|
@ -338,6 +339,7 @@ UNIFIED_SOURCES += [
|
|||
'SameProcessMessageQueue.cpp',
|
||||
'ScreenOrientation.cpp',
|
||||
'ShadowRoot.cpp',
|
||||
'StructuredCloneBlob.cpp',
|
||||
'StructuredCloneHolder.cpp',
|
||||
'StyleSheetList.cpp',
|
||||
'SubtleCrypto.cpp',
|
||||
|
|
|
@ -0,0 +1,98 @@
|
|||
"use strict";
|
||||
|
||||
const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
|
||||
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
const global = this;
|
||||
|
||||
add_task(async function test_structuredCloneHolder() {
|
||||
let principal = Services.scriptSecurityManager.createCodebasePrincipal(
|
||||
Services.io.newURI("http://example.com/"), {});
|
||||
|
||||
let sandbox = Cu.Sandbox(principal);
|
||||
|
||||
const obj = {foo: [{bar: "baz"}]};
|
||||
|
||||
let holder = new StructuredCloneHolder(obj);
|
||||
|
||||
|
||||
// Test same-compartment deserialization
|
||||
|
||||
let res = holder.deserialize(global);
|
||||
|
||||
notEqual(res, obj, "Deserialized result is a different object from the original");
|
||||
|
||||
deepEqual(res, obj, "Deserialized result is deeply equivalent to the original");
|
||||
|
||||
equal(Cu.getObjectPrincipal(res), Cu.getObjectPrincipal(global),
|
||||
"Deserialized result has the correct principal");
|
||||
|
||||
|
||||
// Test non-object-value round-trip.
|
||||
|
||||
equal(new StructuredCloneHolder("foo").deserialize(global), "foo",
|
||||
"Round-tripping non-object values works as expected");
|
||||
|
||||
|
||||
// Test cross-compartment deserialization
|
||||
|
||||
res = holder.deserialize(sandbox);
|
||||
|
||||
notEqual(res, obj, "Cross-compartment-deserialized result is a different object from the original");
|
||||
|
||||
deepEqual(res, obj, "Cross-compartment-deserialized result is deeply equivalent to the original");
|
||||
|
||||
equal(Cu.getObjectPrincipal(res), principal,
|
||||
"Cross-compartment-deserialized result has the correct principal");
|
||||
|
||||
|
||||
// Test message manager transportability
|
||||
|
||||
const MSG = "StructuredCloneHolder";
|
||||
|
||||
let resultPromise = new Promise(resolve => {
|
||||
Services.ppmm.addMessageListener(MSG, resolve);
|
||||
});
|
||||
|
||||
Services.cpmm.sendAsyncMessage(MSG, holder);
|
||||
|
||||
res = await resultPromise;
|
||||
|
||||
ok(res.data instanceof StructuredCloneHolder,
|
||||
"Sending structured clone holders through message managers works as expected");
|
||||
|
||||
deepEqual(res.data.deserialize(global), obj,
|
||||
"Sending structured clone holders through message managers works as expected");
|
||||
});
|
||||
|
||||
// Test that X-rays passed to an exported function are serialized
|
||||
// through their exported wrappers.
|
||||
add_task(async function test_structuredCloneHolder_xray() {
|
||||
let principal = Services.scriptSecurityManager.createCodebasePrincipal(
|
||||
Services.io.newURI("http://example.com/"), {});
|
||||
|
||||
let sandbox1 = Cu.Sandbox(principal, {wantXrays: true});
|
||||
|
||||
let sandbox2 = Cu.Sandbox(principal, {wantXrays: true});
|
||||
Cu.evalInSandbox(`this.x = {y: "z", get z() { return "q" }}`, sandbox2);
|
||||
|
||||
sandbox1.x = sandbox2.x;
|
||||
|
||||
let holder;
|
||||
Cu.exportFunction(function serialize(val) {
|
||||
holder = new StructuredCloneHolder(val, sandbox1);
|
||||
}, sandbox1, {defineAs: "serialize"});
|
||||
|
||||
Cu.evalInSandbox(`serialize(x)`, sandbox1);
|
||||
|
||||
const obj = {y: "z"};
|
||||
|
||||
let res = holder.deserialize(global);
|
||||
|
||||
deepEqual(res, obj, "Deserialized result is deeply equivalent to the expected object");
|
||||
deepEqual(res, sandbox2.x, "Deserialized result is deeply equivalent to the X-ray-wrapped object");
|
||||
|
||||
equal(Cu.getObjectPrincipal(res), Cu.getObjectPrincipal(global),
|
||||
"Deserialized result has the correct principal");
|
||||
});
|
|
@ -38,6 +38,7 @@ head = head_xml.js
|
|||
head = head_xml.js
|
||||
[test_range.js]
|
||||
head = head_xml.js
|
||||
[test_structuredcloneholder.js]
|
||||
[test_thirdpartyutil.js]
|
||||
[test_treewalker.js]
|
||||
head = head_xml.js
|
||||
|
|
|
@ -798,6 +798,11 @@ DOMInterfaces = {
|
|||
'implicitJSContext': [ 'close' ],
|
||||
},
|
||||
|
||||
'StructuredCloneHolder': {
|
||||
'nativeType': 'mozilla::dom::StructuredCloneBlob',
|
||||
'wrapperCache': False,
|
||||
},
|
||||
|
||||
'StyleSheet': {
|
||||
'nativeType': 'mozilla::StyleSheet',
|
||||
'headerFile': 'mozilla/StyleSheetInlines.h',
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* 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/.
|
||||
*/
|
||||
|
||||
/**
|
||||
* A holder for structured-clonable data which can itself be cloned with
|
||||
* little overhead, and deserialized into an arbitrary global.
|
||||
*/
|
||||
[ChromeOnly, Exposed=(Window,System,Worker),
|
||||
/**
|
||||
* Serializes the given value to an opaque structured clone blob, and
|
||||
* returns the result.
|
||||
*
|
||||
* The serialization happens in the compartment of the given global or, if no
|
||||
* global is provided, the compartment of the data value.
|
||||
*/
|
||||
Constructor(any data, optional object? global = null)]
|
||||
interface StructuredCloneHolder {
|
||||
/**
|
||||
* Deserializes the structured clone data in the scope of the given global,
|
||||
* and returns the result.
|
||||
*/
|
||||
[Throws]
|
||||
any deserialize(object global);
|
||||
};
|
|
@ -790,6 +790,7 @@ WEBIDL_FILES = [
|
|||
'StorageEvent.webidl',
|
||||
'StorageManager.webidl',
|
||||
'StorageType.webidl',
|
||||
'StructuredCloneHolder.webidl',
|
||||
'StyleSheet.webidl',
|
||||
'StyleSheetList.webidl',
|
||||
'SubtleCrypto.webidl',
|
||||
|
|
Загрузка…
Ссылка в новой задаче