Add simple browser extension (#109276)

* Add support for TS's Hierarchical refactorings API

https://github.com/microsoft/TypeScript/pull/41975

* Add simple browser extension

This change adds a new 'simple browser' extension. This extension uses a webview to render webpages directly in VS Code. We plan on using it for optionally previewing local servers in both desktop and codespaces

The browser itself has a number of limitations due to the security around iframes:

- It traps keyboard focus
- We can't detect if a page fails to load
- We can't track the current url of the iframe

* Add experimental alert when the iframe is focused

* Disable events on focus warning

* Hooking up simple browser to opener
This commit is contained in:
Matt Bierner 2021-01-04 19:06:53 -08:00 committed by GitHub
parent 69dfa670ef
commit 3ed300eb9d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
25 changed files with 3585 additions and 0 deletions

View file

@ -59,6 +59,7 @@ const compilations = [
'php-language-features/tsconfig.json',
'python/tsconfig.json',
'search-result/tsconfig.json',
'simple-browser/tsconfig.json',
'typescript-language-features/test-workspace/tsconfig.json',
'typescript-language-features/tsconfig.json',
'vscode-api-tests/tsconfig.json',

View file

@ -93,6 +93,7 @@ const indentationFilter = [
'!**/*.Dockerfile',
'!**/*.dockerfile',
'!extensions/markdown-language-features/media/*.js',
'!extensions/simple-browser/media/*.js',
];
const copyrightFilter = [

View file

@ -34,6 +34,7 @@ exports.dirs = [
'extensions/npm',
'extensions/php-language-features',
'extensions/search-result',
'extensions/simple-browser',
'extensions/typescript-language-features',
'extensions/vscode-api-tests',
'extensions/vscode-colorize-tests',

View file

@ -0,0 +1,12 @@
test/**
test-workspace/**
src/**
tsconfig.json
out/test/**
out/**
extension.webpack.config.js
extension-browser.webpack.config.js
cgmanifest.json
yarn.lock
preview-src/**
webpack.config.js

View file

@ -0,0 +1,3 @@
# Simple Browser files
**Notice:** This extension is bundled with Visual Studio Code. It can be disabled but not uninstalled.

View file

@ -0,0 +1,17 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
//@ts-check
'use strict';
const withBrowserDefaults = require('../shared.webpack.config').browser;
module.exports = withBrowserDefaults({
context: __dirname,
entry: {
extension: './src/extension.ts'
}
});

View file

@ -0,0 +1,20 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
//@ts-check
'use strict';
const withDefaults = require('../shared.webpack.config');
module.exports = withDefaults({
context: __dirname,
resolve: {
mainFields: ['module', 'main']
},
entry: {
extension: './src/extension.ts',
}
});

View file

@ -0,0 +1,201 @@
/******/ (function(modules) { // webpackBootstrap
/******/ // The module cache
/******/ var installedModules = {};
/******/
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId]) {
/******/ return installedModules[moduleId].exports;
/******/ }
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ i: moduleId,
/******/ l: false,
/******/ exports: {}
/******/ };
/******/
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/ // Flag the module as loaded
/******/ module.l = true;
/******/
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/
/******/
/******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = modules;
/******/
/******/ // expose the module cache
/******/ __webpack_require__.c = installedModules;
/******/
/******/ // define getter function for harmony exports
/******/ __webpack_require__.d = function(exports, name, getter) {
/******/ if(!__webpack_require__.o(exports, name)) {
/******/ Object.defineProperty(exports, name, { enumerable: true, get: getter });
/******/ }
/******/ };
/******/
/******/ // define __esModule on exports
/******/ __webpack_require__.r = function(exports) {
/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
/******/ }
/******/ Object.defineProperty(exports, '__esModule', { value: true });
/******/ };
/******/
/******/ // create a fake namespace object
/******/ // mode & 1: value is a module id, require it
/******/ // mode & 2: merge all properties of value into the ns
/******/ // mode & 4: return value when already ns object
/******/ // mode & 8|1: behave like require
/******/ __webpack_require__.t = function(value, mode) {
/******/ if(mode & 1) value = __webpack_require__(value);
/******/ if(mode & 8) return value;
/******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
/******/ var ns = Object.create(null);
/******/ __webpack_require__.r(ns);
/******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value });
/******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
/******/ return ns;
/******/ };
/******/
/******/ // getDefaultExport function for compatibility with non-harmony modules
/******/ __webpack_require__.n = function(module) {
/******/ var getter = module && module.__esModule ?
/******/ function getDefault() { return module['default']; } :
/******/ function getModuleExports() { return module; };
/******/ __webpack_require__.d(getter, 'a', getter);
/******/ return getter;
/******/ };
/******/
/******/ // Object.prototype.hasOwnProperty.call
/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "";
/******/
/******/
/******/ // Load entry module and return exports
/******/ return __webpack_require__(__webpack_require__.s = "./preview-src/index.ts");
/******/ })
/************************************************************************/
/******/ ({
/***/ "./preview-src/events.ts":
/*!*******************************!*\
!*** ./preview-src/events.ts ***!
\*******************************/
/*! no static exports found */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.onceDocumentLoaded = void 0;
function onceDocumentLoaded(f) {
if (document.readyState === 'loading' || document.readyState === 'uninitialized') {
document.addEventListener('DOMContentLoaded', f);
}
else {
f();
}
}
exports.onceDocumentLoaded = onceDocumentLoaded;
/***/ }),
/***/ "./preview-src/index.ts":
/*!******************************!*\
!*** ./preview-src/index.ts ***!
\******************************/
/*! no static exports found */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
Object.defineProperty(exports, "__esModule", { value: true });
const events_1 = __webpack_require__(/*! ./events */ "./preview-src/events.ts");
const vscode = acquireVsCodeApi();
function getSettings() {
const element = document.getElementById('simple-browser-settings');
if (element) {
const data = element.getAttribute('data-settings');
if (data) {
return JSON.parse(data);
}
}
throw new Error(`Could not load settings`);
}
const settings = getSettings();
const iframe = document.querySelector('iframe');
const header = document.querySelector('.header');
const input = header.querySelector('.url-input');
const forwardButton = header.querySelector('.forward-button');
const backButton = header.querySelector('.back-button');
const reloadButton = header.querySelector('.reload-button');
const openExternalButton = header.querySelector('.open-external-button');
window.addEventListener('message', e => {
switch (e.data.type) {
case 'focus':
{
iframe.focus();
break;
}
}
});
events_1.onceDocumentLoaded(() => {
setInterval(() => {
var _a;
const iframeFocused = ((_a = document.activeElement) === null || _a === void 0 ? void 0 : _a.tagName) === 'IFRAME';
document.body.classList.toggle('iframe-focused', iframeFocused);
}, 50);
iframe.addEventListener('load', () => {
// Noop
});
input.addEventListener('change', e => {
const url = e.target.value;
navigateTo(url);
});
forwardButton.addEventListener('click', () => {
history.forward();
});
backButton.addEventListener('click', () => {
history.back();
});
openExternalButton.addEventListener('click', () => {
vscode.postMessage({
type: 'openExternal',
url: input.value
});
});
reloadButton.addEventListener('click', () => {
// This does not seem to trigger what we want
// history.go(0);
// This incorrectly adds entries to the history but does reload
iframe.src = input.value;
});
navigateTo(settings.url);
function navigateTo(url) {
iframe.src = url;
}
});
/***/ })
/******/ });
//# sourceMappingURL=index.js.map

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,115 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
:root {
--container-paddding: 20px;
--input-padding-vertical: 2px;
--input-padding-horizontal: 4px;
--input-margin-vertical: 4px;
--input-margin-horizontal: 0;
}
html, body {
height: 100%;
min-height: 100%;
padding: 0;
margin: 0;
}
body {
display: grid;
grid-template-rows: auto 1fr;
}
input:not([type='checkbox']),
textarea {
display: block;
width: 100%;
border: none;
font-family: var(--vscode-font-family);
padding: var(--input-padding-vertical) var(--input-padding-horizontal);
color: var(--vscode-input-foreground);
outline-color: var(--vscode-input-border);
background-color: var(--vscode-input-background);
}
input::placeholder,
textarea::placeholder {
color: var(--vscode-input-placeholderForeground);
}
button {
border: none;
padding: var(--input-padding-vertical) var(--input-padding-horizontal);
text-align: center;
outline: 1px solid transparent;
outline-offset: 2px !important;
color: var(--vscode-button-foreground);
background: var(--vscode-button-background);
}
button:hover:not(:disabled) {
cursor: pointer;
background: var(--vscode-button-hoverBackground);
}
button:disabled {
opacity: 0.5;
background: var(--vscode-button-background);
}
button:focus {
outline-color: var(--vscode-focusBorder);
}
.header {
display: flex;
margin: 0.4em 1em;
}
.url-input {
flex: 1;
}
.controls {
display: flex;
}
.controls button {
display: flex;
margin-right: 0.3em;
}
.content {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
}
iframe {
width: 100%;
height: 100%;
border: none;
}
.iframe-focused-alert {
display: none;
position: absolute;
bottom: 1em;
background: var(--vscode-editorWidget-background);
color: var(--vscode-editorWidget-foreground);
padding: 0.2em 0.2em;
border-radius: 4px;
font-size: 8px;
font-family: monospace;
user-select: none;
pointer-events: none;
}
.iframe-focused .iframe-focused-alert {
display: block;
}

View file

@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M2 2L1 3V13L2 14H14L15 13V3L14 2H2ZM2 13V3H14V13H2ZM12 5H4V6H12V5ZM3 4V7H13V4H3ZM7 9H3V8H7V9ZM3 12H7V11H3V12ZM12 9H10V11H12V9ZM9 8V12H13V8H9Z" fill="#C5C5C5"/>
</svg>

After

Width:  |  Height:  |  Size: 312 B

View file

@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M2 2L1 3V13L2 14H14L15 13V3L14 2H2ZM2 13V3H14V13H2ZM12 5H4V6H12V5ZM3 4V7H13V4H3ZM7 9H3V8H7V9ZM3 12H7V11H3V12ZM12 9H10V11H12V9ZM9 8V12H13V8H9Z" fill="#424242"/>
</svg>

After

Width:  |  Height:  |  Size: 312 B

View file

@ -0,0 +1,68 @@
{
"name": "simple-browser",
"displayName": "%displayName%",
"description": "%description%",
"enableProposedApi": true,
"version": "1.0.0",
"icon": "icon.png",
"publisher": "vscode",
"license": "MIT",
"aiKey": "AIF-d9b70cd4-b9f9-4d70-929b-a071c400b217",
"engines": {
"vscode": "^1.50.0"
},
"main": "./out/extension",
"browser": "./dist/browser/extension",
"categories": [
"Programming Languages"
],
"activationEvents": [
"onCommand:simpleBrowser.show",
"onUriOpen:http",
"onUriOpen:https"
],
"contributes": {
"commands": [
{
"command": "simpleBrowser.show",
"title": "Show",
"category": "Simple Browser"
}
],
"configuration": [
{
"title": "Simple Browser",
"properties": {
"simpleBrowser.opener.enabled": {
"type": "boolean",
"default": false,
"title": "Opener Enabled",
"description": "(Experimental) Enables opening http and https urls using the built-in seper browser"
}
}
}
]
},
"scripts": {
"compile": "gulp compile-extension:markdown-language-features && npm run build-preview",
"watch": "npm run build-preview && gulp watch-extension:markdown-language-features",
"vscode:prepublish": "npm run build-ext && npm run build-preview",
"build-ext": "node ../../node_modules/gulp/bin/gulp.js --gulpfile ../../build/gulpfile.extensions.js compile-extension:markdown-language-features ./tsconfig.json",
"build-preview": "webpack --mode development",
"build-preview-production": "webpack --mode production",
"compile-web": "npx webpack-cli --config extension-browser.webpack.config --mode none",
"watch-web": "npx webpack-cli --config extension-browser.webpack.config --mode none --watch --info-verbosity verbose"
},
"dependencies": {
"vscode-codicons": "^0.0.12",
"vscode-extension-telemetry": "0.1.1",
"vscode-nls": "^4.0.0"
},
"devDependencies": {
"@types/node": "^12.11.7",
"ts-loader": "^6.2.1",
"typescript": "^3.7.3",
"webpack": "^4.41.2",
"webpack-cli": "^3.3.0"
}
}

View file

@ -0,0 +1,28 @@
{
"displayName": "Markdown Language Features",
"description": "Provides rich language support for Markdown.",
"markdown.preview.breaks.desc": "Sets how line-breaks are rendered in the markdown preview. Setting it to 'true' creates a <br> for newlines inside paragraphs.",
"markdown.preview.linkify": "Enable or disable conversion of URL-like text to links in the markdown preview.",
"markdown.preview.doubleClickToSwitchToEditor.desc": "Double click in the markdown preview to switch to the editor.",
"markdown.preview.fontFamily.desc": "Controls the font family used in the markdown preview.",
"markdown.preview.fontSize.desc": "Controls the font size in pixels used in the markdown preview.",
"markdown.preview.lineHeight.desc": "Controls the line height used in the markdown preview. This number is relative to the font size.",
"markdown.preview.markEditorSelection.desc": "Mark the current editor selection in the markdown preview.",
"markdown.preview.scrollEditorWithPreview.desc": "When a markdown preview is scrolled, update the view of the editor.",
"markdown.preview.scrollPreviewWithEditor.desc": "When a markdown editor is scrolled, update the view of the preview.",
"markdown.preview.title": "Open Preview",
"markdown.previewSide.title": "Open Preview to the Side",
"markdown.showLockedPreviewToSide.title": "Open Locked Preview to the Side",
"markdown.showSource.title": "Show Source",
"markdown.styles.dec": "A list of URLs or local paths to CSS style sheets to use from the markdown preview. Relative paths are interpreted relative to the folder open in the explorer. If there is no open folder, they are interpreted relative to the location of the markdown file. All '\\' need to be written as '\\\\'.",
"markdown.showPreviewSecuritySelector.title": "Change Preview Security Settings",
"markdown.trace.desc": "Enable debug logging for the markdown extension.",
"markdown.preview.refresh.title": "Refresh Preview",
"markdown.preview.toggleLock.title": "Toggle Preview Locking",
"configuration.markdown.preview.openMarkdownLinks.description": "Controls how links to other markdown files in the markdown preview should be opened.",
"configuration.markdown.preview.openMarkdownLinks.inEditor": "Try to open links in the editor",
"configuration.markdown.preview.openMarkdownLinks.inPreview": "Try to open links in the markdown preview",
"configuration.markdown.links.openLocation.description": "Controls where links in markdown files should be opened.",
"configuration.markdown.links.openLocation.currentGroup": "Open links in the active editor group.",
"configuration.markdown.links.openLocation.beside": "Open links beside the active editor."
}

View file

@ -0,0 +1,12 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
export function onceDocumentLoaded(f: () => void) {
if (document.readyState === 'loading' || document.readyState as string === 'uninitialized') {
document.addEventListener('DOMContentLoaded', f);
} else {
f();
}
}

View file

@ -0,0 +1,86 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { onceDocumentLoaded } from './events';
declare let acquireVsCodeApi: any;
const vscode = acquireVsCodeApi();
function getSettings() {
const element = document.getElementById('simple-browser-settings');
if (element) {
const data = element.getAttribute('data-settings');
if (data) {
return JSON.parse(data);
}
}
throw new Error(`Could not load settings`);
}
const settings = getSettings();
const iframe = document.querySelector('iframe')!;
const header = document.querySelector('.header')!;
const input = header.querySelector<HTMLInputElement>('.url-input')!;
const forwardButton = header.querySelector<HTMLButtonElement>('.forward-button')!;
const backButton = header.querySelector<HTMLButtonElement>('.back-button')!;
const reloadButton = header.querySelector<HTMLButtonElement>('.reload-button')!;
const openExternalButton = header.querySelector<HTMLButtonElement>('.open-external-button')!;
window.addEventListener('message', e => {
switch (e.data.type) {
case 'focus':
{
iframe.focus();
break;
}
}
});
onceDocumentLoaded(() => {
setInterval(() => {
const iframeFocused = document.activeElement?.tagName === 'IFRAME';
document.body.classList.toggle('iframe-focused', iframeFocused);
}, 50);
iframe.addEventListener('load', () => {
// Noop
});
input.addEventListener('change', e => {
const url = (e.target as HTMLInputElement).value;
navigateTo(url);
});
forwardButton.addEventListener('click', () => {
history.forward();
});
backButton.addEventListener('click', () => {
history.back();
});
openExternalButton.addEventListener('click', () => {
vscode.postMessage({
type: 'openExternal',
url: input.value
});
});
reloadButton.addEventListener('click', () => {
// This does not seem to trigger what we want
// history.go(0);
// This incorrectly adds entries to the history but does reload
iframe.src = input.value;
});
navigateTo(settings.url);
function navigateTo(url: string): void {
iframe.src = url;
}
});

View file

@ -0,0 +1,12 @@
{
"extends": "../../shared.tsconfig.json",
"compilerOptions": {
"outDir": "./dist/",
"jsx": "react",
"lib": [
"es2018",
"DOM",
"DOM.Iterable"
]
}
}

View file

@ -0,0 +1,51 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import { SimpleBrowserManager } from './simpleBrowserManager';
import * as nls from 'vscode-nls';
const localize = nls.loadMessageBundle();
const openCommand = 'simpleBrowser.open';
const showCommand = 'simpleBrowser.show';
export function activate(context: vscode.ExtensionContext) {
const manager = new SimpleBrowserManager(context.extensionUri);
context.subscriptions.push(manager);
context.subscriptions.push(vscode.commands.registerCommand(showCommand, async (url?: string) => {
if (!url) {
url = await vscode.window.showInputBox({
placeHolder: localize('simpleBrowser.show.placeholder', "https://example.com"),
prompt: localize('simpleBrowser.show.prompt', "Enter url to visit")
});
}
if (url) {
manager.show(url);
}
}));
context.subscriptions.push(vscode.commands.registerCommand(openCommand, (url: vscode.Uri) => {
manager.show(url.toString());
}));
context.subscriptions.push(vscode.window.registerExternalUriOpener(['http', 'https'], {
openExternalUri(uri: vscode.Uri): vscode.Command | undefined {
const configuration = vscode.workspace.getConfiguration('simpleBrowser');
if (!configuration.get('opener.enabled', false)) {
return undefined;
}
return {
title: localize('openTitle', "Open in simple browser"),
command: openCommand,
arguments: [uri]
};
}
}));
}

View file

@ -0,0 +1,38 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import { SimpleBrowserView } from './simpleBrowserView';
export class SimpleBrowserManager {
private _activeView?: SimpleBrowserView;
constructor(
private readonly extensionUri: vscode.Uri,
) { }
dispose() {
this._activeView?.dispose();
this._activeView = undefined;
}
public show(url: string): void {
if (this._activeView) {
this._activeView.show(url);
} else {
const view = new SimpleBrowserView(this.extensionUri, url);
view.onDispose(() => {
if (this._activeView === view) {
this._activeView = undefined;
}
});
this._activeView = view;
}
}
}

View file

@ -0,0 +1,131 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import * as nls from 'vscode-nls';
const localize = nls.loadMessageBundle();
export class SimpleBrowserView {
public static readonly viewType = 'simpleBrowser.view';
private static readonly title = localize('view.title', "Simple Browser");
private readonly _webviewPanel: vscode.WebviewPanel;
private readonly _onDidDispose = new vscode.EventEmitter<void>();
public readonly onDispose = this._onDidDispose.event;
constructor(
private readonly extensionUri: vscode.Uri,
url: string,
) {
this._webviewPanel = vscode.window.createWebviewPanel(SimpleBrowserView.viewType, SimpleBrowserView.title, {
viewColumn: vscode.ViewColumn.Active,
}, {
enableScripts: true,
retainContextWhenHidden: true,
});
this._webviewPanel.webview.onDidReceiveMessage(e => {
switch (e.type) {
case 'openExternal':
try {
const url = vscode.Uri.parse(e.url);
vscode.env.openExternal(url);
} catch {
// Noop
}
break;
}
});
this._webviewPanel.onDidDispose(() => {
this.dispose();
});
this.show(url);
}
public dispose() {
this._onDidDispose.fire();
this._webviewPanel.dispose();
}
public show(url: string) {
this._webviewPanel.webview.html = this.getHtml(url);
this._webviewPanel.reveal();
}
private getHtml(url: string) {
const nonce = new Date().getTime() + '' + new Date().getMilliseconds();
const mainJs = this.extensionResourceUrl('media', 'index.js');
const mainCss = this.extensionResourceUrl('media', 'main.css');
const codiconsUri = this.extensionResourceUrl('node_modules', 'vscode-codicons', 'dist', 'codicon.css');
const codiconsFontUri = this.extensionResourceUrl('node_modules', 'vscode-codicons', 'dist', 'codicon.ttf');
return /* html */ `<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-type" content="text/html;charset=UTF-8">
<meta http-equiv="Content-Security-Policy" content="
default-src 'none';
font-src ${codiconsFontUri};
style-src ${this._webviewPanel.webview.cspSource};
script-src 'nonce-${nonce}';
frame-src *;
">
<meta id="simple-browser-settings" data-settings="${escapeAttribute(JSON.stringify({
url: url,
}))}">
<link rel="stylesheet" type="text/css" href="${mainCss}">
<link rel="stylesheet" type="text/css" href="${codiconsUri}">
</head>
<body>
<header class="header">
<nav class="controls">
<button
title="${localize('control.back.title', "Back")}"
class="back-button icon"><i class="codicon codicon-arrow-left"></i></button>
<button
title="${localize('control.forward.title', "Forward")}"
class="forward-button icon"><i class="codicon codicon-arrow-right"></i></button>
<button
title="${localize('control.reload.title', "Reload")}"
class="reload-button icon"><i class="codicon codicon-refresh"></i></button>
</nav>
<input class="url-input" type="text" value=${url}>
<nav class="controls">
<button
title="${localize('control.openExternal.title', "Open in browser")}"
class="open-external-button icon"><i class="codicon codicon-link-external"></i></button>
</nav>
</header>
<div class="content">
<div class="iframe-focused-alert">${localize('view.iframe-focused', "Focus Lock")}</div>
<iframe sandbox="allow-scripts allow-forms allow-same-origin"></iframe>
</div>
<script src="${mainJs}" nonce="${nonce}"></script>
</body>
</html>`;
}
private extensionResourceUrl(...parts: string[]): vscode.Uri {
return this._webviewPanel.webview.asWebviewUri(vscode.Uri.joinPath(this.extensionUri, ...parts));
}
}
function escapeAttribute(value: string | vscode.Uri): string {
return value.toString().replace(/"/g, '&quot;');
}

View file

@ -0,0 +1,7 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/// <reference path='../../../../src/vs/vscode.d.ts'/>
/// <reference path='../../../../src/vs/vscode.proposed.d.ts'/>

View file

@ -0,0 +1,10 @@
{
"extends": "../shared.tsconfig.json",
"compilerOptions": {
"outDir": "./out",
"experimentalDecorators": true
},
"include": [
"src/**/*"
]
}

View file

@ -0,0 +1,28 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
const path = require('path');
module.exports = {
entry: {
index: './preview-src/index.ts',
},
devtool: 'source-map',
module: {
rules: [
{
test: /\.tsx?$/,
use: 'ts-loader',
exclude: /node_modules/
}
]
},
resolve: {
extensions: ['.tsx', '.ts', '.js']
},
output: {
filename: '[name].js',
path: path.resolve(__dirname, 'media')
}
};

File diff suppressed because it is too large Load diff

View file

@ -301,6 +301,7 @@ class TypeScriptRefactorProvider implements vscode.CodeActionProvider<TsCodeActi
const args: Proto.GetApplicableRefactorsRequestArgs = {
...typeConverters.Range.toFileRangeRequestArgs(file, rangeOrSelection),
triggerReason: this.toTsTriggerReason(context),
kind: context.only?.value
};
return this.client.execute('getApplicableRefactors', args, token);
});
@ -384,6 +385,9 @@ class TypeScriptRefactorProvider implements vscode.CodeActionProvider<TsCodeActi
}
private static getKind(refactor: Proto.RefactorActionInfo) {
if (refactor.kind) {
return vscode.CodeActionKind.Empty.append(refactor.kind);
}
const match = allKnownCodeActionKinds.find(kind => kind.matches(refactor));
return match ? match.kind : vscode.CodeActionKind.Refactor;
}