.NET 3.5 SP1 enables a Firefox Extension that allows .NET applications on Firefox, but its Uninstall button is grayed out. Since .NET 3.5 SP1 is on Windows Update and is rolling out to more users than ever before, a number of people have noticed this and are frustrated.
Why is the Uninstall button grayed out? What does this add-in do?
First: To uninstall completely, if you're running XP or Vista, here's an update that will make the .NET ClickOnce Extension/Addin easily uninstallable by adding on a per-user, rather than per-machine basis. This update will also go out on Windows Update if you prefer to wait. Alternatively, you can click Disable and it won't be loaded.
This addin allows users to launch .NET applications from Firefox. The primary issue is that the plugin is enabled at the machine level, rather than the user level. This has the benefit of enabling the Addin for all users, but it as the unfortunate side effect of making the "Uninstall" button grayed out which makes people feel that something sinister has happened or they've lost control.
There are other plugins that use this technique to enable themselves machine-wide, including Google Gears and Java, but I totally understand that some folks don't want certain Firefox Addins enabled.
This is a tricky situation, because you'll notice that many applications enable browser plugins in similar ways, including, but not limited to:
There are lots of other applications that include their own browser addins, many install this same way. Most of these addins are in the "Plugins" tab, rather than the Extensions tab, which might explain why fewer people freak out.
My point here is that the "Extensions" tab is something that Firefox users, myself included, feel strongly about, while they think less about plugins. Extensions often affect UI and make Firefox personalizable, while Plugins usually enable system-level functionality. It's a subtle difference, but it's significant and important to remember. Extensions are personalization points for Firefox at the User level, and are thought of as such. Some addins (not ours) mark themselves as Hidden, which could be perceived as even more sinister.
Ultimately since many people requested .NET support for Firefox, and we wanted to give it to them, we made this feature. When .NET was updated, this feature was included.
Here's some more detail and some insight into what we're doing about it.
In case you want to know the technical details, and in the interest of transparency, it's called "MicrosoftDotNetFrameworkAssistant.xpi." You can rename it to .zip and unzip it. It's got XUL and JavaScript inside - it's just a zip file. If you have made a Firefox add-on, this will look familiar. There's an options dialog, standard stuff, keeping track of two booleans as preferences:
var dotNetAssistantOptions = { onAccept: function() { var prefs = this.getPrefs(); var autoLaunchChk = document.getElementById("autolaunch"); prefs.setBoolPref("clickonce.autolaunch", !autoLaunchChk.checked); var allVersionsChk = document.getElementById("allversions"); prefs.setBoolPref("all_clr_versions_in_useragent", allVersionsChk.checked); // Update the user-agent if preferences changed checkCLRVersionString(); return true; }, onLoad: function() { var prefs = this.getPrefs(); var autoLaunchChk = document.getElementById("autolaunch"); autoLaunchChk.checked = !prefs.getBoolPref("clickonce.autolaunch"); var allVersionsChk = document.getElementById("allversions"); allVersionsChk.checked = prefs.getBoolPref("all_clr_versions_in_useragent"); }, getPrefs: function() { return Components.classes["@mozilla.org/preferences-service;1"] .getService(Components.interfaces.nsIPrefService) .getBranch("microsoft.CLR."); }}
There's a contentType handler. This is the meat of it. We see if the mime/type being requested is "application/x-ms-application," indicating the user is trying to launch a .NET ClickOnce application. We then do some path stuff and launch our "sandbox" application that hosts these apps.
var clickOnce = { dialog_onload: function() { // This is the function that would have been called had our overlay not stepped in. dialog.initDialog(); if (this.isClickOnce()) { this.prefs = Components.classes["@mozilla.org/preferences-service;1"] .getService(Components.interfaces.nsIPrefService) .getBranch("microsoft.CLR.clickonce."); if (this.prefs.getBoolPref("autolaunch")) { this.launch_application(); } else { var button = dialog.dialogElement("launchApplicationButton"); button.collapsed = false; // If the user has both this addon and FFClickOnce installed show only our button var ffClickOnceButton = dialog.dialogElement("FFClickOnce-runbutton"); if (ffClickOnceButton) { ffClickOnceButton.collapsed = true; } // Let the dialog know it must resize to accommodate the new button dialog.mDialog.sizeToContent(); } } }, isClickOnce: function() { var mimeInfo = dialog.mLauncher.MIMEInfo; // Checking the file extension may be second guessing the webserver // but when 'prompt' is set the user will still have a save button. if (mimeInfo.MIMEType == "application/x-ms-application" || mimeInfo.primaryExtension == "application") { return true; } return false; }, launch_application: function() { this.execute(this.getPresentationHostLocation(), "-LaunchApplication " + dialog.mLauncher.source.spec); dialog.mDialog.close(); }, getPresentationHostLocation: function() { var nsIProperties = Components.classes["@mozilla.org/file/directory_service;1"] .getService(Components.interfaces.nsIProperties) // Attempting to forward-proof this extension by testing for where PresentationHost // will probably be for CLR 4 var phNewLoc64 = nsIProperties.get("WinD", Components.interfaces.nsILocalFile); phNewLoc64.appendRelativePath("Microsoft.NET\\Framework64\\wpf\PresentationHost.exe"); if (phNewLoc64.exists()) { return phNewLoc64; } var phNewLoc32 = nsIProperties.get("WinD", Components.interfaces.nsILocalFile); phNewLoc32.appendRelativePath("Microsoft.NET\\Framework\\wpf\PresentationHost.exe"); if (phNewLoc32.exists()) { return phNewLoc32; } // Fallback to the default location var phOldLoc = nsIProperties.get("SysD", Components.interfaces.nsILocalFile); phOldLoc.appendRelativePath("PresentationHost.exe"); return phOldLoc; }, execute: function(exe, strArgs) { // exe should already be an nsILocalFile // create an nsIProcess var process = Components.classes["@mozilla.org/process/util;1"] .createInstance(Components.interfaces.nsIProcess); process.init(exe); // Run the process. // If first param is true, calling thread will be blocked until // called process terminates. // Second and third params are used to pass command-line arguments // to the process. var args = strArgs.split(" "); process.run(false, args, args.length); }}
There's also a UserAgent module that will optionally append the .NET CLR version to the UserAgent so that websites can detect and show content based on
// Check for updated .NET Framework install on browser startupcheckCLRVersionString();function checkCLRVersionString(){ var useragentExtrasPref = Components.classes["@mozilla.org/preferences-service;1"] .getService(Components.interfaces.nsIPrefService) .getBranch("general.useragent.extra."); useragentExtrasPref.QueryInterface(Components.interfaces.nsIPrefBranch2); // startup check the registry for the latest version of the CLR added to the IE user agent var versions = getInstalledCLRVersions(); var addonPref = Components.classes["@mozilla.org/preferences-service;1"] .getService(Components.interfaces.nsIPrefService) .getBranch("microsoft.CLR."); addonPref.QueryInterface(Components.interfaces.nsIPrefBranch2); var allAddonsInAgent = addonPref.getBoolPref("all_clr_versions_in_useragent"); // Allow the user to configure whether they want all version of the CLR in their user agent // or just the highest. if (allAddonsInAgent) { var appendString = "("; for (var i=0; i < versions.length; i++) { if (i != 0) { appendString += "; "; } var localVersion = versions[i]; appendString += ".NET " + localVersion[0] + " " + localVersion[1]; } appendString += ")"; useragentExtrasPref.setCharPref("microsoftdotnet", appendString); } else { var latestVersion = versions[versions.length-1]; var appendString = "(.NET " + latestVersion[0] + " " + latestVersion[1] + ")"; useragentExtrasPref.setCharPref("microsoftdotnet", appendString); }}function getInstalledCLRVersions(){ var wrk = Components.classes["@mozilla.org/windows-registry-key;1"] .createInstance(Components.interfaces.nsIWindowsRegKey); // Enumerate though the CLR version strings that will be appended to the Internet Explorer user agent wrk.open(wrk.ROOT_KEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Internet Settings\\5.0\\User Agent\\Post Platform", wrk.ACCESS_READ); var clrVersions = new Array(); for (var i=0; i < wrk.valueCount; i++) { var name = wrk.getValueName(i); var matches = name.match("\.NET (CLR|Client) \(.*\)"); if (matches && matches.length > 1) { clrVersions.push([matches[1], matches[2]]); } } wrk.close(); clrVersions.sort(function(left, right) { if (left[1] < right[1]) return -1; if (left[1] > right[1]) return 1; // if version numbers are equal, prefer full CLR over Client sku if (left[0] == "Client" && right[0] == "CLR") return -1; if (left[0] == "CLR" && right[0] == "Client") return 1; return 0; }); return clrVersions;}
That's it. Hope this helps. I'll keep my ear to the ground if anything else comes up, so stay tuned.
Related Links
Ads by The Lounge