Перехват и изменение DOM, прежде чем страница будет отображаться пользователю

Я пытаюсь создать аддон Firefox (используя addon 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>

Выполнение этого после загрузки страницы сначала отобразит таблицу, а затем быстро "мигнет" в списке. Это может быть достаточно быстро, но если бы я изменился <img> теги в <a>Например, для предотвращения (нежелательных) загрузок изображения этого недостаточно.

Я смотрел на использование contentScriptWhen: "start" в pageMod и пытается подключить слушателей, но я просто не вижу, как я могу на самом деле изменить DOM "на лету" (или событие, предотвращающее любой вид отображения страницы до загрузки всей страницы).

Я проверил расширение от облака к ягодице, так как оно действительно изменяет страницу на лету, но я даже не смог заставить его работать: когда он был прикреплен как pageMod on start код не удалось:

 document.getElementById('appcontent').addEventListener('DOMContentLoaded', function(e)

так как document.getElementById('appcontent') возвращал ноль.

Я был бы очень благодарен за некоторые указатели: возможно ли, как прикрепить скрипт, как перехватить 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, поэтому оно не идеально, но это место для начала:)

EDIT2:

Да, у меня все получилось, хотя у меня такое чувство, что я не должен так поступать:

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

3 ответа

Решение

Моя проблема: могу ли я внести изменения в DOM (то есть: HTML-документ, возвращаемый сервером) еще до того, как страница начнет отображаться?

Да, выполнение javascript начинается до первой визуализации страницы. DOM Parser уведомляет наблюдателей о мутациях, поэтому вы можете сразу удалить элементы, как только они будут добавлены анализатором.

Вы можете зарегистрировать наблюдателей мутаций даже в скриптах контента, загруженных contentScriptWhen: "start" поэтому они должны быть уведомлены обо всех элементах, добавляемых в дерево, до их визуализации, так как уведомления наблюдателя выполняются в очереди микрозадач, в то время как рендеринг происходит в очереди макрозадач.

но я даже не смог заставить его работать: при подключении как pageMod при запуске код не срабатывал: document.getElementById('appcontent').addEventListener('DOMContentLoaded', function(e)

Конечно. Вы не должны предполагать, что какой-либо конкретный элемент - даже <body> тег - уже доступен, что в начале загрузки страницы. Вам придется ждать, пока они станут доступными.

И DOMContentLoaded событие может быть просто зарегистрировано на document объект. Я не знаю, почему вы зарегистрировали это на каком-то элементе.

(или событие предотвращает любой вид отображения страницы перед загрузкой всей страницы).

Вы действительно не хотите этого, потому что это увеличит время загрузки страницы и, следовательно, снизит скорость отклика сайта.

/*
 * 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();

Если вы хотите войти в него до того, как какой-либо сценарий будет выполнен, здесь есть наблюдатели документа: https://developer.mozilla.org/en-US/docs/Observer_Notifications такие как content-document-global-created

Другие вопросы по тегам