О Google Gears можно услышать довольно много чего интересного. Дескать, есть такой мегаплагин, добавляет кучу возможностей. И еще - с ним можно оптимизировать сайты.
Посмотрим подробнее, что это такое, для чего он нужен, что умеет.
Эта статья не ставит своей целью заменить документацию по Google Gears (которой, к сожалению, нет на русском языке). Ее цель - показать основные возможности Google Gears и существующие способы их применения, включая использование Gears в Joomla, Wordpress, Youtube.
UPDATE: в связи с тем, что технология умерла, статья имеет интерес разве что исторический.
Google Gears (или просто Gears) - плагин для браузера. На момент написания статьи работает во всех современных браузерах, кроме Safari 4 и Opera Desktop до 10 версии включительно.
Конечно, этот плагин надо ставить. Обычно браузер предлагает это сделать в виде выпадающего сообщения.
В спецификации HTML 5 предусмотрено хранение и доступ к данным при помощи SQL: Offline Web Applications. Эта возможность поддерживается в Safari.
Google Gears пошел другим путем и предоставляет свой SQL-интерфейс Database API.
Основное отличие - в Gears запросы к базе синхронные, а в HTML 5 - асинхронные (назначается callback на получение результата). Это удобно, т.к. для асинхронности в gears можно использовать многопоточность там, где это нужно.
Удалены некоторые возможности SQLite - в основном это касается работы напрямую с файловой системой и системных опций.
Жестко задана кодировка: UTF-8.
Есть полнотекстовой поиск FTS2 (не для русского языка).
Файл с базой хранится внутри домашней директории пользователя. Таким образом, разграничение доступа между пользователями компьютера осуществляется средствами OS.
Одновременно в базу может писать только один процесс - иначе генерируется исключение.
Много баз данных. Но базы привязаны к текущему протоколу-порту-домену. То есть, базы с одинаковым именем для разных сайтов никак не пересекаются (Same Origin), и залезть с одного сайта в базу другого нельзя.
Доступ к базе требует разрешения от посетителя.
Следующий пример создаст базу и таблицу (если не существует) и добавит туда запись. Многократный запуск добавит много записей, который сохранятся в базе на локальном компьютере.
Пример: Записать в таблицу и сделать выборку
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.
Многопоточность, а точнее, многопроцессность - пожалуй, самая интересная возможность Google Gears.
Каждый новый поток является отдельным процессом со своими переменными и средой выполнения.
Реализуется она при помощи WorkerPool API, которое представляет собой вольную переработку черновика Web Workers из HTML5.
Новые процессы запускаются вызовом createWorker/createWorkerFromUri класса WorkerPool.
// получить разрешение на доступ к WorkerPool
workerPool = google.gears.factory.create('beta.workerpool')
// создать новый процесс, загрузить для выполнения скрипт worker.js
int childId = workerPool.createWorkerFromUrl('/files/gears/worker.js')
Эти методы возвращают идентификатор, который мы можем использовать для общения с процессом.
Чтобы принять сообщение, в скрипте дочернего процесса (worker.js) должен быть определен метод onmessage на неявно передаваемом в дочерний поток объекте google.gears.workerPool:
Дочерние процессы не имеют доступа к объектам document и window. Для рисования используйте основной javascript-процесс. А вместо XmlHttpRequest и setTimeout/setInterval, которые есть в window, Gears предоставляет сходные аналоги: HttpRequest и Timer.
Невозможна одновременная запись в базу с разных потоков. Один запишет, другие получат исключения. Соответственно, нужно закладывать в код обработку исключений и повторение SQL-запроса.
Метод createWorkerFromUrl не работает из дочерних процессов. В них можно использовать метод createWorker, которому передается не URL, а текст скрипта.
Локальный сервер - пожалуй, самая востребованная часть Google Gears API.
По смыслу - это не совсем тот сервер, о котором привык слышать веб-разработчик.
Он умеет две основные вещи.
Загружать одну страницу или их список в локальное хранилище
Неявно перехватывать обращения браузера к загруженным страницам и отдавать их из локального хранилища.
Разумеется, поддерживается и обновление локального хранилища.
Хранилище работает в рамках стандартного контекста безопасности Same Origin, так что сохранить локально можно только страницы с текущего протокола-домена-порта.
createStore / openStore / removeStore - для управления хранилищем типа ResourceStore
createManagedStore / 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, который позволяет включать или отключать его, при этом не удаляя данные. Он наиболее полезен при отладке, рабочие приложения его обычно не используют.
Это хранилище целиком настраивается на клиенте. Оно позволяет получать с сервера произвольные страницы (или сразу много страниц).
Основной метод: capture(urlOrUrlArray, callback) - получает и сохраняет страницы с сервера.
Для примера посмотрим на таймер. Это обычный div, для которого javascript при помощи xmlhttprequest раз в секунду запрашивает с сервера страницу time.php с текущим серверным временем и выводит ее в div с id="timer".
Кликните, пожалуйста, на кнопку запуска для активации кода таймера:
// используется jQuery функция load, получающая данные
// при помощи XMLHttpRequest
setInterval(function() { $('#timer').load('/files/gears/time.php') }, 1000)
После того, как вы запустите следующий код, время застынет, т.к. страница time.php, содержимое которой ежесекундно попадает в div, станет отдаваться не с сервера, а с вашей машины.
Пример: Загрузить страницу в хранилище
var localServer = google.gears.factory.create('beta.localserver')
var store = localServer.createStore('time-store')
store.capture(
"/files/gears/time.php",
function() { alert('Страница загружена в хранилище') }
)
А этот код удалит страницу из хранилища, после чего таймер опять заработает.
Пример: Удалить страницу из хранилища
var localServer = google.gears.factory.create('beta.localserver')
var store = localServer.createStore('time-store')
store.remove("/files/gears/time.php")
alert('Страница удалена из хранилища')
Для хранилища нет разницы, как запрашивается страница: напрямую или через xmlhttprequest - оно проверяет точное совпадение URL и возвращает страницу из хранилища, если она там есть.
Если не запускать обновления явным образом при помощи checkForUpdate - Gears будет проверять обновления (запрашивать сервер) самостоятельно при каждом получении страницы из хранилища, но не чаще чем раз в 10 секунд.
В этом - важное отличие от обычного ResourceStore, которое обновляется только "вручную".
Как правило, набор статических файлов (картинок, стилей, скриптов) - не меняется в пределах версии сайта/релиза/"апа"/"деплоя на продакшн".
Поэтому, указав эту версию в манифесте (например, точную дату или ревизию), и собрав в нем всю статику, можно позволить Gears один раз загрузить весь набор и навсегда (до следующей версии) избавить посетителя от лишних запросов к серверу.
Все картинки, скрипты и т.п. Gears будет возвращать из соответствующего ManagedResourceStore.
Таким образом оптимизируют свои админки контент-системы, такие как Joomla, Wordpress и т.п.
Хотя для такой настройки сервера есть ряд ограничений, в частности, в URL лучше не иметь знак вопроса - некоторые браузеры не будут жестко кешировать такие страницы, она вполне применима и отлично работает. Без Gears. Более подробно - описано в самой статье.
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.
Canvas API позволяет делать операции crop/resize над файлами-изображениями.
Blob API дает возможность вырезать часть Blob'а, а BlobBuilder - добавить данные к существующему Blob
Локальное хранилище ResourceStore предоставляет метод getAsBlob(url), возвращающий полученный ресурс в виде Blob. Таким образом, Blob можно получить из файла с сервера
HttpRequest (аналог XmlHttpRequest) умеет отправлять Blob на сервер и возвращать текущее состояние процесса загрузки ProgressEvent.
Сетевые операции, как и все операции gears, выполняются только в контексте текущего протокола-домена-порта (same origin).
Gears, по возможности не хранит весь Blob в памяти. Для файлов Blob - это лишь указатель на файл, который может быть размером в сотни и тысячи мегабайт. При этом если файл меняется в процессе работы, и какие-то операции становятся некорректными (например, файл удален с диска) - Gears инициирует исключение.
Что это дает на практике?
Для картинок - можно обрабатывать их локально (на уровне crop/resize). Объект Canvas умеет делать операции над Blob, но не умеет показывать картинку на экране.
Поэтому показ картинки (и вообще, типичный способ добавления Blob в DOM) состоит из следующих шагов:
Подготовить объект типа Blob
Загрузить его в локальное хранилище типа 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 выполняет основные операции.
Рассмотрим ее более подробно.
Декодировать картинку из содержимого первого (в нашем случае - единственного) выбранного файла, отмасштабировать ее, а затем обратно закодировать в Blob.
Инициализовать хранилище
Положить картинку в хранилище по временному адресу, чтобы затем браузер отобразил ее, обратившись по нему. Используется временный URL.
Чтобы избежать возможного кеширования, он выбирается случайным образом.
Хранилище ResourceStore возвратит картинку только при полном совпадении url, так что конфликта с обычным изображением img.jpg не возникнет.
При записи картинки в хранилище явно указан тип содержания: image/jpeg
Использовать URL с картинкой в качестве фона для контейнера. Вторая строчка прокручивает страницу, чтобы сделать контейнер видимым.
Мы использовали хранилище с временным URL для показа картинки. После того, как картинка будет показана, стоит почистить хранилище. Оно хранится на компьютере посетителя, и не стоит забивать его излишним мусором.
Если убрать setTimeout, то remove выполнится тут же, до того как браузер отрендерит background-image. Использование setTimeout откладывает вызов, так что он произойдет после показа картинки.
Как видно из примера, компоненты Gears отлично пригнаны друг к другу и замечательно взаимодействуют между собой.
Пользователь выбирает файлы при помощи desktop.openFiles, что дает нам содержимое файла в виде объекта Blob
Для отправки файла на сервер инициализуется объект up.SingleUploader.
Файл пересылается по частям, максимальный размер части задан константой up.CHUNK_SIZE. Пересылку каждой части выполняет метод upload_.
Внутри upload_ первый, а затем и каждый следующий кусок выдирается из файла в виде строки вызовом Blob API: file.blob.slice. Текущая позиция в файле сохраняется в свойстве offset.
Подготавливаются стандартные заголовки загрузки файла, включая Content-Disposition, а также добавляются заголовки с авторизационными данными и идентификатор пересылки.
Данные файла, заголовки и каллбэки отправляются на выполнение Worker'у, который отсылает запрос на сервер
По мере выполнения запроса HttpRequest вызывает событие onprogress. Это событие Worker через каллбэк транслирует основному процессу, который рисует progress bar.
По завершении закачки Worker вызывает каллбэк основного процесса onUploadComplete_, который, если все в порядке, снова вызывает upload_ (см. 4).
Метод upload_, как только увидит (сравнив offset с размером файла), что пересылка подошла к концу, вызывает finishUpload_
Серверная часть отслеживает пересылку по идентификатору пересылки и собирает большой файл по частям.
Конкретные детали, если они вам понадобятся, вы без труда поймете из кода. Все реализуется корректно и без каких-либо хаков.
Надо сказать, идея Youtube не нова. Такой способ отсылки запросов на сервер можно использовать и со стандартным XMLHTTPRequest. Другое дело, что передать содержимое файла XMLHTTPRequest средствами стандартного javascript нельзя.
Да и остальные компоненты Gears здесь как нельзя кстати.
На этом описание возможностей Google Gears подошло к концу. Все основные возможности мы разобрали.
Остались всего несколько небольших.
Создание shortcut на рабочем столе с указанием иконки через URL. Делается desktop.createShortcut (Google demo)
Получение текущего местоположения позиции вызовом Geolocation API - работает, в том числе, и для мобильных устройств.
Принятие Drag'n'drop объектов на уровне OS (перенос объектов в окошко браузера) при помощи Desktop API.
Эта часть HTML5 частично реализована в Firefox 3.5, но Gears позволяет читать содержимое перенесенных файлов.
Разобраться с этими возможностями вы легко сможете из примеров. Пока я не встречал их удачного применения в известных приложениях, но кто знает - может быть, вы будете первым.
Статья просто великолепна, таких очень мало. Хочется узнать у народа, кто-нибудь делал что-нибудь стоящее на основе этой технологии? Как я понял, она вполне подходит, чтобы создавать не просто веб-приложения, а приложения с локальным хранилищем данных, например, для тех, у кого слабый или дорогой интернет, чтобы не постоянно быть онлайн, а типа регулярно "обновляться", скидывать наработанные в оффлайне данные на сервер? Я какбэ прав?))
Ютуб иногда выдает устарешвую информацию. Но это раньше было. Сейчас они уже все сделали по новой и api редко когда используется в глобальном смысле. Сейчас все больше криптозащита выходит на первый план. Двухуровневая идентификация и все такое. Даже капчи уже понемногу уходят в прошлое
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.database')
var store = localServer.createStore('database')
// (2.1)
var url = '/img.jpg?'+Math.random()
store.captureBlob(blob, url, "image/jpeg")
// var rs = db.execute('select * from Demo order by Timestamp desc');
if (!google.gears.factory || !db) {
return;
}
//db.execSQL("CREATE TABLE IF NOT EXISTS "+dbTable+"(image BLOB);");
//InputStream xmlInputStream = this.getResources().openRawResource(R.drawable.abc);
//byte[] bb = (xmlInputStream.toString()).getBytes();
// db.execSQL("INSERT INTO "+ dbTable + "(image)" + " VALUES (\""+bb+"\");");
/// db.execute('insert into Demo (t) values (?) where Timestamp=?',[getAsBlob( blob)]), rs.field(1)]);
// db.execute('insert into Demo (getAsBlob( blob)) values (?)where Timestamp=?',[ rs.field(1)]);
db.execute('insert into Demo (getAsBlob( blob)) values (?)',canvas.encode("image/jpeg"));
Отличная статья, спасибо!
Спасибо, отличная статья, а где еще можно посмотреть примеры?
Очень-очень интересно написано! Отличная технология.
Да, согласен, технология довольно простоая
Большое спасибо, Илья, очень интересно, недолго и по делу ) Посоветую друзьям.
Статья просто великолепна, таких очень мало. Хочется узнать у народа, кто-нибудь делал что-нибудь стоящее на основе этой технологии? Как я понял, она вполне подходит, чтобы создавать не просто веб-приложения, а приложения с локальным хранилищем данных, например, для тех, у кого слабый или дорогой интернет, чтобы не постоянно быть онлайн, а типа регулярно "обновляться", скидывать наработанные в оффлайне данные на сервер? Я какбэ прав?))
Большое спасибо, а то бы я долго не смог врубиться что это за штука)
ок, пошел тестить, хватило бы только мозгов)
Спасибо за статью. Появился один вопрос - почему youtube не просит у пользователя установки плагина Gears, хотя использует Gears API?
Ютуб иногда выдает устарешвую информацию. Но это раньше было. Сейчас они уже все сделали по новой и api редко когда используется в глобальном смысле. Сейчас все больше криптозащита выходит на первый план. Двухуровневая идентификация и все такое. Даже капчи уже понемногу уходят в прошлое
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.database')
var store = localServer.createStore('database')
// (2.1)
var url = '/img.jpg?'+Math.random()
store.captureBlob(blob, url, "image/jpeg")
// var rs = db.execute('select * from Demo order by Timestamp desc');
if (!google.gears.factory || !db) {
return;
}
//db.execSQL("CREATE TABLE IF NOT EXISTS "+dbTable+"(image BLOB);");
//InputStream xmlInputStream = this.getResources().openRawResource(R.drawable.abc);
//byte[] bb = (xmlInputStream.toString()).getBytes();
// db.execSQL("INSERT INTO "+ dbTable + "(image)" + " VALUES (\""+bb+"\");");
/// db.execute('insert into Demo (t) values (?) where Timestamp=?',[getAsBlob( blob)]), rs.field(1)]);
// db.execute('insert into Demo (getAsBlob( blob)) values (?)where Timestamp=?',[ rs.field(1)]);
db.execute('insert into Demo (getAsBlob( blob)) values (?)',canvas.encode("image/jpeg"));
// (3)
$('#resized').css('background-image', 'url('+url+')')
$('#resized')[0].scrollIntoView()
// (4)
//setTimeout(function (){store.remove(url)},0)
}
Для того, чтобы добавить в ImageList картинки необходимо: 1. Установить размер добавляемых картинок.
FF 3.6.20
Gears 0.5.36.0
примеры не работают
"google is not defined"
Даже Google Chrome 13.0.782.112 ("google is not defined")
А почему технология умерла?
да тоже интересно почему умерла технология?