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 удалит все объявления, которые указаны в объекте.

Все

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

Документация, расширение.