MicrosoftEdge / WebView2Feedback

Feedback and discussions about Microsoft Edge WebView2
https://aka.ms/webview2
452 stars 55 forks source link

Supply iframes with remote objects and web messages #139

Closed RichardSteele closed 2 years ago

RichardSteele commented 4 years ago

window.chrome.webview is undefined within an iframe. This prevents iframes from communicating with the host through objects added by ICoreWebView2::AddRemoteObject or by calling window.chrome.webview.postMessage. Instead, I have to resort to window.parent.chrome.webview or window.parent.postMessage depending on the relationship between an iframe and its parent.

AB#25546101

david-risney commented 4 years ago

Thanks @RichardSteele! We'd need to come up with a way to ensure that the developer considers the iframe content trusted. When you say relationship between iframe and parent do you mean how many iframes deep it is, or do you mean if it has a matching or non-matching origin? Can you talk about your app a bit and how you are using postMessage & remote object?

RichardSteele commented 4 years ago

Our client application integrates various third-party web applications using Microsoft's IE-based WebBrowser control. The control is customized via internet feature control keys to be "as modern" as possible. Unfortunately but understandably, some of our (potential) partners are unwilling to support IE11 (any longer).

There are two ways a web application can interact with our client. The second way is to use window.external. By implementing IDocHostUIHandler::GetExternal, we are able to provide a rich interface to our client.

One of those web applications enables external sources to interact with it by hosting iframes and providing them with a JavaScript framework. "Our" iframe uses window.external to communicate with our client. It would be great to use window.chrome.webview.remoteObjects or window.chrome.webview.postMessage for this purpose.

Right now, I use window.parent.postMessage to notify an event handler in the parent (added via ICoreWebView2::ExecuteScript) which simply relays to window.chrome.webview.remoteObjects or window.chrome.webview.postMessage.

So, by relationship between an iframe and its parent I mean ther mismatching origins. As long as an iframe isn't sandboxed, window.external is still available and it would be great to have a corresponding functionality in WebView2.

champnic commented 3 years ago

Hey all - we have enabled this functionality in an experimental API as part of 1.0.865-prerelease SDK package. Please take a look and give it a try: API Spec ICoreWebView2ExperimentalFrame type add_FrameCreated event

johnsharland77 commented 3 years ago

@champnic thanks for this, is there a way to send a postMessageAsJson from the c# component to the frame rather than it going to the main window? I have the situation where we are hosting a third party web application in our WebView2 control that then loads our web application within an iframe. We need to be able to send messages from the c# component to the iframe and receive messages back, and I have got the messages being sent back working fine using window.chrome.webview.postMessage. If I use the CoreViewView2.PostWebMessageAsJson then it goes to the main window rather than the iframe.

ukandrewc commented 3 years ago

By the looks of the docs, the simplest way would be to add your own object with AddHostObjectToScript and communicate through that.

johnsharland77 commented 3 years ago

@ukandrewc the docs show that will work for the web being able to communicate back to the c# host but doesn't show how that would work the other way. I am trying to get it so that when I call the object from c# it will trigger something to happen within the javascript. If it wasn't for the iframe then I would be using:

window.chrome.webview.addEventListener("message", async (event) => { await handleHostRequest(event.data.type, event.data.payload); });

ukandrewc commented 3 years ago

You'd need to add a JS receiver function from AddScriptToExecuteOnDocumentCreatedAsync (Which now adds to iframes) and communicate with that.

johnsharland77 commented 3 years ago

@ukandrewc Sorry if I am not getting it but in the sample on the getting started page https://docs.microsoft.com/en-us/microsoft-edge/webview2/get-started/winforms#step-8---communication-between-host-and-web-content it shows executing a script to handle the messages as await webView.CoreWebView2.AddScriptToExecuteOnDocumentCreatedAsync("window.chrome.webview.addEventListener(\'message\', event => alert(event.data));"); however I am already doing that in my application. My issue is how do I send the messages to the iframe so that the event listener is called?

If you have an example that would be very much appreciated.

ukandrewc commented 3 years ago

@johnsharland77 Sorry, that could be my mistake. Just a bit caught up in stuff at the moment, but will look at it later.

johnsharland77 commented 3 years ago

@ukandrewc thank you for your help and time. I have worked out how to do it now thanks to your pointers.

In my c# code I am injecting some javascript to post messages to the frame that has my app in it

var javascript = new System.Text.StringBuilder();
javascript.Append("if(window.chrome.webview){");
javascript.Append("  window.chrome.webview.addEventListener(\"message\", async(event) => {");
javascript.Append("  for (let index = 0; index < window.frames.length; index++)");
javascript.Append("  {");
javascript.Append("    const frame = window.frames[index];");
javascript.Append("    frame.postMessage(event.data, \"myurl\");");
javascript.Append("    }");
javascript.Append("  });");
javascript.Append("};");
await webViewHost.CoreWebView2.AddScriptToExecuteOnDocumentCreatedAsync(javascript.ToString());

and then in my app I use the window.addEventListener to subscribe to the messages:

window.addEventListener("message", async (event) => {
    if (event !== undefined) {
      ...
    }
  });
ukandrewc commented 3 years ago

@johnsharland77 That's good, I could initially see a way, then got bogged down in another issue and couldn't remember what I had first thought of ;-)

PS: Have a look at this to save all that appending.

champnic commented 3 years ago

@johnsharland77 Sorry I should have been clearer - the postMessage part of this work is still in progress, but we do intend to have the ability to send messages directly to an iframe.

panxn commented 3 years ago

@champnic Do you mean your team plans to support direct communication between c++ and iframe? We have similar requirement, it will be better if we can avoid using main frame as proxy between c++ and iframe.

champnic commented 3 years ago

We do have plans for direct communication between C++ and iframe, using both postMessage and ExecuteScript - I'm just not sure what the timeline is yet for that work.

champnic commented 2 years ago

This support is now available in 1.0.1083-prerelease+: https://docs.microsoft.com/en-us/dotnet/api/microsoft.web.webview2.core.corewebview2frame.postwebmessageasjson?view=webview2-dotnet-1.0.1083-prerelease

Thanks!

nirdil commented 1 year ago

Is the window.chrome.webview.postMessage supposed to be working for iframes? I think I did everything right but I'm just not receiving any WebMessages. Here's what I did -

I debugged the JS side and I'm not seeing any problems, the script has the postMessage interface available, and it sends the message with no apparent problem. I debugged the .NET side and I get the FrameCreated event and I register for WebMessagres with no apparent problem.

Eventually, I used the following workaround to look up the target function in JS, would love to know of any downside (() => {win = window; while (win.parent != win) win = win.parent; return win.chrome.webview.postMessage;})"