Search code examples
javascriptgoogle-chrome-extensionckeditorckeditor5zendesk

Multiple Messages Sending from Content script to Background script


I am developing a chrome extension for use in Zendesk and one of the features of the extension is to collect strings of two different formats for a given ticket and print them back out in a summary or "timeline" format. I have the feature working but there are some bugs that I cannot figure out.

I would like to have the function run once and only once when selecting the custom context menu option in the chrome extension. Currently it will run several times, equal to the number of right clicks that the user presses before selecting the context menu option. The message is sent from the content script (which runs in the page context) to the background script. The message initializes the onClicked listener for the context menu option which then sends an update back to the content script to run the function.

I have tried to do this the reverse way to see if it will fix the issue but since the content script runs in the page context to access the pages ckeditor instance, I can't figure out how to do it that way. It looks to me like the messages are queued every time the user presses right click, and then the queue empties out once the context menu item is pressed.

script.js (content script)

function generateTimeline(editingArea)
{
   chrome.runtime.sendMessage("nmkeddobbgmklaojjmkimpaffccencgn", {operation : "UPD" }, function(response) {
      if(response === "UPDATED")
      {
         const editorInstance = editingArea.ckeditorInstance;
         const eventContainer = getEventContainer(editingArea);
         let dateTimeRecords = getInternalCommentTextNodes(eventContainer);
         dateTimeRecords.sort(dateComparator);
         duplicateChecker(dateTimeRecords);
         
         editorInstance.model.change(writer => {
            for(record of dateTimeRecords)
            {
               const paragraph = writer.createElement('paragraph');
               writer.append(paragraph, editorInstance.model.document.getRoot());
               const textNode = writer.createText(record.tag + ' ' + record.comment);
               writer.append(textNode, paragraph);
            }
         });
      }
   });
}

document.addEventListener('mouseup', function(e) {
   
   if(this.activeElement.getAttribute("role") === "textbox" && this.activeElement.getAttribute("aria-label") === "Rich Text Editor. Editing area: main")
   {
      const editingArea = singletonEventListener(this.activeElement);
      // left click
      if(e.button === 0) 
      {
         createKeyPressListener(editingArea);
      }
      //right click
      if(e.button === 2)
      {
         generateTimeline(editingArea);
      }
   }
});

background.js

const timeLineGenerator = {
    "id": "timeLineGenerator",
    "title": "Generate Timeline",
    "contexts":["editable"]
};


chrome.runtime.onInstalled.addListener(() => {
    chrome.contextMenus.create(timeLineGenerator);
});

function manageMenuListener(request, sender, sendResponse)
{
    if(request.operation === "UPD")
    {
        if(chrome.contextMenus.onClicked.hasListener) 
        {
            chrome.contextMenus.onClicked.removeListener();
            console.log("listener removed");
        }
        
        chrome.contextMenus.onClicked.addListener(() => {
            return sendResponse("UPDATED");
        });
    } 
}
chrome.runtime.onMessageExternal.addListener(manageMenuListener);

manifest.json

{
    "manifest_version": 3,
    "name": "Zendesk Date Time Tool",
    "description": "Inserts a date time string when a user defined key is pressed",
    "permissions":[
        "scripting",
        "contextMenus",
        "activeTab",
        "tabs"
    ],
    "host_permissions":["<all_urls>"],
    "version": "1.0",
    "icons":{
        "16":"/icons/icon16.png",
        "48":"/icons/icon48.png",
        "128":"/icons/icon128.png"
    },
    "action": {
        "default_popup": "/src/index.html",
        "default_icon": "/icons/icon128.png"
    },
    "background":{
        "service_worker":"/src/background.js"
    },
    "content_scripts":[{
        "world": "MAIN",
        "matches":["https://.zendesk.com/*"],
        "js":["/src/script.js"],
        "run_at":"document_end"
    }],
    "externally_connectable": {
        "matches": ["https://.zendesk.com/*"]
      } 
}

EDIT:

So I moved the return sendResponse("UPDATED"); line to a function named timelineTrigger and returned it. Now I get the following error,

Error in event handler: TypeError: sendResponse is not a function at triggerTimeline

Here is the changed background.js code

const timeLineGenerator = {
    "id": "timeLineGenerator",
    "title": "Generate Timeline",
    "contexts":["editable"]
};


chrome.runtime.onInstalled.addListener(() => {
    chrome.contextMenus.create(timeLineGenerator);
});
function triggerTimeline(request, sender, sendResponse)
{
    return sendResponse("UPDATED");
}
chrome.runtime.onMessageExternal.addListener(function(request, sender, sendResponse){
    if(request.operation === "UPD")
    {
       
        if(chrome.contextMenus.onClicked.hasListener(triggerTimeline)) 
        {
            chrome.contextMenus.onClicked.removeListener(triggerTimeline);
            console.log("listener removed");
        }
        chrome.contextMenus.onClicked.addListener(triggerTimeline);
    } 
});

EDIT 2: global function variable

const timeLineGenerator = {
    "id": "timeLineGenerator",
    "title": "Generate Timeline",
    "contexts":["editable"]
};


chrome.runtime.onInstalled.addListener(() => {
    chrome.contextMenus.create(timeLineGenerator);
});

let triggerTimeline;

chrome.runtime.onMessageExternal.addListener(function(request, sender, sendResponse){
    
    triggerTimeline = function()
    {
        return sendResponse("UPDATED");
    }
    if(request.operation === "UPD")
    {
       
        if(chrome.contextMenus.onClicked.hasListener(triggerTimeline)) 
        {
            chrome.contextMenus.onClicked.removeListener(triggerTimeline);
            console.log("listener removed");
        }
        chrome.contextMenus.onClicked.addListener(triggerTimeline);
    } 
});

EDIT3: Change that fixed the issue. However, now it does not always fire.

const timeLineGenerator = {
    "id": "timeLineGenerator",
    "title": "Generate Timeline",
    "contexts":["editable"]
};


chrome.runtime.onInstalled.addListener(() => {
    chrome.contextMenus.create(timeLineGenerator);
});

let triggerTimeline;

chrome.runtime.onMessageExternal.addListener(function(request, sender, sendResponse){
    if(!triggerTimeline)
    {
        triggerTimeline = function()
        {
            return sendResponse("UPDATED");
        }
    }
    if(request.operation === "UPD")
    {
        if(chrome.contextMenus.onClicked.hasListener(triggerTimeline)) 
        {
            chrome.contextMenus.onClicked.removeListener(triggerTimeline);
            console.log("listener removed");
        }
        chrome.contextMenus.onClicked.addListener(triggerTimeline);
    } 
});


Solution

  • Answer from @wOxxOm

    const timeLineGenerator = {
        "id": "timeLineGenerator",
        "title": "Generate Timeline",
        "contexts":["editable"]
    };
    
    
    chrome.runtime.onInstalled.addListener(() => {
        chrome.contextMenus.create(timeLineGenerator);
    });
    
    let triggerTimeline;
    
    chrome.runtime.onMessageExternal.addListener(function(request, sender, sendResponse){
        if(!triggerTimeline)
        {
            triggerTimeline = function()
            {
                return sendResponse("UPDATED");
            }
        }
        if(request.operation === "UPD")
        {
            if(chrome.contextMenus.onClicked.hasListener(triggerTimeline)) 
            {
                chrome.contextMenus.onClicked.removeListener(triggerTimeline);
                console.log("listener removed");
            }
            chrome.contextMenus.onClicked.addListener(triggerTimeline);
        } 
    });