Skip to content

Commit

Permalink
implement setIconPath for webview
Browse files Browse the repository at this point in the history
Signed-off-by: Oleksii Orel <oorel@redhat.com>
  • Loading branch information
olexii4 committed Nov 29, 2018
1 parent fdccc67 commit 34226fc
Show file tree
Hide file tree
Showing 7 changed files with 181 additions and 127 deletions.
2 changes: 1 addition & 1 deletion dev-packages/application-manager/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@
"circular-dependency-plugin": "^5.0.0",
"copy-webpack-plugin": "^4.5.0",
"css-loader": "^0.28.1",
"style-loader": "^0.23.1",
"electron": "1.8.2-beta.5",
"electron-rebuild": "^1.5.11",
"file-loader": "^1.1.11",
Expand All @@ -52,6 +51,7 @@
"worker-loader": "^1.1.1"
},
"devDependencies": {
"style-loader": "^0.23.1",
"@theia/ext-scripts": "^0.3.16"
},
"nyc": {
Expand Down
2 changes: 1 addition & 1 deletion packages/plugin-ext/src/api/plugin-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -807,7 +807,7 @@ export interface WebviewsMain {
$disposeWebview(handle: string): void;
$reveal(handle: string, showOptions: WebviewPanelShowOptions): void;
$setTitle(handle: string, value: string): void;
$setIconPath(handle: string, value: { light: UriComponents, dark: UriComponents } | undefined): void;
$setIconPath(handle: string, value: { light: string, dark: string } | string | undefined): void;
$setHtml(handle: string, value: string): void;
$setOptions(handle: string, options: theia.WebviewOptions): void;
$postMessage(handle: string, value: any): Thenable<boolean>;
Expand Down
99 changes: 82 additions & 17 deletions packages/plugin-ext/src/main/browser/webview/theme-rules-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,14 @@ import { ThemeService } from '@theia/core/lib/browser/theming';

export const ThemeRulesServiceSymbol = Symbol('ThemeRulesService');

interface IconPath {
light: string,
dark: string
}

export class ThemeRulesService {
private styleElement?: HTMLStyleElement;
private icons = new Map<string, IconPath | string>();
protected readonly themeService = ThemeService.get();
protected readonly themeRules = new Map<string, string[]>();

Expand All @@ -30,25 +37,18 @@ export class ThemeRulesService {
protected constructor() {
const global = window as any; // tslint:disable-line
global[ThemeRulesServiceSymbol] = this;
}

setRules(styleSheet: HTMLElement, newRules: string[]): boolean {
const sheet: {
insertRule: (rule: string, index: number) => void;
removeRule: (index: number) => void;
rules: CSSRuleList;
} | undefined = (<any>styleSheet).sheet;

if (!sheet) {
return false;
}
for (let index = sheet.rules!.length; index > 0; index--) {
sheet.removeRule(0);
}
newRules.forEach((rule: string, index: number) => {
sheet.insertRule(rule, index);
this.themeService.onThemeChange(() => {
this.updateIconStyleElement();
});
return true;
}

createStyleSheet(container: HTMLElement = document.getElementsByTagName('head')[0]): HTMLStyleElement {
const style = document.createElement('style');
style.type = 'text/css';
style.media = 'screen';
container.appendChild(style);
return style;
}

getCurrentThemeRules(): string[] {
Expand Down Expand Up @@ -80,4 +80,69 @@ export class ThemeRulesService {

return cssText;
}

setRules(styleSheet: HTMLElement, newRules: string[]): boolean {
const sheet: {
insertRule: (rule: string, index: number) => void;
removeRule: (index: number) => void;
rules: CSSRuleList;
} | undefined = (<any>styleSheet).sheet;

if (!sheet) {
return false;
}
for (let index = sheet.rules!.length; index > 0; index--) {
sheet.removeRule(0);
}
newRules.forEach((rule: string, index: number) => {
sheet.insertRule(rule, index);
});
return true;
}

setIconPath(webviewId: string, iconPath: IconPath | string | undefined) {
if (!iconPath) {
this.icons.delete(webviewId);
} else {
this.icons.set(webviewId, <IconPath | string>iconPath);
}
if (!this.styleElement) {
this.styleElement = this.createStyleSheet();
this.styleElement.id = 'webview-icons';
}
this.updateIconStyleElement();
}

private updateIconStyleElement() {
if (!this.styleElement) {
return;
}
const cssRules: string[] = [`.webview-icon::before {
background-repeat: no-repeat;
display: inline-block;
text-align: center;
vertical-align: middle;
height: 11px;
width: 11px;
content: "";
}`];
this.icons.forEach((value, key) => {
let path: string;
if (typeof value === 'string') {
path = value;
} else {
path = this.isDark() ? value.dark : value.light;
}
if (path.startsWith('/')) {
path = `/webview${path}`;
}
cssRules.push(`.webview-icon.${key}-file-icon::before { background-image: url(${path}); }`);
});
this.styleElement.innerHTML = cssRules.join('\n');
}

private isDark(): boolean {
const currentThemeId: string = this.themeService.getCurrentTheme().id;
return !currentThemeId.includes('light');
}
}
170 changes: 71 additions & 99 deletions packages/plugin-ext/src/main/browser/webview/webview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,28 +30,20 @@ export interface WebviewEvents {

export class WebviewWidget extends BaseWidget {
private static readonly ID = new IdGenerator('webview-widget-');

protected readonly toDispose = new DisposableCollection();
private iframe: HTMLIFrameElement;
private state: string | undefined = undefined;
private loadTimeout: number | undefined;
// private pendingMessages

constructor(title: string, private options: WebviewWidgetOptions, private eventDelegate: WebviewEvents) {
super();
this.id = WebviewWidget.ID.nextId();
this.title.closable = true;
// this.title.caption = this.title.label = this.props.name || 'Browser';
this.title.label = title;
// this.title.iconClass = this.props.iconClass || MiniBrowser.ICON;
this.addClass(MiniBrowser.Styles.MINI_BROWSER);

}

protected onFrameLoad(): void {
// this.hideLoadIndicator();
// this.focus();
}

protected handleMessage(message: any) {
switch (message.command) {
case 'onmessage':
Expand All @@ -70,79 +62,13 @@ export class WebviewWidget extends BaseWidget {
if (!this.iframe || this.options.allowScripts === options.allowScripts) {
return;
}

this.updateSandboxAttribute(this.iframe, options.allowScripts);

this.options = options;
this.reloadFrame();
}

private reloadFrame() {
if (!this.iframe || !this.iframe.contentDocument || !this.iframe.contentDocument.documentElement) {
return;
}
this.setHTML(this.iframe.contentDocument.documentElement.innerHTML);
}

private updateSandboxAttribute(element: HTMLElement, isAllowScript?: boolean) {
if (!element) {
return;
}
const allowScripts = isAllowScript !== undefined ? isAllowScript : this.options.allowScripts;
element.setAttribute('sandbox', allowScripts ? 'allow-scripts allow-forms allow-same-origin' : 'allow-same-origin');
}

private updateApiScript(contentDocument: Document, isAllowScript?: boolean) {
if (!contentDocument) {
return;
}
const allowScripts = isAllowScript !== undefined ? isAllowScript : this.options.allowScripts;
const scriptId = 'webview-widget-codeApi';
if (!allowScripts) {
const script = contentDocument.getElementById(scriptId);
if (!script) {
return;
}
script!.parentElement!.removeChild(script!);
return;
}

const codeApiScript = contentDocument.createElement('script');
codeApiScript.id = scriptId;
codeApiScript.textContent = `
const acquireVsCodeApi = (function() {
let acquired = false;
let state = ${this.state ? `JSON.parse(${JSON.stringify(this.state)})` : undefined};
return () => {
if (acquired) {
throw new Error('An instance of the VS Code API has already been acquired');
}
acquired = true;
return Object.freeze({
postMessage: function(msg) {
return window.postMessageExt({ command: 'onmessage', data: msg }, '*');
},
setState: function(newState) {
state = newState;
window.postMessageExt({ command: 'do-update-state', data: JSON.stringify(newState) }, '*');
return newState;
},
getState: function() {
return state;
}
});
};
})();
delete window.parent;
delete window.top;
delete window.frameElement;
`;
const parent = contentDocument.head ? contentDocument.head : contentDocument.body;
if (parent.hasChildNodes()) {
parent.insertBefore(codeApiScript, parent.firstChild);
} else {
parent.appendChild(codeApiScript);
}
setIconClass(iconClass: string) {
this.title.iconClass = iconClass;
}

setHTML(html: string) {
Expand Down Expand Up @@ -173,13 +99,6 @@ export class WebviewWidget extends BaseWidget {

const onLoad = (contentDocument: any, contentWindow: any) => {
if (contentDocument.body) {
// // Workaround for https://github.com/Microsoft/vscode/issues/12865
// // check new scrollTop and reset if neccessary
// setInitialScrollPosition(contentDocument.body);

// // Bubble out link clicks
// contentDocument.body.addEventListener('click', handleInnerClick);

if (this.eventDelegate && this.eventDelegate.onKeyboardEvent) {
const eventNames = ['keydown', 'keypress', 'click'];
// Delegate events from the `iframe` to the application.
Expand All @@ -192,26 +111,12 @@ export class WebviewWidget extends BaseWidget {
this.eventDelegate.onLoad(<Document>contentDocument);
}
}

// const newFrame = getPendingFrame();
if (newFrame && newFrame.contentDocument === contentDocument) {
// const oldActiveFrame = getActiveFrame();
// if (oldActiveFrame) {
// document.body.removeChild(oldActiveFrame);
// }
(<any>contentWindow).postMessageExt = (e: any) => {
this.handleMessage(e);
};
// newFrame.setAttribute('id', 'active-frame');
newFrame.style.visibility = 'visible';
newFrame.contentWindow!.focus();

// contentWindow.addEventListener('scroll', handleInnerScroll);

// pendingMessages.forEach((data) => {
// contentWindow.postMessage(data, '*');
// });
// pendingMessages = [];
}
};

Expand All @@ -230,12 +135,79 @@ export class WebviewWidget extends BaseWidget {
onLoad(e.target, newFrame.contentWindow);
}
});

newFrame.contentDocument!.write(newDocument!.documentElement!.innerHTML);
newFrame.contentDocument!.close();

this.updateSandboxAttribute(newFrame);
}

private reloadFrame() {
if (!this.iframe || !this.iframe.contentDocument || !this.iframe.contentDocument.documentElement) {
return;
}
this.setHTML(this.iframe.contentDocument.documentElement.innerHTML);
}

private updateSandboxAttribute(element: HTMLElement, isAllowScript?: boolean) {
if (!element) {
return;
}
const allowScripts = isAllowScript !== undefined ? isAllowScript : this.options.allowScripts;
element.setAttribute('sandbox', allowScripts ? 'allow-scripts allow-forms allow-same-origin' : 'allow-same-origin');
}

private updateApiScript(contentDocument: Document, isAllowScript?: boolean) {
if (!contentDocument) {
return;
}
const allowScripts = isAllowScript !== undefined ? isAllowScript : this.options.allowScripts;
const scriptId = 'webview-widget-codeApi';
if (!allowScripts) {
const script = contentDocument.getElementById(scriptId);
if (!script) {
return;
}
script!.parentElement!.removeChild(script!);
return;
}

const codeApiScript = contentDocument.createElement('script');
codeApiScript.id = scriptId;
codeApiScript.textContent = `
const acquireVsCodeApi = (function() {
let acquired = false;
let state = ${this.state ? `JSON.parse(${JSON.stringify(this.state)})` : undefined};
return () => {
if (acquired) {
throw new Error('An instance of the VS Code API has already been acquired');
}
acquired = true;
return Object.freeze({
postMessage: function(msg) {
return window.postMessageExt({ command: 'onmessage', data: msg }, '*');
},
setState: function(newState) {
state = newState;
window.postMessageExt({ command: 'do-update-state', data: JSON.stringify(newState) }, '*');
return newState;
},
getState: function() {
return state;
}
});
};
})();
delete window.parent;
delete window.top;
delete window.frameElement;
`;
const parent = contentDocument.head ? contentDocument.head : contentDocument.body;
if (parent.hasChildNodes()) {
parent.insertBefore(codeApiScript, parent.firstChild);
} else {
parent.appendChild(codeApiScript);
}
}
}

export namespace WebviewWidget {
Expand Down
Loading

0 comments on commit 34226fc

Please sign in to comment.