
귀엽고 깜찍한 shadow dom이 몰래 당신을 지켜보고 있다.
요즘 프론트엔드 단의 주 업무는 DRM 적용을 위한 비디오 플레이어 교체 작업이다. 물론 아직 겉절이 직원인 나에게 직접적으로 할당된 task는 아니지만, 선임의 코드를 참고하여 관련된 부분을 공부 중이었다. 마침 플레이어의 UI나 자막 관련된 과제가 남아있어 나도 함께 shaka player의 이슈를 찾아보게 되었다.
당시 당면한 문제는 shaka player에 자막을 함께 띄워줄 때, 해당 자막의 위치가 플레이어 하단과 너무 가깝다는 것이었다. 여태까지의 경험으로 이 정도 제어는 당연히 쉽게 해결될 것이라 여겼다. 하지만 shaka 자체가 track tag를 사용하지 않고 자체적으로 파서를 사용해서 cue를 제어하기 때문에 video tag 안에서 track을 이용한 스타일 변경이 불가능했다.
그렇다 하더라도 보통 라이브러리에 지정되어 있는 스타일 클래스 등으로 접근하여 !important 를 이용하면 쉽게 커스텀해오던 경험이 있을 것이다. 문제는 개발자 모드에서 보이는 DOM 객체에서 cue의 스타일을 담당하는 요소를 도무지 찾을 수 없었다. 검색을 해보아도,
video::cue {
...input some style
}
이런 식의 접근 방법 밖에 나오지 않았다. (물론 적용되지 않았다 전역에서 선언을 해도!)
이 글을 쓰기 전까지 나는 shadow dom의 존재에 대해서 알지 못했다. 때문에 shaka player가 제공해주는 문서와 issue등을 뒤져보며 문제를 해결하고자 했다.
문서를 읽다보면 track을 custom할 수 있는 메소드가 제공되고 있긴 했지만, 이는 cue도 함께 제어해야만 했다. shaka에서 cue를 직접 핸들링하는 일은 shaka의 기본 자막 처리 방식을 무시하고 동기화를 직접 관리해야 함을 의미하기도 했고, 주로 실시간 영상에서 자막이 필요할 경우 사용되는 방식이었기 때문에 우리의 케이스에선(그저 자막 위치를 조금 더 위로 올리고 싶었을 뿐인데요 선생님...) 지나치게 과한 공수를 들이는 일이었다.
shadow dom을 발견한건 정말 우연이었다. chorme에서 주로 테스트를 하다가 갑자기 왠일인지 safari에서 테스트를 해보고 싶었기 때문이다. 아마도 하루 반나절을 삽질하고 나니 공간의 변화가 필요했던가 보다. 풍수지리에도 일이 잘 풀리지 않으면, 가구를 재배치해보라고 하지 않는가. 배포할 땐 기도메타가 필요하듯 프론트에서 일이 풀리지 않으면 브라우저를 바꿔보자.
그리고 정말 풍수지리의 영향으로 safari 개발자 모드로 DOM을 확인하니 처음보는 요소가 보였다.


님은...뭔데요...
웹 컴포넌트의 중요한 측면은 캡슐화입니다. 캡슐화를 통해 마크업 구조, 스타일, 동작을 숨기고 페이지의 다른 코드로부터의 분리하여 각기 다른 부분들이 충돌하지 않게 하고, 코드가 깔끔하게 유지될 수 있게 합니다. Shadow DOM API는 캡슐화의 핵심 파트이며, 숨겨진 분리된 DOM을 요소에 부착하는 방법을 제공합니다. 이 문서는 Shadow DOM 사용의 기본을 다룹니다.
https://developer.mozilla.org/ko/docs/Web/API/Web_components/Using_shadow_DOM
대충 다른 요소와 충돌하지 않게 숨겨놓겠다는 의미인 것 같다. shadow dom을 이미 알았던 사람도 있을 것이고, 나처럼 몰랐던 사람도 있겠지만 어쨌든 우린 항상 shadow dom의 영향을 받은 요소들을 보고 있다. 우리가 사용하고 있는 브라우저에서 제공되는 input이나 button들이다. 같은 요소라도 브라우저마다 조금씩 다른 스타일을 보이기 때문에 우리는 프로젝트를 시작할 때 reset css 등을 사용해 초기화시킨다.
아래는 range type을 가지는 input 요소이다.
이를 개발자 모드에서 확인하면 <input type="range">를 확인할 수 있다. 가만 보면 해당 UI가 어떻게 그려지게 된건지 궁금할 법도 하다. HTML tag는 어떠한 영역을 시멘틱하게 나눠주고, 그 안에 텍스트와 이미지 정도를 보여줄 수 있을 뿐이다. CSS를 조금만 만져봐도, 저런 동그랗고 부드러운 곡선은 기본 요소에 존재하지 않다는 것을 안다.
이제 (chrome 기준) 개발자 모드 settings으로 가서 Preferences 탭의 Elements에서 Show user agent shadow DOM을 체크해보자.

chrome 브라우저가 제공하는 input 요소는 div 지옥이며 shadow DOM을 이용하여 자신만의 스타일을 유지시키고 있는 것을 확인할 수 있다.
https://developer.mozilla.org/ko/docs/Web/API/Web_components/Using_shadow_DOM
애초에 shadow dom은 shadow tree라는 독자적인 길을 걷고 있기 때문에, JavaScript로도 해당 요소를 가져올 수 없다. shadow tree는 웹에 랜더링시 다시 DOM tree 구조에 숨겨진 채로 부착되어 은밀하게 보여지게 된다.
결국 shaka 또한 shadow dom을 이용하여 자막 영역을 제어하고 있었기 때문에, 죽었다 깨어나도 DOM 요소를 찾을 수 없었다. 풍수지리가 없었다면 여전히 chat GPT에게 이상한 질문을 던지고, 이상한 답변을 받으며 돈 값 못한다고 채팅으로 욕하고 있을 것이 뻔하다.
이번 일로 배운 것을 간단히 정리하자면,
https://developer.mozilla.org/ko/docs/Web/API/Web_components/Using_shadow_DOM
https://ko.javascript.info/shadow-dom#ref-683
https://codingapple.com/unit/html-31-shadow-dom/