Change:
Issue Type: <bBug</bTesting: See the following: ! I click the Open all links without prompt option, but can also unselect some of the links. Now I don't know what state I am in. Will all links open without a prompt, including the ones I unselected? Or will all links except for the ones I unselected open? What I expected was that the open all links option would toggle the checked state of all links in the list. VS Code version: Code - Insiders 1.38.0-insider (, 2019-08-27T06:13:54.903Z) OS version: WindowsNT x64 10.0. <details<summarySystem Info</summary </details<details<summaryExtensions (12)</summaryExtensionVersion ------ tslint1.0.44 vscode-github-releases-updater0.17.0 vscode-docker0.7.0 python2019.8. remote-containers0.69.0 remote-ssh0.45.6 remote-ssh-edit0.45.6 remote-ssh-explorer0.45.6 remote-wsl0.39.4 vscode-remote-extensionpack0.16.0 azure-account0.8.4 test-search-rg0.0.3 </details<!-- generated by issue reporter --
On the other hand, if you have 10 domains checked and 10 unchecked, and clicks "Open all links without prompt", there's no way to restore the old state other than manually checking the 10 domains out of the 20. Making the first pick item behave different than other ones also doesn't feel nice — the checkbox right above it would toggle all on/off. I feel "Open all links without prompt" is a pretty clear message. So marking this as-designed.
I don't agree. In the mockup above, if I click on a link to it is ambiguous if I will be prompted or not. The entry is not checked suggesting that I will be prompted, but open all links without prompt is selected, suggesting that I won't be prompted. Which one takes precedence? How do I know that?
- what do you think?
/cc
I would treat the "Open All Links" as the same as a "Select All" and select every item in the list. It is very ambiguous if you have some unselected and then "Open all links" is selected. Another option would be to disable the rest of the items when "Open all links" is checked, because that is what will trump all other selections and you want to be clear to the user what this will do especially when others are unselected.
OK, I can try to make the quick input hide all items below the separator.
This would involve a lot of change as we should use a completely different API to achieve this, so pushing out to next milestone.
See

Discussion:
} /** <del> * Localize a message. `message` can contain `{n}` notation where it is replaced by the nth value in `...args`. </del> <ins> * Localize a message. * * `message` can contain `{n}` notation where it is replaced by the nth value in `...args` * For example, `localize('hello {0}', name)` </ins> */ export declare function localize(info: ILocalizeInfo, message: string, ...args: (string | number | boolean | undefined | null)[]): string; /** <del> * Localize a message. `message` can contain `{n}` notation where it is replaced by the nth value in `...args`. </del> <ins> * Localize a message. * * `message` can contain `{n}` notation where it is replaced by the nth value in `...args` * For example, `localize('hello {0}', name)` </ins> */ export declare function localize(key: string, message: string, ...args: (string | number | boolean | undefined | null)[]): string;

<ins> /*--------------------------------------------------------------------------------------------- *  Copyright (c) Microsoft Corporation. All rights reserved. *  Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import { localize } from 'vs/nls'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { URI } from 'vs/base/common/uri'; import { readTrustedDomains } from 'vs/workbench/contrib/url/common/trustedDomainsValidator'; import { IProductService } from 'vs/platform/product/common/product'; import { INotificationService } from 'vs/platform/notification/common/notification'; const TRUSTED_DOMAINS_URI = URI.parse('trustedDomains:/Trusted Domains'); export const configureTrustedDomainSettingsCommand = { id: 'workbench.action.configureTrustedDomain', description: { description: localize('trustedDomain.configureTrustedDomain', 'Configure Trusted Domains'), args: [] }, handler: async (accessor: ServicesAccessor) => { const editorService = accessor.get(IEditorService); editorService.openEditor({ resource: TRUSTED_DOMAINS_URI, mode: 'jsonc' }); return; } }; export const toggleLinkProtection = { id: 'workbench.action.toggleLinkProtection', description: { description: localize( 'trustedDomain.toggleLinkProtectionForTrustedDomains', 'Toggle Link Protection for Trusted Domains' ), args: [] }, handler: async (accessor: ServicesAccessor) => { const storageService = accessor.get(IStorageService); const notificationService = accessor.get(INotificationService); const trustedDomains = readTrustedDomains(storageService, accessor.get(IProductService)); if (trustedDomains.indexOf('*') === -1) { storageService.store( 'http.linkProtectionTrustedDomains', JSON.stringify(trustedDomains.concat(['*'])), StorageScope.GLOBAL ); notificationService.info(localize('trustedDomain.linkProtectionDisabled', 'Link Protection disabled')); } else { storageService.store( 'http.linkProtectionTrustedDomains', JSON.stringify(trustedDomains.filter(x => x !== '*')), StorageScope.GLOBAL ); notificationService.info(localize('trustedDomain.linkProtectionEnabled', 'Link Protection enabled')); } return; } }; export async function configureOpenerTrustedDomainsHandler( trustedDomains: string[], domainToConfigure: string, quickInputService: IQuickInputService, storageService: IStorageService, editorService: IEditorService ) { const openAllLinksItem: IQuickPickItem = { type: 'item', label: localize('trustedDomain.trustAllAndOpenLink', 'Disable Link Protection and open link'), id: '*', picked: trustedDomains.indexOf('*') !== -1 }; const trustDomainAndOpenLinkItem: IQuickPickItem = { type: 'item', label: localize('trustedDomain.trustDomainAndOpenLink', 'Trust {0} and open link', domainToConfigure), id: domainToConfigure, picked: true }; const configureTrustedDomainItem: IQuickPickItem = { type: 'item', label: localize('trustedDomain.configureTrustedDomains', 'Configure Trusted Domains'), id: 'configure' }; const pickedResult = await quickInputService.pick( [openAllLinksItem, trustDomainAndOpenLinkItem, configureTrustedDomainItem], { activeItem: trustDomainAndOpenLinkItem } ); if (pickedResult) { if (pickedResult.id === 'configure') { editorService.openEditor({ resource: TRUSTED_DOMAINS_URI, mode: 'jsonc' }); return trustedDomains; } if (pickedResult.id && trustedDomains.indexOf(pickedResult.id) === -1) { storageService.store( 'http.linkProtectionTrustedDomains', JSON.stringify([...trustedDomains, pickedResult.id]), StorageScope.GLOBAL ); return [...trustedDomains, pickedResult.id]; } } return []; } </ins>

<ins> /*--------------------------------------------------------------------------------------------- *  Copyright (c) Microsoft Corporation. All rights reserved. *  Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import { Event } from 'vs/base/common/event'; import { parse } from 'vs/base/common/json'; import { IDisposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { FileDeleteOptions, FileOverwriteOptions, FileSystemProviderCapabilities, FileType, FileWriteOptions, IFileService, IFileSystemProvider, IStat, IWatchOptions } from 'vs/platform/files/common/files'; import { IProductService } from 'vs/platform/product/common/product'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; const TRUSTED_DOMAINS_SCHEMA = 'trustedDomains'; const TRUSTED_DOMAINS_STAT: IStat = { type: FileType.File, ctime: Date.now(), mtime: Date.now(), size: 0 }; const CONFIG_HELP_TEXT = `// Configure trusted domains by editing and saving this file // You can use `*` to match subdomains. For example https://*.visualstudio.com would match: // - https://code.visualstudio.com // - https://update.code.visualstudio.com `; export class TrustedDomainsFileSystemProvider implements IFileSystemProvider, IWorkbenchContribution { readonly capabilities = FileSystemProviderCapabilities.FileReadWrite; readonly onDidChangeCapabilities = Event.None; readonly onDidChangeFile = Event.None; constructor( @IFileService private readonly fileService: IFileService, @IStorageService private readonly storageService: IStorageService ) { this.fileService.registerProvider(TRUSTED_DOMAINS_SCHEMA, this); } stat(resource: URI): Promise<IStat> { return Promise.resolve(TRUSTED_DOMAINS_STAT); } readFile(resource: URI): Promise<Uint8Array> { let trustedDomains: string[] = []; try { const trustedDomainsSrc = this.storageService.get('http.linkProtectionTrustedDomains', StorageScope.GLOBAL); if (trustedDomainsSrc) { trustedDomains = JSON.parse(trustedDomainsSrc); } } catch (err) { } const trustedDomainsContent = CONFIG_HELP_TEXT + JSON.stringify(trustedDomains, null, 2); const buffer = Buffer.from(trustedDomainsContent, 'utf-8'); return Promise.resolve(buffer); } writeFile(resource: URI, content: Uint8Array, opts: FileWriteOptions): Promise<void> { let trustedDomainsd = []; try { trustedDomainsd = parse(content.toString()); } catch (err) { } this.storageService.store( 'http.linkProtectionTrustedDomains', JSON.stringify(trustedDomainsd), StorageScope.GLOBAL ); return Promise.resolve(); } watch(resource: URI, opts: IWatchOptions): IDisposable { return { dispose() { return; } }; } mkdir(resource: URI): Promise<void> { return Promise.resolve(undefined!); } readdir(resource: URI): Promise<[string, FileType][]> { return Promise.resolve(undefined!); } delete(resource: URI, opts: FileDeleteOptions): Promise<void> { return Promise.resolve(undefined!); } rename(from: URI, to: URI, opts: FileOverwriteOptions): Promise<void> { return Promise.resolve(undefined!); } } </ins>

<ins> /*--------------------------------------------------------------------------------------------- *  Copyright (c) Microsoft Corporation. All rights reserved. *  Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import { Schemas } from 'vs/base/common/network'; import Severity from 'vs/base/common/severity'; import { equalsIgnoreCase } from 'vs/base/common/strings'; import { URI } from 'vs/base/common/uri'; import { localize } from 'vs/nls'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IProductService } from 'vs/platform/product/common/product'; import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { configureOpenerTrustedDomainsHandler } from 'vs/workbench/contrib/url/common/trustedDomains'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; export class OpenerValidatorContributions implements IWorkbenchContribution { constructor( @IOpenerService private readonly _openerService: IOpenerService, @IStorageService private readonly _storageService: IStorageService, @IDialogService private readonly _dialogService: IDialogService, @IProductService private readonly _productService: IProductService, @IQuickInputService private readonly _quickInputService: IQuickInputService, @IEditorService private readonly _editorService: IEditorService ) { this._openerService.registerValidator({ shouldOpen: r => this.validateLink(r) }); } async validateLink(resource: URI): Promise<boolean> { const { scheme, authority } = resource; if (!equalsIgnoreCase(scheme, Schemas.http) && !equalsIgnoreCase(scheme, Schemas.https)) { return true; } const domainToOpen = `${scheme}://${authority}`; const trustedDomains = readTrustedDomains(this._storageService, this._productService); if (isURLDomainTrusted(resource, trustedDomains)) { return true; } else { const { choice } = await this._dialogService.show( Severity.Info, localize( 'openExternalLinkAt', 'Do you want {0} to open the external website?n{1}', this._productService.nameShort, resource.toString(true) ), [ localize('openLink', 'Open Link'), localize('cancel', 'Cancel'), localize('configureTrustedDomains', 'Configure Trusted Domains') ], { cancelId: 1 } ); // Open Link if (choice === 0) { return true; } // Configure Trusted Domains else if (choice === 2) { const pickedDomains = await configureOpenerTrustedDomainsHandler( trustedDomains, domainToOpen, this._quickInputService, this._storageService, this._editorService ); // Trust all domains if (pickedDomains.indexOf('*') !== -1) { return true; } // Trust current domain if (pickedDomains.indexOf(domainToOpen) !== -1) { return true; } return false; } return false; } } } export function readTrustedDomains(storageService: IStorageService, productService: IProductService) { let trustedDomains: string[] = productService.linkProtectionTrustedDomains ? [...productService.linkProtectionTrustedDomains] : []; try { const trustedDomainsSrc = storageService.get('http.linkProtectionTrustedDomains', StorageScope.GLOBAL); if (trustedDomainsSrc) { trustedDomains = JSON.parse(trustedDomainsSrc); } } catch (err) { } return trustedDomains; } const rLocalhost = /^localhost(:d+)?$/i; const r127 = /^127.0.0.1(:d+)?$/; function isLocalhostAuthority(authority: string) { return rLocalhost.test(authority) || r127.test(authority); } /** * Check whether a domain like https://www.microsoft.com matches * the list of trusted domains. * * - Schemes must match * - There's no subdomain matching. For example https://microsoft.com doesn't match https://www.microsoft.com * - Star matches all subdomains. For example https://*.microsoft.com matches https://www.microsoft.com and https://foo.bar.microsoft.com */ export function isURLDomainTrusted(url: URI, trustedDomains: string[]) { if (isLocalhostAuthority(url.authority)) { return true; } const domain = `${url.scheme}://${url.authority}`; for (let i = 0; i < trustedDomains.length; i++) { if (trustedDomains[i] === '*') { return true; } if (trustedDomains[i] === domain) { return true; } if (trustedDomains[i].indexOf('*') !== -1) { const parsedTrustedDomain = URI.parse(trustedDomains[i]); if (url.scheme === parsedTrustedDomain.scheme) { let reversedAuthoritySegments = url.authority.split('.').reverse(); const reversedTrustedDomainAuthoritySegments = parsedTrustedDomain.authority.split('.').reverse(); if ( reversedTrustedDomainAuthoritySegments.length < reversedAuthoritySegments.length && reversedTrustedDomainAuthoritySegments[reversedTrustedDomainAuthoritySegments.length - 1] === '*' ) { reversedAuthoritySegments = reversedAuthoritySegments.slice(0, reversedTrustedDomainAuthoritySegments.length); } if ( reversedAuthoritySegments.every((val, i) => { return reversedTrustedDomainAuthoritySegments[i] === '*' || val === reversedTrustedDomainAuthoritySegments[i]; }) ) { return true; } } } } return false; } </ins>

*  Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ <del> import { localize } from 'vs/nls'; import { SyncActionDescriptor, MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; import { Registry } from 'vs/platform/registry/common/platform'; import { Extensions as ActionExtensions, IWorkbenchActionRegistry } from 'vs/workbench/common/actions'; import { IURLService } from 'vs/platform/url/common/url'; import { IQuickInputService, IQuickPickItem, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; import { URI } from 'vs/base/common/uri'; </del> import { Action } from 'vs/base/common/actions'; <del> import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; </del> <ins> import { URI } from 'vs/base/common/uri'; import { localize } from 'vs/nls'; import { MenuId, MenuRegistry, SyncActionDescriptor } from 'vs/platform/actions/common/actions'; </ins> import { CommandsRegistry } from 'vs/platform/commands/common/commands'; <del> import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; </del> import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; <del> import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { IProductService } from 'vs/platform/product/common/product'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { equalsIgnoreCase } from 'vs/base/common/strings'; import { Schemas } from 'vs/base/common/network'; import Severity from 'vs/base/common/severity'; </del> <ins> import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import { Registry } from 'vs/platform/registry/common/platform'; import { IURLService } from 'vs/platform/url/common/url'; import { Extensions as ActionExtensions, IWorkbenchActionRegistry } from 'vs/workbench/common/actions'; import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; import { configureTrustedDomainSettingsCommand, toggleLinkProtection } from 'vs/workbench/contrib/url/common/trustedDomains'; import { OpenerValidatorContributions } from 'vs/workbench/contrib/url/common/trustedDomainsValidator'; import { TrustedDomainsFileSystemProvider } from 'vs/workbench/contrib/url/common/trustedDomainsFileSystemProvider'; </ins> export class OpenUrlAction extends Action { static readonly ID = 'workbench.action.url.openUrl';

localize('developer', 'Developer') ); <del> const configureTrustedDomainsHandler = async ( quickInputService: IQuickInputService, storageService: IStorageService, linkProtectionTrustedDomains: string[], domainToConfigure?: string ) => { try { const trustedDomainsSrc = storageService.get('http.linkProtectionTrustedDomains', StorageScope.GLOBAL); if (trustedDomainsSrc) { linkProtectionTrustedDomains = JSON.parse(trustedDomainsSrc); } } catch (err) { } const domainQuickPickItems: IQuickPickItem[] = linkProtectionTrustedDomains .filter(d => d !== '*') .map(d => { return { type: 'item', label: d, id: d, picked: true }; }); const specialQuickPickItems: IQuickPickItem[] = [ { type: 'item', label: localize('openAllLinksWithoutPrompt', 'Open all links without prompt'), id: '*', picked: linkProtectionTrustedDomains.indexOf('*') !== -1 } ]; let domainToConfigureItem: IQuickPickItem | undefined = undefined; if (domainToConfigure && linkProtectionTrustedDomains.indexOf(domainToConfigure) === -1) { domainToConfigureItem = { type: 'item', label: domainToConfigure, id: domainToConfigure, picked: true, description: localize('trustDomainAndOpenLink', 'Trust domain and open link') }; specialQuickPickItems.push(<IQuickPickItem>domainToConfigureItem); } const quickPickItems: (IQuickPickItem | IQuickPickSeparator)[] = domainQuickPickItems.length === 0 ? specialQuickPickItems : [...specialQuickPickItems, { type: 'separator' }, ...domainQuickPickItems]; const pickedResult = await quickInputService.pick(quickPickItems, { canPickMany: true, activeItem: domainToConfigureItem }); if (pickedResult) { const pickedDomains: string[] = pickedResult.map(r => r.id!); storageService.store('http.linkProtectionTrustedDomains', JSON.stringify(pickedDomains), StorageScope.GLOBAL); return pickedDomains; } return []; }; const configureTrustedDomainCommand = { id: 'workbench.action.configureLinkProtectionTrustedDomains', description: { description: localize('configureLinkProtectionTrustedDomains', 'Configure Trusted Domains for Link Protection'), args: [{ name: 'domainToConfigure', schema: { type: 'string' } }] }, handler: (accessor: ServicesAccessor, domainToConfigure?: string) => { const quickInputService = accessor.get(IQuickInputService); const storageService = accessor.get(IStorageService); const productService = accessor.get(IProductService); const trustedDomains = productService.linkProtectionTrustedDomains ? [...productService.linkProtectionTrustedDomains] : []; return configureTrustedDomainsHandler(quickInputService, storageService, trustedDomains, domainToConfigure); } }; </del> <ins> /** * Trusted Domains Contribution */ </ins> <del> CommandsRegistry.registerCommand(configureTrustedDomainCommand); </del> <ins> CommandsRegistry.registerCommand(configureTrustedDomainSettingsCommand); </ins> MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { <del> id: configureTrustedDomainCommand.id, title: configureTrustedDomainCommand.description.description </del> <ins> id: configureTrustedDomainSettingsCommand.id, title: configureTrustedDomainSettingsCommand.description.description </ins> } }); <del> class OpenerValidatorContributions implements IWorkbenchContribution { constructor( @IOpenerService private readonly _openerService: IOpenerService, @IStorageService private readonly _storageService: IStorageService, @IDialogService private readonly _dialogService: IDialogService, @IProductService private readonly _productService: IProductService, @IQuickInputService private readonly _quickInputService: IQuickInputService ) { this._openerService.registerValidator({ shouldOpen: r => this.validateLink(r) }); </del> <ins> CommandsRegistry.registerCommand(toggleLinkProtection); MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: toggleLinkProtection.id, title: toggleLinkProtection.description.description </ins> } <ins> }); </ins> <del> async validateLink(resource: URI): Promise<boolean> { const { scheme, authority } = resource; if (!equalsIgnoreCase(scheme, Schemas.http) && !equalsIgnoreCase(scheme, Schemas.https)) { return true; } let trustedDomains: string[] = this._productService.linkProtectionTrustedDomains ? [...this._productService.linkProtectionTrustedDomains] : []; try { const trustedDomainsSrc = this._storageService.get('http.linkProtectionTrustedDomains', StorageScope.GLOBAL); if (trustedDomainsSrc) { trustedDomains = JSON.parse(trustedDomainsSrc); } } catch (err) { } const domainToOpen = `${scheme}://${authority}`; if (isURLDomainTrusted(resource, trustedDomains)) { return true; } else { const { choice } = await this._dialogService.show( Severity.Info, localize( 'openExternalLinkAt', 'Do you want {0} to open the external website?n{1}', this._productService.nameShort, resource.toString(true) ), [ localize('openLink', 'Open Link'), localize('cancel', 'Cancel'), localize('configureTrustedDomains', 'Configure Trusted Domains') ], { cancelId: 1 } ); // Open Link if (choice === 0) { return true; } // Configure Trusted Domains else if (choice === 2) { const pickedDomains = await configureTrustedDomainsHandler( this._quickInputService, this._storageService, trustedDomains, domainToOpen ); if (pickedDomains.indexOf(domainToOpen) !== -1) { return true; } return false; } return false; } } } </del> Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench).registerWorkbenchContribution( OpenerValidatorContributions, LifecyclePhase.Restored ); <del> const rLocalhost = /^localhost(:d+)?$/i; const r127 = /^127.0.0.1(:d+)?$/; function isLocalhostAuthority(authority: string) { return rLocalhost.test(authority) || r127.test(authority); } /** * Check whether a domain like https://www.microsoft.com matches * the list of trusted domains. * * - Schemes must match * - There's no subdomain matching. For example https://microsoft.com doesn't match https://www.microsoft.com * - Star matches all. For example https://*.microsoft.com matches https://www.microsoft.com */ export function isURLDomainTrusted(url: URI, trustedDomains: string[]) { if (isLocalhostAuthority(url.authority)) { return true; } const domain = `${url.scheme}://${url.authority}`; for (let i = 0; i < trustedDomains.length; i++) { if (trustedDomains[i] === '*') { return true; } if (trustedDomains[i] === domain) { return true; } if (trustedDomains[i].indexOf('*') !== -1) { const parsedTrustedDomain = URI.parse(trustedDomains[i]); if (url.scheme === parsedTrustedDomain.scheme) { const authoritySegments = url.authority.split('.'); const trustedDomainAuthoritySegments = parsedTrustedDomain.authority.split('.'); if (authoritySegments.length === trustedDomainAuthoritySegments.length) { if ( authoritySegments.every( (val, i) => trustedDomainAuthoritySegments[i] === '*' || val === trustedDomainAuthoritySegments[i] ) ) { return true; } } } } } return false; } </del> <ins> Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench).registerWorkbenchContribution( TrustedDomainsFileSystemProvider, LifecyclePhase.Ready ); </ins>

import * as assert from 'assert'; <del> import { isURLDomainTrusted } from 'vs/workbench/contrib/url/common/url.contribution'; </del> <ins> import { isURLDomainTrusted } from 'vs/workbench/contrib/url/common/trustedDomainsValidator'; </ins> import { URI } from 'vs/base/common/uri'; suite('Link protection domain matching', () => {

test('* star', () => { assert.ok(isURLDomainTrusted(URI.parse('https://a.x.org'), ['https://*.x.org'])); <ins> assert.ok(isURLDomainTrusted(URI.parse('https://a.b.x.org'), ['https://*.x.org'])); </ins> assert.ok(isURLDomainTrusted(URI.parse('https://a.x.org'), ['https://a.x.*'])); assert.ok(isURLDomainTrusted(URI.parse('https://a.x.org'), ['https://a.*.org'])); assert.ok(isURLDomainTrusted(URI.parse('https://a.x.org'), ['https://*.*.org'])); <del> assert.ok(!isURLDomainTrusted(URI.parse('https://a.b.c.org'), ['https://*.*.org'])); assert.ok(isURLDomainTrusted(URI.parse('https://a.b.c.org'), ['https://*.*.*.org'])); </del> <ins> assert.ok(isURLDomainTrusted(URI.parse('https://a.b.x.org'), ['https://*.b.*.org'])); assert.ok(isURLDomainTrusted(URI.parse('https://a.a.b.x.org'), ['https://*.b.*.org'])); </ins> }); });