<commit_before>const log = require('loglevel');
const path = require('path');
const fs = require('fs');
const chalk = require('chalk');
const process = require('process');
const addSkipped = require('./rules-util.js').addSkipped;
const processRules = require('./rules-util.js').processRules;
const semver = require('semver');

const rulesConf = {
    name: 'package.json',
    rules: [
        {
            desc: 'using public npm version of gfp eslint config',
            errorMsg:
                'remove "gfp-eslint-config" from package.json and install "eslint eslint-config-gfp" instead, or just run:',
            run:
                'npm uninstall --save-dev gfp-eslint-config && npm install --save-dev eslint eslint-config-gfp',
            fn: notUsingStashGfpEslintConfig
        },
        {
            desc: 'using eslint-config-gfp',
            errorMsg: 'Run:',
            run: 'npm install --save-dev eslint-config-gfp',
            fn: missingEslintConfigGfp
        },
        {
            desc: 'using public npm version of angular-module-no-deps',
            errorMsg:
                'remove "angular-module-no-deps" from package.json and install "angular-module-no-deps" instead, or just run:',
            run:
                'npm uninstall --save-dev angular-module-no-deps && npm install --save-dev angular-module-no-deps',
            fn: notUsingStashAngularModuleNoDeps
        },
        {
            desc: 'using documentationjs',
            errorMsg:
                'should be using documentationjs to generate JSDoc to README:\n' +
                'add scripts: \n' +
                '"docs": "npm run docs:md",\n' +
                '"docs:md": "documentation readme <source files>.js --section \'API\'"\n' +
                '"and run:',
            run: 'npm install --save-dev documentation',
            fn: usingDocumentationJs
        },
        {
            desc: 'has npm script lint',
            errorMsg:
                'should have a npm script lint\n' +
                'then add script to package.json: \n' +
                '"lint": "eslint *.js"',
            fn: hasNpmLintScript
        },
        {
            desc: 'should be using babel-cli',
            errorMsg: 'Run:',
            run: 'npm install --save-dev babel-cli@6.22.2',
            fn: shouldUseBabelCli
        },
        {
            desc: 'should be using babel-core',
            errorMsg: 'Run:',
            run: 'npm install --save-dev babel-core@6.22.1',
            fn: shouldUseBabelCore
        },
        {
            desc: 'should be using babel-loader',
            errorMsg: 'Run:',
            run: 'npm install --save-dev babel-loader@6.2.10',
            fn: shouldUseBabelLoader
        },
        {
            desc: 'should be using babel-plugin-istanbul',
            errorMsg: 'Run:',
            run: 'npm install --save-dev babel-plugin-istanbul@4.1.5',
            fn: shouldUseBabelInstanbul
        },
        {
            desc: 'should be using babel-plugin-transform-runtime',
            errorMsg: 'Run:',
            run:
                'npm install --save-dev babel-plugin-transform-runtime@.6.22.0',
            fn: shouldUseBabelTransformRuntime
        },
        {
            desc: 'should be using babel-preset-env',
            errorMsg: 'Run:',
            run: 'npm install --save-dev babel-preset-env@1.6.1',
            fn: shouldUseBabelPresetEnv
        },
        {
            desc: 'should be using babel-preprocessor',
            errorMsg: 'Run:',
            run: 'npm install --save-dev karma-babel-preprocessor@7.0.0',
            fn: shouldUseBabelPreprocessor
        },
        {
            desc: 'should be using puppeteer',
            errorMsg:
                'remove "phantomjs" from package.json and install "puppeteer" instead, or just run:',
            run:
                'npm uninstall --save-dev phantomjs && npm install --save-dev puppeteer',
            fn: shouldUsePuppeteer
        },
        {
            desc: 'should be using headless Chrome',
            errorMsg:
                'remove "karma-phantomjs-launcher" from package.json and install karma-chrome-launcher instead, or just run:',
            run:
                'npm uninstall --save-dev karma-phantomjs-launcher && npm install --save-dev karma-chrome-launcher@2.2.0',
            fn: shouldUseChromeLauncher
        },
        {
            desc: 'should use prepush hooks',
            errorMsg:
                'Add the following to npm scripts:\n' +
                '"prepush": "npm run lint && karma start --single-run"\n' +
                'Run:',
            run: 'npm install --save-dev husky',
            fn: shouldUsePrepushHooks
        },
        {
            desc: 'should use public require-polyfill',
            errorMsg:
                'remove "require-polyfill" from package.json and install public version, or just run:',
            run:
                'npm uninstall --save-dev require-polyfill && npm install --save-dev require-polyfill',
            fn: shouldUsePublicRequirePolyfill
        },
        {
            desc: 'should use eslint-config-gfp 4.0.0 and up',
            errorMsg:
                'set eslint-config-gfp to use version "^4.0.0" in package.json or run:',
            run: 'npm install --save-dev eslint-config-gfp@4.0.0',
            fn: shouldUseEslintConfigGfp300
        },
        {
            desc: 'should have gfp-doctor > 1.12 installed',
            errorMsg: 'Run:',
            run: 'npm install --save-dev gfp-doctor',
            fn: shouldUseGfpDoctor
        },
        {
            desc: 'should run gfp-doctor after install',
            errorMsg:
                'add the following to npm scripts:\n' +
                '"postinstall": "gfp-doctor"',
            fn: shouldRunGfpDoctorPostInstall
        },
        {
            desc: 'should use "karma" 1.7.0 and up',
            errorMsg: 'set "karma" to use "^1.7.0" or run',
            run: 'npm install --save-dev karma@1.7.0',
            fn: shouldUseKarma170
        },
        {
            desc: 'should use "karma-coverage" 1.1.0 and up',
            errorMsg: 'set "karma-coverage" to use "^1.1.0" or run:',
            run: 'npm install --save-dev karma-coverage@1.1.0',
            fn: shouldUseKarmaCoverage110
        },
        {
            desc: 'should use "karma-jasmine" 1.1.0 and up',
            errorMsg: 'set "karma-jasmine" to use "^1.1.0" or run:',
            run: 'npm install --save-dev karma-jasmine@1.1.0',
            fn: shouldUseKarmaJasmine110
        },
        {
            desc: 'should use gff-jira-message and run it on commitmsg',
            errorMsg: 'install gff-jira-message and add it to scripts commitmsg',
            run: 'npm i gff-jira-message --save-dev',
            fn: shouldUseGffJiraMessage
        }
    ]
};

module.exports = function(options) {
    'use strict';
    const packagePath = path.join(
        process.cwd(),
        options.packagePath || 'package.json'
    );
    log.debug(chalk.underline('Examining package.json'), options);

    log.debug(`using ${packagePath}`, options);

    if (!fs.existsSync(packagePath)) {
        log.warn(
            chalk.red('ABORTING: No package.json found in ' + process.cwd())
        );
        return addSkipped(rulesConf);
    }

    const data = fs.readFileSync(packagePath);

    const json = JSON.parse(data);

    // work through all rules, setting passed = true/false
    rulesConf.rules = processRules(rulesConf, json, options);
    return rulesConf;
};

// add more test functions below, should return true if passing the test

function notUsingStashGfpEslintConfig(json) {
    if (json.devDependencies && !json.devDependencies['gfp-eslint-config']) {
        return true;
    }
}

function missingEslintConfigGfp(json) {
    if (json.devDependencies && json.devDependencies['eslint-config-gfp']) {
        return true;
    }
}

function notUsingStashAngularModuleNoDeps(json) {
    // bail out if angular-module-no-deps isn't defined at all
    if (
        json.devDependencies &&
        !json.devDependencies['angular-module-no-deps']
    ) {
        return true;
    }

    if (
        json.devDependencies['angular-module-no-deps'].indexOf('.mrgreen.') !==
        -1
    ) {
        return false;
    }
    return true;
}

function usingDocumentationJs(json) {
    if (json.devDependencies && json.devDependencies['documentation']) {
        return true;
    }
}

function hasNpmLintScript(json) {
    if (json.scripts && json.scripts.lint) {
        return true;
    }
}

function shouldUseBabelCli(json) {
    if (json.devDependencies['babel-cli']) {
        return true;
    }
    return false;
}

function shouldUseBabelCore(json) {
    if (json.devDependencies['babel-core']) {
        return true;
    }
    return false;
}

function shouldUseBabelLoader(json) {
    if (json.devDependencies['babel-loader']) {
        return true;
    }
    return false;
}

function shouldUseBabelInstanbul(json) {
    if (json.devDependencies['babel-plugin-istanbul']) {
        return true;
    }
    return false;
}

function shouldUseBabelTransformRuntime(json) {
    if (json.devDependencies['babel-plugin-transform-runtime']) {
        return true;
    }
    return false;
}

function shouldUseBabelPresetEnv(json) {
    if (json.devDependencies['babel-preset-env']) {
        return true;
    }
    return false;
}

function shouldUseBabelPreprocessor(json) {
    if (json.devDependencies['karma-babel-preprocessor']) {
        return true;
    }
    return false;
}

function shouldUsePuppeteer(json) {
    if (json.devDependencies) {
        if (
            json.devDependencies['phantomjs'] ||
            json.devDependencies['phantomjs-prebuilt']
        ) {
            return false;
        }
        if (json.devDependencies['puppeteer']) {
            return true;
        }
    }
}

function shouldUseChromeLauncher(json) {
    if (json.devDependencies) {
        if (json.devDependencies['karma-phantomjs-launcher']) {
            return false;
        }
        if (json.devDependencies['karma-chrome-launcher']) {
            return true;
        }
    }
}

function shouldUsePrepushHooks(json) {
    if (json.devDependencies && json.devDependencies.husky) {
        if (json.scripts && json.scripts.prepush) {
            return true;
        }
    }
}

function shouldUsePublicRequirePolyfill(json) {
    if (json.devDependencies && json.devDependencies['require-polyfill']) {
        if (json.devDependencies['require-polyfill'].includes('ssh://')) {
            return false;
        }
    }
    return true;
}

function shouldUseEslintConfigGfp300(json) {
    if (json.devDependencies) {
        if (json.devDependencies['eslint-config-gfp']) {
            if (json.devDependencies['eslint-config-gfp'] === 'latest') {
                return false;
            }
            // tries to see if 2.9.9 is less than the current range specified in package.json
            return semver.ltr(
                '3.9.9',
                json.devDependencies['eslint-config-gfp']
            );
        }
    }
}

function shouldUseGfpDoctor(json) {
    if (json.devDependencies && json.devDependencies['gfp-doctor']) {
        return (
            json.devDependencies['gfp-doctor'] === 'latest' ||
            semver.ltr('1.11.9', json.devDependencies['gfp-doctor'])
        );
    }
}

function shouldRunGfpDoctorPostInstall(json) {
    if (
        json.scripts &&
        json.scripts.postinstall &&
        json.scripts.postinstall.includes('gfp-doctor')
    ) {
        return true;
    }
}

function shouldUseKarma170(json) {
    if (json.devDependencies) {
        if (json.devDependencies['karma']) {
            if (json.devDependencies['karma'] === 'latest') {
                return false;
            }
            return semver.ltr('1.6.9', json.devDependencies['karma']);
        }
    }
}

function shouldUseKarmaCoverage110(json) {
    if (json.devDependencies) {
        if (json.devDependencies['karma-coverage']) {
            if (json.devDependencies['karma-coverage'] === 'latest') {
                return false;
            }
            return semver.ltr('1.0.9', json.devDependencies['karma-coverage']);
        }
    }
}

function shouldUseKarmaJasmine110(json) {
    if (json.devDependencies) {
        if (json.devDependencies['karma-jasmine']) {
            if (json.devDependencies['karma-jasmine'] === 'latest') {
                return false;
            }
            return semver.ltr('1.0.9', json.devDependencies['karma-jasmine']);
        }
    }
}

function shouldUseGffJiraMessage(json) {
    if (
        json.scripts &&
        json.scripts.commitmsg &&
        json.scripts.commitmsg.includes('gff-jira-message') &&
        json.devDependencies &&
        json.devDependencies['gff-jira-message']
    ) {
        return true;
    } else {
        return false;
    }
}
<commit_msg>Added version check to gff-jira-message<commit_after>const log = require('loglevel');
const path = require('path');
const fs = require('fs');
const chalk = require('chalk');
const process = require('process');
const addSkipped = require('./rules-util.js').addSkipped;
const processRules = require('./rules-util.js').processRules;
const semver = require('semver');

const rulesConf = {
    name: 'package.json',
    rules: [
        {
            desc: 'using public npm version of gfp eslint config',
            errorMsg:
                'remove "gfp-eslint-config" from package.json and install "eslint eslint-config-gfp" instead, or just run:',
            run:
                'npm uninstall --save-dev gfp-eslint-config && npm install --save-dev eslint eslint-config-gfp',
            fn: notUsingStashGfpEslintConfig
        },
        {
            desc: 'using eslint-config-gfp',
            errorMsg: 'Run:',
            run: 'npm install --save-dev eslint-config-gfp',
            fn: missingEslintConfigGfp
        },
        {
            desc: 'using public npm version of angular-module-no-deps',
            errorMsg:
                'remove "angular-module-no-deps" from package.json and install "angular-module-no-deps" instead, or just run:',
            run:
                'npm uninstall --save-dev angular-module-no-deps && npm install --save-dev angular-module-no-deps',
            fn: notUsingStashAngularModuleNoDeps
        },
        {
            desc: 'using documentationjs',
            errorMsg:
                'should be using documentationjs to generate JSDoc to README:\n' +
                'add scripts: \n' +
                '"docs": "npm run docs:md",\n' +
                '"docs:md": "documentation readme <source files>.js --section \'API\'"\n' +
                '"and run:',
            run: 'npm install --save-dev documentation',
            fn: usingDocumentationJs
        },
        {
            desc: 'has npm script lint',
            errorMsg:
                'should have a npm script lint\n' +
                'then add script to package.json: \n' +
                '"lint": "eslint *.js"',
            fn: hasNpmLintScript
        },
        {
            desc: 'should be using babel-cli',
            errorMsg: 'Run:',
            run: 'npm install --save-dev babel-cli@6.22.2',
            fn: shouldUseBabelCli
        },
        {
            desc: 'should be using babel-core',
            errorMsg: 'Run:',
            run: 'npm install --save-dev babel-core@6.22.1',
            fn: shouldUseBabelCore
        },
        {
            desc: 'should be using babel-loader',
            errorMsg: 'Run:',
            run: 'npm install --save-dev babel-loader@6.2.10',
            fn: shouldUseBabelLoader
        },
        {
            desc: 'should be using babel-plugin-istanbul',
            errorMsg: 'Run:',
            run: 'npm install --save-dev babel-plugin-istanbul@4.1.5',
            fn: shouldUseBabelInstanbul
        },
        {
            desc: 'should be using babel-plugin-transform-runtime',
            errorMsg: 'Run:',
            run:
                'npm install --save-dev babel-plugin-transform-runtime@.6.22.0',
            fn: shouldUseBabelTransformRuntime
        },
        {
            desc: 'should be using babel-preset-env',
            errorMsg: 'Run:',
            run: 'npm install --save-dev babel-preset-env@1.6.1',
            fn: shouldUseBabelPresetEnv
        },
        {
            desc: 'should be using babel-preprocessor',
            errorMsg: 'Run:',
            run: 'npm install --save-dev karma-babel-preprocessor@7.0.0',
            fn: shouldUseBabelPreprocessor
        },
        {
            desc: 'should be using puppeteer',
            errorMsg:
                'remove "phantomjs" from package.json and install "puppeteer" instead, or just run:',
            run:
                'npm uninstall --save-dev phantomjs && npm install --save-dev puppeteer',
            fn: shouldUsePuppeteer
        },
        {
            desc: 'should be using headless Chrome',
            errorMsg:
                'remove "karma-phantomjs-launcher" from package.json and install karma-chrome-launcher instead, or just run:',
            run:
                'npm uninstall --save-dev karma-phantomjs-launcher && npm install --save-dev karma-chrome-launcher@2.2.0',
            fn: shouldUseChromeLauncher
        },
        {
            desc: 'should use prepush hooks',
            errorMsg:
                'Add the following to npm scripts:\n' +
                '"prepush": "npm run lint && karma start --single-run"\n' +
                'Run:',
            run: 'npm install --save-dev husky',
            fn: shouldUsePrepushHooks
        },
        {
            desc: 'should use public require-polyfill',
            errorMsg:
                'remove "require-polyfill" from package.json and install public version, or just run:',
            run:
                'npm uninstall --save-dev require-polyfill && npm install --save-dev require-polyfill',
            fn: shouldUsePublicRequirePolyfill
        },
        {
            desc: 'should use eslint-config-gfp 4.0.0 and up',
            errorMsg:
                'set eslint-config-gfp to use version "^4.0.0" in package.json or run:',
            run: 'npm install --save-dev eslint-config-gfp@4.0.0',
            fn: shouldUseEslintConfigGfp300
        },
        {
            desc: 'should have gfp-doctor > 1.12 installed',
            errorMsg: 'Run:',
            run: 'npm install --save-dev gfp-doctor',
            fn: shouldUseGfpDoctor
        },
        {
            desc: 'should run gfp-doctor after install',
            errorMsg:
                'add the following to npm scripts:\n' +
                '"postinstall": "gfp-doctor"',
            fn: shouldRunGfpDoctorPostInstall
        },
        {
            desc: 'should use "karma" 1.7.0 and up',
            errorMsg: 'set "karma" to use "^1.7.0" or run',
            run: 'npm install --save-dev karma@1.7.0',
            fn: shouldUseKarma170
        },
        {
            desc: 'should use "karma-coverage" 1.1.0 and up',
            errorMsg: 'set "karma-coverage" to use "^1.1.0" or run:',
            run: 'npm install --save-dev karma-coverage@1.1.0',
            fn: shouldUseKarmaCoverage110
        },
        {
            desc: 'should use "karma-jasmine" 1.1.0 and up',
            errorMsg: 'set "karma-jasmine" to use "^1.1.0" or run:',
            run: 'npm install --save-dev karma-jasmine@1.1.0',
            fn: shouldUseKarmaJasmine110
        },
        {
            desc: 'should use gff-jira-message and run it on commitmsg',
            errorMsg:
                'install gff-jira-message and add it to scripts commitmsg',
            run: 'npm i gff-jira-message --save-dev',
            fn: shouldUseGffJiraMessage
        }
    ]
};

module.exports = function(options) {
    'use strict';
    const packagePath = path.join(
        process.cwd(),
        options.packagePath || 'package.json'
    );
    log.debug(chalk.underline('Examining package.json'), options);

    log.debug(`using ${packagePath}`, options);

    if (!fs.existsSync(packagePath)) {
        log.warn(
            chalk.red('ABORTING: No package.json found in ' + process.cwd())
        );
        return addSkipped(rulesConf);
    }

    const data = fs.readFileSync(packagePath);

    const json = JSON.parse(data);

    // work through all rules, setting passed = true/false
    rulesConf.rules = processRules(rulesConf, json, options);
    return rulesConf;
};

// add more test functions below, should return true if passing the test

function notUsingStashGfpEslintConfig(json) {
    if (json.devDependencies && !json.devDependencies['gfp-eslint-config']) {
        return true;
    }
}

function missingEslintConfigGfp(json) {
    if (json.devDependencies && json.devDependencies['eslint-config-gfp']) {
        return true;
    }
}

function notUsingStashAngularModuleNoDeps(json) {
    // bail out if angular-module-no-deps isn't defined at all
    if (
        json.devDependencies &&
        !json.devDependencies['angular-module-no-deps']
    ) {
        return true;
    }

    if (
        json.devDependencies['angular-module-no-deps'].indexOf('.mrgreen.') !==
        -1
    ) {
        return false;
    }
    return true;
}

function usingDocumentationJs(json) {
    if (json.devDependencies && json.devDependencies['documentation']) {
        return true;
    }
}

function hasNpmLintScript(json) {
    if (json.scripts && json.scripts.lint) {
        return true;
    }
}

function shouldUseBabelCli(json) {
    if (json.devDependencies['babel-cli']) {
        return true;
    }
    return false;
}

function shouldUseBabelCore(json) {
    if (json.devDependencies['babel-core']) {
        return true;
    }
    return false;
}

function shouldUseBabelLoader(json) {
    if (json.devDependencies['babel-loader']) {
        return true;
    }
    return false;
}

function shouldUseBabelInstanbul(json) {
    if (json.devDependencies['babel-plugin-istanbul']) {
        return true;
    }
    return false;
}

function shouldUseBabelTransformRuntime(json) {
    if (json.devDependencies['babel-plugin-transform-runtime']) {
        return true;
    }
    return false;
}

function shouldUseBabelPresetEnv(json) {
    if (json.devDependencies['babel-preset-env']) {
        return true;
    }
    return false;
}

function shouldUseBabelPreprocessor(json) {
    if (json.devDependencies['karma-babel-preprocessor']) {
        return true;
    }
    return false;
}

function shouldUsePuppeteer(json) {
    if (json.devDependencies) {
        if (
            json.devDependencies['phantomjs'] ||
            json.devDependencies['phantomjs-prebuilt']
        ) {
            return false;
        }
        if (json.devDependencies['puppeteer']) {
            return true;
        }
    }
}

function shouldUseChromeLauncher(json) {
    if (json.devDependencies) {
        if (json.devDependencies['karma-phantomjs-launcher']) {
            return false;
        }
        if (json.devDependencies['karma-chrome-launcher']) {
            return true;
        }
    }
}

function shouldUsePrepushHooks(json) {
    if (json.devDependencies && json.devDependencies.husky) {
        if (json.scripts && json.scripts.prepush) {
            return true;
        }
    }
}

function shouldUsePublicRequirePolyfill(json) {
    if (json.devDependencies && json.devDependencies['require-polyfill']) {
        if (json.devDependencies['require-polyfill'].includes('ssh://')) {
            return false;
        }
    }
    return true;
}

function shouldUseEslintConfigGfp300(json) {
    if (json.devDependencies) {
        if (json.devDependencies['eslint-config-gfp']) {
            if (json.devDependencies['eslint-config-gfp'] === 'latest') {
                return false;
            }
            // tries to see if 2.9.9 is less than the current range specified in package.json
            return semver.ltr(
                '3.9.9',
                json.devDependencies['eslint-config-gfp']
            );
        }
    }
}

function shouldUseGfpDoctor(json) {
    if (json.devDependencies && json.devDependencies['gfp-doctor']) {
        return (
            json.devDependencies['gfp-doctor'] === 'latest' ||
            semver.ltr('1.11.9', json.devDependencies['gfp-doctor'])
        );
    }
}

function shouldRunGfpDoctorPostInstall(json) {
    if (
        json.scripts &&
        json.scripts.postinstall &&
        json.scripts.postinstall.includes('gfp-doctor')
    ) {
        return true;
    }
}

function shouldUseKarma170(json) {
    if (json.devDependencies) {
        if (json.devDependencies['karma']) {
            if (json.devDependencies['karma'] === 'latest') {
                return false;
            }
            return semver.ltr('1.6.9', json.devDependencies['karma']);
        }
    }
}

function shouldUseKarmaCoverage110(json) {
    if (json.devDependencies) {
        if (json.devDependencies['karma-coverage']) {
            if (json.devDependencies['karma-coverage'] === 'latest') {
                return false;
            }
            return semver.ltr('1.0.9', json.devDependencies['karma-coverage']);
        }
    }
}

function shouldUseKarmaJasmine110(json) {
    if (json.devDependencies) {
        if (json.devDependencies['karma-jasmine']) {
            if (json.devDependencies['karma-jasmine'] === 'latest') {
                return false;
            }
            return semver.ltr('1.0.9', json.devDependencies['karma-jasmine']);
        }
    }
}

function shouldUseGffJiraMessage(json) {
    if (
        json.scripts &&
        json.scripts.commitmsg &&
        json.scripts.commitmsg.includes('gff-jira-message') &&
        json.devDependencies &&
        json.devDependencies['gff-jira-message'] &&
        semver.gte(json.devDependencies['gff-jira-message'], '1.1.0')
    ) {
        return true;
    } else {
        return false;
    }
}
<|endoftext|>


<commit_before>var remixLib = require('remix-lib')
var EventManager = remixLib.EventManager
var ui = remixLib.helpers.ui
var StorageResolver = require('../storage/storageResolver')
var StorageViewer = require('../storage/storageViewer')

var DebuggerSolidityState = require('./solidityState')
var DebuggerSolidityLocals = require('./solidityLocals')

class VmDebuggerLogic {

  constructor (_debugger, tx, _stepManager, _traceManager, _codeManager, _solidityProxy, _callTree) {
    this.event = new EventManager()
    this.debugger = _debugger
    this.stepManager = _stepManager
    this._traceManager = _traceManager
    this._codeManager = _codeManager
    this._solidityProxy = _solidityProxy
    this._callTree = _callTree
    this.storageResolver = null
    this.tx = tx

    this.debuggerSolidityState = new DebuggerSolidityState(tx, _stepManager, _traceManager, _codeManager, _solidityProxy)
    this.debuggerSolidityLocals = new DebuggerSolidityLocals(tx, _stepManager, _traceManager, _callTree)
  }

  start () {
    this.listenToEvents()
    this.listenToCodeManagerEvents()
    this.listenToTraceManagerEvents()
    this.listenToFullStorageChanges()
    this.listenToNewChanges()

    this.listenToSolidityStateEvents()
    this.listenToSolidityLocalsEvents()
  }

  listenToEvents () {
    const self = this
    this.debugger.event.register('traceUnloaded', function () {
      self.event.trigger('traceUnloaded')
    })

    this.debugger.event.register('newTraceLoaded', function () {
      self.event.trigger('newTraceLoaded')
    })
  }

  listenToCodeManagerEvents () {
    const self = this
    this._codeManager.event.register('changed', function (code, address, index) {
      self.event.trigger('codeManagerChanged', [code, address, index])
    })
  }

  listenToTraceManagerEvents () {
    const self = this

    this.event.register('indexChanged', this, function (index) {
      if (index < 0) return
      if (self.stepManager.currentStepIndex !== index) return

      self.event.trigger('indexUpdate', [index])

      self._traceManager.getCallDataAt(index, function (error, calldata) {
        if (error) {
          // console.log(error)
          self.event.trigger('traceManagerCallDataUpdate', [{}])
        } else if (self.stepManager.currentStepIndex === index) {
          self.event.trigger('traceManagerCallDataUpdate', [calldata])
        }
      })

      self._traceManager.getMemoryAt(index, function (error, memory) {
        if (error) {
          // console.log(error)
          self.event.trigger('traceManagerMemoryUpdate', [{}])
        } else if (self.stepManager.currentStepIndex === index) {
          self.event.trigger('traceManagerMemoryUpdate', [ui.formatMemory(memory, 16)])
        }
      })

      self._traceManager.getCallStackAt(index, function (error, callstack) {
        if (error) {
          // console.log(error)
          self.event.trigger('traceManagerCallStackUpdate', [{}])
        } else if (self.stepManager.currentStepIndex === index) {
          self.event.trigger('traceManagerCallStackUpdate', [callstack])
        }
      })

      self._traceManager.getStackAt(index, function (error, callstack) {
        if (error) {
          // console.log(error)
          self.event.trigger('traceManagerStackUpdate', [{}])
        } else if (self.stepManager.currentStepIndex === index) {
          self.event.trigger('traceManagerStackUpdate', [callstack])
        }
      })

      self._traceManager.getCurrentCalledAddressAt(index, (error, address) => {
        if (error) return
        if (!self.storageResolver) return

        var storageViewer = new StorageViewer({ stepIndex: self.stepManager.currentStepIndex, tx: self.tx, address: address }, self.storageResolver, self._traceManager)

        storageViewer.storageRange((error, storage) => {
          if (error) {
            // console.log(error)
            self.event.trigger('traceManagerStorageUpdate', [{}])
          } else if (self.stepManager.currentStepIndex === index) {
            var header = storageViewer.isComplete(address) ? 'completely loaded' : 'partially loaded...'
            self.event.trigger('traceManagerStorageUpdate', [storage, header])
          }
        })
      })

      self._traceManager.getCurrentStep(index, function (error, step) {
        self.event.trigger('traceCurrentStepUpdate', [error, step])
      })

      self._traceManager.getMemExpand(index, function (error, addmem) {
        self.event.trigger('traceMemExpandUpdate', [error, addmem])
      })

      self._traceManager.getStepCost(index, function (error, gas) {
        self.event.trigger('traceStepCostUpdate', [error, gas])
      })

      self._traceManager.getCurrentCalledAddressAt(index, function (error, address) {
        self.event.trigger('traceCurrentCalledAddressAtUpdate', [error, address])
      })

      self._traceManager.getRemainingGas(index, function (error, remaining) {
        self.event.trigger('traceRemainingGasUpdate', [error, remaining])
      })

      self._traceManager.getReturnValue(index, function (error, returnValue) {
        if (error) {
          self.event.trigger('traceReturnValueUpdate', [[error]])
        } else if (self.stepManager.currentStepIndex === index) {
          self.event.trigger('traceReturnValueUpdate', [[returnValue]])
        }
      })
    })
  }

  listenToFullStorageChanges () {
    const self = this

    this.address = []
    this.traceLength = 0

    self.debugger.event.register('newTraceLoaded', function (length) {
      self._traceManager.getAddresses(function (error, addresses) {
        if (error) return
        self.event.trigger('traceAddressesUpdate', [addresses])
        self.addresses = addresses
      })

      self._traceManager.getLength(function (error, length) {
        if (error) return
        self.event.trigger('traceLengthUpdate', [length])
        self.traceLength = length
      })
    })

    self.debugger.event.register('indexChanged', this, function (index) {
      if (index < 0) return
      if (self.stepManager.currentStepIndex !== index) return
      if (!self.storageResolver) return

      if (index !== self.traceLength - 1) {
        return self.event.trigger('traceLengthUpdate', [{}])
      }
      var storageJSON = {}
      for (var k in self.addresses) {
        var address = self.addresses[k]
        var storageViewer = new StorageViewer({ stepIndex: self.stepManager.currentStepIndex, tx: self.tx, address: address }, self.storageResolver, self._traceManager)
        storageViewer.storageRange(function (error, result) {
          if (!error) {
            storageJSON[address] = result
            self.event.trigger('traceStorageUpdate', [storageJSON])
          }
        })
      }
    })
  }

  listenToNewChanges () {
    const self = this
    self.debugger.event.register('newTraceLoaded', this, function () {
      self.storageResolver = new StorageResolver({web3: self.debugger.web3})
      self.debuggerSolidityState.storageResolver = self.storageResolver
      self.debuggerSolidityLocals.storageResolver = self.storageResolver
      self.event.trigger('newTrace', [])
    })

    self.debugger.callTree.event.register('callTreeReady', function () {
      if (self.debugger.callTree.reducedTrace.length) {
        return self.event.trigger('newCallTree', [])
      }
    })
  }

  listenToSolidityStateEvents () {
    const self = this
    this.event.register('indexChanged', this.debuggerSolidityState.init.bind(this.debuggerSolidityState))
    this.debuggerSolidityState.event.register('solidityState', function (state) {
      self.event.trigger('solidityState', [state])
    })
    this.debuggerSolidityState.event.register('solidityStateMessage', function (message) {
      self.event.trigger('solidityStateMessage', [message])
    })
    this.debuggerSolidityState.event.register('solidityStateUpdating', function () {
      self.event.trigger('solidityStateUpdating', [])
    })
    this.event.register('traceUnloaded', this.debuggerSolidityState.reset.bind(this.debuggerSolidityState))
    this.event.register('newTraceLoaded', this.debuggerSolidityState.reset.bind(this.debuggerSolidityState))
  }

  listenToSolidityLocalsEvents () {
    const self = this
    this.event.register('sourceLocationChanged', this.debuggerSolidityLocals.init.bind(this.debuggerSolidityLocals))
    this.debuggerSolidityLocals.event.register('solidityLocals', function (state) {
      self.event.trigger('solidityLocals', [state])
    })
    this.debuggerSolidityLocals.event.register('solidityLocalsMessage', function (message) {
      self.event.trigger('solidityLocalsMessage', [message])
    })
    this.debuggerSolidityLocals.event.register('solidityLocalsUpdating', function () {
      self.event.trigger('solidityLocalsUpdating', [])
    })
    this.debuggerSolidityLocals.event.register('traceReturnValueUpdate', function (data, header) {
      self.event.trigger('traceReturnValueUpdate', [data, header])
    })
  }

}

module.exports = VmDebuggerLogic
<commit_msg>fix(remix-debug): modify the event name in fullStorageChanges<commit_after>var remixLib = require('remix-lib')
var EventManager = remixLib.EventManager
var ui = remixLib.helpers.ui
var StorageResolver = require('../storage/storageResolver')
var StorageViewer = require('../storage/storageViewer')

var DebuggerSolidityState = require('./solidityState')
var DebuggerSolidityLocals = require('./solidityLocals')

class VmDebuggerLogic {

  constructor (_debugger, tx, _stepManager, _traceManager, _codeManager, _solidityProxy, _callTree) {
    this.event = new EventManager()
    this.debugger = _debugger
    this.stepManager = _stepManager
    this._traceManager = _traceManager
    this._codeManager = _codeManager
    this._solidityProxy = _solidityProxy
    this._callTree = _callTree
    this.storageResolver = null
    this.tx = tx

    this.debuggerSolidityState = new DebuggerSolidityState(tx, _stepManager, _traceManager, _codeManager, _solidityProxy)
    this.debuggerSolidityLocals = new DebuggerSolidityLocals(tx, _stepManager, _traceManager, _callTree)
  }

  start () {
    this.listenToEvents()
    this.listenToCodeManagerEvents()
    this.listenToTraceManagerEvents()
    this.listenToFullStorageChanges()
    this.listenToNewChanges()

    this.listenToSolidityStateEvents()
    this.listenToSolidityLocalsEvents()
  }

  listenToEvents () {
    const self = this
    this.debugger.event.register('traceUnloaded', function () {
      self.event.trigger('traceUnloaded')
    })

    this.debugger.event.register('newTraceLoaded', function () {
      self.event.trigger('newTraceLoaded')
    })
  }

  listenToCodeManagerEvents () {
    const self = this
    this._codeManager.event.register('changed', function (code, address, index) {
      self.event.trigger('codeManagerChanged', [code, address, index])
    })
  }

  listenToTraceManagerEvents () {
    const self = this

    this.event.register('indexChanged', this, function (index) {
      if (index < 0) return
      if (self.stepManager.currentStepIndex !== index) return

      self.event.trigger('indexUpdate', [index])

      self._traceManager.getCallDataAt(index, function (error, calldata) {
        if (error) {
          // console.log(error)
          self.event.trigger('traceManagerCallDataUpdate', [{}])
        } else if (self.stepManager.currentStepIndex === index) {
          self.event.trigger('traceManagerCallDataUpdate', [calldata])
        }
      })

      self._traceManager.getMemoryAt(index, function (error, memory) {
        if (error) {
          // console.log(error)
          self.event.trigger('traceManagerMemoryUpdate', [{}])
        } else if (self.stepManager.currentStepIndex === index) {
          self.event.trigger('traceManagerMemoryUpdate', [ui.formatMemory(memory, 16)])
        }
      })

      self._traceManager.getCallStackAt(index, function (error, callstack) {
        if (error) {
          // console.log(error)
          self.event.trigger('traceManagerCallStackUpdate', [{}])
        } else if (self.stepManager.currentStepIndex === index) {
          self.event.trigger('traceManagerCallStackUpdate', [callstack])
        }
      })

      self._traceManager.getStackAt(index, function (error, callstack) {
        if (error) {
          // console.log(error)
          self.event.trigger('traceManagerStackUpdate', [{}])
        } else if (self.stepManager.currentStepIndex === index) {
          self.event.trigger('traceManagerStackUpdate', [callstack])
        }
      })

      self._traceManager.getCurrentCalledAddressAt(index, (error, address) => {
        if (error) return
        if (!self.storageResolver) return

        var storageViewer = new StorageViewer({ stepIndex: self.stepManager.currentStepIndex, tx: self.tx, address: address }, self.storageResolver, self._traceManager)

        storageViewer.storageRange((error, storage) => {
          if (error) {
            // console.log(error)
            self.event.trigger('traceManagerStorageUpdate', [{}])
          } else if (self.stepManager.currentStepIndex === index) {
            var header = storageViewer.isComplete(address) ? 'completely loaded' : 'partially loaded...'
            self.event.trigger('traceManagerStorageUpdate', [storage, header])
          }
        })
      })

      self._traceManager.getCurrentStep(index, function (error, step) {
        self.event.trigger('traceCurrentStepUpdate', [error, step])
      })

      self._traceManager.getMemExpand(index, function (error, addmem) {
        self.event.trigger('traceMemExpandUpdate', [error, addmem])
      })

      self._traceManager.getStepCost(index, function (error, gas) {
        self.event.trigger('traceStepCostUpdate', [error, gas])
      })

      self._traceManager.getCurrentCalledAddressAt(index, function (error, address) {
        self.event.trigger('traceCurrentCalledAddressAtUpdate', [error, address])
      })

      self._traceManager.getRemainingGas(index, function (error, remaining) {
        self.event.trigger('traceRemainingGasUpdate', [error, remaining])
      })

      self._traceManager.getReturnValue(index, function (error, returnValue) {
        if (error) {
          self.event.trigger('traceReturnValueUpdate', [[error]])
        } else if (self.stepManager.currentStepIndex === index) {
          self.event.trigger('traceReturnValueUpdate', [[returnValue]])
        }
      })
    })
  }

  listenToFullStorageChanges () {
    const self = this

    this.address = []
    this.traceLength = 0

    self.debugger.event.register('newTraceLoaded', function (length) {
      self._traceManager.getAddresses(function (error, addresses) {
        if (error) return
        self.event.trigger('traceAddressesUpdate', [addresses])
        self.addresses = addresses
      })

      self._traceManager.getLength(function (error, length) {
        if (error) return
        self.event.trigger('traceLengthUpdate', [length])
        self.traceLength = length
      })
    })

    self.debugger.event.register('indexChanged', this, function (index) {
      if (index < 0) return
      if (self.stepManager.currentStepIndex !== index) return
      if (!self.storageResolver) return

      if (index !== self.traceLength - 1) {
        return self.event.trigger('traceStorageUpdate', [{}])
      }
      var storageJSON = {}
      for (var k in self.addresses) {
        var address = self.addresses[k]
        var storageViewer = new StorageViewer({ stepIndex: self.stepManager.currentStepIndex, tx: self.tx, address: address }, self.storageResolver, self._traceManager)
        storageViewer.storageRange(function (error, result) {
          if (!error) {
            storageJSON[address] = result
            self.event.trigger('traceStorageUpdate', [storageJSON])
          }
        })
      }
    })
  }

  listenToNewChanges () {
    const self = this
    self.debugger.event.register('newTraceLoaded', this, function () {
      self.storageResolver = new StorageResolver({web3: self.debugger.web3})
      self.debuggerSolidityState.storageResolver = self.storageResolver
      self.debuggerSolidityLocals.storageResolver = self.storageResolver
      self.event.trigger('newTrace', [])
    })

    self.debugger.callTree.event.register('callTreeReady', function () {
      if (self.debugger.callTree.reducedTrace.length) {
        return self.event.trigger('newCallTree', [])
      }
    })
  }

  listenToSolidityStateEvents () {
    const self = this
    this.event.register('indexChanged', this.debuggerSolidityState.init.bind(this.debuggerSolidityState))
    this.debuggerSolidityState.event.register('solidityState', function (state) {
      self.event.trigger('solidityState', [state])
    })
    this.debuggerSolidityState.event.register('solidityStateMessage', function (message) {
      self.event.trigger('solidityStateMessage', [message])
    })
    this.debuggerSolidityState.event.register('solidityStateUpdating', function () {
      self.event.trigger('solidityStateUpdating', [])
    })
    this.event.register('traceUnloaded', this.debuggerSolidityState.reset.bind(this.debuggerSolidityState))
    this.event.register('newTraceLoaded', this.debuggerSolidityState.reset.bind(this.debuggerSolidityState))
  }

  listenToSolidityLocalsEvents () {
    const self = this
    this.event.register('sourceLocationChanged', this.debuggerSolidityLocals.init.bind(this.debuggerSolidityLocals))
    this.debuggerSolidityLocals.event.register('solidityLocals', function (state) {
      self.event.trigger('solidityLocals', [state])
    })
    this.debuggerSolidityLocals.event.register('solidityLocalsMessage', function (message) {
      self.event.trigger('solidityLocalsMessage', [message])
    })
    this.debuggerSolidityLocals.event.register('solidityLocalsUpdating', function () {
      self.event.trigger('solidityLocalsUpdating', [])
    })
    this.debuggerSolidityLocals.event.register('traceReturnValueUpdate', function (data, header) {
      self.event.trigger('traceReturnValueUpdate', [data, header])
    })
  }

}

module.exports = VmDebuggerLogic
<|endoftext|>


<commit_before>/* See license.txt for terms of usage */

"use strict";

/**
 * This module is loaded on the backend (can be a remote device) where
 * some module or features (such as Tracing console) don't have to
 * be available. Also Firebug SDK isn't available on the backend.
 */

// Add-on SDK
const { Cu, components } = require("chrome");

// DevTools
const { DebuggerServer } = Cu.import("resource://gre/modules/devtools/dbg-server.jsm", {});
const { devtools } = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
const protocol = devtools["require"]("devtools/server/protocol");
const { method, RetVal, ActorClass, Actor, Arg, types } = protocol;
const Events = devtools["require"]("sdk/event/core");
const { makeInfallible } = devtools["require"]("devtools/toolkit/DevToolsUtils.js");
const DevToolsUtils = devtools["require"]("devtools/toolkit/DevToolsUtils");
const { NodeActor } = devtools["require"]("devtools/server/actors/inspector");

// Constants
const Previewers = DebuggerServer.ObjectActorPreviewers;

// Number of items to preview in objects, arrays, maps, sets, lists,
// collections, etc.
const OBJECT_PREVIEW_MAX_ITEMS = 10;

// For debugging purposes. Note that the tracing module isn't available
// on the backend (in case of remote device debugging).
// const baseUrl = "resource://firequery-at-binaryage-dot-com/";
// const { getTrace } = Cu.import(baseUrl + "node_modules/firebug.sdk/lib/core/actor.js");
// const Trace = getTrace(DebuggerServer.parentMessageManager);
const Trace = {sysout: () => {}};

/**
 * Helper actor state watcher.
 */
function expectState(expectedState, method) {
  return function(...args) {
    if (this.state !== expectedState) {
      Trace.sysout("actor.expectState; ERROR wrong state, expected '" +
        expectedState + "', but current state is '" + this.state + "'" +
        ", method: " + method);

      let msg = "Wrong State: Expected '" + expectedState + "', but current " +
        "state is '" + this.state + "'";

      return Promise.reject(new Error(msg));
    }

    try {
      return method.apply(this, args);
    } catch (err) {
      Cu.reportError("actor.js; expectState EXCEPTION " + err, err);
    }
  };
}

/**
 * @actor This object represents an actor that is dynamically injected
 * (registered) to the debuggee target (back-end). The debuggee target
 * can be a running instance of the browser on local machine or remote
 * device such as mobile phone. The communication with this object is
 * always done through RDP (Remote Debugging Protocol). Read more about
 * {@link https://wiki.mozilla.org/Remote_Debugging_Protocol|RDP}.
 *
 * Read more about Protocol API:
 * https://github.com/mozilla/gecko-dev/blob/master/toolkit/devtools/server/docs/protocol.js.md
 */
var FireQueryActor = ActorClass(
/** @lends FireQueryActor */
{
  typeName: "FireQueryActor",

  /**
   * Events emitted by this actor.
   */
  events: {
    "data-modified": {
      type: "dataModified",
      node: Arg(0, "disconnectedNode"),
      jQueryData: Arg(1, "nullable:json")
    }
  },

  // Initialization

  initialize: function(conn, parent) {
    Trace.sysout("FireQueryActor.initialize; parent: " +
      parent.actorID + ", conn: " + conn.prefix, this);

    Actor.prototype.initialize.call(this, conn);

    this.parent = parent;
    this.state = "detached";

    // DevTools events
    this.onNodeActorForm = this.onNodeActorForm.bind(this);
    this.onNavigate = this.onNavigate.bind(this);

    // jQuery events
    this.onDataModified = this.onDataModified.bind(this);
    this.onJQueryDetected = this.onJQueryDetected.bind(this);

    // Console customization
    this.onBuildPreview = this.onBuildPreview.bind(this);
  },

  /**
   * The destroy is only called automatically by the framework (parent actor)
   * if an actor is instantiated by a parent actor.
   */
  destroy: function() {
    Trace.sysout("FireQueryActor.destroy; state: " + this.state, arguments);

    if (this.state === "attached") {
      this.detach();
    }

    Actor.prototype.destroy.call(this);
  },

  /**
   * Automatically executed by the framework when the parent connection
   * is closed.
   */
  disconnect: function() {
    Trace.sysout("FireQueryActor.disconnect; state: " + this.state, arguments);

    if (this.state === "attached") {
      this.detach();
    }
  },

  /**
   * Attach to this actor. Executed when the front (client) is attaching
   * to this actor.
   */
  attach: method(expectState("detached", function() {
    Trace.sysout("FireQueryActor.attach;", arguments);

    this.state = "attached";

    Previewers.Object.unshift(this.onBuildPreview);

    Events.on(this.parent, "navigate", this.onNavigate);
    Events.on(NodeActor, "form", this.onNodeActorForm);
  }), {
    request: {},
    response: {
      type: "attached"
    }
  }),

  /**
   * Detach from this actor. Executed when the front (client) detaches
   * from this actor.
   */
  detach: method(expectState("attached", function() {
    Trace.sysout("FireQueryActor.detach;", arguments);

    this.state = "detached";

    Events.off(this.parent, "navigate", this.onNavigate);
    Events.off(NodeActor, "form", this.onNodeActorForm);

    // Remove (content) window listeners.
    let win = this.parent.window.wrappedJSObject;
    win.removeEventListener("jQueryDetected", this.onJQueryDetected, true);
    win.removeEventListener("firequery-event", this.onDataModified, true);

    // Remove previewer listener (web console).
    removeItem(Previewers.Object, this.onBuildPreview);
  }), {
    request: {},
    response: {
      type: "detached"
    }
  }),

  /**
   * Build jQuery object JSON preview that is sent back to the client.
   */
  onBuildPreview: makeInfallible(function(actor, grip, rawObj) {
    if (!DevToolsUtils.getProperty(actor.obj, "jquery")) {
      return false;
    }

    let length = DevToolsUtils.getProperty(actor.obj, "length");
    if (typeof length != "number") {
      return false;
    }

    let preview = grip.preview = {
      kind: "jQuery",
      length: length,
    };

    // Introduced in Fx42
    let hooks = actor.hooks;
    if (hooks && hooks.getGripDepth() > 1) {
      return true;
    }

    let threadActor = this.parent.threadActor;
    if (threadActor && threadActor._gripDepth > 1) {
      return true;
    }

    Trace.sysout("FireQueryActor.onBuildPreview;", arguments);

    preview.items = [];

    let raw = actor.obj.unsafeDereference();
    for (let i = 0; i < preview.length; ++i) {
      // Array Xrays filter out various possibly-unsafe properties (like
      // functions, and claim that the value is undefined instead. This
      // is generally the right thing for privileged code accessing untrusted
      // objects, but quite confusing for Object previews. So we manually
      // override this protection by waiving Xrays on the array, and re-applying
      // Xrays on any indexed value props that we pull off of it.
      let desc = Object.getOwnPropertyDescriptor(Cu.waiveXrays(raw), i);
      if (desc && !desc.get && !desc.set) {
        let value = Cu.unwaiveXrays(desc.value);
        value = makeDebuggeeValueIfNeeded(actor.obj, value);

        let grip = this.createValueGrip(actor, value);
        preview.items.push(grip);

        let data = hasJQueryData(desc.value);
        if (data) {
          data = Cu.unwaiveXrays(data);
          data = makeDebuggeeValueIfNeeded(actor.obj, data);
          grip.preview.jQueryData = this.createValueGrip(actor, data);

          // xxxHonza: generate preview for the data?
        }
      } else {
        preview.items.push(null);
      }

      if (preview.length == OBJECT_PREVIEW_MAX_ITEMS) {
        break;
      }
    }

    Trace.sysout("FireQueryActor.onPreview; jQuery preview", preview);

    return true;
  }),

  createValueGrip: function(actor, value) {
    // Introduced in Fx42
    if (actor.hooks) {
      return actor.hooks.createValueGrip(value);
    }

    return actor.threadActor.createValueGrip(value);
  },

  /**
   * Set UI stylesheet for anonymous content (sent from the client).
   */
  patchJQuery: method(expectState("attached", function(patch, watcher, walkerActorID) {
    Trace.sysout("FireQueryActor.patchJQuery;", this);

    // Remember arguments, so we can re-patch when the page is navigated.
    this.patch = patch || this.patch;
    this.watcher = watcher || this.watcher;
    this.walkerActorID = walkerActorID || this.walkerActorID;

    let win = this.parent.window.wrappedJSObject;
    win.addEventListener("jQueryDetected", this.onJQueryDetected, true);
    win.addEventListener("firequery-event", this.onDataModified, true);
    win.eval(this.watcher);
  }), {
    request: {
      patch: Arg(0, "string"),
      watcher: Arg(1, "string"),
      walker: Arg(2, "string")
    },
    response: {
      type: "jquery-patched"
    }
  }),

  onJQueryDetected: makeInfallible(function(event) {
    let win = this.parent.window.wrappedJSObject;

    Trace.sysout("FireQueryActor.onJQueryDetected; " + win.location, event);

    win.eval(this.patch);
  }),

  onDataModified: makeInfallible(function(event) {
    let win = this.parent.window.wrappedJSObject;

    Trace.sysout("FireQueryActor.onDataModified; " + win.location, event);

    if (ignoreEventParsers()) {
      return;
    }

    let walkerActor = this.conn.getActor(this.walkerActorID);
    if (!walkerActor) {
      return;
    }

    let data = walkerActor.attachElement(event.target);
    let jQueryData = hasJQueryData(event.target);

    Trace.sysout("FireQueryActor.onDataModified;", {
      data: data,
      jQueryData: jQueryData
    });

    try {
      Events.emit(this, "data-modified", data, jQueryData);
    } catch(err) {
      Trace.sysout("FireQueryActor.onDataModified; ERROR " + err, err);
    }
  }),

  /**
   * Returns jQuery data for given node.
   */
  getJQueryData: method(expectState("attached", function(node) {
    Trace.sysout("FireQueryActor.getJQueryData;", node);

    return {
      jQueryData: hasJQueryData(node.rawNode)
    }
  }), {
    request: { node: Arg(0, "domnode") },
    response: RetVal("json"),
  }),

  // Events

  /**
   * Page navigation handler.
   */
  onNavigate: function({isTopLevel}) {
    Trace.sysout("FireQueryActor.onNavigate " + isTopLevel);

    if (isTopLevel) {
      this.patchJQuery();
    }
  },

  // Debugger Server Events

  onNodeActorForm: makeInfallible(function(event) {
    let nodeActor = event.target;
    let form = event.data;
    let data = hasJQueryData(nodeActor.rawNode);

    // xxxHonza: check that the event comes from an actor in the
    // same connection as this actor.

    // Pass only a flag that says whether there are any jQuery
    // data associated or not. The data itself will be fetched
    // on demand (when displayed e.g. in a tooltip)
    form.setFormProperty("hasJQueryData", !!data);

    let json = data ? JSON.stringify(data) : "";
  })
});

// Helpers

function makeDebuggeeValueIfNeeded(obj, value) {
  if (value && (typeof value == "object" || typeof value == "function")) {
    return obj.makeDebuggeeValue(value);
  }
  return value;
}

function evalJQueryData(object) {
  try {
    var forceInternals = true;
    var win = object.ownerDocument.defaultView;
    var wrapper = win.wrappedJSObject || win;
    var jQuery = wrapper.jQuery;
    // jQuery 1.4 breaking changes (http://jquery14.com/day-01/jquery-14):
    // jQuery.data(elem) no longer returns an id, it returns the
    // element's object data instead.
    var data = jQuery.originalDataReplacedByFireQuery || jQuery.data;
    var idOrData = data(object.wrappedJSObject || object,
      undefined, undefined, forceInternals);

    if (typeof idOrData == "object") {
      // jQuery 1.4+ path
      return idOrData;
    }

    // jQuery 1.3- path
    return jQuery.cache[idOrData];
  } catch (ex) {
  }
};

function hasJQueryData(object) {
  var data = evalJQueryData(object);
  for (var x in data) {
    if (data.hasOwnProperty(x)) {
      return data;
    }
  }
};

// Patching NodeActor (add custom info into actor form)
// Bug 1036949 - New API: MarkupView customization
// xxxHonza: Can be removed as soon as Fx42 is the
// minimum required version.
let originalForm = NodeActor.prototype.form;
NodeActor.prototype.form = function(detail) {
  let form = originalForm.apply(this, arguments);

  // The return value doesn't always have to be a form object.
  if (detail === "actorid") {
    return form;
  }

  // form.setFormProperty has been introduced in Bug 1036949
  // The content of the following if block is taken from the
  // patch attached to Bug 1036949
  if (!form.setFormProperty) {
    // Add an extra API for custom properties added by other
    // modules/extensions.
    form.setFormProperty = (name, value) => {
      if (!form.props) {
        form.props = {};
      }
      form.props[name] = value;
    };

    // Fire an event so, other modules can create its own properties
    // that should be passed to the client (within the form.props field).
    Events.emit(NodeActor, "form", {
      target: this,
      data: form
    });
  }

  return form;
}

// Helpers

/**
 * Remove an item from specified array. Copied from Firebug.sdk
 * (since the SDK isn't available on the backend).
 */
function removeItem(list, item) {
  for (let i = 0; i < list.length; i++) {
    if (list[i] == item) {
      list.splice(i, 1);
      return true;
    }
  }
  return false;
};

/**
 * EventParser module in DevTools is internally executing jQuery.data
 * function. Ignore these calls.
 */
function ignoreEventParsers() {
  var eventParser = "resource://gre/modules/devtools/event-parsers.js";
  var counter = 0;
  var stack = components.stack;
  while (stack && counter++ < 10) {
    var url = stack.filename;
    if (url.indexOf(eventParser) != -1) {
      return true;
    }
    stack = stack.caller;
  }
  return false;
}

// Exports from this module
exports.FireQueryActor = FireQueryActor;
<commit_msg>Avoid exception<commit_after>/* See license.txt for terms of usage */

"use strict";

/**
 * This module is loaded on the backend (can be a remote device) where
 * some module or features (such as Tracing console) don't have to
 * be available. Also Firebug SDK isn't available on the backend.
 */

// Add-on SDK
const { Cu, components } = require("chrome");

// DevTools
const { DebuggerServer } = Cu.import("resource://gre/modules/devtools/dbg-server.jsm", {});
const { devtools } = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
const protocol = devtools["require"]("devtools/server/protocol");
const { method, RetVal, ActorClass, Actor, Arg, types } = protocol;
const Events = devtools["require"]("sdk/event/core");
const { makeInfallible } = devtools["require"]("devtools/toolkit/DevToolsUtils.js");
const DevToolsUtils = devtools["require"]("devtools/toolkit/DevToolsUtils");
const { NodeActor } = devtools["require"]("devtools/server/actors/inspector");

// Constants
const Previewers = DebuggerServer.ObjectActorPreviewers;

// Number of items to preview in objects, arrays, maps, sets, lists,
// collections, etc.
const OBJECT_PREVIEW_MAX_ITEMS = 10;

// For debugging purposes. Note that the tracing module isn't available
// on the backend (in case of remote device debugging).
// const baseUrl = "resource://firequery-at-binaryage-dot-com/";
// const { getTrace } = Cu.import(baseUrl + "node_modules/firebug.sdk/lib/core/actor.js");
// const Trace = getTrace(DebuggerServer.parentMessageManager);
const Trace = {sysout: () => {}};

/**
 * Helper actor state watcher.
 */
function expectState(expectedState, method) {
  return function(...args) {
    if (this.state !== expectedState) {
      Trace.sysout("actor.expectState; ERROR wrong state, expected '" +
        expectedState + "', but current state is '" + this.state + "'" +
        ", method: " + method);

      let msg = "Wrong State: Expected '" + expectedState + "', but current " +
        "state is '" + this.state + "'";

      return Promise.reject(new Error(msg));
    }

    try {
      return method.apply(this, args);
    } catch (err) {
      Cu.reportError("actor.js; expectState EXCEPTION " + err, err);
    }
  };
}

/**
 * @actor This object represents an actor that is dynamically injected
 * (registered) to the debuggee target (back-end). The debuggee target
 * can be a running instance of the browser on local machine or remote
 * device such as mobile phone. The communication with this object is
 * always done through RDP (Remote Debugging Protocol). Read more about
 * {@link https://wiki.mozilla.org/Remote_Debugging_Protocol|RDP}.
 *
 * Read more about Protocol API:
 * https://github.com/mozilla/gecko-dev/blob/master/toolkit/devtools/server/docs/protocol.js.md
 */
var FireQueryActor = ActorClass(
/** @lends FireQueryActor */
{
  typeName: "FireQueryActor",

  /**
   * Events emitted by this actor.
   */
  events: {
    "data-modified": {
      type: "dataModified",
      node: Arg(0, "disconnectedNode"),
      jQueryData: Arg(1, "nullable:json")
    }
  },

  // Initialization

  initialize: function(conn, parent) {
    Trace.sysout("FireQueryActor.initialize; parent: " +
      parent.actorID + ", conn: " + conn.prefix, this);

    Actor.prototype.initialize.call(this, conn);

    this.parent = parent;
    this.state = "detached";

    // DevTools events
    this.onNodeActorForm = this.onNodeActorForm.bind(this);
    this.onNavigate = this.onNavigate.bind(this);

    // jQuery events
    this.onDataModified = this.onDataModified.bind(this);
    this.onJQueryDetected = this.onJQueryDetected.bind(this);

    // Console customization
    this.onBuildPreview = this.onBuildPreview.bind(this);
  },

  /**
   * The destroy is only called automatically by the framework (parent actor)
   * if an actor is instantiated by a parent actor.
   */
  destroy: function() {
    Trace.sysout("FireQueryActor.destroy; state: " + this.state, arguments);

    if (this.state === "attached") {
      this.detach();
    }

    Actor.prototype.destroy.call(this);
  },

  /**
   * Automatically executed by the framework when the parent connection
   * is closed.
   */
  disconnect: function() {
    Trace.sysout("FireQueryActor.disconnect; state: " + this.state, arguments);

    if (this.state === "attached") {
      this.detach();
    }
  },

  /**
   * Attach to this actor. Executed when the front (client) is attaching
   * to this actor.
   */
  attach: method(expectState("detached", function() {
    Trace.sysout("FireQueryActor.attach;", arguments);

    this.state = "attached";

    Previewers.Object.unshift(this.onBuildPreview);

    Events.on(this.parent, "navigate", this.onNavigate);
    Events.on(NodeActor, "form", this.onNodeActorForm);
  }), {
    request: {},
    response: {
      type: "attached"
    }
  }),

  /**
   * Detach from this actor. Executed when the front (client) detaches
   * from this actor.
   */
  detach: method(expectState("attached", function() {
    Trace.sysout("FireQueryActor.detach;", arguments);

    this.state = "detached";

    Events.off(this.parent, "navigate", this.onNavigate);
    Events.off(NodeActor, "form", this.onNodeActorForm);

    // Remove (content) window listeners.
    let win = this.parent.window.wrappedJSObject;
    win.removeEventListener("jQueryDetected", this.onJQueryDetected, true);
    win.removeEventListener("firequery-event", this.onDataModified, true);

    // Remove previewer listener (web console).
    removeItem(Previewers.Object, this.onBuildPreview);
  }), {
    request: {},
    response: {
      type: "detached"
    }
  }),

  /**
   * Build jQuery object JSON preview that is sent back to the client.
   */
  onBuildPreview: makeInfallible(function(actor, grip, rawObj) {
    if (!DevToolsUtils.getProperty(actor.obj, "jquery")) {
      return false;
    }

    let length = DevToolsUtils.getProperty(actor.obj, "length");
    if (typeof length != "number") {
      return false;
    }

    let preview = grip.preview = {
      kind: "jQuery",
      length: length,
    };

    // Introduced in Fx42
    let hooks = actor.hooks;
    if (hooks && hooks.getGripDepth() > 1) {
      return true;
    }

    let threadActor = this.parent.threadActor;
    if (threadActor && threadActor._gripDepth > 1) {
      return true;
    }

    Trace.sysout("FireQueryActor.onBuildPreview;", arguments);

    preview.items = [];

    let raw = actor.obj.unsafeDereference();
    for (let i = 0; i < preview.length; ++i) {
      // Array Xrays filter out various possibly-unsafe properties (like
      // functions, and claim that the value is undefined instead. This
      // is generally the right thing for privileged code accessing untrusted
      // objects, but quite confusing for Object previews. So we manually
      // override this protection by waiving Xrays on the array, and re-applying
      // Xrays on any indexed value props that we pull off of it.
      let desc = Object.getOwnPropertyDescriptor(Cu.waiveXrays(raw), i);
      if (desc && !desc.get && !desc.set) {
        let value = Cu.unwaiveXrays(desc.value);
        value = makeDebuggeeValueIfNeeded(actor.obj, value);

        let grip = this.createValueGrip(actor, value);
        preview.items.push(grip);

        let data = hasJQueryData(desc.value);
        if (data) {
          data = Cu.unwaiveXrays(data);
          data = makeDebuggeeValueIfNeeded(actor.obj, data);
          grip.preview.jQueryData = this.createValueGrip(actor, data);

          // xxxHonza: generate preview for the data?
        }
      } else {
        preview.items.push(null);
      }

      if (preview.length == OBJECT_PREVIEW_MAX_ITEMS) {
        break;
      }
    }

    Trace.sysout("FireQueryActor.onPreview; jQuery preview", preview);

    return true;
  }),

  createValueGrip: function(actor, value) {
    // Introduced in Fx42
    if (actor.hooks) {
      return actor.hooks.createValueGrip(value);
    }

    return actor.threadActor.createValueGrip(value);
  },

  /**
   * Set UI stylesheet for anonymous content (sent from the client).
   */
  patchJQuery: method(expectState("attached", function(patch, watcher, walkerActorID) {
    Trace.sysout("FireQueryActor.patchJQuery;", this);

    // Remember arguments, so we can re-patch when the page is navigated.
    this.patch = patch || this.patch;
    this.watcher = watcher || this.watcher;
    this.walkerActorID = walkerActorID || this.walkerActorID;

    let win = this.parent.window.wrappedJSObject;
    win.addEventListener("jQueryDetected", this.onJQueryDetected, true);
    win.addEventListener("firequery-event", this.onDataModified, true);
    win.eval(this.watcher);
  }), {
    request: {
      patch: Arg(0, "string"),
      watcher: Arg(1, "string"),
      walker: Arg(2, "string")
    },
    response: {
      type: "jquery-patched"
    }
  }),

  onJQueryDetected: makeInfallible(function(event) {
    let win = this.parent.window.wrappedJSObject;

    Trace.sysout("FireQueryActor.onJQueryDetected; " + win.location, event);

    win.eval(this.patch);
  }),

  onDataModified: makeInfallible(function(event) {
    let win = this.parent.window.wrappedJSObject;

    Trace.sysout("FireQueryActor.onDataModified; " + win.location, event);

    if (ignoreEventParsers()) {
      return;
    }

    let walkerActor = this.conn.getActor(this.walkerActorID);
    if (!walkerActor) {
      return;
    }

    let data = walkerActor.attachElement(event.target);
    let jQueryData = hasJQueryData(event.target);

    Trace.sysout("FireQueryActor.onDataModified;", {
      data: data,
      jQueryData: jQueryData
    });

    try {
      Events.emit(this, "data-modified", data, jQueryData);
    } catch(err) {
      Trace.sysout("FireQueryActor.onDataModified; ERROR " + err, err);
    }
  }),

  /**
   * Returns jQuery data for given node.
   */
  getJQueryData: method(expectState("attached", function(node) {
    Trace.sysout("FireQueryActor.getJQueryData;", node);

    return {
      jQueryData: hasJQueryData(node.rawNode)
    }
  }), {
    request: { node: Arg(0, "domnode") },
    response: RetVal("json"),
  }),

  // Events

  /**
   * Page navigation handler.
   */
  onNavigate: function({isTopLevel}) {
    Trace.sysout("FireQueryActor.onNavigate " + isTopLevel);

    if (isTopLevel) {
      this.patchJQuery();
    }
  },

  // Debugger Server Events

  onNodeActorForm: makeInfallible(function(event) {
    let nodeActor = event.target;
    let form = event.data;
    let data = hasJQueryData(nodeActor.rawNode);

    // xxxHonza: check that the event comes from an actor in the
    // same connection as this actor.

    // Pass only a flag that says whether there are any jQuery
    // data associated or not. The data itself will be fetched
    // on demand (when displayed e.g. in a tooltip)
    form.setFormProperty("hasJQueryData", !!data);

    let json = data ? JSON.stringify(data) : "";
  })
});

// Helpers

function makeDebuggeeValueIfNeeded(obj, value) {
  if (value && (typeof value == "object" || typeof value == "function")) {
    return obj.makeDebuggeeValue(value);
  }
  return value;
}

function evalJQueryData(object) {
  try {
    var forceInternals = true;
    var win = object.ownerDocument.defaultView;
    var wrapper = win.wrappedJSObject || win;
    var jQuery = wrapper.jQuery;
    // jQuery 1.4 breaking changes (http://jquery14.com/day-01/jquery-14):
    // jQuery.data(elem) no longer returns an id, it returns the
    // element's object data instead.
    var data = jQuery.originalDataReplacedByFireQuery || jQuery.data;
    var idOrData = data(object.wrappedJSObject || object,
      undefined, undefined, forceInternals);

    if (typeof idOrData == "object") {
      // jQuery 1.4+ path
      return idOrData;
    }

    // jQuery 1.3- path
    return jQuery.cache[idOrData];
  } catch (ex) {
  }
};

function hasJQueryData(object) {
  var data = evalJQueryData(object);
  for (var x in data) {
    if (data.hasOwnProperty(x)) {
      return data;
    }
  }
};

// Patching NodeActor (add custom info into actor form)
// Bug 1036949 - New API: MarkupView customization
// xxxHonza: Can be removed as soon as Fx42 is the
// minimum required version.
let originalForm = NodeActor.prototype.form;
NodeActor.prototype.form = function(detail) {
  let form = originalForm.apply(this, arguments);

  // The return value doesn't always have to be a form object.
  if (detail === "actorid") {
    return form;
  }

  // form.setFormProperty has been introduced in Bug 1036949
  // The content of the following if block is taken from the
  // patch attached to Bug 1036949
  if (!form.setFormProperty) {
    // Add an extra API for custom properties added by other
    // modules/extensions.
    form.setFormProperty = (name, value) => {
      if (!form.props) {
        form.props = {};
      }
      form.props[name] = value;
    };

    // Fire an event so, other modules can create its own properties
    // that should be passed to the client (within the form.props field).
    Events.emit(NodeActor, "form", {
      target: this,
      data: form
    });
  }

  return form;
}

// Helpers

/**
 * Remove an item from specified array. Copied from Firebug.sdk
 * (since the SDK isn't available on the backend).
 */
function removeItem(list, item) {
  for (let i = 0; i < list.length; i++) {
    if (list[i] == item) {
      list.splice(i, 1);
      return true;
    }
  }
  return false;
};

/**
 * EventParser module in DevTools is internally executing jQuery.data
 * function. Ignore these calls.
 */
function ignoreEventParsers() {
  var eventParser = "resource://gre/modules/devtools/event-parsers.js";
  var counter = 0;
  var stack = components.stack;
  while (stack && counter++ < 10) {
    var url = stack.filename;
    if (url && url.indexOf(eventParser) != -1) {
      return true;
    }
    stack = stack.caller;
  }
  return false;
}

// Exports from this module
exports.FireQueryActor = FireQueryActor;
<|endoftext|>