Snow Safari - создаем свой первый web-броузер

Вообще, это иллюстрация к статьям "WebKit: вид снаружи" и "WebKit: вид изнутри" (будет опубликована в ближайшие дни - прим. ред.). Получилось что-то вроде "Hello World", только без этого самого Hello World'а...


На одной из сессий одной из конференций разработчиков WebKit изобразили так.

Если хотите, можете повторять мои действия на вашем Маке. За несколько минут, если у вас уже установлены "Developer Tools", и вы помните, где лежит главная программа этого инструментария ("/Developer/Applications/Xcode.app"), мы с вами создадим еще один Web-браузер на основе WebKit.

Назовем его "Snow Safari", так как это модно, красиво ("снежное сафари" - что-то в этом есть родное, правда?), и кроме того, в нашем браузере не будет новых оригинальных функциональностей, и footprint (буквально, "след ноги", но в случае Mac OS 10.6, это слово, скорее всего, означает "нагрузку на компьютер") нашего браузера будет не менее крошечным.

Установка и поиск программы несколько усложнят процесс, но на какие жертвы не пойдешь ради такого великого дела?

 

1. Чтобы все получилось...

Нужен любой более-менее современный Мак, на котором работает Mac OS 10.5 "Леопард", установлен инструментарий разработчика (Developer Tools 3.0), желание, внимательность и терпение.

Если Мака нет, хорошо бы его приобрести.

Snow Safari можно воссоздать с помощью более ранних версий Developer Tools, но картинки Interface Builder будут существенно иными. Сам Interface Builder в новой версии инструментария здорово изменился, но то, что от него требуется в нашем случае, он делает не хуже.

Если есть система, но нет инструментария, его можно найти на "http://developer.apple.com", подписавшись предварительно на бесплатное "онлайновое" членство в Apple Developer Connection. По размеру, инструментарий вполне адекватен... если у вас современный широкополосный Интернет. Если еще нет, искренне советую побыстрее таким обзавестись.

Кажется, все...

Хоть Сафари и "снежное", но джип с турелью, автоматический карабин и теплая куртка нам не понадобятся.

Не забыл ли я упомянуть про внимание и терпение?

2. Сотворение мира

Откройте Xcode, и в меню "File" выберите команду "New Project...". Вот что вы должны увидеть:


Выбираем "Cocoa Application". Другие варианты тоже интересны, и, вообще-то, для браузера лучше всего подошел бы прототип "Cocoa Document-based Application", но тогда разработка супербраузера заняла бы у нас на несколько минут больше. А я и без этого замучился делать скриншоты.

Жмем на кнопку с надписью "Next". Во времена NeXTstep, она была точно такой же.


Называем проект, выбираем куда нам удобнее его поместить, и... "Finish". Если вам не совсем понятны его объяснения, обращайтесь: за умеренную плату переведу. А вообще, в нашем ремесле без английского - никак. Это как латынь для медиков.

И, как говорила венгерская переводчица и полиглот Кати Ломб, "язык - это единственное, что стоит знать хотя бы плохо".

Но, тем не менее, для лучшего понимания материала, очень хорошо прочитать о нем и на родном языке.


Прототип сгенерировался, и выглядит он как на картинке, или несущественно иначе. Вместо названия компании, на которую я иногда пописываю программы, я ввел придуманное. И переключил во втором "поп-апе" слева Release на Debug. Это уже рефлекс. Хотя в нашем проекте отладчик нам и не понадобится.

Милая странность Xcode 3.0, благополучно унаследованная от Xcode 1.0 - картинка в правой части не всегда соответствует строке, выбранной в левой. На экране не содержимое папки Frameworks, а содержимое файла InfoPlist.strings, он двумя строками выше. В правой части незачем отражать содержимое папки, в составе которой только папки и нередактируемые файлы. Возможно, это даже не "баг".

Теперь, о ужас, нам надо написать исходный код нашего браузера, на Objective-C...

3. Пишем на Objective-C!

Есть несколько способов создания новых файлов, и добавления их к проекту. Скотт Форстол, демонстрируя процесс создание программы, которая, с помощью Location Manager выбирает в огромном списке друзей тех, кто находится в радиусе 10 км, запросто перетащил триста строк кода с диска в список исходников проекта, но у нас с вами такой подарок судьбы не предвидится.

Мы напишем наши исходники сами. Для начала, наведем курсор на "папку" Classes, с черным треугольником слева, и, нажав и удерживая клавишу Control, нажмем еще и кнопку мыши. Если у вашей мышки есть "правая кнопка", можете просто нажать ее, Control-click придуман для нас, людей с однокнопочными мышами.

На экране появится вот такое меню:


Кстати, попробуйте догадаться, что означают другие команды. Мы же идем дальше, отпускаем кнопку мыши, и...


Еще один ассистент... Верьте мне, в нашем случае отлично подходит именно класс Objective-C.


Называем файл (и класс) как нам заблагорассудится. Не забудем убедиться в том, что файл с таким же именем, но с расширением "h" тоже будет для нас создан. В Cocoa принято имена классов начинать с заглавной буквы. Больше ничего не меняем, жмем "Finish", и...

В "Снежном Сафари" появились первые два файла, и новый класс. Исторический момент. Теперь мы будем писать код. Вот так выглядит файл заголовка после того, как мы его написали:


Собственно, я добавил только две строки. Значок "//" начинает комментарий в Objective-C, как и в некоторых других C-образных языках. Все, что следует за этим значком, компилятор игнорирует.

Во-первых, это строка #import <WebKit/WebKit.h>. Директива #import делает в Objective-C примерно то же самое, что и директива #include, только немного лучше. В языке есть обе, а #include работает точь-в-точь как в C и С++. То есть, мы "включаем в наш исходник" все определения, которых в WebKit около тысячи.

Во-вторых, в качестве переменной мы используем один из сотни классов WebKit, называем его "wv", и даем среде разработки знать, что эту переменную она должна "увидеть" из Interface Builder, которым мы еще займемся.

А это - объявление нового класса, SnoSafMain, наследника класса NSObject. Почти все классы в Cocoa - его наследники. Проблемы хрупкого корневого класса в Obj-C практически не существует.

@interface SnoSafMain: NSObject
{
// В фигурных скобках - переменные объекта.
IBOutlet WebView* wv;
}
/* После фигурных скобок обычно следуют объявления методов, но в нашем случае они... не понадобятся. Все, что нам надо, определено в NSObject. Один из них мы "переопределим", для этого его не надо указывать в заголовке. Он будет вызван автоматически.
Кстати, этот фрагмент - еще один вариант комментария.*/
@end

А вот - собственно "исходник", с ним пришлось поработать больше.


Прокомментированы только внесенные мной изменения. Остальное сгенерировано автоматически. Генератор исходников настраиваемый, обычно я пользуюсь своими шаблонами - но для Snow Safari стандартного достаточно.

Метод (ответ на сообщение awakeFromNib, посылаемое объекту в момент его распаковки, подробности - потом) awakeFromNib определен в NSObject, где он - "пустышка". Все методы в классах Objective-C - "типа виртуальные". В нормальном объектно-ориентированном программировании других и не должно быть. Мы его назвали "номером три".

Номер четыре: мы создаем переменную класса NSURL, из строки, и задаем ее значение. Можете подставить любой другой корректный url, http:// или file:// - без разницы.

Номер пять: собственно, главный трюк. Главному фрейму wv отправляется запрос, порождаемый из NSURL.

Номер шесть - избыточное хулиганство. Все работает и без него. Но даже в Snow Leopard, несмотря на small footprint и все такое, будет QuickTime X... Нам же просто любопытно узнать, как наш браузер будет представляться, заходя на web-страницы. Ответ будет выведен на консоль отладчика.

Если вы не будете писать про меня гадости в комментариях, так уж и быть, результат я вам сообщу.

4. Еще кое-что.

Осталось подключить WebKit. Правой кнопкой мыши, наведя курсор на папку "Linked Frameworks", открываем контекстное меню. Собственно, запросто можно было бы поместить ссылку на существующий фреймворк где угодно, но порядок должен быть.


Список "публичных" фреймворков обычно открывается первым, но если вы искали свои собственные, или, включали в свой проект "приватные" фреймворки, искатель будет открываться там, где вы его в последний раз "бросили". На всякий случай, вот путь к стандартным открытым фреймворкам: "/System/Library/Frameworks". Есть еще "/System/Library/PrivateFrameworks", но туда ходить не советую.


WebKit.framework, как и следовало ожидать, в самом низу. Выбираем, жмем "Add", и без каких-либо колебаний принимаем все, что нам предлагает выскочивший после этого диалог. Обычно он и не предоставляет слишком много выбора.

Вы не поверите, но исходный код "Снежного Сафари" нами уже полностью написан. Используя кнопку Build, проверьте написанное вами на предмет очепяток. Если вы нигде не ошиблись, все должно построиться без малейших проблем. Проверок никогда не бывает слишком много. Кроме того, компилируя и связывая между собой наши исходники, Xcode сообщит программе-партнеру Interface Builder все необходимое.

Говорят, что Interface Builder теперь все видит сам, автоматически, но я не верю в сказки. Хотя уже пробовал, и все вроде работает - но береженого сами знаете кто бережет. Переходя в Interface Builder, я всегда "строю" проект.

5. Самый лучший Interface Builder в мире.

Теперь открываем Interface Builder. Не спешите покидать Xcode: это теперь ваш дом родной до окончания проекта. В папке Resources, найдите оранжевую иконку с именем MainMenu.nib. Два раза щелкните на ней мышкой. Обычной левой кнопкой, если у вашей мыши слишком много кнопок.

О том, откуда взялось расширение ".nib" (среди прочих значений, это "дробленые бобы какао"), я расскажу когда-нибудь в другой раз. Пока отметим для себя, что в nib-файлах хранятся объекты программы, в "запакованном" виде. Кое-какие из них были положены в MainMenu.nib (кстати, этот файл можно переименовывать, все остальные сущности проекта будут об этом своевременно извещены - и я обычно это делаю, иначе, если в Interface Builder одновременно открыто два или три подобных файла из разных проектов, недолго и ошибиться...).

Итак, два щелчка - и мы в Interface Builder.


О том, что в нем лежит, и зачем - расскажу отдельно, в другом месте, если хотите. Пока нам надо "запаковать" объект класса SnoSafMain, чтобы он автоматически распаковывался при запуске программы, и получал при этом сообщение "awakeFromNib". Ну и... связать его с объектом класса WebView, и наверное, с каким-нибудь окном.

В крайне-правой колонке, озаглавленной Library (библиотека), я уже выбрал для нас нужный подраздел раздела Objects and Controllers, а именно - Controllers. Левый верхний "объект" в этом разделе (Object) - самый правильный выбор. Перетащим его в окно MainMenu.nib. Серое поле вокруг иконки означает, что объект "выбран".


Длинное вертикально-ориентированное окна справа от окна MainMenu.nib называется "инспектор". С его помощью можно много-много всего перенастроить, но нам надо только одного: связать этот "объект" с классом в нашем исходнике. Классы, одним из которых запросто может стать наш объект, перечислены в списке, появляющемся при выборе шестой закладочки слева (Object Identity). В списке чудесным образом уже есть SnoSafMain. Выбираем его.

В Snow Safari "наших" объектов в MainMenu немного, и от того, как он будет называться в окошке Interface Builder (и больше нигде) ничего не изменится. Но его все-же лучше переименовать. Надо навести курсор на имя объекта, нажать на кнопку мышки, и задержать ее в нажатом положении пока в имени не появится текстовый курсор.

И назвать его, например, SnoSafCtrl. Роль, которую этот класс будет играть в Снежном Сафари, в парадигме "модель-представление-поведение", или "Model-View-Controller" - "поведение", то есть, controller.


Теперь познакомим контроллер (SnoSafCtrl) с WebView. Сначала перетащим WebView из Library в окно. WebView - единственный элемент в разделе WebKit. Другие здесь и не особо нужны.


Сколько тут всяких других "китов"!

Перетаскиваем "компас" в окно, именуемое Window. Его тоже можно было бы переименовать, но мы поленимся это сделать. делаем так, чтобы WebView занимало всю нижнюю часть нашего окна, оставив узенькую полоску в верхней части. Типа, в версии 1.0 (или 1.5) мы добавим какие-нибудь органы управления, и разместим их здесь.


Можете мне не верить, но белое пространство внизу - WebView. Фон самого окна - светлосерый. Теперь надо связать переменную vw в контроллере с WebView в этом окне.

Старый некстовский способ: нажмите и удерживайте в нажатом положении клавишу Control на клавиатуре. Навердите курсор на синий кубик SnoSafCtrl, и проведите от него линию (синяя линия, подтверждающая, что операция привязки переменной контроллера к ее элементу интерфейса инициирована, потянется вслед за движениями курсора) куда-нибудь во внутреннюю часть WebView. Это проще показать, чем рассказать... но надеюсь, вы меня поняли.

Вот так все должно выглядеть в тот момент, когда кнопку на мышке можно отпустить:


Если бы я работал у Джобса, меня бы за такие скриншоты давно уже выгнали. Но, надеюсь, мои "картинки" можно понять...

После того, как мы "бросили" кнопку на мышке, должно появиться безумно красивое черное окошко, с именем нашей переменной - если мы все сделали правильно:


Выбираем wv, сохраняем nib-файл, и все. Это уже самый настоящий браузер. Правда, не слишком хороший. Если мы изменим размер окна, размер WebView останется неизменным. Исправим это. Выберем WebView (щелкнем внутри белой области окна), и откероем закладочку инспектора с желтой линейкой (третью слева), и сделаем картинку такой-же, как в autosizing на иллюстрации:


Двутавры с четырех сторон обозначают "неизменное расстояние от краев внешнего объекта", полосы со стрелочками внутри - "размер элемента изменяется также, как изменяется внешний объект". Внешний объект - это окно. Живая картинка справа не даст ошибиться: внешний объект (обозначенный белым) все время меняется, а красный прямоугольник показывает, как при этом будет меняться настраиваемый элемент. Очень удобно: в 2.х все было немного сложнее.

Сохраняем сделанное, переходим в Xcode, запускаем Build and Run, и...

6. Snow Safari...

Вот страничка Apple, на которую мы и настроили наш браузер:


На ссылке "iPhone 3G coming soon" в окне - встроенный видеофрагмент. Хорошее испытание для Snow Safari...


Работает!

Работает... всё! Ссылки, JavaScript, различные форматы графики (в том числе и такие, с которыми Internet Explorer испытывает проблемы), видео, аудио, PDF... Более того, наш браузер должен проходить Acid-2, и почти - Acid-3.

Кстати, а как нас видят посещаемые нами web-страницы?

Вот так: "'Mozilla/5.0 (Macintosh; U; PPC Mac OS X 10_5_3; en-us) AppleWebKit/525.18 (KHTML, like Gecko)'". Несколько неожиданные результаты. При желании, все это нетрудно изменить.

Прежде чем рассказывать о том, что такое WebKit, мне захотелось показать, что это такое, что такое Cocoa и Objective-C. Каждая из этих тем затронута очень поверхностно. На каждую из них можно и нужно писать тома...

Получился "Hello world" без этих самых слов. Чтобы были эти самые слова, можно добавить в awakeFromNib "[wv setCustomUserAgent:@"Hello world!"];", и они будут запоминаться в журналах web-страниц, посещаемых нами.

7. Послесловие

Если идея написания своего собственного браузера на движке WebKit вас увлекла, советую изучить чуть более сложный пример, который, если у вас установлен инструментарий разработчика, уже есть на вашем компьютере, и расположен по адресу "/Developer/Examples/WebKit/MiniBrowser". Его вполне можно использовать как основу.

Приятной охоты!

И еще...

Считается, что детальное изложения материала на уровне "как программировать" и исходного кода никому не интересно. Я в этом всегда сомневался, но еще никогда мое сомнение не достигало такой интенсивности, как сейчас.

Если вам это интересно, было бы здорово, если бы выдали об этом знать.

Источник: Олег Свиргстин