Создаём расширение для Chrome
Хотите написать расширение для Chrome, но не знаете, с чего начать? Читайте это руководство с нуля до подготовки к публикации скрипта содержимого. Здесь применяются фреймворк CSS TailWind и универсальный упаковщик Parcel.js, решаются проблемы переопределения стиля страницы и перезагрузки расширения. Весь код вы найдёте в конце.
Написать расширение для Chrome непросто. Это не то же самое, что разработка веб-приложения: не хочется перегружать браузер оверхедом JS, ведь расширения работают одновременно с сайтами. Более того, у нас нет инструментов упаковки или отладки из привычных фреймворков.
Когда я решил заняться созданием расширения для Chrome, то обнаружил: блог-постов и статей об этом довольно мало. И информации оказывается даже ещё меньше, если вам захочется использовать новые инструменты, например TailwindCSS.
В этом руководстве мы напишем расширение для Сhrome с помощью Parcel.js для упаковки и просмотра результатов, а также TailwindCSS для оформления. Кроме того, мы отделим стилизацию расширения от веб-сайта, чтобы избежать конфликта CSS.
Есть несколько типов расширений для Chrome, достойных упоминания:
- Скрипты содержимого. Наиболее распространённый тип. Они запускаются в контексте веб-страницы и могут изменять её. Именно такое расширение мы и будем создавать.
- Выпадающее окно (popup). Использует иконку справа от адресной строки, чтобы открыть окно с каким-то HTML.
- UI с опциями. Пользовательский интерфейс для настройки параметров в качестве расширения. Получить доступ к нему можно, щелкнув правой кнопкой мыши по значку расширения и выбрав пункт “Параметры” или перейдя на страницу расширения из списка расширений Chrome: chrome://extensions .
- Расширение DevTools. Добавляет функциональность в инструменты разработчика. Оно может добавлять новые панели интерфейса, взаимодействовать с проверяемой страницей, получать информацию о сетевых запросах и многое другое — документация Google Chrome.
В этом руководстве мы напишем расширение, используя исключительно скрипты содержимого, отображая содержимое на веб-странице и взаимодействуя с DOM.
Упаковываем расширение с Parcel.js V2
Parcel.js — это упаковщик веб-приложений, не требующий конфигурации. Входным может быть файл любого типа, упаковщик прост в использовании и работает с любыми приложениями, включая расширения Chrome. Создаём папку demo-extension . Удостоверьтесь, что у вас установлены yarn или npm . Здесь будем работать с yarn . Установим Parcel как локальную зависимость и создадим папку src
Добавляем манифест
Каждому браузерному расширению необходим файл манифеста. Именно там мы определяем версию и метаданные расширения, а также скрипты, которые в нём работают. Контент, фон, всплывающее окна, разрешения, если они нужны и так далее. Вы найдёте полное описание файла манифеста в документации Chrome: https://developer.chrome.com/extensions/manifest. Давайте двинемся дальше и добавим в src файл manifest.json с такими строками:
Прежде чем углубиться в детали работы расширения Chrome, установим и настроим TailwindCSS.
Подключаем TailwindCSS
TailwindCSS — это CSS-фреймворк, применяющий служебные классы низкого уровня для создания переиспользуемых и настраиваемых компонентов интерфейса. Tailwind устанавливается двумя способами, самый распространённый — установка с помощью NPM. Кроме того, сразу же стоит добавить autoprefixer и postcss-import :
Они нужны, чтобы добавить префиксы поставщиков к стилям и иметь возможность писать конструкции @import "tailwindcss/base" , импортируя файлы Tailwind прямо из node_modules .
Теперь, когда всё установлено, давайте создадим файл postcss.config.js в корневом каталоге. Этот файл — конфигурация для PostCSS. Вставим в него такой код:
Порядок плагинов здесь имеет значение! Это всё, что нужно, чтобы начать использовать TailwindCSS в вашем расширении. Начинаем. Создадим файл style.css в папке src и импортируем в него стили Tailwind:
Очищаем CSS с помощью PurgeCSS
Убедимся, что мы импортируем только те стили, которые используем, включив очистку. Создадим конфигурационный файл Tailwind, запустив такую команду:
Теперь у нас есть tailwind.config.js . Чтобы удалить неиспользуемый CSS, добавляем пути ко всем нашим файлам JS в поле конфигурации purge :
Теперь CSS будут очищены, а неиспользуемые стили удалены при сборке для продакшна.
Включаем горячую перезагрузку
Chrome не перезагружает файлы при внесении изменении, то есть нам нужно нажимать кнопку “Перезагрузить” на странице расширений каждый раз, когда мы хотим посмотреть на результат. К счастью, есть пакет NPM для автоматической перезагрузки:
Чтобы использовать его, создадим файл background.js в папке src и импортируем в этот файл crx-hotreload :
Наконец, добавим указатель на background.js в manifest.json , чтобы он мог работать с нашим расширением: горячая перезагрузка в продакшне отключена по умолчанию:
Достаточно конфигураций. Давайте создадим небольшую форму-скрипт в расширении.
Типы скриптов расширения Chrome
Как уже упоминалось, у расширений Chrome есть несколько типов скриптов:
- Скрипты содержимого — это сценарии, которые выполняются в контексте посещаемой веб-страницы. Вы можете запустить любой код JavaScript, в противном случае доступный на любой обычной веб-странице, включая доступ к DOM и манипулирование им.
- Фоновые скрипты — это место, где вы можете реагировать на события браузера с доступом к API расширения.
Добавляем скрипт содержимого
Создадим файл content-script.js в папке src . И добавим HTML-форму в только что созданный файл:
Оформление стилей браузерного расширения сложнее, чем кажется. Нужно убедиться, что ваши стили не влияют на стили веб-сайта. Применим Shadow DOM для решения этой проблемы.
Теневой DOM — мощная техника инкапсуляции стилей: область применения стиля ограничивается теневым деревом. Таким образом ничего не просачивается на веб-страницу. Кроме того, внешние стили не переопределяют содержимое дерева, хотя переменные CSS всё ещё доступны.
Теневой хост — это любой элемент DOM, к которому мы хотели бы присоединить теневое дерево. Теневой корень — это то, что возвращается из attachShadow , а его содержимое — то, что визуализируется.
Будьте осторожны: единственный способ стилизовать содержимое теневого дерева — встроить стили. Parcel V2 из коробки есть функция, благодаря которой вы можете импортировать содержимое одного пакета и использовать его в качестве скомпилированного текста внутри ваших файлов JavaScript. Именно это мы и сделали со своим пакетом style.css . Parcel заменит его во время упаковки.
Теперь мы можем автоматически встроить CSS в Shadow DOM во время сборки. Конечно, мы должны сообщить браузеру о файле content-script.js , в котором встраивается style.css . Для этого включаем скрипт содержимого в манифест. Обратите внимание на секцию content-scripts ниже первого блока:
Чтобы обслуживать наше расширение, добавим несколько скриптов к package.json :
Наконец, запускаем yarn watch , переходим в chrome://extensions и убеждаемся, что в правом верхнем углу страницы включен режим разработчика. Нажмите на кнопку “Загрузить распакованный” и выберите папку dist в разделе demo-extension .
Если вы получили ошибку Error: Bundles must have unique filePaths , ее можно исправить, просто удалив main из package.json .
Подготовка к публикации
Прежде чем углубляться в эту тему, давайте добавим новый скрипт в конфигурацию NPM, который поможет сжать файлы расширения в соответствии с требованиями Chrome.
Если у вас ещё не установлен zip, пожалуйста, выполните команду:
- На MacOS: brew install zip .
- На Linux: sudo apt install zip .
- На Windows: powershell Compress-Archive -Path .\\dist\\ -Destination .\\chrome-extension.zip .
Теперь всё, что остается, — это отправиться в Chrome Web Store Developer Dashboard — панель управления разработчика, чтобы настроить учетную запись и опубликовать своё расширение.
Заключение
Расширения Chrome, в конечном счёте, не так уж сильно отличаются от веб-приложений. Сегодня мы написали расширение с применением новейших технологий и практик в веб-разработке. Надеюсь, это руководство поможет вам немного ускорить разработку вашего расширения!
Как сделать расширение для браузера или первое расширение для Chrome
Google предоставляет подробную документацию о том, как сделать расширение для браузера Chrome. И помните, что браузерное расширения — это просто HTML, CSS и JavaScript. Вы можете добавлять библиотеки и фреймворки или разрабатывать код «старомодным» способом.
Настройка
Что нужно знать:
- Обратные вызовы;
- Таймауты;
- Инструменты разработчика Chrome.
При создании расширений под Chrome используется большое количество обратных вызовов. Поэтому перед началом я рекомендую освежить в памяти эту тему.
При создании расширения я не учла, что одновременно буду работать с тремя таймерами. И я сэкономила бы много времени, если бы уделила внимание их организации и ознакомлению с документацией.
Обратите внимание, что это руководство описывает только создание собственного пользовательского окна. Но не редактирование существующих окон или вкладок.
Документация
- Руководство Google по расширениям браузера .
- Руководство по началу работы .
- Обзор по расширениям Chrome .
Файл manifest.json предоставляет браузеру информацию о расширении. В том числе о том, где расположены файлы и иконки расширения, а также данные для доступа к API. Вот как выглядел мой файл manifest.json :
Изображение, которое описывает архитектуру расширения.
Файл background.js является обработчиком событий расширения. Он постоянно прослушивает события браузера, которые вы передаете через Chrome Extension API . Google говорит, что эффективный фоновый скрипт загружается только тогда, когда он необходим, и выгружается, когда простаивает.
Popup — это маленькое окно, которое появляется при клике по иконке расширения в меню Chrome. Оно состоит из разметки и скрипта. Вы можете указать браузеру, где его найти, в разделе manifest.json — page_action: .
Страница параметров является именно тем, что ожидается. На ней отображаются настраиваемые параметры, выводимые пользователю, только когда он кликает правой кнопкой мыши в меню Chrome и выбирают пункт «Параметры» для расширения. Эта страница также состоит из разметки и скриптов. Вы можете указать браузеру, где ее найти, в разделе options_page: FILE_NAME_HERE файла manifest.json.
Content scripts — это крипты, которые будут взаимодействовать с любыми окнами или вкладками, открытыми пользователем. Они также будут взаимодействовать с вкладками и окнами, открытыми расширением.
Отладка
Перед началом не забудьте ознакомиться с руководством по отладке !
Как и в любом другом окне Chrome, в расширении вы можете использовать и встроенные инструменты разработчика.
Например, при тестировании расширения я получила ошибку “This request exceeds the MAX_WRITE_OPERATIONS_PER_HOUR quota”. Оказывается, существуют ограничения на синхронизацию хранимой информации .
Еще одна ошибка, которую я продолжала получать: “Alarm delay is less than minimum of 1 minutes. In released .crx, alarm “ ALARM_NAME_HERE” will fire in approximately 1 minutes” . Оказывается, есть минимальные интервалы времени для предупреждений .
Старые добрые console.log действительно могут помочь с обратными вызовами и прослушивателями!
Я добавила кучу «console.log», пытаясь убрать предупреждения.
Функционал Eye Rest
Что за расширение я создала? Оно позволяет отдохнуть глазам в течение двадцати секунд каждые двадцать минут.
Схема работы расширения:
- Если расширение работает,
- Если пользователь не нажал кнопку «Пауза» во всплывающем окне,
- Если счетчик во всплывающем окне достиг отметки 00:00, ТОГДА
- Открывается новое окно с HTML-таймером, И
- Начинается 20-секундный обратный отсчет в HTML-таймере, И
- Сбрасывается счетчик всплывающего окна на 20:00.
- Закрыть это окно и повторить цикл заново.
Звучит довольно просто, но эти таймеры запутали меня. Чтобы понять суть проблемы, посетите репозиторий GitHub для Eye Rest .
Теперь рассмотрим API, который я использовала для создания этого расширения.
Таймеры
Таймеры Chrome — это в основном setTimeout и setInterval. Для получения дополнительной информации, ознакомьтесь с документацией .
Одно интересное замечание о таймерах в Chrome — они работают постоянно. Так как освобождение ресурсов памяти реализовано здесь плохо. В этом я убедилась, когда использовала метод clearAll для удаления таймеров, созданных при предыдущих загрузках или установках расширения. Единственный способ решения данной проблемы – указывать уникальное имя таймера каждый раз, когда загружается расширение. А также сбрасывать другие таймеры без этого уникального имени.
Фоновые скрипты
В своем расширении Eye Rest используется два фоновых скрипта: прослушиватель событий и файл вспомогательных функций .
Я хотела, чтобы фоновый скрипт очищал все существующие таймеры, создавал новый таймер и добавлял оставшееся время до истечения таймера в локальное хранилище сразу после загрузки расширения.
Чтобы сделать функцию clearAndCreateAlarm доступной для фонового скрипта, я добавила первый элемент helpers.js в background> scripts в файле manifest.json.
Я также хотела, чтобы скрипт всплывающего окна делал то же самое, когда пользователь отключал функционал расширения. Чтобы сделать функцию доступной для всплывающего окна, я размещаю вспомогательный скрипт в HTML-файле вплывающего окна.
Другие API
Windows
Для создания окна таймера используется Windows API . Этот процесс инициируется фоновым скриптом . Я передаю timer.html , type, size, position и другие визуальные опции как параметр URL.
Хранилище
Чтобы передавать информацию между фоновым скриптом и скриптом всплывающего окна, используйте хранилище Chrome или локальное . Одним из преимуществ использования локального хранилища является отсутствие лимитов на операции записи.
Обратный отсчет должен изменяться каждую секунду. Он довольно сложный, и для этого нужно много записей. Вот почему я выбрала вариант локального хранилища. Вы можете увидеть, как я получаю и устанавливаю эти переменные в скриптах Background, Helper и Popup. Найдите в коде date, nextAlarmTime и isPaused.
Declarative Content
Declarative Content API позволяет вывести страницу расширения на основе несколько типов сопоставлений без необходимости получать права доступа к хосту Поэтому он нужен нам, чтобы расширение работало в браузере!
Я реализовала это в моем фоновом скрипте . Всплывающее окно расширения должно отображать на каждой просматриваемой странице в браузере.
Расширение
Вот как выглядело мое оригинальное всплывающее окно, прежде чем я добавила стили.
И вот как оно выглядит с новыми стилями.
А вот как выглядят окно таймера и всплывающее окно!
Публикация
Публикация расширения стандартизирована : архивируете файлы, создаете новую или используете существующую учетную запись Google Developer, загружаете файлы, добавляете некоторые данные и платите 5 долларов США. После этого ваше расширение будет доступно в магазине Chrome. Мое расширение теперь доступно для установки .
Заключение
Создание этого расширения Chrome стоило мне боли в плечах и уставших глаз. Но теперь Eye Rest может напоминать мне, что нужно делать перерыв каждые 20 минут.
Пожалуйста, оставляйте свои комментарии по текущей теме статьи. Мы очень благодарим вас за ваши комментарии, отклики, подписки, дизлайки, лайки!
Пишем расширение для google chrome
Написать расширение для google chrome несложно. Но при написании первого раширения могут возникнуть (и возникают) вопросы. Большинство мануалов по написанию первого расширения расчитаны на использования манифеста первой версии, поддержка которого в скором будущем прекратится.
- Как составлять манифест v.2
- Как работать с удаленными ресурсами
- Как работать с cookies
- Как работать с local storage
- Как работать с уведомлениями
Введение
- Должен иметь поле для добавления события (дата, время, событие)
- Должен отображать все задачи на текущий день, отсортированные по времени
- Все прошедшие события должен отображать зачеркнутыми
- Должен иметь поле для ввода времени, за сколько надо показывать уведомление, а так же чекбокс разрешающий и запрещающий показывать уведомления
- За указанное время до события должен отображать уведомление о приближающемся событии
Манифест
Начнем создавать расширение с самого начала, то есть с манифеста. Манифест – это тот самый файл, в котром прописываются все параметры расширения. Название, описание, версия, разрешение на доступ к сайтам, разрешение на использование кук, уведомлений, локального хранилища. В общем, манифест – это мозг расширения. Создаем файл manifest.json. Манифест – единственный файл, который должен иметь заранее предопределенное имя, все остальные файлы можно будет называть как угодно. В этом файле есть три обязательных поля:
manifest.json
- Версия манифеста должна быть целочисленной, то есть должна писаться как 2, а не “2”.
- Версия расширения должна быть строковой, но содержать только числа и точки, то есть “1.0” — хорошо, а 1.0 и “0.9 beta” — плохо.
С обязательными полями – все, перейдем к созданию всплывающего окна расширения. Для того, чтобы по нажатию на пиктограмму, открывалось окно, необходимо добавить в манифест поле “browser_action”
manifest.json
Теперь создадим всплывающее окно. Это обычная html страница, которая может быть любого размера и цвета, никаких фокусов. Назовем файл “popup.html”. Создать этот файл мало – его надо указать в манифесте. Так мы и сделали: «default_popup»: «popup.html».
Добавление расширения в браузер
Теперь пришло время проверить работоспособность нашего расширния. Для этого загрузим расширение в браузер. Открываем в хроме меню расширений. Ставим птицу на “Developer mode”.
После этого появятся три кнопки. Нажимаем “Load unpacked extension. ”. Выбираем папку с файлами расширения. После этого появится наше расширение. Если все правильно, то по нажатию на иконку – повится окно:Подключение скриптов
Теперь можно приступить к интересному. Подключим два javascript файла. Первый – popup.js, второй – jquery. С первым проблем не возникнет, но jquery будем подключать не локальный, а удаленный, взятый по адресу ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js. Проблемы возникнут от того, что по умолчанию расширение не имеет доступа к сторонним ресурсам. Чтобы получить доступ, надо его указать в манифесте. Доступ к чему-либо указывается в поле “permissions”. Так же, для удаленных скриптов и css надо указывать доступные удаленные ресурсы.
manifest.json
Теперь подключим эти скрипты в popup.html
Storage
При помощи storage в хроме можно хранить пользовательские данные. И именно в storage наше расширение и будет хранить грядущие события. На то есть две причины. Во-первых, данные, хранищиеся в storage можно синхронизировать, если залогиниться в браузере. А во-вторых, данные можно хранить не только в виде строки, как в cookies, а в любом виде, то есть можно хранить и массивы и объекты. Чтобы это заработало, откроем доступ к storage в манифесте.
manifest.json
Теперь переделаем всплавающее окно. Во всплывающем окне будет поле с сегодняшней датой, три инпута для даты, времени и описания нового события, кнопка для добавления нового события, а так же список всех событий на сегодня.
И сразу же добавим отображение даты в блоке #today_date.
Выглядеть должно так:
Итак, при нажатии на кнопку “+” у нас должно добавляться событие. Вначале файла объявим глобальную переменную storage – объект для работы с storage, а так же глобальный массив tasks для хранения событий.
Функция валидации проверяет, что дата записана в формате d.m.yyyy, а время в формате hh:mm, а так же, что в описании события не меньше трех символов.
С добавлением разобрались, переходим к получению событий на сегодня. Для этого надо получить все события из базы, выбираем из всех только сегодняшние события и сортируем их по времени по возрастанию.
Функция getTodayTasks() возвращает из общего списка только события с сегодняшней датой.
Функция sortTasks() сортирует события по возрастанию времени.
Уведомления
Пришло время настроить отображение уведомлений на экране. Добавим во всплывающее окно специальный чекбокс. Если этот чекбокс будет отмечен – уведомлениея будут показываться, если не будет отмечен – не будут. Так же добавим текстовый инпут. Цифра в этом инпуте будет показывать, за какое время до событя будет показываться уведомление. То есть если у нас событие назначено на 19:00, в этом текстовом инпуте будет 5, значит в 18:55 появится уведомление. Добавим в popup.html код с этими инпутами
Теперь давайте разберемся с тем, как это будет работать. При нажатии на чекбокс, будет проверяться его атрибут checked, значение атрибута будет записываться в cookie “show_notifications”. Перейдем к текстовому инпуту. По изменению его значения, новое значение будет валидироваться, если оно целочисленное и не больше 120, записываем новое значение в cookie “when_to_notify”.
Но для того, чтобы у нас это заработало, надо открыть доступ к cookies. Для этого заходим в manifest.json и добавляем в “permissions”
manifest.json
Теперь можно приступать к скрипту. Заходим в popup.js. Для начала установим первоначальные значения в инпутах. По-умолчанию чекбокс не отмечен, то есть уведомления не показываются, а время равно 0. При клике на чекбокс, будет меняться значение cookie “show_notifications”. При изменении значения в тектовом поле, будет меняться значение cookie “when_to_notify”.
Рассмотрим подробнее функции. Начнем с функций работы с cookies. В данном случае были взяты готовые функции с w3schools.com.
Разберемся с функцией setCheckbox(). Она получает cookie “show_notifications” и проверяет, если полученное значение равно “true”(текстовое, да), то параметр checked у чекбокса true, иначе false.
Теперь рассмотрим setWhenToNotify(). Она принимает новое значение таймера. Если это значение – целочисленное и не больше 120 минут, то в cookie “when_to_notify” устанавливается новое значение. Если переменная не прошла эту валидацию, то в инпут возвращается предыдущее значение из cookies “when_to_notify”.
Перейдем к самим уведомлениям. Для этого откроем доступ к уведомлениям и подключим фоновый background.js. Нужно подключить именно фоновый файл, так как если уведомления вызывать из popup.js, то уведомления будут появляться только если открыто всплывающее окно.
manifest.json
Последняя строчка дает доступ к удаленному файлу. Дело в том, что картинка, которая отображается в уведомлении обязательно должна быть доступна расширению удаленно. В данном случае файл локальный, но доступ открывать все равно надо. Теперь возьмемся за background.js. Объявим переменную storage и пустой массив tasks. Далее раз в минуту скрипт будет получать список сегодняшних событий и получать из них список задач, которые должны произойти через указанное время. После этого для каждой такой задачи будет показано уведомление.
background.js
Функции getTodayTasks() и getCookie() взяты из popup.js. Так что начнем разбор с функции getNextTask(). Функция сравнивает текущее время и время события, если оно равно тому значению, которое хранится в cookie “when_to_notify”, то в массив next дописывается строка из времени события и его описания. После проверки всех событий возвращет массив next.
background.js
Функция show() показывает уведомление с заданным текстом.
background.js
Результатом работы этого скрипта будет такое вот уведомление:
Послесловие
Как и обещано, в конце статьи у нас есть готовое расширение-органайзер для Google Chrome.
Теперь добавим расширение в Chrome Web Store. Загружать надо расширение, запакованное в .zip-архив. Для начала заходим в Web Store. Для этого заходим в хроме на вкладку “Приложения” и нажимаем кнопку “Web Strore”
Теперь заходим в меню. Для этого нажимаем шестиренку и открываем “Developer dashboard”
Нажимаем большую кнопку “Add new item”. После этого надо будет выбрать zip-архив с расширением нажать “upload”
Далее надо заполнить небольшую форму с описанием расширения. Теперь есть выбор либо сохранить расширение в черновил либо опубликовать. Просто так опубликовать его не получится так как регистрация в Web Store стоит 5 $. Плату за регистрацию надо платить один раз для аккаунта, а не для каждого расширения. После оплаты появляется позможность опубликовать расширение, но и тут надо быть готовым к тому, что его будут валидировать несколько дней.Браузерное расширение своими руками
Спорим, вы прямо сейчас используете какие-то браузерные расширения. Некоторые из них очень полезны и популярны, например, блокировщики рекламы, менеджеры паролей или средства для просмотра PDF. Функциональность таких расширений (аддонов) не ограничена — вы можете сделать с их помощью что угодно, но сегодня мы будем разбираться, как делать непосредственно их. Другими словами, мы собираемся создать собственное расширение.
Что будем делать?
На сайте Reddit есть аккаунты, которые на добровольной основе занимаются транскрибацией (переводом в текст) изображений, аудио- и видео-контента. Это здорово улучшает доступность для людей с ограниченными возможностями.
Однако эти комментарии находятся в общей ленте и сразу не видны. Мы собираемся решить эту проблему.
Мы создадим расширение Transcribers of Reddit (TOR), которое будет искать и перемещать комментарии с расшифровкой на верх общего списка, а также добавлять aria-атрибуты для скрин-ридеров. Мы даже пойдем чуть дальше и добавим возможность изменить цвет фона и установить рамку для этих комментариев, чтобы улучшить визуальный контраст.
Основная идея этого проекта — показать, как создавать браузерное расширение, а не реализовать полнофункциональный аддон, так что вы всегда можете что-то доработать или переделать на свой вкус.
Мы начнем с расширения для Chromium-браузеров (Google Chrome, Microsoft Edge, Brave и т. д.) В следующих статьях, возможно, мы его портируем на Firefox и Safari, который с недавних пор тоже поддерживает веб-расширения и в MacOS и в iOS.
Рабочая директория
Прежде чем начинать, нужно создать произвести все необходимые подготовительные действия.
Создадим новую папку для проекта — можно назвать ее transcribers-of-reddit . А внутри нее папку src для исходного кода.
Манифест
Точка входа — это файл, который содержит всю важную информацию о расширении (название, описание и т. д.), а также определяет необходимые разрешения и выполняемые скрипты.
Нашей точкой входа будет файл manifest.json в папке src . Добавим в него для начала три свойства:
- Поле manifest_version — это версия используемой спецификации, она определяет, какие API доступны приложению. На данный момент самая свежая версия — mv3.
- Поле name — название расширения, которое будет отображаться, например, в Chrome Web Store.
- Поле version — это версия самого расширения. Она указывается в виде строки, которая должна содержать только цифры и точки. Можно указывать мажорные, минорные версии, а также патчи (например, 1.3.5).
Больше информации
В файл manifest.json можно добавить очень много различных параметров, описывающих наше приложение. Например, его описание (поле description), которое объясняет, что приложение умеет делать.
Добавим также иконки приложения в разных разрешениях и урл домашней страницы проекта.
- Поле description не должно быть длиннее 132 символов. , указанные в поле icons , используются во множестве мест. Предпочтительно загружать файлы png-формата. Иконки для нашего проекта вы можете загрузить прямо из его репозитория.
- В поле homepage_url можно указать любую страницу, которая содержит информацию о приложении. Она будет отображаться в графе Open extention website на странице управления расширением.
Разрешения
Большой плюс расширений — это возможность взаимодействовать напрямую с браузером. Но для этого нужно явно указать, какие API мы собираемся использовать, чтобы получить разрешения. Это нужно сделать в секции permissions файла manifest.json:
Прежде всего, мы хотим, чтобы наше расширение сохраняло пользовательские настройки, поэтому мы должны разрешить браузеру сохранять их — разрешение storage . Например, если пользователь хочет видеть у комментариев красную рамку, мы сохраним этот выбор и в следующий раз применим тот же стиль.
Кроме того, мы должны отслеживать пользовательскую навигацию по текущей странице — разрешение webNavigation . Дело в том, что Reddit — это одностраничное приложение (SPA), которое не вызывает событий перезагрузки страниц. Нам нужно поймать взаимодействие и загрузку комментариев на страницу.
В зависимости от указанных разрешений браузер может выводить сообщение пользователю для их подтверждения. Вот список таких разрешений.
Управление переводами
Браузерным расширениями доступен встроенный API интернационализации (i18n). Он позволяет управлять переводами для множества языков (полный список). Чтобы воспользоваться этой возможностью, нужно создать сами переводы, а также указать дополнительную информацию в manifest.json .
Определяем, какой язык будет использоваться по умолчанию (английский):
Если вдруг в браузере будет включен язык, который не поддерживается нашим расширением, оно просто будет использовать базовый язык.
Сами переводы определяются в папке _locales . Для каждого создается отдельная папка, внутри которой находится файл messages.json:
Перевод каждой фразы оформляется по специальной схеме:
- Определяется ключ перевода (translation key), или идентификатор фразы, по которому мы будем к ней обращаться.
- Для каждого ключа указываются свойства: message , description и placeholders .
- message — это сама переводимая фраза.
- description — опциональное описание перевода, если необходимо. Предназначено, скорее, для самого переводчика, в расширении не используется.
- placeholders — динамический контент внутри фразы, например, слова, которые могут изменяться.
Вот пример перевода одной фразы:
Схема использования плейсхолдеров может показаться довольно сложной.
Для начала мы должны определить, какие части сообщения (фразы) являются динамическими. Их может и не быть. Если такие фрагменты нашлись, нужно обернуть их символами $ .
Каждый такой фрагмент нужно отдельно описать в поле placeholders. Это немного неинтуитивно, но Chrome хочет знать, какое значение следует вставить вместо плейсхолдера. Так как мы хотим использовать динамические (неопределенные) значения, нужно использовать специальную конструкцию $1 , которая является ссылкой на вставленный контент.
Свойство example необязательно, оно может использоваться как подсказка для переводчиков, но в самом расширении не применяется.
Давайте добавим переводы в наш проект. Это оригинальный английский файл, а вы можете добавить столько языков, сколько захотите. Например, для русского нужно создать папку ru , а для немецкого de .
Возможно, вы хотите знать, почему мы не указали API интернационализации в списке разрешений.
Chrome довольно непоследователен в этом отношении — вам не нужно указывать все необходимые разрешения, например, chrome.i18n. Некоторые другие разрешения требуется указывать, но пользователь, который устанавливает ваше расширение о них ничего не знает. Некоторые разрешения вообще являются «гибридными», вы можете использовать некоторые их функции без объявления, но другие обязательно нужно указать в манифесте. Чтобы разобраться во всем этом, загляните лучше в документацию.
Интернационализация манифеста
Первое, что видит пользователь в Chrome Web Store, — это обзорная страница расширения. Мы должны убедиться, что на ней все переведено на актуальный язык, для этого придется внести несколько изменений в манифест — перевести название и описание:
Это специальный синтаксис, который позволяет ссылаться на фразы в messages.json по их идентификатору. Например, строка __MSG_name__ использует фразу с идентификатором name .
Интернационализация HTML-страниц
Чтобы перевести тексты на страницах самого расширения, потребуется добавить немного JavaScript:
Этот код также использует идентификатор фразы из файла messages.json.
Чтобы передать динамические плейсхолдеры, используйте второй параметр метода:
Конечно, перевести таким образом все тексты на странице не самая приятная задача, но мы можем схитрить. Вынесем тексты в data-атрибуты элементов и переведем их все сразу с помощью скрипта.
Создайте новую папку js внутри директории src и добавьте туда файл util.js , в котором будут находиться вспомогательные функции:
Вот так выглядит сам код:
При выполнении этот скрипт найдет все элементы с атрибутом data-intl , найдет соответствующую указанному идентификатору переведенную фразу и вставит полученный текст в innerHTML элемента.
Добавление выпадающего меню и страницы настроек
Прежде чем перейти уже наконец к настоящей разработке, мы должны создать две страницы:
- Страницу настроек, которая будет содержать настройки пользователя.
- Выпадающее меню, которое будет открываться при нажатии на иконку расширения в панели браузера. Его можно использовать для различных сценариев, например, отображать быстрые настройки или собранную статистику.
Вот файлы, которые нам понадобятся:
Файлы .css содержат самый обычный CSS, не больше, не меньше. Вы все наверняка знаете, как это работает При желании вы, разумеется, можете использовать различные препроцессоры, главное, не забудьте скомпилировать код.
Обратите внимание, выпадающее меню — это НЕ отдельная вкладка браузера. Его размер зависит от контента. Если вы хотите задать определенные фиксированные размеры, просто установите свойства width и height для элемента html .
Выпадающее меню
Напишем HTML-код выпадающего меню, а также подключим туда CSS- и JS-файлы.
Заголовок первого уровня будет содержать название расширения и его текущую версию, а кнопка будет открывать страницу настроек. Обратите внимание, для кнопки установлен атрибут data-intl , то есть ее текст будет вставлен автоматически нашим вспомогательным скриптом.
Текст заголовка мы установим отдельно в файле popup.js . Там же будет обработчик клика по кнопке:
Чтобы получить доступ к данным манифеста, это скрипт использует runtime API браузера Chrome. Его метод getManifest возвращает JSON-объект (этот метод не требует специальных разрешений).
Для начала устанавливаем текст заголовка. После этого определяем слушатель для события клика по кнопке. При клике мы хотим открывать страницу настроек, для этого есть специальный метод openOptionsPage() , также не требующий разрешений.
Стили меню в файле popup.css :
Мы полностью закончили меню, но наше расширение пока что ничего о нем не знает. Нужно зарегистировать его в файле manifest.json :
Страница настроек
Страница с пользовательскими настройками создается точно так же.
Начинаем с разметки — файл options.html :
Сейчас здесь нет никаких обещанных настроек, только подготовленный для них блок. Вставлять их мы будем с помощью JavaScript (в файле options.js ).
Сначала определим дефолтные значения и получим ссылку на DOM-элемент контейнера:
Теперь нужно загрузить сохраненные ранее настройки. Для этого нам понадобится storage API (который мы уже зарегистрировали ранее в манифесте).
Можно сохранять данные локально ( chrome.storage.local ) или синхронизировать их между разными устройствами пользователя ( chrome.storage.sync ). Для простоты будем использовать локальное хранилище.
Для получения данных предназначен метод get, который принимает два аргумента: ключ, с которым связано нужное значение, и коллбэк для обработки полученных данных.
У нас ключ — это строка ‘settings’ , но можно также передавать массив строк, если требуется загрузить несколько различных настроек.
Полученные из хранилища данные будут переданы функции-коллбэку:
Рендер опций вынесен в отдельную функцию createOption() :
Здесь мы создаем отдельный блок для каждой настройки и помещаем внутрь него чекбокс, который может быть включен или выключен. Добавляем слушатель события change для этого чекбокса и при изменении вызываем метод updateSetting , который должен обновить сохраненное значение в хранилище. Для этого предназначен метод хранилища set , который принимает два аргумента: обновленное значение всего хранилища и коллбэк (опциональный параметр, который мы не будем использовать).
Так как мы храним настройки в виде объекта, следует использовать spread-оператор, чтобы обновлять только нужный параметр, сохраняя актуальные значения прочих.
Стили страницы в файле options.css:
И конечно же, мы должны зарегистрировать новую страницу в манифесте:
При необходимости вы можете оформить страницу опций в виде модального диалога. Для этого нужно установить свойство open_in_tab: false .
Установка расширения для разработки
Мы сделали всю подготовительную работу, и теперь неплохо было бы проверить, работает ли наше расширение.
Перейдите на страницу chrome://extensions и включите режим разработчика (в правом верхнем углу).
Появятся три кнопки, нам нужна первая — Загрузить распакованное расширение. Кликните на нее и в появившемся окне выберите папку src, в которой хранятся все файлы нашего проекта.
После этого наше расширение появится в списке в статусе Установлено. Также его иконка появится в верхней панели браузера рядом с адресной строкой (его можно найти, кликнув на иконку паззла ?) . Если вы нажмете на нее, то появится выпадающее меню с кнопкой:
На вашем девайсе оно может выглядеть немного по-другому, если в предпочтениях не включена темная тема оформления.
Попробуйте изменить значение переключателей и перезагрузить страницу — вы увидите, что настройки сохраняются в локальном хранилище браузера.
Основной скрипт
Итак, мы сделали выпадающее меню и настроили взаимодействие со страницей настроек, но само расширение все еще не делает ничего полезного. Давайте исправим это и добавим основной скрипт.
Создайте новый файл comment.js внутри директории js .
Теперь его нужно добавить в манифест в секцию content_scripts :
Это массив, каждый элемент которого состоит из двух частей:
- matches — содержит массив урлов, на которых браузер должен запускать наше расширение. Мы указываем всего один урл домен верхнего уровня — www.reddit.com , а изменяющиеся части обозначаются звездочками.
- js — содержит массив скриптов, которые нужно запустить на подходящих урлах.
Помните, что скрипты расширения не могут взаимодействовать на скриптами сайта, не могут использовать их переменные или функции.
Давайте приступим уже к программированию.
Прежде всего добавим несколько констант в файл comment.js , в которых сохраним селекторы DOM-элементов и регулярные выражения для дальнейшей работы.
Объект CommentUtils содержит набор тестовых функций для определения, содержит ли пост «ToR-комментарии» и существует ли вообще блок комментариев на странице.
Затем мы проверяем, находится ли пользователь на странице «поста» с комментариями. Для этого проверяем текущий урл и обновляем значение переменной directPage . Эта проверка сработает в том случае, если пользователь перешел на страницу с внешних ресурсов или собственноручно вбил адрес в адресную строку браузера.
Но в большинстве случаев переходы осуществляются внутри SPA. Чтобы отловить их, мы можем добавить слушатель сообщений, используя runtime API .
Теперь нам нужно написать реализацию функций moveComment и waitForComment .
moveComment — находит в общем списке «ToR-комментарии» с транскрипцией медиа-ресурсов и перемещает их в начало списка. Также при наличии соответствующих настроек она изменяет цвет фона и рамки этих комментариев (это проверяется с помощью уже знакомого нам метода get ).
Функция applyWaiAria добавляет комментариям aria -атрибуты для улучшения доступности для скринридеров. Мы также используем вспомогательную функцию uuidv4 для генерации уникальных идентификаторов.
Функция waitForComment ожидает, пока комментарии загрузятся, и после этого вызывает коллбэк, который был ей передан. Для отслеживания используется API браузера MutationObserver.
Добавляем сервис-воркер
Помните, что мы добавили слушатель сообщений в скрипте нашего расширения? Но пока никаких сообщений нам не приходит, кто-то должен их отправлять. Для этого мы должны зарегистрировать сервис-воркер.
Создайте новый файл sw.js внутри директории src (важно, чтобы он находился в корне проекта).
Сразу же зарегистрируем его в манифесте:
Начнем, как обычно, с констант для сообщений и типов страниц:
Мы будем следить за историей навигации браузера ( onHistoryStateUpdated ). Нужное нам событие будет вызываться каждый раз, когда SPA обращается к History API при обновлении контента страницы без перезагрузки.
При наступлении события мы запрашиваем активную вкладку браузера и получаем ее tabId , а затем отправляем сообщение основному скрипту расширения.
Готово!
Перейдите в менеджер расширений (chrome://extensions) и перезапустите наше расширение, чтобы все изменения применились.
Теперь откройте на Reddit какой-нибудь пост, у которого есть комментарии с транскрибацией (например, вот этот). И вы сразу же увидите нужный комментарий, потому что он отображается в самом верху списка, да еще и с рамкой и красным фоном (если вы установили соответствующие настройки).
Расширение Transcribers of Reddit находит комментарии с транскрибацией изображения, поднимает их на верх списка и выделяет красной рамкой и фоном
Заключение
Создавать браузерные расширения совсем несложно, как вы только что убедились. В них нет ничего особенного — те же самые HTML, CSS и JavaScript, которые вы пишете каждый день, плюс совсем немного магии в manifest.json .
Если у вас возникнут какие-то проблемы, скорее всего вы сможете найти ответ в отличной документации Chrome API.