PageRenderTime 84ms CodeModel.GetById 24ms app.highlight 54ms RepoModel.GetById 1ms app.codeStats 0ms

/Objective-J/CFBundle.js

http://github.com/cacaodev/cappuccino
JavaScript | 740 lines | 537 code | 173 blank | 30 comment | 72 complexity | 76bbe0c73e1883f32fd44d74f6d38d64 MD5 | raw file
  1/*
  2 * CFBundle.js
  3 * Objective-J
  4 *
  5 * Created by Francisco Tolmasky.
  6 * Copyright 2008-2010, 280 North, Inc.
  7 *
  8 * This library is free software; you can redistribute it and/or
  9 * modify it under the terms of the GNU Lesser General Public
 10 * License as published by the Free Software Foundation; either
 11 * version 2.1 of the License, or (at your option) any later version.
 12 *
 13 * This library is distributed in the hope that it will be useful,
 14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 16 * Lesser General Public License for more details.
 17 *
 18 * You should have received a copy of the GNU Lesser General Public
 19 * License along with this library; if not, write to the Free Software
 20 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 21 */
 22
 23var CFBundleUnloaded                = 0,
 24    CFBundleLoading                 = 1 << 0,
 25    CFBundleLoadingInfoPlist        = 1 << 1,
 26    CFBundleLoadingExecutable       = 1 << 2,
 27    CFBundleLoadingSpritedImages    = 1 << 3,
 28    CFBundleLoaded                  = 1 << 4;
 29
 30var CFBundlesForURLStrings   = { },
 31    CFBundlesForClasses      = { },
 32    CFBundlesWithIdentifiers = { },
 33    CFCacheBuster       = new Date().getTime(),
 34    CFTotalBytesLoaded  = 0,
 35    CPApplicationSizeInBytes = 0;
 36
 37GLOBAL(CFBundle) = function(/*CFURL|String*/ aURL)
 38{
 39    aURL = makeAbsoluteURL(aURL).asDirectoryPathURL();
 40
 41    var URLString = aURL.absoluteString(),
 42        existingBundle = CFBundlesForURLStrings[URLString];
 43
 44    if (existingBundle)
 45        return existingBundle;
 46
 47    CFBundlesForURLStrings[URLString] = this;
 48
 49    this._bundleURL = aURL;
 50    this._resourcesDirectoryURL = new CFURL("Resources/", aURL);
 51
 52    this._staticResource = NULL;
 53    this._isValid = NO;
 54
 55    this._loadStatus = CFBundleUnloaded;
 56    this._loadRequests = [];
 57
 58    this._infoDictionary = new CFDictionary();
 59
 60    this._eventDispatcher = new EventDispatcher(this);
 61}
 62
 63DISPLAY_NAME(CFBundle);
 64
 65CFBundle.environments = function()
 66{
 67    // Passed in by GCC.
 68    return ENVIRONMENTS;
 69};
 70
 71DISPLAY_NAME(CFBundle.environments);
 72
 73CFBundle.bundleContainingURL = function(/*CFURL|String*/ aURL)
 74{
 75    aURL = new CFURL(".", makeAbsoluteURL(aURL));
 76
 77    var previousURLString,
 78        URLString = aURL.absoluteString();
 79
 80    while (!previousURLString || previousURLString !== URLString)
 81    {
 82        var bundle = CFBundlesForURLStrings[URLString];
 83
 84        if (bundle && bundle._isValid)
 85            return bundle;
 86
 87        aURL = new CFURL("..", aURL);
 88        previousURLString = URLString;
 89        URLString = aURL.absoluteString();
 90    }
 91
 92    return NULL;
 93};
 94
 95DISPLAY_NAME(CFBundle.bundleContainingURL);
 96
 97CFBundle.mainBundle = function()
 98{
 99    return new CFBundle(mainBundleURL);
100};
101
102DISPLAY_NAME(CFBundle.mainBundle);
103
104function addClassToBundle(aClass, aBundle)
105{
106    if (aBundle)
107        CFBundlesForClasses[aClass.name] = aBundle;
108}
109
110function resetBundle()
111{
112    CFBundlesForURLStrings   = { };
113    CFBundlesForClasses      = { };
114    CFBundlesWithIdentifiers = { };
115    //CFCacheBuster       = new Date().getTime(),
116    CFTotalBytesLoaded  = 0;
117    CPApplicationSizeInBytes = 0;
118}
119
120CFBundle.bundleForClass = function(/*Class*/ aClass)
121{
122    return CFBundlesForClasses[aClass.name] || CFBundle.mainBundle();
123};
124
125DISPLAY_NAME(CFBundle.bundleForClass);
126
127CFBundle.bundleWithIdentifier = function(/*String*/ bundleID)
128{
129    return CFBundlesWithIdentifiers[bundleID] || NULL;
130};
131
132DISPLAY_NAME(CFBundle.bundleWithIdentifier);
133
134CFBundle.prototype.bundleURL = function()
135{
136    return this._bundleURL.absoluteURL();
137};
138
139DISPLAY_NAME(CFBundle.prototype.bundleURL);
140
141CFBundle.prototype.resourcesDirectoryURL = function()
142{
143    return this._resourcesDirectoryURL;
144};
145
146DISPLAY_NAME(CFBundle.prototype.resourcesDirectoryURL);
147
148CFBundle.prototype.resourceURL = function(/*String*/ aResourceName, /*String*/ aType, /*String*/ aSubDirectory)
149 {
150    if (aType)
151        aResourceName = aResourceName + "." + aType;
152
153    if (aSubDirectory)
154        aResourceName = aSubDirectory + "/" + aResourceName;
155
156    var resourceURL = (new CFURL(aResourceName, this.resourcesDirectoryURL())).mappedURL();
157
158    return resourceURL.absoluteURL();
159};
160
161DISPLAY_NAME(CFBundle.prototype.resourceURL);
162
163CFBundle.prototype.mostEligibleEnvironmentURL = function()
164{
165    if (this._mostEligibleEnvironmentURL === undefined)
166        this._mostEligibleEnvironmentURL = new CFURL(this.mostEligibleEnvironment() + ".environment/", this.bundleURL());
167
168    return this._mostEligibleEnvironmentURL;
169}
170
171DISPLAY_NAME(CFBundle.prototype.mostEligibleEnvironmentURL);
172
173CFBundle.prototype.executableURL = function()
174{
175    if (this._executableURL === undefined)
176    {
177        var executableSubPath = this.valueForInfoDictionaryKey("CPBundleExecutable");
178
179        if (!executableSubPath)
180            this._executableURL = NULL;
181        else
182            this._executableURL = new CFURL(executableSubPath, this.mostEligibleEnvironmentURL());
183    }
184
185    return this._executableURL;
186};
187
188DISPLAY_NAME(CFBundle.prototype.executableURL);
189
190CFBundle.prototype.infoDictionary = function()
191{
192    return this._infoDictionary;
193};
194
195DISPLAY_NAME(CFBundle.prototype.infoDictionary);
196
197CFBundle.prototype.valueForInfoDictionaryKey = function(/*String*/ aKey)
198{
199    return this._infoDictionary.valueForKey(aKey);
200};
201
202DISPLAY_NAME(CFBundle.prototype.valueForInfoDictionaryKey);
203
204CFBundle.prototype.identifier = function()
205{
206    return this._infoDictionary.valueForKey("CPBundleIdentifier");
207};
208
209DISPLAY_NAME(CFBundle.prototype.identifier);
210
211CFBundle.prototype.hasSpritedImages = function()
212{
213    var environments = this._infoDictionary.valueForKey("CPBundleEnvironmentsWithImageSprites") || [],
214        index = environments.length,
215        mostEligibleEnvironment = this.mostEligibleEnvironment();
216
217    while (index--)
218        if (environments[index] === mostEligibleEnvironment)
219            return YES;
220
221    return NO;
222};
223
224DISPLAY_NAME(CFBundle.prototype.hasSpritedImages);
225
226CFBundle.prototype.environments = function()
227{
228    return this._infoDictionary.valueForKey("CPBundleEnvironments") || ["ObjJ"];
229};
230
231DISPLAY_NAME(CFBundle.prototype.environments);
232
233CFBundle.prototype.mostEligibleEnvironment = function(/*Array*/ environments)
234{
235    environments = environments || this.environments();
236
237    var objj_environments = CFBundle.environments(),
238        index = 0,
239        count = objj_environments.length,
240        innerCount = environments.length;
241
242    // Ugh, no indexOf, no objects-in-common.
243    for(; index < count; ++index)
244    {
245        var innerIndex = 0,
246            environment = objj_environments[index];
247
248        for (; innerIndex < innerCount; ++innerIndex)
249            if(environment === environments[innerIndex])
250                return environment;
251    }
252
253    return NULL;
254};
255
256DISPLAY_NAME(CFBundle.prototype.mostEligibleEnvironment);
257
258CFBundle.prototype.isLoading = function()
259{
260    return this._loadStatus & CFBundleLoading;
261};
262
263DISPLAY_NAME(CFBundle.prototype.isLoading);
264
265CFBundle.prototype.isLoaded = function()
266{
267    return !!(this._loadStatus & CFBundleLoaded);
268};
269
270DISPLAY_NAME(CFBundle.prototype.isLoaded);
271
272CFBundle.prototype.load = function(/*BOOL*/ shouldExecute)
273{
274    if (this._loadStatus !== CFBundleUnloaded)
275        return;
276
277    this._loadStatus = CFBundleLoading | CFBundleLoadingInfoPlist;
278
279    var self = this,
280        bundleURL = this.bundleURL(),
281        parentURL = new CFURL("..", bundleURL);
282
283    if (parentURL.absoluteString() === bundleURL.absoluteString())
284        parentURL = parentURL.schemeAndAuthority();
285
286    StaticResource.resolveResourceAtURL(parentURL, YES, function(aStaticResource)
287    {
288        var resourceName = bundleURL.lastPathComponent();
289
290        self._staticResource =  aStaticResource._children[resourceName] ||
291                                new StaticResource(bundleURL, aStaticResource, YES, NO);
292
293        function onsuccess(/*Event*/ anEvent)
294        {
295            self._loadStatus &= ~CFBundleLoadingInfoPlist;
296
297            var infoDictionary = anEvent.request.responsePropertyList();
298
299            self._isValid = !!infoDictionary || CFBundle.mainBundle() === self;
300
301            if (infoDictionary)
302            {
303                self._infoDictionary = infoDictionary;
304
305                var identifier = self._infoDictionary.valueForKey("CPBundleIdentifier");
306
307                if (identifier)
308                    CFBundlesWithIdentifiers[identifier] = self;
309            }
310
311            if (!self._infoDictionary)
312            {
313                finishBundleLoadingWithError(self, new Error("Could not load bundle at \"" + path + "\""));
314
315                return;
316            }
317
318            if (self === CFBundle.mainBundle() && self.valueForInfoDictionaryKey("CPApplicationSize"))
319                CPApplicationSizeInBytes = self.valueForInfoDictionaryKey("CPApplicationSize").valueForKey("executable") || 0;
320
321            loadExecutableAndResources(self, shouldExecute);
322        }
323
324        function onfailure()
325        {
326            self._isValid = CFBundle.mainBundle() === self;
327            self._loadStatus = CFBundleUnloaded;
328
329            finishBundleLoadingWithError(self, new Error("Could not load bundle at \"" + self.bundleURL() + "\""));
330        }
331
332        new FileRequest(new CFURL("Info.plist", self.bundleURL()), onsuccess, onfailure);
333    });
334};
335
336DISPLAY_NAME(CFBundle.prototype.load);
337
338function finishBundleLoadingWithError(/*CFBundle*/ aBundle, /*Event*/ anError)
339{
340    resolveStaticResource(aBundle._staticResource);
341
342    aBundle._eventDispatcher.dispatchEvent(
343    {
344        type:"error",
345        error:anError,
346        bundle:aBundle
347    });
348}
349
350function loadExecutableAndResources(/*Bundle*/ aBundle, /*BOOL*/ shouldExecute)
351{
352    if (!aBundle.mostEligibleEnvironment())
353        return failure();
354
355    loadExecutableForBundle(aBundle, success, failure, progress);
356    loadSpritedImagesForBundle(aBundle, success, failure, progress);
357
358    if (aBundle._loadStatus === CFBundleLoading)
359        return success();
360
361    function failure(/*Error*/ anError)
362    {
363        var loadRequests = aBundle._loadRequests,
364            count = loadRequests.length;
365
366        while (count--)
367            loadRequests[count].abort();
368
369        this._loadRequests = [];
370
371        aBundle._loadStatus = CFBundleUnloaded;
372
373        finishBundleLoadingWithError(aBundle, anError || new Error("Could not recognize executable code format in Bundle " + aBundle));
374    }
375
376    function progress(bytesLoaded)
377    {
378        if ((typeof CPApp === "undefined" || !CPApp || !CPApp._finishedLaunching) &&
379             typeof OBJJ_PROGRESS_CALLBACK === "function")
380        {
381            CFTotalBytesLoaded += bytesLoaded;
382
383            var percent = CPApplicationSizeInBytes ? MAX(MIN(1.0, CFTotalBytesLoaded / CPApplicationSizeInBytes), 0.0) : 0;
384
385            OBJJ_PROGRESS_CALLBACK(percent, CPApplicationSizeInBytes, aBundle.bundlePath());
386        }
387    }
388
389    function success()
390    {
391        if (aBundle._loadStatus === CFBundleLoading)
392            aBundle._loadStatus = CFBundleLoaded;
393        else
394            return;
395
396        // Set resolved to true here in case during evaluation this bundle
397        // needs to resolve another bundle which in turn needs it to be resolved (cycle).
398        resolveStaticResource(aBundle._staticResource);
399
400        function complete()
401        {
402            aBundle._eventDispatcher.dispatchEvent(
403            {
404                type:"load",
405                bundle:aBundle
406            });
407        }
408
409        if (shouldExecute)
410            executeBundle(aBundle, complete);
411        else
412            complete();
413    }
414}
415
416function loadExecutableForBundle(/*Bundle*/ aBundle, success, failure, progress)
417{
418    var executableURL = aBundle.executableURL();
419
420    if (!executableURL)
421        return;
422
423    aBundle._loadStatus |= CFBundleLoadingExecutable;
424
425    new FileRequest(executableURL, function(/*Event*/ anEvent)
426    {
427        try
428        {
429            decompileStaticFile(aBundle, anEvent.request.responseText(), executableURL);
430            aBundle._loadStatus &= ~CFBundleLoadingExecutable;
431            success();
432        }
433        catch(anException)
434        {
435            failure(anException);
436        }
437    }, failure, progress);
438}
439
440function spritedImagesTestURLStringForBundle(/*Bundle*/ aBundle)
441{
442    return "mhtml:" + new CFURL("MHTMLTest.txt", aBundle.mostEligibleEnvironmentURL());
443}
444
445function spritedImagesURLForBundle(/*Bundle*/ aBundle)
446{
447    if (CFBundleSupportedSpriteType === CFBundleDataURLSpriteType)
448        return new CFURL("dataURLs.txt", aBundle.mostEligibleEnvironmentURL());
449
450    if (CFBundleSupportedSpriteType === CFBundleMHTMLSpriteType ||
451        CFBundleSupportedSpriteType === CFBundleMHTMLUncachedSpriteType)
452        return new CFURL("MHTMLPaths.txt", aBundle.mostEligibleEnvironmentURL());
453
454    return NULL;
455}
456
457function loadSpritedImagesForBundle(/*Bundle*/ aBundle, success, failure, progress)
458{
459    if (!aBundle.hasSpritedImages())
460        return;
461
462    aBundle._loadStatus |= CFBundleLoadingSpritedImages;
463
464    if (!CFBundleHasTestedSpriteSupport())
465        return CFBundleTestSpriteSupport(spritedImagesTestURLStringForBundle(aBundle), function()
466        {
467            loadSpritedImagesForBundle(aBundle, success, failure, progress);
468        });
469
470    var spritedImagesURL = spritedImagesURLForBundle(aBundle);
471
472    if (!spritedImagesURL)
473    {
474        aBundle._loadStatus &= ~CFBundleLoadingSpritedImages;
475        return success();
476    }
477
478    new FileRequest(spritedImagesURL, function(/*Event*/ anEvent)
479    {
480        try
481        {
482            decompileStaticFile(aBundle, anEvent.request.responseText(), spritedImagesURL);
483            aBundle._loadStatus &= ~CFBundleLoadingSpritedImages;
484            success();
485        }
486        catch(anException)
487        {
488            failure(anException);
489        }
490    }, failure, progress);
491}
492
493var CFBundleSpriteSupportListeners  = [],
494    CFBundleSupportedSpriteType     = -1,
495    CFBundleNoSpriteType            = 0,
496    CFBundleDataURLSpriteType       = 1,
497    CFBundleMHTMLSpriteType         = 2,
498    CFBundleMHTMLUncachedSpriteType = 3;
499
500function CFBundleHasTestedSpriteSupport()
501{
502    return CFBundleSupportedSpriteType !== -1;
503}
504
505function CFBundleTestSpriteSupport(/*String*/ MHTMLPath, /*Function*/ aCallback)
506{
507    if (CFBundleHasTestedSpriteSupport())
508        return;
509
510    CFBundleSpriteSupportListeners.push(aCallback);
511
512    if (CFBundleSpriteSupportListeners.length > 1)
513        return;
514
515    CFBundleSpriteSupportListeners.push(function()
516    {
517        var size = 0,
518            sizeDictionary = CFBundle.mainBundle().valueForInfoDictionaryKey("CPApplicationSize");
519
520        if (!sizeDictionary)
521            return;
522
523        switch (CFBundleSupportedSpriteType)
524        {
525            case CFBundleDataURLSpriteType:
526                size = sizeDictionary.valueForKey("data");
527                break;
528
529            case CFBundleMHTMLSpriteType:
530            case CFBundleMHTMLUncachedSpriteType:
531                size = sizeDictionary.valueForKey("mhtml");
532                break;
533        }
534
535        CPApplicationSizeInBytes += size;
536    });
537
538    CFBundleTestSpriteTypes([
539        CFBundleDataURLSpriteType,
540        "data:image/gif;base64,R0lGODlhAQABAIAAAMc9BQAAACH5BAAAAAAALAAAAAABAAEAAAICRAEAOw==",
541        CFBundleMHTMLSpriteType,
542        MHTMLPath+"!test",
543        CFBundleMHTMLUncachedSpriteType,
544        MHTMLPath+"?"+CFCacheBuster+"!test"
545    ]);
546}
547
548function CFBundleNotifySpriteSupportListeners()
549{
550    var count = CFBundleSpriteSupportListeners.length;
551
552    while (count--)
553        CFBundleSpriteSupportListeners[count]();
554}
555
556function CFBundleTestSpriteTypes(/*Array*/ spriteTypes)
557{
558    // If we don't support Images, then clearly we don't support sprites.
559    if (!("Image" in global) || spriteTypes.length < 2)
560    {
561        CFBundleSupportedSpriteType = CFBundleNoSpriteType;
562        CFBundleNotifySpriteSupportListeners();
563        return;
564    }
565
566    var image = new Image();
567
568    image.onload = function()
569    {
570        if (image.width === 1 && image.height === 1)
571        {
572            CFBundleSupportedSpriteType = spriteTypes[0];
573            CFBundleNotifySpriteSupportListeners();
574        }
575        else
576            image.onerror();
577    };
578
579    image.onerror = function()
580    {
581        CFBundleTestSpriteTypes(spriteTypes.slice(2));
582    };
583
584    image.src = spriteTypes[1];
585}
586
587function executeBundle(/*Bundle*/ aBundle, /*Function*/ aCallback)
588{
589    var staticResources = [aBundle._staticResource];
590
591    function executeStaticResources(index)
592    {
593        for (; index < staticResources.length; ++index)
594        {
595            var staticResource = staticResources[index];
596
597            if (staticResource.isNotFound())
598                continue;
599
600            if (staticResource.isFile())
601            {
602                var executable = new FileExecutable(staticResource.URL());
603
604                if (executable.hasLoadedFileDependencies())
605                    executable.execute();
606
607                else
608                {
609                    executable.loadFileDependencies(function()
610                    {
611                        executeStaticResources(index);
612                    });
613
614                    return;
615                }
616            }
617            else //if (staticResource.isDirectory())
618            {
619                // We don't want to execute resources.
620                if (staticResource.URL().absoluteString() === aBundle.resourcesDirectoryURL().absoluteString())
621                    continue;
622
623                var children = staticResource.children();
624
625                for (var name in children)
626                    if (hasOwnProperty.call(children, name))
627                        staticResources.push(children[name]);
628            }
629        }
630
631        aCallback();
632    }
633
634    executeStaticResources(0);
635}
636
637var STATIC_MAGIC_NUMBER     = "@STATIC",
638    MARKER_PATH             = "p",
639    MARKER_URI              = "u",
640    MARKER_CODE             = "c",
641    MARKER_TEXT             = "t",
642    MARKER_IMPORT_STD       = 'I',
643    MARKER_IMPORT_LOCAL     = 'i';
644
645function decompileStaticFile(/*Bundle*/ aBundle, /*String*/ aString, /*String*/ aPath)
646{
647    var stream = new MarkedStream(aString);
648
649    if (stream.magicNumber() !== STATIC_MAGIC_NUMBER)
650        throw new Error("Could not read static file: " + aPath);
651
652    if (stream.version() !== "1.0")
653        throw new Error("Could not read static file: " + aPath);
654
655    var marker,
656        bundleURL = aBundle.bundleURL(),
657        file = NULL;
658
659    while (marker = stream.getMarker())
660    {
661        var text = stream.getString();
662
663        if (marker === MARKER_PATH)
664        {
665            var fileURL = new CFURL(text, bundleURL),
666                parent = StaticResource.resourceAtURL(new CFURL(".", fileURL), YES);
667
668            file = new StaticResource(fileURL, parent, NO, YES);
669        }
670
671        else if (marker === MARKER_URI)
672        {
673            var URL = new CFURL(text, bundleURL),
674                mappedURLString = stream.getString();
675
676            if (mappedURLString.indexOf("mhtml:") === 0)
677            {
678                mappedURLString = "mhtml:" + new CFURL(mappedURLString.substr("mhtml:".length), bundleURL);
679
680                if (CFBundleSupportedSpriteType === CFBundleMHTMLUncachedSpriteType)
681                {
682                    var exclamationIndex = mappedURLString.indexOf("!"),
683                        firstPart = mappedURLString.substring(0, exclamationIndex),
684                        lastPart = mappedURLString.substring(exclamationIndex);
685
686                    mappedURLString = firstPart + "?" + CFCacheBuster + lastPart;
687                }
688            }
689
690            CFURL.setMappedURLForURL(URL, new CFURL(mappedURLString));
691
692            // The unresolved directories must not be bundles.
693            var parent = StaticResource.resourceAtURL(new CFURL(".", URL), YES);
694
695            new StaticResource(URL, parent, NO, YES);
696        }
697
698        else if (marker === MARKER_TEXT)
699            file.write(text);
700    }
701}
702
703// Event Managament
704
705CFBundle.prototype.addEventListener = function(/*String*/ anEventName, /*Function*/ anEventListener)
706{
707    this._eventDispatcher.addEventListener(anEventName, anEventListener);
708};
709
710DISPLAY_NAME(CFBundle.prototype.addEventListener);
711
712CFBundle.prototype.removeEventListener = function(/*String*/ anEventName, /*Function*/ anEventListener)
713{
714    this._eventDispatcher.removeEventListener(anEventName, anEventListener);
715};
716
717DISPLAY_NAME(CFBundle.prototype.removeEventListener);
718
719CFBundle.prototype.onerror = function(/*Event*/ anEvent)
720{
721    throw anEvent.error;
722};
723
724DISPLAY_NAME(CFBundle.prototype.onerror);
725
726CFBundle.prototype.bundlePath = function()
727{
728    return this.bundleURL().path();
729};
730
731CFBundle.prototype.path = function()
732{
733    CPLog.warn("CFBundle.prototype.path is deprecated, use CFBundle.prototype.bundlePath instead.");
734    return this.bundlePath.apply(this, arguments);
735};
736
737CFBundle.prototype.pathForResource = function(aResource)
738{
739    return this.resourceURL(aResource).absoluteString();
740};