setTimeOut scope in a Chrome extension - javascript

I have a notification functionality in my chrome extension which is managed by a background page (background.html)
This page calls the setNotificationDelay() function which is basically a deletion of the previous timer followed by a setTimeOut.
function setNotificationDelay() {
clearNotificationTimer();
newDelay = getNotificationDelay()
notificationTimer = setTimeout(showNotification, newDelay);
}
}
I have also an options.html page which allows the configuration of the notification hour. I want to modify the background timeout when the configuration is modified, but when I call the setNotificationDelay() from the Option page it doesn't work, and I presume it's because the scope of the timers are attached to the parent page.
If I let the Option page opens the timeout is triggered, if I close it the notification never happened.
How can I attach this timer to the background page ?
Is there a better way to achieve it ?
Thanks.
EDIT: The chrome.extension have the getBackground page method which returns the window object for the background page:
function setNotificationDelay() {
clearNotificationTimer();
newDelay = getNotificationDelay()
notificationTimer = chrome.extension.getBackgroundPage().setTimeout(showNotification, newDelay);
}
}

options.js is only active while the options.html page is open, and I presume you don't want to leave the page open while you're waiting for the notification. What you want (I think) is to have background.js set the notification (both background.js and options.js access the same string variable localStorage.yourNotificationDelay).
One thing to watch out for is that if you are using event pages instead of background pages, a notification of more than a few seconds won't happen because Chrome will unload the background page. In that case, you'll need to use alarms instead.
Edit: Your edit makes me think I'm not understanding you. Perhaps you're expecting the user to change the interval while the timer is running? Then I'd recommend sending a message to background.js with newDelay, and use that to call setNotificationDelay().

Related

How to execute a content script every time a page loads?

I'm writing a Chrome extension that provides a content script to any page on GitHub (i.e. any URL matching https://github.com/*).
I'm simply trying to log something to the console each time a page on GitHub loads, like so:
window.onload = function() {
console.log("LOAD");
};
This listener function is executed the very first time a GitHub page is loaded, but if the user navigates to other pages on GitHub from there (by clicking on links or through other means), it doesn't fire. Why? :(
Steps to reproduce:
Open any repository's page on GitHub (example). You should see the message logged to the console.
Click on any link on that page. When the new page is loaded, no message is logged. :(
How do I solve this?
It seems that GitHub uses AJAX (along with history.pushState) to load some parts of the site, so onload will fire only when the page truly loads, but not when content is loaded via AJAX.
Since GitHub uses pushState to change the URL when AJAX content is done loading, you can detect when that happens, and execute your code.
There isn't actually a native event right now that fires when pushState is used, but there's this little hack:
(function(history){
var pushState = history.pushState;
history.pushState = function(state) {
if (typeof history.onpushstate == "function") {
history.onpushstate({state: state});
}
return pushState.apply(history, arguments);
}
})(window.history);
So, run that, and then, instead of window.onload, you can do:
history.onpushstate = function () {
console.log("LOAD");
};
Not ALL of GitHub page's load this way (AJAX + pushState), so you'd have to use both, window.onload and history.onpushstate.
Also, you should use window.addEventListener('load', fn); instead of window.onload, since you don't know if GitHub's code could be overwriting window.onload.

Why can't I deliver this message to all of the frames running in a tab?

Subject
I am creating a chrome extension that requires me to send a message from my event page to a content script that is running on a specific frame
The message passing operation is invoked when a context menu option is clicked. Said option is only available when the element in focus is categorized as editable by the chrome.contextMenus API. I need to send it to the innermost frame in which the element is contained. (so I can modify its contents by accessing the document.activeElement object)
As of Chrome 41, you are able to send a message to a specific frame by filling the value of the frameID parameter of the options argument to the chrome.tabs.sendMessage() function.
However, Chrome 41 is currently in beta, so I obviously cant expect this feature to be compatible with my users' browsers.
So, I planned of sending a message to all of the frames in the tab (by registering listeners on a content script that had the all_frames property set to true) . Once inside the content script I could check to see if this was the right frame or not by comparing the value of window.location.href with the value I am expecting.
Problem
It appears as If I had a misconception about the way that either event listeners or message passing works. The documentation makes it pretty clear that chrome.tabs.sendMessage() is supposed to send a message to every frame in the tab, but it appears as if there can only be a single chrome.runtime.onMessage() event listener invoked per message, even if there are many listeners registered. Which one actually gets invoked appears to be somehow dictated by the initial loading of the page.
This can be demonstrated with a simplified version of my extension:
manifest.json:
{
"manifest_version":2,
"name":"Demo",
"description":"This is a demonstration",
"version":"1.0",
"permissions":[
"<all_urls>",
"contextMenus"
],
"background": {
"page":"background.html",
"persistent":false
},
"content_scripts":[{
"matches":["<all_urls>"],
"js":["content.js"],
"all_frames":true
}]
}
background.html:
<!DOCTYPE text/html>
<html>
<head>
<textarea id="temp"></textarea>
<script src="event.js"></script>
</head>
<body>
</body>
</html>
event.js:
chrome.contextMenus.onClicked.addListener(requestHandler);
chrome.contextMenus.create({
"title":"Click Me!!",
"contexts":["editable"],
"id":"1"
});
function requestHandler(info, tab)
{
chrome.tabs.sendMessage(tab.id, {"destination":info.frameUrl});
//this should get sent to every frame in the tab
}
content.js:
chrome.runtime.onMessage.addListener(handleRequest);
alert("Hi, i'm: " + window.location.host);
function handleRequest(message)
{
alert("You tried to send a message to: " + message.destination);
alert("I am: " + window.location.href);
}
If you visit a page with many frames, like youtube you should notice the following behavior:
You get many alerts upon the initial loading of the page, indicating that there are many frames being loaded (all with listeners)
When you click the context menu option, only a single alert pops up, indicating that only a single frame received the message
The frame that receives the message remains constant throughout multiple clicks of the context option, but is likely to change when you refresh the page. This indicates that the order in which the frames are initially loaded determines which frame is going to get your message.
So, how can I assure that the frame I am trying to talk with actually gets my message, without resorting to the frameID option that's only available in chrome 41?
At first I thought I could send a response back from the content script, indicating whether or not the message went to the right place. I could just keep doing this in a while loop in the event page until I got back a message telling me to stop, but like I said earlier, the message always seems to go to the same frame until the page is refreshed, so this approach does nothing for me.
Additional Details
Something else I have noticed is that if there are two frames on a page with the same origin, like plus.google.com for example, both frames will respond to the message, if that is the frame set that the message happened to get delivered to. So, the recipient seems to be based on origin rather than by some unique frame ID.
Here is a link to a video I took of the phenomena I am speaking of:
https://www.youtube.com/watch?v=WVz8zBVqgIw&feature=youtu.be
Notice that I got alerts initially from: youtube.com, accounts.google.com, plus.google.com and client6.google.com, but only client6 got my message
Your chrome.runtime.onMessage event listener calls alert. This method blocks the runtime until the dialog is closed. For some reason, this also prevents Chrome from correctly triggering the onMessage events in other frames (reported as crbug.com/456482).
A work-around is to use console.log for debugging instead of alert, or wrap the code within setTimeout to asynchronously process the onMessage event. E.g.
chrome.runtime.onMessage.addListener(function(message) {
setTimeout(function() {
alert('Message: ' + message);
});
});

Jquery Cookie : one time use cookie

I want to put cookie to my page.
Here's the logic :
When I load the page, I want it to load a popup or bootstrap modal. But the modal only load once when the browser is active. And only will load again when the browser tab is closed or exits the browser application. I have used session to do this, but I prefer to use cookie for personal preferences.
Is there a way to do this with javascript?
I've tried with $(window).load(), and $(window).on('beforeunload',function());
Javascript :
<script type="text/javascript">
$(window).load(function () {
if( $.cookie('firstLoad') == 'unloaded' || $.cookie('firstLoad') == 'null' || $.cookie('firstLoad') == null ) {
$('#openLoading').modal('show');
var time_exp = 1;
$.cookie('firstLoad','loaded',{ expires: time_exp });
}
});
$(window).on('beforeunload', function (){
alert($.cookie('firstLoad'));
$.cookie('firstLoad','unloaded');
});
</script>
The problem is sometimes the app browser will execute location.reload() and will reset the cookie in some way and make the popup appear again.
Please provide solution, thanks.
PS : the var time_exp and expires : time_exp is a last resort if the unload doesn't work
The beforeunload event doesn't just fire when the tab is closed. It will fire whenever the user goes to a new page in the same tab, including a page on your site. So you are resetting your cookie every time the user navigates between pages.
There is no event you can use to tell you the user is leaving your site specifically. The closest you can do is not set an expires, so that the cookie will automatically be deleted when the browser exits.
You could put a close button in the modal, and set a cookie when it is clicked so you know the user has viewed the modal and you don't need to show it again for however long you decide.

How do I make it so my Chrome extension will only inject a script once?

I'm using programmatic injection to inject my extension's code into a page only when the browser action is clicked.
This is what I have on my extension's event page (per the example in the documentation):
chrome.browserAction.onClicked.addListener(function callback(tab){
chrome.tabs.executeScript(null, {file: "content-script.js"});
});
However, the way this works, the script is injected every time the button is clicked.
How can I change it so that the script is not injected on subsequent button presses - so that it is inserted only the first time the button is clicked on that page?
Put a global variable in your contentscript to judge if the contentscript has been executed.
if (something) { return; }
One way I can think of right now (easy and simple) is to use html5webstorage. Since you are running this code from your background or popup page it will be ok.
if(!localStorage.getItem("isAlreadyInjected")){
localStorage['isAlreadyInjected'] = "true";
chrome.browserAction.onClicked.addListener(function callback(tab){chrome.tabs.executeScript(null, {file: "content-script.js"});});}
So, the very first time when storage value "isAlreadyInjected" does not exist, the listener will be added. Afterwards, even when the browser closes and opens again this value will remain stored and so the listener will not be added to your extension.
UPDATE
As your background page loads only once at the beginning, it can keep variable that is not re-initialized with the browser action click. So you can use that variable to do your job!
background.js
var isAlreadyInjected =false;
function isInjected(){
if(!isAlreadyInjected ){
isAlreadyInjected=true;
return false;
}
else
return true;
}
popup.js
var bgpage=chrome.extension.getBackgroundPage();
if(!bgpage.isInjected()){
chrome.browserAction.onClicked.addListener(function callback(tab) {chrome.tabs.executeScript(null, {file: "content-script.js"});});
}
or
var bgpage=chrome.extension.getBackgroundPage();
chrome.browserAction.onClicked.addListener(function callback(tab) {
if(!bgpage.isInjected()){
chrome.tabs.executeScript(null, {file: "content-script.js"});
}});
I know this is an older question but I encountered the issue now that Manifest V3 is out and persistent background pages have been replaced with service workers. I figured I'd give what I used as a solution in case anyone else needs it. Code must be executed within the global context of the content script. Anytime it tries to inject it again, the relevant code will only be executed if the global variable is not defined.
if (typeof hasBeenExecuted === 'undefined') {
// Code that needs to execute only once goes here
}
var hasBeenExecuted = true;
Hopefully this is helpful for someone else who comes across the question.

Is it possible to run the injected script without reload?

I did a simple auto form filler, by sending info from a created html to the background and then the content script, so the injected script can change the info on the form.
I know the content script run once the page is load. I want to know if I can run the content script again, without the need of reloading the page.
I got sendRequest function in the content script, that I use to make sure it gets the info, only when the page is ready. It then add the info to the form, and wait for me to send it.
In the content script, I added a onRequest and it works (it get the info). but, I don't see the changes on the form, unless I am realoding the page.
I want to know if it is possible to do and if it does what subjects should I learn to implent this.
I am new to chrome extentions and I am still learning :)
in 1 of the pages, I use jQuery, so an answer with jQuery would be good too.
i found out that if we create a chrome.tabs.sendRequest from background we can use chrome.extestion.onRequest from content script and it will execute every time becuse they both run allmost in the same time.
so i did from background:
chrome.tabs.query({}, function (tabs) {
for (var i = 0; i < tabs.length; i++) {
chrome.tabs.sendRequest(tabs[i].id, {...requests u want to send }, function (response) {
});
}
});
from content script:
chrome.extension.onRequest.addListener(function (request, sender, sendRespons) {
//get requested info here
//call functions.
sendResponse({}); //send info back to background page.
});
form's target could be an iframe which would avoid page reload. not sure how useful it'd be.
The correct way to execute a content script again is by using the chrome.tabs.executeScript method. It receives two arguments. The first argument is the tabId, which can be obtained in many ways, such as one of the chrome.tabs events. Use null to execute the content script in the currently selected tab (caution: this may also be an active dev tools window!).
Examples:
// Reloads the current tab
chrome.tabs.executeScript(null, {code:'location.reload();'});
// Executes contentscript.js in the current tab
chrome.tabs.executeScript(null, {file:'contentscript.js'});
// Executes contentscript.js in all frames in the current tab
chrome.tabs.executeScript(null, {file:'contentscript.js', allFrames: true});
// Receives message from content script, and execute a content script:
chrome.extension.onMessage.addListener(function(details) {
if (details.message === 'load a content script') {
chrome.tabs.executeScript(details.sender.tab.id, {file: 'a_script.js'});
}
});
// The previous one is activated from a content script, as follows:
chrome.extension.sendMessage('load a content script');
(onMessage and sendMessage have to be used instead of onRequest and sendRequest, since Chrome 20)

Categories