텍스트 프래그먼트를 사용하면 URL 프래그먼트에 텍스트 스니펫을 지정할 수 있습니다. 이러한 텍스트 프래그먼트가 있는 URL로 이동하면 브라우저에서 이를 강조하여 사용자의 주의를 끌 수 있습니다.
프래그먼트 식별자
Chrome 80은 대규모 출시였습니다. 여기에는 Web Worker의 ECMAScript 모듈, nullish 병합, 선택적 체이닝 등 많은 기대를 모았던 기능이 포함되어 있습니다. 출시는 평소와 같이 Chromium 블로그의 블로그 게시물을 통해 발표되었습니다. 다음 스크린샷에서 블로그 게시물의 발췌문을 확인할 수 있습니다.
id 속성이 있는 요소 주위에 빨간색 상자가 있는 Chromium 블로그 게시물빨간색 상자가 무엇을 의미하는지 궁금하실 것입니다. DevTools에서 다음 스니펫을 실행한 결과입니다. id 속성이 있는 모든 요소를 강조 표시합니다.
document.querySelectorAll('[id]').forEach((el) => {
el.style.border = 'solid 2px red';
});
프래그먼트 식별자를 사용하면 빨간색 상자로 강조 표시된 모든 요소에 딥 링크를 배치할 수 있으며, 이 식별자는 페이지 URL의 해시에 사용됩니다. aside의 제품 포럼에서 의견 보내기 상자에 딥 링크를 연결하려면 URL https://blog.chromium.org/2019/12/chrome-80-content-indexing-es-modules.html#HTML1을 직접 작성하면 됩니다.
개발자 도구의 Elements 패널에서 볼 수 있듯이 해당 요소에는 값이 HTML1인 id 속성이 있습니다.
id를 보여주는 개발자 도구JavaScript의 URL() 생성자로 이 URL을 파싱하면 다양한 구성요소가 표시됩니다.
hash 속성의 값이 #HTML1입니다.
new URL('https://blog.chromium.org/2019/12/chrome-80-content-indexing-es-modules.html#HTML1');
/* Creates a new `URL` object
URL {
hash: "#HTML1"
host: "blog.chromium.org"
hostname: "blog.chromium.org"
href: "https://blog.chromium.org/2019/12/chrome-80-content-indexing-es-modules.html#HTML1"
origin: "https://blog.chromium.org"
password: ""
pathname: "/2019/12/chrome-80-content-indexing-es-modules.html"
port: ""
protocol: "https:"
search: ""
searchParams: URLSearchParams {}
username: ""
}
*/
하지만 개발자 도구를 열어 요소의 id를 찾아야 한다는 사실은 블로그 게시물 작성자가 페이지의 이 특정 섹션을 연결하려고 했을 가능성이 높다는 것을 의미합니다.
id 없이 무언가에 연결하려면 어떻게 해야 하나요? Web Workers의 ECMAScript 모듈 제목에 연결하고 싶다고 가정해 보겠습니다. 다음 스크린샷에서 볼 수 있듯이 문제의 <h1>에는 id 속성이 없으므로 이 제목에 연결할 방법이 없습니다. 이것이 바로 텍스트 프래그먼트가 해결하는 문제입니다.
id 없이 제목을 표시하는 개발 도구텍스트 프래그먼트
텍스트 프래그먼트 제안은 URL 해시에서 텍스트 스니펫을 지정하는 기능을 지원합니다. 이러한 텍스트 프래그먼트가 있는 URL로 이동할 때 사용자 에이전트는 이를 강조하고 사용자의 주의를 끌 수 있습니다.
브라우저 호환성
보안상의 이유로 이 기능을 사용하려면 noopener 컨텍스트에서 링크를 열어야 합니다.
따라서 <a> 앵커 마크업에 rel="noopener"를 포함하거나 Window.open() 창 기능 목록에 noopener를 추가해야 합니다.
start
가장 간단한 형태의 텍스트 프래그먼트 문법은 다음과 같습니다. 해시 기호 # 뒤에 :~:text=가 오고 마지막으로 링크하려는 URL 인코딩 텍스트를 나타내는 start가 옵니다.
#:~:text=start
예를 들어 Chrome 80의 기능을 발표하는 블로그 게시물에서 웹 작업자의 ECMAScript 모듈 제목에 연결하려는 경우 URL은 다음과 같습니다.
텍스트 프래그먼트는 이와 같이 강조표시됩니다. Chrome과 같이 지원되는 브라우저에서 링크를 클릭하면 텍스트 프래그먼트가 강조표시되고 스크롤되어 표시됩니다.
start 및 end
이제 제목뿐만 아니라 ECMAScript Modules in Web Workers라는 제목의 전체 섹션에 연결하려면 어떻게 해야 할까요? 섹션의 전체 텍스트를 퍼센트 인코딩하면 결과 URL이 너무 길어집니다.
다행히 더 나은 방법이 있습니다. 전체 텍스트가 아닌 텍스트의 특정 부분을 start,end 문법으로 프레이밍할 수 있습니다. 텍스트 프래그먼트의 시작 부분에 퍼센트 인코딩된 단어 몇 개를 지정하고 텍스트 프래그먼트의 끝부분에 퍼센트 인코딩된 단어 몇 개를 지정하며, 이 둘은 쉼표로 구분합니다.
이런 모습입니다.
start의 경우 ECMAScript%20Modules%20in%20Web%20Workers, 쉼표, ES%20Modules%20in%20Web%20Workers.를 end로 사용합니다. Chrome과 같은 지원 브라우저에서 클릭하면 전체 섹션이 강조표시되고 스크롤되어 표시됩니다.
이제 start 및 end를 선택한 이유가 궁금할 수 있습니다. 사실, 양쪽에 두 단어만 있는 약간 더 짧은 URL인 https://blog.chromium.org/2019/12/chrome-80-content-indexing-es-modules.html#:~:text=ECMAScript%20Modules,Web%20Workers.도 작동했을 것입니다. start 및 end을 이전 값과 비교합니다.
여기서 한 단계 더 나아가 start와 end에 모두 한 단어만 사용하면 문제가 발생합니다. URL https://blog.chromium.org/2019/12/chrome-80-content-indexing-es-modules.html#:~:text=ECMAScript,Workers.은 이제 더 짧아졌지만 강조 표시된 텍스트 프래그먼트는 더 이상 원래 선택된 것이 아닙니다. 강조표시는 Workers. 단어가 처음 나타날 때 중지됩니다. 이는 올바르지만 강조표시하려는 의도는 아닙니다. 문제는 선택한 섹션이 현재 한 단어로 된 start 및 end 값으로 고유하게 식별되지 않는다는 것입니다.
prefix- 및 -suffix
start 및 end에 충분히 긴 값을 사용하는 것은 고유한 링크를 얻는 한 가지 방법입니다.
하지만 경우에 따라서는 이것이 불가능할 수 있습니다. 참고로, Chrome 80 출시 블로그 게시물을 예로 선택한 이유는 무엇일까요? 정답은 이 출시 버전에서 텍스트 프래그먼트가 도입되었다는 것입니다.
위 스크린샷에서 'text'라는 단어가 네 번 표시됩니다. 네 번째 항목은 녹색 코드 글꼴로 작성됩니다. 이 특정 단어에 연결하려면 start를 text로 설정합니다. 'text'라는 단어는 한 단어이므로 end가 있을 수 없습니다. 시작하기 전에 URL https://blog.chromium.org/2019/12/chrome-80-content-indexing-es-modules.html#:~:text=text은 제목에 이미 있는 'Text'라는 단어가 처음 나타나는 위치에서 일치합니다.
다행히도 해결책이 있습니다. 이 경우 prefix- 및 -suffix를 지정할 수 있습니다. 녹색 코드 글꼴 'text' 앞의 단어는 'the'이고 뒤의 단어는 'parameter'입니다. 'text'라는 단어가 나타나는 다른 세 위치에는 동일한 주변 단어가 없습니다. 이 정보를 바탕으로 이전 URL을 조정하고 prefix- 및 -suffix를 추가할 수 있습니다. 다른 매개변수와 마찬가지로 이 매개변수도 퍼센트 인코딩되어야 하며 두 개 이상의 단어를 포함할 수 있습니다.
https://blog.chromium.org/2019/12/chrome-80-content-indexing-es-modules.html#:~:text=the-,text,-parameter를 탭합니다.
파서가 prefix- 및 -suffix를 명확하게 식별할 수 있도록 하려면 대시 -를 사용하여 start 및 선택적 end에서 분리해야 합니다.
전체 문법
텍스트 프래그먼트의 전체 문법은 다음과 같습니다. 대괄호는 선택적 매개변수를 나타냅니다.
모든 매개변수의 값은 퍼센트 인코딩되어야 합니다. 이는 대시 -, 앰퍼샌드 &, 쉼표 , 문자에 특히 중요하므로 텍스트 지시어 문법의 일부로 해석되지 않습니다.
#:~:text=[prefix-,]start[,end][,-suffix]
prefix-, start, end, -suffix는 각각 단일 블록 수준 요소 내의 텍스트만 일치시킬 수 있지만 전체 start,end 범위는 여러 블록에 걸쳐 있을 수 있습니다. 예를 들어 다음 예시에서는 시작 문자열 'The quick'이 중단되지 않은 단일 블록 수준 요소 내에 표시되지 않으므로 :~:text=The quick,lazy dog가 일치하지 않습니다.
<div>
The
<div></div>
quick brown fox
</div>
<div>jumped over the lazy dog</div>
하지만 다음 예시에서는 일치합니다.
<div>The quick brown fox</div>
<div>jumped over the lazy dog</div>
브라우저 확장 프로그램으로 텍스트 프래그먼트 URL 만들기
텍스트 프래그먼트 URL을 직접 만드는 것은 지루한 작업이며, 특히 고유한 URL을 만들어야 하는 경우에는 더욱 그렇습니다. 원하는 경우 사양에 몇 가지 도움말이 있으며 텍스트 프래그먼트 URL을 생성하는 정확한 단계가 나와 있습니다. Google에서는 Link to Text Fragment라는 오픈소스 브라우저 확장 프로그램을 제공합니다. 이 확장 프로그램을 사용하면 텍스트를 선택한 다음 컨텍스트 메뉴에서 'Copy Link to Selected Text'를 클릭하여 모든 텍스트에 연결할 수 있습니다. 이 확장 프로그램은 다음 브라우저에서 사용할 수 있습니다.
- Chrome용 Link to Text Fragment
- Microsoft Edge용 Link to Text Fragment
- Mozilla Firefox용 Link to Text Fragment
- Apple Safari의 텍스트 프래그먼트 링크
하나의 URL에 여러 텍스트 프래그먼트가 있는 경우
하나의 URL에 여러 텍스트 프래그먼트가 표시될 수 있습니다. 특정 텍스트 프래그먼트는 앰퍼샌드 문자 &로 구분해야 합니다. 다음은 텍스트 프래그먼트 3개가 포함된 링크의 예입니다.
https://blog.chromium.org/2019/12/chrome-80-content-indexing-es-modules.html#:~:text=Text%20URL%20Fragments&text=text,-parameter&text=:~:text=On%20islands,%20birds%20can%20contribute%20as%20much%20as%2060%25%20of%20a%20cat's%20diet
요소 및 텍스트 프래그먼트 혼합
기존 요소 프래그먼트는 텍스트 프래그먼트와 결합할 수 있습니다. 예를 들어 페이지의 원본 텍스트가 변경되어 텍스트 프래그먼트가 더 이상 일치하지 않는 경우에 대비해 의미 있는 대체 텍스트를 제공하기 위해 동일한 URL에 두 가지를 모두 포함해도 괜찮습니다. 제품 포럼에서 의견을 보내주세요 섹션에 연결되는 URL https://blog.chromium.org/2019/12/chrome-80-content-indexing-es-modules.html#HTML1:~:text=Give%20us%20feedback%20in%20our%20Product%20Forums.에는 요소 프래그먼트(HTML1)와 텍스트 프래그먼트(text=Give%20us%20feedback%20in%20our%20Product%20Forums.)가 모두 포함되어 있습니다.
프래그먼트 지시어
아직 설명하지 않은 문법 요소가 하나 있는데, 바로 프래그먼트 지시어 :~:입니다. 표시된 대로 기존 URL 요소 프래그먼트와의 호환성 문제를 방지하기 위해 텍스트 프래그먼트 사양에는 프래그먼트 지시어가 도입되었습니다. 프래그먼트 지시어는 코드 시퀀스 :~:로 구분된 URL 프래그먼트의 일부입니다. text=와 같은 사용자 에이전트 명령어를 위해 예약되어 있으며, 작성자 스크립트가 직접 상호작용할 수 없도록 로드 중에 URL에서 삭제됩니다. 사용자 에이전트 요청 사항은 지시어라고도 합니다. 따라서 구체적인 경우 text=를 텍스트 지시어라고 합니다.
특성 인식
지원 여부를 감지하려면 document에서 읽기 전용 fragmentDirective 속성을 테스트합니다. 프래그먼트 지시문은 URL이 문서가 아닌 브라우저에 전달되는 안내를 지정하는 메커니즘입니다. 이는 작성자 스크립트와의 직접적인 상호작용을 방지하기 위한 것으로, 기존 콘텐츠에 브레이킹 체인지를 도입할 우려 없이 향후 사용자 에이전트 안내를 추가할 수 있습니다. 향후 추가될 수 있는 기능의 한 가지 예는 변환 힌트입니다.
if ('fragmentDirective' in document) {
// Text Fragments is supported.
}
기능 감지는 주로 링크가 동적으로 생성되는 경우(예: 검색엔진)에 사용되며, 이를 지원하지 않는 브라우저에 텍스트 프래그먼트 링크를 제공하지 않도록 합니다.
텍스트 프래그먼트 스타일 지정
기본적으로 브라우저는 mark의 스타일을 지정하는 것과 동일한 방식으로 텍스트 프래그먼트의 스타일을 지정합니다(일반적으로 노란색 바탕에 검은색, mark의 CSS 시스템 색상). 사용자 에이전트 스타일시트에는 다음과 같은 CSS가 포함되어 있습니다.
:root::target-text {
color: MarkText;
background: Mark;
}
보시다시피 브라우저는 적용된 강조 표시를 맞춤설정하는 데 사용할 수 있는 의사 선택기 ::target-text를 노출합니다. 예를 들어 빨간색 배경에 검은색 텍스트가 표시되도록 텍스트 프래그먼트를 디자인할 수 있습니다. 항상 그렇듯이 재정의 스타일 지정으로 인해 접근성 문제가 발생하지 않도록 색상 대비를 확인하고 강조 표시가 실제로 나머지 콘텐츠와 시각적으로 구분되는지 확인하세요.
:root::target-text {
color: black;
background-color: red;
}
폴리필
텍스트 프래그먼트 기능은 어느 정도 폴리필할 수 있습니다. Google에서는 JavaScript로 기능이 구현되는 텍스트 프래그먼트에 대한 기본 제공 지원을 제공하지 않는 브라우저를 위해 확장 프로그램에서 내부적으로 사용하는 폴리필을 제공합니다.
프로그래매틱 텍스트 프래그먼트 링크 생성
폴리필에는 텍스트 프래그먼트 링크를 생성하기 위해 가져와 사용할 수 있는 fragment-generation-utils.js 파일이 포함되어 있습니다. 이는 코드 샘플에 설명되어 있습니다.
const { generateFragment } = await import('https://unpkg.com/text-fragments-polyfill/dist/fragment-generation-utils.js');
const result = generateFragment(window.getSelection());
if (result.status === 0) {
let url = `${location.origin}${location.pathname}${location.search}`;
const fragment = result.fragment;
const prefix = fragment.prefix ?
`${encodeURIComponent(fragment.prefix)}-,` :
'';
const suffix = fragment.suffix ?
`,-${encodeURIComponent(fragment.suffix)}` :
'';
const start = encodeURIComponent(fragment.textStart);
const end = fragment.textEnd ?
`,${encodeURIComponent(fragment.textEnd)}` :
'';
url += `#:~:text=${prefix}${start}${end}${suffix}`;
console.log(url);
}
분석 목적으로 텍스트 프래그먼트 가져오기
많은 사이트에서 라우팅에 프래그먼트를 사용하므로 브라우저는 이러한 페이지를 손상시키지 않기 위해 텍스트 프래그먼트를 삭제합니다. 예를 들어 분석 목적으로 페이지에 텍스트 프래그먼트 링크를 노출해야 할 필요성이 인정되지만 제안된 솔루션은 아직 구현되지 않았습니다. 현재로서는 다음 코드를 사용하여 정보를 추출할 수 있습니다.
new URL(performance.getEntries().find(({ type }) => type === 'navigate').name).hash;
보안
텍스트 프래그먼트 지시어는 사용자 활성화의 결과인 전체(동일 페이지가 아님) 탐색에서만 호출됩니다.
또한 도착 페이지와 다른 출처에서 시작되는 탐색은 도착 페이지가 충분히 격리된 것으로 알려지도록 noopener 컨텍스트에서 탐색이 이루어져야 합니다. 텍스트 프래그먼트 지시어는 기본 프레임에만 적용됩니다. 즉, iframe 내에서 텍스트가 검색되지 않으며 iframe 탐색은 텍스트 프래그먼트를 호출하지 않습니다.
개인 정보 보호
텍스트 프래그먼트 사양의 구현은 페이지에서 텍스트 프래그먼트가 발견되었는지 여부를 유출하지 않는 것이 중요합니다. 요소 프래그먼트는 원본 페이지 작성자가 완전히 제어할 수 있지만 텍스트 프래그먼트는 누구나 만들 수 있습니다. 제 예시에서 <h1>에 id가 없었기 때문에 ECMAScript Modules in Web Workers 제목에 연결할 방법이 없었지만, 저를 포함한 누구나 텍스트 프래그먼트를 신중하게 작성하여 어디에나 연결할 수 있었던 것을 기억하시나요?
악성 광고 네트워크 evil-ads.example.com를 운영한다고 가정해 보겠습니다. 또한 사용자가 광고와 상호작용하면 광고 iframe 중 하나에서 텍스트 프래그먼트 URL dating.example.com#:~:text=Log%20Out을 사용하여 dating.example.com에 대한 숨겨진 교차 출처 iframe을 동적으로 만들었다고 가정해 보겠습니다. '로그아웃'이라는 텍스트가 발견되면 피해자가 dating.example.com에 로그인되어 있음을 알 수 있으며, 이를 사용자 프로파일링에 사용할 수 있습니다. 단순한 텍스트 프래그먼트 구현은 일치가 성공하면 포커스 전환이 발생해야 한다고 결정할 수 있으므로 evil-ads.example.com에서 blur 이벤트를 수신 대기하여 일치가 발생한 시점을 알 수 있습니다. Chrome에서는 이러한 시나리오가 발생할 수 없도록 텍스트 프래그먼트를 구현했습니다.
또 다른 공격은 스크롤 위치를 기반으로 네트워크 트래픽을 악용하는 것입니다. 내가 회사 인트라넷의 관리자처럼 피해자의 네트워크 트래픽 로그에 액세스할 수 있다고 가정해 보겠습니다. 이제 …으로 고통받는 경우 해야 할 일이라는 긴 인사 문서가 있고 번아웃, 불안 등의 상태 목록이 있다고 가정해 보겠습니다. 목록의 각 항목 옆에 추적 픽셀을 배치할 수 있습니다. 그런 다음 문서 로드가 번아웃 항목 옆에 있는 추적 픽셀의 로드와 일시적으로 동시에 발생한다고 판단하면 인트라넷 관리자는 직원이 기밀이며 누구에게도 표시되지 않는다고 생각했을 수 있는 :~:text=burn%20out가 포함된 텍스트 프래그먼트 링크를 클릭했다고 판단할 수 있습니다. 이 예시는 다소 인위적이며, 이를 악용하려면 매우 구체적인 전제 조건이 충족되어야 하므로 Chrome 보안팀은 탐색 시 스크롤을 구현하는 위험을 관리 가능한 수준으로 평가했습니다.
다른 사용자 에이전트는 대신 수동 스크롤 UI 요소를 표시하도록 결정할 수 있습니다.
선택 해제하려는 사이트의 경우 Chromium은 사용자 에이전트가 텍스트 프래그먼트 URL을 처리하지 않도록 전송할 수 있는 문서 정책 헤더 값을 지원합니다.
Document-Policy: force-load-at-top
텍스트 프래그먼트 사용 중지
이 기능을 사용 중지하는 가장 쉬운 방법은 HTTP 응답 헤더를 삽입할 수 있는 확장 프로그램(예: ModHeader(Google 제품 아님))을 사용하여 다음과 같이 응답(요청이 아님) 헤더를 삽입하는 것입니다.
Document-Policy: force-load-at-top
선택 해제하는 또 다른 방법은 엔터프라이즈 설정 ScrollToTextFragmentEnabled를 사용하는 것입니다.
macOS에서 이 작업을 수행하려면 터미널에 다음 명령어를 붙여넣습니다.
defaults write com.google.Chrome ScrollToTextFragmentEnabled -bool false
Windows에서는 Chrome Enterprise 도움말 지원 사이트의 문서를 따릅니다.
웹 검색의 텍스트 프래그먼트
일부 검색의 경우 검색엔진 Google은 관련 웹사이트의 콘텐츠 스니펫을 사용하여 빠른 답변이나 요약을 제공합니다. 이러한 추천 스니펫은 검색이 질문 형식일 때 표시될 가능성이 가장 높습니다. 사용자가 추천 스니펫을 클릭하면 소스 웹페이지의 추천 스니펫 텍스트로 바로 이동됩니다. 이는 자동으로 생성된 텍스트 프래그먼트 URL 덕분에 작동합니다.
결론
텍스트 프래그먼트 URL은 웹페이지의 임의의 텍스트에 연결하는 강력한 기능입니다. 학술 커뮤니티에서는 이를 사용하여 매우 정확한 인용 또는 참조 링크를 제공할 수 있습니다. 검색엔진은 이를 사용하여 페이지의 텍스트 결과로 딥 링크를 연결할 수 있습니다. 소셜 네트워킹 사이트에서는 이를 사용하여 사용자가 액세스할 수 없는 스크린샷 대신 웹페이지의 특정 구절을 공유하도록 할 수 있습니다. 텍스트 프래그먼트 URL을 사용해 보고 저처럼 유용하다고 느끼시기를 바랍니다. Link to Text Fragment 브라우저 확장 프로그램을 설치하세요.
관련 링크
감사의 말씀
텍스트 프래그먼트는 닉 버리스와 데이비드 보칸이 구현하고 지정했으며 그랜트 왕이 기여했습니다. 이 문서를 철저하게 검토해 준 조 메들리에게 감사드립니다.