偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

Chrome 插件開發(fā)指南

原創(chuàng) 精選
系統(tǒng) 瀏覽器 開發(fā)
開發(fā)插件需要使用前端技術(shù):html css javascript。本文就從入門開始講述如何開發(fā)一款 chrome 插件。
豐富的 chrome 插件極大的提升我們的工作效率和辛福感,比如大名鼎鼎的 adblock 廣告屏蔽、GoFullPage 網(wǎng)頁長截圖、evernote web clipper 收藏網(wǎng)頁。

一般來說,插件的原理是向頁面中注入 javascript 腳本,對頁面進行處理,比如屏蔽頁面中可能的廣告元素,改變某些元素的樣式,增加一些 UI。

開發(fā)插件需要使用前端技術(shù):html css javascript。

本文就從入門開始講述如何開發(fā)一款 chrome 插件。

注意:chrome 插件機制本身也在更新,本文講述的是目前普遍使用的 V2 插件的開發(fā)。

Manifest V3 is available beginning with Chrome 88, and the Chrome Web Store begins accepting MV3 extensions in January 2021.

插件構(gòu)成

chrome 插件通常由以下幾部分組成:

manifest.json:相當于插件的 meta 信息,包含插件的名稱、版本號、圖標、腳本文件名稱等,這個文件是每個插件都必須提供的,其他幾部分都是可選的。

background script:可以調(diào)用全部的 chrome 插件 API,實現(xiàn)跨域請求、網(wǎng)頁截屏、彈出 chrome 通知消息等功能。相當于在一個隱藏的瀏覽器頁面內(nèi)默默運行。

功能頁面:包括點擊插件圖標彈出的頁面(簡稱 popup)、插件的配置頁面(簡稱 options)。

content script:早期也被稱為 injected script,是插件注入到頁面的腳本,但是不會體現(xiàn)在頁面 DOM 結(jié)構(gòu)里。content script 可以操作 DOM,但是它和頁面其他的腳本是隔離的,訪問不到其他腳本定義的變量、函數(shù)等,相當于運行在單獨的沙盒里。content script 可以調(diào)用有限的 chrome 插件 API,網(wǎng)絡請求收到同源策略限制。

插件的架構(gòu)可以參考:https://developer.chrome.com/docs/extensions/mv2/architecture-overview/

重點說明以下幾點:

  1. browser action 和 page action:這倆我們可以理解為插件的按鈕。browser action 會固定在 chrome 的工具欄。而 page action 可以設置特定的網(wǎng)頁才顯示圖標,在地址欄的右端,如下圖:

圖片

大部分插件點擊之后會顯示 UI,也就是上文描述的插件功能頁面部分,一般稱為 popup 頁面,如下圖:

圖片

popup 無法通過程序打開,只能由用戶點擊打開。點擊 popup 之外的區(qū)域會導致 popup 收起。

page action 和 browser action 分別由 manifest.json 的 page_action 和 browser_action 字段配置。

  1. 由于 content script 受到同源策略的限制,所以一般網(wǎng)絡請求都交給 background script 處理。
  2. content script、插件功能頁面、background script 之間的通信架構(gòu)如下圖:

圖片

chrome 可以打開多個瀏覽器窗口,而一個窗口會有多個 tab,所以插件的結(jié)構(gòu)大致如下:

圖片

如上圖,功能頁面是每個 window 一份,但是每個 tab 都會注入 content script。

manifest.json

下文簡稱 manifest ,其中有這么幾個字段可以重點說明:

content_scripts

content_scripts 可以使用以下兩種方式注入頁面,這兩種方式并不沖突,可以結(jié)合使用。

聲明式注入

舉例如下:

{
"content_scripts": [
{
"matches": ["http://*/*", "https://*/*"],
"run_at": "document_idle",
"js": ["content.js"]
}
]
}

在 manifest 中聲明要加載的腳本,各個字段都比較直觀。其中:

  1. matches 表示頁面 url 匹配時才加載
  2. run_at? 表示在什么時機加載,一般是 document_idle,避免 content_scripts 影響頁面加載性能。

需要注意的是,如果用戶已經(jīng)打開了 N 個頁面,然后再安裝插件,這 N 個頁面除非重新刷新,否則是不會加載 manifest 聲明的 content_scripts。安裝插件之后新打開的頁面是可以加載 content_scripts 的。

所以需要在用戶點擊插件圖標時,探測頁面中的 content_scripts 是否存在(發(fā)送消息是否有響應/出錯),再提示用戶刷新頁面。

程序注入

還可以使用程序動態(tài)注入腳本,代碼如下:

chrome.tabs.executeScript({
file: "content.js",
});

比如用戶點擊插件圖標時執(zhí)行注入腳本,則無需刷新頁面,代碼如下:

// 監(jiān)聽插件圖標點擊事件
chrome.browserAction.onClicked.addListener(() => {
chrome.tabs.executeScript({
file: 'content.js',
});
});

值得注意的是,采用以上方式,用戶每次點擊插件圖標時,content.js 都會被執(zhí)行,可能會引起錯誤。

// content.js
let loaded = false;

if (!loaded) {
// do something
loaded = true;
}

console.log(loaded);

第一次執(zhí)行 content.js 會打印 false,而第二次執(zhí)行 content.js 則會報錯,提示 loaded 變量已經(jīng)聲明了。

由此可見 content.js 的執(zhí)行會影響其所在的沙盒。

我們可以這么做:

// content.js

if (!window.contentLoaded) {
// do something
window.contentLoaded = true;
}

console.log(window.contentLoaded);

使用沙盒內(nèi)的全局變量則可以避免 content.js 重復執(zhí)行帶來的問題。

綜上所述:聲明式只會注入一次,缺點是可能需要刷新頁面。程序式不需要刷新頁面,缺點是可能會注入多次。

permissions

該字段是一個字符串數(shù)組,用來聲明插件需要的權(quán)限,這樣才能調(diào)用某些 chrome API,常見的有:

  1. tabs
  2. activeTab
  3. contextMenus:網(wǎng)頁右鍵菜單,browser_action 右鍵菜單
  4. cookies:操作 cookie,和用戶登錄態(tài)相關(guān)的功能可能會用到該權(quán)限
  5. storage:插件存儲,不是 localStorage
  6. web_accessible_resources:網(wǎng)頁能訪問的插件內(nèi)部資源,比如插件提供 SDK 給頁面使用,如 ethereum 的 metamask 錢包插件。或者是修改 DOM 結(jié)構(gòu)用到了插件的樣式、圖片、字體等資源。

permissions 中還可以聲明多個 url patterns,表示插件需要訪問這些 url,比如和 API 通信。

background script

下文簡稱 background,可以理解它是在一個隱藏的 tab 中執(zhí)行,所在的頁面域名為空,這會影響對 document.cookie 的使用。

比如 background 需要和 a.com 通信。首先應該把 *://*.a.com/* 加入到 manifest 的 permissions 數(shù)組中。

當發(fā)送網(wǎng)絡請求時,瀏覽器會自動帶上 a.com 的 cookie,服務器的 set-cookie 也會對瀏覽器生效。這是符合預期的。

但是讀取 document.cookie 時,由于 background 所在的域名為空,a.com 被認為是第三方 cookie,會讀取不到。所以需要使用 chrome.cookies API 來讀取 cookie。

background 設置 document.cookie 時,不能指定域名,否則會設置失敗。比如:

// 會失敗,因為指定的域名和 background 所在的域名不符
document.cookie = `session=xxxxxxx; domain=a.com; max-age=9999999999; path=/`;

// 正確的做法,不要指定域名
document.cookie = `session=xxxxxxx; max-age=9999999999`;

一般不需要這么操作 cookie,但是可能依賴的 npm 包會操作 document.cookie,所以這里說明一下。

background 使用 tabs 接口操作瀏覽器的 tab 窗口,比如:

// 打開新 tab
async function open(url: string): Promise<number> {
return new Promise((resolve) => {
chrome.tabs.create(
{
url,
},
(tab) => resolve(tab.id!)
);
});
}

// 獲取活躍的 tab,通常是用戶正在瀏覽的頁面
async function getActiveTab(): Promise<chrome.tabs.Tab | null> {
return new Promise((resolve) => {
chrome.tabs.query(
{
active: true,
currentWindow: true,
},
(tabs) => {
if (tabs.length > 0) {
resolve(tabs[0]);
} else {
resolve(null);
}
}
);
});
}

// 將指定的 tab 變成活躍的
async function activate(
tabId?: number,
url?: string
): Promise<number | undefined> {
if (typeof tabId === "undefined") {
return tabId;
}

// firefox 不支持 selected 參數(shù)
// https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/tabs/update#parameters
const options: chrome.tabs.UpdateProperties = IS_FIREFOX
? { active: true }
: { selected: true };
if (url) {
options.url = url;
}

return new Promise((resolve) => {
chrome.tabs.update(tabId, options, () => resolve(tabId));
});
}

// 打開新窗口,或者是激活窗口
async function openOrActivate(url: string): Promise<number> {
const pattern = getUrlPattern(url);
return new Promise<number>((resolve) => {
chrome.tabs.query(
{
url: pattern,
},
(tabs) => {
if (tabs.length > 0 && tabs[0].id) {
return Tabs.activate(tabs[0].id);
} else {
this.open(url).then((id) => resolve(id));
}
}
);
});
}

content scripts

下文簡稱 content,它只能使用有限的 chrome API。

由于 content 可以訪問 DOM,可以用它來選擇、修改、刪除、增加網(wǎng)頁元素。

但是 content 是運行在隔離的空間(類似沙盒),所以如果需要和頁面的其他腳本通信,需要采用 window.postMessage 的方式。

比如頁面內(nèi)容如下:

<!-- index.html -->
<html>
<body>
<div id="app"></div>
<button id="btn" type="button">submit</button>
</body>
<script>
window.globalData = {
userId: 12345,
};
</script>
</html>

content 內(nèi)容如下:

// 成功
document.getElementById("app").innerHTML = "hello chrome";

// window.globalData 是 undefined
console.log(window.globalData);

資源注入

content 可以向頁面中注入 <script>,由此給頁面提供 SDK 等功能,注入的腳本和頁面自己的腳本一樣,都無法和 content 直接通信。

注意:注入的資源要先在 menifest 的 web_accessible_resources 字段中聲明。

// content 內(nèi)容
const script = document.createElement("script");
script.src = chrome.runtime.getURL("sdk.js");
document.body.appendChild(script);
// sdk.js
window.jsbridge = {
version: "1.0.1",
// ...
};

content 執(zhí)行之后,可以看到頁面結(jié)構(gòu)多了個 <script src="chrome-extension://xxxxxxxxxxxxx/sdk.js"></script>,xxxxxxxx 表示插件的 id,由 chrome 生成。

注意,注入的 sdk.js 腳本是可以被頁面內(nèi)其他腳本訪問到的(可以看作是頁面自己的腳本,只是 origin 是 chrome-extensions://xxxxxxxxxxxxx),如下:

document.getElementById("btn").addEventListener(
"click",
() => {
console.log(window.jsbridge.version);
},
false
);

通信

content 可以和 background、popup、options 使用 chrome API 通信,參考官方文檔:https://developer.chrome.com/docs/extensions/mv2/background_pages/

常用的通信 API 是 chrome.runtime.sendMessage。

UI

content 可以向頁面中注入 UI,比如 evernote 的剪輯插件。

圖片

前面提到過,點擊 popup 之外的區(qū)域會導致 popup 收起,操作 DOM 會導致 popup 隱藏,而 popup 無法用代碼主動打開,所以 evernote 的剪輯插件的 UI 就無法用 popup 來實現(xiàn)了。

這時候可以把 UI 作為 iframe 插入頁面,比如:

// content
const app = document.createElement("iframe");
app.src = chrome.runtime.getURL("app.html");
document.body.appendChild(app);

神奇的是 iframe 里的 javascript 是可以像 content 一樣和 background 通信的。

background 給 iframe 發(fā)送消息時,不僅需要指定所在 tab 的 id,還需要指定 iframe 的 id。這里說的 iframe id 類似 tab id,是 chrome 分配的,而不是 iframe 標簽的 id 屬性。

功能頁面

popup/options 和 background 的關(guān)系很親密,它們甚至可以通過 chrome.extension.getBackgroundPage()? 獲取到 background 的全局變量。所以它們直接的通信花樣很多,不過一般也是用 chrome.runtime 通信。

popup/options 和 content 之間的通信方式,可以 background -> content 通信類似。

options 用來設置插件,所以一般需要調(diào)用 chrome.storage 存儲配置。

適配其他瀏覽器

目前 chrome 插件適配工作量是比較小的,因為 edge、opera 都已經(jīng)切換到 chromium 內(nèi)核,firefox 也支持 chrome API。

不過需要查看用到的 API 是否支持,以及 API 的入?yún)ⅰ⒊鰠⑹欠褚恢?。比如前文提?firefox chrome.tabs.update 方法第一個參數(shù)不支持 selected 屬性。

firefox 還支持 browser API,和 chrome API 不同的是 browser API 不使用回調(diào)函數(shù),而是返回 promise。比如:

browser.tabs.query({ currentWindow: true }).then((res) => console.log(res));

chrome.tabs.query({ currentWindow: true }, (res) => {
console.log(res);
});

可以參考各瀏覽器的開發(fā)文檔:

  • firefox: https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Build_a_cross_browser_extension
  • edge: https://docs.microsoft.com/zh-cn/microsoft-edge/extensions-chromium/developer-guide/port-chrome-extension
  • 360: http://open.se.#/open/extension_dev/overview.html
  • 搜狗: http://ie.sogou.com/open/doc/

發(fā)布

  • chrome 發(fā)布插件需要花費 5 美元開通賬號:https://developer.chrome.com/docs/webstore/register/
  • firefox 發(fā)布文檔:https://addons.mozilla.org/en-US/developers/
  • edge:https://docs.microsoft.com/zh-cn/microsoft-edge/extensions-chromium/publish/create-dev-account

總結(jié)

總體來說,chrome 插件開發(fā)對前端工程師來說還是比較容易的。

責任編輯:未麗燕 來源: 字節(jié)跳動技術(shù)團隊
相關(guān)推薦

2012-01-04 16:21:11

2014-08-01 09:57:52

Node.jsNode.js插件

2011-07-25 16:21:22

Sencha touc

2023-11-20 09:33:43

開發(fā)指南

2023-08-17 10:20:18

RabbitMQ系統(tǒng)

2012-03-26 09:27:40

谷歌安卓開發(fā)谷歌安卓

2023-05-15 18:44:07

前端開發(fā)

2011-06-09 18:24:36

QT Wince

2009-06-24 16:30:21

JSF組件模型

2015-11-12 16:14:52

Python開發(fā)實踐

2015-12-16 10:30:18

前端開發(fā)指南

2010-06-13 09:27:56

Widget開發(fā)

2019-10-31 08:00:00

機器學習人工智能AI

2011-12-29 10:48:49

移動Web

2021-08-09 09:47:34

Blazor 路由開發(fā)

2011-04-18 11:00:34

使用音頻BlackBerry

2012-05-18 10:08:56

TitaniumAndroid

2021-06-21 15:21:52

鴻蒙HarmonyOS應用開發(fā)

2025-03-11 14:45:31

2022-03-23 15:17:00

Harmony鴻蒙操作系統(tǒng)
點贊
收藏

51CTO技術(shù)棧公眾號