Content Scripts
Outline
深度内容脚本
CSS
在常规网页扩展中,内容脚本的CSS通常是一个单独的CSS文件,添加到 manifest 中:
json
{
"content_scripts": [
{
"css": ["content/style.css"],
"js": ["content/index.js"],
"matches": ["*://*/*"]
}
]
}
在WXT中,为了将CSS注入内容脚本,请导入CSS文件到JS入口,并通过wxt自动添加编译后的CSS输出:
typescript
// entrypoints/example.content/index.ts
import './style.css';
export default defineContentScript({
// ...
});
要为内容脚本添加CSS,请检查 manifest 中的 css
数组:
json
{
"content_scripts": [
{
"css": ["content/style.css"],
"matches": ["*://*/*"]
}
]
}
UI
WXT提供了三种构建UI的方法:集成、阴影根和IFrame。每种方法都有其优势和适用场景。
集成
集成内容脚本UI是将UI与内容一起渲染到页面,这意味着它们会受到页面的CSS影响:
typescript
// entrypoints/example-ui.content.ts
export default defineContentScript({
matches: ['<all_urls>'],
main(ctx) {
const ui = createIntegratedUi(ctx, {
position: 'inline',
anchor: 'body',
onMount: (container) => {
// 在容器中添加UI组件
const app = document.createElement('p');
app.textContent = 'Hello world!';
container.append(app);
},
});
ui.mount();
},
});
阴影根
阴影根是一种隔离CSS的技术,使得UI的样式不受页面影响:
typescript
// entrypoints/example-shadow-root.content.ts
export default defineContentScript({
matches: ['<all_urls>'],
cssInjectionMode: 'ui',
main(ctx) {
const ui = createShadowRootUi(ctx, {
name: 'example-ui',
position: 'inline',
anchor: 'body',
onMount: (container) => {
// 在容器中渲染UI组件
const app = document.createElement('div');
container.append(app);
},
onRemove: () => {
// 从容器中移除UI组件
container?.remove();
},
});
ui.mount();
},
});
IFrame
使用IFrame可以将UI嵌入到另一个页面中,支持HMR:
typescript
// entrypoints/example-iframe.content.ts
export default defineContentScript({
matches: ['<all_urls>'],
main(ctx) {
const ui = createIframeUi(ctx, {
page: '/example-iframe.html',
position: 'inline',
anchor: 'body',
onMount: (wrapper, iframe) => {
// 设置IFrame的样式
iframe.width = '123';
},
});
ui.mount();
},
});
隔离世界 vs 主世界
默认情况下,所有内容脚本运行在隔离环境中。MV3中,内容脚本可以通过world: 'MAIN'
配置选项运行在主世界。
typescript
export default defineContentScript({
world: 'MAIN',
});
嵌入式UI
通过createIntegratedUi
函数可以将UI嵌入到页面:
typescript
// entrypoints/example-content.content.ts
import './style.css';
export default defineContentScript({
matches: ['<all_urls>'],
cssInjectionMode: 'ui',
main(ctx) {
const ui = createIntegratedUi(ctx, {
position: 'inline',
anchor: 'body',
onMount: (container) => {
// 在容器中添加UI组件
const app = document.createElement('p');
app.textContent = 'Hello world!';
container.append(app);
},
});
ui.mount();
},
});
阴影根
通过createShadowRootUi
函数可以创建隔离的UI:
typescript
// entrypoints/example-shadow-root.content.ts
import './style.css';
export default defineContentScript({
matches: ['<all_urls>'],
cssInjectionMode: 'ui',
main(ctx) {
const ui = createShadowRootUi(ctx, {
name: 'example-ui',
position: 'inline',
anchor: 'body',
onMount: (container) => {
// 在容器中渲染UI组件
const app = document.createElement('div');
container.append(app);
},
onRemove: () => {
// 从容器中移除UI组件
container?.remove();
},
});
ui.mount();
},
});
IFrame
通过createIframeUi
函数可以创建嵌入式的UI:
typescript
// entrypoints/example-iframe.content.ts
import './style.css';
export default defineContentScript({
matches: ['<all_urls>'],
cssInjectionMode: 'ui',
main(ctx) {
const ui = createIframeUi(ctx, {
page: '/example-iframe.html',
position: 'inline',
anchor: 'body',
onMount: (wrapper, iframe) => {
// 设置IFrame的样式
iframe.width = '123';
},
});
ui.mount();
},
});
隔离世界与主世界的比较
隔离世界:所有内容脚本共享页面DOM。
主世界:支持更复杂的上下文交互,但不支持MV2。
要启用主世界,需要手动注入脚本:
typescript
// entrypoints/example-main-world.ts
export default defineUnlistedScript(() => {
console.log('Hello from the main world');
});
typescript
// entrypoints/example.content.ts
export default defineContentScript({
matches: ['*://*/*'],
async main() {
await injectScript('/example-main-world.js', {
keepInDom: true,
});
},
});
json
export default defineConfig({
manifest: {
web_accessible_resources: [
{
resources: ["example-main-world.js"],
matches: ["*://*/*"],
}
]
}
});
动态目标UI的挂载
在某些情况下,需要动态添加目标元素。可以通过autoMount
函数自动挂载和移除UI:
typescript
export default defineContentScript({
matches: ['<all_urls>'],
main(ctx) {
const ui = createIntegratedUi(ctx, {
position: 'inline',
anchor: '#your-target-dynamic-element',
onMount: (container) => {
// 添加UI组件到容器
const app = document.createElement('p');
app.textContent = '...';
container.append(app);
},
});
ui.autoMount();
},
});
SPAs的处理
对于SPAs,由于内容脚本只在全页面刷新时运行,无法自动加载。需要手动监听路径变化:
typescript
const watchPattern = new MatchPattern('*://*.youtube.com/watch*');
export default defineContentScript({
matches: ['*://*.youtube.com/*'],
main(ctx) {
ctx.addEventListener(window, 'wxt:locationchange', ({ newUrl }) => {
if (watchPattern.includes(newUrl)) {
mainWatch(ctx);
}
});
},
});
function mainWatch(ctx: ContentScriptContext) {
const ui = createIntegratedUi(ctx, {
position: 'inline',
anchor: '#your-target-element',
onMount: () => {
// 添加UI组件到容器
const app = document.createElement('p');
app.textContent = '...';
container.append(app);
},
});
ui.mount();
}
结论
通过以上方法,可以有效地创建和配置内容脚本,支持动态目标、隔离样式和主世界交互。