asserts.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311
  1. // Copyright 2017 The Closure Library Authors. All Rights Reserved.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS-IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. goog.provide('goog.dom.asserts');
  15. goog.require('goog.asserts');
  16. /**
  17. * @fileoverview Custom assertions to ensure that an element has the appropriate
  18. * type.
  19. *
  20. * Using a goog.dom.safe wrapper on an object on the incorrect type (via an
  21. * incorrect static type cast) can result in security bugs: For instance,
  22. * g.d.s.setAnchorHref ensures that the URL assigned to the .href attribute
  23. * satisfies the SafeUrl contract, i.e., is safe to dereference as a hyperlink.
  24. * However, the value assigned to a HTMLLinkElement's .href property requires
  25. * the stronger TrustedResourceUrl contract, since it can refer to a stylesheet.
  26. * Thus, using g.d.s.setAnchorHref on an (incorrectly statically typed) object
  27. * of type HTMLLinkElement can result in a security vulnerability.
  28. * Assertions of the correct run-time type help prevent such incorrect use.
  29. *
  30. * In some cases, code using the DOM API is tested using mock objects (e.g., a
  31. * plain object such as {'href': url} instead of an actual Location object).
  32. * To allow such mocking, the assertions permit objects of types that are not
  33. * relevant DOM API objects at all (for instance, not Element or Location).
  34. *
  35. * Note that instanceof checks don't work straightforwardly in older versions of
  36. * IE, or across frames (see,
  37. * http://stackoverflow.com/questions/384286/javascript-isdom-how-do-you-check-if-a-javascript-object-is-a-dom-object,
  38. * http://stackoverflow.com/questions/26248599/instanceof-htmlelement-in-iframe-is-not-element-or-object).
  39. *
  40. * Hence, these assertions may pass vacuously in such scenarios. The resulting
  41. * risk of security bugs is limited by the following factors:
  42. * - A bug can only arise in scenarios involving incorrect static typing (the
  43. * wrapper methods are statically typed to demand objects of the appropriate,
  44. * precise type).
  45. * - Typically, code is tested and exercised in multiple browsers.
  46. */
  47. /**
  48. * Asserts that a given object is a Location.
  49. *
  50. * To permit this assertion to pass in the context of tests where DOM APIs might
  51. * be mocked, also accepts any other type except for subtypes of {!Element}.
  52. * This is to ensure that, for instance, HTMLLinkElement is not being used in
  53. * place of a Location, since this could result in security bugs due to stronger
  54. * contracts required for assignments to the href property of the latter.
  55. *
  56. * @param {?Object} o The object whose type to assert.
  57. * @return {!Location}
  58. */
  59. goog.dom.asserts.assertIsLocation = function(o) {
  60. if (goog.asserts.ENABLE_ASSERTS) {
  61. var win = goog.dom.asserts.getWindow_(o);
  62. if (typeof win.Location != 'undefined' &&
  63. typeof win.Element != 'undefined') {
  64. goog.asserts.assert(
  65. o && (o instanceof win.Location || !(o instanceof win.Element)),
  66. 'Argument is not a Location (or a non-Element mock); got: %s',
  67. goog.dom.asserts.debugStringForType_(o));
  68. }
  69. }
  70. return /** @type {!Location} */ (o);
  71. };
  72. /**
  73. * Asserts that a given object is a HTMLAnchorElement.
  74. *
  75. * To permit this assertion to pass in the context of tests where elements might
  76. * be mocked, also accepts objects that are not of type Location nor a subtype
  77. * of Element.
  78. *
  79. * @param {?Object} o The object whose type to assert.
  80. * @return {!HTMLAnchorElement}
  81. */
  82. goog.dom.asserts.assertIsHTMLAnchorElement = function(o) {
  83. if (goog.asserts.ENABLE_ASSERTS) {
  84. var win = goog.dom.asserts.getWindow_(o);
  85. if (typeof win.HTMLAnchorElement != 'undefined' &&
  86. typeof win.Location != 'undefined' &&
  87. typeof win.Element != 'undefined') {
  88. goog.asserts.assert(
  89. o &&
  90. (o instanceof win.HTMLAnchorElement ||
  91. !((o instanceof win.Location) || (o instanceof win.Element))),
  92. 'Argument is not a HTMLAnchorElement (or a non-Element mock); ' +
  93. 'got: %s',
  94. goog.dom.asserts.debugStringForType_(o));
  95. }
  96. }
  97. return /** @type {!HTMLAnchorElement} */ (o);
  98. };
  99. /**
  100. * Asserts that a given object is a HTMLLinkElement.
  101. *
  102. * To permit this assertion to pass in the context of tests where elements might
  103. * be mocked, also accepts objects that are not a subtype of Element.
  104. *
  105. * @param {?Object} o The object whose type to assert.
  106. * @return {!HTMLLinkElement}
  107. */
  108. goog.dom.asserts.assertIsHTMLLinkElement = function(o) {
  109. if (goog.asserts.ENABLE_ASSERTS) {
  110. var win = goog.dom.asserts.getWindow_(o);
  111. if (typeof win.HTMLLinkElement != 'undefined' &&
  112. typeof win.Location != 'undefined' &&
  113. typeof win.Element != 'undefined') {
  114. goog.asserts.assert(
  115. o &&
  116. (o instanceof win.HTMLLinkElement ||
  117. !((o instanceof win.Location) || (o instanceof win.Element))),
  118. 'Argument is not a HTMLLinkElement (or a non-Element mock); got: %s',
  119. goog.dom.asserts.debugStringForType_(o));
  120. }
  121. }
  122. return /** @type {!HTMLLinkElement} */ (o);
  123. };
  124. /**
  125. * Asserts that a given object is a HTMLImageElement.
  126. *
  127. * To permit this assertion to pass in the context of tests where elements might
  128. * be mocked, also accepts objects that are not a subtype of Element.
  129. *
  130. * @param {?Object} o The object whose type to assert.
  131. * @return {!HTMLImageElement}
  132. */
  133. goog.dom.asserts.assertIsHTMLImageElement = function(o) {
  134. if (goog.asserts.ENABLE_ASSERTS) {
  135. var win = goog.dom.asserts.getWindow_(o);
  136. if (typeof win.HTMLImageElement != 'undefined' &&
  137. typeof win.Element != 'undefined') {
  138. goog.asserts.assert(
  139. o &&
  140. (o instanceof win.HTMLImageElement ||
  141. !(o instanceof win.Element)),
  142. 'Argument is not a HTMLImageElement (or a non-Element mock); got: %s',
  143. goog.dom.asserts.debugStringForType_(o));
  144. }
  145. }
  146. return /** @type {!HTMLImageElement} */ (o);
  147. };
  148. /**
  149. * Asserts that a given object is a HTMLEmbedElement.
  150. *
  151. * To permit this assertion to pass in the context of tests where elements might
  152. * be mocked, also accepts objects that are not a subtype of Element.
  153. *
  154. * @param {?Object} o The object whose type to assert.
  155. * @return {!HTMLEmbedElement}
  156. */
  157. goog.dom.asserts.assertIsHTMLEmbedElement = function(o) {
  158. if (goog.asserts.ENABLE_ASSERTS) {
  159. var win = goog.dom.asserts.getWindow_(o);
  160. if (typeof win.HTMLEmbedElement != 'undefined' &&
  161. typeof win.Element != 'undefined') {
  162. goog.asserts.assert(
  163. o &&
  164. (o instanceof win.HTMLEmbedElement ||
  165. !(o instanceof win.Element)),
  166. 'Argument is not a HTMLEmbedElement (or a non-Element mock); got: %s',
  167. goog.dom.asserts.debugStringForType_(o));
  168. }
  169. }
  170. return /** @type {!HTMLEmbedElement} */ (o);
  171. };
  172. /**
  173. * Asserts that a given object is a HTMLFrameElement.
  174. *
  175. * To permit this assertion to pass in the context of tests where elements might
  176. * be mocked, also accepts objects that are not a subtype of Element.
  177. *
  178. * @param {?Object} o The object whose type to assert.
  179. * @return {!HTMLFrameElement}
  180. */
  181. goog.dom.asserts.assertIsHTMLFrameElement = function(o) {
  182. if (goog.asserts.ENABLE_ASSERTS) {
  183. var win = goog.dom.asserts.getWindow_(o);
  184. if (typeof win.HTMLFrameElement != 'undefined' &&
  185. typeof win.Element != 'undefined') {
  186. goog.asserts.assert(
  187. o &&
  188. (o instanceof win.HTMLFrameElement ||
  189. !(o instanceof win.Element)),
  190. 'Argument is not a HTMLFrameElement (or a non-Element mock); got: %s',
  191. goog.dom.asserts.debugStringForType_(o));
  192. }
  193. }
  194. return /** @type {!HTMLFrameElement} */ (o);
  195. };
  196. /**
  197. * Asserts that a given object is a HTMLIFrameElement.
  198. *
  199. * To permit this assertion to pass in the context of tests where elements might
  200. * be mocked, also accepts objects that are not a subtype of Element.
  201. *
  202. * @param {?Object} o The object whose type to assert.
  203. * @return {!HTMLIFrameElement}
  204. */
  205. goog.dom.asserts.assertIsHTMLIFrameElement = function(o) {
  206. if (goog.asserts.ENABLE_ASSERTS) {
  207. var win = goog.dom.asserts.getWindow_(o);
  208. if (typeof win.HTMLIFrameElement != 'undefined' &&
  209. typeof win.Element != 'undefined') {
  210. goog.asserts.assert(
  211. o &&
  212. (o instanceof win.HTMLIFrameElement ||
  213. !(o instanceof win.Element)),
  214. 'Argument is not a HTMLIFrameElement (or a non-Element mock); ' +
  215. 'got: %s',
  216. goog.dom.asserts.debugStringForType_(o));
  217. }
  218. }
  219. return /** @type {!HTMLIFrameElement} */ (o);
  220. };
  221. /**
  222. * Asserts that a given object is a HTMLObjectElement.
  223. *
  224. * To permit this assertion to pass in the context of tests where elements might
  225. * be mocked, also accepts objects that are not a subtype of Element.
  226. *
  227. * @param {?Object} o The object whose type to assert.
  228. * @return {!HTMLObjectElement}
  229. */
  230. goog.dom.asserts.assertIsHTMLObjectElement = function(o) {
  231. if (goog.asserts.ENABLE_ASSERTS) {
  232. var win = goog.dom.asserts.getWindow_(o);
  233. if (typeof win.HTMLObjectElement != 'undefined' &&
  234. typeof win.Element != 'undefined') {
  235. goog.asserts.assert(
  236. o &&
  237. (o instanceof win.HTMLObjectElement ||
  238. !(o instanceof win.Element)),
  239. 'Argument is not a HTMLObjectElement (or a non-Element mock); ' +
  240. 'got: %s',
  241. goog.dom.asserts.debugStringForType_(o));
  242. }
  243. }
  244. return /** @type {!HTMLObjectElement} */ (o);
  245. };
  246. /**
  247. * Asserts that a given object is a HTMLScriptElement.
  248. *
  249. * To permit this assertion to pass in the context of tests where elements might
  250. * be mocked, also accepts objects that are not a subtype of Element.
  251. *
  252. * @param {?Object} o The object whose type to assert.
  253. * @return {!HTMLScriptElement}
  254. */
  255. goog.dom.asserts.assertIsHTMLScriptElement = function(o) {
  256. if (goog.asserts.ENABLE_ASSERTS) {
  257. var win = goog.dom.asserts.getWindow_(o);
  258. if (typeof win.HTMLScriptElement != 'undefined' &&
  259. typeof win.Element != 'undefined') {
  260. goog.asserts.assert(
  261. o &&
  262. (o instanceof win.HTMLScriptElement ||
  263. !(o instanceof win.Element)),
  264. 'Argument is not a HTMLScriptElement (or a non-Element mock); ' +
  265. 'got: %s',
  266. goog.dom.asserts.debugStringForType_(o));
  267. }
  268. }
  269. return /** @type {!HTMLScriptElement} */ (o);
  270. };
  271. /**
  272. * Returns a string representation of a value's type.
  273. *
  274. * @param {*} value An object, or primitive.
  275. * @return {string} The best display name for the value.
  276. * @private
  277. */
  278. goog.dom.asserts.debugStringForType_ = function(value) {
  279. if (goog.isObject(value)) {
  280. return value.constructor.displayName || value.constructor.name ||
  281. Object.prototype.toString.call(value);
  282. } else {
  283. return value === undefined ? 'undefined' :
  284. value === null ? 'null' : typeof value;
  285. }
  286. };
  287. /**
  288. * Gets window of element.
  289. * @param {?Object} o
  290. * @return {!Window}
  291. * @private
  292. */
  293. goog.dom.asserts.getWindow_ = function(o) {
  294. var doc = o && o.ownerDocument;
  295. var win = doc && /** @type {?Window} */ (doc.defaultView || doc.parentWindow);
  296. return win || window;
  297. };