Сценарий использования поисковых систем у меня, видимо, закреплен исторически (когда интернет был медленный): попадая на страницу поисковой выдачи, я открывал несколько вкладок в фоновом режиме, и пока остальные загружались, вполне можно было уже прочитать первую вкладку. В случае, когда находил нужную информацию на одной из вкладок, остальные приходилось закрывать вручную. Если не закрыл сразу, вкладки оставались висеть, раздувая количество открытых вкладок в браузере, которые, как правило, редко после этого закрывались.
К тому же, если переходишь на странице по ссылкам, которые открываются в новом окне, создается несколько связанных между собой (логически) вкладок. Когда находишь нужную информацию, не всегда можно вспомнить какие вкладки связаны между собой, можешь закрыть не все, что также ведет к раздуванию количества открытых вкладок.
Мне всегда нужна была кнопка «Нашел», которая бы подчищала за мной последствия поиска (назовём её «I was lucky»). После того, как окунулся в мир расширений для браузеров, я подумал, что это то, что может помочь в данном случае. Так смутно начало появляться желание написать расширение, которое бы решало мои задачи.
Расскажу вам свою историю, рассказ буду вести в хронологическом порядке, выводы могут оказаться неожиданные.
Первый шаг на пути
Первым делом взялся за настройку инфраструктуры: webpack + babel. И сразу же мне не понравилось, что babel дублировал в каждом модуле код для своих хелперов. Можно было настроить, чтобы он использовал объект
babelHelper
, но тогда файл с кодом babelHelper
нужно было подключать в конфигурации webpack. Хранить такой файл в проекте и указывать его в entry
было некрасиво, я сделал плагин для вебпака, который выполнял это за меня автоматически. Потратив много сил на первый шаг и написав ещё немного кода для самого расширения, я немного притормозил.www.npmjs.com/package/webpack-babel-external-helpers-2
Фундамент
Время шло, а в наличии был только плагин для вебпака, который никак не решал моих задач. И каждый раз, когда я что-то искал и не закрывал вкладки, была мысль: «Хорошо бы доделать то расширение...» Желание росло и росло, и вот, в один прекрасный день, количество переросло в качество.
Самое время рассказать, в чем была основная идея:
Пользователь попадает на страницу поисковой выдачи — СЕРП, мы парсим выдачу, сохраняем себе адреса ссылок, после того, как пользователь перешел по одному из адресов, показываем ему уведомление с остальными адресами и кнопкой «Нашел», чтобы закрыть вкладки.
При переходе на страницу могут быть различные варианты. Самый простой: один запрос — один ответ от сервера (200). Самый сложный: один запрос — несколько серверных перенаправлений (3xx), после чего клиентское перенаправление (с помощью
<meta/>
или javascript), сверху ещё и history API. И комбинации между ними, как правило, большинство сайтов попадает в эту категорию.Простой случай перехода:
![Случай простого перехода (ответ 200)](https://habrastorage.org/webt/d9/ml/od/d9mlodnv9fkgcqczxiomnwwhvqc.png)
Сложный случай перехода:
![Случай сложного перехода (3xx + клиентские перенаправления)](https://habrastorage.org/webt/yl/zc/ps/ylzcpsc_dcgocw-o5vhwci2xoy4.png)
То есть сохранить адрес страницы и при переходе проверять только его не всегда достаточно. Поэтому нужно создать логический Переход, куда записывать все адреса, встретившиеся на пути, а потом проверять, что логический Переход содержит в себе сохраненный адрес. Задача понятна, но не все так прямолинейно в исполнении.
В Хроме есть два API, связанных с навигацией: webNavigation и webRequest — каждый со своими событиями. Первый — связывает переходы и UI браузера, последний — нижележащие сетевые запросы. Поэтому, если изменение адреса на странице произошло за счет history API, не будет никаких событий у последнего, а если во время сетевого запроса происходят перенаправления, то первый об этом никак не сообщает. Следовательно, нужно использовать оба АПИ, собирая по щепотке от каждого события каждого АПИ, формировать один логический Переход.
webNavigation
(wN) выполняются в следующем порядке:onBeforeNavigate -> onCommitted -> onDOMContentLoaded -> onCompleted
Интересующие события
webRequest
(wR):onBeforeRequest -> [onBeforeRedirect -> onBeforeRequest]* -> onCompleted | onErrorOccurred
Но между собой события wR и wN не имеют определенного порядка (на аналогичных стадиях запроса), т.е. в каких-то случаях
wN.onBeforeNavigate
может выполниться раньше wR.onBeforeRequest
, в каких-то наоборот. Что немного усложняет логику работы.Для этих АПИ нужно указывать соответствующие разрешения в манифесте расширения, а посему при установке расширения, пользователю будет выдаваться пугающий текст о возможностях расширения.
Развитие
… Вернемся к моменту, когда количество переросло в качество. С начала разработки до этого момента прошло существенное количество времени: браузеры стали поддерживать es6 модули, shadow DOM и другие современные фичи. Для сборки проект переехал на Rollup, плагин в этот раз писать не пришлось. После постройки фундамента — возможности получения информации о любом переходе в любой вкладке, осталось реализовать логику парсинга поддерживаемых СЕРПов и показа уведомлений на связанных страницах.
Первая задача достаточно примитивная: знаем адрес СЕРПа, лезем в содержимое страницы с помощью контент скрипта, получаем интересующие нас данные, сохраняем, ждем, когда пользователь перейдет на одну из страниц, чтобы показать ему уведомление с остальными страницами.
Для второй задачи нужна реализация самого уведомления, то что показывать на странице пользователю. И здесь тоже без контент скриптов не обойтись.
Изначально был только один обработчик (он же контроллер), отвечающий за логику при взаимодействии пользователя с поисковыми системами. После чего возникла идея почему бы не показывать уведомления на связанных вкладках, когда пользователь просто переходит по ссылкам, открываемых в новых вкладках. Пришлось переделать логику, сделав ее более универсальной. По аналогии с middleware React/Redux, можно подключать несколько обработчиков Переходов, что в будущем позволит реализовать возможность отключения/включения различных обработчиков в настройках расширения.
Приватность
Так как уведомление — это панель внизу экрана, и добавляется она в разметку страницы, то скрипт на странице может получить доступ к этому элементу так же, как и к любому другому элементу на этой странице. То есть теоретически страница могла бы узнать какой поисковый запрос вы использовали, в каком поисковике и какие другие страницы вам предложены, что не очень хорошо.
На помощь приходит технология под названием shadow DOM. В вебе не рекомендуется использовать
closed mode
при создании shadowRoot
, потому что в этом нет большого смысла (все равно придется хранить ссылку на элемент shadowRoot
где-нибудь, если хочется иметь к нему доступ программно; так же можно переопределить функцию attachShadow
, чтобы она создавала shadowRoot
в открытом режиме, и тогда скрипты подгруженные после переопределения уже будут пользоваться новой версией функции). В случае же расширения это не так. Контент скрипты и скрипты страницы живут в параллельных мирах. Скрипты со страницы не имеют доступ к объектам, определенным в контент скриптах, контент скрипты же оперируют с нативной реализацией функций DOM объектов (переопределенная функция скриптом со страницы не имеет эффекта на функцию, с которой работает контент скрипт). Соединяя эти два условия, получаем, что можно создать элемент с закрытым
shadowRoot
, сохранив ссылку на него в переменной.В этом случае скрипт со страницы сможет получить доступ только к элементу обертке, который для него будет пустой. Он не сможет получить текст запроса или предложенные страницы. Нужно внимательно следить, чтобы в сгенерированных событиях не отдать ссылку на какой-нибудь элемент внутри уведомления или открытый текст. Поэтому в расширении в событиях используются сгенерированные id, а уже background скрипт по этому id понимает что от него требуется. Для страницы же этот id достаточно бессмысленный.
Трудности перевода
Изначально расширение разрабатывалось только для Google Chrome, но так как WebExtensions API, где-то в голове держал возможность портирования в другие браузеры. А наличие webextension-polyfill вселяло уверенность. Но как бы не так. Полифил для этого расширения принес только возможность использования chrome API с промисами.
Firefox стал разочарованием года. Несоответствие chrome API в Фаерфоксе (Bug 1543647, Bug 1595621) оказалось критичным для работоспособности расширения, можно сказать оно в этом браузере не работает (как положено).
Vivaldi был наиболее близок, но также не обошлось. Событие
wN.onCreatedNavigationTarget
не возникает, когда пользователь открывает ссылку средней кнопкой мыши или через Shift|Ctrl
+ левая кнопка мыши, вместо этого в событии wN.onCommitted transitionType == 'start_page'
, чего нет в chrome API, из-за этого не во всех случаях расширение работает правильно. Так же в Вивальди не работают горячие клавиши для расширений. Что является киллер-фичей в данном случае в Хроме, позволяет намного быстрее переходить по вкладкам и закрывать их, без необходимости использования для этого мышки.Заключение
В ходе написания кода логика работы показа уведомлений менялась несколько раз, каждый раз упрощаясь. В итоге получилось так, что можно было не городить огород с логическими Переходами, а отлавливать «связанные переходы» пользователя (в событии
wN.onCommitted
есть флаг transitionType
, который указывает из-за чего был переход, во многих случаях он равен «link», означающее что пользователь перешел по ссылке), что значительно бы упростило код и работало во многих случаях, но не во всех.Так же, не находясь в теме, ожидал большей совместимости с точки зрения webExtensions API. Как всегда — хорошо жить в мире современных браузеров, когда не нужна поддержка старых версий. CSS анимации прекрасная вещь: то, для чего раньше нужно было использовать js библиотеку, теперь делается в несколько строк на css. В расширениях не работают Custom elements, зато работает shadow DOM, позволяющий воспользоваться всеми его возможностями.
MonkAlex
В лисе использую TreeStyleTab:
1. Открываю в новом окне поиск.
2. Открываю из поиска любые ссылки для деталей (и новые ссылки из них в том числе).
3. Когда закончил — закрываю всё дерево вкладок, которое относится к изначальному поиску.
fareloz
Думал все время именно об этом расширении пока читал статью.
Хотя мне больше нравится группировка у Вивальди, когда можно группы вкладок делать как одну вкладку, но найти расширение с таким поведением я не смог
SakuradaJun
А я не знал об этом расширении. Пока читал статью, «изобрел» дерево вкладок. Добравшись до комментариев, понял что кто-то уже изобрел его до меня. Надо будет установить.
Группы вкладок (задачи) есть еще у браузера Min. Одно время пользовался им на слабом нетбуке с линуксом.
questor
Думал всё время об этом пока читал статью. Сижу на TST с 2004 года, очень доволен.
kvaps
Тоже люблю TreeStyleTab, в дополнение к нему использую Dustman, расширение которое автоматически закрывает вкладки если я на них не вернулся в течении некоторого времени
ICELedyanoj
TreeStyleTab + OneTab.
С TST уже лет 15. Как-то на пару лет уходил на хром, пока лисе было очень плохо в плане стабильности и производительности, но всё это время мучился и искал альтернативные расширения для Хромиума, но увы — запрещено из коробки движком.
Несколько месяцев нашёл OneTab — и это то, чего мне так не хватало раньше.
Одна единственная кнопка на панели меню закрывает все вкладки и отправляет все URL на специальную локальную веб-страничку, где каждое нажатие на кнопку закрытия вкладок создаёт отдельную сессию. Все вкладки можно не спеша просмотреть в плоском списке, совсем лишнее закрыть, то что нужно — оставить на всякий случай либо открыть заново.
URLы, нужные для работы (Jira, TeamCity, vCenter etc.) у этого расширения в исключениях, и оно их не трогает.
vap1977
Увы, OneTab начинает жутчайше тормозить, когда накопится некоторое количество сброшенных туда URL-ей. Жутчайше — это порядка 20 секунд от нажатия на ссылку до первой реакции браузера на это нажатие при 2000 сброшенных в onetab вкладок.
Так что от необходимости вручную чистить вкладки он не спасает, просто отодвигает порог немного дальше.
ICELedyanoj
Ну до 2К вкладок пока не доводил — подчищаю периодически.
Начинает заходить в голову идея, что нужно подружить OneTab и Dustman.
OneTab не умеет подчищать автоматически, у Dustman нет списка исключений и некоторых других фич. Если бы это было одно дополнение, скидывающее неактивные вкладки, сортируя их по доменам…
madCreator
Есть куда более крутой аналог для хрома Tabs Outliner.
Это вообще лучшее расширение на свете для работы с вкладками, уж поверьте, я их много перепробовал как настоящий вкладочный маньяк!
Он также строит дерево вкладок, однако, это дерево можно тасовать как угодно на своё усмотрение, гасить вкладки, делая их неактивными, ставить на них названия, писать прямо в дереве короткие заметки для вкладок, и, что было бы актуально для автора статьи, гасить кусок дерева сразу, найдя что-то нужное, буквально в два клика!
Дерево, кстати, может быть воистину огромным, несколько тысяч вкладок (неактивных, естественно) — пожалуйста!
Есть бэкап и автобэкап дерева на гуглодиск.
MonkAlex
Не пользовался, щас погуглил и посмотрел скрины.
Оно отдельным окном? Как оно реагирует на хоткеи в окне браузера? Надо ли явно между ними мышкой переключаться?
Как у него с шириной? Надо явно таскать отдельно окно хрома, окно вкладок?
У TST неплохая встраиваемость в браузер, а вот всякие вещи типа заметок мне лично ни разу не были нужны.
ПС: а что значит неактивные вкладки — понятия не имею. Активная одна всего, которая выбрана вроде?
madCreator
Да, оно отдельным окном. На первый взгляд кажется, что это неудобно, и именно так я и думал после TreeStyle Taba, но на практике оказалось, что отдельное окошко на самом деле действительно удобнее.
По сути это как отдельное окошко браузера, через которое управляются все остальные окна. Соотв. растягивать и таскать его можно как угодно. У меня оно вообще висит в отдельном мониторе, повёрнутом вертикально.
По поводу связи с другими окошками и хоткеями — хоткеи, управляющие расширялкой, работают при активации окна раширялки. Хоткеи других окон — в них. Связь окошек и расширялки, естественно, есть. Все открытые окошки в ней отображаются, все манипуляции с ними также.
Правильнее, наверное, сказать — открытая. Расширялка позволяет закрыть вкладку, и оставить на неё в дереве ссылку. Это и есть неактивная вкладка про которую я говорил. Можно использовать как закладки, фактически, скидывать туда странички почитать на потом.
В общем, оно на сто порядков удобнее всего аналогичного и я даже не верю до сих пор, что такая крутая штука мне не сниться, а существует в реальной жизни!
MonkAlex
Zenitchik
А в Вивальди его можно сделать как веб-панель?
ICELedyanoj
Так и не смог к нему привыкнуть — пытался несколько раз. Если бы оно было хотя бы внутри окна браузера — возможно. Но запрет хрома на скрытие оригинальных кнопок вкладок лично для меня — последняя капля. После TST пользоваться этим не получилось.
madCreator
Ну х.з., дело вкуса, конечно. Поначалу тоже отдельное окошко парило, но потом проникся — можно, например, несколько окон браузера открывать — в одном видосы, в другом текст (а именно так я частенько и делаю, раскидав их на разные мониторы). Тогда дерево вкладок в каждом окне избыточно. А растянуть окошки браузера и расширялки рядышком, так как тебе удобно, дело нескольких секунд. Тем более, что ничего близкого по функционалу просто нет в природе. Я на эту штуку как раз с TST пересел из-за его убогости, в тот момент, когда возникли и не решались проблемы с совместимостью при очередном 100500 тысячном обновлении огнелиса. С тех пор сижу на аутлайнере и горя не знаю.
Crandel
В хроме нету контейнеров, поэтому он идет лесом, отдельное окно мне не очень нравится, у меня и так штук десять окон открыто в і3. Все остальное, кроме заметок есть в TST и даже больше, так как можно интегрировать кучу плагинов (использую плагин для отображения закладок)
madCreator
Оно, конечно, дело вкуса, но по удобству использования несравнимо. Ну или тебе просто не нужен функционал Outlinera в полном объёме. Оно ж становиться понятно и ощутимо на сотнях вкладок.
Плагины, это конечно, прекрасно, но зачем тюнинговать запорожец, если можно сразу на мерседесе ездить?
Crandel
Затем, что я не использую хром, в нем нету контейнеров, он убог интерфейсом и кастомизацией и полон зондов от гугля
П.С. а еще я не пользуюсь мышью, как там в оутлайнера с клавиатурными сокращениями?
madCreator
с сокращениями всё прекрасно! Есть на все функции
Интерфейс как интерфейс, всё как у всех. Каких-то провальных минусов кроме управления большим количеством вкладок нет. А этот минус у всех стоковых браузеров без исключений. (Парадоксально на самом деле — это ведь важнейшая функция для браузера!). Что там в нём ещё можно кастомизировать, я как-то даже и теряюсь придумать, со стороны обычного юзверя, конечно.
Про контейнеры — а что было бы, если б они были? Мне как обычному пользователю какой от них прок?
Тут, конечно, каждый сам себе выбирает удобства, кому что критично. Мне важно ворочать кучей вкладок и делать это лучше всего в TabsOutliner. Увы, конкурентов у него нет и близко. А было бы здорово иметь альтернативу!
Про зонды не стоит обольщаться — они везде и всюду, и чем дальше, тем больше. Проще не париться на сей счёт, т.к. победить всё равно невозможно. Добро пожаловать в чудный новый мир.
Zenitchik
Нет веб-панелей. Вы в Опере (настоящей) когда-нибудь F4 нажимали?
dimm_ddr
Ну да, когда есть лишний монитор куда его можно вытащить. Или когда тебе удобно на одном мониторе несколько окон одновременно. У меня лишнего монитора нет, все 3 заняты и удобнее всего когда браузер растянут на весь монитор.
Кстати а этот outliner как с переключением вкладок работает? Нужно ли мне сначала из браузера переключаться на него, прыгать на другую вкладку, а потом обратно? Умеет ли переключаться между текущей и предыдущей активной, а не по порядку как убогий хром?
madCreator
Как-то не задавался таким вопросом, мне удобнее рулить из дерева. Но так навскидку, для этой цели должно быть какая-то спецрасширялка,
Дело привычки. Я тоже изначально был ярым сторонником одного окна, но потом как-то попустило. На мой взгляд, преимущества расширялки настолько мощны, что можно себя переучить, тем более, что это момент не принципиальный вообще. Можно эффективно работать и так и эдак. Лучше бы, конечно, было сделать эту фичу настраиваемой, но оно сделано как сделано
Если монитор с разрешением больше, чем HD, то растягивать там окошко браузера во всю ширь как-то бессмысленно, по-моему, всё равно будет пустота по бокам или сайт растащит во всю ширь, так что целиком в поле зрения не влезет.
А на мелких разрешениях любое дерево будет критично жрать полезное пространство
dimm_ddr
Совершенно объективно — если я что-то делаю на странице, например пишу коммент, то фокус будет у окна со страницей. В случае расширения внутри того же окна мне достаточно нажать комбинацию клавиш для переключения один раз. В случае двух окон мне нужно сначала переключиться на другое окно. Которое может быть даже не предыдущим и его еще придется искать. Или тянуться к мышке.
При этом кейс когда нужно переключаться между 2-3 вкладками часто при работе возникает регулярно. И нет, дополнительные окна не спасают, у меня итак открыты несколько браузеров в каждом из которых есть несколько нужных для конкретной задачи табов.
Ну то есть совершенно объективно ваш вариант — более медленный. К нему можно привыкнуть, я понимаю. Но конкретно вот этот вот довод — это уже не дело вкуса даже.
С одним окном на весь экран — да, наверное дело привычки. Но зачем менять то что мне удобно просто потому что убогий хром настолько не кастомизируем что не может сделать так, как нравится мне, я не понимаю.
token_zero
Не смог я его полюбить больше, чем Sidewise Tree Style Tabs, даже несмотря на устарелость последнего. Для сайдвайза я ещё и тему свою тёмную сделал. И даже в Вивальди удалось его веб-панелью засунуть, правда пришлось немного поковырять его код. На гисте где-то валяется дифф. Только вот с 79 версии хромиума оно перестало адекватно работать, так что я не обновляю браузеры. Если бы автор Tabs Outliner сделал всё не хуже, чем в сайдвайзе + поддержку веб-панелью вивальди, то я бы на него перешёл.
Teomit
Как по мне, Tabs Outliner — очень неудобное расширение из-за его концепции работы в отдельном окне. Например, при сворачивании/разворачивании окна браузера, окно с деревом необходимо сворачивать/разворачивать отдельно, что добавляет кликов. Перетаскивание окон происходит отдельно. В основном окне браузере остаются горизонтальные вкладки, которые накапливаются и занимают место.
![image](https://habrastorage.org/getpro/habr/comment_images/a4f/2cb/486/a4f2cb486dcabf7dfa0f8c1ea0068a85.jpg)
Тот же Tree Style Tab полностью интегрирован в браузер, являсь с ним одним целым. Отключение горизонтальной панели в FF вкладок встроенными в браузер средствами позволяет убрать дублирование этих самых вкладок.
madCreator
На современных мониторах дополнительная строчка с шапками окон мизерно влияет на полезное для просмотра пространство. Куда больше гадит новомодный просторный дизайн.
Отдельное окно — это ж из-за особенностей хрома как такового. Дело привычки на самом деле.
Это совсем небольшая плата за предоставляемый функционал.
Пользовался я когда-то TST, оно и близко по удобству не стоит
Zenitchik
В Вивальди почему-то веб-панели есть. Я, правда, не разобрался, как их использовать в расширениях.
Teomit
Все мои знакомые, которые пользуются вертикальными вкладками, довольны их реализацией Tree Style Tab или Vivaldi. Существующие решения для Chrome, в том числе и Tabs Outliner, им кажутся неудобными. Я понимаю, что вам нравится пользоваться данным расширением, но не все разделяют вашу точку зрения.
Zenitchik
В вивальди оно тоже в боковом контейнере? Надо попробовать.
SepiS
Для меня TST был последним сдерживающим фактором от перехода на Chrome. Но и он в результате не устоял. Жаль, очень удобное расширение, ничего подобного под хром я не нашел.
madCreator
пробуй Tabs Outliner, не пожалеешь!
VioletGiraffe
Лично мне не зашло, удобнее дефолтный стиль табов. Вкладок у меня от 20 до 200 типично.