I've been working on a Chrome extension, coquette-inspect, which adds a debugger for coquette.js games to the Chrome Dev Tools. Chrome has had dev tools extension support for a couple years now, and I've used several awesome extensions in the past (including Ember Inspector and React Dev Tools), so I figured it'd be a well-documented field.
So, you can imagine my surprise when I found that a really common use case was not only poorly-documented in the Dev Tools extension docs, but not documented anywhere, as far as I can tell.
Chrome Dev Tools extensions are a bit more complicated than "traditional" Chrome extensions. There are a few parts involved:
I needed to inject the agent into the context of the inspected page, which would then broadcast information about the game to the script that ran in the context of the Dev Tools panel.
Injecting a script is relatively easy. There's a nice API that can do this,
// inject into inspectedWindow chrome.devtools.inspectedWindow.eval(script);
Now, we've got our JS running in the context of the page, but we need to communicate with it.
The Chrome Dev Tools extension guide has a code sample seems useful. It tells you how to communicate between a dev tools extension and a content script. A content script is similar to an injected script, but while it has access to the DOM of the inspected window, it doesn't have access to the JS context, so we couldn't use it instead of the
inspectedWindow.eval() method. Still, they were similar enough that I naively assumed the solution for content scripts would work.
To my surprise, it, uh, didn't. Here is the repo I made demonstrating this. When the injected script uses Chrome's
chrome.runtime.sendMessage method, it's not broadcast to the content script as it should be (and would be, with a content script).
After two or three hours of cursing and trying different permutations of
sendMessage, I, as usual, found salvation in someone else's solution.
Backbone Debugger had a similar problem to mine, and its solution is really ingenious. Rather than directly trying to send a message from the injected script to the background script, it instead adds an intermediary content script. Now, instead of calling
chrome.runtime.sendMessage in the injected script, it calls
window.postMessage. The message broadcast here is then picked up by the content script's event handler, which then broadcasts it to the background script.
The final data flow ends up looking like:
injected agent | | window.postMessage() V content script | | chrome.runtime.sendMessage() V background script | | port.postMessage() V dev tools panel script
Here is the fixed version of the previous example code, and the diff of what changes were required.