├── .gitignore ├── .travis.yml ├── Assets └── hero.png ├── Examples ├── README.md ├── UIWebView+WBWebViewConsole │ ├── Podfile │ ├── Podfile.lock │ ├── UIWebView+WBWebViewConsole.xcodeproj │ │ ├── project.pbxproj │ │ ├── project.xcworkspace │ │ │ ├── contents.xcworkspacedata │ │ │ └── xcuserdata │ │ │ │ └── wutian.xcuserdatad │ │ │ │ └── UserInterfaceState.xcuserstate │ │ └── xcuserdata │ │ │ └── wutian.xcuserdatad │ │ │ └── xcschemes │ │ │ ├── UIWebView+WBWebViewConsole.xcscheme │ │ │ └── xcschememanagement.plist │ ├── UIWebView+WBWebViewConsole.xcworkspace │ │ └── contents.xcworkspacedata │ ├── UIWebView+WBWebViewConsole │ │ ├── AppDelegate.h │ │ ├── AppDelegate.m │ │ ├── Base.lproj │ │ │ └── LaunchScreen.xib │ │ ├── Images.xcassets │ │ │ └── AppIcon.appiconset │ │ │ │ └── Contents.json │ │ ├── Info.plist │ │ ├── WBUIWebView.h │ │ ├── WBUIWebView.m │ │ ├── WBWebViewController.h │ │ ├── WBWebViewController.m │ │ └── main.m │ └── UIWebView+WBWebViewConsoleTests │ │ ├── Info.plist │ │ └── UIWebView_WBWebViewConsoleTests.m └── WKWebView+WBWebViewConsole │ ├── Podfile │ ├── Podfile.lock │ ├── WKWebView+WBWebViewConsole.xcodeproj │ ├── project.pbxproj │ └── project.xcworkspace │ │ └── contents.xcworkspacedata │ ├── WKWebView+WBWebViewConsole.xcworkspace │ └── contents.xcworkspacedata │ ├── WKWebView+WBWebViewConsole │ ├── AppDelegate.h │ ├── AppDelegate.m │ ├── Base.lproj │ │ └── LaunchScreen.xib │ ├── Images.xcassets │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ ├── Info.plist │ ├── WBWKWebView.h │ ├── WBWKWebView.m │ ├── WBWebViewController.h │ ├── WBWebViewController.m │ └── main.m │ └── WKWebView+WBWebViewConsoleTests │ ├── Info.plist │ └── WKWebView_WBWebViewConsoleTests.m ├── LICENSE ├── README.md ├── WBWebViewConsole.podspec ├── WBWebViewConsole.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcuserdata │ │ └── wutian.xcuserdatad │ │ └── UserInterfaceState.xcuserstate ├── xcshareddata │ └── xcschemes │ │ └── WBWebViewConsole.xcscheme └── xcuserdata │ └── wutian.xcuserdatad │ └── xcschemes │ └── xcschememanagement.plist ├── WBWebViewConsole ├── Controllers │ ├── WBWebDebugConsoleViewController.h │ └── WBWebDebugConsoleViewController.m ├── Models │ ├── UserPrompt │ │ ├── WBWebViewConsoleUserPromptCompletionController.h │ │ ├── WBWebViewConsoleUserPromptCompletionController.m │ │ ├── WBWebViewConsoleUserPromptCompletionDatasource.h │ │ └── WBWebViewConsoleUserPromptCompletionDatasource.m │ ├── WBJSBridgeActionPrivateConsoleLog.h │ ├── WBJSBridgeActionPrivateConsoleLog.m │ ├── WBWebViewConsole.h │ ├── WBWebViewConsole.m │ ├── WBWebViewConsoleInputHistoryEntry.h │ ├── WBWebViewConsoleInputHistoryEntry.m │ ├── WBWebViewConsoleMessage.h │ └── WBWebViewConsoleMessage.m ├── Resources │ └── WBWebBrowserConsole.bundle │ │ ├── console_prompt_completion.js │ │ ├── debug_icon@2x.png │ │ ├── error_icon@2x.png │ │ ├── info_icon@2x.png │ │ ├── issue_icon@2x.png │ │ ├── navigation_issue_icon@2x.png │ │ ├── navigation_success_icon@2x.png │ │ ├── success_icon@2x.png │ │ ├── userinput_action_panel.png │ │ ├── userinput_action_panel@2x.png │ │ ├── userinput_actions.png │ │ ├── userinput_actions@2x.png │ │ ├── userinput_prompt@2x.png │ │ ├── userinput_prompt_previous@2x.png │ │ └── userinput_result@2x.png ├── Supports │ ├── NSDictionary+WBTTypeCast.h │ ├── NSDictionary+WBTTypeCast.m │ ├── NSObject+WBJSONKit.h │ ├── NSObject+WBJSONKit.m │ ├── UIColor+WBTHelpers.h │ ├── UIColor+WBTHelpers.m │ ├── UIDevice+WBTHelpers.h │ ├── UIDevice+WBTHelpers.m │ ├── UIScrollView+WBTUtilities.h │ ├── UIScrollView+WBTUtilities.m │ ├── UIView+WBTSizes.h │ ├── UIView+WBTSizes.m │ ├── WBKeyboardObserver.h │ ├── WBKeyboardObserver.m │ ├── WBTTypeCastUtil.h │ ├── WBTTypeCastUtil.m │ ├── WBTextView.h │ ├── WBTextView.m │ ├── WBWebView │ │ ├── JSBridge │ │ │ ├── Actions │ │ │ │ ├── WBJSBridgeAction.h │ │ │ │ └── WBJSBridgeAction.m │ │ │ ├── Resources │ │ │ │ └── WBWebBrowserJSBridge.bundle │ │ │ │ │ └── wbjs.js │ │ │ ├── WBJSBridgeMessage.h │ │ │ ├── WBJSBridgeMessage.m │ │ │ ├── WBWebViewJSBridge.h │ │ │ └── WBWebViewJSBridge.m │ │ ├── WBWebView.h │ │ ├── WBWebViewUserScript.h │ │ └── WBWebViewUserScript.m │ └── WBWebViewConsoleDefines.h └── Views │ ├── WBWebViewConsoleInputView.h │ ├── WBWebViewConsoleInputView.m │ ├── WBWebViewConsoleInputViewActionButton.h │ ├── WBWebViewConsoleInputViewActionButton.m │ ├── WBWebViewConsoleMessageCell.h │ └── WBWebViewConsoleMessageCell.m └── WBWebViewConsoleTests ├── Base ├── WBTestsUIWebView.h └── WBTestsUIWebView.m ├── Info.plist └── WBWebViewJSBridgeTests.m /.gitignore: -------------------------------------------------------------------------------- 1 | # OS generated files # 2 | ###################### 3 | .DS_Store* 4 | ehthumbs.db 5 | Icon? 6 | Thumbs.db 7 | # Xcode 8 | build/ 9 | *.pbxuser 10 | !default.pbxuser 11 | *.mode1v3 12 | !default.mode1v3 13 | *.mode2v3 14 | !default.mode2v3 15 | *.perspectivev3 16 | !default.perspectivev3 17 | xcuserdata 18 | *.xccheckout 19 | profile 20 | *.moved-aside 21 | DerivedData 22 | *.hmap 23 | *.ipa 24 | 25 | # CocoaPods 26 | Pods -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: objective-c 2 | before_install: 3 | - brew update 4 | - brew reinstall xctool 5 | - gem update cocoapods 6 | xcode_project: WBWebViewConsole.xcodeproj 7 | xcode_scheme: WBWebViewConsole 8 | xcode_sdk: 9 | - iphonesimulator9.3 10 | -------------------------------------------------------------------------------- /Assets/hero.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naituw/WBWebViewConsole/2c9f5b37a7a8b875f303396addfaebef6f5b2bdf/Assets/hero.png -------------------------------------------------------------------------------- /Examples/README.md: -------------------------------------------------------------------------------- 1 | # Sample projects 2 | 3 | ## Building 4 | 5 | Run `pod install` in each sample project directory to set up their 6 | dependencies. 7 | 8 | ## License 9 | 10 | This file provided by Weibo is for non-commercial testing and evaluation 11 | purposes only. Weibo reserves all rights not expressly granted. 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 16 | WEIBO BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 17 | ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /Examples/UIWebView+WBWebViewConsole/Podfile: -------------------------------------------------------------------------------- 1 | source 'https://github.com/CocoaPods/Specs.git' 2 | platform :ios, '6.0' 3 | 4 | target 'UIWebView+WBWebViewConsole' do 5 | pod 'WBWebViewConsole', :path => '../..' 6 | end -------------------------------------------------------------------------------- /Examples/UIWebView+WBWebViewConsole/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - WBWebViewConsole (1.0.2) 3 | 4 | DEPENDENCIES: 5 | - WBWebViewConsole (from `../..`) 6 | 7 | EXTERNAL SOURCES: 8 | WBWebViewConsole: 9 | :path: "../.." 10 | 11 | SPEC CHECKSUMS: 12 | WBWebViewConsole: 1288737d136b44a281cc654828339127f959e21c 13 | 14 | PODFILE CHECKSUM: 34e4d1ba8829f9e9bd3572214cd3e5d4c12ea318 15 | 16 | COCOAPODS: 1.1.1 17 | -------------------------------------------------------------------------------- /Examples/UIWebView+WBWebViewConsole/UIWebView+WBWebViewConsole.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Examples/UIWebView+WBWebViewConsole/UIWebView+WBWebViewConsole.xcodeproj/project.xcworkspace/xcuserdata/wutian.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naituw/WBWebViewConsole/2c9f5b37a7a8b875f303396addfaebef6f5b2bdf/Examples/UIWebView+WBWebViewConsole/UIWebView+WBWebViewConsole.xcodeproj/project.xcworkspace/xcuserdata/wutian.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /Examples/UIWebView+WBWebViewConsole/UIWebView+WBWebViewConsole.xcodeproj/xcuserdata/wutian.xcuserdatad/xcschemes/UIWebView+WBWebViewConsole.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 44 | 45 | 47 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 65 | 66 | 75 | 76 | 82 | 83 | 84 | 85 | 86 | 87 | 93 | 94 | 100 | 101 | 102 | 103 | 105 | 106 | 109 | 110 | 111 | -------------------------------------------------------------------------------- /Examples/UIWebView+WBWebViewConsole/UIWebView+WBWebViewConsole.xcodeproj/xcuserdata/wutian.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | UIWebView+WBWebViewConsole.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | C6B12CA21A8DCCBD004F2AD2 16 | 17 | primary 18 | 19 | 20 | C6B12CBB1A8DCCBD004F2AD2 21 | 22 | primary 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /Examples/UIWebView+WBWebViewConsole/UIWebView+WBWebViewConsole.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Examples/UIWebView+WBWebViewConsole/UIWebView+WBWebViewConsole/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // UIWebView+WBWebViewConsole 4 | // 5 | // Created by 吴天 on 2/13/15. 6 | // 7 | // Copyright (c) 2014-present, Weibo, Corp. 8 | // All rights reserved. 9 | // 10 | // This source code is licensed under the BSD-style license found in the 11 | // LICENSE file in the root directory of this source tree. 12 | // 13 | 14 | #import 15 | 16 | @interface AppDelegate : UIResponder 17 | 18 | @property (strong, nonatomic) UIWindow *window; 19 | 20 | 21 | @end 22 | 23 | -------------------------------------------------------------------------------- /Examples/UIWebView+WBWebViewConsole/UIWebView+WBWebViewConsole/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // UIWebView+WBWebViewConsole 4 | // 5 | // Created by 吴天 on 2/13/15. 6 | // 7 | // Copyright (c) 2014-present, Weibo, Corp. 8 | // All rights reserved. 9 | // 10 | // This source code is licensed under the BSD-style license found in the 11 | // LICENSE file in the root directory of this source tree. 12 | // 13 | 14 | #import "AppDelegate.h" 15 | #import "WBWebViewController.h" 16 | 17 | @interface AppDelegate () 18 | 19 | @end 20 | 21 | @implementation AppDelegate 22 | 23 | 24 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 25 | self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; 26 | 27 | UINavigationController * navigationController = [[UINavigationController alloc] initWithRootViewController:[[WBWebViewController alloc] init]]; 28 | [self.window setRootViewController:navigationController]; 29 | 30 | [self.window makeKeyAndVisible]; 31 | return YES; 32 | } 33 | 34 | - (void)applicationWillResignActive:(UIApplication *)application { 35 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 36 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 37 | } 38 | 39 | - (void)applicationDidEnterBackground:(UIApplication *)application { 40 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 41 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 42 | } 43 | 44 | - (void)applicationWillEnterForeground:(UIApplication *)application { 45 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 46 | } 47 | 48 | - (void)applicationDidBecomeActive:(UIApplication *)application { 49 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 50 | } 51 | 52 | - (void)applicationWillTerminate:(UIApplication *)application { 53 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 54 | } 55 | 56 | @end 57 | -------------------------------------------------------------------------------- /Examples/UIWebView+WBWebViewConsole/UIWebView+WBWebViewConsole/Base.lproj/LaunchScreen.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 20 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /Examples/UIWebView+WBWebViewConsole/UIWebView+WBWebViewConsole/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "ipad", 35 | "size" : "29x29", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "ipad", 40 | "size" : "29x29", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "40x40", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "40x40", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "76x76", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "76x76", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } -------------------------------------------------------------------------------- /Examples/UIWebView+WBWebViewConsole/UIWebView+WBWebViewConsole/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | com.sina.$(PRODUCT_NAME:rfc1034identifier) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /Examples/UIWebView+WBWebViewConsole/UIWebView+WBWebViewConsole/WBUIWebView.h: -------------------------------------------------------------------------------- 1 | // 2 | // WBUIWebView.h 3 | // UIWebView+WBWebViewConsole 4 | // 5 | // Created by 吴天 on 2/13/15. 6 | // 7 | // Copyright (c) 2014-present, Weibo, Corp. 8 | // All rights reserved. 9 | // 10 | // This source code is licensed under the BSD-style license found in the 11 | // LICENSE file in the root directory of this source tree. 12 | // 13 | 14 | #import 15 | #import 16 | 17 | #define WBUIWebViewUsesPrivateAPI 1 18 | 19 | @interface WBUIWebView : UIWebView 20 | 21 | @property (nonatomic, weak) id wb_delegate; 22 | 23 | @end 24 | -------------------------------------------------------------------------------- /Examples/UIWebView+WBWebViewConsole/UIWebView+WBWebViewConsole/WBUIWebView.m: -------------------------------------------------------------------------------- 1 | // 2 | // WBUIWebView.m 3 | // UIWebView+WBWebViewConsole 4 | // 5 | // Created by 吴天 on 2/13/15. 6 | // 7 | // Copyright (c) 2014-present, Weibo, Corp. 8 | // All rights reserved. 9 | // 10 | // This source code is licensed under the BSD-style license found in the 11 | // LICENSE file in the root directory of this source tree. 12 | // 13 | 14 | #import "WBUIWebView.h" 15 | #import 16 | 17 | @interface WBUIWebView () 18 | { 19 | struct { 20 | unsigned int shouldStartLoad:1; 21 | unsigned int didStartLoad:1; 22 | unsigned int didFinishLoad:1; 23 | unsigned int didFailLoad:1; 24 | } _delegateHas; 25 | } 26 | 27 | @property (nonatomic, strong) NSMutableArray * userScripts; 28 | @property (nonatomic, strong) WBWebViewJSBridge * JSBridge; 29 | @property (nonatomic, strong) WBWebViewConsole * console; 30 | 31 | @end 32 | 33 | @implementation WBUIWebView 34 | 35 | - (instancetype)initWithFrame:(CGRect)frame 36 | { 37 | if (self = [super initWithFrame:frame]) { 38 | self.delegate = self; 39 | self.JSBridge = [[WBWebViewJSBridge alloc] initWithWebView:self]; 40 | self.console = [[WBWebViewConsole alloc] initWithWebView:self]; 41 | } 42 | return self; 43 | } 44 | 45 | - (NSMutableArray *)userScripts 46 | { 47 | if (!_userScripts) { 48 | _userScripts = [NSMutableArray array]; 49 | } 50 | return _userScripts; 51 | } 52 | 53 | - (void)wb_addUserScript:(WBWebViewUserScript *)userScript 54 | { 55 | if (!userScript) { 56 | return; 57 | } 58 | [self.userScripts addObject:userScript]; 59 | } 60 | 61 | - (void)wb_removeAllUserScripts 62 | { 63 | [_userScripts removeAllObjects]; 64 | } 65 | 66 | - (NSArray *)wb_userScripts 67 | { 68 | return [_userScripts copy]; 69 | } 70 | 71 | - (void)injectUserScripts 72 | { 73 | for (WBWebViewUserScript * script in _userScripts) { 74 | [self stringByEvaluatingJavaScriptFromString:script.source]; 75 | } 76 | } 77 | 78 | - (void)wb_evaluateJavaScript:(NSString *)javaScriptString completionHandler:(void (^)(NSString *, NSError *))completionHandler 79 | { 80 | NSString * result = [self stringByEvaluatingJavaScriptFromString:javaScriptString]; 81 | 82 | if (completionHandler) { 83 | completionHandler(result, nil); 84 | } 85 | } 86 | 87 | - (void)setWb_delegate:(id)wb_delegate 88 | { 89 | if (_wb_delegate != wb_delegate) { 90 | _wb_delegate = wb_delegate; 91 | 92 | _delegateHas.shouldStartLoad = [wb_delegate respondsToSelector:@selector(webView:shouldStartLoadWithRequest:navigationType:)]; 93 | _delegateHas.didStartLoad = [wb_delegate respondsToSelector:@selector(webViewDidStartLoad:)]; 94 | _delegateHas.didFinishLoad = [wb_delegate respondsToSelector:@selector(webViewDidFinishLoad:)]; 95 | _delegateHas.didFailLoad = [wb_delegate respondsToSelector:@selector(webView:didFailLoadWithError:)]; 96 | } 97 | } 98 | 99 | - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType 100 | { 101 | BOOL result = YES; 102 | 103 | if ([self.JSBridge handleWebViewRequest:request]) { 104 | result = NO; 105 | } else if (_delegateHas.shouldStartLoad) { 106 | result = [_wb_delegate webView:self shouldStartLoadWithRequest:request navigationType:navigationType]; 107 | } 108 | 109 | if (result) { 110 | [self webDebugLogProvisionalNavigation:request navigationType:navigationType result:result]; 111 | } 112 | 113 | return result; 114 | } 115 | 116 | - (void)webViewDidStartLoad:(UIWebView *)webView 117 | { 118 | if (_delegateHas.didStartLoad) { 119 | [_wb_delegate webViewDidStartLoad:webView]; 120 | } 121 | } 122 | 123 | - (void)webViewDidFinishLoad:(UIWebView *)webView 124 | { 125 | #if !WBUIWebViewUsesPrivateAPI 126 | [self injectUserScripts]; 127 | #endif 128 | 129 | if (_delegateHas.didFinishLoad) { 130 | [_wb_delegate webViewDidFinishLoad:webView]; 131 | } 132 | } 133 | 134 | - (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error 135 | { 136 | if (_delegateHas.didFailLoad) { 137 | [_wb_delegate webView:webView didFailLoadWithError:error]; 138 | } 139 | 140 | [self webDebugLogLoadFailedWithError:error]; 141 | } 142 | 143 | #if WBUIWebViewUsesPrivateAPI 144 | 145 | #warning Private API here, DO NOT use in production code 146 | 147 | - (void)webView:(id)webView didClearWindowObject:(id)window forFrame:(id)frame 148 | { 149 | [self injectUserScripts]; 150 | } 151 | 152 | #endif 153 | 154 | - (NSString *)callerWithNavigationType:(UIWebViewNavigationType)type 155 | { 156 | switch (type) 157 | { 158 | case UIWebViewNavigationTypeBackForward: return @"back/forward"; 159 | case UIWebViewNavigationTypeFormResubmitted: return @"form resubmit"; 160 | case UIWebViewNavigationTypeFormSubmitted: return @"form submit"; 161 | case UIWebViewNavigationTypeLinkClicked: return @"link click"; 162 | case UIWebViewNavigationTypeReload: return @"reload"; 163 | default: return nil; 164 | } 165 | } 166 | 167 | - (void)webDebugLogInfo:(NSString *)info 168 | { 169 | [self.console addMessage:info type:WBWebViewConsoleMessageTypeLog level:WBWebViewConsoleMessageLevelInfo source:WBWebViewConsoleMessageSourceNative]; 170 | } 171 | 172 | - (void)webDebugLogProvisionalNavigation:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType result:(BOOL)result 173 | { 174 | if (![[[request URL] absoluteString] isEqualToString:[[request mainDocumentURL] absoluteString]]) return; 175 | 176 | NSString * message = request.URL.absoluteString; 177 | WBWebViewConsoleMessageLevel level = result ? WBWebViewConsoleMessageLevelSuccess : WBWebViewConsoleMessageLevelWarning; 178 | NSString * caller = [self callerWithNavigationType:navigationType]; 179 | 180 | if (caller) 181 | { 182 | caller = [NSString stringWithFormat:@"triggered by %@", caller]; 183 | } 184 | 185 | [self.console addMessage:message level:level source:WBWebViewConsoleMessageSourceNavigation caller:caller]; 186 | } 187 | 188 | - (void)webDebugLogLoadFailedWithError:(NSError *)error 189 | { 190 | if ([error.domain isEqual:NSURLErrorDomain] && error.code == NSURLErrorCancelled) 191 | { 192 | return; 193 | } 194 | 195 | NSString * url = error.userInfo[NSURLErrorFailingURLStringErrorKey]; 196 | NSString * message = nil, * caller = nil; 197 | 198 | if (url) 199 | { 200 | NSMutableString * m = [NSMutableString string]; 201 | 202 | [m appendFormat:@"domain: %@, ", error.domain]; 203 | [m appendFormat:@"code: %ld, ", (long)error.code]; 204 | [m appendFormat:@"reason: %@", error.localizedDescription]; 205 | 206 | message = m; 207 | caller = url; 208 | } 209 | else 210 | { 211 | message = error.description; 212 | } 213 | 214 | [self.console addMessage:message level:WBWebViewConsoleMessageLevelError source:WBWebViewConsoleMessageSourceNative caller:caller]; 215 | } 216 | 217 | @end 218 | -------------------------------------------------------------------------------- /Examples/UIWebView+WBWebViewConsole/UIWebView+WBWebViewConsole/WBWebViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // WBWebViewController.h 3 | // UIWebView+WBWebViewConsole 4 | // 5 | // Created by 吴天 on 2/13/15. 6 | // 7 | // Copyright (c) 2014-present, Weibo, Corp. 8 | // All rights reserved. 9 | // 10 | // This source code is licensed under the BSD-style license found in the 11 | // LICENSE file in the root directory of this source tree. 12 | // 13 | 14 | #import 15 | 16 | @interface WBWebViewController : UIViewController 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /Examples/UIWebView+WBWebViewConsole/UIWebView+WBWebViewConsole/WBWebViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // WBWebViewController.m 3 | // UIWebView+WBWebViewConsole 4 | // 5 | // Created by 吴天 on 2/13/15. 6 | // 7 | // Copyright (c) 2014-present, Weibo, Corp. 8 | // All rights reserved. 9 | // 10 | // This source code is licensed under the BSD-style license found in the 11 | // LICENSE file in the root directory of this source tree. 12 | // 13 | 14 | #import "WBWebViewController.h" 15 | #import "WBUIWebView.h" 16 | #import 17 | #import 18 | 19 | @interface WBWebViewController () 20 | 21 | @property (nonatomic, strong) WBUIWebView * webView; 22 | 23 | @end 24 | 25 | @implementation WBWebViewController 26 | 27 | - (void)viewDidLoad 28 | { 29 | [super viewDidLoad]; 30 | 31 | self.title = @"Browser"; 32 | 33 | self.webView = [[WBUIWebView alloc] initWithFrame:self.view.bounds]; 34 | self.webView.JSBridge.interfaceName = @"UIWebViewBridge"; 35 | self.webView.JSBridge.readyEventName = @"UIWebViewBridgeReady"; 36 | self.webView.JSBridge.invokeScheme = @"uiwebview-bridge://invoke"; 37 | self.webView.wb_delegate = self; 38 | 39 | [self.view addSubview:self.webView]; 40 | 41 | [self.webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://apple.com"]]]; 42 | 43 | self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"Console" style:UIBarButtonItemStylePlain target:self action:@selector(showConsole:)]; 44 | } 45 | 46 | - (void)viewDidAppear:(BOOL)animated 47 | { 48 | [super viewDidAppear:animated]; 49 | 50 | [self webDebugAddContextMenuItems]; 51 | } 52 | 53 | - (void)viewDidDisappear:(BOOL)animated 54 | { 55 | [super viewDidDisappear:animated]; 56 | 57 | [self webDebugRemoveContextMenuItems]; 58 | } 59 | 60 | - (void)showConsole:(id)sender 61 | { 62 | WBWebDebugConsoleViewController * controller = [[WBWebDebugConsoleViewController alloc] initWithConsole:_webView.console]; 63 | 64 | [self.navigationController pushViewController:controller animated:YES]; 65 | } 66 | 67 | - (void)webDebugAddContextMenuItems 68 | { 69 | UIMenuItem * item = [[UIMenuItem alloc] initWithTitle:@"Inspect Element" action:@selector(webDebugInspectCurrentSelectedElement:)]; 70 | [[UIMenuController sharedMenuController] setMenuItems:@[item]]; 71 | } 72 | 73 | - (void)webDebugRemoveContextMenuItems 74 | { 75 | [[UIMenuController sharedMenuController] setMenuItems:nil]; 76 | } 77 | 78 | - (void)webDebugInspectCurrentSelectedElement:(id)sender 79 | { 80 | NSString * variable = @"WeiboConsoleLastSelection"; 81 | 82 | [self.webView.console storeCurrentSelectedElementToJavaScriptVariable:variable completion:^(BOOL success) { 83 | if (success) 84 | { 85 | WBWebDebugConsoleViewController * consoleViewController = [[WBWebDebugConsoleViewController alloc] initWithConsole:self.webView.console]; 86 | consoleViewController.initialCommand = variable; 87 | 88 | [self.navigationController pushViewController:consoleViewController animated:YES]; 89 | } 90 | else 91 | { 92 | UIAlertView * alertView = [[UIAlertView alloc] initWithTitle:@"Can not get current selected element" message:nil delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil]; 93 | [alertView show]; 94 | } 95 | }]; 96 | } 97 | 98 | @end 99 | -------------------------------------------------------------------------------- /Examples/UIWebView+WBWebViewConsole/UIWebView+WBWebViewConsole/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // UIWebView+WBWebViewConsole 4 | // 5 | // Created by 吴天 on 2/13/15. 6 | // 7 | // Copyright (c) 2014-present, Weibo, Corp. 8 | // All rights reserved. 9 | // 10 | // This source code is licensed under the BSD-style license found in the 11 | // LICENSE file in the root directory of this source tree. 12 | // 13 | 14 | #import 15 | #import "AppDelegate.h" 16 | 17 | int main(int argc, char * argv[]) { 18 | @autoreleasepool { 19 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Examples/UIWebView+WBWebViewConsole/UIWebView+WBWebViewConsoleTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | com.sina.$(PRODUCT_NAME:rfc1034identifier) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /Examples/UIWebView+WBWebViewConsole/UIWebView+WBWebViewConsoleTests/UIWebView_WBWebViewConsoleTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIWebView_WBWebViewConsoleTests.m 3 | // UIWebView+WBWebViewConsoleTests 4 | // 5 | // Created by 吴天 on 2/13/15. 6 | // 7 | // Copyright (c) 2014-present, Weibo, Corp. 8 | // All rights reserved. 9 | // 10 | // This source code is licensed under the BSD-style license found in the 11 | // LICENSE file in the root directory of this source tree. 12 | // 13 | 14 | #import 15 | #import 16 | 17 | @interface UIWebView_WBWebViewConsoleTests : XCTestCase 18 | 19 | @end 20 | 21 | @implementation UIWebView_WBWebViewConsoleTests 22 | 23 | - (void)setUp { 24 | [super setUp]; 25 | // Put setup code here. This method is called before the invocation of each test method in the class. 26 | } 27 | 28 | - (void)tearDown { 29 | // Put teardown code here. This method is called after the invocation of each test method in the class. 30 | [super tearDown]; 31 | } 32 | 33 | - (void)testExample { 34 | // This is an example of a functional test case. 35 | XCTAssert(YES, @"Pass"); 36 | } 37 | 38 | - (void)testPerformanceExample { 39 | // This is an example of a performance test case. 40 | [self measureBlock:^{ 41 | // Put the code you want to measure the time of here. 42 | }]; 43 | } 44 | 45 | @end 46 | -------------------------------------------------------------------------------- /Examples/WKWebView+WBWebViewConsole/Podfile: -------------------------------------------------------------------------------- 1 | source 'https://github.com/CocoaPods/Specs.git' 2 | platform :ios, '8.0' 3 | 4 | # also demostrates when use as a dynamic framework 5 | use_frameworks! 6 | 7 | target 'WKWebView+WBWebViewConsole' do 8 | pod 'WBWebViewConsole', :path => '../..' 9 | end -------------------------------------------------------------------------------- /Examples/WKWebView+WBWebViewConsole/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - WBWebViewConsole (1.0.2) 3 | 4 | DEPENDENCIES: 5 | - WBWebViewConsole (from `../..`) 6 | 7 | EXTERNAL SOURCES: 8 | WBWebViewConsole: 9 | :path: "../.." 10 | 11 | SPEC CHECKSUMS: 12 | WBWebViewConsole: 1288737d136b44a281cc654828339127f959e21c 13 | 14 | PODFILE CHECKSUM: 664a31577dd723d8b22983608cc4c78e6c668e8d 15 | 16 | COCOAPODS: 1.1.1 17 | -------------------------------------------------------------------------------- /Examples/WKWebView+WBWebViewConsole/WKWebView+WBWebViewConsole.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Examples/WKWebView+WBWebViewConsole/WKWebView+WBWebViewConsole.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Examples/WKWebView+WBWebViewConsole/WKWebView+WBWebViewConsole/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // WKWebView+WBWebViewConsole 4 | // 5 | // Created by 吴天 on 15/2/25. 6 | // 7 | // Copyright (c) 2014-present, Weibo, Corp. 8 | // All rights reserved. 9 | // 10 | // This source code is licensed under the BSD-style license found in the 11 | // LICENSE file in the root directory of this source tree. 12 | // 13 | 14 | #import 15 | 16 | @interface AppDelegate : UIResponder 17 | 18 | @property (strong, nonatomic) UIWindow *window; 19 | 20 | 21 | @end 22 | 23 | -------------------------------------------------------------------------------- /Examples/WKWebView+WBWebViewConsole/WKWebView+WBWebViewConsole/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // WKWebView+WBWebViewConsole 4 | // 5 | // Created by 吴天 on 15/2/25. 6 | // 7 | // Copyright (c) 2014-present, Weibo, Corp. 8 | // All rights reserved. 9 | // 10 | // This source code is licensed under the BSD-style license found in the 11 | // LICENSE file in the root directory of this source tree. 12 | // 13 | 14 | #import "AppDelegate.h" 15 | #import "WBWebViewController.h" 16 | 17 | @interface AppDelegate () 18 | 19 | @end 20 | 21 | @implementation AppDelegate 22 | 23 | 24 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 25 | self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; 26 | 27 | UINavigationController * navigationController = [[UINavigationController alloc] initWithRootViewController:[[WBWebViewController alloc] init]]; 28 | [self.window setRootViewController:navigationController]; 29 | 30 | [self.window makeKeyAndVisible]; 31 | return YES; 32 | } 33 | 34 | - (void)applicationWillResignActive:(UIApplication *)application { 35 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 36 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 37 | } 38 | 39 | - (void)applicationDidEnterBackground:(UIApplication *)application { 40 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 41 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 42 | } 43 | 44 | - (void)applicationWillEnterForeground:(UIApplication *)application { 45 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 46 | } 47 | 48 | - (void)applicationDidBecomeActive:(UIApplication *)application { 49 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 50 | } 51 | 52 | - (void)applicationWillTerminate:(UIApplication *)application { 53 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 54 | } 55 | 56 | @end 57 | -------------------------------------------------------------------------------- /Examples/WKWebView+WBWebViewConsole/WKWebView+WBWebViewConsole/Base.lproj/LaunchScreen.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 20 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /Examples/WKWebView+WBWebViewConsole/WKWebView+WBWebViewConsole/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "ipad", 35 | "size" : "29x29", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "ipad", 40 | "size" : "29x29", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "40x40", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "40x40", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "76x76", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "76x76", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } -------------------------------------------------------------------------------- /Examples/WKWebView+WBWebViewConsole/WKWebView+WBWebViewConsole/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | com.sina.$(PRODUCT_NAME:rfc1034identifier) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /Examples/WKWebView+WBWebViewConsole/WKWebView+WBWebViewConsole/WBWKWebView.h: -------------------------------------------------------------------------------- 1 | // 2 | // WBWKWebView.h 3 | // WKWebView+WBWebViewConsole 4 | // 5 | // Created by 吴天 on 15/2/25. 6 | // 7 | // Copyright (c) 2014-present, Weibo, Corp. 8 | // All rights reserved. 9 | // 10 | // This source code is licensed under the BSD-style license found in the 11 | // LICENSE file in the root directory of this source tree. 12 | // 13 | 14 | #import 15 | #import 16 | 17 | @interface WBWKWebView : WKWebView 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /Examples/WKWebView+WBWebViewConsole/WKWebView+WBWebViewConsole/WBWKWebView.m: -------------------------------------------------------------------------------- 1 | // 2 | // WBWKWebView.m 3 | // WKWebView+WBWebViewConsole 4 | // 5 | // Created by 吴天 on 15/2/25. 6 | // 7 | // Copyright (c) 2014-present, Weibo, Corp. 8 | // All rights reserved. 9 | // 10 | // This source code is licensed under the BSD-style license found in the 11 | // LICENSE file in the root directory of this source tree. 12 | // 13 | 14 | #import "WBWKWebView.h" 15 | #import 16 | #import 17 | #import 18 | #import 19 | 20 | @interface WBWKWebView () 21 | 22 | @property (nonatomic, strong) WBWebViewJSBridge * JSBridge; 23 | @property (nonatomic, strong) WBWebViewConsole * console; 24 | 25 | @end 26 | 27 | @implementation WBWKWebView 28 | 29 | - (instancetype)initWithFrame:(CGRect)frame 30 | { 31 | if (self = [super initWithFrame:frame]) { 32 | self.navigationDelegate = self; 33 | self.JSBridge = [[WBWebViewJSBridge alloc] initWithWebView:self]; 34 | 35 | self.console = [[WBWebViewConsole alloc] initWithWebView:self]; 36 | } 37 | return self; 38 | } 39 | 40 | - (void)wb_addUserScript:(WBWebViewUserScript *)userScript 41 | { 42 | if (!userScript) { 43 | return; 44 | } 45 | [self.configuration.userContentController addUserScript:[[WKUserScript alloc] initWithSource:userScript.source injectionTime:(WKUserScriptInjectionTime)userScript.scriptInjectionTime forMainFrameOnly:userScript.forMainFrameOnly]]; 46 | } 47 | 48 | - (void)wb_removeAllUserScripts 49 | { 50 | [self.configuration.userContentController removeAllUserScripts]; 51 | } 52 | 53 | - (NSArray *)wb_userScripts 54 | { 55 | return self.configuration.userContentController.userScripts; 56 | } 57 | 58 | - (void)wb_evaluateJavaScript:(NSString *)javaScriptString completionHandler:(void (^)(NSString *, NSError *))completionHandler 59 | { 60 | [self evaluateJavaScript:javaScriptString completionHandler:^(id result, NSError * error) { 61 | if (completionHandler) { 62 | NSString * resultString = nil; 63 | 64 | if ([result isKindOfClass:[NSString class]]) { 65 | resultString = result; 66 | } else if ([result respondsToSelector:@selector(stringValue)]) { 67 | resultString = [result stringValue]; 68 | } else if ([result respondsToSelector:@selector(wb_JSONString)]) { 69 | resultString = [result wb_JSONString]; 70 | } 71 | 72 | completionHandler(resultString, error); 73 | } 74 | }]; 75 | } 76 | 77 | - (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler 78 | { 79 | BOOL result = YES; 80 | 81 | if ([self.JSBridge handleWebViewRequest:navigationAction.request]) { 82 | result = NO; 83 | } 84 | 85 | if (result) { 86 | [self webDebugLogProvisionalNavigation:navigationAction.request navigationType:navigationAction.navigationType result:result]; 87 | decisionHandler(WKNavigationActionPolicyAllow); 88 | } else { 89 | decisionHandler(WKNavigationActionPolicyCancel); 90 | } 91 | } 92 | 93 | - (void)webView:(WKWebView *)webView didFailNavigation:(WKNavigation *)navigation withError:(NSError *)error 94 | { 95 | [self webDebugLogLoadFailedWithError:error]; 96 | } 97 | 98 | - (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(WKNavigation *)navigation withError:(NSError *)error 99 | { 100 | [self webDebugLogLoadFailedWithError:error]; 101 | } 102 | 103 | - (NSString *)callerWithNavigationType:(WKNavigationType)type 104 | { 105 | switch (type) 106 | { 107 | case WKNavigationTypeBackForward: return @"back/forward"; 108 | case WKNavigationTypeFormResubmitted: return @"form resubmit"; 109 | case WKNavigationTypeFormSubmitted: return @"form submit"; 110 | case WKNavigationTypeLinkActivated: return @"link click"; 111 | case WKNavigationTypeReload: return @"reload"; 112 | default: return nil; 113 | } 114 | } 115 | 116 | - (void)webDebugLogInfo:(NSString *)info 117 | { 118 | [self.console addMessage:info type:WBWebViewConsoleMessageTypeLog level:WBWebViewConsoleMessageLevelInfo source:WBWebViewConsoleMessageSourceNative]; 119 | } 120 | 121 | - (void)webDebugLogProvisionalNavigation:(NSURLRequest *)request navigationType:(WKNavigationType)navigationType result:(BOOL)result 122 | { 123 | if (![[[request URL] absoluteString] isEqualToString:[[request mainDocumentURL] absoluteString]]) return; 124 | 125 | NSString * message = request.URL.absoluteString; 126 | WBWebViewConsoleMessageLevel level = result ? WBWebViewConsoleMessageLevelSuccess : WBWebViewConsoleMessageLevelWarning; 127 | NSString * caller = [self callerWithNavigationType:navigationType]; 128 | 129 | if (caller) 130 | { 131 | caller = [NSString stringWithFormat:@"triggered by %@", caller]; 132 | } 133 | 134 | [self.console addMessage:message level:level source:WBWebViewConsoleMessageSourceNavigation caller:caller]; 135 | } 136 | 137 | - (void)webDebugLogLoadFailedWithError:(NSError *)error 138 | { 139 | if ([error.domain isEqual:NSURLErrorDomain] && error.code == NSURLErrorCancelled) 140 | { 141 | return; 142 | } 143 | 144 | NSString * url = error.userInfo[NSURLErrorFailingURLStringErrorKey]; 145 | NSString * message = nil, * caller = nil; 146 | 147 | if (url) 148 | { 149 | NSMutableString * m = [NSMutableString string]; 150 | 151 | [m appendFormat:@"domain: %@, ", error.domain]; 152 | [m appendFormat:@"code: %ld, ", (long)error.code]; 153 | [m appendFormat:@"reason: %@", error.localizedDescription]; 154 | 155 | message = m; 156 | caller = url; 157 | } 158 | else 159 | { 160 | message = error.description; 161 | } 162 | 163 | [self.console addMessage:message level:WBWebViewConsoleMessageLevelError source:WBWebViewConsoleMessageSourceNative caller:caller]; 164 | } 165 | 166 | @end 167 | -------------------------------------------------------------------------------- /Examples/WKWebView+WBWebViewConsole/WKWebView+WBWebViewConsole/WBWebViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // WBWebViewController.h 3 | // WKWebView+WBWebViewConsole 4 | // 5 | // Created by 吴天 on 2/13/15. 6 | // 7 | // Copyright (c) 2014-present, Weibo, Corp. 8 | // All rights reserved. 9 | // 10 | // This source code is licensed under the BSD-style license found in the 11 | // LICENSE file in the root directory of this source tree. 12 | // 13 | 14 | #import 15 | 16 | @interface WBWebViewController : UIViewController 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /Examples/WKWebView+WBWebViewConsole/WKWebView+WBWebViewConsole/WBWebViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // WBWebViewController.m 3 | // WKWebView+WBWebViewConsole 4 | // 5 | // Created by 吴天 on 2/13/15. 6 | // 7 | // Copyright (c) 2014-present, Weibo, Corp. 8 | // All rights reserved. 9 | // 10 | // This source code is licensed under the BSD-style license found in the 11 | // LICENSE file in the root directory of this source tree. 12 | // 13 | 14 | #import "WBWebViewController.h" 15 | #import "WBWKWebView.h" 16 | #import 17 | #import 18 | 19 | @interface WBWebViewController () 20 | 21 | @property (nonatomic, strong) WBWKWebView * webView; 22 | 23 | @end 24 | 25 | @implementation WBWebViewController 26 | 27 | - (void)viewDidLoad 28 | { 29 | [super viewDidLoad]; 30 | 31 | self.title = @"Browser"; 32 | 33 | self.webView = [[WBWKWebView alloc] initWithFrame:self.view.bounds]; 34 | self.webView.JSBridge.interfaceName = @"WKWebViewBridge"; 35 | self.webView.JSBridge.readyEventName = @"WKWebViewBridgeReady"; 36 | self.webView.JSBridge.invokeScheme = @"wkwebview-bridge://invoke"; 37 | 38 | [self.view addSubview:self.webView]; 39 | 40 | [self.webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://apple.com"]]]; 41 | 42 | self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"Console" style:UIBarButtonItemStylePlain target:self action:@selector(showConsole:)]; 43 | } 44 | 45 | - (void)viewDidAppear:(BOOL)animated 46 | { 47 | [super viewDidAppear:animated]; 48 | 49 | [self webDebugAddContextMenuItems]; 50 | } 51 | 52 | - (void)viewDidDisappear:(BOOL)animated 53 | { 54 | [super viewDidDisappear:animated]; 55 | 56 | [self webDebugRemoveContextMenuItems]; 57 | } 58 | 59 | - (void)showConsole:(id)sender 60 | { 61 | WBWebDebugConsoleViewController * controller = [[WBWebDebugConsoleViewController alloc] initWithConsole:_webView.console]; 62 | 63 | [self.navigationController pushViewController:controller animated:YES]; 64 | } 65 | 66 | - (void)webDebugAddContextMenuItems 67 | { 68 | UIMenuItem * item = [[UIMenuItem alloc] initWithTitle:@"Inspect Element" action:@selector(webDebugInspectCurrentSelectedElement:)]; 69 | [[UIMenuController sharedMenuController] setMenuItems:@[item]]; 70 | } 71 | 72 | - (void)webDebugRemoveContextMenuItems 73 | { 74 | [[UIMenuController sharedMenuController] setMenuItems:nil]; 75 | } 76 | 77 | - (void)webDebugInspectCurrentSelectedElement:(id)sender 78 | { 79 | NSString * variable = @"WeiboConsoleLastSelection"; 80 | 81 | [self.webView.console storeCurrentSelectedElementToJavaScriptVariable:variable completion:^(BOOL success) { 82 | if (success) 83 | { 84 | WBWebDebugConsoleViewController * consoleViewController = [[WBWebDebugConsoleViewController alloc] initWithConsole:self.webView.console]; 85 | consoleViewController.initialCommand = variable; 86 | 87 | [self.navigationController pushViewController:consoleViewController animated:YES]; 88 | } 89 | else 90 | { 91 | UIAlertView * alertView = [[UIAlertView alloc] initWithTitle:@"Can not get current selected element" message:nil delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil]; 92 | [alertView show]; 93 | } 94 | }]; 95 | } 96 | 97 | @end 98 | -------------------------------------------------------------------------------- /Examples/WKWebView+WBWebViewConsole/WKWebView+WBWebViewConsole/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // WKWebView+WBWebViewConsole 4 | // 5 | // Created by 吴天 on 15/2/25. 6 | // 7 | // Copyright (c) 2014-present, Weibo, Corp. 8 | // All rights reserved. 9 | // 10 | // This source code is licensed under the BSD-style license found in the 11 | // LICENSE file in the root directory of this source tree. 12 | // 13 | 14 | #import 15 | #import "AppDelegate.h" 16 | 17 | int main(int argc, char * argv[]) { 18 | @autoreleasepool { 19 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Examples/WKWebView+WBWebViewConsole/WKWebView+WBWebViewConsoleTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | com.sina.$(PRODUCT_NAME:rfc1034identifier) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /Examples/WKWebView+WBWebViewConsole/WKWebView+WBWebViewConsoleTests/WKWebView_WBWebViewConsoleTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // WKWebView_WBWebViewConsoleTests.m 3 | // WKWebView+WBWebViewConsoleTests 4 | // 5 | // Created by 吴天 on 15/2/25. 6 | // 7 | // Copyright (c) 2014-present, Weibo, Corp. 8 | // All rights reserved. 9 | // 10 | // This source code is licensed under the BSD-style license found in the 11 | // LICENSE file in the root directory of this source tree. 12 | // 13 | 14 | #import 15 | #import 16 | 17 | @interface WKWebView_WBWebViewConsoleTests : XCTestCase 18 | 19 | @end 20 | 21 | @implementation WKWebView_WBWebViewConsoleTests 22 | 23 | - (void)setUp { 24 | [super setUp]; 25 | // Put setup code here. This method is called before the invocation of each test method in the class. 26 | } 27 | 28 | - (void)tearDown { 29 | // Put teardown code here. This method is called after the invocation of each test method in the class. 30 | [super tearDown]; 31 | } 32 | 33 | - (void)testExample { 34 | // This is an example of a functional test case. 35 | XCTAssert(YES, @"Pass"); 36 | } 37 | 38 | - (void)testPerformanceExample { 39 | // This is an example of a performance test case. 40 | [self measureBlock:^{ 41 | // Put the code you want to measure the time of here. 42 | }]; 43 | } 44 | 45 | @end 46 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD License 2 | 3 | For WBWebViewConsole software 4 | 5 | Copyright (c) 2015-present, Weibo, Corp. All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without modification, 8 | are permitted provided that the following conditions are met: 9 | 10 | * Redistributions of source code must retain the above copyright notice, this 11 | list of conditions and the following disclaimer. 12 | 13 | * Redistributions in binary form must reproduce the above copyright notice, 14 | this list of conditions and the following disclaimer in the documentation 15 | and/or other materials provided with the distribution. 16 | 17 | * Neither the name Weibo nor the names of its contributors may be used to 18 | endorse or promote products derived from this software without specific 19 | prior written permission. 20 | 21 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 22 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 23 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 24 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 25 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 26 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 27 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 28 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 30 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WBWebViewConsole [![Build Status](https://travis-ci.org/Naituw/WBWebViewConsole.svg)](https://travis-ci.org/Naituw/WBWebViewConsole) 2 | 3 | WBWebViewConsole is an In-App debug console for your UIWebView && WKWebView 4 | 5 | WBWebViewConsole 6 | 7 | ## Installation 8 | 9 | WBWebViewConsole is available on [CocoaPods](http://cocoapods.org). Just add the following to your project Podfile: 10 | 11 | ``` 12 | pod 'WBWebViewConsole', '~> 1.0' 13 | ``` 14 | 15 | Bugs are first fixed in master and then made available via a designated release. If you tend to live on the bleeding edge, you can use WBWebViewConsole from master with the following Podfile entry: 16 | 17 | ``` 18 | pod 'WBWebViewConsole', :git => 'https://github.com/Naituw/WBWebViewConsole.git' 19 | ``` 20 | 21 | ## Setup 22 | 23 | - Make your own `UIWebView` or `WKWebView` subclass, and implement all methods in `WBWebView` protocol 24 | - Setup `JSBridge` and `console` when WebView inits 25 | - If you are using `UIWebView`, inject userScript as early as possible after page loading. Otherwise, just use `WKUserScript` to implement. 26 | - In `UIWebView`'s `webView:shouldStartLoadWithRequest:navigationType` or `WKWebView`'s `webView:decidePolicyForNavigationAction:decisionHandler` 27 | - Pass the request to `-[JSBridge handleWebViewRequest:]` and use the return value to decide whether the navigation should start 28 | 29 | ## Usage 30 | 31 | - Use `WBWebViewConsole` to manage all messages 32 | - `addMessage:type:level:source:` 33 | - add message for specific type, level and source 34 | - `clearMessage` 35 | - empty all messages 36 | - `sendMessage` 37 | - input (eval) script 38 | - `storeCurrentSelectedElementToJavaScriptVariable:completion:` 39 | - save current selected element to a js variable 40 | - Use `WBWebDebugConsoleViewController` to display a `WBWebViewConsole` 41 | - `initWithConsole:` 42 | - designated initializer for this class 43 | - `setInitialCommand:` 44 | - set the placeholder command 45 | 46 | ## License 47 | 48 | WBWebViewConsole is BSD-licensed. see the `LICENSE` file. 49 | 50 | The files in the `/Examples` directory are licensed under a separate license as specified in `Examples/README.md`. 51 | -------------------------------------------------------------------------------- /WBWebViewConsole.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "WBWebViewConsole" 3 | s.platform = :ios 4 | s.ios.deployment_target = "6.0" 5 | s.version = "1.0.2" 6 | s.summary = "In-App debug console for your UIWebView && WKWebView" 7 | s.homepage = "https://github.com/Naituw/WBWebViewConsole" 8 | s.license = "BSD" 9 | s.authors = {"Naituw" => "naituw@gmail.com"} 10 | s.source = {:git => "https://github.com/Naituw/WBWebViewConsole.git", :tag => '1.0.2'} 11 | s.source_files = 'WBWebViewConsole/**/*.{h,m}' 12 | s.requires_arc = true 13 | s.resources = ["WBWebViewConsole/Resources/*", "WBWebViewConsole/Supports/WBWebView/JSBridge/Resources/*"] 14 | end -------------------------------------------------------------------------------- /WBWebViewConsole.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /WBWebViewConsole.xcodeproj/project.xcworkspace/xcuserdata/wutian.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naituw/WBWebViewConsole/2c9f5b37a7a8b875f303396addfaebef6f5b2bdf/WBWebViewConsole.xcodeproj/project.xcworkspace/xcuserdata/wutian.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /WBWebViewConsole.xcodeproj/xcshareddata/xcschemes/WBWebViewConsole.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 44 | 45 | 47 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 65 | 66 | 75 | 76 | 82 | 83 | 84 | 85 | 86 | 87 | 93 | 94 | 100 | 101 | 102 | 103 | 105 | 106 | 109 | 110 | 111 | -------------------------------------------------------------------------------- /WBWebViewConsole.xcodeproj/xcuserdata/wutian.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | WBWebViewConsole.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | C6B12C471A8D9CAB004F2AD2 16 | 17 | primary 18 | 19 | 20 | C6B12C521A8D9CAB004F2AD2 21 | 22 | primary 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /WBWebViewConsole/Controllers/WBWebDebugConsoleViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // WBWebDebugConsoleViewController.h 3 | // Weibo 4 | // 5 | // Created by Wutian on 14/7/23. 6 | // 7 | // Copyright (c) 2014-present, Weibo, Corp. 8 | // All rights reserved. 9 | // 10 | // This source code is licensed under the BSD-style license found in the 11 | // LICENSE file in the root directory of this source tree. 12 | // 13 | 14 | #import 15 | 16 | @class WBWebViewConsole; 17 | 18 | @interface WBWebDebugConsoleViewController : UIViewController 19 | 20 | - (instancetype)initWithConsole:(WBWebViewConsole *)console; 21 | 22 | @property (nonatomic, strong, readonly) WBWebViewConsole * console; 23 | 24 | @property (nonatomic, strong) NSString * initialCommand; 25 | 26 | @end 27 | -------------------------------------------------------------------------------- /WBWebViewConsole/Controllers/WBWebDebugConsoleViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // WBWebDebugConsoleViewController.m 3 | // Weibo 4 | // 5 | // Created by Wutian on 14/7/23. 6 | // 7 | // Copyright (c) 2014-present, Weibo, Corp. 8 | // All rights reserved. 9 | // 10 | // This source code is licensed under the BSD-style license found in the 11 | // LICENSE file in the root directory of this source tree. 12 | // 13 | 14 | #import "WBWebDebugConsoleViewController.h" 15 | #import "WBWebViewConsole.h" 16 | #import "WBWebViewConsoleMessageCell.h" 17 | #import "WBWebViewConsoleInputView.h" 18 | #import "UIScrollView+WBTUtilities.h" 19 | #import "UIView+WBTSizes.h" 20 | #import "WBKeyboardObserver.h" 21 | 22 | @interface WBWebDebugConsoleViewController () 23 | { 24 | struct { 25 | unsigned int viewAppeared: 1; 26 | } _flags; 27 | } 28 | 29 | @property (nonatomic, strong) WBWebViewConsole * console; 30 | @property (nonatomic, strong) UITableView * tableView; 31 | @property (nonatomic, strong) WBWebViewConsoleInputView * inputView; 32 | 33 | @end 34 | 35 | @implementation WBWebDebugConsoleViewController 36 | 37 | - (void)dealloc 38 | { 39 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 40 | } 41 | 42 | - (id)initWithConsole:(WBWebViewConsole *)console 43 | { 44 | if (self = [self init]) { 45 | self.console = console; 46 | } 47 | return self; 48 | } 49 | 50 | - (void)viewDidLoad 51 | { 52 | [super viewDidLoad]; 53 | 54 | self.navigationItem.title = NSLocalizedString(@"Debug Console", nil); 55 | 56 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardFrameDidChangeNotification:) name:WBKeyboardObserverFrameDidUpdateNotification object:nil]; 57 | 58 | self.tableView = [[UITableView alloc] initWithFrame:self.view.bounds]; 59 | self.tableView.delegate = self; 60 | self.tableView.dataSource = self; 61 | self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone; 62 | 63 | [self.view addSubview:self.tableView]; 64 | 65 | self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:NSLocalizedString(@"Clear", nil) style:UIBarButtonItemStylePlain target:self action:@selector(clearMessages)]; 66 | 67 | self.inputView = [[WBWebViewConsoleInputView alloc] initWithFrame:CGRectZero]; 68 | [self.inputView setDelegate:self]; 69 | [self.inputView setFont:[WBWebViewConsoleMessageCell messageFont]]; 70 | [self.inputView setConsole:self.console]; 71 | [self.view addSubview:self.inputView]; 72 | 73 | if (self.initialCommand.length) 74 | { 75 | [self.inputView setText:self.initialCommand]; 76 | [self.inputView.textView becomeFirstResponder]; 77 | } 78 | } 79 | 80 | - (void)viewWillAppear:(BOOL)animated 81 | { 82 | [super viewWillAppear:animated]; 83 | 84 | dispatch_async(dispatch_get_main_queue(), ^{ 85 | [self relayoutViewsAnimated:NO]; 86 | 87 | _flags.viewAppeared = YES; 88 | }); 89 | } 90 | 91 | - (void)viewWillDisappear:(BOOL)animated 92 | { 93 | [super viewWillDisappear:animated]; 94 | 95 | dispatch_async(dispatch_get_main_queue(), ^{ 96 | _flags.viewAppeared = NO; 97 | }); 98 | } 99 | 100 | - (void)dismiss:(id)sender 101 | { 102 | [self dismissViewControllerAnimated:YES completion:NULL]; 103 | } 104 | 105 | - (void)setConsole:(WBWebViewConsole *)console 106 | { 107 | if (_console != console) 108 | { 109 | [self unregisterConsoleNotifications]; 110 | 111 | _console = console; 112 | 113 | [self registerNotificationsForCurrentConsole]; 114 | } 115 | } 116 | 117 | - (CGFloat)keyboardHeight 118 | { 119 | CGRect endFrame = [WBKeyboardObserver sharedObserver].frameEnd; 120 | 121 | if (CGRectIsNull(endFrame)) 122 | { 123 | return 0; 124 | } 125 | 126 | return MAX([UIScreen mainScreen].bounds.size.height - endFrame.origin.y, 0); 127 | } 128 | 129 | - (void)relayoutViewsAnimated:(BOOL)animated 130 | { 131 | CGFloat keyboardHeight = [self keyboardHeight]; 132 | CGFloat inputViewHeight = self.inputView.desiredHeight; 133 | 134 | if (animated) 135 | { 136 | WBKeyboardObserver * keyboard = [WBKeyboardObserver sharedObserver]; 137 | 138 | [UIView beginAnimations:nil context:NULL]; 139 | [UIView setAnimationCurve:keyboard.animationCurve]; 140 | [UIView setAnimationDuration:keyboard.animationDuration ? : 0.15]; 141 | } 142 | 143 | self.inputView.frame = CGRectMake(0, self.view.wbtHeight - inputViewHeight - keyboardHeight, self.view.wbtWidth, inputViewHeight); 144 | self.tableView.frame = CGRectMake(0, 0, self.view.wbtWidth, self.view.wbtHeight - inputViewHeight - keyboardHeight); 145 | 146 | if (animated) 147 | { 148 | [UIView commitAnimations]; 149 | } 150 | } 151 | 152 | #pragma mark - TableView Datasource 153 | 154 | - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView 155 | { 156 | return 2; // cleared messages + messages 157 | } 158 | 159 | - (NSArray *)messageDatasourceForSection:(NSInteger)section 160 | { 161 | if (section == 0) 162 | { 163 | return _console.clearedMessages; 164 | } 165 | else 166 | { 167 | return _console.messages; 168 | } 169 | } 170 | 171 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section 172 | { 173 | return [self messageDatasourceForSection:section].count; 174 | } 175 | 176 | - (WBWebViewConsoleMessage *)messageAtIndexPath:(NSIndexPath *)indexPath 177 | { 178 | NSArray * datasource = [self messageDatasourceForSection:indexPath.section]; 179 | 180 | if (indexPath.row >= datasource.count) 181 | { 182 | return nil; 183 | } 184 | 185 | return datasource[indexPath.row]; 186 | } 187 | 188 | - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath 189 | { 190 | WBWebViewConsoleMessage * message = [self messageAtIndexPath:indexPath]; 191 | 192 | return [WBWebViewConsoleMessageCell rowHeightOfDataObject:message tableView:tableView]; 193 | } 194 | 195 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 196 | { 197 | static NSString * const identifier = @"WBWebViewConsoleMessageCell"; 198 | WBWebViewConsoleMessageCell * cell = [tableView dequeueReusableCellWithIdentifier:identifier]; 199 | 200 | if (!cell) { 201 | cell = [[WBWebViewConsoleMessageCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier]; 202 | } 203 | 204 | cell.message = [self messageAtIndexPath:indexPath]; 205 | cell.cleared = indexPath.section == 0; 206 | cell.delegate = self; 207 | 208 | return cell; 209 | } 210 | 211 | - (BOOL)tableView:(UITableView *)tableView shouldShowMenuForRowAtIndexPath:(NSIndexPath *)indexPath 212 | { 213 | return YES; 214 | } 215 | 216 | -(BOOL)tableView:(UITableView *)tableView canPerformAction:(SEL)action forRowAtIndexPath:(NSIndexPath *)indexPath withSender:(id)sender 217 | { 218 | return (action == @selector(copy:)); 219 | } 220 | 221 | - (void)tableView:(UITableView *)tableView performAction:(SEL)action forRowAtIndexPath:(NSIndexPath *)indexPath withSender:(id)sender 222 | { 223 | if (action == @selector(copy:)) 224 | { 225 | WBWebViewConsoleMessage * message = [self messageAtIndexPath:indexPath]; 226 | 227 | if (message) 228 | { 229 | [UIPasteboard generalPasteboard].string = message.message; 230 | } 231 | } 232 | } 233 | 234 | - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath 235 | { 236 | [tableView deselectRowAtIndexPath:indexPath animated:YES]; 237 | } 238 | 239 | - (void)consoleMessageCell:(WBWebViewConsoleMessageCell *)cell copyMessageToInputView:(WBWebViewConsoleMessage *)message 240 | { 241 | self.inputView.text = message.message; 242 | } 243 | 244 | //- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView 245 | //{ 246 | // if (self.inputView.bottom < self.view.height) 247 | // { 248 | // [self.view endEditing:YES]; 249 | // } 250 | //} 251 | 252 | #pragma mark - Console Notifications 253 | 254 | - (void)registerNotificationsForCurrentConsole 255 | { 256 | if (!_console) return; 257 | 258 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(consoleDidAddMessage:) name:WBWebViewConsoleDidAddMessageNotification object:_console]; 259 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(consoleDidClearMessages:) name:WBWebViewConsoleDidClearMessagesNotification object:_console]; 260 | } 261 | - (void)unregisterConsoleNotifications 262 | { 263 | [[NSNotificationCenter defaultCenter] removeObserver:self name:WBWebViewConsoleDidAddMessageNotification object:nil]; 264 | [[NSNotificationCenter defaultCenter] removeObserver:self name:WBWebViewConsoleDidClearMessagesNotification object:nil]; 265 | } 266 | 267 | - (void)consoleDidAddMessage:(NSNotification *)notification 268 | { 269 | BOOL scrollToBottom = self.tableView.wbt_isScrolledToBottom; 270 | 271 | [self.tableView reloadData]; 272 | 273 | if (scrollToBottom) 274 | { 275 | [self.tableView wbt_scrollToBottomAnimated:NO]; 276 | } 277 | } 278 | - (void)consoleDidClearMessages:(NSNotification *)notification 279 | { 280 | [self.tableView reloadData]; 281 | } 282 | 283 | #pragma mark - Actions 284 | 285 | - (void)clearMessages 286 | { 287 | [self.console clearMessages]; 288 | } 289 | 290 | #pragma mark - InputView Delegate 291 | 292 | - (void)consoleInputViewHeightChanged:(WBWebViewConsoleInputView *)inputView 293 | { 294 | [self relayoutViewsAnimated:_flags.viewAppeared]; 295 | } 296 | 297 | - (void)consoleInputViewDidBeginEditing:(WBWebViewConsoleInputView *)inputView 298 | { 299 | [self.tableView wbt_scrollToBottomAnimated:YES]; 300 | } 301 | 302 | - (void)consoleInputView:(WBWebViewConsoleInputView *)inputView didCommitCommand:(NSString *)command 303 | { 304 | [self.tableView wbt_scrollToBottomAnimated:NO]; 305 | } 306 | 307 | #pragma mark - Keyboard Notification 308 | 309 | - (void)keyboardFrameDidChangeNotification:(NSNotification *)notification 310 | { 311 | [self relayoutViewsAnimated:YES]; 312 | } 313 | 314 | @end 315 | -------------------------------------------------------------------------------- /WBWebViewConsole/Models/UserPrompt/WBWebViewConsoleUserPromptCompletionController.h: -------------------------------------------------------------------------------- 1 | // 2 | // WBWebViewConsoleUserPromptCompletionController.h 3 | // Weibo 4 | // 5 | // Created by Wutian on 14/7/25. 6 | // 7 | // Copyright (c) 2014-present, Weibo, Corp. 8 | // All rights reserved. 9 | // 10 | // This source code is licensed under the BSD-style license found in the 11 | // LICENSE file in the root directory of this source tree. 12 | // 13 | 14 | #import 15 | 16 | @protocol WBWebView; 17 | 18 | @interface WBWebViewConsoleUserPromptCompletionController : NSObject 19 | 20 | - (instancetype)initWithWebView:(id)webView; 21 | 22 | - (void)fetchSuggestionsWithPrompt:(NSString *)prompt cursorIndex:(NSInteger)cursorIndex completion:(void (^)(NSArray * suggestions, NSRange replacementRange))completion; 23 | 24 | @end 25 | -------------------------------------------------------------------------------- /WBWebViewConsole/Models/UserPrompt/WBWebViewConsoleUserPromptCompletionController.m: -------------------------------------------------------------------------------- 1 | // 2 | // WBWebViewConsoleUserPromptCompletionController.m 3 | // Weibo 4 | // 5 | // Created by Wutian on 14/7/25. 6 | // 7 | // Copyright (c) 2014-present, Weibo, Corp. 8 | // All rights reserved. 9 | // 10 | // This source code is licensed under the BSD-style license found in the 11 | // LICENSE file in the root directory of this source tree. 12 | // 13 | 14 | #import "WBWebViewConsoleUserPromptCompletionController.h" 15 | #import "WBWebView.h" 16 | #import "WBWebViewConsoleDefines.h" 17 | #import "NSObject+WBJSONKit.h" 18 | #import "NSDictionary+WBTTypeCast.h" 19 | 20 | @interface WBWebViewConsoleUserPromptCompletionController () 21 | 22 | @property (nonatomic, weak) id webView; // weak 23 | @property (nonatomic, strong) NSString * js; 24 | 25 | @end 26 | 27 | @implementation WBWebViewConsoleUserPromptCompletionController 28 | 29 | 30 | - (instancetype)initWithWebView:(id)webView 31 | { 32 | if (self = [super init]) 33 | { 34 | self.webView = webView; 35 | } 36 | return self; 37 | } 38 | 39 | - (void)fetchSuggestionsWithPrompt:(NSString *)prompt cursorIndex:(NSInteger)cursorIndex completion:(void (^)(NSArray * suggestions, NSRange replacementRange))completion 40 | { 41 | if (!completion) return; 42 | 43 | void (^failedBlock)(void) = ^{ 44 | completion(nil, NSMakeRange(NSNotFound, 0)); 45 | }; 46 | 47 | if (!_webView) 48 | { 49 | failedBlock(); 50 | return; 51 | } 52 | 53 | if (!prompt.length) 54 | { 55 | failedBlock(); 56 | return; 57 | } 58 | 59 | NSString * js = self.js; 60 | js = [js stringByAppendingFormat:@"('%@', %ld)", [[prompt dataUsingEncoding:NSUTF8StringEncoding] base64Encoding], (long)cursorIndex]; 61 | 62 | [_webView wb_evaluateJavaScript:js completionHandler:^(id result, NSError * error) { 63 | if (![result isKindOfClass:[NSString class]]) 64 | { 65 | failedBlock(); 66 | return; 67 | } 68 | NSDictionary * resultDict = [result wb_objectFromJSONString]; 69 | NSArray * suggestions = [resultDict wbt_arrayForKey:@"completions"]; 70 | NSInteger tokenStart = [resultDict wbt_integerForKey:@"token_start"]; 71 | NSInteger tokenEnd = [resultDict wbt_integerForKey:@"token_end"]; 72 | 73 | if (![suggestions isKindOfClass:[NSArray class]] || !suggestions.count) 74 | { 75 | failedBlock(); 76 | return; 77 | } 78 | 79 | completion(suggestions, NSMakeRange(tokenStart, tokenEnd - tokenStart)); 80 | }]; 81 | } 82 | 83 | - (NSString *)js 84 | { 85 | if (!_js) 86 | { 87 | NSString * jsPath = [WBWebBrowserConsoleBundle() pathForResource:@"console_prompt_completion" ofType:@"js"]; 88 | NSString * js = [NSString stringWithContentsOfFile:jsPath encoding:NSUTF8StringEncoding error:NULL]; 89 | _js = js; 90 | } 91 | return _js; 92 | } 93 | 94 | @end 95 | -------------------------------------------------------------------------------- /WBWebViewConsole/Models/UserPrompt/WBWebViewConsoleUserPromptCompletionDatasource.h: -------------------------------------------------------------------------------- 1 | // 2 | // WBWebViewConsoleUserPromptCompletionDatasource.h 3 | // Weibo 4 | // 5 | // Created by Wutian on 14/7/25. 6 | // 7 | // Copyright (c) 2014-present, Weibo, Corp. 8 | // All rights reserved. 9 | // 10 | // This source code is licensed under the BSD-style license found in the 11 | // LICENSE file in the root directory of this source tree. 12 | // 13 | 14 | #import 15 | #import 16 | 17 | @class WBWebViewConsoleInputView; 18 | 19 | @interface WBWebViewConsoleUserPromptCompletionDatasource : NSObject 20 | 21 | @property (nonatomic, strong) NSArray * suggestions; 22 | @property (nonatomic, assign) NSRange replacementRange; 23 | @property (nonatomic, weak) WBWebViewConsoleInputView * inputView; 24 | 25 | @end 26 | -------------------------------------------------------------------------------- /WBWebViewConsole/Models/UserPrompt/WBWebViewConsoleUserPromptCompletionDatasource.m: -------------------------------------------------------------------------------- 1 | // 2 | // WBWebViewConsoleUserPromptCompletionDatasource.m 3 | // Weibo 4 | // 5 | // Created by Wutian on 14/7/25. 6 | // 7 | // Copyright (c) 2014-present, Weibo, Corp. 8 | // All rights reserved. 9 | // 10 | // This source code is licensed under the BSD-style license found in the 11 | // LICENSE file in the root directory of this source tree. 12 | // 13 | 14 | #import "WBWebViewConsoleUserPromptCompletionDatasource.h" 15 | #import "WBWebViewConsoleMessageCell.h" 16 | #import "WBWebViewConsoleInputView.h" 17 | #import "UIColor+WBTHelpers.h" 18 | #import "UIView+WBTSizes.h" 19 | 20 | @interface WBWebViewConsoleUserPromptCompletionCell : UITableViewCell 21 | 22 | @property (nonatomic, copy) NSString * suggestion; 23 | 24 | @end 25 | 26 | @implementation WBWebViewConsoleUserPromptCompletionDatasource 27 | 28 | - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView 29 | { 30 | return 1; 31 | } 32 | 33 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section 34 | { 35 | return _suggestions.count; 36 | } 37 | 38 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 39 | { 40 | static NSString * const identifier = @"WBWebViewConsoleUserPromptCompletionCell"; 41 | 42 | WBWebViewConsoleUserPromptCompletionCell * cell = [tableView dequeueReusableCellWithIdentifier:identifier]; 43 | 44 | if (!cell) { 45 | cell = [[WBWebViewConsoleUserPromptCompletionCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier]; 46 | } 47 | 48 | if (indexPath.row < _suggestions.count) 49 | { 50 | cell.suggestion = _suggestions[indexPath.row]; 51 | } 52 | 53 | return cell; 54 | } 55 | 56 | - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath 57 | { 58 | return 36; 59 | } 60 | 61 | - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath 62 | { 63 | [tableView deselectRowAtIndexPath:indexPath animated:NO]; 64 | 65 | if (indexPath.row < _suggestions.count) 66 | { 67 | [_inputView completePromptWithSuggestion:_suggestions[indexPath.row]]; 68 | } 69 | } 70 | 71 | @end 72 | 73 | @implementation WBWebViewConsoleUserPromptCompletionCell 74 | 75 | - (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier 76 | { 77 | if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) 78 | { 79 | self.textLabel.font = [WBWebViewConsoleMessageCell messageFont]; 80 | self.textLabel.textColor = [UIColor whiteColor]; 81 | self.textLabel.lineBreakMode = NSLineBreakByTruncatingMiddle; 82 | } 83 | return self; 84 | } 85 | 86 | - (void)layoutSubviews 87 | { 88 | [super layoutSubviews]; 89 | 90 | self.textLabel.backgroundColor = [UIColor clearColor]; 91 | self.textLabel.frame = CGRectMake(30, 0, self.wbtWidth - 40, self.wbtHeight); 92 | } 93 | 94 | - (void)setSuggestion:(NSString *)suggestion 95 | { 96 | self.textLabel.text = suggestion; 97 | } 98 | - (NSString *)suggestion 99 | { 100 | return self.textLabel.text; 101 | } 102 | 103 | - (void)setHighlighted:(BOOL)highlighted animated:(BOOL)animated 104 | { 105 | [super setHighlighted:highlighted animated:animated]; 106 | 107 | [self.contentView setBackgroundColor:highlighted ? RGBCOLOR(129, 137, 148) : [UIColor colorWithRed:0.69 green:0.72 blue:0.76 alpha:1]]; 108 | } 109 | 110 | @end 111 | -------------------------------------------------------------------------------- /WBWebViewConsole/Models/WBJSBridgeActionPrivateConsoleLog.h: -------------------------------------------------------------------------------- 1 | // 2 | // WBJSBridgeActionPrivateConsoleLog.h 3 | // WBWebViewConsole 4 | // 5 | // Created by 吴天 on 2/14/15. 6 | // 7 | // Copyright (c) 2014-present, Weibo, Corp. 8 | // All rights reserved. 9 | // 10 | // This source code is licensed under the BSD-style license found in the 11 | // LICENSE file in the root directory of this source tree. 12 | // 13 | 14 | #import "WBJSBridgeAction.h" 15 | 16 | @interface WBJSBridgeActionPrivateConsoleLog : WBJSBridgeAction 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /WBWebViewConsole/Models/WBJSBridgeActionPrivateConsoleLog.m: -------------------------------------------------------------------------------- 1 | // 2 | // WBJSBridgeActionPrivateConsoleLog.m 3 | // WBWebViewConsole 4 | // 5 | // Created by 吴天 on 2/14/15. 6 | // 7 | // Copyright (c) 2014-present, Weibo, Corp. 8 | // All rights reserved. 9 | // 10 | // This source code is licensed under the BSD-style license found in the 11 | // LICENSE file in the root directory of this source tree. 12 | // 13 | 14 | #import "WBJSBridgeActionPrivateConsoleLog.h" 15 | #import "WBWebViewConsole.h" 16 | #import "WBWebViewJSBridge.h" 17 | #import "WBWebView.h" 18 | #import "NSDictionary+WBTTypeCast.h" 19 | 20 | @implementation WBJSBridgeActionPrivateConsoleLog 21 | 22 | - (void)startAction 23 | { 24 | WBWebViewConsole * debugConsole = self.bridge.webView.console; 25 | 26 | NSDictionary * message = self.message.parameters; 27 | 28 | NSString * func = [message wbt_stringForKey:@"func"]; 29 | 30 | if (!func) func = @"log"; 31 | 32 | NSString * url = [message wbt_stringForKey:@"file"]; 33 | NSInteger line = [message wbt_integerForKey:@"lineno"]; 34 | NSInteger column = [message wbt_integerForKey:@"colno"]; 35 | 36 | if ([func isEqual:@"clear"]) 37 | { 38 | [debugConsole addMessage:nil type:WBWebViewConsoleMessageTypeClear level:WBWebViewConsoleMessageLevelInfo source:WBWebViewConsoleMessageSourceJS]; 39 | } 40 | else if ([func isEqual:@"assert"]) 41 | { 42 | NSArray * args = [message wbt_arrayForKey:@"args"]; 43 | id condition = [args firstObject]; 44 | 45 | if ([condition isKindOfClass:[NSNumber class]]) 46 | { 47 | if ([condition boolValue]) 48 | { 49 | return; 50 | } 51 | } 52 | 53 | if ([condition isKindOfClass:[NSString class]]) 54 | { 55 | if ([condition length] && 56 | ![condition isEqual:@"false"] && 57 | ![condition isEqual:@"0"] && 58 | ![condition isEqual:@"undefined"] && 59 | ![condition isEqual:@"null"]) 60 | { 61 | return; 62 | } 63 | } 64 | 65 | NSString * message = NSLocalizedString(@"Assert Failed: ", nil); 66 | 67 | if (args.count > 1) 68 | { 69 | NSString * reason = [[args subarrayWithRange:NSMakeRange(1, args.count - 1)] componentsJoinedByString:@" "]; 70 | message = [message stringByAppendingString:reason]; 71 | } 72 | 73 | [debugConsole addMessage:message type:WBWebViewConsoleMessageTypeAssert level:WBWebViewConsoleMessageLevelError source:WBWebViewConsoleMessageSourceJS url:url line:line column:column]; 74 | } 75 | else 76 | { 77 | NSDictionary * levelMap = @{@"warn": @(WBWebViewConsoleMessageLevelWarning), 78 | @"error": @(WBWebViewConsoleMessageLevelError), 79 | @"debug": @(WBWebViewConsoleMessageLevelDebug), 80 | @"info": @(WBWebViewConsoleMessageLevelInfo), 81 | @"log": @(WBWebViewConsoleMessageLevelLog)}; 82 | 83 | WBWebViewConsoleMessageLevel level = [levelMap[func] integerValue]; 84 | 85 | NSArray * args = [message wbt_arrayForKey:@"args"]; 86 | NSString * string = [args componentsJoinedByString:@" "]; 87 | 88 | [debugConsole addMessage:string type:WBWebViewConsoleMessageTypeLog level:level source:WBWebViewConsoleMessageSourceJS url:url line:line column:column]; 89 | } 90 | 91 | [self actionSuccessedWithResult:nil]; 92 | } 93 | 94 | @end 95 | -------------------------------------------------------------------------------- /WBWebViewConsole/Models/WBWebViewConsole.h: -------------------------------------------------------------------------------- 1 | // 2 | // WBWebViewConsole.h 3 | // Weibo 4 | // 5 | // Created by Wutian on 14/7/23. 6 | // 7 | // Copyright (c) 2014-present, Weibo, Corp. 8 | // All rights reserved. 9 | // 10 | // This source code is licensed under the BSD-style license found in the 11 | // LICENSE file in the root directory of this source tree. 12 | 13 | #import 14 | #import "WBWebViewConsoleMessage.h" 15 | 16 | extern NSString * const WBWebViewConsoleDidAddMessageNotification; 17 | extern NSString * const WBWebViewConsoleDidClearMessagesNotification; 18 | 19 | extern NSString * const WBWebViewConsoleLastSelectionElementName; 20 | 21 | @protocol WBWebView; 22 | 23 | @interface WBWebViewConsole : NSObject 24 | 25 | - (instancetype)initWithWebView:(id)webView; 26 | 27 | - (void)addMessage:(NSString *)message type:(WBWebViewConsoleMessageType)type level:(WBWebViewConsoleMessageLevel)level source:(WBWebViewConsoleMessageSource)source; 28 | - (void)addMessage:(NSString *)message type:(WBWebViewConsoleMessageType)type level:(WBWebViewConsoleMessageLevel)level source:(WBWebViewConsoleMessageSource)source url:(NSString *)url line:(NSInteger)line column:(NSInteger)column; 29 | - (void)addMessage:(NSString *)message level:(WBWebViewConsoleMessageLevel)level source:(WBWebViewConsoleMessageSource)source caller:(NSString *)caller; 30 | 31 | - (void)clearMessages; 32 | - (void)reset; 33 | 34 | - (void)sendMessage:(NSString *)message; 35 | 36 | @property (nonatomic, strong, readonly) NSArray * messages; 37 | @property (nonatomic, strong, readonly) NSArray * clearedMessages; 38 | 39 | @property (nonatomic, weak, readonly) id webView; 40 | 41 | - (void)storeCurrentSelectedElementToJavaScriptVariable:(NSString *)variable completion:(void (^)(BOOL success))completion; 42 | 43 | - (void)fetchSuggestionsForPrompt:(NSString *)prompt cursorIndex:(NSInteger)cursorIndex completion:(void (^)(NSArray * suggestions, NSRange replacementRange))completion; 44 | 45 | @end 46 | -------------------------------------------------------------------------------- /WBWebViewConsole/Models/WBWebViewConsoleInputHistoryEntry.h: -------------------------------------------------------------------------------- 1 | // 2 | // WBWebViewConsoleInputHistoryEntry.h 3 | // Weibo 4 | // 5 | // Created by Wutian on 14/7/27. 6 | // 7 | // Copyright (c) 2014-present, Weibo, Corp. 8 | // All rights reserved. 9 | // 10 | // This source code is licensed under the BSD-style license found in the 11 | // LICENSE file in the root directory of this source tree. 12 | // 13 | 14 | #import 15 | 16 | @interface WBWebViewConsoleInputHistoryEntry : NSObject 17 | 18 | @property (nonatomic, copy, readonly) NSString * text; 19 | @property (nonatomic, assign, readonly) NSRange cursor; 20 | 21 | + (instancetype)emptyEntry; 22 | + (instancetype)entryWithText:(NSString *)text cursor:(NSRange)cursor; 23 | 24 | @property (nonatomic, assign, readonly) BOOL isEmpty; 25 | 26 | @end 27 | -------------------------------------------------------------------------------- /WBWebViewConsole/Models/WBWebViewConsoleInputHistoryEntry.m: -------------------------------------------------------------------------------- 1 | // 2 | // WBWebViewConsoleInputHistoryEntry.m 3 | // Weibo 4 | // 5 | // Created by Wutian on 14/7/27. 6 | // 7 | // Copyright (c) 2014-present, Weibo, Corp. 8 | // All rights reserved. 9 | // 10 | // This source code is licensed under the BSD-style license found in the 11 | // LICENSE file in the root directory of this source tree. 12 | // 13 | 14 | #import "WBWebViewConsoleInputHistoryEntry.h" 15 | 16 | @interface WBWebViewConsoleInputHistoryEmptyEntry : WBWebViewConsoleInputHistoryEntry 17 | 18 | @end 19 | 20 | @interface WBWebViewConsoleInputHistoryEntry () 21 | 22 | @property (nonatomic, copy) NSString * text; 23 | @property (nonatomic, assign) NSRange cursor; 24 | 25 | @end 26 | 27 | @implementation WBWebViewConsoleInputHistoryEntry 28 | 29 | 30 | - (instancetype)init 31 | { 32 | if (self = [super init]) 33 | { 34 | _cursor = NSMakeRange(NSNotFound, 0); 35 | } 36 | return self; 37 | } 38 | 39 | + (instancetype)emptyEntry 40 | { 41 | return [WBWebViewConsoleInputHistoryEmptyEntry new]; 42 | } 43 | + (instancetype)entryWithText:(NSString *)text cursor:(NSRange)cursor 44 | { 45 | WBWebViewConsoleInputHistoryEntry * entry = [WBWebViewConsoleInputHistoryEntry new]; 46 | entry.text = text; 47 | entry.cursor = cursor; 48 | 49 | return entry; 50 | } 51 | 52 | - (BOOL)isEmpty 53 | { 54 | return NO; 55 | } 56 | 57 | @end 58 | 59 | @implementation WBWebViewConsoleInputHistoryEmptyEntry 60 | 61 | - (BOOL)isEmpty 62 | { 63 | return YES; 64 | } 65 | 66 | @end 67 | -------------------------------------------------------------------------------- /WBWebViewConsole/Models/WBWebViewConsoleMessage.h: -------------------------------------------------------------------------------- 1 | // 2 | // WBWebViewConsoleMessage.h 3 | // Weibo 4 | // 5 | // Created by Wutian on 14/7/23. 6 | // 7 | // Copyright (c) 2014-present, Weibo, Corp. 8 | // All rights reserved. 9 | // 10 | // This source code is licensed under the BSD-style license found in the 11 | // LICENSE file in the root directory of this source tree. 12 | // 13 | 14 | #import 15 | 16 | typedef NS_ENUM(NSInteger, WBWebViewConsoleMessageSource) 17 | { 18 | WBWebViewConsoleMessageSourceJS = 0, 19 | WBWebViewConsoleMessageSourceNavigation, 20 | WBWebViewConsoleMessageSourceUserCommand, 21 | WBWebViewConsoleMessageSourceUserCommandResult, 22 | WBWebViewConsoleMessageSourceNative, 23 | }; 24 | 25 | typedef NS_ENUM(NSInteger, WBWebViewConsoleMessageType) 26 | { 27 | WBWebViewConsoleMessageTypeLog = 0, 28 | WBWebViewConsoleMessageTypeClear, 29 | WBWebViewConsoleMessageTypeAssert, 30 | }; 31 | 32 | typedef NS_ENUM(NSInteger, WBWebViewConsoleMessageLevel) 33 | { 34 | WBWebViewConsoleMessageLevelNone = 0, 35 | WBWebViewConsoleMessageLevelLog = 1, 36 | WBWebViewConsoleMessageLevelWarning = 2, 37 | WBWebViewConsoleMessageLevelError = 3, 38 | WBWebViewConsoleMessageLevelDebug = 4, 39 | WBWebViewConsoleMessageLevelInfo = 5, 40 | WBWebViewConsoleMessageLevelSuccess = 6, 41 | }; 42 | 43 | @interface WBWebViewConsoleMessage : NSObject 44 | 45 | @property (nonatomic, readonly) WBWebViewConsoleMessageSource source; 46 | @property (nonatomic, readonly) WBWebViewConsoleMessageType type; 47 | @property (nonatomic, readonly) WBWebViewConsoleMessageLevel level; 48 | @property (nonatomic, strong, readonly) NSString * message; 49 | 50 | @property (nonatomic, readonly) NSInteger line; 51 | @property (nonatomic, readonly) NSInteger column; 52 | @property (nonatomic, strong, readonly) NSString * url; 53 | 54 | @property (nonatomic, strong, readonly) NSString * caller; 55 | 56 | @property (nonatomic, readonly) NSInteger repeatCount; 57 | 58 | + (instancetype)messageWithMessage:(NSString *)message type:(WBWebViewConsoleMessageType)type level:(WBWebViewConsoleMessageLevel)level source:(WBWebViewConsoleMessageSource)source; 59 | + (instancetype)messageWithMessage:(NSString *)message type:(WBWebViewConsoleMessageType)type level:(WBWebViewConsoleMessageLevel)level source:(WBWebViewConsoleMessageSource)source url:(NSString *)url line:(NSInteger)line column:(NSInteger)column; 60 | + (instancetype)messageWithMessage:(NSString *)message level:(WBWebViewConsoleMessageLevel)level source:(WBWebViewConsoleMessageSource)source caller:(NSString *)caller; 61 | 62 | @end 63 | 64 | @interface WBWebViewConsoleMessage (Caller) 65 | 66 | @property (nonatomic, strong, readonly) NSString * defaultCaller; 67 | 68 | @end 69 | -------------------------------------------------------------------------------- /WBWebViewConsole/Models/WBWebViewConsoleMessage.m: -------------------------------------------------------------------------------- 1 | // 2 | // WBWebViewConsoleMessage.m 3 | // Weibo 4 | // 5 | // Created by Wutian on 14/7/23. 6 | // 7 | // Copyright (c) 2014-present, Weibo, Corp. 8 | // All rights reserved. 9 | // 10 | // This source code is licensed under the BSD-style license found in the 11 | // LICENSE file in the root directory of this source tree. 12 | // 13 | 14 | #import "WBWebViewConsoleMessage.h" 15 | 16 | @interface WBWebViewConsoleMessage () 17 | 18 | @property (nonatomic) WBWebViewConsoleMessageSource source; 19 | @property (nonatomic) WBWebViewConsoleMessageType type; 20 | @property (nonatomic) WBWebViewConsoleMessageLevel level; 21 | @property (nonatomic, strong) NSString * message; 22 | 23 | @property (nonatomic) NSInteger line; 24 | @property (nonatomic) NSInteger column; 25 | @property (nonatomic, strong) NSString * url; 26 | 27 | @property (nonatomic, strong) NSString * caller; 28 | 29 | @end 30 | 31 | @implementation WBWebViewConsoleMessage 32 | 33 | 34 | + (instancetype)messageWithMessage:(NSString *)message type:(WBWebViewConsoleMessageType)type level:(WBWebViewConsoleMessageLevel)level source:(WBWebViewConsoleMessageSource)source 35 | { 36 | return [self messageWithMessage:message type:type level:level source:source url:nil line:0 column:0]; 37 | } 38 | 39 | + (instancetype)messageWithMessage:(NSString *)message type:(WBWebViewConsoleMessageType)type level:(WBWebViewConsoleMessageLevel)level source:(WBWebViewConsoleMessageSource)source url:(NSString *)url line:(NSInteger)line column:(NSInteger)column 40 | { 41 | WBWebViewConsoleMessage * result = [WBWebViewConsoleMessage new]; 42 | 43 | result.message = message; 44 | result.type = type; 45 | result.level = level; 46 | result.source = source; 47 | result.url = url; 48 | result.line = line; 49 | result.column = column; 50 | result.caller = result.defaultCaller; 51 | 52 | return result; 53 | } 54 | 55 | + (instancetype)messageWithMessage:(NSString *)message level:(WBWebViewConsoleMessageLevel)level source:(WBWebViewConsoleMessageSource)source caller:(NSString *)caller 56 | { 57 | WBWebViewConsoleMessage * result = [WBWebViewConsoleMessage new]; 58 | 59 | result.type = WBWebViewConsoleMessageTypeLog; 60 | result.source = source; 61 | result.level = level; 62 | result.message = message; 63 | result.caller = caller; 64 | 65 | return result; 66 | } 67 | 68 | @end 69 | 70 | @implementation WBWebViewConsoleMessage (Caller) 71 | 72 | - (NSString *)defaultCaller 73 | { 74 | if (!_url.length) return nil; 75 | 76 | NSString * filename = nil; 77 | NSURL * url = [NSURL URLWithString:_url]; 78 | if (url) 79 | { 80 | filename = [url.path lastPathComponent]; 81 | } 82 | else 83 | { 84 | filename = [[_url componentsSeparatedByString:@"/"] lastObject]; 85 | } 86 | 87 | if (!_line) return filename; 88 | 89 | return [NSString stringWithFormat:@"%@:%ld", filename, (long)_line]; 90 | } 91 | 92 | @end 93 | -------------------------------------------------------------------------------- /WBWebViewConsole/Resources/WBWebBrowserConsole.bundle/console_prompt_completion.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014-present, Weibo, Corp. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. 7 | * 8 | * This file is written based on WebKit's CodeMirrorCompletionController.js 9 | * 10 | * Copyright (C) 2013 Apple Inc. All rights reserved. 11 | * 12 | * Redistribution and use in source and binary forms, with or without 13 | * modification, are permitted provided that the following conditions 14 | * are met: 15 | * 1. Redistributions of source code must retain the above copyright 16 | * notice, this list of conditions and the following disclaimer. 17 | * 2. Redistributions in binary form must reproduce the above copyright 18 | * notice, this list of conditions and the following disclaimer in the 19 | * documentation and/or other materials provided with the distribution. 20 | * 21 | * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' 22 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 23 | * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 24 | * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS 25 | * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 26 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 27 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 28 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 29 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 30 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF 31 | * THE POSSIBILITY OF SUCH DAMAGE. 32 | */ 33 | 34 | (function(string, cursor) { 35 | 36 | if (typeof String.prototype.startsWith != 'function') { 37 | String.prototype.startsWith = function(str) { 38 | return this.slice(0, str.length) == str; 39 | }; 40 | } 41 | 42 | if (typeof String.prototype.contains != 'function') { 43 | String.prototype.contains = function(it) { 44 | return this.indexOf(it) != -1; 45 | }; 46 | } 47 | 48 | if (typeof Array.prototype.keySet != 'function') { 49 | Array.prototype.keySet = function() { 50 | var keys = {}; 51 | for (var i = 0; i < this.length; ++i) 52 | keys[this[i]] = true; 53 | return keys; 54 | } 55 | } 56 | 57 | function _scanStringForExpression(string, startOffset, direction, allowMiddleAndEmpty, includeStopCharacter, ignoreInitialUnmatchedOpenBracket, stopCharactersRegex) { 58 | 59 | // console.assert(direction === -1 || direction === 1); 60 | 61 | var stopCharactersRegex = stopCharactersRegex || /[\s=:;,!+\-*/%&|^~?<>.{}()[\]]/; 62 | 63 | function isStopCharacter(character) { 64 | return stopCharactersRegex.test(character); 65 | } 66 | 67 | function isOpenBracketCharacter(character) { 68 | return /[({[]/.test(character); 69 | } 70 | 71 | function isCloseBracketCharacter(character) { 72 | return /[)}\]]/.test(character); 73 | } 74 | 75 | function matchingBracketCharacter(character) { 76 | return { 77 | "{": "}", 78 | "(": ")", 79 | "[": "]", 80 | "}": "{", 81 | ")": "(", 82 | "]": "[" 83 | }[character]; 84 | } 85 | 86 | var endOffset = Math.min(startOffset, string.length); 87 | 88 | var endOfLineOrWord = endOffset === string.length || isStopCharacter(string.charAt(endOffset)); 89 | 90 | if (!endOfLineOrWord && !allowMiddleAndEmpty) 91 | return null; 92 | 93 | var bracketStack = []; 94 | var bracketOffsetStack = []; 95 | var lastCloseBracketOffset = NaN; 96 | 97 | var startOffset = endOffset; 98 | var firstOffset = endOffset + direction; 99 | for (var i = firstOffset; direction > 0 ? i < string.length : i >= 0; i += direction) { 100 | var character = string.charAt(i); 101 | 102 | // Ignore stop characters when we are inside brackets. 103 | if (isStopCharacter(character) && !bracketStack.length) 104 | break; 105 | 106 | if (isCloseBracketCharacter(character)) { 107 | bracketStack.push(character); 108 | bracketOffsetStack.push(i); 109 | } else if (isOpenBracketCharacter(character)) { 110 | if ((!ignoreInitialUnmatchedOpenBracket || i !== firstOffset) && 111 | (!bracketStack.length || matchingBracketCharacter(character) !== bracketStack.lastValue)) 112 | break; 113 | 114 | bracketOffsetStack.pop(); 115 | bracketStack.pop(); 116 | } 117 | 118 | startOffset = i + (direction > 0 ? 1 : 0); 119 | } 120 | 121 | if (bracketOffsetStack.length) 122 | startOffset = bracketOffsetStack.pop() + 1; 123 | 124 | if (includeStopCharacter && startOffset > 0 && startOffset < string.length) 125 | startOffset += direction; 126 | 127 | if (direction > 0) { 128 | var tempEndOffset = endOffset; 129 | endOffset = startOffset; 130 | startOffset = tempEndOffset; 131 | } 132 | 133 | return { 134 | string: string.substring(startOffset, endOffset), 135 | startOffset: startOffset, 136 | endOffset: endOffset 137 | }; 138 | }; 139 | 140 | function _generateJavaScriptCompletions(base, prefix, suffix) 141 | { 142 | // If there is a base expression then we should not attempt to match any keywords or variables. 143 | // Allow only open bracket characters at the end of the base, otherwise leave completions with 144 | // a base up to the delegate to figure out. 145 | if (base && !/[({[]$/.test(base)) 146 | return []; 147 | 148 | var matchingWords = []; 149 | 150 | const allKeywords = ["break", "case", "catch", "const", "continue", "debugger", "default", "delete", "do", "else", "false", "finally", "for", "function", "if", "in", 151 | "Infinity", "instanceof", "NaN", "new", "null", "return", "switch", "this", "throw", "true", "try", "typeof", "undefined", "var", "void", "while", "with"]; 152 | const valueKeywords = ["false", "Infinity", "NaN", "null", "this", "true", "undefined"]; 153 | 154 | function matchKeywords(keywords) 155 | { 156 | matchingWords = matchingWords.concat(keywords.filter(function(word) { 157 | return word.startsWith(prefix); 158 | })); 159 | } 160 | 161 | switch (suffix.substring(0, 1)) { 162 | case "": 163 | case " ": 164 | matchKeywords(allKeywords); 165 | break; 166 | 167 | case ".": 168 | case "[": 169 | matchKeywords(["false", "Infinity", "NaN", "this", "true"]); 170 | break; 171 | 172 | case "(": 173 | matchKeywords(["catch", "else", "for", "function", "if", "return", "switch", "throw", "while", "with"]); 174 | break; 175 | 176 | case "{": 177 | matchKeywords(["do", "else", "finally", "return", "try"]); 178 | break; 179 | 180 | case ":": 181 | matchKeywords(["case", "default"]); 182 | break; 183 | 184 | case ";": 185 | matchKeywords(valueKeywords); 186 | matchKeywords(["break", "continue", "debugger", "return", "void"]); 187 | break; 188 | } 189 | 190 | return matchingWords; 191 | }; 192 | 193 | 194 | function completionControllerCompletionsNeeded(defaultCompletions, base, prefix, suffix, forced) { 195 | // Don't allow non-forced empty prefix completions unless the base is that start of property access. 196 | if (!forced && !prefix && !/[.[]$/.test(base)) { 197 | return null; 198 | } 199 | 200 | // If the base ends with an open parentheses or open curly bracket then treat it like there is 201 | // no base so we get global object completions. 202 | if (/[({]$/.test(base)) 203 | base = ""; 204 | 205 | if (!base.length) { 206 | base = "window"; 207 | } 208 | 209 | var lastBaseIndex = base.length - 1; 210 | var dotNotation = base[lastBaseIndex] === "."; 211 | var bracketNotation = base[lastBaseIndex] === "["; 212 | 213 | if (dotNotation || bracketNotation) { 214 | base = base.substring(0, lastBaseIndex); 215 | 216 | // Don't suggest anything for an empty base that is using dot notation. 217 | // Bracket notation with an empty base will be treated as an array. 218 | if (!base && dotNotation) { 219 | return defaultCompletions; 220 | } 221 | 222 | // Don't allow non-forced empty prefix completions if the user is entering a number, since it might be a float. 223 | // But allow number completions if the base already has a decimal, so "10.0." will suggest Number properties. 224 | if (!forced && !prefix && dotNotation && base.indexOf(".") === -1 && parseInt(base, 10) == base) { 225 | return null; 226 | } 227 | 228 | // An empty base with bracket notation is not property access, it is an array. 229 | // Clear the bracketNotation flag so completions are not quoted. 230 | if (!base && bracketNotation) 231 | bracketNotation = false; 232 | } 233 | 234 | function evaluated(result, wasThrown) { 235 | var type = typeof result; 236 | if (wasThrown || !type || type === "undefined" || (type === "object" && result == null)) { 237 | return defaultCompletions; 238 | } 239 | 240 | function getCompletions(primitiveType) { 241 | var object; 242 | if (primitiveType === "string") 243 | object = new String(""); 244 | else if (primitiveType === "number") 245 | object = new Number(0); 246 | else if (primitiveType === "boolean") 247 | object = new Boolean(false); 248 | else 249 | object = this; 250 | 251 | var resultSet = {}; 252 | for (var o = object; o; o = o.__proto__) { 253 | try { 254 | var names = Object.getOwnPropertyNames(o); 255 | for (var i = 0; i < names.length; ++i) 256 | resultSet[names[i]] = true; 257 | } catch (e) { 258 | // Ignore 259 | } 260 | } 261 | 262 | return resultSet; 263 | } 264 | 265 | if (type === "object" || type === "function") 266 | return getCompletions.apply(result, [type]); 267 | else if (type === "string" || type === "number" || type === "boolean") 268 | return getCompletions(type); 269 | 270 | return {}; 271 | // else 272 | // console.error("Unknown result type: " + result.type); 273 | } 274 | 275 | 276 | function receivedPropertyNames(propertyNames) { 277 | propertyNames = propertyNames || {}; 278 | 279 | propertyNames = Object.keys(propertyNames); 280 | 281 | var implicitSuffix = ""; 282 | if (bracketNotation) { 283 | var quoteUsed = prefix[0] === "'" ? "'" : "\""; 284 | if (suffix !== "]" && suffix !== quoteUsed) 285 | implicitSuffix = "]"; 286 | } 287 | 288 | var completions = defaultCompletions; 289 | var knownCompletions = completions.keySet(); 290 | 291 | for (var i = 0; i < propertyNames.length; ++i) { 292 | var property = propertyNames[i]; 293 | 294 | if (dotNotation && !/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(property)) 295 | continue; 296 | 297 | if (bracketNotation) { 298 | if (parseInt(property) != property) 299 | property = quoteUsed + property.escapeCharacters(quoteUsed + "\\") + (suffix !== quoteUsed ? quoteUsed : ""); 300 | } 301 | 302 | if (!property.startsWith(prefix) || property === prefix || property in knownCompletions) 303 | continue; 304 | 305 | completions.push(property); 306 | knownCompletions[property] = true; 307 | 308 | // if (completions.length > 100) { 309 | // break; // get first 100 properties 310 | // } 311 | } 312 | 313 | function compare(a, b) { 314 | // Try to sort in numerical order first. 315 | var numericCompareResult = a - b; 316 | if (!isNaN(numericCompareResult)) 317 | return numericCompareResult; 318 | 319 | // Not numbers, sort as strings. 320 | return (a < b ? -1 : (a > b ? 1 : 0)); 321 | 322 | // implementation below by webkit is extremely slow, but handles non-english strings better 323 | // return a.localeCompare(b); 324 | } 325 | 326 | completions.sort(compare); 327 | 328 | return completions; 329 | } 330 | 331 | var propertyNames = {}; 332 | 333 | try { 334 | (function() { 335 | var result = eval(base); 336 | propertyNames = evaluated(result, false); 337 | })(); 338 | } catch (error) { 339 | propertyNames = evaluated(null, true); 340 | } 341 | 342 | return receivedPropertyNames(propertyNames); 343 | }; 344 | 345 | try { 346 | var lineString = decodeURIComponent(escape(window.atob(string))); 347 | 348 | var force = false; 349 | 350 | var backwardScanResult = _scanStringForExpression(lineString, cursor, -1, force); 351 | 352 | if (!backwardScanResult) { 353 | return; 354 | } 355 | 356 | var forwardScanResult = _scanStringForExpression(lineString, cursor, 1, true, true); 357 | var suffix = forwardScanResult.string; 358 | 359 | var _startOffset = backwardScanResult.startOffset; 360 | var _endOffset = backwardScanResult.endOffset; 361 | var _prefix = backwardScanResult.string; 362 | var _completions = []; 363 | var _implicitSuffix = ""; 364 | var _forced = force; 365 | 366 | var baseExpressionStopCharactersRegex = /[\s=:;,!+\-*/%&|^~?<>]/; 367 | if (baseExpressionStopCharactersRegex) 368 | var baseScanResult = _scanStringForExpression(lineString, _startOffset, -1, true, false, true, baseExpressionStopCharactersRegex); 369 | 370 | if (!force && !backwardScanResult.string && (!baseScanResult || !baseScanResult.string)) { 371 | return; 372 | } 373 | 374 | var defaultCompletions = []; 375 | 376 | defaultCompletions = _generateJavaScriptCompletions(baseScanResult ? baseScanResult.string : null, _prefix, suffix); 377 | 378 | // TODO: add javascript default completions 379 | 380 | _completions = completionControllerCompletionsNeeded(defaultCompletions, baseScanResult ? baseScanResult.string : null, _prefix, suffix, force); 381 | 382 | var result = { 383 | completions: _completions, 384 | token_start: _startOffset, 385 | token_end: _endOffset 386 | } 387 | 388 | if (_completions.length) { 389 | return JSON.stringify(result); 390 | } else { 391 | return; 392 | } 393 | } catch (error) { 394 | return; 395 | } 396 | return; 397 | }) -------------------------------------------------------------------------------- /WBWebViewConsole/Resources/WBWebBrowserConsole.bundle/debug_icon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naituw/WBWebViewConsole/2c9f5b37a7a8b875f303396addfaebef6f5b2bdf/WBWebViewConsole/Resources/WBWebBrowserConsole.bundle/debug_icon@2x.png -------------------------------------------------------------------------------- /WBWebViewConsole/Resources/WBWebBrowserConsole.bundle/error_icon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naituw/WBWebViewConsole/2c9f5b37a7a8b875f303396addfaebef6f5b2bdf/WBWebViewConsole/Resources/WBWebBrowserConsole.bundle/error_icon@2x.png -------------------------------------------------------------------------------- /WBWebViewConsole/Resources/WBWebBrowserConsole.bundle/info_icon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naituw/WBWebViewConsole/2c9f5b37a7a8b875f303396addfaebef6f5b2bdf/WBWebViewConsole/Resources/WBWebBrowserConsole.bundle/info_icon@2x.png -------------------------------------------------------------------------------- /WBWebViewConsole/Resources/WBWebBrowserConsole.bundle/issue_icon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naituw/WBWebViewConsole/2c9f5b37a7a8b875f303396addfaebef6f5b2bdf/WBWebViewConsole/Resources/WBWebBrowserConsole.bundle/issue_icon@2x.png -------------------------------------------------------------------------------- /WBWebViewConsole/Resources/WBWebBrowserConsole.bundle/navigation_issue_icon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naituw/WBWebViewConsole/2c9f5b37a7a8b875f303396addfaebef6f5b2bdf/WBWebViewConsole/Resources/WBWebBrowserConsole.bundle/navigation_issue_icon@2x.png -------------------------------------------------------------------------------- /WBWebViewConsole/Resources/WBWebBrowserConsole.bundle/navigation_success_icon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naituw/WBWebViewConsole/2c9f5b37a7a8b875f303396addfaebef6f5b2bdf/WBWebViewConsole/Resources/WBWebBrowserConsole.bundle/navigation_success_icon@2x.png -------------------------------------------------------------------------------- /WBWebViewConsole/Resources/WBWebBrowserConsole.bundle/success_icon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naituw/WBWebViewConsole/2c9f5b37a7a8b875f303396addfaebef6f5b2bdf/WBWebViewConsole/Resources/WBWebBrowserConsole.bundle/success_icon@2x.png -------------------------------------------------------------------------------- /WBWebViewConsole/Resources/WBWebBrowserConsole.bundle/userinput_action_panel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naituw/WBWebViewConsole/2c9f5b37a7a8b875f303396addfaebef6f5b2bdf/WBWebViewConsole/Resources/WBWebBrowserConsole.bundle/userinput_action_panel.png -------------------------------------------------------------------------------- /WBWebViewConsole/Resources/WBWebBrowserConsole.bundle/userinput_action_panel@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naituw/WBWebViewConsole/2c9f5b37a7a8b875f303396addfaebef6f5b2bdf/WBWebViewConsole/Resources/WBWebBrowserConsole.bundle/userinput_action_panel@2x.png -------------------------------------------------------------------------------- /WBWebViewConsole/Resources/WBWebBrowserConsole.bundle/userinput_actions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naituw/WBWebViewConsole/2c9f5b37a7a8b875f303396addfaebef6f5b2bdf/WBWebViewConsole/Resources/WBWebBrowserConsole.bundle/userinput_actions.png -------------------------------------------------------------------------------- /WBWebViewConsole/Resources/WBWebBrowserConsole.bundle/userinput_actions@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naituw/WBWebViewConsole/2c9f5b37a7a8b875f303396addfaebef6f5b2bdf/WBWebViewConsole/Resources/WBWebBrowserConsole.bundle/userinput_actions@2x.png -------------------------------------------------------------------------------- /WBWebViewConsole/Resources/WBWebBrowserConsole.bundle/userinput_prompt@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naituw/WBWebViewConsole/2c9f5b37a7a8b875f303396addfaebef6f5b2bdf/WBWebViewConsole/Resources/WBWebBrowserConsole.bundle/userinput_prompt@2x.png -------------------------------------------------------------------------------- /WBWebViewConsole/Resources/WBWebBrowserConsole.bundle/userinput_prompt_previous@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naituw/WBWebViewConsole/2c9f5b37a7a8b875f303396addfaebef6f5b2bdf/WBWebViewConsole/Resources/WBWebBrowserConsole.bundle/userinput_prompt_previous@2x.png -------------------------------------------------------------------------------- /WBWebViewConsole/Resources/WBWebBrowserConsole.bundle/userinput_result@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naituw/WBWebViewConsole/2c9f5b37a7a8b875f303396addfaebef6f5b2bdf/WBWebViewConsole/Resources/WBWebBrowserConsole.bundle/userinput_result@2x.png -------------------------------------------------------------------------------- /WBWebViewConsole/Supports/NSDictionary+WBTTypeCast.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSDictionary+WBTTypeCast.h 3 | // WBTool 4 | // 5 | // Created by Wade Cheng on 8/19/13. 6 | // 7 | // Copyright (c) 2013-present, Weibo, Corp. 8 | // All rights reserved. 9 | // 10 | // This source code is licensed under the BSD-style license found in the 11 | // LICENSE file in the root directory of this source tree. 12 | 13 | #import 14 | 15 | /*! 16 | * 本类主要功能是返还字典子节点的指定类型的值和遍历所有子节点让满足指定类型的子节点执行相应的block 17 | */ 18 | @interface NSDictionary (WBTTypeCast) 19 | /*! 20 | * 返回当前key对应value的NSString值,没有则返回nil 21 | * 22 | * @param key 用来获取当前字典对应值的key 23 | * @return 当key对应的值为NSString类型时返回该值,没有则返回nil 24 | */ 25 | - (NSString *)wbt_stringForKey:(NSString *)key; 26 | 27 | /*! 28 | * 返回当前key对应value的NSDictionary值,没有则返回nil 29 | * 30 | * @param key 用来获取当前字典对应值的key 31 | * @return 返回当前key对应value的NSDictionary值,不存在则返回Nil 32 | */ 33 | - (NSDictionary *)wbt_dictForKey:(NSString *)key; 34 | 35 | /*! 36 | * 返回当前key对应value的NSArray值,没有则返回nil 37 | * 38 | * @param key 用来获取当前字典对应值的key 39 | * @return 返回当前key对应value的NSArray值,不存在则返回nil 40 | */ 41 | - (NSArray *)wbt_arrayForKey:(NSString *)key; 42 | 43 | /*! 44 | * 返回当前key对应value的NSInteger值,没有则返回0 45 | * 46 | * @param key 用来获取当前字典对应值的key 47 | * @return 返回当前key对应value的NSInteger值,不存在则返回0 48 | */ 49 | - (NSInteger)wbt_integerForKey:(NSString *)key; 50 | 51 | @end 52 | -------------------------------------------------------------------------------- /WBWebViewConsole/Supports/NSDictionary+WBTTypeCast.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSDictionary+WBTTypeCast.m 3 | // WBTool 4 | // 5 | // Created by Wade Cheng on 8/19/13. 6 | // 7 | // Copyright (c) 2013-present, Weibo, Corp. 8 | // All rights reserved. 9 | // 10 | // This source code is licensed under the BSD-style license found in the 11 | // LICENSE file in the root directory of this source tree. 12 | 13 | #import "NSDictionary+WBTTypeCast.h" 14 | #import "WBTTypeCastUtil.h" 15 | 16 | /** 17 | * 返回根据所给key值在当前字典对象上对应的值. 18 | */ 19 | #define OFK [self objectForKey:key] 20 | 21 | @implementation NSDictionary (WBTTypeCast) 22 | 23 | #pragma mark - NSString 24 | 25 | - (NSString *)wbt_stringForKey:(NSString *)key defaultValue:(NSString *)defaultValue; 26 | { 27 | return wbt_stringOfValue(OFK, defaultValue); 28 | } 29 | 30 | - (NSString *)wbt_stringForKey:(NSString *)key; 31 | { 32 | return [self wbt_stringForKey:key defaultValue:nil]; 33 | } 34 | 35 | #pragma mark - NSDictionary 36 | 37 | - (NSDictionary *)wbt_dictForKey:(NSString *)key defaultValue:(NSDictionary *)defaultValue 38 | { 39 | return wbt_dictOfValue(OFK, defaultValue); 40 | } 41 | 42 | - (NSDictionary *)wbt_dictForKey:(NSString *)key 43 | { 44 | return [self wbt_dictForKey:key defaultValue:nil]; 45 | } 46 | 47 | - (NSDictionary *)wbt_dictionaryWithValuesForKeys:(NSArray *)keys 48 | { 49 | return [self dictionaryWithValuesForKeys:keys]; 50 | } 51 | 52 | #pragma mark - NSArray 53 | 54 | - (NSArray *)wbt_arrayForKey:(NSString *)key defaultValue:(NSArray *)defaultValue 55 | { 56 | return wbt_arrayOfValue(OFK, defaultValue); 57 | } 58 | 59 | - (NSArray *)wbt_arrayForKey:(NSString *)key 60 | { 61 | return [self wbt_arrayForKey:key defaultValue:nil]; 62 | } 63 | 64 | #pragma mark - NSInteger 65 | 66 | - (NSInteger)wbt_integerForKey:(NSString *)key defaultValue:(NSInteger)defaultValue; 67 | { 68 | return wbt_integerOfValue(OFK, defaultValue); 69 | } 70 | 71 | - (NSInteger)wbt_integerForKey:(NSString *)key; 72 | { 73 | return [self wbt_integerForKey:key defaultValue:0]; 74 | } 75 | 76 | @end 77 | -------------------------------------------------------------------------------- /WBWebViewConsole/Supports/NSObject+WBJSONKit.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSObject+WBJSONKit.h 3 | // WBWebViewConsole 4 | // 5 | // Created by 吴天 on 15/3/3. 6 | // 7 | // Copyright (c) 2014-present, Weibo, Corp. 8 | // All rights reserved. 9 | // 10 | // This source code is licensed under the BSD-style license found in the 11 | // LICENSE file in the root directory of this source tree. 12 | // 13 | 14 | #import 15 | 16 | @interface NSObject (WBJSONKit) 17 | 18 | @end 19 | 20 | @interface NSString (WBJSONKit) 21 | 22 | @property (nonatomic, copy, readonly) NSString * wb_JSONString; 23 | @property (nonatomic, copy, readonly) id wb_objectFromJSONString; 24 | 25 | @end 26 | 27 | @interface NSArray (WBJSONKit) 28 | 29 | @property (nonatomic, copy, readonly) NSString * wb_JSONString; 30 | 31 | @end 32 | 33 | @interface NSDictionary (WBJSONKit) 34 | 35 | @property (nonatomic, copy, readonly) NSString * wb_JSONString; 36 | 37 | @end 38 | 39 | -------------------------------------------------------------------------------- /WBWebViewConsole/Supports/NSObject+WBJSONKit.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSObject+WBJSONKit.m 3 | // WBWebViewConsole 4 | // 5 | // Created by 吴天 on 15/3/3. 6 | // 7 | // Copyright (c) 2014-present, Weibo, Corp. 8 | // All rights reserved. 9 | // 10 | // This source code is licensed under the BSD-style license found in the 11 | // LICENSE file in the root directory of this source tree. 12 | // 13 | 14 | #import "NSObject+WBJSONKit.h" 15 | 16 | @implementation NSObject (WBJSONKit) 17 | 18 | @end 19 | 20 | @implementation NSString (WBJSONKit) 21 | 22 | - (NSString *)wb_JSONString 23 | { 24 | return [self copy]; 25 | } 26 | 27 | - (id)wb_objectFromJSONString 28 | { 29 | NSData * data = [self dataUsingEncoding:NSUTF8StringEncoding]; 30 | return [NSJSONSerialization JSONObjectWithData:data options:0 error:NULL]; 31 | } 32 | 33 | @end 34 | 35 | @implementation NSArray (WBJSONKit) 36 | 37 | - (NSString *)wb_JSONString 38 | { 39 | NSData * data = [NSJSONSerialization dataWithJSONObject:self options:0 error:NULL]; 40 | 41 | return [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; 42 | } 43 | 44 | @end 45 | 46 | @implementation NSDictionary (WBJSONKit) 47 | 48 | - (NSString *)wb_JSONString 49 | { 50 | NSData * data = [NSJSONSerialization dataWithJSONObject:self options:0 error:NULL]; 51 | 52 | return [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; 53 | } 54 | 55 | @end 56 | -------------------------------------------------------------------------------- /WBWebViewConsole/Supports/UIColor+WBTHelpers.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIColor+WBTHelpers.h 3 | // WBWebViewConsole 4 | // 5 | // Created by 吴天 on 2/13/15. 6 | // 7 | // Copyright (c) 2014-present, Weibo, Corp. 8 | // All rights reserved. 9 | // 10 | // This source code is licensed under the BSD-style license found in the 11 | // LICENSE file in the root directory of this source tree. 12 | 13 | #import 14 | 15 | #ifndef RGBCOLOR 16 | #define RGBCOLOR(r,g,b) [UIColor colorWithRed:r/255.0 green:g/255.0 blue:b/255.0 alpha:1] 17 | #endif 18 | 19 | #ifndef RGBACOLOR 20 | #define RGBACOLOR(r,g,b,a) [UIColor colorWithRed:r/255.0 green:g/255.0 blue:b/255.0 alpha:a] 21 | #endif 22 | 23 | @interface UIColor (WBTHelpers) 24 | 25 | + (UIColor *)wbt_colorWithHexValue:(NSInteger)hex alpha:(CGFloat)alpha; 26 | 27 | @end 28 | -------------------------------------------------------------------------------- /WBWebViewConsole/Supports/UIColor+WBTHelpers.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIColor+WBTHelpers.m 3 | // WBWebViewConsole 4 | // 5 | // Created by 吴天 on 2/13/15. 6 | // 7 | // Copyright (c) 2014-present, Weibo, Corp. 8 | // All rights reserved. 9 | // 10 | // This source code is licensed under the BSD-style license found in the 11 | // LICENSE file in the root directory of this source tree. 12 | // 13 | 14 | #import "UIColor+WBTHelpers.h" 15 | 16 | @implementation UIColor (WBTHelpers) 17 | 18 | + (CGFloat)wbt_redColorFromHexRGBColor:(NSInteger)color 19 | { 20 | return (((color & 0xff0000) >> 16) / 255.0); 21 | } 22 | 23 | + (CGFloat)wbt_greenColorFromRGBColor:(NSInteger)color 24 | { 25 | return (((color & 0x00ff00) >> 8) / 255.0); 26 | } 27 | 28 | + (CGFloat)wbt_blueColorFromRGBColor:(NSInteger)color 29 | { 30 | return ((color & 0x0000ff) / 255.0); 31 | } 32 | 33 | + (UIColor *)wbt_colorWithHexValue:(NSInteger)color alpha:(CGFloat)alpha 34 | { 35 | return [UIColor colorWithRed:[UIColor wbt_redColorFromHexRGBColor:color] 36 | green:[UIColor wbt_greenColorFromRGBColor:color] 37 | blue:[UIColor wbt_blueColorFromRGBColor:color] 38 | alpha:alpha]; 39 | } 40 | 41 | @end 42 | -------------------------------------------------------------------------------- /WBWebViewConsole/Supports/UIDevice+WBTHelpers.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIDevice+WBTHelpers.h 3 | // WBWebViewConsole 4 | // 5 | // Created by 吴天 on 2/13/15. 6 | // 7 | // Copyright (c) 2014-present, Weibo, Corp. 8 | // All rights reserved. 9 | // 10 | // This source code is licensed under the BSD-style license found in the 11 | // LICENSE file in the root directory of this source tree. 12 | 13 | #if __IPHONE_7_0 && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_7_0 14 | #define IF_IOS7_OR_GREATER(...) if ([[UIDevice currentDevice] wbt_systemMainVersion] >= 7) { __VA_ARGS__ } 15 | #else 16 | #define IF_IOS7_OR_GREATER(...) 17 | #endif 18 | 19 | #define WBAvalibleOS(os_version) ([[UIDevice currentDevice] wbt_systemMainVersion] >= os_version) 20 | 21 | #import 22 | 23 | @interface UIDevice (WBTHelpers) 24 | 25 | - (int)wbt_systemMainVersion; 26 | 27 | @end 28 | -------------------------------------------------------------------------------- /WBWebViewConsole/Supports/UIDevice+WBTHelpers.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIDevice+WBTHelpers.m 3 | // WBWebViewConsole 4 | // 5 | // Created by 吴天 on 2/13/15. 6 | // 7 | // Copyright (c) 2014-present, Weibo, Corp. 8 | // All rights reserved. 9 | // 10 | // This source code is licensed under the BSD-style license found in the 11 | // LICENSE file in the root directory of this source tree. 12 | // 13 | 14 | #import "UIDevice+WBTHelpers.h" 15 | 16 | @implementation UIDevice (WBTHelpers) 17 | 18 | static int systemMainVersion = 0; 19 | - (int)wbt_systemMainVersion 20 | { 21 | if (systemMainVersion > 0) { 22 | return systemMainVersion; 23 | } 24 | systemMainVersion = [self systemVersion].intValue; 25 | return systemMainVersion; 26 | } 27 | 28 | @end 29 | -------------------------------------------------------------------------------- /WBWebViewConsole/Supports/UIScrollView+WBTUtilities.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIScrollView+WBTUtilities.h 3 | // WBTool 4 | // 5 | // Created by kevin on 14-7-18. 6 | // 7 | // Copyright (c) 2014-present, Weibo, Corp. 8 | // All rights reserved. 9 | // 10 | // This source code is licensed under the BSD-style license found in the 11 | // LICENSE file in the root directory of this source tree. 12 | 13 | #import 14 | #import 15 | 16 | /*! 17 | * 本类主要提供了对UIScollview的相关操作 18 | */ 19 | @interface UIScrollView (WBTUtilities) 20 | 21 | /*! 22 | * 判断当前contenView是否在顶部 23 | * 24 | * @return 返回YES则当前是顶部,NO则不顶部 25 | */ 26 | - (BOOL)wbt_isScrolledToTop; 27 | 28 | /*! 29 | * 将contenView移动至顶部 30 | * 31 | * @param animated YES表示带动画效果,NO则不带 32 | */ 33 | - (void)wbt_scrollToTopAnimated:(BOOL)animated; 34 | 35 | /*! 36 | * 判断当前contenView是否在底部 37 | * 38 | * @return 返回YES则当前是底部,NO则不在底部 39 | */ 40 | - (BOOL)wbt_isScrolledToBottom; 41 | 42 | /*! 43 | * 将contenView移动至底部 44 | * 45 | * @param animated YES表示带动画效果,NO则不带 46 | */ 47 | - (void)wbt_scrollToBottomAnimated:(BOOL)animated; 48 | 49 | @end 50 | -------------------------------------------------------------------------------- /WBWebViewConsole/Supports/UIScrollView+WBTUtilities.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIScrollView+WBTUtilities.m 3 | // WBTool 4 | // 5 | // Created by kevin on 14-7-18. 6 | // 7 | // Copyright (c) 2014-present, Weibo, Corp. 8 | // All rights reserved. 9 | // 10 | // This source code is licensed under the BSD-style license found in the 11 | // LICENSE file in the root directory of this source tree. 12 | // 13 | 14 | #import "UIScrollView+WBTUtilities.h" 15 | 16 | @implementation UIScrollView (WBTUtilities) 17 | 18 | - (BOOL)wbt_isScrolledToTop 19 | { 20 | return self.contentOffset.y <= self.contentInset.top; 21 | } 22 | 23 | - (void)wbt_scrollToTopAnimated:(BOOL)animated 24 | { 25 | [self setContentOffset:CGPointMake(0, -self.contentInset.top) animated:animated]; 26 | } 27 | 28 | - (BOOL)wbt_isScrolledToBottom 29 | { 30 | if (self.contentOffset.y >= self.contentSize.height - self.bounds.size.height) 31 | { 32 | return YES; 33 | } 34 | return NO; 35 | } 36 | 37 | - (void)wbt_scrollToBottomAnimated:(BOOL)animated 38 | { 39 | CGRect rect = CGRectMake(self.contentOffset.x, self.contentSize.height - 1, self.contentSize.width, 1); 40 | [self scrollRectToVisible:rect animated:animated]; 41 | } 42 | 43 | @end 44 | -------------------------------------------------------------------------------- /WBWebViewConsole/Supports/UIView+WBTSizes.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014-present, Weibo, Corp. 2 | // All rights reserved. 3 | // 4 | // This source code is licensed under the BSD-style license found in the 5 | // LICENSE file in the root directory of this source tree. 6 | // 7 | // Copyright 2009-2010 Facebook 8 | // 9 | // Licensed under the Apache License, Version 2.0 (the "License"); 10 | // you may not use this file except in compliance with the License. 11 | // You may obtain a copy of the License at 12 | // 13 | // http://www.apache.org/licenses/LICENSE-2.0 14 | // 15 | // Unless required by applicable law or agreed to in writing, software 16 | // distributed under the License is distributed on an "AS IS" BASIS, 17 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18 | // See the License for the specific language governing permissions and 19 | // limitations under the License. 20 | // 21 | 22 | #import 23 | 24 | @interface UIView (WBTSizes) 25 | 26 | /*! 27 | * Shortcut for frame.origin.x. 28 | * 29 | * Sets frame.origin.x = left 30 | */ 31 | @property (nonatomic) CGFloat wbtLeft; 32 | 33 | /*! 34 | * Shortcut for frame.origin.y 35 | * 36 | * Sets frame.origin.y = top 37 | */ 38 | @property (nonatomic) CGFloat wbtTop; 39 | 40 | /*! 41 | * Shortcut for frame.origin.x + frame.size.width 42 | * 43 | * Sets frame.origin.x = right - frame.size.width 44 | */ 45 | @property (nonatomic) CGFloat wbtRight; 46 | 47 | /*! 48 | * Shortcut for frame.origin.y + frame.size.height 49 | * 50 | * Sets frame.origin.y = bottom - frame.size.height 51 | */ 52 | @property (nonatomic) CGFloat wbtBottom; 53 | 54 | /*! 55 | * Shortcut for frame.size.width 56 | * 57 | * Sets frame.size.width = width 58 | */ 59 | @property (nonatomic) CGFloat wbtWidth; 60 | 61 | /*! 62 | * Shortcut for frame.size.height 63 | * 64 | * Sets frame.size.height = height 65 | */ 66 | @property (nonatomic) CGFloat wbtHeight; 67 | 68 | /*! 69 | * Shortcut for center.x 70 | * 71 | * Sets center.x = centerX 72 | */ 73 | @property (nonatomic) CGFloat wbtCenterX; 74 | 75 | /*! 76 | * Shortcut for center.y 77 | * 78 | * Sets center.y = centerY 79 | */ 80 | @property (nonatomic) CGFloat wbtCenterY; 81 | 82 | @end 83 | -------------------------------------------------------------------------------- /WBWebViewConsole/Supports/UIView+WBTSizes.m: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014-present, Weibo, Corp. 2 | // All rights reserved. 3 | // 4 | // This source code is licensed under the BSD-style license found in the 5 | // LICENSE file in the root directory of this source tree. 6 | // 7 | // Copyright 2009-2010 Facebook 8 | // 9 | // Licensed under the Apache License, Version 2.0 (the "License"); 10 | // you may not use this file except in compliance with the License. 11 | // You may obtain a copy of the License at 12 | // 13 | // http://www.apache.org/licenses/LICENSE-2.0 14 | // 15 | // Unless required by applicable law or agreed to in writing, software 16 | // distributed under the License is distributed on an "AS IS" BASIS, 17 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18 | // See the License for the specific language governing permissions and 19 | // limitations under the License. 20 | // 21 | 22 | /////////////////////////////////////////////////////////////////////////////////////////////////// 23 | /////////////////////////////////////////////////////////////////////////////////////////////////// 24 | /////////////////////////////////////////////////////////////////////////////////////////////////// 25 | 26 | #import 27 | #import 28 | #import "UIView+WBTSizes.h" 29 | 30 | @implementation UIView (WBTSizes) 31 | 32 | 33 | /////////////////////////////////////////////////////////////////////////////////////////////////// 34 | - (CGFloat)wbtLeft { 35 | return self.frame.origin.x; 36 | } 37 | 38 | 39 | /////////////////////////////////////////////////////////////////////////////////////////////////// 40 | - (void)setWbtLeft:(CGFloat)x { 41 | CGRect frame = self.frame; 42 | frame.origin.x = x; 43 | self.frame = frame; 44 | } 45 | 46 | 47 | /////////////////////////////////////////////////////////////////////////////////////////////////// 48 | - (CGFloat)wbtTop { 49 | return self.frame.origin.y; 50 | } 51 | 52 | 53 | /////////////////////////////////////////////////////////////////////////////////////////////////// 54 | - (void)setWbtTop:(CGFloat)y { 55 | CGRect frame = self.frame; 56 | frame.origin.y = y; 57 | self.frame = frame; 58 | } 59 | 60 | 61 | /////////////////////////////////////////////////////////////////////////////////////////////////// 62 | - (CGFloat)wbtRight { 63 | return self.frame.origin.x + self.frame.size.width; 64 | } 65 | 66 | 67 | /////////////////////////////////////////////////////////////////////////////////////////////////// 68 | - (void)setWbtRight:(CGFloat)right { 69 | CGRect frame = self.frame; 70 | frame.origin.x = right - frame.size.width; 71 | self.frame = frame; 72 | } 73 | 74 | 75 | /////////////////////////////////////////////////////////////////////////////////////////////////// 76 | - (CGFloat)wbtBottom { 77 | return self.frame.origin.y + self.frame.size.height; 78 | } 79 | 80 | 81 | /////////////////////////////////////////////////////////////////////////////////////////////////// 82 | - (void)setWbtBottom:(CGFloat)bottom { 83 | CGRect frame = self.frame; 84 | frame.origin.y = bottom - frame.size.height; 85 | self.frame = frame; 86 | } 87 | 88 | 89 | /////////////////////////////////////////////////////////////////////////////////////////////////// 90 | - (CGFloat)wbtCenterX { 91 | return self.center.x; 92 | } 93 | 94 | 95 | /////////////////////////////////////////////////////////////////////////////////////////////////// 96 | - (void)setWbtCenterX:(CGFloat)centerX { 97 | self.center = CGPointMake(centerX, self.center.y); 98 | } 99 | 100 | 101 | /////////////////////////////////////////////////////////////////////////////////////////////////// 102 | - (CGFloat)wbtCenterY { 103 | return self.center.y; 104 | } 105 | 106 | 107 | /////////////////////////////////////////////////////////////////////////////////////////////////// 108 | - (void)setWbtCenterY:(CGFloat)centerY { 109 | self.center = CGPointMake(self.center.x, centerY); 110 | } 111 | 112 | 113 | /////////////////////////////////////////////////////////////////////////////////////////////////// 114 | - (CGFloat)wbtWidth { 115 | return self.frame.size.width; 116 | } 117 | 118 | 119 | /////////////////////////////////////////////////////////////////////////////////////////////////// 120 | - (void)setWbtWidth:(CGFloat)width { 121 | CGRect frame = self.frame; 122 | frame.size.width = width; 123 | self.frame = frame; 124 | } 125 | 126 | 127 | /////////////////////////////////////////////////////////////////////////////////////////////////// 128 | - (CGFloat)wbtHeight { 129 | return self.frame.size.height; 130 | } 131 | 132 | 133 | /////////////////////////////////////////////////////////////////////////////////////////////////// 134 | - (void)setWbtHeight:(CGFloat)height { 135 | CGRect frame = self.frame; 136 | frame.size.height = height; 137 | self.frame = frame; 138 | } 139 | 140 | @end 141 | 142 | 143 | 144 | -------------------------------------------------------------------------------- /WBWebViewConsole/Supports/WBKeyboardObserver.h: -------------------------------------------------------------------------------- 1 | // 2 | // WBKeyboardObserver.h 3 | // Weibo 4 | // 5 | // Created by Wutian on 14/6/27. 6 | // 7 | // Copyright (c) 2014-present, Weibo, Corp. 8 | // All rights reserved. 9 | // 10 | // This source code is licensed under the BSD-style license found in the 11 | // LICENSE file in the root directory of this source tree. 12 | 13 | #import 14 | #import 15 | 16 | extern NSString * const WBKeyboardObserverFrameDidUpdateNotification; 17 | 18 | @interface WBKeyboardObserver : NSObject 19 | 20 | + (instancetype)sharedObserver; 21 | 22 | @property (nonatomic, assign) CGRect frameBegin; 23 | @property (nonatomic, assign) CGRect frameEnd; 24 | @property (nonatomic, assign) NSTimeInterval animationDuration; 25 | @property (nonatomic, assign) UIViewAnimationCurve animationCurve; 26 | 27 | @end 28 | -------------------------------------------------------------------------------- /WBWebViewConsole/Supports/WBKeyboardObserver.m: -------------------------------------------------------------------------------- 1 | // 2 | // WBKeyboardObserver.m 3 | // Weibo 4 | // 5 | // Created by Wutian on 14/6/27. 6 | // 7 | // Copyright (c) 2014-present, Weibo, Corp. 8 | // All rights reserved. 9 | // 10 | // This source code is licensed under the BSD-style license found in the 11 | // LICENSE file in the root directory of this source tree. 12 | // 13 | 14 | #import "WBKeyboardObserver.h" 15 | 16 | NSString * const WBKeyboardObserverFrameDidUpdateNotification = @"WBKeyboardObserverFrameDidUpdateNotification"; 17 | 18 | @implementation WBKeyboardObserver 19 | 20 | + (void)load 21 | { 22 | [WBKeyboardObserver sharedObserver]; 23 | } 24 | 25 | + (instancetype)sharedObserver 26 | { 27 | static WBKeyboardObserver * observer; 28 | static dispatch_once_t onceToken; 29 | dispatch_once(&onceToken, ^{ 30 | observer = [[[self class] alloc] init]; 31 | }); 32 | return observer; 33 | } 34 | 35 | - (void)dealloc 36 | { 37 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 38 | 39 | } 40 | 41 | - (id)init 42 | { 43 | if (self = [super init]) 44 | { 45 | self.frameBegin = CGRectNull; 46 | self.frameEnd = CGRectNull; 47 | self.animationDuration = 0.0; 48 | self.animationCurve = UIViewAnimationCurveEaseInOut; 49 | 50 | NSNotificationCenter * center = [NSNotificationCenter defaultCenter]; 51 | 52 | [center addObserver:self selector:@selector(keyboardDidChangeNotification:) name:UIKeyboardWillChangeFrameNotification object:nil]; 53 | [center addObserver:self selector:@selector(keyboardDidChangeNotification:) name:UIKeyboardWillShowNotification object:nil]; 54 | [center addObserver:self selector:@selector(keyboardDidChangeNotification:) name:UIKeyboardWillHideNotification object:nil]; 55 | } 56 | return self; 57 | } 58 | 59 | - (void)keyboardDidChangeNotification:(NSNotification *)notification 60 | { 61 | NSDictionary * userInfo = notification.userInfo; 62 | 63 | if (!userInfo) return; 64 | 65 | self.frameEnd = [[userInfo objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue]; 66 | self.frameBegin = [[userInfo objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue]; 67 | 68 | if ([notification.name isEqual:UIKeyboardWillHideNotification]) { 69 | CGRect frameEnd = self.frameEnd; 70 | frameEnd.origin.y = MAX(self.frameEnd.origin.y, [UIScreen mainScreen].bounds.size.height); 71 | self.frameEnd = frameEnd; 72 | } 73 | 74 | self.animationDuration = [[userInfo objectForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue]; 75 | self.animationCurve = [[userInfo objectForKey:UIKeyboardAnimationCurveUserInfoKey] integerValue]; 76 | 77 | [[NSNotificationCenter defaultCenter] postNotificationName:WBKeyboardObserverFrameDidUpdateNotification object:self]; 78 | } 79 | 80 | @end 81 | -------------------------------------------------------------------------------- /WBWebViewConsole/Supports/WBTTypeCastUtil.h: -------------------------------------------------------------------------------- 1 | // 2 | // TypeCastUtil.h 3 | // Weibo 4 | // 5 | // Created by Wade Cheng on 8/19/13. 6 | // 7 | // Copyright (c) 2013-present, Weibo, Corp. 8 | // All rights reserved. 9 | // 10 | // This source code is licensed under the BSD-style license found in the 11 | // LICENSE file in the root directory of this source tree. 12 | 13 | 14 | #import 15 | 16 | /*! 17 | * 返回当前value的NSString的值,不是string则返回defaultValue 18 | * 19 | * @param value 需要判断的对象 20 | * @param defaultValue 一个默认的NSString类型值当需要判断的对象value不是NSString类型时则返回该默认值,没有则为Nil 21 | * 22 | * @return 是NSString类型返回value,不是string则返回defaultValue 23 | */ 24 | NSString *wbt_stringOfValue(id value, NSString *defaultValue); 25 | 26 | /*! 27 | * 返回当前value的NSDictionary值,不是则返回defaultValue 28 | * 29 | * @param value 需要判断的对象 30 | * @param defaultValue 一个默认的NSDictionary类型值当需要判断的对象value不是NSDictionary类型时则返回该默认值,没有则为Nil 31 | * 32 | * @return 是NSDictionary类型返回value,不是则返回defaultValue 33 | */ 34 | NSDictionary *wbt_dictOfValue(id value, NSDictionary *defaultValue); 35 | 36 | /*! 37 | * 返回当前value的NSArray值,不是则返回defaultValue 38 | * 39 | * @param value 需要判断的对象 40 | * @param defaultValue 一个默认的NSArray类型值当需要判断的对象value不是NSArray类型时则返回该默认值,没有则为Nil 41 | * 42 | * @return 是NSArray类型返回value,不是则返回defaultValue 43 | */ 44 | NSArray *wbt_arrayOfValue(id value ,NSArray *defaultValue); 45 | 46 | /*! 47 | * 返回当前value的NSInteger值,不是则返回defaultValue 48 | * 49 | * @param value 一个默认的NSInteger类型值当需要判断的对象value不是NSInteger类型时则返回该默认值,没有则为Nil 50 | * @param defaultValue 需要返回的默认值,没有则为Nil 51 | * 52 | * @return 是NSInteger类型返回value,不是则返回defaultValue 53 | */ 54 | NSInteger wbt_integerOfValue(id value, NSInteger defaultValue); 55 | -------------------------------------------------------------------------------- /WBWebViewConsole/Supports/WBTTypeCastUtil.m: -------------------------------------------------------------------------------- 1 | // 2 | // WBTTypeCastUtil.m 3 | // Weibo 4 | // 5 | // Created by Wade Cheng on 8/19/13. 6 | // 7 | // Copyright (c) 2013-present, Weibo, Corp. 8 | // All rights reserved. 9 | // 10 | // This source code is licensed under the BSD-style license found in the 11 | // LICENSE file in the root directory of this source tree. 12 | 13 | #import "WBTTypeCastUtil.h" 14 | 15 | NSString *wbt_stringOfValue(id value, NSString *defaultValue) 16 | { 17 | if (![value isKindOfClass:[NSString class]]) 18 | { 19 | if ([value isKindOfClass:[NSNumber class]]) 20 | { 21 | return [value stringValue]; 22 | } 23 | return defaultValue; 24 | } 25 | return value; 26 | } 27 | 28 | NSDictionary *wbt_dictOfValue(id value, NSDictionary *defaultValue) 29 | { 30 | if ([value isKindOfClass:[NSDictionary class]]) 31 | return value; 32 | 33 | return defaultValue; 34 | } 35 | 36 | NSArray *wbt_arrayOfValue(id value ,NSArray *defaultValue) 37 | { 38 | if ([value isKindOfClass:[NSArray class]]) 39 | return value; 40 | 41 | return defaultValue; 42 | } 43 | 44 | NSInteger wbt_integerOfValue(id value, NSInteger defaultValue) 45 | { 46 | if ([value respondsToSelector:@selector(integerValue)]) 47 | return [value integerValue]; 48 | 49 | return defaultValue; 50 | } 51 | -------------------------------------------------------------------------------- /WBWebViewConsole/Supports/WBTextView.h: -------------------------------------------------------------------------------- 1 | // 2 | // WBTextView.h 3 | // Weibo 4 | // 5 | // Created by Stephen Liu on 14-3-12. 6 | // 7 | // Copyright (c) 2014-present, Weibo, Corp. 8 | // All rights reserved. 9 | // 10 | // This source code is licensed under the BSD-style license found in the 11 | // LICENSE file in the root directory of this source tree. 12 | 13 | #import 14 | 15 | @class WBTextView; 16 | @protocol WBTextViewDelegate 17 | @optional 18 | - (void)textViewHeightChanged:(WBTextView *)textView; 19 | - (void)textView:(WBTextView *)textView willChangeHeight:(NSInteger)height; 20 | @end 21 | 22 | @interface WBTextView : UITextView 23 | @property (nonatomic, strong)UIColor *placeHolderColor; 24 | @property (nonatomic, strong)NSString *placeHolder; 25 | @property (nonatomic, assign)BOOL wraperWithScrollView; 26 | @property (nonatomic, weak)id textDelegate; 27 | @property (nonatomic, assign)NSInteger maxNumberOfLines; 28 | @property (nonatomic, assign)NSInteger minNumberOfLines; 29 | @property (nonatomic, assign)NSInteger minHeight; 30 | @property (nonatomic, assign)NSInteger maxHeight; 31 | @property (nonatomic, assign)BOOL animateHeightChange; 32 | @end 33 | -------------------------------------------------------------------------------- /WBWebViewConsole/Supports/WBWebView/JSBridge/Actions/WBJSBridgeAction.h: -------------------------------------------------------------------------------- 1 | // 2 | // WBJSBridgeAction.h 3 | // WBWebViewConsole 4 | // 5 | // Created by 吴天 on 2/13/15. 6 | // 7 | // Copyright (c) 2014-present, Weibo, Corp. 8 | // All rights reserved. 9 | // 10 | // This source code is licensed under the BSD-style license found in the 11 | // LICENSE file in the root directory of this source tree. 12 | 13 | #import 14 | #import "WBJSBridgeMessage.h" 15 | 16 | @class WBWebViewJSBridge; 17 | 18 | @interface WBJSBridgeAction : NSObject 19 | 20 | - (instancetype)initWithBridge:(WBWebViewJSBridge *)bridge message:(WBJSBridgeMessage *)message; 21 | 22 | @property (nonatomic, weak, readonly) WBWebViewJSBridge * bridge; 23 | @property (nonatomic, strong, readonly) WBJSBridgeMessage * message; 24 | 25 | - (void)startAction; 26 | 27 | - (void)actionSuccessedWithResult:(NSDictionary *)result; 28 | - (void)actionFailed; 29 | 30 | + (Class)actionClassForActionName:(NSString *)actionName; 31 | 32 | @end 33 | -------------------------------------------------------------------------------- /WBWebViewConsole/Supports/WBWebView/JSBridge/Actions/WBJSBridgeAction.m: -------------------------------------------------------------------------------- 1 | // 2 | // WBJSBridgeAction.m 3 | // WBWebViewConsole 4 | // 5 | // Created by 吴天 on 2/13/15. 6 | // 7 | // Copyright (c) 2014-present, Weibo, Corp. 8 | // All rights reserved. 9 | // 10 | // This source code is licensed under the BSD-style license found in the 11 | // LICENSE file in the root directory of this source tree. 12 | // 13 | 14 | #import "WBJSBridgeAction.h" 15 | #import "WBWebViewJSBridge.h" 16 | 17 | @interface WBJSBridgeAction () 18 | 19 | @property (nonatomic, weak) WBWebViewJSBridge * bridge; 20 | @property (nonatomic, strong) WBJSBridgeMessage * message; 21 | 22 | @end 23 | 24 | @implementation WBJSBridgeAction 25 | 26 | - (instancetype)initWithBridge:(WBWebViewJSBridge *)bridge message:(WBJSBridgeMessage *)message 27 | { 28 | if (self = [self init]) { 29 | self.bridge = bridge; 30 | self.message = message; 31 | } 32 | return self; 33 | } 34 | 35 | - (void)startAction 36 | { 37 | [self actionFailed]; 38 | } 39 | 40 | - (void)actionSuccessedWithResult:(NSDictionary *)result 41 | { 42 | [self.bridge actionDidFinish:self success:YES result:result]; 43 | } 44 | 45 | - (void)actionFailed 46 | { 47 | [self.bridge actionDidFinish:self success:NO result:nil]; 48 | } 49 | 50 | NSString * const WBJSBridgeActionNamePrefix = @"WBJSBridgeAction"; 51 | 52 | + (Class)actionClassForActionName:(NSString *)actionName 53 | { 54 | actionName = [actionName stringByReplacingCharactersInRange:NSMakeRange(0, 1) withString:[actionName substringToIndex:1].capitalizedString]; 55 | 56 | NSString * actionClassName = [NSString stringWithFormat:@"%@%@", WBJSBridgeActionNamePrefix, actionName]; 57 | Class klass = NSClassFromString(actionClassName); 58 | 59 | if (klass && [klass isSubclassOfClass:[WBJSBridgeAction class]]) { 60 | return klass; 61 | } 62 | 63 | return NULL; 64 | } 65 | 66 | @end 67 | -------------------------------------------------------------------------------- /WBWebViewConsole/Supports/WBWebView/JSBridge/Resources/WBWebBrowserJSBridge.bundle/wbjs.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2014-present, Weibo, Corp. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. 7 | */ 8 | 9 | (function (config) { 10 | 11 | config = config || {}; 12 | 13 | (function (root, factory) { 14 | var interface = config["interface"]; 15 | if (interface && !root[interface]) { 16 | root[interface] = factory(); 17 | 18 | var eventName = config["readyEvent"]; 19 | var document = root['document']; 20 | if (eventName && document) { 21 | var readyEvent = document.createEvent('Events'); 22 | readyEvent.initEvent(eventName); 23 | document.dispatchEvent(readyEvent); 24 | } 25 | } 26 | } (this, function () { 27 | 28 | var _callbacks = []; 29 | var _callbackIndex = 1000; 30 | var _messageQueue = []; 31 | var _invokeScheme = config['invokeScheme']; 32 | 33 | var Weibo = { 34 | invoke: function (name, params, callback) { 35 | 36 | if (typeof name !== 'string') { 37 | return; 38 | } 39 | 40 | if (!params) { 41 | params = {}; 42 | } 43 | 44 | var callbackID = (_callbackIndex++).toString(); 45 | 46 | if (callback) { 47 | _callbacks[callbackID] = callback; 48 | } 49 | 50 | var message = { 51 | action: name, 52 | params: params, 53 | callback_id: callbackID 54 | } 55 | 56 | _messageQueue.push(message); 57 | location.href = _invokeScheme; 58 | }, 59 | 60 | _messageQueue: function () { 61 | var ret = JSON.stringify(_messageQueue); 62 | _messageQueue = []; 63 | return ret; 64 | }, 65 | 66 | _handleMessage: function (message) { 67 | if (!message) { 68 | return; 69 | } 70 | var callbackID = message.callback_id; 71 | if (!callbackID) { 72 | return; 73 | } 74 | 75 | var callback = _callbacks[callbackID]; 76 | 77 | if (callback) { 78 | var params = message.params; 79 | var success = !message.failed; 80 | 81 | callback(params, success); 82 | } 83 | } 84 | }; 85 | 86 | return Weibo; 87 | })); 88 | }) -------------------------------------------------------------------------------- /WBWebViewConsole/Supports/WBWebView/JSBridge/WBJSBridgeMessage.h: -------------------------------------------------------------------------------- 1 | // 2 | // WBJSBridgeMessage.h 3 | // WBWebViewConsole 4 | // 5 | // Created by 吴天 on 2/13/15. 6 | // 7 | // Copyright (c) 2014-present, Weibo, Corp. 8 | // All rights reserved. 9 | // 10 | // This source code is licensed under the BSD-style license found in the 11 | // LICENSE file in the root directory of this source tree. 12 | 13 | #import 14 | 15 | @interface WBJSBridgeMessage : NSObject 16 | 17 | - (instancetype)initWithDictionary:(NSDictionary *)dict; 18 | 19 | @property (nonatomic, copy, readonly) NSString * action; 20 | @property (nonatomic, copy, readonly) NSDictionary * parameters; 21 | 22 | @property (nonatomic, copy, readonly) NSString * callbackID; 23 | 24 | @end 25 | -------------------------------------------------------------------------------- /WBWebViewConsole/Supports/WBWebView/JSBridge/WBJSBridgeMessage.m: -------------------------------------------------------------------------------- 1 | // 2 | // WBJSBridgeMessage.m 3 | // WBWebViewConsole 4 | // 5 | // Created by 吴天 on 2/13/15. 6 | // 7 | // Copyright (c) 2014-present, Weibo, Corp. 8 | // All rights reserved. 9 | // 10 | // This source code is licensed under the BSD-style license found in the 11 | // LICENSE file in the root directory of this source tree. 12 | // 13 | 14 | #import "WBJSBridgeMessage.h" 15 | #import "NSDictionary+WBTTypeCast.h" 16 | 17 | @interface WBJSBridgeMessage () 18 | 19 | @property (nonatomic, copy) NSString * action; 20 | @property (nonatomic, copy) NSDictionary * parameters; 21 | 22 | @property (nonatomic, copy) NSString * callbackID; 23 | 24 | @end 25 | 26 | @implementation WBJSBridgeMessage 27 | 28 | - (instancetype)initWithDictionary:(NSDictionary *)dict 29 | { 30 | if (self = [self init]) { 31 | self.action = [dict wbt_stringForKey:@"action"]; 32 | self.parameters = [dict wbt_dictForKey:@"params"]; 33 | self.callbackID = [dict wbt_stringForKey:@"callback_id"]; 34 | } 35 | return self; 36 | } 37 | 38 | @end 39 | -------------------------------------------------------------------------------- /WBWebViewConsole/Supports/WBWebView/JSBridge/WBWebViewJSBridge.h: -------------------------------------------------------------------------------- 1 | // 2 | // WBWebViewJSBridge.h 3 | // WBWebViewConsole 4 | // 5 | // Created by 吴天 on 2/13/15. 6 | // 7 | // Copyright (c) 2014-present, Weibo, Corp. 8 | // All rights reserved. 9 | // 10 | // This source code is licensed under the BSD-style license found in the 11 | // LICENSE file in the root directory of this source tree. 12 | 13 | #import 14 | 15 | /** 16 | * Minimal Version of Weibo's JSBridge 17 | */ 18 | 19 | @protocol WBWebView; 20 | @class WBJSBridgeMessage, WBJSBridgeAction; 21 | 22 | @interface WBWebViewJSBridge : NSObject 23 | 24 | - (instancetype)initWithWebView:(id)webView; 25 | 26 | @property (nonatomic, weak, readonly) id webView; 27 | 28 | @property (nonatomic, copy) NSString * interfaceName; 29 | @property (nonatomic, copy) NSString * readyEventName; 30 | @property (nonatomic, copy) NSString * invokeScheme; 31 | 32 | - (NSString *)javascriptSource; 33 | 34 | - (BOOL)handleWebViewRequest:(NSURLRequest *)request; 35 | 36 | - (void)actionDidFinish:(WBJSBridgeAction *)action success:(BOOL)success result:(NSDictionary *)result; 37 | 38 | @end 39 | -------------------------------------------------------------------------------- /WBWebViewConsole/Supports/WBWebView/JSBridge/WBWebViewJSBridge.m: -------------------------------------------------------------------------------- 1 | // 2 | // WBWebViewJSBridge.m 3 | // WBWebViewConsole 4 | // 5 | // Created by 吴天 on 2/13/15. 6 | // 7 | // Copyright (c) 2014-present, Weibo, Corp. 8 | // All rights reserved. 9 | // 10 | // This source code is licensed under the BSD-style license found in the 11 | // LICENSE file in the root directory of this source tree. 12 | // 13 | 14 | #import "WBWebViewJSBridge.h" 15 | #import "WBWebView.h" 16 | #import "WBWebViewUserScript.h" 17 | #import "WBJSBridgeMessage.h" 18 | #import "WBJSBridgeAction.h" 19 | #import "NSObject+WBJSONKit.h" 20 | 21 | @interface WBWebViewJSBridge () 22 | { 23 | NSMutableArray *_actions; 24 | 25 | struct { 26 | unsigned int sourceNeedsUpdate: 1; 27 | } _flags; 28 | } 29 | 30 | @property (nonatomic, weak) id webView; 31 | @property (nonatomic, strong) NSString * javascriptSource; 32 | 33 | @end 34 | 35 | @implementation WBWebViewJSBridge 36 | 37 | - (instancetype)initWithWebView:(id)webView 38 | { 39 | if (self = [super init]) { 40 | 41 | _actions = [[NSMutableArray alloc] init]; 42 | 43 | self.webView = webView; 44 | self.interfaceName = @"WeiboJSBridge"; 45 | self.readyEventName = @"WeiboJSBridgeReady"; 46 | self.invokeScheme = @"wbjs://invoke"; 47 | 48 | dispatch_async(dispatch_get_main_queue(), ^{ 49 | [webView wb_addUserScript:[WBWebViewUserScript scriptWithSource:self.javascriptSource injectionTime:WBUserScriptInjectionTimeAtDocumentStart mainFrameOnly:YES]]; 50 | }); 51 | } 52 | return self; 53 | } 54 | 55 | - (void)setInterfaceName:(NSString *)interfaceName 56 | { 57 | if (_interfaceName != interfaceName) { 58 | _interfaceName = interfaceName; 59 | _flags.sourceNeedsUpdate = YES; 60 | } 61 | } 62 | 63 | - (void)setReadyEventName:(NSString *)readyEventName 64 | { 65 | if (_readyEventName != readyEventName) { 66 | _readyEventName = readyEventName; 67 | _flags.sourceNeedsUpdate = YES; 68 | } 69 | } 70 | 71 | - (void)setInvokeScheme:(NSString *)invokeScheme 72 | { 73 | if (_invokeScheme != invokeScheme) { 74 | _invokeScheme = invokeScheme; 75 | _flags.sourceNeedsUpdate = YES; 76 | } 77 | } 78 | 79 | - (NSString *)javascriptSource 80 | { 81 | if (!_javascriptSource || _flags.sourceNeedsUpdate) { 82 | 83 | NSBundle * bundle = [NSBundle bundleWithPath:[NSString stringWithFormat:@"%@/%@", [[NSBundle bundleForClass:[self class]] bundlePath], @"WBWebBrowserJSBridge.bundle"]]; 84 | 85 | _javascriptSource = [[NSString alloc] initWithContentsOfFile:[bundle pathForResource:@"wbjs" ofType:@"js"] encoding:NSUTF8StringEncoding error:NULL]; 86 | NSAssert(_interfaceName, @"interfaceName must not nil"); 87 | NSAssert(_readyEventName, @"readyEventName must not nil"); 88 | NSAssert(_invokeScheme, @"invokeScheme must not nil"); 89 | 90 | NSDictionary * config = @{@"interface": _interfaceName, 91 | @"readyEvent": _readyEventName, 92 | @"invokeScheme": _invokeScheme}; 93 | NSString * json = [config wb_JSONString]; 94 | _javascriptSource = [_javascriptSource stringByAppendingFormat:@"(%@)", json]; 95 | } 96 | return _javascriptSource; 97 | } 98 | 99 | - (void)processMessage:(WBJSBridgeMessage *)message 100 | { 101 | WBJSBridgeAction * action = nil; 102 | 103 | Class klass = [WBJSBridgeAction actionClassForActionName:message.action]; 104 | 105 | if (klass) 106 | { 107 | action = [[klass alloc] initWithBridge:self message:message]; 108 | } 109 | 110 | if (action) { 111 | [_actions addObject:action]; 112 | [action startAction]; 113 | } else { 114 | [self sendCallbackForMessage:message success:NO result:nil]; 115 | } 116 | } 117 | 118 | - (void)processMessageQueue:(NSArray *)queue 119 | { 120 | for (NSDictionary * dict in queue) { 121 | WBJSBridgeMessage * message = [[WBJSBridgeMessage alloc] initWithDictionary:dict]; 122 | [self processMessage:message]; 123 | } 124 | } 125 | 126 | - (void)actionDidFinish:(WBJSBridgeAction *)action success:(BOOL)success result:(NSDictionary *)result 127 | { 128 | if (![_actions containsObject:action]) return; 129 | 130 | [_actions removeObject:action]; 131 | 132 | [self sendCallbackForMessage:action.message success:success result:result]; 133 | } 134 | 135 | - (void)sendCallbackForMessage:(WBJSBridgeMessage *)message success:(BOOL)success result:(NSDictionary *)result 136 | { 137 | if (!message.callbackID) { 138 | return; 139 | } 140 | NSDictionary * callback = @{@"params": result ? : @{}, 141 | @"failed": @(!success), 142 | @"callback_id": message.callbackID}; 143 | NSString * js = [NSString stringWithFormat:@"%@._handleMessage(%@)", _interfaceName, callback.wb_JSONString]; 144 | [self.webView wb_evaluateJavaScript:js completionHandler:NULL]; 145 | } 146 | 147 | - (BOOL)handleWebViewRequest:(NSURLRequest *)request 148 | { 149 | NSURL * url = request.URL; 150 | if ([url.absoluteString isEqual:self.invokeScheme]) { 151 | NSString * js = [NSString stringWithFormat:@"%@._messageQueue()", _interfaceName]; 152 | [_webView wb_evaluateJavaScript:js completionHandler:^(NSString * result, NSError * error) { 153 | NSArray * queue = [result wb_objectFromJSONString]; 154 | if ([queue isKindOfClass:[NSArray class]]) { 155 | [self processMessageQueue:queue]; 156 | } 157 | }]; 158 | 159 | return YES; 160 | } 161 | return NO; 162 | } 163 | 164 | @end 165 | -------------------------------------------------------------------------------- /WBWebViewConsole/Supports/WBWebView/WBWebView.h: -------------------------------------------------------------------------------- 1 | // 2 | // WBWebView.h 3 | // WBWebViewConsole 4 | // 5 | // Created by 吴天 on 2/13/15. 6 | // 7 | // Copyright (c) 2014-present, Weibo, Corp. 8 | // All rights reserved. 9 | // 10 | // This source code is licensed under the BSD-style license found in the 11 | // LICENSE file in the root directory of this source tree. 12 | 13 | #import 14 | #import "WBWebViewUserScript.h" 15 | #import "WBWebViewJSBridge.h" 16 | 17 | @class WBWebViewConsole; 18 | 19 | /** 20 | * Abstract version of Weibo's WBWebView 21 | * The original one is an auto-switching wrapper of UIWebView & WKWebView 22 | */ 23 | 24 | @protocol WBWebView 25 | 26 | @required 27 | @property (nonatomic, strong, readonly) WBWebViewJSBridge * JSBridge; 28 | @property (nonatomic, strong, readonly) WBWebViewConsole * console; 29 | 30 | - (void)wb_evaluateJavaScript:(NSString *)javaScriptString completionHandler:(void (^)(NSString *, NSError *))completionHandler; 31 | 32 | - (void)wb_addUserScript:(WBWebViewUserScript *)userScript; 33 | - (void)wb_removeAllUserScripts; 34 | 35 | @property (nonatomic, readonly, copy) NSArray * wb_userScripts; 36 | 37 | @end 38 | -------------------------------------------------------------------------------- /WBWebViewConsole/Supports/WBWebView/WBWebViewUserScript.h: -------------------------------------------------------------------------------- 1 | // 2 | // WBWebViewUserScript.h 3 | // Weibo 4 | // 5 | // Created by Wutian on 14/7/11. 6 | // 7 | // Copyright (c) 2014-present, Weibo, Corp. 8 | // All rights reserved. 9 | // 10 | // This source code is licensed under the BSD-style license found in the 11 | // LICENSE file in the root directory of this source tree. 12 | 13 | #import 14 | 15 | typedef NS_ENUM(NSInteger, WBUserScriptInjectionTime) { 16 | WBUserScriptInjectionTimeAtDocumentStart, 17 | WBUserScriptInjectionTimeAtDocumentEnd 18 | }; 19 | 20 | @protocol WBWebViewUserScript 21 | 22 | @property (nonatomic, copy, readonly) NSString * source; 23 | @property (nonatomic, readonly) WBUserScriptInjectionTime scriptInjectionTime; 24 | @property (nonatomic, readonly, getter=isForMainFrameOnly) BOOL forMainFrameOnly; 25 | 26 | @end 27 | 28 | @interface WBWebViewUserScript : NSObject 29 | 30 | + (id)scriptWithSource:(NSString *)source injectionTime:(WBUserScriptInjectionTime)injectionTime mainFrameOnly:(BOOL)mainFrameOnly; 31 | 32 | @end 33 | -------------------------------------------------------------------------------- /WBWebViewConsole/Supports/WBWebView/WBWebViewUserScript.m: -------------------------------------------------------------------------------- 1 | // 2 | // WBWebViewUserScript.m 3 | // Weibo 4 | // 5 | // Created by Wutian on 14/7/11. 6 | // 7 | // Copyright (c) 2014-present, Weibo, Corp. 8 | // All rights reserved. 9 | // 10 | // This source code is licensed under the BSD-style license found in the 11 | // LICENSE file in the root directory of this source tree. 12 | // 13 | 14 | #import "WBWebViewUserScript.h" 15 | 16 | //#if __IPHONE_8_0 && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_8_0 17 | //#if kUsesWKWebView 18 | //#import 19 | // 20 | //@interface WKUserScript (WBAdditions) 21 | // 22 | //@end 23 | // 24 | //static inline WKUserScriptInjectionTime WKInjectionTimeFromWBInjectionTime(WBUserScriptInjectionTime time) 25 | //{ 26 | // switch (time) 27 | // { 28 | // case WBUserScriptInjectionTimeAtDocumentStart: 29 | // return WKUserScriptInjectionTimeAtDocumentStart; 30 | // case WBUserScriptInjectionTimeAtDocumentEnd: 31 | // default: 32 | // return WKUserScriptInjectionTimeAtDocumentEnd; 33 | // } 34 | //} 35 | // 36 | //static inline WBUserScriptInjectionTime WBInjectionTimeFromWKInjectionTime(WKUserScriptInjectionTime time) 37 | //{ 38 | // switch (time) 39 | // { 40 | // case WKUserScriptInjectionTimeAtDocumentStart: 41 | // return WBUserScriptInjectionTimeAtDocumentStart; 42 | // case WKUserScriptInjectionTimeAtDocumentEnd: 43 | // default: 44 | // return WBUserScriptInjectionTimeAtDocumentEnd; 45 | // } 46 | //} 47 | // 48 | //#endif 49 | //#endif 50 | 51 | @implementation WBWebViewUserScript 52 | 53 | 54 | - (instancetype)initWithSource:(NSString *)source scriptInjectionTime:(WBUserScriptInjectionTime)injectionTime forMainFrameOnly:(BOOL)forMainFrameOnly 55 | { 56 | if (self = [self init]) 57 | { 58 | _source = [source copy]; 59 | _scriptInjectionTime = injectionTime; 60 | _forMainFrameOnly = forMainFrameOnly; 61 | } 62 | return self; 63 | } 64 | 65 | + (id)scriptWithSource:(NSString *)source injectionTime:(WBUserScriptInjectionTime)injectionTime mainFrameOnly:(BOOL)mainFrameOnly 66 | { 67 | //#if kUsesWKWebView 68 | // IF_IOS8_OR_GREATER({ 69 | // return [[[WKUserScript alloc] initWithSource:source injectionTime:WKInjectionTimeFromWBInjectionTime(injectionTime) forMainFrameOnly:YES] autorelease]; 70 | // }) 71 | //#endif 72 | 73 | return [[self alloc] initWithSource:source scriptInjectionTime:injectionTime forMainFrameOnly:mainFrameOnly]; 74 | } 75 | 76 | @synthesize source = _source; 77 | @synthesize scriptInjectionTime = _scriptInjectionTime; 78 | @synthesize forMainFrameOnly = _forMainFrameOnly; 79 | @end 80 | 81 | //#if __IPHONE_8_0 && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_8_0 82 | //#if kUsesWKWebView 83 | // 84 | //@implementation WKUserScript (WBAdditions) 85 | // 86 | //- (WBUserScriptInjectionTime)scriptInjectionTime 87 | //{ 88 | // return WBInjectionTimeFromWKInjectionTime(self.injectionTime); 89 | //} 90 | // 91 | //@end 92 | // 93 | //#endif 94 | //#endif 95 | -------------------------------------------------------------------------------- /WBWebViewConsole/Supports/WBWebViewConsoleDefines.h: -------------------------------------------------------------------------------- 1 | // 2 | // WBWebViewConsoleDefines.h 3 | // WBWebViewConsole 4 | // 5 | // Created by 吴天 on 2/13/15. 6 | // 7 | // Copyright (c) 2014-present, Weibo, Corp. 8 | // All rights reserved. 9 | // 10 | // This source code is licensed under the BSD-style license found in the 11 | // LICENSE file in the root directory of this source tree. 12 | 13 | #ifndef WBWebViewConsole_WBWebViewConsoleDefines_h 14 | #define WBWebViewConsole_WBWebViewConsoleDefines_h 15 | 16 | #import "WBWebViewConsole.h" 17 | 18 | inline static NSBundle * WBWebBrowserConsoleBundle() 19 | { 20 | return [NSBundle bundleWithPath:[NSString stringWithFormat:@"%@/%@", [NSBundle bundleForClass:[WBWebViewConsole class]].bundlePath, @"WBWebBrowserConsole.bundle"]]; 21 | } 22 | 23 | #endif 24 | -------------------------------------------------------------------------------- /WBWebViewConsole/Views/WBWebViewConsoleInputView.h: -------------------------------------------------------------------------------- 1 | // 2 | // WBWebViewConsoleInputView.h 3 | // Weibo 4 | // 5 | // Created by Wutian on 14/7/24. 6 | // 7 | // Copyright (c) 2014-present, Weibo, Corp. 8 | // All rights reserved. 9 | // 10 | // This source code is licensed under the BSD-style license found in the 11 | // LICENSE file in the root directory of this source tree. 12 | // 13 | 14 | #import 15 | #import "WBTextView.h" 16 | 17 | @class WBWebViewConsole; 18 | @protocol WBWebViewConsoleInputViewDelegate; 19 | 20 | @interface WBWebViewConsoleInputView : UIView 21 | 22 | @property (nonatomic, weak) id delegate; 23 | 24 | @property (nonatomic, strong) NSString * text; 25 | @property (nonatomic, strong, readonly) WBTextView * textView; 26 | @property (nonatomic, strong) WBWebViewConsole * console; 27 | 28 | - (void)setFont:(UIFont *)font; 29 | 30 | @property (nonatomic, assign, readonly) CGFloat desiredHeight; 31 | 32 | - (void)completePromptWithSuggestion:(NSString *)suggestion; 33 | 34 | @end 35 | 36 | @protocol WBWebViewConsoleInputViewDelegate 37 | 38 | - (void)consoleInputViewHeightChanged:(WBWebViewConsoleInputView *)inputView; 39 | - (void)consoleInputViewDidBeginEditing:(WBWebViewConsoleInputView *)inputView; 40 | - (void)consoleInputView:(WBWebViewConsoleInputView *)inputView didCommitCommand:(NSString *)command; 41 | 42 | @end 43 | -------------------------------------------------------------------------------- /WBWebViewConsole/Views/WBWebViewConsoleInputViewActionButton.h: -------------------------------------------------------------------------------- 1 | // 2 | // WBWebViewConsoleInputViewActionButton.h 3 | // Weibo 4 | // 5 | // Created by Wutian on 14/7/28. 6 | // 7 | // Copyright (c) 2014-present, Weibo, Corp. 8 | // All rights reserved. 9 | // 10 | // This source code is licensed under the BSD-style license found in the 11 | // LICENSE file in the root directory of this source tree. 12 | // 13 | 14 | #import 15 | 16 | @interface WBWebViewConsoleInputViewActionButton : UIView 17 | 18 | @property (nonatomic, weak) UIResponder * responder; 19 | 20 | @property (nonatomic, assign) SEL previousHistorySelector; 21 | @property (nonatomic, assign) SEL nextHistorySelector; 22 | @property (nonatomic, assign) SEL dismissKeyboardSelector; 23 | @property (nonatomic, assign) SEL newlineSelector; 24 | 25 | @end -------------------------------------------------------------------------------- /WBWebViewConsole/Views/WBWebViewConsoleInputViewActionButton.m: -------------------------------------------------------------------------------- 1 | // 2 | // WBWebViewConsoleInputViewActionButton.m 3 | // Weibo 4 | // 5 | // Created by Wutian on 14/7/28. 6 | // 7 | // Copyright (c) 2014-present, Weibo, Corp. 8 | // All rights reserved. 9 | // 10 | // This source code is licensed under the BSD-style license found in the 11 | // LICENSE file in the root directory of this source tree. 12 | // 13 | 14 | #import "WBWebViewConsoleInputViewActionButton.h" 15 | #import "UIColor+WBTHelpers.h" 16 | #import "UIView+WBTSizes.h" 17 | #import "WBWebViewConsoleDefines.h" 18 | 19 | @interface WBWebViewConsoleInputActionPanel : UIImageView 20 | 21 | @property (nonatomic, weak) WBWebViewConsoleInputViewActionButton * actionButton; 22 | 23 | - (void)reloadData; 24 | 25 | @property (nonatomic, strong) NSArray * buttons; 26 | 27 | - (id)initWithActionButton:(WBWebViewConsoleInputViewActionButton *)actionButton; 28 | 29 | - (void)highlightPoint:(CGPoint)point; 30 | - (void)selectPoint:(CGPoint)point; 31 | 32 | @end 33 | 34 | @interface WBWebViewConsoleInputViewActionButton () 35 | 36 | @property (nonatomic, strong) UIImageView * iconImageView; 37 | @property (nonatomic, strong) WBWebViewConsoleInputActionPanel * actionPanel; 38 | 39 | @end 40 | 41 | @implementation WBWebViewConsoleInputViewActionButton 42 | 43 | - (void)dealloc 44 | { 45 | 46 | [_actionPanel setActionButton:nil]; 47 | } 48 | 49 | - (id)initWithFrame:(CGRect)frame 50 | { 51 | if (self = [super initWithFrame:frame]) 52 | { 53 | self.iconImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"userinput_actions.png" inBundle:WBWebBrowserConsoleBundle() compatibleWithTraitCollection:nil]]; 54 | self.iconImageView.contentMode = UIViewContentModeCenter; 55 | 56 | [self addSubview:self.iconImageView]; 57 | 58 | { 59 | UIPanGestureRecognizer * gesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(panGesture:)]; 60 | [self addGestureRecognizer:gesture]; 61 | } 62 | 63 | { 64 | UILongPressGestureRecognizer * gesture = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(panGesture:)]; 65 | gesture.minimumPressDuration = 0.0; 66 | [self addGestureRecognizer:gesture]; 67 | } 68 | } 69 | return self; 70 | } 71 | 72 | - (void)layoutSubviews 73 | { 74 | [super layoutSubviews]; 75 | 76 | self.iconImageView.frame = self.bounds; 77 | } 78 | 79 | - (void)panGesture:(UIGestureRecognizer *)gestureRecognizer 80 | { 81 | UIGestureRecognizerState state = gestureRecognizer.state; 82 | CGPoint point = [gestureRecognizer locationInView:self.actionPanel]; 83 | 84 | switch (state) 85 | { 86 | case UIGestureRecognizerStateBegan: 87 | { 88 | [self showActionPanel]; 89 | break; 90 | } 91 | case UIGestureRecognizerStateChanged: 92 | { 93 | [self.actionPanel highlightPoint:point]; 94 | break; 95 | } 96 | case UIGestureRecognizerStateEnded: 97 | { 98 | [self.actionPanel selectPoint:point]; 99 | [self hideActionPanel]; 100 | break; 101 | } 102 | case UIGestureRecognizerStateCancelled: 103 | case UIGestureRecognizerStateFailed: 104 | { 105 | [self hideActionPanel]; 106 | break; 107 | } 108 | default: 109 | break; 110 | } 111 | } 112 | 113 | #pragma mark - Action Panel 114 | 115 | - (WBWebViewConsoleInputActionPanel *)actionPanel 116 | { 117 | if (!_actionPanel) 118 | { 119 | _actionPanel = [[WBWebViewConsoleInputActionPanel alloc] initWithActionButton:self]; 120 | _actionPanel.layer.zPosition = 10; 121 | } 122 | return _actionPanel; 123 | } 124 | 125 | - (void)showActionPanel 126 | { 127 | if (!self.actionPanel.superview) 128 | { 129 | CGRect frame = CGRectMake(0, 0, 140, 198); 130 | frame.origin.x = self.wbtWidth - frame.size.width + 5; 131 | frame.origin.y = self.wbtHeight - frame.size.height + 10; 132 | 133 | frame = [self.window convertRect:frame fromView:self]; 134 | 135 | _actionPanel.frame = frame; 136 | 137 | [self.window addSubview:_actionPanel]; 138 | } 139 | 140 | [_actionPanel reloadData]; 141 | } 142 | 143 | - (void)hideActionPanel 144 | { 145 | [_actionPanel removeFromSuperview]; 146 | } 147 | 148 | @end 149 | 150 | @interface WBWebViewConsoleInputActionPanelButton : UIView 151 | 152 | @property (nonatomic, assign) SEL selector; 153 | 154 | @property (nonatomic, strong) UILabel * textLabel; 155 | @property (nonatomic, strong) UIView * seperatorView; 156 | 157 | @property (nonatomic, assign) BOOL highlighted; 158 | @property (nonatomic, assign) BOOL disabled; 159 | 160 | @end 161 | 162 | @interface WBWebViewConsoleInputActionPanel () 163 | 164 | @property (nonatomic, strong) UIView * buttonContainerView; // for corner radius 165 | 166 | @end 167 | 168 | @implementation WBWebViewConsoleInputActionPanel 169 | 170 | 171 | - (id)initWithActionButton:(WBWebViewConsoleInputViewActionButton *)actionButton 172 | { 173 | if (self = [super initWithFrame:CGRectZero]) 174 | { 175 | self.actionButton = actionButton; 176 | 177 | self.image = [[UIImage imageNamed:@"userinput_action_panel.png" inBundle:WBWebBrowserConsoleBundle() compatibleWithTraitCollection:nil] stretchableImageWithLeftCapWidth:25 topCapHeight:25]; 178 | 179 | NSMutableArray * buttons = [NSMutableArray array]; 180 | 181 | { 182 | WBWebViewConsoleInputActionPanelButton * button = [[WBWebViewConsoleInputActionPanelButton alloc] initWithFrame:CGRectZero]; 183 | 184 | button.textLabel.text = NSLocalizedString(@"Toggle Keyboard", nil); 185 | button.selector = self.actionButton.dismissKeyboardSelector; 186 | 187 | [buttons addObject:button]; 188 | } 189 | 190 | { 191 | WBWebViewConsoleInputActionPanelButton * button = [[WBWebViewConsoleInputActionPanelButton alloc] initWithFrame:CGRectZero]; 192 | 193 | button.textLabel.text = NSLocalizedString(@"Next", nil); 194 | button.selector = self.actionButton.nextHistorySelector; 195 | 196 | [buttons addObject:button]; 197 | } 198 | 199 | { 200 | WBWebViewConsoleInputActionPanelButton * button = [[WBWebViewConsoleInputActionPanelButton alloc] initWithFrame:CGRectZero]; 201 | 202 | button.textLabel.text = NSLocalizedString(@"Previous", nil); 203 | button.selector = self.actionButton.previousHistorySelector; 204 | 205 | [buttons addObject:button]; 206 | } 207 | 208 | { 209 | WBWebViewConsoleInputActionPanelButton * button = [[WBWebViewConsoleInputActionPanelButton alloc] initWithFrame:CGRectZero]; 210 | 211 | button.textLabel.text = NSLocalizedString(@"New Line", nil); 212 | button.selector = self.actionButton.newlineSelector; 213 | button.seperatorView.hidden = YES; 214 | 215 | [buttons addObject:button]; 216 | } 217 | 218 | self.buttons = buttons; 219 | 220 | self.buttonContainerView = [[UIView alloc] initWithFrame:self.bounds]; 221 | self.buttonContainerView.layer.masksToBounds = YES; 222 | self.buttonContainerView.layer.cornerRadius = 6.0; 223 | 224 | [self addSubview:self.buttonContainerView]; 225 | } 226 | return self; 227 | } 228 | 229 | - (void)reloadData 230 | { 231 | [self layoutSubviews]; 232 | 233 | CGFloat width = self.buttonContainerView.wbtWidth; 234 | CGFloat height = 36; 235 | CGFloat baseY = 0; 236 | 237 | for (WBWebViewConsoleInputActionPanelButton * button in _buttons) 238 | { 239 | [_buttonContainerView addSubview:button]; 240 | 241 | button.frame = CGRectMake(0, baseY, width, height); 242 | baseY += height; 243 | 244 | button.disabled = ![self.actionButton.responder canPerformAction:button.selector withSender:self.actionButton]; 245 | button.highlighted = NO; 246 | 247 | [button layoutSubviews]; 248 | } 249 | } 250 | 251 | - (void)layoutSubviews 252 | { 253 | [super layoutSubviews]; 254 | 255 | self.buttonContainerView.frame = UIEdgeInsetsInsetRect(self.bounds, UIEdgeInsetsMake(4, 10, 45, 10)); 256 | } 257 | 258 | - (void)highlightPoint:(CGPoint)point 259 | { 260 | point = [_buttonContainerView convertPoint:point fromView:self]; 261 | 262 | for (WBWebViewConsoleInputActionPanelButton * button in _buttons) 263 | { 264 | button.highlighted = (CGRectContainsPoint(button.frame, point)); 265 | } 266 | } 267 | 268 | - (void)selectPoint:(CGPoint)point 269 | { 270 | point = [_buttonContainerView convertPoint:point fromView:self]; 271 | 272 | for (WBWebViewConsoleInputActionPanelButton * button in _buttons) 273 | { 274 | button.highlighted = NO; 275 | 276 | if (CGRectContainsPoint(button.frame, point)) 277 | { 278 | if (!button.disabled) 279 | { 280 | [[UIApplication sharedApplication] sendAction:button.selector to:self.actionButton.responder from:self.actionButton forEvent:nil]; 281 | } 282 | return; 283 | } 284 | } 285 | } 286 | 287 | @end 288 | 289 | @implementation WBWebViewConsoleInputActionPanelButton 290 | 291 | 292 | - (id)initWithFrame:(CGRect)frame 293 | { 294 | if (self = [super initWithFrame:frame]) 295 | { 296 | self.textLabel = [[UILabel alloc] initWithFrame:self.bounds]; 297 | self.textLabel.backgroundColor = [UIColor clearColor]; 298 | self.textLabel.font = [UIFont systemFontOfSize:14]; 299 | self.textLabel.textColor = [UIColor blackColor]; 300 | self.textLabel.textAlignment = NSTextAlignmentCenter; 301 | [self addSubview:self.textLabel]; 302 | 303 | self.seperatorView = [[UIView alloc] initWithFrame:CGRectZero]; 304 | self.seperatorView.backgroundColor = [UIColor wbt_colorWithHexValue:0xDFDFDF alpha:1.0]; 305 | [self addSubview:self.seperatorView]; 306 | } 307 | return self; 308 | } 309 | 310 | - (void)layoutSubviews 311 | { 312 | [super layoutSubviews]; 313 | 314 | self.textLabel.frame = self.bounds; 315 | 316 | CGFloat height = 1 / [UIScreen mainScreen].scale; 317 | 318 | self.seperatorView.frame = CGRectMake(0, self.wbtHeight - height, self.wbtWidth, height); 319 | } 320 | 321 | - (void)setHighlighted:(BOOL)highlighted 322 | { 323 | if (_highlighted != highlighted) 324 | { 325 | _highlighted = highlighted; 326 | 327 | [self updateStates]; 328 | } 329 | } 330 | 331 | - (void)setDisabled:(BOOL)disabled 332 | { 333 | if (_disabled != disabled) 334 | { 335 | _disabled = disabled; 336 | 337 | [self updateStates]; 338 | } 339 | } 340 | 341 | - (void)updateStates 342 | { 343 | self.backgroundColor = (!_disabled && _highlighted) ? [UIColor wbt_colorWithHexValue:0x0076FF alpha:1.0] : [UIColor clearColor]; 344 | self.textLabel.textColor = _disabled ? [UIColor wbt_colorWithHexValue:0xBDBDBD alpha:1.0] : (_highlighted ? [UIColor whiteColor] : [UIColor wbt_colorWithHexValue:0x565656 alpha:1.0]); 345 | } 346 | 347 | @end 348 | -------------------------------------------------------------------------------- /WBWebViewConsole/Views/WBWebViewConsoleMessageCell.h: -------------------------------------------------------------------------------- 1 | // 2 | // WBWebViewConsoleMessageCell.h 3 | // Weibo 4 | // 5 | // Created by Wutian on 14/7/23. 6 | // 7 | // Copyright (c) 2014-present, Weibo, Corp. 8 | // All rights reserved. 9 | // 10 | // This source code is licensed under the BSD-style license found in the 11 | // LICENSE file in the root directory of this source tree. 12 | // 13 | 14 | #import 15 | 16 | @class WBWebViewConsoleMessage; 17 | @protocol WBWebViewConsoleMessageCellDelegate; 18 | 19 | @interface WBWebViewConsoleMessageCell : UITableViewCell 20 | 21 | @property (nonatomic, strong) WBWebViewConsoleMessage * message; 22 | @property (nonatomic, assign) BOOL cleared; 23 | 24 | @property (nonatomic, weak) id delegate; 25 | 26 | + (UIFont *)messageFont; 27 | + (CGFloat)rowHeightOfDataObject:(WBWebViewConsoleMessage *)message tableView:(UITableView *)tableView; 28 | 29 | @end 30 | 31 | @protocol WBWebViewConsoleMessageCellDelegate 32 | 33 | - (void)consoleMessageCell:(WBWebViewConsoleMessageCell *)cell copyMessageToInputView:(WBWebViewConsoleMessage *)message; 34 | 35 | @end -------------------------------------------------------------------------------- /WBWebViewConsole/Views/WBWebViewConsoleMessageCell.m: -------------------------------------------------------------------------------- 1 | // 2 | // WBWebViewConsoleMessageCell.m 3 | // Weibo 4 | // 5 | // Created by Wutian on 14/7/23. 6 | // 7 | // Copyright (c) 2014-present, Weibo, Corp. 8 | // All rights reserved. 9 | // 10 | // This source code is licensed under the BSD-style license found in the 11 | // LICENSE file in the root directory of this source tree. 12 | // 13 | 14 | #import "WBWebViewConsoleMessageCell.h" 15 | #import "WBWebViewConsoleMessage.h" 16 | #import "UIDevice+WBTHelpers.h" 17 | #import "UIColor+WBTHelpers.h" 18 | #import "UIView+WBTSizes.h" 19 | #import "WBWebViewConsoleDefines.h" 20 | 21 | @interface WBWebViewConsoleMessageCell () 22 | 23 | @property (nonatomic, strong) UILabel * messageLabel; 24 | @property (nonatomic, strong) UILabel * callerLabel; 25 | 26 | @property (nonatomic, strong) UIImageView * iconImageView; 27 | 28 | @end 29 | 30 | @implementation WBWebViewConsoleMessageCell 31 | 32 | - (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier 33 | { 34 | if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) 35 | { 36 | // self.selectionStyle = UITableViewCellSelectionStyleNone; 37 | UIView * selectionView = [[UIView alloc] initWithFrame:CGRectZero]; 38 | selectionView.backgroundColor = [UIColor colorWithWhite:0.95 alpha:1.0]; 39 | self.selectedBackgroundView = selectionView; 40 | 41 | self.messageLabel = [[UILabel alloc] initWithFrame:CGRectZero]; 42 | self.messageLabel.font = [[self class] messageFont]; 43 | self.messageLabel.textColor = [UIColor blackColor]; 44 | self.messageLabel.backgroundColor = [UIColor clearColor]; 45 | self.messageLabel.numberOfLines = 0; 46 | self.messageLabel.wbtWidth = [UIScreen mainScreen].bounds.size.width - 40; 47 | // self.messageLabel.lineBreakMode = NSLineBreakByCharWrapping; 48 | 49 | [self.contentView addSubview:self.messageLabel]; 50 | 51 | self.iconImageView = [[UIImageView alloc] initWithFrame:CGRectZero]; 52 | self.iconImageView.contentMode = UIViewContentModeCenter; 53 | self.iconImageView.backgroundColor = [UIColor clearColor]; 54 | 55 | [self.contentView addSubview:self.iconImageView]; 56 | 57 | self.callerLabel = [[UILabel alloc] initWithFrame:CGRectZero]; 58 | self.callerLabel.font = [[self class] messageFont]; 59 | self.callerLabel.backgroundColor = [UIColor clearColor]; 60 | self.callerLabel.numberOfLines = 0; 61 | self.callerLabel.wbtWidth = [UIScreen mainScreen].bounds.size.width - 40; 62 | // self.callerLabel.lineBreakMode = NSLineBreakByCharWrapping; 63 | 64 | [self.contentView addSubview:self.callerLabel]; 65 | } 66 | return self; 67 | } 68 | 69 | - (BOOL)usesCustomSelectedBackgroundView 70 | { 71 | return NO; 72 | } 73 | 74 | + (UIFont *)messageFont 75 | { 76 | if (WBAvalibleOS(7)) 77 | { 78 | return [UIFont fontWithName:@"Menlo" size:11]; 79 | } 80 | return [UIFont fontWithName:@"Courier" size:11]; 81 | } 82 | 83 | + (CGFloat)rowHeightOfDataObject:(WBWebViewConsoleMessage *)message tableView:(UITableView *)tableView 84 | { 85 | if (message.type == WBWebViewConsoleMessageTypeClear) 86 | { 87 | return 0; 88 | } 89 | 90 | CGFloat width = tableView.frame.size.width - 40; 91 | 92 | CGFloat height = 5; // top padding 93 | 94 | UIFont * font = [self messageFont]; 95 | 96 | CGFloat messageHeight = ceil([message.message sizeWithFont:font constrainedToSize:CGSizeMake(width, CGFLOAT_MAX) lineBreakMode:NSLineBreakByWordWrapping].height); 97 | height += messageHeight; 98 | 99 | NSString * caller = message.caller; 100 | if (caller.length) 101 | { 102 | height += 2; // padding 103 | 104 | CGFloat callerHeight = ceil([message.caller sizeWithFont:font constrainedToSize:CGSizeMake(width, CGFLOAT_MAX) lineBreakMode:NSLineBreakByWordWrapping].height); 105 | height += callerHeight; 106 | } 107 | 108 | height += 5; // bottom padding 109 | 110 | return height; 111 | } 112 | 113 | - (void)layoutSubviews 114 | { 115 | [super layoutSubviews]; 116 | 117 | self.messageLabel.frame = CGRectMake(30, 5, self.frame.size.width - 40, self.messageLabel.wbtHeight); 118 | self.callerLabel.frame = CGRectMake(30, self.messageLabel.wbtBottom + 2, self.frame.size.width - 40, self.callerLabel.wbtHeight); 119 | self.iconImageView.frame = CGRectMake(0, 0, 30, 24); 120 | } 121 | 122 | //- (void)setHighlighted:(BOOL)highlighted animated:(BOOL)animated 123 | //{ 124 | // [super setHighlighted:highlighted animated:animated]; 125 | // 126 | // self.contentView.backgroundColor = highlighted ? [UIColor colorWithWhite:0.95 alpha:1.0] : [UIColor whiteColor]; 127 | //} 128 | 129 | - (void)setMessage:(WBWebViewConsoleMessage *)message 130 | { 131 | if (_message != message) 132 | { 133 | _message = message; 134 | 135 | self.messageLabel.text = message.message; 136 | self.callerLabel.text = message.caller; 137 | 138 | [self.messageLabel sizeToFit]; 139 | [self.callerLabel sizeToFit]; 140 | 141 | [self updateMessageStyle]; 142 | } 143 | } 144 | 145 | - (void)setCleared:(BOOL)cleared 146 | { 147 | if (_cleared != cleared) 148 | { 149 | _cleared = cleared; 150 | 151 | [self updateMessageStyle]; 152 | } 153 | } 154 | 155 | - (void)updateMessageStyle 156 | { 157 | UIColor * color = nil; 158 | NSString * iconName = nil; 159 | UIColor * callerColor = [UIColor grayColor]; 160 | 161 | if (_cleared) 162 | { 163 | color = [UIColor colorWithWhite:0.0 alpha:0.2]; 164 | callerColor = [UIColor colorWithWhite:0.0 alpha:0.15]; 165 | } 166 | else 167 | { 168 | WBWebViewConsoleMessageLevel level = _message.level; 169 | WBWebViewConsoleMessageSource source = _message.source; 170 | 171 | if (source == WBWebViewConsoleMessageSourceUserCommand) 172 | { 173 | color = RGBCOLOR(77, 153, 255); 174 | iconName = @"userinput_prompt_previous"; 175 | } 176 | else if (source == WBWebViewConsoleMessageSourceUserCommandResult) 177 | { 178 | color = RGBCOLOR(0, 80, 255); 179 | iconName = @"userinput_result"; 180 | } 181 | else 182 | { 183 | switch (level) { 184 | case WBWebViewConsoleMessageLevelDebug: 185 | color = [UIColor blueColor]; 186 | iconName = @"debug_icon"; 187 | break; 188 | case WBWebViewConsoleMessageLevelError: 189 | color = [UIColor redColor]; 190 | iconName = @"error_icon"; 191 | break; 192 | case WBWebViewConsoleMessageLevelWarning: 193 | color = [UIColor blackColor]; 194 | iconName = @"issue_icon"; 195 | if (source == WBWebViewConsoleMessageSourceNavigation) 196 | { 197 | iconName = @"navigation_issue_icon"; 198 | color = RGBCOLOR(246, 187, 14); 199 | } 200 | break; 201 | case WBWebViewConsoleMessageLevelNone: 202 | color = [UIColor colorWithWhite:0.0 alpha:0.2]; 203 | break; 204 | case WBWebViewConsoleMessageLevelInfo: 205 | color = [UIColor colorWithWhite:0.0 alpha:0.5]; 206 | iconName = @"info_icon"; 207 | break; 208 | case WBWebViewConsoleMessageLevelSuccess: 209 | color = RGBCOLOR(0, 148, 10); 210 | iconName = @"success_icon"; 211 | if (source == WBWebViewConsoleMessageSourceNavigation) 212 | { 213 | iconName = @"navigation_success_icon"; 214 | } 215 | break; 216 | case WBWebViewConsoleMessageLevelLog: 217 | default: 218 | color = [UIColor blackColor]; 219 | break; 220 | } 221 | } 222 | 223 | if ([_message.message isEqual:@"undefined"] || 224 | [_message.message isEqual:@"null"]) 225 | { 226 | if (source != WBWebViewConsoleMessageSourceUserCommand) 227 | { 228 | color = [UIColor colorWithWhite:0.0 alpha:0.3]; 229 | callerColor = [UIColor colorWithWhite:0.0 alpha:0.2]; 230 | } 231 | } 232 | } 233 | 234 | self.messageLabel.textColor = color; 235 | self.callerLabel.textColor = callerColor; 236 | self.iconImageView.image = nil; 237 | 238 | if (iconName) 239 | { 240 | self.iconImageView.image = [UIImage imageNamed:iconName inBundle:WBWebBrowserConsoleBundle() compatibleWithTraitCollection:nil]; 241 | } 242 | } 243 | 244 | - (BOOL)canPerformAction:(SEL)action withSender:(id)sender 245 | { 246 | return (action == @selector(copy:) || action == @selector(copyToInputView:)); 247 | } 248 | 249 | - (void)copyToInputView:(id)sender 250 | { 251 | if ([_delegate respondsToSelector:@selector(consoleMessageCell:copyMessageToInputView:)]) 252 | { 253 | [_delegate consoleMessageCell:self copyMessageToInputView:_message]; 254 | } 255 | } 256 | 257 | @end 258 | -------------------------------------------------------------------------------- /WBWebViewConsoleTests/Base/WBTestsUIWebView.h: -------------------------------------------------------------------------------- 1 | // 2 | // WBTestsUIWebView.h 3 | // WBWebViewConsole 4 | // 5 | // Created by 吴天 on 15/3/1. 6 | // 7 | // Copyright (c) 2014-present, Weibo, Corp. 8 | // All rights reserved. 9 | // 10 | // This source code is licensed under the BSD-style license found in the 11 | // LICENSE file in the root directory of this source tree. 12 | // 13 | 14 | #import 15 | #import "WBWebView.h" 16 | 17 | @interface WBTestsUIWebView : UIWebView 18 | 19 | - (BOOL)syncLoadHTMLString:(NSString *)html error:(NSError **)error; 20 | 21 | @end 22 | -------------------------------------------------------------------------------- /WBWebViewConsoleTests/Base/WBTestsUIWebView.m: -------------------------------------------------------------------------------- 1 | // 2 | // WBTestsUIWebView.m 3 | // WBWebViewConsole 4 | // 5 | // Created by 吴天 on 15/3/1. 6 | // 7 | // Copyright (c) 2014-present, Weibo, Corp. 8 | // All rights reserved. 9 | // 10 | // This source code is licensed under the BSD-style license found in the 11 | // LICENSE file in the root directory of this source tree. 12 | // 13 | 14 | #import "WBTestsUIWebView.h" 15 | #import "WBWebViewConsole.h" 16 | 17 | @class WBTestsUIWebView; 18 | 19 | @interface NSError (WBTestsUIWebView) 20 | 21 | - (BOOL)wb_isWebViewMainFrameError:(WBTestsUIWebView *)webView; 22 | - (BOOL)wb_shouldDisplayToWebViewUsers:(WBTestsUIWebView *)webView; 23 | 24 | @end 25 | 26 | @interface WBTestsUIWebView () 27 | 28 | @property (nonatomic, strong) NSMutableArray * userScripts; 29 | @property (nonatomic, strong) WBWebViewJSBridge * JSBridge; 30 | @property (nonatomic, strong) WBWebViewConsole * console; 31 | 32 | @property (nonatomic, strong) NSURLRequest * loadingRequest; 33 | @property (nonatomic, strong) NSError * loadingError; 34 | @property (nonatomic, assign) NSInteger loadingCount; 35 | 36 | @property (nonatomic, assign) BOOL pendingRequest; 37 | 38 | @end 39 | 40 | @implementation WBTestsUIWebView 41 | 42 | - (instancetype)init 43 | { 44 | if (self = [self initWithFrame:CGRectMake(0, 0, 10, 10)]) { 45 | 46 | } 47 | return self; 48 | } 49 | 50 | - (instancetype)initWithFrame:(CGRect)frame 51 | { 52 | if (self = [super initWithFrame:frame]) { 53 | self.delegate = self; 54 | self.JSBridge = [[WBWebViewJSBridge alloc] initWithWebView:self]; 55 | self.console = [[WBWebViewConsole alloc] initWithWebView:self]; 56 | } 57 | return self; 58 | } 59 | 60 | - (NSMutableArray *)userScripts 61 | { 62 | if (!_userScripts) { 63 | _userScripts = [NSMutableArray array]; 64 | } 65 | return _userScripts; 66 | } 67 | 68 | - (void)wb_addUserScript:(WBWebViewUserScript *)userScript 69 | { 70 | if (!userScript) { 71 | return; 72 | } 73 | [self.userScripts addObject:userScript]; 74 | } 75 | 76 | - (void)wb_removeAllUserScripts 77 | { 78 | [_userScripts removeAllObjects]; 79 | } 80 | 81 | - (NSArray *)wb_userScripts 82 | { 83 | return [_userScripts copy]; 84 | } 85 | 86 | - (void)injectUserScripts 87 | { 88 | for (WBWebViewUserScript * script in _userScripts) { 89 | [self stringByEvaluatingJavaScriptFromString:script.source]; 90 | } 91 | } 92 | 93 | - (void)wb_evaluateJavaScript:(NSString *)javaScriptString completionHandler:(void (^)(NSString *, NSError *))completionHandler 94 | { 95 | NSString * result = [self stringByEvaluatingJavaScriptFromString:javaScriptString]; 96 | 97 | if (completionHandler) { 98 | completionHandler(result, nil); 99 | } 100 | } 101 | 102 | - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType 103 | { 104 | BOOL result = YES; 105 | 106 | if ([self.JSBridge handleWebViewRequest:request]) { 107 | result = NO; 108 | } 109 | 110 | if (result) { 111 | self.loadingRequest = request; 112 | } 113 | 114 | return result; 115 | } 116 | 117 | - (void)webViewDidStartLoad:(UIWebView *)webView 118 | { 119 | self.loadingCount++; 120 | } 121 | 122 | - (void)webViewDidFinishLoad:(UIWebView *)webView 123 | { 124 | [self injectUserScripts]; 125 | 126 | self.loadingCount--; 127 | } 128 | 129 | - (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error 130 | { 131 | if ([error wb_shouldDisplayToWebViewUsers:self]) { 132 | self.loadingError = error; 133 | } 134 | self.loadingCount--; 135 | } 136 | 137 | - (void)setLoadingCount:(NSInteger)loadingCount 138 | { 139 | if (_loadingCount != loadingCount) { 140 | _loadingCount = MAX(loadingCount, 0); 141 | 142 | if (loadingCount == 0) { 143 | _pendingRequest = NO; 144 | } 145 | } 146 | } 147 | 148 | - (BOOL)syncLoadHTMLString:(NSString *)html error:(NSError *__autoreleasing *)error 149 | { 150 | self.pendingRequest = YES; 151 | [self loadHTMLString:html baseURL:nil]; 152 | 153 | while (_pendingRequest) { 154 | [[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]; 155 | } 156 | 157 | if (error && _loadingError) { 158 | *error = _loadingError; 159 | } 160 | 161 | return _loadingError == nil; 162 | } 163 | 164 | @end 165 | 166 | @implementation NSError (WBTestsUIWebView) 167 | 168 | - (BOOL)wb_isWebViewMainFrameError:(WBTestsUIWebView *)webView 169 | { 170 | NSString * url = [[self userInfo] objectForKey:NSURLErrorFailingURLStringErrorKey]; 171 | NSString * host = [[NSURL URLWithString:url] host]; 172 | 173 | if (![host isEqual:webView.request.URL.host] && 174 | ![host isEqual:webView.loadingRequest.URL.host]) 175 | { 176 | return NO; 177 | } 178 | 179 | return YES; 180 | } 181 | 182 | - (BOOL)wb_shouldDisplayToWebViewUsers:(WBTestsUIWebView *)webView 183 | { 184 | if (![self wb_isWebViewMainFrameError:webView]) { 185 | return NO; 186 | } 187 | 188 | if ([self.domain isEqual:NSURLErrorDomain]) 189 | { 190 | if (self.code == NSURLErrorCancelled) 191 | { 192 | // interrupted by http redirect, etc. 193 | return NO; 194 | } 195 | 196 | return YES; 197 | } 198 | else if ([self.domain isEqual:@"WebKitErrorDomain"]) 199 | { 200 | if (self.code == 102) 201 | { 202 | // interrupted by urlAction, JSBridge, etc. 203 | return NO; 204 | } 205 | else if (self.code == 204) 206 | { 207 | // interrupted by plugIn, such as movie(music) player 208 | return NO; 209 | } 210 | 211 | return YES; 212 | } 213 | 214 | return NO; 215 | } 216 | 217 | @end 218 | -------------------------------------------------------------------------------- /WBWebViewConsoleTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | com.sina.$(PRODUCT_NAME:rfc1034identifier) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /WBWebViewConsoleTests/WBWebViewJSBridgeTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // WBWebViewJSBridgeTests.m 3 | // WBWebViewConsole 4 | // 5 | // Created by 吴天 on 15/3/1. 6 | // 7 | // Copyright (c) 2014-present, Weibo, Corp. 8 | // All rights reserved. 9 | // 10 | // This source code is licensed under the BSD-style license found in the 11 | // LICENSE file in the root directory of this source tree. 12 | // 13 | 14 | #import 15 | #import 16 | #import "WBTestsUIWebView.h" 17 | #import "WBJSBridgeAction.h" 18 | #import "NSObject+WBJSONKit.h" 19 | 20 | NSString * const WBJSBridgeFinishTestsNotification = @"WBJSBridgeFinishTestsNotification"; 21 | 22 | @interface WBJSBridgeActionGetValueTests : WBJSBridgeAction 23 | 24 | @end 25 | 26 | @interface WBJSBridgeActionFinishTests : WBJSBridgeAction 27 | 28 | @end 29 | 30 | @interface WBWebViewJSBridgeTests : XCTestCase 31 | 32 | @end 33 | 34 | @implementation WBWebViewJSBridgeTests 35 | 36 | - (void)testJSBridgeInjection 37 | { 38 | WBTestsUIWebView * webView = [[WBTestsUIWebView alloc] init]; 39 | 40 | webView.JSBridge.interfaceName = @"JSBridge"; 41 | 42 | XCTAssert([webView syncLoadHTMLString:@"Hello" error:NULL]); 43 | 44 | XCTAssert([[webView stringByEvaluatingJavaScriptFromString:@"typeof window.JSBridge"] isEqualToString:@"object"]); 45 | } 46 | 47 | - (void)testJSBridgeInvokeAndCallback 48 | { 49 | #define QUOTE(...) #__VA_ARGS__ 50 | const char * html_char = QUOTE( 51 | 52 | 53 | 54 | 55 | 88 | ); 89 | #undef QUOTE 90 | 91 | NSString * html = [NSString stringWithUTF8String:html_char]; 92 | 93 | WBTestsUIWebView * webView = [[WBTestsUIWebView alloc] init]; 94 | 95 | webView.JSBridge.interfaceName = @"JSBridge"; 96 | webView.JSBridge.readyEventName = @"JSBridgeReady"; 97 | 98 | XCTAssert([webView syncLoadHTMLString:html error:NULL]); 99 | 100 | [self expectationForNotification:WBJSBridgeFinishTestsNotification object:webView.JSBridge handler:^BOOL(NSNotification *notification) { 101 | 102 | NSString * errorJSON = [webView stringByEvaluatingJavaScriptFromString:@"JSON.stringify(window.jserrors)"]; 103 | NSArray * errors = [errorJSON wb_objectFromJSONString]; 104 | XCTAssertFalse(errors.count, @"errors: %@", errors); 105 | 106 | XCTAssertEqualObjects(notification.userInfo[@"value"], @"weibo.cn"); // see -[WBJSBridgeActionGetValueTests startAction] 107 | 108 | return YES; 109 | }]; 110 | 111 | [self waitForExpectationsWithTimeout:5 handler:NULL]; 112 | } 113 | 114 | @end 115 | 116 | @implementation WBJSBridgeActionGetValueTests 117 | 118 | - (void)startAction 119 | { 120 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ 121 | [self actionSuccessedWithResult:@{@"value": @"weibo.cn"}]; 122 | }); 123 | } 124 | 125 | @end 126 | 127 | @implementation WBJSBridgeActionFinishTests 128 | 129 | - (void)startAction 130 | { 131 | [[NSNotificationCenter defaultCenter] postNotificationName:WBJSBridgeFinishTestsNotification object:self.bridge userInfo:self.message.parameters]; 132 | [self actionSuccessedWithResult:@{}]; 133 | } 134 | 135 | @end 136 | --------------------------------------------------------------------------------