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.