fix(ui): openURL() should not invoke rejectFn when noopener is set (#18221)#18229
fix(ui): openURL() should not invoke rejectFn when noopener is set (#18221)#18229arosstale wants to merge 1 commit intoquasarframework:devfrom
Conversation
When 'noopener' is present in the window features (which is the default), `window.open()` intentionally returns `null` even though the window was successfully opened — the browser withholds the reference for security. Checking `win === null` as a failure signal is therefore incorrect and causes `rejectFn` to always fire on successful opens. Fix: only call `reject` when noopener is not in effect, so null can be treated as a genuine blocked-popup signal. Fixes quasarframework#18221
There was a problem hiding this comment.
Pull request overview
Fixes openURL()’s popup-block detection so that rejectFn is not called when window.open() returns null due to noopener being enabled (default behavior for security).
Changes:
- Track whether
noopeneris in effect and only callrejectFnwhen it is explicitly disabled. - Add inline documentation explaining why
window.open()can returnnulleven on success.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // When "noopener" is set (default), window.open() intentionally returns null | ||
| // even when the window was successfully opened. Only reject if noopener is | ||
| // not in effect, so that the null return can be treated as a blocked popup. | ||
| const cfg = Object.assign({ noopener: true }, windowFeatures) | ||
| const hasNoopener = cfg.noopener === true | ||
|
|
||
| const win = open(url, '_blank', parseFeatures(windowFeatures)) |
There was a problem hiding this comment.
hasNoopener only checks cfg.noopener === true, but parseFeatures() can also include noreferrer (and in browsers noreferrer implies noopener). If a caller sets { noopener: false, noreferrer: true }, window.open() may still return null even when the popup opened, and this code would call reject?.() again. Consider treating noreferrer === true as equivalent to noopener being in effect (or folding both into a single hasNoopenerLikeProtection flag).
| // even when the window was successfully opened. Only reject if noopener is | ||
| // not in effect, so that the null return can be treated as a blocked popup. | ||
| const cfg = Object.assign({ noopener: true }, windowFeatures) | ||
| const hasNoopener = cfg.noopener === true |
There was a problem hiding this comment.
hasNoopener is computed with a strict === true check, but parseFeatures() will treat non-boolean values like 1 or 'yes' as enabled and serialize them (e.g. noopener=1). If such values are passed, window.open() can still return null while this code would think noopener is off and call reject?.(). Consider defining the flag as cfg.noopener !== false (or otherwise matching the same semantics as parseFeatures()).
| const hasNoopener = cfg.noopener === true | |
| const hasNoopener = cfg.noopener !== false |
What kind of change does this PR introduce?
Does this PR introduce a breaking change?
The PR fulfills these requirements:
devbranchProblem
openURL(url, rejectFn)always callsrejectFneven when the window was successfully opened.Root cause: when
noopeneris included in the window features (which is the default — added for security inparseFeatures), the browser intentionally returnsnullfromwindow.open()even though the window opened successfully. The caller has no reference to it for security reasons. The current code treatsnullas a failure and calls `reject?.().Fix
Track whether
noopeneris in effect (it is by default). Only callrejectwhennoopenerisfalse, so anullreturn can still be treated as a genuine blocked-popup signal in that case.Callers that explicitly pass
{ noopener: false }inwindowFeaturesstill getrejectFncalled on genuine popup-blocked cases.Other information: Reproducer: https://codepen.io/escodoy/pen/bNeamxG