[Из песочницы] Немного о внутренностях WebKit

Задался я с задачей, ознакомится с тем как работает в том или ином виде основа всех современных браузеров — WebKit, как происходит процесс загрузки ресурсовю И что с этим всем, собственно, можно сделать. Документации по вопросу, в принципе, достаточно:* структурированная, но не покрывающая и 10 й части от Apple;* разбросаные статьи в вики, разные по степени детализации и степени покрытия.

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

СтруктураWebKit1 — внешнее апи, дефакто для каждого порта свое; WebKit2 — новая его версия, с ним я не работал; JavascriptCore — JavaScript движок; WTF — Web Template Framework; ThirdParty — набор сторонних библиотек, вчасности leveldb; WebCore — базисный функционал WebKit. Как раз про него и большая часть текста. Весь функционал загружен в namespace WebCore. Состоит из подпроектов: loader — WebCore: FrameLoader, WebCore: DocumentLoader, WebCore: DocumentWriter — это оттуда, в общем, все что связано с коммуникацией Document <-> внешний мир — это там; dom — Document Object Model. WebCore: Document; html — WebCore: HTMLDocument — наследник WebCore: Document, WebCore: PluginDocument, WebCore: HTMLElement, его наследники — WebCore: HTMLAnchorElement и так далее. Имплементация докмументной модели HTML. page — WebCore: Frame, WebCore: History, WebCore: ContextMenu — все, что связано с UI или просто с высокоуровневой коммуникацией (History браузера, например); platform — Специфические реализации функционала для разных портов; rendering, css, svg, storage, plugins, inspector — названия говорят сами за себя. Детально не рассматриваются. Процесс загрузки документа, loader programm1.c mainWidget = new QWebView (parent); // mainWidget→setHtml (»HEIL »); mainWidget→page ()→mainFrame ()→setHtml (»HEIL», QUrl ()); QWebFrame: setHtml () → QWebFrameAdapter: setHtml // qt/qtwebkit/Source/WebKit/qt/WebCoreSupport/QWebFrameAdapter.cpp:284 … → WebCore: FrameLoader: load // frame→loader ()→load (WebCore: FrameLoadRequest (frame, request, substituteData)); // «request» description WebCore: FrameLoadRequest ( WebCore: Frame WebCore: ResourceRequest // Request Description — пустой урл WebCore: SubstituteData // Data description — qt/qtwebkit/Source/WebCore/loader/SubstituteData.h // data — WTF: RefPtr // mime-type — «text/html» // encoding — «utf-8» // failingURL ) ) → FrameLoaderClientQt: createDocumentLoader () // RefPtr loader = m_client→createDocumentLoader (request, substitute_data) // WebCore: FrameLoaderClient () → FrameLoader: load (DocumentLoader* newDocumentLoader) // FrameLoader: load (loader.get ()) newDocumentLoader.m_frame = 0; → FrameLoader: loadWithDocumentLoader (newDocumentLoader, type, 0) → FrameLoader: loadWithDocumentLoader (DocumentLoader* loader, FrameLoadType type, PassRefPtr prpFormState) … setPolicyDocumentLoader (loader); // Не только проверка полиси, но устанавливает m_frame в loader. → loader→setFrame (m_frame); → m_writer.setFrame (frame); … // Check policies and with callback jump to callContinueLoadAfterNavigationPolicy via static jumper FrameLoader: callContinueLoadAfterNavigationPolicy (const ResourceRequest&, PassRefPtr formState, bool shouldContinue) → // Контент загружаемой страницы: m_policyDocumentLoader.get ()→substituteData ().content ()→data ();

setProvisionalDocumentLoader (m_policyDocumentLoader.get ()); // m_provisionalDocumentLoader = m_policyDocumentLoader.get ();

FrameLoader: continueLoadAfterWillSubmitForm // RefPtr m_provisionalDocumentLoader; m_provisionalDocumentLoader→startLoadingMainResource // DocumentLoader: m_mainResource — CachedResourceHandle // DocumentLoader: ResourceRequest m_request; // Контент загружаемой страницы m_substituteData.content ()→data () → handleSubstituteDataLoadSoon → handleSubstituteDataLoadNow WebCore: ResourceResponse response (url, m_substituteData.mimeType (), m_substituteData.content ()→size (), m_substituteData.textEncoding (),»); → DocumentLoader: responseReceived (0, response) // responseReceived (CachedResource* resource, const ResourceResponse& response) m_response = response; // m_m_identifierForLoadWithoutResourceLoader=1 // notifier () через него осуществлается посылка евентов в View классы. → frameLoader ()→notifier ()→dispatchDidReceiveResponse (this, m_identifierForLoadWithoutResourceLoader, m_response, 0);

→ DocumentLoader: continueAfterContentPolicy (PolicyUse); // PolicyUse is enum val enum PolicyAction {PolicyUse, PolicyDownload, PolicyIgnore}; // m_response.isHTTP ()=0 isLoadingMainResource ()=1 isStopping ()=0 … → DocumentLoader: dataReceived (0, m_substituteData.content ()→data (), m_substituteData.content ()→size ()); → frameLoader ()→notifier ()→dispatchDidReceiveData (this, m_identifierForLoadWithoutResourceLoader, data, length, -1); → DocumentLoader: commitLoad (const char* data, int length) // data — our html data → frameLoader→client ()→committedLoad (this, data, length); // FrameLoaderClient: committedLoad → FrameLoaderClientQt: committedLoad → void DocumentLoader: commitData (const char* bytes, size_t length) // loader→commitData (data, length) — Святой грааль работы с DocumentWriter, в documentLoader уже сконструирован m_frame. m_writer.begin (documentURL (), false); m_writer.setDocumentWasLoadedAsPartOfNavigation (); → void m_writer.addData (bytes, length); // DocumentWriter: addData

… → finishLoading (0) «Коллстек» выше — это цепочка вызовов от setHtml до коммуникации с Document. Если интересует процесс расстановки полиси и разнообразные виды загрузки данных, то копать надо там. Я выкинул часть переходов, оставив на мой взгляд только те, которые отражают какую-либо смысловую операцию.Процесс, который нас интересует в «коллстеке» выше — начало записи в документ и что для этого нужно. Посмотрев, как именно готовится DocumentWriter в DocumentLoader: commitData, можно programm1.cpp «упростить» (не в смысле кода, а в смысле приближения ее «к земле»).

programm2.c

QWebPage *page = mainWidget→page (); QWebFrame *qtWebFrame = mainWidget→page ()→mainFrame (); QWebFramePrivate *qtWebFramePrivate = qtWebFrame→d; WebCore: Frame *frame = qtWebFramePrivate→frame;

WebCore: DocumentWriter m_writer (frame); m_writer.setFrame (frame); m_writer.begin (url, false); m_writer.setDocumentWasLoadedAsPartOfNavigation (); m_writer.setEncoding («utf-8», true); m_writer.addData (html, strlen (html)); m_writer.end (); Таким же образом надо опуститься ниже — до парсинга (семейство классов WebCore: DocumentParser). Полностью от прослойки Writer избавится не получится: вызов appendBytes содержит в себе writer, плюс Writer отвечает за создание интерфейса декодирования и коммуникацию с View: DocumentWriter: reportDataReceived — вызвает m_frame→document ()→recalcStyle (Node: Force).

void DecodedDataDocumentParser: appendBytes (DocumentWriter* writer, const char* data, size_t length) { … String decoded = writer→createDecoderIfNeeded ()→decode (data, length); … writer→reportDataReceived (); … } Вчленив шаги инициализации из DocumentWriter: begin получим programm3.cpp:

programm3.cpp

const char *html = »HEILIGO»; size_t htmlen = strlen (html);

RefPtr document = WebCore: DOMImplementation: createDocument («text/html», frame, url, false); document→createDOMWindow (); frame→setDocument (document); document→implicitOpen (); frame→script ()→updatePlatformScriptObjects ();

RefPtr parser = document→parser (); WebCore: DocumentWriter writer (frame); m_parser→appendBytes (&writer, html, htmlen); m_parser→finish (); PS На этом мое путешествие в WebKit приостанавливается. Надеюсь, что для кого-либо этот текст понизит точку вхождения в проект.В последнем разделе собраны рецепты быстрого развертывания окружения для экспериментов.

Компиляция За основу был взят пакет qt-everywhere-opensource-src-5.3.1. Оттуда брать все не обязательно — достаточно qtbase i qtwebkit. При сборке я столкнулся со следующей проблемой: статическая сборка с включенным -debug (./configure -static -debug) флагом создает неподемные библиотеки, которые слинковать мне в рабочий пример так и не удалось на 8ГБ машине. Ну и даже если дождаться, линковки — вариант, ждать по несколько минут перекомпиляции простейших примеров не очень подходит. Без symbol info линковка занимает пару секунд, сам libWebkit.a ~ 54Mb.С shared бибилиотекой есть другая проблема: qt — не экспортирует API webkita, а прячет его за своим. Лечится это »-fvisibility=default» вместо hidden. Для хака этого достаточно — в библиотеке нет перекрывающихся имен, для нормального же проекта надо долго и нудно перелопачивать определения экспортированных функций, используя директивы WTF_EXPORT. Про экспортинг есть информация тут, но мне не помогло.

Необходимо также раскрыть доступ к WebCore миру у QWebFrame (qtwebkit/Source/WebKit/qt/WidgetApi/qwebframe.h) — имплементация спрятана за приватной переменной QWebFramePrivate* QWebFrame: d — ее надо сделать паблик. Тогда к WebCore: Frame достучатся можно будет черезWebCore: Frame *frame = [ QWebView ]→page ()→mainFrame ()→frame;

Для тестов, я, все-таки, рекомендую динамическую сборку, иначе ни gdb ни просто линковка удовольствия вам не доставят. Вот мои приблизительные настройки:

cd ./qtbase./configure -opensource -confirm-license -release -nomake tools -nomake examples -no-compile-examples -no-opengl -no-openvg -no-egl -no-eglfs -no-sql-sqlite2 -D QT_NO_GRAPHICSVIEW -D QT_NO_GRAPHICSEFFECT -D QT_NO_STYLESHEET -D QT_NO_STYLE_CDE -D QT_NO_STYLE_CLEANLOOKS -D QT_NO_STYLE_MOTIF -D QT_NO_STYLE_PLASTIQUE -no-qml-debug -no-alsa -no-cups -no-dbus -no-directfb -no-evdev -no-glib -no-gtkstyle -no-kms -no-libudev -no-linuxfb -no-mtdev -no-nis -no-pulseaudio -no-sm -no-xinerama -no-xinput2 -no-xkb -no-xrender -openssl -openssl-linked -icu -fontconfig -system-freetype -system-libpng -system-libjpeg -system-zlib -qt-pcre -qt-harfbuzz -qt-sql-sqlite -qt-xcb -debugmake -j 8

cd …/qtwebkit…/qtbase/bin/qmake WEBKIT_CONFIG-=use_glib\ use_gstreamer\ use_gstreamer010\ use_native_fullscreen_video\ legacy_web_audio\ web_audio\ video\ gamepad -o Makefile.WebCore.Target WebKit.pro

# удаляем -fvisibility=default из полученных make, может ето можно сделать и опцией.

make -f Makefile.WebCore.Target -j 8

Осталось забрать все библиотеки из …/lib/ и можно линковать проект. Минимально необходимо было:

libQt5Core.so libQt5Gui.so libQt5PrintSupport.so libQt5WebKit.so libQt5WebKitWidgets.so libQt5Widgets.so libQt5Newtork.so libQt5Core.so.5 libQt5Gui.so.5 libQt5PrintSupport.so.5 libQt5WebKit.so.5 libQt5WebKitWidgets.so.5 libQt5Widgets.so.5 libQt5Network.so.5 Проблемы сборки Eсли вы увидели: This application failed to start because it could not find or load the Qt platform plugin «xcb».Значит, для статической сборки вы забыли: Q_IMPORT_PLUGIN (QXcbIntegrationPlugin)А для динамической потерялся либо libxcb.so, либо если вы собрали qtbase с ключем -qt-xcb надо создать в проекте папку ./platforms с содежимым qt/qtbase/plugins/platforms (достаточно одного файла libqxcb.so).При компиляции тестового модуля нужно отключить инлайн функции (-fno-inline-small-functions) либо взять весь список дефайнов, с которыми компилировалась библиотека, иначе, поскольку вы используете те же includes, что и скомпилированный WebKit, придется следить за всеми definами, которые попадают в заголовок. В WebCore: Document мой клиентский код из-за данной ошибки для переменной m_parser потерял 8 байт в смещении, и появлялись они — ошибки — в самых разнообразных местах.

© Habrahabr.ru