Hacking OS X: How to write a plugin for Safari

George Brocklehurst (@georgebrock on Twitter)

A presentation at Barcamp London 6, 2009-03-29

These slides: gbrck.com/bcl6
Licensed under Creative Commons BY-NC-SA


What do we need to do?

There is no official plugin API for Safari (or many other OS X applications), so we need to find our own way in:

  1. Get Safari to load our code bundle
  2. Work out which methods in Safari should trigger our code
  3. Replace the relevant methods with our own

Step 1: Getting Safari to load our code

Input Managers

Writing an Input Manager

The property list

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" 
	"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>BundleName</key>
	<string>MySafariPlugin.bundle</string>
	<key>LoadBundleOnLaunch</key>
	<string>YES</string>
	<key>LocalizedNames</key>
	<dict>
		<key>English</key>
		<string>MySafariPlugin</string>
	</dict>
	<key>NoMenuEntry</key>
	<string>YES</string>
</dict>
</plist>

The bundle

Example bundle code

+ (void)load
{
	NSBundle *hostApp = [NSBundle mainBundle];

	// Check the host app is Safari
	NSString *bundleID = [hostApp bundleIdentifier];
	if(![bundleID isEqualToString:@"com.apple.Safari"])
		return;
	
	// Check this version of Safari is supported
	NSDictionary *infoDict = [hostApp infoDictionary];
	float v = [[infoDict 
		valueForKey:@"CFBundleVersion"] floatValue];
	if(v < 5528.16)
	{
		//TODO: Tell the user why the plugin hasn't loaded
		return;
	}
	
	// Initialise your plugin here...
}

SIMBL (Smart InputManager Bundle Loader)


Step 2: Reverse engineering Safari

Our code is loaded into Safari, but we want to add or modify certain behaviours.

Class-dump

F-Script Anywhere

Other tools

Various UNIX command line tools can also be helpful:

nm
Displays the symbol table for an executable
strings
Displays any printable strings contained within a binary file
gdb
The GNU debugger

Step 3: Replacing a Safari method with your own method

Two methods for replacing a built in Safari method with your own:

Posing

Method swizzling

Example code

#import <objc/objc-class.h>

@implementation Swizzler

+ (BOOL)renameSelector:(SEL)oldSelector 
                    to:(SEL)newSelector 
               onClass:(Class)class
{
	Method method = class_getInstanceMethod(class, oldSelector);
	if(method == nil)
		return FALSE;

	method->method_name = newSelector;
	return TRUE;
}

@end

Usage:

[Swizzler 
	renameSelector:@selector(doSomething) 
	to:@selector(myplugin_old_doSomething) 
	onClass:[MyClass class]];
	
[Swizzler 
	renameSelector:@selector(myplugin_new_doSomething) 
	to:@selector(doSomething) 
	onClass:[MyClass class]];

Linker settings


Demo application

Source code for a demonstration plugin is available on GitHub: github.com/georgebrock/bcl6-safari-plugin-demo

The demo code is released under a Creative Commons attribution license, so you can do almost anything you want with it.


Any questions?

Ask them at Barcamp, or get in touch via georgebrock.com