From 1461e4a17e0ba348f9d492dba963da0a2d65abf2 Mon Sep 17 00:00:00 2001 From: Johannes Lumpe Date: Fri, 26 Jun 2015 15:56:22 -0700 Subject: [PATCH 01/23] [Packager] Allow user to specify a custom transformer file Summary: This is an edited re-submission of #1458 because I'm stupid. Closes https://github.com/facebook/react-native/pull/1497 Github Author: Johannes Lumpe Test Plan: Imported from GitHub, without a `Test Plan:` line. --- packager/packager.js | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/packager/packager.js b/packager/packager.js index f2d526e06bcbb7..d179617c1aabec 100644 --- a/packager/packager.js +++ b/packager/packager.js @@ -12,6 +12,7 @@ var fs = require('fs'); var path = require('path'); var execFile = require('child_process').execFile; var http = require('http'); +var isAbsolutePath = require('absolute-path'); var getFlowTypeCheckMiddleware = require('./getFlowTypeCheckMiddleware'); @@ -56,6 +57,11 @@ var options = parseCommandLine([{ }, { command: 'nonPersistent', description: 'Disable file watcher' +}, { + command: 'transformer', + type: 'string', + default: require.resolve('./transformer.js'), + description: 'Specify a custom transformer to be used (absolute path)' }]); if (options.projectRoots) { @@ -208,12 +214,17 @@ function statusPageMiddleware(req, res, next) { } function getAppMiddleware(options) { + var transformerPath = options.transformer; + if (!isAbsolutePath(transformerPath)) { + transformerPath = path.resolve(process.cwd(), transformerPath); + } + return ReactPackager.middleware({ nonPersistent: options.nonPersistent, projectRoots: options.projectRoots, blacklistRE: blacklist(options.platform), cacheVersion: '2', - transformModulePath: require.resolve('./transformer.js'), + transformModulePath: transformerPath, assetRoots: options.assetRoots, assetExts: ['png', 'jpeg', 'jpg'], polyfillModuleNames: [ From de020b44c9ae30d1f335823957ec6ddcd71fc622 Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Fri, 26 Jun 2015 16:17:16 -0700 Subject: [PATCH 02/23] [react-packager] Enable watchman fs crawl Summary: @public Now that watchman perf issue was fixed we can enable watchman-based fs crawling which is faster than node. This showed an existing issue with some files missing from the blacklist which I addressed. Test Plan: ./fbrnios.sh run click around and scroll all the apps --- packager/blacklist.js | 4 ++-- .../__tests__/DependencyGraph-test.js | 3 ++- .../src/DependencyResolver/crawlers/index.js | 14 ++------------ .../src/DependencyResolver/crawlers/watchman.js | 2 +- packager/react-packager/src/FileWatcher/index.js | 10 +++++++--- 5 files changed, 14 insertions(+), 19 deletions(-) diff --git a/packager/blacklist.js b/packager/blacklist.js index a2ba71673af5a2..af2e2879a22593 100644 --- a/packager/blacklist.js +++ b/packager/blacklist.js @@ -24,7 +24,7 @@ var platformBlacklists = { ios: [ 'node_modules/react-tools/src/browser/ui/React.js', 'node_modules/react-tools/src/browser/eventPlugins/ResponderEventPlugin.js', - // 'node_modules/react-tools/src/vendor/core/ExecutionEnvironment.js', + 'node_modules/react-tools/src/vendor/core/ExecutionEnvironment.js', '.web.js', '.android.js', ], @@ -32,7 +32,7 @@ var platformBlacklists = { 'node_modules/react-tools/src/browser/ui/React.js', 'node_modules/react-tools/src/browser/eventPlugins/ResponderEventPlugin.js', 'node_modules/react-tools/src/browser/ReactTextComponent.js', - // 'node_modules/react-tools/src/vendor/core/ExecutionEnvironment.js', + 'node_modules/react-tools/src/vendor/core/ExecutionEnvironment.js', '.web.js', '.ios.js', ], diff --git a/packager/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js b/packager/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js index 673d9c58a9ea3b..471e6069189ac4 100644 --- a/packager/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js +++ b/packager/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js @@ -2353,7 +2353,8 @@ describe('DependencyGraph', function() { } callbacks.push(callback); return this; - } + }, + isWatchman: () => Promise.resolve(false), }; }); diff --git a/packager/react-packager/src/DependencyResolver/crawlers/index.js b/packager/react-packager/src/DependencyResolver/crawlers/index.js index 71290af44741c0..fe755bcb621e13 100644 --- a/packager/react-packager/src/DependencyResolver/crawlers/index.js +++ b/packager/react-packager/src/DependencyResolver/crawlers/index.js @@ -1,21 +1,11 @@ 'use strict'; const nodeCrawl = require('./node'); -//const watchmanCrawl = require('./watchman'); +const watchmanCrawl = require('./watchman'); function crawl(roots, options) { - return nodeCrawl(roots, options); - - // Although, in theory, watchman should be much faster; - // there is currently a bottleneck somewhere in the - // encoding/decoding that is causing it to be slower - // than node crawling. However, this should be fixed soon. - // https://github.com/facebook/watchman/issues/113 - /* const {fileWatcher} = options; return fileWatcher.isWatchman().then(isWatchman => { - - console.log(isWatchman); if (!isWatchman) { return false; } @@ -30,7 +20,7 @@ function crawl(roots, options) { } return nodeCrawl(roots, options); - });*/ + }); } module.exports = crawl; diff --git a/packager/react-packager/src/DependencyResolver/crawlers/watchman.js b/packager/react-packager/src/DependencyResolver/crawlers/watchman.js index d6479a513f008b..1871e3ead8b554 100644 --- a/packager/react-packager/src/DependencyResolver/crawlers/watchman.js +++ b/packager/react-packager/src/DependencyResolver/crawlers/watchman.js @@ -36,7 +36,7 @@ function watchmanRecReadDir(roots, {ignore, fileWatcher, exts}) { } } - const cmd = Promise.promisify(watcher.client.command.bind(watcher.client)); + const cmd = Promise.denodeify(watcher.client.command.bind(watcher.client)); return cmd(['query', watchedRoot, { 'suffix': exts, 'expression': ['allof', ['type', 'f'], 'exists', dirExpr], diff --git a/packager/react-packager/src/FileWatcher/index.js b/packager/react-packager/src/FileWatcher/index.js index 46b4667e72cefa..d9ed7f32fd2519 100644 --- a/packager/react-packager/src/FileWatcher/index.js +++ b/packager/react-packager/src/FileWatcher/index.js @@ -12,9 +12,11 @@ const EventEmitter = require('events').EventEmitter; const sane = require('sane'); const Promise = require('promise'); const exec = require('child_process').exec; +const _ = require('underscore'); const MAX_WAIT_TIME = 25000; +// TODO(amasad): can we use watchman version command instead?r const detectingWatcherClass = new Promise(function(resolve) { exec('which watchman', function(err, out) { if (err || out.length === 0) { @@ -79,9 +81,11 @@ class FileWatcher extends EventEmitter { static createDummyWatcher() { const ev = new EventEmitter(); - ev.end = function() { - return Promise.resolve(); - }; + _.extend(ev, { + isWatchman: () => Promise.resolve(false), + end: () => Promise.resolve(), + }); + return ev; } } From 19e32399b0a5a78398b068d2b0cf1cd82396df64 Mon Sep 17 00:00:00 2001 From: Joe Wood Date: Fri, 26 Jun 2015 16:18:49 -0700 Subject: [PATCH 03/23] [Packager] Windows support for Packager - Blacklist changes Summary: Another Pull Request implementing the changes in issue #468 - Enabled Packager to run on Windows This change relates to the blacklist fixes. It includes the path conversion for blacklist and changes to the default watched directory. It has no impact on Mac OSX. Closes https://github.com/facebook/react-native/pull/893 Github Author: Joe Wood Test Plan: Imported from GitHub, without a `Test Plan:` line. --- packager/blacklist.js | 6 +++++- packager/packager.js | 8 +++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/packager/blacklist.js b/packager/blacklist.js index af2e2879a22593..237691a85e9491 100644 --- a/packager/blacklist.js +++ b/packager/blacklist.js @@ -8,6 +8,8 @@ */ 'use strict'; +var path = require('path'); + // Don't forget to everything listed here to `testConfig.json` // modulePathIgnorePatterns. var sharedBlacklist = [ @@ -39,7 +41,9 @@ var platformBlacklists = { }; function escapeRegExp(str) { - return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&'); + var escaped = str.replace(/[\-\[\]\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&'); + // convert the '/' into an escaped local file separator + return escaped.replace(/\//g,'\\' + path.sep); } function blacklist(platform, additionalBlacklist) { diff --git a/packager/packager.js b/packager/packager.js index d179617c1aabec..ff0faa315b3aa4 100644 --- a/packager/packager.js +++ b/packager/packager.js @@ -69,8 +69,9 @@ if (options.projectRoots) { options.projectRoots = options.projectRoots.split(','); } } else { - if (__dirname.match(/node_modules\/react-native\/packager$/)) { - // packager is running from node_modules of another project + // match on either path separator + if (__dirname.match(/node_modules[\/\\]react-native[\/\\]packager$/)) { + // packager is running from node_modules of another project options.projectRoots = [path.resolve(__dirname, '../../..')]; } else if (__dirname.match(/Pods\/React\/packager$/)) { // packager is running from node_modules of another project @@ -97,7 +98,8 @@ if (options.assetRoots) { }); } } else { - if (__dirname.match(/node_modules\/react-native\/packager$/)) { + // match on either path separator + if (__dirname.match(/node_modules[\/\\]react-native[\/\\]packager$/)) { options.assetRoots = [path.resolve(__dirname, '../../..')]; } else if (__dirname.match(/Pods\/React\/packager$/)) { options.assetRoots = [path.resolve(__dirname, '../../..')]; From 73b032ab8723037aa3bef6a3185dcf2439e83d08 Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Fri, 26 Jun 2015 17:45:34 -0700 Subject: [PATCH 04/23] [react-packager] Update sane to get a new version of fb-watchman (perf) --- .../src/DependencyResolver/DependencyGraph/index.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packager/react-packager/src/DependencyResolver/DependencyGraph/index.js b/packager/react-packager/src/DependencyResolver/DependencyGraph/index.js index 8145bfa03ceb5b..42c1a485f33044 100644 --- a/packager/react-packager/src/DependencyResolver/DependencyGraph/index.js +++ b/packager/react-packager/src/DependencyResolver/DependencyGraph/index.js @@ -29,6 +29,7 @@ const validateOpts = declareOpts({ }, ignoreFilePath: { type: 'function', + default: function(){} }, fileWatcher: { From 8708c35702a756c410643db06344880b10ed2c60 Mon Sep 17 00:00:00 2001 From: Nick Lockwood Date: Sun, 28 Jun 2015 10:58:26 -0700 Subject: [PATCH 05/23] Restructuring FBReactKit project: Part 2 --- Libraries/Components/Touchable/TouchableBounce.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Libraries/Components/Touchable/TouchableBounce.js b/Libraries/Components/Touchable/TouchableBounce.js index 30d05210fb4a2f..9aad783b06595f 100644 --- a/Libraries/Components/Touchable/TouchableBounce.js +++ b/Libraries/Components/Touchable/TouchableBounce.js @@ -20,6 +20,12 @@ var Touchable = require('Touchable'); var merge = require('merge'); var onlyChild = require('onlyChild'); +var invariant = require('invariant'); +invariant( + AnimationExperimental || POPAnimation, + 'Please add the RCTAnimationExperimental framework to your project, or add //Libraries/FBReactKit:RCTPOPAnimation to your BUCK file if running internally within Facebook.' +); + type State = { animationID: ?number; }; From 7196445cc6d9502b316a1b1b15c97923efab39e8 Mon Sep 17 00:00:00 2001 From: Andy Street Date: Mon, 29 Jun 2015 03:15:35 -0700 Subject: [PATCH 06/23] [react_native] Update common UI examples --- Examples/UIExplorer/UIExplorerList.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Examples/UIExplorer/UIExplorerList.js b/Examples/UIExplorer/UIExplorerList.js index 5af5a32c648f92..fa2fe3bb233f9c 100644 --- a/Examples/UIExplorer/UIExplorerList.js +++ b/Examples/UIExplorer/UIExplorerList.js @@ -36,6 +36,14 @@ import type { ExampleModule } from 'ExampleTypes'; var createExamplePage = require('./createExamplePage'); var COMMON_COMPONENTS = [ + require('./ImageExample'), + require('./ListViewExample'), + require('./ListViewPagingExample'), + require('./MapViewExample'), + require('./Navigator/NavigatorExample'), + require('./ScrollViewExample'), + require('./TextInputExample'), + require('./TouchableExample'), require('./ViewExample'), require('./WebViewExample'), ]; @@ -51,23 +59,15 @@ if (Platform.OS === 'ios') { var COMPONENTS = COMMON_COMPONENTS.concat([ require('./ActivityIndicatorIOSExample'), require('./DatePickerIOSExample'), - require('./ImageExample'), - require('./ListViewExample'), - require('./ListViewPagingExample'), - require('./MapViewExample'), - require('./Navigator/NavigatorExample'), require('./NavigatorIOSColorsExample'), require('./NavigatorIOSExample'), require('./PickerIOSExample'), require('./ProgressViewIOSExample'), - require('./ScrollViewExample'), require('./SegmentedControlIOSExample'), require('./SliderIOSExample'), require('./SwitchIOSExample'), require('./TabBarIOSExample'), require('./TextExample.ios'), - require('./TextInputExample'), - require('./TouchableExample'), ]); var APIS = COMMON_APIS.concat([ From c953aa7e0b36c88022475debf1ca38c73047258f Mon Sep 17 00:00:00 2001 From: James Ide Date: Mon, 29 Jun 2015 03:22:37 -0700 Subject: [PATCH 07/23] [Executor] Make executor ID functions non-static to fix ASan Summary: When `RCTGetExecutorID` was a static function in the header file, it would return nil when the app was running with ASan enabled even though directly calling `objc_getAssociatedObject(executor, RCTJavaScriptExecutorID)` returned the correct ID as an NSNumber. Moving this function into the .m file fixes this issue. Closes https://github.com/facebook/react-native/pull/1712 Github Author: James Ide Test Plan: Run the UIExplorer with ASan enabled in Xcode 7. Before this diff, the app would just hang since the executor was unable to read a valid ID and so it would bail out from running JS. With this diff the executor runs the JS and the UIExplorer works fine. --- React/Base/RCTJavaScriptExecutor.h | 15 ++------------- React/Base/RCTJavaScriptExecutor.m | 26 ++++++++++++++++++++++++++ React/React.xcodeproj/project.pbxproj | 4 ++++ 3 files changed, 32 insertions(+), 13 deletions(-) create mode 100644 React/Base/RCTJavaScriptExecutor.m diff --git a/React/Base/RCTJavaScriptExecutor.h b/React/Base/RCTJavaScriptExecutor.h index 146247009a2171..fa5afc5cb8f762 100644 --- a/React/Base/RCTJavaScriptExecutor.h +++ b/React/Base/RCTJavaScriptExecutor.h @@ -67,16 +67,5 @@ typedef void (^RCTJavaScriptCallback)(id json, NSError *error); @end -static const char *RCTJavaScriptExecutorID = "RCTJavaScriptExecutorID"; -__used static void RCTSetExecutorID(id executor) -{ - static NSUInteger executorID = 0; - if (executor) { - objc_setAssociatedObject(executor, RCTJavaScriptExecutorID, @(++executorID), OBJC_ASSOCIATION_RETAIN); - } -} - -__used static NSNumber *RCTGetExecutorID(id executor) -{ - return executor ? objc_getAssociatedObject(executor, RCTJavaScriptExecutorID) : @0; -} +void RCTSetExecutorID(id executor); +NSNumber *RCTGetExecutorID(id executor); diff --git a/React/Base/RCTJavaScriptExecutor.m b/React/Base/RCTJavaScriptExecutor.m new file mode 100644 index 00000000000000..04e3db433ba643 --- /dev/null +++ b/React/Base/RCTJavaScriptExecutor.m @@ -0,0 +1,26 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "RCTJavaScriptExecutor.h" + + +static const char *RCTJavaScriptExecutorID = "RCTJavaScriptExecutorID"; + +void RCTSetExecutorID(id executor) +{ + static NSUInteger executorID = 0; + if (executor) { + objc_setAssociatedObject(executor, RCTJavaScriptExecutorID, @(++executorID), OBJC_ASSOCIATION_RETAIN); + } +} + +NSNumber *RCTGetExecutorID(id executor) +{ + return executor ? objc_getAssociatedObject(executor, RCTJavaScriptExecutorID) : @0; +} diff --git a/React/React.xcodeproj/project.pbxproj b/React/React.xcodeproj/project.pbxproj index 099c120a03a4fc..95bf55c7cfc54e 100644 --- a/React/React.xcodeproj/project.pbxproj +++ b/React/React.xcodeproj/project.pbxproj @@ -65,6 +65,7 @@ 58114A501AAE93D500E7D092 /* RCTAsyncLocalStorage.m in Sources */ = {isa = PBXBuildFile; fileRef = 58114A4E1AAE93D500E7D092 /* RCTAsyncLocalStorage.m */; }; 58C571C11AA56C1900CDF9C8 /* RCTDatePickerManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 58C571BF1AA56C1900CDF9C8 /* RCTDatePickerManager.m */; }; 63F014C01B02080B003B75D2 /* RCTPointAnnotation.m in Sources */ = {isa = PBXBuildFile; fileRef = 63F014BF1B02080B003B75D2 /* RCTPointAnnotation.m */; }; + 783ABB351B38A9D3003FFD95 /* RCTJavaScriptExecutor.m in Sources */ = {isa = PBXBuildFile; fileRef = 783ABB341B38A9D3003FFD95 /* RCTJavaScriptExecutor.m */; }; 830A229E1A66C68A008503DA /* RCTRootView.m in Sources */ = {isa = PBXBuildFile; fileRef = 830A229D1A66C68A008503DA /* RCTRootView.m */; }; 830BA4551A8E3BDA00D53203 /* RCTCache.m in Sources */ = {isa = PBXBuildFile; fileRef = 830BA4541A8E3BDA00D53203 /* RCTCache.m */; }; 832348161A77A5AA00B55238 /* Layout.c in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FC71A68125100A75B9A /* Layout.c */; }; @@ -216,6 +217,7 @@ 58C571C01AA56C1900CDF9C8 /* RCTDatePickerManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTDatePickerManager.h; sourceTree = ""; }; 63F014BE1B02080B003B75D2 /* RCTPointAnnotation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTPointAnnotation.h; sourceTree = ""; }; 63F014BF1B02080B003B75D2 /* RCTPointAnnotation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTPointAnnotation.m; sourceTree = ""; }; + 783ABB341B38A9D3003FFD95 /* RCTJavaScriptExecutor.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTJavaScriptExecutor.m; sourceTree = ""; }; 830213F31A654E0800B993E6 /* RCTBridgeModule.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RCTBridgeModule.h; sourceTree = ""; }; 830A229C1A66C68A008503DA /* RCTRootView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTRootView.h; sourceTree = ""; }; 830A229D1A66C68A008503DA /* RCTRootView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTRootView.m; sourceTree = ""; }; @@ -434,6 +436,7 @@ 83CBBA661A601EF300E9B192 /* RCTEventDispatcher.m */, 83CBBA4C1A601E3B00E9B192 /* RCTInvalidating.h */, 83CBBA631A601ECA00E9B192 /* RCTJavaScriptExecutor.h */, + 783ABB341B38A9D3003FFD95 /* RCTJavaScriptExecutor.m */, 13A1F71C1A75392D00D3D453 /* RCTKeyCommands.h */, 13A1F71D1A75392D00D3D453 /* RCTKeyCommands.m */, 83CBBA4D1A601E3B00E9B192 /* RCTLog.h */, @@ -579,6 +582,7 @@ 134FCB3D1A6E7F0800051CC8 /* RCTContextExecutor.m in Sources */, 14C2CA781B3ACB0400E6CBB2 /* RCTBatchedBridge.m in Sources */, 13E067591A70F44B002CDEE1 /* UIView+React.m in Sources */, + 783ABB351B38A9D3003FFD95 /* RCTJavaScriptExecutor.m in Sources */, 14F484561AABFCE100FDF6B9 /* RCTSliderManager.m in Sources */, 14C2CA741B3AC64300E6CBB2 /* RCTModuleData.m in Sources */, 142014191B32094000CC17BA /* RCTPerformanceLogger.m in Sources */, From 454b5f3c0b88e75bd4fc6100f0ee79528f023335 Mon Sep 17 00:00:00 2001 From: Alex Akers Date: Mon, 29 Jun 2015 05:15:25 -0700 Subject: [PATCH 08/23] [React Native] Replace RCTCache with NSURLCache --- Libraries/Image/RCTImageDownloader.h | 2 +- Libraries/Image/RCTImageDownloader.m | 190 ++++++++++----------- React/Base/RCTCache.h | 29 ---- React/Base/RCTCache.m | 234 -------------------------- React/React.xcodeproj/project.pbxproj | 6 - 5 files changed, 94 insertions(+), 367 deletions(-) delete mode 100644 React/Base/RCTCache.h delete mode 100644 React/Base/RCTCache.m diff --git a/Libraries/Image/RCTImageDownloader.h b/Libraries/Image/RCTImageDownloader.h index 89a77975fdd872..5a4dd1987a32e4 100644 --- a/Libraries/Image/RCTImageDownloader.h +++ b/Libraries/Image/RCTImageDownloader.h @@ -14,7 +14,7 @@ typedef void (^RCTImageDownloadBlock)(UIImage *image, NSError *error); @interface RCTImageDownloader : NSObject -+ (instancetype)sharedInstance; ++ (RCTImageDownloader *)sharedInstance; /** * Downloads a block of raw data and returns it. Note that the callback block diff --git a/Libraries/Image/RCTImageDownloader.m b/Libraries/Image/RCTImageDownloader.m index 760fce614d0dfe..a8a68c0b472870 100644 --- a/Libraries/Image/RCTImageDownloader.m +++ b/Libraries/Image/RCTImageDownloader.m @@ -9,25 +9,27 @@ #import "RCTImageDownloader.h" -#import "RCTCache.h" #import "RCTLog.h" #import "RCTUtils.h" typedef void (^RCTCachedDataDownloadBlock)(BOOL cached, NSData *data, NSError *error); +CGSize RCTTargetSizeForClipRect(CGRect); +CGRect RCTClipRect(CGSize, CGFloat, CGSize, CGFloat, UIViewContentMode); + @implementation RCTImageDownloader { - RCTCache *_cache; + NSURLCache *_cache; dispatch_queue_t _processingQueue; NSMutableDictionary *_pendingBlocks; } -+ (instancetype)sharedInstance ++ (RCTImageDownloader *)sharedInstance { static RCTImageDownloader *sharedInstance; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ - sharedInstance = [[self alloc] init]; + sharedInstance = [[RCTImageDownloader alloc] init]; }); return sharedInstance; } @@ -35,27 +37,22 @@ + (instancetype)sharedInstance - (instancetype)init { if ((self = [super init])) { - _cache = [[RCTCache alloc] initWithName:@"RCTImageDownloader"]; + _cache = [[NSURLCache alloc] initWithMemoryCapacity:5 * 1024 * 1024 diskCapacity:200 * 1024 * 1024 diskPath:@"React/RCTImageDownloader"]; _processingQueue = dispatch_queue_create("com.facebook.React.DownloadProcessingQueue", DISPATCH_QUEUE_SERIAL); _pendingBlocks = [[NSMutableDictionary alloc] init]; } - return self; -} -static NSString *RCTCacheKeyForURL(NSURL *url) -{ - return url.absoluteString; + return self; } - (id)_downloadDataForURL:(NSURL *)url block:(RCTCachedDataDownloadBlock)block { - NSString *cacheKey = RCTCacheKeyForURL(url); + NSString *cacheKey = url.absoluteString; __block BOOL cancelled = NO; __block NSURLSessionDataTask *task = nil; dispatch_block_t cancel = ^{ - cancelled = YES; dispatch_async(_processingQueue, ^{ @@ -88,21 +85,28 @@ - (id)_downloadDataForURL:(NSURL *)url block:(RCTCachedDataDownloadBlock)block }); }; - if ([_cache hasDataForKey:cacheKey]) { - [_cache fetchDataForKey:cacheKey completionHandler:^(NSData *data) { - if (!cancelled) { - runBlocks(YES, data, nil); - } - }]; - } else { - task = [[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { - if (!cancelled) { - runBlocks(NO, data, error); - } - }]; + task = [[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { + if (!cancelled) { + runBlocks(NO, data, error); + } - [task resume]; - } + RCTImageDownloader *strongSelf = weakSelf; + NSCachedURLResponse *cachedResponse = [[NSCachedURLResponse alloc] initWithResponse:response data:data userInfo:nil storagePolicy:NSURLCacheStorageAllowed]; + [strongSelf->_cache storeCachedResponse:cachedResponse forDataTask:task]; + task = nil; + }]; + + [_cache getCachedResponseForDataTask:task completionHandler:^(NSCachedURLResponse *cachedResponse) { + if (cancelled) { + return; + } + + if (cachedResponse) { + runBlocks(YES, cachedResponse.data, nil); + } else { + [task resume]; + } + }]; } }); @@ -111,22 +115,78 @@ - (id)_downloadDataForURL:(NSURL *)url block:(RCTCachedDataDownloadBlock)block - (id)downloadDataForURL:(NSURL *)url block:(RCTDataDownloadBlock)block { - NSString *cacheKey = RCTCacheKeyForURL(url); - __weak RCTImageDownloader *weakSelf = self; return [self _downloadDataForURL:url block:^(BOOL cached, NSData *data, NSError *error) { - if (!cached) { - RCTImageDownloader *strongSelf = weakSelf; - [strongSelf->_cache setData:data forKey:cacheKey]; - } block(data, error); }]; } +- (id)downloadImageForURL:(NSURL *)url + size:(CGSize)size + scale:(CGFloat)scale + resizeMode:(UIViewContentMode)resizeMode + backgroundColor:(UIColor *)backgroundColor + block:(RCTImageDownloadBlock)block +{ + return [self downloadDataForURL:url block:^(NSData *data, NSError *error) { + if (!data || error) { + block(nil, error); + return; + } + + if (CGSizeEqualToSize(size, CGSizeZero)) { + // Target size wasn't available yet, so abort image drawing + block(nil, nil); + return; + } + + UIImage *image = [UIImage imageWithData:data scale:scale]; + if (image) { + + // Get scale and size + CGFloat destScale = scale ?: RCTScreenScale(); + CGRect imageRect = RCTClipRect(image.size, image.scale, size, destScale, resizeMode); + CGSize destSize = RCTTargetSizeForClipRect(imageRect); + + // Opacity optimizations + UIColor *blendColor = nil; + BOOL opaque = !RCTImageHasAlpha(image.CGImage); + if (!opaque && backgroundColor) { + CGFloat alpha; + [backgroundColor getRed:NULL green:NULL blue:NULL alpha:&alpha]; + if (alpha > 0.999) { // no benefit to blending if background is translucent + opaque = YES; + blendColor = backgroundColor; + } + } + + // Decompress image at required size + UIGraphicsBeginImageContextWithOptions(destSize, opaque, destScale); + if (blendColor) { + [blendColor setFill]; + UIRectFill((CGRect){CGPointZero, destSize}); + } + [image drawInRect:imageRect]; + image = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + } + + block(image, nil); + }]; +} + +- (void)cancelDownload:(id)downloadToken +{ + if (downloadToken) { + ((dispatch_block_t)downloadToken)(); + } +} + +@end + /** * Returns the optimal context size for an image drawn using the clip rect * returned by RCTClipRect. */ -CGSize RCTTargetSizeForClipRect(CGRect); CGSize RCTTargetSizeForClipRect(CGRect clipRect) { return (CGSize){ @@ -141,7 +201,6 @@ CGSize RCTTargetSizeForClipRect(CGRect clipRect) * then calculates the optimal rectangle to draw the image into so that it will * be sized and positioned correctly if drawn using the specified content mode. */ -CGRect RCTClipRect(CGSize, CGFloat, CGSize, CGFloat, UIViewContentMode); CGRect RCTClipRect(CGSize sourceSize, CGFloat sourceScale, CGSize destSize, CGFloat destScale, UIViewContentMode resizeMode) @@ -202,66 +261,3 @@ CGRect RCTClipRect(CGSize sourceSize, CGFloat sourceScale, return (CGRect){CGPointZero, destSize}; } } - -- (id)downloadImageForURL:(NSURL *)url - size:(CGSize)size - scale:(CGFloat)scale - resizeMode:(UIViewContentMode)resizeMode - backgroundColor:(UIColor *)backgroundColor - block:(RCTImageDownloadBlock)block -{ - return [self downloadDataForURL:url block:^(NSData *data, NSError *error) { - - if (!data || error) { - block(nil, error); - return; - } - - if (CGSizeEqualToSize(size, CGSizeZero)) { - // Target size wasn't available yet, so abort image drawing - block(nil, nil); - return; - } - - UIImage *image = [UIImage imageWithData:data scale:scale]; - if (image) { - - // Get scale and size - CGFloat destScale = scale ?: RCTScreenScale(); - CGRect imageRect = RCTClipRect(image.size, image.scale, size, destScale, resizeMode); - CGSize destSize = RCTTargetSizeForClipRect(imageRect); - - // Opacity optimizations - UIColor *blendColor = nil; - BOOL opaque = !RCTImageHasAlpha(image.CGImage); - if (!opaque && backgroundColor) { - CGFloat alpha; - [backgroundColor getRed:NULL green:NULL blue:NULL alpha:&alpha]; - if (alpha > 0.999) { // no benefit to blending if background is translucent - opaque = YES; - blendColor = backgroundColor; - } - } - - // Decompress image at required size - UIGraphicsBeginImageContextWithOptions(destSize, opaque, destScale); - if (blendColor) { - [blendColor setFill]; - UIRectFill((CGRect){CGPointZero, destSize}); - } - [image drawInRect:imageRect]; - image = UIGraphicsGetImageFromCurrentImageContext(); - UIGraphicsEndImageContext(); - } - block(image, nil); - }]; -} - -- (void)cancelDownload:(id)downloadToken -{ - if (downloadToken) { - ((dispatch_block_t)downloadToken)(); - } -} - -@end diff --git a/React/Base/RCTCache.h b/React/Base/RCTCache.h deleted file mode 100644 index 8704ff3db342b2..00000000000000 --- a/React/Base/RCTCache.h +++ /dev/null @@ -1,29 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ - -#import - -@interface RCTCache : NSObject - -- (instancetype)init; // name = @"default" -- (instancetype)initWithName:(NSString *)name; - -@property (nonatomic, assign) NSUInteger maximumDiskSize; // in bytes - -#pragma mark - Retrieval - -- (BOOL)hasDataForKey:(NSString *)key; -- (void)fetchDataForKey:(NSString *)key completionHandler:(void (^)(NSData *data))completionHandler; - -#pragma mark - Insertion - -- (void)setData:(NSData *)data forKey:(NSString *)key; -- (void)removeAllData; - -@end diff --git a/React/Base/RCTCache.m b/React/Base/RCTCache.m deleted file mode 100644 index 9390069c432713..00000000000000 --- a/React/Base/RCTCache.m +++ /dev/null @@ -1,234 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ - -#import "RCTCache.h" - -#import -#import - -#import "RCTAssert.h" - -static NSString *const RCTCacheSubdirectoryName = @"React"; -static NSString *const RCTKeyExtendedAttributeName = @"com.facebook.React.RCTCacheManager.Key"; -static NSMapTable *RCTLivingCachesByName; - -static NSError *RCTPOSIXError(int errorNumber) -{ - NSDictionary *userInfo = @{ - NSLocalizedDescriptionKey: @(strerror(errorNumber)) - }; - return [NSError errorWithDomain:NSPOSIXErrorDomain code:errorNumber userInfo:userInfo]; -} - -static NSString *RCTGetExtendedAttribute(NSURL *fileURL, NSString *key, NSError **error) -{ - const char *path = fileURL.fileSystemRepresentation; - ssize_t length = getxattr(path, key.UTF8String, NULL, 0, 0, 0); - if (length <= 0) { - if (error) *error = RCTPOSIXError(errno); - return nil; - } - - char *buffer = malloc(length); - length = getxattr(path, key.UTF8String, buffer, length, 0, 0); - if (length > 0) { - return [[NSString alloc] initWithBytesNoCopy:buffer length:length encoding:NSUTF8StringEncoding freeWhenDone:YES]; - } - - free(buffer); - if (error) *error = RCTPOSIXError(errno); - return nil; -} - -static BOOL RCTSetExtendedAttribute(NSURL *fileURL, NSString *key, NSString *value, NSError **error) -{ - const char *path = fileURL.fileSystemRepresentation; - - int result; - if (value) { - const char *valueUTF8String = value.UTF8String; - result = setxattr(path, key.UTF8String, valueUTF8String, strlen(valueUTF8String), 0, 0); - } else { - result = removexattr(path, key.UTF8String, 0); - } - - if (result) { - if (error) *error = RCTPOSIXError(errno); - return NO; - } - - return YES; -} - -#pragma mark - Cache Record - - -@interface RCTCacheRecord : NSObject - -@property (readonly) NSUUID *UUID; -@property (readonly, weak) dispatch_queue_t queue; -@property (nonatomic, copy) NSData *data; - -@end - -@implementation RCTCacheRecord - -- (instancetype)initWithUUID:(NSUUID *)UUID -{ - if ((self = [super init])) { - _UUID = [UUID copy]; - } - return self; -} - -- (void)enqueueBlock:(dispatch_block_t)block -{ - dispatch_queue_t queue = _queue; - if (!queue) { - NSString *queueName = [NSString stringWithFormat:@"com.facebook.React.RCTCache.%@", _UUID.UUIDString]; - queue = dispatch_queue_create(queueName.UTF8String, DISPATCH_QUEUE_SERIAL); - _queue = queue; - } - - dispatch_async(queue, block); -} - -@end - -#pragma mark - Cache - -@implementation RCTCache -{ - NSString *_name; - NSFileManager *_fileManager; - NSMutableDictionary *_storage; - NSURL *_cacheDirectoryURL; -} - -+ (void)initialize -{ - if (self == [RCTCache class]) { - RCTLivingCachesByName = [NSMapTable strongToWeakObjectsMapTable]; - } -} - -- (instancetype)init -{ - return [self initWithName:@"default"]; -} - -- (instancetype)initWithName:(NSString *)name -{ - RCTAssertParam(name); - RCTAssert(name.length < NAME_MAX, @"Name must be fewer than %i characters in length.", NAME_MAX); - RCTCache *cachedCache = [RCTLivingCachesByName objectForKey:name]; - if (cachedCache) { - self = cachedCache; - return self; - } - - if ((self = [super init])) { - _name = [name copy]; - _fileManager = [[NSFileManager alloc] init]; - _storage = [NSMutableDictionary dictionary]; - - NSURL *cacheDirectoryURL = [[_fileManager URLsForDirectory:NSCachesDirectory inDomains:NSUserDomainMask] lastObject]; - cacheDirectoryURL = [cacheDirectoryURL URLByAppendingPathComponent:RCTCacheSubdirectoryName isDirectory:YES]; - _cacheDirectoryURL = [cacheDirectoryURL URLByAppendingPathComponent:name isDirectory:YES]; - [_fileManager createDirectoryAtURL:_cacheDirectoryURL withIntermediateDirectories:YES attributes:nil error:NULL]; - - NSArray *fileURLs = [_fileManager contentsOfDirectoryAtURL:_cacheDirectoryURL includingPropertiesForKeys:nil options:NSDirectoryEnumerationSkipsHiddenFiles error:NULL]; - for (NSURL *fileURL in fileURLs) { - NSUUID *UUID = [[NSUUID alloc] initWithUUIDString:fileURL.lastPathComponent]; - if (!UUID) continue; - - NSString *key = RCTGetExtendedAttribute(fileURL, RCTKeyExtendedAttributeName, NULL); - if (!key) { - [_fileManager removeItemAtURL:fileURL error:NULL]; - continue; - } - - _storage[key] = [[RCTCacheRecord alloc] initWithUUID:UUID]; - } - } - return self; -} - -- (BOOL)hasDataForKey:(NSString *)key -{ - return _storage[key] != nil; -} - -- (void)fetchDataForKey:(NSString *)key completionHandler:(void (^)(NSData *))completionHandler -{ - NSParameterAssert(key.length > 0); - NSParameterAssert(completionHandler != nil); - RCTCacheRecord *record = _storage[key]; - if (!record) { - completionHandler(nil); - return; - } - - [record enqueueBlock:^{ - if (!record.data) { - record.data = [NSData dataWithContentsOfURL:[_cacheDirectoryURL URLByAppendingPathComponent:record.UUID.UUIDString]]; - } - completionHandler(record.data); - }]; -} - -- (void)setData:(NSData *)data forKey:(NSString *)key -{ - NSParameterAssert(key.length > 0); - RCTCacheRecord *record = _storage[key]; - if (!record) { - if (!data) return; - - record = [[RCTCacheRecord alloc] initWithUUID:[NSUUID UUID]]; - _storage[key] = record; - } - - NSURL *fileURL = [_cacheDirectoryURL URLByAppendingPathComponent:record.UUID.UUIDString]; - - UIBackgroundTaskIdentifier identifier = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:nil]; - [record enqueueBlock:^{ - if (data) { - [data writeToURL:fileURL options:NSDataWritingAtomic error:NULL]; - RCTSetExtendedAttribute(fileURL, RCTKeyExtendedAttributeName, key, NULL); - } else { - [_fileManager removeItemAtURL:fileURL error:NULL]; - } - - if (identifier != UIBackgroundTaskInvalid) { - [[UIApplication sharedApplication] endBackgroundTask:identifier]; - } - }]; -} - -- (void)removeAllData -{ - UIBackgroundTaskIdentifier identifier = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:nil]; - dispatch_group_t group = dispatch_group_create(); - - for (RCTCacheRecord *record in _storage.allValues) { - NSURL *fileURL = [_cacheDirectoryURL URLByAppendingPathComponent:record.UUID.UUIDString]; - dispatch_group_async(group, record.queue, ^{ - [_fileManager removeItemAtURL:fileURL error:NULL]; - }); - } - - if (identifier != UIBackgroundTaskInvalid) { - dispatch_group_notify(group, dispatch_get_main_queue(), ^{ - [[UIApplication sharedApplication] endBackgroundTask:identifier]; - }); - } - - [_storage removeAllObjects]; -} - -@end diff --git a/React/React.xcodeproj/project.pbxproj b/React/React.xcodeproj/project.pbxproj index 95bf55c7cfc54e..54261357db451e 100644 --- a/React/React.xcodeproj/project.pbxproj +++ b/React/React.xcodeproj/project.pbxproj @@ -67,7 +67,6 @@ 63F014C01B02080B003B75D2 /* RCTPointAnnotation.m in Sources */ = {isa = PBXBuildFile; fileRef = 63F014BF1B02080B003B75D2 /* RCTPointAnnotation.m */; }; 783ABB351B38A9D3003FFD95 /* RCTJavaScriptExecutor.m in Sources */ = {isa = PBXBuildFile; fileRef = 783ABB341B38A9D3003FFD95 /* RCTJavaScriptExecutor.m */; }; 830A229E1A66C68A008503DA /* RCTRootView.m in Sources */ = {isa = PBXBuildFile; fileRef = 830A229D1A66C68A008503DA /* RCTRootView.m */; }; - 830BA4551A8E3BDA00D53203 /* RCTCache.m in Sources */ = {isa = PBXBuildFile; fileRef = 830BA4541A8E3BDA00D53203 /* RCTCache.m */; }; 832348161A77A5AA00B55238 /* Layout.c in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FC71A68125100A75B9A /* Layout.c */; }; 83CBBA511A601E3B00E9B192 /* RCTAssert.m in Sources */ = {isa = PBXBuildFile; fileRef = 83CBBA4B1A601E3B00E9B192 /* RCTAssert.m */; }; 83CBBA521A601E3B00E9B192 /* RCTLog.m in Sources */ = {isa = PBXBuildFile; fileRef = 83CBBA4E1A601E3B00E9B192 /* RCTLog.m */; }; @@ -221,8 +220,6 @@ 830213F31A654E0800B993E6 /* RCTBridgeModule.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RCTBridgeModule.h; sourceTree = ""; }; 830A229C1A66C68A008503DA /* RCTRootView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTRootView.h; sourceTree = ""; }; 830A229D1A66C68A008503DA /* RCTRootView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTRootView.m; sourceTree = ""; }; - 830BA4531A8E3BDA00D53203 /* RCTCache.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTCache.h; sourceTree = ""; }; - 830BA4541A8E3BDA00D53203 /* RCTCache.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTCache.m; sourceTree = ""; }; 83BEE46C1A6D19BC00B5863B /* RCTSparseArray.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTSparseArray.h; sourceTree = ""; }; 83BEE46D1A6D19BC00B5863B /* RCTSparseArray.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTSparseArray.m; sourceTree = ""; }; 83CBBA2E1A601D0E00E9B192 /* libReact.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libReact.a; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -427,8 +424,6 @@ 83CBBA5E1A601EAA00E9B192 /* RCTBridge.h */, 83CBBA5F1A601EAA00E9B192 /* RCTBridge.m */, 830213F31A654E0800B993E6 /* RCTBridgeModule.h */, - 830BA4531A8E3BDA00D53203 /* RCTCache.h */, - 830BA4541A8E3BDA00D53203 /* RCTCache.m */, 83CBBACA1A6023D300E9B192 /* RCTConvert.h */, 83CBBACB1A6023D300E9B192 /* RCTConvert.m */, 13AF1F851AE6E777005F5298 /* RCTDefines.h */, @@ -605,7 +600,6 @@ 131B6AF51AF1093D00FFC3E0 /* RCTSegmentedControlManager.m in Sources */, 58114A171AAE854800E7D092 /* RCTPickerManager.m in Sources */, 13B0801A1A69489C00A75B9A /* RCTNavigator.m in Sources */, - 830BA4551A8E3BDA00D53203 /* RCTCache.m in Sources */, 137327E71AA5CF210034F82E /* RCTTabBar.m in Sources */, 00C1A2B31AC0B7E000E89A1C /* RCTDevMenu.m in Sources */, 63F014C01B02080B003B75D2 /* RCTPointAnnotation.m in Sources */, From cf6ff3f81507e359f29d0fa46acce06dc824b9c6 Mon Sep 17 00:00:00 2001 From: Matt Revell Date: Mon, 29 Jun 2015 05:47:21 -0700 Subject: [PATCH 09/23] #1562 Rename 'tick' to 'onTick' to pass iTunes Connect validation. Summary: Should close this issue and successfully pass iTunes Connect validation. Closes https://github.com/facebook/react-native/pull/1722 Github Author: Matt Revell Test Plan: Imported from GitHub, without a `Test Plan:` line. --- React/Base/RCTBatchedBridge.m | 4 ++-- React/Base/RCTFPSGraph.h | 2 +- React/Base/RCTFPSGraph.m | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/React/Base/RCTBatchedBridge.m b/React/Base/RCTBatchedBridge.m index 838e8bc80295d1..0d89d651da3311 100644 --- a/React/Base/RCTBatchedBridge.m +++ b/React/Base/RCTBatchedBridge.m @@ -721,7 +721,7 @@ - (void)_jsThreadUpdate:(CADisplayLink *)displayLink RCTProfileEndEvent(@"DispatchFrameUpdate", @"objc_call", nil); dispatch_async(dispatch_get_main_queue(), ^{ - [self.perfStats.jsGraph tick:displayLink.timestamp]; + [self.perfStats.jsGraph onTick:displayLink.timestamp]; }); } @@ -731,7 +731,7 @@ - (void)_mainThreadUpdate:(CADisplayLink *)displayLink RCTProfileImmediateEvent(@"VSYNC", displayLink.timestamp, @"g"); - [self.perfStats.uiGraph tick:displayLink.timestamp]; + [self.perfStats.uiGraph onTick:displayLink.timestamp]; } - (void)startProfiling diff --git a/React/Base/RCTFPSGraph.h b/React/Base/RCTFPSGraph.h index 905829aba2be7b..0c0e2664a844c6 100644 --- a/React/Base/RCTFPSGraph.h +++ b/React/Base/RCTFPSGraph.h @@ -18,6 +18,6 @@ typedef NS_ENUM(NSUInteger, RCTFPSGraphPosition) { - (instancetype)initWithFrame:(CGRect)frame graphPosition:(RCTFPSGraphPosition)position name:(NSString *)name color:(UIColor *)color NS_DESIGNATED_INITIALIZER; -- (void)tick:(NSTimeInterval)timestamp; +- (void)onTick:(NSTimeInterval)timestamp; @end diff --git a/React/Base/RCTFPSGraph.m b/React/Base/RCTFPSGraph.m index 5e9b0d855e3c75..1aa5efd7dd4fe4 100644 --- a/React/Base/RCTFPSGraph.m +++ b/React/Base/RCTFPSGraph.m @@ -93,7 +93,7 @@ - (UILabel *)createLabel:(UIColor *)color return label; } -- (void)tick:(NSTimeInterval)timestamp +- (void)onTick:(NSTimeInterval)timestamp { _frameCount++; if (_prevTime == -1) { From 555236865b6ddf47c39e2de1e7d53b0957bd0b2f Mon Sep 17 00:00:00 2001 From: Dmitry Soshnikov Date: Mon, 29 Jun 2015 17:32:44 -0700 Subject: [PATCH 10/23] [react-native][jest] Sync to 0.5.x and update to io.js --- package.json | 2 +- packager/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 13c57fbebde27c..e4013c32a35b14 100644 --- a/package.json +++ b/package.json @@ -72,7 +72,7 @@ "yargs": "1.3.2" }, "devDependencies": { - "jest-cli": "0.4.5", + "jest-cli": "git://github.com/facebook/jest#0.5.x", "babel-eslint": "3.1.5", "eslint": "0.21.2", "eslint-plugin-react": "2.3.0" diff --git a/packager/package.json b/packager/package.json index f3af007f350581..cc3f4fc6f969e5 100644 --- a/packager/package.json +++ b/packager/package.json @@ -25,7 +25,7 @@ }, "dependencies": {}, "devDependencies": { - "jest-cli": "0.4.5", + "jest-cli": "git://github.com/facebook/jest#0.5.x", "eslint": "0.9.2" } } From 9f22f67599238934e6929cbd251a88c99fc01e44 Mon Sep 17 00:00:00 2001 From: Jeff Morrison Date: Mon, 29 Jun 2015 21:35:32 -0700 Subject: [PATCH 11/23] [flow 0.13.1] Deploy to --- .flowconfig | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.flowconfig b/.flowconfig index 56d38f30838ddc..31a4d53177816f 100644 --- a/.flowconfig +++ b/.flowconfig @@ -40,9 +40,9 @@ suppress_type=$FlowIssue suppress_type=$FlowFixMe suppress_type=$FixMe -suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(>=0\\.\\(1[0-2]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\) -suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(1[0-2]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)? #[0-9]+ +suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(>=0\\.\\(1[0-3]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\) +suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(1[0-3]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)? #[0-9]+ suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy [version] -0.12.0 +0.13.1 From 7d184adf1aac8d7383639cf819162db98a2f593a Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Tue, 30 Jun 2015 03:53:42 -0700 Subject: [PATCH 12/23] [react-packager] Use latest babel-core in place of babel (40% perf improvement) --- packager/react-packager/index.js | 2 +- packager/transformer.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packager/react-packager/index.js b/packager/react-packager/index.js index d4ea0dd391fbce..c47d762a1203b5 100644 --- a/packager/react-packager/index.js +++ b/packager/react-packager/index.js @@ -8,7 +8,7 @@ */ 'use strict'; -require('babel/register')({ +require('babel-core/register')({ only: /react-packager\/src/ }); diff --git a/packager/transformer.js b/packager/transformer.js index a2efcea3bb2b54..c5b235da85f356 100644 --- a/packager/transformer.js +++ b/packager/transformer.js @@ -10,7 +10,7 @@ */ 'use strict'; -var babel = require('babel'); +var babel = require('babel-core'); function transform(srcTxt, filename, options) { var result = babel.transform(srcTxt, { From e3225f3403c0134b1fe035348358662c29874635 Mon Sep 17 00:00:00 2001 From: James Ide Date: Tue, 30 Jun 2015 04:09:27 -0700 Subject: [PATCH 13/23] [Bridge] Support nullability annotations in bridged methods Summary: Fixes a crash due to the selector regex not knowing about the nullability annotations. Adds support for both the core annotations `__nullable` and `__nonnull` plus their shorthand counterparts `nullable` and `nonnull`. Objective-C allows the shorthand versions only at the front of a parameter type declaration like `(nullable NSString *)` but the regex will pick up `(NSString * nullable)` too. This shouldn't cause any adverse effects and I left the code this way to keep the regex readable. Fixes #1795 Closes https://github.com/facebook/react-native/pull/1796 Github Author: James Ide Test Plan: Wrote a bridge method that uses a nullability annotation and verified that it didn't cause the app to crash: ``` RCT_EXPORT_METHOD(method:(nullable NSNumber *)reactTag) { } ``` Also added a nullable annotation to RCTTest. --- Libraries/RCTTest/RCTTestModule.m | 2 +- React/Base/RCTModuleMethod.m | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/Libraries/RCTTest/RCTTestModule.m b/Libraries/RCTTest/RCTTestModule.m index 54c44513e3206e..9ef60c61175e2c 100644 --- a/Libraries/RCTTest/RCTTestModule.m +++ b/Libraries/RCTTest/RCTTestModule.m @@ -55,7 +55,7 @@ - (instancetype)init }]; } -RCT_EXPORT_METHOD(sendAppEvent:(NSString *)name body:(id)body) +RCT_EXPORT_METHOD(sendAppEvent:(NSString *)name body:(nullable id)body) { [_bridge.eventDispatcher sendAppEventWithName:name body:body]; } diff --git a/React/Base/RCTModuleMethod.m b/React/Base/RCTModuleMethod.m index aca8267b94f56f..f294085e2f10cd 100644 --- a/React/Base/RCTModuleMethod.m +++ b/React/Base/RCTModuleMethod.m @@ -40,10 +40,12 @@ - (instancetype)initWithObjCMethodName:(NSString *)objCMethodName static NSRegularExpression *typeRegex; static NSRegularExpression *selectorRegex; if (!typeRegex) { - NSString *unusedPattern = @"(?:(?:__unused|__attribute__\\(\\(unused\\)\\)))"; + NSString *unusedPattern = @"(?:__unused|__attribute__\\(\\(unused\\)\\))"; NSString *constPattern = @"(?:const)"; - NSString *constUnusedPattern = [NSString stringWithFormat:@"(?:(?:%@|%@)\\s*)", unusedPattern, constPattern]; - NSString *pattern = [NSString stringWithFormat:@"\\(%1$@?(\\w+?)(?:\\s*\\*)?%1$@?\\)", constUnusedPattern]; + NSString *nullabilityPattern = @"(?:__nullable|__nonnull|nullable|nonnull)"; + NSString *annotationPattern = [NSString stringWithFormat:@"(?:(?:%@|%@|%@)\\s*)", + unusedPattern, constPattern, nullabilityPattern]; + NSString *pattern = [NSString stringWithFormat:@"\\(%1$@?(\\w+?)(?:\\s*\\*)?%1$@?\\)", annotationPattern]; typeRegex = [[NSRegularExpression alloc] initWithPattern:pattern options:0 error:NULL]; selectorRegex = [[NSRegularExpression alloc] initWithPattern:@"(?<=:).*?(?=[a-zA-Z_]+:|$)" options:0 error:NULL]; From 301c01260dd05327bd54a6232bae5238be497510 Mon Sep 17 00:00:00 2001 From: James Ide Date: Tue, 30 Jun 2015 13:45:28 -0700 Subject: [PATCH 14/23] [Flow] Update flowconfig's version req to 0.13.1, fix Movies example typechecking This should fix tests. Test Plan: Run Travis CI tests. Also run the movies app and verify that there are no invariant violations. --- .flowconfig | 2 +- Examples/Movies/SearchScreen.js | 7 ++++--- Examples/SampleApp/_flowconfig | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/.flowconfig b/.flowconfig index 56d38f30838ddc..4082e53142662d 100644 --- a/.flowconfig +++ b/.flowconfig @@ -45,4 +45,4 @@ suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(1[0-2]\\|[0-9]\\).[0-9 suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy [version] -0.12.0 +0.13.1 diff --git a/Examples/Movies/SearchScreen.js b/Examples/Movies/SearchScreen.js index 21819df4665f97..c0877d978a7c9b 100644 --- a/Examples/Movies/SearchScreen.js +++ b/Examples/Movies/SearchScreen.js @@ -26,6 +26,8 @@ var { } = React; var TimerMixin = require('react-timer-mixin'); +var invariant = require('invariant'); + var MovieCell = require('./MovieCell'); var MovieScreen = require('./MovieScreen'); @@ -73,18 +75,16 @@ var SearchScreen = React.createClass({ this.searchMovies(''); }, - _urlForQueryAndPage: function(query: string, pageNumber: ?number): string { + _urlForQueryAndPage: function(query: string, pageNumber: number): string { var apiKey = API_KEYS[this.state.queryNumber % API_KEYS.length]; if (query) { return ( - // $FlowFixMe(>=0.13.0) - pageNumber may be null or undefined API_URL + 'movies.json?apikey=' + apiKey + '&q=' + encodeURIComponent(query) + '&page_limit=20&page=' + pageNumber ); } else { // With no query, load latest movies return ( - // $FlowFixMe(>=0.13.0) - pageNumber may be null or undefined API_URL + 'lists/movies/in_theaters.json?apikey=' + apiKey + '&page_limit=20&page=' + pageNumber ); @@ -176,6 +176,7 @@ var SearchScreen = React.createClass({ }); var page = resultsCache.nextPageNumberForQuery[query]; + invariant(page != null, 'Next page number for "%s" is missing', query); fetch(this._urlForQueryAndPage(query, page)) .then((response) => response.json()) .catch((error) => { diff --git a/Examples/SampleApp/_flowconfig b/Examples/SampleApp/_flowconfig index 334ef74616f624..c2feaa128bb1b4 100644 --- a/Examples/SampleApp/_flowconfig +++ b/Examples/SampleApp/_flowconfig @@ -33,4 +33,4 @@ node_modules/react-native/Libraries/react-native/react-native-interface.js module.system=haste [version] -0.12.0 +0.13.1 From d7ddff75544de86e5c8b79ea111aab5b386a6874 Mon Sep 17 00:00:00 2001 From: Alex Kotliarskyi Date: Tue, 30 Jun 2015 17:08:28 -0700 Subject: [PATCH 15/23] [ReactNative] Fix dev menu customization when JS fails to load --- React/Base/RCTBatchedBridge.m | 3 +++ React/Base/RCTBridge.h | 5 +++++ React/Base/RCTBridge.m | 1 + 3 files changed, 9 insertions(+) diff --git a/React/Base/RCTBatchedBridge.m b/React/Base/RCTBatchedBridge.m index 0d89d651da3311..b1bb6cf1aa74d8 100644 --- a/React/Base/RCTBatchedBridge.m +++ b/React/Base/RCTBatchedBridge.m @@ -219,6 +219,9 @@ - (void)registerModules [_frameUpdateObservers addObject:moduleData]; } } + + [[NSNotificationCenter defaultCenter] postNotificationName:RCTDidCreateNativeModules + object:self]; } - (void)initJS diff --git a/React/Base/RCTBridge.h b/React/Base/RCTBridge.h index 1d70a6367eda15..f12f26158c9c5b 100644 --- a/React/Base/RCTBridge.h +++ b/React/Base/RCTBridge.h @@ -33,6 +33,11 @@ RCT_EXTERN NSString *const RCTJavaScriptDidLoadNotification; */ RCT_EXTERN NSString *const RCTJavaScriptDidFailToLoadNotification; +/** + * This notification fires when the bridge created all registered native modules + */ +RCT_EXTERN NSString *const RCTDidCreateNativeModules; + /** * This block can be used to instantiate modules that require additional * init parameters, or additional configuration prior to being used. diff --git a/React/Base/RCTBridge.m b/React/Base/RCTBridge.m index 28fcc460f2041b..c6baf82a6207df 100644 --- a/React/Base/RCTBridge.m +++ b/React/Base/RCTBridge.m @@ -20,6 +20,7 @@ NSString *const RCTReloadNotification = @"RCTReloadNotification"; NSString *const RCTJavaScriptDidLoadNotification = @"RCTJavaScriptDidLoadNotification"; NSString *const RCTJavaScriptDidFailToLoadNotification = @"RCTJavaScriptDidFailToLoadNotification"; +NSString *const RCTDidCreateNativeModules = @"RCTDidCreateNativeModules"; @class RCTBatchedBridge; From 14fef6474df5437e3447eb135f587ea8b5795ec9 Mon Sep 17 00:00:00 2001 From: Philipp von Weitershausen Date: Tue, 30 Jun 2015 18:44:02 -0700 Subject: [PATCH 16/23] [ReactNative] expose missing haste modules through 'react-native' node module --- Examples/UIExplorer/ActionSheetIOSExample.js | 3 ++- Examples/UIExplorer/AdSupportIOSExample.js | 3 +-- Examples/UIExplorer/MapViewExample.js | 2 +- Examples/UIExplorer/TransformExample.js | 10 ++++++---- Examples/UIExplorer/UIExplorerApp.android.js | 10 ++++++---- Examples/UIExplorer/UIExplorerList.js | 2 +- Examples/UIExplorer/WebViewExample.js | 1 - Libraries/react-native/react-native.js | 4 ++++ 8 files changed, 21 insertions(+), 14 deletions(-) diff --git a/Examples/UIExplorer/ActionSheetIOSExample.js b/Examples/UIExplorer/ActionSheetIOSExample.js index 86a3b96db7bd22..5c6ba8f9629e6e 100644 --- a/Examples/UIExplorer/ActionSheetIOSExample.js +++ b/Examples/UIExplorer/ActionSheetIOSExample.js @@ -17,11 +17,12 @@ var React = require('react-native'); var { + ActionSheetIOS, StyleSheet, Text, View, } = React; -var ActionSheetIOS = require('ActionSheetIOS'); + var BUTTONS = [ 'Button Index: 0', 'Button Index: 1', diff --git a/Examples/UIExplorer/AdSupportIOSExample.js b/Examples/UIExplorer/AdSupportIOSExample.js index ef7f076bfa8042..1626249e704699 100644 --- a/Examples/UIExplorer/AdSupportIOSExample.js +++ b/Examples/UIExplorer/AdSupportIOSExample.js @@ -15,10 +15,9 @@ */ 'use strict'; -var AdSupportIOS = require('AdSupportIOS'); - var React = require('react-native'); var { + AdSupportIOS, StyleSheet, Text, View, diff --git a/Examples/UIExplorer/MapViewExample.js b/Examples/UIExplorer/MapViewExample.js index 0d1061acef49b8..5720175747593c 100644 --- a/Examples/UIExplorer/MapViewExample.js +++ b/Examples/UIExplorer/MapViewExample.js @@ -16,9 +16,9 @@ 'use strict'; var React = require('react-native'); -var StyleSheet = require('StyleSheet'); var { MapView, + StyleSheet, Text, TextInput, View, diff --git a/Examples/UIExplorer/TransformExample.js b/Examples/UIExplorer/TransformExample.js index a59a019b347f5a..9e848c3a57cb81 100644 --- a/Examples/UIExplorer/TransformExample.js +++ b/Examples/UIExplorer/TransformExample.js @@ -6,12 +6,14 @@ 'use strict'; var React = require('React'); +var { + StyleSheet, + View, +} = React; -var StyleSheet = require('StyleSheet'); var TimerMixin = require('react-timer-mixin'); -var UIExplorerBlock = require('UIExplorerBlock'); -var UIExplorerPage = require('UIExplorerPage'); -var View = require('View'); +var UIExplorerBlock = require('./UIExplorerBlock'); +var UIExplorerPage = require('./UIExplorerPage'); var TransformExample = React.createClass({ diff --git a/Examples/UIExplorer/UIExplorerApp.android.js b/Examples/UIExplorer/UIExplorerApp.android.js index 69767c4b741a38..154796cef76231 100644 --- a/Examples/UIExplorer/UIExplorerApp.android.js +++ b/Examples/UIExplorer/UIExplorerApp.android.js @@ -17,14 +17,16 @@ 'use strict'; var React = require('react-native'); -var Dimensions = require('Dimensions'); -var DrawerLayoutAndroid = require('DrawerLayoutAndroid'); -var ToolbarAndroid = require('ToolbarAndroid'); -var UIExplorerList = require('./UIExplorerList'); var { + Dimensions, StyleSheet, View, } = React; +var UIExplorerList = require('./UIExplorerList'); + +// TODO: these should be exposed by the 'react-native' module. +var DrawerLayoutAndroid = require('DrawerLayoutAndroid'); +var ToolbarAndroid = require('ToolbarAndroid'); var DRAWER_WIDTH_LEFT = 56; diff --git a/Examples/UIExplorer/UIExplorerList.js b/Examples/UIExplorer/UIExplorerList.js index fa2fe3bb233f9c..1803c7ebd89fbf 100644 --- a/Examples/UIExplorer/UIExplorerList.js +++ b/Examples/UIExplorer/UIExplorerList.js @@ -21,6 +21,7 @@ var { ListView, PixelRatio, Platform, + Settings, StyleSheet, Text, TextInput, @@ -29,7 +30,6 @@ var { } = React; var { TestModule } = React.addons; -var Settings = require('Settings'); import type { ExampleModule } from 'ExampleTypes'; diff --git a/Examples/UIExplorer/WebViewExample.js b/Examples/UIExplorer/WebViewExample.js index fe3cbef6f7d590..b9137e87cec584 100644 --- a/Examples/UIExplorer/WebViewExample.js +++ b/Examples/UIExplorer/WebViewExample.js @@ -16,7 +16,6 @@ 'use strict'; var React = require('react-native'); -var StyleSheet = require('StyleSheet'); var { StyleSheet, Text, diff --git a/Libraries/react-native/react-native.js b/Libraries/react-native/react-native.js index 36727656739638..42cf30f51d7784 100644 --- a/Libraries/react-native/react-native.js +++ b/Libraries/react-native/react-native.js @@ -42,11 +42,14 @@ var ReactNative = Object.assign(Object.create(require('React')), { WebView: require('WebView'), // APIs + ActionSheetIOS: require('ActionSheetIOS'), + AdSupportIOS: require('AdSupportIOS'), AlertIOS: require('AlertIOS'), AppRegistry: require('AppRegistry'), AppStateIOS: require('AppStateIOS'), AsyncStorage: require('AsyncStorage'), CameraRoll: require('CameraRoll'), + Dimensions: require('Dimensions'), ImagePickerIOS: require('ImagePickerIOS'), InteractionManager: require('InteractionManager'), LayoutAnimation: require('LayoutAnimation'), @@ -55,6 +58,7 @@ var ReactNative = Object.assign(Object.create(require('React')), { PanResponder: require('PanResponder'), PixelRatio: require('PixelRatio'), PushNotificationIOS: require('PushNotificationIOS'), + Settings: require('Settings'), StatusBarIOS: require('StatusBarIOS'), StyleSheet: require('StyleSheet'), VibrationIOS: require('VibrationIOS'), From 5418cdf071b168241c4e5bb537a88561512c91d1 Mon Sep 17 00:00:00 2001 From: James Ide Date: Wed, 1 Jul 2015 04:03:44 -0700 Subject: [PATCH 17/23] [CocoaPods] Run `npm install --production` when installing React.podspec Summary: This omits the devDependencies (e.g. test infra), which are intended only for people working on RN. Part of #1737. Closes https://github.com/facebook/react-native/pull/1803 Github Author: James Ide Test Plan: Imported from GitHub, without a `Test Plan:` line. --- React.podspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/React.podspec b/React.podspec index 9e2d37ff9bb25f..7ea3f12615c5b7 100644 --- a/React.podspec +++ b/React.podspec @@ -22,7 +22,7 @@ Pod::Spec.new do |s| s.default_subspec = 'Core' s.requires_arc = true s.platform = :ios, "7.0" - s.prepare_command = 'npm install' + s.prepare_command = 'npm install --production' s.preserve_paths = "cli.js", "Libraries/**/*.js", "lint", "linter.js", "node_modules", "package.json", "packager", "PATENTS", "react-native-cli" s.header_mappings_dir = "." From 776dc97437fba0696217aabc638c302379a5808d Mon Sep 17 00:00:00 2001 From: Joe Savona Date: Wed, 1 Jul 2015 04:51:33 -0700 Subject: [PATCH 18/23] InteractionManager: remove dev timeout warnings --- Libraries/Interaction/InteractionManager.js | 28 --------------------- 1 file changed, 28 deletions(-) diff --git a/Libraries/Interaction/InteractionManager.js b/Libraries/Interaction/InteractionManager.js index 098dfcee9726ab..37625978adc142 100644 --- a/Libraries/Interaction/InteractionManager.js +++ b/Libraries/Interaction/InteractionManager.js @@ -21,11 +21,6 @@ var setImmediate = require('setImmediate'); type Handle = number; -/** - * Maximum time a handle can be open before warning in DEV. - */ -var DEV_TIMEOUT = 2000; - var _emitter = new EventEmitter(); var _interactionSet = new Set(); var _addInteractionSet = new Set(); @@ -94,14 +89,6 @@ var InteractionManager = { scheduleUpdate(); var handle = ++_inc; _addInteractionSet.add(handle); - if (__DEV__) { - // Capture the stack trace of what created the handle. - var error = new Error( - 'InteractionManager: interaction handle not cleared within ' + - DEV_TIMEOUT + ' ms.' - ); - setDevTimeoutHandle(handle, error, DEV_TIMEOUT); - } return handle; }, @@ -166,19 +153,4 @@ function processUpdate() { _deleteInteractionSet.clear(); } -/** - * Wait until `timeout` has passed and warn if the handle has not been cleared. - */ -function setDevTimeoutHandle( - handle: Handle, - error: Error, - timeout: number -): void { - setTimeout(() => { - if (_interactionSet.has(handle)) { - console.warn(error.message + '\n' + error.stack); - } - }, timeout); -} - module.exports = InteractionManager; From 212bd2250cb780e1e8d422a85a5851c9321fa640 Mon Sep 17 00:00:00 2001 From: James Ide Date: Fri, 22 May 2015 23:58:02 -0700 Subject: [PATCH 19/23] [Tests] Update tests to run on io.js with the latest version of jest Updates the tests in small ways so they run on io.js with some updates: - The Cache test which relies on Promises uses `runAllImmediates` for modern versions of Node because bluebird uses `setImmediate` instead of `process.nextTick` for Node >0.10. Test Plan: Run `npm test` with the latest version of jest. --- .travis.yml | 4 ++-- package.json | 2 +- .../react-packager/src/JSTransformer/__tests__/Cache-test.js | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 7da54bdf4076ac..eea5a4eaa1d8c8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,13 +15,13 @@ install: - cp $(brew --prefix nvm)/nvm-exec .nvm/ - export NVM_DIR=.nvm - source $(brew --prefix nvm)/nvm.sh - - nvm install v0.10 + - nvm install iojs-v2 - npm config set spin=false - npm install script: - | - nvm use v0.10 + nvm use iojs-v2 if [ "$TEST_TYPE" = objc ] then diff --git a/package.json b/package.json index 13c57fbebde27c..41c6b586931b1d 100644 --- a/package.json +++ b/package.json @@ -72,7 +72,7 @@ "yargs": "1.3.2" }, "devDependencies": { - "jest-cli": "0.4.5", + "jest-cli": "facebook/jest#0.5.x", "babel-eslint": "3.1.5", "eslint": "0.21.2", "eslint-plugin-react": "2.3.0" diff --git a/packager/react-packager/src/JSTransformer/__tests__/Cache-test.js b/packager/react-packager/src/JSTransformer/__tests__/Cache-test.js index 3877b3dd55dbd0..df3ccfd7ed9b25 100644 --- a/packager/react-packager/src/JSTransformer/__tests__/Cache-test.js +++ b/packager/react-packager/src/JSTransformer/__tests__/Cache-test.js @@ -229,7 +229,7 @@ describe('JSTransformer Cache', function() { return Promise.resolve('baz value'); }); - jest.runAllTicks(); + jest.runAllImmediates(); expect(fs.writeFile).toBeCalled(); }); }); From 7a8398b9560ac68642fe5e44138bc6c01501db0a Mon Sep 17 00:00:00 2001 From: James Ide Date: Wed, 1 Jul 2015 12:51:59 -0700 Subject: [PATCH 20/23] [Flow] Update flowconfig's version req to 0.13.1, fix Movies example typechecking Summary: This should fix tests. Closes https://github.com/facebook/react-native/pull/1819 Github Author: James Ide Test Plan: Run Travis CI tests. Also run the movies app and verify that there are no invariant violations. --- Examples/Movies/SearchScreen.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Examples/Movies/SearchScreen.js b/Examples/Movies/SearchScreen.js index 21819df4665f97..c0877d978a7c9b 100644 --- a/Examples/Movies/SearchScreen.js +++ b/Examples/Movies/SearchScreen.js @@ -26,6 +26,8 @@ var { } = React; var TimerMixin = require('react-timer-mixin'); +var invariant = require('invariant'); + var MovieCell = require('./MovieCell'); var MovieScreen = require('./MovieScreen'); @@ -73,18 +75,16 @@ var SearchScreen = React.createClass({ this.searchMovies(''); }, - _urlForQueryAndPage: function(query: string, pageNumber: ?number): string { + _urlForQueryAndPage: function(query: string, pageNumber: number): string { var apiKey = API_KEYS[this.state.queryNumber % API_KEYS.length]; if (query) { return ( - // $FlowFixMe(>=0.13.0) - pageNumber may be null or undefined API_URL + 'movies.json?apikey=' + apiKey + '&q=' + encodeURIComponent(query) + '&page_limit=20&page=' + pageNumber ); } else { // With no query, load latest movies return ( - // $FlowFixMe(>=0.13.0) - pageNumber may be null or undefined API_URL + 'lists/movies/in_theaters.json?apikey=' + apiKey + '&page_limit=20&page=' + pageNumber ); @@ -176,6 +176,7 @@ var SearchScreen = React.createClass({ }); var page = resultsCache.nextPageNumberForQuery[query]; + invariant(page != null, 'Next page number for "%s" is missing', query); fetch(this._urlForQueryAndPage(query, page)) .then((response) => response.json()) .catch((error) => { From 5aa27586f0793b4edc7225303f6828efdd2244a3 Mon Sep 17 00:00:00 2001 From: James Ide Date: Wed, 1 Jul 2015 12:52:10 -0700 Subject: [PATCH 21/23] [Tests] Update tests to run on io.js with the latest version of jest Summary: [This is a preview diff for getting RN's tests to pass with a future version of jest that supports io.js and other future versions of Node. This can be merged once the diff to update jest is merged upstream and published.] Updates the tests in small ways so they run on io.js with two updates: - The Cache test which relies on Promises uses `runAllImmediates` for modern versions of Node because bluebird uses `setImmediate` instead of `process.nextTick` for Node >0.10. Closes https://github.com/facebook/react-native/pull/1382 Github Author: James Ide Test Plan: Run `npm test` with the latest version of jest. --- .../react-packager/src/JSTransformer/__tests__/Cache-test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packager/react-packager/src/JSTransformer/__tests__/Cache-test.js b/packager/react-packager/src/JSTransformer/__tests__/Cache-test.js index 3877b3dd55dbd0..df3ccfd7ed9b25 100644 --- a/packager/react-packager/src/JSTransformer/__tests__/Cache-test.js +++ b/packager/react-packager/src/JSTransformer/__tests__/Cache-test.js @@ -229,7 +229,7 @@ describe('JSTransformer Cache', function() { return Promise.resolve('baz value'); }); - jest.runAllTicks(); + jest.runAllImmediates(); expect(fs.writeFile).toBeCalled(); }); }); From b45e2ed7ed1feb8379050064df783fc0d3f6cbf9 Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Wed, 1 Jul 2015 16:44:30 -0700 Subject: [PATCH 22/23] [react-packager] fix test --- .../react-packager/src/JSTransformer/__tests__/Cache-test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packager/react-packager/src/JSTransformer/__tests__/Cache-test.js b/packager/react-packager/src/JSTransformer/__tests__/Cache-test.js index df3ccfd7ed9b25..3877b3dd55dbd0 100644 --- a/packager/react-packager/src/JSTransformer/__tests__/Cache-test.js +++ b/packager/react-packager/src/JSTransformer/__tests__/Cache-test.js @@ -229,7 +229,7 @@ describe('JSTransformer Cache', function() { return Promise.resolve('baz value'); }); - jest.runAllImmediates(); + jest.runAllTicks(); expect(fs.writeFile).toBeCalled(); }); }); From 54825b304a48a37aa766d91e792bfa98a977c8b5 Mon Sep 17 00:00:00 2001 From: Hedger Wang Date: Wed, 1 Jul 2015 18:06:39 -0700 Subject: [PATCH 23/23] [WebView]: Kill `shouldInjectAJAXHandler`, and add `injectedJavascriptIOS` Summary: @public The API and implementation of `shouldInjectAJAXHandler` is very opinionated, and it does not solve many of the use cases that we'd like to address. Since `shouldInjectAJAXHandler` is basically juts injecting JS to the web page, we should let developer inject whatever JS that address different issues that they want to fix. Test Plan: Test this snippet at ``` ``` --- Libraries/Components/WebView/WebView.ios.js | 11 ++++- React/Views/RCTWebView.h | 4 +- React/Views/RCTWebView.m | 55 ++++++++++----------- React/Views/RCTWebViewManager.m | 5 +- 4 files changed, 40 insertions(+), 35 deletions(-) diff --git a/Libraries/Components/WebView/WebView.ios.js b/Libraries/Components/WebView/WebView.ios.js index 15ab9e676dec38..83b1c26f629de1 100644 --- a/Libraries/Components/WebView/WebView.ios.js +++ b/Libraries/Components/WebView/WebView.ios.js @@ -43,6 +43,8 @@ var NavigationType = { other: RCTWebViewManager.NavigationType.Other, }; +var JSNavigationScheme = RCTWebViewManager.JSNavigationScheme; + type ErrorEvent = { domain: any; code: any; @@ -75,6 +77,7 @@ var defaultRenderError = (errorDomain, errorCode, errorDesc) => ( var WebView = React.createClass({ statics: { + JSNavigationScheme: JSNavigationScheme, NavigationType: NavigationType, }, @@ -86,7 +89,6 @@ var WebView = React.createClass({ bounces: PropTypes.bool, scrollEnabled: PropTypes.bool, automaticallyAdjustContentInsets: PropTypes.bool, - shouldInjectAJAXHandler: PropTypes.bool, contentInset: EdgeInsetsPropType, onNavigationStateChange: PropTypes.func, startInLoadingState: PropTypes.bool, // force WebView to show loadingView on first load @@ -95,6 +97,11 @@ var WebView = React.createClass({ * Used for android only, JS is enabled by default for WebView on iOS */ javaScriptEnabledAndroid: PropTypes.bool, + /** + * Used for iOS only, sets the JS to be injected when the webpage loads. + */ + injectedJavascriptIOS: PropTypes.string, + /** * Used for iOS only, sets whether the webpage scales to fit the view and the * user can change the scale @@ -152,9 +159,9 @@ var WebView = React.createClass({ style={webViewStyles} url={this.props.url} html={this.props.html} + injectedJavascriptIOS={this.props.injectedJavascriptIOS} bounces={this.props.bounces} scrollEnabled={this.props.scrollEnabled} - shouldInjectAJAXHandler={this.props.shouldInjectAJAXHandler} contentInset={this.props.contentInset} automaticallyAdjustContentInsets={this.props.automaticallyAdjustContentInsets} onLoadingStart={this.onLoadingStart} diff --git a/React/Views/RCTWebView.h b/React/Views/RCTWebView.h index 52e7baaa3a6dad..90846e34920cbb 100644 --- a/React/Views/RCTWebView.h +++ b/React/Views/RCTWebView.h @@ -9,14 +9,16 @@ #import "RCTView.h" +extern NSString *const RCTJSNavigationScheme; + @class RCTEventDispatcher; @interface RCTWebView : RCTView @property (nonatomic, strong) NSURL *URL; @property (nonatomic, assign) UIEdgeInsets contentInset; -@property (nonatomic, assign) BOOL shouldInjectAJAXHandler; @property (nonatomic, assign) BOOL automaticallyAdjustContentInsets; +@property (nonatomic, copy) NSString *injectedJavascriptIOS; - (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher NS_DESIGNATED_INITIALIZER; diff --git a/React/Views/RCTWebView.m b/React/Views/RCTWebView.m index 9e2fd3504881f6..693f00eaa076c7 100644 --- a/React/Views/RCTWebView.m +++ b/React/Views/RCTWebView.m @@ -18,6 +18,13 @@ #import "RCTView.h" #import "UIView+React.h" +// Special scheme that allow JS to notify the WebView to emit +// navigation event. +// +// JavaScript Example: +// window.location.href = 'react-js-navigation://hello' +NSString *const RCTJSNavigationScheme = @"react-js-navigation"; + @interface RCTWebView () @end @@ -26,6 +33,7 @@ @implementation RCTWebView { RCTEventDispatcher *_eventDispatcher; UIWebView *_webView; + NSString *_injectedJavascriptIOS; } - (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher @@ -118,6 +126,19 @@ - (UIColor *)backgroundColor return _webView.backgroundColor; } +- (void)setinjectedJavascriptIOS:(NSString *)jsStr +{ + if (_injectedJavascriptIOS == jsStr) { + return; + } + + if ([_injectedJavascriptIOS isEqualToString:jsStr]) { + return; + } + + _injectedJavascriptIOS = [jsStr copy]; +} + - (NSMutableDictionary *)baseEvent { NSURL *url = _webView.request.URL; @@ -136,7 +157,6 @@ - (NSMutableDictionary *)baseEvent #pragma mark - UIWebViewDelegate methods -static NSString *const RCTJSAJAXScheme = @"react-ajax"; - (BOOL)webView:(__unused UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType @@ -152,8 +172,8 @@ - (BOOL)webView:(__unused UIWebView *)webView shouldStartLoadWithRequest:(NSURLR [_eventDispatcher sendInputEventWithName:@"topLoadingStart" body:event]; } - // AJAX handler - return ![request.URL.scheme isEqualToString:RCTJSAJAXScheme]; + // JS Navigation handler + return ![request.URL.scheme isEqualToString:RCTJSNavigationScheme]; } - (void)webView:(__unused UIWebView *)webView didFailLoadWithError:(NSError *)error @@ -177,33 +197,8 @@ - (void)webView:(__unused UIWebView *)webView didFailLoadWithError:(NSError *)er - (void)webViewDidFinishLoad:(UIWebView *)webView { - if (_shouldInjectAJAXHandler) { - - // From http://stackoverflow.com/questions/5353278/uiwebviewdelegate-not-monitoring-xmlhttprequest - - [webView stringByEvaluatingJavaScriptFromString:[NSString stringWithFormat:@"\ - var s_ajaxListener = new Object(); \n\ - s_ajaxListener.tempOpen = XMLHttpRequest.prototype.open; \n\ - s_ajaxListener.tempSend = XMLHttpRequest.prototype.send; \n\ - s_ajaxListener.callback = function() { \n\ - window.location.href = '%@://' + this.url; \n\ - } \n\ - XMLHttpRequest.prototype.open = function(a,b) { \n\ - s_ajaxListener.tempOpen.apply(this, arguments); \n\ - s_ajaxListener.method = a; \n\ - s_ajaxListener.url = b; \n\ - if (a.toLowerCase() === 'get') { \n\ - s_ajaxListener.data = (b.split('?'))[1]; \n\ - } \n\ - } \n\ - XMLHttpRequest.prototype.send = function(a,b) { \n\ - s_ajaxListener.tempSend.apply(this, arguments); \n\ - if (s_ajaxListener.method.toLowerCase() === 'post') { \n\ - s_ajaxListener.data = a; \n\ - } \n\ - s_ajaxListener.callback(); \n\ - } \n\ - ", RCTJSAJAXScheme]]; + if (_injectedJavascriptIOS != nil) { + [webView stringByEvaluatingJavaScriptFromString:_injectedJavascriptIOS]; } // we only need the final 'finishLoad' call so only fire the event when we're actually done loading. diff --git a/React/Views/RCTWebViewManager.m b/React/Views/RCTWebViewManager.m index a5de572bd57aa8..ff5fcf26a2b85b 100644 --- a/React/Views/RCTWebViewManager.m +++ b/React/Views/RCTWebViewManager.m @@ -27,14 +27,15 @@ - (UIView *)view RCT_REMAP_VIEW_PROPERTY(html, HTML, NSString); RCT_REMAP_VIEW_PROPERTY(bounces, _webView.scrollView.bounces, BOOL); RCT_REMAP_VIEW_PROPERTY(scrollEnabled, _webView.scrollView.scrollEnabled, BOOL); +RCT_REMAP_VIEW_PROPERTY(scalesPageToFit, _webView.scalesPageToFit, BOOL); +RCT_EXPORT_VIEW_PROPERTY(injectedJavascriptIOS, NSString); RCT_EXPORT_VIEW_PROPERTY(contentInset, UIEdgeInsets); RCT_EXPORT_VIEW_PROPERTY(automaticallyAdjustContentInsets, BOOL); -RCT_EXPORT_VIEW_PROPERTY(shouldInjectAJAXHandler, BOOL); -RCT_REMAP_VIEW_PROPERTY(scalesPageToFit, _webView.scalesPageToFit, BOOL); - (NSDictionary *)constantsToExport { return @{ + @"JSNavigationScheme": RCTJSNavigationScheme, @"NavigationType": @{ @"LinkClicked": @(UIWebViewNavigationTypeLinkClicked), @"FormSubmitted": @(UIWebViewNavigationTypeFormSubmitted),