Brion VIBBER has submitted this change and it was merged.

Change subject: Work in progress: JS bridge
......................................................................


Work in progress: JS bridge

Change-Id: I667314bad95e774539a93d17d794169c9cb224fb
---
M Wikipedia-iOS.xcodeproj/project.pbxproj
A Wikipedia-iOS/CommunicationBridge.h
A Wikipedia-iOS/CommunicationBridge.m
M Wikipedia-iOS/ViewController.m
A Wikipedia-iOS/bridge-index.html
A Wikipedia-iOSTests/CommunicationBridgeTests.m
A Wikipedia-iOSTests/NSRunLoop+TimeOutAndFlag.h
A Wikipedia-iOSTests/NSRunLoop+TimeOutAndFlag.m
8 files changed, 373 insertions(+), 39 deletions(-)

Approvals:
  Brion VIBBER: Verified; Looks good to me, approved



diff --git a/Wikipedia-iOS.xcodeproj/project.pbxproj 
b/Wikipedia-iOS.xcodeproj/project.pbxproj
index 3dd962b..71bb59d 100644
--- a/Wikipedia-iOS.xcodeproj/project.pbxproj
+++ b/Wikipedia-iOS.xcodeproj/project.pbxproj
@@ -22,6 +22,12 @@
                D4991465181D51DF00E6073C /* InfoPlist.strings in Resources */ = 
{isa = PBXBuildFile; fileRef = D4991463181D51DF00E6073C /* InfoPlist.strings 
*/; };
                D4991467181D51DF00E6073C /* Wikipedia_iOSTests.m in Sources */ 
= {isa = PBXBuildFile; fileRef = D4991466181D51DF00E6073C /* 
Wikipedia_iOSTests.m */; };
                D4BC22B4181E9E6300CAC673 /* empty.png in Resources */ = {isa = 
PBXBuildFile; fileRef = D4BC22B3181E9E6300CAC673 /* empty.png */; };
+               D4DE203118283FF200148CA2 /* CommunicationBridgeTests.m in 
Sources */ = {isa = PBXBuildFile; fileRef = D4DE203018283FF200148CA2 /* 
CommunicationBridgeTests.m */; };
+               D4DE20341828400C00148CA2 /* CommunicationBridge.m in Sources */ 
= {isa = PBXBuildFile; fileRef = D4DE20331828400C00148CA2 /* 
CommunicationBridge.m */; };
+               D4DE20351828400C00148CA2 /* CommunicationBridge.m in Sources */ 
= {isa = PBXBuildFile; fileRef = D4DE20331828400C00148CA2 /* 
CommunicationBridge.m */; };
+               D4DE20371828518B00148CA2 /* bridge-index.html in Resources */ = 
{isa = PBXBuildFile; fileRef = D4DE20361828518B00148CA2 /* bridge-index.html 
*/; };
+               D4DE20381828518B00148CA2 /* bridge-index.html in Resources */ = 
{isa = PBXBuildFile; fileRef = D4DE20361828518B00148CA2 /* bridge-index.html 
*/; };
+               D4DE203B1828680C00148CA2 /* NSRunLoop+TimeOutAndFlag.m in 
Sources */ = {isa = PBXBuildFile; fileRef = D4DE203A1828680C00148CA2 /* 
NSRunLoop+TimeOutAndFlag.m */; };
                D4EE00B9182443FC0090790F /* PageTitle.m in Sources */ = {isa = 
PBXBuildFile; fileRef = D4EE00B8182443FC0090790F /* PageTitle.m */; };
                D4EE00BA182443FC0090790F /* PageTitle.m in Sources */ = {isa = 
PBXBuildFile; fileRef = D4EE00B8182443FC0090790F /* PageTitle.m */; };
                D4EE00BD1824459D0090790F /* PageTitleTests.m in Sources */ = 
{isa = PBXBuildFile; fileRef = D4EE00BC1824459D0090790F /* PageTitleTests.m */; 
};
@@ -58,6 +64,12 @@
                D4991464181D51DF00E6073C /* en */ = {isa = PBXFileReference; 
lastKnownFileType = text.plist.strings; name = en; path = 
en.lproj/InfoPlist.strings; sourceTree = "<group>"; };
                D4991466181D51DF00E6073C /* Wikipedia_iOSTests.m */ = {isa = 
PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = 
Wikipedia_iOSTests.m; sourceTree = "<group>"; };
                D4BC22B3181E9E6300CAC673 /* empty.png */ = {isa = 
PBXFileReference; lastKnownFileType = image.png; path = empty.png; sourceTree = 
"<group>"; };
+               D4DE203018283FF200148CA2 /* CommunicationBridgeTests.m */ = 
{isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = 
sourcecode.c.objc; path = CommunicationBridgeTests.m; sourceTree = "<group>"; };
+               D4DE20321828400C00148CA2 /* CommunicationBridge.h */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = 
CommunicationBridge.h; sourceTree = "<group>"; };
+               D4DE20331828400C00148CA2 /* CommunicationBridge.m */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path 
= CommunicationBridge.m; sourceTree = "<group>"; };
+               D4DE20361828518B00148CA2 /* bridge-index.html */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = 
"bridge-index.html"; sourceTree = "<group>"; };
+               D4DE20391828680C00148CA2 /* NSRunLoop+TimeOutAndFlag.h */ = 
{isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; 
path = "NSRunLoop+TimeOutAndFlag.h"; sourceTree = "<group>"; };
+               D4DE203A1828680C00148CA2 /* NSRunLoop+TimeOutAndFlag.m */ = 
{isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = 
sourcecode.c.objc; path = "NSRunLoop+TimeOutAndFlag.m"; sourceTree = "<group>"; 
};
                D4EE00B7182443FC0090790F /* PageTitle.h */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = 
PageTitle.h; path = "mw-support/PageTitle.h"; sourceTree = "<group>"; };
                D4EE00B8182443FC0090790F /* PageTitle.m */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name 
= PageTitle.m; path = "mw-support/PageTitle.m"; sourceTree = "<group>"; };
                D4EE00BC1824459D0090790F /* PageTitleTests.m */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name 
= PageTitleTests.m; path = "mw-support/PageTitleTests.m"; sourceTree = 
"<group>"; };
@@ -129,6 +141,9 @@
                                D4991453181D51DE00E6073C /* Images.xcassets */,
                                D499143F181D51DE00E6073C /* Supporting Files */,
                                D4EE00BB182445670090790F /* mw-support */,
+                               D4DE20321828400C00148CA2 /* 
CommunicationBridge.h */,
+                               D4DE20331828400C00148CA2 /* 
CommunicationBridge.m */,
+                               D4DE20361828518B00148CA2 /* bridge-index.html 
*/,
                        );
                        path = "Wikipedia-iOS";
                        sourceTree = "<group>";
@@ -150,6 +165,9 @@
                                D4991466181D51DF00E6073C /* 
Wikipedia_iOSTests.m */,
                                D4991461181D51DF00E6073C /* Supporting Files */,
                                D4EE00BC1824459D0090790F /* PageTitleTests.m */,
+                               D4DE203018283FF200148CA2 /* 
CommunicationBridgeTests.m */,
+                               D4DE20391828680C00148CA2 /* 
NSRunLoop+TimeOutAndFlag.h */,
+                               D4DE203A1828680C00148CA2 /* 
NSRunLoop+TimeOutAndFlag.m */,
                        );
                        path = "Wikipedia-iOSTests";
                        sourceTree = "<group>";
@@ -253,6 +271,7 @@
                        files = (
                                D4991454181D51DE00E6073C /* Images.xcassets in 
Resources */,
                                D499144C181D51DE00E6073C /* 
Main_iPhone.storyboard in Resources */,
+                               D4DE20371828518B00148CA2 /* bridge-index.html 
in Resources */,
                                D4991443181D51DE00E6073C /* InfoPlist.strings 
in Resources */,
                                D4BC22B4181E9E6300CAC673 /* empty.png in 
Resources */,
                        );
@@ -262,6 +281,7 @@
                        isa = PBXResourcesBuildPhase;
                        buildActionMask = 2147483647;
                        files = (
+                               D4DE20381828518B00148CA2 /* bridge-index.html 
in Resources */,
                                D4991465181D51DF00E6073C /* InfoPlist.strings 
in Resources */,
                        );
                        runOnlyForDeploymentPostprocessing = 0;
@@ -273,6 +293,7 @@
                        isa = PBXSourcesBuildPhase;
                        buildActionMask = 2147483647;
                        files = (
+                               D4DE20341828400C00148CA2 /* 
CommunicationBridge.m in Sources */,
                                D4991452181D51DE00E6073C /* ViewController.m in 
Sources */,
                                D4EE00B9182443FC0090790F /* PageTitle.m in 
Sources */,
                                D4991449181D51DE00E6073C /* AppDelegate.m in 
Sources */,
@@ -284,9 +305,12 @@
                        isa = PBXSourcesBuildPhase;
                        buildActionMask = 2147483647;
                        files = (
+                               D4DE20351828400C00148CA2 /* 
CommunicationBridge.m in Sources */,
                                D4991467181D51DF00E6073C /* 
Wikipedia_iOSTests.m in Sources */,
+                               D4DE203B1828680C00148CA2 /* 
NSRunLoop+TimeOutAndFlag.m in Sources */,
                                D4EE00BD1824459D0090790F /* PageTitleTests.m in 
Sources */,
                                D4EE00BA182443FC0090790F /* PageTitle.m in 
Sources */,
+                               D4DE203118283FF200148CA2 /* 
CommunicationBridgeTests.m in Sources */,
                        );
                        runOnlyForDeploymentPostprocessing = 0;
                };
diff --git a/Wikipedia-iOS/CommunicationBridge.h 
b/Wikipedia-iOS/CommunicationBridge.h
new file mode 100644
index 0000000..af8e26b
--- /dev/null
+++ b/Wikipedia-iOS/CommunicationBridge.h
@@ -0,0 +1,27 @@
+//
+//  CommunicationBridge.h
+//  Wikipedia-iOS
+//
+//  Created by Brion on 11/4/13.
+//  Copyright (c) 2013 Wikimedia Foundation. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+typedef void (^JSListener)(NSString *, NSDictionary *);
+
+@interface CommunicationBridge : NSObject <UIWebViewDelegate>
+
+@property BOOL isDOMReady;
+
+// Public methods
+- (CommunicationBridge *)initWithWebView:(UIWebView *)webView;
+- (void)addListener:(NSString *)messageType withBlock:(JSListener)block;
+
+// Methods reserved for internal and testing
+- (NSMutableArray *)listenersForMessageType:(NSString *)messageType;
+- (void)fireEvent:(NSString *)messageType withPayload:(NSDictionary *)payload;
+- (BOOL)isBridgeURL:(NSURL *)url;
+- (NSDictionary *)extractBridgePayload:(NSURL *)url;
+
+@end
diff --git a/Wikipedia-iOS/CommunicationBridge.m 
b/Wikipedia-iOS/CommunicationBridge.m
new file mode 100644
index 0000000..3f06dbc
--- /dev/null
+++ b/Wikipedia-iOS/CommunicationBridge.m
@@ -0,0 +1,117 @@
+//
+//  CommunicationBridge.m
+//  Wikipedia-iOS
+//
+//  Created by Brion on 11/4/13.
+//  Copyright (c) 2013 Wikimedia Foundation. All rights reserved.
+//
+
+#import "CommunicationBridge.h"
+
+@implementation CommunicationBridge {
+    UIWebView *webView;
+    NSMutableDictionary *listenersByEvent;
+}
+
+#pragma mark Public methods
+
+- (CommunicationBridge *)initWithWebView:(UIWebView *)targetWebView
+{
+    self = [super init];
+    if (self) {
+        webView = targetWebView;
+        listenersByEvent = [[NSMutableDictionary alloc] init];
+    }
+    [self setupWebView];
+    return self;
+}
+
+- (void)addListener:(NSString *)messageType withBlock:(JSListener)block
+{
+    NSMutableArray *listeners = [self listenersForMessageType:messageType];
+    if (listeners == nil) {
+        listenersByEvent[messageType] = [NSMutableArray arrayWithObject:block];
+    } else {
+        [listeners addObject:block];
+    }
+}
+
+
+#pragma mark Internal and testing methods
+
+// Methods reserved for internal and testing
+- (NSMutableArray *)listenersForMessageType:(NSString *)messageType
+{
+    return listenersByEvent[messageType];
+}
+
+- (void)fireEvent:(NSString *)messageType withPayload:(NSDictionary *)payload
+{
+    NSArray *listeners = [self listenersForMessageType:messageType];
+    for (JSListener listener in listeners) {
+        listener(messageType, payload);
+    }
+}
+
+#pragma mark Private methods
+
+- (void)setupWebView
+{
+    webView.delegate = self;
+    
+    NSString *path = [[NSBundle mainBundle] pathForResource:@"bridge-index" 
ofType:@"html"];
+    NSURL *baseURL = [NSURL 
URLWithString:@"https://wikipedia-ios.wikipedia.org";]; // fake path
+    NSData *data = [NSData dataWithContentsOfFile:path];
+    [webView loadData:data MIMEType:@"text/html" textEncodingName:@"UTF-8" 
baseURL:baseURL];
+}
+
+static NSString *bridgeURLPrefix = @"x-wikipedia-bridge:";
+
+- (BOOL)isBridgeURL:(NSURL *)url
+{
+    return [[url absoluteString] hasPrefix:bridgeURLPrefix];
+}
+
+- (NSDictionary *)extractBridgePayload:(NSURL *)url
+{
+    NSString *encodedStr = [[url absoluteString] 
substringFromIndex:bridgeURLPrefix.length];
+    NSString *str = [encodedStr 
stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
+    NSData *data = [str dataUsingEncoding:NSUTF8StringEncoding];
+    NSError *err;
+    NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:data 
options:0 error:&err];
+    // fixme throw an exception?
+    return dict;
+}
+
+#pragma mark UIWebViewDelegate methods
+
+- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest 
*)request navigationType:(UIWebViewNavigationType)navigationType
+{
+    NSLog(@"QQQ %@", request.URL);
+    if ([self isBridgeURL:request.URL]) {
+        NSDictionary *message = [self extractBridgePayload:request.URL];
+        NSString *messageType = message[@"type"];
+        NSDictionary *payload = message[@"payload"];
+        [self fireEvent:messageType withPayload:payload];
+        return NO;
+    }
+    return YES;
+}
+
+
+- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error
+{
+    NSLog(@"webView failed to load: %@", error);
+}
+
+- (void)webViewDidFinishLoad:(UIWebView *)webView
+{
+    NSLog(@"webView finished load");
+}
+
+- (void)webViewDidStartLoad:(UIWebView *)webView
+{
+    NSLog(@"webView started load");
+}
+
+@end
diff --git a/Wikipedia-iOS/ViewController.m b/Wikipedia-iOS/ViewController.m
index e98e17e..4b9f45b 100644
--- a/Wikipedia-iOS/ViewController.m
+++ b/Wikipedia-iOS/ViewController.m
@@ -8,17 +8,23 @@
 
 #import "ViewController.h"
 
+#import "CommunicationBridge.h"
+
 @interface ViewController ()
 
 @end
 
-@implementation ViewController
+@implementation ViewController {
+    CommunicationBridge *bridge;
+}
 
 - (void)viewDidLoad
 {
     [super viewDidLoad];
-       // Do any additional setup after loading the view, typically from a nib.
-    [self navigateToPage:@"Main Page"];
+    bridge = [[CommunicationBridge alloc] initWithWebView:self.webView];
+    [bridge addListener:@"DOMLoaded" withBlock:^(NSString *messageType, 
NSDictionary *payload) {
+        NSLog(@"QQQ HEY DOMLoaded!");
+    }];
 }
 
 - (void)didReceiveMemoryWarning
@@ -34,42 +40,6 @@
     [self navigateToPage:textField.text];
     [textField resignFirstResponder];
     return NO;
-}
-
-#pragma mark UIWebViewDelegate methods
-
-- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error
-{
-    NSLog(@"webView failed to load: %@", error);
-}
-
-- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest 
*)request navigationType:(UIWebViewNavigationType)navigationType
-{
-    if (navigationType == UIWebViewNavigationTypeLinkClicked) {
-        NSURL *url = request.URL;
-        NSString *urlStr = [url absoluteString];
-        NSString *prefix = @"https://en.m.wikipedia.org/wiki/";;
-        if ([urlStr hasPrefix:prefix]) {
-            NSString *encTitle = [urlStr substringFromIndex:prefix.length];
-            NSString *title = [encTitle 
stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
-            [self navigateToPage:title];
-        } else {
-            [UIApplication.sharedApplication openURL:url];
-        }
-        return NO;
-    } else {
-        return YES;
-    }
-}
-
-- (void)webViewDidFinishLoad:(UIWebView *)webView
-{
-    
-}
-
-- (void)webViewDidStartLoad:(UIWebView *)webView
-{
-    
 }
 
 #pragma mark action methods
diff --git a/Wikipedia-iOS/bridge-index.html b/Wikipedia-iOS/bridge-index.html
new file mode 100644
index 0000000..4371fa0
--- /dev/null
+++ b/Wikipedia-iOS/bridge-index.html
@@ -0,0 +1,61 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <base href="https://wikipedia.org"; /> <!-- Force links to resolve with 
https as protocol, rather than file:// -->
+        <script>
+            ( function() {
+             var Bridge = function() {
+                this.eventHandlers = {};
+             };
+             
+             Bridge.prototype.handleMessage = function( type, payload ) {
+             var that = this;
+                 if ( this.eventHandlers.hasOwnProperty( type ) ) {
+                     this.eventHandlers[type].forEach( function( callback ) {
+                        callback.call( that, payload );
+                     } );
+                 }
+             };
+             
+             Bridge.prototype.registerListener = function( messageType, 
callback ) {
+                 if ( this.eventHandlers.hasOwnProperty( messageType ) ) {
+                    this.eventHandlers[messageType].push( callback );
+                 } else {
+                    this.eventHandlers[messageType] = [ callback ];
+                 }
+             };
+             
+             Bridge.prototype.sendMessage = function( messageType, payload ) {
+                var messagePack = { type: messageType, payload: payload };
+                var url = "x-wikipedia-bridge:" + JSON.stringify( messagePack 
);
+             
+                // quick iframe version based on 
http://stackoverflow.com/a/6508343/82439
+                // fixme can this be an XHR instead? check Cordova current 
state
+                var iframe = document.createElement('iframe');
+                iframe.setAttribute("src", url);
+                document.documentElement.appendChild(iframe);
+                iframe.parentNode.removeChild(iframe);
+                iframe = null;
+             };
+             
+             window.bridge = new Bridge();
+         } )();
+        </script>
+        <script>
+            ( function() {
+             window.onload = function() {
+                bridge.sendMessage( "DOMLoaded", {} );
+             };
+             
+             bridge.registerListener( "displayLeadSection", function( payload 
) {
+                 document.getElementById( "content" ).innerHTML = 
payload.leadSectionHTML;
+             });
+             
+             } )();
+        </script>
+    </head>
+    <body>
+        <h1 id="title"></h1>
+        <div id="content"></div>
+    </body>
+</html>
\ No newline at end of file
diff --git a/Wikipedia-iOSTests/CommunicationBridgeTests.m 
b/Wikipedia-iOSTests/CommunicationBridgeTests.m
new file mode 100644
index 0000000..44ddbd6
--- /dev/null
+++ b/Wikipedia-iOSTests/CommunicationBridgeTests.m
@@ -0,0 +1,106 @@
+//
+//  CommunicationBridgeTests.m
+//  Wikipedia-iOS
+//
+//  Created by Brion on 11/4/13.
+//  Copyright (c) 2013 Wikimedia Foundation. All rights reserved.
+//
+
+#import <XCTest/XCTest.h>
+#import "CommunicationBridge.h"
+#import "NSRunLoop+TimeOutAndFlag.h"
+
+@interface CommunicationBridgeTests : XCTestCase
+
+@end
+
+@implementation CommunicationBridgeTests {
+    UIWindow *window;
+    UIWebView *webView;
+    CommunicationBridge *bridge;
+}
+
+- (void)setUp
+{
+    [super setUp];
+
+    window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
+    window.screen = [UIScreen mainScreen];
+    webView = [[UIWebView alloc] initWithFrame:window.frame];
+    [window addSubview:webView];
+    [window makeKeyAndVisible]; // ???
+    bridge = [[CommunicationBridge alloc] initWithWebView:webView];
+}
+
+- (void)tearDown
+{
+    [window resignKeyWindow];
+    window = nil;
+    [super tearDown];
+}
+
+- (void)testCreated
+{
+    XCTAssertNotNil(bridge);
+}
+
+- (void)testAddListener
+{
+    JSListener listener = [^(NSString *messageType, NSDictionary *payload) {
+        // whee!
+    } copy];
+    [bridge addListener:@"DOMLoaded" withBlock:listener];
+    NSArray *listeners = [bridge listenersForMessageType:@"DOMLoaded"];
+    XCTAssertNotNil(listeners);
+    XCTAssert(listeners.count > 0);
+    // note: can't check for the actual object as it gets automatically copied 
due to ARC oddities
+}
+
+- (void)testFireEvent
+{
+    __block NSString *foundMessageType = @"<failed to fire>";
+    __block NSDictionary *foundPayload;
+    JSListener listener = [^(NSString *messageType, NSDictionary *payload) {
+        foundMessageType = messageType;
+        foundPayload = payload;
+    } copy];
+    [bridge addListener:@"TestEvent" withBlock:listener];
+    [bridge fireEvent:@"TestEvent" withPayload:@{}];
+    XCTAssertEqualObjects(foundMessageType, @"TestEvent");
+    XCTAssertNotNil(foundPayload);
+}
+
+- (void)testIsBridgeURL
+{
+    XCTAssertFalse([bridge isBridgeURL:[NSURL 
URLWithString:@"http://en.wikipedia.org/wiki/Foo";]]);
+    XCTAssertTrue([bridge isBridgeURL:[NSURL 
URLWithString:@"x-wikipedia-bridge:%7B%7D"]]);
+}
+
+- (void)testExtractBridgePayload
+{
+    NSDictionary *payload = [bridge extractBridgePayload:[NSURL 
URLWithString:@"x-wikipedia-bridge:%7B%7D"]];
+    XCTAssertNotNil(payload);
+
+    payload = [bridge extractBridgePayload:[NSURL 
URLWithString:@"x-wikipedia-bridge:%7B%22key%22%3A%22value%22%7D"]];
+    XCTAssertNotNil(payload);
+    XCTAssertEqualObjects(payload[@"key"], @"value");
+}
+
+- (void)testDOMLoaded
+{
+    __block NSString *foundMessageType = @"<failed to fire>";
+    __block BOOL found = NO;
+    
+    NSLog(@"QQQ WAITING");
+    [bridge addListener:@"DOMLoaded" withBlock:^(NSString *messageType, 
NSDictionary *payload) {
+        NSLog(@"QQQ HEY");
+        foundMessageType = messageType;
+        found = YES;
+    }];
+    [[NSRunLoop mainRunLoop] runUntilTimeout:5 orFinishedFlag:&found];
+    NSLog(@"QQQ DONE WAITING");
+
+    XCTAssertEqualObjects(foundMessageType, @"DOMLoaded");
+}
+
+@end
diff --git a/Wikipedia-iOSTests/NSRunLoop+TimeOutAndFlag.h 
b/Wikipedia-iOSTests/NSRunLoop+TimeOutAndFlag.h
new file mode 100644
index 0000000..3603c5c
--- /dev/null
+++ b/Wikipedia-iOSTests/NSRunLoop+TimeOutAndFlag.h
@@ -0,0 +1,11 @@
+// quickie copied from https://gist.github.com/n-b/2299695
+//
+// NSRunLoop+TimeOutAndFlag.h
+//
+//
+
+@interface NSRunLoop (TimeOutAndFlag)
+
+- (void)runUntilTimeout:(NSTimeInterval)delay orFinishedFlag:(BOOL*)finished;
+
+@end
\ No newline at end of file
diff --git a/Wikipedia-iOSTests/NSRunLoop+TimeOutAndFlag.m 
b/Wikipedia-iOSTests/NSRunLoop+TimeOutAndFlag.m
new file mode 100644
index 0000000..eaec454
--- /dev/null
+++ b/Wikipedia-iOSTests/NSRunLoop+TimeOutAndFlag.m
@@ -0,0 +1,18 @@
+//
+// NSRunLoop+TimeOutAndFlag.m
+//
+//
+
+#import "NSRunLoop+TimeOutAndFlag.h"
+
+@implementation NSRunLoop (TimeOutAndFlag)
+
+- (void)runUntilTimeout:(NSTimeInterval)delay orFinishedFlag:(BOOL*)finished;
+{
+    NSDate * endDate = [NSDate dateWithTimeIntervalSinceNow:delay];
+    do {
+        [self runMode:NSDefaultRunLoopMode beforeDate:nil];
+    } while (!*finished && [endDate compare:[NSDate 
date]]==NSOrderedDescending);
+}
+
+@end
\ No newline at end of file

-- 
To view, visit https://gerrit.wikimedia.org/r/93609
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: merged
Gerrit-Change-Id: I667314bad95e774539a93d17d794169c9cb224fb
Gerrit-PatchSet: 3
Gerrit-Project: apps/ios/wikipedia
Gerrit-Branch: master
Gerrit-Owner: Brion VIBBER <[email protected]>
Gerrit-Reviewer: Brion VIBBER <[email protected]>

_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to