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

в 19:13, , рубрики: canvas, Facebook, html, javascript, php, Вконтакте, Разработка веб-сайтов, расширение для chrome, расширение для Firefox, Расширения для браузеров

Длинное, нудное вступление с претензией на манию величия

Однажды я обнаружил, что меня, как всегда, что-то сильно не устраивает в этом мире. А именно, введя какой-то длинный запрос в поисковике на настольном компьютере и затем перейдя на планшет, я никак не мог вспомнить дословно текст запроса, чтобы выйти ровно на те же результаты. А начиналось все так хорошо. Я увидел в поисковике ссылку на ответ на свой вопрос и понял, что она сулит долгое чтиво. Тогда я выключил комп и плюхнулся на диван с планшетом с мыслью о том, что вот сейчас я просто заново вобью все это в поисковик, открою ту ссылку теперь уже на планшете и лежа, спокойно, в более удобной позе прочитаю… Но не тут-то было. Какие-то мелкие разночтения в тексте — и моей ссылки уже нет в выдаче поисковика. Воспроизвести саму ссылку — тоже не вариант: она слишком длинная. Ломая голову над вариантами текста запроса, я чуть было в ярости не сломал планшет. Черт побери, пришлось вставать, снова включать компьютер, запускать браузер и копаться в истории, чтобы найти точный текст своего запроса.

Я написал кроссбраузерное расширение для вкладок, но вы так не делайте - 1

Расширение, установленное в Chrome и Firefox

Идея

Я подумал, а вот было бы неплохо написать такое расширение для браузера, которое бы позволило перекинуть любые свои открытые вкладки через сервер на любой другой свой компьютер и продолжить работать с ними там. Да, в некоторых браузерах уже есть облачная функция — достаточно лишь авторизоваться на обоих устройствах и… Но в том-то и загвоздка. А что, если у меня на компьютере Хром, а на планшете Фаерфокс? И потом… Всего лишь авторизоваться, ага. Если бы я еще помнил свой пароль от гугло-аккаунта. А аккаунта в Фаерфоксе у меня просто нет. Я даже не в курсе, там, вообще, бывают какие-либо аккаунты? Есть, конечно, сторонние облачные сервисы, где можно зарегистрироваться и наверно как-то текстом перекинуть самому себе нужную ссылку. Но это же еще надо там проходить регистрацию и накрепко зазубрить еще один пароль… Нет, это совершенно не реально. Можно, в конце концов, перекинуть себе ссылку через почту. Но все это долго и копи-паста из, а затем в адресную строку, чем, например, на планшете заниматься крайне неудобно. Нет, все это чушь какая-то…

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

Лирическое отступление

К слову, когда я пишу что-то свое, то я делаю это также по-своему. Я пишу на чистом javaScript в продвинутом блокноте, не пользуюсь гитхабом, не пользуюсь сторонними библиотеками, даже jquery не использую. Поэтому никакого туториала о том, как написать расширение для браузера, здесь не будет. Этому я вас не научу. Не делайте так, как я. Я вас предупреждаю — это плохой пример. Также хочу сказать всем потенциальным критикам, предвосхищая их реакцию — да я совершенно согласен со всем вашим праведным гневом. Да, нельзя пихать весь скрипт в один-два файла, а надо крошить все, как салат, на множество маленьких кусочков, чтобы оно потом собиралось 3 часа по длинной, умной команде из командной строки. Да, нельзя совмещать javascript-код и html-код в одном файле — лучше навернуть еще один уровень абстракции, который будет их объединять. Да, нельзя использовать табличную верстку, но, черт, побери, она железная! И, уж, тем более — сейчас меня сожгут — нельзя использовать innerHtml и создавать сложную html-структуру одним махом, а надо прикреплять и откреплять в цикле все children и старательно навешивать, а затем удалять каждый обработчик… Да, я делаю все неправильно. Грешен, каюсь. Но я делаю это осознанно, потому что так проще. А кто мне запретит? В конце концов, это же не противозаконно, да? Я не умею программировать так, как того требует современная мода, и даже не хочу пытаться, потому что я считаю, что схема процесса разработки излишне усложнена. Я за то, чтобы писать код руками, а не конструировать его из чьих-то кусков, не написав, при этом, ни строчки. Однако, мой код получается легковесным, его не нужно собирать специальной командой и он быстро работает. Я пишу код по технологии F5. Это когда для того, чтобы в любой момент процесса разработки посмотреть, как работает текущая версия, достаточно просто нажать в браузере F5.

Я написал кроссбраузерное расширение для вкладок, но вы так не делайте - 2

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

Хранение вкладок

Итак, я решил, что открытые в браузере вкладки должны сохраняться в некие списки, которые будут называться компьютерами — Домашний компьютер, Рабочий компьютер, Локальный компьютер. Локальный — это когда список хранится не на сервере, а в браузерной базе данных, и пользоваться им, соответственно, можно только в том браузере, из которого он сохранен. Это для тех вкладок, которые не требуется перекидывать между разными компьютерами. Ну а остальные списки можно просматривать на любых компьютерах и браузерах из числа поддерживаемых расширением.

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

Согласно документации по созданию расширений для Chrome и Firefox, код расширения должен располагаться в двух файлах — основном и фоновом. Основной скрипт занимается отображением интерфейса для пользователя, а фоновый скрипт может управлять вкладками браузера и обмениваться данными с сервером. Управление вкладками в Chrome и Firefox очень схоже. Например, обработчик события открытия или закрытия вкладок в Chrome выглядит так:

chrome.tabs.onUpdated.addListener( function(tabId, changeInfo) {
});

А в Firefox так:

browser.tabs.onUpdated.addListener( function(tabId, changeInfo, tabInfo) {
});

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

chrome.tabs.query({},function(data){
});

Да-да, в Firefox записывается тоже chrome… и т.д. Это немного странно, однако, очень радует то, что браузеры движутся к стандартизации своих api.

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

После прочтения открытые в браузере вкладки отправляются на мой сервер и сохраняются там в обычной SQL базе данных. Серверный скрипт написан на PHP. Есть таблица пользователей, в которой хранятся ссылки на строки в другой таблице — таблице данных. Эти данные — и есть адреса страниц, их названия и текстовые комментарии пользователя, хранимые в формате текстовых строк в поле типа text длинной около килобайта. Данные хранятся в незашифрованном виде. На килобайт можно сохранить около 1000 вкладок. Для каждого пользователя предусмотрено до 25 таких хранилищ («Компьютеров»).

Однако, бесплатная версия расширения ограничивает пользователя двумя хранилищами — Домашний компьютер и Рабочий компьютер, причем, в каждом можно сохранить не более 10 вкладок. Над платной версией и способами оплаты я еще работаю. Там будет до 25 «компьютеров» и примерно до 1000 вкладок в каждом, а также выбор фоновых изображений, иконок и названий для каждого компьютера, иерархические списки и многое другое.

Есть еще одно хранилище — Локальный компьютер. Здесь ограничений никаких нет, поскольку данные вкладок хранятся в локальной базе данных браузера на компьютере пользователя. Здесь можно хранить сколько угодно вкладок, но не более, чем позволяет жесткий диск. Причем, поскольку данные хранятся не в Local Storage, а в indexedDB, то ограничение в 5 или сколько-то там мегабайт отсутствует. Важно только случайно не очистить это хранилище вместе с историей браузера. Планирую еще сделать экспорт и импорт вкладок в текстовый файл.

Для работы с indexedDB я написал небольшую подбиблиотечку длинной всего 106 строк. В отличии от базы данных на сервере, здесь все данные не требуется перегонять в текстовую строку, а можно сохранять сразу в ассоциативном массиве как есть. Принцип хранения там такой: ключ — значение. А значением может быть и огромный ассоциативный массив.

Аутентификация пользователя

Я написал кроссбраузерное расширение для вкладок, но вы так не делайте - 3

Firefox

Авторизация, как я уже отметил, происходит через социальные сети, причем, на сервере, по протоколу OAuth. Пока поддерживается Facebook и ВКонтакте, но я планирую увеличить число способов аутентификации пользователя. Схема серверной авторизации по протоколу OAuth подробно описана в документации к обоим соцсетям, поэтому нет смысла останавливаться на ней подробно. Замечу лишь, что мне не нравилось то, что при авторизации открывается новая вкладка браузера даже в том случае, если пользователь уже залогинен в соцсети и уже предоставил права расширению. Эту вкладку приходится отлавливать фоновым скриптом расширения и затем принудительно закрывать и вообще, она выглядит не эстетично, вклинивается к остальным вкладкам и мигает при появлении и уничтожении.

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

oauth.vk.com/blank.html

А для Facebook:

https://www.facebook.com/connect/blank.html

И еще на всякий случай:

facebook.com/connect/login_success.html

Поэтому вначале я отправил в Google Web Store сборку расширения, в которой авторизация производилась на клиенте с предварительным подтягиванием соответствующего клиентского javascript api социальной сети. Причем, поскольку расширение установлено у пользователя, а авторизовать мне надо на своем сервере, то я еще и в фоновом скрипте расширения в и-фрейме подтягивал html со своего сервера, а вот уже в нем подтягивал api соцсети…

И схема обмена данными с сервером была такая. Индексная страница расширения отправляет команду (информацию о клике, например) в фоновый скрипт расширения. Фоновый скрипт при помощи postMessage отправляет данные в i-frame html. I-frame отправляет данные php скрипту на сервер. Сервер отвечает в i-frame, i-frame отвечает в фоновый скрипт, фоновый скрипт отвечает в индекс, пользователь видит ответ. Уф… И главное — все это работало!

В общем, это не прокатило. Гугл завернул мое расширение с рядом формулировок, среди которых было и что-то расплывчатое о том, что, мол, ваше расширение не соответствует нашей политике безопасности. Путем переписки с поддержкой, мне удалось добиться конкретики. А именно, им не нравилось то, что расширение подтягивает html и js со сторонних серверов… В общем, пришлось оказаться от клиентской авторизации и вернуться к серверной. Да, отправлять http post запрос из фонового скрипта расширения напрямую на свой сервер, все же, оказалось разрешено. Ну и ладно. Так даже лучше. Получилось короче и не нужно тянуть javascript api из социальных сетей — это лишнее время ожидания для пользователя.

Просмотр вкладок

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

Я написал кроссбраузерное расширение для вкладок, но вы так не делайте - 4

Список вкладок в Chrome

Цвета для плиток подбираются автоматически на основе имени домена сайта. Функция принимает url. Имя домена делится на три примерно равные группы символов, Первые символы из каждой группы преобразуются в значения компонентов цвета: R, G, B. Это преобразование производится следующим образом. Коды этих трех символов приводится к диапазону d1, от 0 до 25, путем взятия остатка от деления на 26. Даже символы русского алфавита будут приводиться к этом диапазону. Неважно, что 27-я буква там станет первой: нам не требуется, чтобы компоненты цвета всех букв алфавита были непременно различными, пусть повторяются. Затем три полученных числа из диапазона d1 (0...25) пропорционально приводятся к диапазону d2 (150...255). Вообще, эти диапазоны заданы в начале функции и их можно менять на любые другие. Я взял 150...255, чтобы компоненты цвета были яркими. Если задать, например, 0...100, то плитки получатся темными. Таким образом, три символа из имени домена преобразуются в 3 числа из диапазона 150...255. И затем, как вы уже наверно догадались, они интерпретируются как компоненты цвета плитки R, G и B. Все очень просто.

getSiteColor=function(url) {
	var d1=[0,25], d2=[150,255];
	var code=0, codep=0, color=0, str='', domen='', ar=[], inc=0;
	ar=url.split('//'); if (ar.length>1) {str=ar[1];} else {str=ar[0];};
	str=str.split('www.').join('');
	domen=str.split('/')[0];
	str=domen.split('.').join('');
	ar=[];
	inc=Math.floor(str.length/3);
	if (str.length % 3 >0) {inc++;};
	for (var i=0; i<str.length; i+=inc) {
		code=str.slice(i, i+1).charCodeAt(0);
		codep=code % (d1[1]-d1[0]+1);
		color=Math.round( (codep-d1[0]) * (d2[1]-d2[0]) / (d1[1]-d1[0]) ) + d2[0];
		ar.push(color);
	};
	return {r:ar[0], g:ar[1], b:ar[2], domen:domen};
}

Структура html документа

Html документ состоит из нескольких элементов canvas с абсолютным позиционированием, совмещенных путем наложения, и блока div, в который выводится список. На ресайз окна браузера повешена функция, которая изменяет размер всех этих элементов, подстраивая их под новые высоту и ширину. Есть также минимальный размер, преодоление которого вызывает появление скролл-баров. На нижнем канвасе отображается фоновое изображение. Я решил, что для разных списков (то есть, Компьютеров) надо задавать разные фоны, чтобы пользователю было легче ориентироваться. Фоновая картинка заполняет собой окно с сохранением своих пропорций и чуть осветляется, чтобы не перетягивать внимание на себя — поверх нее рисуется белый полупрозрачный прямоугольник. Выше фонового канваса лежит канвас главного меню, он заполняет только верхнюю часть экрана. Ну а еще выше лежит блок div, в котором формируется сам список вкладок. Навигация по канвас-меню осуществляется посредством моей мини-библиотечки.

Я написал кроссбраузерное расширение для вкладок, но вы так не делайте - 5

Опера и Яндекс браузер

Локализация

На главном экране есть флажок, по нажатию на который происходит переключение между русским и английским языком интерфейса. Здесь делается переадресация страницы на тот же адрес, но с параметром в командной строке. Этот параметр читается перед загрузкой всех остальных элементов. И на основании его значения в ассоциативный массив для фраз подгружается тот или иной js-файл с фразами русскими либо английскими: lng_en.js, либо lng_ru.js. А в самом приложении для вывода фраз интерфейса используются уже ссылки на текстовые строки из этого ассоциативного массива. Также, в зависимости от значения в адресной строке, подгружается файл, содержащий элементы графического интерфейса — кнопки: buttons_en.png, либо buttons_ru.png. Я решил, что лучше создать кнопки прямо вместе с иконками и надписями на каждом языке в графическом редакторе, чем создать одну пустую кнопку и накладывать на нее текстом иконки и надписи, тем более, что их не много и весь этот спрайт-лист весит всего 50 Кб. Я планирую в дальнейшем поработать над дизайном и сделать все кнопки разными по форме.

Я написал кроссбраузерное расширение для вкладок, но вы так не делайте - 6

Общий же файл со спрайтами main.png, который содержит графические элементы, не привязанные к конкретной локализации, загружается в любом случае.

Веб-версия

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

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

Я написал кроссбраузерное расширение для вкладок, но вы так не делайте - 7

Веб-версия в IE 11

Реклама

Продвижением своего расширения я пока особо не занимался. Только создал группы в соцсетях и пригласил туда всех друзей. Да запустил рекламную кампанию в Яндекс Директе за 1500 рублей. Каких-то особых результатов она не дала, но, возможно я неправильно составил объявление. Или, может быть, проблема в том, что я указал ссылку на англоязычную версию домашней странички расширения. А, может быть, стоило давать ссылку не на домашнюю страничку, а сразу на один из магазинов расширений. Как вы понимаете, я не гений маркетинга. В общем, попробую еще раз с другими параметрами. Кампания дала 39 кликов. Сложно сказать, приобрел ли я в ее результате каких-то новых пользователей.

Я написал кроссбраузерное расширение для вкладок, но вы так не делайте - 8

Была одна странность с версией под Firefox.

Я написал кроссбраузерное расширение для вкладок, но вы так не делайте - 9

Статистика за 17 октября показывает аж 227 загрузок. Не знаю, способствовала ли этому кампания в Директе или нет, но дальше почему-то опять идет по 1-2 загрузки в день. При этом, для расширения Firefox есть понятие ежедневных пользователей:

Я написал кроссбраузерное расширение для вкладок, но вы так не делайте - 10

Почему загрузок было 227, но ежедневных пользователей оставалось всего 1-2, непонятно. Возможно, это был какой-то сбой. В общем, я так и не понял, что это было, и где все эти люди…

Статистика по магазину расширений Chrome Web Store радовала каким-то вечно не загружающимся графиком — что там, я так и не увидел — и 5-10 ежедневных пользователей.

Я написал кроссбраузерное расширение для вкладок, но вы так не делайте - 11

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

Я написал кроссбраузерное расширение для вкладок, но вы так не делайте - 12

Итог

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

Версии для Chrome и Firefox прошли проверки и были размещены в соответствующих официальных магазинах расширений. Причем, регистрация в качестве разработчика расширений у Google стоит $5. У Mozilla — бесплатно. Веб-версия расширения находится на сервере. Сборка из магазина Chrome прекрасно работает и в совместимых браузерах: ее можно установить также в Yandex Browser и Opera. Планирую выпуск новых версий с еще более расширенными функциями.

В общем, браузерные расширения — это сила. Теперь мне не требуется вставать с дивана, а также нет никакой необходимости в ярости ломать планшет. Это полный дзен. Всем добра.

Автор: Kempston

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js