diff --git a/lib/browser/api/web-contents.ts b/lib/browser/api/web-contents.ts index 51f266e906..51271a149b 100644 --- a/lib/browser/api/web-contents.ts +++ b/lib/browser/api/web-contents.ts @@ -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 { + 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; diff --git a/shell/browser/api/electron_api_web_contents.cc b/shell/browser/api/electron_api_web_contents.cc index 97197d7024..6b670cef73 100644 --- a/shell/browser/api/electron_api_web_contents.cc +++ b/shell/browser/api/electron_api_web_contents.cc @@ -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(min_level), + static_cast(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(min_level), + static_cast(max_level)); + embedder_->web_contents()->OnWebPreferencesChanged(); + } + } +} + void WebContents::SetBackgroundColor(std::optional 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) diff --git a/shell/browser/api/electron_api_web_contents.h b/shell/browser/api/electron_api_web_contents.h index 7325bc9883..8874e670e9 100644 --- a/shell/browser/api/electron_api_web_contents.h +++ b/shell/browser/api/electron_api_web_contents.h @@ -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, diff --git a/shell/browser/web_contents_preferences.cc b/shell/browser/web_contents_preferences.cc index da2fe80b8a..348e1fedc8 100644 --- a/shell/browser/web_contents_preferences.cc +++ b/shell/browser/web_contents_preferences.cc @@ -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); diff --git a/shell/browser/web_contents_preferences.h b/shell/browser/web_contents_preferences.h index 2ae8bac18b..65918fba43 100644 --- a/shell/browser/web_contents_preferences.h +++ b/shell/browser/web_contents_preferences.h @@ -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 default_minimum_page_scale_factor_; + std::optional default_maximum_page_scale_factor_; #if BUILDFLAG(IS_MAC) bool scroll_bounce_; diff --git a/typings/internal-electron.d.ts b/typings/internal-electron.d.ts index da5846af77..e7c15b34ff 100644 --- a/typings/internal-electron.d.ts +++ b/typings/internal-electron.d.ts @@ -18,7 +18,10 @@ declare namespace Electron { setDesktopName(name: string): void; setAppPath(path: string | null): void; _clientCertRequestPasswordHandler: ((params: ClientCertRequestParams) => Promise) | null; - on(event: '-client-certificate-request-password', listener: (event: Event, callback: (password: string) => void) => Promise): this; + on( + event: '-client-certificate-request-password', + listener: (event: Event, callback: (password: string) => void) => Promise + ): 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 & Pick): void; + _callWindowOpenHandler( + event: any, + details: Electron.HandlerDetails + ): { + browserWindowConstructorOptions: Electron.BrowserWindowConstructorOptions | null; + outlivesOpener: boolean; + createWindow?: Electron.CreateWindowFunction; + }; + _setNextChildWebPreferences( + prefs: Partial & + Pick + ): void; _send(internal: boolean, channel: string, args: any): boolean; _sendInternal(channel: string, ...args: any[]): void; _printToPDF(options: any): Promise; @@ -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; // 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; groupsMap: Record; getItemCount(): number; - popupAt(window: BaseWindow, frame: WebFrameMain | undefined, x: number, y: number, positioning: number, sourceType: Required['sourceType'], callback: () => void): void; + popupAt( + window: BaseWindow, + frame: WebFrameMain | undefined, + x: number, + y: number, + positioning: number, + sourceType: Required['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 { + interface IpcRendererInternal + extends NodeJS.EventEmitter, Pick { invoke(channel: string, ...args: any[]): Promise; } @@ -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; }