electron/shell/common/v8_util.cc

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

263 lines
8.8 KiB
C++
Raw Normal View History

// Copyright (c) 2020 Slack Technologies, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include "shell/common/v8_util.h"
#include <cstdint>
#include <utility>
#include <vector>
#include "base/memory/raw_ptr.h"
#include "gin/converter.h"
#include "shell/common/api/electron_api_native_image.h"
#include "skia/public/mojom/bitmap.mojom.h"
#include "third_party/blink/public/common/messaging/cloneable_message.h"
#include "third_party/blink/public/common/messaging/web_message_port.h"
#include "ui/gfx/image/image_skia.h"
#include "v8/include/v8.h"
namespace electron {
namespace {
constexpr uint8_t kNativeImageTag = 'i';
constexpr uint8_t kTrailerOffsetTag = 0xFE;
constexpr uint8_t kVersionTag = 0xFF;
} // namespace
class V8Serializer : public v8::ValueSerializer::Delegate {
public:
explicit V8Serializer(v8::Isolate* isolate)
: isolate_(isolate), serializer_(isolate, this) {}
~V8Serializer() override = default;
bool Serialize(v8::Local<v8::Value> value, blink::CloneableMessage* out) {
refactor: replace `gin_helper::MicrotasksScope` with `v8::MicrotasksScope` (#46963) * Remove microtasks_scope.h and microtasks_scope.cc * Use v8::MicrotasksScope when ignoring browser checkpoint These call always skip the browser checkpoint, so they are equivalent to using v8::MicrotasksScope directly (modulo the optional wrapper behavior). * Remove MicrotasksScope from node_bindings.cc This code seems contradictory: it explicitly specifies "do not run microtasks" yet runs a microtask checkpoint in the browser process. Looking at its history, it [was introduced][1] with the intention to not run microtasks, but a [subtle C++ language behavior][2] caused it to do the opposite later in the same roll. Since the original intention was to not run microtasks, and since that is also the simplest explanation, we can assume `ignore_browser_checkpoint` should be true and migrate this to `v8::MicrotasksScope` as it is equivalent (modulo the optional wrapper behavior). [1]: https://github.com/electron/electron/pull/29058/commits/a4ea80dd47b1f13e10e218d298e70d6dd6b4fd0a#diff-efe58cf03c97028f37f801db044d396a5f428686da6595d2c692f1c052bbd09c [2]: https://github.com/electron/electron/pull/43185 * Migrate gin_helper/promise.h and gin_helper/promise.cc to v8::MicrotasksScope Restores the [original][1] behavior of running the microtask checkpoint at destruction, but preserves the behavior of running microtasks in the browser process. This had last changed in the migration to gin_helper::MicroTasks. [1]: https://github.com/electron/electron/pull/16401
2025-05-07 13:10:34 -06:00
v8::MicrotasksScope microtasks_scope(
isolate_->GetCurrentContext(),
v8::MicrotasksScope::kDoNotRunMicrotasks);
WriteBlinkEnvelope(19);
serializer_.WriteHeader();
bool wrote_value;
if (!serializer_.WriteValue(isolate_->GetCurrentContext(), value)
.To(&wrote_value)) {
isolate_->ThrowException(v8::Exception::Error(
gin::StringToV8(isolate_, "An object could not be cloned.")));
return false;
}
DCHECK(wrote_value);
const auto [data_bytes, data_len] = serializer_.Release();
DCHECK_EQ(std::data(data_), data_bytes);
DCHECK_GE(std::size(data_), data_len);
data_.resize(data_len);
out->owned_encoded_message = std::move(data_);
out->encoded_message = out->owned_encoded_message;
out->sender_agent_cluster_id =
blink::WebMessagePort::GetEmbedderAgentClusterID();
return true;
}
// v8::ValueSerializer::Delegate
void* ReallocateBufferMemory(void* old_buffer,
size_t size,
size_t* actual_size) override {
DCHECK_EQ(old_buffer, data_.data());
data_.resize(size);
*actual_size = data_.capacity();
return data_.data();
}
void FreeBufferMemory(void* buffer) override {
DCHECK_EQ(buffer, data_.data());
data_ = {};
}
v8::Maybe<bool> WriteHostObject(v8::Isolate* isolate,
v8::Local<v8::Object> object) override {
api::NativeImage* native_image;
if (gin::ConvertFromV8(isolate, object, &native_image)) {
// Serialize the NativeImage
WriteTag(kNativeImageTag);
gfx::ImageSkia image = native_image->image().AsImageSkia();
std::vector<gfx::ImageSkiaRep> image_reps = image.image_reps();
serializer_.WriteUint32(image_reps.size());
for (const auto& rep : image_reps) {
serializer_.WriteDouble(rep.scale());
const SkBitmap& bitmap = rep.GetBitmap();
std::vector<uint8_t> bytes =
skia::mojom::InlineBitmap::Serialize(&bitmap);
serializer_.WriteUint32(bytes.size());
serializer_.WriteRawBytes(bytes.data(), bytes.size());
}
return v8::Just(true);
} else {
return v8::ValueSerializer::Delegate::WriteHostObject(isolate, object);
}
}
void ThrowDataCloneError(v8::Local<v8::String> message) override {
isolate_->ThrowException(v8::Exception::Error(message));
}
private:
void WriteTag(const uint8_t tag) { serializer_.WriteRawBytes(&tag, 1U); }
void WriteBlinkEnvelope(uint32_t blink_version) {
// Write a dummy blink version envelope for compatibility with
// blink::V8ScriptValueSerializer
WriteTag(kVersionTag);
serializer_.WriteUint32(blink_version);
}
raw_ptr<v8::Isolate> isolate_;
std::vector<uint8_t> data_;
v8::ValueSerializer serializer_;
};
class V8Deserializer : public v8::ValueDeserializer::Delegate {
public:
V8Deserializer(v8::Isolate* isolate, base::span<const uint8_t> data)
: isolate_(isolate),
deserializer_(isolate, data.data(), data.size(), this) {}
V8Deserializer(v8::Isolate* isolate, const blink::CloneableMessage& message)
: V8Deserializer(isolate, message.encoded_message) {}
v8::Local<v8::Value> Deserialize() {
v8::EscapableHandleScope scope(isolate_);
auto context = isolate_->GetCurrentContext();
uint32_t blink_version;
if (!ReadBlinkEnvelope(&blink_version))
return v8::Null(isolate_);
bool read_header;
if (!deserializer_.ReadHeader(context).To(&read_header))
return v8::Null(isolate_);
DCHECK(read_header);
v8::Local<v8::Value> value;
if (!deserializer_.ReadValue(context).ToLocal(&value))
return v8::Null(isolate_);
return scope.Escape(value);
}
v8::MaybeLocal<v8::Object> ReadHostObject(v8::Isolate* isolate) override {
uint8_t tag = 0;
if (!ReadTag(&tag))
return v8::ValueDeserializer::Delegate::ReadHostObject(isolate);
switch (tag) {
case kNativeImageTag:
if (api::NativeImage* native_image = ReadNativeImage(isolate))
return native_image->GetWrapper(isolate);
break;
}
// Throws an exception.
return v8::ValueDeserializer::Delegate::ReadHostObject(isolate);
}
private:
bool ReadTag(uint8_t* tag) {
const void* tag_bytes = nullptr;
if (!deserializer_.ReadRawBytes(1, &tag_bytes))
return false;
*tag = *reinterpret_cast<const uint8_t*>(tag_bytes);
return true;
}
bool ReadBlinkEnvelope(uint32_t* blink_version) {
// Read a dummy blink version envelope for compatibility with
// blink::V8ScriptValueDeserializer
uint8_t tag = 0;
if (!ReadTag(&tag) || tag != kVersionTag)
return false;
if (!deserializer_.ReadUint32(blink_version))
return false;
chore: bump chromium to 109.0.5382.0 (main) (#36057) * chore: bump chromium in DEPS to 109.0.5364.0 * chore: update patches * chore: bump chromium in DEPS to 109.0.5366.0 * chore: update patches * i3940364: Change PermissionType::WINDOW_PLACEMENT to WINDOW_MANAGEMENT https://chromium-review.googlesource.com/c/chromium/src/+/3940364 * 3866812: Change content::PluginList to only run on the UI thread. https://chromium-review.googlesource.com/c/chromium/src/+/3866812 * chore: bump chromium in DEPS to 109.0.5368.0 * [cleanup] Replace enable_basic_printing with enable_printing https://chromium-review.googlesource.com/c/chromium/src/+/3957357 * chore: update patches * 3956318: Desktop PWAs: Retire kWebAppWindowControlsOverlay flag https://chromium-review.googlesource.com/c/chromium/src/+/3956318 * fixup! Change content::PluginList to only run on the UI thread. (cherry picked from commit 7b5ec87d4ff5d34e7493b4fb46c40c0afeef2005) Co-Authored-By: Robo <hop2deep@gmail.com> * chore: bump chromium in DEPS to 109.0.5370.0 * 3956299: Quota: Cleanup QuotaPermissionContext https://chromium-review.googlesource.com/c/chromium/src/+/3956299 * chore: update patches * 3803867: Add Mojo interface to parse XML for OOP printer capabilities https://chromium-review.googlesource.com/c/chromium/src/+/3803867 * fixup: Add Mojo interface to parse XML for OOP printer capabilities * chore: bump chromium in DEPS to 109.0.5372.0 * chore: update patches * chore: bump chromium in DEPS to 109.0.5374.0 * chore: bump chromium in DEPS to 109.0.5376.0 * chore: bump chromium in DEPS to 109.0.5378.0 * chore: update patches * Quota: Cleanup kPersistent in BrowsingDataRemover https://chromium-review.googlesource.com/c/chromium/src/+/3964859 * 3955976: serial: Create DOMException with V8ThrowDOMException https://chromium-review.googlesource.com/c/chromium/src/+/3955976 * 3758405: Append trailer data to serialized messages. https://chromium-review.googlesource.com/c/chromium/src/+/3758405 * chore: revert clang roll This patch reverts https://chromium-review.googlesource.com/c/chromium/src/+/3967491 because that roll breaks the WOA build: https://crbug.com/1377819 * chore: update patches * chore: bump chromium in DEPS to 109.0.5380.0 * chore: update patches * 3859750: [linux/wayland] Added plumbing for the state of tiled edges. https://chromium-review.googlesource.com/c/chromium/src/+/3859750 Also 3970920: [linux/wayland] Fixed the tiled edges for the GTK frame. https://chromium-review.googlesource.com/c/chromium/src/+/3970920 * chore: bump chromium in DEPS to 109.0.5382.0 * chore: update patches * chore: revert Use accessibility.pkey when setting page access. https://chromium-review.googlesource.com/c/chromium/src/+/3949281 breaks our Linux builds run under Docker. This patch should be removed once https://chromium-review.googlesource.com/c/chromium/src/+/3949284 is merged. * 3976312: Roll clang llvmorg-16-init-8189-g97196a2d-2 : llvmorg-16-init-8697-g60809cd2-1 https://chromium-review.googlesource.com/c/chromium/src/+/3976312 * 3967841: [heap] Remove AllocationSpace::MAP_SPACE enum constant https://chromium-review.googlesource.com/c/v8/v8/+/3967841 * 3956131: [cleanup] Remove flag for Wasm threads & atomics https://chromium-review.googlesource.com/c/v8/v8/+/3956131 * chore: update docs for Quota: Cleanup kPersistent in BrowsingDataRemover https://chromium-review.googlesource.com/c/chromium/src/+/3964859 * test: fixup HID test for ARM CI Co-authored-by: electron-roller[bot] <84116207+electron-roller[bot]@users.noreply.github.com> Co-authored-by: John Kleinschmidt <jkleinsc@github.com> Co-authored-by: PatchUp <73610968+patchup[bot]@users.noreply.github.com> Co-authored-by: John Kleinschmidt <jkleinsc@electronjs.org> Co-authored-by: Jeremy Rose <jeremya@chromium.org> Co-authored-by: electron-patch-conflict-fixer[bot] <83340002+electron-patch-conflict-fixer[bot]@users.noreply.github.com>
2022-10-27 12:37:04 -04:00
static constexpr uint32_t kMinWireFormatVersionWithTrailer = 21;
if (*blink_version >= kMinWireFormatVersionWithTrailer) {
// In these versions, we expect kTrailerOffsetTag (0xFE) followed by an
// offset and size. See details in
// third_party/blink/renderer/core/v8/serialization/serialization_tag.h.
uint8_t trailer_offset_tag = 0;
if (!ReadTag(&trailer_offset_tag) ||
trailer_offset_tag != kTrailerOffsetTag)
return false;
const void* trailer_offset_and_size_bytes = nullptr;
static constexpr size_t kTrailerOffsetDataSize =
sizeof(uint64_t) + sizeof(uint32_t);
if (!deserializer_.ReadRawBytes(kTrailerOffsetDataSize,
&trailer_offset_and_size_bytes))
return false;
}
return true;
}
api::NativeImage* ReadNativeImage(v8::Isolate* isolate) {
gfx::ImageSkia image_skia;
uint32_t num_reps = 0;
if (!deserializer_.ReadUint32(&num_reps))
return nullptr;
for (uint32_t i = 0; i < num_reps; i++) {
double scale = 0.0;
if (!deserializer_.ReadDouble(&scale))
return nullptr;
uint32_t bitmap_size_bytes = 0;
if (!deserializer_.ReadUint32(&bitmap_size_bytes))
return nullptr;
const void* bitmap_data = nullptr;
if (!deserializer_.ReadRawBytes(bitmap_size_bytes, &bitmap_data))
return nullptr;
SkBitmap bitmap;
if (!skia::mojom::InlineBitmap::Deserialize(bitmap_data,
bitmap_size_bytes, &bitmap))
return nullptr;
image_skia.AddRepresentation(gfx::ImageSkiaRep(bitmap, scale));
}
gfx::Image image(image_skia);
return new api::NativeImage(isolate, image);
}
raw_ptr<v8::Isolate> isolate_;
v8::ValueDeserializer deserializer_;
};
bool SerializeV8Value(v8::Isolate* isolate,
v8::Local<v8::Value> value,
blink::CloneableMessage* out) {
return V8Serializer(isolate).Serialize(value, out);
}
v8::Local<v8::Value> DeserializeV8Value(v8::Isolate* isolate,
const blink::CloneableMessage& in) {
return V8Deserializer(isolate, in).Deserialize();
}
v8::Local<v8::Value> DeserializeV8Value(v8::Isolate* isolate,
base::span<const uint8_t> data) {
return V8Deserializer(isolate, data).Deserialize();
}
namespace util {
/**
* SAFETY: There is not yet any v8::ArrayBufferView API that passes the
* UNSAFE_BUFFER_USAGE test, so let's isolate the unsafe API here.
*
* Where possible, Electron should use spans returned here instead of
* |v8::ArrayBufferView::Buffer()->Data()|,
* |v8::ArrayBufferView::ByteOffset()|,
* |v8::ArrayBufferView::ByteLength()|.
*/
base::span<uint8_t> as_byte_span(v8::Local<v8::ArrayBufferView> val) {
uint8_t* data = UNSAFE_BUFFERS(static_cast<uint8_t*>(val->Buffer()->Data()) +
val->ByteOffset());
const size_t size = val->ByteLength();
return UNSAFE_BUFFERS(base::span{data, size});
}
} // namespace util
} // namespace electron