Разработка простого расширения для google chrome

imageРасширения для браузеров очень популярны в наше время. Повод написать какое-либо расширение всегда найдется, и их напашется еще много.

В данной статье я хочу рассказать о том как я написал небольшое расширение для google chrome в личных целях. А цель статьи — помощь молодому программисту, с трудом понимающему английский язык. Не каждый на 3ом курсе сможет читать гугловскую документацию, которая есть только на английском. А сделать расширение хочется.

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

Данный пост будет более продвинутой версией.

Приступим.

Не хочется вдаваться в самые основы и поэтому рекомендую для начала прочесть эту статью.

Скачайте мое расширение и смотрите код параллельно чтению.

Мы уже примерно знаем, что такое manifest.json и для чего он нужен.
Если кратко, то это основной файл расширения, который сообщает браузеру какими вещами расширение будет пользоваться, и основные параметры (название, описание и тд).

Перейдем непосредственно к моему расширению.
Это простой скрипт, который удаляет со страницы фриланса те объявления, которые мешают искать интересные мне объявления (если точнее, то я ищу лишь долгосрочные проекты). Дополнение к существующему встроенному фильтру.

manifest.json

{
  "name":"Ffilter",
  "version": "1.0",
  "background_page": "bg.html",   // имя background страницы.
  "icons": {
    "48":"icon_48.png",
    "128":"icon_128.png"
  },
  "page_action":{      // действие для текущей страницы
    "default_title": "Ffilter",
    "default_icon": "icon_19.png",
    "default_popup":"popup.html"   // имя страницы фильтра
  },
  "permissions": [      // разрешения
    "tabs","http://www.free-lance.ru/*"
  ],
  "content_scripts":[{       // работа с DOM страницы фриланса.
    "matches": ["http://www.free-lance.ru/*"],
    "js": ["jq.js","script.js"]
  }]
}


* This source code was highlighted with Source Code Highlighter.

Рассмотрим.
Имя, версия — это всем понятно.

imagebackground_page — тут должно лежать имя фоновой страницы. Фоновая страницы — очень важный элемент, хотя для некоторых приложений он и не обязателен. Но в нашем случаи без него не обойтись.
Фоновая страницы работает всегда, когда работает расширение (то есть когда оно включено). Она всегда одна и может связываться и управлять всеми остальными элементами.

Иконки нужны разных размеров для отображения в адресной строке, в списке расширений, возле адресной строки.

page_action — важный объект. Он сообщает браузеру, что наше расширение будет индивидуально для каждой вкладки, то есть значок будет выводиться в адресной строке, а не на панели (например gmail checker). Такие расширения как gmail checker не считаются индивидуальными, они открываются один раз для всего браузера (для них в манифесте используется вместо объекта page_action объект browser_action, на картинках соответственно).
image    image

default_title, default_icon — название, иконка соответственно.
default_popup — имя html-страницы расширения, которая будет всплывать при нажатии на иконку. Посмотрите предыдущий топик, если не все ясно.

permissions — массив с разрешениями. Нам пригодиться общаться с системным объектом tabs и обращаться к адресу фриланса (имеется в виду не ajax запросы, а js работа со страницей).

content_scripts — важный для нас объект, именно он разрешает пользоваться js на странице фриланса. Мы указываем адрес страницы и указываем js-файлы, которые будут исполнены сразу после загрузки body. Я использую jQuery и определяю свои функции. Порядок имеет значение.
Важно знать, что скрипты расширения не могу видеть объекты/переменные скриптов самой страницы. Это значит, что если у страницы уже есть свой jQuery мы не сможем его использовать, обязательно надо подгрузить свой. Это называется изолированными мирами и это иногда удобно. Скрипты из расширения, конечно же, могут манипулировать DOM.

Идем далее.
Если Вы все верно представили себе то видите, что наше расширение состоит из 3х основных объектов: фоновая страница (одна для всех), окошко с фильтром (для каждой страницы), и скрипты на каждой странице.

Общий алгоритм таков:
Фильтр должен общаться только со скриптом на текущей странице. Скрипт должен только принимать указания от фильтра на текущей странице и исполнять их. И тот и другой не должен общаться с фоновой страницей, но фоновая страница должна контролировать фильтр. Ведь фильтр должен появляться только на странице фриланса. И, кстати говоря, расширение page_action по умолчанию всегда скрыто, и его нужно включать через фоновую страницу, она это и делает когда загружается страница фриланса.
image


bg.html

chrome.tabs.onUpdated.addListener(function(id,info,tab) {
  if(info.url)
    if(/free-lance.ru/.test(info.url))
      chrome.pageAction.show(id);
});  


* This source code was highlighted with Source Code Highlighter.

Приведен только js код (только он и нужен).
chrome.tabs — системный объект с которым приходится больше всего работать, как Вы догадались, он отвечает за вкладки.

Мы вешаем свою функцию на событие onUpdated (обновление вкладки, переход по ссылке). В качестве параметров приходят идентификатор вкладки, информация о обновлении, объект самой вкладки. Нам понадобиться информация — она содержит адрес текущей страницы. Мы проверяем фриланс ли это и если да, то у объекта chrome.pageAction вызываем метод show передавая туда идентификатор этой вкладки.

chrome.pageAction — объект отвечающий за расширения внутри адресной строки, и мы просим показать иконку в нужной нам вкладке (где только что открылся фриланс).

Все действия фоновой страницы окончены, сайт открылся, иконка появилась, теперь можно на нее кликнуть и появится popup.html.

popup.html

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

Вообще, в документации сразу предлагают этот способ:
chrome.tabs.executeScript(null, {code:"alert(‘hello!’)"});

* This source code was highlighted with Source Code Highlighter.

или можно так:
chrome.tabs.executeScript(null, {file : “script.js”});

* This source code was highlighted with Source Code Highlighter.

imageНо подумайте, как туда передавать параметры? Только строковые? (к слову говоря, null — это значит, что мы хотим исполнить скрипт на текущей вкладке)
Это мне подходило, я искал способ передать объекты. И такой способ нашелся, но он был спрятан в документации.
port — специальный объект в chrome, с помощью которого можно общаться от скрипта к фоновой странице и popup, или от фоновой страницы и popup к скрипту.

Что бы подключиться к скрипту нужно соединяться через табы:
var port = chrome.tabs.connect(id);

* This source code was highlighted with Source Code Highlighter.

где id номер нужной вкладки со скриптом. port — возвращенный объект транспорта.

Что бы подключиться к фоновой странице или всплывающему окну нужно обратиться к объекту расширения:
var port = chrome.extension.connect();

* This source code was highlighted with Source Code Highlighter.

Расширение одно — идентификатор указывать не надо.
Прослушивать эти подключения можно одинаково и там и там:

chrome.extension.onConnect.addListener(function(port){
  port.onMessage.addListener(MyFunc);
});


* This source code was highlighted with Source Code Highlighter.

Для чего onMessage станет понятнее позже.

Вернемся к popup.html.
В этом месте я связываюсь со скриптом, который “дежурит” на данной странице.
Для меня стало проблемой то, что нужно указать идентификатор вкладки, а ведь я не знаю этот ИД. Забавно, что нам нужная текущая вкладка, и можно было бы просто послать null вместо номера, но это не пройдет — нужно знать ИД. Как?
Много времени убил на это и сделал вот так:
chrome.windows.getCurrent(function(w){
  chrome.tabs.getSelected(w.id,function(t){
    port = chrome.tabs.connect(t.id);
  })
});


* This source code was highlighted with Source Code Highlighter.

Мы просим сказать какое окно сейчас активно, и нам в callback возвращают объект окна. Далее мы просим сказать текущую вкладку в этом окне и нам ее возвращают тоже в callback. И только тогда мы открываем соединение со скриптом.
Это работает, но мне кажется, что это неправильно — должен быть способ проще.

Когда все данные фильтра собраны, по нажатию на кнопку мы отправляем в этот порт объект с помощью метода:
port.postMessage(obj);

* This source code was highlighted with Source Code Highlighter.

И в этот момент начинает работать script.js

В script.js важно это:

chrome.extension.onConnect.addListener(function(port){
  port.onMessage.addListener(Filtr);
});


* This source code was highlighted with Source Code Highlighter.

Прослушиваем порт и дожидаемся входящего подключения с объектом port.
У этого объекта есть событие onMessage — событие когда сюда присылают сообщение. Мы вешаем свою функцию Filtr, которая примет все аргументы, которые пришлют с помощью port.postMessage() на другом конце. Функция Filtr удалит все объявления, которые указаны в объекте.

Все

Вот и все. Это работает и помогает мне в поиске заработка.
Было сложно разбираться без знания английского.
Надеюсь Вам пригодится.

Документация, расширение.
AdBlock has stolen the banner, but banners are not teeth — they will be back

More
Ads

Comments 26

    0
    Всё время хочу написать своё расширение, да руки не доходят
      +6
      Это увлекательно, попробуйте :)
      0
      jkeks.ru/jkeks.ru/archives/804 — еще один, но в сравнении с Opera
        +3
        Пара дополнений:

        — content scripts можно исполнять не только сразу после загрузки body — есть несколько вариантов старта выполнения

        — так же можно прямо в манифесте в разделе content_scripts регэкспом указать адреса, для которых они будут выполняться (в Вашем случае — фриланс), чтобы не проверять все открываемые страницы, как это сейчас делается в коде

        — можно внедрить скрипт, взаимодействующий с нативным js страницы — через вставку в DOM <script/>; соответственно, с расширением и другими content scripts он общаться не сможет
          0
          На счет второго, видимо, я Вас не понял. Как же отображать иконку именно для фриланса?

          Спасибо за дополнение, вставлять <script/> — хитро :)
            0
            В манифесте, я вижу, Вы уже указали запускать content_scripts только на фрилансе, поэтому осталось только в самом content script вызывать отображение page action.
              0
              А как в самом content script вызывать отображение page action Вы можете подсказать? Спасибо.
                0
                в content_script дергаете событие, вся логика остается в его обработчике в background.
          0
          Раз уж тема про аддоны для хрома, то я задам тут вопрос. Можно ли из аддона получить доступ к файловой системе?
          Например аддон в ИЕ имеет доступ к AppData/LocalLow даже из Protected Mode. Может ли такое хром?
            0
            Я не уверен, но по-моему нет. Для хранения настроек используется localStorage, а про файлы я не видел упоминаний.
              0
              очень жаль. странно что тока ИЕ на таком привелигированном положении.
                0
                По сути, доступ к AppData/LocalLow — это уже не произвольная файловая система, а тоже некое специализированное хранилище, даже если есть доступ к файлам, не принадлежащим аддону.

                В Хроме для хранения «своих» данных с теми же возможностями хватает localStorage, а к данным других аддонов прямого доступа нет, зато есть возможность общаться между аддонами, в т.ч. передавать данные — ИМХО более правильно.
                  0
                  а этот localStorage — это какое-то место в файловой системе или просто некое виртуальное хранилище?
                  Мне просто нужно какое-то место где мой аддон смог бы создать файлик, а затем внешняя прожка могла к этому файлику обратиться.
                    0
                    Троян что ли кодите? :)
                      0
                      Нее. мне просто надо чтобы про нажатию кнопки на аддоне сохранялся определенный файлик с сайта на комп а потом этот файлик анализируется моей прожкой.
            +3
            А я недавно в качестве своего первого расширения написал корзину, как в опере. Вдвойне приятно и полезно — опыт, новое и неизведанное + удобство! (Это я к тому, что такое расширение наверняка уже написано)

            Если есть интерес, то могу в подобной форме изложить урок! Там в основном работа с вкладками, ранее такого, кажется, на хабре не было урока!
              0
              Предполагаю, что используется chrome.tabs.onRemoved. А можно посмотреть исходники?
                +2
                Хитрость в том, что в onRemoved уже нельзя получить информацию о удаленной вкладке, ее приходится получать при создании и при обновлении вкладки!

                Завтра будет статья! ;)
                  0
                  Ззаинтриговали ) жду.
              0
              кто бы подсказал расширение, которое можно распотрошить для создания менюшки…
              +1
              Кстати, простые экстеншены делаются гораздо проще, т.к. хром умеет работать с GreaseMonkey скриптами.

              В примитивном случае достаточно открыть в хроме файл какое-нибудь-имя.user.js и он предложит установить это как экстеншн (доп. инфа).
                0
                Добрый день!

                Подскажите, пожалуйста, как при загрузке страницы обратится из background в script??

                По идее, должна работать следующая схема:

                В файле background.html отправляем запрос, например
                chrome.tabs.onUpdated.addListener(function(id,info,tab) {
                chrome.tabs.sendRequest(id, {greeting: "hello"});
                });


                А в файле script.js его принимаем
                chrome.extension.onRequest.addListener(
                function(request, sender) {
                alert(request);
                });


                Почему-то не работает.

                Подскажите, пожалуйста, что неправильно.
                Заранее спасибо!
                  0
                  Спасибо большое, статья очень помогла мне разобраться в этом деле)
                    0
                    dropbox 404

                    Only users with full accounts can post comments. Log in, please.