Skip to content

Commit

Permalink
fix: nativeImage remote serialization (#24021)
Browse files Browse the repository at this point in the history
Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
  • Loading branch information
ckerr and codebytere committed Jun 11, 2020
1 parent c2485e1 commit 4fb7b33
Show file tree
Hide file tree
Showing 9 changed files with 238 additions and 23 deletions.
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

0 comments on commit 4fb7b33

Please sign in to comment.