- Remove appropriate route change subcriptions when a root is removed via `m.mount(root, null)`. - Don't pollute `onpopstate` and friends - use standard event listeners instead. - Simplify and streamline subscriptions, in preparation of adding a `remove` parameter to `m.mount`. - Change the redraw internals to redraw immediately, with ability to cancel via returning a sentinel. - Change `"bleeding-edge"` for `m.version` in `next` to instead just be the latest `m.version`. (If you're using `next`, you should know what you're in for.) - Update tests to be aware of these changes. (Some were failing for subtle reasons.) - Drive-by: remove some uses of `string.charAt(n)` and use `string[n]` instead.
199 lines
4.9 KiB
JavaScript
199 lines
4.9 KiB
JavaScript
"use strict"
|
|
|
|
var parseURL = require("../test-utils/parseURL")
|
|
var callAsync = require("../test-utils/callAsync")
|
|
|
|
function debouncedAsync(f) {
|
|
var ref
|
|
return function() {
|
|
if (ref != null) return
|
|
ref = callAsync(function(){
|
|
ref = null
|
|
f()
|
|
})
|
|
}
|
|
}
|
|
|
|
module.exports = function(options) {
|
|
if (options == null) options = {}
|
|
|
|
var $window = options.window || {}
|
|
var protocol = options.protocol || "http:"
|
|
var hostname = options.hostname || "localhost"
|
|
var port = ""
|
|
var pathname = "/"
|
|
var search = ""
|
|
var hash = ""
|
|
|
|
var past = [{url: getURL(), isNew: true, state: null, title: null}], future = []
|
|
|
|
function getURL() {
|
|
if (protocol === "file:") return protocol + "//" + pathname + search + hash
|
|
return protocol + "//" + hostname + prefix(":", port) + pathname + search + hash
|
|
}
|
|
function setURL(value) {
|
|
var data = parseURL(value, {protocol: protocol, hostname: hostname, port: port, pathname: pathname})
|
|
var isNew = false
|
|
if (data.protocol != null && data.protocol !== protocol) protocol = data.protocol, isNew = true
|
|
if (data.hostname != null && data.hostname !== hostname) hostname = data.hostname, isNew = true
|
|
if (data.port != null && data.port !== port) port = data.port, isNew = true
|
|
if (data.pathname != null && data.pathname !== pathname) pathname = data.pathname, isNew = true
|
|
if (data.search != null && data.search !== search) search = data.search, isNew = true
|
|
if (data.hash != null && data.hash !== hash) {
|
|
hash = data.hash
|
|
if (!isNew) {
|
|
hashchange()
|
|
}
|
|
}
|
|
return isNew
|
|
}
|
|
|
|
function prefix(prefix, value) {
|
|
if (value === "") return ""
|
|
return (value.charAt(0) !== prefix ? prefix : "") + value
|
|
}
|
|
function _hashchange() {
|
|
if (typeof $window.onhashchange === "function") $window.onhashchange({type: "hashchange"})
|
|
}
|
|
var hashchange = debouncedAsync(_hashchange)
|
|
function popstate() {
|
|
if (typeof $window.onpopstate === "function") $window.onpopstate({type: "popstate", state: $window.history.state})
|
|
}
|
|
function unload() {
|
|
if (typeof $window.onunload === "function") $window.onunload({type: "unload"})
|
|
}
|
|
|
|
$window.location = {
|
|
get protocol() {
|
|
return protocol
|
|
},
|
|
get hostname() {
|
|
return hostname
|
|
},
|
|
get port() {
|
|
return port
|
|
},
|
|
get pathname() {
|
|
return pathname
|
|
},
|
|
get search() {
|
|
return search
|
|
},
|
|
get hash() {
|
|
return hash
|
|
},
|
|
get origin() {
|
|
if (protocol === "file:") return "null"
|
|
return protocol + "//" + hostname + prefix(":", port)
|
|
},
|
|
get host() {
|
|
if (protocol === "file:") return ""
|
|
return hostname + prefix(":", port)
|
|
},
|
|
get href() {
|
|
return getURL()
|
|
},
|
|
|
|
set protocol(value) {
|
|
throw new Error("Protocol is read-only")
|
|
},
|
|
set hostname(value) {
|
|
unload()
|
|
past.push({url: getURL(), isNew: true})
|
|
future = []
|
|
hostname = value
|
|
},
|
|
set port(value) {
|
|
if (protocol === "file:") throw new Error("Port is read-only under `file://` protocol")
|
|
unload()
|
|
past.push({url: getURL(), isNew: true})
|
|
future = []
|
|
port = value
|
|
},
|
|
set pathname(value) {
|
|
if (protocol === "file:") throw new Error("Pathname is read-only under `file://` protocol")
|
|
unload()
|
|
past.push({url: getURL(), isNew: true})
|
|
future = []
|
|
pathname = prefix("/", value)
|
|
},
|
|
set search(value) {
|
|
unload()
|
|
past.push({url: getURL(), isNew: true})
|
|
future = []
|
|
search = prefix("?", value)
|
|
},
|
|
set hash(value) {
|
|
var oldHash = hash
|
|
past.push({url: getURL(), isNew: false})
|
|
future = []
|
|
hash = prefix("#", value)
|
|
if (oldHash != hash) hashchange()
|
|
},
|
|
|
|
set origin(value) {
|
|
//origin is writable but ignored
|
|
},
|
|
set host(value) {
|
|
//host is writable but ignored in Chrome
|
|
},
|
|
set href(value) {
|
|
var url = getURL()
|
|
var isNew = setURL(value)
|
|
if (isNew) {
|
|
setURL(url)
|
|
unload()
|
|
setURL(value)
|
|
}
|
|
past.push({url: url, isNew: isNew})
|
|
future = []
|
|
},
|
|
}
|
|
$window.history = {
|
|
pushState: function(state, title, url) {
|
|
past.push({url: getURL(), isNew: false, state: state, title: title})
|
|
future = []
|
|
setURL(url)
|
|
},
|
|
replaceState: function(state, title, url) {
|
|
var entry = past[past.length - 1]
|
|
entry.state = state
|
|
entry.title = title
|
|
setURL(url)
|
|
},
|
|
back: function() {
|
|
if (past.length > 1) {
|
|
var entry = past.pop()
|
|
if (entry.isNew) unload()
|
|
future.push({url: getURL(), isNew: false, state: entry.state, title: entry.title})
|
|
setURL(entry.url)
|
|
if (!entry.isNew) popstate()
|
|
}
|
|
},
|
|
forward: function() {
|
|
var entry = future.pop()
|
|
if (entry != null) {
|
|
if (entry.isNew) unload()
|
|
past.push({url: getURL(), isNew: false, state: entry.state, title: entry.title})
|
|
setURL(entry.url)
|
|
if (!entry.isNew) popstate()
|
|
}
|
|
},
|
|
get state() {
|
|
return past.length === 0 ? null : past[past.length - 1].state
|
|
},
|
|
}
|
|
$window.onpopstate = null,
|
|
$window.onhashchange = null,
|
|
$window.onunload = null
|
|
|
|
$window.addEventListener = function (name, handler) {
|
|
$window["on" + name] = handler
|
|
}
|
|
|
|
$window.removeEventListener = function (name, handler) {
|
|
$window["on" + name] = handler
|
|
}
|
|
|
|
return $window
|
|
}
|