Архив рубрики: JavaScript

Установка в Linux Mint 18.3 различных инструментов, используемых для программирования

Мне не нравится современный пользовательский интерфейс Linux Ubuntu, но в то же время мне нравится более классический интерфейс Linux Mint 18.3, построенной (согласно официальной информации) на базе Ubuntu 16.04. Поэтому на моём ноутбуке установлен Linux Mint 18.3.

Поскольку меня интересует программирование на языках C, C#, JavaScript и Java, то в данной заметке я размещаю краткую шпаргалку о том, какой софт можно установить в Linux Mint 18.3 для возможности разработки софта с использованием упомянутых выше языков программирования.

GCC

В качестве компилятора для языка C я конечно же предпочитаю использовать gcc. К сожалению, по умолчанию, в Linux Mint 18.3 установлена весьма старая (5-я) версия этого компилятора. Информацию о версии установленной у вас версии gcc всегда можно получить так:

gcc --version

Инструкцию о том, как можно обновить gcc можно найти здесь. Последовательно выполнив все обозначенные в ней действия, мне без труда удалось успешно обновить gcc до наиболее свежей на сегодняшний день версии (7-й).

На всякий случай дублирую содержимое ссылки, дабы в случае удаления кем-либо указанной выше информации она бы не была безвозвратно утеряна:

These commands are based on a askubuntu answer http://askubuntu.com/a/581497 and https://askubuntu.com/questions/26498/choose-gcc-and-g-version
To install gcc-7 (gcc-7.2.0), I had to do more stuff as shown below.
USE THOSE COMMANDS AT YOUR OWN RISK. I SHALL NOT BE RESPONSIBLE FOR ANYTHING.
ABSOLUTELY NO WARRANTY.

If you are still reading let's carry on with the code.

sudo apt-get update && \
sudo apt-get install build-essential software-properties-common -y && \
sudo add-apt-repository ppa:ubuntu-toolchain-r/test -y && \
sudo apt-get update && \
sudo apt-get install gcc-snapshot -y && \
sudo apt-get update && \
sudo apt-get install gcc-7 g++-7 gcc-6 g++-6 gcc-multilib -y && \
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-7 70 --slave /usr/bin/g++ g++ /usr/bin/g++-7 && \
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-6 60 --slave /usr/bin/g++ g++ /usr/bin/g++-6 && \
sudo apt-get install gcc-5 g++-5 -y && \
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-5 50 --slave /usr/bin/g++ g++ /usr/bin/g++-5;

When completed, you must change to the gcc you want to work with by default. Type in your terminal:
sudo update-alternatives --config gcc

To verify if it worked. Just type in your terminal
gcc -v

If everything went fine you should see gcc 7.2.0 by the time I am writing this gist

Happy coding!

See blog post at https://www.application2000.com

Помимо того, что gcc можно использовать непосредственно из консоли, его так же используют и различного рода IDE, например, используемый мною CLion компании JetBrains. Эта IDE позволяет писать на C90, С99 и даже С11, в отличии от  той жеVisual Studio, позволяющей писать только на  C90. 

VIM & IDE

В качестве текстового редактора я предпочитаю vim. Это приложение особенно полезно для тех, кто владеет слепой десятипальцевой печатью, которой можно обучиться, например, на сайте Владимира Шахиджаняна:  https://solo.nabiraem.ru/ - свои навыки я когда-то получил именно там.

Компания JetBrains предоставляет набор кроссплатформенных IDE для интересующих меня языков программирования:
Для каждого из этих редакторов присутствует возможность установить плагин IdeaVim, позволяющий эмулировать vim в качестве текстового редактора IDE. Т.о. навыки использования vim могут быть успешно использованы в используемых мною IDE.

Установка vim:

sudo apt-get install vim

Проверка установленной версии vim:

vim --version

Краткая обучающая инструкция по базовым основам использования vim:

vimtutor

По vim имеются хорошие книги, в т.ч. и на русском языке:
Установку софта из дистрибутивов, скачанных с сайта JetBrains, можно выполнять из консоли следующим образом:

sudo tar -xzf your.tar.gz -C /opt

В результате приложение будет установлено в каталог /opt.

Visual Studio Code

На официальном сайте Майкрософт можно скачать и установить самый свежий дистрибутив данного кроссплатформенного текстового редактора.

Среди доступных плагинов для этого текстового редактора так же имеется большой набор эмуляторов vim.

Atom

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

Для него так же можно скачать плагин эмуляции vim, как описано здесь:

apm install vim-mode-plus

Git

Вряд ли эта программа нуждается в представлении. На официальном сайте всегда можно скачать и установить самый свежий дистрибутив программы.

JDK

SDK для Java. На официальном сайте можно скачать и установить самую свежую версию JDK, необходимую для разработки кода на Java. Установленный JDK будет использоваться в IntelliJ Idea.

Node.js

Платформа, предоставляющая возможность разрабатывать приложения на JavaScript. С официального сайта устанавливать лучше LTS-версию. При установке Node.js автоматически будет установлен и менеджер пакетов NPM

NPM можно использовать не только для JavaScript, но и в проектах ASP.NET Core MVC 2.

.NET Core SDK

На официальном сайте Майкрософт присутствует подробная инструкция по установке самой свежей версии .NET Core SDK для Linux Ubuntu 16.04, на основе которой создан Linux Mint 18.3.

xUnit

Платформа для разработки модульных тестов для .NET и .NET Core. На официальном сайте даются ссылки на соответствующие NuGet и MyGet пакеты.

Хостинг ASP.NET Core 2 приложений в IIS

На тот случай, если своё web-приложение вы захотите хостить на IIS, компания Майкрософт опубликовала подробную инструкцию по данной теме. Особое внимание следует обратить на то, что на указанной странице, в разделе Установка пакета размещения .NET Core для Windows Server, указан пакет, который необходимо установить на сервере, чтобы IIS научился работать с вашим приложением.

Remmina

Это приложение удобно использовать в качестве RDP-клиента для удалённого подключения к компьютерам, работающим под управлением Windows.

VMWare Horizon Client

На официальном сайте присутствуют клиенты для различных операционных систем, в т.ч. и для Linux. Это приложение удобно использовать для удалённого подключения к различным виртуальным машинам, работающим под управлением VMWare.

MS SQL Server 2017

Всю необходимую информацию по теме можно найти на официальном сайте продукта.

PowerShell Core

Наличие возможности использовать PowerShell в Linux является весьма удобной  для тех, кто привык пользоваться этой командной оболочкой в Windows. Например, для тестирования контроллеров API в ASP.NET Core MVC 2 можно воспользоваться привычной командой Invoke-RestMethod.

Инструкция по установке - на официальном сайте здесь.

UPD
Ниже написал небольшой скрипт, с помощью которого оперативно установил интересующий меня набор приложений:

#!/bin/bash
# (c) Андрей Бушман, 2018
# Данный скрипт устанавливает набор необходимого мне программного обеспечения.

sudo apt-get update
sudo apt-get upgrade

# Remmina
sudo apt-add-repository ppa:remmina-ppa-team/remmina-next
sudo apt-get update
sudo apt-get install remmina remmina-plugin-rdp remmina-plugin-secret

# VMware Horizon Client v4.7.0-7395152
wget -O ./vmware.x64.bundle https://download3.vmware.com/software/view/viewclients/CART18FQ4/VMware-Horizon-Client-4.7.0-7395152.x64.bundle
chmod +x ./vmware.x64.bundle
sudo ./vmware.x64.bundle

# Skype
wget -O skype.deb https://go.skype.com/skypeforlinux-64.deb
sudo dpkg --install ./skype.deb

# Git
sudo add-apt-repository ppa:git-core/ppa
sudo apt update; apt install git

git config --global user.name "Andrey Bushman"
git config --global user.email bushman.andrey@gmail.com
git config --global core.editor "vim"

# Google Chrome
wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
sudo dpkg -i google-chrome-stable_current_amd64.deb

# GCC v7.3.0
sudo apt-get update && \
sudo apt-get install build-essential software-properties-common -y && \
sudo add-apt-repository ppa:ubuntu-toolchain-r/test -y && \
sudo apt-get update && \
sudo apt-get install gcc-snapshot -y && \
sudo apt-get update && \
sudo apt-get install gcc-7 g++-7 gcc-6 g++-6 gcc-multilib -y && \
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-7 70 --slave /usr/bin/g++ g++ /usr/bin/g++-7 && \
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-6 60 --slave /usr/bin/g++ g++ /usr/bin/g++-6 && \
sudo apt-get install gcc-5 g++-5 -y && \
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-5 50 --slave /usr/bin/g++ g++ /usr/bin/g++-5;
sudo update-alternatives --config gcc

# VIM
git clone https://github.com/vim/vim.git
cd vim
git pull
cd vim/src
sudo apt-get install libncurses5-dev libncursesw5-dev
# make distclean  # if you build Vim before
make
sudo make install

# .Net Core 2.1.200 SDK
wget -q packages-microsoft-prod.deb https://packages.microsoft.com/config/ubuntu/16.04/packages-microsoft-prod.deb
sudo dpkg -i packages-microsoft-prod.deb

sudo apt-get install apt-transport-https
sudo apt-get update
sudo apt-get install dotnet-sdk-2.1.200

# Visual Studio Code
curl https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor > microsoft.gpg
sudo mv microsoft.gpg /etc/apt/trusted.gpg.d/microsoft.gpg
sudo sh -c 'echo "deb [arch=amd64] https://packages.microsoft.com/repos/vscode stable main" > /etc/apt/sources.list.d/vscode.list'
sudo apt-get update
sudo apt-get install code # or code-insiders

# Sublime Text 3
wget -qO - https://download.sublimetext.com/sublimehq-pub.gpg | sudo apt-key add -
sudo apt-get install apt-transport-https
echo "deb https://download.sublimetext.com/ apt/stable/" | sudo tee /etc/apt/sources.list.d/sublime-text.list
sudo apt-get update
sudo apt-get install sublime-text


# Atom
curl -L https://packagecloud.io/AtomEditor/atom/gpgkey | sudo apt-key add -
sudo sh -c 'echo "deb [arch=amd64] https://packagecloud.io/AtomEditor/atom/any/ any main" > /etc/apt/sources.list.d/atom.list'
sudo apt-get update
sudo apt-get install atom

# JDK 10.0.1
wget https://download.java.net/java/GA/jdk10/10.0.1/fb4372174a714e6b8c52526dc134031e/10/openjdk-10.0.1_linux-x64_bin.tar.gz
sudo tar xvf openjdk-10.0.1_linux-x64_bin.tar.gz -C /opt

# NodeJS v8.11.1
wget -O node.tar.gz https://nodejs.org/dist/v8.11.1/node-v8.11.1-linux-x64.tar.xz
sudo tar xf ./node.tar.gz -C /opt

# JetBrains Clion 2018.1.2
wget -O ./CLion.tar.gz http://download.jetbrains.com/cpp/CLion-2018.1.2.tar.gz
sudo tar xvf CLion.tar.gz -C /opt

# JetBrains IntelliJ IDEA 2018.1.3
wget -O ./intellij.tar.gz http://download.jetbrains.com/idea/ideaIU-2018.1.3.tar.gz
sudo tar xfz ./intellij.tar.gz -C /opt

# JetBrains Rider 2018.1
wget -O ./rider.tar.gz http://download.jetbrains.com/rider/JetBrains.Rider-2018.1.tar.gz
sudo tar xfz ./rider.tar.gz -C /opt

# JetBrains WebStorm 2018.1.3
wget -O ./webstorm.tar.gz http://download.jetbrains.com/webstorm/WebStorm-2018.1.3.tar.gz
sudo tar xfz ./webstorm.tar.gz -C /opt

# JetBrains Pycharm 2018.1.2
wget -O ./pycharm.tar.gz http://download.jetbrains.com/python/pycharm-professional-2018.1.2.tar.gz
sudo tar xfz ./pycharm.tar.gz -C /opt

# JetBrains DataGrip 2018.1.2
wget -O ./datagrip.tar.gz http://download.jetbrains.com/datagrip/datagrip-2018.1.2.tar.gz
sudo tar xfz ./datagrip.tar.gz -C /opt

# JetBrains GoLand 2018.1.2
wget -O ./goland.tar.gz http://download.jetbrains.com/go/goland-2018.1.2.tar.gz
sudo tar xfz ./goland.tar.gz -C /opt

# Python v3.6.5
wget -O ./python.tar.xz https://www.python.org/ftp/python/3.6.5/Python-3.6.5.tar.xz
tar xf ./python.tar.xz
cd ./Python-3.6.5
./configure
make
make test
sudo make install

# MS SQL Server 2017
sudo curl https://packages.microsoft.com/keys/microsoft.asc | sudo apt-key add -
sudo add-apt-repository "$(curl https://packages.microsoft.com/config/ubuntu/16.04/mssql-server-2017.list)"
sudo apt-get update
wget -qO- https://packages.microsoft.com/keys/microsoft.asc | sudo apt-key add -
sudo add-apt-repository "$(wget -qO- https://packages.microsoft.com/config/ubuntu/16.04/mssql-server-2017.list)"
sudo apt-get update
sudo apt-get install -y mssql-server
sudo /opt/mssql/bin/mssql-conf setup

systemctl status mssql-server 

# MS SQL Server Tools
curl https://packages.microsoft.com/keys/microsoft.asc | sudo apt-key add -
curl https://packages.microsoft.com/config/ubuntu/16.04/prod.list | sudo tee /etc/apt/sources.list.d/msprod.list
sudo apt-get update 
sudo apt-get install mssql-tools unixodbc-dev

sudo apt-get update 
sudo apt-get install mssql-tools

echo 'export PATH="$PATH:/opt/mssql-tools/bin"' >> ~/.bash_profile

echo 'export PATH="$PATH:/opt/mssql-tools/bin"' >> ~/.bashrc
source ~/.bashrc

# Docker CE 17.03
sudo apt-get update
sudo apt-get install -y apt-transport-https ca-certificates curl software-properties-common
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
sudo add-apt-repository "deb https://download.docker.com/linux/$(. /etc/os-release; echo "") $(lsb_release -cs) stable"
sudo apt-get update && sudo apt-get install -y docker-ce=$(apt-cache madison docker-ce | sudo grep 17.03 | sudo head -1 | sudo awk '{print }')

# Kibernetes
sudo apt-get update && sudo apt-get install -y apt-transport-https curl
curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add -
cat <<EOF >/etc/apt/sources.list.d/kubernetes.list
deb http://apt.kubernetes.io/ kubernetes-xenial main
EOF
sudo apt-get update
sudo apt-get install -y kubelet kubeadm kubectl

sudo docker info | sudo grep -i cgroup
cat /etc/systemd/system/kubelet.service.d/10-kubeadm.conf
sudo sed -i "s/cgroup-driver=systemd/cgroup-driver=cgroupfs/g" /etc/systemd/system/kubelet.service.d/10-kubeadm.conf

systemctl daemon-reload
systemctl restart kubelet

# PowerShell Core
sudo apt-get install -y powershell


Приведение к нужному типу объекта, полученного из JSON

Комментарий в коде показывает место, в котором порой допускают ошибку в процессе решения обозначенной задачи.


// JS6
class Foo{
constructor(name,surname){
this.name=name;
this.surname=surname;
};
  fullName(){
  return this.name + ' ' + this.surname;
  };
};

let foo = new Foo('John', 'Smith');
console.log(foo.fullName());

let json = JSON.stringify(foo);
let _foo = JSON.parse(json);

/* ВНИМАНИЕ! Прототип экземпляров класса хранится в его свойстве prototype.
 * Т.е. пытаться получить нужный прототип через Object.getPrototypeOf() в данном
 * случае было бы неправильно. */
Object.setPrototypeOf(_foo, Foo.prototype);

console.log(_foo.fullName());

FS: pages v.2.2

 

Опис:
Можливо часто ви стикалися з такою проблемою, коли на сайті є поодинокі сторінки (рубрики «Інфо», «Контакти», «Про себе»), при цьому зміна такої сторінки супроводжувалося редагуванням файлу вручну. Це, виснажливо і довго.
FS: pages v2.2 замінює весь цей процес. Створювати і змінювати сторінки можна прямо з адмінки скрипта. Для більш зручної роботи доступне використання html-тегів.

Новости сайта:best SEO

Відмінності від попередньої версії [SM (about) v 2.0]:
— Створення необмеженої кількості сторінок.
— Новий дизайн адмін сторінки.
— Предосмотр сторінки перед додаванням або зміною.
— Вставка смайлів і коду після курсору.
— Використання шаблону сторінки (однаковий шаблон для всіх сторінок).
— Видалення декількох сторінок.
— Змінений набір смайлів.

Завантажити

Як зробити зміну (ротацію) зображень на Java Script?


У даній статті розберемо приклад роботи ротації зображень, написаної на Java Script. Рішення досить-таки популярне. Найчастіше використовується як слайдер зображень у верхній частині сайту, прокрутка рекламних банерів або показ слайдів фотогалереї.

Сам скрипт ротації на Java Script вставляємо між тегами <head> і </ head>

Код JavaScript

<script type="text/javascript"><br >
  var array = new Array(<br >
    "images/photo001.jpg",<br >
    "images/photo002.jpg",<br >
  ); //массив с путями к картинкам. Можно по аналогии добавить изображения<br >
  var delay = 2000; // время задержки одного изображения в миллисекундах = 2 сек<br >
  function imageRotation(i) {<br >
    var image = document.getElementById("rotator");<br >
    image.src = array[i];<br >
    i++;<br >
    if (i == array.length) i = 0;<br >
    setTimeout("imageRotation(" + i + ")", delay);<br >
  }<br >
</script>

Вибираємо об’єкт в тілі документа, який повинен стояти між тегами <body> і </ body> і присвоюємо йому id rotator. Візьмемо за приклад зображення і тег <img>

Дивіться також:овощерезка nicer dicer plus

Код HTML

<p><br >
  <img id="rotator" src="" alt="Ротация изображений" width="150" height="150" /><br >
</p>

Спасибі за увагу!

Як стиснути або закодувати JavaScript? Онлайн сервіси


Навіщо потрібно стискати і кодувати код? Стискати слід для зниження ваги файлу або скорочення займаного місця. Що стосується кодування, то це потрібно для тих, хто хоче скористатися Вашим готовим кодом без згоди або як мінімум слова спасибі) Шифровка робить його нечитабельним.
Таким чином, до речі, можна закодувати не тільки код Java Script, але ще і CSS. Перейдемо до сервісів.

Список онлайн сервісів для стиснення та кодування JS

1. JS Crunch
http://www.cfoster.net/jscrunch/
Автор: Charles Foster
Оптимальне шифрування і стиснення. Підтримує UTF-8. З мінусів: не виводить ніякої інформації про виконану роботу — коефіцієнт стиснення, вихідні, кінцеві розміри. Не завжди видає робочий зашифрований код, тому перевіряйте.

2. JavaScript Compressor
http://javascriptcompressor.com/
Автор: Dean Edwards
Онлайн компресор. Надає можливість закодувати код декількома способами.

3. CSS & amp; JavaScript Compressor
http://www.creativyst.com/Prod/3/
Онлайн компресор. Досить дубовий, без будь-яких наворотів, вичищає зайві прогалини, табуляцію і порожні переклади рядків.

Увага! Доведено багатьма життєвими прикладами: завжди зберігайте оригінал коду і перевіряйте працездатність нового стисненого закодованого коду.

Смотрите также:битсы наушники

Так, є сучасні форми стиснення і зчитування: прочитайте про gzip. Однак, не слід забувати про приватних невеликих прикладах, де сервіси можуть стати в нагоді. І як можна частіше займайтеся чищенням і оптимізацією коду. Особливо при високих навантаженнях на сервіс!)

Пишем расширение для Mozilla Firefox

В прошлой статье я рассказывал, как написать расширение для браузера Google Chrome. Моя попытка перейти с Mozilla Firefox на Google Chrome, в силу моих личных пристрастий, не увенчалась успехом, поэтому я вернулся на свой любимый браузер. Итак, в этой статье я постараюсь рассказать о том, как написать add-on для Mozilla Firefox.
Для написания дополнений Firefox существуют два подхода: использование XUL и новый, рекомендуемый подход, с использованием легковесного JavaScript SDK.
В этой статье речь пойдёт об использовании второго подхода -- JavaScript SDK. Сразу же приведу ссылки на tutorial`ы и guide`ы, где Вы сможете найти много полезной информации.
По заверениям Mozilla, новые API имеют ряд преимуществ:
  • простота в использовании высокоуровневого API;
  • Mozilla обещает обратную совместимость для всех будущих версий API;
  • тот же API будет использоваться в мобильной версии браузера;
  • повышенная безопасность;
  • использование опыта наработок Mozilla для повышения юзабилити;
  • больше нет необходимости перезапускать браузер после установки дополнения.
Однако, у XUL подхода есть свои собственные плюсы:
  • поддержка локализации;
  • прямой доступ XPCOM;
  • расширяемый пользовательский интерфейс.
Нужно отметить, что SDK поддерживает базовую локализацию, а доступ к XPCOM может осуществляться через низкоуровневый API.
Итак, я надеюсь вернуться к XUL как-нибудь в другой раз, а сейчас приступим к написанию расширения.

Сегодняшнее расширение будет помечать просмотренные видео на youtube`е.

SDK

Для того, чтобы писать расширения для Firefox нам понадобится пакет SDK, который можно скачать здесь. Кроме того, Вы можете воспользоваться онлайн IDE -- Add-on Builder. На видео ниже приведена демонстрация Add-on Builder`а.
Я буду пользоваться скачанным пакетом SDK. После загрузки SDK Вам нужно распаковать архив в удобное место. В архиве, по большому счёту, нас интересует только файл bin/cfx (для пользователей UNIX-подобных ОС) или bincfx.bat для пользователей MS Windows. Я пользуюсь операционной системой Debian GNU/Linux. Я создал символьную ссылку на файл cfx в каталоге /usr/bin, чтобы не писать полный путь к скачанной папке.
Для того, чтобы ознакомиться со справкой программы cfx нужно запустить её без параметров (из консоли). Нас интересует секция
Supported Commands:
docs - view web-based documentation
init - create a sample addon in an empty directory
test - run tests
run - run program
xpi - generate an xpi
Здесь перечислены допустимые команды. Для того, чтобы создать заготовку проекта нужно запустить cfx с параметром init:
$ cfx init
* lib directory created
* data directory created
* test directory created
* doc directory created
* README.md written
* package.json written
* test/test-main.js written
* lib/main.js written
* doc/main.md written

Your sample add-on is now ready.
Do "cfx test" to test it and "cfx run" to try it. Have fun!

В текущем каталоге будет создана структура пакета дополнения:
youtubemarker
├── data
├── doc
│   └── main.md
├── lib
│   └── main.js
├── package.json
├── README.md
└── test
└── test-main.js
Сейчас нас интересуют два файла: package.json и lib/main.js. В первом файле сосредоточена информация о нашем дополнении
{
"name": "youtubemarker",
"fullName": "youtubemarker",
"description": "a basic add-on",
"author": "",
"license": "MPL 2.0",
"version": "0.1"
}
Мы можем вписать свои данные и дополнить объект необходимыми полями. Спецификация объекта приведена здесь.
Поле id будет создано при первом запуске нашего add-on`а.
$ cfx run
No 'id' in package.json: creating a new ID for you.
package.json modified: please re-run 'cfx run'
{
"name": "youtubemark",
"license": "MPL 2.0",
"author": "brainstream",
"version": "0.1",
"fullName": "YouTube Marker",
"id": "jid1-02UvWxeWz56WUA",
"description": "Marks viewed videos"
}
Если при запуске cfx run программа не смогла найти Ваш Firefox, то путь к его исполняемому файлу нужно указать в опции -b, например так:
$ cfx run -b /opt/firefox/firefox
Кроме опции -b нам понадобится использовать опцию -p, которая задаёт профиль, с которым запускается Firefox. Дело в том, что по умолчанию, Firefox запускается каждый раз со временным профилем и вся сохранённая информация теряется. Запустив cfx следующим образом:
$ cfx run -p profile
мы создадим новый профиль в каталоге profile, который будет располагаться в текущем каталоге. Все последующие запуски
$ cfx run -p profile
будут использовать этот профиль. Сюда Вы можете поставить те дополнения, которые Вам нужны для работы с Вашим дополнением, здесь же будут храниться куки и все данные, которые мы сохраним в процессе работы нашего дополнения.

Встраиваемый скрипт

Прейдём непосредственно к коду дополнения. В add-on`ах Firexox используется два вида скриптов: код в дополнении и встраиваемый в страницу код. Различия между ними состоят в доступе к тем или иным API.
Начнём со скрипта, который будет работать на странице youtube`а:
youTubeMarker = {
start: function() {
youTubeMarker.runListing();
youTubeMarker.processPage();
},

runListing: function() {
self.on("message", function(message) {
if(message == null) return;
switch(message.type) {
case "mark":
youTubeMarker.markElement(message.id);
break;
}
});
},

processPage: function() {
if(document.getElementById("watch-video-container") != null) {
youTubeMarker.processWatch();
} else if(document.getElementById("feed") != null) {
youTubeMarker.processPane("feed-item-container", "feed-video-title");
} else if(document.getElementById("browse-main-column") != null) {
youTubeMarker.processPane("browse-item", "yt-uix-sessionlink");
} else if(document.getElementById("search-main") != null) {
youTubeMarker.processPane("result-item-video", "yt-uix-sessionlink");
}
},

processWatch: function() {
self.postMessage({
type: "watch",
url: document.URL
});
},

processPane: function(itemClass, linkClass) {
var items = document.getElementsByClassName(itemClass);
for(var i in items) {
var links = items[i].getElementsByClassName(linkClass);
if(links.length > 0) {
var url = links[0].getAttribute("href");
var id = "youtube-marker-" + youTubeMarker.lastId++;
items[i].id = id;
youTubeMarker.requestMarking(url, id);
}
}
},

lastId: 0,

requestMarking: function(url, id) {
self.postMessage({
type: "mark",
url: url,
id: id
});
},

markElement: function(id) {
var element = document.getElementById(id);
if(element == null) return;
element.style.opacity = 0.4;
}
};

youTubeMarker.start();
Сохраним этот скрипт под именем content.js в каталоге data нашего add-on`а. В этом коде есть всего две особенности, которые выделятся из обычного JavaScript кода: использование методов self.on и self.postMessage. Эти методы используются для коммуникации между скриптом дополнения и встраиваемым скриптом. Метод on принимает первым параметром имя события, а вторым -- обработчик. Метод postMessage отсылает скрипту дополнения JSON объект. К сожалению, более подробной документации по этим методам и объекту self мне найти не удалось.
Итак, скрипт запускается методом start, который организует подписку на сообщения от add-on`а и запускает парсинг страницы. Если страница является страницой просмотра видео, то скрипт отправляет сообщение о том, что видео просмотрено. Если же страница является списком (главная страница, обзор видео или результаты поиска), то для каждого контейнера устанавливается ID и отсылается URL видео с этим ID дополнению с запросом на пометку. Если запрос возвращается, то видео помечается полупрозрачностью.

Основной скрипт

Перейдём к скрипту main.js, основному скрипту дополнения.
var pageMod = require("page-mod");
var data = require("self").data;
var simpleStorage = require("simple-storage");
var querystring = require("api-utils/querystring");

pageMod.PageMod({
include: ["*.youtube.com"],
contentScriptFile: data.url("content.js"),
contentScriptWhen: "end",
onAttach: function(worker) {
worker.on("message", function(message) {
processMessage(worker, message);
});
}
});

function processMessage(worker, message) {
if(message == null) return;
switch(message.type) {
case "mark":
if(!isVideoWatched(message.url)) return;
worker.postMessage({
type: "mark",
id: message.id,
});
return;
case "watch":
saveWatchedUrl(message.url);
return;
}
}

function unifyUrl(url) {
var query = querystring.parse(url.split("?")[1]);
return query.v;
}

function isVideoWatched(url) {
return simpleStorage.storage[unifyUrl(url)] === true;
}

function saveWatchedUrl(url) {
simpleStorage.storage[unifyUrl(url)] = true;
}
Этот файл представляет куда больший интерес. В начале файла мы получаем ссылки на необходимые модули путём вызова функции require. Этой функции передаётся имя модуля высокоуровневого или низкоуровневого API.
Для хранения данных, переданных из встраиваемого скрипта, используется модуль simple-storage, который позволяет хранить данные точно так же, как и localStorage в DOM API. Мы вычленяем из URL идентификатор видео и сохраняем булеву переменную с этим именем в storage.
Объект PageMod из модуля page-mod позволяет запускать скрипты на страницах. Этот объект содержит шаблоны URL (include), на которых следует выполнять инъекцию, имя файла скрипта (contentScriptFile) или сам скрипт (contentScript). Кроме того, можно указать, в какой момент времени нужно запустить скрипт в опции contentScriptWhen, которая может принимать следующие значения:
  • "start": скрипт выполнится сразу же, как элемент документа будет вставлен в DOM;
  • "ready": скрипт будет выполнен после полной загрузки DOM;
  • "end": запуск скрипта произойдёт после загрузки всего контента (DOM, JavaScript`ы, таблицы стилей и картинки).
Так же объект позволяет подписаться на события, которые происходят при аттаче скрипта и при ошибке. В примере приведён первый случай. На обработчике onAttach мы запускаем прослушивание сообщений от встроенного скрипта. В сообщении о подключении скрипта нам приходит объект Page модуля page-worker, через который возможна коммуникация со встроенным скриптом. Здесь нам доступны методы on и postMessage полностью идентичные тем, что мы использовали во встраиваемом скрипте из объекта self.
Адрес скрипта, хранящегося в каталоге data можно получить, использовав объект data из модуля self. Метод data.url() вернёт ссылку, которую мы вставляем в свойство contentScriptFile объекта PageMod.
Низкоуровневый модуль api-utils/querystring позволяет распарсить строку запроса в URL страницы.

Теперь наше дополнение готово к первому запуску. Запустите
$ cfx run -p profile
и посмотрите на youtube.com пару видео с вашего фида или из обзора. Вернитесь на страницу со списком и увидите, что просмотренные видео полупрозрачны.

Добавляем интерактивность

Add-on работает. Теперь нам хочется интерактивности. Давайте добавим пункт в контекстное меню, который будет отображаться при правом щелчке на блок с видео в списках.
Прежде чем приступить к описанию процесса создания меню, необходимо провести небольшой рефакторинг уже написанного кода: вынесем переменную worker из обработчика события onAttach в глобальную область видимости.
var globalWorker;

pageMod.PageMod({
include: ["*.youtube.com"],
contentScriptFile: data.url("content.js"),
contentScriptWhen: "end",
onAttach: function(worker) {
globalWorker = worker;
worker.on("message", function(message) {
processMessage(message);
});
}
});

function processMessage(message) {
if(message == null) return;
switch(message.type) {
case "mark":
if(!isVideoWatched(message.url)) return;
globalWorker.postMessage({
type: "mark",
id: message.id,
});
return;
case "watch":
saveWatchedUrl(message.url);
return;
}
}
Строки, в которые нужно внести правки, выделены полужирным шрифтом.

Теперь добавим пункт меню.
var menu = require("context-menu");
function createMenu() {
var containers = [
"feed-item-container",
"browse-item",
"result-item-video"
];
for(var i in containers) {
menu.Item({
label: "Mark/unmark as viewd",
context: [
menu.URLContext("*.youtube.com"),
menu.SelectorContext("." + containers[i] + " *")
],
data: containers[i],
contentScriptFile: data.url("markmenu.js"),
onMessage: function (message) {
processMenuMessage(message);
}
});
}
}

createMenu();

function processMenuMessage(message) {
if(message == null) return;
switch(message.type) {
case "mark":
saveWatchedUrl(message.url);
break;
case "unmark":
removeWatchedUrl(message.url);
break;
}
globalWorker.postMessage({
type: message.type,
id : message.id,
});
}
Кроме меню, нам понадобится функция для удаления записей из simpleStorage. Для того, чтобы удалить запись из simpleStorage достаточно применить к этой записи оператор delete.
function removeWatchedUrl(url) {
var unifiedUrl = unifyUrl(url);
if(simpleStorage.storage[unifiedUrl] === true) {
delete simpleStorage.storage[unifiedUrl];
}
}
Итак, наш пункт меню будет переключать состояние элемента с просмотренного на не просмотренное и наоборот. Для создания контекстных меню используется модуль context-menu. Мы хотим добавить один пункт меню, поэтому будем использовать объект класса Item. Кроме того, можно добавлять собственные вложенные меню, используя класс Menu. Из-за того, что мы имеем три разных списка, на элементах которых хотим отображать наш пункт меню, нам придётся создать три почти одинаковых объекта. Через свойство label задаётся отображаемый текст. Огромный интерес же, для нас, представляет свойство context. Это свойство задаёт один или более (как в нашем случае) контекст, для которого отображается пункт меню. Для задания контекста должны использоваться следующие функции модуля context-menu:
  • PageContext() не задаёт ограничений, меню будет отображаться на всех элементах страницы;
  • SelectionContext() отобразит меню, если пользователь сделал выделение;
  • SelectorContext(selector) даёт возможность задать CSS селектор тех эелементов, на которых будет отображаться меню;
  • URLContext(matchPattern) задаёт шаблон URL страниц, на которых отображается меню.
В опцию context можно передать либо один из указанных контекстов, либо их массив. Если передать массив контектов, то отображаться меню будет только в случае удовлетворения всем котекстам сразу.
В нашем случае нужно передать два контекста: первый -- контекст на ограничение URL, а второй -- на HTML элементы, которые находятся в контейнерах-элементах списков.
В поле data помещаются данные, которые будут переданы обработчику нажатия меню.
Обработчик onMessage принимает сообщение подобное  тому, что приходит из встраиваемого скрипта, с той лишь разницей, что поле type принимает два значения: "mark" и "unmark".
Для того, чтобы снять метку, нужно немного дописать метод markElement в файле content.js:
markElement: function(id, mark) {
var element = document.getElementById(id);
if(element == null) return;
element.style.opacity = mark === true ? 0.4 : 1.0;
}
Мы добавили флаг того, нужно ли поставить метку или снять её. С той же целью допишем метод runListing
runListing: function() {
self.on("message", function(message) {
if(message == null) return;
switch(message.type) {
case "mark":
youTubeMarker.markElement(message.id, true);
break;
case "unmark":
youTubeMarker.markElement(message.id, false);
break;
}
});
},
При создании пункта меню, в поле contentScriptFile мы указали новый файл -- markmenu.js. Давайте взглянем на него.
youTubeMarkerMarkMenu = {
start: function() {
self.on("click", function(source, data) {
for(var element = source; element != null; element = element.parentElement) {
if(element.className.indexOf(data) < 0) continue;
var url = youTubeMarkerMarkMenu.findUrl(data, element);
if(url == null) return;
self.postMessage({
type: element.style.opacity == 0.4 ? "unmark" : "mark",
id: element.id,
url: url
});
return;
}
});
},

findUrl: function(className, container) {
var linkclass = null;
switch(className) {
case "feed-item-container":
linkclass = "feed-video-title";
break;
case "browse-item":
case "result-item-video":
linkclass = "yt-uix-sessionlink";
break;
default:
return null;
}
var links = container.getElementsByClassName(linkclass);
if(links.length < 1) return null;
return links[0].getAttribute("href");
}
};

youTubeMarkerMarkMenu.start();
Здесь мы видим уже знакомый нам метод self.on. Теперь он принимает сообщение click и два параметра: элемент, на котором произошёл клик и данные, записанные в параметр data при создании элемента меню. В эти данные мы записали класс контейнера, который необходимо найти. С помощью цикла ищем этот контейнер вверх по дереву. Найдя элемент, получаем URL видео и отправляем сообщение основному скрипту. Что происходит дальше Вы уже видели.

Создание пакета

После того, как Ваш add-on закончен, Вы можете создать *.xpi файл для установки его в браузер. Для этого введите команду
$ cfx xpi
и в Вашем рабочем каталоге появится файл с расширением xpi. Для команды
$ cfx xpi
так же можно использовать опцию -b, если cfx не смогла найти Вашего браузера.

Заключение

В этой статье я показал лишь очень малую часть того, что можно сделать с помощью Add-on SDK. Но надеюсь этого будет достаточно для того, чтобы понять общий подход к написанию дополнений для Mozilla Firefox.
Исходные тексты примера можно скачать отсюда.

Пишем расширение для Google Chrome

Как и любой современный web обозреватель, Google Chrome поддерживает расширения. В этой статье я покажу общий подход к написанию extension`ов, после чего Вы без труда напишете свой собственный плагин.
Пример, который я буду показывать, будет называться GoogleMark и его назначением будет метка результатов поиска в google,как показано на скриншоте ниже.
Дабы не утомлять тех, кому достаточно прочесть документацию, приведу сразу на неё ссылку. Там Вы найдёте краткий обзор и руководство по быстрому старту. Но, хочу предостеречь читателя о том, что этих вводных данных не достаточно для комфортной работы. Информация о необходимых мелочах раскидана по всей документации. В этой статье я постараюсь дать ссылки на самые важные части документации.
Итак, расширения для Google Chrome пишутся на JavaScript. Все файлы расширения должны лежать в одном каталоге. Я буду называть этот каталог каталогом расширения.
Для начала я приведу скрипт, который будет раскрашивать страничку результатов поиска google. В нём совсем немного специфичного для расширения кода, по большей части -- это простой JavaScript, который Вы используете при обработке любых web-страниц.
googleMarker = {
preparePage: function() {
var links = document.getElementsByClassName("l");
var count = links.length;
for(var i = 0; i < count; ++i) {
links[i].removeAttribute("onmousedown");
}
},

markUrl: function(url, color) {
var block = googleMarker.findMarkingBlockByUrl(url);
googleMarker.markBlock(block, color);
googleMarker.saveMarker(block, color);
},

restoreUrlMark: function(url, color) {
googleMarker.markBlock(googleMarker.findMarkingBlockByUrl(url), color);
},

findMarkingBlockByUrl: function(url) {
var listItems = document.getElementsByClassName("l");
var itemsCount = listItems.length;
for(var i = 0; i < itemsCount; ++i) {
var item = listItems[i];
if(item.href == url) {
return googleMarker.findMarkingBlockByNestedElement(item);
}
}
return null;
},

findMarkingBlockByNestedElement: function(element) {
if(element == null || element == 'undefined') {
return null;
}
if(element.className == 'r') {
return element;
}
return googleMarker.findMarkingBlockByNestedElement(element.parentNode);
},

markBlock: function(block, color) {
if(block == null || block == 'undefined') {
return;
}
var image = googleMarker.getImage(color);
if(image == null) {
return;
}
block.insertBefore(image, block.firstChild);
},

getImage: function(color) {
var url = null;
switch(color) {
case "green":
url = chrome.extension.getURL("green.png");
break;
case "yellow":
url = chrome.extension.getURL("yellow.png");
break;
case "red":
url = chrome.extension.getURL("red.png");
break;
}
if(url == null) {
return null;
}
var image = new Image();
image.src = url;
return image;
},

saveMarker: function(block, color) {
var link = block.getElementsByClassName("l")[0];
var url = link.href;
chrome.extension.sendRequest( { color: color, url: url } );
}
}
На случай, если Google поменяет разметку страницы результатов, я приведу HTML одного из блока результатов, которые выдаются сейчас.
<div class="vsc" pved="0CBAQkgowAQ" bved="0CBEQkQo" sig="qmv">
<h3 class="r">
<a href="http://brainstream-dev.blogspot.com/2012/03/c-visual-studio-11.html" target="_blank" class="l">
<em>Ещё один блог о программировании</em>: Асинхронное <b>...</b></a>
</h3>
<div class="vspib" aria-label="Подробнее..." role="button" tabindex="0">
<div class="vspii">
<div class="vspiic"></div>
</div>
</div>
<div class="s">
<div class="f kv">
<cite>brainstream-dev.<b>blogspot</b>.com/2012/03/c-visual-studio-11.html</cite>
<span class="vshid">
<a href="http://webcache.googleusercontent.com/search?q=cache:wtBn88UhcVYJ:brainstream-dev.blogspot.com/2012/03/c-visual-studio-11.html+&amp;cd=2&amp;hl=ru&amp;ct=clnk&amp;gl=ru"
onmousedown="return rwt(this,'','','','2','AFQjCNHoJc76l2Yol_mH3A94wFMHV1Z5AA','t0iOo1MA-N91FmlNaAEdNQ','0CBMQIDAB',null,event)"
target="_blank">

Сохраненная&nbsp;копия
</a>
</span>
<button class="gbil esw eswd"
onclick="window.gbar&amp;&amp;gbar.pw&amp;&amp;gbar.pw.clk(this)"
onmouseover="window.gbar&amp;&amp;gbar.pw&amp;&amp;gbar.pw.hvr(this,google.time())"
g:entity="http://brainstream-dev.blogspot.com/2012/03/c-visual-studio-11.html"
g:undo="poS1" title="Рекомендовать эту страницу"
g:pingback="/gen_204?atyp=i&amp;ct=plusone&amp;cad=S1">

</button>
</div>
<div class="esc slp" id="poS1" style="display:none">
Вы уже поставили +1 этой странице.&nbsp;
<a href="#" class="fl">Отменить</a>
</div>
<span class="st">
<span class="f">4 мар 2012 – </span>
<em>Ещё один блог о программировании</em>.
Все статьи этого блога могут быть использованы в соответствии с лицензией GNU FDL.<br>
</span>
</div>
</div>
Добавлять картинки я буду слева от ссылки в блоке "h3" с классом "l". Теперь разберём скрипт чуть подробнее.
Функция preparePage нужна для того, чтобы убрать все обработчики "onmousedown" из ссылок, иначе ссылка изменится чудесным образом и скрипт её уже никогда не узнает.
markUrl и restoreUrlMark делают одно и то же, за исключением того, что markUrl сохраняет метку (об этом чуть позже).
Функция getImage получает картинки из расширения. Об этом тоже чуть позже.
Все остальные функции являются вспомогательными и просто работают с DOM документа. Сохраним файл со скриптом в каталог расширения под названием googleMarker.js.
Итак, я надеюсь, что с тем, как метки будут появляться на страничке, мы разобрались и я перейду непосредственно к теме статьи.

Манифест

Любое расширение Google Chrome должно содержать файл манифеста, определяющий структуру и права Вашего расширения. Манифест для нашего примера будет следующим:
{
"name": "GoogleMark",
"version": "1.0",
"manifest_version": 2,
"description": "Marks google search results",
"content_scripts": [
{
"matches": [
"*://*.google.com/*",
"*://*.google.ru/*"
]
,
"js": [
"googleMarker.js"
]
,
"css": [
"googleMarker.css"
]
}
]
,
"background": {
"page": "background.html"
}
,
"permissions": [
"tabs",
"webNavigation",
"contextMenus",
"*://*.google.com/*",
"*://*.google.ru/*"
]
,
"icons": {
"16": "menu-icon-16x16.png"
}
,
"web_accessible_resources": [
"green.png",
"yellow.png",
"red.png"
]
}
Как видно, манифест представляет собой JSON объект. Файл манифеста сохраняется в корневой каталог расширения и должен иметь имя "manifest.json". Разберём структуру объекта примера.
Поля "name", "version", "manifest_version" и "description" понятны без объяснений.
Ваше расширение может встраивать в страницы собственные JavaScript`ы и каскадные таблицы стилей. Всё это описывается в поле "content_scripts", которое представляет собой массив объектов с указанием списка файла JavaScript`ов и файлов CSS. Кроме указания файлов, можно указывать шаблоны URL тех сайтов, в которые данный контент должен быть встроен. Спецификацию шаблонов можно найти в документации.
Каждое расширение может иметь свою теневую страницу. Эта страница загружается при запуске дополнения и может не иметь никакой разметки, кроме подключения JavaScript`ов. Теневая страница указывается в поле "background".
Вы можете явно задать HTML страницу в параметре "page" или, если Вам не нужна страница, то она может быть сгенерирована автоматически, если Вы укажете список скриптов в параметре "scripts".
Права, которыми обладает расширение вообще и теневая страница в частности, перечисляются в поле "permissions". В это поле вносятся шаблоны URL, к которым есть доступ у скриптов и список разрешённых модулей API.
В поле "icons" перечислены иконки расширения разных размеров. В частности, иконка 16x16 будет использоваться в меню (см. скриншот в начале статьи).
В списке "web_accessible_resources" перечисляются те ресурсы дополнения, которые могут быть доступны из страницы браузера. В частности, те скрипты, которые определены в поле "content_scripts" могут получить только те ресурсы, которые здесь перечислены. Функция getImage, в скрипте, приведённом в начале статьи, загружает файлы "green.png", "yellow.png" и "red.png" используя функцию chrome.extension.getURL для получения URL на эти ресурсы. URL ресурсов дополнения выглядит так:
chrome-extension://[ID пакета дополнения]/[путь]
Полную спецификацию манифеста можно найти в документации.

Теневая страница

Непосредственно в теневой странице запрещено исполнение JavaScript политикой безопасности, поэтому код HTML страницы будет представлять из себя только подключение скриптовых файлов и структур HTML для хранения отмеченных страниц. Естественно, что такая организация сохранения высосана из пальца только для того, чтобы продемонстрировать работу с теневой страницей. Вот её разметка.
<html>
<head>
<script type="text/javascript" src="jquery-1.7.2.min.js"></script>
<script type="text/javascript" src="background.js"></script>
</head>
<body>
<ul id="green"></ul>
<ul id="yellow"></ul>
<ul id="red"></ul>
</body>
</html>
Как видно, на теневой странице мы можем использовать любые скрипты, в частности, я подключил jQuery.

Контекстное меню

Для работы с контекстным меню в Google Chrome API служет модуль contextMenus. Для его работы нужно добавить строку "contextMenus" в поле "permissions" манифеста. Добавим в файл background.js функцию для создания меню. Внимание, большинство API функций Google Chrome являются асинхронными и возвращают управление сразу после вызова. Для обработки результатов все асинхронные вызовы опционально принимают функцию обратного вызова.
function createMenu() {
var urls = [
"*://*.google.com/*",
"*://*.google.ru/*"
];

var root = chrome.contextMenus.create({
title: "Mark as",
contexts: [ "link" ],
documentUrlPatterns: urls
});

chrome.contextMenus.create({
title: "Green",
contexts: [ "link" ],
parentId: root,
documentUrlPatterns: urls,
onclick: function(info, tab) {
chrome.tabs.executeScript(tab.id, {
code: "googleMarker.markUrl('" + info.linkUrl + "', 'green')"
});
}
});

chrome.contextMenus.create({
title: "Yellow",
contexts: [ "link" ],
parentId: root,
documentUrlPatterns: urls,
onclick: function(info, tab) {
chrome.tabs.executeScript(tab.id, {
code: "googleMarker.markUrl('" + info.linkUrl + "', 'yellow')"
});
}
});

chrome.contextMenus.create({
title: "Red",
contexts: [ "link" ],
parentId: root,
documentUrlPatterns: urls,
onclick: function(info, tab) {
chrome.tabs.executeScript(tab.id, {
code: "googleMarker.markUrl('" + info.linkUrl + "', 'red')"
});
}
});
}

createMenu();
Всё элементарно. Вызывая функцию chrome.contextMenus.create и передавая в неё детальное описание, мы получаем новый пункт меню. Полное описание объекта, описывающего пункт меню можно посмотреть в документации к функции, а я опишу лишь самые основные.
В параметре documentUrlPatterns перечисляются шаблоны URL адресов, на которых пункт меню будет появляться.
В списке contexts перечисляются типы элементов, при правом щелчке на которые, будет появляться данный пункт меню. Доступные варианты такие: "all", "page", "frame", "selection", "link", "editable", "image", "video", "audio".
Параметр parentId является дескриптором родительского пункта меню. Каждый вызов функции chrome.contextMenus.create возвращает такой дескриптор.
Наиболее интересным, для нас, является поле onclick. Сюда помещается обработчик выбора пункта меню.

Вкладки

В функцию обратного вызова onclick элемента меню, помимо информации об элементе, для которого было вызвано контекстное меню, передаётся информация о вкладке, в которой отображается документ с указанным элементом.
Одной из самых полезных функций модуля tabs (не забудьте указать значение "tabs" в поле "permissions", в манифесте) является функция chrome.tabs.executeScript. Как ясно из названия, она позволяет выполнить скрипт в документе, который находится во вкладке, идентификатор которой передаётся первым параметром. Вторым параметром передаётся объект, содержащий код скрипта или имя файла со скриптом. Работу этой функции и демонстрируют обработчики onclick пунктов меню.

Запросы

Для синхронизации работы встраиваемых скриптов с теневой страницей Google Chrome API предоставляют возможность скриптам обмениваться информацией посредством запросов. Напомню, как выглядит функция сохранения метки в файле googleMarker.js
saveMarker: function(block, color) {
var link = block.getElementsByClassName("l")[0];
var url = link.href;
chrome.extension.sendRequest( { color: color, url: url } );
}
В этой функции отсылается запрос с объектом, описывающим маркер для сохранения. Вообще говоря, Вы можете передавать любой объект в качестве запроса в функцию chrome.extension.sendRequest. Кроме того, этой функции можно передать идентификатор дополнения и функцию прослушки ответов.
Для работы с функциями из модуля extension не требуется никаких указаний в манифесте.
Чтобы наше расширение смогло реагировать на запросы, следует подписаться на их прослушивание. В нашем случае это будет выгладить так:
chrome.extension.onRequest.addListener(
function(request, sender, sendResponse) {
var $list = $("#" + request.color);
if($list.lengh == 0) {
return;
}
$list.append($("<li>").append(request.url));
}
);
Параметры функции обработки события chrome.extension.onRequest довольно очевидны; это запрос, отправитель запроса (идентификаторы вкладки и дополнения) и функция отправки ответа.
В нашем случае, мы, воспользовавшись jQuery, добавляем в теневую страницу новый элемент списка.

События навигации

Google Chrome API предоставляют возможность отслеживать события навигации с помощью модуля webNavigation. Для его использования следует добавить в Ваши "permissions" элемент "webNavigation" в файле манифеста. Модуль предоставляет несколько полезных событий и функций, я же продемонстрирую использование события chrome.webNavigation.onDOMContentLoaded, наступающего, как и следует из названия, после загрузки дерева элементов документа. Мы воспользуемся этим событием для подготовки страницы (удаления атрибута onmousedown) и восстановления сохранённых меток.
chrome.webNavigation.onDOMContentLoaded.addListener(
function(details) {
if(details.frameId != 0) {
return;
}
chrome.tabs.executeScript(details.tabId, {
code: "googleMarker.preparePage()"
}
);
var colors = ["green", "yellow", "red"];
for(var i = 0; i < colors.length; ++i) {
var $urls = $("#" + colors[i] + " li");
if($urls.length == 0) {
continue;
}

$urls.each(function() {
chrome.tabs.executeScript(details.tabId, {
code: "googleMarker.restoreUrlMark('" +
this.innerText + "', '" + colors[i] + "')"
}
);
});
}
}
);
В функцию обработки события приходит объект, содержащий идентификатор вкладки, идентификатор фрейма, URL и время окончания загрузки дерева. Идентификатор фрейма равен нулю, если дерево было построено в корневом документе вкладки, иначе -- это уникальный (в пределах вкладки) идентификатор фрейма HTML страницы. Всё остальное из фрагмента кода, приведённого чуть выше, нам уже знакомо.

Завершение

Последнее, что нам осталось сделать -- это добавить файлы googleMarker.css, menu-icon-16x16.png, green.png, yellow.png и red.png для отображения всех необходимых элементов.
.r img {
margin-right: 15px;
}
Теперь плагин готов к установке. Для этого переходим в Google Chrome в меню -> Tools -> Extesions и ставим галку на Developer mode. Нажимаем Load unpacked extension и выбираем каталог с расширением.
Теперь мы можем протестировать новое расширение и отладить его. Все встраиваемые скрипты доступны из стандартного инструментария (F12), а чтобы отладить скрипты теневой страницы достаточно нажать на соответствующую ссылку в менеджере дополнений.

Иконка и popup

Итак, убедившись, что всё работает, мы хотим добавить немного интерактивности в виде иконки расширения и всплывающего окна с кнопкой для очистки всех маркеров.
Для этого добавляем файл иконки (icon.png) и два файла для представления popup окна: popup.html и popup.js. Заносим новые сведения в файл манифеста, добавляя поле browser_action
{
"name": "GoogleMark",
"version": "1.0",
"manifest_version": 2,
"description": "Marks google search results",
"browser_action": {
"default_icon": "icon.png",
"default_popup": "popup.html"
}
,

"content_scripts": [
{
"matches": [
"*://*.google.com/*",
"*://*.google.ru/*"
]
,
"js": [
"googleMarker.js"
]
,
"css": [
"googleMarker.css"
]
}
]
,
"background": {
"page": "background.html"
}
,
"permissions": [
"tabs",
"webNavigation",
"contextMenus",
"*://*.google.com/*",
"*://*.google.ru/*"
]
,
"icons": {
"16": "menu-icon-16x16.png"
}
,
"web_accessible_resources": [
"green.png",
"yellow.png",
"red.png"
]
}
Разметка popup.html очень проста
<html>
<body>
<button id="clear-btn">Clear markers</button>
<script src="popup.js" type="text/javascript"></script>
</body>
</html>
Здесь только кнопка и подключение скрипта. Выполнение inline скриптов снова отключено по соображениям безопасности.
В файле popup.js мы подписываемся на нажатие кнопки и делаем запрос к фоновой странице.
var button = document.getElementById("clear-btn");
button.addEventListener("click", function() {
chrome.extension.sendRequest( { action: "clear" } );
});
Так как у нас появляется второе событие, то вводим поле action в запрос. По этой причине дописываем значение "save" в запросе на сохранение маркера (файл googleMarker.js)
saveMarker: function(block, color) {
var link = block.getElementsByClassName("l")[0];
var url = link.href;
chrome.extension.sendRequest( { action: "save", color: color, url: url } );
}
Осталось немного поправить обработчик запросов:
chrome.extension.onRequest.addListener(
function(request, sender, sendResponse) {
switch(request.action) {
case "save":
var $list = $("#" + request.color);
if($list.lengh == 0) {
return;
}
$list.append($("<li>").append(request.url));
break;
case "clear":
$("ul").empty();
break;
}
}
);
Всё готово! Жмём кнопку Reload в менеджере расширений и у нас появляется значок в верхнем правом углу браузера. Теперь Вы можете упаковать ваше расширение в файл *.crx нажав кнопку Pack extestion в менеджере расширений или залить его в Chrome Web Store.
Скачать исходный код примера можно здесь.