Titanium MobileでJavaScriptが実行されるまでのソースコード追ってみた このエントリをはてなブックマークに登録

2011年08月10日

ダニーダニー / , ,

はじめに

毎日が夏休みのみなさんこんにちは。ダニーです。

Titanium Mobile使ってると、なんでiPhoneアプリなのにObjective-CじゃなくてJavaScriptで作れるか大変不思議ですよね。
今回はTitanium Mobileでアプリ起動から、JavaScript(app.js)を読み込んで実行しているところまでをソースコードを読んで追ってみました。
方針としては、Titanium Mobileで適当なプロジェクトを一つ作成してビルドして生成された/build/iphone/以下にあるソースコードを読んで行きます。
今回のビルドに使ったTitanium SDKのバージョンは1.7.1です。
今回、掲載してるソースコードで行番号が飛んでる部分は長いので省略しています。

ソースコード

/build/iphone/main.m

25:int main(int argc, char *argv[]) {
26:    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
27:
28:#ifdef __LOG__ID__
29:    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
30:    NSString *documentsDirectory = [paths objectAtIndex:0];
31:    NSString *logPath = [documentsDirectory stringByAppendingPathComponent:[NSString stringWithFormat:@"%s.log",STRING(__LOG__ID__)]];
32:    freopen([logPath cStringUsingEncoding:NSUTF8StringEncoding],"w+",stderr);
33:    fprintf(stderr,"[INFO] Application started\n");
34:#endif
35:
36:    int retVal = UIApplicationMain(argc, argv, nil, @"TiApp");
37:    [pool release];
38:    return retVal;
39:}

UIApplicationMain(argc, argv, nil, @”TiApp”)なのでTiApp.mmを見ていきます。
TiAppではアプリケーションが起動したときに呼ばれるapplication:didFinishLaunchingWithOptionsを見ていきます。

/build/iphone/Classes/TiApp.mm

350:- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions_
351:{
352:    started = [NSDate timeIntervalSinceReferenceDate];
353:    NSSetUncaughtExceptionHandler(&MyUncaughtExceptionHandler);
354:
355:    // nibless window
356:    window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
357:
358:    [self initController];
359:
(中略)
384:    if (notification!=nil)
385:    {
386:        [self generateNotification:notification];
387:    }
388:
389:    [self boot];
390:
391:    return YES;
392:}

[self boot]で起動してるみたいなので[self boot]を見ていきます

/build/iphone/Classes/TiApp.mm

281:- (void)boot
282:{
283:    NSLog(@"[INFO] %@/%@ (%s.1293a6d)",TI_APPLICATION_NAME,TI_APPLICATION_VERSION,TI_VERSION_STR);
284:
285:    sessionId = [[TiUtils createUUID] retain];
286:    TITANIUM_VERSION = [[NSString stringWithCString:TI_VERSION_STR encoding:NSUTF8StringEncoding] retain];
287:
288:    NSString *filePath = [[NSBundle mainBundle] pathForResource:@"debugger" ofType:@"plist"];
289:    if (filePath != nil) {
290:        NSMutableDictionary *params = [[NSMutableDictionary alloc] initWithContentsOfFile:filePath];
291:        NSString *host = [params objectForKey:@"host"];
292:        NSString *port = [params objectForKey:@"port"];
293:        if (host != nil && ![host isEqual:@""] && ![host isEqual:@"__DEBUGGER_HOST__"])
294:        {
295:            [self setDebugMode:YES];
296:            TiDebuggerStart(host,[port intValue]);
297:        }
298:    }
299:
300:    kjsBridge = [[KrollBridge alloc] initWithHost:self];
301:
302:    [kjsBridge boot:self url:nil preload:nil];
303:#if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_4_0
304:    if ([TiUtils isIOS4OrGreater])
305:    {
306:       [[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
307:    }
308:#endif
309:}

[kjsBridge boot:self url:nil preload:nil];を見ていきます。
kjsBridgeはKrollBridgeクラスなのでKrollBridgeのbootは以下になってました。

/build/iphone/Classes/KrollBridge.mm

315:- (void)boot:(id)callback url:(NSURL*)url_ preload:(NSDictionary*)preload_
316:{
317:    preload = [preload_ retain];
318:    [super boot:callback url:url_ preload:preload_];
319:    context = [[KrollContext alloc] init];
320:    context.delegate = self;
321:    [context start];
322:}

[context start];を見ていきます。

/build/iphone/Classes/KrollContext.mm

747:-(void)start
748:{
749:    if (stopped!=YES)
750:    {
751:        @throw [NSException exceptionWithName:@"org.test3.kroll"
752:                                        reason:@"already started"
753:                                        userInfo:nil];
754:    }
755:    stopped = NO;
756:    [NSThread detachNewThreadSelector:@selector(main) toTarget:self withObject:nil];
757:}

@selector(main)で指定されてるメソッドを見ていきます。

/build/iphone/Classes/KrollContext.mm

942:-(void)main
943:{
944:    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
945:    [[NSThread currentThread] setName:[self threadName]];
946:    pthread_rwlock_rdlock(&KrollGarbageCollectionLock);
947:// context = TiGlobalContextCreateInGroup([TiApp contextGroup],NULL);
948:    context = TiGlobalContextCreate(NULL);
949:    TiObjectRef globalRef = TiContextGetGlobalObject(context);
950:
951:
952:    // TODO: We might want to be smarter than this, and do some KVO on the delegate's
953:    // 'debugMode' property or something... and start/stop the debugger as necessary.
954:    if ([[self delegate] shouldDebugContext]) {
955:        debugger = TiDebuggerCreate(self,globalRef);
956:    }
(中略)
1042:   loopCount = 0;
1043:   #define GC_LOOP_COUNT 5
1044:
1045:   if (delegate!=nil && [delegate respondsToSelector:@selector(didStartNewContext:)])
1046:   {
1047:      [delegate performSelector:@selector(didStartNewContext:) withObject:self];
1048:   }
1049:   pthread_rwlock_unlock(&KrollGarbageCollectionLock);
1050:
1051:   BOOL exit_after_flush = NO;

[delegate performSelector:@selector(didStartNewContext:) withObject:self]
を見ていきます。KrollBridgeのbootでのdelegateは、以下のメソッドのcontext.delegateで設定している値で、

/build/iphone/Classes/KrollBridge.mm

315:- (void)boot:(id)callback url:(NSURL*)url_ preload:(NSDictionary*)preload_
316:{
317:    preload = [preload_ retain];
318:    [super boot:callback url:url_ preload:preload_];
319:    context = [[KrollContext alloc] init];
320:    context.delegate = self;
321:    [context start];
322:}

selfの型はKrollBridgeクラスなのでKrollBridgeのdidStartNewContextを見ていきます。

/build/iphone/Classes/KrollBridge.mm

523:-(void)didStartNewContext:(KrollContext*)kroll
524:{
525:    // create test3 global object
526:    NSString *basePath = (url==nil) ? [TiHost resourcePath] : [[[url path] stringByDeletingLastPathComponent] stringByAppendingPathComponent:@"."];
527:    _test3 = [[test3Object alloc] initWithContext:kroll host:host context:self baseURL:[NSURL fileURLWithPath:basePath]];
(中略)
563:    else
564:    {
565:        // now load the app.js file and get started
566:        NSURL *startURL = [host startURL];
567:        [self injectPatches];
568:        [self evalFile:[startURL absoluteString] callback:self selector:@selector(booted)];
569:    }
570:}

ここでapp.jsを読もうとしてますね。
[self evalFile:[startURL absoluteString] callback:self selector:@selector(booted)]を見ていきます。

437:- (void)evalFile:(NSString*)path callback:(id)callback selector:(SEL)selector
438:{
439:   [context invokeOnThread:self method:@selector(evalFileOnThread:context:) withObject:path callback:callback selector:selector];
440:}

ここで@selector(evalFileOnThread:context:)で指定してるメソッドは以下のようになっています。

346:- (void)evalFileOnThread:(NSString*)path context:(KrollContext*)context_
347:{
348:    NSError *error = nil;
349:    TiValueRef exception = NULL;
350:
351:    TiContextRef jsContext = [context_ context];
(中略)
395:    const char *urlCString = [[url_ absoluteString] UTF8String];
396:
397:    TiStringRef jsCode = TiStringCreateWithCFString((CFStringRef) jcode);
398:    TiStringRef jsURL = TiStringCreateWithUTF8CString(urlCString);
399:
400:    // validate script
401:   // TODO: we do not need to do this in production app
402:   if (!TiCheckScriptSyntax(jsContext,jsCode,jsURL,1,&exception))
403:   {
404:        id excm = [KrollObject toID:context value:exception];
405:        NSLog(@"[ERROR] Syntax Error = %@",[TiUtils exceptionMessage:excm]);
406:        [self scriptError:[TiUtils exceptionMessage:excm]];
407:   }
408:
409:    // only continue if we don't have any exceptions from above
410:    if (exception == NULL)
411:    {
412:        if ([[self host] debugMode]) {
413:            TiDebuggerBeginScript(context_,urlCString);
414:        }
415:
416:        TiEvalScript(jsContext, jsCode, NULL, jsURL, 1, &exception);
417:
418:        if ([[self host] debugMode]) {
419:            TiDebuggerEndScript(context_);
420:        }
421:
422:        if (exception!=NULL)
423:        {
424:            id excm = [KrollObject toID:context value:exception];
425:            NSLog(@"[ERROR] Script Error = %@.",[TiUtils exceptionMessage:excm]);
426:            [self scriptError:[TiUtils exceptionMessage:excm]];
427:        }
428:        else {
429:            evaluationError = NO;
430:        }

TiCheckScriptSyntaxでJavaScriptの構文をチェックして。TiEvalScriptでJavaScriptを実行しているみたいです。

TiEvalScriptは/build/iphone/headers/TiCore/TiBase.hで定義されていました。

105:/* Script Evaluation */
106:
107:/*!
108:@function TiEvalScript
109:@abstract Evaluates a string of Ti.
110:@param ctx The execution context to use.
111:@param script A TiString containing the script to evaluate.
112:@param thisObject The object to use as "this," or NULL to use the global object as "this."
113:@param sourceURL A TiString containing a URL for the script's source file. This is only used when reporting exceptions. Pass NULL if you do not care to include source file information in exceptions.
114:@param startingLineNumber An integer value specifying the script's starting line number in the file located at sourceURL. This is only used when reporting exceptions.
115:@param exception A pointer to a TiValueRef in which to store an exception, if any. Pass NULL if you do not care to store an exception.
116:@result The TiValue that results from evaluating script, or NULL if an exception is thrown.
117:*/

118:JS_EXPORT TiValueRef TiEvalScript(TiContextRef ctx, TiStringRef script, TiObjectRef thisObject, TiStringRef sourceURL, int startingLineNumber, TiValueRef* exception);

で宣言されています。
JS_EXPORTは以下のようになっていて、共有ライブラリから読みこむようになってました。

/build/iphone/headers/TiCore/TiBase.h

72:/* Ti symbol exports */
73:
74:#undef JS_EXPORT
75:#if defined(BUILDING_WX__)
76:    #define JS_EXPORT
77:#elif defined(__GNUC__) && !defined(__CC_ARM) && !defined(__ARMCC__)
78:    #define JS_EXPORT __attribute__((visibility("default")))
79:#elif defined(_WIN32_WCE)
80:    #if defined(JS_BUILDING_JS)
81:        #define JS_EXPORT __declspec(dllexport)
82:    #elif defined(JS_IMPORT_JS)
83:        #define JS_EXPORT __declspec(dllimport)
84:    #else
85:        #define JS_EXPORT
86:    #endif
87:#elif defined(WIN32) || defined(_WIN32)
88:    /*
89:     * TODO: Export symbols with JS_EXPORT when using MSVC.
90:     * See http://bugs.webkit.org/show_bug.cgi?id=16227
91:     */

92:    #if defined(BUILDING_TiCore) || defined(BUILDING_WTF)
93:    #define JS_EXPORT __declspec(dllexport)
94:    #else
95:    #define JS_EXPORT __declspec(dllimport)
96:    #endif
97:#else
98:    #define JS_EXPORT
99:#endif

TiEvalScriptの実装は/build/iphone以下を探してもみつからなかったので、
https://github.com/appcelerator/titanium_mobileを見てみたら
titanium_mobile/iphone/SConstructに以下のように書いてあり、

10:# NOTE: this is simply a pre-built version of the source at http://github.com/appcelerator/tijscore
11:# since this is so freaking complicated to setup and build in an stable environment, and since
12:# it takes like an hour to build the library, we have, as a convenience, pre-built it from the
13:# exact same source and are providing the pre-compiled versions for i386/arm

http://github.com/appcelerator/tijscoreに実装のソースコードがありました。

/TiCore/API/TiBase.cpp

52:TiValueRef TiEvalScript(TiContextRef ctx, TiStringRef script, TiObjectRef thisObject, TiStringRef sourceURL, int startingLineNumber, TiValueRef* exception)
53:{
54:    TiExcState* exec = toJS(ctx);
55:    APIEntryShim entryShim(exec);
56:
57:    TiObject* jsThisObject = toJS(thisObject);
58:
59:    // evaluate sets "this" to the global object if it is NULL
60:    TiGlobalObject* globalObject = exec->dynamicGlobalObject();
61:    SourceCode source = makeSource(script->ustring(), sourceURL->ustring(), startingLineNumber);
62:    Completion completion = evaluate(globalObject->globalExec(), globalObject->globalScopeChain(), source, jsThisObject);
63:
64:    if (completion.complType() == Throw) {
65:        if (exception)
66:            *exception = toRef(exec, completion.value());
67:        return 0;
68:    }
69:
70:    if (completion.value())
71:        return toRef(exec, completion.value());
72:
73:    // happens, for example, when the only statement is an empty (';') statement
74:    return toRef(exec, jsUndefined());
75:}

tijscoreのREADMEによると
以下のような記述があり。WebKitのKJS(JavaScirptの処理系)をフォークして使ってるようです。

This is a Titanium Mobile fork of WebKit KJS. All changes are made available
under the Apache Public License (version 2).

最後に

長々とソースコードを追ってみましたが。
Titanium MobileでのJavaScriptの実行は、WebKitのKJSをフォークして使ってるという話でした。

クレイについてもっと知りたい方は…

  1. クレイの3つの強みを見てみる。
  2. WEBシステムのことなら何でもご相談ください。

「いいね!」で応援よろしくお願いします!

このエントリーに対するコメント

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)


トラックバック
  1. taichino.com2012/09/21, 3:22 AM

    [Titanium勉強日記 5] Viewを作るJavaScriptが実行される時の流れを追ってみた

    モジュールの作り方が何となく分かったので、さっそくカスタムViewを書き始めましたが、幾つか分からない点があったので記録しておきます。 先ほどの記事にも書きましたが、欲しいの…

we use!!Ruby on RailsAmazon Web Services

このページの先頭へ