javascriptgoogle-chrome-extensionbrowserfirefox-addon

browser extension modifying site's javascript


I want to make a simple browser extension that would do the following:

Replace hjkl vim keys with jkl; at compiler explorer's vim-mode.

I figured out that I can open devtools, put a breakpoint somewhere here and paste the following code in the console

monacoVim.VimMode.Vim.noremap(';', 'l');
monacoVim.VimMode.Vim.noremap('l', 'k');
monacoVim.VimMode.Vim.noremap('k', 'j');
monacoVim.VimMode.Vim.noremap('j', 'h');
monacoVim.VimMode.Vim.noremap('h', ';');

This has the desired effect, however I would like not to have any manual steps. So I would like to write an extension that achieves the same effect.

Apart from compiler explorer I need the approach to be applicable for closed-source sites like hackerrank, so commiting to opensource is not applicable in my case.

How can I write an extension that would modify site's js the way I described? Or are there any other ways to achieve the desired effect?


Solution

  • The webRequest API can alter response bodies on the fly. Compiler Explorer would itself be compiled into an implementation, so you'll have to tweak the sample code below to make it work. I'll explain below.

    manifest.json

    {
      "manifest_version": 2,
      "name": "HackCE",
      "description": "Hack Compiler Explorer",
      "version": "0.1",
      "content_security_policy": "default-src 'none'; object-src 'none'; script-src 'self';",
      "browser_specific_settings": {
        "gecko": {
          "id": "hackce@nanigashi.stackoverflow",
          "strict_min_version": "57.0"
        }
      },
      "background": {
        "scripts": [
          "background.js"
        ]
      },
      "permissions": [
        "webRequest",
        "webRequestBlocking",
        "<all_urls>"
      ]
    }
    

    background.js

    ( function () {
      'use strict';
    
      // look for
      //     enableVim(): void {
      // replace it with
      //     enableVim(): void {
      //         monacoVim.VimMode.Vim.noremap(';', 'l');
      //         monacoVim.VimMode.Vim.noremap('l', 'k');
      //         monacoVim.VimMode.Vim.noremap('k', 'j');
      //         monacoVim.VimMode.Vim.noremap('j', 'h');
      //         monacoVim.VimMode.Vim.noremap('h', ';');
      function onBR( e ) {
        var
          stream = browser.webRequest.filterResponseData( e.requestId ),
          dec = new TextDecoder( 'UTF-8' ),
          data = [];
    
        stream.ondata = function ( e ) {
          data.push( dec.decode( e.data, { stream: true } ) );
        };
        stream.onstop = function () {
          var
            s = data.join( '' ),
            enc = new TextEncoder(),
            textFind = 'enableVim(): void {',
            textAdd = '\n' +
              '        monacoVim.VimMode.Vim.noremap(\';\', \'l\');\n' +
              '        monacoVim.VimMode.Vim.noremap(\'l\', \'k\');\n' +
              '        monacoVim.VimMode.Vim.noremap(\'k\', \'j\');\n' +
              '        monacoVim.VimMode.Vim.noremap(\'j\', \'h\');\n' +
              '        monacoVim.VimMode.Vim.noremap(\'h\', \';\');';
    
          s = s.replace( textFind, '$&' + textAdd );
          stream.write( enc.encode( s ) );
          stream.close();
        };
        return {};
      }
    
      browser.webRequest.onBeforeRequest.addListener(
        onBR, {
          types: [ 'main_frame' ],
          urls: [ 'https://raw.githubusercontent.com/compiler-explorer/*/editor.ts' ]
        }, [
          'blocking'
        ]
      );
    } () );
    

    This example shows how to add the lines you desire to the raw listing on github.

    1. Install the extension (as a temporary add-on).
    2. Go to the link you included in the question. It should look unchanged.
    3. Click the Raw button. Search/scroll to enableVim(): void. It should look different.

    HTTP data is a stream of bytes (unsigned 8-bit int). The stream may be broken into chunks. Gather the chunks as they arrive, decoding them into UTF-8 strings. Concatenate the separate strings into a single string at the end. Edit the data however you want. Write the re-encoded data to the client which requested it. Close the stream.

    The filter tweaks in background.js needed to make it work completely:

    1. Change types from 'main_frame' to 'script' (assuming the script is not inline, which it probably isn't).
    2. Change urls to whatever they really are on a live site.
    3. Change (as necessary) the Find and Add strings, because a live web site probably doesn't serve the script looking like it does on github.

    Put in a good word for me on hackerrank.