7分鐘學會寫一個瀏覽器外掛——突破某SDN未登入禁止複製的限制

獨釣寒江雪發表於2021-10-18

  嚴格意義上說,本篇文章要討論的應該是 “Chrome 擴充套件(Extension)”而不是“Chrome 外掛(Plugins)”,大家不妨先思考一個問題:“瀏覽器外掛”和“瀏覽器擴充套件”之間有什麼區別?如果您對此感到厭煩,不妨直接跳過此小節的閱讀。

關於 Chrome 外掛與擴充套件的誤區

  Chrome 瀏覽器自 2008 年誕生以來發生了很多事情,它是目前功能最完備、對新特性支援最充分的 Web 瀏覽器,Chrome 的出現,在某種意義上擴充套件了我們對瀏覽器功能的定義,並且它支援第三方開發人員製作的應用程式、外掛和擴充套件。但是這三者有什麼區別呢?如果你想獲得廣告攔截器,那是應用程式還是擴充套件程式?在 HTML5 之前曾紅極一時、可以讓網站內容變得更豐富、帶來了視訊/動畫/遊戲卻因安全問題一直飽受詬病的 Adobe Flash,是屬於瀏覽器擴充套件還是外掛?

  在此,我們先來了解一下 Chrome 應用程式、外掛和擴充套件程式之間(Chrome apps, plugins and extensions)的區別,以便你能夠始終知道自己需要的是什麼。

Chrome 應用程式(Chrome Apps)

  Chrome 應用程式的概念,可能是 Chrome 應用程式、外掛和擴充套件程式中最模糊的,因為其實 Google 本身可以互換地指代應用程式和擴充套件程式。想要證明這一點也不難,你可以轉到 Chrome 網上應用店中的擴充套件程式頁面https://chrome.google.com/webstore/category/extensions),在位址列中將“extensions”一詞替換為“apps”,它實際上會重定向回擴充套件(extensions)頁面!

  Web Store 曾經有一個專門的“應用程式(Apps)”部分,在那裡提供獨立的桌面應用程式。但在 2017 年底,谷歌移除了這些傳統的 Chrome 應用程式,因為 Google 推動了“漸進式網路應用程式(Progressive Web Apps,即 PWA)”的構想,從本質上將網站轉變為從桌面或手機即時訪問的應用程式,所以如今的搜尋框下方只有“Extensions”和“Themes”兩部分了。

extensions

  對於普通的 PC 使用者來說,Chrome 應用程式如今已融入擴充套件程式中。如果你正在為 Chrome 尋找廣告攔截器、Web Clipper 等,無論你搜尋“Chrome app”還是“Chrome extension”,您最終都會得到一個擴充套件程式。

Chrome 擴充套件程式(Chrome Extensions)

extensions

  Chrome 擴充套件程式是 Chrome 的附加元件,旨在以各種方式改善您的體驗。這種體驗的改善可以從資料保護程式到廣告攔截器,再到讓你在 Chrome 中雙擊單詞並立即瞭解其含義的字典。您可以在 Chrome 網上應用店中找到擴充套件程式,當您安裝一個擴充套件程式時,它會在 Chrome 瀏覽器的右上角顯示一個小圖示。

  擴充套件程式可以增加瀏覽器本身的功能,也可以呼叫瀏覽器的 API,並且同一個瀏覽器的擴充套件一般也都是可以跨作業系統使用的。比如,你在 Windows 使用的那些 Chrome 擴充套件,換到 Mac 平臺上也一樣能用。

  擴充套件為瀏覽器新增特性與功能,它一般通過 web 技術——HTML,CSS 還有 JavaScript 來建立。擴充套件其實是一個壓縮檔案,Firefox 的擴充套件是.xpi、Chrome 擴充套件格式為.crx

Chrome 外掛(Chrome Plugins)

  外掛最好被描述為“插入”Chrome 的程式碼包,允許 Web 開發人員將某些功能、動畫、視訊等嵌入到他們的網站中,外掛可呼叫作業系統的 API,並且不同作業系統的外掛一般不能混用,外掛主要用於實現瀏覽器無法獨立實現的功能。

  也就是說,外掛使得別的程式才能處理的內容在瀏覽器的頁面中得以展現和處理。所以外掛一般實現的都是比較底層的功能,一旦出現問題,往往就會牽涉到整個作業系統,像 Flash 外掛就屬於經常被扒出高危漏洞的那一類。在 Chrome 57 版本之前,你可以在位址列中的chrome://plugins中檢視 Chrome 的所有外掛列表,其中包括 Adob​​e Flash Player、Chrome PDF Viewer 和 Java 外掛等,現在chrome://chrome-urls/中已經找不到chrome://plugins了。

  chrome://plugins不再存在的部分原因是出於安全考慮,Chrome 不再支援 NPAPI 外掛——一些外掛不再工作,而其他外掛已以各種方式整合到 Chrome 中。例如,基於 Java 的小程式不再在瀏覽器中執行,PDF 檢視器等功能已直接整合到瀏覽器中。因此,有點像應用程式(Chrome Apps),Chrome 外掛的也正在逐步淘汰或整合到瀏覽器的主體中。

  外掛的格式通常是二進位制檔案,如 windows 下的外掛一般是dll,linux 下的外掛一般是.so格式。

  從本質上講,“Chrome 應用程式”會被 PWA 所替代,而這些年來外掛基本上已被棄用,所以你在 Chrome 中唯一需要關心的就是擴充套件程式。

  真正意義上的 Chrome 外掛是更底層的瀏覽器功能擴充套件,但是瀏覽器外掛和擴充套件這兩個概念早已被混淆。鑑於大部分人對 Chrome 外掛的叫法已經習慣,本文也全部採用這種叫法,但您應該知道,本文所描述的 Chrome 外掛實際上指的是 Chrome 擴充套件

順便簡單做個補充,除了以上提到的 3 個概念,你可能還看到過ActiveX(控制元件)、Addon 等概念,其實 ActiveX 是 IE 核心瀏覽器下的外掛(Plugin),隨著 IE 瀏覽器從 Windows 11 中消失,並將在 2022 年正式退出歷史舞臺,我們無需過多關注;Addon 是屬於 Mozilla 系瀏覽器的,中文名字叫附加元件,其實和瀏覽器擴充套件是同一類應用。

瀏覽器外掛的核心元素

manifest.json

  一個 Chrome 擴充套件其實就是一個配置(入口)檔案 manifest.json 和一系列 html、css、js、圖片檔案的集合,擴充套件/外掛從它們的manifest.json開始,所以manifest.json是一個Chrome 外掛最重要也是必不可少的檔案,用來配置所有和外掛相關的配置,必須放在根目錄。一般至少應該包含下面這些配置:

// manifest.json
{
  "name": "Getting Started Example", // 擴充套件的名稱(name)
  "version": "1.0", // 外掛版本號
  "description": "Build an Extension!", // 外掛描述
  "manifest_version": 2, // manifest 版本,目前是必須是2或者3

  // 指定擴充套件在Chrome 工具欄中的圖示,它定義了擴充套件圖示檔案位置(default_icon)、
  // 懸浮提示(default_title)和點選擴充套件圖示所顯示的頁面位置(default_popup)
  "browser_action": {
    "default_title": "Hello, 某SDN!",
    "default_icon": "/images/logo.png", // 瀏覽器右上角圖示設定
    "default_popup": "popup.html"
  }
}

當然,一個manifest.json檔案可以包含很多東西,可以參考Manifest file format,後續使用到其他配置會在註釋中進行說明。

VSCode 在書寫.json 檔案的備註時標紅提示 Comments are not permitted in JSON.(521),此時只需點選 VSCode 介面右下角的“JSON”,在頂部展現出的框中輸入“JSON with Comments”,點選使用即可解決。

popup.html

  這個名稱不是固定的,取決於 manifest.json中對browser_action的“default_popup”欄位的配置,它定義了點選外掛的圖示所展示的彈窗頁面 HTML,popup.html像普通 html 一樣可以直接引入其他 js 檔案。需要注意的一點是,這裡面引入的 js,如果直接操作 DOM,操作的是 popup.html 產生的 DOM,而不是瀏覽器當前展示頁面的 DOM。

content_scripts

  這一項來自於manifest.jsoncontent_scripts配置項,宣告瞭需要直接注入頁面的 JS,這裡的 JS 可以直接操作當前頁面的 DOM 物件,所以這個檔案在需要對開啟的頁面進行操作時會很有用。

開發除錯

  Chrome 外掛沒有嚴格的專案結構要求,只要保證根目錄有一個 manifest.json 即可,也不需要專門的 IDE 進行開發。

  開發除錯時,進入外掛管理頁面最簡潔的方式是在位址列輸入chrome://extensions/進行訪問。其他兩種進入外掛管理頁面的方式如下圖所示:

extensions

extensions

  開啟右上角開發者模式便可以資料夾的形式直接載入外掛,否則只能安裝.crx 格式的檔案。預設情況下,Chrome 要求外掛必須從它的 Chrome 應用商店安裝,其它任何網站下載的以及自己打包的都無法直接安裝。所以,其實我們可以把 crx 檔案解壓,然後通過開發者模式直接載入。

extensions

  開發過程中,程式碼有任何改動都必須重新載入外掛,點選對應外掛的重新載入按鈕或者重新整理當前外掛管理頁面均可。如果出現錯誤,將會出現類似下面的介面,更改後,你需要先點選“錯誤”按鈕進入彈窗對錯誤進行清除。

extensions

開發過程中遇到的更多細節可以參考官方Debugging extensions文件。

3 分鐘寫一個瀏覽器外掛,解決某 SDN 未登入無法複製程式碼的問題

  我們知道,前段時間某 SDN 更新後,未登入時,無法複製文章中的程式碼,給開發者帶來一定不便。今天我們就花 3 分鐘時間寫一個瀏覽器外掛,突破這種限制。

  基本思路其實很簡單,通過將當前頁面的document.body.contentEditable值設定為true來達到可複製的效果。當然,這個我們通過控制檯皮膚或者“自定義書籤“的方式也能輕鬆實現,這裡只是讓大家體驗一下簡單外掛的開發,避免出手就是”Hello World!“,?

某SDN給程式碼塊兒設定了user-select: none;,導致無法進行選中複製,從這裡入手亦可。

書籤功能的實現可參考前端裝逼技巧 108 式(一)—— 打工人

  下面將逐步介紹專案各檔案相關資訊,您也可以直接點選這裡線上檢視完整程式碼。整個專案核心程式碼不足 30 行,相信您能藉此迅速入門 Chrome 外掛開發,激發興趣和潛能,寫出更有用的外掛或者應用。

目錄結構

  目錄結構圖:
extensions

  目錄解釋:

  • images

    • logo.png:外掛圖示。
  • js

    • popup.js:彈窗 popup.html 中使用的 js;
    • content_script.js:需要直接注入頁面的 js;
  • manifest.json:配置檔案,外掛開發中的必備項;
  • popup.html:外掛彈窗;
  • style.css:彈窗樣式檔案;

manifest.json 配置檔案

{
  // 清單檔案的版本,必須是2或者3,
  // 文件見 https://developer.chrome.com/docs/extensions/mv3/manifest/manifest_version/
  "manifest_version": 2,
  // 外掛的名稱
  "name": "Copy SDN",
  // 外掛的版本
  "version": "1.0.0",
  // 外掛描述
  "description": "不登入依然可以在某SDN頁面進行程式碼複製!",
  // 指定擴充套件在Chrome 工具欄中的圖示,它定義了擴充套件圖示檔案位置(default_icon)、
  // 懸浮提示(default_title)和點選擴充套件圖示所顯示的頁面位置(default_popup)
  "browser_action": {
    "default_title": "Hello, 某SDN!",
    "default_icon": "/images/logo.png", // 瀏覽器右上角圖示設定
    "default_popup": "popup.html"
  },
  // https://developer.chrome.com/docs/extensions/mv2/manifest/icons/
  // 128x128 的圖示;它在安裝期間和 Chrome 網上應用店使用
  // 48x48 圖示,用於擴充套件程式管理頁面 (chrome://extensions)
  // 16x16 圖示用作擴充套件頁面的收藏夾圖示
  // 這裡只寫一個其實也是可以的
  "icons": {
    "128": "/images/logo.png"
  },
  // 需要直接注入頁面的JS
  "content_scripts": [
    {
      //"matches": ["http://*/*", "https://*/*"],"<all_urls>" 表示匹配所有地址
      "matches": ["https://blog.csdn.net/*"],
      // 多個JS按順序注入
      "js": ["/js/content_script.js"],
      // "css": ["css/custom.css"],
      // 程式碼注入的時間,可選值: "document_start", "document_end", or "document_idle",
      // document_idle表示頁面空閒時,為預設值
      "run_at": "document_start"
    }
  ],
  // 定義了擴充套件需要向 Chrome 申請的許可權,比如通過 XMLHttpRequest 跨域請求資料、訪問瀏覽器選項卡(tabs)
  // 獲取當前活動選項卡(activeTab)、瀏覽器通知(notifications)、儲存(storage)等,可以根據需要新增。
  "permissions": ["tabs"]
}

popup.html 和 css 製作外掛彈窗

popup.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <link rel="stylesheet" href="./style.css" />
    <title>Copy SDN</title>
  </head>

  <body>
    允許複製Code:<input type="checkbox" class="switch" id="toggle" />
    <!-- 這裡引入了popup.js,來給popup彈窗新增一些互動功能 -->
    <script src="./js/popup.js"></script>
  </body>
</html>

彈窗樣式檔案style.css

body {
  width: 160px;
  height: 24px;
  background-color: lavender;
  display: flex;
  justify-content: center;
  align-items: center;
}

/* Switch開關樣式 */
input[type='checkbox'].switch {
  outline: none;
  appearance: none;
  -webkit-appearance: none;
  -moz-appearance: none;
  position: relative;
  width: 40px;
  height: 20px;
  background: #ccc;
  border-radius: 10px;
  transition: border-color 0.3s, background-color 0.3s;
}

input[type='checkbox'].switch::after {
  content: '';
  display: inline-block;
  width: 1rem;
  height: 1rem;
  border-radius: 50%;
  background: #fff;
  box-shadow: 0, 0, 2px, #999;
  transition: 0.4s;
  top: 2px;
  position: absolute;
  left: 2px;
}

input[type='checkbox'].switch:checked {
  background: rgb(19, 206, 102);
}

/* 當input[type=checkbox]被選中時:偽元素顯示下面樣式 位置發生變化 */
input[type='checkbox'].switch:checked::after {
  content: '';
  position: absolute;
  left: 55%;
  top: 2px;
}

  以上程式碼將構建出如下外掛彈窗 UI:

extensions

popup.js 給彈窗新增互動

// 這裡的js其實是操作popup.html產生的dom的
document.addEventListener('DOMContentLoaded', function () {
  // 獲取開關按鈕的初始值。這裡{ type: 'get_editable' }是可以隨意定義的,可以傳遞任何你想傳遞的資訊
  sendMessageToContentScript({ type: 'get_editable' }, (response) => {
    toggle.checked = ['true', true].includes(response) ? 'checked' : null;
  });

  // 切換contentEditable狀態
  toggle.addEventListener('change', () => {
    sendMessageToContentScript({ type: 'toggle' });
  });
});

// 向content_scripts傳送訊息的函式
function sendMessageToContentScript(message, callback) {
  // 這裡用到了tabs,所以前面配置檔案需要配置"permissions": ["tabs"]
  chrome.tabs.query({ active: true, currentWindow: true }, function (tabs) {
    chrome.tabs.sendMessage(tabs[0].id, message, (response) => {
      if (callback) callback(response);
    });
  });
}

content_script.js 向頁面注入 JS

// 所謂content-scripts,其實就是Chrome外掛中向頁面注入指令碼的一種形式(雖然名為script,其實還可以包括css),
// 藉助content-scripts我們可以實現通過配置的方式輕鬆向指定頁面注入JS和CSS(如果需要動態注入,可以參考下文),
// 最常見的比如:廣告遮蔽、頁面CSS定製,等等。

// 接收訊息
chrome.runtime.onMessage.addListener(function (request, sender, sendResponse) {
  // 資料處理和返回。是不是有點類似redux中reducer資料處理的感覺
  switch (request.type) {
    case 'get_editable':
      // 將當前文件是否可編輯的資訊返回給popup,控制開關的形態
      sendResponse(document.body.contentEditable);
      break;
    case 'toggle':
      // 切換可編輯狀態
      document.body.contentEditable = ![true, 'true'].includes(
        document.body.contentEditable
      );
    default:
      break;
  }
});

打包與釋出

  在外掛管理頁左上角有一個“打包擴充套件程式”的按鈕,點選就會出現如下介面,選擇要打包的資料夾進行打包即可,你會得到一個.crx的壓縮檔案,這個實際上就是你經常安裝外掛時安裝的壓縮包檔案。

extensions
extensions

  要釋出到 Google 應用商店的話,需要先支付 5 美元的註冊費成為開發者:Register as a Chrome Web Store Developer

extensions

參考資料

  本文首發於個人部落格,歡迎指正和star

相關文章