safehtml_test.js 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642
  1. // Copyright 2013 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. /**
  15. * @fileoverview Unit tests for goog.html.SafeHtml and its builders.
  16. */
  17. goog.provide('goog.html.safeHtmlTest');
  18. goog.require('goog.html.SafeHtml');
  19. goog.require('goog.html.SafeScript');
  20. goog.require('goog.html.SafeStyle');
  21. goog.require('goog.html.SafeStyleSheet');
  22. goog.require('goog.html.SafeUrl');
  23. goog.require('goog.html.TrustedResourceUrl');
  24. goog.require('goog.html.testing');
  25. goog.require('goog.i18n.bidi.Dir');
  26. goog.require('goog.labs.userAgent.browser');
  27. goog.require('goog.object');
  28. goog.require('goog.string.Const');
  29. goog.require('goog.testing.jsunit');
  30. goog.setTestOnly('goog.html.safeHtmlTest');
  31. function testSafeHtml() {
  32. // TODO(xtof): Consider using SafeHtmlBuilder instead of newSafeHtmlForTest,
  33. // when available.
  34. var safeHtml = goog.html.testing.newSafeHtmlForTest('Hello <em>World</em>');
  35. assertSameHtml('Hello <em>World</em>', safeHtml);
  36. assertEquals('Hello <em>World</em>', goog.html.SafeHtml.unwrap(safeHtml));
  37. assertEquals('SafeHtml{Hello <em>World</em>}', String(safeHtml));
  38. assertNull(safeHtml.getDirection());
  39. safeHtml = goog.html.testing.newSafeHtmlForTest(
  40. 'World <em>Hello</em>', goog.i18n.bidi.Dir.RTL);
  41. assertSameHtml('World <em>Hello</em>', safeHtml);
  42. assertEquals('World <em>Hello</em>', goog.html.SafeHtml.unwrap(safeHtml));
  43. assertEquals('SafeHtml{World <em>Hello</em>}', String(safeHtml));
  44. assertEquals(goog.i18n.bidi.Dir.RTL, safeHtml.getDirection());
  45. // Interface markers are present.
  46. assertTrue(safeHtml.implementsGoogStringTypedString);
  47. assertTrue(safeHtml.implementsGoogI18nBidiDirectionalString);
  48. // Pre-defined constant.
  49. assertSameHtml('', goog.html.SafeHtml.EMPTY);
  50. assertSameHtml('<br>', goog.html.SafeHtml.BR);
  51. }
  52. /** @suppress {checkTypes} */
  53. function testUnwrap() {
  54. var privateFieldName = 'privateDoNotAccessOrElseSafeHtmlWrappedValue_';
  55. var markerFieldName = 'SAFE_HTML_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_';
  56. var propNames = goog.object.getKeys(goog.html.SafeHtml.htmlEscape(''));
  57. assertContains(privateFieldName, propNames);
  58. assertContains(markerFieldName, propNames);
  59. var evil = {};
  60. evil[privateFieldName] = '<script>evil()</script';
  61. evil[markerFieldName] = {};
  62. var exception = assertThrows(function() { goog.html.SafeHtml.unwrap(evil); });
  63. assertContains('expected object of type SafeHtml', exception.message);
  64. }
  65. function testHtmlEscape() {
  66. // goog.html.SafeHtml passes through unchanged.
  67. var safeHtmlIn = goog.html.SafeHtml.htmlEscape('<b>in</b>');
  68. assertTrue(safeHtmlIn === goog.html.SafeHtml.htmlEscape(safeHtmlIn));
  69. // Plain strings are escaped.
  70. var safeHtml = goog.html.SafeHtml.htmlEscape('Hello <em>"\'&World</em>');
  71. assertSameHtml('Hello &lt;em&gt;&quot;&#39;&amp;World&lt;/em&gt;', safeHtml);
  72. assertEquals(
  73. 'SafeHtml{Hello &lt;em&gt;&quot;&#39;&amp;World&lt;/em&gt;}',
  74. String(safeHtml));
  75. // Creating from a SafeUrl escapes and retains the known direction (which is
  76. // fixed to RTL for URLs).
  77. var safeUrl = goog.html.SafeUrl.fromConstant(
  78. goog.string.Const.from('http://example.com/?foo&bar'));
  79. var escapedUrl = goog.html.SafeHtml.htmlEscape(safeUrl);
  80. assertSameHtml('http://example.com/?foo&amp;bar', escapedUrl);
  81. assertEquals(goog.i18n.bidi.Dir.LTR, escapedUrl.getDirection());
  82. // Creating SafeHtml from a goog.string.Const escapes as well (i.e., the
  83. // value is treated like any other string). To create HTML markup from
  84. // program literals, SafeHtmlBuilder should be used.
  85. assertSameHtml(
  86. 'this &amp; that',
  87. goog.html.SafeHtml.htmlEscape(goog.string.Const.from('this & that')));
  88. }
  89. function testSafeHtmlCreate() {
  90. var br = goog.html.SafeHtml.create('br');
  91. assertSameHtml('<br>', br);
  92. assertSameHtml(
  93. '<span title="&quot;"></span>',
  94. goog.html.SafeHtml.create('span', {'title': '"'}));
  95. assertSameHtml(
  96. '<span>&lt;</span>', goog.html.SafeHtml.create('span', {}, '<'));
  97. assertSameHtml(
  98. '<span><br></span>', goog.html.SafeHtml.create('span', {}, br));
  99. assertSameHtml('<span></span>', goog.html.SafeHtml.create('span', {}, []));
  100. assertSameHtml(
  101. '<span></span>',
  102. goog.html.SafeHtml.create('span', {'title': null, 'class': undefined}));
  103. assertSameHtml(
  104. '<span>x<br>y</span>',
  105. goog.html.SafeHtml.create('span', {}, ['x', br, 'y']));
  106. assertSameHtml(
  107. '<table border="0"></table>',
  108. goog.html.SafeHtml.create('table', {'border': 0}));
  109. var onclick = goog.string.Const.from('alert(/"/)');
  110. assertSameHtml(
  111. '<span onclick="alert(/&quot;/)"></span>',
  112. goog.html.SafeHtml.create('span', {'onclick': onclick}));
  113. var href = goog.html.testing.newSafeUrlForTest('?a&b');
  114. assertSameHtml(
  115. '<a href="?a&amp;b"></a>',
  116. goog.html.SafeHtml.create('a', {'href': href}));
  117. var style = goog.html.testing.newSafeStyleForTest('border: /* " */ 0;');
  118. assertSameHtml(
  119. '<hr style="border: /* &quot; */ 0;">',
  120. goog.html.SafeHtml.create('hr', {'style': style}));
  121. assertEquals(
  122. goog.i18n.bidi.Dir.NEUTRAL,
  123. goog.html.SafeHtml.create('span').getDirection());
  124. assertNull(goog.html.SafeHtml.create('span', {'dir': 'x'}).getDirection());
  125. assertEquals(
  126. goog.i18n.bidi.Dir.NEUTRAL,
  127. goog.html.SafeHtml.create('span', {'dir': 'ltr'}, 'a').getDirection());
  128. assertThrows(function() { goog.html.SafeHtml.create('script'); });
  129. assertThrows(function() { goog.html.SafeHtml.create('br', {}, 'x'); });
  130. assertThrows(function() {
  131. goog.html.SafeHtml.create('img', {'onerror': ''});
  132. });
  133. assertThrows(function() {
  134. goog.html.SafeHtml.create('img', {'OnError': ''});
  135. });
  136. assertThrows(function() { goog.html.SafeHtml.create('a href=""'); });
  137. assertThrows(function() {
  138. goog.html.SafeHtml.create('a', {'title="" href': ''});
  139. });
  140. assertThrows(function() { goog.html.SafeHtml.create('applet'); });
  141. assertThrows(function() {
  142. goog.html.SafeHtml.create('applet', {'code': 'kittens.class'});
  143. });
  144. assertThrows(function() { goog.html.SafeHtml.create('base'); });
  145. assertThrows(function() {
  146. goog.html.SafeHtml.create('base', {'href': 'http://example.org'});
  147. });
  148. assertThrows(function() { goog.html.SafeHtml.create('math'); });
  149. assertThrows(function() { goog.html.SafeHtml.create('meta'); });
  150. assertThrows(function() { goog.html.SafeHtml.create('svg'); });
  151. }
  152. function testSafeHtmlCreate_styleAttribute() {
  153. var style = 'color:red;';
  154. var expected = '<hr style="' + style + '">';
  155. assertThrows(function() {
  156. goog.html.SafeHtml.create('hr', {'style': style});
  157. });
  158. assertSameHtml(expected, goog.html.SafeHtml.create('hr', {
  159. 'style': goog.html.SafeStyle.fromConstant(goog.string.Const.from(style))
  160. }));
  161. assertSameHtml(
  162. expected, goog.html.SafeHtml.create('hr', {'style': {'color': 'red'}}));
  163. }
  164. function testSafeHtmlCreate_urlAttributes() {
  165. // TrustedResourceUrl is allowed.
  166. var trustedResourceUrl = goog.html.TrustedResourceUrl.fromConstant(
  167. goog.string.Const.from('https://google.com/trusted'));
  168. assertSameHtml(
  169. '<img src="https://google.com/trusted">',
  170. goog.html.SafeHtml.create('img', {'src': trustedResourceUrl}));
  171. // SafeUrl is allowed.
  172. var safeUrl = goog.html.SafeUrl.sanitize('https://google.com/safe');
  173. assertSameHtml(
  174. '<imG src="https://google.com/safe">',
  175. goog.html.SafeHtml.create('imG', {'src': safeUrl}));
  176. // Const is allowed.
  177. var constUrl = goog.string.Const.from('https://google.com/const');
  178. assertSameHtml(
  179. '<a href="https://google.com/const"></a>',
  180. goog.html.SafeHtml.create('a', {'href': constUrl}));
  181. // string is allowed but escaped.
  182. assertSameHtml(
  183. '<a href="http://google.com/safe&quot;"></a>',
  184. goog.html.SafeHtml.create('a', {'href': 'http://google.com/safe"'}));
  185. // string is allowed but sanitized.
  186. var badUrl = 'javascript:evil();';
  187. var sanitizedUrl =
  188. goog.html.SafeUrl.unwrap(goog.html.SafeUrl.sanitize(badUrl));
  189. assertTrue(typeof sanitizedUrl == 'string');
  190. assertNotEquals(badUrl, sanitizedUrl);
  191. assertSameHtml(
  192. '<a href="' + sanitizedUrl + '"></a>',
  193. goog.html.SafeHtml.create('a', {'href': badUrl}));
  194. // attribute case is ignored for url attributes purposes
  195. assertSameHtml(
  196. '<a hReF="' + sanitizedUrl + '"></a>',
  197. goog.html.SafeHtml.create('a', {'hReF': badUrl}));
  198. }
  199. /** @suppress {checkTypes} */
  200. function testSafeHtmlCreateIframe() {
  201. // Setting src and srcdoc.
  202. var url = goog.html.TrustedResourceUrl.fromConstant(
  203. goog.string.Const.from('https://google.com/trusted<'));
  204. assertSameHtml(
  205. '<iframe src="https://google.com/trusted&lt;"></iframe>',
  206. goog.html.SafeHtml.createIframe(url, null, {'sandbox': null}));
  207. var srcdoc = goog.html.SafeHtml.BR;
  208. assertSameHtml(
  209. '<iframe srcdoc="&lt;br&gt;"></iframe>',
  210. goog.html.SafeHtml.createIframe(null, srcdoc, {'sandbox': null}));
  211. // sandbox default and overriding it.
  212. assertSameHtml(
  213. '<iframe sandbox=""></iframe>', goog.html.SafeHtml.createIframe());
  214. assertSameHtml(
  215. '<iframe Sandbox="allow-same-origin allow-top-navigation"></iframe>',
  216. goog.html.SafeHtml.createIframe(
  217. null, null, {'Sandbox': 'allow-same-origin allow-top-navigation'}));
  218. // Cannot override src and srddoc.
  219. assertThrows(function() {
  220. goog.html.SafeHtml.createIframe(null, null, {'Src': url});
  221. });
  222. assertThrows(function() {
  223. goog.html.SafeHtml.createIframe(null, null, {'Srcdoc': url});
  224. });
  225. // Unsafe src and srcdoc.
  226. assertThrows(function() {
  227. goog.html.SafeHtml.createIframe('http://example.com');
  228. });
  229. assertThrows(function() {
  230. goog.html.SafeHtml.createIframe(null, '<script>alert(1)</script>');
  231. });
  232. // Can set content.
  233. assertSameHtml(
  234. '<iframe>&lt;</iframe>',
  235. goog.html.SafeHtml.createIframe(null, null, {'sandbox': null}, '<'));
  236. }
  237. /** @suppress {checkTypes} */
  238. function testSafeHtmlcreateSandboxIframe() {
  239. function assertSameHtmlIfSupportsSandbox(referenceHtml, testedHtmlFunction) {
  240. if (!goog.html.SafeHtml.canUseSandboxIframe()) {
  241. assertThrows(testedHtmlFunction);
  242. } else {
  243. assertSameHtml(referenceHtml, testedHtmlFunction());
  244. }
  245. }
  246. // Setting src and srcdoc.
  247. var url = goog.html.SafeUrl.fromConstant(
  248. goog.string.Const.from('https://google.com/trusted<'));
  249. assertSameHtmlIfSupportsSandbox(
  250. '<iframe src="https://google.com/trusted&lt;" sandbox=""></iframe>',
  251. function() { return goog.html.SafeHtml.createSandboxIframe(url, null); });
  252. // If set with a string, src is sanitized.
  253. assertSameHtmlIfSupportsSandbox(
  254. '<iframe src="' + goog.html.SafeUrl.INNOCUOUS_STRING +
  255. '" sandbox=""></iframe>',
  256. function() {
  257. return goog.html.SafeHtml.createSandboxIframe(
  258. "javascript:evil();", null);
  259. });
  260. var srcdoc = '<br>';
  261. assertSameHtmlIfSupportsSandbox(
  262. '<iframe srcdoc="&lt;br&gt;" sandbox=""></iframe>', function() {
  263. return goog.html.SafeHtml.createSandboxIframe(null, srcdoc);
  264. });
  265. // Cannot override src, srcdoc.
  266. assertThrows(function() {
  267. goog.html.SafeHtml.createSandboxIframe(null, null, {'Src': url});
  268. });
  269. assertThrows(function() {
  270. goog.html.SafeHtml.createSandboxIframe(null, null, {'Srcdoc': url});
  271. });
  272. // Sandboxed by default, and can't be overriden.
  273. assertSameHtmlIfSupportsSandbox('<iframe sandbox=""></iframe>', function() {
  274. return goog.html.SafeHtml.createSandboxIframe();
  275. });
  276. assertThrows(function() {
  277. goog.html.SafeHtml.createSandboxIframe(null, null, {'sandbox': ''});
  278. });
  279. assertThrows(function() {
  280. goog.html.SafeHtml.createSandboxIframe(
  281. null, null, {'SaNdBoX': 'allow-scripts'});
  282. });
  283. assertThrows(function() {
  284. goog.html.SafeHtml.createSandboxIframe(
  285. null, null, {'sandbox': 'allow-same-origin allow-top-navigation'});
  286. });
  287. // Can set content.
  288. assertSameHtmlIfSupportsSandbox(
  289. '<iframe sandbox="">&lt;</iframe>', function() {
  290. return goog.html.SafeHtml.createSandboxIframe(null, null, null, '<');
  291. });
  292. }
  293. function testSafeHtmlCanUseIframeSandbox() {
  294. // We know that the IE < 10 do not support the sandbox attribute, so use them
  295. // as a reference.
  296. if (goog.labs.userAgent.browser.isIE() &&
  297. goog.labs.userAgent.browser.getVersion() < 10) {
  298. assertEquals(false, goog.html.SafeHtml.canUseSandboxIframe());
  299. } else {
  300. assertEquals(true, goog.html.SafeHtml.canUseSandboxIframe());
  301. }
  302. }
  303. function testSafeHtmlCreateScript() {
  304. var script =
  305. goog.html.SafeScript.fromConstant(goog.string.Const.from('function1();'));
  306. var scriptHtml = goog.html.SafeHtml.createScript(script);
  307. assertSameHtml('<script>function1();</script>', scriptHtml);
  308. // Two pieces of script.
  309. var otherScript =
  310. goog.html.SafeScript.fromConstant(goog.string.Const.from('function2();'));
  311. scriptHtml = goog.html.SafeHtml.createScript([script, otherScript]);
  312. assertSameHtml('<script>function1();function2();</script>', scriptHtml);
  313. // Set attribute.
  314. scriptHtml = goog.html.SafeHtml.createScript(script, {'id': 'test'});
  315. assertContains('id="test"', goog.html.SafeHtml.unwrap(scriptHtml));
  316. // Set attribute to null.
  317. scriptHtml =
  318. goog.html.SafeHtml.createScript(goog.html.SafeScript.EMPTY, {'id': null});
  319. assertSameHtml('<script></script>', scriptHtml);
  320. // Set attribute to invalid value.
  321. var exception = assertThrows(function() {
  322. goog.html.SafeHtml.createScript(
  323. goog.html.SafeScript.EMPTY, {'invalid.': 'cantdothis'});
  324. });
  325. assertContains('Invalid attribute name', exception.message);
  326. // Cannot override type attribute.
  327. exception = assertThrows(function() {
  328. goog.html.SafeHtml.createScript(
  329. goog.html.SafeScript.EMPTY, {'Type': 'cantdothis'});
  330. });
  331. assertContains('Cannot set "type"', exception.message);
  332. // Cannot set src attribute.
  333. exception = assertThrows(function() {
  334. goog.html.SafeHtml.createScript(
  335. goog.html.SafeScript.EMPTY, {'src': 'cantdothis'});
  336. });
  337. assertContains('Cannot set "src"', exception.message);
  338. // Directionality.
  339. assertEquals(goog.i18n.bidi.Dir.NEUTRAL, scriptHtml.getDirection());
  340. }
  341. /** @suppress {checkTypes} */
  342. function testSafeHtmlCreateScriptSrc() {
  343. var url = goog.html.TrustedResourceUrl.fromConstant(
  344. goog.string.Const.from('https://google.com/trusted<'));
  345. assertSameHtml(
  346. '<script src="https://google.com/trusted&lt;"></script>',
  347. goog.html.SafeHtml.createScriptSrc(url));
  348. assertSameHtml(
  349. '<script src="https://google.com/trusted&lt;" defer="defer"></script>',
  350. goog.html.SafeHtml.createScriptSrc(url, {'defer': 'defer'}));
  351. // Unsafe src.
  352. assertThrows(function() {
  353. goog.html.SafeHtml.createScriptSrc('http://example.com');
  354. });
  355. // Unsafe attribute.
  356. assertThrows(function() {
  357. goog.html.SafeHtml.createScriptSrc(url, {'onerror': 'alert(1)'});
  358. });
  359. // Cannot override src.
  360. assertThrows(function() {
  361. goog.html.SafeHtml.createScriptSrc(url, {'Src': url});
  362. });
  363. }
  364. function testSafeHtmlCreateMeta() {
  365. var url = goog.html.SafeUrl.fromConstant(
  366. goog.string.Const.from('https://google.com/trusted<'));
  367. // SafeUrl with no timeout gets properly escaped.
  368. assertSameHtml(
  369. '<meta http-equiv="refresh" ' +
  370. 'content="0; url=https://google.com/trusted&lt;">',
  371. goog.html.SafeHtml.createMetaRefresh(url));
  372. // SafeUrl with 0 timeout also gets properly escaped.
  373. assertSameHtml(
  374. '<meta http-equiv="refresh" ' +
  375. 'content="0; url=https://google.com/trusted&lt;">',
  376. goog.html.SafeHtml.createMetaRefresh(url, 0));
  377. // Positive timeouts are supported.
  378. assertSameHtml(
  379. '<meta http-equiv="refresh" ' +
  380. 'content="1337; url=https://google.com/trusted&lt;">',
  381. goog.html.SafeHtml.createMetaRefresh(url, 1337));
  382. // Negative timeouts are also kept, though they're not correct HTML.
  383. assertSameHtml(
  384. '<meta http-equiv="refresh" ' +
  385. 'content="-1337; url=https://google.com/trusted&lt;">',
  386. goog.html.SafeHtml.createMetaRefresh(url, -1337));
  387. // String-based URLs work out of the box.
  388. assertSameHtml(
  389. '<meta http-equiv="refresh" ' +
  390. 'content="0; url=https://google.com/trusted&lt;">',
  391. goog.html.SafeHtml.createMetaRefresh('https://google.com/trusted<'));
  392. // Sanitization happens.
  393. assertSameHtml(
  394. '<meta http-equiv="refresh" ' +
  395. 'content="0; url=about:invalid#zClosurez">',
  396. goog.html.SafeHtml.createMetaRefresh('javascript:alert(1)'));
  397. }
  398. function testSafeHtmlCreateStyle() {
  399. var styleSheet = goog.html.SafeStyleSheet.fromConstant(
  400. goog.string.Const.from('P.special { color:"red" ; }'));
  401. var styleHtml = goog.html.SafeHtml.createStyle(styleSheet);
  402. assertSameHtml(
  403. '<style type="text/css">P.special { color:"red" ; }</style>', styleHtml);
  404. // Two stylesheets.
  405. var otherStyleSheet = goog.html.SafeStyleSheet.fromConstant(
  406. goog.string.Const.from('P.regular { color:blue ; }'));
  407. styleHtml = goog.html.SafeHtml.createStyle([styleSheet, otherStyleSheet]);
  408. assertSameHtml(
  409. '<style type="text/css">P.special { color:"red" ; }' +
  410. 'P.regular { color:blue ; }</style>',
  411. styleHtml);
  412. // Set attribute.
  413. styleHtml = goog.html.SafeHtml.createStyle(styleSheet, {'id': 'test'});
  414. var styleHtmlString = goog.html.SafeHtml.unwrap(styleHtml);
  415. assertContains('id="test"', styleHtmlString);
  416. assertContains('type="text/css"', styleHtmlString);
  417. // Set attribute to null.
  418. styleHtml = goog.html.SafeHtml.createStyle(
  419. goog.html.SafeStyleSheet.EMPTY, {'id': null});
  420. assertSameHtml('<style type="text/css"></style>', styleHtml);
  421. // Set attribute to invalid value.
  422. var exception = assertThrows(function() {
  423. goog.html.SafeHtml.createStyle(
  424. goog.html.SafeStyleSheet.EMPTY, {'invalid.': 'cantdothis'});
  425. });
  426. assertContains('Invalid attribute name', exception.message);
  427. // Cannot override type attribute.
  428. exception = assertThrows(function() {
  429. goog.html.SafeHtml.createStyle(
  430. goog.html.SafeStyleSheet.EMPTY, {'Type': 'cantdothis'});
  431. });
  432. assertContains('Cannot override "type"', exception.message);
  433. // Directionality.
  434. assertEquals(goog.i18n.bidi.Dir.NEUTRAL, styleHtml.getDirection());
  435. }
  436. function testSafeHtmlCreateWithDir() {
  437. var ltr = goog.i18n.bidi.Dir.LTR;
  438. assertEquals(ltr, goog.html.SafeHtml.createWithDir(ltr, 'br').getDirection());
  439. }
  440. function testSafeHtmlConcat() {
  441. var br = goog.html.testing.newSafeHtmlForTest('<br>');
  442. var html = goog.html.SafeHtml.htmlEscape('Hello');
  443. assertSameHtml('Hello<br>', goog.html.SafeHtml.concat(html, br));
  444. assertSameHtml('', goog.html.SafeHtml.concat());
  445. assertSameHtml('', goog.html.SafeHtml.concat([]));
  446. assertSameHtml('a<br>c', goog.html.SafeHtml.concat('a', br, 'c'));
  447. assertSameHtml('a<br>c', goog.html.SafeHtml.concat(['a', br, 'c']));
  448. assertSameHtml('a<br>c', goog.html.SafeHtml.concat('a', [br, 'c']));
  449. assertSameHtml('a<br>c', goog.html.SafeHtml.concat(['a'], br, ['c']));
  450. var ltr = goog.html.testing.newSafeHtmlForTest('', goog.i18n.bidi.Dir.LTR);
  451. var rtl = goog.html.testing.newSafeHtmlForTest('', goog.i18n.bidi.Dir.RTL);
  452. var neutral =
  453. goog.html.testing.newSafeHtmlForTest('', goog.i18n.bidi.Dir.NEUTRAL);
  454. var unknown = goog.html.testing.newSafeHtmlForTest('');
  455. assertEquals(
  456. goog.i18n.bidi.Dir.NEUTRAL, goog.html.SafeHtml.concat().getDirection());
  457. assertEquals(
  458. goog.i18n.bidi.Dir.LTR,
  459. goog.html.SafeHtml.concat(ltr, ltr).getDirection());
  460. assertEquals(
  461. goog.i18n.bidi.Dir.LTR,
  462. goog.html.SafeHtml.concat(ltr, neutral, ltr).getDirection());
  463. assertNull(goog.html.SafeHtml.concat(ltr, unknown).getDirection());
  464. assertNull(goog.html.SafeHtml.concat(ltr, rtl).getDirection());
  465. assertNull(goog.html.SafeHtml.concat(ltr, [rtl]).getDirection());
  466. }
  467. function testHtmlEscapePreservingNewlines() {
  468. // goog.html.SafeHtml passes through unchanged.
  469. var safeHtmlIn = goog.html.SafeHtml.htmlEscapePreservingNewlines('<b>in</b>');
  470. assertTrue(
  471. safeHtmlIn ===
  472. goog.html.SafeHtml.htmlEscapePreservingNewlines(safeHtmlIn));
  473. assertSameHtml(
  474. 'a<br>c', goog.html.SafeHtml.htmlEscapePreservingNewlines('a\nc'));
  475. assertSameHtml(
  476. '&lt;<br>', goog.html.SafeHtml.htmlEscapePreservingNewlines('<\n'));
  477. assertSameHtml(
  478. '<br>', goog.html.SafeHtml.htmlEscapePreservingNewlines('\r\n'));
  479. assertSameHtml('<br>', goog.html.SafeHtml.htmlEscapePreservingNewlines('\r'));
  480. assertSameHtml('', goog.html.SafeHtml.htmlEscapePreservingNewlines(''));
  481. }
  482. function testHtmlEscapePreservingNewlinesAndSpaces() {
  483. // goog.html.SafeHtml passes through unchanged.
  484. var safeHtmlIn =
  485. goog.html.SafeHtml.htmlEscapePreservingNewlinesAndSpaces('<b>in</b>');
  486. assertTrue(
  487. safeHtmlIn ===
  488. goog.html.SafeHtml.htmlEscapePreservingNewlinesAndSpaces(safeHtmlIn));
  489. assertSameHtml(
  490. 'a<br>c',
  491. goog.html.SafeHtml.htmlEscapePreservingNewlinesAndSpaces('a\nc'));
  492. assertSameHtml(
  493. '&lt;<br>',
  494. goog.html.SafeHtml.htmlEscapePreservingNewlinesAndSpaces('<\n'));
  495. assertSameHtml(
  496. '<br>', goog.html.SafeHtml.htmlEscapePreservingNewlinesAndSpaces('\r\n'));
  497. assertSameHtml(
  498. '<br>', goog.html.SafeHtml.htmlEscapePreservingNewlinesAndSpaces('\r'));
  499. assertSameHtml(
  500. '', goog.html.SafeHtml.htmlEscapePreservingNewlinesAndSpaces(''));
  501. assertSameHtml(
  502. 'a &#160;b',
  503. goog.html.SafeHtml.htmlEscapePreservingNewlinesAndSpaces('a b'));
  504. }
  505. function testSafeHtmlConcatWithDir() {
  506. var ltr = goog.i18n.bidi.Dir.LTR;
  507. var rtl = goog.i18n.bidi.Dir.RTL;
  508. var br = goog.html.testing.newSafeHtmlForTest('<br>');
  509. assertEquals(ltr, goog.html.SafeHtml.concatWithDir(ltr).getDirection());
  510. assertEquals(
  511. ltr,
  512. goog.html.SafeHtml
  513. .concatWithDir(ltr, goog.html.testing.newSafeHtmlForTest('', rtl))
  514. .getDirection());
  515. assertSameHtml('a<br>c', goog.html.SafeHtml.concatWithDir(ltr, 'a', br, 'c'));
  516. }
  517. function assertSameHtml(expected, html) {
  518. assertEquals(expected, goog.html.SafeHtml.unwrap(html));
  519. }