<commit_before>'use strict';

var VM = require('vm'),
    Q = require('q'),
    QFS = require('q-fs'),
    FS = require('fs'),
    PATH = require('../../path'),
    U = require('../../util'),
    INHERIT = require('inherit'),
    LOGGER = require('../../logger');

exports.API_VER = 2;

exports.techMixin = {

    getTechName: function() {
        return 'deps.js';
    },

    getBuildSuffixesMap: function() {
        return {
            'deps.js': 'deps.js'
        };
    },

    readContentSync: function(path) {
        if (!PATH.existsSync(path)) return '';
        return FS.readFileSync(path, 'utf8');
    },

    readContent: function(path) {
        return U.readFile(path)
            .fail(function() {
                return '';
            });
    },

    getCreateResult: function() {

        return [
            '({',
            '    mustDeps: [],',
            '    shouldDeps: []',
            '})'
        ].join('\n');

    },

    transformBuildDecl: function(decl) {

        return this.expandDeps(decl)
            .then(function(deps) {

                var d = deps.serialize(),
                    o = {};

                if(d['']) {
                    o.deps = d[''][''];
                    delete d[''][''];
                }
                U.isEmptyObject(d) || (o.depsByTechs = d);

                return o;

            });

    },

    expandDeps: function(decl) {

        var _this = this;

        return Q.when(decl)
            .then(function(decl) {
                return new Deps(decl.blocks || decl.deps).expandByFS(_this);
            });

    },

    buildByDecl: function(decl, levels, output, opts) {

        var suffix = this.getTechName(),
            file = this.getPath(PATH.resolve(output), suffix),
            _this = this;

        return Q.all(levels
            .map(function(level) {
                return level.scanFiles();
            }))
            .then(function(){

                var declLastModified = -1;
                if (opts.declarationPath) declLastModified =
                    QFS.lastModified(opts.declarationPath)
                        .fail(function() {
                            return -1;
                        });

                return Q.all([_this.getLastUsedData(file),
                              declLastModified,
                              QFS.lastModified(file)
                                  .fail(function() {
                                      return -1;
                                  })])

                    .spread(function(data, declLastUpdated, lastUpdated) {
                        if (!data || !data.levels ||
                            !~lastUpdated || !~declLastUpdated ||
                            declLastUpdated > lastUpdated) return false;

                        return !_this.checkLevelsChanged(levels, data, lastUpdated);
                    })
                    .then(function(valid) {
                        LOGGER.fverbose('%s is %s', file, valid?'valid':'not valid');
                        if (!valid)
                            return _this.storeBuildResult(
                                _this.getPath(output, suffix),
                                suffix,
                                _this.expandDeps(decl)
                                    .then(function(deps) {
                                        return deps.stringify();
                                    }))
                                .then(function() {
                                    return _this.saveLastUsedData(file,
                                        {
                                            levels: levels.map(function(level) {
                                                return {
                                                    dir: level.dir,
                                                    fileCount: (level.files.files['deps.js'] || []).length
                                                };
                                            })
                                        }
                                    );
                                });
                    });

            });

    },

    checkLevelsChanged: function(levels, cache, lastUpdated) {
        if (levels.count !== cache.levels.count) return true;

        var i;
        for(i = 0; i < levels.length; i++) {
            if (levels[i].dir !== cache.levels[i].dir) {
                return true;
            }

            if (((levels[i].files.files['deps.js'] || [])).length !== cache.levels[i].fileCount) {
                return true;
            }
        }

        for(i = 0; i < levels.length; i++) {
            var level = levels[i],
                files = level.files.files['deps.js'] || [];

            for(var file in files) {
                if (files[file].lastUpdated > lastUpdated) return true;
            }
        }

        return false;
    }

};

var Deps = exports.Deps = INHERIT({

    __constructor: function(deps) {
        this.items = {};
        this.itemsByOrder = [];
        this.uniqExpand = {};

        // Force adding of root item to this.items
        var rootItem = this.rootItem = new DepsItem({});
        this.items[rootItem.buildKey()] = rootItem;

        deps && this.parse(deps);
    },

    add: function(target, depsType, item) {
        var items = this.items,
            targetKey = target.buildKey(),
            itemKey = item.buildKey();

        if(!items[itemKey]) {
            items[itemKey] = item;
            this.itemsByOrder.push(itemKey);
        }

        (items[targetKey] || (items[targetKey] = target))[depsType].push(itemKey);
    },

    remove: function(target, item) {
        target = this.items[target.buildKey()];
        var itemKey = item.buildKey();
        U.removeFromArray(target.shouldDeps, itemKey);
        U.removeFromArray(target.mustDeps, itemKey);
    },

    clone: function(target) {
        target || (target = new this.__self());

        var items = this.items;
        for(var i in items) {
            if(!items.hasOwnProperty(i)) continue;
            target.items[i] = items[i].clone();
        }

        target.itemsByOrder = this.itemsByOrder.concat();
        target.tech = this.tech;
        target.uniqExpand = this.uniqExpand;

        return target;
    },

    parse: function(deps, ctx, fn) {
        fn || (fn = function(i) {
            this.add(this.rootItem, 'shouldDeps', i);
        });

        var _this = this,

            forEachItem = function(type, items, ctx) {
                items && !U.isEmptyObject(items) && (Array.isArray(items) ? items : [items]).forEach(function(item) {

                    if(isSimple(item)) {
                        var i = item;
                        (item = {})[type] = i;
                    }
                    item.name && (item[type] = item.name);

                    var depsItem = new DepsItem(item, ctx);

                    fn.call(_this, depsItem); // _this.add(rootItem, 'shouldDeps', depsItem);

                    _this.parse(
                        item.mustDeps,
                        depsItem,
                        function(i) {
                            this.add(depsItem, 'mustDeps', i);
                        });

                    _this.parse(
                        item.shouldDeps,
                        depsItem,
                        function(i) {
                            this.add(depsItem, 'shouldDeps', i);
                        });

                    _this.parse(
                        item.noDeps,
                        depsItem,
                        function(i) {
                            this.remove(depsItem, i);
                        });

                    forEachItem('elem', item.elems, depsItem);

                    var mods = item.mods;
                    if(mods && !Array.isArray(mods)) { // Object
                        var modsArr = [];
                        for(var m in mods) {
                            if(!mods.hasOwnProperty(m)) continue;
                            modsArr.push({ mod: m });
                            var mod = { mod: m }, v = mods[m];
                            Array.isArray(v) ? (mod.vals = v) : (mod.val = v);
                            modsArr.push(mod);
                        }
                        mods = modsArr;
                    }
                    forEachItem('mod', mods, depsItem);

                    forEachItem('val', item.vals, depsItem);

                });
            };

        forEachItem('block', deps, ctx);

        return this;
    },

    expandByFS: function(tech) {
        this.tech = tech;

        var _this = this,
            depsCount1 = this.getCount(),
            depsCount2;

        return Q.all(tech.getContext().getLevels().map(function(level) {
            return level.scanFiles();
        }))
            .then(function() {
                return Q.when(_this.expandOnceByFS())
                    .then(function again(newDeps) {

                        depsCount2 = newDeps.getCount();
                        if(depsCount1 !== depsCount2) {
                            depsCount1 = depsCount2;
                            return Q.when(newDeps.expandOnceByFS(), again);
                        }

                        return newDeps.clone(_this);

                    });
            });
    },

    expandOnceByFS: function() {

        var newDeps = this.clone(),
            steps = this
                .filter(function(item) {
                    return !newDeps.uniqExpand.hasOwnProperty(item.buildKey());
                })
                .map(function(item) {
                    newDeps.uniqExpand[item.buildKey()] = true;
                    return function() {
                        return newDeps.expandItemByFS(item);
                    };
                });

        if (!steps.length) return Q.resolve(newDeps);

        var i = 0,
            step = function() {
                if (i === steps.length) return newDeps;

                return steps[i++]()
                    .then(step);
            };

        return step();

    },

    expandItemByFS: function(item) {
        var _this = this,
            tech = this.tech,
            levels = tech.getContext().getLevels(),
            files = [];

        levels.forEach(function(level) {
            files = files.concat(
                level.getFileByObjIfExists(item.item, tech)
                .map(function(file) {
                    return tech.readContent(file.absPath);
                }));
        });

        return Q.all(files)
            .then(function(contents) {
                var i = 0;
                contents.forEach(function(content) {
                    if (content) {
                        try {
                            _this.parse(VM.runInThisContext(content, files[i].absPath), item);
                        } catch(e) {
                            e.message = files[i].absPath + '\n' + e.message;
                            throw e;
                        }
                    }
                    i++;
                });
            });
    },

    subtract: function(deps) {
        var items1 = this.items,
            items2 = deps.items;

        for(var k in items2)
            if(k && items2.hasOwnProperty(k)) delete items1[k];
        return this;
    },

    intersect: function(deps) {
        var items1 = this.items,
            items2 = deps.items,
            newItems = {};

        for(var k in items2) {
            if((items2.hasOwnProperty(k) && items1.hasOwnProperty(k)) || !k)
                newItems[k] = items1[k];
        }

        this.items = newItems;

        return this;
    },

    getCount: function() {
        var res = 0,
            items = this.items;

        for(var k in items) items.hasOwnProperty(k) && res++;

        return res;
    },

    forEach: function(fn, uniq, itemsByOrder, ctx) {
        uniq || (uniq = {});
        var _this = this;

        (itemsByOrder || this.items[''].shouldDeps).forEach(function(i) {
            if(i = _this.items[i]) {
                var key = i.buildKey(),
                    ctxTech = ctx && ctx.item.tech || '';
                if(!uniq.hasOwnProperty(key) || !uniq[key].hasOwnProperty(ctxTech)) {
                    (uniq[key] || (uniq[key] = {}))[ctxTech] = true;
                    var newCtx = ctx || i;
                    _this.forEach(fn, uniq, i.mustDeps, newCtx);
                    fn.call(_this, i, newCtx);
                    _this.forEach(fn, uniq, i.shouldDeps, newCtx);
                }
            }
        });
    },

    map: function(fn) {
        var res = [];
        this.forEach(function(item) {
            res.push(fn.call(this, item));
        });
        return res;
    },

    filter: function(fn) {
        var res = [];
        this.forEach(function(item) {
            if (fn.call(this, item)) res.push(item);
        });
        return res;
    },

    serialize: function() {
        var byTech = {};
        this.forEach(function(item, ctx) {
            var t1 = ctx.item.tech || '',
                t2 = item.item.tech || '',
                techsByTech = byTech[t1] || (byTech[t1] = {}),
                i = item.serialize();
            i && (techsByTech[t2] || (techsByTech[t2] = [])).push(i);
        });
        return byTech;
    },

    stringify: function() {
        var res = [],
            deps = this.serialize();

        if(deps['']) {
            res.push('exports.deps = ' + JSON.stringify(deps[''][''], null, 4) + ';\n');
            delete deps[''][''];
        } else {
            res.push('exports.deps = [];\n');
        }

        U.isEmptyObject(deps) || res.push('exports.depsByTechs = ' + JSON.stringify(deps, null, 4) + ';\n');

        return res.join('');
    }

});

var DepsItem = exports.DepsItem = INHERIT({

    __constructor: function(item, ctx) {
        this.shouldDeps = [];
        this.mustDeps = [];
        this.item = {};
        this.extendByCtx({ item: item });
        this.extendByCtx(ctx);
    },

    extendByCtx: function(ctx) {
        if(ctx && (ctx = ctx.item)) {
            var ks = ['tech', 'block', 'elem', 'mod', 'val'],
                k;

            while(k = ks.shift())
                if(this.item[k]) break;
                else ctx[k] && (this.item[k] = ctx[k]);
        }
        return this;
    },

    clone: function() {
        var res = new this.__self({}, this);
        res.shouldDeps = this.shouldDeps.concat();
        res.mustDeps = this.mustDeps.concat();
        this.hasOwnProperty('key') && (res.key = this.key);
        return res;
    },

    extend: function(item) {
        if(!item) return this;
        var ds = ['mustDeps', 'shouldDeps'], d,
            thisDeps, itemDeps;
        while(d = ds.shift()) {
            itemDeps = item[d] || (item[d] = {});
            if(thisDeps = this.item[d]) {
                for(var k in thisDeps)
                    if(thisDeps.hasOwnProperty(k)) {
                        if(!thisDeps[k].extend) throw 'bla';
                        (itemDeps[k] = thisDeps[k].extend(itemDeps[k]));
                    }
            }
        }
        return item;
    },

    cache: function(cache) {
        var key = this.buildKey();
        cache[key] = this.extend(cache[key]);
        return cache[key];
    },

    buildKey: function() {
        if('key' in this) return this.key;

        var i = this.item,
            k = '';

        if(i.block) {
            k += i.block;
            i.elem && (k += '__' + i.elem);
            if(i.mod) {
                k += '_' + i.mod;
                i.val && (k += '_' + i.val);
            }
        }
        i.tech && (k += '.' + i.tech);
        this.key = k;
        return k;
    },

    buildLevelPath: function(level) {
        return level.getByObj(this.item);
    },

    serialize: function() {
        var res = {},
            ks = ['tech', 'block', 'elem', 'mod', 'val'], k;

        while(k = ks.shift()) this.item[k] && (res[k] = this.item[k]);
        if(res.block) return res;
    }

});

function isSimple(o) {
    var t = typeof o;
    return t === 'string' || t === 'number';
}
<commit_msg>Fix levels cache<commit_after>'use strict';

var VM = require('vm'),
    Q = require('q'),
    QFS = require('q-fs'),
    FS = require('fs'),
    PATH = require('../../path'),
    U = require('../../util'),
    INHERIT = require('inherit'),
    LOGGER = require('../../logger');

exports.API_VER = 2;

exports.techMixin = {

    getTechName: function() {
        return 'deps.js';
    },

    getBuildSuffixesMap: function() {
        return {
            'deps.js': 'deps.js'
        };
    },

    readContentSync: function(path) {
        if (!PATH.existsSync(path)) return '';
        return FS.readFileSync(path, 'utf8');
    },

    readContent: function(path) {
        return U.readFile(path)
            .fail(function() {
                return '';
            });
    },

    getCreateResult: function() {

        return [
            '({',
            '    mustDeps: [],',
            '    shouldDeps: []',
            '})'
        ].join('\n');

    },

    transformBuildDecl: function(decl) {

        return this.expandDeps(decl)
            .then(function(deps) {

                var d = deps.serialize(),
                    o = {};

                if(d['']) {
                    o.deps = d[''][''];
                    delete d[''][''];
                }
                U.isEmptyObject(d) || (o.depsByTechs = d);

                return o;

            });

    },

    expandDeps: function(decl) {

        var _this = this;

        return Q.when(decl)
            .then(function(decl) {
                return new Deps(decl.blocks || decl.deps).expandByFS(_this);
            });

    },

    buildByDecl: function(decl, levels, output, opts) {

        var suffix = this.getTechName(),
            file = this.getPath(PATH.resolve(output), suffix),
            _this = this;

        return Q.all(levels
            .map(function(level) {
                return level.scanFiles();
            }))
            .then(function(){

                var declLastModified = -1;
                if (opts.declarationPath) declLastModified =
                    QFS.lastModified(opts.declarationPath)
                        .fail(function() {
                            return -1;
                        });

                return Q.all([_this.getLastUsedData(file),
                              declLastModified,
                              QFS.lastModified(file)
                                  .fail(function() {
                                      return -1;
                                  })])

                    .spread(function(data, declLastUpdated, lastUpdated) {
                        if (!data || !data.levels ||
                            !~lastUpdated || !~declLastUpdated ||
                            declLastUpdated > lastUpdated) return false;

                        return !_this.checkLevelsChanged(levels, data, lastUpdated);
                    })
                    .then(function(valid) {
                        LOGGER.fverbose('%s is %s', file, valid?'valid':'not valid');
                        if (!valid)
                            return _this.storeBuildResult(
                                _this.getPath(output, suffix),
                                suffix,
                                _this.expandDeps(decl)
                                    .then(function(deps) {
                                        return deps.stringify();
                                    }))
                                .then(function() {
                                    return _this.saveLastUsedData(file,
                                        {
                                            levels: levels.map(function(level) {
                                                return {
                                                    dir: level.dir,
                                                    fileCount: (level.files.files['deps.js'] || []).length
                                                };
                                            })
                                        }
                                    );
                                });
                    });

            });

    },

    checkLevelsChanged: function(levels, cache, lastUpdated) {
        if (levels.count !== cache.levels.count) return true;

        if (levels.length !== cache.levels.length) {
            return true;
        }

        var i;
        for(i = 0; i < levels.length; i++) {
            if (levels[i].dir !== cache.levels[i].dir) {
                return true;
            }

            if (((levels[i].files.files['deps.js'] || [])).length !== cache.levels[i].fileCount) {
                return true;
            }
        }

        for(i = 0; i < levels.length; i++) {
            var level = levels[i],
                files = level.files.files['deps.js'] || [];

            for(var file in files) {
                if (files[file].lastUpdated > lastUpdated) return true;
            }
        }

        return false;
    }

};

var Deps = exports.Deps = INHERIT({

    __constructor: function(deps) {
        this.items = {};
        this.itemsByOrder = [];
        this.uniqExpand = {};

        // Force adding of root item to this.items
        var rootItem = this.rootItem = new DepsItem({});
        this.items[rootItem.buildKey()] = rootItem;

        deps && this.parse(deps);
    },

    add: function(target, depsType, item) {
        var items = this.items,
            targetKey = target.buildKey(),
            itemKey = item.buildKey();

        if(!items[itemKey]) {
            items[itemKey] = item;
            this.itemsByOrder.push(itemKey);
        }

        (items[targetKey] || (items[targetKey] = target))[depsType].push(itemKey);
    },

    remove: function(target, item) {
        target = this.items[target.buildKey()];
        var itemKey = item.buildKey();
        U.removeFromArray(target.shouldDeps, itemKey);
        U.removeFromArray(target.mustDeps, itemKey);
    },

    clone: function(target) {
        target || (target = new this.__self());

        var items = this.items;
        for(var i in items) {
            if(!items.hasOwnProperty(i)) continue;
            target.items[i] = items[i].clone();
        }

        target.itemsByOrder = this.itemsByOrder.concat();
        target.tech = this.tech;
        target.uniqExpand = this.uniqExpand;

        return target;
    },

    parse: function(deps, ctx, fn) {
        fn || (fn = function(i) {
            this.add(this.rootItem, 'shouldDeps', i);
        });

        var _this = this,

            forEachItem = function(type, items, ctx) {
                items && !U.isEmptyObject(items) && (Array.isArray(items) ? items : [items]).forEach(function(item) {

                    if(isSimple(item)) {
                        var i = item;
                        (item = {})[type] = i;
                    }
                    item.name && (item[type] = item.name);

                    var depsItem = new DepsItem(item, ctx);

                    fn.call(_this, depsItem); // _this.add(rootItem, 'shouldDeps', depsItem);

                    _this.parse(
                        item.mustDeps,
                        depsItem,
                        function(i) {
                            this.add(depsItem, 'mustDeps', i);
                        });

                    _this.parse(
                        item.shouldDeps,
                        depsItem,
                        function(i) {
                            this.add(depsItem, 'shouldDeps', i);
                        });

                    _this.parse(
                        item.noDeps,
                        depsItem,
                        function(i) {
                            this.remove(depsItem, i);
                        });

                    forEachItem('elem', item.elems, depsItem);

                    var mods = item.mods;
                    if(mods && !Array.isArray(mods)) { // Object
                        var modsArr = [];
                        for(var m in mods) {
                            if(!mods.hasOwnProperty(m)) continue;
                            modsArr.push({ mod: m });
                            var mod = { mod: m }, v = mods[m];
                            Array.isArray(v) ? (mod.vals = v) : (mod.val = v);
                            modsArr.push(mod);
                        }
                        mods = modsArr;
                    }
                    forEachItem('mod', mods, depsItem);

                    forEachItem('val', item.vals, depsItem);

                });
            };

        forEachItem('block', deps, ctx);

        return this;
    },

    expandByFS: function(tech) {
        this.tech = tech;

        var _this = this,
            depsCount1 = this.getCount(),
            depsCount2;

        return Q.all(tech.getContext().getLevels().map(function(level) {
            return level.scanFiles();
        }))
            .then(function() {
                return Q.when(_this.expandOnceByFS())
                    .then(function again(newDeps) {

                        depsCount2 = newDeps.getCount();
                        if(depsCount1 !== depsCount2) {
                            depsCount1 = depsCount2;
                            return Q.when(newDeps.expandOnceByFS(), again);
                        }

                        return newDeps.clone(_this);

                    });
            });
    },

    expandOnceByFS: function() {

        var newDeps = this.clone(),
            steps = this
                .filter(function(item) {
                    return !newDeps.uniqExpand.hasOwnProperty(item.buildKey());
                })
                .map(function(item) {
                    newDeps.uniqExpand[item.buildKey()] = true;
                    return function() {
                        return newDeps.expandItemByFS(item);
                    };
                });

        if (!steps.length) return Q.resolve(newDeps);

        var i = 0,
            step = function() {
                if (i === steps.length) return newDeps;

                return steps[i++]()
                    .then(step);
            };

        return step();

    },

    expandItemByFS: function(item) {
        var _this = this,
            tech = this.tech,
            levels = tech.getContext().getLevels(),
            files = [];

        levels.forEach(function(level) {
            files = files.concat(
                level.getFileByObjIfExists(item.item, tech)
                .map(function(file) {
                    return tech.readContent(file.absPath);
                }));
        });

        return Q.all(files)
            .then(function(contents) {
                var i = 0;
                contents.forEach(function(content) {
                    if (content) {
                        try {
                            _this.parse(VM.runInThisContext(content, files[i].absPath), item);
                        } catch(e) {
                            e.message = files[i].absPath + '\n' + e.message;
                            throw e;
                        }
                    }
                    i++;
                });
            });
    },

    subtract: function(deps) {
        var items1 = this.items,
            items2 = deps.items;

        for(var k in items2)
            if(k && items2.hasOwnProperty(k)) delete items1[k];
        return this;
    },

    intersect: function(deps) {
        var items1 = this.items,
            items2 = deps.items,
            newItems = {};

        for(var k in items2) {
            if((items2.hasOwnProperty(k) && items1.hasOwnProperty(k)) || !k)
                newItems[k] = items1[k];
        }

        this.items = newItems;

        return this;
    },

    getCount: function() {
        var res = 0,
            items = this.items;

        for(var k in items) items.hasOwnProperty(k) && res++;

        return res;
    },

    forEach: function(fn, uniq, itemsByOrder, ctx) {
        uniq || (uniq = {});
        var _this = this;

        (itemsByOrder || this.items[''].shouldDeps).forEach(function(i) {
            if(i = _this.items[i]) {
                var key = i.buildKey(),
                    ctxTech = ctx && ctx.item.tech || '';
                if(!uniq.hasOwnProperty(key) || !uniq[key].hasOwnProperty(ctxTech)) {
                    (uniq[key] || (uniq[key] = {}))[ctxTech] = true;
                    var newCtx = ctx || i;
                    _this.forEach(fn, uniq, i.mustDeps, newCtx);
                    fn.call(_this, i, newCtx);
                    _this.forEach(fn, uniq, i.shouldDeps, newCtx);
                }
            }
        });
    },

    map: function(fn) {
        var res = [];
        this.forEach(function(item) {
            res.push(fn.call(this, item));
        });
        return res;
    },

    filter: function(fn) {
        var res = [];
        this.forEach(function(item) {
            if (fn.call(this, item)) res.push(item);
        });
        return res;
    },

    serialize: function() {
        var byTech = {};
        this.forEach(function(item, ctx) {
            var t1 = ctx.item.tech || '',
                t2 = item.item.tech || '',
                techsByTech = byTech[t1] || (byTech[t1] = {}),
                i = item.serialize();
            i && (techsByTech[t2] || (techsByTech[t2] = [])).push(i);
        });
        return byTech;
    },

    stringify: function() {
        var res = [],
            deps = this.serialize();

        if(deps['']) {
            res.push('exports.deps = ' + JSON.stringify(deps[''][''], null, 4) + ';\n');
            delete deps[''][''];
        } else {
            res.push('exports.deps = [];\n');
        }

        U.isEmptyObject(deps) || res.push('exports.depsByTechs = ' + JSON.stringify(deps, null, 4) + ';\n');

        return res.join('');
    }

});

var DepsItem = exports.DepsItem = INHERIT({

    __constructor: function(item, ctx) {
        this.shouldDeps = [];
        this.mustDeps = [];
        this.item = {};
        this.extendByCtx({ item: item });
        this.extendByCtx(ctx);
    },

    extendByCtx: function(ctx) {
        if(ctx && (ctx = ctx.item)) {
            var ks = ['tech', 'block', 'elem', 'mod', 'val'],
                k;

            while(k = ks.shift())
                if(this.item[k]) break;
                else ctx[k] && (this.item[k] = ctx[k]);
        }
        return this;
    },

    clone: function() {
        var res = new this.__self({}, this);
        res.shouldDeps = this.shouldDeps.concat();
        res.mustDeps = this.mustDeps.concat();
        this.hasOwnProperty('key') && (res.key = this.key);
        return res;
    },

    extend: function(item) {
        if(!item) return this;
        var ds = ['mustDeps', 'shouldDeps'], d,
            thisDeps, itemDeps;
        while(d = ds.shift()) {
            itemDeps = item[d] || (item[d] = {});
            if(thisDeps = this.item[d]) {
                for(var k in thisDeps)
                    if(thisDeps.hasOwnProperty(k)) {
                        if(!thisDeps[k].extend) throw 'bla';
                        (itemDeps[k] = thisDeps[k].extend(itemDeps[k]));
                    }
            }
        }
        return item;
    },

    cache: function(cache) {
        var key = this.buildKey();
        cache[key] = this.extend(cache[key]);
        return cache[key];
    },

    buildKey: function() {
        if('key' in this) return this.key;

        var i = this.item,
            k = '';

        if(i.block) {
            k += i.block;
            i.elem && (k += '__' + i.elem);
            if(i.mod) {
                k += '_' + i.mod;
                i.val && (k += '_' + i.val);
            }
        }
        i.tech && (k += '.' + i.tech);
        this.key = k;
        return k;
    },

    buildLevelPath: function(level) {
        return level.getByObj(this.item);
    },

    serialize: function() {
        var res = {},
            ks = ['tech', 'block', 'elem', 'mod', 'val'], k;

        while(k = ks.shift()) this.item[k] && (res[k] = this.item[k]);
        if(res.block) return res;
    }

});

function isSimple(o) {
    var t = typeof o;
    return t === 'string' || t === 'number';
}
<|endoftext|>


<commit_before>"use strict";
const Socket = require("./common/floop");
const _ = require("lodash");
// const editorAction = require("./common/editor_action");
const CompositeDisposable = require('event-kit').CompositeDisposable;
const prefs = require("./common/userPref_model");

function TerminalManager () {
  // floobits term id to this.Term3 TermialView
  this.terms = {};

  this.term3_name_to_ids = {};

  this.term3 = null;
  this.username = null;
  this.floobits_terms = null;
}

TerminalManager.prototype.send_create_term = function (t) {
  if (!t.getForked()) {
    console.log("not sending create event for remotely driven terminal");
    return;
  }
  const d = t.getDimensions();
  const name = this.nameForTerm(t);
  this.term3_name_to_ids[name] = t.id;
  Socket.send_create_term({term_name: name, size: [d.cols, d.rows]});
};

TerminalManager.prototype.nameForTerm = function (term) {
  return `atom-${this.username}-${term.id}-${Math.floor(Math.random() * 10000)}`;
};

TerminalManager.prototype.on_floobits = function(username, floobits_terms) {
  this.username = username;
  this.floobits_terms = floobits_terms;

  if (!this.term3) {
    return;
  }
  this.start_();
};

TerminalManager.prototype.on_term3_service = function(term3) {
  this.term3 = term3;

  if (!this.floobits_terms || !this.username) {
    return;
  }
  this.start_();
};

TerminalManager.prototype.start_ = function () {
  const that = this;

  this.subs = new CompositeDisposable();

  const disposable = that.term3.onTerm(that.send_create_term.bind(that));

  this.subs.add(disposable);

  that.term3.getTerminals().forEach(that.send_create_term.bind(that));

  _.each(that.floobits_terms, function (t, termID) {
    const term = that.term3.newTerm(false, t.size[1], t.size[0], t.owner);
    that.terms[termID] = term;

    const subs = new CompositeDisposable();

    subs.add(term.onSTDIN(function (d) {
      Socket.send_term_stdin({'data': new Buffer(d).toString("base64"), id: termID});
    }));

    subs.add(term.onExit(function () {
      that.subs.remove(subs);
      subs.dispose();
      delete that.terms[termID];
    }));

    that.subs.add(subs);
  });

  Socket.onCREATE_TERM(function (req) {
    const name = req.term_name;
    const termID = req.id;

    const term3_ID = that.term3_name_to_ids[name];
    const term = _.find(that.term3.getTerminals(), function (t) {
      return t.id === term3_ID;
    });

    if (term) {
      that.terms[termID] = term;
    } else {
      that.terms[termID] = that.term3.newTerm(false);
      that.subs.add(that.terms[termID].onSTDIN(function (d) {
        Socket.send_term_stdin({'data': new Buffer(d).toString("base64"), id: termID});
      }));
      return;
    }

    const d = term.getDimensions();
    Socket.send_update_term({id: termID, size: [d.cols, d.rows]});

    const subs = new CompositeDisposable();

    subs.add(term.onSTDOUT(function (d) {
      Socket.send_term_stdout({data: new Buffer(d).toString("base64"), id: termID});
    }));

    subs.add(term.onResize(function (size) {
      Socket.send_update_term({id: termID, size: [size.cols, size.rows]});
    }));

    subs.add(term.onExit(function () {
      Socket.send_delete_term({id: termID});
      that.subs.remove(subs);
      subs.dispose();
      delete that.terms[termID];
    }));

    that.subs.add(subs);
  });

  Socket.onTERM_STDIN(function (data) {
    const term = that.terms[data.id];
    if (!term) {
      return;
    }

    if (prefs.isFollowing(data.username)) {
      term.focusPane();
    }

    let term_data = data.data;
    if (!term_data.length) {
      return;
    }

    term_data = new Buffer(term_data, "base64").toString("utf-8");

    // TODO: allow terminals to be unsafe
    if (true) {
      /*eslint-disable no-control-regex */
      term_data = term_data.replace(/[\x04\x07\n\r]/g, "");
      /*eslint-enable no-control-regex */
    }

    try {
      term.input(term_data);
    } catch (e) {
      console.warn(e);
    }
  });

  Socket.onDELETE_TERM(function (data) {
    const term = that.terms[data.id];

    if (!term) {
      return;
    }

    term.input("\r\n   *** TERMINAL CLOSED ***   \r\n");
  });

  Socket.onTERM_STDOUT(function (data) {
    const term = that.terms[data.id];

    if (!term) {
      return;
    }

    try {
      term.input(new Buffer(data.data, "base64").toString("utf-8"));
    } catch (e) {
      console.warn(e);
    }
  });

  Socket.onUPDATE_TERM(function (data) {
    const term = that.terms[data.id];

    if (!term) {
      return;
    }

    console.log("update term", data);
    if (data.size) {
      term.resize(data.size[0], data.size[1]);
    }
  });

  Socket.onSYNC(function (terms) {
    console.log("Attempting to sync...");
    _.each(terms, function (data, id) {
      const term = that.terms[id];

      if (!term) {
        return;
      }

      term.resize(data.cols, data.rows);
    });
  });
};

TerminalManager.prototype.stop = function(term3_disposed) {
  if (this.subs) {
    this.subs.dispose();
    this.subs = null;
  }
  // we may not get another term3...
  if (term3_disposed) {
    this.term3 = null;
  }
  this.floobits_terms = null;
  this.username = null;
};

module.exports = new TerminalManager();
<commit_msg>Fixes race condition when cleaning up Term3s<commit_after>"use strict";
const Socket = require("./common/floop");
const _ = require("lodash");
// const editorAction = require("./common/editor_action");
const CompositeDisposable = require('event-kit').CompositeDisposable;
const prefs = require("./common/userPref_model");

function TerminalManager () {
  // floobits term id to this.Term3 TermialView
  this.terms = {};

  this.term3_name_to_ids = {};

  this.term3 = null;
  this.username = null;
  this.floobits_terms = null;
}

TerminalManager.prototype.send_create_term = function (t) {
  if (!t.getForked()) {
    console.log("not sending create event for remotely driven terminal");
    return;
  }
  const d = t.getDimensions();
  const name = this.nameForTerm(t);
  this.term3_name_to_ids[name] = t.id;
  Socket.send_create_term({term_name: name, size: [d.cols, d.rows]});
};

TerminalManager.prototype.nameForTerm = function (term) {
  return `atom-${this.username}-${term.id}-${Math.floor(Math.random() * 10000)}`;
};

TerminalManager.prototype.on_floobits = function(username, floobits_terms) {
  this.username = username;
  this.floobits_terms = floobits_terms;

  if (!this.term3) {
    return;
  }
  this.start_();
};

TerminalManager.prototype.on_term3_service = function(term3) {
  this.term3 = term3;

  if (!this.floobits_terms || !this.username) {
    return;
  }
  this.start_();
};

TerminalManager.prototype.start_ = function () {
  const that = this;

  this.subs = new CompositeDisposable();

  const disposable = that.term3.onTerm(that.send_create_term.bind(that));

  this.subs.add(disposable);

  that.term3.getTerminals().forEach(that.send_create_term.bind(that));

  _.each(that.floobits_terms, function (t, termID) {
    const term = that.term3.newTerm(false, t.size[1], t.size[0], t.owner);
    that.terms[termID] = term;

    const subs = new CompositeDisposable();

    subs.add(term.onSTDIN(function (d) {
      Socket.send_term_stdin({'data': new Buffer(d).toString("base64"), id: termID});
    }));

    subs.add(term.onExit(function () {
      if (that.subs) {
        that.subs.remove(subs);
      }
      subs.dispose();
      delete that.terms[termID];
    }));

    that.subs.add(subs);
  });

  Socket.onCREATE_TERM(function (req) {
    const name = req.term_name;
    const termID = req.id;

    const term3_ID = that.term3_name_to_ids[name];
    const term = _.find(that.term3.getTerminals(), function (t) {
      return t.id === term3_ID;
    });

    if (term) {
      that.terms[termID] = term;
    } else {
      that.terms[termID] = that.term3.newTerm(false);
      that.subs.add(that.terms[termID].onSTDIN(function (d) {
        Socket.send_term_stdin({'data': new Buffer(d).toString("base64"), id: termID});
      }));
      return;
    }

    const d = term.getDimensions();
    Socket.send_update_term({id: termID, size: [d.cols, d.rows]});

    const subs = new CompositeDisposable();

    subs.add(term.onSTDOUT(function (d) {
      Socket.send_term_stdout({data: new Buffer(d).toString("base64"), id: termID});
    }));

    subs.add(term.onResize(function (size) {
      Socket.send_update_term({id: termID, size: [size.cols, size.rows]});
    }));

    subs.add(term.onExit(function () {
      Socket.send_delete_term({id: termID});
      if (that.subs) {
        that.subs.remove(subs);
      }
      subs.dispose();
      delete that.terms[termID];
    }));

    that.subs.add(subs);
  });

  Socket.onTERM_STDIN(function (data) {
    const term = that.terms[data.id];
    if (!term) {
      return;
    }

    if (prefs.isFollowing(data.username)) {
      term.focusPane();
    }

    let term_data = data.data;
    if (!term_data.length) {
      return;
    }

    term_data = new Buffer(term_data, "base64").toString("utf-8");

    // TODO: allow terminals to be unsafe
    if (true) {
      /*eslint-disable no-control-regex */
      term_data = term_data.replace(/[\x04\x07\n\r]/g, "");
      /*eslint-enable no-control-regex */
    }

    try {
      term.input(term_data);
    } catch (e) {
      console.warn(e);
    }
  });

  Socket.onDELETE_TERM(function (data) {
    const term = that.terms[data.id];

    if (!term) {
      return;
    }

    term.input("\r\n   *** TERMINAL CLOSED ***   \r\n");
  });

  Socket.onTERM_STDOUT(function (data) {
    const term = that.terms[data.id];

    if (!term) {
      return;
    }

    try {
      term.input(new Buffer(data.data, "base64").toString("utf-8"));
    } catch (e) {
      console.warn(e);
    }
  });

  Socket.onUPDATE_TERM(function (data) {
    const term = that.terms[data.id];

    if (!term) {
      return;
    }

    console.log("update term", data);
    if (data.size) {
      term.resize(data.size[0], data.size[1]);
    }
  });

  Socket.onSYNC(function (terms) {
    console.log("Attempting to sync...");
    _.each(terms, function (data, id) {
      const term = that.terms[id];

      if (!term) {
        return;
      }

      term.resize(data.cols, data.rows);
    });
  });
};

TerminalManager.prototype.stop = function(term3_disposed) {
  if (this.subs) {
    this.subs.dispose();
    this.subs = null;
  }
  // we may not get another term3...
  if (term3_disposed) {
    this.term3 = null;
  }
  this.floobits_terms = null;
  this.username = null;
};

module.exports = new TerminalManager();
<|endoftext|>


<commit_before>'use babel';

import { CompositeDisposable } from 'atom';
import * as babylon from 'babylon';

import FuzzyView from './token-navigation-fuzzy-view';
import { config } from './configuration';
import {
	sillyParse,
	isInToken,
	isAfterToken,
	isBeforeToken,
	parse,
	toBabelPoint,
	toAtomRange
} from './tokens-utils';

export default {

	subscriptions: null,
	config: config,
	ignoredTokens: config.ignoredTokens.default,
	ignoreCommas: config.ignoreCommas.default,
	fuzzyView: null,
	modalPanel: null,

	activate(state) {
		const editor = atom.workspace.getActiveTextEditor();

		this.fuzzyView = new FuzzyView();
		this.fuzzyView.initialize(this);
		this.fuzzyView.hide = this.hide.bind(this);
		this.fuzzyView.editor = editor;
		this.modalPanel = atom.workspace.addBottomPanel({
			item: this.fuzzyView,
			priority: 0,
			visible: false
		});

		atom.config.observe('token-navigation.ignoredTokens', value => {
			this.ignoredTokens = value;
		});
		atom.config.observe('token-navigation.ignoreCommas', value => {
			this.ignoreCommas = value;
		});
		this.subscriptions = new CompositeDisposable();
		this.subscriptions.add(atom.commands.add('atom-workspace', {
			'token-navigation:nextToken': () => this.multiNextToken(),
			'token-navigation:previousToken': () => this.multiPreviousToken(),
			'token-navigation:fuzzyToken': () => this.fuzzyToken()
		}));
	},

	deactivate() {
		this.disposables = [];
		this.subscriptions.dispose();

		this.renameView && this.renameView.destroy();
		this.renameView = null;

		this.modalPanel && this.modalPanel.destroy();
		this.modalPanel = null;
	},

	serialize() {
		return {};
	},

	shouldIgnore(token, selectedRange) {
		if (!token) {
			return true;
		}
		if (this.ignoredTokens.indexOf(token.type.label) > -1) {
			return true;
		} else if (this.ignoreCommas && token.type.label === ',') {
			return true;
		}

		const atomRange = toAtomRange(token.loc);
		return selectedRange.containsRange(atomRange);
	},

	fuzzyToken() {
		if (this.modalPanel.isVisible()) {
			const isFocused = this.fuzzyView.nameEditor.classList.contains('is-focused');
			isFocused ? this.hide() : this.show();
		} else {
			this.show();
		}
	},

	show() {
		const editor = atom.workspace.getActiveTextEditor();

		this.modalPanel.show();
		this.fuzzyView.nameEditor.focus();
	},

	hide() {
		const editor = atom.workspace.getActiveTextEditor();
		const view = atom.views.getView(editor);
		view.focus();
		this.modalPanel.hide()
	},

	nextNotIgnoredToken(tokens, index, selectedRange) {
		for (let i = index; i < tokens.length; i++) {
			const token = tokens[i];
			if (this.shouldIgnore(token, selectedRange)) {
				continue;
			}
			return token;
		}
	},

	previousNotIgnoredToken(tokens, index, selectedRange) {
		for (let i = index; i >= 0; i--) {
			const token = tokens[i];
			if (this.shouldIgnore(token, selectedRange)) {
				continue;
			}
			return token;
		}
	},

	multiNextToken() {
		const editor = atom.workspace.getActiveTextEditor();
		const buffer = editor.getBuffer();
		const text = buffer.getText();
		const tokens = parse(text);
		const cursors = editor.cursors;
		const selectedRanges = editor.getSelectedBufferRanges();
		const ranges = cursors.map((cursor, index) =>
			this.nextToken(editor, tokens, cursor, selectedRanges[index])
		).filter(x => x);
		if (!ranges.length) {
			return;
		}

		editor.setSelectedBufferRanges(ranges);
	},

	multiPreviousToken() {
		const editor = atom.workspace.getActiveTextEditor();
		const buffer = editor.getBuffer();
		const text = buffer.getText();
		const tokens = parse(text);
		const cursors = editor.cursors;
		const selectedRanges = editor.getSelectedBufferRanges();
		const ranges = cursors.map((cursor, index) =>
			this.previousToken(editor, tokens, cursor, selectedRanges[index])
		).filter(x => x);
		if (!ranges.length) {
			return;
		}

		editor.setSelectedBufferRanges(ranges);
	},

	nextToken(editor, tokens, cursor, selectedRange) {
		const { line, column } = toBabelPoint(cursor.getBufferPosition());
		const isInside = isInToken({ line, column });
		const isBefore = isBeforeToken({ line, column });
		const isAfter = isAfterToken({ line, column });

		for (let i = 0; i < tokens.length - 1; i++) {
			const token = tokens[i];
			const nextToken = tokens[i + 1];

			if (isInside(token)) {
				const nextValidToken = this.nextNotIgnoredToken(tokens, i, selectedRange);
				if (!nextValidToken) {
					return;
				}
				return toAtomRange(nextValidToken.loc);
			}

			if (isAfter(token) && isBefore(nextToken)) {
				const nextValidToken = this.nextNotIgnoredToken(tokens, i + 1, selectedRange);
				if (!nextValidToken) {
					return;
				}
				return toAtomRange(nextValidToken.loc);
			}
		}
	},

	previousToken(editor, tokens, cursor, selectedRange) {
		const { line, column } = toBabelPoint(cursor.getBufferPosition());
		const isInside = isInToken({ line, column });
		const isBefore = isBeforeToken({ line, column });
		const isAfter = isAfterToken({ line, column });

		for (let i = 1; i < tokens.length; i++) {
			const previousToken = tokens[i - 1];
			const token = tokens[i];
			const { start, end } = token.loc;

			if (isInside(token)) {
				const previousValidToken = this.previousNotIgnoredToken(tokens, i, selectedRange);
				if (!previousValidToken) {
					return;
				}
				return toAtomRange(previousValidToken.loc);
			}

			if (isBefore(token) && isAfter(previousToken)) {
				const previousValidToken = this.previousNotIgnoredToken(tokens, i - 1, selectedRange);
				if (!previousValidToken) {
					return;
				}
				return toAtomRange(previousValidToken.loc);
			}
		}
	},

};
<commit_msg>fix fuzzy token search<commit_after>'use babel';

import { CompositeDisposable } from 'atom';
import * as babylon from 'babylon';

import FuzzyView from './token-navigation-fuzzy-view';
import { config } from './configuration';
import {
	sillyParse,
	isInToken,
	isAfterToken,
	isBeforeToken,
	parse,
	toBabelPoint,
	toAtomRange
} from './tokens-utils';

export default {

	subscriptions: null,
	config: config,
	ignoredTokens: config.ignoredTokens.default,
	ignoreCommas: config.ignoreCommas.default,
	fuzzyView: null,
	modalPanel: null,

	activate(state) {
		const editor = atom.workspace.getActiveTextEditor();

		this.fuzzyView = new FuzzyView();
		this.fuzzyView.initialize(this);
		this.fuzzyView.hide = this.hide.bind(this);
		this.fuzzyView.editor = editor;
		this.modalPanel = atom.workspace.addBottomPanel({
			item: this.fuzzyView,
			priority: 0,
			visible: false
		});

		atom.config.observe('token-navigation.ignoredTokens', value => {
			this.ignoredTokens = value;
		});
		atom.config.observe('token-navigation.ignoreCommas', value => {
			this.ignoreCommas = value;
		});
		this.subscriptions = new CompositeDisposable();
		this.subscriptions.add(atom.commands.add('atom-workspace', {
			'token-navigation:nextToken': () => this.multiNextToken(),
			'token-navigation:previousToken': () => this.multiPreviousToken(),
			'token-navigation:fuzzyToken': () => this.fuzzyToken()
		}));
	},

	deactivate() {
		this.disposables = [];
		this.subscriptions.dispose();

		this.renameView && this.renameView.destroy();
		this.renameView = null;

		this.modalPanel && this.modalPanel.destroy();
		this.modalPanel = null;
	},

	serialize() {
		return {};
	},

	shouldIgnore(token, selectedRange) {
		if (!token) {
			return true;
		}
		if (this.ignoredTokens.indexOf(token.type.label) > -1) {
			return true;
		} else if (this.ignoreCommas && token.type.label === ',') {
			return true;
		}

		const atomRange = toAtomRange(token.loc);
		return selectedRange.containsRange(atomRange);
	},

	fuzzyToken() {
		if (this.modalPanel.isVisible()) {
			const isFocused = this.fuzzyView.nameEditor.classList.contains('is-focused');
			isFocused ? this.hide() : this.show();
		} else {
			this.show();
		}
	},

	show() {
		const editor = atom.workspace.getActiveTextEditor();
		this.fuzzyView.nameEditor.getModel().getBuffer().setText('');

		this.modalPanel.show();
		this.fuzzyView.nameEditor.focus();
	},

	hide() {
		const editor = atom.workspace.getActiveTextEditor();
		const view = atom.views.getView(editor);
		view.focus();
		this.modalPanel.hide()
	},

	nextNotIgnoredToken(tokens, index, selectedRange) {
		for (let i = index; i < tokens.length; i++) {
			const token = tokens[i];
			if (this.shouldIgnore(token, selectedRange)) {
				continue;
			}
			return token;
		}
	},

	previousNotIgnoredToken(tokens, index, selectedRange) {
		for (let i = index; i >= 0; i--) {
			const token = tokens[i];
			if (this.shouldIgnore(token, selectedRange)) {
				continue;
			}
			return token;
		}
	},

	multiNextToken() {
		const editor = atom.workspace.getActiveTextEditor();
		const buffer = editor.getBuffer();
		const text = buffer.getText();
		const tokens = parse(text);
		const cursors = editor.cursors;
		const selectedRanges = editor.getSelectedBufferRanges();
		const ranges = cursors.map((cursor, index) =>
			this.nextToken(editor, tokens, cursor, selectedRanges[index])
		).filter(x => x);
		if (!ranges.length) {
			return;
		}

		editor.setSelectedBufferRanges(ranges);
	},

	multiPreviousToken() {
		const editor = atom.workspace.getActiveTextEditor();
		const buffer = editor.getBuffer();
		const text = buffer.getText();
		const tokens = parse(text);
		const cursors = editor.cursors;
		const selectedRanges = editor.getSelectedBufferRanges();
		const ranges = cursors.map((cursor, index) =>
			this.previousToken(editor, tokens, cursor, selectedRanges[index])
		).filter(x => x);
		if (!ranges.length) {
			return;
		}

		editor.setSelectedBufferRanges(ranges);
	},

	nextToken(editor, tokens, cursor, selectedRange) {
		const { line, column } = toBabelPoint(cursor.getBufferPosition());
		const isInside = isInToken({ line, column });
		const isBefore = isBeforeToken({ line, column });
		const isAfter = isAfterToken({ line, column });

		for (let i = 0; i < tokens.length - 1; i++) {
			const token = tokens[i];
			const nextToken = tokens[i + 1];

			if (isInside(token)) {
				const nextValidToken = this.nextNotIgnoredToken(tokens, i, selectedRange);
				if (!nextValidToken) {
					return;
				}
				return toAtomRange(nextValidToken.loc);
			}

			if (isAfter(token) && isBefore(nextToken)) {
				const nextValidToken = this.nextNotIgnoredToken(tokens, i + 1, selectedRange);
				if (!nextValidToken) {
					return;
				}
				return toAtomRange(nextValidToken.loc);
			}
		}
	},

	previousToken(editor, tokens, cursor, selectedRange) {
		const { line, column } = toBabelPoint(cursor.getBufferPosition());
		const isInside = isInToken({ line, column });
		const isBefore = isBeforeToken({ line, column });
		const isAfter = isAfterToken({ line, column });

		for (let i = 1; i < tokens.length; i++) {
			const previousToken = tokens[i - 1];
			const token = tokens[i];
			const { start, end } = token.loc;

			if (isInside(token)) {
				const previousValidToken = this.previousNotIgnoredToken(tokens, i, selectedRange);
				if (!previousValidToken) {
					return;
				}
				return toAtomRange(previousValidToken.loc);
			}

			if (isBefore(token) && isAfter(previousToken)) {
				const previousValidToken = this.previousNotIgnoredToken(tokens, i - 1, selectedRange);
				if (!previousValidToken) {
					return;
				}
				return toAtomRange(previousValidToken.loc);
			}
		}
	},

};
<|endoftext|>


<commit_before>function acquireEvent(expect, subject, channel, responseCount) {
    return expect.promise(function (resolve, reject) {
        var listenerTimeout;
        var seenEvents = [];

        function unexpectedEventListener() {
            responseCount -= 1;

            seenEvents.push(Array.prototype.slice.call(arguments, 0));

            if (responseCount === 0) {
                if (listenerTimeout) clearTimeout(listenerTimeout);

                // unhook event on success
                subject.removeListener(channel, unexpectedEventListener);

                resolve([seenEvents]);
            }
        }

        listenerTimeout = setTimeout(function () {
            subject.removeListener(channel, unexpectedEventListener);

            reject(new Error('Expected event not seen prior to timeout.'));
        }, 1950);

        subject.on(channel, unexpectedEventListener);
    });
}

function assertEvent(expect, subject, channel, responseCount) {
    return acquireEvent(expect, subject, channel, responseCount).spread(function (seenEvents) {
        // single event assertions check the *last* seen event
        var eventValues = seenEvents.pop();

        // if the values flag was set do a varargs comparison
        if (expect.flags.values) {
            return assertEventUsingVarargs(expect, eventValues);
        }

        return expect.promise(function (resolve, reject) {
            var suppliedArgs = expect.args;
            var suppliedValue = suppliedArgs[suppliedArgs.length - 1];

            if (!validateArrayArgument(suppliedArgs, suppliedValue)) {
                reject(new Error('Value supplied was not event values array.'));
            }

            resolve(expect.shift(eventValues));
        });
    });
}

function assertEventUsingVarargs(expect, eventValues) {
    return expect.promise(function (resolve, reject) {
        var suppliedArgs = expect.args;
        var combinationsOfArgs = [];
        var tmp;

        do {
            tmp = suppliedArgs.slice(0, 3);
            combinationsOfArgs.push(tmp);
            suppliedArgs.splice(2, 1);
        } while (suppliedArgs.length > 2);

        if (eventValues.length > combinationsOfArgs.length) {
            reject(new Error('Message was longer than expected definition.'));
        } else if (combinationsOfArgs.length > eventValues.length) {
            reject(new Error('Message was shorter than expected definition.'));
        }

        var argsPromises = combinationsOfArgs.map(function (args) {
            // take an individual value to assert
            var eventValue = eventValues.shift();

            return expect.promise(function () {
                expect.args = args;

                // execute the requested assertion against the value
                return expect.shift(eventValue);
            });
        });

        return expect.promise.all(argsPromises).caught(reject).then(resolve);
    });
}

function assertMultipleEvents(expect, subject, channel) {
    return expect.promise(function (resolve, reject) {
        var suppliedArgs = expect.args;
        var expectedEvents = suppliedArgs[suppliedArgs.length - 1];

        if (!validateArrayArgument(suppliedArgs, expectedEvents)) {
            return reject(new Error('An array of events is expected.'));
        }

        if (!validateEventValues(expectedEvents)) {
            return reject(new Error('Each event must be specified as an array.'));
        }

        var responseCount = expectedEvents.length;

        resolve(acquireEvent(expect, subject, channel, responseCount));
    }).spread(function (seenEvents) {
        return expect.promise(function () {
            return expect.shift(seenEvents);
        });
    });
}

function isArray(x) {
    return (Object.prototype.toString.call(x) === '[object Array]');
}

function isFunction(x) {
    return (typeof x === 'function');
}

function validateArrayArgument(suppliedArgs, suppliedValue) {
    return (suppliedArgs.length == 3 && isArray(suppliedValue));
}

function validateEventValues(expectedEvents) {
    var notArrays = expectedEvents.filter(function (x) { return !isArray(x); });

    return (notArrays.length === 0);
}

module.exports = {
    name: 'unexpected-events',
    version: require('../package.json').version,
    installInto: function (expect) {
        expect.addType({
            base: 'any',
            name: 'EventEmitter',
            identify: function (obj) {
                return (typeof obj === 'object' && obj !== null && isFunction(obj.emit) && isFunction(obj.on));
            }
        });

        expect.addAssertion('<EventEmitter> for the (|first) event [values] on <string> <assertion>', function (expect, subject, channel) {
            expect.errorMode = 'nested';

            return assertEvent(expect, subject, channel, 1);
        });

        expect.addAssertion('<EventEmitter> for the second event [values] on <string> <assertion>', function (expect, subject, channel) {
            expect.errorMode = 'nested';

            return assertEvent(expect, subject, channel, 2);
        });

        expect.addAssertion('<EventEmitter> for the third event [values] on <string> <assertion>', function (expect, subject, channel) {
            expect.errorMode = 'nested';

            return assertEvent(expect, subject, channel, 3);
        });

        expect.addAssertion('<EventEmitter> for multiple events on <string> <assertion>', function (expect, subject, channel) {
            expect.errorMode = 'nested';

            return assertMultipleEvents(expect, subject, channel);
        });
    }
};
<commit_msg>Identify input to the event assertions as an EventEmitter.<commit_after>function acquireEvent(expect, subject, channel, responseCount) {
    return expect.promise(function (resolve, reject) {
        var listenerTimeout;
        var seenEvents = [];

        function unexpectedEventListener() {
            responseCount -= 1;

            seenEvents.push(Array.prototype.slice.call(arguments, 0));

            if (responseCount === 0) {
                if (listenerTimeout) clearTimeout(listenerTimeout);

                // unhook event on success
                subject.removeListener(channel, unexpectedEventListener);

                resolve([seenEvents]);
            }
        }

        listenerTimeout = setTimeout(function () {
            subject.removeListener(channel, unexpectedEventListener);

            reject(new Error('Expected event not seen prior to timeout.'));
        }, 1950);

        subject.on(channel, unexpectedEventListener);
    });
}

function assertEvent(expect, subject, channel, responseCount) {
    return acquireEvent(expect, subject, channel, responseCount).spread(function (seenEvents) {
        // single event assertions check the *last* seen event
        var eventValues = seenEvents.pop();

        // if the values flag was set do a varargs comparison
        if (expect.flags.values) {
            return assertEventUsingVarargs(expect, eventValues);
        }

        return expect.promise(function (resolve, reject) {
            var suppliedArgs = expect.args;
            var suppliedValue = suppliedArgs[suppliedArgs.length - 1];

            if (!validateArrayArgument(suppliedArgs, suppliedValue)) {
                reject(new Error('Value supplied was not event values array.'));
            }

            resolve(expect.shift(eventValues));
        });
    });
}

function assertEventUsingVarargs(expect, eventValues) {
    return expect.promise(function (resolve, reject) {
        var suppliedArgs = expect.args;
        var combinationsOfArgs = [];
        var tmp;

        do {
            tmp = suppliedArgs.slice(0, 3);
            combinationsOfArgs.push(tmp);
            suppliedArgs.splice(2, 1);
        } while (suppliedArgs.length > 2);

        if (eventValues.length > combinationsOfArgs.length) {
            reject(new Error('Message was longer than expected definition.'));
        } else if (combinationsOfArgs.length > eventValues.length) {
            reject(new Error('Message was shorter than expected definition.'));
        }

        var argsPromises = combinationsOfArgs.map(function (args) {
            // take an individual value to assert
            var eventValue = eventValues.shift();

            return expect.promise(function () {
                expect.args = args;

                // execute the requested assertion against the value
                return expect.shift(eventValue);
            });
        });

        return expect.promise.all(argsPromises).caught(reject).then(resolve);
    });
}

function assertMultipleEvents(expect, subject, channel) {
    return expect.promise(function (resolve, reject) {
        var suppliedArgs = expect.args;
        var expectedEvents = suppliedArgs[suppliedArgs.length - 1];

        if (!validateArrayArgument(suppliedArgs, expectedEvents)) {
            return reject(new Error('An array of events is expected.'));
        }

        if (!validateEventValues(expectedEvents)) {
            return reject(new Error('Each event must be specified as an array.'));
        }

        var responseCount = expectedEvents.length;

        resolve(acquireEvent(expect, subject, channel, responseCount));
    }).spread(function (seenEvents) {
        return expect.promise(function () {
            return expect.shift(seenEvents);
        });
    });
}

function isArray(x) {
    return (Object.prototype.toString.call(x) === '[object Array]');
}

function isFunction(x) {
    return (typeof x === 'function');
}

function validateArrayArgument(suppliedArgs, suppliedValue) {
    return (suppliedArgs.length == 3 && isArray(suppliedValue));
}

function validateEventValues(expectedEvents) {
    var notArrays = expectedEvents.filter(function (x) { return !isArray(x); });

    return (notArrays.length === 0);
}

module.exports = {
    name: 'unexpected-events',
    version: require('../package.json').version,
    installInto: function (expect) {
        expect.addType({
            base: 'any',
            name: 'EventEmitter',
            identify: function (obj) {
                return (typeof obj === 'object' && obj !== null && isFunction(obj.emit) && isFunction(obj.on));
            },
            inspect: function (subject, depth, output, inspect) {
                output.text('EventEmitter');
            }
        });

        expect.addAssertion('<EventEmitter> for the (|first) event [values] on <string> <assertion>', function (expect, subject, channel) {
            expect.errorMode = 'nested';

            return assertEvent(expect, subject, channel, 1);
        });

        expect.addAssertion('<EventEmitter> for the second event [values] on <string> <assertion>', function (expect, subject, channel) {
            expect.errorMode = 'nested';

            return assertEvent(expect, subject, channel, 2);
        });

        expect.addAssertion('<EventEmitter> for the third event [values] on <string> <assertion>', function (expect, subject, channel) {
            expect.errorMode = 'nested';

            return assertEvent(expect, subject, channel, 3);
        });

        expect.addAssertion('<EventEmitter> for multiple events on <string> <assertion>', function (expect, subject, channel) {
            expect.errorMode = 'nested';

            return assertMultipleEvents(expect, subject, channel);
        });
    }
};
<|endoftext|>


<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const {classes: Cc, utils: Cu} = Components;

Cu.import('resource://gre/modules/XPCOMUtils.jsm');
XPCOMUtils.defineLazyModuleGetter(this, 'console',
  'resource://gre/modules/Console.jsm');
XPCOMUtils.defineLazyModuleGetter(this, 'CustomizableUI',
  'resource:///modules/CustomizableUI.jsm');
XPCOMUtils.defineLazyModuleGetter(this, 'ObjectUtils',
  'resource://gre/modules/ObjectUtils.jsm');
XPCOMUtils.defineLazyModuleGetter(this, 'PrivateBrowsingUtils',
  'resource://gre/modules/PrivateBrowsingUtils.jsm');
XPCOMUtils.defineLazyModuleGetter(this, 'Services',
  'resource://gre/modules/Services.jsm');
XPCOMUtils.defineLazyModuleGetter(this, 'WindowWatcher',
  'chrome://universalsearch-lib/content/window-watcher.js');

const EXPORTED_SYMBOLS = ['UniversalSearch'];

function Search() {
  /*
  The main UniversalSearch module manages the add-on lifecycle, loading and
  unloading code when the add-on is installed, enabled, disabled, or
  uninstalled. It's invoked by the bootstrap.js file.
  */

  // Due to limitations in passing objects between window contexts, we have to
  // bind the callbacks passed to WindowWatcher.start().
  this.loadIntoWindow = this.loadIntoWindow.bind(this);
  this.unloadFromWindow = this.unloadFromWindow.bind(this);
  this.onError = this.onError.bind(this);
}

Search.prototype = {
  load: function() {
    WindowWatcher.start(this.loadIntoWindow, this.unloadFromWindow, this.onError);
  },

  unload: function() {
    WindowWatcher.stop();
  },

  loadIntoWindow: function(win) {
    console.log('loadIntoWindow start');

    // Sets the app global per window.
    win.universalSearch = win.universalSearch || {}

    win.universalSearch.prefs = this._initializePrefs();
    win.universalSearch.urlbarPrefs = this._initializeUrlbarPrefs();
    this._hideSearchBar(win);
    this._loadStyleSheets(win);
    this._loadScripts(win);
    this._startApp(win);

    console.log('loadIntoWindow finish');
  },

  unloadFromWindow: function(win) {
    console.log('unloadFromWindow start');

    this._stopApp(win);
    this._unloadScripts(win);
    this._unloadStyleSheets(win);
    this._restoreSearchBar(win);

    // Delete any remaining references.
    delete win.universalSearch;

    console.log('unloadFromWindow finish');
  },

  _initializePrefs: function() {
    return Services.prefs.getBranch('extensions.universalsearch.');
  },

  _initializeUrlbarPrefs: function() {
    return Services.prefs.getBranch('browser.urlbar.');
  },

  _hideSearchBar: function(win) {
    // If the search bar is visible, note the location and hide it.
    const searchBarLocation = CustomizableUI.getPlacementOfWidget('search-container');
    if (searchBarLocation) {
      win.universalSearch.searchBarLocation = searchBarLocation;
      CustomizableUI.removeWidgetFromArea('search-container');
    }
  },

  _restoreSearchBar: function(win) {
    // If the search bar was originally visible, restore it.
    if (win.universalSearch.searchBarLocation) {
      const loc = win.universalSearch.searchBarLocation;
      CustomizableUI.addWidgetToArea('search-container', loc.area, loc.position);
    }
  },

  _loadStyleSheets: function(win) {
    const doc = win.document;
    const docEl = doc.documentElement;

    // Insert the main stylesheet with platform-independent styles.
    const stylesheet = doc.createElementNS('http://www.w3.org/1999/xhtml', 'h:link');
    stylesheet.href = 'chrome://universalsearch-skin/content/style.css';
    stylesheet.rel = 'stylesheet';
    stylesheet.type = 'text/css';
    stylesheet.id = 'universal-search-stylesheet';
    stylesheet.style.display = 'none';

    // Insert platform-specific styles, using `Services.appinfo.OS` to
    // distinguish between Mac OS ('Darwin'), Linux, and Windows ('WINNT').
    const stylesheetPlatform = doc.createElementNS('http://www.w3.org/1999/xhtml', 'h:link');
    stylesheetPlatform.rel = 'stylesheet';
    stylesheetPlatform.type = 'text/css';
    stylesheetPlatform.id = 'universal-search-stylesheet-platform';
    stylesheetPlatform.style.display = 'none';

    if (Services.appinfo.OS === 'Darwin') {
      stylesheetPlatform.href = 'chrome://universalsearch-skin/content/style-osx.css';
    } else if (Services.appinfo.OS === 'WINNT') {
      stylesheetPlatform.href = 'chrome://universalsearch-skin/content/style-windows.css';
    } else {
      stylesheetPlatform.href = 'chrome://universalsearch-skin/content/style-linux.css';
    }

    docEl.appendChild(stylesheet);
    docEl.appendChild(stylesheetPlatform);
  },

  _unloadStyleSheets: function(win) {
    const docEl = win.document.documentElement;

    const stylesheet = docEl.getElementById('universal-search-stylesheet');
    const stylesheetPlatform = docEl.getElementById('universal-search-stylesheet-platform');

    docEl.removeChild(stylesheet);
    docEl.removeChild(stylesheetPlatform);
  },

  _loadScripts: function(win) {
    // Note that we load the scripts into the app namespace, to ease cleanup.
    // Each module's EXPORTED_SYMBOLS will be properties on win.universalSearch.
    Cu.import('resource://gre/modules/Console.jsm', win.universalSearch);
    Cu.import('resource://gre/modules/Services.jsm', win.universalSearch);

    Cu.import('chrome://universalsearch-lib/content/events.js', win.universalSearch);
    Cu.import('chrome://universalsearch-lib/content/recommendation-server.js', win.universalSearch);

    Cu.import('chrome://universalsearch-ui/content/highlight-manager.js', win.universalSearch);
    Cu.import('chrome://universalsearch-ui/content/popup.js', win.universalSearch);
    Cu.import('chrome://universalsearch-ui/content/recommendation.js', win.universalSearch);
    Cu.import('chrome://universalsearch-ui/content/recommendation-row.js', win.universalSearch);
    Cu.import('chrome://universalsearch-ui/content/urlbar.js', win.universalSearch);
  },

  _unloadScripts: function(win) {
    // Unload scripts from the namespace. Not clear on whether this is necessary
    // if we just delete win.universalSearch.
    Cu.unload('chrome://universalsearch-ui/content/highlight-manager.js', win.universalSearch);
    Cu.unload('chrome://universalsearch-ui/content/popup.js', win.universalSearch);
    Cu.unload('chrome://universalsearch-ui/content/recommendation.js', win.universalSearch);
    Cu.unload('chrome://universalsearch-ui/content/recommendation-row.js', win.universalSearch);
    Cu.unload('chrome://universalsearch-ui/content/urlbar.js', win.universalSearch);

    Cu.unload('chrome://universalsearch-lib/content/recommendation-server.js', win.universalSearch);
    Cu.unload('chrome://universalsearch-lib/content/events.js', win.universalSearch);

    Cu.unload('resource://gre/modules/Console.jsm', win.universalSearch);
    Cu.unload('resource://gre/modules/Services.jsm', win.universalSearch);
  },

  _startApp: function(win) {
    // Initialize modules and assemble the object graph.

    const app = win.universalSearch;

    app.events = new app.Events();
    app.events.init();

    app.recommendationServer = new app.RecommendationServer({
      events: app.events,
      xhr: Cc['@mozilla.org/xmlextras/xmlhttprequest;1'],
      timeout: 1000,
      // app.console is provided by loading Console.jsm
      console: app.console,
      prefs: app.prefs,
      urlbarPrefs: app.urlbarPrefs
    });
    app.recommendationServer.init();

    // NOTE: order matters here, the urlbar and highlightManager both need
    // to be handed the initialized recommendation
    app.recommendation = new app.Recommendation({
      eTLD: Services.eTLD,
      events: app.events,
      io: Services.io,
      objectUtils: ObjectUtils,
      win: win,
      RecommendationRow: app.RecommendationRow
    });
    app.recommendation.init();

    app.urlbar = new app.Urlbar({
      win: win,
      events: app.events,
      privateBrowsingUtils: PrivateBrowsingUtils,
      recommendation: app.recommendation
    });
    app.urlbar.init();

    app.popup = new app.Popup({
      win: win,
      events: app.events
    });
    app.popup.init();

    app.highlightManager = new app.HighlightManager({
      win: win,
      events: app.events,
      recommendation: app.recommendation,
      popup: app.popup
    });
    app.highlightManager.init();
  },

  _stopApp: function(win) {
    const app = win.universalSearch;

    app.popup.destroy();
    app.recommendation.destroy();
    app.highlightManager.destroy();
    app.urlbar.destroy();
    app.recommendationServer.destroy();
    app.events.destroy();

    delete win.universalSearch;
  },

  onError: function(msg) {
    console.error(msg);
  }
};

// Expose a singleton, since we only need one of these for the add-on.
const UniversalSearch = new Search();
<commit_msg>Unload Console.jsm *after* the last console call<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const {classes: Cc, utils: Cu} = Components;

Cu.import('resource://gre/modules/XPCOMUtils.jsm');
XPCOMUtils.defineLazyModuleGetter(this, 'console',
  'resource://gre/modules/Console.jsm');
XPCOMUtils.defineLazyModuleGetter(this, 'CustomizableUI',
  'resource:///modules/CustomizableUI.jsm');
XPCOMUtils.defineLazyModuleGetter(this, 'ObjectUtils',
  'resource://gre/modules/ObjectUtils.jsm');
XPCOMUtils.defineLazyModuleGetter(this, 'PrivateBrowsingUtils',
  'resource://gre/modules/PrivateBrowsingUtils.jsm');
XPCOMUtils.defineLazyModuleGetter(this, 'Services',
  'resource://gre/modules/Services.jsm');
XPCOMUtils.defineLazyModuleGetter(this, 'WindowWatcher',
  'chrome://universalsearch-lib/content/window-watcher.js');

const EXPORTED_SYMBOLS = ['UniversalSearch'];

function Search() {
  /*
  The main UniversalSearch module manages the add-on lifecycle, loading and
  unloading code when the add-on is installed, enabled, disabled, or
  uninstalled. It's invoked by the bootstrap.js file.
  */

  // Due to limitations in passing objects between window contexts, we have to
  // bind the callbacks passed to WindowWatcher.start().
  this.loadIntoWindow = this.loadIntoWindow.bind(this);
  this.unloadFromWindow = this.unloadFromWindow.bind(this);
  this.onError = this.onError.bind(this);
}

Search.prototype = {
  load: function() {
    WindowWatcher.start(this.loadIntoWindow, this.unloadFromWindow, this.onError);
  },

  unload: function() {
    WindowWatcher.stop();
  },

  loadIntoWindow: function(win) {
    console.log('loadIntoWindow start');

    // Sets the app global per window.
    win.universalSearch = win.universalSearch || {}

    win.universalSearch.prefs = this._initializePrefs();
    win.universalSearch.urlbarPrefs = this._initializeUrlbarPrefs();
    this._hideSearchBar(win);
    this._loadStyleSheets(win);
    this._loadScripts(win);
    this._startApp(win);

    console.log('loadIntoWindow finish');
  },

  unloadFromWindow: function(win) {
    console.log('unloadFromWindow start');

    this._stopApp(win);
    this._unloadScripts(win);
    this._unloadStyleSheets(win);
    this._restoreSearchBar(win);

    // Delete any remaining references.
    delete win.universalSearch;

    console.log('unloadFromWindow finish');

    // We invoke console.log above, so we cannot safely unload Console.jsm
    // until this point.
    Cu.unload('resource://gre/modules/Console.jsm', win.universalSearch);
  },

  _initializePrefs: function() {
    return Services.prefs.getBranch('extensions.universalsearch.');
  },

  _initializeUrlbarPrefs: function() {
    return Services.prefs.getBranch('browser.urlbar.');
  },

  _hideSearchBar: function(win) {
    // If the search bar is visible, note the location and hide it.
    const searchBarLocation = CustomizableUI.getPlacementOfWidget('search-container');
    if (searchBarLocation) {
      win.universalSearch.searchBarLocation = searchBarLocation;
      CustomizableUI.removeWidgetFromArea('search-container');
    }
  },

  _restoreSearchBar: function(win) {
    // If the search bar was originally visible, restore it.
    if (win.universalSearch.searchBarLocation) {
      const loc = win.universalSearch.searchBarLocation;
      CustomizableUI.addWidgetToArea('search-container', loc.area, loc.position);
    }
  },

  _loadStyleSheets: function(win) {
    const doc = win.document;
    const docEl = doc.documentElement;

    // Insert the main stylesheet with platform-independent styles.
    const stylesheet = doc.createElementNS('http://www.w3.org/1999/xhtml', 'h:link');
    stylesheet.href = 'chrome://universalsearch-skin/content/style.css';
    stylesheet.rel = 'stylesheet';
    stylesheet.type = 'text/css';
    stylesheet.id = 'universal-search-stylesheet';
    stylesheet.style.display = 'none';

    // Insert platform-specific styles, using `Services.appinfo.OS` to
    // distinguish between Mac OS ('Darwin'), Linux, and Windows ('WINNT').
    const stylesheetPlatform = doc.createElementNS('http://www.w3.org/1999/xhtml', 'h:link');
    stylesheetPlatform.rel = 'stylesheet';
    stylesheetPlatform.type = 'text/css';
    stylesheetPlatform.id = 'universal-search-stylesheet-platform';
    stylesheetPlatform.style.display = 'none';

    if (Services.appinfo.OS === 'Darwin') {
      stylesheetPlatform.href = 'chrome://universalsearch-skin/content/style-osx.css';
    } else if (Services.appinfo.OS === 'WINNT') {
      stylesheetPlatform.href = 'chrome://universalsearch-skin/content/style-windows.css';
    } else {
      stylesheetPlatform.href = 'chrome://universalsearch-skin/content/style-linux.css';
    }

    docEl.appendChild(stylesheet);
    docEl.appendChild(stylesheetPlatform);
  },

  _unloadStyleSheets: function(win) {
    const docEl = win.document.documentElement;

    const stylesheet = docEl.getElementById('universal-search-stylesheet');
    const stylesheetPlatform = docEl.getElementById('universal-search-stylesheet-platform');

    docEl.removeChild(stylesheet);
    docEl.removeChild(stylesheetPlatform);
  },

  _loadScripts: function(win) {
    // Note that we load the scripts into the app namespace, to ease cleanup.
    // Each module's EXPORTED_SYMBOLS will be properties on win.universalSearch.
    Cu.import('resource://gre/modules/Console.jsm', win.universalSearch);
    Cu.import('resource://gre/modules/Services.jsm', win.universalSearch);

    Cu.import('chrome://universalsearch-lib/content/events.js', win.universalSearch);
    Cu.import('chrome://universalsearch-lib/content/recommendation-server.js', win.universalSearch);

    Cu.import('chrome://universalsearch-ui/content/highlight-manager.js', win.universalSearch);
    Cu.import('chrome://universalsearch-ui/content/popup.js', win.universalSearch);
    Cu.import('chrome://universalsearch-ui/content/recommendation.js', win.universalSearch);
    Cu.import('chrome://universalsearch-ui/content/recommendation-row.js', win.universalSearch);
    Cu.import('chrome://universalsearch-ui/content/urlbar.js', win.universalSearch);
  },

  _unloadScripts: function(win) {
    // Unload scripts from the namespace. Not clear on whether this is necessary
    // if we just delete win.universalSearch.
    Cu.unload('chrome://universalsearch-ui/content/highlight-manager.js', win.universalSearch);
    Cu.unload('chrome://universalsearch-ui/content/popup.js', win.universalSearch);
    Cu.unload('chrome://universalsearch-ui/content/recommendation.js', win.universalSearch);
    Cu.unload('chrome://universalsearch-ui/content/recommendation-row.js', win.universalSearch);
    Cu.unload('chrome://universalsearch-ui/content/urlbar.js', win.universalSearch);

    Cu.unload('chrome://universalsearch-lib/content/recommendation-server.js', win.universalSearch);
    Cu.unload('chrome://universalsearch-lib/content/events.js', win.universalSearch);

    // NOTE: we can't unload Console.jsm until the last console call.
    // It's unloaded by the caller (the unloadFromWindow method).
  },

  _startApp: function(win) {
    // Initialize modules and assemble the object graph.

    const app = win.universalSearch;

    app.events = new app.Events();
    app.events.init();

    app.recommendationServer = new app.RecommendationServer({
      events: app.events,
      xhr: Cc['@mozilla.org/xmlextras/xmlhttprequest;1'],
      timeout: 1000,
      // app.console is provided by loading Console.jsm
      console: app.console,
      prefs: app.prefs,
      urlbarPrefs: app.urlbarPrefs
    });
    app.recommendationServer.init();

    // NOTE: order matters here, the urlbar and highlightManager both need
    // to be handed the initialized recommendation
    app.recommendation = new app.Recommendation({
      eTLD: Services.eTLD,
      events: app.events,
      io: Services.io,
      objectUtils: ObjectUtils,
      win: win,
      RecommendationRow: app.RecommendationRow
    });
    app.recommendation.init();

    app.urlbar = new app.Urlbar({
      win: win,
      events: app.events,
      privateBrowsingUtils: PrivateBrowsingUtils,
      recommendation: app.recommendation
    });
    app.urlbar.init();

    app.popup = new app.Popup({
      win: win,
      events: app.events
    });
    app.popup.init();

    app.highlightManager = new app.HighlightManager({
      win: win,
      events: app.events,
      recommendation: app.recommendation,
      popup: app.popup
    });
    app.highlightManager.init();
  },

  _stopApp: function(win) {
    const app = win.universalSearch;

    app.popup.destroy();
    app.recommendation.destroy();
    app.highlightManager.destroy();
    app.urlbar.destroy();
    app.recommendationServer.destroy();
    app.events.destroy();

    delete win.universalSearch;
  },

  onError: function(msg) {
    console.error(msg);
  }
};

// Expose a singleton, since we only need one of these for the add-on.
const UniversalSearch = new Search();
<|endoftext|>


<commit_before>app.directive('attachmentUpload',

function() {
	"use strict";

	return {
		restrict : 'E',
		replace : false,
		templateUrl : 'attachment/attachment.tpl.html',
		controller: 'AttachmentController',
		scope : {
			expenseItemUid : '='
		},
		link: function($scope, element){
			$scope.addImageToElement = function(base64Image){
				element.append("<image src="+base64Image+"><image>");
			};
		}
	};
});<commit_msg>remove unnecessary code fragment<commit_after>app.directive('attachmentUpload',

function() {
	"use strict";

	return {
		restrict : 'E',
		replace : false,
		templateUrl : 'attachment/attachment.tpl.html',
		controller: 'AttachmentController',
		scope : {
			expenseItemUid : '='
		}
	};
});<|endoftext|>


<commit_before>/* global google */

const gMaps = require('../../location/googleMaps')

let popup = null

const MapBuilder = function () {
  const self = this

  self.updateMarkers = (items) => {
    items
      .forEach((p, i) => {
        const infoWindow = gMaps.buildInfoWindow(self.buildInfoWindowMarkup(p))

        self.infoWindows.push(infoWindow)

        const marker = gMaps.buildMarker(self.getLocation(p), self.map)

        marker.customFields = {
          mapIndex: i
        }

        marker.addListener('click', function () {
          document.querySelectorAll('.card__gmaps-container')
            .forEach((p) => p.parentNode.removeChild(p))

          const location = self.getLocation(p)

          const position = new google.maps.LatLng(location.latitude, location.longitude)

          popup = new gMaps.Popup(
            location.latitude,
            location.longitude,
            self.buildInfoWindowMarkup(p))

          popup.setMap(self.map)
          self.map.setCenter(position)
          if (self.container) {
            self.container.markerClicked(this.customFields.mapIndex)
          }
        })

        self.markers.push(marker)
      })
  }

  self.infoWindows = []
  self.markers = []

  self.itemSelected = function (item) {
    const itemIndex = item.mapIndex()
    self.infoWindows[itemIndex].open(self.map, self.markers[itemIndex])
    self.infoWindows
      .filter((iw, i) => i !== itemIndex)
      .forEach((iw, i) => {
        iw.close()
      })
  }

  self.update = function (items) {
    self.infoWindows = []

    self.markers
      .forEach((m) => {
        m.setVisible(false)
        m.setMap(null)
      })

    self.markers = []

    self.updateMarkers(items)
  }

  self.init = function (items, userLocation, container, buildInfoWindowMarkup, getLocation = (p) => {
    return { latitude: p.latitude(), longitude: p.longitude() }
  }, customOptions = {}) {
    self.map = gMaps.buildMap(userLocation, customOptions)

    self.container = container
    self.buildInfoWindowMarkup = buildInfoWindowMarkup
    self.getLocation = getLocation

    self.markers
      .forEach((m) => m.setMap(null))

    self.infoWindows = []
    self.markers = []

    self.updateMarkers(items)
  }
}

module.exports = MapBuilder
<commit_msg>fix maps<commit_after>/* global google */

const gMaps = require('../../location/googleMaps')

let popup = null

const MapBuilder = function () {
  const self = this

  self.updateMarkers = (items) => {
    items
      .forEach((p, i) => {
        const infoWindow = gMaps.buildInfoWindow(self.buildInfoWindowMarkup(p))

        self.infoWindows.push(infoWindow)

        const marker = gMaps.buildMarker(self.getLocation(p), self.map)

        marker.customFields = {
          mapIndex: i
        }

        marker.addListener('click', function () {
          document.querySelectorAll('.card__gmaps-container')
            .forEach((p) => p.parentNode.removeChild(p))

          const location = self.getLocation(p)

          const position = new google.maps.LatLng(location.latitude, location.longitude)

          popup = new gMaps.Popup(
            location.latitude,
            location.longitude,
            self.buildInfoWindowMarkup(p))

          popup.setMap(self.map)
          self.map.setCenter(position)
          if (self.container) {
            self.container.markerClicked(this.customFields.mapIndex)
          }
        })

        self.markers.push(marker)
      })
  }

  self.infoWindows = []
  self.markers = []

  self.itemSelected = function (item) {
    const itemIndex = item.mapIndex()
    self.infoWindows[itemIndex].open(self.map, self.markers[itemIndex])
    self.infoWindows
      .filter((iw, i) => i !== itemIndex)
      .forEach((iw, i) => {
        iw.close()
      })
  }

  self.update = function (items) {
    self.infoWindows = []

    self.markers
      .forEach((m) => {
        m.setVisible(false)
        m.setMap(null)
      })

    self.markers = []

    self.updateMarkers(items)
  }

  self.init = function (items, userLocation, container, buildInfoWindowMarkup, getLocation = (p) => {
    return { latitude: p.latitude(), longitude: p.longitude() }
  }, customOptions = {}) {

    self.map = gMaps.buildMap(userLocation, customOptions)

    self.container = container
    self.buildInfoWindowMarkup = buildInfoWindowMarkup
    self.getLocation = getLocation

    self.markers
      .forEach((m) => m.setMap(null))

    self.infoWindows = []
    self.markers = []

    self.updateMarkers(items)
  }
}

module.exports = MapBuilder
<|endoftext|>