-
Notifications
You must be signed in to change notification settings - Fork 16.5k
feat: add WebFrameMain detached property #43473
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
feat: add WebFrameMain detached property #43473
Conversation
Reminder todo for me: mainFrame.ipc.on should receive the detached frame messages. This can be looked up via FrameTreeNode ID when dispatching the emitters. |
I don't love introducing APIs that could throw under "normal usage". I think I made this point before but ensuring we attach the correct pinned WebFrameMain information should be doable even after the navigation. We just need to stash the information. Personally I think the much better end-state is to align on things like our new permission handler API which will just expose an origin, then we suggest folks validate the origin instead of the frame and move on with our lives 🤔 |
We currently stash the process and routing IDs associated with the The current design throws if accessed asynchronously. Previously it returned ipcMain.on('unload-event', async (event) => {
await crossOriginNavigationPromise;
event.senderFrame; // ❌ likely throws due to initial RFH destroyed
}); An alternate design could be to create an "empty" WebFrameMain which is initialized in a destroyed state. ipcMain.on('unload-event', async (event) => {
await crossOriginNavigationPromise;
event.senderFrame; // ✅ object exists
event.senderFrame.isDestroyed(); // ✅ true because pinned frame info no longer points to valid RFH
event.senderFrame.url; // ❌ throws as RFH has been destroyed/uninitialized
}); The only thing I don't like about this design is that it's somewhat misleading since the frame could have swapped. Throwing outright will encourage developers to always access synchronously. Throwing also follows the behavior seen in property access such as
I agree on the security side of things, we should prefer exposing the origin directly. That's the approach I intend to follow with the permissions changes once I get to proposing the permissions-v2 branch. This PR is addressing the edge case with unloaded frames which applies to IPCs, permissions, and anywhere else we expose frames as property. |
The Here are some options we can go with for addressing late access. Option A: Throw upon accessipcMain.on('unload-event', async (event) => {
await crossOriginNavigationPromise;
event.senderFrame; // ❌ throws due to RFH destroyed
});
Option B: Return destroyed/empty WebFrameMainipcMain.on('unload-event', async (event) => {
await crossOriginNavigationPromise;
event.senderFrame; // ✅ object exists
event.senderFrame.isDestroyed(); // ✅ true because pinned frame info no longer points to valid RFH
event.senderFrame.url; // ❌ throws as RFH has been destroyed/uninitialized
});
✅ Option C: Return
|
62ef220
to
4fa45ef
Compare
4fa45ef
to
bc851d4
Compare
API LGTM |
some color to my approval:
|
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.
API LGTM
Is it worth adding a short note to the breaking changes doc to mention that a bunch of APIs can now have a null
value for the frame
property? Even if this won't impact many people at runtime, it could cause a little friction with TypeScript type-checking, so it seems worth a high-level mention.
@itsananderson I've added a notice about the change of behavior which mentions the possibility of the frame being null 09e9fba |
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.
API LGTM
014a4fd
to
d520445
Compare
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.
If there are no API concerns, the code/approach here seems reasonable to me.
Is there any existing behavior where this could break handling event.senderFrame, where a developer is assuming it won’t return null? I think the answer is no, as we’re handling an edge case here, just asking in case any other existing documentation should be updated with the updated types.
@VerteDinde the existing behavior is also to return |
fix: throw instead of returning null senderFrame test: detached frames fix: ensure IPCs of pending deletion RFHs are dispatched fix: lookup WFM by FTN ID to dispatch IPCs feat: add frame.isDestroyed() return null fix: return undefined docs: add null to all frame properties refactor: option c, return null and emit warning refactor: add routingId & processId to navigation events test: null frame property docs: clarify warning message better wording clarify null frame fix: browserwindow spec
4be5d6b
to
1bdc5dc
Compare
Release Notes Persisted
|
I have automatically backported this PR to "33-x-y", please check out #44209 |
* feat: add WebFrameMain detached property fix: throw instead of returning null senderFrame test: detached frames fix: ensure IPCs of pending deletion RFHs are dispatched fix: lookup WFM by FTN ID to dispatch IPCs feat: add frame.isDestroyed() return null fix: return undefined docs: add null to all frame properties refactor: option c, return null and emit warning refactor: add routingId & processId to navigation events test: null frame property docs: clarify warning message better wording clarify null frame fix: browserwindow spec * maybe fix 🤷 * fix: use updated util electron#43722 * docs: add notice for frame change of behavior * docs: clarify why frame properties may be null * lint * wip * fix: content::FrameTreeNodeId lookup and converter * refactor: avoid holey array deoptimization --------- Co-authored-by: John Kleinschmidt <jkleinsc@electronjs.org>
* feat: add WebFrameMain detached property fix: throw instead of returning null senderFrame test: detached frames fix: ensure IPCs of pending deletion RFHs are dispatched fix: lookup WFM by FTN ID to dispatch IPCs feat: add frame.isDestroyed() return null fix: return undefined docs: add null to all frame properties refactor: option c, return null and emit warning refactor: add routingId & processId to navigation events test: null frame property docs: clarify warning message better wording clarify null frame fix: browserwindow spec * maybe fix 🤷 * fix: use updated util electron#43722 * docs: add notice for frame change of behavior * docs: clarify why frame properties may be null * lint * wip * fix: content::FrameTreeNodeId lookup and converter * refactor: avoid holey array deoptimization --------- Co-authored-by: John Kleinschmidt <jkleinsc@electronjs.org>
* feat: add WebFrameMain detached property fix: throw instead of returning null senderFrame test: detached frames fix: ensure IPCs of pending deletion RFHs are dispatched fix: lookup WFM by FTN ID to dispatch IPCs feat: add frame.isDestroyed() return null fix: return undefined docs: add null to all frame properties refactor: option c, return null and emit warning refactor: add routingId & processId to navigation events test: null frame property docs: clarify warning message better wording clarify null frame fix: browserwindow spec * maybe fix 🤷 * fix: use updated util electron#43722 * docs: add notice for frame change of behavior * docs: clarify why frame properties may be null * lint * wip * fix: content::FrameTreeNodeId lookup and converter * refactor: avoid holey array deoptimization --------- Co-authored-by: John Kleinschmidt <jkleinsc@electronjs.org>
Description of Change
Note
This is an alternate proposal to #43456
In our security recommendations guide, we list validating senderFrame of IPCs. In the case of IPCs sent after the unload event, it's possible for
senderFrame
to point to a different RenderFrameHost than the sender of the IPC.The process occurs as followed (gist to repro):
kPendingDeletion
WebFrameMain
internally indexes a RFH by its FrameTreeNode ID. This is based on the design of WebFrameMain being similar to how<iframe>
works.To fix this without any breaking changes, we can introduce a few internal changes:
WebFrameMain
lookups byRenderFrameHost
to map directly to that RFHFrameTreeNode
, mark it asdetached
event.senderFrame
is accessed after the frame has already been disposedThe result is that IPCs received while an unload listener is running will have
WebFrameMain.detached
set, but all of the expected RFH properties will still be accessible. Once the RFH is finally deleted, theWebFrameMain
instance will be disposed.As long as we use
dict.Set("frame", rfh)
ordict.SetGetter("frame", rfh)
, it’s guaranteed to point to the correct RFH if immediately accessed after emitted.If accessed asynchronously, it will return null if the RFH has swapped or been destroyed.
Todo:
isDestroyed()
so it's easier to check if WFM was disposed alreadymainFrame.ipc.on
handlers are called in the case of detached framesframe
properties.Checklist
npm test
passesRelease Notes
Notes:
WebFrameMain.detached
for frames in an unloading state.WebFrameMain.isDestroyed()
to determine if a frame has been destroyed.webFrameMain.fromId(processId, frameId)
returning aWebFrameMain
instance which doesn't match the given parameters when the frame is unloading.