mirror of
https://github.com/microsoft/vscode.git
synced 2026-10-05 15:36:22 +01:00
broadcast profiles changes to other windows (#154782)
* broadcast profiles changes to other windows * - add type to broadcast data - adopt in storage service
This commit is contained in:
1 parent
e02c71e3c6
commit
ca5430942d
5 files changed
+109
-94
No files matched your search
@@ -0,0 +1,69 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { getErrorMessage } from 'vs/base/common/errors';
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
import { Disposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
|
||||
export class BroadcastDataChannel<T> extends Disposable {
|
||||
|
||||
private broadcastChannel: BroadcastChannel | undefined;
|
||||
|
||||
private readonly _onDidReceiveData = this._register(new Emitter<T>());
|
||||
readonly onDidReceiveData = this._onDidReceiveData.event;
|
||||
|
||||
constructor(private readonly channelName: string) {
|
||||
super();
|
||||
|
||||
// Use BroadcastChannel
|
||||
if ('BroadcastChannel' in window) {
|
||||
try {
|
||||
this.broadcastChannel = new BroadcastChannel(channelName);
|
||||
const listener = (event: MessageEvent) => {
|
||||
this._onDidReceiveData.fire(event.data);
|
||||
};
|
||||
this.broadcastChannel.addEventListener('message', listener);
|
||||
this._register(toDisposable(() => {
|
||||
if (this.broadcastChannel) {
|
||||
this.broadcastChannel.removeEventListener('message', listener);
|
||||
this.broadcastChannel.close();
|
||||
}
|
||||
}));
|
||||
} catch (error) {
|
||||
console.warn('Error while creating broadcast channel. Falling back to localStorage.', getErrorMessage(error));
|
||||
}
|
||||
}
|
||||
|
||||
// BroadcastChannel is not supported. Use storage.
|
||||
if (!this.broadcastChannel) {
|
||||
this.channelName = `BroadcastDataChannel.${channelName}`;
|
||||
this.createBroadcastChannel();
|
||||
}
|
||||
}
|
||||
|
||||
private createBroadcastChannel(): void {
|
||||
const listener = (event: StorageEvent) => {
|
||||
if (event.key === this.channelName && event.newValue) {
|
||||
this._onDidReceiveData.fire(JSON.parse(event.newValue));
|
||||
}
|
||||
};
|
||||
window.addEventListener('storage', listener);
|
||||
this._register(toDisposable(() => window.removeEventListener('storage', listener)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the data to other BroadcastChannel objects set up for this channel. Data can be structured objects, e.g. nested objects and arrays.
|
||||
* @param data data to broadcast
|
||||
*/
|
||||
postData(data: T): void {
|
||||
if (this.broadcastChannel) {
|
||||
this.broadcastChannel.postMessage(data);
|
||||
} else {
|
||||
// remove previous changes so that event is triggered even if new changes are same as old changes
|
||||
window.localStorage.removeItem(this.channelName);
|
||||
window.localStorage.setItem(this.channelName, JSON.stringify(data));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,15 +5,15 @@
|
||||
|
||||
import { Throttler } from 'vs/base/common/async';
|
||||
import { VSBuffer } from 'vs/base/common/buffer';
|
||||
import { getErrorMessage } from 'vs/base/common/errors';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { ExtUri } from 'vs/base/common/resources';
|
||||
import { isString } from 'vs/base/common/types';
|
||||
import { URI, UriComponents } from 'vs/base/common/uri';
|
||||
import { isString, UriDto } from 'vs/base/common/types';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { localize } from 'vs/nls';
|
||||
import { createFileSystemProviderError, FileChangeType, IFileDeleteOptions, IFileOverwriteOptions, FileSystemProviderCapabilities, FileSystemProviderError, FileSystemProviderErrorCode, FileType, IFileWriteOptions, IFileChange, IFileSystemProviderWithFileReadWriteCapability, IStat, IWatchOptions } from 'vs/platform/files/common/files';
|
||||
import { DBClosedError, IndexedDB } from 'vs/base/browser/indexedDB';
|
||||
import { BroadcastDataChannel } from 'vs/base/browser/broadcast';
|
||||
|
||||
export type IndexedDBFileSystemProviderErrorDataClassification = {
|
||||
owner: 'sandy081';
|
||||
@@ -165,78 +165,6 @@ class IndexedDBFileSystemNode {
|
||||
}
|
||||
}
|
||||
|
||||
type FileChangeDto = {
|
||||
readonly type: FileChangeType;
|
||||
readonly resource: UriComponents;
|
||||
};
|
||||
|
||||
class IndexedDBChangesBroadcastChannel extends Disposable {
|
||||
|
||||
private broadcastChannel: BroadcastChannel | undefined;
|
||||
|
||||
private readonly _onDidFileChanges = this._register(new Emitter<readonly IFileChange[]>());
|
||||
readonly onDidFileChanges: Event<readonly IFileChange[]> = this._onDidFileChanges.event;
|
||||
|
||||
constructor(private readonly changesKey: string) {
|
||||
super();
|
||||
|
||||
// Use BroadcastChannel
|
||||
if ('BroadcastChannel' in window) {
|
||||
try {
|
||||
this.broadcastChannel = new BroadcastChannel(changesKey);
|
||||
const listener = (event: MessageEvent) => {
|
||||
if (isString(event.data)) {
|
||||
this.onDidReceiveChanges(event.data);
|
||||
}
|
||||
};
|
||||
this.broadcastChannel.addEventListener('message', listener);
|
||||
this._register(toDisposable(() => {
|
||||
if (this.broadcastChannel) {
|
||||
this.broadcastChannel.removeEventListener('message', listener);
|
||||
this.broadcastChannel.close();
|
||||
}
|
||||
}));
|
||||
} catch (error) {
|
||||
console.warn('Error while creating broadcast channel. Falling back to localStorage.', getErrorMessage(error));
|
||||
this.createStorageBroadcastChannel(changesKey);
|
||||
}
|
||||
}
|
||||
|
||||
// BroadcastChannel is not supported. Use storage.
|
||||
else {
|
||||
this.createStorageBroadcastChannel(changesKey);
|
||||
}
|
||||
}
|
||||
|
||||
private createStorageBroadcastChannel(changesKey: string): void {
|
||||
const listener = (event: StorageEvent) => {
|
||||
if (event.key === changesKey && event.newValue) {
|
||||
this.onDidReceiveChanges(event.newValue);
|
||||
}
|
||||
};
|
||||
window.addEventListener('storage', listener);
|
||||
this._register(toDisposable(() => window.removeEventListener('storage', listener)));
|
||||
}
|
||||
|
||||
private onDidReceiveChanges(data: string): void {
|
||||
try {
|
||||
const changesDto: FileChangeDto[] = JSON.parse(data);
|
||||
this._onDidFileChanges.fire(changesDto.map(c => ({ type: c.type, resource: URI.revive(c.resource) })));
|
||||
} catch (error) {/* ignore*/ }
|
||||
}
|
||||
|
||||
postChanges(changes: IFileChange[]): void {
|
||||
if (this.broadcastChannel) {
|
||||
this.broadcastChannel.postMessage(JSON.stringify(changes));
|
||||
} else {
|
||||
// remove previous changes so that event is triggered even if new changes are same as old changes
|
||||
window.localStorage.removeItem(this.changesKey);
|
||||
window.localStorage.setItem(this.changesKey, JSON.stringify(changes));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class IndexedDBFileSystemProvider extends Disposable implements IFileSystemProviderWithFileReadWriteCapability {
|
||||
|
||||
readonly capabilities: FileSystemProviderCapabilities =
|
||||
@@ -246,7 +174,7 @@ export class IndexedDBFileSystemProvider extends Disposable implements IFileSyst
|
||||
|
||||
private readonly extUri = new ExtUri(() => false) /* Case Sensitive */;
|
||||
|
||||
private readonly changesBroadcastChannel: IndexedDBChangesBroadcastChannel | undefined;
|
||||
private readonly changesBroadcastChannel: BroadcastDataChannel<UriDto<IFileChange>[]> | undefined;
|
||||
private readonly _onDidChangeFile = this._register(new Emitter<readonly IFileChange[]>());
|
||||
readonly onDidChangeFile: Event<readonly IFileChange[]> = this._onDidChangeFile.event;
|
||||
|
||||
@@ -263,8 +191,10 @@ export class IndexedDBFileSystemProvider extends Disposable implements IFileSyst
|
||||
this.writeManyThrottler = new Throttler();
|
||||
|
||||
if (watchCrossWindowChanges) {
|
||||
this.changesBroadcastChannel = this._register(new IndexedDBChangesBroadcastChannel(`vscode.indexedDB.${scheme}.changes`));
|
||||
this._register(this.changesBroadcastChannel.onDidFileChanges(changes => this._onDidChangeFile.fire(changes)));
|
||||
this.changesBroadcastChannel = this._register(new BroadcastDataChannel<UriDto<IFileChange>[]>(`vscode.indexedDB.${scheme}.changes`));
|
||||
this._register(this.changesBroadcastChannel.onDidReceiveData(changes => {
|
||||
this._onDidChangeFile.fire(changes.map(c => ({ type: c.type, resource: URI.revive(c.resource) })));
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -459,7 +389,7 @@ export class IndexedDBFileSystemProvider extends Disposable implements IFileSyst
|
||||
if (changes.length) {
|
||||
this._onDidChangeFile.fire(changes);
|
||||
|
||||
this.changesBroadcastChannel?.postChanges(changes);
|
||||
this.changesBroadcastChannel?.postData(changes);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,16 +3,21 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { BroadcastDataChannel } from 'vs/base/browser/broadcast';
|
||||
import { revive } from 'vs/base/common/marshalling';
|
||||
import { UriDto } from 'vs/base/common/types';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity';
|
||||
import { IUserDataProfilesService, PROFILES_ENABLEMENT_CONFIG, StoredProfileAssociations, StoredUserDataProfile, UserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile';
|
||||
import { DidChangeProfilesEvent, IUserDataProfile, IUserDataProfilesService, PROFILES_ENABLEMENT_CONFIG, reviveProfile, StoredProfileAssociations, StoredUserDataProfile, UserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile';
|
||||
|
||||
type BroadcastedProfileChanges = UriDto<Omit<DidChangeProfilesEvent, 'all'>>;
|
||||
|
||||
export class BrowserUserDataProfilesService extends UserDataProfilesService implements IUserDataProfilesService {
|
||||
|
||||
protected override readonly defaultProfileShouldIncludeExtensionsResourceAlways: boolean = true;
|
||||
private readonly changesBroadcastChannel: BroadcastDataChannel<BroadcastedProfileChanges>;
|
||||
|
||||
constructor(
|
||||
@IEnvironmentService environmentService: IEnvironmentService,
|
||||
@@ -22,6 +27,13 @@ export class BrowserUserDataProfilesService extends UserDataProfilesService impl
|
||||
) {
|
||||
super(environmentService, fileService, uriIdentityService, logService);
|
||||
super.setEnablement(window.localStorage.getItem(PROFILES_ENABLEMENT_CONFIG) === 'true');
|
||||
this.changesBroadcastChannel = this._register(new BroadcastDataChannel<BroadcastedProfileChanges>(`${UserDataProfilesService.PROFILES_KEY}.changes`));
|
||||
this._register(this.changesBroadcastChannel.onDidReceiveData(changes => {
|
||||
try {
|
||||
this._profilesObject = undefined;
|
||||
this._onDidChangeProfiles.fire({ added: changes.added.map(p => reviveProfile(p, this.profilesHome.scheme)), removed: changes.removed.map(p => reviveProfile(p, this.profilesHome.scheme)), all: this.profiles });
|
||||
} catch (error) {/* ignore */ }
|
||||
}));
|
||||
}
|
||||
|
||||
override setEnablement(enabled: boolean): void {
|
||||
@@ -42,6 +54,11 @@ export class BrowserUserDataProfilesService extends UserDataProfilesService impl
|
||||
return [];
|
||||
}
|
||||
|
||||
protected override triggerProfilesChanges(added: IUserDataProfile[], removed: IUserDataProfile[]) {
|
||||
super.triggerProfilesChanges(added, removed);
|
||||
this.changesBroadcastChannel.postData({ added, removed });
|
||||
}
|
||||
|
||||
protected override saveStoredProfiles(storedProfiles: StoredUserDataProfile[]): void {
|
||||
window.localStorage.setItem(UserDataProfilesService.PROFILES_KEY, JSON.stringify(storedProfiles));
|
||||
}
|
||||
|
||||
@@ -338,6 +338,10 @@ export class UserDataProfilesService extends Disposable implements IUserDataProf
|
||||
}
|
||||
this.saveStoredProfiles(storedProfiles);
|
||||
this._profilesObject = undefined;
|
||||
this.triggerProfilesChanges(added, removed);
|
||||
}
|
||||
|
||||
protected triggerProfilesChanges(added: IUserDataProfile[], removed: IUserDataProfile[]) {
|
||||
this._onDidChangeProfiles.fire({ added, removed, all: this.profiles });
|
||||
}
|
||||
|
||||
|
||||
@@ -3,12 +3,13 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { BroadcastDataChannel } from 'vs/base/browser/broadcast';
|
||||
import { isSafari } from 'vs/base/browser/browser';
|
||||
import { IndexedDB } from 'vs/base/browser/indexedDB';
|
||||
import { DeferredPromise, Promises } from 'vs/base/common/async';
|
||||
import { toErrorMessage } from 'vs/base/common/errorMessage';
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
import { Disposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { assertIsDefined } from 'vs/base/common/types';
|
||||
import { InMemoryStorageDatabase, isStorageItemsChangeEvent, IStorage, IStorageDatabase, IStorageItemsChangeEvent, IUpdateRequest, Storage } from 'vs/base/parts/storage/common/storage';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
@@ -302,7 +303,7 @@ export class IndexedDBStorageDatabase extends Disposable implements IIndexedDBSt
|
||||
private readonly _onDidChangeItemsExternal = this._register(new Emitter<IStorageItemsChangeEvent>());
|
||||
readonly onDidChangeItemsExternal = this._onDidChangeItemsExternal.event;
|
||||
|
||||
private broadcastChannel: BroadcastChannel | undefined;
|
||||
private broadcastChannel: BroadcastDataChannel<IStorageItemsChangeEvent> | undefined;
|
||||
|
||||
private pendingUpdate: Promise<boolean> | undefined = undefined;
|
||||
get hasPendingUpdate(): boolean { return !!this.pendingUpdate; }
|
||||
@@ -317,7 +318,7 @@ export class IndexedDBStorageDatabase extends Disposable implements IIndexedDBSt
|
||||
super();
|
||||
|
||||
this.name = `${IndexedDBStorageDatabase.STORAGE_DATABASE_PREFIX}${options.id}`;
|
||||
this.broadcastChannel = options.broadcastChanges && ('BroadcastChannel' in window) ? new BroadcastChannel(IndexedDBStorageDatabase.STORAGE_BROADCAST_CHANNEL) : undefined;
|
||||
this.broadcastChannel = options.broadcastChanges ? this._register(new BroadcastDataChannel<IStorageItemsChangeEvent>(IndexedDBStorageDatabase.STORAGE_BROADCAST_CHANNEL)) : undefined;
|
||||
|
||||
this.whenConnected = this.connect();
|
||||
|
||||
@@ -329,16 +330,10 @@ export class IndexedDBStorageDatabase extends Disposable implements IIndexedDBSt
|
||||
// Check for storage change events from other
|
||||
// windows/tabs via `BroadcastChannel` mechanisms.
|
||||
if (this.broadcastChannel) {
|
||||
const listener = (event: MessageEvent) => {
|
||||
if (isStorageItemsChangeEvent(event.data)) {
|
||||
this._onDidChangeItemsExternal.fire(event.data);
|
||||
this._register(this.broadcastChannel.onDidReceiveData(data => {
|
||||
if (isStorageItemsChangeEvent(data)) {
|
||||
this._onDidChangeItemsExternal.fire(data);
|
||||
}
|
||||
};
|
||||
|
||||
this.broadcastChannel.addEventListener('message', listener);
|
||||
this._register(toDisposable(() => {
|
||||
this.broadcastChannel?.removeEventListener('message', listener);
|
||||
this.broadcastChannel?.close();
|
||||
}));
|
||||
}
|
||||
}
|
||||
@@ -382,7 +377,7 @@ export class IndexedDBStorageDatabase extends Disposable implements IIndexedDBSt
|
||||
deleted: request.delete
|
||||
};
|
||||
|
||||
this.broadcastChannel.postMessage(event);
|
||||
this.broadcastChannel.postData(event);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user