Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: nativeImage remote serialization #24021

Merged
merged 1 commit into from Jun 11, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
11 changes: 9 additions & 2 deletions lib/browser/rpc-server.js
Expand Up @@ -8,6 +8,7 @@ const v8Util = process.electronBinding('v8_util');
const eventBinding = process.electronBinding('event');
const clipboard = process.electronBinding('clipboard');
const features = process.electronBinding('features');
const { NativeImage } = process.electronBinding('native_image');

const { getContentScripts } = require('@electron/internal/browser/chrome-extension');
const { crashReporterInit } = require('@electron/internal/browser/crash-reporter-init');
Expand All @@ -17,7 +18,7 @@ const objectsRegistry = require('@electron/internal/browser/objects-registry');
const guestViewManager = require('@electron/internal/browser/guest-view-manager');
const bufferUtils = require('@electron/internal/common/buffer-utils');
const errorUtils = require('@electron/internal/common/error-utils');
const typeUtils = require('@electron/internal/common/type-utils');
const { deserialize, serialize } = require('@electron/internal/common/type-utils');
const { isPromise } = require('@electron/internal/common/is-promise');

const hasProp = {}.hasOwnProperty;
Expand Down Expand Up @@ -77,6 +78,8 @@ const valueToMeta = function (sender, contextId, value, optimizeSimpleObject = f
meta.type = 'buffer';
} else if (Array.isArray(value)) {
meta.type = 'array';
} else if (value instanceof NativeImage) {
meta.type = 'nativeimage';
} else if (value instanceof Error) {
meta.type = 'error';
} else if (value instanceof Date) {
Expand All @@ -95,6 +98,8 @@ const valueToMeta = function (sender, contextId, value, optimizeSimpleObject = f
// Fill the meta object according to value's type.
if (meta.type === 'array') {
meta.members = value.map((el) => valueToMeta(sender, contextId, el, optimizeSimpleObject));
} else if (meta.type === 'nativeimage') {
meta.value = serialize(value);
} else if (meta.type === 'object' || meta.type === 'function') {
meta.name = value.constructor ? value.constructor.name : '';

Expand Down Expand Up @@ -181,6 +186,8 @@ const removeRemoteListenersAndLogWarning = (sender, callIntoRenderer) => {
const unwrapArgs = function (sender, frameId, contextId, args) {
const metaToValue = function (meta) {
switch (meta.type) {
case 'nativeimage':
return deserialize(meta.value);
case 'value':
return meta.value;
case 'remote-object':
Expand Down Expand Up @@ -496,7 +503,7 @@ ipcMainUtils.handle('ELECTRON_BROWSER_CLIPBOARD', function (event, method, ...ar
throw new Error(`Invalid method: ${method}`);
}

return typeUtils.serialize(electron.clipboard[method](...typeUtils.deserialize(args)));
return serialize(electron.clipboard[method](...deserialize(args)));
});

if (features.isDesktopCapturerEnabled()) {
Expand Down
53 changes: 47 additions & 6 deletions lib/common/type-utils.ts
Expand Up @@ -6,13 +6,54 @@ const objectMap = function (source: Object, mapper: (value: any) => any) {
return Object.fromEntries(targetEntries);
};

function serializeNativeImage (image: any) {
const representations = [];
const scaleFactors = image.getScaleFactors();

// Use Buffer when there's only one representation for better perf.
// This avoids compressing to/from PNG where it's not necessary to
// ensure uniqueness of dataURLs (since there's only one).
if (scaleFactors.length === 1) {
const scaleFactor = scaleFactors[0];
const size = image.getSize(scaleFactor);
const buffer = image.toBitmap({ scaleFactor });
representations.push({ scaleFactor, size, buffer });
} else {
// Construct from dataURLs to ensure that they are not lost in creation.
for (const scaleFactor of scaleFactors) {
const size = image.getSize(scaleFactor);
const dataURL = image.toDataURL({ scaleFactor });
representations.push({ scaleFactor, size, dataURL });
}
}
return { __ELECTRON_SERIALIZED_NativeImage__: true, representations };
}

function deserializeNativeImage (value: any) {
const image = nativeImage.createEmpty();

// Use Buffer when there's only one representation for better perf.
// This avoids compressing to/from PNG where it's not necessary to
// ensure uniqueness of dataURLs (since there's only one).
if (value.representations.length === 1) {
const { buffer, size, scaleFactor } = value.representations[0];
const { width, height } = size;
image.addRepresentation({ buffer, scaleFactor, width, height });
} else {
// Construct from dataURLs to ensure that they are not lost in creation.
for (const rep of value.representations) {
const { dataURL, size, scaleFactor } = rep;
const { width, height } = size;
image.addRepresentation({ dataURL, scaleFactor, width, height });
}
}

return image;
}

export function serialize (value: any): any {
if (value instanceof NativeImage) {
return {
buffer: value.toBitmap(),
size: value.getSize(),
__ELECTRON_SERIALIZED_NativeImage__: true
};
return serializeNativeImage(value);
} else if (Array.isArray(value)) {
return value.map(serialize);
} else if (value instanceof Buffer) {
Expand All @@ -26,7 +67,7 @@ export function serialize (value: any): any {

export function deserialize (value: any): any {
if (value && value.__ELECTRON_SERIALIZED_NativeImage__) {
return nativeImage.createFromBitmap(value.buffer, value.size);
return deserializeNativeImage(value);
} else if (Array.isArray(value)) {
return value.map(deserialize);
} else if (value instanceof Buffer) {
Expand Down
7 changes: 6 additions & 1 deletion lib/renderer/api/remote.js
@@ -1,10 +1,12 @@
'use strict';

const v8Util = process.electronBinding('v8_util');
const { NativeImage } = process.electronBinding('native_image');

const { CallbacksRegistry } = require('@electron/internal/renderer/callbacks-registry');
const bufferUtils = require('@electron/internal/common/buffer-utils');
const errorUtils = require('@electron/internal/common/error-utils');
const { deserialize, serialize } = require('@electron/internal/common/type-utils');
const { isPromise } = require('@electron/internal/common/is-promise');
const { ipcRendererInternal } = require('@electron/internal/renderer/ipc-renderer-internal');

Expand Down Expand Up @@ -34,7 +36,9 @@ function wrapArgs (args, visited = new Set()) {
};
}

if (Array.isArray(value)) {
if (value instanceof NativeImage) {
return { type: 'nativeimage', value: serialize(value) };
} else if (Array.isArray(value)) {
visited.add(value);
const meta = {
type: 'array',
Expand Down Expand Up @@ -214,6 +218,7 @@ function metaToValue (meta) {
const types = {
value: () => meta.value,
array: () => meta.members.map((member) => metaToValue(member)),
nativeimage: () => deserialize(meta.value),
buffer: () => bufferUtils.metaToBuffer(meta.value),
promise: () => Promise.resolve({ then: metaToValue(meta.then) }),
error: () => metaToPlainObject(meta),
Expand Down
13 changes: 13 additions & 0 deletions native_mate/native_mate/function_template.h
Expand Up @@ -9,6 +9,7 @@

#include "base/callback.h"
#include "base/logging.h"
#include "base/optional.h"
#include "native_mate/arguments.h"
#include "native_mate/wrappable_base.h"
#include "v8/include/v8.h"
Expand Down Expand Up @@ -104,6 +105,18 @@ bool GetNextArgument(Arguments* args,
}
}

// Support base::Optional as an argument.
template <typename T>
bool GetNextArgument(Arguments* args,
int create_flags,
bool is_first,
base::Optional<T>* result) {
T converted;
if (args->GetNext(&converted))
result->emplace(std::move(converted));
return true;
}

// For advanced use cases, we allow callers to request the unparsed Arguments
// object and poke around in it directly.
inline bool GetNextArgument(Arguments* args,
Expand Down
32 changes: 22 additions & 10 deletions shell/common/api/atom_api_native_image.cc
Expand Up @@ -246,22 +246,32 @@ bool NativeImage::IsEmpty() {
return image_.IsEmpty();
}

gfx::Size NativeImage::GetSize() {
return image_.Size();
gfx::Size NativeImage::GetSize(const base::Optional<float> scale_factor) {
float sf = scale_factor.value_or(1.0f);
gfx::ImageSkiaRep image_rep = image_.AsImageSkia().GetRepresentation(sf);
return gfx::Size(image_rep.GetWidth(), image_rep.GetHeight());
}

float NativeImage::GetAspectRatio() {
gfx::Size size = GetSize();
std::vector<float> NativeImage::GetScaleFactors() {
gfx::ImageSkia image_skia = image_.AsImageSkia();
return image_skia.GetSupportedScales();
}

float NativeImage::GetAspectRatio(const base::Optional<float> scale_factor) {
float sf = scale_factor.value_or(1.0f);
gfx::Size size = GetSize(sf);
if (size.IsEmpty())
return 1.f;
else
return static_cast<float>(size.width()) / static_cast<float>(size.height());
}

mate::Handle<NativeImage> NativeImage::Resize(
v8::Isolate* isolate,
mate::Arguments* args,
const base::DictionaryValue& options) {
gfx::Size size = GetSize();
const float scale_factor = GetScaleFactorFromOptions(args);

gfx::Size size = GetSize(scale_factor);
int width = size.width();
int height = size.height();
bool width_set = options.GetInteger("width", &width);
Expand All @@ -271,11 +281,12 @@ mate::Handle<NativeImage> NativeImage::Resize(
if (width_set && !height_set) {
// Scale height to preserve original aspect ratio
size.set_height(width);
size = gfx::ScaleToRoundedSize(size, 1.f, 1.f / GetAspectRatio());
size =
gfx::ScaleToRoundedSize(size, 1.f, 1.f / GetAspectRatio(scale_factor));
} else if (height_set && !width_set) {
// Scale width to preserve original aspect ratio
size.set_width(height);
size = gfx::ScaleToRoundedSize(size, GetAspectRatio(), 1.f);
size = gfx::ScaleToRoundedSize(size, GetAspectRatio(scale_factor), 1.f);
}

skia::ImageOperations::ResizeMethod method =
Expand All @@ -289,8 +300,8 @@ mate::Handle<NativeImage> NativeImage::Resize(

gfx::ImageSkia resized = gfx::ImageSkiaOperations::CreateResizedImage(
image_.AsImageSkia(), method, size);
return mate::CreateHandle(isolate,
new NativeImage(isolate, gfx::Image(resized)));
return mate::CreateHandle(
args->isolate(), new NativeImage(args->isolate(), gfx::Image(resized)));
}

mate::Handle<NativeImage> NativeImage::Crop(v8::Isolate* isolate,
Expand Down Expand Up @@ -504,6 +515,7 @@ void NativeImage::BuildPrototype(v8::Isolate* isolate,
.SetMethod("toJPEG", &NativeImage::ToJPEG)
.SetMethod("toBitmap", &NativeImage::ToBitmap)
.SetMethod("getBitmap", &NativeImage::GetBitmap)
.SetMethod("getScaleFactors", &NativeImage::GetScaleFactors)
.SetMethod("getNativeHandle", &NativeImage::GetNativeHandle)
.SetMethod("toDataURL", &NativeImage::ToDataURL)
.SetMethod("isEmpty", &NativeImage::IsEmpty)
Expand Down
8 changes: 5 additions & 3 deletions shell/common/api/atom_api_native_image.h
Expand Up @@ -7,6 +7,7 @@

#include <map>
#include <string>
#include <vector>

#include "base/values.h"
#include "native_mate/dictionary.h"
Expand Down Expand Up @@ -84,16 +85,17 @@ class NativeImage : public mate::Wrappable<NativeImage> {
v8::Local<v8::Value> ToPNG(mate::Arguments* args);
v8::Local<v8::Value> ToJPEG(v8::Isolate* isolate, int quality);
v8::Local<v8::Value> ToBitmap(mate::Arguments* args);
std::vector<float> GetScaleFactors();
v8::Local<v8::Value> GetBitmap(mate::Arguments* args);
v8::Local<v8::Value> GetNativeHandle(v8::Isolate* isolate,
mate::Arguments* args);
mate::Handle<NativeImage> Resize(v8::Isolate* isolate,
mate::Handle<NativeImage> Resize(mate::Arguments* args,
const base::DictionaryValue& options);
mate::Handle<NativeImage> Crop(v8::Isolate* isolate, const gfx::Rect& rect);
std::string ToDataURL(mate::Arguments* args);
bool IsEmpty();
gfx::Size GetSize();
float GetAspectRatio();
gfx::Size GetSize(const base::Optional<float> scale_factor);
float GetAspectRatio(const base::Optional<float> scale_factor);
void AddRepresentation(const mate::Dictionary& options);

// Mark the image as template image.
Expand Down