Skip to content

Commit 68957a6

Browse files
add ability for embedders to specify urls that should open in a popup with opener (microsoft#136383)
1 parent 1c0f29d commit 68957a6

File tree

3 files changed

+59
-10
lines changed

3 files changed

+59
-10
lines changed

src/vs/base/browser/dom.ts

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1182,7 +1182,7 @@ export function computeScreenAwareSize(cssPx: number): number {
11821182
/**
11831183
* Open safely a new window. This is the best way to do so, but you cannot tell
11841184
* if the window was opened or if it was blocked by the browser's popup blocker.
1185-
* If you want to tell if the browser blocked the new window, use `windowOpenNoOpenerWithSuccess`.
1185+
* If you want to tell if the browser blocked the new window, use {@link windowOpenWithSuccess}.
11861186
*
11871187
* See https://github.com/microsoft/monaco-editor/issues/601
11881188
* To protect against malicious code in the linked site, particularly phishing attempts,
@@ -1201,19 +1201,49 @@ export function windowOpenNoOpener(url: string): void {
12011201
}
12021202

12031203
/**
1204-
* Open safely a new window. This technique is not appropriate in certain contexts,
1205-
* like for example when the JS context is executing inside a sandboxed iframe.
1206-
* If it is not necessary to know if the browser blocked the new window, use
1207-
* `windowOpenNoOpener`.
1204+
* Open a new window in a popup. This is the best way to do so, but you cannot tell
1205+
* if the window was opened or if it was blocked by the browser's popup blocker.
1206+
* If you want to tell if the browser blocked the new window, use {@link windowOpenWithSuccess}.
1207+
*
1208+
* Note: this does not set {@link window.opener} to null. This is to allow the opened popup to
1209+
* be able to use {@link window.close} to close itself. Because of this, you should only use
1210+
* this function on urls that you trust.
1211+
*
1212+
* In otherwords, you should almost always use {@link windowOpenNoOpener} instead of this function.
1213+
*/
1214+
const popupWidth = 780, popupHeight = 640;
1215+
export function windowOpenPopup(url: string): void {
1216+
const left = Math.floor(window.screenLeft + window.innerWidth / 2 - popupWidth / 2);
1217+
const top = Math.floor(window.screenTop + window.innerHeight / 2 - popupHeight / 2);
1218+
window.open(
1219+
url,
1220+
'_blank',
1221+
`width=${popupWidth},height=${popupHeight},top=${top},left=${left}`
1222+
);
1223+
}
1224+
1225+
/**
1226+
* Attempts to open a window and returns whether it succeeded. This technique is
1227+
* not appropriate in certain contexts, like for example when the JS context is
1228+
* executing inside a sandboxed iframe. If it is not necessary to know if the
1229+
* browser blocked the new window, use {@link windowOpenNoOpener}.
12081230
*
12091231
* See https://github.com/microsoft/monaco-editor/issues/601
12101232
* See https://github.com/microsoft/monaco-editor/issues/2474
12111233
* See https://mathiasbynens.github.io/rel-noopener/
1234+
*
1235+
* @param url the url to open
1236+
* @param noOpener whether or not to set the {@link window.opener} to null. You should leave the default
1237+
* (true) unless you trust the url that is being opened.
1238+
* @returns boolean indicating if the {@link window.open} call succeeded
12121239
*/
1213-
export function windowOpenNoOpenerWithSuccess(url: string): boolean {
1240+
export function windowOpenWithSuccess(url: string, noOpener = true): boolean {
12141241
const newTab = window.open();
12151242
if (newTab) {
1216-
(newTab as any).opener = null;
1243+
if (noOpener) {
1244+
// see `windowOpenNoOpener` for details on why this is important
1245+
(newTab as any).opener = null;
1246+
}
12171247
newTab.location.href = url;
12181248
return true;
12191249
}

src/vs/workbench/browser/window.ts

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
*--------------------------------------------------------------------------------------------*/
55

66
import { isSafari, setFullscreen } from 'vs/base/browser/browser';
7-
import { addDisposableListener, addDisposableThrottledListener, detectFullscreen, EventHelper, EventType, windowOpenNoOpenerWithSuccess, windowOpenNoOpener } from 'vs/base/browser/dom';
7+
import { addDisposableListener, addDisposableThrottledListener, detectFullscreen, EventHelper, EventType, windowOpenNoOpener, windowOpenPopup, windowOpenWithSuccess } from 'vs/base/browser/dom';
88
import { DomEmitter } from 'vs/base/browser/event';
99
import { timeout } from 'vs/base/common/async';
1010
import { Event } from 'vs/base/common/event';
@@ -141,10 +141,20 @@ export class BrowserWindow extends Disposable {
141141
}
142142
}
143143

144+
let isAllowedOpener = false;
145+
if (this.environmentService.options?.openerAllowedExternalUrlPrefixes) {
146+
for (const trustedPopupPrefix of this.environmentService.options.openerAllowedExternalUrlPrefixes) {
147+
if (href.startsWith(trustedPopupPrefix)) {
148+
isAllowedOpener = true;
149+
break;
150+
}
151+
}
152+
}
153+
144154
// HTTP(s): open in new window and deal with potential popup blockers
145155
if (matchesScheme(href, Schemas.http) || matchesScheme(href, Schemas.https)) {
146156
if (isSafari) {
147-
const opened = windowOpenNoOpenerWithSuccess(href);
157+
const opened = windowOpenWithSuccess(href, !isAllowedOpener);
148158
if (!opened) {
149159
const showResult = await this.dialogService.show(
150160
Severity.Warning,
@@ -161,7 +171,9 @@ export class BrowserWindow extends Disposable {
161171
);
162172

163173
if (showResult.choice === 0) {
164-
windowOpenNoOpener(href);
174+
isAllowedOpener
175+
? windowOpenPopup(href)
176+
: windowOpenNoOpener(href);
165177
}
166178

167179
if (showResult.choice === 1) {

src/vs/workbench/workbench.web.api.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -448,6 +448,13 @@ interface IWorkbenchConstructionOptions {
448448
*/
449449
readonly additionalTrustedDomains?: string[];
450450

451+
/**
452+
* Urls that will be opened externally that are allowed access
453+
* to the opener window. This is primarily used to allow
454+
* `window.close()` to be called from the newly opened window.
455+
*/
456+
readonly openerAllowedExternalUrlPrefixes?: string[];
457+
451458
/**
452459
* Support for URL callbacks.
453460
*/

0 commit comments

Comments
 (0)