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 f2d526e06..d179617c1 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 a2ba71673..af2e2879a 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 673d9c58a..471e60691 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 71290af44..fe755bcb6 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 d6479a513..1871e3ead 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 46b4667e7..d9ed7f32f 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 af2e2879a..237691a85 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 d179617c1..ff0faa315 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 8145bfa03..42c1a485f 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 30d05210f..9aad783b0 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 5af5a32c6..fa2fe3bb2 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 146247009..fa5afc5cb 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 000000000..04e3db433 --- /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 099c120a0..95bf55c7c 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 89a77975f..5a4dd1987 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 760fce614..a8a68c0b4 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 @@ typedef void (^RCTCachedDataDownloadBlock)(BOOL cached, NSData *data, NSError *e - (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 @@ static NSString *RCTCacheKeyForURL(NSURL *url) }); }; - 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 @@ static NSString *RCTCacheKeyForURL(NSURL *url) - (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 8704ff3db..000000000 --- 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 9390069c4..000000000 --- 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 95bf55c7c..54261357d 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 838e8bc80..0d89d651d 100644 --- a/React/Base/RCTBatchedBridge.m +++ b/React/Base/RCTBatchedBridge.m @@ -721,7 +721,7 @@ RCT_NOT_IMPLEMENTED(-initWithBundleURL:(__unused NSURL *)bundleURL 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 @@ RCT_NOT_IMPLEMENTED(-initWithBundleURL:(__unused NSURL *)bundleURL 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 905829aba..0c0e2664a 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 5e9b0d855..1aa5efd7d 100644 --- a/React/Base/RCTFPSGraph.m +++ b/React/Base/RCTFPSGraph.m @@ -93,7 +93,7 @@ RCT_NOT_IMPLEMENTED(-initWithCoder:(NSCoder *)aDecoder) 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 13c57fbeb..e4013c32a 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 f3af007f3..cc3f4fc6f 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 56d38f308..31a4d5317 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 d4ea0dd39..c47d762a1 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 a2efcea3b..c5b235da8 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 54c44513e..9ef60c611 100644 --- a/Libraries/RCTTest/RCTTestModule.m +++ b/Libraries/RCTTest/RCTTestModule.m @@ -55,7 +55,7 @@ RCT_EXPORT_METHOD(verifySnapshot:(RCTResponseSenderBlock)callback) }]; } -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 aca8267b9..f294085e2 100644 --- a/React/Base/RCTModuleMethod.m +++ b/React/Base/RCTModuleMethod.m @@ -40,10 +40,12 @@ 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 56d38f308..4082e5314 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 21819df46..c0877d978 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 334ef7461..c2feaa128 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 0d89d651d..b1bb6cf1a 100644 --- a/React/Base/RCTBatchedBridge.m +++ b/React/Base/RCTBatchedBridge.m @@ -219,6 +219,9 @@ RCT_NOT_IMPLEMENTED(-initWithBundleURL:(__unused NSURL *)bundleURL [_frameUpdateObservers addObject:moduleData]; } } + + [[NSNotificationCenter defaultCenter] postNotificationName:RCTDidCreateNativeModules + object:self]; } - (void)initJS diff --git a/React/Base/RCTBridge.h b/React/Base/RCTBridge.h index 1d70a6367..f12f26158 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 28fcc460f..c6baf82a6 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 86a3b96db..5c6ba8f96 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 ef7f076bf..1626249e7 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 0d1061ace..572017574 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 a59a019b3..9e848c3a5 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 69767c4b7..154796cef 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 fa2fe3bb2..1803c7ebd 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 fe3cbef6f..b9137e87c 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 367276567..42cf30f51 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 9e2d37ff9..7ea3f1261 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 098dfcee9..37625978a 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 7da54bdf4..eea5a4eaa 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 13c57fbeb..41c6b5869 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 3877b3dd5..df3ccfd7e 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 21819df46..c0877d978 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 3877b3dd5..df3ccfd7e 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 df3ccfd7e..3877b3dd5 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 15ab9e676..83b1c26f6 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 52e7baaa3..90846e349 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 9e2fd3504..693f00eaa 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 @@ { RCTEventDispatcher *_eventDispatcher; UIWebView *_webView; + NSString *_injectedJavascriptIOS; } - (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher @@ -118,6 +126,19 @@ RCT_NOT_IMPLEMENTED(-initWithCoder:(NSCoder *)aDecoder) 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 @@ RCT_NOT_IMPLEMENTED(-initWithCoder:(NSCoder *)aDecoder) #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 @@ static NSString *const RCTJSAJAXScheme = @"react-ajax"; [_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 @@ static NSString *const RCTJSAJAXScheme = @"react-ajax"; - (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 a5de572bd..ff5fcf26a 100644 --- a/React/Views/RCTWebViewManager.m +++ b/React/Views/RCTWebViewManager.m @@ -27,14 +27,15 @@ RCT_REMAP_VIEW_PROPERTY(url, URL, NSURL); 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),