insert content script when page was changed by history.pushState and ajax call

懵懂的女人 提交于 2019-12-01 17:55:11
Daniel Möller

There are two possible ways I can think of:

1 - Use timers to check if your script is still there, if not, add again...
2 - Check for ajax calls and if their url matches one of the urls that remove your script, add the script again.

Your script (the one defined in manifest) is still there, even after the ajax calls, it just doesn't run again (not sure what happens with the history pusher). So, I'm assuming you need to just readd some elements or rerun the stript. I supposed you adding the script appending an html tag.

So what you need is something to readd elements or rerun a certain code.


1 - Timer approach - I created a solution for any element (not only scripts) that I wish to add to a certain target element in a page.

It uses a timer to check if the target element is present. When it finds the target element, it adds mine. Then the timer is adjusted to check if my element is still there. If not, add again.

You just need to call appendChildPersistent a single time and this will keep active all the time you navigate around.

var timers = {}; //stores the setInterval ids

//this is the only method you need to call
//give your script an `id` (1)
//the child is your script, it can be anything JQuery.append can take
//toElem is the Jquery "SELECTOR" of the element to add your script into.
//I'm not sure what would happen if toElem were not a string.
//callback is a function to call after insertion if desired, optional.
appendChildPersistent = function(id, child, toElem, callback)
{
    //wait for target element to appear
    withLateElement(toElem, function(target)
    {
        target.append(child); //appends the element - your script                                                                                                           
        if (typeof callback !== 'undefined') callback(); //execute callback if any

        //create a timer to constantly check if your script is still there
        timers[id] = setInterval(function()
        {                                       
            //if your script is not found, clear this timer and tries to add again          
            if (document.getElementById(id) === null)
            {
                clearInterval(timers[id]);
                delete timers[id];
                appendChildPersistent(id, child, toElem, callback);
            }
        },3000);

    });
}

//this function waits for an element to appear on the page
//since you can't foresee when an ajax call will finish
//selector is the jquery selector of the target element
//doAction is what to do when the element is found
function withLateElement(selector, doAction)
{   
    //checks to see if this element is already being waited for                             
    if (!(selector in timers))
    {
        //create a timer to check if the target element appeared                                                            
        timers[selector] = setInterval(function(){              
            var elem = $(selector);

            //checks if the element exists and is not undefined
            if (elem.length >= 0)
            {
                if (typeof elem[0] !== 'undefined')
                {
                    //stops searching for it and executes the action specified
                    clearInterval(timers[selector]);
                    delete timers[selector];
                    doAction(elem);
                }
            }
        }, 2000);
    }                                                           
}

(1) It seems it's not a problem to add an Id to a script tag: Giving the script tag an ID


2 - Capture the ajax calls

An option is to use chrome.webRequest. But strangely, this didn't work for me. Another option is below.

For this case, check this answer, and don't forget to read the related answer to Chrome extension in there. It will only work if you follow the entire procedure. Fortunately, I tested it today and it works great :p

Here, what you do is to change the XMLHttpRequest methods open and send to detect (and possibly get the parameters too) when they are called.

In the Google Extension, however, it's absolutely necessary that you inject the stript in the page (not a background page or script injecting your content script, but your content script injecting some code into the dom, like the following).

var script = document.createElement('script');
script.textContent = actualCode; //actual code is the code you want to inject, the one that replaces the ajax methods
document.head.appendChild(script); //make sure document.head is already loaded before doing it
script.parentNode.removeChild(script); //I'm not sure why the original answer linked removes the script after that, but I kept doing it in my solution

This is crucial because the extension tries to create an isolated environment, and the changes you do to the XMLHttpRequest in this environment will simply not take part. (That's why JQuery.ajaxComplete doesn't seem to work, you need to inject a script in the page for it to work - look here)

In this pure javascript solution, you replace the methods:

//enclosing the function in parentheses to avoid conflict with vars from the page scope
(function() {
    var XHR = XMLHttpRequest.prototype;

    // Store the orignal methods from the request
    var open = XHR.open;
    var send = XHR.send;

    // Create your own methods to replace those

    //this custom open stores the method requested (get or post) and the url of the request
    XHR.open = function(method, url) {
        this._method = method; //this field was invented here
        this._url = url; //this field was invented here
        return open.apply(this, arguments); //calls the original method without any change

        //what I did here was only to capture the method and the url information
    };


    //this custom send adds an event listener that fires whenever a request is complete/loaded
    XHR.send = function(postData) {
        //add event listener that fires when request loads
        this.addEventListener('load', function() {
            //what you want to do when a request is finished
            //check if your element is there and readd it if necessary
            //if you know the exact request url, you can put an if here, but it's not necessary

            addMyElementsToPage(); //your custom function to add elements
            console.log("The method called in this request was: " + this._method);
            console.log("The url of this request was: " + this._url);
            console.log("The data retrieved is: " + this.responseText);

        });

        //call the original send method without any change
        //so the page can continue it's execution
        return send.apply(this, arguments);

        //what we did here was to insert an interceptor of the success of a request and let the request continue normally
    };
})();
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!