Messages intended for one script in the background context are received by all
Asked Answered
O

1

0

I have a WebExtension with the following structure:

webextension [directory]
   - background.js
   - page1.html
   - page1.js
   - page2.html
   - page2.js

The background.js listens for errors. If any, it instructs its callback function to update the tab with an HTML page, page2.js that contains a button.

The HTML script page2.js starts by sending a message to the background.js and background.js replies to page2.js. This part works fine.

Then, as you see in the code, page2.html contains a button if clicked, it will execute code in the callback function. Then, it will call refreshIndexPage which should send a message to page1.js which is attached to page1.html.

The Problem: When I added the messaging APIs between page2.js and page1.js in the refreshIndexPage, the message from page2 gets sent to the background.js. I do not want this to happen. As I will show in the output, I get: In background.js: received: undefined

Questions:

  1. How can you send a message from page2.js to page1.js without making background.js that also listens for a message from page2.js gets confused and receives the message that is not meant for it? How to specify that this message it is from page2.js to page1.js?

Here is the output after adding messages from page2.js to page1.js. Console output

inside refreshIndexPage
In background.js: received: undefined
inside handleRefreshResponse
  1. page1.html:
<html>
<head>
  <meta charset="UTF-8">
</head>
<body>
  <h1>Pag1.html</h1>
  <input type="button" id="page1-button" value="click"></input>
  <script src="page1.js"></script>
</body>
</html>
  1. page1.js:
function displayAll() {
  console.log("inside display all");
} //end displayAll

//should listen to messages from pag2.js
browser.runtime.onMessage.addListener(handleRefreshMessage);

function handleRefreshMessage(request, sender, sendResponse) {
  console.log("In page1.js: message received" + request.refreshRequest);
  sendResponse("response from page1.js to page2.js");
}
  1. background.js:
console.log("inside background");

//add a listener for the toolbar icon. If clicked, open page1.html
browser.browserAction.onClicked.addListener((tab) => {
  // disable the active tab
  var creating = browser.tabs.create({"url": "page1.html"});
  creating.then((tab) => {
    browser.browserAction.setIcon({tabId: tab.id, path: "icons/red-64.png"});
  });//end creating.then
});//end addListener

var target = "<all_urls>";
function log(responseDetails) {
  console.log("inside response details");
  errorTab = responseDetails.tabId;
  if(true) {
    console.log("inside if");
    browser.tabs.update(responseDetails.tabId,{url: "page2.html"});

    //this message to wait request from page2.js
    browser.runtime.onMessage.addListener(handleMessage);
  } //end if
}//end log

function handleMessage(request, sender, sendResponse) {
  console.log("In background.js: received: " + request.scriptRequest);
  sendResponse({errorTab: errorTab});
}

var errorListening = browser.webRequest.onErrorOccurred.addListener(log, {
    urls: [target],
    types: ["main_frame"]
});
  1. page2.html:
<html>
<head>
  <meta charset="UTF-8">
</head>
<body>
  <h1>Pag2.html</h1>
  <input type="button" id="page2-button" value="click"></input>
  <script src="page2.js"></script>
</body>
</html>
  1. page2.js:
/*self-calling function conatins sendMessage to background script*/
(function notifyBackgroundPage() {
    var sending = browser.runtime.sendMessage({
    scriptRequest: "From pag2.js. request for data"
  });
  sending.then(handleResponse);
})();

function handleResponse(message) {
  console.log(`In page2.js: data from background is: ${message.errorTab}`);
} //handleResponse

function myFunction() {
  refreshIndexPage();//end .then
}//end myFunction

//refreshIndexPage should send message to page1.js only when the button is clicked.
function refreshIndexPage() {
  console.log("inside refreshIndexPage");
  var sending = browser.runtime.sendMessage({
    refreshRequest: "From page2.js"
  });
  sending.then(handleRefreshResponse);
}//end refreshIndex

function handleRefreshResponse() {
  console.log("inside handleRefreshResponse");
}//end handleRefreshResponse

var page2Button = document.getElementById("page2-button");
page2Button.addEventListener('click', myFunction);
Obtect answered 28/6, 2017 at 13:32 Comment(4)
I'll look at it. It may not be right this second, but today. Just FYI: Pinging (including a @[username]) only works for users who have already interacted with the post (i.e. the author, an editor, has posted comments, and some other things). Thus, pinging me like you did here doesn't actually send me a message. Now, once I've posted this comment, pinging me here will send me a message.Schoolteacher
Please use consistent indenting and formatting (e.g. placement of { and }) throughout the code that you place in a question. There aren't any requirements that you use one format over another, just be consistent, at least with all the code you have written within a single question. Doing so makes it significantly easier to read the code. If you desire, there are a variety of programs which will format JavaScript/HTML/CSS for you.Schoolteacher
Please separate this into two Questions, rather than having two issues in one Question. While you can sometimes ask about more than one issue in a Question, it should only be done if the issues are tightly coupled. SO's goal is to have Q's & A's which have the possibility to be applicable to multiple situations. Covering multiple not tightly related issues in the same Q & A makes them less applicable to multiple situations. In addition, to address the issue you have in your #2, we need a minimal reproducible example that actually does duplicate the problem, otherwise we have to be guessing as to what the issue is.Schoolteacher
I went ahead and removed your question #2 as you have explicitly stated that the code provided does not duplicate that problem. Please, feel free to make an additional question with code that does duplicate that problem. You can get the original text from the question's revisions page.Schoolteacher
S
1

Messages sent by runtime.sendMessage() are received in all scripts in the background context which have a runtime.onMessage listener registered, except the script from which the message was sent.1 There is no way for you to restrict the recipients of such messages.

Thus, you have to create some methodology to determine if the message is intended for each script which receives it. This can be done in a wide variety of ways, but all of them are based on either:

  1. The contents of the message and/or
  2. The sender

Both of these are provided as arguments to the runtime.onMessage() listener.

Using message

To use the message you have to choose to impose some structure on the message. The message can be any JSON-ifiable data you choose to send. Imposing some structure allows you to more easily use messages more complex and more reliably communicate information between your scripts. There is nothing that is predefined. You can use whatever you desire. However, being consistent in your choices usually makes it easier to program and almost always makes code easier to maintain.

Personally, I'm usually sending messages for different reasons. For each extension, I'll usually choose to always send an Object with a particular format. That format may be different for each extension, but it usually looks something like:

var message = {
    type: 'requestFoo',
    //subType: 'Used if the type needs to be further split',
    data: dataNeededForRequest
    //You can add whatever other properties you want here.
};

If the extension has multiple possible recipients, then either A) only one recipient would understand how to handle a requestFoo type message, and all others would ignore such message types, or if there were multiple background context scripts which could handle requestFoo types, then I would add a recipient or destination property. Thus, the message would look like:

var message = {
    type: 'requestFoo',
    //subType: 'Used if the type needs to be further split',
    recipient: 'page2.js',
    data: dataNeededForRequest
    //You can add whatever other properties you want here.
};

When each script received the message they would check both that the recipient matched the script which had received the message and that the code understood how to handle the type of message.

Keep in mind that the structure above is just what I happen to use. You can define whatever structure you desire which also fulfills your needs.

Using sender

If a script is never to act on messages received from a specific sender(s), or if it is to only act on messages from specific senders, then that script can check the sender runtime.MessageSender Object to see if the parameters match ones from a sender for which it is to act on the message. If you use this methodology, you will most commonly be checking the sender.url.

Basing a choice to act on a message based solely on the sender is significantly more limited than being based on the contents of the message. However, it can be quite useful when used in addition to information provided in the message. It also provides a way to know the sender of the message which can not be spoofed. In addition, it means that you don't need to communicate information as to which scope was the sender of the message, unless, of course, you have more than one possible sender in the scope (i.e. in the URL/page).


1. A bug in Firefox in versions prior to 51 results in messages being received in the script which sent them. If you expect your extension to be used in that version or earlier you must account for that possibility (i.e. ignore them), as some situations can result in locking up Firefox (e.g. if you always send a new message when you receive a message).

Schoolteacher answered 29/6, 2017 at 4:33 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.