LEFT | RIGHT |
1 /* | 1 /* |
2 * This file is part of Adblock Plus <http://adblockplus.org/>, | 2 * This file is part of Adblock Plus <http://adblockplus.org/>, |
3 * Copyright (C) 2006-2013 Eyeo GmbH | 3 * Copyright (C) 2006-2013 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 |
(...skipping 92 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
103 var index = url.indexOf("#"); | 103 var index = url.indexOf("#"); |
104 if (index >= 0) | 104 if (index >= 0) |
105 url = url.substring(0, index); | 105 url = url.substring(0, index); |
106 | 106 |
107 var result = defaultMatcher.matchesAny(url, type || "DOCUMENT", extractHostFro
mURL(parentUrl || url), false); | 107 var result = defaultMatcher.matchesAny(url, type || "DOCUMENT", extractHostFro
mURL(parentUrl || url), false); |
108 return (result instanceof WhitelistFilter ? result : null); | 108 return (result instanceof WhitelistFilter ? result : null); |
109 } | 109 } |
110 | 110 |
111 var activeNotification = null; | 111 var activeNotification = null; |
112 | 112 |
113 // Adds or removes page action icon according to options. | 113 // Adds or removes browser action icon according to options. |
114 function refreshIconAndContextMenu(tab) | 114 function refreshIconAndContextMenu(tab) |
115 { | 115 { |
116 // The tab could have been closed by the time this function is called | 116 if(!/^https?:/.test(tab.url)) |
117 if(!tab) | |
118 return; | 117 return; |
119 | 118 |
120 var excluded = isWhitelisted(tab.url); | 119 var iconFilename; |
121 var iconFilename = excluded ? "icons/abp-19-whitelisted.png" : "icons/abp-19.p
ng"; | 120 if (require("info").platform == "safari") |
122 | 121 // There is no grayscale version of the icon for whitelisted tabs |
123 if (activeNotification) | 122 // when using Safari, because icons are grayscale already and icons |
124 startIconAnimation(tab, iconFilename); | 123 // aren't per tab in Safari. |
| 124 iconFilename = "icons/abp-16.png" |
125 else | 125 else |
126 chrome.browserAction.setIcon({tabId: tab.id, path: iconFilename}); | 126 { |
127 | 127 var excluded = isWhitelisted(tab.url); |
128 // Only show icon for pages we can influence (http: and https:) | 128 iconFilename = excluded ? "icons/abp-19-whitelisted.png" : "icons/abp-19.png
"; |
129 if(/^https?:/.test(tab.url)) | 129 } |
130 { | 130 |
131 chrome.browserAction.setTitle({tabId: tab.id, title: "Adblock Plus"}); | 131 tab.browserAction.setIcon(iconFilename); |
132 if ("shouldShowIcon" in localStorage && localStorage["shouldShowIcon"] == "f
alse") | 132 tab.browserAction.setTitle(ext.i18n.getMessage("name")); |
133 chrome.browserAction.hide(tab.id); | 133 |
134 else | 134 iconAnimation.registerTab(tab, iconFilename); |
135 chrome.browserAction.show(tab.id); | 135 |
136 | 136 if (localStorage.shouldShowIcon == "false") |
| 137 tab.browserAction.hide(); |
| 138 else |
| 139 tab.browserAction.show(); |
| 140 |
| 141 if (require("info").platform == "chromium") // TODO: Implement context menus f
or Safari |
137 // Set context menu status according to whether current tab has whitelisted
domain | 142 // Set context menu status according to whether current tab has whitelisted
domain |
138 if (excluded) | 143 if (excluded) |
139 chrome.contextMenus.removeAll(); | 144 chrome.contextMenus.removeAll(); |
140 else | 145 else |
141 showContextMenu(); | 146 showContextMenu(); |
142 } | |
143 } | 147 } |
144 | 148 |
145 /** | 149 /** |
146 * Old versions stored filter data in the localStorage object, this will import | 150 * Old versions stored filter data in the localStorage object, this will import |
147 * it into FilterStorage properly. | 151 * it into FilterStorage properly. |
148 */ | 152 */ |
149 function importOldData() | 153 function importOldData() |
150 { | 154 { |
151 function addSubscription(url, title) | 155 function addSubscription(url, title) |
152 { | 156 { |
(...skipping 207 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
360 } | 364 } |
361 else | 365 else |
362 addAcceptable = false; | 366 addAcceptable = false; |
363 } | 367 } |
364 | 368 |
365 if (!addSubscription && !addAcceptable) | 369 if (!addSubscription && !addAcceptable) |
366 return; | 370 return; |
367 | 371 |
368 function notifyUser() | 372 function notifyUser() |
369 { | 373 { |
370 chrome.tabs.create({ | 374 ext.windows.getLastFocused(function(win) |
371 url: chrome.extension.getURL("firstRun.html") | 375 { |
| 376 win.openTab(ext.getURL("firstRun.html")); |
372 }); | 377 }); |
373 } | 378 } |
374 | 379 |
375 if (addSubscription) | 380 if (addSubscription) |
376 { | 381 { |
377 // Load subscriptions data | 382 // Load subscriptions data |
378 var request = new XMLHttpRequest(); | 383 var request = new XMLHttpRequest(); |
379 request.open("GET", "subscriptions.xml"); | 384 request.open("GET", "subscriptions.xml"); |
380 request.addEventListener("load", function() | 385 request.addEventListener("load", function() |
381 { | 386 { |
(...skipping 17 matching lines...) Expand all Loading... |
399 notifyUser(); | 404 notifyUser(); |
400 } | 405 } |
401 | 406 |
402 // Set up context menu for user selection of elements to block | 407 // Set up context menu for user selection of elements to block |
403 function showContextMenu() | 408 function showContextMenu() |
404 { | 409 { |
405 chrome.contextMenus.removeAll(function() | 410 chrome.contextMenus.removeAll(function() |
406 { | 411 { |
407 if(typeof localStorage["shouldShowBlockElementMenu"] == "string" && localSto
rage["shouldShowBlockElementMenu"] == "true") | 412 if(typeof localStorage["shouldShowBlockElementMenu"] == "string" && localSto
rage["shouldShowBlockElementMenu"] == "true") |
408 { | 413 { |
409 chrome.contextMenus.create({'title': chrome.i18n.getMessage('block_element
'), 'contexts': ['image', 'video', 'audio'], 'onclick': function(info, tab) | 414 chrome.contextMenus.create({"title": chrome.i18n.getMessage("block_element
"), "contexts": ["image", "video", "audio"], "onclick": function(info, tab) |
410 { | 415 { |
411 if(info.srcUrl) | 416 if(info.srcUrl) |
412 chrome.tabs.sendRequest(tab.id, {reqtype: "clickhide-new-filter", fi
lter: info.srcUrl}); | 417 chrome.tabs.sendRequest(tab.id, {reqtype: "clickhide-new-filter", fi
lter: info.srcUrl}); |
413 }}); | 418 }}); |
414 } | 419 } |
415 }); | 420 }); |
416 } | 421 } |
417 | 422 |
418 /** | 423 /** |
419 * Opens Options window or focuses an existing one. | 424 * Opens options tab or focuses an existing one, within the last focused window
. |
420 * @param {Function} callback function to be called with the window object of | 425 * @param {Function} callback function to be called with the |
421 * the Options window | 426 Tab object of the options tab |
422 */ | 427 */ |
423 function openOptions(callback) | 428 function openOptions(callback) |
424 { | 429 { |
425 function findOptions(selectTab) | 430 ext.windows.getLastFocused(function(win) |
426 { | 431 { |
427 var views = chrome.extension.getViews({type: "tab"}); | 432 win.getAllTabs(function(tabs) |
428 for (var i = 0; i < views.length; i++) | 433 { |
429 if ("startSubscriptionSelection" in views[i]) | 434 var optionsUrl = ext.getURL("options.html"); |
430 return views[i]; | 435 |
431 | 436 for (var i = 0; i < tabs.length; i++) |
432 return null; | 437 { |
433 } | 438 if (tabs[i].url == optionsUrl) |
434 | |
435 function selectOptionsTab() | |
436 { | |
437 chrome.windows.getAll({populate: true}, function(windows) | |
438 { | |
439 var url = chrome.extension.getURL("options.html"); | |
440 for (var i = 0; i < windows.length; i++) | |
441 for (var j = 0; j < windows[i].tabs.length; j++) | |
442 if (windows[i].tabs[j].url == url) | |
443 chrome.tabs.update(windows[i].tabs[j].id, {selected: true}); | |
444 }); | |
445 } | |
446 | |
447 var view = findOptions(); | |
448 if (view) | |
449 { | |
450 selectOptionsTab(); | |
451 callback(view); | |
452 } | |
453 else | |
454 { | |
455 var onLoad = function() | |
456 { | |
457 var view = findOptions(); | |
458 if (view) | |
459 callback(view); | |
460 }; | |
461 | |
462 chrome.tabs.create({url: chrome.extension.getURL("options.html")}, function(
tab) | |
463 { | |
464 if (tab.status == "complete") | |
465 onLoad(); | |
466 else | |
467 { | |
468 var id = tab.id; | |
469 var listener = function(tabId, changeInfo, tab) | |
470 { | 439 { |
471 if (tabId == id && changeInfo.status == "complete") | 440 tabs[i].activate(); |
472 { | 441 if (callback) |
473 chrome.tabs.onUpdated.removeListener(listener); | 442 callback(tabs[i]); |
474 onLoad(); | 443 return; |
475 } | 444 } |
476 }; | 445 } |
477 chrome.tabs.onUpdated.addListener(listener); | 446 |
478 } | 447 win.openTab(optionsUrl, callback && function(tab) |
479 }); | 448 { |
480 } | 449 tab.onCompleted.addListener(callback); |
481 } | 450 }); |
482 | |
483 var iconAnimationTimer = null; | |
484 var animatedIconTab = null; | |
485 | |
486 function stopIconAnimation() | |
487 { | |
488 if (!iconAnimationTimer) | |
489 return; | |
490 | |
491 clearTimeout(iconAnimationTimer); | |
492 iconAnimationTimer = null; | |
493 animatedIconTab = null; | |
494 } | |
495 | |
496 function loadImages(imageFiles, callback) | |
497 { | |
498 var images = {}; | |
499 var imagesLoaded = 0; | |
500 imageFiles.forEach(function(imageFile) | |
501 { | |
502 var image = new Image(); | |
503 image.src = imageFile; | |
504 image.addEventListener("load", function() | |
505 { | |
506 images[imageFile] = image; | |
507 if (++imagesLoaded === imageFiles.length) | |
508 callback(images); | |
509 }); | 451 }); |
510 }); | 452 }); |
511 } | 453 } |
512 | 454 |
513 function startIconAnimation(tab, iconPath) | |
514 { | |
515 stopIconAnimation(); | |
516 animatedIconTab = tab; | |
517 | |
518 var severitySuffix = activeNotification.severity === "critical" | |
519 ? "critical" : "information"; | |
520 var notificationIconPath = "icons/notification-" + severitySuffix + ".png"; | |
521 var iconFiles = [iconPath, notificationIconPath]; | |
522 loadImages(iconFiles, function(images) | |
523 { | |
524 var icon = images[iconPath]; | |
525 var notificationIcon = images[notificationIconPath]; | |
526 | |
527 var canvas = document.createElement("canvas"); | |
528 canvas.width = icon.width; | |
529 canvas.height = icon.height; | |
530 var context = canvas.getContext("2d"); | |
531 | |
532 var currentFrame = 0; | |
533 var frameOpacities = [0, 0.2, 0.4, 0.6, 0.8, | |
534 1, 1, 1, 1, 1, | |
535 0.8, 0.6, 0.4, 0.2, 0]; | |
536 | |
537 function animationStep() | |
538 { | |
539 var opacity = frameOpacities[currentFrame]; | |
540 context.clearRect(0, 0, canvas.width, canvas.height); | |
541 context.globalAlpha = 1; | |
542 context.drawImage(icon, 0, 0); | |
543 context.globalAlpha = opacity; | |
544 context.drawImage(notificationIcon, 0, 0); | |
545 var imageData = context.getImageData(0, 0, canvas.width, canvas.height); | |
546 chrome.browserAction.setIcon({tabId: tab.id, imageData: imageData}); | |
547 | |
548 var interval; | |
549 currentFrame++; | |
550 if (currentFrame < frameOpacities.length) | |
551 { | |
552 var duration = 3000; | |
553 interval = duration / frameOpacities.length; | |
554 } | |
555 else | |
556 { | |
557 currentFrame = 0; | |
558 interval = 10000; | |
559 } | |
560 iconAnimationTimer = setTimeout(animationStep, interval); | |
561 } | |
562 animationStep(); | |
563 }); | |
564 } | |
565 | |
566 function prepareNotificationIconAndPopup() | 455 function prepareNotificationIconAndPopup() |
567 { | 456 { |
568 activeNotification.onClicked = function() | 457 activeNotification.onClicked = function() |
569 { | 458 { |
570 var tab = animatedIconTab; | 459 iconAnimation.stop(); |
571 stopIconAnimation(); | |
572 activeNotification = null; | 460 activeNotification = null; |
573 refreshIconAndContextMenu(tab); | |
574 }; | 461 }; |
575 | 462 |
576 chrome.windows.getLastFocused({populate: true}, function(window) | 463 iconAnimation.update(activeNotification.severity); |
577 { | |
578 chrome.tabs.query({active: true, windowId: window.id}, function(tabs) | |
579 { | |
580 tabs.forEach(refreshIconAndContextMenu); | |
581 }); | |
582 }); | |
583 } | 464 } |
584 | 465 |
585 function showNotification(notification) | 466 function showNotification(notification) |
586 { | 467 { |
587 activeNotification = notification; | 468 activeNotification = notification; |
588 | 469 |
589 if (activeNotification.severity === "critical" | 470 if (activeNotification.severity === "critical" |
590 && typeof webkitNotifications !== "undefined") | 471 && typeof webkitNotifications !== "undefined") |
591 { | 472 { |
592 var notification = webkitNotifications.createHTMLNotification("notification.
html"); | 473 var notification = webkitNotifications.createHTMLNotification("notification.
html"); |
593 notification.show(); | 474 notification.show(); |
594 notification.addEventListener("close", prepareNotificationIconAndPopup); | 475 notification.addEventListener("close", prepareNotificationIconAndPopup); |
595 } | 476 } |
596 else | 477 else |
597 prepareNotificationIconAndPopup(); | 478 prepareNotificationIconAndPopup(); |
598 } | 479 } |
599 | 480 |
600 /** | 481 /** |
601 * This function is a hack - we only know the tabId and document URL for a | 482 * This function is a hack - we only know the tabId and document URL for a |
602 * message but we need to know the frame ID. Try to find it in webRequest's | 483 * message but we need to know the frame ID. Try to find it in webRequest"s |
603 * frame data. | 484 * frame data. |
604 */ | 485 */ |
605 function getFrameId(tabId, url) | 486 function getFrameId(tab, url) |
606 { | 487 { |
607 if (tabId in frames) | 488 for (var frameId in frames.get(tab)) |
608 { | 489 if (getFrameUrl(tab, frameId) == url) |
609 for (var f in frames[tabId]) | 490 return frameId; |
610 { | |
611 if (getFrameUrl(tabId, f) == url) | |
612 return f; | |
613 } | |
614 } | |
615 return -1; | 491 return -1; |
616 } | 492 } |
617 | 493 |
618 chrome.extension.onRequest.addListener(function(request, sender, sendResponse) | 494 ext.onMessage.addListener(function (msg, sender, sendResponse) |
619 { | 495 { |
620 switch (request.reqtype) | 496 switch (msg.type) |
621 { | 497 { |
622 case "get-settings": | 498 case "get-selectors": |
623 var hostDomain = null; | |
624 var selectors = null; | 499 var selectors = null; |
625 | 500 var frameId = sender.tab ? getFrameId(sender.tab, msg.frameUrl) : -1; |
626 var tabId = -1; | 501 |
627 var frameId = -1; | 502 if (!isFrameWhitelisted(sender.tab, frameId, "DOCUMENT") && |
628 if (sender.tab) | 503 !isFrameWhitelisted(sender.tab, frameId, "ELEMHIDE")) |
629 { | |
630 tabId = sender.tab.id; | |
631 frameId = getFrameId(tabId, request.frameUrl); | |
632 } | |
633 | |
634 var enabled = !isFrameWhitelisted(tabId, frameId, "DOCUMENT") && !isFrameW
hitelisted(tabId, frameId, "ELEMHIDE"); | |
635 if (enabled && request.selectors) | |
636 { | 504 { |
637 var noStyleRules = false; | 505 var noStyleRules = false; |
638 var host = extractHostFromURL(request.frameUrl); | 506 var host = extractHostFromURL(msg.frameUrl); |
639 hostDomain = getBaseDomain(host); | |
640 for (var i = 0; i < noStyleRulesHosts.length; i++) | 507 for (var i = 0; i < noStyleRulesHosts.length; i++) |
641 { | 508 { |
642 var noStyleHost = noStyleRulesHosts[i]; | 509 var noStyleHost = noStyleRulesHosts[i]; |
643 if (host == noStyleHost || (host.length > noStyleHost.length && | 510 if (host == noStyleHost || (host.length > noStyleHost.length && |
644 host.substr(host.length - noStyleHost.leng
th - 1) == "." + noStyleHost)) | 511 host.substr(host.length - noStyleHost.leng
th - 1) == "." + noStyleHost)) |
645 { | 512 { |
646 noStyleRules = true; | 513 noStyleRules = true; |
647 } | 514 } |
648 } | 515 } |
649 selectors = ElemHide.getSelectorsForDomain(host, false); | 516 selectors = ElemHide.getSelectorsForDomain(host, false); |
650 if (noStyleRules) | 517 if (noStyleRules) |
651 { | 518 { |
652 selectors = selectors.filter(function(s) | 519 selectors = selectors.filter(function(s) |
653 { | 520 { |
654 return !/\[style[\^\$]?=/.test(s); | 521 return !/\[style[\^\$]?=/.test(s); |
655 }); | 522 }); |
656 } | 523 } |
657 } | 524 } |
658 | 525 |
659 sendResponse({enabled: enabled, hostDomain: hostDomain, selectors: selecto
rs}); | 526 sendResponse(selectors); |
660 break; | 527 break; |
661 case "should-collapse": | 528 case "should-collapse": |
662 var tabId = -1; | 529 var frameId = sender.tab ? getFrameId(sender.tab, msg.documentUrl) : -1; |
663 var frameId = -1; | 530 |
664 if (sender.tab) | 531 if (isFrameWhitelisted(sender.tab, frameId, "DOCUMENT")) |
665 { | |
666 tabId = sender.tab.id; | |
667 frameId = getFrameId(tabId, request.documentUrl); | |
668 } | |
669 | |
670 if (isFrameWhitelisted(tabId, frameId, "DOCUMENT")) | |
671 { | 532 { |
672 sendResponse(false); | 533 sendResponse(false); |
673 break; | 534 break; |
674 } | 535 } |
675 | 536 |
676 var requestHost = extractHostFromURL(request.url); | 537 var requestHost = extractHostFromURL(msg.url); |
677 var documentHost = extractHostFromURL(request.documentUrl); | 538 var documentHost = extractHostFromURL(msg.documentUrl); |
678 var thirdParty = isThirdParty(requestHost, documentHost); | 539 var thirdParty = isThirdParty(requestHost, documentHost); |
679 var filter = defaultMatcher.matchesAny(request.url, request.type, document
Host, thirdParty); | 540 var filter = defaultMatcher.matchesAny(msg.url, msg.mediatype, documentHos
t, thirdParty); |
680 if (filter instanceof BlockingFilter) | 541 if (filter instanceof BlockingFilter) |
681 { | 542 { |
682 var collapse = filter.collapse; | 543 var collapse = filter.collapse; |
683 if (collapse == null) | 544 if (collapse == null) |
684 collapse = (localStorage.hidePlaceholders != "false"); | 545 collapse = (localStorage.hidePlaceholders != "false"); |
685 sendResponse(collapse); | 546 sendResponse(collapse); |
686 } | 547 } |
687 else | 548 else |
688 sendResponse(false); | 549 sendResponse(false); |
689 break; | 550 break; |
690 case "get-domain-enabled-state": | 551 case "get-domain-enabled-state": |
691 // Returns whether this domain is in the exclusion list. | 552 // Returns whether this domain is in the exclusion list. |
692 // The page action popup asks us this. | 553 // The page action popup asks us this. |
693 if(sender.tab) | 554 if(sender.tab) |
694 { | 555 { |
695 sendResponse({enabled: !isWhitelisted(sender.tab.url)}); | 556 sendResponse({enabled: !isWhitelisted(sender.tab.url)}); |
696 return; | 557 return; |
697 } | 558 } |
698 break; | 559 break; |
699 case "add-filters": | 560 case "add-filters": |
700 if (request.filters && request.filters.length) | 561 if (msg.filters && msg.filters.length) |
701 { | 562 { |
702 for (var i = 0; i < request.filters.length; i++) | 563 for (var i = 0; i < msg.filters.length; i++) |
703 FilterStorage.addFilter(Filter.fromText(request.filters[i])); | 564 FilterStorage.addFilter(Filter.fromText(msg.filters[i])); |
704 } | 565 } |
705 break; | 566 break; |
706 case "add-subscription": | 567 case "add-subscription": |
707 openOptions(function(view) | 568 openOptions(function(tab) |
708 { | 569 { |
709 view.startSubscriptionSelection(request.title, request.url); | 570 tab.sendMessage(msg); |
710 }); | 571 }); |
711 break; | 572 break; |
712 case "forward": | 573 case "forward": |
713 chrome.tabs.sendRequest(sender.tab.id, request.request, sendResponse); | 574 tab.sendMessage(msg.payload, sendResponse); |
714 break; | 575 break; |
715 default: | 576 default: |
716 sendResponse({}); | 577 sendResponse({}); |
717 break; | 578 break; |
718 } | 579 } |
719 }); | 580 }); |
720 | 581 |
721 // Show icon as page action for all tabs that already exist | 582 // Show icon as page action for all tabs that already exist |
722 chrome.windows.getAll({populate: true}, function(windows) | 583 ext.windows.getAll(function(windows) |
723 { | 584 { |
724 for (var i = 0; i < windows.length; i++) | 585 for (var i = 0; i < windows.length; i++) |
725 for (var j = 0; j < windows[i].tabs.length; j++) | 586 { |
726 refreshIconAndContextMenu(windows[i].tabs[j]); | 587 windows[i].getAllTabs(function(tabs) |
| 588 { |
| 589 tabs.forEach(refreshIconAndContextMenu); |
| 590 }); |
| 591 } |
727 }); | 592 }); |
728 | 593 |
729 // Update icon if a tab changes location | 594 // Update icon if a tab changes location |
730 chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) | 595 ext.tabs.onLoading.addListener(function(tab) |
731 { | 596 { |
732 chrome.tabs.sendRequest(tabId, {reqtype: "clickhide-deactivate"}) | 597 tab.sendMessage({type: "clickhide-deactivate"}); |
733 if(changeInfo.status == "loading") | 598 refreshIconAndContextMenu(tab); |
734 refreshIconAndContextMenu(tab); | |
735 }); | |
736 | |
737 // Refresh icon when switching tabs or windows | |
738 chrome.tabs.onActivated.addListener(function(activeInfo) | |
739 { | |
740 refreshIconAndContextMenu(animatedIconTab); | |
741 chrome.tabs.get(activeInfo.tabId, refreshIconAndContextMenu); | |
742 }); | |
743 chrome.windows.onFocusChanged.addListener(function(windowId) | |
744 { | |
745 refreshIconAndContextMenu(animatedIconTab); | |
746 chrome.tabs.query({active: true, windowId: windowId}, function(tabs) | |
747 { | |
748 tabs.forEach(refreshIconAndContextMenu); | |
749 }); | |
750 }); | 599 }); |
751 | 600 |
752 setTimeout(function() | 601 setTimeout(function() |
753 { | 602 { |
754 var notificationToShow = Notification.getNextToShow(); | 603 var notificationToShow = Notification.getNextToShow(); |
755 if (notificationToShow) | 604 if (notificationToShow) |
756 showNotification(notificationToShow); | 605 showNotification(notificationToShow); |
757 }, 3 * 60 * 1000); | 606 }, 3 * 60 * 1000); |
LEFT | RIGHT |