I'm creating a Chrome extension that has a background.html file which requests information from an API once every minute. Once it receives the information, it messages popup.html with the JSON information with which popup uses to append new HTML elements onto the popup's body.
The problem is background is constantly running (as it should), but it will ping popup even when popup is closed. This causes popup to open itself every minute which is very annoying.
I want to know, is there a way to see if popup is closed and not do anything if that's the case? Or is there another way to prevent popup opening on its own?
Here's the Github repository, but the important parts are highlighted below.
Here's how I'm pinging popup:
// background.js
function sendQuestions()
{
var questions = JSON.parse(db.getItem(storage));
chrome.extension.sendRequest(appid, { 'questions': questions }, function() {});
}
setInterval(sendQuestions, 60e3);
Here's how popup handles it:
// popup.js
chrome.extension.onRequest.addListener(function(request) {
if (request.questions) {
displayQuestions(request.questions);
}
});
function displayQuestions(questions)
{
for (i = 0; i < questions.length; i++) {
var question = questions[i];
var htmlBlock = // ... generate a block of html ...
$('#container').prepend(htmlBlock);
}
}
Open a long lived connection from the popup to the background_page anytime it opens. In the background_page you can check to see if the connection is currently active. If it is pass the necessary messages otherwise wait until the connection is active.
Related
So, I'm building an extension that autofills different types of forms. As it's not apparent from the url which form is used on a particular website, I need to match all the urls in my manifest. I'm trying to detect the form by the 'src'-attribute in the web page.
Some of the fields of a certain form are not in the first frame. So "all_frames" has to be true in my manifest. That means content.js fires once for each frame or iFrame.
**content.js:**
async function checkForPaymentType(value, attribute = 'src') {
return document.querySelectorAll(`[${attribute}*="${value}"]`);
}
let hasThisForm;
document.addEventListener('DOMContentLoaded', () => {
checkForPaymentType('formJs.js').then((value) => {
if(value.length) {
hasThisForm = true;
}
if(hasThisForm)
fillForm();
});
});
The problem now is, that that only the first frame has the "src='formJs.js" attribute in one of its elements. So it only fills out those fields in the first frame.
My solution idea
What I am trying to do is some sort of global boolean variable ('hasThisForm') that can only be set true. So once the first frame detected that there is this form on the website the other frames fire fillForm() as well.
Problems
1.I'm not able to set a variable that can be read from all of the executions.
2.I need the other executions of content.js to wait for the first one.
Another solution would be to have some sort of window.querySelectorAll, so every execution of content.js searches in the whole page and not just in its frame.
Thanks in advance:)
So I figured it out.
As pointed out in the comment from #wOxxOm or in this question (How to get all frames to access one single global variable) you need to manage everything via the background page.
You want to set up a listener in every Frame and send a message only from the top frame (to the background page which sends it back to the whole tab).
After hours of trying I noticed that the listeners weren't even ready when the message from the topFrame was sent. I put in a sleeper before sending the message which is not the ideal way I guess. But there is no "listenerIsSet-Event".
This is my code:
content.js
document.addEventListener('DOMContentLoaded', () => {
chrome.runtime.onMessage.addListener(
function (msgFromTopFrame) {
console.log(msgFromTopFrame)
});
if (window === top) {
Sleep(1000).then(() => {
const msgToOtherFrames = {'greeting': 'hello'};
chrome.runtime.sendMessage(msgToOtherFrames);
});
}
});
background.js
chrome.runtime.onMessage.addListener((msg, sender) => {
if(('greeting' in msg)) {
chrome.tabs.sendMessage(sender.tab.id, msg);
}
});
You probably want to execute some code depending on the value received. You can write it only once in the listener. It will execute in all frames including the top frame as the background.js sends it to all frames in the tab.
Note:
There may be some errors with the dicts/keys in the messages "sent around" in this code. Just log the message in all the listeners to get the right expressions.
I need to run through an array of roles and open a modal dialog (in HTML) for each. I had a problem where each next dialog gets opened before I close the previous dialog (because of asynchronous Google Script.
I have tried implementing a solution by setting a while loop for Utilities.sleep() and adding a global variable 'sleeping' that becomes false when the modal dialog is closed.
However, now only the first dialog opens and the code does not run through the full 'for' loop.
function nightStart(nightNumber, playersArray, roleList) {
var sheet = SpreadsheetApp.getActiveSpreadsheet();
var range = sheet.getRange("Controls!G3:G1000");
var wakeupOrder = [];
var sleeping;
var role;
//collecting the array to define in what order roles wake up
for (var i = 1; i<=20; i++) {
var cellValue = range.getCell(i,1).getValue();
wakeupOrder.push(cellValue);
}
//the FOR loop that I am trying to make work (open Dialog for each role)
for (index in wakeupOrder) {
role = wakeupOrder[index];
if (roleList.indexOf(role) != -1) {
sleeping = true;
roleWakeUp(role, playersArray, roleList);
do {
Utilities.sleep(2000);
//calling global sleeping parameter that is defined as FALSE in the 'nightTargetSelection' function
sleeping = PropertiesService.getScriptProperties().getProperty('sleeping');
} while (sleeping != false);
}
}
}
//below is the function that opens the modal dialog (but the server side code still keeps running).
function roleWakeUp (role, playersArray, roleList){
//I have removed all code from here for Stack Overflow. The only part that I believe is important is that it opens an HTML dialog with a form
SpreadsheetApp.getUi().showModalDialog(actionInputDlg, wakeUpText);
}
//Below function is called by the client on HTML form submission. After this form is submitted I need the next dialog to open (i.e need the Utilities.sleep to stop running
function nightTargetSelection (selected, playerNumber){
var sleeping = false;
PropertiesService.getScriptProperties().setProperty('sleeping', sleeping);
}
I need an HTML dialog to open for each 'role' in the 'wakeupOrder' array (if the role exists in 'roleList'). Each next dialog needs to open only after the submission of the previous dialog.
You want to open several dialogs in order.
When the process is finished on a dialog, you want to open next dialog.
Namely, you don't want to open the next dialog before the current job is finished.
If my understanding is correct, how about this answer?
When SpreadsheetApp.getUi().showModalDialog() is opened in order, the dialog is overwritten. I think that this is the reason of your issue. So, here, I would like to introduce a sample script. In this sample script, the next dialog is opened from the current dialog. The flow of this sample script is as follows. Please think of this as just one of several answers.
Open a dialog by running start().
When "ok" button is clicked, the next dialog is opened by including the next job.
By this, each job can be completely done.
When all jobs were finished, done() is run and the dialog is closed.
Sample script:
When you use this script, please copy and paste "Code.gs" and "index.html" to "script" and "HTML" on your script editor, respectively. And please run start(). This sample script supposes that you are using the container-bound script of Spreadsheet.
Code.gs: Google Apps Script
// When all jobs were finished, this function is called.
function done(e) {
Logger.log(e)
}
// Open a dialog
function openDialog(jobs, i) {
var template = HtmlService.createTemplateFromFile('index');
template.jobs = JSON.stringify(jobs);
template.index = i;
SpreadsheetApp.getUi().showModalDialog(template.evaluate(), "sample");
}
// Please run this script
function start() {
var jobs = ["sample1", "sample2", "sample3"];
openDialog(jobs, 0);
}
index.html: HTML and Javascript
<div id="currentjob"></div>
<input type="button" value="ok" onclick="sample()">
<script>
var jobs = JSON.parse(<?= jobs ?>);
var index = Number(<?= index ?>);
document.getElementById("currentjob").innerHTML= "currentJob: " + jobs[index] + ", index: " + index;
function sample() {
if (index < jobs.length - 1) {
google.script.run.openDialog(jobs, index + 1); // Modified
} else {
google.script.run.withSuccessHandler(()=>google.script.host.close()).done("Done.");
}
}
</script>
References:
Class HtmlTemplate
withSuccessHandler()
Note:
This is a simple sample script. So please modify this for your situation.
If I misunderstood your question and this was not the result you want, I apologize.
I'm rather new to Javascript and Crossrider. I believe what I'm trying to do is a rather simple thing - maybe I missed something here?
I am writing an extension that automatically logs you into Dropbox and at a later time will log you out. I can log the user into Dropbox automatically, but now my client wants me to automatically log those people out of dropbox by FINDING the open Dropbox windows and logging each one of them out.
He says he's seen it and it's possible.
Basically what I want is some code that allows me to get the active tabs, and set the location.href of those tabs. Or even close them. So far this is what I got:
//background.js:
appAPI.ready(function($) {
// Initiate background timer
backgroundTimer();
// Function to run backround task every minute
function backgroundTimer() {
if (appAPI.db.get('logout') == true)
{
// retrieves the array of tabs
appAPI.tabs.getAllTabs(function(allTabInfo)
{
// loop through tabs
for (var i=0; i<allTabInfo.length; i++)
{
//is this dropbox?
if (allTabInfo[i].tabUrl.indexOf('www.dropbox.com')!=-1)
{
appAPI.tabs.setActive(allTabInfo[i].tabId);
//gives me something like chrome-extension://...
window.alert(window.location.href);
//code below doesn't work
//window.location.href = 'https://www.dropbox.com/logout';
}
}
appAPI.db.set('logout',false);
});
window.alert('logged out.');
}
setTimeout(function() {
backgroundTimer();
}, 10 * 1000);
}
});
When I do appAPI.tabs.setActive(allTabInfo[i].tabId); and then window.alert(window.location.href); I get as address "chrome-extension://xxx" - which I believe is the address of my extension, which is totally not what I need, but rather the URL of the active window! More than that, I need to navigate the current window to the log out page... or at least refresh it. Can anybody help, please?
-Rowan R. J.
P.S.
Earlier I tried saving the window reference of the dropbox URL I opened, but I couldn't save the window reference into the appAPI.db, so I changed technique. Help!
In general, your use of the Crossrider APIs looks good.
The issue here is that you are trying to use window.location.href to get the address of the active tab. However, in the background scope, the window object relates to the background page/tab and and not the active tab; hence you receive the URL of the background page. [Note: Scopes can't directly interactive with each others objects]
Since your objective is to change/close the URL of the active dropbox tab, you can achieve this using messaging between scopes. So, in your example you can send a message from the background scope to the extension page scope with the request to logout. For example (and I've taken the liberty to simplify the code):
background.js:
appAPI.ready(function($) {
appAPI.setInterval(function() {
if (appAPI.db.get('logout')) {
appAPI.tabs.getAllTabs(function(allTabInfo) {
for (var i=0; i<allTabInfo.length; i++) {
if (allTabInfo[i].tabUrl.indexOf('www.dropbox.com')!=-1) {
// Send a message to all tabs using tabId as an identifier
appAPI.message.toAllTabs({
action: 'logout',
tabId: allTabInfo[i].tabId
});
}
}
appAPI.db.set('logout',false);
});
}
}, 10 * 1000);
});
extension.js:
appAPI.ready(function($) {
// Listen for messsages
appAPI.message.addListener(function(msg) {
// Logout if the tab ids match
if (msg.action === 'logout' && msg.tabId === appAPI.getTabId()) {
// change URL or close code
}
});
});
Disclaimer: I am a Crossrider employee
I am writing a chrome extension that when clicked, will close the current tab after a given amount of time.
I am sending a message with the time, from popup.js to background.js. But the tab won't close.
The alert works when I uncomment it, so it seems to be just the remove line. I assume it's something about tab.id.
chrome.extension.onMessage.addListener(
function message(request, sender, callback) {
var ctr = 0;
ctr = parseInt(request.text, 10);
setTimeout(function() {
chrome.tabs.getCurrent(function(tab) {
//window.alert("Working?");
chrome.tabs.remove(tab.id, function(){});
});
}, ctr);
}
);
1.
chrome.extension has no onMessage event. I assume you mean the correct chrome.runtime.onMessage
2.
You have probably misunderstood(*) the purpose of chrome.tabs.getCurrent:
Gets the tab that this script call is being made from. May be undefined if called from a non-tab context (for example: a background page or popup view).
Since, you are calling it from a non-tab context (namely the background page), tab will be undefined.
(*): "misunderstood" as in "not bother to read the manual"...
3.
It is not clear if you want to close the active tab at the moment the timer is set or at the moment it is triggered. (In your code, you are attempting to do the latter, although the former would make more sense to me.)
The correct way to do it:
chrome.runtime.onMessage.addListener(function message(msg) {
var ctr = 0;
ctr = parseInt(msg.text, 10);
setTimeout(function() {
chrome.tabs.query({ active: true }, function(tabs) {
chrome.tabs.remove(tabs[0].id);
});
}, ctr);
});
Also, note that using functions like setTimeout and setInteval will only work reliably in persistent background pages (but not in event pages). If possible, you are advised to migrate to event pages (which are more "resource-friendly"), in which case you will also have to switch to the alarms API.
I've understood from the docs that closing chrome extension popups when losing focus has been a design choice.
I'm working on an extension where the user chooses to save elements from a webpage. As he interacts with the main webpage I would like the popup to get updated but that's obviously not possible.
What's the proper way of handling this situation? (this is my first chrome extension)
You can have a content script detect the "save" action. Let's suppose it's a specific DOM element you know for sure it's going to be in the specific main, or that you create by yourself.
content.js
//content script
document.onreadystatechange = function () {
if (document.readyState == "complete") {
// Grab the UI frmo the mainpage you want to append the save functionality
var someElementsYouWantToAppendASaveButtonTo = document.getElementsByTagName("...");
var len = someElementsYouWantToAppendASaveButtonTo.length;
for (var i = 0; i < len; i++) {
// Create a UI save button to provide a functionality
var theSaveButton = document.createElement("button");
theSaveButton.value = "Save to Chrome Extension";
// Send data to extension when clicked
theSaveButton.addEventListener("click", function() {
var dataToSentToExtension = {...} // Retrieve from the clicked element, or whatever you want to save
chrome.extension.sendMessage(dataToSentToExtension, function(response) {
if(response.success) console.log("Saved successfully");
else console.log("There was an error while saving")
});
}, false);
someElementsYouWantToAppendASaveButtonTo[i].appendChild(theSaveButton)
}
}
}
Then, on the background, you detect the response and set up the popup as you wish.
background.js
chrome.extension.onMessage.addListener(function(request, sender, sendResponse) {
if(request.dataToSave) {
chrome.storage.local.set(dataToSave, function() {...});
// You can then set upn the proper popup for the next click or even switch to it
switch(request.popupToDisplay) {
case "awesomeDisplay":
chrome.browserAction.setPopup({...})
break;
}
var responseFromExtension = {success: true}
} else {
var responseFromExtension = {error: true}
}
});
It seems you are looking to modify\update your popup.html page in accord to changes in a web page. If so, use content scripts and establish connection for single message communication with background page(Since focus is lost every time) and update popup.html indirectly.
References:
Content Scripts
Background Page
Message Passing
Reference for communication between popup and background page apart from these,
there are bunch of questions on these topics, they will get you started..