| Index: background.js | 
| =================================================================== | 
| --- a/background.js | 
| +++ b/background.js | 
| @@ -113,33 +113,37 @@ | 
| // Adds or removes page action icon according to options. | 
| function refreshIconAndContextMenu(tab) | 
| { | 
| - // The tab could have been closed by the time this function is called | 
| - if(!tab) | 
| + if(!/^https?:/.test(tab.url)) | 
| return; | 
| - var excluded = isWhitelisted(tab.url); | 
| - var iconFilename = excluded ? "icons/abp-19-whitelisted.png" : "icons/abp-19.png"; | 
| + var iconFilename; | 
| + if (require("info").platform == "safari") | 
| + // There is no grayscale version of the icon for whitelisted tabs | 
| + // when using Safari, because icons are grayscale already and icons | 
| + // aren't per tab in Safari. | 
| + iconFilename = "icons/abp-16.png" | 
| + else | 
| + { | 
| + var excluded = isWhitelisted(tab.url); | 
| + iconFilename = excluded ? "icons/abp-19-whitelisted.png" : "icons/abp-19.png"; | 
| + } | 
| - if (activeNotification) | 
| - startIconAnimation(tab, iconFilename); | 
| + tab.pageAction.setIcon(iconFilename); | 
| + tab.pageAction.setTitle(ext.i18n.getMessage("name")); | 
| + | 
| + iconAnimation.registerTab(tab, iconFilename); | 
| + | 
| + if (localStorage.shouldShowIcon == "false") | 
| + tab.pageAction.hide(); | 
| else | 
| - chrome.pageAction.setIcon({tabId: tab.id, path: iconFilename}); | 
| + tab.pageAction.show(); | 
| - // Only show icon for pages we can influence (http: and https:) | 
| - if(/^https?:/.test(tab.url)) | 
| - { | 
| - chrome.pageAction.setTitle({tabId: tab.id, title: "Adblock Plus"}); | 
| - if ("shouldShowIcon" in localStorage && localStorage["shouldShowIcon"] == "false") | 
| - chrome.pageAction.hide(tab.id); | 
| - else | 
| - chrome.pageAction.show(tab.id); | 
| - | 
| + if (require("info").platform == "chromium") // TODO: Implement context menus for Safari | 
| // Set context menu status according to whether current tab has whitelisted domain | 
| if (excluded) | 
| chrome.contextMenus.removeAll(); | 
| else | 
| showContextMenu(); | 
| - } | 
| } | 
| /** | 
| @@ -367,8 +371,9 @@ | 
| function notifyUser() | 
| { | 
| - chrome.tabs.create({ | 
| - url: chrome.extension.getURL("firstRun.html") | 
| + ext.windows.getLastFocused(function(win) | 
| + { | 
| + win.openTab(ext.getURL("firstRun.html")); | 
| }); | 
| } | 
| @@ -406,7 +411,7 @@ | 
| { | 
| if(typeof localStorage["shouldShowBlockElementMenu"] == "string" && localStorage["shouldShowBlockElementMenu"] == "true") | 
| { | 
| - chrome.contextMenus.create({'title': chrome.i18n.getMessage('block_element'), 'contexts': ['image', 'video', 'audio'], 'onclick': function(info, tab) | 
| + chrome.contextMenus.create({"title": chrome.i18n.getMessage("block_element"), "contexts": ["image", "video", "audio"], "onclick": function(info, tab) | 
| { | 
| if(info.srcUrl) | 
| chrome.tabs.sendRequest(tab.id, {reqtype: "clickhide-new-filter", filter: info.srcUrl}); | 
| @@ -416,150 +421,34 @@ | 
| } | 
| /** | 
| - * Opens Options window or focuses an existing one. | 
| - * @param {Function} callback function to be called with the window object of | 
| - * the Options window | 
| - */ | 
| + * Opens options tab 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 | 
| + */ | 
| function openOptions(callback) | 
| { | 
| - function findOptions(selectTab) | 
| + ext.windows.getLastFocused(function(win) | 
| { | 
| - var views = chrome.extension.getViews({type: "tab"}); | 
| - for (var i = 0; i < views.length; i++) | 
| - if ("startSubscriptionSelection" in views[i]) | 
| - return views[i]; | 
| + win.getAllTabs(function(tabs) | 
| + { | 
| + var optionsUrl = ext.getURL("options.html"); | 
| - return null; | 
| - } | 
| + for (var i = 0; i < tabs.length; i++) | 
| + { | 
| + if (tabs[i].url == optionsUrl) | 
| + { | 
| + tabs[i].activate(); | 
| + if (callback) | 
| + callback(tabs[i]); | 
| + return; | 
| + } | 
| + } | 
| - function selectOptionsTab() | 
| - { | 
| - chrome.windows.getAll({populate: true}, function(windows) | 
| - { | 
| - var url = chrome.extension.getURL("options.html"); | 
| - for (var i = 0; i < windows.length; i++) | 
| - for (var j = 0; j < windows[i].tabs.length; j++) | 
| - if (windows[i].tabs[j].url == url) | 
| - chrome.tabs.update(windows[i].tabs[j].id, {selected: true}); | 
| + win.openTab(optionsUrl, callback && function(tab) | 
| + { | 
| + tab.onCompleted.addListener(callback); | 
| + }); | 
| }); | 
| - } | 
| - | 
| - var view = findOptions(); | 
| - if (view) | 
| - { | 
| - selectOptionsTab(); | 
| - callback(view); | 
| - } | 
| - else | 
| - { | 
| - var onLoad = function() | 
| - { | 
| - var view = findOptions(); | 
| - if (view) | 
| - callback(view); | 
| - }; | 
| - | 
| - chrome.tabs.create({url: chrome.extension.getURL("options.html")}, function(tab) | 
| - { | 
| - if (tab.status == "complete") | 
| - onLoad(); | 
| - else | 
| - { | 
| - var id = tab.id; | 
| - var listener = function(tabId, changeInfo, tab) | 
| - { | 
| - if (tabId == id && changeInfo.status == "complete") | 
| - { | 
| - chrome.tabs.onUpdated.removeListener(listener); | 
| - onLoad(); | 
| - } | 
| - }; | 
| - chrome.tabs.onUpdated.addListener(listener); | 
| - } | 
| - }); | 
| - } | 
| -} | 
| - | 
| -var iconAnimationTimer = null; | 
| -var animatedIconTab = null; | 
| - | 
| -function stopIconAnimation() | 
| -{ | 
| - if (!iconAnimationTimer) | 
| - return; | 
| - | 
| - clearTimeout(iconAnimationTimer); | 
| - iconAnimationTimer = null; | 
| - animatedIconTab = null; | 
| -} | 
| - | 
| -function loadImages(imageFiles, callback) | 
| -{ | 
| - var images = {}; | 
| - var imagesLoaded = 0; | 
| - imageFiles.forEach(function(imageFile) | 
| - { | 
| - var image = new Image(); | 
| - image.src = imageFile; | 
| - image.addEventListener("load", function() | 
| - { | 
| - images[imageFile] = image; | 
| - if (++imagesLoaded === imageFiles.length) | 
| - callback(images); | 
| - }); | 
| - }); | 
| -} | 
| - | 
| -function startIconAnimation(tab, iconPath) | 
| -{ | 
| - stopIconAnimation(); | 
| - animatedIconTab = tab; | 
| - | 
| - var severitySuffix = activeNotification.severity === "critical" | 
| - ? "critical" : "information"; | 
| - var notificationIconPath = "icons/notification-" + severitySuffix + ".png"; | 
| - var iconFiles = [iconPath, notificationIconPath]; | 
| - loadImages(iconFiles, function(images) | 
| - { | 
| - var icon = images[iconPath]; | 
| - var notificationIcon = images[notificationIconPath]; | 
| - | 
| - var canvas = document.createElement("canvas"); | 
| - canvas.width = icon.width; | 
| - canvas.height = icon.height; | 
| - var context = canvas.getContext("2d"); | 
| - | 
| - var currentFrame = 0; | 
| - var frameOpacities = [0, 0.2, 0.4, 0.6, 0.8, | 
| - 1, 1, 1, 1, 1, | 
| - 0.8, 0.6, 0.4, 0.2, 0]; | 
| - | 
| - function animationStep() | 
| - { | 
| - var opacity = frameOpacities[currentFrame]; | 
| - context.clearRect(0, 0, canvas.width, canvas.height); | 
| - context.globalAlpha = 1; | 
| - context.drawImage(icon, 0, 0); | 
| - context.globalAlpha = opacity; | 
| - context.drawImage(notificationIcon, 0, 0); | 
| - var imageData = context.getImageData(0, 0, canvas.width, canvas.height); | 
| - chrome.pageAction.setIcon({tabId: tab.id, imageData: imageData}); | 
| - | 
| - var interval; | 
| - currentFrame++; | 
| - if (currentFrame < frameOpacities.length) | 
| - { | 
| - var duration = 3000; | 
| - interval = duration / frameOpacities.length; | 
| - } | 
| - else | 
| - { | 
| - currentFrame = 0; | 
| - interval = 10000; | 
| - } | 
| - iconAnimationTimer = setTimeout(animationStep, interval); | 
| - } | 
| - animationStep(); | 
| }); | 
| } | 
| @@ -567,19 +456,11 @@ | 
| { | 
| activeNotification.onClicked = function() | 
| { | 
| - var tab = animatedIconTab; | 
| - stopIconAnimation(); | 
| + iconAnimation.stop(); | 
| activeNotification = null; | 
| - refreshIconAndContextMenu(tab); | 
| }; | 
| - chrome.windows.getLastFocused({populate: true}, function(window) | 
| - { | 
| - chrome.tabs.query({active: true, windowId: window.id}, function(tabs) | 
| - { | 
| - tabs.forEach(refreshIconAndContextMenu); | 
| - }); | 
| - }); | 
| + iconAnimation.update(activeNotification.severity); | 
| } | 
| function showNotification(notification) | 
| @@ -599,44 +480,30 @@ | 
| /** | 
| * 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 | 
| + * message but we need to know the frame ID. Try to find it in webRequest"s | 
| * frame data. | 
| */ | 
| -function getFrameId(tabId, url) | 
| +function getFrameId(tab, url) | 
| { | 
| - if (tabId in frames) | 
| - { | 
| - for (var f in frames[tabId]) | 
| - { | 
| - if (getFrameUrl(tabId, f) == url) | 
| - return f; | 
| - } | 
| - } | 
| + for (var frameId in frames.get(tab)) | 
| + if (getFrameUrl(tab, frameId) == url) | 
| + return frameId; | 
| return -1; | 
| } | 
| -chrome.extension.onRequest.addListener(function(request, sender, sendResponse) | 
| +ext.onMessage.addListener(function (msg, sender, sendResponse) | 
| { | 
| - switch (request.reqtype) | 
| + switch (msg.type) | 
| { | 
| - case "get-settings": | 
| - var hostDomain = null; | 
| + case "get-selectors": | 
| var selectors = null; | 
| + var frameId = sender.tab ? getFrameId(sender.tab, msg.frameUrl) : -1; | 
| - var tabId = -1; | 
| - var frameId = -1; | 
| - if (sender.tab) | 
| - { | 
| - tabId = sender.tab.id; | 
| - frameId = getFrameId(tabId, request.frameUrl); | 
| - } | 
| - | 
| - var enabled = !isFrameWhitelisted(tabId, frameId, "DOCUMENT") && !isFrameWhitelisted(tabId, frameId, "ELEMHIDE"); | 
| - if (enabled && request.selectors) | 
| + if (!isFrameWhitelisted(sender.tab, frameId, "DOCUMENT") && | 
| + !isFrameWhitelisted(sender.tab, frameId, "ELEMHIDE")) | 
| { | 
| var noStyleRules = false; | 
| - var host = extractHostFromURL(request.frameUrl); | 
| - hostDomain = getBaseDomain(host); | 
| + var host = extractHostFromURL(msg.frameUrl); | 
| for (var i = 0; i < noStyleRulesHosts.length; i++) | 
| { | 
| var noStyleHost = noStyleRulesHosts[i]; | 
| @@ -656,27 +523,21 @@ | 
| } | 
| } | 
| - sendResponse({enabled: enabled, hostDomain: hostDomain, selectors: selectors}); | 
| + sendResponse(selectors); | 
| break; | 
| case "should-collapse": | 
| - var tabId = -1; | 
| - var frameId = -1; | 
| - if (sender.tab) | 
| - { | 
| - tabId = sender.tab.id; | 
| - frameId = getFrameId(tabId, request.documentUrl); | 
| - } | 
| + var frameId = sender.tab ? getFrameId(sender.tab, msg.documentUrl) : -1; | 
| - if (isFrameWhitelisted(tabId, frameId, "DOCUMENT")) | 
| + if (isFrameWhitelisted(sender.tab, frameId, "DOCUMENT")) | 
| { | 
| sendResponse(false); | 
| break; | 
| } | 
| - var requestHost = extractHostFromURL(request.url); | 
| - var documentHost = extractHostFromURL(request.documentUrl); | 
| + var requestHost = extractHostFromURL(msg.url); | 
| + var documentHost = extractHostFromURL(msg.documentUrl); | 
| var thirdParty = isThirdParty(requestHost, documentHost); | 
| - var filter = defaultMatcher.matchesAny(request.url, request.type, documentHost, thirdParty); | 
| + var filter = defaultMatcher.matchesAny(msg.url, msg.mediatype, documentHost, thirdParty); | 
| if (filter instanceof BlockingFilter) | 
| { | 
| var collapse = filter.collapse; | 
| @@ -697,20 +558,20 @@ | 
| } | 
| break; | 
| case "add-filters": | 
| - if (request.filters && request.filters.length) | 
| + if (msg.filters && msg.filters.length) | 
| { | 
| - for (var i = 0; i < request.filters.length; i++) | 
| - FilterStorage.addFilter(Filter.fromText(request.filters[i])); | 
| + for (var i = 0; i < msg.filters.length; i++) | 
| + FilterStorage.addFilter(Filter.fromText(msg.filters[i])); | 
| } | 
| break; | 
| case "add-subscription": | 
| - openOptions(function(view) | 
| + openOptions(function(tab) | 
| { | 
| - view.startSubscriptionSelection(request.title, request.url); | 
| + tab.sendMessage(msg); | 
| }); | 
| break; | 
| case "forward": | 
| - chrome.tabs.sendRequest(sender.tab.id, request.request, sendResponse); | 
| + tab.sendMessage(msg.payload, sendResponse); | 
| break; | 
| default: | 
| sendResponse({}); | 
| @@ -719,34 +580,22 @@ | 
| }); | 
| // Show icon as page action for all tabs that already exist | 
| -chrome.windows.getAll({populate: true}, function(windows) | 
| +ext.windows.getAll(function(windows) | 
| { | 
| for (var i = 0; i < windows.length; i++) | 
| - for (var j = 0; j < windows[i].tabs.length; j++) | 
| - refreshIconAndContextMenu(windows[i].tabs[j]); | 
| + { | 
| + windows[i].getAllTabs(function(tabs) | 
| + { | 
| + tabs.forEach(refreshIconAndContextMenu); | 
| + }); | 
| + } | 
| }); | 
| // Update icon if a tab changes location | 
| -chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) | 
| +ext.tabs.onBeforeNavigate.addListener(function(tab) | 
| { | 
| - chrome.tabs.sendRequest(tabId, {reqtype: "clickhide-deactivate"}) | 
| - if(changeInfo.status == "loading") | 
| - refreshIconAndContextMenu(tab); | 
| -}); | 
| - | 
| -// Refresh icon when switching tabs or windows | 
| -chrome.tabs.onActivated.addListener(function(activeInfo) | 
| -{ | 
| - refreshIconAndContextMenu(animatedIconTab); | 
| - chrome.tabs.get(activeInfo.tabId, refreshIconAndContextMenu); | 
| -}); | 
| -chrome.windows.onFocusChanged.addListener(function(windowId) | 
| -{ | 
| - refreshIconAndContextMenu(animatedIconTab); | 
| - chrome.tabs.query({active: true, windowId: windowId}, function(tabs) | 
| - { | 
| - tabs.forEach(refreshIconAndContextMenu); | 
| - }); | 
| + tab.sendMessage({type: "clickhide-deactivate"}); | 
| + refreshIconAndContextMenu(tab); | 
| }); | 
| setTimeout(function() |