Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

chrome.runtime.sendMessage not working on the 1st click when running normally. it works while debugging though

I have a function in the context.js which loads a panel and sends a message to panel.js at the last. The panel.js function updates the ui on receiving that msg. But it is not working for the first click i.e. it just loads normal ui, not the one that is expected that is updated one after the msg is received. while debugging it works fine.

manifest.json

"background": {
    "scripts": ["background.js"],
    "persistent": false
  },
"content_scripts": [{
    "all_frames": false,
    "matches": ["<all_urls>"],
    "js":["context.js"]
  }],
"permissions": ["activeTab","<all_urls>", "storage","tabs"],
  "web_accessible_resources": 
    "panel.html",
    "panel.js"
  ]

context.js - code


fillUI (){
    var iframeNode = document.createElement('iframe');
    iframeNode.id = "panel"
    iframeNode.style.height = "100%";
    iframeNode.style.width = "400px";
    iframeNode.style.position = "fixed";
    iframeNode.style.top = "0px";
    iframeNode.style.left = "0px";
    iframeNode.style.zIndex = "9000000000000000000";
    iframeNode.frameBorder = "none"; 
    iframeNode.src = chrome.extension.getURL("panel.html")
    document.body.appendChild(iframeNode);
    var dataForUI = "some string data"
    chrome.runtime.sendMessage({action: "update UI", results: dataForUI}, 
        (response)=> {
          console.log(response.message)
        })
     }
}

panel.js - code

var handleRequest = function(request, sender, cb) {
  console.log(request.results)
  if (request.action === 'update Not UI') {
    //do something
  } else if (request.action === 'update UI') {
    document.getElementById("displayContent").value = request.results
  }
};

chrome.runtime.onMessage.addListener(handleRequest);

background.js

chrome.runtime.onMessage.addListener((request,sender,sendResponse) => {
    chrome.tabs.sendMessage(sender.tab.id,request,function(response){
        console.log(response)`
    });
});

panel.html

<!DOCTYPE html>

<html>

<head>
  <meta charset="utf-8">
  <link rel="stylesheet" href="panel.css" />
</head>

<body>
  <textarea id="displayContent" rows="10" cols="40"></textarea>
</body>
</html>

Any suggestions on what I am doing wrong or what can I do instead?

like image 898
Utkarsh Shrivastava Avatar asked Dec 09 '25 00:12

Utkarsh Shrivastava


1 Answers

An iframe with a real URL loads asynchronously so its code runs after the embedding code finishes - hence, your message is sent too early and is lost. The URL in your case points to an extension resource so it's a real URL. For reference, a synchronously loading iframe would have a dummy URL e.g. no src at all (or an empty string) or it would be something like about:blank or javascript:/*some code here*/, possibly srcdoc as well.

Solution 1: send a message in iframe's onload event

Possible disadvantage: all extension frames in all tabs will receive it, including the background script and any other open extension pages such the popup, options, if they also have an onMessage listener.

iframeNode.onload = () => {
  chrome.runtime.sendMessage('foo', res => { console.log(res); });
};
document.body.appendChild(iframeNode);



Solution 2: let iframe send a message to its embedder

Possible disadvantage: wrong data may be sent in case you add several such extension frames in one tab and for example the 2nd one loads earlier than the 1st one due to a bug or an optimization in the browser - in this case you may have to use direct DOM messaging (solution 3).

iframe script (panel.js):

chrome.tabs.getCurrent(ownTab => {
  chrome.tabs.sendMessage(ownTab.id, 'getData', data => {
    console.log('frame got data');
    // process data here
  });
});

content script (context.js):

document.body.appendChild(iframeNode);
chrome.runtime.onMessage.addListener(
  function onMessage(msg, sender, sendResponse) {
    if (msg === 'getData') {
      chrome.runtime.onMessage.removeListener(onMessage)
      sendResponse({ action: 'update UI', results: 'foo' });
    }
  });



Solution 3: direct messaging via postMessage

Use in case of multiple extension frames in one tab.

Disadvantage: no way to tell if the message was forged by the page or by another extension's content script.

The iframe script declares a one-time listener for message event:

window.addEventListener('message', function onMessage(e) {
  if (typeof e.data === 'string' && e.data.startsWith(chrome.runtime.id)) {
    window.removeEventListener('message', onMessage);
    const data = JSON.parse(e.data.slice(chrome.runtime.id.length));
    // process data here
  }
});

Then, additionally, use one of the following:

  • if content script is the initiator

    iframeNode.onload = () => {
      iframeNode.contentWindow.postMessage(
        chrome.runtime.id + JSON.stringify({foo: 'data'}), '*');
    };
    document.body.appendChild(iframeNode);
    
  • if iframe is the initiator

    iframe script:

    parent.postMessage('getData', '*');
    

    content script:

    document.body.appendChild(iframeNode);
    window.addEventListener('message', function onMessage(e) {
      if (e.source === iframeNode) {
        window.removeEventListener('message', onMessage);
        e.source.postMessage(chrome.runtime.id + JSON.stringify({foo: 'data'}), '*');
      }
    });
    
like image 146
wOxxOm Avatar answered Dec 11 '25 14:12

wOxxOm



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!