Tuesday, March 24, 2009

spicIE: Writing IE 7 and IE 8 Plugins in Managed Code

Copyright 2008-2009, Paul Jackson, all rights reserved

spicIE, a recent addition to MSDN Code Gallery, wraps the COMplex aspects of IE plugin development, allowing you to write plugins in managed code – C#, VB.Net, etc.

SpiceLogoSmall“SpicIE is a framework which allows you easily to extend Internet Explorer 7/8 with your own plugins. SpicIE wraps/hides the IE COM extension interfaces and makes it easy to develop browser extensions in managed programming languages.”

In my opinion, this is something long overdue.  Though I really haven’t had a need to write a browser plugin for anything, my perception – based on the availability of plugins and anecdotal evidence – is that it’s far easier to do in, say, Firefox or most other browsers than it is in Internet Explorer.  That being the case, when I saw spicIE today, I decided to download it and see just how much would be involved in writing my first plugin for IE.

First, I downloaded spicIE and installed it.  The install gives you a spicIE program group with two sample solutions, a help file and installation/deinstallation instructions:

image It also installs a Visual Studio template for spicIE projects:

imageThe template creates a project for you, including install/deinstall batch files, a strong-naming key and a Plugin.cs file:

image  Plugin.cs is the starting point for your plugin and inherits from SpicIE.Host.  The template apparently didn’t like my project name of LoveTheDot.PlugIn.IE and this resulted in some initial build errors:

imageThis is listed in the known issues, which I didn’t read before starting:

In case you're creating a new project from the SpicIE template, either in C# or VB.NET, you should be aware of a naming constraint. The project should not contain any dots or other special characters. Otherwise, there will be a problem with the defined class structure in the codefile of the plugin.

But these are quickly cleared up and the project then builds cleanly.

The plugin created by the template does have some basic functionality, it ties in to the OnDocumentComplete event and displays a “Hello World!” messagebox:

   1: public Plugin() : base()
   2: {
   3:     HostInstance = this;
   4:     this.OnDocumentComplete += Plugin_OnDocumentComplete;
   5: }
   6:  
   7: void Plugin_OnDocumentComplete(object pDisp, ref object url)
   8: {
   9:     MessageBox.Show("Hello World!");
  10: }

Building the project and executing the install batch file (which requires it be run as administrator), configures the plugin and now every page that loads within IE will say hello:

imageSo that’s a basic little plugin, but what do I want to do with it?  Well, being a playful and mischievous sort of fellow, I’ve decided to hijack MSDN. 

So with a little event subscription and a couple lines of code, my plugin can now redirect any arrival at a MSDN URI right back here to my little blog:

   1: public Plugin() : base()
   2: {
   3:     HostInstance = this;
   4:     this.OnNavigateComplete += Plugin_OnNavigateComplete;
   5: }
   6:  
   7: void Plugin_OnNavigateComplete(object pDisp, ref object URL)
   8: {
   9:     if (URL.ToString().StartsWith("http://msdn.microsoft.com"))
  10:         this.Navigate("http://www.lovethedot.net");
  11: }

Imagine the fun we could have just by changing those URIs to something more interesting and installing this plugin on coworkers’ machines …

Okay, so fun, but not very useful, what else can we do?  Really anything we might want to.  The spicIE API includes a wealth of possibilities, including access to the page’s Document model.  For instance, we could get a list of all the links on a page:

   1: public Plugin() : base()
   2: {
   3:     HostInstance = this;
   4:     this.OnDocumentComplete += Plugin_OnDocumentComplete;
   5: }
   6:  
   7: void Plugin_OnDocumentComplete(object pDisp, ref object url)
   8: {
   9:     foreach (var item in DocumentProperties.LinkCollection)
  10:     {
  11:         MessageBox.Show(item);
  12:     }
  13: }

(You’ll probably want to be careful where you run this little sample, or you’ll be clicking OK a lot.)

There’s a bit more involved if you want a user-interface (say a toolbar), but not much.

First create a user control and change it to inherit from SpicIE.Controls.Toolbar and there are two properties that must be overridden:

   1: public override string PluginGuid
   2: {
   3:     get
   4:     {
   5:         return "58AE4526-9474-4a80-A0CA-45BEFF07CEC9";
   6:     }
   7: }
   8:  
   9: public override string PluginProgID
  10: {
  11:     get
  12:     {
  13:         return "LoveTheDot.PlugIn.IE.Toolbar1";
  14:     }
  15: }

And some initialization to do in the constructor:

   1: public Toolbar1()
   2: {
   3:     InitializeComponent();
   4:     
   5:     this.ToolbarName = "Love the Dot Toolbar";
   6:     this.ToolbarTitle = "Love the Dot";
   7:  
   8:     this.ToolbarHelpText = "This is a very important toolbar";
   9:     this.ToolbarStyle = ToolbarEnum.ExplorerToolbar;
  10:  
  11:     // represents the startup size of the bar
  12:     this.Size = new Size(20, 20);
  13:     // represents the sizing steps
  14:     this.IntegralSize = new Size(0, 0);
  15:     // represents the minimum size
  16:     this.MinSize = new Size(20, 20);
  17:     // represents the maximum size
  18:     this.MaxSize = new Size(600, 600);
  19:  
  20:     // creates a new control collection and inserts the designer-generated controls
  21:     Control[] ctrls = new Control[Controls.Count];
  22:     Controls.CopyTo(ctrls, 0);
  23:  
  24:     // call internal method for implementing controls in the toolbar
  25:     BuildControls(ctrls);
  26: }

There’s a region in the Plugin.cs class for registering UI controls:

   1: #region Register your new browser UI elements here
   2:  
   3: internal static List<SpicIE.Controls.IControlBase> RunOnceCOMRegistration()
   4: {
   5:     Host.TraceSink.TraceEvent(TraceEventType.Information, 0,"RunOnceRegisterCOMControls");
   6:  
   7:     List<SpicIE.Controls.IControlBase> controls = new List<SpicIE.Controls.IControlBase>();
   8:     
   9:  
  10:  
  11:     return controls;
  12: }
  13:  
  14: #endregion

Just insert a line to add the new toolbar to the controls list:

   1: controls.Add(new Toolbar1());

And it becomes available in the browser:

image

In this case, I gave my toolbar a title and a single button, which I’ll code now just by handling the Clicked event like I would any other button:

   1: private void button1_Click(object sender, EventArgs e)
   2: {
   3:     Plugin.HostInstance.Navigate("http://www.lovethedot.net");
   4: }

The SpicIE.Host class, from which my Plugin descends, has a static method “HostInstance”, which gives access to the browser manipulation methods.  So now clicking on the toolbar button navigates the current browser tab to http://www.lovethedot.net.

By allowing us to write Internet Explorer 7/8 browser plugins with managed code, spicIE opens a plugin development to a whole new group of developers. 

kick it on DotNetKicks.com Shout it

10 comments:

Noter said...

Hi,
Would you be wiling to share this project's source code? I mean the toolbar that navigate to your page.

I think I am doing everything right, I get my toolbar, but when I press the button, it generates an exception error in IE and does not navigate to my desired page.

Paul Jackson said...

I'd be happy to upload the source project, unfortunately it's on my Alienware laptop which is currently in for repair with no expected return date -- yes, I'm a bit perturbed with Alienware.

The code presented in the post is pretty much the entirety, though, so it should work for you -- would you mind sending me the exception you're getting and I'll try to figure something out?

Noter said...

Hi,
Thanks, it says:
=====================
Unhandled exception has occured in a component in your application. If you click Continue, the application will ignore this error and attempt to continue.
Object reference not set to an instance of an object.
====================
Many thanks.

Paul Jackson said...

Are you able to debug and determine if it's Plugin or HostInstance that's null?

If you can't get into debug, try breaking the line up into multiple lines, setting each value into a variable and put messageboxes between them, like:

var plugin = Plugin;
Messagebox ...
var hostInstance = plugin.HostInstance;
Messagebox ...
etc.

That'll tell you what's null and you can go back in the code to ensure that it's being set properly on initialization.

Let me know how it works for you and feel free to email me at pjackson@lovethedot.net -- I'll keep working this with you if you want.

Cameron said...

Thanks for writing such a nice tutorial!

I'm running in to a strange occurrence: the button works fine (it sends a user to a new page), but it always works on the newest tab. For instance, say a user has two tabs open then goes into the first and presses the button. Since the second tab was opened the latest, it changes the address of this tab instead!

Any ideas? Thanks in advance.

Sagar said...

A small request... Is it possible to drag and drop browser text on the Horizontal tool bar? If so how?

I have tried but the event does not fire. Any help would be appreciated.

Artyom said...

Hello!
I'm writting my first plugIn with SpicIE.
I created ExploreToolbar with few buttons. And I'l like to open new verticalToolbar when I click on button1.
How can I make it?
P.S. I think it really easy but...I can't find the answer.

HappyNomad said...
This comment has been removed by the author.
HappyNomad said...

I've found SpicIE's API is sufficient, but many of my questions on SpiciE's discussion forum remain unanswered. Please pass on your knowledge there, especially if you are a COM Interop expert! Continued community-based development would also be nice to see. I for one could contribute an improved HtmlEventProxy.

பாலாஜி இராமச்சந்திரன் said...

Hi i need help from here.How to add my site to trusted site using SpicIE plugin and also How to enable the following things through the Coding
1.Access Data Source Across Domain
2.Automatic prompting for ActivX Controls
3.Download signed ActiveX Controls
4.Download UnSigned ActiveX Controls
5.Run ActiveX Contrls and Plugins