寫 html 頁面沒意思,來挑戰 chrome 插件開發

谷歌瀏覽器插件開發是指開發可以在谷歌瀏覽器中運行的擴展程序,可以爲用戶提供額外的功能和定製化的體驗。谷歌瀏覽器插件通常由 HTML、CSS 和 JavaScript 組成,非常利於前端開發者。 開發者可以利用這些技術在瀏覽器中添加新的功能、修改現有功能或者與網頁進行交互。

要開發谷歌瀏覽器插件,開發者通常需要創建一個包含 * 清單文件(manifest.json)、背景腳本(background script)、內容腳本(content script)* 等文件的項目結構。清單文件是插件的配置文件,包含插件的名稱、版本、描述、權限以及其他相關信息。背景腳本用於處理插件的後臺邏輯,而內容腳本則用於在網頁中執行 JavaScript 代碼。

谷歌瀏覽器插件可以實現各種功能,例如添加新的工具欄按鈕、修改網頁內容、捕獲用戶輸入、與後臺服務器進行通信等。開發者可以通過谷歌瀏覽器插件 API 來訪問瀏覽器的各種功能和數據,實現各種定製化的需求。插件開發涉及的要點:

基礎配置

開發谷歌瀏覽器插件,最重要的文件 manifest.json

{
  "name""Getting Started Example",  // 插件名稱
  "description""Build an Extension!", // 插件描述
  "version""1.0", // 版本
  "manifest_version": 3, // 指定插件版本,這個很重要,指定什麼版本就用什麼樣的api,不能用錯了
  "background"{
    "service_worker""background.js" // 指定background腳本的路徑
  },
  "action"{
    "default_popup""popup.html", // 指定popup的路徑
    "default_icon"{  // 指定popup的圖標,不同尺寸
      "16""/images/icon16.png",
      "32""/images/icon32.png",
      "48""/images/icon48.png",
      "128""/images/icon128.png"
    }
  },
  "icons"{ // 指定插件的圖標,不同尺寸
    "16""/images/icon16.png",
    "32""/images/icon32.png",
    "48""/images/icon48.png",
    "128""/images/icon128.png"
  },
  "permissions"[],// 指定應該在腳本中注入那些變量方法,後文再詳細說
  "options_page""options.html",
  "content_scripts"[ // 指定content腳本配置
    {
      "js"[ "content.js"], // content腳本路徑
      "css":[ "content.css" ],// content的css
      "matches"["<all_urls>"] // 對匹配到的tab起作用。all_urls就是全部都起作用
    }
  ]
}

manifest_version:對應 chrome API 插件版本, 瀏覽器插件採用的版本,目前共 2 種版本,是 2 和最新版 3

官方實例 [1]

官方教程 [2]

打開 pop 彈窗頁面

設置 action 的 default_popup 屬性

{
  "name""Hello world",
  "description""show 'hello world'!",
  "version""1.0",
  "manifest_version": 3,
  "action"{
    "default_popup""popup.html",
    "default_icon"{
      "16""/images/icon16.png",
      "32""/images/icon32.png",
      "48""/images/icon48.png",
      "128""/images/icon128.png"
    }
  },
  "permissions":["tabs""storage""activeTab""idle"],
  "background"{
    "service_worker""background.js"
  },
  "content_scripts"[
    {
      "js"[ "content.js"],
      "css":[ "content.css" ],
      "matches"["<all_urls>"]
    }
  ]
}

創建 popup.html

<!DOCTYPE html>
<html lang="en">

  <head>
    <meta charset="UTF-8">
    <meta >
    <title>顯示出hello world</title>
    <link rel="stylesheet" type="text/css" href="popup.css">
  </head>

  <body>
    <h1>顯示出hello world</h1>
    <button id="clickBtn">點擊按鈕</button>
    <script src="popup.js"></script>
  </body>
</html>

文件可以通過鏈接引入 css、js。

body {
    width: 600px;
    height: 300px;
}
h1 {
    background-color: antiquewhite;
    font-weight: 100;
}
console.log(document.getElementById('clickBtn'));
document.getElementById('clickBtn').addEventListener('click'function () {
  console.log('clicked');
});

點擊插件圖標

點擊圖標可以看到如下的 popup 的頁面。

調試 popup.js 的方法

通過 background 打開獨立頁面

基於backgroundservice_workerAPI 可以打開一個獨立後臺運行腳本。此腳本會隨着插件安裝,初始化執行一次,然後一直在後臺運行。可以用來存儲瀏覽器的全局狀態數據。background 腳本是長時間運行在後臺,隨着瀏覽器打開就運行,直到瀏覽器關閉而結束運行。通常把需要一直運行的、啓動就運行的、全局公用的數據放到 background 腳本。

chrome.action.onClicked.addListener(function () {
  chrome.tabs.create({
    url: chrome.runtime.getURL('newPage.html')
  });
});

爲了打開獨立頁面,需要修改manifest.json

{
  "name""newPage",
  "description""Demonstrates the chrome.tabs API and the chrome.windows API by providing a user interface to manage tabs and windows.",
  "version""0.1",
  "permissions"["tabs"],
  "background"{
    "service_worker""service-worker.js"
  },
  "action"{
    "default_title""Show tab inspector"
  },
  "manifest_version"3
}

爲了實現打開獨立頁面,在 manifest.json 中就不能在配置 action:default_popupnewPage.js文件中可以使用 chrome.tabs[3] 和 chrome.windows[4]API;可以使用 chrome.runtime.getUrl[5] 跳轉一個頁面。

chrome.runtime.onInstalled.addListener(async () ={
  chrome.tabs.create(
    {
      url: chrome.runtime.getURL('newPage.html'),
    }
  );
});

content 內容腳本

content-scripts(內容腳本)是在網頁上下文中運行的文件。通過使用標準的文檔對象模型 (DOM),它能夠讀取瀏覽器訪問的網頁的詳細信息,可以對打開的頁面進行更改,還可以將 DOM 信息傳遞給其父級插件。內容腳本相對於 background 還是有一些訪問 API 上的限制,它可以直接訪問以下 chrome 的 API

content.js運行於一個獨立、隔離的環境,它不會和主頁面的腳本或者其他插件的內容腳本發生衝突 有 2 種方式添加 content 腳本

在配置中設置

"content_scripts"[
  {
    "js"[ "content.js"],
    "css":[ "content.css" ],
    "matches"["<all_urls>"]
  }
]

content_scripts 屬性除了配置 js,還可以設置 css 樣式,來實現修改頁面的樣式。matches 表示需要匹配的頁面;除了這 3 個屬性,還有

動態配置注入

在特定時刻才進行注入,比如點擊了某個按鈕,或者指定的時刻 需要在popup.jsbackground.js中執行注入的代碼。

chrome.tabs.executeScript(tabs[0].id, {
  code: 'document.body.style.backgroundColor = "red";',
});

也可以將整個 content.js 進行注入

chrome.tabs.executeScript(tabs[0].id, {
  file: "content.js",
});

利用 content 製作一個彈窗工具

某天不小心讓你的女神生氣了,爲了能夠道歉爭取到原諒,你是否可以寫一個道歉信貼到每一個頁面上,當女神打開網站,看到每個頁面都會有道歉內容。

道歉信內容自己寫哈,這個具體看你的誠意。下面設置 2 個按鈕,原諒和不原諒。點擊原諒,就可以關閉彈窗。點擊不原諒,這個彈窗調整 css 佈局位置繼續顯示。(有點像惡意貼片廣告了)

下面設置 content.js 的內容

let newDiv = document.createElement('div');
newDiv.innerHTML = `<div id="wrapper">
  <h3>小仙女~消消氣</h3>
  <div><button id="cancel">已消氣</button>
  <button id="reject">不原諒</button></div>
</div>`;
newDiv.id = 'newDiv';
document.body.appendChild(newDiv);
const cancelBtn = document.querySelector('#cancel');
const rejectBtn = document.querySelector('#reject');
cancelBtn.onclick = function() {
  document.body.removeChild(newDiv);
  chrome.storage.sync.set({ state: 'cancel' }(data) ={
  });
}
rejectBtn.onclick = function() {
  newDiv.style.bottom = Math.random() * 200 + 10 + "px";
  newDiv.style.right = Math.random() * 800 + 10 + "px";
}
// chrome.storage.sync.get({ state: '' }(data) ={
//   if (data.state === 'cancel') {
//     document.body.removeChild(newDiv);
//   }
// });

content.css 佈局樣式

#newDiv {
  font-size: 36px;
  color: burlywood;
  position: fixed;
  bottom: 20px;
  right: 0;
  width: 300px;
  height: 200px;
  background-color: rgb(237, 229, 216);
  text-align: center;
  z-index: 9999;
}

打開 option 頁面

options 頁,就是插件的設置頁面,有 2 個入口

可以看到設置的 option.html 頁面

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta >
    <title>插件的option配置</title>
</head>
<body>
    <h3>插件的option配置</h3>
</body>
</html>

此頁面也可以進行 js、css 的引入。

替換瀏覽器默認頁面

override 功能,是可以替換掉瀏覽器默認功能的頁面,可以替換 newtab、history、bookmark 三個功能,將新開頁面、歷史記錄頁面、書籤頁面設置爲自定義的內容。修改manifest.json配置

{
  "chrome_url_overrides"{
    "newtab""newtab.html",
    "history""history.html",
    "bookmarks""bookmarks.html"
  }
}

創建一個 newtab 的 html 頁面

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta >
    <title>Document</title>
  </head>
  <body>
    <h1>new tab</h1>
  </body>
</html>

插件更新後,點開新的 tab,就會出現我們自定義的頁面。第一次的情況會讓用戶進行選擇,是進行更換還是保留原來的配置。

很多插件都是使用 newtab 進行自定義打開的 tab 頁,比如掘金的瀏覽器插件,打開新頁面就是掘金網站插件 [6]。

頁面之間進行數據通信

如需將單條消息發送到擴展程序的其他部分並選擇性地接收響應,請調用 runtime.sendMessage()[7] 或 tabs.sendMessage()[8]。通過這些方法,您可以從內容腳本向擴展程序發送一次性 JSON 可序列化消息,或者從擴展程序向內容腳本發送。如需處理響應,請使用返回的 promise。來源地址:developer.chrome.com/docs/extens…[9]

content 中腳本發送消息

chrome.runtime.sendMessage只能放在 content 的腳本中。

(async () ={
  const response = await chrome.runtime.sendMessage({greeting: "hello"});
  // do something with response here, not outside the function
  console.log(response);
})();

其他頁面發送消息

其他頁面需向內容腳本發送請求,請指定請求應用於哪個標籤頁,如下所示。此示例適用於 Service Worker、彈出式窗口和作爲標籤頁打開的 chrome-extension:// 頁面

(async () ={
  const [tab] = await chrome.tabs.query({active: true, lastFocusedWindow: true});
  const response = await chrome.tabs.sendMessage(tab.id, {greeting: "hello"});
  // do something with response here, not outside the function
  console.log(response);
})();

接收消息使用 onMessage

在擴展程序和內容腳本中使用相同的代碼

chrome.runtime.onMessage.addListener(
  function(request, sender, sendResponse) {
    console.log(sender.tab ?
                "from a content script:" + sender.tab.url :
                "from the extension");
    if (request.greeting === "hello")
      sendResponse({farewell: "goodbye"});
  }
);

添加右鍵菜單

創建菜單

首先在manifest.json的權限中添加配置

{
  "permissions"["contextMenus"]
}

background.js中添加創建菜單的代碼

let menu1 = chrome.contextMenus.create({
  type: 'radio', // 可以是 【normal、checkbox、radio】,默認是normal
  title: 'click me',
  id: "myMenu1Id",
  contexts:['image'] // 只有是圖片時,菜顯示
}function(){
  
})

let menu2 = chrome.contextMenus.create({
  type: 'normal', // 可以是 【normal、checkbox、radio】,默認是normal
  title: 'click me222',
  id: "myMenu222Id",
  contexts:['all'] //所有類型都顯示
}function(){
  
})

let menu3 = chrome.contextMenus.create({
  id: 'baidusearch1',
  title: '使用百度搜索:%s', 
  contexts: ['selection'], //選擇頁面上的文字
});

// 刪除一個菜單
chrome.contextMenus.remove('myMenu222Id'); // 被刪除菜單的id menuItemId
// 刪除所有菜單
chrome.contextMenus.removeAll();

// 綁定菜單點擊事件
chrome.contextMenus.onClicked.addListener(function(info, tab){
  if(info.menuItemId == 'myMenu222Id'){
    console.log('xxx')
  }
})

以下是其他可以使用的 api

// 刪除某一個菜單項
chrome.contextMenus.remove(menuItemId);
// 刪除所有自定義右鍵菜單
chrome.contextMenus.removeAll();
// 更新某一個菜單項
chrome.contextMenus.update(menuItemId, updateProperties);
// 監聽菜單項點擊事件, 這裏使用的是 onClicked
chrome.contextMenus.onClicked.addListener(function(info, tab)) {
  //...
});

綁定點擊事件,發送接口請求

首先需要在manifest.jsonhosts_permissions中添加配置

{
  "host_permissions"["http://*/*""https://*/*"]
}

創建 node 服務器,返回 json 數據

// server.mjs
const { createServer } = require('node:http');
const url = require('url');

const server = createServer((req, res) ={
  var pathname = url.parse(req.url).pathname;

  if (pathname.includes('api')) {
    res.writeHead(200, { 'Content-Type''application/json' });
    res.write(
      JSON.stringify({
        name: 'John Doe',
        age: 30,
      })
    );
    res.end();
  } else {
    res.writeHead(200, { 'Content-Type''text/plain' });
    res.end('Hello World!\n' + pathname);
  }
});

server.listen(8080, '127.0.0.1'() ={
  console.log('Listening on 127.0.0.1:8080');
});

編輯background.js文件

// 插件右鍵快捷鍵
// 點擊右鍵進行選擇
chrome.contextMenus.onClicked.addListener(function (info, tab) {
  if (info.menuItemId === 'group1') {
    console.log('分組文字1', info);
  }
  if (info.menuItemId === 'group2') {
    console.log('分組文字2');
  }
  // 點擊獲取到數據
  if (info.menuItemId === 'fetch') {
    console.log('fetch 獲取數據');
    const res = fetch('http://localhost:8080/api'{
      method: 'GET',
      headers: {
        'Content-Type''application/json',
      },
    }).then((res) ={
      console.log(res, '獲取到http://localhost:8080/api接口數據');
      chrome.storage.sync.set({ color: 'red' }function (err, data) {
        console.log('store success!');
      });
    });
  }
  // 創建百度搜索,並跳轉到搜索結果頁
  if (info.menuItemId === 'baidusearch1') {
    // console.log(info, tab, "baidusearch1")
    // 創建一個新的tab頁面
    chrome.tabs.create({
      url:
        'https://www.baidu.com/s?ie=utf-8&wd=' + encodeURI(info.selectionText),
    });
  }
});

// 創建右鍵快捷鍵
chrome.runtime.onInstalled.addListener(function () {
  // Create one test item for each context type.
  let contexts = [
    'page',
    'selection',
    'link',
    'editable',
    'image',
    'video',
    'audio',
  ];
  // for (let i = 0; i < contexts.length; i++) {
  //   let context = contexts[i];
  //   let title = "Test '" + context + "' menu item";
  //   chrome.contextMenus.create({
  //     title: title,
  //     contexts: [context],
  //     id: context,
  //   });
  // }

  // Create a parent item and two children.
  let parent = chrome.contextMenus.create({
    title: '操作數據分組',
    id: 'parent',
  });
  chrome.contextMenus.create({
    title: '分組1',
    parentId: parent,
    id: 'group1',
  });
  chrome.contextMenus.create({
    title: '分組2',
    parentId: parent,
    id: 'group2',
  });
  chrome.contextMenus.create({
    title: '獲取遠程數據',
    parentId: parent,
    id: 'fetch',
  });

  // Create a radio item.
  chrome.contextMenus.create({
    title: '創建單選按鈕1',
    type: 'radio',
    id: 'radio1',
  });
  chrome.contextMenus.create({
    title: '創建單選按鈕2',
    type: 'radio',
    id: 'radio2',
  });

  // Create a checkbox item.
  chrome.contextMenus.create({
    title: '可以多選的複選框1',
    type: 'checkbox',
    id: 'checkbox',
  });
  chrome.contextMenus.create({
    title: '可以多選的複選框2',
    type: 'checkbox',
    id: 'checkbox2',
  });

  // 在title屬性中有一個%s的標識符,當contexts爲selection,使用%s來表示選中的文字
  chrome.contextMenus.create({
    id: 'baidusearch1',
    title: '使用百度搜索:%s',
    contexts: ['selection'],
  });

  // Intentionally create an invalid item, to show off error checking in the
  // create callback.
  chrome.contextMenus.create(
    { title: 'Oops', parentId: 999, id: 'errorItem' },
    function () {
      if (chrome.runtime.lastError) {
        console.log('Got expected error: ' + chrome.runtime.lastError.message);
      }
    }
  );
});

點擊鼠標右鍵,效果如下

如果在頁面選擇幾個文字,那麼就顯示出百度搜索快捷鍵,

緩存,數據存儲

首先在manifest.json的權限中添加storage配置

{
  "permissions"["storage"]
}
chrome.storage.sync.set({color: 'red'}function(){
  console.log('background js storage set data ok!')
})

然後就可以在 content.js 或 popup.js 中獲取到數據

// 這裏的參數是,獲取不到數據時的默認參數
chrome.storage.sync.get({color: 'yellow'}function(){
  console.log('background js storage set data ok!')
})

tabs 創建頁籤

首先在manifest.json的權限中添加 tabs 配置

{
  "permissions"["tabs"]
}

添加 tabs 的相關操作

chrome.tabs.query({}function(tabs){
  console.log(tabs)
})
function getCurrentTab(){
  let [tab] = chrome.tabs.query({active: true, lastFocusedWindow: true});
  return tab;
}

notifications 消息通知

Chrome 提供 chrome.notifications 的 API 來推送桌面通知;首先在 manifest.json 中配置權限

{
  "permissions"[
    "notifications"
  ],
}

然後在 background.js 腳本中進行創建

// background.js
chrome.notifications.create(null, {
  type: "basic",
  iconUrl: "drink.png",
  title: "喝水小助手",
  message: "看到此消息的人可以和我一起來喝一杯水",
});

devtools 開發擴展工具

在 manifest 中配置一個 devtools.html

{
  "devtools_page""devtools.html",
}

devtools.html 中只引用了 devtools.js,如果寫了其他內容也不會展示

<!DOCTYPE html>
<html lang="en">
  <head> </head>
  <body>
    <script type="text/javascript" src="./devtools.js"></script>
  </body>
</html>

創建 devtools.js 文件

// devtools.js
// 創建擴展面板
chrome.devtools.panels.create(
  // 擴展面板顯示名稱
  "DevPanel",
  // 擴展面板icon,並不展示
  "panel.png",
  // 擴展面板頁面
  "Panel.html",
  function (panel) {
    console.log("自定義面板創建成功!");
  }
);

// 創建自定義側邊欄
chrome.devtools.panels.elements.createSidebarPane(
  "Sidebar",
  function (sidebar) {
    sidebar.setPage("sidebar.html");
  }
);

然後在創建自定的 Panel.html 和 sidebar.html 頁面。

作者:北鳥南遊

原文:https://juejin.cn/post/7350571075548397618

相關代碼下載 [10]

https://gitee.com/shenshuai89/learn-chrome-extension

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/QUgT5bGRutstyo0l6nHFfg