2

我的 Firefox 扩展提供了一个 JavaScript 函数,网站可以使用它来访问插件功能。网站调用此函数并提供两个回调。

网站代码:

function onButtonClick() {
  var callbackSuccess = function() { alert("Yeah!"); };
  var callbackError = function() { alert("Oh no!"); };
  if (window.magicAddon) { // Check if my addon is installed
    magicAddon.doStuff(callbackSuccess, callbackError);
  }
}

内容脚本:

unsafeWindow.magicAddon = {
  doStuff: function(callbackSuccess, callbackError) {
    // Bind the two callbacks to events. The addon will fire one of them
    self.port.on("doStuffSuccess", callbackSuccess);
    self.port.on("doStuffError", callbackError);

    // Fire the event that lets the addon do stuff
    self.port.emit("doStuff");
  }
};

这在第一次调用时效果很好,但是下次网站调用 doStuff() 时,新的侦听器会加起来,并且 alert() 会执行两次。下次三个警报,依此类推。

知道如何优雅地避免听众加起来吗?我可以完全清除事件类型吗?

到目前为止没有什么效果:

  • 改为使用self.port.once(..),因为我有两个回调事件:只有一个被清除的回调事件,另一个保留并与下一个相加。
  • 在注册新的监听器之前,用 删除旧的监听器self.port.removeListener,因为我没有旧的回调引用。

问题似乎类似于如何删除事件侦听器?,只是他使用了一个回调监听器,因此可以使用self.port.once(..).

4

2 回答 2

1

您可以使用self.port.once然后手动删除其他回调:

doStuff: function(callbackSuccess, callbackError) {
  // Bind the two callbacks to events. The addon will fire one of them
  self.port.once("doStuffSuccess", function() {
    callbackSuccess();
    self.port.removeListener(callbackError);
  });
  self.port.once("doStuffError", function() {
    callbackError();
    self.port.removeListener(callbackSuccess);
  });

  // Fire the event that lets the addon do stuff
  self.port.emit("doStuff");
}

您在内容脚本中,因此您无法完全清除事件类型,您只能在主插件代码中执行此操作,并使用低级 API。

但是,我建议避免unsafeWindow提供这种功能,因为它是不安全的。如果您保持 API 异步,您可以使用postMessage内容脚本和页面之间的管道来做同样的事情;并提供一个单独的 javascript 文件,人们可以将其包含在他们的网站中,您可以在其中公开 postMessages 调用的抽象(例如magicAddon.doStuff())。如果您愿意,您还可以在网站中自动从您的插件中注入该脚本。

处理这种机制肯定有点复杂,但你可以避免使用unsafeWindow.

您可以在此处找到有关内容脚本通信的更多信息。

希望能帮助到你!

更新:要回答您的评论,您需要一个变量来跟踪doStuff呼叫活动:

doStuff: function() {
  var executing = false;

  return function(callbackSuccess, callbackError) {
      if (executing)
        return;

      executing = true;

      // Bind the two callbacks to events. The addon will fire one of them
      self.port.once("doStuffSuccess", function() {
        executing = false;
        callbackSuccess();
        self.port.removeListener(callbackError);
      });
      self.port.once("doStuffError", function() {
        executing = false;

        callbackError();
        self.port.removeListener(callbackSuccess);
      });

      // Fire the event that lets the addon do stuff
      self.port.emit("doStuff");
  }
}()

注意()最后。基本上这样设置在末尾doStuff的函数是我们最初分配的函数的结果。通过这种方式,我们为该方法创建一个闭包doStuff,其中有一个executing变量,并跟踪是否已经doStuff执行,以便丢弃任何其他doStuff调用,直到完成。

注意:即使在这种情况下对于 javascript 来说不是必需的,也可能是一个很好的约定将该函数包装在括号中,以识别该函数是“自执行”的:`doStuff: (function() {...}())

您也可以将对象的属性magicAddon用于此作业,但在这种情况下它将被公开。

于 2012-12-12T02:38:41.007 回答
1

您必须考虑网站magicAddon.doStuff()在收到第一次呼叫的响应之前可能会再次呼叫的可能性。因此,在任何时间点都可能执行多个调用 - 您应该确保调用正确的侦听器。此外,如果任一回调触发,您需要删除两个侦听器 - 否则您将泄漏内存。这是如何工作的:

doStuff: function(callbackSuccess, callbackError) {
  // Generate a random call ID
  var callID = Math.random();

  // Bind the two callbacks to events. Make sure to only act on events with the
  // right call ID.
  function onSuccess(id) {
    if (id == callID) {
      callbackSuccess();
      removeListeners();
    }
  }
  function onError(id) {
    if (id == callID) {
      callbackError();
      removeListeners();
    }
  }
  function removeListeners() {
    self.port.removeListener("doStuffSuccess", onSuccess);
    self.port.removeListener("doStuffError", onError);
  };
  self.port.on("doStuffSuccess", onSuccess);
  self.port.on("doStuffError", onError);

  // Fire the event that lets the addon do stuff
  self.port.emit("doStuff", callID);
}

这使用随机调用 ID 来识别调用 - 代码处理doStuff事件获取调用 ID 作为参数,并需要在doStuffSuccessordoStuffError事件中将其发送回以确保调用正确的回调。

于 2012-12-12T09:07:39.313 回答