Skip to content
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

Clipboard API for Plugins #5994

Merged
merged 1 commit into from
Sep 17, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 111 additions & 0 deletions packages/core/src/browser/browser-clipboard-service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
/********************************************************************************
* Copyright (C) 2019 RedHat and others.
akosyakov marked this conversation as resolved.
Show resolved Hide resolved
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the Eclipse
* Public License v. 2.0 are satisfied: GNU General Public License, version 2
* with the GNU Classpath Exception which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/

import { injectable, inject } from 'inversify';
import { isFirefox } from './browser';
import { ClipboardService } from './clipboard-service';
import { ILogger } from '../common/logger';
import { MessageService } from '../common/message-service';

export interface NavigatorClipboard {
readText(): Promise<string>;
writeText(value: string): Promise<void>;
}
export interface PermissionStatus {
state: 'granted' | 'prompt' | 'denied'
}
export interface NavigatorPermissions {
query(options: { name: string }): Promise<PermissionStatus>
}

@injectable()
export class BrowserClipboardService implements ClipboardService {

@inject(MessageService)
protected readonly messageService: MessageService;

@inject(ILogger)
protected readonly logger: ILogger;

async readText(): Promise<string> {
let permission;
try {
permission = await this.queryPermission('clipboard-read');
} catch (e) {
this.logger.error('Failed checking a clipboard-read permission.', e);
// in FireFox, Clipboard API isn't gated with the permissions
try {
return await this.getClipboardAPI().readText();
} catch (e) {
this.logger.error('Failed reading clipboard content.', e);
if (isFirefox) {
this.messageService.warn(`Clipboard API is not available.
It can be enabled by 'dom.events.testing.asyncClipboard' preference on 'about:config' page. Then reload Theia.
Note, it will allow FireFox getting full access to the system clipboard.`);
}
throw new Error('Failed reading clipboard content.');
}
}
if (permission.state === 'denied') {
// most likely, the user intentionally denied the access
this.messageService.error("Access to the clipboard is denied. Check your browser's permission.");
throw new Error('Access to the clipboard is denied.');
}
return this.getClipboardAPI().readText();
}

async writeText(value: string): Promise<void> {
let permission;
try {
permission = await this.queryPermission('clipboard-write');
} catch (e) {
this.logger.error('Failed checking a clipboard-write permission.', e);
// in FireFox, Clipboard API isn't gated with the permissions
try {
await this.getClipboardAPI().writeText(value);
return;
} catch (e) {
this.logger.error('Failed writing to the clipboard.', e);
if (isFirefox) {
this.messageService.warn(`Clipboard API is not available.
It can be enabled by 'dom.events.testing.asyncClipboard' preference on 'about:config' page. Then reload Theia.
Note, it will allow FireFox getting full access to the system clipboard.`);
}
throw new Error('Failed writing the the clipboard.');
}
}
if (permission.state === 'denied') {
// most likely, the user intentionally denied the access
this.messageService.error("Access to the clipboard is denied. Check your browser's permission.");
throw new Error('Access to the clipboard is denied.');
}
return this.getClipboardAPI().writeText(value);
}

protected async queryPermission(name: string): Promise<PermissionStatus> {
if ('permissions' in navigator) {
return (<NavigatorPermissions>navigator['permissions']).query({ name: name });
}
throw new Error('Permissions API unavailable');
}

protected getClipboardAPI(): NavigatorClipboard {
if ('clipboard' in navigator) {
return (<NavigatorClipboard>navigator['clipboard']);
}
throw new Error('Async Clipboard API unavailable');
}
}
23 changes: 23 additions & 0 deletions packages/core/src/browser/clipboard-service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/********************************************************************************
* Copyright (C) 2019 RedHat and others.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the Eclipse
* Public License v. 2.0 are satisfied: GNU General Public License, version 2
* with the GNU Classpath Exception which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/

import { MaybePromise } from '../common/types';

export const ClipboardService = Symbol('ClipboardService');
export interface ClipboardService {
readText(): MaybePromise<string>;
writeText(value: string): MaybePromise<void>;
}
3 changes: 3 additions & 0 deletions packages/core/src/browser/window/browser-window-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,12 @@ import { ContainerModule } from 'inversify';
import { WindowService } from '../../browser/window/window-service';
import { DefaultWindowService } from '../../browser/window/default-window-service';
import { FrontendApplicationContribution } from '../frontend-application';
import { ClipboardService } from '../clipboard-service';
import { BrowserClipboardService } from '../browser-clipboard-service';

export default new ContainerModule(bind => {
bind(DefaultWindowService).toSelf().inSingletonScope();
bind(WindowService).toService(DefaultWindowService);
bind(FrontendApplicationContribution).toService(DefaultWindowService);
bind(ClipboardService).to(BrowserClipboardService).inSingletonScope();
});
32 changes: 32 additions & 0 deletions packages/core/src/electron-browser/electron-clipboard-service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/********************************************************************************
* Copyright (C) 2019 RedHat and others.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the Eclipse
* Public License v. 2.0 are satisfied: GNU General Public License, version 2
* with the GNU Classpath Exception which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/

import { clipboard } from 'electron';
import { injectable } from 'inversify';
import { ClipboardService } from '../browser/clipboard-service';

@injectable()
export class ElectronClipboardService implements ClipboardService {

readText(): string {
return clipboard.readText();
}

writeText(value: string): void {
clipboard.writeText(value);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,11 @@ import { ContainerModule } from 'inversify';
import { WindowService } from '../../browser/window/window-service';
import { ElectronWindowService } from './electron-window-service';
import { FrontendApplicationContribution } from '../../browser/frontend-application';
import { ElectronClipboardService } from '../electron-clipboard-service';
import { ClipboardService } from '../../browser/clipboard-service';

export default new ContainerModule(bind => {
bind(WindowService).to(ElectronWindowService).inSingletonScope();
bind(FrontendApplicationContribution).toService(WindowService);
bind(ClipboardService).to(ElectronClipboardService).inSingletonScope();
});
8 changes: 7 additions & 1 deletion packages/plugin-ext/src/common/plugin-api-rpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1253,6 +1253,11 @@ export interface FileSystemMain {
$unregisterProvider(handle: number): void;
}

export interface ClipboardMain {
$readText(): Promise<string>;
$writeText(value: string): Promise<void>;
}

export const PLUGIN_RPC_CONTEXT = {
COMMAND_REGISTRY_MAIN: <ProxyIdentifier<CommandRegistryMain>>createProxyIdentifier<CommandRegistryMain>('CommandRegistryMain'),
QUICK_OPEN_MAIN: createProxyIdentifier<QuickOpenMain>('QuickOpenMain'),
Expand All @@ -1278,7 +1283,8 @@ export const PLUGIN_RPC_CONTEXT = {
FILE_SYSTEM_MAIN: createProxyIdentifier<FileSystemMain>('FileSystemMain'),
SCM_MAIN: createProxyIdentifier<ScmMain>('ScmMain'),
DECORATIONS_MAIN: createProxyIdentifier<DecorationsMain>('DecorationsMain'),
WINDOW_MAIN: createProxyIdentifier<WindowMain>('WindowMain')
WINDOW_MAIN: createProxyIdentifier<WindowMain>('WindowMain'),
CLIPBOARD_MAIN: <ProxyIdentifier<ClipboardMain>>createProxyIdentifier<ClipboardMain>('ClipboardMain')
};

export const MAIN_RPC_CONTEXT = {
Expand Down
5 changes: 4 additions & 1 deletion packages/plugin-ext/src/hosted/browser/worker/worker-main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import { EditorsAndDocumentsExtImpl } from '../../../plugin/editors-and-document
import { WorkspaceExtImpl } from '../../../plugin/workspace';
import { MessageRegistryExt } from '../../../plugin/message-registry';
import { WorkerEnvExtImpl } from './worker-env-ext';
import { ClipboardExt } from '../../../plugin/clipboard-ext';

// tslint:disable-next-line:no-any
const ctx = self as any;
Expand Down Expand Up @@ -55,6 +56,7 @@ const messageRegistryExt = new MessageRegistryExt(rpc);
const workspaceExt = new WorkspaceExtImpl(rpc, editorsAndDocuments, messageRegistryExt);
const preferenceRegistryExt = new PreferenceRegistryExtImpl(rpc, workspaceExt);
const debugExt = createDebugExtStub(rpc);
const clipboardExt = new ClipboardExt(rpc);

const pluginManager = new PluginManagerExtImpl({
// tslint:disable-next-line:no-any
Expand Down Expand Up @@ -133,7 +135,8 @@ const apiFactory = createAPIFactory(
preferenceRegistryExt,
editorsAndDocuments,
workspaceExt,
messageRegistryExt
messageRegistryExt,
clipboardExt
);
let defaultApi: typeof theia;

Expand Down
5 changes: 4 additions & 1 deletion packages/plugin-ext/src/hosted/node/plugin-host-rpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { EditorsAndDocumentsExtImpl } from '../../plugin/editors-and-documents';
import { WorkspaceExtImpl } from '../../plugin/workspace';
import { MessageRegistryExt } from '../../plugin/message-registry';
import { EnvNodeExtImpl } from '../../plugin/node/env-node-ext';
import { ClipboardExt } from '../../plugin/clipboard-ext';

/**
* Handle the RPC calls.
Expand All @@ -47,6 +48,7 @@ export class PluginHostRPC {
const messageRegistryExt = new MessageRegistryExt(this.rpc);
const workspaceExt = new WorkspaceExtImpl(this.rpc, editorsAndDocumentsExt, messageRegistryExt);
const preferenceRegistryExt = new PreferenceRegistryExtImpl(this.rpc, workspaceExt);
const clipboardExt = new ClipboardExt(this.rpc);
this.pluginManager = this.createPluginManager(envExt, preferenceRegistryExt, this.rpc);
this.rpc.set(MAIN_RPC_CONTEXT.HOSTED_PLUGIN_MANAGER_EXT, this.pluginManager);
this.rpc.set(MAIN_RPC_CONTEXT.EDITORS_AND_DOCUMENTS_EXT, editorsAndDocumentsExt);
Expand All @@ -61,7 +63,8 @@ export class PluginHostRPC {
preferenceRegistryExt,
editorsAndDocumentsExt,
workspaceExt,
messageRegistryExt
messageRegistryExt,
clipboardExt
);
}

Expand Down
38 changes: 38 additions & 0 deletions packages/plugin-ext/src/main/browser/clipboard-main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/********************************************************************************
* Copyright (C) 2019 RedHat and others.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the Eclipse
* Public License v. 2.0 are satisfied: GNU General Public License, version 2
* with the GNU Classpath Exception which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/

import { interfaces } from 'inversify';
import { ClipboardMain } from '../../common';
import { ClipboardService } from '@theia/core/lib/browser/clipboard-service';

export class ClipboardMainImpl implements ClipboardMain {

protected readonly clipboardService: ClipboardService;

constructor(container: interfaces.Container) {
this.clipboardService = container.get(ClipboardService);
}

async $readText(): Promise<string> {
const result = await this.clipboardService.readText();
return result;
}

async $writeText(value: string): Promise<void> {
await this.clipboardService.writeText(value);
}

}
4 changes: 4 additions & 0 deletions packages/plugin-ext/src/main/browser/main-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import { DebugMainImpl } from './debug/debug-main';
import { FileSystemMainImpl } from './file-system-main';
import { ScmMainImpl } from './scm-main';
import { DecorationsMainImpl } from './decorations/decorations-main';
import { ClipboardMainImpl } from './clipboard-main';

export function setUpPluginApi(rpc: RPCProtocol, container: interfaces.Container): void {
const commandRegistryMain = new CommandRegistryMainImpl(rpc, container);
Expand Down Expand Up @@ -113,4 +114,7 @@ export function setUpPluginApi(rpc: RPCProtocol, container: interfaces.Container

const windowMain = new WindowStateMain(rpc, container);
rpc.set(PLUGIN_RPC_CONTEXT.WINDOW_MAIN, windowMain);

const clipboardMain = new ClipboardMainImpl(container);
rpc.set(PLUGIN_RPC_CONTEXT.CLIPBOARD_MAIN, clipboardMain);
}
37 changes: 37 additions & 0 deletions packages/plugin-ext/src/plugin/clipboard-ext.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/********************************************************************************
* Copyright (C) 2019 RedHat and others.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the Eclipse
* Public License v. 2.0 are satisfied: GNU General Public License, version 2
* with the GNU Classpath Exception which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/

import * as theia from '@theia/plugin';
import { RPCProtocol } from '../common/rpc-protocol';
import { PLUGIN_RPC_CONTEXT, ClipboardMain } from '../common';

export class ClipboardExt implements theia.Clipboard {

protected readonly proxy: ClipboardMain;

constructor(rpc: RPCProtocol) {
this.proxy = rpc.getProxy(PLUGIN_RPC_CONTEXT.CLIPBOARD_MAIN);
}

readText(): Promise<string> {
return this.proxy.$readText();
}

writeText(value: string): Promise<void> {
return this.proxy.$writeText(value);
}

}
6 changes: 4 additions & 2 deletions packages/plugin-ext/src/plugin/plugin-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ import { ScmExtImpl } from './scm';
import { DecorationProvider, LineChange } from '@theia/plugin';
import { DecorationsExtImpl } from './decorations';
import { TextEditorExt } from './text-editor';
import { ClipboardExt } from './clipboard-ext';

export function createAPIFactory(
rpc: RPCProtocol,
Expand All @@ -148,7 +149,8 @@ export function createAPIFactory(
preferenceRegistryExt: PreferenceRegistryExtImpl,
editorsAndDocumentsExt: EditorsAndDocumentsExtImpl,
workspaceExt: WorkspaceExtImpl,
messageRegistryExt: MessageRegistryExt
messageRegistryExt: MessageRegistryExt,
clipboard: ClipboardExt
): PluginAPIFactory {

const commandRegistry = rpc.set(MAIN_RPC_CONTEXT.COMMAND_REGISTRY_EXT, new CommandRegistryImpl(rpc));
Expand Down Expand Up @@ -491,7 +493,7 @@ export function createAPIFactory(
get machineId(): string { return envExt.machineId; },
get sessionId(): string { return envExt.sessionId; },
get uriScheme(): string { return envExt.uriScheme; },

clipboard,
getEnvVariable(envVarName: string): PromiseLike<string | undefined> {
return envExt.getEnvVariable(envVarName);
},
Expand Down
Loading