1

I'm porting a Chrome extension to a Firefox extension and due to the nature of the website that it runs on, I need to monitor the pushState.

Chrome Extensions has a handy way to handle this: chrome.webNavigation.onHistoryStateUpdated. The way that I use it in the Chrome extension is as follows:

chrome.webNavigation.onHistoryStateUpdated.addListener(function(details) {
  var tabUrl = details.url;
  if (isTabUrlValid(tabUrl)) {
    $.get(tabUrl, function(data) {
    var videoUrl = $(data).find('meta[itemprop=contentURL]').prop('content');
    videoUrl = validateUrl(videoUrl);
    videoUrl5k = make5kUrl(videoUrl);
  });
 }
});

I need to do the same thing for the Firefox Extension, but I haven't found any good answers. I've tried doing the answer mentioned here: How to get notified about changes of the history via history.pushState?

(function(history) {
  var pushState = history.pushState;
  history.pushState = function(state) {
    if (typeof history.onpushstate == "function") {
        history.onpushstate({state: state});
    }
    var tabUrl = tabs.activeTab.url;
    console.log("UPDATED TAB URL: " + tabUrl);
    if (isTabUrlValid(tabUrl)) {
      $.get(tabUrl, function(data) {
        var videoUrl = $(data).find('meta[itemprop=contentURL]').prop('content');
        videoUrl = validateUrl(videoUrl);
        videoUrl5k = make5kUrl(videoUrl);
      });
    }
    return pushState.apply(history, arguments);
  };
})(window.history);

The problem is that when I do cfx run it complains that history/window is undefined and therefore never gets detected. I think this is due to it being within the SDK, but I don't know of a good workaround.

Any thoughts?

Edit: I looked at @willma's answer below and I don't think that would work for me. The issue is that the URL is updated via pushState and the DOM is not... Is there any good way replicate what I do in the chrome extension?

Edit: Here's the pageMod portion

pageMod.PageMod({
  attachTo: 'top', // Don't attach to iFrames --> http://goo.gl/b6b1Iv
  include: [URLs],
  contentScriptFile: [data.url("jquery-2.1.1.min.js"),
                      data.url("csScript.js")],
  onAttach: function(worker) {
    worker.port.on('url', function(url) {
      var videoUrl = validateUrl(url);
      videoUrl5k = make5kUrl(videoUrl);
      console.log("--5K URL--: " + videoUrl5k);
    });
  }
});
Community
  • 1
  • 1
joshft91
  • 1,755
  • 7
  • 38
  • 53

1 Answers1

1

That history code needs to get injected into a tab using a content script. Right now your logic says when the history event occurs, check to see if the tab URL is valid.

In Firefox, the logic will be the other way around: when a tab is opened, check if its URL is valid, and if so, then attach a script to it that will monitor for the history event. To do so you'll need to use a Page Mod.


Edit: All the code

One key concept you're missing is the difference between a content script and a main/library script. The library scripts are stored in lib and have access to all the SDK modules, but don't have access to the DOM, window object… The content scripts are stored in data, are injected into a page using the PageMod or tabs modules, can access the dom and window objects, but have no access to any SDK modules. Content scripts are essentially like the page scripts you'd attach your standard HTML page (with <script></script>) with the caveats that they can't share variables other page scripts but they can communicate with the main scripts.

The only reason I bring this up is because your initial problem was trying to access the window object from a main script and the problem in your fiddle is that you're trying to access the tabs module inside a content script. It's worth reading the topmost link in this answer if this is still confusing.

main.js

const { PageMod } = require('sdk/page-mod');

var sendXHR = function(url) {
  // Do something with the new URL
  // See Request Module docs (below) for sending XHRs from main script.
}

const pageMod = PageMod({
  attachTo: 'top',
  include: '*',
  onAttach: function(worker) {
    worker.port.on('newURL', sendXHR);
  }
});

content.js

var sendNewUrlToMain = function() {
  self.port.emit('newURL', location.href);
}

var pushState = window.history.pushState;
window.history.pushState = function(state) {
    if (typeof history.onpushstate == "function") {
        history.onpushstate({state: state});
    }
    sendNewUrlToMain();
    return pushState.apply(history, arguments);
}

window.addEventListener('hashchange', sendNewUrlToMain); 

Here are the request module docs, for making XHRs.

NB: if you don't want to use the request module (the only reason being that you already have standard XHR code for your chrome extension and don't want to take the time to learn/rewrite that code), you can send a standard XHR from the content script, but in doing so, you risk allowing the user to close the tab and thus destroy the script before your XHR callbacks are executed.

willlma
  • 7,353
  • 2
  • 30
  • 45
  • Thanks for the response. I am using the Page Mod API to inject a content script if certain URLs are hit. However, after I load one page with a valid URL and the content script is run, there is a list of 'related videos' that you can click. After clicking one, the URL is updated but the content script is never hit because the DOM isn't updated which is due to the page being loaded with the `pushState` – joshft91 Sep 29 '14 at 13:14
  • 1
    You need to put the history listener *inside* the content script, not in main.js. So the logic will be like this: use `pageMod` to check if original URL is valid. If so, inject your content script that has some main function **and** a history event listener. When the event listener is triggered, check if the new URL is valid, and if so, call the main function again. – willlma Sep 30 '14 at 20:49
  • Hmm, interesting way of going about it. Thanks - I'll give that a shot when I have time; currently migrating my code base to a new laptop so it will take me a bit to test this, but I appreciate the help. – joshft91 Sep 30 '14 at 20:59
  • Alright, I made the changes but the history event is never touched it seems. Here's what the content script contains: http://jsfiddle.net/j3y5qqst/3/. If a history state is updated (supposedly), then I'd like to get the current tab URL, send an XHR, get the new URL and then send it back to `main.js` – joshft91 Oct 02 '14 at 14:26
  • See my updated answer. You will only be able to access the *new* URL from the `pushstate` event, so you'll need to store the previous URL somewhere if you'd like to have access to both of them at the same time. – willlma Oct 03 '14 at 18:30
  • I'm having problems even to make this work. It seems that firefox doesn't allow to set custom functions for `window.{whatever}` Any ideas? – Gleb Sevruk Jun 10 '15 at 17:52
  • What's the error you're getting? Are you sure you're running that code in a content script? – willlma Jun 10 '15 at 17:54
  • There are no errors, firefox still uses original pushState. This approach http://stackoverflow.com/a/4646900/2377370 seems to work. I'm not sure yet whether it is possible to call addon methods from script injected in body – Gleb Sevruk Jun 10 '15 at 19:36
  • If you use that technique, you won't be able to use the `self` object or anything defined in your content scripts (so no content-script/add-on methods). Maybe try using [`unsafeWindow`](https://developer.mozilla.org/en-US/Add-ons/SDK/Guides/Content_Scripts/Interacting_with_page_scripts#Access_objects_defined_by_page_scripts). – willlma Jun 10 '15 at 20:06