I want to create an extension that redirects the user to another website if he clicks on the extension button. So far I have only seen extensions which create a new tab for each click.
Is it possible to redirect the user to another website using the active tab?
I tried something like this:
chrome.browserAction.onClicked.addListener(function(tab) {
var url = "https://www.mipanga.com/Content/Submit?url="
+ encodeURIComponent(tab.url)
+ "&title=" + encodeURIComponent(tab.title);
document.location.href = url; // <-- this does not work
});
Attention: If you develop cross-browser extensions (I hope you do!), I recommend that you use chrome.tabs.query(). Please see Jean-Marc Amon's answer for more information. This answer still works in both Firefox and Chrome, but query() is more commonly used, has more options, and works in background pages and popup views.
From the chrome.tabs API, you can use getCurrent(), query(), or update().
Right now, I prefer update() as this allows you to update the current tab without needing to do much else.
NB: You cannot use update() from content scripts.
If updating the url from a content script is required then you should look to use query instead. Jean-Marc Amon's answer provides a wonderful example of how to get the active tab in this case (don't forget to upvote him!).
update()
let myNewUrl = `https://www.mipanga.com/Content/Submit?url=${encodeURIComponent(tab.url)}&title=${encodeURIComponent(tab.title)}`;
chrome.tabs.update(undefined, { url: myNewUrl });
Here, we have set the first argument of update to undefined. This is the tab id that you're wanting to update. If it's undefined then Chrome will update the current tab in the current window.
Please see Domino's answer for more information on update and also note that undefined is not needed. Again, please don't forget to upvote their answer as wellif you find it useful.
getCurrent()
getCurrent also cannot be called from a non-tab context (eg a background page or popup view).
Once you have the current tab, simply pass update().
chrome.tabs.getCurrent(function (tab) {
//Your code below...
let myNewUrl = `https://www.mipanga.com/Content/Submit?url=${encodeURIComponent(tab.url)}&title=${encodeURIComponent(tab.title)}`;
//Update the url here.
chrome.tabs.update(tab.id, { url: myNewUrl });
});
NB: In order to use this this functionality, you must ensure that you have the tabs permission enabled in your manifest.json file:
"permissions": [
"tabs"
],
You can use chrome.tabs.query too
chrome.tabs.query({currentWindow: true, active: true}, function (tab) {
chrome.tabs.update(tab.id, {url: your_new_url});
});
The chrome.tabs.update method will automatically run on the current active tab if no tab id is passed.
This has the added advantage of not requiring the tabs permission. Extensions with this permission warn the user that they can read the browsing history, so you should avoid asking for it if you don't need to.
Changing the current tab's URL is as simple as writing this:
chrome.tabs.update(undefined, {url: 'http://example.com'});
Or as mentionned by farwayer in the comments, you don't need to put two arguments at all.
chrome.tabs.update({url: 'http://example.com'});
The answers given here no longer work: the Chrome Tabs API can no longer be used by content scripts, only by service workers and extension pages.
Instead, you can send a message to a service worker to get it to update the location of the current tab: see https://stackoverflow.com/a/62461987.
See this for a simple working example.
Related
Overview of the problem
I cannot access the DOM of a window that I open programmatically in my chrome extension. I believe this may be due to origin/cross-site scripting restrictions. I have tried to give it the maximal permissions I can, including "<all_urls>", "activeTab" and "tabs"
I also played around with "content_security_policy_" settings but from the documentation I was hoping rather than expecting that to help.
I have a simple extension that runs code when the button is clicked, I want to open a few different tabs (but from different domains) and then access the DOM of each. However, it only seems to work if I'm on a tab of a domain and then I open another tab of the same of domain. Then I can access, for example, window onload of the new tab. I have no luck when it is a different domain.
e.g. if I press the button with activeTab "foo.com" then if it window.opens a new tab "foo.com/something" then I can access the document of that opened tab. But if it was "bar.com" then I wouldn't be able to access "foo.com/something"'s DOM
p.s. please note that executeScripts is used instead of manifest content scripts because it is necessary for my code to work. I must inject at least some of the files there this way, otherwise my code will not work (for reasons that are not completely apparent in the example)
my Question
What's way(s) can I get around this - I mean be able to access the DOM of any tab that I open, regardless of what site is in the active tab when the extension button is pressed in the toolbar?
Should I inject content scripts into a a tab that has been opened with window.open and somehow pass its document to Code.js? If so, how could I go about doing that? Can I somehow pass the document to the background.js? and somehow pass it to the injected Code.js?
If this will work (get around security restrictions) then can these content scripts be injected programatically (I don't know exactly what sites they will be until runtime, so I can't really specify them in the manifest)
or is there some way I can just relax the security restrictions and be able to directly access window.open's returned window's document? (which as I mentioned above currently only works on same-domain sites)
background.js
// this is the background code...
// listen for our browerAction to be clicked
chrome.browserAction.onClicked.addListener(function (tab) {
executeScripts(null, [ // jquery can be inserted here: https://stackoverflow.com/questions/21317476/how-to-use-jquery-in-chrome-extension
{ file: "lib/jquery-3.2.1.js" },
{ file: "lib/kotlin.js" },
{ file: "Code.js" } // don't include it in manifest "content_scripts" because it gives an error about kotlin.js not present that way
])
});
/*
* https://stackoverflow.com/questions/21535233/injecting-multiple-scripts-through-executescript-in-google-chrome
*/
function executeScripts(tabId, injectDetailsArray)
{
function createCallback(tabId, injectDetails, innerCallback) {
return function () {
chrome.tabs.executeScript(tabId, injectDetails, innerCallback);
};
}
var callback = null;
for (var i = injectDetailsArray.length - 1; i >= 0; --i)
callback = createCallback(tabId, injectDetailsArray[i], callback);
if (callback !== null)
callback(); // execute outermost function
}
code flow
background.js
Code.jscalls a function that starts with var w = window.open(url)
then w.addEventListener("load", ...), which is the problem. It only fires if the url belongs to the same domain as the active tab. (onLoad can't be used any-which-way, it never fires that's why I use addEventListener)
notes
I have "manifest_version": 2
I need to inject this way rather than include content scripts in the manifest (I guess I could put the jquery library in the manifest but the others I can't and I prefer to keep all injections together in the "code")
I've got a Chrome extension that creates a popup window that the user needs to use for a login system. You click on the extension icon and it opens up its application (AngularJS in my case). The user then clicks on a button which calls chrome.windows.create() to open a popup.
I would like the main extension app to monitor the URL of that popup for changes.
I create the popup from the extension this way:
chrome.windows.create(
{
url: 'https://some.external.url.com/whatever',
type: 'panel',
width: 600,
height: 600
},
function (windowReference) {
console.log('My Window:', windowReference);
// start monitoring URL of windowReference somehow
// could be as simple as a setInterval() loop
}
)
The problem is that the windowReference object passed to the callback doesn't have the current URL of the popup. Since the user can interact with the page in the popup (I'm pointing it at out OAuth2 system), the URL will change at times. I want to see that - either actively as changes are made, or by simply querying the current URL periodically.
This is what the windowReference object contains:
{
alwaysOnTop:false,
focused:false,
height:600,
id:1089,
incognito:false,
left:61,
state:"normal",
top:23,
type:"popup",
width:600
}
You can see that there is an ID there, and that, to me, suggest that I might be able to use it to call some other method to get the real URL information I'm after.
Any help would be appreciated.
So the answer turns out to be pretty simple. As Rob W mentioned in a comment, you use the chrome.tabs.query() method to do the search as you would for any other tab.
The missing part for me was that you can use the id from the window reference you get when the popup is created to get the desired results from the tabs query:
chrome.tabs.query(
{ windowId: windowReference.id },
function callback(tabs) {
var popup = tabs[0];
$log.debug("Popup URL:", popup.url);
}
);
You can see that I passed the ID as the value of the windowId parameter in the search query object.
I have created a custom URL protocol handler.
http://
mailto://
custom://
I have registered a WinForms application to respond accordingly. This all works great.
But I would like to be able to gracefully handle the case where the user doesn't have the custom URL protocol handler installed, yet.
In order to be able to do this I need to be able to detect the browser's registered protocol handlers, I would assume from JavaScript. But I have been unable to find a way to poll for the information. I am hoping to find a solution to this problem.
Thanks for any ideas you might be able to share.
This would be a very, very hacky way to do this... but would this work?
Put the link in as normal...
But attach an onclick handler to it, that sets a timer and adds an onblur handler for the window
(in theory) if the browser handles the link (application X) will load stealing the focus from the window...
If the onblur event fires, clear the timer...
Otherwise in 3-5seconds let your timeout fire... and notify the user "Hmm, looks like you don't have the Mega Uber Cool Application installed... would you like to install it now? (Ok) (Cancel)"
Far from bulletproof... but it might help?
There's no great cross-browser way to do this. In IE10+ on Win8+, a new msLaunchUri api enables you to launch a protocol, like so:
navigator.msLaunchUri('skype:123456',
function()
{
alert('success');
},
function()
{
alert('failed');
}
);
If the protocol is not installed, the failure callback will fire. Otherwise, the protocol will launch and the success callback will fire.
I discuss this topic a bit further here:
https://web.archive.org/web/20180308105244/https://blogs.msdn.microsoft.com/ieinternals/2011/07/13/understanding-protocols/
This topic is of recent (2021) interest; see https://github.com/fingerprintjs/external-protocol-flooding for discussion.
HTML5 defines Custom scheme and content handlers (to my knowledge Firefox is the only implementor so far), but unfortunately there is currently no way to check if a handler already exists—it has been proposed, but there was no follow-up. This seems like a critical feature to use custom handlers effectively and we as developers should bring attention to this issue in order to get it implemented.
There seems to be no straightforward way via javascript to detect the presence of an installed app that has registered a protocol handler.
In the iTunes model, Apple provides urls to their servers, which then provide pages that run some javascript:
http://ax.itunes.apple.com/detection/itmsCheck.js
So the iTunes installer apparently deploys plugins for the major browsers, whose presence can then be detected.
If your plugin is installed, then you can be reasonably sure that redirecting to your app-specific url will succeed.
What seams the most easy solution is to ask the user the first time.
Using a Javascript confirm dialog per example:
You need this software to be able to read this link. Did you install it ?
if yes: create a cookie to not ask next time; return false and the link applies
if false: window.location.href = '/downloadpage/'
If you have control of the program you're trying to run (the code), one way to see if the user was successful in running the application would be to:
Before trying to open the custom protocol, make an AJAX request to a server script that saves the user's intent in a database (for example, save the userid and what he wanted to do).
Try to open the program, and pass on the intent data.
Have the program make a request to the server to remove the database entry (using the intent data to find the correct row).
Make the javascript poll the server for a while to see if the database entry is gone. If the entry is gone, you'll know the user was successful in opening the application, otherwise the entry will remain (you can remove it later with cronjob).
I have not tried this method, just thought it.
I was able to finally get a cross-browser (Chrome 32, Firefox 27, IE 11, Safari 6) solution working with a combination of this and a super-simple Safari extension. Much of this solution has been mentioned in one way or another in this and this other question.
Here's the script:
function launchCustomProtocol(elem, url, callback) {
var iframe, myWindow, success = false;
if (Browser.name === "Internet Explorer") {
myWindow = window.open('', '', 'width=0,height=0');
myWindow.document.write("<iframe src='" + url + "'></iframe>");
setTimeout(function () {
try {
myWindow.location.href;
success = true;
} catch (ex) {
console.log(ex);
}
if (success) {
myWindow.setTimeout('window.close()', 100);
} else {
myWindow.close();
}
callback(success);
}, 100);
} else if (Browser.name === "Firefox") {
try {
iframe = $("<iframe />");
iframe.css({"display": "none"});
iframe.appendTo("body");
iframe[0].contentWindow.location.href = url;
success = true;
} catch (ex) {
success = false;
}
iframe.remove();
callback(success);
} else if (Browser.name === "Chrome") {
elem.css({"outline": 0});
elem.attr("tabindex", "1");
elem.focus();
elem.blur(function () {
success = true;
callback(true); // true
});
location.href = url;
setTimeout(function () {
elem.off('blur');
elem.removeAttr("tabindex");
if (!success) {
callback(false); // false
}
}, 1000);
} else if (Browser.name === "Safari") {
if (myappinstalledflag) {
location.href = url;
success = true;
} else {
success = false;
}
callback(success);
}
}
The Safari extension was easy to implement. It consisted of a single line of injection script:
myinject.js:
window.postMessage("myappinstalled", window.location.origin);
Then in the web page JavaScript, you need to first register the message event and set a flag if the message is received:
window.addEventListener('message', function (msg) {
if (msg.data === "myappinstalled") {
myappinstalledflag = true;
}
}, false);
This assumes the application which is associated with the custom protocol will manage the installation of the Safari extension.
In all cases, if the callback returns false, you know to inform the user that the application (i.e., it's custom protocol) is not installed.
You say you need to detect the browser's protocol handlers - do you really?
What if you did something like what happens when you download a file from sourceforge? Let's say you want to open myapp://something. Instead of simply creating a link to it, create a link to another HTML page accessed via HTTP. Then, on that page, say that you're attempting to open the application for them. If it doesn't work, they need to install your application, which they can do by clicking on the link you'll provide. If it does work, then you're all set.
This was a recommended approach for IE by Microsoft support
http://msdn.microsoft.com/en-us/library/ms537503%28VS.85%29.aspx#related_topics
"If you have some control over the binaries being installed on a user’s machine, checking the UA in script seems like a relevant approach:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Internet Settings\5.0\User Agent\Post Platform
" -- By M$ support
Every web page has access to the userAgent string and if you drop a custom post platform value, detecting this in javascript using navigator.userAgent is quite simple.
Fortunately, other major browsers like Firefox and Chrome (barring Safari :( ), do not throw "page not found" errors when a link with a custom protocol is clicked and the protocol is not installed on the users machine. IE is very unforgiving here, any trick to click in a invisible frame or trap javascript errors does not work and ends up with ugly "webpage cannot be displayed" error. The trick we use in our case is to inform users with browser specific images that clicking on the custom protocol link will open an application. And if they do not find the app opening up, they can click on an "install" page. In terms of XD this wprks way better than the ActiveX approach for IE.
For FF and Chrome, just go ahead and launch the custom protocol without any detection. Let the user tell you what he sees.
For Safari, :( no answers yet
I'm trying to do something similar and I just discovered a trick that works with Firefox. If you combine it with the trick for IE you can have one that works on both main browsers (I'm not sure if it works in Safari and I know it doesn't work in Chrome)
if (navigator.appName=="Microsoft Internet Explorer" && document.getElementById("testprotocollink").protocolLong=="Unknown Protocol") {
alert("No handler registered");
} else {
try {
window.location = "custom://stuff";
} catch(err) {
if (err.toString().search("NS_ERROR_UNKNOWN_PROTOCOL") != -1) {
alert("No handler registered");
}
}
}
In order for this to work you also need to have a hidden link somewhere on the page, like this:
<a id="testprotocollink" href="custom://testprotocol" style="display: none;">testprotocollink</a>
It's a bit hacky but it works. The Firefox version unfortunately still pops up the default alert that comes up when you try to visit a link with an unknown protocol, but it will run your code after the alert is dismissed.
You can try something like this:
function OpenCustomLink(link) {
var w = window.open(link, 'xyz', 'status=0,toolbar=0,menubar=0,height=0,width=0,top=-10,left=-10');
if(w == null) {
//Work Fine
}
else {
w.close();
if (confirm('You Need a Custom Program. Do you want to install?')) {
window.location = 'SetupCustomProtocol.exe'; //URL for installer
}
}
}
This is not a trivial task; one option might be to use signed code, which you could leverage to access the registry and/or filesystem (please note that this is a very expensive option). There is also no unified API or specification for code signing, so you would be required to generate specific code for each target browser. A support nightmare.
Also, I know that Steam, the gaming content delivery system, doesn't seem to have this problem solved either.
Here's another hacky answer that would require (hopefully light) modification to your application to 'phone home' on launch.
User clicks link, which attempts to launch the application. A unique
identifier is put in the link, so that it's passed to the
application when it launches. Web app shows a spinner or something of that nature.
Web page then starts checking for a
'application phone home' event from an app with this same unique ID.
When launched, your application does an HTTP post to your web app
with the unique identifier, to indicate presence.
Either the web page sees that the application launched, eventually, or moves on with a 'please download' page.
I try to write a Google Chrome extension that simply opens a new tab when I click left-right in a short interval. The JavaScript is no problem but I implemented this as a "content_scripts" script.
In some other threads I read that I can't access the chrome.* APIs from content_scripts (except the chrome.extension API).
Even if it's not necessary to access the chrome.tabs API to open a new window (window.open should do the job) it seems I need it though for opening a new tab with the new tab page which obviously isn't possible via window.open.
So I can't really figure out what is the best way to do that. I could use a background page which I could call from the content_script but I think there should be a much more simple way to do that, I just don't get it.
Anyone have an idea?
I think your content script will have to send a message to your background page to invoke chrome.tabs.create - content scripts cannot use the chrome api, nor can they directly communicate with the background page.
Here's a reference about message passing inside Chrome extensions for further detail, but here's the example code ( modified from the example in said reference )
// in background
chrome.extension.onMessage.addListener(
function(request, sender, sendResponse) {
switch ( request.action) {
case 'newTab' : {
//note: passing an empty object opens a new blank tab,
//but an object must be passed
chrome.tabs.create({/*options*/});
// run callback / send response
} break;
}
return true; //required if you want your callback to run, IIRC
});
// in content script:
chrome.extension.sendMessage({action: "newTab"}, function(response) {
//optional callback code here.
});
simple and easy
document.body.onclick = function openNewWindow( ) {
window.location.href = 'javascript:void window.open( "chrome://newtab" )';
}
manifest:
,"permissions":[
"http://*/*"
,"https://*/*"
]
,"manifest_version": 2
,"content_scripts":[{
"matches":[
"http://*/*"
,"https://*/*"
]
,"js":[
"js/openWindow.js"
]
}]
alright i miss understanding the question... modified
I have created a custom URL protocol handler.
http://
mailto://
custom://
I have registered a WinForms application to respond accordingly. This all works great.
But I would like to be able to gracefully handle the case where the user doesn't have the custom URL protocol handler installed, yet.
In order to be able to do this I need to be able to detect the browser's registered protocol handlers, I would assume from JavaScript. But I have been unable to find a way to poll for the information. I am hoping to find a solution to this problem.
Thanks for any ideas you might be able to share.
This would be a very, very hacky way to do this... but would this work?
Put the link in as normal...
But attach an onclick handler to it, that sets a timer and adds an onblur handler for the window
(in theory) if the browser handles the link (application X) will load stealing the focus from the window...
If the onblur event fires, clear the timer...
Otherwise in 3-5seconds let your timeout fire... and notify the user "Hmm, looks like you don't have the Mega Uber Cool Application installed... would you like to install it now? (Ok) (Cancel)"
Far from bulletproof... but it might help?
There's no great cross-browser way to do this. In IE10+ on Win8+, a new msLaunchUri api enables you to launch a protocol, like so:
navigator.msLaunchUri('skype:123456',
function()
{
alert('success');
},
function()
{
alert('failed');
}
);
If the protocol is not installed, the failure callback will fire. Otherwise, the protocol will launch and the success callback will fire.
I discuss this topic a bit further here:
https://web.archive.org/web/20180308105244/https://blogs.msdn.microsoft.com/ieinternals/2011/07/13/understanding-protocols/
This topic is of recent (2021) interest; see https://github.com/fingerprintjs/external-protocol-flooding for discussion.
HTML5 defines Custom scheme and content handlers (to my knowledge Firefox is the only implementor so far), but unfortunately there is currently no way to check if a handler already exists—it has been proposed, but there was no follow-up. This seems like a critical feature to use custom handlers effectively and we as developers should bring attention to this issue in order to get it implemented.
There seems to be no straightforward way via javascript to detect the presence of an installed app that has registered a protocol handler.
In the iTunes model, Apple provides urls to their servers, which then provide pages that run some javascript:
http://ax.itunes.apple.com/detection/itmsCheck.js
So the iTunes installer apparently deploys plugins for the major browsers, whose presence can then be detected.
If your plugin is installed, then you can be reasonably sure that redirecting to your app-specific url will succeed.
What seams the most easy solution is to ask the user the first time.
Using a Javascript confirm dialog per example:
You need this software to be able to read this link. Did you install it ?
if yes: create a cookie to not ask next time; return false and the link applies
if false: window.location.href = '/downloadpage/'
If you have control of the program you're trying to run (the code), one way to see if the user was successful in running the application would be to:
Before trying to open the custom protocol, make an AJAX request to a server script that saves the user's intent in a database (for example, save the userid and what he wanted to do).
Try to open the program, and pass on the intent data.
Have the program make a request to the server to remove the database entry (using the intent data to find the correct row).
Make the javascript poll the server for a while to see if the database entry is gone. If the entry is gone, you'll know the user was successful in opening the application, otherwise the entry will remain (you can remove it later with cronjob).
I have not tried this method, just thought it.
I was able to finally get a cross-browser (Chrome 32, Firefox 27, IE 11, Safari 6) solution working with a combination of this and a super-simple Safari extension. Much of this solution has been mentioned in one way or another in this and this other question.
Here's the script:
function launchCustomProtocol(elem, url, callback) {
var iframe, myWindow, success = false;
if (Browser.name === "Internet Explorer") {
myWindow = window.open('', '', 'width=0,height=0');
myWindow.document.write("<iframe src='" + url + "'></iframe>");
setTimeout(function () {
try {
myWindow.location.href;
success = true;
} catch (ex) {
console.log(ex);
}
if (success) {
myWindow.setTimeout('window.close()', 100);
} else {
myWindow.close();
}
callback(success);
}, 100);
} else if (Browser.name === "Firefox") {
try {
iframe = $("<iframe />");
iframe.css({"display": "none"});
iframe.appendTo("body");
iframe[0].contentWindow.location.href = url;
success = true;
} catch (ex) {
success = false;
}
iframe.remove();
callback(success);
} else if (Browser.name === "Chrome") {
elem.css({"outline": 0});
elem.attr("tabindex", "1");
elem.focus();
elem.blur(function () {
success = true;
callback(true); // true
});
location.href = url;
setTimeout(function () {
elem.off('blur');
elem.removeAttr("tabindex");
if (!success) {
callback(false); // false
}
}, 1000);
} else if (Browser.name === "Safari") {
if (myappinstalledflag) {
location.href = url;
success = true;
} else {
success = false;
}
callback(success);
}
}
The Safari extension was easy to implement. It consisted of a single line of injection script:
myinject.js:
window.postMessage("myappinstalled", window.location.origin);
Then in the web page JavaScript, you need to first register the message event and set a flag if the message is received:
window.addEventListener('message', function (msg) {
if (msg.data === "myappinstalled") {
myappinstalledflag = true;
}
}, false);
This assumes the application which is associated with the custom protocol will manage the installation of the Safari extension.
In all cases, if the callback returns false, you know to inform the user that the application (i.e., it's custom protocol) is not installed.
You say you need to detect the browser's protocol handlers - do you really?
What if you did something like what happens when you download a file from sourceforge? Let's say you want to open myapp://something. Instead of simply creating a link to it, create a link to another HTML page accessed via HTTP. Then, on that page, say that you're attempting to open the application for them. If it doesn't work, they need to install your application, which they can do by clicking on the link you'll provide. If it does work, then you're all set.
This was a recommended approach for IE by Microsoft support
http://msdn.microsoft.com/en-us/library/ms537503%28VS.85%29.aspx#related_topics
"If you have some control over the binaries being installed on a user’s machine, checking the UA in script seems like a relevant approach:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Internet Settings\5.0\User Agent\Post Platform
" -- By M$ support
Every web page has access to the userAgent string and if you drop a custom post platform value, detecting this in javascript using navigator.userAgent is quite simple.
Fortunately, other major browsers like Firefox and Chrome (barring Safari :( ), do not throw "page not found" errors when a link with a custom protocol is clicked and the protocol is not installed on the users machine. IE is very unforgiving here, any trick to click in a invisible frame or trap javascript errors does not work and ends up with ugly "webpage cannot be displayed" error. The trick we use in our case is to inform users with browser specific images that clicking on the custom protocol link will open an application. And if they do not find the app opening up, they can click on an "install" page. In terms of XD this wprks way better than the ActiveX approach for IE.
For FF and Chrome, just go ahead and launch the custom protocol without any detection. Let the user tell you what he sees.
For Safari, :( no answers yet
I'm trying to do something similar and I just discovered a trick that works with Firefox. If you combine it with the trick for IE you can have one that works on both main browsers (I'm not sure if it works in Safari and I know it doesn't work in Chrome)
if (navigator.appName=="Microsoft Internet Explorer" && document.getElementById("testprotocollink").protocolLong=="Unknown Protocol") {
alert("No handler registered");
} else {
try {
window.location = "custom://stuff";
} catch(err) {
if (err.toString().search("NS_ERROR_UNKNOWN_PROTOCOL") != -1) {
alert("No handler registered");
}
}
}
In order for this to work you also need to have a hidden link somewhere on the page, like this:
<a id="testprotocollink" href="custom://testprotocol" style="display: none;">testprotocollink</a>
It's a bit hacky but it works. The Firefox version unfortunately still pops up the default alert that comes up when you try to visit a link with an unknown protocol, but it will run your code after the alert is dismissed.
You can try something like this:
function OpenCustomLink(link) {
var w = window.open(link, 'xyz', 'status=0,toolbar=0,menubar=0,height=0,width=0,top=-10,left=-10');
if(w == null) {
//Work Fine
}
else {
w.close();
if (confirm('You Need a Custom Program. Do you want to install?')) {
window.location = 'SetupCustomProtocol.exe'; //URL for installer
}
}
}
This is not a trivial task; one option might be to use signed code, which you could leverage to access the registry and/or filesystem (please note that this is a very expensive option). There is also no unified API or specification for code signing, so you would be required to generate specific code for each target browser. A support nightmare.
Also, I know that Steam, the gaming content delivery system, doesn't seem to have this problem solved either.
Here's another hacky answer that would require (hopefully light) modification to your application to 'phone home' on launch.
User clicks link, which attempts to launch the application. A unique
identifier is put in the link, so that it's passed to the
application when it launches. Web app shows a spinner or something of that nature.
Web page then starts checking for a
'application phone home' event from an app with this same unique ID.
When launched, your application does an HTTP post to your web app
with the unique identifier, to indicate presence.
Either the web page sees that the application launched, eventually, or moves on with a 'please download' page.