| Index: lib/icon.js | 
| diff --git a/lib/icon.js b/lib/icon.js | 
| index 2011d1e23701aada50df48f3f2722514d3b3b24e..9a72c63df67e4098a4afa8879737833fea64a63c 100644 | 
| --- a/lib/icon.js | 
| +++ b/lib/icon.js | 
| @@ -17,73 +17,158 @@ | 
| /** @module icon */ | 
| -const numberOfFrames = 10; | 
| +"use strict"; | 
| +const frameOpacities = [0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, | 
| + 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, | 
| + 0.9, 0.8, 0.7, 0.6, 0.5, 0.4, 0.3, 0.2, 0.1, 0.0]; | 
| +const numberOfFrames = frameOpacities.length; | 
| +const safariPlatform = require("info").platform == "safari"; | 
| + | 
| +let stopAnimation = null; | 
| +let animationPlaying = false; | 
| let whitelistedState = new ext.PageMap(); | 
| -let notificationType = null; | 
| -let animationInterval = null; | 
| -let animationStep = 0; | 
| -function getIconPath(whitelisted) | 
| +let loadImageAsCanvas = | 
| +/** | 
| + * Loads an image and draws it onto a canvas. | 
| + * | 
| + * @param {string} url URL string of the image to load | 
| + * @return {Promise} | 
| + */ | 
| +exports.loadImageAsCanvas = function(url) | 
| { | 
| - let filename = "icons/abp-$size"; | 
| + return new Promise((resolve, reject) => { | 
| + let image = new Image(); | 
| + image.src = url; | 
| + image.addEventListener("load", () => | 
| + { | 
| + let canvas = document.createElement("canvas"); | 
| + let context = canvas.getContext("2d"); | 
| + canvas.height = image.height; | 
| 
 
Sebastian Noack
2016/01/25 15:44:45
With the new approach, we don't necessarily need a
 
kzar
2016/01/26 15:01:42
Good point about the canvas, Done. Not sure about
 
 | 
| + canvas.width = image.width; | 
| + context.drawImage(image, 0, 0); | 
| + resolve(canvas); | 
| + }); | 
| + image.addEventListener("error", () => { | 
| + reject("Failed to load image " + url); | 
| + }); | 
| + }); | 
| +}; | 
| - // If the current page is whitelisted, pick an icon that indicates that | 
| - // Adblock Plus is disabled, however not when the notification icon has | 
| - // full opacity, or on Safari where icons are genrally grayscale-only. | 
| - if (whitelisted && animationStep < numberOfFrames && require("info").platform != "safari") | 
| - filename += "-whitelisted"; | 
| +function setIcon(page, animationType, opacity, frames) | 
| +{ | 
| + opacity = opacity || 0; | 
| + let greyed = whitelistedState.get(page) && !safariPlatform && true || false; | 
| 
 
Sebastian Noack
2016/01/25 15:44:43
Nit: Please don't add redundant boilerplate to cas
 
kzar
2016/01/26 15:01:41
Actually the value here is either null or the whit
 
Sebastian Noack
2016/01/26 16:12:36
I see. But how about:
  let whitelisted = !!white
 
kzar
2016/01/26 17:35:10
Done.
 
 | 
| - // If the icon is currently animating to indicate a pending notification, | 
| - // pick the icon for the corresponing notification type and animation frame. | 
| - if (notificationType && animationStep > 0) | 
| + if (!animationType || !frames) | 
| { | 
| - filename += "-notification-" + notificationType; | 
| - | 
| - if (animationStep < numberOfFrames) | 
| - filename += "-" + animationStep; | 
| + if (animationType && opacity > 0.5) | 
| + page.browserAction.setIcon("icons/abp-$size-notification-" | 
| + + animationType + ".png"); | 
| + else | 
| + page.browserAction.setIcon("icons/abp-$size" + | 
| + (greyed ? "-whitelisted" : "") + ".png"); | 
| + } | 
| + else | 
| + { | 
| + frames.then(frames => | 
| + { | 
| + chrome.browserAction.setIcon({ | 
| + tabId: page._id, | 
| + imageData: {19: frames["" + opacity + greyed + 19], | 
| + 38: frames["" + opacity + greyed + 38]} | 
| + }); | 
| + }); | 
| } | 
| - | 
| - return filename + ".png"; | 
| } | 
| -function setIcon(page) | 
| +function renderFrames(animationType) | 
| { | 
| - page.browserAction.setIcon(getIconPath(whitelistedState.get(page))); | 
| + return Promise.all([ | 
| + loadImageAsCanvas("icons/abp-19.png"), | 
| + loadImageAsCanvas("icons/abp-19-whitelisted.png"), | 
| + loadImageAsCanvas("icons/abp-19-notification-" + animationType + ".png"), | 
| + loadImageAsCanvas("icons/abp-38.png"), | 
| + loadImageAsCanvas("icons/abp-38-whitelisted.png"), | 
| + loadImageAsCanvas("icons/abp-38-notification-" + animationType + ".png"), | 
| + ]).then(images => | 
| + { | 
| + let images = { | 
| + 19: { base: [images[0], images[1]], overlay: images[2] }, | 
| 
 
Sebastian Noack
2016/01/25 15:44:44
While using a structured object here, how about us
 
kzar
2016/01/26 15:01:40
Could do but then the logic later wouldn't be as e
 
Sebastian Noack
2016/01/26 16:12:35
Fair enough.
 
 | 
| + 38: { base: [images[3], images[4]], overlay: images[5] } | 
| + }; | 
| + | 
| + let frames = {}; | 
| + let canvas = document.createElement("canvas"); | 
| + let context = canvas.getContext("2d"); | 
| + | 
| + for (let size of [19, 38]) | 
| + { | 
| + canvas.width = size; | 
| + canvas.height = size; | 
| + for (let whitelisted of [false, true]) | 
| + { | 
| + for (let opacity of [0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1]) | 
| 
 
Sebastian Noack
2016/01/25 15:44:44
I'd rather generate the opacity values rather than
 
kzar
2016/01/26 15:01:40
As would I but unfortunately that doesn't work. (T
 
Sebastian Noack
2016/01/26 16:12:36
Alright, damn floating point precision. But you co
 
kzar
2016/01/26 17:35:09
Yea, I thought about those options but in the end
 
Sebastian Noack
2016/01/26 18:10:34
Well, instead a custom unique() function, we shoul
 
kzar
2016/01/26 18:46:34
Fair enough, how about this?
 
 | 
| + { | 
| + context.globalAlpha = 1; | 
| + context.drawImage(images[size]["base"][whitelisted | 0], 0, 0); | 
| + context.globalAlpha = opacity; | 
| + context.drawImage(images[size]["overlay"], 0, 0); | 
| + frames["" + opacity + | 
| 
 
Sebastian Noack
2016/01/25 15:44:44
Following data structure would be preferable:
  f
 
kzar
2016/01/26 15:01:41
Good point, Done.
 
 | 
| + whitelisted + size] = context.getImageData(0, 0, size, size); | 
| + } | 
| + } | 
| + } | 
| + | 
| + return frames; | 
| + }); | 
| } | 
| -function runAnimation() | 
| +function runAnimation(animationType) | 
| 
 
Sebastian Noack
2016/01/25 15:44:45
Any reason you changed the semantic from notificat
 
kzar
2016/01/26 15:01:41
Done.
 
 | 
| { | 
| - return setInterval(function() | 
| + let frames = !safariPlatform && renderFrames(animationType); | 
| 
 
Sebastian Noack
2016/01/25 15:44:44
I'd rather wait for the promise to be fulfilled be
 
kzar
2016/01/26 15:01:41
Good point, Done.
 
 | 
| + let frameInterval; | 
| + | 
| + function playAnimation() | 
| { | 
| - ext.pages.query({active: true}, function(pages) | 
| + animationPlaying = true; | 
| + let animationStep = 0; | 
| + ext.pages.query({active: true}, pages => | 
| { | 
| - let fadeInInterval = setInterval(function() | 
| + function appendActivePage(page) | 
| { | 
| - animationStep++; | 
| - pages.forEach(setIcon); | 
| + pages.push(page); | 
| + } | 
| + ext.pages.onActivated.addListener(appendActivePage); | 
| 
 
Sebastian Noack
2016/01/25 15:44:43
It seems that you only implemented ext.pages.onAct
 
kzar
2016/01/26 15:01:42
I have implemented it for Safari too, check safari
 
 | 
| - if (animationStep < numberOfFrames) | 
| - return; | 
| + frameInterval = setInterval(() => | 
| + { | 
| + let opacity = frameOpacities[animationStep++]; | 
| + pages.forEach(page => { | 
| + if (whitelistedState.has(page)) | 
| + setIcon(page, animationType, opacity, frames); | 
| + }); | 
| - setTimeout(function() | 
| + if (animationStep > numberOfFrames) | 
| { | 
| - let fadeOutInterval = setInterval(function() | 
| - { | 
| - animationStep--; | 
| - pages.forEach(setIcon); | 
| - | 
| - if (animationStep > 0) | 
| - return; | 
| - | 
| - clearInterval(fadeOutInterval); | 
| - }, 100); | 
| - },1000); | 
| - | 
| - clearInterval(fadeInInterval); | 
| + animationStep = 0; | 
| + clearInterval(frameInterval); | 
| + animationPlaying = false; | 
| + ext.pages.onActivated.removeListener(appendActivePage); | 
| + } | 
| }, 100); | 
| }); | 
| - }, 10000); | 
| + } | 
| + | 
| + playAnimation(); | 
| + let animationInterval = setInterval(playAnimation, 10000); | 
| + return () => | 
| + { | 
| + clearInterval(frameInterval); | 
| 
 
Sebastian Noack
2016/01/25 15:44:43
I think the previous logic, using global variables
 
kzar
2016/01/26 15:01:42
I disagree there.
 
Sebastian Noack
2016/01/26 16:12:35
It's not that we return a function with different
 
kzar
2016/01/26 17:35:09
I just think that having as much of the animation
 
Sebastian Noack
2016/01/26 18:10:34
You don't have any less global state. But the glob
 
kzar
2016/01/26 18:46:34
Fair enough, Done.
 
 | 
| + clearInterval(animationInterval); | 
| + animationPlaying = false; | 
| + }; | 
| } | 
| /** | 
| @@ -95,8 +180,9 @@ function runAnimation() | 
| */ | 
| exports.updateIcon = function(page, whitelisted) | 
| { | 
| - page.browserAction.setIcon(getIconPath(whitelisted)); | 
| whitelistedState.set(page, whitelisted); | 
| + if (!animationPlaying) | 
| + setIcon(page); | 
| }; | 
| /** | 
| @@ -106,10 +192,8 @@ exports.updateIcon = function(page, whitelisted) | 
| */ | 
| exports.startIconAnimation = function(type) | 
| { | 
| - notificationType = type; | 
| - | 
| - if (animationInterval == null) | 
| - animationInterval = runAnimation(); | 
| + stopAnimation && stopAnimation(); | 
| + stopAnimation = runAnimation(type); | 
| }; | 
| /** | 
| @@ -117,11 +201,5 @@ exports.startIconAnimation = function(type) | 
| */ | 
| exports.stopIconAnimation = function() | 
| { | 
| - if (animationInterval != null) | 
| - { | 
| - clearInterval(animationInterval); | 
| - animationInterval = null; | 
| - } | 
| - | 
| - notificationType = null; | 
| + stopAnimation && stopAnimation(); | 
| }; |