Thursday, August 30, 2012

Package-registered browsers/previewers in Visual Studio

Over the last months I’ve been asked twice how should a Visual Studio package register a new standard previewer that will show up in the BrowseWith… dialog. It turns out that MSDN documentation is not very explicit about what’s possible and what’s not. Also, there are a couple of holes in Manage Packet Framework (MPF) that will need additional explanation to avoid them. So here’s this article and sample hoping to bring some light.

In Visual Studio, when you right click a html file in Solution Explorer and choose BrowseWith… you can access a dialog listing all system-installed browsers (auto-detected by Visual Studio), package-registered browsers or browsers added explicitly by the user.




For user-registered browsers, in Visual Studio 2012 the “Add program” dialog has been changed to allow specifying explicitly program’s arguments, and the code has been changed to support saving and passing the arguments at the browser’s invocation time. (To some extent arguments could have been passed in VS2010 as well in the Program edit box, but that was not evident to the user, plus there were bugs that caused the arguments to be discarded in various situations).

Packages can also register browsers via the SVsUIShellOpenDocument service. Here is how adding a new browser is supposed to work:

1) the package should write a key under “Visual Studio\11.0\AddStandardPreviewer” with the package Guid. Unfortunately there is no RegistrationAttribute helper class in MPF that can be used to declare these keys, so you’d have to write your own.

2) at the appropriate time, the environment parses the registry entries and calls ResetDefaults, passing in a value of PKGRF_ADDSTDPREVIEWER for the grfFlags parameter on the VSPackage. Unfortunately, the Package class in MPF already implements ResetDefaults() and does not allow derived classes to override virtual functions for registering previewers.

3) at that point the VSPackage should call IVsUIShellOpenDocument.AddStandardPreviewer. The function however does not have an explicit argument for passing browser’s arguments (it only has ‘string pszExePath’ suggesting the browser’s executable and not the actual command line string), so it may not be clear this thing is possible/supported.

To demonstrate how a package can register a browser, I wrote a sample, that implements a package registering 2 browsers: Internet Explorer InPrivate (allowing InPrivate previewing of the file of interest) and Internet Explorer Kiosk (which opens the browser without toolbars, menus, status bar, tabs, etc. – to close the browser use Alt-F4).



The sample defines a registration attribute, ProvideStandardPreviewerAttribute, that can be used on the package class to declare the package will register standard previewers. Hopefully, future versions of MPF will include such class so you won’t have to write your own.


Then, the package will have to re-implement the IVsPackage interface as shown in the above picture, and re-implement the ResetDefaults method so it can intercept the calls with __VSPKGRESETFLAGS.PKGRF_ADDSTDPREVIEWER flag


As for the browser’s invocation, when registering a standard previewer, the first argument in the AddStandardPreviewer allows specifying command line arguments for the previewer in addition to the path to the browser's executable. If there are no quotes in the path, the whole string is assumed to be the full path to the binary exe file of the previewer. However, if the pszExePath string beings with a quote (quotes are used around the executable binary) then arguments can be added separated by a space from the quoted filename.

%1, %URL or %url can be used in the arguments list, and Visual Studio will replace it at runtime with the name of the file to be previewed, (as a URL-formatted quoted string, e.g. "file:///C:/temp/My%20File.txt" ).  If the arguments don't contain either of %1, %URL, %url, then Visual Studio will append the file name to be previewed to the pszExePath string before launching the previewer process with that concatenated string as arguments.
Multiple previewers can be registered with the same executable path if the list of arguments passed are different.

The 2 browsers registered by the sample invoke “IExplore.exe –k [filename]” for the Kiosk mode and “IExplore.exe –private [filename]” for InPrivate browsing, as described in Internet Explorer Command-Line Options MSDN article.

And finally, a few words about the system browsers listed in the dialog. When the OpenWith… dialog is displayed, Visual Studio will try to detect the browsers installed on the system. These browsers cannot be deleted from the dialog (well, you can delete them but they will re-detected and will appear back next time the dialog is reopen, unless the browser has been uninstalled at that point). In order to detect system browsers, Visual Studio looks in a couple of known locations:

- The shell\open\command for the ProgID associated with the http protocol for the current user or with the .htm files.

- The Default value under HKLM\Software\Microsoft\Windows\CurrentVersion\App Paths\[Executable.exe] or HKCU\Software\Microsoft\Windows\CurrentVersion\App Paths\[Executable.exe]

- HKCR\Applications\[Executable.exe]\Shell\Open\Command, for a couple of known executable of browsers with highest market use share.

- HKCU\SOFTWARE\Clients\StartMenuInternet and HKLM\SOFTWARE\Clients\StartMenuInternet, as described in this MSDN article

Some browsers don’t write any version-independent registry values that can be used to locate their executable, hence they are not listed in the dialog unless they are also made the default system browser. Current examples of browsers with bad registration are SRWare Iron, Maxthon, Lynx. System browsers are encouraged to write their registration under StartMenuInternet so they can be detected by Visual Studio and Windows (and be listed in Windows’ Change Program Defaults dialogs as well).

The sample in this article can be downloaded from:

No comments: