From eb57d1467365ee93b514472ebfc6e354d9aff177 Mon Sep 17 00:00:00 2001 From: Francisco Ryan Tolmasky I Date: Wed, 3 Mar 2010 01:04:25 -0800 Subject: [PATCH] Further migration from file paths to CFURLs. Reviewed by me. --- Foundation/CPBundle.j | 2 +- Foundation/CPURL.j | 464 ++---------------------- Objective-J/Bootstrap.js | 57 +-- Objective-J/CFBundle.js | 271 +++++++------- Objective-J/CFHTTPRequest.js | 13 +- Objective-J/CFURL.js | 148 +++++++- Objective-J/CommonJS/lib/objective-j.js | 6 +- Objective-J/EventDispatcher.js | 4 +- Objective-J/Executable.js | 106 +++--- Objective-J/FileDependency.js | 14 +- Objective-J/FileExecutable.js | 9 +- Objective-J/FileExecutableSearch.js | 31 +- Objective-J/Preprocessor.js | 18 +- Objective-J/StaticResource.js | 322 ++++++---------- Tests/Objective-J/CFURLTest.j | 63 ++++ 15 files changed, 631 insertions(+), 897 deletions(-) create mode 100644 Tests/Objective-J/CFURLTest.j diff --git a/Foundation/CPBundle.j b/Foundation/CPBundle.j index 202781a71..b2c4fd4e4 100644 --- a/Foundation/CPBundle.j +++ b/Foundation/CPBundle.j @@ -113,7 +113,7 @@ var CPBundlesForPaths = { }; - (id)objectForInfoDictionaryKey:(CPString)aKey { - return _bundle.valueForInfoDictionary(aKey); + return _bundle.valueForInfoDictionaryKey(aKey); } // diff --git a/Foundation/CPURL.j b/Foundation/CPURL.j index fc7a52405..e5ef1e3e2 100644 --- a/Foundation/CPURL.j +++ b/Foundation/CPURL.j @@ -29,19 +29,23 @@ CPURLCustomIconKey = @"CPURLCustomIconKey"; @implementation CPURL : CPObject { - CPURL _base @accessors(readonly, property=baseURL); - CPString _relative @accessors(readonly, property=relativeString); - - CPDictionary _resourceValues; } -- (id)initWithScheme:(CPString)scheme host:(CPString)host path:(CPString)path ++ (id)alloc { - var uri = new URI(); - uri.scheme = scheme; - uri.authority = host; - uri.path = path; - [self initWithString:uri.toString()]; + return new CFURL(); +} + +- (id)init +{ + return nil; +} + +- (id)initWithScheme:(CPString)aScheme host:(CPString)aHost path:(CPString)aPath +{ + var URLString = (aScheme ? aScheme + ":" : "") + (aHost ? aHost + "//" : "") + (aPath || ""); + + return [self initWithString:URLString]; } - (id)initWithString:(CPString)URLString @@ -54,51 +58,35 @@ CPURLCustomIconKey = @"CPURLCustomIconKey"; return [[self alloc] initWithString:URLString]; } -- (id)initWithString:(CPString)URLString relativeToURL:(CPURL)baseURL +- (id)initWithString:(CPString)URLString relativeToURL:(CPURL)aBaseURL { - if (!URI_RE.test(URLString)) - return nil; - - if (self) - { - _base = baseURL; - _relative = URLString; - _resourceValues = [CPDictionary dictionary]; - } - - return self; + return new CFURL(URLString, aBaseURL); } -+ (id)URLWithString:(CPString)URLString relativeToURL:(CPURL)baseURL ++ (id)URLWithString:(CPString)URLString relativeToURL:(CPURL)aBaseURL { - return [[self alloc] initWithString:URLString relativeToURL:baseURL]; + return [[self alloc] initWithString:URLString relativeToURL:aBaseURL]; } - (CPURL)absoluteURL { - var absStr = [self absoluteString]; - - if (absStr !== _relative) - return [[CPURL alloc] initWithString:absStr]; - - return self; + return self.absoluteURL(); } - (CPString)absoluteString { - return resolve([_base absoluteString] || "", _relative); + return self.absoluteString(); } // if absolute, returns same as absoluteString - (CPString)relativeString { - return _relative; + return self.string(); } - (CPString)path { - var str = [self absoluteString]; - return URI_RE.test(str) ? (parse(str).path || nil) : nil; + return [self absoluteString].path(); } // if absolute, returns the same as path @@ -107,11 +95,9 @@ CPURLCustomIconKey = @"CPURLCustomIconKey"; return URI_RE.test(_relative) ? (parse(_relative).path || nil) : nil; } - - (CPString)scheme { - var str = [self absoluteString]; - return URI_RE.test(str) ? (parse(str).protocol || nil) : nil; + return self.scheme(); } - (CPString)user @@ -164,15 +150,12 @@ CPURLCustomIconKey = @"CPURLCustomIconKey"; - (CPString)lastPathComponent { - var path = [self path]; - return path ? path.split("/").pop() : nil; + return [self absoluteURL].lastPathComponent(); } - (CPString)pathExtension { - var path = [self path], - ext = path.match(/\.(\w+)$/); - return ext ? ext[1] : ""; + return self.pathExtension(); } - (CPURL)standardizedURL @@ -192,413 +175,38 @@ CPURLCustomIconKey = @"CPURLCustomIconKey"; - (id)resourceValueForKey:(CPString)aKey { - return [_resourceValues objectForKey:aKey]; + return self.resourcePropertyForKey(aKey); } - (id)setResourceValue:(id)anObject forKey:(CPString)aKey { - [_resourceValues setObject:anObject forKey:aKey]; + return self.setResourcePropertyForKey(aKey, anObject); } - (CPString)staticResourceData { - // FIXME: This probably shouldn't go through CFBundle for no reason. - return CFBundle.dataContentsAtPath([self path]); + return self.staticResourceData(); } @end +var CPURLURLStringKey = @"CPURLURLStringKey", + CPURLBaseURLKey = @"CPURLBaseURLKey"; + @implementation CPURL (CPCoding) - (id)initWithCoder:(CPCoder)aCoder { - _base = [aCoder decodeObjectForKey:"CPURLBaseKey"]; - _relative = [aCoder decodeObjectForKey:"CPURLRelativeKey"]; - return self; + return [self initWithURLString:[aCoder decodeObjectForKey:CPURLURLStringKey] + baseURL:[aCoder decodeObjectForKey:CPURLBaseURLKey]]; } - (void)encodeWithCoder:(CPCoder)aCoder { - [aCoder encodeObject:_base forKey:"CPURLBaseKey"]; - [aCoder encodeObject:_relative forKey:"CPURLRelativeKey"]; + [aCoder encodeObject:_baseURL forKey:CPURLBaseURLKey]; + [aCoder encodeObject:_string forKey:CPURLURLStringKey]; } @end -// original code: http://code.google.com/p/js-uri/ - -// Based on the regex in RFC2396 Appendix B. -var URI_RE = /^(?:([^:\/?\#]+):)?(?:\/\/([^\/?\#]*))?([^?\#]*)(?:\?([^\#]*))?(?:\#(.*))?/; - -/** - * Uniform Resource Identifier (URI) - RFC3986 - */ -var URI = function(str) { - if (!str) str = ""; - var result = str.match(URI_RE); - this.scheme = result[1] || null; - this.authority = result[2] || null; - this.path = result[3] || null; - this.query = result[4] || null; - this.fragment = result[5] || null; -} - -/** - * Convert the URI to a String. - */ -URI.prototype.toString = function () { - var str = ""; - - if (this.scheme) - str += this.scheme + ":"; - - if (this.authority) - str += "//" + this.authority; - - if (this.path) - str += this.path; - - if (this.query) - str += "?" + this.query; - - if (this.fragment) - str += "#" + this.fragment; - - return str; -} - -var parse = function(uri) { - return new URI(uri); -} - -var unescape = function(str, plus) { - return decodeURI(str).replace(/\+/g, " "); -} - -var unescapeComponent = function(str, plus) { - return decodeURIComponent(str).replace(/\+/g, " "); -} - -// from Chiron's HTTP module: - -/**** keys - members of a parsed URI object. -*/ -var keys = [ - "url", - "protocol", - "authorityRoot", - "authority", - "userInfo", - "user", - "password", - "domain", - "domains", - "port", - "path", - "root", - "directory", - "directories", - "file", - "query", - "anchor" -]; - -/**** expressionKeys - members of a parsed URI object that you get - from evaluting the strict regular expression. -*/ -var expressionKeys = [ - "url", - "protocol", - "authorityRoot", - "authority", - "userInfo", - "user", - "password", - "domain", - "port", - "path", - "root", - "directory", - "file", - "query", - "anchor" -]; - -/**** strictExpression -*/ -var strictExpression = new RegExp( /* url */ - "^" + - "(?:" + - "([^:/?#]+):" + /* protocol */ - ")?" + - "(?:" + - "(//)" + /* authorityRoot */ - "(" + /* authority */ - "(?:" + - "(" + /* userInfo */ - "([^:@]*)" + /* user */ - ":?" + - "([^:@]*)" + /* password */ - ")?" + - "@" + - ")?" + - "([^:/?#]*)" + /* domain */ - "(?::(\\d*))?" + /* port */ - ")" + - ")?" + - "(" + /* path */ - "(/?)" + /* root */ - "((?:[^?#/]*/)*)" + - "([^?#]*)" + /* file */ - ")" + - "(?:\\?([^#]*))?" + /* query */ - "(?:#(.*))?" /*anchor */ -); - -/**** Parser - returns a URI parser function given - a regular expression that renders - `expressionKeys` and returns an `Object` - mapping all `keys` to values. -*/ -var Parser = function (expression) { - return function (url) { - if (typeof url == "undefined") - throw new Error("HttpError: URL is undefined"); - if (typeof url != "string") return new Object(url); - - var items = {}; - var parts = expression.exec(url); - - for (var i = 0; i < parts.length; i++) { - items[expressionKeys[i]] = parts[i] ? parts[i] : ""; - } - - items.root = (items.root || items.authorityRoot) ? '/' : ''; - - items.directories = items.directory.split("/"); - if (items.directories[items.directories.length - 1] == "") { - items.directories.pop(); - } - - /* normalize */ - var directories = []; - for (var i = 0; i < items.directories.length; i++) { - var directory = items.directories[i]; - if (directory == '.') { - } else if (directory == '..') { - if (directories.length && directories[directories.length - 1] != '..') - directories.pop(); - else - directories.push('..'); - } else { - directories.push(directory); - } - } - items.directories = directories; - - items.domains = items.domain.split("."); - - return items; - }; -}; - -/**** parse - a strict URI parser. -*/ -var parse = Parser(strictExpression); - -/**** format - accepts a parsed URI object and returns - the corresponding string. -*/ -var format = function (object) { - if (typeof(object) == 'undefined') - throw new Error("UrlError: URL undefined for urls#format"); - if (object instanceof String || typeof(object) == 'string') - return object; - var domain = - object.domains ? - object.domains.join(".") : - object.domain; - var userInfo = ( - object.user || - object.password - ) ? - ( - (object.user || "") + - (object.password ? ":" + object.password : "") - ) : - object.userInfo; - var authority = ( - userInfo || - domain || - object.port - ) ? ( - (userInfo ? userInfo + "@" : "") + - (domain || "") + - (object.port ? ":" + object.port : "") - ) : - object.authority; - var directory = - object.directories ? - object.directories.join("/") : - object.directory; - var path = - directory || object.file ? - ( - (directory ? directory + "/" : "") + - (object.file || "") - ) : - object.path; - return ( - (object.protocol ? object.protocol + ":" : "") + - (authority ? "//" + authority : "") + - (object.root || (authority && path) ? "/" : "") + - (path ? path : "") + - (object.query ? "?" + object.query : "") + - (object.anchor ? "#" + object.anchor : "") - ) || object.url || ""; -}; - -/**** resolveObject - returns an object representing a URL resolved from - a relative location and a source location. -*/ -var resolveObject = function (source, relative) { - if (!source) - return relative; - - source = parse(source); - relative = parse(relative); - - if (relative.url == "") - return source; - - delete source.url; - delete source.authority; - delete source.domain; - delete source.userInfo; - delete source.path; - delete source.directory; - - if ( - relative.protocol && relative.protocol != source.protocol || - relative.authority && relative.authority != source.authority - ) { - source = relative; - } else { - if (relative.root) { - source.directories = relative.directories; - } else { - - var directories = relative.directories; - for (var i = 0; i < directories.length; i++) { - var directory = directories[i]; - if (directory == ".") { - } else if (directory == "..") { - if (source.directories.length) { - source.directories.pop(); - } else { - source.directories.push('..'); - } - } else { - source.directories.push(directory); - } - } - - if (relative.file == ".") { - relative.file = ""; - } else if (relative.file == "..") { - source.directories.pop(); - relative.file = ""; - } - } - } - - if (relative.root) - source.root = relative.root; - if (relative.protcol) - source.protocol = relative.protocol; - if (!(!relative.path && relative.anchor)) - source.file = relative.file; - source.query = relative.query; - source.anchor = relative.anchor; - - return source; -}; - -/**** relativeObject - returns an object representing a relative URL to - a given target URL from a source URL. -*/ -var relativeObject = function (source, target) { - target = parse(target); - source = parse(source); - - delete target.url; - - if ( - target.protocol == source.protocol && - target.authority == source.authority - ) { - delete target.protocol; - delete target.authority; - delete target.userInfo; - delete target.user; - delete target.password; - delete target.domain; - delete target.domains; - delete target.port; - if ( - !!target.root == !!source.root && !( - target.root && - target.directories[0] != source.directories[0] - ) - ) { - delete target.path; - delete target.root; - delete target.directory; - while ( - source.directories.length && - target.directories.length && - target.directories[0] == source.directories[0] - ) { - target.directories.shift(); - source.directories.shift(); - } - while (source.directories.length) { - source.directories.shift(); - target.directories.unshift('..'); - } - - if (!target.root && !target.directories.length && !target.file && source.file) - target.directories.push('.'); - - if (source.file == target.file) - delete target.file; - if (source.query == target.query) - delete target.query; - if (source.anchor == target.anchor) - delete target.anchor; - } - } - - return target; -}; - -/**** resolve - returns a URL resovled to a relative URL from a source URL. -*/ -var resolve = function (source, relative) { - return format(resolveObject(source, relative)); -}; - -/**** relative - returns a relative URL to a target from a source. -*/ -var relative = function (source, target) { - return format(relativeObject(source, target)); -}; +CFURL.prototype.isa = [CPURL class]; diff --git a/Objective-J/Bootstrap.js b/Objective-J/Bootstrap.js index 598867210..26b526b6a 100644 --- a/Objective-J/Bootstrap.js +++ b/Objective-J/Bootstrap.js @@ -20,42 +20,52 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ -var cwd = FILE.cwd(), - rootResource = new StaticResource("", NULL, YES, cwd !== "/"); -StaticResource.root = rootResource; +#ifdef COMMONJS +var mainBundleURL = new CFURL("file:" + require("file").cwd()); +#elif defined(BROWSER) +// To determine where our application lives, start with the current URL of the page. +var pageURL = new CFURL(window.location.href), -#ifdef BROWSER +// Look for any tags and choose the last one (which is the one that will take effect). + DOMBaseElements = document.getElementsByTagName("base"), + DOMBaseElementsCount = DOMBaseElements.length; + +if (DOMBaseElementsCount > 0) +{ + var DOMBaseElement = DOMBaseElements[DOMBaseElementsCount - 1], + DOMBaseElementHref = DOMBaseElement && DOMBaseElement.getAttribute("href"); + + // If we have one, use it instead. + if (DOMBaseElementHref) + pageURL = new CFURL(DOMBaseElementHref, pageURL); +} + +// Turn the main file into a URL. +var mainFileURL = new CFURL(window.OBJJ_MAIN_FILE || "main.j"), + +// The main bundle is the containing folder of the main file. + mainBundleURL = new CFURL(".", new CFURL(mainFileURL, pageURL)).absoluteURL(); + +StaticResource.resourceAtURL(new CFURL("..", mainBundleURL).absoluteURL(), YES); exports.bootstrap = function() { - if (rootResource.isResolved()) - { - rootResource.nodeAtSubPath(FILE.dirname(cwd), YES); - resolveCWD(); - } - else - { - rootResource.resolve(); - rootResource.addEventListener("resolve", resolveCWD); - } + resolveMainBundleURL(); } -function resolveCWD() +function resolveMainBundleURL() { - rootResource.resolveSubPath(cwd, YES, function(/*StaticResource*/ aResource) + StaticResource.resolveResourceAtURL(mainBundleURL, YES, function(/*StaticResource*/ aResource) { - var includePaths = StaticResource.includePaths(), + var includeURLs = StaticResource.includeURLs(), index = 0, - count = includePaths.length; + count = includeURLs.length; for (; index < count; ++index) - aResource.nodeAtSubPath(FILE.normal(includePaths[index]), YES); + aResource.resourceAtURL(includeURLs[index], YES); - if (typeof OBJJ_MAIN_FILE === "undefined") - OBJJ_MAIN_FILE = "main.j"; - - Executable.fileImporterForPath(cwd)(OBJJ_MAIN_FILE || "main.j", YES, function() + Executable.fileImporterForURL(mainBundleURL)(mainFileURL, YES, function() { afterDocumentLoad(main); }); @@ -81,7 +91,6 @@ afterDocumentLoad(function() documentLoaded = YES; }); - if (typeof OBJJ_AUTO_BOOTSTRAP === "undefined" || OBJJ_AUTO_BOOTSTRAP) exports.bootstrap(); diff --git a/Objective-J/CFBundle.js b/Objective-J/CFBundle.js index bb535e2b5..bc1d7e401 100644 --- a/Objective-J/CFBundle.js +++ b/Objective-J/CFBundle.js @@ -27,25 +27,27 @@ var CFBundleUnloaded = 0, CFBundleLoadingSpritedImages = 1 << 3, CFBundleLoaded = 1 << 4; -var CFBundlesForPaths = { }, - CFBundlesForClasses = { }, +var CFBundlesForURLStrings = { }, + CFBundlesForClasses = { }, CFCacheBuster = new Date().getTime(), CFTotalBytesLoaded = 0, CPApplicationSizeInBytes = 0; -GLOBAL(CFBundle) = function(/*String*/ aPath) +GLOBAL(CFBundle) = function(/*CFURL|String*/ aURL) { - aPath = FILE.absolute(aPath); + aURL = resolveURL(aURL).asDirectoryPathURL(); - var existingBundle = CFBundlesForPaths[aPath]; + var URLString = aURL.absoluteString(), + existingBundle = CFBundlesForURLStrings[URLString]; if (existingBundle) return existingBundle; - CFBundlesForPaths[aPath] = this; + CFBundlesForURLStrings[URLString] = this; + + this._bundleURL = aURL; + this._resourcesDirectoryURL = new CFURL("Resources/", aURL); - this._path = aPath; - this._name = FILE.basename(aPath); this._staticResource = NULL; this._loadStatus = CFBundleUnloaded; @@ -63,18 +65,18 @@ CFBundle.environments = function() return ENVIRONMENTS; } -CFBundle.bundleContainingPath = function(/*String*/ aPath) +CFBundle.bundleContainingURL = function(/*CFURL*/ aURL) { - aPath = FILE.absolute(aPath); + aURL = new CFURL(".", resolveURL(aURL)); - while (aPath !== "/") + while (aURL.path() !== "/") { - var bundle = CFBundlesForPaths[aPath]; + var bundle = CFBundlesForURLStrings[aURL.absoluteString()]; if (bundle) return bundle; - aPath = FILE.dirname(aPath); + aURL = new CFURL("..", aURL); } return NULL; @@ -82,7 +84,7 @@ CFBundle.bundleContainingPath = function(/*String*/ aPath) CFBundle.mainBundle = function() { - return new CFBundle(FILE.cwd()); + return new CFBundle(mainBundleURL); } function addClassToBundle(aClass, aBundle) @@ -96,9 +98,50 @@ CFBundle.bundleForClass = function(/*Class*/ aClass) return CFBundlesForClasses[aClass.name] || CFBundle.mainBundle(); } -CFBundle.prototype.path = function() +CFBundle.prototype.bundleURL = function() { - return this._path; + return this._bundleURL; +} + +CFBundle.prototype.resourcesDirectoryURL = function() +{ + return this._resourcesDirectoryURL; +} + +CFBundle.prototype.resourceURL = function(/*String*/ aResourceName, /*String*/ aType, /*String*/ aSubDirectory) + { + if (aType) + aResourceName = aResourceName + "." + aType; + + if (aSubDirectory) + aResourceName = aSubDirectory + "/" + aResourceName; + + var resourceURL = (new CFURL(aResourceName, this.resourcesDirectoryURL())).mappedURL(); + + return resourceURL.absoluteURL(); +} + +CFBundle.prototype.mostEligibleEnvironmentURL = function() +{ + if (this._mostEligibleEnvironmentURL === undefined) + this._mostEligibleEnvironmentURL = new CFURL(this.mostEligibleEnvironment() + ".environment/", this.bundleURL()); + + return this._mostEligibleEnvironmentURL; +} + +CFBundle.prototype.executableURL = function() +{ + if (this._executableURL === undefined) + { + var executableSubPath = this.valueForInfoDictionaryKey("CPBundleExecutable"); + + if (!executableSubPath) + this._executableURL = NULL; + else + this._executableURL = new CFURL(executableSubPath, this.mostEligibleEnvironmentURL()); + } + + return this._executableURL; } CFBundle.prototype.infoDictionary = function() @@ -106,37 +149,11 @@ CFBundle.prototype.infoDictionary = function() return this._infoDictionary; } -CFBundle.prototype.valueForInfoDictionary = function(/*String*/ aKey) +CFBundle.prototype.valueForInfoDictionaryKey = 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") || [], @@ -192,23 +209,19 @@ CFBundle.prototype.load = function(/*BOOL*/ shouldExecute) var self = this; - rootResource.resolveSubPath(FILE.dirname(self.path()), YES, function(aStaticResource) + + var parentURL = new CFURL("..", this.bundleURL()); + +//??? + if (parentURL.absoluteString() === this.bundleURL().absoluteString()) + parentURL = parentURL.schemeAndAuthority(); + + StaticResource.resolveResourceAtURL(parentURL, YES, function(aStaticResource) { - var path = self.path(); + var resourceName = self.bundleURL().absoluteURL().lastPathComponent(); - // If this bundle exists at the root path, no need to create a node. - if (path === "/") - self._staticResource = rootResource; - - else - { - var name = FILE.basename(path); - - self._staticResource = aStaticResource._children[name]; - - if (!self._staticResource) - self._staticResource = new StaticResource(name, aStaticResource, YES, NO); - } + self._staticResource = aStaticResource._children[resourceName] || + new StaticResource(resourceName, aStaticResource, YES, NO); function onsuccess(/*Event*/ anEvent) { @@ -222,8 +235,8 @@ CFBundle.prototype.load = function(/*BOOL*/ shouldExecute) return; } - if (self === CFBundle.mainBundle() && self.valueForInfoDictionary("CPApplicationSize")) - CPApplicationSizeInBytes = self.valueForInfoDictionary("CPApplicationSize").valueForKey("executable") || 0; + if (self === CFBundle.mainBundle() && self.valueForInfoDictionaryKey("CPApplicationSize")) + CPApplicationSizeInBytes = self.valueForInfoDictionaryKey("CPApplicationSize").valueForKey("executable") || 0; loadExecutableAndResources(self, shouldExecute); } @@ -232,10 +245,10 @@ CFBundle.prototype.load = function(/*BOOL*/ shouldExecute) { self._loadStatus = CFBundleUnloaded; - finishBundleLoadingWithError(self, new Error("Could not load bundle at \"" + path + "\"")); + finishBundleLoadingWithError(self, new Error("Could not load bundle at \"" + self.bundleURL() + "\"")); } - new FileRequest(FILE.join(path, "Info.plist"), onsuccess, onfailure); + new FileRequest(new CFURL("Info.plist", self.bundleURL()), onsuccess, onfailure); }); } @@ -279,7 +292,7 @@ function loadExecutableAndResources(/*Bundle*/ aBundle, /*BOOL*/ shouldExecute) function success() { - if ((typeof CPApp === "undefined" || !CPApp || !CPApp._finishedLaunching) && + if ((typeof CPApp === "undefined" || !CPApp || !CPApp._finishedLaunching) && typeof OBJJ_PROGRESS_CALLBACK === "function" && CPApplicationSizeInBytes) { OBJJ_PROGRESS_CALLBACK(MAX(MIN(1.0, CFTotalBytesLoaded / CPApplicationSizeInBytes), 0.0), CPApplicationSizeInBytes, aBundle.path()) @@ -312,17 +325,19 @@ function loadExecutableAndResources(/*Bundle*/ aBundle, /*BOOL*/ shouldExecute) function loadExecutableForBundle(/*Bundle*/ aBundle, success, failure) { - if (!aBundle.executablePath()) + var executableURL = aBundle.executableURL(); + + if (!executableURL) return; aBundle._loadStatus |= CFBundleLoadingExecutable; - new FileRequest(aBundle.executablePath(), function(/*Event*/ anEvent) + new FileRequest(executableURL, function(/*Event*/ anEvent) { try { CFTotalBytesLoaded += anEvent.request.responseText().length; - decompileStaticFile(aBundle, anEvent.request.responseText(), aBundle.executablePath()); + decompileStaticFile(aBundle, anEvent.request.responseText(), executableURL); aBundle._loadStatus &= ~CFBundleLoadingExecutable; success(); } @@ -333,6 +348,23 @@ function loadExecutableForBundle(/*Bundle*/ aBundle, success, failure) }, failure); } +function spritedImagesTestURLStringForBundle(/*Bundle*/ aBundle) +{ + return "mhtml:" + new CFURL("MHTMLTest.txt", aBundle.mostEligibleEnvironmentURL()); +} + +function spritedImagesURLForBundle(/*Bundle*/ aBundle) +{ + if (CFBundleSupportedSpriteType === CFBundleDataURLSpriteType) + return new CFURL("dataURLs.txt", aBundle.mostEligibleEnvironmentURL()); + + if (CFBundleSupportedSpriteType === CFBundleMHTMLSpriteType || + CFBundleSupportedSpriteType === CFBundleMHTMLUncachedSpriteType) + return new CFURL("MHTMLPaths.txt", aBundle.mostEligibleEnvironmentURL()); + + return NULL; +} + function loadSpritedImagesForBundle(/*Bundle*/ aBundle, success, failure) { if (!aBundle.hasSpritedImages()) @@ -341,25 +373,25 @@ function loadSpritedImagesForBundle(/*Bundle*/ aBundle, success, failure) aBundle._loadStatus |= CFBundleLoadingSpritedImages; if (!CFBundleHasTestedSpriteSupport()) - return CFBundleTestSpriteSupport(spritedImagesTestPathForBundle(aBundle), function() + return CFBundleTestSpriteSupport(spritedImagesTestURLStringForBundle(aBundle), function() { loadSpritedImagesForBundle(aBundle, success, failure); }); - var spritedImagesPath = spritedImagesPathForBundle(aBundle); + var spritedImagesURL = spritedImagesURLForBundle(aBundle); - if (!spritedImagesPath) + if (!spritedImagesURL) { aBundle._loadStatus &= ~CFBundleLoadingSpritedImages; return success(); } - new FileRequest(spritedImagesPath, function(/*Event*/ anEvent) + new FileRequest(spritedImagesURL, function(/*Event*/ anEvent) { try { CFTotalBytesLoaded += anEvent.request.responseText().length; - decompileStaticFile(aBundle, anEvent.request.responseText(), spritedImagesPath); + decompileStaticFile(aBundle, anEvent.request.responseText(), spritedImagesURL); aBundle._loadStatus &= ~CFBundleLoadingSpritedImages; success(); } @@ -395,7 +427,7 @@ function CFBundleTestSpriteSupport(/*String*/ MHTMLPath, /*Function*/ aCallback) CFBundleSpriteSupportListeners.push(function() { var size = 0, - sizeDictionary = CFBundle.mainBundle().valueForInfoDictionary("CPApplicationSize"); + sizeDictionary = CFBundle.mainBundle().valueForInfoDictionaryKey("CPApplicationSize"); if (!sizeDictionary) return; @@ -463,45 +495,9 @@ function CFBundleTestSpriteTypes(/*Array*/ spriteTypes) image.src = spriteTypes[1]; } -function mhtmlBasePath() -{ -#ifdef BROWSER - //FIXME: URL stuff is kind of broken - return window.location.protocol + "//" + window.location.hostname + (window.location.port ? (":" + window.location.port) : ""); -#else - return ""; -#endif -} - -function spritedImagesTestPathForBundle(/*Bundle*/ aBundle) -{ - return "mhtml:" + mhtmlBasePath() + 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 || CFBundleSupportedSpriteType === CFBundleMHTMLUncachedSpriteType) - return mhtmlBasePath() + FILE.join(aBundle.path(), aBundle.mostEligibleEnvironment() + ".environment", "MHTMLPaths.txt"); - - return NULL; -} - -CFBundle.dataContentsAtPath = function(/*String*/ aPath) -{ - var data = new CFMutableData(); - - data.setRawString(rootResource.nodeAtSubPath(aPath).contents()); - - return data; -} - function executeBundle(/*Bundle*/ aBundle, /*Function*/ aCallback) { - var staticResources = [aBundle._staticResource], - resourcesPath = aBundle.resourcesPath(); + var staticResources = [aBundle._staticResource]; function executeStaticResources(index) { @@ -514,7 +510,7 @@ function executeBundle(/*Bundle*/ aBundle, /*Function*/ aCallback) if (staticResource.isFile()) { - var executable = new FileExecutable(staticResource.path()); + var executable = new FileExecutable(staticResource.URL()); if (executable.hasLoadedFileDependencies()) executable.execute(); @@ -532,7 +528,7 @@ function executeBundle(/*Bundle*/ aBundle, /*Function*/ aCallback) else //if (staticResource.isDirectory()) { // We don't want to execute resources. - if (staticResource.path() === aBundle.resourcesPath()) + if (staticResource.URL().absoluteString() === aBundle.resourcesDirectoryURL().absoluteString()) continue; var children = staticResource.children(); @@ -562,13 +558,13 @@ function decompileStaticFile(/*Bundle*/ aBundle, /*String*/ aString, /*String*/ var stream = new MarkedStream(aString); if (stream.magicNumber() !== STATIC_MAGIC_NUMBER) - throw new Error("Could not read static file: "+aPath); + throw new Error("Could not read static file: " + aPath); if (stream.version() !== "1.0") - throw new Error("Could not read static file: "+aPath); + throw new Error("Could not read static file: " + aPath); var marker, - bundlePath = aBundle.path(), + bundleURL = aBundle.bundleURL(), file = NULL; while (marker = stream.getMarker()) @@ -577,35 +573,44 @@ function decompileStaticFile(/*Bundle*/ aBundle, /*String*/ aString, /*String*/ if (marker === MARKER_PATH) { - var absolutePath = FILE.join(bundlePath, text), - parent = rootResource.nodeAtSubPath(FILE.dirname(absolutePath), YES); + var fileURL = new CFURL(text, bundleURL), + parent = StaticResource.resourceAtURL(new CFURL(".", fileURL), YES); - file = new StaticResource(FILE.basename(absolutePath), parent, NO, YES); + file = new StaticResource(fileURL.lastPathComponent(), parent, NO, YES); } else if (marker === MARKER_URI) { - var URI = stream.getString(); + var URL = new CFURL(text, bundleURL), + mappedURL, + mappedURLString = stream.getString(); - if (URI.toLowerCase().indexOf("mhtml:") === 0) + if (mappedURLString.toLowerCase().indexOf("mhtml:") === 0) { - URI = "mhtml:" + mhtmlBasePath() + FILE.join(bundlePath, URI.substr("mhtml:".length)); + mappedURLString = "mhtml:" + new CFURL(URLString.substr("mhtml:".length), bundleURL); +/* + URLString = "mhtml:" + URL; if (CFBundleSupportedSpriteType === CFBundleMHTMLUncachedSpriteType) { - var exclamationIndex = URI.indexOf("!"), - firstPart = URI.substring(0, exclamationIndex), - lastPart = URI.substring(exclamationIndex); + var exclamationIndex = URLString.indexOf("!"), + firstPart = URLString.substring(0, exclamationIndex), + lastPart = URLString.substring(exclamationIndex); - URI = firstPart + "?" + CFCacheBuster + lastPart; + URLString = firstPart + "?" + CFCacheBuster + lastPart; } + + mappedURL = new CFURL(URLString);*/ } - aBundle._URIMap[text] = URI; + else + mappedURL = new CFURL(mappedURLString); + + CFURL.setMappedURLForURL(URL, mappedURL); // The unresolved directories must not be bundles. - var parent = rootResource.nodeAtSubPath(FILE.join(bundlePath, FILE.dirname(text)), YES); + var parent = StaticResource.resourceAtURL(new CFURL(".", URL), YES); - new StaticResource(FILE.basename(text), parent, NO, YES); + new StaticResource(URL.lastPathComponent(), parent, NO, YES); } else if (marker === MARKER_TEXT) @@ -629,3 +634,15 @@ CFBundle.prototype.onerror = function(/*Event*/ anEvent) { throw anEvent.error; } + +// + +CFBundle.prototype.path = function() +{ + return this._bundleURL.absoluteString(); +} + +CFBundle.prototype.pathForResource = function(aResource) +{ + return this.resourceURL(aResource).absoluteString(); +} diff --git a/Objective-J/CFHTTPRequest.js b/Objective-J/CFHTTPRequest.js index 51c76cea1..17199af09 100644 --- a/Objective-J/CFHTTPRequest.js +++ b/Objective-J/CFHTTPRequest.js @@ -252,7 +252,7 @@ function determineAndDispatchHTTPRequestEvents(/*CFHTTPRequest*/ aRequest) } } -function FileRequest(/*String*/ aFilePath, onsuccess, onfailure) +function FileRequest(/*CFURL*/ aURL, onsuccess, onfailure) { #ifdef BROWSER var request = new CFHTTPRequest(); @@ -260,16 +260,19 @@ function FileRequest(/*String*/ aFilePath, onsuccess, onfailure) request.onsuccess = Asynchronous(onsuccess); request.onfailure = Asynchronous(onfailure); - if (FILE.extension(aFilePath) === ".plist") + if (aURL.pathExtension() === "plist") request.overrideMimeType("text/xml"); - request.open("GET", aFilePath, YES); + request.open("GET", aURL.absoluteString(), YES); request.send(""); #else - if (!FILE.exists(aFilePath)) + var FILE = require("file"), + filePath = aURL.absoluteURL().path(); + + if (!FILE.exists(filePath)) return onfailure(); - this._responseText = FILE.read(aFilePath, { charset:"UTF-8" }); + this._responseText = FILE.read(filePath, { charset:"UTF-8" }); onsuccess({ type:"success", request:this }); #endif diff --git a/Objective-J/CFURL.js b/Objective-J/CFURL.js index 0ed479eca..50c2af6fd 100644 --- a/Objective-J/CFURL.js +++ b/Objective-J/CFURL.js @@ -1,24 +1,64 @@ // Based on the regex in RFC2396 Appendix B. var URI_RE = /^(([^:\/?#]+):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?/; -GLOBAL(CFURL) = function(/*CFURL|String*/ aURLOrString, /*CFURL*/ aBaseURL) +GLOBAL(CFURL) = function(/*CFURL|String*/ aURL, /*CFURL*/ aBaseURL) { - if (aURLOrString instanceof CFURL) - return new CFURL(aURLOrString.string(), aBaseURL); + if (aURL instanceof CFURL) + if (!aBaseURL) + return aURL + else + { + var existingBaseURL = aURL.baseURL(); - this._string = aURLOrString; + if (existingBaseURL) + aBaseURL = new CFURL(existingBaseURL, aBaseURL); - var result = (aURLOrString || "").match(URI_RE); + return new CFURL(aURL.string(), aBaseURL); + } + + this._UID = objj_generateObjectUID(); + this._string = aURL; + + var result = (aURL || "").match(URI_RE); this._baseURL = aBaseURL; + this._resourcePropertiesForKeys = new CFMutableDictionary(); - this._scheme = result[2] || NULL; - this._authority = result[4] || NULL; - this._path = result[5] || NULL; + this._scheme = result[2]; + this._authority = result[4]; + this._path = result[5] || ""; this._queryString = result[7] || NULL; this._fragment = result[9] || NULL; } +var URLMap = { }; + +CFURL.prototype.mappedURL = function() +{ + return URLMap[this.absoluteString()] || this; +} + +CFURL.setMappedURLForURL = function(/*CFURL*/ fromURL, /*CFURL*/ toURL) +{ + URLMap[fromURL.absoluteString()] = toURL; +} + +CFURL.prototype.schemeAndAuthority = function() +{ + var string = "", + scheme = this.scheme(); + + if (scheme) + string += scheme + ":"; + + var authority = this.authority(); + + if (authority) + string += "//" + authority; + + return string; +} + CFURL.prototype.absoluteString = function() { return this.absoluteURL().string(); @@ -35,7 +75,7 @@ CFURL.prototype.absoluteURL = function() { var baseURL = this._baseURL; - this._absoluteURL = baseURL ? resolve(baseURL.string(), this.string()) : this; + this._absoluteURL = baseURL ? new CFURL(resolve(baseURL.absoluteString(), this.string())) : this; } return this._absoluteURL; @@ -48,12 +88,22 @@ CFURL.prototype.string = function() CFURL.prototype.authority = function() { - return this._authority; + var authority = this._authority; + + if (authority === undefined) + { + var baseURL = this.baseURL(); + + authority = baseURL && baseURL.authority() || NULL; + this._authority = authority; + } + + return authority; } CFURL.prototype.hasDirectoryPath = function() { - var path = this._path; + var path = this.path(); if (!path) return NO; @@ -93,6 +143,39 @@ CFURL.prototype.path = function() return this._path; } +CFURL.prototype.pathComponents = function() +{ + if (!this._pathComponents) + { + var path = this.path(); + + if (!path) + this._pathComponents = []; + else + { + var components = path.split("/"), + result = [], + index = 0, + count = components.length; + + for (; index < count; ++index) + { + var component = components[index]; + + if (component) + result.push(component); + + else if (index === 0) + result.push("/"); + } + + this._pathComponents = result; + } + } + + return this._pathComponents; +} + CFURL.prototype.pathExtension = function() { var lastPathComponent = this.lastPathComponent(); @@ -114,7 +197,17 @@ CFURL.prototype.queryString = function() CFURL.prototype.scheme = function() { - return this._scheme; + var scheme = this._scheme; + + if (scheme === undefined) + { + var baseURL = this.baseURL(); + + scheme = baseURL && baseURL.scheme() || NULL; + this._scheme = scheme; + } + + return scheme; } CFURL.prototype.baseURL = function() @@ -122,7 +215,32 @@ CFURL.prototype.baseURL = function() return this._baseURL; } +CFURL.prototype.asDirectoryPathURL = function() +{ + if (this.hasDirectoryPath()) + return this; + return new CFURL(this.lastPathComponent() + "/", this); +} + +CFURL.prototype.resourcePropertyForKey = function(/*String*/ aKey) +{ + return this._resourcePropertiesForKeys.objectForKey(aKey); +} + +CFURL.prototype.setResourcePropertyForKey = function(/*String*/ aKey, /*id*/ aValue) +{ + this._resourcePropertiesForKeys.setObjectForKey(aKey, aValue); +} + +CFURL.prototype.staticResourceData = function() +{ + var data = new CFMutableData(); + + data.setRawString(StaticResource.resourceAtURL(this).contents()); + + return data; +} // from Chiron's HTTP module: @@ -335,7 +453,7 @@ var resolveObject = function (source, relative) { ) { source = relative; } else { - if (relative.root) { + if (relative.root || relative.protocol) { source.directories = relative.directories; } else { @@ -363,9 +481,9 @@ var resolveObject = function (source, relative) { } } - if (relative.root) + if (relative.root || relative.protocol) source.root = relative.root; - if (relative.protcol) + if (relative.protocol) source.protocol = relative.protocol; if (!(!relative.path && relative.anchor)) source.file = relative.file; diff --git a/Objective-J/CommonJS/lib/objective-j.js b/Objective-J/CommonJS/lib/objective-j.js index 70962bb9a..c1778c65f 100644 --- a/Objective-J/CommonJS/lib/objective-j.js +++ b/Objective-J/CommonJS/lib/objective-j.js @@ -155,8 +155,8 @@ exports.objj_eval = function(/*String*/ aString) var code = executable._code; // Not clear why these should be global, varing them doesn't seem to take effect with evaluateString. - global.objj_executeFile = Executable.fileExecuterForPath(FILE.cwd()); - global.objj_importFile = Executable.fileImporterForPath(FILE.cwd()); + global.objj_executeFile = Executable.fileExecuterForURL(FILE.cwd()); + global.objj_importFile = Executable.fileImporterForURL(FILE.cwd()); if (typeof system !== "undefined" && system.engine === "rhino") return Packages.org.mozilla.javascript.Context.getCurrentContext().evaluateString(global, code, "objj_eval", 0, NULL); @@ -173,7 +173,7 @@ exports.make_narwhal_factory = function(path) { Executable.setCommonJSArguments(require, exports, module, system, print, window); - Executable.fileImporterForPath(FILE.dirname(path))(path, function() + Executable.fileImporterForURL(FILE.dirname(path))(path, YES, function() { print("all done"); }); diff --git a/Objective-J/EventDispatcher.js b/Objective-J/EventDispatcher.js index 47f9bcc3b..048ef3540 100644 --- a/Objective-J/EventDispatcher.js +++ b/Objective-J/EventDispatcher.js @@ -30,7 +30,7 @@ EventDispatcher.prototype.addEventListener = function(/*String*/ anEventName, /* { var eventListenersForEventNames = this._eventListenersForEventNames; - if (!hasOwnProperty.call(this._eventListenersForEventNames, anEventName)) + if (!hasOwnProperty.call(eventListenersForEventNames, anEventName)) { var eventListenersForEventName = []; eventListenersForEventNames[anEventName] = eventListenersForEventName; @@ -54,7 +54,7 @@ EventDispatcher.prototype.removeEventListener = function(/*String*/ anEventName, if (!hasOwnProperty.call(eventListenersForEventNames, anEventName)) return; - var eventListenersForEventName = eventListenersForEventNames[anEventName]. + var eventListenersForEventName = eventListenersForEventNames[anEventName], index = eventListenersForEventName.length; while (index--) diff --git a/Objective-J/Executable.js b/Objective-J/Executable.js index 2e3b81a40..490951e85 100644 --- a/Objective-J/Executable.js +++ b/Objective-J/Executable.js @@ -22,7 +22,9 @@ var ExecutableUnloadedFileDependencies = 0, ExecutableLoadingFileDependencies = 1, - ExecutableLoadedFileDependencies = 2; + ExecutableLoadedFileDependencies = 2, + AnonymousExecutableCount = 0; + function Executable(/*String*/ aCode, /*Array*/ fileDependencies, /*CFURL*/ aURL, /*Function*/ aFunction) { @@ -31,7 +33,7 @@ function Executable(/*String*/ aCode, /*Array*/ fileDependencies, /*CFURL*/ aURL this._code = aCode; this._function = aFunction || NULL; - this._URL = aURL || new CFURL("(Anonymous)", mainBundleURL); + this._URL = resolveURL(aURL || new CFURL("(Anonymous" + (AnonymousExecutableCount++) + ")", mainBundleURL)); this._fileDependencies = fileDependencies; this._fileDependencyLoadStatus = ExecutableUnloadedFileDependencies; @@ -124,7 +126,7 @@ Executable.prototype.execute = function() #endif var oldContextBundle = CONTEXT_BUNDLE; - CONTEXT_BUNDLE = CFBundle.bundleContainingPath(this.path()); + CONTEXT_BUNDLE = CFBundle.bundleContainingURL(this.URL()); var result = this._function.apply(global, this.functionArguments()); @@ -197,7 +199,7 @@ Executable.prototype.loadFileDependencies = function() this._fileDependencyLoadStatus = ExecutableLoadingFileDependencies; - var searchedPaths = [{ }, { }], + var searchedURLStrings = [{ }, { }], fileExecutableSearches = new CFMutableDictionary(), incompleteFileExecutableSearches = new CFMutableDictionary(), loadingExecutables = { }; @@ -215,11 +217,11 @@ Executable.prototype.loadFileDependencies = function() if (executable.hasLoadedFileDependencies()) continue; - var executablePath = executable.path(); + var executableURLString = executable.URL().absoluteString(); - loadingExecutables[executablePath] = executable; + loadingExecutables[executableURLString] = executable; - var cwd = FILE.dirname(executablePath), + var referenceURL = new CFURL(".", executable.URL()), fileDependencies = executable.fileDependencies(), fileDependencyIndex = 0, fileDependencyCount = fileDependencies.length; @@ -228,14 +230,19 @@ Executable.prototype.loadFileDependencies = function() { var fileDependency = fileDependencies[fileDependencyIndex], isLocal = fileDependency.isLocal(), - path = importablePath(fileDependency.path(), isLocal, cwd); + URL = fileDependency.URL(); - if (searchedPaths[isLocal ? 1 : 0][path]) + if (isLocal) + URL = new CFURL(URL, referenceURL); + + var URLString = URL.absoluteString(); + + if (searchedURLStrings[isLocal ? 1 : 0][URLString]) continue; - searchedPaths[isLocal ? 1 : 0][path] = YES; + searchedURLStrings[isLocal ? 1 : 0][URLString] = YES; - var fileExecutableSearch = new FileExecutableSearch(path, isLocal), + var fileExecutableSearch = new FileExecutableSearch(URL, isLocal), fileExecutableSearchUID = fileExecutableSearch.UID(); if (fileExecutableSearches.containsKey(fileExecutableSearchUID)) @@ -278,14 +285,14 @@ Executable.prototype.loadFileDependencies = function() CPLog("DEPENDENCY: Ended"); #endif - for (var path in loadingExecutables) - if (hasOwnProperty.call(loadingExecutables, path)) - loadingExecutables[path]._fileDependencyLoadStatus = ExecutableLoadedFileDependencies; + for (var URLString in loadingExecutables) + if (hasOwnProperty.call(loadingExecutables, URLString)) + loadingExecutables[URLString]._fileDependencyLoadStatus = ExecutableLoadedFileDependencies; - for (var path in loadingExecutables) - if (hasOwnProperty.call(loadingExecutables, path)) + for (var URLString in loadingExecutables) + if (hasOwnProperty.call(loadingExecutables, URLString)) { - var executable = loadingExecutables[path]; + var executable = loadingExecutables[URLString]; executable._eventDispatcher.dispatchEvent( { @@ -308,81 +315,67 @@ Executable.prototype.removeEventListener = function(/*String*/ anEventName, /*Fu this._eventDispatcher.removeEventListener(anEventName, aListener); } -function importablePath(/*String*/ aPath, /*BOOL*/ isLocal, /*String*/ aCWD) -{ - aPath = FILE.normal(aPath); - - if (FILE.isAbsolute(aPath)) - return aPath; - - if (isLocal) - aPath = FILE.normal(FILE.join(aCWD, aPath)); - - return aPath; -} - Executable.prototype.fileImporter = function() { - return Executable.fileImporterForPath(FILE.dirname(this.path())); + return Executable.fileImporterForURL(new CFURL(".", this.URL())); } Executable.prototype.fileExecuter = function() { - return Executable.fileExecuterForPath(FILE.dirname(this.path())); + return Executable.fileExecuterForURL(new CFURL(".", this.URL())); } -var cachedFileExecutersForPaths = { }; +var cachedFileExecutersForURLStrings = { }; -Executable.fileExecuterForPath = function(/*String*/ referencePath) +Executable.fileExecuterForURL = function(/*CFURL|String*/ aURL) { - referencePath = FILE.normal(referencePath); + var referenceURL = resolveURL(aURL), + referenceURLString = referenceURL.absoluteString(), + cachedFileExecuter = cachedFileExecutersForURLStrings[referenceURLString]; - var fileExecuter = cachedFileExecutersForPaths[referencePath]; - - if (!fileExecuter) + if (!cachedFileExecuter) { - fileExecuter = function(/*String*/ aPath, /*BOOL*/ isLocal, /*BOOL*/ shouldForce) + cachedFileExecuter = function(/*CFURL*/ aURL, /*BOOL*/ isQuoted, /*BOOL*/ shouldForce) { - aPath = importablePath(aPath, isLocal, referencePath); + aURL = new CFURL(aURL, isQuoted ? referenceURL : NULL); - var fileExecutableSearch = new FileExecutableSearch(aPath, isLocal), + var fileExecutableSearch = new FileExecutableSearch(aURL, isQuoted), fileExecutable = fileExecutableSearch.result(); - if (0 && !fileExecutable.hasLoadedFileDependencies()) - throw "No executable loaded for file at path " + aPath; + if (!fileExecutable.hasLoadedFileDependencies()) + throw "No executable loaded for file at URL " + aURL; fileExecutable.execute(shouldForce); } - cachedFileExecutersForPaths[referencePath] = fileExecuter; + cachedFileExecutersForURLStrings[referenceURLString] = cachedFileExecuter; } - return fileExecuter; + return cachedFileExecuter; } -var cachedImportersForPaths = { }; +var cachedImportersForURLStrings = { }; -Executable.fileImporterForPath = function(/*String*/ referencePath) +Executable.fileImporterForURL = function(/*CFURL*/ aURL) { - referencePath = FILE.normal(referencePath); - - var cachedImporter = cachedImportersForPaths[referencePath]; + var referenceURL = resolveURL(aURL), + referenceURLString = referenceURL.absoluteString(), + cachedImporter = cachedImportersForURLStrings[referenceURLString]; if (!cachedImporter) { - cachedImporter = function(/*String*/ aPath, /*BOOL*/ isLocal, /*Function*/ aCallback) + cachedImporter = function(/*CFURL*/ aURL, /*BOOL*/ isQuoted, /*Function*/ aCallback) { - aPath = importablePath(aPath, isLocal, referencePath); + aURL = new CFURL(aURL, isQuoted ? referenceURL : NULL); - var fileExecutableSearch = new FileExecutableSearch(aPath, isLocal); + var fileExecutableSearch = new FileExecutableSearch(aURL, isQuoted); function searchComplete(/*FileExecutableSearch*/ aFileExecutableSearch) { var fileExecutable = aFileExecutableSearch.result(), - fileExecuter = Executable.fileExecuterForPath(referencePath), executeAndCallback = function () { - fileExecuter(aPath, isLocal); + fileExecutable.execute(); if (aCallback) aCallback(); @@ -406,9 +399,8 @@ Executable.fileImporterForPath = function(/*String*/ referencePath) }); } - cachedImportersForPaths[referencePath] = cachedImporter; + cachedImportersForURLStrings[referenceURLString] = cachedImporter; } return cachedImporter; } - diff --git a/Objective-J/FileDependency.js b/Objective-J/FileDependency.js index 2fdd851d4..c65861f7b 100644 --- a/Objective-J/FileDependency.js +++ b/Objective-J/FileDependency.js @@ -20,17 +20,17 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ -function FileDependency(/*String*/ aPath, /*BOOL*/ isLocal) +function FileDependency(/*CFURL*/ aURL, /*BOOL*/ isLocal) { - this._path = FILE.normal(aPath); + this._URL = aURL; this._isLocal = isLocal; } exports.FileDependency = FileDependency; -FileDependency.prototype.path = function() +FileDependency.prototype.URL = function() { - return this._path; + return this._URL; } FileDependency.prototype.isLocal = function() @@ -40,11 +40,13 @@ FileDependency.prototype.isLocal = function() FileDependency.prototype.toMarkedString = function() { + var URLString = this.URL().absoluteString(); + return (this.isLocal() ? MARKER_IMPORT_LOCAL : MARKER_IMPORT_STD) + ";" + - this.path().length + ";" + this.path(); + URLString.length + ";" + URLString; } FileDependency.prototype.toString = function() { - return (this.isLocal() ? "LOCAL: " : "STD: ") + this.path(); + return (this.isLocal() ? "LOCAL: " : "STD: ") + this.URL(); } diff --git a/Objective-J/FileExecutable.js b/Objective-J/FileExecutable.js index 478060f8e..18c7d477d 100644 --- a/Objective-J/FileExecutable.js +++ b/Objective-J/FileExecutable.js @@ -24,7 +24,8 @@ var FileExecutablesForURLStrings = { }; function FileExecutable(/*CFURL*/ aURL, /*Executable*/ anExecutable) { -if (!aURL._path) aURL = new CFURL(aURL); + aURL = resolveURL(aURL); + var URLString = aURL.absoluteString(), existingFileExecutable = FileExecutablesForURLStrings[URLString]; @@ -33,7 +34,7 @@ if (!aURL._path) aURL = new CFURL(aURL); FileExecutablesForURLStrings[URLString] = this; - var fileContents = rootResource.nodeAtSubPath(aURL.path()).contents(), + var fileContents = StaticResource.resourceAtURL(aURL).contents(), executable = NULL, extension = aURL.pathExtension(); @@ -111,10 +112,10 @@ function decompile(/*String*/ aString, /*CFURL*/ aURL) code += text; else if (marker === MARKER_IMPORT_STD) - dependencies.push(new FileDependency(text, NO)); + dependencies.push(new FileDependency(new CFURL(text), NO)); else if (marker === MARKER_IMPORT_LOCAL) - dependencies.push(new FileDependency(text, YES)); + dependencies.push(new FileDependency(new CFURL(text), YES)); } return new Executable(code, dependencies, aURL); diff --git a/Objective-J/FileExecutableSearch.js b/Objective-J/FileExecutableSearch.js index 395e2f6c7..1314c8cc1 100644 --- a/Objective-J/FileExecutableSearch.js +++ b/Objective-J/FileExecutableSearch.js @@ -22,22 +22,23 @@ var FileExecutableSearchesForPaths = [{ }, { }]; -function FileExecutableSearch(/*String*/ aPath, /*BOOL*/ isLocal) +function FileExecutableSearch(/*CFURL*/ aURL, /*BOOL*/ isQuoted) { - if (!FILE.isAbsolute(aPath) && isLocal) - throw "Local searches cannot be relative: " + aPath; - - var existingSearch = FileExecutableSearchesForPaths[isLocal ? 1 : 0][aPath]; +// if (isQuoted && !aURL.protocol()) +// throw "Local searches cannot be relative: " + aPath; + var URLString = aURL.absoluteString(), + existingSearch = FileExecutableSearchesForPaths[isQuoted ? 1 : 0][URLString]; if (existingSearch) return existingSearch; - FileExecutableSearchesForPaths[isLocal ? 1 : 0][aPath] = this; - + FileExecutableSearchesForPaths[isQuoted ? 1 : 0][URLString] = this; + this._UID = objj_generateObjectUID(); + + this._URL = aURL; this._isComplete = NO; this._eventDispatcher = new EventDispatcher(this); - this._path = aPath; this._result = NULL; @@ -46,9 +47,9 @@ function FileExecutableSearch(/*String*/ aPath, /*BOOL*/ isLocal) function completed(/*String*/ aStaticResource) { if (!aStaticResource) - throw new Error("Could not load file at " + aPath); + throw new Error("Could not load file at " + aURL); - self._result = new FileExecutable(aStaticResource.path()); + self._result = new FileExecutable(aStaticResource.URL()); self._isComplete = YES; self._eventDispatcher.dispatchEvent( @@ -58,17 +59,17 @@ function FileExecutableSearch(/*String*/ aPath, /*BOOL*/ isLocal) }); } - if (isLocal || FILE.isAbsolute(aPath)) - rootResource.resolveSubPath(aPath, NO, completed); + if (isQuoted) + StaticResource.resolveResourceAtURL(aURL, NO, completed); else - StaticResource.resolveStandardNodeAtPath(aPath, completed); + StaticResource.resolveResourceAtURLSearchingIncludeURLs(aURL, completed); } exports.FileExecutableSearch = FileExecutableSearch; -FileExecutableSearch.prototype.path = function() +FileExecutableSearch.prototype.URL = function() { - return this._path; + return this._URL; } FileExecutableSearch.prototype.result = function() diff --git a/Objective-J/Preprocessor.js b/Objective-J/Preprocessor.js index 54e5d97c7..22b0924f9 100644 --- a/Objective-J/Preprocessor.js +++ b/Objective-J/Preprocessor.js @@ -534,30 +534,30 @@ Preprocessor.prototype.implementation = function(tokens, /*StringBuffer*/ aStrin Preprocessor.prototype._import = function(tokens) { - var path = "", + var URLString = "", token = tokens.skip_whitespace(), - isLocal = (token != TOKEN_LESS_THAN); + isQuoted = (token !== TOKEN_LESS_THAN); if (token === TOKEN_LESS_THAN) { - while((token = tokens.next()) && token != TOKEN_GREATER_THAN) - path += token; + while((token = tokens.next()) && token !== TOKEN_GREATER_THAN) + URLString += token; if(!token) throw new SyntaxError(this.error_message("*** Unterminated import statement.")); } - else if (token.charAt(0) == TOKEN_DOUBLE_QUOTE) - path = token.substr(1, token.length - 2); + else if (token.charAt(0) === TOKEN_DOUBLE_QUOTE) + URLString = token.substr(1, token.length - 2); else throw new SyntaxError(this.error_message("*** Expecting '<' or '\"', found \"" + token + "\".")); CONCAT(this._buffer, "objj_executeFile(\""); - CONCAT(this._buffer, path); - CONCAT(this._buffer, isLocal ? "\", true);" : "\", false);"); + CONCAT(this._buffer, URLString); + CONCAT(this._buffer, isQuoted ? "\", YES);" : "\", NO);"); - this._dependencies.push(new FileDependency(path, isLocal)); + this._dependencies.push(new FileDependency(new CFURL(URLString), isQuoted)); } Preprocessor.prototype.method = function(/*Lexer*/ tokens) diff --git a/Objective-J/StaticResource.js b/Objective-J/StaticResource.js index 238a8d718..02537ff4b 100644 --- a/Objective-J/StaticResource.js +++ b/Objective-J/StaticResource.js @@ -1,124 +1,5 @@ -var FILE = -#ifdef COMMONJS -require("file"); -#else -{ - absolute: function(/*String*/ aPath) - { - aPath = FILE.normal(aPath); - - if (FILE.isAbsolute(aPath)) - return aPath; - - return FILE.join(FILE.cwd(), aPath); - }, - - basename: function(/*String*/ aPath) - { - var components = FILE.split(FILE.normal(aPath)); - - return components[components.length - 1]; - }, - - extension: function(/*String*/ aPath) - { - aPath = FILE.basename(aPath); - aPath = aPath.replace(/^\.*/, ''); - var index = aPath.lastIndexOf("."); - return index <= 0 ? "" : aPath.substring(index); - }, - - cwd: function() - { - return FILE._cwd; - }, - - normal: function(/*String*/ aPath) - { - if (!aPath) - return ""; - - var components = aPath.split("/"), - results = [], - index = 0, - count = components.length, - isRoot = aPath.charAt(0) === "/"; - - for (; index < count; ++index) - { - var component = components[index]; - - // These simply remain in the current directory. - if (component === "" || component === ".") - continue; - - if (component !== "..") - { - results.push(component); - continue; - } - - var resultsCount = results.length; - - // If we have a valid previous component, "climb" it. - if (resultsCount > 0 && results[resultsCount - 1] !== "..") - results.pop(); - - // If this isn't a root listing, and we are preceded by only ..'s, or - // nothing at all, then add it since it makes sense for relative paths. - else if (!isRoot && resultsCount === 0 || results[resultsCount - 1] === "..") - results.push(component); - } - - return (isRoot ? "/" : "") + results.join("/"); - }, - - dirname: function(/*String*/ aPath) - { - var aPath = FILE.normal(aPath), - components = FILE.split(aPath); - - if (components.length === 2) - components.unshift(""); - - return FILE.join.apply(FILE, components.slice(0, components.length - 1)); - }, - - isAbsolute: function(/*String*/ aPath) - { - return aPath.charAt(0) === "/"; - }, - - join: function() - { - if (arguments.length === 1 && arguments[0] === "") - return "/"; - - return FILE.normal(Array.prototype.join.call(arguments, "/")); - }, - - split: function(/*String*/ aPath) - { - return FILE.normal(aPath).split("/"); - } -} - -var path = window.location.pathname, - DOMBaseElement = document.getElementsByTagName("base")[0]; - -if (DOMBaseElement) - path = DOMBaseElement.getAttribute("href"); - -// If this is a directory, then use it as our relative path. -if (path.charAt(path.length - 1) === "/") - FILE._cwd = path; - -// If not, use it's parent. -else - FILE._cwd = FILE.dirname(path); - -#endif +var rootResources = { }; function StaticResource(/*String*/ aName, /*StaticResource*/ aParent, /*BOOL*/ isDirectory, /*BOOL*/ isResolved) { @@ -127,7 +8,13 @@ function StaticResource(/*String*/ aName, /*StaticResource*/ aParent, /*BOOL*/ i this._name = aName; this._isResolved = !!isResolved; - this._path = FILE.join(aParent ? aParent.path() : "", aName); + this._URL = new CFURL(aName, aParent && aParent.URL().asDirectoryPathURL()); + + if (isDirectory) + this._URL = this._URL.asDirectoryPathURL(); + + if (!aParent) + rootResources[aName] = this; this._isDirectory = !!isDirectory; this._isNotFound = NO; @@ -142,6 +29,11 @@ function StaticResource(/*String*/ aName, /*StaticResource*/ aParent, /*BOOL*/ i this._contents = ""; } +StaticResource.rootResources = function() +{ + return rootResources; +} + exports.StaticResource = StaticResource; function resolveStaticResource(/*StaticResource*/ aResource) @@ -158,7 +50,7 @@ StaticResource.prototype.resolve = function() { if (this.isDirectory()) { - var bundle = new CFBundle(this.path()); + var bundle = new CFBundle(this.URL()); // Eat any errors. bundle.onerror = function() { }; @@ -182,7 +74,7 @@ StaticResource.prototype.resolve = function() resolveStaticResource(self); } - new FileRequest(this.path(), onsuccess, onfailure); + new FileRequest(this.URL(), onsuccess, onfailure); } } @@ -191,9 +83,9 @@ StaticResource.prototype.name = function() return this._name; } -StaticResource.prototype.path = function() +StaticResource.prototype.URL = function() { - return this._path; + return this._URL; } StaticResource.prototype.contents = function() @@ -221,50 +113,88 @@ StaticResource.prototype.write = function(/*String*/ aString) this._contents += aString; } -StaticResource.prototype.resolveSubPath = function(/*String*/ aPath, /*BOOL*/ isDirectory, /*Function*/ aCallback) +function rootResourceForAbsoluteURL(/*CFURL*/ anAbsoluteURL) { - aPath = FILE.normal(aPath); + var schemeAndAuthority = anAbsoluteURL.schemeAndAuthority(), + resource = rootResources[schemeAndAuthority]; - if (aPath === "/") - return aCallback(rootResource); + if (!resource) + resource = new StaticResource(schemeAndAuthority, NULL, YES, YES); - if (!FILE.isAbsolute(aPath)) - aPath = FILE.join(this.path(), aPath); - - var components = FILE.split(aPath), - index = this === rootResource ? 1 : FILE.split(this.path()).length; - - resolvePathComponents(this, isDirectory, components, index, aCallback); + return resource; } -function resolvePathComponents(/*StaticResource*/ startResource, /*BOOL*/ isDirectory, /*Array*/ components, /*Integer*/ index, /*Function*/ aCallback) +function resolveURL(/*CFURL|String*/ aURL) { - var count = components.length, - parent = startResource; + return new CFURL(aURL, mainBundleURL); +} - function continueResolution() +StaticResource.resourceAtURL = function(/*CFURL*/ aURL, /*BOOL*/ resolveAsDirectoriesIfNecessary) +{ + aURL = resolveURL(aURL).absoluteURL(); + + var resource = rootResourceForAbsoluteURL(aURL), + components = aURL.pathComponents(), + index = 0, + count = components.length; + + for (; index < count; ++index) { - resolvePathComponents(parent, isDirectory, components, index, aCallback); + var name = components[index]; + + if (hasOwnProperty.call(resource._children, name)) + resource = resource._children[name]; + + else if (resolveAsDirectoriesIfNecessary) + resource = new StaticResource(name, resource, YES, YES); + + else + throw new Error("Static Resource at " + aURL + " is not resolved (\"" + name + "\")"); } + return resource; +} + +StaticResource.prototype.resourceAtURL = function(/*CFURL|String*/ aURL, /*BOOL*/ resolveAsDirectoriesIfNecessary) +{ + return StaticResource.resourceAtURL(new CFURL(aURL, this.URL()), resolveAsDirectoriesIfNecessary); +} + +StaticResource.resolveResourceAtURL = function(/*CFURL*/ aURL, /*BOOL*/ isDirectory, /*Function*/ aCallback) +{ + aURL = resolveURL(aURL).absoluteURL(); + + resolveResourceComponents(rootResourceForAbsoluteURL(aURL), isDirectory, aURL.pathComponents(), 0, aCallback); +} + +StaticResource.prototype.resolveResourceAtURL = function(/*CFURL|String*/ aURL, /*BOOL*/ isDirectory, /*Function*/ aCallback) +{ + StaticResource.resolveResourceAtURL(new CFURL(aURL, this.URL()).absoluteURL(), isDirectory, aCallback); +} + +function resolveResourceComponents(/*StaticResource*/ aResource, /*BOOL*/ isDirectory, /*Array*/ components, /*Integer*/ index, /*Function*/ aCallback) +{ + var count = components.length; + for (; index < count; ++index) { var name = components[index], - child = parent._children[name]; -//CPLog(index + " " + components + ":" + (childNode && childNode.isResolved()) + ":"); -//CPLog(name + " of " + parentNode.name() + " " + (childNode && childNode.name())); -//CPLog(parentNode._childNodes); -// + "(" + components + ")" + " " + index + "/" + count + ":" + (childNode && childNode.name()) +">" + (childNode ? 1:0) + " " + (childNode && childNode.isResolved())); + child = hasOwnProperty.call(aResource._children, name) && aResource._children[name]; + // If the child doesn't exist, create and resolve it. if (!child) { - child = new StaticResource(name, parent, index + 1 < count || isDirectory , NO); + child = new StaticResource(name, aResource, index + 1 < count || isDirectory , NO); child.resolve(); } // If this resource is still being resolved, just wait and rerun this same method when it's ready. if (!child.isResolved()) - return child.addEventListener("resolve", continueResolution); + return child.addEventListener("resolve", function() + { + // Continue resolving once this is done. + resolveResourceComponents(aResource, isDirectory, components, index, aCallback); + }); // If we've already determined that this file doesn't exist... if (child.isNotFound()) @@ -274,10 +204,36 @@ function resolvePathComponents(/*StaticResource*/ startResource, /*BOOL*/ isDire if ((index + 1 < count) && child.isFile()) return aCallback(null, new Error("File is not a directory: " + components.join("/"))); - parent = child; + aResource = child; } - return aCallback(parent); + aCallback(aResource); +} + +function resolveResourceAtURLSearchingIncludeURLs(/*CFURL*/ aURL, /*Number*/ anIndex, /*Function*/ aCallback) +{ + var includeURLs = StaticResource.includeURLs(), + searchURL = new CFURL(aURL, includeURLs[anIndex]).absoluteURL(); + + StaticResource.resolveResourceAtURL(searchURL, NO, function(/*StaticResource*/ aStaticResource) + { + if (!aStaticResource) + { + if (anIndex + 1 < includeURLs.length) + resolveResourceAtURLSearchingIncludeURLs(aURL, anIndex + 1, aCallback); + else + aCallback(NULL); + + return; + } + + aCallback(aStaticResource); + }); +} + +StaticResource.resolveResourceAtURLSearchingIncludeURLs = function(/*CFURL*/ aURL, /*Function*/ aCallback) +{ + resolveResourceAtURLSearchingIncludeURLs(aURL, 0, aCallback); } StaticResource.prototype.addEventListener = function(/*String*/ anEventName, /*Function*/ anEventListener) @@ -310,7 +266,7 @@ StaticResource.prototype.toString = function(/*BOOL*/ includeNotFounds) if (this.isNotFound()) return ""; - var string = this.parent() ? this.name() : "/"; + var string = this.name(); if (this.isDirectory()) { @@ -329,61 +285,25 @@ StaticResource.prototype.toString = function(/*BOOL*/ includeNotFounds) return string; } -StaticResource.prototype.nodeAtSubPath = function(/*String*/ aPath, /*BOOL*/ shouldResolveAsDirectories) +var includeURLs = NULL; + +StaticResource.includeURLs = function() { - aPath = FILE.normal(aPath); + if (includeURLs) + return includeURLs; - var components = FILE.split(FILE.isAbsolute(aPath) ? aPath : FILE.join(this.path(), aPath)), - index = 1, - count = components.length, - parent = rootResource; + var includeURLs = []; - for (; index < count; ++index) - { - var name = components[index]; + if (!global.OBJJ_INCLUDE_PATHS && !global.OBJJ_INCLUDE_URLS) + includeURLs = ["Frameworks", "Frameworks/Debug"]; - if (hasOwnProperty.call(parent._children, name)) - parent = parent._children[name]; + else + includeURLs = (global.OBJJ_INCLUDE_PATHS || []).concat(global.OBJJ_INCLUDE_URLS || []); - else if (shouldResolveAsDirectories) - parent = new StaticResource(name, parent, YES, YES); + var count = includeURLs.length; - else - throw NULL; - } + while (count--) + includeURLs[count] = new CFURL(includeURLs[count]).asDirectoryPathURL(); - return parent; + return includeURLs; } - -StaticResource.resolveStandardNodeAtPath = function(/*String*/ aPath, /*Function*/ aCallback) -{ - var includePaths = StaticResource.includePaths(), - resolveStandardNodeAtPath = function(/*String*/ aPath, /*int*/ anIndex) - { - var searchPath = FILE.absolute(FILE.join(includePaths[anIndex], FILE.normal(aPath))); - - rootResource.resolveSubPath(searchPath, NO, function(/*StaticResource*/ aStaticResource) - { - if (!aStaticResource) - { - if (anIndex + 1< includePaths.length) - resolveStandardNodeAtPath(aPath, anIndex + 1); - else - aCallback(NULL); - - return; - } - - aCallback(aStaticResource); - }); - }; - - resolveStandardNodeAtPath(aPath, 0); -} - -StaticResource.includePaths = function() -{ - return global.OBJJ_INCLUDE_PATHS || ["Frameworks", "Frameworks/Debug"]; -} - -StaticResource.cwd = FILE.cwd(); diff --git a/Tests/Objective-J/CFURLTest.j b/Tests/Objective-J/CFURLTest.j new file mode 100644 index 000000000..f168bdfed --- /dev/null +++ b/Tests/Objective-J/CFURLTest.j @@ -0,0 +1,63 @@ + +@implementation CFURLTest : OJTestCase +{ +} + +- (void)testRelativeURLs +{ + var URLStrings = + { + "g:h" : "g:h", + "g" : "http://a/b/c/g", + "./g" : "http://a/b/c/g", + "g/" : "http://a/b/c/g/", + "/g" : "http://a/g", + //"//g" : "http://g", + "?y" : "http://a/b/c/?y", + "g?y" : "http://a/b/c/g?y", + //"#s" : "(current document)#s", + "g#s" : "http://a/b/c/g#s", + "g?y#s" : "http://a/b/c/g?y#s", + ";x" : "http://a/b/c/;x", + "g;x" : "http://a/b/c/g;x", + "g;x?y#s" : "http://a/b/c/g;x?y#s", + "." : "http://a/b/c/", + "./" : "http://a/b/c/", + ".." : "http://a/b/", + "../" : "http://a/b/", + "../g" : "http://a/b/g", + "../.." : "http://a/", + "../../" : "http://a/", + "../../g" : "http://a/g", + "../../../g" : "http://a/../g", + "../../../../g" : "http://a/g",//"http://a/../../g", + "/./g" : "http://a/g",//"http://a/./g", +// "/../g" : "http://a/g",//"http://a/../g", + "g." : "http://a/b/c/g.", + ".g" : "http://a/b/c/.g", + "g.." : "http://a/b/c/g..", + "..g" : "http://a/b/c/..g", + "./../g" : "http://a/b/g", + "./g/." : "http://a/b/c/g/", + "g/./h" : "http://a/b/c/g/h", + "g/../h" : "http://a/b/c/h", + "g;x=1/./y" : "http://a/b/c/g;x=1/y", + "g;x=1/../y" : "http://a/b/c/y", + "g?y/./x" : "http://a/b/c/g?y/./x", + "g?y/../x" : "http://a/b/c/g?y/../x", + "g#s/./x" : "http://a/b/c/g#s/./x", + "g#s/../x" : "http://a/b/c/g#s/../x" + }; + + var URLString, + baseURL = new CFURL("http://a/b/c/d;p?q"); + + for (URLString in URLStrings) + if (URLStrings.hasOwnProperty(URLString)) + { + print(URLStrings[URLString] + " " + new CFURL(URLString, baseURL).absoluteString()); + [self assert:URLStrings[URLString] equals:new CFURL(URLString, baseURL).absoluteString()]; + } +} + +@end