Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Google chrome extension: how to inject script immediately after page reload?

I have a background script that periodically reloads the current tab.

var code = 'window.location.reload();';
chrome.tabs.executeScript(my_active_tab, {code: code});

After each page reload, immediately I want to inject another script.

chrome.tabs.onUpdated.addListener(function (tabId, changeInfo, tab){
    if (changeInfo.status == 'complete') {
        chrome.tabs.executeScript(tabId, { file: "my_script.js" });
    }
});

Above is the code I have at the moment. The thing is, it works but it is too slow, because it literally waits after every single image has been loaded.

My goal is to execute the script immediately after DOM load. Any ideas?

like image 493
Anonymous Avatar asked Feb 10 '17 04:02

Anonymous


People also ask

When JavaScript files are injected into the web page?

When JavaScript files are injected into the web page is controlled by the run_at field. The preffered and default field is "document_idle", but can also be specified as "document_start" or "document_end" if needed. ... ... Preferred.

How does the browser decide when to inject scripts?

The browser chooses a time to inject scripts between "document_end" and immediately after the window.onload event fires. The exact moment of injection depends on how complex the document is and how long it is taking to load, and is optimized for page load speed.

Can I inject content scripts?

Content scripts can be injected as code. Or an entire file can be injected. Use declarative injection for content scripts that should be automatically run on specified pages. Declaratively injected scripts are registered in the manifest under the "content_scripts" field. They can include JavaScript files, CSS files or both.

How to execute your own script in Chrome browser?

Chrome extensions provide a way out to execute our own script into a web page via “content-script”. Here “jquery-1.11.3.min.js”, “script.js” , “mycss.css” are the files I have defined as my content script. You might already know that chrome browser requires a “manifest.json” file in the extension’s directory.


1 Answers

Use a manifest.json content_scripts entry with "run_at": "document_start"

Using a manifest.json content_scripts entry with "run_at": "document_start" is the only way that you can guarantee your content script is injected prior to the page existing. Code injected at this time will find both document.body and document.head will be null.

Earliest possible time to inject using tabs.executeScript()

The earliest time that works to call tabs.executeScript() is in the webRequest.onHeadersReceived event that fires after the webNavigation.onBeforeNavigate event for the page navigation you are interested in. Using tabs.executeScript() prior to this event may result in your script not being injected in the new page without any reported error. Given the inherent asynchronous nature of the timing between your background script and the process of loading the page, such failures can be intermittent, and will be affected by both OS/system configuration and the exact page that's being loaded. In fact, my statement that webRequest.onHeadersReceived will work is based on testing, as opposed to verifying in the Chrome source code. As a result, there may be corner cases which I did not test.

Injecting at that point consistently works, but the time at which the injection occurs with respect to page loading is somewhat inconsistent. Sometimes, the document.head and document.body will be null (as will always be the case with a manifest.json content_scripts injection with "run_at":"document_start"). Other times, the document.head and document.body will contain a valid DOM. It does not appear to be possible to get this timed any tighter (i.e. always have document.head and document.body be null) due to the background script and the content being in different processes: thus, inherently asynchronous.

Using webNavigation.onBeforeNavigate and not waiting for at least the associated webRequest.onHeadersReceived event is too early and does not function (content script not injected). In other words, even the associated webRequest.onSendHeaders event is too early.

Obviously, to get the content script injected that early, you have to specify runAt:'document_start' in your call to tabs.executeScript().

To inject just after the DOM exists: webNavigation.onCommitted

If you want something that is easier, and will result in the content script being injected prior to anything in the page other than the main HTML document, then you can just use the webNavigation.onCommitted event for the desired URL to trigger your tabs.executeScript(). This will result in the injected content script being loaded immediately after the main HTML document. Using webNavigation.onCommitted is made easier because it has the ability to specify a filter for your event. Thus, you can have your event listener only be called for the URLs you are interested in.

The webNavigation.onCommitted event fires after the webRequest.ResponseStarted event for the main HTML page, but before any resources are fetched (i.e. prior to any webRequest.BeforeRequest events for page resources). Interestingly, it does fire after the tabs.onUpdated event that declares a status:'loading'. The tabs.onUpdated event with status:'loading' is not a good one to trigger on by itself, because it can fire for other reasons with identical properties, but which don't indicate a page load/reload.

If you only want to tabs.executeScript() upon a page reload

The webNavigation.onCommitted event listener receives a property: transitionType, which will be different values based on the cause of the navigation. One of those values is 'reload', which you could use to filter for only page reloads.

Given that you are interested in page reload, and not loading frames, you will want to make sure that the webNavigation events are for frameId:0.

These are the events which occur when you reload a tab by clicking on the "reload this page" button:

webNavigation.onBeforeNavigate    ->  arg[0]= {"frameId":0,"parentFrameId":-1,"processId":-1,"tabId":411,"timeStamp":1500401223978.314,"url":"http://www.example.com/"}        
webRequest.onBeforeRequest        ->  arg[0]= {"frameId":0,"method":"GET","parentFrameId":-1,"requestId":"260870","tabId":411,"timeStamp":1500401223979.044,"type":"main_frame","url":"http://www.example.com/"}        
webRequest.onBeforeSendHeaders    ->  arg[0]= {"frameId":0,"method":"GET","parentFrameId":-1,"requestHeaders":[{"name":"Upgrade-Insecure-Requests","value":"1"},{"name":"User-Agent","value":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36"},{"name":"Accept","value":"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8"},{"name":"Accept-Encoding","value":"gzip, deflate"},{"name":"Accept-Language","value":"en-US,en;q=0.8"}],"requestId":"260870","tabId":411,"timeStamp":1500401223979.3242,"type":"main_frame","url":"http://www.example.com/"}        
webRequest.onSendHeaders          ->  arg[0]= {"frameId":0,"method":"GET","parentFrameId":-1,"requestHeaders":[{"name":"Upgrade-Insecure-Requests","value":"1"},{"name":"User-Agent","value":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36"},{"name":"Accept","value":"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8"},{"name":"Accept-Encoding","value":"gzip, deflate"},{"name":"Accept-Language","value":"en-US,en;q=0.8"}],"requestId":"260870","tabId":411,"timeStamp":1500401223979.538,"type":"main_frame","url":"http://www.example.com/"}        
webRequest.onHeadersReceived      ->  arg[0]= {"frameId":0,"method":"GET","parentFrameId":-1,"requestId":"260870","responseHeaders":[{"name":"Content-Encoding","value":"gzip"},{"name":"Accept-Ranges","value":"bytes"},{"name":"Cache-Control","value":"max-age=604800"},{"name":"Content-Type","value":"text/html"},{"name":"Date","value":"Tue, 18 Jul 2017 18:07:03 GMT"},{"name":"Etag","value":"\"359670651\""},{"name":"Expires","value":"Tue, 25 Jul 2017 18:07:03 GMT"},{"name":"Last-Modified","value":"Fri, 09 Aug 2013 23:54:35 GMT"},{"name":"Server","value":"ECS (rhv/818F)"},{"name":"Vary","value":"Accept-Encoding"},{"name":"X-Cache","value":"HIT"},{"name":"Content-Length","value":"606"}],"statusCode":200,"statusLine":"HTTP/1.1 200 OK","tabId":411,"timeStamp":1500401224072.296,"type":"main_frame","url":"http://www.example.com/"}        
---^^^^^^^^^^^^^^^^^^^^^^^^^-Earliest tabs.executeScript() injection is in the handler for the webRequest.onHeadersReceived event.
webRequest.onResponseStarted      ->  arg[0]= {"frameId":0,"fromCache":false,"ip":"93.184.216.34","method":"GET","parentFrameId":-1,"requestId":"260870","responseHeaders":[{"name":"Content-Encoding","value":"gzip"},{"name":"Accept-Ranges","value":"bytes"},{"name":"Cache-Control","value":"max-age=604800"},{"name":"Content-Type","value":"text/html"},{"name":"Date","value":"Tue, 18 Jul 2017 18:07:03 GMT"},{"name":"Etag","value":"\"359670651\""},{"name":"Expires","value":"Tue, 25 Jul 2017 18:07:03 GMT"},{"name":"Last-Modified","value":"Fri, 09 Aug 2013 23:54:35 GMT"},{"name":"Server","value":"ECS (rhv/818F)"},{"name":"Vary","value":"Accept-Encoding"},{"name":"X-Cache","value":"HIT"},{"name":"Content-Length","value":"606"}],"statusCode":200,"statusLine":"HTTP/1.1 200 OK","tabId":411,"timeStamp":1500401224072.5032,"type":"main_frame","url":"http://www.example.com/"}        
webRequest.onCompleted            ->  arg[0]= {"frameId":0,"fromCache":false,"ip":"93.184.216.34","method":"GET","parentFrameId":-1,"requestId":"260870","responseHeaders":[{"name":"Content-Encoding","value":"gzip"},{"name":"Accept-Ranges","value":"bytes"},{"name":"Cache-Control","value":"max-age=604800"},{"name":"Content-Type","value":"text/html"},{"name":"Date","value":"Tue, 18 Jul 2017 18:07:03 GMT"},{"name":"Etag","value":"\"359670651\""},{"name":"Expires","value":"Tue, 25 Jul 2017 18:07:03 GMT"},{"name":"Last-Modified","value":"Fri, 09 Aug 2013 23:54:35 GMT"},{"name":"Server","value":"ECS (rhv/818F)"},{"name":"Vary","value":"Accept-Encoding"},{"name":"X-Cache","value":"HIT"},{"name":"Content-Length","value":"606"}],"statusCode":200,"statusLine":"HTTP/1.1 200 OK","tabId":411,"timeStamp":1500401224074.0261,"type":"main_frame","url":"http://www.example.com/"}        
tabs.onUpdated                    ->  arg[0]= 411 :: arg[1]= {"status":"loading","url":"http://www.example.com/"} :: arg[2]= {"active":true,"audible":false,"autoDiscardable":true,"discarded":false,"height":902,"highlighted":true,"id":411,"incognito":false,"index":1,"mutedInfo":{"muted":false},"pinned":false,"selected":true,"status":"loading","title":"www.example.com","url":"http://www.example.com/","width":1282,"windowId":10}    
tabs.onZoomChange                 ->  arg[0]= {"newZoomFactor":1,"oldZoomFactor":1,"tabId":411,"zoomSettings":{"mode":"automatic","scope":"per-origin"}}        
webNavigation.onCommitted         ->  arg[0]= {"frameId":0,"processId":107,"tabId":411,"timeStamp":1500401224079.4019,"transitionQualifiers":[],"transitionType":"reload","url":"http://www.example.com/"}
--->>Here is where you can tell it's a reload --------------------------------------------------------------------------------------------------^^^^^^^^^^^^^^^^^^^^^^^^^
history.onVisited                 ->  arg[0]= {"id":"42","lastVisitTime":1500401224077.579,"title":"Example Domain","typedCount":1,"url":"http://www.example.com/","visitCount":12}        
tabs.onUpdated                    ->  arg[0]= 411 :: arg[1]= {"title":"Example Domain"} :: arg[2]= {"active":true,"audible":false,"autoDiscardable":true,"discarded":false,"height":902,"highlighted":true,"id":411,"incognito":false,"index":1,"mutedInfo":{"muted":false},"pinned":false,"selected":true,"status":"loading","title":"Example Domain","url":"http://www.example.com/","width":1282,"windowId":10}    
webNavigation.onDOMContentLoaded  ->  arg[0]= {"frameId":0,"processId":107,"tabId":411,"timeStamp":1500401224093.404,"url":"http://www.example.com/"}        
webNavigation.onCompleted         ->  arg[0]= {"frameId":0,"processId":107,"tabId":411,"timeStamp":1500401224094.768,"url":"http://www.example.com/"}        
tabs.onUpdated                    ->  arg[0]= 411 :: arg[1]= {"status":"complete"} :: arg[2]= {"active":true,"audible":false,"autoDiscardable":true,"discarded":false,"height":902,"highlighted":true,"id":411,"incognito":false,"index":1,"mutedInfo":{"muted":false},"pinned":false,"selected":true,"status":"complete","title":"Example Domain","url":"http://www.example.com/","width":1282,"windowId":10}   

Note: This information is based on my own testing. I have found no documentation from Google with this level of specificity. The exact timing of what actually works may change in future versions of Chrome.

like image 194
Makyen Avatar answered Oct 29 '22 06:10

Makyen