Перехват и изменение 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