Jetpack, HTML5 pushState and unsafeWindow
I wrote a cross-browser extension to replace the ads in the sidebar of Facebook with content of my own. Facebook uses entirely client-side navigation: new data for the next view is loaded via Ajax, pushState is fired, and the next view is rendered.
Unfortunately, Firefox Jetpack is still lagging behind the support that Chrome extensions have for navigation events. In Chrome, it was simple to set up a listener for URL changes:
var match = ""; // the url to match
var callback = function(tabId, changeInfo, tab) {
if (changeInfo.url.indexOf(match) != -1 || changeInfo.url == match) {
chrome.tabs.executeScript(null, {file:"lib/jquery-1.8.3.min.js"}, function() {
chrome.tabs.executeScript(null, {file:"scripts/bgs.js"}); // the script to execute
});
}
};
chrome.tabs.onUpdated.addListener(callback);
Jetpack was a little more involved. The closest listener available is called pageshow
, but it’s a far cry from Chrome’s onUpdate. Ultimately, the best choice ended up being to extend the window’s history object in JavaScript to add a listener for pushState events.
var match = ""; // the url to match
var pushState = history.pushState;
unsafeWindow.history.pushState = function(state) {
if (typeof history.onpushstate == "function") {
if (state.indexOf(match) != -1 || state == match) {
history.onpushstate({state: state});
}
}
return pushState.apply(history, arguments);
}
unsafeWindow.history.onpushstate = function(state) {callback();};
Similar to the onpopstate
event, this method implements onpushstate
by monkey patching the native history object. While using the unsafeWindow
object is frowned upon (and rightly so), I’m limiting my exposure by restricting the plugin to facebook.com, and chances are, they won’t change their codebase to exploit my extension.
I did find that I needed to add a small delay with setTimeout before I loaded my content, after pushState event was handled. This allowed the DOM time to process the changes for the new view.