成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

Chrome 插件開(kāi)發(fā)指南

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

一般來(lái)說(shuō),插件的原理是向頁(yè)面中注入 javascript 腳本,對(duì)頁(yè)面進(jìn)行處理,比如屏蔽頁(yè)面中可能的廣告元素,改變某些元素的樣式,增加一些 UI。

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

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

注意:chrome 插件機(jī)制本身也在更新,本文講述的是目前普遍使用的 V2 插件的開(kāi)發(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:相當(dāng)于插件的 meta 信息,包含插件的名稱(chēng)、版本號(hào)、圖標(biāo)、腳本文件名稱(chēng)等,這個(gè)文件是每個(gè)插件都必須提供的,其他幾部分都是可選的。

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

功能頁(yè)面:包括點(diǎn)擊插件圖標(biāo)彈出的頁(yè)面(簡(jiǎn)稱(chēng) popup)、插件的配置頁(yè)面(簡(jiǎn)稱(chēng) options)。

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

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

重點(diǎn)說(shuō)明以下幾點(diǎn):

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

圖片

大部分插件點(diǎn)擊之后會(huì)顯示 UI,也就是上文描述的插件功能頁(yè)面部分,一般稱(chēng)為 popup 頁(yè)面,如下圖:

圖片

popup 無(wú)法通過(guò)程序打開(kāi),只能由用戶(hù)點(diǎn)擊打開(kāi)。點(diǎn)擊 popup 之外的區(qū)域會(huì)導(dǎo)致 popup 收起。

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

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

圖片

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

圖片

如上圖,功能頁(yè)面是每個(gè) window 一份,但是每個(gè) tab 都會(huì)注入 content script。

manifest.json

下文簡(jiǎn)稱(chēng) manifest ,其中有這么幾個(gè)字段可以重點(diǎn)說(shuō)明:

content_scripts

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

聲明式注入

舉例如下:

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

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

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

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

所以需要在用戶(hù)點(diǎn)擊插件圖標(biāo)時(shí),探測(cè)頁(yè)面中的 content_scripts 是否存在(發(fā)送消息是否有響應(yīng)/出錯(cuò)),再提示用戶(hù)刷新頁(yè)面。

程序注入

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

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

比如用戶(hù)點(diǎn)擊插件圖標(biāo)時(shí)執(zhí)行注入腳本,則無(wú)需刷新頁(yè)面,代碼如下:

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

值得注意的是,采用以上方式,用戶(hù)每次點(diǎn)擊插件圖標(biāo)時(shí),content.js 都會(huì)被執(zhí)行,可能會(huì)引起錯(cuò)誤。

// content.js
let loaded = false;

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

console.log(loaded);

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

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

我們可以這么做:

// content.js

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

console.log(window.contentLoaded);

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

綜上所述:聲明式只會(huì)注入一次,缺點(diǎn)是可能需要刷新頁(yè)面。程序式不需要刷新頁(yè)面,缺點(diǎn)是可能會(huì)注入多次。

permissions

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

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

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

background script

下文簡(jiǎn)稱(chēng) background,可以理解它是在一個(gè)隱藏的 tab 中執(zhí)行,所在的頁(yè)面域名為空,這會(huì)影響對(duì) document.cookie 的使用。

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

當(dāng)發(fā)送網(wǎng)絡(luò)請(qǐng)求時(shí),瀏覽器會(huì)自動(dòng)帶上 a.com 的 cookie,服務(wù)器的 set-cookie 也會(huì)對(duì)瀏覽器生效。這是符合預(yù)期的。

但是讀取 document.cookie 時(shí),由于 background 所在的域名為空,a.com 被認(rèn)為是第三方 cookie,會(huì)讀取不到。所以需要使用 chrome.cookies API 來(lái)讀取 cookie。

background 設(shè)置 document.cookie 時(shí),不能指定域名,否則會(huì)設(shè)置失敗。比如:

// 會(huì)失敗,因?yàn)橹付ǖ挠蛎?background 所在的域名不符
document.cookie = `session=xxxxxxx; domain=a.com; max-age=9999999999; path=/`;

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

一般不需要這么操作 cookie,但是可能依賴(lài)的 npm 包會(huì)操作 document.cookie,所以這里說(shuō)明一下。

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

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

// 獲取活躍的 tab,通常是用戶(hù)正在瀏覽的頁(yè)面
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));
});
}

// 打開(kāi)新窗口,或者是激活窗口
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

下文簡(jiǎn)稱(chēng) content,它只能使用有限的 chrome API。

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

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

比如頁(yè)面內(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 可以向頁(yè)面中注入 <script>,由此給頁(yè)面提供 SDK 等功能,注入的腳本和頁(yè)面自己的腳本一樣,都無(wú)法和 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í)行之后,可以看到頁(yè)面結(jié)構(gòu)多了個(gè) <script src="chrome-extension://xxxxxxxxxxxxx/sdk.js"></script>,xxxxxxxx 表示插件的 id,由 chrome 生成。

注意,注入的 sdk.js 腳本是可以被頁(yè)面內(nèi)其他腳本訪問(wèn)到的(可以看作是頁(yè)面自己的腳本,只是 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 可以向頁(yè)面中注入 UI,比如 evernote 的剪輯插件。

圖片

前面提到過(guò),點(diǎn)擊 popup 之外的區(qū)域會(huì)導(dǎo)致 popup 收起,操作 DOM 會(huì)導(dǎo)致 popup 隱藏,而 popup 無(wú)法用代碼主動(dòng)打開(kāi),所以 evernote 的剪輯插件的 UI 就無(wú)法用 popup 來(lái)實(shí)現(xiàn)了。

這時(shí)候可以把 UI 作為 iframe 插入頁(yè)面,比如:

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

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

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

功能頁(yè)面

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

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

options 用來(lái)設(shè)置插件,所以一般需要調(diào)用 chrome.storage 存儲(chǔ)配置。

適配其他瀏覽器

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

不過(guò)需要查看用到的 API 是否支持,以及 API 的入?yún)ⅰ⒊鰠⑹欠褚恢隆1热缜拔奶岬?firefox chrome.tabs.update 方法第一個(gè)參數(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);
});

可以參考各瀏覽器的開(kāi)發(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.360.cn/open/extension_dev/overview.html
  • 搜狗: http://ie.sogou.com/open/doc/

發(fā)布

  • chrome 發(fā)布插件需要花費(fèi) 5 美元開(kāi)通賬號(hào):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é)

總體來(lái)說(shuō),chrome 插件開(kāi)發(fā)對(duì)前端工程師來(lái)說(shuō)還是比較容易的。

責(zé)任編輯:未麗燕 來(lái)源: 字節(jié)跳動(dòng)技術(shù)團(tuán)隊(duì)
相關(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

開(kāi)發(fā)指南

2023-08-17 10:20:18

RabbitMQ系統(tǒng)

2011-06-09 18:24:36

QT Wince

2012-03-26 09:27:40

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

2023-05-15 18:44:07

前端開(kāi)發(fā)

2009-06-24 16:30:21

JSF組件模型

2015-11-12 16:14:52

Python開(kāi)發(fā)實(shí)踐

2015-12-16 10:30:18

前端開(kāi)發(fā)指南

2010-06-13 09:27:56

Widget開(kāi)發(fā)

2019-10-31 08:00:00

機(jī)器學(xué)習(xí)人工智能AI

2021-08-09 09:47:34

Blazor 路由開(kāi)發(fā)

2011-12-29 10:48:49

移動(dòng)Web

2011-04-18 11:00:34

使用音頻BlackBerry

2012-05-18 10:08:56

TitaniumAndroid

2021-06-21 15:21:52

鴻蒙HarmonyOS應(yīng)用開(kāi)發(fā)

2025-03-11 14:45:31

2011-08-02 17:58:09

iPhone開(kāi)發(fā) 事件
點(diǎn)贊
收藏

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

主站蜘蛛池模板: 欧洲色综合 | 天天草av | 日韩一区二区久久 | 亚洲性爰| 欧美国产精品一区二区三区 | 国产96在线| 久草网址 | 亚洲国产免费 | av手机免费在线观看 | 亚洲精品无 | 国产精品久久久久久久岛一牛影视 | 91成人在线 | 91欧美精品成人综合在线观看 | 日韩av手机在线观看 | 婷婷在线网站 | 亚洲男人天堂2024 | 成人午夜精品一区二区三区 | 国产亚洲欧美日韩精品一区二区三区 | 亚洲国产精品一区 | 欧美不卡视频 | 69热视频在线观看 | 视频一区二区三区在线观看 | 手机在线观看 | 一级看片免费视频 | 91精品国产综合久久久久久丝袜 | 黄色一级视频免费 | 动漫www.被爆羞羞av44 | 久视频在线观看 | 欧美日韩一区二区三区在线观看 | 精品亚洲一区二区三区 | 福利视频一区二区 | 成年男女免费视频网站 | 欧美一区二区三区大片 | 国产激情一区二区三区 | 精品久久影院 | 亚洲 欧美 综合 | 精品国产乱码久久久久久蜜退臀 | 国产精品中文字幕在线 | 高清视频一区二区三区 | 成人免费黄色片 | 日韩中文字幕网 |