Index: background.js |
=================================================================== |
--- a/background.js |
+++ b/background.js |
@@ -1,6 +1,6 @@ |
/* |
* This file is part of Adblock Plus <http://adblockplus.org/>, |
- * Copyright (C) 2006-2013 Eyeo GmbH |
+ * Copyright (C) 2006-2014 Eyeo GmbH |
* |
* Adblock Plus is free software: you can redistribute it and/or modify |
* it under the terms of the GNU General Public License version 3 as |
@@ -26,6 +26,13 @@ |
{ |
this.Subscription = Subscription; |
this.DownloadableSubscription = DownloadableSubscription; |
+ this.SpecialSubscription = SpecialSubscription; |
+} |
+with(require("whitelisting")) |
+{ |
+ this.isWhitelisted = isWhitelisted; |
+ this.isFrameWhitelisted = isFrameWhitelisted; |
+ this.processKeyException = processKeyException; |
} |
var FilterStorage = require("filterStorage").FilterStorage; |
var ElemHide = require("elemHide").ElemHide; |
@@ -34,13 +41,20 @@ |
var Synchronizer = require("synchronizer").Synchronizer; |
var Utils = require("utils").Utils; |
var Notification = require("notification").Notification; |
+var initAntiAdblockNotification = require("antiadblockInit").initAntiAdblockNotification; |
// Some types cannot be distinguished |
RegExpFilter.typeMap.OBJECT_SUBREQUEST = RegExpFilter.typeMap.OBJECT; |
RegExpFilter.typeMap.MEDIA = RegExpFilter.typeMap.FONT = RegExpFilter.typeMap.OTHER; |
-var isFirstRun = false; |
+// Chrome on Linux does not fully support chrome.notifications until version 35 |
+// https://code.google.com/p/chromium/issues/detail?id=291485 |
+var canUseChromeNotifications = require("info").platform == "chromium" |
+ && "notifications" in chrome |
+ && (navigator.platform.indexOf("Linux") == -1 || parseInt(require("info").applicationVersion) > 34); |
+ |
var seenDataCorruption = false; |
+var filterlistsReinitialized = false; |
require("filterNotifier").FilterNotifier.addListener(function(action) |
{ |
if (action == "load") |
@@ -48,15 +62,32 @@ |
var importingOldData = importOldData(); |
var addonVersion = require("info").addonVersion; |
- var prevVersion = localStorage.currentVersion; |
- if (prevVersion != addonVersion) |
+ var prevVersion = ext.storage.currentVersion; |
+ |
+ // There are no filters stored so we need to reinitialize all filterlists |
+ if (!FilterStorage.firstRun && FilterStorage.subscriptions.length === 0) |
{ |
- isFirstRun = !prevVersion; |
- localStorage.currentVersion = addonVersion; |
+ filterlistsReinitialized = true; |
+ prevVersion = null; |
+ } |
+ |
+ if (prevVersion != addonVersion || FilterStorage.firstRun) |
+ { |
+ seenDataCorruption = prevVersion && FilterStorage.firstRun; |
+ ext.storage.currentVersion = addonVersion; |
if (!importingOldData) |
addSubscription(prevVersion); |
} |
+ |
+ if (canUseChromeNotifications) |
+ initChromeNotifications(); |
+ initAntiAdblockNotification(); |
} |
+ |
+ // update browser actions when whitelisting might have changed, |
+ // due to loading filters or saving filter changes |
+ if (action == "load" || action == "save") |
+ refreshIconAndContextMenuForAllPages(); |
}); |
// Special-case domains for which we cannot use style-based hiding rules. |
@@ -68,76 +99,57 @@ |
var deprecatedOptions = ["specialCaseYouTube", "experimental", "disableInlineTextAds"]; |
deprecatedOptions.forEach(function(option) |
{ |
- if (option in localStorage) |
- delete localStorage[option]; |
+ if (option in ext.storage) |
+ delete ext.storage[option]; |
}); |
} |
-// Sets options to defaults, upgrading old options from previous versions as necessary |
-function setDefaultOptions() |
-{ |
- function defaultOptionValue(opt, val) |
- { |
- if(!(opt in localStorage)) |
- localStorage[opt] = val; |
- } |
- |
- defaultOptionValue("shouldShowBlockElementMenu", "true"); |
- |
- removeDeprecatedOptions(); |
-} |
- |
-// Upgrade options before we do anything else. |
-setDefaultOptions(); |
- |
-/** |
- * Checks whether a page is whitelisted. |
- * @param {String} url |
- * @param {String} [parentUrl] URL of the parent frame |
- * @param {String} [type] content type to be checked, default is "DOCUMENT" |
- * @return {Filter} filter that matched the URL or null if not whitelisted |
- */ |
-function isWhitelisted(url, parentUrl, type) |
-{ |
- // Ignore fragment identifier |
- var index = url.indexOf("#"); |
- if (index >= 0) |
- url = url.substring(0, index); |
- |
- var result = defaultMatcher.matchesAny(url, type || "DOCUMENT", extractHostFromURL(parentUrl || url), false); |
- return (result instanceof WhitelistFilter ? result : null); |
-} |
+// Remove deprecated options before we do anything else. |
+removeDeprecatedOptions(); |
var activeNotification = null; |
+var contextMenuItem = { |
+ title: ext.i18n.getMessage("block_element"), |
+ contexts: ["image", "video", "audio"], |
+ onclick: function(srcUrl, page) |
+ { |
+ if (srcUrl) |
+ page.sendMessage({type: "clickhide-new-filter", filter: srcUrl}); |
+ } |
+}; |
+ |
// Adds or removes browser action icon according to options. |
-function refreshIconAndContextMenu(tab) |
+function refreshIconAndContextMenu(page) |
{ |
- if(!/^https?:/.test(tab.url)) |
- return; |
+ var whitelisted = isWhitelisted(page.url); |
var iconFilename; |
- if (require("info").platform == "safari") |
- // There is no grayscale version of the icon for whitelisted tabs |
+ if (whitelisted && require("info").platform != "safari") |
+ // There is no grayscale version of the icon for whitelisted pages |
// when using Safari, because icons are grayscale already and icons |
- // aren't per tab in Safari. |
- iconFilename = "icons/abp-16.png" |
+ // aren't per page in Safari. |
+ iconFilename = "icons/abp-$size-whitelisted.png"; |
else |
+ iconFilename = "icons/abp-$size.png"; |
+ |
+ page.browserAction.setIcon(iconFilename); |
+ iconAnimation.registerPage(page, iconFilename); |
+ |
+ // show or hide the context menu entry dependent on whether |
+ // adblocking is active on that page |
+ page.contextMenus.removeAll(); |
+ |
+ if (Prefs.shouldShowBlockElementMenu && !whitelisted && /^https?:/.test(page.url)) |
+ page.contextMenus.create(contextMenuItem); |
+} |
+ |
+function refreshIconAndContextMenuForAllPages() |
+{ |
+ ext.pages.query({}, function(pages) |
{ |
- var excluded = isWhitelisted(tab.url); |
- iconFilename = excluded ? "icons/abp-19-whitelisted.png" : "icons/abp-19.png"; |
- } |
- |
- tab.browserAction.setIcon(iconFilename); |
- tab.browserAction.setTitle(ext.i18n.getMessage("name")); |
- |
- iconAnimation.registerTab(tab, iconFilename); |
- |
- // Set context menu status according to whether current tab has whitelisted domain |
- if (excluded) |
- chrome.contextMenus.removeAll(); |
- else |
- showContextMenu(); |
+ pages.forEach(refreshIconAndContextMenu); |
+ }); |
} |
/** |
@@ -217,15 +229,25 @@ |
addAcceptable = false; |
} |
+ // Add "anti-adblock messages" subscription for new users and users updating from old ABP versions |
+ if (!prevVersion || Services.vc.compare(prevVersion, "1.8") < 0) |
+ { |
+ var subscription = Subscription.fromURL(Prefs.subscriptions_antiadblockurl); |
+ if (subscription && !(subscription.url in FilterStorage.knownSubscriptions)) |
+ { |
+ subscription.disabled = true; |
+ FilterStorage.addSubscription(subscription); |
+ if (subscription instanceof DownloadableSubscription && !subscription.lastDownload) |
+ Synchronizer.execute(subscription); |
+ } |
+ } |
+ |
if (!addSubscription && !addAcceptable) |
return; |
function notifyUser() |
{ |
- ext.windows.getLastFocused(function(win) |
- { |
- win.openTab(ext.getURL("firstRun.html")); |
- }); |
+ ext.pages.open(ext.getURL("firstRun.html")); |
} |
if (addSubscription) |
@@ -255,91 +277,234 @@ |
notifyUser(); |
} |
-// Set up context menu for user selection of elements to block |
-function showContextMenu() |
+Prefs.addListener(function(name) |
{ |
- ext.contextMenus.removeAll(function() |
- { |
- if(typeof localStorage["shouldShowBlockElementMenu"] == "string" && localStorage["shouldShowBlockElementMenu"] == "true") |
- { |
- ext.contextMenus.create(ext.i18n.getMessage("block_element"), ["image", "video", "audio"], function(srcUrl, tab) |
- { |
- if(srcUrl) |
- tab.sendMessage({type: "clickhide-new-filter", filter: srcUrl}); |
- }); |
- } |
- }); |
-} |
+ if (name == "shouldShowBlockElementMenu") |
+ refreshIconAndContextMenuForAllPages(); |
+}); |
/** |
- * Opens options tab or focuses an existing one, within the last focused window. |
+ * Opens options page or focuses an existing one, within the last focused window. |
* @param {Function} callback function to be called with the |
- Tab object of the options tab |
+ Page object of the options page |
*/ |
function openOptions(callback) |
{ |
- ext.windows.getLastFocused(function(win) |
+ ext.pages.query({lastFocusedWindow: true}, function(pages) |
{ |
- win.getAllTabs(function(tabs) |
+ var optionsUrl = ext.getURL("options.html"); |
+ |
+ for (var i = 0; i < pages.length; i++) |
{ |
- var optionsUrl = ext.getURL("options.html"); |
+ var page = pages[i]; |
+ if (page.url == optionsUrl) |
+ { |
+ page.activate(); |
+ if (callback) |
+ callback(page); |
+ return; |
+ } |
+ } |
- for (var i = 0; i < tabs.length; i++) |
- { |
- if (tabs[i].url == optionsUrl) |
- { |
- tabs[i].activate(); |
- if (callback) |
- callback(tabs[i]); |
- return; |
- } |
- } |
- |
- win.openTab(optionsUrl, callback && function(tab) |
- { |
- tab.onCompleted.addListener(callback); |
- }); |
- }); |
+ ext.pages.open(optionsUrl, callback); |
}); |
} |
function prepareNotificationIconAndPopup() |
{ |
+ var animateIcon = (activeNotification.type !== "question"); |
activeNotification.onClicked = function() |
{ |
- iconAnimation.stop(); |
- activeNotification = null; |
+ if (animateIcon) |
+ iconAnimation.stop(); |
+ notificationClosed(); |
}; |
+ if (animateIcon) |
+ iconAnimation.update(activeNotification.type); |
+} |
- iconAnimation.update(activeNotification.severity); |
+function openNotificationLinks() |
+{ |
+ if (activeNotification.links) |
+ { |
+ activeNotification.links.forEach(function(link) |
+ { |
+ ext.windows.getLastFocused(function(win) |
+ { |
+ win.openTab(Utils.getDocLink(link)); |
+ }); |
+ }); |
+ } |
+} |
+ |
+function notificationButtonClick(buttonIndex) |
+{ |
+ if (activeNotification.type === "question") |
+ { |
+ Notification.triggerQuestionListeners(activeNotification.id, buttonIndex === 0); |
+ Notification.markAsShown(activeNotification.id); |
+ activeNotification.onClicked(); |
+ } |
+ else if (activeNotification.links && activeNotification.links[buttonIndex]) |
+ { |
+ ext.windows.getLastFocused(function(win) |
+ { |
+ win.openTab(Utils.getDocLink(activeNotification.links[buttonIndex])); |
+ }); |
+ } |
+} |
+ |
+function notificationClosed() |
+{ |
+ activeNotification = null; |
+} |
+ |
+function imgToBase64(url, callback) |
+{ |
+ var canvas = document.createElement("canvas"), |
+ ctx = canvas.getContext("2d"), |
+ img = new Image; |
+ img.src = url; |
+ img.onload = function() |
+ { |
+ canvas.height = img.height; |
+ canvas.width = img.width; |
+ ctx.drawImage(img, 0, 0); |
+ callback(canvas.toDataURL("image/png")); |
+ canvas = null; |
+ }; |
+} |
+ |
+function initChromeNotifications() |
+{ |
+ // Chrome hides notifications in notification center when clicked so we need to clear them |
+ function clearActiveNotification(notificationId) |
+ { |
+ if (activeNotification && activeNotification.type != "question" && !("links" in activeNotification)) |
+ return; |
+ |
+ chrome.notifications.clear(notificationId, function(wasCleared) |
+ { |
+ if (wasCleared) |
+ notificationClosed(); |
+ }); |
+ } |
+ |
+ chrome.notifications.onButtonClicked.addListener(function(notificationId, buttonIndex) |
+ { |
+ notificationButtonClick(buttonIndex); |
+ clearActiveNotification(notificationId); |
+ }); |
+ chrome.notifications.onClicked.addListener(clearActiveNotification); |
+ chrome.notifications.onClosed.addListener(notificationClosed); |
} |
function showNotification(notification) |
{ |
+ if (activeNotification && activeNotification.id === notification.id) |
+ return; |
+ |
activeNotification = notification; |
+ if (activeNotification.type === "critical" || activeNotification.type === "question") |
+ { |
+ var hasWebkitNotifications = typeof webkitNotifications !== "undefined"; |
+ if (hasWebkitNotifications && "createHTMLNotification" in webkitNotifications) |
+ { |
+ var notification = webkitNotifications.createHTMLNotification("notification.html"); |
+ notification.show(); |
+ prepareNotificationIconAndPopup(); |
+ return; |
+ } |
- if (activeNotification.severity === "critical" |
- && typeof webkitNotifications !== "undefined") |
- { |
- var notification = webkitNotifications.createHTMLNotification("notification.html"); |
- notification.show(); |
- notification.addEventListener("close", prepareNotificationIconAndPopup); |
+ var texts = Notification.getLocalizedTexts(notification); |
+ var title = texts.title || ""; |
+ var message = texts.message ? texts.message.replace(/<\/?(a|strong)>/g, "") : ""; |
+ var iconUrl = ext.getURL("icons/abp-128.png"); |
+ var hasLinks = activeNotification.links && activeNotification.links.length > 0; |
+ |
+ if (canUseChromeNotifications) |
+ { |
+ var opts = { |
+ type: "basic", |
+ title: title, |
+ message: message, |
+ buttons: [], |
+ priority: 2 // We use the highest priority to prevent the notification from closing automatically |
+ }; |
+ if (activeNotification.type === "question") |
+ { |
+ opts.buttons.push({title: ext.i18n.getMessage("overlay_notification_button_yes")}); |
+ opts.buttons.push({title: ext.i18n.getMessage("overlay_notification_button_no")}); |
+ } |
+ else |
+ { |
+ var regex = /<a>(.*?)<\/a>/g; |
+ var plainMessage = texts.message || ""; |
+ var match; |
+ while (match = regex.exec(plainMessage)) |
+ opts.buttons.push({title: match[1]}); |
+ } |
+ |
+ imgToBase64(iconUrl, function(iconData) |
+ { |
+ opts["iconUrl"] = iconData; |
+ chrome.notifications.create("", opts, function() {}); |
+ }); |
+ } |
+ else if (hasWebkitNotifications && "createNotification" in webkitNotifications && activeNotification.type !== "question") |
+ { |
+ if (hasLinks) |
+ message += " " + ext.i18n.getMessage("notification_without_buttons"); |
+ |
+ imgToBase64(iconUrl, function(iconData) |
+ { |
+ var notification = webkitNotifications.createNotification(iconData, title, message); |
+ notification.show(); |
+ notification.addEventListener("click", openNotificationLinks, false); |
+ notification.addEventListener("close", notificationClosed, false); |
+ }); |
+ } |
+ else |
+ { |
+ var message = title + "\n" + message; |
+ if (hasLinks) |
+ message += "\n\n" + ext.i18n.getMessage("notification_with_buttons"); |
+ |
+ var approved = confirm(message); |
+ if (activeNotification.type === "question") |
+ notificationButtonClick(approved ? 0 : 1); |
+ else if (approved) |
+ openNotificationLinks(); |
+ } |
} |
- else |
- prepareNotificationIconAndPopup(); |
+ prepareNotificationIconAndPopup(); |
} |
-/** |
- * This function is a hack - we only know the tabId and document URL for a |
- * message but we need to know the frame ID. Try to find it in webRequest"s |
- * frame data. |
- */ |
-function getFrameId(tab, url) |
+// This is a hack to speedup loading of the options page on Safari. |
+// Once we replaced the background page proxy with message passing |
+// this global function should removed. |
+function getUserFilters() |
{ |
- for (var frameId in frames.get(tab)) |
- if (getFrameUrl(tab, frameId) == url) |
- return frameId; |
- return -1; |
+ var filters = []; |
+ var exceptions = []; |
+ |
+ for (var i = 0; i < FilterStorage.subscriptions.length; i++) |
+ { |
+ var subscription = FilterStorage.subscriptions[i]; |
+ if (!(subscription instanceof SpecialSubscription)) |
+ continue; |
+ |
+ for (var j = 0; j < subscription.filters.length; j++) |
+ { |
+ var filter = subscription.filters[j]; |
+ if (filter instanceof WhitelistFilter && /^@@\|\|([^\/:]+)\^\$document$/.test(filter.text)) |
+ exceptions.push(RegExp.$1); |
+ else |
+ filters.push(filter.text); |
+ } |
+ } |
+ |
+ return {filters: filters, exceptions: exceptions}; |
} |
ext.onMessage.addListener(function (msg, sender, sendResponse) |
@@ -347,14 +512,13 @@ |
switch (msg.type) |
{ |
case "get-selectors": |
- var selectors = null; |
- var frameId = sender.tab ? getFrameId(sender.tab, msg.frameUrl) : -1; |
+ var selectors = []; |
- if (!isFrameWhitelisted(sender.tab, frameId, "DOCUMENT") && |
- !isFrameWhitelisted(sender.tab, frameId, "ELEMHIDE")) |
+ if (!isFrameWhitelisted(sender.page, sender.frame, "DOCUMENT") && |
+ !isFrameWhitelisted(sender.page, sender.frame, "ELEMHIDE")) |
{ |
var noStyleRules = false; |
- var host = extractHostFromURL(msg.frameUrl); |
+ var host = extractHostFromFrame(sender.frame); |
for (var i = 0; i < noStyleRulesHosts.length; i++) |
{ |
var noStyleHost = noStyleRulesHosts[i]; |
@@ -377,23 +541,21 @@ |
sendResponse(selectors); |
break; |
case "should-collapse": |
- var frameId = sender.tab ? getFrameId(sender.tab, msg.documentUrl) : -1; |
- |
- if (isFrameWhitelisted(sender.tab, frameId, "DOCUMENT")) |
+ if (isFrameWhitelisted(sender.page, sender.frame, "DOCUMENT")) |
{ |
sendResponse(false); |
break; |
} |
var requestHost = extractHostFromURL(msg.url); |
- var documentHost = extractHostFromURL(msg.documentUrl); |
+ var documentHost = extractHostFromFrame(sender.frame); |
var thirdParty = isThirdParty(requestHost, documentHost); |
var filter = defaultMatcher.matchesAny(msg.url, msg.mediatype, documentHost, thirdParty); |
if (filter instanceof BlockingFilter) |
{ |
var collapse = filter.collapse; |
if (collapse == null) |
- collapse = (localStorage.hidePlaceholders != "false"); |
+ collapse = Prefs.hidePlaceholders; |
sendResponse(collapse); |
} |
else |
@@ -402,9 +564,9 @@ |
case "get-domain-enabled-state": |
// Returns whether this domain is in the exclusion list. |
// The browser action popup asks us this. |
- if(sender.tab) |
+ if(sender.page) |
{ |
- sendResponse({enabled: !isWhitelisted(sender.tab.url)}); |
+ sendResponse({enabled: !isWhitelisted(sender.page.url)}); |
return; |
} |
break; |
@@ -416,15 +578,18 @@ |
} |
break; |
case "add-subscription": |
- openOptions(function(tab) |
+ openOptions(function(page) |
{ |
- tab.sendMessage(msg); |
+ page.sendMessage(msg); |
}); |
break; |
+ case "add-key-exception": |
+ processKeyException(msg.token, sender.page, sender.frame); |
+ break; |
case "forward": |
- if (sender.tab) |
+ if (sender.page) |
{ |
- sender.tab.sendMessage(msg.payload, sendResponse); |
+ sender.page.sendMessage(msg.payload, sendResponse); |
// Return true to indicate that we want to call |
// sendResponse asynchronously |
return true; |
@@ -436,23 +601,11 @@ |
} |
}); |
-// Show icon as browser action for all tabs that already exist |
-ext.windows.getAll(function(windows) |
+// update icon when page changes location |
+ext.pages.onLoading.addListener(function(page) |
{ |
- for (var i = 0; i < windows.length; i++) |
- { |
- windows[i].getAllTabs(function(tabs) |
- { |
- tabs.forEach(refreshIconAndContextMenu); |
- }); |
- } |
-}); |
- |
-// Update icon if a tab changes location |
-ext.tabs.onLoading.addListener(function(tab) |
-{ |
- tab.sendMessage({type: "clickhide-deactivate"}); |
- refreshIconAndContextMenu(tab); |
+ page.sendMessage({type: "clickhide-deactivate"}); |
+ refreshIconAndContextMenu(page); |
}); |
setTimeout(function() |