text: // Copyright 2014 The Chromium Authors. All rights reserved.
  // Use of this source code is governed by a BSD-style license that can be
  // found in the LICENSE file.
  
  /**
   * @fileoverview CryptoToken background page
   */
  
  'use strict';
  
  /** @const */
  var BROWSER_SUPPORTS_TLS_CHANNEL_ID = true;
  
  /** @const */
  var HTTP_ORIGINS_ALLOWED = false;
  
  /** @const */
  var LOG_SAVER_EXTENSION_ID = 'fjajfjhkeibgmiggdfehjplbhmfkialk';
  
  // Singleton tracking available devices.
  var gnubbies = new Gnubbies();
  HidGnubbyDevice.register(gnubbies);
  UsbGnubbyDevice.register(gnubbies);
  
  var TIMER_FACTORY = new CountdownTimerFactory();
  
  var FACTORY_REGISTRY = new FactoryRegistry(
      new CryptoTokenApprovedOrigin(),
      TIMER_FACTORY,
      new EtldOriginChecker(),
      new UsbHelper(),
      new XhrTextFetcher());
  
  var DEVICE_FACTORY_REGISTRY = new DeviceFactoryRegistry(
      new UsbGnubbyFactory(gnubbies),
      TIMER_FACTORY,
      new GoogleCorpIndividualAttestation());
  
  /**
   * @param {*} request The received request
   * @return {boolean} Whether the request is a register/enroll request.
   */
  function isRegisterRequest(request) {
    if (!request) {
      return false;
    }
    switch (request.type) {
      case GnubbyMsgTypes.ENROLL_WEB_REQUEST:
        return true;
  
      case MessageTypes.U2F_REGISTER_REQUEST:
        return true;
  
      default:
        return false;
    }
  }
  
  /**
   * Default response callback to deliver a response to a request.
   * @param {*} request The received request.
   * @param {function(*): void} sendResponse A callback that delivers a response.
   * @param {*} response The response to return.
   */
  function defaultResponseCallback(request, sendResponse, response) {
    response['requestId'] = request['requestId'];
    try {
      sendResponse(response);
    } catch (e) {
      console.warn(UTIL_fmt('caught: ' + e.message));
    }
  }
  
  /**
   * Response callback that delivers a response to a request only when the
   * sender is a foreground tab.
   * @param {*} request The received request.
   * @param {!MessageSender} sender The message sender.
   * @param {function(*): void} sendResponse A callback that delivers a response.
   * @param {*} response The response to return.
   */
  function sendResponseToActiveTabOnly(request, sender, sendResponse, response) {
    tabInForeground(sender.tab.id).then(function(result) {
      // If the tab is no longer in the foreground, drop the result: the user
      // is no longer interacting with the tab that originated the request.
      if (result) {
        defaultResponseCallback(request, sendResponse, response);
      }
    });
  }
  
  /**
   * Common handler for messages received from chrome.runtime.sendMessage and
   * chrome.runtime.connect + postMessage.
   * @param {*} request The received request
   * @param {!MessageSender} sender The message sender
   * @param {function(*): void} sendResponse A callback that delivers a response
   * @return {Closeable} A Closeable request handler.
   */
  function messageHandler(request, sender, sendResponse) {
    var responseCallback;
    if (isRegisterRequest(request)) {
      responseCallback =
          sendResponseToActiveTabOnly.bind(null, request, sender, sendResponse);
    } else {
      responseCallback =
          defaultResponseCallback.bind(null, request, sendResponse);
    }
    var closeable = handleWebPageRequest(/** @type {Object} */(request),
        sender, responseCallback);
    return closeable;
  }
  
  /**
   * Listen to individual messages sent from (whitelisted) webpages via
   * chrome.runtime.sendMessage
   * @param {*} request The received request
   * @param {!MessageSender} sender The message sender
   * @param {function(*): void} sendResponse A callback that delivers a response
   * @return {boolean}
   */
  function messageHandlerExternal(request, sender, sendResponse) {
    if (sender.id && sender.id === LOG_SAVER_EXTENSION_ID) {
      return handleLogSaverMessage(request);
    }
  
    messageHandler(request, sender, sendResponse);
    return true;  // Tell Chrome not to destroy sendResponse yet
  }
  chrome.runtime.onMessageExternal.addListener(messageHandlerExternal);
  
  // Listen to direct connection events, and wire up a message handler on the port
  chrome.runtime.onConnectExternal.addListener(function(port) {
    function sendResponse(response) {
      port.postMessage(response);
    }
  
    var closeable;
    port.onMessage.addListener(function(request) {
      var sender = /** @type {!MessageSender} */ (port.sender);
      closeable = messageHandler(request, sender, sendResponse);
    });
    port.onDisconnect.addListener(function() {
      if (closeable) {
        closeable.close();
      }
    });
  });
  
  /**
   * Handles messages from the log-saver app. Temporarily replaces UTIL_fmt with
   * a wrapper that also sends formatted messages to the app.
   * @param {*} request The message received from the app
   * @return {boolean} Used as chrome.runtime.onMessage handler return value
   */
  function handleLogSaverMessage(request) {
    if (request === 'start') {
      if (originalUtilFmt_) {
        // We're already sending
        return false;
      }
      originalUtilFmt_ = UTIL_fmt;
      UTIL_fmt = function(s) {
        var line = originalUtilFmt_(s);
        chrome.runtime.sendMessage(LOG_SAVER_EXTENSION_ID, line);
        return line;
      };
    } else if (request === 'stop') {
      if (originalUtilFmt_) {
        UTIL_fmt = originalUtilFmt_;
        originalUtilFmt_ = null;
      }
    }
    return false;
  }
  
  /** @private */
  var originalUtilFmt_ = null;
  