Why greasemonkey doesn't detect some page changes in facebook? - javascript

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

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.

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.

+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.

Related

How can I detect back button in the browser?

I have a function named back() which will be used for ajax calls. Actually I have an array stack contains last 5 search results and that back function will switch to the previous result set (according to that array stack) and it even changes the URL using window.history.pushState() when you click on the back button.
That back button I was talking about, is an element inside the browser which revokes back() function. Now I want to revoke back() function also when user click on the back button of the browser. Something like this:
window.onhashchange = function() {
back(); // this function also changes the url
}
But sadly window.onhashchange will be revokes twice when I click on the back of the browser. Because window.onhashchange will be revoked when you change the URL using window.history.pushState().
Anyway, how can I detect what things changes the URL? Either my JS code or the back button of the browser?
You can use performance.navigation.type
At any given point, for example on document.onload, you can read the value of type and, if it's:
0 The page was accessed by following a link, a bookmark, a form submission, a script, or typing the URL in the address bar.
1 The page was accessed by clicking the Reload button or via the Location.reload() method.
2 The page was accessed by navigating into the history.
255 any other way.
Just beware that support is limited according to the compatibilty table.
However, from the looks of it, it seems the table is outdated. It says it is not supported on chrome and I just tested it and works as expected on my chrome version (67.0)
One of solution is to implement onunload event with localstorage option.
This is from my head maybe you will need correction but this is base !
var history = [];
window.onload = function(){
var handler;
if ( localStorage.getItem('history') == null ) {
// FIRST TIME
history[0] = window.location.href;
localStorage.setItem("history", JSON.stringify(history));
}
else {
handler = localStorage.getItem('history');
handler = JSON.parse(handler);
history = handler;
// Just compare now
if (history[history.length-1] == window.location.href) {
// no change
} else {
history.push(window.location.href);
}
}
}
window.onunload = function(){
localStorage.setItem('history', JSON.stringify(history));
}
Note :
Since 25 May 2011, the HTML5 specification states that calls to
window.alert(), window.confirm(), and window.prompt() methods may be
ignored during this event. See the HTML5 specification for more
details.

addEventListener and setInterval fail silently in a Greasemonkey script

I am trying to get code to execute every x amount of time and begin doing so after the page has loaded for the first time, while my debug throws no errors, the script doesn't actually do anything.
So, I am at a bit of a loss and I am thinking it has something to do with the syntax or the way the code is nested. Can you be so kind as to look at my code below and tell me what in the heck I am doing wrong and if there is a better way to fix it?
window.addEventListener('load', function() {
setInterval(function setTitle(){
var sInfo = document.getElementById("play_info");
var iTags = sInfo.innerHTML.split("\">");
var pTags = iTags[3].split("<");
document.title = pTags[0];
return setTitle;
},5000);
});
This is a code inside a Greasemonkey script that will run inside Firefox. Thank you.
Update (Now that target page was given):
The question code did not match the structure of the actual page. It typically would throw TypeError: iTags[3] is undefined errors.
Using DOM methods, to get the desired info, does the job. A working script is:
// ==UserScript==
// #name _KROQ, song info to page title
// #include http://betaplayer.radio.com/player/*
// #grant GM_addStyle
// ==/UserScript==
/*- The #grant directive is needed to work around a design change
introduced in GM 1.0. It restores the sandbox.
*/
if (window.top != window.self) //-- Don't run on frames or iframes.
return;
window.addEventListener ('load', function () {
setInterval (setTitle, 5000);
}, false);
function setTitle () {
var songInfoNode = document.querySelector (
"#play_info #oflw_shield div.sleeve.hori"
);
if (songInfoNode) {
var songInfoText = songInfoNode.textContent.trim ();
//console.log ("songInfoText", songInfoText);
document.title = songInfoText;
}
}
That code is "brittle", but it will work fine on a site that has the structure you seem to expect. However, the only "output" is that the page title might change. That doesn't happen?
One or more of the following is the script's immediate problem:
The page does not have the structure you are expecting, so the code throws exceptions. You say, "My debug throws no errors". Verify that you checked Firefox's error console, ControlShiftJ, with the display set to "Errors" or to "All".
Something else in the script, that you are not showing us, is the problem. Include or link to the complete Greasemonkey script.
The script is operating on <iframe>d content, so changing document.title will have no visible effect. Link to the target page.
For reference, here is more robust version of that code, that also has a console message, so that you can verify operation:
window.addEventListener ('load', function () {
setInterval (setTitle, 5000);
}, false);
function setTitle () {
console.log ("Running setTitle().");
var sInfo = document.getElementById ("play_info");
if (sInfo) {
var iTags = sInfo.innerHTML.split ("\">");
if (iTags && iTags.length > 3) {
var pTags = iTags[3].split ("<");
if (pTags && pTags.length) {
document.title = pTags[0];
}
}
}
}
Note that it's not good practice to parse HTML that way, but without seeing the actual target page (or at least the structure of the play_info node), we can't provide a specific alternative.
As Brandon Boone stated, if you are looking for a pure JS solution to find cross-browser solutions for addEventListener, I suggest you use a solution like this.
var e = window.addEventListener ? window.addEventListener : window.attachEvent;
e('load',function(){ ... };

Simulate click on GitHub's newsfeed "More" button in JavaScript doesn't work

I'm trying to create a GreaseMonkey script that automatically extends the list of alerts in GitHub's newsfeed but that doesn't work.
Here is my code (inspired from this post):
var moreLink = $("a:contains('More')");
var evt = document.createEvent("MouseEvents");
evt.initEvent("click", true, true);
moreLink[0].dispatchEvent(evt);
But instead of expending the list of alerts like it does when you manually click on it, it just opens the page the link points too (https://github.com/organizations/my_organization?page=2)
How can I do this?
Edit:
Here is the HTML source code of the link, it looks like there is no javascript or onClick event associated to it:
More
Edit 2:
Here is my full greasemonkey script:
// ==UserScript==
// #name test
// #namespace test
// #description test
// #include https://github.com
// #require http://ajax.googleapis.com/ajax/libs/jquery/1.5.1/jquery.min.js
// #version 1
// ==/UserScript==
var moreLink = $("#dashboard div.news div.pagination a:contains('More')");
var evt = document.createEvent("MouseEvents");
evt.initEvent("click", true, true);
moreLink[0].dispatchEvent(evt);
//alert($(".alert").length);
Your code works perfectly for me -- on the main page, when logged in. Though I would recommend that you use a more specific selector, to avoid false positives.
EG, use:
var moreLink = $("#dashboard div.news div.pagination a:contains('More')");
What version of Firefox and Greasemonkey are you using?
Is NoScript, Adblock, or similar running?
You say that it looks like there is no javascript or onClick event associated with the link... How did you check? Firebug shows several event-listeners on that node.
What errors appear in Firefox's error console (CtlShiftJ), besides the zillions of CSS warnings that GitHub generates?
Is your script doing anything else to the page? Especially adding or deleting content or using innerHTML? If so, post or link to the full script.
Update for new information:
On further testing, from more environments, it now appears as if GitHub's pagination is not always initialized by the time the Greasemonkey script fires. To get around that, use the waitForKeyElements() utility and check for pagination-engine readiness before attempting to click the link. See below.
Greasemonkey 0.9.19 was buggy as all get-out -- hence it was only active for a few days.
Go to the Greasemonkey Version History page and install either version 0.9.20 or version 0.9.18.
The #include directive may not be firing when you want it. It needs to be at least:
// #include https://github.com/
But
// #include https://github.com/*
might be better, and the selector is specific enough that a broader include should cause no harm.
Step up to jQuery version 1.7.2 -- we've used it extensively with no problems. (1.5.1 is probably not the problem, but best to eliminate that variable.)
Putting all that together, the following script works for me, from a variety of (Windows) environments. I left most of the debugging code in, just in case...
// ==UserScript==
// #name _GitHub "news" item auto-paging
// #namespace _pc
// #include https://github.com/
// #require http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js
// #require https://gist.github.com/raw/2625891/waitForKeyElements.js
// ==/UserScript==
if (typeof unsafeWindow.console.clear != 'undefined') {
unsafeWindow.console.clear ();
}
unsafeWindow.console.log ("Start..."); //-- Does not require Firebug.
waitForKeyElements (
"#dashboard div.news div.pagination a:contains('More')",
clickAjaxMoreLink,
true //-- Stop on first successful click.
);
function clickAjaxMoreLink (jNode) {
this.numRuns = this.numRuns || 1;
unsafeWindow.console.log (
"moreLink:", jNode,
" | Parent classes:", jNode.parent ().attr ("class"),
" | Run:", this.numRuns
);
this.numRuns++;
/*--- COMMENT THIS NEXT CHECK OUT, if waitForKeyElements is set to
continually click via clickAjaxMoreLink. (The last param is false.)
*/
if (this.numRuns > 25) {
unsafeWindow.console.log ("*** Excessive runcount, abort! ***");
return false;
}
if (jNode.parent ().hasClass ("loading") ) {
return true; //-- Cancel the "found" status.
}
var unsfJQ_Body = unsafeWindow.$(document.body);
if ( ! unsfJQ_Body
|| ! unsfJQ_Body.length
|| document.readyState != "complete" //-- Order is important here
|| (typeof unsfJQ_Body.pageUpdate) != "function"
) {
return true;
}
unsafeWindow.console.log ("Num news items, start:", $(".alert").length);
setTimeout ( function () {
unsafeWindow.console.log (
"Num news items, after AJAX delay:", $(".alert").length
);
}, 2333);
var evt = document.createEvent ("MouseEvents");
evt.initEvent ("click", true, true);
jNode[0].dispatchEvent (evt);
return false;
}
unsafeWindow.console.log ("Setup complete...");

History.js and manual refresh of the page

i'm working with History.js and i'm trying to obtain an url as Google Plus urls.
(function(window, undefined){
var History = window.History; // Note: We are using a capital H instead of a lower h
if ( !History.enabled ) {
// History.js is disabled for this browser.
// This is because we can optionally choose to support HTML4 browsers or not.
return false;
}
History.Adapter.bind(window, 'statechange', function(){ // Note: We are using statechange instead of popstate
var State = History.getState(); // Note: We are using History.getState() instead of event.state
History.log(State.data, State.title, State.url);
console.log(State.data.page);
});
$('.item').live('click', function(e){
e.preventDefault();
var url = $(this).children(1).attr('href');
$(document).remove('#content');
$('#loaded').load('/controlpanel' + url + '.php #content');
History.pushState({page: url + '.php'}, "Prova Pagina", History.getRootUrl() + 'controlpanel' + url); // logs {state:1}, "State 1", "?state=1"
});
})(window);
this script works when I click on the link, but when I manually refresh the page, and I've already clicked on a link and the url becomes http://www.mysite.com/something/page, the browser gives me an 404 Error.
How can I solve it?
I would like to obtain something like: https://plus.google.com/explore
Even though JS allows you to not reload the whole page, the URL should still be accessible on the server side, so that someone accessing directly the link will see something (not to mention graceful degradation).
Also, you have to add a click handler on the anchor links, so that you can prevent the default action (going to the link) and use the pushState method to change the URL. Something like this:
document.body.onclick = function( e ) {
var evt = e || window.event,
target = evt.target || evt.srcElement // Get the target cross-browser
// If the element clicked is a link
if ( target.nodeName === 'A' ) {
// Use the History API to change the URL
History.pushState() // Use the library correctly
// Don't forget to return false so that the link doesn't make you reload the page
return false
}
}
This is definitely not production code (you should check for external links, not bind on the body, etc), but you get the idea.

What's an effective way to move data from one open browser tab to another?

I am looking for a quick way to grab some data off of one Web page and throw it into another. I don't have access to the query string in the URL of the second page, so passing the data that way is not an option. Right now, I am using a Greasemonkey user script in tandem with a JS bookmarklet trigger: javascript:doIt();
// ==UserScript==
// #include public_site
// #include internal_site
// ==/UserScript==
if (document.location.host.match(internal_site)) {
var datum1 = GM_getValue("d1");
var datum2 = GM_getValue("d2");
}
unsafeWindow.doIt = function() {
if(document.location.host.match(public_site)) {
var d1 = innerHTML of page element 1;
var d2 = innerHTML of page element 2;
//Next two lines use setTimeout to bypass GM_setValue restriction
window.setTimeout(function() {GM_setValue("d1", d1);}, 0);
window.setTimeout(function() {GM_setValue("d2", d2);}, 0);
}
else if(document.location.host.match(internal_site)) {
document.getElementById("field1").value = datum1;
document.getElementById("field2").value = datum2;
}
}
While I am open to another method, I would prefer to stay with this basic model if possible, as this is just a small fraction of the code in doIt() which is used on several other pages, mostly to automate date-based form fills; people really like their "magic button."
The above code works, but there's an interruption to the workflow: In order for the user to know which page on the public site to grab data from, the internal page has to be opened first. Then, once the GM cookie is set from the public page, the internal page has to be reloaded to get the proper information into the internal page variables. I'm wondering if there's any way to GM_getValue() at bookmarklet-clicktime to prevent the need for a refresh. Thanks!
Can you move the bookmarklet to a button or link -- that Greasemonkey will add to the page(s)?
Then you could set click-event handlers to fire GM_getValue().
It looks like the current method is exploiting a "security hole" -- one that may be closed in the future. You might consider doing everything in a Firefox extension, instead.
Possibly useful link: http://articles.sitepoint.com/article/ten-tips-firefox-extensions/1

Categories