Автор материала: Илья Кантор
О Google Gears можно услышать довольно много чего интересного. Дескать, есть такой мегаплагин, добавляет кучу возможностей. И еще - с ним можно оптимизировать сайты.
Посмотрим подробнее, что это такое, для чего он нужен, что умеет.
Эта статья не ставит своей целью заменить документацию по Google Gears (которой, к сожалению, нет на русском языке). Ее цель - показать основные возможности Google Gears и существующие способы их применения, включая использование Gears в Joomla, Wordpress, Youtube.
Google Gears (или просто Gears) - плагин для браузера. На момент написания статьи работает во всех современных браузерах, кроме Safari 4 и Opera Desktop до 10 версии включительно.
Конечно, этот плагин надо ставить. Обычно браузер предлагает это сделать в виде выпадающего сообщения.
Gears добавляет в javascript:
Большинство этих возможностей требуют разрешения на использование от посетителя. Редкие, самые безопасные, сработают и без.
Разберем все это поподробнее.
В спецификации HTML 5 предусмотрено хранение и доступ к данным при помощи SQL: Offline Web Applications. Эта возможность поддерживается в Safari.
Google Gears пошел другим путем и предоставляет свой SQL-интерфейс Database API.
Основное отличие - в Gears запросы к базе синхронные, а в HTML 5 - асинхронные (назначается callback на получение результата). Это удобно, т.к. для асинхронности в gears можно использовать многопоточность там, где это нужно.
Доступ к базе требует разрешения от посетителя.
Следующий пример создаст базу и таблицу (если не существует) и добавит туда запись. Многократный запуск добавит много записей, который сохранятся в базе на локальном компьютере.
var db = google.gears.factory.create('beta.database'); db.open('js'); db.execute('create table if not exists test(phrase text, created datetime)'); var phrase = prompt("Введите фразу") db.execute('insert into test values (?, datetime())', [phrase]) var rs = db.execute('select * from test order by created desc') while (rs.isValidRow()) { alert(rs.field(0) + ' создано ' + rs.field(1)) rs.next() } rs.close()
var db = google.gears.factory.create('beta.database') db.open('js') db.remove()
Здесь, как и в дальнейших примерах, пропущена проверка ошибок выполнения. Например, посетитель может запретить Google Gears. При любых ошибках gears инициирует исключения, которые легко поймать конструкцией try..catch.
В SDK, которое предоставляет google, есть SQL-консоль для работы с базой.
API на сайте Google: http://code.google.com/apis/gears/api_database.html.
Многопоточность, а точнее, многопроцессность - пожалуй, самая интересная возможность Google Gears.
Каждый новый поток является отдельным процессом со своими переменными и средой выполнения.
Реализуется она при помощи WorkerPool API, которое представляет собой вольную переработку черновика Web Workers из HTML5.
Новые процессы запускаются вызовом createWorker/createWorkerFromUri
класса WorkerPool
.
// получить разрешение на доступ к WorkerPool workerPool = google.gears.factory.create('beta.workerpool') // создать новый процесс, загрузить для выполнения скрипт worker.js int childId = workerPool.createWorkerFromUrl('./worker.js')
Эти методы возвращают идентификатор, который мы можем использовать для общения с процессом.
Это делается методом workerPool.sendMessage
:
workerPool.sendMessage("Ответь мне, процесс!", childId)
Чтобы принять сообщение, в скрипте дочернего процесса (worker.js) должен быть определен метод onmessage
на неявно передаваемом в дочерний поток объекте google.gears.workerPool
:
google.gears.workerPool.onmessage = function(a,b, message) { var reply = "Пришло: "+message.body // ответить пославшему google.gears.workerPool.sendMessage(reply, message.sender); }
У метода onmessage
- три аргумента. Первые 2 устарели и не нужны, а третий - собственно, сообщение message
со свойствами:
body
sendMessage
сообщение. В примере - строка, а вообще - может быть любой объектsender
origin
В ответ наш основной процесс может вывести то, что получил:
workerPool.onmessage = function(a,b, message) { alert("От потомка: "+message.body) }
Итак, полный код примера:
workerPool = google.gears.factory.create('beta.workerpool') // работает синхронно, т.е. к следующей строке кода поток будет готов childId = workerPool.createWorkerFromUrl('./worker.js') workerPool.onmessage = function(a,b, message) { alert("От потомка: "+message.body) } workerPool.sendMessage("Ответь мне, процесс!", childId)
Файл worker.js: ./worker.js.
Скрипт, загруженный createWorkerFromUrl
с другого URL, будет выполняться в контексте безопасности этого URL, и при этом общаться с основным процессом.
Соответственно, можно наладить кросс-доменную коммуникацию без всяких трюков с iframe и проксированием, и даже без postMessage/XDomainRequest.
Об этом написано в секции документации Cross-origin workers WorkerPool API.
XmlHttpRequest
и setTimeout/setInterval, которые есть в window
, Gears предоставляет сходные аналоги: HttpRequest и Timer.createWorkerFromUrl
не работает из дочерних процессов. В них можно использовать метод createWorker
, которому передается не URL, а текст скрипта.API на сайте Google: http://code.google.com/intl/ru/apis/gears/api_workerpool.html.
Локальный сервер - пожалуй, самая востребованная часть Google Gears API.
По смыслу - это не совсем тот сервер, о котором привык слышать веб-разработчик.
Он умеет две основные вещи.
Разумеется, поддерживается и обновление локального хранилища.
Хранилище работает в рамках стандартного контекста безопасности Same Origin, так что сохранить локально можно только страницы с текущего протокола-домена-порта.
Для инициализации хранилища используется фабричный класс LocalServer
var localServer = google.gears.factory.create('beta.localserver')
Этот вызов требует разрешения от посетителя.
Основные методы LocalServer:
createStore / openStore / removeStore
- для управления хранилищем типа ResourceStorecreateManagedStore / openManagedStore / removeManagedStore
- для управления хранилищем типа ManagedResourceStoreТаким образом, можно создавать именованные хранилища двух видов, которые различаются логикой хранения и обновления.
Если запрошенная посетителем страница есть в хранилище - запрос будет перехвачен LocalServer'ом, и она будет взята из локального хранилища, а не с удаленного сервера.
Функции LocalServer предусматривают также атрибут requiredCookie
, который может ограничивать доступ к хранилищу и требовать наличия, либо отсутствия у посетителя заданного cookie.
Таким образом, можно например привязать хранилище к ID посетителя:
var localServer = google.gears.factory.create('beta.localserver') // создать или открыть, если есть var store = localServer.createManagedStore('data', 'userid=123')
Данные этого хранилища будут доступны только посетителю с cookie: userid=123
.
Конечно, эта мера скорее нацелена на удобство, нежели на безопасность.
Но часто бывает, что у одного человека есть несколько аккаунтов, скажем, почтовых, и здесь ограничение requiredCookie
может быть как нельзя более удобно.
У каждого хранилища есть атрибут enabled
, который позволяет включать или отключать его, при этом не удаляя данные. Он наиболее полезен при отладке, рабочие приложения его обычно не используют.
ResourceStore
Это хранилище целиком настраивается на клиенте. Оно позволяет получать с сервера произвольные страницы (или сразу много страниц).
Основной метод: capture(urlOrUrlArray, callback)
- получает и сохраняет страницы с сервера.
Для примера посмотрим на таймер. Это обычный div
, для которого javascript при помощи xmlhttprequest раз в секунду запрашивает с сервера страницу time.php
с текущим серверным временем и выводит ее в div
с id="timer"
.
Кликните, пожалуйста, на кнопку запуска для активации кода таймера:
// используется jQuery функция load, получающая данные // при помощи XMLHttpRequest setInterval(function() { $('#timer').load('./time.php') }, 1000)
После того, как вы запустите следующий код, время застынет, т.к. страница time.php
, содержимое которой ежесекундно попадает в div
, станет отдаваться не с сервера, а с вашей машины.
var localServer = google.gears.factory.create('beta.localserver') var store = localServer.createStore('time-store') store.capture( "./time.php", function() { alert('Страница загружена в хранилище') } )
А этот код удалит страницу из хранилища, после чего таймер опять заработает.
var localServer = google.gears.factory.create('beta.localserver') var store = localServer.createStore('time-store') store.remove("./time.php") alert('Страница удалена из хранилища')
Для хранилища нет разницы, как запрашивается страница: напрямую или через xmlhttprequest - оно проверяет точное совпадение URL и возвращает страницу из хранилища, если она там есть.
ManagedResourceStore
Это, пожалуй, самый востребованный тип хранилища. У него есть два важных свойства:
ignoreQuery
matchQuery
После получения манифеста Gears скачивает все перечисленные там страницы и сохраняет их локально.
При каждом изменении версии манифеста все файлы будут скачаны заново.
Типичный способ использования:
var localServer = google.gears.factory.create('beta.localserver') var store = localServer.createManagedStore('store')
store.manifestUrl = './manifest.php'
store.checkForUpdate()
Метод checkForUpdate
работает асинхронно, ход обновления можно узнать, прикрепив обработчики к хранилищу для событий onprogress/onerror/oncomplete
.
Если не запускать обновления явным образом при помощи
checkForUpdate
- Gears будет проверять обновления (запрашивать сервер) самостоятельно при каждом получении страницы из хранилища, но не чаще чем раз в 10 секунд.В этом - важное отличие от обычного
ResourceStore
, которое обновляется только "вручную".
Следующий пример манифеста содержит основные используемые опции с комментариями к ним.
{ // версия манифеста "betaManifestVersion": 2, // версия манифеста "version": "123456789", // список страниц, // относительные пути - идут относительно URL манифеста "entries": [ // загрузить ресурс с сервера и сохранить локально { "url": "main.js" } // локальный URL: main.html, удаленный - main_offline.html // при запросе main.html, LocalServer вернет main_offline.html { "url": "main.html", "src": "main_offline.html" }, // вместо src указана опция redirect // LocalServer перенаправит браузер на main.html со статусом 302 { "url": ".", "redirect": "main.html" }, // выдавать локально сохраненный formHandler.html // при любых запросах вида formHandler.html?param1=..¶m2... { "url": "formHandler.html", "ignoreQuery": true } ] }
Более подробную информацию вы можете получить в документации к манифесту.
Как правило, набор статических файлов (картинок, стилей, скриптов) - не меняется в пределах версии сайта/релиза/"апа"/"деплоя на продакшн".
Поэтому, указав эту версию в манифесте (например, точную дату или ревизию), и собрав в нем всю статику, можно позволить Gears один раз загрузить весь набор и навсегда (до следующей версии) избавить посетителя от лишних запросов к серверу.
Все картинки, скрипты и т.п. Gears будет возвращать из соответствующего ManagedResourceStore
.
Таким образом оптимизируют свои админки контент-системы, такие как Joomla, Wordpress и т.п.
Грамотно настроенный веб-сервер/приложение, использующие технологии Умное Кеширование и Версионность в Javascript/CSS, могут быть так же эффективны, и при этом не нужно плагина на клиенте.
Хотя для такой настройки сервера есть ряд ограничений, в частности, в URL лучше не иметь знак вопроса - некоторые браузеры не будут жестко кешировать такие страницы, она вполне применима и отлично работает. Без Gears. Более подробно - описано в самой статье.
ManagedResourceStore
, по сравнению с "умным кешированием"ManagedResourceStore
почти не требует изменения в приложении, просто пишется небольшой дополнительный код для создания и обновления манифеста.ManagedResourceStore
позволяет:
ignoreQuery/matchQuery
openManagedStore
requiredCookie
Как видите, есть место для обоих технологий.
В Firefox к Gears имеют доступ расширения. Это можно использовать. Например, Greasemonkey может запускать пользовательские скрипты для сайта.
Такой скрипт может загружать статику в локальное хранилище вне зависимости от поддержки Gears сайтом.
Таким образом оптимизация сайта может быть сделана на стороне посетителя, независимо от поддержки сайтом разных полезных технологий ;)
Все начинается с класса Desktop, метода openFiles
.
Он позволяет посетителю выбрать несколько файлов "родным" селектором операционной системы. Например, в Windows можно выделить файлы мышкой.
Первый аргумент - функция, которой будут передан
ы выбранные файлы, вторым можно указать объект опций с параметрами filter/singleFile
(см. OpenFileOptions).
var desktop = google.gears.factory.create('beta.desktop'); desktop.openFiles( function (files) { alert('Выбрано файлов: ' + files.length) } )
Вызвать эту функцию можно, например, при клике на кнопку, то есть вообще без элемента типа <input type="file">.
Файлы передаются в виде массива объектов типа File.
Каждый файл имеет имя name
и содержимое blob
.
Доступ к содержимому файла предоставляет наибольший интерес.
Это объект типа Blob.
Gears предоставляет следующие возможности для манипуляции Blob
.
crop/resize
над файлами-изображениями.Blob
'а, а BlobBuilder - добавить данные к существующему Blob
getAsBlob(url)
, возвращающий полученный ресурс в виде Blob. Таким образом, Blob можно получить из файла с сервераСетевые операции, как и все операции gears, выполняются только в контексте текущего протокола-домена-порта (same origin).
Что это дает на практике?
Для картинок - можно обрабатывать их локально (на уровне crop/resize
). Объект Canvas умеет делать операции над Blob, но не умеет показывать картинку на экране.
Поэтому показ картинки (и вообще, типичный способ добавления Blob в DOM) состоит из следующих шагов:
ResourceStore
, используя метод captureBlob(Blob blob, string url, string optContentType)
url
, например <img src="url">
Продемонстрируем это на примере.
Следующий пример масштабирует выбранную картинку до размера 150x150 без сохранения пропорций и показывает результат. А может отправить получившееся изображение на сервер и т.п.
Конечно, не всем нужно масштабировать картинки, но пример - типовой и демонстрирует один из вариантов использования разных компонент gears вместе.
Контейнер для картинки:
function resize() { var desktop = google.gears.factory.create('beta.desktop'); // опция singleFile для выбора не более одного файла desktop.openFiles(resizeAndShow, { singleFile : true, filter: ['image/jpeg', 'image/gif', 'image/png'] } ) } function resizeAndShow(files) { // (1) var canvas = google.gears.factory.create('beta.canvas'); canvas.decode(files[0].blob) canvas.resize(150,150) var blob = canvas.encode("image/jpeg") // (2) var localServer = google.gears.factory.create('beta.localserver') var store = localServer.createStore('store') // (2.1) var url = '/img.jpg?'+Math.random() store.captureBlob(blob, url, "image/jpeg") // (3) $('#resized').css('background-image', 'url('+url+')') $('#resized')[0].scrollIntoView() // (4) setTimeout(function (){store.remove(url)},0) }
Функция resize
запускает выбор файла, а resizeAndShow
выполняет основные операции.
Рассмотрим ее более подробно.
resizeAndShow
Чтобы избежать возможного кеширования, он выбирается случайным образом.
Хранилище ResourceStore
возвратит картинку только при полном совпадении url, так что конфликта с обычным изображением img.jpg
не возникнет.
При записи картинки в хранилище явно указан тип содержания: image/jpeg
Если убрать setTimeout, то remove
выполнится тут же, до того как браузер отрендерит background-image
. Использование setTimeout
откладывает вызов, так что он произойдет после показа картинки.
Как видно из примера, компоненты Gears отлично пригнаны друг к другу и замечательно взаимодействуют между собой.
Youtube давно использует Google Gears для закачивания больших файлов: http://www.youtube.com/my_videos_multiupload.
При помощи Gears это реализуется очень удобно.
Используемые компоненты Gears:
Файлы, реализующие закачку, не обфусцированы и доступны напрямую по ссылкам (на всякий случай сделал зеркало).
HttpRequest
Общая схема загрузки файла на Youtube такова.
desktop.openFiles
, что дает нам содержимое файла в виде объекта Blobup.SingleUploader
.up.CHUNK_SIZE
. Пересылку каждой части выполняет метод upload_
.upload_
первый, а затем и каждый следующий кусок выдирается из файла в виде строки вызовом Blob API: file.blob.slice
. Текущая позиция в файле сохраняется в свойстве offset
.Content-Disposition
, а также добавляются заголовки с авторизационными данными и идентификатор пересылки.onprogress
. Это событие Worker через каллбэк транслирует основному процессу, который рисует progress bar.onUploadComplete_
, который, если все в порядке, снова вызывает upload_
(см. 4).upload_
, как только увидит (сравнив offset
с размером файла), что пересылка подошла к концу, вызывает finishUpload_
Конкретные детали, если они вам понадобятся, вы без труда поймете из кода. Все реализуется корректно и без каких-либо хаков.
Надо сказать, идея Youtube не нова. Такой способ отсылки запросов на сервер можно использовать и со стандартным XMLHTTPRequest. Другое дело, что передать содержимое файла XMLHTTPRequest средствами стандартного javascript нельзя.
Да и остальные компоненты Gears здесь как нельзя кстати.
На этом описание возможностей Google Gears подошло к концу. Все основные возможности мы разобрали.
Остались всего несколько небольших.
desktop.createShortcut
(Google demo)Разобраться с этими возможностями вы легко сможете из примеров. Пока я не встречал их удачного применения в известных приложениях, но кто знает - может быть, вы будете первым.
Теперь и правда все. Gear it up, dude! ;)
Следующие ссылки содержат основную информацию о разработке и Gears API.
|
Программирование для чайников.
|