fix: nativeImage.createThumbnailFromPath and shell.openExternal in renderer (#41909)

* fix: nativeImage.createThumbnailFromPath in renderer

Co-authored-by: Jeremy Rose <jeremya@chromium.org>

* also fix shell.openExternal

Co-authored-by: Jeremy Rose <jeremya@chromium.org>

---------

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Jeremy Rose <jeremya@chromium.org>
This commit is contained in:
trop[bot] 2024-04-19 10:56:43 -05:00 committed by GitHub
parent 98c9e20913
commit 91f257044d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 60 additions and 34 deletions

View file

@ -14,6 +14,7 @@
#include "base/apple/foundation_util.h" #include "base/apple/foundation_util.h"
#include "base/strings/sys_string_conversions.h" #include "base/strings/sys_string_conversions.h"
#include "base/task/bind_post_task.h"
#include "gin/arguments.h" #include "gin/arguments.h"
#include "shell/common/gin_converters/image_converter.h" #include "shell/common/gin_converters/image_converter.h"
#include "shell/common/gin_helper/promise.h" #include "shell/common/gin_helper/promise.h"
@ -38,6 +39,23 @@ double safeShift(double in, double def) {
return def; return def;
} }
void ReceivedThumbnailResult(CGSize size,
gin_helper::Promise<gfx::Image> p,
QLThumbnailRepresentation* thumbnail,
NSError* error) {
if (error || !thumbnail) {
std::string err_msg([error.localizedDescription UTF8String]);
p.RejectWithErrorMessage("unable to retrieve thumbnail preview "
"image for the given path: " +
err_msg);
} else {
NSImage* result = [[NSImage alloc] initWithCGImage:[thumbnail CGImage]
size:size];
gfx::Image image(result);
p.Resolve(image);
}
}
// static // static
v8::Local<v8::Promise> NativeImage::CreateThumbnailFromPath( v8::Local<v8::Promise> NativeImage::CreateThumbnailFromPath(
v8::Isolate* isolate, v8::Isolate* isolate,
@ -70,31 +88,15 @@ v8::Local<v8::Promise> NativeImage::CreateThumbnailFromPath(
size:cg_size size:cg_size
scale:[screen backingScaleFactor] scale:[screen backingScaleFactor]
representationTypes:QLThumbnailGenerationRequestRepresentationTypeAll]); representationTypes:QLThumbnailGenerationRequestRepresentationTypeAll]);
__block gin_helper::Promise<gfx::Image> p = std::move(promise); __block auto block_callback = base::BindPostTaskToCurrentDefault(
base::BindOnce(&ReceivedThumbnailResult, cg_size, std::move(promise)));
auto completionHandler =
^(QLThumbnailRepresentation* thumbnail, NSError* error) {
std::move(block_callback).Run(thumbnail, error);
};
[[QLThumbnailGenerator sharedGenerator] [[QLThumbnailGenerator sharedGenerator]
generateBestRepresentationForRequest:request generateBestRepresentationForRequest:request
completionHandler:^( completionHandler:completionHandler];
QLThumbnailRepresentation* thumbnail,
NSError* error) {
if (error || !thumbnail) {
std::string err_msg(
[error.localizedDescription UTF8String]);
dispatch_async(dispatch_get_main_queue(), ^{
p.RejectWithErrorMessage(
"unable to retrieve thumbnail preview "
"image for the given path: " +
err_msg);
});
} else {
NSImage* result = [[NSImage alloc]
initWithCGImage:[thumbnail CGImage]
size:cg_size];
gfx::Image image(result);
dispatch_async(dispatch_get_main_queue(), ^{
p.Resolve(image);
});
}
}];
return handle; return handle;
} }

View file

@ -15,11 +15,15 @@
#include "base/apple/osstatus_logging.h" #include "base/apple/osstatus_logging.h"
#include "base/files/file_path.h" #include "base/files/file_path.h"
#include "base/files/file_util.h" #include "base/files/file_util.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h" #include "base/functional/callback.h"
#include "base/logging.h" #include "base/logging.h"
#include "base/mac/scoped_aedesc.h" #include "base/mac/scoped_aedesc.h"
#include "base/strings/stringprintf.h" #include "base/strings/stringprintf.h"
#include "base/strings/sys_string_conversions.h" #include "base/strings/sys_string_conversions.h"
#include "base/task/thread_pool.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "net/base/apple/url_conversions.h" #include "net/base/apple/url_conversions.h"
#include "ui/views/widget/widget.h" #include "ui/views/widget/widget.h"
#include "url/gurl.h" #include "url/gurl.h"
@ -183,15 +187,12 @@ void OpenExternal(const GURL& url,
return; return;
} }
bool activate = options.activate; base::ThreadPool::PostTaskAndReplyWithResult(
__block OpenCallback c = std::move(callback); FROM_HERE,
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), {base::MayBlock(), base::WithBaseSyncPrimitives(),
^{ base::TaskPriority::USER_BLOCKING,
__block std::string error = OpenURL(ns_url, activate); base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN},
dispatch_async(dispatch_get_main_queue(), ^{ base::BindOnce(&OpenURL, ns_url, options.activate), std::move(callback));
std::move(c).Run(error);
});
});
} }
bool MoveItemToTrashWithError(const base::FilePath& full_path, bool MoveItemToTrashWithError(const base::FilePath& full_path,

View file

@ -1,6 +1,6 @@
import { expect } from 'chai'; import { expect } from 'chai';
import { nativeImage } from 'electron/common'; import { nativeImage } from 'electron/common';
import { ifdescribe, ifit } from './lib/spec-helpers'; import { ifdescribe, ifit, itremote, useRemoteContext } from './lib/spec-helpers';
import * as path from 'node:path'; import * as path from 'node:path';
describe('nativeImage module', () => { describe('nativeImage module', () => {
@ -424,6 +424,8 @@ describe('nativeImage module', () => {
}); });
ifdescribe(process.platform !== 'linux')('createThumbnailFromPath(path, size)', () => { ifdescribe(process.platform !== 'linux')('createThumbnailFromPath(path, size)', () => {
useRemoteContext({ webPreferences: { contextIsolation: false, nodeIntegration: true } });
it('throws when invalid size is passed', async () => { it('throws when invalid size is passed', async () => {
const badSize = { width: -1, height: -1 }; const badSize = { width: -1, height: -1 };
@ -471,6 +473,13 @@ describe('nativeImage module', () => {
const result = await nativeImage.createThumbnailFromPath(imgPath, maxSize); const result = await nativeImage.createThumbnailFromPath(imgPath, maxSize);
expect(result.getSize()).to.deep.equal(maxSize); expect(result.getSize()).to.deep.equal(maxSize);
}); });
itremote('works in the renderer', async (path: string) => {
const { nativeImage } = require('electron');
const goodSize = { width: 100, height: 100 };
const result = await nativeImage.createThumbnailFromPath(path, goodSize);
expect(result.isEmpty()).to.equal(false);
}, [path.join(fixturesPath, 'assets', 'logo.png')]);
}); });
describe('addRepresentation()', () => { describe('addRepresentation()', () => {

View file

@ -31,7 +31,7 @@ describe('shell module', () => {
}); });
afterEach(closeAllWindows); afterEach(closeAllWindows);
it('opens an external link', async () => { async function urlOpened () {
let url = 'http://127.0.0.1'; let url = 'http://127.0.0.1';
let requestReceived: Promise<any>; let requestReceived: Promise<any>;
if (process.platform === 'linux') { if (process.platform === 'linux') {
@ -53,12 +53,26 @@ describe('shell module', () => {
url = (await listen(server)).url; url = (await listen(server)).url;
requestReceived = new Promise<void>(resolve => server.on('connection', () => resolve())); requestReceived = new Promise<void>(resolve => server.on('connection', () => resolve()));
} }
return { url, requestReceived };
}
it('opens an external link', async () => {
const { url, requestReceived } = await urlOpened();
await Promise.all<void>([ await Promise.all<void>([
shell.openExternal(url), shell.openExternal(url),
requestReceived requestReceived
]); ]);
}); });
it('opens an external link in the renderer', async () => {
const { url, requestReceived } = await urlOpened();
const w = new BrowserWindow({ show: false, webPreferences: { sandbox: false, contextIsolation: false, nodeIntegration: true } });
await w.loadURL('about:blank');
await Promise.all<void>([
w.webContents.executeJavaScript(`require("electron").shell.openExternal(${JSON.stringify(url)})`),
requestReceived
]);
});
}); });
describe('shell.trashItem()', () => { describe('shell.trashItem()', () => {