Может ли расширение firefox изменить DOM HTML-документа, а затем сохранить его как HTML?

Я создаю расширение Firefox, которое позволяет оператору выполнять различные действия, изменяющие содержимое HTML-документа. Оператор не редактирует HTML, он выполняет другие действия, а мое расширение модифицирует документ, вставляя элементы, добавляя атрибуты и так далее.

Когда оператор закончит работу, он должен иметь возможность сохранить HTML-документ в виде файла (или разрешить моему расширению отправить его в пункт назначения в Интернете, но это не требуется, поскольку он может отправить сохраненный файл по электронной почте).

Я подумал, что, возможно, изменения, сделанные кодом javascript в моем расширении, будут отражены в документе HTML, но когда я прошу браузер Firefox «просмотреть исходный код» после внесения изменений, он отображает исходный текст HTML.

Мои вопросы:

#1: Как проще всего оператору сохранить HTML-документ со всеми изменениями, внесенными моим расширением?

# 2: Каков самый простой способ для кода javascript в моем расширении обработать содержимое HTML-документа и записать в HTML-файл на локальном диске?

#3: Является ли какой-либо допустимый HTML-контент неспособным к точному представлению в сохраненном файле?

#4: Является ли TreeWalker частью решения (см. ниже)?


Пара наблюдений из моего исследования:

Я читал об объекте TreeWalker, который, кажется, обеспечивает довольно безболезненный способ для расширения пройти через все (? или почти все?) в HTML-документе. Но раскрывает ли он все, чтобы все в оригинале (и моих модификациях) можно было сохранить, не теряя ничего важного?

Проходит ли TreeWalker по HTML-документу в «правильном порядке» — порядке, необходимом моему расширению для создания исходного и/или измененного HTML-документа?

Что-нибудь неясное или сложное в этих проблемах?


person honestann    schedule 28.11.2013    source источник


Ответы (4)


Итак, я предполагаю, что у вас есть доступ к странице DOM. Что вам нужно сделать, в основном внесите изменения в dom, а затем получите весь код dom и сохраните его в виде файла. Вот как вы можете загрузить html-код страницы. Это создаст тег a, по которому пользователь должен щелкнуть, чтобы файл загрузился.

var a = document.createElement('a'), code = document.querySelectorAll('html')[0].innerHTML;
a.setAttribute('download', 'filename.html');
a.setAttribute('href', 'data:text/html,' + code);

Теперь вы можете вставить этот тег в любом месте DOM, и файл будет загружен, когда пользователь щелкнет по нему.

Примечание. Это своего рода хак, он вставляет весь html файла в тег a, теоретически он должен работать в любом up текущий браузер (кроме, неожиданно, IE). Есть более стабильные и менее хакерские способы сделать это, например, сохранить его в файле API файловой системы, а затем вместо этого загрузить этот файл.

Изменить. Строка document.querySelectorAll обращается к DOM страницы. Чтобы это работало, document должен быть доступен. Вы говорите, что модифицируете DOM, так что он уже должен быть там. Убедитесь, что вы добавляете код на страницу, а не код расширения. Этот код будет находиться в том же месте, что и ваш код модификации DOM, а не ваши страницы расширения, которые не могут получить доступ к DOM.

А что касается тега a, то он будет вставлен на страницу. Я пропустил шаги, так как предположил, что вы уже знаете, как манипулировать DOM, а также потому, что я не знаю, куда вы хотели бы добавить ссылку. И вы также можете пропустить действие пользователя по переходу по ссылке, но это хак и работает только в современных браузерах. Вы можете вставить тег a где-нибудь на исходной странице, где пользователь его не увидит, а затем вызвать функцию a.click() для имитации события клика по ссылке. Но это незаконный способ, и я лично использую его только в своих практических проектах для вызова прослушивателей событий кликов.

Я могу проверить это только на хроме, а не на FF, но попробуйте этот код, для этого вам даже не потребуется добавлять ссылку в DOM. Вам нужно добавить это рядом с кодом манипулирования DOM. Это сработает, если удача будет на вашей стороне :)

var a = document.createElement('a'), code = document.querySelectorAll('html')[0].innerHTML;
a.setAttribute('download', 'filename.html');
a.setAttribute('href', 'data:text/html,' + code);
a.click();
person Achshar    schedule 29.11.2013
comment
Похоже, ваш код сохраняет один элемент документа DOM в файл на диске. Что мне нужно сделать, так это сохранить весь HTML-документ в файл на диске. Знаете ли вы, что вы видите, когда выбираете просмотр источника страницы в меню браузера? Вот что мне нужно сохранить — весь HTML-документ в текстовом формате HTML. ОДНАКО, после того как мое расширение вносит много изменений в DOM (например, вставляет десятки новых элементов с новыми атрибутами), то, что вы видите при просмотре исходного кода страницы, является исходным HTML, загруженным в браузер. Мне нужно сохранить текст HTML, ВКЛЮЧАЮЩИЙ все изменения. - person honestann; 29.11.2013
comment
Да, это тег html, который будет представлять собой всю страницу, за исключением типа документа и любых атрибутов самого тега, потому что тег html всегда содержит весь код страницы. Так что это будет все на странице view source, и я проверил, innerHTML действительно отражает любые изменения, внесенные в DOM через js, так что вы должны быть золотыми. - person Achshar; 29.11.2013
comment
Это очень круто, спасибо! Можете ли вы объяснить, что делает каждый шаг? Кроме того, есть ли способ сделать то же самое полностью с моим кодом расширения, чтобы оператору не нужно было ничего делать в браузере (например, находить и нажимать кнопку)? Я предполагаю, что то, что вы показываете выше, это то, что я вставляю с помощью javascript в свое расширение, верно? Когда вы говорите, что файл будет загружен, я предполагаю, что вы имеете в виду, что HTML-документ будет записан в файл filename.html в локальной файловой системе оператора (возможно, по какому-то неясному пути). Могу ли я указать путь, например, chrome://... где находится мое расширение? - person honestann; 30.11.2013
comment
Кстати, мой javascript расширения выполнил эти строки после того, как он внес 10 изменений в DOM, но консоль ошибок вывела следующее сообщение об ошибке: TypeError: document.querySelectorAll(...)[0] is undefined. Вы знаете, в чем проблема? - person honestann; 30.11.2013
comment
Я не могу понять, как изменить эту строку document.querySlectorAll(), чтобы она работала (не генерировала никаких ошибок). Я пробовал читать документацию по этой функции, но это выше моего понимания! Еще вопрос по вашему коду. Вы создаете элемент, а затем устанавливаете для него два атрибута, но никуда не вставляете его в документ DOM. Все хорошо? - person honestann; 30.11.2013
comment
Когда пользователь нажмет кнопку, откроется приглашение для загрузки. Как и любой файл, который вы загружаете из Интернета. Я сделал некоторые предположения, когда писал ответ, я обновлю его снова, чтобы ответить на некоторые другие ваши вопросы. - person Achshar; 30.11.2013
comment
Упс! Майор, майор упс! Мое расширение должно работать на любых случайных веб-страницах (или HTML-файлах), которые кто-либо загружает. Поэтому мое расширение НЕ МОЖЕТ контролировать то, что находится в исходном HTML-документе... оно может добавлять контент только через javascript в расширение, вызывая такие функции, как document.createElement() и element.setAttribute() и так далее. Итак... нам нужно начать сначала и искать другой путь? - person honestann; 01.12.2013
comment
Когда вы говорите, что манипулируете DOM, изменяется ли веб-страница или страница расширения? Если это страница расширения, то, боюсь, вам придется начать заново и переосмыслить поток приложений. - person Achshar; 01.12.2013
comment
Это мое первое расширение для Firefox. Я не понимаю всю терминологию, отчасти потому, что мое расширение делает меньше, чем большинство расширений. Мое расширение не добавляет/изменяет элементы меню Firefox или элементы графического интерфейса. Мое расширение позволяет оператору firefox останавливать курсор мыши над любым термином (словом, фразой или аббревиатурой) и добавлять вокруг них 1+ элементов. Например, он может установить новый цвет переднего плана (текста), выделить термин жирным шрифтом или курсивом или добавить/установить атрибуты, являющиеся собственностью моего приложения). Когда закончите, он хочет сохранить HTML-страницу! Это все. Я даже не знаю, что такое страница расширения! - person honestann; 02.12.2013

Нет простого способа сделать это только с помощью веб-API, по крайней мере, когда вам нужен результат, который не пропускает такие вещи, как тип документа или комментарии. Вы все еще можете сами написать сериализатор, который проходит через document.childNodes и сериализуется в соответствии с типом узла (Element.outerHTML, Comment.data и т. д.).

К счастью, вы пишете надстройку для Firefox, так что у вас есть доступ к гораздо большему (мощному) материалу.

Хотя все еще не на 100 % идеальный, nsIDocumentEncoder реализации будут давать довольно приличные результаты, которые должны отличаться не более чем некоторыми пробелами и явным объявлением кодировки (все остальное - ошибка). Вот пример того, как можно использовать этот компонент:

function serializeDocument(document) {
    const {
        classes: Cc,
        interfaces: Ci,
        utils: Cu
    } = Components;
    let encoder = Cc['@mozilla.org/layout/documentEncoder;1?type=text/html'].createInstance(Ci.nsIDocumentEncoder);
    encoder.init(document, 'text/html', Ci.nsIDocumentEncoder.OutputLFLineBreak | Ci.nsIDocumentEncoder.OutputRaw);
    encoder.setCharset("utf-8");
    return encoder.encodeToString();
}

Если вы пишете надстройку SDK, все становится сложнее, поскольку SDK абстрагирует некоторые важные вещи. Вам нужно пройти через chrome модуль, а также сами разберитесь с активным окном и вкладкой. Что-то вроде Services.wm.getMostRecentWindow("navigator:browser").content.document (Services.jsm) должно выполнять обманывать.

В оверлейных надстройках XUL content.document должно быть достаточно, чтобы получить документ текущей активной вкладки, а у вас уже есть Components доступ.

Тем не менее, вам нужно позволить пользователю выбрать место назначения файла, обычно через nsIFilePicker а затем на самом деле запишите файл, используя что-то вроде файлового потока. или полностью асинхронный API OS.File.

person nmaier    schedule 02.12.2013
comment
Спасибо за идеи и информацию. Мне нужно изучить термины и функции в вашем сообщении, так как я с ними не знаком. То, что будет делать мое расширение, ошеломляет, но вся гениальность заключается в разделяемой библиотеке C и связанных файлах, а не в этом простом расширении. Как я уже сказал, мое расширение не использует графический интерфейс или меню в браузере, но отображает крошечные окна без полей, которые позволяют пользователю управлять расширением. Что мне нужно, так это наставник или подрядчик, чтобы помочь (как только я объясню, что я делаю, чего я не могу делать публично). Если вы обдумаете это, напишите мне по адресу honorann-at-ymail-dot-com. Спасибо. - person honestann; 03.12.2013
comment
Некоторые незначительные примечания о небольших различиях в пробелах, непонятных полях в документе HTML, которые не могут быть сериализованы легко или естественно, и кодировке символов документа. Один из способов обойти это — сохранить и отправить исходный документ, а также тело измененного документа. Таким образом, другое приложение могло бы вставить новое тело в исходный документ. Кроме того, из-за характера моего приложения мое расширение действительно должно сохранять документ в UTF-8, даже если оригинал был не в UTF-8. При необходимости мое приложение могло работать ТОЛЬКО с документами UTF-8. - person honestann; 03.12.2013

Похоже, я сам отвечу на свой вопрос благодаря кому-то из mozilla #extdev IRC.

Я был полностью сфальсифицирован "просмотреть источник". Когда я не видел своих изменений в окне, отображаемом «просмотр исходного кода», я предположил, что браузер не предоставит информацию.

Однако угадайте, что? Когда я "файл" ===>> "сохранить страницу как...", затем просмотрите содержимое страницы с помощью обычного текстового редактора... конечно же, он содержал изменения, сделанные моим расширением firefox! Сюрприз!

person honestann    schedule 14.12.2013

Браузер не имеет прямого доступа для записи к локальной файловой системе. Единственный доступ для чтения, который у него есть, — это явное указание URL-адреса file:// (см. примечание 1 ниже).

В вашем случае мы явно говорим о javascript, который может читать и записывать файлы cookie и локальное хранилище. Он также может отправлять данные обратно на сервер и извлекать их, например. используя АЯКС.

Материалы, которые вы помещаете в локальное хранилище/файлы cookie, фактически недоступны для других программ (например, почтовых клиентов).

Можно создавать очень длинные URL-адреса mailto: (см. примечание 2), но они обрабатывают только встроенный контент в электронной почте, и вы столкнетесь со всеми видами проблем с кодировкой, с которыми вы не готовы иметь дело.

Следовательно, я бы рекомендовал использовать сервер хранения через AJAX - и посмотреть на локальное хранилище, как только вы отсортируете / заработаете.

Примечание 1: это не совсем так. надежный, подписанный javascript имеет доступ к дополнительным функциям, которые могут включать прямой доступ к файлам.

Примечание 2: (ограничение зависит от браузера и почтового клиента - Lotus Notes довольно сильно обрезает содержимое)

person symcbean    schedule 29.11.2013
comment
Я предлагаю вам перечитать вопрос: Автор явно спрашивает о том, чтобы делать эти вещи с надстройкой Firefox, которая имеет те же возможности, что и сам браузер, в т.ч. прямой доступ к файлам для чтения/записи. Вы говорите о доступе к веб-контенту, который может быть, а может и не быть, а это совсем другое. - person nmaier; 29.11.2013
comment
Конечно, браузер имеет доступ к файловой системе. Он сохраняет мои загрузки в файловой системе. - person Thomas Weller; 30.04.2020