<commit_before>/**
 * @license
 * Copyright 2016 Google Inc.
 *
 * 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.
 */

// Karma configuration
// Install required modules by running "npm install"

const fs = require('fs');
const path = require('path');
const rimraf = require('rimraf');
const which = require('which');

/**
 * @param {Object} config
 */
module.exports = (config) => {
  const SHAKA_LOG_MAP = {
    none: 0,
    error: 1,
    warning: 2,
    info: 3,
    debug: 4,
    v1: 5,
    v2: 6,
  };

  const KARMA_LOG_MAP = {
    disable: config.LOG_DISABLE,
    error: config.LOG_ERROR,
    warn: config.LOG_WARN,
    info: config.LOG_INFO,
    debug: config.LOG_DEBUG,
  };

  // Find the settings JSON object in the command arguments
  const args = process.argv;
  const settingsIndex = args.indexOf('--settings');
  const settings =
      settingsIndex >= 0 ? JSON.parse(args[settingsIndex + 1]) : {};

  if (settings.browsers && settings.browsers.length == 1 &&
      settings.browsers[0] == 'help') {
    console.log('Available browsers:');
    console.log('===================');
    for (const name of allUsableBrowserLaunchers(config)) {
      console.log('  ' + name);
    }
    process.exit(1);
  }

  config.set({
    // base path that will be used to resolve all patterns (eg. files, exclude)
    basePath: '.',

    // frameworks to use
    // available frameworks: https://npmjs.org/browse/keyword/karma-adapter
    frameworks: [
      'jasmine-ajax',
      'jasmine',
    ],

    plugins: [
      'karma-*',  // default
    ],

    // list of files / patterns to load in the browser
    files: [
      // Polyfills first, primarily for IE 11 and older TVs:
      //   Promise polyfill, required since we test uncompiled code on IE11
      'node_modules/es6-promise-polyfill/promise.js',
      //   Babel polyfill, required for async/await
      'node_modules/babel-polyfill/dist/polyfill.js',

      // muxjs module next
      'node_modules/mux.js/dist/mux.min.js',

      // load closure base, the deps tree, and the uncompiled library
      'third_party/closure/goog/base.js',
      'dist/deps.js',
      'shaka-player.uncompiled.js',

      // cajon module (an AMD variant of requirejs) next
      'node_modules/cajon/cajon.js',

      // bootstrapping for the test suite
      'test/test/boot.js',

      // test utils next
      'test/test/util/*.js',

      // list of test assets next
      'demo/common/asset.js',
      'demo/common/assets.js',

      // if --test-custom-asset *is not* present, we will add unit tests.
      // if --quick *is not* present, we will add integration tests.
      // if --external *is* present, we will add external asset tests.

      // load relevant demo files
      {pattern: 'demo/!(main|load|demo_uncompiled).js', included: true},

      // source files - these are only watched and served
      {pattern: 'lib/**/*.js', included: false},
      {pattern: 'ui/**/*.js', included: false},
      {pattern: 'ui/**/*.less', included: false},
      {pattern: 'third_party/closure/goog/**/*.js', included: false},
      {pattern: 'third_party/language-mapping-list/*.js', included: false},
      {pattern: 'test/test/assets/*', included: false},
      {pattern: 'dist/shaka-player.ui.js', included: false},
      {pattern: 'dist/locales.js', included: false},
      {pattern: 'node_modules/**/*.js', included: false},
    ],

    // NOTE: Do not use proxies at all!  They cannot be used with the --hostname
    // option, which is necessary for some of our lab testing.
    proxies: {},

    // to avoid DISCONNECTED messages on Safari:
    browserDisconnectTimeout: 10 * 1000,  // 10s to reconnect
    browserDisconnectTolerance: 1,  // max of 1 disconnect is OK
    browserNoActivityTimeout: 5 * 60 * 1000,  // disconnect after 5m silence
    processKillTimeout: 5 * 1000,  // allow up to 5s for process to shut down
    captureTimeout: settings.capture_timeout,
    // https://support.saucelabs.com/customer/en/portal/articles/2440724

    client: {
      // Only capture the client's logs if the settings want logging.
      captureConsole: !!settings.logging && settings.logging != 'none',
      // |args| must be an array; pass a key-value map as the sole client
      // argument.
      args: [{
        // Run Player integration tests against external assets.
        // Skipped by default.
        external: !!settings.external,

        // Run Player integration tests against DRM license servers.
        // Skipped by default.
        drm: !!settings.drm,

        // Run quarantined tests which do not consistently pass.
        // Skipped by default.
        quarantined: !!settings.quarantined,

        // Run Player integration tests with uncompiled code for debugging.
        uncompiled: !!settings.uncompiled,

        // Limit which tests to run. If undefined, all tests should run.
        specFilter: settings.filter,

        // Set what level of logs for the player to print.
        logLevel: SHAKA_LOG_MAP[settings.logging],

        // Delay tests to aid in debugging async failures that pollute
        // subsequent tests.
        delayTests: settings.delay_tests,

        // Run playback tests on a custom manifest URI.
        testCustomAsset: settings.test_custom_asset,
        testCustomLicenseServer: settings.test_custom_license_server,

        // Overrides the default test timeout value.
        testTimeout: settings.test_timeout,
      }],
    },

    // Specify the hostname to be used when capturing browsers.
    hostname: settings.hostname,

    // Specify the port where the server runs.
    port: settings.port,

    // Set which browsers to run on. If this is null, then Karma will wait for
    // an incoming connection.
    browsers: settings.browsers,

    // Enable / disable colors in the output (reporters and logs). Defaults
    // to true.
    colors: settings.colors,

    // Set Karma's level of logging.
    logLevel: KARMA_LOG_MAP[settings.log_level],

    // Should Karma xecute tests whenever a file changes?
    autoWatch: settings.auto_watch,

    // Do a single run of the tests on captured browsers and then quit.
    // Defaults to true.
    singleRun: settings.single_run,

    // Set the time limit (ms) that should be used to identify slow tests.
    reportSlowerThan: settings.report_slower_than,

    // Force failure when running empty test-suites.
    failOnEmptyTestSuite: true,

    coverageReporter: {
      includeAllSources: true,
      reporters: [
        {type: 'text'},
      ],
    },

    specReporter: {
      suppressSkipped: true,
    },
  });

  if (settings.babel) {
    config.set({
      preprocessors: {
        // Use babel to convert ES6 to ES5 so we can still run tests everywhere.
        // Use sourcemap to read inline source maps from babel into karma.
        'demo/**/*.js': ['babel', 'sourcemap'],
        'lib/**/*.js': ['babel', 'sourcemap'],
        'ui/**/*.js': ['babel', 'sourcemap'],
        'test/**/*.js': ['babel', 'sourcemap'],
        'third_party/language-mapping-list/*.js': ['babel', 'sourcemap'],
      },

      babelPreprocessor: {
        options: {
          presets: ['env'],
          // Add source maps so that backtraces refer to the original code.
          // Babel will output inline source maps, and the 'sourcemap'
          // preprocessor will read them and feed them to Karma.  Karma will
          // then use them to reformat stack traces in errors.
          sourceMap: 'inline',
          // Add instrumentation for code coverage.
          plugins: [
            ['istanbul', {
              // Don't instrument these parts of the codebase.
              exclude: [
                'demo/**/*.js',
                'lib/(debug|deprecate|polyfill)/*.js',
                'test/**/*.js',
                'third_party/**/*.js',
              ],
            }],
          ],
        },
      },
    });
  }

  if (settings.test_custom_asset) {
    // If testing custom assets, we don't serve other unit or integration tests.
    // External asset tests are the basis for custom asset testing, so this file
    // is automatically included.
    config.files.push('test/player_external.js');
  } else {
    // In a normal test run, we serve unit tests.
    config.files.push('test/**/*_unit.js');

    if (!settings.quick) {
      // If --quick is present, we don't serve integration tests.
      config.files.push('test/**/*_integration.js');
    }
    if (settings.external) {
      // If --external is present, we serve external asset tests.
      config.files.push('test/**/*_external.js');
    }
  }
  // We just modified the config in-place.  No need for config.set() after we
  // push to config.files.

  const reporters = [];

  if (settings.reporters) {
    // Explicit reporters, use these.
    reporters.push(...settings.reporters);
  } else if (settings.logging && settings.logging != 'none') {
    // With logging, default to 'spec', which makes logs easier to associate
    // with individual tests.
    reporters.push('spec');
  } else {
    // Without logging, default to 'progress'.
    reporters.push('progress');
  }

  if (settings.html_coverage_report) {
    // Wipe out any old coverage reports to avoid confusion.
    rimraf.sync('coverage', {});  // Like rm -rf

    config.set({
      coverageReporter: {
        reporters: [
          {type: 'html', dir: 'coverage'},
          {type: 'cobertura', dir: 'coverage', file: 'coverage.xml'},
        ],
      },
    });

    // The report requires the 'coverage' reporter to be added to the list.
    reporters.push('coverage');
  }

  config.set({reporters: reporters});

  if (settings.random) {
    // If --seed was specified use that value, else generate a seed so that the
    // exact order can be reproduced if it catches an issue.
    const seed = settings.seed == null ? new Date().getTime() : settings.seed;

    // Run tests in a random order.
    const clientArgs = config.client.args[0];
    clientArgs.random = true;
    clientArgs.seed = seed;

    console.log('Using a random test order (--random) with --seed=' + seed);
  }
};

/**
 * Determines which launchers and customLaunchers can be used and returns an
 * array of strings.
 *
 * @param {!Object} config
 * @return {!Array.<string>}
 */
function allUsableBrowserLaunchers(config) {
  const browsers = [];

  // Load all launcher plugins.
  // The format of the items in this list is something like:
  // {
  //   'launcher:foo1': ['type', Function],
  //   'launcher:foo2': ['type', Function],
  // }
  // Where the launchers grouped together into one item were defined by a single
  // plugin, and the Functions in the inner array are the constructors for those
  // launchers.
  const plugins = require('karma/lib/plugin').resolve(['karma-*-launcher']);
  for (const map of plugins) {
    for (const name in map) {
      // Launchers should all start with 'launcher:', but occasionally we also
      // see 'test' come up for some reason.
      if (!name.startsWith('launcher:')) {
        continue;
      }

      const browserName = name.split(':')[1];
      const pluginConstructor = map[name][1];

      // Most launchers requiring configuration through customLaunchers have
      // no DEFAULT_CMD.  Some launchers have DEFAULT_CMD, but not for this
      // platform.  Finally, WebDriver has DEFAULT_CMD, but still requires
      // configuration, so we simply blacklist it by name.
      // eslint-disable-next-line no-restricted-syntax
      const DEFAULT_CMD = pluginConstructor.prototype.DEFAULT_CMD;
      if (!DEFAULT_CMD || !DEFAULT_CMD[process.platform]) {
        continue;
      }
      if (browserName == 'WebDriver') {
        continue;
      }

      // Now that we've filtered out the browsers that can't be launched without
      // custom config or that can't be launched on this platform, we filter out
      // the browsers you don't have installed.
      // eslint-disable-next-line no-restricted-syntax
      const ENV_CMD = pluginConstructor.prototype.ENV_CMD;
      const browserPath = process.env[ENV_CMD] || DEFAULT_CMD[process.platform];

      if (!fs.existsSync(browserPath) &&
          !which.sync(browserPath, {nothrow: true})) {
        continue;
      }

      browsers.push(browserName);
    }
  }

  // Once we've found the names of all the standard launchers, add to that list
  // the names of any custom launcher configurations.
  if (config.customLaunchers) {
    browsers.push(...Object.keys(config.customLaunchers));
  }

  return browsers;
}
<commit_msg>Don't load the service worker in tests.<commit_after>/**
 * @license
 * Copyright 2016 Google Inc.
 *
 * 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.
 */

// Karma configuration
// Install required modules by running "npm install"

const fs = require('fs');
const path = require('path');
const rimraf = require('rimraf');
const which = require('which');

/**
 * @param {Object} config
 */
module.exports = (config) => {
  const SHAKA_LOG_MAP = {
    none: 0,
    error: 1,
    warning: 2,
    info: 3,
    debug: 4,
    v1: 5,
    v2: 6,
  };

  const KARMA_LOG_MAP = {
    disable: config.LOG_DISABLE,
    error: config.LOG_ERROR,
    warn: config.LOG_WARN,
    info: config.LOG_INFO,
    debug: config.LOG_DEBUG,
  };

  // Find the settings JSON object in the command arguments
  const args = process.argv;
  const settingsIndex = args.indexOf('--settings');
  const settings =
      settingsIndex >= 0 ? JSON.parse(args[settingsIndex + 1]) : {};

  if (settings.browsers && settings.browsers.length == 1 &&
      settings.browsers[0] == 'help') {
    console.log('Available browsers:');
    console.log('===================');
    for (const name of allUsableBrowserLaunchers(config)) {
      console.log('  ' + name);
    }
    process.exit(1);
  }

  config.set({
    // base path that will be used to resolve all patterns (eg. files, exclude)
    basePath: '.',

    // frameworks to use
    // available frameworks: https://npmjs.org/browse/keyword/karma-adapter
    frameworks: [
      'jasmine-ajax',
      'jasmine',
    ],

    plugins: [
      'karma-*',  // default
    ],

    // list of files / patterns to load in the browser
    files: [
      // Polyfills first, primarily for IE 11 and older TVs:
      //   Promise polyfill, required since we test uncompiled code on IE11
      'node_modules/es6-promise-polyfill/promise.js',
      //   Babel polyfill, required for async/await
      'node_modules/babel-polyfill/dist/polyfill.js',

      // muxjs module next
      'node_modules/mux.js/dist/mux.min.js',

      // load closure base, the deps tree, and the uncompiled library
      'third_party/closure/goog/base.js',
      'dist/deps.js',
      'shaka-player.uncompiled.js',

      // cajon module (an AMD variant of requirejs) next
      'node_modules/cajon/cajon.js',

      // bootstrapping for the test suite
      'test/test/boot.js',

      // test utils next
      'test/test/util/*.js',

      // list of test assets next
      'demo/common/asset.js',
      'demo/common/assets.js',

      // if --test-custom-asset *is not* present, we will add unit tests.
      // if --quick *is not* present, we will add integration tests.
      // if --external *is* present, we will add external asset tests.

      // load relevant demo files
      {
        pattern: 'demo/!(main|load|demo_uncompiled|service_worker).js',
        included: true,
      },

      // source files - these are only watched and served
      {pattern: 'lib/**/*.js', included: false},
      {pattern: 'ui/**/*.js', included: false},
      {pattern: 'ui/**/*.less', included: false},
      {pattern: 'third_party/closure/goog/**/*.js', included: false},
      {pattern: 'third_party/language-mapping-list/*.js', included: false},
      {pattern: 'test/test/assets/*', included: false},
      {pattern: 'dist/shaka-player.ui.js', included: false},
      {pattern: 'dist/locales.js', included: false},
      {pattern: 'node_modules/**/*.js', included: false},
    ],

    // NOTE: Do not use proxies at all!  They cannot be used with the --hostname
    // option, which is necessary for some of our lab testing.
    proxies: {},

    // to avoid DISCONNECTED messages on Safari:
    browserDisconnectTimeout: 10 * 1000,  // 10s to reconnect
    browserDisconnectTolerance: 1,  // max of 1 disconnect is OK
    browserNoActivityTimeout: 5 * 60 * 1000,  // disconnect after 5m silence
    processKillTimeout: 5 * 1000,  // allow up to 5s for process to shut down
    captureTimeout: settings.capture_timeout,
    // https://support.saucelabs.com/customer/en/portal/articles/2440724

    client: {
      // Only capture the client's logs if the settings want logging.
      captureConsole: !!settings.logging && settings.logging != 'none',
      // |args| must be an array; pass a key-value map as the sole client
      // argument.
      args: [{
        // Run Player integration tests against external assets.
        // Skipped by default.
        external: !!settings.external,

        // Run Player integration tests against DRM license servers.
        // Skipped by default.
        drm: !!settings.drm,

        // Run quarantined tests which do not consistently pass.
        // Skipped by default.
        quarantined: !!settings.quarantined,

        // Run Player integration tests with uncompiled code for debugging.
        uncompiled: !!settings.uncompiled,

        // Limit which tests to run. If undefined, all tests should run.
        specFilter: settings.filter,

        // Set what level of logs for the player to print.
        logLevel: SHAKA_LOG_MAP[settings.logging],

        // Delay tests to aid in debugging async failures that pollute
        // subsequent tests.
        delayTests: settings.delay_tests,

        // Run playback tests on a custom manifest URI.
        testCustomAsset: settings.test_custom_asset,
        testCustomLicenseServer: settings.test_custom_license_server,

        // Overrides the default test timeout value.
        testTimeout: settings.test_timeout,
      }],
    },

    // Specify the hostname to be used when capturing browsers.
    hostname: settings.hostname,

    // Specify the port where the server runs.
    port: settings.port,

    // Set which browsers to run on. If this is null, then Karma will wait for
    // an incoming connection.
    browsers: settings.browsers,

    // Enable / disable colors in the output (reporters and logs). Defaults
    // to true.
    colors: settings.colors,

    // Set Karma's level of logging.
    logLevel: KARMA_LOG_MAP[settings.log_level],

    // Should Karma xecute tests whenever a file changes?
    autoWatch: settings.auto_watch,

    // Do a single run of the tests on captured browsers and then quit.
    // Defaults to true.
    singleRun: settings.single_run,

    // Set the time limit (ms) that should be used to identify slow tests.
    reportSlowerThan: settings.report_slower_than,

    // Force failure when running empty test-suites.
    failOnEmptyTestSuite: true,

    coverageReporter: {
      includeAllSources: true,
      reporters: [
        {type: 'text'},
      ],
    },

    specReporter: {
      suppressSkipped: true,
    },
  });

  if (settings.babel) {
    config.set({
      preprocessors: {
        // Use babel to convert ES6 to ES5 so we can still run tests everywhere.
        // Use sourcemap to read inline source maps from babel into karma.
        'demo/**/*.js': ['babel', 'sourcemap'],
        'lib/**/*.js': ['babel', 'sourcemap'],
        'ui/**/*.js': ['babel', 'sourcemap'],
        'test/**/*.js': ['babel', 'sourcemap'],
        'third_party/language-mapping-list/*.js': ['babel', 'sourcemap'],
      },

      babelPreprocessor: {
        options: {
          presets: ['env'],
          // Add source maps so that backtraces refer to the original code.
          // Babel will output inline source maps, and the 'sourcemap'
          // preprocessor will read them and feed them to Karma.  Karma will
          // then use them to reformat stack traces in errors.
          sourceMap: 'inline',
          // Add instrumentation for code coverage.
          plugins: [
            ['istanbul', {
              // Don't instrument these parts of the codebase.
              exclude: [
                'demo/**/*.js',
                'lib/(debug|deprecate|polyfill)/*.js',
                'test/**/*.js',
                'third_party/**/*.js',
              ],
            }],
          ],
        },
      },
    });
  }

  if (settings.test_custom_asset) {
    // If testing custom assets, we don't serve other unit or integration tests.
    // External asset tests are the basis for custom asset testing, so this file
    // is automatically included.
    config.files.push('test/player_external.js');
  } else {
    // In a normal test run, we serve unit tests.
    config.files.push('test/**/*_unit.js');

    if (!settings.quick) {
      // If --quick is present, we don't serve integration tests.
      config.files.push('test/**/*_integration.js');
    }
    if (settings.external) {
      // If --external is present, we serve external asset tests.
      config.files.push('test/**/*_external.js');
    }
  }
  // We just modified the config in-place.  No need for config.set() after we
  // push to config.files.

  const reporters = [];

  if (settings.reporters) {
    // Explicit reporters, use these.
    reporters.push(...settings.reporters);
  } else if (settings.logging && settings.logging != 'none') {
    // With logging, default to 'spec', which makes logs easier to associate
    // with individual tests.
    reporters.push('spec');
  } else {
    // Without logging, default to 'progress'.
    reporters.push('progress');
  }

  if (settings.html_coverage_report) {
    // Wipe out any old coverage reports to avoid confusion.
    rimraf.sync('coverage', {});  // Like rm -rf

    config.set({
      coverageReporter: {
        reporters: [
          {type: 'html', dir: 'coverage'},
          {type: 'cobertura', dir: 'coverage', file: 'coverage.xml'},
        ],
      },
    });

    // The report requires the 'coverage' reporter to be added to the list.
    reporters.push('coverage');
  }

  config.set({reporters: reporters});

  if (settings.random) {
    // If --seed was specified use that value, else generate a seed so that the
    // exact order can be reproduced if it catches an issue.
    const seed = settings.seed == null ? new Date().getTime() : settings.seed;

    // Run tests in a random order.
    const clientArgs = config.client.args[0];
    clientArgs.random = true;
    clientArgs.seed = seed;

    console.log('Using a random test order (--random) with --seed=' + seed);
  }
};

/**
 * Determines which launchers and customLaunchers can be used and returns an
 * array of strings.
 *
 * @param {!Object} config
 * @return {!Array.<string>}
 */
function allUsableBrowserLaunchers(config) {
  const browsers = [];

  // Load all launcher plugins.
  // The format of the items in this list is something like:
  // {
  //   'launcher:foo1': ['type', Function],
  //   'launcher:foo2': ['type', Function],
  // }
  // Where the launchers grouped together into one item were defined by a single
  // plugin, and the Functions in the inner array are the constructors for those
  // launchers.
  const plugins = require('karma/lib/plugin').resolve(['karma-*-launcher']);
  for (const map of plugins) {
    for (const name in map) {
      // Launchers should all start with 'launcher:', but occasionally we also
      // see 'test' come up for some reason.
      if (!name.startsWith('launcher:')) {
        continue;
      }

      const browserName = name.split(':')[1];
      const pluginConstructor = map[name][1];

      // Most launchers requiring configuration through customLaunchers have
      // no DEFAULT_CMD.  Some launchers have DEFAULT_CMD, but not for this
      // platform.  Finally, WebDriver has DEFAULT_CMD, but still requires
      // configuration, so we simply blacklist it by name.
      // eslint-disable-next-line no-restricted-syntax
      const DEFAULT_CMD = pluginConstructor.prototype.DEFAULT_CMD;
      if (!DEFAULT_CMD || !DEFAULT_CMD[process.platform]) {
        continue;
      }
      if (browserName == 'WebDriver') {
        continue;
      }

      // Now that we've filtered out the browsers that can't be launched without
      // custom config or that can't be launched on this platform, we filter out
      // the browsers you don't have installed.
      // eslint-disable-next-line no-restricted-syntax
      const ENV_CMD = pluginConstructor.prototype.ENV_CMD;
      const browserPath = process.env[ENV_CMD] || DEFAULT_CMD[process.platform];

      if (!fs.existsSync(browserPath) &&
          !which.sync(browserPath, {nothrow: true})) {
        continue;
      }

      browsers.push(browserName);
    }
  }

  // Once we've found the names of all the standard launchers, add to that list
  // the names of any custom launcher configurations.
  if (config.customLaunchers) {
    browsers.push(...Object.keys(config.customLaunchers));
  }

  return browsers;
}
<|endoftext|>


<commit_before>module.exports = (config) => {
    const options = {
        reporters: ["coverage-istanbul", "junit"],
        frameworks: ["mocha"],
        files: [
            {pattern: require.resolve("chai/chai"), watched: false},
            {pattern: require.resolve("vue/dist/vue"), watched: false},
            {pattern: require.resolve("three"), watched: false},
            {pattern: "test/**/*.spec.js", watched: false}
        ],
        preprocessors: {
            "test/**/*.spec.js": ["rollup"]
        },
        rollupPreprocessor: {
            format: "iife",
            external: "three",
            globals: {
                three: "THREE"
            },
            plugins: [
                require("rollup-plugin-istanbul")({include: "src/**"}),
                require("rollup-plugin-babel")()
            ],
            sourcemap: "inline"
        },
        coverageIstanbulReporter: {
            reports: ["html"],
            dir: require("path").resolve("coverage/%browser%")
        },
        junitReporter: {
            outputDir: "junit"
        }
    };

    if (process.env.CI) {
        options.reporters.push("saucelabs");
        options.sauceLabs = {
            testName: "VueGL unit test",
            recordScreenshots: false,
            public: "public restricted"
        };
        options.customLaunchers = {
            "Chrome 26 on Windows 7": {
                base: "SauceLabs",
                platform: "Windows 7",
                browserName: "chrome",
                version: "26"
            },
            "Chrome (latest) on Windows 7": {
                base: "SauceLabs",
                platform: "Windows 7",
                browserName: "chrome",
                version: "latest"
            },
            "Internet Explorer 9 on Windows 7": {
                base: "SauceLabs",
                platform: "Windows 7",
                browserName: "internet explorer",
                version: "9"
            },
            "Internet Explorer (latest) on Windows 7": {
                base: "SauceLabs",
                platform: "Windows 7",
                browserName: "internet explorer",
                version: "latest"
            },
            "Firefox 4 on Windows 7": {
                base: "SauceLabs",
                platform: "Windows 7",
                browserName: "firefox",
                version: "4"
            },
            "Firefox (latest) on Windows 7": {
                base: "SauceLabs",
                platform: "Windows 7",
                browserName: "firefox",
                version: "latest"
            },
            "Opera 11 on Windows 7": {
                base: "SauceLabs",
                platform: "Windows 7",
                browserName: "Opera",
                version: "11"
            },
            "Opera (latest) on Windows 7": {
                base: "SauceLabs",
                platform: "Windows 7",
                browserName: "Opera",
                version: "latest"
            }
        };
        options.browsers = Object.keys(options.customLaunchers);
        options.singleRun = true;
        options.concurrency = 5;
    }

    config.set(options);
};
<commit_msg>Add the edge on win10 cababilities for the unit testing.<commit_after>module.exports = (config) => {
    const options = {
        reporters: ["coverage-istanbul", "junit"],
        frameworks: ["mocha"],
        files: [
            {pattern: require.resolve("chai/chai"), watched: false},
            {pattern: require.resolve("vue/dist/vue"), watched: false},
            {pattern: require.resolve("three"), watched: false},
            {pattern: "test/**/*.spec.js", watched: false}
        ],
        preprocessors: {
            "test/**/*.spec.js": ["rollup"]
        },
        rollupPreprocessor: {
            format: "iife",
            external: "three",
            globals: {
                three: "THREE"
            },
            plugins: [
                require("rollup-plugin-istanbul")({include: "src/**"}),
                require("rollup-plugin-babel")()
            ],
            sourcemap: "inline"
        },
        coverageIstanbulReporter: {
            reports: ["html"],
            dir: require("path").resolve("coverage/%browser%")
        },
        junitReporter: {
            outputDir: "junit"
        }
    };

    if (process.env.CI) {
        options.reporters.push("saucelabs");
        options.sauceLabs = {
            testName: "VueGL unit test",
            recordScreenshots: false,
            public: "public restricted"
        };
        options.customLaunchers = {
            "Chrome 26 on Windows 7": {
                base: "SauceLabs",
                platform: "Windows 7",
                browserName: "chrome",
                version: "26"
            },
            "Chrome (latest) on Windows 7": {
                base: "SauceLabs",
                platform: "Windows 7",
                browserName: "chrome",
                version: "latest"
            },
            "Internet Explorer 9 on Windows 7": {
                base: "SauceLabs",
                platform: "Windows 7",
                browserName: "internet explorer",
                version: "9"
            },
            "Internet Explorer (latest) on Windows 7": {
                base: "SauceLabs",
                platform: "Windows 7",
                browserName: "internet explorer",
                version: "latest"
            },
            "Firefox 4 on Windows 7": {
                base: "SauceLabs",
                platform: "Windows 7",
                browserName: "firefox",
                version: "4"
            },
            "Firefox (latest) on Windows 7": {
                base: "SauceLabs",
                platform: "Windows 7",
                browserName: "firefox",
                version: "latest"
            },
            "Opera 11 on Windows 7": {
                base: "SauceLabs",
                platform: "Windows 7",
                browserName: "Opera",
                version: "11"
            },
            "Opera (latest) on Windows 7": {
                base: "SauceLabs",
                platform: "Windows 7",
                browserName: "Opera",
                version: "latest"
            },
            "Edge 13 on Windows 10": {
                base: "SauceLabs",
                platform: "Windows 10",
                browserName: "MicrosoftEdge",
                version: "13"
            },
            "Edge (latest) on Windows 10": {
                base: "SauceLabs",
                platform: "Windows 10",
                browserName: "MicrosoftEdge",
                version: "latest"
            }
        };
        options.browsers = Object.keys(options.customLaunchers);
        options.singleRun = true;
        options.concurrency = 5;
    }

    config.set(options);
};
<|endoftext|>


<commit_before>define(["Tone/core/Tone", "Tone/effect/Effect", "Tone/component/LFO", "Tone/component/Panner"], function(Tone){

	"use strict";

	/**
	 *  @class AutoPanner is a Tone.Panner with an LFO connected to the pan amount
	 *
	 *  @constructor
	 *  @extends {Tone.Effect}
	 *  @param { number= } frequency (optional) rate in HZ of the left-right pan
	 */
	Tone.AutoPanner = function(){

		var options = this.optionsObject(arguments, ["frequency"], Tone.AutoPanner.defaults);
		Tone.Effect.call(this, options);

		/**
		 *  the lfo which drives the panning
		 *  @type {Tone.LFO}
		 *  @private
		 */
		this._lfo = new Tone.LFO(options.frequency, 0, 1);

		/**
		 *  the panner node which does the panning
		 *  @type {Tone.Panner}
		 *  @private
		 */
		this._panner = new Tone.Panner();

		//connections
		this.connectEffect(this._panner);
		this._lfo.connect(this._panner.pan);
		this.setType(options.type);
	};

	//extend Effect
	Tone.extend(Tone.AutoPanner, Tone.Effect);

	/**
	 *  defaults
	 *  @static
	 *  @type {Object}
	 */
	Tone.AutoPanner.defaults = {
		"frequency" : 1,
		"type" : "sine"
	};

	/**
	 * Start the panner
	 *
	 * @param {Tone.Time} [time=now] the panner begins.
	 */
	Tone.AutoPanner.prototype.start = function(time){
		this._lfo.start(time);
	};

	/**
	 * Stop the panner
	 *
	 * @param {Tone.Time} [time=now] the panner stops.
	 */
	Tone.AutoPanner.prototype.stop = function(time){
		this._lfo.stop(time);
	};

	/**
	 * Set the type of oscillator attached to the AutoPanner.
	 *
	 * @param {string} type of oscillator the panner is attached to (sine|sawtooth|triangle|square)
	 */
	Tone.AutoPanner.prototype.setType = function(type){
		this._lfo.setType(type);
	};

	/**
	 * Set frequency of the oscillator attached to the AutoPanner.
	 *
	 * @param {number|string} freq in HZ of the oscillator's frequency.
	 */
	Tone.AutoPanner.prototype.setFrequency = function(freq){
		this._lfo.setFrequency(freq);
	};

	/**
	 *  set all of the parameters with an object
	 *  @param {Object} params
	 */
	Tone.AutoPanner.prototype.set = function(params){
		if (!this.isUndef(params.frequency)) this.setFrequency(params.frequency);
		if (!this.isUndef(params.type)) this.setType(params.type);
		Tone.Effect.prototype.set.call(this, params);
	};

	/**
	 *  clean up
	 */
	Tone.AutoPanner.prototype.dispose = function(){
		Tone.Effect.prototype.dispose.call(this);
		this._lfo.dispose();
		this._panner.dispose();
		this._lfo = null;
		this._panner = null;
	};

	return Tone.AutoPanner;
});
<commit_msg>default args<commit_after>define(["Tone/core/Tone", "Tone/effect/Effect", "Tone/component/LFO", "Tone/component/Panner"], function(Tone){

	"use strict";

	/**
	 *  @class AutoPanner is a Tone.Panner with an LFO connected to the pan amount
	 *
	 *  @constructor
	 *  @extends {Tone.Effect}
	 *  @param {number} [frequency=1] (optional) rate in HZ of the left-right pan
	 */
	Tone.AutoPanner = function(){

		var options = this.optionsObject(arguments, ["frequency"], Tone.AutoPanner.defaults);
		Tone.Effect.call(this, options);

		/**
		 *  the lfo which drives the panning
		 *  @type {Tone.LFO}
		 *  @private
		 */
		this._lfo = new Tone.LFO(options.frequency, 0, 1);

		/**
		 *  the panner node which does the panning
		 *  @type {Tone.Panner}
		 *  @private
		 */
		this._panner = new Tone.Panner();

		//connections
		this.connectEffect(this._panner);
		this._lfo.connect(this._panner.pan);
		this.setType(options.type);
	};

	//extend Effect
	Tone.extend(Tone.AutoPanner, Tone.Effect);

	/**
	 *  defaults
	 *  @static
	 *  @type {Object}
	 */
	Tone.AutoPanner.defaults = {
		"frequency" : 1,
		"type" : "sine"
	};

	/**
	 * Start the panner
	 *
	 * @param {Tone.Time} [time=now] the panner begins.
	 */
	Tone.AutoPanner.prototype.start = function(time){
		this._lfo.start(time);
	};

	/**
	 * Stop the panner
	 *
	 * @param {Tone.Time} [time=now] the panner stops.
	 */
	Tone.AutoPanner.prototype.stop = function(time){
		this._lfo.stop(time);
	};

	/**
	 * sync the panner to the transport
	 */
	Tone.AutoPanner.prototype.sync = function(){
		this._lfo.sync();
	};

	/**
	 * unsync the panner from the transport
	 */
	Tone.AutoPanner.prototype.unsync = function(){
		this._lfo.unsync();
	};

	/**
	 * Set the type of oscillator attached to the AutoPanner.
	 *
	 * @param {string} type of oscillator the panner is attached to (sine|sawtooth|triangle|square)
	 */
	Tone.AutoPanner.prototype.setType = function(type){
		this._lfo.setType(type);
	};

	/**
	 * Set frequency of the oscillator attached to the AutoPanner.
	 *
	 * @param {number|string} freq in HZ of the oscillator's frequency.
	 */
	Tone.AutoPanner.prototype.setFrequency = function(freq){
		this._lfo.setFrequency(freq);
	};

	/**
	 *  set all of the parameters with an object
	 *  @param {Object} params
	 */
	Tone.AutoPanner.prototype.set = function(params){
		if (!this.isUndef(params.frequency)) this.setFrequency(params.frequency);
		if (!this.isUndef(params.type)) this.setType(params.type);
		Tone.Effect.prototype.set.call(this, params);
	};

	/**
	 *  clean up
	 */
	Tone.AutoPanner.prototype.dispose = function(){
		Tone.Effect.prototype.dispose.call(this);
		this._lfo.dispose();
		this._panner.dispose();
		this._lfo = null;
		this._panner = null;
	};

	return Tone.AutoPanner;
});
<|endoftext|>


<commit_before>// ==UserScript==
// @name        Gurkr AD Detector
// @namespace   NetCharm - [https://github.com/netcharm]/[https://netcharm.bitbucket.org/]
// @author      netcharm
// @description Gurkr AD Detector
// @include     http://*.guokr.com/post/*
// @include     http://*.guokr.com/question/*
// @include     http://*.guokr.com/blog/*
// @version     1.2.4.18
// @run-at      document-end
// @updateURL   https://raw.githubusercontent.com/netcharm/greasemonkey-code/master/Gurkr_AD_Detector.user.js
// @downloadURL https://raw.githubusercontent.com/netcharm/greasemonkey-code/master/Gurkr_AD_Detector.user.js
// @grant       none
// ==/UserScript==

var jQueryVersion = $.fn.jquery;

const ADS = [
  '爸爸去哪儿', '爸爸去哪兒',
  '中国好声音', '中國好聲音',
  '中獎信息',
  '小姐联系电话', '/..小姐/',
  '极美茵',
  '/[伯博蚾秡渤卜箔].{0,6}[来莱梾俫庲婡].{0,6}[世狮轼史是时式試].{0,6}[特忒慝忑]/',
  //'伯来世特', '伯莱狮特', '博来狮特', '蚾梾轼忒', '秡猍狮特', '渤俫史特', '伯庲是特', '卜婡时慝', '伯俫世特', '箔婡式忑',
  '叆鲱迪坷',
  '妙女郎', '酵素梅', '酵素', '总代理', '世纪本草', '芸蓉集', '臻悦',
  '一小兜', 'yixiaodou.com',
  '天津妇科', '香港健康医疗', '香港性别鉴定', '性别检测', '医务顾问', '胎儿性别鉴定',
  '咨詢熱線', '咨询热线',
  '新闻牙膏', '新闻牙刷',
  '海华伦', '扇贝王',
  '成都装修', '苹果官方',
  //'91y',
  '贝贝游戏', '贝贝银子', '贝贝酒吧', '贝贝棋牌', '1908游戏', '747官网',
  '有动静',
  '微营销',
  '微商'
];

function makePat(words)
{
  var pat = "";
  if(words.startsWith('/') && words.endsWith('/'))
  {
    pat = words.substr(1, words.length-2);
  }
  else
  {
    for(idx in words.split(''))
    {
      if(pat.length <= 0)
      {
        pat = words[idx];
      }
      else
      {
        pat = pat + ".{0,6}" + words[idx];
      }
    }
  }
  return(new RegExp(pat, 'gi'));
}

function makePats(words)
{
  var regexs = new Array();
  for(idx in words)
  {
    regexs[regexs.length] = makePat(words[idx]);
    //console.log(regexs);
  }
  return(regexs);
}

function matchAD(text, regex)
{
  var hasAD = false;
  var results = text.match(regex);
  if(results && (results.length>0))
  {
    hasAD = true;
  }
  return(hasAD);
}

function notifyAD(info, fg, bg)
{
  var bgcolor='red';
  var fgcolor='white';
  if(bg) bgcolor = bg;
  if(fg) fgcolor = fg;

  $('a.gh-i-notice').css('background-color', bgcolor);
  $('a.gh-i-notice').css('color', fgcolor);
  var title = $('a.gh-i-notice').attr('title');
  if(title)
  {
    $('a.gh-i-notice').attr('title', title + '\n' + info);
  }
  else
  {
    $('a.gh-i-notice').attr('title', info);
  }

  $reportButton = $('.gh-notice li:first').before('
<button id="reportAD" style="margin-top:8px;">举报</button>
');
  //$reportButton.bind('click', reportAD);
  $('#reportAD').on('click', reportAD);

}

function highlightAD(word, node, mode)
{
  var ad_style = 'color:white; background-color:red;';
  var link_style = 'color:white; background-color:yellow;';
  var style = ad_style;

  var gwrap = $('div.gwrap');
  if(node)
  {
    gwrap = $(node);
  }
  if(mode && mode.toLowerCase()=='link')
  {
    style = link_style;
  }
  var html = gwrap.html().replace(word, function(m){
    return ''+m+''
  });
  gwrap.html( html );
}

function findingAD(items, regex, notice, mode)
{
  var hasAD = false;

  items.each(function(){
    var text = '';
    if(mode && mode.toLowerCase()=='link')
    {
      text = this.innerHTML;
    }
    else
    {
      text = this.textContent;
    }
    hasAD |= matchAD(text, regex);
    if(hasAD)
    {
      highlightAD(regex, this, mode);
    }
  });

  if(hasAD)
  {
    var info = "已发现" + notice;
    notifyAD(info);
    console.warn(info);
    //alert(info);
  }
  else
  {
    var info = "未发现" + notice;
    console.info(info);
  }
  return(hasAD);
}

function findingLink(items, hasAD)
{
  var bgcolor = 'yellow';
  var fgcolor = 'red';
  var hasLink = false;
  var notice = '外链';
  var link_pat = new RegExp('http://(?!.*?\.guokr\.com).*?$', 'gi');

  items.each(function(){
    var links = $(this).find('a');
    links.each(function(){
      var link = $(this);
      var isExtLink = link[0].href.match(link_pat);
      if(isExtLink && isExtLink.length>0)
      {
        hasLink = true;
        link.css('background-color', bgcolor);
        link.css('color', fgcolor);
      }
    });
  });

  if(hasLink)
  {
    var info = "已发现" + notice;
    if(hasAD) bgcolor='orange';
    notifyAD(info, fgcolor, bgcolor);
    console.warn(info);
    //alert(info);
  }
  else
  {
    var info = "未发现" + notice;
    console.info(info);
  }
  return(hasLink);
}

function getReportParam()
{
  var ca = document.cookie.split(';');
  var accessToken = null;
  for(idx in ca)
  {
    var item = ca[idx];
    var kv = item.split('=');
    var k = kv[0].trim();
    var v = kv[1].trim();
    if(k.endsWith('access_token'))
    {
      accessToken = v;
      break;
    }
  }
  return({url:document.location.href, reason:'垃圾广告', access_token:accessToken});
}

function reportAD()
{
  var reportParam = getReportParam();
  //console.log(reportParam);
  $.post('http://www.guokr.com/apis/censor/report.json', reportParam, function( data ){
    if(data.ok)
    {
      $('#reportAD').text('举报成功');
      console.log($('#reportAD').text());
    }
  }, "json");
}

function reportADs(btn)
{
  var reportParam = getReportParam();
  reportParam.url = $(btn).attr('data-url');
  //console.log(reportParam);
  $.post('http://www.guokr.com/apis/censor/report.json', reportParam, function( data ){
    if(data.ok)
    {
      $(btn).text('举报成功');
      console.log($(btn).text());
    }
  }, "json");
}

function addReportButtons()
{
  console.log('add button');
  var poster = $('.post-pic a, .author-pic');
  if(poster.length>0)
  {
    poster = $(poster[0]);
    poster.after('
<button id="reportUSER_poster" class="reportUSERs" title="举报此用户">举报</button>');
    var btnPoster = $('#reportUSER_poster');
    btnPoster.attr('data-url', poster[0].href);
    btnPoster.bind('click', function(){reportADs($(this))});
  }

  var avators = $('.pt-pic a');
  for(idx in avators)
  {
      var user = $(avators[idx]);
      var floor = user.siblings('.cmt-floor');
      //if(floor.length>0 && $.isNumeric(idx))
      if(floor.length>0 && idx<floor.length)
      {
        if(user.length>0)
        {
          floor = $(floor[0]);

          var btnUserID = 'reportUSER_'+ idx;
          floor.after('
<button id="'+ btnUserID +'" class="reportUSERs" title="举报此用户">举报</button>');

          var btnUser = $('#'+btnUserID);
          btnUser.attr('data-url', user[0].href);
          btnUser.bind('click', function(){reportADs($(this))});
        }
      }
  }

  var reportLinks = $('a.ghide.red-link');
  for(idx in reportLinks)
  {
    var link = $(reportLinks[idx]);
    var like = link.siblings('a.cmt-do-quote');
    //if(like.length>0 && $.isNumeric(idx))
    if(like.length>0 && idx<like.length)
    {
      like = $(like[0]);

      var btnID = 'reportAD_'+ idx;
      like.before('<button id="'+ btnID +'" class="reportADs" title="举报此回帖">举报</button>');

      var btn = $('#'+btnID);
      btn.attr('data-img', link.attr('data-img'));
      btn.attr('data-url', link.attr('data-url'));
      btn.attr('data-title', link.attr('data-title'));
      btn.attr('data-type', link.attr('data-type'));
      btn.attr('data-report', link.attr('data-report'));
      btn.bind('click', function(){reportADs($(this))});
    }
  }
}

function main(loaded)
{
  addReportButtons();

  var hasAD = false;

  var regexs = makePats(ADS);
  var author = $('#articleAuthor');
  var title = $('#articleTitle');
  var article = $('#articleContent');
  //var posts = $('.post-txt');
  var comments = $('.cmtContent, .answerTxt');

  //var items = $.extend({}, author, title, article, comments);
  var items = $.merge($.merge($.merge(author, title), article), comments);

  for(idx in regexs)
  {
    var AD = ADS[idx];
    //console.log('Finding ' + AD + ' ...');
    //console.log('Finding ' + regexs[idx] + ' ...');
    hasAD |= findingAD(items, regexs[idx], '广告:'+AD);
  }

  findingLink(items, hasAD);

}

main();
<commit_msg>1. fixed loop condition<commit_after>// ==UserScript==
// @name        Gurkr AD Detector
// @namespace   NetCharm - [https://github.com/netcharm]/[https://netcharm.bitbucket.org/]
// @author      netcharm
// @description Gurkr AD Detector
// @include     http://*.guokr.com/post/*
// @include     http://*.guokr.com/question/*
// @include     http://*.guokr.com/blog/*
// @version     1.2.4.20
// @run-at      document-end
// @updateURL   https://raw.githubusercontent.com/netcharm/greasemonkey-code/master/Gurkr_AD_Detector.user.js
// @downloadURL https://raw.githubusercontent.com/netcharm/greasemonkey-code/master/Gurkr_AD_Detector.user.js
// @grant       none
// ==/UserScript==

var jQueryVersion = $.fn.jquery;

const ADS = [
  '爸爸去哪儿', '爸爸去哪兒',
  '中国好声音', '中國好聲音',
  '中獎信息',
  '小姐联系电话', '/..小姐/',
  '极美茵',
  '/[伯博蚾秡渤卜箔].{0,6}[来莱梾俫庲婡].{0,6}[世狮轼史是时式試].{0,6}[特忒慝忑]/',
  //'伯来世特', '伯莱狮特', '博来狮特', '蚾梾轼忒', '秡猍狮特', '渤俫史特', '伯庲是特', '卜婡时慝', '伯俫世特', '箔婡式忑',
  '叆鲱迪坷',
  '妙女郎', '酵素梅', '酵素', '总代理', '世纪本草', '芸蓉集', '臻悦',
  '一小兜', 'yixiaodou.com',
  '天津妇科', '香港健康医疗', '香港性别鉴定', '性别检测', '医务顾问', '胎儿性别鉴定',
  '咨詢熱線', '咨询热线',
  '新闻牙膏', '新闻牙刷',
  '海华伦', '扇贝王',
  '成都装修', '苹果官方',
  //'91y',
  '贝贝游戏', '贝贝银子', '贝贝酒吧', '贝贝棋牌', '1908游戏', '747官网',
  '有动静',
  '微营销',
  '微商'
];

function makePat(words)
{
  var pat = "";
  if(words.startsWith('/') && words.endsWith('/'))
  {
    pat = words.substr(1, words.length-2);
  }
  else
  {
    for(idx in words.split(''))
    {
      if(pat.length <= 0)
      {
        pat = words[idx];
      }
      else
      {
        pat = pat + ".{0,6}" + words[idx];
      }
    }
  }
  return(new RegExp(pat, 'gi'));
}

function makePats(words)
{
  var regexs = new Array();
  for(idx in words)
  {
    regexs[regexs.length] = makePat(words[idx]);
    //console.log(regexs);
  }
  return(regexs);
}

function matchAD(text, regex)
{
  var hasAD = false;
  var results = text.match(regex);
  if(results && (results.length>0))
  {
    hasAD = true;
  }
  return(hasAD);
}

function notifyAD(info, fg, bg)
{
  var bgcolor='red';
  var fgcolor='white';
  if(bg) bgcolor = bg;
  if(fg) fgcolor = fg;

  $('a.gh-i-notice').css('background-color', bgcolor);
  $('a.gh-i-notice').css('color', fgcolor);
  var title = $('a.gh-i-notice').attr('title');
  if(title)
  {
    $('a.gh-i-notice').attr('title', title + '\n' + info);
  }
  else
  {
    $('a.gh-i-notice').attr('title', info);
  }

  $reportButton = $('.gh-notice li:first').before('
<button id="reportAD" style="margin-top:8px;">举报</button>
');
  //$reportButton.bind('click', reportAD);
  $('#reportAD').on('click', reportAD);

}

function highlightAD(word, node, mode)
{
  var ad_style = 'color:white; background-color:red;';
  var link_style = 'color:white; background-color:yellow;';
  var style = ad_style;

  var gwrap = $('div.gwrap');
  if(node)
  {
    gwrap = $(node);
  }
  if(mode && mode.toLowerCase()=='link')
  {
    style = link_style;
  }
  var html = gwrap.html().replace(word, function(m){
    return ''+m+''
  });
  gwrap.html( html );
}

function findingAD(items, regex, notice, mode)
{
  var hasAD = false;

  items.each(function(){
    var text = '';
    if(mode && mode.toLowerCase()=='link')
    {
      text = this.innerHTML;
    }
    else
    {
      text = this.textContent;
    }
    hasAD |= matchAD(text, regex);
    if(hasAD)
    {
      highlightAD(regex, this, mode);
    }
  });

  if(hasAD)
  {
    var info = "已发现" + notice;
    notifyAD(info);
    console.warn(info);
    //alert(info);
  }
  else
  {
    var info = "未发现" + notice;
    console.info(info);
  }
  return(hasAD);
}

function findingLink(items, hasAD)
{
  var bgcolor = 'yellow';
  var fgcolor = 'red';
  var hasLink = false;
  var notice = '外链';
  var link_pat = new RegExp('http://(?!.*?\.guokr\.com).*?$', 'gi');

  items.each(function(){
    var links = $(this).find('a');
    links.each(function(){
      var link = $(this);
      var isExtLink = link[0].href.match(link_pat);
      if(isExtLink && isExtLink.length>0)
      {
        hasLink = true;
        link.css('background-color', bgcolor);
        link.css('color', fgcolor);
      }
    });
  });

  if(hasLink)
  {
    var info = "已发现" + notice;
    if(hasAD) bgcolor='orange';
    notifyAD(info, fgcolor, bgcolor);
    console.warn(info);
    //alert(info);
  }
  else
  {
    var info = "未发现" + notice;
    console.info(info);
  }
  return(hasLink);
}

function getReportParam()
{
  var ca = document.cookie.split(';');
  var accessToken = null;
  for(idx in ca)
  {
    var item = ca[idx];
    var kv = item.split('=');
    var k = kv[0].trim();
    var v = kv[1].trim();
    if(k.endsWith('access_token'))
    {
      accessToken = v;
      break;
    }
  }
  return({url:document.location.href, reason:'垃圾广告', access_token:accessToken});
}

function reportAD()
{
  var reportParam = getReportParam();
  //console.log(reportParam);
  $.post('http://www.guokr.com/apis/censor/report.json', reportParam, function( data ){
    if(data.ok)
    {
      $('#reportAD').text('举报成功');
      console.log($('#reportAD').text());
    }
  }, "json");
}

function reportADs(btn)
{
  var reportParam = getReportParam();
  reportParam.url = $(btn).attr('data-url');
  //console.log(reportParam);
  $.post('http://www.guokr.com/apis/censor/report.json', reportParam, function( data ){
    if(data.ok)
    {
      $(btn).text('举报成功');
      console.log($(btn).text());
    }
  }, "json");
}

function addReportButtons()
{
  console.log('add button');
  var poster = $('.post-pic a, .author-pic');
  if(poster.length>0)
  {
    poster = $(poster[0]);
    poster.after('
<button id="reportUSER_poster" class="reportUSERs" title="举报此用户">举报</button>');
    var btnPoster = $('#reportUSER_poster');
    btnPoster.attr('data-url', poster[0].href);
    btnPoster.bind('click', function(){reportADs($(this))});
  }

  var avators = $('.pt-pic a');
  for(idx in avators)
  {
      var user = $(avators[idx]);
      var floor = user.siblings('.cmt-floor');
      //if(floor.length>0 && $.isNumeric(idx))
      if(floor.length>0 && idx<avators.length)
      {
        if(user.length>0)
        {
          floor = $(floor[0]);

          var btnUserID = 'reportUSER_'+ idx;
          floor.after('
<button id="'+ btnUserID +'" class="reportUSERs" title="举报此用户">举报</button>');

          var btnUser = $('#'+btnUserID);
          btnUser.attr('data-url', user[0].href);
          btnUser.bind('click', function(){reportADs($(this))});
        }
      }
  }

  var reportLinks = $('a.ghide.red-link');
  for(idx in reportLinks)
  {
    var link = $(reportLinks[idx]);
    var like = link.siblings('a.cmt-do-quote');
    //if(like.length>0 && $.isNumeric(idx))
    if(like.length>0 && idx<reportLinks.length)
    {
      like = $(like[0]);

      var btnID = 'reportAD_'+ idx;
      like.before('<button id="'+ btnID +'" class="reportADs" title="举报此回帖">举报</button>');

      var btn = $('#'+btnID);
      btn.attr('data-img', link.attr('data-img'));
      btn.attr('data-url', link.attr('data-url'));
      btn.attr('data-title', link.attr('data-title'));
      btn.attr('data-type', link.attr('data-type'));
      btn.attr('data-report', link.attr('data-report'));
      btn.bind('click', function(){reportADs($(this))});
    }
  }
}

function main(loaded)
{
  addReportButtons();

  var hasAD = false;

  var regexs = makePats(ADS);
  var author = $('#articleAuthor');
  var title = $('#articleTitle');
  var article = $('#articleContent');
  //var posts = $('.post-txt');
  var comments = $('.cmtContent, .answerTxt');

  //var items = $.extend({}, author, title, article, comments);
  var items = $.merge($.merge($.merge(author, title), article), comments);

  for(idx in regexs)
  {
    var AD = ADS[idx];
    //console.log('Finding ' + AD + ' ...');
    //console.log('Finding ' + regexs[idx] + ' ...');
    hasAD |= findingAD(items, regexs[idx], '广告:'+AD);
  }

  findingLink(items, hasAD);

}

main();
<|endoftext|>


<commit_before>import React from "react";
import moment from "moment";

class Record extends React.Component {
    render() {
        const {record} = this.props;

        return (

Id

{record.id}

First Name

{record.first_name}

Last Name

{record.last_name}

Email

<a href={`mailto:${record.email}`}>{record.email}

Date Joined

{moment(record.date_joined).format('dddd MMMM Do YYYY, h:mm A')}

Last Login

{moment(record.last_login).format('dddd MMMM Do YYYY, h:mm A')}

        );
    }
}

export default Record;
<commit_msg>Use short format when displaying dates on user record<commit_after>import React from "react";
import moment from "moment";

class Record extends React.Component {
    render() {
        const {record} = this.props;

        return (

Id

{record.id}

First Name

{record.first_name}

Last Name

{record.last_name}

Email

<a href={`mailto:${record.email}`}>{record.email}

Date Joined

{moment(record.date_joined).format('ddd. MMM. Do YYYY, h:mm A')}

Last Login

{moment(record.last_login).format('ddd. MMM. Do YYYY, h:mm A')}

        );
    }
}

export default Record;
<|endoftext|>


<commit_before>type $npm$classnames$Classes =
  string |
  {[className: string]: * } |
  Array<string> |
  false |
  void |
  null

declare module 'classnames' {
  declare function exports(
    ...classes: Array<$npm$classnames$Classes>
  ): string;
}
<commit_msg>update libdef definitions<commit_after>type $npm$classnames$Classes =
  | string
  | { [className: string]: * }
  | Array<string>
  | false
  | void
  | null;

declare module 'classnames' {
  declare function exports(...classes: Array<$npm$classnames$Classes>): string;
}

declare module 'classnames/bind' {
  declare module.exports: $Exports<'classnames'>;
}
<|endoftext|>


<commit_before>var child_process = require('child_process')

function exec(command)
{
	return child_process.execSync(command).toString().trim()
}

var metadata_changed = exec('git ls-files --modified PhoneNumberMetadata.xml')

if (!metadata_changed)
{
	console.log()
	console.log('========================================')
	console.log('=   Metadata is up-to-date. Exiting.   =')
	console.log('========================================')
	console.log()

	// The absense of the `update-metadata` branch will tell the script
	// that the metadata is up-to-date and doesn't need updating
	console.log(exec('git checkout master'))
	console.log(exec('git branch -D update-metadata'))
	process.exit(0)
}

console.log()
console.log('========================================')
console.log('= Metadata has changed, updating files =')
console.log('========================================')
console.log()

console.log(exec('npm run metadata:generate'))

console.log()
console.log('========================================')
console.log('=             Running tests            =')
console.log('========================================')
console.log()

console.log('* Actually not running tests because if they fail then it won\'t be reported in any way, and if instead tests fail for the Pull Request on github then the repo owner will be notified by Travis CI about that.')
// console.log(exec('npm test'))

var modified_files = exec('git ls-files --modified').split(/\s/)

if (modified_files.length > 2)
{
	console.log()
	console.log('========================================')
	console.log('=                 Error                =')
	console.log('========================================')
	console.log()
	console.log('Only `PhoneNumberMetadata.xml` and `metadata.min.json` should be modified.')
	console.log()
	console.log(modified_files.join('\n'))

	process.exit(1)
}

// Doesn't work
//
// // http://stackoverflow.com/questions/33610682/git-list-of-staged-files
// var staged_files = exec('git diff --name-only --cached').split(/\s/)
//
// if (staged_files.length > 0)
// {
// 	console.log()
// 	console.log('========================================')
// 	console.log('=                 Error                =')
// 	console.log('========================================')
// 	console.log()
// 	console.log('There are some staged files already. Aborting metadata update process.')
// 	console.log()
// 	console.log(staged_files.join('\n'))
//
// 	process.exit(1)
// }

console.log()
console.log('========================================')
console.log('=          Committing changes          =')
console.log('========================================')
console.log()

console.log(exec('git add PhoneNumberMetadata.xml metadata.min.json'))

console.log(exec('git commit -m "Phone number medatada update"'))
console.log(exec('git push'))

console.log()
console.log('========================================')
console.log('=               Finished               =')
console.log('========================================')<commit_msg>trying<commit_after>var child_process = require('child_process')

function exec(command)
{
	return child_process.execSync(command).toString().trim()
}

var metadata_changed = exec('git ls-files --modified PhoneNumberMetadata.xml')

if (!metadata_changed)
{
	console.log()
	console.log('========================================')
	console.log('=   Metadata is up-to-date. Exiting.   =')
	console.log('========================================')
	console.log()

	// The absense of the `update-metadata` branch will tell the script
	// that the metadata is up-to-date and doesn't need updating
	console.log(exec('git checkout master'))
	console.log(exec('git branch -D update-metadata'))
	process.exit(0)
}

console.log()
console.log('========================================')
console.log('= Metadata has changed, updating files =')
console.log('========================================')
console.log()

console.log(exec('npm run metadata:generate'))

console.log()
console.log('========================================')
console.log('=             Running tests            =')
console.log('========================================')
console.log()

console.log('* Actually not running tests because if they fail then it won\'t be reported in any way, and if instead tests fail for the Pull Request on github then the repo owner will be notified by Travis CI about that.')
// console.log(exec('npm test'))

var modified_files = exec('git ls-files --modified').split(/\s/)

if (modified_files.length > 2)
{
	console.log()
	console.log('========================================')
	console.log('=                 Error                =')
	console.log('========================================')
	console.log()
	console.log('Only `PhoneNumberMetadata.xml` and `metadata.min.json` should be modified.')
	console.log()
	console.log(modified_files.join('\n'))

	process.exit(1)
}

// Doesn't work
//
// // http://stackoverflow.com/questions/33610682/git-list-of-staged-files
// var staged_files = exec('git diff --name-only --cached').split(/\s/)
//
// if (staged_files.length > 0)
// {
// 	console.log()
// 	console.log('========================================')
// 	console.log('=                 Error                =')
// 	console.log('========================================')
// 	console.log()
// 	console.log('There are some staged files already. Aborting metadata update process.')
// 	console.log()
// 	console.log(staged_files.join('\n'))
//
// 	process.exit(1)
// }

console.log()
console.log('========================================')
console.log('=          Committing changes          =')
console.log('========================================')
console.log()

console.log(exec('git add PhoneNumberMetadata.xml metadata.min.json'))

console.log(exec('git commit -m "Phone number medatada update"'))
console.log(exec('git push origin update-metadata'))

console.log()
console.log('========================================')
console.log('=               Finished               =')
console.log('========================================')<|endoftext|>


<commit_before>function print() {
    __asmh__({},"#include <stdio.h>");
    for ( var i = 0, e = arguments.length; i < e; ++i ) {
        if (i > 0)
            __asm__({},[],[],"putchar(' ');");
        __asm__({},[],[["x", arguments[i]]],
                '%[x] = js::toString(&frame, %[x]);\n'+
                'printf("%s", %[x].raw.sval->getStr());'
        );
    }
    __asm__({},[],[],"putchar('\\n');");
}

var console = { log: print };

Function.prototype.call = function call (thisArg)
{
   __asm__({},[],[["thisArg", thisArg]],
            'return argc > 1' +
              '? js::call(&frame, argv[0], argc-1, argv+1)' +
              ': js::call(&frame, argv[0], 1, &%[thisArg]);'
   );
}
<commit_msg>runtime[core.js]: update for the latest __asm__ changes<commit_after>function print() {
    __asmh__({},"#include <stdio.h>");
    for ( var i = 0, e = arguments.length; i < e; ++i ) {
        if (i > 0)
            __asm__({},[],[],[],"putchar(' ');");
        __asm__({},[],[["x", arguments[i]]],[],
                '%[x] = js::toString(%[%frame], %[x]);\n'+
                'printf("%s", %[x].raw.sval->getStr());'
        );
    }
    __asm__({},[],[],[],"putchar('\\n');");
}

var console = { log: print };

Function.prototype.call = function call (thisArg)
{
    return __asm__({},["result"],[["thisArg", thisArg]],[],
        '%[result] = %[%argc] > 1' +
            '? js::call(%[%frame], %[%argv][0], %[%argc]-1, %[%argv]+1)' +
            ': js::call(%[%frame], %[%argv][0], 1, &%[thisArg]);'
    );
};
<|endoftext|>


<commit_before>
/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (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.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is guacamole-common-js.
 *
 * The Initial Developer of the Original Code is
 * Michael Jumper.
 * Portions created by the Initial Developer are Copyright (C) 2010
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

// Guacamole namespace
var Guacamole = Guacamole || {};

/**
 * Provides cross-browser mouse events for a given element. The events of
 * the given element are automatically populated with handlers that translate
 * mouse events into a non-browser-specific event provided by the
 * Guacamole.Mouse instance.
 *
 * Touch events are translated into mouse events as if the touches occurred
 * on a touchpad (drag to push the mouse pointer, tap to click).
 *
 * @constructor
 * @param {Element} element The Element to use to provide mouse events.
 */
Guacamole.Mouse = function(element) {

    /**
     * Reference to this Guacamole.Mouse.
     * @private
     */
    var guac_mouse = this;

    /**
     * The current mouse state. The properties of this state are updated when
     * mouse events fire. This state object is also passed in as a parameter to
     * the handler of any mouse events.
     *
     * @type Guacamole.Mouse.State
     */
    this.currentState = new Guacamole.Mouse.State(
        0, 0,
        false, false, false, false, false
    );

    /**
     * Fired whenever the user presses a mouse button down over the element
     * associated with this Guacamole.Mouse.
     *
     * @event
     * @param {Guacamole.Mouse.State} state The current mouse state.
     */
	this.onmousedown = null;

    /**
     * Fired whenever the user releases a mouse button down over the element
     * associated with this Guacamole.Mouse.
     *
     * @event
     * @param {Guacamole.Mouse.State} state The current mouse state.
     */
	this.onmouseup = null;

    /**
     * Fired whenever the user moves the mouse over the element associated with
     * this Guacamole.Mouse.
     *
     * @event
     * @param {Guacamole.Mouse.State} state The current mouse state.
     */
	this.onmousemove = null;

    function cancelEvent(e) {
        e.stopPropagation();
        if (e.preventDefault) e.preventDefault();
        e.returnValue = false;
    }

    function moveMouse(pageX, pageY) {

        guac_mouse.currentState.x = pageX - element.offsetLeft;
        guac_mouse.currentState.y = pageY - element.offsetTop;

        // This is all JUST so we can get the mouse position within the element
        var parent = element.offsetParent;
        while (parent) {

            if (parent.offsetLeft)
                guac_mouse.currentState.x -= parent.offsetLeft;

            if (parent.offsetTop)
                guac_mouse.currentState.y -= parent.offsetTop;

            parent = parent.offsetParent;
        }

        if (guac_mouse.onmousemove)
            guac_mouse.onmousemove(guac_mouse.currentState);

    }

    // Block context menu so right-click gets sent properly
    element.addEventListener("contextmenu", function(e) {
        cancelEvent(e);
    }, false);

    element.addEventListener("mousemove", function(e) {

        // Don't handle if we aren't supposed to
        if (gesture_in_progress) return;

        cancelEvent(e);

        moveMouse(e.pageX, e.pageY);

    }, false);

    var touch_count = 0;
    var last_touch_x = 0;
    var last_touch_y = 0;
    var last_touch_time = 0;
    var pixels_moved = 0;

    var touch_buttons = {
        1: "left",
        2: "right",
        3: "middle"
    };

    var gesture_in_progress = false;
    var click_release_timeout = null;

    element.addEventListener("touchend", function(e) {

        // If we're handling a gesture AND this is the last touch
        if (gesture_in_progress && e.touches.length == 1) {

            cancelEvent(e);

            var time = new Date().getTime();

            // Get corresponding mouse button
            var button = touch_buttons[touch_count];

            // If mouse already down, release anad clear timeout
            if (guac_mouse.currentState[button]) {

                // Fire button up event
                guac_mouse.currentState[button] = false;
                if (guac_mouse.onmouseup)
                    guac_mouse.onmouseup(guac_mouse.currentState);

                // Clear timeout, if set
                if (click_release_timeout) {
                    window.clearTimeout(click_release_timeout);
                    click_release_timeout = null;
                }

            }

            // If single tap detected (based on time and distance)
            if (time - last_touch_time <= 250 && pixels_moved < 10) {

                // Fire button down event
                guac_mouse.currentState[button] = true;
                if (guac_mouse.onmousedown)
                    guac_mouse.onmousedown(guac_mouse.currentState);

                // Delay mouse up - mouse up should be canceled if
                // touchstart within timeout.
                click_release_timeout = window.setTimeout(function() {

                    // Fire button up event
                    guac_mouse.currentState[button] = false;
                    if (guac_mouse.onmouseup)
                        guac_mouse.onmouseup(guac_mouse.currentState);

                    // Allow mouse events now that touching is over
                    gesture_in_progress = false;

                }, 250);

            }

        }

    }, false);

    element.addEventListener("touchstart", function(e) {

        // Track number of touches, but no more than three
        touch_count = Math.max(e.touches.length, 3);

        // Record initial touch location and time for touch movement
        // and tap gestures
        if (e.touches.length == 1) {

            cancelEvent(e);

            // Stop mouse events while touching
            gesture_in_progress = true;

            // Clear timeout, if set
            if (click_release_timeout) {
                window.clearTimeout(click_release_timeout);
                click_release_timeout = null;
            }

            // Record touch location and time
            var starting_touch = e.touches[0];
            last_touch_x = starting_touch.screenX;
            last_touch_y = starting_touch.screenY;
            last_touch_time = new Date().getTime();
            pixels_moved = 0;

        }

    }, false);

    element.addEventListener("touchmove", function(e) {

        cancelEvent(e);

        // Get change in touch location
        var touch = e.touches[0];
        var delta_x = touch.screenX - last_touch_x;
        var delta_y = touch.screenY - last_touch_y;

        // Track pixels moved
        pixels_moved += Math.abs(delta_x) + Math.abs(delta_y);

        // Update mouse location
        guac_mouse.currentState.x += delta_x;
        guac_mouse.currentState.y += delta_y;

        // Prevent mouse from leaving screen

        if (guac_mouse.currentState.x < 0)
            guac_mouse.currentState.x = 0;
        else if (guac_mouse.currentState.x >= element.offsetWidth)
            guac_mouse.currentState.x = element.offsetWidth - 1;

        if (guac_mouse.currentState.y < 0)
            guac_mouse.currentState.y = 0;
        else if (guac_mouse.currentState.y >= element.offsetHeight)
            guac_mouse.currentState.y = element.offsetHeight - 1;

        // Fire movement event, if defined
        if (guac_mouse.onmousemove)
            guac_mouse.onmousemove(guac_mouse.currentState);

        // Update touch location
        last_touch_x = touch.screenX;
        last_touch_y = touch.screenY;

    }, false);

    element.addEventListener("mousedown", function(e) {

        // Don't handle if we aren't supposed to
        if (gesture_in_progress) return;

        cancelEvent(e);

        switch (e.button) {
            case 0:
                guac_mouse.currentState.left = true;
                break;
            case 1:
                guac_mouse.currentState.middle = true;
                break;
            case 2:
                guac_mouse.currentState.right = true;
                break;
        }

        if (guac_mouse.onmousedown)
            guac_mouse.onmousedown(guac_mouse.currentState);

    }, false);

    element.addEventListener("mouseup", function(e) {

        // Don't handle if we aren't supposed to
        if (gesture_in_progress) return;

        cancelEvent(e);

        switch (e.button) {
            case 0:
                guac_mouse.currentState.left = false;
                break;
            case 1:
                guac_mouse.currentState.middle = false;
                break;
            case 2:
                guac_mouse.currentState.right = false;
                break;
        }

        if (guac_mouse.onmouseup)
            guac_mouse.onmouseup(guac_mouse.currentState);

    }, false);

    element.addEventListener("mouseout", function(e) {

        // Don't handle if we aren't supposed to
        if (gesture_in_progress) return;

        // Get parent of the element the mouse pointer is leaving
       	if (!e) e = window.event;

        // Check that mouseout is due to actually LEAVING the element
        var target = e.relatedTarget || e.toElement;
        while (target != null) {
            if (target === element)
                return;
            target = target.parentNode;
        }

        cancelEvent(e);

        // Release all buttons
        if (guac_mouse.currentState.left
            || guac_mouse.currentState.middle
            || guac_mouse.currentState.right) {

            guac_mouse.currentState.left = false;
            guac_mouse.currentState.middle = false;
            guac_mouse.currentState.right = false;

            if (guac_mouse.onmouseup)
                guac_mouse.onmouseup(guac_mouse.currentState);
        }

    }, false);

    // Override selection on mouse event element.
    element.addEventListener("selectstart", function(e) {
        cancelEvent(e);
    }, false);

    // Scroll wheel support
    function mousewheel_handler(e) {

        // Don't handle if we aren't supposed to
        if (gesture_in_progress) return;

        var delta = 0;
        if (e.detail)
            delta = e.detail;
        else if (e.wheelDelta)
            delta = -event.wheelDelta;

        // Up
        if (delta < 0) {
            if (guac_mouse.onmousedown) {
                guac_mouse.currentState.up = true;
                guac_mouse.onmousedown(guac_mouse.currentState);
            }

            if (guac_mouse.onmouseup) {
                guac_mouse.currentState.up = false;
                guac_mouse.onmouseup(guac_mouse.currentState);
            }
        }

        // Down
        if (delta > 0) {
            if (guac_mouse.onmousedown) {
                guac_mouse.currentState.down = true;
                guac_mouse.onmousedown(guac_mouse.currentState);
            }

            if (guac_mouse.onmouseup) {
                guac_mouse.currentState.down = false;
                guac_mouse.onmouseup(guac_mouse.currentState);
            }
        }

        cancelEvent(e);

    }
    element.addEventListener('DOMMouseScroll', mousewheel_handler, false);
    element.addEventListener('mousewheel',     mousewheel_handler, false);

};

/**
 * Simple container for properties describing the state of a mouse.
 *
 * @constructor
 * @param {Number} x The X position of the mouse pointer in pixels.
 * @param {Number} y The Y position of the mouse pointer in pixels.
 * @param {Boolean} left Whether the left mouse button is pressed.
 * @param {Boolean} middle Whether the middle mouse button is pressed.
 * @param {Boolean} right Whether the right mouse button is pressed.
 * @param {Boolean} up Whether the up mouse button is pressed (the fourth
 *                     button, usually part of a scroll wheel).
 * @param {Boolean} down Whether the down mouse button is pressed (the fifth
 *                       button, usually part of a scroll wheel).
 */
Guacamole.Mouse.State = function(x, y, left, middle, right, up, down) {

    /**
     * The current X position of the mouse pointer.
     * @type Number
     */
    this.x = x;

    /**
     * The current Y position of the mouse pointer.
     * @type Number
     */
    this.y = y;

    /**
     * Whether the left mouse button is currently pressed.
     * @type Boolean
     */
    this.left = left;

    /**
     * Whether the middle mouse button is currently pressed.
     * @type Boolean
     */
    this.middle = middle

    /**
     * Whether the right mouse button is currently pressed.
     * @type Boolean
     */
    this.right = right;

    /**
     * Whether the up mouse button is currently pressed. This is the fourth
     * mouse button, associated with upward scrolling of the mouse scroll
     * wheel.
     * @type Boolean
     */
    this.up = up;

    /**
     * Whether the down mouse button is currently pressed. This is the fifth
     * mouse button, associated with downward scrolling of the mouse scroll
     * wheel.
     * @type Boolean
     */
    this.down = down;

};

<commit_msg>Check for last touch by verifying touch length 0, not 1. Fix wrong use of max() (should be min).<commit_after>
/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (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.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is guacamole-common-js.
 *
 * The Initial Developer of the Original Code is
 * Michael Jumper.
 * Portions created by the Initial Developer are Copyright (C) 2010
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

// Guacamole namespace
var Guacamole = Guacamole || {};

/**
 * Provides cross-browser mouse events for a given element. The events of
 * the given element are automatically populated with handlers that translate
 * mouse events into a non-browser-specific event provided by the
 * Guacamole.Mouse instance.
 *
 * Touch events are translated into mouse events as if the touches occurred
 * on a touchpad (drag to push the mouse pointer, tap to click).
 *
 * @constructor
 * @param {Element} element The Element to use to provide mouse events.
 */
Guacamole.Mouse = function(element) {

    /**
     * Reference to this Guacamole.Mouse.
     * @private
     */
    var guac_mouse = this;

    /**
     * The current mouse state. The properties of this state are updated when
     * mouse events fire. This state object is also passed in as a parameter to
     * the handler of any mouse events.
     *
     * @type Guacamole.Mouse.State
     */
    this.currentState = new Guacamole.Mouse.State(
        0, 0,
        false, false, false, false, false
    );

    /**
     * Fired whenever the user presses a mouse button down over the element
     * associated with this Guacamole.Mouse.
     *
     * @event
     * @param {Guacamole.Mouse.State} state The current mouse state.
     */
	this.onmousedown = null;

    /**
     * Fired whenever the user releases a mouse button down over the element
     * associated with this Guacamole.Mouse.
     *
     * @event
     * @param {Guacamole.Mouse.State} state The current mouse state.
     */
	this.onmouseup = null;

    /**
     * Fired whenever the user moves the mouse over the element associated with
     * this Guacamole.Mouse.
     *
     * @event
     * @param {Guacamole.Mouse.State} state The current mouse state.
     */
	this.onmousemove = null;

    function cancelEvent(e) {
        e.stopPropagation();
        if (e.preventDefault) e.preventDefault();
        e.returnValue = false;
    }

    function moveMouse(pageX, pageY) {

        guac_mouse.currentState.x = pageX - element.offsetLeft;
        guac_mouse.currentState.y = pageY - element.offsetTop;

        // This is all JUST so we can get the mouse position within the element
        var parent = element.offsetParent;
        while (parent) {

            if (parent.offsetLeft)
                guac_mouse.currentState.x -= parent.offsetLeft;

            if (parent.offsetTop)
                guac_mouse.currentState.y -= parent.offsetTop;

            parent = parent.offsetParent;
        }

        if (guac_mouse.onmousemove)
            guac_mouse.onmousemove(guac_mouse.currentState);

    }

    // Block context menu so right-click gets sent properly
    element.addEventListener("contextmenu", function(e) {
        cancelEvent(e);
    }, false);

    element.addEventListener("mousemove", function(e) {

        // Don't handle if we aren't supposed to
        if (gesture_in_progress) return;

        cancelEvent(e);

        moveMouse(e.pageX, e.pageY);

    }, false);

    var touch_count = 0;
    var last_touch_x = 0;
    var last_touch_y = 0;
    var last_touch_time = 0;
    var pixels_moved = 0;

    var touch_buttons = {
        1: "left",
        2: "right",
        3: "middle"
    };

    var gesture_in_progress = false;
    var click_release_timeout = null;

    element.addEventListener("touchend", function(e) {

        // If we're handling a gesture AND this is the last touch
        if (gesture_in_progress && e.touches.length == 0) {

            cancelEvent(e);

            var time = new Date().getTime();

            // Get corresponding mouse button
            var button = touch_buttons[touch_count];

            // If mouse already down, release anad clear timeout
            if (guac_mouse.currentState[button]) {

                // Fire button up event
                guac_mouse.currentState[button] = false;
                if (guac_mouse.onmouseup)
                    guac_mouse.onmouseup(guac_mouse.currentState);

                // Clear timeout, if set
                if (click_release_timeout) {
                    window.clearTimeout(click_release_timeout);
                    click_release_timeout = null;
                }

            }

            // If single tap detected (based on time and distance)
            if (time - last_touch_time <= 250 && pixels_moved < 10) {

                // Fire button down event
                guac_mouse.currentState[button] = true;
                if (guac_mouse.onmousedown)
                    guac_mouse.onmousedown(guac_mouse.currentState);

                // Delay mouse up - mouse up should be canceled if
                // touchstart within timeout.
                click_release_timeout = window.setTimeout(function() {

                    // Fire button up event
                    guac_mouse.currentState[button] = false;
                    if (guac_mouse.onmouseup)
                        guac_mouse.onmouseup(guac_mouse.currentState);

                    // Allow mouse events now that touching is over
                    gesture_in_progress = false;

                }, 250);

            }

        }

    }, false);

    element.addEventListener("touchstart", function(e) {

        // Track number of touches, but no more than three
        touch_count = Math.min(e.touches.length, 3);

        // Record initial touch location and time for touch movement
        // and tap gestures
        if (e.touches.length == 1) {

            cancelEvent(e);

            // Stop mouse events while touching
            gesture_in_progress = true;

            // Clear timeout, if set
            if (click_release_timeout) {
                window.clearTimeout(click_release_timeout);
                click_release_timeout = null;
            }

            // Record touch location and time
            var starting_touch = e.touches[0];
            last_touch_x = starting_touch.screenX;
            last_touch_y = starting_touch.screenY;
            last_touch_time = new Date().getTime();
            pixels_moved = 0;

        }

    }, false);

    element.addEventListener("touchmove", function(e) {

        cancelEvent(e);

        // Get change in touch location
        var touch = e.touches[0];
        var delta_x = touch.screenX - last_touch_x;
        var delta_y = touch.screenY - last_touch_y;

        // Track pixels moved
        pixels_moved += Math.abs(delta_x) + Math.abs(delta_y);

        // Update mouse location
        guac_mouse.currentState.x += delta_x;
        guac_mouse.currentState.y += delta_y;

        // Prevent mouse from leaving screen

        if (guac_mouse.currentState.x < 0)
            guac_mouse.currentState.x = 0;
        else if (guac_mouse.currentState.x >= element.offsetWidth)
            guac_mouse.currentState.x = element.offsetWidth - 1;

        if (guac_mouse.currentState.y < 0)
            guac_mouse.currentState.y = 0;
        else if (guac_mouse.currentState.y >= element.offsetHeight)
            guac_mouse.currentState.y = element.offsetHeight - 1;

        // Fire movement event, if defined
        if (guac_mouse.onmousemove)
            guac_mouse.onmousemove(guac_mouse.currentState);

        // Update touch location
        last_touch_x = touch.screenX;
        last_touch_y = touch.screenY;

    }, false);

    element.addEventListener("mousedown", function(e) {

        // Don't handle if we aren't supposed to
        if (gesture_in_progress) return;

        cancelEvent(e);

        switch (e.button) {
            case 0:
                guac_mouse.currentState.left = true;
                break;
            case 1:
                guac_mouse.currentState.middle = true;
                break;
            case 2:
                guac_mouse.currentState.right = true;
                break;
        }

        if (guac_mouse.onmousedown)
            guac_mouse.onmousedown(guac_mouse.currentState);

    }, false);

    element.addEventListener("mouseup", function(e) {

        // Don't handle if we aren't supposed to
        if (gesture_in_progress) return;

        cancelEvent(e);

        switch (e.button) {
            case 0:
                guac_mouse.currentState.left = false;
                break;
            case 1:
                guac_mouse.currentState.middle = false;
                break;
            case 2:
                guac_mouse.currentState.right = false;
                break;
        }

        if (guac_mouse.onmouseup)
            guac_mouse.onmouseup(guac_mouse.currentState);

    }, false);

    element.addEventListener("mouseout", function(e) {

        // Don't handle if we aren't supposed to
        if (gesture_in_progress) return;

        // Get parent of the element the mouse pointer is leaving
       	if (!e) e = window.event;

        // Check that mouseout is due to actually LEAVING the element
        var target = e.relatedTarget || e.toElement;
        while (target != null) {
            if (target === element)
                return;
            target = target.parentNode;
        }

        cancelEvent(e);

        // Release all buttons
        if (guac_mouse.currentState.left
            || guac_mouse.currentState.middle
            || guac_mouse.currentState.right) {

            guac_mouse.currentState.left = false;
            guac_mouse.currentState.middle = false;
            guac_mouse.currentState.right = false;

            if (guac_mouse.onmouseup)
                guac_mouse.onmouseup(guac_mouse.currentState);
        }

    }, false);

    // Override selection on mouse event element.
    element.addEventListener("selectstart", function(e) {
        cancelEvent(e);
    }, false);

    // Scroll wheel support
    function mousewheel_handler(e) {

        // Don't handle if we aren't supposed to
        if (gesture_in_progress) return;

        var delta = 0;
        if (e.detail)
            delta = e.detail;
        else if (e.wheelDelta)
            delta = -event.wheelDelta;

        // Up
        if (delta < 0) {
            if (guac_mouse.onmousedown) {
                guac_mouse.currentState.up = true;
                guac_mouse.onmousedown(guac_mouse.currentState);
            }

            if (guac_mouse.onmouseup) {
                guac_mouse.currentState.up = false;
                guac_mouse.onmouseup(guac_mouse.currentState);
            }
        }

        // Down
        if (delta > 0) {
            if (guac_mouse.onmousedown) {
                guac_mouse.currentState.down = true;
                guac_mouse.onmousedown(guac_mouse.currentState);
            }

            if (guac_mouse.onmouseup) {
                guac_mouse.currentState.down = false;
                guac_mouse.onmouseup(guac_mouse.currentState);
            }
        }

        cancelEvent(e);

    }
    element.addEventListener('DOMMouseScroll', mousewheel_handler, false);
    element.addEventListener('mousewheel',     mousewheel_handler, false);

};

/**
 * Simple container for properties describing the state of a mouse.
 *
 * @constructor
 * @param {Number} x The X position of the mouse pointer in pixels.
 * @param {Number} y The Y position of the mouse pointer in pixels.
 * @param {Boolean} left Whether the left mouse button is pressed.
 * @param {Boolean} middle Whether the middle mouse button is pressed.
 * @param {Boolean} right Whether the right mouse button is pressed.
 * @param {Boolean} up Whether the up mouse button is pressed (the fourth
 *                     button, usually part of a scroll wheel).
 * @param {Boolean} down Whether the down mouse button is pressed (the fifth
 *                       button, usually part of a scroll wheel).
 */
Guacamole.Mouse.State = function(x, y, left, middle, right, up, down) {

    /**
     * The current X position of the mouse pointer.
     * @type Number
     */
    this.x = x;

    /**
     * The current Y position of the mouse pointer.
     * @type Number
     */
    this.y = y;

    /**
     * Whether the left mouse button is currently pressed.
     * @type Boolean
     */
    this.left = left;

    /**
     * Whether the middle mouse button is currently pressed.
     * @type Boolean
     */
    this.middle = middle

    /**
     * Whether the right mouse button is currently pressed.
     * @type Boolean
     */
    this.right = right;

    /**
     * Whether the up mouse button is currently pressed. This is the fourth
     * mouse button, associated with upward scrolling of the mouse scroll
     * wheel.
     * @type Boolean
     */
    this.up = up;

    /**
     * Whether the down mouse button is currently pressed. This is the fifth
     * mouse button, associated with downward scrolling of the mouse scroll
     * wheel.
     * @type Boolean
     */
    this.down = down;

};

<|endoftext|>