navigate.js 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120
  1. /* This Source Code Form is subject to the terms of the Mozilla Public
  2. * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  3. * You can obtain one at http://mozilla.org/MPL/2.0/. */
  4. "use strict";
  5. const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
  6. Cu.importGlobalProperties(["URL"]);
  7. this.EXPORTED_SYMBOLS = ["navigate"];
  8. this.navigate = {};
  9. /**
  10. * Determines if we expect to get a DOM load event (DOMContentLoaded)
  11. * on navigating to the |future| URL.
  12. *
  13. * @param {string} current
  14. * URL the browser is currently visiting.
  15. * @param {string=} future
  16. * Destination URL, if known.
  17. *
  18. * @return {boolean}
  19. * Full page load would be expected if future is followed.
  20. *
  21. * @throws TypeError
  22. * If |current| is not defined, or any of |current| or |future|
  23. * are invalid URLs.
  24. */
  25. navigate.isLoadEventExpected = function (current, future = undefined) {
  26. if (typeof current == "undefined") {
  27. throw TypeError("Expected at least one URL");
  28. }
  29. // assume we will go somewhere exciting
  30. if (typeof future == "undefined") {
  31. return true;
  32. }
  33. let cur = new navigate.IdempotentURL(current);
  34. let fut = new navigate.IdempotentURL(future);
  35. // assume javascript:<whatever> will modify current document
  36. // but this is not an entirely safe assumption to make,
  37. // considering it could be used to set window.location
  38. if (fut.protocol == "javascript:") {
  39. return false;
  40. }
  41. // navigating to same url, but with any hash
  42. if (cur.origin == fut.origin &&
  43. cur.pathname == fut.pathname &&
  44. fut.hash != "") {
  45. return false;
  46. }
  47. return true;
  48. };
  49. /**
  50. * Sane URL implementation that normalises URL fragments (hashes) and
  51. * path names for "data:" URLs, and makes them idempotent.
  52. *
  53. * At the time of writing this, the web is approximately 10 000 days (or
  54. * ~27.39 years) old. One should think that by this point we would have
  55. * solved URLs. The following code is prudent example that we have not.
  56. *
  57. * When a URL with a fragment identifier but no explicit name for the
  58. * fragment is given, i.e. "#", the {@code hash} property a {@code URL}
  59. * object computes is an empty string. This is incidentally the same as
  60. * the default value of URLs without fragments, causing a lot of confusion.
  61. *
  62. * This means that the URL "http://a/#b" produces a hash of "#b", but that
  63. * "http://a/#" produces "". This implementation rectifies this behaviour
  64. * by returning the actual full fragment, which is "#".
  65. *
  66. * "data:" URLs that contain fragments, which if they have the same origin
  67. * and path name are not meant to cause a page reload on navigation,
  68. * confusingly adds the fragment to the {@code pathname} property.
  69. * This implementation remedies this behaviour by trimming it off.
  70. *
  71. * The practical result of this is that while {@code URL} objects are
  72. * not idempotent, the returned URL elements from this implementation
  73. * guarantees that |url.hash == url.hash|.
  74. *
  75. * @param {string|URL} o
  76. * Object to make an URL of.
  77. *
  78. * @return {navigate.IdempotentURL}
  79. * Considered by some to be a somewhat saner URL.
  80. *
  81. * @throws TypeError
  82. * If |o| is not a valid type or if is a string that cannot be parsed
  83. * as a URL.
  84. */
  85. navigate.IdempotentURL = function (o) {
  86. let url = new URL(o);
  87. let hash = url.hash;
  88. if (hash == "" && url.href[url.href.length - 1] == "#") {
  89. hash = "#";
  90. }
  91. return {
  92. hash: hash,
  93. host: url.host,
  94. hostname: url.hostname,
  95. href: url.href,
  96. origin: url.origin,
  97. password: url.password,
  98. pathname: url.pathname,
  99. port: url.port,
  100. protocol: url.protocol,
  101. search: url.search,
  102. searchParams: url.searchParams,
  103. username: url.username,
  104. };
  105. };