Merge from vscode cfbd1999769f4f08dce29629fb92fdc0fac53829

This commit is contained in:
ADS Merger
2020-08-06 07:08:52 +00:00
parent 9c67832880
commit 540046ba00
362 changed files with 7588 additions and 6584 deletions

View File

@@ -749,6 +749,16 @@ export function getShadowRoot(domNode: Node): ShadowRoot | null {
return isShadowRoot(domNode) ? domNode : null;
}
export function getActiveElement(): Element | null {
let result = document.activeElement;
while (result?.shadowRoot) {
result = result.shadowRoot.activeElement;
}
return result;
}
export function createStyleSheet(container: HTMLElement = document.getElementsByTagName('head')[0]): HTMLStyleElement {
let style = document.createElement('style');
style.type = 'text/css';

View File

@@ -137,17 +137,9 @@ export class BaseActionViewItem extends Disposable implements IActionViewItem {
this._register(DOM.addDisposableListener(element, DOM.EventType.CLICK, e => {
DOM.EventHelper.stop(e, true);
// See https://developer.mozilla.org/en-US/Add-ons/WebExtensions/Interact_with_the_clipboard
// > Writing to the clipboard
// > You can use the "cut" and "copy" commands without any special
// permission if you are using them in a short-lived event handler
// for a user action (for example, a click handler).
// => to get the Copy and Paste context menu actions working on Firefox,
// there should be no timeout here
if (this.options && this.options.isMenu) {
this.onClick(e);
} else {
// menus do not use the click event
if (!(this.options && this.options.isMenu)) {
platform.setImmediate(() => this.onClick(e));
}
}));

View File

@@ -173,7 +173,7 @@ export class ActionBar extends Disposable implements IActionRunner {
this.focusTracker = this._register(DOM.trackFocus(this.domNode));
this._register(this.focusTracker.onDidBlur(() => {
if (document.activeElement === this.domNode || !DOM.isAncestor(document.activeElement, this.domNode)) {
if (DOM.getActiveElement() === this.domNode || !DOM.isAncestor(DOM.getActiveElement(), this.domNode)) {
this._onDidBlur.fire();
this.focusedItem = undefined;
}
@@ -214,7 +214,7 @@ export class ActionBar extends Disposable implements IActionRunner {
private updateFocusedItem(): void {
for (let i = 0; i < this.actionsList.children.length; i++) {
const elem = this.actionsList.children[i];
if (DOM.isAncestor(document.activeElement, elem)) {
if (DOM.isAncestor(DOM.getActiveElement(), elem)) {
this.focusedItem = i;
break;
}

View File

@@ -156,7 +156,7 @@ export class ContextView extends Disposable {
if (this.useShadowDOM) {
this.shadowRootHostElement = DOM.$('.shadow-root-host');
this.container.appendChild(this.shadowRootHostElement);
this.shadowRoot = this.shadowRootHostElement.attachShadow({ mode: 'closed' });
this.shadowRoot = this.shadowRootHostElement.attachShadow({ mode: 'open' });
this.shadowRoot.innerHTML = `
<style>
${SHADOW_ROOT_CSS}
@@ -215,6 +215,10 @@ export class ContextView extends Disposable {
}
}
getViewElement(): HTMLElement {
return this.view;
}
layout(): void {
if (!this.isVisible()) {
return;
@@ -372,9 +376,9 @@ let SHADOW_ROOT_CSS = /* css */ `
:host-context(.windows:lang(ja)) { font-family: "Segoe WPC", "Segoe UI", "Yu Gothic UI", "Meiryo UI", sans-serif; }
:host-context(.windows:lang(ko)) { font-family: "Segoe WPC", "Segoe UI", "Malgun Gothic", "Dotom", sans-serif; }
:host-context(.mac).linux) { font-family: system-ui, "Ubuntu", "Droid Sans", sans-serif; }
:host-context(.mac).linux:lang(zh-Hans)) { font-family: system-ui, "Ubuntu", "Droid Sans", "Source Han Sans SC", "Source Han Sans CN", "Source Han Sans", sans-serif; }
:host-context(.mac).linux:lang(zh-Hant)) { font-family: system-ui, "Ubuntu", "Droid Sans", "Source Han Sans TC", "Source Han Sans TW", "Source Han Sans", sans-serif; }
:host-context(.mac).linux:lang(ja)) { font-family: system-ui, "Ubuntu", "Droid Sans", "Source Han Sans J", "Source Han Sans JP", "Source Han Sans", sans-serif; }
:host-context(.mac).linux:lang(ko)) { font-family: system-ui, "Ubuntu", "Droid Sans", "Source Han Sans K", "Source Han Sans JR", "Source Han Sans", "UnDotum", "FBaekmuk Gulim", sans-serif; }
:host-context(.linux) { font-family: system-ui, "Ubuntu", "Droid Sans", sans-serif; }
:host-context(.linux:lang(zh-Hans)) { font-family: system-ui, "Ubuntu", "Droid Sans", "Source Han Sans SC", "Source Han Sans CN", "Source Han Sans", sans-serif; }
:host-context(.linux:lang(zh-Hant)) { font-family: system-ui, "Ubuntu", "Droid Sans", "Source Han Sans TC", "Source Han Sans TW", "Source Han Sans", sans-serif; }
:host-context(.linux:lang(ja)) { font-family: system-ui, "Ubuntu", "Droid Sans", "Source Han Sans J", "Source Han Sans JP", "Source Han Sans", sans-serif; }
:host-context(.linux:lang(ko)) { font-family: system-ui, "Ubuntu", "Droid Sans", "Source Han Sans K", "Source Han Sans JR", "Source Han Sans", "UnDotum", "FBaekmuk Gulim", sans-serif; }
`;

View File

@@ -8,7 +8,7 @@ import * as strings from 'vs/base/common/strings';
import { IActionRunner, IAction, SubmenuAction, Separator, IActionViewItemProvider } from 'vs/base/common/actions';
import { ActionBar, ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar';
import { ResolvedKeybinding, KeyCode } from 'vs/base/common/keyCodes';
import { addClass, EventType, EventHelper, EventLike, removeTabIndexAndUpdateFocus, isAncestor, hasClass, addDisposableListener, removeClass, append, $, addClasses, removeClasses, clearNode, createStyleSheet, isInShadowDOM } from 'vs/base/browser/dom';
import { addClass, EventType, EventHelper, EventLike, removeTabIndexAndUpdateFocus, isAncestor, hasClass, addDisposableListener, removeClass, append, $, addClasses, removeClasses, clearNode, createStyleSheet, isInShadowDOM, getActiveElement, Dimension, IDomNodePagePosition } from 'vs/base/browser/dom';
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { RunOnceScheduler } from 'vs/base/common/async';
import { DisposableStore } from 'vs/base/common/lifecycle';
@@ -16,11 +16,13 @@ import { Color } from 'vs/base/common/color';
import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement';
import { ScrollbarVisibility, ScrollEvent } from 'vs/base/common/scrollable';
import { Event } from 'vs/base/common/event';
import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview';
import { AnchorAlignment, layout, LayoutAnchorPosition } from 'vs/base/browser/ui/contextview/contextview';
import { isLinux, isMacintosh } from 'vs/base/common/platform';
import { Codicon, registerIcon, stripCodicons } from 'vs/base/common/codicons';
import { BaseActionViewItem, ActionViewItem, IActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionViewItems';
import { formatRule } from 'vs/base/browser/ui/codicons/codiconStyles';
import { isFirefox } from 'vs/base/browser/browser';
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
export const MENU_MNEMONIC_REGEX = /\(&([^\s&])\)|(^|[^&])&([^\s&])/;
export const MENU_ESCAPED_MNEMONIC_REGEX = /(&amp;)?(&amp;)([^\s&])/g;
@@ -203,7 +205,7 @@ export class Menu extends ActionBar {
e.preventDefault();
}));
menuElement.style.maxHeight = `${Math.max(10, window.innerHeight - container.getBoundingClientRect().top - 30)}px`;
menuElement.style.maxHeight = `${Math.max(10, window.innerHeight - container.getBoundingClientRect().top - 35)}px`;
actions = actions.filter(a => {
if (options.submenuIds?.has(a.id)) {
@@ -423,7 +425,37 @@ class BaseMenuActionViewItem extends BaseActionViewItem {
// with BaseActionViewItem #101537
// add back if issues arise and link new issue
EventHelper.stop(e, true);
this.onClick(e);
// See https://developer.mozilla.org/en-US/Add-ons/WebExtensions/Interact_with_the_clipboard
// > Writing to the clipboard
// > You can use the "cut" and "copy" commands without any special
// permission if you are using them in a short-lived event handler
// for a user action (for example, a click handler).
// => to get the Copy and Paste context menu actions working on Firefox,
// there should be no timeout here
if (isFirefox) {
const mouseEvent = new StandardMouseEvent(e);
// Allowing right click to trigger the event causes the issue described below,
// but since the solution below does not work in FF, we must disable right click
if (mouseEvent.rightButton) {
return;
}
this.onClick(e);
}
// In all other cases, set timout to allow context menu cancellation to trigger
// otherwise the action will destroy the menu and a second context menu
// will still trigger for right click.
setTimeout(() => {
this.onClick(e);
}, 0);
}));
this._register(addDisposableListener(this.element, EventType.CONTEXT_MENU, e => {
EventHelper.stop(e, true);
}));
}, 100);
@@ -679,7 +711,7 @@ class SubmenuMenuActionViewItem extends BaseMenuActionViewItem {
}, 250);
this.hideScheduler = new RunOnceScheduler(() => {
if (this.element && (!isAncestor(document.activeElement, this.element) && this.parentData.submenu === this.mysubmenu)) {
if (this.element && (!isAncestor(getActiveElement(), this.element) && this.parentData.submenu === this.mysubmenu)) {
this.parentData.parent.focus(false);
this.cleanupExistingSubmenu(true);
}
@@ -713,7 +745,7 @@ class SubmenuMenuActionViewItem extends BaseMenuActionViewItem {
this._register(addDisposableListener(this.element, EventType.KEY_DOWN, e => {
let event = new StandardKeyboardEvent(e);
if (document.activeElement === this.item) {
if (getActiveElement() === this.item) {
if (event.equals(KeyCode.RightArrow) || event.equals(KeyCode.Enter)) {
EventHelper.stop(e, true);
}
@@ -733,7 +765,7 @@ class SubmenuMenuActionViewItem extends BaseMenuActionViewItem {
}));
this._register(addDisposableListener(this.element, EventType.FOCUS_OUT, e => {
if (this.element && !isAncestor(document.activeElement, this.element)) {
if (this.element && !isAncestor(getActiveElement(), this.element)) {
this.hideScheduler.schedule();
}
}));
@@ -769,6 +801,33 @@ class SubmenuMenuActionViewItem extends BaseMenuActionViewItem {
}
}
private calculateSubmenuMenuLayout(windowDimensions: Dimension, submenu: Dimension, entry: IDomNodePagePosition, expandDirection: Direction): { top: number, left: number } {
const ret = { top: 0, left: 0 };
// Start with horizontal
ret.left = layout(windowDimensions.width, submenu.width, { position: expandDirection === Direction.Right ? LayoutAnchorPosition.Before : LayoutAnchorPosition.After, offset: entry.left, size: entry.width });
// We don't have enough room to layout the menu fully, so we are overlapping the menu
if (ret.left >= entry.left && ret.left < entry.left + entry.width) {
if (entry.left + 10 + submenu.width <= windowDimensions.width) {
ret.left = entry.left + 10;
}
entry.top += 10;
entry.height = 0;
}
// Now that we have a horizontal position, try layout vertically
ret.top = layout(windowDimensions.height, submenu.height, { position: LayoutAnchorPosition.Before, offset: entry.top, size: 0 });
// We didn't have enough room below, but we did above, so we shift down to align the menu
if (ret.top + submenu.height === entry.top && ret.top + entry.height + submenu.height <= windowDimensions.height) {
ret.top += entry.height;
}
return ret;
}
private createSubmenu(selectFirstItem = true): void {
if (!this.element) {
return;
@@ -776,36 +835,40 @@ class SubmenuMenuActionViewItem extends BaseMenuActionViewItem {
if (!this.parentData.submenu) {
this.updateAriaExpanded('true');
this.submenuContainer = append(this.element, $('div.monaco-submenu'));
this.submenuContainer = document.createElement('div.monaco-submenu');
addClasses(this.submenuContainer, 'menubar-menu-items-holder', 'context-view');
// Set the top value of the menu container before construction
// This allows the menu constructor to calculate the proper max height
const computedStyles = getComputedStyle(this.parentData.parent.domNode);
const paddingTop = parseFloat(computedStyles.paddingTop || '0') || 0;
this.submenuContainer.style.top = `${this.element.offsetTop - this.parentData.parent.scrollOffset - paddingTop}px`;
// this.submenuContainer.style.top = `${this.element.offsetTop - this.parentData.parent.scrollOffset - paddingTop}px`;
this.submenuContainer.style.zIndex = '1';
this.submenuContainer.style.position = 'fixed';
this.submenuContainer.style.top = '0';
this.submenuContainer.style.left = '0';
this.parentData.submenu = new Menu(this.submenuContainer, this.submenuActions, this.submenuOptions);
if (this.menuStyle) {
this.parentData.submenu.style(this.menuStyle);
}
const boundingRect = this.element.getBoundingClientRect();
const childBoundingRect = this.submenuContainer.getBoundingClientRect();
this.element.appendChild(this.submenuContainer);
if (this.expandDirection === Direction.Right) {
if (window.innerWidth <= boundingRect.right + childBoundingRect.width) {
this.submenuContainer.style.left = '10px';
this.submenuContainer.style.top = `${this.element.offsetTop - this.parentData.parent.scrollOffset + boundingRect.height}px`;
} else {
this.submenuContainer.style.left = `${this.element.offsetWidth}px`;
this.submenuContainer.style.top = `${this.element.offsetTop - this.parentData.parent.scrollOffset - paddingTop}px`;
}
} else if (this.expandDirection === Direction.Left) {
this.submenuContainer.style.right = `${this.element.offsetWidth}px`;
this.submenuContainer.style.left = 'auto';
this.submenuContainer.style.top = `${this.element.offsetTop - this.parentData.parent.scrollOffset - paddingTop}px`;
}
// layout submenu
const entryBox = this.element.getBoundingClientRect();
const entryBoxUpdated = {
top: entryBox.top - paddingTop,
left: entryBox.left,
height: entryBox.height + 2 * paddingTop,
width: entryBox.width
};
const viewBox = this.submenuContainer.getBoundingClientRect();
const { top, left } = this.calculateSubmenuMenuLayout({ height: window.innerHeight, width: window.innerWidth }, viewBox, entryBoxUpdated, this.expandDirection);
this.submenuContainer.style.left = `${left}px`;
this.submenuContainer.style.top = `${top}px`;
this.submenuDisposables.add(addDisposableListener(this.submenuContainer, EventType.KEY_UP, e => {
let event = new StandardKeyboardEvent(e);
@@ -911,13 +974,13 @@ let MENU_WIDGET_CSS: string = /* css */`
${formatRule(menuSelectionIcon)}
${formatRule(menuSubmenuIcon)}
.monaco-action-bar {
.monaco-menu .monaco-action-bar {
text-align: right;
overflow: hidden;
white-space: nowrap;
}
.monaco-action-bar .actions-container {
.monaco-menu .monaco-action-bar .actions-container {
display: flex;
margin: 0 auto;
padding: 0;
@@ -925,60 +988,60 @@ ${formatRule(menuSubmenuIcon)}
justify-content: flex-end;
}
.monaco-action-bar.vertical .actions-container {
.monaco-menu .monaco-action-bar.vertical .actions-container {
display: inline-block;
}
.monaco-action-bar.reverse .actions-container {
.monaco-menu .monaco-action-bar.reverse .actions-container {
flex-direction: row-reverse;
}
.monaco-action-bar .action-item {
.monaco-menu .monaco-action-bar .action-item {
cursor: pointer;
display: inline-block;
transition: transform 50ms ease;
position: relative; /* DO NOT REMOVE - this is the key to preventing the ghosting icon bug in Chrome 42 */
}
.monaco-action-bar .action-item.disabled {
.monaco-menu .monaco-action-bar .action-item.disabled {
cursor: default;
}
.monaco-action-bar.animated .action-item.active {
.monaco-menu .monaco-action-bar.animated .action-item.active {
transform: scale(1.272019649, 1.272019649); /* 1.272019649 = √φ */
}
.monaco-action-bar .action-item .icon,
.monaco-action-bar .action-item .codicon {
.monaco-menu .monaco-action-bar .action-item .icon,
.monaco-menu .monaco-action-bar .action-item .codicon {
display: inline-block;
}
.monaco-action-bar .action-item .codicon {
.monaco-menu .monaco-action-bar .action-item .codicon {
display: flex;
align-items: center;
}
.monaco-action-bar .action-label {
.monaco-menu .monaco-action-bar .action-label {
font-size: 11px;
margin-right: 4px;
}
.monaco-action-bar .action-item.disabled .action-label,
.monaco-action-bar .action-item.disabled .action-label:hover {
.monaco-menu .monaco-action-bar .action-item.disabled .action-label,
.monaco-menu .monaco-action-bar .action-item.disabled .action-label:hover {
opacity: 0.4;
}
/* Vertical actions */
.monaco-action-bar.vertical {
.monaco-menu .monaco-action-bar.vertical {
text-align: left;
}
.monaco-action-bar.vertical .action-item {
.monaco-menu .monaco-action-bar.vertical .action-item {
display: block;
}
.monaco-action-bar.vertical .action-label.separator {
.monaco-menu .monaco-action-bar.vertical .action-label.separator {
display: block;
border-bottom: 1px solid #bbb;
padding-top: 1px;
@@ -986,16 +1049,12 @@ ${formatRule(menuSubmenuIcon)}
margin-right: .8em;
}
.monaco-action-bar.animated.vertical .action-item.active {
transform: translate(5px, 0);
}
.secondary-actions .monaco-action-bar .action-label {
.monaco-menu .secondary-actions .monaco-action-bar .action-label {
margin-left: 6px;
}
/* Action Items */
.monaco-action-bar .action-item.select-container {
.monaco-menu .monaco-action-bar .action-item.select-container {
overflow: hidden; /* somehow the dropdown overflows its container, we prevent it here to not push */
flex: 1;
max-width: 170px;
@@ -1140,11 +1199,11 @@ ${formatRule(menuSubmenuIcon)}
/* High Contrast Theming */
.hc-black .context-view.monaco-menu-container {
:host-context(.hc-black) .context-view.monaco-menu-container {
box-shadow: none;
}
.hc-black .monaco-menu .monaco-action-bar.vertical .action-item.focused {
:host-context(.hc-black) .monaco-menu .monaco-action-bar.vertical .action-item.focused {
background: none;
}
@@ -1175,7 +1234,7 @@ ${formatRule(menuSubmenuIcon)}
margin-bottom: 0.2em;
}
linux .monaco-menu .monaco-action-bar.vertical .action-label.separator {
:host-context(.linux) .monaco-menu .monaco-action-bar.vertical .action-label.separator {
margin-left: 0;
margin-right: 0;
}
@@ -1195,4 +1254,110 @@ linux .monaco-menu .monaco-action-bar.vertical .action-label.separator {
cursor: default;
}
/* Arrows */
.monaco-scrollable-element > .scrollbar > .scra {
cursor: pointer;
font-size: 11px !important;
}
.monaco-scrollable-element > .visible {
opacity: 1;
/* Background rule added for IE9 - to allow clicks on dom node */
background:rgba(0,0,0,0);
transition: opacity 100ms linear;
}
.monaco-scrollable-element > .invisible {
opacity: 0;
pointer-events: none;
}
.monaco-scrollable-element > .invisible.fade {
transition: opacity 800ms linear;
}
/* Scrollable Content Inset Shadow */
.monaco-scrollable-element > .shadow {
position: absolute;
display: none;
}
.monaco-scrollable-element > .shadow.top {
display: block;
top: 0;
left: 3px;
height: 3px;
width: 100%;
box-shadow: #DDD 0 6px 6px -6px inset;
}
.monaco-scrollable-element > .shadow.left {
display: block;
top: 3px;
left: 0;
height: 100%;
width: 3px;
box-shadow: #DDD 6px 0 6px -6px inset;
}
.monaco-scrollable-element > .shadow.top-left-corner {
display: block;
top: 0;
left: 0;
height: 3px;
width: 3px;
}
.monaco-scrollable-element > .shadow.top.left {
box-shadow: #DDD 6px 6px 6px -6px inset;
}
/* ---------- Default Style ---------- */
:host-context(.vs) .monaco-scrollable-element > .scrollbar > .slider {
background: rgba(100, 100, 100, .4);
}
:host-context(.vs-dark) .monaco-scrollable-element > .scrollbar > .slider {
background: rgba(121, 121, 121, .4);
}
:host-context(.hc-black) .monaco-scrollable-element > .scrollbar > .slider {
background: rgba(111, 195, 223, .6);
}
.monaco-scrollable-element > .scrollbar > .slider:hover {
background: rgba(100, 100, 100, .7);
}
:host-context(.hc-black) .monaco-scrollable-element > .scrollbar > .slider:hover {
background: rgba(111, 195, 223, .8);
}
.monaco-scrollable-element > .scrollbar > .slider.active {
background: rgba(0, 0, 0, .6);
}
:host-context(.vs-dark) .monaco-scrollable-element > .scrollbar > .slider.active {
background: rgba(191, 191, 191, .4);
}
:host-context(.hc-black) .monaco-scrollable-element > .scrollbar > .slider.active {
background: rgba(111, 195, 223, 1);
}
:host-context(.vs-dark) .monaco-scrollable-element .shadow.top {
box-shadow: none;
}
:host-context(.vs-dark) .monaco-scrollable-element .shadow.left {
box-shadow: #000 6px 0 6px -6px inset;
}
:host-context(.vs-dark) .monaco-scrollable-element .shadow.top.left {
box-shadow: #000 6px 6px 6px -6px inset;
}
:host-context(.hc-black) .monaco-scrollable-element .shadow.top {
box-shadow: none;
}
:host-context(.hc-black) .monaco-scrollable-element .shadow.left {
box-shadow: none;
}
:host-context(.hc-black) .monaco-scrollable-element .shadow.top.left {
box-shadow: none;
}
`;

View File

@@ -217,7 +217,8 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi
// Intercept keyboard handling
this._register(dom.addDisposableListener(this.selectElement, dom.EventType.KEY_DOWN, (e: KeyboardEvent) => {
// React on KEY_UP since the actionBar also reacts on KEY_UP so that appropriate events get canceled
this._register(dom.addDisposableListener(this.selectElement, dom.EventType.KEY_UP, (e: KeyboardEvent) => {
const event = new StandardKeyboardEvent(e);
let showDropDown = false;
@@ -234,7 +235,7 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi
if (showDropDown) {
this.showSelectDropDown();
dom.EventHelper.stop(e);
dom.EventHelper.stop(e, true);
}
}));
}

View File

@@ -5,14 +5,13 @@
import { Menu, MenuItem, BrowserWindow, ipcMain, IpcMainEvent } from 'electron';
import { ISerializableContextMenuItem, CONTEXT_MENU_CLOSE_CHANNEL, CONTEXT_MENU_CHANNEL, IPopupOptions } from 'vs/base/parts/contextmenu/common/contextmenu';
import { withNullAsUndefined } from 'vs/base/common/types';
export function registerContextMenuListener(): void {
ipcMain.on(CONTEXT_MENU_CHANNEL, (event: IpcMainEvent, contextMenuId: number, items: ISerializableContextMenuItem[], onClickChannel: string, options?: IPopupOptions) => {
const menu = createMenu(event, onClickChannel, items);
menu.popup({
window: withNullAsUndefined(BrowserWindow.fromWebContents(event.sender)),
window: BrowserWindow.fromWebContents(event.sender),
x: options ? options.x : undefined,
y: options ? options.y : undefined,
positioningItem: options ? options.positioningItem : undefined,

View File

@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { Event, Emitter } from 'vs/base/common/event';
import { IMessagePassingProtocol, IPCClient } from 'vs/base/parts/ipc/common/ipc';
import { IMessagePassingProtocol, IPCClient, IIPCLogger } from 'vs/base/parts/ipc/common/ipc';
import { IDisposable, Disposable, dispose } from 'vs/base/common/lifecycle';
import { VSBuffer } from 'vs/base/common/buffer';
import * as platform from 'vs/base/common/platform';
@@ -403,8 +403,8 @@ export class Client<TContext = string> extends IPCClient<TContext> {
get onClose(): Event<void> { return this.protocol.onClose; }
constructor(private protocol: Protocol | PersistentProtocol, id: TContext) {
super(protocol, id);
constructor(private protocol: Protocol | PersistentProtocol, id: TContext, ipcLogger: IIPCLogger | null = null) {
super(protocol, id, ipcLogger);
}
dispose(): void {

View File

@@ -12,7 +12,7 @@ import { VSBuffer } from 'vs/base/common/buffer';
import { getRandomElement } from 'vs/base/common/arrays';
import { isFunction, isUndefinedOrNull } from 'vs/base/common/types';
import { revive } from 'vs/base/common/marshalling';
import { isUpperAsciiLetter } from 'vs/base/common/strings';
import * as strings from 'vs/base/common/strings';
/**
* An `IChannel` is an abstraction over a collection of commands.
@@ -42,6 +42,19 @@ export const enum RequestType {
EventDispose = 103
}
function requestTypeToStr(type: RequestType): string {
switch (type) {
case RequestType.Promise:
return 'req';
case RequestType.PromiseCancel:
return 'cancel';
case RequestType.EventListen:
return 'subscribe';
case RequestType.EventDispose:
return 'unsubscribe';
}
}
type IRawPromiseRequest = { type: RequestType.Promise; id: number; channelName: string; name: string; arg: any; };
type IRawPromiseCancelRequest = { type: RequestType.PromiseCancel, id: number };
type IRawEventListenRequest = { type: RequestType.EventListen; id: number; channelName: string; name: string; arg: any; };
@@ -56,6 +69,20 @@ export const enum ResponseType {
EventFire = 204
}
function responseTypeToStr(type: ResponseType): string {
switch (type) {
case ResponseType.Initialize:
return `init`;
case ResponseType.PromiseSuccess:
return `reply:`;
case ResponseType.PromiseError:
case ResponseType.PromiseErrorObj:
return `replyErr:`;
case ResponseType.EventFire:
return `event:`;
}
}
type IRawInitializeResponse = { type: ResponseType.Initialize };
type IRawPromiseSuccessResponse = { type: ResponseType.PromiseSuccess; id: number; data: any };
type IRawPromiseErrorResponse = { type: ResponseType.PromiseError; id: number; data: { message: string, name: string, stack: string[] | undefined } };
@@ -268,7 +295,7 @@ export class ChannelServer<TContext = string> implements IChannelServer<TContext
// They will timeout after `timeoutDelay`.
private pendingRequests = new Map<string, PendingRequest[]>();
constructor(private protocol: IMessagePassingProtocol, private ctx: TContext, private timeoutDelay: number = 1000) {
constructor(private protocol: IMessagePassingProtocol, private ctx: TContext, private logger: IIPCLogger | null = null, private timeoutDelay: number = 1000) {
this.protocolListener = this.protocol.onMessage(msg => this.onRawMessage(msg));
this.sendResponse({ type: ResponseType.Initialize });
}
@@ -282,29 +309,41 @@ export class ChannelServer<TContext = string> implements IChannelServer<TContext
private sendResponse(response: IRawResponse): void {
switch (response.type) {
case ResponseType.Initialize:
return this.send([response.type]);
case ResponseType.Initialize: {
const msgLength = this.send([response.type]);
if (this.logger) {
this.logger.logOutgoing(msgLength, 0, RequestInitiator.OtherSide, responseTypeToStr(response.type));
}
return;
}
case ResponseType.PromiseSuccess:
case ResponseType.PromiseError:
case ResponseType.EventFire:
case ResponseType.PromiseErrorObj:
return this.send([response.type, response.id], response.data);
case ResponseType.PromiseErrorObj: {
const msgLength = this.send([response.type, response.id], response.data);
if (this.logger) {
this.logger.logOutgoing(msgLength, response.id, RequestInitiator.OtherSide, responseTypeToStr(response.type), response.data);
}
return;
}
}
}
private send(header: any, body: any = undefined): void {
private send(header: any, body: any = undefined): number {
const writer = new BufferWriter();
serialize(writer, header);
serialize(writer, body);
this.sendBuffer(writer.buffer);
return this.sendBuffer(writer.buffer);
}
private sendBuffer(message: VSBuffer): void {
private sendBuffer(message: VSBuffer): number {
try {
this.protocol.send(message);
return message.byteLength;
} catch (err) {
// noop
return 0;
}
}
@@ -316,12 +355,24 @@ export class ChannelServer<TContext = string> implements IChannelServer<TContext
switch (type) {
case RequestType.Promise:
if (this.logger) {
this.logger.logIncoming(message.byteLength, header[1], RequestInitiator.OtherSide, `${requestTypeToStr(type)}: ${header[2]}.${header[3]}`, body);
}
return this.onPromise({ type, id: header[1], channelName: header[2], name: header[3], arg: body });
case RequestType.EventListen:
if (this.logger) {
this.logger.logIncoming(message.byteLength, header[1], RequestInitiator.OtherSide, `${requestTypeToStr(type)}: ${header[2]}.${header[3]}`, body);
}
return this.onEventListen({ type, id: header[1], channelName: header[2], name: header[3], arg: body });
case RequestType.PromiseCancel:
if (this.logger) {
this.logger.logIncoming(message.byteLength, header[1], RequestInitiator.OtherSide, `${requestTypeToStr(type)}`);
}
return this.disposeActiveRequest({ type, id: header[1] });
case RequestType.EventDispose:
if (this.logger) {
this.logger.logIncoming(message.byteLength, header[1], RequestInitiator.OtherSide, `${requestTypeToStr(type)}`);
}
return this.disposeActiveRequest({ type, id: header[1] });
}
}
@@ -442,6 +493,16 @@ export class ChannelServer<TContext = string> implements IChannelServer<TContext
}
}
export const enum RequestInitiator {
LocalSide = 0,
OtherSide = 1
}
export interface IIPCLogger {
logIncoming(msgLength: number, requestId: number, initiator: RequestInitiator, str: string, data?: any): void;
logOutgoing(msgLength: number, requestId: number, initiator: RequestInitiator, str: string, data?: any): void;
}
export class ChannelClient implements IChannelClient, IDisposable {
private state: State = State.Uninitialized;
@@ -449,12 +510,14 @@ export class ChannelClient implements IChannelClient, IDisposable {
private handlers = new Map<number, IHandler>();
private lastRequestId: number = 0;
private protocolListener: IDisposable | null;
private logger: IIPCLogger | null;
private readonly _onDidInitialize = new Emitter<void>();
readonly onDidInitialize = this._onDidInitialize.event;
constructor(private protocol: IMessagePassingProtocol) {
constructor(private protocol: IMessagePassingProtocol, logger: IIPCLogger | null = null) {
this.protocolListener = this.protocol.onMessage(msg => this.onBuffer(msg));
this.logger = logger;
}
getChannel<T extends IChannel>(channelName: string): T {
@@ -579,27 +642,39 @@ export class ChannelClient implements IChannelClient, IDisposable {
private sendRequest(request: IRawRequest): void {
switch (request.type) {
case RequestType.Promise:
case RequestType.EventListen:
return this.send([request.type, request.id, request.channelName, request.name], request.arg);
case RequestType.EventListen: {
const msgLength = this.send([request.type, request.id, request.channelName, request.name], request.arg);
if (this.logger) {
this.logger.logOutgoing(msgLength, request.id, RequestInitiator.LocalSide, `${requestTypeToStr(request.type)}: ${request.channelName}.${request.name}`, request.arg);
}
return;
}
case RequestType.PromiseCancel:
case RequestType.EventDispose:
return this.send([request.type, request.id]);
case RequestType.EventDispose: {
const msgLength = this.send([request.type, request.id]);
if (this.logger) {
this.logger.logOutgoing(msgLength, request.id, RequestInitiator.LocalSide, requestTypeToStr(request.type));
}
return;
}
}
}
private send(header: any, body: any = undefined): void {
private send(header: any, body: any = undefined): number {
const writer = new BufferWriter();
serialize(writer, header);
serialize(writer, body);
this.sendBuffer(writer.buffer);
return this.sendBuffer(writer.buffer);
}
private sendBuffer(message: VSBuffer): void {
private sendBuffer(message: VSBuffer): number {
try {
this.protocol.send(message);
return message.byteLength;
} catch (err) {
// noop
return 0;
}
}
@@ -611,12 +686,18 @@ export class ChannelClient implements IChannelClient, IDisposable {
switch (type) {
case ResponseType.Initialize:
if (this.logger) {
this.logger.logIncoming(message.byteLength, 0, RequestInitiator.LocalSide, responseTypeToStr(type));
}
return this.onResponse({ type: header[0] });
case ResponseType.PromiseSuccess:
case ResponseType.PromiseError:
case ResponseType.EventFire:
case ResponseType.PromiseErrorObj:
if (this.logger) {
this.logger.logIncoming(message.byteLength, header[1], RequestInitiator.LocalSide, responseTypeToStr(type), body);
}
return this.onResponse({ type: header[0], id: header[1], data: body });
}
}
@@ -844,13 +925,13 @@ export class IPCClient<TContext = string> implements IChannelClient, IChannelSer
private channelClient: ChannelClient;
private channelServer: ChannelServer<TContext>;
constructor(protocol: IMessagePassingProtocol, ctx: TContext) {
constructor(protocol: IMessagePassingProtocol, ctx: TContext, ipcLogger: IIPCLogger | null = null) {
const writer = new BufferWriter();
serialize(writer, ctx);
protocol.send(writer.buffer);
this.channelClient = new ChannelClient(protocol);
this.channelServer = new ChannelServer(protocol, ctx);
this.channelClient = new ChannelClient(protocol, ipcLogger);
this.channelServer = new ChannelServer(protocol, ctx, ipcLogger);
}
getChannel<T extends IChannel>(channelName: string): T {
@@ -1068,7 +1149,68 @@ export function createChannelSender<T>(channel: IChannel, options?: IChannelSend
function propertyIsEvent(name: string): boolean {
// Assume a property is an event if it has a form of "onSomething"
return name[0] === 'o' && name[1] === 'n' && isUpperAsciiLetter(name.charCodeAt(2));
return name[0] === 'o' && name[1] === 'n' && strings.isUpperAsciiLetter(name.charCodeAt(2));
}
//#endregion
const colorTables = [
['#2977B1', '#FC802D', '#34A13A', '#D3282F', '#9366BA'],
['#8B564C', '#E177C0', '#7F7F7F', '#BBBE3D', '#2EBECD']
];
function prettyWithoutArrays(data: any): any {
if (Array.isArray(data)) {
return data;
}
if (data && typeof data === 'object' && typeof data.toString === 'function') {
let result = data.toString();
if (result !== '[object Object]') {
return result;
}
}
return data;
}
function pretty(data: any): any {
if (Array.isArray(data)) {
return data.map(prettyWithoutArrays);
}
return prettyWithoutArrays(data);
}
export function logWithColors(direction: string, totalLength: number, msgLength: number, req: number, initiator: RequestInitiator, str: string, data: any): void {
data = pretty(data);
const colorTable = colorTables[initiator];
const color = colorTable[req % colorTable.length];
let args = [`%c[${direction}]%c[${strings.pad(totalLength, 7, ' ')}]%c[len: ${strings.pad(msgLength, 5, ' ')}]%c${strings.pad(req, 5, ' ')} - ${str}`, 'color: darkgreen', 'color: grey', 'color: grey', `color: ${color}`];
if (/\($/.test(str)) {
args = args.concat(data);
args.push(')');
} else {
args.push(data);
}
console.log.apply(console, args as [string, ...string[]]);
}
export class IPCLogger implements IIPCLogger {
private _totalIncoming = 0;
private _totalOutgoing = 0;
constructor(
private readonly _outgoingPrefix: string,
private readonly _incomingPrefix: string,
) { }
public logOutgoing(msgLength: number, requestId: number, initiator: RequestInitiator, str: string, data?: any): void {
this._totalOutgoing += msgLength;
logWithColors(this._outgoingPrefix, this._totalOutgoing, msgLength, requestId, initiator, str, data);
}
public logIncoming(msgLength: number, requestId: number, initiator: RequestInitiator, str: string, data?: any): void {
this._totalIncoming += msgLength;
logWithColors(this._incomingPrefix, this._totalIncoming, msgLength, requestId, initiator, str, data);
}
}

View File

@@ -209,6 +209,37 @@ export interface SaveDialogReturnValue {
bookmark?: string;
}
export interface CrashReporterStartOptions {
companyName: string;
/**
* URL that crash reports will be sent to as POST.
*/
submitURL: string;
/**
* Defaults to `app.name`.
*/
productName?: string;
/**
* Whether crash reports should be sent to the server. Default is `true`.
*/
uploadToServer?: boolean;
/**
* Default is `false`.
*/
ignoreSystemCrashHandler?: boolean;
/**
* An object you can define that will be sent along with the report. Only string
* properties are sent correctly. Nested objects are not supported. When using
* Windows, the property names and values must be fewer than 64 characters.
*/
extra?: Record<string, string>;
/**
* Directory to store the crash reports temporarily (only used when the crash
* reporter is started via `process.crashReporter.start`).
*/
crashesDirectory?: string;
}
export interface FileFilter {
// Docs: http://electronjs.org/docs/api/structures/file-filter
@@ -250,62 +281,3 @@ export interface MouseInputEvent extends InputEvent {
x: number;
y: number;
}
export interface CrashReporterStartOptions {
/**
* URL that crash reports will be sent to as POST.
*/
submitURL: string;
/**
* Defaults to `app.name`.
*/
productName?: string;
/**
* Deprecated alias for `{ globalExtra: { _companyName: ... } }`.
*
* @deprecated
*/
companyName?: string;
/**
* Whether crash reports should be sent to the server. If false, crash reports will
* be collected and stored in the crashes directory, but not uploaded. Default is
* `true`.
*/
uploadToServer?: boolean;
/**
* If true, crashes generated in the main process will not be forwarded to the
* system crash handler. Default is `false`.
*/
ignoreSystemCrashHandler?: boolean;
/**
* If true, limit the number of crashes uploaded to 1/hour. Default is `false`.
*
* @platform darwin,win32
*/
rateLimit?: boolean;
/**
* If true, crash reports will be compressed and uploaded with `Content-Encoding:
* gzip`. Not all collection servers support compressed payloads. Default is
* `false`.
*
* @platform darwin,win32
*/
compress?: boolean;
/**
* Extra string key/value annotations that will be sent along with crash reports
* that are generated in the main process. Only string values are supported.
* Crashes generated in child processes will not contain these extra parameters to
* crash reports generated from child processes, call `addExtraParameter` from the
* child process.
*/
extra?: Record<string, string>;
/**
* Extra string key/value annotations that will be sent along with any crash
* reports generated in any process. These annotations cannot be changed once the
* crash reporter has been started. If a key is present in both the global extra
* parameters and the process-specific extra parameters, then the global one will
* take precedence. By default, `productName` and the app version are included, as
* well as the Electron version.
*/
globalExtra?: Record<string, string>;
}

View File

@@ -74,16 +74,15 @@
},
/**
* Support for subset of methods of Electron's `crashReporter` type.
* Support for subset of methods of Electron's `crashReporter` type.
*/
crashReporter: {
/**
* @param {string} key
* @param {string} value
* @param {Electron.CrashReporterStartOptions} options
*/
addExtraParameter(key, value) {
crashReporter.addExtraParameter(key, value);
start(options) {
crashReporter.start(options);
}
},

View File

@@ -3,6 +3,8 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { CrashReporterStartOptions } from 'vs/base/parts/sandbox/common/electronTypes';
export const ipcRenderer = (window as any).vscode.ipcRenderer as {
/**
@@ -52,23 +54,32 @@ export const webFrame = (window as any).vscode.webFrame as {
export const crashReporter = (window as any).vscode.crashReporter as {
/**
* Set an extra parameter to be sent with the crash report. The values specified
* here will be sent in addition to any values set via the `extra` option when
* `start` was called.
* You are required to call this method before using any other `crashReporter` APIs
* and in each process (main/renderer) from which you want to collect crash
* reports. You can pass different options to `crashReporter.start` when calling
* from different processes.
*
* Parameters added in this fashion (or via the `extra` parameter to
* `crashReporter.start`) are specific to the calling process. Adding extra
* parameters in the main process will not cause those parameters to be sent along
* with crashes from renderer or other child processes. Similarly, adding extra
* parameters in a renderer process will not result in those parameters being sent
* with crashes that occur in other renderer processes or in the main process.
* **Note** Child processes created via the `child_process` module will not have
* access to the Electron modules. Therefore, to collect crash reports from them,
* use `process.crashReporter.start` instead. Pass the same options as above along
* with an additional one called `crashesDirectory` that should point to a
* directory to store the crash reports temporarily. You can test this out by
* calling `process.crash()` to crash the child process.
*
* **Note:** Parameters have limits on the length of the keys and values. Key names
* must be no longer than 39 bytes, and values must be no longer than 127 bytes.
* Keys with names longer than the maximum will be silently ignored. Key values
* longer than the maximum length will be truncated.
* **Note:** If you need send additional/updated `extra` parameters after your
* first call `start` you can call `addExtraParameter` on macOS or call `start`
* again with the new/updated `extra` parameters on Linux and Windows.
*
* **Note:** On macOS and windows, Electron uses a new `crashpad` client for crash
* collection and reporting. If you want to enable crash reporting, initializing
* `crashpad` from the main process using `crashReporter.start` is required
* regardless of which process you want to collect crashes from. Once initialized
* this way, the crashpad handler collects crashes from all processes. You still
* have to call `crashReporter.start` from the renderer or child process, otherwise
* crashes from them will get reported without `companyName`, `productName` or any
* of the `extra` information.
*/
addExtraParameter(key: string, value: string): void;
start(options: CrashReporterStartOptions): void;
};
export const process = (window as any).vscode.process as {

View File

@@ -28,11 +28,11 @@ function getWorker(workerId: string, label: string): Worker | Promise<Worker> {
}
// ESM-comment-begin
export function getWorkerBootstrapUrl(scriptPath: string, label: string): string {
if (/^(http:)|(https:)|(file:)/.test(scriptPath)) {
export function getWorkerBootstrapUrl(scriptPath: string, label: string, forceDataUri: boolean = false): string {
if (forceDataUri || /^((http:)|(https:)|(file:))/.test(scriptPath)) {
const currentUrl = String(window.location);
const currentOrigin = currentUrl.substr(0, currentUrl.length - window.location.hash.length - window.location.search.length - window.location.pathname.length);
if (scriptPath.substring(0, currentOrigin.length) !== currentOrigin) {
if (forceDataUri || scriptPath.substring(0, currentOrigin.length) !== currentOrigin) {
// this is the cross-origin case
// i.e. the webpage is running at a different origin than where the scripts are loaded from
const myPath = 'vs/base/worker/defaultWorkerFactory.js';