So..after developing a few Firefox & Chrome extensions, I've decided to try and expand my skillset by developing an Internet Explorer extension in C#. I went into it thinking it wouldn't be too bad.
Wow.
I was really wrong. So, my question is, does anyone here have experience with/in developing IE extensions that can share their knowledge? This would include code samples, or links to good ones, or documentation on the process, or anything.
I really want to do this, but I'm hitting a giant wall with lousy documentation, lousy code/example code/lack thereof. Any help/resources you could offer would be greatly appreciated.
Specifically, I would like to start with how to get access to/manipulate the DOM from within a IE extension.
EDIT:
Even more details:
Ideally, I would like to plant a toolbar button that, when clicked, popped a menu up that contains links to external sites. I would also like to access the DOM and plant Javascript on the page depending on some conditions. I would also like to know what is the best way to persist information in an IE extension. In Firefox/Chrome/Most modern browsers, you use window.localStorage, but obviously with IE8/IE7, that's not an option. Maybe a SQLite DB or such? It is ok to assume that .NET 4.0 will be installed on a user's computer.
Thanks!
EDIT:
I'm providing a 500 reputation bounty on this. I'm serious about learning how to build a Internet Explorer extension. I don't want to use Spice IE as I want to build one that is compatible with IE9 as well. I've added the C++ tag to this question as well, because if it's better to build one in C++, I can do that.
I hope it's worth 500 reputation to somebody to help not only me, but future people with the same intentions.
One last edit:
It looks like the general consensus is get a 24 pack of diet coke, two monitors, and lots of time. But if anyone else would like to offer up a great answer, please do so before I have to award the bounty.
Man... this has been a lot of work!
I was so curious about how to do this, that I did it myself.
First of all... credit is not all mine. This is a compilation of what I found, on these sites:
And of course, I wanted my answer to have the features you asked:
- DOM traversal to find something;
- a button that shows a window (in my case to setup)
- persist the configuration (I will use regitry for that)
- and finally execute javascript.
I will describe it step by step, how I managed to do it working with Internet Explorer 8, in Windows 7 x64... note that I could not test in other configurations. Hope you understand =)
Creating a Working Internet Explorer 8 Addon
I am using Visual Studio 2010, C# 4, .Net Framework 4, so some of these steps might be slightly different for you.
Created a class library. I called mine InternetExplorerExtension.
Add these references to the project:
- Interop.SHDocVw
- Microsoft.mshtml
this is what my references section in csproj contains:
<Reference Include="Interop.SHDocVw, Version=1.1.0.0, Culture=neutral, PublicKeyToken=90ba9c70f846762e, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<EmbedInteropTypes>True</EmbedInteropTypes>
<HintPath>C:\Program Files (x86)\Microsoft Visual Studio 9.0\Common7\IDE\PrivateAssemblies\Interop.SHDocVw.dll</HintPath>
</Reference>
<Reference Include="Microsoft.CSharp" />
<Reference Include="Microsoft.mshtml, Version=7.0.3300.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<EmbedInteropTypes>True</EmbedInteropTypes>
</Reference>
<Reference Include="System" />
<Reference Include="System.Data" />
<Reference Include="System.Drawing" />
<Reference Include="System.Windows.Forms" />
<Reference Include="System.Xml" />
Create the following files:
IEAddon.cs
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using Interop.ShDocVw;
using mshtml;
using Microsoft.Win32;
using System.Windows.Forms;
namespace InternetExplorerExtension
{
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
[Guid("D40C654D-7C51-4EB3-95B2-1E23905C2A2D")]
[ProgId("MyBHO.WordHighlighter")]
public class WordHighlighterBHO : IObjectWithSite, IOleCommandTarget
{
const string DefaultTextToHighlight = "browser";
IWebBrowser2 browser;
#region Highlight Text
void OnDocumentComplete(object pDisp, ref object URL)
{
var document = browser.Document as IHTMLDocument2;
var window = document.parentWindow;
window.execScript(@"function FncAddedByAddon() { alert('Message added by addon.'); }");
Queue<IHTMLElement> queue = new Queue<IHTMLElement>();
queue.Enqueue(document.body);
while (queue.Count > 0)
{
var el = queue.Dequeue();
// replacing desired text with a highlighted version of it
el.innerHTML = el.innerHTML.Replace(TextToHighlight, "<span style='background-color: yellow; cursor: hand;' onclick='javascript:FncAddedByAddon()' title='Click to open script based alert window.'>" + TextToHighlight + "</span>");
// adding children to collection
var x = (HTMLElementCollection)(el.children);
foreach (IHTMLElement eachChild in x)
queue.Enqueue(eachChild);
}
}
#endregion
#region Load and Save Data
static string TextToHighlight = DefaultTextToHighlight;
public static string RegData = "Software\\MyIEExtension";
private static void SaveOptions()
{
RegistryKey registryKey = Registry.LocalMachine.OpenSubKey(RegData, true);
if (registryKey == null)
registryKey = Registry.LocalMachine.CreateSubKey(RegData);
registryKey.SetValue("Data", TextToHighlight);
registryKey.Close();
}
private static void LoadOptions()
{
RegistryKey registryKey = Registry.LocalMachine.OpenSubKey(RegData, true);
if (registryKey == null)
TextToHighlight = DefaultTextToHighlight;
TextToHighlight = (string)registryKey.GetValue("Data");
registryKey.Close();
}
#endregion
#region Implementation of IObjectWithSite
int IObjectWithSite.SetSite(object site)
{
if (site != null)
{
LoadOptions();
browser = (IWebBrowser2)site;
((DWebBrowserEvents2_Event)browser).DocumentComplete +=
new DWebBrowserEvents2_DocumentCompleteEventHandler(this.OnDocumentComplete);
}
else
{
((DWebBrowserEvents2_Event)browser).DocumentComplete -=
new DWebBrowserEvents2_DocumentCompleteEventHandler(this.OnDocumentComplete);
browser = null;
}
return 0;
}
int IObjectWithSite.GetSite(ref Guid guid, out IntPtr ppvSite)
{
IntPtr punk = Marshal.GetIUnknownForObject(browser);
int hr = Marshal.QueryInterface(punk, ref guid, out ppvSite);
Marshal.Release(punk);
return hr;
}
#endregion
#region Implementation of IOleCommandTarget
int IOleCommandTarget.QueryStatus(IntPtr pguidCmdGroup, uint cCmds, ref OLECMD prgCmds, IntPtr pCmdText)
{
return 0;
}
int IOleCommandTarget.Exec(IntPtr pguidCmdGroup, uint nCmdID, uint nCmdexecopt, IntPtr pvaIn, IntPtr pvaOut)
{
var form = new HighlighterOptionsForm();
form.InputText = TextToHighlight;
if (form.ShowDialog() != DialogResult.Cancel)
{
TextToHighlight = form.InputText;
SaveOptions();
}
return 0;
}
#endregion
#region Registering with regasm
public static string RegBHO = "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Browser Helper Objects";
public static string RegCmd = "Software\\Microsoft\\Internet Explorer\\Extensions";
[ComRegisterFunction]
public static void RegisterBHO(Type type)
{
string guid = type.GUID.ToString("B");
// BHO
{
RegistryKey registryKey = Registry.LocalMachine.OpenSubKey(RegBHO, true);
if (registryKey == null)
registryKey = Registry.LocalMachine.CreateSubKey(RegBHO);
RegistryKey key = registryKey.OpenSubKey(guid);
if (key == null)
key = registryKey.CreateSubKey(guid);
key.SetValue("Alright", 1);
registryKey.Close();
key.Close();
}
// Command
{
RegistryKey registryKey = Registry.LocalMachine.OpenSubKey(RegCmd, true);
if (registryKey == null)
registryKey = Registry.LocalMachine.CreateSubKey(RegCmd);
RegistryKey key = registryKey.OpenSubKey(guid);
if (key == null)
key = registryKey.CreateSubKey(guid);
key.SetValue("ButtonText", "Highlighter options");
key.SetValue("CLSID", "{1FBA04EE-3024-11d2-8F1F-0000F87ABD16}");
key.SetValue("ClsidExtension", guid);
key.SetValue("Icon", "");
key.SetValue("HotIcon", "");
key.SetValue("Default Visible", "Yes");
key.SetValue("MenuText", "&Highlighter options");
key.SetValue("ToolTip", "Highlighter options");
//key.SetValue("KeyPath", "no");
registryKey.Close();
key.Close();
}
}
[ComUnregisterFunction]
public static void UnregisterBHO(Type type)
{
string guid = type.GUID.ToString("B");
// BHO
{
RegistryKey registryKey = Registry.LocalMachine.OpenSubKey(RegBHO, true);
if (registryKey != null)
registryKey.DeleteSubKey(guid, false);
}
// Command
{
RegistryKey registryKey = Registry.LocalMachine.OpenSubKey(RegCmd, true);
if (registryKey != null)
registryKey.DeleteSubKey(guid, false);
}
}
#endregion
}
}
Interop.cs
using System;
using System.Runtime.InteropServices;
namespace InternetExplorerExtension
{
[ComVisible(true)]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid("FC4801A3-2BA9-11CF-A229-00AA003D7352")]
public interface IObjectWithSite
{
[PreserveSig]
int SetSite([MarshalAs(UnmanagedType.IUnknown)]object site);
[PreserveSig]
int GetSite(ref Guid guid, [MarshalAs(UnmanagedType.IUnknown)]out IntPtr ppvSite);
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct OLECMDTEXT
{
public uint cmdtextf;
public uint cwActual;
public uint cwBuf;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 100)]
public char rgwz;
}
[StructLayout(LayoutKind.Sequential)]
public struct OLECMD
{
public uint cmdID;
public uint cmdf;
}
[ComImport(), ComVisible(true),
Guid("B722BCCB-4E68-101B-A2BC-00AA00404770"),
InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)]
public interface IOleCommandTarget
{
[return: MarshalAs(UnmanagedType.I4)]
[PreserveSig]
int QueryStatus(
[In] IntPtr pguidCmdGroup,
[In, MarshalAs(UnmanagedType.U4)] uint cCmds,
[In, Out, MarshalAs(UnmanagedType.Struct)] ref OLECMD prgCmds,
//This parameter must be IntPtr, as it can be null
[In, Out] IntPtr pCmdText);
[return: MarshalAs(UnmanagedType.I4)]
[PreserveSig]
int Exec(
//[In] ref Guid pguidCmdGroup,
//have to be IntPtr, since null values are unacceptable
//and null is used as default group!
[In] IntPtr pguidCmdGroup,
[In, MarshalAs(UnmanagedType.U4)] uint nCmdID,
[In, MarshalAs(UnmanagedType.U4)] uint nCmdexecopt,
[In] IntPtr pvaIn,
[In, Out] IntPtr pvaOut);
}
}
and finally a form, that we will use to configure the options. In this form place a TextBox and an Ok Button. Set the DialogResult of the button to Ok. Place this code in the form code:
using System.Windows.Forms;
namespace InternetExplorerExtension
{
public partial class HighlighterOptionsForm : Form
{
public HighlighterOptionsForm()
{
InitializeComponent();
}
public string InputText
{
get { return this.textBox1.Text; }
set { this.textBox1.Text = value; }
}
}
}
In the project properties, do the following:
- Sign the assembly with a strong-key;
- In the Debug tab, set Start External Program to
C:\Program Files (x86)\Internet Explorer\iexplore.exe
- In the Debug tab, set Command Line Arguments to
http://msdn.microsoft.com/en-us/library/ms976373.aspx#bho_getintouch
In the Build Events tab, set Post-build events command line to:
"C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\Bin\NETFX 4.0 Tools\x64\gacutil.exe" /f /i $(TargetDir)$(TargetFileName)
"C:\Windows\Microsoft.NET\Framework\v4.0.30319\RegAsm.exe" /unregister $(TargetDir)$(TargetFileName)
"C:\Windows\Microsoft.NET\Framework\v4.0.30319\RegAsm.exe" $(TargetDir)$(TargetFileName)
Attention: as my computer is x64, there is a specific x64 inside the path of gacutil executable on my machine that may be different on yours.
How this addon works
It traverses all DOM tree, replacing the text, configured using the button, by itself with a yellow background. If you click on the yellowed texts, it calls a javascript function that was inserted on the page dynamically. The default word is 'browser', so that it matches a lot of them!
EDIT: after changing the string to be highlighted, you must click the URL box and press Enter... F5 will not work, I think that it is because F5 is considered as 'navigation', and it would require to listen to navigate event (maybe). I'll try to fix that later.
Now, it is time to go. I am very tired.
Feel free to ask questions... may be I will not be abled to answer since I am going on a trip... in 3 days I'm back, but I'll try to come here in the meantime.