There is no official plugin API for Safari (or many other OS X applications), so we need to find our own way in:
- Get Safari to load our code bundle
- Work out which methods in Safari should trigger our code
- Replace the relevant methods with our own
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
There is no official plugin API for Safari (or many other OS X applications), so we need to find our own way in:
<?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>
+(void)load
[NSBundle mainBundle]
to get information about the host application
[[NSBundle mainBundle] bundleIdentifier]
e.g. com.apple.Safari[[NSBundle mainBundle] infoDictionary]
for other information+ (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...
}
Our code is loaded into Safari, but we want to add or modify certain behaviours.
Various UNIX command line tools can also be helpful:
Two methods for replacing a built in Safari method with your own:
[[ClassB class] poseAsClass:[ClassA class]]
(where ClassB
is a subclass of ClassA
)poseAsClass
#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]];
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.
Ask them at Barcamp, or get in touch via georgebrock.com