9

I was trying to do a user.js to /messages page in facebook, but looks like greasemonkey doens't notice when the navigation changes from / to /messages. It also occurs in other internal pages. First i thought that it was caused by AJAX navigation, but the URL changes (not hash part), so it's normal navigation, right?

This is a test page that I used:

// ==UserScript==
// @name           Test
// @namespace      none
// @description    just an alert when page changes
// @include        http*://www.facebook.com/*
// ==/UserScript==

alert(location.href);

How can I correctly detect page changes?


Firefox version: 6.0.2

Greasemonkey version: 0.9.11

Ravan Scafi
  • 6,382
  • 2
  • 24
  • 32
  • This other question may be relevant. http://stackoverflow.com/questions/3522090/event-when-window-location-href-changes – bronsoja Sep 11 '11 at 21:36

3 Answers3

11

For browsers that support it, including Firefox 4+, Facebook takes advantage of the HTML5 History API. This API allows the location to be changed using the history.pushState() method although no navigation actually occurs. Though the page may seem to have changed, all that's happened is a behind-the-scenes ajax call that changes most of the content.

If you wanted to capture this change, you'd have to proxy the pushState() method with your own function:

(function (old) {
    window.history.pushState = function () {
        old.apply(window.history, arguments);
        alert(window.location.href);
    }
})(window.history.pushState); 

Read more about the History API at https://developer.mozilla.org/en/DOM/Manipulating_the_browser_history.

Andy E
  • 338,112
  • 86
  • 474
  • 445
  • I don't think you have to use a proxy -- couldn't you just handle the `window.onpopstate` event? – Coderer Jan 08 '14 at 09:26
  • `onpopstate` doesn't detect when pushState is called for new pages, just when you use the forward or back buttons (or forward and back API calls). – rampion Jan 08 '14 at 13:57
  • the issue with this approach is that the new page elements have not necessarily been loaded after `old.apply(window.history, arguments)` returns, so it may not be ready for your script to process. – rampion Jan 08 '14 at 14:09
  • @andy-e for some reason this doesn't work on user scripts for me - the function is not mutate. Is it possible that Twitter is preserving a copy of the original object in a closure? I am using this workaround right now but it is suboptimal as it runs too many times https://github.com/giuseppeg/refined-twitter-lite/blob/ab6e66cb8972f4f824e6048a840e2648855292e3/index.user.js#L27-L35 – G.G. Jan 27 '19 at 13:34
4

Another approach is to hook DOMNodeInserted for the page, and to run when the path matches /messages after insertion:

// ==UserScript==
// ...
// @include https://www.facebook.com/*
// ...
// ==/UserScript==

var url = document.location.toString();
function scriptBody(){
   if (!url.match(/facebook.com\/messages/)) return;
   // ...
   // do stuff
   // ...
});

scriptBody(); // run on initial page load

document.querySelector('html').addEventListener('DOMNodeInserted', function(ev){
  var new_url = document.location.toString();
  if (url == new_url) return; // already checked or processed
  url = new_url;

  scriptBody(); // run when URL changes
});

Note that if you users use the forward/back buttons you may get 'DOMNodeInserted' events for content that is being reinserted to the page that you've already modified with your script, so you'll need to make sure you check whether whatever changes you normally make to the page have already been made, to prevent inserting duplicate controls or whatever.

rampion
  • 87,131
  • 49
  • 199
  • 315
0

+1 to @rampion suggestions. I wanted to perform a simple redirect though and looking for elements on page was not very useful as I don't want to redirect user unattendedly.

Anyway, I used this code to install a listener that would redirect where needed upon user clicking on a link with particular href:

if (document.addEventListener ){
  document.addEventListener("click", function(event) {
    var targetElement = event.target || event.srcElement;
    // TODO: support deeper search for parent element with a href attribute
    var href = targetElement.getAttribute('href') || targetElement.parentElement.getAttribute('href') ;
    if (href && videoURLRe.test(href)) {
      var target = "";
      if (href.indexOf("/") == 0) {
        target = "https://m.facebook.com" + href
      } else {
        target = href.replace("www.facebook", "m.facebook");
      }
      window.location.assign(target);
    }   
  }, true);
}

Works pretty neat. I had to figure correct third addEventListener param so that my listener is executed before any others.

For full script look at https://greasyfork.org/en/scripts/8176-switch-to-mobile-version-on-facebook-video-page

I couldn't find any way to reliably detect URL changes regardless of method used by the web site to change that URL. @andy-e approach seems awesome but didn't work for me for some reason. Perhaps I couldn't make script @grant tag properly.

akostadinov
  • 17,364
  • 6
  • 77
  • 85