2 * Copyright (c) 2015 Samsung Electronics Co., Ltd All Rights Reserved
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 #include "runtime/browser/web_application.h"
21 #include <cynara-client.h>
31 #include "common/application_data.h"
32 #include "common/app_db.h"
33 #include "common/app_control.h"
34 #include "common/command_line.h"
35 #include "common/locale_manager.h"
36 #include "common/logger.h"
37 #include "common/profiler.h"
38 #include "common/resource_manager.h"
39 #include "common/string_utils.h"
40 #include "extensions/renderer/xwalk_extension_renderer_controller.h"
41 #include "runtime/browser/native_window.h"
42 #include "runtime/browser/notification_manager.h"
43 #include "runtime/browser/popup.h"
44 #include "runtime/browser/popup_string.h"
45 #include "runtime/browser/vibration_manager.h"
46 #include "runtime/browser/web_view.h"
47 #include "runtime/browser/splash_screen.h"
48 #include "runtime/browser/ui_runtime.h"
49 #include "extensions/common/xwalk_extension_server.h"
51 #ifndef INJECTED_BUNDLE_PATH
52 #error INJECTED_BUNDLE_PATH is not set.
55 #define TIMER_INTERVAL 0.1
57 using namespace extensions;
62 const char* kKeyNameBack = "back";
63 const char* kKeyNameMenu = "menu";
65 const char* kConsoleLogEnableKey = "WRT_CONSOLE_LOG_ENABLE";
66 const char* kConsoleMessageLogTag = "ConsoleMessage";
68 const char* kVerboseKey = "verbose";
69 const char* kPortKey = "port";
71 const char* kAppControlEventScript =
73 "var __event = document.createEvent(\"CustomEvent\");\n"
74 "__event.initCustomEvent(\"appcontrol\", true, true, null);\n"
75 "document.dispatchEvent(__event);\n"
77 "for (var i=0; i < window.frames.length; i++)\n"
78 "{ window.frames[i].document.dispatchEvent(__event); }"
80 const char* kBackKeyEventScript =
82 "var __event = document.createEvent(\"CustomEvent\");\n"
83 "__event.initCustomEvent(\"tizenhwkey\", true, true, null);\n"
84 "__event.keyName = \"back\";\n"
85 "document.dispatchEvent(__event);\n"
87 "for (var i=0; i < window.frames.length; i++)\n"
88 "{ window.frames[i].document.dispatchEvent(__event); }"
90 const char* kMenuKeyEventScript =
92 "var __event = document.createEvent(\"CustomEvent\");\n"
93 "__event.initCustomEvent(\"tizenhwkey\", true, true, null);\n"
94 "__event.keyName = \"menu\";\n"
95 "document.dispatchEvent(__event);\n"
97 "for (var i=0; i < window.frames.length; i++)\n"
98 "{ window.frames[i].document.dispatchEvent(__event); }"
100 const char* kAmbientTickEventScript =
102 "var __event = document.createEvent(\"CustomEvent\");\n"
103 "__event.initCustomEvent(\"timetick\", true, true);\n"
104 "document.dispatchEvent(__event);\n"
106 "for (var i=0; i < window.frames.length; i++)\n"
107 "{ window.frames[i].document.dispatchEvent(__event); }"
109 const char* kCameraPrivilege = "http://tizen.org/privilege/camera";
110 const char* kFullscreenPrivilege = "http://tizen.org/privilege/fullscreen";
111 const char* kFullscreenFeature = "fullscreen";
112 const char* kNotificationPrivilege = "http://tizen.org/privilege/notification";
113 const char* kLocationPrivilege = "http://tizen.org/privilege/location";
114 const char* kRecordPrivilege = "http://tizen.org/privilege/recorder";
115 const char* kStoragePrivilege = "http://tizen.org/privilege/unlimitedstorage";
116 const char* kUsermediaPrivilege = "http://tizen.org/privilege/mediacapture";
117 const char* kNotiIconFile = "noti_icon.png";
118 const char* kFileScheme = "file://";
120 const char* kVisibilitySuspendFeature = "visibility,suspend";
121 const char* kMediastreamRecordFeature = "mediastream,record";
122 const char* kEncryptedDatabaseFeature = "encrypted,database";
123 const char* kRotationLockFeature = "rotation,lock";
124 const char* kBackgroundMusicFeature = "background,music";
125 const char* kSoundModeFeature = "sound,mode";
126 const char* kBackgroundVibrationFeature = "background,vibration";
127 const char* kCSPFeature = "csp";
129 const char* kGeolocationPermissionPrefix = "__WRT_GEOPERM_";
130 const char* kNotificationPermissionPrefix = "__WRT_NOTIPERM_";
131 const char* kQuotaPermissionPrefix = "__WRT_QUOTAPERM_";
132 const char* kCertificateAllowPrefix = "__WRT_CERTIPERM_";
133 const char* kUsermediaPermissionPrefix = "__WRT_USERMEDIAPERM_";
134 const char* kDBPrivateSection = "private";
136 const char* kDefaultCSPRule =
137 "default-src *; script-src 'self'; style-src 'self'; object-src 'none';";
138 const char* kResWgtPath = "res/wgt/";
139 const char* kAppControlMain = "http://tizen.org/appcontrol/operation/main";
141 // Looking for added privilege by Application developer in config.xml.
142 bool FindPrivilegeFromConfig(common::ApplicationData* app_data,
143 const std::string& privilege) {
144 if (app_data->permissions_info().get() == NULL) return false;
145 LOGGER(INFO) << "Finding privilege from config.xml";
146 auto it = app_data->permissions_info()->GetAPIPermissions().begin();
147 auto end = app_data->permissions_info()->GetAPIPermissions().end();
148 for (; it != end; ++it) {
149 if (*it == privilege) return true;
154 // Looking for given default privilege when application installed.
155 bool FindPrivilegeFromCynara(const std::string& privilege_name) {
156 LOGGER(INFO) << "Finding privilege from cynara db";
157 static constexpr char kSmackLabelFilePath[] = "/proc/self/attr/current";
158 std::ifstream file(kSmackLabelFilePath);
159 if (!file.is_open()) {
160 LOGGER(ERROR) << "Failed to open " << kSmackLabelFilePath;
165 cynara* p_cynara = NULL;
166 ret = cynara_initialize(&p_cynara, 0);
167 if (CYNARA_API_SUCCESS != ret) {
168 LOGGER(ERROR) << "Failed. The result of cynara_initialize() : " << ret;
172 std::string uid = std::to_string(getuid());
173 std::string smack_label{std::istreambuf_iterator<char>(file),
174 std::istreambuf_iterator<char>()};
177 ret = cynara_check(p_cynara, smack_label.c_str(), "", uid.c_str(), privilege_name.c_str());
178 if (CYNARA_API_ACCESS_ALLOWED != ret) {
179 LOGGER(ERROR) << "Access denied. The result of cynara_check() : " << ret;
181 LOGGER(INFO) << "Access allowed! The result of cynara_check() : " << ret;
186 ret = cynara_finish(p_cynara);
187 if (CYNARA_API_SUCCESS != ret) {
188 LOGGER(ERROR) << "Failed. The result of cynara_finish() : " << ret;
195 static void SendDownloadRequest(const std::string& url) {
196 common::AppControl request;
197 request.set_operation(APP_CONTROL_OPERATION_DOWNLOAD);
198 request.set_uri(url);
199 request.LaunchRequest();
202 static void InitializeNotificationCallback(Ewk_Context* ewk_context,
203 WebApplication* app) {
204 auto show = [](Ewk_Context*, Ewk_Notification* noti, void* user_data) {
205 WebApplication* self = static_cast<WebApplication*>(user_data);
206 if (self == NULL) return;
207 uint64_t id = ewk_notification_id_get(noti);
208 std::string title(ewk_notification_title_get(noti)
209 ? ewk_notification_title_get(noti)
212 ewk_notification_body_get(noti) ? ewk_notification_body_get(noti) : "");
213 std::string icon_path = self->data_path() + "/" + kNotiIconFile;
214 if (!ewk_notification_icon_save_as_png(noti, icon_path.c_str())) {
217 if (NotificationManager::GetInstance()->Show(id, title, body, icon_path))
218 ewk_notification_showed(id);
220 auto hide = [](Ewk_Context*, uint64_t noti_id, void*) {
221 NotificationManager::GetInstance()->Hide(noti_id);
222 ewk_notification_closed(noti_id, EINA_FALSE);
224 ewk_context_notification_callbacks_set(ewk_context, show, hide, app);
227 static Eina_Bool ExitAppIdlerCallback(void* data) {
228 WebApplication* app = static_cast<WebApplication*>(data);
231 LOGGER(DEBUG) << "Terminate";
235 return ECORE_CALLBACK_CANCEL;
238 static bool ClearCookie(Ewk_Context* ewk_context) {
239 Ewk_Cookie_Manager* cookie_manager =
240 ewk_context_cookie_manager_get(ewk_context);
241 if (!cookie_manager) {
242 LOGGER(ERROR) << "Fail to get cookie manager";
245 ewk_cookie_manager_cookies_clear(cookie_manager);
249 static bool ProcessWellKnownScheme(const std::string& url) {
250 if (common::utils::StartsWith(url, "file:") ||
251 common::utils::StartsWith(url, "app:") ||
252 common::utils::StartsWith(url, "data:") ||
253 common::utils::StartsWith(url, "http:") ||
254 common::utils::StartsWith(url, "https:") ||
255 common::utils::StartsWith(url, "widget:") ||
256 common::utils::StartsWith(url, "about:") ||
257 common::utils::StartsWith(url, "blob:")) {
261 std::unique_ptr<common::AppControl> request(
262 common::AppControl::MakeAppcontrolFromURL(url));
263 if (request.get() == NULL || !request->LaunchRequest()) {
264 LOGGER(ERROR) << "Fail to send appcontrol request";
265 SLoggerE("Fail to send appcontrol request [%s]", url.c_str());
268 // Should return true, to stop the WebEngine progress step about this URL
274 std::vector<unsigned> ParseTizenVersion(const std::string& tizen_string) {
275 std::vector<unsigned> version(3, 0);
276 for (unsigned i = 0, index = 0; i < tizen_string.size(); i++) {
278 if (isdigit(tizen_string[i]) && index < version.size())
279 version[index++] = atoi(&tizen_string[i]);
281 return std::vector<unsigned>(3, 0);
282 } else if (tizen_string[i] != '.')
283 return std::vector<unsigned>(3, 0);
288 WebApplication::WebApplication(
289 NativeWindow* window,
290 common::ApplicationData* app_data)
293 verbose_mode_(false),
294 lang_changed_mode_(false),
295 is_terminate_called_(false),
297 ewk_context_new_with_injected_bundle_path(INJECTED_BUNDLE_PATH)),
298 has_ownership_of_ewk_context_(true),
300 appid_(app_data->app_id()),
302 locale_manager_(new common::LocaleManager()),
307 WebApplication::WebApplication(
308 NativeWindow* window,
309 common::ApplicationData* app_data,
310 Ewk_Context* context)
313 verbose_mode_(false),
314 is_terminate_called_(false),
315 ewk_context_(context),
316 has_ownership_of_ewk_context_(false),
318 appid_(app_data->app_id()),
320 locale_manager_(new common::LocaleManager()),
325 WebApplication::~WebApplication() {
326 window_->SetContent(NULL);
327 auto it = view_stack_.begin();
328 for (; it != view_stack_.end(); ++it) {
333 if (ewk_context_ && has_ownership_of_ewk_context_)
334 ewk_context_delete(ewk_context_);
337 bool WebApplication::Initialize() {
339 std::unique_ptr<char, decltype(std::free)*> path{app_get_data_path(),
341 app_data_path_ = path.get();
343 if (app_data_->setting_info() != NULL &&
344 app_data_->setting_info()->screen_orientation() ==
345 wgt::parse::SettingInfo::ScreenOrientation::AUTO) {
346 ewk_context_tizen_extensible_api_string_set(ewk_context_,
347 kRotationLockFeature, true);
348 window_->SetAutoRotation();
349 } else if (app_data_->setting_info() != NULL &&
350 app_data_->setting_info()->screen_orientation() ==
351 wgt::parse::SettingInfo::ScreenOrientation::PORTRAIT) {
352 window_->SetRotationLock(NativeWindow::ScreenOrientation::PORTRAIT_PRIMARY);
353 } else if (app_data_->setting_info() != NULL &&
354 app_data_->setting_info()->screen_orientation() ==
355 wgt::parse::SettingInfo::ScreenOrientation::LANDSCAPE) {
356 window_->SetRotationLock(
357 NativeWindow::ScreenOrientation::LANDSCAPE_PRIMARY);
360 splash_screen_.reset(new SplashScreen(
361 window_, app_data_->splash_screen_info(), app_data_->application_path()));
362 resource_manager_.reset(
363 new common::ResourceManager(app_data_, locale_manager_.get()));
364 resource_manager_->set_base_resource_path(app_data_->application_path());
366 auto extension_server = extensions::XWalkExtensionServer::GetInstance();
367 extension_server->SetupIPC(ewk_context_);
370 ewk_context_cache_model_set(ewk_context_, EWK_CACHE_MODEL_DOCUMENT_BROWSER);
373 auto cookie_manager = ewk_context_cookie_manager_get(ewk_context_);
374 ewk_cookie_manager_accept_policy_set(cookie_manager,
375 EWK_COOKIE_ACCEPT_POLICY_ALWAYS);
377 // set persistent storage path
378 std::string cookie_path = data_path() + ".cookie";
379 ewk_cookie_manager_persistent_storage_set(
380 cookie_manager, cookie_path.c_str(),
381 EWK_COOKIE_PERSISTENT_STORAGE_SQLITE);
383 // vibration callback
384 auto vibration_start_callback = [](uint64_t ms, void*) {
385 platform::VibrationManager::GetInstance()->Start(static_cast<int>(ms));
387 auto vibration_stop_callback = [](void* /*user_data*/) {
388 platform::VibrationManager::GetInstance()->Stop();
390 ewk_context_vibration_client_callbacks_set(
391 ewk_context_, vibration_start_callback, vibration_stop_callback, NULL);
393 auto download_callback = [](const char* downloadUrl, void* /*data*/) {
394 SendDownloadRequest(downloadUrl);
396 ewk_context_did_start_download_callback_set(ewk_context_, download_callback,
398 InitializeNotificationCallback(ewk_context_, this);
400 if (FindPrivilegeFromConfig(app_data_, kFullscreenPrivilege)) {
401 ewk_context_tizen_extensible_api_string_set(ewk_context_,
402 kFullscreenFeature, true);
405 if (app_data_->setting_info() != NULL &&
406 app_data_->setting_info()->background_support_enabled()) {
407 ewk_context_tizen_extensible_api_string_set(
408 ewk_context_, kVisibilitySuspendFeature, true);
409 ewk_context_tizen_extensible_api_string_set(ewk_context_,
410 kBackgroundMusicFeature, true);
412 ewk_context_tizen_extensible_api_string_set(
413 ewk_context_, kVisibilitySuspendFeature, false);
414 ewk_context_tizen_extensible_api_string_set(ewk_context_,
415 kBackgroundMusicFeature, false);
417 ewk_context_tizen_extensible_api_string_set(ewk_context_,
418 kMediastreamRecordFeature, true);
419 ewk_context_tizen_extensible_api_string_set(ewk_context_,
420 kEncryptedDatabaseFeature, true);
422 if (app_data_->setting_info() != NULL &&
423 app_data_->setting_info()->sound_mode() ==
424 wgt::parse::SettingInfo::SoundMode::EXCLUSIVE) {
425 ewk_context_tizen_extensible_api_string_set(ewk_context_, kSoundModeFeature,
429 if (app_data_->setting_info() != NULL &&
430 app_data_->setting_info()->background_vibration()) {
431 ewk_context_tizen_extensible_api_string_set(
432 ewk_context_, kBackgroundVibrationFeature, true);
435 if (app_data_->widget_info() != NULL &&
436 !app_data_->widget_info()->default_locale().empty()) {
437 locale_manager_->SetDefaultLocale(
438 app_data_->widget_info()->default_locale());
441 if (app_data_->widget_info() != NULL &&
442 app_data_->widget_info()->view_modes() == "fullscreen") {
443 window_->SetCurrentViewModeFullScreen(true);
444 window_->FullScreen(true);
447 if (app_data_->csp_info() != NULL || app_data_->csp_report_info() != NULL ||
448 app_data_->allowed_navigation_info() != NULL) {
449 security_model_version_ = 2;
450 if (app_data_->csp_info() == NULL ||
451 app_data_->csp_info()->security_rules().empty()) {
452 csp_rule_ = kDefaultCSPRule;
454 csp_rule_ = app_data_->csp_info()->security_rules();
456 if (app_data_->csp_report_info() != NULL &&
457 !app_data_->csp_report_info()->security_rules().empty()) {
458 csp_report_rule_ = app_data_->csp_report_info()->security_rules();
460 ewk_context_tizen_extensible_api_string_set(ewk_context_, kCSPFeature,
463 security_model_version_ = 1;
466 #ifdef MANUAL_ROTATE_FEATURE_SUPPORT
467 // Set manual rotation
468 window_->EnableManualRotation(true);
469 #endif // MANUAL_ROTATE_FEATURE_SUPPORT
474 void WebApplication::SetupTizenVersion() {
475 if (app_data_->tizen_application_info() != NULL &&
476 !app_data_->tizen_application_info()->required_version().empty()) {
477 std::string tizen_version = app_data_->tizen_application_info()->required_version();
478 std::vector<unsigned> parsed_tizen_version = ParseTizenVersion(tizen_version);
479 m_tizenCompatibilitySettings.m_major = parsed_tizen_version[0];
480 m_tizenCompatibilitySettings.m_minor = parsed_tizen_version[1];
481 m_tizenCompatibilitySettings.m_release = parsed_tizen_version[2];
485 bool WebApplication::tizenWebKitCompatibilityEnabled() const {
486 return m_tizenCompatibilitySettings.tizenWebKitCompatibilityEnabled();
489 void WebApplication::Launch(std::unique_ptr<common::AppControl> appcontrol) {
490 // send widget info to injected bundle
491 ewk_context_tizen_app_id_set(ewk_context_, appid_.c_str());
496 WebView* view = new WebView(window_, ewk_context_);
498 SetupWebViewCompatibilitySettings(view);
500 std::unique_ptr<common::ResourceManager::Resource> res =
501 resource_manager_->GetStartResource(appcontrol.get());
502 view->SetDefaultEncoding(res->encoding());
504 STEP_PROFILE_END("OnCreate -> URL Set");
505 STEP_PROFILE_START("URL Set -> Rendered");
507 window_->SetContent(view->evas_object());
509 #ifdef PROFILE_MOBILE
510 // rotate and resize window forcibily for landscape mode.
511 // window rotate event is generated after window show. so
512 // when app get width and height from viewport, wrong value can be returned.
513 if (app_data_->setting_info()->screen_orientation() ==
514 wgt::parse::SettingInfo::ScreenOrientation::LANDSCAPE) {
515 LOGGER(DEBUG) << "rotate and resize window for landscape mode";
516 elm_win_rotation_with_resize_set(window_->evas_object(), 270);
517 evas_norender(evas_object_evas_get(window_->evas_object()));
519 #endif // PROFILE_MOBILE
521 view->LoadUrl(res->uri(), res->mime());
522 view_stack_.push_front(view);
524 #ifdef PROFILE_WEARABLE
525 // ewk_view_bg_color_set is not working at webview initialization.
526 if (app_data_->app_type() == common::ApplicationData::WATCH) {
527 view->SetBGColor(0, 0, 0, 255);
529 #endif // PROFILE_WEARABLE
531 if (appcontrol->data(AUL_K_DEBUG) == "1") {
533 LaunchInspector(appcontrol.get());
535 if (appcontrol->data(kVerboseKey) == "true") {
536 verbose_mode_ = true;
541 #ifdef PROFILE_MOBILE
542 if (!common::utils::StartsWith(view->GetUrl(), kFileScheme)) {
543 LOGGER(DEBUG) << "Show window after launch for remote URL";
547 #endif // PROFILE_MOBILE
550 void WebApplication::AppControl(
551 std::unique_ptr<common::AppControl> appcontrol) {
552 std::unique_ptr<common::ResourceManager::Resource> res =
553 resource_manager_->GetStartResource(appcontrol.get());
555 bool do_reset = res->should_reset();
558 std::string current_page = view_stack_.front()->GetUrl();
559 std::string localized_page = res->uri();
561 if (current_page != localized_page) {
564 SendAppControlEvent();
568 // handle http://tizen.org/appcontrol/operation/main operation specially.
569 // only menu-screen app can send launch request with main operation.
570 // in this case, web app should have to resume web app not reset.
571 if (do_reset && (appcontrol->operation() == kAppControlMain)){
572 LOGGER(DEBUG) << "resume app for main operation";
574 SendAppControlEvent();
580 WebView* view = view_stack_.front();
582 SetupWebViewCompatibilitySettings(view);
583 view->SetDefaultEncoding(res->encoding());
584 view->LoadUrl(res->uri(), res->mime());
585 window_->SetContent(view->evas_object());
588 if (!debug_mode_ && appcontrol->data(AUL_K_DEBUG) == "1") {
590 LaunchInspector(appcontrol.get());
592 if (!verbose_mode_ && appcontrol->data(kVerboseKey) == "true") {
593 verbose_mode_ = true;
598 void WebApplication::SendAppControlEvent() {
599 if (view_stack_.size() > 0 && view_stack_.front() != NULL)
600 view_stack_.front()->EvalJavascript(kAppControlEventScript);
603 void WebApplication::ClearViewStack() {
604 window_->SetContent(NULL);
605 WebView* front = view_stack_.front();
606 auto it = view_stack_.begin();
607 for (; it != view_stack_.end(); ++it) {
614 view_stack_.push_front(front);
617 void WebApplication::Resume() {
618 if (view_stack_.size() > 0 && view_stack_.front() != NULL)
619 view_stack_.front()->SetVisibility(true);
621 if (app_data_->setting_info() != NULL &&
622 app_data_->setting_info()->background_support_enabled()) {
626 auto it = view_stack_.begin();
627 for (; it != view_stack_.end(); ++it) {
632 void WebApplication::Suspend() {
633 if (view_stack_.size() > 0 && view_stack_.front() != NULL)
634 view_stack_.front()->SetVisibility(false);
636 if (app_data_->setting_info() != NULL &&
637 app_data_->setting_info()->background_support_enabled()) {
638 LOGGER(DEBUG) << "gone background (backgroud support enabed)";
642 auto it = view_stack_.begin();
643 for (; it != view_stack_.end(); ++it) {
648 void WebApplication::Terminate() {
649 is_terminate_called_ = true;
651 LOGGER(DEBUG) << "terminator_";
654 LOGGER(ERROR) << "There's no registered terminator.";
657 auto extension_server = extensions::XWalkExtensionServer::GetInstance();
658 LOGGER(DEBUG) << "Shutdown extension server";
659 extension_server->Shutdown();
662 void WebApplication::ClosePage() {
664 int valid_evas_object_count = 0;
665 auto it = view_stack_.begin();
666 if (it != view_stack_.end()) {
667 for (; it != view_stack_.end(); ++it) {
668 (*it)->ReplyToJavascriptDialog();
669 view_stack_.front()->SetVisibility(false);
670 if (ewk_view_page_close((*it)->evas_object())) {
671 LOGGER(DEBUG) << "ewk_view_page_close returns true";
672 valid_evas_object_count++;
674 LOGGER(DEBUG) << "ewk_view_page_close returns false";
678 if (valid_evas_object_count == 0) {
679 if (is_terminate_called_) {
680 ecore_main_loop_quit();
683 LOGGER(DEBUG) << "terminator_";
686 LOGGER(ERROR) << "There's no registered terminator.";
693 void WebApplication::OnCreatedNewWebView(WebView* /*view*/, WebView* new_view) {
694 if (view_stack_.size() > 0 && view_stack_.front() != NULL)
695 view_stack_.front()->SetVisibility(false);
697 SetupWebView(new_view);
698 SetupWebViewCompatibilitySettings(new_view);
699 view_stack_.push_front(new_view);
700 window_->SetContent(new_view->evas_object());
703 void WebApplication::RemoveWebViewFromStack(WebView* view) {
704 if (view_stack_.size() == 0)
707 WebView* current = view_stack_.front();
708 if (current == view) {
709 // In order to prevent the crash issue due to the callback
710 // which occur after destroying WebApplication class,
711 // we have to set the 'SetEventListener' to NULL.
712 view->SetEventListener(NULL);
713 view_stack_.pop_front();
715 auto found = std::find(view_stack_.begin(), view_stack_.end(), view);
716 if (found != view_stack_.end()) {
717 // In order to prevent the crash issue due to the callback
718 // which occur after destroying WebApplication class,
719 // we have to set the 'SetEventListener' to NULL.
720 view->SetEventListener(NULL);
721 view_stack_.erase(found);
725 if (view_stack_.size() == 0) {
726 // If |Terminate()| hasn't been called,
727 // main loop shouldn't be terminated here.
728 if (!is_terminate_called_) {
729 auto extension_server = XWalkExtensionServer::GetInstance();
730 LOGGER(DEBUG) << "Shutdown extension server";
731 extension_server->Shutdown();
733 } else if (current != view_stack_.front()) {
734 view_stack_.front()->SetVisibility(true);
735 window_->SetContent(view_stack_.front()->evas_object());
738 // Delete after the callback context(for ewk view) was not used
739 ecore_idler_add([](void* view) {
740 WebView* obj = static_cast<WebView*>(view);
747 Eina_Bool WebApplication::CheckPluginSession(void* user_data)
749 WebApplication* that = static_cast<WebApplication*>(user_data);
750 if(XWalkExtensionRendererController::plugin_session_count > 0) {
751 LOGGER(ERROR) << "plugin_session_count : " <<
752 XWalkExtensionRendererController::plugin_session_count;
753 return ECORE_CALLBACK_RENEW;
755 LOGGER(DEBUG) << "plugin_session_count : " <<
756 XWalkExtensionRendererController::plugin_session_count;
757 LOGGER(DEBUG) << "Execute deferred termination of main loop";
758 if (that->is_terminate_called_) {
759 ecore_main_loop_quit();
761 if (that->terminator_) {
762 LOGGER(DEBUG) << "terminator_";
765 LOGGER(ERROR) << "There's no registered terminator.";
769 return ECORE_CALLBACK_CANCEL;
772 void WebApplication::OnClosedWebView(WebView* view) {
773 Ecore_Timer* timeout_id = NULL;
774 // Reply to javascript dialog for preventing freeze issue.
775 view->ReplyToJavascriptDialog();
776 RemoveWebViewFromStack(view);
778 LOGGER(DEBUG) << "plugin_session_count : " <<
779 XWalkExtensionRendererController::plugin_session_count;
780 if (!ecore_timer_add(TIMER_INTERVAL, CheckPluginSession, this))
781 LOGGER(ERROR) << "It's failed to create timer";
784 void WebApplication::OnReceivedWrtMessage(WebView* view,
785 Ewk_IPC_Wrt_Message_Data* msg) {
786 Eina_Stringshare* msg_type = ewk_ipc_wrt_message_data_type_get(msg);
788 #define TYPE_BEGIN(x) (!strncmp(msg_type, x, strlen(x)))
789 #define TYPE_IS(x) (!strcmp(msg_type, x))
791 if (TYPE_BEGIN("xwalk://")) {
792 auto extension_server = extensions::XWalkExtensionServer::GetInstance();
793 extension_server->HandleIPCMessage(msg);
795 Eina_Stringshare* msg_id = ewk_ipc_wrt_message_data_id_get(msg);
796 Eina_Stringshare* msg_ref_id =
797 ewk_ipc_wrt_message_data_reference_id_get(msg);
798 Eina_Stringshare* msg_value = ewk_ipc_wrt_message_data_value_get(msg);
800 if (TYPE_IS("tizen://hide")) {
803 } else if (TYPE_IS("tizen://exit")) {
805 // Reply to javascript dialog for preventing freeze issue.
806 view->ReplyToJavascriptDialog();
807 ecore_idler_add(ExitAppIdlerCallback, this);
808 } else if (TYPE_IS("tizen://changeUA")) {
810 // Change UserAgent of current WebView
812 if (view_stack_.size() > 0 && view_stack_.front() != NULL) {
813 ret = view_stack_.front()->SetUserAgent(std::string(msg_value));
816 Ewk_IPC_Wrt_Message_Data* ans = ewk_ipc_wrt_message_data_new();
817 ewk_ipc_wrt_message_data_type_set(ans, msg_type);
818 ewk_ipc_wrt_message_data_reference_id_set(ans, msg_id);
820 ewk_ipc_wrt_message_data_value_set(ans, "success");
822 ewk_ipc_wrt_message_data_value_set(ans, "failed");
823 if (!ewk_ipc_wrt_message_send(ewk_context_, ans)) {
824 LOGGER(ERROR) << "Failed to send response";
826 ewk_ipc_wrt_message_data_del(ans);
827 } else if (TYPE_IS("tizen://deleteAllCookies")) {
828 Ewk_IPC_Wrt_Message_Data* ans = ewk_ipc_wrt_message_data_new();
829 ewk_ipc_wrt_message_data_type_set(ans, msg_type);
830 ewk_ipc_wrt_message_data_reference_id_set(ans, msg_id);
831 if (ClearCookie(ewk_context_))
832 ewk_ipc_wrt_message_data_value_set(ans, "success");
834 ewk_ipc_wrt_message_data_value_set(ans, "failed");
835 if (!ewk_ipc_wrt_message_send(ewk_context_, ans)) {
836 LOGGER(ERROR) << "Failed to send response";
838 ewk_ipc_wrt_message_data_del(ans);
839 } else if (TYPE_IS("tizen://hide_splash_screen")) {
840 splash_screen_->HideSplashScreen(SplashScreen::HideReason::CUSTOM);
843 eina_stringshare_del(msg_ref_id);
844 eina_stringshare_del(msg_id);
845 eina_stringshare_del(msg_value);
851 eina_stringshare_del(msg_type);
854 void WebApplication::OnOrientationLock(
855 WebView* view, bool lock,
856 NativeWindow::ScreenOrientation preferred_rotation) {
857 if (view_stack_.size() == 0) return;
859 // Only top-most view can set the orientation relate operation
860 if (view_stack_.front() != view) return;
862 // This is for 2.4 compatibility. Requested by Webengine Team.
864 // In Tizen 2.4 WebKit locking screen orientation was possible with Web API
865 // screen.lockOrientation(). This API was deprecated and replaced with
866 // screen.orientation.lock(). But for compatibility case we need to support
868 if(!tizenWebKitCompatibilityEnabled()) {
869 auto orientaion_setting = app_data_->setting_info() != NULL
870 ? app_data_->setting_info()->screen_orientation()
871 : wgt::parse::SettingInfo::ScreenOrientation::AUTO;
872 if (wgt::parse::SettingInfo::ScreenOrientation::AUTO != orientaion_setting) {
873 // If Tizen version is 3.0, it return.
879 window_->SetRotationLock(preferred_rotation);
881 window_->SetAutoRotation();
885 void WebApplication::OnHardwareKey(WebView* view, const std::string& keyname) {
886 // NOTE: This code is added to enable back-key on remote URL
887 bool enabled = app_data_->setting_info() != NULL
888 ? app_data_->setting_info()->hwkey_enabled()
891 if (!common::utils::StartsWith(view->GetUrl(), kFileScheme)) {
892 if (kKeyNameBack == keyname) {
893 LOGGER(DEBUG) << "Back to previous page for remote URL";
895 view->EvalJavascript(kBackKeyEventScript);
896 if (!view->Backward()) {
897 LOGGER(DEBUG) << "Terminate";
904 if (enabled && kKeyNameBack == keyname) {
905 view->EvalJavascript(kBackKeyEventScript);
906 // NOTE: This code is added for backward compatibility.
907 // If the 'backbutton_presence' is true, WebView should be navigated back.
908 if ((app_data_->setting_info() != NULL &&
909 app_data_->setting_info()->backbutton_presence()) ||
910 (app_data_->widget_info() != NULL &&
911 app_data_->widget_info()->view_modes() == "windowed")) {
912 if (!view->Backward()) {
913 LOGGER(DEBUG) << "Terminate";
917 } else if (enabled && kKeyNameMenu == keyname) {
918 view->EvalJavascript(kMenuKeyEventScript);
922 void WebApplication::OnLanguageChanged() {
923 lang_changed_mode_ = true;
924 locale_manager_->UpdateSystemLocale();
925 ewk_context_cache_clear(ewk_context_);
926 auto it = view_stack_.begin();
927 for (; it != view_stack_.end(); ++it) {
932 void WebApplication::OnConsoleMessage(const std::string& msg, int level) {
933 static bool enabled = (getenv(kConsoleLogEnableKey) != NULL);
936 std::string split_msg = msg;
937 std::size_t pos = msg.find(kResWgtPath);
938 if (pos != std::string::npos) {
939 split_msg = msg.substr(pos + strlen(kResWgtPath));
942 if (debug_mode_ || verbose_mode_ || enabled) {
943 int dlog_level = DLOG_DEBUG;
945 case EWK_CONSOLE_MESSAGE_LEVEL_WARNING:
946 dlog_level = DLOG_WARN;
948 case EWK_CONSOLE_MESSAGE_LEVEL_ERROR:
949 dlog_level = DLOG_ERROR;
952 dlog_level = DLOG_DEBUG;
955 LOGGER_RAW(dlog_level, kConsoleMessageLogTag)
956 << "[" << app_data_->pkg_id() << "] " << split_msg;
960 void WebApplication::OnLowMemory() {
961 ewk_context_cache_clear(ewk_context_);
962 ewk_context_notify_low_memory(ewk_context_);
965 void WebApplication::OnSoftKeyboardChangeEvent(WebView* /*view*/,
966 SoftKeyboardChangeEventValue softkeyboard_value) {
967 LOGGER(DEBUG) << "OnSoftKeyboardChangeEvent";
968 std::stringstream script;
971 << "var __event = document.createEvent(\"CustomEvent\");\n"
972 << "var __detail = {};\n"
973 << "__event.initCustomEvent(\"softkeyboardchange\",true,true,__detail);\n"
974 << "__event.state = \"" << softkeyboard_value.state << "\";\n"
975 << "__event.width = " << softkeyboard_value.width << ";\n"
976 << "__event.height = " << softkeyboard_value.height << ";\n"
977 << "document.dispatchEvent(__event);\n"
979 << "for (var i=0; i < window.frames.length; i++)\n"
980 << "{ window.frames[i].document.dispatchEvent(__event); }"
982 std::string kSoftKeyboardScript = script.str();
983 if (view_stack_.size() > 0 && view_stack_.front() != NULL)
984 view_stack_.front()->EvalJavascript(kSoftKeyboardScript.c_str());
987 #ifdef ROTARY_EVENT_FEATURE_SUPPORT
988 void WebApplication::OnRotaryEvent(WebView* /*view*/,
989 RotaryEventType type) {
990 LOGGER(DEBUG) << "OnRotaryEvent";
991 std::stringstream script;
994 << "var __event = document.createEvent(\"CustomEvent\");\n"
995 << "var __detail = {};\n"
996 << "__event.initCustomEvent(\"rotarydetent\", true, true, __detail);\n"
997 << "__event.detail.direction = \""
998 << (type == RotaryEventType::CLOCKWISE ? "CW" : "CCW")
1000 << "document.dispatchEvent(__event);\n"
1002 << "for (var i=0; i < window.frames.length; i++)\n"
1003 << "{ window.frames[i].document.dispatchEvent(__event); }"
1005 std::string kRotaryEventScript = script.str();
1006 if (view_stack_.size() > 0 && view_stack_.front() != NULL)
1007 view_stack_.front()->EvalJavascript(kRotaryEventScript.c_str());
1009 #endif // ROTARY_EVENT_FEATURE_SUPPORT
1011 void WebApplication::OnTimeTick(long time) {
1013 LOGGER(DEBUG) << "TimeTick";
1014 if (view_stack_.size() > 0 && view_stack_.front() != NULL)
1015 view_stack_.front()->EvalJavascript(kAmbientTickEventScript);
1019 void WebApplication::OnAmbientTick(long time) {
1020 LOGGER(DEBUG) << "AmbientTick";
1021 if (view_stack_.size() > 0 && view_stack_.front() != NULL)
1022 view_stack_.front()->EvalJavascript(kAmbientTickEventScript);
1025 void WebApplication::OnAmbientChanged(bool ambient_mode) {
1026 LOGGER(DEBUG) << "AmbientChanged";
1027 std::stringstream script;
1030 << "var __event = document.createEvent(\"CustomEvent\");\n"
1031 << "var __detail = {};\n"
1032 << "__event.initCustomEvent(\"ambientmodechanged\",true,true,__detail);\n"
1033 << "__event.detail.ambientMode = "
1034 << (ambient_mode ? "true" : "false") << ";\n"
1035 << "document.dispatchEvent(__event);\n"
1037 << "for (var i=0; i < window.frames.length; i++)\n"
1038 << "{ window.frames[i].document.dispatchEvent(__event); }"
1040 std::string kAmbientChangedEventScript = script.str();
1041 if (view_stack_.size() > 0 && view_stack_.front() != NULL)
1042 view_stack_.front()->EvalJavascript(kAmbientChangedEventScript.c_str());
1045 bool WebApplication::OnContextMenuDisabled(WebView* /*view*/) {
1046 return !(app_data_->setting_info() != NULL
1047 ? app_data_->setting_info()->context_menu_enabled()
1051 void WebApplication::OnLoadStart(WebView* /*view*/) {
1052 LOGGER(DEBUG) << "LoadStart";
1055 void WebApplication::OnLoadFinished(WebView* /*view*/) {
1056 LOGGER(DEBUG) << "LoadFinished";
1057 splash_screen_->HideSplashScreen(SplashScreen::HideReason::LOADFINISHED);
1060 void WebApplication::OnRendered(WebView* view) {
1061 STEP_PROFILE_END("URL Set -> Rendered");
1062 STEP_PROFILE_END("Start -> Launch Completed");
1063 LOGGER(DEBUG) << "Rendered";
1064 splash_screen_->HideSplashScreen(SplashScreen::HideReason::RENDERED);
1066 // Do not show(), active() for language change
1067 if(lang_changed_mode_ == false){
1068 // Show window after frame rendered.
1069 #ifdef PROFILE_MOBILE
1070 if (common::utils::StartsWith(view->GetUrl(), kFileScheme)) {
1074 #else // PROFILE_MOBILE
1080 lang_changed_mode_ = false;
1084 #ifdef MANUAL_ROTATE_FEATURE_SUPPORT
1085 void WebApplication::OnRotatePrepared(WebView* /*view*/) {
1086 window_->ManualRotationDone();
1088 #endif // MANUAL_ROTATE_FEATURE_SUPPORT
1090 void WebApplication::LaunchInspector(common::AppControl* appcontrol) {
1091 unsigned int port = ewk_context_inspector_server_start(ewk_context_, 0);
1092 std::stringstream ss;
1094 std::map<std::string, std::vector<std::string>> data;
1095 data[kPortKey] = {ss.str()};
1096 appcontrol->Reply(data);
1099 void WebApplication::SetupWebView(WebView* view) {
1100 view->SetEventListener(this);
1103 if (security_model_version_ == 2) {
1104 view->SetCSPRule(csp_rule_, false);
1105 if (!csp_report_rule_.empty()) {
1106 view->SetCSPRule(csp_report_rule_, true);
1110 // Setup longpolling value
1111 if (app_data_->setting_info() != NULL &&
1112 app_data_->setting_info()->long_polling()) {
1113 boost::optional <unsigned int> polling_val(app_data_->setting_info()->long_polling());
1114 unsigned long *ptr = reinterpret_cast <unsigned long *> (&polling_val.get());
1115 view->SetLongPolling(*ptr);
1119 void WebApplication::SetupWebViewCompatibilitySettings(WebView* view) {
1120 if (tizenWebKitCompatibilityEnabled()) {
1121 Ewk_Settings* settings = ewk_view_settings_get(view->evas_object());
1122 ewk_settings_tizen_compatibility_mode_set(settings,
1123 m_tizenCompatibilitySettings.m_major,
1124 m_tizenCompatibilitySettings.m_minor,
1125 m_tizenCompatibilitySettings.m_release);
1126 ewk_settings_text_autosizing_enabled_set(settings, EINA_FALSE);
1130 bool WebApplication::OnDidNavigation(WebView* /*view*/,
1131 const std::string& url) {
1133 // except(file , http, https, app) pass to appcontrol and return false
1134 if (ProcessWellKnownScheme(url)) {
1138 // send launch request for blocked URL to guarrenty backward-compatibility.
1139 if (resource_manager_->AllowNavigation(url)) {
1142 LOGGER(DEBUG) << "URL is blocked. send launch request for URL : " << url;
1143 std::unique_ptr<common::AppControl> request(
1144 common::AppControl::MakeAppcontrolFromURL(url));
1145 if (request.get() == NULL || !request->LaunchRequest()) {
1146 LOGGER(ERROR) << "Fail to send appcontrol request";
1152 void WebApplication::OnNotificationPermissionRequest(
1153 WebView*, const std::string& url,
1154 std::function<void(bool)> result_handler) {
1155 auto db = common::AppDB::GetInstance();
1156 std::string reminder =
1157 db->Get(kDBPrivateSection, kNotificationPermissionPrefix + url);
1158 if (reminder == "allowed") {
1159 result_handler(true);
1161 } else if (reminder == "denied") {
1162 result_handler(false);
1166 // Local Domain: Grant permission if defined, otherwise Popup user prompt.
1167 // Remote Domain: Popup user prompt.
1168 if (common::utils::StartsWith(url, "file://") &&
1169 FindPrivilegeFromConfig(app_data_, kNotificationPrivilege)) {
1170 result_handler(true);
1174 Popup* popup = Popup::CreatePopup(window_);
1175 popup->SetButtonType(Popup::ButtonType::AllowDenyButton);
1176 popup->SetTitle(popup_string::kPopupTitleWebNotification);
1177 popup->SetBody(popup_string::kPopupBodyWebNotification);
1178 popup->SetCheckBox(popup_string::kPopupCheckRememberPreference);
1179 popup->SetResultHandler(
1180 [db, result_handler, url](Popup* popup, void* /*user_data*/) {
1181 bool result = popup->GetButtonResult();
1182 bool remember = popup->GetCheckBoxResult();
1184 db->Set(kDBPrivateSection, kNotificationPermissionPrefix + url,
1185 result ? "allowed" : "denied");
1187 result_handler(result);
1193 void WebApplication::OnGeolocationPermissionRequest(
1194 WebView*, const std::string& url,
1195 std::function<void(bool)> result_handler) {
1196 auto db = common::AppDB::GetInstance();
1197 std::string reminder =
1198 db->Get(kDBPrivateSection, kGeolocationPermissionPrefix + url);
1199 if (reminder == "allowed") {
1200 result_handler(true);
1202 } else if (reminder == "denied") {
1203 result_handler(false);
1207 // Local Domain: Grant permission if defined, otherwise block execution.
1208 // Remote Domain: Popup user prompt if defined, otherwise block execution.
1209 if (!FindPrivilegeFromConfig(app_data_, kLocationPrivilege) &&
1210 !FindPrivilegeFromCynara(kLocationPrivilege)) {
1211 result_handler(false);
1215 if (common::utils::StartsWith(url, "file://")) {
1216 result_handler(true);
1220 Popup* popup = Popup::CreatePopup(window_);
1221 popup->SetButtonType(Popup::ButtonType::AllowDenyButton);
1222 popup->SetTitle(popup_string::kPopupTitleGeoLocation);
1223 popup->SetBody(popup_string::kPopupBodyGeoLocation);
1224 popup->SetCheckBox(popup_string::kPopupCheckRememberPreference);
1225 popup->SetResultHandler(
1226 [db, result_handler, url](Popup* popup, void* /*user_data*/) {
1227 bool result = popup->GetButtonResult();
1228 bool remember = popup->GetCheckBoxResult();
1230 db->Set(kDBPrivateSection, kGeolocationPermissionPrefix + url,
1231 result ? "allowed" : "denied");
1233 result_handler(result);
1239 void WebApplication::OnQuotaExceed(WebView*, const std::string& url,
1240 std::function<void(bool)> result_handler) {
1241 auto db = common::AppDB::GetInstance();
1242 std::string reminder =
1243 db->Get(kDBPrivateSection, kQuotaPermissionPrefix + url);
1244 if (reminder == "allowed") {
1245 result_handler(true);
1247 } else if (reminder == "denied") {
1248 result_handler(false);
1252 // Local Domain: Grant permission if defined, otherwise Popup user prompt.
1253 // Remote Domain: Popup user prompt.
1254 if (common::utils::StartsWith(url, "file://") &&
1255 FindPrivilegeFromConfig(app_data_, kStoragePrivilege)) {
1256 result_handler(true);
1260 Popup* popup = Popup::CreatePopup(window_);
1261 popup->SetButtonType(Popup::ButtonType::AllowDenyButton);
1262 popup->SetTitle(popup_string::kPopupTitleWebStorage);
1263 popup->SetBody(popup_string::kPopupBodyWebStorage);
1264 popup->SetCheckBox(popup_string::kPopupCheckRememberPreference);
1265 popup->SetResultHandler(
1266 [db, result_handler, url](Popup* popup, void* /*user_data*/) {
1267 bool result = popup->GetButtonResult();
1268 bool remember = popup->GetCheckBoxResult();
1270 db->Set(kDBPrivateSection, kQuotaPermissionPrefix + url,
1271 result ? "allowed" : "denied");
1273 result_handler(result);
1279 void WebApplication::OnAuthenticationRequest(
1280 WebView*, const std::string& /*url*/, const std::string& /*message*/,
1281 std::function<void(bool submit, const std::string& id,
1282 const std::string& password)> result_handler) {
1283 Popup* popup = Popup::CreatePopup(window_);
1284 popup->SetButtonType(Popup::ButtonType::LoginCancelButton);
1285 popup->SetFirstEntry(popup_string::kPopupLabelAuthusername,
1286 Popup::EntryType::Edit);
1287 popup->SetSecondEntry(popup_string::kPopupLabelPassword,
1288 Popup::EntryType::PwEdit);
1289 popup->SetTitle(popup_string::kPopupTitleAuthRequest);
1290 popup->SetBody(popup_string::kPopupBodyAuthRequest);
1291 popup->SetResultHandler([result_handler](Popup* popup, void* /*user_data*/) {
1292 bool result = popup->GetButtonResult();
1293 std::string id = popup->GetFirstEntryResult();
1294 std::string passwd = popup->GetSecondEntryResult();
1295 result_handler(result, id, passwd);
1301 void WebApplication::OnCertificateAllowRequest(
1302 WebView*, const std::string& url, const std::string& pem,
1303 std::function<void(bool allow)> result_handler) {
1304 auto db = common::AppDB::GetInstance();
1305 std::string reminder =
1306 db->Get(kDBPrivateSection, kCertificateAllowPrefix + pem);
1307 if (reminder == "allowed") {
1308 result_handler(true);
1310 } else if (reminder == "denied") {
1311 result_handler(false);
1315 Popup* popup = Popup::CreatePopup(window_);
1316 popup->SetButtonType(Popup::ButtonType::AllowDenyButton);
1317 popup->SetTitle(popup_string::kPopupTitleCert);
1318 popup->SetBody(popup_string::GetText(
1319 popup_string::kPopupBodyCert) + "\n\n" + url);
1320 popup->SetCheckBox(popup_string::kPopupCheckRememberPreference);
1321 popup->SetResultHandler(
1322 [db, result_handler, pem](Popup* popup, void* /*user_data*/) {
1323 bool result = popup->GetButtonResult();
1324 bool remember = popup->GetCheckBoxResult();
1326 db->Set(kDBPrivateSection, kCertificateAllowPrefix + pem,
1327 result ? "allowed" : "denied");
1329 result_handler(result);
1335 void WebApplication::OnUsermediaPermissionRequest(
1336 WebView*, const std::string& url,
1337 std::function<void(bool)> result_handler) {
1338 auto db = common::AppDB::GetInstance();
1339 std::string reminder =
1340 db->Get(kDBPrivateSection, kUsermediaPermissionPrefix + url);
1341 if (reminder == "allowed") {
1342 result_handler(true);
1344 } else if (reminder == "denied") {
1345 result_handler(false);
1349 // Local Domain: Grant permission if defined, otherwise block execution.
1350 // Remote Domain: Popup user prompt if defined, otherwise block execution.
1351 if (!FindPrivilegeFromConfig(app_data_, kUsermediaPrivilege) &&
1352 !(FindPrivilegeFromCynara(kCameraPrivilege) && FindPrivilegeFromCynara(kRecordPrivilege))) {
1353 result_handler(false);
1357 if (common::utils::StartsWith(url, "file://")) {
1358 result_handler(true);
1362 Popup* popup = Popup::CreatePopup(window_);
1363 popup->SetButtonType(Popup::ButtonType::AllowDenyButton);
1364 popup->SetTitle(popup_string::kPopupTitleUserMedia);
1365 popup->SetBody(popup_string::kPopupBodyUserMedia);
1366 popup->SetCheckBox(popup_string::kPopupCheckRememberPreference);
1367 popup->SetResultHandler(
1368 [db, result_handler, url](Popup* popup, void* /*user_data*/) {
1369 bool result = popup->GetButtonResult();
1370 bool remember = popup->GetCheckBoxResult();
1372 db->Set(kDBPrivateSection, kUsermediaPermissionPrefix + url,
1373 result ? "allowed" : "denied");
1375 result_handler(result);
1381 } // namespace runtime