In my script for Greasemonkey/Tampermonkey is working perfectly in Google Chrome but in Firefox this:
waitForKeyElements ("table#ID-rowTable tr td span._GAmD", replaceAffid);
is not waiting for AJAX loaded content.
Here is the relevant part of my script:
// ==UserScript==
// #name ChangeProvider
// #description Change Provider Name
// #include https://analytics.google.com/analytics/web/*
// #version 1
// #grant GM_xmlhttpRequest
// ==/UserScript==
<snip>...
waitForKeyElements ("table#ID-rowTable tr td span._GAmD", replaceAffid);
<snip>...
function waitForKeyElements (
selectorTxt, /* Required: The jQuery selector string that
specifies the desired element(s).
*/
actionFunction, /* Required: The code to run when elements are
found. It is passed a jNode to the matched
element.
*/
bWaitOnce, /* Optional: If false, will continue to scan for
new elements even after the first match is
found.
*/
iframeSelector /* Optional: If set, identifies the iframe to
search.
*/
) {
var targetNodes, btargetsFound;
if (typeof iframeSelector == "undefined")
targetNodes = $(selectorTxt);
else
targetNodes = $(iframeSelector).contents ()
.find (selectorTxt);
<snip>...
The full code is at pastebin.com.
The problem(s) is/are:
waitForKeyElements() requires jQuery.
Your script must either provide jQuery (recommended), or use #grant none mode and be running on a page that already uses jQuery (a brittle way to do things, AKA "time-bomb code").
Tampermonkey has a bug and possible security weakness whereby it doesn't always
sandbox properly. This means that the script can (sometimes) see the page's jQuery, even when #grant is not none. This allowed the script to run in Chrome (for now) but is a very dicey thing to depend on.
Firefox properly sandboxes the scope when you use #grant GM_ ... so the script cannot see the page's jQuery.
If you had looked at Firefox's Browser Console, you would have seen error messages pointing you to the problem.
The solution is: Don't use waitForKeyElements without #requireing jQuery!
In fact, you should require both libraries, as shown in this answer, as it (A) Runs faster, (B) Only fetches the code once, when you install/edit the userscript, and (C) makes for cleaner, easier to grok code.
So, your entire script would become something like:
// ==UserScript==
// #name GoogleAnalytics Change Provider
// #description Change Provider Name
// #include https://analytics.google.com/analytics/web/*
// #version 1
// #require http://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js
// #require https://gist.github.com/raw/2625891/waitForKeyElements.js
// #grant GM_xmlhttpRequest
// ==/UserScript==
var providers = new Array ();
GM_xmlhttpRequest ( {
method: "GET",
url: "http://localhost:3000/api/t2_provider_list",
onload: function (response) {
var provider_list = JSON.parse (response.responseText);
for (i = 0; i < provider_list.length; i++) {
providers[provider_list[i].analytics_prefix] = provider_list[i].provider_name;
}
waitForKeyElements ("table#ID-rowTable tr td span._GAmD", replaceAffid);
}
} );
/*--- replaceAffid (): Match the fields with a given pattern
and replace with the provider name and affid
*/
function replaceAffid () {
console.log (providers);
var regExp = /([a-z,A-Z,0-9]+)---([a-z,A-Z,0-9,_,-]+)/g;
var spans = document.evaluate ("//span[contains(#class, '_GAmD') and not(contains(#class, '_GAe'))]/text()", document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
console.log (spans);
for (var i = 0; i < spans.snapshotLength; i++) {
match = regExp.exec (spans.snapshotItem (i).textContent);
if (match != null) {
if (typeof providers[match[1]] === undefined) {
// do nothing
} else {
spans.snapshotItem (i).textContent = "Provider: " + providers[match[1]] + " \n\r Affid: " + match[2];
}
}
}
}
Finally, it looks like you pasted in an old version of waitForKeyElements.
Since May of 2012, that function has had this text near the top:
IMPORTANT: This function requires your script to have loaded jQuery.
If you grabbed your copy of the function from one of my old answers, I apologize. I just updated it to avoid a repeat of this confusion.
Related
I am unable to retrieve a username from a dynamic webpage using the wait function. However when I execute $("#label301354000").text(); in Chrome Developer tool, I get my username. Any idea how to tweak the function to capture the id value?
// ==UserScript==
// #name UI
// #namespace http://tampermonkey.net/
// #version 0.1
// #description Test
// #author Dejan
// #match http://url/*
// #require https://ajax.googleapis.com/ajax/libs/jquery/2.2.0
// #require https://gist.githubusercontent.com/raw/2625891/waitForKeyElements.js
// #grant GM_addStyle
// ==/UserScript==
/* jshint -W097 */
'use strict';
waitForKeyElements("#label301354000", getUser);
function getUser(jNode) {
console.log("User is "+ jNode.text() );
}
The question omits critical details, see the comments above.
But in general, if $("#label301354000").text(); always works and
function getUser(jNode) {
console.log("User is "+ jNode.text() );
}
Comes up empty in the Tampermonkey script, it suggests that the target node is not the best choice or that the node is created at some point and filled in by the page later.
waitForKeyElements checks the callback return for cases like that. Change getUser() to:
function getUser(jNode) {
var usrText = jNode.text ().trim ();
if (usrText) {
console.log ("User is " + usrText);
}
else
return true; //-- Keep waiting.
}
If that doesn't work, provide a proper MCVE and/or link to the target page. (Per SO guidelines, you should always provide one or both.)
The website is: lexin.nada.kth.se/lexin/#searchinfo=both,swe_gre,hej;
My script is:
function main(){
var links=document.getElementsByTagName("a");
alert("There are " + links.length + "links.");
}
main();
Running the script gives me two alert messages saying
There are 0 links.
Any ideas why I can't get the right amount of links from the document? And why do I get the alert twice?
The alert fires more than once because that page contains iFrames (with, de facto, the same URL as the main page). Greasemonkey treats iFrames as if they were standalone web pages. Use #noframes to stop that.
The script is not finding the links because they are added, by javascript, long after the page loads and the GM script fires. This is a common problem with scripts and AJAX. A simple, robust solution is use waitForKeyElements() (and jQuery).
Here is a complete sample script that avoids the iFrames and shows how to get dynamic links:
// ==UserScript==
// #name _Find elements added by AJAX
// #include http://YOUR_SERVER.COM/YOUR_PATH/*
// #match http://stackoverflow.com/questions/*
// #require http://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js
// #require https://gist.github.com/raw/2625891/waitForKeyElements.js
// #noframes
// #grant GM_addStyle
// ==/UserScript==
/*- The #grant directive is needed to work around a design change
introduced in GM 1.0. It restores the sandbox.
*/
var totalUsrLinks = 0;
waitForKeyElements ("a[href*='/users/']", listLinks);
function listLinks (jNode) {
var usrMtch = jNode.attr ("href").match (/^.*\/users\/(\d+)\/.*$/);
if (usrMtch && usrMtch.length > 1) {
totalUsrLinks++;
var usrId = usrMtch[1];
console.log ("Found link for user: ", usrId, "Total links = ", totalUsrLinks);
}
}
It's returning an HTMLcollection because of .getElementsByTagName and because of that, you will have to state the HTMLcollection with .getElementsByTagName and then find the length, and alert it. It will look like this...
(function main(){
var links = document.getElementsByTagName("a").length
alert("There are "+ links + " links.");
})()
I added an IIFE or an Immediately-Invoked-Function-Expression more on IIFEs here, so you don't have to call the function, and so the code is small and able to be "swallowed". lastly, it's alerting 2 alert boxes, because there's one[alert box] in the function and you're calling that function so it's going to do the same thing.
I frequent a forum that has a horrible way of ignoring users. If you place someone on ignore, it almost makes that users presence more prevalent.
So I wrote this to hide them completely:
// ==UserScript==
// #name Freddie
// #namespace http://tampermonkey.net/
// #version 0.1
// #description hide annoying forum users
// #author You
// #match http://www.scout.com/college/kansas/forums/*
// #grant none
// ==/UserScript==
/* jshint -W097 */
'use strict';
function checkForDiv() { // crappy workaround function to wait for AJAX content
if (!document.getElementById("wrapper")) {
setTimeout(checkForDiv, 300);
} else {
checkNames();
}
}
function checkNames() {
var mybannedList = ["Pyros", "GOHawksGators", "th30r3o"]; // add usernames here
var nms = document.body.querySelectorAll('a.authName'), i = 0, len = nms.length;
for (i; i < len; i++) {
if (mybannedList.indexOf(nms[i].innerHTML) != -1) {
nms[i].parentNode.parentNode.style.display = "none";
}
}
}
checkForDiv();
But when you go to the page with the ignored users they still appear, upon refreshing, the script runs and they disappear.
Please good sirs, what do I do?
The site uses AJAX for navigation so the page address changes without reloading, that's why Tampermonkey doesn't inject your script when you navigate from another page on that site.
The simplest solution would be to include the entire site: // #match http://www.scout.com/*
There are other more advanced methods of detecting page transitions based on MutationObserver or some DOM event or property change that occurs on navigation.
Also beware of #grant none with jQuery loaded via #require: it breaks sites that also load jQuery unless you use jQuery.noConflict. The simplest solution is to remove that line as you don't need to access the web page variables.
P.S. There's a known timer-based wrapper: waitForKeyElements.
I am currently using the following script in Tampermonkey in Google Chrome:
// ==UserScript==
// #name Youtube opt in Ads per channel
// #namespace schippi
// #include http://www.youtube.com/watch*
// #version 1
// ==/UserScript==
var u = window.location.href;
if (u.search("user=") == -1) {
var cont = document.getElementById("watch7-user-header").innerHTML;
var user=cont.replace(/.+\/user\//i,'').replace(/\?(?:.|\s)*/m,'');
window.location.href = u+"&user="+user;
}
It seems to work perfectly in Firefox with Greasemonkey but in Google Chrome, it seems that it only works on the first click on a YouTube video.
More specifically, if I click on a YouTube video:
youtube.com/watch?v=MijmeoH9LT4,
it redirects me to:
youtube.com/watch?v=MijmeoH9LT4&user=Computerphile
However, if I click on a video from the related videos vertical bar, it doesn't seem to do any further redirection.
Alas, there is still no really "neat" way to do this in Chrome. (Firefox has more options.)
Your best bet is just to poll location.search; see below.
Your other options in Chrome, currently, are not recommended -- but here they are for reference:
Hack into the history.pushState function. This gives faster notice of page changes, BUT fires before you can run your code, so it will still need a timer. Plus, it brings in cross-scope problems, in a userscript environment.
Use Mutation Observers to monitor changes to the <title> tag. this may work okay, but may fire after you want it to, causing delays and pronounced "flicker". Also may not work on poorly designed pages (YouTube is okay).
Also note that the replace() statements, from the question, will blow up the URL and 404 the script in several cases. Use DOM methods to get the user (see below).
Polling code (Simple, robust, cross-browser):
// ==UserScript==
// #name Youtube opt in Ads per channel
// #namespace schippi
// #include http://www.youtube.com/watch*
// #version 1
// #grant GM_addStyle
// ==/UserScript==
/*- The #grant directive is needed to work around a design change
introduced in GM 1.0. It restores the sandbox.
*/
var elemCheckTimer = null;
var pageURLCheckTimer = setInterval (
function () {
if (this.lastQueryStr !== location.search) {
this.lastQueryStr = location.search;
gmMain ();
}
}
, 111 //-- Nine times a second. Plenty fast w/o bogging page
);
function gmMain () {
if ( ! /user=/.test (window.location.href) ) {
elemCheckTimer = setInterval (checkUserAndRelocate, 24);
}
}
function checkUserAndRelocate () {
var elem = document.querySelector (
"#watch7-user-header a[href*='/user/']"
);
if (elem) {
clearInterval (elemCheckTimer);
var user = elem.href.match (/\/user\/(\w+)\W?/);
if (user && user.length > 1) {
location.replace (location.href + "&user=" + user[1]);
}
}
}
I am making a javascript greasemonkey script that redirects you when it finds a certain string in a webpage. Here is my code:
// ==UserScript==<br>
// #name No 009 Sound System!<br>
// #version 1.0<br>
// #namespace http://www.cakeman.biz/<br>
// #description Warn user when about to view a youtube video with 009 Sound System audio<br>
// #include http://*/*<br>
// #copyright 2011+, Zack Marotta
// ==/UserScript==
var result, search_term = 'somerandomsearchterm';
var str = document.URL;
var url_check = str.search(search_term);
if (url_check !== -1) {
var result = search_term;
alert(str);
window.location = "http://www.web.site";
}
Nothing happens when the script runs. What did I do wrong?
For some specific functions, you have to use unsafeWindow instead of window in a Greasemonkey UserScript. If you want to get your script work at GreaseMonkey and other locations, use this:
var win = typeof unsafeWindow != "undefined" ? unsafeWindow : window;
I've shrinkened by original answer, because that seems not to be your issue. Most likely, you've forgot to add // #include to your meta block.
If your script has to redirect a certain, fixed page using GreaseMonkey, you can also shorten your scrip to:
// ==UserScript==
// #name Redirect
// #include *i-am-afraid-of-this-word*
// ==/UserScript==
location.href = "http://www.google.com/search?q=need-help-getting-courage";
For more information about the meta block, see: http://wiki.greasespot.net/Metadata_block
You notice that you're performing a Case-Sensitive search? it would not trigger if just one letter is capital when looking for a lower-case, or vice versa, if you want a case-Insensitive you need a RegExp as your "search-term".
Other thing is, you don't need <br> in the metadata block, because then the include is the one not working, otherwise the page would need to have <br> in the address to match and execute.
Extra: The !== should work just fine, but it's not needed, in this case a != would do, and it is strongly recommended that you don't use unsafeWindow.