Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

API for host objects for iframes #1122

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
275 changes: 275 additions & 0 deletions specs/AddHostObjectToScriptWithOrigins.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,275 @@
# Background
Currently WebView2 supports adding host objects to script from client application
and then later those objects can be accessed from the JavaScript of the top level frame
using `window.chrome.webview.hostObjects.{name}`.
Documentation can be found [here](https://docs.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2?view=webview2-1.0.705.50#addhostobjecttoscript).

We were asked to support this feature in iframes including
child frames that don't match the origin of the parent. In this document we describe API
additions to support host objects in iframes. We'd appreciate your feedback.


# Description
The FrameCreated event is raised when an iframe is created and before starting any
navigation and content loading. The FrameDeleted event is raised when an iframe is deleted
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm guessing that a "deleted" frame is one that has been removed from the document, but may not have been destroyed. Is there a FrameAdded event that lets me know when it has been added back to the document? Right now, there seems to be a mismatch in event pairing.

✓ Created ✘ Destroyed
✘ Added ✓ Deleted

Another way of looking at it is with a frame lifetime diagram:

Sequence

but we are told only about Created and Deleted. So if we create some tracking object in Created, we might leak it if it never got Added. And if a frame is Deleted, we can prematurely destroy the tracking object if the frame is subsequently Added back.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's use Created and Destroyed for naming based on conversation.

or the document containing that iframe is destroyed.
By providing event handler for frame created event,
user can obtain the iframe and add host objects to it specifying the list of origins
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens if I use a malformed origin, like https://microsoft.com/jobs? Is it silently ignored? Is it stripped down to https://microsoft.com? Is the call rejected?

Is https://microsoft.com/ a legal origin, with the trailing slash?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Document and implement that less than an origin (scheme, host, port) or more than an origin, or just not any of those things will fail.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(with allowance for optional trailing slash)

using `AddHostObjectToScriptWithOrigins` method.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume name collisions are resolved the same way as AddHostObjectToScript, whatever that is. (AddHostObjectToScript does not document how name collisions are resolved. Second caller wins? second caller fails? Second caller silently ignored?)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Update docs here and for AddHostObjectToScript how this works (or make it clearer if its already in the existing method docs).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What origins does AddHostObjectToScript allow? Is AddHostObjectToScript(name, o) equivalent to

  • AddHostObjectToScriptWithOrigins(name, o, new[] {}) - disallow all
  • AddHostObjectToScriptWithOrigins(name, o, new[] { "*" }) - allow all
  • AddHostObjectToScriptWithOrigins(name, o, new[] { coreWebView2.Source }) - allow same origin as document
  • Something else?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Update docs for old method to say what equivalent is in new method

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I found the name AddHostObjectToScriptWithOrigins confusing because it sounds like the host object is the one with origins. I thought this was "Treat the host object as having come from any of these origins, and determine whether the frame can access the object by resolving the frame's source against these origins, following standard CORS rules."

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For .NET & WinRT lets name this AddHostObjectToScript (like overload) and then in the ABI and in COM AddHostObjectToScriptWithAllowedOrigins.

When script in the iframe tries to access a host object the origin of the iframe will be
checked against the provided list. The Frame object has a Name property with the value of the
name attribute from the iframe html tag declaring it. The NameChanged event is raised when

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the principle of least surprise here? Meaning if I don't see that event (or somehow forget to hook it up) and now the frame has a new name... is that most likely to be a bug (possibly a security bug) or is it just some obscure edge-cases that care? Reason for asking: if it's more likely to be a (security) bug, then make the de-registration automatic and provide a way to opt-out of it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't removing the object when the name changes be closing the door after the horse has bolted? The page already got the object and could have uploaded your sensitive information to a secret server. Revoking access after the name changed won't delete your sensitive information from the secret server.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider doing something else with the name event in the sample. This is unrelated to normal web security practices.

corresponding iframe changes its window.name property.

The host object will be removed during iframe deletion automatically.


# Examples
## Frame created event, frame name changed event, adding and removing host object

## .NET, WinRT
```c#
void SubscribeToFrameCreated()
{
webView.CoreWebView2.FrameCreated += delegate (object sender, CoreWebView2FrameCreatedEventArgs args)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sample: adding lambdas as event handlers means you can't unregister them. It is better to have a specific method that you can unregister later (if needed).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replace with ( ) => { } syntax rather than delegate () { } syntax.
Because we never plan to remove the event handler we're ok with lambda.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You might not in this sample, but people might copy-paste and then have problems

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's true but that's a general principle of event handlers. This sounds like a suggestion that all sample code avoid lambda event handlers, but I think that's taking the issue too far. In samples and in my code I don't like the noise of a method for the typical case of a short handler that doesn't need to be unregistered.

{
if (args.Frame.Name.Equals("iframe_name"))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Name can never be null? Note that in WinRT, an empty HSTRING projects as null.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe we decided that unnamed frames return String.Empty instead of null. (Similarly for other string properties.)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That works, I think I got it backwards; in HSTRING there's no support for empty, but if you pass a null HSTRING to C# it projects as String.Empty.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

{
String[] origins = new String[] { "https://appassets.example" };
args.Frame.AddHostObjectToScriptWithOrigins("bridge", new BridgeAddRemoteObject(), origins);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you need WithOrigins? That can be the ABI name but it looks like a trivial overload of the existing API?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, see above

}
args.Frame.NameChanged += delegate (object nameChangedSender, object nameChangedArgs)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason why the sender and args are not strongly typed?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use actual types instead of object. (Or just remove type names and use () => { } syntax)

{
CoreWebView2Frame frame = (CoreWebView2Frame)nameChangedSender;
if (!frame.Name.Equals("iframe_name"))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As noted earlier, this doesn't improve security since the page already got the bridge object under the old name.

I think a more realistic example would be

    if (frame.Name.Equals("iframe_name"))
    {
        String[] origins = new String[] { "https://appassets.example" };
        frame.AddHostObjectToScriptWithOrigins("bridge", new BridgeAddRemoteObject(), origins);
    }

where we deal with a frame that starts out nameless, but later changes its name, and then we recognize that it's the special frame and inject the object for it to use.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, like above we'll find a better sample for this.

{
frame.RemoveHostObjectFromScript("bridge");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will crash the second time the frame name changes, since "bridge" was already removed.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Discussion: Should Remove not throw if the object doesn't exist? Silently ignore calling remove for something that doesn't exist? Makes it easier to write cleanup code. Result: Continue to throw. But update sample app and add comment explaining that Remove can throw in that case.

}
};
};
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How synchronous is the NameChanged event? If I want to inject an object when the name matches the special name, is injecting in the NameChanged handler sufficient to deal with this:

// Set my name to the magic value that gives me access to the bridge
window.name = "iframe_name";
// Go get it!
var bridge = chrome.webview.hostObjects.bridge; // does this succeed?
var token = await bridge.GetToken(); // ask the bridge for a token

or is there a race condition where the script needs to wait for the object to be injected

// Set my name to the magic value that gives me access to the bridge
window.name = "iframe_name";
// Poll waiting for the bridge to show up
var check = async () => {
    var bridge = chrome.webview.hostObjects.bridge;
    if (!bridge) { setTimeout(check, 100); return; } // check again in 100ms
    var token = await bridge.GetToken(); // finally we can ask the bridge for a token
};
check();

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Additional point against adding host object to script based on name because name is async as mentioned.

```

## Win32 C++
```cpp
void SampleClass::SampleMethod()
{
// Register a handler for the FrameCreated event.
// This handler can be used to add host objects to the created iframe.
CHECK_FAILURE(m_webView->add_FrameCreated(
Callback<ICoreWebView2FrameCreatedEventHandler>(
[this](
ICoreWebView2* sender,
ICoreWebView2FrameCreatedEventArgs* args) -> HRESULT
{
wil::com_ptr<ICoreWebView2Frame> webviewFrame;
CHECK_FAILURE(args->get_Frame(&webviewFrame));

// Subscribe to frame name changed event and remove host object when name is changed
webviewFrame->add_NameChanged(
Callback<ICoreWebView2FrameNameChangedEventHandler>(
[this](ICoreWebView2Frame* sender,
IUnknown* args) -> HRESULT {
LPWSTR newName;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this string need to be freed?

Suggested change
LPWSTR newName;
wil::unique_cotaskmem_string newName;

Similarly below.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. Pls update

CHECK_FAILURE(sender->get_Name(&newName));
if (std::wcscmp(newName, L"iframe_name") != 0)
{
sender->RemoveHostObjectFromScript(L"sample");
}
return S_OK;
}).Get(), NULL);

LPWSTR name;
CHECK_FAILURE(webviewFrame->get_Name(&name));
if (std::wcscmp(name, L"iframe_name") == 0)
{
// Wrap host object
VARIANT remoteObjectAsVariant = {};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RAII type will auto-VariantClear the object at destruction.

Suggested change
VARIANT remoteObjectAsVariant = {};
wil::unique_variant remoteObjectAsVariant;

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. Pls use this

m_hostObject.query_to<IDispatch>(&remoteObjectAsVariant.pdispVal);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that this will throw if m_hostObject fails the QI.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a comment noting this and that its our object so should always succeed.

remoteObjectAsVariant.vt = VT_DISPATCH;

// Create list of origins which will be checked.
// iframe will have access to host object only if its origin belongs
// to this list.
LPCWSTR origin = L"https://appassets.example/";
// Add host object to a script for iframe access
CHECK_FAILURE(
webviewFrame->AddHostObjectToScriptWithOrigins(
L"sample", &remoteObjectAsVariant, 1, &origin));

remoteObjectAsVariant.pdispVal->Release();
}

return S_OK;
}).Get(), &m_frameCreatedToken));
}
```

# API Notes
See [API Details](#api-details) section below for API reference.

# API Details
## IDL
```c#
/// WebView2Frame provides direct access to the iframes information and use.
[uuid(18ae830b-8c83-459f-8a8c-95a1022af774), object, pointer_default(unique)]
interface ICoreWebView2Frame : IUnknown {
/// The name of the iframe from the iframe html tag declaring it.
/// Calling this method fails if it is called after the iframe is deleted.
[propget] HRESULT Name([ out, retval ] LPWSTR * name);
/// Raised when the iframe changes its window.name property
HRESULT add_NameChanged(
[in] ICoreWebView2FrameNameChangedEventHandler *eventHandler,
[out] EventRegistrationToken * token);
/// Remove an event handler previously added with add_NameChanged.
HRESULT remove_NameChanged([in] EventRegistrationToken token);

/// Add the provided host object to script running in the iframe with the
/// specified name for the list of the specified origins. The host object
/// will be accessible for this iframe only if the iframe's origin during
/// access matches one of the origins which are passed. The provided origins
/// will be normalized before comparing to the origin of the document.
/// So the scheme name is made lower case, the host will be punycode decoded
/// as appropriate, default port values will be removed, and so on.
/// This means the origin's host may be punycode encoded or not and will match
/// regardless.
/// If the iframe is declared with no src attribute its origin is considered
/// the same as the origin of the parent document.
/// List of origins will be treated as following:
/// 1. empty list - call will fail and no host object will be added for the
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or should this successfully add an object that is visible to no one? Could be an edge case where somebody builds a vector of allowed origins, and wind up with no allowed origins, and now they have to catch the edge case here and bypass the call to avoid a crash.

(Depending on the rules for how name collisions are resolved, it might be useful to create an object that is visible to no one, just so you can squat the name.)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure. Let's allow empty list for that reason (but still not null). And empty list means the object is not exposed to any origin

/// iframe;
/// 2. list with origins - during access to host object from iframe the
/// origin will be checked that it belongs to this list;
/// 3. list with "*" element - host object will be available for iframe for
/// all origins. We suggest not to use this feature without understanding
/// security implications of giving access to host object from from iframes
/// with unknown origins.
/// Calling this method fails if it is called after the iframe is deleted.
/// \snippet ScenarioAddHostObject.cpp AddHostObjectToScriptWithOrigins
/// For more information about host objects navigate to [AddHostObjectToScript]
HRESULT AddHostObjectToScriptWithOrigins(
[in] LPCWSTR name,
[in] VARIANT *object,
[in] UINT32 originsCount,
[in, size_is(originsCount)] LPCWSTR* origins);
/// Remove the host object specified by the name so that it is no longer
/// accessible from JavaScript code in the iframe. While new access
/// attempts are denied, if the object is already obtained by JavaScript code
/// in the iframe, the JavaScript code continues to have access to that
/// object. Calling this method for a name that is already removed or was never
/// added fails. If the iframe is deleted this method will return fail also.
HRESULT RemoveHostObjectFromScript([in] LPCWSTR name);

/// The FrameDeleted event is raised when the iframe corresponding to this CoreWebView2Frame
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This event is already on the Frame object, so would this just be "Deleted"?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove 'Frame' prefix from the one on this runtimeclass (the created event still needs the prefix since its for a different runtimeclass)

/// object is removed or the document containing that iframe is destroyed
HRESULT add_FrameDeleted(
[in] ICoreWebView2FrameDeletedEventHandler *eventHandler,
[out] EventRegistrationToken * token);
/// Remove an event handler previously added with add_FrameDeleted.
HRESULT remove_FrameDeleted([in] EventRegistrationToken token);
}

[uuid(ae64d3bc-01ba-4769-a470-a1888de6d30b), object, pointer_default(unique)]
interface ICoreWebView2 : IUnknown {
/// Raised when a new iframe is created. Use the CoreWebView2Frame.add_FrameDeleted
/// to listen for when this iframe goes away.
HRESULT add_FrameCreated(
[in] ICoreWebView2FrameCreatedEventHandler *eventHandler,
[out] EventRegistrationToken * token);
/// Remove an event handler previously added with add_FrameCreated.
HRESULT remove_FrameCreated([in] EventRegistrationToken token);
}

[uuid(6c2d169c-7912-4332-a288-0aee1626759f), object, pointer_default(unique)]
interface ICoreWebView2FrameCreatedEventHandler: IUnknown {
/// Provides the result for the iframe created event
HRESULT Invoke([in] ICoreWebView2 * sender,
[in] ICoreWebView2FrameCreatedEventArgs *
args);
}

// Event args for the iframe created events.
[uuid(5fc1cd71-cd9a-4e8f-ab5f-2f134f941a3d), object, pointer_default(unique)]
interface ICoreWebView2FrameCreatedEventArgs : IUnknown {
/// The frame which was created.
[propget] HRESULT Frame([ out, retval ] ICoreWebView2Frame **frame);
}

[uuid(8a1860c1-528f-4286-823c-23b0439340d2), object, pointer_default(unique)]
interface ICoreWebView2FrameDeletedEventHandler: IUnknown {
/// Provides the result for the iframe deleted event.
/// No event args exist and the `args` parameter is set to `null`.
HRESULT Invoke([in] ICoreWebView2Frame * sender, [in] IUnknown * args);
}

[uuid(cfd00c6a-8889-47de-84b2-3016d44dbaf4), object, pointer_default(unique)]
interface ICoreWebView2FrameNameChangedEventHandler : IUnknown {
/// Provides the result for the iframe name changed event.
/// No event args exist and the `args` parameter is set to `null`.
HRESULT Invoke([in] ICoreWebView2Frame * sender, [in] IUnknown * args);
}
```
## .NET and WinRT
```c#
namespace Microsoft.Web.WebView2.Core
{
/// CoreWebView2Frame provides direct access to the iframes information and use.
runtimeclass CoreWebView2Frame
{
/// The name of the iframe from the iframe html tag declaring it.
/// Calling this method fails if it is called after the iframe is deleted.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be a problem for code that posts work to the UI thread, since by the time the posted work runs, the frame may already be dead.

webView.CoreWebView2.FrameCreated += async (s, e) =>
{
    var bridge = await Bridge.CreateAsync(); // creating a bridge takes time
    bridge.Server = GetServerFromFrameName(e.Frame.Name); // crash
    e.Frame.AddHostObjectToScriptWithOrigins("bridge", bridge, trustedOrigins); // crash
};

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We'll add a property IsDestroyed that works even after the frame is destroyed. Must be true during the destroyed event. Allow removing event handlers without throwing.

String Name { get; };

/// Raised when the iframe changes its window.name property
event Windows.Foundation.TypedEventHandler<CoreWebView2Frame, Object> NameChanged;
/// The FrameDeleted event is raised when the iframe corresponding to this CoreWebView2Frame
/// object is removed or the document containing that iframe is destroyed
event Windows.Foundation.TypedEventHandler<CoreWebView2Frame, Object> FrameDeleted;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the frame still valid during this event? E.g. can I access the Name property or not?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See above.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a way to ask a frame, "Have you been deleted"? Or does the app have to keep its own HashSet<Frame> to keep track of which frames have been created and not yet deleted?

webView2.FrameCreated += (s, e) =>
{
    activeFrames.Add(e.Frame);
    e.Frame.FrameDeleted += (s, e) =>
    {
        activeFrames.Remove(s);
    };
};

string TryGetFrameName(Frame frame)
{
    if (activeFrames.Contains(frame)) return frame.Name;
    return "";
}

void TryAddHostObjectToFrame(Frame frame, string name, object o)
{
    if (activeFrames.Contains(frame)) return frame.AddHostObject(name, o);
    return;
}
...etc for every member of Frame...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See above.


/// Add the provided host object to script running in the iframe with the
/// specified name for the list of the specified origins. The host object
/// will be accessible for this iframe only if the iframe's origin during
/// access matches one of the origins which are passed. The provided origins
/// will be normalized before comparing to the origin of the document.
/// So the scheme name is made lower case, the host will be punycode decoded
/// as appropriate, default port values will be removed, and so on.
/// This means the origin's host may be punycode encoded or not and will match
/// regardless.
/// If the iframe is declared with no src attribute its origin is considered
/// the same as the origin of the parent document.
/// List of origins will be treated as following:
/// 1. empty list - call will fail and no host object will be added for the
/// iframe;
/// 2. list with origins - during access to host object from iframe the
/// origin will be checked that it belongs to this list;
/// 3. list with "*" element - host object will be available for iframe for
/// all origins. We suggest not to use this feature without understanding
/// security implications of giving access to host object from from iframes
/// with unknown origins.
/// Calling this method fails if it is called after the iframe is deleted.
/// \snippet ScenarioAddHostObject.cpp AddHostObjectToScriptWithOrigins
/// For more information about host objects navigate to [AddHostObjectToScript]
void AddHostObjectToScriptWithOrigins(String name, Object rawObject, String[] origins);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

String[] or IVector<String>?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • IEnumerable for C#
  • IIterable for IDL3
  • remains pointer+size for COM

/// Remove the host object specified by the name so that it is no longer
/// accessible from JavaScript code in the iframe. While new access
/// attempts are denied, if the object is already obtained by JavaScript code
/// in the iframe, the JavaScript code continues to have access to that
/// object. Calling this method for a name that is already removed or was never
/// added fails. If the iframe is deleted this method will return fail also.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does "fail" mean? It should be a no-op to delete something that was never there, for example (not an exception).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

see above

void RemoveHostObjectFromScript(String name);
}

runtimeclass CoreWebView2
{
//..
/// Raised when a new iframe is created. Use the CoreWebView2Frame.FrameDeleted event to
/// listen for when this iframe goes away.
event Windows.Foundation.TypedEventHandler<CoreWebView2, CoreWebView2FrameCreatedEventArgs> FrameCreated;
}
runtimeclass CoreWebView2FrameCreatedEventArgs
{
/// The frame which was created.
CoreWebView2Frame Frame { get; };
}
}
```