Skip to content

Commit

Permalink
Multi window support for web views
Browse files Browse the repository at this point in the history
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`

Contributed on behalf of ST Microelectronics and Ericsson and by ARM and EclipseSource.

Co-authored-by: Stefan Dirix <sdirix@eclipsesource.com>
Co-authored-by: robmor01 <Rob.Moran@arm.com>
Signed-off-by: Lucas Koehler <lkoehler@eclipsesource.com>
  • Loading branch information
3 people committed Sep 2, 2022
1 parent 9e5db77 commit 434690b
Show file tree
Hide file tree
Showing 43 changed files with 888 additions and 40 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,14 @@

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

## v1.30.0

- [core] Added support for moving webview-based views into a secondary window/tab for browser applications. Added new extension `secondary-window-ui` 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

<a name="breaking_changes_1.30.0">[Breaking Changes:](#breaking_changes_1.30.0)</a>

- [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

## v1.29.0 - 8/25/2022

- [application-manager] added the `applicationName` in the frontend generator [#11575](https://github.com/eclipse-theia/theia/pull/11575)
Expand Down
1 change: 1 addition & 0 deletions dev-packages/application-manager/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
"source-map": "^0.6.1",
"source-map-loader": "^2.0.1",
"source-map-support": "^0.5.19",
"string-replace-loader": "^3.1.0",
"style-loader": "^2.0.0",
"umd-compat-loader": "^2.1.2",
"webpack": "^5.48.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export class FrontendGenerator extends AbstractGenerator {
const frontendModules = this.pck.targetFrontendModules;
await this.write(this.pck.frontend('index.html'), this.compileIndexHtml(frontendModules));
await this.write(this.pck.frontend('index.js'), this.compileIndexJs(frontendModules));
await this.write(this.pck.frontend('secondary-window.html'), this.compileSecondaryWindowHtml());
if (this.pck.isElectron()) {
const electronMainModules = this.pck.targetElectronMainModules;
await this.write(this.pck.frontend('electron-main.js'), this.compileElectronMain(electronMainModules));
Expand Down Expand Up @@ -195,4 +196,48 @@ module.exports = Promise.resolve()${this.compileElectronMainModuleImports(electr
`;
}

/** HTML for secondary windows that contain an extracted widget. */
protected compileSecondaryWindowHtml(): string {
return `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Theia — Secondary Window</title>
<style>
html, body {
overflow: hidden;
-ms-overflow-style: none;
}
body {
margin: 0;
}
html,
head,
body,
#widget-host,
.p-Widget {
width: 100% !important;
height: 100% !important;
}
</style>
<script>
window.addEventListener('message', e => {
// Only process messages from Theia main window
if (e.source === window.opener) {
// Delegate message to iframe
document.getElementsByTagName('iframe').item(0).contentWindow.postMessage({ ...e.data }, '*');
}
});
</script>
</head>
<body>
<div id="widget-host"></div>
</body>
</html>`;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ export class WebpackGenerator extends AbstractGenerator {
const path = require('path');
const webpack = require('webpack');
const yargs = require('yargs');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const CircularDependencyPlugin = require('circular-dependency-plugin');
const CompressionPlugin = require('compression-webpack-plugin')
Expand All @@ -72,6 +73,12 @@ const { mode, staticCompression } = yargs.option('mode', {
const development = mode === 'development';
const plugins = [
new CopyWebpackPlugin({
patterns: [{
// copy secondary window html file to lib folder
from: path.resolve(__dirname, 'src-gen/frontend/secondary-window.html')
}]
}),
new webpack.ProvidePlugin({
// the Buffer class doesn't exist in the browser but some dependencies rely on it
Buffer: ['buffer', 'Buffer']
Expand Down Expand Up @@ -104,6 +111,16 @@ module.exports = {
cache: staticCompression,
module: {
rules: [
{
// Removes the host check in PhosphorJS to enable moving widgets to secondary windows.
test: /widget\\.js$/,
loader: 'string-replace-loader',
include: /node_modules[\\\\/]@phosphor[\\\\/]widgets[\\\\/]lib/,
options: {
search: /^.*?throw new Error\\('Host is not attached.'\\).*?$/gm,
replace: ''
}
},
{
test: /\\.css$/,
exclude: /materialcolors\\.css$|\\.useable\\.css$/,
Expand Down
1 change: 1 addition & 0 deletions examples/browser/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
"@theia/scm": "1.29.0",
"@theia/scm-extra": "1.29.0",
"@theia/search-in-workspace": "1.29.0",
"@theia/secondary-window-ui": "1.29.0",
"@theia/task": "1.29.0",
"@theia/terminal": "1.29.0",
"@theia/timeline": "1.29.0",
Expand Down
3 changes: 3 additions & 0 deletions examples/browser/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,9 @@
{
"path": "../../packages/search-in-workspace"
},
{
"path": "../../packages/secondary-window-ui"
},
{
"path": "../../packages/task"
},
Expand Down
3 changes: 3 additions & 0 deletions packages/core/i18n/nls.cs.json
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,9 @@
"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.",
"searchOnEditorModification": "Prohledat aktivní editor při úpravě."
},
"secondary-window-ui": {
"extract-widget": "Přesunutí zobrazení do sekundárního okna"
},
"task": {
"attachTask": "Připojte úkol...",
"clearHistory": "Vymazat historii",
Expand Down
3 changes: 3 additions & 0 deletions packages/core/i18n/nls.de.json
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,9 @@
"resultSubset": "Dies ist nur eine Teilmenge aller Ergebnisse. Verwenden Sie einen spezifischeren Suchbegriff, um die Ergebnisliste einzugrenzen.",
"searchOnEditorModification": "Durchsucht den aktiven Editor nach Änderungen."
},
"secondary-window-ui": {
"extract-widget": "Ansicht in sekundäres Fenster verschieben"
},
"task": {
"attachTask": "Aufgabe anhängen...",
"clearHistory": "Geschichte löschen",
Expand Down
3 changes: 3 additions & 0 deletions packages/core/i18n/nls.es.json
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,9 @@
"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.",
"searchOnEditorModification": "Busca en el editor activo cuando se modifica."
},
"secondary-window-ui": {
"extract-widget": "Mover la vista a la ventana secundaria"
},
"task": {
"attachTask": "Adjuntar tarea...",
"clearHistory": "Historia clara",
Expand Down
3 changes: 3 additions & 0 deletions packages/core/i18n/nls.fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,9 @@
"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.",
"searchOnEditorModification": "Rechercher l'éditeur actif lorsqu'il est modifié."
},
"secondary-window-ui": {
"extract-widget": "Déplacer la vue vers une fenêtre secondaire"
},
"task": {
"attachTask": "Attacher la tâche...",
"clearHistory": "Histoire claire",
Expand Down
3 changes: 3 additions & 0 deletions packages/core/i18n/nls.hu.json
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,9 @@
"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.",
"searchOnEditorModification": "Keresés az aktív szerkesztőben, amikor módosítják."
},
"secondary-window-ui": {
"extract-widget": "Nézet áthelyezése másodlagos ablakba"
},
"task": {
"attachTask": "Feladat csatolása...",
"clearHistory": "Történelem törlése",
Expand Down
3 changes: 3 additions & 0 deletions packages/core/i18n/nls.it.json
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,9 @@
"resultSubset": "Questo è solo un sottoinsieme di tutti i risultati. Usa un termine di ricerca più specifico per restringere la lista dei risultati.",
"searchOnEditorModification": "Cerca l'editor attivo quando viene modificato."
},
"secondary-window-ui": {
"extract-widget": "Sposta la vista nella finestra secondaria"
},
"task": {
"attachTask": "Allegare il compito...",
"clearHistory": "Storia chiara",
Expand Down
3 changes: 3 additions & 0 deletions packages/core/i18n/nls.ja.json
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,9 @@
"resultSubset": "これは、すべての結果の一部に過ぎません。より具体的な検索用語を使って、結果リストを絞り込んでください。",
"searchOnEditorModification": "修正されたときにアクティブなエディタを検索します。"
},
"secondary-window-ui": {
"extract-widget": "セカンダリーウィンドウへの表示移動"
},
"task": {
"attachTask": "タスクの添付...",
"clearHistory": "明確な歴史",
Expand Down
3 changes: 3 additions & 0 deletions packages/core/i18n/nls.json
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,9 @@
"resultSubset": "This is only a subset of all results. Use a more specific search term to narrow down the result list.",
"searchOnEditorModification": "Search the active editor when modified."
},
"secondary-window-ui": {
"extract-widget": "Move View to Secondary Window"
},
"task": {
"attachTask": "Attach Task...",
"clearHistory": "Clear History",
Expand Down
3 changes: 3 additions & 0 deletions packages/core/i18n/nls.pl.json
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,9 @@
"resultSubset": "To jest tylko podzbiór wszystkich wyników. Użyj bardziej szczegółowego terminu wyszukiwania, aby zawęzić listę wyników.",
"searchOnEditorModification": "Przeszukiwanie aktywnego edytora po modyfikacji."
},
"secondary-window-ui": {
"extract-widget": "Przenieś widok do okna podrzędnego"
},
"task": {
"attachTask": "Dołącz zadanie...",
"clearHistory": "Czysta historia",
Expand Down
3 changes: 3 additions & 0 deletions packages/core/i18n/nls.pt-br.json
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,9 @@
"resultSubset": "Este é apenas um subconjunto de todos os resultados. Use um termo de busca mais específico para restringir a lista de resultados.",
"searchOnEditorModification": "Pesquise o editor ativo quando modificado."
},
"secondary-window-ui": {
"extract-widget": "Mover vista para a janela secundária"
},
"task": {
"attachTask": "Anexar Tarefa...",
"clearHistory": "Histórico claro",
Expand Down
3 changes: 3 additions & 0 deletions packages/core/i18n/nls.pt-pt.json
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,9 @@
"resultSubset": "Este é apenas um subconjunto de todos os resultados. Use um termo de pesquisa mais específico para restringir a lista de resultados.",
"searchOnEditorModification": "Pesquisar o editor activo quando modificado."
},
"secondary-window-ui": {
"extract-widget": "Mover vista para a janela secundária"
},
"task": {
"attachTask": "Anexar Tarefa...",
"clearHistory": "História clara",
Expand Down
3 changes: 3 additions & 0 deletions packages/core/i18n/nls.ru.json
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,9 @@
"resultSubset": "Это только часть всех результатов. Используйте более конкретный поисковый запрос, чтобы сузить список результатов.",
"searchOnEditorModification": "Поиск активного редактора при изменении."
},
"secondary-window-ui": {
"extract-widget": "Переместить вид в дополнительное окно"
},
"task": {
"attachTask": "Прикрепите задание...",
"clearHistory": "Чистая история",
Expand Down
3 changes: 3 additions & 0 deletions packages/core/i18n/nls.zh-cn.json
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,9 @@
"resultSubset": "这只是所有结果的一个子集。使用一个更具体的搜索词来缩小结果列表。",
"searchOnEditorModification": "修改时搜索活动的编辑器。"
},
"secondary-window-ui": {
"extract-widget": "将视图移至第二窗口"
},
"task": {
"attachTask": "附加任务...",
"clearHistory": "清除历史",
Expand Down
3 changes: 3 additions & 0 deletions packages/core/src/browser/frontend-application-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ import { TooltipService, TooltipServiceImpl } from './tooltip-service';
import { BackendRequestService, RequestService, REQUEST_SERVICE_PATH } from '@theia/request';
import { bindFrontendStopwatch, bindBackendStopwatch } from './performance';
import { SaveResourceService } from './save-resource-service';
import { SecondaryWindowHandler } from './secondary-window-handler';
import { UserWorkingDirectoryProvider } from './user-working-directory-provider';
import { TheiaDockPanel } from './shell/theia-dock-panel';
import { bindStatusBar } from './status-bar';
Expand Down Expand Up @@ -430,4 +431,6 @@ export const frontendApplicationModule = new ContainerModule((bind, _unbind, _is
bind(StylingService).toSelf().inSingletonScope();
bindContributionProvider(bind, StylingParticipant);
bind(FrontendApplicationContribution).toService(StylingService);

bind(SecondaryWindowHandler).toSelf().inSingletonScope();
});
Loading

0 comments on commit 434690b

Please sign in to comment.