-
Notifications
You must be signed in to change notification settings - Fork 54
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
base: main
Are you sure you want to change the base?
Conversation
API spec for CoreWebView2Frame.AddHostObjectToScriptWithOrigins and frame created/deleted/name changed events: - WebView2 FrameCreated event; - New interface for iframes ICoreWebView2Frame with name property, FrameDeleted and NameChanged events; - AddHostObjectToScriptWithOrigins method for adding host objects to iframe and corresponding RemoveHostObjectFromScript method.
using `AddHostObjectToScriptWithOrigins` method. | ||
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 |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
```c# | ||
void SubscribeToFrameCreated() | ||
{ | ||
webView.CoreWebView2.FrameCreated += delegate (object sender, CoreWebView2FrameCreatedEventArgs args) |
There was a problem hiding this comment.
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).
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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")) | ||
{ | ||
String[] origins = new String[] { "https://appassets.example" }; | ||
args.Frame.AddHostObjectToScriptWithOrigins("bridge", new BridgeAddRemoteObject(), origins); |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, see above
String[] origins = new String[] { "https://appassets.example" }; | ||
args.Frame.AddHostObjectToScriptWithOrigins("bridge", new BridgeAddRemoteObject(), origins); | ||
} | ||
args.Frame.NameChanged += delegate (object nameChangedSender, object nameChangedArgs) |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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)
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; |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See above.
/// 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); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
String[]
or IVector<String>
?
There was a problem hiding this comment.
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
/// 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. |
There was a problem hiding this comment.
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).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
see above
|
||
# 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 |
There was a problem hiding this comment.
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:
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.
There was a problem hiding this comment.
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 | ||
using `AddHostObjectToScriptWithOrigins` method. |
There was a problem hiding this comment.
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?)
There was a problem hiding this comment.
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).
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 | ||
using `AddHostObjectToScriptWithOrigins` method. |
There was a problem hiding this comment.
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 allAddHostObjectToScriptWithOrigins(name, o, new[] { "*" })
- allow allAddHostObjectToScriptWithOrigins(name, o, new[] { coreWebView2.Source })
- allow same origin as document- Something else?
There was a problem hiding this comment.
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
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 | ||
using `AddHostObjectToScriptWithOrigins` method. |
There was a problem hiding this comment.
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."
There was a problem hiding this comment.
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.
using `AddHostObjectToScriptWithOrigins` method. | ||
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 |
There was a problem hiding this comment.
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.
{ | ||
// Wrap host object | ||
VARIANT remoteObjectAsVariant = {}; | ||
m_hostObject.query_to<IDispatch>(&remoteObjectAsVariant.pdispVal); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
/// 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 |
There was a problem hiding this comment.
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.)
There was a problem hiding this comment.
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
/// 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 |
There was a problem hiding this comment.
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"?
There was a problem hiding this comment.
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)
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. |
There was a problem hiding this comment.
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
};
There was a problem hiding this comment.
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.
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; |
There was a problem hiding this comment.
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...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See above.
Callback<ICoreWebView2FrameNameChangedEventHandler>( | ||
[this](ICoreWebView2Frame* sender, | ||
IUnknown* args) -> HRESULT { | ||
LPWSTR newName; |
There was a problem hiding this comment.
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?
LPWSTR newName; | |
wil::unique_cotaskmem_string newName; |
Similarly below.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes. Pls update
if (std::wcscmp(name, L"iframe_name") == 0) | ||
{ | ||
// Wrap host object | ||
VARIANT remoteObjectAsVariant = {}; |
There was a problem hiding this comment.
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.
VARIANT remoteObjectAsVariant = {}; | |
wil::unique_variant remoteObjectAsVariant; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes. Pls use this
navigation and content loading. The FrameDeleted event is raised when an iframe is deleted | ||
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 |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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)
|
||
# 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 |
There was a problem hiding this comment.
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.
navigation and content loading. The FrameDeleted event is raised when an iframe is deleted | ||
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 |
There was a problem hiding this comment.
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.
navigation and content loading. The FrameDeleted event is raised when an iframe is deleted | ||
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 |
There was a problem hiding this comment.
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)
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 | ||
using `AddHostObjectToScriptWithOrigins` method. |
There was a problem hiding this comment.
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).
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 | ||
using `AddHostObjectToScriptWithOrigins` method. |
There was a problem hiding this comment.
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
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. |
There was a problem hiding this comment.
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.
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; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See above.
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; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See above.
/// 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); |
There was a problem hiding this comment.
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
/// 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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
see above
{ | ||
webView.CoreWebView2.FrameCreated += delegate (object sender, CoreWebView2FrameCreatedEventArgs args) | ||
{ | ||
if (args.Frame.Name.Equals("iframe_name")) |
There was a problem hiding this comment.
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
.
There was a problem hiding this comment.
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.)
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Null
projects as empty string. https://docs.microsoft.com/en-us/windows/win32/winrt/hstring.
API spec for CoreWebView2Frame.AddHostObjectToScriptWithOrigins and frame created/deleted/name changed events: