Change:
<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>

Discussion:
<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

Issue Type: <bFeature Request</bPlease allow us to delete trusted domains for link protection. Selecting a domain and hitting Backspace and Delete don't do anything. Even better would be to have an option to reset the list of links. VS Code version: Code 1.38.0 (, 2019-09-03T21:47:00.464Z) OS version: Darwin x64 18.7.0 <!-- generated by issue reporter --
(Experimental duplicate detection) Thanks for submitting this issue. Please also check if it is already covered by an existing one, like: <!-- score: 0.541 --- <!-- score: 0.509 --- <!-- score: 0.492 --<!-- potentialduplicatescomment --
If you run , you can toggle off domains to delete them. Does that not work for you?
Okay thanks, that does indeed work. I thought it would just be a simple toggle. Where it this data located? It would be nice to have it as an array in the VSC , like most of the other permanent settings.
For security purposes, we decided to not put it in , which is writable by any extensions.
Is there any way to reset the list, other than doing it manually?
Double click the upper left checkbox should unselect all — or do you want reset instead of clear?
I would like an option to reset the list, so that if I a bunch of custom URLs, then resetting the list would remove my custom URLs, and re-add any of the default URLs that I removed, if any.
See

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