4

What I want to do.

Using a Chrome extension I need to inject a script into the page context in such a way that the injected script runs before any other javascript on the page.

Why do I need this?

I need to hijack all console commands for a specific page so my extension can listen to the messages.

My current issue

Currently I am catching some of the messages logged by the page but not all of them, specifically, all messages from web-directory-132a3f16cf1ea31e167fdf5294387073.js are not being caught. After some digging I discovered that the web-directory-132a3f16cf1ea31e167fdf5294387073.js is also hijacking console but doing so before my script has a chance to.

As a visual, if I look at the network tab after loading the page, I see this:

enter image description here

My injected script is consoleInterceptor.js. It correctly captures the output of the js files that are loaded here accept web-directory-132a3f16cf1ea31e167fdf5294387073.js

Inside of web-directory-132a3f16cf1ea31e167fdf5294387073.js is some code something like this:

   ....
    _originalLogger: t.default.Logger,
   ...
    // do stuff with logging ....
    this._originalLogger[e].apply(this._originalLogger, s),

What I think the problem is

It seems to me that, web-directory-132a3f16cf1ea31e167fdf5294387073.js is grabbing the standard console functions and storing them internally before my script has had a chance to replace them with my own versions. So even though my script works, the web-directory-132a3f16cf1ea31e167fdf5294387073.js still uses the original standard console functions it saved.

Note that web-directory-132a3f16cf1ea31e167fdf5294387073.js is an ember application and I dont see any simple way to hook into that code to overwrite those functions too but Im open to that as a solution.

My current code:

manifest.js

  ...
  "web_accessible_resources": [
    "js/ajaxInterceptor.js",
    "js/consoleInterceptor.js"
  ],
  "version" : "5.2",
  "manifest_version": 2,
  "permissions": [
    "<all_urls>",
    "tabs",
    "activeTab",
    "storage",
    "webNavigation",
    "unlimitedStorage",
    "notifications",
    "clipboardWrite",
    "downloads",
    "tabCapture",
    "cookies",
    "browsingData",
    "webRequest",
    "*://*/*",
    "gcm",
    "contextMenus",
    "management"
  ],
  "externally_connectable": {
    "matches": ["*://apps.mypurecloud.com/*","*://*.cloudfront.net/*"]
  },
  ...

background.js

var options = {url: [{hostContains: 'apps.mypurecloud.com'}]};
chrome.webNavigation.onCommitted.addListener(function(details) {
        // first inject the chrome extension's id
        chrome.tabs.executeScript(details.tabId, {
            code: "var chromeExtensionId = " + JSON.stringify(chrome.runtime.id)
        });
        // then inject the script which will use the dynamically added extension id
        // to talk to the extension
        chrome.tabs.executeScript(details.tabId, {
            file: 'js/injectConsoleInterceptor.js'
        });
    },
    options
);
chrome.runtime.onMessageExternal.addListener(
    function(msg, sender, sendResponse) {
        if(msg.action === 'console_intercepted'){
            _this.processConsoleMessage(sender, msg.details.method, msg.details.arguments);

        }
    });

injectConsoleInterceptor.js

var interceptorScript = document.createElement('script');
interceptorScript.src = chrome.extension.getURL('js/consoleInterceptor.js');
interceptorScript.onload = function(){this.remove();};
(document.head || document.documentElement).prepend(interceptorScript);

consoleInterceptor.js

if(!window.hasConsoleInterceptor){
    window.hasConsoleInterceptor = true;
    console.log('overriding console functions');
    var originals ={};
    var console = window.console;
    if (console){
        function interceptConsoleMethod(method){
            originals[method] = console[method];
            console[method] = function(){
                // send the data to the extension
                // chromeExtensionId should be injected into the page separately and before this script
                var data = {
                    action: 'console_intercepted',
                    details: {
                        method: method,
                        arguments: arguments
                    }
                };
                chrome.runtime.sendMessage(chromeExtensionId, data);
                originals[method].apply(console, arguments)
            }
        }
        // an array of the methods we want to observe 
        var methods = ['assert', 'count', 'debug', 'dir', 'dirxml', 'error', 'group','groupCollapsed','groupEnd','info','log', 'profile', 'profileEnd','time','timeEnd','timeStamp','trace','warn','table'];
        for (var i = 0; i < methods.length; i++){
            interceptConsoleMethod(methods[i])
        }
        console.log('Successfully overridden console functions: '+methods.join(','));
    }
}

My question

What can I do to make consoleInterceptor.js run before web-directory-132a3f16cf1ea31e167fdf5294387073.js loads so that web-directory-132a3f16cf1ea31e167fdf5294387073.js uses my modified console functions rather than the default browser console funcitons?

Wesley Smith
  • 19,401
  • 22
  • 85
  • 133
  • Have you tried utilizing the `run_at` property in the manifest? https://developer.chrome.com/extensions/content_scripts – Rob M. Jul 21 '17 at 22:34
  • 2
    1. Use a content script declared in manifest.json with "run_at": "document_start" + 2. inject the code as a [literal string](https://stackoverflow.com/a/9517879), not as an URL – wOxxOm Jul 21 '17 at 22:37
  • @RobM. Yeah, I have it set as `"run_at": "document_start",`. Ill add it above. However, I don't think it actually applies here since the script isnt added via `content_scripts` but rather directly injected via `executeScript` in the `webNavigation.onCommitted` listener, but, I could definitely be wrong – Wesley Smith Jul 21 '17 at 22:38
  • 1
    The point is to use manifest-declared content script, not executeScript which adds async delays. Also, literal code is to avoid https://crbug.com/634381 – wOxxOm Jul 21 '17 at 22:39
  • @wOxxOm yeah, I get what you're saying saw that after posting, im trying that now, ty – Wesley Smith Jul 21 '17 at 22:41
  • @wOxxOm I moved the injection script to content_scripts and used the `matches` property to limit the sites it's injected into and that did solve it. I mistakenly thought that injecting it in the `onCommitted` event would avoid the issues you mention. Thank you, throw up an answer and Ill accept it – Wesley Smith Jul 21 '17 at 23:29
  • 2
    Well, IIRC this question's tasks are covered in separate answers already so maybe someone else will add a combined answer, not me. P.S. there's also [declarativeContent](https://developer.chrome.com/extensions/declarativeContent) API with RequestContentScript action which can be used from the background page without the timing issues of onCommitted + executeScript. – wOxxOm Jul 21 '17 at 23:44
  • @DelightedD0D, If, `onCommitted` happens too late for your purpose, then, if you're going to use `tabs.executeScript()`, the [earliest time you can inject is is in the `webRequest.onHeadersReceived` event that fires after the `webNavigation.onBeforeNavigate` event](https://stackoverflow.com/a/42151795/3773011) – Makyen Jul 23 '17 at 18:07
  • 1
    Possible duplicate of [Google chrome extension: how to inject script immediately after page reload?](https://stackoverflow.com/questions/42151342/google-chrome-extension-how-to-inject-script-immediately-after-page-reload) – Makyen Jul 23 '17 at 18:08
  • Possible duplicate of [Insert code into the page context using a content script](https://stackoverflow.com/questions/9515704/insert-code-into-the-page-context-using-a-content-script) – Shayan Oct 06 '19 at 09:26

1 Answers1

3

You can try this.

In manifest.json file:


    "content_scripts": [
            {
              "matches": ["http://*/*", "https://*/*"],
              "js": ["script/inject.js"],
              "run_at":"document_start"
            }
        ]

In inject.js:

var ss = document.createElement("script");
ss.innerHTML= "xxx";
document.documentElement.appendChild(ss);
  • Note that a `window` object in the inner html (where you have 'xxx') will evaluate to the correct final window, whereas an outer `window`, or indeed the `document.defaultView` evaluate to some other window object that as far as I can tell is good for nothing. – EoghanM Oct 04 '22 at 18:17
  • Ok didn't realize this is normal feature of chrome extensions and is due to https://developer.chrome.com/docs/extensions/mv3/content_scripts/#isolated_world – EoghanM Oct 05 '22 at 09:39
  • So the 'isolated worlds' concept may affect the OPs ability to hijack/monkeypatch `console.log` — it will need to be done in the page context. – EoghanM Oct 05 '22 at 09:40