Skip to content

Commit

Permalink
Bytecode client for iOS
Browse files Browse the repository at this point in the history
Summary: Changelog: [Internal]

Reviewed By: javache

Differential Revision: D21206851

fbshipit-source-id: 67ab59688c19870ef419711fdfd489bf0442bb54
  • Loading branch information
cpojer authored and facebook-github-bot committed Jun 15, 2020
1 parent 6d6268e commit 382f089
Show file tree
Hide file tree
Showing 4 changed files with 52 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@
#import "RCTConvert.h"
#import "RCTDefines.h"

#if __has_include("hermes.h") || __has_include(<hermes/hermes.h>)
#import <hermes/hermes.h>
#define HAS_BYTECODE_VERSION
#endif

NSString *const RCTBundleURLProviderUpdatedNotification = @"RCTBundleURLProviderUpdatedNotification";

const NSUInteger kRCTBundleURLProviderDefaultPort = RCT_METRO_PORT;
Expand Down Expand Up @@ -214,12 +219,21 @@ + (NSURL *)jsBundleURLForBundleRoot:(NSString *)bundleRoot
runModule:(BOOL)runModule
{
NSString *path = [NSString stringWithFormat:@"/%@.bundle", bundleRoot];
#ifdef HAS_BYTECODE_VERSION
NSString *runtimeBytecodeVersion =
[NSString stringWithFormat:@"&runtimeBytecodeVersion=%u", facebook::hermes::HermesRuntime::getBytecodeVersion()];
#else
NSString *runtimeBytecodeVersion = @"";
#endif

// When we support only iOS 8 and above, use queryItems for a better API.
NSString *query = [NSString stringWithFormat:@"platform=ios&dev=%@&minify=%@&modulesOnly=%@&runModule=%@",
NSString *query = [NSString stringWithFormat:@"platform=ios&dev=%@&minify=%@&modulesOnly=%@&runModule=%@%@",
enableDev ? @"true" : @"false",
enableMinification ? @"true" : @"false",
modulesOnly ? @"true" : @"false",
runModule ? @"true" : @"false"];
runModule ? @"true" : @"false",
runtimeBytecodeVersion];

NSString *bundleID = [[NSBundle mainBundle] objectForInfoDictionaryKey:(NSString *)kCFBundleIdentifierKey];
if (bundleID) {
query = [NSString stringWithFormat:@"%@&app=%@", query, bundleID];
Expand Down
5 changes: 5 additions & 0 deletions React/Base/RCTJavaScriptLoader.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@

extern NSString *const RCTJavaScriptLoaderErrorDomain;

extern const UInt32 RCT_BYTECODE_ALIGNMENT;

UInt32 RCTReadUInt32LE(NSData *script, UInt32 offset);
bool RCTIsBytecodeBundle(NSData *script);

NS_ENUM(NSInteger){
RCTJavaScriptLoaderErrorNoScriptURL = 1,
RCTJavaScriptLoaderErrorFailedOpeningFile = 2,
Expand Down
25 changes: 22 additions & 3 deletions React/Base/RCTJavaScriptLoader.mm
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,20 @@

static const int32_t JSNoBytecodeFileFormatVersion = -1;

const UInt32 RCT_BYTECODE_ALIGNMENT = 4;
UInt32 RCTReadUInt32LE(NSData *script, UInt32 offset)
{
return [script length] < offset + 4 ? 0 : CFSwapInt32LittleToHost(*(((uint32_t *)[script bytes]) + offset / 4));
}

bool RCTIsBytecodeBundle(NSData *script)
{
static const UInt32 BYTECODE_BUNDLE_MAGIC_NUMBER = 0xffe7c3c3;
return (
[script length] > 8 && RCTReadUInt32LE(script, 0) == BYTECODE_BUNDLE_MAGIC_NUMBER &&
RCTReadUInt32LE(script, 4) > 0);
}

@interface RCTSource () {
@public
NSURL *_url;
Expand All @@ -37,7 +51,10 @@ @implementation RCTSource
{
RCTSource *source = [RCTSource new];
source->_url = url;
source->_data = data;
// Multipart responses may give us an unaligned view into the buffer. This ensures memory is aligned.
source->_data = (RCTIsBytecodeBundle(data) && ((long)[data bytes] % RCT_BYTECODE_ALIGNMENT))
? [[NSData alloc] initWithData:data]
: data;
source->_length = length;
source->_filesChangedCount = RCTSourceFilesChangedCountNotBuiltByBundler;
return source;
Expand Down Expand Up @@ -300,7 +317,8 @@ static void attemptAsynchronousLoadOfBundleAtURL(
// Validate that the packager actually returned javascript.
NSString *contentType = headers[@"Content-Type"];
NSString *mimeType = [[contentType componentsSeparatedByString:@";"] firstObject];
if (![mimeType isEqualToString:@"application/javascript"] && ![mimeType isEqualToString:@"text/javascript"]) {
if (![mimeType isEqualToString:@"application/javascript"] && ![mimeType isEqualToString:@"text/javascript"] &&
![mimeType isEqualToString:@"application/x-metro-bytecode-bundle"]) {
NSString *description;
if ([mimeType isEqualToString:@"application/json"]) {
NSError *parseError;
Expand Down Expand Up @@ -332,7 +350,8 @@ static void attemptAsynchronousLoadOfBundleAtURL(
}
progressHandler:^(NSDictionary *headers, NSNumber *loaded, NSNumber *total) {
// Only care about download progress events for the javascript bundle part.
if ([headers[@"Content-Type"] isEqualToString:@"application/javascript"]) {
if ([headers[@"Content-Type"] isEqualToString:@"application/javascript"] ||
[headers[@"Content-Type"] isEqualToString:@"application/x-metro-bytecode-bundle"]) {
onProgress(progressEventFromDownloadProgress(loaded, total));
}
}];
Expand Down
10 changes: 9 additions & 1 deletion React/CxxBridge/RCTCxxBridge.mm
Original file line number Diff line number Diff line change
Expand Up @@ -1414,7 +1414,15 @@ - (void)executeApplicationScript:(NSData *)script url:(NSURL *)url async:(BOOL)a
// hold a local reference to reactInstance in case a parallel thread
// resets it between null check and usage
auto reactInstance = self->_reactInstance;
if (isRAMBundle(script)) {
if (reactInstance && RCTIsBytecodeBundle(script)) {
UInt32 offset = 8;
while (offset < script.length) {
UInt32 fileLength = RCTReadUInt32LE(script, offset);
NSData *unit = [script subdataWithRange:NSMakeRange(offset + 4, fileLength)];
reactInstance->loadScriptFromString(std::make_unique<NSDataBigString>(unit), sourceUrlStr.UTF8String, false);
offset += ((fileLength + RCT_BYTECODE_ALIGNMENT - 1) & ~(RCT_BYTECODE_ALIGNMENT - 1)) + 4;
}
} else if (isRAMBundle(script)) {
[self->_performanceLogger markStartForTag:RCTPLRAMBundleLoad];
auto ramBundle = std::make_unique<JSIndexedRAMBundle>(sourceUrlStr.UTF8String);
std::unique_ptr<const JSBigString> scriptStr = ramBundle->getStartupCode();
Expand Down

0 comments on commit 382f089

Please sign in to comment.