简体   繁体   English

当history.pushState和ajax调用更改页面时插入内容脚本

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

I've faced with the problem with inserting content script into the page which was changed by history.pushState and ajax call. 我遇到了将内容脚本插入到由history.pushState和ajax调用更改的页面中的问题。 I've found the similar topic at stackoverflow, but that solution doesn't work for me (that solution was in using of chrome.webNavigation.onHistoryStateUpdated and "popstate" event ). 我在stackoverflow上找到了类似的主题 ,但该解决方案对我不起作用(该解决方案是使用chrome.webNavigation.onHistoryStateUpdated和“popstate”事件)。

Here is a fragment of my manifest: 这是我的清单的一个片段:

"content_scripts": [
    {
      "matches": ["https://vk.com/audios*", "https://vk.com/al_audio.php*"],
      "js": ["jquery-2.1.4.min.js", "getListOfSongs.js"]
    }
  ]

chrome.webNavigation.onHistoryStateUpdated works only if i navigate to the another page, if i navigate to the same page many times in sequence nothing happens. chrome.webNavigation.onHistoryStateUpdated仅当我导航到另一个页面时才有效,如果我按顺序多次导航到同一页面没有任何反应。 For example: it works when 例如:它工作时

1) Go to the https://vk.com/audios * - opening page first time or reloading 1)首次访问https://vk.com/audios * - 首页或重新加载

2) Go to the https://vk.com/some_other_page - ajax call 2)转到https://vk.com/some_other_page - ajax调用

3) Go to the https://vk.com/audios * - ajax call 3)转到https://vk.com/audios * - ajax电话

It doesn't work when 它不起作用

1) Go to the https://vk.com/audios * - opening page first time or reloading 1)首次访问https://vk.com/audios * - 首页或重新加载

2) Again go to the https://vk.com/audios * - ajax call, at this point content script isn't injecting 2)再次转到https://vk.com/audios * - ajax调用,此时内容脚本没有注入
3) Again go to the https://vk.com/audios * - ajax call, at this point content script isn't injecting and so on 3)再次转到https://vk.com/audios * - ajax调用,此时内容脚本没有注入,依此类推

Every time i am clicking to the same page for the second time and so on the following request is generating: 每次我第二次点击同一页面时依次生成以下请求:

https://vk.com/al_audio.php?__query=audios *********&_ref=left_nav&_smt=audio%3A2&al=-1&al_id=********&_rndVer=60742 https://vk.com/al_audio.php?__query=audios *********&_ ref = left_nav&_smt = audio%3A2&al = -1&al_id = ********&_ rndVer = 60742

(parameters of request may vary) (请求参数可能有所不同)

Also JQuery .ajaxComplete doesn't catch any events in this case. 在这种情况下, JQuery .ajaxComplete也不会捕获任何事件。

And pushState doesn't fire "popstate" event, so i can't use window.onpopstate event 并且pushState不会触发“popstate”事件,因此我无法使用window.onpopstate事件

I might use chrome.webNavigation.onDOMContentLoaded and chrome.webNavigation.onCompleted but when i reload page these events are happening more then one time, so script will be injected more than one time. 我可能会使用chrome.webNavigation.onDOMContentLoadedchrome.webNavigation.onCompleted,但是当我重新加载页面时,这些事件发生的时间超过一次,因此脚本将被多次注入。

What is the best solution for this case? 这种情况的最佳解决方案是什么?

There are two possible ways I can think of: 我可以想到两种可能的方式:

1 - Use timers to check if your script is still there, if not, add again... 1 - 使用计时器检查脚本是否仍在那里,如果没有,再添加一次......
2 - Check for ajax calls and if their url matches one of the urls that remove your script, add the script again. 2 - 检查ajax调用,如果他们的url与删除脚本的其中一个URL匹配,请再次添加脚本。

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). 您的脚本(清单中定义的脚本)仍然存在,即使在ajax调用之后,它也不会再次运行(不确定历史推送器会发生什么)。 So, I'm assuming you need to just readd some elements or rerun the stript. 所以,我假设您需要读取一些元素或重新运行stript。 I supposed you adding the script appending an html tag. 我假设您添加了附加html标记的脚本。

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. 1 - 计时器方法 - 我为我希望添加到页面中某个目标元素的 任何元素 (不仅是脚本)创建了一个解决方案。

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. 您只需要一次调用appendChildPersistent ,这将在您浏览时始终保持活动状态。

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 (1)向脚本标记添加Id似乎不是问题: 为脚本标记提供ID


2 - Capture the ajax calls 2 - 捕获ajax调用

An option is to use chrome.webRequest . 一个选项是使用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. 对于这种情况,请检查此答案 ,并且不要忘记在那里阅读Chrome扩展程序的相关答案。 It will only work if you follow the entire procedure. 它只有在您遵循整个过程时才有效。 Fortunately, I tested it today and it works great :p 幸运的是,我今天测试了它并且效果很好: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. 在这里,您要做的是更改XMLHttpRequest方法opensend以在调用它们时检测(并且可能也获取参数)。

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). 但是,在Google Extension中,绝对有必要在页面中插入stript (而不是注入内容脚本的后台页面或脚本,但是内容脚本会将一些代码注入到dom中 ,如下所示)。

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. 这是至关重要的,因为扩展尝试创建一个隔离的环境,并且您在此环境中对XMLHttpRequest所做的更改将不会参与。 (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 ) (这就是JQuery.ajaxComplete似乎不起作用的原因,你需要在页面中注入一个脚本才能工作 - 看看这里

In this pure javascript solution , you replace the methods: 这个纯JavaScript的解决方案中 ,您可以替换方法:

//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
    };
})();

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM