Chrome 插件开发

浏览器插件是我们每天都在打交道的工具,他为我们提供了各式各样便捷的功能。按照惯例,先介绍一下插件的定义:

Extensions are small software programs that customize the browsing experience. They let users tailor Chrome functionality and behavior in many ways. Extensions are built on web technologies such as HTML, JavaScript, and CSS. They run in a separate, sandboxed execution environment and interact with the Chrome browser.

扩展是自定义浏览体验的小型软件程序。 它们让用户可以通过多种方式定制 Chrome 的功能和行为。扩展是基于 HTML、JavaScript 和 CSS 等 Web 技术构建的。 它们在单独的沙盒执行环境中运行,并与 Chrome 浏览器交互。

一、manifest.json

类似 package.json 的存在。官网所有配置项

{





  "manifest_version": 3,
  "name": "My Extension",
  "version": "0.0.1",
  "description": "A plain text description",
  "icons": {...},
  "author": "...",

  // action配置项主要用于点击图标弹出框,对于弹出框接受的是html文件
  "action": {
     "default_title": "Click to view a popup",
   	 "default_popup": "popup.html"
   },
  

  // background.js 处理和监听浏览器事件,相当于是在后台运行的脚本
  "background": {
    "service_worker": "background.js",
    // 配置type来使service worker声明为ES Module
    "type": "module"
  },

  // content_script.js 网页上执行的javascript
  "content_scripts": [
     {
       "matches": ["https://*.snqu.com/*"],
       "css": ["my-styles.css"],
       "js": ["content-script.js"]
     }
   ],

  // 使用/添加devtools中的功能
  "devtools_page": "devtools.html",
  
  // 三个permission
  // host_permissions - 允许使用扩展的域名
  // permissions - 声明需要授予的权限
  // optional_permissions - 与常规类似permissions,但由扩展的用户在运行时授予,而不是提前授予【安全】
  // 常见选项
  // {
  //		activeTab: 当扩展卡选项被改变需要重新获取新的权限
  //		tabs: 操作选项卡api(改变位置等)
  //		downloads: 访问chrome.downloads API 的权限 便于下载但还是会受到跨域影响
  //		history: history api权限
  //		storage: 访问localstorage/sessionStorage权限
  // }
  "host_permissions": ["http://*/*", "https://*/*"],
  "permissions": ["tabs"],
  "optional_permissions": ["downloads"],

  // 插件配置/设置页面
  "options_ui": {
    "page": "options.html",
    "open_in_tab": false
  }
}

二、service_worker(background script)

background script处理和监听浏览器事件,相当于是在后台持续运行的脚本。仅在需要的时候才会运行,其余时候都处于休眠状态。

注意点:

  1. 可以使用浏览器的全部api;但是不能和页面内容直接交互。
  2. 由于mv3版本的service_worker在五分钟后就会终止进程,其中所有的状态和定时任务都将丢失。所以不要再service_worker中直接定义变量和使用setTimeoutsetInterval等定时任务。改用chrome.storage储存需要的变量。使用chrome.alarm来执行定时任务。
  3. service_worker中的请求不存在跨域问题

三、content script

content scripts是在网页上执行的javascript,可以读取和修改网页上的dom元素,但只能使用部分的浏览器api

1. 运行环境

content scripts存在于一个孤立的环境中,允许内容脚本对其JavaScript环境进行更改,而不会与页面或其他扩展的content scripts发生冲突。

比如content scripts的变量对于主机页面和其他插件的content scripts是不可见的;又或者通过content scripts改写了页面的某个api,而页面原本的元素在触发这个api时并没有按照预期的效果执行,其原因就是content scripts修改的只是自身环境的api,而不是原页面的api。

2. 注入方式

content scripts的注入方式分为两种:静态注入和动态注入。

静态注入

manifest.json中配置的脚本即为静态注入,在匹配的页面上自动注入脚本。

{





  "name": "test",


  ...




  "content_scripts": [
    {
      "matches": ["https://*.test.com/*"],
      "css": ["styles.css"],
      "js": ["content-script.js"]
    }
  ],
  ...
}
配置项 详情
matches 指定此内容脚本将被注入到哪些页面,详见match_patterns
js 要注入匹配页面的 JavaScript 文件列表。它们按照它们在此数组中出现的顺序注入。
css 要注入匹配页面的 CSS 文件列表。在为页面构造或显示任何 DOM 之前,它们按照它们在此数组中出现的顺序注入。
all_frames 默认为false,表示仅匹配顶部框架。 如果指定true,它将注入所有框架,即使该框架不是选项卡中最顶层的框架。每个框架都独立检查 URL 要求,如果不满足 URL 要求,它不会注入子框架。
run_at document_idle 参考:运行
document_start
document_end

动态注入

使用chrome.scripting.executeScript进行动态注入,在注入之前需要获取注入页面的主机权限,可以设置host_permissions权限获取临时主机权限。

chrome.action.onClicked.addListener((tab) => {
  chrome.scripting.executeScript({
    target: { tabId: tab.id },
    files: ['content-script.js']
  });

});


动态注入的脚本可以通过设置world属性为MAIN直接进入网页文档,而不是独立的环境。这时在主页面就可以访问到脚本的变量等信息

所以,如果想要主动将脚本注入主页面,可以在background监听页面进行注入;或者在静态注入的脚本中动态插入script标签并且将src设置为想要注入主页面的脚本的url。

四、popup page

弹出窗口是一个 HTML 文件,当用户单击操作图标时,它会显示在一个特殊的窗口中。弹出窗口的工作方式与网页非常相似;它可以包含指向样式表和脚本标签的链接,但不允许内联 JavaScript。

{





  "name": "Test",

  ...




  "action": {
    "default_popup": "popup.html"
  }
  ...
}

弹出窗口也可以通过调用动态设置action.setPopup

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'});
  }
});

五、Options Page

右键浏览器图标之后点击选项弹出的页面,可以让用户在其中自定义插件的功能。

{





  "name": "Test",

  ...




  "options_ui": {
    "page": "options.html",
    "open_in_tab": false
  },

  ...

}

弹出模式分为两种,通过manifest.json中的options_ui.open_in_tab字段来控制。

  1. open_in_tabture

  1. open_in_tabfalse

六、devtools

DevTools扩展都有一个DevTools页面,每次打开DevTools窗口时,都会创建一个扩展的DevTools页面实例。DevTools页面在DevTools窗口的整个生命周期内都存在。DevTools页面可以访问DevTools API和一组有限的扩展API

{





  "name": "test",


  ...




	"devtools_page": "devtools.html",
  ...
}

七、Override pages

扩展可以用自定义HTML文件覆盖和替换历史记录、新选项卡或书签页面。与弹出窗口一样,它可以包含专门的逻辑和样式。

单个扩展仅限于覆盖三个可能页面中的一个。

newtab – 标签页

bookmarks – 书签页

history – 历史页

{





  "name": "test",


  ...





  "chrome_url_overrides" : {
    "newtab": "override_page.html"
  },

  ...

}

八、通信

developer.chrome.com/docs/extens…

由于content_scripts运行在网页中,而不是浏览器插件的环境,所以它需要一些特殊的方式来和浏览器插件的内容进行通信。

通信的方式包括用于一次性请求的简单通信和用于长期连接的长连接通信。

简单通信

简单通信可以使用 runtime.sendMessage()tabs.sendMessage() 与插件的其他部分进行通信。

上述api可以将json字段从content_scripts发送给插件,当然也可以从插件将信息发送给content_scripts。如果要处理返回的数据,需要使用返回的Promise;也可以设置回调函数。

从content_scripts发送请求:

(async () => {
  const response = await chrome.runtime.sendMessage({
    data: "hello background",
  });
  console.log("接收到来自background的响应:", response.data);
})();

在background接收消息:

chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {

  console.log("接收到来自content的事件:", request, sender);
  if (request.data === "hello background") {
    sendResponse({ data: "hello content!" });
  }

});


注意:如果没有生效,检查权限是否配置 "permissions": ["tabs"]

从background发送:

从插件向content_scripts发送请求是类似的,但是要指定将请求发送到哪个选项卡:

const click = async () => {
  const [tab] = await chrome.tabs.query({
    active: true,
    lastFocusedWindow: true,
  });

  console.log("taaa", tab);
  const response = await chrome.tabs.sendMessage(tab.id, {
    data: "hello content",
  });
  console.log("response", response);
};
chrome.tabs.onUpdated.addListener(click);

注意:从background发送通信需要配合事件监听

从content接收信息:

在事件接收端需要设置一个 runtime.onMessage 事件侦听器来处理消息。

chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {

  console.log("接收到来自background的事件:", request, sender);
  if (request.data === "hello content") {
    sendResponse({ data: "hello background!" });
  }

});


需要注意的是:sendResponse是同步调用的,如果想要在异步调用需要在onMessage的事件处理函数中添加return true;

chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
	// console.log(request, sender);
	(async () => {
    if (request.data === "hello content") {
  		const data = await getData();
  		sendResponse(data);
    }
	})();
	return true;
});

如果多个页面侦听onMessage事件,则只有第一个调用sendResponse()特定事件的页面才能成功发送响应。其他对该事件的所有其他响应事件将被忽略。

长连接通信

某些情况下需要持续事件更长的通信,这时可以使用长连接通信,分别使用runtime.connecttabs.connect打开一个从content_scripts到插件的长期通道或插件到content_scripts的长期通道。

建立连接

建立连接时,每一端都会获得一个runtime.Port对象,用于发送和接收消息。

const port = chrome.tabs.connect(tabId, {name: 'test-connect'});
port.postMessage({question: '你是谁啊?'});
port.onMessage.addListener(function(msg) {
  alert('收到消息:'+msg.answer);
  if(msg.answer && msg.answer.startsWith('我是')) {
    port.postMessage({question: '哦,原来是你啊!'});
  }

});

从插件向content_scripts发送请求是类似的,只需要指定要连接到哪个选项卡。只需将上例中的连接调用替换为tabs.connect()即可。

当连接断开时,可以通过 runtime.Port.onDisconnect 事件进行监听。

处理连接信息

需要设置一个runtime.onConnect()事件侦听器。当插件的另一部分调用connect()时,将触发此事件,其中回调函数的参数是用来通过连接发送和接收消息的runtime.Port对象

chrome.runtime.onConnect.addListener(function(port) {
  if(port.name == 'test-connect') {
    port.onMessage.addListener(function(msg) {
      console.log('收到长连接消息:', msg);
      if(msg.question == '你是谁啊?') port.postMessage({answer: '我是小徐'});
    });
  }

});

需要断开连接时可以使用 runtime.Port.disconnect() 方法手动断开连接。

通信方式总结

  • 短连接(chrome.tabs.sendMessage和chrome.runtime.sendMessage)
  • 长连接(chrome.tabs.connect和chrome.runtime.connect)
  • popup可以直接调用background中的JS方法,也可以直接访问background的DOM
  • content-script可以直接访问页面的DOM,但是不能直接调用页面中的JS方法
  • background可以直接调用content-script中的JS方法,但是不能直接访问页面的DOM

© 版权声明
THE END
喜欢就支持一下吧
点赞0

Warning: mysqli_query(): (HY000/3): Error writing file '/tmp/MY37QUD8' (Errcode: 28 - No space left on device) in /www/wwwroot/583.cn/wp-includes/class-wpdb.php on line 2345
admin的头像-五八三
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

图形验证码
取消
昵称代码图片