Change:
<img width="607" alt="image" src="https://user-"When clicking on "Configure Trusted Domains" you are sent to the picker which shows domain that you just clicked on in the list and checked: <img width="626" alt="image" src="https://user-"This leaves you in a very confused state because it is now checked. So the natural thinking is since it's checked, why did it ask me to open the link? So I cancel the quickpick, click the link again and I get the same message. After inspecting it further, it appears that the "Configure Trusted Domains" actually adds the entry and you have to click on "Ok" to apply it. This is extremely confusing as the user has no idea this was , this list is always treated as a static list. Additionally, looking back on our notes from the last ux sync, I thought we all agreed the actions would be: Trust Once, Trust Always, Cancel. And the "Configure" part was going to be a setting? It appears like this isn't reflected at all.
That came out of discussion at Would it make sense to make the message a bit different so it stands out? For example, everything below the separator -already trusted domains. Top two options are: Open all links without prompt Trust new domain and open link (and remove the details)
I don't think the message would improve the UX. Something that could help/improve is to have an action to add a domain when results are empty: <img width="651" alt="image" src="https://user-"To me, "Configure Trusted Domains" does not equal "Add this domain" so we either should update the button text to indicate it's also adding, and then I'd auto-fill the input field to indicate that it's adding.
See 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> }); });