Как стать автором
Обновить

Расширения для Google Chrome. Часть первая. Getting started

Время на прочтение7 мин
Количество просмотров60K
Добрый день, Хабр.

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

В первой (этой, то бишь) статье, на примере простейшего расширения, будут рассмотрены все основные моменты, связанные с разработкой, отладкой и использованием расширения, конфигурационный файл manifest.json и начала chrome.* API. Первая же статья, думаю, будет не очень полезна опытным разработчикам (это дисклеймер).

Hello world


Лучшая теория — практика, а поэтому, не откладывая в долгий ящик, создаём папку hello_world, а в ней текстовый документ manifest.json и печатаем туда следующий код:
{
	"name" : "Hello world", //Название расширения
	"version" : "1.0", //Версия
	"description" : "This is a simple chrome extention" //Краткое описание
}

Это — программа минимум. Если зайти в «Гаечный ключ → Инструменты → Расширения», установить галку «Режим разработчика», нажать кнопку «Загрузить распакованное расширение...» и указать нашу папку «Hello world», то расширение появится в списке установленных, но делать, естественно, оно пока ничего не будет, потому как не умеет.

image

Учим и отлаживаем


А раз не умеет — надо научить. Отредактируем manifest.json:
{
	"name" : "Hello world", 
	"version" : "1.0", 
	"description" : "This is a simple chrome extention",
	"background_page" : "background.html"
}

И создадим файл background.html в котором будет написан сценарий, выполняемый в фоновом режиме. Например, такой:
<script type="application/javascript">
	window.onload = function() {
		window.setInterval( function() {
			console.log("Hello world");
		}, 10000);
	}
</script>

Сценарий в background.html будет выполнен один раз, при старте браузера и расширения, то есть, при открытии ещё одной вкладки или окна, повторное исполнение сценария не произойдёт. В нашем случае он каждые 10 секунд будет писать в консоль сакраментальную фразу, и это, кстати, надо бы проверить.

Для отладки удобно использовать служебную страницу chrome://extensions/ со включённым режимом разработчика.

image

В принципе, она дублирует функционал страницы управления расширениями из «Гаечного ключа», но мне, субъективно, нравится больше. Как-то компактнее, что ли?

Здесь нас интересуют две позиции: строка «ID» с внутренним идентификатором расширения и подраздел «Проверить активные режимы просмотра» в котором мы видим созданный нами background.html и, щёлкнув по нему, можем проконтролировать исполняемый сценарий.

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

image

Обратите внимание на заголовок формата chrome-extension://ID/filename. Зная идентификатор расширения таким образом можно добраться до любого его файла. Опять же удобно в процессе отладки расширения.

Взаимодействие с браузером


Пока наше расширение представляет эдакую вещь в себе, исполняя в фоне некий сценарий. Для того, чтобы оно начало взаимодействовать с браузером и его компонентами нужно познакомиться с chrome.* API. Так, например, для взаимодействия с окном браузера используются методы chrome.browserAction, а значения по умолчанию задаются в manifest.json следующим образом:
{
	"name" : "Hello world", 
	"version" : "1.0", 
	"description" : "This is a simple chrome extention",
	"background_page" : "background.html",

	"browser_action" : {
		"default_title" : "Hello world!!!", //Текст, всплывающий при наведении курсора на иконку (если не задан, то всплывает название расширения)
		"default_icon" : "img/icon_world.png", //Иконка для панели расширений (по умолчанию)
		"default_popup" : "popup.html" //Всплывающее окно при клике на иконке
	}
}

Не забываем создать popup.html (пока оставим его пустым) и положить иконку в папку img, щёлкаем на «Перезагрузить» на странице chrome://extensions/ и смотрим на результат. Иконка нашего расширения появилась на панели расширений, а при клике на неё возникает пустое всплывающее окошко.

image

Иконка для тех, кто проходит по шагам:
image

Всё это управлябельно с помощью методов chrome.browserAction из сценариев:
<script type="text/javascript">
	chrome.browserAction.setTitle({title:"New title"}); //Устанавливает новый всплывающий при наведении на иконку текст
	chrome.browserAction.setPopup({popup:"new_popup.html"}); //Устанавливает новое всплывающее окно при клике на иконке
	chrome.browserAction.setIcon({path:"new_icon.png"}); //Устанавливает новую иконку
	chrome.browserAction.setBadgeText({text:"text"}); //Устанавливает текст поверх иконки
	chrome.browserAction.setBadgeBackgroundColor({color:[0,0,0]}); //Устанавливает фон текста поверх иконки
</script>


Для практике давайте заставим background.html сделать что-нибудь полезное, вместо того, чтобы просто гадить в консоль. Вот, хотя бы часы. Поверх иконки будет отображаться количество минут, при наведении — время в формате ЧЧ: ММ: СС, а во всплывающем окошке — часы со стрелками.

background.html
<script type="application/javascript">
	window.onload = function() {
		window.setInterval( function() {
			var now = new Date();
			var h = now.getHours();
			var m = now.getMinutes();
			var s = now.getSeconds();
			
			var badge_text = (m < 10 ? "0" + m : m).toString();
			var title_text = (h < 10 ? "0" + h : h) + ":" + (m < 10 ? "0" + m : m) + ":" + (s < 10 ? "0" + s : s);
			
			chrome.browserAction.setBadgeText({text: badge_text});
			chrome.browserAction.setTitle({title: title_text});
		}, 1000);
	}
</script>


popup.html
<!DOCTYPE html>
<html>
	<head>
		<style type="text/css">
			* { margin: 0; padding: 0; border: 0; }
			body { background: #000; }
		</style>
		<script type="text/javascript">
			Clock = function() {
				this.canvas = false;
				this.pi = Math.PI;
			}

			Clock.prototype = {

				get_time: function() {
					var now = new Date();
					var result = {
						milliseconds: now.getMilliseconds(),
						seconds: now.getSeconds(),
						minutes: now.getMinutes(),
						hours: now.getHours()
					}
					return result;
				},

				init: function() {
					this.canvas = document.getElementById("clock").getContext("2d");
				},
	
				draw: function() {		
					var now = this.get_time();
					var hangle = (this.pi/6)*now.hours + (this.pi/360)*now.minutes + (this.pi/21600)*now.seconds + (this.pi/21600000)*now.milliseconds;
					var mangle = (this.pi/30)*now.minutes + (this.pi/1800)*now.seconds + (this.pi/1800000)*now.milliseconds;
					var sangle = (this.pi/30)*now.seconds + (this.pi/30000)*now.milliseconds;
		
					this.canvas.save();
					this.canvas.fillStyle = "#000";
					this.canvas.strokeStyle = "#000";
					this.canvas.clearRect(0,0,200,200);
					this.canvas.fillRect(0,0,200,200);
					this.canvas.translate(100,100);
					this.canvas.rotate(-this.pi/2);		
		
					this.canvas.save();
					this.canvas.rotate(hangle);
					this.canvas.lineWidth = 8;
					this.canvas.strokeStyle = "#ffffff";
					this.canvas.fillStyle = "#ffffff";
					this.canvas.lineCap = "round";
					this.canvas.beginPath();
					this.canvas.moveTo(-10,0);
					this.canvas.lineTo(50,0);
					this.canvas.stroke();
					this.canvas.restore();

					this.canvas.save();
					this.canvas.rotate(mangle);
					this.canvas.lineWidth = 4;
					this.canvas.strokeStyle = "#ffffff";
					this.canvas.lineCap = "square";
					this.canvas.beginPath();
					this.canvas.moveTo(-20,0);
					this.canvas.lineTo(75,0);
					this.canvas.stroke();
					this.canvas.restore();

					this.canvas.save();
					this.canvas.lineWidth = 2;
					this.canvas.strokeStyle = "#ffffff";
					this.canvas.fillStyle = "#333";
					this.canvas.beginPath();
					this.canvas.arc(0,0,8,0,this.pi*2,true);
					this.canvas.fill();
					this.canvas.stroke();
					this.canvas.restore();		
		
					this.canvas.save();
					this.canvas.rotate(sangle);
					this.canvas.lineWidth = 2;
					this.canvas.strokeStyle = "#ff0000";
					this.canvas.lineCap = "square";
					this.canvas.beginPath();
					this.canvas.moveTo(-30,0);
					this.canvas.lineTo(85,0);
					this.canvas.stroke();
					this.canvas.restore();		

					this.canvas.save();
					this.canvas.lineWidth = 6;
					this.canvas.fillStyle = "#ff0000";
					this.canvas.beginPath();
					this.canvas.arc(0,0,3,0,this.pi*2,true);
					this.canvas.fill();
					this.canvas.restore();
		
					this.canvas.save();
					this.canvas.lineWidth = 6;
					this.canvas.strokeStyle = "#ffffff";
					this.canvas.beginPath();
					this.canvas.arc(0,0,95,0,this.pi*2,true);
					this.canvas.stroke();
					this.canvas.restore();
		
					this.canvas.restore();
				}
		
			}

			window.onload = function() {
				var clock = new Clock();
				clock.init();
				window.setInterval(function() {
					clock.draw();
				}, 10);
			}		
		</script>
	</head>
	<body>
		<canvas id="clock" width="200" height="200"></canvas>
	</body>
</html>


Сохраняем, перезапускаем, проверяем — красота!
image

Собственно, мы сделали простое расширение (а заодно и canvas припомнили). Для Getting Started, во всяком случае, достаточно. Осталось только привести его к годному для распространения виду — упаковать. Для этого на той же странице chrome://extensions/ давим на «Упаковка расширений...», указываем корневой каталог (тот, где лежит manifest.json), давим «Ок» и получаем файл формата *.crx на выходе. Это и есть наше упакованное расширение. Открыв его с помощью Хрома, мы установим расширение.

Упакованный пример из статьи для установки
Архив с исходниками

В следующей статье цикла я планирую подробно разобрать chrome.* API, а в дальнейшем — взаимодействие с различными сайтами и использование локальных хранилищ данных. Если вы считаете, что я что-то упустил в азах или у вас есть пожелания по поводу следующих статей цикла — прошу изложить их в комментариях.

See ya!
Теги:
Хабы:
Всего голосов 108: ↑95 и ↓13+82
Комментарии39

Публикации