webview.ts 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703
  1. // Copyright 2019-2024 Tauri Programme within The Commons Conservancy
  2. // SPDX-License-Identifier: Apache-2.0
  3. // SPDX-License-Identifier: MIT
  4. /**
  5. * Provides APIs to create webviews, communicate with other webviews and manipulate the current webview.
  6. *
  7. * ## Webview events
  8. *
  9. * Events can be listened to using {@link Webview.listen}:
  10. * ```typescript
  11. * import { getCurrentWebview } from "@tauri-apps/api/webview";
  12. * getCurrentWebview().listen("my-webview-event", ({ event, payload }) => { });
  13. * ```
  14. *
  15. * @module
  16. */
  17. import { PhysicalPosition, PhysicalSize } from './dpi'
  18. import type { LogicalPosition, LogicalSize } from './dpi'
  19. import type { EventName, EventCallback, UnlistenFn } from './event'
  20. import {
  21. TauriEvent,
  22. // imported for documentation purposes
  23. type EventTarget,
  24. emit,
  25. emitTo,
  26. listen,
  27. once
  28. } from './event'
  29. import { invoke } from './core'
  30. import { Window, getCurrentWindow } from './window'
  31. import { WebviewWindow } from './webviewWindow'
  32. /** The drag and drop event types. */
  33. type DragDropEvent =
  34. | { type: 'enter'; paths: string[]; position: PhysicalPosition }
  35. | { type: 'over'; position: PhysicalPosition }
  36. | { type: 'drop'; paths: string[]; position: PhysicalPosition }
  37. | { type: 'leave' }
  38. /**
  39. * Get an instance of `Webview` for the current webview.
  40. *
  41. * @since 2.0.0
  42. */
  43. function getCurrentWebview(): Webview {
  44. return new Webview(
  45. getCurrentWindow(),
  46. window.__TAURI_INTERNALS__.metadata.currentWebview.label,
  47. {
  48. // @ts-expect-error `skip` is not defined in the public API but it is handled by the constructor
  49. skip: true
  50. }
  51. )
  52. }
  53. /**
  54. * Gets a list of instances of `Webview` for all available webviews.
  55. *
  56. * @since 2.0.0
  57. */
  58. async function getAllWebviews(): Promise<Webview[]> {
  59. return invoke<Array<{ windowLabel: string; label: string }>>(
  60. 'plugin:webview|get_all_webviews'
  61. ).then((webviews) =>
  62. webviews.map(
  63. (w) =>
  64. new Webview(
  65. new Window(w.windowLabel, {
  66. // @ts-expect-error `skip` is not defined in the public API but it is handled by the constructor
  67. skip: true
  68. }),
  69. w.label,
  70. {
  71. // @ts-expect-error `skip` is not defined in the public API but it is handled by the constructor
  72. skip: true
  73. }
  74. )
  75. )
  76. )
  77. }
  78. /** @ignore */
  79. // events that are emitted right here instead of by the created webview
  80. const localTauriEvents = ['tauri://created', 'tauri://error']
  81. /** @ignore */
  82. export type WebviewLabel = string
  83. /**
  84. * Create new webview or get a handle to an existing one.
  85. *
  86. * Webviews are identified by a *label* a unique identifier that can be used to reference it later.
  87. * It may only contain alphanumeric characters `a-zA-Z` plus the following special characters `-`, `/`, `:` and `_`.
  88. *
  89. * @example
  90. * ```typescript
  91. * import { Window } from "@tauri-apps/api/window"
  92. * import { Webview } from "@tauri-apps/api/webview"
  93. *
  94. * const appWindow = new Window('uniqueLabel');
  95. *
  96. * // loading embedded asset:
  97. * const webview = new Webview(appWindow, 'theUniqueLabel', {
  98. * url: 'path/to/page.html'
  99. * });
  100. * // alternatively, load a remote URL:
  101. * const webview = new Webview(appWindow, 'theUniqueLabel', {
  102. * url: 'https://github.com/tauri-apps/tauri'
  103. * });
  104. *
  105. * webview.once('tauri://created', function () {
  106. * // webview successfully created
  107. * });
  108. * webview.once('tauri://error', function (e) {
  109. * // an error happened creating the webview
  110. * });
  111. *
  112. * // emit an event to the backend
  113. * await webview.emit("some-event", "data");
  114. * // listen to an event from the backend
  115. * const unlisten = await webview.listen("event-name", e => {});
  116. * unlisten();
  117. * ```
  118. *
  119. * @since 2.0.0
  120. */
  121. class Webview {
  122. /** The webview label. It is a unique identifier for the webview, can be used to reference it later. */
  123. label: WebviewLabel
  124. /** The window hosting this webview. */
  125. window: Window
  126. /** Local event listeners. */
  127. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  128. listeners: Record<string, Array<EventCallback<any>>>
  129. /**
  130. * Creates a new Webview.
  131. * @example
  132. * ```typescript
  133. * import { Window } from '@tauri-apps/api/window'
  134. * import { Webview } from '@tauri-apps/api/webview'
  135. * const appWindow = new Window('my-label')
  136. * const webview = new Webview(appWindow, 'my-label', {
  137. * url: 'https://github.com/tauri-apps/tauri'
  138. * });
  139. * webview.once('tauri://created', function () {
  140. * // webview successfully created
  141. * });
  142. * webview.once('tauri://error', function (e) {
  143. * // an error happened creating the webview
  144. * });
  145. * ```
  146. *
  147. * @param window the window to add this webview to.
  148. * @param label The unique webview label. Must be alphanumeric: `a-zA-Z-/:_`.
  149. * @returns The {@link Webview} instance to communicate with the webview.
  150. */
  151. constructor(window: Window, label: WebviewLabel, options: WebviewOptions) {
  152. this.window = window
  153. this.label = label
  154. // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
  155. this.listeners = Object.create(null)
  156. // @ts-expect-error `skip` is not a public API so it is not defined in WebviewOptions
  157. if (!options?.skip) {
  158. invoke('plugin:webview|create_webview', {
  159. windowLabel: window.label,
  160. label,
  161. options
  162. })
  163. .then(async () => this.emit('tauri://created'))
  164. .catch(async (e: string) => this.emit('tauri://error', e))
  165. }
  166. }
  167. /**
  168. * Gets the Webview for the webview associated with the given label.
  169. * @example
  170. * ```typescript
  171. * import { Webview } from '@tauri-apps/api/webview';
  172. * const mainWebview = Webview.getByLabel('main');
  173. * ```
  174. *
  175. * @param label The webview label.
  176. * @returns The Webview instance to communicate with the webview or null if the webview doesn't exist.
  177. */
  178. static async getByLabel(label: string): Promise<Webview | null> {
  179. return (await getAllWebviews()).find((w) => w.label === label) ?? null
  180. }
  181. /**
  182. * Get an instance of `Webview` for the current webview.
  183. */
  184. static getCurrent(): Webview {
  185. return getCurrentWebview()
  186. }
  187. /**
  188. * Gets a list of instances of `Webview` for all available webviews.
  189. */
  190. static async getAll(): Promise<Webview[]> {
  191. return getAllWebviews()
  192. }
  193. /**
  194. * Listen to an emitted event on this webview.
  195. *
  196. * @example
  197. * ```typescript
  198. * import { getCurrentWebview } from '@tauri-apps/api/webview';
  199. * const unlisten = await getCurrentWebview().listen<string>('state-changed', (event) => {
  200. * console.log(`Got error: ${payload}`);
  201. * });
  202. *
  203. * // you need to call unlisten if your handler goes out of scope e.g. the component is unmounted
  204. * unlisten();
  205. * ```
  206. *
  207. * @param event Event name. Must include only alphanumeric characters, `-`, `/`, `:` and `_`.
  208. * @param handler Event handler.
  209. * @returns A promise resolving to a function to unlisten to the event.
  210. * Note that removing the listener is required if your listener goes out of scope e.g. the component is unmounted.
  211. */
  212. async listen<T>(
  213. event: EventName,
  214. handler: EventCallback<T>
  215. ): Promise<UnlistenFn> {
  216. if (this._handleTauriEvent(event, handler)) {
  217. return () => {
  218. // eslint-disable-next-line security/detect-object-injection
  219. const listeners = this.listeners[event]
  220. listeners.splice(listeners.indexOf(handler), 1)
  221. }
  222. }
  223. return listen(event, handler, {
  224. target: { kind: 'Webview', label: this.label }
  225. })
  226. }
  227. /**
  228. * Listen to an emitted event on this webview only once.
  229. *
  230. * @example
  231. * ```typescript
  232. * import { getCurrentWebview } from '@tauri-apps/api/webview';
  233. * const unlisten = await getCurrent().once<null>('initialized', (event) => {
  234. * console.log(`Webview initialized!`);
  235. * });
  236. *
  237. * // you need to call unlisten if your handler goes out of scope e.g. the component is unmounted
  238. * unlisten();
  239. * ```
  240. *
  241. * @param event Event name. Must include only alphanumeric characters, `-`, `/`, `:` and `_`.
  242. * @param handler Event handler.
  243. * @returns A promise resolving to a function to unlisten to the event.
  244. * Note that removing the listener is required if your listener goes out of scope e.g. the component is unmounted.
  245. */
  246. async once<T>(
  247. event: EventName,
  248. handler: EventCallback<T>
  249. ): Promise<UnlistenFn> {
  250. if (this._handleTauriEvent(event, handler)) {
  251. return () => {
  252. // eslint-disable-next-line security/detect-object-injection
  253. const listeners = this.listeners[event]
  254. listeners.splice(listeners.indexOf(handler), 1)
  255. }
  256. }
  257. return once(event, handler, {
  258. target: { kind: 'Webview', label: this.label }
  259. })
  260. }
  261. /**
  262. * Emits an event to all {@link EventTarget|targets}.
  263. *
  264. * @example
  265. * ```typescript
  266. * import { getCurrentWebview } from '@tauri-apps/api/webview';
  267. * await getCurrentWebview().emit('webview-loaded', { loggedIn: true, token: 'authToken' });
  268. * ```
  269. *
  270. * @param event Event name. Must include only alphanumeric characters, `-`, `/`, `:` and `_`.
  271. * @param payload Event payload.
  272. */
  273. async emit(event: string, payload?: unknown): Promise<void> {
  274. if (localTauriEvents.includes(event)) {
  275. // eslint-disable-next-line
  276. for (const handler of this.listeners[event] || []) {
  277. handler({
  278. event,
  279. id: -1,
  280. payload
  281. })
  282. }
  283. return
  284. }
  285. return emit(event, payload)
  286. }
  287. /**
  288. * Emits an event to all {@link EventTarget|targets} matching the given target.
  289. *
  290. * @example
  291. * ```typescript
  292. * import { getCurrentWebview } from '@tauri-apps/api/webview';
  293. * await getCurrentWebview().emitTo('main', 'webview-loaded', { loggedIn: true, token: 'authToken' });
  294. * ```
  295. *
  296. * @param target Label of the target Window/Webview/WebviewWindow or raw {@link EventTarget} object.
  297. * @param event Event name. Must include only alphanumeric characters, `-`, `/`, `:` and `_`.
  298. * @param payload Event payload.
  299. */
  300. async emitTo(
  301. target: string | EventTarget,
  302. event: string,
  303. payload?: unknown
  304. ): Promise<void> {
  305. if (localTauriEvents.includes(event)) {
  306. // eslint-disable-next-line
  307. for (const handler of this.listeners[event] || []) {
  308. handler({
  309. event,
  310. id: -1,
  311. payload
  312. })
  313. }
  314. return
  315. }
  316. return emitTo(target, event, payload)
  317. }
  318. /** @ignore */
  319. _handleTauriEvent<T>(event: string, handler: EventCallback<T>): boolean {
  320. if (localTauriEvents.includes(event)) {
  321. if (!(event in this.listeners)) {
  322. // eslint-disable-next-line security/detect-object-injection
  323. this.listeners[event] = [handler]
  324. } else {
  325. // eslint-disable-next-line security/detect-object-injection
  326. this.listeners[event].push(handler)
  327. }
  328. return true
  329. }
  330. return false
  331. }
  332. // Getters
  333. /**
  334. * The position of the top-left hand corner of the webview's client area relative to the top-left hand corner of the desktop.
  335. * @example
  336. * ```typescript
  337. * import { getCurrentWebview } from '@tauri-apps/api/webview';
  338. * const position = await getCurrentWebview().position();
  339. * ```
  340. *
  341. * @returns The webview's position.
  342. */
  343. async position(): Promise<PhysicalPosition> {
  344. return invoke<{ x: number; y: number }>('plugin:webview|webview_position', {
  345. label: this.label
  346. }).then(({ x, y }) => new PhysicalPosition(x, y))
  347. }
  348. /**
  349. * The physical size of the webview's client area.
  350. * The client area is the content of the webview, excluding the title bar and borders.
  351. * @example
  352. * ```typescript
  353. * import { getCurrentWebview } from '@tauri-apps/api/webview';
  354. * const size = await getCurrentWebview().size();
  355. * ```
  356. *
  357. * @returns The webview's size.
  358. */
  359. async size(): Promise<PhysicalSize> {
  360. return invoke<{ width: number; height: number }>(
  361. 'plugin:webview|webview_size',
  362. {
  363. label: this.label
  364. }
  365. ).then(({ width, height }) => new PhysicalSize(width, height))
  366. }
  367. // Setters
  368. /**
  369. * Closes the webview.
  370. * @example
  371. * ```typescript
  372. * import { getCurrentWebview } from '@tauri-apps/api/webview';
  373. * await getCurrentWebview().close();
  374. * ```
  375. *
  376. * @returns A promise indicating the success or failure of the operation.
  377. */
  378. async close(): Promise<void> {
  379. return invoke('plugin:webview|close', {
  380. label: this.label
  381. })
  382. }
  383. /**
  384. * Resizes the webview.
  385. * @example
  386. * ```typescript
  387. * import { getCurrent, LogicalSize } from '@tauri-apps/api/webview';
  388. * await getCurrentWebview().setSize(new LogicalSize(600, 500));
  389. * ```
  390. *
  391. * @param size The logical or physical size.
  392. * @returns A promise indicating the success or failure of the operation.
  393. */
  394. async setSize(size: LogicalSize | PhysicalSize): Promise<void> {
  395. if (!size || (size.type !== 'Logical' && size.type !== 'Physical')) {
  396. throw new Error(
  397. 'the `size` argument must be either a LogicalSize or a PhysicalSize instance'
  398. )
  399. }
  400. const value = {} as Record<string, unknown>
  401. value[`${size.type}`] = {
  402. width: size.width,
  403. height: size.height
  404. }
  405. return invoke('plugin:webview|set_webview_size', {
  406. label: this.label,
  407. value
  408. })
  409. }
  410. /**
  411. * Sets the webview position.
  412. * @example
  413. * ```typescript
  414. * import { getCurrent, LogicalPosition } from '@tauri-apps/api/webview';
  415. * await getCurrentWebview().setPosition(new LogicalPosition(600, 500));
  416. * ```
  417. *
  418. * @param position The new position, in logical or physical pixels.
  419. * @returns A promise indicating the success or failure of the operation.
  420. */
  421. async setPosition(
  422. position: LogicalPosition | PhysicalPosition
  423. ): Promise<void> {
  424. if (
  425. !position ||
  426. (position.type !== 'Logical' && position.type !== 'Physical')
  427. ) {
  428. throw new Error(
  429. 'the `position` argument must be either a LogicalPosition or a PhysicalPosition instance'
  430. )
  431. }
  432. const value = {} as Record<string, unknown>
  433. value[`${position.type}`] = {
  434. x: position.x,
  435. y: position.y
  436. }
  437. return invoke('plugin:webview|set_webview_position', {
  438. label: this.label,
  439. value
  440. })
  441. }
  442. /**
  443. * Bring the webview to front and focus.
  444. * @example
  445. * ```typescript
  446. * import { getCurrentWebview } from '@tauri-apps/api/webview';
  447. * await getCurrentWebview().setFocus();
  448. * ```
  449. *
  450. * @returns A promise indicating the success or failure of the operation.
  451. */
  452. async setFocus(): Promise<void> {
  453. return invoke('plugin:webview|set_webview_focus', {
  454. label: this.label
  455. })
  456. }
  457. /**
  458. * Set webview zoom level.
  459. * @example
  460. * ```typescript
  461. * import { getCurrentWebview } from '@tauri-apps/api/webview';
  462. * await getCurrentWebview().setZoom(1.5);
  463. * ```
  464. *
  465. * @returns A promise indicating the success or failure of the operation.
  466. */
  467. async setZoom(scaleFactor: number): Promise<void> {
  468. return invoke('plugin:webview|set_webview_zoom', {
  469. label: this.label,
  470. value: scaleFactor
  471. })
  472. }
  473. /**
  474. * Moves this webview to the given label.
  475. * @example
  476. * ```typescript
  477. * import { getCurrentWebview } from '@tauri-apps/api/webview';
  478. * await getCurrentWebview().reparent('other-window');
  479. * ```
  480. *
  481. * @returns A promise indicating the success or failure of the operation.
  482. */
  483. async reparent(window: Window | WebviewWindow | string): Promise<void> {
  484. return invoke('plugin:webview|reparent', {
  485. label: this.label,
  486. window: typeof window === 'string' ? window : window.label
  487. })
  488. }
  489. /**
  490. * Clears all browsing data for this webview.
  491. * @example
  492. * ```typescript
  493. * import { getCurrentWebview } from '@tauri-apps/api/webview';
  494. * await getCurrentWebview().clearAllBrowsingData();
  495. * ```
  496. *
  497. * @returns A promise indicating the success or failure of the operation.
  498. */
  499. async clearAllBrowsingData(): Promise<void> {
  500. return invoke('plugin:webview|clear_all_browsing_data')
  501. }
  502. // Listeners
  503. /**
  504. * Listen to a file drop event.
  505. * The listener is triggered when the user hovers the selected files on the webview,
  506. * drops the files or cancels the operation.
  507. *
  508. * @example
  509. * ```typescript
  510. * import { getCurrentWebview } from "@tauri-apps/api/webview";
  511. * const unlisten = await getCurrentWebview().onDragDropEvent((event) => {
  512. * if (event.payload.type === 'hover') {
  513. * console.log('User hovering', event.payload.paths);
  514. * } else if (event.payload.type === 'drop') {
  515. * console.log('User dropped', event.payload.paths);
  516. * } else {
  517. * console.log('File drop cancelled');
  518. * }
  519. * });
  520. *
  521. * // you need to call unlisten if your handler goes out of scope e.g. the component is unmounted
  522. * unlisten();
  523. * ```
  524. *
  525. * @returns A promise resolving to a function to unlisten to the event.
  526. * Note that removing the listener is required if your listener goes out of scope e.g. the component is unmounted.
  527. */
  528. async onDragDropEvent(
  529. handler: EventCallback<DragDropEvent>
  530. ): Promise<UnlistenFn> {
  531. type DragPayload = { paths: string[]; position: PhysicalPosition }
  532. const unlistenDragEnter = await this.listen<DragPayload>(
  533. TauriEvent.DRAG_ENTER,
  534. (event) => {
  535. handler({
  536. ...event,
  537. payload: {
  538. type: 'enter',
  539. paths: event.payload.paths,
  540. position: mapPhysicalPosition(event.payload.position)
  541. }
  542. })
  543. }
  544. )
  545. const unlistenDragOver = await this.listen<DragPayload>(
  546. TauriEvent.DRAG_OVER,
  547. (event) => {
  548. handler({
  549. ...event,
  550. payload: {
  551. type: 'over',
  552. position: mapPhysicalPosition(event.payload.position)
  553. }
  554. })
  555. }
  556. )
  557. const unlistenDragDrop = await this.listen<DragPayload>(
  558. TauriEvent.DRAG_DROP,
  559. (event) => {
  560. handler({
  561. ...event,
  562. payload: {
  563. type: 'drop',
  564. paths: event.payload.paths,
  565. position: mapPhysicalPosition(event.payload.position)
  566. }
  567. })
  568. }
  569. )
  570. const unlistenDragLeave = await this.listen<null>(
  571. TauriEvent.DRAG_LEAVE,
  572. (event) => {
  573. handler({ ...event, payload: { type: 'leave' } })
  574. }
  575. )
  576. return () => {
  577. unlistenDragEnter()
  578. unlistenDragDrop()
  579. unlistenDragOver()
  580. unlistenDragLeave()
  581. }
  582. }
  583. }
  584. function mapPhysicalPosition(m: PhysicalPosition): PhysicalPosition {
  585. return new PhysicalPosition(m.x, m.y)
  586. }
  587. /**
  588. * Configuration for the webview to create.
  589. *
  590. * @since 2.0.0
  591. */
  592. interface WebviewOptions {
  593. /**
  594. * Remote URL or local file path to open.
  595. *
  596. * - URL such as `https://github.com/tauri-apps` is opened directly on a Tauri webview.
  597. * - data: URL such as `data:text/html,<html>...` is only supported with the `webview-data-url` Cargo feature for the `tauri` dependency.
  598. * - local file path or route such as `/path/to/page.html` or `/users` is appended to the application URL (the devServer URL on development, or `tauri://localhost/` and `https://tauri.localhost/` on production).
  599. */
  600. url?: string
  601. /** The initial vertical position. */
  602. x: number
  603. /** The initial horizontal position. */
  604. y: number
  605. /** The initial width. */
  606. width: number
  607. /** The initial height. */
  608. height: number
  609. /**
  610. * Whether the webview is transparent or not.
  611. * Note that on `macOS` this requires the `macos-private-api` feature flag, enabled under `tauri.conf.json > app > macOSPrivateApi`.
  612. * WARNING: Using private APIs on `macOS` prevents your application from being accepted to the `App Store`.
  613. */
  614. transparent?: boolean
  615. /**
  616. * Whether the drag and drop is enabled or not on the webview. By default it is enabled.
  617. *
  618. * Disabling it is required to use HTML5 drag and drop on the frontend on Windows.
  619. */
  620. dragDropEnabled?: boolean
  621. /**
  622. * Whether clicking an inactive webview also clicks through to the webview on macOS.
  623. */
  624. acceptFirstMouse?: boolean
  625. /**
  626. * The user agent for the webview.
  627. */
  628. userAgent?: string
  629. /**
  630. * Whether or not the webview should be launched in incognito mode.
  631. *
  632. * #### Platform-specific
  633. *
  634. * - **Android:** Unsupported.
  635. */
  636. incognito?: boolean
  637. /**
  638. * The proxy URL for the WebView for all network requests.
  639. *
  640. * Must be either a `http://` or a `socks5://` URL.
  641. *
  642. * #### Platform-specific
  643. *
  644. * - **macOS**: Requires the `macos-proxy` feature flag and only compiles for macOS 14+.
  645. * */
  646. proxyUrl?: string
  647. /**
  648. * Whether page zooming by hotkeys is enabled
  649. *
  650. * ## Platform-specific:
  651. *
  652. * - **Windows**: Controls WebView2's [`IsZoomControlEnabled`](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/winrt/microsoft_web_webview2_core/corewebview2settings?view=webview2-winrt-1.0.2420.47#iszoomcontrolenabled) setting.
  653. * - **MacOS / Linux**: Injects a polyfill that zooms in and out with `ctrl/command` + `-/=`,
  654. * 20% in each step, ranging from 20% to 1000%. Requires `webview:allow-set-webview-zoom` permission
  655. *
  656. * - **Android / iOS**: Unsupported.
  657. */
  658. zoomHotkeysEnabled?: boolean
  659. }
  660. export { Webview, getCurrentWebview, getAllWebviews }
  661. export type { DragDropEvent, WebviewOptions }