Can I make a browser extension return a custom response for a web request?
P

1

6

In a Firefox or Chrome(1) extension (using WebExtensions), is it possible to interrupt a request and return an alternate response instead, preventing the network request? What I'd like to do is store some html data (dynamically) using the storage API, and then return that html when the browser attempts to send off a specific request.

webRequest.onBeforeRequest seems to only support cancelling the request or returning a redirect. Is there a way to redirect to something inside the extension that returns the data? Or a way to craft and return a response directly?

(1) The Chrome docs for webRequest seem to reflect the same reality as the Firefox docs, and it seems that Firefox deliberately adopted much of WebExtensions to be consistent with Chrome.

Pint answered 25/11, 2017 at 3:51 Comment(2)
It looks like redirecting to an extension page throws a browser security error. I'm not sure if it's possible to get around that, but it does seem that doing so will result in an ugly extension url, which isn't what I want at all.Pint
The more I investigate, the more it seems that this just isn't one of the engineered use cases, and so is impossible with webRequest.Pint
M
0

I'm not sure that the original asker still cares about a question from 7 years ago, but it is possible to redirect (or cancel) a web request and return HTML from browser.storage, preventing the network request.

manifest.json

...
  "background": {
    "scripts": [
      "background.js"
    ]
  },
  "permissions": [
    "storage",
    "webRequest",
    "webRequestBlocking",
    "*://*/*"
  ],
  "web_accessible_resources": [
    "content.htm"
  ]
...

background.js

( function () {
  'use strict';

  var
    baseURL = browser.runtime.getURL( 'content.htm' ) + '?q=';

  // filter requests by target
  // redirect into extension if hit
  function onRequest( e ) {
    var
      rc = {};

    // in practice, compare e.url to more than one value
    if ( e.url === 'https://www.youtube.com/watch?v=9xp1XWmJ_Wo' ) {
      rc = { redirectUrl: baseURL + encodeURIComponent( e.url ) };
    }
    return rc;
  }
  browser.webRequest.onBeforeRequest.addListener(
    onRequest, {
      types: [ 'main_frame' ],
      urls: [ '*://*/*' ]
    }, [
      'blocking'
    ]
  );
} () );

content.htm

<!DOCTYPE html><html lang="en"><head>
  <title>Placeholder</title>
  <script src="content.js"></script>
</head><body>
</body></html>

content.js

( function () {
  'use strict';

  var
    url = decodeURIComponent( location.search.replace( /^\?q=/, '' ) );

  // storage is keyed on the original url
  browser.storage.local.get( url ).then( function ( o ) {
    var
      html = o[ url ],
      newDOM;

    if ( html ) {
      document.documentElement.textContent = '';
      newDOM = new DOMParser().parseFromString( html, 'text/html' );
      newDOM = newDOM.firstElementChild; // <html>
      while ( newDOM.children.length ) {
        document.documentElement.appendChild( newDOM.firstElementChild );
      }
    } else {
      document.body.textContent =
        'No content found for: ' + url;
    }
  }, function ( err ) {
    document.body.textContent =
      'Error fetching storage: ' + err.name + ' ' + err.message;
    console.error( err );
  } );
} () );

In background.js, intercept the web requests and compare the URL to known URLs. (This example code uses only one URL, a particular YouTube video.) If there's a match, redirect the web request into the extension, using the original URL as a query. The HTML document in the extension uses the query to look in storage for replacement HTML, which it parses and transfers into the DOM.

For this example, set the extension storage (e.g., in the debug console) for the URL and replacement HTML with:

browser.storage.local.set( { 'https://www.youtube.com/watch?v=9xp1XWmJ_Wo':
  '<head><title>Stack Overflow Answer</title></head><body><p>This is not a YouTube video.</p></body>' } )

It's everything that would go inside the <html> tags, without the <html> tags. The Parser puts them in, anyway, but the code has to throw them away, because document.documentElement cannot be replaced.

Then go to https://www.youtube.com/watch?v=9xp1XWmJ_Wo. The replacement HTML is displayed, instead.

The code above only works for HTML in a main frame, but it proves the concept. It relies on the fact that JavaScript in a page served from the extension runs with elevated privilege, so it has access to browser.storage.

Metencephalon answered 22/10 at 14:13 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.