如何在(javascript)Firefox插件中有效地存储传入数据

How to store incoming data efficiently in a (javascript) firefox addon?

本文关键字:有效地 存储 数据 插件 Firefox javascript      更新时间:2023-09-26

请求某个URL时,我想保存传入的markdown,将其转换为HTML,然后将其传递给显示。如果使用观察器,我可以获取通道,并通过 nsiTraceableChannel 将通道的侦听器设置为我的特殊"覆盖"侦听器,然后将数据传递给原始侦听器进行显示,但我很困惑此时该怎么做。onDataAvailable 方法传递一个 nsiInputStream,该流无法在 JavaScript 代码中读取。虽然我可以将其包装在 nsiScriptableInputStream 中并从中读取,但这似乎会引入大量围绕可能重复多次的简单读取操作的包装。我宁愿一次用漂亮的封闭二进制代码阅读它。

我想做的是使用 NetUtils.asyncCopy 将该输入流复制到存储流,并在完成后将存储流的结果转换为传递原始侦听器的内容。但是,这不会也继续使用 onDataAvailable 调用我的覆盖侦听器吗?文档说onDataAvalilable必须在返回之前从inputStream中准确读取那么多字节,所以我想使用nsiScriptableInputStream是强制性的吗?我是否只是从可编写脚本的输入流中读取,然后忽略并丢弃它,而异步副本在后台继续?asyncCopy 是用自己的侦听器替换我的覆盖侦听器,这很好,还是它们堆叠,这会很糟糕?

理想情况下,我想要一个接受输出流的东西,并返回一个流侦听器以传递给 nsiTraceableChannel.setInputStream,但我找不到类似的东西,甚至找不到实现 nsiStreamListener 的列表。

所以,像这样:

var {classes: Cc, interfaces: Ci, results: Cr, Constructor: CC, utils: Cu } = Components;
var hack = 3;
/* 
   0 = use nsiScriptableInputStream
   1 = use NetUtil.asyncCopy, and then use nsiScriptableInputStream but ignore
   2 = use NetUtil.asyncCopy, and it overrides our own override listener from then on
   3 = use NetUtil.asyncCopy, but our own override listener keeps getting onDataAvailable, but we just ignore it
*/
var ScriptableInputStream;
if(hack == 0 || hack == 1)  {
    ScriptableInputStream =  CC("@mozilla.org/scriptableinputstream;1","nsIScriptableInputStream", "init");
}
var StorageStream;
var NetUtil;
if(hack != 0) {
    StorageStream = Cc["@mozilla.org/storagestream;1"];
    Cu.import("resource://gre/modules/NetUtil.jsm");
}
function HTMLRestyler(tracingChannel) {
    this.originalListener = tracingChannel.setNewListener(this);
    if(hack == 0) {
        this.data = "";
    } else {
        /* I wonder if creating one of these is as expensive as creating a
           nsiScriptableInputStream for every read operation? */
        this.storage = StorageStream.createInstance(Ci.nsIStorageStream);
        this.storage.init(256,256,null);
        this.data = this.storage.getOutputStream(0);
    }
}
HTMLRestyler.prototype = {
    QueryInterface: function(id)
    {
            if (id.equals(Components.interfaces.nsIStreamListener) ||
                id.equals(Components.interfaces.nsISupportsWeakReference) ||
                id.equals(Components.interfaces.nsISupports))
                return this;
        throw Components.results.NS_NOINTERFACE;
    }
    onDataAvailable: function(request, context, inputStream, offset, count)
    {
        if(hack == 0) {
            var scriptStream = new ScriptableInputStream(inputStream);
            this.data += scriptStream.read(count);
            scriptStream.close();
            /* the easy way (ow my CPU cycles) */
        } else if(hack == 1) {
            if(!this.iscopying) {
                NetUtils.asyncCopy(inputStream,this.data,this.finished);
                this.iscopying = true;
            }
            /* still have to read the data twice once in asyncCopy, once here
               is there any point to doing this? */
            var scriptStream = new ScriptableInputStream(inputStream);
            var ignored = scriptStream.read(count);
            scriptStream.close();
        } else if(hack == 2) {
            NetUtils.asyncCopy(inputStream,this.data,this.finished);
            /* the "best" way
               (probably doesn't work)
               onDataAvailable and onStopRequest no longer called from here on, 
               as this listener has been overridden */
        } else if(hack == 3) {
            if(!this.iscopying) {
                NetUtils.asyncCopy(inputStream,this.data,this.finished);
                this.iscopying = true;
            }
            /* but no scriptable input stream needed because it's ok to just ignore 
               the inputStream here in the override listener and not read data*/
        }
    }
    onStartRequest: function(request, context) {
        this.request = request;
        this.context = context;
    },
    onStopRequest: function(request, context, statusCode) {
        if(hack != 2) {
            this.finished(statusCode);
        }
    },
    finished: function(status) {
        this.originalListener.onStartRequest(this.request,this.context);
        if(hack != 0) {
            var scriptStream = new ScriptableInputStream(this.storage.newInputStream(1));
            this.data = scriptStream.read(1000);
            this.storage.close();
        }
        this.originalListener.onDataAvailable(this.transform(this.data));
        this.originalListener.onStopRequest(this.request, this.context, status);
    },
    transform: function(data) {
        return "derp "+ data;
    }
}

正如您自己已经指出的那样,合同onDataAvailable必须消耗所有数据。因此,异步 API 是不够的。

这将留下同步 API。

  1. nsIStorageStreamnsIPipe存储数据直到完成,然后获取js-string。
  2. nsIScriptableInputStream并连接成 js 字符串
  3. nsIBinaryInputStream并连接到 js 字符串、八位字节或 ArrayBuffer。

我尝试了很多方法来有效地使用数据,onDataAvailable DownThemAll!在我的用例中,最好在nsIPipe的输出流端使用.writeFrom,这不需要先将数据从C++拉入 JS 土地。

但是,您的情况可能会有所不同:您需要实际修改/转换数据,因此无论如何您都需要一个 js 字符串来进行实际转换。将数据存储在一些XPCOM流(如nsIStorageStreamnsIPipe)中,最终仍然会让你将整个事情读入js-stream,修改它,然后将其放回另一个流中,你可以传递给链下的下一个onDataAvailable监听器。这意味着,您有额外的内存开销(存储流和js字符串,而不仅仅是js字符串),而实际上只节省了非常非常少的XPCOM开销。

数组缓冲区也是如此。

所以最后,考虑到你的用例,我会主张将接收到的数据直接连接成一个js字符串。但是,您应该测量时间和内存,然后决定自己进行各种选项。

当然,更有可能产生更大影响的是编写一个有状态的解析器/转换器,它不需要先缓存整个响应,而是随心所欲地进行转换。