///////////////////////////////////////////////////////////////////////////// // Name: src/gtk/webview_webkit2.cpp // Purpose: GTK WebKit2 backend for web view component // Author: Scott Talbert // Copyright: (c) 2017 Scott Talbert // Licence: wxWindows licence ///////////////////////////////////////////////////////////////////////////// // For compilers that support precompilation, includes "wx.h". #include "wx/wxprec.h" #if wxUSE_WEBVIEW && wxUSE_WEBVIEW_WEBKIT2 #include "wx/dir.h" #include "wx/dynlib.h" #include "wx/filename.h" #include "wx/stdpaths.h" #include "wx/stockitem.h" #include "wx/gtk/webview_webkit.h" #include "wx/gtk/control.h" #include "wx/gtk/private.h" #include "wx/filesys.h" #include "wx/base64.h" #include "wx/log.h" #include "wx/gtk/private/webview_webkit2_extension.h" #include "wx/gtk/private/string.h" #include "wx/gtk/private/webkit.h" #include "wx/gtk/private/error.h" #include "wx/private/jsscriptwrapper.h" #include #include #include // Helper function to get string from Webkit JS result bool wxGetStringFromJSResult(WebKitJavascriptResult* js_result, wxString* output) { JSGlobalContextRef context = webkit_javascript_result_get_global_context(js_result); JSValueRef value = webkit_javascript_result_get_value(js_result); JSValueRef exception = NULL; wxJSStringRef js_value ( JSValueIsObject(context, value) ? JSValueCreateJSONString(context, value, 0, &exception) : JSValueToStringCopy(context, value, &exception) ); if ( exception ) { if ( output ) { wxJSStringRef ex_value(JSValueToStringCopy(context, exception, NULL)); *output = ex_value.ToWxString(); } return false; } if ( output != NULL ) *output = js_value.ToWxString(); return true; } // ---------------------------------------------------------------------------- // GTK callbacks // ---------------------------------------------------------------------------- extern "C" { static void wxgtk_webview_webkit_load_changed(GtkWidget *, WebKitLoadEvent load_event, wxWebViewWebKit *webKitCtrl) { wxString url = webKitCtrl->GetCurrentURL(); wxString target; // TODO: get target (if possible) if (load_event == WEBKIT_LOAD_FINISHED) { webKitCtrl->m_busy = false; wxWebViewEvent event(wxEVT_WEBVIEW_LOADED, webKitCtrl->GetId(), url, target); event.SetEventObject(webKitCtrl); webKitCtrl->HandleWindowEvent(event); } else if (load_event == WEBKIT_LOAD_COMMITTED) { webKitCtrl->m_busy = true; wxWebViewEvent event(wxEVT_WEBVIEW_NAVIGATED, webKitCtrl->GetId(), url, target); event.SetEventObject(webKitCtrl); webKitCtrl->HandleWindowEvent(event); } } static gboolean wxgtk_webview_webkit_navigation(WebKitWebView *, WebKitPolicyDecision *decision, wxWebViewWebKit *webKitCtrl) { WebKitNavigationPolicyDecision* navigation_decision = WEBKIT_NAVIGATION_POLICY_DECISION(decision); WebKitNavigationAction* action = webkit_navigation_policy_decision_get_navigation_action(navigation_decision); WebKitURIRequest* request = webkit_navigation_action_get_request(action); const gchar* uri = webkit_uri_request_get_uri(request); wxString target = webkit_navigation_policy_decision_get_frame_name(navigation_decision); //If m_creating is true then we are the result of a new window //and so we need to send the event and veto the load if(webKitCtrl->m_creating) { webKitCtrl->m_creating = false; wxWebViewEvent event(wxEVT_WEBVIEW_NEWWINDOW, webKitCtrl->GetId(), wxString(uri, wxConvUTF8), target); event.SetEventObject(webKitCtrl); webKitCtrl->HandleWindowEvent(event); webkit_policy_decision_ignore(decision); return TRUE; } webKitCtrl->m_busy = true; wxWebViewEvent event(wxEVT_WEBVIEW_NAVIGATING, webKitCtrl->GetId(), wxString( uri, wxConvUTF8 ), target); event.SetEventObject(webKitCtrl); webKitCtrl->HandleWindowEvent(event); if (!event.IsAllowed()) { webKitCtrl->m_busy = false; webkit_policy_decision_ignore(decision); return TRUE; } else { return FALSE; } } static gboolean wxgtk_webview_webkit_load_failed(WebKitWebView *, WebKitLoadEvent, gchar *uri, GError *error, wxWebViewWebKit* webKitWindow) { webKitWindow->m_busy = false; wxWebViewNavigationError type = wxWEBVIEW_NAV_ERR_OTHER; wxString description(error->message, wxConvUTF8); if (strcmp(g_quark_to_string(error->domain), "soup_http_error_quark") == 0) { switch (error->code) { case SOUP_STATUS_CANCELLED: type = wxWEBVIEW_NAV_ERR_USER_CANCELLED; break; case SOUP_STATUS_CANT_RESOLVE: case SOUP_STATUS_NOT_FOUND: type = wxWEBVIEW_NAV_ERR_NOT_FOUND; break; case SOUP_STATUS_CANT_RESOLVE_PROXY: case SOUP_STATUS_CANT_CONNECT: case SOUP_STATUS_CANT_CONNECT_PROXY: case SOUP_STATUS_SSL_FAILED: case SOUP_STATUS_IO_ERROR: type = wxWEBVIEW_NAV_ERR_CONNECTION; break; case SOUP_STATUS_MALFORMED: type = wxWEBVIEW_NAV_ERR_REQUEST; break; case SOUP_STATUS_BAD_REQUEST: type = wxWEBVIEW_NAV_ERR_REQUEST; break; case SOUP_STATUS_UNAUTHORIZED: case SOUP_STATUS_FORBIDDEN: type = wxWEBVIEW_NAV_ERR_AUTH; break; case SOUP_STATUS_METHOD_NOT_ALLOWED: case SOUP_STATUS_NOT_ACCEPTABLE: type = wxWEBVIEW_NAV_ERR_SECURITY; break; case SOUP_STATUS_PROXY_AUTHENTICATION_REQUIRED: type = wxWEBVIEW_NAV_ERR_AUTH; break; case SOUP_STATUS_REQUEST_TIMEOUT: type = wxWEBVIEW_NAV_ERR_CONNECTION; break; case SOUP_STATUS_REQUEST_ENTITY_TOO_LARGE: case SOUP_STATUS_REQUEST_URI_TOO_LONG: case SOUP_STATUS_UNSUPPORTED_MEDIA_TYPE: type = wxWEBVIEW_NAV_ERR_REQUEST; break; case SOUP_STATUS_BAD_GATEWAY: case SOUP_STATUS_SERVICE_UNAVAILABLE: case SOUP_STATUS_GATEWAY_TIMEOUT: type = wxWEBVIEW_NAV_ERR_CONNECTION; break; case SOUP_STATUS_HTTP_VERSION_NOT_SUPPORTED: type = wxWEBVIEW_NAV_ERR_REQUEST; break; } } else if (strcmp(g_quark_to_string(error->domain), "webkit-network-error-quark") == 0) { switch (error->code) { case WEBKIT_NETWORK_ERROR_UNKNOWN_PROTOCOL: type = wxWEBVIEW_NAV_ERR_REQUEST; break; case WEBKIT_NETWORK_ERROR_CANCELLED: type = wxWEBVIEW_NAV_ERR_USER_CANCELLED; break; case WEBKIT_NETWORK_ERROR_FILE_DOES_NOT_EXIST: type = wxWEBVIEW_NAV_ERR_NOT_FOUND; break; } } else if (strcmp(g_quark_to_string(error->domain), "webkit-policy-error-quark") == 0) { switch (error->code) { case WEBKIT_POLICY_ERROR_CANNOT_USE_RESTRICTED_PORT: type = wxWEBVIEW_NAV_ERR_SECURITY; break; } } wxWebViewEvent event(wxEVT_WEBVIEW_ERROR, webKitWindow->GetId(), uri, ""); event.SetEventObject(webKitWindow); event.SetString(description); event.SetInt(type); webKitWindow->HandleWindowEvent(event); return FALSE; } static gboolean wxgtk_webview_webkit_new_window(WebKitPolicyDecision *decision, wxWebViewWebKit *webKitCtrl) { WebKitNavigationPolicyDecision* navigation_decision = WEBKIT_NAVIGATION_POLICY_DECISION(decision); WebKitNavigationAction* action = webkit_navigation_policy_decision_get_navigation_action(navigation_decision); WebKitURIRequest* request = webkit_navigation_action_get_request(action); const gchar* uri = webkit_uri_request_get_uri(request); wxString target = webkit_navigation_policy_decision_get_frame_name(navigation_decision); wxWebViewEvent event(wxEVT_WEBVIEW_NEWWINDOW, webKitCtrl->GetId(), wxString( uri, wxConvUTF8 ), target); event.SetEventObject(webKitCtrl); webKitCtrl->HandleWindowEvent(event); //We always want the user to handle this themselves webkit_policy_decision_ignore(decision); return TRUE; } static gboolean wxgtk_webview_webkit_enter_fullscreen(WebKitWebView *WXUNUSED(web_view), wxWebViewWebKit *webKitCtrl) { wxWebViewEvent event(wxEVT_WEBVIEW_FULLSCREEN_CHANGED, webKitCtrl->GetId(), wxString(), wxString()); event.SetEventObject(webKitCtrl); event.SetInt(1); webKitCtrl->HandleWindowEvent(event); return FALSE; } static gboolean wxgtk_webview_webkit_leave_fullscreen(WebKitWebView *WXUNUSED(web_view), wxWebViewWebKit *webKitCtrl) { wxWebViewEvent event(wxEVT_WEBVIEW_FULLSCREEN_CHANGED, webKitCtrl->GetId(), wxString(), wxString()); event.SetEventObject(webKitCtrl); event.SetInt(0); webKitCtrl->HandleWindowEvent(event); return FALSE; } static void wxgtk_webview_webkit_script_message_received(WebKitUserContentManager *WXUNUSED(content_manager), WebKitJavascriptResult *js_result, wxWebViewWebKit *webKitCtrl) { wxWebViewEvent event(wxEVT_WEBVIEW_SCRIPT_MESSAGE_RECEIVED, webKitCtrl->GetId(), webKitCtrl->GetCurrentURL(), ""); wxString msgStr; if (wxGetStringFromJSResult(js_result, &msgStr)) event.SetString(msgStr); webKitCtrl->HandleWindowEvent(event); } static gboolean wxgtk_webview_webkit_decide_policy(WebKitWebView *web_view, WebKitPolicyDecision *decision, WebKitPolicyDecisionType type, wxWebViewWebKit *webKitCtrl) { switch (type) { case WEBKIT_POLICY_DECISION_TYPE_NAVIGATION_ACTION: return wxgtk_webview_webkit_navigation(web_view, decision, webKitCtrl); case WEBKIT_POLICY_DECISION_TYPE_NEW_WINDOW_ACTION: return wxgtk_webview_webkit_new_window(decision, webKitCtrl); default: return FALSE; } } static void wxgtk_webview_webkit_title_changed(GtkWidget* widget, GParamSpec *, wxWebViewWebKit *webKitCtrl) { gchar *title; g_object_get(G_OBJECT(widget), "title", &title, NULL); wxWebViewEvent event(wxEVT_WEBVIEW_TITLE_CHANGED, webKitCtrl->GetId(), webKitCtrl->GetCurrentURL(), ""); event.SetEventObject(webKitCtrl); event.SetString(wxString(title, wxConvUTF8)); webKitCtrl->HandleWindowEvent(event); g_free(title); } static void wxgtk_webview_webkit_uri_scheme_request_cb(WebKitURISchemeRequest *request, wxWebViewWebKit *webKitCtrl) { const wxString scheme = wxString::FromUTF8(webkit_uri_scheme_request_get_scheme(request)); wxSharedPtr handler; wxVector > handlers = webKitCtrl->GetHandlers(); for(wxVector >::iterator it = handlers.begin(); it != handlers.end(); ++it) { if(scheme == (*it)->GetName()) { handler = (*it); } } if(handler) { const wxString uri = wxString::FromUTF8(webkit_uri_scheme_request_get_uri(request)); wxFSFile* file = handler->GetFile(uri); if(file) { gint64 length = file->GetStream()->GetLength(); guint8 *data = g_new(guint8, length); file->GetStream()->Read(data, length); GInputStream *stream = g_memory_input_stream_new_from_data(data, length, g_free); wxString mime = file->GetMimeType(); webkit_uri_scheme_request_finish(request, stream, length, mime.utf8_str()); } else { wxGtkError error(g_error_new(WEBKIT_NETWORK_ERROR, WEBKIT_NETWORK_ERROR_FILE_DOES_NOT_EXIST, "File not found: %s", uri.utf8_str().data())); webkit_uri_scheme_request_finish_error(request, error); } } else { wxGtkError error(g_error_new(WEBKIT_NETWORK_ERROR, WEBKIT_NETWORK_ERROR_UNKNOWN_PROTOCOL, "Unknown scheme: %s", scheme.utf8_str().data())); webkit_uri_scheme_request_finish_error(request, error); } } static gboolean wxgtk_webview_webkit_context_menu(WebKitWebView *, WebKitContextMenu *, GdkEvent *, WebKitHitTestResult *, wxWebViewWebKit *webKitCtrl) { return !webKitCtrl->IsContextMenuEnabled(); } static WebKitWebView* wxgtk_webview_webkit_create_webview(WebKitWebView *web_view, WebKitNavigationAction *, wxWebViewWebKit *webKitCtrl) { //As we do not know the uri being loaded at this point allow the load to //continue and catch it in navigation-policy-decision-requested webKitCtrl->m_creating = true; return web_view; } static void wxgtk_webview_webkit_counted_matches(WebKitFindController *, guint match_count, int *findCount) { *findCount = match_count; } // This function checks if the specified directory contains our web extension. static bool CheckDirectoryForWebExt(const wxString& dirname) { wxDir dir; if ( !wxDir::Exists(dirname) || !dir.Open(dirname) ) return false; wxString file; bool cont = dir.GetFirst ( &file, "webkit2_ext*" + wxDynamicLibrary::GetDllExt(wxDL_MODULE), wxDIR_FILES ); while ( cont ) { wxDynamicLibrary dl; if ( dl.Load(wxFileName(dirname, file).GetFullPath(), wxDL_VERBATIM | wxDL_LAZY) && dl.HasSymbol("webkit_web_extension_initialize_with_user_data") ) { // Looks like our extension. return true; } cont = dir.GetNext(&file); } return false; } static bool TrySetWebExtensionsDirectory(WebKitWebContext *context, const wxString& dir) { if (dir.empty() || !CheckDirectoryForWebExt(dir)) return false; webkit_web_context_set_web_extensions_directory(context, dir.utf8_str()); return true; } static wxString GetStandardWebExtensionsDir() { wxString dir = wxDynamicLibrary::GetPluginsDirectory(); if ( !dir.empty() ) dir += "/web-extensions"; return dir; } static void wxgtk_initialize_web_extensions(WebKitWebContext *context, GDBusServer *dbusServer) { const char *address = g_dbus_server_get_client_address(dbusServer); GVariant *user_data = g_variant_new("(s)", address); // Try to setup extension loading from the location it is supposed to be // normally installed in. if ( !TrySetWebExtensionsDirectory(context, GetStandardWebExtensionsDir()) ) { // These relative locations are used as fallbacks to allow running // the tests and sample using wxWebView before installing it. wxString exepath = wxFileName(wxStandardPaths::Get().GetExecutablePath()).GetPath(); if ( !exepath.empty() ) { wxString const directories[] = { exepath + "/..", exepath + "/../..", exepath + "/lib", }; for ( size_t n = 0; n < WXSIZEOF(directories); ++n ) { if ( !TrySetWebExtensionsDirectory(context, directories[n]) ) break; } } } webkit_web_context_set_web_extensions_initialization_user_data(context, user_data); } static gboolean wxgtk_new_connection_cb(GDBusServer *, GDBusConnection *connection, GDBusProxy **proxy) { GError *error = NULL; GDBusProxyFlags flags = GDBusProxyFlags(G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES | G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS); *proxy = g_dbus_proxy_new_sync(connection, flags, NULL, NULL, WXGTK_WEB_EXTENSION_OBJECT_PATH, WXGTK_WEB_EXTENSION_INTERFACE, NULL, &error); if (error) { g_warning("Failed to create dbus proxy: %s", error->message); g_error_free(error); } return TRUE; } static gboolean wxgtk_dbus_peer_is_authorized(GCredentials *peer_credentials) { static GCredentials *own_credentials = g_credentials_new(); GError *error = NULL; if (peer_credentials && g_credentials_is_same_user(peer_credentials, own_credentials, &error)) { return TRUE; } if (error) { g_warning("Failed to authorize web extension connection: %s", error->message); g_error_free(error); } return FALSE; } static gboolean wxgtk_authorize_authenticated_peer_cb(GDBusAuthObserver *, GIOStream *, GCredentials *credentials, wxWebViewWebKit *) { return wxgtk_dbus_peer_is_authorized(credentials); } } // extern "C" //----------------------------------------------------------------------------- // wxWebViewFactoryWebKit //----------------------------------------------------------------------------- wxVersionInfo wxWebViewFactoryWebKit::GetVersionInfo() { return wxVersionInfo("webkit2", webkit_get_major_version(), webkit_get_minor_version(), webkit_get_micro_version()); } //----------------------------------------------------------------------------- // wxWebViewWebKit //----------------------------------------------------------------------------- wxIMPLEMENT_DYNAMIC_CLASS(wxWebViewWebKit, wxWebView); wxWebViewWebKit::wxWebViewWebKit() { m_web_view = NULL; m_dbusServer = NULL; m_extension = NULL; } bool wxWebViewWebKit::Create(wxWindow *parent, wxWindowID id, const wxString &url, const wxPoint& pos, const wxSize& size, long style, const wxString& name) { m_web_view = NULL; m_dbusServer = NULL; m_extension = NULL; m_busy = false; m_guard = false; m_creating = false; FindClear(); // We currently unconditionally impose scrolling in both directions as it's // necessary to show arbitrary pages. style |= wxHSCROLL | wxVSCROLL; if (!PreCreation( parent, pos, size ) || !CreateBase( parent, id, pos, size, style, wxDefaultValidator, name )) { wxFAIL_MSG( wxT("wxWebViewWebKit creation failed") ); return false; } SetupWebExtensionServer(); g_signal_connect(webkit_web_context_get_default(), "initialize-web-extensions", G_CALLBACK(wxgtk_initialize_web_extensions), m_dbusServer); m_web_view = WEBKIT_WEB_VIEW(webkit_web_view_new()); GTKCreateScrolledWindowWith(GTK_WIDGET(m_web_view)); g_object_ref(m_widget); if (!m_customUserAgent.empty()) SetUserAgent(m_customUserAgent); g_signal_connect(m_web_view, "decide-policy", G_CALLBACK(wxgtk_webview_webkit_decide_policy), this); g_signal_connect(m_web_view, "load-failed", G_CALLBACK(wxgtk_webview_webkit_load_failed), this); g_signal_connect(m_web_view, "notify::title", G_CALLBACK(wxgtk_webview_webkit_title_changed), this); g_signal_connect(m_web_view, "context-menu", G_CALLBACK(wxgtk_webview_webkit_context_menu), this); g_signal_connect(m_web_view, "create", G_CALLBACK(wxgtk_webview_webkit_create_webview), this); g_signal_connect(m_web_view, "enter-fullscreen", G_CALLBACK(wxgtk_webview_webkit_enter_fullscreen), this); g_signal_connect(m_web_view, "leave-fullscreen", G_CALLBACK(wxgtk_webview_webkit_leave_fullscreen), this); WebKitFindController* findctrl = webkit_web_view_get_find_controller(m_web_view); g_signal_connect(findctrl, "counted-matches", G_CALLBACK(wxgtk_webview_webkit_counted_matches), &m_findCount); m_parent->DoAddChild( this ); PostCreation(size); /* Open a webpage */ webkit_web_view_load_uri(m_web_view, url.utf8_str()); // last to avoid getting signal too early g_signal_connect(m_web_view, "load-changed", G_CALLBACK(wxgtk_webview_webkit_load_changed), this); return true; } wxWebViewWebKit::~wxWebViewWebKit() { if (m_web_view) GTKDisconnect(m_web_view); if (m_dbusServer) { g_dbus_server_stop(m_dbusServer); g_signal_handlers_disconnect_by_data( webkit_web_context_get_default(), m_dbusServer); } g_clear_object(&m_dbusServer); g_clear_object(&m_extension); } bool wxWebViewWebKit::Enable( bool enable ) { if (!wxControl::Enable(enable)) return false; gtk_widget_set_sensitive(gtk_bin_get_child(GTK_BIN(m_widget)), enable); return true; } GdkWindow* wxWebViewWebKit::GTKGetWindow(wxArrayGdkWindows& WXUNUSED(windows)) const { GdkWindow* window = gtk_widget_get_parent_window(m_widget); return window; } void wxWebViewWebKit::ZoomIn() { SetWebkitZoom(GetWebkitZoom() + 0.1f); } void wxWebViewWebKit::ZoomOut() { SetWebkitZoom(GetWebkitZoom() - 0.1f); } void wxWebViewWebKit::SetWebkitZoom(float level) { webkit_web_view_set_zoom_level(m_web_view, double(level)); } float wxWebViewWebKit::GetWebkitZoom() const { return webkit_web_view_get_zoom_level(m_web_view); } void wxWebViewWebKit::EnableAccessToDevTools(bool enable) { WebKitSettings* settings = webkit_web_view_get_settings(m_web_view); webkit_settings_set_enable_developer_extras(settings, enable); } bool wxWebViewWebKit::IsAccessToDevToolsEnabled() const { WebKitSettings* settings = webkit_web_view_get_settings(m_web_view); return webkit_settings_get_enable_developer_extras(settings); } bool wxWebViewWebKit::SetUserAgent(const wxString& userAgent) { if (m_web_view) { WebKitSettings* settings = webkit_web_view_get_settings(m_web_view); webkit_settings_set_user_agent(settings, userAgent.utf8_str()); } else m_customUserAgent = userAgent; return true; } void wxWebViewWebKit::Stop() { webkit_web_view_stop_loading(m_web_view); } void wxWebViewWebKit::Reload(wxWebViewReloadFlags flags) { if (flags & wxWEBVIEW_RELOAD_NO_CACHE) { webkit_web_view_reload_bypass_cache(m_web_view); } else { webkit_web_view_reload(m_web_view); } } void wxWebViewWebKit::LoadURL(const wxString& url) { webkit_web_view_load_uri(m_web_view, wxGTK_CONV(url)); } void wxWebViewWebKit::GoBack() { webkit_web_view_go_back(m_web_view); } void wxWebViewWebKit::GoForward() { webkit_web_view_go_forward(m_web_view); } bool wxWebViewWebKit::CanGoBack() const { return webkit_web_view_can_go_back(m_web_view) != 0; } bool wxWebViewWebKit::CanGoForward() const { return webkit_web_view_can_go_forward(m_web_view) != 0; } void wxWebViewWebKit::ClearHistory() { // In WebKit2GTK+, the BackForwardList can't be cleared so do nothing. } void wxWebViewWebKit::EnableHistory(bool) { // In WebKit2GTK+, history can't be disabled so do nothing here. } wxVector > wxWebViewWebKit::GetBackwardHistory() { wxVector > backhist; WebKitBackForwardList* history = webkit_web_view_get_back_forward_list(m_web_view); GList* list = webkit_back_forward_list_get_back_list(history); //We need to iterate in reverse to get the order we desire for(int i = g_list_length(list) - 1; i >= 0 ; i--) { WebKitBackForwardListItem* gtkitem = (WebKitBackForwardListItem*)g_list_nth_data(list, i); wxWebViewHistoryItem* wxitem = new wxWebViewHistoryItem( webkit_back_forward_list_item_get_uri(gtkitem), webkit_back_forward_list_item_get_title(gtkitem)); wxitem->m_histItem = gtkitem; wxSharedPtr item(wxitem); backhist.push_back(item); } return backhist; } wxVector > wxWebViewWebKit::GetForwardHistory() { wxVector > forwardhist; WebKitBackForwardList* history = webkit_web_view_get_back_forward_list(m_web_view); GList* list = webkit_back_forward_list_get_forward_list(history); for(guint i = 0; i < g_list_length(list); i++) { WebKitBackForwardListItem* gtkitem = (WebKitBackForwardListItem*)g_list_nth_data(list, i); wxWebViewHistoryItem* wxitem = new wxWebViewHistoryItem( webkit_back_forward_list_item_get_uri(gtkitem), webkit_back_forward_list_item_get_title(gtkitem)); wxitem->m_histItem = gtkitem; wxSharedPtr item(wxitem); forwardhist.push_back(item); } return forwardhist; } void wxWebViewWebKit::LoadHistoryItem(wxSharedPtr item) { WebKitBackForwardListItem* gtkitem = (WebKitBackForwardListItem*)item->m_histItem; if(gtkitem) { webkit_web_view_go_to_back_forward_list_item(m_web_view, WEBKIT_BACK_FORWARD_LIST_ITEM(gtkitem)); } } extern "C" { static void wxgtk_can_execute_editing_command_cb(GObject*, GAsyncResult *res, void* user_data) { GAsyncResult** res_out = static_cast(user_data); g_object_ref(res); *res_out = res; } } bool wxWebViewWebKit::CanExecuteEditingCommand(const gchar* command) const { GAsyncResult *result = NULL; webkit_web_view_can_execute_editing_command(m_web_view, command, NULL, wxgtk_can_execute_editing_command_cb, &result); GMainContext *main_context = g_main_context_get_thread_default(); while (!result) { g_main_context_iteration(main_context, TRUE); } gboolean can_execute = webkit_web_view_can_execute_editing_command_finish(m_web_view, result, NULL); g_object_unref(result); return can_execute != 0; } bool wxWebViewWebKit::CanCut() const { return CanExecuteEditingCommand(WEBKIT_EDITING_COMMAND_CUT); } bool wxWebViewWebKit::CanCopy() const { return CanExecuteEditingCommand(WEBKIT_EDITING_COMMAND_COPY); } bool wxWebViewWebKit::CanPaste() const { return CanExecuteEditingCommand(WEBKIT_EDITING_COMMAND_PASTE); } void wxWebViewWebKit::Cut() { webkit_web_view_execute_editing_command(m_web_view, WEBKIT_EDITING_COMMAND_CUT); } void wxWebViewWebKit::Copy() { webkit_web_view_execute_editing_command(m_web_view, WEBKIT_EDITING_COMMAND_COPY); } void wxWebViewWebKit::Paste() { webkit_web_view_execute_editing_command(m_web_view, WEBKIT_EDITING_COMMAND_PASTE); } bool wxWebViewWebKit::CanUndo() const { return CanExecuteEditingCommand(WEBKIT_EDITING_COMMAND_UNDO); } bool wxWebViewWebKit::CanRedo() const { return CanExecuteEditingCommand(WEBKIT_EDITING_COMMAND_REDO); } void wxWebViewWebKit::Undo() { webkit_web_view_execute_editing_command(m_web_view, WEBKIT_EDITING_COMMAND_UNDO); } void wxWebViewWebKit::Redo() { webkit_web_view_execute_editing_command(m_web_view, WEBKIT_EDITING_COMMAND_REDO); } wxString wxWebViewWebKit::GetCurrentURL() const { // FIXME: check which encoding the web kit control uses instead of // assuming UTF8 (here and elsewhere too) return wxString::FromUTF8(webkit_web_view_get_uri(m_web_view)); } wxString wxWebViewWebKit::GetCurrentTitle() const { return wxString::FromUTF8(webkit_web_view_get_title(m_web_view)); } extern "C" { static void wxgtk_web_resource_get_data_cb(GObject*, GAsyncResult *res, void* user_data) { GAsyncResult** res_out = static_cast(user_data); g_object_ref(res); *res_out = res; } } wxString wxWebViewWebKit::GetPageSource() const { WebKitWebResource *resource = webkit_web_view_get_main_resource(m_web_view); if (!resource) { return wxString(); } GAsyncResult *result = NULL; webkit_web_resource_get_data(resource, NULL, wxgtk_web_resource_get_data_cb, &result); GMainContext *main_context = g_main_context_get_thread_default(); while (!result) { g_main_context_iteration(main_context, TRUE); } size_t length; guchar *source = webkit_web_resource_get_data_finish(resource, result, &length, NULL); if (result) { g_object_unref(result); } if (source) { wxString wxs(source, wxConvUTF8, length); free(source); return wxs; } return wxString(); } float wxWebViewWebKit::GetZoomFactor() const { return GetWebkitZoom(); } void wxWebViewWebKit::SetZoomFactor(float zoom) { SetWebkitZoom(zoom); } void wxWebViewWebKit::SetZoomType(wxWebViewZoomType type) { WebKitSettings* settings = webkit_web_view_get_settings(m_web_view); webkit_settings_set_zoom_text_only(settings, type == wxWEBVIEW_ZOOM_TYPE_TEXT); } wxWebViewZoomType wxWebViewWebKit::GetZoomType() const { WebKitSettings* settings = webkit_web_view_get_settings(m_web_view); gboolean tozoom = webkit_settings_get_zoom_text_only(settings); if (tozoom) return wxWEBVIEW_ZOOM_TYPE_TEXT; else return wxWEBVIEW_ZOOM_TYPE_LAYOUT; } bool wxWebViewWebKit::CanSetZoomType(wxWebViewZoomType) const { // this port supports all zoom types return true; } void wxWebViewWebKit::DoSetPage(const wxString& html, const wxString& baseUri) { webkit_web_view_load_html(m_web_view, html.mb_str(wxConvUTF8), baseUri.mb_str(wxConvUTF8)); } void wxWebViewWebKit::Print() { WebKitPrintOperation* printop = webkit_print_operation_new(m_web_view); webkit_print_operation_run_dialog(printop, NULL); g_object_unref(printop); } bool wxWebViewWebKit::IsBusy() const { return m_busy; } void wxWebViewWebKit::SetEditable(bool enable) { #if WEBKIT_CHECK_VERSION(2, 8, 0) webkit_web_view_set_editable(m_web_view, enable); #else // Not supported in older versions. wxUnusedVar(enable); #endif } bool wxWebViewWebKit::IsEditable() const { #if WEBKIT_CHECK_VERSION(2, 8, 0) gboolean editable; g_object_get(m_web_view, "editable", &editable, NULL); return editable != 0; #else return false; #endif } void wxWebViewWebKit::DeleteSelection() { GDBusProxy *extension = GetExtensionProxy(); if (extension) { guint64 page_id = webkit_web_view_get_page_id(m_web_view); GVariant *retval = g_dbus_proxy_call_sync(extension, "DeleteSelection", g_variant_new("(t)", page_id), G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL); if (retval) { g_variant_unref(retval); } } } bool wxWebViewWebKit::HasSelection() const { GDBusProxy *extension = GetExtensionProxy(); if (extension) { guint64 page_id = webkit_web_view_get_page_id(m_web_view); GVariant *retval = g_dbus_proxy_call_sync(extension, "HasSelection", g_variant_new("(t)", page_id), G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL); if (retval) { gboolean has_selection = FALSE; g_variant_get(retval, "(b)", &has_selection); g_variant_unref(retval); return has_selection != 0; } } return false; } void wxWebViewWebKit::SelectAll() { webkit_web_view_execute_editing_command(m_web_view, WEBKIT_EDITING_COMMAND_SELECT_ALL); } wxString wxWebViewWebKit::GetSelectedText() const { GDBusProxy *extension = GetExtensionProxy(); if (extension) { guint64 page_id = webkit_web_view_get_page_id(m_web_view); GVariant *retval = g_dbus_proxy_call_sync(extension, "GetSelectedText", g_variant_new("(t)", page_id), G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL); if (retval) { char *text; g_variant_get(retval, "(s)", &text); g_variant_unref(retval); return wxString(text, wxConvUTF8); } } return wxString(); } wxString wxWebViewWebKit::GetSelectedSource() const { GDBusProxy *extension = GetExtensionProxy(); if (extension) { guint64 page_id = webkit_web_view_get_page_id(m_web_view); GVariant *retval = g_dbus_proxy_call_sync(extension, "GetSelectedSource", g_variant_new("(t)", page_id), G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL); if (retval) { char *source; g_variant_get(retval, "(s)", &source); g_variant_unref(retval); return wxString(source, wxConvUTF8); } } return wxString(); } void wxWebViewWebKit::ClearSelection() { GDBusProxy *extension = GetExtensionProxy(); if (extension) { guint64 page_id = webkit_web_view_get_page_id(m_web_view); GVariant *retval = g_dbus_proxy_call_sync(extension, "ClearSelection", g_variant_new("(t)", page_id), G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL); if (retval) { g_variant_unref(retval); } } } wxString wxWebViewWebKit::GetPageText() const { GDBusProxy *extension = GetExtensionProxy(); if (extension) { guint64 page_id = webkit_web_view_get_page_id(m_web_view); GVariant *retval = g_dbus_proxy_call_sync(extension, "GetPageText", g_variant_new("(t)", page_id), G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL); if (retval) { char *text; g_variant_get(retval, "(s)", &text); g_variant_unref(retval); return wxString(text, wxConvUTF8); } } return wxString(); } class wxWebKitRunScriptParams { public: const wxWebViewWebKit* webKitCtrl; void* clientData; }; extern "C" { static void wxgtk_run_javascript_cb(GObject *, GAsyncResult *res, void *user_data) { wxWebKitRunScriptParams* params = static_cast(user_data); params->webKitCtrl->ProcessJavaScriptResult(res, params); } } // extern "C" void wxWebViewWebKit::ProcessJavaScriptResult(GAsyncResult *res, wxWebKitRunScriptParams* params) const { wxGtkError error; wxWebKitJavascriptResult js_result ( webkit_web_view_run_javascript_finish ( m_web_view, res, error.Out() ) ); if ( js_result ) { wxString scriptResult; if ( wxGetStringFromJSResult(js_result, &scriptResult) ) { wxString scriptOutput; bool success = wxJSScriptWrapper::ExtractOutput(scriptResult, &scriptOutput); SendScriptResult(params->clientData, success, scriptOutput); } } else SendScriptResult(params->clientData, false, error.GetMessage()); delete params; } void wxWebViewWebKit::RunScriptAsync(const wxString& javascript, void* clientData) const { wxJSScriptWrapper wrapJS(javascript, wxJSScriptWrapper::JS_OUTPUT_STRING); // Collect parameters for access from the callback wxWebKitRunScriptParams* params = new wxWebKitRunScriptParams; params->webKitCtrl = this; params->clientData = clientData; webkit_web_view_run_javascript(m_web_view, wrapJS.GetWrappedCode().utf8_str(), NULL, wxgtk_run_javascript_cb, params); } bool wxWebViewWebKit::AddScriptMessageHandler(const wxString& name) { if (!m_web_view) return false; WebKitUserContentManager *ucm = webkit_web_view_get_user_content_manager(m_web_view); g_signal_connect(ucm, wxString::Format("script-message-received::%s", name).utf8_str(), G_CALLBACK(wxgtk_webview_webkit_script_message_received), this); bool res = webkit_user_content_manager_register_script_message_handler(ucm, name.utf8_str()); if (res) { // Make webkit message handler available under common name wxString js = wxString::Format("window.%s = window.webkit.messageHandlers.%s;", name, name); AddUserScript(js); RunScript(js); } return res; } bool wxWebViewWebKit::RemoveScriptMessageHandler(const wxString& name) { WebKitUserContentManager *ucm = webkit_web_view_get_user_content_manager(m_web_view); webkit_user_content_manager_unregister_script_message_handler(ucm, name.utf8_str()); return true; } bool wxWebViewWebKit::AddUserScript(const wxString& javascript, wxWebViewUserScriptInjectionTime injectionTime) { WebKitUserScript* userScript = webkit_user_script_new( javascript.utf8_str(), WEBKIT_USER_CONTENT_INJECT_ALL_FRAMES, (injectionTime == wxWEBVIEW_INJECT_AT_DOCUMENT_START) ? WEBKIT_USER_SCRIPT_INJECT_AT_DOCUMENT_START : WEBKIT_USER_SCRIPT_INJECT_AT_DOCUMENT_END, NULL, NULL ); WebKitUserContentManager *ucm = webkit_web_view_get_user_content_manager(m_web_view); webkit_user_content_manager_add_script(ucm, userScript); webkit_user_script_unref(userScript); return true; } void wxWebViewWebKit::RemoveAllUserScripts() { WebKitUserContentManager *ucm = webkit_web_view_get_user_content_manager(m_web_view); webkit_user_content_manager_remove_all_scripts(ucm); } void wxWebViewWebKit::RegisterHandler(wxSharedPtr handler) { m_handlerList.push_back(handler); WebKitWebContext* context = webkit_web_context_get_default(); webkit_web_context_register_uri_scheme(context, handler->GetName().utf8_str(), (WebKitURISchemeRequestCallback)wxgtk_webview_webkit_uri_scheme_request_cb, this, NULL); } void wxWebViewWebKit::EnableContextMenu(bool enable) { wxWebView::EnableContextMenu(enable); } long wxWebViewWebKit::Find(const wxString& text, int flags) { WebKitFindController* findctrl = webkit_web_view_get_find_controller(m_web_view); bool newSearch = false; if(text != m_findText || (flags & wxWEBVIEW_FIND_MATCH_CASE) != (m_findFlags & wxWEBVIEW_FIND_MATCH_CASE)) { newSearch = true; //If it is a new search we need to clear existing highlights webkit_find_controller_search_finish(findctrl); } m_findFlags = flags; m_findText = text; //If the search string is empty then we clear any selection and highlight if(text.empty()) { webkit_find_controller_search_finish(findctrl); ClearSelection(); return wxNOT_FOUND; } bool wrap = false, forward = true; guint32 options = WEBKIT_FIND_OPTIONS_NONE; if(flags & wxWEBVIEW_FIND_WRAP) { wrap = true; options |= WEBKIT_FIND_OPTIONS_WRAP_AROUND; } if(!(flags & wxWEBVIEW_FIND_MATCH_CASE)) { options |= WEBKIT_FIND_OPTIONS_CASE_INSENSITIVE; } if(flags & wxWEBVIEW_FIND_BACKWARDS) { forward = false; options |= WEBKIT_FIND_OPTIONS_BACKWARDS; } if(newSearch) { //Initially we count the matches to know how many we have m_findCount = -1; webkit_find_controller_count_matches(findctrl, wxGTK_CONV(text), options, G_MAXUINT); GMainContext *main_context = g_main_context_get_thread_default(); while (m_findCount == -1) { g_main_context_iteration(main_context, TRUE); } //Highlight them if needed if(flags & wxWEBVIEW_FIND_HIGHLIGHT_RESULT) { webkit_find_controller_search(findctrl, wxGTK_CONV(text), options, G_MAXUINT); } //In this case we return early to match IE behaviour m_findPosition = -1; return m_findCount; } else { if(forward) m_findPosition++; else m_findPosition--; if(m_findPosition < 0) m_findPosition += m_findCount; if(m_findPosition > m_findCount) m_findPosition -= m_findCount; } if(forward) { webkit_find_controller_search_next(findctrl); if(m_findPosition == m_findCount && !wrap) { return wxNOT_FOUND; } } else { webkit_find_controller_search_previous(findctrl); if(m_findPosition == -1 && !wrap) { return wxNOT_FOUND; } } return newSearch ? m_findCount : m_findPosition; } void wxWebViewWebKit::FindClear() { m_findCount = 0; m_findFlags = 0; m_findText.clear(); m_findPosition = -1; } // static wxVisualAttributes wxWebViewWebKit::GetClassDefaultAttributes(wxWindowVariant WXUNUSED(variant)) { return GetDefaultAttributesFromGTKWidget(webkit_web_view_new()); } void wxWebViewWebKit::SetupWebExtensionServer() { char *address = g_strdup_printf("unix:tmpdir=%s", g_get_tmp_dir()); char *guid = g_dbus_generate_guid(); GDBusAuthObserver *observer = g_dbus_auth_observer_new(); GError *error = NULL; g_signal_connect(observer, "authorize-authenticated-peer", G_CALLBACK(wxgtk_authorize_authenticated_peer_cb), this); m_dbusServer = g_dbus_server_new_sync(address, G_DBUS_SERVER_FLAGS_NONE, guid, observer, NULL, &error); if (error) { g_warning("Failed to start web extension server on %s: %s", address, error->message); g_error_free(error); } else { g_signal_connect(m_dbusServer, "new-connection", G_CALLBACK(wxgtk_new_connection_cb), &m_extension); g_dbus_server_start(m_dbusServer); } g_free(address); g_free(guid); g_object_unref(observer); } GDBusProxy *wxWebViewWebKit::GetExtensionProxy() const { if (!m_extension) { g_warning("Web extension not found in \"%s\", " "some wxWebView functionality will be not available", (const char*)GetStandardWebExtensionsDir().utf8_str()); } return m_extension; } #endif // wxUSE_WEBVIEW && wxUSE_WEBVIEW_WEBKIT2