Plasmo 学习路线图 (三):Content Script 开发详解:与网页内容深度交互
深入学习 Plasmo 框架中的 Content Script 开发。了解如何在 Plasmo 中创建 Content Script、其重要性及隔离世界机制。通过实际代码示例,掌握 Content Script 如何与网页 DOM 深度交互,包括修改元素、读取数据和注入 CSS 样式,构建功能强大的浏览器扩展
一、 前篇回顾
简单回顾系列文章前两篇内容:
- 第一篇: 使用 Plasmo 从零搭建了第一个浏览器扩展项目,熟悉了基本项目结构和开发流程。
- 第二篇: 学习了
Popup
页面的开发,了解了如何构建扩展的弹窗界面并添加基础交互和样式。
通过前文的介绍,我们已经熟练掌握了如何从零到一创建一个 plasmo
项目,以及如何开发 Popup
页面。在这一篇中,我们将介绍使用 plasmo
简化开发另一个重要组件:Content Script
。
Content Script
在浏览器插件中扮演着重要的角色:它是唯一能够直接接触并修改用户访问的网页内容的组件。
二、 Content Script
简介
2.1 什么是 Content Script
Content Script
是一段被注入到浏览器网页中的脚本,它运行在网页的上下文环境中,可以访问和修改网页的 DOM
。Content Script
和网页自身的 JavaScript
程序不同,它运行在隔离的环境中(Isolated world
),Content Script
不能直接跟网页自身的 JavaScript
进行变量及函数访问。此外,Content Script
也不能直接访问大部分的 Chrome
扩展 API
或 Popup
/Service Worker
中的变量。总结如下:
- 定义:
Content Script
是一段被注入到浏览器网页中的脚本; - 运行环境: 它运行在网页的上下文环境中(
Isolated World
,隔离世界),可以访问和修改网页的DOM
; - 与网页脚本的关系: 与网页自身的
JavaScript
完全隔离; - 与扩展其他部分的关系: 与扩展的
API
隔离,无法访问Popup
/Service Worker
变量。
2.2 Content Script
的重要性及常见用途
Content Script
是实现与网页互动功能的基础,常见的用途有:
- 修改网页的
CSS
样式,改变页面外观(例如:夜间模式、屏蔽广告元素);
- 向网页中注入新的
HTML
元素,添加新的UI
或信息; - 从网页中读取特定的数据(例如:抓取页面上的价格、文本);
- 注入
JavaScript
文件、模拟用户操作,与网页元素进行交互(例如:自动点击按钮、填写表单) 我们会在稍后的内容中讲解。
2.3 隔离世界 (Isolated World
) 的重要性
其实我们可以把 Isolated World
理解为浏览器为 Content Script
提供的一种沙箱机制。通过将 Content Script
的 JavaScript
环境与网页的 JavaScript
环境隔离开来,有效地防止了变量冲突和安全漏洞,保障了浏览器扩展和网页的稳定、安全运行,并允许多个扩展在同一页面上互不干扰地工作。虽然 Content Script
可以在隔离世界中访问和修改网页的 DOM
,但其代码本身是与网页代码隔离的。如果需要 Content Script
和网页之间进行更深度的 JavaScript
层面交互,需要通过 window.postMessage
等特定的消息传递机制来实现。
隔离世界的好处有:
- 保护扩展:防止恶意网页脚本访问或篡改扩展的关键数据和功能。
- 保护网页:防止
Content Script
意外地干扰网页自身的JavaScript
运行。
正因为隔离世界,Content Script
需要通过消息传递 (Message Passing
) 来与扩展的其他部分(如 Service Worker
、Popup
)进行通信。这在后续的内容中会详细讲解。
三、 在 Plasmo
中开发 Content Script
3.1 Plasmo
中的 Content Script
文件约定
在传统的浏览器扩展开发中,一般有三种方式开发 Content Script
:
- 静态声明 (
Static Declarations
): 在manifest.json
文件中声明,浏览器会在指定的页面加载时自动注入; - 动态声明 (
Dynamic Declarations
): 在运行时根据需要注册和取消注册Content Script
; - 程序化注入 (
Programmatic Injection
): 使用chrome.scripting.executeScript
API
在特定时机将脚本注入到网页中。
但是在 Plasmo
框架中,Content Script
的开发得到了很大的简化。跟 Popup
相同,Content Script
在 Plasmo
中也是通过文件约定来识别和打包的,同样也有两种方式创建 Content Script
文件:
- 在项目的根目录创建
content.ts
文件(或其他支持的扩展名,例如:.tsx
、.js
、.jsx
); - 在根目录创建
content
文件夹,然后在其中创建index.ts
文件。
分别如图:
这两种写法是一致的,我们以第一种为例继续演示后续的内容。
3.2 创建并验证第一个 Content Script
我们先来开发第一个 Content Script
,它只有一个简单的功能,在控制台输出加载成功。代码如下:
console.log('Plasmo Content Script Loaded Successfully!');
然后我们开始验证:
- 终端运行
pnpm dev
; - 在浏览器中打开任意网站,例如百度;
- 打开开发者工具,切换到
Console
(控制台)标签; - 查看
Content Script
输出的日志信息。
如图所示:
这里的报错是因为我们没有导出 React
组件,如果想消除报错可以把代码修改一下:
import React, { useEffect } from 'react';
const LogicOnlyContentScript = () => {
useEffect(() => {
console.log('Plasmo Content Script Loaded Successfully!');
}, []);
return null; // 不渲染任何UI
};
export default LogicOnlyContentScript;
刷新页面,发现报错已经没有了,如图:
3.3 与网页 DOM
交互:修改页面内容
下面我们再来看一下 Content Script
是如何修改页面内容的,我们来实现一个简单的改变网页背景颜色和在页面顶部插入一条消息的功能。代码如下:
import React, { useEffect } from 'react';
const LogicOnlyContentScript = () => {
useEffect(() => {
console.log('Plasmo Content Script Loaded Successfully!!');
document.body.style.backgroundColor = 'lightblue'; // 改变背景色
const message = document.createElement('h1');
message.textContent = 'Hello from Plasmo Content Script!';
document.body.prepend(message); // 在 body 开头插入消息
}, []);
return null; // 不渲染任何UI
};
export default LogicOnlyContentScript;
然后我们刷新页面,由于 Plasmo
支持热加载,我们不需要重启服务。结果如图:
3.4 与网页 DOM
交互:读取页面数据
接下来我们再来看看 Content Script
是如何读取页面数据的,我们来简单实现一下读取网页标题和页面元素。代码如下:
import React, { useEffect } from 'react';
const LogicOnlyContentScript = () => {
useEffect(() => {
console.log('Plasmo Content Script Loaded Successfully!!');
console.log('Page Title:', document.title); // 获取网页标题
const firstHeading = document.querySelector('body'); // 获取页面第一个 h1 元素
if (firstHeading) {
console.log('body Text:', firstHeading.textContent.slice(0, 100)); // 获取前面 100 个字符
}
// 示例:获取特定 class 元素的文本
const hiddenElement = document.querySelector('.hidden');
if (hiddenElement) {
console.log('Text from .hidden:', hiddenElement.textContent);
}
}, []);
return null; // 不渲染任何UI
};
export default LogicOnlyContentScript;
然后我们刷新页面,结果如图:
3.5 Content Script
中的样式 (CSS
注入)
接下来我们演示一下 Content Script
中的样式注入,即 CSS
注入。在 Plasmo
框架中,Content Script
的样式注入有两种方式:
- 在
content.tsx
中直接import
一个.css
文件, 然后Plasmo
会自动处理打包和注入; - 通过JavaScript动态创建
<style>
标签,并添加到<head>
中。
我们分别演示如下:
3.5.1 使用 import
关键字导入 .css
文件
创建 content.css
文件,代码如下:
/* content.css */
.plasmo-injected-message {
color: red;
border: 1px solid blue;
padding: 10px;
}
然后在 content.tsx
中 import
,然后修改属性,代码如下:
import React, { useEffect } from 'react';
import './content.css';
const LogicOnlyContentScript = () => {
useEffect(() => {
console.log('Plasmo Content Script Loaded Successfully!!');
console.log('Page Title:', document.title); // 获取网页标题
const firstHeading = document.querySelector('body'); // 获取页面第一个 h1 元素
if (firstHeading) {
console.log('body Text:', firstHeading.textContent.slice(0, 100));
}
// 示例:获取特定 class 元素的文本
const hiddenElement = document.querySelector('.hidden');
if (hiddenElement) {
console.log('Text from .hidden:', hiddenElement.textContent);
}
// 示例:修改属性
const message = document.createElement('h1');
message.textContent = 'Hello from Plasmo Content Script with Style!';
message.classList.add('plasmo-injected-message'); // 添加 CSS 类
document.body.prepend(message);
}, []);
return null; // 不渲染任何UI
};
export default LogicOnlyContentScript;
刷新页面,如图:
注意:注入的
CSS
会影响整个网页,需要注意选择器的作用范围,避免干扰网页原有样式。可以使用更具体的选择器或者 Shadow DOM
(更高级)。
3.5.2 通过 JavaScript
动态创建 <style>
标签,并添加到 <head>
中
为了区分,我们把样式的文字颜色设置为黑色,代码如下:
import React, { useEffect } from 'react';
// import './content.css'; // 移除此行
const LogicOnlyContentScript = () => {
useEffect(() => {
console.log('Plasmo Content Script Loaded Successfully!!');
console.log('Page Title:', document.title); // 获取网页标题
// 动态创建 <style> 标签并添加 CSS 规则
const styleElement = document.createElement('style');
styleElement.textContent = `
/* Dynamically Injected Styles */
.plasmo-injected-message {
color: black;
border: 1px solid blue;
padding: 10px;
}
`;
document.head.appendChild(styleElement);
const firstHeading = document.querySelector('body'); // 获取页面第一个 h1 元素
if (firstHeading) {
console.log('body Text:', firstHeading.textContent.slice(0, 100));
}
// 示例:获取特定 class 元素的文本
const hiddenElement = document.querySelector('.hidden');
if (hiddenElement) {
console.log('Text from .hidden:', hiddenElement.textContent);
}
// 示例:修改属性
const message = document.createElement('h1');
message.textContent = 'Hello from Plasmo Content Script with Style!';
message.classList.add('plasmo-injected-message'); // 添加 CSS 类
document.body.prepend(message);
}, []);
return null; // 不渲染任何UI
};
export default LogicOnlyContentScript;
刷新页面,效果如图所示:
3.6 进阶:Content Script
配置
除了以上内容,我们还可以从 content.ts
导出一个对象来配置 Content Script
的行为。当我们有多个 Content Script
文件时,更推荐的做法是新建 content
文件夹,把这些 Content Script
文件都放在 content
文件夹中统一管理,然后通过 Plasmo
提供的 PlasmoCSConfig
来导出并配置。我们把上面的代码修改成一个简单的示例,主要功能是在百度的页面上使用 Content Script
实现上面的效果,代码如下:
import type { PlasmoCSConfig, PlasmoGetStyle } from "plasmo";
import React, { useEffect } from 'react';
// import './content.css'; // 移除此行
export const config: PlasmoCSConfig = {
matches: ["https://www.baidu.com/"], // 示例:匹配所有URL
all_frames: true // 示例:在所有frame中注入
};
export const getStyle: PlasmoGetStyle = () => {
const styleElement = document.createElement('style');
styleElement.textContent = `
/* Styles injected via Plasmo getStyle */
.plasmo-injected-message {
color: black; /* 用户更新的颜色 */
border: 1px solid blue;
padding: 10px;
}
`;
return styleElement;
};
const LogicOnlyContentScript = () => {
useEffect(() => {
console.log('Plasmo Content Script Loaded Successfully!!');
console.log('Page Title:', document.title); // 获取网页标题
const firstHeading = document.querySelector('body'); // 获取页面第一个 h1 元素
if (firstHeading) {
console.log('body Text:', firstHeading.textContent.slice(0, 100));
}
// 示例:获取特定 class 元素的文本
const hiddenElement = document.querySelector('.hidden');
if (hiddenElement) {
console.log('Text from .hidden:', hiddenElement.textContent);
}
// 示例:修改属性
const message = document.createElement('h1');
message.textContent = 'Hello from Plasmo Content Script with Style!';
message.classList.add('plasmo-injected-message'); // 添加 CSS 类
document.body.prepend(message);
}, []);
return null; // 不渲染任何UI
};
export default LogicOnlyContentScript;
效果跟上面是一样的,这里就不截图展示了。
Content Script
配置是一项好用且实用的功能,这里只是一个简单的演示效果,后续我们会有更深入的讨论,敬请期待。
四、 下一步
总的来说,Content Script
的能力主要是直接与网页互动,从而实现强大的功能。Content Script
不能直接调用 Popup
、Service Worker
的函数,它们需要使用消息传递(Message Passing
)来沟通。关于消息传递(Message Passing
),我们将在下一篇讲解。
期待您的持续关注。
结尾
感谢阅读本篇 Content Script
教程!
- 动手实践:尝试用
Content Script
修改你喜欢的网站,或者读取一些有趣的数据。 - 期待下一篇关于扩展通信的教程,它将打开更多
Plasmo
开发的可能性! - 返回 Plasmo 学习路线图 (一):从零搭建你的第一个扩展项目
- 返回 Plasmo 学习路线图 (二):构建你的第一个 Popup 页面
- 返回 Plasmo 学习路线图 (三):Content Script:与网页内容亲密接触