スキップしてメイン コンテンツに移動

Dissecting WebKit2 Implementation as of October 2010

WebKit2 was first disclosed to the public in April 2010. Now half a year has passed, has there been any noticeable progress? This time I delve into the WebKit2 from the source-code point of view.

First let me recap what WebKit2 actually is and how it's related to the current WebKit with the official high level design document as the reference.

For those who are not very familiar with WebKit, it may be confusing that WebKit as a whole contains a part that shares the same WebKit name. WebKit as the framework has 3 major components - WebKit, WebCore, and JavaScriptCore - as you see in the source directory. WebCore is the layout engine that parses an HTML5 web page into a DOM tree and renders a render tree created out of a DOM tree with CSS information computed on each node. In addition to the abstract or platform-agnostic parts, it contains platform-specific implementations such as graphics and I/O if necessary. JavaScriptCore is the JavaScript engine. Google Chrome swaps it with its own V8 engine whereas Safari uses JavaScriptCore under the name Nitro. JavaScriptCore/V8 bindings to DOM live in the bindings directory in the WebCore. A WebKit user controls WebCore through a high-level API set which is differently implemented for each platform. The WebKit directory contains such an API set. For example, the Windows version of Safari implements it by COM classes in C++ in the win directory while the Mac version does it with Objective-C in the mac directory. Due to some dependency on proprietary libraries owned by Apple found in the Windows implementation for Safari, WebKit embedders should try other API layer implementations such as Gtk+ or Qt unless adopting Chromium suits the need.


WebKit2 effectively splits this WebKit API layer into two. The first part, UI Process, is obviously the interface accessed by the platform API user. Inside this shell, or outside if you follow its process model, there are Web Processes that implement the second layer of the new WebKit2 API. The UI Process controls multiple Web Processes through accessing the API based on an IPC protocol agreed by them. The API of the UI Process should work in almost the same way for a user as the old API except that the WebKit2 API are non-blocking. Most likely the underlying WebCore doesn't change much as a multiprocess architecture has typically a lot less synchronization issues than a multithreaded architecture. Also the current WebKit API should continue to work alongside of the WebKit2 development unless Chromium tries to merge it, but the prospect looks unlikely at this point.

Let's take a look at the source code itself. The most easiest entry point would be an example that uses WebKit2 API. Fortunately it's already available as the MiniBrowser. Right now there are 3 implementations: Mac, Windows, and Qt. The Mac version seems the most active, the next is the Qt version. The Windows version is a plain Windows application that is not related to the current Windows version of Safari for some reason. Since it's the most simple, I start from there. The main.cpp has its message loop.

int WINAPI _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpstrCmdLine, int nCmdShow)
{

MiniBrowser::shared().initialize(hInstance);



// Create and show our initial window.

MiniBrowser::shared().createNewWindow();



MSG message;

while (BOOL result = ::GetMessage(&message, 0, 0, 0)) {

if (result == -1)

break;

::TranslateMessage(&message);



if (!MiniBrowser::shared().handleMessage(&message))

::DispatchMessage(&message);

}



return 0;
}



MiniBrowser::shared() returns the static singleton of the MiniBrowser object. So it calls MiniBrowser::createNewWindow() which then creates a BrowserWindow object with its member BrowserView object (m_browserView). The actual browser view is created at the end of BrowserWindow::onCreate. It calls BrowserView::create.

void BrowserView::create(RECT webViewRect, BrowserWindow* parentWindow)

{

assert(!m_webView);



bool isShiftKeyDown = ::GetKeyState(VK_SHIFT) & HIGH_BIT_MASK_SHORT;



WKContextRef context;

if (isShiftKeyDown)

context = WKContextGetSharedThreadContext();

else

context = WKContextGetSharedProcessContext();



WKPageNamespaceRef pageNamespace = WKPageNamespaceCreate(context);



m_webView = WKViewCreate(webViewRect, pageNamespace, parentWindow->window());



WKPageUIClient uiClient = {

0,              /* version */

parentWindow,   /* clientInfo */

createNewPage,

showPage,

closePage,

runJavaScriptAlert,

runJavaScriptConfirm,

runJavaScriptPrompt,

setStatusText,

mouseDidMoveOverElement,

0               /* didNotHandleKeyEvent */

};



WKPageSetPageUIClient(WKViewGetPage(m_webView), &uiClient);

}



A lot of things seem to happen, but apparently this is the starting point of embedding WebKit2. isShiftKeyDown is probably there for the debug purpose, it's used to switch the context. WKContextGetSharedThreadContext is declared in WKContextPrivate.h while WKContextGetSharedProcessContext is declared in WKContext.h, both are WebKit2 C API functions. These actually refer to the WebContext object of the UI Process.

WebContext* WebContext::sharedProcessContext()

{

WTF::initializeMainThread();

RunLoop::initializeMainRunLoop();

static WebContext* context = adoptRef(new WebContext(ProcessModelSharedSecondaryProcess, String())).leakRef();

return context;

}



WebContext* WebContext::sharedThreadContext()

{

RunLoop::initializeMainRunLoop();

static WebContext* context = adoptRef(new WebContext(ProcessModelSharedSecondaryThread, String())).leakRef();

return context;

}



This RunLoop is declared in RunLoop.h,but most of its work is defined in the platform-specific implementation, for example the Windows version can be found in RunLoopWin.cpp. It's a message queue window for WebKit work items. So, back to the BrowserView::create, it creates a WebContext of some flavor. Its member declaration is like this.

class WebContext : public APIObject {

public:

static const Type APIType = TypeContext;



static WebContext* sharedProcessContext();

static WebContext* sharedThreadContext();



static PassRefPtr<WebContext> create(const String& injectedBundlePath);



~WebContext();



// snip



private:

WebContext(ProcessModel, const String& injectedBundlePath);



virtual Type type() const { return APIType; }



void ensureWebProcess();

bool hasValidProcess() const { return m_process && m_process->isValid(); }

void platformInitializeWebProcess(WebProcessCreationParameters&);



static void languageChanged(void* context);

void languageChanged();



ProcessModel m_processModel;



// FIXME: In the future, this should be one or more WebProcessProxies.

RefPtr<WebProcessProxy> m_process;



HashSet<WebPageNamespace*> m_pageNamespaces;

RefPtr<WebPreferences> m_preferences;



String m_injectedBundlePath;

WebContextInjectedBundleClient m_injectedBundleClient;



WebHistoryClient m_historyClient;



PluginInfoStore m_pluginInfoStore;

VisitedLinkProvider m_visitedLinkProvider;



HashSet<String> m_schemesToRegisterAsEmptyDocument;

HashSet<String> m_schemesToRegisterAsSecure;

HashSet<String> m_schemesToSetDomainRelaxationForbiddenFor;



Vector<pair<String, RefPtr<APIObject> > > m_pendingMessagesToPostToInjectedBundle;



CacheModel m_cacheModel;



#if PLATFORM(WIN)

bool m_shouldPaintNativeControls;

#endif

};



It has document-related objects such as WebHistoryClient and VisitedLinkProvider, also it has WebPreferences that should be applied to all pages. So WebContext is the class that represents a single web browser.

The other important member object is WebProcessProxy that is a proxy to a renderer process. As the comment suggests, it will be the proxy to multiple Web Processes in the future. The web process proxy is initialized before creating a web page by WebContext::ensureWebProcess.

void WebContext::ensureWebProcess()

{

if (m_process)

return;



m_process = WebProcessManager::shared().getWebProcess(this);



WebProcessCreationParameters parameters;



parameters.applicationCacheDirectory = applicationCacheDirectory();



if (!injectedBundlePath().isEmpty()) {

parameters.injectedBundlePath = injectedBundlePath();



#if ENABLE(WEB_PROCESS_SANDBOX)

char* sandboxBundleTokenUTF8 = 0;

CString injectedBundlePathUTF8 = injectedBundlePath().utf8();

sandbox_issue_extension(injectedBundlePathUTF8.data(), &sandboxBundleTokenUTF8);

String sandboxBundleToken = String::fromUTF8(sandboxBundleTokenUTF8);

if (sandboxBundleTokenUTF8)

free(sandboxBundleTokenUTF8);



parameters.injectedBundlePathToken = sandboxBundleToken;

#endif

}



parameters.shouldTrackVisitedLinks = m_historyClient.shouldTrackVisitedLinks();

parameters.cacheModel = m_cacheModel;

parameters.languageCode = defaultLanguage();

parameters.applicationCacheDirectory = applicationCacheDirectory();



copyToVector(m_schemesToRegisterAsEmptyDocument, parameters.urlSchemesRegistererdAsEmptyDocument);

copyToVector(m_schemesToRegisterAsSecure, parameters.urlSchemesRegisteredAsSecure);

copyToVector(m_schemesToSetDomainRelaxationForbiddenFor, parameters.urlSchemesForWhichDomainRelaxationIsForbidden);



// Add any platform specific parameters

platformInitializeWebProcess(parameters);



m_process->send(Messages::WebProcess::InitializeWebProcess(parameters), 0);

}



WebProcessManager does simple creation of a Web Process proxy via WebProcessProxy::create dependent on the process model: Shared Secondary Process, Shared Secondary Thread, Secondary Process. The names are a bit confusing but Shared Secondary Thread means using threads for Web Processes, and Shared Secondary Process means using multiple processes. The last one manages multiple WebContext objects and correspondent Web Proxy objects in a hash map.

WebProcessProxy* WebProcessManager::getWebProcess(WebContext* context)

{

switch (context->processModel()) {

case ProcessModelSharedSecondaryProcess: {

if (!m_sharedProcess)

m_sharedProcess = WebProcessProxy::create(context);

return m_sharedProcess.get();

}

case ProcessModelSharedSecondaryThread: {

if (!m_sharedThread)

m_sharedThread = WebProcessProxy::create(context);

return m_sharedThread.get();

}

case ProcessModelSecondaryProcess: {

std::pair<ProcessMap::iterator, bool> result = m_processMap.add(context, 0);

if (result.second) {

ASSERT(!result.first->second);

result.first->second = WebProcessProxy::create(context);

}



ASSERT(result.first->second);

return result.first->second.get();

}

}



ASSERT_NOT_REACHED();

return 0;

}



WebProcessProxy::create is just a call to WebProcessProxy::connect via its constructor.

PassRefPtr<WebProcessProxy> WebProcessProxy::create(WebContext* context)

{

return adoptRef(new WebProcessProxy(context));

}



WebProcessProxy::WebProcessProxy(WebContext* context)

: m_responsivenessTimer(this)

, m_context(context)

{

connect();

}



WebProcessProxy::~WebProcessProxy()

{

ASSERT(!m_connection);



for (size_t i = 0; i < m_pendingMessages.size(); ++i)

m_pendingMessages[i].releaseArguments();



if (m_processLauncher) {

m_processLauncher->invalidate();

m_processLauncher = 0;

}



if (m_threadLauncher) {

m_threadLauncher->invalidate();

m_threadLauncher = 0;

}

}



void WebProcessProxy::connect()

{

if (m_context->processModel() == ProcessModelSharedSecondaryThread) {

ASSERT(!m_threadLauncher);

m_threadLauncher = ThreadLauncher::create(this);

} else {

ASSERT(!m_processLauncher);



ProcessLauncher::LaunchOptions launchOptions;

launchOptions.processType = ProcessLauncher::WebProcess;

#if PLATFORM(MAC)

// We want the web process to match the architecture of the UI process.

launchOptions.architecture = ProcessLauncher::LaunchOptions::MatchCurrentArchitecture;

#endif

m_processLauncher = ProcessLauncher::create(this, launchOptions);

}

}



If the process model is Shared Secondary Thread, it creates a ThreadLauncher instance by its create method.

class ThreadLauncher : public ThreadSafeShared<ThreadLauncher> {

public:

class Client {

public:

virtual ~Client() { }

virtual void didFinishLaunching(ThreadLauncher*, CoreIPC::Connection::Identifier) = 0;

};



static PassRefPtr<ThreadLauncher> create(Client* client)

{

return adoptRef(new ThreadLauncher(client));

}



Again, it's just a proxy to ThreadLauncher::launchThread.

ThreadLauncher::ThreadLauncher(Client* client)

: m_client(client)

{

launchThread();

}



void ThreadLauncher::launchThread()

{

m_isLaunching = true;



CoreIPC::Connection::Identifier connectionIdentifier = createWebThread();



// We've finished launching the thread, message back to the main run loop.

RunLoop::main()->scheduleWork(WorkItem::create(this, &ThreadLauncher::didFinishLaunchingThread, connectionIdentifier));

}



ThreadLauncher::createWebThread is a platform-specific method. It's defined in ThreadLauncherWin.cpp for Windows.

CoreIPC::Connection::Identifier ThreadLauncher::createWebThread()

{

// First, create the server and client identifiers.

HANDLE serverIdentifier, clientIdentifier;

if (!CoreIPC::Connection::createServerAndClientIdentifiers(serverIdentifier, clientIdentifier)) {

// FIXME: What should we do here?

ASSERT_NOT_REACHED();

}



if (!createThread(webThreadBody, reinterpret_cast<void*>(clientIdentifier), "WebKit2: WebThread")) {

::CloseHandle(serverIdentifier);

::CloseHandle(clientIdentifier);

return 0;

}



return serverIdentifier;

}



createThread is a utility function declared in the WTF namespace of JavaScriptCore. In wtf/Threading.cpp, it calls platform-specific createThreadInternal.

ThreadIdentifier createThread(ThreadFunction entryPoint, void* data, const char* name)

{

// Visual Studio has a 31-character limit on thread names. Longer names will

// be truncated silently, but we'd like callers to know about the limit.

#if !LOG_DISABLED

if (strlen(name) > 31)

LOG_ERROR("Thread name \"%s\" is longer than 31 characters and will be truncated by Visual Studio", name);

#endif



NewThreadContext* context = new NewThreadContext(entryPoint, data, name);



// Prevent the thread body from executing until we've established the thread identifier.

MutexLocker locker(context->creationMutex);



return createThreadInternal(threadEntryPoint, context, name);

}



For example, the Windows version is defined in wtf/ThreadingWin.cpp (for some reason this is not in a separate folder).

ThreadIdentifier createThreadInternal(ThreadFunction entryPoint, void* data, const char* threadName)

{

unsigned threadIdentifier = 0;

ThreadIdentifier threadID = 0;

ThreadFunctionInvocation* invocation = new ThreadFunctionInvocation(entryPoint, data);

#if OS(WINCE)

// This is safe on WINCE, since CRT is in the core and innately multithreaded.

// On desktop Windows, need to use _beginthreadex (not available on WinCE) if using any CRT functions

HANDLE threadHandle = CreateThread(0, 0, (LPTHREAD_START_ROUTINE)wtfThreadEntryPoint, invocation, 0, (LPDWORD)&threadIdentifier);

#else

HANDLE threadHandle = reinterpret_cast<HANDLE>(_beginthreadex(0, 0, wtfThreadEntryPoint, invocation, 0, &threadIdentifier));

#endif

if (!threadHandle) {

#if OS(WINCE)

LOG_ERROR("Failed to create thread at entry point %p with data %p: %ld", entryPoint, data, ::GetLastError());

#elif defined(NO_ERRNO)

LOG_ERROR("Failed to create thread at entry point %p with data %p.", entryPoint, data);

#else

LOG_ERROR("Failed to create thread at entry point %p with data %p: %ld", entryPoint, data, errno);

#endif

return 0;

}



threadID = static_cast<ThreadIdentifier>(threadIdentifier);

storeThreadHandleByIdentifier(threadIdentifier, threadHandle);



return threadID;

}



_beginthreadex is the underlying thread creation function on Windows. In this model, the thread ID is used as the return value which becomes CoreIPC::Connection::Identifier.

You may remember the single process model in Chromium as a mode akin to this Shared Secondary Thread prcess model in WebKit2.

OK let's get back to the more interesting one, the Shared Secondary Process model that sports ProcessLauncher. When created via the create method, its constructor adds a function call to ProcessLauncher::launchProcess to the end of its WorkQueue singleton.

class ProcessLauncher : public ThreadSafeShared<ProcessLauncher> {

public:

class Client {

public:

virtual ~Client() { }



virtual void didFinishLaunching(ProcessLauncher*, CoreIPC::Connection::Identifier) = 0;

};

// snip



static PassRefPtr<ProcessLauncher> create(Client* client, const LaunchOptions& launchOptions)

{

return adoptRef(new ProcessLauncher(client, launchOptions));

}



static WorkQueue& processLauncherWorkQueue()

{

DEFINE_STATIC_LOCAL(WorkQueue, processLauncherWorkQueue, ("com.apple.WebKit.ProcessLauncher"));

return processLauncherWorkQueue;

}



ProcessLauncher::ProcessLauncher(Client* client, const LaunchOptions& launchOptions)

: m_client(client)

, m_launchOptions(launchOptions)

, m_processIdentifier(0)

{

// Launch the process.

m_isLaunching = true;

processLauncherWorkQueue().scheduleWork(WorkItem::create(this, &ProcessLauncher::launchProcess));

}



WorkQueue::scheduleWork is defined as a platform-specific function. In the case of Windows, it's based on the QueueUserWorkItem API that uses the thread pool of Windows OS. Again, ProcessLauncher::launchProcess is implemented in the platform-specific form. For Windows, it's in win/ProcessLauncherWin.cpp.

void ProcessLauncher::launchProcess()

{

// First, create the server and client identifiers.

HANDLE serverIdentifier, clientIdentifier;

if (!CoreIPC::Connection::createServerAndClientIdentifiers(serverIdentifier, clientIdentifier)) {

// FIXME: What should we do here?

ASSERT_NOT_REACHED();

}



// Ensure that the child process inherits the client identifier.

::SetHandleInformation(clientIdentifier, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT);



// To get the full file path to WebKit2WebProcess.exe, we fild the location of WebKit.dll,

// remove the last path component, and then append WebKit2WebProcess(_debug).exe.

HMODULE webKitModule = ::GetModuleHandleW(webKitDLLName);

ASSERT(webKitModule);

if (!webKitModule)

return;



WCHAR pathStr[MAX_PATH];

if (!::GetModuleFileNameW(webKitModule, pathStr, WTF_ARRAY_LENGTH(pathStr)))

return;



::PathRemoveFileSpecW(pathStr);

if (!::PathAppendW(pathStr, webProcessName))

return;



String commandLine(pathStr);



// FIXME: It would be nice if we could just create a CommandLine object and output a command line vector from it.

Vector<UChar> commandLineVector;

append(commandLineVector, commandLine);

append(commandLineVector, " -type webprocess");

append(commandLineVector, " -clientIdentifier ");

append(commandLineVector, String::number(reinterpret_cast<uintptr_t>(clientIdentifier)));

commandLineVector.append('\0');



STARTUPINFO startupInfo = { 0 };

startupInfo.cb = sizeof(startupInfo);

PROCESS_INFORMATION processInformation = { 0 };

BOOL result = ::CreateProcessW(0, commandLineVector.data(), 0, 0, true, 0, 0, 0, &startupInfo, &processInformation);



// We can now close the client identifier handle.

::CloseHandle(clientIdentifier);



if (!result) {

// FIXME: What should we do here?

DWORD error = ::GetLastError();

ASSERT_NOT_REACHED();

}



// Don't leak the thread handle.

::CloseHandle(processInformation.hThread);



// We've finished launching the process, message back to the run loop.

RunLoop::main()->scheduleWork(WorkItem::create(this, &ProcessLauncher::didFinishLaunchingProcess, processInformation.hProcess, serverIdentifier));

}



CoreIPC::Connection::createServerAndClientIdentifiers creates a platform primitive for IPC (InterProcess Communication) between the UI Process and Web Processes. The whole implementation of IPC for WebKit2 is found in the CoreIPC directory. The Windows version of Connection::createServerAndClientIdentifiers uses Named Pipe for its IPC channel.

bool Connection::createServerAndClientIdentifiers(HANDLE& serverIdentifier, HANDLE& clientIdentifier)

{

String pipeName;



while (true) {

unsigned uniqueID = randomNumber() * std::numeric_limits<unsigned>::max();

pipeName = String::format("\\\\.\\pipe\\com.apple.WebKit.%x", uniqueID);



serverIdentifier = ::CreateNamedPipe(pipeName.charactersWithNullTermination(),

PIPE_ACCESS_DUPLEX | FILE_FLAG_FIRST_PIPE_INSTANCE | FILE_FLAG_OVERLAPPED,

PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE, 1, inlineMessageMaxSize, inlineMessageMaxSize,

0, 0);

if (!serverIdentifier && ::GetLastError() == ERROR_PIPE_BUSY) {

// There was already a pipe with this name, try again.

continue;

}



break;

}



if (!serverIdentifier)

return false;



clientIdentifier = ::CreateFileW(pipeName.charactersWithNullTermination(), GENERIC_READ | GENERIC_WRITE, 0, 0, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, 0);

if (!clientIdentifier) {

::CloseHandle(serverIdentifier);

return false;

}



DWORD mode = PIPE_READMODE_MESSAGE;

if (!::SetNamedPipeHandleState(clientIdentifier, &mode, 0, 0)) {

::CloseHandle(serverIdentifier);

::CloseHandle(clientIdentifier);

return false;

}



return true;

}



In Chromium, a similar operation is handled by its IPC Channel implementation. It may be interesting to compare it with the one in WebKit2.

bool Channel::ChannelImpl::CreatePipe(const std::string& channel_id,

Mode mode) {

DCHECK(pipe_ == INVALID_HANDLE_VALUE);

const std::wstring pipe_name = PipeName(channel_id);

if (mode == MODE_SERVER) {

SECURITY_ATTRIBUTES security_attributes = {0};

security_attributes.bInheritHandle = FALSE;

security_attributes.nLength = sizeof(SECURITY_ATTRIBUTES);

if (!GetLogonSessionOnlyDACL(

reinterpret_cast<SECURITY_DESCRIPTOR**>(

&security_attributes.lpSecurityDescriptor))) {

NOTREACHED();

}



pipe_ = CreateNamedPipeW(pipe_name.c_str(),

PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED |

FILE_FLAG_FIRST_PIPE_INSTANCE,

PIPE_TYPE_BYTE | PIPE_READMODE_BYTE,

1,         // number of pipe instances

// output buffer size (XXX tune)

Channel::kReadBufferSize,

// input buffer size (XXX tune)

Channel::kReadBufferSize,

5000,      // timeout in milliseconds (XXX tune)

&security_attributes);

LocalFree(security_attributes.lpSecurityDescriptor);

} else {

pipe_ = CreateFileW(pipe_name.c_str(),

GENERIC_READ | GENERIC_WRITE,

0,

NULL,

OPEN_EXISTING,

SECURITY_SQOS_PRESENT | SECURITY_IDENTIFICATION |

FILE_FLAG_OVERLAPPED,

NULL);

}

if (pipe_ == INVALID_HANDLE_VALUE) {

// If this process is being closed, the pipe may be gone already.

LOG(WARNING) << "failed to create pipe: " << GetLastError();

return false;

}



// Create the Hello message to be sent when Connect is called

scoped_ptr<Message> m(new Message(MSG_ROUTING_NONE,

HELLO_MESSAGE_TYPE,

IPC::Message::PRIORITY_NORMAL));

if (!m->WriteInt(GetCurrentProcessId())) {

CloseHandle(pipe_);

pipe_ = INVALID_HANDLE_VALUE;

return false;

}



output_queue_.push(m.release());

return true;

}



Going back to ProcessLauncher::launchProcess, it locates the executable module for the Web Process and creates a new process with the given parameters. When it's done, the callback function ProcessLauncher::didFinishLaunchingProcess is put on the work queue of the RunLoop. It then calls WebProcessProxy::didFinishLaunching since m_client is actually WebProcessProxy as you may notice if you remember what was going on in WebProcessProxy::connect. WebProcessProxy inherits ProcessLauncher::Client.

void ProcessLauncher::didFinishLaunchingProcess(PlatformProcessIdentifier processIdentifier, CoreIPC::Connection::Identifier identifier)

{

m_processIdentifier = processIdentifier;

m_isLaunching = false;



if (!m_client) {

// FIXME: Dispose of the connection identifier.

return;

}



m_client->didFinishLaunching(this, identifier);

}



It then opens an IPC server connection (RefPtr<CoreIPC::Connection> m_connection) in WebProcessProxy::didFinishLaunching.

void WebProcessProxy::didFinishLaunching(ProcessLauncher*, CoreIPC::Connection::Identifier connectionIdentifier)

{

didFinishLaunching(connectionIdentifier);

}



void WebProcessProxy::didFinishLaunching(ThreadLauncher*, CoreIPC::Connection::Identifier connectionIdentifier)

{

didFinishLaunching(connectionIdentifier);

}



void WebProcessProxy::didFinishLaunching(CoreIPC::Connection::Identifier connectionIdentifier)

{

ASSERT(!m_connection);



m_connection = CoreIPC::Connection::createServerConnection(connectionIdentifier, this, RunLoop::main());

m_connection->open();



for (size_t i = 0; i < m_pendingMessages.size(); ++i) {

CoreIPC::Connection::OutgoingMessage& outgoingMessage = m_pendingMessages[i];

m_connection->sendMessage(outgoingMessage.messageID(), adoptPtr(outgoingMessage.arguments()));

}



m_pendingMessages.clear();



// Tell the context that we finished launching.

m_context->processDidFinishLaunching(this);

}



The IPC server in the UI Process hosts Web Processes. Connection::open is also implemented for a specific platform. Its Windows version starts listening for read and write state events on event handles to communicate with Web Processes. It's a very standard server behavior.

bool Connection::open()

{

// Start listening for read and write state events.

m_connectionQueue.registerHandle(m_readState.hEvent, WorkItem::create(this, &Connection::readEventHandler));

m_connectionQueue.registerHandle(m_writeState.hEvent, WorkItem::create(this, &Connection::writeEventHandler));



// Schedule a read.

m_connectionQueue.scheduleWork(WorkItem::create(this, &Connection::readEventHandler));



return true;

}



Finally, WebContext::processDidFinishLaunching is called to do some preparation for its internal history and pending IPC messages.

void WebContext::processDidFinishLaunching(WebProcessProxy* process)

{

// FIXME: Once we support multiple processes per context, this assertion won't hold.

ASSERT(process == m_process);



m_visitedLinkProvider.populateVisitedLinksIfNeeded();



for (size_t i = 0; i != m_pendingMessagesToPostToInjectedBundle.size(); ++i) {

pair<String, RefPtr<APIObject> >& message = m_pendingMessagesToPostToInjectedBundle[i];

m_process->send(InjectedBundleMessage::PostMessage, 0, CoreIPC::In(message.first, WebContextUserMessageEncoder(message.second.get())));

}

m_pendingMessagesToPostToInjectedBundle.clear();

}



The above call stack is what happens when a new WebContext is initialized in BrowserView::create. The next thing it does is to call the WKPageNamespaceCreate API.

WKPageNamespaceRef WKPageNamespaceCreate(WKContextRef ownerContextRef)

{

return toAPI(toImpl(ownerContextRef)->createPageNamespace());

}



It calls WebContext::createPageNamespace then WebPageNamespace::create.

class WebPageNamespace : public APIObject {

public:

static const Type APIType = TypePageNamespace;



static PassRefPtr<WebPageNamespace> create(WebContext* context)

{

return adoptRef(new WebPageNamespace(context));

}



For now it seems WebPageNamespace does nothing when created except for some reference counting.

#ifndef NDEBUG

static WTF::RefCountedLeakCounter webPageNamespaceCounter("WebPageNamespace");

#endif



WebPageNamespace::WebPageNamespace(WebContext* context)

: m_context(context)

{

#ifndef NDEBUG

webPageNamespaceCounter.increment();

#endif

}



After creating a namespace for scripting, BrowserView::create calls the WKViewCreate API. For Windows, it's just a call to WebView::create which instanciates WebView via its constructor.

WKViewRef WKViewCreate(RECT rect, WKPageNamespaceRef pageNamespaceRef, HWND parentWindow)

{

RefPtr<WebView> view = WebView::create(rect, toImpl(pageNamespaceRef), parentWindow);

return toAPI(view.release().releaseRef());

}



class WebView : public APIObject, public PageClient, WebCore::WindowMessageListener {

public:

static PassRefPtr<WebView> create(RECT rect, WebPageNamespace* pageNamespace, HWND parentWindow)

{

return adoptRef(new WebView(rect, pageNamespace, parentWindow));

}

~WebView();



// snip



virtual HWND nativeWindow();



// WebCore::WindowMessageListener

virtual void windowReceivedMessage(HWND, UINT message, WPARAM, LPARAM);



RECT m_rect;

HWND m_window;

HWND m_topLevelParentWindow;

HWND m_toolTipWindow;



HCURSOR m_lastCursorSet;

HCURSOR m_webCoreCursor;

HCURSOR m_overrideCursor;



bool m_trackingMouseLeave;

bool m_isBeingDestroyed;



RefPtr<WebPageProxy> m_page;

};



WebView::WebView(RECT rect, WebPageNamespace* pageNamespace, HWND parentWindow)

: m_rect(rect)

, m_topLevelParentWindow(0)

, m_toolTipWindow(0)

, m_lastCursorSet(0)

, m_webCoreCursor(0)

, m_overrideCursor(0)

, m_trackingMouseLeave(false)

, m_isBeingDestroyed(false)

{

registerWebViewWindowClass();



m_page = pageNamespace->createWebPage();

m_page->setPageClient(this);

m_page->setDrawingArea(ChunkedUpdateDrawingAreaProxy::create(this));



m_window = ::CreateWindowEx(0, kWebKit2WebViewWindowClassName, 0, WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN,

rect.top, rect.left, rect.right - rect.left, rect.bottom - rect.top, parentWindow ? parentWindow : HWND_MESSAGE, 0, instanceHandle(), this);

ASSERT(::IsWindow(m_window));



m_page->initializeWebPage(IntRect(rect).size());



::ShowWindow(m_window, SW_SHOW);



// FIXME: Initializing the tooltip window here matches WebKit win, but seems like something

// we could do on demand to save resources.

initializeToolTipWindow();



// Initialize the top level parent window and register it with the WindowMessageBroadcaster.

windowAncestryDidChange();

}



WebView has a native window handle (HWND) and a reference to a WebPageProxy object which is also initialized here. It has a DrawingAreaProxy as a member. Sometimes it receives an inter-process draw message via WebPageProxy::didReceiveMessage. DrawingAreaProxy is the proxy in the UI Process to the DrawingArea object in a Web Process.

void WebPageProxy::didReceiveMessage(CoreIPC::Connection* connection, CoreIPC::MessageID messageID, CoreIPC::ArgumentDecoder* arguments)

{

if (messageID.is<CoreIPC::MessageClassDrawingAreaProxy>()) {

m_drawingArea->didReceiveMessage(connection, messageID, arguments);

return;

}



didReceiveWebPageProxyMessage(connection, messageID, arguments);

}



From here on, I'll show you what the graphics update loop is like in WebKit2. You'd notice that in WebView::WebView the WebPageProxy object is initialized with ChunkedUpdateDrawingAreaProxy as its DrawingAreaProxy object. It is the default drawing strategy for the Windows version. It is a proxy to a ChunkedUpdateDrawingArea object in a Web Process.

ChunkedUpdateDrawingArea::ChunkedUpdateDrawingArea(DrawingAreaID identifier, WebPage* webPage)

: DrawingArea(ChunkedUpdateDrawingAreaType, identifier, webPage)

, m_isWaitingForUpdate(false)

, m_paintingIsSuspended(false)

, m_displayTimer(WebProcess::shared().runLoop(), this, &ChunkedUpdateDrawingArea::display)

{

}



// snip



void ChunkedUpdateDrawingArea::scheduleDisplay()

{

if (m_paintingIsSuspended)

return;



if (m_isWaitingForUpdate)

return;



if (m_dirtyRect.isEmpty())

return;



if (m_displayTimer.isActive())

return;



m_displayTimer.startOneShot(0);

}



It schedules the first update of the drawing area by pushing the ChunkedUpdateDrawingArea::display function into the RunLoop.

void ChunkedUpdateDrawingArea::display()

{

ASSERT(!m_isWaitingForUpdate);



if (m_paintingIsSuspended)

return;



if (m_dirtyRect.isEmpty())

return;



// Layout if necessary.

m_webPage->layoutIfNeeded();



IntRect dirtyRect = m_dirtyRect;

m_dirtyRect = IntRect();



// Create a new UpdateChunk and paint into it.

UpdateChunk updateChunk(dirtyRect);

paintIntoUpdateChunk(&updateChunk);



WebProcess::shared().connection()->send(DrawingAreaProxyMessage::Update, m_webPage->pageID(), CoreIPC::In(updateChunk));



m_isWaitingForUpdate = true;

m_displayTimer.stop();

}



When there is a dirty rect as the result of modified page layout, it creates an UpdateChunk object. The Windows version of UpdateChunk is a shared memory bitmap created with the CreateFileMapping API to make it accessible from multiple processes. Then the content of the dirty rect is written into the bitmap with ChunkedUpdateDrawingArea::paintIntoUpdateChunk.

UpdateChunk::UpdateChunk(const IntRect& rect)

: m_rect(rect)

{

// Create our shared memory mapping.

unsigned memorySize = rect.height() * rect.width() * 4;

m_bitmapSharedMemory = ::CreateFileMapping(INVALID_HANDLE_VALUE, 0, PAGE_READWRITE, 0, memorySize, 0);

}



void ChunkedUpdateDrawingArea::paintIntoUpdateChunk(UpdateChunk* updateChunk)

{

OwnPtr<HDC> hdc(::CreateCompatibleDC(0));



void* bits;

BitmapInfo bmp = BitmapInfo::createBottomUp(updateChunk->rect().size());

OwnPtr<HBITMAP> hbmp(::CreateDIBSection(0, &bmp, DIB_RGB_COLORS, &bits, updateChunk->memory(), 0));



HBITMAP hbmpOld = static_cast<HBITMAP>(::SelectObject(hdc.get(), hbmp.get()));



GraphicsContext gc(hdc.get());

gc.save();



// FIXME: Is this white fill needed?

RECT rect = updateChunk->rect();

::FillRect(hdc.get(), &rect, (HBRUSH)::GetStockObject(WHITE_BRUSH));

gc.translate(-updateChunk->rect().x(), -updateChunk->rect().y());



m_webPage->drawRect(gc, updateChunk->rect());



gc.restore();



// Re-select the old HBITMAP

::SelectObject(hdc.get(), hbmpOld);

}



When it's done, the DrawingAreaProxyMessage::Update IPC message is sent to the ChunkedUpdateDrawingAreaProxy in the UI Process with the bitmap created. It receives the message in ChunkedUpdateDrawingAreaProxy::didReceiveMessage and decodes the bitmap packed as an IPC payload.

void ChunkedUpdateDrawingAreaProxy::update(UpdateChunk* updateChunk)
{
if (!m_isVisible) {

// We got an update request that must have been sent before we told the web process to suspend painting.

// Don't paint this into the backing store, because that could leave the backing store in an inconsistent state.

// Instead, we will just tell the drawing area to repaint everything when we resume painting.

m_forceRepaintWhenResumingPainting = true;

} else {

// Just paint into backing store.

drawUpdateChunkIntoBackingStore(updateChunk);

}



WebPageProxy* page = this->page();

page->process()->send(DrawingAreaMessage::DidUpdate, page->pageID(), CoreIPC::In(info().id));

}



void ChunkedUpdateDrawingAreaProxy::didReceiveMessage(CoreIPC::Connection*, CoreIPC::MessageID messageID, CoreIPC::ArgumentDecoder* arguments)

{

switch (messageID.get<DrawingAreaProxyMessage::Kind>()) {

case DrawingAreaProxyMessage::Update: {

UpdateChunk updateChunk;

if (!arguments->decode(updateChunk))

return;



update(&updateChunk);

break;

}

case DrawingAreaProxyMessage::DidSetSize: {

UpdateChunk updateChunk;

if (!arguments->decode(CoreIPC::Out(updateChunk)))

return;



didSetSize(&updateChunk);

break;

}

default:

ASSERT_NOT_REACHED();

}

}


Then, in ChunkedUpdateDrawingAreaProxy::update it calls ChunkedUpdateDrawingAreaProxy::drawUpdateChunkIntoBackingStore to BitBlt from the updated image to the backing store.

void ChunkedUpdateDrawingAreaProxy::drawUpdateChunkIntoBackingStore(UpdateChunk* updateChunk)
{
ensureBackingStore();



OwnPtr<HDC> updateChunkBitmapDC(::CreateCompatibleDC(m_backingStoreDC.get()));



// Create a bitmap.

BitmapInfo bitmapInfo = BitmapInfo::createBottomUp(updateChunk->rect().size());



// Duplicate the update chunk handle.

HANDLE updateChunkHandle;

BOOL result = ::DuplicateHandle(m_webView->page()->process()->processIdentifier(), updateChunk->memory(),

::GetCurrentProcess(), &updateChunkHandle, STANDARD_RIGHTS_REQUIRED | FILE_MAP_READ | FILE_MAP_WRITE, false, DUPLICATE_CLOSE_SOURCE);



void* pixels = 0;

OwnPtr<HBITMAP> hBitmap(::CreateDIBSection(0, &bitmapInfo, DIB_RGB_COLORS, &pixels, updateChunkHandle, 0));

::SelectObject(updateChunkBitmapDC.get(), hBitmap.get());



// BitBlt from the UpdateChunk to the backing store.

::BitBlt(m_backingStoreDC.get(), updateChunk->rect().x(), updateChunk->rect().y(), updateChunk->rect().width(), updateChunk->rect().height(), updateChunkBitmapDC.get(), 0, 0, SRCCOPY);



// FIXME: We should not do this here.

::CloseHandle(updateChunkHandle);



// Invalidate the WebView's HWND.

RECT rect = updateChunk->rect();

::InvalidateRect(m_webView->window(), &rect, false);

}


As the event notification, the DrawingAreaMessage::DidUpdate IPC message is sent to the Web Process which triggers a subsequent drawing operation.

Now the page view is ready. After that, in BrowserView::create, it sets application-defined callbacks with the WKPageSetPageUIClient API to invoke appropriate UI elements on events returned from WebKit.

WKPageUIClient uiClient = {
0,              /* version */
parentWindow,   /* clientInfo */
createNewPage,
showPage,
closePage,
runJavaScriptAlert,
runJavaScriptConfirm,
runJavaScriptPrompt,
setStatusText,
mouseDidMoveOverElement,
0,          /* didNotHandleKeyEvent */
0,          /* toolbarsAreVisible */
0,          /* setToolbarsAreVisible */
0,          /* menuBarIsVisible */
0,          /* setMenuBarIsVisible */
0,          /* statusBarIsVisible */
0,          /* setStatusBarIsVisible */
0,          /* isResizable */
0,          /* setIsResizable */
0,          /* getWindowFrame */
0,          /* setWindowFrame */
0,          /* runBeforeUnloadConfirmPanel */
0,          /* didDraw */
0           /* pageDidScroll */
};

WKPageSetPageUIClient(WKViewGetPage(m_webView), &uiClient);


Though this article uses the Windows version as the primary example, the Mac version of MiniBrowser in Objective-C has more features implemented already by using other WebKit2 APIs. Besides WKPageUIClient, WKPage.h offers other kinds of clients such as WKPageLoaderClient, WKPagePolicyClient and WKPageFindClient for embedders to customize the application behavior by defining and plugging in their own callback functions.

As for other multiprocess-related implementation details, the Web Process has a VisitedLinkTable object as its member. Its hash table is on the SharedMemory object for cross-process update.

Examples of WebKit2 usage are also found in the tests for the WebKit2 API so browse them if you are interested.

Since WebKit2 is in its very early stage, the code size is still kept compact so reading its source code and comprehending its structure is super easy and fun. Going back to my original question at the beginning of this article, it seems the basic functions of WebKit2 are all in after 6 months. Though my personal attention has been on Chromium development rather than on WebKit, it may be interesting to see how these 2 interact each other in future.

コメント