chrome.notification.create 和 chrome.notification.onClicked 在 Firefox WebExtension 附加组件中

chrome.notification.create with chrome.notification.onClicked in a Firefox WebExtension add-on

我无法理解 WebExtensions notification.onClicked 事件的文档。

最终,我试图在您单击时将通知文本复制到剪贴板。但是,现在我在理解回调的时候遇到了问题,或者我必须在哪里插入 notification.onClicked 函数。

目前,我不知道为什么 notification.onClicked 侦听器什么都不做。

我的代码(作为 WebExtension Firefox 附加组件演示问题所需的所有代码):

manifest.json

{
    "description": "Test Webextension",
    "manifest_version": 2,
    "name": "Σ",
    "version": "1.0",

    "permissions": [
        "<all_urls>",
        "notifications",
        "webRequest"
    ],

    "background": {
        "scripts": ["background.js"]
    }
}

background.js

'use strict';

function logURL(requestDetails) {
    notify("Testmessage");
    chrome.notifications.onClicked.addListener(function() {
        console.log("TEST TEST");
    });
}

function notify(notifyMessage) {
    var options = {
        type: "basic",
        iconUrl: chrome.extension.getURL("icons/photo.png"),
        title: "",
        message: notifyMessage
    };
    chrome.notifications.create("ID123", options);
}

chrome.webRequest.onBeforeRequest.addListener(
    logURL, {
        urls: ["<all_urls>"]
    }
);

首先,您需要在 Firefox 47.0+ 中对此进行测试,因为在版本 47.0 中添加了对 chrome.notifications.onClicked() 的支持。虽然这可能不是您的问题,但这是一种可能。

您的代码存在多个问题。有些在您的代码中,但主要是您 运行 陷入了 Firefox 错误。

Firefox 错误:
您的主要问题是您 运行 遇到了一个 Firefox 错误,如果您尝试过快地创建通知,Firefox 会感到困惑。因此,我实现了一个通知队列并限制了通知的创建速度。什么是 "too rapidly" 可能同时依赖于 OS 和 CPU,因此您最好谨慎行事,并设置调用 chrome.notifications.create() to a higher value. in the code below, the delay is 500ms. I have added a note regarding this issue in the chrome.notifications.create() page on MDN and on the Chrome incompatibilities 页面之间的延迟。

添加同一监听器的多个副本:
您在代码中做错的主要事情是您将匿名函数添加为侦听器,多次使用 chrome.notifications.onClicked.addListener(),同一事件。这是事件处理程序的一般问题。当您使用匿名函数时,每次您尝试添加它时,它都是一个不同的 actual 函数,因此相同的功能(在多个相同的函数中)会被添加多次。您不应该多次向同一个事件添加执行完全相同的操作的函数。这样做几乎总是在您的程序中出错并导致意外操作。

在这种情况下,每次用户单击通知时,多个函数最终会向控制台输出多行 TEST TEST。对于导致调用 logURL 的每个 Web 请求,每次点击输出的行数将增加一。

防止这样做的方法是确保只添加监听器一次。如果您使用的是匿名函数,则只能通过确保只执行 addListener(或 addEventlistener一次(通常只添加来自主代码的监听器(不是来自函数内),或来自仅调用 一次 的函数。或者,您可以直接在全局范围内 name/define 您的监听器函数(或您尝试添加侦听器的所有地方都可以访问的其他范围)(例如 function myListener(){...})。然后,当您添加 myListener 时,您总是指的是与 [=83= 完全相同的函数] 自动阻止您以相同的方式多次添加到同一事件。

应该注意的是,如果您试图从另一个侦听器添加一个匿名函数作为侦听器,您几乎总是做错了。将相同匿名侦听器的副本多次添加到同一事件是一个常见错误。

获取通知文字:
虽然您没有实施任何关于使用通知文本的操作,但您声明了您希望在用户单击通知时将通知文本添加到剪贴板。您无法从 chrome.notifications API 的任何部分获取通知文本。因此,您必须自己存储该信息。下面的代码实现了一个对象来执行此操作,因此可以在 chrome.notifications.onClicked() 处理程序中访问文本。

示例代码:
下面的代码实现了我相信你想要的。它只是创建并单击通知,同时可以访问 chrome.notifications.onClicked() 侦听器中的通知文本。它没有实现关于将文本放入剪贴板的部分,因为这实际上没有在您的问题的代码中实现。我在代码中添加了自由注释来解释正在发生的事情,并提供了相当多的 console.log() 输出来帮助显示正在发生的事情。我已经在 Firefox Developer Edition(当前为 v51.0a2)和 Google Chrome.

中对其进行了测试

background.js(你的manifest.json没有变化):

'use strict';
//* For testing, open the Browser Console
var isFirefox = window.InstallTrigger?true:false;
try{
    if(isFirefox){  //Only do this in Firefox
        //Alert is not supported in Firefox. This forces the Browser Console open.
        //This abuse of a misfeature works in FF49.0b+, not in FF48
        alert('Open the Browser Console.');
    }
}catch(e){
    //alert throws an error in Firefox versions below 49
    console.log('Alert threw an error. Probably Firefox version below 49.');
}
//*

//Firefox gets confused if we try to create notifications too fast (this is a bug in
//  Firefox).  So, for Firefox, we rate limit showing the notifications.
//  The maximum rate possible (minimum delay) is probably OS and CPU speed dependent.
//  Thus, you should error  on the side of caution and make the delay longer.
//  No delay is needed in Chrome.
var notificationRateLimit = isFirefox ? 500:0;//Firefox:Only one notification every 500m
var notificationRateLimitTimeout=-1; //Timeout for notification rate limit
var sentNotifications={};
var notificationsQueue=[];
var notificationIconUrl = chrome.extension.getURL("icons/photo.png");
function logURL(requestDetails) {
    //console.log('webRequest.onBeforeRequest URL:' + requestDetails.url);
    //NOTE: In Chrome, a webRequest is issued to obtain the icon for the notification. 
    //  If Chrome finds the icon, that webRequest for the icon is only issued twice.
    //  However, if the icon does not exist, then this sets up an infinite loop which
    //  will peg one CPU at maximum utilization.
    //  Thus, you should not notify for the icon URL.
    //  You should consider excluding from notification all URLs from within your
    //  own extension.
    if(requestDetails.url !== notificationIconUrl ){
        notify('webRequest URL: ' + requestDetails.url);
    }
    //Your Original code in the Question:
    //Unconditionally adding an anonymous notifications.onClicked listener
    //  here would result in multiple lines of 'TEST TEST' ouput for each click
    //  on a notification. You should add the listener only once.
}

function notify(notifyMessage) {
    //Add the message to the notifications queue.
    notificationsQueue.push(notifyMessage);
    console.log('Notification added to queue. message:' + notifyMessage);
    if(notificationsQueue.length == 1){
        //If this is the only notification in the queue, send it.
        showNotificationQueueWithRateLimit();
    }
    //If the notificationsQueue has additional entries, they will get
    //  shown when the current notification has completed being shown.
}

function showNotificationQueueWithRateLimit(){
    if(notificationRateLimitTimeout===-1){
        //There is no current delay active, so immediately send the notification.
        showNextNotification();
    }
    //If there is a delay active, we don't need to do anything as the notification
    //  will be sent when it gets processed out of the queue.
}

function showNextNotification() {
    notificationRateLimitTimeout=-1; //Indicate that there is no current timeout running.
    if(notificationsQueue.length === 0){
        return;  //Nothing in queue
    }
    //Indicate that there will be a timeout running.
    //  Neeed because we set the timeout in the notifications.create callback function.
    notificationRateLimitTimeout=-2;
    //Get the next notification from the queue
    let notifyMessage = notificationsQueue.shift();
    console.log('Showing notification message:' + notifyMessage);
    //Set our standard options
    let options = {
        type: "basic",
        //If the icon does not exist an error is generated in Chrome, but not Firefox.
        //  In Chrome a webRequest is generated to fetch the icon. Thus, we need to know
        //  the iconUrl in the webRequest handler, and not notify for that URL.
        iconUrl: notificationIconUrl,
        title: "",
        message: notifyMessage
    };
    //If you want multiple notifications shown at the same time, your message ID must be
    //  unique (at least within your extension).
    //Creating a notification with the same ID causes the prior notification to be
    //  destroyed and the new one created in its place (not just the text being replaced).
    //Use the following two lines if you want only one notification at a time.  If you are
    //  actually going to notify on each webRequest (rather than doing so just being a way
    //  to test), you should probably only have one notification as they will rapedly be
    //  off the screen for many pages.
    //let myId = 'ID123';
    //chrome.notifications.create(myId,options,function(id){
    //If you want multiple notifications without having to create a unique ID for each one,
    //  then let the ID be created for you by using the following line:
    chrome.notifications.create(options,function(id){
        //In this callback the notification has not necessarily actually been shown yet,
        //  just that the notification ID has been created and the notification is in the
        //  process of being shown.
        console.log('Notification created, id=' + id + ':: message:' + notifyMessage);
        logIfError();
        //Remember the text so we can get it later
        sentNotifications[id] = {
            message: notifyMessage
        }
        //Show the next notification in the FIFO queue after a rate limiting delay
        //  This is called unconditionally in order to start the delay should another
        //  notification be queued, even if one is not in the queue now.
        notificationRateLimitTimeout = setTimeout(showNextNotification
                                                  ,notificationRateLimit);
    });
}

function logIfError(){
    if(chrome.runtime.lastError){
        let message =chrome.runtime.lastError.message;
        console.log('Error: ' + message);
    }
}

chrome.webRequest.onBeforeRequest.addListener(
    logURL, {
        urls: ["<all_urls>"]
    }
);

//Add the notifications.onClicked anonymous listener only once:
//  Personally, I consider it better practice to use a named function that
//  is defined in the global scope. Doing so prevents inadvertantly adding
//  it multiple times. Although, your code should be written such that you 
//  don't do that anyway.
chrome.notifications.onClicked.addListener(function(id) {
    //We can not get the notification text from here, just the ID.  Thus, we
    //  have to use the text which was remembered.
    console.log('Clicked notification message text: ', sentNotifications[id].message);
    //In Firefox the notification is automatically cleared when it is clicked.
    //  If you want the same functionality in Chrome, you will need to clear() it
    //  yourself: 
    //Always do this instead of only when not in Firefox so that it remains consistent
    //  Even if Firefox changes to match Chrome.
    chrome.notifications.clear(id);
    //This is the last place we use the text of the notification, so we delete it
    //  from sentNotifications so we don't have a memory leak.
    delete sentNotifications[id];
});

//Test the notifications directly without the need to have webRequests:
notify('Background.js loaded');
notify('Second notification');

在解决这个问题的过程中,我发现 Chrome 和 Firefox 之间存在多个不兼容问题。我正在更新 MDN 以提及 MDN 文档中的不兼容性。