|
| 1 | +//>>excludeStart("jqmBuildExclude", pragmas.jqmBuildExclude); |
| 2 | +//>>description: Path parsing and manipulation helpers |
| 3 | +//>>label: AJAX Navigation System |
| 4 | +//>>group: Navigation |
| 5 | +define([ |
| 6 | + "jquery", |
| 7 | + "./jquery.mobile.core" ], function( $ ) { |
| 8 | +//>>excludeEnd("jqmBuildExclude"); |
| 9 | + |
| 10 | + var path, documentBase, $base, dialogHashKey = "&ui-state=dialog"; |
| 11 | + |
| 12 | + $.mobile.path = path = { |
| 13 | + // This scary looking regular expression parses an absolute URL or its relative |
| 14 | + // variants (protocol, site, document, query, and hash), into the various |
| 15 | + // components (protocol, host, path, query, fragment, etc that make up the |
| 16 | + // URL as well as some other commonly used sub-parts. When used with RegExp.exec() |
| 17 | + // or String.match, it parses the URL into a results array that looks like this: |
| 18 | + // |
| 19 | + // [0]: http://jblas:password@mycompany.com:8080/mail/inbox?msg=1234&type=unread#msg-content |
| 20 | + // [1]: http://jblas:password@mycompany.com:8080/mail/inbox?msg=1234&type=unread |
| 21 | + // [2]: http://jblas:password@mycompany.com:8080/mail/inbox |
| 22 | + // [3]: http://jblas:password@mycompany.com:8080 |
| 23 | + // [4]: http: |
| 24 | + // [5]: // |
| 25 | + // [6]: jblas:password@mycompany.com:8080 |
| 26 | + // [7]: jblas:password |
| 27 | + // [8]: jblas |
| 28 | + // [9]: password |
| 29 | + // [10]: mycompany.com:8080 |
| 30 | + // [11]: mycompany.com |
| 31 | + // [12]: 8080 |
| 32 | + // [13]: /mail/inbox |
| 33 | + // [14]: /mail/ |
| 34 | + // [15]: inbox |
| 35 | + // [16]: ?msg=1234&type=unread |
| 36 | + // [17]: #msg-content |
| 37 | + // |
| 38 | + urlParseRE: /^(((([^:\/#\?]+:)?(?:(\/\/)((?:(([^:@\/#\?]+)(?:\:([^:@\/#\?]+))?)@)?(([^:\/#\?\]\[]+|\[[^\/\]@#?]+\])(?:\:([0-9]+))?))?)?)?((\/?(?:[^\/\?#]+\/+)*)([^\?#]*)))?(\?[^#]+)?)(#.*)?/, |
| 39 | + |
| 40 | + // Abstraction to address xss (Issue #4787) by removing the authority in |
| 41 | + // browsers that auto decode it. All references to location.href should be |
| 42 | + // replaced with a call to this method so that it can be dealt with properly here |
| 43 | + getLocation: function( url ) { |
| 44 | + var uri = url ? this.parseUrl( url ) : location, |
| 45 | + hash = this.parseUrl( url || location.href ).hash; |
| 46 | + |
| 47 | + // mimic the browser with an empty string when the hash is empty |
| 48 | + hash = hash === "#" ? "" : hash; |
| 49 | + |
| 50 | + // Make sure to parse the url or the location object for the hash because using location.hash |
| 51 | + // is autodecoded in firefox, the rest of the url should be from the object (location unless |
| 52 | + // we're testing) to avoid the inclusion of the authority |
| 53 | + return uri.protocol + "//" + uri.host + uri.pathname + uri.search + hash; |
| 54 | + }, |
| 55 | + |
| 56 | + parseLocation: function() { |
| 57 | + return this.parseUrl( this.getLocation() ); |
| 58 | + }, |
| 59 | + |
| 60 | + //Parse a URL into a structure that allows easy access to |
| 61 | + //all of the URL components by name. |
| 62 | + parseUrl: function( url ) { |
| 63 | + // If we're passed an object, we'll assume that it is |
| 64 | + // a parsed url object and just return it back to the caller. |
| 65 | + if ( $.type( url ) === "object" ) { |
| 66 | + return url; |
| 67 | + } |
| 68 | + |
| 69 | + var matches = path.urlParseRE.exec( url || "" ) || []; |
| 70 | + |
| 71 | + // Create an object that allows the caller to access the sub-matches |
| 72 | + // by name. Note that IE returns an empty string instead of undefined, |
| 73 | + // like all other browsers do, so we normalize everything so its consistent |
| 74 | + // no matter what browser we're running on. |
| 75 | + return { |
| 76 | + href: matches[ 0 ] || "", |
| 77 | + hrefNoHash: matches[ 1 ] || "", |
| 78 | + hrefNoSearch: matches[ 2 ] || "", |
| 79 | + domain: matches[ 3 ] || "", |
| 80 | + protocol: matches[ 4 ] || "", |
| 81 | + doubleSlash: matches[ 5 ] || "", |
| 82 | + authority: matches[ 6 ] || "", |
| 83 | + username: matches[ 8 ] || "", |
| 84 | + password: matches[ 9 ] || "", |
| 85 | + host: matches[ 10 ] || "", |
| 86 | + hostname: matches[ 11 ] || "", |
| 87 | + port: matches[ 12 ] || "", |
| 88 | + pathname: matches[ 13 ] || "", |
| 89 | + directory: matches[ 14 ] || "", |
| 90 | + filename: matches[ 15 ] || "", |
| 91 | + search: matches[ 16 ] || "", |
| 92 | + hash: matches[ 17 ] || "" |
| 93 | + }; |
| 94 | + }, |
| 95 | + |
| 96 | + //Turn relPath into an asbolute path. absPath is |
| 97 | + //an optional absolute path which describes what |
| 98 | + //relPath is relative to. |
| 99 | + makePathAbsolute: function( relPath, absPath ) { |
| 100 | + if ( relPath && relPath.charAt( 0 ) === "/" ) { |
| 101 | + return relPath; |
| 102 | + } |
| 103 | + |
| 104 | + relPath = relPath || ""; |
| 105 | + absPath = absPath ? absPath.replace( /^\/|(\/[^\/]*|[^\/]+)$/g, "" ) : ""; |
| 106 | + |
| 107 | + var absStack = absPath ? absPath.split( "/" ) : [], |
| 108 | + relStack = relPath.split( "/" ); |
| 109 | + for ( var i = 0; i < relStack.length; i++ ) { |
| 110 | + var d = relStack[ i ]; |
| 111 | + switch ( d ) { |
| 112 | + case ".": |
| 113 | + break; |
| 114 | + case "..": |
| 115 | + if ( absStack.length ) { |
| 116 | + absStack.pop(); |
| 117 | + } |
| 118 | + break; |
| 119 | + default: |
| 120 | + absStack.push( d ); |
| 121 | + break; |
| 122 | + } |
| 123 | + } |
| 124 | + return "/" + absStack.join( "/" ); |
| 125 | + }, |
| 126 | + |
| 127 | + //Returns true if both urls have the same domain. |
| 128 | + isSameDomain: function( absUrl1, absUrl2 ) { |
| 129 | + return path.parseUrl( absUrl1 ).domain === path.parseUrl( absUrl2 ).domain; |
| 130 | + }, |
| 131 | + |
| 132 | + //Returns true for any relative variant. |
| 133 | + isRelativeUrl: function( url ) { |
| 134 | + // All relative Url variants have one thing in common, no protocol. |
| 135 | + return path.parseUrl( url ).protocol === ""; |
| 136 | + }, |
| 137 | + |
| 138 | + //Returns true for an absolute url. |
| 139 | + isAbsoluteUrl: function( url ) { |
| 140 | + return path.parseUrl( url ).protocol !== ""; |
| 141 | + }, |
| 142 | + |
| 143 | + //Turn the specified realtive URL into an absolute one. This function |
| 144 | + //can handle all relative variants (protocol, site, document, query, fragment). |
| 145 | + makeUrlAbsolute: function( relUrl, absUrl ) { |
| 146 | + if ( !path.isRelativeUrl( relUrl ) ) { |
| 147 | + return relUrl; |
| 148 | + } |
| 149 | + |
| 150 | + if ( absUrl === undefined ) { |
| 151 | + absUrl = path.documentBase; |
| 152 | + } |
| 153 | + |
| 154 | + var relObj = path.parseUrl( relUrl ), |
| 155 | + absObj = path.parseUrl( absUrl ), |
| 156 | + protocol = relObj.protocol || absObj.protocol, |
| 157 | + doubleSlash = relObj.protocol ? relObj.doubleSlash : ( relObj.doubleSlash || absObj.doubleSlash ), |
| 158 | + authority = relObj.authority || absObj.authority, |
| 159 | + hasPath = relObj.pathname !== "", |
| 160 | + pathname = path.makePathAbsolute( relObj.pathname || absObj.filename, absObj.pathname ), |
| 161 | + search = relObj.search || ( !hasPath && absObj.search ) || "", |
| 162 | + hash = relObj.hash; |
| 163 | + |
| 164 | + return protocol + doubleSlash + authority + pathname + search + hash; |
| 165 | + }, |
| 166 | + |
| 167 | + //Add search (aka query) params to the specified url. |
| 168 | + addSearchParams: function( url, params ) { |
| 169 | + var u = path.parseUrl( url ), |
| 170 | + p = ( typeof params === "object" ) ? $.param( params ) : params, |
| 171 | + s = u.search || "?"; |
| 172 | + return u.hrefNoSearch + s + ( s.charAt( s.length - 1 ) !== "?" ? "&" : "" ) + p + ( u.hash || "" ); |
| 173 | + }, |
| 174 | + |
| 175 | + convertUrlToDataUrl: function( absUrl ) { |
| 176 | + var u = path.parseUrl( absUrl ); |
| 177 | + if ( path.isEmbeddedPage( u ) ) { |
| 178 | + // For embedded pages, remove the dialog hash key as in getFilePath(), |
| 179 | + // and remove otherwise the Data Url won't match the id of the embedded Page. |
| 180 | + return u.hash |
| 181 | + .split( dialogHashKey )[0] |
| 182 | + .replace( /^#/, "" ) |
| 183 | + .replace( /\?.*$/, "" ); |
| 184 | + } else if ( path.isSameDomain( u, path.documentBase ) ) { |
| 185 | + return u.hrefNoHash.replace( path.documentBase.domain, "" ).split( dialogHashKey )[0]; |
| 186 | + } |
| 187 | + |
| 188 | + return window.decodeURIComponent(absUrl); |
| 189 | + }, |
| 190 | + |
| 191 | + //get path from current hash, or from a file path |
| 192 | + get: function( newPath ) { |
| 193 | + if ( newPath === undefined ) { |
| 194 | + newPath = path.parseLocation().hash; |
| 195 | + } |
| 196 | + return path.stripHash( newPath ).replace( /[^\/]*\.[^\/*]+$/, '' ); |
| 197 | + }, |
| 198 | + |
| 199 | + //return the substring of a filepath before the sub-page key, for making a server request |
| 200 | + getFilePath: function( path ) { |
| 201 | + var splitkey = '&' + $.mobile.subPageUrlKey; |
| 202 | + return path && path.split( splitkey )[0].split( dialogHashKey )[0]; |
| 203 | + }, |
| 204 | + |
| 205 | + //set location hash to path |
| 206 | + set: function( path ) { |
| 207 | + location.hash = path; |
| 208 | + }, |
| 209 | + |
| 210 | + //test if a given url (string) is a path |
| 211 | + //NOTE might be exceptionally naive |
| 212 | + isPath: function( url ) { |
| 213 | + return ( /\// ).test( url ); |
| 214 | + }, |
| 215 | + |
| 216 | + //return a url path with the window's location protocol/hostname/pathname removed |
| 217 | + clean: function( url ) { |
| 218 | + return url.replace( path.documentBase.domain, "" ); |
| 219 | + }, |
| 220 | + |
| 221 | + //just return the url without an initial # |
| 222 | + stripHash: function( url ) { |
| 223 | + return url.replace( /^#/, "" ); |
| 224 | + }, |
| 225 | + |
| 226 | + // TODO leave the dialog hashkey cleaning in nav core |
| 227 | + //remove the preceding hash, any query params, and dialog notations |
| 228 | + cleanHash: function( hash ) { |
| 229 | + return path.stripHash( hash.replace( /\?.*$/, "" ).replace( dialogHashKey, "" ) ); |
| 230 | + }, |
| 231 | + |
| 232 | + isHashValid: function( hash ) { |
| 233 | + return ( /^#[^#]+$/ ).test( hash ); |
| 234 | + }, |
| 235 | + |
| 236 | + //check whether a url is referencing the same domain, or an external domain or different protocol |
| 237 | + //could be mailto, etc |
| 238 | + isExternal: function( url ) { |
| 239 | + var u = path.parseUrl( url ); |
| 240 | + return u.protocol && u.domain !== path.documentUrl.domain ? true : false; |
| 241 | + }, |
| 242 | + |
| 243 | + hasProtocol: function( url ) { |
| 244 | + return ( /^(:?\w+:)/ ).test( url ); |
| 245 | + }, |
| 246 | + |
| 247 | + //check if the specified url refers to the first page in the main application document. |
| 248 | + isFirstPageUrl: function( url ) { |
| 249 | + // We only deal with absolute paths. |
| 250 | + var u = path.parseUrl( path.makeUrlAbsolute( url, path.documentBase ) ), |
| 251 | + |
| 252 | + // Does the url have the same path as the document? |
| 253 | + samePath = u.hrefNoHash === path.documentUrl.hrefNoHash || ( path.documentBaseDiffers && u.hrefNoHash === documentBase.hrefNoHash ), |
| 254 | + |
| 255 | + // Get the first page element. |
| 256 | + fp = $.mobile.firstPage, |
| 257 | + |
| 258 | + // Get the id of the first page element if it has one. |
| 259 | + fpId = fp && fp[0] ? fp[0].id : undefined; |
| 260 | + |
| 261 | + // The url refers to the first page if the path matches the document and |
| 262 | + // it either has no hash value, or the hash is exactly equal to the id of the |
| 263 | + // first page element. |
| 264 | + return samePath && ( !u.hash || u.hash === "#" || ( fpId && u.hash.replace( /^#/, "" ) === fpId ) ); |
| 265 | + }, |
| 266 | + |
| 267 | + isEmbeddedPage: function( url ) { |
| 268 | + var u = path.parseUrl( url ); |
| 269 | + |
| 270 | + //if the path is absolute, then we need to compare the url against |
| 271 | + //both the documentUrl and the documentBase. The main reason for this |
| 272 | + //is that links embedded within external documents will refer to the |
| 273 | + //application document, whereas links embedded within the application |
| 274 | + //document will be resolved against the document base. |
| 275 | + if ( u.protocol !== "" ) { |
| 276 | + return ( u.hash && ( u.hrefNoHash === path.documentUrl.hrefNoHash || ( path.documentBaseDiffers && u.hrefNoHash === path.documentBase.hrefNoHash ) ) ); |
| 277 | + } |
| 278 | + return ( /^#/ ).test( u.href ); |
| 279 | + }, |
| 280 | + |
| 281 | + |
| 282 | + // Some embedded browsers, like the web view in Phone Gap, allow cross-domain XHR |
| 283 | + // requests if the document doing the request was loaded via the file:// protocol. |
| 284 | + // This is usually to allow the application to "phone home" and fetch app specific |
| 285 | + // data. We normally let the browser handle external/cross-domain urls, but if the |
| 286 | + // allowCrossDomainPages option is true, we will allow cross-domain http/https |
| 287 | + // requests to go through our page loading logic. |
| 288 | + isPermittedCrossDomainRequest: function( docUrl, reqUrl ) { |
| 289 | + return $.mobile.allowCrossDomainPages && |
| 290 | + docUrl.protocol === "file:" && |
| 291 | + reqUrl.search( /^https?:/ ) !== -1; |
| 292 | + } |
| 293 | + }; |
| 294 | + |
| 295 | + path.documentUrl = path.parseLocation(); |
| 296 | + |
| 297 | + $base = $( "head" ).find( "base" ); |
| 298 | + |
| 299 | + path.documentBase = $base.length ? |
| 300 | + path.parseUrl( path.makeUrlAbsolute( $base.attr( "href" ), path.documentUrl.href ) ) : |
| 301 | + path.documentUrl; |
| 302 | + |
| 303 | + path.documentBaseDiffers = (path.documentUrl.hrefNoHash !== path.documentBase.hrefNoHash); |
| 304 | + |
| 305 | +//>>excludeStart("jqmBuildExclude", pragmas.jqmBuildExclude); |
| 306 | +}); |
| 307 | +//>>excludeEnd("jqmBuildExclude"); |
0 commit comments