/*******************************************************************************

    uMatrix - a browser extension to black/white list requests.
    Copyright (C) 2014-present Raymond Hill

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see {http://www.gnu.org/licenses/}.

    Home: https://github.com/gorhill/uMatrix
*/

/* globals publicSuffixList */

'use strict';

/******************************************************************************/
/******************************************************************************/

// Default handler

(function() {

var µm = µMatrix;

/******************************************************************************/

// Default is for commonly used message.

function onMessage(request, sender, callback) {
    // Async
    switch ( request.what ) {
    case 'getAssetContent':
        µm.assets.get(request.url, { dontCache: true }, callback);
        return;

    case 'selectAssets':
        µm.selectAssets(request, callback);
        return;

    default:
        break;
    }

    // Sync
    var response;

    switch ( request.what ) {
    case 'blacklistMatrixCell':
        µm.tMatrix.blacklistCell(
            request.srcHostname,
            request.desHostname,
            request.type
        );
        break;

    case 'forceReloadTab':
        µm.forceReload(request.tabId, request.bypassCache);
        break;

    case 'forceUpdateAssets':
        µm.scheduleAssetUpdater(0);
        µm.assets.updateStart({ delay: 2000 });
        break;

    case 'getCellColors':
        let ruleParts = request.ruleParts;
        let tColors = [];
        let pColors = [];
        for ( let i = 0, n = ruleParts.length; i < n; i += 3 ) {
            tColors.push(µm.tMatrix.evaluateCellZXY(
                ruleParts[i+0],
                ruleParts[i+1],
                ruleParts[i+2]
            ));
            pColors.push(µm.pMatrix.evaluateCellZXY(
                ruleParts[i+0],
                ruleParts[i+1],
                ruleParts[i+2]
            ));
        }
        response = { tColors, pColors };
        break;

    case 'getUserSettings':
        response = {
            userSettings: µm.userSettings,
            matrixSwitches: {
                'https-strict': µm.pMatrix.evaluateSwitch('https-strict', '*') === 1,
                'referrer-spoof': µm.pMatrix.evaluateSwitch('referrer-spoof', '*') === 1,
                'noscript-spoof': µm.pMatrix.evaluateSwitch('noscript-spoof', '*') === 1
            }
        };
        break;

    case 'gotoExtensionURL':
        µm.gotoExtensionURL(request);
        break;

    case 'graylistMatrixCell':
        µm.tMatrix.graylistCell(
            request.srcHostname,
            request.desHostname,
            request.type
        );
        break;

    case 'mustBlock':
        response = µm.mustBlock(
            request.scope,
            request.hostname,
            request.type
        );
        break;

    case 'rulesetRevert':
        µm.tMatrix.copyRuleset(request.entries, µm.pMatrix, true);
        break;

    case 'rulesetPersist':
        if ( µm.pMatrix.copyRuleset(request.entries, µm.tMatrix, true) ) {
            µm.saveMatrix();
        }
        break;

    case 'readRawSettings':
        response = µm.stringFromRawSettings();
        break;

    case 'reloadHostsFiles':
        µm.reloadHostsFiles();
        break;

    case 'reloadRecipeFiles':
        µm.loadRecipes(true);
        break;

    case 'setMatrixSwitch':
        µm.tMatrix.setSwitch(request.switchName, '*', request.state);
        if ( µm.pMatrix.setSwitch(request.switchName, '*', request.state) ) {
            µm.saveMatrix();
        }
        break;

    case 'userSettings':
        if ( request.hasOwnProperty('value') === false ) {
            request.value = undefined;
        }
        response = µm.changeUserSettings(request.name, request.value);
        break;

    case 'whitelistMatrixCell':
        µm.tMatrix.whitelistCell(
            request.srcHostname,
            request.desHostname,
            request.type
        );
        break;

    case 'writeRawSettings':
        µm.rawSettingsFromString(request.content);
        break;

    default:
        return vAPI.messaging.UNHANDLED;
    }

    callback(response);
}

/******************************************************************************/

vAPI.messaging.setup(onMessage);

/******************************************************************************/

})();

/******************************************************************************/
/******************************************************************************/

(function() {

// popup.js

var µm = µMatrix;

/******************************************************************************/

// Constructor is faster than object literal

var RowSnapshot = function(srcHostname, desHostname, desDomain) {
    this.domain = desDomain;
    this.temporary = µm.tMatrix.evaluateRowZXY(srcHostname, desHostname);
    this.permanent = µm.pMatrix.evaluateRowZXY(srcHostname, desHostname);
    this.counts = RowSnapshot.counts.slice();
    this.totals = RowSnapshot.counts.slice();
};

RowSnapshot.counts = (function() {
    var aa = [];
    for ( var i = 0, n = µm.Matrix.columnHeaderIndices.size; i < n; i++ ) {
        aa[i] = 0;
    }
    return aa;
})();

/******************************************************************************/

var matrixSnapshot = function(pageStore, details) {
    let µmuser = µm.userSettings;
    let headerIndices = µm.Matrix.columnHeaderIndices;

    let r = {
        appVersion: vAPI.app.version,
        blockedCount: pageStore.perLoadBlockedRequestCount,
        collapseAllDomains: µmuser.popupCollapseAllDomains,
        collapseBlacklistedDomains: µmuser.popupCollapseBlacklistedDomains,
        diff: [],
        domain: pageStore.pageDomain,
        has3pReferrer: pageStore.has3pReferrer,
        hasMixedContent: pageStore.hasMixedContent,
        hasNoscriptTags: pageStore.hasNoscriptTags,
        hasWebWorkers: pageStore.hasWebWorkers,
        headerIndices: Array.from(headerIndices),
        hostname: pageStore.pageHostname,
        mtxContentModified: pageStore.mtxContentModifiedTime !== details.mtxContentModifiedTime,
        mtxCountModified: pageStore.mtxCountModifiedTime !== details.mtxCountModifiedTime,
        mtxContentModifiedTime: pageStore.mtxContentModifiedTime,
        mtxCountModifiedTime: pageStore.mtxCountModifiedTime,
        pMatrixModified: µm.pMatrix.modifiedTime !== details.pMatrixModifiedTime,
        pMatrixModifiedTime: µm.pMatrix.modifiedTime,
        pSwitches: {},
        rows: {},
        rowCount: 0,
        scope: '*',
        tabId: pageStore.tabId,
        tMatrixModified: µm.tMatrix.modifiedTime !== details.tMatrixModifiedTime,
        tMatrixModifiedTime: µm.tMatrix.modifiedTime,
        tSwitches: {},
        url: pageStore.pageUrl,
        userSettings: {
            colorBlindFriendly: µmuser.colorBlindFriendly,
            displayTextSize: µmuser.displayTextSize,
            noTooltips: µmuser.noTooltips,
            popupScopeLevel: µmuser.popupScopeLevel
        }
    };

    if (
        (typeof details.scope === 'string') &&
        (details.scope === '*' || r.hostname.endsWith(details.scope))
    ) {
        r.scope = details.scope;
    } else if ( µmuser.popupScopeLevel === 'site' ) {
        r.scope = r.hostname;
    } else if ( µmuser.popupScopeLevel === 'domain' ) {
        r.scope = r.domain;
    }

    for ( let switchName of µm.Matrix.switchNames ) {
        r.tSwitches[switchName] = µm.tMatrix.evaluateSwitchZ(switchName, r.scope);
        r.pSwitches[switchName] = µm.pMatrix.evaluateSwitchZ(switchName, r.scope);
    }

    // These rows always exist
    r.rows['*'] = new RowSnapshot(r.scope, '*', '*');
    r.rows['1st-party'] = new RowSnapshot(r.scope, '1st-party', '1st-party');
    r.rowCount += 1;

    let µmuri = µm.URI;
    let anyIndex = headerIndices.get('*');

    // Ensure that the current scope is also reported in the matrix. This may
    // not be the case for documents which are fetched without going through
    // our webRequest listener (ex. `file:`).
    if ( pageStore.hostnameTypeCells.has(r.hostname + ' doc') === false ) {
        pageStore.hostnameTypeCells.set(r.hostname + ' doc', new Set([ 0 ]));
    }

    for ( let entry of pageStore.hostnameTypeCells ) {
        let pos = entry[0].indexOf(' ');
        let reqHostname = entry[0].slice(0, pos);
        let reqType = entry[0].slice(pos + 1);
        // rhill 2013-10-23: hostname can be empty if the request is a data url
        // https://github.com/gorhill/httpswitchboard/issues/26
        if ( reqHostname === '' ) {
            reqHostname = pageStore.pageHostname;
        }
        let reqDomain = µmuri.domainFromHostname(reqHostname) || reqHostname;

        // We want rows of self and ancestors
        let desHostname = reqHostname;
        for (;;) {
            // If row exists, ancestors exist
            if ( r.rows.hasOwnProperty(desHostname) !== false ) { break; }
            r.rows[desHostname] = new RowSnapshot(r.scope, desHostname, reqDomain);
            r.rowCount += 1;
            if ( desHostname === reqDomain ) { break; }
            pos = desHostname.indexOf('.');
            if ( pos === -1 ) { break; }
            desHostname = desHostname.slice(pos + 1);
        }

        let count = entry[1].size;
        let typeIndex = headerIndices.get(reqType);
        let row = r.rows[reqHostname];
        row.counts[typeIndex] += count;
        row.counts[anyIndex] += count;
        row = r.rows[reqDomain];
        row.totals[typeIndex] += count;
        row.totals[anyIndex] += count;
        row = r.rows['*'];
        row.totals[typeIndex] += count;
        row.totals[anyIndex] += count;
    }

    r.diff = µm.tMatrix.diff(µm.pMatrix, r.hostname, Object.keys(r.rows));

    return r;
};

/******************************************************************************/

var matrixSnapshotFromTabId = function(details, callback) {
    var matrixSnapshotIf = function(tabId, details) {
        var pageStore = µm.pageStoreFromTabId(tabId);
        if ( pageStore === null ) {
            callback('ENOTFOUND');
            return;
        }

        // First verify whether we must return data or not.
        if (
            µm.tMatrix.modifiedTime === details.tMatrixModifiedTime &&
            µm.pMatrix.modifiedTime === details.pMatrixModifiedTime &&
            pageStore.mtxContentModifiedTime === details.mtxContentModifiedTime &&
            pageStore.mtxCountModifiedTime === details.mtxCountModifiedTime
        ) {
            callback('ENOCHANGE');
            return ;
        }

        callback(matrixSnapshot(pageStore, details));
    };

    // Specific tab id requested?
    if ( details.tabId ) {
        matrixSnapshotIf(details.tabId, details);
        return;
    }

    // Fall back to currently active tab
    var onTabReady = function(tab) {
        if ( tab instanceof Object === false ) {
            callback('ENOTFOUND');
            return;
        }
        matrixSnapshotIf(tab.id, details);
    };

    vAPI.tabs.get(null, onTabReady);
};

/******************************************************************************/

var onMessage = function(request, sender, callback) {
    // Async
    switch ( request.what ) {
    case 'fetchRecipes':
        µm.recipeManager.fetch(
            request.srcHostname,
            request.desHostnames,
            callback
        );
        return;

    case 'matrixSnapshot':
        matrixSnapshotFromTabId(request, callback);
        return;

    default:
        break;
    }

    // Sync
    var response;

    switch ( request.what ) {
    case 'applyRecipe':
        µm.recipeManager.apply(request);
        break;

    case 'fetchRecipeCommitStatuses':
        response = µm.recipeManager.statuses(request);
        break;

    case 'toggleMatrixSwitch':
        µm.tMatrix.setSwitchZ(
            request.switchName,
            request.srcHostname,
            µm.tMatrix.evaluateSwitchZ(request.switchName, request.srcHostname) === false
        );
        break;

    case 'applyDiffToPermanentMatrix': // aka "persist"
        if ( µm.pMatrix.applyDiff(request.diff, µm.tMatrix) ) {
            µm.saveMatrix();
        }
        break;

    case 'applyDiffToTemporaryMatrix': // aka "revert"
        µm.tMatrix.applyDiff(request.diff, µm.pMatrix);
        break;

    case 'revertTemporaryMatrix':
        µm.tMatrix.assign(µm.pMatrix);
        break;

    default:
        return vAPI.messaging.UNHANDLED;
    }

    callback(response);
};

vAPI.messaging.listen('popup.js', onMessage);

})();

/******************************************************************************/
/******************************************************************************/

// content scripts

(function() {

var µm = µMatrix;

/******************************************************************************/

var foundInlineCode = function(tabId, pageStore, details, type) {
    if ( pageStore === null ) { return; }

    let srcHn = pageStore.pageHostname,
        µmuri = µm.URI.set(details.documentURI),
        desHn = µmuri.hostname,
        frameURL = µmuri.normalizedURI();

    let blocked = details.blocked;
    if ( blocked === undefined ) {
        blocked = µm.mustBlock(srcHn, desHn, type);
    }

    let mapTo = {
        css: 'style',
        script: 'script'
    };

    // https://github.com/gorhill/httpswitchboard/issues/333
    //   Look-up here whether inline scripting is blocked for the frame.
    let desURL = frameURL + '{inline_' + mapTo[type] + '}';
    pageStore.recordRequest(type, desURL, blocked);
    µm.logger.writeOne({ tabId, srcHn, desHn, desURL, type, blocked });
};

/******************************************************************************/

var contentScriptLocalStorageHandler = function(tabId, originURL) {
    let tabContext = µm.tabContextManager.lookup(tabId);
    if ( tabContext === null ) { return; }

    let srcHn = tabContext.rootHostname,
        desHn = µm.URI.hostnameFromURI(originURL),
        type = 'cookie',
        blocked = µm.mustBlock(srcHn, desHn, type);

    let pageStore = µm.pageStoreFromTabId(tabId);
    if ( pageStore !== null ) {
        let desURL = originURL + '/{localStorage}';
        pageStore.recordRequest(type, desURL, blocked);
        µm.logger.writeOne({ tabId, srcHn, desHn, desURL, type, blocked });
    }

    var removeStorage = blocked && µm.userSettings.deleteLocalStorage;
    if ( removeStorage ) {
        µm.localStorageRemovedCounter++;
    }

    return removeStorage;
};

/******************************************************************************/

// Evaluate many URLs against the matrix.

var lookupBlockedCollapsibles = function(tabId, requests) {
    if ( placeholdersReadTime < µm.rawSettingsWriteTime ) {
        placeholders = undefined;
    }

    if ( placeholders === undefined ) {
        placeholders = {
            frame: µm.rawSettings.framePlaceholder,
            image: µm.rawSettings.imagePlaceholder
        };
        if ( placeholders.frame ) {
            placeholders.frameDocument =
                µm.rawSettings.framePlaceholderDocument.replace(
                    '{{bg}}',
                    µm.rawSettings.framePlaceholderBackground !== 'default' ?
                        µm.rawSettings.framePlaceholderBackground :
                        µm.rawSettings.placeholderBackground
                );
        }
        if ( placeholders.image ) {
            placeholders.imageBorder =
                µm.rawSettings.imagePlaceholderBorder !== 'default' ?
                    µm.rawSettings.imagePlaceholderBorder :
                    µm.rawSettings.placeholderBorder;
            placeholders.imageBackground =
                µm.rawSettings.imagePlaceholderBackground !== 'default' ?
                    µm.rawSettings.imagePlaceholderBackground :
                    µm.rawSettings.placeholderBackground;
        }
        placeholdersReadTime = Date.now();
    }

    var response = {
        blockedResources: [],
        hash: requests.hash,
        id: requests.id,
        placeholders: placeholders
    };

    var tabContext = µm.tabContextManager.lookup(tabId);
    if ( tabContext === null ) {
        return response;
    }

    var pageStore = µm.pageStoreFromTabId(tabId);
    if ( pageStore !== null ) {
        pageStore.lookupBlockedCollapsibles(requests, response);
    }

    return response;
};

var placeholders,
    placeholdersReadTime = 0;

/******************************************************************************/

var onMessage = function(request, sender, callback) {
    // Async
    switch ( request.what ) {
    default:
        break;
    }

    let tabId = sender && sender.tab ? sender.tab.id || 0 : 0,
        tabContext = µm.tabContextManager.lookup(tabId),
        srcHn = tabContext && tabContext.rootHostname,
        pageStore = µm.pageStoreFromTabId(tabId);

    // Sync
    let response;

    switch ( request.what ) {
    case 'contentScriptHasLocalStorage':
        response = contentScriptLocalStorageHandler(tabId, request.originURL);
        break;

    case 'lookupBlockedCollapsibles':
        response = lookupBlockedCollapsibles(tabId, request);
        break;

    case 'mustRenderNoscriptTags?':
        if ( tabContext === null ) { break; }
        response =
            µm.tMatrix.mustBlock(srcHn, srcHn, 'script') &&
            µm.tMatrix.evaluateSwitchZ('noscript-spoof', srcHn);
        if ( pageStore !== null ) {
            pageStore.hasNoscriptTags = true;
        }
        // https://github.com/gorhill/uMatrix/issues/225
        //   A good place to force an update of the page title, as at
        //   this point the DOM has been loaded.
        µm.updateTitle(tabId);
        break;

    case 'securityPolicyViolation':
        if ( request.directive === 'worker-src' ) {
            let desURL = request.blockedURI;
            let desHn = µm.URI.hostnameFromURI(desURL);
            if ( desHn === '' ) {
                desURL = request.documentURI;
                desHn = µm.URI.hostnameFromURI(desURL);
            }
            if ( pageStore !== null ) {
                pageStore.hasWebWorkers = true;
                pageStore.recordRequest('script', desURL, request.blocked);
            }
            if ( tabContext !== null ) {
                µm.logger.writeOne({
                    tabId,
                    srcHn,
                    desHn,
                    desURL,
                    type: 'worker',
                    blocked: request.blocked
                });
            }
        } else if ( request.directive === 'script-src' ) {
            foundInlineCode(tabId, pageStore, request, 'script');
        } else if ( request.directive === 'style-src' ) {
            foundInlineCode(tabId, pageStore, request, 'css');
        }
        break;

    case 'shutdown?':
        if ( tabContext !== null ) {
            response = µm.tMatrix.evaluateSwitchZ('matrix-off', srcHn);
        }
        break;

    default:
        return vAPI.messaging.UNHANDLED;
    }

    callback(response);
};

vAPI.messaging.listen('contentscript.js', onMessage);

/******************************************************************************/

})();

/******************************************************************************/
/******************************************************************************/

// cloud-ui.js

(function() {

/******************************************************************************/

var µm = µMatrix;

/******************************************************************************/

var onMessage = function(request, sender, callback) {
    // Async
    switch ( request.what ) {
    case 'cloudGetOptions':
        vAPI.cloud.getOptions(function(options) {
            options.enabled = µm.userSettings.cloudStorageEnabled === true;
            callback(options);
        });
        return;

    case 'cloudSetOptions':
        vAPI.cloud.setOptions(request.options, callback);
        return;

    case 'cloudPull':
        return vAPI.cloud.pull(request.datakey, callback);

    case 'cloudPush':
        return vAPI.cloud.push(request.datakey, request.data, callback);

    default:
        break;
    }

    // Sync
    var response;

    switch ( request.what ) {
    // For when cloud storage is disabled.
    case 'cloudPull':
        // fallthrough
    case 'cloudPush':
        break;

    default:
        return vAPI.messaging.UNHANDLED;
    }

    callback(response);
};

/******************************************************************************/

vAPI.messaging.listen('cloud-ui.js', onMessage);

})();

/******************************************************************************/
/******************************************************************************/

// user-rules.js

(function() {

var µm = µMatrix;

var modifyRuleset = function(details) {
    let ruleset = details.permanent ? µm.pMatrix : µm.tMatrix,
        modifiedTime = ruleset.modifiedTime;
    let toRemove = new Set(details.toRemove.trim().split(/\s*[\n\r]+\s*/));
    for ( let rule of toRemove ) {
        ruleset.removeFromLine(rule);
    }
    let toAdd = new Set(details.toAdd.trim().split(/\s*[\n\r]+\s*/));
    for ( let rule of toAdd ) {
        ruleset.addFromLine(rule);
    }
    if ( details.permanent && ruleset.modifiedTime !== modifiedTime ) {
        µm.saveMatrix();
    }
};

/******************************************************************************/

var onMessage = function(request, sender, callback) {

    // Async
    switch ( request.what ) {
    default:
        break;
    }

    // Sync
    var response;

    switch ( request.what ) {
    case 'modifyRuleset':
        modifyRuleset(request);
        /* falls through */

    case 'getRuleset':
        response = {
            temporaryRules: µm.tMatrix.toArray(),
            permanentRules: µm.pMatrix.toArray()
        };
        break;

    default:
        return vAPI.messaging.UNHANDLED;
    }

    callback(response);
};

vAPI.messaging.listen('user-rules.js', onMessage);

})();

/******************************************************************************/
/******************************************************************************/

// hosts-files.js

(function() {

var µm = µMatrix;

/******************************************************************************/

var getAssets = function(callback) {
    var r = {
        autoUpdate: µm.userSettings.autoUpdate,
        blockedHostnameCount: µm.ubiquitousBlacklist.count,
        hosts: null,
        recipes: null,
        userRecipes: µm.userSettings.userRecipes,
        cache: null,
        contributor: µm.rawSettings.contributorMode
    };
    var onMetadataReady = function(entries) {
        r.cache = entries;
        callback(r);
    };
    var onAvailableRecipeFilesReady = function(collection) {
        r.recipes = Array.from(collection);
        µm.assets.metadata(onMetadataReady);
    };
    var onAvailableHostsFilesReady = function(collection) {
        r.hosts = Array.from(collection);
        µm.getAvailableRecipeFiles(onAvailableRecipeFilesReady);
    };
    µm.getAvailableHostsFiles(onAvailableHostsFilesReady);
};

/******************************************************************************/

var onMessage = function(request, sender, callback) {
    var µm = µMatrix;

    // Async
    switch ( request.what ) {
    case 'getAssets':
        return getAssets(callback);

    default:
        break;
    }

    // Sync
    var response;

    switch ( request.what ) {
    case 'purgeCache':
        µm.assets.purge(request.assetKey);
        µm.assets.remove('compiled/' + request.assetKey);
        break;

    case 'purgeAllCaches':
        if ( request.hard ) {
            µm.assets.remove(/./);
        } else {
            µm.assets.purge(/./, 'public_suffix_list.dat');
        }
        break;

    default:
        return vAPI.messaging.UNHANDLED;
    }

    callback(response);
};

vAPI.messaging.listen('hosts-files.js', onMessage);

})();

/******************************************************************************/
/******************************************************************************/

// about.js

(function() {

var µm = µMatrix;

/******************************************************************************/

var restoreUserData = function(userData) {
    var countdown = 0;
    var onCountdown = function() {
        countdown -= 1;
        if ( countdown === 0 ) {
            vAPI.app.restart();
        }
    };

    var onAllRemoved = function() {
        let µm = µMatrix;
        countdown += 1;
        vAPI.storage.set(userData.settings, onCountdown);
        countdown += 1;
        let bin = { userMatrix: userData.rules };
        if ( userData.hostsFiles instanceof Object ) {
            bin.liveHostsFiles = userData.hostsFiles;
        }
        vAPI.storage.set(bin, onCountdown);
        if ( userData.rawSettings instanceof Object ) {
            countdown += 1;
            µm.saveRawSettings(userData.rawSettings, onCountdown);
        }
    };

    // If we are going to restore all, might as well wipe out clean local
    // storage
    vAPI.storage.clear(onAllRemoved);
};

/******************************************************************************/

var resetUserData = function() {
    var onAllRemoved = function() {
        vAPI.app.restart();
    };
    vAPI.storage.clear(onAllRemoved);
};

/******************************************************************************/

var onMessage = function(request, sender, callback) {
    // Async
    switch ( request.what ) {
    default:
        break;
    }

    // Sync
    var response;

    switch ( request.what ) {
    case 'getAllUserData':
        response = {
            app: vAPI.app.name,
            version: vAPI.app.version,
            when: Date.now(),
            settings: µm.userSettings,
            rules: µm.pMatrix.toArray().sort(),
            rawSettings: µm.rawSettings
        };
        break;

    case 'getSomeStats':
        response = {
            version: vAPI.app.version,
            storageUsed: µm.storageUsed
        };
        break;

    case 'restoreAllUserData':
        restoreUserData(request.userData);
        break;

    case 'resetAllUserData':
        resetUserData();
        break;

    default:
        return vAPI.messaging.UNHANDLED;
    }

    callback(response);
};

vAPI.messaging.listen('about.js', onMessage);

/******************************************************************************/
/******************************************************************************/

// logger-ui.js

(function() {

/******************************************************************************/

var µm = µMatrix,
    loggerURL = vAPI.getURL('logger-ui.html');

/******************************************************************************/

var onMessage = function(request, sender, callback) {
    // Async
    switch ( request.what ) {
        default:
            break;
    }

    // Sync
    var response;

    switch ( request.what ) {
    case 'getPublicSuffixListData':
        response = publicSuffixList.toSelfie();
        break;

    case 'getRuleEditorOptions':
        response = {
            colorBlindFriendly: µm.userSettings.colorBlindFriendly,
            popupScopeLevel: µm.userSettings.popupScopeLevel
        };
        break;

    case 'readMany':
        if (
            µm.logger.ownerId !== undefined &&
            request.ownerId !== µm.logger.ownerId
        ) {
            response = { unavailable: true };
            break;
        }
        let pageStores;
        if ( request.pageStoresToken !== µm.pageStoresToken ) {
            pageStores = [];
            for ( let entry of µm.pageStores ) {
                let tabId = entry[0];
                let pageStore = entry[1];
                if ( pageStore.rawURL.startsWith(loggerURL) ) { continue; }
                pageStores.push([ tabId, pageStore.title || pageStore.rawURL ]);
            }
        }
        response = {
            colorBlind: false,
            entries: µm.logger.readAll(request.ownerId),
            maxLoggedRequests: µm.userSettings.maxLoggedRequests,
            noTabId: vAPI.noTabId,
            pageStores: pageStores,
            pageStoresToken: µm.pageStoresToken
        };
        break;

    case 'releaseView':
        if ( request.ownerId === µm.logger.ownerId ) {
            µm.logger.ownerId = undefined;
        }
        break;

    default:
        return vAPI.messaging.UNHANDLED;
    }

    callback(response);
};

vAPI.messaging.listen('logger-ui.js', onMessage);

/******************************************************************************/

})();

/******************************************************************************/
/******************************************************************************/

})();

/******************************************************************************/
