Skip to content

Commit

Permalink
[TIMOB-24011] iOS: require fails to resolve paths like 'hyperloop/uik…
Browse files Browse the repository at this point in the history
…it/uilabel' (#8493)

* Add unit test for requiring file whose first path segment matches native module id, but isn't part of native module.

* Fix iOS implementation to handle use case properly.

* [TIMOB-24012] Android: crashing trying to load native modules
  • Loading branch information
sgtcoolguy committed Oct 12, 2016
1 parent f5fe41c commit 73b9057
Show file tree
Hide file tree
Showing 6 changed files with 313 additions and 72 deletions.
21 changes: 12 additions & 9 deletions android/runtime/common/src/js/module.js
Expand Up @@ -311,7 +311,8 @@ Module.prototype.require = function (request, context) {
Module.prototype.loadCoreModule = function (id, context) {
var wrapper = this.wrapperCache[id],
parts,
externalBinding;
externalBinding,
externalCommonJsContents;
// check if we have a cached copy of the wrapper
if (wrapper) {
return wrapper;
Expand All @@ -330,14 +331,16 @@ Module.prototype.loadCoreModule = function (id, context) {

// Could be a sub-module (CommonJS) of an external native module.
// We allow that since TIMOB-9730.
externalCommonJsContents = kroll.getExternalCommonJsModule(id);
if (externalCommonJsContents) {
// found it
// FIXME Re-use loadAsJavaScriptText?
var module = new Module(id, this, context);
Module.cache[id] = module;
module.load(id, externalCommonJsContents);
return module.exports;
if (kroll.isExternalCommonJsModule(parts[0])) {
externalCommonJsContents = kroll.getExternalCommonJsModule(id);
if (externalCommonJsContents) {
// found it
// FIXME Re-use loadAsJavaScriptText?
var module = new Module(id, this, context);
Module.cache[id] = module;
module.load(id, externalCommonJsContents);
return module.exports;
}
}
}

Expand Down
6 changes: 6 additions & 0 deletions android/runtime/v8/src/native/KrollBindings.cpp
Expand Up @@ -317,6 +317,12 @@ void KrollBindings::getExternalCommonJsModule(const FunctionCallbackInfo<Value>&
subPath = nameKey.substr(slashPos + 1);
}

bool exists = (externalCommonJsModules.count(moduleRoot) > 0);
if (!exists) {
args.GetReturnValue().Set(v8::Undefined(isolate));
return;
}

JNIEnv *env = JNIScope::getEnv();
jobject sourceProvider = externalCommonJsModules[moduleRoot];
jmethodID sourceRetrievalMethod = commonJsSourceRetrievalMethods[moduleRoot];
Expand Down
127 changes: 64 additions & 63 deletions iphone/Classes/KrollBridge.m
Expand Up @@ -805,6 +805,45 @@ -(NSString*)pathToModuleClassName:(NSString*)path
return modulename;
}

- (TiModule *)loadTopLevelNativeModule:(TiModule *)module withPath:(NSString *)path withContext:(KrollContext *)kroll
{
// does it have JS? No, then nothing else to do...
if (![module isJSModule]) {
return module;
}
NSData* data = [module moduleJS];
if (data == nil) {
// Uh oh, no actual data. Let's just punt and return the native module as-is
return module;
}

NSString* contents = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
KrollWrapper* wrapper = (id) [self loadJavascriptText:contents fromFile:path withContext:kroll];

// For right now, we need to mix any compiled JS on top of a compiled module, so that both components
// are accessible. We store the exports object and then put references to its properties on the toplevel
// object.

TiContextRef jsContext = [[self krollContext] context];
TiObjectRef jsObject = [wrapper jsobject];
KrollObject* moduleObject = [module krollObjectForContext:[self krollContext]];
[moduleObject noteObject:jsObject forTiString:kTiStringExportsKey context:jsContext];

TiPropertyNameArrayRef properties = TiObjectCopyPropertyNames(jsContext, jsObject);
size_t count = TiPropertyNameArrayGetCount(properties);
for (size_t i=0; i < count; i++) {
// Mixin the property onto the module JS object if it's not already there
TiStringRef propertyName = TiPropertyNameArrayGetNameAtIndex(properties, i);
if (!TiObjectHasProperty(jsContext, [moduleObject jsobject], propertyName)) {
TiValueRef property = TiObjectGetProperty(jsContext, jsObject, propertyName, NULL);
TiObjectSetProperty([[self krollContext] context], [moduleObject jsobject], propertyName, property, kTiPropertyAttributeReadOnly, NULL);
}
}
TiPropertyNameArrayRelease(properties);

return module;
}

- (TiModule *)loadCoreModule:(NSString *)path withContext:(KrollContext *)kroll
{
// make sure path doesn't begin with ., .., or /
Expand All @@ -814,17 +853,16 @@ - (TiModule *)loadCoreModule:(NSString *)path withContext:(KrollContext *)kroll
}

// moduleId then is the first path component
// try to load up the native module's class...
NSString *moduleID = [[path pathComponents] objectAtIndex:0];
NSString *moduleClassName = [self pathToModuleClassName:moduleID];
Class moduleClass = NSClassFromString(moduleClassName);

// If no such module exists, bail out!
if (moduleClass == nil) {
return nil;
}

// We have a module to load resources from! Now we need to determine if
// it's a base module (which should be cached) or a pure JS resource
// stored on the module.
// Ok, we have a native module, make sure instantiate and cache it
TiModule *module = [modules objectForKey:moduleID];
if (module == nil) {
module = [[moduleClass alloc] _initWithPageContext:self];
Expand All @@ -834,69 +872,32 @@ - (TiModule *)loadCoreModule:(NSString *)path withContext:(KrollContext *)kroll
[module autorelease];
}

// TODO Handle the oddball case of mixing in JS to the native module...
/*
// TODO: Support package.json 'main' file identifier which will load instead
// of module JS. Currently neither iOS nor Android support package information.
NSRange separatorLocation = [fullPath rangeOfString:@"/"];
if (separatorLocation.location == NSNotFound) { // Indicates toplevel module
loadNativeJS:
if ([module isJSModule]) {
data = [module moduleJS];
}
[self setCurrentURL:[NSURL URLWithString:fullPath relativeToURL:[[self host] baseURL]]];
}
else {
NSString* assetPath = [fullPath substringFromIndex:separatorLocation.location+1];
// Handle the degenerate case (supported by MW) where we're loading
// module.id/module.id, which should resolve to module.id and mixin.
// Rather than create a utility method for this (or C&P if native loading changes)
// we use a goto to jump into the if block above.
if ([assetPath isEqualToString:moduleID]) {
goto loadNativeJS;
}
NSString* filepath = [assetPath stringByAppendingString:@".js"];
data = [module loadModuleAsset:filepath];
// Have to reset module so that this code doesn't get mixed in and is loaded as pure JS
module = nil;
// Are they just trying to load the top-level module?
NSRange separatorLocation = [path rangeOfString:@"/"];
if (separatorLocation.location == NSNotFound) {
// Indicates toplevel module
return [self loadTopLevelNativeModule:module withPath:path withContext:kroll];
}

if (data == nil && isAbsolute) {
// We may have an absolute URL which tried to load from a module instead of a directory. Fix
// the fullpath back to the right value, so we can try again.
fullPath = [path hasPrefix:@"/"]?[path substringFromIndex:1]:path;
}
else if (data != nil) {
// Set the current URL; it should be the fullPath relative to the host's base URL.
[self setCurrentURL:[NSURL URLWithString:[fullPath stringByDeletingLastPathComponent] relativeToURL:[[self host] baseURL]]];
// check rest of path
NSString* assetPath = [path substringFromIndex: separatorLocation.location + 1];
// Treat require('module.id/module.id') == require('module.id')
if ([assetPath isEqualToString:moduleID]) {
return [self loadTopLevelNativeModule:module withPath:path withContext:kroll];
}

// For right now, we need to mix any compiled JS on top of a compiled module, so that both components
// are accessible. We store the exports object and then put references to its properties on the toplevel
// object.
TiContextRef jsContext = [[self krollContext] context];
TiObjectRef jsObject = [wrapper jsobject];
KrollObject* moduleObject = [module krollObjectForContext:[self krollContext]];
[moduleObject noteObject:jsObject forTiString:kTiStringExportsKey context:jsContext];
TiPropertyNameArrayRef properties = TiObjectCopyPropertyNames(jsContext, jsObject);
size_t count = TiPropertyNameArrayGetCount(properties);
for (size_t i=0; i < count; i++) {
// Mixin the property onto the module JS object if it's not already there
TiStringRef propertyName = TiPropertyNameArrayGetNameAtIndex(properties, i);
if (!TiObjectHasProperty(jsContext, [moduleObject jsobject], propertyName)) {
TiValueRef property = TiObjectGetProperty(jsContext, jsObject, propertyName, NULL);
TiObjectSetProperty([[self krollContext] context], [moduleObject jsobject], propertyName, property, kTiPropertyAttributeReadOnly, NULL);
}
// not top-level module!
// Try to load the file as module asset!
NSString* filepath = [assetPath stringByAppendingString:@".js"];
NSData* data = [module loadModuleAsset:filepath];
// does it exist in module?
if (data == nil) {
// nope, return nil so we can try to fall back to resource in user's app
return nil;
}
TiPropertyNameArrayRelease(properties);
*/

return module;
NSString* contents = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
// This is an asset inside the native module. Load it like a "normal" common js file
return [self loadJavascriptText:contents fromFile:filepath withContext:kroll];
}

- (NSString *)loadFile:(NSString *)path
Expand Down Expand Up @@ -1187,7 +1188,7 @@ - (id)require:(KrollContext *)kroll path:(NSString *)path

// TODO Find a way to determine if the first path segment refers to a CommonJS module, and if so don't log
// TODO How can we make this spit this out to Ti.API.log?
NSLog(@"require called with un-prefixed module id, should be a core or CommonJS module. Falling back to old Ti behavior and assuming it's an absolute file");
NSLog(@"require called with un-prefixed module id: %@, should be a core or CommonJS module. Falling back to old Ti behavior and assuming it's an absolute path: /%@", path, path);
module = [self loadAsFileOrDirectory:[path stringByStandardizingPath] withContext:context];
if (module) {
return module;
Expand Down
3 changes: 3 additions & 0 deletions tests/Resources/facebook/example.js
@@ -0,0 +1,3 @@
module.exports = {
name: 'facebook/example.js'
};

0 comments on commit 73b9057

Please sign in to comment.