원저자의 허락을 받아 New alternatives to innerHTML을 한국어로 번역한 글입니다. 🙂
브라우저 지원 참고 사항: setHTMLUnsafe
는 모든 브라우저에서 지원됩니다. setHTML
은 아직 표준화 작업이 진행 중이며, 현재는 파이어폭스에서 플래그를 활성화해야만 사용할 수 있습니다. getHTML
은 버전 125부터 크롬과 엣지에서 지원됩니다.
최근 브라우저에 setHTMLUnsafe
메서드가 새롭게 추가되었습니다. 여기서 안전하지 않다(unsafe)는 것은 innerHTML
과 마찬가지로 입력 값 검증(sanitization)을 수행하지 않음을 의미합니다. 이 명칭은 기존 브라우저 API의 네이밍 방식과 차이가 있습니다. 예를 들어, innerHTML
을 굳이 innerHTMLUnsafe
라고 하지 않았고, eval
을 evalUnsafe()
라고 하지 않았습니다. 그렇다고 해서 setHTMLUnsafe
은 기존 메서드들보다 더 위험한 건 아닙니다. 오래된 메서드들과 다르게 안전한 메서드(setHTML
)와 안전하지 않은 메서드(setHTMLUnsafe
)가 있기 때문에 메서드 이름에 'unsafe'를 명시하는 방식을 사용한 것입니다.
Sanitizer API 명세에는 다음과 같은 구절이 있습니다.
"안전한" 메서드는 스크립트를 실행하는 마크업을 생성하지 않습니다. 즉, XSS(Cross-Site Scripting)로부터 안전해야 합니다.
텍스트 <input>
과 사용자가 입력한 값에 따라 DOM을 변경하는 자바스크립트 코드를 갖는 HTML form이 있다고 가정해봅시다.
form.addEventListener('submit', function(event) {
event.preventDefault();
const markup = `<h2>${input.value}</h2>`
div.innerHTML = markup;
});
만약 사용자가 input에 <img src=doesnotexist>
와 같이 입력했다면, 해당 자바스크립트 코드는 브라우저에서 작동하게 되며 .setHTMLUnsafe()
메서드도 동일한 위험을 지니고 있습니다.
이 간단한 예에서는 코드가 사용자의 브라우저에서만 실행되고 있습니다. 하지만 이러한 종류의 입력 값이 데이터베이스에 저장되어 다른 사용자에게 동적 콘텐츠로 보여지는 데 사용된다면, 다른 사용자의 브라우저에서 임의의 잠재적 악성 자바스크립트가 실행될 수 있습니다.
setHTML
를 사용하면, DOM에 삽입되는 것은 <img src="doesnotexist">
뿐입니다. 이미지는 페이지에 삽입되지만 자바스크립트는 제거됩니다.
Sanitizer API는 아직 개발 중이지만, 이러한 배경지식은 setHTMLUnsafe
의 이름의 맥락을 이해하는 데 도움이 됩니다.
이미 innerHTML
이 있고 (다행히도) setHTML
가 개발되고 있는데 왜 setHTMLUnsafe
가 필요할까요? 바로 선언적 섀도 DOM(declarative shadow DOM) 때문입니다.
HTML <template>
요소는 다음 두 가지 방식으로 사용될 수 있습니다.
<template>
이 shadowrootmode
속성을 포함하는 경우, 섀도 루트에서 해당 요소는 DOM 내의 요소 콘텐츠로 대체됩니다.innerHTML
은 전자의 방식에서는 잘 작동하지만 후자의 방식을 처리하진 못합니다.
const main = document.querySelector('main');
main.innerHTML = `
<h2>I am in the Light DOM</h2>
<div>
<template shadowrootmode="open">
<style>
h2 { color: blue; }
</style>
<h2>Shadow DOM</h2>
</template>
</div>`
innerHTML
은 페이지에 <template>
를 삽입할 순 있지만 여전히 <template>
요소로 남아있습니다. 이 <template>
요소는 내용도 렌더링되지 않고, shadowrootmode
속성 여부에 관계없이 섀도 DOM으로 변환되지도 않습니다.
setHTML
은 <template>
과 그 콘텐츠를 의도적으로 제거합니다.
const main = document.querySelector('main');
main.setHTML(`
<h2>I am in the Light DOM</h2>
<div>
<template shadowrootmode="open">
<style>
h2 { color: blue; }
</style>
<h2>Shadow DOM</h2>
</template>
</div>`);
위 예제에서 main
의 콘텐츠는 h2
와 빈 div
뿐입니다. template
은 "안전하지 않은 노드"로 취급됩니다.
이것이 바로 브라우저가 setHTMLUnsafe
메서드를 추가한 이유입니다. 페이지에 선언적 섀도 DOM을 동적으로 추가하기 위함입니다.
main.setHTMLUnsafe(`
<h2>I am in the Light DOM</h2>
<div>
<template shadowrootmode="open">
<style>
h2 { color: blue; }
</style>
<h2>Shadow DOM</h2>
</template>
</div>
`);
setHTMLUnsafe
메서드를 사용하면 <template>
의 콘텐츠는 섀도 DOM 내부에 렌더링됩니다.
setHTML
과 setHTMLUnsafe
는 innerHTML
을 완전히 대체할 순 없습니다. innerHTML
은 HTML을 설정하고(set) 가져올(get) 수 있기 때문입니다. setHTML
과 setHTMLUnsafe
을 보완하는 함수가 getHTML
입니다(안전하지 않은 버전은 없습니다).
const main = document.querySelector('main');
const html = main.getHTML();
기본적으로 getHTML
은 섀도 DOM 내에 있는 어떠한 마크업도 반환하지 않지만, 구성을 다르게 하여 제어할 수 있습니다.
const main = document.querySelector('main');
const html = main.getHTML({serializableShadowRoots: true} );
serializableShadowRoots
프로퍼티를 true로 설정하면 직렬화가 가능한 모든 섀도 DOM 트리를 직렬화합니다.
shadowrootserializable
속성을 사용하여 template
요소의 직렬화를 가능하게 만들 수 있습니다.
<template shadowrootmode="open" shadowrootserializable>
이와 비슷하게 자바스크립트 attachShadow
메서드에는 serializable
boolean 옵션이 있습니다.
this.attachShadow({ mode: "open", serializable: true });
섀도 루트 배열을 전달하여 특정한 섀도 DOM 트리만 직렬화할 수도 있습니다.
const markup= main.getHTML({
shadowRoots: [document.querySelector('.example').shadowRoot]
});
배열의 모든 섀도 루트는 직렬화가 가능하다고 명시적으로 표시되어 있지 않더라도 직렬화됩니다.
This is an excellent article. This is, in my opinion, one of the best posts ever written. Your work is excellent and inspiring. Thank you very much try play game purble place https://purble-place.io
이거 오해 일으킬까봐 답글을 안 달 수가 없는데, 원문도 마찬가지고,
setHTML 에서 쓰이는 HTML Sanitizer API 표준은 중단되었습니다.
https://developer.mozilla.org/en-US/docs/Web/API/HTML_Sanitizer_API
https://developer.chrome.com/blog/sanitizer-api-deprecation
즉, HTML 소독(Sanitize) 기능은 브라우저 자체로 없고 별도로 사용해야 합니다.
변조 가능성이 있어서 신뢰해서도 안되고요.