Heatmap.js in a Firefox extension Content Script



  • Ok, so I'm trying to create a Firefox extension for a client that draws a heatmap on the page. I have the script that fetches the data all ready and working, and installed in the extension as a content script, and it's all working fine. Woo.

    Now I have a problem: I'm using heatmap.js to draw the heatmap on the page in a canvas. This works as expected if I install the .js in a testing HTML file, but it fails when included as a content script-- but I'm stumped as to why!

    The relevant code is:

    resize: function () {
    var me = this,
    element = me.get("element"),
    canvas = me.get("canvas"),
    acanvas = me.get("acanvas");
    canvas.width = acanvas.width = me.get("width") || element.style.width.replace(/px/, "") || me.getWidth(element);
    this.set("width", canvas.width);
    canvas.height = acanvas.height = me.get("height") || element.style.height.replace(/px/, "") || me.getHeight(element);
    this.set("height", canvas.height);
    },

    The error is on line 361 in the source: "canvas" is null.

    Canvas is created in the library's init function here:

    init: function(){
    var me = this,
    canvas = document.createElement("canvas"),
    acanvas = document.createElement("canvas"),
    ctx = canvas.getContext("2d"),
    actx = acanvas.getContext("2d"),
    element = me.get("element");

    My guess is that the "document" object doesn't exist in the context of a content script-- but I'm not sure that's correct because I have other code in the solution that uses document.getElementById() and in that case the document object works fine. Needless-to-say, the debugger doesn't work in content scripts, and also needless-to-say there's no documentation I can find telling what is and is not allowed in scripts running in this context.

    Does anybody have any ideas? Ideally ones I can try without editing heatmap.js, which I'd prefer to keep in-sync with the repo. Thanks.



  • Ok I've dug some more into this, and I think the problem has to do with how heatmap.js is handling its data store:

    var _ = {
    // data is a two dimensional array
    // a datapoint gets saved as data[point-x-value][point-y-value]
    // the value at [point-x-value][point-y-value] is the occurrence of the datapoint
    data: [],
    // tight coupling of the heatmap object
    heatmap: hmap
    };
    // the max occurrence - the heatmaps radial gradient alpha transition is based on it
    this.max = 1;

    this.get = function(key){
    return _[key];
    };
    this.set = function(key, value){
    _[key] = value;
    };
    }

    Except nothing here really looks like a problem except possibly the use of underscore as a variable name?



  • @blakeyrat said:

    Needless-to-say, the debugger doesn't work in content scripts, and also needless-to-say there's no documentation I can find telling what is and is not allowed in scripts running in this context.

    For the documentation of what is and is not allowed regarding DOM objects in content scripts you can check Accessing the DOM - Add-on SDK Documentation on AMO.

    For debugging content scripts: go to about:config and set the following preferences:

    devtools.chrome.enabled: true
    devtools.debugger.remote-enabled: true

    Then restart the browser and open the Firefox web developer tools (not Firebug, but the built in tools). You should now have a new tool entry named "Browser Debugger". More information as well as these instructions can be found under the Debugging JavaScript article on MDN.



  • Thanks for the tip on the debugger.



  • @blakeyrat said:

    Thanks for the tip on the debugger.

    You're welcome. Some watches, breakpoints and stepping should help you get to the bottom of the problem fairly quickly, I'd wager.

    Do share if you figure this one out. I'm kind of curious what would cause this failure myself. As you say; there doesn't seem to be anything in there that smells like it should fail.



  • Ok wow.

    First of all, of the 47 different debugger windows in Firefox, the one you use for this is named "Browser Debugger".

    Secondly, the debugger seems to simply lock-up at this line:

    heatmap = h337.create(config);

    Step Over and Step Into both don't work from that line, so... the mystery deepens? Oh well, still digging...



  • Oh wait the second time, it locked-up on a different line of code. This debugger seems to be utterly, completely broken. Awesome.



  • Ok, (using alert() debugging), I've gotten closer-- the problem is this line:

    me.set("element", (config.element instanceof Object)?config.element:document.getElementById(config.element));

    I'm passing in the BODY element like so:

    var config = {
    element: document.getElementsByTagName('BODY')[0]

    But when I look in the alert box, it looks like the object is wrapped in something called "XRayWrapper"

    It looks like there is an " XPCNativeWrapper.unwrap" you can use to "unwrap" the object, but reading up on StackOverflow, someone posts that it's there for the Firefox security model and I worry that unwrapping it before passing it into heatmap.js might product security warnings or implications. So I think what I'll try is instead giving the BODY an id and passing in that ID so the library will use its own document.getElementById() to find the object. I'll post back if that solves it.



  • And... that seems to have fixed it. Whee.



  • God fucking DAMNIT I got so far and now stopped dead by a Firefox bug!!! JESASDJKDHAUWYDI

    Firefox is the WORST.

    Anyway, it turns out that if you call .getImageData(); from a plug-in, Firefox pukes itself and returns a generic uncaught exception:

    Timestamp: 8/20/2013 11:47:40 AM
    Error: An exception occurred.
    NS_ERROR_FAILURE: Failure
    Traceback (most recent call last):
    File "resource://gre/modules/commonjs/sdk/timers.js", line 31, in notify
    callback.apply(null, args);
    File "resource://gre/modules/commonjs/sdk/widget.js", line 850, in WC_addEventHandlers/listener/<
    self._widget._onEvent(EVENTS[e.type], null, self.node);
    File "resource://gre/modules/commonjs/sdk/widget.js", line 428, in WidgetView__onEvent
    this._baseWidget._onEvent(type, this._public);
    File "resource://gre/modules/commonjs/sdk/widget.js", line 280, in _onEvent
    this._emit(type, eventData);
    File "resource://gre/modules/commonjs/sdk/deprecated/events.js", line 123, in _emit
    return this._emitOnObject.apply(this, args);
    File "resource://gre/modules/commonjs/sdk/deprecated/events.js", line 153, in _emitOnObject
    listener.apply(targetObj, params);
    File "resource://jid0-wg3h8x3vqzuefebmilcz4jkxvuq-at-jetpack/plugin/lib/main.js", line 26, in exports.main/<.onClick
    data.url("embed.js")
    File "resource://gre/modules/commonjs/sdk/tabs/tab-firefox.js", line 222, in attach
    return Worker(options, this._contentWindow);
    File "resource://gre/modules/commonjs/sdk/tabs/worker.js", line 11, in Worker
    let worker = ContentWorker(options);
    File "resource://gre/modules/commonjs/sdk/deprecated/traits.js", line 114, in Trait
    return self.constructor.apply(self, arguments) || self._public;
    File "resource://gre/modules/commonjs/sdk/content/worker.js", line 491, in Worker
    if ("window" in options) this._attach(options.window);
    File "resource://gre/modules/commonjs/sdk/content/worker.js", line 518, in Worker<._attach
    this._contentWorker = WorkerSandbox(this);
    File "resource://gre/modules/commonjs/sdk/deprecated/traits.js", line 114, in Trait
    return self.constructor.apply(self, arguments) || self._public;
    File "resource://gre/modules/commonjs/sdk/content/worker.js", line 303, in WorkerSandbox
    this._importScripts.apply(this, contentScriptFile);
    File "resource://gre/modules/commonjs/sdk/content/worker.js", line 362, in _importScripts
    load(this._sandbox, String(uri));
    File "resource://gre/modules/commonjs/sdk/loader/sandbox.js", line 47, in load
    return scriptLoader.loadSubScript(uri, sandbox, 'UTF-8');
    File "resource://jid0-wg3h8x3vqzuefebmilcz4jkxvuq-at-jetpack/plugin/data/embed.js", line 12, in null
    initHeatmap();
    File "resource://jid0-wg3h8x3vqzuefebmilcz4jkxvuq-at-jetpack/plugin/data/embed.js", line 42, in initHeatmap
    heatmap.store.addDataPoint(100, 100, 1);
    File "resource://jid0-wg3h8x3vqzuefebmilcz4jkxvuq-at-jetpack/plugin/data/heatmap.js", line 77, in store.prototype.addDataPoint
    heatmap.drawAlpha(x, y, data[x][y], true);
    File "resource://jid0-wg3h8x3vqzuefebmilcz4jkxvuq-at-jetpack/plugin/data/heatmap.js", line 632, in heatmap.prototype.drawAlpha
    me.colorize(xb,yb);
    File "resource://jid0-wg3h8x3vqzuefebmilcz4jkxvuq-at-jetpack/plugin/data/heatmap.js", line 555, in heatmap.prototype.colorize
    image = actx.getImageData(left, top, right-left, bottom-top);

    I don't suppose anybody has a work-around?



  • Days later...

    It turns out Firefox has a "invisible limit" on the size of a canvas, and I was exceeding it. Firefox doesn't bother telling you when you *create* a canvas exceeding the limit, no, that would be too easy. Instead, it just fails with that vague, unnamed exception whenever you try to do any activities with the canvas.

    Fuck me, I lost DAYS to this.



  • @blakeyrat said:

    Days later...

    It turns out Firefox has a "invisible limit" on the size of a canvas, and I was exceeding it. Firefox doesn't bother telling you when you *create* a canvas exceeding the limit, no, that would be too easy. Instead, it just fails with that vague, unnamed exception whenever you try to do any activities with the canvas.

    Fuck me, I lost DAYS to this.

    Wait, how big was this canvas?

    I've successfully used canvases at 8192x8192, and that's several times the size of my monitor. What the hell were you doing?



  • I was having trouble getting the HTML element to cover the entire page (which, BTW is about 10,000 pixels tall-- the screen doesn't matter, the page does), so I just arbitrarily set the canvas to 10000x10000. So yes, I fucked myself by taking a shortcut. Isn't that always the way it is.

    But there's no excuse for Firefox's terrible error-checking here-- if the canvas was too big, why not just TELL ME when I created it, instead of waiting for me to do a drawing action and failing horribly?



  • If the limit is that small, how am I ever going to display a 16-bit, 44.1KHz waveform at 1:1 zoom? It's stifling!



  • Better only show a 4-bit sample, weasel-word "like" in there so you can claim your diagram is technically accurate, then ignore all tweets and comments requesting a correction.

    It's all Firefox's fault, you see.



  • @blakeyrat said:

    ignore
     

    No, I actually got him to correct nearly half of the article based on a super-long email of mine. Couldn't get him to relinquish the Vinyl > CD idea, and if I couldn't do that in one hit, I decided it's not worth the trouble.


Log in to reply