I have a following illustrative extension that will terminate tab process and reload page if user visits "www.ru":
manifest.json
{
"manifest_version" : 2,
"name" : "test extension",
"version" : "1.0.0",
"permissions" : [ "tabs", "experimental" ],
"background" : { "scripts" : [ "extension.js" ], "persistent" : true }
}
extension.js
function reload() {
chrome.tabs.query( {}, function( tabs ) {
for( var i = 0; i < tabs.length; i ++ ) {
if( tabs[ i ].url.match( /www\.ru/ ) ) {
window.setTimeout( function() {
chrome.experimental.processes.getProcessIdForTab( tabs[ i ].id, function( id ) {
chrome.experimental.processes.terminate( id );
})
}, 5000);
window.setTimeout( function() {
chrome.tabs.reload( tabs[ i ].id );
}, 10000);
return;
}
}
window.setTimeout( reload, 1000 );
});
}
window.setTimeout( reload, 1000 );
I start latest google chrome (26.0.1410.64 m 32-bit for windows downloaded from chrome.google.com minutes ago), create second chrome user, switch to that user, install extension, open 'www.ru', switch to first user - and after 10 seconds chrome forcibly switches me back to second user in order to demonstrate tab reload!
Only process kill + tab reload produces this weird behavior (they pause in between can be any length). If terminate or reload parts are commented out, corresponding action is carried but user account is newer switched.
Is it some chrome bug i'm facing or this is documented behavior? Is it possible to prevent forcible account switch? It's very unpleasant for me to be interrupted in the middle of something just to display that some web crawler extension on web crawler account found tab that is not responding and reloaded it :(.
Eye of Hell, this answer is an educated guess. It's possible that Chrome uses a heuristic to determine whether a tab should become the active tab during a load or reload. The heuristic might be that the first load/reload for the tab process should make it the active tab, unless the tab was explicitly created with createProperties active: false. This would make sense if you think of a new tab's default for the active state, and understand that at process creation time (including the process creation after a terminate/reload), the tab is likely set to that default.
If this is true, then instead of reloading the tab after killing its process, perhaps you could do this instead:
Remember the about-to-be-killed tab's URL.
Kill the process and close the tab OR just close the tab.
chrome.tabs.create({url: savedUrl, active: false}).
It's possible that this has some other undesirable side effects, such as losing session cookies. But without knowing more about your use case, this seems like an equivalent approach to what you're trying to do.
By the way, your recovery process could be faster. Rather than setting the 5-second and 10-second timeouts to serialize the kill/reload operations, you can chain the reload (or create) operation in the terminate function's callback. Then it's guaranteed to be called after the terminate is complete, and it won't waste wall-clock time waiting for the extra five seconds.
Related
I'm working on a simple link sharing extension (pinboard, readability, delicious, etc), and have a question about how to properly deal with a context menu item. In my non-persistent background page I call chrome.contextMenus.create and chrome.contextMenus.onClicked.addListener to setup/respond to the context menu.
The context menu entry works as expected. But the background page is showing the following error (right after it starts and before I've used the entry) :
contextMenus.create: Cannot create item with duplicate id id_share_link at chrome-extension://.../share.js:52:30 lastError:29 set
This made me realize that at no point do I remove the item or the listener. Knowing little about javascript and extensions, I'm left wondering if I'm doing everything correctly. I'm assuming this top-level code is going to re-execute every time the background page is invoked. So there are going to be redundant calls to create and addListener (and hence the error I see being logged).
I clearly can't do cleanup in response to suspend, as these calls need to be present to wake up the background script.
Should I be handling things differently?
If you want to use an event page, ie a non-persistent background page, as you call it, you should register a context menu via contextMenus.create in the event handler of runtime.onInstalled, as these context menu registrations ”persist“ anyways.
You have to add the listener-function for the contextMenus.onClicked event every time the event page gets reloaded, though, as the registration of your wish to listen on that event persists, while the handler callback itself does not. So generally don't call contextMenus.onClicked.addListener from runtime.onInstalled, but from top level or other code, that is guaranteed to be executed each time the event page loads.[1]
You can handle it one of two ways:
You can add the context menu and the listeners on install using:
chrome.runtime.onInstalled.addListener(function() {
/* Add context menu and listener */
});
You can remove the context menu and listener, and then re-add it each time the file is called.
[solution may no longer be the case, read comment]
runtime.onInstalled is not triggered if you disable/enable your extension.
My solution is to always add menu items and swallow errors:
'use strict';
{
let seqId = 0;
const createMenuLinkEntry = (title, tab2url) => {
const id = (++seqId).toString();
chrome.contextMenus.create({
id: id,
title: title,
contexts: ['browser_action'],
}, () => {
const err = chrome.runtime.lastError;
if(err) {
console.warn('Context menu error ignored:', err);
}
});
};
createMenuLinkEntry('Go to Google', (tab) => 'https://google.com');
createMenuLinkEntry('Go to GitHub', (tab) => 'https://github.com');
} // namespace
I've been attempting to write a very simple Chrome extension (manifest v3) to automatically close those annoying tabs zoom leaves open after you join a meeting.
So far I have been able to get most pages to automatically close with my extension but it simply refuses to run on certain domains, including the one I actually need it to run on: https://company-name-here.zoom.us/. I ultimately would like to set the content script matchers to just zoom but for now I have expanded it to all sites in an effort to reduce sources of error.
It is not working no matter how I attempt to load the page, be it by clicking the redirect url on a google calendar event, reloading the page manually after it has already been opened, and even manually typing out the url and hitting enter. The zoom home page suffers from the same problem but other sites such as stack overflow show the "Content script loaded" console log and close in 5 seconds as I would expect.
Please find the entire source for the extension below:
manifest.json
{
"manifest_version": 3,
"name": "Zoom Auto Closer",
"version": "1.0",
"background": {
"service_worker": "src/background.js"
},
"content_scripts": [{
"run_at": "document_start",
"matches": ["<all_urls>"],
"js": ["src/content.js"]
}]
}
src/content.js
const closeDelay = 5_000;
const closeCurrentTab = () => chrome.runtime.sendMessage('close-tab');
const main = () => {
console.log('Content script loaded');
setTimeout(closeCurrentTab, closeDelay);
};
main();
src/background.js
const closeTab = tabId => chrome.tabs.remove(tabId);
const onMessage = (message, sender) => {
console.log('Received a message:', message);
switch (message) {
case 'close-tab': return closeTab(sender.tab.id);
}
}
const main = () => {
console.log('Service worker registered');
chrome.runtime.onMessage.addListener(onMessage);
}
main();
The issue might be with the usage of <all_urls>.
Google says on the matching patterns docs:
The special pattern <all_urls> matches any URL that starts with a
permitted scheme.
And the permitted schemes are http:, https:, and file:.
I am not too familiar with Zoom, but this article suggests that zoom uses the protocol zoommtg: to launch the the desktop program, so this falls outside of what <all_urls> covers.
Edit:
Now I see that you stated that the urls start with https:// so that might invalidate what I suggested. Still might be worth trying "*://*.zoom.us/*" instead of <all_urls>.
You could try using "*://*.zoom.us/*" instead. If that doesn't work you could try ditching the content script and handling everything in the background service worker.
In the background service worker, you could add a listener for chrome.tabs.onUpdated and check the url value to see if it matches the url for a Zoom tab and close it from there. You would also need to use the Alarms API for the delay.
This method wouldn't be as efficient because it is called on every tab update (not really worth worrying about), but it is a possible workaround if you can't get the content script method to work.
I want to build a extension that behaves like a timer. It should count down the seconds when activated, but should do nothing with inactive.
The chrome.alarms API is interesting, but does not have enough precision nor granularity. It only fires at most once per minute, and it may fire late. If I want something to execute more often than that, I can't use this API.
Then, the next natural solution is to use a background page and use setTimeout or setInterval in there. However, background pages are persistent, and they take up resources (e.g. memory) even when idle. So they are not ideal.
The best solution seems to be an event page to run the timer. However, the documentation says:
Once it has been loaded, the event page will stay running as long as it is active (for example, calling an extension API or issuing a network request).
[…]
Once the event page has been idle a short time (a few seconds), the runtime.onSuspend event is dispatched. The event page has a few more seconds to handle this event before it is forcibly unloaded.
[…]
If your extension uses window.setTimeout() or window.setInterval(), switch to using the alarms API instead. DOM-based timers won't be honored if the event page shuts down.
Unfortunately, having an active setInterval is not enough to consider an event page active. In fact, from my tests, an interval up to 10 seconds is short enough to keep the event page running, but anything greater than 10 or 15 seconds is too far apart and the event page will get unloaded. I've tested this on my crx-reload-tab project.
I believe what I want is a middle ground:
I want a background page that I can load and unload on demand. (Instead of one that keeps loaded all the time.)
I want an event page that stays persistent in memory for as long as I say; but otherwise could be unloaded. (Instead of one that gets unloaded automatically by the browser.)
Is it possible? How can I do it?
Background pages cannot be unloaded on demand, and Chrome decides Event page lifecycle for you (there is nothing you can do in onSuspend to prevent it).
If your concern is timers, you could try my solution from this answer, which basically splits a timer into shorter timers for a "sparse" busy-wait. That's enough to keep the event page loaded and is a viable solution if you don't need to do that frequently.
In general, there are some things that will keep an event page loaded:
If you're using message passing, be sure to close unused message ports. The event page will not shut down until all message ports are closed.
This can be exploited if you have any other context to keep an open Port to, for example a content script. See Long-lived connections docs for more details.
In practice, if you often or constantly need precise, sub-minute timers, an Event page is a bad solution. Your resource gains from using one might not justify it.
As mentioned in Xan's answer we can abuse messaging. There's nothing wrong about it either in case you want to temporarily prevent the event page from unloading. For example while displaying a progress meter using chrome.notifications API or any other activity based on setTimeout/setInterval that may exceed the default unload timeout which is 5-15 seconds.
Demo
It creates an iframe in the background page and the iframe connects to the background page. In addition to manifest.json and a background script you'll need to make two additional files bg-iframe.html and bg-iframe.js with the code specified below.
manifest.json excerpt:
"background": {
"scripts": ["bg.js"],
"persistent": false
}
bg.js:
function preventUnload() {
let iframe = document.querySelector('iframe');
if (!iframe) {
iframe = document.createElement('iframe');
document.body.appendChild(iframe).src = 'bg-iframe.html';
}
}
function allowUnload() {
let iframe = document.querySelector('iframe');
if (iframe) iframe.remove();
}
chrome.runtime.onConnect.addListener(() => {});
bg-iframe.html:
<script src="bg-iframe.js"></script>
bg-iframe.js:
chrome.runtime.connect();
Usage example in bg.js:
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message === 'start') doSomething();
});
function doSomething() {
preventUnload();
// do something asynchronous that's spread over time
// like for example consecutive setTimeout or setInterval calls
let ticks = 20;
const interval = setInterval(tick, 1000);
function tick() {
// do something
// ................
if (--ticks <= 0) done();
}
function done() {
clearInterval(interval);
allowUnload();
}
}
I use this function:
function _doNotSleep() {
if (isActive) {
setTimeout(() => {
fetch(chrome.runtime.getURL('manifest.json'));
_doNotSleep();
}, 2000);
}
}
But the problem with such approach is that Devtools network tab polluted with this http stub.
I got a quick question for you guys, I'm new to making Chrome Extensions and the idea I have for one I'm not sure if I can do this with an extension or not. I've been looking through the API but haven't run across something that might help. So my idea for my extension is that whoever downloads the extension will be able to set a pin code they will click the icon and it basically will lock down the browser so if someone else came to the browser they would only be able to access that one page and what it would lead to, they wouldn't be able to us the url bar or have access to the tabs unless permitted.Then the owner can press a hot key and it will ask them for there pin and will unlock the browser if need be.Or even put it in the presentation mode but not able to get out of it without a password? Is this something a chrome extension could do or am I going at this the wrong way? I noticed there are some options in the Chrome://about settings where you can compact the url bar and also make the tabs on the side bar. Any help or direction for this would be great, thanks!
You can create an options page where the extension settings are saved, and then create an option called eg DisableBrowser.
In file background.js, we monitor the onBeforeRequest event, and then check the value of variable DisableBrowser if it has true value, set the value of cancel parameter onBeforeRequest event, being equal to true when cancel is equal value to true, the request is canceled.
In short, just cancel and set equal to true and everything is rejected, ie, the browser will not open urls while the extension is installed and enabled.
Update:
The sample code below is the content of background.js file, showing how to allow only certain urls that are allowed in a list is executed successfully, and consequently all other urls will be denied and fails when opened.
// callback
var onBeforeRequestCallback = function( details ) {
// List of Urls Allowed
// You can create an array or use localStorage through options.html page,
// to save the urls allowed,
// then check and if an allowed URL, the request is not canceled, or in other words, it is permitted,
// in case of failure it is canceled and is not permitted.
if ( details.url === 'https://www.google.com/' || details.url === 'http://www.bing.com/' ) {
return {
cancel : false
};
} else {
return {
cancel : true
};
}
};
// filter
var onBeforeRequestFilter = {
urls : [
"http://*/*",
"https://*/*"
]
};
// opt_extraInfoSpec
var onBeforeRequestInfo = [
"blocking",
"requestBody"
];
// Monitors onBeforeRequest event
chrome.webRequest.onBeforeRequest.addListener( onBeforeRequestCallback, onBeforeRequestFilter, onBeforeRequestInfo );
Help Links:
options
background
onBeforeRequest
localStorage
I have a single page app written in pure HTML with no external plugings and using a full screen chromium for the client. (kiosk mode)
Though Chromium itself is very stable with very minor crashes, But I would like to have it restarted to the page, if it really crashes.
My thought this should be done with an external process like a watchdog, But how does the external monitor chrome? since there are several individual processes in the process table. Sometimes even it crashes, the process still alive out there.
Any suggestion or mature soutuion?
I just solved this myself. At first I tried using the processes API, but that isn't in the version of chrome I'm using on the kiosk. I've configured my kiosk with an extension that I wrote to handle other stuff (TUIO touch input, etc.), so I already had a place to add it.
My kiosk is running a little web server locally, because I found getting chromium to show file:// urls was just too much of a pain in the neck. If you are using file URLs, then your manifest will need to match those, instead of http URLs.
Here's the critical stuff from manifest.json:
"content_scripts": [
{
"matches": ["http://*/*"],
"js": ["kiosk.js"]
}
],
"background": {
"scripts": [
"background.js"
]
},
"permissions": [
"webNavigation",
"tabs",
"runtime",
"<all_urls>"
],
This goes into kiosk.js:
chrome.runtime.onMessage.addListener(
function(message, sender, response) {
response(message);
}
);
Basically, it's a ping responder. If you send it a message, it sends it right back.
Here is background.js in its entirety:
var tab_id = -1;
var send_count = 0;
var recv_count = 0;
chrome.webNavigation.onBeforeNavigate.addListener(function (details) {
tab_id = details.tabId;
});
setInterval(function() {
if (tab_id == -1) return;
if (send_count > recv_count+2) {
chrome.tabs.reload(tab_id);
send_count = recv_count = 0;
}
++send_count;
chrome.tabs.sendMessage(tab_id, "heartbeat", function(resp) {
if (resp) {
recv_count = send_count;
}
});
}, 1000);
It listens for my page showing up, and grabs the tab ID. It pings the responder. The documentation says that if an error happens, sendMessage will get called with no response, but that's not true. It actually doesn't get called at all. I coded it to handle either case.
Note that I originally said ++recv_count in the handler, but if you think about it, the above will be a little more robust to slowness on the receiving page.
Assuming your kiosk is linux (that is, you aren't insane), then you can test this easily enough by ssh'ing into your kiosk, and doing a ps axfww |grep render and then kill the first process listed. You'll see the sick computer screen for a sec, and then it will reload.