14 November, 2009

Расширение Delicious Bookmarks для Google Chrome/Chromium

Введение


Решил научиться писать собственные расширения для Google Chrome/Chromium.

За идею взял официальное расширение от Yahoo! для Firefox - Delicious Bookmarks.

Структура расширения


Расширение - файл с расширением .crx. На самом деле это просто ZIP-архив с файлом манифеста внутри.

Файл манифеста

{
"name": "Delicious plugin", // 1
"version": "0.2", // 2
"background_page": "background.html", // 3
"permissions": [ // 4
"bookmarks",
"tabs"
],

"browser_action": { // 5
"name": "Save bookmark to delicious.com",
"default_title": "Save bookmark to delicious.com",
"default_icon": "delicious.20.gif" // optional
},

"content_scripts": [ // 6
{
"matches": ["http://*/*", "https://*/*"],
"js": ["getDocumentSelection.js"]
}
],

"options_page": "options.html" // 7
}

Манифест - файл в формате JSON.

Манифест состоит из следующих частей:

1. name - Имя расширения
2. version - Версия расширения
3. background_page - основной файл с расширением. Это обычный HTML-файл с разметкой.
4. permissions - разрешения для расширения. Указываем, что нам нужен доступ к вкладкам (tabs) и закладкам (bookmarks).
5. browser_action - указываем, что нужно отобразить на панели инструментов браузера.
6. content_scripts - Для доступа к DOM отображаемой страницы необходимы content scripts, данный блок регистрирует скрипт getDocumentSelection.js для всех http и https страниц.
7. options_page - страница с настройками.

Подробное описание файла манифеста есть на официальном сайте.

Описание работы расширения



Расширение работает следующим образом:
1. При клике на значок расширения вызывается обработчик addBookmark
2. Обработчик открывает коммуникационный порт и отправляет сообщение скрипту содержимого.
3. Скрипт содержимого забирает выделенный текст и отправляет назад расширению выделенный текст.
4. Расширение забирает выделенный текст, определяет настройки сохранения (Private/Public) и вызывает окно сохранения Delicious.

Реализация расширения



background.html:
<html>
<head>
<script type="text/javascript">
    function saveBookmark() {
        // Send our password to the current tab when clicked.
        chrome.tabs.getSelected(null, function(tab) {
            var port = chrome.tabs.connect(tab.id, {
                name : "deliciousBookmark"
            });
            port.postMessage( {
                action : 'getSelection'
            });
        });
    }

    function addBookmark(id, bookmark) {
        console.log("added bookmark:  " + bookmark);
        saveBookmark();
    }

    chrome.bookmarks.onCreated.addListener(addBookmark);
    console.log("Registered listener");

    function getShareStatus() {
        var markPrivate = localStorage["markPrivate"];
        var share = "yes";
        if (markPrivate == "true") {
            share = "no";
        }
        return share;
    }

    chrome.extension.onConnect.addListener(function(port) {
        console.assert(port.name == "deliciousBookmark");
        port.onMessage.addListener(function(msg) {
            var selection = msg.selection;
            chrome.tabs.getSelected(null, function(tab) {

                var url = encodeURIComponent(tab.url);

                if (!url || url === "") {
                    return;
                }

                var title = encodeURIComponent(tab.title);
                var description = encodeURIComponent(selection);

                var share = getShareStatus();

                var f = 'http://delicious.com/save?url=' + url + '&title=' + title + '&notes=' + description
                        + '&share=' + share + '&v=5&';
                window.open(f + 'noui=1&jump=doclose', 'deliciousuiv5',
                        'location=yes,links=no,scrollbars=no,toolbar=no,width=550,height=550');

            });
        });
    });

    chrome.browserAction.onClicked.addListener(saveBookmark);
</script>
</head>
</html>



getDocumentSelection.js

var port = chrome.extension.connect( {
name : "deliciousBookmark"
});

// Also listen for new channels from the extension for when the button is
// pressed.
chrome.extension.onConnect.addListener(function(port) {
console.assert(port.name == "deliciousBookmark");
port.onMessage.addListener(function(msg) {
if (msg.action == 'getSelection') {
var responsePort = chrome.extension.connect( {
name : "deliciousBookmark"
});
var description = document.getSelection() ? '' + document.getSelection() : '';
responsePort.postMessage( {
selection : description
});
}
});
});




options.html

<html>
<html>
<head>
<title>Delicious Bookmarks Options</title>
</head>
<script type="text/javascript">
    // Saves options to localStorage.
    function saveOptions() {
        var share = document.getElementById("share");
        localStorage["markPrivate"] = share.checked;

        // Update status to let user know options were saved.
        var status = document.getElementById("status");
        status.innerHTML = "Options Saved.";
        setTimeout(function() {
            status.innerHTML = "";
        }, 1500);
    }

    // Restores select box state to saved value from localStorage.
    function restoreOptions() {
        var share = localStorage["markPrivate"];

        if (!share) {
            return;
        }
        var shareCheckbox = document.getElementById("share");

        shareCheckbox.checked = share;
        
    }
</script>

<body onload="restoreOptions()">

<label for="share">Mark as Private</label>
<input type="checkbox" class="checkbox" name="share" id="share" />


<br>
<button onclick="saveOptions();">Save</button>
<div id="status">&nbsp;</div>
</body>
</html>


Сборка расширения


В Chromium есть возможность собрать расширение.
1. Открываем адрес chrome://extensions/
2. В правом верхнем углу нажимаем Developer Mode.
3. На появившейся панели инструментов кликаем Pack Extension
4. Выбираем каталог, в котором находится расширение
5. Chromium автоматически собирает расширение в каталоге с исходниками расширения.

Заключение


Писать расширения для Google Chrome не просто, а очень просто.

Готовое и собранное расширение можно скачать здесь.

Дополнительная информация


  1. Google Chrome Extensions Developer's Guide
  2. Manifest file description
  3. Chrome Extensions API
  4. Extension Packaging


В следующей заметке опишу работу messaging для коммуникации расширения со скриптами содержимого.

9 comments:

  1. Спасибо. Статья очень понравилась а расширением уже пользуюсь ;)

    ReplyDelete
  2. @Denis
    На здоровье. О найденных ошибках и своих предложениях можешь сообщать в комментариях к этому посту.

    ReplyDelete
  3. А браузер нужно перезапускать? Сорри за такой глупый вопрос, у меня тут дилема: куча нужных вкладок открыто, а Chrome, как FF не спрашивает сохранить ли их при закрытии :( По крайней мере что-то не нашла такой галочки :( Сижу думаю, что делать, delicious не вызывается и закрывать не хоцца ...

    ReplyDelete
  4. Достаточно обновить страницу, которую вы хотите сохранить.

    Кроме того, нужна версия браузера выше 4.0.227.0

    ReplyDelete
  5. Спасибо! =) Все-таки вчера не дождалась ответа и руками подабовляла в delicious на delicious.com =)

    ReplyDelete
  6. @karri Надеюсь, больше вам этого не придётся делать и расширение сослужит для вас хорошую службу. В следующей версии расширения будет возможность получить быстрый доступ к своему аккаунту на delicious, а также возможность быстрого доступа к закладкам с определённой меткой. Для чего это может быть полезно - читайте этот и этот посты.

    ReplyDelete
  7. А у вас тут интересно, хоть я и неважный ITтишник =) Пожалуй, подпишусь на вас! =)

    ReplyDelete
  8. @karri
    Подпишитесь :). Правда, пишу я нерегулярно.

    ReplyDelete
  9. Очень много расширений уже реализуют добавление закладок в социальные закладки, но пока не встретил ни одного, которое реализует синхронизацию локальных закладок с сервером... Как это делает например расширение делисиоус для огнелиса. Если сумеете реализовать, цены расширению не будет!

    ReplyDelete