I'm trying to use a shared vue.js state in a web extension.
The state is stored in the background script's DOM and rendered in a popup page.
My first attempt was to use a simple store without vuex:
var store = {
count: 0
};
browser.runtime.getBackgroundPage().then(bg => {
var store = bg.store;
var vue = new Vue({
el: '#app',
data: {
state: store
},
})
})
<div id="app">
<p>{{ state.count }}</p>
<p>
<button @click="state.count++">+</button>
</p>
</div>
<script src="vue.js"></script>
<script src="popup.js"></script>
This works the first time the popup is opened (you can increment the counter and the value is updated) but when the popup is opened a second time, rendering fails with [Vue warn]: Error in render: "TypeError: can't access dead object"
. This seems to be caused by the fact that vue.js instance from the first popup modified the store
by setting its own getter/setters, which have now been deleted since the first popup was closed, making the shared state unusable. This seems to be unavoidable, so I decided I'd give vuex a try.
var store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment: state => state.count++,
}
})
browser.runtime.getBackgroundPage().then(bg => {
var store = bg.store;
var vue = new Vue({
el: '#app',
computed: {
count () {
return store.state.count
}
},
methods: {
increment () {
store.commit('increment')
},
}
});
})
<div id="app">
<p>{{ count }}</p>
<p>
<button @click="increment">+</button>
</p>
</div>
<script src="vue.js"></script>
<script src="popup.js"></script>
Unfortunately, this doesn't work either. You can open the popup and see the current counter value, but incrementing it doesn't update the view (you need to reopen the popup to see the new value).
When taking the same code but with the store declared in popup.js, the code works as intended so this should work, but for some reason it doesn't
My questions:
This does not work because your vue instance is not the same in background and popup. So, when you get the state from background, the watchers on the state makes react the Vue inside the background page, not the view in popup. You can achieve that by using the same store in background and popup and synchronizing the state between both. To synchronize the state, you can use the excellent plugin vuex-shared-mutations which uses localStorage to propagate mutations through different instances of a store. In your store, add
import createMutationsSharer from 'vuex-shared-mutations'
//...
export default new Vuex.Store({
//...
plugins: [createMutationsSharer({ predicate: ['increment'] })],
});
Now your popup is reacting to the button and your background is incremented. If you reopen the popup, count is 0 because you created a new store. You now need to load the initial state when the popup is initialized :
export default new Vuex.Store({
state: {
count: 0
},
mutations: {
increment: state => state.count++,
setCount (state, count) {
state.count = count
},
},
plugins: [createMutationsSharer({ predicate: ['increment'] })],
actions: {
getCount ({ commit }) {
browser.runtime.sendMessage({type: "storeinit", key: "count"}).then(count => {
commit('setCount', count)
})
}
}
});
import store from './store';
browser.runtime.onMessage.addListener((message, sender) => {
if (message.type === 'storeinit') {
return Promise.resolve(store.state[message.key]);
}
});
import store from '../store';
var vue = new Vue({
//...
created () {
this.$store.dispatch('getCount')
}
});
These difficulties are not vue related, react users have use a proxy to propagate state in browser extension : react-chrome-redux
Sorry for the delay, I create a node module for it:
https://github.com/MitsuhaKitsune/vuex-webextensions
The module use the webextensions messaging API for sync all store instances on the webextension.
The install are like another vuex plugins, you can check it on the Readme.
If you have any question or feedback just tell me here or on github issues.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With