I have set something in local storage (where tabId is the id of the triggering tab [onUpdated event])
var visited = {};
visited[tabId] = true;
chrome.storage.local.set(visited);
I then wish to change the stored variable to false when the page unloads (which I gather happens on refresh, moving to a new webpage or closing the tab)
window.onunload = resetStorage;
function resetStorage() {
var visited = {};
chrome.tabs.query({ currentWindow: true }, function (result) {
result.forEach(function (tab) {
visited[tab.id] = false;
console.log(visited);
chrome.storage.local.set(visited);
});
});
};
But this doesn't seem to be triggering (I can't get a console.log to come out, not sure if you can on an unload event?) as it does not change the stored values.
What am I doing wrong?
As some background I am keeping track of whether I have already run code on a page so that it doesn't trigger multiple times from iframe loading or redirections (I think I need additional code to handle redirects).
Related
On my chrome extension, I have a popup page and a background script.
As default, when I click on the extension's icon in two different windows, a popup will open in both windows.
I want to limit the amount of popups opened by the extension to be at most one at a time.
Here's how the full scenario that I'm trying to create:
At first no pop up is activated.
Window A opened a popup.
Window B opened a popup, in which case, Window A's popup will close.
Window C is created, go to 2, but this time Window A<-Window B and Window B<-Window C
If in any time The only popup that is open was closed, return to 1.
I know that a popup windows was created because I have a simple port connection that is invoked on the popup startup. Thus, the background is in theory aware of all popup windows that are created, namely, that is the code that I run in the popup to connect:
const port = chrome.runtime.connect({ name: 'popup-communications' });
I attempted to solve the problem in 3 ways, all of them failed.
Attempt 1
Hold the last popup that was connected. If a new one appears, close the old one before you save the new one. Use chrome.extension.getViews to get the new port. I imagined this would work, but rapid clicks on the extension icon (to invoke browserAction) makes this popUp state confused.
let popUp;
chrome.runtime.onConnect.addListener(function connect(port) {
if (port.name === 'popup-communications') {
// attempt 1
if (popUp) {
popUp?.close?.();
popUp = null;
console.log('removed old pop up');
}
[popUp] = chrome.extension.getViews({
type: 'popup',
});
});
Attempt 2
Close all popups that received from chrome.extension.getView, but the last one. The problem with this approach is that the chrome.extension.getView does not guarantee any order.
let popUp;
chrome.runtime.onConnect.addListener(function connect(port) {
if (port.name === 'popup-communications') {
// attempt 2
const popUps = chrome.extension.getViews({
type: 'popup',
});
console.log(popUps);
for (let i = 0; i < popUps.length - 1; i++) {
popUps[i].close();
}
});
I also experimented with chrome.browserAction.disable and chrome.browserAction.enable. This solution maintains indeed maintains 1 popup at all time, but I want it the popup to be available whenever I click on the extension icon, and this will not happen with this approach (instead I will need to find the relevant window with this popup)
Is there a way to achieve what I'm trying to do here?
I was able to achieve this behavior in the following way.
background.js
The background listens to connecting popups.
When a popup connects, it will broadcast a message to all open popups to close. Note this message is not sent over the port since the port connection does not broadcast.
There should exist a way to optimize, since at most one other popup can be open, but I will leave that for another time and prefer not to create global variables.
chrome.runtime.onConnect.addListener(function connect(port) {
if (port.name === 'popup-communications') {
port.onMessage.addListener(function (msg) {
if (msg.here) {
const activeTabId = msg.here;
// broadcast close request
chrome.runtime.sendMessage({closeUnmatched: activeTabId});
}
});
}
});
popup.js
Perform a lookup of its tab id.
Add a message listener to know when to close. If the message to close does not match current tab id, popup will close. window.close() is sufficient to close a popup.
"announce" to background that popup is ready by sending the tab Id over the port.
async function getCurrentTab() {
let queryOptions = {active: true, currentWindow: true};
let [tab] = await chrome.tabs.query(queryOptions);
return tab;
}
function addListener(myTabId) {
chrome.runtime.onMessage.addListener(function (msg) {
if (msg.closeUnmatched && msg.closeUnmatched !== myTabId) {
window.close();
}
});
}
(async function constructor() {
const port = chrome.runtime.connect({name: 'popup-communications'});
// whoami lookup
const {id: myTabId} = await getCurrentTab();
// add handler to self-close
addListener(myTabId);
// tell background I'm here
port.postMessage({here: myTabId});
// do whatever with port..
}());
I assume steps 1-3 can be done faster than user switching tabs/windows to activate another popup. The port connection was complicating things, but I left it in the answer, since you may have a use case for it.
I am making a Chrome extension. Clicking a button in popup.html opens a new window and loads feedback-panel.html.
This works but, on click, I'd like to check if the window is already open and if so focus to it instead of creating a new one.
JavaScript window.open only if the window does not already exist looked promissing but it relies on the open windows being stored as variables on the parent page when the window is opened and checking those before opening a new one. This wont work for me since the parent window (popup.html) will often be closed and reopened itself and I'd lose the variables.
I tried to implement the same idea but store the window variables in with chrome.storage since it lets you store objects. Well, it does let you store objects but it serializes them first so the window variable loses all of it's functions and I end up with
result.feedbackPanel.focus() is not a function
Here is my attempt:
function openFeedbackPanel(){
chrome.storage.local.get('feedbackPanel', function (result) {
console.log( result.feedbackPanel); // logs window object sans all functions
if(result.feedbackPanel && ! result.feedbackPanel.closed){
try{
result.feedbackPanel.focus();
}
catch(error){
chrome.storage.local.remove('feedbackPanel', function () {
});
createFeedbackPanel();
}
}
else{
createFeedbackPanel();
}
});
}
function createFeedbackPanel(){
var win = window.open('feedback-panel.html', 'Feedback', 'width=935, height=675');
console.log(win); // logs full object as expected
chrome.storage.local.set({"feedbackPanel": win});
}
$('#some-button').click(openFeedbackPanel());
So, since this doesnt work:
How can I check if a popup window is already open from a non-parent window (one that did not open the popup)?
no need to track windows and store them.
if you know your extension ID, the simplest way is to test all tabs url's and see if it's already opened
chrome.tabs.query({}, function(tabs) {
var doFlag = true;
for (var i=tabs.length-1; i>=0; i--) {
if (tabs[i].url === "chrome-extension://EXTENSION_ID/feedback-panel.html") {
//your popup is alive
doFlag = false;
chrome.tabs.update(tabs[i].id, {active: true}); //focus it
break;
}
}
if (doFlag) { //it didn't found anything, so create it
window.open('feedback-panel.html', 'Feedback', 'width=935, height=675');
}
});
and here is already answered how to get extension ID,
You can also use the messaging system. This is an example I use for an extension's options. Call a function like this in the onClick for the button:
// send message to the option tab to focus it.
// if not found, create it
showOptionsTab: function() {
chrome.runtime.sendMessage({window: 'highlight'}, null, function(response) {
if (!response) {
// no one listening
chrome.tabs.create({url: '../html/options.html'});
}
});
},
And in your window/tab listen for the message like this
// listen for request to make ourselves highlighted
chrome.runtime.onMessage.addListener(t.onMessage);
...
// message: highlight ourselves and tell the sender we are here
t.onMessage = function(request, sender, response) {
if (request.window === 'highlight') {
chrome.tabs.getCurrent(function(t) {
chrome.tabs.update(t.id, {'highlighted': true});
});
response(JSON.stringify({message: 'OK'}));
}
};
One advantage of this method is it does not need the tabs permission.
I have the website example.com and on this website is a button with the id "button1" and by clicking on this button, it opens an unknown url "example.com/unknownUrl" with the known button "buttonOnUnknownPage" on it. How can i be sure that the unknown page has finished loading and i can click "buttonOnUnknownPage"?
NOTE: "example.com" is opened in another window than the script. that means, the script don't stops running after "example.com" reloads.
I have until now used this:
// open example.com and get button1
exampleWindow = window.open("http://example.com", "_blank");
button1 = exampleWindow.document.getElementById('button1');
// clicking on button 1 opens example.com/unknownUrl
button1.click();
//and now wait 1000ms until page might be loaded
setTimeout(function() {
buttonOnUnknownPage = exampleWindow.document.getElementById('buttonOnUnknownPage');
buttonOnUnknownPage.click()
}, 1000);
The problem of this is, that i need to wait everytime 1000ms and can still not be sure, that "example.com/unknownUrl" was loaded.
Is there a more efficient method, to be sure that "example.com/unknownUrl" has loaded ? Something like document.onload ?
The monitoring of some other window as it changes location is fairly complicated and the reason it's complicated is that each time the other window loads a new document, the entire window state is cleared and all event listeners are wiped out and a whole new document is created. So, you can't install an event listener once and keep using it because it gets wiped out each time a new link is clicked and the page location is changed.
It is further complicated by the fact that the process of creating a new window for a particular URL will (in some browsers) first load a URL called about:blank and then load the real URL causing your monitoring to sometimes detect the loading of the about:blank internal URL, not the real URL you want to monitor. IE (even new versions) is particular bad for this (no surprise).
So, it is possible to track the loading of these external windows, but it takes some hacking to get it to work. The hacking requires these steps:
Get the original URL of the window (what it was before you told it to load something new).
Wait until the window.location.href value for that window is no longer the original URL. This signifies that the window has now started to load its new URL.
Once it is loading the new URL, then wait until the window shows that it has a .addEventListener property. For some unknown reason, newly created windows in IE don't yet have this property. That means you can't install a load event handler on a newly create window. Instead, you have to wait until that property is available and then you can install the load event handler.
When the .addEventListener property is available, see if the document is already done loading. If so, proceed with your DOM operation. If not, then register an event handler for when the document is done loading.
I've created a function call monitorWindowLoad() for carrying out these steps above:
function monitorWindowLoad(win, origURL, fn) {
log("monitorWindowLoad: origURL = " + origURL);
function windowInitiated() {
// at this point, we know the new URL is in window.location.href
// so the loading of the new window has started
// unfortunately for us, IE does not necessarily have a fully formed
// window object yet so we have to wait until the addEventListener
// property is available
checkCondition(function() {
return !!win.addEventListener;
}, windowListen);
}
// new window is ready for a listener
function windowListen() {
if (win.document.readyState === "complete") {
log("found readyState");
fn();
} else {
log("no readyState, setting load event handler");
win.addEventListener("load", fn);
}
}
if (origURL) {
// wait until URL changes before starting to monitor
// the changing of the URL will signal that the new loading window has been initialized
// enough for us to monitor its load status
checkCondition(function() {
return win.location.href !== origURL;
}, windowInitiated);
} else {
windowInitiated();
}
}
// Check a condition. If immediately true, then call completeFn
// if not immediately true, then keep testing the condition
// on an interval timer until it is true
function checkCondition(condFn, completeFn) {
if (condFn()) {
completeFn();
} else {
var timer = setInterval(function() {
if (condFn()) {
clearInterval(timer);
completeFn();
}
}, 1);
}
}
This function can then be used to click successive links in several loading pages like this:
function go() {
// open new window
var exampleWindow = window.open("window2.html");
monitorWindowLoad(exampleWindow, "about:blank", function() {
var loc = exampleWindow.location.href;
clickButton(exampleWindow, "button2");
monitorWindowLoad(exampleWindow, loc, function() {
loc = exampleWindow.location.href;
clickButton(exampleWindow, "button3");
monitorWindowLoad(exampleWindow, loc, function() {
// last page loaded now
});
});
});
}
There's actually working demo of this concept here. This loads a file named window1a.html. The Javascript in that page opens a new window for window2.html, and when that is loaded, it clicks a specific link in that window. Clicking that link opens window3.html and when that is loaded, it clicks a link in that window which then opens window4.html. You should end up with two windows open (window1a.html and window4.html). window1a.html will contain a log of the various events it carried out.
The script in window1.html doesn't know any of the URLs. It's just clicking links and monitoring when the newly loaded window has loaded so it can click the next link and so on.
I have a list of items in a listview. Clicking on one li sends a JSON request and opens a description page.
Since the opening of the description page takes 1 or 2 seconds there is time to click on another item in the list which then triggers more events, which I don't want. This eventually makes the scrolling (with iscrollview) messy with the bottom bar going up and down when going back to the list.
How can I stop listening to more taps on the listview while processing the opening of the description page?
Without any to look at, it's very difficult for us to help you.
However, the simplest method of avoiding this is to use a global variable as a flag.
You would set the global variable (ie: in the root-level of your JavaSCript file), as false:
tapProcessing = false;
Then, whenever you start processing you, check against this flag and - if not true, then process.
Here's a rudimentary example to show you what I mean:
$('.selector').click(function(e){
if(!tapProcessing){
//the function is not processing, therefore set the flag to true:
tapProcessing = true;
//do your load/etc, and reset the flag to false in the callback (when finished):
$.get("test.php", function(data) {
// process your data here
// set your flag back to false:
tapProcessing = false;
});
}else{
//the function is already being processed from a previous click
}
e.preventDefault(); //assuming it's a link
});
Here are the bits of code I added :
el is my list, event is the landing page
setting tapProcessing to false should only be done when "after the changePage() request has finished loading the page into the DOM and all page transition animations have completed" (JQM doc : http://jquerymobile.com/demos/1.2.0/docs/api/events.html )
$('#el').click(function(e){
if(tapProcessing) {
alert('prevent');
e.preventDefault(); //assuming it's a link
} else {
alert('oktap');
tapProcessing = true;
}
});
$(document).bind("pagechange", function(event, options) {
if(options['toPage']['selector'] == '#event') {
tapProcessing = false;
}
});
I also set tapProcessing to false if I receive a disconnect event or on timeout.
I need to set up a custom script for tracking a users click through on a form submission field. This is what I've got so far. As the user navigates down through the form fields the counter variable (base) totals up how far along the path the user has reached. I want to send the results off when the user leaves the page by sending out the base variable. I'm thinking of using the .unload function in jQuery. However for some reason unload isn't responding the way I think it should. Any ideas?
var base = 0; //declares a variable of 0. This should refresh when a new user lands on the form page.
function checkPath(fieldNo, path) { //this function should check the current base value of the base variable vs the current fieldNo
if (fieldNo >= path) { //checks to see if base if lower than fieldNo
base = fieldNo; //if true, base is set to current fieldNo
return base;
} else {
return base; //if false, simply returns base.
}
};
$('#order_customer_fields_forename').focus(function () { //when the form box is selected should run checkPath then alert result.
checkPath(1, base);
});
$('#order_customer_fields_surname').focus(function () {
checkPath(2, base);
});
$('#order_customer_fields_postcode').focus(function () {
checkPath(3, base);
});
$('#order_customer_fields_address1').focus(function () {
checkPath(4, base);
});
$('#order_customer_fields_address2').focus(function () {
checkPath(5, base);
});
$(window).unload(function () {
alert(base);
});
The unload event fires too late for the effect you need. You should try using the onbeforeunload event using either vanilla Javascript:
window.onbeforeunload = function (e) {
// Your code here
};
Or jQuery:
$(window).bind('beforeunload', function (e) {
// Your code here
});
Either way, you should be aware that this is not an ideal solution for what you are trying to achieve. This event is implemented unevenly across browsers. Chrome seems to be the most restrictive, and IE the most permissive, in its implementation.
A different direction you may want to take is sending the data to the server by XHR whenever the user completes a field.