// // Created by boyan on 10/21/21. // #include "webview_window.h" #include #include "message_channel_plugin.h" namespace { gboolean on_load_failed_with_tls_errors( WebKitWebView *web_view, char *failing_uri, GTlsCertificate *certificate, GTlsCertificateFlags errors, gpointer user_data) { auto *webview = static_cast(user_data); g_critical("on_load_failed_with_tls_errors: %s %p error= %d", failing_uri, webview, errors); // TODO allow certificate for some certificate ? // maybe we can use the pem from https://source.chromium.org/chromium/chromium/src/+/master:net/data/ssl/ev_roots/ // webkit_web_context_allow_tls_certificate_for_host(webkit_web_view_get_context(web_view), certificate, uri->host); // webkit_web_view_load_uri(web_view, failing_uri); return false; } GtkWidget *on_create(WebKitWebView *web_view, WebKitNavigationAction *navigation_action, gpointer user_data) { return GTK_WIDGET(web_view); } void on_load_changed(WebKitWebView *web_view, WebKitLoadEvent load_event, gpointer user_data) { auto *window = static_cast(user_data); window->OnLoadChanged(load_event); } gboolean decide_policy_cb(WebKitWebView *web_view, WebKitPolicyDecision *decision, WebKitPolicyDecisionType type, gpointer user_data) { auto *window = static_cast(user_data); return window->DecidePolicy(decision, type); } } WebviewWindow::WebviewWindow( FlMethodChannel *method_channel, int64_t window_id, std::function on_close_callback, const std::string &title, int width, int height, int title_bar_height ) : method_channel_(method_channel), window_id_(window_id), on_close_callback_(std::move(on_close_callback)), default_user_agent_() { g_object_ref(method_channel_); window_ = gtk_window_new(GTK_WINDOW_TOPLEVEL); g_signal_connect(G_OBJECT(window_), "destroy", G_CALLBACK(+[](GtkWidget *, gpointer arg) { auto *window = static_cast(arg); if (window->on_close_callback_) { window->on_close_callback_(); } auto *args = fl_value_new_map(); fl_value_set(args, fl_value_new_string("id"), fl_value_new_int(window->window_id_)); fl_method_channel_invoke_method( FL_METHOD_CHANNEL(window->method_channel_), "onWindowClose", args, nullptr, nullptr, nullptr); }), this); gtk_window_set_title(GTK_WINDOW(window_), title.c_str()); gtk_window_set_default_size(GTK_WINDOW(window_), width, height); gtk_window_set_position(GTK_WINDOW(window_), GTK_WIN_POS_CENTER); box_ = GTK_BOX(gtk_box_new(GTK_ORIENTATION_VERTICAL, 0)); gtk_container_add(GTK_CONTAINER(window_), GTK_WIDGET(box_)); // initial flutter_view g_autoptr(FlDartProject) project = fl_dart_project_new(); const char *args[] = {"web_view_title_bar", g_strdup_printf("%ld", window_id), nullptr}; fl_dart_project_set_dart_entrypoint_arguments(project, const_cast(args)); auto *title_bar = fl_view_new(project); g_autoptr(FlPluginRegistrar) webview_universal_registrar = fl_plugin_registry_get_registrar_for_plugin(FL_PLUGIN_REGISTRY(title_bar), "WebviewUniversalPlugin"); client_message_channel_plugin_register_with_registrar(webview_universal_registrar); gtk_widget_set_size_request(GTK_WIDGET(title_bar), -1, title_bar_height); gtk_box_pack_start(box_, GTK_WIDGET(title_bar), FALSE, FALSE, 0); // initial web_view webview_ = webkit_web_view_new(); g_signal_connect(G_OBJECT(webview_), "load-failed-with-tls-errors", G_CALLBACK(on_load_failed_with_tls_errors), this); g_signal_connect(G_OBJECT(webview_), "create", G_CALLBACK(on_create), this); g_signal_connect(G_OBJECT(webview_), "load-changed", G_CALLBACK(on_load_changed), this); g_signal_connect(G_OBJECT(webview_), "decide-policy", G_CALLBACK(decide_policy_cb), this); auto settings = webkit_web_view_get_settings(WEBKIT_WEB_VIEW(webview_)); webkit_settings_set_javascript_can_open_windows_automatically(settings, true); default_user_agent_ = webkit_settings_get_user_agent(settings); gtk_box_pack_start(box_, webview_, true, true, 0); gtk_widget_grab_focus(GTK_WIDGET(webview_)); gtk_widget_show_all(window_); gtk_widget_queue_resize(GTK_WIDGET(title_bar)); } WebviewWindow::~WebviewWindow() { g_object_unref(method_channel_); g_debug("~WebviewWindow"); } void WebviewWindow::Navigate(const char *url) { webkit_web_view_load_uri(WEBKIT_WEB_VIEW(webview_), url); } void WebviewWindow::RunJavaScriptWhenContentReady(const char *java_script) { auto *manager = webkit_web_view_get_user_content_manager(WEBKIT_WEB_VIEW(webview_)); webkit_user_content_manager_add_script( manager, webkit_user_script_new(java_script, WEBKIT_USER_CONTENT_INJECT_TOP_FRAME, WEBKIT_USER_SCRIPT_INJECT_AT_DOCUMENT_START, nullptr, nullptr)); } void WebviewWindow::SetApplicationNameForUserAgent(const std::string &app_name) { auto *setting = webkit_web_view_get_settings(WEBKIT_WEB_VIEW(webview_)); webkit_settings_set_user_agent(setting, (default_user_agent_ + app_name).c_str()); } void WebviewWindow::Close() { gtk_window_close(GTK_WINDOW(window_)); } void WebviewWindow::OnLoadChanged(WebKitLoadEvent load_event) { // notify history changed event. { auto can_go_back = webkit_web_view_can_go_back(WEBKIT_WEB_VIEW(webview_)); auto can_go_forward = webkit_web_view_can_go_forward(WEBKIT_WEB_VIEW(webview_)); auto *args = fl_value_new_map(); fl_value_set(args, fl_value_new_string("id"), fl_value_new_int(window_id_)); fl_value_set(args, fl_value_new_string("canGoBack"), fl_value_new_bool(can_go_back)); fl_value_set(args, fl_value_new_string("canGoForward"), fl_value_new_bool(can_go_forward)); fl_method_channel_invoke_method( FL_METHOD_CHANNEL(method_channel_), "onHistoryChanged", args, nullptr, nullptr, nullptr); } // notify load start/finished event. switch (load_event) { case WEBKIT_LOAD_STARTED: { auto *args = fl_value_new_map(); fl_value_set(args, fl_value_new_string("id"), fl_value_new_int(window_id_)); fl_method_channel_invoke_method( FL_METHOD_CHANNEL(method_channel_), "onNavigationStarted", args, nullptr, nullptr, nullptr); break; } case WEBKIT_LOAD_FINISHED: { auto *args = fl_value_new_map(); fl_value_set(args, fl_value_new_string("id"), fl_value_new_int(window_id_)); fl_method_channel_invoke_method( FL_METHOD_CHANNEL(method_channel_), "onNavigationCompleted", args, nullptr, nullptr, nullptr); break; } default :break; } } void WebviewWindow::GoForward() { webkit_web_view_go_forward(WEBKIT_WEB_VIEW(webview_)); } void WebviewWindow::GoBack() { webkit_web_view_go_back(WEBKIT_WEB_VIEW(webview_)); } void WebviewWindow::Reload() { webkit_web_view_reload(WEBKIT_WEB_VIEW(webview_)); } void WebviewWindow::StopLoading() { webkit_web_view_stop_loading(WEBKIT_WEB_VIEW(webview_)); } gboolean WebviewWindow::DecidePolicy(WebKitPolicyDecision *decision, WebKitPolicyDecisionType type) { if (type == WEBKIT_POLICY_DECISION_TYPE_NAVIGATION_ACTION) { auto *navigation_decision = WEBKIT_NAVIGATION_POLICY_DECISION (decision); auto *navigation_action = webkit_navigation_policy_decision_get_navigation_action(navigation_decision); auto *request = webkit_navigation_action_get_request(navigation_action); auto *uri = webkit_uri_request_get_uri(request); auto *args = fl_value_new_map(); fl_value_set(args, fl_value_new_string("id"), fl_value_new_int(window_id_)); fl_value_set(args, fl_value_new_string("url"), fl_value_new_string(uri)); fl_method_channel_invoke_method( FL_METHOD_CHANNEL(method_channel_), "onUrlRequested", args, nullptr, nullptr, nullptr); } return false; } void WebviewWindow::EvaluateJavaScript(const char *java_script, FlMethodCall *call) { webkit_web_view_run_javascript( WEBKIT_WEB_VIEW(webview_), java_script, nullptr, [](GObject *object, GAsyncResult *result, gpointer user_data) { auto *call = static_cast(user_data); GError *error = nullptr; auto *js_result = webkit_web_view_run_javascript_finish(WEBKIT_WEB_VIEW(object), result, &error); if (!js_result) { fl_method_call_respond_error(call, "failed to evaluate javascript.", error->message, nullptr, nullptr); g_error_free(error); } else { auto *js_value = jsc_value_to_json(webkit_javascript_result_get_js_value(js_result), 0); fl_method_call_respond_success(call, js_value ? fl_value_new_string(js_value) : nullptr, nullptr); } g_object_unref(call); }, g_object_ref(call)); }