<commit_before>const commandLineArgs = require('command-line-args');
const sleep = require('system-sleep');
const raml = require('raml-parser');
const deasync = require('deasync');
const http = require('http');
const uuid = require('uuid');
const fs = require('fs');
const ip = require('ip');

function ServiceRAMLParser() {
  var self = this;

  self.extractResources = function(filename) {
    // Parse RAML file to retrieve available resources list
    return raml.loadFile(filename).then(function(data) {
      var resources = [];
      data.resources.forEach(function(node) {
        self.extractNodeResources(node).forEach(function(resource) {
          resources.push(resource);
        })
      });
      return resources;
    }, function(error) {
      throw new Error(error);
    });
  }

  self.extractNodeResources = function(node) {
    var paths = [];

    if (node.resources) {
      node.resources.forEach(function(childNode) {
        self.extractNodeResources(childNode).forEach(function(path) {
          paths.push(node.relativeUri + path);
        });
      });
    }

    if (node.methods instanceof Array && node.methods.length > 0){
      paths.push(node.relativeUri);
    }

    return paths;
  }
}
function ServiceConsulRegistrator(host, port) {
  self = this;
  self.host = host;
  self.port = port;
  self.attempts = 10;
  self.attemptTimeout = 500;

  self.registerService = function(serviceData) {
    return registerServiceBase(serviceData, 'PUT');
  }

  self.deregisterService = function(serviceData) {
    return registerServiceBase(serviceData, 'DELETE');
  }

  self.registerResources = function(serviceData, resourceArray) {
    return registerResourcesBase(serviceData, resourceArray, 'PUT');
  }

  self.deregisterResources = function(serviceData, resourceArray) {
    return registerResourcesBase(serviceData, resourceArray, 'DELETE');
  }

  function registerServiceBase(serviceData, mode) {
    var requestPath = mode === 'PUT' ? 'register' : 'deregister/' + serviceData.ID;
    var data = JSON.stringify(serviceData);
    var settings = {
      host: self.host,
      port: self.port,
      path: '/v1/agent/service/' + requestPath,
      method: 'PUT'
    }

    // Send the request
    for (var i = 0; i < self.attempts; i++) {
      var response = awaitRequest(settings, data);

      if (response.statusCode === 200) {
        break;
      }

      sleep(self.attemptTimeout);
    }
  }

  function registerResourcesBase(serviceData, resourceArray, mode) {
    var settings = [];
    var data = [];

    // Prepare settings and data for requests
    resourceArray.forEach(function(resource) {
      var setting = {
        host: self.host,
        port: self.port,
        path: '/v1/kv/resources' + resource,
        method: mode
      }

      settings.push(setting);
      data.push(serviceData.Name);
    });

    // Send all requests at once
    for (var i = 0; i < self.attempts; i++) {
      var responses = awaitRequests(settings, data);
      var success = !responses.some(function(response) { return response.statusCode !== 200 });

      if (success) {
        break;
      }

      sleep(self.attemptTimeout);
    }
  }

  function awaitRequests(settingArray, dataArray) {
    // Runs multiple parallel HTTP requests and waits for their completion
    var size = settingArray.length;
    var results = settingArray.map(function() { return null; });
    var completed = 0;

    for (var i = 0; i < size; i++) {
      (function(i) {
        var request = http.request(settingArray[i], function(response) {
          // This operation is safe in node.js
          completed++;
          results[i] = response;
        });

        request.write(dataArray[i]);
        request.end();
      })(i);
    };

    while(completed !== size) {
      deasync.runLoopOnce();
    }

    return results;
  }

  function awaitRequest(settings, data) {
    // Runs a single HTTP request synchronously
    var result;
    var request = http.request(settings, function(response) {
      result = response;
    });

    request.write(data);
    request.end();

    while(!result) {
      deasync.runLoopOnce();
    }

    return result;
  }
}
function RegistrationService(host, port) {
  var self = this;

  self.consulHost = host;
  self.consulPort = port;
  self.filename = '.consul_service_id~';

  self.registrator = new ServiceConsulRegistrator(self.consulHost, self.consulPort);;
  self.parser = new ServiceRAMLParser();

  self.register = function(args) {
    console.log("Registering service...");
    registrationBase(args, 'register');
    console.log("Registration finished!");
  }
  self.deregister = function(args) {
    console.log("Deregistering service...");
    registrationBase(args, 'deregister');
    console.log("Deregistration finished!");
  }

  function registrationBase(args, mode) {
    registerService(args.service, mode);

    if (args.raml) {
      registerRaml(args.service, args.raml, mode);
    }

    if (args.path) {
      registerPath(args.service, args.path, mode);
    }
  }
  function registerService(service, mode) {
    service.ID = generateServiceId(service.Name);

    if (mode === 'register') {
      self.registrator.registerService(service);
    } else {
      self.registrator.deregisterService(service);
    }
  }
  function registerRaml(service, filename, mode) {
    var completed = false;
    service.ID = generateServiceId(service.Name);

    self.parser.extractResources(filename).then(function(resources) {
      if (mode === 'register') {
        self.registrator.registerResources(service, resources);
      } else {
        self.registrator.deregisterResources(service, resources);
      }

      completed = true;
    });

    // Wait for RAML parsing
    while(!completed) {
      deasync.runLoopOnce();
    }
  }
  function registerPath(service, paths, mode) {
    service.ID = generateServiceId(service.Name);

    for (var i = 0; i < paths.length; i++) {
      if (paths[i].indexOf("/") !== 0) {
        paths[i] = "/" + paths[i];
      }
    }

    if (mode === 'register') {
      self.registrator.registerResources(service, paths);
    } else {
      self.registrator.deregisterResources(service, paths);
    }
  }

  function generateServiceId(serviceName) {
    var serviceId = null;

    try {
      fs.accessSync(self.filename, fs.R_OK | fs.W_OK);
      serviceId = fs.readFileSync(fs.openSync(self.filename, 'r+')).toString();
    } catch (err) {
      serviceId = uuid() + '-' + serviceName;
      fs.writeSync(fs.openSync(self.filename, 'w+'), serviceId);
    }

    return serviceId;
  }
  function clearServiceId() {
    try {
      fs.unlinkSync(self.filename);
    } catch (err) {
      console.error("Service ID file could not be found or accessed.");
    }
  }
}
function CommandLineResolver() {
  var self = this;

  self.processArgs = function(mapping) {
    mapping = mapping || getCommandArgsMapping();

    var args = commandLineArgs(mapping);
    var settings = getSettings(args);
    validateSettings(settings);

    return settings;
  }

  function validateSettings(settings) {
    if (!settings.command) {
      throw new Error("Command parameter is missing.");
    } else if (!(settings.raml || settings.path)) {
      throw new Error("You must either provide path or file parameter.");
    }
  }
  function getSettings(args) {
    var settings = {
      service: getDefaultServiceValues()
    };

    // Fill config file data
    if (args['config-file']) {
      var config = JSON.parse(fs.readFileSync(args['config-file']).toString());
      fillBaseSettings(settings, config);

      if (config['service']) {
        for (var key in config.service) {
          settings.service[key] = config.service[key];
        }
      }
    }

    // Fill command line arguments
    fillBaseSettings(settings, args);
    if (args['name']) {
      settings.service.Name = args.name;
    }

    return settings;
  }
  function getCommandArgsMapping() {
    return [
      { name: 'config-file', alias: 'f', type: String },
      { name: 'command', alias: 'c', type: String },
      { name: 'name', alias: 'n', type: String },
      { name: 'raml', alias: 'r', type: String },
      { name: 'path', alias: 'p', type: String, multiple: true }
    ];
  }
  function getDefaultServiceValues() {
    return {
      'ID': null,
      'Name': null,
      'Port': 80,
      'Address': ip.address(),
      'Tags': [],
      'EnableTagOverride': false
    };
  }
  function fillBaseSettings(settings, args) {
    var keys = ['command', 'path', 'raml'];
    for (var i = 0; i < keys.length; i++) {
      var keyword = keys[i];
      if (args[keyword]) {
        settings[keyword] = args[keyword];
      }
    }
  }
}

try {
  var consulHost = process.env.CONSUL_HOST || 'consul';
  var consulPort = process.env.CONSUL_PORT || '8500';

  // Retrieve arguments passed to script
  var args = new CommandLineResolver().processArgs();
  var registration = new RegistrationService(consulHost, consulPort);

  if (args.command === 'register') {
    registration.register(args);
  } else if (args.command === 'deregister') {
    registration.deregister(args);
  } else {
    throw new Error("Invalid command. It should be either 'register' or 'deregister'.")
  }
} catch(err) {
  console.error("Error during service registration:")
  console.error(err.message);
  process.exit(1);
}
<commit_msg>Refractored registration script to display pretty error messages.<commit_after>const commandLineArgs = require('command-line-args');
const sleep = require('system-sleep');
const raml = require('raml-parser');
const deasync = require('deasync');
const http = require('http');
const uuid = require('uuid');
const util = require('util');
const fs = require('fs');
const ip = require('ip');

function ServiceRAMLParser() {
  var self = this;

  self.extractResources = function(filename) {
    // Parse RAML file to retrieve available resources list
    return raml.loadFile(filename).then(function(data) {
      var resources = [];
      data.resources.forEach(function(node) {
        self.extractNodeResources(node).forEach(function(resource) {
          resources.push(resource);
        })
      });
      return resources;
    }, function(error) {
      throw new Error(error);
    });
  }

  self.extractNodeResources = function(node) {
    var paths = [];

    if (node.resources) {
      node.resources.forEach(function(childNode) {
        self.extractNodeResources(childNode).forEach(function(path) {
          paths.push(node.relativeUri + path);
        });
      });
    }

    if (node.methods instanceof Array && node.methods.length > 0){
      paths.push(node.relativeUri);
    }

    return paths;
  }
}
function ServiceConsulRegistrator(host, port) {
  self = this;
  self.host = host;
  self.port = port;
  self.attempts = 10;
  self.attemptTimeout = 500;

  self.registerService = function(serviceData) {
    return registerServiceBase(serviceData, 'PUT');
  }

  self.deregisterService = function(serviceData) {
    return registerServiceBase(serviceData, 'DELETE');
  }

  self.registerResources = function(serviceData, resourceArray) {
    return registerResourcesBase(serviceData, resourceArray, 'PUT');
  }

  self.deregisterResources = function(serviceData, resourceArray) {
    return registerResourcesBase(serviceData, resourceArray, 'DELETE');
  }

  function registerServiceBase(serviceData, mode) {
    var requestPath = mode === 'PUT' ? 'register' : 'deregister/' + serviceData.ID;
    var data = JSON.stringify(serviceData);
    var settings = {
      host: self.host,
      port: self.port,
      path: '/v1/agent/service/' + requestPath,
      method: 'PUT'
    }

    // Send the request
    for (var i = 0; i < self.attempts; i++) {
      var response = awaitRequest(settings, data);

      if (response.statusCode === 200) {
        break;
      }

      sleep(self.attemptTimeout);
    }
  }

  function registerResourcesBase(serviceData, resourceArray, mode) {
    var settings = [];
    var data = [];

    // Prepare settings and data for requests
    resourceArray.forEach(function(resource) {
      var setting = {
        host: self.host,
        port: self.port,
        path: '/v1/kv/resources' + resource,
        method: mode
      }

      settings.push(setting);
      data.push(serviceData.Name);
    });

    // Send all requests at once
    for (var i = 0; i < self.attempts; i++) {
      var responses = awaitRequests(settings, data);
      var success = !responses.some(function(response) { return response.statusCode !== 200 });

      if (success) {
        break;
      }

      sleep(self.attemptTimeout);
    }
  }

  function awaitRequests(settingArray, dataArray) {
    // Runs multiple parallel HTTP requests and waits for their completion
    var size = settingArray.length;
    var results = settingArray.map(function() { return null; });
    var completed = 0;

    for (var i = 0; i < size; i++) {
      (function(i) {
        var request = http.request(settingArray[i], function(response) {
          // This operation is safe in node.js
          completed++;
          results[i] = response;
        });

        request.write(dataArray[i]);
        request.end();
      })(i);
    };

    while(completed !== size) {
      deasync.runLoopOnce();
    }

    return results;
  }

  function awaitRequest(settings, data) {
    // Runs a single HTTP request synchronously
    var result;
    var request = http.request(settings, function(response) {
      result = response;
    });

    request.write(data);
    request.end();

    while(!result) {
      deasync.runLoopOnce();
    }

    return result;
  }
}
function RegistrationService(host, port) {
  var self = this;

  self.consulHost = host;
  self.consulPort = port;
  self.filename = '.consul_service_id~';

  self.registrator = new ServiceConsulRegistrator(self.consulHost, self.consulPort);;
  self.parser = new ServiceRAMLParser();

  self.register = function(args) {
    console.log("Registering service...");
    registrationBase(args, 'register');
    console.log("Registration finished!");
  }
  self.deregister = function(args) {
    console.log("Deregistering service...");
    registrationBase(args, 'deregister');
    console.log("Deregistration finished!");
  }

  function registrationBase(args, mode) {
    registerService(args.service, mode);

    if (args.raml) {
      registerRaml(args.service, args.raml, mode);
    }

    if (args.path) {
      registerPath(args.service, args.path, mode);
    }
  }
  function registerService(service, mode) {
    service.ID = generateServiceId(service.Name);

    if (mode === 'register') {
      self.registrator.registerService(service);
    } else {
      self.registrator.deregisterService(service);
    }
  }
  function registerRaml(service, filename, mode) {
    var completed = false;
    service.ID = generateServiceId(service.Name);

    self.parser.extractResources(filename).then(function(resources) {
      if (mode === 'register') {
        self.registrator.registerResources(service, resources);
      } else {
        self.registrator.deregisterResources(service, resources);
      }

      completed = true;
    });

    // Wait for RAML parsing
    while(!completed) {
      deasync.runLoopOnce();
    }
  }
  function registerPath(service, paths, mode) {
    service.ID = generateServiceId(service.Name);

    for (var i = 0; i < paths.length; i++) {
      if (paths[i].indexOf("/") !== 0) {
        paths[i] = "/" + paths[i];
      }
    }

    if (mode === 'register') {
      self.registrator.registerResources(service, paths);
    } else {
      self.registrator.deregisterResources(service, paths);
    }
  }

  function generateServiceId(serviceName) {
    var serviceId = null;

    try {
      fs.accessSync(self.filename, fs.R_OK | fs.W_OK);
      serviceId = fs.readFileSync(fs.openSync(self.filename, 'r+')).toString();
    } catch (err) {
      serviceId = uuid() + '-' + serviceName;
      fs.writeSync(fs.openSync(self.filename, 'w+'), serviceId);
    }

    return serviceId;
  }
  function clearServiceId() {
    try {
      fs.unlinkSync(self.filename);
    } catch (err) {
      console.error("Service ID file could not be found or accessed.");
    }
  }
}
function CommandLineResolver() {
  var self = this;

  self.processArgs = function(mapping) {
    mapping = mapping || getCommandArgsMapping();

    var args = commandLineArgs(mapping);
    var settings = getSettings(args);
    validateSettings(settings);

    return settings;
  }

  function validateSettings(settings) {
    if (!settings.command) {
      throw new Error("Command parameter is missing.");
    } else if (!(settings.raml || settings.path)) {
      throw new Error("You must either provide path or file parameter.");
    }
  }
  function getSettings(args) {
    var settings = {
      service: getDefaultServiceValues()
    };

    // Fill config file data
    if (args['config-file']) {
      var config = JSON.parse(fs.readFileSync(args['config-file']).toString());
      fillBaseSettings(settings, config);

      if (config['service']) {
        for (var key in config.service) {
          settings.service[key] = config.service[key];
        }
      }
    }

    // Fill command line arguments
    fillBaseSettings(settings, args);
    if (args['name']) {
      settings.service.Name = args.name;
    }

    return settings;
  }
  function getCommandArgsMapping() {
    return [
      { name: 'config-file', alias: 'f', type: String },
      { name: 'command', alias: 'c', type: String },
      { name: 'name', alias: 'n', type: String },
      { name: 'raml', alias: 'r', type: String },
      { name: 'path', alias: 'p', type: String, multiple: true }
    ];
  }
  function getDefaultServiceValues() {
    return {
      'ID': null,
      'Name': null,
      'Port': 80,
      'Address': ip.address(),
      'Tags': [],
      'EnableTagOverride': false
    };
  }
  function fillBaseSettings(settings, args) {
    var keys = ['command', 'path', 'raml'];
    for (var i = 0; i < keys.length; i++) {
      var keyword = keys[i];
      if (args[keyword]) {
        settings[keyword] = args[keyword];
      }
    }
  }
}

// Function ran while executing as script
(function register() {
  function formatError(error) {
    var templates = {
      "original": "%s",
      "formatted": "%s\n(original message: \"%s\")"
    };

    var messages = {
      "ECONNRESET": "Could not reach Consul host: " + consulHost + ":" + consulPort
    };

    var errorMessage = error.message;
    var formatMessage = messages[error.code];

    return formatMessage ?
      util.format(templates.formatted, formatMessage, errorMessage) : errorMessage;
  }

  var consulHost = process.env.CONSUL_HOST || 'consul';
  var consulPort = process.env.CONSUL_PORT || '8500';
  var registration = new RegistrationService(consulHost, consulPort);

  try {
    // Retrieve arguments passed to script
    var args = new CommandLineResolver().processArgs();

    if (args.command === 'register') {
      registration.register(args);
    } else if (args.command === 'deregister') {
      registration.deregister(args);
    } else {
      throw new Error("Invalid command. It should be either 'register' or 'deregister'.")
    }
  } catch(err) {
    console.error("[ERROR]:")
    console.error(formatError(err));
    process.exit(1);
  }
})();
<|endoftext|>


<commit_before>/*!
 *
 * Copyright (c) 2013 Sebastian Golasch
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 *
 */

'use strict';

/**
 * Checks & loads reporter modules
 *
 * @module DalekJS
 * @class Reporter
 * @namespace Dalek
 */

var Reporter = {

  /**
   * Checks if the requested reporter exists
   *
   * @method isReporter
   * @param {string} reporter Name of the reporter
   * @return {bool} isReporter Reporter exists
   */

  isReporter: function (reporter) {
    try {
      require.resolve('dalek-reporter-' + reporter);
    } catch (e) {
      return false;
    }
    return true;
  },

  /**
   * Loads a requested reporter
   *
   * @method loadReporter
   * @param {string} reporter Name of the reporter
   * @param {object} options Options to pass to the reporter
   * @return {object} reporterInstance Reporter instance
   */

  loadReporter: function (reporter, options) {
    return require('dalek-reporter-' + reporter)(options);
  }
};

// export the module
module.exports = Reporter;
<commit_msg>Updated dependencies, added coverage badge, added trello board, added npm badges<commit_after>/*!
 *
 * Copyright (c) 2013 Sebastian Golasch
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 *
 */

'use strict';

/**
 * Checks & loads reporter modules
 *
 * @module DalekJS
 * @class Reporter
 * @namespace Dalek
 * @part Reporter
 * @api
 */

var Reporter = {

  /**
   * Checks if the requested reporter exists
   *
   * @method isReporter
   * @param {string} reporter Name of the reporter
   * @return {bool} isReporter Reporter exists
   */

  isReporter: function (reporter) {
    try {
      require.resolve('dalek-reporter-' + reporter);
    } catch (e) {
      return false;
    }
    return true;
  },

  /**
   * Loads a requested reporter
   *
   * @method loadReporter
   * @param {string} reporter Name of the reporter
   * @param {object} options Options to pass to the reporter
   * @return {object} reporterInstance Reporter instance
   */

  loadReporter: function (reporter, options) {
    return require('dalek-reporter-' + reporter)(options);
  }
};

// export the module
module.exports = Reporter;
<|endoftext|>


<commit_before>/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */

'use strict';

var EXPORTED_SYMBOLS = ['FlashStreamConverter1', 'FlashStreamConverter2'];

const Cc = Components.classes;
const Ci = Components.interfaces;
const Cr = Components.results;
const Cu = Components.utils;

// True only if this is the version of pdf.js that is included with firefox.
const SHUMWAY_CONTENT_TYPE = 'application/x-shockwave-flash';
const EXPECTED_PLAYPREVIEW_URI_PREFIX = 'data:application/x-moz-playpreview;,' +
                                        SHUMWAY_CONTENT_TYPE;

const FIREFOX_ID = '{ec8030f7-c20a-464f-9b0e-13a3a9e97384}';
const SEAMONKEY_ID = '{92650c4d-4b8e-4d2a-b7eb-24ecf4f6b63a}';

Cu.import('resource://gre/modules/XPCOMUtils.jsm');
Cu.import('resource://gre/modules/Services.jsm');
Cu.import('resource://gre/modules/NetUtil.jsm');

let appInfo = Cc['@mozilla.org/xre/app-info;1']
                  .getService(Ci.nsIXULAppInfo);
let Svc = {};
XPCOMUtils.defineLazyServiceGetter(Svc, 'mime',
                                   '@mozilla.org/mime;1',
                                   'nsIMIMEService');

function getBoolPref(pref, def) {
  try {
    return Services.prefs.getBoolPref(pref);
  } catch (ex) {
    return def;
  }
}

function log(aMsg) {
  let msg = 'FlashStreamConverter.js: ' + (aMsg.join ? aMsg.join('') : aMsg);
  Services.console.logStringMessage(msg);
  dump(msg + '\n');
}

function getDOMWindow(aChannel) {
  var requestor = aChannel.notificationCallbacks;
  var win = requestor.getInterface(Components.interfaces.nsIDOMWindow);
  return win;
}

// Combines two URLs. The baseUrl shall be absolute URL. If the url is an
// absolute URL, it will be returned as is.
function combineUrl(baseUrl, url) {
  if (url.indexOf(':') >= 0)
    return url;
  if (url.charAt(0) == '/') {
    // absolute path
    var i = baseUrl.indexOf('://');
    i = baseUrl.indexOf('/', i + 3);
    return baseUrl.substring(0, i) + url;
  } else {
    // relative path
    var pathLength = baseUrl.length, i;
    i = baseUrl.lastIndexOf('#');
    pathLength = i >= 0 ? i : pathLength;
    i = baseUrl.lastIndexOf('?', pathLength);
    pathLength = i >= 0 ? i : pathLength;
    var prefixLength = baseUrl.lastIndexOf('/', pathLength);
    return baseUrl.substring(0, prefixLength + 1) + url;
  }
}

function parseQueryString(qs) {
  if (!qs)
    return {};

  if (qs.charAt(0) == '?')
    qs = qs.slice(1);

  var values = qs.split('&');
  var obj = {};
  for (var i = 0; i < values.length; i++) {
    var kv = values[i].split('=');
    var key = kv[0], value = kv[1];
    obj[key] = value;
  }

  return obj;
}

// All the priviledged actions.
function ChromeActions(url, params, baseUrl, window) {
  this.url = url;
  this.params = params;
  this.baseUrl = baseUrl;
  this.isOverlay = false;
  this.isPausedAtStart = false;
  this.window = window;
}

ChromeActions.prototype = {
  getBoolPref: function (data) {
    if (!/^shumway\./.test(data.pref)) {
      return null;
    }
    return getBoolPref(data.pref, data.def);
  },
  getPluginParams: function getPluginParams() {
    return JSON.stringify({
      url: this.url,
      baseUrl : this.baseUrl,
      params: this.params,
      isOverlay: this.isOverlay,
      isPausedAtStart: this.isPausedAtStart
     });
  },
  _canDownloadFile: function canDownloadFile(url, checkPolicyFile) {
    // TODO flash cross-origin request
    if (url === this.url)
      return true; // allow downloading for the original file

    // let's allow downloading from http(s) and same origin
    var basePrefix = /^(https?:\/\/[A-Za-z0-9\-_\.:\[\]]+\/)/i.exec(this.url);
    if (basePrefix) {
      var urlPrefix = /^(https?:\/\/[A-Za-z0-9\-_\.:\[\]]+\/)/i.exec(url);
      if (urlPrefix && basePrefix[1] === urlPrefix[1])
        return true;
    }

    return false;
  },
  loadFile: function loadFile(data) {
    var url = data.url;
    var checkPolicyFile = data.checkPolicyFile;
    var sessionId = data.sessionId;
    var limit = data.limit || 0;
    var method = data.method || "GET";
    var postData = data.postData || null;

    if (!this._canDownloadFile(url, checkPolicyFile)) {
      log("bad url " + url + " " + this.url);
      win.postMessage({callback:"loadFile", sessionId: sessionId, topic: "error",
        error: "only original swf file or file from the same origin loading supported"}, "*");
      return;
    }

    var win = this.window;

    var xhr = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"]
                         .createInstance(Ci.nsIXMLHttpRequest);
    xhr.open(method, url, true);
    // arraybuffer is not provide onprogress, fetching as regular chars
    if ('overrideMimeType' in xhr)
      xhr.overrideMimeType('text/plain; charset=x-user-defined');

    if (this.baseUrl) {
      // Setting the referer uri, some site doing checks if swf is embedded
      // on the original page.
      xhr.setRequestHeader("Referer", this.baseUrl);
    }

    // TODO apply range request headers if limit is specified

    var lastPosition = 0;
    xhr.onprogress = function (e) {
      var position = e.loaded;
      var chunk = xhr.responseText.substring(lastPosition, position);
      var data = new Uint8Array(chunk.length);
      for (var i = 0; i < data.length; i++)
        data[i] = chunk.charCodeAt(i) & 0xFF;
      win.postMessage({callback:"loadFile", sessionId: sessionId, topic: "progress",
                       array: data, loaded: e.loaded, total: e.total}, "*");
      lastPosition = position;
      if (limit && e.total >= limit) {
        xhr.abort();
      }
    };
    xhr.onreadystatechange = function(event) {
      if (xhr.readyState === 4) {
        if (xhr.status !== 200 && xhr.status !== 0) {
          win.postMessage({callback:"loadFile", sessionId: sessionId, topic: "error",
                           error: xhr.statusText}, "*");
        }
        win.postMessage({callback:"loadFile", sessionId: sessionId, topic: "close"}, "*");
      }
    }
    xhr.send(postData);
    win.postMessage({callback:"loadFile", sessionId: sessionId, topic: "open"}, "*");
  },
  fallback: function() {
    var obj = this.window.frameElement;
    var doc = obj.ownerDocument;
    var e = doc.createEvent("CustomEvent");
    e.initCustomEvent("MozPlayPlugin", true, true, null);
    obj.dispatchEvent(e);
  }
};

// Event listener to trigger chrome privedged code.
function RequestListener(actions) {
  this.actions = actions;
}
// Receive an event and synchronously or asynchronously responds.
RequestListener.prototype.receive = function(event) {
  var message = event.target;
  var doc = message.ownerDocument;
  var action = event.detail.action;
  var data = event.detail.data;
  var sync = event.detail.sync;
  var actions = this.actions;
  if (!(action in actions)) {
    log('Unknown action: ' + action);
    return;
  }
  if (sync) {
    var response = actions[action].call(this.actions, data);
    var detail = event.detail;
    detail.__exposedProps__ = {response: 'r'};
    detail.response = response;
  } else {
    var response;
    if (!event.detail.callback) {
      doc.documentElement.removeChild(message);
      response = null;
    } else {
      response = function sendResponse(response) {
        try {
          var listener = doc.createEvent('CustomEvent');
          listener.initCustomEvent('shumway.response', true, false,
                                   {response: response,
                                    __exposedProps__: {response: 'r'}});

          return message.dispatchEvent(listener);
        } catch (e) {
          // doc is no longer accessible because the requestor is already
          // gone. unloaded content cannot receive the response anyway.
        }
      };
    }
    actions[action].call(this.actions, data, response);
  }
};

function FlashStreamConverterBase() {
}

FlashStreamConverterBase.prototype = {
  QueryInterface: XPCOMUtils.generateQI([
      Ci.nsISupports,
      Ci.nsIStreamConverter,
      Ci.nsIStreamListener,
      Ci.nsIRequestObserver
  ]),

  /*
   * This component works as such:
   * 1. asyncConvertData stores the listener
   * 2. onStartRequest creates a new channel, streams the viewer and cancels
   *    the request so pdf.js can do the request
   * Since the request is cancelled onDataAvailable should not be called. The
   * onStopRequest does nothing. The convert function just returns the stream,
   * it's just the synchronous version of asyncConvertData.
   */

  // nsIStreamConverter::convert
  convert: function(aFromStream, aFromType, aToType, aCtxt) {
    throw Cr.NS_ERROR_NOT_IMPLEMENTED;
  },

  isValidRequest: function() {
    return true;
  },

  createChromeActions: function(window, urlHint) {
    var url;
    var element = window.frameElement;
    var isOverlay = false;
    var params = {};
    if (element) {
      var tagName = element.nodeName;
      while (tagName != 'EMBED' && tagName != 'OBJECT') {
        // plugin overlay skipping until the target plugin is found
        isOverlay = true;
        element = element.parentNode;
        if (!element)
          throw 'Plugin element is not found';
        tagName = element.nodeName;
      }
      if (tagName == 'EMBED') {
        params = parseQueryString(element.getAttribute('flashvars'));
        url = element.getAttribute('src');
      } else {
        for (var i = 0; i < element.childNodes.length; ++i) {
          var paramElement = element.childNodes[i];
          if (paramElement.nodeType != 1 ||
              paramElement.nodeName != 'PARAM') continue;

          params[paramElement.getAttribute('name')] = paramElement.getAttribute('value');
        }
        var dataAttribute = element.getAttribute('data');
        url = dataAttribute || params.movie || params.src;
      }
    }
    var element = window.frameElement;
    var baseUrl = element ? element.ownerDocument.location.href : null; // XXX base url?

    url = url ? combineUrl(baseUrl, url) : urlHint;

    var actions = new ChromeActions(url, params, baseUrl, window);
    actions.isOverlay = isOverlay;
    actions.isPausedAtStart = /\bpaused=true$/.test(urlHint);
    return actions;
  },

  // nsIStreamConverter::asyncConvertData
  asyncConvertData: function(aFromType, aToType, aListener, aCtxt) {
    if(!this.isValidRequest(aCtxt))
      throw Cr.NS_ERROR_NOT_IMPLEMENTED;

    // Store the listener passed to us
    this.listener = aListener;
  },

  // nsIStreamListener::onDataAvailable
  onDataAvailable: function(aRequest, aContext, aInputStream, aOffset, aCount) {
    // Do nothing since all the data loading is handled by the viewer.
    log('SANITY CHECK: onDataAvailable SHOULD NOT BE CALLED!');
  },

  // nsIRequestObserver::onStartRequest
  onStartRequest: function(aRequest, aContext) {

    // Setup the request so we can use it below.
    aRequest.QueryInterface(Ci.nsIChannel);
    // Cancel the request so the viewer can handle it.
    aRequest.cancel(Cr.NS_BINDING_ABORTED);

    var originalURI = aRequest.URI;

    // checking if the plug-in shall be run in simple mode
    var isSimpleMode = originalURI.spec === EXPECTED_PLAYPREVIEW_URI_PREFIX &&
      getBoolPref('shumway.simpleMode', false);

    // Create a new channel that is viewer loaded as a resource.
    var ioService = Services.io;
    var channel = ioService.newChannel(isSimpleMode ?
                    'resource://shumway/web/simple.html' :
                    'resource://shumway/web/viewer.html', null, null);

    var converter = this;
    var listener = this.listener;
    // Proxy all the request observer calls, when it gets to onStopRequest
    // we can get the dom window.
    var proxy = {
      onStartRequest: function() {
        listener.onStartRequest.apply(listener, arguments);
      },
      onDataAvailable: function() {
        listener.onDataAvailable.apply(listener, arguments);
      },
      onStopRequest: function() {
        var domWindow = getDOMWindow(channel);
        // Double check the url is still the correct one.
        if (domWindow.document.documentURIObject.equals(channel.originalURI)) {
          let actions = converter.createChromeActions(domWindow,
                                                      originalURI.spec);
          let requestListener = new RequestListener(actions);
          domWindow.addEventListener('shumway.message', function(event) {
            requestListener.receive(event);
          }, false, true);
        }
        listener.onStopRequest.apply(listener, arguments);
      }
    };

    // XXX? Keep the URL the same so the browser sees it as the same.
    // channel.originalURI = aRequest.URI;
    channel.asyncOpen(proxy, aContext);
  },

  // nsIRequestObserver::onStopRequest
  onStopRequest: function(aRequest, aContext, aStatusCode) {
    // Do nothing.
  }
};

// properties required for XPCOM registration:
function copyProperties(obj, template) {
  for (var prop in template) {
    obj[prop] = template[prop];
  }
}

function FlashStreamConverter1() {}
FlashStreamConverter1.prototype = new FlashStreamConverterBase();
copyProperties(FlashStreamConverter1.prototype, {
  classID: Components.ID('{4c6030f7-e20a-264f-5b0e-ada3a9e97384}'),
  classDescription: 'Shumway Content Converter Component',
  contractID: '@mozilla.org/streamconv;1?from=application/x-shockwave-flash&to=*/*'
});

function FlashStreamConverter2() {}
FlashStreamConverter2.prototype = new FlashStreamConverterBase();
copyProperties(FlashStreamConverter2.prototype, {
  classID: Components.ID('{4c6030f7-e20a-264f-5f9b-ada3a9e97384}'),
  classDescription: 'Shumway PlayPreview Component',
  contractID: '@mozilla.org/streamconv;1?from=application/x-moz-playpreview&to=*/*'
});
FlashStreamConverter2.prototype.isValidRequest =
  (function(aCtxt) {
    try {
      var request = aCtxt;
      request.QueryInterface(Ci.nsIChannel);
      var spec = request.URI.spec;
      return spec.indexOf(EXPECTED_PLAYPREVIEW_URI_PREFIX) === 0;
    } catch (e) {
      return false;
    }
  });

var NSGetFactory1 = XPCOMUtils.generateNSGetFactory([FlashStreamConverter1]);
var NSGetFactory2 = XPCOMUtils.generateNSGetFactory([FlashStreamConverter2]);
<commit_msg>FlashStreamConverter: Use the flashvars <param> tag, not any <param> tag<commit_after>/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */

'use strict';

var EXPORTED_SYMBOLS = ['FlashStreamConverter1', 'FlashStreamConverter2'];

const Cc = Components.classes;
const Ci = Components.interfaces;
const Cr = Components.results;
const Cu = Components.utils;

// True only if this is the version of pdf.js that is included with firefox.
const SHUMWAY_CONTENT_TYPE = 'application/x-shockwave-flash';
const EXPECTED_PLAYPREVIEW_URI_PREFIX = 'data:application/x-moz-playpreview;,' +
                                        SHUMWAY_CONTENT_TYPE;

const FIREFOX_ID = '{ec8030f7-c20a-464f-9b0e-13a3a9e97384}';
const SEAMONKEY_ID = '{92650c4d-4b8e-4d2a-b7eb-24ecf4f6b63a}';

Cu.import('resource://gre/modules/XPCOMUtils.jsm');
Cu.import('resource://gre/modules/Services.jsm');
Cu.import('resource://gre/modules/NetUtil.jsm');

let appInfo = Cc['@mozilla.org/xre/app-info;1']
                  .getService(Ci.nsIXULAppInfo);
let Svc = {};
XPCOMUtils.defineLazyServiceGetter(Svc, 'mime',
                                   '@mozilla.org/mime;1',
                                   'nsIMIMEService');

function getBoolPref(pref, def) {
  try {
    return Services.prefs.getBoolPref(pref);
  } catch (ex) {
    return def;
  }
}

function log(aMsg) {
  let msg = 'FlashStreamConverter.js: ' + (aMsg.join ? aMsg.join('') : aMsg);
  Services.console.logStringMessage(msg);
  dump(msg + '\n');
}

function getDOMWindow(aChannel) {
  var requestor = aChannel.notificationCallbacks;
  var win = requestor.getInterface(Components.interfaces.nsIDOMWindow);
  return win;
}

// Combines two URLs. The baseUrl shall be absolute URL. If the url is an
// absolute URL, it will be returned as is.
function combineUrl(baseUrl, url) {
  if (url.indexOf(':') >= 0)
    return url;
  if (url.charAt(0) == '/') {
    // absolute path
    var i = baseUrl.indexOf('://');
    i = baseUrl.indexOf('/', i + 3);
    return baseUrl.substring(0, i) + url;
  } else {
    // relative path
    var pathLength = baseUrl.length, i;
    i = baseUrl.lastIndexOf('#');
    pathLength = i >= 0 ? i : pathLength;
    i = baseUrl.lastIndexOf('?', pathLength);
    pathLength = i >= 0 ? i : pathLength;
    var prefixLength = baseUrl.lastIndexOf('/', pathLength);
    return baseUrl.substring(0, prefixLength + 1) + url;
  }
}

function parseQueryString(qs) {
  if (!qs)
    return {};

  if (qs.charAt(0) == '?')
    qs = qs.slice(1);

  var values = qs.split('&');
  var obj = {};
  for (var i = 0; i < values.length; i++) {
    var kv = values[i].split('=');
    var key = kv[0], value = kv[1];
    obj[key] = value;
  }

  return obj;
}

// All the priviledged actions.
function ChromeActions(url, params, baseUrl, window) {
  this.url = url;
  this.params = params;
  this.baseUrl = baseUrl;
  this.isOverlay = false;
  this.isPausedAtStart = false;
  this.window = window;
}

ChromeActions.prototype = {
  getBoolPref: function (data) {
    if (!/^shumway\./.test(data.pref)) {
      return null;
    }
    return getBoolPref(data.pref, data.def);
  },
  getPluginParams: function getPluginParams() {
    return JSON.stringify({
      url: this.url,
      baseUrl : this.baseUrl,
      params: this.params,
      isOverlay: this.isOverlay,
      isPausedAtStart: this.isPausedAtStart
     });
  },
  _canDownloadFile: function canDownloadFile(url, checkPolicyFile) {
    // TODO flash cross-origin request
    if (url === this.url)
      return true; // allow downloading for the original file

    // let's allow downloading from http(s) and same origin
    var basePrefix = /^(https?:\/\/[A-Za-z0-9\-_\.:\[\]]+\/)/i.exec(this.url);
    if (basePrefix) {
      var urlPrefix = /^(https?:\/\/[A-Za-z0-9\-_\.:\[\]]+\/)/i.exec(url);
      if (urlPrefix && basePrefix[1] === urlPrefix[1])
        return true;
    }

    return false;
  },
  loadFile: function loadFile(data) {
    var url = data.url;
    var checkPolicyFile = data.checkPolicyFile;
    var sessionId = data.sessionId;
    var limit = data.limit || 0;
    var method = data.method || "GET";
    var postData = data.postData || null;

    if (!this._canDownloadFile(url, checkPolicyFile)) {
      log("bad url " + url + " " + this.url);
      win.postMessage({callback:"loadFile", sessionId: sessionId, topic: "error",
        error: "only original swf file or file from the same origin loading supported"}, "*");
      return;
    }

    var win = this.window;

    var xhr = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"]
                         .createInstance(Ci.nsIXMLHttpRequest);
    xhr.open(method, url, true);
    // arraybuffer is not provide onprogress, fetching as regular chars
    if ('overrideMimeType' in xhr)
      xhr.overrideMimeType('text/plain; charset=x-user-defined');

    if (this.baseUrl) {
      // Setting the referer uri, some site doing checks if swf is embedded
      // on the original page.
      xhr.setRequestHeader("Referer", this.baseUrl);
    }

    // TODO apply range request headers if limit is specified

    var lastPosition = 0;
    xhr.onprogress = function (e) {
      var position = e.loaded;
      var chunk = xhr.responseText.substring(lastPosition, position);
      var data = new Uint8Array(chunk.length);
      for (var i = 0; i < data.length; i++)
        data[i] = chunk.charCodeAt(i) & 0xFF;
      win.postMessage({callback:"loadFile", sessionId: sessionId, topic: "progress",
                       array: data, loaded: e.loaded, total: e.total}, "*");
      lastPosition = position;
      if (limit && e.total >= limit) {
        xhr.abort();
      }
    };
    xhr.onreadystatechange = function(event) {
      if (xhr.readyState === 4) {
        if (xhr.status !== 200 && xhr.status !== 0) {
          win.postMessage({callback:"loadFile", sessionId: sessionId, topic: "error",
                           error: xhr.statusText}, "*");
        }
        win.postMessage({callback:"loadFile", sessionId: sessionId, topic: "close"}, "*");
      }
    }
    xhr.send(postData);
    win.postMessage({callback:"loadFile", sessionId: sessionId, topic: "open"}, "*");
  },
  fallback: function() {
    var obj = this.window.frameElement;
    var doc = obj.ownerDocument;
    var e = doc.createEvent("CustomEvent");
    e.initCustomEvent("MozPlayPlugin", true, true, null);
    obj.dispatchEvent(e);
  }
};

// Event listener to trigger chrome privedged code.
function RequestListener(actions) {
  this.actions = actions;
}
// Receive an event and synchronously or asynchronously responds.
RequestListener.prototype.receive = function(event) {
  var message = event.target;
  var doc = message.ownerDocument;
  var action = event.detail.action;
  var data = event.detail.data;
  var sync = event.detail.sync;
  var actions = this.actions;
  if (!(action in actions)) {
    log('Unknown action: ' + action);
    return;
  }
  if (sync) {
    var response = actions[action].call(this.actions, data);
    var detail = event.detail;
    detail.__exposedProps__ = {response: 'r'};
    detail.response = response;
  } else {
    var response;
    if (!event.detail.callback) {
      doc.documentElement.removeChild(message);
      response = null;
    } else {
      response = function sendResponse(response) {
        try {
          var listener = doc.createEvent('CustomEvent');
          listener.initCustomEvent('shumway.response', true, false,
                                   {response: response,
                                    __exposedProps__: {response: 'r'}});

          return message.dispatchEvent(listener);
        } catch (e) {
          // doc is no longer accessible because the requestor is already
          // gone. unloaded content cannot receive the response anyway.
        }
      };
    }
    actions[action].call(this.actions, data, response);
  }
};

function FlashStreamConverterBase() {
}

FlashStreamConverterBase.prototype = {
  QueryInterface: XPCOMUtils.generateQI([
      Ci.nsISupports,
      Ci.nsIStreamConverter,
      Ci.nsIStreamListener,
      Ci.nsIRequestObserver
  ]),

  /*
   * This component works as such:
   * 1. asyncConvertData stores the listener
   * 2. onStartRequest creates a new channel, streams the viewer and cancels
   *    the request so pdf.js can do the request
   * Since the request is cancelled onDataAvailable should not be called. The
   * onStopRequest does nothing. The convert function just returns the stream,
   * it's just the synchronous version of asyncConvertData.
   */

  // nsIStreamConverter::convert
  convert: function(aFromStream, aFromType, aToType, aCtxt) {
    throw Cr.NS_ERROR_NOT_IMPLEMENTED;
  },

  isValidRequest: function() {
    return true;
  },

  createChromeActions: function(window, urlHint) {
    var url;
    var element = window.frameElement;
    var isOverlay = false;
    var params = {};
    if (element) {
      var tagName = element.nodeName;
      while (tagName != 'EMBED' && tagName != 'OBJECT') {
        // plugin overlay skipping until the target plugin is found
        isOverlay = true;
        element = element.parentNode;
        if (!element)
          throw 'Plugin element is not found';
        tagName = element.nodeName;
      }
      if (tagName == 'EMBED') {
        params = parseQueryString(element.getAttribute('flashvars'));
        url = element.getAttribute('src');
      } else {
        for (var i = 0; i < element.childNodes.length; ++i) {
          var paramElement = element.childNodes[i];
          if (paramElement.nodeType != 1 ||
              paramElement.nodeName != 'PARAM' ||
              paramElement.getAttribute('name').toLowerCase() != 'flashvars')
            continue;

          params = parseQueryString(paramElement.getAttribute('value'));
        }
        var dataAttribute = element.getAttribute('data');
        url = dataAttribute || params.movie || params.src;
      }
    }
    var element = window.frameElement;
    var baseUrl = element ? element.ownerDocument.location.href : null; // XXX base url?

    url = url ? combineUrl(baseUrl, url) : urlHint;

    var actions = new ChromeActions(url, params, baseUrl, window);
    actions.isOverlay = isOverlay;
    actions.isPausedAtStart = /\bpaused=true$/.test(urlHint);
    return actions;
  },

  // nsIStreamConverter::asyncConvertData
  asyncConvertData: function(aFromType, aToType, aListener, aCtxt) {
    if(!this.isValidRequest(aCtxt))
      throw Cr.NS_ERROR_NOT_IMPLEMENTED;

    // Store the listener passed to us
    this.listener = aListener;
  },

  // nsIStreamListener::onDataAvailable
  onDataAvailable: function(aRequest, aContext, aInputStream, aOffset, aCount) {
    // Do nothing since all the data loading is handled by the viewer.
    log('SANITY CHECK: onDataAvailable SHOULD NOT BE CALLED!');
  },

  // nsIRequestObserver::onStartRequest
  onStartRequest: function(aRequest, aContext) {

    // Setup the request so we can use it below.
    aRequest.QueryInterface(Ci.nsIChannel);
    // Cancel the request so the viewer can handle it.
    aRequest.cancel(Cr.NS_BINDING_ABORTED);

    var originalURI = aRequest.URI;

    // checking if the plug-in shall be run in simple mode
    var isSimpleMode = originalURI.spec === EXPECTED_PLAYPREVIEW_URI_PREFIX &&
      getBoolPref('shumway.simpleMode', false);

    // Create a new channel that is viewer loaded as a resource.
    var ioService = Services.io;
    var channel = ioService.newChannel(isSimpleMode ?
                    'resource://shumway/web/simple.html' :
                    'resource://shumway/web/viewer.html', null, null);

    var converter = this;
    var listener = this.listener;
    // Proxy all the request observer calls, when it gets to onStopRequest
    // we can get the dom window.
    var proxy = {
      onStartRequest: function() {
        listener.onStartRequest.apply(listener, arguments);
      },
      onDataAvailable: function() {
        listener.onDataAvailable.apply(listener, arguments);
      },
      onStopRequest: function() {
        var domWindow = getDOMWindow(channel);
        // Double check the url is still the correct one.
        if (domWindow.document.documentURIObject.equals(channel.originalURI)) {
          let actions = converter.createChromeActions(domWindow,
                                                      originalURI.spec);
          let requestListener = new RequestListener(actions);
          domWindow.addEventListener('shumway.message', function(event) {
            requestListener.receive(event);
          }, false, true);
        }
        listener.onStopRequest.apply(listener, arguments);
      }
    };

    // XXX? Keep the URL the same so the browser sees it as the same.
    // channel.originalURI = aRequest.URI;
    channel.asyncOpen(proxy, aContext);
  },

  // nsIRequestObserver::onStopRequest
  onStopRequest: function(aRequest, aContext, aStatusCode) {
    // Do nothing.
  }
};

// properties required for XPCOM registration:
function copyProperties(obj, template) {
  for (var prop in template) {
    obj[prop] = template[prop];
  }
}

function FlashStreamConverter1() {}
FlashStreamConverter1.prototype = new FlashStreamConverterBase();
copyProperties(FlashStreamConverter1.prototype, {
  classID: Components.ID('{4c6030f7-e20a-264f-5b0e-ada3a9e97384}'),
  classDescription: 'Shumway Content Converter Component',
  contractID: '@mozilla.org/streamconv;1?from=application/x-shockwave-flash&to=*/*'
});

function FlashStreamConverter2() {}
FlashStreamConverter2.prototype = new FlashStreamConverterBase();
copyProperties(FlashStreamConverter2.prototype, {
  classID: Components.ID('{4c6030f7-e20a-264f-5f9b-ada3a9e97384}'),
  classDescription: 'Shumway PlayPreview Component',
  contractID: '@mozilla.org/streamconv;1?from=application/x-moz-playpreview&to=*/*'
});
FlashStreamConverter2.prototype.isValidRequest =
  (function(aCtxt) {
    try {
      var request = aCtxt;
      request.QueryInterface(Ci.nsIChannel);
      var spec = request.URI.spec;
      return spec.indexOf(EXPECTED_PLAYPREVIEW_URI_PREFIX) === 0;
    } catch (e) {
      return false;
    }
  });

var NSGetFactory1 = XPCOMUtils.generateNSGetFactory([FlashStreamConverter1]);
var NSGetFactory2 = XPCOMUtils.generateNSGetFactory([FlashStreamConverter2]);
<|endoftext|>


<commit_before>/**
 * Mousetrap is a simple keyboard command library for Javascript that does
 * not depend on using any existing framework
 *
 * @author Craig Campbell <iamcraigcampbell@gmail.com>
 */
window['Mousetrap'] = (function() {

    /**
     * mapping of special keys
     */
    var _MAP = {
            'backspace': 8,
            'tab': 9,
            'enter': 13,
            'return': 13,
            'shift': 16,
            'ctrl': 17,
            'alt': 18,
            'option': 18,
            'capslock': 20,
            'esc': 27,
            'escape': 27,
            'space': 32,
            'pageup': 33,
            'pagedown': 34,
            'end': 35,
            'home': 36,
            'left': 37,
            'up': 38,
            'right': 39,
            'down': 40,
            'del': 46,
            'meta': 91,
            'command': 91,
            ';': 186,
            '=': 187,
            ',': 188,
            '-': 189,
            '.': 190,
            '/': 191,
            '`': 192,
            '[': 219,
            '\\': 220,
            ']': 221,
            '\'': 222
        },

        _SHIFT_MAP = {
            '~': '`',
            '!': '1',
            '@': '2',
            '#': '3',
            '$': '4',
            '%': '5',
            '^': '6',
            '&': '7',
            '*': '8',
            '(': '9',
            ')': '0',
            '_': '-',
            '+': '=',
            ':': ';',
            '\"': '\'',
            '<': ',',
            '>': '.',
            '?': '/',
            '|': '\\'
        },

        /**
         * a list of all the callbacks setup via Mousetrap.bind()
         */
        _callbacks = {},

        /**
         * direct map used for trigger()
         */
        _direct_map = {},

        _chain_levels = {},

        _reset_timer;

        for (var i = 1; i < 20; ++i) {
            _MAP['f' + i] = 111 + i;
        }

    /**
     * cross browser add event
     */
    function _addEvent(object, type, callback) {
        if (object.addEventListener) {
            return object.addEventListener(type, callback, false);
        }

        object.attachEvent('on' + type, callback);
    }

    function _keyCodeFromEvent(e) {
        // right command on webkit, command on gecko
        if (e.keyCode == 93 || e.keyCode == 224) {
            return 91;
        }

        return e.keyCode;
    }

    function _stop(e) {
        var element = e.target || e.srcElement,
            tag_name = element.tagName;

        // if the element has the class "mousetrap" then still fire off events
        if ((' ' + element.className + ' ').indexOf(' mousetrap ') > -1) {
            return false;
        }

        // stop for input, select, and textarea
        return tag_name == 'INPUT' || tag_name == 'SELECT' || tag_name == 'TEXTAREA';
    }

    function _modifiersMatch(group1, group2) {
        return group1.sort().join(',') === group2.sort().join(',');
    }

    function _resetCounters(no_reset) {
        no_reset = no_reset || {};

        for (var key in _chain_levels) {
            if (!no_reset[key]) {
                _chain_levels[key] = 0;
            }
        }
    }

    function _getMatches(code, modifiers, action, remove) {
        var i,
            is_modifier = _isModifier(code),
            callback,
            matches = [],
            chain_match = false;

        if (!_callbacks[code]) {
            if (!is_modifier) {
                _resetCounters();
            }
            return [];
        }

        // if a modifier key is coming up we should allow it
        if (action == 'up' && is_modifier) {
            modifiers = [code];
        }

        // loop through all callbacks for the key that was pressed
        // and see if any of them match
        for (i = 0; i < _callbacks[code].length; ++i) {
            callback = _callbacks[code][i];

            // if this is a chain but it is not at the right level
            // then move onto the next match
            if (callback['chain'] && _chain_levels[callback['chain']] != callback['level']) {
                continue;
            }

            if (action == callback.action && _modifiersMatch(modifiers, callback.modifiers)) {
                if (callback['chain']) {
                    chain_match = true;
                }

                if (remove) {
                    _callbacks[code].splice(i, 1);
                }

                matches.push(callback);
            }
        }

        return matches;
    }

    function _eventModifiers(e) {
        var modifiers = [];

        if (e.shiftKey) {
            modifiers.push(_MAP.shift);
        }

        if (e.altKey) {
            modifiers.push(_MAP.alt);
        }

        if (e.ctrlKey) {
            modifiers.push(_MAP.ctrl);
        }

        if (e.metaKey) {
            modifiers.push(_MAP.command);
        }

        return modifiers;
    }

    function _fireCallback(code, action, e) {
        var callbacks = _getMatches(code, _eventModifiers(e), action),
            i,
            do_not_reset = {},
            processed_chain_callback = false,
            apply_reset = !_isModifier(code);

        // console.log('matching callbacks', callbacks);

        for (i = 0; i < callbacks.length; ++i) {

            // fire for all chain callbacks
            if (callbacks[i]['chain']) {
                processed_chain_callback = true;
                do_not_reset[callbacks[i]['chain']] = 1;
                callbacks[i].callback(e);
                continue;
            }

            // first non chain callback fire and exit
            if (!processed_chain_callback) {
                callbacks[i].callback(e);
                break;
            }
        }

        if ((apply_reset || processed_chain_callback) && callbacks.length) {
            _resetCounters(do_not_reset);
        }
    }

    function _handleKeyDown(e) {
        if (_stop(e)) {
            return;
        }

        return _fireCallback(_keyCodeFromEvent(e), '', e);
    }

    function _handleKeyUp(e) {
        if (_stop(e)) {
            return;
        }

        _fireCallback(_keyCodeFromEvent(e), 'up', e);
    }

    function _isModifier(code) {
        return (code > 15 && code < 19) || code == 91;
    }

    function _resetChain(chain) {
        clearTimeout(_reset_timer);
        _reset_timer = setTimeout(function() {
            _chain_levels[chain] = 0;
        }, 1000);
    }

    function _bindChain(combo, keys, callback, action) {
        _chain_levels[combo] = 0;

        var _increaseChain = function() {
                ++_chain_levels[combo];
                _resetChain(combo);
            },

            _callbackAndReset = function() {
                callback();

                // weird race condition if a sequence ends with the key
                // another sequence begins with
                setTimeout(_resetCounters, 10);
            },
            i;

        for (i = 0; i < keys.length; ++i) {
            _bindSingle(keys[i], i < keys.length - 1 ? _increaseChain : _callbackAndReset, action, combo, i);
        }
    }

    /**
     * binds a single event
     */
    function _bindSingle(combination, callback, action, chain_name, level) {

        // strip out any spaces around a plus sign
        // also make sure multiple spaces in a row become a single space
        combination = combination.replace(/\s+\+\s+/g, '+').replace(/\s+/, ' ');

        var chain = combination.split(' '),
            i,
            key,
            keys,
            modifiers = [];

        if (chain.length > 1) {
            return _bindChain(combination, chain, callback, action);
        }

        keys = combination.split('+');

        for (i = 0; i < keys.length; ++i) {
            key = keys[i];

            if (_SHIFT_MAP[key]) {
                modifiers.push(_MAP.shift);
                key = _SHIFT_MAP[key];
            }

            key = _MAP[key] || key.toUpperCase().charCodeAt(0);

            if (_isModifier(key)) {
                modifiers.push(key);
            }
        }

        // make sure to initialize array if this is the first time
        // a callback is added for this key
        if (!_callbacks[key]) {
            _callbacks[key] = [];
        }

        // remove an existing match if there is one
        _getMatches(key, modifiers, action, !!!chain_name);

        // add this call back to the array
        // if it is a chain put it at the beginning
        // if not put it at the end
        _callbacks[key][chain_name ? 'unshift' : 'push']({
            callback: callback,
            modifiers: modifiers,
            action: action,
            chain: chain_name,
            level: level
        });
    }

    /**
     * binds multiple combinations to the same callback
     */
    function _bindMultiple(combinations, callback, action) {
        for (var i = 0; i < combinations.length; ++i) {
            _bindSingle(combinations[i], callback, action);
        }
    }

    return {
        bind: function(keys, callback, action) {
            action = action == 'up' ? 'up' : '';
            _bindMultiple(keys instanceof Array ? keys : keys.split(','), callback, action);
            _direct_map[keys + ':' + action] = callback;
        },

        trigger: function(keys, action) {
            _direct_map[keys + ':' + (action == 'up' ? 'up' : '')]();
        },

        addEvent: function(object, type, callback) {
            _addEvent(object, type, callback);
        },

        clear: function() {
            _callbacks = {};
            _direct_map = {};
        },

        init: function() {
            _addEvent(document, 'keydown', _handleKeyDown);
            _addEvent(document, 'keyup', _handleKeyUp);
        }
    };
}) ();

Mousetrap.addEvent(window, 'load', Mousetrap.init);
<commit_msg>Properly cancel out existing sequences<commit_after>/**
 * Mousetrap is a simple keyboard command library for Javascript that does
 * not depend on using any existing framework
 *
 * @author Craig Campbell <iamcraigcampbell@gmail.com>
 */
window['Mousetrap'] = (function() {

    /**
     * mapping of special keys
     */
    var _MAP = {
            'backspace': 8,
            'tab': 9,
            'enter': 13,
            'return': 13,
            'shift': 16,
            'ctrl': 17,
            'alt': 18,
            'option': 18,
            'capslock': 20,
            'esc': 27,
            'escape': 27,
            'space': 32,
            'pageup': 33,
            'pagedown': 34,
            'end': 35,
            'home': 36,
            'left': 37,
            'up': 38,
            'right': 39,
            'down': 40,
            'del': 46,
            'meta': 91,
            'command': 91,
            ';': 186,
            '=': 187,
            ',': 188,
            '-': 189,
            '.': 190,
            '/': 191,
            '`': 192,
            '[': 219,
            '\\': 220,
            ']': 221,
            '\'': 222
        },

        _SHIFT_MAP = {
            '~': '`',
            '!': '1',
            '@': '2',
            '#': '3',
            '$': '4',
            '%': '5',
            '^': '6',
            '&': '7',
            '*': '8',
            '(': '9',
            ')': '0',
            '_': '-',
            '+': '=',
            ':': ';',
            '\"': '\'',
            '<': ',',
            '>': '.',
            '?': '/',
            '|': '\\'
        },

        /**
         * a list of all the callbacks setup via Mousetrap.bind()
         */
        _callbacks = {},

        /**
         * direct map used for trigger()
         */
        _direct_map = {},

        _chain_levels = {},

        _reset_timer;

        for (var i = 1; i < 20; ++i) {
            _MAP['f' + i] = 111 + i;
        }

    /**
     * cross browser add event
     */
    function _addEvent(object, type, callback) {
        if (object.addEventListener) {
            return object.addEventListener(type, callback, false);
        }

        object.attachEvent('on' + type, callback);
    }

    function _keyCodeFromEvent(e) {
        // right command on webkit, command on gecko
        if (e.keyCode == 93 || e.keyCode == 224) {
            return 91;
        }

        return e.keyCode;
    }

    function _stop(e) {
        var element = e.target || e.srcElement,
            tag_name = element.tagName;

        // if the element has the class "mousetrap" then still fire off events
        if ((' ' + element.className + ' ').indexOf(' mousetrap ') > -1) {
            return false;
        }

        // stop for input, select, and textarea
        return tag_name == 'INPUT' || tag_name == 'SELECT' || tag_name == 'TEXTAREA';
    }

    function _modifiersMatch(group1, group2) {
        return group1.sort().join(',') === group2.sort().join(',');
    }

    function _resetCounters(no_reset) {
        no_reset = no_reset || {};

        for (var key in _chain_levels) {
            if (!no_reset[key]) {
                _chain_levels[key] = 0;
            }
        }
    }

    function _getMatches(code, modifiers, action, remove) {
        var i,
            callback,
            matches = [],
            chain_match = false;

        if (!_callbacks[code]) {
            return [];
        }

        // if a modifier key is coming up we should allow it
        if (action == 'up' && _isModifier(code)) {
            modifiers = [code];
        }

        // loop through all callbacks for the key that was pressed
        // and see if any of them match
        for (i = 0; i < _callbacks[code].length; ++i) {
            callback = _callbacks[code][i];

            // if this is a chain but it is not at the right level
            // then move onto the next match
            if (callback['chain'] && _chain_levels[callback['chain']] != callback['level']) {
                continue;
            }

            if (action == callback.action && _modifiersMatch(modifiers, callback.modifiers)) {
                if (callback['chain']) {
                    chain_match = true;
                }

                if (remove) {
                    _callbacks[code].splice(i, 1);
                }

                matches.push(callback);
            }
        }

        return matches;
    }

    function _eventModifiers(e) {
        var modifiers = [];

        if (e.shiftKey) {
            modifiers.push(_MAP.shift);
        }

        if (e.altKey) {
            modifiers.push(_MAP.alt);
        }

        if (e.ctrlKey) {
            modifiers.push(_MAP.ctrl);
        }

        if (e.metaKey) {
            modifiers.push(_MAP.command);
        }

        return modifiers;
    }

    function _fireCallback(code, action, e) {
        var callbacks = _getMatches(code, _eventModifiers(e), action),
            i,
            do_not_reset = {},
            processed_chain_callback = false,
            apply_reset = !_isModifier(code);

        for (i = 0; i < callbacks.length; ++i) {

            // fire for all chain callbacks
            if (callbacks[i]['chain']) {
                processed_chain_callback = true;
                do_not_reset[callbacks[i]['chain']] = 1;
                callbacks[i].callback(e);
                continue;
            }

            // first non chain callback fire and exit
            if (!processed_chain_callback) {
                callbacks[i].callback(e);
                break;
            }
        }

        if ((apply_reset || processed_chain_callback) && !action) {
            _resetCounters(do_not_reset);
        }
    }

    function _handleKeyDown(e) {
        if (_stop(e)) {
            return;
        }

        return _fireCallback(_keyCodeFromEvent(e), '', e);
    }

    function _handleKeyUp(e) {
        if (_stop(e)) {
            return;
        }

        _fireCallback(_keyCodeFromEvent(e), 'up', e);
    }

    function _isModifier(code) {
        return (code > 15 && code < 19) || code == 91;
    }

    function _resetChain(chain) {
        clearTimeout(_reset_timer);
        _reset_timer = setTimeout(function() {
            _chain_levels[chain] = 0;
        }, 1000);
    }

    function _bindChain(combo, keys, callback, action) {
        _chain_levels[combo] = 0;

        var _increaseChain = function() {
                ++_chain_levels[combo];
                _resetChain(combo);
            },

            _callbackAndReset = function() {
                callback();

                // weird race condition if a sequence ends with the key
                // another sequence begins with
                setTimeout(_resetCounters, 10);
            },
            i;

        for (i = 0; i < keys.length; ++i) {
            _bindSingle(keys[i], i < keys.length - 1 ? _increaseChain : _callbackAndReset, action, combo, i);
        }
    }

    /**
     * binds a single event
     */
    function _bindSingle(combination, callback, action, chain_name, level) {

        // strip out any spaces around a plus sign
        // also make sure multiple spaces in a row become a single space
        combination = combination.replace(/\s+\+\s+/g, '+').replace(/\s+/, ' ');

        var chain = combination.split(' '),
            i,
            key,
            keys,
            modifiers = [];

        if (chain.length > 1) {
            return _bindChain(combination, chain, callback, action);
        }

        keys = combination.split('+');

        for (i = 0; i < keys.length; ++i) {
            key = keys[i];

            if (_SHIFT_MAP[key]) {
                modifiers.push(_MAP.shift);
                key = _SHIFT_MAP[key];
            }

            key = _MAP[key] || key.toUpperCase().charCodeAt(0);

            if (_isModifier(key)) {
                modifiers.push(key);
            }
        }

        // make sure to initialize array if this is the first time
        // a callback is added for this key
        if (!_callbacks[key]) {
            _callbacks[key] = [];
        }

        // remove an existing match if there is one
        _getMatches(key, modifiers, action, !!!chain_name);

        // add this call back to the array
        // if it is a chain put it at the beginning
        // if not put it at the end
        _callbacks[key][chain_name ? 'unshift' : 'push']({
            callback: callback,
            modifiers: modifiers,
            action: action,
            chain: chain_name,
            level: level
        });
    }

    /**
     * binds multiple combinations to the same callback
     */
    function _bindMultiple(combinations, callback, action) {
        for (var i = 0; i < combinations.length; ++i) {
            _bindSingle(combinations[i], callback, action);
        }
    }

    return {
        bind: function(keys, callback, action) {
            action = action == 'up' ? 'up' : '';
            _bindMultiple(keys instanceof Array ? keys : keys.split(','), callback, action);
            _direct_map[keys + ':' + action] = callback;
        },

        trigger: function(keys, action) {
            _direct_map[keys + ':' + (action == 'up' ? 'up' : '')]();
        },

        addEvent: function(object, type, callback) {
            _addEvent(object, type, callback);
        },

        clear: function() {
            _callbacks = {};
            _direct_map = {};
        },

        init: function() {
            _addEvent(document, 'keydown', _handleKeyDown);
            _addEvent(document, 'keyup', _handleKeyUp);
        }
    };
}) ();

Mousetrap.addEvent(window, 'load', Mousetrap.init);
<|endoftext|>