window_test.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384
  1. // Copyright 2009 The Closure Library Authors. All Rights Reserved.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS-IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. goog.provide('goog.windowTest');
  15. goog.setTestOnly('goog.windowTest');
  16. goog.require('goog.Promise');
  17. goog.require('goog.dom');
  18. goog.require('goog.dom.TagName');
  19. goog.require('goog.events');
  20. goog.require('goog.functions');
  21. goog.require('goog.html.SafeUrl');
  22. goog.require('goog.labs.userAgent.browser');
  23. goog.require('goog.labs.userAgent.engine');
  24. goog.require('goog.labs.userAgent.platform');
  25. goog.require('goog.string');
  26. goog.require('goog.testing.PropertyReplacer');
  27. goog.require('goog.testing.TestCase');
  28. goog.require('goog.testing.jsunit');
  29. goog.require('goog.window');
  30. var newWin;
  31. var REDIRECT_URL_PREFIX = 'window_test.html?runTests=';
  32. var WIN_LOAD_TRY_TIMEOUT = 100;
  33. var MAX_WIN_LOAD_TRIES = 50; // 50x100ms = 5s waiting for window to load.
  34. var stubs = new goog.testing.PropertyReplacer();
  35. function shouldRunTests() {
  36. // MS Edge has a bunch of flaky test failures around window.open.
  37. // TODO(joeltine): Remove this when http://b/25455129 is fixed.
  38. return !goog.labs.userAgent.browser.isEdge();
  39. }
  40. function setUpPage() {
  41. var anchors = goog.dom.getElementsByTagNameAndClass(
  42. goog.dom.TagName.DIV, 'goog-like-link');
  43. for (var i = 0; i < anchors.length; i++) {
  44. goog.events.listen(anchors[i], 'click', function(e) {
  45. goog.window.open(goog.dom.getTextContent(e.target), {'noreferrer': true});
  46. });
  47. }
  48. goog.testing.TestCase.getActiveTestCase().promiseTimeout = 60000; // 60s
  49. }
  50. // To test goog.window.open we open a new window with this file again. Once
  51. // the new window parses this file it sets this variable to true, indicating
  52. // that the parent test may check window properties like referrer and location.
  53. var newWinLoaded = true;
  54. function setUp() {
  55. newWin = undefined;
  56. }
  57. function tearDown() {
  58. if (newWin) {
  59. newWin.close();
  60. }
  61. stubs.reset();
  62. }
  63. /**
  64. * Uses setTimeout to poll a new window for the "newWinLoaded" variable, which
  65. * is set once the JavaScript is evaluated in that window.
  66. *
  67. * @param {Window} win
  68. * @return {!goog.Promise<!Window>} Promise for a window that resolves once the
  69. * window has loaded.
  70. */
  71. function waitForTestWindow(win) {
  72. return new goog.Promise(function(resolve, reject) {
  73. var checkWindow = function(numTries) {
  74. if (!win) {
  75. fail('Could not open new window. Check if popup blocker is enabled.');
  76. }
  77. if (numTries > MAX_WIN_LOAD_TRIES) {
  78. fail('Window did not load after maximum number of checks.');
  79. }
  80. if (win.newWinLoaded) {
  81. resolve(win);
  82. } else {
  83. window.setTimeout(checkWindow, WIN_LOAD_TRY_TIMEOUT);
  84. }
  85. };
  86. checkWindow(0);
  87. });
  88. }
  89. /**
  90. * Opens a window and then verifies that the new window has the expected
  91. * properties.
  92. *
  93. * @param {boolean} noreferrer Whether to test the noreferrer option.
  94. * @param {string} urlParam Url param to append to the url being opened.
  95. * @param {boolean} encodeUrlParam_opt Whether to percent-encode urlParam. This
  96. * is needed because IE will not encode it automatically like other browsers
  97. * browser and the Closure test server will 400 on certain characters in
  98. * the URL (like '<' and '"').
  99. * @return {!goog.Promise} Promise that resolves once the test is complete.
  100. */
  101. function doTestOpenWindow(noreferrer, urlParam, encodeUrlParam_opt) {
  102. if (encodeUrlParam_opt) {
  103. urlParam = encodeURIComponent(urlParam);
  104. }
  105. // TODO(mlourenco): target is set because goog.window.open() will currently
  106. // allow it to be undefined, which in IE seems to result in the same window
  107. // being reused, instead of a new one being created. If goog.window.open()
  108. // is fixed to use "_blank" by default then target can be removed here.
  109. newWin = goog.window.open(
  110. REDIRECT_URL_PREFIX + urlParam,
  111. {'noreferrer': noreferrer, 'target': '_blank'});
  112. return waitForTestWindow(newWin).then(function(win) {
  113. verifyWindow(win, noreferrer, urlParam);
  114. });
  115. }
  116. /**
  117. * Asserts that a newly created window has the correct parameters.
  118. *
  119. * @param {Window} win
  120. * @param {boolean} noreferrer Whether the noreferrer option is being tested.
  121. * @param {string} urlParam Url param appended to the url being opened.
  122. */
  123. function verifyWindow(win, noreferrer, urlParam) {
  124. if (noreferrer) {
  125. assertEquals(
  126. 'Referrer should have been stripped', '', win.document.referrer);
  127. }
  128. var winUrl = decodeURI(win.location);
  129. var expectedUrlSuffix = decodeURI(urlParam);
  130. assertTrue(
  131. 'New window href should have ended with <' + expectedUrlSuffix +
  132. '> but was <' + winUrl + '>',
  133. goog.string.endsWith(winUrl, expectedUrlSuffix));
  134. }
  135. function testOpenNotEncoded() {
  136. return doTestOpenWindow(false, 'bogus~');
  137. }
  138. function testOpenEncoded() {
  139. return doTestOpenWindow(false, 'bogus%7E');
  140. }
  141. function testOpenEncodedPercent() {
  142. // Intent of url is to pass %7E to the server, so it was encoded to %257E .
  143. return doTestOpenWindow(false, 'bogus%257E');
  144. }
  145. function testOpenNotEncodedHidingReferrer() {
  146. return doTestOpenWindow(true, 'bogus~');
  147. }
  148. function testOpenEncodedHidingReferrer() {
  149. return doTestOpenWindow(true, 'bogus%7E');
  150. }
  151. function testOpenEncodedPercentHidingReferrer() {
  152. // Intent of url is to pass %7E to the server, so it was encoded to %257E .
  153. return doTestOpenWindow(true, 'bogus%257E');
  154. }
  155. function testOpenSemicolon() {
  156. return doTestOpenWindow(true, 'beforesemi;aftersemi');
  157. }
  158. function testTwoSemicolons() {
  159. return doTestOpenWindow(true, 'a;b;c');
  160. }
  161. function testOpenAmpersand() {
  162. return doTestOpenWindow(true, 'this&that');
  163. }
  164. function testOpenSingleQuote() {
  165. return doTestOpenWindow(true, "'");
  166. }
  167. function testOpenDoubleQuote() {
  168. return doTestOpenWindow(true, '"', goog.labs.userAgent.browser.isIE());
  169. }
  170. function testOpenTag() {
  171. return doTestOpenWindow(true, '<', goog.labs.userAgent.browser.isIE());
  172. }
  173. function testOpenWindowSanitization() {
  174. var navigatedUrl;
  175. var mockWin = {open: function(url) { navigatedUrl = url; }};
  176. goog.window.open('javascript:evil();', {}, mockWin);
  177. assertEquals(goog.html.SafeUrl.INNOCUOUS_STRING, navigatedUrl);
  178. // Try the other code path
  179. goog.window.open({href: 'javascript:evil();'}, {}, mockWin);
  180. assertEquals(goog.html.SafeUrl.INNOCUOUS_STRING, navigatedUrl);
  181. goog.window.open('javascript:\'\'', {}, mockWin);
  182. assertEquals(goog.html.SafeUrl.INNOCUOUS_STRING, navigatedUrl);
  183. goog.window.open('about:blank', {}, mockWin);
  184. assertEquals(goog.html.SafeUrl.INNOCUOUS_STRING, navigatedUrl);
  185. }
  186. function testOpenWindowNoSanitization() {
  187. var navigatedUrl;
  188. var mockWin = {open: function(url) { navigatedUrl = url; }};
  189. goog.window.open('', {}, mockWin);
  190. assertEquals('', navigatedUrl);
  191. goog.window.open(goog.html.SafeUrl.ABOUT_BLANK, {}, mockWin);
  192. assertEquals('about:blank', navigatedUrl);
  193. }
  194. function testOpenBlank() {
  195. newWin = goog.window.openBlank('Loading...');
  196. var urlParam = 'bogus~';
  197. newWin.location.href = REDIRECT_URL_PREFIX + urlParam;
  198. return waitForTestWindow(newWin).then(function() {
  199. verifyWindow(newWin, false, urlParam);
  200. });
  201. }
  202. function testOpenBlankReturnsNullPopupBlocker() {
  203. var mockWin = {
  204. // emulate popup-blocker by returning a null window on open().
  205. open: function() { return null; }
  206. };
  207. var win = goog.window.openBlank('', {noreferrer: true}, mockWin);
  208. assertNull(win);
  209. }
  210. function testOpenBlankEscapesSafely() {
  211. // Opening a window with javascript: and then reading from its document.body
  212. // is problematic because in some browsers the document.body won't have been
  213. // updated yet, and in some IE versions the parent window does not have
  214. // access to document.body in new blank window.
  215. var navigatedUrl;
  216. var mockWin = {open: function(url) { navigatedUrl = url; }};
  217. // Test string determines that all necessary escaping transformations happen,
  218. // and that they happen in the right order (HTML->JS->URI).
  219. // - " which would be escaped by HTML escaping and JS string escaping. It
  220. // should be HTML escaped.
  221. // - \ which would be escaped by JS string escaping and percent-encoded
  222. // by encodeURI(). It gets JS string escaped first (to two '\') and then
  223. // percent-encoded.
  224. var win = goog.window.openBlank('"\\', {}, mockWin);
  225. assertEquals('javascript:"&quot;%5C%5C"', navigatedUrl);
  226. }
  227. function testOpenIosBlank() {
  228. if (!goog.labs.userAgent.engine.isWebKit() || !window.navigator) {
  229. // Don't even try this on IE8!
  230. return;
  231. }
  232. var attrs = {};
  233. var dispatchedEvent = null;
  234. var element = {
  235. setAttribute: function(name, value) { attrs[name] = value; },
  236. dispatchEvent: function(event) { dispatchedEvent = event; }
  237. };
  238. stubs.replace(window.document, 'createElement', function(name) {
  239. if (name == goog.dom.TagName.A) {
  240. return element;
  241. }
  242. return null;
  243. });
  244. stubs.set(window.navigator, 'standalone', true);
  245. stubs.replace(goog.labs.userAgent.platform, 'isIos', goog.functions.TRUE);
  246. var newWin = goog.window.open('http://google.com', {target: '_blank'});
  247. // This mode cannot return a new window.
  248. assertNotNull(newWin);
  249. assertUndefined(newWin.document);
  250. // Attributes.
  251. // element.href is directly set through goog.dom.safe.setAnchorHref, not with
  252. // element.setAttribute.
  253. assertEquals('http://google.com', element.href);
  254. assertEquals('_blank', attrs['target']);
  255. assertEquals('', attrs['rel'] || '');
  256. // Click event.
  257. assertNotNull(dispatchedEvent);
  258. assertEquals('click', dispatchedEvent.type);
  259. }
  260. function testOpenIosBlankNoreferrer() {
  261. if (!goog.labs.userAgent.engine.isWebKit() || !window.navigator) {
  262. // Don't even try this on IE8!
  263. return;
  264. }
  265. var attrs = {};
  266. var dispatchedEvent = null;
  267. var element = {
  268. setAttribute: function(name, value) { attrs[name] = value; },
  269. dispatchEvent: function(event) { dispatchedEvent = event; }
  270. };
  271. stubs.replace(window.document, 'createElement', function(name) {
  272. if (name == goog.dom.TagName.A) {
  273. return element;
  274. }
  275. return null;
  276. });
  277. stubs.set(window.navigator, 'standalone', true);
  278. stubs.replace(goog.labs.userAgent.platform, 'isIos', goog.functions.TRUE);
  279. var newWin = goog.window.open(
  280. 'http://google.com', {target: '_blank', noreferrer: true});
  281. // This mode cannot return a new window.
  282. assertNotNull(newWin);
  283. assertUndefined(newWin.document);
  284. // Attributes.
  285. // element.href is directly set through goog.dom.safe.setAnchorHref, not with
  286. // element.setAttribute.
  287. assertEquals('http://google.com', element.href);
  288. assertEquals('_blank', attrs['target']);
  289. assertEquals('noreferrer', attrs['rel']);
  290. // Click event.
  291. assertNotNull(dispatchedEvent);
  292. assertEquals('click', dispatchedEvent.type);
  293. }
  294. function testOpenNoReferrerEscapesUrl() {
  295. var documentWriteHtml;
  296. var mockNewWin = {};
  297. mockNewWin.document = {
  298. write: function(html) { documentWriteHtml = html; },
  299. close: function() {}
  300. };
  301. var mockWin = {open: function() { return mockNewWin; }};
  302. goog.window.open('https://hello&world', {noreferrer: true}, mockWin);
  303. assertRegExp(
  304. 'Does not contain expected HTML-escaped string: ' + documentWriteHtml,
  305. /hello&amp;world/, documentWriteHtml);
  306. }