├── .gitignore ├── LICENSE ├── SDAWVPluginAccelerometer.h ├── SDAWVPluginAccelerometer.js ├── SDAWVPluginAccelerometer.m ├── SDAWVPluginAudio.h ├── SDAWVPluginAudio.js ├── SDAWVPluginAudio.m ├── SDAWVPluginOrientation.h ├── SDAWVPluginOrientation.js ├── SDAWVPluginOrientation.m ├── SDAWVPluginShake.h ├── SDAWVPluginShake.js ├── SDAWVPluginShake.m ├── SDAdvancedWebViewCommand.h ├── SDAdvancedWebViewCommand.m ├── SDAdvancedWebViewCommunicationCenter.js ├── SDAdvancedWebViewController.h ├── SDAdvancedWebViewController.m ├── SDAdvancedWebViewPlugin.h └── SDAdvancedWebViewPlugin.m /.gitignore: -------------------------------------------------------------------------------- 1 | # xcode noise 2 | *.mode1v3 3 | *.pbxuser 4 | *.perspective 5 | *.perspectivev3 6 | *.pyc 7 | *~.nib/ 8 | build/* 9 | 10 | # Textmate - if you build your xcode projects with it 11 | *.tm_build_errors 12 | 13 | # osx noise 14 | .DS_Store 15 | profile 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010 Olivier Poitrey 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is furnished 8 | to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | 21 | -------------------------------------------------------------------------------- /SDAWVPluginAccelerometer.h: -------------------------------------------------------------------------------- 1 | // 2 | // SDAWVPluginAccelerometer.h 3 | // SDAdvancedWebView 4 | // 5 | // Created by Olivier Poitrey on 10/06/10. 6 | // Copyright 2010 Dailymotion. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "SDAdvancedWebViewPlugin.h" 11 | 12 | @interface SDAWVPluginAccelerometer : SDAdvancedWebViewPlugin 13 | { 14 | } 15 | 16 | - (void)start:(NSArray *)arguments options:(NSDictionary *)options; 17 | - (void)stop:(NSArray *)arguments options:(NSDictionary *)options; 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /SDAWVPluginAccelerometer.js: -------------------------------------------------------------------------------- 1 | // 2 | // This code is highly inspired from the PhoneGap project 3 | // 4 | 5 | function Acceleration(x, y, z) 6 | { 7 | this.x = x; 8 | this.y = y; 9 | this.z = z; 10 | this.timestamp = new Date().getTime(); 11 | } 12 | 13 | function AccelerationOptions() 14 | { 15 | /** 16 | * The timeout after which if acceleration data cannot be obtained the errorCallback 17 | * is called. 18 | */ 19 | this.timeout = 10000; 20 | } 21 | 22 | 23 | /** 24 | * This class provides access to device accelerometer data. 25 | * @constructor 26 | */ 27 | function Accelerometer() 28 | { 29 | /** 30 | * The last known acceleration. 31 | */ 32 | this.lastAcceleration = new Acceleration(0, 0, 0); 33 | 34 | // private callback called from Obj-C by name 35 | this._onAccelUpdate = function(x, y, z) 36 | { 37 | this.lastAcceleration = new Acceleration(x, y, z); 38 | } 39 | 40 | /** 41 | * Asynchronously aquires the current acceleration. 42 | * @param {Function} successCallback The function to call when the acceleration 43 | * data is available 44 | * @param {Function} errorCallback The function to call when there is an error 45 | * getting the acceleration data. 46 | * @param {AccelerationOptions} options The options for getting the accelerometer data 47 | * such as timeout. 48 | */ 49 | this.getCurrentAcceleration = function(successCallback, errorCallback, options) 50 | { 51 | // If the acceleration is available then call success 52 | // If the acceleration is not available then call error 53 | 54 | // Created for iPhone, Iphone passes back _accel obj litteral 55 | if (typeof successCallback == "function") 56 | { 57 | successCallback(this.lastAcceleration); 58 | } 59 | } 60 | 61 | /** 62 | * Asynchronously aquires the acceleration repeatedly at a given interval. 63 | * @param {Function} successCallback The function to call each time the acceleration 64 | * data is available 65 | * @param {Function} errorCallback The function to call when there is an error 66 | * getting the acceleration data. 67 | * @param {AccelerationOptions} options The options for getting the accelerometer data 68 | * such as timeout. 69 | */ 70 | this.watchAcceleration = function(successCallback, errorCallback, options) 71 | { 72 | //this.getCurrentAcceleration(successCallback, errorCallback, options); 73 | // TODO: add the interval id to a list so we can clear all watches 74 | var frequency = (options != undefined && options.frequency != undefined) ? options.frequency : 10000; 75 | var updatedOptions = {desiredFrequency: frequency}; 76 | navigator.comcenter.exec("Accelerometer.start", options); 77 | 78 | var self = this; 79 | return setInterval(function() {self.getCurrentAcceleration(successCallback, errorCallback, options)}, frequency); 80 | } 81 | 82 | /** 83 | * Clears the specified accelerometer watch. 84 | * @param {String} watchId The ID of the watch returned from #watchAcceleration. 85 | */ 86 | this.clearWatch = function(watchId) 87 | { 88 | navigator.comcenter.exec("Accelerometer.stop"); 89 | clearInterval(watchId); 90 | } 91 | } 92 | 93 | SDAdvancedWebViewObjects['accelerometer'] = new Accelerometer(); 94 | 95 | -------------------------------------------------------------------------------- /SDAWVPluginAccelerometer.m: -------------------------------------------------------------------------------- 1 | // 2 | // SDAWVPluginAccelerometer.m 3 | // SDAdvancedWebView 4 | // 5 | // Created by Olivier Poitrey on 10/06/10. 6 | // Copyright 2010 Dailymotion. All rights reserved. 7 | // 8 | 9 | #import "SDAWVPluginAccelerometer.h" 10 | 11 | // defaults to 100 msec 12 | #define kAccelerometerInterval 100 13 | // max rate of 40 msec 14 | #define kMinAccelerometerInterval 40 15 | // min rate of 1/sec 16 | #define kMaxAccelerometerInterval 1000 17 | 18 | @implementation SDAWVPluginAccelerometer 19 | 20 | #pragma mark Plugin API 21 | 22 | - (void)start:(NSArray *)arguments options:(NSDictionary *)options 23 | { 24 | NSTimeInterval frequency = kAccelerometerInterval; 25 | 26 | if ([options objectForKey:@"frequency"]) 27 | { 28 | frequency = [(NSString *)[options objectForKey:@"frequency"] intValue]; 29 | // Special case : returns 0 if int conversion fails 30 | if(frequency == 0) 31 | { 32 | frequency = kAccelerometerInterval; 33 | } 34 | else if(frequency < kMinAccelerometerInterval) 35 | { 36 | frequency = kMinAccelerometerInterval; 37 | } 38 | else if(frequency > kMaxAccelerometerInterval) 39 | { 40 | frequency = kMaxAccelerometerInterval; 41 | } 42 | } 43 | UIAccelerometer *accelerometer = [UIAccelerometer sharedAccelerometer]; 44 | // Accelerometer expects fractional seconds, but we have msecs 45 | accelerometer.updateInterval = frequency / 1000; 46 | accelerometer.delegate = self; 47 | } 48 | 49 | - (void)stop:(NSArray *)arguments options:(NSDictionary *)options 50 | { 51 | UIAccelerometer *accelerometer = [UIAccelerometer sharedAccelerometer]; 52 | if (accelerometer.delegate == self) 53 | { 54 | accelerometer.delegate = nil; 55 | } 56 | } 57 | 58 | - (void)cleanup 59 | { 60 | [self stop:nil options:nil]; 61 | } 62 | 63 | #pragma mark UIAccelerometerDelegate 64 | 65 | - (void)accelerometer:(UIAccelerometer *)accelerometer didAccelerate:(UIAcceleration *)acceleration 66 | { 67 | NSString *script = [[NSString alloc] initWithFormat:@"SDAdvancedWebViewObjects.accelerometer._onAccelUpdate(%f,%f,%f);", acceleration.x, acceleration.y, acceleration.z]; 68 | [delegate.webView stringByEvaluatingJavaScriptFromString:script]; 69 | [script release]; 70 | } 71 | 72 | #pragma mark NSObject 73 | 74 | - (void)dealloc 75 | { 76 | [self stop:nil options:nil]; 77 | [super dealloc]; 78 | } 79 | 80 | @end 81 | -------------------------------------------------------------------------------- /SDAWVPluginAudio.h: -------------------------------------------------------------------------------- 1 | // 2 | // SDAWVPluginAudio.h 3 | // Dailymotion 4 | // 5 | // Created by Olivier Poitrey on 14/06/10. 6 | // Copyright 2010 Dailymotion. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #import "SDAdvancedWebViewPlugin.h" 12 | 13 | @interface SDAWVPluginAudio : SDAdvancedWebViewPlugin 14 | { 15 | AVAudioPlayer *player; 16 | NSURLConnection *connection; 17 | NSMutableData *audioData; 18 | } 19 | 20 | @property (nonatomic, retain) AVAudioPlayer *player; 21 | 22 | - (void)load:(NSArray *)arguments options:(NSDictionary *)options; 23 | - (void)play:(NSArray *)arguments options:(NSDictionary *)options; 24 | - (void)pause:(NSArray *)arguments options:(NSDictionary *)options; 25 | - (void)stop:(NSArray *)arguments options:(NSDictionary *)options; 26 | 27 | @end 28 | -------------------------------------------------------------------------------- /SDAWVPluginAudio.js: -------------------------------------------------------------------------------- 1 | function AudioOptions() 2 | { 3 | /** 4 | * The number of times a sound will return to the beginning, upon reaching the end, to repeat playback. 5 | * 6 | * A value of 0, which is the default, means to play the sound once. Set a positive integer value to specify 7 | * the number of times to return to the start and play again. For example, specifying a value of 1 results 8 | * in a total of two plays of the sound. Set any negative integer value to loop the sound indefinitely until 9 | * you call the stop method. 10 | */ 11 | this.numberOfLoops = 0; 12 | } 13 | 14 | function Audio() 15 | { 16 | this.playing = false; 17 | 18 | this._onSuccessCallback = function() {} 19 | this._onErrorCallback = function() {} 20 | 21 | this._onPlayingStateChange = function(playing) 22 | { 23 | this.playing = playing; 24 | } 25 | 26 | /** 27 | * Load the sound file at the given URL and prepare for playing. 28 | * 29 | * @param url URL of the sound file to load. As the sound is played in memory, sound file size is limited to 1MB. 30 | * @param onSuccessCallback function called when the 31 | */ 32 | this.load = function(url, onSuccessCallback, onErrorCallback) 33 | { 34 | this._onSuccessCallback = onSuccessCallback; 35 | this._onErrorCallback = onErrorCallback; 36 | navigator.comcenter.exec("Audio.load", {"url": url}); 37 | } 38 | 39 | /** 40 | * Plays the previousely loaded sound file. 41 | * 42 | * @param numberOfLoops 43 | * A value of 0, which is the default, means to play the sound once. Set a positive integer value to specify 44 | * the number of times to return to the start and play again. For example, specifying a value of 1 results 45 | * in a total of two plays of the sound. Set any negative integer value to loop the sound indefinitely until 46 | * you call the stop method. 47 | */ 48 | this.play = function(numberOfLoops) 49 | { 50 | navigator.comcenter.exec("Audio.play", numberOfLoops); 51 | } 52 | 53 | this.pause = function() 54 | { 55 | navigator.comcenter.exec("Audio.pause"); 56 | } 57 | 58 | this.stop = function() 59 | { 60 | navigator.comcenter.exec("Audio.stop"); 61 | } 62 | } 63 | 64 | SDAdvancedWebViewObjects['audio'] = new Audio(); 65 | -------------------------------------------------------------------------------- /SDAWVPluginAudio.m: -------------------------------------------------------------------------------- 1 | // 2 | // SDAWVPluginAudio.m 3 | // Dailymotion 4 | // 5 | // Created by Olivier Poitrey on 14/06/10. 6 | // Copyright 2010 Dailymotion. All rights reserved. 7 | // 8 | 9 | #import "SDAWVPluginAudio.h" 10 | 11 | #define kMaxAudioFileBytes 1024*1024 12 | 13 | @interface SDAWVPluginAudio () 14 | @property (nonatomic, retain) NSURLConnection *connection; 15 | @property (nonatomic, retain) NSMutableData *audioData; 16 | @end 17 | 18 | @implementation SDAWVPluginAudio 19 | @synthesize player, connection, audioData; 20 | 21 | #pragma mark Audio Download 22 | 23 | - (void)cancelCurrentDownload 24 | { 25 | if (connection) 26 | { 27 | [connection cancel]; 28 | self.connection = nil; 29 | self.audioData = nil; 30 | } 31 | } 32 | 33 | - (void)downloadAudioData:(NSURL *)audioURL 34 | { 35 | [self cancelCurrentDownload]; 36 | self.connection = [[NSURLConnection alloc] initWithRequest:[NSURLRequest requestWithURL:audioURL] delegate:self]; 37 | if (connection) 38 | { 39 | self.audioData = [NSMutableData data]; 40 | } 41 | } 42 | 43 | - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data 44 | { 45 | [audioData appendData:data]; 46 | if (audioData.length > kMaxAudioFileBytes) 47 | { 48 | [self cancelCurrentDownload]; 49 | [self call:@"_onErrorCallback" args:nil]; 50 | } 51 | } 52 | 53 | - (void)connectionDidFinishLoading:(NSURLConnection *)connection 54 | { 55 | self.connection = nil; 56 | 57 | self.player = [[AVAudioPlayer alloc] initWithData:audioData error:nil]; 58 | player.delegate = self; 59 | [player prepareToPlay]; 60 | self.audioData = nil; 61 | 62 | [self call:@"_onSuccessCallback" args:nil]; 63 | } 64 | 65 | - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error 66 | { 67 | self.connection = nil; 68 | self.audioData = nil; 69 | [self call:@"_onErrorCallback" args:nil]; 70 | } 71 | 72 | #pragma mark Public Methods 73 | 74 | - (void)load:(NSArray *)arguments options:(NSDictionary *)options 75 | { 76 | NSURL *audioURL = nil; 77 | if ([options objectForKey:@"url"]) 78 | { 79 | audioURL = [NSURL URLWithString:[options objectForKey:@"url"] 80 | relativeToURL:delegate.webView.request.URL]; 81 | } 82 | 83 | if (player) 84 | { 85 | if ([player.url isEqual:audioURL]) 86 | { 87 | [self call:@"_onSuccessCallback" args:nil]; 88 | return; 89 | } 90 | else 91 | { 92 | [self stop:nil options:nil]; 93 | } 94 | } 95 | 96 | if (!audioURL) 97 | { 98 | return; 99 | } 100 | 101 | [self downloadAudioData:audioURL]; 102 | } 103 | 104 | - (void)play:(NSArray *)arguments options:(NSDictionary *)options 105 | { 106 | NSInteger numberOfLoops = 0; 107 | if (arguments.count >= 1) 108 | { 109 | numberOfLoops = [[arguments objectAtIndex:0] intValue]; 110 | } 111 | 112 | player.numberOfLoops = numberOfLoops; 113 | if ([player play]) 114 | { 115 | [self call:@"_onPlayingStateChange" args:[NSArray arrayWithObjects:[NSNumber numberWithBool:YES], nil]]; 116 | } 117 | } 118 | 119 | - (void)pause:(NSArray *)arguments options:(NSDictionary *)options 120 | { 121 | [player pause]; 122 | } 123 | 124 | - (void)stop:(NSArray *)arguments options:(NSDictionary *)options 125 | { 126 | if (player) 127 | { 128 | [player stop]; 129 | player.delegate = nil; 130 | self.player = nil; 131 | } 132 | } 133 | 134 | #pragma mark AVAudioPlayerDelegate 135 | 136 | - (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag 137 | { 138 | [self call:@"_onPlayingStateChange" args:[NSArray arrayWithObjects:[NSNumber numberWithBool:NO], nil]]; 139 | } 140 | 141 | #pragma mark SDAdvancedWebViewPlugin 142 | 143 | - (void)cleanup 144 | { 145 | [self stop:nil options:nil]; 146 | } 147 | 148 | #pragma mark NSObject 149 | 150 | - (void)dealloc 151 | { 152 | [self stop:nil options:nil]; 153 | [connection release], connection = nil; 154 | [audioData release], audioData = nil; 155 | [super dealloc]; 156 | } 157 | 158 | 159 | @end 160 | -------------------------------------------------------------------------------- /SDAWVPluginOrientation.h: -------------------------------------------------------------------------------- 1 | // 2 | // SDAWVPluginOrientation.h 3 | // Dailymotion 4 | // 5 | // Created by Olivier Poitrey on 10/06/10. 6 | // Copyright 2010 Dailymotion. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "SDAdvancedWebViewPlugin.h" 11 | 12 | typedef enum 13 | { 14 | SDAWVContentOrientationUnknown = 0, 15 | SDAWVContentOrientationPortrait = UIInterfaceOrientationPortrait, 16 | SDAWVContentOrientationPortraitUpsideDown = UIInterfaceOrientationPortraitUpsideDown, 17 | SDAWVContentOrientationLandscapeLeft = UIInterfaceOrientationLandscapeLeft, 18 | SDAWVContentOrientationLandscapeRight = UIInterfaceOrientationLandscapeRight 19 | } SDAWVContentOrientation; 20 | 21 | @interface SDAWVPluginOrientation : SDAdvancedWebViewPlugin 22 | { 23 | SDAWVContentOrientation forcedContentOrientation; 24 | } 25 | 26 | - (void)setContentOrientation:(NSArray *)arguments options:(NSDictionary *)options; 27 | 28 | - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation; 29 | - (void)notifyCurrentOrientation; 30 | 31 | @end 32 | -------------------------------------------------------------------------------- /SDAWVPluginOrientation.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This class provides access to the device orientation. 3 | * @constructor 4 | */ 5 | function Orientation() 6 | { 7 | /** 8 | * The current orientation, or null if the orientation hasn't changed yet. 9 | */ 10 | this.currentOrientation = null; 11 | 12 | this.init = function() 13 | { 14 | this._notifyCurrentOrientation(this.currentOrientation); 15 | } 16 | 17 | this.shouldAutorotateToContentOrientation = function(orientation) 18 | { 19 | return null; 20 | } 21 | 22 | this._notifyCurrentOrientation = function(orientation) 23 | { 24 | if (document != null && document.body != null) 25 | { 26 | // swap portrait/landscape class on the body element 27 | portrait = orientation == 0 || orientation == 180; 28 | document.body.className = document.body.className.replace(/(?:^| +)(?:portrait|landscape)(?: +|$)/g, " ") 29 | + " " + (portrait ? "portrait" : "landscape"); 30 | 31 | if (this.currentOrientation != null && this.currentOrientation != orientation) 32 | { 33 | var event = document.createEvent('Events'); 34 | event.initEvent('orientationchange', true); 35 | document.dispatchEvent(event); 36 | } 37 | } 38 | 39 | this.currentOrientation = orientation; 40 | } 41 | 42 | this.setContentOrientation = function(orientation) 43 | { 44 | navigator.comcenter.exec("Orientation.setContentOrientation", orientation); 45 | } 46 | } 47 | 48 | SDAdvancedWebViewObjects['orientation'] = new Orientation(); 49 | -------------------------------------------------------------------------------- /SDAWVPluginOrientation.m: -------------------------------------------------------------------------------- 1 | // 2 | // SDAWVPluginOrientation.m 3 | // Dailymotion 4 | // 5 | // Created by Olivier Poitrey on 10/06/10. 6 | // Copyright 2010 Dailymotion. All rights reserved. 7 | // 8 | 9 | #import "SDAWVPluginOrientation.h" 10 | 11 | @implementation SDAWVPluginOrientation 12 | 13 | #pragma mark Private Methods 14 | 15 | - (int)degreeWithOrientation:(SDAWVContentOrientation)interfaceOrientation 16 | { 17 | switch (interfaceOrientation) 18 | { 19 | case SDAWVContentOrientationPortrait: return 0; 20 | case SDAWVContentOrientationPortraitUpsideDown: return 180; 21 | case SDAWVContentOrientationLandscapeLeft: return 90; 22 | case SDAWVContentOrientationLandscapeRight: return -90; 23 | default: return 0; 24 | } 25 | } 26 | 27 | - (SDAWVContentOrientation)orientationWithDegree:(int)degree 28 | { 29 | switch (degree) 30 | { 31 | case 0: return SDAWVContentOrientationPortrait; 32 | case 180: return SDAWVContentOrientationPortraitUpsideDown; 33 | case 90: return SDAWVContentOrientationLandscapeLeft; 34 | case -90: return SDAWVContentOrientationLandscapeRight; 35 | default: return SDAWVContentOrientationUnknown; 36 | } 37 | } 38 | 39 | #pragma mark Plugin API 40 | 41 | - (void)setContentOrientation:(NSArray *)arguments options:(NSDictionary *)options 42 | { 43 | if (arguments.count == 0) 44 | { 45 | return; 46 | } 47 | 48 | if([[arguments objectAtIndex:0] isEqualToString:@""]) 49 | { 50 | forcedContentOrientation = SDAWVContentOrientationUnknown; 51 | } 52 | else 53 | { 54 | forcedContentOrientation = [self orientationWithDegree:[[arguments objectAtIndex:0] intValue]]; 55 | } 56 | 57 | if (delegate.interfaceOrientation != forcedContentOrientation) 58 | { 59 | [delegate retain]; 60 | UIViewController *parent = [delegate parentViewController]; 61 | [delegate dismissModalViewControllerAnimated:NO]; 62 | [parent presentModalViewController:delegate animated:NO]; 63 | [delegate release]; 64 | } 65 | } 66 | 67 | - (void)cleanup 68 | { 69 | forcedContentOrientation = SDAWVContentOrientationUnknown; 70 | } 71 | 72 | #pragma mark Handlers 73 | 74 | - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation 75 | { 76 | if (forcedContentOrientation != SDAWVContentOrientationUnknown) 77 | { 78 | return forcedContentOrientation == interfaceOrientation; 79 | } 80 | else 81 | { 82 | NSString *result = [self call:@"shouldAutorotateToContentOrientation" 83 | args:[NSArray arrayWithObjects:[NSNumber numberWithInt:[self degreeWithOrientation:interfaceOrientation]], nil]]; 84 | if ([result isEqualToString:@""]) 85 | { 86 | return [delegate shouldAutorotateToInterfaceOrientationDefault:interfaceOrientation]; 87 | } 88 | else 89 | { 90 | return [result isEqualToString:@"true"]; 91 | } 92 | } 93 | } 94 | 95 | - (void)notifyCurrentOrientation 96 | { 97 | // UIWebView doesn't have the proper interface orientation info set as it is done in Mobile Safari 98 | // As we can't change the standard window.orientation property, we choose to store the info in navigator.orientation 99 | // as PhoneGap does 100 | [self call:@"_notifyCurrentOrientation" 101 | args:[NSArray arrayWithObjects:[NSNumber numberWithInt:[self degreeWithOrientation:delegate.interfaceOrientation]], nil]]; 102 | } 103 | 104 | @end 105 | -------------------------------------------------------------------------------- /SDAWVPluginShake.h: -------------------------------------------------------------------------------- 1 | // 2 | // SDAWVPluginShake.h 3 | // Dailymotion 4 | // 5 | // Created by Olivier Poitrey on 11/06/10. 6 | // Copyright 2010 Dailymotion. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "SDAdvancedWebViewPlugin.h" 11 | 12 | @interface SDAWVPluginShake : SDAdvancedWebViewPlugin 13 | { 14 | BOOL histeresisExcited; 15 | UIAcceleration *lastAcceleration; 16 | } 17 | 18 | - (void)start:(NSArray *)arguments options:(NSDictionary *)options; 19 | - (void)stop:(NSArray *)arguments options:(NSDictionary *)options; 20 | 21 | @end 22 | -------------------------------------------------------------------------------- /SDAWVPluginShake.js: -------------------------------------------------------------------------------- 1 | function Shake() 2 | { 3 | this._notifyShakeDetected = function() 4 | { 5 | var event = document.createEvent('Events'); 6 | event.initEvent('shake', true); 7 | document.dispatchEvent(event); 8 | } 9 | 10 | this.startListener = function() 11 | { 12 | navigator.comcenter.exec("Shake.start"); 13 | } 14 | 15 | this.stopListener = function() 16 | { 17 | navigator.comcenter.exec("Shake.stop"); 18 | } 19 | } 20 | 21 | SDAdvancedWebViewObjects['shake'] = new Shake(); 22 | -------------------------------------------------------------------------------- /SDAWVPluginShake.m: -------------------------------------------------------------------------------- 1 | // 2 | // SDAWVPluginShake.m 3 | // Dailymotion 4 | // 5 | // Created by Olivier Poitrey on 11/06/10. 6 | // Copyright 2010 Dailymotion. All rights reserved. 7 | // 8 | 9 | #import "SDAWVPluginShake.h" 10 | 11 | static BOOL AccelerationIsShaking(UIAcceleration *last, UIAcceleration *current, double threshold) 12 | { 13 | double deltaX = fabs(last.x - current.x), 14 | deltaY = fabs(last.y - current.y), 15 | deltaZ = fabs(last.z - current.z); 16 | 17 | return (deltaX > threshold && deltaY > threshold) || 18 | (deltaX > threshold && deltaZ > threshold) || 19 | (deltaY > threshold && deltaZ > threshold); 20 | } 21 | 22 | @interface SDAWVPluginShake () 23 | @property (nonatomic, retain) UIAcceleration *lastAcceleration; 24 | @end 25 | 26 | @implementation SDAWVPluginShake 27 | @synthesize lastAcceleration; 28 | 29 | #pragma mark Plugin API 30 | 31 | - (void)start:(NSArray *)arguments options:(NSDictionary *)options 32 | { 33 | [UIAccelerometer sharedAccelerometer].delegate = self; 34 | [UIAccelerometer sharedAccelerometer].updateInterval = 1.0 / 15; 35 | } 36 | 37 | - (void)stop:(NSArray *)arguments options:(NSDictionary *)options 38 | { 39 | if ([UIAccelerometer sharedAccelerometer].delegate == self) 40 | { 41 | [UIAccelerometer sharedAccelerometer].delegate = nil; 42 | } 43 | } 44 | 45 | - (void)cleanup 46 | { 47 | [self stop:nil options:nil]; 48 | } 49 | 50 | #pragma mark UIAccelerometerDelegate 51 | 52 | - (void)accelerometer:(UIAccelerometer *)accelerometer didAccelerate:(UIAcceleration *)acceleration 53 | { 54 | if (self.lastAcceleration) 55 | { 56 | if (!histeresisExcited && AccelerationIsShaking(self.lastAcceleration, acceleration, 0.7)) 57 | { 58 | histeresisExcited = YES; 59 | 60 | // Shake detected, notify webview 61 | [self call:@"_notifyShakeDetected" args:nil]; 62 | } 63 | else if (histeresisExcited && !AccelerationIsShaking(self.lastAcceleration, acceleration, 0.2)) 64 | { 65 | histeresisExcited = NO; 66 | } 67 | } 68 | 69 | self.lastAcceleration = acceleration; 70 | } 71 | 72 | #pragma mark NSObject 73 | 74 | - (void)dealloc 75 | { 76 | [self stop:nil options:nil]; 77 | [super dealloc]; 78 | } 79 | 80 | @end 81 | -------------------------------------------------------------------------------- /SDAdvancedWebViewCommand.h: -------------------------------------------------------------------------------- 1 | // 2 | // SDAdvancedWebViewCommand.h 3 | // SDAdvancedWebView 4 | // 5 | // Created by Olivier Poitrey on 10/06/10. 6 | // Copyright 2010 Dailymotion. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface SDAdvancedWebViewCommand : NSObject 12 | { 13 | @private 14 | NSString *command, *pluginName; 15 | SEL pluginSelector; 16 | NSArray* arguments; 17 | NSDictionary* options; 18 | } 19 | 20 | @property (nonatomic, retain) NSString *command, *pluginName; 21 | @property (nonatomic, assign) SEL pluginSelector; 22 | @property (nonatomic, retain) NSArray *arguments; 23 | @property (nonatomic, retain) NSDictionary *options; 24 | 25 | - (id)initWithURL:(NSURL *)url; 26 | 27 | @end 28 | -------------------------------------------------------------------------------- /SDAdvancedWebViewCommand.m: -------------------------------------------------------------------------------- 1 | // 2 | // SDAdvancedWebViewCommand.m 3 | // SDAdvancedWebView 4 | // 5 | // Inspired from Shazron Abdullah's PhoneGap's InvokedUrlCommand class 6 | // Created by Olivier Poitrey on 10/06/10. 7 | // Copyright 2010 Dailymotion. All rights reserved. 8 | // 9 | 10 | #import "SDAdvancedWebViewCommand.h" 11 | 12 | @implementation SDAdvancedWebViewCommand 13 | @synthesize command, pluginName, pluginSelector, arguments, options; 14 | 15 | - (id)initWithURL:(NSURL *)url 16 | { 17 | if ((self = [super init])) 18 | { 19 | self.command = url.host; 20 | 21 | NSString *path = [url.path substringFromIndex:1]; // remove the leading slash 22 | NSMutableArray *args = [NSMutableArray array]; 23 | for (NSString *arg in [NSMutableArray arrayWithArray:[path componentsSeparatedByString:@"/"]]) 24 | { 25 | [args addObject:[arg stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]]; 26 | } 27 | self.arguments = args; 28 | 29 | NSMutableDictionary *opts = [NSMutableDictionary dictionary]; 30 | for (NSString *component in [url.query componentsSeparatedByString:@"&"]) 31 | { 32 | NSArray *key_value = [component componentsSeparatedByString:@"="]; 33 | NSString *name = [(NSString *)[key_value objectAtIndex:0] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; 34 | NSObject *value; 35 | if (key_value.count == 2) 36 | { 37 | value = [(NSString *)[key_value objectAtIndex:1] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; 38 | } 39 | else 40 | { 41 | value = [NSNull null]; 42 | } 43 | [opts setObject:value forKey:name]; 44 | } 45 | self.options = opts; 46 | 47 | NSArray* components = [url.host componentsSeparatedByString:@"."]; 48 | if (components.count == 1) 49 | { 50 | self.pluginName = nil; 51 | self.pluginSelector = NSSelectorFromString([NSString stringWithFormat:@"%@:options:", url.host]); 52 | } 53 | else if (components.count == 2) 54 | { 55 | self.pluginName = [components objectAtIndex:0]; 56 | self.pluginSelector = NSSelectorFromString([NSString stringWithFormat:@"%@:options:", [components objectAtIndex:1]]); 57 | } 58 | } 59 | return self; 60 | } 61 | 62 | - (NSString *)description 63 | { 64 | return [NSString stringWithFormat:@"%@(%@, %@)", command, [arguments componentsJoinedByString:@", "], options]; 65 | } 66 | 67 | - (void)dealloc 68 | { 69 | [command release], arguments = nil; 70 | [pluginName release], pluginName = nil; 71 | [arguments release], arguments = nil; 72 | [options release], options = nil; 73 | [super dealloc]; 74 | } 75 | 76 | 77 | @end 78 | -------------------------------------------------------------------------------- /SDAdvancedWebViewCommunicationCenter.js: -------------------------------------------------------------------------------- 1 | // 2 | // This code is highly inspired from the PhoneGap project 3 | // 4 | 5 | function SDAdvancedWebViewComCenter() 6 | { 7 | this.queue = 8 | { 9 | ready: true, 10 | commands: [], 11 | timer: null 12 | }; 13 | 14 | /** 15 | * Execute commands in a queued fashion, to ensure commands do not 16 | * execute with any race conditions, and only run when comcenter is ready to 17 | * recieve them. 18 | * @param {String} command Command to be run, e.g. "ClassName.method" 19 | * @param {String[]} [args] Zero or more arguments to pass to the method 20 | * @param {Object} an optional dictionnary e.g. {"arg1": "value1", "arg2", "value2"} 21 | */ 22 | this.exec = function() 23 | { 24 | this.queue.commands.push(arguments); 25 | if (this.queue.timer == null) 26 | { 27 | var self = this; 28 | this.queue.timer = setInterval(function() {self.runCommand()}, 10); 29 | } 30 | } 31 | 32 | /** 33 | * Internal function used to dispatch the request back to UIWebView. It processes the 34 | * command queue and executes the next command on the list. If one of the 35 | * arguments is a JavaScript object, it will be passed on the QueryString of the 36 | * url, which will be turned into a dictionary on the other end. 37 | * @private 38 | */ 39 | this.runCommand = function() 40 | { 41 | if (!this.queue.ready) 42 | { 43 | return; 44 | } 45 | 46 | this.queue.ready = false; 47 | 48 | var args = this.queue.commands.shift(); 49 | if (this.queue.commands.length == 0) 50 | { 51 | clearInterval(this.queue.timer); 52 | this.queue.timer = null; 53 | } 54 | 55 | var command = args[0]; 56 | var path = []; 57 | var options = ''; 58 | var last_arg_idx = args.length - 1; 59 | 60 | if (args.length > 1 && typeof(args[args.length - 1]) == 'object') 61 | { 62 | last_arg_idx--; 63 | var dict = args[args.length - 1]; 64 | var components = []; 65 | for (var name in dict) 66 | { 67 | if (typeof(name) != 'string') 68 | { 69 | continue; 70 | } 71 | components.push(encodeURIComponent(name) + "=" + encodeURIComponent(dict[name])); 72 | } 73 | if (components.length > 0) 74 | { 75 | options = '?' + components.join("&"); 76 | } 77 | } 78 | 79 | for (var i = 1; i <= last_arg_idx; i++) 80 | { 81 | var arg = args[i]; 82 | if (arg == undefined || arg == null) 83 | { 84 | arg = ''; 85 | } 86 | path.push(encodeURIComponent(arg)); 87 | } 88 | 89 | document.location = "comcenter://" + command + "/" + path.join("/") + options; 90 | } 91 | } 92 | 93 | SDAdvancedWebViewObjects = 94 | { 95 | 'comcenter': new SDAdvancedWebViewComCenter() 96 | }; 97 | 98 | (function() 99 | { 100 | var timer = setInterval(function() 101 | { 102 | var state = document.readyState; 103 | 104 | if ((state == 'loaded' || state == 'complete')) 105 | { 106 | clearInterval(timer); // stop looking 107 | 108 | for (var name in SDAdvancedWebViewObjects) 109 | { 110 | navigator[name] = SDAdvancedWebViewObjects[name]; 111 | if (navigator[name].init != null) 112 | { 113 | navigator[name].init(); 114 | } 115 | } 116 | 117 | var event = document.createEvent('Events'); 118 | event.initEvent('deviceready', true); 119 | document.dispatchEvent(event); 120 | } 121 | }, 1); 122 | })(); 123 | -------------------------------------------------------------------------------- /SDAdvancedWebViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // SDAdvancedWebViewController.h 3 | // SDAdvancedWebView 4 | // 5 | // Created by Olivier Poitrey on 07/06/10. 6 | // Copyright 2010 Dailymotion. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class SDAdvancedWebViewPlugin; 12 | @class SDAdvancedWebViewControllerDelegate; 13 | @protocol SDAdvancedWebViewControllerDelegate 14 | @optional 15 | - (void)advancedWebViewController:(SDAdvancedWebViewControllerDelegate *)advancedWebViewController didOpenExternalUrl:(NSURL *)externalUrl; 16 | - (void)advancedWebViewController:(SDAdvancedWebViewControllerDelegate *)advancedWebViewController didCancelExternalUrl:(NSURL *)externalUrl; 17 | @end 18 | 19 | @interface SDAdvancedWebViewController : UIViewController 20 | { 21 | @private 22 | UIWebView *webView; 23 | id delegate; 24 | id webViewDelegate; 25 | NSMutableDictionary *loadedPlugins; 26 | NSURL *invokeURL; 27 | } 28 | 29 | @property (nonatomic, retain) IBOutlet UIWebView *webView; 30 | @property (nonatomic, assign) IBOutlet id delegate; 31 | @property (nonatomic, assign) IBOutlet id webViewDelegate; 32 | 33 | - (SDAdvancedWebViewPlugin *)pluginWithName:(NSString *)pluginName load:(BOOL)load; 34 | - (BOOL)shouldAutorotateToInterfaceOrientationDefault:(UIInterfaceOrientation)interfaceOrientation; 35 | 36 | @end 37 | -------------------------------------------------------------------------------- /SDAdvancedWebViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // SDAdvancedWebViewController.m 3 | // SDAdvancedWebView 4 | // 5 | // Created by Olivier Poitrey on 07/06/10. 6 | // Copyright 2010 Dailymotion. All rights reserved. 7 | // 8 | 9 | #import "SDAdvancedWebViewController.h" 10 | #import "SDAdvancedWebViewCommand.h" 11 | #import "SDAdvancedWebViewPlugin.h" 12 | 13 | // Plugins 14 | #import "SDAWVPluginAccelerometer.h" 15 | #import "SDAWVPluginOrientation.h" 16 | #import "SDAWVPluginShake.h" 17 | #import "SDAWVPluginAudio.h" 18 | 19 | @interface SDAdvancedWebViewController () 20 | @property (nonatomic, retain) NSMutableDictionary *loadedPlugins; 21 | @property (nonatomic, retain) NSURL *invokeURL; 22 | @end 23 | 24 | @implementation SDAdvancedWebViewController 25 | @synthesize delegate, webViewDelegate, loadedPlugins, invokeURL; 26 | @dynamic webView; 27 | 28 | #pragma mark Public Methods 29 | 30 | - (SDAdvancedWebViewPlugin *)pluginWithName:(NSString *)pluginName load:(BOOL)load 31 | { 32 | SDAdvancedWebViewPlugin *plugin = [loadedPlugins objectForKey:pluginName]; 33 | if (!plugin && load) 34 | { 35 | Class pluginClass = NSClassFromString([NSString stringWithFormat:@"SDAWVPlugin%@", pluginName]); 36 | plugin = [[pluginClass alloc] init]; 37 | plugin.delegate = self; 38 | if (!loadedPlugins) 39 | { 40 | self.loadedPlugins = [NSMutableDictionary dictionary]; 41 | } 42 | [loadedPlugins setObject:plugin forKey:pluginName]; 43 | [plugin release]; 44 | } 45 | return plugin; 46 | } 47 | 48 | #pragma mark SDAdvancedWebViewController (accessors) 49 | 50 | - (UIWebView *)webView 51 | { 52 | if (!webView && ![self isViewLoaded]) 53 | { 54 | // force the loading of the view in order to generate the webview 55 | [self view]; 56 | } 57 | return [[webView retain] autorelease]; 58 | } 59 | 60 | - (void)setWebView:(UIWebView *)newWebView 61 | { 62 | if (webView != newWebView) 63 | { 64 | if (newWebView) 65 | { 66 | if (!newWebView.delegate) 67 | { 68 | newWebView.delegate = self; 69 | } 70 | 71 | if (!newWebView.superview) 72 | { 73 | newWebView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; 74 | newWebView.frame = self.view.bounds; 75 | [self.view addSubview:newWebView]; 76 | } 77 | } 78 | else 79 | { 80 | if (webView.delegate == self) 81 | { 82 | webView.delegate = nil; // Prevents BAD_EXEC if self is released before webView 83 | } 84 | 85 | // Force release loaded page elements by loading about:blank special page 86 | // (iPad have a bug with media player continuing playback even once webview is released) 87 | [webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"about:blank"]]]; 88 | } 89 | 90 | [webView release]; 91 | webView = [newWebView retain]; 92 | } 93 | } 94 | 95 | #pragma mark UIViewController 96 | 97 | - (void)viewDidLoad 98 | { 99 | [super viewDidLoad]; 100 | 101 | if (!webView) 102 | { 103 | self.webView = [[[UIWebView alloc] initWithFrame:self.view.bounds] autorelease]; 104 | webView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; 105 | } 106 | } 107 | 108 | - (void)viewDidUnload 109 | { 110 | [super viewDidUnload]; 111 | self.webView = nil; 112 | } 113 | 114 | - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation 115 | { 116 | SDAWVPluginOrientation *orientationPlugin = (SDAWVPluginOrientation *)[self pluginWithName:@"Orientation" load:NO]; 117 | if (orientationPlugin) 118 | { 119 | return [orientationPlugin shouldAutorotateToInterfaceOrientation:interfaceOrientation]; 120 | } 121 | else 122 | { 123 | return [self shouldAutorotateToInterfaceOrientationDefault:(UIInterfaceOrientation)interfaceOrientation]; 124 | } 125 | } 126 | 127 | - (BOOL)shouldAutorotateToInterfaceOrientationDefault:(UIInterfaceOrientation)interfaceOrientation 128 | { 129 | // Web content not loaded yet, applying some defaults 130 | if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) 131 | { 132 | // On iPhone, upside down orientation is not advised 133 | return interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown; 134 | } 135 | else 136 | { 137 | // On iPad, all orientations are allowed by default (until web content says something else) 138 | return YES; 139 | } 140 | } 141 | 142 | - (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation 143 | { 144 | [(SDAWVPluginOrientation *)[self pluginWithName:@"Orientation" load:NO] notifyCurrentOrientation]; 145 | } 146 | 147 | #pragma mark UIWebViewDelegate 148 | 149 | - (void)webViewDidStartLoad:(UIWebView *)aWebView 150 | { 151 | if ([webViewDelegate respondsToSelector:@selector(webViewDidStartLoad:)]) 152 | { 153 | [webViewDelegate webViewDidStartLoad:aWebView]; 154 | } 155 | } 156 | 157 | - (void)webViewDidFinishLoad:(UIWebView *)aWebView 158 | { 159 | NSMutableString *script = [NSMutableString string]; 160 | 161 | // Load communication center code 162 | NSString *comcenterPath = [[NSBundle mainBundle] pathForResource:@"SDAdvancedWebViewCommunicationCenter" ofType:@"js"]; 163 | [script appendString:[NSString stringWithContentsOfFile:comcenterPath encoding:NSUTF8StringEncoding error:nil]]; 164 | 165 | // Send device information 166 | UIDevice *device = [UIDevice currentDevice]; 167 | [script appendFormat:@"DeviceInfo = {\"platform\": \"%@\", \"version\": \"%@\", \"uuid\": \"%@\", \"name\": \"%@\"};", 168 | device.model, device.systemVersion, device.uniqueIdentifier, device.name]; 169 | 170 | [aWebView stringByEvaluatingJavaScriptFromString:script]; 171 | 172 | // Cleanup the loaded plugins, each pages have to be isolated 173 | if (loadedPlugins) 174 | { 175 | [loadedPlugins.allValues makeObjectsPerformSelector:@selector(cleanup)]; 176 | } 177 | 178 | // Init plugins for the view 179 | [SDAWVPluginAccelerometer installPluginForWebview:aWebView]; 180 | [SDAWVPluginOrientation installPluginForWebview:aWebView]; 181 | [SDAWVPluginShake installPluginForWebview:aWebView]; 182 | [SDAWVPluginAudio installPluginForWebview:aWebView]; 183 | 184 | // Inject the current orientation into the webview 185 | [(SDAWVPluginOrientation *)[self pluginWithName:@"Orientation" load:YES] notifyCurrentOrientation]; 186 | 187 | if ([webViewDelegate respondsToSelector:@selector(webViewDidFinishLoad:)]) 188 | { 189 | [webViewDelegate webViewDidFinishLoad:aWebView]; 190 | } 191 | } 192 | 193 | - (void)webView:(UIWebView *)aWebView didFailLoadWithError:(NSError *)error 194 | { 195 | if ([webViewDelegate respondsToSelector:@selector(webView:didFailLoadWithError:)]) 196 | { 197 | [webViewDelegate webView:aWebView didFailLoadWithError:error]; 198 | } 199 | } 200 | 201 | - (BOOL)webView:(UIWebView *)aWebView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType 202 | { 203 | NSURL *url = request.URL; 204 | 205 | if ([url.scheme isEqualToString:@"comcenter"]) 206 | { 207 | // Tell the JS code that we've gotten this command, and we're ready for another 208 | [aWebView stringByEvaluatingJavaScriptFromString:@"navigator.comcenter.queue.ready = true;"]; 209 | 210 | // Try to execute the command 211 | SDAdvancedWebViewCommand *command = [[SDAdvancedWebViewCommand alloc] initWithURL:url]; 212 | SDAdvancedWebViewPlugin *plugin = [self pluginWithName:command.pluginName load:YES]; 213 | if ([plugin respondsToSelector:command.pluginSelector]) 214 | { 215 | #if DEBUG==1 216 | NSLog(@"JS->ObjC command: %@", command); 217 | #endif 218 | [plugin performSelector:command.pluginSelector withObject:command.arguments withObject:command.options]; 219 | } 220 | else 221 | { 222 | NSLog(@"Invalid command: %@", command); 223 | } 224 | 225 | [command release]; 226 | 227 | return NO; 228 | } 229 | 230 | if ([webViewDelegate respondsToSelector:@selector(webView:shouldStartLoadWithRequest:navigationType:)]) 231 | { 232 | if (![webViewDelegate webView:aWebView shouldStartLoadWithRequest:request navigationType:navigationType]) 233 | { 234 | return NO; 235 | } 236 | } 237 | 238 | // Handles special URLs like URLs to AppStore or other kinds of schemes like tel: appname: etc... 239 | // The user is asked if he accept to leave the app in order to open those external resources 240 | if ((![url.scheme isEqualToString:@"http"] && ![url.scheme isEqualToString:@"https"] && ![url.scheme isEqualToString:@"about"]) 241 | || [url.host isEqualToString:@"phobos.apple.com"] 242 | || [url.host isEqualToString:@"itunes.apple.com"] 243 | || [url.host isEqualToString:@"maps.google.com"]) 244 | { 245 | if ([[UIApplication sharedApplication] canOpenURL:url]) 246 | { 247 | NSString *targetName; 248 | if ([url.host isEqualToString:@"phobos.apple.com"] || [url.host isEqualToString:@"itunes.apple.com"]) 249 | { 250 | if (([url.path rangeOfString:@"/app/"]).location == NSNotFound) 251 | { 252 | targetName = @"iTunes"; 253 | } 254 | else 255 | { 256 | targetName = @"App Store"; 257 | } 258 | } 259 | else if ([url.host isEqualToString:@"maps.google.com"]) 260 | { 261 | targetName = NSLocalizedString(@"Maps", @"Do you want to open "); 262 | } 263 | else if ([url.scheme isEqualToString:@"mailto"]) 264 | { 265 | targetName = NSLocalizedString(@"Mail", @"Do you want to open "); 266 | } 267 | else if ([url.scheme isEqualToString:@"sms"]) 268 | { 269 | targetName = NSLocalizedString(@"Text", @"Do you want to open "); 270 | } 271 | else if ([url.scheme isEqualToString:@"tel"]) 272 | { 273 | targetName = NSLocalizedString(@"Phone", @"Do you want to open: "); 274 | } 275 | else 276 | { 277 | targetName = NSLocalizedString(@"another application", @"Do you want to open "); 278 | } 279 | 280 | self.invokeURL = url; 281 | [[[[UIAlertView alloc] initWithTitle:[NSString stringWithFormat:NSLocalizedString(@"You are about to leave %@", nil), 282 | [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleExecutable"]] 283 | message:[NSString stringWithFormat:NSLocalizedString(@"Do you want to open %@?", nil), targetName] 284 | delegate:self 285 | cancelButtonTitle:NSLocalizedString(@"Cancel", nil) 286 | otherButtonTitles:NSLocalizedString(@"Open", nil), nil] autorelease] show]; 287 | } 288 | return NO; 289 | } 290 | 291 | return YES; 292 | } 293 | 294 | #pragma mark UIAlertViewDelegate 295 | 296 | - (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex 297 | { 298 | if (buttonIndex >= alertView.firstOtherButtonIndex) 299 | { 300 | [[UIApplication sharedApplication] openURL:invokeURL]; 301 | 302 | if ([delegate respondsToSelector:@selector(advancedWebViewController:didOpenExternalUrl:)]) 303 | { 304 | [delegate performSelector:@selector(advancedWebViewController:didOpenExternalUrl:) withObject:self withObject:invokeURL]; 305 | } 306 | } 307 | else if (buttonIndex == alertView.cancelButtonIndex) 308 | { 309 | if ([delegate respondsToSelector:@selector(advancedWebViewController:didCancelExternalUrl:)]) 310 | { 311 | [delegate performSelector:@selector(advancedWebViewController:didCancelExternalUrl:) withObject:self withObject:invokeURL]; 312 | } 313 | } 314 | 315 | self.invokeURL = nil; 316 | } 317 | 318 | #pragma mark NSObject 319 | 320 | - (void)dealloc 321 | { 322 | self.webView = nil; 323 | [loadedPlugins.allValues makeObjectsPerformSelector:@selector(setDelegate:) withObject:nil]; 324 | [loadedPlugins release], loadedPlugins = nil; 325 | [invokeURL release], invokeURL = nil; 326 | [super dealloc]; 327 | } 328 | 329 | @end 330 | -------------------------------------------------------------------------------- /SDAdvancedWebViewPlugin.h: -------------------------------------------------------------------------------- 1 | // 2 | // SDAdvancedWebViewPlugin.h 3 | // SDAdvancedWebView 4 | // 5 | // Created by Olivier Poitrey on 10/06/10. 6 | // Copyright 2010 Dailymotion. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "SDAdvancedWebViewController.h" 11 | 12 | @interface SDAdvancedWebViewPlugin : NSObject 13 | { 14 | SDAdvancedWebViewController *delegate; 15 | } 16 | 17 | @property (nonatomic, assign) SDAdvancedWebViewController *delegate; 18 | 19 | + (void)installPluginForWebview:(UIWebView *)aWebView; 20 | - (NSString *)call:(NSString *)methodName args:(NSArray *)arguments; 21 | - (void)cleanup; 22 | 23 | @end 24 | -------------------------------------------------------------------------------- /SDAdvancedWebViewPlugin.m: -------------------------------------------------------------------------------- 1 | // 2 | // SDAdvancedWebViewPlugin.m 3 | // SDAdvancedWebView 4 | // 5 | // Created by Olivier Poitrey on 10/06/10. 6 | // Copyright 2010 Dailymotion. All rights reserved. 7 | // 8 | 9 | #import "SDAdvancedWebViewPlugin.h" 10 | 11 | @implementation SDAdvancedWebViewPlugin 12 | @synthesize delegate; 13 | 14 | + (void)installPluginForWebview:(UIWebView *)aWebView 15 | { 16 | NSString *path = [[NSBundle mainBundle] pathForResource:NSStringFromClass(self) ofType:@"js"]; 17 | if (path) 18 | { 19 | NSString *script = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil]; 20 | [aWebView stringByEvaluatingJavaScriptFromString:script]; 21 | } 22 | } 23 | 24 | - (NSString *)call:(NSString *)methodName args:(NSArray *)arguments 25 | { 26 | NSMutableArray *jsArguments = [NSMutableArray arrayWithCapacity:arguments.count]; 27 | if (arguments) 28 | { 29 | for (id arg in arguments) 30 | { 31 | if ([arg isKindOfClass:NSString.class]) 32 | { 33 | [jsArguments addObject:[NSString stringWithFormat:@"\"%@\"", 34 | [arg stringByReplacingOccurrencesOfString:@"\"" withString:@"\\\""]]]; 35 | } 36 | else if ([arg isKindOfClass:NSNumber.class]) 37 | { 38 | [jsArguments addObject:[arg stringValue]]; 39 | } 40 | else 41 | { 42 | [jsArguments addObject:@"null"]; 43 | } 44 | } 45 | } 46 | 47 | NSString *pluginName = [[NSStringFromClass(self.class) stringByReplacingOccurrencesOfString:@"SDAWVPlugin" withString:@""] lowercaseString]; 48 | NSString *script = [NSString stringWithFormat:@"SDAdvancedWebViewObjects.%@.%@(%@)", pluginName, methodName, [jsArguments componentsJoinedByString:@", "]]; 49 | NSString *result = [delegate.webView stringByEvaluatingJavaScriptFromString:script]; 50 | #if DEBUG==1 51 | NSLog(@"ObjC->JS method: %@ result: %@", script, result); 52 | #endif 53 | return result; 54 | } 55 | 56 | - (void)cleanup 57 | { 58 | } 59 | 60 | - (void)dealloc 61 | { 62 | [self cleanup]; 63 | [super dealloc]; 64 | } 65 | 66 | 67 | @end 68 | --------------------------------------------------------------------------------