Skip to main content
content script 與頁面共用同一份 document。頁面的 CSS 會影響你的標記,你的 CSS 也會影響頁面。shadow root 同時切斷這兩個方向。 Extension.js 不會替你建立 shadow root。由你建立,工具鏈只負責辨識你建立出來的宿主元素。本頁講的就是兩者之間的合約。

基本模式

每個 content 範本都採用同一種寫法:
src/content/scripts.js
其中有三處是有分量的:
  • data-extension-root 標記宿主元素,Extension.js 靠它找到你的根。
  • all: initial !important 保護宿主元素本身。shadow root 屏蔽的是它的後代,而不是宿主。少了這一行,頁面上一條 div { opacity: .8 } 就會讓整個元件變淡。
  • 回傳的那個函式 負責移除宿主元素。Extension.js 會在掛載下一個版本之前呼叫它。

Extension.js 尋找的宿主元素

Extension.js 用一個選擇器找到你的宿主:
請使用 data-extension-root 屬性或 extension-root 這個 id。沒有以 class 為基礎的寫法。屬性的值可以自由決定,"true" 只是慣例,不是要求。 extension-js-devtools 這個值是保留的。內建的開發者浮層會佔用它,而選擇器會把它排除在外,這樣兩者永遠不會互相認領對方的根。 在開發期間,Extension.js 會在你的宿主元素上蓋上一些記帳用的屬性: 這些屬性會從正式建置中移除。不要針對它們撰寫選擇器。

把 CSS 送進 shadow root

shadow root 會忽略活在它外面的樣式表。下面每一種情況,都由這一條規則解釋。

你在 script 中 import 的 CSS

從 content script import 樣式表時,Extension.js 會把它內嵌成 data: URL,而不是輸出一個 link。請把它取回來,再把文字放進 shadow root 內部的 <style> 元素中:
src/content/scripts.js
該樣式表中的任何 url() 都會在建置時被改寫,因此它解析到的是擴充功能本身,而不是頁面。

你從來不插入的 CSS

當一份樣式表進了 bundle,卻沒有任何程式碼去插入它時,Extension.js 會替你把它補進你的 shadow root。它會插入一個 <style data-extjs-bundle-css="true"> 元素,作為根的第一個子節點。 這份幫忙是有條件的。只要 shadow root 裡出現了你自己的、帶有文字的 <style> 元素,Extension.js 就會移除它插入的那個元素並退開。你自己的樣式表說了算。

在 manifest.json 中宣告的 CSS

列在 content_scripts[].css 之下的樣式表由瀏覽器注入,注入到頁面的 document。它永遠進不了 shadow root。 用 manifest CSS 為頁面本身設定樣式。shadow root 內部的一切,請用 import 進來的 CSS。

網頁字型

shadow root 內部的 @font-face 規則不會生效。字型(font face)的解析對象是 document,而不是 shadow tree。請改為把字型註冊到 document.fonts 上。完整範例請見 CSS、Sass 與 Less

重新載入行為

Extension.js 重新載入 content script 的方式,是把整個 bundle 重新注入一次。它不會在活著的頁面裡做模組熱抽換。 每次存檔時的流程是:
  1. Extension.js 呼叫你上一次掛載所回傳的清理函式。
  2. 它移除帶有這個 script 的 owner token、且來自較舊建置的宿主元素。
  3. 它再次執行你的 default export,藉此建出新的宿主元素。
  4. 它重新整理先前由它補進去的樣式表。
這就是清理函式為什麼重要。少了它,每次存檔都會在頁面上留下前一個宿主元素,元件就會越疊越多。 Extension.js 只會移除它能證明屬於這個 script、且屬於較舊建置的宿主元素。比這次掛載更早存在的宿主永遠不會被認領,另一個擴充功能的宿主也絕不會被動到。

同一頁面上的多個進入點

一個 content_scripts 區塊中的每個檔案都有自己的重新注入索引鍵。因此某個 script 的清理只會處置它自己的宿主,絕不會動到同伴的。 請給每個進入點各自的宿主元素。兩個進入點共用一個宿主,就會在清理時互相打架。

範本

Content scripts 分組中的每個範本都掛載到 shadow root: contentcontent-css-modulescontent-custom-fontcontent-envcontent-lesscontent-less-modulescontent-main-worldcontent-multi-one-entrycontent-multi-three-entriescontent-preactcontent-reactcontent-sasscontent-sass-modulescontent-sveltecontent-typescriptcontent-vue 產生 React 那個範本,看看 shadow root 裡面的框架根長什麼樣:
儲存庫:extension-js/examples/content-react 框架根需要在清理函式中做屬於它自己的拆卸:
src/content/scripts.jsx

最佳實務

  • 永遠回傳一個會移除宿主元素的清理函式。
  • 宿主元素上的 all: initial !important 要一直留著。
  • 除非你有理由隱藏這棵樹,否則請選 mode: "open"。closed 的根比較難除錯。
  • 查詢你自己的節點時,請在 shadowRoot 內部查,絕不要在 document 裡查。
  • 不要依賴 data-extjs-* 屬性。它們只為開發循環而存在。

後續步驟