2011-01-24 Maciej Stachowiak <mjs@apple.com>
[WebKit.git] / Source / WebKit2 / UIProcess / WebContext.cpp
1 /*
2  * Copyright (C) 2010 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #include "WebContext.h"
27
28 #include "DownloadProxy.h"
29 #include "ImmutableArray.h"
30 #include "InjectedBundleMessageKinds.h"
31 #include "RunLoop.h"
32 #include "SandboxExtension.h"
33 #include "TextChecker.h"
34 #include "WKContextPrivate.h"
35 #include "WebContextMessageKinds.h"
36 #include "WebContextUserMessageCoders.h"
37 #include "WebCoreArgumentCoders.h"
38 #include "WebDatabaseManagerProxy.h"
39 #include "WebGeolocationManagerProxy.h"
40 #include "WebPageGroup.h"
41 #include "WebMemorySampler.h"
42 #include "WebProcessCreationParameters.h"
43 #include "WebProcessManager.h"
44 #include "WebProcessMessages.h"
45 #include "WebProcessProxy.h"
46 #include <WebCore/Language.h>
47 #include <WebCore/LinkHash.h>
48 #include <wtf/CurrentTime.h>
49
50 #ifndef NDEBUG
51 #include <wtf/RefCountedLeakCounter.h>
52 #endif
53
54 using namespace WebCore;
55
56 namespace WebKit {
57
58 #ifndef NDEBUG
59 static WTF::RefCountedLeakCounter webContextCounter("WebContext");
60 #endif
61
62 WebContext* WebContext::sharedProcessContext()
63 {
64     WTF::initializeMainThread();
65     RunLoop::initializeMainRunLoop();
66     static WebContext* context = adoptRef(new WebContext(ProcessModelSharedSecondaryProcess, String())).leakRef();
67     return context;
68 }
69
70 WebContext* WebContext::sharedThreadContext()
71 {
72     RunLoop::initializeMainRunLoop();
73     static WebContext* context = adoptRef(new WebContext(ProcessModelSharedSecondaryThread, String())).leakRef();
74     return context;
75 }
76
77 PassRefPtr<WebContext> WebContext::create(const String& injectedBundlePath)
78 {
79     WTF::initializeMainThread();
80     RunLoop::initializeMainRunLoop();
81     return adoptRef(new WebContext(ProcessModelSecondaryProcess, injectedBundlePath));
82 }
83     
84 WebContext::WebContext(ProcessModel processModel, const String& injectedBundlePath)
85     : m_processModel(processModel)
86     , m_defaultPageGroup(WebPageGroup::create())
87     , m_injectedBundlePath(injectedBundlePath)
88     , m_visitedLinkProvider(this)
89     , m_alwaysUsesComplexTextCodePath(false)
90     , m_cacheModel(CacheModelDocumentViewer)
91     , m_clearResourceCachesForNewWebProcess(false)
92     , m_clearApplicationCacheForNewWebProcess(false)
93     , m_memorySamplerEnabled(false)
94     , m_memorySamplerInterval(1400.0)
95     , m_databaseManagerProxy(WebDatabaseManagerProxy::create(this))
96     , m_geolocationManagerProxy(WebGeolocationManagerProxy::create(this))
97 #if PLATFORM(WIN)
98     , m_shouldPaintNativeControls(true)
99 #endif
100 {
101     addLanguageChangeObserver(this, languageChanged);
102
103 #ifndef NDEBUG
104     webContextCounter.increment();
105 #endif
106 }
107
108 WebContext::~WebContext()
109 {
110     removeLanguageChangeObserver(this);
111
112     WebProcessManager::shared().contextWasDestroyed(this);
113
114     m_geolocationManagerProxy->invalidate();
115     m_geolocationManagerProxy->clearContext();
116
117     m_databaseManagerProxy->invalidate();
118     m_databaseManagerProxy->clearContext();
119
120 #ifndef NDEBUG
121     webContextCounter.decrement();
122 #endif
123 }
124
125 void WebContext::initializeInjectedBundleClient(const WKContextInjectedBundleClient* client)
126 {
127     m_injectedBundleClient.initialize(client);
128 }
129
130 void WebContext::initializeHistoryClient(const WKContextHistoryClient* client)
131 {
132     m_historyClient.initialize(client);
133     
134     if (!hasValidProcess())
135         return;
136         
137     m_process->send(Messages::WebProcess::SetShouldTrackVisitedLinks(m_historyClient.shouldTrackVisitedLinks()), 0);
138 }
139
140 void WebContext::initializeDownloadClient(const WKContextDownloadClient* client)
141 {
142     m_downloadClient.initialize(client);
143 }
144     
145 void WebContext::languageChanged(void* context)
146 {
147     static_cast<WebContext*>(context)->languageChanged();
148 }
149
150 void WebContext::languageChanged()
151 {
152     if (!hasValidProcess())
153         return;
154
155     m_process->send(Messages::WebProcess::LanguageChanged(defaultLanguage()), 0);
156 }
157
158 void WebContext::ensureWebProcess()
159 {
160     if (m_process)
161         return;
162
163     m_process = WebProcessManager::shared().getWebProcess(this);
164
165     WebProcessCreationParameters parameters;
166
167     parameters.applicationCacheDirectory = applicationCacheDirectory();
168
169     if (!injectedBundlePath().isEmpty()) {
170         parameters.injectedBundlePath = injectedBundlePath();
171
172         SandboxExtension::createHandle(parameters.injectedBundlePath, SandboxExtension::ReadOnly, parameters.injectedBundlePathExtensionHandle);
173     }
174
175     parameters.shouldTrackVisitedLinks = m_historyClient.shouldTrackVisitedLinks();
176     parameters.cacheModel = m_cacheModel;
177     parameters.languageCode = defaultLanguage();
178     parameters.applicationCacheDirectory = applicationCacheDirectory();
179     parameters.databaseDirectory = databaseDirectory();
180     parameters.clearResourceCaches = m_clearResourceCachesForNewWebProcess;
181     parameters.clearApplicationCache = m_clearApplicationCacheForNewWebProcess;
182 #if PLATFORM(MAC)
183     parameters.presenterApplicationPid = getpid();
184 #endif
185
186     m_clearResourceCachesForNewWebProcess = false;
187     m_clearApplicationCacheForNewWebProcess = false;
188     
189     copyToVector(m_schemesToRegisterAsEmptyDocument, parameters.urlSchemesRegistererdAsEmptyDocument);
190     copyToVector(m_schemesToRegisterAsSecure, parameters.urlSchemesRegisteredAsSecure);
191     copyToVector(m_schemesToSetDomainRelaxationForbiddenFor, parameters.urlSchemesForWhichDomainRelaxationIsForbidden);
192
193     parameters.shouldAlwaysUseComplexTextCodePath = m_alwaysUsesComplexTextCodePath;
194
195     parameters.textCheckerState = TextChecker::state();
196
197     // Add any platform specific parameters
198     platformInitializeWebProcess(parameters);
199
200     m_process->send(Messages::WebProcess::InitializeWebProcess(parameters, WebContextUserMessageEncoder(m_injectedBundleInitializationUserData.get())), 0);
201
202     for (size_t i = 0; i != m_pendingMessagesToPostToInjectedBundle.size(); ++i) {
203         pair<String, RefPtr<APIObject> >& message = m_pendingMessagesToPostToInjectedBundle[i];
204         m_process->send(InjectedBundleMessage::PostMessage, 0, CoreIPC::In(message.first, WebContextUserMessageEncoder(message.second.get())));
205     }
206     m_pendingMessagesToPostToInjectedBundle.clear();
207 }
208
209 void WebContext::processDidFinishLaunching(WebProcessProxy* process)
210 {
211     // FIXME: Once we support multiple processes per context, this assertion won't hold.
212     ASSERT_UNUSED(process, process == m_process);
213
214     m_visitedLinkProvider.processDidFinishLaunching();
215     
216     // Sometimes the memorySampler gets initialized after process initialization has happened but before the process has finished launching
217     // so check if it needs to be started here
218     if(m_memorySamplerEnabled) {
219         SandboxExtension::Handle sampleLogSandboxHandle;        
220         double now = WTF::currentTime();
221         String sampleLogFilePath = String::format("WebProcess%llu", static_cast<uint64_t>(now));
222         sampleLogFilePath = SandboxExtension::createHandleForTemporaryFile(sampleLogFilePath, SandboxExtension::WriteOnly, sampleLogSandboxHandle);
223         
224         m_process->send(Messages::WebProcess::StartMemorySampler(sampleLogSandboxHandle, sampleLogFilePath, m_memorySamplerInterval), 0);
225     }
226 }
227
228 void WebContext::processDidClose(WebProcessProxy* process)
229 {
230     // FIXME: Once we support multiple processes per context, this assertion won't hold.
231     ASSERT_UNUSED(process, process == m_process);
232
233     m_visitedLinkProvider.processDidClose();
234
235     // Invalidate all outstanding downloads.
236     for (HashMap<uint64_t, RefPtr<DownloadProxy> >::iterator::Values it = m_downloads.begin().values(), end = m_downloads.end().values(); it != end; ++it) {
237         (*it)->processDidClose();
238         (*it)->invalidate();
239     }
240
241     m_downloads.clear();
242
243     m_databaseManagerProxy->invalidate();
244     m_geolocationManagerProxy->invalidate();
245
246     m_process = 0;
247 }
248
249 WebPageProxy* WebContext::createWebPage(PageClient* pageClient, WebPageGroup* pageGroup)
250 {
251     ensureWebProcess();
252
253     if (!pageGroup)
254         pageGroup = m_defaultPageGroup.get();
255
256     return m_process->createWebPage(pageClient, this, pageGroup);
257 }
258
259 void WebContext::relaunchProcessIfNecessary()
260 {
261     ensureWebProcess();
262 }
263
264 void WebContext::download(WebPageProxy* initiatingPage, const ResourceRequest& request)
265 {
266     uint64_t downloadID = createDownloadProxy();
267     uint64_t initiatingPageID = initiatingPage ? initiatingPage->pageID() : 0;
268
269     process()->send(Messages::WebProcess::DownloadRequest(downloadID, initiatingPageID, request), 0);
270 }
271
272 void WebContext::postMessageToInjectedBundle(const String& messageName, APIObject* messageBody)
273 {
274     if (!m_process || !m_process->canSendMessage()) {
275         m_pendingMessagesToPostToInjectedBundle.append(make_pair(messageName, messageBody));
276         return;
277     }
278
279     // FIXME: We should consider returning false from this function if the messageBody cannot
280     // be encoded.
281     m_process->send(InjectedBundleMessage::PostMessage, 0, CoreIPC::In(messageName, WebContextUserMessageEncoder(messageBody)));
282 }
283
284 // InjectedBundle client
285
286 void WebContext::didReceiveMessageFromInjectedBundle(const String& messageName, APIObject* messageBody)
287 {
288     m_injectedBundleClient.didReceiveMessageFromInjectedBundle(this, messageName, messageBody);
289 }
290
291 void WebContext::didReceiveSynchronousMessageFromInjectedBundle(const String& messageName, APIObject* messageBody, RefPtr<APIObject>& returnData)
292 {
293     m_injectedBundleClient.didReceiveSynchronousMessageFromInjectedBundle(this, messageName, messageBody, returnData);
294 }
295
296 // HistoryClient
297
298 void WebContext::didNavigateWithNavigationData(uint64_t pageID, const WebNavigationDataStore& store, uint64_t frameID) 
299 {
300     WebFrameProxy* frame = m_process->webFrame(frameID);
301     if (!frame->page())
302         return;
303     
304     m_historyClient.didNavigateWithNavigationData(this, frame->page(), store, frame);
305 }
306
307 void WebContext::didPerformClientRedirect(uint64_t pageID, const String& sourceURLString, const String& destinationURLString, uint64_t frameID)
308 {
309     WebFrameProxy* frame = m_process->webFrame(frameID);
310     if (!frame->page())
311         return;
312     
313     m_historyClient.didPerformClientRedirect(this, frame->page(), sourceURLString, destinationURLString, frame);
314 }
315
316 void WebContext::didPerformServerRedirect(uint64_t pageID, const String& sourceURLString, const String& destinationURLString, uint64_t frameID)
317 {
318     WebFrameProxy* frame = m_process->webFrame(frameID);
319     if (!frame->page())
320         return;
321     
322     m_historyClient.didPerformServerRedirect(this, frame->page(), sourceURLString, destinationURLString, frame);
323 }
324
325 void WebContext::didUpdateHistoryTitle(uint64_t pageID, const String& title, const String& url, uint64_t frameID)
326 {
327     WebFrameProxy* frame = m_process->webFrame(frameID);
328     if (!frame->page())
329         return;
330
331     m_historyClient.didUpdateHistoryTitle(this, frame->page(), title, url, frame);
332 }
333
334 void WebContext::populateVisitedLinks()
335 {
336     m_historyClient.populateVisitedLinks(this);
337 }
338
339 WebContext::Statistics& WebContext::statistics()
340 {
341     static Statistics statistics = Statistics();
342
343     return statistics;
344 }
345
346 void WebContext::setAdditionalPluginsDirectory(const String& directory)
347 {
348     Vector<String> directories;
349     directories.append(directory);
350
351     m_pluginInfoStore.setAdditionalPluginsDirectories(directories);
352 }
353
354 void WebContext::setAlwaysUsesComplexTextCodePath(bool alwaysUseComplexText)
355 {
356     m_alwaysUsesComplexTextCodePath = alwaysUseComplexText;
357
358     if (!hasValidProcess())
359         return;
360
361     m_process->send(Messages::WebProcess::SetAlwaysUsesComplexTextCodePath(alwaysUseComplexText), 0);
362 }
363
364 void WebContext::registerURLSchemeAsEmptyDocument(const String& urlScheme)
365 {
366     m_schemesToRegisterAsEmptyDocument.add(urlScheme);
367
368     if (!hasValidProcess())
369         return;
370
371     m_process->send(Messages::WebProcess::RegisterURLSchemeAsEmptyDocument(urlScheme), 0);
372 }
373
374 void WebContext::registerURLSchemeAsSecure(const String& urlScheme)
375 {
376     m_schemesToRegisterAsSecure.add(urlScheme);
377
378     if (!hasValidProcess())
379         return;
380
381     m_process->send(Messages::WebProcess::RegisterURLSchemeAsSecure(urlScheme), 0);
382 }
383
384 void WebContext::setDomainRelaxationForbiddenForURLScheme(const String& urlScheme)
385 {
386     m_schemesToSetDomainRelaxationForbiddenFor.add(urlScheme);
387
388     if (!hasValidProcess())
389         return;
390
391     m_process->send(Messages::WebProcess::SetDomainRelaxationForbiddenForURLScheme(urlScheme), 0);
392 }
393
394 void WebContext::setCacheModel(CacheModel cacheModel)
395 {
396     m_cacheModel = cacheModel;
397
398     if (!hasValidProcess())
399         return;
400     m_process->send(Messages::WebProcess::SetCacheModel(static_cast<uint32_t>(m_cacheModel)), 0);
401 }
402
403 void WebContext::addVisitedLink(const String& visitedURL)
404 {
405     if (visitedURL.isEmpty())
406         return;
407
408     LinkHash linkHash = visitedLinkHash(visitedURL.characters(), visitedURL.length());
409     addVisitedLinkHash(linkHash);
410 }
411
412 void WebContext::addVisitedLinkHash(LinkHash linkHash)
413 {
414     m_visitedLinkProvider.addVisitedLink(linkHash);
415 }
416
417 void WebContext::getPlugins(bool refresh, Vector<PluginInfo>& plugins)
418 {
419     if (refresh)
420         pluginInfoStore()->refresh();
421     pluginInfoStore()->getPlugins(plugins);
422 }
423
424 void WebContext::getPluginPath(const String& mimeType, const String& urlString, String& pluginPath)
425 {
426     String newMimeType = mimeType.lower();
427
428     PluginInfoStore::Plugin plugin = pluginInfoStore()->findPlugin(newMimeType, KURL(ParsedURLString, urlString));
429     if (!plugin.path)
430         return;
431
432     pluginPath = plugin.path;
433 }
434
435 uint64_t WebContext::createDownloadProxy()
436 {
437     RefPtr<DownloadProxy> downloadProxy = DownloadProxy::create(this);
438     uint64_t downloadID = downloadProxy->downloadID();
439
440     m_downloads.set(downloadID, downloadProxy.release());
441
442     return downloadID;
443 }
444
445 void WebContext::downloadFinished(DownloadProxy* downloadProxy)
446 {
447     ASSERT(m_downloads.contains(downloadProxy->downloadID()));
448
449     downloadProxy->invalidate();
450     m_downloads.remove(downloadProxy->downloadID());
451 }
452
453 // FIXME: This is not the ideal place for this function.
454 HashSet<String, CaseFoldingHash> WebContext::pdfAndPostScriptMIMETypes()
455 {
456     HashSet<String, CaseFoldingHash> mimeTypes;
457
458     mimeTypes.add("application/pdf");
459     mimeTypes.add("application/postscript");
460     mimeTypes.add("text/pdf");
461     
462     return mimeTypes;
463 }
464
465 void WebContext::didReceiveMessage(CoreIPC::Connection* connection, CoreIPC::MessageID messageID, CoreIPC::ArgumentDecoder* arguments)
466 {
467     if (messageID.is<CoreIPC::MessageClassWebContext>()) {
468         didReceiveWebContextMessage(connection, messageID, arguments);
469         return;
470     }
471
472     if (messageID.is<CoreIPC::MessageClassDownloadProxy>()) {
473         if (DownloadProxy* downloadProxy = m_downloads.get(arguments->destinationID()).get())
474             downloadProxy->didReceiveDownloadProxyMessage(connection, messageID, arguments);
475         
476         return;
477     }
478
479     if (messageID.is<CoreIPC::MessageClassWebDatabaseManagerProxy>()) {
480         m_databaseManagerProxy->didReceiveWebDatabaseManagerProxyMessage(connection, messageID, arguments);
481         return;
482     }
483
484     if (messageID.is<CoreIPC::MessageClassWebGeolocationManagerProxy>()) {
485         m_geolocationManagerProxy->didReceiveMessage(connection, messageID, arguments);
486         return;
487     }
488
489     switch (messageID.get<WebContextLegacyMessage::Kind>()) {
490         case WebContextLegacyMessage::PostMessage: {
491             String messageName;
492             RefPtr<APIObject> messageBody;
493             WebContextUserMessageDecoder messageDecoder(messageBody, this);
494             if (!arguments->decode(CoreIPC::Out(messageName, messageDecoder)))
495                 return;
496
497             didReceiveMessageFromInjectedBundle(messageName, messageBody.get());
498             return;
499         }
500         case WebContextLegacyMessage::PostSynchronousMessage:
501             ASSERT_NOT_REACHED();
502     }
503
504     ASSERT_NOT_REACHED();
505 }
506
507 CoreIPC::SyncReplyMode WebContext::didReceiveSyncMessage(CoreIPC::Connection* connection, CoreIPC::MessageID messageID, CoreIPC::ArgumentDecoder* arguments, CoreIPC::ArgumentEncoder* reply)
508 {
509     if (messageID.is<CoreIPC::MessageClassWebContext>())
510         return didReceiveSyncWebContextMessage(connection, messageID, arguments, reply);
511
512     if (messageID.is<CoreIPC::MessageClassDownloadProxy>()) {
513         if (DownloadProxy* downloadProxy = m_downloads.get(arguments->destinationID()).get())
514             return downloadProxy->didReceiveSyncDownloadProxyMessage(connection, messageID, arguments, reply);
515
516         return CoreIPC::AutomaticReply;
517     }
518     
519     switch (messageID.get<WebContextLegacyMessage::Kind>()) {
520         case WebContextLegacyMessage::PostSynchronousMessage: {
521             // FIXME: We should probably encode something in the case that the arguments do not decode correctly.
522
523             String messageName;
524             RefPtr<APIObject> messageBody;
525             WebContextUserMessageDecoder messageDecoder(messageBody, this);
526             if (!arguments->decode(CoreIPC::Out(messageName, messageDecoder)))
527                 return CoreIPC::AutomaticReply;
528
529             RefPtr<APIObject> returnData;
530             didReceiveSynchronousMessageFromInjectedBundle(messageName, messageBody.get(), returnData);
531             reply->encode(CoreIPC::In(WebContextUserMessageEncoder(returnData.get())));
532             return CoreIPC::AutomaticReply;
533         }
534         case WebContextLegacyMessage::PostMessage:
535             ASSERT_NOT_REACHED();
536     }
537
538     return CoreIPC::AutomaticReply;
539 }
540
541 void WebContext::clearResourceCaches()
542 {
543     if (!hasValidProcess()) {
544         // FIXME <rdar://problem/8727879>: Setting this flag ensures that the next time a WebProcess is created, this request to
545         // clear the resource cache will be respected. But if the user quits the application before another WebProcess is created,
546         // their request will be ignored.
547         m_clearResourceCachesForNewWebProcess = true;
548         return;
549     }
550
551     m_process->send(Messages::WebProcess::ClearResourceCaches(), 0);
552 }
553
554 void WebContext::clearApplicationCache()
555 {
556     if (!hasValidProcess()) {
557         // FIXME <rdar://problem/8727879>: Setting this flag ensures that the next time a WebProcess is created, this request to
558         // clear the application cache will be respected. But if the user quits the application before another WebProcess is created,
559         // their request will be ignored.
560         m_clearApplicationCacheForNewWebProcess = true;
561         return;
562     }
563
564     m_process->send(Messages::WebProcess::ClearApplicationCache(), 0);
565 }
566     
567 void WebContext::startMemorySampler(const double interval)
568 {    
569     // For new WebProcesses we will also want to start the Memory Sampler
570     m_memorySamplerEnabled = true;
571     m_memorySamplerInterval = interval;
572     
573     // For UIProcess
574 #if ENABLE(MEMORY_SAMPLER)
575     WebMemorySampler::shared()->start(interval);
576 #endif
577     
578     if (!hasValidProcess())
579         return;
580     
581     // For WebProcess
582     SandboxExtension::Handle sampleLogSandboxHandle;    
583     double now = WTF::currentTime();
584     String sampleLogFilePath = String::format("WebProcess%llu", static_cast<uint64_t>(now));
585     sampleLogFilePath = SandboxExtension::createHandleForTemporaryFile(sampleLogFilePath, SandboxExtension::WriteOnly, sampleLogSandboxHandle);
586     
587     m_process->send(Messages::WebProcess::StartMemorySampler(sampleLogSandboxHandle, sampleLogFilePath, interval), 0);
588 }
589
590 void WebContext::stopMemorySampler()
591 {    
592     // For WebProcess
593     m_memorySamplerEnabled = false;
594     
595     // For UIProcess
596 #if ENABLE(MEMORY_SAMPLER)
597     WebMemorySampler::shared()->stop();
598 #endif
599     
600     if (!hasValidProcess())
601         return;
602     
603     m_process->send(Messages::WebProcess::StopMemorySampler(), 0);
604 }
605
606 String WebContext::databaseDirectory() const
607 {
608     if (!m_overrideDatabaseDirectory.isEmpty())
609         return m_overrideDatabaseDirectory;
610
611     return platformDefaultDatabaseDirectory();
612 }
613
614 } // namespace WebKit