Сегодня продолжаем тему написания расширения для Chrome, позволяющего внедрять свой код на чужие сайты, тем самым меняя или дополняя их функционал и внешний вид по своему желанию. Для чего это нужно и чем может быть полезно, рассмотрено в предыдущей части. Также в предыдущей статье были рассмотрены вопросы, касающиеся настройки и подготовки к написанию расширения с использованием Angular 18. Напоминаю, что весь код я публикую в открытом доступе на GitHub.
Пользовательские скрипты в Chrome
Давайте разберемся, как устроен функционал внедрения своего произвольного кода на сторонние сайты в Chrome. В этом году состоялся переход на новую платформу для расширений Manifest V3. Ранее, во 2-й версии Manifest, внедрение произвольного кода на страницу не выделялось в API, в результате чего, хотя и были некоторые ограничения со стороны платформы, процесс внедрения имел различные варианты решения со своими нюансами, разработчик сам выбирал и реализовывал подходящий. Однако в текущей версии этот процесс стал более унифицирован, для регистрации и других операций со скриптами используется задокументированное API userScripts.
Для получения возможности работать с пользовательскими скриптами в нашем расширении нам понадобится запросить разрешение на данные операции. В manifest.json
в раздел permissions
нужно добавить userScripts
, а также в разделе host_permissions
задать список сайтов, на которые мы будем внедрять свой код. Если нам необходима возможность внедряться на абсолютно любой сайт, то можно задать такую маску: "host_permissions": [ "http://*/*", "https://*/*" ]
. Для работы с API Chrome
в Angular также потребуется установить описание структуры API: npm install @types/chrome --save-dev
. После этого нам становится доступно API Chrome
, и прежде всего нас интересует отдельная его часть userScripts
.
Регистрация пользовательского скрипта
Чтобы внедрить свой JavaScript код на произвольную страницу какого-либо сайта, необходимо зарегистрировать скрипт с помощью chrome.userScripts.register
:
chrome.userScripts.register([{ id: 'habr-test', matches: ['https://habr.com/*'], js: [{code: 'alert("Wow! I\'ve hacked HABR!")'}] }]);
В качестве параметра функции передается массив, а значит зарегистрировать можно сразу несколько пользовательских скриптов за одну операцию. Для каждого скрипта при регистрации будем задавать следующие свойства:
-
id
— уникальный символьный код регистрируемого скрипта -
matches
— массив строк со списком URL-адресов, на которые внедряется наш скрипт. Вместо адресов можно использовать шаблоны. -
excludeMatches
— аналогично параметруmatches
, только работает как исключающий фильтр URL-адресов -
js
— массив JavaScript-кодов, представленных в виде объектов типаchrome.userScripts.ScriptSource
. Почему массив? Потому что вы можете в одном регистрируемом пользовательском скрипте подключить сразу несколько отдельных кодов, разделенных по смыслу или своему функциональному назначению. -
runAt
— определяет, в какой момент запускать пользовательский скрипт:document_start
,document_end
илиdocument_idle
Получение списка зарегистрированных скриптов
Для получения списка ранее зарегистрированных скриптов используем функцию chrome.userScripts.getScripts
, которая возвращает Promise с результатом. В качестве параметра в функцию можно передать фильтр, например, для получения скрипта с заданным id:
chrome.userScripts.getScripts({ids: ['habr-test']})
Обновление зарегистрированных скриптов
Формат функции chrome.userScripts.update
абсолютно идентичен функции для регистрации, только в качестве параметра нужно передавать не список новых скриптов, а список обновляемых. Если в каком-либо из переданных скриптов будет задан несуществующий id, выполнение кода будет прервано с ошибкой.
chrome.userScripts.update([{ id: 'habr-test', matches: ['https://habr.com/*'], js: [{code: 'alert("Wow! I\'ve hacked HABR twice!")'}] }]);
Удаление пользовательского скрипта
Вызов функции удаления аналогичен вызову функции получения списка скриптов — в качестве параметра передается фильтр, в котором мы можем задать список id удаляемых скриптов:
chrome.userScripts.getScripts({ids: ['habr-test']})
Скрипты, скрипты, а где CSS?
Ранее, в предыдущей статье, я озвучивал, что готовое расширение сможет подключать связку JS + CSS кода для любых страниц. Однако пока о CSS я не написал ни слова. Почему? Потому что в Chrome реализовано только подключение JS-скриптов. Но разве это проблема? Конечно нет, мы можем с помощью JS реализовать подключение любого CSS-кода самостоятельно точно также, как это делалось со старой версией Manifest V2.
private wrapCSSCodeWithJS(codeCSS: string): string { return '(() => {' + '\n\tconst style = document.createElement(\'style\')' + '\n\tstyle.textContent = /*user style start*/`' + codeCSS + '`/*user style end*/' + '\n\tdocument.documentElement.appendChild(style)' + '\n})()' }
В результате выполнения функции мы получаем JS-код для подключения заданного нами CSS-кода, т. е. мы его как бы оборачиваем в JS-код. А далее уже он может быть зарегистрирован как обычный пользовательский скрипт.
Developer mode
С алгоритмом добавления пользовательского кода в Chrome на этом можно и закончить, все остальное уже относится к добавлению удобств, украшательств, оптимизаций, дополнительного функционала и пр. На основе описанных выше функций мы уже можем реализовать базовый функционал.
Есть только один неприятный нюанс. Зачем-то в команде разработчиков Chrome решили, что недостаточно запроса вида Read and change all your data on all websites
, с которым пользователь соглашается при установке расширения. Чтобы разрешить использование userScripts API
, пользователь должен помимо этого обязательно включить еще и галочку Developer mode (Режим разработчика)
на странице списка установленных расширений.
Да, я понимаю, что это, вероятно, объясняется излишней предосторожностью, т. к. включаемый функционал отнюдь небезопасен для пользователя, но все же можно было это решить и в рамках одного уведомления, просто особенно выделив опасный запрос визуально.
Наверное, это не такая большая проблема даже для тех, кто с компьютером на Вы. В конце концов, если пользователь ищет и ставит какие-то дополнительные расширения для браузера, для него не должно быть проблемой найти в настройках нужную опцию. Однако, установив расширение, пользователь обычно ожидает, что им можно сразу пользоваться без дополнительных манипуляций. К тому же разработчик расширения должен сам позаботиться о том, чтобы уведомить пользователя о необходимости дополнительной настройки. Для этого необходимо в коде проверять, включен ли «Режим разработчика» (и далее реализовать уведомление пользователя о необходимости включить дополнительную опцию для работы расширения):
isChromeUserscriptsAvailable(): boolean { try { chrome.userScripts; return true; } catch { return false; } }
Развитие функционала
Если посмотреть на представленный Chrome’ом userScripts API
, выглядит он очень скудно, с дополнительным функционалом тут не разгуляешься. Например, возьмем простейшую задачу — временное включение/отключение пользовательских скриптов.
Официальный API такой возможности не предоставляет, а сохраняемый браузером объект chrome.userScripts.RegisteredUserScript
не содержит какого-либо свойства для хранения дополнительной информации о регистрируемом скрипте. В связи с этим необходимо создавать свою структуру для хранения всех дополнительных опций скриптов, которые нам понадобятся в дальнейшем.
Сохранять будем в chrome.storage.local
— специальное локальное хранилище, доступное расширениям. Для его использования в manifest.json
в раздел permisions
необходимо добавить storage
и unlimitedStorage
(без второго разрешения максимальный размер хранимых данных будет ограничен 10 Мб). Создаваемая структура будет связана по id с объектом RegisteredUserScript
, сохраненным в браузере.
Также здесь мы дублируем и остальные свойства сохраненного пользовательского скрипта. Зачем дублировать? Во-первых, так удобнее будет работать, когда все данные будут извлекаться из одного места. Во-вторых, и основное — для реализации требуемого нам функционала (включения/отключения скрипта) нам потребуется удалять зарегистрированный в браузере объект (т. к. он не имеет состояния «отключен»). При этом сами данные скрипта — код, привязка к URL — должны сохраниться. Они и будут храниться в создаваемой нами дополнительной структуре:
export interface CodeBundle { id: string urlPatterns: string[] urlPatternsCommaSeparated?: string js: string css: string isEnabled: boolean }
Здесь мы добавили дополнительное свойство isEnabled
— именно на основании него мы теперь можем включать/отключать скрипты, т. е. по сути регистрировать и отменять их регистрацию в браузере.
В итоге, реализовав все вышеописанное в коде, мы получаем интерфейс вот такого вида, где мы можем управлять созданными нами скриптами и стилями, привязанными к заданным URL:
Быстрый доступ к функционалу
Чтобы постоянно не лезть в настройки расширения и упростить процесс добавления/редактирования кода, следует добавить возможность добавлять/редактировать код для текущей открытой страницы или сайта в пару кликов. Для этого мы используем специальное всплывающее окно, которое появляется при нажатии на иконку нашего расширения (как включить данную возможность, рассматривалось в предыдущей статье). При открытии данного всплывающего окна нам необходимо получить URL текущей активной вкладки. Для этого добавим простую функцию, использующую Tabs API
:
getCurrentUrlAsync(): Promise<string | undefined> { return chrome.tabs.query({ active: true, currentWindow: true, }).then((tabs: chrome.tabs.Tab[]) => tabs[0].url) }
Чтобы работать с данным API, нам необходимо также добавить в manifest.json
дополнительный запрос на разрешение tabs
в раздел permissions
.
Теперь, получив текущий URL, мы можем перебрать все добавленные нами пользовательские скрипты, сравнить шаблоны URL (в т. ч. исключающие) с текущим URL и отобразить во всплывающем окне те скрипты, которые привязаны к текущей странице. Также здесь реализуем возможность быстрого добавления скрипта к текущей странице или всему сайту. В итоге, кликнув по иконке установленного расширения, получаем такое всплывающее окно:
На этом написание базового функционала закончено. У нас есть все необходимое для создания своих скриптов, а также стилей и привязки их к определенным страницам. Далее можно заняться различными удобными и полезными улучшениями, к которым мы и приступим в следующей части статьи.
ссылка на оригинал статьи https://habr.com/ru/articles/858078/
Добавить комментарий