<commit_before>const { AppSetting } = require('../core/app_setting');
const { BagItProfile } = require('../bagit/bagit_profile');
const { Constants } = require('../core/constants');
const { fork, spawn } = require('child_process');
const fs = require('fs');
const { Job } = require('../core/job');
const { JobParams } = require('../core/job_params');
const os = require('os');
const { PackageOperation } = require('../core/package_operation');
const path = require('path');
const { TestUtil } = require('../core/test_util');
const { Workflow } = require('../core/workflow');

// Values for these three will be set below.
let jobId = null;
let workflow = null;
let outputPath = null;

const bagItProfileId = '28f48fcc-064d-4acf-bb5b-ea6ad5c6264d';
const tarPluginId = '90110710-1ff9-4650-a086-d7b23772238f';

beforeAll(() => {
    setBaggingDir();
    saveBagItProfile();
    jobId = saveRunnableJob();
    workflow = saveRunnableWorkflow();
});

afterAll(() => {
    TestUtil.deleteJsonFile('AppSetting');
    TestUtil.deleteJsonFile('BagItProfile');
    TestUtil.deleteJsonFile('Job');
    TestUtil.deleteJsonFile('Workflow');
});

function setBaggingDir() {
    new AppSetting({
        name: 'Bagging Directory',
        value: os.tmpdir()
    }).save();
}

function saveBagItProfile() {
    let profile = BagItProfile.load(path.join(__dirname, '..', 'test', 'profiles', 'multi_manifest.json'));
    profile.save();
}

function saveRunnableJob() {
    let outputDir = AppSetting.list()[0].value;
    outputPath = path.join(outputDir, 'CLI_Test_Bag.tar');
    let bagItProfile = BagItProfile.find(bagItProfileId);
    setTag(bagItProfile, 'Source-Organization', 'Test University');
    setTag(bagItProfile, 'Title', 'CLI Test Bag');
    setTag(bagItProfile, 'Access', 'Institution');
    setTag(bagItProfile, 'Storage-Option', 'Standard');
    let packageOp = new PackageOperation('CLI_Test_Bag', outputPath);
    packageOp.packageFormat = 'BagIt';
    packageOp.pluginId = 'BagIt';
    packageOp.sourceFiles = [ __dirname ];
    let job = new Job({
        bagItProfile: bagItProfile,
        packageOp: packageOp
    });
    job.save();
    return job.id;
}

function setTag(profile, name, value) {
    profile.firstMatchingTag('tagName', name).userValue = value;
}

function saveRunnableWorkflow() {
    let workflow = new Workflow({
	    name: "CLI Test Workflow",
	    description: "Includes BagIt packaging but no uploads",
	    packageFormat: "BagIt",
	    packagePluginId: tarPluginId,
	    bagItProfileId: bagItProfileId,
	    storageServiceIds: []
    });
    workflow.save();
    return workflow;
}

function createJobParams() {
    return new JobParams({
        workflowName: 'CLI Test Workflow',
        packageName: path.basename(outputPath),
        files: [ __dirname ],
        tags: [
  		    {
  			    "tagFile": "bag-info.txt",
  			    "tagName": "Bag-Group-Identifier",
  			    "userValue": "Photos_2019"
  		    },
  		    {
  			    "tagFile": "bag-info.txt",
  			    "tagName": "Source-Organization",
  			    "userValue": "Test University"
  		    },
  		    {
  			    "tagFile": "aptrust-info.txt",
  			    "tagName": "Title",
  			    "userValue": "Photos from 2019"
  		    },
  		    {
  			    "tagFile": "aptrust-info.txt",
  			    "tagName": "Storage-Option",
  			    "userValue": "Standard"
  		    },
  		    {
  			    "tagFile": "aptrust-info.txt",
  			    "tagName": "Access",
  			    "userValue": "Institution"
  		    }
        ]
    });
}

function forkProcess(param, stdinData) {
    let modulePath = path.join(__dirname, '..', 'main.js');
    let params = ['--job', param];
    if (stdinData) {
        params = ['--stdin'];
    }
    let proc = fork(
        modulePath,
        params,
        { stdio: ['pipe', 'pipe', 'pipe', 'ipc'] }
    );
    if (stdinData) {
        proc.stdin.write(stdinData + "\n");
        proc.stdin.end();
    }
    proc.on('message', (data) => {
         console.log(data.toString());
    });
    proc.stdout.on('data', function(data) {
        console.log(data.toString());
    });
    proc.stderr.on('data', function(data) {
        console.error(data.toString());
    });
    expect(proc).toBeTruthy();
    return proc;
}

// Be safe about this. Don't delete anything outside the
// tmp dir.
function removeFile(filepath) {
    if (filepath.startsWith(os.tmpdir())&& fs.existsSync(filepath)) {
        fs.unlinkSync(filepath);
    }
}

test('Run job by UUID', done => {
    // Pass in the job UUID as the command line arg.
    // The JobLoader should load the job from the DART Jobs DB.
    forkProcess(jobId).on('exit', function(exitCode){
        expect(exitCode).toEqual(Constants.EXIT_SUCCESS);
        expect(outputPath).toBeTruthy();
        expect(fs.existsSync(outputPath)).toBe(true);
        removeFile(outputPath);
        done();
    });
});

test('Run job from Job JSON file', done => {
    let job = Job.find(jobId);
    let jsonPath = path.join(os.tmpdir(), 'DART_CLI_Test_Job.json');
    fs.writeFileSync(jsonPath, JSON.stringify(job));

    // Pass in the path of the Job JSON file as arg.
    // The JobLoader should load the job from that file.
    forkProcess(jsonPath).on('exit', function(exitCode){
        expect(exitCode).toEqual(Constants.EXIT_SUCCESS);
        expect(outputPath).toBeTruthy();
        expect(fs.existsSync(outputPath)).toBe(true);
        removeFile(outputPath);
        removeFile(jsonPath);
        done();
    });
});

test('Run job from JobParams JSON file', done => {
    let jobParams = createJobParams();
    let jsonPath = path.join(os.tmpdir(), 'DART_CLI_Test_JobParams.json');
    fs.writeFileSync(jsonPath, JSON.stringify(jobParams));

    // Pass in the path of the JobParams JSON file as arg.
    // The JobLoader should parse that file, assemble a Job
    // from it, and run the job.
    forkProcess(jsonPath).on('exit', function(exitCode){
        expect(exitCode).toEqual(Constants.EXIT_SUCCESS);
        expect(outputPath).toBeTruthy();
        expect(fs.existsSync(outputPath)).toBe(true);
        removeFile(outputPath);
        removeFile(jsonPath);
        done();
    });
});

test('Run job from Job JSON passed through STDIN', done => {
    let job = Job.find(jobId);
    let jobJson = JSON.stringify(job);
    forkProcess('', jobJson).on('exit', function(exitCode){
        expect(exitCode).toEqual(Constants.EXIT_SUCCESS);
        expect(outputPath).toBeTruthy();
        expect(fs.existsSync(outputPath)).toBe(true);
        removeFile(outputPath);
        done();
    });
});

test('Run job from JobParams JSON passed through STDIN', done => {
    let jobParams = createJobParams();
    let jsonData = JSON.stringify(jobParams);

    forkProcess(null, jsonData).on('exit', function(exitCode){
        expect(exitCode).toEqual(Constants.EXIT_SUCCESS);
        expect(outputPath).toBeTruthy();
        expect(fs.existsSync(outputPath)).toBe(true);
        removeFile(outputPath);
        done();
    });
});
<commit_msg>Allow time for process to start in CLI test<commit_after>const { AppSetting } = require('../core/app_setting');
const { BagItProfile } = require('../bagit/bagit_profile');
const { Constants } = require('../core/constants');
const { fork, spawn } = require('child_process');
const fs = require('fs');
const { Job } = require('../core/job');
const { JobParams } = require('../core/job_params');
const os = require('os');
const { PackageOperation } = require('../core/package_operation');
const path = require('path');
const { TestUtil } = require('../core/test_util');
const { Workflow } = require('../core/workflow');

// Values for these three will be set below.
let jobId = null;
let workflow = null;
let outputPath = null;

const bagItProfileId = '28f48fcc-064d-4acf-bb5b-ea6ad5c6264d';
const tarPluginId = '90110710-1ff9-4650-a086-d7b23772238f';

beforeAll(() => {
    setBaggingDir();
    saveBagItProfile();
    jobId = saveRunnableJob();
    workflow = saveRunnableWorkflow();
});

afterAll(() => {
    TestUtil.deleteJsonFile('AppSetting');
    TestUtil.deleteJsonFile('BagItProfile');
    TestUtil.deleteJsonFile('Job');
    TestUtil.deleteJsonFile('Workflow');
});

function setBaggingDir() {
    new AppSetting({
        name: 'Bagging Directory',
        value: os.tmpdir()
    }).save();
}

function saveBagItProfile() {
    let profile = BagItProfile.load(path.join(__dirname, '..', 'test', 'profiles', 'multi_manifest.json'));
    profile.save();
}

function saveRunnableJob() {
    let outputDir = AppSetting.list()[0].value;
    outputPath = path.join(outputDir, 'CLI_Test_Bag.tar');
    let bagItProfile = BagItProfile.find(bagItProfileId);
    setTag(bagItProfile, 'Source-Organization', 'Test University');
    setTag(bagItProfile, 'Title', 'CLI Test Bag');
    setTag(bagItProfile, 'Access', 'Institution');
    setTag(bagItProfile, 'Storage-Option', 'Standard');
    let packageOp = new PackageOperation('CLI_Test_Bag', outputPath);
    packageOp.packageFormat = 'BagIt';
    packageOp.pluginId = 'BagIt';
    packageOp.sourceFiles = [ __dirname ];
    let job = new Job({
        bagItProfile: bagItProfile,
        packageOp: packageOp
    });
    job.save();
    return job.id;
}

function setTag(profile, name, value) {
    profile.firstMatchingTag('tagName', name).userValue = value;
}

function saveRunnableWorkflow() {
    let workflow = new Workflow({
	    name: "CLI Test Workflow",
	    description: "Includes BagIt packaging but no uploads",
	    packageFormat: "BagIt",
	    packagePluginId: tarPluginId,
	    bagItProfileId: bagItProfileId,
	    storageServiceIds: []
    });
    workflow.save();
    return workflow;
}

function createJobParams() {
    return new JobParams({
        workflowName: 'CLI Test Workflow',
        packageName: path.basename(outputPath),
        files: [ __dirname ],
        tags: [
  		    {
  			    "tagFile": "bag-info.txt",
  			    "tagName": "Bag-Group-Identifier",
  			    "userValue": "Photos_2019"
  		    },
  		    {
  			    "tagFile": "bag-info.txt",
  			    "tagName": "Source-Organization",
  			    "userValue": "Test University"
  		    },
  		    {
  			    "tagFile": "aptrust-info.txt",
  			    "tagName": "Title",
  			    "userValue": "Photos from 2019"
  		    },
  		    {
  			    "tagFile": "aptrust-info.txt",
  			    "tagName": "Storage-Option",
  			    "userValue": "Standard"
  		    },
  		    {
  			    "tagFile": "aptrust-info.txt",
  			    "tagName": "Access",
  			    "userValue": "Institution"
  		    }
        ]
    });
}

function forkProcess(param, stdinData) {
    let modulePath = path.join(__dirname, '..', 'main.js');
    let params = ['--job', param];
    if (stdinData) {
        params = ['--stdin'];
    }
    let proc = fork(
        modulePath,
        params,
        { stdio: ['pipe', 'pipe', 'pipe', 'ipc'] }
    );
    proc.on('message', (data) => {
        // console.log(data.toString());
    });
    proc.stdout.on('data', function(data) {
        // console.log(data.toString());
    });
    proc.stderr.on('data', function(data) {
        // console.error(data.toString());
    });
    // When sending job json to the child process' stdin,
    // we have to wait till it's ready, or it reads only
    // the last N bytes of the input. Node processes do not
    // emit a 'started' or 'ready' event, so we have to
    // give it some time to load before sending data to stdin.
    // The load time below is a guess, and varies from system
    // to system. Not sure why fork returns a process that isn't
    // fully initialized. :(
    if (stdinData) {
        setTimeout(function() {
            proc.stdin.write(stdinData + "\n");
            proc.stdin.end();
        }, 800)
    }
    expect(proc).toBeTruthy();
    return proc;
}

// Be safe about this. Don't delete anything outside the
// tmp dir.
function removeFile(filepath) {
    if (filepath.startsWith(os.tmpdir())&& fs.existsSync(filepath)) {
        fs.unlinkSync(filepath);
    }
}

test('Run job by UUID', done => {
    // Pass in the job UUID as the command line arg.
    // The JobLoader should load the job from the DART Jobs DB.
    forkProcess(jobId).on('exit', function(exitCode){
        expect(exitCode).toEqual(Constants.EXIT_SUCCESS);
        expect(outputPath).toBeTruthy();
        expect(fs.existsSync(outputPath)).toBe(true);
        removeFile(outputPath);
        done();
    });
});

test('Run job from Job JSON file', done => {
    let job = Job.find(jobId);
    let jsonPath = path.join(os.tmpdir(), 'DART_CLI_Test_Job.json');
    fs.writeFileSync(jsonPath, JSON.stringify(job));

    // Pass in the path of the Job JSON file as arg.
    // The JobLoader should load the job from that file.
    forkProcess(jsonPath).on('exit', function(exitCode){
        expect(exitCode).toEqual(Constants.EXIT_SUCCESS);
        expect(outputPath).toBeTruthy();
        expect(fs.existsSync(outputPath)).toBe(true);
        removeFile(outputPath);
        removeFile(jsonPath);
        done();
    });
});

test('Run job from JobParams JSON file', done => {
    let jobParams = createJobParams();
    let jsonPath = path.join(os.tmpdir(), 'DART_CLI_Test_JobParams.json');
    fs.writeFileSync(jsonPath, JSON.stringify(jobParams));

    // Pass in the path of the JobParams JSON file as arg.
    // The JobLoader should parse that file, assemble a Job
    // from it, and run the job.
    forkProcess(jsonPath).on('exit', function(exitCode){
        expect(exitCode).toEqual(Constants.EXIT_SUCCESS);
        expect(outputPath).toBeTruthy();
        expect(fs.existsSync(outputPath)).toBe(true);
        removeFile(outputPath);
        removeFile(jsonPath);
        done();
    });
});

test('Run job from Job JSON passed through STDIN', done => {
    let job = Job.find(jobId);
    let jobJson = JSON.stringify(job);
    forkProcess('', jobJson).on('exit', function(exitCode){
        expect(exitCode).toEqual(Constants.EXIT_SUCCESS);
        expect(outputPath).toBeTruthy();
        expect(fs.existsSync(outputPath)).toBe(true);
        removeFile(outputPath);
        done();
    });
});

test('Run job from JobParams JSON passed through STDIN', done => {
    let jobParams = createJobParams();
    let jsonData = JSON.stringify(jobParams);

    forkProcess(null, jsonData).on('exit', function(exitCode){
        expect(exitCode).toEqual(Constants.EXIT_SUCCESS);
        expect(outputPath).toBeTruthy();
        expect(fs.existsSync(outputPath)).toBe(true);
        removeFile(outputPath);
        done();
    });
});
<|endoftext|>


<commit_before>const cheerio = require('cheerio');
const { isString, isFunction, castArray } = require('lodash');

/**
 * Methods to handle the manipulation of DOM documents.
 * Uses cheerio under the hood.
 *
 * @type {object}
 */
module.exports = {

  /**
   * Loads the document. If the argument provided is a string,
   * it will be transformed into a cheerio document.
   *
   * @param  {(string|cheerio)} data  The document data.
   * @return {cheerio}  The loaded cheerio document.
   */
	loadDocument: function(data) {
		return cheerio.load(data);
	},

  /**
   * Loads the root of the document and wraps it in a cheerio instance.
   * If no root is specified, it's retrieved from the cheerio document itself.
   *
   * @param  {object}
   * @return {cheerio}
   */
	loadRoot: function(root) {
    if (root === undefined)
      root = this.options.document.root();

		return this.options.document(root);
	},

  /**
   * Formats and prepares a path for a query.
   * The method expects either a string in a format
   * inspired by the XPath standard or a function.
   *
   * @param  {(string|function)}  path  A function or string to format.
   * @return {(array|function)}  The formatted path;
   */
	formatPath: function(path) {
		if (isString(path))
			return path.split('/');

    if (isFunction(path))
      return path;

		return [''];
	},

  /**
   * Queries the cheerio document given a path.
   * If the first part of the formatted root is an empty string, the root of the document is returned.
   * If the provided path is a function, it will simply call that function and pass the cheerio
   * document and the intended root of the document.
   *
   * @param  {(array|function)} path  The path to query.
   * @return {*} The result of the query.
   */
	query: function(path) {
    if (path[0] === '')
      return this.options.root;

		if (isFunction(path))
			return path(this.options.document, this.options.root);

    return this.options.document(path[0], this.options.root);
	},

  /**
   * Queries the cheerio document for children given a path.
   * If the result of the query is either not a cheerio result set or the set contains
   * no results, then undefined is returned.
   *
   * @param  {(array|function)} path  The path to query.
   * @return {cheerio}  The children query set.
   */
  queryChildren: function(path) {
    let result = this.query(path);

    if (!(result instanceof cheerio) || result.length == 0)
      return undefined;

    return result.toArray();
  },

  /**
   * Queries the document for a link.
   * Will attempt to grab the href attribute of a cheerio dom element if
   * no other attribute was specified in the path.
   *
   * @param  {(array|function)} path  The path to the link.
   * @return {*}  The retrieved link.
   */
	queryLink: function(path) {
		return this.queryValue(path, 'href');
	},

  /**
   * Queries the document for an element and attempts to resolve an attribute from it.
   *
   * Accepts a fallback array of attributes to return if one is not specified.
   * Each attribute in the array has a higher return priority than the one succeeding it.
   *
   * If no attribute can be resolved from the result of the query, undefined is returned instead.
   *
   * @param  {(array|function)} path          The path used in the query.
   * @param  {string[]}         defaultAttrs  The fallback array of attributes to try for.
   * @return {*}  The retrieved value.
   */
	queryValue: function(path, defaultAttrs=[]) {
    let value = this.query(path);

    if (!(value instanceof cheerio))
      return value;

    if (value.length === 0)
      return undefined;

    let attrs = castArray(path[1] || defaultAttrs);

    if (attrs.length == 0 && value.text() !== '') {
      return value.text();
    }

    for (let attr of attrs) {
      if (value.attr(attr) !== undefined)
        return value.attr(attr);
    }
	}
}<commit_msg>further cleaned up the dom queryValue method<commit_after>const cheerio = require('cheerio');
const { isString, isFunction, castArray } = require('lodash');

/**
 * Methods to handle the manipulation of DOM documents.
 * Uses cheerio under the hood.
 *
 * @type {object}
 */
module.exports = {

  /**
   * Loads the document. If the argument provided is a string,
   * it will be transformed into a cheerio document.
   *
   * @param  {(string|cheerio)} data  The document data.
   * @return {cheerio}  The loaded cheerio document.
   */
	loadDocument: function(data) {
		return cheerio.load(data);
	},

  /**
   * Loads the root of the document and wraps it in a cheerio instance.
   * If no root is specified, it's retrieved from the cheerio document itself.
   *
   * @param  {object}
   * @return {cheerio}
   */
	loadRoot: function(root) {
    if (root === undefined)
      root = this.options.document.root();

		return this.options.document(root);
	},

  /**
   * Formats and prepares a path for a query.
   * The method expects either a string in a format
   * inspired by the XPath standard or a function.
   *
   * @param  {(string|function)}  path  A function or string to format.
   * @return {(array|function)}  The formatted path;
   */
	formatPath: function(path) {
		if (isString(path))
			return path.split('/');

    if (isFunction(path))
      return path;

		return [''];
	},

  /**
   * Queries the cheerio document given a path.
   * If the first part of the formatted root is an empty string, the root of the document is returned.
   * If the provided path is a function, it will simply call that function and pass the cheerio
   * document and the intended root of the document.
   *
   * @param  {(array|function)} path  The path to query.
   * @return {*} The result of the query.
   */
	query: function(path) {
    if (path[0] === '')
      return this.options.root;

		if (isFunction(path))
			return path(this.options.document, this.options.root);

    return this.options.document(path[0], this.options.root);
	},

  /**
   * Queries the cheerio document for children given a path.
   * If the result of the query is either not a cheerio result set or the set contains
   * no results, then undefined is returned.
   *
   * @param  {(array|function)} path  The path to query.
   * @return {cheerio}  The children query set.
   */
  queryChildren: function(path) {
    let result = this.query(path);

    if (!(result instanceof cheerio) || result.length == 0)
      return undefined;

    return result.toArray();
  },

  /**
   * Queries the document for a link.
   * Will attempt to grab the href attribute of a cheerio dom element if
   * no other attribute was specified in the path.
   *
   * @param  {(array|function)} path  The path to the link.
   * @return {*}  The retrieved link.
   */
	queryLink: function(path) {
		return this.queryValue(path, 'href');
	},

  /**
   * Queries the document for an element and attempts to resolve an attribute from it.
   *
   * Accepts a fallback array of attributes to return if one is not specified.
   * Each attribute in the array has a higher return priority than the one succeeding it.
   *
   * If no attribute can be resolved from the result of the query, undefined is returned instead.
   *
   * @param  {(array|function)} path          The path used in the query.
   * @param  {string[]}         defaultAttrs  The fallback array of attributes to try for.
   * @return {*}  The retrieved value.
   */
	queryValue: function(path, defaultAttrs=[]) {
    let value = this.query(path);
    let attrs = castArray(path[1] || defaultAttrs);

    if (!(value instanceof cheerio))
      return value;

    if (value.length === 0)
      return undefined;

    if (attrs.length == 0 && value.text() !== '')
      return value.text();

    for (let attr of attrs) {
      if (value.attr(attr) !== undefined)
        return value.attr(attr);
    }
	}
}<|endoftext|>


<commit_before>
import './redis';
import http from 'http';
import https from 'https';
import Koa from 'koa';
import { readFile } from 'fs-promise';
import { resolve } from 'path';
import useMiddlewares from '../utils/useMiddlewares';
import { init } from '../config';
import { appLogger } from '../utils/logger';
import mount from 'koa-mount';
import { initPlugins } from '../utils/plugins';

process.on('message', async (buf) => {
	const config = init(JSON.parse(buf.toString()));

	await initPlugins(config);

	const {
		port, root,
		ssl: { enable: enableHttps, port: httpsPort, key, cert },
	} = config;

	const app = new Koa();

	app.mount = (...args) => app.use(mount(...args));

	useMiddlewares(app);

	const handleError = (server) => {
		server.on('error', appLogger.error.bind(appLogger));
	};

	const tryReadFile = async (file) => {
		try {
			return await readFile(resolve(root, file));
		}
		catch (err) {
			appLogger.error(`Failed to read file "${file}".`);
			appLogger.debug(err);
		}
	};

	if (enableHttps) {
		const options = {
			key: await tryReadFile(key),
			cert: await tryReadFile(cert),
		};
		handleError(http.createServer(app.callback()).listen(port));
		handleError(https.createServer(options, app.callback()).listen(httpsPort));
	}
	else {
		handleError(app.listen(port));
	}
});
<commit_msg>optimize debug<commit_after>
import './redis';
import http from 'http';
import https from 'https';
import Koa from 'koa';
import { readFile } from 'fs-promise';
import { resolve } from 'path';
import useMiddlewares from '../utils/useMiddlewares';
import { init } from '../config';
import { appLogger } from '../utils/logger';
import mount from 'koa-mount';
import { initPlugins } from '../utils/plugins';

process.on('message', async (buf) => {
	try {
		const config = init(JSON.parse(buf.toString()));

		await initPlugins(config);

		const {
			port, root,
			ssl: { enable: enableHttps, port: httpsPort, key, cert },
		} = config;

		const app = new Koa();

		app.mount = (...args) => app.use(mount(...args));

		useMiddlewares(app);

		const handleError = (server) => {
			server.on('error', appLogger.error.bind(appLogger));
		};

		const tryReadFile = async (file) => {
			try {
				return await readFile(resolve(root, file));
			}
			catch (err) {
				appLogger.error(`Failed to read file "${file}".`);
				appLogger.debug(err);
			}
		};

		if (enableHttps) {
			const options = {
				key: await tryReadFile(key),
				cert: await tryReadFile(cert),
			};
			handleError(
				http.createServer(app.callback()).listen(port)
			);
			handleError(
				https.createServer(options, app.callback()).listen(httpsPort)
			);
		}
		else {
			handleError(app.listen(port));
		}
	}
	catch (err) {
		appLogger.fatal(`Failed to start server:`, err);
	}
});
<|endoftext|>


<commit_before>(function () {
  'use strict';

  var app = angular.module('URLabuseApp', ['ui.bootstrap']);

  app.factory("flash", function($rootScope) {
    var queue = [];
    var currentMessage = "";

    $rootScope.$on("newFlashMessage", function() {
      currentMessage = queue.shift() || "";
    });

    return {
      setMessage: function(message) {
        queue.push(message);
      },
      getMessage: function() {
        return currentMessage;
      }
    };
  });

  app.factory('globFct', [ '$log', '$http', '$timeout', function($log, $http, $timeout){
      return {
          poller: function myself(jobID, callback) {
            var timeout = "";
            // fire another request
            $http.get('_result/' + jobID).
                success(function(data, status, headers, config) {
                    if(status === 202) {
                        $log.log(data, status);
                    } else if (status === 200){
                        $log.log(data);
                        $timeout.cancel(timeout);
                        callback(angular.fromJson(data));
                        return;
                    }
                    // continue to call the poller() function every 2 seconds
                    // until the timout is cancelled
                    timeout = $timeout(function() {myself(jobID, callback);}, 2000);
                });
          },
          query: function(path, data, callback) {
            $http.post(path, data).
                success(callback).
                error(function(error) {
                    $log.log(error);
                });
          }
      };
    }]);

  app.controller('URLabuseController', function($scope, $log, globFct, flash) {

    $scope.poller = globFct.poller;
    $scope.query = globFct.query;
    $scope.flash = flash;

    var get_redirects = function(jobID) {
        $scope.poller(jobID, function(data){
            $log.log(data);
            $scope.urls = data;
        });
    };

    $scope.getResults = function() {
      // get the URL from the input
      $scope.query_url = '';
      $scope.urls = '';
      // Reset the message
      $scope.$emit('newFlashMessage', '');

      var userInput = $scope.input_url;

      var check_validity = function(jobID) {
        $scope.poller(jobID, function(data){
            $scope.query_url = data[1];
            if(data[0] === false){
              $scope.error = data[2];
            } else {
                $scope.query('urls', {"url": data[1]}, get_redirects);
            }
        });
      };

      $scope.query('start', {"url": userInput}, check_validity);
    };

     $scope.submit_email = function() {
        $scope.query('submit', {"url":  $scope.query_url}, function(){
            $scope.query_url = '';
            $scope.urls = '';
            $scope.input_url = '';
            flash.setMessage("Mail sent to CIRCL");
            $scope.$emit('newFlashMessage', '');
        });
    };

  });

  app.directive('uqUrlreport', function(globFct) {

    return {
        scope: {
                  url: '=uqUrlreport',
                  // status: {isFirstOpen: true, isFirstDisabled: false}
          },
        link: function(scope, element, attrs) {
            var get_ips = function(jobID) {
                globFct.poller(jobID, function(data){
                    scope.ipv4 = data[0];
                    scope.ipv6 = data[1];
                });
            };
            globFct.query('resolve', {"url": scope.url}, get_ips);
        },
        templateUrl: 'urlreport',
    };

  });

  app.directive('uqPhishtank', function(globFct) {
      return {
          scope: {
                    query: '=data',
          },
          link: function(scope, element, attrs) {
              var get_response = function(jobID) {
                globFct.poller(jobID, function(data){
                    scope.response = data;
                });
              };
              globFct.query('phishtank', {"query": scope.query}, get_response);
          },
      template: function(elem, attr){
          return '
<alert type="danger">Known phishing website on Phishtank. More details.</alert>
';}
      };
  });

    app.directive('uqVirustotal', function(globFct) {
        return {
            scope: {
                      query: '=data',
            },
            link: function(scope, element, attrs) {
                var get_response = function(jobID) {
                  globFct.poller(jobID, function(data){
                      scope.message = data[0];
                      scope.link = data[1];
                      scope.positives = data[2];
                      scope.total = data[3];
                      if(scope.link && scope.positives === null){
                        scope.alert_val = "info";
                        scope.message = "Scan request successfully queued, report available soon.";
                      } else if (scope.link && scope.positives === 0){
                        scope.message = "None of the " + data[3] + " scanners know this URL as malicious.";
                        scope.alert_val = "success";
                      } else if (scope.link && scope.positives < scope.total/3){
                        scope.message = data[2] + " of the " + data[3] + " scanners know this URL as malicious.";
                        scope.alert_val = "warning";
                      } else if (scope.link && scope.positives >= scope.total/3){
                        scope.message = data[2] + " of the " + data[3] + " scanners know this URL as malicious.";
                        scope.alert_val = "danger";
                      }
                  });
                };
                globFct.query('virustotal_report', {"query": scope.query}, get_response);
            },
            template: function(elem, attr){
                return '
<alert type="{{alert_val}}">{{message}} More details.</alert>
';}
        };
    });

    app.directive('uqGooglesafebrowsing', function(globFct) {
        return {
            scope: {
                      query: '=data',
            },
            link: function(scope, element, attrs) {
                var get_response = function(jobID) {
                  globFct.poller(jobID, function(data){
                      scope.response = data;
                  });
                };
                globFct.query('googlesafebrowsing', {"query": scope.query}, get_response);
            },
        template: function(elem, attr){
            return '
<alert type="danger">Known {{response}} website on Google Safe Browsing. More details.</alert>
';}
        };
    });

    app.directive('uqUrlquery', function(globFct) {
        return {
            scope: {
                      query: '=data',
            },
            link: function(scope, element, attrs) {
                var get_response = function(jobID) {
                  globFct.poller(jobID, function(data){
                      scope.response = data;
                  });
                };
                globFct.query('urlquery', {"query": scope.query}, get_response);
            },
            template: function(elem, attr){
                return '
<alert type="danger">The total alert count on URLquery is {{response}}.</alert>
';}
            };
    });

    app.directive('uqTicket', function(globFct) {
        return {
            scope: {
                      query: '=data',
            },
            link: function(scope, element, attrs) {
                var get_response = function(jobID) {
                  globFct.poller(jobID, function(data){
                      scope.response = data;
                  });
                };
                globFct.query('ticket', {"query": scope.query}, get_response);
            },
        template: '
 0" class="animate-show">Tickets:

{{ticket}}

'
        };
    });

    app.directive('uqWhois', function(globFct) {
        return {
            scope: {
                      query: '=data',
            },
            link: function(scope, element, attrs) {
                var get_response = function(jobID) {
                  globFct.poller(jobID, function(data){
                      scope.response = data.join();
                  });
                };
                globFct.query('whois', {"query": scope.query}, get_response);
            },
        template: '
Contact points from Whois: {{ response }}
'
        };
    });
    app.directive('uqPdnscircl', function(globFct) {
        return {
            scope: {
                      query: '=data',
            },
            link: function(scope, element, attrs) {
                var get_response = function(jobID) {
                  globFct.poller(jobID, function(data){
                      scope.nbentries = data[0];
                      scope.lastentries = data[1];
                  });
                };
                globFct.query('pdnscircl', {"query": scope.query}, get_response);
            },
        template: '
Has {{nbentries}} unique entries in CIRCL Passive DNS. {{lastentries.length}} most recent one(s):

{{domain}}

'
        };
    });
    app.directive('uqPsslcircl', function(globFct) {
        return {
            scope: {
                      query: '=data',
            },
            link: function(scope, element, attrs) {
                var get_response = function(jobID) {
                  globFct.poller(jobID, function(data){
                      scope.entries = data;
                  });
                };
                globFct.query('psslcircl', {"query": scope.query}, get_response);
            },
        template: '
SSL certificates related to this IP:

{{entry}}

'
        };
    });
    app.directive('uqBgpranking', function(globFct) {
        return {
            scope: {
                      query: '=data',
            },
            link: function(scope, element, attrs) {
                var get_response = function(jobID) {
                  globFct.poller(jobID, function(data){
                      scope.ptr = data[0];
                      scope.asndesc = data[1];
                      scope.asn = data[2];
                      scope.position = data[3];
                      scope.total = data[4];
                      scope.value = data[5];
                      if (scope.position < 100){
                          scope.alert_val = "danger";
                      } else if (scope.position < 1000){
                        scope.alert_val = "warning";
                      } else {
                          scope.alert_val = "info";
                      }
                  });
                };
                globFct.query('bgpranking', {"query": scope.query}, get_response);
            },
            template: '
<alert type="{{alert_val}}">Information from BGP Ranking:

PTR Resource Record: {{ptr}}

Announced by: {{asndesc}} ({{asn}})

This ASN is at position {{position}} in the list of {{total}} known ASNs ({{value}}).

</alert>
'
        };
    });
}());
<commit_msg>Display when a host or a domain is NX<commit_after>(function () {
  'use strict';

  var app = angular.module('URLabuseApp', ['ui.bootstrap']);

  app.factory("flash", function($rootScope) {
    var queue = [];
    var currentMessage = "";

    $rootScope.$on("newFlashMessage", function() {
      currentMessage = queue.shift() || "";
    });

    return {
      setMessage: function(message) {
        queue.push(message);
      },
      getMessage: function() {
        return currentMessage;
      }
    };
  });

  app.factory('globFct', [ '$log', '$http', '$timeout', function($log, $http, $timeout){
      return {
          poller: function myself(jobID, callback) {
            var timeout = "";
            // fire another request
            $http.get('_result/' + jobID).
                success(function(data, status, headers, config) {
                    if(status === 202) {
                        $log.log(data, status);
                    } else if (status === 200){
                        $log.log(data);
                        $timeout.cancel(timeout);
                        callback(angular.fromJson(data));
                        return;
                    }
                    // continue to call the poller() function every 2 seconds
                    // until the timout is cancelled
                    timeout = $timeout(function() {myself(jobID, callback);}, 2000);
                });
          },
          query: function(path, data, callback) {
            $http.post(path, data).
                success(callback).
                error(function(error) {
                    $log.log(error);
                });
          }
      };
    }]);

  app.controller('URLabuseController', function($scope, $log, globFct, flash) {

    $scope.poller = globFct.poller;
    $scope.query = globFct.query;
    $scope.flash = flash;

    var get_redirects = function(jobID) {
        $scope.poller(jobID, function(data){
            $log.log(data);
            $scope.urls = data;
        });
    };

    $scope.getResults = function() {
      // get the URL from the input
      $scope.query_url = '';
      $scope.urls = '';
      // Reset the message
      $scope.$emit('newFlashMessage', '');

      var userInput = $scope.input_url;

      var check_validity = function(jobID) {
        $scope.poller(jobID, function(data){
            $scope.query_url = data[1];
            if(data[0] === false){
              $scope.error = data[2];
            } else {
                $scope.query('urls', {"url": data[1]}, get_redirects);
            }
        });
      };

      $scope.query('start', {"url": userInput}, check_validity);
    };

     $scope.submit_email = function() {
        $scope.query('submit', {"url":  $scope.query_url}, function(){
            $scope.query_url = '';
            $scope.urls = '';
            $scope.input_url = '';
            flash.setMessage("Mail sent to CIRCL");
            $scope.$emit('newFlashMessage', '');
        });
    };

  });

  app.directive('uqUrlreport', function(globFct) {

    return {
        scope: {
                  url: '=uqUrlreport',
                  // status: {isFirstOpen: true, isFirstDisabled: false}
          },
        link: function(scope, element, attrs) {
            var get_ips = function(jobID) {
                globFct.poller(jobID, function(data){
                    scope.ipv4 = data[0];
                    scope.ipv6 = data[1];
                    if (scope.ipv4.length === 0){
                        scope.ipv4 = ['Unable to resolve in IPv4'];
                    }
                    if (scope.ipv6.length === 0){
                        scope.ipv6 = ['Unable to resolve in IPv6'];
                    }
                });
            };
            globFct.query('resolve', {"url": scope.url}, get_ips);
        },
        templateUrl: 'urlreport',
    };

  });

  app.directive('uqPhishtank', function(globFct) {
      return {
          scope: {
                    query: '=data',
          },
          link: function(scope, element, attrs) {
              var get_response = function(jobID) {
                globFct.poller(jobID, function(data){
                    scope.response = data;
                });
              };
              globFct.query('phishtank', {"query": scope.query}, get_response);
          },
      template: function(elem, attr){
          return '
<alert type="danger">Known phishing website on Phishtank. More details.</alert>
';}
      };
  });

    app.directive('uqVirustotal', function(globFct) {
        return {
            scope: {
                      query: '=data',
            },
            link: function(scope, element, attrs) {
                var get_response = function(jobID) {
                  globFct.poller(jobID, function(data){
                      scope.message = data[0];
                      scope.link = data[1];
                      scope.positives = data[2];
                      scope.total = data[3];
                      if(scope.link && scope.positives === null){
                        scope.alert_val = "info";
                        scope.message = "Scan request successfully queued, report available soon.";
                      } else if (scope.link && scope.positives === 0){
                        scope.message = "None of the " + data[3] + " scanners know this URL as malicious.";
                        scope.alert_val = "success";
                      } else if (scope.link && scope.positives < scope.total/3){
                        scope.message = data[2] + " of the " + data[3] + " scanners know this URL as malicious.";
                        scope.alert_val = "warning";
                      } else if (scope.link && scope.positives >= scope.total/3){
                        scope.message = data[2] + " of the " + data[3] + " scanners know this URL as malicious.";
                        scope.alert_val = "danger";
                      }
                  });
                };
                globFct.query('virustotal_report', {"query": scope.query}, get_response);
            },
            template: function(elem, attr){
                return '
<alert type="{{alert_val}}">{{message}} More details.</alert>
';}
        };
    });

    app.directive('uqGooglesafebrowsing', function(globFct) {
        return {
            scope: {
                      query: '=data',
            },
            link: function(scope, element, attrs) {
                var get_response = function(jobID) {
                  globFct.poller(jobID, function(data){
                      scope.response = data;
                  });
                };
                globFct.query('googlesafebrowsing', {"query": scope.query}, get_response);
            },
        template: function(elem, attr){
            return '
<alert type="danger">Known {{response}} website on Google Safe Browsing. More details.</alert>
';}
        };
    });

    app.directive('uqUrlquery', function(globFct) {
        return {
            scope: {
                      query: '=data',
            },
            link: function(scope, element, attrs) {
                var get_response = function(jobID) {
                  globFct.poller(jobID, function(data){
                      scope.response = data;
                  });
                };
                globFct.query('urlquery', {"query": scope.query}, get_response);
            },
            template: function(elem, attr){
                return '
<alert type="danger">The total alert count on URLquery is {{response}}.</alert>
';}
            };
    });

    app.directive('uqTicket', function(globFct) {
        return {
            scope: {
                      query: '=data',
            },
            link: function(scope, element, attrs) {
                var get_response = function(jobID) {
                  globFct.poller(jobID, function(data){
                      scope.response = data;
                  });
                };
                globFct.query('ticket', {"query": scope.query}, get_response);
            },
        template: '
 0" class="animate-show">Tickets:

{{ticket}}

'
        };
    });

    app.directive('uqWhois', function(globFct) {
        return {
            scope: {
                      query: '=data',
            },
            link: function(scope, element, attrs) {
                var get_response = function(jobID) {
                  globFct.poller(jobID, function(data){
                      scope.response = data.join();
                  });
                };
                globFct.query('whois', {"query": scope.query}, get_response);
            },
        template: '
Contact points from Whois: {{ response }}
'
        };
    });
    app.directive('uqPdnscircl', function(globFct) {
        return {
            scope: {
                      query: '=data',
            },
            link: function(scope, element, attrs) {
                var get_response = function(jobID) {
                  globFct.poller(jobID, function(data){
                      scope.nbentries = data[0];
                      scope.lastentries = data[1];
                  });
                };
                globFct.query('pdnscircl', {"query": scope.query}, get_response);
            },
        template: '
Has {{nbentries}} unique entries in CIRCL Passive DNS. {{lastentries.length}} most recent one(s):

{{domain}}

'
        };
    });
    app.directive('uqPsslcircl', function(globFct) {
        return {
            scope: {
                      query: '=data',
            },
            link: function(scope, element, attrs) {
                var get_response = function(jobID) {
                  globFct.poller(jobID, function(data){
                      scope.entries = data;
                  });
                };
                globFct.query('psslcircl', {"query": scope.query}, get_response);
            },
        template: '
SSL certificates related to this IP:

{{entry}}

'
        };
    });
    app.directive('uqBgpranking', function(globFct) {
        return {
            scope: {
                      query: '=data',
            },
            link: function(scope, element, attrs) {
                var get_response = function(jobID) {
                  globFct.poller(jobID, function(data){
                      scope.ptr = data[0];
                      scope.asndesc = data[1];
                      scope.asn = data[2];
                      scope.position = data[3];
                      scope.total = data[4];
                      scope.value = data[5];
                      if (scope.position < 100){
                          scope.alert_val = "danger";
                      } else if (scope.position < 1000){
                        scope.alert_val = "warning";
                      } else {
                          scope.alert_val = "info";
                      }
                  });
                };
                globFct.query('bgpranking', {"query": scope.query}, get_response);
            },
            template: '
<alert type="{{alert_val}}">Information from BGP Ranking:

PTR Resource Record: {{ptr}}

Announced by: {{asndesc}} ({{asn}})

This ASN is at position {{position}} in the list of {{total}} known ASNs ({{value}}).

</alert>
'
        };
    });
}());
<|endoftext|>


<commit_before>// VERSION 1.8.9

// This looks for file defaults.js in resource folder, expects:
//	objName - (string) main object name for the add-on, to be added to window element
//	objPathString - (string) add-on path name to use in URIs
//	addonUUID = (string) used to register the add-on's about: uri; must be unique for each add-on! See http://www.famkruithof.net/uuid/uuidgen for generating one
//	prefList - (object) { prefName: defaultValue } - add-on preferences
//	paneList - (iterable) of array with a panes arguments to apply to PrefPanes.setList(); see PrefPanes.jsm
//	addonUris - (object) containing any of the following string properties
//		homepage: add-on homepage
//		support: add-on support page
//		fullchangelog: add-on detailed changelog (usually github commits page)
//		email: developer e-mail
//		profile: developer profile or homepage
//		api: address from where it should fetch the data for the About pane's current development state
//		development: where the follow ongoing add-on development
//	startConditions(aData, aReason) -	(optional) (method) should return false if any requirements the add-on needs aren't met,
//						otherwise return true or call continueStartup(aData, aReason)
//	onStartup/onShutdown/onInstall/onUninstall(aData, aReason) - (optional) (methods) to be called appropriately as their name suggest
// handleDeadObject(ex) - 	expects [nsIScriptError object] ex. Shows dead object notices as warnings only in the console.
//				If the code can handle them accordingly and firefox does its thing, they shouldn't cause any problems.
// prepareObject(window, aName) - initializes a window-dependent add-on object with utils loaded into it, returns the newly created object
//	window - (xul object) the window object to be initialized
//	(optional) aName - (string) the object name, defaults to objName
// removeObject(window, aName) - closes and removes the object initialized by prepareObject()
//	see prepareObject()
// preparePreferences(window, aName) - loads the preferencesUtils module into that window's object initialized by prepareObject() (if it hasn't yet, it will be initialized)
//	see prepareObject()
// listenOnce(aSubject, type, handler, capture) - adds handler to window listening to event type that will be removed after one execution.
//	aSubject - (xul object) to add the handler to
//	type - (string) event type to listen to
//	handler - (function(event, aSubject)) - method to be called when event is triggered
//	(optional) capture - (bool) capture mode
// callOnLoad(aSubject, aCallback, beforeComplete) - calls aCallback immediately if aWindow is already loaded, otherwise waits for the load event to be fired on that window.
//	aSubject - (xul object) to execute aCallback on
//	aCallback - (function(aSubject)) to be called on aSubject
//	(optional) beforeComplete - (bool) if true, aCallback will be called on aSubject immediately, regardless of its readyState value; defaults to false.
// disable() - disables the add-on, in general the add-on disabling itself is a bad idea so I shouldn't use it

var UNLOADED = false;
var STARTED = false;
var Addon = {};
var AddonData = null;
var onceListeners = [];
var alwaysRunOnShutdown = [];
var MessengerLoaded = false;
var isChrome = true;
var isContent = false;

// Globals - lets me use objects that I can share through all the windows
var Globals = {};

// actual add-on data, to be overriden by defaults.js
var objName = null;
var objPathString = null;
var prefList = null;
var paneList = null;
var addonUUID = null;

// add-on relevant links, to be overriden by defaults.js
var addonUris = {
	homepage: '',
	support: '',
	fullchangelog: '',
	email: '',
	profile: '',
	api: '',
	development: ''
};

var {classes: Cc, interfaces: Ci, utils: Cu, manager: Cm, results: Cr} = Components;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "AddonManager", "resource://gre/modules/AddonManager.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils", "resource://gre/modules/PlacesUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PluralForm", "resource://gre/modules/PluralForm.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils", "resource://gre/modules/PrivateBrowsingUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Promise", "resource://gre/modules/Promise.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Task", "resource://gre/modules/Task.jsm");

// easy and useful helpers for when I'm debugging
XPCOMUtils.defineLazyModuleGetter(this, "console", "resource://gre/modules/Console.jsm");
function LOG(str) {
	if(!str) { str = typeof(str)+': '+str; }
	console.log(objName+' :: CHROME :: '+str);
}
function STEPLOGGER(name) {
	this.name = name;
	this.steps = [];
	this.initTime = new Date().getTime();
	this.lastTime = this.initTime;
}
STEPLOGGER.prototype = {
	step: function(name) {
		let time = new Date().getTime();
		this.steps.push({ name, time: time - this.lastTime});
		this.lastTime = time;
	},
	end: function() {
		this.step('end');
		let endTime = new Date().getTime();
		let report = { name: this.name, total: endTime - this.initTime };
		for(let x of this.steps) {
			report[x.name] = x.time;
		}
		console.log(report);
	}
};

XPCOMUtils.defineLazyServiceGetter(Services, "xulStore", "@mozilla.org/xul/xulstore;1", "nsIXULStore");
XPCOMUtils.defineLazyServiceGetter(Services, "navigator", "@mozilla.org/network/protocol;1?name=http", "nsIHttpProtocolHandler");
XPCOMUtils.defineLazyServiceGetter(Services, "stylesheet", "@mozilla.org/content/style-sheet-service;1", "nsIStyleSheetService");

// I check these pretty much everywhere, so might as well keep a single reference to them
var WINNT = Services.appinfo.OS == 'WINNT';
var DARWIN = Services.appinfo.OS == 'Darwin';
var LINUX = !WINNT && !DARWIN;

function handleDeadObject(ex) {
	if(ex.message == "can't access dead object") {
		let scriptError = Cc["@mozilla.org/scripterror;1"].createInstance(Ci.nsIScriptError);
		scriptError.init("Can't access dead object. This shouldn't cause any problems.", ex.sourceName || ex.fileName || null, ex.sourceLine || null, ex.lineNumber || null, ex.columnNumber || null, scriptError.warningFlag, 'XPConnect JavaScript');
		Services.console.logMessage(scriptError);
		return true;
	} else {
		Cu.reportError(ex);
		return false;
	}
}

function prepareObject(window, aName) {
	// I can override the object name if I want
	let objectName = aName || objName;
	if(window[objectName]) { return; }

	var rtl = getComputedStyle(window.document.documentElement).direction == 'rtl';

	window[objectName] = {
		objName: objectName,
		objPathString: objPathString,
		_UUID: new Date().getTime(),
		RTL: rtl,
		LTR: !rtl,

		// every supposedly global variable is inaccessible because bootstraped means sandboxed, so I have to reference all these;
		// it's easier to reference more specific objects from within the modules for better control, only setting these two here because they're more generalized
		window: window,
		get document () { return window.document; },
		$: function(id) { return window.document.getElementById(id); },
		$$: function(sel, parent = window.document) { return parent.querySelectorAll(sel); },
		$ª: function(parent, anonid, anonattr = 'anonid') { return window.document.getAnonymousElementByAttribute(parent, anonattr, anonid); }
	};

	Services.scriptloader.loadSubScript("resource://"+objPathString+"/modules/utils/Modules.jsm", window[objectName]);
	Services.scriptloader.loadSubScript("resource://"+objPathString+"/modules/utils/windowUtilsPreload.jsm", window[objectName]);
	window[objectName].Modules.load("utils/windowUtils");

	setAttribute(window.document.documentElement, objectName+'_UUID', window[objectName]._UUID);
	setAttribute(window.document.documentElement, objectName+'_Version', AddonData.version);
}

function removeObject(window, aName) {
	let objectName = aName || objName;

	if(window[objectName]) {
		removeAttribute(window.document.documentElement, objectName+'_UUID');
		removeAttribute(window.document.documentElement, objectName+'_Version');
		window[objectName].Modules.unload("utils/windowUtils");
		delete window[objectName];
	}
}

function preparePreferences(window, aName) {
	let objectName = aName || objName;

	if(!window[objectName]) {
		prepareObject(window, objectName);
	}
	window[objectName].Modules.load("utils/preferencesUtils");
}

function removeOnceListener(oncer) {
	for(var i=0; i<onceListeners.length; i++) {
		if(!oncer) {
			onceListeners[i]();
			continue;
		}

		if(onceListeners[i] == oncer) {
			onceListeners.splice(i, 1);
			return;
		}
	}

	if(!oncer) {
		onceListeners = [];
	}
}

function listenOnce(aSubject, type, handler, capture) {
	if(UNLOADED || !aSubject || !aSubject.addEventListener) { return; }

	var runOnce = function(event) {
		try { aSubject.removeEventListener(type, runOnce, capture); }
		catch(ex) { handleDeadObject(ex); } // Prevents some can't access dead object errors
		if(!UNLOADED && event !== undefined) {
			removeOnceListener(runOnce);
			try { handler(event, aSubject); }
			catch(ex) { Cu.reportError(ex); }
		}
	};

	aSubject.addEventListener(type, runOnce, capture);
	onceListeners.push(runOnce);
}

function callOnLoad(aSubject, aCallback, beforeComplete) {
	if(aSubject.document.readyState == 'complete' || beforeComplete) {
		try { aCallback(aSubject); }
		catch(ex) { Cu.reportError(ex); }
		return;
	}

	// don't wait for the load event if we're terminating
	if(UNLOADED) { return; }

	listenOnce(aSubject, "load", function() {
		if(UNLOADED) { return; }

		try { aCallback(aSubject); }
		catch(ex) { Cu.reportError(ex); }
	}, false);
}

function disable() {
	AddonManager.getAddonByID(AddonData.id, function(addon) {
		addon.userDisabled = true;
	});
}

function continueStartup(aReason) {
	STARTED = aReason;

	// append actual preferences panes into the preferences tab
	if(paneList) {
		PrefPanes.setList(paneList);
	}

	if(typeof(onStartup) == 'function') {
		onStartup(AddonData, aReason);
	}
}

function startup(aData, aReason) {
	UNLOADED = false;
	AddonData = aData;

	// to make sure we get always the most recent files when updating the add-on, see:
	// https://bugzilla.mozilla.org/show_bug.cgi?id=918033
	// https://bugzilla.mozilla.org/show_bug.cgi?id=1051238
	AddonData.initTime = new Date().getTime();

	// This includes the optionsURL property
	AddonManager.getAddonByID(AddonData.id, function(addon) {
		if(typeof(UNLOADED) == 'undefined' || UNLOADED) { return; }
		Addon = addon;
	});

	// Set the default strings for the add-on
	let alias = Services.io.newFileURI(AddonData.installPath);
	let defaultsURI = ((AddonData.installPath.isDirectory()) ? alias.spec : 'jar:' + alias.spec + '!/')+'resource/defaults.js';
	Services.scriptloader.loadSubScript(defaultsURI, this);

	// Get the utils.jsm module into our sandbox
	Services.scriptloader.loadSubScript("resource://"+objPathString+"/modules/utils/Modules.jsm", this);
	Services.scriptloader.loadSubScript("resource://"+objPathString+"/modules/utils/sandboxUtilsPreload.jsm", this);
	Modules.load("utils/sandboxUtils");

	if(typeof(startConditions) != 'function' || startConditions(aReason)) {
		if(aReason == APP_STARTUP) {
			continueStartup(aReason);
		}
		// In non-e10s, loadFrameScript from this startup can run load content script even before the previous sandboxed content module had a chance to shutdown.
		// Case: enable add-on (instance A). Disable add-on. Re-enable add-on (instance B).
		// This has to happen in immediate succession, i.e. when updating add-on, but not only.
		// Sandbox content B tries to load before sandbox A received the shutdown message, so sandbox B never loads (because A is still loaded).
		// Then A receives the shutdown message, it shuts down, and we are left with nothing initialized in the child process.
		// Here's hoping that a delay of 500ms covers the vast majority of cases, edge-cases (ultra-busy CPU during add-on update?) may still suffer from this.
		// This doesn't seem to happen in e10s, but I'm leaving the delay there as well because why not. Maybe when e10s is norm this can be removed.
		else {
			aSync(function() {
				continueStartup(aReason);
			}, 500);
		}
	}
}

function shutdown(aData, aReason) {
	UNLOADED = aReason;

	if(aReason == APP_SHUTDOWN) {
		// List of methods that must always be run on shutdown, such as restoring some native prefs
		while(alwaysRunOnShutdown.length > 0) {
			alwaysRunOnShutdown.pop()();
		}

		removeOnceListener();
		return;
	}

	if(STARTED) {
		// content scripts should know as soon as possible that we're disabling the add-on, before any of them starts unloading,
		// otherwise they could try to load modules after the resource:// uri was gone
		if(MessengerLoaded) {
			Services.ppmm.broadcastAsyncMessage(Messenger.messageName('disable'));
		}

		if(typeof(onShutdown) == 'function') {
			onShutdown(aData, aReason);
		}
	}

	Modules.unload("utils/sandboxUtils");
	removeOnceListener();
}

function install(aData, aReason) {
	if(typeof(onInstall) == 'function') {
		onInstall(aData, aReason);
	}
}

function uninstall(aData, aReason) {
	if(typeof(onUninstall) == 'function') {
		onUninstall(aData, aReason);
	}
}
<commit_msg>fix memory leak on chrome tabs (by the8472)<commit_after>// VERSION 1.8.10

// This looks for file defaults.js in resource folder, expects:
//	objName - (string) main object name for the add-on, to be added to window element
//	objPathString - (string) add-on path name to use in URIs
//	addonUUID = (string) used to register the add-on's about: uri; must be unique for each add-on! See http://www.famkruithof.net/uuid/uuidgen for generating one
//	prefList - (object) { prefName: defaultValue } - add-on preferences
//	paneList - (iterable) of array with a panes arguments to apply to PrefPanes.setList(); see PrefPanes.jsm
//	addonUris - (object) containing any of the following string properties
//		homepage: add-on homepage
//		support: add-on support page
//		fullchangelog: add-on detailed changelog (usually github commits page)
//		email: developer e-mail
//		profile: developer profile or homepage
//		api: address from where it should fetch the data for the About pane's current development state
//		development: where the follow ongoing add-on development
//	startConditions(aData, aReason) -	(optional) (method) should return false if any requirements the add-on needs aren't met,
//						otherwise return true or call continueStartup(aData, aReason)
//	onStartup/onShutdown/onInstall/onUninstall(aData, aReason) - (optional) (methods) to be called appropriately as their name suggest
// handleDeadObject(ex) - 	expects [nsIScriptError object] ex. Shows dead object notices as warnings only in the console.
//				If the code can handle them accordingly and firefox does its thing, they shouldn't cause any problems.
// prepareObject(window, aName) - initializes a window-dependent add-on object with utils loaded into it, returns the newly created object
//	window - (xul object) the window object to be initialized
//	(optional) aName - (string) the object name, defaults to objName
// removeObject(window, aName) - closes and removes the object initialized by prepareObject()
//	see prepareObject()
// preparePreferences(window, aName) - loads the preferencesUtils module into that window's object initialized by prepareObject() (if it hasn't yet, it will be initialized)
//	see prepareObject()
// listenOnce(aSubject, type, handler, capture) - adds handler to window listening to event type that will be removed after one execution.
//	aSubject - (xul object) to add the handler to
//	type - (string) event type to listen to
//	handler - (function(event, aSubject)) - method to be called when event is triggered
//	(optional) capture - (bool) capture mode
// callOnLoad(aSubject, aCallback, beforeComplete) - calls aCallback immediately if aWindow is already loaded, otherwise waits for the load event to be fired on that window.
//	aSubject - (xul object) to execute aCallback on
//	aCallback - (function(aSubject)) to be called on aSubject
//	(optional) beforeComplete - (bool) if true, aCallback will be called on aSubject immediately, regardless of its readyState value; defaults to false.
// disable() - disables the add-on, in general the add-on disabling itself is a bad idea so I shouldn't use it

var UNLOADED = false;
var STARTED = false;
var Addon = {};
var AddonData = null;
var onceListeners = [];
var alwaysRunOnShutdown = [];
var MessengerLoaded = false;
var isChrome = true;
var isContent = false;

// Globals - lets me use objects that I can share through all the windows
var Globals = {};

// actual add-on data, to be overriden by defaults.js
var objName = null;
var objPathString = null;
var prefList = null;
var paneList = null;
var addonUUID = null;

// add-on relevant links, to be overriden by defaults.js
var addonUris = {
	homepage: '',
	support: '',
	fullchangelog: '',
	email: '',
	profile: '',
	api: '',
	development: ''
};

var {classes: Cc, interfaces: Ci, utils: Cu, manager: Cm, results: Cr} = Components;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "AddonManager", "resource://gre/modules/AddonManager.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils", "resource://gre/modules/PlacesUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PluralForm", "resource://gre/modules/PluralForm.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils", "resource://gre/modules/PrivateBrowsingUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Promise", "resource://gre/modules/Promise.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Task", "resource://gre/modules/Task.jsm");

// easy and useful helpers for when I'm debugging
XPCOMUtils.defineLazyModuleGetter(this, "console", "resource://gre/modules/Console.jsm");
function LOG(str) {
	if(!str) { str = typeof(str)+': '+str; }
	console.log(objName+' :: CHROME :: '+str);
}
function STEPLOGGER(name) {
	this.name = name;
	this.steps = [];
	this.initTime = new Date().getTime();
	this.lastTime = this.initTime;
}
STEPLOGGER.prototype = {
	step: function(name) {
		let time = new Date().getTime();
		this.steps.push({ name, time: time - this.lastTime});
		this.lastTime = time;
	},
	end: function() {
		this.step('end');
		let endTime = new Date().getTime();
		let report = { name: this.name, total: endTime - this.initTime };
		for(let x of this.steps) {
			report[x.name] = x.time;
		}
		console.log(report);
	}
};

XPCOMUtils.defineLazyServiceGetter(Services, "xulStore", "@mozilla.org/xul/xulstore;1", "nsIXULStore");
XPCOMUtils.defineLazyServiceGetter(Services, "navigator", "@mozilla.org/network/protocol;1?name=http", "nsIHttpProtocolHandler");
XPCOMUtils.defineLazyServiceGetter(Services, "stylesheet", "@mozilla.org/content/style-sheet-service;1", "nsIStyleSheetService");

// I check these pretty much everywhere, so might as well keep a single reference to them
var WINNT = Services.appinfo.OS == 'WINNT';
var DARWIN = Services.appinfo.OS == 'Darwin';
var LINUX = !WINNT && !DARWIN;

function handleDeadObject(ex) {
	if(ex.message == "can't access dead object") {
		let scriptError = Cc["@mozilla.org/scripterror;1"].createInstance(Ci.nsIScriptError);
		scriptError.init("Can't access dead object. This shouldn't cause any problems.", ex.sourceName || ex.fileName || null, ex.sourceLine || null, ex.lineNumber || null, ex.columnNumber || null, scriptError.warningFlag, 'XPConnect JavaScript');
		Services.console.logMessage(scriptError);
		return true;
	} else {
		Cu.reportError(ex);
		return false;
	}
}

function prepareObject(window, aName) {
	// I can override the object name if I want
	let objectName = aName || objName;
	if(window[objectName]) { return; }

	var rtl = getComputedStyle(window.document.documentElement).direction == 'rtl';

	window[objectName] = {
		objName: objectName,
		objPathString: objPathString,
		_UUID: new Date().getTime(),
		RTL: rtl,
		LTR: !rtl,

		// every supposedly global variable is inaccessible because bootstraped means sandboxed, so I have to reference all these;
		// it's easier to reference more specific objects from within the modules for better control, only setting these two here because they're more generalized
		window: window,
		get document () { return window.document; },
		$: function(id) { return window.document.getElementById(id); },
		$$: function(sel, parent = window.document) { return parent.querySelectorAll(sel); },
		$ª: function(parent, anonid, anonattr = 'anonid') { return window.document.getAnonymousElementByAttribute(parent, anonattr, anonid); }
	};

	Services.scriptloader.loadSubScript("resource://"+objPathString+"/modules/utils/Modules.jsm", window[objectName]);
	Services.scriptloader.loadSubScript("resource://"+objPathString+"/modules/utils/windowUtilsPreload.jsm", window[objectName]);
	window[objectName].Modules.load("utils/windowUtils");

	setAttribute(window.document.documentElement, objectName+'_UUID', window[objectName]._UUID);
	setAttribute(window.document.documentElement, objectName+'_Version', AddonData.version);
}

function removeObject(window, aName) {
	let objectName = aName || objName;

	if(window[objectName]) {
		removeAttribute(window.document.documentElement, objectName+'_UUID');
		removeAttribute(window.document.documentElement, objectName+'_Version');
		window[objectName].Modules.unload("utils/windowUtils");
		delete window[objectName];
	}
}

function preparePreferences(window, aName) {
	let objectName = aName || objName;

	if(!window[objectName]) {
		prepareObject(window, objectName);
	}
	window[objectName].Modules.load("utils/preferencesUtils");
}

function removeOnceListener(oncer) {
	for(var i=0; i<onceListeners.length; i++) {
		let unwrapped = onceListeners[i].get();

		// clean up dead weak ref
		if(!unwrapped) {
			onceListeners.splice(i, 1);
			i--;
			continue;
		}

		if(!oncer) {
			unwrapped();
			continue;
		}

		if(unwrapped == oncer) {
			onceListeners.splice(i, 1);
			return;
		}
	}

	if(!oncer) {
		onceListeners = [];
	}
}

function listenOnce(aSubject, type, handler, capture) {
	if(UNLOADED || !aSubject || !aSubject.addEventListener) { return; }

	var runOnce = function(event) {
		try { aSubject.removeEventListener(type, runOnce, capture); }
		catch(ex) { handleDeadObject(ex); } // Prevents some can't access dead object errors
		if(!UNLOADED && event !== undefined) {
			removeOnceListener(runOnce);
			try { handler(event, aSubject); }
			catch(ex) { Cu.reportError(ex); }
		}
	};

	aSubject.addEventListener(type, runOnce, capture);

	let weak = Cu.getWeakReference(runOnce);
	onceListeners.push(weak);
}

function callOnLoad(aSubject, aCallback, beforeComplete) {
	if(aSubject.document.readyState == 'complete' || beforeComplete) {
		try { aCallback(aSubject); }
		catch(ex) { Cu.reportError(ex); }
		return;
	}

	// don't wait for the load event if we're terminating
	if(UNLOADED) { return; }

	listenOnce(aSubject, "load", function() {
		if(UNLOADED) { return; }

		try { aCallback(aSubject); }
		catch(ex) { Cu.reportError(ex); }
	}, false);
}

function disable() {
	AddonManager.getAddonByID(AddonData.id, function(addon) {
		addon.userDisabled = true;
	});
}

function continueStartup(aReason) {
	STARTED = aReason;

	// append actual preferences panes into the preferences tab
	if(paneList) {
		PrefPanes.setList(paneList);
	}

	if(typeof(onStartup) == 'function') {
		onStartup(AddonData, aReason);
	}
}

function startup(aData, aReason) {
	UNLOADED = false;
	AddonData = aData;

	// to make sure we get always the most recent files when updating the add-on, see:
	// https://bugzilla.mozilla.org/show_bug.cgi?id=918033
	// https://bugzilla.mozilla.org/show_bug.cgi?id=1051238
	AddonData.initTime = new Date().getTime();

	// This includes the optionsURL property
	AddonManager.getAddonByID(AddonData.id, function(addon) {
		if(typeof(UNLOADED) == 'undefined' || UNLOADED) { return; }
		Addon = addon;
	});

	// Set the default strings for the add-on
	let alias = Services.io.newFileURI(AddonData.installPath);
	let defaultsURI = ((AddonData.installPath.isDirectory()) ? alias.spec : 'jar:' + alias.spec + '!/')+'resource/defaults.js';
	Services.scriptloader.loadSubScript(defaultsURI, this);

	// Get the utils.jsm module into our sandbox
	Services.scriptloader.loadSubScript("resource://"+objPathString+"/modules/utils/Modules.jsm", this);
	Services.scriptloader.loadSubScript("resource://"+objPathString+"/modules/utils/sandboxUtilsPreload.jsm", this);
	Modules.load("utils/sandboxUtils");

	if(typeof(startConditions) != 'function' || startConditions(aReason)) {
		if(aReason == APP_STARTUP) {
			continueStartup(aReason);
		}
		// In non-e10s, loadFrameScript from this startup can run load content script even before the previous sandboxed content module had a chance to shutdown.
		// Case: enable add-on (instance A). Disable add-on. Re-enable add-on (instance B).
		// This has to happen in immediate succession, i.e. when updating add-on, but not only.
		// Sandbox content B tries to load before sandbox A received the shutdown message, so sandbox B never loads (because A is still loaded).
		// Then A receives the shutdown message, it shuts down, and we are left with nothing initialized in the child process.
		// Here's hoping that a delay of 500ms covers the vast majority of cases, edge-cases (ultra-busy CPU during add-on update?) may still suffer from this.
		// This doesn't seem to happen in e10s, but I'm leaving the delay there as well because why not. Maybe when e10s is norm this can be removed.
		else {
			aSync(function() {
				continueStartup(aReason);
			}, 500);
		}
	}
}

function shutdown(aData, aReason) {
	UNLOADED = aReason;

	if(aReason == APP_SHUTDOWN) {
		// List of methods that must always be run on shutdown, such as restoring some native prefs
		while(alwaysRunOnShutdown.length > 0) {
			alwaysRunOnShutdown.pop()();
		}

		removeOnceListener();
		return;
	}

	if(STARTED) {
		// content scripts should know as soon as possible that we're disabling the add-on, before any of them starts unloading,
		// otherwise they could try to load modules after the resource:// uri was gone
		if(MessengerLoaded) {
			Services.ppmm.broadcastAsyncMessage(Messenger.messageName('disable'));
		}

		if(typeof(onShutdown) == 'function') {
			onShutdown(aData, aReason);
		}
	}

	Modules.unload("utils/sandboxUtils");
	removeOnceListener();
}

function install(aData, aReason) {
	if(typeof(onInstall) == 'function') {
		onInstall(aData, aReason);
	}
}

function uninstall(aData, aReason) {
	if(typeof(onUninstall) == 'function') {
		onUninstall(aData, aReason);
	}
}
<|endoftext|>