Nikita 웃
Nikita 웃

Reputation: 2060

GM_xmlhttpRequest data is being placed in the wrong places

I am using the following excellent userscript (for Chrome, Chrome's Tampermonkey and Firefox Greasemonkey). It's supposed to display movie ratings next to each IMDb movie link, but it stopped working properly.

This is the complete script:

// ==UserScript==
// @name           Add IMDb rating next to all IMDb links (+voter count)
// @author         Ali
// @description    Adds movie ratings and number of voters to any IMDb link. Modified version of http://userscripts.org/scripts/show/9174
// @include        *
// @version        2013-05-12
// @namespace      http://userscripts.org/scripts/show/96884
// @grant          GM_xmlhttpRequest
// @downloadURL    http://www.alibakir.com/upload/addimdbratings.js
// @updateURL      http://www.alibakir.com/upload/addimdbratings.js
// ==/UserScript==
var IMDBpluginlinks = document.links;
var IMDBcontinueelement=document.createElement("button");
IMDBcontinueelement.innerHTML="Get rating";

function processIMDBLinks(s){
    IMDBcontinueelement.style.display = 'none';
    var r=0;
    for (IMDBi = s; IMDBi < IMDBpluginlinks.length; IMDBi++) {
        if (IMDBpluginlinks[IMDBi].href.indexOf("/title/") != -1 && IMDBpluginlinks[IMDBi].href.indexOf("imdb.") != -1){
            if(r>300){
                IMDBcontinueelement.onclick=function(){ processIMDBLinks(IMDBi); };
                IMDBcontinueelement.style.display='inline';
                IMDBpluginlinks[IMDBi].parentNode.insertBefore(IMDBcontinueelement, IMDBpluginlinks[IMDBi]);
                break;
            }
            r++;
            GM_xmlhttpRequest({
                method: 'get',
                headers: {
                },
                url: IMDBpluginlinks[IMDBi].href,
                onload: function (IMDBi){return function(result) {
                     rating = result.responseText.match(/Users rated this (.*) \(/);
                     votes = result.responseText.match(/\((.*) votes\) -/);
                     IMDBpluginlinks[IMDBi].parentNode.insertBefore(document.createElement("span"), IMDBpluginlinks[IMDBi]).innerHTML = (rating ? "<b> [" + rating[1] + " - "+votes[1]+"] </b>" : "<b style='color: red;'>[NA] </b>&nbsp;");
                }}(IMDBi)
            });
        }
    }
}
processIMDBLinks(0);


And this is how an example page looks at the moment:

Results being added to the wrong links


As you can see, the script displays the results in the wrong places.

Why does it present them in the wrong place and how can it be fixed?

Upvotes: 0

Views: 660

Answers (1)

Brock Adams
Brock Adams

Reputation: 93473

The issue causing your main complaint (results in the wrong places) is that GM_xmlhttpRequest operates asynchronously (which is good) and the onload was improperly constructed.

You either need to wrap the call to GM_xmlhttpRequest in a proper closure or provide a context in the GM_xmlhttpRequest call. (See the code below.)

For more information on why closures are needed, see this answer to the same type of problem.


Other big problems include AJAX-fetching dozens of improper links and firing on every page and iframe. Both of these slow the browser down quite a bit.

Don't use @include *. Even if you don't mind, other users of the script will. Add @include or @match lines for just the sites that you know have IMDB links.

I thought that I might want to use this script myself, so I started cleaning it up. You can read the inline comments and compare to the original script for an idea of some of the lesser problems. (Don't use onclick and always check match returns, etc.)

// ==UserScript==
// @name         add IMDb rating next to all IMDb links (+voter count)
// @description  Adds movie ratings and number of voters to any imdb link. Modified version of http://userscripts.org/scripts/show/96884
// @match        *://www.imdb.com/*
// @grant        GM_xmlhttpRequest
// ==/UserScript==

var maxLinksAtATime     = 50; //-- pages can have 100's of links to fetch. Don't spam server or browser.
var fetchedLinkCnt      = 0;

function processIMDB_Links () {
    //--- Get only links that could be to IMBD movie/TV pages.
    var linksToIMBD_Shows   = document.querySelectorAll ("a[href*='/title/']");

    for (var J = 0, L = linksToIMBD_Shows.length;  J < L;  J++) {
        var currentLink = linksToIMBD_Shows[J];

        /*--- Strict tests for the correct IMDB link to keep from spamming the page
            with erroneous results.
        */
        if (    ! /^(?:www\.)?IMDB\.com$/i.test (currentLink.hostname)
            ||  ! /^\/title\/tt\d+\/?$/i.test (currentLink.pathname)
        )
            continue;

        if (! currentLink.getAttribute ("data-gm-fetched") ){
            if (fetchedLinkCnt >= maxLinksAtATime){
                //--- Position the "continue" button.
                continueBttn.style.display = 'inline';
                currentLink.parentNode.insertBefore (continueBttn, currentLink);
                break;
            }

            fetchTargetLink (currentLink); //-- AJAX-in the ratings for a given link.

            //---Mark the link with a data attribute, so we know it's been fetched.
            currentLink.setAttribute ("data-gm-fetched", "true");
            fetchedLinkCnt++;
        }
    }
}

function fetchTargetLink (linkNode) {
    //--- This function provides a closure so that the callbacks can work correctly.

    /*--- Must either call AJAX in a closure or pass a context.
        But Tampermonkey does not implement context correctly!
        (Tries to JSON serialize a DOM node.)
    */
    GM_xmlhttpRequest ( {
        method:     'get',
        url:        linkNode.href,
        //context:    linkNode,
        onload:     function (response) {
            prependIMDB_Rating (response, linkNode);
        },
        onload:     function (response) {
            prependIMDB_Rating (response, linkNode);
        },
        onabort:     function (response) {
            prependIMDB_Rating (response, linkNode);
        }
    } );
}

function prependIMDB_Rating (resp, targetLink) {
    var isError     = true;
    var ratingTxt   = "** Unknown Error!";

    if (resp.status != 200  &&  resp.status != 304) {
        ratingTxt   = '** ' + resp.status + ' Error!';
    }
    else {
        if (/\(awaiting \d+ votes\)|\(voting begins after release\)|in development/i.test (resp.responseText) ) {
            ratingTxt   = "NR";
            isError     = false;
        }
        else {
            var ratingM = resp.responseText.match (/Users rated this (.*) \(/);
            var votesM  = resp.responseText.match (/\((.*) votes\) -/);

            if (ratingM  &&  ratingM.length > 1  &&  votesM  &&  votesM.length > 1) {
                isError     = false;
                ratingTxt   = ratingM[1] + " - " + votesM[1];
            }
        }
    }

    var resltSpan       = document.createElement ("span");
    resltSpan.innerHTML = '<b> [' + ratingTxt + '] </b>&nbsp;';

    if (isError)
        resltSpan.style.color = 'red';

    //var targetLink      = resp.context;
    //console.log ("targetLink: ", targetLink);

    targetLink.parentNode.insertBefore (resltSpan, targetLink);
}

//--- Create the continue button
var continueBttn        = document.createElement ("button");
continueBttn.innerHTML  = "Get ratings";

continueBttn.addEventListener ("click", function (){
        fetchedLinkCnt              = 0;
        continueBttn.style.display  = 'none';
        processIMDB_Links ();
    },
    false
);

processIMDB_Links ();

Upvotes: 2

Related Questions