mirror of
https://github.com/electron/electron.git
synced 2026-05-02 03:00:22 -04:00
fix: persist visual zoom level limits across navigations
setVisualZoomLevelLimits previously only called through to the renderer via IPC, and ElectronBrowserClient::OverrideWebPreferences unconditionally reset default_minimum/maximum_page_scale_factor to 1.f on every navigation, wiping out any limits that had been set. Additionally, for webview guests, Chromium routes touchpad pinch-to-zoom through the root (embedder) compositor rather than the guest's compositor, so setting page scale limits on the guest alone had no effect. This stores the limits in WebContentsPreferences so they survive navigation resets, and propagates them to the embedder for webview guests.
This commit is contained in:
@@ -152,11 +152,10 @@ WebContents.prototype.sendToFrame = function (frameId, channel, ...args) {
|
||||
};
|
||||
|
||||
// Following methods are mapped to webFrame.
|
||||
const webFrameMethods = ['insertCSS', 'insertText', 'removeInsertedCSS', 'setVisualZoomLevelLimits'] as (
|
||||
const webFrameMethods = ['insertCSS', 'insertText', 'removeInsertedCSS'] as (
|
||||
| 'insertCSS'
|
||||
| 'insertText'
|
||||
| 'removeInsertedCSS'
|
||||
| 'setVisualZoomLevelLimits'
|
||||
)[];
|
||||
|
||||
for (const method of webFrameMethods) {
|
||||
@@ -165,6 +164,20 @@ for (const method of webFrameMethods) {
|
||||
};
|
||||
}
|
||||
|
||||
// setVisualZoomLevelLimits persists the limits in WebContentsPreferences so
|
||||
// they survive cross-navigation preference resets, then forwards to the
|
||||
// renderer for immediate effect on the current page.
|
||||
WebContents.prototype.setVisualZoomLevelLimits = function (minimumLevel: number, maximumLevel: number): Promise<void> {
|
||||
this._setVisualZoomLevelLimits(minimumLevel, maximumLevel);
|
||||
return ipcMainUtils.invokeInWebContents(
|
||||
this,
|
||||
IPC_MESSAGES.RENDERER_WEB_FRAME_METHOD,
|
||||
'setVisualZoomLevelLimits',
|
||||
minimumLevel,
|
||||
maximumLevel
|
||||
);
|
||||
};
|
||||
|
||||
const waitTillCanExecuteJavaScript = async (webContents: Electron.WebContents) => {
|
||||
if (webContents.getURL() && !webContents.isLoadingMainFrame()) return;
|
||||
|
||||
|
||||
@@ -4024,6 +4024,26 @@ void WebContents::SetImageAnimationPolicy(const std::string& new_policy) {
|
||||
web_contents()->OnWebPreferencesChanged();
|
||||
}
|
||||
|
||||
void WebContents::SetVisualZoomLevelLimits(double min_level, double max_level) {
|
||||
auto* web_preferences = WebContentsPreferences::From(web_contents());
|
||||
if (web_preferences) {
|
||||
web_preferences->SetVisualZoomLevelLimits(static_cast<float>(min_level),
|
||||
static_cast<float>(max_level));
|
||||
}
|
||||
|
||||
// Touchpad pinch-to-zoom for child frames (webview guests) is handled by the
|
||||
// root compositor, so propagate the limits to the embedder as well.
|
||||
if (embedder_) {
|
||||
auto* embedder_prefs =
|
||||
WebContentsPreferences::From(embedder_->web_contents());
|
||||
if (embedder_prefs) {
|
||||
embedder_prefs->SetVisualZoomLevelLimits(static_cast<float>(min_level),
|
||||
static_cast<float>(max_level));
|
||||
embedder_->web_contents()->OnWebPreferencesChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void WebContents::SetBackgroundColor(std::optional<SkColor> maybe_color) {
|
||||
SkColor color = maybe_color.value_or((is_guest() && guest_transparent_) ||
|
||||
type_ == Type::kBrowserView
|
||||
@@ -4727,6 +4747,8 @@ void WebContents::FillObjectTemplate(v8::Isolate* isolate,
|
||||
.SetMethod("takeHeapSnapshot", &WebContents::TakeHeapSnapshot)
|
||||
.SetMethod("setImageAnimationPolicy",
|
||||
&WebContents::SetImageAnimationPolicy)
|
||||
.SetMethod("_setVisualZoomLevelLimits",
|
||||
&WebContents::SetVisualZoomLevelLimits)
|
||||
.SetMethod("_getProcessMemoryInfo", &WebContents::GetProcessMemoryInfo)
|
||||
.SetProperty("id", &WebContents::ID)
|
||||
.SetProperty("session", &WebContents::Session)
|
||||
|
||||
@@ -431,6 +431,7 @@ class WebContents final : public ExclusiveAccessContext,
|
||||
void SetTemporaryZoomLevel(double level);
|
||||
|
||||
void SetImageAnimationPolicy(const std::string& new_policy);
|
||||
void SetVisualZoomLevelLimits(double min_level, double max_level);
|
||||
|
||||
// content::RenderWidgetHost::InputEventObserver:
|
||||
void OnInputEvent(const content::RenderWidgetHost& rfh,
|
||||
|
||||
@@ -149,6 +149,8 @@ void WebContentsPreferences::Clear() {
|
||||
v8_cache_options_ = blink::mojom::V8CacheOptions::kDefault;
|
||||
deprecated_paste_enabled_ = false;
|
||||
focus_on_navigation_ = true;
|
||||
default_minimum_page_scale_factor_ = std::nullopt;
|
||||
default_maximum_page_scale_factor_ = std::nullopt;
|
||||
|
||||
#if BUILDFLAG(IS_MAC)
|
||||
scroll_bounce_ = false;
|
||||
@@ -278,6 +280,12 @@ bool WebContentsPreferences::SetImageAnimationPolicy(std::string policy) {
|
||||
return false;
|
||||
}
|
||||
|
||||
void WebContentsPreferences::SetVisualZoomLevelLimits(float min_level,
|
||||
float max_level) {
|
||||
default_minimum_page_scale_factor_ = min_level;
|
||||
default_maximum_page_scale_factor_ = max_level;
|
||||
}
|
||||
|
||||
bool WebContentsPreferences::IsSandboxed() const {
|
||||
if (sandbox_)
|
||||
return *sandbox_;
|
||||
@@ -471,6 +479,13 @@ void WebContentsPreferences::OverrideWebkitPrefs(
|
||||
prefs->v8_cache_options = v8_cache_options_;
|
||||
|
||||
prefs->dom_paste_enabled = deprecated_paste_enabled_;
|
||||
|
||||
if (default_minimum_page_scale_factor_)
|
||||
prefs->default_minimum_page_scale_factor =
|
||||
*default_minimum_page_scale_factor_;
|
||||
if (default_maximum_page_scale_factor_)
|
||||
prefs->default_maximum_page_scale_factor =
|
||||
*default_maximum_page_scale_factor_;
|
||||
}
|
||||
|
||||
WEB_CONTENTS_USER_DATA_KEY_IMPL(WebContentsPreferences);
|
||||
|
||||
@@ -69,6 +69,7 @@ class WebContentsPreferences
|
||||
}
|
||||
bool ShouldIgnoreMenuShortcuts() const { return ignore_menu_shortcuts_; }
|
||||
bool SetImageAnimationPolicy(std::string policy);
|
||||
void SetVisualZoomLevelLimits(float min_level, float max_level);
|
||||
bool ShouldDisableHtmlFullscreenWindowResize() const {
|
||||
return disable_html_fullscreen_window_resize_;
|
||||
}
|
||||
@@ -135,6 +136,8 @@ class WebContentsPreferences
|
||||
blink::mojom::V8CacheOptions v8_cache_options_;
|
||||
bool deprecated_paste_enabled_ = false;
|
||||
bool focus_on_navigation_;
|
||||
std::optional<float> default_minimum_page_scale_factor_;
|
||||
std::optional<float> default_maximum_page_scale_factor_;
|
||||
|
||||
#if BUILDFLAG(IS_MAC)
|
||||
bool scroll_bounce_;
|
||||
|
||||
171
typings/internal-electron.d.ts
vendored
171
typings/internal-electron.d.ts
vendored
@@ -18,7 +18,10 @@ declare namespace Electron {
|
||||
setDesktopName(name: string): void;
|
||||
setAppPath(path: string | null): void;
|
||||
_clientCertRequestPasswordHandler: ((params: ClientCertRequestParams) => Promise<string>) | null;
|
||||
on(event: '-client-certificate-request-password', listener: (event: Event<ClientCertRequestParams>, callback: (password: string) => void) => Promise<void>): this;
|
||||
on(
|
||||
event: '-client-certificate-request-password',
|
||||
listener: (event: Event<ClientCertRequestParams>, callback: (password: string) => void) => Promise<void>
|
||||
): this;
|
||||
}
|
||||
|
||||
interface AutoUpdater {
|
||||
@@ -34,7 +37,10 @@ declare namespace Electron {
|
||||
_setEscapeTouchBarItem: (item: TouchBarItemType | {}) => void;
|
||||
_refreshTouchBarItem: (itemID: string) => void;
|
||||
on(event: '-touch-bar-interaction', listener: (event: Event, itemID: string, details: any) => void): this;
|
||||
removeListener(event: '-touch-bar-interaction', listener: (event: Event, itemID: string, details: any) => void): this;
|
||||
removeListener(
|
||||
event: '-touch-bar-interaction',
|
||||
listener: (event: Event, itemID: string, details: any) => void
|
||||
): this;
|
||||
}
|
||||
|
||||
interface BrowserWindow extends BaseWindow {
|
||||
@@ -45,12 +51,15 @@ declare namespace Electron {
|
||||
frameName: string;
|
||||
_browserViews: BrowserView[];
|
||||
on(event: '-touch-bar-interaction', listener: (event: Event, itemID: string, details: any) => void): this;
|
||||
removeListener(event: '-touch-bar-interaction', listener: (event: Event, itemID: string, details: any) => void): this;
|
||||
removeListener(
|
||||
event: '-touch-bar-interaction',
|
||||
listener: (event: Event, itemID: string, details: any) => void
|
||||
): this;
|
||||
}
|
||||
|
||||
interface BrowserView {
|
||||
ownerWindow: BrowserWindow | null
|
||||
webContentsView: WebContentsView
|
||||
ownerWindow: BrowserWindow | null;
|
||||
webContentsView: WebContentsView;
|
||||
}
|
||||
|
||||
interface BrowserWindowConstructorOptions {
|
||||
@@ -63,7 +72,7 @@ declare namespace Electron {
|
||||
overrideGlobalValueFromIsolatedWorld(keys: string[], value: any): void;
|
||||
overrideGlobalValueWithDynamicPropsFromIsolatedWorld(keys: string[], value: any): void;
|
||||
overrideGlobalPropertyFromIsolatedWorld(keys: string[], getter: Function, setter?: Function): void;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
interface ServiceWorkers {
|
||||
@@ -73,7 +82,7 @@ declare namespace Electron {
|
||||
|
||||
interface ServiceWorkerMain {
|
||||
_send(internal: boolean, channel: string, args: any): void;
|
||||
_startExternalRequest(hasTimeout: boolean): { id: string, ok: boolean };
|
||||
_startExternalRequest(hasTimeout: boolean): { id: string; ok: boolean };
|
||||
_finishExternalRequest(uuid: string): void;
|
||||
_countExternalRequests(): number;
|
||||
}
|
||||
@@ -95,8 +104,18 @@ declare namespace Electron {
|
||||
_getPreloadScript(): Electron.PreloadScript | null;
|
||||
browserWindowOptions: BrowserWindowConstructorOptions;
|
||||
_windowOpenHandler: ((details: Electron.HandlerDetails) => any) | null;
|
||||
_callWindowOpenHandler(event: any, details: Electron.HandlerDetails): {browserWindowConstructorOptions: Electron.BrowserWindowConstructorOptions | null, outlivesOpener: boolean, createWindow?: Electron.CreateWindowFunction};
|
||||
_setNextChildWebPreferences(prefs: Partial<Electron.BrowserWindowConstructorOptions['webPreferences']> & Pick<Electron.BrowserWindowConstructorOptions, 'backgroundColor'>): void;
|
||||
_callWindowOpenHandler(
|
||||
event: any,
|
||||
details: Electron.HandlerDetails
|
||||
): {
|
||||
browserWindowConstructorOptions: Electron.BrowserWindowConstructorOptions | null;
|
||||
outlivesOpener: boolean;
|
||||
createWindow?: Electron.CreateWindowFunction;
|
||||
};
|
||||
_setNextChildWebPreferences(
|
||||
prefs: Partial<Electron.BrowserWindowConstructorOptions['webPreferences']> &
|
||||
Pick<Electron.BrowserWindowConstructorOptions, 'backgroundColor'>
|
||||
): void;
|
||||
_send(internal: boolean, channel: string, args: any): boolean;
|
||||
_sendInternal(channel: string, ...args: any[]): void;
|
||||
_printToPDF(options: any): Promise<Buffer>;
|
||||
@@ -114,8 +133,8 @@ declare namespace Electron {
|
||||
_goToIndex(index: number): void;
|
||||
_removeNavigationEntryAtIndex(index: number): boolean;
|
||||
_getHistory(): Electron.NavigationEntry[];
|
||||
_restoreHistory(index: number, entries: Electron.NavigationEntry[]): void
|
||||
_clearHistory():void
|
||||
_restoreHistory(index: number, entries: Electron.NavigationEntry[]): void;
|
||||
_clearHistory(): void;
|
||||
destroy(): void;
|
||||
// <webview>
|
||||
attachToIframe(embedderWebContents: Electron.WebContents, embedderFrameToken: string): void;
|
||||
@@ -123,6 +142,7 @@ declare namespace Electron {
|
||||
setEmbedder(embedder: Electron.WebContents): void;
|
||||
viewInstanceId: number;
|
||||
_setOwnerWindow(w: BaseWindow | null): void;
|
||||
_setVisualZoomLevelLimits(minLevel: number, maxLevel: number): void;
|
||||
}
|
||||
|
||||
interface WebFrameMain {
|
||||
@@ -166,7 +186,15 @@ declare namespace Electron {
|
||||
commandsMap: Record<string, MenuItem>;
|
||||
groupsMap: Record<string, MenuItem[]>;
|
||||
getItemCount(): number;
|
||||
popupAt(window: BaseWindow, frame: WebFrameMain | undefined, x: number, y: number, positioning: number, sourceType: Required<Electron.PopupOptions>['sourceType'], callback: () => void): void;
|
||||
popupAt(
|
||||
window: BaseWindow,
|
||||
frame: WebFrameMain | undefined,
|
||||
x: number,
|
||||
y: number,
|
||||
positioning: number,
|
||||
sourceType: Required<Electron.PopupOptions>['sourceType'],
|
||||
callback: () => void
|
||||
): void;
|
||||
closePopupAt(id: number): void;
|
||||
setSublabel(index: number, label: string): void;
|
||||
setToolTip(index: number, tooltip: string): void;
|
||||
@@ -234,17 +262,76 @@ declare namespace Electron {
|
||||
}
|
||||
|
||||
interface WebContents {
|
||||
on(event: '-new-window', listener: (event: Electron.Event, url: string, frameName: string, disposition: Electron.HandlerDetails['disposition'],
|
||||
rawFeatures: string, referrer: Electron.Referrer, postData: LoadURLOptions['postData']) => void): this;
|
||||
on(event: '-add-new-contents', listener: (event: Event, webContents: Electron.WebContents, disposition: string,
|
||||
_userGesture: boolean, _left: number, _top: number, _width: number, _height: number, url: string, frameName: string,
|
||||
referrer: Electron.Referrer, rawFeatures: string, postData: LoadURLOptions['postData']) => void): this;
|
||||
on(event: '-will-add-new-contents', listener: (event: Electron.Event, url: string, frameName: string, rawFeatures: string, disposition: Electron.HandlerDetails['disposition'], referrer: Electron.Referrer, postData: LoadURLOptions['postData']) => void): this;
|
||||
on(event: '-ipc-message', listener: (event: Electron.IpcMainEvent, internal: boolean, channel: string, args: any[]) => void): this;
|
||||
on(event: '-ipc-message-sync', listener: (event: Electron.IpcMainEvent, internal: boolean, channel: string, args: any[]) => void): this;
|
||||
on(event: '-ipc-invoke', listener: (event: Electron.IpcMainInvokeEvent, internal: boolean, channel: string, args: any[]) => void): this;
|
||||
on(event: '-ipc-ports', listener: (event: Electron.IpcMainEvent, internal: boolean, channel: string, message: any, ports: any[]) => void): this;
|
||||
on(event: '-run-dialog', listener: (info: {frame: WebFrameMain, dialogType: 'prompt' | 'confirm' | 'alert', messageText: string, defaultPromptText: string}, callback: (success: boolean, user_input: string) => void) => void): this;
|
||||
on(
|
||||
event: '-new-window',
|
||||
listener: (
|
||||
event: Electron.Event,
|
||||
url: string,
|
||||
frameName: string,
|
||||
disposition: Electron.HandlerDetails['disposition'],
|
||||
rawFeatures: string,
|
||||
referrer: Electron.Referrer,
|
||||
postData: LoadURLOptions['postData']
|
||||
) => void
|
||||
): this;
|
||||
on(
|
||||
event: '-add-new-contents',
|
||||
listener: (
|
||||
event: Event,
|
||||
webContents: Electron.WebContents,
|
||||
disposition: string,
|
||||
_userGesture: boolean,
|
||||
_left: number,
|
||||
_top: number,
|
||||
_width: number,
|
||||
_height: number,
|
||||
url: string,
|
||||
frameName: string,
|
||||
referrer: Electron.Referrer,
|
||||
rawFeatures: string,
|
||||
postData: LoadURLOptions['postData']
|
||||
) => void
|
||||
): this;
|
||||
on(
|
||||
event: '-will-add-new-contents',
|
||||
listener: (
|
||||
event: Electron.Event,
|
||||
url: string,
|
||||
frameName: string,
|
||||
rawFeatures: string,
|
||||
disposition: Electron.HandlerDetails['disposition'],
|
||||
referrer: Electron.Referrer,
|
||||
postData: LoadURLOptions['postData']
|
||||
) => void
|
||||
): this;
|
||||
on(
|
||||
event: '-ipc-message',
|
||||
listener: (event: Electron.IpcMainEvent, internal: boolean, channel: string, args: any[]) => void
|
||||
): this;
|
||||
on(
|
||||
event: '-ipc-message-sync',
|
||||
listener: (event: Electron.IpcMainEvent, internal: boolean, channel: string, args: any[]) => void
|
||||
): this;
|
||||
on(
|
||||
event: '-ipc-invoke',
|
||||
listener: (event: Electron.IpcMainInvokeEvent, internal: boolean, channel: string, args: any[]) => void
|
||||
): this;
|
||||
on(
|
||||
event: '-ipc-ports',
|
||||
listener: (event: Electron.IpcMainEvent, internal: boolean, channel: string, message: any, ports: any[]) => void
|
||||
): this;
|
||||
on(
|
||||
event: '-run-dialog',
|
||||
listener: (
|
||||
info: {
|
||||
frame: WebFrameMain;
|
||||
dialogType: 'prompt' | 'confirm' | 'alert';
|
||||
messageText: string;
|
||||
defaultPromptText: string;
|
||||
},
|
||||
callback: (success: boolean, user_input: string) => void
|
||||
) => void
|
||||
): this;
|
||||
on(event: '-cancel-dialogs', listener: () => void): this;
|
||||
on(event: 'ready-to-show', listener: () => void): this;
|
||||
on(event: '-before-unload-fired', listener: (event: Electron.Event, proceed: boolean) => void): this;
|
||||
@@ -263,7 +350,12 @@ declare namespace Electron {
|
||||
|
||||
declare namespace ElectronInternal {
|
||||
interface DesktopCapturer {
|
||||
startHandling(captureWindow: boolean, captureScreen: boolean, thumbnailSize: Electron.Size, fetchWindowIcons: boolean): void;
|
||||
startHandling(
|
||||
captureWindow: boolean,
|
||||
captureScreen: boolean,
|
||||
thumbnailSize: Electron.Size,
|
||||
fetchWindowIcons: boolean
|
||||
): void;
|
||||
_onerror?: (error: string) => void;
|
||||
_onfinished?: (sources: Electron.DesktopCapturerSource[], fetchWindowIcons: boolean) => void;
|
||||
}
|
||||
@@ -283,7 +375,8 @@ declare namespace ElectronInternal {
|
||||
appIcon: Electron.NativeImage | null;
|
||||
}
|
||||
|
||||
interface IpcRendererInternal extends NodeJS.EventEmitter, Pick<Electron.IpcRenderer, 'send' | 'sendSync' | 'invoke'> {
|
||||
interface IpcRendererInternal
|
||||
extends NodeJS.EventEmitter, Pick<Electron.IpcRenderer, 'send' | 'sendSync' | 'invoke'> {
|
||||
invoke<T>(channel: string, ...args: any[]): Promise<T>;
|
||||
}
|
||||
|
||||
@@ -305,21 +398,21 @@ declare namespace ElectronInternal {
|
||||
}
|
||||
|
||||
type MediaSize = {
|
||||
name: string,
|
||||
custom_display_name: string,
|
||||
height_microns: number,
|
||||
width_microns: number,
|
||||
imageable_area_left_microns?: number,
|
||||
imageable_area_bottom_microns?: number,
|
||||
imageable_area_right_microns?: number,
|
||||
imageable_area_top_microns?: number,
|
||||
is_default?: 'true',
|
||||
}
|
||||
name: string;
|
||||
custom_display_name: string;
|
||||
height_microns: number;
|
||||
width_microns: number;
|
||||
imageable_area_left_microns?: number;
|
||||
imageable_area_bottom_microns?: number;
|
||||
imageable_area_right_microns?: number;
|
||||
imageable_area_top_microns?: number;
|
||||
is_default?: 'true';
|
||||
};
|
||||
|
||||
type PageSize = {
|
||||
width: number,
|
||||
height: number,
|
||||
}
|
||||
width: number;
|
||||
height: number;
|
||||
};
|
||||
|
||||
type ModuleLoader = () => any;
|
||||
|
||||
@@ -329,7 +422,7 @@ declare namespace ElectronInternal {
|
||||
}
|
||||
|
||||
interface UtilityProcessWrapper extends NodeJS.EventEmitter {
|
||||
readonly pid: (number) | (undefined);
|
||||
readonly pid: number | undefined;
|
||||
kill(): boolean;
|
||||
postMessage(message: any, transfer?: any[]): void;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user