diff --git a/Documentation/CallingFromWebKit.txt b/Documentation/CallingFromWebKit.txt new file mode 100644 index 0000000..4124705 --- /dev/null +++ b/Documentation/CallingFromWebKit.txt @@ -0,0 +1,81 @@ +Introduction +============ + +A lot of the display code in GitX is based on the HTML views that are rendered +by WebKit. As such, it's necessary to access a lot of information from WebKit +that is only available in the Objective-C environment. + +Because of this, some convenience methods have been added. Most notably, some +objects can be accessed completely from WebKit, including: + + * All webcontrollers + * All PBGitRefs and PBGitCommits + +All WebKit views should be controlled by a subclass of PBWebController. + + +Subclassing PBWebController +=========================== + +PBWebController does some magic to make using it easier. First of all, it +automatically loads in the view specified by "startFile". This means that +your views have to conform to a specific structure. For example, if you set +startFile (in your awakeFromNib) to "commit", there should be a file named +html/views/commit/index.html that will get loaded. + +Furthermore, the controller keeps a finishedLoading member that indicates +whether or not the webview is ready to be used. If it is ready, and your +subclass has implemented the didLoad method, that method will be called. + +After the document has succesfully loaded, the controller inserts itself into +the Javascript environment under the "Controller" variable. + +Useful PBWebController functions +================================ + +There are some functions that might be useful in any context. These are put in +the PBWebController class for easy access. To access these, replace the colons +with underscores in the JS call. for example, + + - (void) log:(NSString) logMessage + +would be called in JS by: + + Controller.log_("Hello from javascript!"); + +## - (void) log:(NSString) logMessage + +This method simply relays a JS message to the program, which outputs it using +NSLog. Handy for debugging. + +## - (BOOL) isReachable:(NSString *)hostname + +Will return true if the hostname is reachable from Javascript without creating +an internet connection (for example, dialup). This is useful for optional +features that depend on a working internet connection. + +** NOTE ** The GitX JS environment is _NOT_ secure, which means that external +HTTP calls should only be made to trusted sites. For example, DON'T open any +link in a commit message, as this could potentially allow any site to run any +command on your mac. + +## - (void) runCommand:(WebScriptObject *)arguments + inRepository:(PBGitRepository *)repository + callBack:(WebScriptObject *)callBack + +This somewhat specific function allows you to call any git command and have +its output returned to a callback function. For example, the following: + +Controller.runCommand_inRepository_callBack_(["--version"], obj.repository, + function(v) { notify("Git Version: " + v, 1); }); + +will show a notification displaying the current git version. This method is +async, which means that it doesn't matter if it takes a long time to run. + +## - (void) callSelector:(NSString *)selectorString + onObject:(id)object + callBack:(WebScriptObject *)callBack + +While you can have direct access to most objects, sometimes it is useful to +have an async task, for example if the operation can take a long time. Use +this function to keep the UI responsive. \ No newline at end of file diff --git a/GitX.xcodeproj/project.pbxproj b/GitX.xcodeproj/project.pbxproj index b7e1ed0..4c3d722 100644 --- a/GitX.xcodeproj/project.pbxproj +++ b/GitX.xcodeproj/project.pbxproj @@ -381,8 +381,8 @@ 911111F60E594F3F00BF76B4 /* PBRepositoryDocumentController.h */, 911111F70E594F3F00BF76B4 /* PBRepositoryDocumentController.m */, F5E926040E8827D300056E75 /* PBViewController.h */, - F5EF8C8C0E9D4A5D0050906B /* PBWebController.h */, F5E926050E8827D300056E75 /* PBViewController.m */, + F5EF8C8C0E9D4A5D0050906B /* PBWebController.h */, F5EF8C8D0E9D4A5D0050906B /* PBWebController.m */, F5FE6C010EB13BC900F30D12 /* PBServicesController.h */, F5FE6C020EB13BC900F30D12 /* PBServicesController.m */, diff --git a/PBWebController.h b/PBWebController.h index 4fd4fbc..34d6c10 100644 --- a/PBWebController.h +++ b/PBWebController.h @@ -13,6 +13,9 @@ IBOutlet WebView* view; NSString *startFile; BOOL finishedLoading; + + // For async git reading + NSMapTable *callbacks; } @property (retain) NSString *startFile; diff --git a/PBWebController.m b/PBWebController.m index dcc3429..7142d68 100644 --- a/PBWebController.m +++ b/PBWebController.m @@ -7,7 +7,8 @@ // #import "PBWebController.h" - +#import "PBGitRepository.h" +#include @implementation PBWebController @@ -19,6 +20,7 @@ NSString* file = [[NSBundle mainBundle] pathForResource:@"index" ofType:@"html" inDirectory:path]; NSLog(@"path: %@, file: %@", path, file); NSURLRequest * request = [NSURLRequest requestWithURL:[NSURL fileURLWithPath:file]]; + callbacks = [NSMapTable mapTableWithKeyOptions:(NSPointerFunctionsObjectPointerPersonality|NSPointerFunctionsStrongMemory) valueOptions:(NSPointerFunctionsObjectPointerPersonality|NSPointerFunctionsStrongMemory)]; finishedLoading = NO; [view setUIDelegate:self]; @@ -51,13 +53,12 @@ } #pragma mark Functions to be used from JavaScript + - (void) log: (NSString*) logMessage { NSLog(@"%@", logMessage); } -#include - - (BOOL) isReachable:(NSString *)hostname { SCNetworkConnectionFlags flags; @@ -71,4 +72,62 @@ return flags > 0; } +#pragma mark Using async function from JS + +- (void) runCommand:(WebScriptObject *)arguments inRepository:(PBGitRepository *)repository callBack:(WebScriptObject *)callBack +{ + // The JS bridge does not handle JS Arrays, even though the docs say it does. So, we convert it ourselves. + int length = [[arguments valueForKey:@"length"] intValue]; + NSMutableArray *realArguments = [NSMutableArray arrayWithCapacity:length]; + int i = 0; + for (i = 0; i < length; i++) + [realArguments addObject:[arguments webScriptValueAtIndex:i]]; + + NSFileHandle *handle = [repository handleInWorkDirForArguments:realArguments]; + [callbacks setObject:callBack forKey:handle]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(JSRunCommandDone:) name:NSFileHandleReadToEndOfFileCompletionNotification object:handle]; + [handle readToEndOfFileInBackgroundAndNotify]; +} + +- (void) callSelector:(NSString *)selectorString onObject:(id)object callBack:(WebScriptObject *)callBack +{ + NSArray *arguments = [NSArray arrayWithObjects:selectorString, object, nil]; + NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(runInThread:) object:arguments]; + [callbacks setObject:callBack forKey:thread]; + [thread start]; +} + +- (void) runInThread:(NSArray *)arguments +{ + SEL selector = NSSelectorFromString([arguments objectAtIndex:0]); + id object = [arguments objectAtIndex:1]; + id ret = [object performSelector:selector]; + NSArray *returnArray = [NSArray arrayWithObjects:[NSThread currentThread], ret, nil]; + [self performSelectorOnMainThread:@selector(threadFinished:) withObject:returnArray waitUntilDone:NO]; +} + + +- (void) returnCallBackForObject:(id)object withData:(id)data +{ + WebScriptObject *a = [callbacks objectForKey: object]; + if (!a) { + NSLog(@"Could not find a callback for object: %@", object); + return; + } + + [callbacks removeObjectForKey:object]; + [a callWebScriptMethod:@"call" withArguments:[NSArray arrayWithObjects:@"", data, nil]]; +} + +- (void) threadFinished:(NSArray *)arguments +{ + [self returnCallBackForObject:[arguments objectAtIndex:0] withData:[arguments objectAtIndex:1]]; +} + +- (void) JSRunCommandDone:(NSNotification *)notification +{ + NSString *data = [[NSString alloc] initWithData:[[notification userInfo] valueForKey:NSFileHandleNotificationDataItem] encoding:NSUTF8StringEncoding]; + [self returnCallBackForObject:[notification object] withData:data]; +} + @end