Bidirectional asynchronous messaging between website and extension popup - javascript

Use case:
website opens an extension popup window when a user clicks on a button
user performs some actions in the extension popup
a result of that action is sent back to the browser tab which initially opened the extension
Approach
(1) window.postMessage(data): Send a message to the content script where window.addEventListener("message", (event) => {...} is receiving the message event.
(2) chrome.runtime.sendMessage(event.data, function(response) {...}: This functionality is in the listener above and forwards the message to the background script where chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {...}) is receiving the message.
(3) Background scripts opens the extension as a dedicated popup window:
chrome.windows.getLastFocused().then((window) => {
const width = 600 + 100;
const height = 400 + 100;
const left = window.width - width;
chrome.windows.create({url: path, type: "popup", height: height, width: width, left: left, focused: true});
});
(4) After having processed some user actions within the extension popup a response message should be returned to the content script:
chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
chrome.tabs.sendMessage(tabs[0].id, {...})}
Problem
Everything works fine up to step (4). Because the extension popup has been opened programmatically as a dedicated popup window tabs[0].id contains a tab id from the extension popup window instead of the browser window which triggered the whole flow and where the content script runs.
How can the correct tab id be determined or forwarded along the chain to the popup?
Is this messaging scenario the right approach for my requirements?
Any feedback is very much appreciated!

Related

How to run a chrome extension from a click button?

After a lot of times spend to find the solution, i'm here to ask your help.
I have a simple chrome extension, and what I try to do is simple:
Detect on the page when a button is clicked, then, open the extension popup.html.
A good example is MetaMask, the html page can open the popup to connect the wallet, send transaction ...
Thank you !
Run a content script on the page and send a message to the background when the button is clicked.
// Content Script
button.addEventListener("click", () => {
chrome.runtime.sendMessage("OpenPopup")
})
Catch the message in the background and create a window with your popup URL. Provide a "top" and a "left" value to place it at the top right of the users' screen. Use the type "popup" to get rid of the stuff that is usually at the top of a window. Read more about the chrome.windows.create method here.
// Background Script
chrome.runtime.onMessage.addListener(request => {
if (request == "OpenPopup") {
chrome.windows.create({
url: "popup.html",
type: "popup",
focused: true,
width: 400,
height: 600,
top: 0,
left: screen.width - 400,
}, () => {
console.log("Opened popup!")
})
}
})

Make Chrome extension javascript document apply to open webpage, not popup [duplicate]

This question already has answers here:
How to access the webpage DOM/HTML from an extension popup or background script?
(2 answers)
Closed 2 years ago.
I'm writing a chrome extension in which i need to click on a item on the webpage. The item can be found with document.getElementsByClassName("className")[0].click() but if I run this in the popup, it searches for the item in the popup menu instead. How can I make it return and click the item in the webpage?
You should make a content script and use that to perform the click. Content scripts are loaded in the context of the HTML page, as opposed to the extension popup.
You can message between your content script and your main extension code by using the messaging API.
Your extension code could send a message to the content script like this:
// Query the active tab in the current window (there will only be one)
chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
// Send a message to that tab (found as tabs[0]),
chrome.tabs.sendMessage(tabs[0].id, {message: "perform_click"}, function(response) {
console.log("Click performed.");
});
});
And your content script could handle the request like this:
// Listen for events from the background script (main part of the extension)
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
// This function will be called in the content script to handle messages received from the main extension code.
// If the extension is requesting a click, we do that here
if (request.message == "perform_click") {
document.getElementsByClassName("className")[0].click()
}
});

Get window position on close

I have a chrome extension that creates popups in a static location. However, if the user moves the popup and then closes it. I'd like to set the next popup to open in the same location the previous one was closed.
My background page is already listening for onRemoved and onFocusedChanged, but those only return the Window ID.
Is there a way to get the last top and left locations of a popup window after a user moves it, and on or before it is closed?
We can use beforeunload event and we need a synchronous method of saving data in order to guarantee it successfully completes before the execution context of the page is destroyed.
If the popup runs your extension page.
Popup's page script can save the size in the synchronous localStorage shared between all your extension pages, including the background script where you can use the saved value next time to create the popup window.
window.addEventListener('beforeunload', () => {
localStorage.windowSize = JSON.stringify({
left: window.screenX,
top: window.screenY,
width: window.outerWidth,
height: window.outerHeight,
});
});
If the popup runs a web URL.
We'll have to use a content script where we can abuse chrome.runtime.connect to pass the data synchronously in the port name to the background script which will parse and save the data in chrome.storage.local. Of course you can use this approach for the first case as well.
content script:
window.addEventListener('beforeunload', () => {
chrome.runtime.connect({
name: 'windowSize:' + JSON.stringify({
left: window.screenX,
top: window.screenY,
width: window.outerWidth,
height: window.outerHeight,
})
});
});
Make sure the content script runs inside the popup, for example, via chrome.tabs.executeScript in your background script right after creating the popup window.
background script:
chrome.runtime.onConnect.addListener(port => {
if (port.name.startsWith('windowSize:')) {
const windowSize = JSON.parse(port.name.slice('windowSize:'.length));
chrome.storage.local.set({windowSize});
}
});

How to communicate between frame and Edge Extension content script?

I am developing an Edge extension, I need to send a message from frame document(not the top document, top document works fine) to content script.
As following:
`contentscript.js
window.addEventListener("message", function(event) {
console.log("window top message...event:");
console.log(event);
});`
Send a message from the frame document, with:
window.top.postMessage("Hi, I am from frame", "*").
In the console panel, I can see window top message...event: and then the browser reload the page. It seems the message was blocked.
The extension can be got from github.
steps to reproduce:
Load the extension, open Google, open console panel, switch to Frame, and type
window.top.postMessage("Hi, I am from frame", "*").
Could anybody help?
If you have a background page, and your contentscript.js has access to browser. object, the cheap'n'easy way might be to bounce message to extension and back like so:
contentscript.js
browser.runtime.sendMessage({name: 'bounce', payload: {name: hello}});
background.js
browser.runtime.onMessage.addListener(function (request, sender) {
if (request.name === 'bounce') {
browser.tabs.sendMessage(sender.tab.id, request.payload);
}
});
and just listen for {name: hello} on your contentscript. All frames, including top should get this message.

How to prevent chrome extension to reopen the same popup window while it's still open?

I'm working on a chrome extension that has a login popup window. So the idea is for the extension to work it would require user to login so that it could interact with our API and get/post data.
So I override the browserAction to check if the user is logged in or not then if it is not it should show a login popup. My problem is when I click again the extension icon it reopens the same login popup window. How do I prevent that to happen and only one instance of login window should appear if it is open?
background.js
// omitted some codes for brevity
chrome.browserAction.onClicked.addListener(function () {
// omitted some codes for brevity
chrome.windows.create({
'url': 'login.html',
'type': 'popup',
'width': width,
'height': height,
'left': (screen.width/2) - (width/2),
'top': (screen.height/2) - (height/2),
'focused': true
});
});
There are multiple ways that you can do this.
Use chrome.tabs.query() to detect window is open
You could check for the existence of your popup window by calling chrome.tabs.query():
var popupUrl = chrome.runtime.getURL('/login.html');
chrome.tabs.query({url:popupUrl},function(tabs){
if(tabs.length > -1){
//The popup exists
}
});
In order for the above to work, you have to declare the tabs permission.
However, chrome.tabs.query() will not detect the popup window during the time between the call to chrome.windows.create() and when the window actually opens. Based on testing, this time is long enough for the user to click the browser action button multiple times, opening multiple popup windows.
Keep track of the popup window with a global variable
Given the drawbacks of using chrome.tabs.query() to detect the window, my preference is to use a global variable to hold the current state of the popup window. In the code below, the variable popupWindowId can have three general states:
false : Window is not open
true : Window is in the process of being opened
typeof popupWindowId === 'number' : Popup window is open. The number is the window ID.
If the user clicked the browser action button, then the user wants the popup. While, we don't want to open a new popup, we do want to provide the user with what they want. Thus, if the popup is already open, then the popup window is focused. This will bring it to the top, showing it to the user. A full implementation should also check that the popup window is within the visible bounds of the screen. If it is not visible, then it should be moved to be visible. This later functionality is not implemented here.
To detect when the popup window is closed, a chrome.windows.onRemoved listener is used.
I also like to provide feedback to the user of what is going on, so the title of the popup is changed to "Popup window is already open. Click to focus popup." while the popup is open. When the window is closed, the title is changed back to "Open popup window".
background.js:
var windowNotOpenTitle = 'Open popup window';
var windowIsOpenTitle = 'Popup window is already open. Click to focus popup.';
var popupWindowId = false; //popupWindowId can be true, false, or the popup's window Id.
chrome.browserAction.onClicked.addListener(function () {
let width= 400;
let height= 300;
if(popupWindowId === false){
popupWindowId = true; //Prevent user pressing pressing the button multiple times.
chrome.browserAction.setTitle({title:windowIsOpenTitle});
chrome.windows.create({
'url': 'login.html',
'type': 'popup',
'width': width,
'height': height,
'left': (screen.width/2) - (width/2),
'top': (screen.height/2) - (height/2),
'focused': true
},function(win){
popupWindowId = win.id;
});
return;
}else if(typeof popupWindowId === 'number'){
//The window is open, and the user clicked the button.
// Focus the window.
chrome.windows.update(popupWindowId,{focused:true});
}
});
chrome.windows.onRemoved.addListener(function (winId){
if(popupWindowId === winId){
//chrome.browserAction.enable();
chrome.browserAction.setTitle({title:windowNotOpenTitle});
popupWindowId = false;
}
});

Categories