text: // Copyright 2009 The Closure Library Authors. All Rights Reserved.
  //
  // Licensed under the Apache License, Version 2.0 (the "License");
  // you may not use this file except in compliance with the License.
  // You may obtain a copy of the License at
  //
  //      http://www.apache.org/licenses/LICENSE-2.0
  //
  // Unless required by applicable law or agreed to in writing, software
  // distributed under the License is distributed on an "AS-IS" BASIS,
  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  // See the License for the specific language governing permissions and
  // limitations under the License.
  
  goog.module('goog.windowTest');
  goog.setTestOnly();
  
  const GoogPromise = goog.require('goog.Promise');
  const PropertyReplacer = goog.require('goog.testing.PropertyReplacer');
  const SafeUrl = goog.require('goog.html.SafeUrl');
  const TagName = goog.require('goog.dom.TagName');
  const TestCase = goog.require('goog.testing.TestCase');
  const browser = goog.require('goog.labs.userAgent.browser');
  const dom = goog.require('goog.dom');
  const engine = goog.require('goog.labs.userAgent.engine');
  const events = goog.require('goog.events');
  const functions = goog.require('goog.functions');
  const googString = goog.require('goog.string');
  const googWindow = goog.require('goog.window');
  const platform = goog.require('goog.labs.userAgent.platform');
  const testSuite = goog.require('goog.testing.testSuite');
  
  const REDIRECT_URL_PREFIX = 'window_test.html?runTests=';
  const WIN_LOAD_TRY_TIMEOUT = 100;
  const MAX_WIN_LOAD_TRIES = 50;  // 50x100ms = 5s waiting for window to load.
  
  const stubs = new PropertyReplacer();
  
  // To test goog.window.open we open a new window with this file again. Once
  // the new window parses this file it sets this variable to true, indicating
  // that the parent test may check window properties like referrer and location.
  window.newWinLoaded = true;
  
  let /** ?Window */ newWin = null;
  
  
  /**
   * Returns a promise for `win` once JS has been evaluated in it.
   * @param {Window} win
   * @return {!GoogPromise<!Window>} Promise for a window that resolves once the
   *     window has loaded.
   */
  function waitForTestWindow(win) {
    return new GoogPromise((resolve, reject) => {
      if (!win) {
        fail('Could not open new window. Check if popup blocker is enabled.');
      }
  
      let attemptCount = 0;
      const intervalToken = window.setInterval(() => {
        if (++attemptCount > MAX_WIN_LOAD_TRIES) {
          try {
            fail('Window did not load after maximum number of checks.');
          } catch (e) {
            window.clearInterval(intervalToken);
            reject(e);
          }
        } else if (win.newWinLoaded) {
          window.clearInterval(intervalToken);
          resolve(win);
        }
      }, WIN_LOAD_TRY_TIMEOUT);
    });
  }
  
  /**
   * Opens a window and then verifies that the new window has the expected
   * properties.
   * @param {boolean} noreferrer Whether to test the noreferrer option.
   * @param {string} urlParam Url param to append to the url being opened.
   * @param {boolean=} encodeUrlParam_opt Whether to percent-encode urlParam. This
   *     is needed because IE will not encode it automatically like other browsers
   *     browser and the Closure test server will 400 on certain characters in the
   *     URL (like '<' and '"').
   * @return {!GoogPromise} Promise that resolves once the test is complete.
   */
  function doTestOpenWindow(noreferrer, urlParam, encodeUrlParam_opt) {
    if (encodeUrlParam_opt) {
      urlParam = encodeURIComponent(urlParam);
    }
    // TODO(mlourenco): target is set because goog.window.open() will currently
    // allow it to be undefined, which in IE seems to result in the same window
    // being reused, instead of a new one being created. If goog.window.open()
    // is fixed to use "_blank" by default then target can be removed here.
    newWin = googWindow.open(
        REDIRECT_URL_PREFIX + urlParam,
        {'noreferrer': noreferrer, 'target': '_blank'});
  
    return waitForTestWindow(newWin).then((win) => {
      verifyWindow(win, noreferrer, urlParam);
    });
  }
  
  /**
   * Asserts that a newly created window has the correct parameters.
   * @param {Window} win
   * @param {boolean} noreferrer Whether the noreferrer option is being tested.
   * @param {string} urlParam Url param appended to the url being opened.
   */
  function verifyWindow(win, noreferrer, urlParam) {
    if (noreferrer) {
      assertEquals(
          'Referrer should have been stripped', '', win.document.referrer);
    }
  
    const winUrl = decodeURI(String(win.location));
    const expectedUrlSuffix = decodeURI(urlParam);
    assertTrue(
        `New window href should have ended with <${expectedUrlSuffix}` +
            '> but was <' + winUrl + '>',
        googString.endsWith(winUrl, expectedUrlSuffix));
  }
  
  testSuite({
    shouldRunTests() {
      // TODO(b/25455129): Edge has a flaky test failures around window.open.
      return !browser.isEdge();
    },
  
    setUpPage() {
      const anchors =
          dom.getElementsByTagNameAndClass(TagName.DIV, 'goog-like-link');
      for (let i = 0; i < anchors.length; i++) {
        events.listen(anchors[i], 'click', (e) => {
          googWindow.open(dom.getTextContent(e.target), {'noreferrer': true});
        });
      }
      TestCase.getActiveTestCase().promiseTimeout = 60000;  // 60s
    },
  
    setUp() {
      newWin = null;
    },
  
    tearDown() {
      if (newWin) {
        newWin.close();
      }
      stubs.reset();
    },
  
    testOpenNotEncoded() {
      return doTestOpenWindow(false, 'bogus~');
    },
  
    testOpenEncoded() {
      return doTestOpenWindow(false, 'bogus%7E');
    },
  
    testOpenEncodedPercent() {
      // Intent of url is to pass %7E to the server, so it was encoded to %257E .
      return doTestOpenWindow(false, 'bogus%257E');
    },
  
    testOpenNotEncodedHidingReferrer() {
      return doTestOpenWindow(true, 'bogus~');
    },
  
    testOpenEncodedHidingReferrer() {
      return doTestOpenWindow(true, 'bogus%7E');
    },
  
    testOpenEncodedPercentHidingReferrer() {
      // Intent of url is to pass %7E to the server, so it was encoded to %257E .
      return doTestOpenWindow(true, 'bogus%257E');
    },
  
    testOpenSemicolon() {
      return doTestOpenWindow(true, 'beforesemi;aftersemi');
    },
  
    testTwoSemicolons() {
      return doTestOpenWindow(true, 'a;b;c');
    },
  
    testOpenAmpersand() {
      return doTestOpenWindow(true, 'this&that');
    },
  
    testOpenSingleQuote() {
      return doTestOpenWindow(true, '\'');
    },
  
    testOpenDoubleQuote() {
      return doTestOpenWindow(true, '"', browser.isIE());
    },
  
    testOpenTag() {
      return doTestOpenWindow(true, '<', browser.isIE());
    },
  
    testOpenWindowSanitization() {
      let navigatedUrl;
      const /** ? */ mockWin = {
        open: function(url) {
          navigatedUrl = url;
        },
      };
  
      googWindow.open('javascript:evil();', {}, mockWin);
      assertEquals(SafeUrl.INNOCUOUS_STRING, navigatedUrl);
  
      // Try the other code path
      googWindow.open({href: 'javascript:evil();'}, {}, mockWin);
      assertEquals(SafeUrl.INNOCUOUS_STRING, navigatedUrl);
  
      googWindow.open('javascript:\'\'', {}, mockWin);
      assertEquals(SafeUrl.INNOCUOUS_STRING, navigatedUrl);
  
      googWindow.open('about:blank', {}, mockWin);
      assertEquals(SafeUrl.INNOCUOUS_STRING, navigatedUrl);
    },
  
    testOpenWindowNoSanitization() {
      let navigatedUrl;
      const /** ? */ mockWin = {
        open: function(url) {
          navigatedUrl = url;
        },
      };
  
      googWindow.open('', {}, mockWin);
      assertEquals('', navigatedUrl);
  
      googWindow.open(SafeUrl.ABOUT_BLANK, {}, mockWin);
      assertEquals('about:blank', navigatedUrl);
    },
  
    testOpenBlank() {
      newWin = googWindow.openBlank('Loading...');
      const urlParam = 'bogus~';
      newWin.location.href = REDIRECT_URL_PREFIX + urlParam;
      return waitForTestWindow(newWin).then(() => {
        verifyWindow(newWin, false, urlParam);
      });
    },
  
    testOpenBlankReturnsNullPopupBlocker() {
      const /** ? */ mockWin = {
        // emulate popup-blocker by returning a null window on open().
        open: function() {
          return null;
        },
      };
      const win = googWindow.openBlank('', {noreferrer: true}, mockWin);
      assertNull(win);
    },
  
    testOpenBlankEscapesSafely() {
      // Opening a window with javascript: and then reading from its document.body
      // is problematic because in some browsers the document.body won't have been
      // updated yet, and in some IE versions the parent window does not have
      // access to document.body in new blank window.
      let navigatedUrl;
      const /** ? */ mockWin = {
        open: function(url) {
          navigatedUrl = url;
        },
      };
  
      // Test string determines that all necessary escaping transformations
      // happen, and that they happen in the right order (HTML->JS->URI).
      // - " which would be escaped by HTML escaping and JS string escaping. It
      //     should be HTML escaped.
      // - \ which would be escaped by JS string escaping and percent-encoded
      //     by encodeURI(). It gets JS string escaped first (to two '\') and then
      //     percent-encoded.
      const win = googWindow.openBlank('"\\', {}, mockWin);
      assertEquals('javascript:"&quot;%5C%5C"', navigatedUrl);
    },
  
    testOpenIosBlank() {
      if (!engine.isWebKit() || !window.navigator) {
        // Don't even try this on IE8!
        return;
      }
      const attrs = {};
      let dispatchedEvent = null;
      const element = {
        setAttribute: function(name, value) {
          attrs[name] = value;
        },
        dispatchEvent: function(event) {
          dispatchedEvent = event;
        },
        href: undefined,
      };
      stubs.replace(window.document, 'createElement', (name) => {
        if (name == TagName.A) {
          return element;
        }
        return null;
      });
      stubs.set(window.navigator, 'standalone', true);
      stubs.replace(platform, 'isIos', functions.TRUE);
  
      const newWin = googWindow.open('http://google.com', {target: '_blank'});
  
      // This mode cannot return a new window.
      assertNotNull(newWin);
      assertUndefined(newWin.document);
  
      // Attributes.
      // element.href is directly set through goog.dom.safe.setAnchorHref, not
      // with element.setAttribute.
      assertEquals('http://google.com', element.href);
      assertEquals('_blank', attrs['target']);
      assertEquals('', attrs['rel'] || '');
  
      // Click event.
      assertNotNull(dispatchedEvent);
      assertEquals('click', dispatchedEvent.type);
    },
  
    testOpenIosBlankNoreferrer() {
      if (!engine.isWebKit() || !window.navigator) {
        // Don't even try this on IE8!
        return;
      }
      const attrs = {};
      let dispatchedEvent = null;
      const element = {
        setAttribute: function(name, value) {
          attrs[name] = value;
        },
        dispatchEvent: function(event) {
          dispatchedEvent = event;
        },
        href: undefined,
      };
      stubs.replace(window.document, 'createElement', (name) => {
        if (name == TagName.A) {
          return element;
        }
        return null;
      });
      stubs.set(window.navigator, 'standalone', true);
      stubs.replace(platform, 'isIos', functions.TRUE);
  
      const newWin = googWindow.open(
          'http://google.com', {target: '_blank', noreferrer: true});
  
      // This mode cannot return a new window.
      assertNotNull(newWin);
      assertUndefined(newWin.document);
  
      // Attributes.
      // element.href is directly set through goog.dom.safe.setAnchorHref, not
      // with element.setAttribute.
      assertEquals('http://google.com', element.href);
      assertEquals('_blank', attrs['target']);
      assertEquals('noreferrer', attrs['rel']);
  
      // Click event.
      assertNotNull(dispatchedEvent);
      assertEquals('click', dispatchedEvent.type);
    },
  
    testOpenNoReferrerEscapesUrl() {
      let documentWriteHtml;
      const mockNewWin = {};
      mockNewWin.document = {
        write: function(html) {
          documentWriteHtml = html;
        },
        close: function() {},
      };
      const /** ? */ mockWin = {
        open: function() {
          return mockNewWin;
        },
      };
      googWindow.open('https://hello&world', {noreferrer: true}, mockWin);
      assertRegExp(
          `Does not contain expected HTML-escaped string: ${documentWriteHtml}`,
          /hello&amp;world/, documentWriteHtml);
    },
  
    testOpenNewWindowNoopener() {
      newWin = googWindow.open(
          `${REDIRECT_URL_PREFIX}theBest`,
          {'target': '_blank', 'noopener': true});
  
      // This mode cannot return a new window.
      assertNotNull(newWin);
      assertNotEquals(undefined, newWin.document);
      assertNull(newWin.opener);
  
      return waitForTestWindow(newWin).then((win) => {
        verifyWindow(win, false, 'theBest');
      });
    },
  });
  