App Framework URL Jazz ====================== Gathered thoughts about URL woes at the various levels in our stack of components which deal with them. URLs are hard to get right, but it is important to do so. Application-level URL Handling ------------------------------ Public methods that change the URL: * `save()` -> `_save()` * `replace()` -> `_save()` * `upgrade()` -> `replace()` * `navigate()` -> `save()` So, Eventaully everything goes to `_save()`. ### Default `_save()` Behavior (Router) Router's default implementation of `_save()` will either use HTML5 `pushState()` or set `window.location.hash`. A security limitation exists when using HTML5 `pushState()` which does not exist for hash-based URLs, this being the same-origin policy. This method gets around of ever causing the browser to throw the security error by _always_ assuming the URL passed-in is from the same-origin and ignores everything before the path-part. ```javascript // Current page: http://example.com/ // Will happlily change the URL to: http://example.com/foo/ router.save('http://www.google.com/foo/'); ``` ### App's "enhanced" `_save()` Behavior App overrides `_save()` to do some extra things in the spirit of progressive-enhancement. The contract with `_save()` is to add or replace a history entry; so can also be accomplished by assigning a new URL to `window.location` or calling `window.location.resplace()`. To make your app a good citizen of the web, App adds a `serverRouting` attribute that cues App on whether or not the server can handle requests to all full-path URLs for the app. When `serverRouting` is `true`, and `html5` is `false`, these fallbacks are used instead of using hash-based URLs. ### Should `navigate()` and `save()` have the same result? I can see the argument for the example above where `save()` with an absolute URL of a different origin simply ignores the origin difference and just uses the path-part of the URL. But I don't think a method called `navigate()` should do this! ```javascript // Current page: http://example.com/ // Seems wrong that this would go to: http://different.domain.com/foo/ app.navigate('http://different.domain.com/foo/'); ``` It could be argued that this is silly, developers would never do this, but think about this case: ```javascript // Current page: http://example.com/ // Navigate to a secure page: app.navigate('https://example.com/sign-in/'); // Currently this would just go to: http://example.com/sign-in/ (not secure!) ``` These two URLs have different origins because their schemes are different. ### Route's `hasRoute()` Method with Absolute URLs We recently made sure that `hasRoute()` will not barf when passed an absolute URL, but its behavior is questionable like `navigate()`'s. ```javascript var router = new Y.Router({ routes: [ {path: '/foo/', callback: function () {}} ] }); // Current URL: http://example.com/ // Should `router` have a route for: http://www.google.com/foo/ route.hasRoute('http://www.google.com/foo/'); // => true!? ``` The router is reporting that it does in fact have a route handler for a URL which is at a completely different origin, this seems really confusing. Things I'm Thinking About ------------------------- ### Behavior of `navigate()` * What should `navigate()` do when called with a URL of a different origin from the current page? If it should go to that page using `window.location`, does this mean the default navigate behavior would either `save()` to the router, or use `window.location`. Should Pjax's link-click handler then always fire the `navigate` event, even when no route matches? <- That would seem weird when multiple Y.Apps are on the page. It seems like the `navigate` event should _only_ fire if there is a matching route to handle it. Inside of the `navigate()` method it can simply assign the absolute URL of a different origin to `window.location`, or check that a route matches and fire the `navigate` event. * What happens when an App is using `serverRouting: false`, and a link like: `` is clicked? Should the App dispatch to the route handlers for this (assuming there is one)? What if the link starts with "/": ``? * Calling `navigate()` with a relative URL (path-only, does not start with /) when `serverRouting` is `undefined` or `false` will not resolve the URLs correctly. Host-relative URLs (at least) must be used! ### Router's Handling of Absolute URLs * If Router does not care about same-origin, that should be documented. * What should Router do if it is asked to `save()` a URL of different origin (the method is chainable)? Since this is asked to save a new history entry, what is the correct thing to do? Nothing? Throw an error like `pushState()` would if called with a URL of different origin? * Should router always resolve URLs (making them absolute)? ### General URL Stuff * When a URL is considered resolved, it should be absolute. * Scheme-relative URLs (stats with //) can be get current `window.location.protocol`. * Host-relative paths (starts with /) can be considered normalized. * The page's origin should can be considered constant for the YUI instance. * The path-root for normalizing paths should be considered dynamic. * URL relative URLs (path that doesn't start with /), need to be resolved against the current path-root.