/*
* This file is part of Adblock Plus ,
* Copyright (C) 2006-2017 eyeo GmbH
*
* Adblock Plus is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
* published by the Free Software Foundation.
*
* Adblock Plus 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 Adblock Plus. If not, see .
*/
/* globals require */
"use strict";
(function(global)
{
let ext = global.ext || require("ext_background");
const {port} = require("messaging");
const {Prefs} = require("prefs");
const {Utils} = require("utils");
const {FilterStorage} = require("filterStorage");
const {FilterNotifier} = require("filterNotifier");
const {defaultMatcher} = require("matcher");
const {ElemHideEmulation} = require("elemHideEmulation");
const {Notification: NotificationStorage} = require("notification");
const {Filter, BlockingFilter, RegExpFilter} = require("filterClasses");
const {Synchronizer} = require("synchronizer");
const info = require("info");
const {Subscription,
DownloadableSubscription,
SpecialSubscription} = require("subscriptionClasses");
// Some modules doesn't exist on Firefox. Moreover,
// require() throws an exception on Firefox in that case.
// However, try/catch causes the whole function to to be
// deoptimized on V8. So we wrap it into another function.
function tryRequire(module)
{
try
{
return require(module);
}
catch (e)
{
return null;
}
}
function convertObject(keys, obj)
{
let result = {};
for (let key of keys)
{
if (key in obj)
result[key] = obj[key];
}
return result;
}
function convertSubscription(subscription)
{
let obj = convertObject(["disabled", "downloadStatus", "homepage",
"lastDownload", "title", "url"], subscription);
obj.isDownloading = Synchronizer.isExecuting(subscription.url);
return obj;
}
let convertFilter = convertObject.bind(null, ["text"]);
let changeListeners = new ext.PageMap();
let listenedPreferences = Object.create(null);
let listenedFilterChanges = Object.create(null);
let messageTypes = new Map([
["app", "app.respond"],
["filter", "filters.respond"],
["pref", "prefs.respond"],
["subscription", "subscriptions.respond"]
]);
function sendMessage(type, action, ...args)
{
let pages = changeListeners.keys();
if (pages.length == 0)
return;
let convertedArgs = [];
for (let arg of args)
{
if (arg instanceof Subscription)
convertedArgs.push(convertSubscription(arg));
else if (arg instanceof Filter)
convertedArgs.push(convertFilter(arg));
else
convertedArgs.push(arg);
}
for (let page of pages)
{
let filters = changeListeners.get(page);
let actions = filters[type];
if (actions && actions.indexOf(action) != -1)
{
page.sendMessage({
type: messageTypes.get(type),
action,
args: convertedArgs
});
}
}
}
function addFilterListeners(type, actions)
{
for (let action of actions)
{
let name;
if (type == "filter" && action == "loaded")
name = "load";
else
name = type + "." + action;
if (!(name in listenedFilterChanges))
{
listenedFilterChanges[name] = null;
FilterNotifier.on(name, (...args) =>
{
sendMessage(type, action, ...args);
});
}
}
}
function getListenerFilters(page)
{
let listenerFilters = changeListeners.get(page);
if (!listenerFilters)
{
listenerFilters = Object.create(null);
changeListeners.set(page, listenerFilters);
}
return listenerFilters;
}
port.on("app.get", (message, sender) =>
{
if (message.what == "issues")
{
let subscriptionInit = tryRequire("subscriptionInit");
let result = subscriptionInit ? subscriptionInit.reinitialized : false;
return {filterlistsReinitialized: result};
}
if (message.what == "doclink")
return Utils.getDocLink(message.link);
if (message.what == "localeInfo")
{
let bidiDir;
if ("chromeRegistry" in Utils)
{
let isRtl = Utils.chromeRegistry.isLocaleRTL("adblockplus");
bidiDir = isRtl ? "rtl" : "ltr";
}
else
bidiDir = ext.i18n.getMessage("@@bidi_dir");
return {locale: Utils.appLocale, bidiDir};
}
if (message.what == "features")
{
return {
devToolsPanel: info.platform == "chromium"
};
}
return info[message.what];
});
port.on("app.listen", (message, sender) =>
{
getListenerFilters(sender.page).app = message.filter;
});
port.on("app.open", (message, sender) =>
{
if (message.what == "options")
ext.showOptions();
});
port.on("filters.add", (message, sender) =>
{
let result = require("filterValidation").parseFilter(message.text);
let errors = [];
if (result.error)
errors.push(result.error.toString());
else if (result.filter)
FilterStorage.addFilter(result.filter);
return errors;
});
port.on("filters.blocked", (message, sender) =>
{
let filter = defaultMatcher.matchesAny(message.url,
RegExpFilter.typeMap[message.requestType], message.docDomain,
message.thirdParty);
return filter instanceof BlockingFilter;
});
port.on("filters.get", (message, sender) =>
{
if (message.what == "elemhideemulation")
{
let filters = [];
const {checkWhitelisted} = require("whitelisting");
if (Prefs.enabled && !checkWhitelisted(sender.page, sender.frame,
RegExpFilter.typeMap.DOCUMENT |
RegExpFilter.typeMap.ELEMHIDE))
{
let {hostname} = sender.frame.url;
filters = ElemHideEmulation.getRulesForDomain(hostname);
filters = filters.map((filter) =>
{
return {
selector: filter.selector,
text: filter.text
};
});
}
return filters;
}
let subscription = Subscription.fromURL(message.subscriptionUrl);
if (!subscription)
return [];
return subscription.filters.map(convertFilter);
});
port.on("filters.importRaw", (message, sender) =>
{
let result = require("filterValidation").parseFilters(message.text);
let errors = [];
for (let error of result.errors)
{
if (error.type != "unexpected-filter-list-header")
errors.push(error.toString());
}
if (errors.length > 0)
return errors;
let seenFilter = Object.create(null);
for (let filter of result.filters)
{
FilterStorage.addFilter(filter);
seenFilter[filter.text] = null;
}
if (!message.removeExisting)
return errors;
for (let subscription of FilterStorage.subscriptions)
{
if (!(subscription instanceof SpecialSubscription))
continue;
for (let j = subscription.filters.length - 1; j >= 0; j--)
{
let filter = subscription.filters[j];
if (/^@@\|\|([^/:]+)\^\$document$/.test(filter.text))
continue;
if (!(filter.text in seenFilter))
FilterStorage.removeFilter(filter);
}
}
return errors;
});
port.on("filters.listen", (message, sender) =>
{
getListenerFilters(sender.page).filter = message.filter;
addFilterListeners("filter", message.filter);
});
port.on("filters.remove", (message, sender) =>
{
let filter = Filter.fromText(message.text);
let subscription = null;
if (message.subscriptionUrl)
subscription = Subscription.fromURL(message.subscriptionUrl);
if (!subscription)
FilterStorage.removeFilter(filter);
else
FilterStorage.removeFilter(filter, subscription, message.index);
});
port.on("prefs.get", (message, sender) =>
{
return Prefs[message.key];
});
port.on("prefs.listen", (message, sender) =>
{
getListenerFilters(sender.page).pref = message.filter;
for (let preference of message.filter)
{
if (!(preference in listenedPreferences))
{
listenedPreferences[preference] = null;
Prefs.on(preference, () =>
{
sendMessage("pref", preference, Prefs[preference]);
});
}
}
});
port.on("prefs.toggle", (message, sender) =>
{
if (message.key == "notifications_ignoredcategories")
NotificationStorage.toggleIgnoreCategory("*");
else
Prefs[message.key] = !Prefs[message.key];
});
port.on("subscriptions.add", (message, sender) =>
{
let subscription = Subscription.fromURL(message.url);
if ("title" in message)
subscription.title = message.title;
if ("homepage" in message)
subscription.homepage = message.homepage;
if (message.confirm)
{
ext.showOptions(() =>
{
sendMessage("app", "addSubscription", subscription);
});
}
else
{
subscription.disabled = false;
FilterStorage.addSubscription(subscription);
if (subscription instanceof DownloadableSubscription &&
!subscription.lastDownload)
Synchronizer.execute(subscription);
}
});
port.on("subscriptions.get", (message, sender) =>
{
let subscriptions = FilterStorage.subscriptions.filter((s) =>
{
if (message.ignoreDisabled && s.disabled)
return false;
if (s instanceof DownloadableSubscription && message.downloadable)
return true;
if (s instanceof SpecialSubscription && message.special)
return true;
return false;
});
return subscriptions.map(convertSubscription);
});
port.on("subscriptions.listen", (message, sender) =>
{
getListenerFilters(sender.page).subscription = message.filter;
addFilterListeners("subscription", message.filter);
});
port.on("subscriptions.remove", (message, sender) =>
{
let subscription = Subscription.fromURL(message.url);
if (subscription.url in FilterStorage.knownSubscriptions)
FilterStorage.removeSubscription(subscription);
});
port.on("subscriptions.toggle", (message, sender) =>
{
let subscription = Subscription.fromURL(message.url);
if (subscription.url in FilterStorage.knownSubscriptions)
{
if (subscription.disabled || message.keepInstalled)
subscription.disabled = !subscription.disabled;
else
FilterStorage.removeSubscription(subscription);
}
else
{
subscription.disabled = false;
subscription.title = message.title;
subscription.homepage = message.homepage;
FilterStorage.addSubscription(subscription);
if (!subscription.lastDownload)
Synchronizer.execute(subscription);
}
});
port.on("subscriptions.update", (message, sender) =>
{
let {subscriptions} = FilterStorage;
if (message.url)
subscriptions = [Subscription.fromURL(message.url)];
for (let subscription of subscriptions)
{
if (subscription instanceof DownloadableSubscription)
Synchronizer.execute(subscription, true);
}
});
})(this);