chrom-插件学习

一、什么是chrom插件?

就是 extensions 扩展,可以通过自定义界面、监听一些浏览器的事件和处理网络请求提升浏览器使用体验的工具。

二、chrom 浏览器插件是如何构建的?

就是使用web技术写 html、css、js 构建的

现在开发chrom 插件可以使用 vite/webpack 结合vue/react ,pinia/zustand(状态管理),native-ui/ant design(ui 库) 开发插件

三、chrom 浏览器插件可以做什么?

1、设计界面和用户交互

比如 chrome 工具栏、侧边栏、菜单等触发

一些相关api

侧边栏 chrome.slidePanel 可以把内容托管在浏览器侧边栏中的网页主要内容旁边。

操作项 action 控制插件图标在工具栏显示

菜单项 menus 在上下文菜单添加项

2、控制浏览器

借助浏览器的一些插件api 改变浏览器的工作方式

manifest.json 配置 chrom_settings_overrides 可以覆盖页面

显示通知 利用 chrome.notifications API

管理历史记录 chrome.history

控制标签页和窗口 chrome.tabs 、chrome.tanGroups 、chrome.windows

下载管理 chrom.dowloads

等等

3、控制网络等

可以通过注入脚本、拦截网络请求以及请用 web api 与 网页交互

比如 注入 js 和css、访问当前tab、监听网络请求、发起网络请求、录音录屏等

四、chrome 插件的核心概念

manifest

列出了有关该插件的结构和行为的重要信息。

{
  "manifest_version": 3,
  "name": "My Chrome Extension",
  "version": "0.0.1",
  "description": "My Chrome Extension Description"
}

manifest_version:用于指定插件使用的清单文件格式版本,目前是 3

name:插件名称

version:插件版本

如果需要发布插件市场需要

description:插件描述

icons:图标

Service Worker

Service worker 是一个基于事件的脚本,在浏览器后台运行,用于处理数据、协调扩展中的不同的任务。

示例

{
  "background": {
    "service_worker": "service_worker.js",
    "type": "module"
  }
}

将脚本导入 Service Worker 的方法有两种:import 语句和 importScripts() 方法。

如需使用 import 语句,请将 "type" 字段添加到 manifest.json 文件中并指定 "module"。

import { tldLocales } from './locales.js';
importScripts('locales.js');

常用的相关事件

chrome.action

当有用户与插件的工具栏图标互动时触发,无论该操作是针对特定网页(标签页)还是整个插件。

chrome.management

提供与安装、卸载、启用和停用插件相关的事件。

chrome.notifications

提供与用户与插件生成的系统通知互动相关的事件。

chrome.permissions

指示用户何时授予或撤消插件权限。

chrome.runtime

提供与插件生命周期相关的事件、插件的其他部分发送的消息,以及可用插件或 Chrome 更新的通知。

chrome.storage.onChanged

每当任何 StorageArea 对象被清除或某个键的值被更改或设置时触发。请注意,每个 StorageArea 实例都有自己的 onChanged 事件。

chrome.webNavigation

提供有关导航请求状态的信息。

Permissions 权限

插件在浏览器中获取的功能和数据访问权限,通过声明所需的权限,插件可以使用相关功能。

插件可以请求使用相应键指定的以下类别的权限:

permissions

  包含已知字符串列表中的项

optional_permissions

  由用户在运行时(而不是在安装时)授予

content_scripts.matches

  包含一个或多个匹配模式,允许内容脚本注入到一个或多个主机中

host_permissions

  包含一个或多个匹配格式,可授予对一个或多个主机的访问权限

optional_host_permissions

  由用户在运行时(而不是在安装时)授予

示例

{
  "permissions": [
    "activeTab",
    "contextMenus",
    "storage"
  ],
  "optional_permissions": [
    "topSites",
  ],
  "host_permissions": [
    "https://www.developer.chrome.com/*"
  ],
  "optional_host_permissions":[
    "https://*/*",
    "http://*/*"
  ]
}

Content script 内容脚本

内容脚本是在网页环境中运行的文件,可以操作 DOM,读取浏览器访问网页的信息和修改。

示例

"content_scripts": [
  {
    "matches": ["https://www.taobao.com/"],
    "js": ["content_scripts.js"]
  }
]

content_scripts.js 文件代码

console.log('this is content scripts')

打开这个网站 选择console 就可以看到输出

如果选择sources 选择 content scripts 就可以看到 文件代码

内容脚本相关api

dom

i18n

storage

runtime.connect()

runtime.getManifest()

runtime.getURL()

runtime.id

runtime.onConnect

runtime.onMessage

runtime.sendMessage()

Action

浏览器工具栏中显示的图标或按钮,用户可以单击该图标或按钮来执行插件提供的功能或操作。

配置示例

{
  "manifest_version": 3,
  "name": "My Chrome Extension",
  "version": "0.0.1",
  "description": "My Chrome Extension Description",
  "icons": {
    "16": "icons/icon_16.png",
    "32": "icons/icon_32.png",
    "48": "icons/icon_48.png",
    "128": "icons/icon_128.png"
  },
  "action": {
    "default_icon": "icons/icon.png",
    "default_title": "Popup Title",
    "default_popup": "popup.html"
  }
}

popup.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    *{
      padding: 0;
      margin: 0;
    }
    div{
      width: 300px;
      height: 300px;
      text-align: center;
      line-height: 300px;
      background: gray;
    }
  </style>
</head>
<body>
  <div>popup html</div>
</body>
</html>

Messaging 消息传递

一般来说,消息传递是指 action、content script、service worker 三者之间进行消息传递。

任一端都可以监听另一端发送的消息,并在同一通道上做出响应

消息传递API

runtime API

  runtime.sendMessage()

  runtime.onMessage.addListener()

  runtime.connect()

  runtime.onConnect.addListener()

  runtime.onMessageExternal

  runtime.onConnectExternal

tabs API

  tabs.sendMessage()

  tabs.connect()

一次性请求

  runtime.sendMessage() 或 tabs.sendMessage() 发送消息,runtime.onMessage 监听消息

长期有效的连接

  runtime.connect() 或 tabs.connect() 发送消息,runtime.onConnect 监听消息

与其他插件进行通信

  runtime.sendMessage 或 runtime.connect 发送消息,runtime.onMessageExternal 或 runtime.onConnectExternal 监听消息

接收从网页发送的消息

  manifest.json 中使用 externally_connectable 指定与哪些网站通信

Action(popup) 和 background(service worker) 之间的通信

popup 中使用 chrome.runtime.sendMessage 进行消息发送
  chrome.runtime.sendMessage({
    action: 'fromPopup',
    message: 'Hello from Popup!'
  });
在 service_worker.js 中接收消息chrome.runtime.onMessage.addListener
chrome.runtime.onMessage.addListener(async (message, sender, sendResponse) => {
  if (message.action === 'fromPopup') {
    chrome.notifications.create(
      {
        type: "basic",
        title: "Notifications Title",
        message: "Notifications message to display",
        iconUrl: "../icons/icon.png"
      },
      (notificationId) => {
        console.log('notificationId-->', notificationId)
      }
    );
  }
});

Content script 和 background(Service Worker) 通信

在 content_scripts 中添加点击事件进行消息发送

content_scripts 中使用 chrome.runtime.sendMessage 进行消息发送

$('#contentBut').click(async (e) => {
  // 发送消息
  chrome.runtime.sendMessage({action: "fromContent"});
})
在 Service_worker.js 里面进行消息接收

service_worker 中使用 chrome.runtime.onMessage.addListener 进行消息监听,通过 .action 来判断来源

chrome.runtime.onMessage.addListener(async (message, sender, sendResponse) => {
  if (message.action === 'fromContent') {
    chrome.notifications.create(
      {
        type: "basic",
        title: "Notifications Title",
        message: "Notifications message to display",
        iconUrl: "../icons/icon.png"
      },
      (notificationId) => {
        console.log('notificationId-->', notificationId)
      }
    );
  }
});

Action(popup) 和 content 通信

因为 content 是注入页面的脚本,所以和 content 通信,需要获取当前 tab 信息

1. 获取当前 tab 信息
const [tab] = await chrome.tabs.query({
  url: ["https://www.test.com/*"],
  active: true,
  currentWindow: true
});
console.log('tab', tab)
2、popup 向 content 发送消息,content 接收消息
popup 中使用 chrome.tabs.sendMessage 发送消息,content 中使用 chrome.runtime.onMessage.addListener 接收消息
1、popup 代码
plugin_search_but.onclick = async function () {
  const [tab] = await chrome.tabs.query({
    url: ["https://movie.douban.com/*"],
    active: true,
    currentWindow: true
  });
  console.log('tab', tab)
  if (tab) {
    // 使用 chrome.tabs.sendMessage 发送消息
    chrome.tabs.sendMessage(tab.id, {
      action: 'fromPopup2Content'
    })
  }
}
2、content 使用 chrome.runtime.onMessage.addListener 进行消息监听
chrome.runtime.onMessage.addListener((e) => {
  console.log('e', e)
})
popup 中使用 chrome.tabs.connect 发送消息,content 使用 chrome.runtime.onConnect.addListener 来接收消息
1、popup 代码
plugin_search_but.onclick = async function () {
  const [tab] = await chrome.tabs.query({
    url: ["https://movie.douban.com/*"],
    active: true,
    currentWindow: true
  });
  console.log('tab', tab)
  if (tab) {
    const connect = chrome.tabs.connect(tab.id, {name: 'fromPopup2Content'});
    console.log('connect', connect)
    connect.postMessage('这里是弹出框页面,你是谁?')
    connect.onMessage.addListener((mess) => {
      console.log(mess)
    })
  }
}
2、content 中使用 chrome.runtime.onConnect.addListener 进行消息监听
chrome.runtime.onConnect.addListener((res) => {
  console.log('contentjs中的 chrome.runtime.onConnect:',res)
  if (res.name === 'fromPopup2Content') {
    res.onMessage.addListener(mess => {
      console.log('contentjs中的 res.onMessage.addListener:', mess)
      res.postMessage('哈哈哈,我是contentjs')
    })
  }
})

与其他插件进行通信

如需监听传入请求和来自其他插件的连接,需使用 runtime.onMessageExternal 或 runtime.onConnectExternal 方法
// 一次性请求
chrome.runtime.onMessageExternal.addListener(
function(request, sender, sendResponse) {
  if (sender.id === blocklistedExtension)
    return;  // don't allow this extension access
  else if (request.getTargetData)
    sendResponse({targetData: targetData});
  else if (request.activateLasers) {
    var success = activateLasers();
    sendResponse({activateLasers: success});
  }
});
// 长期连接
chrome.runtime.onConnectExternal.addListener(function(port) {
  port.onMessage.addListener(function(msg) {
    // See other examples for sample onMessage handlers.
  });
});
要向其他插件发送消息,需要其他插件的 ID
// 插件 ID
var laserExtensionId = "abcdefghijklmnoabcdefhijklmnoabc";

// 一次性请求
chrome.runtime.sendMessage(laserExtensionId, {getTargetData: true},
  function(response) {
    if (targetInRange(response.targetData))
      chrome.runtime.sendMessage(laserExtensionId, {activateLasers: true});
  }
);

// 长期请求
var port = chrome.runtime.connect(laserExtensionId);
port.postMessage(...);

Storage 存储

Chrome 插件有一个专门的 storage API,用来进行数据存储。

"permissions": [
  "storage"
]

功能

所有插件上下文都可以访问 Storage API

可序列化的 JSON 值存储为对象属性

Storage API 是异步的,支持批量读取和写入操作

即使用户清除缓存和浏览记录,这些数据仍会保留

即使在无痕模式拆分后,存储的设置也会保留

匹配模式

用于指定插件的内容脚本或页面操作脚本在哪些 URL 匹配模式下执行

五、插件安装

1、Chrome 应用商店安装

浏览器输入:https://chromewebstore.google.com/

搜索和安装自己需要的插件即可

2、安装包安装

浏览器输入:chrome://extensions/

点击【加载已解压的拓展程序】按钮

选择已解压的文件夹即可

六、Manifest.json 文件全字段示例

{
  "manifest_version": 3, 
  "name": "My Chrome Ext Name", 
  "version": "0.0.1",
  "description": "My Chrome Extension description",
  "icons": {
    "16": "public/icons/icon_16.png",
    "32": "public/icons/icon_32.png",
    "48": "public/icons/icon_48.png",
    "128": "public/icons/icon_128.png"
  },
  "action": {
    "default_icon": {              
      "16": "images/icon16.png",   
      "24": "images/icon24.png",   
      "32": "images/icon32.png"    
    },
    "default_title": "Click Me",   
    "default_popup": "popup.html"  
  },
  "author": {
    "email": "user@example.com"
  },
  "background": {
    "service_worker": "service-worker.js",
    "type": "module"
  },
  "chrome_settings_overrides": {
    "homepage": "https://www.homepage.com",
    "search_provider": {
      "name": "name.__MSG_url_domain__",
      "keyword": "keyword.__MSG_url_domain__",
      "search_url": "https://www.foo.__MSG_url_domain__/s?q={searchTerms}",
      "favicon_url": "https://www.foo.__MSG_url_domain__/favicon.ico",
      "suggest_url": "https://www.foo.__MSG_url_domain__/suggest?q={searchTerms}",
      "instant_url": "https://www.foo.__MSG_url_domain__/instant?q={searchTerms}",
      "image_url": "https://www.foo.__MSG_url_domain__/image?q={searchTerms}",
      "search_url_post_params": "search_lang=__MSG_url_domain__",
      "suggest_url_post_params": "suggest_lang=__MSG_url_domain__",
      "instant_url_post_params": "instant_lang=__MSG_url_domain__",
      "image_url_post_params": "image_lang=__MSG_url_domain__",
      "alternate_urls": [
        "https://www.moo.__MSG_url_domain__/s?q={searchTerms}",
        "https://www.noo.__MSG_url_domain__/s?q={searchTerms}"
      ],
      "encoding": "UTF-8",
      "is_default": true
    },
    "startup_pages": ["https://www.startup.com"]
  },
  "chrome_url_overrides" : {
    "bookmarks": "myBookmarks.html",
    "history": "myHistory.html",
    "newtab": "myNewtab.html"
  },
  "commands": {
    "run-foo": {
      "suggested_key": {
        "default": "Ctrl+Shift+Y",
        "mac": "Command+Shift+Y"
      },
      "description": "Run 'foo' on the current page."
    }
  },
  "content_scripts": [
    {
      "matches": ["https://*.nytimes.com/*"],
      "css": ["my-styles.css"],
      "js": ["content-script.js"]
    }
  ],
  "content_security_policy": {
    "extension_pages": "script-src 'self'; object-src 'self';",
    "sandbox": "sandbox allow-scripts allow-forms allow-popups allow-modals; script-src 'self' 'unsafe-inline' 'unsafe-eval'; child-src 'self';"
  },
  "cross_origin_embedder_policy": {
    "value": "require-corp"
  },
  "cross_origin_opener_policy": {
    "value": "same-origin"
  },
  "declarative_net_request" : {
    "rule_resources" : [{
      "id": "ruleset_1",
      "enabled": true,
      "path": "rules_1.json"
    }, {
      "id": "ruleset_2",
      "enabled": false,
      "path": "rules_2.json"
    }]
  },
  "default_locale": "en",
  "devtools_page": "devtools.html",
  "export": {
    "allowlist": [
      "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
      "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
    ]
  },
  "externally_connectable": {
    "ids": [
      "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
      "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
    ],
    "matches": [
      "https://*.google.com/*",
      "*://*.chromium.org/*",
    ],
    "accepts_tls_channel_id": false
  },
  "homepage_url": "https://guoqiankun.blog.csdn.net/",
  "permissions": [
    "activeTab",
    "contextMenus",
    "storage"
  ],
  "optional_permissions": [
    "topSites"
  ],
  "host_permissions": [
    "https://www.developer.chrome.com/*"
  ],
  "optional_host_permissions":[
    "https://*/*",
    "http://*/*"
  ],
  "import": [
    {
      "id": "cccccccccccccccccccccccccccccccc"
    },
    {
      "id": "dddddddddddddddddddddddddddddddd",
      "minimum_version": "0.5"
    }
  ],
  "incognito": "not_allowed",
  "key": "ThisKeyIsChromeKey",
  "minimum_chrome_version": "120.0.6099.129",
  "oauth2": {
    "client_id": "yourExtensionOAuthClientIDWillGoHere.apps.googleusercontent.com",
    "scopes":[""]
  },
  "omnibox": { 
    "keyword": "newTab"
  },
  "options_page": "index.html",
  "options_ui": {
    "page": "options.html",
    "open_in_tab": false
  },
  "requirements": {
    "3D": {
      "features": ["webgl"]
    }
  },
  "sandbox": {
    "pages": [
      "page1.html",
      "directory/page2.html"
    ]
  },
  "short_name": "short name",
  "side_panel": {
    "default_path": "sidepanel.html"
  },
  "storage": {
    "managed_schema": "schema.json"
  },
  "tts_engine": {
    "voices": [
      {
        "voice_name": "Alice",
        "lang": "en-US",
        "event_types": ["start", "marker", "end"]
      },
      {
        "voice_name": "Pat",
        "lang": "en-US",
        "event_types": ["end"]
      }
    ]
  },
  "update_url": "https://myhost.com/mytestextension/updates.xml",
  "version_name": "1.0 beta",
  "web_accessible_resources": [
    {
      "resources": [ "test1.png", "test2.png" ],
      "matches": [ "https://web-accessible-resources-1.glitch.me/*" ]
    }, {
      "resources": [ "test3.png", "test4.png" ],
      "matches": [ "https://web-accessible-resources-2.glitch.me/*" ],
      "use_dynamic_url": true
    }
  ],
  "file_browser_handlers": [
    {
      "id": "upload",
      "default_title": "Save to Gallery",
      "file_filters": [
        "filesystem:*.jpg",
        "filesystem:*.jpeg",
        "filesystem:*.png"
      ]
    }
  ],
  "file_handlers": [
    {
      "action": "/open_text.html",
      "name": "Plain text",
      "accept": {
        "text/plain": [".txt"]
      },
      "launch_type": "single-client"
    }
  ],
  "file_system_provider_capabilities": {
    "configurable": true,
    "watchable": false,
    "multiple_mounts": true,
    "source": "network"
  },
  "input_components": [{
    "name": "ToUpperIME",
    "id": "ToUpperIME",
    "language": "en",
    "layouts": ["us::eng"]
  }]
}

  • 7
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值