Files
cappuccino/Objective-J/CFBundle.js
T
2010-02-05 20:18:18 -08:00

525 lines
14 KiB
JavaScript

var CFBundleUnloaded = 0,
CFBundleLoading = 1 << 0,
CFBundleLoadingInfoPlist = 1 << 1,
CFBundleLoadingExecutable = 1 << 2,
CFBundleLoadingSpritedImages = 1 << 3,
CFBundleLoaded = 1 << 4;
var CFBundlesForPaths = { },
CFBundlesForClasses = { },
CFCacheBuster = new Date().getTime();
function CFBundle(/*String*/ aPath)
{
aPath = FILE.absolute(aPath);
var existingBundle = CFBundlesForPaths[aPath];
if (existingBundle)
return existingBundle;
CFBundlesForPaths[aPath] = this;
this._path = aPath;
this._name = FILE.basename(aPath);
this._staticResourceNode = NULL;
this._loadStatus = CFBundleUnloaded;
this._loadRequests = [];
this._infoDictionary = NULL;
this._URIMap = { };
this._eventDispatcher = new EventDispatcher(this);
}
CFBundle.bundleContainingPath = function(/*String*/ aPath)
{
aPath = FILE.absolute(aPath);
while (aPath !== "/")
{
var bundle = CFBundlesForPaths[aPath];
if (bundle)
return bundle;
aPath = FILE.dirname(aPath);
}
return NULL;
}
CFBundle.mainBundle = function()
{
return new CFBundle(FILE.cwd());
}
function addClassToBundle(aClass, aBundle)
{
if (aBundle)
CFBundlesForClasses[aClass.name] = aBundle;
}
CFBundle.bundleForClass = function(/*Class*/ aClass)
{
return CFBundlesForClasses[aClass.name] || CFBundle.mainBundle();
}
CFBundle.prototype.path = function()
{
return this._path;
}
CFBundle.prototype.infoDictionary = function()
{
return this._infoDictionary;
}
CFBundle.prototype.valueForInfoDictionary = function(/*String*/ aKey)
{
return this._infoDictionary.valueForKey(aKey);
}
CFBundle.prototype.resourcesPath = function()
{
return FILE.join(this.path(), "Resources");
}
CFBundle.prototype.pathForResource = function(/*String*/ aPath)
{
var mappedPath = this._URIMap[FILE.join("Resources", aPath)];
if (mappedPath)
return mappedPath;
// If not, return the trivial path.
return FILE.join(this.resourcesPath(), aPath);
}
CFBundle.prototype.executablePath = function()
{
var executableSubPath = this._infoDictionary.valueForKey("CPBundleExecutable");
if (executableSubPath)
return FILE.join(this.path(), this.mostEligibleEnvironment() + ".environment", executableSubPath);
return NULL;
}
CFBundle.prototype.hasSpritedImages = function()
{
var environments = this._infoDictionary.valueForKey("CPBundleEnvironmentsWithImageSprites") || [],
index = environments.length,
mostEligibleEnvironment = this.mostEligibleEnvironment();
while (index--)
if (environments[index] === mostEligibleEnvironment)
return YES;
return NO;
}
CFBundle.prototype.environments = function()
{
return this._infoDictionary.valueForKey("CPBundleEnvironments") || ["ObjJ"];
}
CFBundle.prototype.mostEligibleEnvironment = function(/*Array*/ environments)
{
environments = environments || this.environments();
var objj_environments = exports.environments(),
index = 0,
count = objj_environments.length,
innerCount = environments.length;
// Ugh, no indexOf, no objects-in-common.
for(; index < count; ++index)
{
var innerIndex = 0,
environment = objj_environments[index];
for (; innerIndex < innerCount; ++innerIndex)
if(environment === environments[innerIndex])
return environment;
}
return NULL;
}
CFBundle.prototype.isLoading = function()
{
return this._loadStatus & CFBundleLoading;
}
CFBundle.prototype.load = function(/*BOOL*/ shouldExecute)
{
if (this._loadStatus !== CFBundleUnloaded)
return;
var self = this;
self._loadStatus = CFBundleLoading | CFBundleLoadingInfoPlist;
rootNode.resolveSubPath(FILE.dirname(self.path()), StaticResourceNode.DirectoryType, function(aStaticResourceNode)
{
self._staticResourceNode = new StaticResourceNode(FILE.basename(self.path()), aStaticResourceNode, StaticResourceNode.DirectoryType, NO);
function onsuccess(/*Event*/ anEvent)
{
self._loadStatus &= ~CFBundleLoadingInfoPlist;
self._infoDictionary = anEvent.request.responsePropertyList();
if (!self._infoDictionary)
{
self._eventDispatcher.dispatchEvent(
{
type:"error",
error: new Error("Could not load bundle at \"" + self.path() + "\"")
});
return;
}
loadExecutableAndResources(self, shouldExecute);
}
function onfailure()
{
self._staticResourceNode._isResolved = YES;
self._loadStatus = CFBundleUnloaded;
self._eventDispatcher.dispatchEvent(
{
type:"error",
error: new Error("Could not load bundle at \"" + self.path() + "\"")
});
}
new FileRequest(FILE.join(self.path(), "Info.plist"), onsuccess, onfailure);
});
}
function loadExecutableAndResources(/*Bundle*/ aBundle, /*BOOL*/ shouldExecute)
{
if (!aBundle.mostEligibleEnvironment())
return failure();
loadExecutableForBundle(aBundle, success, failure);
loadSpritedImagesForBundle(aBundle, success, failure);
if (aBundle._loadStatus === CFBundleLoading)
return success();
function failure(/*Error*/ anError)
{
var loadRequests = aBundle._loadRequests,
count = loadRequests.length;
while (count--)
loadRequests[count].abort();
this._loadRequests = [];
aBundle._loadStatus = CFBundleUnloaded;
resolveStaticResourceNode(aBundle._staticResourceNode, NO);
aBundle._eventDispatcher.dispatchEvent(
{
type:"error",
error:anError || new Error("Could not recognize executable code format in Bundle " + aBundle),
bundle:aBundle
});
resolveStaticResourceNode(aBundle._staticResourceNode, YES);
}
function success()
{
if (aBundle._loadStatus === CFBundleLoading)
aBundle._loadStatus = CFBundleLoaded;
else
return;
// Set resolved to true here in case during evaluation this bundle
// needs to resolve another bundle which in turn needs it to be resolved (cycle).
resolveStaticResourceNode(aBundle._staticResourceNode, NO);
function complete()
{
aBundle._eventDispatcher.dispatchEvent(
{
type:"load",
bundle:aBundle
});
resolveStaticResourceNode(aBundle._staticResourceNode, YES);
}
if (shouldExecute)
executeBundle(aBundle, complete);
else
complete();
}
}
function loadExecutableForBundle(/*Bundle*/ aBundle, success, failure)
{
if (!aBundle.executablePath())
return;
aBundle._loadStatus |= CFBundleLoadingExecutable;
new FileRequest(aBundle.executablePath(), function(/*Event*/ anEvent)
{
try
{
decompileStaticFile(aBundle, anEvent.request.responseText())
aBundle._loadStatus &= ~CFBundleLoadingExecutable;
success();
}
catch(anException)
{
failure(anException);
}
}, failure);
}
function loadSpritedImagesForBundle(/*Bundle*/ aBundle, success, failure)
{
if (!aBundle.hasSpritedImages())
return;
aBundle._loadStatus |= CFBundleLoadingSpritedImages;
if (!CFBundleHasTestedSpriteSupport())
return CFBundleTestSpriteSupport(spritedImagesTestPathForBundle(aBundle), function()
{
loadSpritedImagesForBundle(aBundle, success, failure);
});
var spritedImagesPath = spritedImagesPathForBundle(aBundle);
if (!spritedImagesPath)
{
aBundle._loadStatus &= ~CFBundleLoadingSpritedImages;
return success();
}
new FileRequest(spritedImagesPath, function(/*Event*/ anEvent)
{
try
{
decompileStaticFile(aBundle, anEvent.request.responseText());
aBundle._loadStatus &= ~CFBundleLoadingSpritedImages;
success();
}
catch(anException)
{
failure(anException);
}
}, failure);
}
var CFBundleSpriteSupportListeners = [],
CFBundleSupportedSpriteType = -1,
CFBundleNoSpriteType = 0,
CFBundleDataURLSpriteType = 1,
CFBundleMHTMLSpriteType = 2,
CFBundleMHTMLUncachedSpriteType = 3;
function CFBundleHasTestedSpriteSupport()
{
return CFBundleSupportedSpriteType !== -1;
}
function CFBundleTestSpriteSupport(/*String*/ MHTMLPath, /*Function*/ aCallback)
{
if (CFBundleHasTestedSpriteSupport())
return;
CFBundleSpriteSupportListeners.push(aCallback);
if (CFBundleSpriteSupportListeners.length > 1)
return;
CFBundleTestSpriteTypes([
CFBundleDataURLSpriteType,
"data:image/gif;base64,R0lGODlhAQABAIAAAMc9BQAAACH5BAAAAAAALAAAAAABAAEAAAICRAEAOw==",
CFBundleMHTMLSpriteType,
MHTMLPath,
CFBundleMHTMLUncachedSpriteType,
MHTMLPath+"?"+CFCacheBuster]);
}
function CFBundleNotifySpriteSupportListeners()
{
var count = CFBundleSpriteSupportListeners.length;
while (count--)
CFBundleSpriteSupportListeners[count]();
}
function CFBundleTestSpriteTypes(/*Array*/ spriteTypes)
{
var image = new Image();
image.onload = function()
{
if (image.width === 1 && image.height === 1)
{
CFBundleSupportedSpriteType = spriteTypes[0];
CFBundleNotifySpriteSupportListeners();
}
else
image.onerror();
}
image.onerror = function()
{
if (spriteTypes.length === 2)
{
CFBundleSupportedSpriteType = CFBundleNoSpriteType;
CFBundleNotifySpriteSupportListeners();
}
else
CFBundleTestSpriteType(spriteTypes.slice(2));
}
image.src = spriteTypes[1];
}
function spritedImagesTestPathForBundle(/*Bundle*/ aBundle)
{
return FILE.join(aBundle.path(), aBundle.mostEligibleEnvironment() + ".environment", "MHTMLTest.txt");
}
function spritedImagesPathForBundle(/*Bundle*/ aBundle)
{
if (CFBundleSupportedSpriteType === CFBundleDataURLSpriteType)
return FILE.join(aBundle.path(), aBundle.mostEligibleEnvironment() + ".environment", "dataURLs.txt");
if (CFBundleSupportedSpriteType === CFBundleMHTMLSpriteType)
return FILE.join(aBundle.path(), aBundle.mostEligibleEnvironment() + ".environment", "MHTML.txt");
if (CFBundleSupportedSpriteType === CFBundleMHTMLUncachedSpriteType)
return FILE.join(aBundle.path(), aBundle.mostEligibleEnvironment() + ".environment", "MHTML.txt?" + CFCacheBuster);
return NULL;
}
CFBundle.dataContentsAtPath = function(/*String*/ aPath)
{
var data = new CFMutableData();
data.setEncodedString(rootNode.nodeAtSubPath(aPath).contents());
return data;
}
function executeBundle(/*Bundle*/ aBundle)
{
var staticResources = [aBundle._staticResourceNode];
function executeStaticResources(staticResources, index)
{
for (; index < staticResources.length; ++index)
{
var staticResource = staticResources[index];
if (staticResource.type() === StaticResourceNode.FileType)
{
var executable = new FileExecutable(staticResource.path());
if (staticResource.hasLoadedFileDependencies())
executable.execute();
else
{
executable.addEventListeners("dependenciesload", function()
{
executeStaticResources(index);
});
return;
}
}
else
staticResources = staticResources.concat(staticResource.children());
}
}
executeStaticResources(0);
}
var STATIC_MAGIC_NUMBER = "@STATIC",
MARKER_PATH = "p",
MARKER_URI = "u",
MARKER_CODE = "c",
MARKER_TEXT = "t",
MARKER_IMPORT_STD = 'I',
MARKER_IMPORT_LOCAL = 'i';
function decompileStaticFile(/*Bundle*/ aBundle, /*String*/ aString)
{
var stream = new MarkedStream(aString);
if (stream.magicNumber() !== STATIC_MAGIC_NUMBER)
throw new Error("Could not read static file.");
if (stream.version() !== "1.0")
throw new Error("Could not read static file.");
var marker,
bundlePath = aBundle.path();
while (marker = stream.getMarker())
{
var text = stream.getString();
if (marker === MARKER_PATH)
{
var absolutePath = FILE.join(bundlePath, text),
parentNode = rootNode.nodeAtSubPath(FILE.dirname(absolutePath), YES);
fileNode = new StaticResourceNode(FILE.basename(absolutePath), parentNode, StaticResourceNode.FileType, YES);
}
else if (marker === MARKER_URI)
{
var URI = stream.getString();
if (URI.toLowerCase().indexOf("mhtml:") === 0)
URI = "mhtml:" + FILE.join(bundlePath, URI.substr("mhtml:".length));
aBundle._URIMap[text] = URI;
// The unresolved directories must not be bundles.
rootNode.nodeAtSubPath(FILE.join(bundlePath, FILE.dirname(text)), YES);
}
else if (marker === MARKER_TEXT)
fileNode.write(text);
}
}
// Event Managament
CFBundle.prototype.addEventListener = function(/*String*/ anEventName, /*Function*/ anEventListener)
{
this._eventDispatcher.addEventListener(anEventName, anEventListener);
}
CFBundle.prototype.removeEventListener = function(/*String*/ anEventName, /*Function*/ anEventListener)
{
this._eventDispatcher.removeEventListener(anEventName, anEventListener);
}
CFBundle.prototype.onerror = function(/*Event*/ anEvent)
{
throw anEvent.error;
}
exports.CFBundle = CFBundle;