Skip to content
This repository was archived by the owner on Oct 8, 2021. It is now read-only.
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Added more complete unit tests, modified implementation comments
  • Loading branch information
Oskari Koskimies committed Apr 12, 2011
commit 0bd848c067ffe483b3b594bd783adf28ad936737
106 changes: 73 additions & 33 deletions js/jquery.mobile.navigation.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,30 +69,45 @@
},

// There are several supported url formats, which are cleaned (converted to canonical form) in this function.
// Syntax: current_location + url => resulting_location (returned canonical form)

// 1) Relative: example.com/a/#b/c.html + ../d/e.html => example.com/a/#d/e.html (#d/e.html)
// 2) Hash-relative: example.com/a/#b/c.html + #d/e.html => example.com/a/#d/e.html (#d/e.html)
// 3) Hash-absolute: example.com/a/#b/c.html + #/d/e.html => example.com/a/#d/e.html (#d/e.html)
// 4) Absolute path: example.com/a/#b/c.html + /d/e.html => example.com/a/#../d/e.html (#../d/e.html)
// 5) Absolute same-host: example.com/a/#b/c.html + example.com/d/e.html => example.com/a/#../d/e.html (#../d/e.html)
// 6) Absolute diff-host: example.com/a/#b/c.html + other.com/d/e.html => other.com/d/e.html (other.com/d/e.html)
// 7) Hashed same-host same-path: example.com/a/#b/c.html + example.com/a/#d/e.html => example.com/a/#d/e.html (#d/e.html)
// 8) Hashed same-host diff-path: example.com/a/#b/c.html + example.com/d/#e.html => example.com/d/#e.html (example.com/d/#e.html)
// 9) Hashed diff-host: example.com/a/#b/c.html + other.com/d/#e.html => other.com/d/#e.html (other.com/d/#e.html)
// The returned value will contain protocol and hostname part only if the url is an external url which should
// not be loaded with XHR.
// Note that the entry page (the page which loads the jQuery Mobile library) should preferably always be an
// index.* file, so that you can refer to it with path that ends in a slash.
// E.g. example.com/a/#b/c.html rather than example.com/a/app.html#b/c.html
// This makes the hashed urls look more clean and intuitive.
// Common use case examples are given below.
// Note: For brevity protocol has been omitted from urls, but it would be present whenever hostname is present.
// Syntax: current_location + url => resulting_location (returned canonical form)

// 01) Relative: example.com/a/#b/c.html + ../d/e.html => example.com/a/#d/e.html (#d/e.html)
// 02) Hash-relative: example.com/a/#b/c.html + #d/e.html => example.com/a/#d/e.html (#d/e.html)
// 03) Hash-root: example.com/a/#b/c.html + #/d/e.html => example.com/a/#d/e.html (#d/e.html)
// 04) Absolute path: example.com/a/#b/c.html + /d/e.html => example.com/a/#../d/e.html (#../d/e.html)
// 05) Host-absolute (same host): example.com/a/#b/c.html + example.com/d/e.html => example.com/a/#../d/e.html (#../d/e.html)
// 06) Host-absolute (diff host): example.com/a/#b/c.html + other.com/d/e.html => other.com/d/e.html (other.com/d/e.html)
// 07) Hashed relative (same path): example.com/a/#b/c.html + ../#d/e.html => example.com/a/#d/e.html (#d/e.html)
// 08) Hashed relative (diff path): example.com/a/#b/c.html + ../d/#e.html => example.com/d/#e.html (example.com/d/#e.html)
// 09) Hashed absolute (same path): example.com/a/#b/c.html + /a/#d/e.html => example.com/a/#d/e.html (#d/e.html)
// 10) Hashed absolute (diff path): example.com/a/#b/c.html + /d/#e.html => example.com/d/#e.html (example.com/d/#e.html)
// 11) Hashed host-abs (same path): example.com/a/#b/c.html + example.com/a/#d/e.html => example.com/a/#d/e.html (#d/e.html)
// 12) Hashed host-abs (diff path): example.com/a/#b/c.html + example.com/d/#e.html => example.com/d/#e.html (example.com/d/#e.html)
// 13) Hashed host-abs (diff host): example.com/a/#b/c.html + other.com/d/#e.html => other.com/d/#e.html (other.com/d/#e.html)

// The canonical form is hash-relative (e.g. #d/e.html) for urls on the same host
// and original form (no change) for urls on a different host.
// A hashed url (cases 7-9) must always use the canonical form in the hash part.
// A hashed url (cases 07-13) must always use the canonical form in the hash part.
// E.g. example.com/a/#/b/c.html is illegal, you must use example.com/a/#b/c.html instead.
// In non-external urls index.* is never explicitly included at the end (ends in slash instead).
// Hash-absolute form is same as hash-relative, just with a slash prefix. The reason it is supported
// Hash-root form is same as hash-relative, just with a slash prefix. The reason it is supported
// is to allow you to write "#/" to refer to $.mobile.firstPage (and to write other hash urls in
// a way consistent with that).
// You can refer to $.mobile.firstPage in several ways:
// 1) Hash-absolute: example.com/a/#b/c.html + #/ => example.com/a/# (#)
// 2) Relative: example.com/a/#b/c.html + .. => example.com/a/# (#)
// 3) Absolute path: example.com/a/#b/c.html + /a/ => example.com/a/# (#)
// 1) Hash-root: example.com/a/#b/c.html + #/ => example.com/a/# (#)
// 2) Relative: example.com/a/#b/c.html + .. => example.com/a/# (#)
// 3) Absolute path: example.com/a/#b/c.html + /a/ => example.com/a/# (#)
// 4) Hashed relative: example.com/a/#b/c.html + ../# => example.com/a/# (#)
// 5) Hashed absolute: example.com/a/#b/c.html + /a/# => example.com/a/# (#)
// The recommended way is hash-root (#/), because it has not depend on current url.
// You can of course also refer to $.mobile.firstPage using its data-url attribute
// (=== ID if not defined), e.g. "#MyFirstPageId".
// This will be handled equivalently to "#/".
Expand All @@ -101,17 +116,31 @@
//console.log("clean:", url);
// Replace the protocol, host, and pathname only once at the beginning of the url to avoid
// problems when it's included as a part of a param
var leadingUrlRootRegex;
if (url.indexOf("#") > 0) {
var leadingUrlRootRegex, checkForRoot;
var hashIndex = url.indexOf("#");
if (hashIndex > 0) { // Hash, but not at beginning
// Add protocol and host to absolute and relative hashed urls so that they will be considered external
// if the pathname (before hash) does not match current pathname.
if (url.charAt(0) === "/") { // hashed absolute
url = location.protocol + "//" + location.host + url;
} else if (url.slice(0, location.protocol.length) !== location.protocol) { // hashed relative
url = location.protocol + "//" + location.host +
path.canonize(location.pathname + path.get() + url.slice(0, hashIndex)) +
url.slice(hashIndex);
}
// Absolute (with hostname) urls that contain a hash are converted to canonical form
// only if the pathname (before hash) matches current location.pathname.
// Otherwise it is considered an external URL and returned as-is
// (because regexp will not match and protocol is thus not removed).
// Such an URL should always be loaded as external, and isExternal will return true for it
// since protocol remains in the URL after path.clean.
leadingUrlRootRegex = new RegExp("^" + location.protocol + "//" + location.host + location.pathname);
leadingUrlRootRegex = new RegExp("^" + location.protocol + "//" + location.host + location.pathname + "(#.*)$");
} else {
leadingUrlRootRegex = new RegExp("^" + location.protocol + "//" + location.host);
leadingUrlRootRegex = new RegExp("^" + location.protocol + "//" + location.host + "(.*)$");
}
checkForRoot = leadingUrlRootRegex.exec(url);
if (checkForRoot) {
url = checkForRoot[1];
}
url = url.replace(leadingUrlRootRegex, "");
//console.log("Without host & pathname:", url);
Expand Down Expand Up @@ -151,21 +180,32 @@
return "#" + canonPath.join("/");
} else { // Relative
//console.log("path:", path.get());
canonPath = (path.get() + url).split("/");
//console.log("canonPath:" + canonPath);
// Remove dot-dot parts where possible by removing both the dot-dot and the preceding segment
i = 0;
while(i < canonPath.length) {
if (canonPath[i] === ".." && i > 0 && canonPath[i-1] !== "..") {
i--;
canonPath.splice(i,2);
} else {
i++;
}
}
return "#" + canonPath.join("/");
return "#" + path.canonize(path.get() + url);
}
}
},

// Remove dot-dot segments where possible by removing both the dot-dot segment and the preceding segment.
// E.g. a/b/../c becomes a/c, but ../a/b remains the same.
// Dot or empty segments are simply removed.
// e.g. a/./b/c or a//b/c becomes a/b/c
canonize: function( path ) {
var canonPath = path.split("/"), i = 0;
//console.log("canonPath:" + canonPath);
while(i < canonPath.length) {
if (canonPath[i] === ".." && i > 0 && canonPath[i-1] !== "..") {
i--;
canonPath.splice(i,2);
} else if (canonPath[i] === "." ||
// Cannot remove first/last because they mark beginning/trailing slash
(canonPath[i] === "" && i > 0 && i < canonPath.length-1)) {
canonPath.splice(i,1);
} else {
i++;
}
}
return canonPath.join("/");

},

//just return the url without an initial #
Expand Down
75 changes: 58 additions & 17 deletions tests/unit/navigation/navigation_core.js
Original file line number Diff line number Diff line change
Expand Up @@ -145,9 +145,13 @@
hrelpath1 = "#foo.html",
hrelpath2 = "#foo/bar.html",
hrelpath3 = "#../../foo/bar.html",
habspath1 = "#/foo.html",
habspath2 = "#/foo/bar.html",
habspath3 = "#/../../foo/bar.html",
hrootpath1 = "#/foo.html",
hrootpath2 = "#/foo/bar.html",
hrootpath3 = "#/../../foo/bar.html",
hashedrel1 = "../foo/#bar.html",
hashedrel2 = "../#foo/bar.html",
hashedabs1 = "/foo/#bar.html",
hashedabs2 = location.pathname + "#foo/bar.html",
segments = location.pathname.split("/"),
uppath = "#",
i;
Expand All @@ -159,20 +163,57 @@
// We are in a subdirectory, so one more ..
uppath += "foo/bar.html";

same( $.mobile.path.clean( localpath ), fakepath, "removes location protocol, host, port, pathname from same-domain path");
same( $.mobile.path.clean( remotepath ), remotepath, "does nothing to an external domain path");
same( $.mobile.path.clean( pathWithParam ), "#bar?baz=" + localroot, "doesn't remove params with localroot value");
same( $.mobile.path.clean( relpath1 ), "#foo.html", ".. removed from current path, no dirs left");
same( $.mobile.path.clean( relpath2 ), "#foo/bar.html", ".. removed from current path, one dir left");
same( $.mobile.path.clean( relpath3 ), "#../foo/bar.html", ".. removed from current path, one .. still left");
same( $.mobile.path.clean( abspath1 ), uppath, "absolute path above localroot");
same( $.mobile.path.clean( abspath2 ), "#foo/bar.html", "absolute path below localroot");
same( $.mobile.path.clean( hrelpath1 ), "#foo.html", "hash-relative path stays the same 1");
same( $.mobile.path.clean( hrelpath2 ), "#foo/bar.html", "hash-relative path stays the same 2");
same( $.mobile.path.clean( hrelpath3 ), "#../../foo/bar.html", "hash-relative path stays the same 3");
same( $.mobile.path.clean( habspath1 ), "#foo.html", "hash-absolute path stays the same minus leading slash 1");
same( $.mobile.path.clean( habspath2 ), "#foo/bar.html", "hash-absolute path stays the same minus leading slash 2");
same( $.mobile.path.clean( habspath3 ), "#../../foo/bar.html", "hash-absolute path stays the same minus leading slash 3");
same( $.mobile.path.clean( localpath ), fakepath,
"removes location protocol, host, port, pathname from same-domain path");
same( $.mobile.path.clean( pathWithParam ), "#bar?baz=" + localroot,
"doesn't remove params with localroot value");
// relative
same( $.mobile.path.clean( relpath1 ), "#foo.html",
".. removed from current path, no dirs left");
same( $.mobile.path.clean( relpath2 ), "#foo/bar.html",
".. removed from current path, one dir left");
same( $.mobile.path.clean( relpath3 ), "#../foo/bar.html",
".. removed from current path, one .. still left");
// hash-relative
same( $.mobile.path.clean( hrelpath1 ), "#foo.html",
"hash-relative path stays the same 1");
same( $.mobile.path.clean( hrelpath2 ), "#foo/bar.html",
"hash-relative path stays the same 2");
same( $.mobile.path.clean( hrelpath3 ), "#../../foo/bar.html",
"hash-relative path stays the same 3");
// hash-root
same( $.mobile.path.clean( hrootpath1 ), "#foo.html",
"hash-absolute path stays the same minus leading slash 1");
same( $.mobile.path.clean( hrootpath2 ), "#foo/bar.html",
"hash-absolute path stays the same minus leading slash 2");
same( $.mobile.path.clean( hrootpath3 ), "#../../foo/bar.html",
"hash-absolute path stays the same minus leading slash 3");
// absolute
same( $.mobile.path.clean( abspath1 ), uppath,
"absolute path above localroot");
same( $.mobile.path.clean( abspath2 ), "#foo/bar.html",
"absolute path below localroot");
// hashed relative
same( $.mobile.path.clean( hashedrel1 ),
location.protocol + "//" + location.host + location.pathname + "foo/#bar.html",
"hashed relative, different path: returns full url");
same( $.mobile.path.clean( hashedrel2 ), "#foo/bar.html",
"hashed relative, same path: returns hash-relative path");
// hashed absolute
same( $.mobile.path.clean( hashedabs1 ),
location.protocol + "//" + location.host + "/foo/#bar.html",
"hashed absolute, different path: return full url");
same( $.mobile.path.clean( hashedabs2 ), "#foo/bar.html",
"hashed absolute, same path: returns hash-relative path");
// hashed host-absolute
same( $.mobile.path.clean( location.protocol + "//" + location.host + hashedabs1 ),
location.protocol + "//" + location.host + "/foo/#bar.html",
"hashed host-absolute, different path: return full url");
same( $.mobile.path.clean( location.protocol + "//" + location.host + hashedabs2 ),
"#foo/bar.html",
"hashed host-absolute, same path: return hash-relative path");
same( $.mobile.path.clean( remotepath ), remotepath,
"Hashed host-absolute, different host: return full url");
});

test( "path.stripHash is working properly", function(){
Expand Down