웹 플랫폼 설계 원칙

W3C 그룹 노트,

이 문서에 대한 추가 정보
이 버전:
https://www.w3.org/TR/2025/NOTE-design-principles-20251007/
최신 공개 버전:
https://www.w3.org/TR/design-principles/
편집자 초안:
https://w3ctag.github.io/design-principles/
이전 버전:
히스토리:
https://www.w3.org/standards/history/design-principles/
피드백:
GitHub
편집자:
Martin Thomson (Mozilla)
Jeffrey Yasskin (Google)
이전 편집자:
Lea Verou (초청 전문가)
Sangwhan Moon (Google)
Domenic Denicola (Google)
(Microsoft)
작성:
TAG의 현재 및 이전 멤버
참여:
GitHub w3ctag/design-principles (이슈 등록; 오픈 이슈)

요약

이 문서는 웹 플랫폼 기술을 설계할 때 사용되는 설계 원칙들의 모음을 담고 있습니다. 이 원칙들은 검토 중에 TAG(Technical Architecture Group)의 논의에서 수집되었으며, 윤리적 웹 원칙 [ETHICAL-WEB]을 기반으로 합니다. 명세 설계자들이 이 문서를 읽고 설계 결정을 내릴 때 참고 자료로 활용하길 권장합니다.

이 문서의 상태

이 섹션은 이 문서가 발행된 시점의 상태를 설명합니다. 현재 W3C 발행물 목록과 이 기술 보고서의 최신 개정판은 W3C 표준 및 초안 인덱스에서 확인할 수 있습니다.

이 문서는 기술 아키텍처 그룹에 의해 노트 트랙을 이용하여 그룹 노트로 발행되었습니다. 그룹 노트는 W3C 또는 그 회원들에 의해 승인된 것이 아닙니다.

이 문서에 대한 피드백과 의견을 환영합니다. 이슈를 등록하거나 이 문서의 GitHub 저장소에서 의견을 남겨주세요.

W3C 특허 정책은 이 문서에 대해 어떠한 라이선스 요구사항이나 약속도 적용되지 않습니다.

이 문서는 2025년 8월 18일 W3C 프로세스 문서에 따라 관리됩니다.

1. 웹 API 설계의 원칙

설계 원칙은 윤리적 웹 원칙 [ETHICAL-WEB]에서 제시된 윤리적 기준에 직접적으로 근거합니다. 이러한 원칙은 웹 플랫폼 개발 시 요구되는 높은 수준의 윤리적 책임에 대한 구체적이고 실질적인 조언을 제공합니다.

1.1. 사용자 우선(이해관계 우선순위)

트레이드오프가 필요한 경우, 항상 사용자 요구를 최우선으로 두어야 합니다.

마찬가지로 API 설계를 시작할 때, 해당 API가 해결하고자 하는 사용자 요구를 반드시 이해하고 문서화해야 합니다.

인터넷은 최종 사용자 위한 것: 웹 플랫폼의 모든 변화는 수많은 사람들에게 영향을 미칠 수 있으며, 개인의 삶에 깊은 영향을 줄 수 있습니다. [RFC8890]

사용자 요구는 웹 페이지 작성자 요구보다 우선하고, 작성자 요구는 사용자 에이전트 구현자 요구보다 우선, 구현자 요구는 명세 작성자 요구보다 우선, 명세 작성자 요구는 이론적 순수성보다 우선합니다.

모든 원칙과 마찬가지로, 이것이 절대적인 것은 아닙니다. 작성의 용이성은 콘텐츠가 사용자에게 도달하는 방식에 영향을 줍니다. 사용자 에이전트는 한정된 엔지니어링 자원을 우선순위화해야 하며, 이는 기능이 작성자에게 도달하는 방식에 영향을 줍니다. 명세 작성자도 자원이 한정되어 있고, 이론적 고려사항은 이 모든 그룹의 근본적 요구를 반영합니다.

참고:

1.2. 웹 페이지 방문은 안전해야 함

새로운 기능을 추가할 때, 사용자가 웹 페이지를 방문하는 것이 일반적으로 안전하다는 기대가 유지될 수 있도록 설계해야 합니다.

웹은 하이퍼링크로 이루어진 구조에서 이름을 따왔습니다. 웹이 활기차게 유지되려면, 사용자는 단순히 링크를 방문하는 것만으로도 자신의 컴퓨터 보안이나 프라이버시에 영향을 미치지 않을 것이라 기대할 수 있어야 합니다.

예를 들어, 어떤 API가 보조 기술 사용을 감지할 수 있다면, 해당 기술 사용자들은 웹 페이지 방문 시 자신의 정보가 노출될 수 있어 불안함을 느낄 수 있습니다.

사용자가 현실적으로 안전하다고 기대할 수 있다면, 웹 기반 기술과 다른 기술 사이에서 정보에 근거한 결정을 내릴 수 있습니다. 예를 들어, 사용자가 네이티브 앱을 설치하는 것보다 웹 기반 음식 주문 페이지를 선택할 수 있습니다. 네이티브 앱 설치는 웹 페이지 방문보다 더 위험할 수 있기 때문입니다.

웹에서의 안전이 사용자의 기대와 일치하도록 노력하기 위해, 새로운 기능을 추가할 때 상호보완적 접근을 취할 수 있습니다:

새로운 기능이 안전성 위험을 도입하더라도, 웹 페이지에서 더 안전하게 작업할 수 있게 해준다면 전체적으로 사용자 안전을 높일 수 있습니다. 그러나 이 이점은 웹 페이지에서 합리적인 안전 기대를 유지하는 공동 목표와 균형을 이루어야 합니다.

참고:

1.3. 신뢰할 수 있는 사용자 인터페이스는 신뢰받아야 함

새로운 기능이 신뢰할 수 있는 사용자 인터페이스에 영향을 미치는지 고려하세요.

사용자들은 주소창, 보안 표시, 권한 요청창 등 신뢰할 수 있는 사용자 인터페이스에 의존하여 자신이 누구와 어떻게 상호작용하는지 이해합니다. 이러한 신뢰할 수 있는 인터페이스는 사용자가 신뢰하고 검증할 수 있도록 설계되어야 하며, 제공된 정보가 진짜이고, 웹사이트에 의해 조작되거나 변조되지 않았음을 보장해야 합니다.

새로운 기능이 신뢰할 수 없는 인터페이스가 신뢰받는 인터페이스처럼 보이게 한다면, 사용자가 어떤 정보가 신뢰 가능한지 이해하기 더 어려워집니다.

예를 들어, 자바스크립트 alert() 는 페이지가 브라우저의 일부처럼 보이는 모달 다이얼로그를 표시할 수 있게 해줍니다. 이는 사용자를 사기 사이트로 유도하는 데 자주 악용됩니다. 만약 이 기능이 지금 제안되었다면, 도입되지 않았을 것입니다.

사용자 요구를 충족시키기 위해 웹 페이지는 잠재적으로 해를 끼칠 수 있는 기능을 사용하고자 할 수 있습니다. 이러한 기능은 사람들이 해당 기능을 사용하도록 의미 있는 동의를 할 수 있고, 거부도 효과적으로 할 수 있도록 설계되어야 합니다.

의미 있는 동의를 위해 사용자는 다음을 만족해야 합니다:

기능이 사용자 동의를 요구할 만큼 강력하다면, 일반 사용자가 무엇에 동의하는지 설명할 수 없다면, 기능 설계를 재고해야 할 신호입니다.

권한 요청이 표시되고, 사용자가 권한을 허용하지 않았다면, 웹 페이지는 사용자가 거부한 것으로 믿는 어떤 것도 실행할 수 없어야 합니다.

동의를 요청함으로써, 웹 페이지가 어떤 기능을 갖고 있는지 사용자에게 알릴 수 있으며, 웹이 안전하다는 신뢰를 강화할 수 있습니다. 그러나 새로운 기능의 사용자 이점은 웹 페이지에서 기능마다 권한을 결정하는 추가 부담을 정당화해야 합니다.

명세에서는 사용 권한 요청사용자에게 선택 요청 알고리즘([permissions])을 활용할 수 있습니다.

거부는 사이트가 거부 상황을 다른 일반 상황과 구분하지 못할 때 가장 효과적입니다. 이를 통해 사이트가 사용자에게 동의를 강요하기 더 어렵게 만들 수 있습니다.

예를 들어, Geolocation API 는 사용자의 위치에 접근할 수 있습니다. 이는 지도 앱 등 일부 상황에서 도움이 되지만, 다른 상황에서는 사용자에게 위험할 수 있습니다. 특히 사용자 동의 없이 사용될 경우 더 그렇습니다. 사용자가 자신의 위치가 웹 페이지에서 사용될지 결정할 수 있도록, 권한 요청 창이 표시되어야 하며, 사용자가 권한을 거부하면 웹 페이지에 위치 정보가 제공되지 않습니다.

참고:

1.5. 문맥에 맞는 신원 사용

사람들이 웹의 다양한 문맥에서 자신에 대해 제시하는 정보에 대해 통제권을 가지게 하고, 투명성을 제공해야 합니다.

"신원"은 다양한 방식으로 이해될 수 있는 복잡한 개념입니다. 이는 개인이 자신을 어떻게 표현하거나 인식하는지, 타인·집단·기관과의 관계, 행동 방식, 타인에게 대우받는 방식 등을 포함합니다. 웹 아키텍처에서 "신원"은 자주 식별자와 그에 연결된 정보를 의미하는 약어로 쓰입니다.

식별자와 그에 연결된 데이터(행동 데이터, 폼 입력 등)를 활용하거나 의존하는 기능은 단일 API나 시스템을 넘어서는 프라이버시 위험을 수반합니다.

이러한 기능에 대해서는, 해당 기능이 어떤 문맥에서 사용될지, 그리고 웹의 다른 기능들과 어떻게 함께 동작할지 이해해야 합니다. 사용자가 적절한 동의를 할 수 있도록 설계하고, 최소한의 데이터만 수집해야 합니다. 영구적인 식별자가 반드시 필요한 경우를 제외하고는 단기·임시 식별자를 사용하세요.

1.6. 모든 기기와 플랫폼 지원(미디어 독립성)

웹 기능이 다양한 입력·출력 기기, 화면 크기, 상호작용 방식, 플랫폼, 미디어에서 동작하도록 가능한 한 보장하세요.

웹의 주요 가치 중 하나는 매우 유연하다는 점입니다: 웹 페이지는 사실상 모든 소비자 컴퓨팅 기기에서 다양한 화면 크기로 볼 수 있고, 인쇄물로도 생성할 수 있으며, 다양한 방식으로 상호작용할 수 있습니다. 새로운 기능은 웹 플랫폼의 기존 유연성을 따라야 합니다.

이는 모든 문맥에서 동작하지 않는 기능이 배제되어야 함을 의미하지는 않습니다. 예를 들어, 하이퍼링크는 종이 인쇄 시 방문할 수 없고, click 이벤트는 터치 입력 기기에서는 정확히 일치하지 않습니다(터치 기기에서는 위치 지정과 클릭이 동시에 이루어져 "탭"이 됨).

이러한 기능들도 다양한 문맥에서 동작하며, 원래 의도대로 작동하지 않는 기기에서도 적응될 수 있습니다. 예를 들어 모바일 기기에서의 탭은 click 이벤트로 대체됩니다.

기능은 가장 쉽게 사용할 때도 유연성이 유지되도록 설계되어야 합니다.

CSS의 'display: block', 'display: flex', 'display: grid' 레이아웃 모델은 모두 기본적으로 콘텐츠를 가용 공간 내에 겹치지 않게 배치하여, 화면 크기에 관계없이 동작하고, 사용자 개별 폰트·폰트 크기 선택 시에도 텍스트가 넘치지 않게 합니다.

때로는 어떤 플랫폼이나 구현에서는 기능이 아직 제공되지 않을 수 있습니다. 이런 경우, 기능은 코드가 우아하게 실패하거나 폴리필될 수 있도록 설계되어야 합니다. § 2.6 새로운 기능은 감지 가능해야 함 참고.

1.7. 새로운 기능은 신중하게 추가하기

웹에 새로운 기능을 추가할 때는 기존 기능 및 콘텐츠와의 연계를 고려하세요.

웹에는 확장성을 위한 다양한 지점이 있습니다; 예를 들어 HTML § 1.7.2 확장성 참고.

항목을 추가하기 전, 기존 유사 기능과의 통합을 고려하세요. 만약 이를 통해 단순히 항목을 추가하는 것만으로 구현할 수 없는 더 나은 설계 방법이 도출된다면, 여전히 가능할 수 있습니다; § 1.8 기존 사용을 충분히 이해한 후에만 기능 제거 또는 변경 참고.

변경 또는 제거가 불가능하다고 단정하지 말고 반드시 확인하세요.

1.8. 기존 사용을 충분히 이해한 후에만 기능 제거 또는 변경

기능을 제거하거나 변경할 때는 기존 콘텐츠와의 호환성을 우선시하세요.

특정 동작에 상당한 양의 콘텐츠가 의존하게 되었다면, 해당 동작을 제거하거나 변경하는 것은 권장되지 않습니다. 기능이나 능력의 제거·변경은 가능하지만, 기존 콘텐츠에 미치는 영향의 성격과 범위를 충분히 이해해야 합니다. 이를 위해 기존 콘텐츠에서 기능이 어떻게 사용되는지 조사해야 할 수도 있습니다.

기존 사용을 이해할 의무는 콘텐츠가 의존하는 모든 기능에 적용됩니다. 여기에는 벤더의 독자적 기능이나 구현상의 버그로 간주될 수 있는 동작도 포함됩니다. 웹의 기능은 명세에서만 정의되는 것이 아니라, 콘텐츠에서 기능이 어떻게 사용되는지에 의해서도 정의됩니다.

1.9. 웹을 더 나은 곳으로 만들기

웹 플랫폼에 새로운 기능을 추가할 때는, 전체 플랫폼을 개선하는 방식으로 추가해야 합니다.

플랫폼의 한 부분에 결함이 있다고 해서, 해당 결함을 확장하거나 추가하는 것이 정당화되어서는 안 되며, 이는 전체 플랫폼 품질을 더 저하시킬 수 있습니다.

기존의 문제 많은 설계 패턴과의 일관성은 새로운 Web API나 플랫폼 기능에서 그러한 패턴을 반복하는 근거가 될 수 없습니다. 가능하다면, 기존 결함을 개선하여 전체 플랫폼 품질을 높이는 새로운 웹 기능을 구축하세요.

결함이 알려진 플랫폼 기능을 확장할 때는, 결함 해결 방안을 반드시 고려하세요. 기존 문제를 완전히 해결할 필요는 없지만, 알려진 문제를 확장할 때는 결함을 어느 정도 해결하려는 노력이 필요합니다. 이렇게 함으로써 새로운 사용이나 사용 증가가 결함으로 인한 피해를 늘리는 결과를 초래하지 않도록 할 수 있습니다.

웹 플랫폼의 각 부분은 독립적으로 진화합니다. 지금 특정 웹 기술에 존재하는 문제도 이후 반복에서 해결될 수 있습니다. 이러한 문제를 중복하면 해결이 더 어려워집니다. 이 원칙을 따르면 전체 플랫폼 품질이 시간이 지남에 따라 점진적으로 향상될 수 있습니다.

1.10. 사용자 데이터 최소화

기능은 사용자의 목표를 달성하는 데 필요한 최소한의 데이터만으로 동작하도록 설계하세요.

데이터 최소화 는 데이터가 부적절하게 공개되거나 오용될 위험을 줄여줍니다.

웹 API는 사이트가 소량의 데이터, 더 세분화된 데이터, 더 구체적인 데이터를 요청·수집·전송하기 쉽게 설계되어야 하며, 대량의 일반 데이터보다 사용이 쉬워야 합니다. API는 특히 개인 데이터와 관련해 세분화 및 사용자 제어를 제공해야 하며, 추가 기능에 추가 데이터가 필요한 경우에는 사용자 동의(권한 요청창, 사용자 활성화 등)를 통해 제공할 수 있습니다.

한때 Font Enumeration API가 제안되었지만, 사용자 데이터 노출에 대한 트레이드오프가 사용 사례를 정당화하지 못했습니다. 대신, 사용자가 실제로 선택한 폰트만 노출하는 대안이 제안되었습니다.

2. 여러 언어에서의 API 설계

2.1. 단순한 솔루션을 우선하기

해결하고자 하는 사용자 요구에 대해 단순한 솔루션을 신중하게 찾아보세요.

단순한 솔루션이 복잡한 솔루션보다 일반적으로 더 낫지만, 찾기는 더 어려울 수 있습니다. 단순한 기능은 사용자 에이전트가 구현·테스트하기 쉽고, 상호운용성이 높으며, 작성자가 이해하기 쉽습니다. 특히 가장 흔한 사용 사례를 쉽게 구현할 수 있도록 설계하는 것이 중요합니다.

사용자 요구가 명확하게 정의되어 있는지 반드시 확인하세요. 이렇게 하면 범위가 불필요하게 커지는 것을 막을 수 있고, API가 실제로 모든 사용자의 요구를 충족하는지 확인할 수 있습니다. 물론 복잡하거나 드문 사용 사례도 해결할 가치가 있습니다. 이런 경우 솔루션이 더 복잡할 수 있습니다. 앨런 케이의 말처럼, "단순한 것은 단순해야 하고, 복잡한 것은 가능해야 한다."

참고로 흔한 경우가 꼭 단순한 것은 아니며, 흔함과 복잡함이 항상 연관되는 것은 아닙니다.

HTML을 정제하여 XSS 공격을 방지하는 과정은 광범위한 보안 지식이 필요한 복잡한 과정이지만, Sanitizer API는 이 흔한 사용 사례를 위한 지름길을 제공합니다. 더 단순한 필터링도 가능하지만, 더 많은 설정이 필요할 수 있습니다.

참고:

2.2. 고수준과 저수준 API 간의 트레이드오프 고려

고수준 API는 사용자 에이전트가 사용자를 위해 접근성, 프라이버시, 사용성 등을 보장하는 등 다양한 방식으로 개입할 수 있게 해줍니다.

폰트 선택기(고수준 API)는 TAG의 권고를 받았습니다 저수준인 Font Enumeration API 대신 대부분의 사용 사례를 해결하면서 지문 채취 우려가 없는 프라이버시도 보장합니다. 네이티브 폰트 선택기는 접근성도 내장되어 있고, 최종 사용자에게 일관성도 제공합니다.

저수준 API는 작성자에게 실험할 여지를 주어, 시간이 지나면서 고수준 API가 사용 패턴에서 자연스럽게 탄생할 수 있도록 합니다. 또한 고수준 API가 충분하지 않을 때, 탈출구를 제공합니다.

저수준 빌딩 블록이 항상 Web API로 노출될 수 있는 것은 아닙니다. 그 이유는 사용자 보안·프라이버시를 보호하거나, Web API가 특정 하드웨어에 종속되지 않도록 하는 등의 목적 때문입니다. 하지만 고수준 API는 가능하면 저수준 API 위에 빌딩 블록으로 설계되어야 합니다. 이는 API가 어느 정도 고수준이어야 하는지 결정에 도움이 될 수 있습니다.

잘 계층화된 솔루션은 사용 용이성과 기능 간 트레이드오프 곡선의 연속성을 보장하고, 사용 사례가 조금만 복잡해져도 코드 복잡도가 급격히 늘어나는 "절벽"을 피해야 합니다.

2.3. 이름을 신중하게 짓기

API의 이름을 신중하게 지으세요. 적절한 이름은 작성자가 API를 올바르게 사용하기 쉽게 만듭니다.

구체적인 이름 짓기 지침은 이름 짓기 원칙 섹션을 참고하세요.

2.4. 일관성 유지

API를 설계할 때 선례를 고려하고, 일관성을 유지하려고 노력하는 것이 좋습니다.

API의 사용성과 일관성 사이에 긴장이 존재할 수 있습니다. 기존 선례가 사용성이 나쁜 경우, 사용성을 개선하기 위해 일관성을 깨는 것이 합리적일 수 있지만, 그 개선이 매우 커야만 정당화됩니다.

웹 플랫폼은 점진적으로 진화해왔기 때문에, 서로 상충되는 여러 선례가 있을 수 있습니다. 어떤 선례를 따를지 결정할 때는 보편성(모든 것이 같다면 더 널리 쓰인 선례), API 사용성(모든 것이 같다면 더 사용성 좋은 선례), API 연령(모든 것이 같다면 더 최신 선례)을 고려해 선택할 수 있습니다.

내부 일관성과 외부 일관성 사이에도 긴장이 있습니다. 내부 일관성은 시스템 전체와의 일관성, 외부 일관성은 세상 전체와의 일관성입니다. 웹 플랫폼에서는 기술 내 일관성(CSS 등), 웹 플랫폼 전체와의 일관성, 때로는 외부 도메인과의 일관성(특화 분야)이 있습니다. 이런 경우, 대다수 사용자가 누구일지 고려하는 것이 유용합니다. 대부분의 API는 정의된 기술에 익숙한 사람이 주요 사용자이므로, 그쪽과의 일관성을 우선하는 것이 좋습니다.

한 예로 CIE L*a*b* 색상은 L(0%-100%)에 CSS 전체와 일관성 있게 백분율을 쓸지, 컬러 과학과 일관성 있게 단위 없는 수(0-100)를 쓸지 논쟁이 있었습니다. 결국 CSS 내부 일관성을 우선하여 백분율을 채택했습니다.

이름 일관성에 관한 별도의 섹션도 있습니다.

2.5. 기능 명세의 가이드라인 따르기

어떤 기능을 사용할 때, 해당 기능의 명세에 있는 가이드라인을 따르세요.

다른 명세에서 사용할 기능 명세에는 그 기능을 올바르고 일관되게 사용할 수 있도록 가이드라인이 포함되어야 합니다. 이렇게 하면 플랫폼 전체에서 기능이 올바르고 일관되게 사용될 수 있습니다.

이런 가이드라인의 예시는 다음과 같습니다:

해당 명세를 사용할 때 관련 커뮤니티와 상의하세요. 이렇게 하면 커뮤니티에 새로운 사용 사례를 알릴 수 있고, 사용하는 명세와 사용되는 명세 모두를 개선할 기회가 생깁니다.

2.6. 새로운 기능은 감지 가능해야 함

작성자가 기능이 사용 가능한지 프로그래밍적으로 감지할 수 있도록 하세요. 이렇게 하면 웹 콘텐츠가 해당 기능이 없을 때도 우아하게 처리할 수 있습니다.

기존 기능이 페이지에서 사용 불가능한 이유는 다양합니다. 대표적인 두 가지는 아직 구현되지 않았거나, 보안 컨텍스트에서만 제공되는 경우입니다.

작성자가 각 경우마다 별도의 코드를 작성할 필요는 없어야 합니다. 이렇게 하면 작성자가 특정 경우만 알거나 신경 써도, 코드가 모든 경우를 처리할 수 있습니다.

기능은 있지만 필요한 기기가 없어서 사용할 수 없다면, 기능의 존재는 노출하되 기기가 없는 것은 별도로 감지하는 것이 더 낫습니다. 이렇게 하면 작성자가 기기 미사용과 기능 미지원 상황을 다르게 처리할 수 있습니다. 예를 들어 기기 연결 또는 활성화를 안내할 수 있습니다.

§ 9.2 디바이스 선택·열거 API 노출 시 주의도 참고하세요.

작성자는 항상 자바스크립트에서 기능을 감지할 수 있어야 하며, 어떤 경우에는 기능이 사용되는 언어에서도 감지 가능해야 합니다 (예: CSS의 @supports).

어떤 경우에는 기능 감지가 적절하지 않을 수도 있습니다. 기능 감지 지원 여부는 해당 기능에 대한 사용자 요구에 따라 결정하세요. 기능 감지가 있으면 원칙이나 사용자 요구가 실패한다면, 기능 감지를 지원하지 않아야 합니다.

기능의 사용 가능성 감지는 동의 여부 감지와는 다릅니다. 일반적으로 기능 구현 여부와 사용 권한 여부는 별도로 감지해야 합니다. 어떤 경우에는 기능 감지를 비활성화해야 사용 요청 거부를 허용할 수 있습니다.

또한, 일반적으로 개발자에게 노출되지 않는 기능이라면 기능 감지를 지원하는 것이 적절하지 않습니다. 예를 들어 프라이빗 브라우징 모드는 웹 명세에서 인정되는 개념이지만, 작성자에게 노출되지 않습니다. 사용자의 요구를 위해 프라이빗 브라우징 모드는 감지할 수 없어야 합니다.

참고:

2.7. 텍스트 포맷은 사람이 사용하기 쉽게 설계하기

사람이 쉽게 만들고 사용할 수 있는 텍스트 포맷을 설계하세요. 텍스트 포맷은 투명성도 높여줍니다.

가독성을 크기보다 우선하세요. 파일 크기는 도구로 최적화할 수 있고, 시간이 지나면 우선순위가 낮아집니다. 크기가 훨씬 더 중요한 경우라면, 텍스트 포맷이 적합하지 않을 수 있습니다.

SVG 경로 문법은 단일 문자 명령과 뒤따르는 좌표들로 크기를 줄이기 위해 설계되었습니다. 당시에는 파일 크기가 가장 중요한 요소였지만, 시간이 지나면서 크기 효율의 중요성은 낮아졌고, 가독성 저하의 문제는 그대로 남아 있습니다.

텍스트 포맷을 제공받은 사람은 텍스트 편집기를 사용하여 손쉽게 콘텐츠를 생성·수정할 수 있어야 합니다. 텍스트를 직접 수정하면 다양한 오류가 생길 수 있지만, 오류를 찾고 고치기는 어려울 수 있습니다.

사람들은 자신의 수정이 어떻게 처리되는지 일정 수준의 유연성을 기대합니다. 문법적 유연성을 명확하게 정의(공백, 인용, 구분자 등)하면 콘텐츠를 쉽게 수정할 수 있고 일관된 결과를 얻을 수 있습니다.

JSON 포맷은 유연해 보이지만, 이런 기본적인 사용성 편의성은 부족합니다. JSON에는 주석 기능이 없고, 객체와 배열에 트레일링 콤마를 쓸 수 없으며, 객체 키에 반드시 따옴표가 필요합니다. 이 때문에 수동 편집 시 문법 오류가 발생하기 쉽습니다.

문법 오류가 포맷 영향 범위를 제한하면 견고성과 사용성이 향상될 수 있습니다. 이를 위해 명세는 처리와 오류 처리를 충분히 정의하여 어떤 입력에도 일관된 결과가 나오게 해야 합니다.

CSS 속성에서 오타가 나면 해당 속성만 무시됩니다. 속성 오류로 인해 전체 규칙이 사라지는 일은 드뭅니다.

포맷이 기계만을 위한 것이라면, 바이너리 포맷이 더 효율적일 수 있으며, 사람이 직접 작성하거나 수정하는 것을 억제할 수 있습니다.

2.8. 새로운 기능을 보안 컨텍스트로 제한하는 것 고려

기능이 인증, 무결성, 기밀성이 없는 상황에서 사용자에게 위험하다면 반드시 보안 컨텍스트에서만 동작하도록 제한하세요.

예시로, Geolocation은 사용자의 위치를 안전하지 않게 전송하면 프라이버시 위험이 있으므로 보안 컨텍스트에서만 동작해야 합니다.

그 외 기능에 대해서는 TAG 멤버들 간에 일반적인 조언에 대해 합의가 없습니다. 어떤 사람들은 모든 신규 기능(기존 기능의 확장 제외)은 보안 컨텍스트에서만 동작해야 한다고 생각합니다. 이는 HTTPS 사용을 장려하고, 사용자가 더 안전하게 웹을 쓸 수 있게 합니다.

다른 사람들은 보안·프라이버시 영향이 명확한 경우에만 보안 컨텍스트로 제한해야 한다고 생각합니다. 이렇게 하면 보안·프라이버시에 영향을 주지 않는 신규 기능을 더 쉽게 웹 페이지에 도입할 수 있습니다.

명세 작성자는 Web IDL [WEBIDL] 에서 대부분의 기능을 [SecureContext] 확장 속성으로 인터페이스·네임스페이스·멤버(메서드·속성 등)에 보안 컨텍스트 제한을 적용할 수 있습니다.

그러나 일부 API(예: 이벤트 디스패치)는 보안 컨텍스트 제한을 명세의 규범적 설명으로 정의해야 합니다. 이런 경우에는 향후 API 개발자를 위해 [SecureContext] 와 유사한 메커니즘을 추가하는 방안을 고려해도 좋습니다.

§ 2.6 새로운 기능은 감지 가능해야 함에서 설명했듯이 기능이 사용 가능한지 프로그래밍적으로 감지할 수 있도록 해야 하며, 기능이 보안 컨텍스트가 아니어서 사용 불가한 경우도 포함해야 합니다.

그러나 어떤 이유로 코드가 기능 미지원 상황을 우아하게 처리할 수 없다면, 기능을 보안 컨텍스트로 제한하면 보안·비보안 컨텍스트 모두에서 사용할 수 있는 코드(예: 라이브러리)에 문제가 생길 수 있습니다.

2.9. 프라이빗 브라우징 모드 사용 노출 금지

기능이 작성자에게 프라이빗 브라우징 모드 감지 방법을 제공하지 않도록 하세요.

일부 사용자는 프라이빗 브라우징 모드를 자신의 안전을 위해 사용합니다. 이 때문에 프라이빗 브라우징 모드 사용 사실 자체가 민감한 정보일 수 있습니다. 이 정보가 해를 끼칠 수 있으며 고용주, 부모, 파트너, 국가 기관 등 권력을 가진 타인에게 노출될 경우 위험할 수 있습니다.

이러한 위험 때문에, 웹사이트가 프라이빗 브라우징 모드 사용 여부를 감지할 수 없어야 합니다.

IndexedDB를 지원하는 사용자 에이전트는 프라이빗 브라우징 모드에서 IndexedDB를 비활성화하면 프라이빗 브라우징 모드 사용 여부가 노출되므로 비활성화해서는 안 됩니다.
Payment Request APIshow() 메서드를 호출하면, 사용자 에이전트가 사용자가 즉시 결제 요청을 취소한 것처럼 동작할 수 있습니다.

이렇게 하면 사용자 에이전트가 프라이빗 브라우징 모드에서 자동으로 결제 요청을 취소할 수 있게 되며, (사용자의 배송·청구지 등 민감한 정보 보호) 프라이빗 브라우징 모드 사용 여부를 노출하지 않습니다.

참고:

2.10. 프라이빗 브라우징 모드에서 API 동작 고려

필요하다면, 프라이빗 브라우징 모드에서 API가 다르게 동작해야 하는지 명세하세요.

예를 들어, API가 어떤 정보 노출로 인해 개인 사용자의 활동을 프라이빗 브라우징 모드 안팎에서 연결할 수 있게 한다면, 잡음 추가, 권한 요청창을 통해 사용자에게 의미 있는 동의 정보를 제공하는 등의 추적 동의 완화책을 고려하세요.

프라이빗 브라우징 모드는 사용자가 개인 브라우징 흔적을 기기에 남기지 않고 웹을 이용할 수 있게 해줍니다. 따라서 클라이언트 측 저장소를 제공하는 API는 프라이빗 브라우징 모드에서 저장한 데이터가 모드가 해제된 후에도 남지 않도록 해야 합니다. 이때 사이트에 감지 가능한 API 차이가 노출되지 않도록 해야 합니다.

localStorage를 지원하는 사용자 에이전트는 프라이빗 브라우징 모드에서 저장 영역 변경을 모드 해제 후에도 유지하지 않아야 합니다.

사용자 에이전트가 한 사이트에 동시에 두 세션(프라이빗·일반)을 가진 경우, 프라이빗 세션에서 변경한 저장 영역은 일반 세션에 노출되지 않아야 하며, 그 반대도 마찬가지입니다. (storage 이벤트는 다른 세션 window 객체에 발생하지 않아야 합니다.)

참고:

2.11. 보조 기술 사용 노출 금지

API가 작성자에게 사용자가 보조 기술을 사용 중임을 감지할 방법을 제공하지 않도록 하세요.

웹 플랫폼은 장애가 있는 사람도 접근/사용할 수 있어야 합니다. 사이트가 사용자의 보조 기술 사용 여부를 감지할 수 있다면, 해당 서비스 접근을 거부하거나 제한할 수 있습니다.

보조 기술을 사용하는 사람들은 종종 사회적 약자이며, 보조 기술 사용 여부는 민감한 정보입니다. API가 이 정보를 노출하면, 국가 기관 등 해를 끼칠 수 있는 제3자에게 노출될 위험이 있습니다.

보조 기술 사용자의 경험을 개선하려는 기능 제안이 부수적으로 사용자의 보조 기술 사용 사실을 노출하는 경우가 있습니다. 이런 의도는 좋더라도, § 1.2 웹 페이지 방문은 안전해야 함을 위반하므로, 대안 솔루션을 찾아야 합니다.

Accessibility Object Model(AOM)은 한때 이벤트 세트를 정의해, 이벤트 발생 시 보조 기술 사용 여부를 노출했습니다.

AOM은 이후 해당 이벤트를 제거했고, 보조 기술 사용 노출 없이 동작하는 가상 DOM 이벤트로 대체했습니다.

참고:

2.12. 강력한 API에는 사용자 활성화 필요

일부 강력한 API는 침입적인 UI(예: 오디오 자동재생), 사용자 데이터 노출(예: 클립보드 접근), 사용자에게 명확한 표시 없이 백그라운드 작업(예: 로컬스토리지 접근), 신뢰할 수 있는 UI와의 상호작용(예: 권한 요청, 디바이스 기능 등)을 유발할 수 있습니다. 이런 API는 사용자 활성화와 같은 사용자의 의도 표시가 있어야만 동작하도록 설계해야 합니다. 이렇게 하면 사용자가 해당 웹 페이지와 의도적으로 상호작용 중임을 나타낼 수 있습니다.

사용자 활성화는 HTML 표준에서 자세히 정의되어 있습니다. API가 사용자 경험과 사용자에게 미치는 위험에 어떤 영향을 주는지 고려해, 사용자 활성화가 한 번만 필요할지(sticky), 주기적으로(transient), API 호출마다 한 번(transient consuming) 필요한지 결정하세요.

사용자 활성화가 대부분의 경우 필요하지만, 항상 충분하지는 않으며, 의미 있는 동의도 중요합니다.

2.13. 완전 활성 상태가 아닌 BFCached 문서도 지원

가능하다면, 비-완전 활성 BFCached(뒤로/앞으로 캐시) 문서에서 기능이 어떻게 동작하는지 명세하세요.

기능이 아래 범주에 해당하는 무언가를 한다면:

비-완전 활성 BFCached 문서에서 기능이 어떻게 동작하는지 BFCached 문서 지원 가이드를 따라 명세하세요.

참고: 문서는 BF캐싱과 관계없이 다른 이유로도 비-완전 활성이 될 수 있습니다. (예: iframe이 분리될 때 등) 이 가이드는 BF캐시에만 초점을 맞추며, 다른 경우까지 포괄하지는 않습니다.

2.14. 서드파티 도구와의 호환성보다 사용성 우선

새로운 기능을 설계할 때 사용성을 최우선으로 하고, 서드파티 도구와의 호환성은 부차적으로 고려하세요.

웹 플랫폼은 개발을 더 쉽고 빠르게 해주는 다양한 도구 생태계의 혜택을 받습니다. 신규 웹 플랫폼 기능의 문법이 서드파티 도구와 충돌해 깨지는 경우도 많습니다. 특히 서드파티 도구는 신규 기능 프로토타이핑에 자주 사용되므로 이런 일이 흔합니다.

대체로 웹 플랫폼 기능이 서드파티 도구보다 훨씬 오래 지속되므로, 기능에 최적의 문법·기능을 제공하는 것이 중요합니다.

어떤 경우에는 충돌로 인해 많은 웹사이트에서 문제가 생겨, 기능 문법을 다시 설계해야 할 수도 있습니다.

Array.prototype.contains()는 PrototypeJS 라이브러리의 동일한(하지만 호환되지 않는) 메서드와 충돌을 피하기 위해 Array.prototype.includes()로 이름이 바뀌었습니다. PrototypeJS는 수백만 사이트에서 사용되었습니다.

하지만 이런 예외는 드물어야 합니다.

새 문법이 서드파티 도구를 깨뜨릴 때, 깨지는 심각도, 도구의 인기 등 여러 요소를 고려하세요.

가장 중요한 것은 문법을 바꿔 서드파티 도구와 충돌을 피할 때 기능의 사용성이 얼마나 크게 저하되는지입니다. 사용성이 비슷한 여러 대안이 있다면, 서드파티 도구에 불편이 덜한 쪽을 우선하는 것이 좋습니다.

CSS WG가 CSS Grid Layout 설계 시, 사각 대괄호를 괄호 대신 선택해 유명 사전처리기 Sass와의 충돌을 피했습니다.

그러나 서드파티 도구와의 충돌을 피하는 것이 기능 사용성에 심각한 악영향을 주는 경우에는 실제로 운영 중인 사이트에 큰 피해가 없으면 이런 트레이드오프는 거의 허용되지 않습니다.

언어는 작성자가 미래의 네이티브 기능을 깨지 않으면서 언어를 확장할 수 있는 메커니즘도 제공해야 합니다. 이렇게 하면 향후 이런 딜레마를 줄일 수 있습니다.

2.15. 복잡한 타입은 더 단순한 타입의 조합으로 만들기

항상 상위 타입 대신 사용할 수 있는 하위 타입을 정의하세요. 상속을 사용하는 인터페이스는 부모 인스턴스에 대해 항상 적용되는 모든 것이 자식에게도 반드시 적용되어야만 사용하세요.

상속에서는 하위 타입이 상위 타입 대신 쓸 수 있는 아이템을 만듭니다. 하위 타입은 상위 타입과 동일한 속성과 메서드를 제공해야 하고, 행동도 일관성을 유지해야 합니다. 하위 타입이 동작 방식을 바꾸면, 상위 타입을 처리하는 코드가 제대로 동작하지 않을 수 있습니다.

타입 시스템 이론에서는 이것을 리스코프 치환 원칙이라고 합니다.

Event 타입은 KeyboardEvent, PointerEvent 의 상위 타입입니다. 이벤트는 항상 동일한 기본 속성과 동작이 적용되며, bubbles와 같은 속성이 있습니다. 하위 타입은 새로운 속성과 동작을 추가하지만, 인스턴스들은 여전히 모든 면에서 Event로 동작합니다.

더 단순한 접근은 상속을 피하고 기존 기능을 조합하여 재사용하는 것입니다. 새 아이템은 필요한 기존 컴포넌트를 속성으로 포함할 수 있습니다.

폼 연관 커스텀 요소는 커스텀 요소가 폼 제출과 상호작용할 수 있게 합니다. HTMLInputElement를 특화하는 것은 어렵습니다. input 요소는 매우 복잡하며, HTMLInputElement의 특화는 그 복잡함을 유지해야 합니다. 커스텀 요소는 HTMLInputElement의 복잡함을 다룰 필요 없이 필요한 항목을 연결(즉, 조합)하여 폼과 상호작용할 수 있습니다.
.

3. HTML

이 섹션은 HTML을 통해 노출되는 기능의 설계 원칙을 자세히 설명합니다.

3.1. 유사 기능에는 HTML 속성 이름만 재사용

HTML 속성으로 지정되는 기능을 추가할 때, 유사한 기능을 지정하는 기존 속성 이름이 다른 요소에 있는지 확인하세요. 기존 HTML 속성 이름을 재사용하면 작성자는 기존 지식을 활용할 수 있고, 언어 전체의 일관성을 유지하며, 용어 집합을 작게 유지할 수 있습니다.

동일한 속성 이름 multipleselect 요소에서 여러 값을 선택하도록, input 요소에서 여러 값을 입력하도록 각각 사용됩니다.
open 속성은 details 요소에 도입된 후, dialog 요소에도 재사용되었습니다.

기존 HTML 속성을 재사용한다면, 기존 속성의 문법과 최대한 비슷하게 유지하세요.

for 속성은 label 요소에 도입되어, 어떤 폼 요소와 연관될지 지정합니다. 이후 output 요소에도 사용되어, 어떤 요소가 입력값을 제공하거나 계산에 영향을 주는지 지정합니다. 후자의 문법은 더 넓게, 공백으로 구분된 id 목록을 허용하지만, 전자는 하나의 id만 허용합니다. 하지만 두 경우 모두 동일한 문법을 따릅니다. 예를 들어 한쪽은 id 목록, 다른 한쪽은 셀렉터를 허용한다면 이는 잘못된 패턴입니다.

반대의 경우도 마찬가지입니다: 기존 HTML 속성 이름의 기능과 유사하지 않은 기능을 추가할 때는, 기존 속성 이름을 재사용하지 마세요.

type 속성은 input, button 요소에서 요소 타입을 세분화하는 데 사용되지만, link, script, style 등에서는 MIME 타입을 지정합니다. 이는 잘못된 패턴이며, 한쪽 그룹은 다른 이름을 사용했어야 합니다.

3.2. 짧은 값 목록에는 공백 구분 속성, 긴 목록에는 별도 요소 사용

요소에 대한 메타데이터가 값 목록이 될 수 있을 때, 일반적으로 공백 구분 목록을 사용하며 DOMTokenList로 노출합니다.

class 속성은 공백 구분된 클래스 이름 목록을 가집니다. classListDOMTokenList 로, 작성자가 클래스 이름을 추가·제거할 수 있게 해줍니다.

sandbox 속성은 공백 구분된 샌드박스 플래그 목록을 가집니다. iframe.sandboxDOMTokenList 로, 작성자가 샌드박스 플래그를 추가·제거할 수 있습니다.

웹 플랫폼의 다른 부분과의 일관성도 중요하며, 이를 위해 다른 구분 문자를 사용해야 할 수도 있습니다.

accept 속성은 쉼표로 구분된 값 목록을 갖습니다. 이는 `Accept` HTTP 헤더의 문법과 일치해야 하기 때문입니다.

문법과 관계없이, 속성은 짧은 값 목록에만 사용해야 합니다. 긴 목록을 속성에 모두 담는 것은 권장되지 않습니다. 대신 현재는 목록 항목(및 메타데이터)을 별도 요소로 나타내는 것이 관행입니다. 이 요소들은 해당 요소의 자식이거나 속성을 통해 연결될 수 있습니다.

select 요소의 값 목록은 option 요소 자식들로 제공됩니다. 반면 input 요소에 추천 값 목록을 제공할 땐, 별도의 datalist 요소를 사용하고, list 속성으로 연결합니다.

video 또는 audio 요소의 미디어 소스 목록은 source 요소 자식들로 제공됩니다.

드물게 다른 트레이드오프가 필요할 때도 있습니다.

srcset 속성은 쉼표로 구분된 이미지 후보 문자열 목록을 허용합니다. 이 문법은 자식 요소 목록 대신 선택되었는데, img 요소가 자식 요소를 허용하지 않는 빈 요소이기 때문입니다. 각 항목이 여러 값을 포함하므로, 공백 구분 문법은 사용할 수 없습니다.

3.3. HTML 파서를 멈추지 않기

외부 리소스를 처리하기 위해 HTML 파서가 멈추도록 설계하지 않도록 하세요.

페이지를 파싱하면서, 브라우저는 필요한 자산을 발견하고, 병렬로 로딩할 우선순위를 결정합니다. 이후 리소스 발견을 방해하는 리소스가 있으면 파싱이 중단됩니다. 최악의 경우, 브라우저가 항목을 병렬이 아닌 순차적으로 다운로드해야 하고, 최선의 경우에도 브라우저가 추측 파싱에 따라 다운로드를 대기시키는데, 이는 잘못될 수도 있습니다.

파서를 차단하는 기능은 보통 이후 콘텐츠보다 먼저 추가 콘텐츠를 파서에 공급하고 싶어서입니다. 이는 레거시 <script src="…"> 요소의 경우처럼, document.write(…)로 파서에 주입할 수 있습니다. 위의 성능 문제로 인해, 신규 기능에서는 이런 방식을 사용하지 않아야 합니다.

3.4. 렌더링을 차단하는 기능은 피하기

자원 로딩이나 기타 작업이 페이지 렌더링 전에 필요하면, 빈 페이지(또는 이전 페이지)가 노출될 수 있습니다. 이는 좋지 않은 사용자 경험입니다.

이런 기능은 전체 사용자 경험이 개선되는 경우에만 추가하세요. 대표적 예는 스타일시트를 다운로드·처리하기 위해 렌더링을 차단하는 경우입니다. 대안은 스타일이 적용되지 않은 콘텐츠가 잠시 노출되는 것으로, 이는 바람직하지 않습니다.

§ 10.2.1 일부 API는 전용 워커에만 노출도 참고하세요.

3.5. 속성 동기화 유지

새 콘텐츠 속성에는 동일한 이름의 IDL 속성이 있어야 하며, 두 속성의 상태는 동기화되어야 합니다. 동기화된 IDL 속성의 이름이 다르면 혼란을 초래하니 피하세요.

반대 방향은 성립하지 않습니다. 새 IDL 속성이 항상 콘텐츠 속성 대응을 필요로 하지는 않습니다.
이 지침의 반례로는 inputvalue, optionselected, inputchecked 등이 있습니다. 이들 콘텐츠 속성은 동일한 이름의 IDL 속성에 반영되지 않습니다. 각각의 IDL 속성은 defaultValue, defaultSelected, defaultChecked 입니다.

3.6. URL 포함 속성은 주요 용도에 따라 이름 지정

요소가 속성에 포함된 URL로 이동할 수 있게 한다면, href로 이름 붙이세요. 예: a 요소의 href 속성.

참고: 뒤돌아보면, formaction 속성도 href로 이름 붙였어야 했습니다.

요소가 지정된 URL의 리소스를 로드한다면, src로 이름 붙이세요. 예: img 요소의 src 속성, script 요소의 src 속성.

참고: HTML에는 모방해서는 안 될 레거시 불일치가 있습니다. 예를 들어 linkhref 속성은 rel 속성 값에 따라 탐색 또는 리소스 로딩에 쓰일 수 있습니다.

속성이 요소 목적에 부수적으로 관련된 URL을 식별하면, videoposter, qcite, aping 등처럼, 의미에 따라 이름 붙이세요.

참고: URL을 포함하는 속성은 IDL에서 USVString으로 나타내야 합니다. § 8.2 문자열 적절히 표현도 참고하세요.

3.7. 각 HTML 요소는 하나의 목적만 갖도록 하기

각 HTML 요소는 명확한 목적을 가져야 합니다. HTML 속성으로 요소의 의미를 수정할 수 있지만, 속성은 그 목적을 근본적으로 바꾸면 안 됩니다.

여러 모드에서 동작하는 요소를 정의하기보다는, 각 모드마다 별도의 요소를 두는 것이 더 낫습니다. 이렇게 하면 작성자가 더 쉽게 사용할 수 있습니다.

input 요소는 type 속성으로 매우 다양한 모드로 전환할 수 있습니다. 각 모드가 사용자 입력을 받는 공통 목표를 공유하지만, 이는 너무 광범위한 목적입니다. 이러한 모드의 의미는 별도의 요소 이름으로 더 명확하게 표현할 수 있었을 것입니다.

속성으로 동일 요소의 의미를 오버로딩해 다른 동작을 선택할 수 있게 하는 것은, 어떤 속성 값이든 목적이 같을 때만 의미가 있습니다.

textarea 요소와 input 요소의 type 값이 "text"인 경우는 두 요소의 차이가 크지 않으므로 동일 요소로 처리할 수도 있었습니다.

반례로는 과도한 세분화와, 맥락에 따라 의미가 달라지는 요소가 있습니다.

현재는 폐지된 acronym 요소는 abbr 요소와 중복되어 과도한 세분화에 해당합니다.
source 요소는 맥락에 따라 목적이 달라질 수 있습니다. source 요소는 독립적으로 사용되지 않고, 의미는 부모 요소에 의해 결정됩니다.

4. 캐스케이딩 스타일 시트(CSS)

이 섹션은 CSS를 통해 노출되는 기능의 설계 원칙을 상세히 설명합니다.

4.1. 별도의 캐스케이드가 필요한 기준으로 CSS 속성 분리

어떤 값을 CSS 속성으로 묶고, 어떤 값을 별도의 속성으로 둘지 독립적으로 설정하는 것이 합리적인 기준에 따라 결정하세요.

CSS 캐스케이딩선언이 서로 다른 규칙이나 스타일시트에서 서로 덮어쓸 수 있게 합니다. 함께 덮어써야 하는 값 집합은 하나의 속성으로 묶어 캐스케이드되게 하고, 독립적으로 덮어쓸 수 있어야 하는 값은 별도의 속성으로 분리하세요.

예를 들어, initial-letter 속성의 "size"와 "sink" 측면은 드롭캡, 선큰캡, 레이즈드캡 등 하나의 이펙트의 일부이므로 하나의 속성에 포함되어야 합니다.

반면 initial-letter-align 속성은 별도여야 합니다. 이는 해당 효과 전체에 대해 문서 내에서 정렬 정책을 설정하며, 이는 일반적인 스타일 선택이고, 문서 내에서 사용하는 스크립트(예: 라틴, 키릴, 아랍어)의 기능이기도 합니다.

4.2. CSS 속성 상속 여부를 적절히 선택

속성을 상속시킬지 여부는, 속성이 상위 요소와 하위 요소에 설정될 때 효과를 덮어써야 하는지, 추가해야 하는지에 따라 결정하세요.

하위 요소에 속성을 설정하면 상위 요소의 효과를 덮어써야 한다면, 해당 속성은 상속되어야 합니다.

하위 요소에 속성을 설정하면 상위 요소의 효과에 추가되는 별도의 효과라면, 해당 속성은 상속되지 않아야 합니다.

상속되지 않는 속성을 명세하면서 요소 처리 시 상위 요소의 속성 값을 참조하도록 하면(느릴 수도 있음), 이는 "코드 스멜"로, 해당 속성은 상속되어야 했다는 신호입니다. 상속되는 속성을 명세하면서 부모 요소와 값이 같으면 무시하도록 하면, 이는 "코드 스멜"로, 해당 속성은 상속되지 말았어야 한다는 신호입니다.

속성이 텍스트에 영향을 준다면, 하위 요소가 상위 요소의 효과를 덮어써야 하므로 거의 항상 상속되어야 합니다. 이는 스타일 없는 인라인 요소를 텍스트 주위에 삽입해도 텍스트 모양이 바뀌지 않는다는 설계 원칙을 유지하기 위해서도 필요합니다.
예를 들어 background-image 속성은 상속되지 않습니다.

만약 background-image 속성이 상속되었다면, 명세는 부분적으로 투명한 이미지가 각 하위 요소에 반복 표시되지 않게 복잡한 처리를 해야 했을 것입니다. 이는 부모 요소와 값이 같을 때 다르게 동작해야 하는 추가 복잡성을 수반하며, 이는 위에서 언급한 "코드 스멜"로, 해당 속성은 상속되지 말아야 했다는 신호입니다.

또 다른 예는 font-size 속성으로, 상속됩니다. 이 속성은 요소 내 텍스트의 폰트 크기를 설정하며, font-size를 다르게 선언하지 않은 모든 하위 요소에도 계속 적용됩니다.

만약 font-size 속성이 상속되지 않는다면, 초기값은 상위 요소 중 해당 값이 아닌 첫 요소를 찾아 올라가야 하게 될 것입니다. 이는 위에서 언급한 "코드 스멜"로, 해당 속성은 상속되어야 한다는 신호입니다.

4.3. 속성이 어떻게 상속될지에 따라 계산 값 타입 선택

CSS 속성의 계산 값은 속성이 어떻게 상속될지에 따라, 다른 속성에 의존하는 값의 상속 방식을 포함해 결정하세요.

상속이란, 요소가 부모와 동일한 계산 값을 속성에 대해 가지는 것을 뜻합니다. 계산 값에 도달하기 전 처리 단계는 상속되는 값에 영향을 주고, 이후 단계(예: 사용 값)는 영향을 주지 않습니다.

참고: 계산 값해결 값과 혼동하지 마세요. 해결 값은 getComputedStyle() 메서드에서 반환됩니다.
예를 들어 line-height 속성은 <number> 값을 받을 수 있습니다. 예시: line-height: 1.4. 이 값은 font-size의 배수를 의미합니다. font-size20px이면, 실제 값28px이 됩니다.

하지만 이 경우 계산 값<number> 1.4이고, <length> 28px이 아닙니다. (사용 값28px입니다.)

line-height 속성은 font-size가 다른 요소에도 상속될 수 있으며, 해당 요소에서 line-height에 의존하는 속성은 해당 요소의 font-size를 참조해야 하며, font-size를 상속한 요소의 값을 참조하면 안 됩니다.

<body style="font-size: 20px; line-height: 1.4">

  <p>이 본문 텍스트는 줄 높이가 28px입니다.</p>

  <h2 style="font-size: 200%">
    이 제목은 줄 높이가 56px입니다.
    본문에서 줄 높이를 선언했어도 28px이 아닙니다.
    즉, 40px 폰트가 줄 높이를 넘지 않습니다.
  </h2>
</body>

이러한 숫자 값은 line-height에 사용하기에 더 적합하며, 길이 값보다 상속이 더 잘됩니다.

참고:

4.4. CSS 속성과 값은 적절하게 이름 지정

CSS 속성 이름은 대개 명사이고, 값 이름은 대개 형용사(가끔 명사)입니다.

속성과 값의 단어는 하이픈으로 구분합니다. 약어는 일반적으로 피합니다.

가능하면 문법적 접두사나 접미사가 없는 원형 단어를 사용하세요("size"를 "sizing" 대신 사용).

속성 값 목록은 새로운 값을 추가할 수 있도록 선택하세요. yes, no, true, false와 같이 복잡한 이름이 사실상 동등한 값은 피하세요.

속성 이름에 "mode"나 "state"와 같은 단어는 피하세요. 속성은 일반적으로 모드나 상태를 설정하기 때문입니다.

이름 짓기 일반 조언은 § 12 이름 짓기 원칙을 참고하세요.

4.5. 콘텐츠는 기본적으로 보이고 접근 가능해야 함

CSS 속성 또는 CSS 레이아웃 시스템(일반적으로 display 속성 값)이 콘텐츠가 기본적으로 보이고, 접근 가능하며, 사용 가능하도록 설계하세요.

예를 들어 CSS의 모든 레이아웃 시스템의 기본 동작은 콘텐츠가 잘려나가거나, 다른 콘텐츠와 겹치거나, 스크롤로 접근할 수 없게 만들지 않습니다. 이러한 현상은 overflow: hidden 또는 left: -40em처럼 명확하게 그런 동작을 선택한 CSS 기능을 사용할 때만 발생해야 하며, display: flexposition: relative 같은 기본 동작에서는 발생하지 않아야 합니다.

5. 자바스크립트 언어

5.1. 웹 API에는 자바스크립트 사용

웹의 명령형 API를 설계할 때는 자바스크립트를 사용하세요. 특히, 언어 고유의 의미와 관례에 자유롭게 의존할 수 있으며, 일반화할 필요가 없습니다.

예를 들어, CustomElementRegistry.define() 메서드는 Constructor Method에 대한 참조를 인자로 받습니다.

이는 자바스크립트에 클래스가 비교적 최근에 추가된 점과, 메서드 참조가 자바스크립트에서 매우 쉽게 사용된다는 점을 활용한 예입니다.

자바스크립트는 [ECMASCRIPT]라는 이름으로 표준화되어 있습니다.
[WEBIDL]에는 별도의 "ECMAScript 바인딩" 섹션이 있지만, 이는 Web IDL이 다른 프로그래밍 언어에 바인딩하려는 의도가 있음을 의미하지 않습니다.

5.2. run-to-completion 의미 유지

자바스크립트 실행 컨텍스트 외부에서 상태 변경이 발생하면, 예를 들어 작업 큐를 사용하거나 렌더링 업데이트 과정에서 자바스크립트 작업 사이에 변경 사항을 전파하세요.

C++나 Rust 같은 저수준 언어와 달리, 자바스크립트는 전통적으로 한 번에 하나의 코드만 실행되는 것으로 작동합니다. 그래서 자바스크립트 작성자는 함수가 실행되는 동안 사용 가능한 데이터가 예상치 못하게 바뀌지 않으리라 기대합니다.

개발자 행동 결과가 아닌 상태 변경과, 비동기적으로 전달되는 변경은 다른 자바스크립트 실행 도중(마이크로태스크 사이 포함)에 발생하지 않아야 합니다.

동기 실행(예: while 루프) 중이나, 이미 resolve된 Promiseawait한 뒤에는, 개발자가 다음과 같은 일이 일어날 것이라 기대하지 않습니다:

이러한 동작은 현재 실행 중인 스크립트에서 발생하지 않으므로, 현재 작업 중에는 바뀌지 않아야 합니다.

데이터는 개발자 행동 결과로 동기적으로 바뀔 수 있습니다.

node.remove() 는 DOM을 동기적으로 변경하며 즉시 관찰 가능합니다.

다음과 같은 경우에는 이 규칙을 위반해도 됩니다:

5.3. 가비지 컬렉션 노출 금지

자바스크립트 웹 API가 작성자가 가비지 컬렉션 시점을 알 수 있도록 하지 마세요.

가비지 컬렉션의 타이밍은 사용자 에이전트마다 다르고, 성능 개선에 따라 시간이 지나면서 달라질 수 있습니다. 만약 API가 가비지 컬렉션 타이밍을 노출하면, 프로그램 동작이 상황에 따라 달라질 수 있습니다. 이는 작성자가 이러한 차이를 처리하는 추가 코드를 작성해야 함을 의미합니다. 또한, 코드가 타이밍에 의존하면 사용자 에이전트마다 다른 가비지 컬렉션 전략 구현이 더 어려워질 수 있습니다.

즉, 가비지 컬렉션이 실행되면 null이 되는 속성 등 약한 참조처럼 동작하는 API를 노출하지 마세요. 자바스크립트 코드의 객체·데이터 생명주기는 예측 가능해야 합니다.

getElementsByTagNameHTMLCollection 객체를 반환합니다. 같은 Document 객체에 같은 태그 이름으로 두 번 호출하면 같은 객체가 재사용될 수 있습니다. 실제로는 해당 객체가 가비지 컬렉션되지 않았을 때만 같은 객체가 반환됩니다. 즉, 가비지 컬렉션 타이밍에 따라 동작이 달라집니다.

getElementsByTagName 를 오늘날 설계한다면, 출력 재사용을 항상 보장하거나, 호출할 때마다 새 HTMLCollection을 반환하도록 설계했을 것입니다.

getElementsByTagName 은 가비지 컬렉션 타이밍에 의존할 수 있다는 신호를 주지 않습니다. 반면, WeakRefFinalizationRegistry처럼 가비지 컬렉션에 명확히 의존하도록 설계된 API는 작성자에게 상호작용이 명확하게 기대된다는 점을 알려줍니다.

6. 자바스크립트 API 설계

6.1. WebIDL 딕셔너리, 인터페이스, 네임스페이스를 적절히 사용

새 API에는 적절한 WebIDL 메커니즘을 사용하세요.

WebIDL은 Web API 정의를 위한 여러 구조체를 제공합니다. 딕셔너리, 인터페이스, 네임스페이스는 각각 용도에 따라 다른 특성을 가집니다.

목표는 Web 개발자에게 자연스럽고 일관된 API를 제공하면서 "가짜 클래스"나 기능 없는 클래스와 같은 함정을 피하는 것입니다.

"설정" 또는 "입력 전용" 데이터에는 딕셔너리 사용

API 일부가 일시적인 데이터를 나타내고, 특히 API가 파라미터, 설정, 옵션 집합을 받는 경우 딕셔너리를 선택하세요.

딕셔너리는 데이터가 저장·변경되지 않고 호출 시에만 사용하는 경우에 이상적입니다.

예시: Web Share ShareData 멤버 [WEB-SHARE]:

dictionary ShareData {
  USVString title;
  USVString text;
  USVString url;
};

일반적 사용 예:

await navigator.share({text: "Text being shared" });

딕셔너리는 확장성이 뛰어나며, 이후 필요에 따라 옵션 필드를 쉽게 추가할 수 있습니다. 딕셔너리의 멤버는 기본적으로 선택적이며, 필요하다면 required로 지정할 수 있습니다.

딕셔너리는 자바스크립트에서 매우 자연스럽게 사용됩니다. { ... }를 인라인으로 전달하는 것이 자바스크립트에서 설정값을 제공하는 가장 자연스러운 방식입니다.

딕셔너리는 사용자 에이전트의 처리 방식 덕분에 미래 호환성도 뛰어납니다. 구현체가 이해하지 못하는 딕셔너리 멤버는 무시됩니다. 따라서 새로운 멤버를 추가해도 기존 코드가 깨지지 않습니다.

딕셔너리는 라이프사이클 내에서 타입 구분이 필요 없는 객체에 가장 적합합니다(즉, instanceof는 의미가 없음. 항상 Object).

딕셔너리는 메서드에 "값으로 전달"됩니다(즉, 복사됨). 브라우저 엔진은 자바스크립트 객체를 WebIDL로 변환할 때 알 수 없는 멤버를 제거합니다. 즉, API에 값을 전달한 뒤에 값을 변경해도 아무런 영향이 없습니다.

다시 ShareData 딕셔너리 예시:

const data = {
    "text": "Text being shared",
    // 브라우저에 "whatever" 파라미터가 없으면 무시됨.
    "whatever": 123,
};
let p = navigator.share(data);

// .share() 호출 후 값을 변경해도 아무 효과 없음
data.text = "New text";

기능, 상태, 식별에는 인터페이스 선택

인터페이스는 자바스크립트의 클래스와 거의 동일합니다. 명세에서 외부 속성과 내부 "슬롯" 모두 상태와 그 상태에 대한 연산(메서드)을 묶어야 할 때 인터페이스를 사용하세요.

딕셔너리와 달리, 인터페이스는:

인터페이스 정의는 글로벌 스코프에 노출되어, 정적 메서드 명세가 가능합니다. 예: URL 인터페이스의 canParse() 정적 메서드.

if (URL.canParse(someURL)) {
    // Do stuff...
}

상태가 있는 인터페이스에는 가능하다면 생성자를 제공합니다. 상태가 없는 클래스에는 생성자를 추가하지 마세요. 그렇게 하면 나쁜 관행으로, 효과 없는 "가짜 클래스"가 만들어집니다. 즉, 인터페이스 인스턴스가 정적 메서드로도 할 수 있는 일밖에 못합니다. DOMParserDOMImplementation가 가짜 클래스 예시입니다.

인터페이스 데이터를 더 쉽게 쓸 수 있게 serializer 제공

인터페이스 인스턴스를 여러 애플리케이션에서 사용할 수 있는 형태로 변환하는 serializer를 추가하세요.

.toJSON() 메서드는 인터페이스 인스턴스를 유용한 JSON 직렬화로 만듭니다. .toBlob() 메서드는 인터페이스의 바이너리 표현을 추출할 수 있습니다.

이렇게 하면 객체를 API에서 자연스럽게 사용할 수 있습니다. 예시: GeolocationPosition에는 toJSON() 메서드가 있습니다.

const position = await new Promise((resolve, reject) => {
    navigator.geolocation.getCurrentPosition(resolve, reject);
});

const message = JSON.stringify({
    user: userId,
    time: Date.now(),
    position, // .stringify()가 .toJSON()을 자동 호출함
});

행위만 있는 유틸리티에 "가짜 클래스" 대신 네임스페이스 사용

네임스페이스는 정적 속성과 메서드 집합을 모으기 위한 용도입니다. 네임스페이스는 프로토타입이 없습니다. 예시로 JS의 Math, Intl, Atomics, Console 객체가 있습니다.

정적 함수가 한두 개뿐이면, 전체 네임스페이스를 만드는 것은 과할 수 있습니다. 기존 객체에 붙이는 편이 더 적절할 수 있습니다.

반대로, 네임스페이스가 너무 크거나 범위가 넓어지면 더 나은 조직화나 논리적 분할이 필요할 수 있습니다.

"의사 네임스페이스"

WebIDL 인터페이스는 속성을 가질 수 있으므로, 생성 불가능한 인터페이스를 속성으로 정의해 "의사 네임스페이스"를 만들 수 있습니다.

대표적 예시는 navigator 객체에 부착된 모든 속성입니다. navigator 객체는 (Navigator) 인터페이스 정의를 가지며, 해당 인터페이스는 다른 속성을 통해 추가 기능에 접근할 수 있게 해줍니다.

예를 들어:

의사 네임스페이스는 명세 작성자가 특정 인스턴스를 참조할 필요가 있을 때 유용합니다. (예: navigator.permissions처럼 특정 브라우징 컨텍스트에 적용되는 권한) 해당 항목이 페이지에 가시적인 상태를 노출하지 않아도 그렇습니다.

6.2. 속성은 데이터 프로퍼티처럼 동작해야 함

[WEBIDL] 속성은 단순 자바스크립트 객체 프로퍼티처럼 동작해야 합니다.

실제로 IDL 속성은 getter/setter가 있는 접근자 프로퍼티로 구현됩니다. 자바스크립트 객체 프로퍼티처럼 동작하게 하려면:

이런 방식으로 동작하지 않는 속성은 메서드로 구현하는 것이 더 낫습니다.

예를 들어 offsetTop 은 레이아웃을 수행하며 복잡하고 시간이 오래 걸릴 수 있습니다. 이 경우 getBoundingClientRect()와 같은 메서드로 구현하는 것이 더 좋았을 것입니다.

6.3. 객체가 라이브 또는 정적인지 고려

API가 내부 상태를 나타내는 객체에 접근할 수 있을 때, 해당 객체가 상태 변경 시 계속 업데이트되어야 하는지 결정하세요.

항상 현재 상태를 나타내는 객체는 라이브 객체이고, 생성 시 상태를 나타내는 객체는 정적 객체입니다.

라이브 객체

작성자가 내부 상태를 변경할 수 있다면, 해당 객체는 라이브여야 합니다. 예를 들어 DOM Node는 라이브 객체입니다. 작성자가 현재 상태를 이해하고 문서를 변경할 수 있습니다.

라이브 객체의 속성은 접근 시 계산될 수 있습니다. 이는 데이터가 복잡하게 계산될 때 라이브 객체가 더 나은 선택일 수 있음을 의미합니다. 객체를 반환하기 전에 모든 데이터를 계산할 필요가 없으므로.

라이브 객체는 정적 객체로 복사할 필요가 없으므로 메모리 사용량도 줄일 수 있습니다.

정적 객체

객체가 변경될 수 있는 목록을 나타낸다면, 대부분의 경우 객체는 정적이어야 합니다. 이렇게 하면 목록을 반복하는 코드가 반복 중 목록이 바뀔 가능성을 처리할 필요가 없습니다.

getElementsByTagName 은 목록을 나타내는 라이브 객체를 반환하며, 작성자는 항목을 반복할 때 주의해야 합니다:
let list = document.getElementsByTagName("td");

for (let i = 0; i < list.length; i++) {
    let td = list[i];
    let tr = document.createElement("tr");
    tr.innerHTML = td.outerHTML;

    // td가 목록에서 제거되어 반복이 예측 불가해짐
    td.parentNode.replaceChild(tr, td);
}

querySelectorAll() 이 정적 객체를 반환하도록 한 것은 명세 작성자가 getElementsByTagName 이 문제를 일으키는 것을 발견한 뒤 결정되었습니다.

URLSearchParams 는 목록을 나타내지만 정적이 아닙니다. 이는 작성자가 URL의 쿼리 문자열을 변경할 수 있는 방식이기 때문입니다.

참고: maplikesetlike 타입에는 이 조언이 적용되지 않을 수 있습니다. 이 타입은 반복 중에 변경되어도 잘 동작하도록 설계되었습니다.

속성을 접근할 때 값을 계산할 수 없다면, 정적 객체가 객체가 사용되지 않는 동안 업데이트를 유지할 필요가 없어집니다.

정적 객체가 자주 변경되는 상태를 나타낸다면, 속성보다는 메서드에서 반환하도록 하세요.

참고:

6.4. 접근자는 속성처럼, 메서드처럼 동작하지 않게

객체 속성이나 getter를 설명하는 IDL 속성은 객체 상태에 대한 정보를 제공합니다.

참고: 차단 연산의 반례는 offsetTop과 같이 getter가 레이아웃을 수행하는 경우입니다.

IDL 속성을 정의할 때는, 가능하다면 setter에 입력한 값을 getter에서 반환할 수 있도록 하세요. 즉, obj.property = xobj.property === x가 true여야 합니다. (정규화나 타입 변환이 필요하면 불가능할 수 있으나, 일반 코드 경로에서는 목표로 삼으세요.)

반환하고자 하는 객체는 라이브 또는 정적일 수 있습니다. 즉:

6.5. 옵션 및/또는 기본형 인자는 딕셔너리로 받기

API 메서드는 일반적으로 여러 옵션 인자 대신 딕셔너리 인자를 사용하세요.

이렇게 하면 메서드 호출 코드가 더 읽기 쉬워지고, 시그니처를 기억하기 쉬워집니다. 또한 이후 동일 타입 인자가 여러 개 필요할 경우 API 확장성도 좋아집니다.

예시:
new Event("example", { bubbles: true, cancelable: false})

new Event("example", true, false)
보다 훨씬 읽기 쉽습니다.

필수 파라미터도 딕셔너리로 받는 것을 고려하세요. 특히 기본형 타입일 때 가독성이 향상된다면.

window.scrollBy({ left: 50, top: 0 })

window.scrollBy(50, 0)
보다 더 읽기 쉽습니다.

딕셔너리 자체는 선택적 인자로 두세요. 작성자가 모든 기본값에 만족한다면 추가 인자를 전달하지 않아도 됩니다.

예시:
element.scrollIntoView(false, {});

element.scrollIntoView(false);
와 같습니다.

참고:

6.6. 메서드 인자는 가능하다면 선택적으로 만들기

API 메서드의 인자가 합리적 기본값을 가진다면, 해당 인자를 선택적으로 만들고 기본값을 명시하세요.

addEventListener() 는 선택적 불리언 useCapture 인자를 받습니다. 기본값은 false로, 이벤트가 기본적으로 버블링 단계에서 리스너에 전달됨을 의미합니다.

불리언 인자는 기본값을 false로 두는 것이 강력히 권장됩니다.

XMLHttpRequest 의 세 번째 선택적 인자는 예외적으로 true가 기본값입니다. 이는 레거시 상호운용성 때문이며, 좋은 설계 예시는 아닙니다.

기본값은 대부분의 작성자가 선택할 값이어야 하며, 그 선택이 명확하다면 그렇게 하세요. 불리언 속성의 경우, false가 일반적인 선택이 되도록 속성 이름을 정해야 할 수도 있습니다.

API에서 여러 리스트 데이터 타입을 결정할 때는, 별도 요구가 없다면 다음 타입을 사용하세요:

참고:

6.7. 옵션 인자 이름은 적절히 지정

옵션 인자의 이름을 기본 동작이 분명하게 드러나도록, 부정형 이름이 되지 않게 지정하세요.

이 조언은 딕셔너리단일 인자 모두에 적용됩니다.

addEventListener()options 객체를 받으며, once 옵션이 포함됩니다. 이는 리스너가 반복적으로 호출되지 않아야 함을 나타냅니다.

이 옵션을 repeat로 이름붙일 수도 있었으나, 그 경우 기본값true여야 했습니다. noRepeat로 이름짓는 대신, API 작성자는 once로 명명해 부정을 사용하지 않고 기본 동작을 반영했습니다.

기타 예시:

참고:

6.8. 오버로딩은 신중하게 사용

메서드의 동작이 전달된 인자에 따라 크게 달라진다면, 단일 메서드 오버로딩 대신 별도의 메서드를 정의하는 것이 보통 더 낫습니다.

메서드 인자 중 하나가 단일 값이나 배열이 될 수 있도록 오버로딩하는 것은 유용합니다. 딕셔너리 옵션 전달도 유연성을 높이므로 좋은 패턴입니다.

하지만 첫 인자에 따라 이후 인자가 달라지거나, 인자 타입에 따라 메서드 동작이 달라지면 코드 가독성이 떨어지며, API 기능을 작성자가 파악하기 어렵게 되니 피하세요.

6.9. 클래스에는 가능하다면 생성자 제공

API에 포함된 클래스에는 적절하다면 생성자를 반드시 제공하세요.

기본적으로 [WEBIDL] 인터페이스는 "생성 불가능" 클래스를 생성합니다. new X()로 인스턴스 생성 시 TypeError가 발생합니다. 생성 가능하게 하려면, 인터페이스에 적절한 생성자 연산을 추가하고, 인스턴스 생성 알고리즘을 정의하세요.

이렇게 하면 자바스크립트 개발자가 테스트, 목(mock) 작성, 해당 클래스를 인자로 받는 서드파티 라이브러리 연동 등 다양한 용도로 인스턴스를 생성할 수 있습니다. 또한 클래스의 서브클래스를 만들 수 있게 되는데, 이는 자바스크립트의 서브클래스 처리 방식 때문에 기본적으로 불가능합니다.

항상 적절한 것은 아닙니다. 예시:

Event 클래스 및 모든 파생 인터페이스는 생성 가능합니다. 이벤트를 처리하는 코드를 테스트할 때 유용하며, 작성자가 해당 이벤트 타입을 처리하는 메서드에 Event를 직접 생성해 전달할 수 있습니다.

Window 클래스는 생성 불가능합니다. 새 window 생성은 특권 작업으로 부작용이 크므로, 대신 window.open() 메서드를 사용합니다.

ImageBitmap 클래스는 생성 불가능합니다. 불변의, 바로 그릴 수 있는 비트맵 이미지를 나타내며, 준비 과정이 비동기로 진행되어야 합니다. 대신 createImageBitmap() 팩토리 메서드로 생성합니다.

DOMTokenList 클래스는 유감스럽게도 생성 불가능합니다. 이로 인해 커스텀 요소에서 토큰 리스트 속성을 DOMTokenList로 노출할 수 없습니다.

여러 생성 불가능 클래스(예: Navigator, History, Crypto)는 싱글턴 객체로서 창별 정보 접근을 나타내므로 생성 불가능합니다. 이런 경우 WebIDL 네임스페이스 기능이 더 적합할 수 있지만, 이 기능들은 네임스페이스 도입 이전에 설계되었고, 네임스페이스만으로는 현재 불가능한 범위를 넘습니다.

API에 이런 싱글턴이 필요하다면, 네임스페이스 사용을 고려하고, 문제 있으면 WebIDL 이슈 등록을 검토하세요.

팩토리 메서드는 생성자를 보완할 수 있지만, 대신 사용하는 것은 일반적으로 피하세요. 팩토리 메서드는 추가 이점이 있을 때 생성자 외에도 포함하는 것이 가치 있을 수 있습니다. 예시로, API에 기반 클래스와 여러 특화 서브클래스가 있고, 팩토리 메서드가 전달된 파라미터에 따라 적절한 서브클래스를 생성하는 경우가 있습니다. 팩토리 메서드는 반환 결과의 가장 가까운 기반 서브클래스의 정적 메서드일 때가 많습니다.

createElement 메서드는 생성자로 구현할 수 없는 팩토리 메서드 예시입니다. 반환값이 Element의 여러 서브클래스일 수 있기 때문입니다.
폐지된 MouseEvent.initMouseEvent() 팩토리 메서드는 MouseEvent 객체만 생성합니다. 원래는 생성 불가능했으나, 기술적 이유는 없었고, 결국 폐지되어 MouseEvent 객체가 생성 가능하게 변경되었습니다.

6.10. 적절할 때 동기적으로 동작

가능하다면, 새로운 API를 설계할 때 동기적 API를 우선하세요. 동기적 API는 사용이 더 간단하고, 함수에 async를 붙이는 등 별도의 인프라 설정이 덜 필요합니다.

API가 아래 기준에 해당하면 일반적으로 동기적이어야 합니다:

6.11. 비동기 API는 Promise로 설계

API 메서드가 비동기여야 한다면, 콜백 함수 대신 Promise를 사용하세요.

웹 플랫폼 전체에서 일관되게 Promise를 사용하면 API를 함께 사용하기 쉽고, Promise를 체이닝할 수 있습니다. Promise를 사용하는 코드는 콜백 함수 사용하는 코드보다 이해하기 쉽습니다.

API가 비동기여야 하는 경우 예시:

참고:

콜백 함수를 언제 사용할지

즉각적인 응답이 필요할 때만 동기 콜백을 사용하세요.

특히 지연에 민감한 동작이 필요한 API에서 동기적 행동이 필요할 때가 있습니다. 예를 들어, 브라우저는 이벤트 버블링이 끝난 직후 어떻게 처리할지 즉시 알아야 하고, 미디어 처리도 항상 다음 작업 경계까지 기다릴 수는 없습니다. 동기 행동은 Promise로 모델링할 수 없습니다. 대신 명시적 콜백이나 이벤트를 사용하세요.

Promise reaction 콜백에서는 고정된 마이크로태스크 수 내에 동작을 요구하지 않을 수 있습니다.

이렇게 하면 Promise 합성(Promise 체이닝, await, 래핑 헬퍼, 로깅, Promise.race(), 여러 .then() 분기 등)이 추가 마이크로태스크를 큐에 넣어도 코드의 동작이 크게 바뀌지 않게 보호할 수 있습니다.

이벤트 핸들러는 동기적으로 호출 및 취소됩니다. 개발자가 event.preventDefault() 또는 fetchEvent.respondWith()로 응답 객체를 선택해야 한다면, Promise.then()에 넘기는 reaction이 아니라 콜백이어야 합니다.

이벤트가 아닌 예시로, captureController.setFocusBehavior() 메서드는 사용자가 화면 공유할 창을 앞으로 내보냅니다. 앱은 선택된 창에 따라 결정할 수 있습니다. 여기서 지연을 허용하면 사이트의 클릭재킹 공격 위험이 있습니다.

Promise reaction 마이크로태스크에서 동기적으로 메서드를 호출하게 강제하는 대신, 실제로는 동일한 promise reaction 작업에서 호출하도록, 1초 제한을 두는 방식으로 해결했습니다.

6.12. 비동기 API/작업 취소는 AbortSignal 사용

비동기 메서드가 취소 가능하다면, 옵션 딕셔너리의 일부로 AbortSignal을 전달할 수 있게 하세요.

const controller = new AbortController();
const signal = controller.signal;
geolocation.read({ signal });

AbortSignal을 일관되게 사용하면 비동기 작업 취소 코드가 복잡하지 않게 됩니다.

예를 들어 여러 작업에 단일 AbortSignal을 사용하고, AbortController로 모두 한 번에 취소(예: 사용자가 "취소" 버튼 클릭, 싱글페이지 앱에서 네비게이션 발생 등)하는 패턴이 있습니다.

취소가 항상 보장되지 않아도, AbortController를 여전히 사용할 수 있습니다. abort() 호출은 "요청"일 뿐 "보장"이 아닙니다.

6.13. 상수와 열거형에는 문자열 사용

상수나 열거형 값에는 문자열을 사용하세요.

문자열은 개발자가 값을 확인하고, 해당 값을 사용하는 코드를 읽기 쉽게 만듭니다. 자바스크립트 엔진에서는 문자열 대신 정수를 사용해도 성능 이점이 없습니다. WebIDL enum 타입의 값은 이 원칙에 따라 문자열입니다.

여러 속성의 조합 상태(다른 언어라면 비트마스크로 표현)를 나타내야 한다면, 딕셔너리 객체를 사용하세요. 이 객체는 비트마스크 값처럼 쉽게 전달할 수 있습니다.

전용 Worker의 타입은 WorkerType 열거형 인자로 설정합니다. 값은 "classic"과 "module" 두 가지이며, Boolean 타입 대신 쓸 수도 있었지만, 문자열 사용이 코드 가독성 향상에 아무런 비용이 들지 않습니다.
XMLHttpRequestreadyState getter는 열거형 정수 값을 반환합니다. 이 API를 사용하는 코드는 해당 API를 잘 알아야만 이해할 수 있습니다.

6.14. 동기와 비동기 메서드가 모두 필요하다면, 동기는 예외

같은 목적에 대해 동기/비동기 메서드가 모두 필요한 드문 경우에는, 기본값은 비동기, 동기는 예외로 하세요.

현재 웹 플랫폼의 일부 영역은 비동기 지원이 부족합니다. 그래서 비동기 메서드 외에 동기 메서드 지원이 사용성 면에서 유리할 수 있습니다. 이런 경우를 예외로 취급하고, 웹 플랫폼 기능이 진화함에 따라 동기 메서드의 폐기 경로를 명확히 하세요.

대부분의 경우, 동기 버전은 Sync() 접미사를 붙여 구분하세요.

예시로, Web Neural Network API두 방식 모두 지원해야 했지만, 플랫폼 제약을 우회하기 위해 비동기를 기본으로, 동기는 예외로 처리했습니다.
현재로서, 이 원칙이 적용되는 대표적 사례는 자바스크립트와 WASM 경계에서 설계가 필요할 때입니다. Promise 통합 은 아직 개발 중입니다.

6.15. 바이트 배열 출력은 Uint8Array 사용

API가 바이트 배열을 반환한다면, Uint8Array로 반환하세요. ArrayBuffer 대신.

ArrayBuffer는 직접 읽을 수 없으므로, 개발자가 Uint8Array 뷰를 생성해야 데이터를 읽을 수 있습니다. Uint8Array를 바로 제공하면 불필요한 노력을 줄일 수 있습니다.

버퍼 내 바이트가 다른 TypedArray 타입으로 해석된다면, 그 타입을 대신 제공하세요. 예를 들어 바이트가 Float32 값을 나타내면 Float32Array를 사용하세요.

6.16. 부작용이 있는 함수는 undefined 반환

함수의 목적이 값을 계산하는 것이 아니라 부작용을 유발하는 것이라면, 해당 함수는 undefined를 반환하도록 명세하세요.

사이트가 이런 반환값에 의존할 가능성이 낮으므로, 향후 실제 반환값이 필요한 사용 사례가 발견되면 함수 반환값을 쉽게 바꿀 수 있습니다.

HTMLMediaElementplay() 메서드는 미디어 요소의 상태를 변경하는 것이 목적이었으므로 원래 undefined를 반환하도록 정의되었습니다.

미디어 재생 요청은 다양한 방식으로 실패할 수 있으므로, play()Promise 반환으로 변경되었습니다. 만약 API가 처음부터 undefined가 아닌 값(예: 미디어 요소 자체, 체이닝에 흔히 쓰이는 패턴 등)을 반환하도록 정의되어 있었다면, 이 방식으로 API 사용성을 개선하는 것이 하위 호환성을 깨뜨릴 수 있었을 것입니다.

참고:

7. 이벤트 설계

7.1. 일회성 이벤트에는 Promise 사용

Promise를 사용하는 명세 작성 가이드일회성 이벤트 권고를 따르세요.

7.2. 취소 가능한 이벤트에는 Promise 사용 금지

콜백 함수 사용 시점 참고.

7.3. 이벤트는 관련 Promise resolve 전에 발생해야 함

Promise 기반 비동기 알고리즘이 이벤트를 디스패치한다면, Promise resolve 이후가 아니라 resolve 전에 이벤트를 디스패치하세요.

Promise가 resolve되면, 마이크로태스크가 큐에 들어가 reaction 콜백을 실행합니다. 마이크로태스크는 자바스크립트 스택이 비게 될 때 처리됩니다. 이벤트 디스패치는 동기적으로 이루어지며, 각 리스너 사이에 자바스크립트 스택이 비워집니다. 결과적으로, Promise를 이벤트보다 먼저 resolve하면, Promise에 반응하는 마이크로태스크가 이벤트 리스너 1번과 2번 사이에 실행될 수 있습니다.

이벤트를 먼저 디스패치하면 이런 interleave를 방지할 수 있습니다. 모든 이벤트 리스너가 실행된 뒤에야 Promise reaction 콜백이 실행됩니다.

7.4. 자체 이벤트 리스너와 유사 인프라 발명 금지

알림을 생성하는 프로세스를 시작/중지할 수 있는 API를 만들 때는, 기존 이벤트 인프라를 사용하여 알림 수신을 지원하세요. 프로세스 시작/중지를 위한 별도의 API 제어를 만드세요.

예를 들어, Web Bluetooth API는 BluetoothRemoteGATTCharacteristic 글로벌 객체에 startNotifications() 메서드를 제공합니다. 이 메서드는 객체를 "활성 알림 컨텍스트 집합"에 추가합니다.

사용자 에이전트가 Bluetooth 디바이스에서 알림을 받으면, 해당 알림 컨텍스트 집합의 BluetoothRemoteGATTCharacteristic 객체에 이벤트를 발생시킵니다.

참고:

7.5. 항상 이벤트 핸들러 속성 추가

API에 새로운 이벤트 타입을 추가하면, 해당 이벤트를 처리할 수 있는 모든 EventHandler 인터페이스에 onyourevent 이벤트 핸들러 IDL 속성을 추가하세요.

이벤트 핸들러 IDL 속성을 계속 정의하는 것이 중요한 이유:

일관성을 위해, 이벤트를 HTML 및 SVG 요소가 처리해야 한다면 해당 이벤트 핸들러 IDL 속성을 관련 요소 인터페이스가 아니라 GlobalEventHandlers 인터페이스 믹스인에 추가하세요. 마찬가지로 WindowEventHandlers에 추가하세요. Window에 직접 추가하지 말고.

7.6. 알림에는 이벤트 사용

이벤트는 변화를 유발하는 데 쓰지 말고, 이미 발생한 변화를 알리는 데만 사용하세요.

창 크기 변경 시, resize 이벤트가 Window 객체에 발생합니다.

이벤트를 가로채서 크기 변경을 막을 수 없고, 직접 resize 이벤트를 발생시켜 창 크기를 바꿀 수도 없습니다. 이벤트는 단지 이미 크기가 변경되었음을 알리는 용도입니다.

7.7. 재귀 가능성 방지

API에 긴 시간 실행되거나 복잡한 알고리즘이 포함되면, 이미 실행 중일 때 다시 알고리즘을 호출하지 않도록 하세요.

API 메서드가 긴 알고리즘을 시작하게 하면, 이벤트로 알고리즘 진행 상황을 사용자 코드에 알릴 수 있습니다. 하지만 이벤트를 처리하는 사용자 코드가 같은 API 메서드를 다시 호출하면, 복잡한 알고리즘이 재귀적으로 실행될 수 있습니다. 같은 이벤트가 다시 발생해 같은 핸들러가 반복 실행될 수 있습니다.

이를 방지하려면, "재귀" 호출 시 API 메서드가 즉시 반환되도록 하세요. 이 기법을 "가드"라고 합니다.

AbortSignaladd, remove, signal abort 모두 신호가 aborted 상태인지 확인하는 것으로 시작합니다. 이미 aborted라면 나머지 알고리즘은 실행하지 않습니다.

이 경우 중요한 복잡성은 signal abort 단계에서 실행되는 알고리즘에 있습니다. 이 단계는 addremove 메서드가 관리하는 알고리즘 집합을 반복합니다.

예를 들어 ReadableStreamPipeTo 정의는 add 알고리즘을 AbortSignal의 집합에 추가해 signal abort 단계에서 실행되도록 합니다. abort()AbortController에 호출합니다.

이 알고리즘은 promise를 resolve해 코드 실행을 유발할 수 있으며, 여기에는 AbortSignal의 다른 메서드 호출도 포함될 수 있습니다. signal abort 단계에서는 알고리즘 집합을 반복하므로, 실행 중에는 해당 집합을 수정할 수 없어야 합니다.

signal abort가 재귀 호출을 유발하는 코드를 트리거했으므로, 이미 signal abort 단계가 실행 중이면, 해당 단계를 다시 실행하지 않도록 해 재귀를 방지해야 합니다.

참고: 조기 종료에 대한 주의점: 종료되는 알고리즘이 중요한 상태 일관성을 보장해야 한다면, 알고리즘 조기 종료 전에 상태도 반드시 조정하세요. 그렇지 않으면 일관성 없는 상태와 구현 시 사용자에게 노출되는 버그가 생길 수 있습니다.

참고: 조기 종료 시 예외 던지기는 신중히 하세요. 개발자가 알고리즘을 호출하는 상황과, 해당 예외를 처리할 것으로 기대할 만한지 고려하세요. 예를 들어, 이 알고리즘에서 해당 예외가 유일한가?

항상 이런 방식으로 "가드"할 수 있는 것은 아닙니다. 알고리즘에 진입로가 너무 많아 모든 진입점을 검사할 수 없다면, 저자 코드 호출을 이후 작업이나 마이크로태스크로 지연하는 것도 방법입니다. 이렇게 하면 재귀 스택은 피할 수 있지만, 후속 작업이 무한 반복될 위험은 해결할 수 없습니다.

이벤트 지연은 보통 "작업 큐에 이벤트 발생..."으로 명세됩니다.

알고리즘이 다른 스레드나 프로세스에서 실행된다면 이벤트를 반드시 지연하세요. 이렇게 하면 이벤트가 작업 큐의 올바른 작업에서 처리될 수 있습니다.

"가드"와 "지연" 기법 모두 장단점이 있습니다.

"가드" 알고리즘의 장점:

이벤트를 지연하면:

참고: 이 섹션에서 설명한 재귀 가능성 노출 이벤트는 "동기 이벤트"라고 불리기도 했지만, 이벤트를 비동기로 디스패치할 수 있다고 오해하게 하므로 권장되지 않습니다. 모든 이벤트는 동기적으로 디스패치됩니다. "비동기 이벤트"란 실제로는 이벤트 발생을 지연시키는 경우를 의미합니다.

7.8. 상태에는 일반 Event 사용

가능하다면, 지정된 type을 가진 일반 Event를 사용하고, 상태 정보는 target 객체에 담으세요.

새로운 Event의 하위 클래스를 만드는 일은 대부분 필요하지 않습니다.

7.9. 이벤트와 옵저버를 적절히 사용

일반적으로 EventTarget과 알림 Event를 사용하고, 옵저버 패턴은 EventTarget이 해당 기능에 적합하지 않을 때만 사용하세요.

EventTarget을 사용하면 once와 같은 공유 기반 클래스 개선의 혜택을 받을 수 있습니다.

이벤트 사용에 문제가 있거나 재귀가 피할 수 없이 생긴다면 옵저버 패턴을 사용하는 것을 고려하세요.

MutationObserver, Intersection Observer, Resize Observers, IndexedDB Observers 모두 옵저버 패턴 예시입니다.

MutationObserver는 개발자들이 발견한 뒤 DOM Mutation Events(구식)를 대체하게 되었습니다.

Mutation Observer의 장점:

참고: 이벤트도 알림을 묶어서 전달할 수 있지만, DOM Mutation Events는 그렇게 설계되지 않았습니다. 이벤트가 항상 이벤트 전파에 참여할 필요는 없지만, DOM Node의 이벤트는 보통 전파됩니다.

옵저버 패턴 동작 방식:

IntersectionObserver 사용 예시:
function checkElementStillVisible(element, observer) {
    delete element.visibleTimeout;

    // 아직 작업 큐에 남아 있을 수 있는 관측 결과 처리
    processChanges(observer.takeRecords());

    if ('isVisible' in element) {
        delete element.isVisible;
        logAdImpressionToServer();

        // 해당 요소 관찰 중단
        observer.unobserve(element);
    }
}

function processChanges(changes) {
    changes.forEach(function(changeRecord) {
        var element = changeRecord.target;
        element.isVisible = isVisible(changeRecord.boundingClientRect,
                                      changeRecord.intersectionRect);
        if ('isVisible' in element) {
            // 요소가 보이게 됨
            element.visibleTimeout = setTimeout(() => {
                checkElementStillVisible(element, observer);
            }, 1000);
        } else {
            // 요소가 숨겨짐
            if ('visibleTimeout' in element) {
                clearTimeout(element.visibleTimeout);
                delete element.visibleTimeout;
            }
        }
    });
}

// 콜백 및 옵션으로 IntersectionObserver 생성
var observer = new IntersectionObserver(processChanges,
                                        { threshold: [0.5] });

// "ad" 요소 관찰 시작
var ad = document.querySelector('#ad');
observer.observe(ad);

(예제 코드는 IntersectionObserver explainer에서 참고함.)

옵저버 패턴을 쓰려면 다음을 정의해야 합니다:

  1. 새로운 옵저버 객체 타입

  2. 관찰 옵션용 객체 타입

  3. 관찰될 기록용 객체 타입

이 추가 작업의 대가는 다음과 같은 장점들입니다:

ObserverEventTarget의 공통점:

IntersectionObserverEventTarget 서브클래스인 가상 예시:
const io = new ETIntersectionObserver(element, { root, rootMargin, threshold });

function listener(e) {
    for (const change of e.changes) {
        // ...
    }
}

io.addEventListener("intersect", listener);
io.removeEventListener("intersect", listener);

Observer 버전과 비교:

Observer 버전과 공통점:

이러한 특성은 두 방식 모두로 구현할 수 있습니다.

참고:

8. Web IDL, 타입, 단위

8.1. 숫자 타입을 적절히 사용

설계 중인 API에서 숫자를 사용한다면, 특별한 이유가 없는 한 아래 [WEBIDL] 숫자 타입 중 하나를 사용하세요:

unrestricted double

무한대와 NaN을 포함한 모든 자바스크립트 숫자

double

무한대와 NaN을 제외한 모든 자바스크립트 숫자

[EnforceRange] long long

-263 ~ 263 범위의 자바스크립트 숫자를 정수로 반올림. 범위를 벗어나면 바인딩에서 TypeError 발생.

[EnforceRange] unsigned long long

0 ~ 264 범위의 자바스크립트 숫자를 정수로 반올림. 범위를 벗어나면 바인딩에서 TypeError 발생.

자바스크립트에는 단 하나의 숫자 타입 Number만 있습니다: IEEE 754 배정밀도 부동소수점(±0, ±Infinity, NaN 포함). [WEBIDL] 숫자 타입은 모든 자바스크립트 숫자를 특정 성질의 부분집합으로 변환하는 규칙입니다. 이 규칙은 숫자가 IDL에서 정의된 인터페이스(메서드, 속성 setter 등)로 전달될 때 실행됩니다.

숫자에 추가 규칙을 적용해야 한다면, 알고리즘에서 명시하세요.

WEBIDL 규칙에 따라 자바스크립트 숫자를 더 작은 비트로 변환할 때(예: octet [0, 255]), 자바스크립트 숫자의 모듈로 연산이 수행됩니다. 예를 들어, 300을 octet로 변환하면 300 mod 255 = 45가 되어, 예기치 않은 결과가 나옵니다.

대신 [EnforceRange] octet을 쓰면 범위를 벗어나면 TypeError를 던질 수 있고, [Clamp] octet을 쓰면 값을 범위로 고정(예: 300→255)할 수 있습니다.

이 방식은 short, long 등 다른 짧은 타입에도 적용됩니다.

bigint는 253 초과 또는 -253 미만 값을 기대할 때만 사용하세요.

API가 BigIntNumber를 동시에 지원하지 마세요. 두 타입을 다형성으로 지원하거나, 각각 별도 API를 만드는 것은 암묵적 변환으로 정밀도 손실 위험이 있어 BigInt의 목적을 훼손합니다.

8.2. 문자열을 적절히 표현

이 섹션은 문자열 정의[international-specs]의 일관성을 유지하도록 작성되었습니다. 문서가 발전하며 불일치가 생기면 제보해 주세요.

웹 플랫폼 기능이 문자열을 다룬다면, 특별한 이유가 없는 한 DOMString을 사용하세요.

대부분의 문자열 연산은 문자열 내 코드 유닛을 해석할 필요가 없으므로, DOMString이 가장 적합합니다. 아래 설명한 특정 상황에서는 USVString이나 ByteString을 써야 할 수 있습니다. [INFRA] [WEBIDL]

USVString스칼라 값 문자열을 나타내는 Web IDL 타입입니다. 가장 일반적인 알고리즘이 스칼라 값을 다루거나 (예: 퍼센트 인코딩), 입력에 서로게이트가 있으면 처리할 수 없는 작업 (예: 문자열을 네이티브 플랫폼 API로 전달하는 경우)에는 USVString을 써야 합니다.

IDL 속성 반영 중 내용 속성이 URL을 담도록 정의된 경우 (예: href) USVString을 써야 합니다. [HTML]

ByteString은 HTTP 등 바이트와 문자열을 구분하지 않는 프로토콜 데이터를 표현할 때만 써야 하며, 범용 문자열 타입이 아닙니다. sequence of bytes를 표현해야 한다면 Uint8Array를 쓰세요.

8.3. 시간 측정에는 밀리초 사용

API 설계 시 시간 측정값을 받는다면, 시간 단위를 밀리초로 표현하세요.

초(혹은 다른 단위)가 해당 API의 도메인에서는 더 자연스럽더라도, 밀리초를 고수하면 API 간 상호운용성이 보장됩니다. 즉, 작성자가 한 API에서 사용한 값을 다른 API에서 사용하려고 변환할 필요가 없고, 어디에 어떤 시간 단위를 써야 하는지 따로 기억할 필요도 없습니다.

이 관례는 setTimeout()Date API에서 시작되어 지금까지 이어지고 있습니다.

참고: 고해상도 시간은 보통 부동소수점 값으로 분수 밀리초로 표현하며, 나노초 같은 더 작은 정수 단위로 표현하지 않습니다.

8.4. 시간 및 날짜는 적절한 타입으로 표현

플랫폼에서 날짜-시간을 표현할 때는 DOMHighResTimeStamp 타입을 사용하세요. DOMHighResTimeStamp 는 사용자 시간 설정에 상관없이 타임스탬프 비교를 할 수 있게 해줍니다.

DOMHighResTimeStamp 값은 밀리초 단위의 시간값을 나타냅니다. 자세한 내용은 [HIGHRES-TIME] 참고.

구체적인 날짜-시간 값 표현에는 자바스크립트 Date 클래스를 사용하지 마세요. Date 객체는 변경 가능(값을 바꿀 수 있음)하며, 불변으로 만들 방법이 없습니다.

Date 를 사용하면 안 되는 이유에 대한 배경은 다음을 참고하세요:

8.5. 오류에는 Error 또는 DOMException 사용

웹 API에서 오류는 ECMAScript의 에러 객체(예: Error) 또는 DOMException으로 표현하세요. 이는 예외, Promise reject 값, 속성 등 모두에 적용됩니다.

9. 디바이스 또는 브라우저 기능 접근을 감싸는 API

웹 플랫폼에서는 디바이스와 상호작용하는 새로운 API들이 개발되고 있습니다. 예를 들어, 작성자는 웹을 통해 마이크와 카메라, 제네릭 센서(예: 자이로스코프, 가속도계), Bluetooth, USB 연결 주변기기, 자동차 등과 연결하고 싶어 합니다.

이는 호스트 시스템이나 외부 서비스가 선택적으로 제공하는 기능에도 동일하게 적용됩니다. 여기에는 사용자가 기능 접근에 비용을 지불해야 하는 경우도 포함됩니다.

이런 기능은 기본 운영체제나 네이티브 서드파티 라이브러리에서 제공할 수 있습니다. API는 네이티브 기능을 "랩"하면서 브라우저에 안전하게 노출하되, 복잡성을 크게 늘리지 않는 추상화를 제공합니다. 이러한 API를 래퍼(wrapper) API라고 합니다.

이 섹션에서는 해당 기능용 API를 설계할 때 고려해야 할 원칙을 안내합니다.

9.1. 불필요한 기능 정보 노출 금지

데이터 최소화 원칙에 따라, 웹사이트가 기능 정보를 필요로 한다면 반드시 최소한의 데이터만 노출하세요.

먼저, 정보를 정말 노출해야 하는지 신중히 생각하세요. 사용자 요구가 더 적은 권한의 API로도 충족될 수 있는지 고려하세요.

디바이스 존재, 추가 정보, 디바이스 식별자 노출은 모두 사용자의 프라이버시 침해 위험을 높입니다.

사용자가 디바이스나 기능 접근을 거부할 경우, 해당 기능이 존재하는지 여부가 노출되지 않아야 합니다. 이런 상황에서 정보 유출을 줄이는 것이 해당 기능을 허용할 때보다 더 중요합니다.

더 구체적인 정보가 공유될수록 사이트에서 얻을 수 있는 지문 채취 데이터가 커지고, 일부 정보가 특정 사용자에게 민감한 정보일 확률도 높아집니다.

더 약한 API로 설계할 수 없다면, 디바이스 정보 노출 시 다음 가이드라인을 따르세요:

식별자 정보 최소화

웹 플랫폼에 노출되는 디바이스 식별자에는 최대한 식별 가능한 정보를 적게 포함하세요. 식별 정보란 브랜드, 제조사, 모델명 등입니다. 보통은 무작위로 생성된 식별자를 대신 사용할 수 있습니다. 식별자가 추측 불가능하고, 재사용되지 않도록 하세요.

사용자 통제 권한 유지

사용자가 브라우징 데이터를 삭제하면 저장된 디바이스 식별자도 반드시 삭제되도록 하세요.

민감 정보는 사용자 권한 뒤에 숨기기

익명 식별자 생성이 불가능하다면 접근을 제한하세요. 사용자가 해당 정보에 접근하는 웹페이지에 의미 있는 동의를 제공할 수 있도록 하세요.

동일 오리진 모델과 연결

동일 물리 디바이스라도 접근하는 오리진마다 별도의 식별자를 생성하세요.

같은 오리진에서 동일 디바이스를 여러 번 요청하면 (사용자가 브라우징 데이터를 지우지 않는 한) 항상 동일한 식별자를 반환하세요. 이렇게 하면 작성자가 동일 디바이스를 여러 번 중복해서 다루지 않아도 됩니다.

필요할 때만 영속적 식별자 제공

디바이스 식별자 생성에 시간이 많이 걸린다면, 작성자가 한 세션에서 생성된 식별자를 나중에 다시 쓸 수 있도록 하세요. 이는 식별자 생성 절차가 동일 디바이스·동일 오리진에 대해 항상 같은 값을 내도록 하면 됩니다.

참고:

9.2. 디바이스 선택·열거 API 노출 시 주의

디바이스 열거를 피할 방법을 찾아보세요. 피할 수 없다면 가능한 한 정보를 최소로 노출하세요.

API가 여러 디바이스의 존재, 기능, 식별자를 노출한다면, § 9.1 불필요한 기능 정보 노출 금지에 나온 위험이 디바이스 개수만큼 늘어납니다. 같은 이유로, 사용자 요구가 더 약한 API로도 충족되는지 반드시 고려하세요. [LEAST-POWER]

API 목적이 사용자가 특정 종류의 사용 가능 디바이스 중 디바이스를 선택할 수 있게 하는 것이라면, 스크립트에 목록을 노출할 필요가 없을 수 있습니다. 사용자 에이전트가 제공하는 디바이스 선택기를 호출하는 API면 충분할 수 있습니다. 이런 API는:

사용자가 디바이스를 선택할 수 있는 API를 설계할 때, 선택할 수 있는 디바이스가 존재한다는 사실을 노출해야 할 수도 있습니다. 이렇게 하면 웹사이트에 사용자 환경에 대한 지문 채취 데이터가 1비트 노출되며, 이런 기능이 없는 API보다는 안전하지 않습니다.

RemotePlayback 인터페이스는 사용 가능 원격 재생 디바이스 목록을 노출하지 않습니다. 대신, 사용자 에이전트가 제공하는 디바이스 선택기에서 사용자가 디바이스를 하나 선택할 수 있게 합니다.

웹사이트는 원격 재생 디바이스가 사용 가능한지 여부를 감지할 수 있으므로, 사용자가 디바이스 선택기를 표시할 수 있는 UI 컨트롤을 표시/숨길 수 있습니다.

이렇게 웹사이트에 한 비트의 추가 정보를 허용하면, API가 작성자가 UI를 덜 혼란스럽게 만들 수 있게 해줍니다. 최소 한 개의 디바이스가 있을 때만 선택기 트리거 버튼을 표시할 수 있습니다.

디바이스 목록을 반드시 노출해야 한다면, 사용자 요구를 충족할 최소 부분집합만 노출하도록 하세요.

예를 들어, 웹사이트가 필터·제약된 목록만 요청할 수 있는 API는 디바이스 수를 줄이는 한 가지 방법입니다. 하지만 작성자가 다양한 제약으로 여러 번 요청할 수 있다면 결국 전체 목록에 접근할 수 있습니다.

마지막으로, 특정 종류의 디바이스 전체 목록을 반드시 노출해야 한다면, 반드시 디바이스 정렬 순서를 엄격히 정의하세요. 이렇게 하면 상호운용성 문제를 줄이고, 지문 채취 위험도 완화할 수 있습니다. (정렬 순서가 추가 정보를 노출할 수 있으니, Mitigating Browser Fingerprinting in Web Specifications § 6.2 Standardization 참고)

참고: API가 구현 정의 순서로 디바이스 전체 목록을 노출하지 않아야 하지만, 웹 호환성 때문에 필요할 수 있습니다.

9.3. 기능 자체가 아닌 사용자 요구 기반으로 설계

웹에 새 네이티브 기능을 도입할 때는 사용자 요구에 맞게 노출하세요.

기존 네이티브 API를 웹에 그대로 옮기는 것은 피하세요.

대신 네이티브 API가 제공하는 기능과 해당 기능이 해결하는 사용자 요구를 고려해, 실제 사용자 요구를 충족하는 API를 설계하세요. 구현이 기존 네이티브 API에 의존하더라도 마찬가지입니다.

기본 네이티브 API의 정확한 생명주기와 데이터 구조 노출은 특히 주의하세요. 가능하다면 새로운 하드웨어 유연성도 고려하세요.

즉, 새로 제안된 API는 하드웨어, 디바이스, 네이티브 API가 오늘날 동작하는 방식이 아니라 어떻게 사용될 목적으로 설계됐는지 중심으로 만들어져야 합니다.

9.4. 안전성을 사전에 고려

네이티브 기능을 웹 플랫폼에 도입할 때는 방어적으로 설계하려고 노력하세요.

네이티브 기능을 웹에 도입하면 많은 함의가 따릅니다. 사용자는 자신의 컴퓨터에 특정 기능이 있다는 사실을 웹사이트가 알기를 원하지 않을 수도 있습니다. 따라서 논리적 오리진 경계 밖의 모든 접근은 권한 제어가 필요합니다.

예를 들어, 디바이스가 상태를 저장할 수 있고, 그 상태를 여러 오리진이 동시에 읽을 수 있다면, 해당 상태를 읽고 쓸 수 있는 API 집합은 웹의 오리진 모델을 무너뜨리는 사이드 채널이 됩니다.

이런 이유로, 디바이스가 비독점 접근을 허용하더라도 오리진별 독점 접근을 강제하거나, 심지어 현재 활성 탭에만 제한하는 것도 고려해야 합니다.

또한, API는 디바이스가 분리되는 물리적 장애 등도 애플리케이션이 우아하게 처리할 수 있도록 설계해야 합니다.

9.5. 웹 플랫폼 원칙을 적용해 네이티브 API 래핑

네이티브 운영체제 API를 웹에 적용할 때는 웹 플랫폼 원칙을 고려해 새로운 웹 API를 설계하세요.

웹 API는 여러 플랫폼에서 구현 가능해야 함

래퍼 API를 설계할 때 다양한 플랫폼이 해당 기능을 어떻게 제공하는지 고려하세요.

이상적으로는 모든 구현이 완전히 동일하게 동작해야 하지만, 경우에 따라 일부 플랫폼에서만 동작하는 옵션을 노출해야 할 수도 있습니다. 이런 경우가 생기면, 작성자가 모든 플랫폼에서 동작하는 코드를 어떻게 작성해야 하는지 반드시 안내하세요. § 2.6 새로운 기능은 감지 가능해야 함 참고.

기저 프로토콜은 개방적이어야 함

외부 하드웨어나 서비스와 교환이 필요한 API는 폐쇄적 또는 독점적 프로토콜에 의존하지 않아야 합니다. 비공개 프로토콜에 의존하면 웹의 개방성을 훼손합니다.

사용자가 오프라인일 때도 API가 동작해야 함

API가 원격 서버가 제공하는 서비스에 의존한다면, 사용자가 어떤 이유로든 원격 서버에 접근할 수 없을 때도 API가 제대로 동작하는지 확인하세요.

추가 지문 채취 면적 피하기

래퍼 API가 의도치 않게 더 넓은 지문 채취 면적을 노출할 수 있습니다. 추가 정보는 TAG의 비인가 추적 권고를 참고하세요.

10. 기타 API 설계 고려사항

10.1. 새로운 기능에 대한 폴리필 지원

폴리필은 웹 플랫폼에 새로운 기능을 도입하는 데 큰 도움이 될 수 있습니다. Technical Architecture Group의 Polyfills and the Evolution of the Web 관련 권고는 새로운 기능 개발 시 꼭 고려해야 할 가이드라인을 제공합니다. 주요 내용은 다음과 같습니다:

10.2. 가능하다면 API를 전용 워커에 노출

기능을 노출할 때, 해당 기능을 전용 워커(DedicatedWorkerGlobalScope 인터페이스)를 통해 노출하는 것이 합리적인지 고려하세요.

많은 기능이 전용 워커에서 바로 동작할 수 있는데, 이를 지원하지 않으면 사용자가 논블로킹 방식으로 코드를 실행할 수 있는 능력이 제한될 수 있습니다.

전용 워커에 기능을 노출할 때는 몇 가지 어려움이 있을 수 있습니다. 특히 권한 요청, 선택기/픽커 표시 등 사용자 입력이 필요한 기능일 경우입니다. 이러한 어려움 때문에 명세 작성자가 전용 워커 지원을 꺼릴 수 있지만, 향후 전용 워커 노출이 어렵지 않게 하도록 항상 이를 염두에 두고 설계하는 것이 좋습니다.

10.2.1. 일부 API는 전용 워커에만 노출해야 함

개발자는 복잡한 코드보다 단순한 코드를 선호합니다. API가 허용하는 가장 간단한 방식으로 더 많이 사용됩니다.

렌더링을 차단하는 기능 추가는 반드시 피해야 합니다. § 3.4 렌더링을 차단하는 기능은 피하기

API를 가장 쉽게 사용할 때 렌더링 차단이나 “jank”가 발생한다면, 사용자 경험은 저하됩니다. (이 문제는 저사양 기기에서 더 두드러지며, 이런 기기를 사용하는 소외·취약 계층 사용자가 많습니다. 참고: 웹은 모두의 것입니다.)

따라서, 의도대로 사용하면 메인 스레드를 자주 차단하는 API는 Window 인터페이스에 노출하지 않아야 합니다. 대신 DedicatedWorkerGlobalScope 인터페이스에만 노출해, 웹 개발자에게 가장 좋은 사용자 경험을 제공하는 "쉬운" 사용 경로가 되도록 해야 합니다.

ScriptProcessorNode 는 Web Audio API에서 AudioWorklet로 대체되었습니다. 이는 ScriptProcessorNode를 메인 스레드에서 사용할 때 자주 사용자 경험이 나빠졌기 때문입니다. [WebAudio]

10.3. 순수 계산 기능만 모든 환경에 노출

기능을 노출할 때, 해당 기능을 모든 환경에 노출하는 것이 합리적인지 고려하세요 ([Exposed=*] 어노테이션 또는 모든 글로벌 스코프 인터페이스에 포함).

순수 계산 기능만 모든 환경에 노출해야 합니다. 즉, I/O를 수행하지 않으며 사용자 에이전트나 사용자의 기기 상태에 영향을 주지 않습니다.

TextEncoder 인터페이스는 문자열을 UTF-8 바이트로 변환합니다. 순수 계산 인터페이스로 자바스크립트 언어 기능으로써 매우 유용하므로 모든 환경에 노출해야 합니다.

localStorage는 사용자 에이전트의 상태에 영향을 주므로 모든 환경에 노출해서는 안 됩니다.

기술적으로 console은 사용자 에이전트 상태(개발자 도구에 로그 메시지 표시) 또는 사용자 기기 상태(로그 파일 기록)에 영향을 줄 수 있습니다. 하지만 이는 실행 중인 코드에서 관찰할 수 없으며, console을 어디서나 사용할 수 있는 실용성이 단점을 능가합니다.

또한 이벤트 루프에 의존하는 것은 모든 환경에 노출해서는 안 됩니다. 모든 글로벌 스코프에 이벤트 루프가 있는 것은 아닙니다.

timeout 메서드는 AbortSignal의 일부로 이벤트 루프에 의존하므로 모든 환경에 노출해서는 안 됩니다. AbortSignal의 나머지 기능은 순수 계산이므로 어디서나 노출해야 합니다.

[Exposed=*] 어노테이션도 신중하게 적용해야 합니다. 만약 어떤 기능이 다른 환경에 노출되지 않은 기능 없이는 별로 쓸모가 없다면, 해당 기능도 기본적으로 노출하지 않는 것이 좋습니다.

Blob 인터페이스는 순수 계산 기능이지만, Blob 객체는 대부분 I/O용이므로 보수적 노출 원칙에 따라 어디서나 노출하지 않아야 합니다.

10.4. 새 데이터 포맷은 올바르게 추가

새로운 데이터 포맷을 추가할 때는 반드시 대응하는 MIME 타입을 정의하고, 기존 API가 해당 타입을 지원하도록 확장하세요.

웹에서 새로운 기능이 새로운 데이터 포맷 추가를 포함하는 경우가 있습니다. 이는 이미지, 비디오, 오디오, 텍스트 등 브라우저가 처리해야 하는 모든 유형일 수 있습니다. 새로운 포맷에는 엄격히 검증된 표준 MIME 타입을 사용하세요. 텍스트를 포함하는 새로운 포맷은 반드시 UTF-8 인코딩만 지원해야 합니다.

레거시 미디어 포맷은 MIME 타입을 엄격히 적용하지 않거나 헤더를 훔쳐보는 방식에 의존하기도 하지만, 이는 대부분 레거시 호환성 때문이며 새로운 포맷에서는 기대하거나 구현해서는 안 됩니다.

명세 작성자는 새로운 포맷을 기존 API에 통합해야 하며, 브라우저 입장에서 인그레스(예: ReadableStream에서 디코딩), 이그레스(예: WritableStream으로 인코딩) 지점에서 모두 safelist에 포함되어야 합니다.

예를 들어 이미지를 웹 플랫폼에 추가한다면, 먼저 해당 포맷의 MIME 타입을 추가하세요. 그 후 해당 이미지 포맷을 HTMLImageElement에서 디코딩(및 인코딩)할 수 있도록 디코더를 추가해야 하며, HTMLCanvasElement.toBlob(), HTMLCanvasElement.toDataURL() 등 이그레스 지점에서도 지원을 추가해야 합니다.

레거시 이유로 브라우저는 MIME 타입 스니핑을 지원하지만, 패턴 매칭 알고리즘 확장은 보안상의 이유로 권장하지 않으며, 새로운 포맷에는 엄격한 MIME 타입 적용을 권장합니다.

새 MIME 타입은 명세가 있어야 하며 Internet Assigned Numbers Authority (IANA)에 등록되어야 합니다.

10.5. 새로운 매니페스트 대신 기존 매니페스트 확장

기능에 매니페스트가 필요하다면, 기존 매니페스트 스키마를 확장할 수 있는지 조사하세요.

새 웹 기능은 자체적으로 완결되고 자기 기술적이어야 하며, 이상적으로는 추가적인 매니페스트 파일이 필요하지 않아야 합니다. 기존 매니페스트 파일 예시는 다음과 같습니다:

기존 매니페스트 파일 확장을 권장합니다. 가능하면 원래 명세에 변경사항을 반영하거나, 최소한 명세 편집자와 확장 논의를 하세요. 이러한 논의를 하면 더 나은 설계가 나오고 플랫폼 통합도 더 잘될 가능성이 높습니다.

매니페스트의 새로운 키와 값을 설계할 때는 반드시 필요성(실제 활용 시나리오)을 확인하세요. 유사한 키가 이미 있는지도 꼭 확인하세요. 기존 키/값 쌍이 필요한 기능을 어느 정도 커버한다면, 기존 명세와 협력해 활용 사례에 맞게 확장하는 것이 좋습니다.

원래 명세 작성자가 즉시 매니페스트 포맷 변경을 통합하지 않는 경우도 있습니다. 이는 프로세스(예: CR 단계 진입) 때문일 수도 있고, 추가의 범위가 다르기 때문일 수도 있습니다(예: Web App Manifest 확장이 스토어 또는 결제에만 영향을 줄 때). 이런 경우에는 원래 명세 편집자와 합의만 된다면 monkey patch도 가능합니다.

그러나 기능에 특정 도메인에 특화된 복잡한 메타데이터 집합이 필요하다면 새로운 매니페스트를 만드는 것이 정당화될 수 있습니다.

매니페스트 파일의 도메인이 기존 매니페스트와 다르다면(예: 페치 타이밍이 다른 경우, 매니페스트 복잡성이 충분하다면) 새로운 매니페스트 파일을 만들어야 할 수도 있습니다. 애플리케이션 메타데이터는 Web App Manifest에 추가하거나 그 확장이어야 하며, 특정 애플리케이션용이거나 브라우저 이외와의 상호운용성이 필요하면 다른 접근 방식이 필요할 수 있습니다. Payment Method Manifest, Publication Manifest, Origin Policy가 그런 사례입니다.

예를 들어, 메타데이터가 하나뿐이고 페치 타이밍만 기존 매니페스트와 다르더라도, 기존 매니페스트를 사용하는 것이 보통 더 낫습니다(이상적으로는 매니페스트 자체가 필요 없게 설계). 그러나 기능에 특정 도메인에 특화된 복잡한 메타데이터 집합이 필요하다면 새로운 매니페스트를 만드는 것이 정당화될 수 있습니다.

모든 경우에 명명 규칙은 일관되어야 합니다(§ 12 이름 짓기 원칙 참고).

참고: 기존 매니페스트는 원칙적으로 소문자, 언더스코어 구분 이름을 사용합니다. 매니페스트의 딕셔너리를 DOM API에서 재사용하려면 이름을 카멜 케이스로 변환해야 할 때도 있습니다. image resource가 그 예입니다. 이런 이유로, 키가 명확히 단어 하나로 표현 가능하다면 그렇게 하는 것이 좋습니다.

10.6. 직렬화 시 소비자 고려

파서 또는 직렬화기를 포함한 기능을 추가하거나 확장할 때에는 직렬화 결과가 미치는 영향을 반드시 고려해야 합니다. 직렬화 결과의 이해관계자는 다음과 같습니다:

언어별 기대치를 고려하세요 – 예를 들어, 일부 언어에서는 공백의 존재 여부가 중요할 수 있습니다. 언어마다 부동소수점 숫자 허용 정밀도가 다를 수 있습니다. 직렬화 결과는 다음을 만족해야 합니다:

10.7. 기능은 개발자 친화적이어야 함

모든 신규 기능은 개발자 친화적이어야 합니다. 친화성은 수치화하기 어렵지만, 최소한 다음을 고려하세요.

예외의 에러 텍스트는 일반적이어야 하지만, 개발자 콘솔 등 개발자용 에러 메시지는 반드시 의미가 있어야 합니다. 개발자가 오류를 만날 때, 메시지는 해당 오류 상황에 구체적이어야 하며, 지나치게 일반적이어서는 안 됩니다.

이상적으로, 개발자용 에러 메시지는 문제가 어디서 발생했는지 파악할 수 있도록 충분한 정보를 제공해야 합니다.

CSS 같은 선언형 기능은 구현에서 디버깅 용이성을 위해 추가 작업이 필요할 수 있습니다. 명세에서 이를 정의하면 기능이 개발자 친화적으로 될 뿐만 아니라, 사용자에게 일관된 개발 경험도 보장할 수 있습니다.

Web Animations 명세에서는 디버깅 용이성(개발자 친화성)이 명세의 일부로 정의된 좋은 사례입니다. (Web Animations 참고)

10.8. 최고의 암호 사용, 진화를 기대

보안 전문가가 객관적으로 검토한 암호 알고리즘만 사용하세요. 선택한 알고리즘이 검증되고 최신임을 반드시 확인하세요. 암호 프로토콜과 알고리즘은 빠르게 진화하며, 구식이 되거나 안전하지 않게 될 수 있습니다.

10.9. Client Hints로 새로운 정보 노출 금지

Client Hints를 사용할 때 웹 페이지가 이미 접근 가능한 정보만 노출하세요.

Client Hints는 중요한 최적화이지만, 사이트에 정보를 노출하는 유일한 수단이 되어서는 안 됩니다. RFC 8942 §4.1에서 정의된 바와 같이:

따라서, 이 문서를 기반으로 Client Hint 헤더를 정의하는 기능은 MUST NOT 기존 요청 헤더, HTML, CSS, JavaScript 등 사용자 에이전트가 애플리케이션에 이미 제공하는 정보 이외의 새로운 정보를 제공해서는 안 됩니다.

웹 페이지가 다른 방식으로 접근할 수 없는 정보를 노출하는 새로운 client hint를 추가하려면, 우선 API를 통해 해당 정보를 노출하는 방안을 먼저 검토하세요.

11. 좋은 명세 작성

이 문서는 주로 웹의 API 설계에 대해 다루지만, API 설계자는 자신이 설계한 API에 대한 명세도 작성하기를 바랍니다.

11.1. 명세의 각 요구사항 대상자 명확히 구분

API를 사용하는 작성자가 좋은 코드를 어떻게 작성해야 하는지, API 구현자가 잘못 작성된 코드를 어떻게 처리해야 하는지도 문서화하세요.

웹은 특히 다른 플랫폼에 비해 잘못된 마크업도 허용하도록 설계되었습니다. 이는 웹 표준의 옛 버전을 사용하는 웹페이지도 최신 사용자 에이전트에서 볼 수 있게 하고, 작성자의 학습 곡선도 완만하게 만듭니다.

이를 지원하기 위해, 웹 명세 작성자는 마크업이 잘못된 경우와 올바른 경우 모두 어떻게 해석할지 기술해야 합니다.

구현자는 "지원되는 언어"를 이해할 수 있어야 하며, 이는 작성자가 지향해야 하는 "준수 언어(conforming language)"보다 더 복잡합니다.

예를 들어, Processing model (처리 모델)은 <table> 요소의 내용을 처리하는 방법을 설명하며, Content model을 준수하지 않는 경우도 포함합니다.

11.2. 명확히, 빠짐없이 명세

기능의 동작을 명세할 때는 작성자가 구현별로 다른 코드를 작성하지 않아도 되도록 충분한 정보를 제공하세요.

명세가 충분히 구체적이지 않으면 구현자가 각자 다른 선택을 하게 되고, 작성자가 그 차이를 처리하기 위한 추가 코드를 써야 하는 문제가 생깁니다.

구현자가 다른 구현의 세부사항을 확인해야만 이런 상황을 피할 수 있다면 올바른 명세가 아닙니다. 대신, 명세 자체가 충분히 완전하고 명확해야 합니다.

참고: 이것이 구현마다 렌더링이 달라질 수 없다는 의미는 아닙니다. 권한 요청 등의 UI는 서로 달라도 됩니다.

참고: 명확하지 않은 명세에 대해서는 구현자가 버그를 제기해야 합니다.

11.2.1. 알고리즘을 명확하게 정의

알고리즘은 명확하고 간결하게 작성하세요.

알고리즘 작성의 가장 일반적인 방식은 명시적인 단계(sequence of steps)로 작성하는 것입니다. 이는 흔히 의사코드처럼 보입니다.

showModal() 메서드는 단계별로 번호를 매겨 예외를 언제 던지고, HTML 명세의 다른 부분에서 정의된 알고리즘을 언제 실행하는지 명확히 설명합니다.

단계별로 작성할 때는, 실제 함수 코드처럼 생각하세요.

알고리즘의 목적을 먼저 요약한 뒤 세부 단계로 들어가면, 독자가 세부 단계를 읽을지 말지 결정할 수 있습니다. 예를 들어 다음 단계는 최상위 브라우징 컨텍스트당 X 콜백이 한 번만 pending 상태가 되도록 보장합니다.

단계 순서가 항상 알고리즘 작성의 최선은 아닙니다. 반복을 피하기 위해 공식 문법(syntax)이나 문법을 재사용하거나, 상태 머신에 사용할 특정 상태를 정의하는 것이 더 나을 수 있습니다. 이런 추가 구조를 사용할 때도 앞서 언급한 조언이 적용됩니다.

가능한 한, 알고리즘은 실제 구현 방식에 최대한 가깝게 설명하세요. 이렇게 하면 명세 작성은 어려워질 수 있지만, 구현자가 명세에서 실제 구현으로 어떻게 옮길지 고민할 필요가 없어집니다. 특히, 구현이 달라질 경우 일부 구현에서는 이후 기능이 가능하지만, 다른 구현에서는 불가능해질 수 있습니다.

CSS 셀렉터는 왼쪽에서 오른쪽으로 읽고 이해하지만, 실제 구현에서는 오른쪽에서 왼쪽으로 매칭합니다. 이렇게 하면 가장 구체적인 항목을 빠르게 매칭 또는 배제하여 불필요한 연산을 줄일 수 있습니다. CSS 셀렉터 매칭 알고리즘도 이런 방식으로 작성되어, CSS 작성자가 읽는 방식과는 다를 수 있습니다.

참고:

11.2.2. 상태에는 명시적 플래그 사용

상태를 단어로 설명하는 대신, 알고리즘 작성 시 명시적 플래그(flag)를 사용하세요.

명시적 플래그를 사용하면 오류 조건에 따라 상태 변화가 있는지, 플래그로 묘사한 상태가 언제 초기화되는지 명확해집니다.

11.3. 상호운용성과 구현 가능성의 긴장 해소

기능을 명세하는 것은 모든 엔지니어링 작업과 마찬가지로 트레이드오프와 타협이 필요합니다.

때로는, 모두가 최선을 다해도 모든 구현자가 자신의 엔진에서 구현 가능하다고 동의하는 방식으로 상호운용성 있게 기능을 명세하지 못할 수 있습니다.

이럴 때는 최종 사용자에게 가장 좋은 것이 무엇인지가 최우선 기준이 되어야 합니다.

먼저, 구현 가능성 문제의 본질을 분석하세요. 구현자가 최종 사용자 피해 가능성을 발견해 해당 기능을 구현하지 않기로 했다면, 기능을 명세하지 않는 것이 나을 수 있습니다. 이미 해당 기능을 배포한 구현자가 있다면, 배포 중단(un-ship)도 고려해야 합니다. 다만 배포 중단은 사용자와 작성자에게 혼란을 줄 수 있습니다.

Spectre 공격이 발견되었을 때, SharedArrayBuffer는 모든 구현에서 배포가 중단되었습니다. 이 기능이 고정밀 타이머로 악용되어 Spectre 공격에 사용될 수 있었기 때문입니다.

때때로 같은 API가 모든 엔진에서 구현 가능하지만, 동작을 완전히 상호운용성 있게 만들 수 없는 경우도 있습니다. 구현 간 동작이 다를 때는 항상 최종 사용자 영향부터 고려해야 합니다. 한 가지 단점은 이런 차이가 기능 감지로 확인되지 않는다는 것입니다. 한편, 미래에 모든 구현이 동작을 통합할 수 있게 되면, 사이트를 업데이트하지 않고도 이점을 활용할 수 있습니다.

참고: 작성자는 지배적인 구현의 동작이 올바르다고 가정하고 다른 동작은 버그로 간주할 수 있어, 지배적 구현이 더 굳건해질 수 있습니다.

backdrop-filter 속성은 구현 간에 눈에 띄는 동작 차이가 많이 있습니다. 현재로서는 API를 공유하는 장점이 차이로 인한 상호운용성 비용보다 크다고 판단되어 유지되고 있습니다.

동작 차이가 심하고, 더 이상 통합할 수 없다면, 각 구현자가 구현 가능한 대안을 선택해 서로 다른 API를 명세하는 편이 나을 수 있습니다.

이 방식의 위험은, 결국 구현 간 동작 통합이 가능해지면 여러 대체 API를 계속 지원해야 해 개발자 복잡성과 구현 유지비가 늘어난다는 점입니다.

같은 목적의 두 API를 노출하면 작성자는 기능 감지로 브라우저별로 다른 코드를 써야 합니다.

참고: 작성자는 지배적 구현의 API가 "올바르다"고 생각하거나, 다른 구현의 API는 잘 모른 채 사용할 수 있어, 지배적 구현이 더 굳어질 위험이 있습니다.

이 방식으로 간다면, 작성자 비용을 줄이기 위해 API 간 차이를 최소화하세요. 비표준 부분을 더 넓은 표준 프레임워크의 작고 독립적인 컴포넌트로 설계할 수도 있습니다.

[ENCRYPTED-MEDIA][payment-request]는 비표준 컴포넌트(각각 Content Decryption Modulespayment methods)를 (공유되는) API 표면에서 분리해 API 차이를 최소화한 명세 예시입니다.

단체에서 API를 명세할 때 일부 구현자가 구현 의사가 없다는 것을 알면서도 명세하는 경우가 있습니다. 작성자는 기능 감지로 API를 사용할 수 있을 때만 사용하게 하고, 사용 가능한 곳에서 사용자에게 혜택을 줄 수 있습니다. 이 방식은 항상 문제를 남기며, 구현자가 하나뿐일 때 최악입니다. 일부 표준화 그룹은 구현자가 하나뿐인 기능의 표준화 자체를 명시적으로 금지합니다. 그런 그룹이 아니더라도, 단일 구현 기능의 표준화는 강력히 권장하지 않습니다.

이 모든 옵션이 그룹에 받아들여지지 않을 수도 있습니다. 최고의 선택이 기능 명세를 하지 않는 것일 수 있지만, 일부 구현이 비표준 API로 기능을 배포할 위험이 있습니다.

11.4. 몽키패칭은 피하기

몽키패치(monkey patch)는 기존 명세에 새로운 기능을 계층적으로 추가해, 기존 명세의 동작을 확장, 오버라이드, 변경하는 방식입니다. 몽키패칭일반적으로 나쁜 관행으로 간주되며 피해야 합니다 (아래 이유 참고). 그러나 불가피한 경우도 있으니 몽키패칭이 불가피할 때의 가이드를 참고하세요.

몽키패칭 예시: 명세 A가 내부 알고리즘을 정의하면, 명세 B가 해당 알고리즘을 직접 오버라이드/변경(공개 확장포인트 사용 없이)하는 경우입니다.

몽키패칭은 기반 기능이 변경될 수 없고, 변경되지 않을 것이라는 잘못된 가정에 기반합니다. 이로 인해 여러 문제가 발생할 수 있습니다:

Wikipedia에서는 몽키패칭의 추가적인 문제점도 설명합니다.

11.4.1. 몽키패칭이 불가피할 경우

때로는(예: 새 명세 초기 설계 단계) 몽키패칭이 불가피할 수 있습니다. 이런 경우에는 다음을 반드시 지키세요:
  1. 명세에서 몽키패치를 다른 명세에 제안된 임시 변경임을 명확히 표기하세요. 예시 문구:

    # HTTP fetch 수정
    
    <p class="issue">합의가 충분해지면 [[Fetch]]에 병합하세요.</p>
    
  2. 변경할 위치를 명세 텍스트 인용 또는 정의된 용어 링크로 명확히 지정하세요. 번호가 매겨진 항목을 변경한다면 현재 번호도 포함하되, 번호만으로 충분하지 않으니 인용을 함께 사용하세요. 정의 내 여러 위치를 변경한다면 전체 정의를 <blockquote>로 붙이고 <ins>, <del>로 변경 내역을 기술하세요.

    HTTP fetch의 4.2단계 "If request’s redirect mode is "follow", then set request’s service-workers mode to "none"." 앞에 다음 단계를 삽입하세요:

    1. 첫 번째 새 단계.

    CSS2.1 블록 레벨 포맷팅에 대한 [CSS2] 규칙 중 “over-constrained” 계산에 관한 CSS 2 § 10.3.3 블록 레벨, 대체되지 않은 요소의 정상 흐름 규칙은 본 명세의 정렬 규칙을 우선하며, margin 속성의 사용 값도 보정하지 않습니다.

    URLSearchParams.delete()URLSearchParams.has() 정의에 optional USVString value 파라미터 추가:

    partial interface URLSearchParams {
      undefined delete(USVString name, optional USVString value);
      boolean has(USVString name, optional USVString value);
    };
    

    font-size-adjust 정의를 다음과 같이 변경하세요:

    Name: font-size-adjust
    Value: none | [ ex-height | cap-height | ch-width | ic-width | ic-height ]? [ from-font | <number> ]
    Initial: none
    Applies to: all elements and text
    Inherited: yes
    Percentages: N/A
    Computed value: a number or the keyword none the keyword none, or a pair of a metric keyword and a <number>
    Canonical order: per grammar
    Animation type: discrete if the keywords differ, otherwise by computed value type
  3. 몽키패치는 짧게 유지하세요. 알고리즘에 두세 단계 이상 추가한다면, 명세에 별도 자체 알고리즘을 정의해 몽키패치가 그 알고리즘을 호출하게 하세요.

  4. 알고리즘의 단계를 대체하거나 추가한다면, 새 단계를 편집자가 상위 알고리즘에 바로 복사해 넣을 수 있게 작성하세요. "return"이나 "abort" 같은 제어 흐름은 몽키패치 섹션이 아니라 전체 상위 알고리즘에서 반환/중단합니다.

  5. 기능이 커뮤니티 내부 검토를 마치고 충분히 좋은 아이디어라는 합의가 생기면, 기존 명세에 이슈를 제기해 상위 커뮤니티가 몽키패치를 검토하도록 요청하세요. 커뮤니티가 더 좋은 방법을 제안할 수 있으니 진지하게 받아들이세요. 기존 확장포인트를 활용하거나 새 확장포인트를 빨리 만들 수 있는 방법을 알려줄 수도 있습니다. 이렇게 하면 예상보다 빨리 몽키패치를 제거할 수 있습니다.

  6. 상위 명세에 이슈를 제기했다면 위의 <p class="issue"> 블록을 해당 이슈로 가리키도록 업데이트하세요.

  7. 작업이 충분히 지지를 받아 상위 명세에 병합할 수 있게 되면, 해당 명세 관리자와 협력해 병합하세요.

  8. 기존 명세에 몽키패치가 통합되면, 내 명세에서 몽키패치를 제거하세요.

몽키패칭은 "모듈화(modularization)"와 다릅니다. 모듈화는 기존 기술을 자체적으로 확장해 다른 명세에 부작용을 일으키지 않으며(예: CSS 모듈), 좋은 관행으로 간주됩니다.

WebIDL의 partial interface, partial dictionary 등으로 명세를 확장하는 것도 보통 괜찮지만, 그 경우에도 확장 대상 명세 작성자와 적극적으로 협력하는 것이 좋습니다.

12. 이름 짓기 원칙

이름은 다음에서 의미를 얻습니다:

12.1. 일상적인 단어 사용

API 이름은 반드시 읽기 쉬운 미국 영어로 작성해야 합니다. 대부분의 웹 개발자가 영어 원어민이 아닌 점을 고려하세요. 가능하다면, 처음 접했을 때 대다수 영어 사용자가 이해할 수 있는 일반적인 어휘를 사용해 이름을 지으세요.

예를 들어 setSizecardinality보다 더 영어 친화적인 이름입니다.

가독성을 간결성보다 우선하세요. 단, 짧은 이름이 더 명확한 경우도 많다는 점을 기억하세요. 예를 들어, API가 정의된 명세에서는 전문 용어나 잘 알려진 용어를 써도 적절할 수 있습니다.

예를 들어, Fetch API의 Body 믹스인의 json() 메서드는 반환 객체의 종류를 따서 이름지었습니다. JSON은 Fetch API를 사용할 법한 웹 개발자에게 잘 알려진 전문 용어입니다. 반환 타입과 직접 연결되지 않은 이름을 사용하면 오히려 이해를 방해합니다. [FETCH]

12.2. ASCII 이름 사용

이름은 CSS 식별자 규칙 등 지역 언어 제한을 따라야 하며, 가능하면 ASCII 범위여야 합니다.

12.3. 이름은 반드시 다른 사람과 상의

API 이름을 널리 상의하세요.

뜻밖의 곳에서 좋은 이름이나 영감을 얻을 수 있습니다.

기본 원칙에 근거한 명확한 이유가 있는 조언은 특히 주의해서 참고하세요.

Tantek Çelik은 URL의 각 부분 이름 지정에 대해 광범위하게 연구했습니다. URL 명세 편집자들은 이 연구를 참고해 명세를 편집했습니다. [URL]

웹에 일관된 이름 사용

웹 이외 기술 스택에도 노출되는 기능이나 API의 이름을 선택할 때는 웹 생태계의 명명 규칙을 다른 커뮤니티보다 우선해야 합니다.

NFC 표준은 웹에서 MIME type이라 부르는 개념을 media라고 칭합니다. 이런 경우 Web NFC의 기능이나 API 명명은 MIME type과 일관되게 해야 합니다.

포용적 언어 사용

가능하면 포용적 언어를 사용하세요.

예를 들어, blacklist, whitelist 대신 blocklist, allowlist를, master, slave 대신 source, replica를 사용하세요.

일반적인 페르소나(작성자, 사용자 등)를 지칭해야 한다면 "they", "their" 등 성중립 대명사를 사용하세요. 예: "A user may wish to adjust their preferences".

12.4. 목적을 설명하는 이름 사용

이름은 동작 방식이 아니라 하는 일을 기준으로 지으세요.

목적을 반영하는 이름이 더 미래 호환성을 가집니다. 웹에서 API 제거는 어렵기 때문에, 이름은 구현 세부사항보다 오래 가야 합니다.

특히, 코드네임, 브랜드명, 기능 구현에 사용된 기술 세부사항은 이름에 포함하지 마세요. 이런 것들은 구식이 되거나 나중에 바뀔 수 있습니다.

Remote Playback API는 영감을 준 기존 독점 시스템(Chromecast나 AirPlay 등)이 아니라 API가 하는 일을 설명하는 일반 용어로 이름지었습니다. [REMOTE-PLAYBACK]

WebTransport API는 QUIC 프로토콜이 제공하는 네트워킹 기능을 사용하지만, 이름은 데이터를 운반하는 더 일반적인 목적을 반영합니다. [WebTransport][RFC9000]

12.5. 이름은 일관성 있게

이름 체계는 혼란을 피하기 위해 일관성을 목표로 해야 합니다.

관련된 이름 집합은 다음에 대해 서로 일치해야 합니다:

불리언 프로퍼티 vs. 불리언 반환 메서드

불리언 프로퍼티, 옵션, API 인자가 인자에 대해 질문하는 경우에는 is로 시작하지 않아야 하며, 같은 목적의 부작용 없는 메서드는 is로 시작해야 플랫폼 전체와 일관성을 가집니다.

기존 API와 일관된 대소문자 규칙 사용

웹 플랫폼 API 설계 역사에서 일관되게 지켜지진 않았지만, 다음 규칙들이 자리잡았습니다:

대소문자 규칙 예시
메서드 및 프로퍼티
(Web IDL 속성, 연산, 딕셔너리 키)
카멜 케이스 createAttribute()
compatMode
클래스와 믹스인
(Web IDL 인터페이스)
파스칼 케이스 NamedNodeMap
NonElementParentNode
API 내 이니셜리즘 첫 단어가 메서드/프로퍼티일 때만 제외하고 모두 대문자 HTMLCollection
innerHTML
bgColor
API 내 반복 이니셜리즘 같은 규칙 따름 HTMLHRElement
RTCDTMFSender
"identity"/"identifier" 약어 Id, 단 첫 단어가 메서드/프로퍼티일 때만 제외 getElementById()
pointerId
id
열거형 값 소문자, 대시(-) 구분 "no-referrer-when-downgrade"
이벤트 소문자, 연결 canplaythrough
languagechange
HTML 요소 및 속성 소문자, 연결 figcaption
maxlength
JSON 키 소문자, 언더스코어 구분 short_name
특히 HTML 속성이 프로퍼티로 반영(reflect)될 때는, 속성과 프로퍼티의 케이스가 반드시 일치하지는 않습니다. 예를 들어 HTML 속성 ismapimg 요소에서 반영되어 isMap 프로퍼티가 됩니다 HTMLImageElement에서.

JSON 키 규칙은 HTTP로 전송되거나 디스크에 저장된 특정 JSON 파일 형식에만 적용되며, 일반적 자바스크립트 객체 키에는 적용되지 않습니다.

반복 이니셜리즘은 플랫폼 전반에 특히 일관성이 떨어집니다. 역사적으로 유명한 위반 사례는 XMLHttpRequestHTMLHtmlElement 입니다. 이런 예시를 따르지 말고, 반복되더라도 항상 이니셜리즘은 대문자로 표기하세요.

팩토리 메서드 이름은 create 또는 from으로 시작

팩토리 메서드 이름은 create 또는 from으로 시작하고, 필요에 따라 명확한 명사를 뒤에 붙이세요.

팩토리 메서드가 새 빈 객체를 생성한다면 create로, 기존 데이터로부터 객체를 생성한다면 from으로 시작하세요.

팩토리 메서드는 예외적이어야 하며, 유효한 이유가 있을 때만 사용하세요. 예시: 객체 생성 시 부모 객체와 연결이 필요할 때(예: document.createXXX()).

소스 객체에서 타겟 객체로 변환할 때는 from 접두사를 사용하세요. 예: Foo.fromBar()Bar 객체로 Foo 객체를 생성함을 의미합니다.

일반적인 패턴은 팩토리 메서드를 create() 또는 from()으로 명명하는 것입니다.

다른 접두사 발명이나 레거시 접두사 사용은 강력한 이유가 없는 한 피하세요. 예외를 둘만한 이유는 동일 객체 내 기존 팩토리 메서드와 일관성을 위해서입니다(예: document.initXXX()). 새로운 팩토리 메서드는 이 관례를 따르지 말아야 합니다.

12.6. 위험한 기능은 이름으로 경고

가능하다면 개발자에게 제공하는 보장(guarantee)을 약화시키는 기능은 이름을 "unsafe"로 시작해 눈에 띄게 표시하세요.

예를 들어, Content Security Policy (CSP) 는 특정 콘텐츠 삽입 취약점 보호를 제공합니다. CSP에는 이를 약화시키는 기능도 있는데, 예를 들어 unsafe-inline 키워드는 인라인 스크립트를 허용해 CSP의 보호를 감소시킵니다.

13. 기타 참고 자료

명세 작성 방법에 대한 유용한 조언은 다음에서도 볼 수 있습니다:

감사의 글

이 문서는 TAG의 현재 및 과거 멤버들이 TAG 디자인 리뷰 과정에서 모은 원칙들로 이루어져 있습니다. 디자인 리뷰를 요청해주신 모든 분께 감사드립니다.

TAG는 Adrian Hope-Bailie, Alan Stearns, Aleksandar Totic, Alex Russell, Alice Boxhall, Andreas Stöckel, Andrew Betts, Anne van Kesteren, Benjamin C. Wiley Sittler, Boris Zbarsky, Brian Kardell, Charles McCathieNevile, Chris Wilson, Dan Connolly, Daniel Ehrenberg, Daniel Murphy, David Baron, Domenic Denicola, Eiji Kitamura, Eric Shepherd, Ethan Resnick, fantasai, François Daoust, Henri Sivonen, HE Shi-Jun, Ian Hickson, Irene Knapp, Jake Archibald, Jeffrey Yasskin, Jeremy Roman, Jirka Kosek, Kenneth Rohde Christiansen, Kevin Marks, Lachlan Hunt, Léonie Watson, L. Le Meur, Lukasz Olejnik, Maciej Stachowiak, Marcos Cáceres, Mark Nottingham, Martin Thomson, Matt Giuca, Matt Wolenetz, Michael[tm] Smith, Mike West, Nick Doty, Nigel Megitt, Nik Thierry, Ojan Vafai, Olli Pettay, Pete Snyder, Philip Jägenstedt, Philip Taylor, Reilly Grant, Richard Ishida, Rick Byers, Rossen Atanassov, Ryan Sleevi, Sangwhan Moon, Sergey Konstantinov, Stefan Zager, Stephen Stewart, Steven Faulkner, Surma, Tab Atkins-Bittner, Tantek Çelik, Tobie Langel, Travis Leithead, 그리고 Yoav Weiss 등 본 문서와 그 이전의 HTML Design Principles에 기여해주신 모든 분께 감사드립니다.

특히 Anne van Kesteren 및 Maciej Stachowiak께서 HTML Design Principles 문서를 편집해주셨습니다.

이 문서에 기여하셨는데 이름이 누락되었다면, 편집자에게 알려주시면 바로 수정하겠습니다.

색인

이 명세서에서 정의된 용어

참조로 정의된 용어

참고 문헌

규범적 참고 문헌

[CSS-CASCADE-5]
Elika Etemad; Miriam Suzanne; Tab Atkins Jr.. CSS Cascading and Inheritance Level 5. URL: https://drafts.csswg.org/css-cascade-5/
[CSS-CONDITIONAL-3]
Chris Lilley; David Baron; Elika Etemad. CSS Conditional Rules Module Level 3. URL: https://drafts.csswg.org/css-conditional-3/
[DOM]
Anne van Kesteren. DOM 표준. Living Standard. URL: https://dom.spec.whatwg.org/
[ECMASCRIPT]
ECMAScript 언어 명세. URL: https://tc39.es/ecma262/multipage/
[ENCODING]
Anne van Kesteren. 인코딩 표준. Living Standard. URL: https://encoding.spec.whatwg.org/
[FETCH]
Anne van Kesteren. Fetch 표준. Living Standard. URL: https://fetch.spec.whatwg.org/
[FINGERPRINTING-GUIDANCE]
Nick Doty; Tom Ritter. 웹 명세에서 브라우저 지문 채취 완화. URL: https://w3c.github.io/fingerprinting-guidance/
[HIGHRES-TIME]
Yoav Weiss. 고해상도 시간. URL: https://w3c.github.io/hr-time/
[HTML]
Anne van Kesteren; et al. HTML 표준. Living Standard. URL: https://html.spec.whatwg.org/multipage/
[I18N-GLOSSARY]
Richard Ishida; Addison Phillips. 국제화 용어집. URL: https://w3c.github.io/i18n-glossary/
[IndexedDB-3]
Steven Becker. Indexed Database API 3.0. URL: https://w3c.github.io/IndexedDB/
[INFRA]
Anne van Kesteren; Domenic Denicola. Infra 표준. Living Standard. URL: https://infra.spec.whatwg.org/
[MEDIAQUERIES-5]
Dean Jackson; et al. Media Queries Level 5. URL: https://drafts.csswg.org/mediaqueries-5/
[PAYMENT-REQUEST]
Marcos Caceres; Ian Jacobs; Stephen McGruer. Payment Request API. URL: https://w3c.github.io/payment-request/
[PERMISSIONS]
Marcos Caceres; Mike Taylor. Permissions. URL: https://w3c.github.io/permissions/
[PRIVACY-PRINCIPLES]
Robin Berjon; Jeffrey Yasskin. 프라이버시 원칙. URL: https://w3ctag.github.io/privacy-principles/
[REQUESTIDLECALLBACK]
Scott Haseley. requestIdleCallback(). URL: https://w3c.github.io/requestidlecallback/
[RFC9205]
M. Nottingham. HTTP로 프로토콜 구축하기. June 2022. Best Current Practice. URL: https://httpwg.org/specs/rfc9205.html
[SERVICE-WORKERS]
Yoshisato Yanagisawa; Monica CHINTALA. Service Workers. URL: https://w3c.github.io/ServiceWorker/
[URL]
Anne van Kesteren. URL 표준. Living Standard. URL: https://url.spec.whatwg.org/
[WEB-ANIMATIONS-2]
Brian Birtles; Robert Flack. Web Animations Level 2. URL: https://drafts.csswg.org/web-animations-2/
[WEBIDL]
Edgar Chen; Timothy Gu. Web IDL 표준. Living Standard. URL: https://webidl.spec.whatwg.org/
[XHR]
Anne van Kesteren. XMLHttpRequest 표준. Living Standard. URL: https://xhr.spec.whatwg.org/

비규범적 참고 문헌

[APPMANIFEST]
Marcos Caceres; et al. 웹 애플리케이션 매니페스트. URL: https://w3c.github.io/manifest/
[CONSOLE]
Dominic Farolino; Robert Kowalski; Terin Stock. 콘솔 표준. Living Standard. URL: https://console.spec.whatwg.org/
[CREDENTIAL-MANAGEMENT-1]
Nina Satragno; Marcos Caceres. Credential Management Level 1. URL: https://w3c.github.io/webappsec-credential-management/
[CSS-BACKGROUNDS-3]
Elika Etemad; Brad Kemper. CSS Backgrounds and Borders Module Level 3. URL: https://drafts.csswg.org/css-backgrounds/
[CSS-FONTS-4]
Chris Lilley. CSS Fonts Module Level 4. URL: https://drafts.csswg.org/css-fonts-4/
[CSS-FONTS-5]
Chris Lilley. CSS Fonts Module Level 5. URL: https://drafts.csswg.org/css-fonts-5/
[CSS-GRID-1]
Tab Atkins Jr.; et al. CSS Grid Layout Module Level 1. URL: https://drafts.csswg.org/css-grid-1/
[CSS-INLINE-3]
Elika Etemad. CSS Inline Layout Module Level 3. URL: https://drafts.csswg.org/css-inline-3/
[CSS-VALUES-4]
Tab Atkins Jr.; Elika Etemad. CSS Values and Units Module Level 4. URL: https://drafts.csswg.org/css-values-4/
[CSS2]
Bert Bos; et al. Cascading Style Sheets Level 2 Revision 1 (CSS 2.1) 명세. URL: https://drafts.csswg.org/css2/
[CSSOM-1]
Daniel Glazman; Emilio Cobos Álvarez. CSS 객체 모델 (CSSOM). URL: https://drafts.csswg.org/cssom/
[CSSOM-VIEW-1]
Simon Fraser; Emilio Cobos Álvarez. CSSOM View Module. URL: https://drafts.csswg.org/cssom-view/
[ENCRYPTED-MEDIA]
Joey Parrish; Greg Freedman. Encrypted Media Extensions. URL: https://w3c.github.io/encrypted-media/
[ETHICAL-WEB]
Daniel Appelquist; Hadley Beeman; Amy Guy. 윤리적 웹 원칙. URL: https://w3ctag.github.io/ethical-web-principles/
[FileAPI]
Marijn Kruisselbrink. File API. URL: https://w3c.github.io/FileAPI/
[FILTER-EFFECTS-2]
Filter Effects Module Level 2. Editor's Draft. URL: https://drafts.fxtf.org/filter-effects-2/
[GAMEPAD]
Steve Agoston; Matthew Reynolds. Gamepad. URL: https://w3c.github.io/gamepad/
[INTERNATIONAL-SPECS]
Richard Ishida; Addison Phillips. 명세 개발자를 위한 국제화 모범 사례. URL: https://w3c.github.io/bp-i18n-specdev/
[INTERSECTION-OBSERVER]
Stefan Zager; Emilio Cobos Álvarez; Traian Captan. Intersection Observer. URL: https://w3c.github.io/IntersectionObserver/
[LEAST-POWER]
Tim Berners-Lee; Noah Mendelsohn. 최소 권한의 원칙(The Rule of Least Power). 2006년 2월 23일. TAG Finding. URL: https://www.w3.org/2001/tag/doc/leastPower
[POINTEREVENTS3]
Patrick Lauke; Robert Flack. Pointer Events. URL: https://w3c.github.io/pointerevents/
[REFERRER-POLICY]
Jochen Eisinger; Emily Stark. Referrer Policy. URL: https://w3c.github.io/webappsec-referrer-policy/
[REMOTE-PLAYBACK]
Mark Foltz. Remote Playback API. URL: https://w3c.github.io/remote-playback/
[RFC8890]
M. Nottingham. 인터넷은 최종 사용자를 위한 것입니다(The Internet is for End Users). 2020년 8월. Informational. URL: https://www.rfc-editor.org/rfc/rfc8890
[RFC9000]
J. Iyengar, Ed.; M. Thomson, Ed.. QUIC: UDP 기반 멀티플렉스 및 보안 전송. 2021년 5월. Proposed Standard. URL: https://www.rfc-editor.org/rfc/rfc9000
[RFC9110]
R. Fielding, Ed.; M. Nottingham, Ed.; J. Reschke, Ed.. HTTP 시맨틱스. 2022년 6월. Internet Standard. URL: https://httpwg.org/specs/rfc9110.html
[SCREEN-CAPTURE]
Jan-Ivar Bruaroey; Elad Alon. 화면 캡처. URL: https://w3c.github.io/mediacapture-screen-share/
[UIEVENTS]
Gary Kacmarcik; Travis Leithead. UI 이벤트. URL: https://w3c.github.io/uievents/
[UNSANCTIONED-TRACKING]
Mark Nottingham. 비인가 웹 추적. 2015년 7월 17일. TAG Finding. URL: https://www.w3.org/2001/tag/doc/unsanctioned-tracking/
[WEB-LOCKS]
Kagami Rosylight. Web Locks API. URL: https://w3c.github.io/web-locks/
[WEB-SHARE]
Marcos Caceres; Eric Willigers; Matt Giuca. Web Share API. URL: https://w3c.github.io/web-share/
[WebAudio]
Paul Adenot; Hongchan Choi. Web Audio API. URL: https://webaudio.github.io/web-audio-api/
[WEBCRYPTO-2]
Daniel Huigens. Web Cryptography Level 2. URL: https://w3c.github.io/webcrypto/
[WEBRTC]
Cullen Jennings; et al. WebRTC: 브라우저의 실시간 통신. URL: https://w3c.github.io/webrtc-pc/
[WebTransport]
Nidhi Jaju; Victor Vasiliev; Jan-Ivar Bruaroey. WebTransport. URL: https://w3c.github.io/webtransport/

속성 색인

이름 초기값 적용 대상 상속 퍼센트 애니메이션 타입 정식 순서 계산된 값
font-size-adjust none | [ ex-height | cap-height | ch-width | ic-width | ic-height ]? [ from-font | <number> ] none 모든 요소와 텍스트 해당 없음 키워드가 다르면 이산(discrete), 아니면 계산값 타입에 따름 문법에 따름 숫자 또는 키워드 none, 키워드 none, 또는 메트릭 키워드와 <number>의 쌍