tauri-apps / tauri

Build smaller, faster, and more secure desktop applications with a web frontend.
https://tauri.app
Apache License 2.0
81.93k stars 2.46k forks source link

[bug] Iframe is not able to receive invoke callbacks #6204

Open gamgi opened 1 year ago

gamgi commented 1 year ago

Describe the bug

I am mounting an iframe ("child") within a Tauri app. I control both the parent and child and they both are rendered via tauri's vite build. Both the parent and the child have access to window.__TAURI__ and related methods.

The problem is that I'm not able to invoke handlers from the iframe. Using invoke from the ifram produces the following warning in the developer console:

[TAURI] Couldn't find callback id 90962087 in window. This happens when the app is reloaded while Rust is running an asynchronous operation.

It seems that iframe invoke successfully registers callbacks and calls the rust handlers, which are successfully run. However, when rust tries to return the response, the callback is caught by the parent window instead of the iframe. The parent does not have the callbacks registered, hence the warning.

Is there some way to get Tauri to work with this setup? One way to go about this would be to prefix the callback handlers by frame or window?

Reproduction

Repro:

  1. Clone repo at https://github.com/gamgi/repro-tauri and enter its root.
  2. Install prerequisites npm install
  3. Start the tauri app cargo tauri dev
  4. In parent greet box, type name and press greet (it works)
  5. In child greet box, type name and press greet (it does not work)
  6. Open console, see error [TAURI] Couldn't find callback id 90962087 in window. This happens when the app is reloaded while Rust is running an asynchronous operation.

Additional verifications:

  1. The iframe does have tauri IPC defined, this can be verified by document.querySelector("#root > div > iframe").contentWindow.__TAURI__.
  2. The iframe is able to call tauri, which can be verified by the "Rust backend called with ..." log from rust

Expected behavior

I expect the child window to be able to invoke tauri handlers from the iframe and to get a response.

Platform and versions

Environment › OS: Mac OS 10.15.7 X64 › Node.js: 18.12.1 › npm: 8.19.2 › pnpm: Not installed! › yarn: Not installed! › rustup: 1.25.1 › rustc: 1.66.1 › cargo: 1.66.1 › Rust toolchain: 1.66.1-x86_64-apple-darwin

Packages › @tauri-apps/cli [NPM]: 1.2.3 › @tauri-apps/api [NPM]: 1.2.0 › tauri [RUST]: 1.2.4, › tauri-build [RUST]: 1.2.1, › tao [RUST]: 0.15.8, › wry [RUST]: 0.23.4,

App › build-type: bundle › CSP: unset › distDir: ../dist › devPath: http://localhost:1420/ › framework: React › bundler: Vite

App directory structure ├─ node_modules ├─ public ├─ src-tauri ├─ .git ├─ .vscode └─ src

Stack trace

`[TAURI] Couldn't find callback id 90962087 in window. This happens when the app is reloaded while Rust is running an asynchronous operation.`

Additional context

No response

gamgi commented 1 year ago

Indeed, the child iframe is able to invoke tauri commands, but the callbacks are fired on root window instead of the iframe, resulting in Couldn't find callback id ... in window. This seems like a bug.

The workaround is to use a custom invoke initialization script using tauri::Builder::invoke_system(invoke_initialization_script, ...) which adds proxies from the parent window to the iframe.

I have a working implementation outlined in this commit.

chippers commented 1 year ago

Hi, just leaving more information here because I'm familiar with the topic. WebKit (macOS and Linux) actually supports an option that makes all invoking be able to happen on a parent or a child, separately. It allows you to create and use callbacks solely on the child frame. Unfortunately Webview2 (Windows) does not support something like this (at the last time I checked which is around Tauri 1.0) and all callbacks get received by the parent. Because of this, the feature is not enabled on WebKit to ensure the same behavior across platforms.

Your solution probably works just fine if the iframe is not sandboxed (I didn't run your repro but that commit diff makes sense) but might break if the sandbox is activated for the iframe. An alternative solution that works with the sandbox is to use postMessage to communicate between the parent and child. This is actually how the Isolation Pattern is implemented, where the parent IPC potentially sets up communication with the child frame. The key generation and encryption stuff is specific to the Isolation Pattern, but you can see how they are using postMessage to communicate between them.

Perhaps we could come up with some provided scripts or documentation example to help people implement that for their own iframe children easier.

On a side note, you might be interested in the template feature of serialize-to-javascript for your solution.

giakas commented 6 months ago

any update on this? We have a scenario with a site with an iFrame. and we need to communicate from child iFrame to the rust layer. Can someone please help with how I can setup Tauri to have the child iFrame-rust communication.

I am only building child iFrame code with Tauri/api. The host site doesn't know anything about tauri

muwoo commented 3 months ago

If your iframe and main window have the same domain name, then this may be a good method(Add the following code to the very top of iframe js initialization):

// Override the __TAURI_IPC__ function
window.__TAURI_IPC__ = (args) => {
  //The parent window callback points to the child window
  window.parent[`_${args.callback}`] = window[`_${args.callback}`];

  // Call the function of the parent window 
  window.parent.__TAURI_IPC__(args);
};

First, Tauri will mount the global window.__TAURI_IPC__ in the main window by default, but there is no such function in the child iframe. However, many of Tauri's APIs are based on the window.__TAURI_IPC__ function to communicate with the Tauri backend, so we need to set a window.__TAURI_IPC__ function for the child iframe.

However, the callback function of the communication is defined in the child iframe, so when the communication ends, the callback function cannot be found in the parent window, so we need to assign the callback function of the child iframe to the window object of the parent window.