Grafting Local Static Resources onto Production #

tl;dr: Using the webRequest Chrome extension API it is possible to “graft” development/localhost JavaScript and CSS assets on a production web service, thus allowing rapid debugging iteration against real production data sets. Demo site and extension.

During the summer of 2015 I was investigating an annoying bug in Quip where our message list would not stay “bottom-anchored” in some circumstances¹. Unfortunately I was only able to trigger it on our live production site, not on my local development setup. Though Chrome’s developer tools are quite nice, I did not have the necessary ability to rapidly iterate on the code in order to further investigate the bug. I had in the past pushed alternate builds our staging site to debug such production-only issues, but that would still take several minutes to see the results of every change.

My next thought was that I could instead try to reproduce the bug in our soon-to-be-released desktop app. The app can use local (minimally processed) JavaScript while running against production data. Unfortunately the bug did not manifest itself in our Mac app. I chalked this up to rendering engine differences (the bug was only visible in Chrome, and our Mac app uses a WebKit-backed WebView). I then tried our Windows app (which uses the same rendering engine as Chrome via the Chromium Embedded Framework), but it didn’t happen there it either. I was forced to conclude that the bug was due to some specific behavior in our website when running against production data, not something in the shared React-based UI.

As I was wishing for a way to use JavaScript and CSS from my laptop with production data (for security reasons my local Quip server cannot connect to the production databases) I remembered that Gmail used to have exactly such a mode. As I recall it, you could start a local CaribouGmail server, go to your (work) Gmail instance and append a special URL parameter that would cause the JavaScript from the local server to be requested instead². With most of Gmail’s behavior being driven by the client-side JavaScript (with the server serving as an API endpoint) this meant that it was possible to try out pretty complex changes on your own data without having to “deploy” them.

I considered adding this mode to Quip, but that seemed scary, security-wise, since it was effectively intentional cross-site scripting. It also would have meant waiting for the next day’s production push (and I wanted to solve the problem as soon as possible). However, it then occurred to me that I didn’t actually need to have the server change it behavior; I could instead write a Chrome extension which (via the webRequest API) would “graft” the local JavaScript and CSS files from my local server onto the production site when loaded in my browser.

I had hoped that the extension could modify the HTML that is initially served and replace the JavaScript and CSS URLs, but it turns out the webRequest API cannot modify the HTTP response body. What did work was to intercept the JavaScript and CSS requests before they were sent to our CDN and redirect them to paths on my local server. Chrome would initially flag this as being insecure (since we use HTTPS in production, and the redirected URLs were over plain HTTP), but it is possible to convince it to load the resources anyway.

Once I had the necessary tooling and ability to iterate quickly, fixing the bug that prompted all this was pretty straightforward (it was caused by the “mount point” system that we used to incrementally migrate our website to React, but that’s a whole other blog post). Since then it’s come in handy in debugging other hard-to-recreate problems, and for measuring JavaScript performance against more realistic data. It did briefly break when we added a Content Security Policy (CSP) — since we were loading scripts from an unknown domain the browser was correctly blocking the “grafted” response. However, the webRequest API also allows the extension to edit the response headers, thus it was straightforward to have it intercept the main HTML page request and strip the CSP header.

The extension that I wrote to accomplish this is very barebones and hardcodes a bunch of Quip-specific logic and URLs, thus is not easily shared. However, I have recreated a simplified version of it and put it in my web experiments repository. There is also a demo site that it can be applied to.

  1. Yet more developer time spent faking something that should be a built-in capability, further confirming Bret’s observation.
  2. For a bit more history: back in 2005 I was using Greasemonkey to hack Gmail left and right. When I talked to the Gmail team about this approach (versus working in my 20% time to add those features to Gmail directly) I rationalized it as “Greasemonkey lets me do UI experiments on the real email in my account with minimal lag, instead of needing to wait for code reviews and production pushes.” Darick Tong (a Gmail engineer) took this feedback to heart and added the custom JavaScript mode. Unfortunately by that point I had mostly moved on from Gmail hacking (Reader was keeping me plenty busy, JavaScript-wise), so I never got to actually use it.

Post a Comment