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 #23543

Merged
merged 4 commits into from May 18, 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
17 changes: 17 additions & 0 deletions lib/browser/remote/server.ts
Expand Up @@ -5,6 +5,7 @@ import { EventEmitter } from 'events';
import objectsRegistry from './objects-registry';
import { ipcMainInternal } from '../ipc-main-internal';
import { isPromise, isSerializableObject } from '@electron/internal/common/type-utils';
import { Size } from 'electron/main';

const v8Util = process.electronBinding('v8_util');
const eventBinding = process.electronBinding('event');
Expand Down Expand Up @@ -242,6 +243,9 @@ type MetaTypeFromRenderer = {
id: number,
location: string,
length: number
} | {
type: 'nativeimage',
value: { size: Size, buffer: Buffer, scaleFactor: number }[]
}

const fakeConstructor = (constructor: Function, name: string) =>
Expand All @@ -259,6 +263,19 @@ const fakeConstructor = (constructor: Function, name: string) =>
const unwrapArgs = function (sender: electron.WebContents, frameId: number, contextId: string, args: any[]) {
const metaToValue = function (meta: MetaTypeFromRenderer): any {
switch (meta.type) {
case 'nativeimage': {
const image = electron.nativeImage.createEmpty();
for (const rep of meta.value) {
const { buffer, size, scaleFactor } = rep;
image.addRepresentation({
buffer,
width: size.width,
height: size.height,
scaleFactor
});
}
return image;
}
case 'value':
return meta.value;
case 'remote-object':
Expand Down
11 changes: 10 additions & 1 deletion lib/renderer/api/remote.js
Expand Up @@ -2,6 +2,7 @@

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

const { CallbacksRegistry } = require('@electron/internal/renderer/remote/callbacks-registry');
const { isPromise, isSerializableObject } = require('@electron/internal/common/type-utils');
Expand Down Expand Up @@ -35,7 +36,15 @@ function wrapArgs (args, visited = new Set()) {
};
}

if (Array.isArray(value)) {
if (value instanceof NativeImage) {
const images = [];
for (const scaleFactor of value.getScaleFactors()) {
const size = value.getSize(scaleFactor);
const buffer = value.toBitmap({ scaleFactor });
images.push({ buffer, scaleFactor, size });
}
return { type: 'nativeimage', value: images };
} else if (Array.isArray(value)) {
visited.add(value);
const meta = {
type: 'array',
Expand Down
33 changes: 23 additions & 10 deletions shell/common/api/electron_api_native_image.cc
Expand Up @@ -248,21 +248,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());
}

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

float NativeImage::GetAspectRatio() {
gfx::Size size = GetSize();
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());
}

gin::Handle<NativeImage> NativeImage::Resize(v8::Isolate* isolate,
gin::Handle<NativeImage> NativeImage::Resize(gin::Arguments* args,
base::DictionaryValue options) {
gfx::Size size = GetSize();
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 @@ -272,11 +283,12 @@ gin::Handle<NativeImage> NativeImage::Resize(v8::Isolate* isolate,
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 @@ -290,8 +302,8 @@ gin::Handle<NativeImage> NativeImage::Resize(v8::Isolate* isolate,

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

gin::Handle<NativeImage> NativeImage::Crop(v8::Isolate* isolate,
Expand Down Expand Up @@ -505,6 +517,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/electron_api_native_image.h
Expand Up @@ -7,6 +7,7 @@

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

#include "base/values.h"
#include "gin/handle.h"
Expand Down Expand Up @@ -84,15 +85,16 @@ class NativeImage : public gin_helper::Wrappable<NativeImage> {
v8::Local<v8::Value> ToPNG(gin::Arguments* args);
v8::Local<v8::Value> ToJPEG(v8::Isolate* isolate, int quality);
v8::Local<v8::Value> ToBitmap(gin::Arguments* args);
std::vector<float> GetScaleFactors();
v8::Local<v8::Value> GetBitmap(gin::Arguments* args);
v8::Local<v8::Value> GetNativeHandle(gin_helper::ErrorThrower thrower);
gin::Handle<NativeImage> Resize(v8::Isolate* isolate,
gin::Handle<NativeImage> Resize(gin::Arguments* args,
base::DictionaryValue options);
gin::Handle<NativeImage> Crop(v8::Isolate* isolate, const gfx::Rect& rect);
std::string ToDataURL(gin::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 gin_helper::Dictionary& options);

// Mark the image as template image.
Expand Down
45 changes: 45 additions & 0 deletions spec-main/api-remote-spec.ts
Expand Up @@ -5,6 +5,7 @@ import { ifdescribe } from './spec-helpers';

import { ipcMain, BrowserWindow } from 'electron/main';
import { emittedOnce } from './events-helpers';
import { NativeImage } from 'electron/common';

const features = process.electronBinding('features');

Expand Down Expand Up @@ -222,6 +223,50 @@ ifdescribe(features.isRemoteModuleEnabled())('remote module', () => {
});
});

describe('nativeImage serialization', () => {
const w = makeWindow();
const remotely = makeRemotely(w);

it('can serialize an empty nativeImage', async () => {
const getEmptyImage = (img: NativeImage) => img.isEmpty();

w().webContents.once('remote-get-global', (event) => {
event.returnValue = getEmptyImage;
});

await expect(remotely(() => {
const emptyImage = require('electron').nativeImage.createEmpty();
return require('electron').remote.getGlobal('someFunction')(emptyImage);
})).to.eventually.be.true();
});

it('can serialize a non-empty nativeImage', async () => {
const getNonEmptyImage = (img: NativeImage) => img.getSize();

w().webContents.once('remote-get-global', (event) => {
event.returnValue = getNonEmptyImage;
});

await expect(remotely(() => {
const { nativeImage } = require('electron');
const nonEmptyImage = nativeImage.createFromDataURL('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAIAAAD91JpzAAAAFklEQVQYlWP8//8/AwMDEwMDAwMDAwAkBgMBBMzldwAAAABJRU5ErkJggg==');
return require('electron').remote.getGlobal('someFunction')(nonEmptyImage);
})).to.eventually.deep.equal({ width: 2, height: 2 });
});

it('can properly create a menu with an nativeImage icon in the renderer', async () => {
await expect(remotely(() => {
const { remote, nativeImage } = require('electron');
remote.Menu.buildFromTemplate([
{
label: 'hello',
icon: nativeImage.createEmpty()
}
]);
})).to.be.fulfilled();
});
});

describe('remote listeners', () => {
afterEach(closeAllWindows);

Expand Down