Compare commits

..

18 Commits

Author SHA1 Message Date
Electron Bot
0c2150a6fa Bump v14.0.0-nightly.20210406 2021-04-06 07:33:22 -07:00
Jeremy Rose
62b38812b6 fix: invoke the window open handler for _blank links (#28498)
* fix: invoke the window open handler for _blank links

* add test
2021-04-06 01:04:14 -07:00
Shelley Vohr
3ed8da0931 fix: dialog DCHECK on Linux (#28478) 2021-04-05 23:54:58 -07:00
Samuel Attard
e2f49edf83 build: ignore ninja summary print failures (#28514) 2021-04-05 20:22:25 -04:00
Jeremy Rose
1fcd6e2740 docs: Remove GoDaddy from Windows code signing options (#28494) 2021-04-05 17:13:29 -07:00
Michaela Laurencin
19d7a6b761 fix: enable system maximization for frameless windows except if transparent (#28207)
* fix: move widget maximization check

* fix linting error

* change workaround to only effect transparent windows

* disable menu maximize and restore for transparent windows

* disable double clicking title bar max/unmax for transparent windows

* add docs change and address review
2021-04-06 08:53:59 +09:00
SushiJackal
82ea8ea68c fix: reject task append to JumpList when description exceeds 260 characters (#28485)
* fix: reject task when description exceeds 260 characters

* Switched out wcslen() for size() [linear -> constant time]

* Included comment describing the need for the additional check

* Added information about character limit to documentation

* Added newline character to end of jump-list-category.md
2021-04-06 08:50:39 +09:00
Samuel Attard
c280d770dc docs: the minimum supported version of macOS is now 10.11 (#28480)
* chore: the minimum supported version is now 10.11

Chromium bumped this version back in December

* Update support.md
2021-04-05 12:18:12 -07:00
Electron Bot
29603bcc27 Bump v14.0.0-nightly.20210405 2021-04-05 07:33:32 -07:00
Milan Burda
55c66e3e92 chore: add types for electron_renderer_web_frame binding (#28455)
* chore: add types for electron_renderer_web_frame binding

* chore: use keyof for getWebPreference type

Co-authored-by: Samuel Attard <sattard@slack-corp.com>
2021-04-02 14:34:28 -07:00
Electron Bot
9904438118 Bump v14.0.0-nightly.20210402 2021-04-02 07:33:46 -07:00
Anna Henningsen
e6aefed0ee fix: free IsolateData in ~NodeEnvironment (#28469)
This seems to just have been missing here, leaking memory
(and breaking the API contract for Node.js embedding).
2021-04-01 16:46:11 -07:00
Tristan Partin
fa65faa4b0 feat: Use GtkFileChooserNative to support the XDG Desktop Portal specification (#19159)
* feat: Use GtkFileChooserNative if available to support XDG portals

With this commit, users on KDE/plasma will finally have support in
Electron for their native file choosers dialogs.

* fix: namespace

* fix: labels were reversed

* fix: lint issue

* fix: clean up some implementation

* fix: remove deprecation branch

* fix: remove unused header

* fix: remove unused gi18n.h include

Not sure why this is

* fix: add the set_data call into the mirrored SetGtkTransientForAura func

* fix: remove gmodule support and use native for the dialog regardless

* fix: undo yarn.lock changes

* fix: lint

* fix: remove x11 unncessary x11 include

* fix: lint

* fix: remove SetGtkTransientForAura

* Revert "fix: remove gmodule support and use native for the dialog regardless"

This reverts commit 062db5951e59cf99fcce566ab8ebab7ddc031aeb.

* fix: add support in a backwards compatible way

Use GModule to dynamically load functions from libgtk in order to
support GtkNativeDialog.

* fix: lint

* docs: update comment

* Revert "fix: remove x11 unncessary x11 include"

This reverts commit 589cff583add458c25ca5a2202232fdff916c673.

* fix: compiler errors

* fix: int -> x11::time

* fix: move GtkNativeDialog static data to global state

* fix: revert yarn.lock change

* update: for code review comments

* fix: remove functional header

* fix: variable name

* fix: rename GTK native initalization func

* Help out the compiler

* Help out the compiler

* Help out the compiler

* Fix function signature

* Remove unused header

* Rename optional boolean for GtkFileChooserNative support

* Add back in USE_X11 check

* Satisfy linter

* Resatisfy linter

* Fix alignment of if

* Fix alignment of arguments

* linting...

* fix: add back in the i18n hack

* fix: lint

* Respond to some review comments

* fix: lint

* Make adding filter agnostic

* fix: transform is in place

* fix: remove std::transform because not c++17

* Remove unused include

* fix: address Cheng's review

* fix: Remove unused header
2021-04-02 08:29:27 +09:00
Samuel Attard
e323bfe661 refactor: remove unneeded patch_count variable (#28467) 2021-04-02 08:27:35 +09:00
Electron Bot
976222b509 Bump v14.0.0-nightly.20210401 2021-04-01 07:33:12 -07:00
Michaela Laurencin
2a55ae4b85 docs: add frameName note for window.open (#28435) 2021-03-31 13:57:39 -07:00
dependabot[bot]
05d164e660 build(deps): bump y18n from 5.0.1 to 5.0.5 in /spec (#28445)
Bumps [y18n](https://github.com/yargs/y18n) from 5.0.1 to 5.0.5.
- [Release notes](https://github.com/yargs/y18n/releases)
- [Changelog](https://github.com/yargs/y18n/blob/master/CHANGELOG.md)
- [Commits](https://github.com/yargs/y18n/compare/v5.0.1...v5.0.5)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-03-31 13:57:18 -07:00
Samuel Attard
ba3b2189ad build: fix unicode patch file comparison in git.py (#28454)
This caused some patches to fail incorrectly as the patch file included
non-ascii characters, we have to manually convert using the utf8 charset
2021-03-31 13:48:21 -07:00
24 changed files with 386 additions and 137 deletions

View File

@@ -932,7 +932,7 @@ step-ninja-summary: &step-ninja-summary
command: |
set +e
set +o pipefail
python depot_tools/post_build_ninja_summary.py -C src/out/Default
python depot_tools/post_build_ninja_summary.py -C src/out/Default || echo Ninja Summary Failed
step-ninja-report: &step-ninja-report
store_artifacts:

View File

@@ -1 +1 @@
14.0.0-nightly.20210331
14.0.0-nightly.20210406

View File

@@ -871,6 +871,10 @@ re-add a removed item to a custom category earlier than that will result in the
entire custom category being omitted from the Jump List. The list of removed
items can be obtained using `app.getJumpListSettings()`.
**Note:** The maximum length of a Jump List item's `description` property is
260 characters. Beyond this limit, the item will not be added to the Jump
List, nor will it be displayed.
Here's a very simple example of creating a custom Jump List:
```javascript

View File

@@ -83,8 +83,10 @@ win.show()
blur effect to the content below the window (i.e. other applications open on
the user's system).
* The window will not be transparent when DevTools is opened.
* On Windows operating systems, transparent windows will not work when DWM is
* On Windows operating systems,
* transparent windows will not work when DWM is
disabled.
* transparent windows can not be maximized using the Windows system menu or by double clicking the title bar. The reasoning behind this can be seen on [this pull request](https://github.com/electron/electron/pull/28207).
* On Linux, users have to put `--enable-transparent-visuals --disable-gpu` in
the command line to disable GPU and allow ARGB to make transparent window,
this is caused by an upstream bug that [alpha channel doesn't work on some

View File

@@ -19,3 +19,7 @@
property set then its `type` is assumed to be `tasks`. If the `name` property
is set but the `type` property is omitted then the `type` is assumed to be
`custom`.
**Note:** The maximum length of a Jump List item's `description` property is
260 characters. Beyond this limit, the item will not be added to the Jump
List, nor will it be displayed.

View File

@@ -17,7 +17,7 @@
* `title` String (optional) - The text to be displayed for the item in the Jump List.
Should only be set if `type` is `task`.
* `description` String (optional) - Description of the task (displayed in a tooltip).
Should only be set if `type` is `task`.
Should only be set if `type` is `task`. Maximum length 260 characters.
* `iconPath` String (optional) - The absolute path to an icon to be displayed in a
Jump List, which can be an arbitrary resource file that contains an icon
(e.g. `.ico`, `.exe`, `.dll`). You can usually specify `process.execPath` to

View File

@@ -65,6 +65,7 @@ window.open('https://github.com', '_blank', 'top=500,left=200,frame=false,nodeIn
* Non-standard features (that are not handled by Chromium or Electron) given in
`features` will be passed to any registered `webContents`'s
`did-create-window` event handler in the `additionalFeatures` argument.
* `frameName` follows the specification of `windowName` located in the [native documentation](https://developer.mozilla.org/en-US/docs/Web/API/Window/open#parameters).
To customize or cancel the creation of the window, you can optionally set an
override handler with `webContents.setWindowOpenHandler()` from the main

View File

@@ -190,7 +190,6 @@ it may be worth your time to shop around. Popular resellers include:
* [digicert](https://www.digicert.com/code-signing/microsoft-authenticode.htm)
* [Sectigo](https://sectigo.com/ssl-certificates-tls/code-signing)
* [GoDaddy](https://au.godaddy.com/web-security/code-signing-certificate)
* Amongst others, please shop around to find one that suits your needs, Google
is your friend 😄

View File

@@ -94,7 +94,7 @@ Following platforms are supported by Electron:
### macOS
Only 64bit binaries are provided for macOS, and the minimum macOS version
supported is macOS 10.10 (Yosemite).
supported is macOS 10.11 (El Capitan).
Native support for Apple Silicon (`arm64`) devices was added in Electron 11.0.0.

View File

@@ -433,7 +433,11 @@ WebContents.prototype._callWindowOpenHandler = function (event: Electron.Event,
event.preventDefault();
return null;
} else if (response.action === 'allow') {
if (typeof response.overrideBrowserWindowOptions === 'object' && response.overrideBrowserWindowOptions !== null) { return response.overrideBrowserWindowOptions; } else { return {}; }
if (typeof response.overrideBrowserWindowOptions === 'object' && response.overrideBrowserWindowOptions !== null) {
return response.overrideBrowserWindowOptions;
} else {
return {};
}
} else {
event.preventDefault();
console.error('The window open handler response must be an object with an \'action\' property of \'allow\' or \'deny\'.');
@@ -565,19 +569,22 @@ WebContents.prototype._init = function () {
// Make new windows requested by links behave like "window.open".
this.on('-new-window' as any, (event: ElectronInternal.Event, url: string, frameName: string, disposition: string,
rawFeatures: string, referrer: Electron.Referrer, postData: PostData) => {
openGuestWindow({
event,
embedder: event.sender,
disposition,
referrer,
postData,
overrideBrowserWindowOptions: {},
windowOpenArgs: {
url,
frameName,
features: rawFeatures
}
});
const options = this._callWindowOpenHandler(event, url, frameName, rawFeatures);
if (!event.defaultPrevented) {
openGuestWindow({
event,
embedder: event.sender,
disposition,
referrer,
postData,
overrideBrowserWindowOptions: options || {},
windowOpenArgs: {
url,
frameName,
features: rawFeatures
}
});
}
});
let windowOpenOverriddenOptions: BrowserWindowConstructorOptions | null = null;

View File

@@ -42,7 +42,6 @@ export function openGuestWindow ({ event, embedder, guest, referrer, disposition
windowOpenArgs: WindowOpenArgs,
}): BrowserWindow | undefined {
const { url, frameName, features } = windowOpenArgs;
const isNativeWindowOpen = !!guest;
const { options: browserWindowOptions, additionalFeatures } = makeBrowserWindowOptions({
embedder,
features,
@@ -74,11 +73,14 @@ export function openGuestWindow ({ event, embedder, guest, referrer, disposition
webContents: guest,
...browserWindowOptions
});
if (!isNativeWindowOpen) {
if (!guest) {
// We should only call `loadURL` if the webContents was constructed by us in
// the case of BrowserWindowProxy (non-sandboxed, nativeWindowOpen: false),
// as navigating to the url when creating the window from an existing
// webContents is not necessary (it will navigate there anyway).
// This can also happen if we enter this function from OpenURLFromTab, in
// which case the browser process is responsible for initiating navigation
// in the new window.
window.loadURL(url, {
httpReferrer: referrer,
...(postData && {

View File

@@ -11,16 +11,16 @@ class WebFrame extends EventEmitter {
this.setMaxListeners(0);
}
findFrameByRoutingId (...args: Array<any>) {
return getWebFrame(binding._findFrameByRoutingId(this.context, ...args));
findFrameByRoutingId (routingId: number) {
return getWebFrame(binding._findFrameByRoutingId(this.context, routingId));
}
getFrameForSelector (...args: Array<any>) {
return getWebFrame(binding._getFrameForSelector(this.context, ...args));
getFrameForSelector (selector: string) {
return getWebFrame(binding._getFrameForSelector(this.context, selector));
}
findFrameByName (...args: Array<any>) {
return getWebFrame(binding._findFrameByName(this.context, ...args));
findFrameByName (name: string) {
return getWebFrame(binding._findFrameByName(this.context, name));
}
get opener () {
@@ -62,12 +62,12 @@ for (const name in binding) {
if (!worldSafeJS && name.startsWith('executeJavaScript')) {
deprecate.log(`Security Warning: webFrame.${name} was called without worldSafeExecuteJavaScript enabled. This is considered unsafe. worldSafeExecuteJavaScript will be enabled by default in Electron 12.`);
}
return binding[name](this.context, ...args);
return (binding as any)[name](this.context, ...args);
};
// TODO(MarshallOfSound): Remove once the above deprecation is removed
if (name.startsWith('executeJavaScript')) {
(WebFrame as any).prototype[`_${name}`] = function (...args: Array<any>) {
return binding[name](this.context, ...args);
return (binding as any)[name](this.context, ...args);
};
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "electron",
"version": "14.0.0-nightly.20210331",
"version": "14.0.0-nightly.20210406",
"repository": "https://github.com/electron/electron",
"description": "Build cross platform desktop apps with JavaScript, HTML, and CSS",
"devDependencies": {

View File

@@ -306,18 +306,18 @@ def export_patches(repo, out_dir, patch_range=None, dry_run=False):
# If we're doing a dry run, iterate through each patch and see if the newly
# exported patch differs from what exists. Report number of mismatched
# patches and fail if there's more than one.
patch_count = 0
bad_patches = []
for patch in patches:
filename = get_file_name(patch)
filepath = posixpath.join(out_dir, filename)
existing_patch = io.open(filepath, 'rb').read()
existing_patch = unicode(io.open(filepath, 'rb').read(), "utf-8")
formatted_patch = join_patch(patch)
if formatted_patch != existing_patch:
patch_count += 1
if patch_count > 0:
bad_patches.append(filename)
if len(bad_patches) > 0:
sys.stderr.write(
"Patches in {} not up to date: {} patches need update\n".format(
out_dir, patch_count
"Patches in {} not up to date: {} patches need update\n-- {}\n".format(
out_dir, len(bad_patches), "\n-- ".join(bad_patches)
)
)
exit(1)

View File

@@ -363,7 +363,9 @@ void JavascriptEnvironment::OnMessageLoopDestroying() {
NodeEnvironment::NodeEnvironment(node::Environment* env) : env_(env) {}
NodeEnvironment::~NodeEnvironment() {
auto* isolate_data = env_->isolate_data();
node::FreeEnvironment(env_);
node::FreeIsolateData(isolate_data);
}
} // namespace electron

View File

@@ -530,7 +530,7 @@ void NativeWindowViews::Maximize() {
void NativeWindowViews::Unmaximize() {
#if defined(OS_WIN)
if (!(::GetWindowLong(GetAcceleratedWidget(), GWL_STYLE) & WS_THICKFRAME)) {
if (transparent()) {
SetBounds(restore_bounds_, false);
return;
}
@@ -540,21 +540,22 @@ void NativeWindowViews::Unmaximize() {
}
bool NativeWindowViews::IsMaximized() {
// For window without WS_THICKFRAME style, we can not call IsMaximized().
// This path will be used for transparent windows as well.
if (widget()->IsMaximized()) {
return true;
} else {
#if defined(OS_WIN)
if (!(::GetWindowLong(GetAcceleratedWidget(), GWL_STYLE) & WS_THICKFRAME)) {
// Compare the size of the window with the size of the display
auto display = display::Screen::GetScreen()->GetDisplayNearestWindow(
GetNativeWindow());
// Maximized if the window is the same dimensions and placement as the
// display
return GetBounds() == display.work_area();
}
if (transparent()) {
// Compare the size of the window with the size of the display
auto display = display::Screen::GetScreen()->GetDisplayNearestWindow(
GetNativeWindow());
// Maximized if the window is the same dimensions and placement as the
// display
return GetBounds() == display.work_area();
}
#endif
return widget()->IsMaximized();
return false;
}
}
void NativeWindowViews::Minimize() {

View File

@@ -149,8 +149,8 @@ std::set<NativeWindowViews*> NativeWindowViews::forwarding_windows_;
HHOOK NativeWindowViews::mouse_hook_ = NULL;
void NativeWindowViews::Maximize() {
// Only use Maximize() when window has WS_THICKFRAME style
if (::GetWindowLong(GetAcceleratedWidget(), GWL_STYLE) & WS_THICKFRAME) {
// Only use Maximize() when window is NOT transparent style
if (!transparent()) {
if (IsVisible())
widget()->Maximize();
else
@@ -324,8 +324,31 @@ bool NativeWindowViews::PreHandleMSG(UINT message,
GET_Y_LPARAM(l_param), &prevent_default);
return prevent_default;
}
default:
case WM_SYSCOMMAND: {
// Mask is needed to account for double clicking title bar to maximize
WPARAM max_mask = 0xFFF0;
if (transparent() && ((w_param & max_mask) == SC_MAXIMIZE)) {
return true;
}
return false;
}
case WM_INITMENU: {
// This is handling the scenario where the menu might get triggered by the
// user doing "alt + space" resulting in system maximization and restore
// being used on transparent windows when that does not work.
if (transparent()) {
HMENU menu = GetSystemMenu(GetAcceleratedWidget(), false);
EnableMenuItem(menu, SC_MAXIMIZE,
MF_BYCOMMAND | MF_DISABLED | MF_GRAYED);
EnableMenuItem(menu, SC_RESTORE,
MF_BYCOMMAND | MF_DISABLED | MF_GRAYED);
return true;
}
return false;
}
default: {
return false;
}
}
}

View File

@@ -23,7 +23,7 @@
<key>LSApplicationCategoryType</key>
<string>public.app-category.developer-tools</string>
<key>LSMinimumSystemVersion</key>
<string>10.10.0</string>
<string>${MACOSX_DEPLOYMENT_TARGET}</string>
<key>NSMainNibFile</key>
<string>MainMenu</string>
<key>NSPrincipalClass</key>

View File

@@ -50,8 +50,8 @@ END
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 14,0,0,20210331
PRODUCTVERSION 14,0,0,20210331
FILEVERSION 14,0,0,20210406
PRODUCTVERSION 14,0,0,20210406
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L

View File

@@ -2,16 +2,17 @@
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include <memory>
#include <gmodule.h>
#include "shell/browser/ui/file_dialog.h"
#include "shell/browser/ui/gtk_util.h"
#include <memory>
#include "base/callback.h"
#include "base/files/file_util.h"
#include "base/strings/string_util.h"
#include "shell/browser/javascript_environment.h"
#include "shell/browser/native_window_views.h"
#include "shell/browser/ui/file_dialog.h"
#include "shell/browser/ui/gtk_util.h"
#include "shell/browser/unresponsive_suppressor.h"
#include "shell/common/gin_converters/file_path_converter.h"
#include "ui/base/glib/glib_signal.h"
@@ -27,6 +28,27 @@
namespace file_dialog {
static GModule* gtk_module;
static base::Optional<bool> supports_gtk_file_chooser_native;
using dl_gtk_native_dialog_show_t = void (*)(void*);
using dl_gtk_native_dialog_destroy_t = void (*)(void*);
using dl_gtk_native_dialog_set_modal_t = void (*)(void*, gboolean);
using dl_gtk_native_dialog_run_t = int (*)(void*);
using dl_gtk_native_dialog_hide_t = void (*)(void*);
using dl_gtk_file_chooser_native_new_t = void* (*)(const char*,
GtkWindow*,
GtkFileChooserAction,
const char*,
const char*);
static dl_gtk_native_dialog_show_t dl_gtk_native_dialog_show;
static dl_gtk_native_dialog_destroy_t dl_gtk_native_dialog_destroy;
static dl_gtk_native_dialog_set_modal_t dl_gtk_native_dialog_set_modal;
static dl_gtk_native_dialog_run_t dl_gtk_native_dialog_run;
static dl_gtk_native_dialog_hide_t dl_gtk_native_dialog_hide;
static dl_gtk_file_chooser_native_new_t dl_gtk_file_chooser_native_new;
DialogSettings::DialogSettings() = default;
DialogSettings::DialogSettings(const DialogSettings&) = default;
DialogSettings::~DialogSettings() = default;
@@ -36,19 +58,71 @@ namespace {
static const int kPreviewWidth = 256;
static const int kPreviewHeight = 512;
// Makes sure that .jpg also shows .JPG.
gboolean FileFilterCaseInsensitive(const GtkFileFilterInfo* file_info,
std::string* file_extension) {
// Makes .* file extension matches all file types.
if (*file_extension == ".*")
return true;
return base::EndsWith(file_info->filename, *file_extension,
base::CompareCase::INSENSITIVE_ASCII);
}
void InitGtkFileChooserNativeSupport() {
// Return early if we have already setup the native functions or we have tried
// once before and failed. Avoid running expensive dynamic library operations.
if (supports_gtk_file_chooser_native) {
return;
}
// Deletes |data| when gtk_file_filter_add_custom() is done with it.
void OnFileFilterDataDestroyed(std::string* file_extension) {
delete file_extension;
// Mark that we have attempted to initialize support at least once
supports_gtk_file_chooser_native = false;
if (!g_module_supported()) {
return;
}
gtk_module = g_module_open("libgtk-3.so", G_MODULE_BIND_LAZY);
if (!gtk_module) {
return;
}
// Will never be unloaded
g_module_make_resident(gtk_module);
bool found = g_module_symbol(
gtk_module, "gtk_file_chooser_native_new",
reinterpret_cast<void**>(&dl_gtk_file_chooser_native_new));
if (!found) {
return;
}
found = g_module_symbol(
gtk_module, "gtk_native_dialog_set_modal",
reinterpret_cast<void**>(&dl_gtk_native_dialog_set_modal));
if (!found) {
return;
}
found =
g_module_symbol(gtk_module, "gtk_native_dialog_destroy",
reinterpret_cast<void**>(&dl_gtk_native_dialog_destroy));
if (!found) {
return;
}
found = g_module_symbol(gtk_module, "gtk_native_dialog_show",
reinterpret_cast<void**>(&dl_gtk_native_dialog_show));
if (!found) {
return;
}
found = g_module_symbol(gtk_module, "gtk_native_dialog_hide",
reinterpret_cast<void**>(&dl_gtk_native_dialog_hide));
if (!found) {
return;
}
found = g_module_symbol(gtk_module, "gtk_native_dialog_run",
reinterpret_cast<void**>(&dl_gtk_native_dialog_run));
if (!found) {
return;
}
supports_gtk_file_chooser_native =
dl_gtk_file_chooser_native_new && dl_gtk_native_dialog_set_modal &&
dl_gtk_native_dialog_destroy && dl_gtk_native_dialog_run &&
dl_gtk_native_dialog_show && dl_gtk_native_dialog_hide;
}
class FileChooserDialog {
@@ -66,30 +140,43 @@ class FileChooserDialog {
else if (action == GTK_FILE_CHOOSER_ACTION_OPEN)
confirm_text = gtk_util::kOpenLabel;
dialog_ = gtk_file_chooser_dialog_new(
settings.title.c_str(), nullptr, action, gtk_util::kCancelLabel,
GTK_RESPONSE_CANCEL, confirm_text, GTK_RESPONSE_ACCEPT, NULL);
InitGtkFileChooserNativeSupport();
if (*supports_gtk_file_chooser_native) {
dialog_ = GTK_FILE_CHOOSER(
dl_gtk_file_chooser_native_new(settings.title.c_str(), NULL, action,
confirm_text, gtk_util::kCancelLabel));
} else {
dialog_ = GTK_FILE_CHOOSER(gtk_file_chooser_dialog_new(
settings.title.c_str(), NULL, action, gtk_util::kCancelLabel,
GTK_RESPONSE_CANCEL, confirm_text, GTK_RESPONSE_ACCEPT, NULL));
}
if (parent_) {
parent_->SetEnabled(false);
gtk::SetGtkTransientForAura(dialog_, parent_->GetNativeWindow());
gtk_window_set_modal(GTK_WINDOW(dialog_), TRUE);
if (*supports_gtk_file_chooser_native) {
dl_gtk_native_dialog_set_modal(static_cast<void*>(dialog_), TRUE);
} else {
gtk::SetGtkTransientForAura(GTK_WIDGET(dialog_),
parent_->GetNativeWindow());
gtk_window_set_modal(GTK_WINDOW(dialog_), TRUE);
}
}
if (action == GTK_FILE_CHOOSER_ACTION_SAVE)
gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog_),
TRUE);
gtk_file_chooser_set_do_overwrite_confirmation(dialog_, TRUE);
if (action != GTK_FILE_CHOOSER_ACTION_OPEN)
gtk_file_chooser_set_create_folders(GTK_FILE_CHOOSER(dialog_), TRUE);
gtk_file_chooser_set_create_folders(dialog_, TRUE);
if (!settings.default_path.empty()) {
base::ThreadRestrictions::ScopedAllowIO allow_io;
if (base::DirectoryExists(settings.default_path)) {
gtk_file_chooser_set_current_folder(
GTK_FILE_CHOOSER(dialog_), settings.default_path.value().c_str());
dialog_, settings.default_path.value().c_str());
} else {
if (settings.default_path.IsAbsolute()) {
gtk_file_chooser_set_current_folder(
GTK_FILE_CHOOSER(dialog_),
settings.default_path.DirName().value().c_str());
dialog_, settings.default_path.DirName().value().c_str());
}
gtk_file_chooser_set_current_name(
@@ -101,14 +188,25 @@ class FileChooserDialog {
if (!settings.filters.empty())
AddFilters(settings.filters);
preview_ = gtk_image_new();
g_signal_connect(dialog_, "update-preview",
G_CALLBACK(OnUpdatePreviewThunk), this);
gtk_file_chooser_set_preview_widget(GTK_FILE_CHOOSER(dialog_), preview_);
// GtkFileChooserNative does not support preview widgets through the
// org.freedesktop.portal.FileChooser portal. In the case of running through
// the org.freedesktop.portal.FileChooser portal, anything having to do with
// the update-preview signal or the preview widget will just be ignored.
if (!*supports_gtk_file_chooser_native) {
preview_ = gtk_image_new();
g_signal_connect(dialog_, "update-preview",
G_CALLBACK(OnUpdatePreviewThunk), this);
gtk_file_chooser_set_preview_widget(dialog_, preview_);
}
}
~FileChooserDialog() {
gtk_widget_destroy(dialog_);
if (*supports_gtk_file_chooser_native) {
dl_gtk_native_dialog_destroy(static_cast<void*>(dialog_));
} else {
gtk_widget_destroy(GTK_WIDGET(dialog_));
}
if (parent_)
parent_->SetEnabled(true);
}
@@ -117,7 +215,7 @@ class FileChooserDialog {
const auto hasProp = [properties](OpenFileDialogProperty prop) {
return gboolean((properties & prop) != 0);
};
auto* file_chooser = GTK_FILE_CHOOSER(dialog());
auto* file_chooser = dialog();
gtk_file_chooser_set_select_multiple(file_chooser,
hasProp(OPEN_DIALOG_MULTI_SELECTIONS));
gtk_file_chooser_set_show_hidden(file_chooser,
@@ -128,7 +226,7 @@ class FileChooserDialog {
const auto hasProp = [properties](SaveFileDialogProperty prop) {
return gboolean((properties & prop) != 0);
};
auto* file_chooser = GTK_FILE_CHOOSER(dialog());
auto* file_chooser = dialog();
gtk_file_chooser_set_show_hidden(file_chooser,
hasProp(SAVE_DIALOG_SHOW_HIDDEN_FILES));
gtk_file_chooser_set_do_overwrite_confirmation(
@@ -136,21 +234,23 @@ class FileChooserDialog {
}
void RunAsynchronous() {
g_signal_connect(dialog_, "delete-event",
G_CALLBACK(gtk_widget_hide_on_delete), NULL);
g_signal_connect(dialog_, "response", G_CALLBACK(OnFileDialogResponseThunk),
this);
gtk_widget_show_all(dialog_);
if (*supports_gtk_file_chooser_native) {
dl_gtk_native_dialog_show(static_cast<void*>(dialog_));
} else {
gtk_widget_show_all(GTK_WIDGET(dialog_));
#if defined(USE_X11)
if (!features::IsUsingOzonePlatform()) {
// We need to call gtk_window_present after making the widgets visible to
// make sure window gets correctly raised and gets focus.
x11::Time time = ui::X11EventSource::GetInstance()->GetTimestamp();
gtk_window_present_with_time(GTK_WINDOW(dialog_),
static_cast<uint32_t>(time));
}
if (!features::IsUsingOzonePlatform()) {
// We need to call gtk_window_present after making the widgets visible
// to make sure window gets correctly raised and gets focus.
x11::Time time = ui::X11EventSource::GetInstance()->GetTimestamp();
gtk_window_present_with_time(GTK_WINDOW(dialog_),
static_cast<uint32_t>(time));
}
#endif
}
}
void RunSaveAsynchronous(
@@ -170,7 +270,7 @@ class FileChooserDialog {
}
base::FilePath GetFileName() const {
gchar* filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog_));
gchar* filename = gtk_file_chooser_get_filename(dialog_);
const base::FilePath path(filename);
g_free(filename);
return path;
@@ -194,7 +294,7 @@ class FileChooserDialog {
GtkWidget*,
int);
GtkWidget* dialog() const { return dialog_; }
GtkFileChooser* dialog() const { return dialog_; }
private:
void AddFilters(const Filters& filters);
@@ -202,7 +302,7 @@ class FileChooserDialog {
electron::NativeWindowViews* parent_;
electron::UnresponsiveSuppressor unresponsive_suppressor_;
GtkWidget* dialog_;
GtkFileChooser* dialog_;
GtkWidget* preview_;
Filters filters_;
@@ -210,13 +310,17 @@ class FileChooserDialog {
std::unique_ptr<gin_helper::Promise<gin_helper::Dictionary>> open_promise_;
// Callback for when we update the preview for the selection.
CHROMEG_CALLBACK_0(FileChooserDialog, void, OnUpdatePreview, GtkWidget*);
CHROMEG_CALLBACK_0(FileChooserDialog, void, OnUpdatePreview, GtkFileChooser*);
DISALLOW_COPY_AND_ASSIGN(FileChooserDialog);
};
void FileChooserDialog::OnFileDialogResponse(GtkWidget* widget, int response) {
gtk_widget_hide(dialog_);
if (*supports_gtk_file_chooser_native) {
dl_gtk_native_dialog_hide(static_cast<void*>(dialog_));
} else {
gtk_widget_hide(GTK_WIDGET(dialog_));
}
v8::Isolate* isolate = electron::JavascriptEnvironment::GetIsolate();
v8::HandleScope scope(isolate);
if (save_promise_) {
@@ -250,35 +354,33 @@ void FileChooserDialog::AddFilters(const Filters& filters) {
GtkFileFilter* gtk_filter = gtk_file_filter_new();
for (const auto& extension : filter.second) {
auto file_extension = std::make_unique<std::string>("." + extension);
gtk_file_filter_add_custom(
gtk_filter, GTK_FILE_FILTER_FILENAME,
reinterpret_cast<GtkFileFilterFunc>(FileFilterCaseInsensitive),
file_extension.release(),
reinterpret_cast<GDestroyNotify>(OnFileFilterDataDestroyed));
// guarantee a pure lowercase variant
std::string file_extension = base::ToLowerASCII("*." + extension);
gtk_file_filter_add_pattern(gtk_filter, file_extension.c_str());
// guarantee a pure uppercase variant
file_extension = base::ToUpperASCII("*." + extension);
gtk_file_filter_add_pattern(gtk_filter, file_extension.c_str());
}
gtk_file_filter_set_name(gtk_filter, filter.first.c_str());
gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog_), gtk_filter);
gtk_file_chooser_add_filter(dialog_, gtk_filter);
}
}
void FileChooserDialog::OnUpdatePreview(GtkWidget* chooser) {
gchar* filename =
gtk_file_chooser_get_preview_filename(GTK_FILE_CHOOSER(chooser));
void FileChooserDialog::OnUpdatePreview(GtkFileChooser* chooser) {
CHECK(!*supports_gtk_file_chooser_native);
gchar* filename = gtk_file_chooser_get_preview_filename(chooser);
if (!filename) {
gtk_file_chooser_set_preview_widget_active(GTK_FILE_CHOOSER(chooser),
FALSE);
gtk_file_chooser_set_preview_widget_active(chooser, FALSE);
return;
}
// Don't attempt to open anything which isn't a regular file. If a named pipe,
// this may hang. See https://crbug.com/534754.
// Don't attempt to open anything which isn't a regular file. If a named
// pipe, this may hang. See https://crbug.com/534754.
struct stat stat_buf;
if (stat(filename, &stat_buf) != 0 || !S_ISREG(stat_buf.st_mode)) {
g_free(filename);
gtk_file_chooser_set_preview_widget_active(GTK_FILE_CHOOSER(chooser),
FALSE);
gtk_file_chooser_set_preview_widget_active(chooser, FALSE);
return;
}
@@ -290,12 +392,30 @@ void FileChooserDialog::OnUpdatePreview(GtkWidget* chooser) {
gtk_image_set_from_pixbuf(GTK_IMAGE(preview_), pixbuf);
g_object_unref(pixbuf);
}
gtk_file_chooser_set_preview_widget_active(GTK_FILE_CHOOSER(chooser),
pixbuf ? TRUE : FALSE);
gtk_file_chooser_set_preview_widget_active(chooser, pixbuf ? TRUE : FALSE);
}
} // namespace
void ShowFileDialog(const FileChooserDialog& dialog) {
if (*supports_gtk_file_chooser_native) {
dl_gtk_native_dialog_show(static_cast<void*>(dialog.dialog()));
} else {
gtk_widget_show_all(GTK_WIDGET(dialog.dialog()));
}
}
int RunFileDialog(const FileChooserDialog& dialog) {
int response = 0;
if (*supports_gtk_file_chooser_native) {
response = dl_gtk_native_dialog_run(static_cast<void*>(dialog.dialog()));
} else {
response = gtk_dialog_run(GTK_DIALOG(dialog.dialog()));
}
return response;
}
bool ShowOpenDialogSync(const DialogSettings& settings,
std::vector<base::FilePath>* paths) {
GtkFileChooserAction action = GTK_FILE_CHOOSER_ACTION_OPEN;
@@ -304,8 +424,9 @@ bool ShowOpenDialogSync(const DialogSettings& settings,
FileChooserDialog open_dialog(action, settings);
open_dialog.SetupOpenProperties(settings.properties);
gtk_widget_show_all(open_dialog.dialog());
int response = gtk_dialog_run(GTK_DIALOG(open_dialog.dialog()));
ShowFileDialog(open_dialog);
const int response = RunFileDialog(open_dialog);
if (response == GTK_RESPONSE_ACCEPT) {
*paths = open_dialog.GetFileNames();
return true;
@@ -327,8 +448,9 @@ bool ShowSaveDialogSync(const DialogSettings& settings, base::FilePath* path) {
FileChooserDialog save_dialog(GTK_FILE_CHOOSER_ACTION_SAVE, settings);
save_dialog.SetupSaveProperties(settings.properties);
gtk_widget_show_all(save_dialog.dialog());
int response = gtk_dialog_run(GTK_DIALOG(save_dialog.dialog()));
ShowFileDialog(save_dialog);
const int response = RunFileDialog(save_dialog);
if (response == GTK_RESPONSE_ACCEPT) {
*path = save_dialog.GetFileName();
return true;

View File

@@ -28,6 +28,13 @@ bool AppendTask(const JumpListItem& item, IObjectCollection* collection) {
FAILED(link->SetDescription(item.description.c_str())))
return false;
// SetDescription limits the size of the parameter to INFOTIPSIZE (1024),
// which suggests rejection when exceeding that limit, but experimentation
// has shown that descriptions longer than 260 characters cause a silent
// failure, despite SetDescription returning the success code S_OK.
if (item.description.size() > 260)
return false;
if (!item.icon_path.empty() &&
FAILED(link->SetIconLocation(item.icon_path.value().c_str(),
item.icon_index)))

View File

@@ -112,16 +112,21 @@ describe('webContents.setWindowOpenHandler', () => {
const { browserWindowOptions } = testConfig[testName];
describe(testName, () => {
beforeEach((done) => {
beforeEach(async () => {
browserWindow = new BrowserWindow(browserWindowOptions);
browserWindow.loadURL('about:blank');
browserWindow.on('ready-to-show', () => { browserWindow.show(); done(); });
await browserWindow.loadURL('about:blank');
browserWindow.show();
});
afterEach(closeAllWindows);
it('does not fire window creation events if an override returns action: deny', (done) => {
browserWindow.webContents.setWindowOpenHandler(() => ({ action: 'deny' }));
it('does not fire window creation events if an override returns action: deny', async () => {
const denied = new Promise((resolve) => {
browserWindow.webContents.setWindowOpenHandler(() => {
setTimeout(resolve);
return { action: 'deny' };
});
});
browserWindow.webContents.on('new-window', () => {
assert.fail('new-window should not to be called with an overridden window.open');
});
@@ -132,9 +137,51 @@ describe('webContents.setWindowOpenHandler', () => {
browserWindow.webContents.executeJavaScript("window.open('about:blank') && true");
setTimeout(() => {
done();
}, 500);
await denied;
});
it('is called when clicking on a target=_blank link', async () => {
const denied = new Promise((resolve) => {
browserWindow.webContents.setWindowOpenHandler(() => {
setTimeout(resolve);
return { action: 'deny' };
});
});
browserWindow.webContents.on('new-window', () => {
assert.fail('new-window should not to be called with an overridden window.open');
});
browserWindow.webContents.on('did-create-window', () => {
assert.fail('did-create-window should not to be called with an overridden window.open');
});
await browserWindow.webContents.loadURL('data:text/html,<a target="_blank" href="http://example.com" style="display: block; width: 100%; height: 100%; position: fixed; left: 0; top: 0;">link</a>');
browserWindow.webContents.sendInputEvent({ type: 'mouseDown', x: 10, y: 10, button: 'left', clickCount: 1 });
browserWindow.webContents.sendInputEvent({ type: 'mouseUp', x: 10, y: 10, button: 'left', clickCount: 1 });
await denied;
});
it('is called when shift-clicking on a link', async () => {
const denied = new Promise((resolve) => {
browserWindow.webContents.setWindowOpenHandler(() => {
setTimeout(resolve);
return { action: 'deny' };
});
});
browserWindow.webContents.on('new-window', () => {
assert.fail('new-window should not to be called with an overridden window.open');
});
browserWindow.webContents.on('did-create-window', () => {
assert.fail('did-create-window should not to be called with an overridden window.open');
});
await browserWindow.webContents.loadURL('data:text/html,<a href="http://example.com" style="display: block; width: 100%; height: 100%; position: fixed; left: 0; top: 0;">link</a>');
browserWindow.webContents.sendInputEvent({ type: 'mouseDown', x: 10, y: 10, button: 'left', clickCount: 1, modifiers: ['shift'] });
browserWindow.webContents.sendInputEvent({ type: 'mouseUp', x: 10, y: 10, button: 'left', clickCount: 1, modifiers: ['shift'] });
await denied;
});
it('fires handler with correct params', (done) => {

View File

@@ -1047,9 +1047,9 @@ xmlbuilder@~9.0.1:
integrity sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=
y18n@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.1.tgz#1ad2a7eddfa8bce7caa2e1f6b5da96c39d99d571"
integrity sha512-/jJ831jEs4vGDbYPQp4yGKDYPSCCEQ45uZWJHE1AoYBzqdZi8+LDWas0z4HrmJXmKdpFsTiowSHXdxyFhpmdMg==
version "5.0.5"
resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.5.tgz#8769ec08d03b1ea2df2500acef561743bbb9ab18"
integrity sha512-hsRUr4FFrvhhRH12wOdfs38Gy7k2FFzB9qgN9v3aLykRq0dRcdcpz5C9FxdS2NuhOrI/628b/KSTJ3rwHysYSg==
yargs-parser@^20.0.0:
version "20.0.0"

View File

@@ -101,6 +101,33 @@ declare namespace NodeJS {
removeGuest(embedder: Electron.WebContents, guestInstanceId: number): void;
}
interface InternalWebPreferences {
contextIsolation: boolean;
disableElectronSiteInstanceOverrides: boolean;
guestInstanceId: number;
hiddenPage: boolean;
nativeWindowOpen: boolean;
nodeIntegration: boolean;
openerId: number;
preload: string
preloadScripts: string[];
webviewTag: boolean;
worldSafeExecuteJavaScript: boolean;
}
interface WebFrameBinding {
_findFrameByRoutingId(window: Window, routingId: number): Window;
_getFrameForSelector(window: Window, selector: string): Window;
_findFrameByName(window: Window, name: string): Window;
_getOpener(window: Window): Window;
_getParent(window: Window): Window;
_getTop(window: Window): Window;
_getFirstChild(window: Window): Window;
_getNextSibling(window: Window): Window;
_getRoutingId(window: Window): number;
getWebPreference<K extends keyof InternalWebPreferences>(window: Window, name: K): InternalWebPreferences[K];
}
type DataPipe = {
write: (buf: Uint8Array) => Promise<void>;
done: () => void;
@@ -218,6 +245,7 @@ declare namespace NodeJS {
}
_linkedBinding(name: 'electron_renderer_crash_reporter'): Electron.CrashReporter;
_linkedBinding(name: 'electron_renderer_ipc'): { ipc: IpcRendererBinding };
_linkedBinding(name: 'electron_renderer_web_frame'): WebFrameBinding;
log: NodeJS.WriteStream['write'];
activateUvLoop(): void;