Firefox Extension 如何在页面中写入 HTML 和 JS?
How do you write HTML and JS to a page with Firefox Extension?
我不知道如何将 HTML 和 javascript 注入带有 Firefox 扩展的网页。
这适用于 Chrome 扩展,但不适用于 Firefox。请注意,我使用 chrome.extension.getURL 来引入 HTML 和 Javascript.
这是我的清单 - 注意我什至不使用背景材料
{
"background": {
"scripts": [ "js/background.js", "js/jquery.js"]
},
"content_scripts": [ {
"js": [ "js/jquery.js", "js/chart-min.js", "js/chart-colors.js", "js/jquery-ui.min.js", "js/profiles/my_request.js", "js/options.js", "js/profiles/projects_builder.js", "js/profiles/board_builder.js", "js/profiles/my_api.js", "js/profiles/user_board_builder.js", "js/profiles/user_board.js","js/profiles/default_labels.js", "js/profiles/default_lists.js", "js/profiles/master_board.js", "js/profiles/projects.js", "js/profiles/estimates.js", "js/profiles/new_todo_label.js","js/profiles/reports_dashboard.js", "js/profiles/mutation_observer.js", "js/profiles/completion_chart.js", "js/profiles/cumulative_flow_chart.js" ],
"matches": [ "https://asana.com/*" ],
"all_frames": true
}],
"permissions":[ "identity", "cookies", "storage", "activeTab", "https://asana.com/*"],
"name": "Boards here",
"short_name" : "boards",
"version": "3.1.9.5",
"manifest_version": 2,
"icons" : { "48": "images/logo_thicker.png"},
"description": "html for website",
"browser_action":{
"default_icon":"images/logo_thicker.png",
"default_popup":"html/popup.html"
},
"web_accessible_resources": [ "images/*", "html/*" ]
}
默认列表示例 -- default_lists.js 利用 my_request 这只是一个 jquery ajax 包装器
DefaultLists = (function() {
function DefaultLists() {
if (window.location.href.includes('#default_lists')) {
this.show_form()
}
DefaultLists.prototype.show_form = function {
my_request.ajax({
url: chrome.extension.getURL("html/manage_default_lists.html"),
type: 'get',
success: function (data) {
$('.panel.panel--project').remove()
$('.panel.panel--perma').html(data)
}
});
};
}
return DefaultLists;
})();
window.default_lists = new DefaultLists();
所以现在 manage_default_lists.html 看起来像
<section style="position:relative;top:-50px;" class="message-board__content">
<bc-infinite-page page="1" reached-infinity="" direction="down" trigger="bottom">
<table class="my_labels" data-infinite-page-container="">
<tbody>
<tr id="loading_lists" >
<td>
<span style="margin-left:280px;color:grey">Loading...</span>
</td>
</tr>
<tr id="create_row" style="display:none">
<td>
<span class="">
<input id="new_label_name" placeholder="List name" type="text" style="width:180px;font-size:15px;margin-left:42px;padding:5px;border-radius: 0.4rem;border: 1px solid #bfbfbf;" value="">
<a style="margin-left:10px;float:right;margin-right:80px" class="cancel_new btn--small btn small" href="#">cancel</a>
<input id="create_label" style="float:right;" type="submit" value="save" class="btn btn--small btn--primary primary small" data-behavior="loading_on_submit primary_submit" data-loading-text="saving…">
</span>
</td>
</tr>
</tbody>
</table>
</bc-infinite-page>
</section>
</article>
<script>
$('#cancel_delete_label_button').on('click', function(event){
$('#delete_label_modal').hide()
});
$('#cancel_force_lists_button').on('click', function(event){
$('#force_lists_modal').hide()
});
$(document).off( "click", '.edit_label').on('click', '.edit_label', function(event) {
td = $(this).parents('td')
td.find('.show_row').hide()
td.find('.edit_row').show()
event.preventDefault()
});
$(document).off( "click", '.cancel_edit').on('click', '.cancel_edit', function(event) {
td = $(this).parents('td')
td.find('.show_row').show()
td.find('.edit_row').hide()
event.preventDefault()
});
$(document).off( "click", '.cancel_new').on('click', '.cancel_new', function(event) {
// console.log('cancel')
$('#create_row').hide()
event.preventDefault()
});
$(document).off( "click", '#new_label_button').on('click', '#new_label_button', function(event) {
$('#create_row').show()
$('#new_label_name').val('')
event.preventDefault()
});
$(document).off( "click", '#labels_article').on('click', '#labels_article', function(event) {
// console.log(event.target.className)
if (event.target.className != 'color-editor-bg'){
$('.label-colors').hide();
}
});
</script>
您正在使用您的内容脚本和 web_accessible_resources 将此 HTML 添加到网页中。它与禁止扩展页面(如 browser_action 弹出窗口或选项页面)中的内联脚本的扩展 CSP 无关。在您的情况下,页面的 CSP 适用于您在其中添加的内容。该页面可能很容易禁止内联脚本,网站经常这样做。
您可以使用 webRequest API 重写页面的 Content-Security-Policy
HTTP header 或 稍微修改代码 ,后者这是一个更好的解决方案,不仅因为它更专注,而且因为当多个扩展在同一个 HTTP 请求中重写 HTTP header 时,重写的结果是随机的。
所以让我们重写代码:
- 单独存储脚本
- 通过browser.tabs.executeScript
获取和注入
- 运行 需要时注入的代码
行为的重要变化是代码将 运行ning 在内容脚本的上下文中,使用内容脚本的 jQuery 和变量。以前您的代码在页面上下文中是 运行ning,因此它使用了 jQuery 和页面的其他变量。
我正在使用 Mozilla's browser
WebExtension namespace polyfill 和 async/await 语法。
manifest.json:
"background": {
"scripts": [
"browser-polyfill.min.js",
"background.js"
]
},
"content_scripts": [{
"js": [
"browser-polyfill.min.js",
"content.js"
],
"matches": ["........."]
}],
目录结构:
- html/manage_default_lists.html
- html/manage_default_lists.js
由于executeScript只能在扩展页面中使用,不能在内容脚本中使用,我们将通过指令向后台脚本发送消息。
content.js:
async function getResource(name) {
const htmlUrl = browser.runtime.getURL(`html/${name}.html`);
const [html, scriptId] = await Promise.all([
fetch(htmlUrl).then(r => r.text()),
browser.runtime.sendMessage({
cmd: 'executeScript',
path: `html/${name}.js`,
}),
]);
const js = window[scriptId];
delete window[scriptId];
return {html, js};
}
DefaultLists.prototype.show_form = async () => {
const {html, js} = await getResource('manage_default_lists');
$('.panel.panel--project').remove()
$('.panel.panel--perma').html(html);
js();
};
background.js:
browser.runtime.onMessage.addListener(async (message, sender) => {
if (!message) return;
switch (message.cmd) {
case 'executeScript': {
const js = await (await fetch(browser.runtime.getURL(message.path))).text();
const id = `js.${performance.now()}${Math.random()}`;
await browser.tabs.executeScript(sender.tab.id, {
// 0 at the end is to prevent executeScript from additionally returning
// the function's code as a string, which we don't need
code: `window["${id}"] = () => { ${js} };0`,
frameId: sender.frameId,
matchAboutBlank: true,
runAt: 'document_start',
});
return id;
}
}
});
我不知道如何将 HTML 和 javascript 注入带有 Firefox 扩展的网页。
这适用于 Chrome 扩展,但不适用于 Firefox。请注意,我使用 chrome.extension.getURL 来引入 HTML 和 Javascript.
这是我的清单 - 注意我什至不使用背景材料
{
"background": {
"scripts": [ "js/background.js", "js/jquery.js"]
},
"content_scripts": [ {
"js": [ "js/jquery.js", "js/chart-min.js", "js/chart-colors.js", "js/jquery-ui.min.js", "js/profiles/my_request.js", "js/options.js", "js/profiles/projects_builder.js", "js/profiles/board_builder.js", "js/profiles/my_api.js", "js/profiles/user_board_builder.js", "js/profiles/user_board.js","js/profiles/default_labels.js", "js/profiles/default_lists.js", "js/profiles/master_board.js", "js/profiles/projects.js", "js/profiles/estimates.js", "js/profiles/new_todo_label.js","js/profiles/reports_dashboard.js", "js/profiles/mutation_observer.js", "js/profiles/completion_chart.js", "js/profiles/cumulative_flow_chart.js" ],
"matches": [ "https://asana.com/*" ],
"all_frames": true
}],
"permissions":[ "identity", "cookies", "storage", "activeTab", "https://asana.com/*"],
"name": "Boards here",
"short_name" : "boards",
"version": "3.1.9.5",
"manifest_version": 2,
"icons" : { "48": "images/logo_thicker.png"},
"description": "html for website",
"browser_action":{
"default_icon":"images/logo_thicker.png",
"default_popup":"html/popup.html"
},
"web_accessible_resources": [ "images/*", "html/*" ]
}
默认列表示例 -- default_lists.js 利用 my_request 这只是一个 jquery ajax 包装器
DefaultLists = (function() {
function DefaultLists() {
if (window.location.href.includes('#default_lists')) {
this.show_form()
}
DefaultLists.prototype.show_form = function {
my_request.ajax({
url: chrome.extension.getURL("html/manage_default_lists.html"),
type: 'get',
success: function (data) {
$('.panel.panel--project').remove()
$('.panel.panel--perma').html(data)
}
});
};
}
return DefaultLists;
})();
window.default_lists = new DefaultLists();
所以现在 manage_default_lists.html 看起来像
<section style="position:relative;top:-50px;" class="message-board__content">
<bc-infinite-page page="1" reached-infinity="" direction="down" trigger="bottom">
<table class="my_labels" data-infinite-page-container="">
<tbody>
<tr id="loading_lists" >
<td>
<span style="margin-left:280px;color:grey">Loading...</span>
</td>
</tr>
<tr id="create_row" style="display:none">
<td>
<span class="">
<input id="new_label_name" placeholder="List name" type="text" style="width:180px;font-size:15px;margin-left:42px;padding:5px;border-radius: 0.4rem;border: 1px solid #bfbfbf;" value="">
<a style="margin-left:10px;float:right;margin-right:80px" class="cancel_new btn--small btn small" href="#">cancel</a>
<input id="create_label" style="float:right;" type="submit" value="save" class="btn btn--small btn--primary primary small" data-behavior="loading_on_submit primary_submit" data-loading-text="saving…">
</span>
</td>
</tr>
</tbody>
</table>
</bc-infinite-page>
</section>
</article>
<script>
$('#cancel_delete_label_button').on('click', function(event){
$('#delete_label_modal').hide()
});
$('#cancel_force_lists_button').on('click', function(event){
$('#force_lists_modal').hide()
});
$(document).off( "click", '.edit_label').on('click', '.edit_label', function(event) {
td = $(this).parents('td')
td.find('.show_row').hide()
td.find('.edit_row').show()
event.preventDefault()
});
$(document).off( "click", '.cancel_edit').on('click', '.cancel_edit', function(event) {
td = $(this).parents('td')
td.find('.show_row').show()
td.find('.edit_row').hide()
event.preventDefault()
});
$(document).off( "click", '.cancel_new').on('click', '.cancel_new', function(event) {
// console.log('cancel')
$('#create_row').hide()
event.preventDefault()
});
$(document).off( "click", '#new_label_button').on('click', '#new_label_button', function(event) {
$('#create_row').show()
$('#new_label_name').val('')
event.preventDefault()
});
$(document).off( "click", '#labels_article').on('click', '#labels_article', function(event) {
// console.log(event.target.className)
if (event.target.className != 'color-editor-bg'){
$('.label-colors').hide();
}
});
</script>
您正在使用您的内容脚本和 web_accessible_resources 将此 HTML 添加到网页中。它与禁止扩展页面(如 browser_action 弹出窗口或选项页面)中的内联脚本的扩展 CSP 无关。在您的情况下,页面的 CSP 适用于您在其中添加的内容。该页面可能很容易禁止内联脚本,网站经常这样做。
您可以使用 webRequest API 重写页面的 Content-Security-Policy
HTTP header 或 稍微修改代码 ,后者这是一个更好的解决方案,不仅因为它更专注,而且因为当多个扩展在同一个 HTTP 请求中重写 HTTP header 时,重写的结果是随机的。
所以让我们重写代码:
- 单独存储脚本
- 通过browser.tabs.executeScript 获取和注入
- 运行 需要时注入的代码
行为的重要变化是代码将 运行ning 在内容脚本的上下文中,使用内容脚本的 jQuery 和变量。以前您的代码在页面上下文中是 运行ning,因此它使用了 jQuery 和页面的其他变量。
我正在使用 Mozilla's browser
WebExtension namespace polyfill 和 async/await 语法。
manifest.json:
"background": {
"scripts": [
"browser-polyfill.min.js",
"background.js"
]
},
"content_scripts": [{
"js": [
"browser-polyfill.min.js",
"content.js"
],
"matches": ["........."]
}],
目录结构:
- html/manage_default_lists.html
- html/manage_default_lists.js
由于executeScript只能在扩展页面中使用,不能在内容脚本中使用,我们将通过指令向后台脚本发送消息。
content.js:
async function getResource(name) {
const htmlUrl = browser.runtime.getURL(`html/${name}.html`);
const [html, scriptId] = await Promise.all([
fetch(htmlUrl).then(r => r.text()),
browser.runtime.sendMessage({
cmd: 'executeScript',
path: `html/${name}.js`,
}),
]);
const js = window[scriptId];
delete window[scriptId];
return {html, js};
}
DefaultLists.prototype.show_form = async () => {
const {html, js} = await getResource('manage_default_lists');
$('.panel.panel--project').remove()
$('.panel.panel--perma').html(html);
js();
};
background.js:
browser.runtime.onMessage.addListener(async (message, sender) => {
if (!message) return;
switch (message.cmd) {
case 'executeScript': {
const js = await (await fetch(browser.runtime.getURL(message.path))).text();
const id = `js.${performance.now()}${Math.random()}`;
await browser.tabs.executeScript(sender.tab.id, {
// 0 at the end is to prevent executeScript from additionally returning
// the function's code as a string, which we don't need
code: `window["${id}"] = () => { ${js} };0`,
frameId: sender.frameId,
matchAboutBlank: true,
runAt: 'document_start',
});
return id;
}
}
});