テキスト フラグメントを使用すると、URL フラグメントにテキスト スニペットを指定できます。このようなテキスト フラグメントを含む URL に移動すると、ブラウザはテキストを強調したり、ユーザーの注意を引いたりできます。
フラグメント ID
Chrome 80 は大きなリリースでした。Web ワーカーの ECMAScript モジュール、nullish coalescing、optional chaining など、多くの期待されている機能が含まれていました。このリリースは通常どおり、Chromium ブログのブログ投稿で発表されました。以下のスクリーンショットは、ブログ投稿の抜粋です。
赤いボックスが何を意味するのか、疑問に思われるかもしれません。これは、DevTools で次のスニペットを実行した結果です。id
属性を持つすべての要素がハイライト表示されます。
document.querySelectorAll('[id]').forEach((el) => {
el.style.border = 'solid 2px red';
});
フラグメント識別子をページの URL のハッシュで使用することで、赤いボックスでハイライト表示された要素にディープリンクを配置できます。たとえば、横にある [フィードバックを送信するプロダクト フォーラム] ボックスにディープリンクを設定する場合は、https://blog.chromium.org/2019/12/chrome-80-content-indexing-es-modules.html#HTML1
という URL をハンドクラフトします。デベロッパー ツールの [Elements] パネルに示すように、問題の要素には id
属性があり、値は HTML1
です。
JavaScript の URL()
コンストラクタでこの URL を解析すると、さまざまなコンポーネントが明らかになります。値が #HTML1
の hash
プロパティに注目してください。
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 Worker の ECMAScript モジュールの見出しにリンクするとします。以下のスクリーンショットからわかるように、問題の <h1>
には id
属性がありません。つまり、この見出しにリンクすることはできません。これが、テキスト フラグメントが解決する問題です。
テキスト フラグメント
テキスト フラグメントの提案では、URL ハッシュでテキスト スニペットを指定するためのサポートが追加されます。このようなテキスト フラグメントを含む URL に移動すると、ユーザー エージェントはテキストを強調したり、ユーザーの注意を引いたりできます。
ブラウザの互換性
セキュリティ上の理由から、この機能ではリンクを noopener
コンテキストで開く必要があります。したがって、<a>
アンカー マークアップに rel="noopener"
を含めるか、ウィンドウ機能のリストに Window.open()
に noopener
を追加してください。
start
最も単純な形式のテキスト フラグメントの構文は、ハッシュ記号 #
の後に :~:text=
が続き、最後に start
となります。これはリンク先のパーセント エンコード テキストを表します。
#:~:text=start
たとえば、Chrome 80 の新機能を発表するブログ投稿の「ECMAScript モジュールとウェブ ワーカー」という見出しにリンクする場合、URL は次のようになります。
テキスト フラグメントはこのように強調されます。Chrome などの対応ブラウザでリンクをクリックすると、テキスト フラグメントがハイライト表示され、スクロールして表示されます。
start
、end
次に、見出しだけでなく、Web Worker の ECMAScript モジュールというタイトルのセクション全体にリンクする場合はどうすればよいでしょうか。セクションのテキスト全体をパーセント エンコードすると、生成される URL が実用的でないほど長くなります。
幸いなことに、もっと良い方法があります。テキスト全体ではなく、start,end
構文を使用して目的のテキストをフレームできます。したがって、目的のテキストの先頭にはパーセントでエンコードされた単語をいくつか指定し、テキストの末尾にはパーセントでエンコードされた単語をカンマ ,
で区切って指定します。
次のようになります。
start
には、ECMAScript%20Modules%20in%20Web%20Workers
、カンマ ,
、ES%20Modules%20in%20Web%20Workers.
が end
として続いています。Chrome などの対応ブラウザでクリックすると、セクション全体がハイライト表示され、スクロールして表示されます。
start
と end
の選択について疑問に思われるかもしれません。実際には、両側に 2 つの単語しかない、少し短い URL https://blog.chromium.org/2019/12/chrome-80-content-indexing-es-modules.html#:~:text=ECMAScript%20Modules,Web%20Workers.
でも問題ありません。start
と end
を前の値と比較します。
さらに一歩進んで、start
と end
の両方に 1 つの単語のみを使用すると、問題が発生します。URL https://blog.chromium.org/2019/12/chrome-80-content-indexing-es-modules.html#:~:text=ECMAScript,Workers.
はさらに短くなりましたが、ハイライト表示されたテキスト フラグメントは、元々目的としていたものではありません。Workers.
という単語が最初に出現した時点でハイライト表示が停止します。これは正しいのですが、ここで強調したい箇所ではありません。問題は、現在の 1 語の start
値と end
値では、目的のセクションが一意に識別されないことです。
prefix-
、-suffix
start
と end
に十分長い値を使用することは、一意のリンクを取得するための 1 つのソリューションです。ただし、状況によってはこれができないこともあります。余談ですが、Chrome 80 リリース ブログ投稿を例として選んだのはなぜでしょうか。答えは、このリリースでテキスト フラグメントが導入されたことです。
上のスクリーンショットでは、「text」という単語が 4 回出現しています。4 番目の出現は緑色のコードフォントで記述されています。この単語にリンクする場合は、start
を text
に設定します。「text」という単語は 1 つの単語であるため、end
は使用できません。その場合、何をすべきでしょうか。URL https://blog.chromium.org/2019/12/chrome-80-content-indexing-es-modules.html#:~:text=text
は、ヘッダーにすでに存在する「Text」という単語が最初に出現する箇所で一致します。
幸い、解決策があります。このような場合は、prefix-
と -suffix
を指定できます。緑色のコードフォント「text」の前の単語は「the」、後の単語は「parameter」です。他の 3 つの単語「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 を生成する手順が正確に記載されています。Link to Text Fragment というオープンソースのブラウザ拡張機能を提供しています。この拡張機能を使用すると、任意のテキストを選択して、コンテキスト メニューで [Copy Link to Selected Text] をクリックすると、そのテキストにリンクできます。この拡張機能は、次のブラウザで使用できます。
- Google Chrome のテキスト フラグメントへのリンク
- Microsoft Edge のテキスト フラグメントへのリンク
- Mozilla Firefox のテキスト フラグメントへのリンク
- Apple Safari のテキスト フラグメントへのリンク
1 つの URL に複数のテキスト フラグメントがある
1 つの 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.
)の両方が含まれています。
フラグメント ディレクティブ
この構文には、まだ説明していない要素が 1 つあります。フラグメント ディレクティブ :~:
です。上記のように、既存の 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;
}
ポリフィルの適用
テキスト フラグメント機能は、ある程度のポリフィルが可能です。テキスト フラグメントの組み込みサポートを提供しないブラウザ(機能が 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 モジュールの見出しにリンクする方法がありませんでしたが、私を含め、誰でもテキスト フラグメントを慎重に作成することで、どこにでもリンクできるようになりました。
悪意のある広告ネットワーク evil-ads.example.com
を運営しているとします。さらに、ユーザーが広告を操作すると、広告 iframe の 1 つで、テキスト フラグメント URL dating.example.com#:~:text=Log%20Out
を持つ dating.example.com
への非表示のクロスオリジン iframe を動的に作成したとします。「Log Out」というテキストが見つかった場合、被害者が現在 dating.example.com
にログインしていることがわかります。これは、ユーザー プロファイリングに使用できます。単純な Text Fragments の実装では、一致が成功するとフォーカスが切り替わるように判断される可能性があるため、evil-ads.example.com
では blur
イベントをリッスンして、一致が発生したタイミングを把握できます。Chrome では、上記のシナリオが発生しないように Text Fragment を実装しています。
スクロール位置に基づくネットワーク トラフィックの悪用という攻撃もあります。会社イントラネットの管理者として、被害者のネットワーク トラフィック ログにアクセスできるとします。たとえば、人事に関する長いドキュメント「困難な状況に直面した場合の対処方法」があり、そこに「燃え尽き症候群」、「不安」などの状態のリストがあるとします。リスト内の各項目の横にトラッキング ピクセルを配置できます。ドキュメントの読み込みが、たとえば [燃え尽き症候群] 項目の横にあるトラッキング ピクセルの読み込みと同時に行われたと判断した場合、イントラネット管理者は、社員が :~:text=burn%20out
を含むテキスト フラグメント リンクをクリックしたことを判断できます。社員は、このリンクが機密情報であり、誰にも公開されないものであると想定していた可能性があります。この例は最初からやや不自然であり、その悪用には非常に具体的な前提条件を満たす必要があるため、Chrome セキュリティ チームは、ナビゲーションでのスクロールの実装リスクを管理可能と評価しました。他のユーザー エージェントでは、代わりに手動スクロールの UI 要素が表示される場合があります。
オプトアウトを希望するサイトの場合、Chromium は ドキュメント ポリシー ヘッダー値をサポートしているため、ユーザー エージェントがテキスト フラグメント URL を処理しないようにすることができます。
Document-Policy: force-load-at-top
テキスト フラグメントの無効化
この機能を無効にする最も簡単な方法は、HTTP レスポンス ヘッダーを挿入できる拡張機能を使用することです。たとえば、(Google プロダクトではなく)ModHeader を挿入し、次のようにレスポンス(リクエストではない)ヘッダーを挿入します。
Document-Policy: force-load-at-top
より複雑なオプトアウト方法としては、エンタープライズ設定 ScrollToTextFragmentEnabled
を使用する方法もあります。macOS の場合は、以下のコマンドをターミナルに貼り付けます。
defaults write com.google.Chrome ScrollToTextFragmentEnabled -bool false
Windows の場合は、Google Chrome Enterprise ヘルプのサポート サイトのドキュメントに沿って操作します。
ウェブ検索のテキスト フラグメント
一部の検索では、関連するウェブサイトのコンテンツ スニペットを使用して、簡単な回答やサマリーが提供されます。こうした強調スニペットは、質問の形式で検索された場合に表示される可能性が最も高くなります。ユーザーが強調スニペットをクリックすると、ソースのウェブページにある強調スニペットのテキストに直接移動します。これは、自動的に作成されたテキスト フラグメント URL によって機能します。
まとめ
テキスト フラグメント URL は、ウェブページ上の任意のテキストにリンクするための強力な機能です。学術コミュニティでこれを使用して、非常に正確な引用または参照リンクを提供できます。検索エンジンは、この情報を使用することで、ページ上のテキスト結果にディープリンクできます。ソーシャル ネットワーク サイトでは、アクセスできないスクリーンショットの代わりに、ウェブページの特定の一節をユーザーが共有できるようにします。テキスト フラグメントの URL の使用を開始して、私と同じように役立つことを願っています。必ず テキスト フラグメントへのリンク ブラウザ拡張機能をインストールしてください。
関連リンク
- 仕様ドラフト
- TAG の審査
- Chrome プラットフォームのステータスのエントリ
- Chrome のトラッキング バグ
- 配送インテントのスレッド
- WebKit-Dev スレッド
- Mozilla 標準位置スレッド
謝辞
テキスト フラグメントは、Nick Burris と David Bokan によって実装および仕様化され、Grant Wang が貢献しました。この記事を詳しく確認していただいた Joe Medley に感謝いたします。Unsplash の Greg Rakozy によるヒーロー画像。