Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions build/.moduleignore
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,13 @@ vsda/**
!vsda/rust/web/**
!vsda/rust/bundler/**

@vscode/metered/build/**
@vscode/metered/src/**
@vscode/metered/binding.gyp
@vscode/metered/README.md
@vscode/metered/index.d.ts
!@vscode/metered/build/Release/vscode-metered.node
Comment thread
dmitrivMS marked this conversation as resolved.

@vscode/policy-watcher/build/**
@vscode/policy-watcher/.husky/**
@vscode/policy-watcher/src/**
Expand Down
1 change: 1 addition & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -1543,6 +1543,7 @@ export default defineConfig(
'@vscode/vscode-languagedetection',
'@vscode/ripgrep-universal',
'@vscode/iconv-lite-umd',
'@vscode/metered',
'@vscode/native-watchdog',
'@vscode/policy-watcher',
'@vscode/proxy-agent',
Expand Down
24 changes: 24 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@
"@vscode/diff": "0.0.2-7",
"@vscode/fs-copyfile": "2.0.0",
"@vscode/iconv-lite-umd": "0.7.1",
"@vscode/metered": "^0.1.0",
Comment thread
dmitrivMS marked this conversation as resolved.
Comment thread
dmitrivMS marked this conversation as resolved.
"@vscode/native-watchdog": "^1.4.6",
"@vscode/os-proxy-resolver": "^0.3.0",
"@vscode/policy-watcher": "^1.4.0",
Expand Down Expand Up @@ -313,6 +314,7 @@
"@vscode/native-watchdog@1.4.6": true,
"@vscode/ripgrep@1.17.1": true,
"@vscode/deviceid@0.1.5": true,
"@vscode/metered@0.1.0": true,
"@vscode/policy-watcher@1.4.0": true,
"@vscode/spdlog@0.15.8": true,
"@vscode/sqlite3@5.1.12-vscode": true,
Expand Down
2 changes: 1 addition & 1 deletion src/vs/code/electron-main/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1212,7 +1212,7 @@ export class CodeApplication extends Disposable {
services.set(IGlobalKeybindingsMainService, new SyncDescriptor(GlobalKeybindingsMainService, [globalShortcut]));

// Metered Connection
const meteredConnectionService = new MeteredConnectionMainService(this.configurationService);
const meteredConnectionService = new MeteredConnectionMainService(undefined, this.configurationService, this.logService);
Comment thread
dmitrivMS marked this conversation as resolved.
Outdated
services.set(IMeteredConnectionService, meteredConnectionService);

// Web Contents Extractor
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,11 @@ flakySuite('Native Modules (all platforms)', () => {
assert.ok(typeof watcher.createWatcher === 'function', testErrorMessage('@vscode/policy-watcher'));
});

test('@vscode/metered', async () => {
const metered = await import('@vscode/metered');
assert.ok(typeof metered.createMonitor === 'function', testErrorMessage('@vscode/metered'));
});

test('node-pty', async () => {
const nodePty = await import('node-pty');
assert.ok(typeof nodePty.spawn === 'function', testErrorMessage('node-pty'));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,43 @@
import { toDisposable } from '../../../base/common/lifecycle.js';
import { IConfigurationService } from '../../configuration/common/configuration.js';
import { InstantiationType, registerSingleton } from '../../instantiation/common/extensions.js';
import { AbstractMeteredConnectionService, getIsBrowserConnectionMetered, IMeteredConnectionService, NavigatorWithConnection } from '../common/meteredConnection.js';
import { AbstractMeteredConnectionService, IMeteredConnectionService } from '../common/meteredConnection.js';

/**
* Browser Network Information API properties used for metered detection.
* See https://developer.mozilla.org/en-US/docs/Web/API/Network_Information_API
*/
interface NetworkInformation {
saveData?: boolean;
metered?: boolean;
effectiveType?: 'slow-2g' | '2g' | '3g' | '4g';
addEventListener(type: 'change', listener: () => void): void;
removeEventListener(type: 'change', listener: () => void): void;
}

/**
* Extends Navigator with the optional browser Network Information API.
*/
interface NavigatorWithConnection {
readonly connection?: NetworkInformation;
}

/**
* Returns whether the browser Network Information API indicates a metered connection.
*/
function getIsBrowserConnectionMetered(): boolean {
const connection = (navigator as NavigatorWithConnection).connection;
if (!connection) {
return false;
}

if (connection.saveData || connection.metered) {
return true;
}

const effectiveType = connection.effectiveType;
return effectiveType === '2g' || effectiveType === 'slow-2g';
}

/**
* Browser implementation of the metered connection service.
Expand All @@ -18,7 +54,7 @@ export class MeteredConnectionService extends AbstractMeteredConnectionService {

const connection = (navigator as NavigatorWithConnection).connection;
if (connection) {
const onChange = () => this.setIsBrowserConnectionMetered(getIsBrowserConnectionMetered());
const onChange = () => this.setIsUnderlyingConnectionMetered(getIsBrowserConnectionMetered());
connection.addEventListener('change', onChange);
this._register(toDisposable(() => connection.removeEventListener('change', onChange)));
}
Expand Down
61 changes: 12 additions & 49 deletions src/vs/platform/meteredConnection/common/meteredConnection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,45 +35,8 @@ export interface IMeteredConnectionService {
}

export const METERED_CONNECTION_SETTING_KEY = 'network.meteredConnection';

export type MeteredConnectionSettingValue = 'on' | 'off' | 'auto';

/**
* Network Information API
* See https://developer.mozilla.org/en-US/docs/Web/API/Network_Information_API
*/
export interface NetworkInformation {
saveData?: boolean;
metered?: boolean;
effectiveType?: 'slow-2g' | '2g' | '3g' | '4g';
addEventListener(type: 'change', listener: () => void): void;
removeEventListener(type: 'change', listener: () => void): void;
}

/**
* Extended Navigator interface for Network Information API
*/
export interface NavigatorWithConnection {
readonly connection?: NetworkInformation;
}

/**
* Check if the current network connection is metered according to the Network Information API.
*/
export function getIsBrowserConnectionMetered() {
const connection = (navigator as NavigatorWithConnection).connection;
if (!connection) {
return false;
}

if (connection.saveData || connection.metered) {
return true;
}

const effectiveType = connection.effectiveType;
return effectiveType === '2g' || effectiveType === 'slow-2g';
}

/**
* Abstract base class for metered connection services.
*/
Expand All @@ -84,15 +47,15 @@ export abstract class AbstractMeteredConnectionService extends Disposable implem
public readonly onDidChangeIsConnectionMetered = this._onDidChangeIsConnectionMetered.event;

private _isConnectionMetered: boolean;
private _isBrowserConnectionMetered: boolean;
private _isUnderlyingConnectionMetered: boolean;
private _meteredConnectionSetting: MeteredConnectionSettingValue;

constructor(configurationService: IConfigurationService, isBrowserConnectionMetered: boolean) {
constructor(configurationService: IConfigurationService, isUnderlyingConnectionMetered: boolean) {
super();

this._isBrowserConnectionMetered = isBrowserConnectionMetered;
this._isUnderlyingConnectionMetered = isUnderlyingConnectionMetered;
this._meteredConnectionSetting = configurationService.getValue<MeteredConnectionSettingValue>(METERED_CONNECTION_SETTING_KEY);
this._isConnectionMetered = this._meteredConnectionSetting === 'on' || (this._meteredConnectionSetting !== 'off' && this._isBrowserConnectionMetered);
this._isConnectionMetered = this._meteredConnectionSetting === 'on' || (this._meteredConnectionSetting !== 'off' && this._isUnderlyingConnectionMetered);

this._register(configurationService.onDidChangeConfiguration(e => {
if (e.affectsConfiguration(METERED_CONNECTION_SETTING_KEY)) {
Expand All @@ -109,23 +72,23 @@ export abstract class AbstractMeteredConnectionService extends Disposable implem
return this._isConnectionMetered;
}

protected get isBrowserConnectionMetered(): boolean {
return this._isBrowserConnectionMetered;
protected get isUnderlyingConnectionMetered(): boolean {
return this._isUnderlyingConnectionMetered;
}

public setIsBrowserConnectionMetered(value: boolean) {
if (value !== this._isBrowserConnectionMetered) {
this._isBrowserConnectionMetered = value;
this.onChangeBrowserConnection();
protected setIsUnderlyingConnectionMetered(value: boolean) {
if (value !== this._isUnderlyingConnectionMetered) {
this._isUnderlyingConnectionMetered = value;
this.onChangeUnderlyingConnection();
}
}

protected onChangeBrowserConnection() {
protected onChangeUnderlyingConnection() {
this.onUpdated();
}

protected onUpdated() {
const value = this._meteredConnectionSetting === 'on' || (this._meteredConnectionSetting !== 'off' && this._isBrowserConnectionMetered);
const value = this._meteredConnectionSetting === 'on' || (this._meteredConnectionSetting !== 'off' && this._isUnderlyingConnectionMetered);
if (value !== this._isConnectionMetered) {
this._isConnectionMetered = value;
this.onChangeIsConnectionMetered();
Expand Down
18 changes: 10 additions & 8 deletions src/vs/platform/meteredConnection/common/meteredConnectionIpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { onUnexpectedError } from '../../../base/common/errors.js';
import { Emitter } from '../../../base/common/event.js';
import { Disposable } from '../../../base/common/lifecycle.js';
import { IChannel } from '../../../base/parts/ipc/common/ipc.js';
Expand All @@ -16,7 +17,6 @@ export const METERED_CONNECTION_CHANNEL = 'meteredConnection';
export enum MeteredConnectionCommand {
OnDidChangeIsConnectionMetered = 'OnDidChangeIsConnectionMetered',
IsConnectionMetered = 'IsConnectionMetered',
SetIsBrowserConnectionMetered = 'SetIsBrowserConnectionMetered',
}

/**
Expand All @@ -36,18 +36,20 @@ export class MeteredConnectionChannelClient extends Disposable implements IMeter
constructor(channel: IChannel) {
super();

channel.call<boolean>(MeteredConnectionCommand.IsConnectionMetered).then(value => {
this._isConnectionMetered = value;
if (value) {
this._onDidChangeIsConnectionMetered.fire(value);
}
});

let receivedEvent = false;
this._register(channel.listen<boolean>(MeteredConnectionCommand.OnDidChangeIsConnectionMetered)(value => {
receivedEvent = true;
if (this._isConnectionMetered !== value) {
this._isConnectionMetered = value;
this._onDidChangeIsConnectionMetered.fire(value);
}
}));

channel.call<boolean>(MeteredConnectionCommand.IsConnectionMetered).then(value => {
if (!receivedEvent && this._isConnectionMetered !== value) {
this._isConnectionMetered = value;
this._onDidChangeIsConnectionMetered.fire(value);
}
Comment thread
dmitrivMS marked this conversation as resolved.
Outdated
}, onUnexpectedError);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,46 +3,19 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { toDisposable } from '../../../base/common/lifecycle.js';
import { IChannel } from '../../../base/parts/ipc/common/ipc.js';
import { IConfigurationService } from '../../configuration/common/configuration.js';
import { SyncDescriptor } from '../../instantiation/common/descriptors.js';
import { registerSingleton } from '../../instantiation/common/extensions.js';
import { InstantiationType, registerSingleton } from '../../instantiation/common/extensions.js';
import { IMainProcessService } from '../../ipc/common/mainProcessService.js';
import { AbstractMeteredConnectionService, getIsBrowserConnectionMetered, IMeteredConnectionService, NavigatorWithConnection } from '../common/meteredConnection.js';
import { METERED_CONNECTION_CHANNEL, MeteredConnectionCommand } from '../common/meteredConnectionIpc.js';
import { IMeteredConnectionService } from '../common/meteredConnection.js';
import { METERED_CONNECTION_CHANNEL, MeteredConnectionChannelClient } from '../common/meteredConnectionIpc.js';

/**
* Electron-browser implementation of the metered connection service.
* This implementation monitors navigator.connection and reports changes to the main process via IPC channel.
* The native state and user override are owned by the main process.
*/
export class NativeMeteredConnectionService extends AbstractMeteredConnectionService {
private readonly _channel: IChannel;

constructor(
private readonly connectionMeteredDetector: () => boolean,
@IConfigurationService configurationService: IConfigurationService,
@IMainProcessService mainProcessService: IMainProcessService
) {
super(configurationService, connectionMeteredDetector());
this._channel = mainProcessService.getChannel(METERED_CONNECTION_CHANNEL);
void this._channel.call(MeteredConnectionCommand.SetIsBrowserConnectionMetered, this.isBrowserConnectionMetered);

const connection = (navigator as NavigatorWithConnection).connection;
if (connection) {
const onChange = () => this.setIsBrowserConnectionMetered(this.connectionMeteredDetector());
connection.addEventListener('change', onChange);
this._register(toDisposable(() => connection.removeEventListener('change', onChange)));
}
}

/**
* Notify the main process about changes to the navigator connection state.
*/
protected override onChangeBrowserConnection(): void {
super.onChangeBrowserConnection();
this._channel.call(MeteredConnectionCommand.SetIsBrowserConnectionMetered, this.isBrowserConnectionMetered);
export class NativeMeteredConnectionService extends MeteredConnectionChannelClient {
constructor(@IMainProcessService mainProcessService: IMainProcessService) {
super(mainProcessService.getChannel(METERED_CONNECTION_CHANNEL));
}
}

registerSingleton(IMeteredConnectionService, new SyncDescriptor(NativeMeteredConnectionService, [getIsBrowserConnectionMetered], false));
registerSingleton(IMeteredConnectionService, NativeMeteredConnectionService, InstantiationType.Delayed);
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,6 @@ export class MeteredConnectionChannel implements IServerChannel {
switch (command) {
case MeteredConnectionCommand.IsConnectionMetered:
return this.service.isConnectionMetered;
case MeteredConnectionCommand.SetIsBrowserConnectionMetered:
this.service.setIsBrowserConnectionMetered(arg);
break;
default:
throw new Error(`Call not found: ${command}`);
}
Expand Down
Loading
Loading