Как написать расширение для chrome
Перейти к содержимому

Как написать расширение для chrome

  • автор:

Делаем своё расширение для браузера за 10 минут

В конце 2020 года мы делали проект со снежинками — писали специальный скрипт, который запускал падающий снег на сайтах. Если бы мы хотели сделать такой снег на любом своём сайте, это не составило бы труда: добавляешь скрипт в код страницы, и готово.

А вот на чужих сайтах была проблема. Скрипт нужно было вставлять через консоль. А если на сайте была настроена политика безопасности, которая запрещает запуск внешних скриптов, то магия не срабатывала.

Сегодня мы это исправим — сделаем расширение для браузера, которое может запускать любой скрипт на любой странице. Мы охватим принцип, на основе которого вы сможете сделать собственные расширения, в том числе намного более сложные.

�� Что такое расширение

Расширение для Chrome — это небольшая программа, которая выполняется внутри браузера и помогает расширить возможности сайтов. Сила расширения в том, что оно может выполняться прямо из меню браузера и не зависит от политик безопасности.

Примеры того, что может сделать расширение:

  • Сохранить ваши пароли и другие личные данные,
  • Выдрать из страницы скрытые данные (например, ссылку на скачивание музыки).
  • Менять что-то на странице, например, отключать рекламу, переводить текст, удалять ненужное.
  • Собирать статистику вашей работы в интернете.
  • Подключаться к другим сервисам (почте, чатам, файлообменникам) и взаимодействовать с ними из браузера.

В этой статье

Мы сделаем самое простое расширение для браузера Chrome, которое позволит запускать скрипт со снежинками на любом сайте, независимо от настроенной политики безопасности. Для этого воспользуемся официальным руководством Google по созданию расширений.

Манифест

В каждом расширении для браузера должен быть манифест — документ, в котором написано:

  • как называется расширение;
  • к чему расширение может получить доступ;
  • какие скрипты будут работать в фоне;
  • как должна выглядеть иконка расширения;
  • что показать или что выполнить, когда пользователь нажмёт на иконку расширения.

Манифест задаёт общие правила для всего расширения, поэтому манифест — единственный обязательный компонент. Можно обойтись без иконок и скриптов, но манифест обязательно должен быть.Каждый манифест хранится в файле manifest.json — создадим пустой файл с таким именем и напишем внутри такое:

«name»: «Запускаем снежинки на любом сайте»,
«description»: «Проект журнала Код»,
«version»: «1.0»,
«manifest_version»: 3
>

Первые две строчки — это название и подробное описание расширения. Третья отвечает за номер версии расширения, а последняя говорит браузеру, какая версия манифеста используется в описании. На момент выхода статьи в феврале 2021 года используется третья версия.

Сохраняем файл и всё, расширение готово. Оно ничего не умеет, ничего не делает, зато мы уже можем добавить его в браузер. Для этого запускаем Хром и в адресной строке пишем:

Мы попадаем на страницу, которая нам покажет все установленные расширения:

Делаем своё расширение для браузера за 10 минут

Чтобы добавить своё расширение, в правом верхнем углу включаем режим разработчика, а затем нажимаем «Загрузить распакованное расширение»:

Делаем своё расширение для браузера за 10 минут

Теперь выбираем папку, в которой лежит наш манифест:

Делаем своё расширение для браузера за 10 минут

Отлично, мы только что добавили в браузер новое расширение:

Делаем своё расширение для браузера за 10 минут

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

Чтобы было проще работать и тестировать расширение, закрепим его на панели браузера:

Делаем своё расширение для браузера за 10 минут

Иконки

У расширения есть две иконки, которыми мы можем управлять:

  1. Картинка в карточке расширения на странице настроек.
  2. Иконка на панели браузера.

Чтобы не рисовать всё с нуля, скачаем папку с иконками из того же руководства Google и положим её в ту же папку, что и манифест:

Теперь добавим иконки в манифест. За картинку в карточке отвечает блок icon, а за иконку на панели — блок action. Разные размеры картинки нужны для того, чтобы на разных мониторах с любой плотностью пикселей иконки выглядели хорошо:

 < "name": "Запускаем снежинки на любом сайте", "description": "Проект журнала Код", "version": "1.0", "manifest_version": 3, "action": < "default_icon": < "16": "/images/get_started16.png", "32": "/images/get_started32.png", "48": "/images/get_started48.png", "128": "/images/get_started128.png" >>, "icons": < "16": "/images/get_started16.png", "32": "/images/get_started32.png", "48": "/images/get_started48.png", "128": "/images/get_started128.png" >>

Сохраняем манифест, обновляем расширение на странице настроек и смотрим результат:

Добавляем иконки в манифест

Настраиваем разрешения

Разрешения — это то, что браузер позволяет делать расширению со страницами и с их содержимым. Для запуска снежинок нам нужно сделать две вещи:

  1. Понять, какая вкладка сейчас активная, чтобы запустить снежинки именно на ней.
  2. Запустить наш скрипт со снежинками.

Чтобы получить доступ к активной вкладке и к запуску скриптов, добавим в манифест такую строку:

«permissions»: [«activeTab», «scripting»],

Показываем меню

Если мы сейчас нажмём на иконку расширения на панели браузера, то ничего не призойдёт, потому что мы ничего не запрограммировали. Исправим это — сделаем так, чтобы при нажатии расширение показывало кнопку запуска. Когда расширение будет уметь больше, вместо одной кнопки можно будет показать целое меню.

Чтобы сделать всплывающее меню, добавим в манифест в раздел action такую строку:

Она означает, что при нажатии на иконку мы увидим рядом с ней мини-страничку, на которой что-то будет.Создадим в той же папке расширения файл popup.html и добавим в него такой код:

     /* задаём размеры кнопки и размер текста на кнопке */ button   

Чтобы браузер не ругался, что у нас нет файла popup.js , создадим пустой файл с таким названием и положим его в ту же папку:

Показываем меню расширения

Сохраняем манифест, обновляем его на странице настроек и видим, что у нашего расширения появилось меню с кнопкой:

Показываем меню расширения

Запускаем снежинки

Вся магия будет происходить в файле popup.js — откроем его и добавим такой код:

// получаем доступ к кнопке let snow = document.getElementById("snow"); // когда кнопка нажата — находим активную вкладку и запускаем нужную функцию snow.addEventListener("click", async () => < // получаем доступ к активной вкладке let [tab] = await chrome.tabs.query(< active: true, currentWindow: true >); // выполняем скрипт chrome.scripting.executeScript(< // скрипт будет выполняться во вкладке, которую нашли на предыдущем этапе target: < tabId: tab.id >, // вызываем функцию, в которой лежит запуск снежинок function: snowFall, >); >); // запускаем снег function snowFall()

Последнее, что нам осталось сделать, — положить в функцию snowFall() полный код скрипта из проекта со снежинками и сохранить файл.

Проверка

В прошлый раз мы не смогли запустить скрипт на любой странице Яндекса — мешала политика безопасности. Теперь всё работает:

Проверяем расширение

Скачать упакованное расширение. Перед установкой его нужно распаковать в любую папку.

Как создать расширение для Chrome

У вас в браузере наверняка есть хотя бы одно расширение. Если вам интересно узнать, как они делаются, читайте наш материал по написанию самого простого плагина для Google Chrome.

Расширения и плагины — полезные дополнения к уже существующим функциям на сайте и в браузере. С их помощью можно записывать аудио и видео с экрана, включать поиск ошибок, а также многое другое.

В этой статье мы рассмотрим создание самого простого расширения — запускатора избранных сайтов. Хотя приложение и будет примитивным, оно всё-таки раскроет процесс создания и загрузки расширения для google Chrome.

Желательно знать HTML, CSS и JS (если придётся расширить набор функций) на самом базовом уровне, чтобы понимать материал лучше, но в любом случае мы будем объяснять код.

В каждом расширении для Chrome должен быть файл manifest.json . Он служит только для описания функций приложения, общего описания, номера версии и разрешений. Более подробно вы сможете ознакомиться с этим файлом в блоге команды разработчиков Chrome.

Давайте же внесём свой вклад в развитие web

Здесь всё очень просто:

< "manifest_version": 2, "name": "Tproger Launcher", "description": "Запускатор представительств Tproger", "version": "1.0.0", "icons": , "browser_action": < "default_icon": "icon.png", "default_popup": "popup.html" >, "permissions": ["activeTab"] > 

После того как мы описали наше расширение в файле manifest.json , можно благополучно переходить к следующему этапу, а именно к разметке.

Для начала давайте напишем базовый HTML-код:

Выше мы написали каркас для плагина, сейчас он полностью пуст и нужно указать название, ссылки на иконки и шрифт. Это можно сделать с помощью тега link , обратите внимание, он не закрывается:

    Tproger Media Quick Launcher      

Не забывайте указывать кодировку, иначе не отобразятся кириллические буквы.

Перейдём ко второму блоку кода, а именно к тегу body и его содержимому.

Так как наше расширение — модальное окно, давайте соответствующим образом назовём контейнеры. Сначала добавим контейнер шапки расширения, в которой укажем ссылку к иконке, напишем название и добавим номер версии.

Переходим к следующему контейнеру. Он содержит описание функций расширений.

 
Быстрый доступ к контентным площадкам Типичного Программиста

Далее следует контейнер modal-icons , внутри которого ещё 5 контейнеров.

Для каждой иконки мы выделили отдельный контейнер с классом flex , чтобы знать, к каким элементам будем применять Flexbox.

Кроме того, мы указали названия иконок для каждого ресурса. Более детально со всеми доступными элементами можно ознакомиться на сайте Bootstrap.

Стили

Чтобы расширение выглядело красивее и было удобнее, чем сейчас, нужно добавить стили на CSS.

 /* Модальная структура документа */ /*общие настройки для всего документа*/ html, body < font-family: 'Open Sans', sans-serif; font-size: 14px; margin: 0; min-height: 180px; padding: 0; width: 380px; >/*задаём настройки для заголовков первого уровня*/ h1 < font-family: 'Menlo', monospace; font-size: 22px; font-weight: 400; margin: 0; color: #2f5876; >a:link, a:visited < color: #000000; outline: 0; text-decoration: none; >/*задаём ширину картинки*/ img < width: 30px; /*ширина изображений*/ >.modal-header < align-items: center; /*выравнивание элементов по центру*/ border-bottom: 0.5px solid #dadada; /*свойства нижней разделительной линии*/ height: 50px; >.modal-content < padding: 0 22px; /*отступы сверху и снизу, сверху и слева*/ >.modal-icons < border-top: 0.5px solid #dadada; /*свойства верхней разделительной линии*/ height: 50px; width: 100%; >.logo < padding: 16px; /*отступы со всех сторон*/ >.logo-icon < vertical-align: text-bottom; /*выравнивание по нижней части текста*/ margin-right: 12px; /*задётся отступ элементов от изображения*/ >.version

Основные настройки документа заданы, давайте перейдём к следующему фрагменту кода, в котором как раз и будет применён Flexbox, о котором шла речь в начале статьи.

.flex-container < display: flex; /*отображает контейнер в виде блочного элемента*/ justify-content: space-between; /*равномерное выравнивание элементов*/ padding: 10px 22px; >/*задаём настройки для контейнеров с иконками*/ .flex < opacity: 1; /*параметр непрозрачности иконок*/ width: 120px; >.flex:hover < opacity: 0.4; /*уровень непрозрачности при наведении курсора на элемент*/ >.flex .fa

Мы постарались как можно подробнее объяснить в комментариях относительно сложные моменты. А сейчас нам нужно лишь загрузить наше расширение в браузер Chrome и оно будет работать, а если пройдёт модерацию, то появится в магазине расширений (плагинов).

Теперь давайте добавим файл с расширением .js, если вдруг потребуется расширить функции дополнения для браузера.

  Tproger Media Quick Launcher    

Проверка кода и публикация

Прежде чем опубликовать, проверьте ещё раз весь код. Если вы делали всё так, как мы, то у должно было получиться следующее:

     Запускатор Tproger      /* Модальная структура документа */ /*общие настройки для всего документа*/ html, body < font-family: 'Open Sans', sans-serif; font-size: 14px; margin: 0; min-height: 180px; padding: 0; width: 380px; >/*задаём настройки для заголовков первого уровня*/ h1 < font-family: 'Menlo', monospace; font-size: 22px; font-weight: 400; margin: 0; color: #2f5876; >a:link, a:visited < color: #000000; outline: 0; text-decoration: none; >/*задаём ширину картинки*/ img < width: 30px; /*ширина изображений*/ >.modal-header < align-items: center; /*выравнивание элементов по центру*/ border-bottom: 0.5px solid #dadada; /*свойства нижней разделительной линии*/ height: 50px; >.modal-content < padding: 0 22px; /*отступы сверху и снизу, сверху и слева*/ >.modal-icons < border-top: 0.5px solid #dadada; /*свойства верхней разделительной линии*/ height: 50px; width: 100%; >.logo < padding: 16px; /*отступы со всех сторон*/ >.logo-icon < vertical-align: text-bottom; /*выравнивание по нижней части текста*/ margin-right: 12px; /*задётся отступ элементов от изображения*/ >.version < color: #444; font-size: 18px; >.flex-container < display: flex; /*отображает контейнер в виде блочного элемента*/ justify-content: space-between; /*равномерное выравнивание элементов*/ padding: 10px 22px; >/*задаём настройки для контейнеров с иконками*/ .flex < opacity: 1; /*параметр непрозрачности иконок*/ width: 120px; >.flex:hover < opacity: 0.4; /*уровень непрозрачности при наведении курсора на элемент*/ >.flex .fa -->  
Быстрый доступ к контентным площадкам Типичного Программиста

После проверки можно приступать к публикации расширения. Для этого у вас должны быть следующие файлы и папки:

Как создать расширение для Chrome 1

И далее следуем инструкциям на скриншотах ниже.

Для загрузки расширения в магазин нам нужно зайти в меню, навести мышку на «дополнительные настройки», а затем выбрать «расширения» или ввести в адресной строке chrome://extensions/ .

Как создать расширение для Chrome 2

Далее нажимаем на «загрузить распакованное расширение» и выбираем папку с файлами.

Как создать расширение для Chrome 3

После того, как вы выбрали папку с файлами, она загрузится в браузер и превратится в расширение, которое будет доступно за пределами правой части адресной строки.

Как создать расширение для Chrome 4

Надеемся, что всё работает правильно и вы понимаете структуру расширений для Chrome.

Создаём расширение для Chrome

Хотите написать расширение для Chrome, но не знаете, с чего начать? Читайте это руководство с нуля до подготовки к публикации скрипта содержимого. Здесь применяются фреймворк CSS TailWind и универсальный упаковщик Parcel.js, решаются проблемы переопределения стиля страницы и перезагрузки расширения. Весь код вы найдёте в конце.

Написать расширение для Chrome непросто. Это не то же самое, что разработка веб-приложения: не хочется перегружать браузер оверхедом JS, ведь расширения работают одновременно с сайтами. Более того, у нас нет инструментов упаковки или отладки из привычных фреймворков.

Когда я решил заняться созданием расширения для Chrome, то обнаружил: блог-постов и статей об этом довольно мало. И информации оказывается даже ещё меньше, если вам захочется использовать новые инструменты, например TailwindCSS.

В этом руководстве мы напишем расширение для Сhrome с помощью Parcel.js для упаковки и просмотра результатов, а также TailwindCSS для оформления. Кроме того, мы отделим стилизацию расширения от веб-сайта, чтобы избежать конфликта CSS.

Есть несколько типов расширений для Chrome, достойных упоминания:

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

Упаковываем расширение с Parcel.js V2

Parcel.js — это упаковщик веб-приложений, не требующий конфигурации. Входным может быть файл любого типа, упаковщик прост в использовании и работает с любыми приложениями, включая расширения Chrome. Создаём папку demo-extension . Удостоверьтесь, что у вас установлены yarn или npm . Здесь будем работать с yarn . Установим Parcel как локальную зависимость и создадим папку src

mkdir demo-extension && cd demo-extension && 
льyarn init -y
yarn add -D parcel@nextmkdir src

Добавляем манифест

Каждому браузерному расширению необходим файл манифеста. Именно там мы определяем версию и метаданные расширения, а также скрипты, которые в нём работают. Контент, фон, всплывающее окна, разрешения, если они нужны и так далее. Вы найдёте полное описание файла манифеста в документации Chrome: https://developer.chrome.com/extensions/manifest. Давайте двинемся дальше и добавим в src файл manifest.json с такими строками:

 "name": "Demo extension", 
"description": "An extension built with Parcel and TailwindCSS.",
"version": "1.0",
"manifest_version": 2,
>

Прежде чем углубиться в детали работы расширения Chrome, установим и настроим TailwindCSS.

Подключаем TailwindCSS

TailwindCSS — это CSS-фреймворк, применяющий служебные классы низкого уровня для создания переиспользуемых и настраиваемых компонентов интерфейса. Tailwind устанавливается двумя способами, самый распространённый — установка с помощью NPM. Кроме того, сразу же стоит добавить autoprefixer и postcss-import :

yarn add tailwindcssyarn add -D autoprefixer postcss-import

Они нужны, чтобы добавить префиксы поставщиков к стилям и иметь возможность писать конструкции @import «tailwindcss/base» , импортируя файлы Tailwind прямо из node_modules .

Теперь, когда всё установлено, давайте создадим файл postcss.config.js в корневом каталоге. Этот файл — конфигурация для PostCSS. Вставим в него такой код:

module.exports = plugins: [ 
require("postcss-import"),
require("tailwindcss"),
require("autoprefixer"),
],
>;

Порядок плагинов здесь имеет значение! Это всё, что нужно, чтобы начать использовать TailwindCSS в вашем расширении. Начинаем. Создадим файл style.css в папке src и импортируем в него стили Tailwind:

@import "tailwindcss/base"; 
@import "tailwindcss/utilities";

Очищаем CSS с помощью PurgeCSS

Убедимся, что мы импортируем только те стили, которые используем, включив очистку. Создадим конфигурационный файл Tailwind, запустив такую команду:

$ npx tailwindcss init

Теперь у нас есть tailwind.config.js . Чтобы удалить неиспользуемый CSS, добавляем пути ко всем нашим файлам JS в поле конфигурации purge :

module.exports = purge: [ 
'./src/**/*.js', ��
],
theme: <>,
variants: <>,
plugins: [],
>

Теперь CSS будут очищены, а неиспользуемые стили удалены при сборке для продакшна.

Включаем горячую перезагрузку

Chrome не перезагружает файлы при внесении изменении, то есть нам нужно нажимать кнопку “Перезагрузить” на странице расширений каждый раз, когда мы хотим посмотреть на результат. К счастью, есть пакет NPM для автоматической перезагрузки:

$ yarn add crx-hotreload

Чтобы использовать его, создадим файл background.js в папке src и импортируем в этот файл crx-hotreload :

import "crx-hotreload";

Наконец, добавим указатель на background.js в manifest.json , чтобы он мог работать с нашим расширением: горячая перезагрузка в продакшне отключена по умолчанию:

 "name": "Demo extension", 
"description": "An extension built with Parcel and TailwindCSS.",
"version": "1.0",
"manifest_version": 2,
"background": < ��
"scripts": ["background.js"]
>,
>

Достаточно конфигураций. Давайте создадим небольшую форму-скрипт в расширении.

Типы скриптов расширения Chrome

Как уже упоминалось, у расширений Chrome есть несколько типов скриптов:

Добавляем скрипт содержимого

Создадим файл content-script.js в папке src . И добавим HTML-форму в только что созданный файл:

import cssText from "bundle-text. /dist/style.css";const html =
`
$


New event!


pl-1 block mb-1 text-black text-xl"
>
Event name


name="event-name"
type="text"
placeholder="web.dev LIVE"
w-full focus:outline-none"
/>



pl-1 block mb-1 text-black text-xl"
>
Date


name="event-date"
type="date"
w-full focus:outline-none"
/>



for="event-time-input"
pl-1 block mb-1 text-xl"
>
Time


type="time"
value="17:30"
mr-4 lowercase duration-400 w-auto bg-white text-xl border-2 rounded-lg px-4 py-4 focus:outline-none focus:shadow-outline"
/>

Casablanca
Africa






`const shadowHost = document.createElement("div");
document.body.insertAdjacentElement("beforebegin", shadowHost);
const shadowRoot = shadowHost.attachShadow(< mode: 'open' >);
shadowRoot.innerHTML = html

Оформление стилей браузерного расширения сложнее, чем кажется. Нужно убедиться, что ваши стили не влияют на стили веб-сайта. Применим Shadow DOM для решения этой проблемы.

Теневой DOM — мощная техника инкапсуляции стилей: область применения стиля ограничивается теневым деревом. Таким образом ничего не просачивается на веб-страницу. Кроме того, внешние стили не переопределяют содержимое дерева, хотя переменные CSS всё ещё доступны.

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

Будьте осторожны: единственный способ стилизовать содержимое теневого дерева — встроить стили. Parcel V2 из коробки есть функция, благодаря которой вы можете импортировать содержимое одного пакета и использовать его в качестве скомпилированного текста внутри ваших файлов JavaScript. Именно это мы и сделали со своим пакетом style.css . Parcel заменит его во время упаковки.

Теперь мы можем автоматически встроить CSS в Shadow DOM во время сборки. Конечно, мы должны сообщить браузеру о файле content-script.js , в котором встраивается style.css . Для этого включаем скрипт содержимого в манифест. Обратите внимание на секцию content-scripts ниже первого блока:

 "name": "Demo extension", 
"description": "An extension built with Parcel and TailwindCSS.",
"version": "1.0",
"manifest_version": 2,
"background": "scripts": ["background.js"]
>,
"content_scripts": [
"matches": [""],
"js": ["content-script.js"],
>
]
>

Чтобы обслуживать наше расширение, добавим несколько скриптов к package.json :

"scripts": "prebuild": "rm -rf dist .cache .parcel-cache", 
"build:tailwind": "tailwindcss build src/style.css -c ./tailwind.config.js -o dist/style.css",
"watch": "NODE_ENV=development yarn build:tailwind && cp src/manifest.json dist/ && parcel watch --no-hmr src/",
"build": "NODE_ENV=production yarn build:tailwind && cp src/manifest.json dist/ && parcel build src/",
>

Наконец, запускаем yarn watch , переходим в chrome://extensions и убеждаемся, что в правом верхнем углу страницы включен режим разработчика. Нажмите на кнопку “Загрузить распакованный” и выберите папку dist в разделе demo-extension .

Если вы получили ошибку Error: Bundles must have unique filePaths , ее можно исправить, просто удалив main из package.json .

Подготовка к публикации

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

"scripts": "prebuild": "rm -rf dist .cache", 
"build:tailwind": "tailwindcss build src/style.css -c ./tailwind.config.js -o dist/style.css",
"watch": "NODE_ENV=development yarn build:tailwind && cp src/manifest.json dist/ && parcel watch --no-hmr src/",
"build": "NODE_ENV=production yarn build:tailwind && cp src/manifest.json dist/ && parcel build src/",
"zip": "zip -r chrome-extension.zip ./dist" ��
>

Если у вас ещё не установлен zip, пожалуйста, выполните команду:

Теперь всё, что остается, — это отправиться в Chrome Web Store Developer Dashboard — панель управления разработчика, чтобы настроить учетную запись и опубликовать своё расширение.

Заключение

Расширения Chrome, в конечном счёте, не так уж сильно отличаются от веб-приложений. Сегодня мы написали расширение с применением новейших технологий и практик в веб-разработке. Надеюсь, это руководство поможет вам немного ускорить разработку вашего расширения!

Создание расширения браузера Google Chrome для извлечения всех изображений web-страницы. Часть 2

Представляю вашему вниманию вторую часть статьи о создании расширения web-браузера Chrome, которое позволяет извлечь все изображения с web-страницы.

Данный материал, как и материал первой статьи предназначен для разработчиков, которые умеют писать web-приложения на современном JavaScript, но не пробовали создавать web-расширения для Google Chrome и хотели бы начать это делать. Эта и предыдущая статьи не являются исчерпывающим руководством или справочником о всех возможностях, которые доступны при создании расширений браузера, однако могут помочь быстро начать. Затем расширять свои знания можно с помощью официальной документации и справочника по API.

Напомню, что по итогам первой части мы создали расширение web-браузера, которое умеет извлекать URL-пути всех изображений текущей web-страницы и копировать их список в буфер обмена.

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

В итоге при правильном выполнении всех действий вы получите web-расширение браузера, которое будет выглядеть и работать так как показано на этом видео:

В итоге я опишу процесс публикации готового расширения в магазин Chrome Web Store.

Создание вкладки со списком изображений

Итак, в первой части список извлеченных изображений копировался в буфер обмена. Далее мы сделаем чтобы вместо буфера обмена, этот список отображался на новой странице в отдельной вкладке браузера. На этой странице мы создадим интерфейс, позволяющий пользователю выбрать все или некоторые из этих картинок для дальнейшей выгрузки в zip-архив.

Напомню, что список изображений формировался и копировался в буфер обмена при нажатии на кнопку «GRAB NOW» во всплывающем окне popup.html , в функции onResult , определенной в файле popup.js так, как показано ниже.

/** * Выполняется после того как вызовы grabImages * выполнены во всех фреймах удаленной web-страницы. * Функция объединяет результаты в строку и копирует * список путей к изображениям в буфер обмена * * @param <[]InjectionResult>frames Массив результатов * функции grabImages */ function onResult(frames) < // Если результатов нет if (!frames || !frames.length) < alert("Could not retrieve images from specified page"); return; >// Объединить списки URL из каждого фрейма в один массив const imageUrls = frames.map(frame=>frame.result) .reduce((r1,r2)=>r1.concat(r2)); // Скопировать в буфер обмена полученный массив // объединив его в строку, используя возврат каретки // как разделитель window.navigator.clipboard .writeText(imageUrls.join("\n")) .then(()=>< // закрыть окно расширения после // завершения window.close(); >); >

Заменим все что связано с копированием в буфер обмена (все строки начиная с window.navigator.clipboard ) на вызов функции openImagesPage(urls) , которая будет открывать отдельную вкладку и показывать в ней все изображения из списка urls.

/** * Выполняется после того как вызовы grabImages * выполнены во всех фреймах удаленной web-страницы. * Функция объединяет результаты в строку и копирует * список путей к изображениям в буфер обмена * * @param <[]InjectionResult>frames Массив результатов * функции grabImages */ function onResult(frames) < // Если результатов нет if (!frames || !frames.length) < alert("Could not retrieve images from specified page"); return; >// Объединить списки URL из каждого фрейма в один массив const imageUrls = frames.map(frame=>frame.result) .reduce((r1,r2)=>r1.concat(r2)); // Скопировать в буфер обмена полученный массив // объединив его в строку, используя возврат каретки // как разделитель openImagesPage(imageUrls) > /** * Открывает новую вкладку браузера со списком изображений * @param urls - Массив URL-ов изображений для построения страницы */ function openImagesPage(urls) < // TODO: // * Открыть новую закладку браузера с HTML-страницей интерфейса // * Передать массив `urls` на эту страницу >

Создадим теперь эту страницу и напишем функцию openImagesPage , которая будет ее открывать.

Расширение Chrome может содержать любое количество страниц. На данном этапе есть только popup.html . Это главная страница расширения. В ней список изображений извлекается и формируется. Теперь создадим вторую страницу, в которую этот список будет передаваться. Назовем ее page.html . Создайте эту страницу со следующим содержимым и сохраните ее в корневой папке расширения, то есть там же где и popup.html:

   Image Grabber   
  Select all
Image Grabber

Данная страница состоит из двух блоков. Для них назначены CSS-классы header и container . Блок заголовка содержит чекбокс для выбора всех изображений из списка и кнопку «Download», для их выгрузки. Блок container в данный момент пуст. Он будет содержать список изображений, которые будут переданы функцией openImagesPage из страницы popup.html .

После заполнения массивом URL-ов изображений и после применения CSS-стилей, страница page.html будет выглядеть так:

Интерфейс выбора изображений

Как видно, у каждой картинки будет чекбокс, позволяющий ее либо добавить, либо убрать из выборки.

Открытие новой вкладки браузера из расширения

Открыть новую вкладку браузера можно с помощью функции chrome.tabs.create из Chrome Tabs API, которая определена следующим образом:

chrome.tabs.create(createProperties, callback)

Теперь перепишем функцию openImagesPage , чтобы открыть новую вкладку:

function openImagesPage(urls) < // TODO: // * Открыть новую закладку браузера с HTML-страницей интерфейса chrome.tabs.create(,(tab) => < alert(tab.id) // * Передать массив `urls` на эту страницу >); >

Если теперь вы запустите расширение и нажмете кнопку GRAB NOW, то должна открыться новая вкладка браузера с содержимым созданной ранее страницы page.html :

Новая страница page.html

При вызове chrome.tabs.create была указана callback-функция, которая должна показывать оповещение с идентификатором созданной вкладки — alert(tab.id) . В дальнейшем, эта функция будет отправлять список путей к картинкам в эту новую вкладку. Однако это не сработает и вы не увидите никакого оповещения. Это интересная ошибка, которую стоит подробнее рассмотреть.

Сначала вы нажимаете кнопку GRAB NOW на странице popup.html . При этом открывается страница page.html в новой вкладке браузера и эта вкладка активируется. При активации вкладки фокус перемещается на страницу page.html и уходит с всплывающего окна popup.html . В момент потери фокуса, всплывающее окно уничтожается и исчезает. Это происходит до того, как срабатывает функция callback, которая должна выполнить alert(tab.id) . Как это исправить? Например, можно открыть новую вкладку, но не активировать ее в момент открытия, а активировать ее из кода позже, в самой функции callback, после того как она выполнится. Для этого нужно указать дополнительный параметр создания новой вкладки active:false :

chrome.tabs.create(, . )

Далее, callback должна передать данные в открытую вкладку и в конце активировать ее. В терминах Chrome API, активировать вкладку это значит «изменить статус вкладки на активный«. Для изменения параметров вкладки, включая ее статус, используется функция chrome.tabs.update , определяемая следующим образом:

chrome.tabs.update(tabId, updateOptions, callback);

Соответственно, для того чтобы активировать новую вкладку, нужно сделать такой вызов:

chrome.tabs.update(tab.id, );

Свойство active устанавливается для только что созданной вкладки tab . Здесь мы не указывали callback-функцию за ненадобностью, так как все действия с новой вкладкой будут осуществлены до ее активации.

function openImagesPage(urls) < // TODO: // Открыть новую закладку браузера с HTML-страницей интерфейса chrome.tabs.create(,(tab) => < alert(tab.id) // Передать массив `urls` на эту страницу // сделать вкладку активной chrome.tabs.update(tab.id, ); >); >

Теперь все должно работать правильно при нажатии кнопки GRAB NOW — сначала создается вкладка со страницей page.html , затем появляется оповещение с идентификатором вкладки, затем вкладка с этим идентификатором активируется и только после этого, всплывающее окно с кнопкой GRAB NOW исчезает.

Теперь пришло время убрать этот временный alert и вместо него написать код, который будет передавать список путей к изображениям на страницу page.html .

Передача списка путей в новую вкладку

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

Для отправки сообщений используется функция

chrome.tabs.sendMessage(tabId, message, responseFn)

Добавим отправку сообщения со списком URL в функцию openImagesPage:

function openImagesPage(urls) < // Открыть новую закладку браузера с HTML-страницей интерфейса chrome.tabs.create(,(tab) => < chrome.tabs.sendMessage(tab.id, urls, (resp) =>< // сделать вкладку активной chrome.tabs.update(tab.id, ); >) >); >

Теперь эта функция сначала создает новую вкладку со страницей page.html , затем отправляет в нее массив urls как сообщение и после того как принимающая сторона ответит, делает эту вкладку активной.

Вкладке нужно некоторое время, чтобы загрузить страницу и скрипт, который будет принимать сообщение. Поэтому рекомендую сделать небольшую паузу после открытия вкладки и до отправки сообщения, то есть обернуть в setTimeout . Я добавил паузу в 500 миллисекунд:

function openImagesPage(urls) < // Открыть новую вкладку браузера с HTML-страницей интерфейса chrome.tabs.create(,(tab) => < setTimeout(()=> < // отправить список URL в новую вкладку chrome.tabs.sendMessage(tab.id, urls, (resp) =>< // сделать вкладку активной chrome.tabs.update(tab.id, ); >) >,500) >); >

Прием списка путей и отображение их на странице page.html

Теперь перейдем к приему сообщения страницей page.html . Сообщение может быть принято только скриптом, поэтому создайте файл page.js и добавьте этот скрипт в page.html :

   Image Grabber   
  Select all
Image Grabber

За прием сообщений отвечает событие chrome.runtime.onMessage . Этот объект содержит метод addEventListener(func) , который позволяет установить функцию func для реакции на новые сообщения. В ней запустится процесс генерации HTML-разметки, показывающей список изображений на этой странице в блоке div с классом container . Добавьте следующий код в файл page.js :

chrome.runtime.onMessage .addListener(function(message,sender,sendResponse) < addImagesToContainer(message); sendResponse("OK"); >); /** * Функция, которая будет генерировать HTML-разметку * списка изображений * @param <> urls - Массив путей к изображениям */ function addImagesToContainer(urls) < // TODO Создать HTML-разметку в элементе с // классом container для показа // списка изображений и выбора изображений, // для выгрузки в ZIP-архив document.write(JSON.stringify(urls)); > 

В этом коде мы добавили функцию реакции на сообщение. Эта функция содержит три параметра:

В итоге мы приняли сообщение со списком URL, передали этот список в функцию addImagesToContainer и ответили отправителю фразой «OK». В ответ на это, отправляющая функция openImagesPage должна активировать данную вкладку.

На данном этапе, функция addImagesToContainer это просто прототип. Она выводит список изображений как JSON-строку:

Список изображений как JSON-строка

Настоящий интерфейс предстоит создать в следующем разделе.

Создание функционала выгрузки изображений в ZIP-архив

Итак, используя кнопку «GRAB NOW» мы извлекли список путей ко всем картинкам текущей web-страницы и передали этот список на страницу page.html . Это все что нам было нужно от страницы popup.html и скрипта popup.js . С этого момента мы будем работать только со страницей page.html и в частности с ее скриптом page.js . Напомним, что эта страница содержит блок заголовка с кнопкой «Download» и флажком «Select all», а также блок container . Следующая задача это показать картинки из полученного списка в этом контейнере, а затем, запрограммировать флажок «Select all» и кнопку «Download», чтобы выделить их все и скачать в ZIP-архиве.

Создание HTML-разметки списка изображений

Измените функцию addImagesToContainer(urls) следующим образом:

/** * Функция, для генерации HTML-разметки * списка изображений * @param <> urls - Массив путей к изображениям */ function addImagesToContainer(urls) < if (!urls || !urls.length) < return; >const container = document.querySelector(".container"); urls.forEach(url => addImageNode(container, url)) > /** * Функция создает элемент DIV для каждого изображения * и добавляет его в родительский DIV. * Создаваемый блок содержит само изображение и флажок * чтобы его выбрать * @param <*>container - родительский DIV * @param <*>url - URL изображения */ function addImageNode(container, url)

Функция addImagesToContainer сначала проверяет что список путей к изображениям не пуст и затем вызывает для каждого элемента функцию addImageNode . Эта функция генерирует HTML-элемент для каждой картинки и добавляет этот элемент в DIV с классом container . Для каждого элемента списка генерируется следующая разметка:

 
/> />

Это обычный блок с классом «imageDiv». Этот класс будет использоваться в CSS. Этот блок содержит картинку с переданным url и флажок. Флажок также имеет атрибут url, который в дальнейшем будет использоваться для добавления картинки в ZIP-архив, если ее флажок включен.

Если нажать GRAB NOW сейчас, то вы увидите примерно такой интерфейс:

Список изображений

Под заголовком мы видим список изображений. С каждым изображением связан флажок. На данный момент и сами картинки и их флажки расположены хаотично. В дальнейшем мы применим CSS-стили чтобы это исправить.

Теперь сделаем так, чтобы этот интерфейс заработал, а именно, активируем флажок «Select All» и кнопку «Download».

Реализация функции для выбора всех картинок

Флажок выбора всех элементов имеет Воспользуемся этим id для написания обработчика «change» этого флажка:

document.getElementById("selectAll") .addEventListener("change", (event) => < const items = document.querySelectorAll(".container input"); for (let item of items) < item.checked = event.target.checked; >; >);

Данная функция будет срабатывать при включении/выключении этого флажка. Ее задача установить статус этого флажка всем остальным флажкам страницы, то есть, всем флажкам картинок. Статус флажка selectAll находится в свойстве события event.target.checked . Если запустить интерфейс после добавления этого кода, то все флажки будут либо включаться, либо отключаться вместе с флажком Select All.

Реализация функции Download

При нажатии на кнопку Download должны происходить следующие действия:

Кнопка «Download» имеет идентификатор downloadBtn . Соответственно, приведенные выше действия должны выполняться в функции обработчике события нажатия этой кнопки. Определим структуру этой функции:

document.getElementById("downloadBtn") .addEventListener("click", async() => < try < const urls = getSelectedUrls(); const archive = await createArchive(urls); downloadArchive(archive); >catch (err) < alert(err.message) >>) function getSelectedUrls() < // TODO: Получить список всех включенных флажков, // извлечь из каждого из них значение атрибута "url" // и вернуть массив этих значений >async function createArchive(urls) < // TODO: Создать пустой ZIP-архив, затем используя // массив "urls", скачать каждое изображение, поместить // его в виде файла в этот ZIP-архив и в конце // вернуть этот ZIP-архив >function downloadArchive(archive) < // TODO: Создать невидимую ссылку (тег ), // которая будет указывать на переданный ZIP-архив "archive" // и автоматически нажать на эту ссылку. Таким образом // браузер откроет окно сохранения загруженного файла или // автоматически загрузит его (зависит от типа ОС) >

В этом коде функция обработчик нажатия кнопки Download делает в точности 3 действия из списка выше. Для каждого действия будет использоваться отдельная функция. Все содержимое обработчика помещено в блок try/catch чтобы единообразно обработать любые исключения, возникающие в каждой из функций. Также, функция createArchive , которая скачивает картинки и создает из них ZIP-архив асинхронна и возвращает Promise. Здесь для работы с promise я использую метод async/await, вместо then(), чтобы сделать код проще и чище. Соответственно и функция createArchive объявлена как async, и сам обработчик нажатия кнопки Download, так как в нем используется ключевое слово await .

Реализуем эти функции одну за другой.

Получение списка выделенных изображений

Функция getSelectedUrl() запрашивает список всех флажков внутри контейнера, извлекает из каждого и них атрибут url и возвращает массив значений этих аттрибутов:

function getSelectedUrls() < const urls = Array.from(document.querySelectorAll(".container input")) .filter(item=>item.checked) .map(item=>item.getAttribute("url")); if (!urls || !urls.length) < throw new Error("Please, select at least one image"); >return urls; >

Здесь запрашиваются все теги «input» внутри элемента с классом «container». В данном случае теги «input» это только флажки. Других полей ввода в списке изображений нет.

Если пользователь нажмет на кнопку не включив ни одного флажка, то будет выброшено исключение «Please select at least one image». Это исключение будет обработано вышестоящей функцией.

Загрузка изображений из списка

Функция createArchive получает список «urls» для скачивания. Картинки будем скачивать с помощью fetch. Это универсальная функция для выполнения HTTP-запросов любого типа и у нее может быть множество параметров. Однако в данном случае, достаточно отправить запрос GET с указанием пути:

const response = await fetch(url);

Функция fetch выполняет запрос по указанному адресу и возвращает Promise с результатом выполнения этого запроса. Конструкция await разрешает этот Promise и возвращает результат запроса как объект Response, либо выбрасывает исключение, если запрос выполнить не удалось.

Объект response содержит сырой HTTP-ответ и набор методов и свойств, упрощающих работу с ним. В частности, в нем есть методы для получения тела ответа в различных форматах. Например, метод .text() позволяет получить ответ в виде текста, а .json() в виде объекта JSON. Однако мы скачиваем картинку и необходимо получить двоичные данные этой картинки. Для этого служит метод .blob() . Напишем часть функции, которая скачивает картинки и получает их двоичные данные:

async function createArchive(urls) < for (let index in urls) < const url = urls[index]; try < const response = await fetch(url); const blob = await response.blob(); console.log(blob); >catch (err) < console.error(err); >>; >

Для каждого URL из списка, функция выполняет fetch запрос и затем извлекает объект BLOB из ответа на этот запрос. При возникновении исключения, она просто выводит сообщение об ошибке в консоль и пропускает эту картинку. (Можно обрабатывать исключения связанные с каждой картинкой как-то по другому, но я здесь не стал усложнять).

Двоичные данные каждой картинки это объект BLOB — Binary Large Object. Кроме непосредственно данных, он содержит некоторые полезные свойства для работы с ними, такие, как:

У объектов BLOB есть и другие свойства, описанные в документации.

Далее нужно помещать загруженные файлы в архив, но есть одна проблема. У нас нет имен файлов. BLOB содержит только двоичные данные и их описание, но не имя файла. С одной стороны, можно использовать часть URL как имя файла, но далеко не всегда URL картинки это имя файла. Эти пути могут содержать много разного мусора, поэтому я решил не использовать их для этого. Вместо этого, в качестве имени файла будет использоваться просто порядковый номер, а в качестве расширения, последняя часть MIME-типа после «/».

Я решил написать отдельную функцию, которая проверит полученные двоичные данные и присвоит для них имя файла, если эти данные действительно являются не пустой картинкой. Вспомогательная функция называется checkAndGetFileName(index,blob) :

function checkAndGetFileName(index, blob) < let name = parseInt(index)+1; const [type, extension] = blob.type.split("/"); if (type != "image" || blob.size return name+"."+extension; >

Эта функция принимает порядковый номер изображения и его двоичные данные. Дальше она проверяет размер этих данных и их MIME-тип. Если размер положителен и MIME-тип содержит «image» в качестве первого компонента, то функция возвращает имя, состоящее из порядкового номера и второго компонента MIME-типа. Если проверка не пройдена, то выбрасывается исключение и эта картинка игнорируется.

Теперь все готово к созданию ZIP-архива.

Создание ZIP-архива

Самим реализовывать этот процесс довольно трудоемко, поэтому мы воспользуемся сторонней библиотекой JSZip: https://stuk.github.io/jszip/. Нужно скачать архив с этой библиотекой и распаковать. Можно использовать прямую ссылку на архив: https://github.com/Stuk/jszip/zipball/main.

Это довольно объемный архив, но нужен только один файл из него: dist/jszip.min.js . Нужно импортировать его в расширение. Для этого создайте какую-нибудь папку в корне расширения, например lib , скопируйте этот файл в эту папку и подключите в страницу page.html перед page.js :

   Image Grabber   
  Select all
Image Grabber

При загрузке страницы с этой библиотекой, создается глобальный класс JSZip, который может использоваться для создания ZIP-архивов и добавления файлов в них. Процесс работы с ZIP-архивом с помощью этой библиотеки можно описать следующим псевдокодом:

const zip = new JSZip(); zip.file(filename1, blob1); zip.file(filename2, blob2); . . . zip.file(filenameN, blobN); const blob = await zip.generateAsync( < type:'blob', compression:'DEFLATE', compressionOptions:< level: 9 >>);

Сначала создается объект zip , затем в него добавляются файлы. Каждый файл определяется его именем и двоичным объектом BLOB. Затем, конструируется двоичный объект самого архива с помощью функции generateAsync . Эта функция принимает объект options с различными параметрами создания ZIP-архива. Среди них:

Здесь описаны только параметры, которые мы используем. Список всех возможностей библиотеки JSZip можно узнать в документации.

Теперь все готово для завершения функции createArchive:

async function createArchive(urls) < const zip = new JSZip(); for (let index in urls) < try < const url = urls[index]; const response = await fetch(url); const blob = await response.blob(); zip.file(checkAndGetFileName(index, blob),blob); >catch (err) < console.error(err); >>; return zip.generateAsync( < type:'blob', compression: "DEFLATE", compressionOptions: < level: 9 >>); > function checkAndGetFileName(index, blob) < let name = parseInt(index)+1; [type, extension] = blob.type.split("/"); if (type != "image" || blob.size return name+"."+extension; >

Сначала создается пустой ZIP-архив. Далее, в цикле, каждая картинка загружается и добавляется в архив. При этом с помощью функции checkAndGetFileName для нее генерируется имя. При возникновении ошибок на этапе загрузки или на этапе проверки функцией checkAndGetFileName часть картинок может быть отброшена. В итоге функция генерирует двоичные данные ZIP-архива и возвращает их.

Осталось последнее — дать пользователю скачать этот архив.

Создание ссылки для скачивания ZIP-архива

Чтобы дать пользователю возможность скачать файл, нужно создать ссылку на этот файл. В нашем случае нужно создать ссылку не на файл, а на BLOB. Для этого есть функция:

window.URL.createObjectURL(blob)

Все это реализуется в функции downloadArchive:

function downloadArchive(archive)

Вот и все с автоматизацией. Теперь стоит немного почистить код.

Итоговый код page.js

Чтобы освежить в памяти все что было сделано привожу полный листинг файла page.js с комментариями:

chrome.runtime.onMessage .addListener(function(message,sender,sendResponse) < addImagesToContainer(message); sendResponse("OK"); >); /** * Функция, генерирует HTML-разметку * списка изображений * @param <> urls - Массив путей к изображениям */ function addImagesToContainer(urls) < if (!urls || !urls.length) < return; >const container = document.querySelector(".container"); urls.forEach(url => addImageNode(container, url)) > /** * Функция создает элемент DIV для каждого изображения * и добавляет его в родительский DIV. * Создаваемый блок содержит само изображение и флажок * чтобы его выбрать * @param <*>container - родительский DIV * @param <*>url - URL изображения */ function addImageNode(container, url) < const div = document.createElement("div"); div.className = "imageDiv"; const img = document.createElement("img"); img.src = url; div.appendChild(img); const checkbox = document.createElement("input"); checkbox.type = "checkbox"; checkbox.setAttribute("url",url); div.appendChild(checkbox); container.appendChild(div) >/** * Обработчик события "onChange" флажка Select All * Включает/выключает все флажки картинок */ document.getElementById("selectAll") .addEventListener("change", (event) => < const items = document.querySelectorAll(".container input"); for (let item of items) < item.checked = event.target.checked; >; >); /** * Обработчик события "onClick" кнопки Download. * Сжимает все выбранные картинки в ZIP-архив * и скачивает его. */ document.getElementById("downloadBtn") .addEventListener("click", async() => < try < const urls = getSelectedUrls(); const archive = await createArchive(urls); downloadArchive(archive); >catch (err) < alert(err.message) >>) /** * Функция возвращает список URL всех выбранных картинок * @returns Array Массив путей к картинкам */ function getSelectedUrls() < const urls = Array.from(document.querySelectorAll(".container input")) .filter(item=>item.checked) .map(item=>item.getAttribute("url")); if (!urls || !urls.length) < throw new Error("Please, select at least one image"); >return urls; > /** * Функция загружает картинки из массива "urls" * и сжимает их в ZIP-архив * @param <> urls - массив путей к картинкам * @returns BLOB-объект ZIP-архива */ async function createArchive(urls) < const zip = new JSZip(); for (let index in urls) < try < const url = urls[index]; const response = await fetch(url); const blob = await response.blob(); zip.file(checkAndGetFileName(index, blob),blob); >catch (err) < console.error(err); >>; return await zip.generateAsync( < type:'blob', compression: 'DEFLATE', compressionOptions: < level:9 >>); > /** * Проверяет переданный объект blob, чтобы он был не пустой * картинкой и генерирует для него имя файла * @param <> index - Порядковый номер картинки в массиве * @param <*>blob - BLOB-объект с данными картинки * @returns string Имя файла с расширением */ function checkAndGetFileName(index, blob) < let name = parseInt(index)+1; const [type, extension] = blob.type.split("/"); if (type != "image" || blob.size return name+"."+extension.split("+").shift(); > /** * Функция генерирует ссылку на ZIP-архив * и автоматически ее нажимает что приводит * к скачиванию архива браузером пользователя * @param <> archive - BLOB архива для скачивания */ function downloadArchive(archive)

Теперь если нажать кнопку GRAB NOW, затем выбрать либо все, либо определенные картинки и нажать кнопку Download, то zip-архив с именем images.zip будет загружен браузером. На данном этапе интерфейс выглядит примерно так:

Этот интерфейс работает, но пользоваться совсем не удобно. Давайте стилизуем его.

Добавление CSS

Процесс стилизации страницы расширения не отличается от стилизации обычной HTML-страницы. Создайте файл page.css и добавьте ссылку на него на странице page.html .

   Image Grabber   
  Select all
Image Grabber

Как стилизовать интерфейс это дело вкуса, я например стилизовал так, как показано ниже. Добавьте следующий код в page.css :

body < margin:0px; padding:0px; background-color: #ffffff; >.header < display:flex; flex-wrap: wrap; flex-direction: row; justify-content: space-between; align-items: center; width:100%; position: fixed; padding:10px; background: linear-gradient( #5bc4bc, #01a9e1); z-index:100; box-shadow: 0px 5px 5px #00222266; >.header > span < font-weight: bold; color: black; text-transform: uppercase; color: #ffffff; text-shadow: 3px 3px 3px #000000ff; font-size: 24px; >.header > div < display: flex; flex-direction: row; align-items: center; margin-right: 10px; >.header > div > span < font-weight: bold; color: #ffffff; font-size:16px; text-shadow: 3px 3px 3px #00000088; >.header input < width:20px; height:20px; >.header > button < color:white; background:linear-gradient(#01a9e1, #5bc4bc); border-width:0px; border-radius:5px; padding:10px; font-weight: bold; cursor:pointer; box-shadow: 2px 2px #00000066; margin-right: 20px; font-size:16px; text-shadow: 2px 2px 2px#00000088; >.header > button:hover < background:linear-gradient( #5bc4bc,#01a9e1); box-shadow: 2px 2px #00000066; >.container < display: flex; flex-wrap: wrap; flex-direction: row; justify-content: center; align-items: flex-start; padding-top: 70px; >.imageDiv < display:flex; flex-direction: row; align-items: center; justify-content: center; position:relative; width:150px; height:150px; padding:10px; margin:10px; border-radius: 5px; background: linear-gradient(#01a9e1, #5bc4bc); box-shadow: 5px 5px 5px #00222266; >.imageDiv:hover < background: linear-gradient(#5bc4bc,#01a9e1); box-shadow: 10px 10px 10px #00222266; >.imageDiv img < max-width:100%; max-height:100%; >.imageDiv input

Данный стиль построен на базе Flexbox с использованием свойства flex-wrap . Благодаря этому интерфейс перестраивает себя так, чтобы корректно выглядеть на экранах различного размера.

Законченный интерфейс на широком экранеЗаконченный интерфейс на узких экранах

ВСЕ! На этом мы заканчиваем создание расширения. Конечно его можно улучшать и отлаживать дальше, например, добавить больше проверок на типы загружаемых картинок, чтобы убрать лишний мусор. Однако в целом расширение готово к публикации в Chrome Web Store.

Публикация расширения в Chrome Web Store

После того как вы достаточно протестировали расширение установленное локально, пришло время дать другим возможность его скачивать и устанавливать. Процесс публикации расширения в Chrome Web Store чем-то похож на публикацию мобильного приложения в Google PlayMarket или в Apple AppStore. В связи с этим сразу оговорюсь: всё что будет описано далее может стать неактуальным при изменении правил в Google. Наиболее актуальное руководство по процессу и условиям публикации расширения есть на их сайте по этой ссылке: https://developer.chrome.com/docs/webstore/publish/. Соответственно, прежде чем выгружать расширение в Web Store, изучите эти материалы.

Все описанное ниже лишь отражает мой собственный опыт. Относитесь к этому критично и используйте только в качестве дополнительной информации.

  1. Запакуйте папку с расширением в ZIP-архив.
  2. Зарегистрируйтесь в Chrome Web Store по адресу https://chrome.google.com/webstore/devconsole/. При регистрации можно использовать уже существующий аккаунт Google, например зарегистрированный в GMail.
  3. Оплатите разовую комиссию 5$.
  4. В консоли Chrome Web Store создайте новый продукт (это и есть расширение).
  5. При создании продукта нужно заполнить форму различной информацией о расширении и загрузить ZIP-архив с ним. Эту информацию будет видеть пользователь на странице расширения в Web Store. Она также включает изображения расширения различного размера. Также, можно предварительно создать видео для расширения, загрузить его в YouTube и указать ссылку на это видео в этой форме.
  6. Необязательно заполнять всю форму сразу. Можно указать часть информации, затем нажать кнопку «Save Draft» и вернуться к заполнению позже. Когда оставшаяся информация будет готова, нужно будет найти данное расширение в списке продуктов, открыть его и продолжить заполнение.
  7. После заполнения всей формы, нажмите кнопку «Submit for Review» и если все указано без ошибок, расширение будет отправлено на проверку в Google. Проверка может занять несколько дней. Статус проверки будет отображаться в списке продуктов.
  8. В процессе ожидания периодически заходите и проверяйте статус, потому что Google может не оповестить по почте о том что проверка завершена.
  9. Если расширение не прошло проверку, то будут указаны ошибки, которые необходимо исправить. После исправления нужно создать новый ZIP-файл, загрузить его в эту же форму и снова отправить на проверку. Насколько я знаю, ограничений на количество отправок нет или не было, когда я отправлял в последний раз.
  10. После того как расширение успешно пройдет проверку, его статус изменится на «Published» и оно будет доступно для поиска и установки в Chrome Web Store: https://chrome.google.com/webstore/.

Отдельно остановлюсь на создании архива с расширением для проверки. Архив должен иметь как можно меньший размер, так как пользователи будут его скачивать себе на компьютеры. Расширение не должно содержать никаких лишних файлов. Должно быть только то, что реально используется расширением. Соответственно, если вы использовали различные фреймворки для создания интерфейса, то в архиве, отправляемом на проверку не должно быть никаких вспомогательных файлов WebPack, никаких package.json и т.п. Только manifest.json, HTML-страницы указанные в нем и JavaScript файлы, картинки и другие данные, используемые этими страницами. С другой стороны, все внешние библиотеки, используемые расширением тоже должны быть в составе расширения, то есть загружены в папку с расширением и использованы из нее. В коде не должно быть никаких внешних ссылок на скрипты в Интернете. Наличие ссылок на внешние библиотеки является основанием отклонить расширение при проверке из соображений безопасности, даже если это CDN-ссылка на безобидную JQuery. Файл manifest.json тоже не должен содержать ничего лишнего, к чему можно было бы придраться. Например, мое расширение один раз завернули из-за того, что в разделе «permissions» были указаны разрешения, которые реально не использовались в коде. Поэтому не нужно указывать как можно больше разрешений «на всякий случай». Это не пройдет. В описании каждого метода Chrome API указывается, какие разрешения для него требуются, поэтому добавляйте только то что нужно.

Расширение Image Grabber, созданное в этой статье, успешно прошло проверку с первого раза и было опубликовано. На весь процесс ушло два дня. Расширение можно найти и установить здесь.

Рекомендую создать его с нуля читая статью, однако для нетерпеливых его исходный код доступен здесь: https://github.com/AndreyGermanov/image_grabber.

Это расширение использует не более 5% из того что можно сделать с помощью Chrome API. Например, я опубликовал еще одно расширение для сервиса распознавания текста. Оно позволяет распознавать текст из картинок в браузере с помощью контекстного меню: https://chrome.google.com/webstore/detail/image-reader/acaljenpmopdeajikpkgbilhbkddjglh.

Все возможности можно узнать в документации. Изучайте и пробуйте! Успехов в учебе и в бою работе!

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *