123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703 |
- // Copyright 2019-2024 Tauri Programme within The Commons Conservancy
- // SPDX-License-Identifier: Apache-2.0
- // SPDX-License-Identifier: MIT
- /**
- * Provides APIs to create webviews, communicate with other webviews and manipulate the current webview.
- *
- * ## Webview events
- *
- * Events can be listened to using {@link Webview.listen}:
- * ```typescript
- * import { getCurrentWebview } from "@tauri-apps/api/webview";
- * getCurrentWebview().listen("my-webview-event", ({ event, payload }) => { });
- * ```
- *
- * @module
- */
- import { PhysicalPosition, PhysicalSize } from './dpi'
- import type { LogicalPosition, LogicalSize } from './dpi'
- import type { EventName, EventCallback, UnlistenFn } from './event'
- import {
- TauriEvent,
- // imported for documentation purposes
- type EventTarget,
- emit,
- emitTo,
- listen,
- once
- } from './event'
- import { invoke } from './core'
- import { Window, getCurrentWindow } from './window'
- import { WebviewWindow } from './webviewWindow'
- /** The drag and drop event types. */
- type DragDropEvent =
- | { type: 'enter'; paths: string[]; position: PhysicalPosition }
- | { type: 'over'; position: PhysicalPosition }
- | { type: 'drop'; paths: string[]; position: PhysicalPosition }
- | { type: 'leave' }
- /**
- * Get an instance of `Webview` for the current webview.
- *
- * @since 2.0.0
- */
- function getCurrentWebview(): Webview {
- return new Webview(
- getCurrentWindow(),
- window.__TAURI_INTERNALS__.metadata.currentWebview.label,
- {
- // @ts-expect-error `skip` is not defined in the public API but it is handled by the constructor
- skip: true
- }
- )
- }
- /**
- * Gets a list of instances of `Webview` for all available webviews.
- *
- * @since 2.0.0
- */
- async function getAllWebviews(): Promise<Webview[]> {
- return invoke<Array<{ windowLabel: string; label: string }>>(
- 'plugin:webview|get_all_webviews'
- ).then((webviews) =>
- webviews.map(
- (w) =>
- new Webview(
- new Window(w.windowLabel, {
- // @ts-expect-error `skip` is not defined in the public API but it is handled by the constructor
- skip: true
- }),
- w.label,
- {
- // @ts-expect-error `skip` is not defined in the public API but it is handled by the constructor
- skip: true
- }
- )
- )
- )
- }
- /** @ignore */
- // events that are emitted right here instead of by the created webview
- const localTauriEvents = ['tauri://created', 'tauri://error']
- /** @ignore */
- export type WebviewLabel = string
- /**
- * Create new webview or get a handle to an existing one.
- *
- * Webviews are identified by a *label* a unique identifier that can be used to reference it later.
- * It may only contain alphanumeric characters `a-zA-Z` plus the following special characters `-`, `/`, `:` and `_`.
- *
- * @example
- * ```typescript
- * import { Window } from "@tauri-apps/api/window"
- * import { Webview } from "@tauri-apps/api/webview"
- *
- * const appWindow = new Window('uniqueLabel');
- *
- * // loading embedded asset:
- * const webview = new Webview(appWindow, 'theUniqueLabel', {
- * url: 'path/to/page.html'
- * });
- * // alternatively, load a remote URL:
- * const webview = new Webview(appWindow, 'theUniqueLabel', {
- * url: 'https://github.com/tauri-apps/tauri'
- * });
- *
- * webview.once('tauri://created', function () {
- * // webview successfully created
- * });
- * webview.once('tauri://error', function (e) {
- * // an error happened creating the webview
- * });
- *
- * // emit an event to the backend
- * await webview.emit("some-event", "data");
- * // listen to an event from the backend
- * const unlisten = await webview.listen("event-name", e => {});
- * unlisten();
- * ```
- *
- * @since 2.0.0
- */
- class Webview {
- /** The webview label. It is a unique identifier for the webview, can be used to reference it later. */
- label: WebviewLabel
- /** The window hosting this webview. */
- window: Window
- /** Local event listeners. */
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- listeners: Record<string, Array<EventCallback<any>>>
- /**
- * Creates a new Webview.
- * @example
- * ```typescript
- * import { Window } from '@tauri-apps/api/window'
- * import { Webview } from '@tauri-apps/api/webview'
- * const appWindow = new Window('my-label')
- * const webview = new Webview(appWindow, 'my-label', {
- * url: 'https://github.com/tauri-apps/tauri'
- * });
- * webview.once('tauri://created', function () {
- * // webview successfully created
- * });
- * webview.once('tauri://error', function (e) {
- * // an error happened creating the webview
- * });
- * ```
- *
- * @param window the window to add this webview to.
- * @param label The unique webview label. Must be alphanumeric: `a-zA-Z-/:_`.
- * @returns The {@link Webview} instance to communicate with the webview.
- */
- constructor(window: Window, label: WebviewLabel, options: WebviewOptions) {
- this.window = window
- this.label = label
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
- this.listeners = Object.create(null)
- // @ts-expect-error `skip` is not a public API so it is not defined in WebviewOptions
- if (!options?.skip) {
- invoke('plugin:webview|create_webview', {
- windowLabel: window.label,
- label,
- options
- })
- .then(async () => this.emit('tauri://created'))
- .catch(async (e: string) => this.emit('tauri://error', e))
- }
- }
- /**
- * Gets the Webview for the webview associated with the given label.
- * @example
- * ```typescript
- * import { Webview } from '@tauri-apps/api/webview';
- * const mainWebview = Webview.getByLabel('main');
- * ```
- *
- * @param label The webview label.
- * @returns The Webview instance to communicate with the webview or null if the webview doesn't exist.
- */
- static async getByLabel(label: string): Promise<Webview | null> {
- return (await getAllWebviews()).find((w) => w.label === label) ?? null
- }
- /**
- * Get an instance of `Webview` for the current webview.
- */
- static getCurrent(): Webview {
- return getCurrentWebview()
- }
- /**
- * Gets a list of instances of `Webview` for all available webviews.
- */
- static async getAll(): Promise<Webview[]> {
- return getAllWebviews()
- }
- /**
- * Listen to an emitted event on this webview.
- *
- * @example
- * ```typescript
- * import { getCurrentWebview } from '@tauri-apps/api/webview';
- * const unlisten = await getCurrentWebview().listen<string>('state-changed', (event) => {
- * console.log(`Got error: ${payload}`);
- * });
- *
- * // you need to call unlisten if your handler goes out of scope e.g. the component is unmounted
- * unlisten();
- * ```
- *
- * @param event Event name. Must include only alphanumeric characters, `-`, `/`, `:` and `_`.
- * @param handler Event handler.
- * @returns A promise resolving to a function to unlisten to the event.
- * Note that removing the listener is required if your listener goes out of scope e.g. the component is unmounted.
- */
- async listen<T>(
- event: EventName,
- handler: EventCallback<T>
- ): Promise<UnlistenFn> {
- if (this._handleTauriEvent(event, handler)) {
- return () => {
- // eslint-disable-next-line security/detect-object-injection
- const listeners = this.listeners[event]
- listeners.splice(listeners.indexOf(handler), 1)
- }
- }
- return listen(event, handler, {
- target: { kind: 'Webview', label: this.label }
- })
- }
- /**
- * Listen to an emitted event on this webview only once.
- *
- * @example
- * ```typescript
- * import { getCurrentWebview } from '@tauri-apps/api/webview';
- * const unlisten = await getCurrent().once<null>('initialized', (event) => {
- * console.log(`Webview initialized!`);
- * });
- *
- * // you need to call unlisten if your handler goes out of scope e.g. the component is unmounted
- * unlisten();
- * ```
- *
- * @param event Event name. Must include only alphanumeric characters, `-`, `/`, `:` and `_`.
- * @param handler Event handler.
- * @returns A promise resolving to a function to unlisten to the event.
- * Note that removing the listener is required if your listener goes out of scope e.g. the component is unmounted.
- */
- async once<T>(
- event: EventName,
- handler: EventCallback<T>
- ): Promise<UnlistenFn> {
- if (this._handleTauriEvent(event, handler)) {
- return () => {
- // eslint-disable-next-line security/detect-object-injection
- const listeners = this.listeners[event]
- listeners.splice(listeners.indexOf(handler), 1)
- }
- }
- return once(event, handler, {
- target: { kind: 'Webview', label: this.label }
- })
- }
- /**
- * Emits an event to all {@link EventTarget|targets}.
- *
- * @example
- * ```typescript
- * import { getCurrentWebview } from '@tauri-apps/api/webview';
- * await getCurrentWebview().emit('webview-loaded', { loggedIn: true, token: 'authToken' });
- * ```
- *
- * @param event Event name. Must include only alphanumeric characters, `-`, `/`, `:` and `_`.
- * @param payload Event payload.
- */
- async emit(event: string, payload?: unknown): Promise<void> {
- if (localTauriEvents.includes(event)) {
- // eslint-disable-next-line
- for (const handler of this.listeners[event] || []) {
- handler({
- event,
- id: -1,
- payload
- })
- }
- return
- }
- return emit(event, payload)
- }
- /**
- * Emits an event to all {@link EventTarget|targets} matching the given target.
- *
- * @example
- * ```typescript
- * import { getCurrentWebview } from '@tauri-apps/api/webview';
- * await getCurrentWebview().emitTo('main', 'webview-loaded', { loggedIn: true, token: 'authToken' });
- * ```
- *
- * @param target Label of the target Window/Webview/WebviewWindow or raw {@link EventTarget} object.
- * @param event Event name. Must include only alphanumeric characters, `-`, `/`, `:` and `_`.
- * @param payload Event payload.
- */
- async emitTo(
- target: string | EventTarget,
- event: string,
- payload?: unknown
- ): Promise<void> {
- if (localTauriEvents.includes(event)) {
- // eslint-disable-next-line
- for (const handler of this.listeners[event] || []) {
- handler({
- event,
- id: -1,
- payload
- })
- }
- return
- }
- return emitTo(target, event, payload)
- }
- /** @ignore */
- _handleTauriEvent<T>(event: string, handler: EventCallback<T>): boolean {
- if (localTauriEvents.includes(event)) {
- if (!(event in this.listeners)) {
- // eslint-disable-next-line security/detect-object-injection
- this.listeners[event] = [handler]
- } else {
- // eslint-disable-next-line security/detect-object-injection
- this.listeners[event].push(handler)
- }
- return true
- }
- return false
- }
- // Getters
- /**
- * The position of the top-left hand corner of the webview's client area relative to the top-left hand corner of the desktop.
- * @example
- * ```typescript
- * import { getCurrentWebview } from '@tauri-apps/api/webview';
- * const position = await getCurrentWebview().position();
- * ```
- *
- * @returns The webview's position.
- */
- async position(): Promise<PhysicalPosition> {
- return invoke<{ x: number; y: number }>('plugin:webview|webview_position', {
- label: this.label
- }).then(({ x, y }) => new PhysicalPosition(x, y))
- }
- /**
- * The physical size of the webview's client area.
- * The client area is the content of the webview, excluding the title bar and borders.
- * @example
- * ```typescript
- * import { getCurrentWebview } from '@tauri-apps/api/webview';
- * const size = await getCurrentWebview().size();
- * ```
- *
- * @returns The webview's size.
- */
- async size(): Promise<PhysicalSize> {
- return invoke<{ width: number; height: number }>(
- 'plugin:webview|webview_size',
- {
- label: this.label
- }
- ).then(({ width, height }) => new PhysicalSize(width, height))
- }
- // Setters
- /**
- * Closes the webview.
- * @example
- * ```typescript
- * import { getCurrentWebview } from '@tauri-apps/api/webview';
- * await getCurrentWebview().close();
- * ```
- *
- * @returns A promise indicating the success or failure of the operation.
- */
- async close(): Promise<void> {
- return invoke('plugin:webview|close', {
- label: this.label
- })
- }
- /**
- * Resizes the webview.
- * @example
- * ```typescript
- * import { getCurrent, LogicalSize } from '@tauri-apps/api/webview';
- * await getCurrentWebview().setSize(new LogicalSize(600, 500));
- * ```
- *
- * @param size The logical or physical size.
- * @returns A promise indicating the success or failure of the operation.
- */
- async setSize(size: LogicalSize | PhysicalSize): Promise<void> {
- if (!size || (size.type !== 'Logical' && size.type !== 'Physical')) {
- throw new Error(
- 'the `size` argument must be either a LogicalSize or a PhysicalSize instance'
- )
- }
- const value = {} as Record<string, unknown>
- value[`${size.type}`] = {
- width: size.width,
- height: size.height
- }
- return invoke('plugin:webview|set_webview_size', {
- label: this.label,
- value
- })
- }
- /**
- * Sets the webview position.
- * @example
- * ```typescript
- * import { getCurrent, LogicalPosition } from '@tauri-apps/api/webview';
- * await getCurrentWebview().setPosition(new LogicalPosition(600, 500));
- * ```
- *
- * @param position The new position, in logical or physical pixels.
- * @returns A promise indicating the success or failure of the operation.
- */
- async setPosition(
- position: LogicalPosition | PhysicalPosition
- ): Promise<void> {
- if (
- !position ||
- (position.type !== 'Logical' && position.type !== 'Physical')
- ) {
- throw new Error(
- 'the `position` argument must be either a LogicalPosition or a PhysicalPosition instance'
- )
- }
- const value = {} as Record<string, unknown>
- value[`${position.type}`] = {
- x: position.x,
- y: position.y
- }
- return invoke('plugin:webview|set_webview_position', {
- label: this.label,
- value
- })
- }
- /**
- * Bring the webview to front and focus.
- * @example
- * ```typescript
- * import { getCurrentWebview } from '@tauri-apps/api/webview';
- * await getCurrentWebview().setFocus();
- * ```
- *
- * @returns A promise indicating the success or failure of the operation.
- */
- async setFocus(): Promise<void> {
- return invoke('plugin:webview|set_webview_focus', {
- label: this.label
- })
- }
- /**
- * Set webview zoom level.
- * @example
- * ```typescript
- * import { getCurrentWebview } from '@tauri-apps/api/webview';
- * await getCurrentWebview().setZoom(1.5);
- * ```
- *
- * @returns A promise indicating the success or failure of the operation.
- */
- async setZoom(scaleFactor: number): Promise<void> {
- return invoke('plugin:webview|set_webview_zoom', {
- label: this.label,
- value: scaleFactor
- })
- }
- /**
- * Moves this webview to the given label.
- * @example
- * ```typescript
- * import { getCurrentWebview } from '@tauri-apps/api/webview';
- * await getCurrentWebview().reparent('other-window');
- * ```
- *
- * @returns A promise indicating the success or failure of the operation.
- */
- async reparent(window: Window | WebviewWindow | string): Promise<void> {
- return invoke('plugin:webview|reparent', {
- label: this.label,
- window: typeof window === 'string' ? window : window.label
- })
- }
- /**
- * Clears all browsing data for this webview.
- * @example
- * ```typescript
- * import { getCurrentWebview } from '@tauri-apps/api/webview';
- * await getCurrentWebview().clearAllBrowsingData();
- * ```
- *
- * @returns A promise indicating the success or failure of the operation.
- */
- async clearAllBrowsingData(): Promise<void> {
- return invoke('plugin:webview|clear_all_browsing_data')
- }
- // Listeners
- /**
- * Listen to a file drop event.
- * The listener is triggered when the user hovers the selected files on the webview,
- * drops the files or cancels the operation.
- *
- * @example
- * ```typescript
- * import { getCurrentWebview } from "@tauri-apps/api/webview";
- * const unlisten = await getCurrentWebview().onDragDropEvent((event) => {
- * if (event.payload.type === 'hover') {
- * console.log('User hovering', event.payload.paths);
- * } else if (event.payload.type === 'drop') {
- * console.log('User dropped', event.payload.paths);
- * } else {
- * console.log('File drop cancelled');
- * }
- * });
- *
- * // you need to call unlisten if your handler goes out of scope e.g. the component is unmounted
- * unlisten();
- * ```
- *
- * @returns A promise resolving to a function to unlisten to the event.
- * Note that removing the listener is required if your listener goes out of scope e.g. the component is unmounted.
- */
- async onDragDropEvent(
- handler: EventCallback<DragDropEvent>
- ): Promise<UnlistenFn> {
- type DragPayload = { paths: string[]; position: PhysicalPosition }
- const unlistenDragEnter = await this.listen<DragPayload>(
- TauriEvent.DRAG_ENTER,
- (event) => {
- handler({
- ...event,
- payload: {
- type: 'enter',
- paths: event.payload.paths,
- position: mapPhysicalPosition(event.payload.position)
- }
- })
- }
- )
- const unlistenDragOver = await this.listen<DragPayload>(
- TauriEvent.DRAG_OVER,
- (event) => {
- handler({
- ...event,
- payload: {
- type: 'over',
- position: mapPhysicalPosition(event.payload.position)
- }
- })
- }
- )
- const unlistenDragDrop = await this.listen<DragPayload>(
- TauriEvent.DRAG_DROP,
- (event) => {
- handler({
- ...event,
- payload: {
- type: 'drop',
- paths: event.payload.paths,
- position: mapPhysicalPosition(event.payload.position)
- }
- })
- }
- )
- const unlistenDragLeave = await this.listen<null>(
- TauriEvent.DRAG_LEAVE,
- (event) => {
- handler({ ...event, payload: { type: 'leave' } })
- }
- )
- return () => {
- unlistenDragEnter()
- unlistenDragDrop()
- unlistenDragOver()
- unlistenDragLeave()
- }
- }
- }
- function mapPhysicalPosition(m: PhysicalPosition): PhysicalPosition {
- return new PhysicalPosition(m.x, m.y)
- }
- /**
- * Configuration for the webview to create.
- *
- * @since 2.0.0
- */
- interface WebviewOptions {
- /**
- * Remote URL or local file path to open.
- *
- * - URL such as `https://github.com/tauri-apps` is opened directly on a Tauri webview.
- * - data: URL such as `data:text/html,<html>...` is only supported with the `webview-data-url` Cargo feature for the `tauri` dependency.
- * - 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).
- */
- url?: string
- /** The initial vertical position. */
- x: number
- /** The initial horizontal position. */
- y: number
- /** The initial width. */
- width: number
- /** The initial height. */
- height: number
- /**
- * Whether the webview is transparent or not.
- * Note that on `macOS` this requires the `macos-private-api` feature flag, enabled under `tauri.conf.json > app > macOSPrivateApi`.
- * WARNING: Using private APIs on `macOS` prevents your application from being accepted to the `App Store`.
- */
- transparent?: boolean
- /**
- * Whether the drag and drop is enabled or not on the webview. By default it is enabled.
- *
- * Disabling it is required to use HTML5 drag and drop on the frontend on Windows.
- */
- dragDropEnabled?: boolean
- /**
- * Whether clicking an inactive webview also clicks through to the webview on macOS.
- */
- acceptFirstMouse?: boolean
- /**
- * The user agent for the webview.
- */
- userAgent?: string
- /**
- * Whether or not the webview should be launched in incognito mode.
- *
- * #### Platform-specific
- *
- * - **Android:** Unsupported.
- */
- incognito?: boolean
- /**
- * The proxy URL for the WebView for all network requests.
- *
- * Must be either a `http://` or a `socks5://` URL.
- *
- * #### Platform-specific
- *
- * - **macOS**: Requires the `macos-proxy` feature flag and only compiles for macOS 14+.
- * */
- proxyUrl?: string
- /**
- * Whether page zooming by hotkeys is enabled
- *
- * ## Platform-specific:
- *
- * - **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.
- * - **MacOS / Linux**: Injects a polyfill that zooms in and out with `ctrl/command` + `-/=`,
- * 20% in each step, ranging from 20% to 1000%. Requires `webview:allow-set-webview-zoom` permission
- *
- * - **Android / iOS**: Unsupported.
- */
- zoomHotkeysEnabled?: boolean
- }
- export { Webview, getCurrentWebview, getAllWebviews }
- export type { DragDropEvent, WebviewOptions }
|