| LEFT | RIGHT | 
|    1 /* |    1 /* | 
|    2  * This file is part of Adblock Plus <https://adblockplus.org/>, |    2  * This file is part of Adblock Plus <https://adblockplus.org/>, | 
|    3  * Copyright (C) 2006-2016 Eyeo GmbH |    3  * Copyright (C) 2006-2016 Eyeo GmbH | 
|    4  * |    4  * | 
|    5  * Adblock Plus is free software: you can redistribute it and/or modify |    5  * Adblock Plus is free software: you can redistribute it and/or modify | 
|    6  * it under the terms of the GNU General Public License version 3 as |    6  * it under the terms of the GNU General Public License version 3 as | 
|    7  * published by the Free Software Foundation. |    7  * published by the Free Software Foundation. | 
|    8  * |    8  * | 
|    9  * Adblock Plus is distributed in the hope that it will be useful, |    9  * Adblock Plus is distributed in the hope that it will be useful, | 
|   10  * but WITHOUT ANY WARRANTY; without even the implied warranty of |   10  * but WITHOUT ANY WARRANTY; without even the implied warranty of | 
|   11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the |   11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | 
|   12  * GNU General Public License for more details. |   12  * GNU General Public License for more details. | 
|   13  * |   13  * | 
|   14  * You should have received a copy of the GNU General Public License |   14  * You should have received a copy of the GNU General Public License | 
|   15  * along with Adblock Plus.  If not, see <http://www.gnu.org/licenses/>. |   15  * along with Adblock Plus.  If not, see <http://www.gnu.org/licenses/>. | 
|   16  */ |   16  */ | 
|   17  |   17  | 
|   18 /** @module icon */ |   18 /** @module icon */ | 
 |   19  | 
|   19 "use strict"; |   20 "use strict"; | 
|   20  |   21  | 
|   21 let {Utils} = require("utils"); |   22 const frameOpacities =  [0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, | 
 |   23                          1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, | 
 |   24                          0.9, 0.8, 0.7, 0.6, 0.5, 0.4, 0.3, 0.2, 0.1, 0.0]; | 
 |   25 const numberOfFrames = frameOpacities.length; | 
 |   26 const safariPlatform = require("info").platform == "safari"; | 
|   22  |   27  | 
|   23 const frameOpacities =  [0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, |   28 let frameInterval = null; | 
|   24                          1, 1, 1, 1, 1, |   29 let animationInterval = null; | 
|   25                          0.9, 0.8, 0.7, 0.6, 0.5, 0.4, 0.3, 0.2, 0.1, 0]; |  | 
|   26 const numberOfFrames = frameOpacities.length; |  | 
|   27  |  | 
|   28 let stopAnimation = null; |  | 
|   29 let animationPlaying = false; |  | 
|   30 let whitelistedState = new ext.PageMap(); |   30 let whitelistedState = new ext.PageMap(); | 
|   31  |   31  | 
|   32 function getImageData(size, baseIcon, overlayIcon, opacity, |   32 function loadImage(url) | 
|   33                       animationStep, frameCache) |  | 
|   34 { |   33 { | 
|   35   let cacheIndex = baseIcon + size + opacity; |   34   return new Promise((resolve, reject) => | 
 |   35   { | 
 |   36     let image = new Image(); | 
 |   37     image.src = url; | 
 |   38     image.addEventListener("load", () => | 
 |   39     { | 
 |   40       resolve(image); | 
 |   41     }); | 
 |   42     image.addEventListener("error", () => | 
 |   43     { | 
 |   44       reject("Failed to load image " + url); | 
 |   45     }); | 
 |   46   }); | 
 |   47 }; | 
|   36  |   48  | 
|   37   if (frameCache && frameCache[cacheIndex]) |   49 function setIcon(page, notificationType, opacity, frames) | 
|   38     return frameCache[cacheIndex]; |   50 { | 
 |   51   opacity = opacity || 0; | 
 |   52   let whitelisted = !!whitelistedState.get(page) && !safariPlatform; | 
|   39  |   53  | 
|   40   let canvas = document.createElement("canvas"); |   54   if (!notificationType || !frames) | 
|   41   let context = canvas.getContext("2d"); |   55   { | 
 |   56     if (opacity > 0.5) | 
 |   57       page.browserAction.setIcon("icons/abp-$size-notification-" | 
 |   58                                  + notificationType + ".png"); | 
 |   59     else | 
 |   60       page.browserAction.setIcon("icons/abp-$size" + | 
 |   61                                  (whitelisted ? "-whitelisted" : "") + ".png"); | 
 |   62   } | 
 |   63   else | 
 |   64   { | 
 |   65     chrome.browserAction.setIcon({ | 
 |   66       tabId: page._id, | 
 |   67       imageData: frames["" + opacity + whitelisted] | 
 |   68     }); | 
 |   69   } | 
 |   70 } | 
|   42  |   71  | 
|   43   let imageData = Promise.all( |   72 function renderFrames(notificationType) | 
|   44     [Utils.loadImage(baseIcon, size), Utils.loadImage(overlayIcon, size)] |   73 { | 
|   45   ).then(function(icons) |   74   if (safariPlatform) | 
 |   75     return Promise.resolve(null); | 
 |   76  | 
 |   77   return Promise.all([ | 
 |   78     loadImage("icons/abp-19.png"), | 
 |   79     loadImage("icons/abp-19-whitelisted.png"), | 
 |   80     loadImage("icons/abp-19-notification-" + notificationType + ".png"), | 
 |   81     loadImage("icons/abp-38.png"), | 
 |   82     loadImage("icons/abp-38-whitelisted.png"), | 
 |   83     loadImage("icons/abp-38-notification-" + notificationType + ".png"), | 
 |   84   ]).then(images => | 
|   46   { |   85   { | 
|   47     baseIcon = icons[0]; |   86     let images = { | 
|   48     overlayIcon = icons[1]; |   87       19: { base: [images[0], images[1]], overlay: images[2] }, | 
 |   88       38: { base: [images[3], images[4]], overlay: images[5] } | 
 |   89     }; | 
|   49  |   90  | 
|   50     canvas.width = baseIcon.width; |   91     let frames = {}; | 
|   51     canvas.height = baseIcon.height; |   92     let canvas = document.createElement("canvas"); | 
 |   93     let context = canvas.getContext("2d"); | 
|   52  |   94  | 
|   53     context.globalAlpha = 1; |   95     for (let whitelisted of [false, true]) | 
|   54     context.drawImage(baseIcon, 0, 0); |  | 
|   55  |  | 
|   56     if (overlayIcon && opacity) |  | 
|   57     { |   96     { | 
|   58       context.globalAlpha = opacity; |   97       for (let i = 0, opacity = 0; i <= 10; opacity = ++i / 10) | 
|   59       context.drawImage(overlayIcon, 0, 0); |   98       { | 
 |   99         let imageData = {}; | 
 |  100         for (let size of [19, 38]) | 
 |  101         { | 
 |  102           canvas.width = size; | 
 |  103           canvas.height = size; | 
 |  104           context.globalAlpha = 1; | 
 |  105           context.drawImage(images[size]["base"][whitelisted | 0], 0, 0); | 
 |  106           context.globalAlpha = opacity; | 
 |  107           context.drawImage(images[size]["overlay"], 0, 0); | 
 |  108           imageData[size] = context.getImageData(0, 0, size, size); | 
 |  109         } | 
 |  110         frames["" + opacity + whitelisted] = imageData; | 
 |  111       } | 
|   60     } |  112     } | 
|   61  |  113  | 
|   62     return context.getImageData(0, 0, canvas.width, canvas.height); |  114     return frames; | 
|   63   }); |  | 
|   64   if (frameCache) |  | 
|   65     frameCache[cacheIndex] = imageData; |  | 
|   66   return imageData; |  | 
|   67 } |  | 
|   68  |  | 
|   69 function setIcon(page, animationType, animationStep, frameCache) |  | 
|   70 { |  | 
|   71   let safari = require("info").platform == "safari"; |  | 
|   72   let opacity = animationType ? frameOpacities[animationStep] : 0; |  | 
|   73   let greyed = whitelistedState.get(page) && !safari; |  | 
|   74   let blending = (animationType && opacity > 0 && opacity < 1); |  | 
|   75  |  | 
|   76   let filename = "icons/abp-$size"; |  | 
|   77   let baseIcon = filename + (greyed ? "-whitelisted" : "") + ".png"; |  | 
|   78   let overlayIcon = animationType && (filename + "-notification-" + |  | 
|   79                                       animationType + ".png"); |  | 
|   80  |  | 
|   81  |  | 
|   82   // If the icon doesn't need any modifications, or the platform doesn't support |  | 
|   83   // data URLs, we can just use the image's filename with the $size placeholder. |  | 
|   84   if (!blending || safari) |  | 
|   85     if (overlayIcon && opacity > 0.5) |  | 
|   86       return page.browserAction.setIcon(overlayIcon); |  | 
|   87     else |  | 
|   88       return page.browserAction.setIcon(baseIcon); |  | 
|   89  |  | 
|   90   // Otherwise we must process the images using a canvas and return a data URL |  | 
|   91   // of the result for each size that's required. (19px and 38px are required by |  | 
|   92   // Chrome/Opera.) |  | 
|   93   let imageData = [19, 38].map(function(size) |  | 
|   94   { |  | 
|   95     return getImageData(size, baseIcon, overlayIcon, opacity, |  | 
|   96                         animationStep, frameCache); |  | 
|   97   }); |  | 
|   98   Promise.all(imageData).then(function(imageData) |  | 
|   99   { |  | 
|  100     chrome.browserAction.setIcon({tabId: page._id, |  | 
|  101                                   imageData: {19: imageData[0], |  | 
|  102                                               38: imageData[1]}}); |  | 
|  103   }); |  115   }); | 
|  104 } |  116 } | 
|  105  |  117  | 
|  106 function runAnimation(animationType) |  118 function runAnimation(notificationType) | 
|  107 { |  119 { | 
|  108   let frameCache = {}; |  120   function playAnimation(frames) | 
|  109   let frameInterval; |  121   { | 
 |  122     let animationStep = 0; | 
 |  123     ext.pages.query({active: true}, pages => | 
 |  124     { | 
 |  125       function appendActivePage(page) | 
 |  126       { | 
 |  127         pages.push(page); | 
 |  128       } | 
 |  129       ext.pages.onActivated.addListener(appendActivePage); | 
|  110  |  130  | 
|  111   function playAnimation() |  131       frameInterval = setInterval(() => | 
|  112   { |  | 
|  113     animationPlaying = true; |  | 
|  114     ext.pages.query({active: true}, function(pages) |  | 
|  115     { |  | 
|  116       let animationStep = 0; |  | 
|  117       frameInterval = setInterval(function() |  | 
|  118       { |  132       { | 
|  119         pages.forEach(function (page) { |  133         let opacity = frameOpacities[animationStep++]; | 
|  120           setIcon(page, animationType, animationStep++, frameCache); |  134         for (let page of pages) | 
|  121         }); |  135         { | 
|  122         if (animationStep >= numberOfFrames) |  136           if (whitelistedState.has(page)) | 
 |  137             setIcon(page, notificationType, opacity, frames); | 
 |  138         }; | 
 |  139  | 
 |  140         if (animationStep > numberOfFrames) | 
|  123         { |  141         { | 
|  124           animationStep = 0; |  142           animationStep = 0; | 
|  125           clearInterval(frameInterval); |  143           clearInterval(frameInterval); | 
|  126           animationPlaying = false; |  144           frameInterval = null; | 
 |  145           ext.pages.onActivated.removeListener(appendActivePage); | 
|  127         } |  146         } | 
|  128       }, 100); |  147       }, 100); | 
|  129     }); |  148     }); | 
|  130   } |  149   } | 
|  131  |  150  | 
|  132   playAnimation(); |  151   renderFrames(notificationType).then(frames => | 
|  133   let animationInterval = setInterval(playAnimation, 10000); |  | 
|  134   return function() |  | 
|  135   { |  152   { | 
|  136     clearInterval(frameInterval); |  153     playAnimation(frames); | 
|  137     clearInterval(animationInterval); |  154     animationInterval = setInterval(() => { playAnimation(frames); }, 10000); | 
|  138     animationPlaying = false; |  155   }); | 
|  139   }; |  | 
|  140 } |  156 } | 
|  141  |  157  | 
|  142 /** |  158 /** | 
|  143  * Set the browser action icon for the given page, indicating whether |  159  * Set the browser action icon for the given page, indicating whether | 
|  144  * adblocking is active there, and considering the icon animation. |  160  * adblocking is active there, and considering the icon animation. | 
|  145  * |  161  * | 
|  146  * @param {Page}    page         The page to set the browser action icon for |  162  * @param {Page}    page         The page to set the browser action icon for | 
|  147  * @param {Boolean} whitelisted  Whether the page has been whitelisted |  163  * @param {Boolean} whitelisted  Whether the page has been whitelisted | 
|  148  */ |  164  */ | 
|  149 exports.updateIcon = function(page, whitelisted) |  165 exports.updateIcon = function(page, whitelisted) | 
|  150 { |  166 { | 
|  151   whitelistedState.set(page, whitelisted); |  167   whitelistedState.set(page, whitelisted); | 
|  152   if (!animationPlaying) |  168   if (frameInterval == null) | 
|  153     setIcon(page); |  169     setIcon(page); | 
 |  170 }; | 
 |  171  | 
 |  172 let stopIconAnimation = | 
 |  173 /** | 
 |  174  * Stops to animate the browser action icon. | 
 |  175  */ | 
 |  176 exports.stopIconAnimation = function() | 
 |  177 { | 
 |  178   if (frameInterval != null) | 
 |  179     clearInterval(frameInterval); | 
 |  180   if (animationInterval != null) | 
 |  181     clearInterval(animationInterval); | 
 |  182   frameInterval = animationInterval = null; | 
|  154 }; |  183 }; | 
|  155  |  184  | 
|  156 /** |  185 /** | 
|  157  * Starts to animate the browser action icon to indicate a pending notifcation. |  186  * Starts to animate the browser action icon to indicate a pending notifcation. | 
|  158  * |  187  * | 
|  159  * @param {string} type  The notification type (i.e: "information" or "critical"
     ) |  188  * @param {string} type  The notification type (i.e: "information" or "critical"
     ) | 
|  160  */ |  189  */ | 
|  161 exports.startIconAnimation = function(type) |  190 exports.startIconAnimation = function(type) | 
|  162 { |  191 { | 
|  163   stopAnimation && stopAnimation(); |  192   stopIconAnimation(); | 
|  164   stopAnimation = runAnimation(type); |  193   runAnimation(type); | 
|  165 }; |  194 }; | 
|  166  |  | 
|  167 /** |  | 
|  168  * Stops to animate the browser action icon. |  | 
|  169  */ |  | 
|  170 exports.stopIconAnimation = function() |  | 
|  171 { |  | 
|  172   stopAnimation && stopAnimation(); |  | 
|  173 }; |  | 
| LEFT | RIGHT |