| 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 /* globals Services, URL */ | |
| 19 | |
| 20 "use strict"; | 18 "use strict"; |
| 21 | 19 |
| 22 /** | 20 /** |
| 23 * @fileOverview Handles notifications. | 21 * @fileOverview Handles notifications. |
| 24 */ | 22 */ |
| 25 | 23 |
| 26 Cu.import("resource://gre/modules/Services.jsm"); | 24 const {Services} = Cu.import("resource://gre/modules/Services.jsm", {}); |
| 27 | 25 |
| 28 let {Prefs} = require("prefs"); | 26 const {Prefs} = require("prefs"); |
| 29 let {Downloader, Downloadable, | 27 const {Downloader, Downloadable, |
| 30 MILLIS_IN_MINUTE, MILLIS_IN_HOUR, MILLIS_IN_DAY} = require("downloader"); | 28 MILLIS_IN_MINUTE, MILLIS_IN_HOUR, MILLIS_IN_DAY} = require("downloader"); |
| 31 let {Utils} = require("utils"); | 29 const {Utils} = require("utils"); |
| 32 let {Matcher, defaultMatcher} = require("matcher"); | 30 const {Matcher, defaultMatcher} = require("matcher"); |
| 33 let {Filter, RegExpFilter, WhitelistFilter} = require("filterClasses"); | 31 const {Filter, RegExpFilter, WhitelistFilter} = require("filterClasses"); |
| 34 | 32 |
| 35 let INITIAL_DELAY = 1 * MILLIS_IN_MINUTE; | 33 const INITIAL_DELAY = 1 * MILLIS_IN_MINUTE; |
| 36 let CHECK_INTERVAL = 1 * MILLIS_IN_HOUR; | 34 const CHECK_INTERVAL = 1 * MILLIS_IN_HOUR; |
| 37 let EXPIRATION_INTERVAL = 1 * MILLIS_IN_DAY; | 35 const EXPIRATION_INTERVAL = 1 * MILLIS_IN_DAY; |
| 38 let TYPE = { | 36 const TYPE = { |
| 39 information: 0, | 37 information: 0, |
| 40 question: 1, | 38 question: 1, |
| 41 relentless: 2, | 39 relentless: 2, |
| 42 critical: 3 | 40 critical: 3 |
| 43 }; | 41 }; |
| 44 | 42 |
| 45 let showListeners = []; | 43 let showListeners = []; |
| 46 let questionListeners = {}; | 44 let questionListeners = {}; |
| 47 | 45 |
| 48 function getNumericalSeverity(notification) | 46 function getNumericalSeverity(notification) |
| (...skipping 53 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 102 */ | 100 */ |
| 103 *_getDownloadables() | 101 *_getDownloadables() |
| 104 { | 102 { |
| 105 let downloadable = new Downloadable(Prefs.notificationurl); | 103 let downloadable = new Downloadable(Prefs.notificationurl); |
| 106 if (typeof Prefs.notificationdata.lastError === "number") | 104 if (typeof Prefs.notificationdata.lastError === "number") |
| 107 downloadable.lastError = Prefs.notificationdata.lastError; | 105 downloadable.lastError = Prefs.notificationdata.lastError; |
| 108 if (typeof Prefs.notificationdata.lastCheck === "number") | 106 if (typeof Prefs.notificationdata.lastCheck === "number") |
| 109 downloadable.lastCheck = Prefs.notificationdata.lastCheck; | 107 downloadable.lastCheck = Prefs.notificationdata.lastCheck; |
| 110 if (typeof Prefs.notificationdata.data === "object" && | 108 if (typeof Prefs.notificationdata.data === "object" && |
| 111 "version" in Prefs.notificationdata.data) | 109 "version" in Prefs.notificationdata.data) |
| 110 { |
| 112 downloadable.lastVersion = Prefs.notificationdata.data.version; | 111 downloadable.lastVersion = Prefs.notificationdata.data.version; |
| 112 } |
| 113 if (typeof Prefs.notificationdata.softExpiration === "number") | 113 if (typeof Prefs.notificationdata.softExpiration === "number") |
| 114 downloadable.softExpiration = Prefs.notificationdata.softExpiration; | 114 downloadable.softExpiration = Prefs.notificationdata.softExpiration; |
| 115 if (typeof Prefs.notificationdata.hardExpiration === "number") | 115 if (typeof Prefs.notificationdata.hardExpiration === "number") |
| 116 downloadable.hardExpiration = Prefs.notificationdata.hardExpiration; | 116 downloadable.hardExpiration = Prefs.notificationdata.hardExpiration; |
| 117 if (typeof Prefs.notificationdata.downloadCount === "number") | 117 if (typeof Prefs.notificationdata.downloadCount === "number") |
| 118 downloadable.downloadCount = Prefs.notificationdata.downloadCount; | 118 downloadable.downloadCount = Prefs.notificationdata.downloadCount; |
| 119 yield downloadable; | 119 yield downloadable; |
| 120 }, | 120 }, |
| 121 | 121 |
| 122 _onExpirationChange(downloadable) | 122 _onExpirationChange(downloadable) |
| (...skipping 23 matching lines...) Expand all Loading... |
| 146 } | 146 } |
| 147 catch (e) | 147 catch (e) |
| 148 { | 148 { |
| 149 Cu.reportError(e); | 149 Cu.reportError(e); |
| 150 errorCallback("synchronize_invalid_data"); | 150 errorCallback("synchronize_invalid_data"); |
| 151 return; | 151 return; |
| 152 } | 152 } |
| 153 | 153 |
| 154 Prefs.notificationdata.lastError = 0; | 154 Prefs.notificationdata.lastError = 0; |
| 155 Prefs.notificationdata.downloadStatus = "synchronize_ok"; | 155 Prefs.notificationdata.downloadStatus = "synchronize_ok"; |
| 156 [Prefs.notificationdata.softExpiration, | 156 [ |
| 157 Prefs.notificationdata.hardExpiration] = | 157 Prefs.notificationdata.softExpiration, |
| 158 downloader.processExpirationInterval(EXPIRATION_INTERVAL); | 158 Prefs.notificationdata.hardExpiration |
| 159 ] = downloader.processExpirationInterval(EXPIRATION_INTERVAL); |
| 159 Prefs.notificationdata.downloadCount = downloadable.downloadCount; | 160 Prefs.notificationdata.downloadCount = downloadable.downloadCount; |
| 160 saveNotificationData(); | 161 saveNotificationData(); |
| 161 | 162 |
| 162 Notification.showNext(); | 163 Notification.showNext(); |
| 163 }, | 164 }, |
| 164 | 165 |
| 165 _onDownloadError(downloadable, downloadURL, error, channelStatus, | 166 _onDownloadError(downloadable, downloadURL, error, channelStatus, |
| 166 responseStatus, redirectCallback) | 167 responseStatus, redirectCallback) |
| 167 { | 168 { |
| 168 Prefs.notificationdata.lastError = Date.now(); | 169 Prefs.notificationdata.lastError = Date.now(); |
| (...skipping 18 matching lines...) Expand all Loading... |
| 187 */ | 188 */ |
| 188 removeShowListener(listener) | 189 removeShowListener(listener) |
| 189 { | 190 { |
| 190 let index = showListeners.indexOf(listener); | 191 let index = showListeners.indexOf(listener); |
| 191 if (index != -1) | 192 if (index != -1) |
| 192 showListeners.splice(index, 1); | 193 showListeners.splice(index, 1); |
| 193 }, | 194 }, |
| 194 | 195 |
| 195 /** | 196 /** |
| 196 * Determines which notification is to be shown next. | 197 * Determines which notification is to be shown next. |
| 197 * @param {String} url URL to match notifications to (optional) | 198 * @param {string} url URL to match notifications to (optional) |
| 198 * @return {Object} notification to be shown, or null if there is none | 199 * @return {Object} notification to be shown, or null if there is none |
| 199 */ | 200 */ |
| 200 _getNextToShow(url) | 201 _getNextToShow(url) |
| 201 { | 202 { |
| 202 function checkTarget(target, parameter, name, version) | 203 function checkTarget(target, parameter, name, version) |
| 203 { | 204 { |
| 204 let minVersionKey = parameter + "MinVersion"; | 205 let minVersionKey = parameter + "MinVersion"; |
| 205 let maxVersionKey = parameter + "MaxVersion"; | 206 let maxVersionKey = parameter + "MaxVersion"; |
| 206 return !((parameter in target && target[parameter] != name) || | 207 return !((parameter in target && target[parameter] != name) || |
| 207 (minVersionKey in target && | 208 (minVersionKey in target && |
| 208 Services.vc.compare(version, target[minVersionKey]) < 0) || | 209 Services.vc.compare(version, target[minVersionKey]) < 0) || |
| 209 (maxVersionKey in target && | 210 (maxVersionKey in target && |
| 210 Services.vc.compare(version, target[maxVersionKey]) > 0)); | 211 Services.vc.compare(version, target[maxVersionKey]) > 0)); |
| 211 } | 212 } |
| 212 | 213 |
| 213 let remoteData = []; | 214 let remoteData = []; |
| 214 if (typeof Prefs.notificationdata.data == "object" && | 215 if (typeof Prefs.notificationdata.data == "object" && |
| 215 Prefs.notificationdata.data.notifications instanceof Array) | 216 Prefs.notificationdata.data.notifications instanceof Array) |
| 217 { |
| 216 remoteData = Prefs.notificationdata.data.notifications; | 218 remoteData = Prefs.notificationdata.data.notifications; |
| 219 } |
| 217 | 220 |
| 218 let notifications = localData.concat(remoteData); | 221 let notifications = localData.concat(remoteData); |
| 219 if (notifications.length === 0) | 222 if (notifications.length === 0) |
| 220 return null; | 223 return null; |
| 221 | 224 |
| 222 let {addonName, addonVersion, application, | 225 const {addonName, addonVersion, application, |
| 223 applicationVersion, platform, platformVersion} = require("info"); | 226 applicationVersion, platform, platformVersion} = require("info"); |
| 224 let notificationToShow = null; | 227 let notificationToShow = null; |
| 225 for (let notification of notifications) | 228 for (let notification of notifications) |
| 226 { | 229 { |
| 227 if (typeof notification.type === "undefined" || | 230 if (typeof notification.type === "undefined" || |
| 228 notification.type !== "critical") | 231 notification.type !== "critical") |
| 229 { | 232 { |
| 230 let shown; | 233 let shown; |
| 231 if (typeof Prefs.notificationdata.shown == "object") | 234 if (typeof Prefs.notificationdata.shown == "object") |
| 232 shown = Prefs.notificationdata.shown[notification.id]; | 235 shown = Prefs.notificationdata.shown[notification.id]; |
| 233 | 236 |
| 234 if (typeof shown != "undefined") | 237 if (typeof shown != "undefined") |
| 235 { | 238 { |
| 236 if (typeof notification.interval == "number") | 239 if (typeof notification.interval == "number") |
| 237 { | 240 { |
| 238 if (shown + notification.interval > Date.now()) | 241 if (shown + notification.interval > Date.now()) |
| 239 continue; | 242 continue; |
| 240 } | 243 } |
| 241 else if (shown) | 244 else if (shown) |
| 242 continue; | 245 continue; |
| 243 } | 246 } |
| 244 | 247 |
| 245 if (notification.type !== "relentless" && | 248 if (notification.type !== "relentless" && |
| 246 Prefs.notifications_ignoredcategories.indexOf("*") != -1) | 249 Prefs.notifications_ignoredcategories.indexOf("*") != -1) |
| 250 { |
| 247 continue; | 251 continue; |
| 252 } |
| 248 } | 253 } |
| 249 | 254 |
| 250 if (typeof url === "string" || notification.urlFilters instanceof Array) | 255 if (typeof url === "string" || notification.urlFilters instanceof Array) |
| 251 { | 256 { |
| 252 if (Prefs.enabled && typeof url === "string" && | 257 if (Prefs.enabled && typeof url === "string" && |
| 253 notification.urlFilters instanceof Array) | 258 notification.urlFilters instanceof Array) |
| 254 { | 259 { |
| 255 let host; | 260 let host; |
| 256 try | 261 try |
| 257 { | 262 { |
| 258 host = new URL(url).hostname; | 263 host = new URL(url).hostname; |
| 259 } | 264 } |
| 260 catch (e) | 265 catch (e) |
| 261 { | 266 { |
| 262 host = ""; | 267 host = ""; |
| 263 } | 268 } |
| 264 | 269 |
| 265 let exception = defaultMatcher.matchesAny( | 270 let exception = defaultMatcher.matchesAny( |
| 266 url, RegExpFilter.typeMap.DOCUMENT, host, false, null | 271 url, RegExpFilter.typeMap.DOCUMENT, host, false, null |
| 267 ); | 272 ); |
| 268 if (exception instanceof WhitelistFilter) | 273 if (exception instanceof WhitelistFilter) |
| 269 continue; | 274 continue; |
| 270 | 275 |
| 271 let matcher = new Matcher(); | 276 let matcher = new Matcher(); |
| 272 for (let urlFilter of notification.urlFilters) | 277 for (let urlFilter of notification.urlFilters) |
| 273 matcher.add(Filter.fromText(urlFilter)); | 278 matcher.add(Filter.fromText(urlFilter)); |
| 274 if (!matcher.matchesAny(url, RegExpFilter.typeMap.DOCUMENT, host, | 279 if (!matcher.matchesAny(url, RegExpFilter.typeMap.DOCUMENT, host, |
| 275 false, null)) | 280 false, null)) |
| 281 { |
| 276 continue; | 282 continue; |
| 283 } |
| 277 } | 284 } |
| 278 else | 285 else |
| 279 continue; | 286 continue; |
| 280 } | 287 } |
| 281 | 288 |
| 282 if (notification.targets instanceof Array) | 289 if (notification.targets instanceof Array) |
| 283 { | 290 { |
| 284 let match = false; | 291 let match = false; |
| 285 for (let target of notification.targets) | 292 for (let target of notification.targets) |
| 286 { | 293 { |
| (...skipping 15 matching lines...) Expand all Loading... |
| 302 getNumericalSeverity(notificationToShow)) | 309 getNumericalSeverity(notificationToShow)) |
| 303 notificationToShow = notification; | 310 notificationToShow = notification; |
| 304 } | 311 } |
| 305 | 312 |
| 306 return notificationToShow; | 313 return notificationToShow; |
| 307 }, | 314 }, |
| 308 | 315 |
| 309 /** | 316 /** |
| 310 * Invokes the listeners added via addShowListener() with the next | 317 * Invokes the listeners added via addShowListener() with the next |
| 311 * notification to be shown. | 318 * notification to be shown. |
| 312 * @param {String} url URL to match notifications to (optional) | 319 * @param {string} url URL to match notifications to (optional) |
| 313 */ | 320 */ |
| 314 showNext(url) | 321 showNext(url) |
| 315 { | 322 { |
| 316 let notification = Notification._getNextToShow(url); | 323 let notification = Notification._getNextToShow(url); |
| 317 if (notification) | 324 if (notification) |
| 318 { | 325 { |
| 319 for (let showListener of showListeners) | 326 for (let showListener of showListeners) |
| 320 showListener(notification); | 327 showListener(notification); |
| 321 } | 328 } |
| 322 }, | 329 }, |
| 323 | 330 |
| 324 /** | 331 /** |
| 325 * Marks a notification as shown. | 332 * Marks a notification as shown. |
| 326 * @param {String} id ID of the notification to be marked as shown | 333 * @param {string} id ID of the notification to be marked as shown |
| 327 */ | 334 */ |
| 328 markAsShown(id) | 335 markAsShown(id) |
| 329 { | 336 { |
| 330 let now = Date.now(); | 337 let now = Date.now(); |
| 331 let data = Prefs.notificationdata; | 338 let data = Prefs.notificationdata; |
| 332 | 339 |
| 333 if (data.shown instanceof Array) | 340 if (data.shown instanceof Array) |
| 334 { | 341 { |
| 335 let newShown = {}; | 342 let newShown = {}; |
| 336 for (let oldId of data.shown) | 343 for (let oldId of data.shown) |
| 337 newShown[oldId] = now; | 344 newShown[oldId] = now; |
| 338 data.shown = newShown; | 345 data.shown = newShown; |
| 339 } | 346 } |
| 340 | 347 |
| 341 if (typeof data.shown != "object") | 348 if (typeof data.shown != "object") |
| 342 data.shown = {}; | 349 data.shown = {}; |
| 343 | 350 |
| 344 data.shown[id] = now; | 351 data.shown[id] = now; |
| 345 | 352 |
| 346 saveNotificationData(); | 353 saveNotificationData(); |
| 347 }, | 354 }, |
| 348 | 355 |
| 349 /** | 356 /** |
| 350 * Localizes the texts of the supplied notification. | 357 * Localizes the texts of the supplied notification. |
| 351 * @param {Object} notification notification to translate | 358 * @param {Object} notification notification to translate |
| 352 * @param {String} locale the target locale (optional, defaults to the | 359 * @param {string} locale the target locale (optional, defaults to the |
| 353 * application locale) | 360 * application locale) |
| 354 * @return {Object} the translated texts | 361 * @return {Object} the translated texts |
| 355 */ | 362 */ |
| 356 getLocalizedTexts(notification, locale) | 363 getLocalizedTexts(notification, locale) |
| 357 { | 364 { |
| 358 locale = locale || Utils.appLocale; | 365 locale = locale || Utils.appLocale; |
| 359 let textKeys = ["title", "message"]; | 366 let textKeys = ["title", "message"]; |
| 360 let localizedTexts = []; | 367 let localizedTexts = []; |
| 361 for (let key of textKeys) | 368 for (let key of textKeys) |
| 362 { | 369 { |
| (...skipping 25 matching lines...) Expand all Loading... |
| 388 removeNotification(notification) | 395 removeNotification(notification) |
| 389 { | 396 { |
| 390 let index = localData.indexOf(notification); | 397 let index = localData.indexOf(notification); |
| 391 if (index > -1) | 398 if (index > -1) |
| 392 localData.splice(index, 1); | 399 localData.splice(index, 1); |
| 393 }, | 400 }, |
| 394 | 401 |
| 395 /** | 402 /** |
| 396 * A callback function which listens to see if notifications were approved. | 403 * A callback function which listens to see if notifications were approved. |
| 397 * | 404 * |
| 398 * @callback approvedListener | 405 * @callback QuestionListener |
| 399 * @param {Boolean} approved | 406 * @param {boolean} approved |
| 400 */ | 407 */ |
| 401 | 408 |
| 402 /** | 409 /** |
| 403 * Adds a listener for question-type notifications | 410 * Adds a listener for question-type notifications |
| 404 * @param {String} id | 411 * @param {string} id |
| 405 * @param {approvedListener} listener | 412 * @param {QuestionListener} listener |
| 406 */ | 413 */ |
| 407 addQuestionListener(id, listener) | 414 addQuestionListener(id, listener) |
| 408 { | 415 { |
| 409 if (!(id in questionListeners)) | 416 if (!(id in questionListeners)) |
| 410 questionListeners[id] = []; | 417 questionListeners[id] = []; |
| 411 if (questionListeners[id].indexOf(listener) === -1) | 418 if (questionListeners[id].indexOf(listener) === -1) |
| 412 questionListeners[id].push(listener); | 419 questionListeners[id].push(listener); |
| 413 }, | 420 }, |
| 414 | 421 |
| 415 /** | 422 /** |
| 416 * Removes a listener that was previously added via addQuestionListener | 423 * Removes a listener that was previously added via addQuestionListener |
| 417 * @param {String} id | 424 * @param {string} id |
| 418 * @param {approvedListener} listener | 425 * @param {QuestionListener} listener |
| 419 */ | 426 */ |
| 420 removeQuestionListener(id, listener) | 427 removeQuestionListener(id, listener) |
| 421 { | 428 { |
| 422 if (!(id in questionListeners)) | 429 if (!(id in questionListeners)) |
| 423 return; | 430 return; |
| 424 let index = questionListeners[id].indexOf(listener); | 431 let index = questionListeners[id].indexOf(listener); |
| 425 if (index > -1) | 432 if (index > -1) |
| 426 questionListeners[id].splice(index, 1); | 433 questionListeners[id].splice(index, 1); |
| 427 if (questionListeners[id].length === 0) | 434 if (questionListeners[id].length === 0) |
| 428 delete questionListeners[id]; | 435 delete questionListeners[id]; |
| 429 }, | 436 }, |
| 430 | 437 |
| 431 /** | 438 /** |
| 432 * Notifies question listeners about interactions with a notification | 439 * Notifies question listeners about interactions with a notification |
| 433 * @param {String} id notification ID | 440 * @param {string} id notification ID |
| 434 * @param {Boolean} approved indicator whether notification has been approved | 441 * @param {boolean} approved indicator whether notification has been approved |
| 435 */ | 442 */ |
| 436 triggerQuestionListeners(id, approved) | 443 triggerQuestionListeners(id, approved) |
| 437 { | 444 { |
| 438 if (!(id in questionListeners)) | 445 if (!(id in questionListeners)) |
| 439 return; | 446 return; |
| 440 let listeners = questionListeners[id]; | 447 let listeners = questionListeners[id]; |
| 441 for (let listener of listeners) | 448 for (let listener of listeners) |
| 442 listener(approved); | 449 listener(approved); |
| 443 }, | 450 }, |
| 444 | 451 |
| 445 /** | 452 /** |
| 446 * Toggles whether notifications of a specific category should be ignored | 453 * Toggles whether notifications of a specific category should be ignored |
| 447 * @param {String} category notification category identifier | 454 * @param {string} category notification category identifier |
| 448 * @param {Boolean} [forceValue] force specified value | 455 * @param {boolean} [forceValue] force specified value |
| 449 */ | 456 */ |
| 450 toggleIgnoreCategory(category, forceValue) | 457 toggleIgnoreCategory(category, forceValue) |
| 451 { | 458 { |
| 452 let categories = Prefs.notifications_ignoredcategories; | 459 let categories = Prefs.notifications_ignoredcategories; |
| 453 let index = categories.indexOf(category); | 460 let index = categories.indexOf(category); |
| 454 if (index == -1 && forceValue !== false) | 461 if (index == -1 && forceValue !== false) |
| 455 { | 462 { |
| 456 categories.push(category); | 463 categories.push(category); |
| 457 Prefs.notifications_showui = true; | 464 Prefs.notifications_showui = true; |
| 458 } | 465 } |
| 459 else if (index != -1 && forceValue !== true) | 466 else if (index != -1 && forceValue !== true) |
| 460 categories.splice(index, 1); | 467 categories.splice(index, 1); |
| 461 | 468 |
| 462 // HACK: JSON values aren't saved unless they are assigned a | 469 // HACK: JSON values aren't saved unless they are assigned a |
| 463 // different object. | 470 // different object. |
| 464 Prefs.notifications_ignoredcategories = | 471 Prefs.notifications_ignoredcategories = |
| 465 JSON.parse(JSON.stringify(categories)); | 472 JSON.parse(JSON.stringify(categories)); |
| 466 } | 473 } |
| 467 }; | 474 }; |
| 468 Notification.init(); | 475 Notification.init(); |
| LEFT | RIGHT |