Compare commits

..

1 Commits

Author SHA1 Message Date
Shelley Vohr
19c382677b fix: paintWhenInitiallyHidden on Windows/Linux 2026-02-25 12:31:20 +01:00
10 changed files with 59 additions and 91 deletions

View File

@@ -21,10 +21,7 @@ The `dialog` module has the following methods:
* `window` [BaseWindow](base-window.md) (optional)
* `options` Object
* `title` string (optional)
* `defaultPath` string (optional) - Absolute directory path, absolute file
path, or file name to use by default. If not provided, the dialog will
default to the user's Downloads folder, or their home directory if Downloads
doesn't exist.
* `defaultPath` string (optional)
* `buttonLabel` string (optional) - Custom label for the confirmation button, when
left empty the default label will be used.
* `filters` [FileFilter[]](structures/file-filter.md) (optional)
@@ -96,10 +93,7 @@ dialog.showOpenDialogSync(mainWindow, {
* `window` [BaseWindow](base-window.md) (optional)
* `options` Object
* `title` string (optional)
* `defaultPath` string (optional) - Absolute directory path, absolute file
path, or file name to use by default. If not provided, the dialog will
default to the user's Downloads folder, or their home directory if Downloads
doesn't exist.
* `defaultPath` string (optional)
* `buttonLabel` string (optional) - Custom label for the confirmation button, when
left empty the default label will be used.
* `filters` [FileFilter[]](structures/file-filter.md) (optional)
@@ -181,9 +175,7 @@ dialog.showOpenDialog(mainWindow, {
* `options` Object
* `title` string (optional) - The dialog title. Cannot be displayed on some _Linux_ desktop environments.
* `defaultPath` string (optional) - Absolute directory path, absolute file
path, or file name to use by default. If not provided, the dialog will
default to the user's Downloads folder, or their home directory if Downloads
doesn't exist.
path, or file name to use by default.
* `buttonLabel` string (optional) - Custom label for the confirmation button, when
left empty the default label will be used.
* `filters` [FileFilter[]](structures/file-filter.md) (optional)
@@ -214,9 +206,7 @@ The `filters` specifies an array of file types that can be displayed, see
* `options` Object
* `title` string (optional) - The dialog title. Cannot be displayed on some _Linux_ desktop environments.
* `defaultPath` string (optional) - Absolute directory path, absolute file
path, or file name to use by default. If not provided, the dialog will
default to the user's Downloads folder, or their home directory if Downloads
doesn't exist.
path, or file name to use by default.
* `buttonLabel` string (optional) - Custom label for the confirmation button, when
left empty the default label will be used.
* `filters` [FileFilter[]](structures/file-filter.md) (optional)

View File

@@ -14,17 +14,6 @@ This document uses the following convention to categorize breaking changes:
## Planned Breaking API Changes (42.0)
### Behavior Changed: Dialog methods default to Downloads directory
The `defaultPath` option for the following methods now defaults to the user's Downloads folder (or their home directory if Downloads doesn't exist) when not explicitly provided:
* `dialog.showOpenDialog`
* `dialog.showOpenDialogSync`
* `dialog.showSaveDialog`
* `dialog.showSaveDialogSync`
Previously, these methods used the last-opened directory or an OS-determined default. To preserve the old behavior, explicitly pass `defaultPath` when calling these methods.
### Behavior Changed: Offscreen rendering will use `1.0` as default device scale factor.
Previously, OSR used the primary display's device scale factor for rendering, which made the output frame size vary across users.

View File

@@ -49,6 +49,7 @@ BrowserWindow::BrowserWindow(gin::Arguments* args,
// when initially hidden
bool paint_when_initially_hidden = true;
options.Get(options::kPaintWhenInitiallyHidden, &paint_when_initially_hidden);
paint_when_initially_hidden_ = paint_when_initially_hidden;
if (!paint_when_initially_hidden) {
bool show = true;
options.Get(options::kShow, &show);
@@ -288,6 +289,17 @@ void BrowserWindow::OnWindowHide() {
BaseWindow::OnWindowHide();
}
void BrowserWindow::RenderViewReady() {
// When paintWhenInitiallyHidden is true and the native window has not been
// shown yet, tell the WebContents it is visible so the renderer's compositor
// starts producing frames. Without this the renderer's LayerTreeHost stays
// hidden and no CompositorFrames (and therefore no presentation callbacks)
// are ever produced, which means PerformanceObserver paint-timing entries
// (first-paint / first-contentful-paint) never fire.
if (paint_when_initially_hidden_ && !window()->IsVisible())
web_contents()->WasShown();
}
void BrowserWindow::Show() {
web_contents()->WasShown();
BaseWindow::Show();

View File

@@ -42,6 +42,7 @@ class BrowserWindow : public BaseWindow,
// content::WebContentsObserver:
void BeforeUnloadDialogCancelled() override;
void RenderViewReady() override;
void WebContentsDestroyed() override;
// ExtendedWebContentsObserver:
@@ -79,6 +80,12 @@ class BrowserWindow : public BaseWindow,
private:
// Helpers.
// When true and the window is created with show: false, the renderer is
// told it is visible as soon as it is ready so that PerformanceObserver
// painttiming entries (first-paint / first-contentful-paint) are
// produced even before the native window is shown.
bool paint_when_initially_hidden_ = false;
v8::Global<v8::Value> web_contents_;
v8::Global<v8::Value> web_contents_view_;
base::WeakPtr<api::WebContents> api_web_contents_;

View File

@@ -13,7 +13,6 @@
#include "shell/browser/javascript_environment.h"
#include "shell/browser/native_window_views.h"
#include "shell/browser/ui/file_dialog.h"
#include "shell/common/electron_paths.h"
#include "shell/common/gin_converters/callback_converter.h"
#include "shell/common/gin_converters/file_path_converter.h"
#include "shell/common/gin_helper/dictionary.h"
@@ -82,18 +81,14 @@ class FileChooserDialog : public ui::SelectFileDialog::Listener {
ui::SelectFileDialog::FileTypeInfo file_info =
GetFilterInfo(settings.filters);
ApplySettings(settings);
base::FilePath default_path = settings.default_path.empty()
? electron::GetDefaultPath()
: settings.default_path;
dialog_->SelectFile(ui::SelectFileDialog::SELECT_SAVEAS_FILE,
base::UTF8ToUTF16(settings.title), default_path,
&file_info /* file_types */, 0 /* file_type_index */,
base::FilePath::StringType() /* default_extension */,
settings.parent_window
? settings.parent_window->GetNativeWindow()
: nullptr,
nullptr);
dialog_->SelectFile(
ui::SelectFileDialog::SELECT_SAVEAS_FILE,
base::UTF8ToUTF16(settings.title), settings.default_path,
&file_info /* file_types */, 0 /* file_type_index */,
base::FilePath::StringType() /* default_extension */,
settings.parent_window ? settings.parent_window->GetNativeWindow()
: nullptr,
nullptr);
}
void RunSaveDialog(gin_helper::Promise<gin_helper::Dictionary> promise,
@@ -113,13 +108,9 @@ class FileChooserDialog : public ui::SelectFileDialog::Listener {
ui::SelectFileDialog::FileTypeInfo file_info =
GetFilterInfo(settings.filters);
ApplySettings(settings);
base::FilePath default_path = settings.default_path.empty()
? electron::GetDefaultPath()
: settings.default_path;
dialog_->SelectFile(
GetDialogType(settings.properties), base::UTF8ToUTF16(settings.title),
default_path, &file_info, 0 /* file_type_index */,
settings.default_path, &file_info, 0 /* file_type_index */,
base::FilePath::StringType() /* default_extension */,
settings.parent_window ? settings.parent_window->GetNativeWindow()
: nullptr,

View File

@@ -21,7 +21,6 @@
#include "content/public/browser/browser_thread.h"
#include "electron/mas.h"
#include "shell/browser/native_window.h"
#include "shell/common/electron_paths.h"
#include "shell/common/gin_converters/file_path_converter.h"
#include "shell/common/gin_helper/dictionary.h"
#include "shell/common/gin_helper/promise.h"
@@ -187,18 +186,14 @@ void SetupDialog(NSSavePanel* dialog, const DialogSettings& settings) {
[dialog setShowsTagField:settings.shows_tag_field];
base::FilePath default_path = settings.default_path.empty()
? electron::GetDefaultPath()
: settings.default_path;
NSString* default_dir = nil;
NSString* default_filename = nil;
if (!default_path.empty()) {
if (!settings.default_path.empty()) {
electron::ScopedAllowBlockingForElectron allow_blocking;
if (base::DirectoryExists(default_path)) {
default_dir = base::SysUTF8ToNSString(default_path.value());
if (base::DirectoryExists(settings.default_path)) {
default_dir = base::SysUTF8ToNSString(settings.default_path.value());
} else {
if (default_path.IsAbsolute()) {
if (settings.default_path.IsAbsolute()) {
default_dir =
base::SysUTF8ToNSString(settings.default_path.DirName().value());
}

View File

@@ -21,7 +21,6 @@
#include "base/win/registry.h"
#include "shell/browser/native_window_views.h"
#include "shell/browser/ui/win/dialog_thread.h"
#include "shell/common/electron_paths.h"
#include "shell/common/gin_converters/file_path_converter.h"
#include "shell/common/gin_helper/dictionary.h"
#include "shell/common/gin_helper/promise.h"
@@ -107,12 +106,8 @@ static HRESULT ShowFileDialog(IFileDialog* dialog,
static void ApplySettings(IFileDialog* dialog, const DialogSettings& settings) {
std::wstring file_part;
base::FilePath default_path = settings.default_path.empty()
? electron::GetDefaultPath()
: settings.default_path;
if (!IsDirectory(default_path))
file_part = default_path.BaseName().value();
if (!IsDirectory(settings.default_path))
file_part = settings.default_path.BaseName().value();
dialog->SetFileName(file_part.c_str());
@@ -154,8 +149,8 @@ static void ApplySettings(IFileDialog* dialog, const DialogSettings& settings) {
}
}
if (default_path.IsAbsolute()) {
SetDefaultFolder(dialog, default_path);
if (settings.default_path.IsAbsolute()) {
SetDefaultFolder(dialog, settings.default_path);
}
}

View File

@@ -114,18 +114,4 @@ void RegisterPathProvider() {
PATH_END);
}
base::FilePath GetDefaultPath() {
base::FilePath path;
ScopedAllowBlockingForElectron allow_blocking;
if (base::PathService::Get(chrome::DIR_DEFAULT_DOWNLOADS, &path) &&
base::DirectoryExists(path))
return path;
if (base::PathService::Get(base::DIR_HOME, &path))
return path;
return base::FilePath();
}
} // namespace electron

View File

@@ -51,10 +51,6 @@ static_assert(PATH_START < PATH_END, "invalid PATH boundaries");
// Register the path provider with the base::PathService.
void RegisterPathProvider();
// Returns a default directory for file dialogs when no default path is
// provided.
base::FilePath GetDefaultPath();
} // namespace electron
#endif // ELECTRON_SHELL_COMMON_ELECTRON_PATHS_H_

View File

@@ -6721,28 +6721,35 @@ describe('BrowserWindow module', () => {
w.loadFile(path.join(fixtures, 'pages', 'send-after-node.html'));
});
// TODO(codebytere): fix on Windows and Linux too
ifdescribe(process.platform === 'darwin')('window.webContents initial paint', () => {
describe('window.webContents initial paint', () => {
afterEach(closeAllWindows);
it('paints when a window is initially hidden', async () => {
const w = new BrowserWindow({ show: false });
it('paints when a window is initially hidden with paintWhenInitiallyHidden', async () => {
const w = new BrowserWindow({
show: false,
paintWhenInitiallyHidden: true
});
await w.loadFile(path.join(fixtures, 'pages', 'a.html'));
const entries = await w.webContents.executeJavaScript(`
new Promise((resolve) => {
const observer = new PerformanceObserver((performance) => {
observer.disconnect();
resolve(performance.getEntries());
const observer = new PerformanceObserver((list) => {
const paintEntries = list.getEntries().filter(
e => e.name === 'first-paint' || e.name === 'first-contentful-paint'
);
if (paintEntries.length > 0) {
observer.disconnect();
resolve(paintEntries.map(e => e.name));
}
});
observer.observe({ entryTypes: ['paint'] });
});
const header = document.createElement('h1');
header.innerText = 'Paint me!!';
document.getElementById('div').appendChild(header);
const header = document.createElement('h1');
header.innerText = 'Paint me!!';
document.getElementById('div').appendChild(header);
});
`);
expect(JSON.stringify(entries)).to.eq('{}');
expect(entries).to.be.an('array').that.includes('first-contentful-paint');
});
});