今日 TMI: 套件用的好,讓你沒煩惱
今日 TMI: 套件用的好,讓你沒煩惱
{ "name": "Extension DEMO ", "version": "1", "manifest_version": 3 //...略 }
{ "name": "Extension DEMO ", "version": "1", "manifest_version": 3 //...略 }
{ "description": "DEMO Description", // 建議 "icons": {...}, // 建議 }
{ "description": "DEMO Description", // 建議 "icons": {...}, // 建議 }
{ permissions": [ "storage", "activeTab", //目前視窗頁 "tabs", ], }
{ permissions": [ "storage", "activeTab", //目前視窗頁 "tabs", ], }
* 提醒: 建議沒有使用到的權限記得就不要宣告,要不然會導致最後 google 送審 時會未能通過
{ "host_permissions": [ "https://www.google.com/", // 指定特定連結 "https://www.google.com/*", // 指定在特定網域下所有連結 "<all_urls>" //所有都可以使用 ] }
{ "host_permissions": [ "https://www.google.com/", // 指定特定連結 "https://www.google.com/*", // 指定在特定網域下所有連結 "<all_urls>" //所有都可以使用 ] }
更多 Manifest 相關功能設定 請見 -> 官方文件
Extension
Browser
// manifest.json { ... "background": { "service_worker": "background.js", "type": "module" //optional } ... }
// manifest.json { ... "background": { "service_worker": "background.js", "type": "module" //optional } ... }
//background.js const request = async (url) => { //...略 let response = await fetch(url).then((res) => res.json()); //...略 return response; }; //當換頁更新時觸發 chrome.tabs.onUpdated.addListener(async (id, info, tab) => { if (tab.url === "https://xxx.com") { let data = await request("https://xxx.com/api"); } });
//background.js const request = async (url) => { //...略 let response = await fetch(url).then((res) => res.json()); //...略 return response; }; //當換頁更新時觸發 chrome.tabs.onUpdated.addListener(async (id, info, tab) => { if (tab.url === "https://xxx.com") { let data = await request("https://xxx.com/api"); } });
更多 Tab API 相關功能設定 請見 -> 官方文件
// 設定storage chrome.storage.sync.set({ key: value }, () => {}); // 取得 storage chrome.storage.sync.get([key], (res) => res.key); //Storage 改變時觸發事件 chrome.storage.onChanged.addListener((changes, areaName) => { console.log(changes); });
// 設定storage chrome.storage.sync.set({ key: value }, () => {}); // 取得 storage chrome.storage.sync.get([key], (res) => res.key); //Storage 改變時觸發事件 chrome.storage.onChanged.addListener((changes, areaName) => { console.log(changes); });
更多 Storage API 相關功能設定 請見 -> 官方文件
// manifest.json ... "action": { "default_popup": "/action/action.html", "default_title": "Click Me", "default_icon": { "16": "images/icon16.png", // optional "24": "images/icon24.png", // optional "32": "images/icon32.png" } ... },
// manifest.json ... "action": { "default_popup": "/action/action.html", "default_title": "Click Me", "default_icon": { "16": "images/icon16.png", // optional "24": "images/icon24.png", // optional "32": "images/icon32.png" } ... },
// popup.js chrome.storage.local.get("signed_in", (data) => { if (data.signed_in) { chrome.action.setPopup({ popup: "popup.html" }); } else { chrome.action.setPopup({ popup: "popup_sign_in.html" }); } });
// popup.js chrome.storage.local.get("signed_in", (data) => { if (data.signed_in) { chrome.action.setPopup({ popup: "popup.html" }); } else { chrome.action.setPopup({ popup: "popup_sign_in.html" }); } });
// popup.js chrome.action.setBadgeText({ text: "ON" }); chrome.action.setBadgeBackgroundColor({ color: "#4688F1" });
// popup.js chrome.action.setBadgeText({ text: "ON" }); chrome.action.setBadgeBackgroundColor({ color: "#4688F1" });
"content_scripts": [ { "matches": ["https://*.xxx.com/*"], //指定特定網域 "css": ["content.css"], "js": ["content.js"], "run_at": "document_start" // 注入的時機 } ],
"content_scripts": [ { "matches": ["https://*.xxx.com/*"], //指定特定網域 "css": ["content.css"], "js": ["content.js"], "run_at": "document_start" // 注入的時機 } ],
『run_at 欄位』 為指定何時將 Cotent Script 程式 注入於頁面中,分別有三個選項: document_idle、 document_start 或 document_end
更多 靜態 Inject 相關功能設定 請見 -> 官方文件
// manifest.json { "permissions": ["scripting"] }
// manifest.json { "permissions": ["scripting"] }
// background.js ...略 chrome.scripting.executeScript({ target: { tabId: id }, func: injectedFunction, // 也可以指定檔案路徑的形式 files: ['script.js'], args: ["我是動態注入"], },(d)=>{ console.log(d) // 這邊可以接收Function 回傳值 }); ...略
// background.js ...略 chrome.scripting.executeScript({ target: { tabId: id }, func: injectedFunction, // 也可以指定檔案路徑的形式 files: ['script.js'], args: ["我是動態注入"], },(d)=>{ console.log(d) // 這邊可以接收Function 回傳值 }); ...略
// background.js chrome.tabs.query({ active: true, currentWindow: true }, async (tabs) => { //...略 chrome.tabs.sendMessage( tabs[0].id, { from: "background", message: data }, (response) => { console.log(response.from); //接收回傳訊息 } ); });
// background.js chrome.tabs.query({ active: true, currentWindow: true }, async (tabs) => { //...略 chrome.tabs.sendMessage( tabs[0].id, { from: "background", message: data }, (response) => { console.log(response.from); //接收回傳訊息 } ); });
* 提醒: 使用 Tab 相關方法須在 manifest.json 的 permission 欄位 宣告 『tab』及 『activeTab』 權限
// Content Script to Background chrome.runtime.sendMessage({ from: "contentScript" }, (response) => { console.log("response from:", response); });
// Content Script to Background chrome.runtime.sendMessage({ from: "contentScript" }, (response) => { console.log("response from:", response); });
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { if (request.from === "popup.js") { // 可以在 sendMessage 的 callback 中取得,此 sendResponse 的內容 // 需要注意若在多個地方呼叫同時呼叫 sendResponse,將只會收到一個 sendResponse({ from: "background.js", }); } return true; });
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { if (request.from === "popup.js") { // 可以在 sendMessage 的 callback 中取得,此 sendResponse 的內容 // 需要注意若在多個地方呼叫同時呼叫 sendResponse,將只會收到一個 sendResponse({ from: "background.js", }); } return true; });
//content.js //建立 port const port = chrome.runtime.connect({ name: "contentPort" }); //傳送 port.postMessage({ from: "content script" }); // 接收 port.onMessage.addListener((msg) => {});
//content.js //建立 port const port = chrome.runtime.connect({ name: "contentPort" }); //傳送 port.postMessage({ from: "content script" }); // 接收 port.onMessage.addListener((msg) => {});
// background.js chrome.runtime.onConnect.addListener((port) => { if (port.name === "contentPort") { port.onMessage.addListener((res) => { port.postMessage({ from: "popup" }); }); } });
// background.js chrome.runtime.onConnect.addListener((port) => { if (port.name === "contentPort") { port.onMessage.addListener((res) => { port.postMessage({ from: "popup" }); }); } });
快 ! 快 ! 快 !
//npm: npm create vite@latest extension-name
//npm: npm create vite@latest extension-name
. ├── popup.html //根目錄 ├── package.json ├── public │ ├── logo.png │ └── manifest.json ├── src │ ├── background.js │ ├── content_script │ │ └── content.js │ └── popup │ └── popup.js └── vite.config.js
. ├── popup.html //根目錄 ├── package.json ├── public │ ├── logo.png │ └── manifest.json ├── src │ ├── background.js │ ├── content_script │ │ └── content.js │ └── popup │ └── popup.js └── vite.config.js
export default defineConfig({ .... build: { rollupOptions: { output: { entryFileNames: `[name].js`, chunkFileNames: `[name].js`, assetFileNames: `[name].[ext]`, }, input: { popup: resolve(__dirname, 'popup.html'), background: resolve(__dirname, '/src/background.js'), content: resolve(__dirname, '/src/content/content.js') } } }, ... })
export default defineConfig({ .... build: { rollupOptions: { output: { entryFileNames: `[name].js`, chunkFileNames: `[name].js`, assetFileNames: `[name].[ext]`, }, input: { popup: resolve(__dirname, 'popup.html'), background: resolve(__dirname, '/src/background.js'), content: resolve(__dirname, '/src/content/content.js') } } }, ... })
Extension
Browser
// manifest { ... "web_accessible_resources": [ { "resources": [ "logo.png", "/images/*"], "matches": [ "https://*/*" ], use_dynamic_url: true; //會動態產生url } ], ... }
// manifest { ... "web_accessible_resources": [ { "resources": [ "logo.png", "/images/*"], "matches": [ "https://*/*" ], use_dynamic_url: true; //會動態產生url } ], ... }
// background.js or content.js const extensionURL = chrome.runtime.getURL(fileName); console.log(extensionURL); // chrome-extension://dmbemmhmhfjoiehocmomfognglhnjean/content.html
// background.js or content.js const extensionURL = chrome.runtime.getURL(fileName); console.log(extensionURL); // chrome-extension://dmbemmhmhfjoiehocmomfognglhnjean/content.html
// injectScriptModule.js const El = document.querySelector("#extension_iframe"); const iframeEl = document.createElement("iframe"); if (!El) { const div = document.createElement("div"); div.id = "extension_iframe"; div.setAttribute( "style", "z-index:2147483647; position:fixed; bottom:30px; right:20px;font-size:16px;" ); // 注入網站中 document.body.appendChild(div); }
// injectScriptModule.js const El = document.querySelector("#extension_iframe"); const iframeEl = document.createElement("iframe"); if (!El) { const div = document.createElement("div"); div.id = "extension_iframe"; div.setAttribute( "style", "z-index:2147483647; position:fixed; bottom:30px; right:20px;font-size:16px;" ); // 注入網站中 document.body.appendChild(div); }
//injectScriptModule.js iframeEl.sandbox = "allow-scripts allow-popups"; iframeEl.width = "335px"; iframeEl.height = "440px"; iframe.frameborder = "0"; iframeEl.scrolling = "no"; iframeEl.src = chrome.runtime.getURL("content.html"); //透過getURL方法取得存放在 頁Extension 端 content.html El.prepend(iframe);
//injectScriptModule.js iframeEl.sandbox = "allow-scripts allow-popups"; iframeEl.width = "335px"; iframeEl.height = "440px"; iframe.frameborder = "0"; iframeEl.scrolling = "no"; iframeEl.src = chrome.runtime.getURL("content.html"); //透過getURL方法取得存放在 頁Extension 端 content.html El.prepend(iframe);
//動態Inject chrome.scripting.executeScript( { target: {tabId: tabId}, files: ['injectScriptModule.js'], }, () => { ... }); )
//動態Inject chrome.scripting.executeScript( { target: {tabId: tabId}, files: ['injectScriptModule.js'], }, () => { ... }); )