【问题标题】:Intercept and modify DOM before page is displayed to user在页面显示给用户之前拦截和修改 DOM
【发布时间】:2015-04-18 20:10:55
【问题描述】:

我正在尝试创建一个 Firefox 插件(使用插件 SDK)来修改页面的显示方式,主要是作为培训/学习练习。

对于某些任务(例如用新功能扩充页面),使用pageMod 非常好。页面加载,我运行一些 JS 来显示/隐藏/添加元素。

我的问题是:我可以在页面开始显示之前对 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>

在页面加载后执行此操作将首先显示表格,然后它会快速“闪烁”到列表中。它可能足够快,但如果我要将&lt;img&gt; 标记更改为&lt;a&gt;,例如防止(不需要)图像加载,这还不够。

我正在查看在 pageMod 中使用 contentScriptWhen: "start" 并尝试附加侦听器,但我只是看不出我如何能够“即时”修改 DOM(或事件阻止任何类型的页面显示在所有页面之前已加载)。

我已经检查了cloud-to-butt 扩展,因为它确实会即时修改页面,但我什至无法让它工作:当在start 上作为 pageMod 附加时,代码失败:

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

【问题讨论】:

  • 我实际上最近不得不在一个 FF 扩展中做同样的事情,并决定在加载头部后添加一个隐藏整个身体的样式标签来“破解”它,然后修改HTML,完成后,删除样式标签,显示正文,不闪烁,问题解决。可能有更好的方法来做到这一点。
  • 这是一个有趣的想法,将来可能会派上用场 :) 虽然我实际上正在寻找一种方法来 - 除其他外 - 删除一些脚本,删除一些图像(类似广告块) - 再次, amonsts others - 节省加载时间。
  • 如果要截取数据并反馈修改后的数据使用nsITraceableChannel:github.com/Noitidart/demo-nsITraceableChannel

标签: javascript firefox-addon firefox-addon-sdk mozilla


【解决方案1】:
/*
 * 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();

【讨论】:

  • 这个想法+1。它可以工作,但不会加载或执行脚本,不知道为什么。它们必须手动插入。此外,无需再次通过 XMLHttpRequest 请求页面。
  • @brandon,如果不需要重新请求页面,还有什么替代方法?
【解决方案2】:

我的问题是:我可以在页面开始显示之前对 DOM(即服务器返回的 HTML 文档)进行修改吗?

是的,javascript 执行在页面第一次呈现之前开始。 DOM 解析器会通知mutation observers,因此您可以在解析器添加元素后立即删除它们。

您甚至可以在加载了contentScriptWhen: "start" 的内容脚本中注册突变观察者,因此应该在渲染之前通知他们所有添加到树中的元素,因为观察者通知是在微任务队列中执行的,而渲染发生在宏任务队列。

但我什至无法让它工作:当在启动时作为 pageMod 附加时,代码失败: document.getElementById('appcontent').addEventListener('DOMContentLoaded', function(e)

当然。您不应该假设任何特定的元素——甚至&lt;body&gt; 标签——在页面加载的早期就已经可用。您必须等待它们可用。

DOMContentLoaded 事件可以简单地在document 对象上注册。我不知道你为什么要在某些元素上注册它。

(或在所有页面加载之前阻止任何类型的页面显示的事件)。

您并不真正希望这样做,因为它会增加页面加载时间,从而降低网站的响应速度。

【讨论】:

  • 我认为 AdBlock 通过阻止用户下载广告图片、执行广告脚本等来减少页面加载时间 - 虽然处理响应当然需要时间,但它实际上可能不会结束增加页面加载时间 (YMMV)。
  • 我会看看突变观察者,他们可能会派上用场:)
  • 您提到的(阻止请求)是与修改 DOM 不同的问题,并且可以在不中断页面加载的情况下有效地完成,这就是 nsIContentPolicys 的用途。
  • 这是一个副作用,而不是主要目标 - 请参阅原始问题。我想获取一个网页,对其进行转换(想想:从一个臃肿的网页中提取内容并放入一个轻量级的 html/css 中)并只显示那个轻量级的页面。
【解决方案3】:

如果您想在任何脚本执行之前进入它,这里有文档观察者:https://developer.mozilla.org/en-US/docs/Observer_Notifications#Documents 例如content-document-global-created

【讨论】:

  • 链接失效
猜你喜欢
  • 1970-01-01
  • 2015-08-27
  • 1970-01-01
  • 2017-07-07
  • 2018-04-14
  • 2010-09-21
  • 2023-04-01
  • 2012-10-02
  • 1970-01-01
相关资源
最近更新 更多