/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim:expandtab:shiftwidth=2:tabstop=2: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "nsGNOMEShellSearchProvider.h" #include "RemoteUtils.h" #include "nsIStringBundle.h" #include "nsServiceManagerUtils.h" #include "nsPrintfCString.h" #include "mozilla/XREAppData.h" #include "nsAppRunner.h" #define DBUS_BUS_NAME_TEMPLATE "org.mozilla.%s.SearchProvider" #define DBUS_OBJECT_PATH_TEMPLATE "/org/mozilla/%s/SearchProvider" const char* GetDBusBusName() { static const char* name = []() { nsAutoCString appName; gAppData->GetDBusAppName(appName); return ToNewCString(nsPrintfCString(DBUS_BUS_NAME_TEMPLATE, appName.get())); // Intentionally leak }(); return name; } const char* GetDBusObjectPath() { static const char* path = []() { nsAutoCString appName; gAppData->GetDBusAppName(appName); return ToNewCString(nsPrintfCString(DBUS_OBJECT_PATH_TEMPLATE, appName.get())); // Intentionally leak }(); return path; } static bool GetGnomeSearchTitle(const char* aSearchedTerm, nsAutoCString& aGnomeSearchTitle) { static nsCOMPtr bundle; if (!bundle) { nsCOMPtr sbs = do_GetService(NS_STRINGBUNDLE_CONTRACTID); if (NS_WARN_IF(!sbs)) { return false; } sbs->CreateBundle("chrome://browser/locale/browser.properties", getter_AddRefs(bundle)); if (NS_WARN_IF(!bundle)) { return false; } } AutoTArray formatStrings; CopyUTF8toUTF16(nsCString(aSearchedTerm), *formatStrings.AppendElement()); nsAutoString gnomeSearchTitle; bundle->FormatStringFromName("gnomeSearchProviderSearchWeb", formatStrings, gnomeSearchTitle); AppendUTF16toUTF8(gnomeSearchTitle, aGnomeSearchTitle); return true; } int DBusGetIndexFromIDKey(const char* aIDKey) { // ID is NN:URL where NN is index to our current history // result container. char tmp[] = {aIDKey[0], aIDKey[1], '\0'}; return atoi(tmp); } static void ConcatArray(nsACString& aOutputStr, const char** aStringArray) { for (const char** term = aStringArray; *term; term++) { aOutputStr.Append(*term); if (*(term + 1)) { aOutputStr.Append(" "); } } } // GetInitialResultSet :: (as) → (as) // GetSubsearchResultSet :: (as,as) → (as) void DBusHandleResultSet(RefPtr aSearchResult, GVariant* aParameters, bool aInitialSearch, GDBusMethodInvocation* aReply) { // Inital search has params (as), any following one has (as,as) and we want // the second string array. fprintf(stderr, "%s\n", g_variant_get_type_string(aParameters)); RefPtr variant = dont_AddRef( g_variant_get_child_value(aParameters, aInitialSearch ? 0 : 1)); const char** stringArray = g_variant_get_strv(variant, nullptr); if (!stringArray) { g_dbus_method_invocation_return_error( aReply, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, "Wrong params!"); return; } aSearchResult->SetReply(aReply); nsAutoCString searchTerm; ConcatArray(searchTerm, stringArray); aSearchResult->SetSearchTerm(searchTerm.get()); GetGNOMEShellHistoryService()->QueryHistory(aSearchResult); // DBus reply will be send asynchronously by // nsGNOMEShellHistorySearchResult::SendDBusSearchResultReply() // when GetGNOMEShellHistoryService() has the results. g_free(stringArray); } /* "icon-data": a tuple of type (iiibiiay) describing a pixbuf with width, height, rowstride, has-alpha, bits-per-sample, channels, image data */ static void DBusAppendIcon(GVariantBuilder* aBuilder, GnomeHistoryIcon* aIcon) { GVariantBuilder b; g_variant_builder_init(&b, G_VARIANT_TYPE("(iiibiiay)")); g_variant_builder_add_value(&b, g_variant_new_int32(aIcon->GetWidth())); g_variant_builder_add_value(&b, g_variant_new_int32(aIcon->GetHeight())); g_variant_builder_add_value(&b, g_variant_new_int32(aIcon->GetWidth() * 4)); g_variant_builder_add_value(&b, g_variant_new_boolean(true)); g_variant_builder_add_value(&b, g_variant_new_int32(8)); g_variant_builder_add_value(&b, g_variant_new_int32(4)); g_variant_builder_add_value( &b, g_variant_new_fixed_array(G_VARIANT_TYPE("y"), aIcon->GetData(), aIcon->GetWidth() * aIcon->GetHeight() * 4, sizeof(char))); g_variant_builder_add(aBuilder, "{sv}", "icon-data", g_variant_builder_end(&b)); } /* Appends history search results to the DBUS reply. We can return those fields at GetResultMetas: "id": the result ID "name": the display name for the result "icon": a serialized GIcon (see g_icon_serialize()), or alternatively, "gicon": a textual representation of a GIcon (see g_icon_to_string()), or alternativly, "icon-data": a tuple of type (iiibiiay) describing a pixbuf with width, height, rowstride, has-alpha, bits-per-sample, and image data "description": an optional short description (1-2 lines) */ static already_AddRefed DBusAppendResultID( nsGNOMEShellHistorySearchResult* aSearchResult, const char* aID) { nsCOMPtr container = aSearchResult->GetSearchResultContainer(); int index = DBusGetIndexFromIDKey(aID); nsCOMPtr child; container->GetChild(index, getter_AddRefs(child)); nsAutoCString title; if (!child || NS_FAILED(child->GetTitle(title))) { return nullptr; } if (title.IsEmpty()) { if (NS_FAILED(child->GetUri(title)) || title.IsEmpty()) { return nullptr; } } GVariantBuilder b; g_variant_builder_init(&b, G_VARIANT_TYPE("a{sv}")); const char* titleStr = title.get(); g_variant_builder_add(&b, "{sv}", "id", g_variant_new_string(aID)); g_variant_builder_add(&b, "{sv}", "name", g_variant_new_string(titleStr)); GnomeHistoryIcon* icon = aSearchResult->GetHistoryIcon(index); if (icon) { DBusAppendIcon(&b, icon); } else { g_variant_builder_add(&b, "{sv}", "gicon", g_variant_new_string("text-html")); } return dont_AddRef(g_variant_ref_sink(g_variant_builder_end(&b))); } // Search the web for: "searchTerm" to the DBUS reply. static already_AddRefed DBusAppendSearchID(const char* aID) { /* aID contains: KEYWORD_SEARCH_STRING:ssssss KEYWORD_SEARCH_STRING is a 'special:search' keyword ssssss is a searched term, must be at least one character long */ // aID contains only 'KEYWORD_SEARCH_STRING:' so we're missing searched // string. if (strlen(aID) <= KEYWORD_SEARCH_STRING_LEN + 1) { return nullptr; } GVariantBuilder b; g_variant_builder_init(&b, G_VARIANT_TYPE("a{sv}")); g_variant_builder_add(&b, "{sv}", "id", g_variant_new_string(KEYWORD_SEARCH_STRING)); // Extract ssssss part from aID nsAutoCString searchTerm(aID + KEYWORD_SEARCH_STRING_LEN + 1); nsAutoCString gnomeSearchTitle; if (GetGnomeSearchTitle(searchTerm.get(), gnomeSearchTitle)) { g_variant_builder_add(&b, "{sv}", "name", g_variant_new_string(gnomeSearchTitle.get())); // TODO: When running on flatpak/snap we may need to use // icon like org.mozilla.Firefox or so. g_variant_builder_add(&b, "{sv}", "gicon", g_variant_new_string("firefox")); } return dont_AddRef(g_variant_ref_sink(g_variant_builder_end(&b))); } // GetResultMetas :: (as) → (aa{sv}) void DBusHandleResultMetas( RefPtr aSearchResult, GVariant* aParameters, GDBusMethodInvocation* aReply) { RefPtr variant = dont_AddRef(g_variant_get_child_value(aParameters, 0)); gsize elements; const char** stringArray = g_variant_get_strv(variant, &elements); if (!stringArray) { g_dbus_method_invocation_return_error( aReply, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, "Wrong params!"); return; } GVariantBuilder b; g_variant_builder_init(&b, G_VARIANT_TYPE("aa{sv}")); for (gsize i = 0; i < elements; i++) { RefPtr value; if (strncmp(stringArray[i], KEYWORD_SEARCH_STRING, KEYWORD_SEARCH_STRING_LEN) == 0) { value = DBusAppendSearchID(stringArray[i]); } else { value = DBusAppendResultID(aSearchResult, stringArray[i]); } if (value) { g_variant_builder_add_value(&b, value); } } GVariant* v = g_variant_builder_end(&b); g_dbus_method_invocation_return_value(aReply, g_variant_new_tuple(&v, 1)); g_free(stringArray); } // namespace mozilla static void ActivateResultID( RefPtr aSearchResult, const char* aResultID, uint32_t aTimeStamp) { char* commandLine = nullptr; int tmp; if (strncmp(aResultID, KEYWORD_SEARCH_STRING, KEYWORD_SEARCH_STRING_LEN) == 0) { const char* urlList[3] = {"unused", "--search", aSearchResult->GetSearchTerm().get()}; commandLine = ConstructCommandLine(std::size(urlList), (char**)urlList, nullptr, &tmp); } else { int keyIndex = atoi(aResultID); nsCOMPtr child; aSearchResult->GetSearchResultContainer()->GetChild(keyIndex, getter_AddRefs(child)); if (!child) { return; } nsAutoCString uri; nsresult rv = child->GetUri(uri); if (NS_FAILED(rv)) { return; } const char* urlList[2] = {"unused", uri.get()}; commandLine = ConstructCommandLine(std::size(urlList), (char**)urlList, nullptr, &tmp); } if (commandLine) { aSearchResult->HandleCommandLine(commandLine, aTimeStamp); free(commandLine); } } static void DBusLaunchWithAllResults( RefPtr aSearchResult, uint32_t aTimeStamp) { uint32_t childCount = 0; nsresult rv = aSearchResult->GetSearchResultContainer()->GetChildCount(&childCount); if (NS_FAILED(rv) || childCount == 0) { return; } if (childCount > MAX_SEARCH_RESULTS_NUM) { childCount = MAX_SEARCH_RESULTS_NUM; } // Allocate space for all found results, "unused", "--search" and // potential search request. char** urlList = (char**)moz_xmalloc(sizeof(char*) * (childCount + 3)); int urlListElements = 0; urlList[urlListElements++] = strdup("unused"); for (uint32_t i = 0; i < childCount; i++) { nsCOMPtr child; aSearchResult->GetSearchResultContainer()->GetChild(i, getter_AddRefs(child)); if (!IsHistoryResultNodeURI(child)) { continue; } nsAutoCString uri; nsresult rv = child->GetUri(uri); if (NS_FAILED(rv)) { continue; } urlList[urlListElements++] = strdup(uri.get()); } // When there isn't any uri to open pass search at least. if (!childCount) { urlList[urlListElements++] = strdup("--search"); urlList[urlListElements++] = strdup(aSearchResult->GetSearchTerm().get()); } int tmp; char* commandLine = ConstructCommandLine(urlListElements, urlList, nullptr, &tmp); if (commandLine) { aSearchResult->HandleCommandLine(commandLine, aTimeStamp); free(commandLine); } for (int i = 0; i < urlListElements; i++) { free(urlList[i]); } free(urlList); } // ActivateResult :: (s,as,u) → () void DBusActivateResult(RefPtr aSearchResult, GVariant* aParameters, GDBusMethodInvocation* aReply) { const char* resultID; // aParameters is "(s,as,u)" type RefPtr r = dont_AddRef(g_variant_get_child_value(aParameters, 0)); if (!(resultID = g_variant_get_string(r, nullptr))) { g_dbus_method_invocation_return_error( aReply, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, "Wrong params!"); return; } RefPtr t = dont_AddRef(g_variant_get_child_value(aParameters, 2)); uint32_t timestamp = g_variant_get_uint32(t); ActivateResultID(aSearchResult, resultID, timestamp); g_dbus_method_invocation_return_value(aReply, nullptr); } // LaunchSearch :: (as,u) → () void DBusLaunchSearch(RefPtr aSearchResult, GVariant* aParameters, GDBusMethodInvocation* aReply) { RefPtr variant = dont_AddRef(g_variant_get_child_value(aParameters, 1)); if (!variant) { g_dbus_method_invocation_return_error( aReply, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, "Wrong params!"); return; } DBusLaunchWithAllResults(aSearchResult, g_variant_get_uint32(variant)); g_dbus_method_invocation_return_value(aReply, nullptr); } bool IsHistoryResultNodeURI(nsINavHistoryResultNode* aHistoryNode) { uint32_t type; nsresult rv = aHistoryNode->GetType(&type); if (NS_FAILED(rv) || type != nsINavHistoryResultNode::RESULT_TYPE_URI) return false; nsAutoCString title; rv = aHistoryNode->GetTitle(title); if (NS_SUCCEEDED(rv) && !title.IsEmpty()) { return true; } rv = aHistoryNode->GetUri(title); return NS_SUCCEEDED(rv) && !title.IsEmpty(); }