element.mjs 3.4 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495
  1. /**
  2. * @license
  3. * SPDX-License-Identifier: Apache-2.0
  4. */
  5. /**
  6. * @fileoverview This contains safe wrappers for properties that aren't specific
  7. * to one kind of HTMLElement (like innerHTML), plus other setters and functions
  8. * that are not tied to elements (like location.href or Worker constructor).
  9. */
  10. import { unwrapAttributePrefix } from '../../internals/attribute_impl';
  11. import { unwrapHtml } from '../../internals/html_impl';
  12. import { unwrapStyle } from '../../internals/style_impl';
  13. /**
  14. * Safely set {@link Element.innerHTML} on a given ShadowRoot or Element which
  15. * may not be a `<script>` element or a `<style>` element.
  16. */
  17. export function setInnerHtml(elOrRoot, v) {
  18. if (isElement(elOrRoot)) {
  19. throwIfScriptOrStyle(elOrRoot);
  20. }
  21. elOrRoot.innerHTML = unwrapHtml(v);
  22. }
  23. /**
  24. * Safely set {@link Element.outerHTML} for the given Element.
  25. */
  26. export function setOuterHtml(e, v) {
  27. const parent = e.parentElement;
  28. if (parent !== null) {
  29. throwIfScriptOrStyle(parent);
  30. }
  31. e.outerHTML = unwrapHtml(v);
  32. }
  33. /**
  34. * Set `ElementCSSInlineStyle.cssText` for the given `ElementCSSInlineStyle`.
  35. */
  36. export function setCssText(e, v) {
  37. e.style.cssText = unwrapStyle(v);
  38. }
  39. /**
  40. * Safely call {@link Element.insertAdjacentHTML} for the given Element.
  41. */
  42. export function insertAdjacentHtml(element, position, v) {
  43. const tagContext = (position === 'beforebegin' || position === 'afterend') ?
  44. element.parentElement :
  45. element;
  46. if (tagContext !== null) {
  47. throwIfScriptOrStyle(tagContext);
  48. }
  49. element.insertAdjacentHTML(position, unwrapHtml(v));
  50. }
  51. /**
  52. * Given a set of known-to-be-safe prefixes (e.g., "data-", "aria-", "js"),
  53. * return a setter function that allows you to set attributes on an element,
  54. * as long as the names of the attributes to be set has one of the prefixes.
  55. *
  56. * The returned setter ensures that setting any dangerous attribute, e.g.,
  57. * "src", "href" will cause an exception. This is intended to be used as the
  58. * safe alterantive of `Element#setAttribute`, when applications need to set
  59. * attributes that do not have security implications and do not have a
  60. * corresponding DOM property.
  61. */
  62. export function buildPrefixedAttributeSetter(prefix, ...otherPrefixes) {
  63. const prefixes = [prefix, ...otherPrefixes];
  64. return (e, attr, value) => {
  65. setPrefixedAttribute(prefixes, e, attr, value);
  66. };
  67. }
  68. /**
  69. * The safe alternative to Element#setAttribute. The function takes a list of
  70. * `SafeAttributePrefix`, making developer intention explicit. The attribute
  71. * to be set must has one of the safe prefixes, otherwise the function throws
  72. * an Error.
  73. */
  74. export function setPrefixedAttribute(attrPrefixes, e, attr, value) {
  75. if (attrPrefixes.length === 0) {
  76. throw new Error('No prefixes are provided');
  77. }
  78. const prefixes = attrPrefixes.map(s => unwrapAttributePrefix(s));
  79. const attrLower = attr.toLowerCase();
  80. if (prefixes.every(p => attrLower.indexOf(p) !== 0)) {
  81. throw new Error(`Attribute "${attr}" does not match any of the allowed prefixes.`);
  82. }
  83. e.setAttribute(attr, value);
  84. }
  85. function throwIfScriptOrStyle(element) {
  86. if (element.tagName.toLowerCase() === 'script') {
  87. throw new Error('Use setTextContent with a SafeScript.');
  88. }
  89. else if (element.tagName.toLowerCase() === 'style') {
  90. throw new Error('Use setTextContent with a SafeStyleSheet.');
  91. }
  92. }
  93. function isElement(elOrRoot) {
  94. return elOrRoot.tagName !== undefined;
  95. }