Concerns with shared vuex state in a web-extension due to dead object complications

I've been experimenting with using a shared vue.js state within a web extension.

The state is actually stored in the background script's DOM and then rendered in a popup page.

Initial Strategy

Initially, I tried using a simple store without vuex:

background.js

var store = {
  count: 0
};

popup.js

browser.runtime.getBackgroundPage().then(bg => {
    var store = bg.store;

    var vue = new Vue({
        el: '#app',
        data: {
            state: store
        },
    })
})

popup.html

<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 method worked successfully upon first opening the popup. The counter could be incremented and the value would update accordingly. However, on subsequent openings of the popup, rendering failed with an error message stating

[Vue warn]: Error in render: "TypeError: can't access dead object"
. This appeared to be due to the fact that the initial popup instance of vue.js had modified the `store` by adding its own getter/setters, which were no longer available when the popup was reopened, rendering the shared state useless. Since this issue seemed inevitable, I decided it was time to try using vuex.

Second Attempt

background.js

var store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increment: state => state.count++,
  }
})

popup.js

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')
            },
        }
    });
})

popup.html

<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 approach also encountered issues. While you could see the current counter value upon opening the popup, incrementing it didn't automatically update the view (you needed to reopen the popup to see the updated value).

When moving the same code but declaring the store directly in popup.js, everything functioned as expected. It should have worked with the shared store, but for some reason it did not.

My questions:

  • Is vue.js simply unable to handle this specific use case?
  • If so, would other frameworks such as Angular or React be better suited for this scenario?

Answer №1

The issue here stems from the fact that your Vue instance in the background and popup pages are not synchronized. This results in the state being manipulated by watchers in the background, rather than reflecting changes in the popup view. To overcome this, you can use the same store in both the background and popup pages, ensuring that the state remains consistent across all instances. One way to achieve this synchronization is by utilizing the helpful plugin vuex-shared-mutations, which leverages localStorage to propagate mutations throughout different store instances.

import createMutationsSharer from 'vuex-shared-mutations'
//...
export default new Vuex.Store({
   //...
   plugins: [createMutationsSharer({ predicate: ['increment'] })],
});

With this setup, your popup will respond to button clicks, incrementing the count in the background. However, if you reopen the popup, the count resets to 0 due to a new store instance. To address this, ensure the initial state is loaded when the popup initializes:

store.js :

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)
      })
    }
  }
});

background.js

import store from './store';
browser.runtime.onMessage.addListener((message, sender) => {
    if (message.type === 'storeinit') {
        return Promise.resolve(store.state[message.key]);
    }
});

popup.js

import store from '../store';
var vue = new Vue({
  //...
  created () {
    this.$store.dispatch('getCount')
  }
});

It's worth noting that similar challenges exist for React users working on browser extensions, requiring techniques like using a proxy to manage state propagation: react-chrome-redux

Answer №2

Apologies for the delay, I have developed a node module specifically for this purpose:

https://github.com/MitsuhaKitsune/vuex-webextensions

This module utilizes the webextensions messaging API to synchronize all store instances within the webextension.

The installation process is similar to other vuex plugins - you can find detailed instructions in the Readme file.

If you have any questions or feedback, feel free to reach out to me here or by creating an issue on GitHub.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Incorporate a PHP-generated array into JavaScript

I'm currently working on implementing a JavaScript slideshow on my index page. Rather than having a static array, I'd like to dynamically build the array using PHP and then include it in the script. However, I'm not sure how to go about this ...

Guide to selecting and clicking multiple elements with a specific class using jQuery

On my html page, I have the following elements: $(".pv-profile-section__card-action-bar").length 3 I want to click on all of these elements instead of just the first one. Currently, I am achieving this with the code: $(".pv-profile-section__card-action- ...

What are the reasons behind the issues encountered when enabling "Optimization" feature in Angular that affect the proper rendering of PrimeNg elements?

Angular Version : 9.x Primeng Version : 9.x We've encountered an issue with PrimeNg elements not rendering correctly in our dev/prod environments, although they work fine in the local environment. After some investigation, we found that the culprit ...

Tips for preserving line breaks when sending a message through the mail

Hi, I'm currently facing an issue: I am trying to send text from a textarea using POST to a PHP script that will write it to a file and display it on the website. However, when I do this, the line breaks disappear and the displayed text ends up lookin ...

Leveraging javascript to extract data from several input fields and dynamically incorporate them into a table

I'm currently in the process of developing a script that extracts field values and organizes them into a table for verification purposes. The challenge I face is that users have the ability to add multiple fields (up to 5), each with its own unique id ...

Embedding JavaScript directly into text and executing it once the page has finished loading

Currently, I am dealing with an ASPx/C# page where clicking the Save button triggers a post back to the server. If the data in the controls already exists in the checked data stores, it is supposed to trigger an alert message. Previously, the developer uti ...

The swipe function of Hammer.js is unable to detect gestures on a rotated iframe

After creating a rotated iframe on the page using CSS transforms, I attempted to implement swipe events within the iframe. body.rotate iframe { transform: rotate(90deg); transform-origin: top right; position: absolute; t ...

Firebase 9 - Creating a New Document Reference

Hey everyone, I need some help converting this code to modular firebase 9: fb8: const userRef = db.collection('Users').doc(); to fb9: const userRef = doc(db, 'Users'); But when I try to do that, I keep getting this error message: Fir ...

How can I filter an array to retain only the initial 5 characters of every element?

I need help using the .map function to transform this array so that only the first 5 characters are displayed, like "01:00", "02:00"? This is the array: ["01:00:00 AM", "02:00:00 AM", "03:00:00 AM", "04:00:00 AM", "05:00:00 AM", "06:00:00 AM", "07:00:00 ...

Guide to Generating Downloadable Links for JPG Images Stored in MongoDB Using Node.js

I have successfully uploaded an image to MongoDB as a Buffer. Now, I need to figure out how to display this image in my React Native app using a URL, for example: http://localhost:8080/fullImg.jpeg How can I achieve this? Below is the MongoDB Schema I am ...

What is the best way to transmit UTF-8 Arrow &#8594; from typescript to html and showcase it effectively?

When working with HTML, I often find myself creating a div with a tooltip that is connected to a function. Here's an example of what I typically use: <div *ngFor="let user of users"> <div class="myClass" [matToolt ...

Insert half a million records into a database table using JavaScript seamlessly without causing the webpage to crash

I am facing an issue with my report system where running a single report query results in over 500,000 rows being returned. The process of retrieving the data via AJAX takes some time, but the real problem arises when the browser freezes while adding the H ...

Summernote information embedded with HTML elements

I just started using the summernote text editor and I'm trying to figure out how to extract the content from the textarea without all the HTML tags. This is what I have in my code: <textarea class="summernote" id="summernote" ng-model="blog.c ...

The animated loading image is taking an eternity to actually load

My website is loaded with heavy front-end features and I'm using a loading gif to help with the loading process. However, I've noticed that in Safari, there is a delay before the gif loads after the background does, which takes away from the inte ...

Send multiple forms simultaneously using a single button in JSP

I am facing an issue while trying to submit two forms using just one button. The value of the first form input appears to be null. test.jsp <body> <script> function submitAllForms(){ console.lo ...

Update the network name in the Axios request

Currently, I am utilizing Axios for handling both GET and POST requests. One question that has been on my mind is how to modify the network name: At present, the name serves as the parameter accompanying each request. However, I ponder whether it's f ...

Having trouble with JavaScript not working when clicking an image and toggling a div?

Why isn't the onclick image and toggle div functionality working with JavaScript? I made the change from: <input type="button" id="Showdiv1" name="Showdiv1" value="Show Div 1" onclick="showDiv('div1')" /> to: <img src="https://d ...

Exploring the capabilities of Vue combined with Typescript and Audio Worklets

I've encountered a challenge with configuring Vue to compile audio worklets. Specifically, I am facing a similar issue to this problem that has already been resolved, but using Typescript instead of JavaScript. My approach was to include the ts-loader ...

What causes special characters to be replaced when using the 'require' function in NodeJS?

I am a beginner in JavaScript and NodeJS, so please be patient with me if this issue seems obvious. The file I have outsourced is a simple config file. Here is an abbreviated version of it: config.js: var config = {}; config.Web = {}; config.Web.Title ...

Executing a function when ng-init is set to true in AngularJS

Greetings! I am working with the following piece of code: html: <div class="portlet-titlebar" ng-click="toggleCollapsed(portlet, $event)" ng-class="{current: hover}" ng-init="hover = false" ng-mouseenter="hover = hoverIn()" ng-mouseleave="ho ...