0

I have a button on the extension popup window, when the user clicks on it I have to use the latestIncomingChatsNumber value to verify if a DOM container in the website have children but when I try to use the variable outside the chrome message listener I get an "undefined". I recogn that the problem is because of the async nature of the chrome.runtime.onMessage.addListener so everytime I try to use the variable in another event handler I get an undefined. Is there any way to use a variable coming from a message passing action in something like an onclick function ?

Content Script:

  let incomingChatsNumber = incomingChatsContainer.children().length;
  chrome.runtime.sendMessage({ incomingChatsNumber: incomingChatsNumber });

Background JS:

chrome.runtime.onMessage.addListener(function (message) {
  let incomingChatsNumber = message.incomingChatsNumber;
  chrome.runtime.sendMessage({ incomingChatsNumber: incomingChatsNumber });
});

Popup JS:

  let latestIncomingChatsNumber;
  chrome.runtime.onMessage.addListener((message) => {
    let incomingChatsNumber = message.incomingChatsNumber;
    latestIncomingChatsNumber = incomingChatsNumber;
    return true;
  });

  //#js-toggleSorting is the id of the button inside the popup window
  $("#js-toggleSorting").on("click", function () {
    //This console.log will get an "undefined"
    console.log(latestIncomingChatsNumber);
  }

UPDATE:

I have updated the content script to storage the value like this:

let latestIncomingChatsNumber = incomingChatsContainer.children().length;
  chrome.storage.local.set(
    { latestIncomingChatsNumber: latestIncomingChatsNumber },
    function () {
      //This works fine 
      console.log(`Value is set to + ${latestIncomingChatsNumber}`);
  }
);

This is how I'm trying to get the value from the popup.js, please note "frontEndDev.js" (or what I called the content script is being injected from the popup itself....¿bad practice?)

"use strict";
function save_button_state() {
  var buttonStateText = $("#js-toggleSorting").html();
  var buttonStateAttribute = $("#js-toggleSorting").attr("data-click-state");
  var sortMessage = $(".message").html();
  chrome.storage.local.set(
    {
      buttonStateText: buttonStateText,
      buttonStateAttribute: buttonStateAttribute,
      sortMessage: sortMessage,
    },
    function () {
      // console.log(
      //   `Saved State is: ${buttonStateText} and Saved Attribute is: ${buttonStateAttribute} and Saved Message is: ${sortMessage}`
      // );
    }
  );
}
function get_button_state() {
  chrome.storage.local.get(
    ["buttonStateText", "buttonStateAttribute", "sortMessage"],
    function (data) {
      $(".message").html(data.sortMessage);
      $("#js-toggleSorting").html(data.buttonStateText);
      $("#js-toggleSorting").attr(
        "data-click-state",
        data.buttonStateAttribute
      );
    }
  );
}
$(document).ready(() => {
  get_button_state();
  chrome.tabs.executeScript(null, {
    file: "libraries/jquery-3.5.1.min.js",
  });
  let sortFunction = function (goSortParam) {
    if (goSortParam) {
      chrome.tabs.executeScript(
        null,
        { code: "var goSortParam=true;" },
        function () {
          chrome.tabs.executeScript(null, { file: "scripts/frontEndDev.js" });
        }
      );
    } else {
      chrome.tabs.executeScript(
        null,
        { code: "var goSortParam=false;" },
        function () {
          chrome.tabs.executeScript(null, { file: "scripts/frontEndDev.js" });
        }
      );
    }
  };

  let latestIncomingChatsNumber;
  chrome.storage.local.get(["latestIncomingChatsNumber"], function (result) {
    console.log("Value currently is " + result.latestIncomingChatsNumber);
    latestIncomingChatsNumber = result.latestIncomingChatsNumber;
  });

  $("#js-toggleSorting").on("click", function () {
    $(".message").html("");
    console.log(latestIncomingChatsNumber);
    if (latestIncomingChatsNumber <= 0) {
      $(".message").html("<p>No Chats To Sort Yet</p>");
    } else {
      $(".message").html("<p>Sorting Chats</p>");
      if ($(this).attr("data-click-state") == 1) {
        $(this).attr("data-click-state", 0);
        $(".message").html("");
        $(this).html("SORT INCOMING CHATS");
        sortFunction(false);
      } else {
        $(this).attr("data-click-state", 1);
        $(this).html("STOP SORTING INCOMING CHATS");
        sortFunction(true);
      }
      save_button_state();
    }
  });
});

This is the manifest.json:

{
  "manifest_version": 2,
  "name": "Chrome Extension",
  "version": "1.0.0",
  "author": "Jorge Páez",
  "description": "Test",
  "short_name": "Extension Test",
  "background": {
    "scripts": ["libraries/jquery-3.5.1.min.js", "scripts/background.js"],
    "persistent": false
  },
  "content_scripts": [
    {
      "matches": ["https://someurl*"],
      "css": ["styles/custom.css"],
      "js": ["libraries/jquery-3.5.1.min.js", "scripts/content.js"]
    }
  ],
  "web_accessible_resources": ["images/*.*", "scripts/*.*", "libraries/*.*"],
  "icons": {
    "16": "icons/icon16.png",
    "32": "icons/icon32.png",
    "48": "icons/icon48.png",
    "128": "icons/icon128.png"
  },
  "permissions": ["activeTab", "declarativeContent", "storage"],
  "page_action": {
    "default_title": "Extension",
    "default_popup": "popup.html"
  },
  "update_url": "xyz"
}

But that console.log will still give me an undefined value. I can't say this enough, I'm a newbie when it comes to chrome extensions and stil trying to figure out the nuances of specially message passing and local storage, so thanks for your patience.

Lowtrux
  • 156
  • 2
  • 12
  • 41
  • The popup runs only when shown so it won't receive any messages when hidden. You can use chrome.storage.local to store the value in content script, then read it in the popup script, no need for messaging or background script! Alternatively your popup script can send a message to the content script directly using chrome.tabs.sendMessage and ask the current value of the variable (it'll be stored now in the content script). – wOxxOm Aug 20 '20 at 12:25
  • Thanks @wOxxOm I'm trying the first approach that you are suggesting but still having problems, if you have a min can you please check out my update on the original question ? many thanks ! – Lowtrux Aug 20 '20 at 13:14
  • Show us your manifest.json. – wOxxOm Aug 20 '20 at 13:22
  • @wOxxOm just posted on the update, many thanks ! – Lowtrux Aug 20 '20 at 13:25
  • 1
    Assuming you look in devtools of the popup (it's opened by right-clicking inside the popup, then selecting "inspect"), the storage code looks fine. So, I guess you open the popup before the content script runs, which happens when the page has finished loading by default. Also, make sure you're clicking "reload" icon in extension's card on chrome://extensions page after you edit content scripts, background scripts, manifest.json. – wOxxOm Aug 20 '20 at 13:30
  • Might be also [Why is my variable unaltered after I modify it inside of a function? - Asynchronous code reference](https://stackoverflow.com/q/23667086) – wOxxOm Aug 20 '20 at 13:32
  • I just realized one thing, and I'm assuming is related to your comment. frontEndDev.js is called from inside the popup with chrome.tabs.executeScript so I guess this is a 100% bad practice is causing me all of these drama right ? Will the right thing to do is to move all this code to an actual content script injected via the manifest ? I will update the original post. Thanks ! – Lowtrux Aug 20 '20 at 13:35

1 Answers1

0

Having in mind all the good advice from @wOxxOm:

  • The main problem here was that I was injecting the so called "content script" (frontEndDev.js) in the popup.js so I was indeed opening the popup before "frontEndDev" was running so before the page has finished loading, hence the undefined state of the variable coming from the website. Long story short I was trying to evaluate the variable before it existed.

So, to fix that problem I have moved the part where I save the value of the variable to a proper content script injected via the manifest, so in content.js:

//Getting the children from the DOM container on the website:
let latestIncomingChatsNumber = incomingChatsContainer.children().length;
let hasIncomingChats;
//If children then "true" if no children then "false"
if (latestIncomingChatsNumber > 0) {
  hasIncomingChats = true;
} else {
  hasIncomingChats = false;
}
//Saving the value to the local storage
chrome.storage.local.set(
  { hasIncomingChats: hasIncomingChats },
  function () {
    console.log(
      `Sending state of incoming chats to Extension set to: ${hasIncomingChats}`
    );
  }
);

Then in order to access the "hasIncomingChats" variable from the popup.js (and as suggested by @wOxxOm too, thanks !) I get the variable from the local storage:

  let hasIncomingChats;
  chrome.storage.local.get(["hasIncomingChats"], function (result) {
    console.log(
      `Zendesk, do incoming chats exist?  ${result.hasIncomingChats}`
    );
    hasIncomingChats = result.hasIncomingChats;
  });

And then I can use the variable inside the same popup.js like this:

$("#js-toggleSorting").on("click", function () {
    $(".message").html();
    //Using the variable "hasIncomingChats" coming from the local storage
    if (hasIncomingChats === false) {
      //Do stuff
    } else {
      //Do other Stuff
    }
  });
});

Now (and I know this is another question) this has a caveat. Since that website container is dynamic (html nodes came in and out all the time) the popup will never get the number of children dynamically so it will store the value of "hasIncomingChats" the first time the page is loaded and keep the same value until the page is reloaded even if the container at some point has no children.

Lowtrux
  • 156
  • 2
  • 12
  • 41
  • 1
    For dynamic updates you can use MutationObserver in the content script and chrome.storage.onChanged event in the popup. – wOxxOm Aug 21 '20 at 04:07