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

时间:2015-01-25 09:50:20

标签: javascript firefox firefox-addon mozilla

在请求某个网址时,我想保存传入的降价信息,将其转换为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;
    }
}

1 个答案:

答案 0 :(得分:2)

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

这会留下同步API。

  1. nsIStorageStreamnsIPipe存储数据直到完成,然后获取js-string。
  2. nsIScriptableInputStream并连接成js-string
  3. nsIBinaryInputStream并连接成一个js-string,八位字节或一个ArrayBuffer。
  4. 我尝试了很多方法来有效地使用onDataAvailable中的DownThemAll!来使用数据。在我的用例中,最好在.writeFrom的输出流端使用nsIPipe,这不需要先将数据从C ++中提取到JS域。

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

    数组缓冲区也一样。

    所以最后,考虑到你的用例,我主张将接收的数据直接连接成一个js-string。但是,你应该自己测量各种选项的时间和记忆,然后决定。

    更有可能产生更大的影响,特别是对于内存使用,当然,编写一个有状态的解析器/转换器,不需要先缓存整个响应,而是随时转换。