From a4d73330cc7f7ff7cedbc28687dc50ea419c7751 Mon Sep 17 00:00:00 2001 From: Francisco Ryan Tolmasky I Date: Fri, 29 Jan 2010 21:43:23 -0800 Subject: [PATCH] Data URLs. Reviewed by me. --- Foundation/CPBundle.j | 10 +- Objective-J/CFBundle.js | 219 ++++++++++++++---- .../lib/objective-j/jake/bundletask.js | 18 +- 3 files changed, 189 insertions(+), 58 deletions(-) diff --git a/Foundation/CPBundle.j b/Foundation/CPBundle.j index d49d3d3a2..2979e2034 100644 --- a/Foundation/CPBundle.j +++ b/Foundation/CPBundle.j @@ -102,14 +102,8 @@ var CPBundlesForPaths = { }; } - (CPString)pathForResource:(CPString)aFilename -{return [self resourcePath] + '/' + aFilename; - var actualPath = [self resourcePath] + '/' + aFilename, - mappedPath = _URIMap["Resources/" + aFilename]; - - if (mappedPath) - return mappedPath; - - return actualPath; +{ + return _bundle.pathForResource(aFilename); } - (CPDictionary)infoDictionary diff --git a/Objective-J/CFBundle.js b/Objective-J/CFBundle.js index ce79d083d..17b289314 100644 --- a/Objective-J/CFBundle.js +++ b/Objective-J/CFBundle.js @@ -1,10 +1,10 @@ -var CFBundleUnloaded = 0, - CFBundleLoading = 1 << 0, - CFBundleLoadingInfoPlist = 1 << 1, - CFBundleLoadingExecutable = 1 << 2, - CFBundleLoadingResources = 1 << 3, - CFBundleLoaded = 1 << 4; +var CFBundleUnloaded = 0, + CFBundleLoading = 1 << 0, + CFBundleLoadingInfoPlist = 1 << 1, + CFBundleLoadingExecutable = 1 << 2, + CFBundleLoadingSpritedImages = 1 << 3, + CFBundleLoaded = 1 << 4; var CFBundlesForPaths = { }, CFBundlesForClasses = { }; @@ -22,10 +22,14 @@ CPLog("created bundle for " + aPath); this._path = aPath; this._name = FILE.basename(aPath); - this._loadStatus = CFBundleUnloaded; this._staticResourceNode = NULL; + this._loadStatus = CFBundleUnloaded; + this._loadRequests = []; + this._infoDictionary = NULL; + this._URIMap = { }; + this._eventDispatcher = new EventDispatcher(this); } @@ -77,6 +81,22 @@ 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"); @@ -87,6 +107,19 @@ CFBundle.prototype.executablePath = function() 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"]; @@ -169,22 +202,24 @@ CFBundle.prototype.load = function(/*BOOL*/ shouldExecute) function loadExecutableAndResources(/*Bundle*/ aBundle, /*BOOL*/ shouldExecute) { - aBundle._loadStatus |= (aBundle.executablePath() && CFBundleLoadingExecutable); - //aBundle._loadStatus |= (aBundle.mappedResourcesPath() && Bundle.LoadingResources); + if (!aBundle.mostEligibleEnvironment()) + return failure(); + + loadExecutableForBundle(aBundle, success, failure); + loadSpritedImagesForBundle(aBundle, success, failure); + + if (aBundle._loadStatus === CFBundleLoading) + return success(); function failure() { - if (aBundle._loadExecutableRequest) - { - aBundle._loadExecutableRequest.abort(); - aBundle._loadExecutableRequest = NULL; - } + var loadRequests = aBundle._loadRequests, + count = loadRequests.length; - if (aBundle._loadResourcesRequest) - { - aBundle._loadResourcesRequest.abort(); - aBundle._loadResourcesRequest = NULL; - } + while (count--) + loadRequests[count].abort(); + + this._loadRequests = []; aBundle._loadStatus = CFBundleUnloaded; @@ -194,7 +229,7 @@ function loadExecutableAndResources(/*Bundle*/ aBundle, /*BOOL*/ shouldExecute) { type:"error", error:new Error("Could not recognize executable code format in Bundle " + aBundle), - bundle:self + bundle:aBundle }); resolveStaticResourceNode(aBundle._staticResourceNode, YES); @@ -204,6 +239,8 @@ function loadExecutableAndResources(/*Bundle*/ aBundle, /*BOOL*/ shouldExecute) { 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). @@ -226,35 +263,135 @@ CPLog("IN HErE FOR " + aBundle.path()); else complete(); } +} - if (aBundle._loadStatus === CFBundleLoading) - return success(); +function loadExecutableForBundle(/*Bundle*/ aBundle, success, failure) +{ + if (!aBundle.executablePath()) + return; - if (!aBundle.mostEligibleEnvironment()) - failure(); + aBundle._loadStatus |= CFBundleLoadingExecutable; - if (aBundle._loadStatus & CFBundleLoadingExecutable) + new FileRequest(aBundle.executablePath(), function(/*Event*/ anEvent) { - function executableSuccess(/*Event*/ anEvent) + try { - decompileExecutable(aBundle, anEvent.request.responseText()); - + decompileStaticFile(aBundle, anEvent.request.responseText()) aBundle._loadStatus &= ~CFBundleLoadingExecutable; - success(); } + catch(anException) + { + failure(anException); + } + }, failure); +} - new FileRequest(aBundle.executablePath(), executableSuccess, failure); +function loadSpritedImagesForBundle(/*Bundle*/ aBundle, success, failure) +{ + if (!aBundle.hasSpritedImages()) + return; + + aBundle._loadStatus |= CFBundleLoadingSpritedImages; + + if (!CFBundleHasTestedSpriteSupport()) + return CFBundleTestSpriteSupport(function() + { + loadSpritedImagesForBundle(aBundle, success, failure); + }); + + new FileRequest(spritedImagesPathForBundle(aBundle), 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; + +function CFBundleHasTestedSpriteSupport() +{ + return CFBundleSupportedSpriteType !== -1; +} + +function CFBundleTestSpriteSupport(/*Function*/ aCallback, /*String*/ MHTMLPath) +{ + if (CFBundleHasTestedSpriteSupport()) + return; + + CFBundleSpriteSupportListeners.push(aCallback); + + if (CFBundleSpriteSupportListeners.length > 1) + return; + + CFBundleTestSpriteTypes([ + CFBundleDataURLSpriteType, + "data:image/gif;base64,R0lGODlhAQABAIAAAMc9BQAAACH5BAAAAAAALAAAAAABAAEAAAICRAEAOw=="]); +} + +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 spritedImagesPathForBundle(/*Bundle*/ aBundle) +{ + if (CFBundleSupportedSpriteType === CFBundleDataURLSpriteType) + return FILE.join(aBundle.path(), aBundle.mostEligibleEnvironment() + ".environment", "dataURLs.txt"); + + return NULL; } CFBundle.dataContentsAtPath = function(/*String*/ aPath) { var data = new CFMutableData(); -CPLog("uh oh"); + data.setEncodedString(rootNode.nodeAtSubPath(aPath).contents()); - CPLog("it is: " + data._encodedString); -CPLog(data.encodedString()); + return data; } @@ -300,23 +437,15 @@ var STATIC_MAGIC_NUMBER = "@STATIC", MARKER_IMPORT_STD = 'I', MARKER_IMPORT_LOCAL = 'i'; -function decompileExecutable(/*Bundle*/ aBundle, /*String*/ aString) +function decompileStaticFile(/*Bundle*/ aBundle, /*String*/ aString) { var stream = new MarkedStream(aString); if (stream.magicNumber() !== STATIC_MAGIC_NUMBER) - aBundle._eventDispatcher.dispatchEvent( - { - type:"error", - error: new Error("Could not recognize executable code format in Bundle " + aBundle) - }); + throw new Error("Could not read static file."); if (stream.version() !== "1.0") - aBundle._eventDispatcher.dispatchEvent( - { - type:"error", - error: new Error("Could not recognize executable code format in Bundle " + aBundle) - }); + throw new Error("Could not read static file."); var marker, bundlePath = aBundle.path(); @@ -340,7 +469,7 @@ function decompileExecutable(/*Bundle*/ aBundle, /*String*/ aString) if (URI.toLowerCase().indexOf("mhtml:") === 0) URI = "mhtml:" + FILE.join(bundlePath, URI.substr("mhtml:".length)); -// aBundle._URIMap[text] = URI; + aBundle._URIMap[text] = URI; } else if (marker === MARKER_TEXT) diff --git a/Objective-J/CommonJS/lib/objective-j/jake/bundletask.js b/Objective-J/CommonJS/lib/objective-j/jake/bundletask.js index b3eca0301..971a2efe8 100644 --- a/Objective-J/CommonJS/lib/objective-j/jake/bundletask.js +++ b/Objective-J/CommonJS/lib/objective-j/jake/bundletask.js @@ -10,8 +10,8 @@ var FILE = require("file"), environment = require("objective-j/jake/environment"), ObjectiveJ = require("objective-j"); -var CFPropertyList = require("objective-j").CFPropertyList, - CFMutableDictionary = require("objective-j").CFMutableDictionary; +var CFPropertyList = ObjectiveJ.CFPropertyList, + CFMutableDictionary = ObjectiveJ.CFMutableDictionary; var Task = Jake.Task, filedir = Jake.filedir; @@ -321,7 +321,7 @@ BundleTask.prototype.buildProductMHTMLPathForEnvironment = function(anEnvironmen BundleTask.prototype.buildProductDataURLPathForEnvironment = function(anEnvironment) { - return FILE.join(this.buildProductPath(), anEnvironment.name() + ".environment", "dataURL.txt"); + return FILE.join(this.buildProductPath(), anEnvironment.name() + ".environment", "dataURLs.txt"); } BundleTask.prototype.defineTasks = function() @@ -362,6 +362,14 @@ BundleTask.prototype.infoPlist = function() return anEnvironment.name(); })); infoPlist.setValueForKey("CPBundleExecutable", this.productName() + ".sj"); + infoPlist.setValueForKey("CPBundleEnvironmentsWithImageSprites", this.environments().filter( + function(anEnvironment) + { + return anEnvironment.spritesImages(); + }).map(function(anEnvironment) + { + return anEnvironment.name(); + })); var principalClass = this.principalClass(); @@ -592,8 +600,8 @@ BundleTask.prototype.defineSpritedImagesTask = function() var resourcePath = "Resources/" + FILE.relative(resourcesPath, aFilename); - dataURLStream.write("u;" + resourcePath.length + ";"); - MHTMLStream.write("u;" + resourcePath.length + ";"); + dataURLStream.write("u;" + resourcePath.length + ";" + resourcePath); + MHTMLStream.write("u;" + resourcePath.length + ";" + resourcePath); // As data URL... var contents = "data:" + mimeType(aFilename) +