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