1/*
2 * Copyright (C) 2012 Samsung Electronics
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Library General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12 * Library General Public License for more details.
13 *
14 * You should have received a copy of the GNU Library General Public License
15 * along with this program; see the file COPYING.LIB.  If not, write to
16 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 * Boston, MA 02110-1301, USA.
18 *
19 */
20
21#include "config.h"
22#include "ewk_context.h"
23
24#include "BatteryProvider.h"
25#include "ContextHistoryClientEfl.h"
26#include "DownloadManagerEfl.h"
27#include "RequestManagerClientEfl.h"
28#include "WKAPICast.h"
29#include "WKContextPrivate.h"
30#include "WKContextSoup.h"
31#include "WKNumber.h"
32#include "WKString.h"
33#include "WebContext.h"
34#include "WebIconDatabase.h"
35#include "ewk_application_cache_manager_private.h"
36#include "ewk_context_private.h"
37#include "ewk_cookie_manager_private.h"
38#include "ewk_database_manager_private.h"
39#include "ewk_favicon_database_private.h"
40#include "ewk_private.h"
41#include "ewk_storage_manager_private.h"
42#include "ewk_url_scheme_request_private.h"
43#include <JavaScriptCore/JSContextRef.h>
44#include <WebCore/FileSystem.h>
45#include <WebCore/IconDatabase.h>
46#include <WebCore/Language.h>
47#include <wtf/HashMap.h>
48#include <wtf/NeverDestroyed.h>
49#include <wtf/text/WTFString.h>
50
51#if ENABLE(SPELLCHECK)
52#include "TextCheckerClientEfl.h"
53#endif
54
55using namespace WebCore;
56using namespace WebKit;
57
58typedef HashMap<WKContextRef, EwkContext*> ContextMap;
59
60static inline ContextMap& contextMap()
61{
62    static NeverDestroyed<ContextMap> map;
63    return map;
64}
65
66EwkContext::EwkContext(WKContextRef context)
67    : m_context(context)
68    , m_databaseManager(std::make_unique<EwkDatabaseManager>(WKContextGetDatabaseManager(context)))
69    , m_storageManager(std::make_unique<EwkStorageManager>(WKContextGetKeyValueStorageManager(context)))
70#if ENABLE(BATTERY_STATUS)
71    , m_batteryProvider(BatteryProvider::create(context))
72#endif
73    , m_downloadManager(std::make_unique<DownloadManagerEfl>(context))
74    , m_requestManagerClient(std::make_unique<RequestManagerClientEfl>(context))
75    , m_historyClient(std::make_unique<ContextHistoryClientEfl>(context))
76    , m_jsGlobalContext(nullptr)
77{
78    ContextMap::AddResult result = contextMap().add(context, this);
79    ASSERT_UNUSED(result, result.isNewEntry);
80
81#if ENABLE(MEMORY_SAMPLER)
82    static bool initializeMemorySampler = false;
83    static const char environmentVariable[] = "SAMPLE_MEMORY";
84
85    if (!initializeMemorySampler && getenv(environmentVariable)) {
86        WKContextStartMemorySampler(context, adoptWK(WKDoubleCreate(0.0)).get());
87        initializeMemorySampler = true;
88    }
89#endif
90
91#if ENABLE(SPELLCHECK)
92    // Load the default dictionary to show context menu spellchecking items
93    // independently of checking spelling while typing setting.
94    TextCheckerClientEfl::instance().ensureSpellCheckingLanguage();
95#endif
96
97    m_callbackForMessageFromInjectedBundle.callback = nullptr;
98    m_callbackForMessageFromInjectedBundle.userData = nullptr;
99}
100
101EwkContext::~EwkContext()
102{
103    ASSERT(contextMap().get(m_context.get()) == this);
104
105    if (m_jsGlobalContext)
106        JSGlobalContextRelease(m_jsGlobalContext);
107
108    contextMap().remove(m_context.get());
109}
110
111PassRefPtr<EwkContext> EwkContext::findOrCreateWrapper(WKContextRef context)
112{
113    if (contextMap().contains(context))
114        return contextMap().get(context);
115
116    return adoptRef(new EwkContext(context));
117}
118
119PassRefPtr<EwkContext> EwkContext::create()
120{
121    return adoptRef(new EwkContext(adoptWK(WKContextCreate()).get()));
122}
123
124PassRefPtr<EwkContext> EwkContext::create(const String& injectedBundlePath)
125{
126    if (!fileExists(injectedBundlePath))
127        return 0;
128
129    WKRetainPtr<WKStringRef> path = adoptWK(toCopiedAPI(injectedBundlePath));
130
131    return adoptRef(new EwkContext(adoptWK(WKContextCreateWithInjectedBundlePath(path.get())).get()));
132}
133
134EwkContext* EwkContext::defaultContext()
135{
136    static EwkContext* defaultInstance = create().leakRef();
137
138    return defaultInstance;
139}
140
141EwkApplicationCacheManager* EwkContext::applicationCacheManager()
142{
143    if (!m_applicationCacheManager)
144        m_applicationCacheManager = std::make_unique<EwkApplicationCacheManager>(WKContextGetApplicationCacheManager(m_context.get()));
145
146    return m_applicationCacheManager.get();
147}
148
149EwkCookieManager* EwkContext::cookieManager()
150{
151    if (!m_cookieManager)
152        m_cookieManager = std::make_unique<EwkCookieManager>(WKContextGetCookieManager(m_context.get()));
153
154    return m_cookieManager.get();
155}
156
157EwkDatabaseManager* EwkContext::databaseManager()
158{
159    return m_databaseManager.get();
160}
161
162void EwkContext::ensureFaviconDatabase()
163{
164    if (m_faviconDatabase)
165        return;
166
167    m_faviconDatabase = std::make_unique<EwkFaviconDatabase>(WKContextGetIconDatabase(m_context.get()));
168}
169
170bool EwkContext::setFaviconDatabaseDirectoryPath(const String& databaseDirectory)
171{
172    ensureFaviconDatabase();
173    // FIXME: Hole in WK2 API layering must be fixed when C API is available.
174    WebIconDatabase* iconDatabase = toImpl(WKContextGetIconDatabase(m_context.get()));
175
176    // The database path is already open so its path was
177    // already set.
178    if (iconDatabase->isOpen())
179        return false;
180
181    // If databaseDirectory is empty, we use the default database path for the platform.
182    String databasePath = databaseDirectory.isEmpty() ? toImpl(m_context.get())->iconDatabasePath() : pathByAppendingComponent(databaseDirectory, WebCore::IconDatabase::defaultDatabaseFilename());
183    toImpl(m_context.get())->setIconDatabasePath(databasePath);
184
185    return true;
186}
187
188EwkFaviconDatabase* EwkContext::faviconDatabase()
189{
190    ensureFaviconDatabase();
191    ASSERT(m_faviconDatabase);
192
193    return m_faviconDatabase.get();
194}
195
196EwkStorageManager* EwkContext::storageManager() const
197{
198    return m_storageManager.get();
199}
200
201RequestManagerClientEfl* EwkContext::requestManager()
202{
203    return m_requestManagerClient.get();
204}
205
206void EwkContext::addVisitedLink(const String& visitedURL)
207{
208    WKContextAddVisitedLink(m_context.get(), adoptWK(toCopiedAPI(visitedURL)).get());
209}
210
211// Ewk_Cache_Model enum validation
212inline WKCacheModel toWKCacheModel(Ewk_Cache_Model cacheModel)
213{
214    switch (cacheModel) {
215    case EWK_CACHE_MODEL_DOCUMENT_VIEWER:
216        return kWKCacheModelDocumentViewer;
217    case EWK_CACHE_MODEL_DOCUMENT_BROWSER:
218        return kWKCacheModelDocumentBrowser;
219    case EWK_CACHE_MODEL_PRIMARY_WEBBROWSER:
220        return kWKCacheModelPrimaryWebBrowser;
221    }
222    ASSERT_NOT_REACHED();
223
224    return kWKCacheModelDocumentViewer;
225}
226
227void EwkContext::setCacheModel(Ewk_Cache_Model cacheModel)
228{
229    WKContextSetCacheModel(m_context.get(), toWKCacheModel(cacheModel));
230}
231
232Ewk_Cache_Model EwkContext::cacheModel() const
233{
234    return static_cast<Ewk_Cache_Model>(WKContextGetCacheModel(m_context.get()));
235}
236
237inline WKProcessModel toWKProcessModel(Ewk_Process_Model processModel)
238{
239    switch (processModel) {
240    case EWK_PROCESS_MODEL_SHARED_SECONDARY:
241        return kWKProcessModelSharedSecondaryProcess;
242    case EWK_PROCESS_MODEL_MULTIPLE_SECONDARY:
243        return kWKProcessModelMultipleSecondaryProcesses;
244    }
245    ASSERT_NOT_REACHED();
246
247    return kWKProcessModelSharedSecondaryProcess;
248}
249
250void EwkContext::setProcessModel(Ewk_Process_Model processModel)
251{
252    WKProcessModel newWKProcessModel = toWKProcessModel(processModel);
253
254    if (WKContextGetProcessModel(m_context.get()) == newWKProcessModel)
255        return;
256
257    WKContextSetUsesNetworkProcess(m_context.get(), newWKProcessModel == kWKProcessModelMultipleSecondaryProcesses);
258    WKContextSetProcessModel(m_context.get(), newWKProcessModel);
259}
260
261inline Ewk_Process_Model toEwkProcessModel(WKProcessModel processModel)
262{
263    switch (processModel) {
264    case kWKProcessModelSharedSecondaryProcess:
265        return EWK_PROCESS_MODEL_SHARED_SECONDARY;
266    case kWKProcessModelMultipleSecondaryProcesses:
267        return EWK_PROCESS_MODEL_MULTIPLE_SECONDARY;
268    }
269    ASSERT_NOT_REACHED();
270
271    return EWK_PROCESS_MODEL_SHARED_SECONDARY;
272}
273
274Ewk_Process_Model EwkContext::processModel() const
275{
276    return toEwkProcessModel(WKContextGetProcessModel(m_context.get()));
277}
278
279#if ENABLE(NETSCAPE_PLUGIN_API)
280void EwkContext::setAdditionalPluginPath(const String& path)
281{
282    // FIXME: Hole in WK2 API layering must be fixed when C API is available.
283    toImpl(m_context.get())->setAdditionalPluginsDirectory(path);
284}
285#endif
286
287void EwkContext::clearResourceCache()
288{
289    WKResourceCacheManagerClearCacheForAllOrigins(WKContextGetResourceCacheManager(m_context.get()), WKResourceCachesToClearAll);
290}
291
292
293JSGlobalContextRef EwkContext::jsGlobalContext()
294{
295    if (!m_jsGlobalContext)
296        m_jsGlobalContext = JSGlobalContextCreate(0);
297
298    return m_jsGlobalContext;
299}
300
301Ewk_Application_Cache_Manager* ewk_context_application_cache_manager_get(const Ewk_Context* ewkContext)
302{
303    EWK_OBJ_GET_IMPL_OR_RETURN(const EwkContext, ewkContext, impl, nullptr);
304
305    return const_cast<EwkContext*>(impl)->applicationCacheManager();
306}
307
308Ewk_Cookie_Manager* ewk_context_cookie_manager_get(const Ewk_Context* ewkContext)
309{
310    EWK_OBJ_GET_IMPL_OR_RETURN(const EwkContext, ewkContext, impl, nullptr);
311
312    return const_cast<EwkContext*>(impl)->cookieManager();
313}
314
315Ewk_Database_Manager* ewk_context_database_manager_get(const Ewk_Context* ewkContext)
316{
317    EWK_OBJ_GET_IMPL_OR_RETURN(const EwkContext, ewkContext, impl, nullptr);
318
319    return const_cast<EwkContext*>(impl)->databaseManager();
320}
321
322Eina_Bool ewk_context_favicon_database_directory_set(Ewk_Context* ewkContext, const char* directoryPath)
323{
324    EWK_OBJ_GET_IMPL_OR_RETURN(EwkContext, ewkContext, impl, false);
325
326    return impl->setFaviconDatabaseDirectoryPath(String::fromUTF8(directoryPath));
327}
328
329Ewk_Favicon_Database* ewk_context_favicon_database_get(const Ewk_Context* ewkContext)
330{
331    EWK_OBJ_GET_IMPL_OR_RETURN(const EwkContext, ewkContext, impl, nullptr);
332
333    return const_cast<EwkContext*>(impl)->faviconDatabase();
334}
335
336Ewk_Storage_Manager* ewk_context_storage_manager_get(const Ewk_Context* ewkContext)
337{
338    EWK_OBJ_GET_IMPL_OR_RETURN(const EwkContext, ewkContext, impl, nullptr);
339
340    return impl->storageManager();
341}
342
343DownloadManagerEfl* EwkContext::downloadManager() const
344{
345    return m_downloadManager.get();
346}
347
348ContextHistoryClientEfl* EwkContext::historyClient()
349{
350    return m_historyClient.get();
351}
352
353static inline EwkContext* toEwkContext(const void* clientInfo)
354{
355    return static_cast<EwkContext*>(const_cast<void*>(clientInfo));
356}
357
358void EwkContext::didReceiveMessageFromInjectedBundle(WKContextRef, WKStringRef messageName, WKTypeRef messageBody, const void* clientInfo)
359{
360    toEwkContext(clientInfo)->processReceivedMessageFromInjectedBundle(messageName, messageBody, nullptr);
361}
362
363void EwkContext::didReceiveSynchronousMessageFromInjectedBundle(WKContextRef, WKStringRef messageName, WKTypeRef messageBody, WKTypeRef* returnData, const void* clientInfo)
364{
365    toEwkContext(clientInfo)->processReceivedMessageFromInjectedBundle(messageName, messageBody, returnData);
366}
367
368void EwkContext::setMessageFromInjectedBundleCallback(Ewk_Context_Message_From_Injected_Bundle_Cb callback, void* userData)
369{
370    m_callbackForMessageFromInjectedBundle.userData = userData;
371
372    if (m_callbackForMessageFromInjectedBundle.callback == callback)
373        return;
374
375    if (!m_callbackForMessageFromInjectedBundle.callback) {
376        WKContextInjectedBundleClientV1 client;
377        memset(&client, 0, sizeof(client));
378
379        client.base.version = 1;
380        client.base.clientInfo = this;
381        client.didReceiveMessageFromInjectedBundle = didReceiveMessageFromInjectedBundle;
382        client.didReceiveSynchronousMessageFromInjectedBundle = didReceiveSynchronousMessageFromInjectedBundle;
383
384        WKContextSetInjectedBundleClient(m_context.get(), &client.base);
385    } else if (!callback)
386        WKContextSetInjectedBundleClient(m_context.get(), nullptr);
387
388    m_callbackForMessageFromInjectedBundle.callback = callback;
389}
390
391void EwkContext::processReceivedMessageFromInjectedBundle(WKStringRef messageName, WKTypeRef messageBody, WKTypeRef* returnData)
392{
393    if (!m_callbackForMessageFromInjectedBundle.callback)
394        return;
395
396    CString name = toImpl(messageName)->string().utf8();
397    CString body;
398    if (messageBody && WKStringGetTypeID() == WKGetTypeID(messageBody))
399        body = toImpl(static_cast<WKStringRef>(messageBody))->string().utf8();
400
401    if (returnData) {
402        char* returnString = nullptr;
403        m_callbackForMessageFromInjectedBundle.callback(name.data(), body.data(), &returnString, m_callbackForMessageFromInjectedBundle.userData);
404        if (returnString) {
405            *returnData = WKStringCreateWithUTF8CString(returnString);
406            free(returnString);
407        } else
408            *returnData = WKStringCreateWithUTF8CString("");
409    } else
410        m_callbackForMessageFromInjectedBundle.callback(name.data(), body.data(), nullptr, m_callbackForMessageFromInjectedBundle.userData);
411}
412
413Ewk_TLS_Error_Policy EwkContext::ignoreTLSErrors() const
414{
415    return toImpl(m_context.get())->ignoreTLSErrors() ? EWK_TLS_ERROR_POLICY_IGNORE : EWK_TLS_ERROR_POLICY_FAIL;
416}
417
418void EwkContext::setIgnoreTLSErrors(Ewk_TLS_Error_Policy TLSErrorPolicy) const
419{
420    bool isNewPolicy = TLSErrorPolicy == EWK_TLS_ERROR_POLICY_IGNORE;
421    if (toImpl(m_context.get())->ignoreTLSErrors() == isNewPolicy)
422        return;
423
424    toImpl(m_context.get())->setIgnoreTLSErrors(isNewPolicy);
425}
426
427Ewk_Context* ewk_context_default_get()
428{
429    return EwkContext::defaultContext();
430}
431
432Ewk_Context* ewk_context_new()
433{
434    return EwkContext::create().leakRef();
435}
436
437Ewk_Context* ewk_context_new_with_injected_bundle_path(const char* path)
438{
439    EINA_SAFETY_ON_NULL_RETURN_VAL(path, nullptr);
440
441    return EwkContext::create(String::fromUTF8(path)).leakRef();
442}
443
444Eina_Bool ewk_context_url_scheme_register(Ewk_Context* ewkContext, const char* scheme, Ewk_Url_Scheme_Request_Cb callback, void* userData)
445{
446    EWK_OBJ_GET_IMPL_OR_RETURN(EwkContext, ewkContext, impl, false);
447    EINA_SAFETY_ON_NULL_RETURN_VAL(scheme, false);
448    EINA_SAFETY_ON_NULL_RETURN_VAL(callback, false);
449
450    impl->requestManager()->registerURLSchemeHandler(String::fromUTF8(scheme), callback, userData);
451
452    return true;
453}
454
455void ewk_context_history_callbacks_set(Ewk_Context* ewkContext, Ewk_History_Navigation_Cb navigate, Ewk_History_Client_Redirection_Cb clientRedirect, Ewk_History_Server_Redirection_Cb serverRedirect, Ewk_History_Title_Update_Cb titleUpdate, Ewk_History_Populate_Visited_Links_Cb populateVisitedLinks, void* data)
456{
457    EWK_OBJ_GET_IMPL_OR_RETURN(EwkContext, ewkContext, impl);
458
459    impl->historyClient()->setCallbacks(navigate, clientRedirect, serverRedirect, titleUpdate, populateVisitedLinks, data);
460}
461
462void ewk_context_visited_link_add(Ewk_Context* ewkContext, const char* visitedURL)
463{
464    EWK_OBJ_GET_IMPL_OR_RETURN(EwkContext, ewkContext, impl);
465    EINA_SAFETY_ON_NULL_RETURN(visitedURL);
466
467    impl->addVisitedLink(visitedURL);
468}
469
470Eina_Bool ewk_context_cache_model_set(Ewk_Context* ewkContext, Ewk_Cache_Model cacheModel)
471{
472    EWK_OBJ_GET_IMPL_OR_RETURN(EwkContext, ewkContext, impl, false);
473
474    impl->setCacheModel(cacheModel);
475
476    return true;
477}
478
479Ewk_Cache_Model ewk_context_cache_model_get(const Ewk_Context* ewkContext)
480{
481    EWK_OBJ_GET_IMPL_OR_RETURN(const EwkContext, ewkContext, impl, EWK_CACHE_MODEL_DOCUMENT_VIEWER);
482
483    return impl->cacheModel();
484}
485
486Eina_Bool ewk_context_additional_plugin_path_set(Ewk_Context* ewkContext, const char* path)
487{
488#if ENABLE(NETSCAPE_PLUGIN_API)
489    EWK_OBJ_GET_IMPL_OR_RETURN(EwkContext, ewkContext, impl, false);
490    EINA_SAFETY_ON_NULL_RETURN_VAL(path, false);
491
492    impl->setAdditionalPluginPath(String::fromUTF8(path));
493    return true;
494#else
495    UNUSED_PARAM(ewkContext);
496    UNUSED_PARAM(path);
497    return false;
498#endif
499}
500
501void ewk_context_resource_cache_clear(Ewk_Context* ewkContext)
502{
503    EWK_OBJ_GET_IMPL_OR_RETURN(EwkContext, ewkContext, impl);
504
505    impl->clearResourceCache();
506}
507
508void ewk_context_message_post_to_injected_bundle(Ewk_Context* ewkContext, const char* name, const char* body)
509{
510    EWK_OBJ_GET_IMPL_OR_RETURN(EwkContext, ewkContext, impl);
511    EINA_SAFETY_ON_NULL_RETURN(name);
512    EINA_SAFETY_ON_NULL_RETURN(body);
513
514    WKRetainPtr<WKStringRef> messageName(AdoptWK, WKStringCreateWithUTF8CString(name));
515    WKRetainPtr<WKStringRef> messageBody(AdoptWK, WKStringCreateWithUTF8CString(body));
516    WKContextPostMessageToInjectedBundle(impl->wkContext(), messageName.get(), messageBody.get());
517}
518
519void ewk_context_message_from_injected_bundle_callback_set(Ewk_Context* ewkContext, Ewk_Context_Message_From_Injected_Bundle_Cb callback, void* userData)
520{
521    EWK_OBJ_GET_IMPL_OR_RETURN(EwkContext, ewkContext, impl);
522
523    impl->setMessageFromInjectedBundleCallback(callback, userData);
524}
525
526Eina_Bool ewk_context_process_model_set(Ewk_Context* ewkContext, Ewk_Process_Model processModel)
527{
528#if ENABLE(NETWORK_PROCESS)
529    EWK_OBJ_GET_IMPL_OR_RETURN(EwkContext, ewkContext, impl, false);
530
531    impl->setProcessModel(processModel);
532
533    return true;
534#else
535    UNUSED_PARAM(ewkContext);
536    UNUSED_PARAM(processModel);
537    return false;
538#endif
539}
540
541Ewk_Process_Model ewk_context_process_model_get(const Ewk_Context* ewkContext)
542{
543#if ENABLE(NETWORK_PROCESS)
544    EWK_OBJ_GET_IMPL_OR_RETURN(const EwkContext, ewkContext, impl, EWK_PROCESS_MODEL_SHARED_SECONDARY);
545
546    return impl->processModel();
547#else
548    UNUSED_PARAM(ewkContext);
549    return EWK_PROCESS_MODEL_SHARED_SECONDARY;
550#endif
551}
552
553Ewk_TLS_Error_Policy ewk_context_tls_error_policy_get(const Ewk_Context* context)
554{
555    EWK_OBJ_GET_IMPL_OR_RETURN(const EwkContext, context, impl, EWK_TLS_ERROR_POLICY_FAIL);
556
557    return impl->ignoreTLSErrors();
558}
559
560void ewk_context_tls_error_policy_set(Ewk_Context* context, Ewk_TLS_Error_Policy tls_error_policy)
561{
562    EWK_OBJ_GET_IMPL_OR_RETURN(const EwkContext, context, impl);
563    impl->setIgnoreTLSErrors(tls_error_policy);
564}
565
566void ewk_context_preferred_languages_set(Eina_List* languages)
567{
568    Vector<String> preferredLanguages;
569    if (languages) {
570        Eina_List* l;
571        void* data;
572        EINA_LIST_FOREACH(languages, l, data)
573            preferredLanguages.append(String::fromUTF8(static_cast<char*>(data)).lower().replace("_", "-"));
574    }
575
576    WebCore::overrideUserPreferredLanguages(preferredLanguages);
577    WebCore::languageDidChange();
578}
579