在页面显示给用户之前拦截并修改 DOM
Intercept and modify DOM before page is displayed to user
我正在尝试创建一个 Firefox 插件(使用插件 SDK)来修改页面的显示方式,主要作为 training/learning 练习。
对于某些任务(例如使用新功能扩充页面),使用 pageMod
非常好。页面加载,我 运行 一些 JS 到 show/hide/add 元素。
我的问题是:我可以在页面开始显示之前对 DOM(因此:服务器返回的 HTML 文档)进行修改吗?
例如:从服务器返回的页面是:
<html>
<body>
<table>
<tr>
<td>Item 1.1</td>
<td>Item 1.2</td>
<td>Item 1.3</td>
</tr>
<tr>
<td>Item 2.1</td>
<td>Item 2.2</td>
<td>Item 2.3</td>
</tr>
</table>
</body>
</html>
但我希望 FF 渲染:
<html>
<body>
<ul>
<li>Item 1.1, Item 1.2, Item 1.3</li>
<li>Item 2.1, Item 2.2, Item 2.3</li>
</ul>
</body>
</html>
在页面加载后执行此操作将首先显示 table,然后它会快速 'blink' 到列表中。它可能足够快,但如果我将 <img>
标签更改为 <a>
,例如防止(不需要的)图像加载,这还不够。
我正在考虑在 pageMod 中使用 contentScriptWhen: "start"
并尝试附加侦听器,但我看不出如何实际修改 DOM 'on the fly'(或事件阻止任何加载所有页面之前显示的页面类型)。
我已经检查了 cloud-to-butt 扩展,因为它确实会即时修改页面,但我什至无法让它工作:当作为 pageMod 附加在 start
上时代码失败于:
document.getElementById('appcontent').addEventListener('DOMContentLoaded', function(e)
因为 document.getElementById('appcontent')
返回 null。
我将非常感谢一些指点:是否可能,如何附加脚本,如何拦截 HTML 并在修改后将其发送回原路。
编辑:
好的,所以我认为我能够拦截数据:
let { Ci,Cr,CC } = require('chrome');
let { on } = require('sdk/system/events');
let { newURI } = require('sdk/url/utils');
let ScriptableInputStream = CC("@mozilla.org/scriptableinputstream;1", "nsIScriptableInputStream", "init");
on('http-on-examine-response', function (event) {
var httpChannel = event.subject.QueryInterface(Ci.nsIHttpChannel);
var traceChannel = event.subject.QueryInterface(Ci.nsITraceableChannel);
if (/example.com/.test(event.subject.URI.spec)) {
traceChannel.setNewListener(new MyListener());
}
}, true);
function MyListener(downloader) {
this.data = "";
}
MyListener.prototype = {
onStartRequest: function(request, ctx) {
this.data = [];
},
onDataAvailable : function(request, context, inputStream, offset, count) {
var scriptStream = new ScriptableInputStream(inputStream);
this.data.push(scriptStream.read(count));
scriptStream.close();
},
onStopRequest: function(request, ctx, status) {
console.log(this.data.join(''));
}
}
现在 onStopRequest
我想对数据做一些事情并将其输出回原来的位置...
请注意,这适用于不是 DOM 的字符串,因此它并不完美,但它是一个开始的地方:)
编辑 2:
嗯,我成功了,虽然我觉得我不应该那样做:
onStopRequest: function(request, ctx, status) {
//var newPage = this.data.join('');
var newPage = "<html><body><h1>TEST!</h1></body></html>";
var stream = converter.convertToInputStream(newPage);
var count = {};
converter.convertToByteArray(newPage, count);
this.originalListener.onDataAvailable(request, ctx,
stream, 0, count.value);
this.originalListener.onStopRequest(request, ctx, status);
},
My problem is: can I perform modification on DOM (so: the HTML document that is returned by server) before the page even starts displaying?
是的,javascript 执行在页面第一次呈现之前开始。 DOM 解析器会通知 mutation observers,因此您可以在解析器添加元素后立即删除它们。
即使在加载了 contentScriptWhen: "start"
的内容脚本中,您也可以注册突变观察者,因此在渲染之前应通知他们所有添加到树中的元素,因为观察者通知是在微任务队列中执行的,而渲染发生在宏任务队列上。
but I wasn't even able to get it to work: when attached as a pageMod on start the code failed on:
document.getElementById('appcontent').addEventListener('DOMContentLoaded', function(e)
当然可以。您不应该假设任何特定的元素——甚至 <body>
标签——在页面加载的早期就已经可用。您将不得不等待它们可用。
并且 DOMContentLoaded
事件可以简单地注册到 document
对象上。我不知道你为什么要在某个元素上注册它。
(or event prevent any kind of page display before all page was loaded).
您真的不希望这样做,因为它会增加页面加载时间,从而降低网站的响应速度。
如果你想在任何脚本执行之前进入它,这里有文档观察者:https://developer.mozilla.org/en-US/docs/Observer_Notifications#Documents 例如 content-document-global-created
/*
* contentScriptWhen: "start"
*
* "start": Load content scripts immediately after the document
* element is inserted into the DOM, but before the DOM content
* itself has been loaded
*/
/*
* use an empty HTMLElement both as a place_holder
* and a way to prevent the DOM content from loading
*/
document.replaceChild(
document.createElement("html"), document.children[0]);
var rqst = new XMLHttpRequest();
rqst.open("GET", document.URL);
rqst.responseType = 'document';
rqst.onload = function(){
if(this.status == 200) {
/* edit the document */
this.response.children[0].children[1].querySelector(
"#content-load + div + script").remove();
/* replace the place_holder */
document.replaceChild(
document.adoptNode(
this.response.children[0]),
document.children[0]);
// use_the_new_world();
}
};
rqst.send();
我正在尝试创建一个 Firefox 插件(使用插件 SDK)来修改页面的显示方式,主要作为 training/learning 练习。
对于某些任务(例如使用新功能扩充页面),使用 pageMod
非常好。页面加载,我 运行 一些 JS 到 show/hide/add 元素。
我的问题是:我可以在页面开始显示之前对 DOM(因此:服务器返回的 HTML 文档)进行修改吗?
例如:从服务器返回的页面是:
<html>
<body>
<table>
<tr>
<td>Item 1.1</td>
<td>Item 1.2</td>
<td>Item 1.3</td>
</tr>
<tr>
<td>Item 2.1</td>
<td>Item 2.2</td>
<td>Item 2.3</td>
</tr>
</table>
</body>
</html>
但我希望 FF 渲染:
<html>
<body>
<ul>
<li>Item 1.1, Item 1.2, Item 1.3</li>
<li>Item 2.1, Item 2.2, Item 2.3</li>
</ul>
</body>
</html>
在页面加载后执行此操作将首先显示 table,然后它会快速 'blink' 到列表中。它可能足够快,但如果我将 <img>
标签更改为 <a>
,例如防止(不需要的)图像加载,这还不够。
我正在考虑在 pageMod 中使用 contentScriptWhen: "start"
并尝试附加侦听器,但我看不出如何实际修改 DOM 'on the fly'(或事件阻止任何加载所有页面之前显示的页面类型)。
我已经检查了 cloud-to-butt 扩展,因为它确实会即时修改页面,但我什至无法让它工作:当作为 pageMod 附加在 start
上时代码失败于:
document.getElementById('appcontent').addEventListener('DOMContentLoaded', function(e)
因为 document.getElementById('appcontent')
返回 null。
我将非常感谢一些指点:是否可能,如何附加脚本,如何拦截 HTML 并在修改后将其发送回原路。
编辑: 好的,所以我认为我能够拦截数据:
let { Ci,Cr,CC } = require('chrome');
let { on } = require('sdk/system/events');
let { newURI } = require('sdk/url/utils');
let ScriptableInputStream = CC("@mozilla.org/scriptableinputstream;1", "nsIScriptableInputStream", "init");
on('http-on-examine-response', function (event) {
var httpChannel = event.subject.QueryInterface(Ci.nsIHttpChannel);
var traceChannel = event.subject.QueryInterface(Ci.nsITraceableChannel);
if (/example.com/.test(event.subject.URI.spec)) {
traceChannel.setNewListener(new MyListener());
}
}, true);
function MyListener(downloader) {
this.data = "";
}
MyListener.prototype = {
onStartRequest: function(request, ctx) {
this.data = [];
},
onDataAvailable : function(request, context, inputStream, offset, count) {
var scriptStream = new ScriptableInputStream(inputStream);
this.data.push(scriptStream.read(count));
scriptStream.close();
},
onStopRequest: function(request, ctx, status) {
console.log(this.data.join(''));
}
}
现在 onStopRequest
我想对数据做一些事情并将其输出回原来的位置...
请注意,这适用于不是 DOM 的字符串,因此它并不完美,但它是一个开始的地方:)
编辑 2:
嗯,我成功了,虽然我觉得我不应该那样做:
onStopRequest: function(request, ctx, status) {
//var newPage = this.data.join('');
var newPage = "<html><body><h1>TEST!</h1></body></html>";
var stream = converter.convertToInputStream(newPage);
var count = {};
converter.convertToByteArray(newPage, count);
this.originalListener.onDataAvailable(request, ctx,
stream, 0, count.value);
this.originalListener.onStopRequest(request, ctx, status);
},
My problem is: can I perform modification on DOM (so: the HTML document that is returned by server) before the page even starts displaying?
是的,javascript 执行在页面第一次呈现之前开始。 DOM 解析器会通知 mutation observers,因此您可以在解析器添加元素后立即删除它们。
即使在加载了 contentScriptWhen: "start"
的内容脚本中,您也可以注册突变观察者,因此在渲染之前应通知他们所有添加到树中的元素,因为观察者通知是在微任务队列中执行的,而渲染发生在宏任务队列上。
but I wasn't even able to get it to work: when attached as a pageMod on start the code failed on:
document.getElementById('appcontent').addEventListener('DOMContentLoaded', function(e)
当然可以。您不应该假设任何特定的元素——甚至 <body>
标签——在页面加载的早期就已经可用。您将不得不等待它们可用。
并且 DOMContentLoaded
事件可以简单地注册到 document
对象上。我不知道你为什么要在某个元素上注册它。
(or event prevent any kind of page display before all page was loaded).
您真的不希望这样做,因为它会增加页面加载时间,从而降低网站的响应速度。
如果你想在任何脚本执行之前进入它,这里有文档观察者:https://developer.mozilla.org/en-US/docs/Observer_Notifications#Documents 例如 content-document-global-created
/*
* contentScriptWhen: "start"
*
* "start": Load content scripts immediately after the document
* element is inserted into the DOM, but before the DOM content
* itself has been loaded
*/
/*
* use an empty HTMLElement both as a place_holder
* and a way to prevent the DOM content from loading
*/
document.replaceChild(
document.createElement("html"), document.children[0]);
var rqst = new XMLHttpRequest();
rqst.open("GET", document.URL);
rqst.responseType = 'document';
rqst.onload = function(){
if(this.status == 200) {
/* edit the document */
this.response.children[0].children[1].querySelector(
"#content-load + div + script").remove();
/* replace the place_holder */
document.replaceChild(
document.adoptNode(
this.response.children[0]),
document.children[0]);
// use_the_new_world();
}
};
rqst.send();