Skip to content

Commit a07b746

Browse files
lucas-koehlersdirixthegecko
authored
Multi window support for web views (#11048)
Support for moving webview-based views into a secondary window or tab. For webview-based views a new button becomes available in the toolbar of the view to move the view to a secondary window. This is only supported for webview-based views and only one view can be moved into the secondary window. The window extraction is not added to the electron app for now as there are issues with close handling of secondary windows on Electron. There can be multiple secondary windows though. Primary code changes: - Add concept of extractable widgets. Only widgets implementing the interface can be extracted. - Add `SecondaryWindowHandler` that encapsulates logic to move widgets to new windows. - Add service `SecondaryWindowService` to handle creating and focussing external windows based on the platform. - Only webviews can be extracted - Configure opened secondary windows in electron - Hide electron menu - Always use the native window frame for secondary windows to get window controls - Do not show secondary window icon if main window uses a custom title bar - Contribute widget extraction button in a separate new extension `widget-extraction-ui` - Extend application shell areas with a `secondaryWindow` area that contains all extracted widgets - Extend frontend and webpack generators to generate the base html for secondary windows and copy it to the lib folder - Bridge plugin communication securely via secure messaging between external webview and Theia: Webviews only accept messages from its direct parent window. This is necessary to avoid Cross Site Scripting (XSS). To make the messaging work in secondary windows, messages are sent to the external widget and then delegated to the webview's iframe. Thereby, the secondary window only accepts messages from its opener which is the theia main window. To achieve this, a webview knows the secondary window it is in (if any). It then sends messages to this window instead of directly to the webview iframe. - Patch PhosphorJS during application webpack build - Use string-replace-loader to remove the critical line from PhosphorJS during application bundleing - Extend `webpack generator.ts` to add this to applications' `gen-webpack.config.js` - Add extension `secondary-window` which contributes the UI integration (and possibly later more) Contributed on behalf of ST Microelectronics and Ericsson and by ARM and EclipseSource. Co-authored-by: Stefan Dirix <[email protected]> Co-authored-by: robmor01 <[email protected]> Signed-off-by: Lucas Koehler <[email protected]>
1 parent 9e5db77 commit a07b746

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+889
-40
lines changed

CHANGELOG.md

+8
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,14 @@
44

55
- [Previous Changelogs](https://github.com/eclipse-theia/theia/tree/master/doc/changelogs/)
66

7+
## v1.30.0
8+
9+
- [core] Added support for moving webview-based views into a secondary window for browser applications. Added new extension `secondary-window` that contributes the UI integration to use this. [#11048](https://github.com/eclipse-theia/theia/pull/11048) - Contributed on behalf of ST Microelectronics and Ericsson and by ARM and EclipseSource
10+
11+
<a name="breaking_changes_1.30.0">[Breaking Changes:](#breaking_changes_1.30.0)</a>
12+
13+
- [core] Added constructor injection to `ApplicationShell`: `SecondaryWindowHandler`. [#11048](https://github.com/eclipse-theia/theia/pull/11048) - Contributed on behalf of ST Microelectronics and Ericsson and by ARM and EclipseSource
14+
715
## v1.29.0 - 8/25/2022
816

917
- [application-manager] added the `applicationName` in the frontend generator [#11575](https://github.com/eclipse-theia/theia/pull/11575)

dev-packages/application-manager/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
"source-map": "^0.6.1",
5555
"source-map-loader": "^2.0.1",
5656
"source-map-support": "^0.5.19",
57+
"string-replace-loader": "^3.1.0",
5758
"style-loader": "^2.0.0",
5859
"umd-compat-loader": "^2.1.2",
5960
"webpack": "^5.48.0",

dev-packages/application-manager/src/generator/frontend-generator.ts

+45
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ export class FrontendGenerator extends AbstractGenerator {
2525
const frontendModules = this.pck.targetFrontendModules;
2626
await this.write(this.pck.frontend('index.html'), this.compileIndexHtml(frontendModules));
2727
await this.write(this.pck.frontend('index.js'), this.compileIndexJs(frontendModules));
28+
await this.write(this.pck.frontend('secondary-window.html'), this.compileSecondaryWindowHtml());
2829
if (this.pck.isElectron()) {
2930
const electronMainModules = this.pck.targetElectronMainModules;
3031
await this.write(this.pck.frontend('electron-main.js'), this.compileElectronMain(electronMainModules));
@@ -195,4 +196,48 @@ module.exports = Promise.resolve()${this.compileElectronMainModuleImports(electr
195196
`;
196197
}
197198

199+
/** HTML for secondary windows that contain an extracted widget. */
200+
protected compileSecondaryWindowHtml(): string {
201+
return `<!DOCTYPE html>
202+
<html lang="en">
203+
204+
<head>
205+
<meta charset="UTF-8">
206+
<title>Theia — Secondary Window</title>
207+
<style>
208+
html, body {
209+
overflow: hidden;
210+
-ms-overflow-style: none;
211+
}
212+
213+
body {
214+
margin: 0;
215+
}
216+
217+
html,
218+
head,
219+
body,
220+
#widget-host,
221+
.p-Widget {
222+
width: 100% !important;
223+
height: 100% !important;
224+
}
225+
</style>
226+
<script>
227+
window.addEventListener('message', e => {
228+
// Only process messages from Theia main window
229+
if (e.source === window.opener) {
230+
// Delegate message to iframe
231+
document.getElementsByTagName('iframe').item(0).contentWindow.postMessage({ ...e.data }, '*');
232+
}
233+
});
234+
</script>
235+
</head>
236+
237+
<body>
238+
<div id="widget-host"></div>
239+
</body>
240+
241+
</html>`;
242+
}
198243
}

dev-packages/application-manager/src/generator/webpack-generator.ts

+17
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ export class WebpackGenerator extends AbstractGenerator {
5656
const path = require('path');
5757
const webpack = require('webpack');
5858
const yargs = require('yargs');
59+
const CopyWebpackPlugin = require('copy-webpack-plugin');
5960
const CircularDependencyPlugin = require('circular-dependency-plugin');
6061
const CompressionPlugin = require('compression-webpack-plugin')
6162
@@ -72,6 +73,12 @@ const { mode, staticCompression } = yargs.option('mode', {
7273
const development = mode === 'development';
7374
7475
const plugins = [
76+
new CopyWebpackPlugin({
77+
patterns: [{
78+
// copy secondary window html file to lib folder
79+
from: path.resolve(__dirname, 'src-gen/frontend/secondary-window.html')
80+
}]
81+
}),
7582
new webpack.ProvidePlugin({
7683
// the Buffer class doesn't exist in the browser but some dependencies rely on it
7784
Buffer: ['buffer', 'Buffer']
@@ -104,6 +111,16 @@ module.exports = {
104111
cache: staticCompression,
105112
module: {
106113
rules: [
114+
{
115+
// Removes the host check in PhosphorJS to enable moving widgets to secondary windows.
116+
test: /widget\\.js$/,
117+
loader: 'string-replace-loader',
118+
include: /node_modules[\\\\/]@phosphor[\\\\/]widgets[\\\\/]lib/,
119+
options: {
120+
search: /^.*?throw new Error\\('Host is not attached.'\\).*?$/gm,
121+
replace: ''
122+
}
123+
},
107124
{
108125
test: /\\.css$/,
109126
exclude: /materialcolors\\.css$|\\.useable\\.css$/,

examples/browser/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
"@theia/scm": "1.29.0",
4848
"@theia/scm-extra": "1.29.0",
4949
"@theia/search-in-workspace": "1.29.0",
50+
"@theia/secondary-window": "1.29.0",
5051
"@theia/task": "1.29.0",
5152
"@theia/terminal": "1.29.0",
5253
"@theia/timeline": "1.29.0",

examples/browser/tsconfig.json

+3
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,9 @@
104104
{
105105
"path": "../../packages/search-in-workspace"
106106
},
107+
{
108+
"path": "../../packages/secondary-window"
109+
},
107110
{
108111
"path": "../../packages/task"
109112
},

packages/core/i18n/nls.cs.json

+3
Original file line numberDiff line numberDiff line change
@@ -409,6 +409,9 @@
409409
"resultSubset": "Jedná se pouze o podmnožinu všech výsledků. Pro zúžení seznamu výsledků použijte konkrétnější vyhledávací výraz.",
410410
"searchOnEditorModification": "Prohledat aktivní editor při úpravě."
411411
},
412+
"secondary-window": {
413+
"extract-widget": "Přesunutí zobrazení do sekundárního okna"
414+
},
412415
"task": {
413416
"attachTask": "Připojte úkol...",
414417
"clearHistory": "Vymazat historii",

packages/core/i18n/nls.de.json

+3
Original file line numberDiff line numberDiff line change
@@ -409,6 +409,9 @@
409409
"resultSubset": "Dies ist nur eine Teilmenge aller Ergebnisse. Verwenden Sie einen spezifischeren Suchbegriff, um die Ergebnisliste einzugrenzen.",
410410
"searchOnEditorModification": "Durchsucht den aktiven Editor nach Änderungen."
411411
},
412+
"secondary-window": {
413+
"extract-widget": "Ansicht in sekundäres Fenster verschieben"
414+
},
412415
"task": {
413416
"attachTask": "Aufgabe anhängen...",
414417
"clearHistory": "Geschichte löschen",

packages/core/i18n/nls.es.json

+3
Original file line numberDiff line numberDiff line change
@@ -409,6 +409,9 @@
409409
"resultSubset": "Esto es sólo un subconjunto de todos los resultados. Utilice un término de búsqueda más específico para reducir la lista de resultados.",
410410
"searchOnEditorModification": "Busca en el editor activo cuando se modifica."
411411
},
412+
"secondary-window": {
413+
"extract-widget": "Mover la vista a la ventana secundaria"
414+
},
412415
"task": {
413416
"attachTask": "Adjuntar tarea...",
414417
"clearHistory": "Historia clara",

packages/core/i18n/nls.fr.json

+3
Original file line numberDiff line numberDiff line change
@@ -409,6 +409,9 @@
409409
"resultSubset": "Il ne s'agit que d'un sous-ensemble de tous les résultats. Utilisez un terme de recherche plus spécifique pour réduire la liste des résultats.",
410410
"searchOnEditorModification": "Rechercher l'éditeur actif lorsqu'il est modifié."
411411
},
412+
"secondary-window": {
413+
"extract-widget": "Déplacer la vue vers une fenêtre secondaire"
414+
},
412415
"task": {
413416
"attachTask": "Attacher la tâche...",
414417
"clearHistory": "Histoire claire",

packages/core/i18n/nls.hu.json

+3
Original file line numberDiff line numberDiff line change
@@ -409,6 +409,9 @@
409409
"resultSubset": "Ez csak egy részhalmaza az összes eredménynek. A találati lista szűkítéséhez használjon konkrétabb keresési kifejezést.",
410410
"searchOnEditorModification": "Keresés az aktív szerkesztőben, amikor módosítják."
411411
},
412+
"secondary-window": {
413+
"extract-widget": "Nézet áthelyezése másodlagos ablakba"
414+
},
412415
"task": {
413416
"attachTask": "Feladat csatolása...",
414417
"clearHistory": "Történelem törlése",

packages/core/i18n/nls.it.json

+3
Original file line numberDiff line numberDiff line change
@@ -409,6 +409,9 @@
409409
"resultSubset": "Questo è solo un sottoinsieme di tutti i risultati. Usa un termine di ricerca più specifico per restringere la lista dei risultati.",
410410
"searchOnEditorModification": "Cerca l'editor attivo quando viene modificato."
411411
},
412+
"secondary-window": {
413+
"extract-widget": "Sposta la vista nella finestra secondaria"
414+
},
412415
"task": {
413416
"attachTask": "Allegare il compito...",
414417
"clearHistory": "Storia chiara",

packages/core/i18n/nls.ja.json

+3
Original file line numberDiff line numberDiff line change
@@ -409,6 +409,9 @@
409409
"resultSubset": "これは、すべての結果の一部に過ぎません。より具体的な検索用語を使って、結果リストを絞り込んでください。",
410410
"searchOnEditorModification": "修正されたときにアクティブなエディタを検索します。"
411411
},
412+
"secondary-window": {
413+
"extract-widget": "セカンダリーウィンドウへの表示移動"
414+
},
412415
"task": {
413416
"attachTask": "タスクの添付...",
414417
"clearHistory": "明確な歴史",

packages/core/i18n/nls.json

+3
Original file line numberDiff line numberDiff line change
@@ -409,6 +409,9 @@
409409
"resultSubset": "This is only a subset of all results. Use a more specific search term to narrow down the result list.",
410410
"searchOnEditorModification": "Search the active editor when modified."
411411
},
412+
"secondary-window": {
413+
"extract-widget": "Move View to Secondary Window"
414+
},
412415
"task": {
413416
"attachTask": "Attach Task...",
414417
"clearHistory": "Clear History",

packages/core/i18n/nls.pl.json

+3
Original file line numberDiff line numberDiff line change
@@ -409,6 +409,9 @@
409409
"resultSubset": "To jest tylko podzbiór wszystkich wyników. Użyj bardziej szczegółowego terminu wyszukiwania, aby zawęzić listę wyników.",
410410
"searchOnEditorModification": "Przeszukiwanie aktywnego edytora po modyfikacji."
411411
},
412+
"secondary-window": {
413+
"extract-widget": "Przenieś widok do okna podrzędnego"
414+
},
412415
"task": {
413416
"attachTask": "Dołącz zadanie...",
414417
"clearHistory": "Czysta historia",

packages/core/i18n/nls.pt-br.json

+3
Original file line numberDiff line numberDiff line change
@@ -409,6 +409,9 @@
409409
"resultSubset": "Este é apenas um subconjunto de todos os resultados. Use um termo de busca mais específico para restringir a lista de resultados.",
410410
"searchOnEditorModification": "Pesquise o editor ativo quando modificado."
411411
},
412+
"secondary-window": {
413+
"extract-widget": "Mover vista para a janela secundária"
414+
},
412415
"task": {
413416
"attachTask": "Anexar Tarefa...",
414417
"clearHistory": "Histórico claro",

packages/core/i18n/nls.pt-pt.json

+3
Original file line numberDiff line numberDiff line change
@@ -409,6 +409,9 @@
409409
"resultSubset": "Este é apenas um subconjunto de todos os resultados. Use um termo de pesquisa mais específico para restringir a lista de resultados.",
410410
"searchOnEditorModification": "Pesquisar o editor activo quando modificado."
411411
},
412+
"secondary-window": {
413+
"extract-widget": "Mover vista para a janela secundária"
414+
},
412415
"task": {
413416
"attachTask": "Anexar Tarefa...",
414417
"clearHistory": "História clara",

packages/core/i18n/nls.ru.json

+3
Original file line numberDiff line numberDiff line change
@@ -409,6 +409,9 @@
409409
"resultSubset": "Это только часть всех результатов. Используйте более конкретный поисковый запрос, чтобы сузить список результатов.",
410410
"searchOnEditorModification": "Поиск активного редактора при изменении."
411411
},
412+
"secondary-window": {
413+
"extract-widget": "Переместить вид в дополнительное окно"
414+
},
412415
"task": {
413416
"attachTask": "Прикрепите задание...",
414417
"clearHistory": "Чистая история",

packages/core/i18n/nls.zh-cn.json

+3
Original file line numberDiff line numberDiff line change
@@ -409,6 +409,9 @@
409409
"resultSubset": "这只是所有结果的一个子集。使用一个更具体的搜索词来缩小结果列表。",
410410
"searchOnEditorModification": "修改时搜索活动的编辑器。"
411411
},
412+
"secondary-window": {
413+
"extract-widget": "将视图移至第二窗口"
414+
},
412415
"task": {
413416
"attachTask": "附加任务...",
414417
"clearHistory": "清除历史",

packages/core/src/browser/frontend-application-module.ts

+3
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ import { TooltipService, TooltipServiceImpl } from './tooltip-service';
126126
import { BackendRequestService, RequestService, REQUEST_SERVICE_PATH } from '@theia/request';
127127
import { bindFrontendStopwatch, bindBackendStopwatch } from './performance';
128128
import { SaveResourceService } from './save-resource-service';
129+
import { SecondaryWindowHandler } from './secondary-window-handler';
129130
import { UserWorkingDirectoryProvider } from './user-working-directory-provider';
130131
import { TheiaDockPanel } from './shell/theia-dock-panel';
131132
import { bindStatusBar } from './status-bar';
@@ -430,4 +431,6 @@ export const frontendApplicationModule = new ContainerModule((bind, _unbind, _is
430431
bind(StylingService).toSelf().inSingletonScope();
431432
bindContributionProvider(bind, StylingParticipant);
432433
bind(FrontendApplicationContribution).toService(StylingService);
434+
435+
bind(SecondaryWindowHandler).toSelf().inSingletonScope();
433436
});

0 commit comments

Comments
 (0)