-
Notifications
You must be signed in to change notification settings - Fork 2.5k
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
electron: enable context isolation (with inversify) #12380
Conversation
defde0b
to
817e932
Compare
@tsmaeder @msujew Note that even though I said that this work builds "on top" of #12299, it does so in a way that replaces completely what is done in the original PR. I had to split up and re-organize code which is the extend of "built on top". In short, unless there's big show-stoppers I'm proposing to drop #12299 and keep going forward with this PR. For instance, the issue when closing a window not preventing application exit that Mark mentioned wasn't an issue in this PR. This is because by adding typings to the channel messages I caught the issue from the original code. |
I fixed an issue where the button to maximize the window wasn't updating: I was sending the events but not listening for them, oops. I don't think there's much we can do with typings to catch these bugs ;) Also made so |
0b38ebb
to
5570684
Compare
Hi, what's the plan with this PR? Having a new diff against the current |
@kittaakos I believe the newest version is already rebased on the current |
58f4ef6
to
5eea67b
Compare
This is a rewrite of the preload infrastructure to align the implementation with the rest of the framework. This commit breaks up APIs exposed from the preload context into several Inversify components. Functionality is now contributed through Inversify `ContainerModule` instances exposed as `default` export in Theia `preload` contributions. New typed APIs are wrapping untyped Electron APIs: `TheiaIpcRenderer`, `TheiaIpcMain`, `TheiaIpcWindow`, `IpcChannel`, `createIpcChannel`, `createIpcNamespace`. In order to contribute new APIs from the preload context to the browser context you should use the `preloadServiceIdentifier<T>(name)` API to create your Inversify service identifiers and then use the `bindPreloadApi(bind, yourPreloadServiceId)` API to bind your own components and have them exposed in the browser context. Electron doesn't handle instances with prototypes when exposing APIs, so you may use the `@proxyable() class` and `@proxy() field` APIs when creating your preload components to expose in the browser context. Internally the `IpcHandleConverter` is the component that handles the `@proxyable()` and `@proxy()` metadata to convert objects in an Electron-friendly way. The `TheiaContextBridge` component wraps Electron's `contextBridge` API to use this conversion mechanism. See ./packages/core/ELECTRON-COMMON.md for more details. Internally, we now use `MessagePorts` to commmunicate between the browser and main contexts.
5eea67b
to
4c9753c
Compare
Make the `IpcHandleConverterImpl` more robust. Added more tests. Allow controlling how references are replaced when creating handles. Add a contribution excluding known references that may cause issues over IPC (i.e. `TheiaIpcRenderer` or Electron's `ipcRenderer`). Improve new APIs. Improve documentation.
I just noticed an issue with the current context isolation feature and what's being contributed here: Essentially what we do is setup a bridge in the preload context that talks to the main context on behalf of the browser context, all via message passing. But there already is a mechanism to communicate from the browser to the main context using JSON-RPC proxies: theia/examples/api-samples/src/electron-main/update/sample-updater-main-module.ts Lines 28 to 34 in 877f962
This infrastructure overlaps very much with what has been introduced to handle context isolation, the key differences being:
Instead of keeping the two somewhat redundant systems (communication between browser and main), I'd rather consolidate both. I think we should replace the old JSON-RPC proxies with proxies based on Electron's IPC mechanisms. I think it would be neat to allow proxies to follow the following naming convention for fields:
This naming convention is easily implementable from a proxy handler point-of-view, and Electron's IPC APIs support all the use cases mentioned. Creating proxies this way would also most likely lower the amount of boilerplate required to setup communication. The system introduced to support context isolation requires you to:
The proxy system I propose would require you to:
Again, the main goal being to consolidate the browser/main communication infrastructure and reduce boilerplate. I think this current PR is irrelevant if we decide to go the route of consolidation, because I don't want to expose the APIs implemented here if we plan on getting rid of them later to implement the consolidated proxy API. |
@paul-marechal I think using the same mechanism for exposing electron-main API as we do for exposing back-end services is a great idea! I think it would tick all the boxes:
The problem that prevents us from reusing the current rpc system "as is" is that we need to have synchronous service calls for electron-main API, whereas the current system only supports asynchronous calls. However, that seems more of an implementation detail than a conceptual problem: |
I would call it a capability. From the proxy instance point-of-view, I'm having it deal with an // expected interface to abstract away the connection from the proxying bits:
export interface RpcClient {
sendNotification?(method: string, params: unknown[]): void;
sendRequest?(method: string, params: unknown[]): Promise<unknown>;
sendRequestSync?(method: string, params: unknown[]): unknown;
} When I said:
I meant to not emulate a buffer message connection on top of Electron's // simplified implementation to illustrate:
const client: RpcClient = {
sendNotification: (method, params) => {
ipcRenderer.send('rpc-notification-channel', method, params);
},
sendRequest: (method, params) => ipcRenderer.invoke('rpc-request-channel', method, params),
sendRequestSync: (method, params) => ipcRenderer.sendSync('ipc-request-sync-channel', method, params),
}; I believe that we will be able to provide valid implementations for
Less boilerplate, and it avoids having to think about whole APIs having to be synchronous or asynchronous without the ability of mixing. Consider the NodeJS All this talk about synchronous API support but ultimately it might be flawed. Yes we can make it work and it gets us out of a pickle with our own infrastructure that expects sync values here and there, but that's the main problem we should fix long term: We should not expect sync values from remote contexts because sync calls will block the thread they are made from until they return. It's an anti-pattern as far as the JS event-loop is concerned. I don't plan on fixing this now. |
For one the The Don't get me wrong, the more generic concept of "client" is still relevant to what we're doing (one main process, potentially multiple windows = multiple clients) but I will just avoid relying on the way it's been implemented with our other proxies. In the future the new system could replace the old and it would help some implementations make a lot more sense (read "more maintainable".) |
Further work required in a future alternative PR. |
What it does
This is work on top of #12299 to use Inversify in the preload context.
It also breaks up the monolithic
TheiaCoreAPI
into several smaller components.The challenges that had to be solved are the following:
How to share Inversify bindings from one context to another:
An instance of
TheiaPreloadContext
is exposed to the browser global scope. When initializing the browser Inversify containers, we calltheiaPreloadContext.getAllServices()
to get pairs of[serviceIdentifier: string, serviceProxy: object]
which we use to rebind each component asbind(serviceIdentifier).toConstantValue(serviceProxy)
.How to share proxies of instances with prototypes:
Electron only supports proxying objects used as records/dictionaries and won't follow prototype chains. This means we have to pre-process objects to make it work with Electron's cross-context APIs. This is done with the help of a decorator-based API
@proxyable()
and@proxy()
. ThenIpcHandleConverter
is handling the pre-processing of instances using the metadata defined using the decorators.It is now possible to bind and rebind preload components as a framework user.
Please see
./packages/core/ELECTRON-COMMON.md
for more details about how to contribute preload APIs.I also write more information in my commit messages.
How to test
Execute commands such as:
Maximazing/minimizing/closing windows should still work.
Review checklist
Reminder for reviewers