Monkey-patching Vue.js from a Browser Addon

Posted on September 23, 2020

Once I found myself in a situation where I have to alter the behavior of a Vue.js webpage that is not written by me. I managed to do that using a browser addon. The same method should apply to userscript manager (such as greasemonkey) as well.

Explore Vue’s internals from console

For a reactive framework like Vue.js, manipulating DOM externally doesn’t do anything as DOM is merely the reflective display of the model. You will have to modify the underlaying javascript object to fulfill the goal. Fortunately, Vue’s thin layer of abstraction makes this kinds of hacking easy.

Launch a browser console and have a try:

// suppose '#app' is where you mount Vue to
let vue = document.getElementById('app').__vue__;
// access data
vue.$data.someData = 12345;
// access children components
let fstChild = vue.$children[0];
// event handling
vue.$on(event, fn);
// access component methods
fstChild.methodName(arg1, arg2);

You can use most of these “$..”s as if you are writing vue. And component methods can be called directly as well. You can now do many things, modifying data, call component methods, accessing vuex storage and etc.

If you want to go more low-level, you can have a look at properties and methods starting with underscore. Manipulating these requires having a deeper understanding of vue’s internals. If you wants to dig deep, you can have a look at the “Read Vue Source Code” series.

Code injection

Now you know how to manipulate the vue object. But you have no direct access to it because a browser extension’s content script lives in an “isolated world”.

What you can do is to add a <script> element via your content script. Upon creation, code would be executed in the page’s context. You can then remove the element.

var script = document.createElement('script');
script.textContent =
  'let vue = document.getElementById(\'app\');vue.$data.someData=12345;';
(document.head||document.documentElement).appendChild(script);
script.remove();

Be aware the execution of injected code will be asynchronous (and usually parallel) with execution of content scripts.

Returning values and error handling from page’s context

You can inject any code by creating <script> elements. But to communicate back to content scripts, you will need to utilize DOM events.

Report back from injected scripts:

/* get someData from vue model */
window.dispatchEvent(new CustomEvent('myEvent', {detail: someData}));

Listen to the custom event from content scripts:

window.addEventListener('myEvent', function (e) { console.log(e.detail) }, true);

That’s it. Enjoy!