mirror of
https://github.com/cappuccino/cappuccino.git
synced 2026-08-25 13:07:02 +00:00
525 lines
14 KiB
JavaScript
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;
|