feat: remove deprecated base::Value-based serialization (#21560)

* feat: remove deprecated base::Value-based serialization

* add note to breaking-changes
This commit is contained in:
Jeremy Apthorp 2019-12-17 22:24:50 -08:00 коммит произвёл Cheng Zhao
Родитель cc8bf12351
Коммит 1cac62f0a2
5 изменённых файлов: 40 добавлений и 120 удалений

Просмотреть файл

@ -28,6 +28,22 @@ Electron 8.x, and has been removed in Electron 9.x. The layout zoom level limits
are now fixed at a minimum of 0.25 and a maximum of 5.0, as defined
[here](https://chromium.googlesource.com/chromium/src/+/938b37a6d2886bf8335fc7db792f1eb46c65b2ae/third_party/blink/common/page/page_zoom.cc#11).
### Sending non-JS objects over IPC now throws an exception
In Electron 8.0, IPC was changed to use the Structured Clone Algorithm,
bringing significant performance improvements. To help ease the transition, the
old IPC serialization algorithm was kept and used for some objects that aren't
serializable with Structured Clone. In particular, DOM objects (e.g. `Element`,
`Location` and `DOMMatrix`), Node.js objects backed by C++ classes (e.g.
`process.env`, some members of `Stream`), and Electron objects backed by C++
classes (e.g. `WebContents`, `BrowserWindow` and `WebFrame`) are not
serializable with Structured Clone. Whenever the old algorithm was invoked, a
deprecation warning was printed.
In Electron 9.0, the old serialization algorithm has been removed, and sending
such non-serializable objects will now throw an "object could not be cloned"
error.
## Planned Breaking API Changes (8.0)
### Values sent over IPC are now serialized with Structured Clone Algorithm

Просмотреть файл

@ -12,8 +12,6 @@
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "gin/converter.h"
#include "mojo/public/cpp/base/values_mojom_traits.h"
#include "mojo/public/mojom/base/values.mojom.h"
#include "shell/common/deprecate_util.h"
#include "shell/common/gin_converters/value_converter.h"
#include "shell/common/gin_helper/dictionary.h"
@ -510,42 +508,23 @@ bool Converter<network::mojom::ReferrerPolicy>::FromV8(
}
namespace {
constexpr uint8_t kNewSerializationTag = 0;
constexpr uint8_t kOldSerializationTag = 1;
class V8Serializer : public v8::ValueSerializer::Delegate {
public:
explicit V8Serializer(v8::Isolate* isolate,
bool use_old_serialization = false)
: isolate_(isolate),
serializer_(isolate, this),
use_old_serialization_(use_old_serialization) {}
explicit V8Serializer(v8::Isolate* isolate)
: isolate_(isolate), serializer_(isolate, this) {}
~V8Serializer() override = default;
bool Serialize(v8::Local<v8::Value> value, blink::CloneableMessage* out) {
serializer_.WriteHeader();
if (use_old_serialization_) {
WriteTag(kOldSerializationTag);
if (!WriteBaseValue(value)) {
isolate_->ThrowException(
StringToV8(isolate_, "An object could not be cloned."));
return false;
}
} else {
WriteTag(kNewSerializationTag);
bool wrote_value;
v8::TryCatch try_catch(isolate_);
if (!serializer_.WriteValue(isolate_->GetCurrentContext(), value)
.To(&wrote_value)) {
try_catch.Reset();
if (!V8Serializer(isolate_, true).Serialize(value, out)) {
try_catch.ReThrow();
return false;
}
return true;
}
DCHECK(wrote_value);
bool wrote_value;
if (!serializer_.WriteValue(isolate_->GetCurrentContext(), value)
.To(&wrote_value)) {
isolate_->ThrowException(v8::Exception::Error(
StringToV8(isolate_, "An object could not be cloned.")));
return false;
}
DCHECK(wrote_value);
std::pair<uint8_t*, size_t> buffer = serializer_.Release();
DCHECK_EQ(buffer.first, data_.data());
@ -555,29 +534,6 @@ class V8Serializer : public v8::ValueSerializer::Delegate {
return true;
}
bool WriteBaseValue(v8::Local<v8::Value> object) {
node::Environment* env = node::Environment::GetCurrent(isolate_);
if (env) {
electron::EmitDeprecationWarning(
env,
"Passing functions, DOM objects and other non-cloneable JavaScript "
"objects to IPC methods is deprecated and will throw an exception "
"beginning with Electron 9.",
"DeprecationWarning");
}
base::Value value;
if (!ConvertFromV8(isolate_, object, &value)) {
return false;
}
mojo::Message message = mojo_base::mojom::Value::SerializeAsMessage(&value);
serializer_.WriteUint32(message.data_num_bytes());
serializer_.WriteRawBytes(message.data(), message.data_num_bytes());
return true;
}
void WriteTag(uint8_t tag) { serializer_.WriteRawBytes(&tag, 1); }
// v8::ValueSerializer::Delegate
void* ReallocateBufferMemory(void* old_buffer,
size_t size,
@ -601,7 +557,6 @@ class V8Serializer : public v8::ValueSerializer::Delegate {
v8::Isolate* isolate_;
std::vector<uint8_t> data_;
v8::ValueSerializer serializer_;
bool use_old_serialization_;
};
class V8Deserializer : public v8::ValueDeserializer::Delegate {
@ -620,54 +575,11 @@ class V8Deserializer : public v8::ValueDeserializer::Delegate {
if (!deserializer_.ReadHeader(context).To(&read_header))
return v8::Null(isolate_);
DCHECK(read_header);
uint8_t tag;
if (!ReadTag(&tag))
v8::Local<v8::Value> value;
if (!deserializer_.ReadValue(context).ToLocal(&value)) {
return v8::Null(isolate_);
switch (tag) {
case kNewSerializationTag: {
v8::Local<v8::Value> value;
if (!deserializer_.ReadValue(context).ToLocal(&value)) {
return v8::Null(isolate_);
}
return scope.Escape(value);
}
case kOldSerializationTag: {
v8::Local<v8::Value> value;
if (!ReadBaseValue(&value)) {
return v8::Null(isolate_);
}
return scope.Escape(value);
}
default:
NOTREACHED() << "Invalid tag: " << tag;
return v8::Null(isolate_);
}
}
bool ReadTag(uint8_t* tag) {
const void* tag_bytes;
if (!deserializer_.ReadRawBytes(1, &tag_bytes))
return false;
*tag = *reinterpret_cast<const uint8_t*>(tag_bytes);
return true;
}
bool ReadBaseValue(v8::Local<v8::Value>* value) {
uint32_t length;
const void* data;
if (!deserializer_.ReadUint32(&length) ||
!deserializer_.ReadRawBytes(length, &data)) {
return false;
}
mojo::Message message(
base::make_span(reinterpret_cast<const uint8_t*>(data), length), {});
base::Value out;
if (!mojo_base::mojom::Value::DeserializeFromMessage(std::move(message),
&out)) {
return false;
}
*value = ConvertToV8(isolate_, out);
return true;
return scope.Escape(value);
}
private:

Просмотреть файл

@ -52,22 +52,15 @@ describe('ipcRenderer module', () => {
expect(Buffer.from(data).equals(received)).to.be.true()
})
// TODO(nornagon): Change this test to expect an exception to be thrown in
// Electron 9.
it('can send objects with DOM class prototypes', async () => {
w.webContents.executeJavaScript(`{
it('throws when sending objects with DOM class prototypes', async () => {
await expect(w.webContents.executeJavaScript(`{
const { ipcRenderer } = require('electron')
ipcRenderer.send('message', document.location)
}`)
const [, value] = await emittedOnce(ipcMain, 'message')
expect(value.protocol).to.equal('about:')
expect(value.hostname).to.equal('')
}`)).to.eventually.be.rejected()
})
// TODO(nornagon): Change this test to expect an exception to be thrown in
// Electron 9.
it('does not crash when sending external objects', async () => {
w.webContents.executeJavaScript(`{
await expect(w.webContents.executeJavaScript(`{
const { ipcRenderer } = require('electron')
const http = require('http')
@ -75,10 +68,7 @@ describe('ipcRenderer module', () => {
const stream = request.agent.sockets['127.0.0.1:5000:'][0]._handle._externalStream
ipcRenderer.send('message', stream)
}`)
const [, externalStreamValue] = await emittedOnce(ipcMain, 'message')
expect(externalStreamValue).to.be.null()
}`)).to.eventually.be.rejected()
})
it('can send objects that both reference the same object', async () => {

Просмотреть файл

@ -48,11 +48,12 @@ function makeWindow () {
before(async () => {
w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true, enableRemoteModule: true } })
await w.loadURL('about:blank')
await w.webContents.executeJavaScript(`
await w.webContents.executeJavaScript(`{
const chai_1 = window.chai_1 = require('chai')
chai_1.use(require('chai-as-promised'))
chai_1.use(require('dirty-chai'))
`)
null
}`)
})
after(closeAllWindows)
return () => w
@ -63,11 +64,12 @@ function makeEachWindow () {
beforeEach(async () => {
w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true, enableRemoteModule: true } })
await w.loadURL('about:blank')
await w.webContents.executeJavaScript(`
await w.webContents.executeJavaScript(`{
const chai_1 = window.chai_1 = require('chai')
chai_1.use(require('chai-as-promised'))
chai_1.use(require('dirty-chai'))
`)
null
}`)
})
afterEach(closeAllWindows)
return () => w

Просмотреть файл

@ -20,7 +20,7 @@ describe('modules support', () => {
it('can be required in renderer', async () => {
const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true } })
w.loadURL('about:blank')
await expect(w.webContents.executeJavaScript(`{ require('echo') }`)).to.be.fulfilled()
await expect(w.webContents.executeJavaScript(`{ require('echo'); null }`)).to.be.fulfilled()
})
ifit(features.isRunAsNodeEnabled())('can be required in node binary', function (done) {