Revert "Move extensions to projects/"

This reverts commit 3e94e634ae54925da650a57b9982fe087658c580.
This commit is contained in:
Matt A. Tobin
2019-02-21 15:54:42 -05:00
committed by Roy Tam
parent 307532a94c
commit ebc19cacf5
367 changed files with 61164 additions and 28 deletions
+32
View File
@@ -0,0 +1,32 @@
#filter substitution
pref("extensions.@ADDON_CHROME_NAME@.currentVersion", "0.0");
pref("extensions.@ADDON_CHROME_NAME@.enabled", true);
pref("extensions.@ADDON_CHROME_NAME@.frameobjects", true);
pref("extensions.@ADDON_CHROME_NAME@.fastcollapse", false);
pref("extensions.@ADDON_CHROME_NAME@.showinstatusbar", false);
pref("extensions.@ADDON_CHROME_NAME@.detachsidebar", false);
pref("extensions.@ADDON_CHROME_NAME@.defaulttoolbaraction", 0);
pref("extensions.@ADDON_CHROME_NAME@.defaultstatusbaraction", 0);
pref("extensions.@ADDON_CHROME_NAME@.sidebar_key", "Accel Shift V, Accel Shift U");
pref("extensions.@ADDON_CHROME_NAME@.sendReport_key", "");
pref("extensions.@ADDON_CHROME_NAME@.filters_key", "Accel Shift E, Accel Shift F, Accel Shift O");
pref("extensions.@ADDON_CHROME_NAME@.enable_key", "");
pref("extensions.@ADDON_CHROME_NAME@.flash_scrolltoitem", true);
pref("extensions.@ADDON_CHROME_NAME@.previewimages", true);
pref("extensions.@ADDON_CHROME_NAME@.data_directory", "adblockplus");
pref("extensions.@ADDON_CHROME_NAME@.patternsbackups", 5);
pref("extensions.@ADDON_CHROME_NAME@.patternsbackupinterval", 24);
pref("extensions.@ADDON_CHROME_NAME@.whitelistschemes", "about chrome file irc moz-safe-about news resource snews x-jsd addbook cid imap mailbox nntp pop data javascript moz-icon");
pref("extensions.@ADDON_CHROME_NAME@.hideimagemanager", true);
pref("extensions.@ADDON_CHROME_NAME@.subscriptions_autoupdate", true);
pref("extensions.@ADDON_CHROME_NAME@.subscriptions_listurl", "https://adblockplus.org/subscriptions2.xml");
pref("extensions.@ADDON_CHROME_NAME@.subscriptions_fallbackurl", "https://adblockplus.org/getSubscription?version=%VERSION%&url=%SUBSCRIPTION%&downloadURL=%URL%&error=%ERROR%&channelStatus=%CHANNELSTATUS%&responseStatus=%RESPONSESTATUS%");
pref("extensions.@ADDON_CHROME_NAME@.subscriptions_fallbackerrors", 5);
pref("extensions.@ADDON_CHROME_NAME@.documentation_link", "https://adblockplus.org/redirect?link=%LINK%&lang=%LANG%");
pref("extensions.@ADDON_CHROME_NAME@.savestats", true);
pref("extensions.@ADDON_CHROME_NAME@.composer_default", 2);
pref("extensions.@ADDON_CHROME_NAME@.clearStatsOnHistoryPurge", true);
pref("extensions.@ADDON_CHROME_NAME@.report_submiturl", "");
pref("extensions.@ADDON_CHROME_NAME@.recentReports", "[]");
pref("services.sync.engine.@ADDON_CHROME_NAME@", false);
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

+58
View File
@@ -0,0 +1,58 @@
<?xml version="1.0"?>
<!-- This Source Code is subject to the terms of the Mozilla Public License
- version 2.0 (the "License"). You can obtain a copy of the License at
- http://mozilla.org/MPL/2.0/. -->
#filter substitution
<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:em="http://www.mozilla.org/2004/em-rdf#">
<Description about="urn:mozilla:install-manifest">
<em:id>@ADDON_ID@</em:id>
<em:version>@ADDON_VERSION@</em:version>
<em:name>@ADDON_NAME@</em:name>
<em:description>@ADDON_SHORT_DESC@</em:description>
<em:creator>@ADDON_AUTHOR@</em:creator>
#ifdef ADDON_URL
<em:homepageURL>@ADDON_URL@</em:homepageURL>
#endif
<em:type>2</em:type>
<em:targetApplication>
<Description>
<!-- @ADDON_TARGET_APP_NAME@ -->
<em:id>@ADDON_TARGET_APP_ID@</em:id>
<em:minVersion>@ADDON_TARGET_APP_MINVER@</em:minVersion>
<em:maxVersion>@ADDON_TARGET_APP_MAXVER@</em:maxVersion>
</Description>
</em:targetApplication>
#ifdef ADDON_TARGET_BASILISK
<em:targetApplication>
<Description>
<!-- Basilisk -->
<em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
<em:minVersion>52.0</em:minVersion>
<em:maxVersion>55.*</em:maxVersion>
<em:basilisk>true</em:basilisk>
</Description>
</em:targetApplication>
#endif
<em:targetApplication>
<Description>
<!-- Borealis (Navigator) -->
<em:id>{a3210b97-8e8a-4737-9aa0-aa0e607640b9}</em:id>
<em:minVersion>1.0.0a1</em:minVersion>
<em:maxVersion>2.*</em:maxVersion>
</Description>
</em:targetApplication>
<em:targetApplication>
<Description>
<!-- Interlink (Mail) -->
<em:id>{3550f703-e582-4d05-9a08-453d09bdfdc6}</em:id>
<em:minVersion>52.9.6884</em:minVersion>
<em:maxVersion>52.9.*</em:maxVersion>
</Description>
</em:targetApplication>
</Description>
</RDF>
+12
View File
@@ -0,0 +1,12 @@
# 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/.
#filter substitution
[.] chrome.jar:
% resource @ADDON_CHROME_NAME@ /
% component {d32a3c00-4ed3-11de-8a39-0800200c9a66} components/Initializer.js
% contract @adblockplus.org/abp/startup;1 {d32a3c00-4ed3-11de-8a39-0800200c9a66}
% category profile-after-change @adblockplus.org/abp/startup;1 @adblockplus.org/abp/startup;1
+16
View File
@@ -0,0 +1,16 @@
# vim: set filetype=python:
# 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/.
FINAL_TARGET_FILES += [
'icon.png',
'icon64.png',
]
FINAL_TARGET_PP_FILES += ['install.rdf']
FINAL_TARGET_PP_FILES.defaults += ['patterns.ini']
FINAL_TARGET_PP_FILES.defaults.preferences += ['adblockplus.js']
JAR_MANIFESTS += ['jar.mn']
+2
View File
@@ -0,0 +1,2 @@
# Adblock Plus preferences
version=4
+8
View File
@@ -0,0 +1,8 @@
# vim: set filetype=python:
# 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/.
# Never add tier dirs after the application srcdir because they
# apparently won't get packaged properly on Mac.
DIRS += ['/abprime']
+7
View File
@@ -0,0 +1,7 @@
# 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/.
package:
cd $(DIST)/bin; \
zip -Dr9X ../${ADDON_XPI_NAME}-${ADDON_VERSION}.xpi * -x \*/.mkdir.done; \
+94
View File
@@ -0,0 +1,94 @@
/*
* This Source Code is subject to the terms of the Mozilla Public License
* version 2.0 (the "License"). You can obtain a copy of the License at
* http://mozilla.org/MPL/2.0/.
*/
#filter substitution
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cr = Components.results;
const Cu = Components.utils;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
// Gecko 1.9.0/1.9.1 compatibility - add XPCOMUtils.defineLazyServiceGetter
if (!("defineLazyServiceGetter" in XPCOMUtils))
{
XPCOMUtils.defineLazyServiceGetter = function XPCU_defineLazyServiceGetter(obj, prop, contract, iface)
{
obj.__defineGetter__(prop, function XPCU_serviceGetter()
{
delete obj[prop];
return obj[prop] = Cc[contract].getService(Ci[iface]);
});
};
}
/**
* Application startup/shutdown observer, triggers init()/shutdown() methods in Bootstrap.jsm module.
* @constructor
*/
function Initializer() {}
Initializer.prototype =
{
classDescription: "Adblock Plus initializer",
contractID: "@adblockplus.org/abp/startup;1",
classID: Components.ID("{d32a3c00-4ed3-11de-8a39-0800200c9a66}"),
_xpcom_categories: [{ category: "app-startup", service: true }],
QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
observe: function(subject, topic, data)
{
let observerService = Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService);
switch (topic)
{
case "app-startup":
observerService.addObserver(this, "profile-after-change", true);
break;
case "profile-after-change":
observerService.addObserver(this, "quit-application", true);
// Don't init in Fennec, initialization will happen when UI is ready
let appInfo = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULAppInfo);
if (appInfo.ID != "{a23983c0-fd0e-11dc-95ff-0800200c9a66}" && appInfo.ID != "{aa3c5121-dab2-40e2-81ca-7ea25febc110}")
{
try
{
// Gecko 2.0 and higher - chrome URLs can be loaded directly
Cu.import("resource://@ADDON_CHROME_NAME@/modules/Bootstrap.jsm");
}
catch (e)
{
// Gecko 1.9.x - have to convert chrome URLs to file URLs first
let chromeRegistry = Cc["@mozilla.org/chrome/chrome-registry;1"].getService(Ci.nsIChromeRegistry);
let ioService = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
let bootstrapURL = chromeRegistry.convertChromeURL(ioService.newURI("resource://@ADDON_CHROME_NAME@/modules/Bootstrap.jsm", null, null));
Cu.import(bootstrapURL.spec);
}
Bootstrap.startup();
}
break;
case "quit-application":
try {
// This will fail if component was added via chrome.manifest (Gecko 2.0)
observerService.removeObserver(this, "profile-after-change");
}catch(e) {}
observerService.removeObserver(this, "quit-application");
if ("@adblockplus.org/abp/private;1" in Cc)
{
let baseURL = Cc["@adblockplus.org/abp/private;1"].getService(Ci.nsIURI);
Cu.import(baseURL.spec + "Bootstrap.jsm");
Bootstrap.shutdown(false);
}
break;
}
}
};
if (XPCOMUtils.generateNSGetFactory)
var NSGetFactory = XPCOMUtils.generateNSGetFactory([Initializer]);
else
var NSGetModule = XPCOMUtils.generateNSGetModule([Initializer]);
+9
View File
@@ -0,0 +1,9 @@
# vim: set filetype=python:
# 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/.
# Don't use sub-manifest files
NO_JS_MANIFEST = True
EXTRA_PP_COMPONENTS += ['Initializer.js']
+164
View File
@@ -0,0 +1,164 @@
/*
* This Source Code is subject to the terms of the Mozilla Public License
* version 2.0 (the "License"). You can obtain a copy of the License at
* http://mozilla.org/MPL/2.0/.
*/
#filter substitution
try
{
Cu.import("resource://gre/modules/AddonManager.jsm");
}
catch (e) {}
function init()
{
let ioService = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
if (typeof AddonManager != "undefined")
{
let addon = AddonManager.getAddonByID(Utils.addonID, function(addon)
{
loadInstallManifest(addon.getResourceURI("install.rdf"), addon.name, addon.homepageURL);
});
}
else if ("@mozilla.org/extensions/manager;1" in Cc)
{
let extensionManager = Cc["@mozilla.org/extensions/manager;1"].getService(Ci.nsIExtensionManager);
let rdf = Cc["@mozilla.org/rdf/rdf-service;1"].getService(Ci.nsIRDFService);
let root = rdf.GetResource("urn:mozilla:item:" + Utils.addonID);
function emResource(prop)
{
return rdf.GetResource("http://www.mozilla.org/2004/em-rdf#" + prop);
}
function getTarget(prop)
{
let target = extensionManager.datasource.GetTarget(root, emResource(prop), true);
if (target)
return target.QueryInterface(Ci.nsIRDFLiteral).Value;
else
return null;
}
let installLocation = extensionManager.getInstallLocation(Utils.addonID);
let installManifestFile = installLocation.getItemFile(Utils.addonID, "install.rdf");
loadInstallManifest(ioService.newFileURI(installManifestFile), getTarget("name"), getTarget("homepageURL"));
}
else
{
// No add-on manager, no extension manager - we must be running in K-Meleon.
// Load Manifest.jsm as last solution.
Cu.import(baseURL + "Manifest.jsm");
setExtensionData(manifest.name, manifest.version, manifest.homepage, [manifest.creator], manifest.contributors, manifest.translators);
}
}
function loadInstallManifest(installManifestURI, name, homepage)
{
let rdf = Cc["@mozilla.org/rdf/rdf-service;1"].getService(Ci.nsIRDFService);
let ds = rdf.GetDataSource(installManifestURI.spec);
let root = rdf.GetResource("urn:mozilla:install-manifest");
function emResource(prop)
{
return rdf.GetResource("http://www.mozilla.org/2004/em-rdf#" + prop);
}
function getTargets(prop)
{
let targets = ds.GetTargets(root, emResource(prop), true);
let result = [];
while (targets.hasMoreElements())
result.push(targets.getNext().QueryInterface(Ci.nsIRDFLiteral).Value);
return result;
}
function dataSourceLoaded()
{
setExtensionData(name, getTargets("version")[0],
homepage, getTargets("creator"),
getTargets("contributor"), getTargets("translator"));
}
if (ds instanceof Ci.nsIRDFRemoteDataSource && ds.loaded)
dataSourceLoaded();
else
{
let sink = ds.QueryInterface(Ci.nsIRDFXMLSink);
sink.addXMLSinkObserver({
onBeginLoad: function() {},
onInterrupt: function() {},
onResume: function() {},
onEndLoad: function() {
sink.removeXMLSinkObserver(this);
dataSourceLoaded();
},
onError: function() {},
});
}
}
function cmpNoCase(a, b)
{
let aLC = a.toLowerCase();
let bLC = b.toLowerCase();
if (aLC < bLC)
return -1;
else if (aLC > bLC)
return 1;
else
return 0;
}
function setExtensionData(name, version, homepage, authors, contributors, translators)
{
authors.sort(cmpNoCase);
contributors.sort(cmpNoCase);
translators.sort(cmpNoCase);
E("title").value = name;
E("version").value = version;
E("homepage").value = homepage;
E("authors").textContent = authors.join(", ");
E("contributors").textContent = contributors.join(", ");
E("translators").textContent = translators.join(", ");
let request = new XMLHttpRequest();
request.open("GET", "chrome://@ADDON_CHROME_NAME@/content/subscriptions.xml");
request.addEventListener("load", setSubscriptionAuthors, false);
request.send(null);
}
function setSubscriptionAuthors()
{
let doc = this.responseXML;
if (!doc || doc.documentElement.localName != "subscriptions")
return;
let authors = {__proto__: null};
for (let node = doc.documentElement.firstChild; node; node = node.nextSibling)
{
if (node.localName != "subscription" || !node.hasAttribute("author"))
continue;
for each (let author in node.getAttribute("author").split(","))
{
author = author.replace(/^\s+/, "").replace(/\s+$/, "");
if (author == "")
continue;
authors[author] = true;
}
}
let list = [];
for (let author in authors)
list.push(author);
list.sort(cmpNoCase)
E("subscriptionAuthors").textContent = list.join(", ");
E("mainBox").setAttribute("loaded", "true");
}
+62
View File
@@ -0,0 +1,62 @@
<?xml version="1.0"?>
<!-- This Source Code is subject to the terms of the Mozilla Public License
- version 2.0 (the "License"). You can obtain a copy of the License at
- http://mozilla.org/MPL/2.0/. -->
#filter substitution
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
<?xml-stylesheet href="chrome://@ADDON_CHROME_NAME@/skin/about.css" type="text/css"?>
<!DOCTYPE dialog SYSTEM "chrome://@ADDON_CHROME_NAME@/locale/about.dtd">
<dialog
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
title="&dialog.title;"
id="adblockAboutWindow"
windowtype="abp:about"
onload="init()"
buttons="accept">
<script type="application/x-javascript;version=1.7" src="utils.js"/>
<script type="application/x-javascript;version=1.7" src="about.js"/>
<vbox id="mainBox">
<description id="title" value=" "/>
<hbox align="baseline">
<label control="version" value="&version.title;"/>
<textbox id="version" flex="1" class="plain" readonly="true" tabindex="-1"/>
</hbox>
<groupbox id="mainGroup" flex="1">
<description id="description">
&description;
</description>
<description id="homepageTitle" value="&homepage.label;"/>
<description id="homepage" class="text-link" onclick="Utils.loadInBrowser(this.getAttribute('value'))"/>
<vbox id="authorsBox" align="top">
<label id="authorsTitle" control="authors" value="&author.label;"/>
<description id="authors"/>
</vbox>
<vbox id="contributorsBox" align="top">
<label id="contributorsTitle" control="contributors" value="&contributors.label;"/>
<description id="contributors"/>
</vbox>
<vbox id="subscriptionAuthorsBox" align="top">
<label id="subscriptionAuthorsTitle" control="subscriptionAuthors" value="&subscriptionAuthors.label;"/>
<description id="subscriptionAuthors"/>
</vbox>
<vbox id="translatorsBox">
<label id="translatorsTitle" control="translators" value="&translators.label;"/>
<description id="translators"/>
</vbox>
</groupbox>
</vbox>
</dialog>
+401
View File
@@ -0,0 +1,401 @@
/*
* This Source Code is subject to the terms of the Mozilla Public License
* version 2.0 (the "License"). You can obtain a copy of the License at
* http://mozilla.org/MPL/2.0/.
*/
let nodes = null;
let item = null;
let advancedMode = false;
function init()
{
[nodes, item] = window.arguments;
E("filterType").value = (!item.filter || item.filter.disabled || item.filter instanceof WhitelistFilter ? "filterlist" : "whitelist");
E("customPattern").value = item.location;
let insertionPoint = E("customPatternBox");
let addSuggestion = function(address)
{
// Always drop protocol and www. from the suggestion
address = address.replace(/^[\w\-]+:\/+(?:www\.)?/, "");
let suggestion = document.createElement("radio");
suggestion.setAttribute("value", address);
suggestion.setAttribute("label", address);
suggestion.setAttribute("crop", "center");
suggestion.setAttribute("class", "suggestion");
insertionPoint.parentNode.insertBefore(suggestion, insertionPoint);
return address;
}
let ioService = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
try
{
let suggestions = [""];
let url = ioService.newURI(item.location, null, null)
.QueryInterface(Ci.nsIURL);
let suffix = (url.query ? "?*" : "");
url.query = "";
suggestions[1] = addSuggestion(url.spec + suffix);
let parentURL = ioService.newURI(url.fileName == "" ? ".." : ".", null, url);
if (!parentURL.equals(url))
suggestions[2] = addSuggestion(parentURL.spec + "*");
else
suggestions[2] = suggestions[1];
let rootURL = ioService.newURI("/", null, url);
if (!rootURL.equals(parentURL) && !rootURL.equals(url))
suggestions[3] = addSuggestion(rootURL.spec + "*");
else
suggestions[3] = suggestions[2];
try
{
suggestions[4] = addSuggestion(url.host.replace(/^www\./, "") + "^");
// Prefer example.com^ to example.com/*
let undesired = suggestions[4].replace(/\^$/, "/*");
for (let i = 0; i < suggestions.length - 1; i++)
if (suggestions[i] == undesired)
suggestions[i] = suggestions[4];
for (let child = insertionPoint.parentNode.firstChild; child; child = child.nextSibling)
{
if (child.localName == "radio" && child.getAttribute("value") == undesired)
{
child.parentNode.removeChild(child);
break;
}
}
}
catch (e)
{
suggestions[4] = suggestions[3];
}
try
{
let effectiveTLD = Cc["@mozilla.org/network/effective-tld-service;1"].getService(Ci.nsIEffectiveTLDService);
let host = url.host;
let baseDomain = effectiveTLD.getBaseDomainFromHost(host);
if (baseDomain != host.replace(/^www\./, ""))
suggestions[5] = addSuggestion(baseDomain + "^");
else
suggestions[5] = suggestions[4];
}
catch (e)
{
suggestions[5] = suggestions[4];
}
E("patternGroup").value = (Prefs.composer_default in suggestions ? suggestions[Prefs.composer_default] : suggestions[1]);
}
catch (e)
{
// IOService returned nsIURI - not much we can do with it
addSuggestion(item.location);
E("patternGroup").value = "";
}
if (Prefs.composer_default == 0)
E("customPattern").focus();
else
E("patternGroup").focus();
let types = [];
for (let type in Policy.localizedDescr)
{
types.push(parseInt(type));
}
types.sort(function(a, b) {
if (a < b)
return -1;
else if (a > b)
return 1;
else
return 0;
});
let docDomain = item.docDomain;
let thirdParty = item.thirdParty;
if (docDomain)
docDomain = docDomain.replace(/^www\./i, "").replace(/\.+$/, "");
if (docDomain)
E("domainRestriction").value = docDomain;
E("thirdParty").hidden = !thirdParty;
E("firstParty").hidden = thirdParty;
let typeGroup = E("typeGroup");
let defaultTypes = RegExpFilter.prototype.contentType & ~RegExpFilter.typeMap.DOCUMENT;
let isDefaultType = (RegExpFilter.typeMap[item.typeDescr] & defaultTypes) != 0;
for each (let type in types)
{
if (type == Policy.type.ELEMHIDE)
continue;
let typeNode = document.createElement("checkbox");
typeNode.setAttribute("value", Policy.typeDescr[type].toLowerCase().replace(/\_/g, "-"));
typeNode.setAttribute("label", Policy.localizedDescr[type].toLowerCase());
let typeMask = RegExpFilter.typeMap[Policy.typeDescr[type]];
typeNode._defaultType = (typeMask & defaultTypes) != 0;
if ((isDefaultType && typeNode._defaultType) || (!isDefaultType && item.type == type))
typeNode.setAttribute("checked", "true");
if (item.type == type)
typeNode.setAttribute("disabled", "true");
typeNode.addEventListener("command", function() checkboxUpdated(this), false);
typeGroup.appendChild(typeNode);
}
let collapseDefault = E("collapseDefault");
collapseDefault.label = collapseDefault.getAttribute(Prefs.fastcollapse ? "label_no" : "label_yes");
E("collapse").value = "";
E("collapse").setAttribute("label", collapseDefault.label);
let warning = E("disabledWarning");
generateLinkText(warning);
warning.hidden = Prefs.enabled;
updatePatternSelection();
}
function checkboxUpdated(checkbox)
{
checkbox._lastChange = Date.now();
updateFilter();
}
function updateFilter()
{
let filter = "";
let type = E("filterType").value
if (type == "whitelist")
filter += "@@";
let pattern = E("patternGroup").value;
if (pattern == "")
pattern = E("customPattern").value;
if (E("anchorStart").checked)
filter += E("anchorStart").flexibleAnchor ? "||" : "|";
filter += pattern;
if (E("anchorEnd").checked)
filter += "|";
if (advancedMode)
{
let options = [];
if (E("domainRestrictionEnabled").checked)
{
let domainRestriction = E("domainRestriction").value.replace(/[,\s]/g, "").replace(/\.+$/, "");
if (domainRestriction)
options.push([E("domainRestrictionEnabled")._lastChange || 0, "domain=" + domainRestriction]);
}
if (E("firstParty").checked)
options.push([E("firstParty")._lastChange || 0, "~third-party"]);
if (E("thirdParty").checked)
options.push([E("thirdParty")._lastChange || 0, "third-party"]);
if (E("matchCase").checked)
options.push([E("matchCase")._lastChange || 0, "match-case"]);
let collapse = E("collapse");
disableElement(collapse, type == "whitelist", "value", "");
if (collapse.value != "")
options.push([collapse._lastChange, collapse.value]);
let enabledTypes = [];
let disabledTypes = [];
let forceEnabledTypes = [];
for (let typeNode = E("typeGroup").firstChild; typeNode; typeNode = typeNode.nextSibling)
{
let value = typeNode.getAttribute("value");
if (value == "document")
disableElement(typeNode, type != "whitelist", "checked", false);
if (!typeNode._defaultType)
{
if (typeNode.getAttribute("checked") == "true")
forceEnabledTypes.push([typeNode._lastChange || 0, value]);
}
else if (typeNode.getAttribute("checked") == "true")
enabledTypes.push([typeNode._lastChange || 0, value]);
else
disabledTypes.push([typeNode._lastChange || 0, "~" + value]);
}
if (!forceEnabledTypes.length && disabledTypes.length < enabledTypes.length)
options.push.apply(options, disabledTypes);
else
options.push.apply(options, enabledTypes);
options.push.apply(options, forceEnabledTypes);
if (options.length)
{
options.sort(function(a, b) a[0] - b[0]);
filter += "$" + options.map(function(o) o[1]).join(",");
}
}
else
{
let defaultTypes = RegExpFilter.prototype.contentType & ~RegExpFilter.typeMap.DOCUMENT;
let isDefaultType = (RegExpFilter.typeMap[item.typeDescr] & defaultTypes) != 0;
if (!isDefaultType)
filter += "$" + item.typeDescr.toLowerCase().replace(/\_/g, "-");
}
filter = Filter.normalize(filter);
E("regexpWarning").hidden = !Filter.regexpRegExp.test(filter);
let isSlow = false;
let compiledFilter = Filter.fromText(filter);
if (E("regexpWarning").hidden)
{
if (compiledFilter instanceof RegExpFilter && defaultMatcher.isSlowFilter(compiledFilter))
isSlow = true;
}
E("shortpatternWarning").hidden = !isSlow;
E("matchWarning").hidden = compiledFilter instanceof RegExpFilter && compiledFilter.matches(item.location, item.typeDescr, item.docDomain, item.thirdParty);
E("filter").value = filter;
}
function generateLinkText(element, replacement)
{
let template = element.getAttribute("textTemplate");
if (typeof replacement != "undefined")
template = template.replace(/\?1\?/g, replacement)
let [, beforeLink, linkText, afterLink] = /(.*)\[link\](.*)\[\/link\](.*)/.exec(template) || [null, "", template, ""];
while (element.firstChild && element.firstChild.nodeType != Node.ELEMENT_NODE)
element.removeChild(element.firstChild);
while (element.lastChild && element.lastChild.nodeType != Node.ELEMENT_NODE)
element.removeChild(element.lastChild);
if (!element.firstChild)
return;
element.firstChild.textContent = linkText;
element.insertBefore(document.createTextNode(beforeLink), element.firstChild);
element.appendChild(document.createTextNode(afterLink));
}
function updatePatternSelection()
{
let pattern = E("patternGroup").value;
if (pattern == "")
{
pattern = E("customPattern").value;
}
else
{
E("anchorStart").checked = true;
E("anchorEnd").checked = false;
}
function testFilter(/**String*/ filter) /**Boolean*/
{
return RegExpFilter.fromText(filter + "$" + item.typeDescr).matches(item.location, item.typeDescr, item.docDomain, item.thirdParty);
}
let anchorStartCheckbox = E("anchorStart");
if (!/^\*/.test(pattern) && testFilter("||" + pattern))
{
disableElement(anchorStartCheckbox, false, "checked", false);
anchorStartCheckbox.setAttribute("label", anchorStartCheckbox.getAttribute("labelFlexible"));
anchorStartCheckbox.accessKey = anchorStartCheckbox.getAttribute("accesskeyFlexible");
anchorStartCheckbox.flexibleAnchor = true;
}
else
{
disableElement(anchorStartCheckbox, /^\*/.test(pattern) || !testFilter("|" + pattern), "checked", false);
anchorStartCheckbox.setAttribute("label", anchorStartCheckbox.getAttribute("labelRegular"));
anchorStartCheckbox.accessKey = anchorStartCheckbox.getAttribute("accesskeyRegular");
anchorStartCheckbox.flexibleAnchor = false;
}
disableElement(E("anchorEnd"), /[\*\^]$/.test(pattern) || !testFilter(pattern + "|"), "checked", false);
updateFilter();
setAdvancedMode(document.documentElement.getAttribute("advancedMode") == "true");
}
function updateCustomPattern()
{
E("patternGroup").value = "";
updatePatternSelection();
}
function addFilter() {
let filter = Filter.fromText(document.getElementById("filter").value);
filter.disabled = false;
FilterStorage.addFilter(filter);
if (nodes)
Policy.refilterNodes(nodes, item);
return true;
}
function setAdvancedMode(mode) {
advancedMode = mode;
var dialog = document.documentElement;
dialog.setAttribute("advancedMode", advancedMode);
var button = dialog.getButton("disclosure");
button.setAttribute("label", dialog.getAttribute(advancedMode ? "buttonlabeldisclosure_off" : "buttonlabeldisclosure_on"));
updateFilter();
}
function disableElement(element, disable, valueProperty, disabledValue) {
if ((element.getAttribute("disabled") == "true") == disable)
return;
if (disable)
{
element.setAttribute("disabled", "true");
element._abpStoredValue = element[valueProperty];
element[valueProperty] = disabledValue;
}
else
{
element.removeAttribute("disabled");
if ("_abpStoredValue" in element)
element[valueProperty] = element._abpStoredValue;
delete element._abpStoredValue;
}
}
function openPreferences() {
Utils.openFiltersDialog(Filter.fromText(E("filter").value));
}
function doEnable() {
Prefs.enabled = true;
E("disabledWarning").hidden = true;
}
/**
* Selects or unselects all type checkboxes except those
* that are disabled.
*/
function selectAllTypes(/**Boolean*/ select)
{
for (let typeNode = E("typeGroup").firstChild; typeNode; typeNode = typeNode.nextSibling)
if (typeNode.getAttribute("disabled") != "true")
typeNode.checked = select;
updateFilter();
}
+107
View File
@@ -0,0 +1,107 @@
<?xml version="1.0"?>
<!-- This Source Code is subject to the terms of the Mozilla Public License
- version 2.0 (the "License"). You can obtain a copy of the License at
- http://mozilla.org/MPL/2.0/. -->
#filter substitution
<!DOCTYPE overlay SYSTEM "chrome://@ADDON_CHROME_NAME@/locale/composer.dtd">
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
<?xml-stylesheet href="chrome://@ADDON_CHROME_NAME@/skin/composer.css" type="text/css"?>
<dialog id="abp-composer"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
title="&dialog.title;"
onload="init()"
ondialogaccept="return addFilter()"
ondialogdisclosure="setAdvancedMode(!advancedMode)"
buttons="accept,cancel,disclosure"
width="800px"
height="400px"
persist="screenX screenY width height sizemode advancedMode"
advancedMode="false"
buttonlabelaccept="&accept.label;"
buttonlabeldisclosure="&advanced.label;"
buttonlabeldisclosure_on="&advanced.label;"
buttonlabeldisclosure_off="&basic.label;"
windowtype="abp:composer">
<script type="application/x-javascript;version=1.7" src="utils.js"/>
<script type="application/x-javascript;version=1.7" src="composer.js"/>
<popupset>
<tooltip id="domainRestrictionHelp" label="&domainRestriction.help;"/>
</popupset>
<description id="disabledWarning" hidden="true" textTemplate="&disabled.warning;">
<label class="text-link" onclick="doEnable()"/>
</description>
<hbox id="filterBox" align="center">
<label control="filter" value="&filter.label;" accesskey="&filter.accesskey;"/>
<textbox id="filter" flex="1" tabindex="-1" readonly="true"/>
<button id="preferences" label="&preferences.label;" accesskey="&preferences.accesskey;" oncommand="openPreferences()"/>
</hbox>
<radiogroup orient="horizontal" id="filterType" oncommand="updateFilter()">
<radio label="&type.filter.label;" accesskey="&type.filter.accesskey;" value="filterlist" flex="1"/>
<radio label="&type.whitelist.label;" accesskey="&type.whitelist.accesskey;" value="whitelist" flex="1"/>
</radiogroup>
<hbox flex="1">
<groupbox id="pattern" flex="1">
<caption label="&pattern.label;"/>
<radiogroup id="patternGroup" flex="1" oncommand="updatePatternSelection()" style="overflow: auto;">
<description id="patternExplanation">&pattern.explanation;</description>
<description id="regexpWarning" hidden="true">&regexp.warning;</description>
<description id="shortpatternWarning" hidden="true">&shortpattern.warning;</description>
<description id="matchWarning" hidden="true">&match.warning;</description>
<hbox id="customPatternBox">
<radio id="customPatternRadio" label="&custom.pattern.label;" accesskey="&custom.pattern.accesskey;" value="" control="customPattern"/>
<textbox id="customPattern" flex="1" oninput="updateCustomPattern()"/>
</hbox>
</radiogroup>
<hbox id="anchorGroup" pack="start" align="baseline">
<label value="&anchors.label;"/>
<description flex="1" style="margin: 0; padding: 0;">
<checkbox id="anchorStart" labelRegular="&anchor.start.label;" accesskeyRegular="&anchor.start.accesskey;"
labelFlexible="&anchor.start.flexible.label;" accesskeyFlexible="&anchor.start.flexible.accesskey;"
oncommand="updateFilter()"/>
<checkbox id="anchorEnd" label="&anchor.end.label;" accesskey="&anchor.end.accesskey;" oncommand="updateFilter()"/>
</description>
</hbox>
</groupbox>
<groupbox id="options">
<caption label="&options.label;"/>
<checkbox id="firstParty" label="&firstParty.label;" accesskey="&firstParty.accesskey;" oncommand="checkboxUpdated(this);"/>
<checkbox id="thirdParty" label="&thirdParty.label;" accesskey="&thirdParty.accesskey;" oncommand="checkboxUpdated(this);"/>
<checkbox id="matchCase" label="&matchCase.label;" accesskey="&matchCase.accesskey;" oncommand="checkboxUpdated(this);"/>
<hbox align="baseline">
<checkbox id="domainRestrictionEnabled" label="&domainRestriction.label;" accesskey="&domainRestriction.accesskey;" oncommand="checkboxUpdated(this);"/>
<description class="help" value="?" tooltip="domainRestrictionHelp"/>
</hbox>
<textbox id="domainRestriction" oninput="updateFilter()"/>
<label id="typeGroupLabel" value="&types.label;"/>
<hbox>
<label id="selectAllTypes" class="text-link" value="&selectAllTypes.label;" onclick="selectAllTypes(true)"/>
<spacer flex="1"/>
<label id="unselectAllTypes" class="text-link" value="&unselectAllTypes.label;" onclick="selectAllTypes(false)"/>
</hbox>
<vbox flex="1" id="typeGroup"/>
<vbox>
<label control="collapse" value="&collapse.label;" accesskey="&collapse.accesskey;"/>
<menulist id="collapse" oncommand="updateFilter()">
<menupopup>
<menuitem id="collapseDefault" value="" label_yes="&collapse.default.yes.label;" label_no="&collapse.default.no.label;" selected="true"/>
<menuitem label="&collapse.yes.label;" value="collapse"/>
<menuitem label="&collapse.no.label;" value="~collapse"/>
</menupopup>
</menulist>
</vbox>
</groupbox>
</hbox>
</dialog>
+96
View File
@@ -0,0 +1,96 @@
<html>
<head>
<title>Adblock Plus Errors</title>
<style type="text/css">
.warning, .error
{
border: 1px dashed black;
margin: 5px;
padding: 2px;
white-space: pre-wrap;
}
.error
{
background-color: #fff0f0;
}
.warning
{
background-color: #ffffe0;
}
button
{
float: right;
}
</style>
</head>
<body>
<button onclick="window.location.reload();">Refresh</button>
<button onclick="clearErrors();">Clear errors</button>
<script type="application/x-javascript;version=1.7">
try {
Components.utils.import("chrome://adblockplus-modules/content/Utils.jsm");
let text = "You are running Adblock Plus " + Utils.addonVersion;
let build = Utils.addonBuild;
if (build)
text += ", build " + build;
text += ".";
document.write("<p>" + text + "</p>");
} catch (e) {}
let messages = {};
Components.classes["@mozilla.org/consoleservice;1"]
.getService(Components.interfaces.nsIConsoleService)
.getMessageArray(messages, {});
messages = messages.value || [];
messages = messages.filter(function(message)
{
return (message instanceof Components.interfaces.nsIScriptError &&
!/^https?:/i.test(message.sourceName) &&
(/adblock/i.test(message.errorMessage) || /adblock/i.test(message.sourceName)));
});
if (messages.length)
{
document.write("<p>Errors related to Adblock Plus:</p>");
for each (let message in messages)
{
let type = (message.flags & Components.interfaces.nsIScriptError.warningFlag ? "warning" : "error");
let html = "<b>" + (type == "warning" ? "Warning:" : "Error:") + "</b><br>";
html += encodeHTML(message.errorMessage) + "<br><br>";
if (message.sourceLine)
html += "Source line: " + encodeHTML(message.sourceLine) + "<br>";
if (message.sourceName)
html += "Location: " + encodeHTML(message.sourceName) + " line " + message.lineNumber + "<br>";
html = html.replace(/(<br>)+$/, "");
document.write("<div class='" + type + "'>" +
html +
"</div>");
}
}
else
{
document.write("<p>No errors found.</p>");
}
function encodeHTML(string)
{
return string.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
}
function clearErrors()
{
Components.classes["@mozilla.org/consoleservice;1"]
.getService(Components.interfaces.nsIConsoleService)
.reset();
window.location.reload();
}
</script>
</body>
</html>
+331
View File
@@ -0,0 +1,331 @@
/*
* This Source Code is subject to the terms of the Mozilla Public License
* version 2.0 (the "License"). You can obtain a copy of the License at
* http://mozilla.org/MPL/2.0/.
*/
Cu.import("resource://gre/modules/FileUtils.jsm");
/**
* Implementation of backup and restore functionality.
* @class
*/
var Backup =
{
/**
* Template for menu items to be displayed in the Restore menu (for automated
* backups).
* @type Element
*/
restoreTemplate: null,
/**
* Element after which restore items should be inserted.
* @type Element
*/
restoreInsertionPoint: null,
/**
* Regular expression to recognize checksum comments.
*/
CHECKSUM_REGEXP: /^!\s*checksum[\s\-:]+([\w\+\/]+)/i,
/**
* Regular expression to recognize group title comments.
*/
GROUPTITLE_REGEXP: /^!\s*\[(.*)\]((?:\/\w+)*)\s*$/,
/**
* Initializes backup UI.
*/
init: function()
{
this.restoreTemplate = E("restoreBackupTemplate");
this.restoreInsertionPoint = this.restoreTemplate.previousSibling;
this.restoreTemplate.parentNode.removeChild(this.restoreTemplate);
this.restoreTemplate.removeAttribute("id");
this.restoreTemplate.removeAttribute("hidden");
},
/**
* Gets the default download dir, as used by the browser itself.
*/
getDefaultDir: function() /**nsIFile*/
{
try
{
return Utils.prefService.getComplexValue("browser.download.lastDir", Ci.nsILocalFile);
}
catch (e)
{
// No default download location. Default to desktop.
return FileUtils.getDir("Desk", [], false);
}
},
/**
* Saves new default download dir after the user chose a different directory to
* save his files to.
*/
saveDefaultDir: function(/**nsIFile*/ dir)
{
try
{
Utils.prefService.setComplexValue("browser.download.lastDir", Ci.nsILocalFile, dir);
} catch(e) {};
},
/**
* Called when the Restore menu is being opened, fills in "Automated backup"
* entries.
*/
fillRestorePopup: function()
{
while (this.restoreInsertionPoint.nextSibling && !this.restoreInsertionPoint.nextSibling.id)
this.restoreInsertionPoint.parentNode.removeChild(this.restoreInsertionPoint.nextSibling);
let files = FilterStorage.getBackupFiles().reverse();
for (let i = 0; i < files.length; i++)
{
let file = files[i];
let item = this.restoreTemplate.cloneNode(true);
let label = item.getAttribute("label");
label = label.replace(/\?1\?/, Utils.formatTime(file.lastModifiedTime));
item.setAttribute("label", label);
item.addEventListener("command", function()
{
Backup.restoreAllData(file);
}, false);
this.restoreInsertionPoint.parentNode.insertBefore(item, this.restoreInsertionPoint.nextSibling);
}
},
/**
* Lets the user choose a file to restore filters from.
*/
restoreFromFile: function()
{
let picker = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
picker.init(window, E("backupButton").getAttribute("_restoreDialogTitle"), picker.modeOpen);
picker.defaultExtension = ".ini";
picker.appendFilter(E("backupButton").getAttribute("_fileFilterComplete"), "*.ini");
picker.appendFilter(E("backupButton").getAttribute("_fileFilterCustom"), "*.txt");
if (picker.show() != picker.returnCancel)
{
this.saveDefaultDir(picker.file.parent);
if (picker.filterIndex == 0)
this.restoreAllData(picker.file);
else
this.restoreCustomFilters(picker.file);
}
},
/**
* Restores patterns.ini from a file.
*/
restoreAllData: function(/**nsIFile*/ file)
{
let stream = Cc["@mozilla.org/network/file-input-stream;1"].createInstance(Ci.nsIFileInputStream);
stream.init(file, FileUtils.MODE_RDONLY, FileUtils.PERMS_FILE, 0);
stream.QueryInterface(Ci.nsILineInputStream);
let lines = [];
let line = {value: null};
if (stream.readLine(line))
lines.push(line.value);
if (stream.readLine(line))
lines.push(line.value);
stream.close();
let match;
if (lines.length < 2 || lines[0] != "# Adblock Plus preferences" || !(match = /version=(\d+)/.exec(lines[1])))
{
Utils.alert(window, E("backupButton").getAttribute("_restoreError"), E("backupButton").getAttribute("_restoreDialogTitle"));
return;
}
let warning = E("backupButton").getAttribute("_restoreCompleteWarning");
let minVersion = parseInt(match[1], 10);
if (minVersion > FilterStorage.formatVersion)
warning += "\n\n" + E("backupButton").getAttribute("_restoreVersionWarning");
if (!Utils.confirm(window, warning, E("backupButton").getAttribute("_restoreDialogTitle")))
return;
FilterStorage.loadFromDisk(file);
},
/**
* Restores custom filters from a file.
*/
restoreCustomFilters: function(/**nsIFile*/ file)
{
IO.readFromFile(file, true, {
seenHeader: false,
subscription: null,
process: function(line)
{
if (!this.seenHeader)
{
// This should be a header
this.seenHeader = true;
let match = /\[Adblock(?:\s*Plus\s*([\d\.]+)?)?\]/i.exec(line);
if (match)
{
let warning = E("backupButton").getAttribute("_restoreCustomWarning");
let minVersion = match[1];
if (minVersion && Utils.versionComparator.compare(minVersion, Utils.addonVersion) > 0)
warning += "\n\n" + E("backupButton").getAttribute("_restoreVersionWarning");
if (Utils.confirm(window, warning, E("backupButton").getAttribute("_restoreDialogTitle")))
{
let subscriptions = FilterStorage.subscriptions.filter(function(s) s instanceof SpecialSubscription);
for (let i = 0; i < subscriptions.length; i++)
FilterStorage.removeSubscription(subscriptions[i]);
return;
}
else
throw Cr.NS_BASE_STREAM_WOULD_BLOCK;
}
else
throw new Error("Invalid file");
}
else if (line === null)
{
// End of file
if (this.subscription)
FilterStorage.addSubscription(this.subscription);
E("tabs").selectedIndex = 1;
}
else if (Backup.CHECKSUM_REGEXP.test(line))
{
// Ignore checksums
}
else if (Backup.GROUPTITLE_REGEXP.test(line))
{
// New group start
if (this.subscription)
FilterStorage.addSubscription(this.subscription);
let [, title, options] = Backup.GROUPTITLE_REGEXP.exec(line);
this.subscription = SpecialSubscription.create(title);
let defaults = [];
if (options)
options = options.split("/");
for (let j = 0; j < options.length; j++)
if (options[j] in SpecialSubscription.defaultsMap)
defaults.push(options[j]);
if (defaults.length)
this.subscription.defaults = defaults;
}
else
{
// Regular filter
let filter = Filter.fromText(Filter.normalize(line));
if (filter)
{
if (!this.subscription)
this.subscription = SpecialSubscription.create(Utils.getString("newGroup_title"));
this.subscription.filters.push(filter);
}
}
}
}, function(e)
{
if (e && e.result != Cr.NS_BASE_STREAM_WOULD_BLOCK)
{
Cu.reportError(e);
Utils.alert(window, E("backupButton").getAttribute("_restoreError"), E("backupButton").getAttribute("_restoreDialogTitle"));
}
});
},
/**
* Lets the user choose a file to backup filters to.
*/
backupToFile: function()
{
let picker = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
picker.init(window, E("backupButton").getAttribute("_backupDialogTitle"), picker.modeSave);
picker.defaultExtension = ".ini";
picker.appendFilter(E("backupButton").getAttribute("_fileFilterComplete"), "*.ini");
picker.appendFilter(E("backupButton").getAttribute("_fileFilterCustom"), "*.txt");
if (picker.show() != picker.returnCancel)
{
this.saveDefaultDir(picker.file.parent);
if (picker.filterIndex == 0)
this.backupAllData(picker.file);
else
this.backupCustomFilters(picker.file);
}
},
/**
* Writes all patterns.ini data to a file.
*/
backupAllData: function(/**nsIFile*/ file)
{
FilterStorage.saveToDisk(file);
},
/**
* Writes user's custom filters to a file.
*/
backupCustomFilters: function(/**nsIFile*/ file)
{
let subscriptions = FilterStorage.subscriptions.filter(function(s) s instanceof SpecialSubscription);
let list = ["[Adblock Plus 2.0]"];
for (let i = 0; i < subscriptions.length; i++)
{
let subscription = subscriptions[i];
let typeAddition = "";
if (subscription.defaults)
typeAddition = "/" + subscription.defaults.join("/");
list.push("! [" + subscription.title + "]" + typeAddition);
for (let j = 0; j < subscription.filters.length; j++)
{
let filter = subscription.filters[j];
// Skip checksums
if (filter instanceof CommentFilter && this.CHECKSUM_REGEXP.test(filter.text))
continue;
// Skip group headers
if (filter instanceof CommentFilter && this.GROUPTITLE_REGEXP.test(filter.text))
continue;
list.push(filter.text);
}
}
// Insert checksum. Have to add an empty line to the end of the list to
// account for the trailing newline in the file.
list.push("");
let checksum = Utils.generateChecksum(list);
list.pop();
if (checksum)
list.splice(1, 0, "! Checksum: " + checksum);
function generator()
{
for (let i = 0; i < list.length; i++)
yield list[i];
}
IO.writeToFile(file, true, generator(), function(e)
{
if (e)
{
Cu.reportError(e);
Utils.alert(window, E("backupButton").getAttribute("_backupError"), E("backupButton").getAttribute("_backupDialogTitle"));
}
});
}
};
window.addEventListener("load", function()
{
Backup.init();
}, false);
+539
View File
@@ -0,0 +1,539 @@
/*
* This Source Code is subject to the terms of the Mozilla Public License
* version 2.0 (the "License"). You can obtain a copy of the License at
* http://mozilla.org/MPL/2.0/.
*/
/**
* Implementation of the various actions performed on the filters.
* @class
*/
var FilterActions =
{
/**
* Initializes filter actions.
*/
init: function()
{
let me = this;
this.treeElement.parentNode.addEventListener("keypress", function(event)
{
me.keyPress(event);
}, true);
this.treeElement.view = FilterView;
this.treeElement.inputField.addEventListener("keypress", function(event)
{
// Prevent the tree from capturing cursor keys pressed in the input field
if (event.keyCode >= event.DOM_VK_PAGE_UP && event.keyCode <= event.DOM_VK_DOWN)
event.stopPropagation();
}, false);
// Create a copy of the view menu
function fixId(node, newId)
{
if (node.nodeType == node.ELEMENT_NODE)
{
if (node.hasAttribute("id"))
node.setAttribute("id", node.getAttribute("id").replace(/\d+$/, newId));
for (let i = 0, len = node.childNodes.length; i < len; i++)
fixId(node.childNodes[i], newId);
}
return node;
}
E("viewMenu").appendChild(fixId(E("filters-view-menu1").cloneNode(true), "2"));
},
/**
* <tree> element containing the filters.
* @type XULElement
*/
get treeElement() E("filtersTree"),
/**
* Tests whether the tree is currently visible.
*/
get visible()
{
return !this.treeElement.parentNode.collapsed;
},
/**
* Tests whether the tree is currently focused.
* @type Boolean
*/
get focused()
{
let focused = document.commandDispatcher.focusedElement;
while (focused)
{
if ("treeBoxObject" in focused && focused.treeBoxObject == FilterView.boxObject)
return true;
focused = focused.parentNode;
}
return false;
},
/**
* Updates visible filter commands whenever the selected subscription changes.
*/
updateCommands: function()
{
E("filters-add-command").setAttribute("disabled", !FilterView.editable);
},
/**
* Called whenever filter actions menu is opened, initializes menu items.
*/
fillActionsPopup: function()
{
let editable = FilterView.editable;
let items = FilterView.selectedItems.filter(function(i) !i.filter.dummy);
items.sort(function(entry1, entry2) entry1.index - entry2.index);
let activeItems = items.filter(function(i) i.filter instanceof ActiveFilter);
E("filters-edit-command").setAttribute("disabled", !editable || !items.length);
E("filters-delete-command").setAttribute("disabled", !editable || !items.length);
E("filters-resetHitCounts-command").setAttribute("disabled", !activeItems.length);
E("filters-moveUp-command").setAttribute("disabled", !editable || FilterView.isSorted() || !items.length || items[0].index == 0);
E("filters-moveDown-command").setAttribute("disabled", !editable || FilterView.isSorted() || !items.length || items[items.length - 1].index == FilterView.rowCount - 1);
E("filters-copy-command").setAttribute("disabled", !items.length);
E("filters-cut-command").setAttribute("disabled", !editable || !items.length);
E("filters-paste-command").setAttribute("disabled", !editable || !Utils.clipboard.hasDataMatchingFlavors(["text/unicode"], 1, Utils.clipboard.kGlobalClipboard));
},
/**
* Changes sort current order for the tree. Sorts by filter column if the list is unsorted.
* @param {String} order either "ascending" or "descending"
*/
setSortOrder: function(sortOrder)
{
let col = (FilterView.sortColumn ? FilterView.sortColumn.id : "col-filter");
FilterView.sortBy(col, sortOrder);
},
/**
* Toggles the visibility of a tree column.
*/
toggleColumn: function(/**String*/ id)
{
let col = E(id);
col.setAttribute("hidden", col.hidden ? "false" : "true");
},
/**
* Enables or disables all filters in the current selection.
*/
selectionToggleDisabled: function()
{
if (this.treeElement.editingColumn)
return;
let items = FilterView.selectedItems.filter(function(i) i.filter instanceof ActiveFilter);
if (items.length)
{
FilterView.boxObject.beginUpdateBatch();
let newValue = !items[0].filter.disabled;
for (let i = 0; i < items.length; i++)
items[i].filter.disabled = newValue;
FilterView.boxObject.endUpdateBatch();
}
},
/**
* Selects all entries in the list.
*/
selectAll: function()
{
if (this.treeElement.editingColumn)
return;
FilterView.selection.selectAll();
this.treeElement.focus();
},
/**
* Starts editing the current filter.
*/
startEditing: function()
{
if (this.treeElement.editingColumn)
return;
this.treeElement.startEditing(FilterView.selection.currentIndex, FilterView.boxObject.columns.getNamedColumn("col-filter"));
},
/**
* Starts editing a new filter at the current position.
*/
insertFilter: function()
{
if (!FilterView.editable || this.treeElement.editingColumn)
return;
FilterView.insertEditDummy();
this.startEditing();
let tree = this.treeElement;
let listener = function(event)
{
if (event.attrName == "editing" && tree.editingRow < 0)
{
tree.removeEventListener("DOMAttrModified", listener, false);
FilterView.removeEditDummy();
}
}
tree.addEventListener("DOMAttrModified", listener, false);
},
/**
* Deletes items from the list.
*/
deleteItems: function(/**Array*/ items)
{
let oldIndex = FilterView.selection.currentIndex;
items.sort(function(entry1, entry2) entry2.index - entry1.index);
for (let i = 0; i < items.length; i++)
FilterStorage.removeFilter(items[i].filter, FilterView._subscription, items[i].index);
FilterView.selectRow(oldIndex);
},
/**
* Deletes selected filters.
*/
deleteSelected: function()
{
if (!FilterView.editable || this.treeElement.editingColumn)
return;
let items = FilterView.selectedItems;
if (items.length == 0 || (items.length >= 2 && !Utils.confirm(window, this.treeElement.getAttribute("_removewarning"))))
return;
this.deleteItems(items)
},
/**
* Resets hit counts of the selected filters.
*/
resetHitCounts: function()
{
if (this.treeElement.editingColumn)
return;
let items = FilterView.selectedItems.filter(function(i) i.filter instanceof ActiveFilter);
if (items.length)
FilterStorage.resetHitCounts(items.map(function(i) i.filter));
},
/**
* Moves items to a different position in the list.
* @param {Array} items
* @param {Integer} offset negative offsets move the items up, positive down
*/
_moveItems: function(/**Array*/ items, /**Integer*/ offset)
{
if (!items.length)
return;
if (offset < 0)
{
items.sort(function(entry1, entry2) entry1.index - entry2.index);
let position = items[0].index + offset;
if (position < 0)
return;
for (let i = 0; i < items.length; i++)
FilterStorage.moveFilter(items[i].filter, FilterView._subscription, items[i].index, position++);
FilterView.selection.rangedSelect(position - items.length, position - 1, false);
}
else if (offset > 0)
{
items.sort(function(entry1, entry2) entry2.index - entry1.index);
let position = items[0].index + offset;
if (position >= FilterView.rowCount)
return;
for (let i = 0; i < items.length; i++)
FilterStorage.moveFilter(items[i].filter, FilterView._subscription, items[i].index, position--);
FilterView.selection.rangedSelect(position + 1, position + items.length, false);
}
},
/**
* Moves selected filters one line up.
*/
moveUp: function()
{
if (!FilterView.editable || FilterView.isEmpty || FilterView.isSorted() || this.treeElement.editingColumn)
return;
this._moveItems(FilterView.selectedItems, -1);
},
/**
* Moves selected filters one line down.
*/
moveDown: function()
{
if (!FilterView.editable || FilterView.isEmpty || FilterView.isSorted() || this.treeElement.editingColumn)
return;
this._moveItems(FilterView.selectedItems, 1);
},
/**
* Fills the context menu of the filters columns.
*/
fillColumnPopup: function(/**Element*/ element)
{
let suffix = element.id.match(/\d+$/)[0] || "1";
E("filters-view-filter" + suffix).setAttribute("checked", !E("col-filter").hidden);
E("filters-view-slow" + suffix).setAttribute("checked", !E("col-slow").hidden);
E("filters-view-enabled" + suffix).setAttribute("checked", !E("col-enabled").hidden);
E("filters-view-hitcount" + suffix).setAttribute("checked", !E("col-hitcount").hidden);
E("filters-view-lasthit" + suffix).setAttribute("checked", !E("col-lasthit").hidden);
let sortColumn = FilterView.sortColumn;
let sortColumnID = (sortColumn ? sortColumn.id : null);
let sortDir = (sortColumn ? sortColumn.getAttribute("sortDirection") : "natural");
E("filters-sort-none" + suffix).setAttribute("checked", sortColumn == null);
E("filters-sort-filter" + suffix).setAttribute("checked", sortColumnID == "col-filter");
E("filters-sort-enabled" + suffix).setAttribute("checked", sortColumnID == "col-enabled");
E("filters-sort-hitcount" + suffix).setAttribute("checked", sortColumnID == "col-hitcount");
E("filters-sort-lasthit" + suffix).setAttribute("checked", sortColumnID == "col-lasthit");
E("filters-sort-asc" + suffix).setAttribute("checked", sortDir == "ascending");
E("filters-sort-desc" + suffix).setAttribute("checked", sortDir == "descending");
},
/**
* Fills tooltip with the item data.
*/
fillTooltip: function(event)
{
let item = FilterView.getItemAt(event.clientX, event.clientY);
if (!item || item.filter.dummy)
{
event.preventDefault();
return;
}
function setMultilineContent(box, text)
{
while (box.firstChild)
box.removeChild(box.firstChild);
for (var i = 0; i < text.length; i += 80)
{
var description = document.createElement("description");
description.setAttribute("value", text.substr(i, 80));
box.appendChild(description);
}
}
setMultilineContent(E("tooltip-filter"), item.filter.text);
E("tooltip-hitcount-row").hidden = !(item.filter instanceof ActiveFilter);
E("tooltip-lasthit-row").hidden = !(item.filter instanceof ActiveFilter) || !item.filter.lastHit;
if (item.filter instanceof ActiveFilter)
{
E("tooltip-hitcount").setAttribute("value", item.filter.hitCount)
E("tooltip-lasthit").setAttribute("value", Utils.formatTime(item.filter.lastHit))
}
E("tooltip-additional").hidden = false;
if (item.filter instanceof InvalidFilter && item.filter.reason)
E("tooltip-additional").textContent = item.filter.reason;
else if (item.filter instanceof RegExpFilter && defaultMatcher.isSlowFilter(item.filter))
E("tooltip-additional").textContent = Utils.getString("filter_regexp_tooltip");
else
E("tooltip-additional").hidden = true;
},
/**
* Called whenever a key is pressed on the list.
*/
keyPress: function(/**Event*/ event)
{
if (event.target != E("filtersTree"))
return;
let modifiers = 0;
if (event.altKey)
modifiers |= SubscriptionActions._altMask;
if (event.ctrlKey)
modifiers |= SubscriptionActions._ctrlMask;
if (event.metaKey)
modifiers |= SubscriptionActions._metaMask;
if (event.charCode == " ".charCodeAt(0) && modifiers == 0 && !E("col-enabled").hidden)
this.selectionToggleDisabled();
else if (event.keyCode == Ci.nsIDOMKeyEvent.DOM_VK_UP && modifiers == SubscriptionActions._accelMask)
{
E("filters-moveUp-command").doCommand();
event.preventDefault();
event.stopPropagation();
}
else if (event.keyCode == Ci.nsIDOMKeyEvent.DOM_VK_DOWN && modifiers == SubscriptionActions._accelMask)
{
E("filters-moveDown-command").doCommand();
event.preventDefault();
event.stopPropagation();
}
},
/**
* Copies selected items to clipboard and optionally removes them from the
* list after that.
*/
copySelected: function(/**Boolean*/ keep)
{
let items = FilterView.selectedItems;
if (!items.length)
return;
items.sort(function(entry1, entry2) entry1.index - entry2.index);
let text = items.map(function(i) i.filter.text).join(IO.lineBreak);
Utils.clipboardHelper.copyString(text);
if (!keep && FilterView.editable && !this.treeElement.editingColumn)
this.deleteItems(items);
},
/**
* Pastes text from clipboard as filters at the current position.
*/
paste: function()
{
if (!FilterView.editable || this.treeElement.editingColumn)
return;
let transferable = Cc["@mozilla.org/widget/transferable;1"].createInstance(Ci.nsITransferable);
transferable.addDataFlavor("text/unicode");
let data;
try
{
data = {};
Utils.clipboard.getData(transferable, Utils.clipboard.kGlobalClipboard);
transferable.getTransferData("text/unicode", data, {});
data = data.value.QueryInterface(Ci.nsISupportsString).data;
}
catch (e) {
return;
}
let item = FilterView.currentItem;
let position = (item ? item.index : FilterView.data.length);
let lines = data.replace(/\r/g, "").split("\n");
for (let i = 0; i < lines.length; i++)
{
let filter = Filter.fromText(lines[i]);
if (filter)
FilterStorage.addFilter(filter, FilterView._subscription, position++);
}
},
dragItems: null,
/**
* Called whenever the user starts a drag operation.
*/
startDrag: function(/**Event*/ event)
{
let items = FilterView.selectedItems;
if (!items.length)
return;
items.sort(function(entry1, entry2) entry1.index - entry2.index);
event.dataTransfer.setData("text/plain", items.map(function(i) i.filter.text).join(IO.lineBreak));
this.dragItems = items;
event.stopPropagation();
},
/**
* Called to check whether moving the items to the given position is possible.
*/
canDrop: function(/**Integer*/ newPosition, /**nsIDOMDataTransfer*/ dataTransfer)
{
if (!FilterView.editable || this.treeElement.editingColumn)
return false;
// If we aren't dragging items then maybe we got filters as plain text
if (!this.dragItems)
return dataTransfer && dataTransfer.getData("text/plain");
if (FilterView.isEmpty || FilterView.isSorted())
return false;
if (newPosition < this.dragItems[0].index)
return true;
else if (newPosition > this.dragItems[this.dragItems.length - 1].index + 1)
return true;
else
return false;
},
/**
* Called when the user decides to drop the items.
*/
drop: function(/**Integer*/ newPosition, /**nsIDOMDataTransfer*/ dataTransfer)
{
if (!FilterView.editable || this.treeElement.editingColumn)
return;
if (!this.dragItems)
{
// We got filters as plain text, insert them into the list
let data = (dataTransfer ? dataTransfer.getData("text/plain") : null);
if (data)
{
let lines = data.replace(/\r/g, "").split("\n");
for (let i = 0; i < lines.length; i++)
{
let filter = Filter.fromText(lines[i]);
if (filter)
FilterStorage.addFilter(filter, FilterView._subscription, newPosition++);
}
}
return;
}
if (FilterView.isEmpty || FilterView.isSorted())
return;
if (newPosition < this.dragItems[0].index)
this._moveItems(this.dragItems, newPosition - this.dragItems[0].index);
else if (newPosition > this.dragItems[this.dragItems.length - 1].index + 1)
this._moveItems(this.dragItems, newPosition - this.dragItems[this.dragItems.length - 1].index - 1);
},
/**
* Called whenever the a drag operation finishes.
*/
endDrag: function(/**Event*/ event)
{
this.dragItems = null;
},
/**
* Called if filters have been dragged into a subscription and need to be removed.
*/
removeDraggedFilters: function()
{
if (!this.dragItems)
return;
this.deleteItems(this.dragItems);
}
};
window.addEventListener("load", function()
{
FilterActions.init();
}, false);
+820
View File
@@ -0,0 +1,820 @@
/*
* This Source Code is subject to the terms of the Mozilla Public License
* version 2.0 (the "License"). You can obtain a copy of the License at
* http://mozilla.org/MPL/2.0/.
*/
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
/**
* nsITreeView implementation to display filters of a particular filter
* subscription.
* @class
*/
var FilterView =
{
/**
* Initialization function.
*/
init: function()
{
if (this.sortProcs)
return;
function compareText(/**Filter*/ filter1, /**Filter*/ filter2)
{
if (filter1.text < filter2.text)
return -1;
else if (filter1.text > filter2.text)
return 1;
else
return 0;
}
function compareSlow(/**Filter*/ filter1, /**Filter*/ filter2)
{
let isSlow1 = filter1 instanceof RegExpFilter && defaultMatcher.isSlowFilter(filter1);
let isSlow2 = filter2 instanceof RegExpFilter && defaultMatcher.isSlowFilter(filter2);
return isSlow1 - isSlow2;
}
function compareEnabled(/**Filter*/ filter1, /**Filter*/ filter2)
{
let hasEnabled1 = (filter1 instanceof ActiveFilter ? 1 : 0);
let hasEnabled2 = (filter2 instanceof ActiveFilter ? 1 : 0);
if (hasEnabled1 != hasEnabled2)
return hasEnabled1 - hasEnabled2;
else if (hasEnabled1)
return (filter2.disabled - filter1.disabled);
else
return 0;
}
function compareHitCount(/**Filter*/ filter1, /**Filter*/ filter2)
{
let hasHitCount1 = (filter1 instanceof ActiveFilter ? 1 : 0);
let hasHitCount2 = (filter2 instanceof ActiveFilter ? 1 : 0);
if (hasHitCount1 != hasHitCount2)
return hasHitCount1 - hasHitCount2;
else if (hasHitCount1)
return filter1.hitCount - filter2.hitCount;
else
return 0;
}
function compareLastHit(/**Filter*/ filter1, /**Filter*/ filter2)
{
let hasLastHit1 = (filter1 instanceof ActiveFilter ? 1 : 0);
let hasLastHit2 = (filter2 instanceof ActiveFilter ? 1 : 0);
if (hasLastHit1 != hasLastHit2)
return hasLastHit1 - hasLastHit2;
else if (hasLastHit1)
return filter1.lastHit - filter2.lastHit;
else
return 0;
}
/**
* Creates a sort function from a primary and a secondary comparison function.
* @param {Function} cmpFunc comparison function to be called first
* @param {Function} fallbackFunc (optional) comparison function to be called if primary function returns 0
* @param {Boolean} desc if true, the result of the primary function (not the secondary function) will be reversed - sorting in descending order
* @result {Function} comparison function to be used
*/
function createSortFunction(cmpFunc, fallbackFunc, desc)
{
let factor = (desc ? -1 : 1);
return function(entry1, entry2)
{
// Comment replacements not bound to a filter always go last
let isLast1 = ("origFilter" in entry1 && entry1.filter == null);
let isLast2 = ("origFilter" in entry2 && entry2.filter == null);
if (isLast1)
return (isLast2 ? 0 : 1)
else if (isLast2)
return -1;
let ret = cmpFunc(entry1.filter, entry2.filter);
if (ret == 0 && fallbackFunc)
return fallbackFunc(entry1.filter, entry2.filter);
else
return factor * ret;
}
}
this.sortProcs = {
filter: createSortFunction(compareText, null, false),
filterDesc: createSortFunction(compareText, null, true),
slow: createSortFunction(compareSlow, compareText, true),
slowDesc: createSortFunction(compareSlow, compareText, false),
enabled: createSortFunction(compareEnabled, compareText, false),
enabledDesc: createSortFunction(compareEnabled, compareText, true),
hitcount: createSortFunction(compareHitCount, compareText, false),
hitcountDesc: createSortFunction(compareHitCount, compareText, true),
lasthit: createSortFunction(compareLastHit, compareText, false),
lasthitDesc: createSortFunction(compareLastHit, compareText, true)
};
let me = this;
let proxy = function()
{
return me._onChange.apply(me, arguments);
};
FilterNotifier.addListener(proxy);
window.addEventListener("unload", function()
{
FilterNotifier.removeListener(proxy);
}, false);
},
/**
* Filter change processing.
* @see FilterNotifier.addListener()
*/
_onChange: function(action, item, param1, param2, param3)
{
switch (action)
{
case "subscription.updated":
{
if (item == this._subscription)
this.refresh(true);
break;
}
case "filter.disabled":
case "filter.hitCount":
case "filter.lastHit":
{
this.updateFilter(item);
break;
}
case "filter.added":
{
let subscription = param1;
let position = param2;
if (subscription == this._subscription)
this.addFilterAt(position, item);
break;
}
case "filter.removed":
{
let subscription = param1;
let position = param2;
if (subscription == this._subscription)
this.removeFilterAt(position);
break;
}
case "filter.moved":
{
let subscription = param1;
let oldPosition = param2;
let newPosition = param3;
if (subscription == this._subscription)
this.moveFilterAt(oldPosition, newPosition);
break;
}
}
},
/**
* Box object of the tree that this view is attached to.
* @type nsITreeBoxObject
*/
boxObject: null,
/**
* Map of used cell properties to the corresponding nsIAtom representations.
*/
atoms: null,
/**
* "Filter" to be displayed if no filter group is selected.
*/
noGroupDummy: null,
/**
* "Filter" to be displayed if the selected group is empty.
*/
noFiltersDummy: null,
/**
* "Filter" to be displayed for a new filter being edited.
*/
editDummy: null,
/**
* Displayed list of filters, might be sorted.
* @type Filter[]
*/
data: [],
/**
* <tree> element that the view is attached to.
* @type XULElement
*/
get treeElement() this.boxObject ? this.boxObject.treeBody.parentNode : null,
/**
* Checks whether the list is currently empty (regardless of dummy entries).
* @type Boolean
*/
get isEmpty()
{
return !this._subscription || !this._subscription.filters.length;
},
/**
* Checks whether the filters in the view can be changed.
* @type Boolean
*/
get editable()
{
return (FilterView._subscription instanceof SpecialSubscription);
},
/**
* Returns current item of the list.
* @type Object
*/
get currentItem()
{
let index = this.selection.currentIndex;
if (index >= 0 && index < this.data.length)
return this.data[index];
return null;
},
/**
* Returns items that are currently selected in the list.
* @type Object[]
*/
get selectedItems()
{
let items = []
for (let i = 0; i < this.selection.getRangeCount(); i++)
{
let min = {};
let max = {};
this.selection.getRangeAt(i, min, max);
for (let j = min.value; j <= max.value; j++)
if (j >= 0 && j < this.data.length)
items.push(this.data[j]);
}
return items;
},
getItemAt: function(x, y)
{
let row = this.boxObject.getRowAt(x, y);
if (row >= 0 && row < this.data.length)
return this.data[row];
else
return null;
},
_subscription: 0,
/**
* Filter subscription being displayed.
* @type Subscription
*/
get subscription() this._subscription,
set subscription(value)
{
if (value == this._subscription)
return;
// Make sure the editor is done before we update the list.
if (this.treeElement)
this.treeElement.stopEditing(true);
this._subscription = value;
this.refresh(true);
},
/**
* Will be true if updates are outstanding because the list was hidden.
*/
_dirty: false,
/**
* Updates internal view data after a change.
* @param {Boolean} force if false, a refresh will only happen if previous
* changes were suppressed because the list was hidden
*/
refresh: function(force)
{
if (FilterActions.visible)
{
if (!force && !this._dirty)
return;
this._dirty = false;
this.updateData();
this.selectRow(0);
}
else
this._dirty = true;
},
/**
* Map of comparison functions by column ID or column ID + "Desc" for
* descending sort order.
* @const
*/
sortProcs: null,
/**
* Column that the list is currently sorted on.
* @type Element
*/
sortColumn: null,
/**
* Sorting function currently in use.
* @type Function
*/
sortProc: null,
/**
* Resorts the list.
* @param {String} col ID of the column to sort on. If null, the natural order is restored.
* @param {String} direction "ascending" or "descending", if null the sort order is toggled.
*/
sortBy: function(col, direction)
{
let newSortColumn = null;
if (col)
{
newSortColumn = this.boxObject.columns.getNamedColumn(col).element;
if (!direction)
{
if (this.sortColumn == newSortColumn)
direction = (newSortColumn.getAttribute("sortDirection") == "ascending" ? "descending" : "ascending");
else
direction = "ascending";
}
}
if (this.sortColumn && this.sortColumn != newSortColumn)
this.sortColumn.removeAttribute("sortDirection");
this.sortColumn = newSortColumn;
if (this.sortColumn)
{
this.sortColumn.setAttribute("sortDirection", direction);
this.sortProc = this.sortProcs[col.replace(/^col-/, "") + (direction == "descending" ? "Desc" : "")];
}
else
this.sortProc = null;
if (this.data.length > 1)
{
this.updateData();
this.boxObject.invalidate();
}
},
/**
* Inserts dummy entry into the list if necessary.
*/
addDummyRow: function()
{
if (this.boxObject && this.data.length == 0)
{
if (this._subscription)
this.data.splice(0, 0, this.noFiltersDummy);
else
this.data.splice(0, 0, this.noGroupDummy);
this.boxObject.rowCountChanged(0, 1);
}
},
/**
* Removes dummy entry from the list if present.
*/
removeDummyRow: function()
{
if (this.boxObject && this.isEmpty && this.data.length)
{
this.data.splice(0, 1);
this.boxObject.rowCountChanged(0, -1);
}
},
/**
* Inserts dummy row when a new filter is being edited.
*/
insertEditDummy: function()
{
FilterView.removeDummyRow();
let position = this.selection.currentIndex;
if (position >= this.data.length)
position = this.data.length - 1;
if (position < 0)
position = 0;
this.editDummy.index = (position < this.data.length ? this.data[position].index : this.data.length);
this.editDummy.position = position;
this.data.splice(position, 0, this.editDummy);
this.boxObject.rowCountChanged(position, 1);
this.selectRow(position);
},
/**
* Removes dummy row once the edit is finished.
*/
removeEditDummy: function()
{
let position = this.editDummy.position;
if (typeof position != "undefined" && position < this.data.length && this.data[position] == this.editDummy)
{
this.data.splice(position, 1);
this.boxObject.rowCountChanged(position, -1);
FilterView.addDummyRow();
this.selectRow(position);
}
},
/**
* Selects a row in the tree and makes sure it is visible.
*/
selectRow: function(row)
{
if (this.selection)
{
row = Math.min(Math.max(row, 0), this.data.length - 1);
this.selection.select(row);
this.boxObject.ensureRowIsVisible(row);
}
},
/**
* Finds a particular filter in the list and selects it.
*/
selectFilter: function(/**Filter*/ filter)
{
let index = -1;
for (let i = 0; i < this.data.length; i++)
{
if (this.data[i].filter == filter)
{
index = i;
break;
}
}
if (index >= 0)
{
this.selectRow(index);
this.treeElement.focus();
}
},
/**
* Updates value of data property on sorting or filter subscription changes.
*/
updateData: function()
{
let oldCount = this.rowCount;
if (this._subscription && this._subscription.filters.length)
{
this.data = this._subscription.filters.map(function(f, i) ({index: i, filter: f}));
if (this.sortProc)
{
// Hide comments in the list, they should be sorted like the filter following them
let followingFilter = null;
for (let i = this.data.length - 1; i >= 0; i--)
{
if (this.data[i].filter instanceof CommentFilter)
{
this.data[i].origFilter = this.data[i].filter;
this.data[i].filter = followingFilter;
}
else
followingFilter = this.data[i].filter;
}
this.data.sort(this.sortProc);
// Restore comments
for (let i = 0; i < this.data.length; i++)
{
if ("origFilter" in this.data[i])
{
this.data[i].filter = this.data[i].origFilter;
delete this.data[i].origFilter;
}
}
}
}
else
this.data = [];
if (this.boxObject)
{
this.boxObject.rowCountChanged(0, -oldCount);
this.boxObject.rowCountChanged(0, this.rowCount);
}
this.addDummyRow();
},
/**
* Called to update the view when a filter property is changed.
*/
updateFilter: function(/**Filter*/ filter)
{
for (let i = 0; i < this.data.length; i++)
if (this.data[i].filter == filter)
this.boxObject.invalidateRow(i);
},
/**
* Called if a filter has been inserted at the specified position.
*/
addFilterAt: function(/**Integer*/ position, /**Filter*/ filter)
{
if (this.data.length == 1 && this.data[0].filter.dummy)
{
this.data.splice(0, 1);
this.boxObject.rowCountChanged(0, -1);
}
if (this.sortProc)
{
this.updateData();
for (let i = 0; i < this.data.length; i++)
{
if (this.data[i].index == position)
{
position = i;
break;
}
}
}
else
{
for (let i = 0; i < this.data.length; i++)
if (this.data[i].index >= position)
this.data[i].index++;
this.data.splice(position, 0, {index: position, filter: filter});
}
this.boxObject.rowCountChanged(position, 1);
this.selectRow(position);
},
/**
* Called if a filter has been removed at the specified position.
*/
removeFilterAt: function(/**Integer*/ position)
{
for (let i = 0; i < this.data.length; i++)
{
if (this.data[i].index == position)
{
this.data.splice(i, 1);
this.boxObject.rowCountChanged(i, -1);
i--;
}
else if (this.data[i].index > position)
this.data[i].index--;
}
this.addDummyRow();
},
/**
* Called if a filter has been moved within the list.
*/
moveFilterAt: function(/**Integer*/ oldPosition, /**Integer*/ newPosition)
{
let dir = (oldPosition < newPosition ? 1 : -1);
for (let i = 0; i < this.data.length; i++)
{
if (this.data[i].index == oldPosition)
this.data[i].index = newPosition;
else if (dir * this.data[i].index > dir * oldPosition && dir * this.data[i].index <= dir * newPosition)
this.data[i].index -= dir;
}
if (!this.sortProc)
{
let item = this.data[oldPosition];
this.data.splice(oldPosition, 1);
this.data.splice(newPosition, 0, item);
this.boxObject.invalidateRange(Math.min(oldPosition, newPosition), Math.max(oldPosition, newPosition));
}
},
QueryInterface: XPCOMUtils.generateQI([Ci.nsITreeView]),
setTree: function(boxObject)
{
this.init();
this.boxObject = boxObject;
if (this.boxObject)
{
this.noGroupDummy = {index: 0, filter: {text: this.boxObject.treeBody.getAttribute("noGroupText"), dummy: true}};
this.noFiltersDummy = {index: 0, filter: {text: this.boxObject.treeBody.getAttribute("noFiltersText"), dummy: true}};
this.editDummy = {filter: {text: ""}};
let atomService = Cc["@mozilla.org/atom-service;1"].getService(Ci.nsIAtomService);
let stringAtoms = ["col-filter", "col-enabled", "col-hitcount", "col-lasthit", "type-comment", "type-filterlist", "type-whitelist", "type-elemhide", "type-invalid"];
let boolAtoms = ["selected", "dummy", "slow", "disabled"];
this.atoms = {};
for each (let atom in stringAtoms)
this.atoms[atom] = atomService.getAtom(atom);
for each (let atom in boolAtoms)
{
this.atoms[atom + "-true"] = atomService.getAtom(atom + "-true");
this.atoms[atom + "-false"] = atomService.getAtom(atom + "-false");
}
let columns = this.boxObject.columns;
for (let i = 0; i < columns.length; i++)
if (columns[i].element.hasAttribute("sortDirection"))
this.sortBy(columns[i].id, columns[i].element.getAttribute("sortDirection"));
this.refresh(true);
}
},
selection: null,
get rowCount() this.data.length,
getCellText: function(row, col)
{
if (row < 0 || row >= this.data.length)
return null;
col = col.id;
if (col != "col-filter" && col != "col-slow" && col != "col-hitcount" && col != "col-lasthit")
return null;
let filter = this.data[row].filter;
if (col == "col-filter")
return filter.text;
else if (col == "col-slow")
return (filter instanceof RegExpFilter && defaultMatcher.isSlowFilter(filter) ? "!" : null);
else if (filter instanceof ActiveFilter)
{
if (col == "col-hitcount")
return filter.hitCount;
else if (col == "col-lasthit")
return (filter.lastHit ? Utils.formatTime(filter.lastHit) : null);
}
return null;
},
generateProperties: function(list, properties)
{
if (properties)
{
// Gecko 21 and below: we have an nsISupportsArray parameter, add atoms
// to that.
for (let i = 0; i < list.length; i++)
if (list[i] in this.atoms)
properties.AppendElement(this.atoms[list[i]]);
return null;
}
else
{
// Gecko 22+: no parameter, just return a string
return list.join(" ");
}
},
getColumnProperties: function(col, properties)
{
return this.generateProperties(["col-" + col.id], properties);
},
getRowProperties: function(row, properties)
{
if (row < 0 || row >= this.data.length)
return "";
let list = [];
let filter = this.data[row].filter;
list.push("selected-" + this.selection.isSelected(row));
list.push("slow-" + (filter instanceof RegExpFilter && defaultMatcher.isSlowFilter(filter)));
if (filter instanceof ActiveFilter)
list.push("disabled-" + filter.disabled);
list.push("dummy-" + ("dummy" in filter));
if (filter instanceof CommentFilter)
list.push("type-comment");
else if (filter instanceof BlockingFilter)
list.push("type-filterlist");
else if (filter instanceof WhitelistFilter)
list.push("type-whitelist");
else if (filter instanceof ElemHideFilter)
list.push("type-elemhide");
else if (filter instanceof InvalidFilter)
list.push("type-invalid");
return this.generateProperties(list, properties);
},
getCellProperties: function(row, col, properties)
{
return this.getRowProperties(row, properties) + " " + this.getColumnProperties(col, properties);
},
cycleHeader: function(col)
{
let oldDirection = col.element.getAttribute("sortDirection");
if (oldDirection == "ascending")
this.sortBy(col.id, "descending");
else if (oldDirection == "descending")
this.sortBy(null, null);
else
this.sortBy(col.id, "ascending");
},
isSorted: function()
{
return (this.sortProc != null);
},
canDrop: function(row, orientation, dataTransfer)
{
if (orientation == Ci.nsITreeView.DROP_ON || row < 0 || row >= this.data.length || !this.editable)
return false;
let item = this.data[row];
let position = (orientation == Ci.nsITreeView.DROP_BEFORE ? item.index : item.index + 1);
return FilterActions.canDrop(position, dataTransfer);
},
drop: function(row, orientation, dataTransfer)
{
if (orientation == Ci.nsITreeView.DROP_ON || row < 0 || row >= this.data.length || !this.editable)
return;
let item = this.data[row];
let position = (orientation == Ci.nsITreeView.DROP_BEFORE ? item.index : item.index + 1);
FilterActions.drop(position, dataTransfer);
},
isEditable: function(row, col)
{
if (row < 0 || row >= this.data.length || !this.editable)
return false;
let filter = this.data[row].filter;
if (col.id == "col-filter")
return !("dummy" in filter);
else
return false;
},
setCellText: function(row, col, value)
{
if (row < 0 || row >= this.data.length || col.id != "col-filter")
return;
let oldFilter = this.data[row].filter;
let position = this.data[row].index;
value = Filter.normalize(value);
if (!value || value == oldFilter.text)
return;
// Make sure we don't get called recursively (see https://adblockplus.org/forum/viewtopic.php?t=9003)
this.treeElement.stopEditing();
let newFilter = Filter.fromText(value);
if (this.data[row] == this.editDummy)
this.removeEditDummy();
else
FilterStorage.removeFilter(oldFilter, this._subscription, position);
FilterStorage.addFilter(newFilter, this._subscription, position);
},
cycleCell: function(row, col)
{
if (row < 0 || row >= this.data.length || col.id != "col-enabled")
return;
let filter = this.data[row].filter;
if (filter instanceof ActiveFilter)
filter.disabled = !filter.disabled;
},
isContainer: function(row) false,
isContainerOpen: function(row) false,
isContainerEmpty: function(row) true,
getLevel: function(row) 0,
getParentIndex: function(row) -1,
hasNextSibling: function(row, afterRow) false,
toggleOpenState: function(row) {},
getProgressMode: function() null,
getImageSrc: function() null,
isSeparator: function() false,
performAction: function() {},
performActionOnRow: function() {},
performActionOnCell: function() {},
getCellValue: function() null,
setCellValue: function() {},
selectionChanged: function() {},
};
+263
View File
@@ -0,0 +1,263 @@
/*
* This Source Code is subject to the terms of the Mozilla Public License
* version 2.0 (the "License"). You can obtain a copy of the License at
* http://mozilla.org/MPL/2.0/.
*/
/**
* Implementation of the filter search functionality.
* @class
*/
var FilterSearch =
{
/**
* Initializes findbar widget.
*/
init: function()
{
let filters = E("filtersTree");
for (let prop in FilterSearch.fakeBrowser)
filters[prop] = FilterSearch.fakeBrowser[prop];
Object.defineProperty(filters, "_lastSearchString", {
get: function()
{
return this.finder.searchString;
},
enumerable: true,
configurable: true
});
let findbar = E("findbar");
findbar.browser = filters;
findbar.addEventListener("keypress", function(event)
{
// Work-around for bug 490047
if (event.keyCode == KeyEvent.DOM_VK_RETURN)
event.preventDefault();
}, false);
// Hack to prevent "highlight all" from getting enabled
findbar.toggleHighlight = function() {};
},
/**
* Performs a text search.
* @param {String} text text to be searched
* @param {Integer} direction search direction: -1 (backwards), 0 (forwards
* starting with current), 1 (forwards starting with next)
* @param {Boolean} caseSensitive if true, a case-sensitive search is performed
* @result {Integer} one of the nsITypeAheadFind constants
*/
search: function(text, direction, caseSensitive)
{
function normalizeString(string) caseSensitive ? string : string.toLowerCase();
function findText(text, direction, startIndex)
{
let list = E("filtersTree");
let col = list.columns.getNamedColumn("col-filter");
let count = list.view.rowCount;
for (let i = startIndex + direction; i >= 0 && i < count; i += (direction || 1))
{
let filter = normalizeString(list.view.getCellText(i, col));
if (filter.indexOf(text) >= 0)
{
FilterView.selectRow(i);
return true;
}
}
return false;
}
text = normalizeString(text);
// First try to find the entry in the current list
if (findText(text, direction, E("filtersTree").currentIndex))
return Ci.nsITypeAheadFind.FIND_FOUND;
// Now go through the other subscriptions
let result = Ci.nsITypeAheadFind.FIND_FOUND;
let subscriptions = FilterStorage.subscriptions.slice();
subscriptions.sort((s1, s2) => (s1 instanceof SpecialSubscription) - (s2 instanceof SpecialSubscription));
let current = subscriptions.indexOf(FilterView.subscription);
direction = direction || 1;
for (let i = current + direction; ; i+= direction)
{
if (i < 0)
{
i = subscriptions.length - 1;
result = Ci.nsITypeAheadFind.FIND_WRAPPED;
}
else if (i >= subscriptions.length)
{
i = 0;
result = Ci.nsITypeAheadFind.FIND_WRAPPED;
}
if (i == current)
break;
let subscription = subscriptions[i];
for (let j = 0; j < subscription.filters.length; j++)
{
let filter = normalizeString(subscription.filters[j].text);
if (filter.indexOf(text) >= 0)
{
let list = E(subscription instanceof SpecialSubscription ? "groups" : "subscriptions");
let node = Templater.getNodeForData(list, "subscription", subscription);
if (!node)
break;
// Select subscription in its list and restore focus after that
let oldFocus = document.commandDispatcher.focusedElement;
E("tabs").selectedIndex = (subscription instanceof SpecialSubscription ? 1 : 0);
list.ensureElementIsVisible(node);
list.selectItem(node);
if (oldFocus)
{
oldFocus.focus();
Utils.runAsync(() => oldFocus.focus());
}
Utils.runAsync(() => findText(text, direction, direction == 1 ? -1 : subscription.filters.length));
return result;
}
}
}
return Ci.nsITypeAheadFind.FIND_NOTFOUND;
}
};
/**
* Fake browser implementation to make findbar widget happy - searches in
* the filter list.
*/
FilterSearch.fakeBrowser =
{
finder:
{
_resultListeners: [],
searchString: null,
caseSensitive: false,
lastResult: null,
_notifyResultListeners: function(result, findBackwards)
{
this.lastResult = result;
for (let listener of this._resultListeners)
{
// See https://bugzilla.mozilla.org/show_bug.cgi?id=958101, starting
// with Gecko 29 only one parameter is expected.
try
{
if (listener.onFindResult.length == 1)
{
listener.onFindResult({searchString: this.searchString,
result: result, findBackwards: findBackwards});
}
else
listener.onFindResult(result, findBackwards);
}
catch (e)
{
Cu.reportError(e);
}
}
},
fastFind: function(searchString, linksOnly, drawOutline)
{
this.searchString = searchString;
let result = FilterSearch.search(this.searchString, 0,
this.caseSensitive);
this._notifyResultListeners(result, false);
},
findAgain: function(findBackwards, linksOnly, drawOutline)
{
let result = FilterSearch.search(this.searchString,
findBackwards ? -1 : 1,
this.caseSensitive);
this._notifyResultListeners(result, findBackwards);
},
addResultListener: function(listener)
{
if (this._resultListeners.indexOf(listener) === -1)
this._resultListeners.push(listener);
},
removeResultListener: function(listener)
{
let index = this._resultListeners.indexOf(listener);
if (index !== -1)
this._resultListeners.splice(index, 1);
},
// Irrelevant for us
requestMatchesCount: function(searchString, matchLimit, linksOnly) {},
highlight: function(highlight, word) {},
enableSelection: function() {},
removeSelection: function() {},
focusContent: function() {},
keyPress: function() {}
},
currentURI: Utils.makeURI("http://example.com/"),
contentWindow:
{
focus: function()
{
E("filtersTree").focus();
},
scrollByLines: function(num)
{
E("filtersTree").boxObject.scrollByLines(num);
},
scrollByPages: function(num)
{
E("filtersTree").boxObject.scrollByPages(num);
},
},
messageManager:
{
_messageMap: {
"Findbar:Mouseup": "mouseup",
"Findbar:Keypress": "keypress"
},
_messageFromEvent: function(event)
{
for (let message in this._messageMap)
if (this._messageMap[message] == event.type)
return {target: event.currentTarget, name: message, data: event};
return null;
},
addMessageListener: function(message, listener)
{
if (!this._messageMap.hasOwnProperty(message))
return;
if (!("_ABPHandler" in listener))
listener._ABPHandler = (event) => listener.receiveMessage(this._messageFromEvent(event));
E("filtersTree").addEventListener(this._messageMap[message], listener._ABPHandler, false);
},
removeMessageListener: function(message, listener)
{
if (this._messageMap.hasOwnProperty(message) && listener._ABPHandler)
E("filtersTree").removeEventListener(this._messageMap[message], listener._ABPHandler, false);
},
sendAsyncMessage: function() {}
}
};
window.addEventListener("load", function()
{
FilterSearch.init();
}, false);
@@ -0,0 +1,592 @@
/*
* This Source Code is subject to the terms of the Mozilla Public License
* version 2.0 (the "License"). You can obtain a copy of the License at
* http://mozilla.org/MPL/2.0/.
*/
/**
* Implemetation of the various actions that can be performed on subscriptions.
* @class
*/
var SubscriptionActions =
{
/**
* Returns the subscription list currently having focus if any.
* @type Element
*/
get focusedList()
{
return E("tabs").selectedPanel.getElementsByTagName("richlistbox")[0];
},
/**
* Returns the currently selected and focused subscription item if any.
* @type Element
*/
get selectedItem()
{
let list = this.focusedList;
return (list ? list.selectedItem : null);
},
/**
* Finds the subscription for a particular filter, selects it and selects the
* filter.
*/
selectFilter: function(/**Filter*/ filter)
{
let node = null;
let tabIndex = -1;
let subscriptions = filter.subscriptions.slice();
subscriptions.sort(function(s1, s2) s1.disabled - s2.disabled);
for (let i = 0; i < subscriptions.length; i++)
{
let subscription = subscriptions[i];
let list = E(subscription instanceof SpecialSubscription ? "groups" : "subscriptions");
tabIndex = (subscription instanceof SpecialSubscription ? 1 : 0);
node = Templater.getNodeForData(list, "subscription", subscription);
if (node)
break;
}
if (node)
{
E("tabs").selectedIndex = tabIndex;
Utils.runAsync(function()
{
node.parentNode.ensureElementIsVisible(node);
node.parentNode.selectItem(node);
if (!FilterActions.visible)
E("subscription-showHideFilters-command").doCommand();
Utils.runAsync(FilterView.selectFilter, FilterView, filter);
});
}
},
/**
* Updates subscription commands whenever the selected subscription changes.
* Note: this method might be called with a wrong "this" value.
*/
updateCommands: function()
{
let node = SubscriptionActions.selectedItem;
let data = Templater.getDataForNode(node);
let subscription = (data ? data.subscription : null)
E("subscription-editTitle-command").setAttribute("disabled", !subscription ||
subscription.fixedTitle);
E("subscription-update-command").setAttribute("disabled", !subscription ||
!(subscription instanceof DownloadableSubscription) ||
Synchronizer.isExecuting(subscription.url));
E("subscription-moveUp-command").setAttribute("disabled", !subscription ||
!node || !node.previousSibling || !!node.previousSibling.id);
E("subscription-moveDown-command").setAttribute("disabled", !subscription ||
!node || !node.nextSibling || !!node.nextSibling.id);
},
/**
* Starts title editing for the selected subscription.
*/
editTitle: function()
{
let node = this.selectedItem;
if (node)
TitleEditor.start(node);
},
/**
* Triggers re-download of a filter subscription.
*/
updateFilters: function(/**Node*/ node)
{
let data = Templater.getDataForNode(node || this.selectedItem);
if (data && data.subscription instanceof DownloadableSubscription)
Synchronizer.execute(data.subscription, true, true);
},
/**
* Triggers re-download of all filter subscriptions.
*/
updateAllFilters: function()
{
for (let i = 0; i < FilterStorage.subscriptions.length; i++)
{
let subscription = FilterStorage.subscriptions[i];
if (subscription instanceof DownloadableSubscription)
Synchronizer.execute(subscription, true, true);
}
},
/**
* Sets Subscription.disabled field to a new value.
*/
setDisabled: function(/**Element*/ node, /**Boolean*/ value)
{
let data = Templater.getDataForNode(node || this.selectedItem);
if (data)
data.subscription.disabled = value;
},
/**
* Enables all disabled filters in a subscription.
*/
enableFilters: function(/**Element*/ node)
{
let data = Templater.getDataForNode(node);
if (!data)
return;
let filters = data.subscription.filters;
for (let i = 0, l = filters.length; i < l; i++)
if (filters[i] instanceof ActiveFilter && filters[i].disabled)
filters[i].disabled = false;
},
/**
* Removes a filter subscription from the list (after a warning).
*/
remove: function(/**Node*/ node)
{
let data = Templater.getDataForNode(node || this.selectedItem);
if (data && Utils.confirm(window, Utils.getString(data.subscription instanceof SpecialSubscription ? "remove_group_warning" : "remove_subscription_warning")))
FilterStorage.removeSubscription(data.subscription);
},
/**
* Adds a new filter group and allows the user to change its title.
*/
addGroup: function()
{
let subscription = SpecialSubscription.create();
FilterStorage.addSubscription(subscription);
let list = E("groups");
let node = Templater.getNodeForData(list, "subscription", subscription);
if (node)
{
list.focus();
list.ensureElementIsVisible(node);
list.selectedItem = node;
this.editTitle();
}
},
/**
* Moves a filter subscription one line up.
*/
moveUp: function(/**Node*/ node)
{
node = Templater.getDataNode(node || this.selectedItem);
let data = Templater.getDataForNode(node);
if (!data)
return;
let previousData = Templater.getDataForNode(node.previousSibling);
if (!previousData)
return;
FilterStorage.moveSubscription(data.subscription, previousData.subscription);
},
/**
* Moves a filter subscription one line down.
*/
moveDown: function(/**Node*/ node)
{
node = Templater.getDataNode(node || this.selectedItem);
let data = Templater.getDataForNode(node);
if (!data)
return;
let nextNode = node.nextSibling;
if (!Templater.getDataForNode(nextNode))
return;
let nextData = Templater.getDataForNode(nextNode.nextSibling);
FilterStorage.moveSubscription(data.subscription, nextData ? nextData.subscription : null);
},
/**
* Opens the context menu for a subscription node.
*/
openMenu: function(/**Event*/ event, /**Node*/ node)
{
node.getElementsByClassName("actionMenu")[0].openPopupAtScreen(event.screenX, event.screenY, true);
},
_altMask: 2,
_ctrlMask: 4,
_metaMask: 8,
get _accelMask()
{
let result = this._ctrlMask;
try {
let accelKey = Utils.prefService.getIntPref("ui.key.accelKey");
if (accelKey == Ci.nsIDOMKeyEvent.DOM_VK_META)
result = this._metaMask;
else if (accelKey == Ci.nsIDOMKeyEvent.DOM_VK_ALT)
result = this._altMask;
} catch(e) {}
this.__defineGetter__("_accelMask", function() result);
return result;
},
/**
* Called when a key is pressed on the subscription list.
*/
keyPress: function(/**Event*/ event)
{
let modifiers = 0;
if (event.altKey)
modifiers |= this._altMask;
if (event.ctrlKey)
modifiers |= this._ctrlMask;
if (event.metaKey)
modifiers |= this._metaMask;
if (event.charCode == " ".charCodeAt(0) && modifiers == 0)
{
// Ignore if Space is pressed on a button
for (let node = event.target; node; node = node.parentNode)
if (node.localName == "button")
return;
let data = Templater.getDataForNode(this.selectedItem);
if (data)
data.subscription.disabled = !data.subscription.disabled;
}
else if (event.keyCode == Ci.nsIDOMKeyEvent.DOM_VK_UP && modifiers == this._accelMask)
{
E("subscription-moveUp-command").doCommand();
event.preventDefault();
event.stopPropagation();
}
else if (event.keyCode == Ci.nsIDOMKeyEvent.DOM_VK_DOWN && modifiers == this._accelMask)
{
E("subscription-moveDown-command").doCommand();
event.preventDefault();
event.stopPropagation();
}
},
/**
* Subscription currently being dragged if any.
* @type Subscription
*/
dragSubscription: null,
/**
* Called when a subscription entry is dragged.
*/
startDrag: function(/**Event*/ event, /**Node*/ node)
{
let data = Templater.getDataForNode(node);
if (!data)
return;
event.dataTransfer.addElement(node);
event.dataTransfer.setData("text/x-moz-url", data.subscription.url);
event.dataTransfer.setData("text/plain", data.subscription.title);
this.dragSubscription = data.subscription;
event.stopPropagation();
},
/**
* Called when something is dragged over a subscription entry or subscriptions list.
*/
dragOver: function(/**Event*/ event)
{
// Don't allow dragging onto a scroll bar
for (let node = event.originalTarget; node; node = node.parentNode)
if (node.localName == "scrollbar")
return;
// Don't allow dragging onto element's borders
let target = event.originalTarget;
while (target && target.localName != "richlistitem")
target = target.parentNode;
if (!target)
target = event.originalTarget;
let styles = window.getComputedStyle(target, null);
let rect = target.getBoundingClientRect();
if (event.clientX < rect.left + parseInt(styles.borderLeftWidth, 10) ||
event.clientY < rect.top + parseInt(styles.borderTopWidth, 10) ||
event.clientX > rect.right - parseInt(styles.borderRightWidth, 10) - 1 ||
event.clientY > rect.bottom - parseInt(styles.borderBottomWidth, 10) - 1)
{
return;
}
// If not dragging a subscription check whether we can accept plain text
if (!this.dragSubscription)
{
let data = Templater.getDataForNode(event.target);
if (!data || !(data.subscription instanceof SpecialSubscription) || !event.dataTransfer.getData("text/plain"))
return;
}
event.preventDefault();
event.stopPropagation();
},
/**
* Called when something is dropped on a subscription entry or subscriptions list.
*/
drop: function(/**Event*/ event, /**Node*/ node)
{
if (!this.dragSubscription)
{
// Not dragging a subscription, maybe this is plain text that we can add as filters?
let data = Templater.getDataForNode(node);
if (data && data.subscription instanceof SpecialSubscription)
{
let lines = event.dataTransfer.getData("text/plain").replace(/\r/g, "").split("\n");
for (let i = 0; i < lines.length; i++)
{
let filter = Filter.fromText(lines[i]);
if (filter)
FilterStorage.addFilter(filter, data.subscription);
}
FilterActions.removeDraggedFilters();
event.stopPropagation();
}
return;
}
// When dragging down we need to insert after the drop node, otherwise before it.
node = Templater.getDataNode(node);
if (node)
{
let dragNode = Templater.getNodeForData(node.parentNode, "subscription", this.dragSubscription);
if (node.compareDocumentPosition(dragNode) & node.DOCUMENT_POSITION_PRECEDING)
node = node.nextSibling;
}
let data = Templater.getDataForNode(node);
FilterStorage.moveSubscription(this.dragSubscription, data ? data.subscription : null);
event.stopPropagation();
},
/**
* Called when the drag operation for a subscription is finished.
*/
endDrag: function()
{
this.dragSubscription = null;
}
};
/**
* Subscription title editing functionality.
* @class
*/
var TitleEditor =
{
/**
* List item corresponding with the currently edited subscription if any.
* @type Node
*/
subscriptionEdited: null,
/**
* Starts editing of a subscription title.
* @param {Node} node subscription list entry or a child node
* @param {Boolean} [checkSelection] if true the editor will not start if the
* item was selected in the preceding mousedown event
*/
start: function(node, checkSelection)
{
if (this.subscriptionEdited)
this.end(true);
let subscriptionNode = Templater.getDataNode(node);
if (!subscriptionNode || (checkSelection && !subscriptionNode._wasSelected))
return;
let subscription = Templater.getDataForNode(subscriptionNode).subscription;
if (!subscription || subscription.fixedTitle)
return;
subscriptionNode.getElementsByClassName("titleBox")[0].selectedIndex = 1;
let editor = subscriptionNode.getElementsByClassName("titleEditor")[0];
editor.value = subscription.title;
editor.setSelectionRange(0, editor.value.length);
this.subscriptionEdited = subscriptionNode;
editor.focus();
},
/**
* Stops editing of a subscription title.
* @param {Boolean} save if true the entered value will be saved, otherwise dismissed
*/
end: function(save)
{
if (!this.subscriptionEdited)
return;
let subscriptionNode = this.subscriptionEdited;
this.subscriptionEdited = null;
let newTitle = null;
if (save)
{
newTitle = subscriptionNode.getElementsByClassName("titleEditor")[0].value;
newTitle = newTitle.replace(/^\s+/, "").replace(/\s+$/, "");
}
let subscription = Templater.getDataForNode(subscriptionNode).subscription
if (newTitle && newTitle != subscription.title)
subscription.title = newTitle;
else
{
subscriptionNode.getElementsByClassName("titleBox")[0].selectedIndex = 0;
subscriptionNode.parentNode.focus();
}
},
/**
* Processes keypress events on the subscription title editor field.
*/
keyPress: function(/**Event*/ event)
{
// Prevent any key presses from triggering outside actions
event.stopPropagation();
if (event.keyCode == event.DOM_VK_RETURN || event.keyCode == event.DOM_VK_ENTER)
{
event.preventDefault();
this.end(true);
}
else if (event.keyCode == event.DOM_VK_CANCEL || event.keyCode == event.DOM_VK_ESCAPE)
{
event.preventDefault();
this.end(false);
}
}
};
/**
* Methods called when choosing and adding a new filter subscription.
* @class
*/
var SelectSubscription =
{
/**
* Starts selection of a filter subscription to add.
*/
start: function(/**Event*/ event)
{
let panel = E("selectSubscriptionPanel");
let list = E("selectSubscription");
let template = E("selectSubscriptionTemplate");
let parent = list.menupopup;
if (panel.state == "open")
{
list.focus();
return;
}
// Remove existing entries if any
while (parent.lastChild)
parent.removeChild(parent.lastChild);
// Load data
let request = new XMLHttpRequest();
request.open("GET", "subscriptions.xml");
request.onload = function()
{
// Avoid race condition if two downloads are started in parallel
if (panel.state == "open")
return;
// Add subscription entries to the list
let subscriptions = request.responseXML.getElementsByTagName("subscription");
let listedSubscriptions = [];
for (let i = 0; i < subscriptions.length; i++)
{
let subscription = subscriptions[i];
let url = subscription.getAttribute("url");
if (!url || url in FilterStorage.knownSubscriptions)
continue;
let localePrefix = Utils.checkLocalePrefixMatch(subscription.getAttribute("prefixes"));
let node = Templater.process(template, {
__proto__: null,
node: subscription,
localePrefix: localePrefix
});
parent.appendChild(node);
listedSubscriptions.push(subscription);
}
let selectedNode = Utils.chooseFilterSubscription(listedSubscriptions);
list.selectedItem = Templater.getNodeForData(parent, "node", selectedNode) || parent.firstChild;
// Show panel and focus list
let position = "bottomcenter topleft";
panel.openPopup(E("selectSubscriptionButton"), position, 0, 0, false, false, event);
Utils.runAsync(list.focus, list);
};
request.send();
},
/**
* Adds filter subscription that is selected.
*/
add: function()
{
E("selectSubscriptionPanel").hidePopup();
let data = Templater.getDataForNode(E("selectSubscription").selectedItem);
if (!data)
return;
let subscription = Subscription.fromURL(data.node.getAttribute("url"));
if (!subscription)
return;
FilterStorage.addSubscription(subscription);
subscription.disabled = false;
subscription.title = data.node.getAttribute("title");
subscription.homepage = data.node.getAttribute("homepage");
// Make sure the subscription is visible and selected
let list = E("subscriptions");
let node = Templater.getNodeForData(list, "subscription", subscription);
if (node)
{
list.ensureElementIsVisible(node);
list.selectedItem = node;
list.focus();
}
// Trigger download if necessary
if (subscription instanceof DownloadableSubscription && !subscription.lastDownload)
Synchronizer.execute(subscription);
},
/**
* Called if the user chooses to view the complete subscriptions list.
*/
chooseOther: function()
{
E("selectSubscriptionPanel").hidePopup();
window.openDialog("subscriptionSelection.xul", "_blank", "chrome,centerscreen,modal,resizable,dialog=no", null, null);
},
/**
* Called for keys pressed on the subscription selection panel.
*/
keyPress: function(/**Event*/ event)
{
// Buttons and text links handle Enter key themselves
if (event.target.localName == "button" || event.target.localName == "label")
return;
if (event.keyCode == event.DOM_VK_RETURN || event.keyCode == event.DOM_VK_ENTER)
{
// This shouldn't accept our dialog, only the panel
event.preventDefault();
E("selectSubscriptionAccept").doCommand();
}
}
};
+288
View File
@@ -0,0 +1,288 @@
/*
* This Source Code is subject to the terms of the Mozilla Public License
* version 2.0 (the "License"). You can obtain a copy of the License at
* http://mozilla.org/MPL/2.0/.
*/
/**
* Fills a list of filter groups and keeps it updated.
* @param {Element} list richlistbox element to be filled
* @param {Node} template template to use for the groups
* @param {Function} filter filter to decide which lists should be included
* @param {Function} listener function to be called on changes
* @constructor
*/
function ListManager(list, template, filter, listener)
{
this._list = list;
this._template = template;
this._filter = filter;
this._listener = listener || function(){};
this._deck = this._list.parentNode;
this._list.listManager = this;
this.reload();
let me = this;
let proxy = function()
{
return me._onChange.apply(me, arguments);
};
FilterNotifier.addListener(proxy);
window.addEventListener("unload", function()
{
FilterNotifier.removeListener(proxy);
}, false);
}
ListManager.prototype =
{
/**
* List element being managed.
* @type Element
*/
_list: null,
/**
* Template used for the groups.
* @type Node
*/
_template: null,
/**
* Filter function to decide which subscriptions should be included.
* @type Function
*/
_filter: null,
/**
* Function to be called whenever list contents change.
* @type Function
*/
_listener: null,
/**
* Deck switching between list display and "no entries" message.
* @type Element
*/
_deck: null,
/**
* Completely rebuilds the list.
*/
reload: function()
{
// Remove existing entries if any
while (this._list.firstChild)
this._list.removeChild(this._list.firstChild);
// Now add all subscriptions
let subscriptions = FilterStorage.subscriptions.filter(this._filter, this);
if (subscriptions.length)
{
for each (let subscription in subscriptions)
this.addSubscription(subscription, null);
// Make sure first list item is selected after list initialization
Utils.runAsync(function()
{
this._list.selectItem(this._list.getItemAtIndex(this._list.getIndexOfFirstVisibleRow()));
}, this);
}
this._deck.selectedIndex = (subscriptions.length ? 1 : 0);
this._listener();
},
/**
* Adds a filter subscription to the list.
*/
addSubscription: function(/**Subscription*/ subscription, /**Node*/ insertBefore) /**Node*/
{
let disabledFilters = 0;
for (let i = 0, l = subscription.filters.length; i < l; i++)
if (subscription.filters[i] instanceof ActiveFilter && subscription.filters[i].disabled)
disabledFilters++;
let node = Templater.process(this._template, {
__proto__: null,
subscription: subscription,
isExternal: subscription instanceof ExternalSubscription,
downloading: Synchronizer.isExecuting(subscription.url),
disabledFilters: disabledFilters
});
if (insertBefore)
this._list.insertBefore(node, insertBefore);
else
this._list.appendChild(node);
return node;
},
/**
* Map indicating subscriptions that need their "disabledFilters" property to
* be updated by next updateDisabled() call.
* @type Object
*/
_scheduledUpdateDisabled: null,
/**
* Updates subscriptions that had some of their filters enabled/disabled.
*/
updateDisabled: function()
{
let list = this._scheduledUpdateDisabled;
this._scheduledUpdateDisabled = null;
for (let url in list)
{
let subscription = Subscription.fromURL(url);
let subscriptionNode = Templater.getNodeForData(this._list, "subscription", subscription);
if (subscriptionNode)
{
let data = Templater.getDataForNode(subscriptionNode);
let disabledFilters = 0;
for (let i = 0, l = subscription.filters.length; i < l; i++)
if (subscription.filters[i] instanceof ActiveFilter && subscription.filters[i].disabled)
disabledFilters++;
if (disabledFilters != data.disabledFilters)
{
data.disabledFilters = disabledFilters;
Templater.update(this._template, subscriptionNode);
if (!document.commandDispatcher.focusedElement)
this._list.focus();
}
}
}
},
/**
* Subscriptions change processing.
* @see FilterNotifier.addListener()
*/
_onChange: function(action, item, param1, param2)
{
if (action == "filter.disabled")
{
if (this._scheduledUpdateDisabled == null)
{
this._scheduledUpdateDisabled = {__proto__: null};
Utils.runAsync(this.updateDisabled, this);
}
for (let i = 0; i < item.subscriptions.length; i++)
this._scheduledUpdateDisabled[item.subscriptions[i].url] = true;
return;
}
if (action != "load" && !this._filter(item))
return;
switch (action)
{
case "load":
{
this.reload();
break;
}
case "subscription.added":
{
let index = FilterStorage.subscriptions.indexOf(item);
if (index >= 0)
{
let insertBefore = null;
for (index++; index < FilterStorage.subscriptions.length && !insertBefore; index++)
insertBefore = Templater.getNodeForData(this._list, "subscription", FilterStorage.subscriptions[index]);
this.addSubscription(item, insertBefore);
this._deck.selectedIndex = 1;
this._listener();
}
break;
}
case "subscription.removed":
{
let node = Templater.getNodeForData(this._list, "subscription", item);
if (node)
{
let newSelection = node.nextSibling || node.previousSibling;
node.parentNode.removeChild(node);
if (!this._list.firstChild)
{
this._deck.selectedIndex = 0;
this._list.selectedIndex = -1;
}
else if (newSelection)
{
this._list.ensureElementIsVisible(newSelection);
this._list.selectedItem = newSelection;
}
this._listener();
}
break
}
case "subscription.moved":
{
let node = Templater.getNodeForData(this._list, "subscription", item);
if (node)
{
node.parentNode.removeChild(node);
let insertBefore = null;
let index = FilterStorage.subscriptions.indexOf(item);
if (index >= 0)
for (index++; index < FilterStorage.subscriptions.length && !insertBefore; index++)
insertBefore = Templater.getNodeForData(this._list, "subscription", FilterStorage.subscriptions[index]);
this._list.insertBefore(node, insertBefore);
this._list.ensureElementIsVisible(node);
this._listener();
}
break;
}
case "subscription.title":
case "subscription.disabled":
case "subscription.homepage":
case "subscription.lastDownload":
case "subscription.downloadStatus":
{
let subscriptionNode = Templater.getNodeForData(this._list, "subscription", item);
if (subscriptionNode)
{
Templater.getDataForNode(subscriptionNode).downloading = Synchronizer.isExecuting(item.url);
Templater.update(this._template, subscriptionNode);
if (!document.commandDispatcher.focusedElement)
this._list.focus();
this._listener();
}
break;
}
case "subscription.fixedTitle":
{
SubscriptionActions.updateCommands();
break;
}
case "subscription.updated":
{
if (this._scheduledUpdateDisabled == null)
{
this._scheduledUpdateDisabled = {__proto__: null};
Utils.runAsync(this.updateDisabled, this);
}
this._scheduledUpdateDisabled[item.url] = true;
break;
}
}
}
};
/**
* Attaches list managers to the lists.
*/
ListManager.init = function()
{
new ListManager(E("subscriptions"),
E("subscriptionTemplate"),
function(s) s instanceof RegularSubscription,
SubscriptionActions.updateCommands);
new ListManager(E("groups"),
E("groupTemplate"),
function(s) s instanceof SpecialSubscription,
SubscriptionActions.updateCommands);
};
window.addEventListener("load", ListManager.init, false);
+210
View File
@@ -0,0 +1,210 @@
/*
* This Source Code is subject to the terms of the Mozilla Public License
* version 2.0 (the "License"). You can obtain a copy of the License at
* http://mozilla.org/MPL/2.0/.
*/
/**
* Initialization function, called when the window is loaded.
*/
function init()
{
if (window.arguments && window.arguments.length)
{
let filter = window.arguments[0].wrappedJSObject;
if (filter instanceof Filter)
Utils.runAsync(SubscriptionActions.selectFilter, SubscriptionActions, filter);
}
}
/**
* Called whenever the currently selected tab changes.
*/
function onTabChange(/**Element*/ tabbox)
{
updateSelectedSubscription();
Utils.runAsync(function()
{
let panel = tabbox.selectedPanel;
if (panel)
panel.getElementsByClassName("initialFocus")[0].focus();
SubscriptionActions.updateCommands();
});
}
/**
* Called whenever the selected subscription changes.
*/
function onSelectionChange(/**Element*/ list)
{
SubscriptionActions.updateCommands();
updateSelectedSubscription();
list.focus();
// Take elements of the previously selected item out of the tab order
if ("previousSelection" in list && list.previousSelection)
{
let elements = list.previousSelection.getElementsByClassName("tabable");
for (let i = 0; i < elements.length; i++)
elements[i].setAttribute("tabindex", "-1");
}
// Put elements of the selected item into tab order
if (list.selectedItem)
{
let elements = list.selectedItem.getElementsByClassName("tabable");
for (let i = 0; i < elements.length; i++)
elements[i].removeAttribute("tabindex");
}
list.previousSelection = list.selectedItem;
}
/**
* Called when splitter state changes to make sure it is persisted properly.
*/
function onSplitterStateChange(/**Element*/ splitter)
{
let state = splitter.getAttribute("state");
if (!state)
{
splitter.setAttribute("state", "open");
document.persist(splitter.id, "state");
}
}
/**
* Updates filter list when selected subscription changes.
*/
function updateSelectedSubscription()
{
let panel = E("tabs").selectedPanel;
if (!panel)
return;
let list = panel.getElementsByTagName("richlistbox")[0];
if (!list)
return;
let data = Templater.getDataForNode(list.selectedItem);
FilterView.subscription = (data ? data.subscription : null);
FilterActions.updateCommands();
}
/**
* Template processing functions.
* @class
*/
var Templater =
{
/**
* Processes a template node using given data object.
*/
process: function(/**Node*/ template, /**Object*/ data) /**Node*/
{
// Use a sandbox to resolve attributes (for convenience, not security)
let sandbox = Cu.Sandbox(window);
for (let key in data)
sandbox[key] = data[key];
sandbox.formatTime = Utils.formatTime;
// Clone template but remove id/hidden attributes from it
let result = template.cloneNode(true);
result.removeAttribute("id");
result.removeAttribute("hidden");
result._data = data;
// Resolve any attributes of the for attr="{obj.foo}"
let conditionals = [];
let nodeIterator = document.createNodeIterator(result, NodeFilter.SHOW_ELEMENT, null, false);
for (let node = nodeIterator.nextNode(); node; node = nodeIterator.nextNode())
{
if (node.localName == "if")
conditionals.push(node);
for (let i = 0; i < node.attributes.length; i++)
{
let attribute = node.attributes[i];
let len = attribute.value.length;
if (len >= 2 && attribute.value[0] == "{" && attribute.value[len - 1] == "}")
attribute.value = Cu.evalInSandbox(attribute.value.substr(1, len - 2), sandbox);
}
}
// Process <if> tags - remove if condition is false, replace by their children
// if it is true
for each (let node in conditionals)
{
let fragment = document.createDocumentFragment();
let condition = node.getAttribute("condition");
if (condition == "false")
condition = false;
for (let i = 0; i < node.childNodes.length; i++)
{
let child = node.childNodes[i];
if (child.localName == "elif" || child.localName == "else")
{
if (condition)
break;
condition = (child.localName == "elif" ? child.getAttribute("condition") : true);
if (condition == "false")
condition = false;
}
else if (condition)
fragment.appendChild(node.childNodes[i--]);
}
node.parentNode.replaceChild(fragment, node);
}
return result;
},
/**
* Updates first child of a processed template if the underlying data changed.
*/
update: function(/**Node*/ template, /**Node*/ node)
{
if (!("_data" in node))
return;
let newChild = Templater.process(template.firstChild, node._data);
delete newChild._data;
node.replaceChild(newChild, node.firstChild);
},
/**
* Walks up the parent chain for a node until the node corresponding with a
* template is found.
*/
getDataNode: function(/**Node*/ node) /**Node*/
{
while (node)
{
if ("_data" in node)
return node;
node = node.parentNode;
}
return null;
},
/**
* Returns the data used to generate the node from a template.
*/
getDataForNode: function(/**Node*/ node) /**Object*/
{
node = Templater.getDataNode(node);
if (node)
return node._data;
else
return null;
},
/**
* Returns a node that has been generated from a template using a particular
* data object.
*/
getNodeForData: function(/**Node*/ parent, /**String*/ property, /**Object*/ data) /**Node*/
{
for (let child = parent.firstChild; child; child = child.nextSibling)
if ("_data" in child && property in child._data && child._data[property] == data)
return child;
return null;
}
};
+395
View File
@@ -0,0 +1,395 @@
<?xml version="1.0"?>
<!-- This Source Code is subject to the terms of the Mozilla Public License
- version 2.0 (the "License"). You can obtain a copy of the License at
- http://mozilla.org/MPL/2.0/. -->
#filter substitution
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
<?xml-stylesheet href="chrome://@ADDON_CHROME_NAME@/skin/filters.css" type="text/css"?>
<!DOCTYPE dialog SYSTEM "chrome://@ADDON_CHROME_NAME@/locale/filters.dtd">
<dialog
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
title="&dialog.title;"
id="abpFiltersWindow"
onload="init()"
buttons="accept"
width="950"
height="450"
persist="screenX screenY width height sizemode"
windowtype="abp:filters">
<script type="application/x-javascript;version=1.7" src="utils.js"/>
<script type="application/x-javascript;version=1.7" src="filters.js"/>
<script type="application/x-javascript;version=1.7" src="filters-subscriptionview.js"/>
<script type="application/x-javascript;version=1.7" src="filters-subscriptionactions.js"/>
<script type="application/x-javascript;version=1.7" src="filters-filterview.js"/>
<script type="application/x-javascript;version=1.7" src="filters-filteractions.js"/>
<script type="application/x-javascript;version=1.7" src="filters-backup.js"/>
<script type="application/x-javascript;version=1.7" src="filters-search.js"/>
<keyset id="filtersKeyset">
<key id="subscription-update-key" key="T" modifiers="accel" command="subscription-update-command"/>
<key id="subscription-update-all-key" key="T" modifiers="accel,shift" command="subscription-update-all-command"/>
<key id="edit-key" keycode="VK_F2" oncommand="E(FilterActions.focused ? 'filters-edit-command' : 'subscription-editTitle-command').doCommand();"/>
<key id="delete-key" keycode="VK_DELETE" oncommand="E(FilterActions.focused ? 'filters-delete-command' : 'subscription-delete-command').doCommand();"/>
<key id="subscription-showHideFilters-key" key="R" modifiers="accel" command="subscription-showHideFilters-command"/>
<key id="moveUp-key" keycode="VK_UP" modifiers="accel"/>
<key id="moveDown-key" keycode="VK_DOWN" modifiers="accel"/>
<key id="filters-add-key" keycode="VK_INSERT" oncommand="E('filters-add-command').doCommand();"/>
<key id="filters-selectAll-key" key="A" modifiers="accel" oncommand="if (FilterActions.focused) E('filters-selectAll-command').doCommand();"/>
<key id="filters-copy-key" key="C" modifiers="accel" oncommand="if (FilterActions.focused) E('filters-copy-command').doCommand();"/>
<key id="filters-cut-key" key="X" modifiers="accel" oncommand="if (FilterActions.focused) E('filters-cut-command').doCommand();"/>
<key id="filters-paste-key" key="V" modifiers="accel" oncommand="if (FilterActions.focused) E('filters-paste-command').doCommand();"/>
<key id="backup-key" key="E" modifiers="accel" oncommand="E('backup').doCommand();"/>
<key id="restore-key" key="I" modifiers="accel" oncommand="E('restoreOwnBackup').doCommand();"/>
<key id="find-key" key="F" modifiers="accel" oncommand="if (FilterActions.visible) E('find-command').doCommand();"/>
<key id="find-again-key" key="G" modifiers="accel" oncommand="if (FilterActions.visible) E('find-again-command').doCommand();"/>
<key id="find-previous-key" key="G" modifiers="accel,shift" oncommand="if (FilterActions.visible) E('find-previous-command').doCommand();"/>
<key id="find-again-key2" keycode="VK_F3" oncommand="if (FilterActions.visible) E('find-again-command').doCommand();"/>
<key id="find-previous-key2" keycode="VK_F3" modifiers="shift" oncommand="if (FilterActions.visible) E('find-previous-command').doCommand();"/>
</keyset>
<commandset id="filtersCommandset">
<command id="subscription-update-command" oncommand="SubscriptionActions.updateFilters();"/>
<command id="subscription-update-all-command" oncommand="SubscriptionActions.updateAllFilters();"/>
<command id="subscription-editTitle-command" oncommand="SubscriptionActions.editTitle();"/>
<command id="subscription-delete-command" oncommand="SubscriptionActions.remove();"/>
<command id="subscription-showHideFilters-command" oncommand="E('filtersGrippy').doCommand();"/>
<command id="subscription-moveUp-command" oncommand="SubscriptionActions.moveUp();"/>
<command id="subscription-moveDown-command" oncommand="SubscriptionActions.moveDown();"/>
<command id="subscription-add-command" oncommand="SelectSubscription.start(event);"/>
<command id="subscription-addSelected-command" oncommand="SelectSubscription.add();"/>
<command id="subscription-addOther-command" oncommand="SelectSubscription.chooseOther();"/>
<command id="group-add-command" oncommand="SubscriptionActions.addGroup();"/>
<command id="filters-selectAll-command" oncommand="FilterActions.selectAll();"/>
<command id="filters-edit-command" oncommand="FilterActions.startEditing();"/>
<command id="filters-add-command" oncommand="FilterActions.insertFilter();"/>
<command id="filters-delete-command" oncommand="FilterActions.deleteSelected();"/>
<command id="filters-resetHitCounts-command" oncommand="FilterActions.resetHitCounts();"/>
<command id="filters-moveUp-command" oncommand="FilterActions.moveUp();"/>
<command id="filters-moveDown-command" oncommand="FilterActions.moveDown();"/>
<command id="filters-copy-command" oncommand="FilterActions.copySelected(true);"/>
<command id="filters-cut-command" oncommand="FilterActions.copySelected(false);"/>
<command id="filters-paste-command" oncommand="FilterActions.paste();"/>
<command id="find-command" oncommand="E('findbar').startFind(E('findbar').FIND_NORMAL)"/>
<command id="find-again-command" oncommand="E('findbar').onFindAgainCommand(false)"/>
<command id="find-previous-command" oncommand="E('findbar').onFindAgainCommand(true)"/>
</commandset>
<popupset id="filtersPopupset">
<menupopup id="filters-view-menu1" onpopupshowing="FilterActions.fillColumnPopup(this);">
<menuitem id="filters-view-filter1" label="&filter.column;" accesskey="&filter.accesskey;" type="checkbox" disabled="true"/>
<menuitem id="filters-view-slow1" label="&slow.column;" accesskey="&slow.accesskey;" type="checkbox" oncommand="FilterActions.toggleColumn('col-slow')"/>
<menuitem id="filters-view-enabled1" label="&enabled.column;" accesskey="&enabled.accesskey;" type="checkbox" oncommand="FilterActions.toggleColumn('col-enabled')"/>
<menuitem id="filters-view-hitcount1" label="&hitcount.column;" accesskey="&hitcount.accesskey;" type="checkbox" oncommand="FilterActions.toggleColumn('col-hitcount')"/>
<menuitem id="filters-view-lasthit1" label="&lasthit.column;" accesskey="&lasthit.accesskey;" type="checkbox" oncommand="FilterActions.toggleColumn('col-lasthit')"/>
<menuseparator/>
<menu id="filters-sort-menu1" label="&sort.label;" accesskey="&sort.accesskey;">
<menupopup id="filters-sort-popup1">
<menuitem id="filters-sort-none1" label="&sort.none.label;" accesskey="&sort.none.accesskey;" type="radio" name="sortColumn" oncommand="FilterView.sortBy(null)"/>
<menuitem id="filters-sort-filter1" label="&filter.column;" accesskey="&filter.accesskey;" type="radio" name="sortColumn" oncommand="FilterView.sortBy('col-filter')"/>
<menuitem id="filters-sort-slow1" label="&slow.column;" accesskey="&slow.accesskey;" type="radio" name="sortColumn" oncommand="FilterView.sortBy('col-slow')"/>
<menuitem id="filters-sort-enabled1" label="&enabled.column;" accesskey="&enabled.accesskey;" type="radio" name="sortColumn" oncommand="FilterView.sortBy('col-enabled')"/>
<menuitem id="filters-sort-hitcount1" label="&hitcount.column;" accesskey="&hitcount.accesskey;" type="radio" name="sortColumn" oncommand="FilterView.sortBy('col-hitcount')"/>
<menuitem id="filters-sort-lasthit1" label="&lasthit.column;" accesskey="&lasthit.accesskey;" type="radio" name="sortColumn" oncommand="FilterView.sortBy('col-lasthit')"/>
<menuseparator/>
<menuitem id="filters-sort-asc1" label="&sort.ascending.label;" accesskey="&sort.ascending.accesskey;" type="radio" name="sortOrder" oncommand="FilterActions.setSortOrder('ascending')"/>
<menuitem id="filters-sort-desc1" label="&sort.descending.label;" accesskey="&sort.descending.accesskey;" type="radio" name="sortOrder" oncommand="FilterActions.setSortOrder('descending')"/>
</menupopup>
</menu>
</menupopup>
<tooltip id="filtersTooltip" onpopupshowing="FilterActions.fillTooltip(event);">
<grid>
<columns>
<column/>
<column flex="1"/>
</columns>
<rows>
<row id="tooltip-filter-row" align="top">
<label class="tooltipLabel" value="&filter.column;"/>
<vbox id="tooltip-filter"/>
</row>
<row id="tooltip-hitcount-row">
<label class="tooltipLabel" value="&hitcount.column;"/>
<description id="tooltip-hitcount"/>
</row>
<row id="tooltip-lasthit-row">
<label class="tooltipLabel" value="&lasthit.column;"/>
<description id="tooltip-lasthit"/>
</row>
</rows>
</grid>
<description id="tooltip-additional"/>
</tooltip>
</popupset>
<hbox id="content" flex="1">
<tabbox id="tabs" flex="1" persist="selectedIndex">
<tabs onselect="onTabChange(this.parentNode);">
<tab label="&subscriptions.tab.label;"/>
<tab label="&filters.tab.label;"/>
</tabs>
<tabpanels flex="1">
<tabpanel id="subscriptionsTab" orient="vertical" flex="1">
<hbox pack="end">
<button id="selectSubscriptionButton" label="&addSubscription.label;…" accesskey="&addSubscription.accesskey;" command="subscription-add-command"/>
</hbox>
<panel id="selectSubscriptionPanel" type="arrow" position="bottomcenter topleft"
orient="vertical" onkeypress="SelectSubscription.keyPress(event);">
<menuitem id="selectSubscriptionTemplate" hidden="true"
class="{localePrefix ? 'localeMatch' : ''}"
label="{node.getAttribute('title')}"
value="{node.getAttribute('url')}">
<label class="selectSubscriptionItem" value="{node.getAttribute('title')}"/>
<label class="selectSubscriptionItem" value=" ("/>
<label class="selectSubscriptionItem" value="{node.getAttribute('specialization')}"/>
<label class="selectSubscriptionItem" value=")"/>
</menuitem>
<menulist id="selectSubscription">
<menupopup/>
</menulist>
<hbox align="baseline">
<label class="text-link" value="&addSubscriptionOther.label;" onclick="E('subscription-addOther-command').doCommand();"/>
<spacer flex="1"/>
<button id="selectSubscriptionAccept" default="true" label="&addSubscriptionAdd.label;" command="subscription-addSelected-command"/>
<spacer flex="1"/>
<button label="&addSubscriptionCancel.label;" oncommand="E('selectSubscriptionPanel').hidePopup();"/>
</hbox>
</panel>
<richlistitem id="subscriptionTemplate" class="subscription" hidden="true" orient="vertical"
onmousedown="this._wasSelected = (this.parentNode.selectedItem == this);"
ondragstart="SubscriptionActions.startDrag(event, this);"
ondragend="SubscriptionActions.endDrag();"
ondragover="SubscriptionActions.dragOver(event);"
ondrop="SubscriptionActions.drop(event, this);"
oncontextmenu="SubscriptionActions.openMenu(event, this);">
<vbox class="{subscription.disabled ? 'disabled' : ''}">
<hbox align="center">
<checkbox label="&subscription.enabled.label;" class="enabledCheckbox tabable" tabindex="-1"
checked="{subscription.disabled ? 'false' : 'true'}" oncommand="SubscriptionActions.setDisabled(this, !this.checked);"/>
<vbox flex="1">
<hbox align="center">
<deck class="titleBox" flex="1" selectedIndex="0" onselect="event.stopPropagation();">
<description ondblclick="if (event.button == 0) TitleEditor.start(this, true);">
<description class="title" value="{subscription.title}" flex="1" crop="end"/>
<hbox align="baseline">
<description value=" ("/>
<if condition="{isExternal}">
<description value="&subscription.external.label;"/>
<else/>
<if condition="{subscription.homepage}">
<description class="link" value="&subscription.homepage.label;"
_url="{subscription.homepage}" tooltiptext="{subscription.homepage}"
onclick="if (event.button == 0) { event.stopPropagation();Utils.loadInBrowser(this.getAttribute('_url')); }"/>
<description value=", "/>
</if>
<description class="link" value="&subscription.source.label;"
_url="{subscription.url}" tooltiptext="{subscription.url}"
onclick="if (event.button == 0) { event.stopPropagation();Utils.loadInBrowser(this.getAttribute('_url')); }"/>
</if>
<description value=")"/>
</hbox>
</description>
<textbox oncontextmenu="event.stopPropagation();" class="titleEditor" onkeypress="TitleEditor.keyPress(event);" onblur="TitleEditor.end(true);"/>
</deck>
</hbox>
<hbox align="center">
<description flex="1" class="status">
<description value="&subscription.lastDownload.label;"/>
<description value=" "/>
<if condition="{downloading}">
<description value="&subscription.lastDownload.inProgress;"/>
<elif condition="{!subscription.lastDownload}"/>
<description value="&subscription.lastDownload.unknown;"/>
<else/>
<description value="{formatTime(subscription.lastDownload * 1000)}"/>
<description value=" "/>
<if condition="{subscription.downloadStatus}">
<hbox>
<description value=" ("/>
<if condition="{subscription.downloadStatus == 'synchronize_invalid_url'}">
<description value="&subscription.lastDownload.invalidURL;"/>
<elif condition="{subscription.downloadStatus == 'synchronize_connection_error'}"/>
<description value="&subscription.lastDownload.connectionError;"/>
<elif condition="{subscription.downloadStatus == 'synchronize_invalid_data'}"/>
<description value="&subscription.lastDownload.invalidData;"/>
<elif condition="{subscription.downloadStatus == 'synchronize_checksum_mismatch'}"/>
<description value="&subscription.lastDownload.checksumMismatch;"/>
<else/> <!-- synchronize_ok -->
<description value="&subscription.lastDownload.success;"/>
</if>
<description value=")"/>
</hbox>
</if>
</if>
</description>
</hbox>
</vbox>
<button class="actionButton tabable" type="menu" label="&subscription.actions.label;" tabindex="-1">
<menupopup class="actionMenu">
<menuitem label="&subscription.editTitle.label;" key="edit-key" command="subscription-editTitle-command"/>
<menuitem label="&subscription.update.label;" key="subscription-update-key" command="subscription-update-command"/>
<menuitem label="&subscription.showHideFilters.label;" key="subscription-showHideFilters-key" command="subscription-showHideFilters-command"/>
<menuitem label="&subscription.delete.label;…" key="delete-key" command="subscription-delete-command"/>
<menuseparator/>
<menuitem label="&subscription.moveUp.label;" key="moveUp-key" command="subscription-moveUp-command"/>
<menuitem label="&subscription.moveDown.label;" key="moveDown-key" command="subscription-moveDown-command"/>
</menupopup>
</button>
</hbox>
<description class="warning" hidden="{!subscription.upgradeRequired}">&subscription.minVersion.warning;</description>
<description class="warning" hidden="{!disabledFilters}">
&subscription.disabledFilters.warning;
<description class="link" value="&subscription.disabledFilters.enable;" onclick="SubscriptionActions.enableFilters(this);"/>
</description>
</vbox>
</richlistitem>
<deck id="noSubscriptionsDeck" flex="1">
<description flex="1">&noSubscriptions.text;</description>
<richlistbox id="subscriptions" class="initialFocus" flex="1"
onselect="onSelectionChange(this);"
ondragover="SubscriptionActions.dragOver(event);"
ondrop="SubscriptionActions.drop(event, null);"
onkeypress="SubscriptionActions.keyPress(event);">
</richlistbox>
</deck>
</tabpanel>
<tabpanel id="filtersTab" orient="vertical" flex="1">
<hbox pack="end">
<button id="addGroupButton" label="&addGroup.label;" accesskey="&addGroup.accesskey;" command="group-add-command"/>
</hbox>
<richlistitem id="groupTemplate" class="subscription" hidden="true" orient="vertical"
onmousedown="this._wasSelected = (this.parentNode.selectedItem == this);"
ondragstart="SubscriptionActions.startDrag(event, this);"
ondragend="SubscriptionActions.endDrag();"
ondragover="SubscriptionActions.dragOver(event);"
ondrop="SubscriptionActions.drop(event, this);"
oncontextmenu="SubscriptionActions.openMenu(event, this);">
<hbox class="{subscription.disabled ? 'disabled' : ''}" align="center">
<checkbox label="&subscription.enabled.label;" class="enabledCheckbox tabable" tabindex="-1"
checked="{subscription.disabled ? 'false' : 'true'}" oncommand="SubscriptionActions.setDisabled(this, !this.checked);"/>
<hbox align="center" flex="1">
<deck class="titleBox" flex="1" selectedIndex="0" onselect="event.stopPropagation();">
<description class="title" value="{subscription.title}" crop="end" ondblclick="if (event.button == 0) TitleEditor.start(this, true);"/>
<textbox oncontextmenu="event.stopPropagation();" class="titleEditor" onkeypress="TitleEditor.keyPress(event);" onblur="TitleEditor.end(true);"/>
</deck>
</hbox>
<button class="actionButton tabable" type="menu" label="&subscription.actions.label;" tabindex="-1">
<menupopup class="actionMenu">
<menuitem label="&subscription.editTitle.label;" key="edit-key" command="subscription-editTitle-command"/>
<menuitem label="&subscription.showHideFilters.label;" key="subscription-showHideFilters-key" command="subscription-showHideFilters-command"/>
<menuitem label="&subscription.delete.label;…" key="delete-key" command="subscription-delete-command"/>
<menuseparator/>
<menuitem label="&subscription.moveUp.label;" key="moveUp-key" command="subscription-moveUp-command"/>
<menuitem label="&subscription.moveDown.label;" key="moveDown-key" command="subscription-moveDown-command"/>
</menupopup>
</button>
</hbox>
</richlistitem>
<deck id="noFiltersDeck" flex="1">
<description flex="1">&noFilters.text;</description>
<richlistbox id="groups" class="initialFocus" flex="1"
onselect="onSelectionChange(this);"
ondragover="SubscriptionActions.dragOver(event);"
ondrop="SubscriptionActions.drop(event, null);"
onkeypress="SubscriptionActions.keyPress(event);">
</richlistbox>
</deck>
</tabpanel>
</tabpanels>
</tabbox>
<splitter id="filtersSplitter" persist="state" orient="horizontal" collapse="after" state="collapsed" oncommand="FilterView.refresh();onSplitterStateChange(this);">
<grippy id="filtersGrippy"/>
</splitter>
<vbox id="filtersContainer" persist="width height" width="500">
<hbox pack="end">
<button id="findButton" label="&find.label;" accesskey="&find.accesskey;" command="find-command"/>
<button id="filterActionButton" type="menu" label="&filter.actions.label;">
<menupopup id="filterActionMenu" onpopupshowing="FilterActions.fillActionsPopup();">
<menuitem label="&filter.edit.label;" key="edit-key" command="filters-edit-command"/>
<menuitem label="&filter.cut.label;" key="filters-cut-key" command="filters-cut-command"/>
<menuitem label="&filter.copy.label;" key="filters-copy-key" command="filters-copy-command"/>
<menuitem label="&filter.paste.label;" key="filters-paste-key" command="filters-paste-command"/>
<menuitem label="&filter.delete.label;" key="delete-key" command="filters-delete-command"/>
<menuseparator/>
<menuitem label="&filter.selectAll.label;" key="filters-selectAll-key" command="filters-selectAll-command"/>
<menuitem label="&filter.resetHitCounts.label;" command="filters-resetHitCounts-command"/>
<menuseparator/>
<menuitem label="&filter.moveUp.label;" key="moveUp-key" command="filters-moveUp-command"/>
<menuitem label="&filter.moveDown.label;" key="moveDown-key" command="filters-moveDown-command"/>
<menuseparator/>
<menu id="viewMenu" label="&viewMenu.label;"/>
</menupopup>
</button>
<button id="addFilterButton" label="&addFilter.label;" accesskey="&addFilter.accesskey;" command="filters-add-command"/>
</hbox>
<tree id="filtersTree"
flex="1"
editable="true"
seltype="multiple"
enableColumnDrag="true"
hidecolumnpicker="true"
_removewarning="&filters.remove.warning;">
<treecols context="filters-view-menu1">
<treecol id="col-enabled" label="&enabled.column;" cycler="true" flex="0" persist="width ordinal sortDirection hidden"/>
<splitter class="tree-splitter"/>
<treecol id="col-filter" label="&filter.column;" flex="10" persist="width ordinal sortDirection hidden"/>
<splitter class="tree-splitter"/>
<treecol id="col-slow" label="!" display="&slow.column;" tooltiptext="&slow.column;" flex="0" width="16" persist="width ordinal sortDirection hidden"/>
<splitter class="tree-splitter"/>
<treecol id="col-hitcount" label="&hitcount.column;" flex="0" persist="width ordinal sortDirection hidden"/>
<splitter class="tree-splitter"/>
<treecol id="col-lasthit" label="&lasthit.column;" hidden="true" flex="4" persist="width ordinal sortDirection hidden"/>
</treecols>
<treechildren id="filtersTreeChildren"
oncontextmenu="E('filterActionMenu').openPopupAtScreen(event.screenX, event.screenY, true);"
tooltip="filtersTooltip"
noGroupText="&noGroupSelected.text;"
noFiltersText="&noFiltersInGroup.text;"
ondragstart="FilterActions.startDrag(event);"
ondragend="FilterActions.endDrag(event);"/>
</tree>
</vbox>
</hbox>
<findbar id="findbar"/>
<hbox id="buttons">
<button id="backupButton" type="menu"
label="&backupButton.label;" accesskey="&backupButton.accesskey;"
_backupDialogTitle="&backup.label;" _restoreDialogTitle="&restore.own.label;"
_fileFilterComplete="&backup.complete.title;" _fileFilterCustom="&backup.custom.title;"
_backupError="&backup.error;" _restoreError="&restore.error;"
_restoreCompleteWarning="&restore.complete.warning;" _restoreCustomWarning="&restore.custom.warning;"
_restoreVersionWarning="&restore.minVersion.warning;"
oncommand="if (event.target == this) Utils.runAsync(function() this.open = true, this);">
<menupopup onpopupshowing="Backup.fillRestorePopup();">
<menuitem id="backup" key="backup-key" label="&backup.label;…" oncommand="Backup.backupToFile();"/>
<menuseparator/>
<menuitem id="restoreBackupTemplate" label="&restore.default.label;" hidden="true"/>
<menuitem id="restoreOwnBackup" key="restore-key" label="&restore.own.label;…" oncommand="Backup.restoreFromFile();"/>
</menupopup>
</button>
<spacer flex="1"/>
<button id="close" dlgtype="accept" label="&close.label;"/>
</hbox>
</dialog>
+98
View File
@@ -0,0 +1,98 @@
/*
* This Source Code is subject to the terms of the Mozilla Public License
* version 2.0 (the "License"). You can obtain a copy of the License at
* http://mozilla.org/MPL/2.0/.
*/
function init()
{
if (Utils.isFennec)
{
let topWnd = window.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation)
.QueryInterface(Ci.nsIDocShellTreeItem)
.rootTreeItem
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindow);
if (topWnd.wrappedJSObject)
topWnd = topWnd.wrappedJSObject;
// window.close() closes the entire window (bug 642604), make sure to close
// only a single tab instead.
if ("BrowserUI" in topWnd)
{
window.close = function()
{
topWnd.BrowserUI.closeTab();
};
}
}
generateLinkText(E("changeDescription"));
for each (let subscription in FilterStorage.subscriptions)
{
if (subscription instanceof DownloadableSubscription && subscription.url)
{
E("listName").textContent = subscription.title;
let link = E("listHomepage");
link.setAttribute("_url", subscription.homepage);
link.setAttribute("tooltiptext", subscription.homepage);
E("listNameContainer").hidden = false;
E("listNone").hidden = true;
break;
}
}
}
function generateLinkText(element)
{
let template = element.getAttribute("_textTemplate");
let [, beforeLink, linkText, afterLink] = /(.*)\[link\](.*)\[\/link\](.*)/.exec(template) || [null, "", template, ""];
while (element.firstChild && element.firstChild.nodeType != Node.ELEMENT_NODE)
element.removeChild(element.firstChild);
while (element.lastChild && element.lastChild.nodeType != Node.ELEMENT_NODE)
element.removeChild(element.lastChild);
if (!element.firstChild)
return;
element.firstChild.textContent = linkText;
element.insertBefore(document.createTextNode(beforeLink), element.firstChild);
element.appendChild(document.createTextNode(afterLink));
}
function openFilters()
{
if (Utils.isFennec)
{
let topWnd = window.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation)
.QueryInterface(Ci.nsIDocShellTreeItem)
.rootTreeItem
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindow);
if (topWnd.wrappedJSObject)
topWnd = topWnd.wrappedJSObject;
// window.close() closes the entire window (bug 642604), make sure to close
// only a single tab instead.
if ("BrowserUI" in topWnd)
{
topWnd.BrowserUI.showPanel("addons-container");
function showOptions()
{
if (!topWnd.ExtensionsView.getElementForAddon(Utils.addonID))
Utils.runAsync(showOptions);
else
topWnd.ExtensionsView.showOptions(Utils.addonID);
}
showOptions();
}
}
else
Utils.openFiltersDialog();
}
+43
View File
@@ -0,0 +1,43 @@
<?xml version="1.0"?>
<!-- This Source Code is subject to the terms of the Mozilla Public License
- version 2.0 (the "License"). You can obtain a copy of the License at
- http://mozilla.org/MPL/2.0/. -->
#filter substitution
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
<?xml-stylesheet href="chrome://@ADDON_CHROME_NAME@/skin/firstRun.css" type="text/css"?>
<!DOCTYPE dialog SYSTEM "chrome://@ADDON_CHROME_NAME@/locale/firstRun.dtd">
<dialog
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
buttons="accept"
title="&dialog.title;"
id="abpFirstRun"
windowtype="abp:firstRun"
onload="init()">
<script type="application/x-javascript;version=1.7" src="utils.js"/>
<script type="application/x-javascript;version=1.7" src="firstRun.js"/>
<description>&confirmation;</description>
<vbox class="sectionContainer">
<description class="sectionTitle">&advancedSection;</description>
<description>&listSelection1;</description>
<vbox id="listNameContainer" hidden="true">
<description id="listName"/>
<label class="text-link" id="listHomepage" value="&visitHomepage.label;" onclick="Utils.loadInBrowser(this.getAttribute('_url'))"/>
</vbox>
<description id="listNone">&noList;</description>
</vbox>
<description id="changeDescription" _textTemplate="&listSelection2;">
<label class="text-link" onclick="openFilters();"/>
</description>
</dialog>
+104
View File
@@ -0,0 +1,104 @@
/*
* This Source Code is subject to the terms of the Mozilla Public License
* version 2.0 (the "License"). You can obtain a copy of the License at
* http://mozilla.org/MPL/2.0/.
*/
/**
* Draws a blinking border for a list of matching nodes.
*/
var flasher = {
nodes: null,
count: 0,
timer: null,
flash: function(nodes)
{
this.stop();
if (nodes)
nodes = nodes.filter(function(node) node.nodeType == Node.ELEMENT_NODE);
if (!nodes || !nodes.length)
return;
if (Prefs.flash_scrolltoitem && nodes[0].ownerDocument)
{
// Ensure that at least one node is visible when flashing
let wnd = nodes[0].ownerDocument.defaultView;
try
{
let hooks = wnd.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation)
.QueryInterface(Ci.nsIDocShellTreeItem)
.rootTreeItem
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindow)
.document.getElementById("abp-hooks");
if (hooks.wrappedJSObject)
hooks = hooks.wrappedJSObject;
let viewer = hooks.getBrowser().markupDocumentViewer;
viewer.scrollToNode(nodes[0]);
}
catch(e)
{
Cu.reportError(e);
}
}
this.nodes = nodes;
this.count = 0;
this.doFlash();
},
doFlash: function() {
if (this.count >= 12) {
this.stop();
return;
}
if (this.count % 2)
this.switchOff();
else
this.switchOn();
this.count++;
this.timer = window.setTimeout(function() {flasher.doFlash()}, 300);
},
stop: function() {
if (this.timer) {
window.clearTimeout(this.timer);
this.timer = null;
}
if (this.nodes) {
this.switchOff();
this.nodes = null;
}
},
setOutline: function(outline, offset)
{
for (var i = 0; i < this.nodes.length; i++)
{
if ("style" in this.nodes[i])
{
this.nodes[i].style.outline = outline;
this.nodes[i].style.outlineOffset = offset;
}
}
},
switchOn: function()
{
this.setOutline("#CC0000 dotted 2px", "-2px");
},
switchOff: function()
{
this.setOutline("", "");
}
};
+53
View File
@@ -0,0 +1,53 @@
# 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/.
#filter substitution
[.] chrome.jar:
% content @ADDON_CHROME_NAME@ chrome/content/
* content/about.js
* content/about.xul
content/composer.js
* content/composer.xul
content/errors.html
content/filters-backup.js
content/filters-filteractions.js
content/filters-filterview.js
content/filters-search.js
content/filters-subscriptionactions.js
content/filters-subscriptionview.js
content/filters.js
* content/filters.xul
content/firstRun.js
* content/firstRun.xul
content/flasher.js
* content/mailOverlay.xul
* content/navigatorOverlay.xul
content/objtabs.css
* content/overlay.js
* content/overlayGeneral.xul
* content/phoenixOverlay.xul
content/progressBar.xml
content/sendReport.js
* content/sendReport.xul
* content/settings.xul
* content/sidebar.js
* content/sidebar.xul
* content/sidebarDetached.xul
content/subscriptions.xml
content/subscriptionSelection.js
* content/subscriptionSelection.xul
* content/utils.js
#ifdef ADDON_TARGET_BASILISK
% overlay chrome://browser/content/browser.xul chrome://@ADDON_CHROME_NAME@/content/phoenixOverlay.xul application=@ADDON_TARGET_APP_ID@ application={ec8030f7-c20a-464f-9b0e-13a3a9e97384}
#else
% overlay chrome://browser/content/browser.xul chrome://@ADDON_CHROME_NAME@/content/phoenixOverlay.xul application=@ADDON_TARGET_APP_ID@
#endif
% overlay chrome://navigator/content/navigator.xul chrome://@ADDON_CHROME_NAME@/content/navigatorOverlay.xul application={a3210b97-8e8a-4737-9aa0-aa0e607640b9}
% overlay chrome://navigator/content/navigator.xul chrome://@ADDON_CHROME_NAME@/content/mailOverlay.xul application={3550f703-e582-4d05-9a08-453d09bdfdc6}
# Hack to prevent .Net Framework Assistant from messing up the browser
% override chrome://dotnetassistant/content/bootstrap.xul data:text/xml,<nada/>
+52
View File
@@ -0,0 +1,52 @@
<?xml version="1.0"?>
<!-- This Source Code is subject to the terms of the Mozilla Public License
- version 2.0 (the "License"). You can obtain a copy of the License at
- http://mozilla.org/MPL/2.0/. -->
<?xul-overlay href="chrome://adblockplus/content/ui/overlayGeneral.xul"?>
<overlay id="abp-overlay" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<!-- Window extensions -->
<window id="messengerWindow">
<popupset id="abp-popupset"/>
<keyset id="abp-keyset"/>
<commandset id="abp-commandset"/>
<box id="abp-hooks" getBrowser="return this.window.getMessageBrowser();"
addTab="this.window.openNewTabWith(arguments[0], (arguments[1] ? this.window.content.document : null), (arguments[1] ? arguments[1].shiftKey : false));"
getContextMenu="return this.E('messagePaneContext');"
getToolbox="return this.E('mail-toolbox')"
getDefaultToolbar="return this.E('msgToolbar');" toolbarInsertBefore="return this.E('button-junk');"/>
</window>
<!-- Status bar -->
<statusbar id="status-bar">
<statusbarpanel id="abp-status"/>
</statusbar>
<!-- Toolbar -->
<toolbarpalette id="MailToolbarPalette">
<toolbarbutton id="abp-toolbarbutton" type="menu-button" insertafter="button-junk"
class="toolbarbutton-1"/>
</toolbarpalette>
<!-- Tools menu -->
<menupopup id="taskPopup">
<menu id="abp-menuitem" insertafter="downloadmgr,javaScriptConsole"/>
</menupopup>
<!-- Context menu -->
<menupopup id="messagePaneContext">
<menuitem id="abp-image-menuitem"/>
<menuitem id="abp-object-menuitem"/>
<menuitem id="abp-media-menuitem"/>
<menuitem id="abp-frame-menuitem"/>
<menuitem id="abp-removeWhitelist-menuitem"/>
</menupopup>
<!-- Fake sidebar -->
<vbox id="messagepanebox">
<splitter id="abp-sidebar-splitter"/>
<vbox id="abp-sidebar"/>
</vbox>
</overlay>
@@ -3,5 +3,4 @@
# 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/.
JAR_MANIFESTS += ['jar.mn']
+59
View File
@@ -0,0 +1,59 @@
<?xml version="1.0"?>
<!-- This Source Code is subject to the terms of the Mozilla Public License
- version 2.0 (the "License"). You can obtain a copy of the License at
- http://mozilla.org/MPL/2.0/. -->
#filter substitution
<?xul-overlay href="chrome://@ADDON_CHROME_NAME@/content/overlayGeneral.xul"?>
<overlay id="abp-overlay" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<!-- Window extensions -->
<window id="main-window">
<popupset id="abp-popupset"/>
<keyset id="abp-keyset"/>
<commandset id="abp-commandset"/>
<box id="abp-hooks"
getBrowser="return this.window.gBrowser;"
addTab="
if (arguments[1] &amp;&amp; 'openNewTabWith' in this.window)
this.window.openNewTabWith(arguments[0], this.window.content.document, arguments[1].shiftKey);
else
this.window.gBrowser.loadOneTab(arguments[0], {inBackground: false});"
getContextMenu="return this.E('contentAreaContextMenu');"
getToolbox="return this.E('navigator-toolbox')"
getDefaultToolbar="return this.E('nav-bar');" toolbarInsertBefore="return this.E('throbber-box');"/>
</window>
<!-- Status bar -->
<statusbar id="status-bar">
<statusbarpanel id="abp-status" mousethrough="never" insertbefore="resizerBottomRight"/>
</statusbar>
<!-- Toolbar -->
<toolbarpalette id="BrowserToolbarPalette">
<toolbarbutton id="abp-toolbarbutton" type="menu-button" pack="end"
class="toolbarbutton-1"/>
</toolbarpalette>
<!-- Tools menu -->
<menupopup id="taskPopup">
<menu id="abp-menuitem" insertbefore="navBeginGlobalItems"/>
</menupopup>
<!-- Context menu -->
<menupopup id="contentAreaContextMenu">
<menuitem id="abp-image-menuitem"/>
<menuitem id="abp-object-menuitem"/>
<menuitem id="abp-media-menuitem"/>
<menuitem id="abp-frame-menuitem"/>
<menuitem id="abp-removeWhitelist-menuitem"/>
</menupopup>
<!-- Fake sidebar -->
<vbox id="appcontent">
<splitter id="abp-sidebar-splitter"/>
<vbox id="abp-sidebar"/>
</vbox>
</overlay>
+71
View File
@@ -0,0 +1,71 @@
/*
* This Source Code is subject to the terms of the Mozilla Public License
* version 2.0 (the "License"). You can obtain a copy of the License at
* http://mozilla.org/MPL/2.0/.
*/
@namespace url("http://www.w3.org/1999/xhtml");
.%%CLASSVISIBLETOP%%, .%%CLASSVISIBLEBOTTOM%%, .%%CLASSHIDDEN%%
{
position: fixed !important;
display: block !important;
width: auto !important;
height: auto !important;
right: auto !important;
bottom: auto !important;
z-index: 65535 !important;
float: left !important;
border-color: black !important;
border-style: solid !important;
background: white !important;
color: black !important;
cursor: pointer !important;
white-space: nowrap !important;
font-family: Arial,Helvetica,Sans-Serif !important;
font-size: 10px !important;
font-style: normal !important;
font-variant: normal !important;
font-weight: normal !important;
letter-spacing: normal !important;
line-height: normal !important;
text-align: center !important;
text-decoration: none !important;
text-indent: 0px !important;
text-transform: none !important;
direction: ltr !important;
padding: 0px 5px !important;
-moz-binding: none !important;
-moz-user-focus: none !important;
-moz-user-input: none !important;
-moz-user-select: none !important;
}
.%%CLASSVISIBLETOP%%, .%%CLASSHIDDEN%%
{
border-width: 1px 1px 0px 1px !important;
border-top-left-radius: 10px !important;
border-top-right-radius: 10px !important;
border-bottom-left-radius: 0px !important;
border-bottom-right-radius: 0px !important;
}
.%%CLASSVISIBLEBOTTOM%%
{
border-width: 0px 1px 1px 1px !important;
border-top-left-radius: 0px !important;
border-top-right-radius: 0px !important;
border-bottom-left-radius: 10px !important;
border-bottom-right-radius: 10px !important;
}
.%%CLASSVISIBLETOP%%, .%%CLASSVISIBLEBOTTOM%%
{
visibility: visible !important;
}
.%%CLASSHIDDEN%%
{
visibility: hidden !important;
}
+30
View File
@@ -0,0 +1,30 @@
/*
* This Source Code is subject to the terms of the Mozilla Public License
* version 2.0 (the "License"). You can obtain a copy of the License at
* http://mozilla.org/MPL/2.0/.
*/
#filter substitution
{
let Cc = Components.classes;
let Ci = Components.interfaces;
let Cr = Components.results;
let Cu = Components.utils;
// Use UIReady event to initialize in Fennec (bug 531071)
let eventName = Cu.import("resource://@ADDON_CHROME_NAME@/modules/Utils.jsm", null).Utils.isFennec ? "UIReady" : "load";
window.addEventListener(eventName, function()
{
window.removeEventListener(eventName, arguments.callee, false);
if (!("@adblockplus.org/abp/public;1" in Cc))
{
// Force initialization (in Fennec we won't be initialized at this point)
Cu.import("resource://@ADDON_CHROME_NAME@/modules/Bootstrap.jsm", null).Bootstrap.startup();
}
Cu.import("resource://@ADDON_CHROME_NAME@/modules/AppIntegration.jsm", null).AppIntegration.addWindow(window);
}, false);
}
+97
View File
@@ -0,0 +1,97 @@
<?xml version="1.0"?>
<!-- This Source Code is subject to the terms of the Mozilla Public License
- version 2.0 (the "License"). You can obtain a copy of the License at
- http://mozilla.org/MPL/2.0/. -->
#filter substitution
<?xml-stylesheet href="chrome://@ADDON_CHROME_NAME@/skin/overlay.css" type="text/css"?>
<!DOCTYPE overlay SYSTEM "chrome://@ADDON_CHROME_NAME@/locale/overlay.dtd">
<overlay id="abp-overlay-general" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<script type="application/x-javascript;version=1.7" src="overlay.js"/>
<popupset id="abp-popupset">
<!-- Icon's tooltip -->
<tooltip id="abp-tooltip" orient="vertical">
<description id="abp-tooltip-action" hidden="true"/>
<label id="abp-tooltip-status-label" value="&status.tooltip;"/>
<description id="abp-tooltip-status"/>
<label id="abp-tooltip-blocked-label" value="&blocked.tooltip;" hidden="true"/>
<description id="abp-tooltip-blocked" hidden="true"/>
<label id="abp-tooltip-filters-label" value="&filters.tooltip;" hidden="true"/>
<vbox id="abp-tooltip-filters" hidden="true"/>
<description id="abp-tooltip-more-filters" value="…" hidden="true"/>
</tooltip>
<!-- Icon's context menu -->
<menupopup id="abp-status-popup" context="">
<menuitem id="abp-status-opensidebar" label="&opensidebar.label;" accesskey="&opensidebar.accesskey;" key="abp-key-sidebar" command="abp-command-sidebar"/>
<menuitem id="abp-status-closesidebar" label="&closesidebar.label;" accesskey="&closesidebar.accesskey;" key="abp-key-sidebar" command="abp-command-sidebar"/>
<menuitem id="abp-status-filters" label="&filters.label;…" accesskey="&filters.accesskey;" key="abp-key-filters" command="abp-command-filters"/>
<menuseparator id="abp-status-whitelist-sep"/>
<menuitem id="abp-status-whitelistsite" labeltempl="&whitelist.site.label;" type="checkbox" command="abp-command-togglesitewhitelist"/>
<menuitem id="abp-status-whitelistpage" label="&whitelist.page.label;" type="checkbox" command="abp-command-togglepagewhitelist"/>
<menuitem id="abp-status-disabled" label="&disable.label;" type="checkbox" key="abp-key-enable" command="abp-command-enable"/>
<menuseparator/>
<menu id="abp-status-options" label="&options.label;" accesskey="&options.accesskey;">
<menupopup id="abp-status-options-popup">
<menuitem id="abp-status-frameobjects" label="&objecttabs.label;" accesskey="&objecttabs.accesskey;" type="checkbox" command="abp-command-toggleobjtabs"/>
<menuitem id="abp-status-slowcollapse" label="&hideplaceholders.label;" accesskey="&hideplaceholders.accesskey;" type="checkbox" command="abp-command-togglecollapse"/>
<menuitem id="abp-status-savestats" label="&counthits.label;" accesskey="&counthits.accesskey;" type="checkbox" command="abp-command-togglesavestats"/>
<menuseparator id="abp-status-iconSettingsSeparator"/>
<menuitem id="abp-status-showintoolbar" label="&showintoolbar.label;" accesskey="&showintoolbar.accesskey;" type="checkbox" command="abp-command-toggleshowintoolbar"/>
<menuitem id="abp-status-showinstatusbar" label="&showinstatusbar.label;" accesskey="&showinstatusbar.accesskey;" type="checkbox" command="abp-command-toggleshowinstatusbar"/>
</menupopup>
</menu>
</menupopup>
</popupset>
<keyset id="abp-keyset"/>
<commandset id="abp-commandset">
<!-- Dummy oncommand attributes are work-arounds for bug 371900 -->
<command id="abp-command-filters" oncommand="//"/>
<command id="abp-command-settings" oncommand="//"/>
<command id="abp-command-sidebar" oncommand="//"/>
<command id="abp-command-togglesitewhitelist"/>
<command id="abp-command-togglepagewhitelist"/>
<command id="abp-command-toggleobjtabs"/>
<command id="abp-command-togglecollapse"/>
<command id="abp-command-togglesavestats"/>
<command id="abp-command-togglesync"/>
<command id="abp-command-toggleshowintoolbar"/>
<command id="abp-command-toggleshowinstatusbar"/>
<command id="abp-command-enable" oncommand="//"/>
</commandset>
<statusbarpanel id="abp-status" class="statusbarpanel-iconic"
context="abp-status-popup" tooltip="abp-tooltip"/>
<toolbarbutton id="abp-toolbarbutton" label="&toolbarbutton.label;"
tooltip="abp-tooltip" context="abp-status-popup"/>
<box id="abp-hooks" objtabtext="&objecttab.title;…" objtabtooltip="&objecttab.tooltip;"/>
<!-- Tools menu -->
<menu id="abp-menuitem" label="&toolsmenu.label;"/>
<!-- Context menu -->
<menuitem id="abp-image-menuitem" label="&context.image.label;…" hidden="true"/>
<menuitem id="abp-object-menuitem" label="&context.object.label;…" hidden="true"/>
<menuitem id="abp-media-menuitem" label="&context.media.label;…" hidden="true"/>
<menuitem id="abp-frame-menuitem" label="&context.frame.label;…" hidden="true"/>
<menuitem id="abp-removeWhitelist-menuitem" label="&context.removeWhitelist.label;" hidden="true"/>
<!-- Fake sidebar -->
<splitter id="abp-sidebar-splitter" hidden="true"/>
<vbox id="abp-sidebar" height="200" width="200" hidden="true" persist="height width">
<toolbox id="abp-sidebar-header">
<toolbar id="abp-sidebar-toolbar" align="center" grippyhidden="true" fullscreentoolbar="true">
<label id="abp-sidebar-title" control="abp-sidebar-browser" value="&sidebar.title;" flex="1" crop="end"/>
<toolbarbutton id="abp-sidebar-close" command="abp-command-sidebar" tooltiptext="&closesidebar.label;"/>
</toolbar>
</toolbox>
<iframe id="abp-sidebar-browser" flex="1"/>
</vbox>
</overlay>
+56
View File
@@ -0,0 +1,56 @@
<?xml version="1.0"?>
<!-- This Source Code is subject to the terms of the Mozilla Public License
- version 2.0 (the "License"). You can obtain a copy of the License at
- http://mozilla.org/MPL/2.0/. -->
#filter substitution
<?xul-overlay href="chrome://@ADDON_CHROME_NAME@/content/overlayGeneral.xul"?>
<overlay id="abp-overlay" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<!-- Window extensions -->
<window id="main-window">
<popupset id="abp-popupset"/>
<keyset id="abp-keyset"/>
<commandset id="abp-commandset"/>
<box id="abp-hooks"
getBrowser="return this.window.gBrowser;"
addTab="this.window.gBrowser.loadOneTab(arguments[0], null, null, null, false);"
getContextMenu="return this.E('contentAreaContextMenu');"
getToolbox="return this.E('navigator-toolbox')"
getDefaultToolbar="return this.E('nav-bar');"/>
</window>
<!-- Status bar -->
<statusbar id="status-bar">
<statusbarpanel id="abp-status" mousethrough="never" insertbefore="resizerBottomRight"/>
</statusbar>
<!-- Toolbar -->
<toolbarpalette id="BrowserToolbarPalette">
<toolbarbutton id="abp-toolbarbutton" type="menu-button" insertbefore="print-button"
class="toolbarbutton-1"/>
</toolbarpalette>
<!-- Tools menu -->
<menupopup id="menu_ToolsPopup">
<menu id="abp-menuitem" insertbefore="webDeveloperMenu" />
</menupopup>
<!-- Context menu -->
<menupopup id="contentAreaContextMenu">
<menuitem id="abp-image-menuitem"/>
<menuitem id="abp-object-menuitem"/>
<menuitem id="abp-media-menuitem"/>
<menuitem id="abp-frame-menuitem"/>
<menuitem id="abp-removeWhitelist-menuitem"/>
</menupopup>
<!-- Fake sidebar -->
<statuspanel id="statusbar-display" fixed="true"/> <!-- Make sure not to resize this element -->
<vbox id="appcontent">
<splitter id="abp-sidebar-splitter"/>
<vbox id="abp-sidebar"/>
</vbox>
</overlay>
+154
View File
@@ -0,0 +1,154 @@
<?xml version="1.0"?>
<!-- This Source Code is subject to the terms of the Mozilla Public License
- version 2.0 (the "License"). You can obtain a copy of the License at
- http://mozilla.org/MPL/2.0/. -->
<bindings id="progressBarBindings"
xmlns="http://www.mozilla.org/xbl"
xmlns:xbl="http://www.mozilla.org/xbl"
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns:html="http://www.w3.org/1999/xhtml">
<binding id="progressBar">
<content orient="horizontal" pack="center">
<xul:stack flex="1">
<html:canvas anonid="canvas" width="1" height="1"/>
<children/>
</xul:stack>
</content>
<implementation>
<field name="_gapWidth">5</field>
<field name="_arrowheadWidth">5</field>
<field name="_canvas">document.getAnonymousElementByAttribute(this, "anonid", "canvas")</field>
<constructor>
<![CDATA[
// Run _init() after window loads, correct sizes might be unknown during construction
let me = this;
let callback = function()
{
window.removeEventListener("load", callback, false);
window.setTimeout(function() me._init(), 0);
}
window.addEventListener("load", callback, false);
]]>
</constructor>
<method name="_init">
<body>
<![CDATA[
let canvas = this._canvas;
let width = canvas.width = canvas.offsetWidth;
let height = canvas.height = canvas.offsetHeight;
let context = canvas.getContext("2d");
context.fillStyle = window.getComputedStyle(this, "").color;
context.strokeStyle = window.getComputedStyle(this, "").color;
context.lineWidth = 1;
let panelCount = this.childNodes.length;
let panelWidth = (width - this._gapWidth * (panelCount - 1) - 1) / panelCount;
for (let i = 0; i < panelCount; i++)
{
context.save();
context.translate(Math.round(i * (panelWidth + this._gapWidth)) + 0.5, 0.5);
context.beginPath();
if (i)
context.moveTo(-this._arrowheadWidth, 0);
else
context.moveTo(0, 0);
context.lineTo(panelWidth - this._arrowheadWidth, 0);
context.lineTo(panelWidth, (height - 1) / 2);
context.lineTo(panelWidth - this._arrowheadWidth, height - 1);
if (i)
{
context.lineTo(-this._arrowheadWidth, height - 1);
context.lineTo(0, (height - 1) / 2);
context.lineTo(-this._arrowheadWidth, 0);
}
else
{
context.lineTo(0, height - 1);
context.lineTo(0, 0);
}
context.stroke();
context.restore();
let childLeft = Math.round(i * (panelWidth + this._gapWidth) + 1);
let childWidth = panelWidth - this._arrowheadWidth - 2;
let child = this.childNodes[i];
child.style.marginLeft = childLeft + "px";
child.style.marginRight = (width - childLeft - childWidth) + "px";
child.style.width = childWidth + "px";
}
// Resize after initialization should be ignored
canvas.parentNode.removeAttribute("flex");
]]>
</body>
</method>
<property name="activeItem">
<getter>
<![CDATA[
for (let i = 0; i < this.childNodes.length; i++)
{
let child = this.childNodes[i];
if (/\bactive\b/.test(child.className))
return child;
}
return null;
]]>
</getter>
<setter>
<![CDATA[
let complete = true;
for (let i = 0; i < this.childNodes.length; i++)
{
let child = this.childNodes[i];
if (child == val)
complete = false;
if (!complete && child.value[0] == "✔")
child.value = child.value.replace(/^✔\s*/, "");
else if (complete && child.value[0] != "✔")
child.value = "✔ " + child.value;
if (child != val && /\bactive\b/.test(child.className))
child.className = child.className.replace(/\s*\bactive\b/, "");
else if (child == val && !/\bactive\b/.test(child.className))
child.className += " active";
}
return null;
]]>
</setter>
</property>
<property name="activeItemComplete">
<getter>
<![CDATA[
let activeItem = this.activeItem;
if (!activeItem)
return false;
else
return activeItem.value[0] == "✔";
]]>
</getter>
<setter>
<![CDATA[
let activeItem = this.activeItem;
if (!activeItem)
return;
if (!val && activeItem.value[0] == "✔")
activeItem.value = child.value.replace(/^✔\s*/, "");
else if (val && activeItem.value[0] != "✔")
activeItem.value = "✔ " + activeItem.value;
]]>
</setter>
</property>
</implementation>
</binding>
</bindings>
File diff suppressed because it is too large Load Diff
+248
View File
@@ -0,0 +1,248 @@
<?xml version="1.0"?>
<!-- This Source Code is subject to the terms of the Mozilla Public License
- version 2.0 (the "License"). You can obtain a copy of the License at
- http://mozilla.org/MPL/2.0/. -->
#filter substitution
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
<?xml-stylesheet href="chrome://global/skin/tree.css" type="text/css"?>
<?xml-stylesheet href="chrome://@ADDON_CHROME_NAME@/skin/sendReport.css" type="text/css"?>
<!DOCTYPE dialog [
<!ENTITY % reporterDTD SYSTEM "chrome://@ADDON_CHROME_NAME@/locale/sendReport.dtd">
%reporterDTD;
<!ENTITY % filtersDTD SYSTEM "chrome://@ADDON_CHROME_NAME@/locale/filters.dtd">
%filtersDTD;
]>
<wizard
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
title="&wizard.title;"
id="abpSendReportWizard"
onload="initWizard();"
width="800"
height="550"
sendbuttonlabel="&sendButton.label;"
sendbuttonaccesskey="&sendButton.accesskey;"
windowtype="abp:sendReport">
<script type="application/x-javascript;version=1.7" src="utils.js"/>
<script type="application/x-javascript;version=1.7" src="sendReport.js"/>
<keyset id="wizardKeys">
<key id="undoKey" modifiers="accel" key="Z" oncommand="if (document.documentElement.currentPage.id == 'screenshotPage') screenshotDataSource.undo();"/>
</keyset>
<box hidden="true">
<xbl:bindings id="headerBindings" xmlns:xbl="http://www.mozilla.org/xbl">
<xbl:binding id="headerBinding">
<xbl:content orient="vertical">
<deck xbl:inherits="selectedIndex=viewIndex">
<description class="wizard-header-label" xbl:inherits="value=label"/>
<progressbar id="progressBar" style="-moz-binding: url(progressBar.xml#progressBar);">
<label id="typeSelectorHeader" class="progressLabel" value="&typeSelector.heading;" crop="end"/>
<label id="screenshotHeader" class="progressLabel" value="&screenshot.heading;" crop="end"/>
<label id="commentPageHeader" class="progressLabel" value="&commentPage.heading;" crop="end"/>
<label id="sendPageHeader" class="progressLabel" value="&sendPage.heading;" crop="end"/>
</progressbar>
</deck>
</xbl:content>
</xbl:binding>
</xbl:bindings>
<label id="privacyLink" class="text-link" value="&privacyPolicy.label;" onclick="Utils.loadDocLink('reporter_privacy');"/>
</box>
<wizardpage id="dataCollectorPage" pageid="dataCollector" next="typeSelector" label="&dataCollector.heading;" onpageshow="initDataCollectorPage();">
<description>&dataCollector.description;</description>
<progressmeter id="dataCollectorProgress" mode="undetermined"/>
</wizardpage>
<wizardpage id="typeSelectorPage" pageid="typeSelector" next="screenshot" label="&typeSelector.heading;" onpageshow="initTypeSelectorPage();">
<description>&typeSelector.description;</description>
<radiogroup id="typeGroup" oncommand="typeSelectionUpdated();">
<radio id="typeFalsePositive" value="false positive" label="&typeSelector.falsePositive.label;" accesskey="&typeSelector.falsePositive.accesskey;"/>
<description class="radioDescription">&typeSelector.falsePositive.description;</description>
<radio id="typeFalseNegative" value="false negative" label="&typeSelector.falseNegative.label;" accesskey="&typeSelector.falseNegative.accesskey;"/>
<description class="radioDescription">&typeSelector.falseNegative.description;</description>
<radio id="typeOther" value="other" label="&typeSelector.other.label;" accesskey="&typeSelector.other.accesskey;"/>
<description class="radioDescription">&typeSelector.other.description;</description>
</radiogroup>
<deck id="recentReports" currentIndex="0" flex="1">
<vbox pack="end">
<label class="text-link" value="&showRecentReports.label;" onclick="E('recentReports').selectedIndex = 1;"/>
</vbox>
<groupbox flex="1">
<caption label="&recentReports.label;"/>
<grid flex="1" id="recentReportsList">
<columns>
<column flex="2"/>
<column flex="1"/>
<column/>
</columns>
<rows id="recentReportsRows" onclick="reportsListDataSource.handleClick(event);"/>
</grid>
<hbox pack="start">
<button label="&recentReports.clear.label;" accesskey="&recentReports.clear.accesskey;" oncommand="reportsListDataSource.clear();"/>
</hbox>
</groupbox>
</deck>
</wizardpage>
<wizardpage id="updatePage" pageid="update" next="screenshot" onpageshow="subscriptionUpdateDataSource.showPage();" reloadButtonLabel="&reloadButton.label;" reloadButtonAccesskey="&reloadButton.accesskey;">
<vbox id="updateInProgress">
<description>&update.inProgress.description;</description>
<progressmeter mode="undetermined"/>
</vbox>
<description id="updateFixedIssue" hidden="true">&update.fixed.description;</description>
<vbox id="outdatedSubscriptions">
<description>&outdatedSubscriptions.description;</description>
<description id="outdatedSubscriptionTemplate" class="text-link" onclick="Utils.loadInBrowser(this.getAttribute('_url'));"/>
<vbox id="outdatedSubscriptionsList"/>
<hbox>
<button label="&update.start.label;" oncommand="subscriptionUpdateDataSource.updateOutdated();window.close();"/>
<button label="&issues.openPreferences.label;" oncommand="Utils.openFiltersDialog();window.close();"/>
</hbox>
</vbox>
</wizardpage>
<wizardpage id="issuesPage" pageid="issues" next="screenshot" onpageshow="initIssuesPage();" reloadButtonLabel="&reloadButton.label;" reloadButtonAccesskey="&reloadButton.accesskey;">
<description>&issues.description;</description>
<vbox id="issuesBox" flex="1">
<groupbox id="issuesWhitelistBox" hidden="true">
<description>&issues.whitelist.description;</description>
<hbox pack="end">
<button label="&issues.whitelist.remove.label;" oncommand="issuesDataSource.removeWhitelist();"/>
</hbox>
</groupbox>
<groupbox id="issuesDisabledBox" hidden="true">
<description>&issues.disabled.description;</description>
<hbox pack="end">
<button label="&issues.disabled.enable.label;" oncommand="issuesDataSource.enable();"/>
</hbox>
</groupbox>
<groupbox id="issuesNoFiltersBox" hidden="true">
<description>&issues.nofilters.description;</description>
</groupbox>
<groupbox id="issuesNoSubscriptionsBox" hidden="true">
<description>&issues.nosubscriptions.description;</description>
<hbox pack="end">
<button label="&issues.nosubscriptions.add.label;" oncommand="issuesDataSource.addSubscription();"/>
</hbox>
</groupbox>
<groupbox id="issuesSubscriptionCountBox" hidden="true">
<description>&issues.subscriptionCount.description;</description>
<hbox pack="end">
<button label="&issues.openPreferences.label;" oncommand="Utils.openFiltersDialog();window.close();"/>
</hbox>
</groupbox>
<groupbox id="issuesOwnFiltersBox" hidden="true">
<description>&issues.ownfilters.description;</description>
<hbox id="issuesOwnFiltersTemplate" align="center" hidden="true">
<description flex="1" crop="end"/>
<button label="&issues.ownfilters.disable.label;" oncommand="issuesDataSource.disableFilter(this.parentNode);"/>
</hbox>
<vbox id="issuesOwnFilters"/>
</groupbox>
<groupbox id="issuesDisabledSubscriptionsBox" hidden="true">
<description>&issues.disabledgroups.description;</description>
<hbox id="issuesDisabledSubscriptionsTemplate" align="center" hidden="true">
<description flex="1" crop="end"/>
<button label="&issues.disabledgroups.enable.label;" oncommand="issuesDataSource.enableSubscription(this.parentNode);"/>
</hbox>
<vbox id="issuesDisabledSubscriptions"/>
</groupbox>
<groupbox id="issuesDisabledFiltersBox" hidden="true">
<description>&issues.disabledfilters.description;</description>
<hbox id="issuesDisabledFiltersTemplate" align="center" hidden="true">
<description flex="1" crop="end"/>
<button label="&issues.disabledfilters.enable.label;" oncommand="issuesDataSource.enableFilter(this.parentNode);"/>
</hbox>
<vbox id="issuesDisabledFilters"/>
</groupbox>
</vbox>
<checkbox id="issuesOverride" label="&issues.override.label;" accesskey="&issues.override.accesskey;" oncommand="updateIssuesOverride();"/>
<description id="issuesChangeMessage" hidden="true">&issues.change.description;</description>
</wizardpage>
<wizardpage id="typeWarningPage" pageid="typeWarning" next="screenshot" onpageshow="initTypeWarningPage();">
<description id="typeWarningText">
&typeWarning.description;
<label id="typeWarningTextLink" class="text-link" onclick="Utils.loadDocLink('reporter_other_link');"/>
</description>
<checkbox id="typeWarningOverride" label="&typeWarning.override.label;" accesskey="&typeWarning.override.accesskey;" oncommand="updateTypeWarningOverride();"/>
</wizardpage>
<wizardpage id="screenshotPage" pageid="screenshot" next="comment" label="&screenshot.heading;" onpageshow="initScreenshotPage();">
<description>&screenshot.description;</description>
<checkbox id="screenshotCheckbox" checked="true" label="&screenshot.attach.label;" accesskey="&screenshot.attach.accesskey;" oncommand="screenshotDataSource.enabled = this.checked;"/>
<hbox id="screenshotButtons" pack="end">
<button id="screenshotMarkButton" type="radio" group="selectionType" oncommand="screenshotDataSource.selectionType = 'mark';" checked="true" label="&screenshot.mark.label;" accesskey="&screenshot.mark.accesskey;"/>
<button id="screenshotRemoveButton" type="radio" group="selectionType" oncommand="screenshotDataSource.selectionType = 'remove';" label="&screenshot.remove.label;" accesskey="&screenshot.remove.accesskey;"/>
<button id="screenshotUndoButton" oncommand="screenshotDataSource.undo();" disabled="true" label="&screenshot.undo.label;" accesskey="&screenshot.undo.accesskey;"/>
</hbox>
<vbox id="screenshotBox" flex="1">
<canvas xmlns="http://www.w3.org/1999/xhtml" id="screenshotCanvas" onmousedown="screenshotDataSource.startSelection(event);" onmouseup="screenshotDataSource.stopSelection(event);" onmouseout="screenshotDataSource.stopSelection(event);" onmousemove="screenshotDataSource.updateSelection(event);"/>
</vbox>
</wizardpage>
<wizardpage id="commentPage" pageid="comment" next="send" label="&commentPage.heading;" onpageshow="initCommentPage();">
<description>&commentPage.description;</description>
<label class="topLabel" control="comment" value="&comment.label;" accesskey="&comment.accesskey;"/>
<textbox id="comment" multiline="true" flex="1" oninput="updateComment();"/>
<hbox align="baseline">
<label control="email" value="&email.label;" accesskey="&email.accesskey;"/>
<textbox id="email" flex="1" maxlength="200" oninput="updateEmail();"/>
</hbox>
<description id="commentLengthWarning" visible="false">&comment.lengthWarning;</description>
<checkbox id="extensionsCheckbox" label="&attachExtensions.label;" accesskey="&attachExtensions.accesskey;" oncommand="updateExtensions(this.checked);"/>
<deck id="dataDeck" selectedIndex="0" flex="2">
<vbox pack="start">
<label class="text-link" value="&showData.label;" onclick="showDataField();"/>
</vbox>
<vbox>
<label control="data" value="&data.label;" accesskey="&data.accesskey;"/>
<textbox id="data" readonly="true" multiline="true" wrap="off" flex="1"/>
</vbox>
</deck>
</wizardpage>
<wizardpage id="sendPage" pageid="send" label="&sendPage.heading;" onpageshow="initSendPage();">
<description id="sendReportMessage">&sendPage.waitMessage;</description>
<vbox id="sendReportErrorBox" align="end" hidden="true">
<description id="sendReportError" textTemplate="&sendPage.errorMessage;" defaultError="&subscription.lastDownload.connectionError;">
<label id="sendReportErrorLinks" class="text-link" onclick="Utils.loadDocLink('reporter_connect_issue');"/>
</description>
<button id="sendRetryButton" label="&sendPage.retry.label;" oncommand="initSendPage();"/>
</vbox>
<progressmeter id="sendReportProgress" mode="undetermined"/>
<iframe id="result" type="content" flex="1" hidden="true" onclick="processLinkClick(event);"
confirmationMessage="&sendPage.confirmation;" knownIssueMessage="&sendPage.knownIssue;"/>
<hbox id="copyLinkBox" pack="end" hidden="true">
<button id="copyLink" disabled="true" label="&copyLink.label;" accesskey="&copyLink.accesskey;" oncommand="copyLink(this.getAttribute('url'));"/>
</hbox>
</wizardpage>
</wizard>
+29
View File
@@ -0,0 +1,29 @@
<?xml version="1.0"?>
<!-- This Source Code is subject to the terms of the Mozilla Public License
- version 2.0 (the "License"). You can obtain a copy of the License at
- http://mozilla.org/MPL/2.0/. -->
#filter substitution
<!DOCTYPE vbox SYSTEM "chrome://adblockplus/locale/overlay.dtd">
<window
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
width="1"
height="1"
onload="window.close();AppIntegration.openMenu(window.opener);">
<script type="application/x-javascript;version=1.7" src="utils.js"/>
<setting type="control">
<button id="adblockplus-filters" label="&filters.label;…"/>
</setting>
<setting pref="extensions.@ADDON_CHROME_NAME@.enabled" type="bool" inverted="true" title="&disable.label;"/>
<setting pref="extensions.@ADDON_CHROME_NAME@.frameobjects" type="bool" title="&objecttabs.label;"/>
<setting pref="extensions.@ADDON_CHROME_NAME@.fastcollapse" type="bool" inverted="true" title="&hideplaceholders.label;"/>
<setting id="adblockplus-savestats" type="bool" title="&counthits.label;"/>
<setting id="adblockplus-sync" type="bool" title="&sync.label;"/>
<setting id="adblockplus-showinaddonbar" type="bool" title="&showinaddonbar.label;"/>
<setting id="adblockplus-showintoolbar" type="bool" title="&showintoolbar.label;"/>
<setting id="adblockplus-showinstatusbar" pref="extensions.@ADDON_CHROME_NAME@.showinstatusbar" type="bool" title="&showinstatusbar.label;"/>
</window>
File diff suppressed because it is too large Load Diff
+128
View File
@@ -0,0 +1,128 @@
<?xml version="1.0"?>
<!-- This Source Code is subject to the terms of the Mozilla Public License
- version 2.0 (the "License"). You can obtain a copy of the License at
- http://mozilla.org/MPL/2.0/. -->
#filter substitution
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
<?xml-stylesheet href="chrome://@ADDON_CHROME_NAME@/skin/sidebar.css" type="text/css"?>
<!DOCTYPE page SYSTEM "chrome://@ADDON_CHROME_NAME@/locale/sidebar.dtd">
<page
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
id="abp-sidebar"
onload="init()"
onunload="cleanUp()"
docDomainThirdParty="&docDomain.thirdParty;"
docDomainFirstParty="&docDomain.firstParty;">
<script type="application/x-javascript;version=1.7" src="utils.js"/>
<script type="application/x-javascript;version=1.7" src="sidebar.js"/>
<script type="application/x-javascript;version=1.7" src="flasher.js"/>
<keyset id="sidebarKeys">
<key id="block-key" keycode="VK_ENTER"/>
<key id="copy-key" modifiers="accel" key="C" command="copy-command"/>
<key id="selectAll-key" modifiers="accel" key="A" command="selectAll-command"/>
</keyset>
<commandset id="sidebarCommands">
<command id="copy-command" oncommand="copyToClipboard()" disabled="true"/>
<command id="selectAll-command" oncommand="selectAll()"/>
</commandset>
<popupset id="sidebarPopups">
<tooltip id="tooltip" orient="vertical" onpopupshowing="fillInTooltip(event);">
<description id="tooltipDummy"/>
<hbox id="tooltipPreviewBox" pack="start">
<image id="tooltipPreview" validate="never"/>
</hbox>
<grid>
<columns>
<column/>
<column flex="1"/>
</columns>
<rows>
<row id="tooltipAddressRow" align="top">
<label value="&tooltip.address.label;"/>
<vbox id="tooltipAddress"/>
</row>
<row id="tooltipTypeRow">
<label value="&tooltip.type.label;"/>
<description id="tooltipType" filtered="&tooltip.type.blocked;" whitelisted="&tooltip.type.whitelisted;"/>
</row>
<row id="tooltipSizeRow">
<label value="&tooltip.size.label;"/>
<description id="tooltipSize"/>
</row>
<row id="tooltipDocDomainRow">
<label value="&tooltip.docDomain.label;"/>
<description id="tooltipDocDomain"/>
</row>
<row id="tooltipFilterRow" align="top">
<label value="&tooltip.filter.label;"/>
<vbox id="tooltipFilter" disabledText="&tooltip.filter.disabled;"/>
</row>
<row id="tooltipFilterSourceRow" align="top">
<label value="&tooltip.filterSource.label;"/>
<vbox id="tooltipFilterSource"/>
</row>
</rows>
</grid>
</tooltip>
<menupopup id="context" onpopupshowing="return fillInContext(event)">
<menuitem id="contextBlock" label="&context.block.label;…" oncommand="doBlock()" key="block-key"/>
<menuitem id="contextWhitelist" label="&context.whitelist.label;…" oncommand="doBlock()" key="block-key"/>
<menuitem id="contextEditFilter" label="&context.editfilter.label;…" oncommand="editFilter()"/>
<menuitem id="contextDisableFilter" labeltempl="&context.disablefilter.label;" oncommand="enableFilter(treeView.getSelectedItem().filter, false)"/>
<menuitem id="contextEnableFilter" labeltempl="&context.enablefilter.label;" oncommand="enableFilter(treeView.getSelectedItem().filter, true)"/>
<menuitem id="contextDisableOnSite" labeltempl="&context.disablefilteronsite.label;" oncommand="disableOnSite()"/>
<menuseparator id="contextOpenSep"/>
<menuitem id="contextOpen" label="&context.open.label;" oncommand="openInTab(null, event)"/>
<menuitem id="contextFlash" label="&context.flash.label;" oncommand="onSelectionChange()"/>
<menuitem id="contextCopy" label="&context.copy.label;" command="copy-command" key="copy-key"/>
<menuitem id="contextCopyFilter" label="&context.copyFilter.label;" oncommand="copyFilter()"/>
<menuseparator id="contextSelectSep"/>
<menuitem id="contextSelectAll" label="&context.selectAll.label;" command="selectAll-command" key="selectAll-key"/>
</menupopup>
</popupset>
<hbox>
<hbox align="center" flex="1">
<label value="&search.label;" accesskey="&search.accesskey;" control="searchField"/>
<textbox id="searchField" flex="1" type="search" oncommand="treeView.setFilter(this.value)"/>
</hbox>
<description id="detachButton" value="&detach.label;" onclick="detach(true)"/>
<description id="reattachButton" value="&reattach.label;" onclick="if (this.getAttribute('disabled') != 'true') detach(false)" hidden="true"/>
</hbox>
<tree id="list" context="context" flex="1" seltype="multiple" enableColumnDrag="true"
defaultSort="state descending" persist="defaultSort"
onkeypress="if (event.keyCode == event.DOM_VK_RETURN || event.keyCode == event.DOM_VK_ENTER) doBlock()">
<treecols>
<treecol id="address" label="&address.label;" flex="2" crop="center" persist="width ordinal sortDirection hidden"/>
<splitter class="tree-splitter"/>
<treecol id="filter" label="&filter.label;" flex="1" persist="width ordinal sortDirection hidden"/>
<splitter class="tree-splitter"/>
<treecol id="type" label="&type.label;" width="80" persist="width ordinal sortDirection hidden"/>
<splitter class="tree-splitter"/>
<treecol id="state" label="&state.label;" width="16" persist="width ordinal sortDirection hidden"/>
<splitter class="tree-splitter"/>
<treecol id="size" label="&size.label;" width="60" hidden="true" persist="width ordinal sortDirection hidden"/>
<splitter class="tree-splitter"/>
<treecol id="docDomain" label="&docDomain.label;" width="100" hidden="true" persist="width ordinal sortDirection hidden"/>
<splitter class="tree-splitter"/>
<treecol id="filterSource" label="&filterSource.label;" width="100" hidden="true" persist="width ordinal sortDirection hidden"/>
</treecols>
<treechildren id="treechildren"
tooltip="tooltip"
onclick="handleClick(event)"
ondblclick="handleDblClick(event)"
noitemslabel="&noitems.label;"
whitelistedlabel="&whitelisted.label;"/>
</tree>
</page>
+39
View File
@@ -0,0 +1,39 @@
<?xml version="1.0"?>
<!-- This Source Code is subject to the terms of the Mozilla Public License
- version 2.0 (the "License"). You can obtain a copy of the License at
- http://mozilla.org/MPL/2.0/. -->
#filter substitution
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
<!DOCTYPE page SYSTEM "chrome://@ADDON_CHROME_NAME@/locale/sidebar.dtd">
<window
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
id="abpDetachedSidebar"
title="&detached.title;"
persist="screenX screenY width height sizemode"
onclose="document.getElementById('abp-command-sidebar').doCommand(); return false;">
<script type="application/x-javascript">
// Some people actually switch off browser.frames.enabled and are surprised
// that things stop working...
window.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
.getInterface(Components.interfaces.nsIWebNavigation)
.QueryInterface(Components.interfaces.nsIDocShell)
.allowSubframes = true;
</script>
<keyset id="detached-keyset">
<key keycode="VK_ESCAPE" command="abp-command-sidebar"/>
<key modifiers="accel" key="w" command="abp-command-sidebar"/>
</keyset>
<commandset id="detached-commandset">
<command id="abp-command-sidebar" oncommand="document.getElementById('sidebarFrame').contentWindow.doClose()"/>
</commandset>
<iframe src="sidebar.xul" id="sidebarFrame" flex="1"/>
</window>
+296
View File
@@ -0,0 +1,296 @@
/*
* This Source Code is subject to the terms of the Mozilla Public License
* version 2.0 (the "License"). You can obtain a copy of the License at
* http://mozilla.org/MPL/2.0/.
*/
Cu.import("resource://gre/modules/FileUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
let subscriptionListLoading = false;
function init()
{
if (window.arguments && window.arguments.length && window.arguments[0])
{
let source = window.arguments[0];
setCustomSubscription(source.title, source.url,
source.mainSubscriptionTitle, source.mainSubscriptionURL);
E("all-subscriptions-container").hidden = true;
E("fromWebText").hidden = false;
}
else
loadSubscriptionList();
}
function updateSubscriptionInfo()
{
let selectedSubscription = E("all-subscriptions").selectedItem;
E("subscriptionInfo").setAttribute("invisible", !selectedSubscription);
if (selectedSubscription)
{
let url = selectedSubscription.getAttribute("_url");
let homePage = selectedSubscription.getAttribute("_homepage")
let viewLink = E("view-list");
viewLink.setAttribute("_url", url);
viewLink.setAttribute("tooltiptext", url);
let homePageLink = E("visit-homepage");
homePageLink.hidden = !homePage;
if (homePage)
{
homePageLink.setAttribute("_url", homePage);
homePageLink.setAttribute("tooltiptext", homePage);
}
}
}
function reloadSubscriptionList()
{
subscriptionListLoading = false;
loadSubscriptionList();
}
function loadSubscriptionList()
{
if (subscriptionListLoading)
return;
E("all-subscriptions-container").selectedIndex = 0;
E("all-subscriptions-loading").hidden = false;
let request = new XMLHttpRequest();
let errorHandler = function()
{
E("all-subscriptions-container").selectedIndex = 2;
E("all-subscriptions-loading").hidden = true;
};
let successHandler = function()
{
if (!request.responseXML || request.responseXML.documentElement.localName != "subscriptions")
{
errorHandler();
return;
}
try
{
processSubscriptionList(request.responseXML);
E("all-subscriptions").selectedIndex = 0;
E("all-subscriptions").focus();
}
catch (e)
{
Cu.reportError(e);
errorHandler();
}
};
request.open("GET", Prefs.subscriptions_listurl);
request.addEventListener("error", errorHandler, false);
request.addEventListener("load", successHandler, false);
request.send(null);
subscriptionListLoading = true;
}
function processSubscriptionList(doc)
{
let list = E("all-subscriptions");
while (list.firstChild)
list.removeChild(list.firstChild);
addSubscriptions(list, doc.documentElement, 0, null, null);
E("all-subscriptions-container").selectedIndex = 1;
E("all-subscriptions-loading").hidden = true;
}
function addSubscriptions(list, parent, level, parentTitle, parentURL)
{
for (let i = 0; i < parent.childNodes.length; i++)
{
let node = parent.childNodes[i];
if (node.nodeType != Node.ELEMENT_NODE || node.localName != "subscription")
continue;
if (node.getAttribute("type") != "ads" || node.getAttribute("deprecated") == "true")
continue;
let variants = node.getElementsByTagName("variants");
if (!variants.length || !variants[0].childNodes.length)
continue;
variants = variants[0].childNodes;
let isFirst = true;
let mainTitle = null;
let mainURL = null;
for (let j = 0; j < variants.length; j++)
{
let variant = variants[j];
if (variant.nodeType != Node.ELEMENT_NODE || variant.localName != "variant")
continue;
let item = document.createElement("richlistitem");
item.setAttribute("_title", variant.getAttribute("title"));
item.setAttribute("_url", variant.getAttribute("url"));
if (parentTitle && parentURL && variant.getAttribute("complete") != "true")
{
item.setAttribute("_supplementForTitle", parentTitle);
item.setAttribute("_supplementForURL", parentURL);
}
item.setAttribute("tooltiptext", variant.getAttribute("url"));
item.setAttribute("_homepage", node.getAttribute("homepage"));
let title = document.createElement("description");
if (isFirst)
{
if (Utils.checkLocalePrefixMatch(node.getAttribute("prefixes")))
title.setAttribute("class", "subscriptionTitle localeMatch");
else
title.setAttribute("class", "subscriptionTitle");
title.textContent = node.getAttribute("title") + " (" + node.getAttribute("specialization") + ")";
mainTitle = variant.getAttribute("title");
mainURL = variant.getAttribute("url");
isFirst = false;
}
title.setAttribute("flex", "1");
title.style.marginLeft = (20 * level) + "px";
item.appendChild(title);
let variantTitle = document.createElement("description");
variantTitle.setAttribute("class", "variant");
variantTitle.textContent = variant.getAttribute("title");
variantTitle.setAttribute("crop", "end");
item.appendChild(variantTitle);
list.appendChild(item);
}
let supplements = node.getElementsByTagName("supplements");
if (supplements.length)
addSubscriptions(list, supplements[0], level + 1, mainTitle, mainURL);
}
}
function onSelectionChange()
{
let selectedItem = E("all-subscriptions").selectedItem;
if (!selectedItem)
return;
setCustomSubscription(selectedItem.getAttribute("_title"), selectedItem.getAttribute("_url"),
selectedItem.getAttribute("_supplementForTitle"), selectedItem.getAttribute("_supplementForURL"));
updateSubscriptionInfo();
}
function setCustomSubscription(title, url, mainSubscriptionTitle, mainSubscriptionURL)
{
E("title").value = title;
E("location").value = url;
let messageElement = E("supplementMessage");
let addMainCheckbox = E("addMainSubscription");
if (mainSubscriptionURL && !hasSubscription(mainSubscriptionURL))
{
messageElement.removeAttribute("invisible");
addMainCheckbox.removeAttribute("invisible");
let [, beforeLink, afterLink] = /(.*)\?1\?(.*)/.exec(messageElement.getAttribute("_textTemplate")) || [null, messageElement.getAttribute("_textTemplate"), ""];
while (messageElement.firstChild)
messageElement.removeChild(messageElement.firstChild);
messageElement.appendChild(document.createTextNode(beforeLink));
let link = document.createElement("label");
link.className = "text-link";
link.setAttribute("tooltiptext", mainSubscriptionURL);
link.addEventListener("click", function() Utils.loadInBrowser(mainSubscriptionURL), false);
link.textContent = mainSubscriptionTitle;
messageElement.appendChild(link);
messageElement.appendChild(document.createTextNode(afterLink));
addMainCheckbox.value = mainSubscriptionURL;
addMainCheckbox.setAttribute("_mainSubscriptionTitle", mainSubscriptionTitle)
addMainCheckbox.label = addMainCheckbox.getAttribute("_labelTemplate").replace(/\?1\?/g, mainSubscriptionTitle);
addMainCheckbox.accessKey = addMainCheckbox.accessKey;
}
else
{
messageElement.setAttribute("invisible", "true");
addMainCheckbox.setAttribute("invisible", "true");
}
}
function validateURL(url)
{
if (!url)
return null;
url = url.replace(/^\s+/, "").replace(/\s+$/, "");
// Is this a file path?
try {
let file = new FileUtils.File(url);
return Services.io.newFileURI(file).spec;
} catch (e) {}
// Is this a valid URL?
let uri = Utils.makeURI(url);
if (uri)
return uri.spec;
return null;
}
function addSubscription()
{
let url = E("location").value;
url = validateURL(url);
if (!url)
{
Utils.alert(window, Utils.getString("subscription_invalid_location"));
E("location").focus();
return false;
}
let title = E("title").value.replace(/^\s+/, "").replace(/\s+$/, "");
if (!title)
title = url;
doAddSubscription(url, title);
let addMainCheckbox = E("addMainSubscription")
if (addMainCheckbox.getAttribute("invisible") != "true" && addMainCheckbox.checked)
{
let mainSubscriptionTitle = addMainCheckbox.getAttribute("_mainSubscriptionTitle");
let mainSubscriptionURL = validateURL(addMainCheckbox.value);
if (mainSubscriptionURL)
doAddSubscription(mainSubscriptionURL, mainSubscriptionTitle);
}
return true;
}
/**
* Adds a new subscription to the list.
*/
function doAddSubscription(/**String*/ url, /**String*/ title)
{
let subscription = Subscription.fromURL(url);
if (!subscription)
return;
FilterStorage.addSubscription(subscription);
subscription.disabled = false;
subscription.title = title;
if (subscription instanceof DownloadableSubscription && !subscription.lastDownload)
Synchronizer.execute(subscription);
}
function hasSubscription(url)
{
return FilterStorage.subscriptions.some(function(subscription) subscription instanceof DownloadableSubscription && subscription.url == url);
}
+64
View File
@@ -0,0 +1,64 @@
<?xml version="1.0"?>
<!-- This Source Code is subject to the terms of the Mozilla Public License
- version 2.0 (the "License"). You can obtain a copy of the License at
- http://mozilla.org/MPL/2.0/. -->
#filter substitution
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
<?xml-stylesheet href="chrome://@ADDON_CHROME_NAME@/skin/subscriptionSelection.css" type="text/css"?>
<!DOCTYPE dialog SYSTEM "chrome://@ADDON_CHROME_NAME@/locale/subscriptionSelection.dtd">
<dialog
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
buttons="accept,cancel"
buttonlabelaccept="&addSubscription.label;"
title="&dialog.title;"
id="abpSubscriptionSelection"
windowtype="abp:subscriptionSelection"
onload="init();"
ondialogaccept="return addSubscription();">
<script type="application/x-javascript;version=1.7" src="utils.js"/>
<script type="application/x-javascript;version=1.7" src="subscriptionSelection.js"/>
<deck id="all-subscriptions-container" selectedIndex="0" flex="1">
<vbox pack="center">
<progressmeter id="all-subscriptions-loading" mode="undetermined"/>
</vbox>
<vbox>
<richlistbox id="all-subscriptions" onselect="onSelectionChange()" flex="1"/>
<hbox id="subscriptionInfo" invisible="true">
<label id="view-list" class="text-link" value="&viewList.label;" onclick="Utils.loadInBrowser(this.getAttribute('_url'))"/>
<spacer flex="1"/>
<label id="visit-homepage" class="text-link" value="&visitHomepage.label;" onclick="Utils.loadInBrowser(this.getAttribute('_url'))"/>
</hbox>
</vbox>
<vbox pack="center" align="center">
<description value="&list.download.failed;"/>
<hbox>
<button label="&list.download.retry;" oncommand="reloadSubscriptionList()"/>
<button label="&list.download.website;" oncommand="Utils.loadDocLink('subscriptions')"/>
</hbox>
</vbox>
</deck>
<description id="fromWebText" hidden="true">&fromWeb.description;</description>
<groupbox id="differentSubscription">
<label value="&title.label;" accesskey="&title.accesskey;" control="title"/>
<textbox id="title"/>
<label value="&location.label;" accesskey="&location.accesskey;" control="location"/>
<textbox id="location"/>
</groupbox>
<description id="supplementMessage" invisible="true" _textTemplate="&supplementMessage;">
&supplementMessage;
<label class="text-link" oncommand="">dummy dummy dummy dummy dummy dummy dummy dummy dummy dummy</label>
</description>
<checkbox id="addMainSubscription" invisible="true" checked="true" _labelTemplate="&addMain.label;" label="&addMain.label;" accesskey="&addMain.accesskey;"/>
</dialog>
+28
View File
@@ -0,0 +1,28 @@
<?xml version="1.0"?>
<subscriptions>
<subscription title="EasyList"
specialization="English"
url="https://easylist.to/easylist/easylist.txt"
homepage="https://easylist.to/"
prefixes="en"
author="EasyList"/>
<subscription title="EasyPrivacy"
specialization="English"
url="https://easylist.to/easylist/easyprivacy.txt"
homepage="https://easylist.to/"
prefixes="en"
author="EasyList"/>
<subscription title="Fanboy&#39;s Annoyance List"
specialization="English"
url="https://easylist.to/easylist/fanboy-annoyance.txt"
homepage="https://easylist.to/"
prefixes="en"
author="Fanboy"/>
<subscription title="Fanboy&#39;s Social Blocking List"
specialization="English"
url="https://easylist.to/easylist/fanboy-social.txt"
homepage="https://easylist.to/"
prefixes="en"
author="Fanboy"/>
</subscriptions>
+36
View File
@@ -0,0 +1,36 @@
/*
* This Source Code is subject to the terms of the Mozilla Public License
* version 2.0 (the "License"). You can obtain a copy of the License at
* http://mozilla.org/MPL/2.0/.
*/
#filter substitution
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cr = Components.results;
const Cu = Components.utils;
let baseURL = "resource://@ADDON_CHROME_NAME@/modules/";
Cu.import(baseURL + "AppIntegration.jsm");
Cu.import(baseURL + "ContentPolicy.jsm");
Cu.import(baseURL + "FilterClasses.jsm");
Cu.import(baseURL + "FilterListener.jsm");
Cu.import(baseURL + "FilterStorage.jsm");
Cu.import(baseURL + "FilterNotifier.jsm");
Cu.import(baseURL + "IO.jsm");
Cu.import(baseURL + "Matcher.jsm");
Cu.import(baseURL + "Prefs.jsm");
Cu.import(baseURL + "RequestNotifier.jsm");
Cu.import(baseURL + "SubscriptionClasses.jsm");
Cu.import(baseURL + "Synchronizer.jsm");
/* Cu.import(baseURL + "Sync.jsm"); */
Cu.import(baseURL + "Utils.jsm");
/**
* Shortcut for document.getElementById(id)
*/
function E(id)
{
return document.getElementById(id);
}
+16
View File
@@ -0,0 +1,16 @@
#filter substitution
<!ENTITY dialog.title "About @ADDON_NAME@">
<!ENTITY version.title "Version">
<!ENTITY description "
@ADDON_NAME@ allows you to decide what you want to see on the web.
You don't need to download adverts and banners any more; if you
don't want them - remove them with @ADDON_NAME@!
">
<!ENTITY homepage.label "@ADDON_NAME@ homepage:">
<!ENTITY author.label "Author:">
<!ENTITY contributors.label "Contributors:">
<!ENTITY subscriptionAuthors.label "Filter subscription authors:">
<!ENTITY translators.label "Translators:">
+53
View File
@@ -0,0 +1,53 @@
#filter substitution
<!ENTITY dialog.title "Add @ADDON_NAME@ filter rule">
<!ENTITY accept.label "Add filter">
<!ENTITY advanced.label "Advanced view">
<!ENTITY basic.label "Basic view">
<!ENTITY disabled.warning "@ADDON_NAME@ is currently disabled. You can still add filters but they will not be applied unless you [link]enable @ADDON_NAME@[/link].">
<!ENTITY filter.label "New filter:">
<!ENTITY filter.accesskey "f">
<!ENTITY preferences.label "Show existing filters...">
<!ENTITY preferences.accesskey "S">
<!ENTITY type.filter.label "Blocking filter">
<!ENTITY type.filter.accesskey "B">
<!ENTITY type.whitelist.label "Exception rule">
<!ENTITY type.whitelist.accesskey "x">
<!ENTITY pattern.label "Look for pattern">
<!ENTITY pattern.explanation "The pattern can be any part of the address; asterisks (*) act as wildcards. The filter will only be applied to addresses matching the pattern provided.">
<!ENTITY regexp.warning "The pattern you entered will be interpreted as a regular expression which cannot be efficiently processed by @ADDON_NAME@ and may slow down your browsing experience. If you didn't intend to use a regular expression, add an asterisk (*) to the end of the pattern.">
<!ENTITY shortpattern.warning "The pattern you entered is too short to be optimized and may slow down your browsing experience. It is recommended that you choose a longer string for this filter to allow @ADDON_NAME@ to process the filter more efficiently.">
<!ENTITY match.warning "The pattern you entered no longer matches the address to be blocked/whitelisted and will have no effect on it.">
<!ENTITY custom.pattern.label "Custom:">
<!ENTITY custom.pattern.accesskey "C">
<!ENTITY anchors.label "Accept pattern only:">
<!ENTITY anchor.start.label "at the beginning of the address">
<!-- Note: This access key should usually be the same as anchor.start.flexible.accesskey. It is the same checkbox with different label depending on suggested filter.-->
<!ENTITY anchor.start.accesskey "g">
<!ENTITY anchor.start.flexible.label "at the beginning of the domain name">
<!-- Note: This access key should usually be the same as anchor.start.accesskey. It is the same checkbox with different label depending on suggested filter.-->
<!ENTITY anchor.start.flexible.accesskey "g">
<!ENTITY anchor.end.label "at the end of the address">
<!ENTITY anchor.end.accesskey "n">
<!ENTITY options.label "Options">
<!ENTITY domainRestriction.label "Restrict to domain:">
<!ENTITY domainRestriction.accesskey "d">
<!ENTITY domainRestriction.help "Use this option to specify one or more domains separated by a bar line (|). The filter will only be applied on the domain(s) selected. A tilde (~) before a domain name indicates that the filter will not be applied on that domain.">
<!ENTITY firstParty.label "First-party only">
<!ENTITY firstParty.accesskey "r">
<!ENTITY thirdParty.label "Third-party only">
<!ENTITY thirdParty.accesskey "T">
<!ENTITY matchCase.label "Match case">
<!ENTITY matchCase.accesskey "M">
<!ENTITY types.label "Apply to types:">
<!ENTITY selectAllTypes.label "Select all">
<!ENTITY unselectAllTypes.label "Select none">
<!ENTITY collapse.label "Collapse blocked:">
<!ENTITY collapse.accesskey "l">
<!ENTITY collapse.default.yes.label "Use default (yes)">
<!ENTITY collapse.default.no.label "Use default (no)">
<!ENTITY collapse.yes.label "Yes">
<!ENTITY collapse.no.label "No">
+116
View File
@@ -0,0 +1,116 @@
#filter substitution
<!ENTITY dialog.title "@ADDON_NAME@ Filter Preferences">
<!ENTITY subscriptions.tab.label "Filter subscriptions">
<!ENTITY filters.tab.label "Custom filters">
<!ENTITY addSubscription.label "Add filter subscription">
<!ENTITY addSubscription.accesskey "f">
<!ENTITY addSubscriptionAdd.label "Add">
<!ENTITY addSubscriptionCancel.label "Cancel">
<!ENTITY addSubscriptionOther.label "Add a different subscription">
<!ENTITY noSubscriptions.text "
You haven't added any filter subscriptions yet. @ADDON_NAME@ won't block
anything without filters, please use &quot;Add filter subscription&quot; to
add some.
">
<!ENTITY subscription.homepage.label "Homepage">
<!ENTITY subscription.external.label "Updated by another extension">
<!ENTITY subscription.source.label "Filter list">
<!ENTITY subscription.enabled.label "Enabled">
<!ENTITY subscription.lastDownload.label "Last download:">
<!ENTITY subscription.lastDownload.inProgress "Downloading…">
<!ENTITY subscription.lastDownload.unknown "N/A">
<!ENTITY subscription.lastDownload.invalidURL "Failed, not a valid address">
<!ENTITY subscription.lastDownload.connectionError "Failed, download failure">
<!ENTITY subscription.lastDownload.invalidData "Failed, not a valid filters list">
<!ENTITY subscription.lastDownload.checksumMismatch "Failed, checksum mismatch">
<!ENTITY subscription.lastDownload.success "Success">
<!ENTITY subscription.minVersion.warning "This filter subscription requires a newer @ADDON_NAME@ version, you should update to the latest @ADDON_NAME@ version.">
<!ENTITY subscription.disabledFilters.warning "Some filters in this subscription are disabled.">
<!ENTITY subscription.disabledFilters.enable "Enable disabled filters">
<!ENTITY subscription.actions.label "Actions">
<!ENTITY subscription.update.label "Update filters">
<!ENTITY subscription.editTitle.label "Edit title">
<!ENTITY subscription.delete.label "Delete">
<!ENTITY subscription.showHideFilters.label "Show/hide filters">
<!ENTITY subscription.moveUp.label "Move up">
<!ENTITY subscription.moveDown.label "Move down">
<!ENTITY acceptableAds2.label "Allow some non-intrusive advertising">
<!ENTITY acceptableAds2.accesskey "i">
<!ENTITY viewList.label "View list">
<!ENTITY readMore.label "Read more">
<!ENTITY addGroup.label "Add filter group">
<!ENTITY addGroup.accesskey "g">
<!ENTITY noFilters.text "
You don't have any custom filters yet.
">
<!ENTITY addFilter.label "Add filter">
<!ENTITY addFilter.accesskey "d">
<!ENTITY filter.actions.label "Filter actions">
<!ENTITY filter.edit.label "Edit">
<!ENTITY filter.cut.label "Cut">
<!ENTITY filter.copy.label "Copy">
<!ENTITY filter.paste.label "Paste">
<!ENTITY filter.delete.label "Delete">
<!ENTITY filter.selectAll.label "Select All">
<!ENTITY filter.resetHitCounts.label "Reset hit statistics">
<!ENTITY filter.moveUp.label "Move up">
<!ENTITY filter.moveDown.label "Move down">
<!ENTITY viewMenu.label "View">
<!ENTITY filter.column "Filter rule">
<!ENTITY filter.accesskey "F">
<!ENTITY slow.column "Slow filters">
<!ENTITY slow.accesskey "w">
<!ENTITY enabled.column "Enabled">
<!ENTITY enabled.accesskey "n">
<!ENTITY hitcount.column "Hits">
<!ENTITY hitcount.accesskey "H">
<!ENTITY lasthit.column "Last hit">
<!ENTITY lasthit.accesskey "L">
<!ENTITY sort.label "Sort by">
<!ENTITY sort.accesskey "S">
<!ENTITY sort.none.label "Unsorted">
<!ENTITY sort.none.accesskey "U">
<!ENTITY sort.ascending.label "A > Z sort order">
<!ENTITY sort.ascending.accesskey "A">
<!ENTITY sort.descending.label "Z > A sort order">
<!ENTITY sort.descending.accesskey "Z">
<!ENTITY noGroupSelected.text "You need to select a filter group before its filters can be displayed.">
<!ENTITY noFiltersInGroup.text "The selected group is empty.">
<!ENTITY filters.remove.warning "Do you really want to remove all selected filters?">
<!ENTITY backupButton.label "Backup and Restore">
<!ENTITY backupButton.accesskey "B">
<!ENTITY backup.label "Create new backup">
<!-- Note: the placeholder ?1? will be replaced by date/time of the automatic backup -->
<!ENTITY restore.default.label "Restore backup from ?1?">
<!ENTITY restore.own.label "Restore own backup">
<!ENTITY backup.complete.title "All filters and subscriptions">
<!ENTITY backup.custom.title "Custom filters only">
<!ENTITY backup.error "There was an error writing filters to the file. Make sure that the file isn't write protected or in use by another application.">
<!ENTITY restore.error "The file's data could not be processed, maybe this isn't an @ADDON_NAME@ backup file?">
<!ENTITY restore.complete.warning "All your filter preferences will be replaced by the contents of the selected file. Do you want to proceeed?">
<!ENTITY restore.custom.warning "All your custom filters will be replaced by the contents of the selected file. Do you want to proceeed?">
<!ENTITY restore.minVersion.warning "Warning: the file has been created with a newer @ADDON_NAME@ version. You should update to the latest @ADDON_NAME@ version before restoring from this file.">
<!ENTITY find.label "Find">
<!ENTITY find.accesskey "n">
<!ENTITY close.label "Close">
+27
View File
@@ -0,0 +1,27 @@
#filter substitution
<!ENTITY dialog.title "@ADDON_NAME@ installation complete">
<!ENTITY confirmation "
Thank you for installing @ADDON_NAME@. Ads will be blocked from now on.
Enjoy!">
<!ENTITY advancedSection "Advanced options">
<!ENTITY listSelection1 "
The following filter list has been configured to block advertising:
">
<!ENTITY noList "No filter list">
<!ENTITY visitHomepage.label "Visit list home page">
<!ENTITY acceptableAds2 "
@ADDON_NAME@ has also been configured to allow some non-intrusive advertising.
">
<!ENTITY viewList.label "View list">
<!ENTITY readMore.label "Read more about this">
<!ENTITY listSelection2 "
You can change this selection at any time in the [link]Filter Preferences[/link].
">
+60
View File
@@ -0,0 +1,60 @@
#filter substitution
default_dialog_title=@ADDON_NAME@
action0_tooltip=Click to bring up context menu, middle-click to enable/disable.
action1_tooltip=Click to open/close blockable items, middle-click to enable/disable.
action2_tooltip=Click to open preferences, middle-click to enable/disable.
action3_tooltip=Click to enable/disable @ADDON_NAME@.
disabled_tooltip=@ADDON_NAME@ is disabled.
# Note: the placeholder ?1? will be replaced by the number of active filter subscriptions, the placeholder ?2? by the number of custom filters
active_tooltip=@ADDON_NAME@ is enabled, ?1? filter subscription(s) and ?2? custom filter(s) in use.
whitelisted_tooltip=@ADDON_NAME@ is disabled on current page.
# Note: the placeholder ?1? will be replaced by the number of blocked items, the placeholder ?2? by the total number of items on current page
blocked_count_tooltip=?1? out of ?2?
# Note: the placeholder ?1? will be replaced by the number of whitelisted items, the placeholder ?2? by the number of hidden items on current page
blocked_count_addendum=(also whitelisted: ?1?, hidden: ?2?)
no_blocking_suggestions=No blockable items on the current page
whitelisted_page=@ADDON_NAME@ has been disabled for the current page
newGroup_title=New filter group
whitelistGroup_title=Exception Rules
blockingGroup_title=Ad Blocking Rules
elemhideGroup_title=Element Hiding Rules
remove_subscription_warning=Do you really want to remove this subscription?
remove_group_warning=Do you really want to remove this group?
clearStats_warning=This will reset all filter hit statistics and disable counting filter hits. Do you want to proceed?
filter_regexp_tooltip=This filter is either a regular expression or too short to be optimized. Too many of these filters might slow down your browsing.
filter_elemhide_duplicate_id=Only one ID of the element to be hidden can be specified
filter_elemhide_nocriteria=No criteria specified to recognize the element to be hidden
subscription_invalid_location=Filter list location is neither a valid URL nor a valid file name.
type_label_other=other
type_label_script=script
type_label_image=image
type_label_stylesheet=stylesheet
type_label_object=object
type_label_subdocument=frame
type_label_document=document
type_label_elemhide=hidden
type_label_popup=pop-up window
type_label_websocket=websocket
type_label_webrtc=webtrc
type_label_csp=csp
type_label_xmlhttprequest=XML request
type_label_object_subrequest=object subrequest
type_label_media=audio/video
type_label_font=font
mobile_menu_enable=ABP: Enable
# Note: the placeholder ?1? will be replaced by the site name. Ideally it should be at the end of the string (space is limited and site names can be long).
mobile_menu_enable_site=ABP: Enable on ?1?
# Note: the placeholder ?1? will be replaced by the site name. Ideally it should be at the end of the string (space is limited and site names can be long).
mobile_menu_disable_site=ABP: Disable on ?1?
+17
View File
@@ -0,0 +1,17 @@
# 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/.
#filter substitution
[.] chrome.jar:
% locale @ADDON_CHROME_NAME@ en-US chrome/locale/
* locale/about.dtd
* locale/composer.dtd
* locale/filters.dtd
* locale/firstRun.dtd
* locale/global.properties
* locale/overlay.dtd
* locale/sendReport.dtd
* locale/sidebar.dtd
* locale/subscriptionSelection.dtd
+6
View File
@@ -0,0 +1,6 @@
# vim: set filetype=python:
# 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/.
JAR_MANIFESTS += ['jar.mn']
+54
View File
@@ -0,0 +1,54 @@
#filter substitution
<!ENTITY status.tooltip "Status:">
<!ENTITY blocked.tooltip "Blocked items on this page:">
<!ENTITY filters.tooltip "Most active filters:">
<!ENTITY menuitem.label "@ADDON_NAME@ Preferences">
<!ENTITY menuitem.accesskey "b">
<!ENTITY toolbarbutton.label "@ADDON_NAME@">
<!ENTITY toolsmenu.label "@ADDON_NAME@">
<!ENTITY context.image.label "@ADDON_NAME@: Block image">
<!ENTITY context.object.label "@ADDON_NAME@: Block object">
<!ENTITY context.frame.label "@ADDON_NAME@: Block frame">
<!ENTITY context.media.label "@ADDON_NAME@: Block audio/video">
<!ENTITY context.removeWhitelist.label "@ADDON_NAME@: Re-enable on this page">
<!ENTITY sidebar.title "Blockable items on current page">
<!ENTITY sendReport.label "Report issue on this page">
<!ENTITY sendReport.accesskey "R">
<!ENTITY filters.label "Filter preferences">
<!ENTITY filters.accesskey "F">
<!ENTITY opensidebar.label "Open blockable items">
<!-- Note: This access key should usually be the same as closesidebar.accesskey. It is the same menu item with different label depending on whether the sidebar is currently open.-->
<!ENTITY opensidebar.accesskey "b">
<!ENTITY closesidebar.label "Close blockable items">
<!-- Note: This access key should usually be the same as opensidebar.accesskey. It is the same menu item with different label depending on whether the sidebar is currently open.-->
<!ENTITY closesidebar.accesskey "b">
<!-- Note: the placeholder ?1? will be replaced by the domain name of the current page -->
<!ENTITY whitelist.site.label "Disable on ?1?">
<!ENTITY whitelist.page.label "Disable on this page only">
<!ENTITY disable.label "Disable everywhere">
<!ENTITY options.label "Options">
<!ENTITY options.accesskey "O">
<!ENTITY contribute.label "Contribute to @ADDON_NAME@">
<!ENTITY showintoolbar.label "Show in toolbar">
<!ENTITY showintoolbar.accesskey "b">
<!ENTITY showinstatusbar.label "Show in status bar">
<!ENTITY showinstatusbar.accesskey "s">
<!ENTITY showinaddonbar.label "Show in add-on bar">
<!ENTITY showinaddonbar.accesskey "b">
<!ENTITY objecttabs.label "Show tabs on Flash and Java">
<!ENTITY objecttabs.accesskey "t">
<!ENTITY hideplaceholders.label "Hide placeholders of blocked elements">
<!ENTITY hideplaceholders.accesskey "l">
<!ENTITY counthits.label "Count filter hits">
<!ENTITY counthits.accesskey "h">
<!ENTITY sync.label "Sync @ADDON_NAME@ settings">
<!ENTITY sync.accesskey "c">
<!ENTITY objecttab.title "Block">
<!ENTITY objecttab.tooltip "Click here to block this object with @ADDON_NAME@">
+201
View File
@@ -0,0 +1,201 @@
#filter substitution
<!ENTITY wizard.title "Issue reporter">
<!ENTITY privacyPolicy.label "Privacy policy">
<!ENTITY dataCollector.heading "Welcome to the issue reporter">
<!ENTITY dataCollector.description "Please wait a few moments while @ADDON_NAME@ gathers the required data.">
<!-- Please keep typeSelector.heading short - it is shown in the progress bar, not much space there -->
<!ENTITY typeSelector.heading "Select issue type">
<!ENTITY typeSelector.description "
This window will guide you through the steps required for the submission of an Adblock
Latitude issue report. First, please select the type of issue that you are experiencing
on this page:
">
<!ENTITY typeSelector.falsePositive.label "@ADDON_NAME@ is blocking too much">
<!ENTITY typeSelector.falsePositive.accesskey "m">
<!ENTITY typeSelector.falsePositive.description "
Select this option if the page lacks important content, displays incorrectly or
fails to function properly. You can determine whether @ADDON_NAME@ is the cause
of the problem by disabling it temporarily.
">
<!ENTITY typeSelector.falseNegative.label "@ADDON_NAME@ doesn't block an advertisement">
<!ENTITY typeSelector.falseNegative.accesskey "v">
<!ENTITY typeSelector.falseNegative.description "
Select this option if an advertisement is displayed despite
@ADDON_NAME@ being enabled.
">
<!ENTITY typeSelector.other.label "Other issue">
<!ENTITY typeSelector.other.accesskey "t">
<!ENTITY typeSelector.other.description "
Select this option if you suspect an issue with @ADDON_NAME@ itself rather
than its filters.
">
<!ENTITY showRecentReports.label "Show recently submitted reports">
<!ENTITY recentReports.label "Your recently submitted reports">
<!ENTITY recentReports.clear.label "Remove all reports">
<!ENTITY recentReports.clear.accesskey "R">
<!ENTITY update.inProgress.description "
@ADDON_NAME@ needs to update your filter subscriptions to make sure that the
issue hasn't been resolved already. Please wait...
">
<!ENTITY update.fixed.description "
The updates to your filter subscriptions likely resolved the issue that you
were reporting. Please reload the page and retry, hit Report again if the
problem remains.
">
<!ENTITY outdatedSubscriptions.description "
The following filter subscriptions haven't been updated for at least two
weeks. Please update these subscriptions before submitting a report, the
issue might be resolved already.
">
<!ENTITY update.start.label "Start update now">
<!ENTITY issues.description "
@ADDON_NAME@ has detected issues with your configuration that might be responsible
for this issue or will make investigating the report difficult.
">
<!ENTITY issues.whitelist.description "
@ADDON_NAME@ is currently disabled on the page you are reporting. Please re-enable
it and reload the page before submitting the report to assist the investigation of
this issue.
">
<!ENTITY issues.whitelist.remove.label "Re-enable @ADDON_NAME@ on this page">
<!ENTITY issues.disabled.description "
@ADDON_NAME@ is disabled, it will not block anything in its current state.
">
<!ENTITY issues.disabled.enable.label "Enable @ADDON_NAME@">
<!ENTITY issues.nofilters.description "
@ADDON_NAME@ isn't blocking anything on the current page. The issue you are
observing is most likely unrelated to @ADDON_NAME@.
">
<!ENTITY issues.nosubscriptions.description "
You do not appear to be subscribed to any of the pre-made filter lists that
automatically remove unwanted content from websites.
">
<!ENTITY issues.nosubscriptions.add.label "Add filter subscription">
<!ENTITY issues.subscriptionCount.description "
It seems that you are subscribed to too many filter subscriptions. This
setup is not recommended because it will make the likeliness
of issues much higher. We also cannot accept your issue report because it
is unclear which filter subscription author needs to take action. Please
remove all but the really necessary filter subscriptions and test whether
the issue still occurs then.
">
<!ENTITY issues.openPreferences.label "Open filter preferences">
<!ENTITY issues.ownfilters.description "
Some of the filters applied on this page are user-defined. Please disable
the filters that might have caused the issue:
">
<!ENTITY issues.ownfilters.disable.label "Disable filter">
<!ENTITY issues.disabledgroups.description "
The following filter subscriptions / filter groups are disabled, yet they might have
an effect on this page:
">
<!ENTITY issues.disabledgroups.enable.label "Enable filter subscription / filter group">
<!ENTITY issues.disabledfilters.description "
The following filters are disabled, yet they might have an effect on this page:
">
<!ENTITY issues.disabledfilters.enable.label "Enable filter">
<!ENTITY issues.override.label "The configuration is correct, continue with the report">
<!ENTITY issues.override.accesskey "c">
<!ENTITY issues.change.description "
Your configuration has been changed. Please reload the page to test the changes
and submit a report if the issue hasn't been resolved by the alterations.
">
<!ENTITY typeWarning.description "
You have indicated that you want to report a general issue with @ADDON_NAME@ rather
than a problem with the filters. Please note that such issues are best reported
in the [link]@ADDON_NAME@ forum[/link]. You should only use the issue reporter to
supplement an existing discussion, as nobody will notice your report
unless you provide them with the link to it. The automatically generated link
will be provided after submitting the report.
">
<!ENTITY typeWarning.override.label "I understand and want to submit the report anyway">
<!ENTITY typeWarning.override.accesskey "s">
<!ENTITY reloadButton.label "Reload page">
<!ENTITY reloadButton.accesskey "R">
<!-- Please keep screenshot.heading short - it is shown in the progress bar, not much space there -->
<!ENTITY screenshot.heading "Attach screenshot">
<!ENTITY screenshot.description "
The same page can look different for different people. It may help us to
understand the problem if you attach a screenshot to your report. You can remove
sections containing sensitive information as well as mark areas where the
problem is noticeable. To do that click the corresponding button and select
a section of the image with your mouse.
">
<!ENTITY screenshot.attach.label "Attach a page image to the report">
<!ENTITY screenshot.attach.accesskey "t">
<!ENTITY screenshot.mark.label "Mark the problem">
<!ENTITY screenshot.mark.accesskey "M">
<!ENTITY screenshot.remove.label "Remove sensitive data">
<!ENTITY screenshot.remove.accesskey "R">
<!ENTITY screenshot.undo.label "Undo">
<!ENTITY screenshot.undo.accesskey "U">
<!-- Please keep commentPage.heading short - it is shown in the progress bar, not much space there -->
<!ENTITY commentPage.heading "Enter comment">
<!ENTITY commentPage.description "
The text field below allows you to enter a comment to help us understand the issue.
This step is optional but recommended if the problem isn't obvious.
You can also review the report data before it is sent.
">
<!ENTITY comment.label "Comment (optional):">
<!ENTITY comment.accesskey "C">
<!ENTITY comment.lengthWarning "The length of your comment exceeds 1000 characters. Only the first 1000 characters will be sent.">
<!ENTITY email.label "Email for further inquiries (optional):">
<!ENTITY email.accesskey "m">
<!ENTITY attachExtensions.label "Attach a list of active extensions to the report in case add-on conflict is the cause of the problem">
<!ENTITY attachExtensions.accesskey "x">
<!ENTITY sendButton.label "Send report">
<!ENTITY sendButton.accesskey "n">
<!ENTITY showData.label "Show report data">
<!ENTITY data.label "Report data:">
<!ENTITY data.accesskey "p">
<!-- Please keep sendPage.heading short - it is shown in the progress bar, not much space there -->
<!ENTITY sendPage.heading "Send report">
<!ENTITY sendPage.waitMessage "Please wait while @ADDON_NAME@ is submitting your report.">
<!ENTITY sendPage.confirmation "Your report has been saved. You can access it at the following address:">
<!ENTITY sendPage.knownIssue "The issue you reported is probably already known. More information:">
<!-- Note: the placeholder ?1? will be replaced by the error code -->
<!ENTITY sendPage.errorMessage "
An attempt to send the report failed with error code &quot;?1?&quot;. Please ensure you are
connected to the Internet and retry. If the problem persists please request
assistance in the [link]@ADDON_NAME@ forum[/link].
">
<!ENTITY sendPage.retry.label "Send again">
<!ENTITY copyLink.label "Copy report link">
<!ENTITY copyLink.accesskey "C">
+44
View File
@@ -0,0 +1,44 @@
#filter substitution
<!ENTITY detached.title "@ADDON_NAME@: Blockable items (detached)">
<!ENTITY detach.label "Detach">
<!ENTITY reattach.label "Reattach">
<!ENTITY search.label "Search:">
<!ENTITY search.accesskey "S">
<!ENTITY type.label "Type">
<!ENTITY address.label "Address">
<!ENTITY filter.label "Filter">
<!ENTITY state.label "State">
<!ENTITY size.label "Size">
<!ENTITY filterSource.label "Filter source">
<!ENTITY docDomain.label "Document source">
<!ENTITY docDomain.thirdParty "(third party)">
<!ENTITY docDomain.firstParty "(first party)">
<!ENTITY noitems.label "No blockable items">
<!ENTITY whitelisted.label "Whitelisted page">
<!ENTITY tooltip.address.label "Address:">
<!ENTITY tooltip.type.label "Type:">
<!ENTITY tooltip.type.blocked "(blocked)">
<!ENTITY tooltip.type.whitelisted "(whitelisted)">
<!ENTITY tooltip.size.label "Size:">
<!ENTITY tooltip.docDomain.label "Document source:">
<!ENTITY tooltip.filter.label "Filter in effect:">
<!ENTITY tooltip.filter.disabled "(disabled)">
<!ENTITY tooltip.filterSource.label "Filter source:">
<!ENTITY context.block.label "Block this item">
<!ENTITY context.editfilter.label "Edit filter in effect">
<!ENTITY context.whitelist.label "Add exception rule for item">
<!-- Note: the placeholder ?1? will be replaced by the filter text -->
<!ENTITY context.disablefilter.label "Disable filter ?1?">
<!-- Note: the placeholder ?1? will be replaced by the filter text -->
<!ENTITY context.enablefilter.label "Re-enable filter ?1?">
<!-- Note: the placeholder ?1? will be replaced by the domain name of the current page -->
<!ENTITY context.disablefilteronsite.label "Disable this filter on ?1?">
<!ENTITY context.open.label "Open in New Tab">
<!ENTITY context.flash.label "Flash item's borders">
<!ENTITY context.copy.label "Copy item's address">
<!ENTITY context.copyFilter.label "Copy filter">
<!ENTITY context.selectAll.label "Select all">
+27
View File
@@ -0,0 +1,27 @@
#filter substitution
<!ENTITY dialog.title "Add @ADDON_NAME@ filter subscription">
<!ENTITY subscriptionSelector.label "Please choose a filter subscription from the list:">
<!ENTITY viewList.label "View filters">
<!ENTITY visitHomepage.label "Visit home page">
<!ENTITY addSubscription.label "Add subscription">
<!ENTITY list.download.failed "@ADDON_NAME@ failed to retrieve the list of subscriptions.">
<!ENTITY list.download.retry "Try again">
<!ENTITY list.download.website "View website">
<!ENTITY fromWeb.description "Please confirm that you want to add this filter subscription. You can change the subscription title or location before adding it.">
<!ENTITY title.label "Subscription title:">
<!ENTITY title.accesskey "t">
<!ENTITY location.label "Filter list location:">
<!ENTITY location.accesskey "l">
<!-- Note: the placeholder (?1?) will be replaced by the name of the filter subscription required -->
<!ENTITY supplementMessage "This filter subscription is meant to be used with the filter subscription &quot;?1?&quot; which you are not using yet.">
<!-- Note: the placeholder (?1?) will be replaced by the name of the filter subscription required -->
<!ENTITY addMain.label "Add filter subscription &quot;?1?&quot; as well">
<!ENTITY addMain.accesskey "s">
File diff suppressed because it is too large Load Diff
+167
View File
@@ -0,0 +1,167 @@
/*
* This Source Code is subject to the terms of the Mozilla Public License
* version 2.0 (the "License"). You can obtain a copy of the License at
* http://mozilla.org/MPL/2.0/.
*/
#filter substitution
/**
* @fileOverview Bootstrap module, will initialize Adblock Plus when loaded
*/
var EXPORTED_SYMBOLS = ["Bootstrap"];
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cr = Components.results;
const Cu = Components.utils;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
let baseURL = "resource://@ADDON_CHROME_NAME@/modules/";
Cu.import(baseURL + "Utils.jsm");
Cu.import(baseURL + "TimeLine.jsm");
let publicURL = Services.io.newURI(baseURL + "Public.jsm", null, null);
if (publicURL instanceof Ci.nsIMutable)
publicURL.mutable = false;
const cidPublic = Components.ID("5e447bce-1dd2-11b2-b151-ec21c2b6a135");
const contractIDPublic = "@adblockplus.org/abp/public;1";
let factoryPublic =
{
createInstance: function(outer, iid)
{
if (outer)
throw Cr.NS_ERROR_NO_AGGREGATION;
return publicURL.QueryInterface(iid);
}
};
let defaultModules = [
baseURL + "Prefs.jsm",
baseURL + "FilterListener.jsm",
baseURL + "ContentPolicy.jsm",
baseURL + "Synchronizer.jsm",
// baseURL + "Sync.jsm"
];
let loadedModules = {__proto__: null};
let initialized = false;
/**
* Allows starting up and shutting down Adblock Plus functions.
* @class
*/
var Bootstrap =
{
/**
* Initializes add-on, loads and initializes all modules.
*/
startup: function()
{
if (initialized)
return;
initialized = true;
TimeLine.enter("Entered Bootstrap.startup()");
// Register component to allow retrieving public URL
let registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
registrar.registerFactory(cidPublic, "Adblock Plus public module URL", contractIDPublic, factoryPublic);
TimeLine.log("done registering URL components");
// Load and initialize modules
TimeLine.log("started initializing modules");
for each (let url in defaultModules)
Bootstrap.loadModule(url);
TimeLine.leave("Bootstrap.startup() done");
},
/**
* Shuts down add-on.
*/
shutdown: function()
{
if (!initialized)
return;
TimeLine.enter("Entered Bootstrap.shutdown()");
// Shut down modules
for (let url in loadedModules)
Bootstrap.shutdownModule(url);
TimeLine.leave("Bootstrap.shutdown() done");
},
/**
* Loads and initializes a module.
*/
loadModule: function(/**String*/ url)
{
if (url in loadedModules)
return;
let module = {};
try
{
Cu.import(url, module);
}
catch (e)
{
Cu.reportError("Adblock Plus: Failed to load module " + url + ": " + e);
return;
}
for each (let obj in module)
{
if ("startup" in obj)
{
try
{
obj.startup();
loadedModules[url] = obj;
}
catch (e)
{
Cu.reportError("Adblock Plus: Calling method startup() for module " + url + " failed: " + e);
}
return;
}
}
Cu.reportError("Adblock Plus: No exported object with startup() method found for module " + url);
},
/**
* Shuts down a module.
*/
shutdownModule: function(/**String*/ url)
{
if (!(url in loadedModules))
return;
let obj = loadedModules[url];
if ("shutdown" in obj)
{
try
{
obj.shutdown();
}
catch (e)
{
Cu.reportError("Adblock Plus: Calling method shutdown() for module " + url + " failed: " + e);
}
return;
}
}
};
+634
View File
@@ -0,0 +1,634 @@
/*
* This Source Code is subject to the terms of the Mozilla Public License
* version 2.0 (the "License"). You can obtain a copy of the License at
* http://mozilla.org/MPL/2.0/.
*/
#filter substitution
/**
* @fileOverview Content policy implementation, responsible for blocking things.
*/
var EXPORTED_SYMBOLS = ["Policy"];
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cr = Components.results;
const Cu = Components.utils;
let baseURL = "resource://@ADDON_CHROME_NAME@/modules/";
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import(baseURL + "TimeLine.jsm");
Cu.import(baseURL + "Utils.jsm");
Cu.import(baseURL + "Prefs.jsm");
Cu.import(baseURL + "FilterStorage.jsm");
Cu.import(baseURL + "FilterClasses.jsm");
Cu.import(baseURL + "Matcher.jsm");
Cu.import(baseURL + "ObjectTabs.jsm");
Cu.import(baseURL + "RequestNotifier.jsm");
/**
* List of explicitly supported content types
* @type Array of String
*/
const contentTypes = ["OTHER", "SCRIPT", "IMAGE", "STYLESHEET", "OBJECT", "SUBDOCUMENT", "DOCUMENT", "XMLHTTPREQUEST", "OBJECT_SUBREQUEST", "FONT", "MEDIA", "WEBSOCKET", "WEBRTC", "CSP"];
/**
* List of content types that aren't associated with a visual document area
* @type Array of String
*/
const nonVisualTypes = ["SCRIPT", "STYLESHEET", "XMLHTTPREQUEST", "OBJECT_SUBREQUEST", "FONT", "WEBSOCKET", "WEBRTC", "CSP"];
/**
* Public policy checking functions and auxiliary objects
* @class
*/
var Policy =
{
/**
* Map of content type identifiers by their name.
* @type Object
*/
type: {},
/**
* Map of content type names by their identifiers (reverse of type map).
* @type Object
*/
typeDescr: {},
/**
* Map of localized content type names by their identifiers.
* @type Object
*/
localizedDescr: {},
/**
* Lists the non-visual content types.
* @type Object
*/
nonVisual: {},
/**
* Map containing all schemes that should be ignored by content policy.
* @type Object
*/
whitelistSchemes: {},
/**
* Called on module startup.
*/
startup: function()
{
TimeLine.enter("Entered ContentPolicy.startup()");
// type constant by type description and type description by type constant
var iface = Ci.nsIContentPolicy;
for each (let typeName in contentTypes)
{
if ("TYPE_" + typeName in iface)
{
let id = iface["TYPE_" + typeName];
Policy.type[typeName] = id;
Policy.typeDescr[id] = typeName;
Policy.localizedDescr[id] = Utils.getString("type_label_" + typeName.toLowerCase());
}
}
Policy.type.ELEMHIDE = 0xFFFD;
Policy.typeDescr[0xFFFD] = "ELEMHIDE";
Policy.localizedDescr[0xFFFD] = Utils.getString("type_label_elemhide");
Policy.type.POPUP = 0xFFFE;
Policy.typeDescr[0xFFFE] = "POPUP";
Policy.localizedDescr[0xFFFE] = Utils.getString("type_label_popup");
for each (let type in nonVisualTypes)
Policy.nonVisual[Policy.type[type]] = true;
// whitelisted URL schemes
for each (var scheme in Prefs.whitelistschemes.toLowerCase().split(" "))
Policy.whitelistSchemes[scheme] = true;
TimeLine.log("done initializing types");
// Generate class identifier used to collapse node and register corresponding
// stylesheet.
TimeLine.log("registering global stylesheet");
let offset = "a".charCodeAt(0);
Utils.collapsedClass = "";
for (let i = 0; i < 20; i++)
Utils.collapsedClass += String.fromCharCode(offset + Math.random() * 26);
let collapseStyle = Utils.makeURI("data:text/css," +
encodeURIComponent("." + Utils.collapsedClass +
"{-moz-binding: url(chrome://global/content/bindings/general.xml#foobarbazdummy) !important;}"));
Utils.styleService.loadAndRegisterSheet(collapseStyle, Ci.nsIStyleSheetService.USER_SHEET);
TimeLine.log("done registering stylesheet");
// Register our content policy
TimeLine.log("registering component");
let registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
try
{
registrar.registerFactory(PolicyPrivate.classID, PolicyPrivate.classDescription, PolicyPrivate.contractID, PolicyPrivate);
}
catch (e)
{
// Don't stop on errors - the factory might already be registered
Cu.reportError(e);
}
let catMan = Utils.categoryManager;
for each (let category in PolicyPrivate.xpcom_categories)
catMan.addCategoryEntry(category, PolicyPrivate.classDescription, PolicyPrivate.contractID, false, true);
Services.obs.addObserver(PolicyPrivate, "http-on-modify-request", true);
Services.obs.addObserver(PolicyPrivate, "content-document-global-created", true);
TimeLine.leave("ContentPolicy.startup() done");
},
shutdown: function()
{
PolicyPrivate.previousRequest = null;
},
/**
* Checks whether a node should be blocked, hides it if necessary
* @param wnd {nsIDOMWindow}
* @param node {nsIDOMElement}
* @param contentType {String}
* @param location {nsIURI}
* @param collapse {Boolean} true to force hiding of the node
* @return {Boolean} false if the node should be blocked
*/
processNode: function(wnd, node, contentType, location, collapse)
{
let topWnd = wnd.top;
if (!topWnd || !topWnd.location || !topWnd.location.href)
return true;
let originWindow = Utils.getOriginWindow(wnd);
let wndLocation = originWindow.location.href;
let docDomain = getHostname(wndLocation);
let match = null;
if (!match && Prefs.enabled)
{
let testWnd = wnd;
let parentWndLocation = getWindowLocation(testWnd);
while (true)
{
let testWndLocation = parentWndLocation;
parentWndLocation = (testWnd == testWnd.parent ? testWndLocation : getWindowLocation(testWnd.parent));
match = Policy.isWhitelisted(testWndLocation, parentWndLocation);
if (!(match instanceof WhitelistFilter))
if (match instanceof WhitelistFilter)
{
FilterStorage.increaseHitCount(match);
RequestNotifier.addNodeData(testWnd.document, topWnd, Policy.type.DOCUMENT, getHostname(parentWndLocation), false, testWndLocation, match);
return true;
}
if (testWnd.parent == testWnd)
break;
else
testWnd = testWnd.parent;
}
}
// Data loaded by plugins should be attached to the document
if (contentType == Policy.type.OBJECT_SUBREQUEST && node instanceof Ci.nsIDOMElement)
node = node.ownerDocument;
// Fix type for objects misrepresented as frames or images
if (contentType != Policy.type.OBJECT && (node instanceof Ci.nsIDOMHTMLObjectElement || node instanceof Ci.nsIDOMHTMLEmbedElement))
contentType = Policy.type.OBJECT;
let locationText = location.spec;
if (!match && contentType == Policy.type.ELEMHIDE)
{
let testWnd = wnd;
let parentWndLocation = getWindowLocation(testWnd);
while (true)
{
let testWndLocation = parentWndLocation;
parentWndLocation = (testWnd == testWnd.parent ? testWndLocation : getWindowLocation(testWnd.parent));
let parentDocDomain = getHostname(parentWndLocation);
match = defaultMatcher.matchesAny(testWndLocation, "ELEMHIDE", parentDocDomain, false);
if (match instanceof WhitelistFilter)
{
FilterStorage.increaseHitCount(match);
RequestNotifier.addNodeData(testWnd.document, topWnd, contentType, parentDocDomain, false, testWndLocation, match);
return true;
}
if (testWnd.parent == testWnd)
break;
else
testWnd = testWnd.parent;
}
match = location;
locationText = match.text.replace(/^.*?#/, '#');
location = locationText;
if (!match.isActiveOnDomain(docDomain))
return true;
}
let thirdParty = (contentType == Policy.type.ELEMHIDE ? false : isThirdParty(location, docDomain));
if (!match && Prefs.enabled)
{
match = defaultMatcher.matchesAny(locationText, Policy.typeDescr[contentType] || "", docDomain, thirdParty);
if (match instanceof BlockingFilter && node.ownerDocument && !(contentType in Policy.nonVisual))
{
let prefCollapse = (match.collapse != null ? match.collapse : !Prefs.fastcollapse);
if (collapse || prefCollapse)
Utils.schedulePostProcess(node);
}
// Track mouse events for objects
if (!match && contentType == Policy.type.OBJECT && node.nodeType == Ci.nsIDOMNode.ELEMENT_NODE)
{
node.addEventListener("mouseover", objectMouseEventHander, true);
node.addEventListener("mouseout", objectMouseEventHander, true);
}
}
// Store node data
RequestNotifier.addNodeData(node, topWnd, contentType, docDomain, thirdParty, locationText, match);
if (match)
FilterStorage.increaseHitCount(match);
return !match || match instanceof WhitelistFilter;
},
/**
* Checks whether the location's scheme is blockable.
* @param location {nsIURI}
* @return {Boolean}
*/
isBlockableScheme: function(location)
{
return !(location.scheme in Policy.whitelistSchemes);
},
/**
* Checks whether a page is whitelisted.
* @param {String} url
* @param {String} [parentUrl] location of the parent page
* @return {Filter} filter that matched the URL or null if not whitelisted
*/
isWhitelisted: function(url, parentUrl)
{
if (!url)
return null;
// Do not apply exception rules to schemes on our whitelistschemes list.
let match = /^([\w\-]+):/.exec(url);
if (match && match[1] in Policy.whitelistSchemes)
return null;
if (!parentUrl)
parentUrl = url;
// Ignore fragment identifier
let index = url.indexOf("#");
if (index >= 0)
url = url.substring(0, index);
let result = defaultMatcher.matchesAny(url, "DOCUMENT", getHostname(parentUrl), false);
return (result instanceof WhitelistFilter ? result : null);
},
/**
* Checks whether the page loaded in a window is whitelisted.
* @param wnd {nsIDOMWindow}
* @return {Filter} matching exception rule or null if not whitelisted
*/
isWindowWhitelisted: function(wnd)
{
return Policy.isWhitelisted(getWindowLocation(wnd));
},
/**
* Asynchronously re-checks filters for given nodes.
*/
refilterNodes: function(/**Node[]*/ nodes, /**RequestEntry*/ entry)
{
// Ignore nodes that have been blocked already
if (entry.filter && !(entry.filter instanceof WhitelistFilter))
return;
for each (let node in nodes)
Utils.runAsync(refilterNode, this, node, entry);
}
};
/**
* Private nsIContentPolicy and nsIChannelEventSink implementation
* @class
*/
var PolicyPrivate =
{
classDescription: "Adblock Plus content policy",
classID: Components.ID("cfeaabe6-1dd1-11b2-a0c6-cb5c268894c9"),
contractID: "@adblockplus.org/abp/policy;1",
xpcom_categories: ["content-policy", "net-channel-event-sinks"],
//
// nsISupports interface implementation
//
QueryInterface: XPCOMUtils.generateQI([Ci.nsIContentPolicy, Ci.nsIObserver,
Ci.nsIChannelEventSink, Ci.nsIFactory, Ci.nsISupportsWeakReference]),
//
// nsIContentPolicy interface implementation
//
shouldLoad: function(contentType, contentLocation, requestOrigin, node, mimeTypeGuess, extra)
{
// Ignore requests without context and top-level documents
if (!node || contentType == Policy.type.DOCUMENT)
return Ci.nsIContentPolicy.ACCEPT;
// Ignore standalone objects
if (contentType == Policy.type.OBJECT && node.ownerDocument && !/^text\/|[+\/]xml$/.test(node.ownerDocument.contentType))
return Ci.nsIContentPolicy.ACCEPT;
let wnd = Utils.getWindow(node);
if (!wnd)
return Ci.nsIContentPolicy.ACCEPT;
// Ignore whitelisted schemes
let location = Utils.unwrapURL(contentLocation);
if (!Policy.isBlockableScheme(location))
return Ci.nsIContentPolicy.ACCEPT;
// Interpret unknown types as "other"
if (!(contentType in Policy.typeDescr))
contentType = Policy.type.OTHER;
let result = Policy.processNode(wnd, node, contentType, location, false);
if (result)
{
// We didn't block this request so we will probably see it again in
// http-on-modify-request. Keep it so that we can associate it with the
// channel there - will be needed in case of redirect.
PolicyPrivate.previousRequest = [location, contentType];
}
return (result ? Ci.nsIContentPolicy.ACCEPT : Ci.nsIContentPolicy.REJECT_REQUEST);
},
shouldProcess: function(contentType, contentLocation, requestOrigin, insecNode, mimeType, extra)
{
return Ci.nsIContentPolicy.ACCEPT;
},
//
// nsIObserver interface implementation
//
observe: function(subject, topic, data, additional)
{
switch (topic)
{
case "content-document-global-created":
{
if (!(subject instanceof Ci.nsIDOMWindow) || !subject.opener)
return;
let uri = additional || Utils.makeURI(subject.location.href);
if (!Policy.processNode(subject.opener, subject.opener.document, Policy.type.POPUP, uri, false))
{
subject.stop();
Utils.runAsync(subject.close, subject);
}
else if (uri.spec == "about:blank")
{
// An about:blank pop-up most likely means that a load will be
// initiated synchronously. Set a flag for our "http-on-modify-request"
// handler.
PolicyPrivate.expectingPopupLoad = true;
Utils.runAsync(function()
{
PolicyPrivate.expectingPopupLoad = false;
});
}
break;
}
case "http-on-modify-request":
{
if (!(subject instanceof Ci.nsIHttpChannel))
return;
if (Prefs.enabled)
{
let match = defaultMatcher.matchesAny(subject.URI.spec, "DONOTTRACK", null, false);
if (match && match instanceof BlockingFilter)
{
FilterStorage.increaseHitCount(match);
subject.setRequestHeader("DNT", "1", false);
// Bug 23845 - Some routers are broken and cannot handle DNT header
// following Connection header. Make sure Connection header is last.
try
{
let connection = subject.getRequestHeader("Connection");
subject.setRequestHeader("Connection", null, false);
subject.setRequestHeader("Connection", connection, false);
} catch(e) {}
}
}
if (PolicyPrivate.previousRequest && subject.URI == PolicyPrivate.previousRequest[0] &&
subject instanceof Ci.nsIWritablePropertyBag)
{
// We just handled a content policy call for this request - associate
// the data with the channel so that we can find it in case of a redirect.
subject.setProperty("abpRequestType", PolicyPrivate.previousRequest[1]);
PolicyPrivate.previousRequest = null;
}
if (PolicyPrivate.expectingPopupLoad)
{
let wnd = Utils.getRequestWindow(subject);
if (wnd && wnd.opener && wnd.location.href == "about:blank")
PolicyPrivate.observe(wnd, "content-document-global-created", null, subject.URI);
}
break;
}
}
},
//
// nsIChannelEventSink interface implementation
//
asyncOnChannelRedirect: function(oldChannel, newChannel, flags, callback)
{
let result = Cr.NS_OK;
try
{
// Try to retrieve previously stored request data from the channel
let contentType;
if (oldChannel instanceof Ci.nsIWritablePropertyBag)
{
try
{
contentType = oldChannel.getProperty("abpRequestType");
}
catch(e)
{
// No data attached, ignore this redirect
return;
}
}
let newLocation = null;
try
{
newLocation = newChannel.URI;
} catch(e2) {}
if (!newLocation)
return;
let wnd = Utils.getRequestWindow(newChannel);
if (!wnd)
return;
if (!Policy.processNode(wnd, wnd.document, contentType, newLocation, false))
result = Cr.NS_BINDING_ABORTED;
}
catch (e)
{
// We shouldn't throw exceptions here - this will prevent the redirect.
Cu.reportError(e);
}
finally
{
callback.onRedirectVerifyCallback(result);
}
},
//
// nsIFactory interface implementation
//
createInstance: function(outer, iid)
{
if (outer)
throw Cr.NS_ERROR_NO_AGGREGATION;
return this.QueryInterface(iid);
}
};
/**
* Extracts the hostname from a URL (might return null).
*/
function getHostname(/**String*/ url) /**String*/
{
try
{
return Utils.unwrapURL(url).host;
}
catch(e)
{
return null;
}
}
/**
* Retrieves the location of a window.
* @param wnd {nsIDOMWindow}
* @return {String} window location or null on failure
*/
function getWindowLocation(wnd)
{
if ("name" in wnd && wnd.name == "messagepane")
{
// Thunderbird branch
try
{
let mailWnd = wnd.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation)
.QueryInterface(Ci.nsIDocShellTreeItem)
.rootTreeItem
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindow);
// Typically we get a wrapped mail window here, need to unwrap
try
{
mailWnd = mailWnd.wrappedJSObject;
} catch(e) {}
if ("currentHeaderData" in mailWnd && "content-base" in mailWnd.currentHeaderData)
{
return mailWnd.currentHeaderData["content-base"].headerValue;
}
else if ("currentHeaderData" in mailWnd && "from" in mailWnd.currentHeaderData)
{
let emailAddress = Utils.headerParser.extractHeaderAddressMailboxes(mailWnd.currentHeaderData.from.headerValue);
if (emailAddress)
return 'mailto:' + emailAddress.replace(/^[\s"]+/, "").replace(/[\s"]+$/, "").replace(/\s/g, '%20');
}
} catch(e) {}
}
else
{
// Firefox branch
return wnd.location.href;
}
}
/**
* Checks whether the location's origin is different from document's origin.
*/
function isThirdParty(/**nsIURI*/location, /**String*/ docDomain) /**Boolean*/
{
if (!location || !docDomain)
return true;
try
{
return Utils.effectiveTLD.getBaseDomain(location) != Utils.effectiveTLD.getBaseDomainFromHost(docDomain);
}
catch (e)
{
// EffectiveTLDService throws on IP addresses, just compare the host name
let host = "";
try
{
host = location.host;
} catch (e) {}
return host != docDomain;
}
}
/**
* Re-checks filters on an element.
*/
function refilterNode(/**Node*/ node, /**RequestEntry*/ entry)
{
let wnd = Utils.getWindow(node);
if (!wnd || wnd.closed)
return;
if (entry.type == Policy.type.OBJECT)
{
node.removeEventListener("mouseover", objectMouseEventHander, true);
node.removeEventListener("mouseout", objectMouseEventHander, true);
}
Policy.processNode(wnd, node, entry.type, Utils.makeURI(entry.location), true);
}
+265
View File
@@ -0,0 +1,265 @@
/*
* This Source Code is subject to the terms of the Mozilla Public License
* version 2.0 (the "License"). You can obtain a copy of the License at
* http://mozilla.org/MPL/2.0/.
*/
#filter substitution
/**
* @fileOverview Content policy to be loaded in the content process for a multi-process setup (currently only Fennec)
*/
var EXPORTED_SYMBOLS = ["PolicyRemote"];
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cr = Components.results;
const Cu = Components.utils;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://@ADDON_CHROME_NAME@/modules/Utils.jsm");
/**
* nsIContentPolicy and nsIChannelEventSink implementation
* @class
*/
var PolicyRemote =
{
classDescription: "Adblock Plus content policy",
classID: Components.ID("094560a0-4fed-11e0-b8af-0800200c9a66"),
contractID: "@adblockplus.org/abp/policy-remote;1",
xpcom_categories: ["content-policy", "net-channel-event-sinks"],
cache: new Cache(512),
startup: function()
{
let registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
try
{
registrar.registerFactory(PolicyRemote.classID, PolicyRemote.classDescription, PolicyRemote.contractID, PolicyRemote);
}
catch (e)
{
// Don't stop on errors - the factory might already be registered
Cu.reportError(e);
}
let catMan = Utils.categoryManager;
for each (let category in PolicyRemote.xpcom_categories)
catMan.addCategoryEntry(category, PolicyRemote.classDescription, PolicyRemote.contractID, false, true);
Services.obs.addObserver(PolicyRemote, "http-on-modify-request", true);
Services.obs.addObserver(PolicyRemote, "content-document-global-created", true);
// Generate class identifier used to collapse node and register corresponding
// stylesheet.
let offset = "a".charCodeAt(0);
Utils.collapsedClass = "";
for (let i = 0; i < 20; i++)
Utils.collapsedClass += String.fromCharCode(offset + Math.random() * 26);
let collapseStyle = Utils.makeURI("data:text/css," +
encodeURIComponent("." + Utils.collapsedClass +
"{-moz-binding: url(chrome://global/content/bindings/general.xml#foobarbazdummy) !important;}"));
Utils.styleService.loadAndRegisterSheet(collapseStyle, Ci.nsIStyleSheetService.USER_SHEET);
// Get notified if we need to invalidate our matching cache
Utils.childMessageManager.addMessageListener("AdblockPlus:Matcher:clearCache", function(message)
{
PolicyRemote.cache.clear();
});
},
//
// nsISupports interface implementation
//
QueryInterface: XPCOMUtils.generateQI([Ci.nsIContentPolicy, Ci.nsIObserver,
Ci.nsIChannelEventSink, Ci.nsIFactory, Ci.nsISupportsWeakReference]),
//
// nsIContentPolicy interface implementation
//
shouldLoad: function(contentType, contentLocation, requestOrigin, node, mimeTypeGuess, extra)
{
// Ignore requests without context and top-level documents
if (!node || contentType == Ci.nsIContentPolicy.TYPE_DOCUMENT)
return Ci.nsIContentPolicy.ACCEPT;
let wnd = Utils.getWindow(node);
if (!wnd)
return Ci.nsIContentPolicy.ACCEPT;
wnd = Utils.getOriginWindow(wnd);
let locations = [];
let testWnd = wnd;
while (true)
{
locations.push(testWnd.location.href);
if (testWnd.parent == testWnd)
break;
else
testWnd = testWnd.parent;
}
let key = contentType + " " + contentLocation.spec + " " + locations.join(" ");
if (!(key in this.cache.data))
{
this.cache.add(key, Utils.childMessageManager.sendSyncMessage("AdblockPlus:Policy:shouldLoad", {
contentType: contentType,
contentLocation: contentLocation.spec,
locations: locations})[0]);
}
let result = this.cache.data[key];
if (result.value == Ci.nsIContentPolicy.ACCEPT)
{
// We didn't block this request so we will probably see it again in
// http-on-modify-request. Keep it so that we can associate it with the
// channel there - will be needed in case of redirect.
PolicyRemote.previousRequest = [Utils.unwrapURL(contentLocation), contentType];
}
else if (result.postProcess)
Utils.schedulePostProcess(node);
return result.value;
},
shouldProcess: function(contentType, contentLocation, requestOrigin, insecNode, mimeType, extra)
{
return Ci.nsIContentPolicy.ACCEPT;
},
//
// nsIObserver interface implementation
//
observe: function(subject, topic, data, additional)
{
switch (topic)
{
case "content-document-global-created":
{
if (!(subject instanceof Ci.nsIDOMWindow) || !subject.opener)
return;
let uri = additional || Utils.makeURI(subject.location.href);
if (PolicyRemote.shouldLoad(0xFFFE /*Policy.type.POPUP*/, uri, null, subject.opener.document, null, null) != Ci.nsIContentPolicy.ACCEPT)
{
subject.stop();
Utils.runAsync(subject.close, subject);
}
else if (uri.spec == "about:blank")
{
// An about:blank pop-up most likely means that a load will be
// initiated synchronously. Set a flag for our "http-on-modify-request"
// handler.
PolicyRemote.expectingPopupLoad = true;
Utils.runAsync(function()
{
PolicyRemote.expectingPopupLoad = false;
});
}
break;
}
case "http-on-modify-request":
{
if (!(subject instanceof Ci.nsIHttpChannel))
return;
// TODO: Do-not-track header
if (PolicyRemote.previousRequest && subject.URI == PolicyRemote.previousRequest[0] &&
subject instanceof Ci.nsIWritablePropertyBag)
{
// We just handled a content policy call for this request - associate
// the data with the channel so that we can find it in case of a redirect.
subject.setProperty("abpRequestType", PolicyRemote.previousRequest[1]);
PolicyRemote.previousRequest = null;
}
if (PolicyRemote.expectingPopupLoad)
{
let wnd = Utils.getRequestWindow(subject);
if (wnd && wnd.opener && wnd.location.href == "about:blank")
PolicyRemote.observe(wnd, "content-document-global-created", null, subject.URI);
}
break;
}
}
},
//
// nsIChannelEventSink interface implementation
//
onChannelRedirect: function(oldChannel, newChannel, flags)
{
try
{
// Try to retrieve previously stored request data from the channel
let contentType;
if (oldChannel instanceof Ci.nsIWritablePropertyBag)
{
try
{
contentType = oldChannel.getProperty("abpRequestType");
}
catch(e)
{
// No data attached, ignore this redirect
return;
}
}
let newLocation = null;
try
{
newLocation = newChannel.URI;
} catch(e2) {}
if (!newLocation)
return;
let wnd = Utils.getRequestWindow(newChannel);
if (!wnd)
return;
// HACK: NS_BINDING_ABORTED would be proper error code to throw but this will show up in error console (bug 287107)
if (PolicyRemote.shouldLoad(contentType, newLocation, null, wnd.document) != Ci.nsIContentPolicy.ACCEPT)
throw Cr.NS_BASE_STREAM_WOULD_BLOCK;
else
return;
}
catch (e if (e != Cr.NS_BASE_STREAM_WOULD_BLOCK))
{
// We shouldn't throw exceptions here - this will prevent the redirect.
Cu.reportError(e);
}
},
asyncOnChannelRedirect: function(oldChannel, newChannel, flags, callback)
{
this.onChannelRedirect(oldChannel, newChannel, flags);
// If onChannelRedirect didn't throw an exception indicate success
callback.onRedirectVerifyCallback(Cr.NS_OK);
},
//
// nsIFactory interface implementation
//
createInstance: function(outer, iid)
{
if (outer)
throw Cr.NS_ERROR_NO_AGGREGATION;
return this.QueryInterface(iid);
}
};
PolicyRemote.startup();
+447
View File
@@ -0,0 +1,447 @@
/*
* This Source Code is subject to the terms of the Mozilla Public License
* version 2.0 (the "License"). You can obtain a copy of the License at
* http://mozilla.org/MPL/2.0/.
*/
#filter substitution
/**
* @fileOverview Element hiding implementation.
*/
var EXPORTED_SYMBOLS = ["ElemHide"];
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cr = Components.results;
const Cu = Components.utils;
let baseURL = "resource://@ADDON_CHROME_NAME@/modules/";
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import(baseURL + "Utils.jsm");
Cu.import(baseURL + "IO.jsm");
Cu.import(baseURL + "Prefs.jsm");
Cu.import(baseURL + "ContentPolicy.jsm");
Cu.import(baseURL + "FilterNotifier.jsm");
Cu.import(baseURL + "FilterClasses.jsm");
Cu.import(baseURL + "TimeLine.jsm");
/**
* Lookup table, filters by their associated key
* @type Object
*/
let filterByKey = {__proto__: null};
/**
* Lookup table, keys of the filters by filter text
* @type Object
*/
let keyByFilter = {__proto__: null};
/**
* Currently applied stylesheet URL
* @type nsIURI
*/
let styleURL = null;
/**
* Element hiding component
* @class
*/
var ElemHide =
{
/**
* Indicates whether filters have been added or removed since the last apply() call.
* @type Boolean
*/
isDirty: false,
/**
* Inidicates whether the element hiding stylesheet is currently applied.
* @type Boolean
*/
applied: false,
/**
* Called on module startup.
*/
init: function()
{
TimeLine.enter("Entered ElemHide.init()");
Prefs.addListener(function(name)
{
if (name == "enabled")
ElemHide.apply();
});
TimeLine.log("done adding prefs listener");
let styleFile = IO.resolveFilePath(Prefs.data_directory);
styleFile.append("elemhide.css");
styleURL = Utils.ioService.newFileURI(styleFile).QueryInterface(Ci.nsIFileURL);
TimeLine.log("done determining stylesheet URL");
TimeLine.log("registering component");
let registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
registrar.registerFactory(ElemHidePrivate.classID, ElemHidePrivate.classDescription,
"@mozilla.org/network/protocol/about;1?what=" + ElemHidePrivate.aboutPrefix, ElemHidePrivate);
TimeLine.leave("ElemHide.init() done");
},
/**
* Removes all known filters
*/
clear: function()
{
filterByKey = {__proto__: null};
keyByFilter = {__proto__: null};
ElemHide.isDirty = false;
ElemHide.unapply();
},
/**
* Add a new element hiding filter
* @param {ElemHideFilter} filter
*/
add: function(filter)
{
if (filter.text in keyByFilter)
return;
let key;
do {
key = Math.random().toFixed(15).substr(5);
} while (key in filterByKey);
filterByKey[key] = filter;
keyByFilter[filter.text] = key;
ElemHide.isDirty = true;
},
/**
* Removes an element hiding filter
* @param {ElemHideFilter} filter
*/
remove: function(filter)
{
if (!(filter.text in keyByFilter))
return;
let key = keyByFilter[filter.text];
delete filterByKey[key];
delete keyByFilter[filter.text];
ElemHide.isDirty = true;
},
/**
* Will be set to true if apply() is running (reentrance protection).
* @type Boolean
*/
_applying: false,
/**
* Will be set to true if an apply() call arrives while apply() is already
* running (delayed execution).
* @type Boolean
*/
_needsApply: false,
/**
* Generates stylesheet URL and applies it globally
*/
apply: function()
{
if (this._applying)
{
this._needsApply = true;
return;
}
TimeLine.enter("Entered ElemHide.apply()");
if (!ElemHide.isDirty || !Prefs.enabled)
{
// Nothing changed, looks like we merely got enabled/disabled
if (Prefs.enabled && !ElemHide.applied)
{
try
{
Utils.styleService.loadAndRegisterSheet(styleURL, Ci.nsIStyleSheetService.USER_SHEET);
ElemHide.applied = true;
}
catch (e)
{
Cu.reportError(e);
}
TimeLine.log("Applying existing stylesheet finished");
}
else if (!Prefs.enabled && ElemHide.applied)
{
ElemHide.unapply();
TimeLine.log("ElemHide.unapply() finished");
}
TimeLine.leave("ElemHide.apply() done (no file changes)");
return;
}
IO.writeToFile(styleURL.file, false, this._generateCSSContent(), function(e)
{
TimeLine.enter("ElemHide.apply() write callback");
this._applying = false;
if (e && e.result == Cr.NS_ERROR_NOT_AVAILABLE)
{
e = null;
try
{
styleURL.file.remove(false);
} catch (e2) {}
}
else if (e)
Cu.reportError(e);
if (this._needsApply)
{
this._needsApply = false;
this.apply();
}
else if (!e)
{
ElemHide.isDirty = false;
ElemHide.unapply();
TimeLine.log("ElemHide.unapply() finished");
if (styleURL.file.exists())
{
try
{
Utils.styleService.loadAndRegisterSheet(styleURL, Ci.nsIStyleSheetService.USER_SHEET);
ElemHide.applied = true;
}
catch (e)
{
Cu.reportError(e);
}
TimeLine.log("Applying stylesheet finished");
}
FilterNotifier.triggerListeners("elemhideupdate");
}
TimeLine.leave("ElemHide.apply() write callback done");
}.bind(this), "ElemHideWrite");
this._applying = true;
TimeLine.leave("ElemHide.apply() done", "ElemHideWrite");
},
_generateCSSContent: function()
{
// Grouping selectors by domains
TimeLine.log("start grouping selectors");
let domains = {__proto__: null};
let hasFilters = false;
for (let key in filterByKey)
{
let filter = filterByKey[key];
let domain = filter.selectorDomain || "";
let list;
if (domain in domains)
list = domains[domain];
else
{
list = {__proto__: null};
domains[domain] = list;
}
list[filter.selector] = key;
hasFilters = true;
}
TimeLine.log("done grouping selectors");
if (!hasFilters)
throw Cr.NS_ERROR_NOT_AVAILABLE;
function escapeChar(match)
{
return "\\" + match.charCodeAt(0).toString(16) + " ";
}
// Return CSS data
let cssTemplate = "-moz-binding: url(about:" + ElemHidePrivate.aboutPrefix + "?%ID%#dummy) !important;";
for (let domain in domains)
{
let rules = [];
let list = domains[domain];
if (domain)
yield ('@-moz-document domain("' + domain.split(",").join('"),domain("') + '"){').replace(/[^\x01-\x7F]/g, escapeChar);
else
{
// Only allow unqualified rules on a few protocols to prevent them from blocking chrome
yield '@-moz-document url-prefix("http://"),url-prefix("https://"),'
+ 'url-prefix("mailbox://"),url-prefix("imap://"),'
+ 'url-prefix("news://"),url-prefix("snews://"){';
}
for (let selector in list)
yield selector.replace(/[^\x01-\x7F]/g, escapeChar) + "{" + cssTemplate.replace("%ID%", list[selector]) + "}";
yield '}';
}
},
/**
* Unapplies current stylesheet URL
*/
unapply: function()
{
if (ElemHide.applied)
{
try
{
Utils.styleService.unregisterSheet(styleURL, Ci.nsIStyleSheetService.USER_SHEET);
}
catch (e)
{
Cu.reportError(e);
}
ElemHide.applied = false;
}
},
/**
* Retrieves the currently applied stylesheet URL
* @type String
*/
get styleURL() ElemHide.applied ? styleURL.spec : null,
/**
* Retrieves an element hiding filter by the corresponding protocol key
*/
getFilterByKey: function(/**String*/ key) /**Filter*/
{
return (key in filterByKey ? filterByKey[key] : null);
}
};
/**
* Private nsIAboutModule implementation
* @class
*/
var ElemHidePrivate =
{
classID: Components.ID("{55fb7be0-1dd2-11b2-98e6-9e97caf8ba67}"),
classDescription: "Element hiding hit registration protocol handler",
aboutPrefix: "abp-elemhidehit",
//
// Factory implementation
//
createInstance: function(outer, iid)
{
if (outer != null)
throw Cr.NS_ERROR_NO_AGGREGATION;
return this.QueryInterface(iid);
},
//
// About module implementation
//
getURIFlags: function(uri)
{
return ("HIDE_FROM_ABOUTABOUT" in Ci.nsIAboutModule ? Ci.nsIAboutModule.HIDE_FROM_ABOUTABOUT : 0);
},
newChannel: function(uri)
{
let match = /\?(\d+)/.exec(uri.path)
if (!match)
throw Cr.NS_ERROR_FAILURE;
return new HitRegistrationChannel(uri, match[1]);
},
QueryInterface: XPCOMUtils.generateQI([Ci.nsIFactory, Ci.nsIAboutModule])
};
/**
* Channel returning data for element hiding hits.
* @constructor
*/
function HitRegistrationChannel(uri, key)
{
this.key = key;
this.URI = this.originalURI = uri;
}
HitRegistrationChannel.prototype = {
key: null,
URI: null,
originalURI: null,
contentCharset: "utf-8",
contentLength: 0,
contentType: "text/xml",
owner: Utils.systemPrincipal,
securityInfo: null,
notificationCallbacks: null,
loadFlags: 0,
loadGroup: null,
name: null,
status: Cr.NS_OK,
asyncOpen: function(listener, context)
{
let stream = this.open();
Utils.runAsync(function()
{
try {
listener.onStartRequest(this, context);
} catch(e) {}
try {
listener.onDataAvailable(this, context, stream, 0, stream.available());
} catch(e) {}
try {
listener.onStopRequest(this, context, Cr.NS_OK);
} catch(e) {}
}, this);
},
open: function()
{
let data = "<bindings xmlns='http://www.mozilla.org/xbl'><binding id='dummy' bindToUntrustedContent='true'/></bindings>";
if (this.key in filterByKey)
{
let wnd = Utils.getRequestWindow(this);
if (wnd && wnd.document && !Policy.processNode(wnd, wnd.document, Policy.type.ELEMHIDE, filterByKey[this.key]))
data = "<bindings xmlns='http://www.mozilla.org/xbl'/>";
}
let stream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(Ci.nsIStringInputStream);
stream.setData(data, data.length);
return stream;
},
isPending: function()
{
return false;
},
cancel: function()
{
throw Cr.NS_ERROR_NOT_IMPLEMENTED;
},
suspend: function()
{
throw Cr.NS_ERROR_NOT_IMPLEMENTED;
},
resume: function()
{
throw Cr.NS_ERROR_NOT_IMPLEMENTED;
},
QueryInterface: XPCOMUtils.generateQI([Ci.nsIChannel, Ci.nsIRequest])
};
+186
View File
@@ -0,0 +1,186 @@
/*
* This Source Code is subject to the terms of the Mozilla Public License
* version 2.0 (the "License"). You can obtain a copy of the License at
* http://mozilla.org/MPL/2.0/.
*/
#filter substitution
/**
* @fileOverview Element hiding protocol to be loaded in the content process for a multi-process setup (currently only Fennec)
*/
var EXPORTED_SYMBOLS = ["ElemHideRemote"];
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cr = Components.results;
const Cu = Components.utils;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://@ADDON_CHROME_NAME@/modules/Utils.jsm");
/**
* Currently applied stylesheet URL
* @type nsIURI
*/
let styleURL = null;
/**
* nsIAboutModule implementation
* @class
*/
var ElemHideRemote =
{
classID: Components.ID("{55fb7be0-1dd2-11b2-98e6-9e97caf8ba67}"),
classDescription: "Element hiding hit registration protocol handler",
aboutPrefix: "abp-elemhidehit",
startup: function()
{
let registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
registrar.registerFactory(ElemHideRemote.classID, ElemHideRemote.classDescription,
"@mozilla.org/network/protocol/about;1?what=" + ElemHideRemote.aboutPrefix, ElemHideRemote);
styleURL = Utils.makeURI(Utils.childMessageManager.sendSyncMessage("AdblockPlus:ElemHide:styleURL"));
if (styleURL)
Utils.styleService.loadAndRegisterSheet(styleURL, Ci.nsIStyleSheetService.USER_SHEET);
// Get notified about style URL changes
Utils.childMessageManager.addMessageListener("AdblockPlus:ElemHide:updateStyleURL", function(message)
{
if (styleURL)
Utils.styleService.unregisterSheet(styleURL, Ci.nsIStyleSheetService.USER_SHEET);
styleURL = Utils.makeURI(message.json);
if (styleURL)
Utils.styleService.loadAndRegisterSheet(styleURL, Ci.nsIStyleSheetService.USER_SHEET);
});
},
//
// Factory implementation
//
createInstance: function(outer, iid)
{
if (outer != null)
throw Cr.NS_ERROR_NO_AGGREGATION;
return this.QueryInterface(iid);
},
//
// About module implementation
//
getURIFlags: function(uri)
{
return Ci.nsIAboutModule.HIDE_FROM_ABOUTABOUT;
},
newChannel: function(uri)
{
let match = /\?(\d+)/.exec(uri.path)
if (!match)
throw Cr.NS_ERROR_FAILURE;
return new HitRegistrationChannel(uri, match[1]);
},
QueryInterface: XPCOMUtils.generateQI([Ci.nsIFactory, Ci.nsIAboutModule])
};
/**
* Channel returning data for element hiding hits.
* @constructor
*/
function HitRegistrationChannel(uri, key)
{
this.key = key;
this.URI = this.originalURI = uri;
}
HitRegistrationChannel.prototype = {
key: null,
URI: null,
originalURI: null,
contentCharset: "utf-8",
contentLength: 0,
contentType: "text/xml",
owner: Utils.systemPrincipal,
securityInfo: null,
notificationCallbacks: null,
loadFlags: 0,
loadGroup: null,
name: null,
status: Cr.NS_OK,
asyncOpen: function(listener, context)
{
let stream = this.open();
Utils.runAsync(function()
{
try {
listener.onStartRequest(this, context);
} catch(e) {}
try {
listener.onDataAvailable(this, context, stream, 0, stream.available());
} catch(e) {}
try {
listener.onStopRequest(this, context, Cr.NS_OK);
} catch(e) {}
}, this);
},
open: function()
{
let data = "<bindings xmlns='http://www.mozilla.org/xbl'><binding id='dummy' bindToUntrustedContent='true'/></bindings>";
let wnd = Utils.getRequestWindow(this);
if (wnd)
{
wnd = Utils.getOriginWindow(wnd);
let locations = [];
let testWnd = wnd;
while (true)
{
locations.push(testWnd.location.href);
if (testWnd.parent == testWnd)
break;
else
testWnd = testWnd.parent;
}
let result = Utils.childMessageManager.sendSyncMessage("AdblockPlus:ElemHide:checkHit", {
key: this.key,
locations: locations})[0];
if (result)
data = "<bindings xmlns='http://www.mozilla.org/xbl'/>";
}
let stream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(Ci.nsIStringInputStream);
stream.setData(data, data.length);
return stream;
},
isPending: function()
{
return false;
},
cancel: function()
{
throw Cr.NS_ERROR_NOT_IMPLEMENTED;
},
suspend: function()
{
throw Cr.NS_ERROR_NOT_IMPLEMENTED;
},
resume: function()
{
throw Cr.NS_ERROR_NOT_IMPLEMENTED;
},
QueryInterface: XPCOMUtils.generateQI([Ci.nsIChannel, Ci.nsIRequest])
};
ElemHideRemote.startup();
+813
View File
@@ -0,0 +1,813 @@
/*
* This Source Code is subject to the terms of the Mozilla Public License
* version 2.0 (the "License"). You can obtain a copy of the License at
* http://mozilla.org/MPL/2.0/.
*/
#filter substitution
/**
* @fileOverview Definition of Filter class and its subclasses.
*/
var EXPORTED_SYMBOLS = ["Filter", "InvalidFilter", "CommentFilter", "ActiveFilter", "RegExpFilter", "BlockingFilter", "WhitelistFilter", "ElemHideFilter"];
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cr = Components.results;
const Cu = Components.utils;
let baseURL = "resource://@ADDON_CHROME_NAME@/modules/";
Cu.import(baseURL + "Utils.jsm");
Cu.import(baseURL + "FilterNotifier.jsm");
/**
* Abstract base class for filters
*
* @param {String} text string representation of the filter
* @constructor
*/
function Filter(text)
{
this.text = text;
this.subscriptions = [];
}
Filter.prototype =
{
/**
* String representation of the filter
* @type String
*/
text: null,
/**
* Filter subscriptions the filter belongs to
* @type Array of Subscription
*/
subscriptions: null,
/**
* Serializes the filter to an array of strings for writing out on the disk.
* @param {Array of String} buffer buffer to push the serialization results into
*/
serialize: function(buffer)
{
buffer.push("[Filter]");
buffer.push("text=" + this.text);
},
toString: function()
{
return this.text;
}
};
/**
* Cache for known filters, maps string representation to filter objects.
* @type Object
*/
Filter.knownFilters = {__proto__: null};
/**
* Regular expression that element hiding filters should match
* @type RegExp
*/
Filter.elemhideRegExp = /^([^\/\*\|\@"!]*?)#(?:([\w\-]+|\*)((?:\([\w\-]+(?:[$^*]?=[^\(\)"]*)?\))*)|#([^{}]+))$/;
/**
* Regular expression that RegExp filters specified as RegExps should match
* @type RegExp
*/
Filter.regexpRegExp = /^(@@)?\/.*\/(?:\$~?[\w\-]+(?:=[^,\s]+)?(?:,~?[\w\-]+(?:=[^,\s]+)?)*)?$/;
/**
* Regular expression that options on a RegExp filter should match
* @type RegExp
*/
Filter.optionsRegExp = /\$(~?[\w\-]+(?:=[^,\s]+)?(?:,~?[\w\-]+(?:=[^,\s]+)?)*)$/;
/**
* Creates a filter of correct type from its text representation - does the basic parsing and
* calls the right constructor then.
*
* @param {String} text as in Filter()
* @return {Filter} filter or null if the filter couldn't be created
*/
Filter.fromText = function(text)
{
if (text in Filter.knownFilters)
return Filter.knownFilters[text];
if (!/\S/.test(text))
return null;
let ret;
let match = Filter.elemhideRegExp.exec(text);
if (match)
ret = ElemHideFilter.fromText(text, match[1], match[2], match[3], match[4]);
else if (text[0] == "!")
ret = new CommentFilter(text);
else
ret = RegExpFilter.fromText(text);
Filter.knownFilters[ret.text] = ret;
return ret;
}
/**
* Deserializes a filter
*
* @param {Object} obj map of serialized properties and their values
* @return {Filter} filter or null if the filter couldn't be created
*/
Filter.fromObject = function(obj)
{
let ret = Filter.fromText(obj.text);
if (ret instanceof ActiveFilter)
{
if ("disabled" in obj)
ret._disabled = (obj.disabled == "true");
if ("hitCount" in obj)
ret._hitCount = parseInt(obj.hitCount) || 0;
if ("lastHit" in obj)
ret._lastHit = parseInt(obj.lastHit) || 0;
}
return ret;
}
/**
* Removes unnecessary whitespaces from filter text, will only return null if
* the input parameter is null.
*/
Filter.normalize = function(/**String*/ text) /**String*/
{
if (!text)
return text;
// Remove line breaks and such
text = text.replace(/[^\S ]/g, "");
if (/^\s*!/.test(text))
{
// Don't remove spaces inside comments
return text.replace(/^\s+/, "").replace(/\s+$/, "");
}
else if (Filter.elemhideRegExp.test(text))
{
// Special treatment for element hiding filters, right side is allowed to contain spaces
let [, domain, separator, selector] = /^(.*?)(#+)(.*)$/.exec(text); // .split(..., 2) will cut off the end of the string
return domain.replace(/\s/g, "") + separator + selector.replace(/^\s+/, "").replace(/\s+$/, "");
}
else
return text.replace(/\s/g, "");
}
/**
* Class for invalid filters
* @param {String} text see Filter()
* @param {String} reason Reason why this filter is invalid
* @constructor
* @augments Filter
*/
function InvalidFilter(text, reason)
{
Filter.call(this, text);
this.reason = reason;
}
InvalidFilter.prototype =
{
__proto__: Filter.prototype,
/**
* Reason why this filter is invalid
* @type String
*/
reason: null,
/**
* See Filter.serialize()
*/
serialize: function(buffer) {}
};
/**
* Class for comments
* @param {String} text see Filter()
* @constructor
* @augments Filter
*/
function CommentFilter(text)
{
Filter.call(this, text);
}
CommentFilter.prototype =
{
__proto__: Filter.prototype,
/**
* See Filter.serialize()
*/
serialize: function(buffer) {}
};
/**
* Abstract base class for filters that can get hits
* @param {String} text see Filter()
* @param {String} domains (optional) Domains that the filter is restricted to separated by domainSeparator e.g. "foo.com|bar.com|~baz.com"
* @constructor
* @augments Filter
*/
function ActiveFilter(text, domains)
{
Filter.call(this, text);
if (domains)
{
this.domainSource = domains;
this.__defineGetter__("domains", this._getDomains);
}
}
ActiveFilter.prototype =
{
__proto__: Filter.prototype,
_disabled: false,
_hitCount: 0,
_lastHit: 0,
/**
* Defines whether the filter is disabled
* @type Boolean
*/
get disabled() this._disabled,
set disabled(value)
{
if (value != this._disabled)
{
let oldValue = this._disabled;
this._disabled = value;
FilterNotifier.triggerListeners("filter.disabled", this, value, oldValue);
}
return this._disabled;
},
/**
* Number of hits on the filter since the last reset
* @type Number
*/
get hitCount() this._hitCount,
set hitCount(value)
{
if (value != this._hitCount)
{
let oldValue = this._hitCount;
this._hitCount = value;
FilterNotifier.triggerListeners("filter.hitCount", this, value, oldValue);
}
return this._hitCount;
},
/**
* Last time the filter had a hit (in milliseconds since the beginning of the epoch)
* @type Number
*/
get lastHit() this._lastHit,
set lastHit(value)
{
if (value != this._lastHit)
{
let oldValue = this._lastHit;
this._lastHit = value;
FilterNotifier.triggerListeners("filter.lastHit", this, value, oldValue);
}
return this._lastHit;
},
/**
* String that the domains property should be generated from
* @type String
*/
domainSource: null,
/**
* Separator character used in domainSource property, must be overridden by subclasses
* @type String
*/
domainSeparator: null,
/**
* Map containing domains that this filter should match on/not match on or null if the filter should match on all domains
* @type Object
*/
domains: null,
/**
* Called first time domains property is requested, triggers _generateDomains method.
*/
_getDomains: function()
{
this._generateDomains();
return this.domains;
},
/**
* Generates domains property when it is requested for the first time.
*/
_generateDomains: function()
{
let domains = this.domainSource.split(this.domainSeparator);
delete this.domainSource;
delete this.domains;
if (domains.length == 1 && domains[0][0] != "~")
{
// Fast track for the common one-domain scenario
this.domains = {__proto__: null, "": false};
this.domains[domains[0]] = true;
}
else
{
let hasIncludes = false;
for (let i = 0; i < domains.length; i++)
{
let domain = domains[i];
if (domain == "")
continue;
let include;
if (domain[0] == "~")
{
include = false;
domain = domain.substr(1);
}
else
{
include = true;
hasIncludes = true;
}
if (!this.domains)
this.domains = {__proto__: null};
this.domains[domain] = include;
}
this.domains[""] = !hasIncludes;
}
},
/**
* Checks whether this filter is active on a domain.
*/
isActiveOnDomain: function(/**String*/ docDomain) /**Boolean*/
{
// If no domains are set the rule matches everywhere
if (!this.domains)
return true;
// If the document has no host name, match only if the filter isn't restricted to specific domains
if (!docDomain)
return this.domains[""];
docDomain = docDomain.replace(/\.+$/, "").toUpperCase();
while (true)
{
if (docDomain in this.domains)
return this.domains[docDomain];
let nextDot = docDomain.indexOf(".");
if (nextDot < 0)
break;
docDomain = docDomain.substr(nextDot + 1);
}
return this.domains[""];
},
/**
* Checks whether this filter is active only on a domain and its subdomains.
*/
isActiveOnlyOnDomain: function(/**String*/ docDomain) /**Boolean*/
{
if (!docDomain || !this.domains || this.domains[""])
return false;
docDomain = docDomain.replace(/\.+$/, "").toUpperCase();
for (let domain in this.domains)
if (this.domains[domain] && domain != docDomain && (domain.length <= docDomain.length || domain.indexOf("." + docDomain) != domain.length - docDomain.length - 1))
return false;
return true;
},
/**
* See Filter.serialize()
*/
serialize: function(buffer)
{
if (this._disabled || this._hitCount || this._lastHit)
{
Filter.prototype.serialize.call(this, buffer);
if (this._disabled)
buffer.push("disabled=true");
if (this._hitCount)
buffer.push("hitCount=" + this._hitCount);
if (this._lastHit)
buffer.push("lastHit=" + this._lastHit);
}
}
};
/**
* Abstract base class for RegExp-based filters
* @param {String} text see Filter()
* @param {String} regexpSource filter part that the regular expression should be build from
* @param {Number} contentType (optional) Content types the filter applies to, combination of values from RegExpFilter.typeMap
* @param {Boolean} matchCase (optional) Defines whether the filter should distinguish between lower and upper case letters
* @param {String} domains (optional) Domains that the filter is restricted to, e.g. "foo.com|bar.com|~baz.com"
* @param {Boolean} thirdParty (optional) Defines whether the filter should apply to third-party or first-party content only
* @constructor
* @augments ActiveFilter
*/
function RegExpFilter(text, regexpSource, contentType, matchCase, domains, thirdParty)
{
ActiveFilter.call(this, text, domains);
if (contentType != null)
this.contentType = contentType;
if (matchCase)
this.matchCase = matchCase;
if (thirdParty != null)
this.thirdParty = thirdParty;
if (regexpSource.length >= 2 && regexpSource[0] == "/" && regexpSource[regexpSource.length - 1] == "/")
{
// The filter is a regular expression - convert it immediately to catch syntax errors
this.regexp = new RegExp(regexpSource.substr(1, regexpSource.length - 2), this.matchCase ? "" : "i");
}
else
{
// No need to convert this filter to regular expression yet, do it on demand
this.regexpSource = regexpSource;
this.__defineGetter__("regexp", this._generateRegExp);
}
}
RegExpFilter.prototype =
{
__proto__: ActiveFilter.prototype,
/**
* Number of filters contained, will always be 1 (required to optimize Matcher).
* @type Integer
*/
length: 1,
/**
* @see ActiveFilter.domainSeparator
*/
domainSeparator: "|",
/**
* Expression from which a regular expression should be generated - for delayed creation of the regexp property
* @type String
*/
regexpSource: null,
/**
* Regular expression to be used when testing against this filter
* @type RegExp
*/
regexp: null,
/**
* Content types the filter applies to, combination of values from RegExpFilter.typeMap
* @type Number
*/
contentType: 0x7FFFFFFF,
/**
* Defines whether the filter should distinguish between lower and upper case letters
* @type Boolean
*/
matchCase: false,
/**
* Defines whether the filter should apply to third-party or first-party content only. Can be null (apply to all content).
* @type Boolean
*/
thirdParty: null,
/**
* Generates regexp property when it is requested for the first time.
* @return {RegExp}
*/
_generateRegExp: function()
{
// Remove multiple wildcards
let source = this.regexpSource.replace(/\*+/g, "*");
// Remove leading wildcards
if (source[0] == "*")
source = source.substr(1);
// Remove trailing wildcards
let pos = source.length - 1;
if (pos >= 0 && source[pos] == "*")
source = source.substr(0, pos);
source = source.replace(/\^\|$/, "^") // remove anchors following separator placeholder
.replace(/\W/g, "\\$&") // escape special symbols
.replace(/\\\*/g, ".*") // replace wildcards by .*
// process separator placeholders (all ANSI charaters but alphanumeric characters and _%.-)
.replace(/\\\^/g, "(?:[\\x00-\\x24\\x26-\\x2C\\x2F\\x3A-\\x40\\x5B-\\x5E\\x60\\x7B-\\x80]|$)")
.replace(/^\\\|\\\|/, "^[\\w\\-]+:\\/+(?!\\/)(?:[^.\\/]+\\.)*?") // process extended anchor at expression start
.replace(/^\\\|/, "^") // process anchor at expression start
.replace(/\\\|$/, "$"); // process anchor at expression end
let regexp = new RegExp(source, this.matchCase ? "" : "i");
delete this.regexp;
delete this.regexpSource;
return (this.regexp = regexp);
},
/**
* Tests whether the URL matches this filter
* @param {String} location URL to be tested
* @param {String} contentType content type identifier of the URL
* @param {String} docDomain domain name of the document that loads the URL
* @param {Boolean} thirdParty should be true if the URL is a third-party request
* @return {Boolean} true in case of a match
*/
matches: function(location, contentType, docDomain, thirdParty)
{
if (this.regexp.test(location) &&
(RegExpFilter.typeMap[contentType] & this.contentType) != 0 &&
(this.thirdParty == null || this.thirdParty == thirdParty) &&
this.isActiveOnDomain(docDomain))
{
return true;
}
return false;
}
};
RegExpFilter.prototype.__defineGetter__("0", function()
{
return this;
});
/**
* Creates a RegExp filter from its text representation
* @param {String} text same as in Filter()
*/
RegExpFilter.fromText = function(text)
{
let blocking = true;
let origText = text;
if (text.indexOf("@@") == 0)
{
blocking = false;
text = text.substr(2);
}
let contentType = null;
let matchCase = null;
let domains = null;
let thirdParty = null;
let collapse = null;
let options;
let match = Filter.optionsRegExp.exec(text);
if (match)
{
options = match[1].toUpperCase().split(",");
text = match.input.substr(0, match.index);
for each (let option in options)
{
let value = null;
let separatorIndex = option.indexOf("=");
if (separatorIndex >= 0)
{
value = option.substr(separatorIndex + 1);
option = option.substr(0, separatorIndex);
}
option = option.replace(/-/, "_");
if (option in RegExpFilter.typeMap)
{
if (contentType == null)
contentType = 0;
contentType |= RegExpFilter.typeMap[option];
}
else if (option[0] == "~" && option.substr(1) in RegExpFilter.typeMap)
{
if (contentType == null)
contentType = RegExpFilter.prototype.contentType;
contentType &= ~RegExpFilter.typeMap[option.substr(1)];
}
else if (option == "MATCH_CASE")
matchCase = true;
else if (option == "DOMAIN" && typeof value != "undefined")
domains = value;
else if (option == "THIRD_PARTY")
thirdParty = true;
else if (option == "~THIRD_PARTY")
thirdParty = false;
else if (option == "COLLAPSE")
collapse = true;
else if (option == "~COLLAPSE")
collapse = false;
}
}
if (!blocking && (contentType == null || (contentType & RegExpFilter.typeMap.DOCUMENT)) &&
(!options || options.indexOf("DOCUMENT") < 0) && !/^\|?[\w\-]+:/.test(text))
{
// Exception filters shouldn't apply to pages by default unless they start with a protocol name
if (contentType == null)
contentType = RegExpFilter.prototype.contentType;
contentType &= ~RegExpFilter.typeMap.DOCUMENT;
}
try
{
if (blocking)
return new BlockingFilter(origText, text, contentType, matchCase, domains, thirdParty, collapse);
else
return new WhitelistFilter(origText, text, contentType, matchCase, domains, thirdParty);
}
catch (e)
{
return new InvalidFilter(text, e);
}
}
/**
* Maps type strings like "SCRIPT" or "OBJECT" to bit masks
*/
RegExpFilter.typeMap = {
OTHER: 1,
SCRIPT: 2,
IMAGE: 4,
STYLESHEET: 8,
OBJECT: 16,
SUBDOCUMENT: 32,
DOCUMENT: 64,
XBL: 1,
PING: 1,
XMLHTTPREQUEST: 2048,
OBJECT_SUBREQUEST: 4096,
DTD: 1,
MEDIA: 16384,
FONT: 32768,
WEBSOCKET: 1,
WEBRTC: 1,
CSP: 1,
BACKGROUND: 4, // Backwards compat, same as IMAGE
POPUP: 0x10000000,
DONOTTRACK: 0x20000000,
ELEMHIDE: 0x40000000
};
// ELEMHIDE, DONOTTRACK, POPUP option shouldn't be there by default
RegExpFilter.prototype.contentType &= ~(
RegExpFilter.typeMap.ELEMHIDE |
RegExpFilter.typeMap.DONOTTRACK |
RegExpFilter.typeMap.POPUP |
RegExpFilter.typeMap.WEBSOCKET |
RegExpFilter.typeMap.WEBRTC |
RegExpFilter.typeMap.CSP
);
/**
* Class for blocking filters
* @param {String} text see Filter()
* @param {String} regexpSource see RegExpFilter()
* @param {Number} contentType see RegExpFilter()
* @param {Boolean} matchCase see RegExpFilter()
* @param {String} domains see RegExpFilter()
* @param {Boolean} thirdParty see RegExpFilter()
* @param {Boolean} collapse defines whether the filter should collapse blocked content, can be null
* @constructor
* @augments RegExpFilter
*/
function BlockingFilter(text, regexpSource, contentType, matchCase, domains, thirdParty, collapse)
{
RegExpFilter.call(this, text, regexpSource, contentType, matchCase, domains, thirdParty);
this.collapse = collapse;
}
BlockingFilter.prototype =
{
__proto__: RegExpFilter.prototype,
/**
* Defines whether the filter should collapse blocked content. Can be null (use the global preference).
* @type Boolean
*/
collapse: null
};
/**
* Class for whitelist filters
* @param {String} text see Filter()
* @param {String} regexpSource see RegExpFilter()
* @param {Number} contentType see RegExpFilter()
* @param {Boolean} matchCase see RegExpFilter()
* @param {String} domains see RegExpFilter()
* @param {Boolean} thirdParty see RegExpFilter()
* @constructor
* @augments RegExpFilter
*/
function WhitelistFilter(text, regexpSource, contentType, matchCase, domains, thirdParty)
{
RegExpFilter.call(this, text, regexpSource, contentType, matchCase, domains, thirdParty);
}
WhitelistFilter.prototype =
{
__proto__: RegExpFilter.prototype,
}
/**
* Class for element hiding filters
* @param {String} text see Filter()
* @param {String} domains (optional) Host names or domains the filter should be restricted to
* @param {String} selector CSS selector for the HTML elements that should be hidden
* @constructor
* @augments ActiveFilter
*/
function ElemHideFilter(text, domains, selector)
{
ActiveFilter.call(this, text, domains ? domains.toUpperCase() : null);
if (domains)
this.selectorDomain = domains.replace(/,~[^,]+/g, "").replace(/^~[^,]+,?/, "").toLowerCase();
this.selector = selector;
}
ElemHideFilter.prototype =
{
__proto__: ActiveFilter.prototype,
/**
* @see ActiveFilter.domainSeparator
*/
domainSeparator: ",",
/**
* Host name or domain the filter should be restricted to (can be null for no restriction)
* @type String
*/
selectorDomain: null,
/**
* CSS selector for the HTML elements that should be hidden
* @type String
*/
selector: null
};
/**
* Creates an element hiding filter from a pre-parsed text representation
*
* @param {String} text same as in Filter()
* @param {String} domain domain part of the text representation (can be empty)
* @param {String} tagName tag name part (can be empty)
* @param {String} attrRules attribute matching rules (can be empty)
* @param {String} selector raw CSS selector (can be empty)
* @return {ElemHideFilter or InvalidFilter}
*/
ElemHideFilter.fromText = function(text, domain, tagName, attrRules, selector)
{
if (!selector)
{
if (tagName == "*")
tagName = "";
let id = null;
let additional = "";
if (attrRules) {
attrRules = attrRules.match(/\([\w\-]+(?:[$^*]?=[^\(\)"]*)?\)/g);
for each (let rule in attrRules) {
rule = rule.substr(1, rule.length - 2);
let separatorPos = rule.indexOf("=");
if (separatorPos > 0) {
rule = rule.replace(/=/, '="') + '"';
additional += "[" + rule + "]";
}
else {
if (id)
return new InvalidFilter(text, Utils.getString("filter_elemhide_duplicate_id"));
else
id = rule;
}
}
}
if (id)
selector = tagName + "." + id + additional + "," + tagName + "#" + id + additional;
else if (tagName || additional)
selector = tagName + additional;
else
return new InvalidFilter(text, Utils.getString("filter_elemhide_nocriteria"));
}
return new ElemHideFilter(text, domain, selector);
}
+269
View File
@@ -0,0 +1,269 @@
/*
* This Source Code is subject to the terms of the Mozilla Public License
* version 2.0 (the "License"). You can obtain a copy of the License at
* http://mozilla.org/MPL/2.0/.
*/
#filter substitution
/**
* @fileOverview Component synchronizing filter storage with Matcher instances and ElemHide.
*/
var EXPORTED_SYMBOLS = ["FilterListener"];
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cr = Components.results;
const Cu = Components.utils;
let baseURL = "resource://@ADDON_CHROME_NAME@/modules/";
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import(baseURL + "TimeLine.jsm");
Cu.import(baseURL + "FilterStorage.jsm");
Cu.import(baseURL + "FilterNotifier.jsm");
Cu.import(baseURL + "ElemHide.jsm");
Cu.import(baseURL + "Matcher.jsm");
Cu.import(baseURL + "FilterClasses.jsm");
Cu.import(baseURL + "SubscriptionClasses.jsm");
Cu.import(baseURL + "Prefs.jsm");
Cu.import(baseURL + "Utils.jsm");
/**
* Value of the FilterListener.batchMode property.
* @type Boolean
*/
let batchMode = false;
/**
* Increases on filter changes, filters will be saved if it exceeds 1.
* @type Integer
*/
let isDirty = 0;
/**
* This object can be used to change properties of the filter change listeners.
* @class
*/
var FilterListener =
{
/**
* Called on module initialization, registers listeners for FilterStorage changes
*/
startup: function()
{
TimeLine.enter("Entered FilterListener.startup()");
FilterNotifier.addListener(function(action, item, newValue, oldValue)
{
let match = /^(\w+)\.(.*)/.exec(action);
if (match && match[1] == "filter")
onFilterChange(match[2], item, newValue, oldValue);
else if (match && match[1] == "subscription")
onSubscriptionChange(match[2], item, newValue, oldValue);
else
onGenericChange(action, item);
});
ElemHide.init();
FilterStorage.loadFromDisk();
TimeLine.log("done initializing data structures");
Services.obs.addObserver(FilterListenerPrivate, "browser:purge-session-history", true);
TimeLine.log("done adding observers");
TimeLine.leave("FilterListener.startup() done");
},
/**
* Set to true when executing many changes, changes will only be fully applied after this variable is set to false again.
* @type Boolean
*/
get batchMode()
{
return batchMode;
},
set batchMode(value)
{
batchMode = value;
flushElemHide();
},
/**
* Increases "dirty factor" of the filters and calls FilterStorage.saveToDisk()
* if it becomes 1 or more. Save is executed delayed to prevent multiple
* subsequent calls. If the parameter is 0 it forces saving filters if any
* changes were recorded after the previous save.
*/
setDirty: function(/**Integer*/ factor)
{
if (factor == 0 && isDirty > 0)
isDirty = 1;
else
isDirty += factor;
if (isDirty >= 1)
FilterStorage.saveToDisk();
}
};
/**
* Private nsIObserver implementation.
* @class
*/
var FilterListenerPrivate =
{
observe: function(subject, topic, data)
{
if (topic == "browser:purge-session-history" && Prefs.clearStatsOnHistoryPurge)
{
FilterStorage.resetHitCounts();
FilterListener.setDirty(0); // Force saving to disk
Prefs.recentReports = "[]";
}
},
QueryInterface: XPCOMUtils.generateQI([Ci.nsISupportsWeakReference, Ci.nsIObserver])
};
/**
* Calls ElemHide.apply() if necessary.
*/
function flushElemHide()
{
if (!batchMode && ElemHide.isDirty)
ElemHide.apply();
}
/**
* Notifies Matcher instances or ElemHide object about a new filter
* if necessary.
* @param {Filter} filter filter that has been added
*/
function addFilter(filter)
{
if (!(filter instanceof ActiveFilter) || filter.disabled)
return;
let hasEnabled = false;
for (let i = 0; i < filter.subscriptions.length; i++)
if (!filter.subscriptions[i].disabled)
hasEnabled = true;
if (!hasEnabled)
return;
if (filter instanceof RegExpFilter)
defaultMatcher.add(filter);
else if (filter instanceof ElemHideFilter)
ElemHide.add(filter);
}
/**
* Notifies Matcher instances or ElemHide object about removal of a filter
* if necessary.
* @param {Filter} filter filter that has been removed
*/
function removeFilter(filter)
{
if (!(filter instanceof ActiveFilter))
return;
if (!filter.disabled)
{
let hasEnabled = false;
for (let i = 0; i < filter.subscriptions.length; i++)
if (!filter.subscriptions[i].disabled)
hasEnabled = true;
if (hasEnabled)
return;
}
if (filter instanceof RegExpFilter)
defaultMatcher.remove(filter);
else if (filter instanceof ElemHideFilter)
ElemHide.remove(filter);
}
/**
* Subscription change listener
*/
function onSubscriptionChange(action, subscription, newValue, oldValue)
{
FilterListener.setDirty(1);
if (action != "added" && action != "removed" && action != "disabled" && action != "updated")
return;
if (action != "removed" && !(subscription.url in FilterStorage.knownSubscriptions))
{
// Ignore updates for subscriptions not in the list
return;
}
if ((action == "added" || action == "removed" || action == "updated") && subscription.disabled)
{
// Ignore adding/removing/updating of disabled subscriptions
return;
}
if (action == "added" || action == "removed" || action == "disabled")
{
let method = (action == "added" || (action == "disabled" && newValue == false) ? addFilter : removeFilter);
if (subscription.filters)
subscription.filters.forEach(method);
}
else if (action == "updated")
{
subscription.oldFilters.forEach(removeFilter);
subscription.filters.forEach(addFilter);
}
flushElemHide();
}
/**
* Filter change listener
*/
function onFilterChange(action, filter, newValue, oldValue)
{
if (action == "hitCount" || action == "lastHit")
FilterListener.setDirty(0.002);
else
FilterListener.setDirty(1);
if (action != "added" && action != "removed" && action != "disabled")
return;
if ((action == "added" || action == "removed") && filter.disabled)
{
// Ignore adding/removing of disabled filters
return;
}
if (action == "added" || (action == "disabled" && newValue == false))
addFilter(filter);
else
removeFilter(filter);
flushElemHide();
}
/**
* Generic notification listener
*/
function onGenericChange(action)
{
if (action == "load")
{
isDirty = 0;
defaultMatcher.clear();
ElemHide.clear();
for each (let subscription in FilterStorage.subscriptions)
if (!subscription.disabled)
subscription.filters.forEach(addFilter);
flushElemHide();
}
else if (action == "save")
isDirty = 0;
}
+70
View File
@@ -0,0 +1,70 @@
/*
* This Source Code is subject to the terms of the Mozilla Public License
* version 2.0 (the "License"). You can obtain a copy of the License at
* http://mozilla.org/MPL/2.0/.
*/
#filter substitution
/**
* @fileOverview FilterNotifier class manages listeners and distributes messages
* about filter changes to them.
*/
var EXPORTED_SYMBOLS = ["FilterNotifier"];
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cr = Components.results;
const Cu = Components.utils;
/**
* List of registered listeners
* @type Array of function(action, item, newValue, oldValue)
*/
let listeners = [];
/**
* This class allows registering and triggering listeners for filter events.
* @class
*/
var FilterNotifier =
{
/**
* Adds a listener
*/
addListener: function(/**function(action, item, newValue, oldValue)*/ listener)
{
if (listeners.indexOf(listener) >= 0)
return;
listeners.push(listener);
},
/**
* Removes a listener that was previosly added via addListener
*/
removeListener: function(/**function(action, item, newValue, oldValue)*/ listener)
{
let index = listeners.indexOf(listener);
if (index >= 0)
listeners.splice(index, 1);
},
/**
* Notifies listeners about an event
* @param {String} action event code ("load", "save", "elemhideupdate",
* "subscription.added", "subscription.removed",
* "subscription.disabled", "subscription.title",
* "subscription.lastDownload", "subscription.downloadStatus",
* "subscription.homepage", "subscription.updated",
* "filter.added", "filter.removed", "filter.moved",
* "filter.disabled", "filter.hitCount", "filter.lastHit")
* @param {Subscription|Filter} item item that the change applies to
*/
triggerListeners: function(action, item, param1, param2, param3)
{
for each (let listener in listeners)
listener(action, item, param1, param2, param3);
}
};
+776
View File
@@ -0,0 +1,776 @@
/*
* This Source Code is subject to the terms of the Mozilla Public License
* version 2.0 (the "License"). You can obtain a copy of the License at
* http://mozilla.org/MPL/2.0/.
*/
#filter substitution
/**
* @fileOverview FilterStorage class responsible to managing user's subscriptions and filters.
*/
var EXPORTED_SYMBOLS = ["FilterStorage"];
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cr = Components.results;
const Cu = Components.utils;
let baseURL = "resource://@ADDON_CHROME_NAME@/modules/";
Cu.import(baseURL + "Utils.jsm");
Cu.import(baseURL + "IO.jsm");
Cu.import(baseURL + "Prefs.jsm");
Cu.import(baseURL + "FilterClasses.jsm");
Cu.import(baseURL + "SubscriptionClasses.jsm");
Cu.import(baseURL + "FilterNotifier.jsm");
Cu.import(baseURL + "TimeLine.jsm");
/**
* Version number of the filter storage file format.
* @type Integer
*/
const formatVersion = 4;
/**
* This class reads user's filters from disk, manages them in memory and writes them back.
* @class
*/
var FilterStorage =
{
/**
* Version number of the patterns.ini format used.
* @type Integer
*/
get formatVersion() formatVersion,
/**
* File that the filter list has been loaded from and should be saved to
* @type nsIFile
*/
get sourceFile()
{
let file = null;
if (Prefs.patternsfile)
{
// Override in place, use it instead of placing the file in the regular data dir
file = IO.resolveFilePath(Prefs.patternsfile);
}
if (!file)
{
// Place the file in the data dir
file = IO.resolveFilePath(Prefs.data_directory);
if (file)
file.append("patterns.ini");
}
if (!file)
{
// Data directory pref misconfigured? Try the default value
try
{
file = IO.resolveFilePath(Prefs.defaultBranch.getCharPref("data_directory"));
if (file)
file.append("patterns.ini");
} catch(e) {}
}
if (!file)
Cu.reportError("Adblock Plus: Failed to resolve filter file location from extensions.@ADDON_CHROME_NAME@.patternsfile preference");
this.__defineGetter__("sourceFile", function() file);
return this.sourceFile;
},
/**
* Map of properties listed in the filter storage file before the sections
* start. Right now this should be only the format version.
*/
fileProperties: {__proto__: null},
/**
* List of filter subscriptions containing all filters
* @type Array of Subscription
*/
subscriptions: [],
/**
* Map of subscriptions already on the list, by their URL/identifier
* @type Object
*/
knownSubscriptions: {__proto__: null},
/**
* Finds the filter group that a filter should be added to by default. Will
* return null if this group doesn't exist yet.
*/
getGroupForFilter: function(/**Filter*/ filter) /**SpecialSubscription*/
{
let generalSubscription = null;
for each (let subscription in FilterStorage.subscriptions)
{
if (subscription instanceof SpecialSubscription && !subscription.disabled)
{
// Always prefer specialized subscriptions
if (subscription.isDefaultFor(filter))
return subscription;
// If this is a general subscription - store it as fallback
if (!generalSubscription && (!subscription.defaults || !subscription.defaults.length))
generalSubscription = subscription;
}
}
return generalSubscription;
},
/**
* Adds a filter subscription to the list
* @param {Subscription} subscription filter subscription to be added
* @param {Boolean} silent if true, no listeners will be triggered (to be used when filter list is reloaded)
*/
addSubscription: function(subscription, silent)
{
if (subscription.url in FilterStorage.knownSubscriptions)
return;
FilterStorage.subscriptions.push(subscription);
FilterStorage.knownSubscriptions[subscription.url] = subscription;
addSubscriptionFilters(subscription);
if (!silent)
FilterNotifier.triggerListeners("subscription.added", subscription);
},
/**
* Removes a filter subscription from the list
* @param {Subscription} subscription filter subscription to be removed
* @param {Boolean} silent if true, no listeners will be triggered (to be used when filter list is reloaded)
*/
removeSubscription: function(subscription, silent)
{
for (let i = 0; i < FilterStorage.subscriptions.length; i++)
{
if (FilterStorage.subscriptions[i].url == subscription.url)
{
removeSubscriptionFilters(subscription);
FilterStorage.subscriptions.splice(i--, 1);
delete FilterStorage.knownSubscriptions[subscription.url];
if (!silent)
FilterNotifier.triggerListeners("subscription.removed", subscription);
return;
}
}
},
/**
* Moves a subscription in the list to a new position.
* @param {Subscription} subscription filter subscription to be moved
* @param {Subscription} [insertBefore] filter subscription to insert before
* (if omitted the subscription will be put at the end of the list)
*/
moveSubscription: function(subscription, insertBefore)
{
let currentPos = FilterStorage.subscriptions.indexOf(subscription);
if (currentPos < 0)
return;
let newPos = insertBefore ? FilterStorage.subscriptions.indexOf(insertBefore) : -1;
if (newPos < 0)
newPos = FilterStorage.subscriptions.length;
if (currentPos < newPos)
newPos--;
if (currentPos == newPos)
return;
FilterStorage.subscriptions.splice(currentPos, 1);
FilterStorage.subscriptions.splice(newPos, 0, subscription);
FilterNotifier.triggerListeners("subscription.moved", subscription);
},
/**
* Replaces the list of filters in a subscription by a new list
* @param {Subscription} subscription filter subscription to be updated
* @param {Array of Filter} filters new filter lsit
*/
updateSubscriptionFilters: function(subscription, filters)
{
removeSubscriptionFilters(subscription);
subscription.oldFilters = subscription.filters;
subscription.filters = filters;
addSubscriptionFilters(subscription);
FilterNotifier.triggerListeners("subscription.updated", subscription);
delete subscription.oldFilters;
// Do not keep empty subscriptions disabled
if (subscription instanceof SpecialSubscription && !subscription.filters.length && subscription.disabled)
subscription.disabled = false;
},
/**
* Adds a user-defined filter to the list
* @param {Filter} filter
* @param {SpecialSubscription} [subscription] particular group that the filter should be added to
* @param {Integer} [position] position within the subscription at which the filter should be added
* @param {Boolean} silent if true, no listeners will be triggered (to be used when filter list is reloaded)
*/
addFilter: function(filter, subscription, position, silent)
{
if (!subscription)
{
if (filter.subscriptions.some(function(s) s instanceof SpecialSubscription && !s.disabled))
return; // No need to add
subscription = FilterStorage.getGroupForFilter(filter);
}
if (!subscription)
{
// No group for this filter exists, create one
subscription = SpecialSubscription.createForFilter(filter);
this.addSubscription(subscription);
return;
}
if (typeof position == "undefined")
position = subscription.filters.length;
if (filter.subscriptions.indexOf(subscription) < 0)
filter.subscriptions.push(subscription);
subscription.filters.splice(position, 0, filter);
if (!silent)
FilterNotifier.triggerListeners("filter.added", filter, subscription, position);
},
/**
* Removes a user-defined filter from the list
* @param {Filter} filter
* @param {SpecialSubscription} [subscription] a particular filter group that
* the filter should be removed from (if ommited will be removed from all subscriptions)
* @param {Integer} [position] position inside the filter group at which the
* filter should be removed (if ommited all instances will be removed)
*/
removeFilter: function(filter, subscription, position)
{
let subscriptions = (subscription ? [subscription] : filter.subscriptions.slice());
for (let i = 0; i < subscriptions.length; i++)
{
let subscription = subscriptions[i];
if (subscription instanceof SpecialSubscription)
{
let positions = [];
if (typeof position == "undefined")
{
let index = -1;
do
{
index = subscription.filters.indexOf(filter, index + 1);
if (index >= 0)
positions.push(index);
} while (index >= 0);
}
else
positions.push(position);
for (let j = positions.length - 1; j >= 0; j--)
{
let position = positions[j];
if (subscription.filters[position] == filter)
{
subscription.filters.splice(position, 1);
if (subscription.filters.indexOf(filter) < 0)
{
let index = filter.subscriptions.indexOf(subscription);
if (index >= 0)
filter.subscriptions.splice(index, 1);
}
FilterNotifier.triggerListeners("filter.removed", filter, subscription, position);
}
}
}
}
},
/**
* Moves a user-defined filter to a new position
* @param {Filter} filter
* @param {SpecialSubscription} subscription filter group where the filter is located
* @param {Integer} oldPosition current position of the filter
* @param {Integer} newPosition new position of the filter
*/
moveFilter: function(filter, subscription, oldPosition, newPosition)
{
if (!(subscription instanceof SpecialSubscription) || subscription.filters[oldPosition] != filter)
return;
newPosition = Math.min(Math.max(newPosition, 0), subscription.filters.length - 1);
if (oldPosition == newPosition)
return;
subscription.filters.splice(oldPosition, 1);
subscription.filters.splice(newPosition, 0, filter);
FilterNotifier.triggerListeners("filter.moved", filter, subscription, oldPosition, newPosition);
},
/**
* Increases the hit count for a filter by one
* @param {Filter} filter
*/
increaseHitCount: function(filter)
{
if (!Prefs.savestats || Prefs.privateBrowsing || !(filter instanceof ActiveFilter))
return;
filter.hitCount++;
filter.lastHit = Date.now();
},
/**
* Resets hit count for some filters
* @param {Array of Filter} filters filters to be reset, if null all filters will be reset
*/
resetHitCounts: function(filters)
{
if (!filters)
{
filters = [];
for each (let filter in Filter.knownFilters)
filters.push(filter);
}
for each (let filter in filters)
{
filter.hitCount = 0;
filter.lastHit = 0;
}
},
_loading: false,
/**
* Loads all subscriptions from the disk
* @param {nsIFile} [sourceFile] File to read from
*/
loadFromDisk: function(sourceFile)
{
if (this._loading)
return;
TimeLine.enter("Entered FilterStorage.loadFromDisk()");
let explicitFile = true;
if (!sourceFile)
{
sourceFile = FilterStorage.sourceFile;
explicitFile = false;
if (!sourceFile || !sourceFile.exists())
sourceFile = Utils.makeURI("resource://@ADDON_CHROME_NAME@/defaults/patterns.ini");
}
let readFile = function(sourceFile, backupIndex)
{
TimeLine.enter("FilterStorage.loadFromDisk() -> readFile()");
let parser = new INIParser();
IO.readFromFile(sourceFile, true, parser, function(e)
{
TimeLine.enter("FilterStorage.loadFromDisk() read callback");
if (!e && parser.subscriptions.length == 0)
{
// No filter subscriptions in the file, this isn't right.
e = new Error("No data in the file");
}
if (e)
Cu.reportError(e);
if (e && !explicitFile)
{
// Attempt to load a backup
sourceFile = this.sourceFile;
if (sourceFile)
{
let [, part1, part2] = /^(.*)(\.\w+)$/.exec(sourceFile.leafName) || [null, sourceFile.leafName, ""];
sourceFile = sourceFile.clone();
sourceFile.leafName = part1 + "-backup" + (++backupIndex) + part2;
if (sourceFile.exists())
{
readFile(sourceFile, backupIndex);
TimeLine.leave("FilterStorage.loadFromDisk() read callback done");
return;
}
}
}
// Old special groups might have been converted, remove them if they are empty
let specialMap = {"~il~": true, "~wl~": true, "~fl~": true, "~eh~": true};
let knownSubscriptions = {__proto__: null};
for (let i = 0; i < parser.subscriptions.length; i++)
{
let subscription = parser.subscriptions[i];
if (subscription instanceof SpecialSubscription && subscription.filters.length == 0 && subscription.url in specialMap)
parser.subscriptions.splice(i--, 1);
else
knownSubscriptions[subscription.url] = subscription;
}
this.fileProperties = parser.fileProperties;
this.subscriptions = parser.subscriptions;
this.knownSubscriptions = knownSubscriptions;
Filter.knownFilters = parser.knownFilters;
Subscription.knownSubscriptions = parser.knownSubscriptions;
if (parser.userFilters)
{
for (let i = 0; i < parser.userFilters.length; i++)
{
let filter = Filter.fromText(parser.userFilters[i]);
if (filter)
this.addFilter(filter, null, undefined, true);
}
}
TimeLine.log("Initializing data done, triggering observers")
this._loading = false;
FilterNotifier.triggerListeners("load");
if (sourceFile != this.sourceFile)
this.saveToDisk();
TimeLine.leave("FilterStorage.loadFromDisk() read callback done");
}.bind(this), "FilterStorageRead");
TimeLine.leave("FilterStorage.loadFromDisk() <- readFile()", "FilterStorageRead");
}.bind(this);
this._loading = true;
readFile(sourceFile, 0);
TimeLine.leave("FilterStorage.loadFromDisk() done");
},
_generateFilterData: function(subscriptions)
{
yield "# Adblock Plus preferences";
yield "version=" + formatVersion;
let saved = {__proto__: null};
let buf = [];
// Save filter data
for (let i = 0; i < subscriptions.length; i++)
{
let subscription = subscriptions[i];
for (let j = 0; j < subscription.filters.length; j++)
{
let filter = subscription.filters[j];
if (!(filter.text in saved))
{
filter.serialize(buf);
saved[filter.text] = filter;
for (let k = 0; k < buf.length; k++)
yield buf[k];
buf.splice(0);
}
}
}
// Save subscriptions
for (let i = 0; i < subscriptions.length; i++)
{
let subscription = subscriptions[i];
yield "";
subscription.serialize(buf);
if (subscription.filters.length)
{
buf.push("", "[Subscription filters]")
subscription.serializeFilters(buf);
}
for (let k = 0; k < buf.length; k++)
yield buf[k];
buf.splice(0);
}
},
/**
* Will be set to true if saveToDisk() is running (reentrance protection).
* @type Boolean
*/
_saving: false,
/**
* Will be set to true if a saveToDisk() call arrives while saveToDisk() is
* already running (delayed execution).
* @type Boolean
*/
_needsSave: false,
/**
* Saves all subscriptions back to disk
* @param {nsIFile} [targetFile] File to be written
*/
saveToDisk: function(targetFile)
{
let explicitFile = true;
if (!targetFile)
{
targetFile = FilterStorage.sourceFile;
explicitFile = false;
}
if (!targetFile)
return;
if (!explicitFile && this._saving)
{
this._needsSave = true;
return;
}
TimeLine.enter("Entered FilterStorage.saveToDisk()");
try {
targetFile.normalize();
} catch (e) {}
// Make sure the file's parent directory exists
try {
targetFile.parent.create(Ci.nsIFile.DIRECTORY_TYPE, 0755);
} catch (e) {}
let backupFileParts = null;
if (!explicitFile && targetFile.exists() && Prefs.patternsbackups > 0)
{
// Check whether we need to backup the file
let [, part1, part2] = /^(.*)(\.\w+)$/.exec(targetFile.leafName) || [null, targetFile.leafName, ""];
let newestBackup = targetFile.clone();
newestBackup.leafName = part1 + "-backup1" + part2;
if (!newestBackup.exists() || (Date.now() - newestBackup.lastModifiedTime) / 3600000 >= Prefs.patternsbackupinterval)
backupFileParts = [part1 + "-backup", part2];
}
let writeFilters = function()
{
TimeLine.enter("FilterStorage.saveToDisk() -> writeFilters()");
IO.writeToFile(targetFile, true, this._generateFilterData(subscriptions), function(e)
{
TimeLine.enter("FilterStorage.saveToDisk() write callback");
if (!explicitFile)
this._saving = false;
if (e)
reportError(e);
if (!explicitFile && this._needsSave)
{
this._needsSave = false;
this.saveToDisk();
}
else
FilterNotifier.triggerListeners("save");
TimeLine.leave("FilterStorage.saveToDisk() write callback done");
}.bind(this), "FilterStorageWrite");
TimeLine.leave("FilterStorage.saveToDisk() -> writeFilters()", "FilterStorageWrite");
}.bind(this);
let removeLastBackup = function()
{
TimeLine.enter("FilterStorage.saveToDisk() -> removeLastBackup()");
let file = targetFile.clone();
file.leafName = backupFileParts.join(Prefs.patternsbackups);
IO.removeFile(file, function(e) renameBackup(Prefs.patternsbackups - 1));
TimeLine.leave("FilterStorage.saveToDisk() <- removeLastBackup()");
}.bind(this);
let renameBackup = function(index)
{
TimeLine.enter("FilterStorage.saveToDisk() -> renameBackup()");
if (index > 0)
{
let fromFile = targetFile.clone();
fromFile.leafName = backupFileParts.join(index);
let toName = backupFileParts.join(index + 1);
IO.renameFile(fromFile, toName, function(e) renameBackup(index - 1));
}
else
{
let toFile = targetFile.clone();
toFile.leafName = backupFileParts.join(index + 1);
IO.copyFile(targetFile, toFile, writeFilters);
}
TimeLine.leave("FilterStorage.saveToDisk() <- renameBackup()");
}.bind(this);
// Do not persist external subscriptions
let subscriptions = this.subscriptions.filter(function(s) !(s instanceof ExternalSubscription));
if (!explicitFile)
this._saving = true;
if (backupFileParts)
removeLastBackup();
else
writeFilters();
TimeLine.leave("FilterStorage.saveToDisk() done");
},
/**
* Returns the list of existing backup files.
*/
getBackupFiles: function() /**nsIFile[]*/
{
let result = [];
let [, part1, part2] = /^(.*)(\.\w+)$/.exec(FilterStorage.sourceFile.leafName) || [null, FilterStorage.sourceFile.leafName, ""];
for (let i = 1; ; i++)
{
let file = FilterStorage.sourceFile.clone();
file.leafName = part1 + "-backup" + i + part2;
if (file.exists())
result.push(file);
else
break;
}
return result;
}
};
/**
* Joins subscription's filters to the subscription without any notifications.
* @param {Subscription} subscription filter subscription that should be connected to its filters
*/
function addSubscriptionFilters(subscription)
{
if (!(subscription.url in FilterStorage.knownSubscriptions))
return;
for each (let filter in subscription.filters)
filter.subscriptions.push(subscription);
}
/**
* Removes subscription's filters from the subscription without any notifications.
* @param {Subscription} subscription filter subscription to be removed
*/
function removeSubscriptionFilters(subscription)
{
if (!(subscription.url in FilterStorage.knownSubscriptions))
return;
for each (let filter in subscription.filters)
{
let i = filter.subscriptions.indexOf(subscription);
if (i >= 0)
filter.subscriptions.splice(i, 1);
}
}
/**
* IO.readFromFile() listener to parse filter data.
*/
function INIParser()
{
this.fileProperties = this.curObj = {};
this.subscriptions = [];
this.knownFilters = {__proto__: null};
this.knownSubscriptions = {__proto__: null};
}
INIParser.prototype =
{
subscriptions: null,
knownFilters: null,
knownSubscrptions : null,
wantObj: true,
fileProperties: null,
curObj: null,
curSection: null,
userFilters: null,
process: function(val)
{
let origKnownFilters = Filter.knownFilters;
Filter.knownFilters = this.knownFilters;
let origKnownSubscriptions = Subscription.knownSubscriptions;
Subscription.knownSubscriptions = this.knownSubscriptions;
let match;
try
{
if (this.wantObj === true && (match = /^(\w+)=(.*)$/.exec(val)))
this.curObj[match[1]] = match[2];
else if (val === null || (match = /^\s*\[(.+)\]\s*$/.exec(val)))
{
if (this.curObj)
{
// Process current object before going to next section
switch (this.curSection)
{
case "filter":
case "pattern":
if ("text" in this.curObj)
Filter.fromObject(this.curObj);
break;
case "subscription":
let subscription = Subscription.fromObject(this.curObj);
if (subscription)
this.subscriptions.push(subscription);
break;
case "subscription filters":
case "subscription patterns":
if (this.subscriptions.length)
{
let subscription = this.subscriptions[this.subscriptions.length - 1];
for each (let text in this.curObj)
{
let filter = Filter.fromText(text);
if (filter)
{
subscription.filters.push(filter);
filter.subscriptions.push(subscription);
}
}
}
break;
case "user patterns":
this.userFilters = this.curObj;
break;
}
}
if (val === null)
return;
this.curSection = match[1].toLowerCase();
switch (this.curSection)
{
case "filter":
case "pattern":
case "subscription":
this.wantObj = true;
this.curObj = {};
break;
case "subscription filters":
case "subscription patterns":
case "user patterns":
this.wantObj = false;
this.curObj = [];
break;
default:
this.wantObj = undefined;
this.curObj = null;
}
}
else if (this.wantObj === false && val)
this.curObj.push(val.replace(/\\\[/g, "["));
}
finally
{
Filter.knownFilters = origKnownFilters;
Subscription.knownSubscriptions = origKnownSubscriptions;
}
}
};
+317
View File
@@ -0,0 +1,317 @@
/*
* This Source Code is subject to the terms of the Mozilla Public License
* version 2.0 (the "License"). You can obtain a copy of the License at
* http://mozilla.org/MPL/2.0/.
*/
#filter substitution
/**
* @fileOverview Module containing file I/O helpers.
*/
var EXPORTED_SYMBOLS = ["IO"];
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cr = Components.results;
const Cu = Components.utils;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/FileUtils.jsm");
Cu.import("resource://gre/modules/NetUtil.jsm");
let baseURL = "resource://@ADDON_CHROME_NAME@/modules/";
Cu.import(baseURL + "TimeLine.jsm");
var IO =
{
/**
* Retrieves the platform-dependent line break string.
*/
get lineBreak()
{
let lineBreak = (Services.appinfo.OS == "WINNT" ? "\r\n" : "\n");
delete IO.lineBreak;
IO.__defineGetter__("lineBreak", function() lineBreak);
return IO.lineBreak;
},
/**
* Tries to interpret a file path as an absolute path or a path relative to
* user's profile. Returns a file or null on failure.
*/
resolveFilePath: function(/**String*/ path) /**nsIFile*/
{
if (!path)
return null;
try {
// Assume an absolute path first
return new FileUtils.File(path);
} catch (e) {}
try {
// Try relative path now
return FileUtils.getFile("ProfD", path.split("/"));
} catch (e) {}
return null;
},
/**
* Reads strings from a file asynchronously, calls listener.process() with
* each line read and with a null parameter once the read operation is done.
* The callback will be called when the operation is done.
*/
readFromFile: function(/**nsIFile|nsIURI*/ file, /**Boolean*/ decode, /**Object*/ listener, /**Function*/ callback, /**String*/ timeLineID)
{
try
{
let uri = file instanceof Ci.nsIFile ? Services.io.newFileURI(file) : file;
let channel = Services.io.newChannelFromURI(uri);
let converter = null;
if (decode)
{
converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"].createInstance(Ci.nsIScriptableUnicodeConverter);
converter.charset = "utf-8";
}
channel.asyncOpen({
buffer: "",
QueryInterface: XPCOMUtils.generateQI([Ci.nsIRequestObserver, Ci.nsIStreamListener]),
onStartRequest: function(request, context) {},
onDataAvailable: function(request, context, stream, offset, count)
{
if (timeLineID)
{
TimeLine.asyncStart(timeLineID);
}
let data = this.buffer + NetUtil.readInputStreamToString(stream, count);
let index = Math.max(data.lastIndexOf("\n"), data.lastIndexOf("\r"));
if (index >= 0)
{
this.buffer = data.substr(index + 1);
data = data.substr(0, index + 1);
if (converter)
data = converter.ConvertToUnicode(data);
let lines = data.split(/[\r\n]+/);
lines.pop();
for (let i = 0; i < lines.length; i++)
listener.process(lines[i]);
}
else
this.buffer = data;
if (timeLineID)
{
TimeLine.asyncEnd(timeLineID);
}
},
onStopRequest: function(request, context, result)
{
if (timeLineID)
{
TimeLine.asyncStart(timeLineID);
}
if (Components.isSuccessCode(result) && this.buffer.length)
listener.process(this.buffer);
listener.process(null);
if (timeLineID)
{
TimeLine.asyncEnd(timeLineID);
TimeLine.asyncDone(timeLineID);
}
if (!Components.isSuccessCode(result))
{
let e = Cc["@mozilla.org/js/xpc/Exception;1"].createInstance(Ci.nsIXPCException);
e.initialize("File read operation failed", result, null, Components.stack, file, null);
callback(e);
}
else
callback(null);
}
}, null);
}
catch (e)
{
callback(e);
}
},
/**
* Writes string data to a file asynchronously, optionally encodes it into
* UTF-8 first. The callback will be called when the write operation is done.
*/
writeToFile: function(/**nsIFile*/ file, /**Boolean*/ encode, /**Iterator*/ data, /**Function*/ callback, /**String*/ timeLineID)
{
try
{
let fileStream = FileUtils.openSafeFileOutputStream(file, FileUtils.MODE_WRONLY | FileUtils.MODE_CREATE | FileUtils.MODE_TRUNCATE);
let pipe = Cc["@mozilla.org/pipe;1"].createInstance(Ci.nsIPipe);
pipe.init(true, true, 0, 0x8000, null);
let outStream = pipe.outputStream;
if (encode)
{
outStream = Cc["@mozilla.org/intl/converter-output-stream;1"].createInstance(Ci.nsIConverterOutputStream);
outStream.init(pipe.outputStream, "UTF-8", 0, Ci.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER);
}
let copier = Cc["@mozilla.org/network/async-stream-copier;1"].createInstance(Ci.nsIAsyncStreamCopier);
copier.init(pipe.inputStream, fileStream, null, true, false, 0x8000, true, true);
copier.asyncCopy({
onStartRequest: function(request, context) {},
onStopRequest: function(request, context, result)
{
if (timeLineID)
{
TimeLine.asyncDone(timeLineID);
}
if (!Components.isSuccessCode(result))
{
let e = Cc["@mozilla.org/js/xpc/Exception;1"].createInstance(Ci.nsIXPCException);
e.initialize("File write operation failed", result, null, Components.stack, file, null);
callback(e);
}
else
callback(null);
}
}, null);
let lineBreak = this.lineBreak;
function writeNextChunk()
{
let buf = [];
bufLen = 0;
while (bufLen < 0x4000)
{
try
{
let str = data.next();
buf.push(str);
bufLen += str.length;
}
catch (e)
{
if (e instanceof StopIteration)
break;
else if (typeof e == "number")
pipe.outputStream.closeWithStatus(e);
else if (e instanceof Ci.nsIException)
pipe.outputStream.closeWithStatus(e.result);
else
{
Cu.reportError(e);
pipe.outputStream.closeWithStatus(Cr.NS_ERROR_FAILURE);
}
return;
}
}
pipe.outputStream.asyncWait({
onOutputStreamReady: function()
{
if (timeLineID)
{
TimeLine.asyncStart(timeLineID);
}
if (buf.length)
{
let str = buf.join(lineBreak) + lineBreak;
if (encode)
outStream.writeString(str);
else
outStream.write(str, str.length);
writeNextChunk();
}
else
outStream.close();
if (timeLineID)
{
TimeLine.asyncEnd(timeLineID);
}
}
}, 0, 0, Services.tm.currentThread);
}
writeNextChunk();
}
catch (e)
{
callback(e);
}
},
/**
* Copies a file asynchronously. The callback will be called when the copy
* operation is done.
*/
copyFile: function(/**nsIFile*/ fromFile, /**nsIFile*/ toFile, /**Function*/ callback)
{
try
{
let inStream = Cc["@mozilla.org/network/file-input-stream;1"].createInstance(Ci.nsIFileInputStream);
inStream.init(fromFile, FileUtils.MODE_RDONLY, 0, Ci.nsIFile.DEFER_OPEN);
let outStream = FileUtils.openFileOutputStream(toFile, FileUtils.MODE_WRONLY | FileUtils.MODE_CREATE | FileUtils.MODE_TRUNCATE);
NetUtil.asyncCopy(inStream, outStream, function(result)
{
if (!Components.isSuccessCode(result))
{
let e = Cc["@mozilla.org/js/xpc/Exception;1"].createInstance(Ci.nsIXPCException);
e.initialize("File write operation failed", result, null, Components.stack, file, null);
callback(e);
}
else
callback(null);
});
}
catch (e)
{
callback(e);
}
},
/**
* Renames a file within the same directory, will call callback when done.
*/
renameFile: function(/**nsIFile*/ fromFile, /**String*/ newName, /**Function*/ callback)
{
try
{
fromFile.moveTo(null, newName);
callback(null);
}
catch(e)
{
callback(e);
}
},
/**
* Removes a file, will call callback when done.
*/
removeFile: function(/**nsIFile*/ file, /**Function*/ callback)
{
try
{
file.remove(false);
callback(null);
}
catch(e)
{
callback(e);
}
}
}
+410
View File
@@ -0,0 +1,410 @@
/*
* This Source Code is subject to the terms of the Mozilla Public License
* version 2.0 (the "License"). You can obtain a copy of the License at
* http://mozilla.org/MPL/2.0/.
*/
#filter substitution
/**
* @fileOverview Matcher class implementing matching addresses against a list of filters.
*/
var EXPORTED_SYMBOLS = ["Matcher", "CombinedMatcher", "defaultMatcher"];
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cr = Components.results;
const Cu = Components.utils;
let baseURL = "resource://@ADDON_CHROME_NAME@/modules/";
Cu.import(baseURL + "FilterClasses.jsm");
/**
* Blacklist/whitelist filter matching
* @constructor
*/
function Matcher()
{
this.clear();
}
Matcher.prototype = {
/**
* Lookup table for filters by their associated keyword
* @type Object
*/
filterByKeyword: null,
/**
* Lookup table for keywords by the filter text
* @type Object
*/
keywordByFilter: null,
/**
* Removes all known filters
*/
clear: function()
{
this.filterByKeyword = {__proto__: null};
this.keywordByFilter = {__proto__: null};
},
/**
* Adds a filter to the matcher
* @param {RegExpFilter} filter
*/
add: function(filter)
{
if (filter.text in this.keywordByFilter)
return;
// Look for a suitable keyword
let keyword = this.findKeyword(filter);
let oldEntry = this.filterByKeyword[keyword];
if (typeof oldEntry == "undefined")
this.filterByKeyword[keyword] = filter;
else if (oldEntry.length == 1)
this.filterByKeyword[keyword] = [oldEntry, filter];
else
oldEntry.push(filter);
this.keywordByFilter[filter.text] = keyword;
},
/**
* Removes a filter from the matcher
* @param {RegExpFilter} filter
*/
remove: function(filter)
{
if (!(filter.text in this.keywordByFilter))
return;
let keyword = this.keywordByFilter[filter.text];
let list = this.filterByKeyword[keyword];
if (list.length <= 1)
delete this.filterByKeyword[keyword];
else
{
let index = list.indexOf(filter);
if (index >= 0)
{
list.splice(index, 1);
if (list.length == 1)
this.filterByKeyword[keyword] = list[0];
}
}
delete this.keywordByFilter[filter.text];
},
/**
* Chooses a keyword to be associated with the filter
* @param {String} text text representation of the filter
* @return {String} keyword (might be empty string)
*/
findKeyword: function(filter)
{
// For donottrack filters use "donottrack" as keyword if nothing else matches
let defaultResult = (filter.contentType & RegExpFilter.typeMap.DONOTTRACK ? "donottrack" : "");
let text = filter.text;
if (Filter.regexpRegExp.test(text))
return defaultResult;
// Remove options
let match = Filter.optionsRegExp.exec(text);
if (match)
text = match.input.substr(0, match.index);
// Remove whitelist marker
if (text.substr(0, 2) == "@@")
text = text.substr(2);
let candidates = text.toLowerCase().match(/[^a-z0-9%*][a-z0-9%]{3,}(?=[^a-z0-9%*])/g);
if (!candidates)
return defaultResult;
let hash = this.filterByKeyword;
let result = defaultResult;
let resultCount = 0xFFFFFF;
let resultLength = 0;
for (let i = 0, l = candidates.length; i < l; i++)
{
let candidate = candidates[i].substr(1);
let count = (candidate in hash ? hash[candidate].length : 0);
if (count < resultCount || (count == resultCount && candidate.length > resultLength))
{
result = candidate;
resultCount = count;
resultLength = candidate.length;
}
}
return result;
},
/**
* Checks whether a particular filter is being matched against.
*/
hasFilter: function(/**RegExpFilter*/ filter) /**Boolean*/
{
return (filter.text in this.keywordByFilter);
},
/**
* Returns the keyword used for a filter, null for unknown filters.
*/
getKeywordForFilter: function(/**RegExpFilter*/ filter) /**String*/
{
if (filter.text in this.keywordByFilter)
return this.keywordByFilter[filter.text];
else
return null;
},
/**
* Checks whether the entries for a particular keyword match a URL
*/
_checkEntryMatch: function(keyword, location, contentType, docDomain, thirdParty)
{
let list = this.filterByKeyword[keyword];
for (let i = 0; i < list.length; i++)
{
let filter = list[i];
if (filter.matches(location, contentType, docDomain, thirdParty))
return filter;
}
return null;
},
/**
* Tests whether the URL matches any of the known filters
* @param {String} location URL to be tested
* @param {String} contentType content type identifier of the URL
* @param {String} docDomain domain name of the document that loads the URL
* @param {Boolean} thirdParty should be true if the URL is a third-party request
* @return {RegExpFilter} matching filter or null
*/
matchesAny: function(location, contentType, docDomain, thirdParty)
{
let candidates = location.toLowerCase().match(/[a-z0-9%]{3,}/g);
if (candidates === null)
candidates = [];
if (contentType == "DONOTTRACK")
candidates.unshift("donottrack");
else
candidates.push("");
for (let i = 0, l = candidates.length; i < l; i++)
{
let substr = candidates[i];
if (substr in this.filterByKeyword)
{
let result = this._checkEntryMatch(substr, location, contentType, docDomain, thirdParty);
if (result)
return result;
}
}
return null;
}
};
/**
* Combines a matcher for blocking and exception rules, automatically sorts
* rules into two Matcher instances.
* @constructor
*/
function CombinedMatcher()
{
this.blacklist = new Matcher();
this.whitelist = new Matcher();
this.resultCache = {__proto__: null};
}
/**
* Maximal number of matching cache entries to be kept
* @type Number
*/
CombinedMatcher.maxCacheEntries = 1000;
CombinedMatcher.prototype =
{
/**
* Matcher for blocking rules.
* @type Matcher
*/
blacklist: null,
/**
* Matcher for exception rules.
* @type Matcher
*/
whitelist: null,
/**
* Lookup table of previous matchesAny results
* @type Object
*/
resultCache: null,
/**
* Number of entries in resultCache
* @type Number
*/
cacheEntries: 0,
/**
* @see Matcher#clear
*/
clear: function()
{
this.blacklist.clear();
this.whitelist.clear();
this.resultCache = {__proto__: null};
this.cacheEntries = 0;
},
/**
* @see Matcher#add
*/
add: function(filter)
{
if (filter instanceof WhitelistFilter)
{
this.whitelist.add(filter);
}
else
this.blacklist.add(filter);
if (this.cacheEntries > 0)
{
this.resultCache = {__proto__: null};
this.cacheEntries = 0;
}
},
/**
* @see Matcher#remove
*/
remove: function(filter)
{
if (filter instanceof WhitelistFilter)
{
this.whitelist.remove(filter);
}
else
this.blacklist.remove(filter);
if (this.cacheEntries > 0)
{
this.resultCache = {__proto__: null};
this.cacheEntries = 0;
}
},
/**
* @see Matcher#findKeyword
*/
findKeyword: function(filter)
{
if (filter instanceof WhitelistFilter)
return this.whitelist.findKeyword(filter);
else
return this.blacklist.findKeyword(filter);
},
/**
* @see Matcher#hasFilter
*/
hasFilter: function(filter)
{
if (filter instanceof WhitelistFilter)
return this.whitelist.hasFilter(filter);
else
return this.blacklist.hasFilter(filter);
},
/**
* @see Matcher#getKeywordForFilter
*/
getKeywordForFilter: function(filter)
{
if (filter instanceof WhitelistFilter)
return this.whitelist.getKeywordForFilter(filter);
else
return this.blacklist.getKeywordForFilter(filter);
},
/**
* Checks whether a particular filter is slow
*/
isSlowFilter: function(/**RegExpFilter*/ filter) /**Boolean*/
{
let matcher = (filter instanceof WhitelistFilter ? this.whitelist : this.blacklist);
if (matcher.hasFilter(filter))
return !matcher.getKeywordForFilter(filter);
else
return !matcher.findKeyword(filter);
},
/**
* Optimized filter matching testing both whitelist and blacklist matchers
* simultaneously. For parameters see Matcher.matchesAny().
* @see Matcher#matchesAny
*/
matchesAnyInternal: function(location, contentType, docDomain, thirdParty)
{
let candidates = location.toLowerCase().match(/[a-z0-9%]{3,}/g);
if (candidates === null)
candidates = [];
if (contentType == "DONOTTRACK")
candidates.unshift("donottrack");
else
candidates.push("");
let blacklistHit = null;
for (let i = 0, l = candidates.length; i < l; i++)
{
let substr = candidates[i];
if (substr in this.whitelist.filterByKeyword)
{
let result = this.whitelist._checkEntryMatch(substr, location, contentType, docDomain, thirdParty);
if (result)
return result;
}
if (substr in this.blacklist.filterByKeyword && blacklistHit === null)
blacklistHit = this.blacklist._checkEntryMatch(substr, location, contentType, docDomain, thirdParty);
}
return blacklistHit;
},
/**
* @see Matcher#matchesAny
*/
matchesAny: function(location, contentType, docDomain, thirdParty)
{
let key = location + " " + contentType + " " + docDomain + " " + thirdParty;
if (key in this.resultCache)
return this.resultCache[key];
let result = this.matchesAnyInternal(location, contentType, docDomain, thirdParty);
if (this.cacheEntries >= CombinedMatcher.maxCacheEntries)
{
this.resultCache = {__proto__: null};
this.cacheEntries = 0;
}
this.resultCache[key] = result;
this.cacheEntries++;
return result;
},
}
/**
* Shared CombinedMatcher instance that should usually be used.
* @type CombinedMatcher
*/
var defaultMatcher = new CombinedMatcher();
+504
View File
@@ -0,0 +1,504 @@
/*
* This Source Code is subject to the terms of the Mozilla Public License
* version 2.0 (the "License"). You can obtain a copy of the License at
* http://mozilla.org/MPL/2.0/.
*/
/**
* @fileOverview Code responsible for showing and hiding object tabs.
*/
#filter substitution
var EXPORTED_SYMBOLS = ["objectMouseEventHander"];
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cr = Components.results;
const Cu = Components.utils;
let baseURL = "resource://@ADDON_CHROME_NAME@/modules/";
Cu.import(baseURL + "Utils.jsm");
Cu.import(baseURL + "Prefs.jsm");
Cu.import(baseURL + "RequestNotifier.jsm");
// Run asynchronously to prevent cyclic module loads
Utils.runAsync(function()
{
Cu.import(baseURL + "ContentPolicy.jsm");
});
/**
* Class responsible for showing and hiding object tabs.
* @class
*/
var objTabs =
{
/**
* Number of milliseconds to wait until hiding tab after the mouse moves away.
* @type Integer
*/
HIDE_DELAY: 1000,
/**
* Flag used to trigger object tabs initialization first time object tabs are
* used.
* @type Boolean
*/
initialized: false,
/**
* Will be set to true while initialization is in progress.
* @type Boolean
*/
initializing: false,
/**
* Parameters for _showTab, to be called once initialization is complete.
*/
delayedShowParams: null,
/**
* Randomly generated class to be used for visible object tabs on top of object.
* @type String
*/
objTabClassVisibleTop: null,
/**
* Randomly generated class to be used for visible object tabs at the bottom of the object.
* @type String
*/
objTabClassVisibleBottom: null,
/**
* Randomly generated class to be used for invisible object tabs.
* @type String
*/
objTabClassHidden: null,
/**
* Document element the object tab is currently being displayed for.
* @type Element
*/
currentElement: null,
/**
* Windows that the window event handler is currently registered for.
* @type Array of Window
*/
windowListeners: null,
/**
* Panel element currently used as object tab.
* @type Element
*/
objtabElement: null,
/**
* Time of previous position update.
* @type Integer
*/
prevPositionUpdate: 0,
/**
* Timer used to update position of the object tab.
* @type nsITimer
*/
positionTimer: null,
/**
* Timer used to delay hiding of the object tab.
* @type nsITimer
*/
hideTimer: null,
/**
* Used when hideTimer is running, time when the tab should be hidden.
* @type Integer
*/
hideTargetTime: 0,
/**
* Initializes object tabs (generates random classes and registers stylesheet).
*/
_initCSS: function()
{
this.delayedShowParams = arguments;
if (!this.initializing)
{
this.initializing = true;
function processCSSData(data)
{
let rnd = [];
let offset = "a".charCodeAt(0);
for (let i = 0; i < 60; i++)
rnd.push(offset + Math.random() * 26);
this.objTabClassVisibleTop = String.fromCharCode.apply(String, rnd.slice(0, 20));
this.objTabClassVisibleBottom = String.fromCharCode.apply(String, rnd.slice(20, 40));
this.objTabClassHidden = String.fromCharCode.apply(String, rnd.slice(40, 60));
let url = Utils.makeURI("data:text/css," + encodeURIComponent(data.replace(/%%CLASSVISIBLETOP%%/g, this.objTabClassVisibleTop)
.replace(/%%CLASSVISIBLEBOTTOM%%/g, this.objTabClassVisibleBottom)
.replace(/%%CLASSHIDDEN%%/g, this.objTabClassHidden)));
Utils.styleService.loadAndRegisterSheet(url, Ci.nsIStyleSheetService.USER_SHEET);
this.initializing = false;
this.initialized = true;
if (this.delayedShowParams)
this._showTab.apply(this, this.delayedShowParams);
}
// Load CSS asynchronously
try {
let request = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].createInstance(Ci.nsIXMLHttpRequest);
request.open("GET", "chrome://@ADDON_CHROME_NAME@/content/objtabs.css");
request.overrideMimeType("text/plain");
let me = this;
request.addEventListener("load", function()
{
processCSSData.call(me, request.responseText);
}, false);
request.send(null);
}
catch (e)
{
Cu.reportError(e);
this.initializing = false;
}
}
},
/**
* Called to show object tab for an element.
*/
showTabFor: function(/**Element*/ element)
{
if (!Prefs.frameobjects)
return;
if (this.hideTimer)
{
this.hideTimer.cancel();
this.hideTimer = null;
}
if (this.objtabElement)
this.objtabElement.style.setProperty("opacity", "1", "important");
if (this.currentElement != element)
{
this._hideTab();
let data = RequestNotifier.getDataForNode(element, true, Policy.type.OBJECT);
if (data)
{
let hooks = this.getHooksForElement(element);
if (hooks)
{
if (this.initialized)
this._showTab(hooks, element, data[1]);
else
this._initCSS(hooks, element, data[1]);
}
}
}
},
/**
* Looks up the chrome window containing an element and returns abp-hooks
* element for this window if any.
*/
getHooksForElement: function(/**Element*/ element) /**Element*/
{
let doc = element.ownerDocument.defaultView
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation)
.QueryInterface(Ci.nsIDocShellTreeItem)
.rootTreeItem
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindow)
.document;
let hooks = doc.getElementById("abp-hooks");
if (hooks && hooks.wrappedJSObject)
hooks = hooks.wrappedJSObject;
return hooks;
},
/**
* Called to hide object tab for an element (actual hiding happens delayed).
*/
hideTabFor: function(/**Element*/ element)
{
if (element != this.currentElement || this.hideTimer)
return;
this.hideTargetTime = Date.now() + this.HIDE_DELAY;
this.hideTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
this.hideTimer.init(this, 40, Ci.nsITimer.TYPE_REPEATING_SLACK);
},
/**
* Makes the tab element visible.
*/
_showTab: function(/**Element*/ hooks, /**Element*/ element, /**RequestEntry*/ data)
{
let doc = element.ownerDocument.defaultView.top.document;
this.objtabElement = doc.createElementNS("http://www.w3.org/1999/xhtml", "a");
this.objtabElement.textContent = hooks.getAttribute("objtabtext");
this.objtabElement.setAttribute("title", hooks.getAttribute("objtabtooltip"));
this.objtabElement.setAttribute("href", data.location);
this.objtabElement.setAttribute("class", this.objTabClassHidden);
this.objtabElement.style.setProperty("opacity", "1", "important");
this.objtabElement.nodeData = data;
this.objtabElement.hooks = hooks;
this.currentElement = element;
// Register paint listeners for the relevant windows
this.windowListeners = [];
let wnd = element.ownerDocument.defaultView;
while (wnd)
{
wnd.addEventListener("MozAfterPaint", objectWindowEventHandler, false);
this.windowListeners.push(wnd);
wnd = (wnd.parent != wnd ? wnd.parent : null);
}
// Register mouse listeners on the object tab
this.objtabElement.addEventListener("mouseover", objectTabEventHander, false);
this.objtabElement.addEventListener("mouseout", objectTabEventHander, false);
this.objtabElement.addEventListener("click", objectTabEventHander, true);
// Insert the tab into the document and adjust its position
doc.documentElement.appendChild(this.objtabElement);
if (!this.positionTimer)
{
this.positionTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
this.positionTimer.init(this, 200, Ci.nsITimer.TYPE_REPEATING_SLACK);
}
this._positionTab();
},
/**
* Hides the tab element.
*/
_hideTab: function()
{
this.delayedShowParams = null;
if (this.objtabElement)
{
// Prevent recursive calls via popuphidden handler
let objtab = this.objtabElement;
this.objtabElement = null;
this.currentElement = null;
if (this.hideTimer)
{
this.hideTimer.cancel();
this.hideTimer = null;
}
if (this.positionTimer)
{
this.positionTimer.cancel();
this.positionTimer = null;
}
try {
objtab.parentNode.removeChild(objtab);
} catch (e) {}
objtab.removeEventListener("mouseover", objectTabEventHander, false);
objtab.removeEventListener("mouseout", objectTabEventHander, false);
objtab.nodeData = null;
for each (let wnd in this.windowListeners)
wnd.removeEventListener("MozAfterPaint", objectWindowEventHandler, false);
this.windowListeners = null;
}
},
/**
* Updates position of the tab element.
*/
_positionTab: function()
{
// Test whether element is still in document
let elementDoc = null;
try
{
elementDoc = this.currentElement.ownerDocument;
} catch (e) {} // Ignore "can't access dead object" error
if (!elementDoc || !this.currentElement.offsetWidth || !this.currentElement.offsetHeight ||
!elementDoc.defaultView || !elementDoc.documentElement)
{
this._hideTab();
return;
}
let objRect = this._getElementPosition(this.currentElement);
let className = this.objTabClassVisibleTop;
let left = objRect.right - this.objtabElement.offsetWidth;
let top = objRect.top - this.objtabElement.offsetHeight;
if (top < 0)
{
top = objRect.bottom;
className = this.objTabClassVisibleBottom;
}
if (this.objtabElement.style.left != left + "px")
this.objtabElement.style.setProperty("left", left + "px", "important");
if (this.objtabElement.style.top != top + "px")
this.objtabElement.style.setProperty("top", top + "px", "important");
if (this.objtabElement.getAttribute("class") != className)
this.objtabElement.setAttribute("class", className);
this.prevPositionUpdate = Date.now();
},
/**
* Calculates element's position relative to the top frame and considering
* clipping due to scrolling.
* @return {left: Number, top: Number, right: Number, bottom: Number}
*/
_getElementPosition: function(/**Element*/ element)
{
// Restrict rectangle coordinates by the boundaries of a window's client area
function intersectRect(rect, wnd)
{
// Cannot use wnd.innerWidth/Height because they won't account for scrollbars
let doc = wnd.document;
let wndWidth = doc.documentElement.clientWidth;
let wndHeight = doc.documentElement.clientHeight;
if (doc.compatMode == "BackCompat") // clientHeight will be bogus in quirks mode
wndHeight = Math.max(doc.documentElement.offsetHeight, doc.body.offsetHeight) - wnd.scrollMaxY - 1;
rect.left = Math.max(rect.left, 0);
rect.top = Math.max(rect.top, 0);
rect.right = Math.min(rect.right, wndWidth);
rect.bottom = Math.min(rect.bottom, wndHeight);
}
let rect = element.getBoundingClientRect();
let wnd = element.ownerDocument.defaultView;
let style = wnd.getComputedStyle(element, null);
let offsets = [
parseFloat(style.borderLeftWidth) + parseFloat(style.paddingLeft),
parseFloat(style.borderTopWidth) + parseFloat(style.paddingTop),
parseFloat(style.borderRightWidth) + parseFloat(style.paddingRight),
parseFloat(style.borderBottomWidth) + parseFloat(style.paddingBottom)
];
rect = {left: rect.left + offsets[0], top: rect.top + offsets[1],
right: rect.right - offsets[2], bottom: rect.bottom - offsets[3]};
while (true)
{
intersectRect(rect, wnd);
if (!wnd.frameElement)
break;
// Recalculate coordinates to be relative to frame's parent window
let frameElement = wnd.frameElement;
wnd = frameElement.ownerDocument.defaultView;
let frameRect = frameElement.getBoundingClientRect();
let frameStyle = wnd.getComputedStyle(frameElement, null);
let relLeft = frameRect.left + parseFloat(frameStyle.borderLeftWidth) + parseFloat(frameStyle.paddingLeft);
let relTop = frameRect.top + parseFloat(frameStyle.borderTopWidth) + parseFloat(frameStyle.paddingTop);
rect.left += relLeft;
rect.right += relLeft;
rect.top += relTop;
rect.bottom += relTop;
}
return rect;
},
doBlock: function()
{
Cu.import(baseURL + "AppIntegration.jsm");
let wrapper = AppIntegration.getWrapperForWindow(this.objtabElement.hooks.ownerDocument.defaultView);
if (wrapper)
wrapper.blockItem(this.currentElement, this.objtabElement.nodeData);
},
/**
* Called whenever a timer fires.
*/
observe: function(/**nsISupport*/ subject, /**String*/ topic, /**String*/ data)
{
if (subject == this.positionTimer)
{
// Don't update position if it was already updated recently (via MozAfterPaint)
if (Date.now() - this.prevPositionUpdate > 100)
this._positionTab();
}
else if (subject == this.hideTimer)
{
let now = Date.now();
if (now >= this.hideTargetTime)
this._hideTab();
else if (this.hideTargetTime - now < this.HIDE_DELAY / 2)
this.objtabElement.style.setProperty("opacity", (this.hideTargetTime - now) * 2 / this.HIDE_DELAY, "important");
}
}
};
/**
* Function called whenever the mouse enters or leaves an object.
*/
function objectMouseEventHander(/**Event*/ event)
{
if (!event.isTrusted)
return;
if (event.type == "mouseover")
objTabs.showTabFor(event.target);
else if (event.type == "mouseout")
objTabs.hideTabFor(event.target);
}
/**
* Function called for paint events of the object tab window.
*/
function objectWindowEventHandler(/**Event*/ event)
{
if (!event.isTrusted)
return;
// Don't trigger update too often, avoid overusing CPU on frequent page updates
if (event.type == "MozAfterPaint" && Date.now() - objTabs.prevPositionUpdate > 20)
objTabs._positionTab();
}
/**
* Function called whenever the mouse enters or leaves an object tab.
*/
function objectTabEventHander(/**Event*/ event)
{
if (!event.isTrusted)
return;
if (event.type == "click" && event.button == 0)
{
event.preventDefault();
event.stopPropagation();
objTabs.doBlock();
}
else if (event.type == "mouseover")
objTabs.showTabFor(objTabs.currentElement);
else if (event.type == "mouseout")
objTabs.hideTabFor(objTabs.currentElement);
}
+313
View File
@@ -0,0 +1,313 @@
/*
* This Source Code is subject to the terms of the Mozilla Public License
* version 2.0 (the "License"). You can obtain a copy of the License at
* http://mozilla.org/MPL/2.0/.
*/
#filter substitution
/**
* @fileOverview Manages Adblock Plus preferences.
*/
var EXPORTED_SYMBOLS = ["Prefs"];
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cr = Components.results;
const Cu = Components.utils;
let baseURL = "resource://@ADDON_CHROME_NAME@/modules/";
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import(baseURL + "TimeLine.jsm");
Cu.import(baseURL + "Utils.jsm");
const prefRoot = "extensions.@ADDON_CHROME_NAME@.";
/**
* Will be set to true if Adblock Plus is scheduled to be uninstalled on
* browser restart.
* @type Boolean
*/
let willBeUninstalled = false;
/**
* Preferences branch containing Adblock Plus preferences.
* @type nsIPrefBranch
*/
let branch = Utils.prefService.getBranch(prefRoot);
/**
* List of listeners to be notified whenever preferences are updated
* @type Array of Function
*/
let listeners = [];
/**
* This object allows easy access to Adblock Plus preferences, all defined
* preferences will be available as its members.
* @class
*/
var Prefs =
{
/**
* Will be set to true if the user enters private browsing mode.
* @type Boolean
*/
privateBrowsing: false,
/**
* Called on module startup.
*/
startup: function()
{
TimeLine.enter("Entered Prefs.startup()");
// Initialize prefs list
let defaultBranch = this.defaultBranch;
for each (let name in defaultBranch.getChildList("", {}))
{
let type = defaultBranch.getPrefType(name);
switch (type)
{
case Ci.nsIPrefBranch.PREF_INT:
defineIntegerProperty(name);
break;
case Ci.nsIPrefBranch.PREF_BOOL:
defineBooleanProperty(name);
break;
case Ci.nsIPrefBranch.PREF_STRING:
defineStringProperty(name);
break;
}
if ("_update_" + name in PrefsPrivate)
PrefsPrivate["_update_" + name]();
}
// Always disable object tabs in Fennec, they aren't usable
if (Utils.isFennec)
Prefs.frameobjects = false;
TimeLine.log("done loading initial values");
// Register observers
TimeLine.log("registering observers");
registerObservers();
TimeLine.leave("Prefs.startup() done");
},
/**
* Backwards compatibility, this pref is optional
*/
get patternsfile() /**String*/
{
let result = null;
try
{
result = branch.getCharPref("patternsfile");
} catch(e) {}
this.__defineGetter__("patternsfile", function() result);
return this.patternsfile;
},
/**
* Retrieves the preferences branch containing default preference values.
*/
get defaultBranch() /**nsIPreferenceBranch*/
{
return Utils.prefService.getDefaultBranch(prefRoot);
},
/**
* Called on module shutdown.
*/
shutdown: function()
{
TimeLine.enter("Entered Prefs.shutdown()");
if (willBeUninstalled)
{
// Make sure that a new installation after uninstall will be treated like
// an update.
try {
branch.clearUserPref("currentVersion");
} catch(e) {}
}
TimeLine.leave("Prefs.shutdown() done");
},
/**
* Adds a preferences listener that will be fired whenever preferences are
* reloaded
*/
addListener: function(/**Function*/ listener)
{
let index = listeners.indexOf(listener);
if (index < 0)
listeners.push(listener);
},
/**
* Removes a preferences listener
*/
removeListener: function(/**Function*/ listener)
{
let index = listeners.indexOf(listener);
if (index >= 0)
listeners.splice(index, 1);
}
};
/**
* Private nsIObserver implementation
* @class
*/
var PrefsPrivate =
{
/**
* If set to true notifications about preference changes will no longer cause
* a reload. This is to prevent unnecessary reloads while saving.
* @type Boolean
*/
ignorePrefChanges: false,
/**
* nsIObserver implementation
*/
observe: function(subject, topic, data)
{
if (topic == "private-browsing")
{
if (data == "enter")
Prefs.privateBrowsing = true;
else if (data == "exit")
Prefs.privateBrowsing = false;
}
else if (topic == "em-action-requested")
{
if (subject instanceof Ci.nsIUpdateItem && subject.id == Utils.addonID)
willBeUninstalled = (data == "item-uninstalled");
}
else if (topic == "nsPref:changed" && !this.ignorePrefChanges && "_update_" + data in PrefsPrivate)
PrefsPrivate["_update_" + data]();
},
QueryInterface: XPCOMUtils.generateQI([Ci.nsISupportsWeakReference, Ci.nsIObserver])
}
/**
* Adds observers to keep various properties of Prefs object updated.
*/
function registerObservers()
{
// Observe preferences changes
try {
branch.QueryInterface(Ci.nsIPrefBranchInternal)
.addObserver("", PrefsPrivate, true);
}
catch (e) {
Cu.reportError(e);
}
Services.obs.addObserver(PrefsPrivate, "em-action-requested", true);
// Add Private Browsing observer
if ("@mozilla.org/privatebrowsing;1" in Cc)
{
try
{
Prefs.privateBrowsing = Cc["@mozilla.org/privatebrowsing;1"].getService(Ci.nsIPrivateBrowsingService).privateBrowsingEnabled;
Services.obs.addObserver(PrefsPrivate, "private-browsing", true);
}
catch(e)
{
Cu.reportError(e);
}
}
}
/**
* Triggers preference listeners whenever a preference is changed.
*/
function triggerListeners(/**String*/ name)
{
for each (let listener in listeners)
listener(name);
}
/**
* Sets up getter/setter on Prefs object for preference.
*/
function defineProperty(/**String*/ name, defaultValue, /**Function*/ readFunc, /**Function*/ writeFunc)
{
let value = defaultValue;
PrefsPrivate["_update_" + name] = function()
{
try
{
value = readFunc();
triggerListeners(name);
}
catch(e)
{
Cu.reportError(e);
}
}
Prefs.__defineGetter__(name, function() value);
Prefs.__defineSetter__(name, function(newValue)
{
if (value == newValue)
return value;
try
{
PrefsPrivate.ignorePrefChanges = true;
writeFunc(newValue);
value = newValue;
triggerListeners(name);
}
catch(e)
{
Cu.reportError(e);
}
finally
{
PrefsPrivate.ignorePrefChanges = false;
}
return value;
});
}
/**
* Sets up getter/setter on Prefs object for an integer preference.
*/
function defineIntegerProperty(/**String*/ name)
{
defineProperty(name, 0, function() branch.getIntPref(name),
function(newValue) branch.setIntPref(name, newValue));
}
/**
* Sets up getter/setter on Prefs object for a boolean preference.
*/
function defineBooleanProperty(/**String*/ name)
{
defineProperty(name, false, function() branch.getBoolPref(name),
function(newValue) branch.setBoolPref(name, newValue));
}
/**
* Sets up getter/setter on Prefs object for a string preference.
*/
function defineStringProperty(/**String*/ name)
{
defineProperty(name, "", function() branch.getComplexValue(name, Ci.nsISupportsString).data,
function(newValue)
{
let str = Cc["@mozilla.org/supports-string;1"].createInstance(Ci.nsISupportsString);
str.data = newValue;
branch.setComplexValue(name, Ci.nsISupportsString, str);
});
}
+185
View File
@@ -0,0 +1,185 @@
/*
* This Source Code is subject to the terms of the Mozilla Public License
* version 2.0 (the "License"). You can obtain a copy of the License at
* http://mozilla.org/MPL/2.0/.
*/
#filter substitution
/**
* @fileOverview Public Adblock Plus API.
*/
var EXPORTED_SYMBOLS = ["AdblockPlus"];
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cr = Components.results;
const Cu = Components.utils;
let baseURL = "resource://@ADDON_CHROME_NAME@/modules/";
Cu.import(baseURL + "Utils.jsm");
Cu.import(baseURL + "FilterStorage.jsm");
Cu.import(baseURL + "FilterClasses.jsm");
Cu.import(baseURL + "SubscriptionClasses.jsm");
const externalPrefix = "~external~";
/**
* Class implementing public Adblock Plus API
* @class
*/
var AdblockPlus =
{
/**
* Returns current subscription count
* @type Integer
*/
get subscriptionCount()
{
return FilterStorage.subscriptions.length;
},
/**
* Gets a subscription by its URL
*/
getSubscription: function(/**String*/ id) /**IAdblockPlusSubscription*/
{
if (id in FilterStorage.knownSubscriptions)
return createSubscriptionWrapper(FilterStorage.knownSubscriptions[id]);
return null;
},
/**
* Gets a subscription by its position in the list
*/
getSubscriptionAt: function(/**Integer*/ index) /**IAdblockPlusSubscription*/
{
if (index < 0 || index >= FilterStorage.subscriptions.length)
return null;
return createSubscriptionWrapper(FilterStorage.subscriptions[index]);
},
/**
* Updates an external subscription and creates it if necessary
*/
updateExternalSubscription: function(/**String*/ id, /**String*/ title, /**Array of Filter*/ filters) /**String*/
{
if (id.substr(0, externalPrefix.length) != externalPrefix)
id = externalPrefix + id;
let subscription = Subscription.fromURL(id);
if (!subscription)
subscription = new ExternalSubscription(id, title);
subscription.lastDownload = parseInt(new Date().getTime() / 1000);
let newFilters = [];
for each (let filter in filters)
{
filter = Filter.fromText(Filter.normalize(filter));
if (filter)
newFilters.push(filter);
}
if (id in FilterStorage.knownSubscriptions)
FilterStorage.updateSubscriptionFilters(subscription, newFilters);
else
{
subscription.filters = newFilters;
FilterStorage.addSubscription(subscription);
}
return id;
},
/**
* Removes an external subscription by its identifier
*/
removeExternalSubscription: function(/**String*/ id) /**Boolean*/
{
if (id.substr(0, externalPrefix.length) != externalPrefix)
id = externalPrefix + id;
if (!(id in FilterStorage.knownSubscriptions))
return false;
FilterStorage.removeSubscription(FilterStorage.knownSubscriptions[id]);
return true;
},
/**
* Adds user-defined filters to the list
*/
addPatterns: function(/**Array of String*/ filters)
{
for each (let filter in filters)
{
filter = Filter.fromText(Filter.normalize(filter));
if (filter)
{
filter.disabled = false;
FilterStorage.addFilter(filter);
}
}
},
/**
* Removes user-defined filters from the list
*/
removePatterns: function(/**Array of String*/ filters)
{
for each (let filter in filters)
{
filter = Filter.fromText(Filter.normalize(filter));
if (filter)
FilterStorage.removeFilter(filter);
}
},
/**
* Returns installed Adblock Plus version
*/
getInstalledVersion: function() /**String*/
{
return Utils.addonVersion;
},
/**
* Returns source code revision this Adblock Plus build was created from (if available)
*/
getInstalledBuild: function() /**String*/
{
return Utils.addonBuild;
},
};
/**
* Wraps a subscription into IAdblockPlusSubscription structure.
*/
function createSubscriptionWrapper(/**Subscription*/ subscription) /**IAdblockPlusSubscription*/
{
if (!subscription)
return null;
return {
url: subscription.url,
special: subscription instanceof SpecialSubscription,
title: subscription.title,
autoDownload: true,
disabled: subscription.disabled,
external: subscription instanceof ExternalSubscription,
lastDownload: subscription instanceof RegularSubscription ? subscription.lastDownload : 0,
downloadStatus: subscription instanceof DownloadableSubscription ? subscription.downloadStatus : "synchronize_ok",
lastModified: subscription instanceof DownloadableSubscription ? subscription.lastModified : null,
expires: subscription instanceof DownloadableSubscription ? subscription.expires : 0,
getPatterns: function()
{
let result = subscription.filters.map(function(filter)
{
return filter.text;
});
return result;
}
};
}
+346
View File
@@ -0,0 +1,346 @@
/*
* This Source Code is subject to the terms of the Mozilla Public License
* version 2.0 (the "License"). You can obtain a copy of the License at
* http://mozilla.org/MPL/2.0/.
*/
#filter substitution
/**
* @fileOverview Stores Adblock Plus data to be attached to a window.
*/
var EXPORTED_SYMBOLS = ["RequestNotifier"];
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cr = Components.results;
const Cu = Components.utils;
let baseURL = "resource://@ADDON_CHROME_NAME@/modules/";
Cu.import(baseURL + "Utils.jsm");
Cu.import(baseURL + "FilterClasses.jsm");
Utils.runAsync(Cu.import, Cu, baseURL + "ContentPolicy.jsm"); // delay to avoid circular imports
let nodeData = new WeakMap();
let windowStats = new WeakMap();
let windowSelection = new WeakMap();
/**
* List of notifiers in use - these notifiers need to receive notifications on
* new requests.
* @type RequestNotifier[]
*/
let activeNotifiers = [];
/**
* Creates a notifier object for a particular window. After creation the window
* will first be scanned for previously saved requests. Once that scan is
* complete only new requests for this window will be reported.
* @param {Window} wnd window to attach the notifier to
* @param {Function} listener listener to be called whenever a new request is found
* @param {Object} [listenerObj] "this" pointer to be used when calling the listener
*/
function RequestNotifier(wnd, listener, listenerObj)
{
this.window = wnd;
this.listener = listener;
this.listenerObj = listenerObj || null;
activeNotifiers.push(this);
if (wnd)
this.startScan(wnd);
else
this.scanComplete = true;
}
RequestNotifier.prototype =
{
/**
* The window this notifier is associated with.
* @type Window
*/
window: null,
/**
* The listener to be called when a new request is found.
* @type Function
*/
listener: null,
/**
* "this" pointer to be used when calling the listener.
* @type Object
*/
listenerObj: null,
/**
* Will be set to true once the initial window scan is complete.
* @type Boolean
*/
scanComplete: false,
/**
* Shuts down the notifier once it is no longer used. The listener
* will no longer be called after that.
*/
shutdown: function()
{
delete this.window;
delete this.listener;
delete this.listenerObj;
for (let i = activeNotifiers.length - 1; i >= 0; i--)
if (activeNotifiers[i] == this)
activeNotifiers.splice(i, 1);
},
/**
* Notifies listener about a new request.
*/
notifyListener: function(/**Window*/ wnd, /**Node*/ node, /**RequestEntry*/ entry)
{
this.listener.call(this.listenerObj, wnd, node, entry, this.scanComplete);
},
/**
* Number of currently posted scan events (will be 0 when the scan finishes
* running).
*/
eventsPosted: 0,
/**
* Starts the initial scan of the window (will recurse into frames).
* @param {Window} wnd the window to be scanned
*/
startScan: function(wnd)
{
let currentThread = Utils.threadManager.currentThread;
let doc = wnd.document;
let walker = doc.createTreeWalker(doc, Ci.nsIDOMNodeFilter.SHOW_ELEMENT, null, false);
let runnable =
{
notifier: null,
run: function()
{
if (!this.notifier.listener)
return;
let node = walker.currentNode;
let data = nodeData.get(node);
if (typeof data != "undefined")
for (let i = data.length - 1; i >= 0; i--)
this.notifier.notifyListener(wnd, node, data[i]);
if (walker.nextNode())
currentThread.dispatch(runnable, Ci.nsIEventTarget.DISPATCH_NORMAL);
else
{
// Done with the current window, start the scan for its frames
for (let i = 0; i < wnd.frames.length; i++)
this.notifier.startScan(wnd.frames[i]);
this.notifier.eventsPosted--;
if (!this.notifier.eventsPosted)
{
this.notifier.scanComplete = true;
this.notifier.notifyListener(wnd, null, null);
}
this.notifier = null;
}
}
};
runnable.notifier = this;
// Process each node in a separate event on current thread to allow other
// events to process
this.eventsPosted++;
currentThread.dispatch(runnable, Ci.nsIEventTarget.DISPATCH_NORMAL);
}
};
RequestNotifier.storeSelection = function(/**Window*/ wnd, /**String*/ selection)
{
windowSelection.set(wnd, selection);
};
RequestNotifier.getSelection = function(/**Window*/ wnd) /**String*/
{
if (windowSelection.has(wnd))
return windowSelection.get(wnd);
else
return null;
};
/**
* Attaches request data to a DOM node.
* @param {Node} node node to attach data to
* @param {Window} topWnd top-level window the node belongs to
* @param {Integer} contentType request type, one of the Policy.type.* constants
* @param {String} docDomain domain of the document that initiated the request
* @param {Boolean} thirdParty will be true if a third-party server has been requested
* @param {String} location the address that has been requested
* @param {Filter} filter filter applied to the request or null if none
*/
RequestNotifier.addNodeData = function(/**Node*/ node, /**Window*/ topWnd, /**Integer*/ contentType, /**String*/ docDomain, /**Boolean*/ thirdParty, /**String*/ location, /**Filter*/ filter)
{
return new RequestEntry(node, topWnd, contentType, docDomain, thirdParty, location, filter);
}
/**
* Retrieves the statistics for a window.
* @result {Object} Object with the properties items, blocked, whitelisted, hidden, filters containing statistics for the window (might be null)
*/
RequestNotifier.getWindowStatistics = function(/**Window*/ wnd)
{
if (windowStats.has(wnd.document))
return windowStats.get(wnd.document);
else
return null;
}
/**
* Retrieves the request entry associated with a DOM node.
* @param {Node} node
* @param {Boolean} noParent if missing or false, the search will extend to the parent nodes until one is found that has data associated with it
* @param {Integer} [type] request type to be looking for
* @param {String} [location] request location to be looking for
* @result {[Node, RequestEntry]}
* @static
*/
RequestNotifier.getDataForNode = function(node, noParent, type, location)
{
while (node)
{
let data = nodeData.get(node);
if (typeof data != "undefined")
{
// Look for matching entry starting at the end of the list (most recent first)
for (let i = data.length - 1; i >= 0; i--)
{
let entry = data[i];
if ((typeof type == "undefined" || entry.type == type) &&
(typeof location == "undefined" || entry.location == location))
{
return [node, entry];
}
}
}
// If we don't have any match on this node then maybe its parent will do
if ((typeof noParent != "boolean" || !noParent) &&
node.parentNode instanceof Ci.nsIDOMElement)
{
node = node.parentNode;
}
else
{
node = null;
}
}
return null;
};
function RequestEntry(node, topWnd, contentType, docDomain, thirdParty, location, filter)
{
this.type = contentType;
this.docDomain = docDomain;
this.thirdParty = thirdParty;
this.location = location;
this.filter = filter;
this.attachToNode(node);
// Update window statistics
if (!windowStats.has(topWnd.document))
{
windowStats.set(topWnd.document, {
items: 0,
hidden: 0,
blocked: 0,
whitelisted: 0,
filters: {}
});
}
let stats = windowStats.get(topWnd.document);
if (filter && filter instanceof ElemHideFilter)
stats.hidden++;
else
stats.items++;
if (filter)
{
if (filter instanceof BlockingFilter)
stats.blocked++;
else if (filter instanceof WhitelistFilter)
stats.whitelisted++;
if (filter.text in stats.filters)
stats.filters[filter.text]++;
else
stats.filters[filter.text] = 1;
}
// Notify listeners
for each (let notifier in activeNotifiers)
if (!notifier.window || notifier.window == topWnd)
notifier.notifyListener(topWnd, node, this);
}
RequestEntry.prototype =
{
/**
* Content type of the request (one of the nsIContentPolicy constants)
* @type Integer
*/
type: null,
/**
* Domain name of the requesting document
* @type String
*/
docDomain: null,
/**
* True if the request goes to a different domain than the domain of the containing document
* @type Boolean
*/
thirdParty: false,
/**
* Address being requested
* @type String
*/
location: null,
/**
* Filter that was applied to this request (if any)
* @type Filter
*/
filter: null,
/**
* String representation of the content type, e.g. "subdocument"
* @type String
*/
get typeDescr() Policy.typeDescr[this.type],
/**
* User-visible localized representation of the content type, e.g. "frame"
* @type String
*/
get localizedDescr() Policy.localizedDescr[this.type],
/**
* Attaches this request object to a DOM node.
*/
attachToNode: function(/**Node*/ node)
{
let existingData = nodeData.get(node);
if (typeof existingData != "undefined")
{
// Add the new entry to the existing data
existingData.push(this);
}
else
{
// Associate the node with a new array
nodeData.set(node, [this]);
}
}
};
+560
View File
@@ -0,0 +1,560 @@
/*
* This Source Code is subject to the terms of the Mozilla Public License
* version 2.0 (the "License"). You can obtain a copy of the License at
* http://mozilla.org/MPL/2.0/.
*/
#filter substitution
/**
* @fileOverview Definition of Subscription class and its subclasses.
*/
var EXPORTED_SYMBOLS = ["Subscription", "SpecialSubscription", "RegularSubscription", "ExternalSubscription", "DownloadableSubscription"];
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cr = Components.results;
const Cu = Components.utils;
let baseURL = "resource://@ADDON_CHROME_NAME@/modules/";
Cu.import(baseURL + "Utils.jsm");
Cu.import(baseURL + "FilterClasses.jsm");
Cu.import(baseURL + "FilterNotifier.jsm");
/**
* Abstract base class for filter subscriptions
*
* @param {String} url download location of the subscription
* @param {String} [title] title of the filter subscription
* @constructor
*/
function Subscription(url, title)
{
this.url = url;
this.filters = [];
this._title = title || Utils.getString("newGroup_title");
Subscription.knownSubscriptions[url] = this;
}
Subscription.prototype =
{
/**
* Download location of the subscription
* @type String
*/
url: null,
/**
* Filters contained in the filter subscription
* @type Array of Filter
*/
filters: null,
_title: null,
_fixedTitle: false,
_disabled: false,
/**
* Title of the filter subscription
* @type String
*/
get title() this._title,
set title(value)
{
if (value != this._title)
{
let oldValue = this._title;
this._title = value;
FilterNotifier.triggerListeners("subscription.title", this, value, oldValue);
}
return this._title;
},
/**
* Determines whether the title should be editable
* @type Boolean
*/
get fixedTitle() this._fixedTitle,
set fixedTitle(value)
{
if (value != this._fixedTitle)
{
let oldValue = this._fixedTitle;
this._fixedTitle = value;
FilterNotifier.triggerListeners("subscription.fixedTitle", this, value, oldValue);
}
return this._fixedTitle;
},
/**
* Defines whether the filters in the subscription should be disabled
* @type Boolean
*/
get disabled() this._disabled,
set disabled(value)
{
if (value != this._disabled)
{
let oldValue = this._disabled;
this._disabled = value;
FilterNotifier.triggerListeners("subscription.disabled", this, value, oldValue);
}
return this._disabled;
},
/**
* Serializes the filter to an array of strings for writing out on the disk.
* @param {Array of String} buffer buffer to push the serialization results into
*/
serialize: function(buffer)
{
buffer.push("[Subscription]");
buffer.push("url=" + this.url);
buffer.push("title=" + this._title);
if (this._fixedTitle)
buffer.push("fixedTitle=true");
if (this._disabled)
buffer.push("disabled=true");
},
serializeFilters: function(buffer)
{
for each (let filter in this.filters)
buffer.push(filter.text.replace(/\[/g, "\\["));
},
toString: function()
{
let buffer = [];
this.serialize(buffer);
return buffer.join("\n");
}
};
/**
* Cache for known filter subscriptions, maps URL to subscription objects.
* @type Object
*/
Subscription.knownSubscriptions = {__proto__: null};
/**
* Returns a subscription from its URL, creates a new one if necessary.
* @param {String} url URL of the subscription
* @return {Subscription} subscription or null if the subscription couldn't be created
*/
Subscription.fromURL = function(url)
{
if (url in Subscription.knownSubscriptions)
return Subscription.knownSubscriptions[url];
try
{
// Test URL for validity
url = Utils.ioService.newURI(url, null, null).spec;
return new DownloadableSubscription(url, null);
}
catch (e)
{
return new SpecialSubscription(url);
}
}
/**
* Deserializes a subscription
*
* @param {Object} obj map of serialized properties and their values
* @return {Subscription} subscription or null if the subscription couldn't be created
*/
Subscription.fromObject = function(obj)
{
let result;
try
{
obj.url = Utils.ioService.newURI(obj.url, null, null).spec;
// URL is valid - this is a downloadable subscription
result = new DownloadableSubscription(obj.url, obj.title);
if ("nextURL" in obj)
result.nextURL = obj.nextURL;
if ("downloadStatus" in obj)
result._downloadStatus = obj.downloadStatus;
if ("lastModified" in obj)
result.lastModified = obj.lastModified;
if ("lastSuccess" in obj)
result.lastSuccess = parseInt(obj.lastSuccess) || 0;
if ("lastCheck" in obj)
result._lastCheck = parseInt(obj.lastCheck) || 0;
if ("expires" in obj)
result.expires = parseInt(obj.expires) || 0;
if ("softExpiration" in obj)
result.softExpiration = parseInt(obj.softExpiration) || 0;
if ("errors" in obj)
result._errors = parseInt(obj.errors) || 0;
if ("requiredVersion" in obj)
{
result.requiredVersion = obj.requiredVersion;
if (Utils.versionComparator.compare(result.requiredVersion, "3.5.0") > 0)
result.upgradeRequired = true;
}
if ("alternativeLocations" in obj)
result.alternativeLocations = obj.alternativeLocations;
if ("homepage" in obj)
result._homepage = obj.homepage;
if ("lastDownload" in obj)
result._lastDownload = parseInt(obj.lastDownload) || 0;
}
catch (e)
{
// Invalid URL - custom filter group
if (!("title" in obj))
{
// Backwards compatibility - titles and filter types were originally
// determined by group identifier.
if (obj.url == "~wl~")
obj.defaults = "whitelist";
else if (obj.url == "~fl~")
obj.defaults = "blocking";
else if (obj.url == "~eh~")
obj.defaults = "elemhide";
if ("defaults" in obj)
obj.title = Utils.getString(obj.defaults + "Group_title");
}
result = new SpecialSubscription(obj.url, obj.title);
if ("defaults" in obj)
result.defaults = obj.defaults.split(" ");
}
if ("fixedTitle" in obj)
result._fixedTitle = (obj.fixedTitle == "true");
if ("disabled" in obj)
result._disabled = (obj.disabled == "true");
return result;
}
/**
* Class for special filter subscriptions (user's filters)
* @param {String} url see Subscription()
* @param {String} [title] see Subscription()
* @constructor
* @augments Subscription
*/
function SpecialSubscription(url, title)
{
Subscription.call(this, url, title);
}
SpecialSubscription.prototype =
{
__proto__: Subscription.prototype,
/**
* Filter types that should be added to this subscription by default
* (entries should correspond to keys in SpecialSubscription.defaultsMap).
* @type Array of String
*/
defaults: null,
/**
* Tests whether a filter should be added to this group by default
* @param {Filter} filter filter to be tested
* @return {Boolean}
*/
isDefaultFor: function(filter)
{
if (this.defaults && this.defaults.length)
{
for each (let type in this.defaults)
{
if (filter instanceof SpecialSubscription.defaultsMap[type])
return true;
if (!(filter instanceof ActiveFilter) && type == "blacklist")
return true;
}
}
return false;
},
/**
* See Subscription.serialize()
*/
serialize: function(buffer)
{
Subscription.prototype.serialize.call(this, buffer);
if (this.defaults && this.defaults.length)
buffer.push("defaults=" + this.defaults.filter(function(type) type in SpecialSubscription.defaultsMap).join(" "));
if (this._lastDownload)
buffer.push("lastDownload=" + this._lastDownload);
}
};
SpecialSubscription.defaultsMap = {
__proto__: null,
"whitelist": WhitelistFilter,
"blocking": BlockingFilter,
"elemhide": ElemHideFilter
};
/**
* Creates a new user-defined filter group.
* @param {String} [title] title of the new filter group
* @result {SpecialSubscription}
*/
SpecialSubscription.create = function(title)
{
let url;
do
{
url = "~user~" + Math.round(Math.random()*1000000);
} while (url in Subscription.knownSubscriptions);
return new SpecialSubscription(url, title)
};
/**
* Creates a new user-defined filter group and adds the given filter to it.
* This group will act as the default group for this filter type.
*/
SpecialSubscription.createForFilter = function(/**Filter*/ filter) /**SpecialSubscription*/
{
let subscription = SpecialSubscription.create();
subscription.filters.push(filter);
for (let type in SpecialSubscription.defaultsMap)
{
if (filter instanceof SpecialSubscription.defaultsMap[type])
subscription.defaults = [type];
}
if (!subscription.defaults)
subscription.defaults = ["blocking"];
subscription.title = Utils.getString(subscription.defaults[0] + "Group_title");
return subscription;
};
/**
* Abstract base class for regular filter subscriptions (both internally and externally updated)
* @param {String} url see Subscription()
* @param {String} [title] see Subscription()
* @constructor
* @augments Subscription
*/
function RegularSubscription(url, title)
{
Subscription.call(this, url, title || url);
}
RegularSubscription.prototype =
{
__proto__: Subscription.prototype,
_homepage: null,
_lastDownload: 0,
/**
* Filter subscription homepage if known
* @type String
*/
get homepage() this._homepage,
set homepage(value)
{
if (value != this._homepage)
{
let oldValue = this._homepage;
this._homepage = value;
FilterNotifier.triggerListeners("subscription.homepage", this, value, oldValue);
}
return this._homepage;
},
/**
* Time of the last subscription download (in seconds since the beginning of the epoch)
* @type Number
*/
get lastDownload() this._lastDownload,
set lastDownload(value)
{
if (value != this._lastDownload)
{
let oldValue = this._lastDownload;
this._lastDownload = value;
FilterNotifier.triggerListeners("subscription.lastDownload", this, value, oldValue);
}
return this._lastDownload;
},
/**
* See Subscription.serialize()
*/
serialize: function(buffer)
{
Subscription.prototype.serialize.call(this, buffer);
if (this._homepage)
buffer.push("homepage=" + this._homepage);
if (this._lastDownload)
buffer.push("lastDownload=" + this._lastDownload);
}
};
/**
* Class for filter subscriptions updated by externally (by other extension)
* @param {String} url see Subscription()
* @param {String} [title] see Subscription()
* @constructor
* @augments RegularSubscription
*/
function ExternalSubscription(url, title)
{
RegularSubscription.call(this, url, title);
}
ExternalSubscription.prototype =
{
__proto__: RegularSubscription.prototype,
/**
* See Subscription.serialize()
*/
serialize: function(buffer)
{
throw new Error("Unexpected call, external subscriptions should not be serialized");
}
};
/**
* Class for filter subscriptions updated by externally (by other extension)
* @param {String} url see Subscription()
* @param {String} [title] see Subscription()
* @constructor
* @augments RegularSubscription
*/
function DownloadableSubscription(url, title)
{
RegularSubscription.call(this, url, title);
}
DownloadableSubscription.prototype =
{
__proto__: RegularSubscription.prototype,
_downloadStatus: null,
_lastCheck: 0,
_errors: 0,
/**
* Next URL the downloaded should be attempted from (in case of redirects)
* @type String
*/
nextURL: null,
/**
* Status of the last download (ID of a string)
* @type String
*/
get downloadStatus() this._downloadStatus,
set downloadStatus(value)
{
let oldValue = this._downloadStatus;
this._downloadStatus = value;
FilterNotifier.triggerListeners("subscription.downloadStatus", this, value, oldValue);
return this._downloadStatus;
},
/**
* Value of the Last-Modified header returned by the server on last download
* @type String
*/
lastModified: null,
/**
* Time of the last successful download (in seconds since the beginning of the
* epoch).
*/
lastSuccess: 0,
/**
* Time when the subscription was considered for an update last time (in seconds
* since the beginning of the epoch). This will be used to increase softExpiration
* if the user doesn't use Adblock Plus for some time.
* @type Number
*/
get lastCheck() this._lastCheck,
set lastCheck(value)
{
if (value != this._lastCheck)
{
let oldValue = this._lastCheck;
this._lastCheck = value;
FilterNotifier.triggerListeners("subscription.lastCheck", this, value, oldValue);
}
return this._lastCheck;
},
/**
* Hard expiration time of the filter subscription (in seconds since the beginning of the epoch)
* @type Number
*/
expires: 0,
/**
* Soft expiration time of the filter subscription (in seconds since the beginning of the epoch)
* @type Number
*/
softExpiration: 0,
/**
* Number of download failures since last success
* @type Number
*/
get errors() this._errors,
set errors(value)
{
if (value != this._errors)
{
let oldValue = this._errors;
this._errors = value;
FilterNotifier.triggerListeners("subscription.errors", this, value, oldValue);
}
return this._errors;
},
/**
* Minimal Adblock Plus version required for this subscription
* @type String
*/
requiredVersion: null,
/**
* Should be true if requiredVersion is higher than current Adblock Plus version
* @type Boolean
*/
upgradeRequired: false,
/**
* Value of the X-Alternative-Locations header: comma-separated list of URLs
* with their weighting factors, e.g.: http://foo.example.com/;q=0.5,http://bar.example.com/;q=2
* @type String
*/
alternativeLocations: null,
/**
* See Subscription.serialize()
*/
serialize: function(buffer)
{
RegularSubscription.prototype.serialize.call(this, buffer);
if (this.nextURL)
buffer.push("nextURL=" + this.nextURL);
if (this.downloadStatus)
buffer.push("downloadStatus=" + this.downloadStatus);
if (this.lastModified)
buffer.push("lastModified=" + this.lastModified);
if (this.lastSuccess)
buffer.push("lastSuccess=" + this.lastSuccess);
if (this.lastCheck)
buffer.push("lastCheck=" + this.lastCheck);
if (this.expires)
buffer.push("expires=" + this.expires);
if (this.softExpiration)
buffer.push("softExpiration=" + this.softExpiration);
if (this.errors)
buffer.push("errors=" + this.errors);
if (this.requiredVersion)
buffer.push("requiredVersion=" + this.requiredVersion);
if (this.alternativeLocations)
buffer.push("alternativeLocations=" + this.alternativeLocations);
}
};
+425
View File
@@ -0,0 +1,425 @@
/*
* This Source Code is subject to the terms of the Mozilla Public License
* version 2.0 (the "License"). You can obtain a copy of the License at
* http://mozilla.org/MPL/2.0/.
*/
#filter substitution
/**
* @fileOverview Module containing a bunch of utility functions.
*/
var EXPORTED_SYMBOLS = ["Sync"];
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cr = Components.results;
const Cu = Components.utils;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
let baseURL = "resource://@ADDON_CHROME_NAME@/modules/";
Cu.import(baseURL + "Utils.jsm");
Cu.import(baseURL + "FilterStorage.jsm");
Cu.import(baseURL + "SubscriptionClasses.jsm");
Cu.import(baseURL + "FilterClasses.jsm");
Cu.import(baseURL + "FilterNotifier.jsm");
Cu.import(baseURL + "Synchronizer.jsm");
/**
* ID of the only record stored
* @type String
*/
const filtersRecordID = "6fad6286-8207-46b6-aa39-8e0ce0bd7c49";
/**
* Weave tracker class (is set when Weave is initialized).
*/
var Tracker = null;
var Sync =
{
/**
* Will be set to true if/when Weave starts up.
* @type Boolean
*/
initialized: false,
/**
* Whether Weave requested us to track changes.
* @type Boolean
*/
trackingEnabled: false,
/**
* Called on module startup.
*/
startup: function()
{
Services.obs.addObserver(SyncPrivate, "weave:service:ready", true);
Services.obs.addObserver(SyncPrivate, "weave:engine:start-tracking", true);
Services.obs.addObserver(SyncPrivate, "weave:engine:stop-tracking", true);
},
/**
* Returns Adblock Plus sync engine.
* @result Engine
*/
getEngine: function()
{
if (this.initialized)
return Weave.Engines.get("adblockplus");
else
return null;
}
};
var SyncPrivate =
{
QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
observe: function(subject, topic, data)
{
switch (topic)
{
case "weave:service:ready":
Cu.import("resource://services-sync/main.js");
Tracker = Weave.SyncEngine.prototype._trackerObj;
ABPEngine.prototype.__proto__ = Weave.SyncEngine.prototype;
ABPStore.prototype.__proto__ = Weave.Store.prototype;
ABPTracker.prototype.__proto__ = Tracker.prototype;
Weave.Engines.register(ABPEngine);
Sync.initialized = true;
break;
case "weave:engine:start-tracking":
Sync.trackingEnabled = true;
if (trackerInstance)
trackerInstance.startTracking();
break;
case "weave:engine:stop-tracking":
Sync.trackingEnabled = false;
if (trackerInstance)
trackerInstance.stopTracking();
break;
}
}
};
function ABPEngine()
{
Weave.SyncEngine.call(this, "AdblockPlus");
}
ABPEngine.prototype =
{
_storeObj: ABPStore,
_trackerObj: ABPTracker,
version: 1,
_reconcile: function(item)
{
// Always process server data, we will do the merging ourselves
return true;
}
};
function ABPStore(name)
{
Weave.Store.call(this, name);
}
ABPStore.prototype =
{
getAllIDs: function()
{
let result = {}
result[filtersRecordID] = true;
return result;
},
changeItemID: function(oldId, newId)
{
// This should not be called, our engine doesn't implement _findDupe
throw Cr.NS_ERROR_UNEXPECTED;
},
itemExists: function(id)
{
// Only one id exists so far
return (id == filtersRecordID);
},
createRecord: function(id, collection)
{
let record = new ABPEngine.prototype._recordObj(collection, id);
if (id == filtersRecordID)
{
record.cleartext = {
id: id,
subscriptions: [],
};
for each (let subscription in FilterStorage.subscriptions)
{
if (subscription instanceof ExternalSubscription)
continue;
let subscriptionEntry =
{
url: subscription.url,
disabled: subscription.disabled
};
if (subscription instanceof SpecialSubscription)
{
subscriptionEntry.filters = [];
for each (let filter in subscription.filters)
{
let filterEntry = {text: filter.text};
if (filter instanceof ActiveFilter)
filterEntry.disabled = filter.disabled;
subscriptionEntry.filters.push(filterEntry);
}
}
else
subscriptionEntry.title = subscription.title;
record.cleartext.subscriptions.push(subscriptionEntry);
}
// Data sent, forget about local changes now
trackerInstance.clearPrivateChanges()
}
else
record.deleted = true;
return record;
},
create: function(record)
{
// This should not be called because our record list doesn't change but
// call update just in case.
this.update(record);
},
update: function(record)
{
if (record.id != filtersRecordID)
return;
this._log.trace("Merging in remote data");
let data = record.cleartext.subscriptions;
// First make sure we have the same subscriptions on both sides
let seenSubscription = {__proto__: null};
for each (let remoteSubscription in data)
{
seenSubscription[remoteSubscription.url] = true;
if (remoteSubscription.url in FilterStorage.knownSubscriptions)
{
let subscription = FilterStorage.knownSubscriptions[remoteSubscription.url];
if (!trackerInstance.didSubscriptionChange(remoteSubscription))
{
// Only change local subscription if there were no changes, otherwise dismiss remote changes
subscription.disabled = remoteSubscription.disabled;
if (subscription instanceof DownloadableSubscription)
subscription.title = remoteSubscription.title;
}
}
else if (!trackerInstance.didSubscriptionChange(remoteSubscription))
{
// Subscription was added remotely, add it locally as well
let subscription = Subscription.fromURL(remoteSubscription.url);
if (!subscription)
continue;
subscription.disabled = remoteSubscription.disabled;
if (subscription instanceof DownloadableSubscription)
{
subscription.title = remoteSubscription.title;
FilterStorage.addSubscription(subscription);
Synchronizer.execute(subscription);
}
}
}
for each (let subscription in FilterStorage.subscriptions.slice())
{
if (!(subscription.url in seenSubscription) && subscription instanceof DownloadableSubscription && !trackerInstance.didSubscriptionChange(subscription))
{
// Subscription was removed remotely, remove it locally as well
FilterStorage.removeSubscription(subscription);
}
}
// Now sync the custom filters
let seenFilter = {__proto__: null};
for each (let remoteSubscription in data)
{
if (!("filters" in remoteSubscription))
continue;
for each (let remoteFilter in remoteSubscription.filters)
{
seenFilter[remoteFilter.text] = true;
let filter = Filter.fromText(remoteFilter.text);
if (!filter || trackerInstance.didFilterChange(filter))
continue;
if (filter.subscriptions.some(function(subscription) subscription instanceof SpecialSubscription))
{
// Filter might have been changed remotely
if (filter instanceof ActiveFilter)
filter.disabled = remoteFilter.disabled;
}
else
{
// Filter was added remotely, add it locally as well
FilterStorage.addFilter(filter);
}
}
}
for each (let subscription in FilterStorage.subscriptions)
{
if (!(subscription instanceof SpecialSubscription))
continue;
for each (let filter in subscription.filters.slice())
{
if (!(filter.text in seenFilter) && !trackerInstance.didFilterChange(filter))
{
// Filter was removed remotely, remove it locally as well
FilterStorage.removeFilter(filter);
}
}
}
// Merge done, forget about local changes now
trackerInstance.clearPrivateChanges()
},
remove: function(record)
{
// Shouldn't be called but if it is - ignore
},
wipe: function()
{
this._log.trace("Got wipe command, removing all data");
for each (let subscription in FilterStorage.subscriptions.slice())
{
if (subscription instanceof DownloadableSubscription)
FilterStorage.removeSubscription(subscription);
else if (subscription instanceof SpecialSubscription)
{
for each (let filter in subscription.filters.slice())
FilterStorage.removeFilter(filter);
}
}
// Data wiped, forget about local changes now
trackerInstance.clearPrivateChanges()
}
};
/**
* Hack to allow store to use the tracker - store tracker pointer globally.
*/
let trackerInstance = null;
function ABPTracker(name)
{
Tracker.call(this, name);
this.privateTracker = new Tracker(name + ".private");
trackerInstance = this;
this.onChange = this._bindMethod(this.onChange);
if (Sync.trackingEnabled)
this.startTracking();
}
ABPTracker.prototype =
{
privateTracker: null,
_bindMethod: function(method)
{
let me = this;
return function() method.apply(me, arguments);
},
startTracking: function()
{
FilterNotifier.addListener(this.onChange);
},
stopTracking: function()
{
FilterNotifier.removeListener(this.onChange);
},
clearPrivateChanges: function()
{
this.privateTracker.clearChangedIDs();
},
addPrivateChange: function(id)
{
// Ignore changes during syncing
if (this.ignoreAll)
return;
this.addChangedID(filtersRecordID);
this.privateTracker.addChangedID(id);
this.score += 10;
},
didSubscriptionChange: function(subscription)
{
return ("subscription " + subscription.url) in this.privateTracker.changedIDs;
},
didFilterChange: function(filter)
{
return ("filter " + filter.text) in this.privateTracker.changedIDs;
},
onChange: function(action, item)
{
switch (action)
{
case "subscription.updated":
if ("oldSubscription" in item)
{
// Subscription moved to a new address
this.addPrivateChange("subscription " + item.url);
this.addPrivateChange("subscription " + item.oldSubscription.url);
}
else if (item instanceof SpecialSubscription)
{
// User's filters changed via Preferences window
for each (let filter in item.filters)
this.addPrivateChange("filter " + filter.text);
for each (let filter in item.oldFilters)
this.addPrivateChange("filter " + filter.text);
}
break;
case "subscription.added":
case "subscription.removed":
case "subscription.disabled":
case "subscription.title":
this.addPrivateChange("subscription " + item.url);
break;
case "filter.added":
case "filter.removed":
case "filter.disabled":
this.addPrivateChange("filter " + item.text);
break;
}
}
};
+594
View File
@@ -0,0 +1,594 @@
/*
* This Source Code is subject to the terms of the Mozilla Public License
* version 2.0 (the "License"). You can obtain a copy of the License at
* http://mozilla.org/MPL/2.0/.
*/
#filter substitution
/**
* @fileOverview Manages synchronization of filter subscriptions.
*/
var EXPORTED_SYMBOLS = ["Synchronizer"];
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cr = Components.results;
const Cu = Components.utils;
let baseURL = "resource://@ADDON_CHROME_NAME@/modules/";
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import(baseURL + "TimeLine.jsm");
Cu.import(baseURL + "Utils.jsm");
Cu.import(baseURL + "FilterStorage.jsm");
Cu.import(baseURL + "FilterNotifier.jsm");
Cu.import(baseURL + "FilterClasses.jsm");
Cu.import(baseURL + "SubscriptionClasses.jsm");
Cu.import(baseURL + "Prefs.jsm");
const MILLISECONDS_IN_SECOND = 1000;
const SECONDS_IN_MINUTE = 60;
const SECONDS_IN_HOUR = 60 * SECONDS_IN_MINUTE;
const SECONDS_IN_DAY = 24 * SECONDS_IN_HOUR;
const INITIAL_DELAY = 6 * SECONDS_IN_MINUTE;
const CHECK_INTERVAL = SECONDS_IN_HOUR;
const MIN_EXPIRATION_INTERVAL = 1 * SECONDS_IN_DAY;
const MAX_EXPIRATION_INTERVAL = 14 * SECONDS_IN_DAY;
const MAX_ABSENSE_INTERVAL = 1 * SECONDS_IN_DAY;
let timer = null;
/**
* Map of subscriptions currently being downloaded, all currently downloaded
* URLs are keys of that map.
*/
let executing = {__proto__: null};
/**
* This object is responsible for downloading filter subscriptions whenever
* necessary.
* @class
*/
var Synchronizer =
{
/**
* Called on module startup.
*/
startup: function()
{
TimeLine.enter("Entered Synchronizer.startup()");
let callback = function()
{
timer.delay = CHECK_INTERVAL * MILLISECONDS_IN_SECOND;
checkSubscriptions();
};
timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
timer.initWithCallback(callback, INITIAL_DELAY * MILLISECONDS_IN_SECOND, Ci.nsITimer.TYPE_REPEATING_SLACK);
TimeLine.leave("Synchronizer.startup() done");
},
/**
* Checks whether a subscription is currently being downloaded.
* @param {String} url URL of the subscription
* @return {Boolean}
*/
isExecuting: function(url)
{
return url in executing;
},
/**
* Starts the download of a subscription.
* @param {DownloadableSubscription} subscription Subscription to be downloaded
* @param {Boolean} manual true for a manually started download (should not trigger fallback requests)
* @param {Boolean} forceDownload if true, the subscription will even be redownloaded if it didn't change on the server
*/
execute: function(subscription, manual, forceDownload)
{
// Delay execution, SeaMonkey 2.1 won't fire request's event handlers
// otherwise if the window that called us is closed.
Utils.runAsync(this.executeInternal, this, subscription, manual, forceDownload);
},
executeInternal: function(subscription, manual, forceDownload)
{
let url = subscription.url;
if (url in executing)
return;
let newURL = subscription.nextURL;
let hadTemporaryRedirect = false;
subscription.nextURL = null;
let curVersion = "3.5.0";
let loadFrom = newURL;
let isBaseLocation = true;
if (!loadFrom)
loadFrom = url;
if (loadFrom == url)
{
if (subscription.alternativeLocations)
{
// We have alternative download locations, choose one. "Regular"
// subscription URL always goes in with weight 1.
let options = [[1, url]];
let totalWeight = 1;
for each (let alternative in subscription.alternativeLocations.split(','))
{
if (!/^https?:\/\//.test(alternative))
continue;
let weight = 1;
let match = /;q=([\d\.]+)$/.exec(alternative);
if (match)
{
weight = parseFloat(match[1]);
if (isNaN(weight) || !isFinite(weight) || weight < 0)
weight = 1;
if (weight > 10)
weight = 10;
alternative = alternative.substr(0, match.index);
}
options.push([weight, alternative]);
totalWeight += weight;
}
let choice = Math.random() * totalWeight;
for each (let [weight, alternative] in options)
{
choice -= weight;
if (choice < 0)
{
loadFrom = alternative;
break;
}
}
isBaseLocation = (loadFrom == url);
}
}
else
{
// Ignore modification date if we are downloading from a different location
forceDownload = true;
}
loadFrom = loadFrom.replace(/%VERSION%/, "ABP" + "3.5.0");
let request = null;
function errorCallback(error)
{
let channelStatus = -1;
try
{
channelStatus = request.channel.status;
} catch (e) {}
let responseStatus = "";
try
{
responseStatus = request.channel.QueryInterface(Ci.nsIHttpChannel).responseStatus;
} catch (e) {}
setError(subscription, error, channelStatus, responseStatus, loadFrom, isBaseLocation, manual);
}
try
{
request = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].createInstance(Ci.nsIXMLHttpRequest);
request.mozBackgroundRequest = true;
request.open("GET", loadFrom);
}
catch (e)
{
errorCallback("synchronize_invalid_url");
return;
}
try {
request.overrideMimeType("text/plain");
request.channel.loadFlags = request.channel.loadFlags |
request.channel.INHIBIT_CACHING |
request.channel.VALIDATE_ALWAYS;
// Override redirect limit from preferences, user might have set it to 1
if (request.channel instanceof Ci.nsIHttpChannel)
request.channel.redirectionLimit = 5;
var oldNotifications = request.channel.notificationCallbacks;
var oldEventSink = null;
request.channel.notificationCallbacks =
{
QueryInterface: XPCOMUtils.generateQI([Ci.nsIInterfaceRequestor, Ci.nsIChannelEventSink]),
getInterface: function(iid)
{
if (iid.equals(Ci.nsIChannelEventSink))
{
try {
oldEventSink = oldNotifications.QueryInterface(iid);
} catch(e) {}
return this;
}
if (oldNotifications)
return oldNotifications.QueryInterface(iid);
else
throw Cr.NS_ERROR_NO_INTERFACE;
},
asyncOnChannelRedirect: function(oldChannel, newChannel, flags, callback)
{
if (isBaseLocation && !hadTemporaryRedirect && oldChannel instanceof Ci.nsIHttpChannel)
{
try
{
subscription.alternativeLocations = oldChannel.getResponseHeader("X-Alternative-Locations");
}
catch (e)
{
subscription.alternativeLocations = null;
}
}
if (flags & Ci.nsIChannelEventSink.REDIRECT_TEMPORARY)
hadTemporaryRedirect = true;
else if (!hadTemporaryRedirect)
newURL = newChannel.URI.spec;
if (oldEventSink)
oldEventSink.asyncOnChannelRedirect(oldChannel, newChannel, flags, callback);
else
callback.onRedirectVerifyCallback(Cr.NS_OK);
}
}
}
catch (e)
{
Cu.reportError(e)
}
if (subscription.lastModified && !forceDownload)
request.setRequestHeader("If-Modified-Since", subscription.lastModified);
request.addEventListener("error", function(ev)
{
delete executing[url];
try {
request.channel.notificationCallbacks = null;
} catch (e) {}
errorCallback("synchronize_connection_error");
}, false);
request.addEventListener("load", function(ev)
{
delete executing[url];
try {
request.channel.notificationCallbacks = null;
} catch (e) {}
// Status will be 0 for non-HTTP requests
if (request.status && request.status != 200 && request.status != 304)
{
errorCallback("synchronize_connection_error");
return;
}
let newFilters = null;
if (request.status != 304)
{
newFilters = readFilters(subscription, request.responseText, errorCallback);
if (!newFilters)
return;
subscription.lastModified = request.getResponseHeader("Last-Modified");
}
if (isBaseLocation && !hadTemporaryRedirect)
subscription.alternativeLocations = request.getResponseHeader("X-Alternative-Locations");
subscription.lastSuccess = subscription.lastDownload = Math.round(Date.now() / MILLISECONDS_IN_SECOND);
subscription.downloadStatus = "synchronize_ok";
subscription.errors = 0;
// Expiration header is relative to server time - use Date header if it exists, otherwise local time
let now = Math.round((new Date(request.getResponseHeader("Date")).getTime() || Date.now()) / MILLISECONDS_IN_SECOND);
let expires = Math.round(new Date(request.getResponseHeader("Expires")).getTime() / MILLISECONDS_IN_SECOND) || 0;
let expirationInterval = (expires ? expires - now : 0);
for each (let filter in newFilters || subscription.filters)
{
if (!(filter instanceof CommentFilter))
continue;
let match = /\bExpires\s*(?::|after)\s*(\d+)\s*(h)?/i.exec(filter.text);
if (match)
{
let interval = parseInt(match[1], 10);
if (match[2])
interval *= SECONDS_IN_HOUR;
else
interval *= SECONDS_IN_DAY;
if (interval > expirationInterval)
expirationInterval = interval;
}
}
// Expiration interval should be within allowed range
expirationInterval = Math.min(Math.max(expirationInterval, MIN_EXPIRATION_INTERVAL), MAX_EXPIRATION_INTERVAL);
// Hard expiration: download immediately after twice the expiration interval
subscription.expires = (subscription.lastDownload + expirationInterval * 2);
// Soft expiration: use random interval factor between 0.8 and 1.2
subscription.softExpiration = (subscription.lastDownload + Math.round(expirationInterval * (Math.random() * 0.4 + 0.8)));
// Process some special filters and remove them
if (newFilters)
{
let fixedTitle = false;
for (let i = 0; i < newFilters.length; i++)
{
let filter = newFilters[i];
if (!(filter instanceof CommentFilter))
continue;
let match = /^!\s*(\w+)\s*:\s*(.*)/.exec(filter.text);
if (match)
{
let keyword = match[1].toLowerCase();
let value = match[2];
let known = true;
if (keyword == "redirect")
{
if (isBaseLocation && value != url)
subscription.nextURL = value;
}
else if (keyword == "homepage")
{
let uri = Utils.makeURI(value);
if (uri && (uri.scheme == "http" || uri.scheme == "https"))
subscription.homepage = uri.spec;
}
else if (keyword == "title")
{
if (value)
{
subscription.title = value;
fixedTitle = true;
}
}
else
known = false;
if (known)
newFilters.splice(i--, 1);
}
}
subscription.fixedTitle = fixedTitle;
}
if (isBaseLocation && newURL && newURL != url)
{
let listed = (subscription.url in FilterStorage.knownSubscriptions);
if (listed)
FilterStorage.removeSubscription(subscription);
url = newURL;
let newSubscription = Subscription.fromURL(url);
for (let key in newSubscription)
delete newSubscription[key];
for (let key in subscription)
newSubscription[key] = subscription[key];
delete Subscription.knownSubscriptions[subscription.url];
newSubscription.oldSubscription = subscription;
subscription = newSubscription;
subscription.url = url;
if (!(subscription.url in FilterStorage.knownSubscriptions) && listed)
FilterStorage.addSubscription(subscription);
}
if (newFilters)
FilterStorage.updateSubscriptionFilters(subscription, newFilters);
delete subscription.oldSubscription;
}, false);
executing[url] = true;
FilterNotifier.triggerListeners("subscription.downloadStatus", subscription);
try
{
request.send(null);
}
catch (e)
{
delete executing[url];
errorCallback("synchronize_connection_error");
return;
}
}
};
/**
* Checks whether any subscriptions need to be downloaded and starts the download
* if necessary.
*/
function checkSubscriptions()
{
if (!Prefs.subscriptions_autoupdate)
return;
let time = Math.round(Date.now() / MILLISECONDS_IN_SECOND);
for each (let subscription in FilterStorage.subscriptions)
{
if (!(subscription instanceof DownloadableSubscription))
continue;
if (subscription.lastCheck && time - subscription.lastCheck > MAX_ABSENSE_INTERVAL)
{
// No checks for a long time interval - user must have been offline, e.g.
// during a weekend. Increase soft expiration to prevent load peaks on the
// server.
subscription.softExpiration += time - subscription.lastCheck;
}
subscription.lastCheck = time;
// Sanity check: do expiration times make sense? Make sure people changing
// system clock don't get stuck with outdated subscriptions.
if (subscription.expires - time > MAX_EXPIRATION_INTERVAL)
subscription.expires = time + MAX_EXPIRATION_INTERVAL;
if (subscription.softExpiration - time > MAX_EXPIRATION_INTERVAL)
subscription.softExpiration = time + MAX_EXPIRATION_INTERVAL;
if (subscription.softExpiration > time && subscription.expires > time)
continue;
// Do not retry downloads more often than MIN_EXPIRATION_INTERVAL
if (time - subscription.lastDownload >= MIN_EXPIRATION_INTERVAL)
Synchronizer.execute(subscription, false);
}
}
/**
* Extracts a list of filters from text returned by a server.
* @param {DownloadableSubscription} subscription subscription the info should be placed into
* @param {String} text server response
* @param {Function} errorCallback function to be called on error
* @return {Array of Filter}
*/
function readFilters(subscription, text, errorCallback)
{
let lines = text.split(/[\r\n]+/);
let match = /\[Adblock(?:\s*Plus\s*([\d\.]+)?)?\]/i.exec(lines[0]);
if (!match)
{
errorCallback("synchronize_invalid_data");
return null;
}
let minVersion = match[1];
for (let i = 0; i < lines.length; i++)
{
let match = /!\s*checksum[\s\-:]+([\w\+\/]+)/i.exec(lines[i]);
if (match)
{
lines.splice(i, 1);
let checksum = Utils.generateChecksum(lines);
if (checksum && checksum != match[1])
{
errorCallback("synchronize_checksum_mismatch");
return null;
}
break;
}
}
delete subscription.requiredVersion;
delete subscription.upgradeRequired;
if (minVersion)
{
subscription.requiredVersion = minVersion;
if (Utils.versionComparator.compare(minVersion, "3.5.0") > 0)
subscription.upgradeRequired = true;
}
lines.shift();
let result = [];
for each (let line in lines)
{
let filter = Filter.fromText(Filter.normalize(line));
if (filter)
result.push(filter);
}
return result;
}
/**
* Handles an error during a subscription download.
* @param {DownloadableSubscription} subscription subscription that failed to download
* @param {Integer} channelStatus result code of the download channel
* @param {String} responseStatus result code as received from server
* @param {String} downloadURL the URL used for download
* @param {String} error error ID in global.properties
* @param {Boolean} isBaseLocation false if the subscription was downloaded from a location specified in X-Alternative-Locations header
* @param {Boolean} manual true for a manually started download (should not trigger fallback requests)
*/
function setError(subscription, error, channelStatus, responseStatus, downloadURL, isBaseLocation, manual)
{
// If download from an alternative location failed, reset the list of
// alternative locations - have to get an updated list from base location.
if (!isBaseLocation)
subscription.alternativeLocations = null;
try {
Cu.reportError("Adblock Plus: Downloading filter subscription " + subscription.title + " failed (" + Utils.getString(error) + ")\n" +
"Download address: " + downloadURL + "\n" +
"Channel status: " + channelStatus + "\n" +
"Server response: " + responseStatus);
} catch(e) {}
subscription.lastDownload = Math.round(Date.now() / MILLISECONDS_IN_SECOND);
subscription.downloadStatus = error;
// Request fallback URL if necessary - for automatic updates only
if (!manual)
{
if (error == "synchronize_checksum_mismatch")
{
// No fallback for successful download with checksum mismatch, reset error counter
subscription.errors = 0;
}
else
subscription.errors++;
if (subscription.errors >= Prefs.subscriptions_fallbackerrors && /^https?:\/\//i.test(subscription.url))
{
subscription.errors = 0;
let fallbackURL = Prefs.subscriptions_fallbackurl;
fallbackURL = fallbackURL.replace(/%VERSION%/g, encodeURIComponent("3.5.0"));
fallbackURL = fallbackURL.replace(/%SUBSCRIPTION%/g, encodeURIComponent(subscription.url));
fallbackURL = fallbackURL.replace(/%URL%/g, encodeURIComponent(downloadURL));
fallbackURL = fallbackURL.replace(/%ERROR%/g, encodeURIComponent(error));
fallbackURL = fallbackURL.replace(/%CHANNELSTATUS%/g, encodeURIComponent(channelStatus));
fallbackURL = fallbackURL.replace(/%RESPONSESTATUS%/g, encodeURIComponent(responseStatus));
let request = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].createInstance(Ci.nsIXMLHttpRequest);
request.mozBackgroundRequest = true;
request.open("GET", fallbackURL);
request.overrideMimeType("text/plain");
request.channel.loadFlags = request.channel.loadFlags |
request.channel.INHIBIT_CACHING |
request.channel.VALIDATE_ALWAYS;
request.addEventListener("load", function(ev)
{
if (!(subscription.url in FilterStorage.knownSubscriptions))
return;
let match = /^(\d+)(?:\s+(\S+))?$/.exec(request.responseText);
if (match && match[1] == "301" && match[2]) // Moved permanently
subscription.nextURL = match[2];
else if (match && match[1] == "410") // Gone
{
let data = "[Adblock]\n" + subscription.filters.map(function(f) f.text).join("\n");
let url = "data:text/plain," + encodeURIComponent(data);
let newSubscription = Subscription.fromURL(url);
newSubscription.title = subscription.title;
newSubscription.disabled = subscription.disabled;
FilterStorage.removeSubscription(subscription);
FilterStorage.addSubscription(newSubscription);
Synchronizer.execute(newSubscription);
}
}, false);
request.send(null);
}
}
}
+153
View File
@@ -0,0 +1,153 @@
/*
* This Source Code is subject to the terms of the Mozilla Public License
* version 2.0 (the "License"). You can obtain a copy of the License at
* http://mozilla.org/MPL/2.0/.
*/
#filter substitution
/**
* @fileOverview Debugging module used for load time measurements.
*/
var EXPORTED_SYMBOLS = ["TimeLine"];
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cr = Components.results;
const Cu = Components.utils;
let nestingCounter = 0;
let firstTimeStamp = null;
let lastTimeStamp = null;
let asyncActions = {__proto__: null};
/**
* Time logging module, used to measure startup time of Adblock Plus (development builds only).
* @class
*/
var TimeLine = {
/**
* Logs an event to console together with the time it took to get there.
*/
log: function(/**String*/ message, /**Boolean*/ _forceDisplay)
{
if (!_forceDisplay && nestingCounter <= 0)
return;
let now = Date.now();
let diff = lastTimeStamp ? Math.round(now - lastTimeStamp) : "first event";
lastTimeStamp = now;
// Indent message depending on current nesting level
for (let i = 0; i < nestingCounter; i++)
message = "* " + message;
// Pad message with spaces
let padding = [];
for (let i = message.toString().length; i < 80; i++)
padding.push(" ");
dump("[" + now + "] ABP timeline: " + message + padding.join("") + "\t (" + diff + ")\n");
},
/**
* Called to indicate that application entered a block that needs to be timed.
*/
enter: function(/**String*/ message)
{
if (nestingCounter <= 0)
firstTimeStamp = Date.now();
this.log(message, true);
nestingCounter = (nestingCounter <= 0 ? 1 : nestingCounter + 1);
},
/**
* Called when application exited a block that TimeLine.enter() was called for.
* @param {String} message message to be logged
* @param {String} [asyncAction] identifier of a pending async action
*/
leave: function(message, asyncAction)
{
if (typeof asyncAction != "undefined")
message += " (async action pending)";
nestingCounter--;
this.log(message, true);
if (nestingCounter <= 0)
{
if (firstTimeStamp !== null)
dump("ABP timeline: Total time elapsed: " + Math.round(Date.now() - firstTimeStamp) + "\n");
firstTimeStamp = null;
lastTimeStamp = null;
}
if (typeof asyncAction != "undefined")
{
if (asyncAction in asyncActions)
dump("ABP timeline: Warning: Async action " + asyncAction + " already executing\n");
asyncActions[asyncAction] = {start: Date.now(), total: 0};
}
},
/**
* Called when the application starts processing of an async action.
*/
asyncStart: function(/**String*/ asyncAction)
{
if (asyncAction in asyncActions)
{
let action = asyncActions[asyncAction];
if ("currentStart" in action)
dump("ABP timeline: Warning: Processing reentered for async action " + asyncAction + "\n");
action.currentStart = Date.now();
}
else
dump("ABP timeline: Warning: Async action " + asyncAction + " is unknown\n");
},
/**
* Called when the application finishes processing of an async action.
*/
asyncEnd: function(/**String*/ asyncAction)
{
if (asyncAction in asyncActions)
{
let action = asyncActions[asyncAction];
if ("currentStart" in action)
{
action.total += Date.now() - action.currentStart;
delete action.currentStart;
}
else
dump("ABP timeline: Warning: Processing not entered for async action " + asyncAction + "\n");
}
else
dump("ABP timeline: Warning: Async action " + asyncAction + " is unknown\n");
},
/**
* Called when an async action is done and its time can be logged.
*/
asyncDone: function(/**String*/ asyncAction)
{
if (asyncAction in asyncActions)
{
let action = asyncActions[asyncAction];
let now = Date.now();
let diff = now - action.start;
if ("currentStart" in action)
dump("ABP timeline: Warning: Still processing for async action " + asyncAction + "\n");
let message = "Async action " + asyncAction + " done";
let padding = [];
for (let i = message.toString().length; i < 80; i++)
padding.push(" ");
dump("[" + now + "] ABP timeline: " + message + padding.join("") + "\t (" + action.total + "/" + diff + ")\n");
}
else
dump("ABP timeline: Warning: Async action " + asyncAction + " is unknown\n");
}
};
+820
View File
@@ -0,0 +1,820 @@
/*
* This Source Code is subject to the terms of the Mozilla Public License
* version 2.0 (the "License"). You can obtain a copy of the License at
* http://mozilla.org/MPL/2.0/.
*/
#filter substitution
/**
* @fileOverview Module containing a bunch of utility functions.
*/
var EXPORTED_SYMBOLS = ["Utils", "Cache"];
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cr = Components.results;
const Cu = Components.utils;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/FileUtils.jsm");
Cu.import("resource://gre/modules/NetUtil.jsm");
let sidebarParams = null;
/**
* Provides a bunch of utility functions.
* @class
*/
var Utils =
{
/**
* Returns the add-on ID used by Adblock Plus
*/
get addonID()
{
return "@ADDON_ID@";
},
/**
* Returns the installed Adblock Plus version
*/
get addonVersion()
{
let version = "@ADDON_VERSION@";
return (version[0] == "{" ? "99.9" : version);
},
/**
* Returns the VCS revision used for this Adblock Plus build
*/
get addonBuild()
{
let build = "";
return (build[0] == "{" ? "" : build);
},
/**
* Returns ID of the application
*/
get appID()
{
let id = Services.appinfo.ID;
Utils.__defineGetter__("appID", function() id);
return Utils.appID;
},
/**
* Returns whether we are running in Fennec, for Fennec-specific hacks
* @type Boolean
*/
get isFennec()
{
let result = (this.appID == "{a23983c0-fd0e-11dc-95ff-0800200c9a66}" || this.appID == "{aa3c5121-dab2-40e2-81ca-7ea25febc110}");
Utils.__defineGetter__("isFennec", function() result);
return result;
},
/**
* Returns the user interface locale selected for adblockplus chrome package.
*/
get appLocale()
{
let locale = "en-US";
try
{
locale = Utils.chromeRegistry.getSelectedLocale("@ADDON_CHROME_NAME@");
}
catch (e)
{
Cu.reportError(e);
}
Utils.__defineGetter__("appLocale", function() locale);
return Utils.appLocale;
},
/**
* Returns version of the Gecko platform
*/
get platformVersion()
{
let platformVersion = Services.appinfo.platformVersion;
Utils.__defineGetter__("platformVersion", function() platformVersion);
return Utils.platformVersion;
},
/**
* Retrieves a string from global.properties string bundle, will throw if string isn't found.
*
* @param {String} name string name
* @return {String}
*/
getString: function(name)
{
let stringBundle = Services.strings.createBundle("chrome://@ADDON_CHROME_NAME@/locale/global.properties");
Utils.getString = function(name)
{
return stringBundle.GetStringFromName(name);
}
return Utils.getString(name);
},
/**
* Shows an alert message like window.alert() but with a custom title.
*
* @param {Window} parentWindow parent window of the dialog (can be null)
* @param {String} message message to be displayed
* @param {String} [title] dialog title, default title will be used if omitted
*/
alert: function(parentWindow, message, title)
{
if (!title)
title = Utils.getString("default_dialog_title");
Utils.promptService.alert(parentWindow, title, message);
},
/**
* Asks the user for a confirmation like window.confirm() but with a custom title.
*
* @param {Window} parentWindow parent window of the dialog (can be null)
* @param {String} message message to be displayed
* @param {String} [title] dialog title, default title will be used if omitted
* @return {Bool}
*/
confirm: function(parentWindow, message, title)
{
if (!title)
title = Utils.getString("default_dialog_title");
return Utils.promptService.confirm(parentWindow, title, message);
},
/**
* Retrieves the window for a document node.
* @return {Window} will be null if the node isn't associated with a window
*/
getWindow: function(/**Node*/ node)
{
if ("ownerDocument" in node && node.ownerDocument)
node = node.ownerDocument;
if ("defaultView" in node)
return node.defaultView;
return null;
},
/**
* If the window doesn't have its own security context (e.g. about:blank or
* data: URL) walks up the parent chain until a window is found that has a
* security context.
*/
getOriginWindow: function(/**Window*/ wnd) /**Window*/
{
while (wnd != wnd.parent)
{
let uri = Utils.makeURI(wnd.location.href);
if (uri.spec != "about:blank" && uri.spec != "moz-safe-about:blank" &&
!Utils.netUtils.URIChainHasFlags(uri, Ci.nsIProtocolHandler.URI_INHERITS_SECURITY_CONTEXT))
{
break;
}
wnd = wnd.parent;
}
return wnd;
},
/**
* If a protocol using nested URIs like jar: is used - retrieves innermost
* nested URI.
*/
unwrapURL: function(/**nsIURI or String*/ url) /**nsIURI*/
{
if (!(url instanceof Ci.nsIURI))
url = Utils.makeURI(url);
if (url instanceof Ci.nsINestedURI)
return url.innermostURI;
else
return url;
},
/**
* Translates a string URI into its nsIURI representation, will return null for
* invalid URIs.
*/
makeURI: function(/**String*/ url) /**nsIURI*/
{
try
{
return Utils.ioService.newURI(url, null, null);
}
catch (e) {
return null;
}
},
/**
* Posts an action to the event queue of the current thread to run it
* asynchronously. Any additional parameters to this function are passed
* as parameters to the callback.
*/
runAsync: function(/**Function*/ callback, /**Object*/ thisPtr)
{
let params = Array.prototype.slice.call(arguments, 2);
let runnable = {
run: function()
{
callback.apply(thisPtr, params);
}
};
Utils.threadManager.currentThread.dispatch(runnable, Ci.nsIEventTarget.DISPATCH_NORMAL);
},
/**
* Gets the DOM window associated with a particular request (if any).
*/
getRequestWindow: function(/**nsIChannel*/ channel) /**nsIDOMWindow*/
{
try
{
if (channel.notificationCallbacks)
return channel.notificationCallbacks.getInterface(Ci.nsILoadContext).associatedWindow;
} catch(e) {}
try
{
if (channel.loadGroup && channel.loadGroup.notificationCallbacks)
return channel.loadGroup.notificationCallbacks.getInterface(Ci.nsILoadContext).associatedWindow;
} catch(e) {}
return null;
},
/**
* Generates filter subscription checksum.
*
* @param {Array of String} lines filter subscription lines (with checksum line removed)
* @return {String} checksum or null
*/
generateChecksum: function(lines)
{
let stream = null;
try
{
// Checksum is an MD5 checksum (base64-encoded without the trailing "=") of
// all lines in UTF-8 without the checksum line, joined with "\n".
let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"].createInstance(Ci.nsIScriptableUnicodeConverter);
converter.charset = "UTF-8";
stream = converter.convertToInputStream(lines.join("\n"));
let hashEngine = Cc["@mozilla.org/security/hash;1"].createInstance(Ci.nsICryptoHash);
hashEngine.init(hashEngine.MD5);
hashEngine.updateFromStream(stream, stream.available());
return hashEngine.finish(true).replace(/=+$/, "");
}
catch (e)
{
return null;
}
finally
{
if (stream)
stream.close();
}
},
/**
* Opens filter preferences dialog or focuses an already open dialog.
* @param {Filter} [filter] filter to be selected
*/
openFiltersDialog: function(filter)
{
var dlg = Utils.windowMediator.getMostRecentWindow("abp:filters");
if (dlg)
{
try
{
dlg.focus();
}
catch (e) {}
if (filter)
dlg.SubscriptionActions.selectFilter(filter);
}
else
{
Utils.windowWatcher.openWindow(null, "chrome://@ADDON_CHROME_NAME@/content/filters.xul", "_blank", "chrome,centerscreen,resizable,dialog=no", {wrappedJSObject: filter});
}
},
/**
* Opens a URL in the browser window. If browser window isn't passed as parameter,
* this function attempts to find a browser window. If an event is passed in
* it should be passed in to the browser if possible (will e.g. open a tab in
* background depending on modifiers keys).
*/
loadInBrowser: function(/**String*/ url, /**Window*/ currentWindow, /**Event*/ event)
{
let abpHooks = currentWindow ? currentWindow.document.getElementById("abp-hooks") : null;
if (!abpHooks || !abpHooks.addTab)
{
let enumerator = Utils.windowMediator.getZOrderDOMWindowEnumerator(null, true);
if (!enumerator.hasMoreElements())
{
// On Linux the list returned will be empty, see bug 156333. Fall back to random order.
enumerator = Utils.windowMediator.getEnumerator(null);
}
while (enumerator.hasMoreElements())
{
let window = enumerator.getNext().QueryInterface(Ci.nsIDOMWindow);
abpHooks = window.document.getElementById("abp-hooks");
if (abpHooks && abpHooks.addTab)
{
if (!currentWindow)
window.focus();
break;
}
}
}
if (abpHooks && abpHooks.addTab)
abpHooks.addTab(url, event);
else
{
let protocolService = Cc["@mozilla.org/uriloader/external-protocol-service;1"].getService(Ci.nsIExternalProtocolService);
protocolService.loadURI(Utils.makeURI(url), null);
}
},
/**
* Opens a pre-defined documentation link in the browser window. This will
* send the UI language to adblockplus.org so that the correct language
* version of the page can be selected.
*/
loadDocLink: function(/**String*/ linkID)
{
let baseURL = "resource://@ADDON_CHROME_NAME@/modules/";
Cu.import(baseURL + "Prefs.jsm");
let link = Prefs.documentation_link.replace(/%LINK%/g, linkID).replace(/%LANG%/g, Utils.appLocale);
Utils.loadInBrowser(link);
},
/**
* Formats a unix time according to user's locale.
* @param {Integer} time unix time in milliseconds
* @return {String} formatted date and time
*/
formatTime: function(time)
{
try
{
let date = new Date(time);
return Utils.dateFormatter.FormatDateTime("", Ci.nsIScriptableDateFormat.dateFormatShort,
Ci.nsIScriptableDateFormat.timeFormatNoSeconds,
date.getFullYear(), date.getMonth() + 1, date.getDate(),
date.getHours(), date.getMinutes(), date.getSeconds());
}
catch(e)
{
// Make sure to return even on errors
Cu.reportError(e);
return "";
}
},
/**
* Checks whether any of the prefixes listed match the application locale,
* returns matching prefix if any.
*/
checkLocalePrefixMatch: function(/**String*/ prefixes) /**String*/
{
if (!prefixes)
return null;
let appLocale = Utils.appLocale;
for each (let prefix in prefixes.split(/,/))
if (new RegExp("^" + prefix + "\\b").test(appLocale))
return prefix;
return null;
},
/**
* Chooses the best filter subscription for user's language.
* XXX: Removed code that will select at random
*/
chooseFilterSubscription: function(/**NodeList*/ subscriptions) /**Node*/
{
let selectedItem = null;
let selectedPrefix = null;
let matchCount = 0;
for (let i = 0; i < subscriptions.length; i++)
{
let subscription = subscriptions[i];
if (!selectedItem)
selectedItem = subscription;
let prefix = Utils.checkLocalePrefixMatch(subscription.getAttribute("prefixes"));
if (prefix)
{
if (!selectedPrefix || selectedPrefix.length < prefix.length)
{
selectedItem = subscription;
selectedPrefix = prefix;
matchCount = 1;
}
}
}
return selectedItem;
},
/**
* Saves sidebar state before detaching/reattaching
*/
setParams: function(params)
{
sidebarParams = params;
},
/**
* Retrieves and removes sidebar state after detaching/reattaching
*/
getParams: function()
{
let ret = sidebarParams;
sidebarParams = null;
return ret;
},
/**
* Randomly generated class for collapsed nodes.
* @type String
*/
collapsedClass: null,
/**
* Nodes scheduled for post-processing (might be null).
* @type Array of Node
*/
scheduledNodes: null,
/**
* Schedules a node for post-processing.
*/
schedulePostProcess: function(node)
{
if (Utils.scheduledNodes)
Utils.scheduledNodes.push(node);
else
{
Utils.scheduledNodes = [node];
Utils.runAsync(Utils.postProcessNodes);
}
},
/**
* Processes nodes scheduled for post-processing (typically hides them).
*/
postProcessNodes: function()
{
let nodes = Utils.scheduledNodes;
Utils.scheduledNodes = null;
for each (let node in nodes)
{
// adjust frameset's cols/rows for frames
let parentNode = node.parentNode;
if (parentNode && parentNode instanceof Ci.nsIDOMHTMLFrameSetElement)
{
let hasCols = (parentNode.cols && parentNode.cols.indexOf(",") > 0);
let hasRows = (parentNode.rows && parentNode.rows.indexOf(",") > 0);
if ((hasCols || hasRows) && !(hasCols && hasRows))
{
let index = -1;
for (let frame = node; frame; frame = frame.previousSibling)
if (frame instanceof Ci.nsIDOMHTMLFrameElement || frame instanceof Ci.nsIDOMHTMLFrameSetElement)
index++;
let property = (hasCols ? "cols" : "rows");
let weights = parentNode[property].split(",");
weights[index] = "0";
parentNode[property] = weights.join(",");
}
}
else
node.className += " " + Utils.collapsedClass;
}
},
/**
* Verifies RSA signature. The public key and signature should be base64-encoded.
*/
verifySignature: function(/**String*/ key, /**String*/ signature, /**String*/ data) /**Boolean*/
{
if (!Utils.crypto)
return false;
// Maybe we did the same check recently, look it up in the cache
if (!("_cache" in Utils.verifySignature))
Utils.verifySignature._cache = new Cache(5);
let cache = Utils.verifySignature._cache;
let cacheKey = key + " " + signature + " " + data;
if (cacheKey in cache.data)
return cache.data[cacheKey];
else
cache.add(cacheKey, false);
let keyInfo, pubKey, context;
try
{
let keyItem = Utils.crypto.getSECItem(atob(key));
keyInfo = Utils.crypto.SECKEY_DecodeDERSubjectPublicKeyInfo(keyItem.address());
if (keyInfo.isNull())
throw new Error("SECKEY_DecodeDERSubjectPublicKeyInfo failed");
pubKey = Utils.crypto.SECKEY_ExtractPublicKey(keyInfo);
if (pubKey.isNull())
throw new Error("SECKEY_ExtractPublicKey failed");
let signatureItem = Utils.crypto.getSECItem(atob(signature));
context = Utils.crypto.VFY_CreateContext(pubKey, signatureItem.address(), Utils.crypto.SEC_OID_ISO_SHA_WITH_RSA_SIGNATURE, null);
if (context.isNull())
return false; // This could happen if the signature is invalid
let error = Utils.crypto.VFY_Begin(context);
if (error < 0)
throw new Error("VFY_Begin failed");
error = Utils.crypto.VFY_Update(context, data, data.length);
if (error < 0)
throw new Error("VFY_Update failed");
error = Utils.crypto.VFY_End(context);
if (error < 0)
return false;
cache.data[cacheKey] = true;
return true;
}
catch (e)
{
Cu.reportError(e);
return false;
}
finally
{
if (keyInfo && !keyInfo.isNull())
Utils.crypto.SECKEY_DestroySubjectPublicKeyInfo(keyInfo);
if (pubKey && !pubKey.isNull())
Utils.crypto.SECKEY_DestroyPublicKey(pubKey);
if (context && !context.isNull())
Utils.crypto.VFY_DestroyContext(context, true);
}
}
};
/**
* A cache with a fixed capacity, newer entries replace entries that have been
* stored first.
* @constructor
*/
function Cache(/**Integer*/ size)
{
this._ringBuffer = new Array(size);
this.data = {__proto__: null};
}
Cache.prototype =
{
/**
* Ring buffer storing hash keys, allows determining which keys need to be
* evicted.
* @type Array
*/
_ringBuffer: null,
/**
* Index in the ring buffer to be written next.
* @type Integer
*/
_bufferIndex: 0,
/**
* Cache data, maps values to the keys. Read-only access, for writing use
* add() method.
* @type Object
*/
data: null,
/**
* Adds a key and the corresponding value to the cache.
*/
add: function(/**String*/ key, value)
{
if (!(key in this.data))
{
// This is a new key - we need to add it to the ring buffer and evict
// another entry instead.
let oldKey = this._ringBuffer[this._bufferIndex];
if (typeof oldKey != "undefined")
delete this.data[oldKey];
this._ringBuffer[this._bufferIndex] = key;
this._bufferIndex++;
if (this._bufferIndex >= this._ringBuffer.length)
this._bufferIndex = 0;
}
this.data[key] = value;
},
/**
* Clears cache contents.
*/
clear: function()
{
this._ringBuffer = new Array(this._ringBuffer.length);
this.data = {__proto__: null};
}
}
// Getters for common services, this should be replaced by Services.jsm in future
XPCOMUtils.defineLazyServiceGetter(Utils, "categoryManager", "@mozilla.org/categorymanager;1", "nsICategoryManager");
XPCOMUtils.defineLazyServiceGetter(Utils, "ioService", "@mozilla.org/network/io-service;1", "nsIIOService");
XPCOMUtils.defineLazyServiceGetter(Utils, "threadManager", "@mozilla.org/thread-manager;1", "nsIThreadManager");
XPCOMUtils.defineLazyServiceGetter(Utils, "promptService", "@mozilla.org/embedcomp/prompt-service;1", "nsIPromptService");
XPCOMUtils.defineLazyServiceGetter(Utils, "effectiveTLD", "@mozilla.org/network/effective-tld-service;1", "nsIEffectiveTLDService");
XPCOMUtils.defineLazyServiceGetter(Utils, "netUtils", "@mozilla.org/network/util;1", "nsINetUtil");
XPCOMUtils.defineLazyServiceGetter(Utils, "styleService", "@mozilla.org/content/style-sheet-service;1", "nsIStyleSheetService");
XPCOMUtils.defineLazyServiceGetter(Utils, "prefService", "@mozilla.org/preferences-service;1", "nsIPrefService");
XPCOMUtils.defineLazyServiceGetter(Utils, "versionComparator", "@mozilla.org/xpcom/version-comparator;1", "nsIVersionComparator");
XPCOMUtils.defineLazyServiceGetter(Utils, "windowMediator", "@mozilla.org/appshell/window-mediator;1", "nsIWindowMediator");
XPCOMUtils.defineLazyServiceGetter(Utils, "windowWatcher", "@mozilla.org/embedcomp/window-watcher;1", "nsIWindowWatcher");
XPCOMUtils.defineLazyServiceGetter(Utils, "chromeRegistry", "@mozilla.org/chrome/chrome-registry;1", "nsIXULChromeRegistry");
XPCOMUtils.defineLazyServiceGetter(Utils, "systemPrincipal", "@mozilla.org/systemprincipal;1", "nsIPrincipal");
XPCOMUtils.defineLazyServiceGetter(Utils, "dateFormatter", "@mozilla.org/intl/scriptabledateformat;1", "nsIScriptableDateFormat");
XPCOMUtils.defineLazyServiceGetter(Utils, "childMessageManager", "@mozilla.org/childprocessmessagemanager;1", "nsISyncMessageSender");
XPCOMUtils.defineLazyServiceGetter(Utils, "parentMessageManager", "@mozilla.org/parentprocessmessagemanager;1", "nsIFrameMessageManager");
XPCOMUtils.defineLazyServiceGetter(Utils, "httpProtocol", "@mozilla.org/network/protocol;1?name=http", "nsIHttpProtocolHandler");
XPCOMUtils.defineLazyServiceGetter(Utils, "clipboard", "@mozilla.org/widget/clipboard;1", "nsIClipboard");
XPCOMUtils.defineLazyServiceGetter(Utils, "clipboardHelper", "@mozilla.org/widget/clipboardhelper;1", "nsIClipboardHelper");
XPCOMUtils.defineLazyGetter(Utils, "crypto", function()
{
try
{
let ctypes = Components.utils.import("resource://gre/modules/ctypes.jsm", null).ctypes;
let nsslib = ctypes.open(ctypes.libraryName("nss3"));
let result = {};
// seccomon.h
result.siUTF8String = 14;
// secoidt.h
result.SEC_OID_ISO_SHA_WITH_RSA_SIGNATURE = 15;
// The following types are opaque to us
result.VFYContext = ctypes.void_t;
result.SECKEYPublicKey = ctypes.void_t;
result.CERTSubjectPublicKeyInfo = ctypes.void_t;
/*
* seccomon.h
* struct SECItemStr {
* SECItemType type;
* unsigned char *data;
* unsigned int len;
* };
*/
result.SECItem = ctypes.StructType("SECItem", [
{type: ctypes.int},
{data: ctypes.unsigned_char.ptr},
{len: ctypes.int}
]);
/*
* cryptohi.h
* extern VFYContext *VFY_CreateContext(SECKEYPublicKey *key, SECItem *sig,
* SECOidTag sigAlg, void *wincx);
*/
result.VFY_CreateContext = nsslib.declare(
"VFY_CreateContext",
ctypes.default_abi, result.VFYContext.ptr,
result.SECKEYPublicKey.ptr,
result.SECItem.ptr,
ctypes.int,
ctypes.voidptr_t
);
/*
* cryptohi.h
* extern void VFY_DestroyContext(VFYContext *cx, PRBool freeit);
*/
result.VFY_DestroyContext = nsslib.declare(
"VFY_DestroyContext",
ctypes.default_abi, ctypes.void_t,
result.VFYContext.ptr,
ctypes.bool
);
/*
* cryptohi.h
* extern SECStatus VFY_Begin(VFYContext *cx);
*/
result.VFY_Begin = nsslib.declare("VFY_Begin",
ctypes.default_abi, ctypes.int,
result.VFYContext.ptr
);
/*
* cryptohi.h
* extern SECStatus VFY_Update(VFYContext *cx, const unsigned char *input,
* unsigned int inputLen);
*/
result.VFY_Update = nsslib.declare(
"VFY_Update",
ctypes.default_abi, ctypes.int,
result.VFYContext.ptr,
ctypes.unsigned_char.ptr,
ctypes.int
);
/*
* cryptohi.h
* extern SECStatus VFY_End(VFYContext *cx);
*/
result.VFY_End = nsslib.declare(
"VFY_End",
ctypes.default_abi, ctypes.int,
result.VFYContext.ptr
);
/*
* keyhi.h
* extern CERTSubjectPublicKeyInfo *
* SECKEY_DecodeDERSubjectPublicKeyInfo(SECItem *spkider);
*/
result.SECKEY_DecodeDERSubjectPublicKeyInfo = nsslib.declare(
"SECKEY_DecodeDERSubjectPublicKeyInfo",
ctypes.default_abi, result.CERTSubjectPublicKeyInfo.ptr,
result.SECItem.ptr
);
/*
* keyhi.h
* extern void SECKEY_DestroySubjectPublicKeyInfo(CERTSubjectPublicKeyInfo *spki);
*/
result.SECKEY_DestroySubjectPublicKeyInfo = nsslib.declare(
"SECKEY_DestroySubjectPublicKeyInfo",
ctypes.default_abi, ctypes.void_t,
result.CERTSubjectPublicKeyInfo.ptr
);
/*
* keyhi.h
* extern SECKEYPublicKey *
* SECKEY_ExtractPublicKey(CERTSubjectPublicKeyInfo *);
*/
result.SECKEY_ExtractPublicKey = nsslib.declare(
"SECKEY_ExtractPublicKey",
ctypes.default_abi, result.SECKEYPublicKey.ptr,
result.CERTSubjectPublicKeyInfo.ptr
);
/*
* keyhi.h
* extern void SECKEY_DestroyPublicKey(SECKEYPublicKey *key);
*/
result.SECKEY_DestroyPublicKey = nsslib.declare(
"SECKEY_DestroyPublicKey",
ctypes.default_abi, ctypes.void_t,
result.SECKEYPublicKey.ptr
);
// Convenience method
result.getSECItem = function(data)
{
var dataArray = new ctypes.ArrayType(ctypes.unsigned_char, data.length)();
for (let i = 0; i < data.length; i++)
dataArray[i] = data.charCodeAt(i) % 256;
return new result.SECItem(result.siUTF8String, dataArray, dataArray.length);
};
return result;
}
catch (e)
{
Cu.reportError(e);
// Expected, ctypes isn't supported in Gecko 1.9.2
return null;
}
});
if ("@mozilla.org/messenger/headerparser;1" in Cc)
XPCOMUtils.defineLazyServiceGetter(Utils, "headerParser", "@mozilla.org/messenger/headerparser;1", "nsIMsgHeaderParser");
else
Utils.headerParser = null;
+29
View File
@@ -0,0 +1,29 @@
# vim: set filetype=python:
# 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/.
EXTRA_PP_JS_MODULES += [
'AppIntegration.jsm',
'Bootstrap.jsm',
'ContentPolicy.jsm',
'ContentPolicyRemote.jsm',
'ElemHide.jsm',
'ElemHideRemote.jsm',
'FilterClasses.jsm',
'FilterListener.jsm',
'FilterNotifier.jsm',
'FilterStorage.jsm',
'IO.jsm',
'Matcher.jsm',
'ObjectTabs.jsm',
'Prefs.jsm',
'Public.jsm',
'RequestNotifier.jsm',
'SubscriptionClasses.jsm',
'Sync.jsm',
'Synchronizer.jsm',
'TimeLine.jsm',
'Utils.jsm',
]
+14
View File
@@ -0,0 +1,14 @@
# vim: set filetype=python:
# 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/.
DIRS += [
'addon',
'components',
'content',
'locale',
'modules',
'skin',
]
+68
View File
@@ -0,0 +1,68 @@
# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# 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/.
# We could use confvars.sh and configure.in but for some reason it gets a bit messy...
# Just use what we have.. While UXP may reduce the importance of moz.configure we
# likely will never be able to eliminate it entirely... Oh well.
# Disables building the platform code
# NOTE: This negates any possiblity of an XPIDL or Binary XPCOM Components
set_config('MOZ_DISABLE_PLATFORM', '1')
# Minimum required by moz.configure since we don't want everything in
# toolkit/moz.configure when the platform isn't built
set_config('MOZ_PACKAGER_FORMAT', 'omni')
set_config('MOZ_JAR_MAKER_FILE_FORMAT', 'flat')
# moz.configure won't allow setting normal variables in a global context so
# create a function that can be called to get specific info
def confvars(key):
metadata = {
'name': 'ABPrime',
'id': 'abprime@projects.binaryoutcast.com',
'version': '1.0.7',
'creator': 'Binary Outcast',
'description': 'Bootstrapped adblocking is yesterday!',
'slug': 'abprime',
'chrome': 'abprime',
'targetApp': 'Pale Moon',
'targetID': '{8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}',
'targetMinVer': '27.0.0',
'targetMaxVer': '28.*',
'basilisk': '1'
}
return metadata[key]
# These will set the config/substs
set_config('ADDON_NAME', confvars('name'))
set_config('ADDON_ID', confvars('id'))
set_config('ADDON_VERSION', confvars('version'))
set_config('ADDON_AUTHOR', confvars('creator'))
set_config('ADDON_SHORT_DESC', confvars('description'))
set_config('ADDON_XPI_NAME', confvars('slug'))
set_config('ADDON_CHROME_NAME', confvars('chrome'))
set_config('ADDON_TARGET_APP_NAME', confvars('targetApp'))
set_config('ADDON_TARGET_APP_ID', confvars('targetID'))
set_config('ADDON_TARGET_APP_MINVER', confvars('targetMinVer'))
set_config('ADDON_TARGET_APP_MAXVER', confvars('targetMaxVer'))
set_config('ADDON_TARGET_BASILISK', confvars('basilisk'))
# These will set defines
# Should weed them down to just what is required which I believe
# is just ADDON_TARGET_BASILISK
set_define('ADDON_NAME', confvars('name'))
set_define('ADDON_ID', confvars('id'))
set_define('ADDON_VERSION', confvars('version'))
set_define('ADDON_AUTHOR', confvars('creator'))
set_define('ADDON_SHORT_DESC', confvars('description'))
set_define('ADDON_XPI_NAME', confvars('slug'))
set_define('ADDON_CHROME_NAME', confvars('chrome'))
set_define('ADDON_TARGET_APP_NAME', confvars('targetApp'))
set_define('ADDON_TARGET_APP_ID', confvars('targetID'))
set_define('ADDON_TARGET_APP_MINVER', confvars('targetMinVer'))
set_define('ADDON_TARGET_APP_MAXVER', confvars('targetMaxVer'))
set_define('ADDON_TARGET_BASILISK', confvars('basilisk'))
+40
View File
@@ -0,0 +1,40 @@
/*
* This Source Code is subject to the terms of the Mozilla Public License
* version 2.0 (the "License"). You can obtain a copy of the License at
* http://mozilla.org/MPL/2.0/.
*/
@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
#mainBox
{
visibility: hidden;
}
#mainBox[loaded]
{
visibility: visible;
}
#title
{
font-size: 48px;
font-weight: bold;
}
#mainGroup
{
margin-top: 15px;
width: 450px;
height: 400px;
overflow: auto;
}
#homepageTitle, #authorsTitle, #contributorsTitle, #subscriptionAuthorsTitle, #translatorsTitle
{
font-weight: bold;
}
#description, #homepage, #authorsBox, #contributorsBox, #subscriptionAuthorsBox
{
margin-bottom: 10px;
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 356 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

+66
View File
@@ -0,0 +1,66 @@
/*
* This Source Code is subject to the terms of the Mozilla Public License
* version 2.0 (the "License"). You can obtain a copy of the License at
* http://mozilla.org/MPL/2.0/.
*/
@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
@namespace html url("http://www.w3.org/1999/xhtml");
/*
* Force left-to-right everywhere where we are displaying addresses
*/
.suggestion > .radio-label-box:-moz-locale-dir(rtl),
html|*.textbox-input:-moz-locale-dir(rtl)
{
direction: ltr;
text-align: end;
}
#patternGroup {
overflow: auto;
}
#anchorGroup {
padding-left: 20px;
}
#typeGroupLabel {
margin-top: 10px;
}
#typeGroup {
overflow: auto;
margin-bottom: 10px
}
:root:not([advancedMode="true"]) #options {
display: none;
}
#disabledWarning, #groupDisabledWarning, #regexpWarning, #shortpatternWarning, #matchWarning {
color: #E00000;
}
#disabledWarning > *, #groupDisabledWarning > * {
margin: 0px;
font-size: inherit;
}
.text-link {
font-size: 80%;
-moz-user-focus: ignore;
}
.help {
color: #0000E0;
border-bottom: 1px dotted #0000E0;
cursor: help;
margin: 0px;
padding: 0px;
}
tooltip {
/* Gecko 1.8.1 doesn't support multiline tooltips :-( */
max-width: none;
}
+208
View File
@@ -0,0 +1,208 @@
/*
* This Source Code is subject to the terms of the Mozilla Public License
* version 2.0 (the "License"). You can obtain a copy of the License at
* http://mozilla.org/MPL/2.0/.
*/
@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
#buttons
{
margin-top: 10px;
}
#noSubscriptions
{
font-style: italic;
}
.localeMatch
{
font-weight: bold;
}
.selectSubscriptionItem
{
margin: 0px;
}
.subscription
{
padding: 5px;
}
.subscription:not(:last-child)
{
border-bottom: 1px solid rgba(0, 0, 0, 0.25);
}
.subscription:not([selected="true"]) > .disabled
{
}
.subscription:not([selected="true"]) > .disabled .titleBox
{
color: #808080;
}
.subscription:not([selected="true"]) > .disabled .status
{
color: #808080;
}
.titleBox .title,
.titleBox > .titleEditor
{
font-weight: bold;
}
.subscription description, .subscription textbox
{
margin: 0px !important;
padding: 0px !important;
border-width: 0px !important;
-moz-appearance: none !important;
}
.subscription .link
{
text-decoration: underline;
cursor: pointer;
}
.subscription .warning
{
color: #FF0000;
}
.enabledCheckbox
{
padding: 2px;
-moz-margin-end: 10px;
}
.enabledCheckbox:focus
{
outline: 1px dotted gray;
}
.enabledCheckbox .checkbox-label-box
{
display: none;
}
.actionButton
{
font: -moz-info;
}
splitter
{
border-width: 0px !important;
}
#filtersTooltip
{
max-width: none;
}
.tooltipLabel
{
font-weight: bold;
-moz-margin-end: 10px;
}
#tooltip-additional
{
color: #C00000;
margin-top: 10px;
}
tree
{
margin: 0px;
}
#col-slow {
text-align: center;
}
#col-hitcount, #col-lasthit {
text-align: right;
}
#col-hitcount
{
min-width: 60px;
}
#col-enabled
{
min-width: 48px;
}
#col-slow
{
min-width: 30px;
}
/*
* Force left-to-right for filter text but not comments
*/
treechildren:-moz-locale-dir(rtl)::-moz-tree-cell(col-filter, type-invalid),
treechildren:-moz-locale-dir(rtl)::-moz-tree-cell(col-filter, type-whitelist),
treechildren:-moz-locale-dir(rtl)::-moz-tree-cell(col-filter, type-filterlist),
treechildren:-moz-locale-dir(rtl)::-moz-tree-cell(col-filter, type-elemhide)
{
direction: ltr;
text-align: end;
}
treechildren::-moz-tree-cell-text(col-filter, dummy-true)
{
font-style: italic;
}
treechildren::-moz-tree-cell-text(col-filter, type-whitelist, selected-false)
{
color: #008000;
}
treechildren::-moz-tree-cell-text(col-filter, type-elemhide, selected-false)
{
color: #000080;
}
treechildren::-moz-tree-cell-text(col-slow)
{
font-size: 0px;
}
treechildren::-moz-tree-cell-text(col-filter, disabled-true, selected-false)
{
color: #808080;
}
treechildren::-moz-tree-cell-text(col-filter, type-comment, selected-false)
{
color: #808080;
}
treechildren::-moz-tree-cell-text(col-filter, type-invalid, selected-false)
{
color: #C00000;
}
treechildren::-moz-tree-image(col-enabled, disabled-true)
{
list-style-image: url(checkbox.png);
-moz-image-region: rect(13px 13px 26px 0px);
}
treechildren::-moz-tree-image(col-enabled, disabled-false)
{
list-style-image: url(checkbox.png);
-moz-image-region: rect(0px 13px 13px 0px);
}
treechildren::-moz-tree-image(col-slow, slow-true)
{
list-style-image: url(slow.png);
}
.findbar-highlight
{
display: none;
}
+52
View File
@@ -0,0 +1,52 @@
/*
* This Source Code is subject to the terms of the Mozilla Public License
* version 2.0 (the "License"). You can obtain a copy of the License at
* http://mozilla.org/MPL/2.0/.
*/
@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
:root
{
max-width: 500px;
background-image: url(abp-icon-big.png);
background-repeat: no-repeat;
background-position: 95% 5%;
font-size: 130%;
}
.sectionTitle
{
font-weight: bold;
}
#changeDescription > .text-link
{
margin: 0px;
}
#listNameContainer,
#listNone
{
margin: 10px 40px;
}
#listNone
{
font-style: italic;
}
#acceptableAds
{
font-size: 90%;
font-weight: bold;
}
#acceptableAds > *
{
font-weight: normal;
}
.sectionContainer
{
margin: 1em 2em;
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 533 B

+25
View File
@@ -0,0 +1,25 @@
# 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/.
#filter substitution
[.] chrome.jar:
% skin @ADDON_CHROME_NAME@ classic/1.0 chrome/skin/
skin/about.css
skin/abp-icon-big.png
skin/abp-status-16.png
skin/abp-status.png
skin/checkbox.png
skin/close.png
skin/composer.css
skin/filters.css
skin/firstRun.css
skin/item-state.png
skin/overlay.css
skin/sendReport.css
skin/sidebar.css
skin/slow.png
skin/subscriptionSelection.css
% style chrome://global/content/customizeToolbar.xul chrome://@ADDON_CHROME_NAME@/skin/overlay.css
+6
View File
@@ -0,0 +1,6 @@
# vim: set filetype=python:
# 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/.
JAR_MANIFESTS += ['jar.mn']
+165
View File
@@ -0,0 +1,165 @@
/*
* This Source Code is subject to the terms of the Mozilla Public License
* version 2.0 (the "License"). You can obtain a copy of the License at
* http://mozilla.org/MPL/2.0/.
*/
@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
#abp-status
{
cursor: pointer;
}
toolbar[iconsize="small"] #abp-toolbarbutton,
#PersonalToolbar #abp-toolbarbutton,
#header-view-toolbar > #abp-toolbarbutton,
#abp-status {
list-style-image: url("abp-status-16.png");
-moz-image-region: rect(0px, 16px, 16px, 0px);
}
toolbar[iconsize="small"] #abp-toolbarbutton[abpstate="disabled"],
#PersonalToolbar #abp-toolbarbutton[abpstate="disabled"],
#header-view-toolbar > #abp-toolbarbutton[abpstate="disabled"],
#abp-status[abpstate="disabled"],
toolbar[iconsize="small"] #abp-toolbarbutton[abpstate="whitelisted"],
#PersonalToolbar #abp-toolbarbutton[abpstate="whitelisted"],
#header-view-toolbar > #abp-toolbarbutton[abpstate="whitelisted"],
#abp-status[abpstate="whitelisted"] {
-moz-image-region: rect(16px, 16px, 32px, 0px);
}
#abp-toolbar-popup {
list-style-image: none;
-moz-image-region: rect(0px, 0px, 0px, 0px);
}
toolbox[vertical="true"] toolbar #abp-toolbarbutton dropmarker {
display: none !important;
}
menuitem[default="true"] {
font-weight: bold;
}
#abp-toolbarbutton,
#abp-site-info {
list-style-image: url("abp-status.png");
-moz-image-region: rect(0px, 24px, 24px, 0px);
}
#abp-toolbarbutton[abpstate="disabled"],
#abp-toolbarbutton[abpstate="whitelisted"],
#abp-site-info[abpaction="enable"],
#abp-site-info[abpaction="enable_site"] {
-moz-image-region: rect(24px, 24px, 48px, 0px);
}
/* Hack: force the label to be displayed below icon for type="menu" */
#abp-toolbarbutton[type="menu"]
{
-moz-box-orient: horizontal;
}
toolbar[mode="full"]:not([labelalign="end"]) #abp-toolbarbutton[type="menu"]
{
-moz-binding: url("chrome://global/content/bindings/toolbarbutton.xml#menu-vertical");
}
/* Thunderbird-specific toolbar icon styles */
#header-view-toolbar > #abp-toolbarbutton
{
-moz-appearance: dualbutton;
padding: 0px !important;
}
/* Hide toolbar icon text in Thunderbird to save space */
#header-view-toolbar > #abp-toolbarbutton .toolbarbutton-text
{
display: none;
}
/* SeaMonkey expects the icon to be rather large, add margin */
#mail-toolbox #abp-toolbarbutton .toolbarbutton-icon
{
margin-top: 5px;
}
#abp-status-image {
margin-left: 10px;
margin-right: 10px;
}
#abp-site-info .pageaction-image {
width: 32px;
height: 32px;
padding: 4px;
}
#abp-toolbarbutton > toolbarbutton {
/* Argh, Songbird defines image region directly on the anonymous toolbarbutton element */
-moz-image-region: inherit !important;
}
#abp-tooltip {
max-width: none;
}
#abp-tooltip label {
font-weight: bold;
margin-bottom: 0px;
}
#abp-tooltip description:not([hidden="true"])+label {
margin-top: 10px;
}
#abp-sidebar-title {
padding-left: 4px;
}
#abp-sidebar-toolbar {
display: -moz-box !important;
visibility: visible !important;
}
#abp-sidebar-close {
padding: 4px 2px;
border-style: none !important;
-moz-user-focus: normal;
list-style-image: url("close.png");
-moz-appearance: none;
-moz-image-region: rect(0px, 14px, 14px, 0px);
}
#abp-sidebar-close:hover {
-moz-image-region: rect(0px, 28px, 14px, 14px);
}
#abp-sidebar-close:hover:active {
-moz-image-region: rect(0px, 42px, 14px, 28px);
}
.abp-contributebutton
{
margin-top: 20px;
}
.abp-contributebutton-btn
{
font: -moz-info;
margin-left: 40px;
margin-right: 40px;
}
.abp-contributebutton-close
{
border-style: none !important;
-moz-user-focus: normal;
list-style-image: url("close.png");
-moz-appearance: none;
-moz-image-region: rect(0px, 14px, 14px, 0px);
}
.abp-contributebutton-close:hover
{
-moz-image-region: rect(0px, 28px, 14px, 14px);
}
+111
View File
@@ -0,0 +1,111 @@
/*
* This Source Code is subject to the terms of the Mozilla Public License
* version 2.0 (the "License"). You can obtain a copy of the License at
* http://mozilla.org/MPL/2.0/.
*/
@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
.wizard-header
{
-moz-binding: url(chrome://adblockplus/content/sendReport.xul#headerBinding) !important;
padding: 10px 5px !important;
}
.progressLabel
{
margin: 5px 0px;
text-align: center;
font-size: 110%;
font-weight: normal;
}
.progressLabel.active
{
font-weight: bold;
}
progressmeter
{
margin-top: 100px;
}
.radioDescription
{
-moz-margin-start: 32px;
}
radio, checkbox, .topLabel, #dataDeck
{
margin-top: 15px;
}
#recentReports
{
margin-top: 15px;
}
#recentReportsList
{
overflow-x: hidden;
overflow-y: auto;
}
#outdatedSubscriptionsList
{
margin: 10px 20px;
}
#issuesBox
{
overflow: auto;
}
#issuesChangeMessage
{
color: red;
}
#screenshotButtons
{
margin-top: 10px;
}
#screenshotBox
{
overflow-y: scroll;
}
#commentLengthWarning
{
color: red;
}
#commentLengthWarning[visible="false"]
{
visibility: hidden;
}
/*
* Force left-to-right everywhere where we are displaying addresses
*/
#data:-moz-locale-dir(rtl)
{
direction: ltr;
}
#sendReportError
{
color: red;
font-size: 150%;
}
#sendReportErrorLinks, #typeWarningTextLink
{
margin: 0px;
}
#sendReportErrorBox
{
margin-bottom: 10px;
}
+99
View File
@@ -0,0 +1,99 @@
/*
* This Source Code is subject to the terms of the Mozilla Public License
* version 2.0 (the "License"). You can obtain a copy of the License at
* http://mozilla.org/MPL/2.0/.
*/
@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
#suggestionsList {
margin: 0px;
}
#detachButton, #reattachButton:not([disabled="true"]) {
text-decoration: underline;
cursor: pointer;
}
#reattachButton[disabled="true"] {
color: GrayText;
}
#detachButton, #reattachButton {
font-size: 90%;
}
tooltip {
max-width: none;
}
#tooltipPreview {
margin:10px;
max-width: 300px;
max-height: 300px;
}
#tooltip label {
font-weight: bold;
}
#contextBlock,
#contextWhitelist {
font-weight: bold;
}
#state {
min-width: 16px;
}
#size {
text-align: end;
}
/*
* Force left-to-right everywhere where we are displaying addresses
*/
treechildren:-moz-locale-dir(rtl)::-moz-tree-cell(col-filter),
treechildren:-moz-locale-dir(rtl)::-moz-tree-cell(col-address),
treechildren:-moz-locale-dir(rtl)::-moz-tree-cell(col-size)
{
direction: ltr;
text-align: end;
}
.disabledTextLabel
{
font-style: italic;
}
treechildren::-moz-tree-cell-text(state-filtered, selected-false),
treechildren::-moz-tree-cell-text(state-hidden, selected-false) {
color: #C00000;
}
treechildren::-moz-tree-cell-text(state-whitelisted, selected-false) {
color: #008000;
}
treechildren::-moz-tree-image(col-state, dummy-false)
{
list-style-image: url(item-state.png);
-moz-image-region: rect(0px 10px 10px 0px);
-moz-margin-start: 3px;
}
treechildren::-moz-tree-image(col-state, filter-disabled-true, dummy-false) {
-moz-image-region: rect(10px 10px 20px 0px);
}
treechildren::-moz-tree-image(col-state, state-filtered, dummy-false),
treechildren::-moz-tree-image(col-state, state-hidden, dummy-false) {
-moz-image-region: rect(20px 10px 30px 0px);
}
treechildren::-moz-tree-image(col-state, state-whitelisted, dummy-false) {
-moz-image-region: rect(30px 10px 40px 0px);
}
treechildren::-moz-tree-cell-text(col-filter, state-hidden, selected-false) {
color: #000080;
}
treechildren::-moz-tree-cell-text(col-filter, filter-disabled-true, selected-false) {
color: #C0C0C0;
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 718 B

+58
View File
@@ -0,0 +1,58 @@
/*
* This Source Code is subject to the terms of the Mozilla Public License
* version 2.0 (the "License"). You can obtain a copy of the License at
* http://mozilla.org/MPL/2.0/.
*/
@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
dialog
{
width: 550px;
}
*[invisible="true"]
{
visibility: hidden;
}
#supplementMessage
{
color: #F00000;
}
.localeMatch
{
font-weight: bold;
}
#all-subscriptions-loading
{
margin: 50px;
}
#all-subscriptions
{
min-height: 200px;
}
#all-subscriptions > richlistitem > .variant
{
width: 200px;
}
#all-subscriptions > richlistitem:not(:first-child) > .subscriptionTitle,
#all-subscriptions > richlistitem:not(:first-child) > .subscriptionTitle + .variant
{
border-top: 1px dashed black;
margin-top: 0px;
padding-top: 4px;
}
#supplementMessage
{
margin-top: 5px;
}
#supplementMessage > label
{
margin-left: 0px;
margin-right: 0px;
}
-23
View File
@@ -1,23 +0,0 @@
# vim: set filetype=python:
# 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/.
if CONFIG['MOZ_COMPOSER']:
DIRS += ['/editor/ui']
if CONFIG['MOZ_MAILNEWS']:
DIRS += [
'/modules/ldap',
'/modules/mork',
'/mailnews',
]
if CONFIG['MOZ_CALENDAR']:
DIRS += [
'/modules/libical',
'/calendar/lightning',
'/calendar/timezones'
]
DIRS += ['/communicator']
+14
View File
@@ -0,0 +1,14 @@
/* 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/. */
pref("inspector.blink.border-color", "#CC0000");
pref("inspector.blink.border-width", 2);
pref("inspector.blink.duration", 1200);
pref("inspector.blink.on", true);
pref("inspector.blink.speed", 100);
pref("inspector.blink.invert", false);
pref("inspector.dom.showAnon", true);
pref("inspector.dom.showWhitespaceNodes", true);
pref("inspector.dom.showAccessibleNodes", false);
pref("inspector.dom.showProcessingInstructions", true);

Some files were not shown because too many files have changed in this diff Show More