There has been a lot of talk lately about Input Managers and whether they will be completely removed in Leopard. Just recently David Watanabe (who is using Input Managers for his popular Safari extension) blogged about the uncertain future of Inquisitor. It would be a shame for such an awesome Safari extension to become extinct.
Thankfully, it is not that dire a situation. After spending weeks on this problem, we have found a way to make our 1Passwd Password Manger work on Leopard without using an Input Manager.
Read on to find out how we accomplished this, the bumps we ran into along the way, and why we believe the future is bright. But first, some background information.
What is an Input Manager?
Input Managers provide developers a way to extend OS X applications at runtime (even though Apple planned that their use would be more modest). Plug-in bundles are loaded dynamically into the runtime of an application when they are launched, and they are then able to modify code within the launching application. Sorry folks, but this works for Cocoa apps only!
How does this work? Well, OS X will automatically load Input Manager bundles from these folders:
Bundles in the first folder will be loaded into all applications, for every user on the system. Bundles in the users home folder will only apply to that user. When the bundles load they can modify the application using various techniques whose details will have to wait for another day.
So why are Input Managers so important? The reason is there are so many awesome extensions that have been written for so many programs.
Inquisitor is an amazing Safari plugin that extends the default search box to have live search results from Google. Speaking of Google, Google Desktop leverages Input Managers too. Many people say Safari is unusable without SafariStand, and there are entire websites dedicated on how to Pimp Your Safari and Pimp Your Camino, whose "pimps" are mostly based on Input Managers (such as PipthHelmet, Saft, and SafariStand). Our Password Manager uses Input Managers to integrate our functionality directly into your favorite web browsers.
While most extensions are related to browsers, they are not limited to them. For instance, TextMate has a cool Edit in TextMate extension that allows you to edit any Cocoa Text Field using TextMate. I was going to mention DockStar and Mail Act-On, but they are not using Input Managers, although they could achieve their magic using them if they wanted to.
What makes Input Managers so amazing is that developers from around the globe can extend applications in ways their original developers never dreamed of.
Input Managers Removed In Leopard?
Input Managers are incredibly powerful because they are automatically loaded for every Cocoa application and are able to modify practically anything. This power is also a cause for concern, since they could be used for nefarious purposes. In theory a virus or worm could exploit Input Managers to spread themselves.
It appears security concerns are the main reasons for Apple deciding to make changes in Leopard on how Input Managers work. What makes this so confusing is that each seed has made changes to the Input Manager rules. First they were allowed. Then they were disabled by default, and a prompt was added asking if you wanted to allow the Input Managers to load. Then later, in the WWDC seed, Input Managers were completely disabled and there was no way to enable them. In more recent seeds, it looks like Input Managers will be allowed, but only under specific circumstances (i.e. must be owned by root, etc).
Due to this lack of certainty, we set out to find another way to keep the full functionality of 1Passwd within Leopard. I use the term "we" loosely. Truth be told, Roustem is the master hacker when it comes to the low-level mechanics of OS X.
Input Managers Are Not the Only Option
The ability to dynamically load your code into a running application is not unique to Input Managers. There are several alternatives that will achieve the same result.
An Intermediary Application Launcher could be used to launch the "real" application after you configure the dynamic library path to load your own code. Effectively, instead of launching Safari directly, you would launch Safari-Launcher instead. This loader would setup your bundles by changing the
DYLD\_LIBRARY\_PATH environment varibale and then launch Safari. The consequences of this approach, however, are rather dire. First, you need to change the user's Dock icons to point to your launcher, which is a big no-no. Second, "snoopy" applications like Little Snitch will alert users that the launcher wants to connect to websites, not the target application.
Another approach is to modify the system Dynamic library path so that your bundle gets loaded into all applications that load. The biggest problem with this approach is that your code gets injected into every single application, so whenever any app crashes, your application will be listed in the binary image of the stack trace, and you'll get a lot of calls asking why your app is poking around where it's not supposed to. While loading into all applications might be a requirement for system wide applications (like the Edit in TextMate extension), it is not what we needed for 1Passwd.
There is also Unsanity's Application Enhancer (APE) that can be licensed and used to extend practically anything. Licensing the SDK is quite reasonable and can be a great solution. A big benefit of APE The problem of having your application appear in every stack trace is now gone (in fact, it still exists but has been pushed to the Unsanity teams inbox). The only downside is you will get emails from people who uninstalled APE and are wondering why your application no longer works.
All of these approaches had enough downside that we decided to keep looking. Since we were specifically interested in a web browser extension (most extensions are related to web browser also), we decided to revisit the built-in Plugin architecture in WebKit. It turns out that plugins can work quite effectively and can modify the loading process just like an Input Manager can, but normally it only works after you visit a web page that contains an object that requires your plugin. In other words, your plugin is not loaded until it is required. After a lot of tinkering, however, Roustem found a way to craftily construct a plugin that will load whenever Safari or WebKit browsers load.
Pimp Your Browsers With Plugins
There are many benefits to the plugin approach.
First, your code is only loaded into the applications that use the WebKit framework.
Secondly, you install your own plugin file, without needing to rely on any other frameworks. This means you never need to worry about a user messing up his APE or SIMBL installations, and then blaming your program.
Of course, there are some downsides too. First, if you want to have your plugin work in applications that don't use WebKit, you're out of luck. Also, support for Plugins in Camino seems
flaky unique and doesn't seem to work as desired. More investigation is needed on this front.
UPDATE: Bennett reminded me in the comments that Safari 2 and 3 are very different beasts. The browser plugin approach can work in Safari 2, but there didn't seem to be a way to get the plugin to load at startup, which was a big problem for us. We therefore decided to continue using Input Managers in Tiger, and use plugins in Leopard where Safari 3 is the default.
How to Write a Pimpin' Plugin
The main idea is to make a browser plugin in such a way that Safari will be forced to load it on startup. This is not easy as Safari is smart and will check the
WebPluginMIMETypes property to determine if this plugin is needed for the page. If there is no objects on the current page that have this MIME type then Safari will not load your code. A few days deep in WebKit source code revealed the
WebPluginMIMETypesFilename property that is used to tell about MIME type via external property list file. The trick is that if the file does not exist then Safari will load your plugin and ask it to create the file. Instead of creating the file we extend Safari just as any other Input Manager-based extension does.
Here are the steps to create the plugin:
- Create a Cocoa bundle project in Xcode
- Make sure the target extension is "plugin"
- Add the following information to Info.plist
<key>WebPluginDescription</key> <string>InternetPlugin</string> <key>WebPluginMIMETypesFilename</key> <string>com.example.plugin.plist</string> <key>WebPluginName</key> <string>InternetPlugin Name</string> <key>CSResourcesFileMapped</key> <true/>
- Add a RSRC file
- Add "Build ResourceManager Resources" Build Phase
- Implement + (void)load for the main bundle class
- Implement a set of empty Netscape NPAPI functions. The details are too big to post here; contact us or comment if you want the details.
What's The Verdict?
As of right now we have decided to use plugins to achieve full 1Passwd support in Leopard. This may change in the future, but we're content in knowing that there is more than one way to skin this cat if need be.
We have considered creating a SIMBL-like plugin manager so that users can selectively enable/disable browser plugins, along with a robust set of rules to ensure security is maintained. If you're interested in such a solution, let us know in the comments; we don't plan on moving forward on it unless there is demand from the developer community.