You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

235 line
9.1 KiB

  1. //
  2. // Created by boyan on 10/21/21.
  3. //
  4. #include "webview_window.h"
  5. #include <utility>
  6. #include "message_channel_plugin.h"
  7. namespace {
  8. gboolean on_load_failed_with_tls_errors(
  9. WebKitWebView *web_view,
  10. char *failing_uri,
  11. GTlsCertificate *certificate,
  12. GTlsCertificateFlags errors,
  13. gpointer user_data) {
  14. auto *webview = static_cast<WebviewWindow *>(user_data);
  15. g_critical("on_load_failed_with_tls_errors: %s %p error= %d", failing_uri, webview, errors);
  16. // TODO allow certificate for some certificate ?
  17. // maybe we can use the pem from https://source.chromium.org/chromium/chromium/src/+/master:net/data/ssl/ev_roots/
  18. // webkit_web_context_allow_tls_certificate_for_host(webkit_web_view_get_context(web_view), certificate, uri->host);
  19. // webkit_web_view_load_uri(web_view, failing_uri);
  20. return false;
  21. }
  22. GtkWidget *on_create(WebKitWebView *web_view,
  23. WebKitNavigationAction *navigation_action,
  24. gpointer user_data) {
  25. return GTK_WIDGET(web_view);
  26. }
  27. void on_load_changed(WebKitWebView *web_view,
  28. WebKitLoadEvent load_event,
  29. gpointer user_data) {
  30. auto *window = static_cast<WebviewWindow *>(user_data);
  31. window->OnLoadChanged(load_event);
  32. }
  33. gboolean decide_policy_cb(WebKitWebView *web_view,
  34. WebKitPolicyDecision *decision,
  35. WebKitPolicyDecisionType type,
  36. gpointer user_data) {
  37. auto *window = static_cast<WebviewWindow *>(user_data);
  38. return window->DecidePolicy(decision, type);
  39. }
  40. }
  41. WebviewWindow::WebviewWindow(
  42. FlMethodChannel *method_channel,
  43. int64_t window_id,
  44. std::function<void()> on_close_callback,
  45. const std::string &title,
  46. int width,
  47. int height,
  48. int title_bar_height
  49. ) : method_channel_(method_channel),
  50. window_id_(window_id),
  51. on_close_callback_(std::move(on_close_callback)),
  52. default_user_agent_() {
  53. g_object_ref(method_channel_);
  54. window_ = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  55. g_signal_connect(G_OBJECT(window_), "destroy",
  56. G_CALLBACK(+[](GtkWidget *, gpointer arg) {
  57. auto *window = static_cast<WebviewWindow *>(arg);
  58. if (window->on_close_callback_) {
  59. window->on_close_callback_();
  60. }
  61. auto *args = fl_value_new_map();
  62. fl_value_set(args, fl_value_new_string("id"), fl_value_new_int(window->window_id_));
  63. fl_method_channel_invoke_method(
  64. FL_METHOD_CHANNEL(window->method_channel_), "onWindowClose", args,
  65. nullptr, nullptr, nullptr);
  66. }), this);
  67. gtk_window_set_title(GTK_WINDOW(window_), title.c_str());
  68. gtk_window_set_default_size(GTK_WINDOW(window_), width, height);
  69. gtk_window_set_position(GTK_WINDOW(window_), GTK_WIN_POS_CENTER);
  70. box_ = GTK_BOX(gtk_box_new(GTK_ORIENTATION_VERTICAL, 0));
  71. gtk_container_add(GTK_CONTAINER(window_), GTK_WIDGET(box_));
  72. // initial flutter_view
  73. g_autoptr(FlDartProject) project = fl_dart_project_new();
  74. const char *args[] = {"web_view_title_bar", g_strdup_printf("%ld", window_id), nullptr};
  75. fl_dart_project_set_dart_entrypoint_arguments(project, const_cast<char **>(args));
  76. auto *title_bar = fl_view_new(project);
  77. g_autoptr(FlPluginRegistrar) webview_universal_registrar =
  78. fl_plugin_registry_get_registrar_for_plugin(FL_PLUGIN_REGISTRY(title_bar), "WebviewUniversalPlugin");
  79. client_message_channel_plugin_register_with_registrar(webview_universal_registrar);
  80. gtk_widget_set_size_request(GTK_WIDGET(title_bar), -1, title_bar_height);
  81. gtk_box_pack_start(box_, GTK_WIDGET(title_bar), FALSE, FALSE, 0);
  82. // initial web_view
  83. webview_ = webkit_web_view_new();
  84. g_signal_connect(G_OBJECT(webview_), "load-failed-with-tls-errors",
  85. G_CALLBACK(on_load_failed_with_tls_errors), this);
  86. g_signal_connect(G_OBJECT(webview_), "create",
  87. G_CALLBACK(on_create), this);
  88. g_signal_connect(G_OBJECT(webview_), "load-changed",
  89. G_CALLBACK(on_load_changed), this);
  90. g_signal_connect(G_OBJECT(webview_), "decide-policy",
  91. G_CALLBACK(decide_policy_cb), this);
  92. auto settings = webkit_web_view_get_settings(WEBKIT_WEB_VIEW(webview_));
  93. webkit_settings_set_javascript_can_open_windows_automatically(settings, true);
  94. default_user_agent_ = webkit_settings_get_user_agent(settings);
  95. gtk_box_pack_start(box_, webview_, true, true, 0);
  96. gtk_widget_grab_focus(GTK_WIDGET(webview_));
  97. gtk_widget_show_all(window_);
  98. gtk_widget_queue_resize(GTK_WIDGET(title_bar));
  99. }
  100. WebviewWindow::~WebviewWindow() {
  101. g_object_unref(method_channel_);
  102. g_debug("~WebviewWindow");
  103. }
  104. void WebviewWindow::Navigate(const char *url) {
  105. webkit_web_view_load_uri(WEBKIT_WEB_VIEW(webview_), url);
  106. }
  107. void WebviewWindow::RunJavaScriptWhenContentReady(const char *java_script) {
  108. auto *manager = webkit_web_view_get_user_content_manager(WEBKIT_WEB_VIEW(webview_));
  109. webkit_user_content_manager_add_script(
  110. manager,
  111. webkit_user_script_new(java_script,
  112. WEBKIT_USER_CONTENT_INJECT_TOP_FRAME,
  113. WEBKIT_USER_SCRIPT_INJECT_AT_DOCUMENT_START,
  114. nullptr,
  115. nullptr));
  116. }
  117. void WebviewWindow::SetApplicationNameForUserAgent(const std::string &app_name) {
  118. auto *setting = webkit_web_view_get_settings(WEBKIT_WEB_VIEW(webview_));
  119. webkit_settings_set_user_agent(setting, (default_user_agent_ + app_name).c_str());
  120. }
  121. void WebviewWindow::Close() {
  122. gtk_window_close(GTK_WINDOW(window_));
  123. }
  124. void WebviewWindow::OnLoadChanged(WebKitLoadEvent load_event) {
  125. // notify history changed event.
  126. {
  127. auto can_go_back = webkit_web_view_can_go_back(WEBKIT_WEB_VIEW(webview_));
  128. auto can_go_forward = webkit_web_view_can_go_forward(WEBKIT_WEB_VIEW(webview_));
  129. auto *args = fl_value_new_map();
  130. fl_value_set(args, fl_value_new_string("id"), fl_value_new_int(window_id_));
  131. fl_value_set(args, fl_value_new_string("canGoBack"), fl_value_new_bool(can_go_back));
  132. fl_value_set(args, fl_value_new_string("canGoForward"), fl_value_new_bool(can_go_forward));
  133. fl_method_channel_invoke_method(
  134. FL_METHOD_CHANNEL(method_channel_), "onHistoryChanged", args,
  135. nullptr, nullptr, nullptr);
  136. }
  137. // notify load start/finished event.
  138. switch (load_event) {
  139. case WEBKIT_LOAD_STARTED: {
  140. auto *args = fl_value_new_map();
  141. fl_value_set(args, fl_value_new_string("id"), fl_value_new_int(window_id_));
  142. fl_method_channel_invoke_method(
  143. FL_METHOD_CHANNEL(method_channel_), "onNavigationStarted", args,
  144. nullptr, nullptr, nullptr);
  145. break;
  146. }
  147. case WEBKIT_LOAD_FINISHED: {
  148. auto *args = fl_value_new_map();
  149. fl_value_set(args, fl_value_new_string("id"), fl_value_new_int(window_id_));
  150. fl_method_channel_invoke_method(
  151. FL_METHOD_CHANNEL(method_channel_), "onNavigationCompleted", args,
  152. nullptr, nullptr, nullptr);
  153. break;
  154. }
  155. default :break;
  156. }
  157. }
  158. void WebviewWindow::GoForward() {
  159. webkit_web_view_go_forward(WEBKIT_WEB_VIEW(webview_));
  160. }
  161. void WebviewWindow::GoBack() {
  162. webkit_web_view_go_back(WEBKIT_WEB_VIEW(webview_));
  163. }
  164. void WebviewWindow::Reload() {
  165. webkit_web_view_reload(WEBKIT_WEB_VIEW(webview_));
  166. }
  167. void WebviewWindow::StopLoading() {
  168. webkit_web_view_stop_loading(WEBKIT_WEB_VIEW(webview_));
  169. }
  170. gboolean WebviewWindow::DecidePolicy(WebKitPolicyDecision *decision, WebKitPolicyDecisionType type) {
  171. if (type == WEBKIT_POLICY_DECISION_TYPE_NAVIGATION_ACTION) {
  172. auto *navigation_decision = WEBKIT_NAVIGATION_POLICY_DECISION (decision);
  173. auto *navigation_action = webkit_navigation_policy_decision_get_navigation_action(navigation_decision);
  174. auto *request = webkit_navigation_action_get_request(navigation_action);
  175. auto *uri = webkit_uri_request_get_uri(request);
  176. auto *args = fl_value_new_map();
  177. fl_value_set(args, fl_value_new_string("id"), fl_value_new_int(window_id_));
  178. fl_value_set(args, fl_value_new_string("url"), fl_value_new_string(uri));
  179. fl_method_channel_invoke_method(
  180. FL_METHOD_CHANNEL(method_channel_), "onUrlRequested", args,
  181. nullptr, nullptr, nullptr);
  182. }
  183. return false;
  184. }
  185. void WebviewWindow::EvaluateJavaScript(const char *java_script, FlMethodCall *call) {
  186. webkit_web_view_run_javascript(
  187. WEBKIT_WEB_VIEW(webview_), java_script, nullptr,
  188. [](GObject *object, GAsyncResult *result, gpointer user_data) {
  189. auto *call = static_cast<FlMethodCall *>(user_data);
  190. GError *error = nullptr;
  191. auto *js_result = webkit_web_view_run_javascript_finish(WEBKIT_WEB_VIEW(object), result, &error);
  192. if (!js_result) {
  193. fl_method_call_respond_error(call, "failed to evaluate javascript.", error->message, nullptr, nullptr);
  194. g_error_free(error);
  195. } else {
  196. auto *js_value = jsc_value_to_json(webkit_javascript_result_get_js_value(js_result), 0);
  197. fl_method_call_respond_success(call, js_value ? fl_value_new_string(js_value) : nullptr, nullptr);
  198. }
  199. g_object_unref(call);
  200. },
  201. g_object_ref(call));
  202. }