Firefox扩展(SDK)中的内容脚本添加了事件侦听器

时间:2012-12-11 20:22:23

标签: javascript firefox firefox-addon firefox-addon-sdk

我的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删除旧的侦听器,因为我没有旧的回调引用。

问题似乎与How to remove an event listener?类似,只是他使用了一个回调侦听器,因此可以使用self.port.once(..)

2 个答案:

答案 0 :(得分: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

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

希望它有所帮助!

更新:要回复您的评论,您需要一个变量来跟踪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对象的属性来完成这项工作,但是在这种情况下它会被曝光。

答案 1 :(得分: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作为参数,并需要在doStuffSuccessdoStuffError事件中将其发回确保调用正确的回调。