Path: blob/trunk/third_party/closure/goog/window/window.js
4049 views
/**1* @license2* Copyright The Closure Library Authors.3* SPDX-License-Identifier: Apache-2.04*/56/**7* @fileoverview Utilities for window manipulation.8*/91011goog.provide('goog.window');1213goog.require('goog.dom');14goog.require('goog.dom.TagName');15goog.require('goog.dom.safe');16goog.require('goog.html.SafeUrl');17goog.require('goog.html.uncheckedconversions');18goog.require('goog.labs.userAgent.platform');19goog.require('goog.string');20goog.require('goog.string.Const');21goog.require('goog.userAgent');22goog.requireType('goog.string.TypedString');232425/**26* Default height for popup windows27* @type {number}28*/29goog.window.DEFAULT_POPUP_HEIGHT = 500;303132/**33* Default width for popup windows34* @type {number}35*/36goog.window.DEFAULT_POPUP_WIDTH = 690;373839/**40* Default target for popup windows41* @type {string}42*/43goog.window.DEFAULT_POPUP_TARGET = 'google_popup';444546/**47* @return {!Window}48* @suppress {checkTypes}49* @private50*/51goog.window.createFakeWindow_ = function() {52'use strict';53return /** @type {!Window} */ ({});54};5556/**57* Opens a new window.58*59* @param {!goog.html.SafeUrl|string|!Object|null} linkRef If an Object with an60* 'href' attribute (such as HTMLAnchorElement) is passed then the value of61* 'href' is used, otherwise its toString method is called. Note that if a62* string|Object is used, it will be sanitized with SafeUrl.sanitize().63*64* @param {?Object=} opt_options supports the following options:65* 'target': (string) target (window name). If null, linkRef.target will66* be used.67* 'width': (number) window width.68* 'height': (number) window height.69* 'top': (number) distance from top of screen70* 'left': (number) distance from left of screen71* 'toolbar': (boolean) show toolbar72* 'scrollbars': (boolean) show scrollbars73* 'location': (boolean) show location74* 'statusbar': (boolean) show statusbar75* 'menubar': (boolean) show menubar76* 'resizable': (boolean) resizable77* 'noreferrer': (boolean) whether to attempt to remove the referrer header78* from the request headers. Does this by opening a blank window that79* then redirects to the target url, so users may see some flickering.80* 'noopener': (boolean) whether to remove the `opener` property from the81* window object of the newly created window. The property contains a82* reference to the original window, and can be used to launch a83* reverse tabnabbing attack.84*85* @param {?Window=} opt_parentWin Parent window that should be used to open the86* new window.87*88* @return {?Window} Returns the window object that was opened. This returns89* null if a popup blocker prevented the window from being90* opened. In case when a new window is opened in a different91* browser sandbox (such as iOS standalone mode), the returned92* object is a emulated Window object that functions as if93* a cross-origin window has been opened.94*/95goog.window.open = function(linkRef, opt_options, opt_parentWin) {96'use strict';97if (!opt_options) {98opt_options = {};99}100var parentWin = opt_parentWin || window;101102/** @type {!goog.html.SafeUrl} */103var safeLinkRef;104105if (linkRef instanceof goog.html.SafeUrl) {106safeLinkRef = linkRef;107} else {108// HTMLAnchorElement has a toString() method with the same behavior as109// goog.Uri in all browsers except for Safari, which returns110// '[object HTMLAnchorElement]'. We check for the href first, then111// assume that it's a goog.Uri or String otherwise.112/**113* @type {string|!goog.string.TypedString}114* @suppress {missingProperties}115*/116var url =117typeof linkRef.href != 'undefined' ? linkRef.href : String(linkRef);118safeLinkRef = goog.html.SafeUrl.sanitize(url);119}120121/** @suppress {strictMissingProperties} */122const browserSupportsCoop = self.crossOriginIsolated !== undefined;123let referrerPolicy = 'strict-origin-when-cross-origin';124if (window.Request) {125/** @suppress {missingProperties} */126referrerPolicy = new Request('/').referrerPolicy;127}128const pageSetsUnsafeReferrerPolicy = referrerPolicy === 'unsafe-url';129130// Opening popups with `noreferrer` and a COOP policy of131// `same-origin-allow-popups` doesn't work. The below is a workaround132// for this browser limitation.133let noReferrerOption = opt_options['noreferrer'];134if (browserSupportsCoop && noReferrerOption) {135if (pageSetsUnsafeReferrerPolicy) {136// If the browser supports COOP, and the page explicitly sets a137// referrer-policy of `unsafe-url`, and the caller requests that the138// referrer is hidden, then things may break. We can't support the139// noreferrer option in this case, but ignoring it is potentially unsafe140// since the page is configured to expose the full URL. We just throw in141// this case so that callers can make a decision as to what they want to142// do here.143throw new Error(144'Cannot use the noreferrer option on a page that sets a referrer-policy of `unsafe-url` in modern browsers!');145}146// Any browser that supports COOP defaults to a referrer policy that hides147// the full URL. So we don't need to explicitly hide the referrer ourselves148// and can instead rely on the browser's default referrer policy to hide the149// referrer.150noReferrerOption = false;151}152153/** @suppress {missingProperties} loose references to 'target' */154/** @suppress {strictMissingProperties} */155var target = opt_options.target || linkRef.target;156157var sb = [];158for (var option in opt_options) {159switch (option) {160case 'width':161case 'height':162case 'top':163case 'left':164sb.push(option + '=' + opt_options[option]);165break;166case 'target':167case 'noopener':168case 'noreferrer':169break;170default:171sb.push(option + '=' + (opt_options[option] ? 1 : 0));172}173}174var optionString = sb.join(',');175176var newWin;177if (goog.labs.userAgent.platform.isIos() && parentWin.navigator &&178parentWin.navigator['standalone'] && target && target != '_self') {179// iOS in standalone mode disregards "target" in window.open and always180// opens new URL in the same window. The workaround is to create an "A"181// element and send a click event to it.182// Notice that the "A" tag does NOT have to be added to the DOM.183184var a = goog.dom.createElement(goog.dom.TagName.A);185goog.dom.safe.setAnchorHref(a, safeLinkRef);186187a.target = target;188if (noReferrerOption) {189a.rel = 'noreferrer';190}191192var click = /** @type {!MouseEvent} */ (document.createEvent('MouseEvent'));193click.initMouseEvent(194'click',195true, // canBubble196true, // cancelable197parentWin,1981); // detail = mousebutton199a.dispatchEvent(click);200// New window is not available in this case. Instead, a fake Window object201// is returned. In particular, it will have window.document undefined. In202// general, it will appear to most of clients as a Window for a different203// origin. Since iOS standalone web apps are run in their own sandbox, this204// is the most appropriate return value.205newWin = goog.window.createFakeWindow_();206} else if (noReferrerOption) {207// This code used to use meta-refresh to stop the referrer from being208// included in the request headers. This was the only cross-browser way209// to remove the referrer circa 2009. However, this never worked in Chrome,210// and, instead newWin.opener had to be set to null on this browser. This211// behavior is slated to be removed in Chrome and should not be relied212// upon. Referrer Policy is the only spec'd and supported way of stripping213// referrers and works across all current browsers. This is used in214// addition to the aforementioned tricks.215//216// We also set the opener to be set to null in the new window, thus217// disallowing the opened window from navigating its opener.218//219// Detecting user agent and then using a different strategy per browser220// would allow the referrer to leak in case of an incorrect/missing user221// agent.222newWin = goog.dom.safe.openInWindow('', parentWin, target, optionString);223224var sanitizedLinkRef = goog.html.SafeUrl.unwrap(safeLinkRef);225if (newWin) {226if (goog.userAgent.EDGE_OR_IE) {227// IE/EDGE can't parse the content attribute if the url contains228// a semicolon. We can fix this by adding quotes around the url, but229// then we can't parse quotes in the URL correctly. We take a230// best-effort approach.231//232// If the URL has semicolons, wrap it in single quotes to protect233// the semicolons.234// If the URL has semicolons and single quotes, url-encode the single235// quotes as well.236//237// This is imperfect. Notice that both ' and ; are reserved characters238// in URIs, so this could do the wrong thing, but at least it will239// do the wrong thing in only rare cases.240// ugh.241if (goog.string.contains(sanitizedLinkRef, ';')) {242sanitizedLinkRef =243'\'' + sanitizedLinkRef.replace(/'/g, '%27') + '\'';244}245}246newWin.opener = null;247// Using a blank value for the URL causes the new window to load248// this window's location. Instead, using this javascript URL causes an249// error to be thrown in the blank document and abort the loading of the250// page location. The window's location does update, but the content is251// never requested/loaded.252// Other values tried here include:253// - an empty string or no value at all (page load succeeds)254// - 'about:blank' (causes security exceptions if users255// later try and assign to the window's location, as about:blank is now256// cross-origin from this window).257if (sanitizedLinkRef === '') {258sanitizedLinkRef = 'javascript:\'\'';259}260// TODO(rjamet): Building proper SafeHtml with SafeHtml.createMetaRefresh261// pulls in a lot of compiled code, which is composed of various unneeded262// goog.html parts such as SafeStyle.create among others. So, for now,263// keep the unchecked conversion until we figure out how to make the264// dependencies of createSafeHtmlTagSecurityPrivateDoNotAccessOrElse less265// heavy.266var safeHtml =267goog.html.uncheckedconversions268.safeHtmlFromStringKnownToSatisfyTypeContract(269goog.string.Const.from(270'b/12014412, meta tag with sanitized URL'),271'<meta name="referrer" content="no-referrer">' +272'<meta http-equiv="refresh" content="0; url=' +273goog.string.htmlEscape(sanitizedLinkRef) + '">');274275// During window loading `newWin.document` may be unset in some browsers.276// Storing and checking a reference to the document prevents NPEs.277var newDoc = newWin.document;278if (newDoc && newDoc.write) {279goog.dom.safe.documentWrite(newDoc, safeHtml);280newDoc.close();281}282}283} else {284newWin = goog.dom.safe.openInWindow(285safeLinkRef, parentWin, target, optionString);286// Passing in 'noopener' into the 'windowFeatures' param of window.open(...)287// will yield a feature-deprived browser. This is an known issue, tracked288// here: https://github.com/whatwg/html/issues/1902289if (newWin && opt_options['noopener']) {290newWin.opener = null;291}292// If the caller specified noreferrer and we hit this branch, it means that293// we're already running on a modern enough browser that the referrer is294// hidden by default. But setting noreferrer implies noopener too, so we295// also have to clear the opener here.296if (newWin && opt_options['noreferrer']) {297newWin.opener = null;298}299}300// newWin is null if a popup blocker prevented the window open.301return newWin;302};303304305/**306* Opens a new window without any real content in it.307*308* This can be used to get around popup blockers if you need to open a window309* in response to a user event, but need to do asynchronous work to determine310* the URL to open, and then set the URL later.311*312* Example usage:313*314* var newWin = goog.window.openBlank('Loading...');315* setTimeout(316* function() {317* newWin.location.href = 'http://www.google.com';318* }, 100);319*320* @param {string=} opt_message String to show in the new window. This string321* will be HTML-escaped to avoid XSS issues.322* @param {?Object=} opt_options Options to open window with.323* {@see goog.window.open for exact option semantics}.324* @param {?Window=} opt_parentWin Parent window that should be used to open the325* new window.326* @return {?Window} Returns the window object that was opened. This returns327* null if a popup blocker prevented the window from being328* opened.329*/330goog.window.openBlank = function(opt_message, opt_options, opt_parentWin) {331'use strict';332const win =333/** @type {?Window} */ (goog.window.open('', opt_options, opt_parentWin));334if (win && opt_message) {335const body = win.document.body;336if (body) {337// The body can be undefined in IE, where for some reason the created338// document doesn't have a body.339body.textContent = opt_message;340}341}342return win;343};344345346/**347* Raise a help popup window, defaulting to "Google standard" size and name.348*349* (If your project is using GXPs, consider using {@link PopUpLink.gxp}.)350*351* @param {?goog.html.SafeUrl|string|?Object} linkRef If an Object with an352* 'href' attribute (such as HTMLAnchorElement) is passed then the value of353* 'href' is used, otherwise otherwise its toString method is called. Note354* that if a string|Object is used, it will be sanitized with355* SafeUrl.sanitize().356*357* @param {?Object=} opt_options Options to open window with.358* {@see goog.window.open for exact option semantics}359* Additional wrinkles to the options:360* - if 'target' field is null, linkRef.target will be used. If *that's*361* null, the default is "google_popup".362* - if 'width' field is not specified, the default is 690.363* - if 'height' field is not specified, the default is 500.364*365* @return {boolean} true if the window was not popped up, false if it was.366*/367goog.window.popup = function(linkRef, opt_options) {368'use strict';369if (!opt_options) {370opt_options = {};371}372373// set default properties374opt_options['target'] = opt_options['target'] || linkRef['target'] ||375goog.window.DEFAULT_POPUP_TARGET;376opt_options['width'] =377opt_options['width'] || goog.window.DEFAULT_POPUP_WIDTH;378opt_options['height'] =379opt_options['height'] || goog.window.DEFAULT_POPUP_HEIGHT;380381var newWin = goog.window.open(linkRef, opt_options);382if (!newWin) {383return true;384}385newWin.focus();386387return false;388};389390391