33

I work on a javascript library that customers include on their site to embed a UI widget. I want a way to test dev versions of the library live on the customer's site without requiring them to make any changes to their code. This would make it easy to debug issues and test new versions.

To do this I need to change the script include to point to my dev server, and then override the load() method that's called in the page to add an extra parameter to tell it what server to point to when making remote calls.

It looks like I can add JS to the page using a chrome extension, but I don't see any way to modify the page before it's loaded. Is there something I'm missing, or are chrome extensions not allowed to do this kind of thing?

Herms
  • 37,540
  • 12
  • 78
  • 101
  • Haven't tried it yet, but this looks promising: http://developer.chrome.com/extensions/devtools_inspectedWindow.html#method-eval – Hartley Brody Aug 23 '13 at 15:00
  • You can use [Requestly Redirect Rule](https://requestly.io/feature/redirect-url/) to swap Production script with development script and [Requestly Insert Script Rule](https://requestly.io/feature/insert-custom-scripts/) to insert additional scripts on your page. Chrome extensions can't do much about Inline scripts. You can use Requestly Desktop app for making modifications in Inline scripts too. Read this [answer for more details](https://stackoverflow.com/a/71204243/816213) – Sachin Jain Feb 21 '22 at 10:01

6 Answers6

39

I've done a fair amount of Chrome extension development, and I don't think there's any way to edit a page source before it's rendered by the browser. The two closest options are:

  • Content scripts allow you to toss in extra JavaScript and CSS files. You might be able to use these scripts to rewrite existing script tags in the page, but I'm not sure it would work out, since any script tags visible to your script through the DOM are already loaded or are being loaded.

  • WebRequest allows you to hijack HTTP requests, so you could have an extension reroute a request for library.js to library_dev.js.

Assuming your site is www.mysite.com and you keep your scripts in the /js directory:

chrome.webRequest.onBeforeRequest.addListener(
    function(details) {
        if( details.url == "http://www.mysite.com/js/library.js" )
            return {redirectUrl: "http://www.mysite.com/js/library_dev.js" };
    },
    {urls: ["*://www.mysite.com/*.js"]},
    ["blocking"]);

The HTML source will look the same, but the document pulled in by <script src="library.js"></script> will now be a different file. This should achieve what you want.

apsillers
  • 112,806
  • 17
  • 235
  • 239
  • I found the content scripts, and looked like doing a run_at = document_end might let me do what I need. I hadn't seen the web request bit though. That might make the script include change easier. – Herms Apr 09 '12 at 17:09
  • 1
    I did a test just now, and content scripts won't work in general. As I suspected, using document_start means the DOM isn't built yet, and document_end means the old scripts have already been loaded. – apsillers Apr 09 '12 at 17:14
  • WebRequest might be all I need. The only change to the JS in the page (other than the script include) that I need is to tell it to hit a different server when making requests. I think WebRequest might let me redirect those as well. I'll give that a shot. – Herms Apr 09 '12 at 17:16
  • 1
    Yeah, WebRequest is powerful enough to reroute virtually any requests coming out of your browser (explicit address bar requests, Ajax requests, script/image resources, etc.). Good luck! – apsillers Apr 09 '12 at 17:25
  • Where does the chrome.webRequest call go? In the background page? As a content script? – Herms Apr 09 '12 at 17:28
  • 2
    I believe it goes in your background page. – apsillers Apr 09 '12 at 17:37
  • 1
    background page it is. I have something quick and dirty set up that appears to be working. Thanks for the help! – Herms Apr 09 '12 at 18:04
  • Another way to redirect js script tags is to use a content script and beforeload http://stackoverflow.com/questions/7931565/chrome-extension-override-js-file/8739621#8739621. Also, at document_start you have the document.documentElement that you can work with. – PAEz Apr 09 '12 at 19:54
4

Here's a way to modify content before it is loaded on the page using the WebRequest API. This requires the content to be loaded into a string variable before the onBeforeRequest listener returns. This example is for javascript, but it should work equally well for other types of content.

chrome.webRequest.onBeforeRequest.addListener(
    function (details) {
        var javascriptCode = loadSynchronously(details.url);
        // modify javascriptCode here
        return { redirectUrl: "data:text/javascript," 
                             + encodeURIComponent(javascriptCode) };
    },
    { urls: ["*://*.example.com/*.js"] },
    ["blocking"]);

loadSynchronously() can be implemented with a regular XMLHttpRequest. Synchronous loading will block the event loop and is deprecated in XMLHttpRequest, but it is unfortunately hard to avoid with this solution.

Lars Christian Jensen
  • 1,407
  • 1
  • 13
  • 14
1

You might be interested in the hooks available in the Opera browser. Opera used to have* very powerful hooks, available both to User JavaScript files (single-file things, very easy to write and deploy) and Extensions. Some of these are:

BeforeExternalScript:

This event is fired when a script element with a src attribute is encountered. You may examine the element, including its src attribute, change it, add more specific event listeners to it, or cancel its loading altogether.

One nice trick is to cancel its loading, load the external script in an AJAX call, perform text replacement on it, and then re-inject it into the webpage as a script tag, or using eval.

window.opera.defineMagicVariable:

This method can be used by User JavaScripts to override global variables defined by regular scripts. Any reference to the global name being overridden will call the provided getter and setter functions.

window.opera.defineMagicFunction:

This method can be used by User JavaScripts to override global functions defined by regular scripts. Any invocation of the global name being overridden will call the provided implementation.


*: Opera recently switched over to the Webkit engine, and it seems they have removed some of these hooks. You can still find Opera 12 for download on their website, though.

Tobia
  • 17,856
  • 6
  • 74
  • 93
  • "Last update: 2012-06-02". The `window.opera` object was removed when they switched to Chrome. However, the outdated article you're referring to has not been updated (yet). The latest docs can be found at http://dev.opera.com/extension-docs/. – Rob W Dec 22 '13 at 13:17
  • Too bad. Those were pretty powerful hooks. – Tobia Dec 22 '13 at 15:28
  • 2
    I suggest to delete the answer, or at least edit it to make it clear that the contents of the answer are not relevant any more (obsolete, redundant, etc.). – Rob W Dec 22 '13 at 15:30
0

I had an idea, but I didn't try it, but it worked in theory.

Run content_script that was executed before the document was loaded, and register a ServiceWorker to replace page's requested file content in real time. (ServiceWorker can intercept all requests in the page, including those initiated directly through the dom)

KInGcC
  • 372
  • 1
  • 2
  • 9
0

Chrome extension (manifest v3) allow us to add rules for declarativeNetRequest:

chrome.declarativeNetRequest.updateDynamicRules({
    addRules: [
        {
            "id": 1002,
            "priority": 1,
            "action": { 
                "type": "redirect", 
                "redirect": { 
                    "url": "https://example.com/script.js" 
                } 
            },
            "condition": {
                "urlFilter": 'https://www.replaceme.com/js/some_script_to_replace.js',
                "resourceTypes": [
                    'csp_report',
                    'font',
                    'image',
                    'main_frame',
                    'media',
                    'object',
                    'other',
                    'ping',
                    'script',
                    'stylesheet',
                    'sub_frame',
                    'webbundle',
                    'websocket',
                    'webtransport',
                    'xmlhttprequest'
                ]

            }
        },
    ],
    removeRuleIds: [1002]

});

and debug it by adding listener:

chrome.declarativeNetRequest.onRuleMatchedDebug.addListener(
    c => console.log('onRuleMatchedDebug', c)
)
santakllouse
  • 109
  • 1
  • 4
-2

It's not a Chrome extension, but Fiddler can change the script to point to your development server (see this answer for setup instructions from the author of Fiddler). Also, with Fiddler you can setup a search and replace to add that extra parameter that you need.

Community
  • 1
  • 1
Josh
  • 2,142
  • 2
  • 23
  • 20