1

I use a chrome extension to fire two content scripts to inject css. If the user opens the page the contentscript-on.js loads (defined in my manifest.json):

manifest.json

{
    "name": "tools",
    "version": "1.1",
    "description": "tools",
    "browser_action": {
        "default_icon": "icon-on.png",
        "default_title": "tools"
    },
    "manifest_version": 2,
    "content_scripts": [
        {
            "matches": [ "*://*/*" ],
            "include_globs": [ "*://app.example.*/*" ],
            "js": ["jquery-1.11.0.min.js", "contentscript-on.js"]
        }
    ],
    "background": {
        "scripts": ["background.js"],
        "persistent": true
    },
    "permissions": [
        "storage",
        "https://*.app.example.de/*", "tabs", "webNavigation"
    ]   
}

background.js

function getToggle(callback) { // expects function(value){...}
  chrome.storage.local.get('toggle', function(data){
    if(data.toggle === undefined) {
      callback(true); // default value
    } else {
      callback(data.toggle);
    }
  });
}

function setToggle(value, callback){ // expects function(){...}
  chrome.storage.local.set({toggle : value}, function(){
    if(chrome.runtime.lastError) {
      throw Error(chrome.runtime.lastError);
    } else {
      callback();
    }
  });
}

chrome.browserAction.onClicked.addListener( function(tab) {
  getToggle(function(toggle){
    toggle = !toggle;
    setToggle(toggle, function(){
      if(toggle){
    //change the icon after pushed the icon to On
    chrome.browserAction.setIcon({path: "icon-on.png", tabId:tab.id});
    //start the content script to hide dashboard
    chrome.tabs.executeScript({file:"contentscript-on.js"});
  }
  else{

    //change the icon after pushed the icon to Off
    chrome.browserAction.setIcon({path: "icon-off.png", tabId:tab.id});
    //start the content script to hide dashboard
    chrome.tabs.executeScript({file:"contentscript-off.js"});
  }
    });
  });
});  

contentscript-on.js

$(document).ready(function() {

    chrome.storage.local.get('toggle', function(data) {
        if (data.toggle === false) {
            return;
        } else {
            // do some css inject
        }
    });

});

contentscript-off.js

$(document).ready(function() {
  // set css to original 
});

Everything works fine, but how can I save the "state" of the icon? If the user close the browser and open it again, the last used contentscript should load.

Thank you very much for your help.

Xan
  • 74,770
  • 16
  • 179
  • 206
mm1975
  • 1,583
  • 4
  • 30
  • 51

1 Answers1

4

You have two methods (at least), one is "old" and one is "new".

  1. Old: localStorage

    Your extension pages share a common localStorage object you can read/write, and it is persistent through browser restarts.

    Working with it is synchronous:

    var toggle;
    if(localStorage.toggle === undefined){
      localStorage.toggle = true;
    }
    toggle = localStorage.toggle;
    
    chrome.browserAction.onClicked.addListener( function(tab) {
      var toggle = !toggle;
      localStorage.toggle = toggle;
      /* The rest of your code; at this point toggle is saved */
    });
    

    It's simple to work with, but there are downsides: localStorage context is different for content scripts, so they need to communicate via Messaging to get the values from the background script; also, complications arise if the extension is used in Incognito mode.

  2. New: chrome.storage API

    To work with the new method, you need permission "storage" in the manifest (does not generate a warning).

    Also, unlike localStorage, working with it is asynchronous, i.e. you will need to use callbacks:

    function getToggle(callback) { // expects function(value){...}
      chrome.storage.local.get('toggle', function(data){
        if(data.toggle === undefined) {
          callback(true); // default value
        } else {
          callback(data.toggle);
        }
      });
    }
    
    function setToggle(value, callback){ // expects function(){...}
      chrome.storage.local.set({toggle : value}, function(){
        if(chrome.runtime.lastError) {
          throw Error(chrome.runtime.lastError);
        } else {
          callback();
        }
      });
    }
    
    chrome.browserAction.onClicked.addListener( function(tab) {
      getToggle(function(toggle){
        toggle = !toggle;
        setToggle(toggle, function(){
          /* The rest of your code; at this point toggle is saved */
        });
      });
    });
    

    Asynchronous code is a bit harder to work with, but you get some advantages. Namely, content scripts can use chrome.storage directly instead of communicating with the parent, you can watch for changes with onChanged, and you can use chrome.storage.sync instead of (or together with) chrome.storage.local to propagate changes to all browsers a user is logged into.

EDIT

I'm including a full solution, since the OP made a mistake of mixing per-tab state and global state.

contentscript.js

$(document).ready(function() {
  chrome.storage.local.get('toggle', function(data) {
    if (data.toggle === false) {
      return;
    } else {
      /* do some css inject */
    }
  });

  chrome.storage.onChanged.addListener(function(changes, areaName){
    if(areaName == "local" && changes.toggle) { 
      if(changes.toggle.newValue) {
        /* do some css inject */
      } else {
        /* set css to original */
      }
    }
  });
});

background.js:

    /* getToggle, setToggle as above */

    function setIcon(value){
      var path = (value)?"icon-on.png":"icon-off.png";
      chrome.browserAction.setIcon({path: path});
    }

    getToggle(setIcon); // Initial state

    chrome.browserAction.onClicked.addListener( function(tab) {
      getToggle(function(toggle){
        setToggle(!toggle, function(){
          setIcon(!toggle);
        });
      });
    });

This way, you only need one content script.

Xan
  • 74,770
  • 16
  • 179
  • 206
  • o.k. I´ve tried the hard way :-) I put the hole code in the background.js, right? At your comment /* The rest of .... I insert if(toggle){ //change the icon after pushed the icon to On... But now, I cannot switch between the two content scripts. – mm1975 Apr 28 '14 at 13:09
  • Hey Xan, I´ve made a mistake. Now it works, I can toggle and the last state was stored. Pls see the code above. But now I still have one problem: If I open the example-page, the icon was always set to icon-on.png on the start and do not consider the state. How can I fix this issue? Thank you very much for your help – mm1975 Apr 28 '14 at 14:42
  • @user3542250 Edited as requested. – Xan Apr 28 '14 at 14:54
  • @user3542250 You are, however, mixing global and per-tab state, this is **not** going to work as you want. You need to save the state per tab, or execute your on/off script in every tab. – Xan Apr 28 '14 at 15:03
  • @user3542250 Or better yet! You can have just one script that listens to `chrome.storage.local.onChanged`. All you need to do then is to flip the value in the background. – Xan Apr 28 '14 at 15:09
  • @user3542250 Included that in the answer. If you need per-tab state, you need to modify the logic. – Xan Apr 28 '14 at 15:28
  • Do these solutions work on browsers other than Chrome? For instance, Firefox can accept Manifest v2 extensions – Nuclear_Man_D Aug 12 '22 at 12:50