chromium/components/test/data/password_manager/form_annotation_extension/background.js

// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

/**
 * The identation for generated Python code.
 *
 * @type {string}
 */
const IDENT_PAD = '  ';

/**
 * If the extension should generate Python code for signup or change password
 * forms, then this value should be 'True' (i.e. it will be expected that
 * password generation will be offered on the given field). Otherwise, the value
 * should be 'False'.
 *
 * @type {string}
 */
const IS_PWD_CREATION_VALUE = 'False';

/**
 * Used by the extension to store the steps that user performs to reach
 * a password form (signin or signup).
 *
 * When the user is reaching the form, the extension saves to the array the
 * URLs of visited pages, clicked elements' CSS selectors and other stuff.
 * When the user has reached the form, the extension converts the array to
 * Python test code and clears the array (before visiting the next site).
 *
 * @type {Array}
 */
let steps = [];

/**
 * The index of the last visited site from |sitesToVisit| (sites_to_visit.js).
 *
 * @type {number}
 */
let lastVisitedSiteIndex = 0;

/**
 * Generated Python tests.
 *
 * A test is printed to the background page's console right after the user click
 * on the password field. The test is also appended to this variable to be able
 * to print all generated tests at once.
 * Attention: if the extension is reloaded, the variable is cleared. So, print
 * the variable and save the generated tests before extension reload,
 * browser closing or so.
 *
 * @type {string}
 */
let allTests = '\n';

/**
 * Return the name of the test based on the form's url |url|
 * (e.g. http://login.example.com/path => test_login_example_com).
 *
 * @param {string} url The form's url.
 * @return {string} The test name.
 */
function getTestName(url) {
  const a = document.createElement('a');
  a.href = url;
  return 'test_' + a.hostname.split(/[.-]+/).join('_');
}

/**
 * Removes query and anchor from |url|
 * (e.g. https://example.com/path?query=1#anchor => https://example.com/path).
 *
 * @param {string} url The full url to be processed.
 * @return {string} The url w/o parameters and anchors.
 */
function stripUrl(url) {
  const a = document.createElement('a');
  a.href = url;
  return a.origin + a.pathname;
}

/**
 * Removes the elements before the first element in |steps| that might be the
 * first in Python script (i.e. it is possible to go to its url and find needed
 * element). See |couldBeFirstPageOfScript| on content.js for clarification.
 *
 * @param {Array} steps The steps to reach the password field.
 * @return {Array} Reduced list of steps.
 */
function removeUnnecessarySteps(steps) {
  const n = steps.length;
  const result = [];
  for (let i = n - 1; i >= 0; i--) {
    result.unshift(steps[i]);
    if (steps[i].couldBeFirst) {
      break;
    }
  }
  return result;
}

/**
 * Generate python code to switch to another frame.
 *
 * @param {Object} step A step of the script.
 * @return {string} Python code that performs frame switching.
 */
function switchToIframeIfNecessary(step) {
  let result = '';
  for (let i = 0; i < step.frames.length; i++) {
     result += IDENT_PAD + IDENT_PAD + 'self.SwitchTo(\'' + step.frames[i] +
         '\')\n';
  }
  return result;
}

/**
 * Outputs to the console the code of a Python test based on script steps
 * accumulated in |steps|. Also appends the test code to |allTests|.
 */
function outputPythonTestCode() {
  const lastStepUrl = stripUrl(steps[steps.length - 1].url);
  let test = '';
  test += IDENT_PAD + 'def ' + getTestName(steps[steps.length - 1].url) +
      '(self):\n';
  steps = removeUnnecessarySteps(steps);
  test += IDENT_PAD + IDENT_PAD + 'self.GoTo("' + stripUrl(steps[0].url) +
      '")\n';
  for (let i = 0; i <= steps.length - 2; i++) {
    test += switchToIframeIfNecessary(steps[i]);
    test += IDENT_PAD + IDENT_PAD + 'self.Click("' + steps[i].selector + '")\n';
  }
  test += switchToIframeIfNecessary(steps[steps.length - 1]);

  test +=
      IDENT_PAD + IDENT_PAD + 'self.CheckPwdField("' +
      steps[steps.length - 1].selector + '", ' +
      'is_pwd_creation=' + IS_PWD_CREATION_VALUE + ')\n';
  test += '\n';

  console.info(test);
  allTests += test;
  steps = [];
}

/**
 * Moves the current tab to the next site.
 */
function visitNextSite() {
  console.info(
      'next site: ' + sitesToVisit[lastVisitedSiteIndex] + ' ' +
      lastVisitedSiteIndex);
  chrome.tabs.update({url: 'http://' + sitesToVisit[lastVisitedSiteIndex]});
  steps = [];
  lastVisitedSiteIndex += 1;
}

/**
 * Add listener for messages from content script.
 */
chrome.runtime.onMessage.addListener(
  function(request, sender, sendResponse) {
    steps.push(request);
    // If the user clicked on the password field, output a Python test and go to
    // the next site.
    if (request.isPwdField) {
      outputPythonTestCode();
      visitNextSite();
    }
});

/**
 * Add listener for browser action (i.e. click on the icon of the extension).
 */
chrome.browserAction.onClicked.addListener(function(tab) {
  visitNextSite();
});