nsGNOMEShellDBusHelper.cpp (14963B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim:expandtab:shiftwidth=2:tabstop=2: 3 */ 4 /* This Source Code Form is subject to the terms of the Mozilla Public 5 * License, v. 2.0. If a copy of the MPL was not distributed with this 6 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 7 8 #include "nsGNOMEShellSearchProvider.h" 9 10 #include "RemoteUtils.h" 11 #include "nsIStringBundle.h" 12 #include "nsServiceManagerUtils.h" 13 #include "nsPrintfCString.h" 14 #include "mozilla/XREAppData.h" 15 #include "nsAppRunner.h" 16 #include "nsImportModule.h" 17 #include "nsIOpenTabsProvider.h" 18 19 #define DBUS_BUS_NAME_TEMPLATE "org.mozilla.%s.SearchProvider" 20 #define DBUS_OBJECT_PATH_TEMPLATE "/org/mozilla/%s/SearchProvider" 21 22 const char* GetDBusBusName() { 23 static const char* name = []() { 24 nsAutoCString appName; 25 gAppData->GetDBusAppName(appName); 26 return ToNewCString(nsPrintfCString(DBUS_BUS_NAME_TEMPLATE, 27 appName.get())); // Intentionally leak 28 }(); 29 return name; 30 } 31 32 const char* GetDBusObjectPath() { 33 static const char* path = []() { 34 nsAutoCString appName; 35 gAppData->GetDBusAppName(appName); 36 return ToNewCString(nsPrintfCString(DBUS_OBJECT_PATH_TEMPLATE, 37 appName.get())); // Intentionally leak 38 }(); 39 return path; 40 } 41 42 static bool GetGnomeSearchTitle(const char* aSearchedTerm, 43 nsAutoCString& aGnomeSearchTitle) { 44 static nsCOMPtr<nsIStringBundle> bundle; 45 if (!bundle) { 46 nsCOMPtr<nsIStringBundleService> sbs = 47 do_GetService(NS_STRINGBUNDLE_CONTRACTID); 48 if (NS_WARN_IF(!sbs)) { 49 return false; 50 } 51 52 sbs->CreateBundle("chrome://browser/locale/browser.properties", 53 getter_AddRefs(bundle)); 54 if (NS_WARN_IF(!bundle)) { 55 return false; 56 } 57 } 58 59 AutoTArray<nsString, 1> formatStrings; 60 CopyUTF8toUTF16(nsCString(aSearchedTerm), *formatStrings.AppendElement()); 61 62 nsAutoString gnomeSearchTitle; 63 bundle->FormatStringFromName("gnomeSearchProviderSearchWeb", formatStrings, 64 gnomeSearchTitle); 65 AppendUTF16toUTF8(gnomeSearchTitle, aGnomeSearchTitle); 66 return true; 67 } 68 69 int DBusGetIndexFromIDKey(const char* aIDKey) { 70 // ID is NN:S:URL where NN is index to our current history 71 // result container. 72 char tmp[] = {aIDKey[0], aIDKey[1], '\0'}; 73 return atoi(tmp); 74 } 75 76 char DBusGetStateFromIDKey(const char* aIDKey) { 77 // ID is NN:S:URL where NN is index to our current history 78 // result container, and S is the state, which can be 'o'pen or 'h'istory 79 if (std::strlen(aIDKey) > 3) { 80 return aIDKey[3]; 81 } 82 // Should never happen, but just to avoid any possible segfault, we 83 // default to state 'history'. 84 return 'h'; 85 } 86 87 static void ConcatArray(nsACString& aOutputStr, const char** aStringArray) { 88 for (const char** term = aStringArray; *term; term++) { 89 aOutputStr.Append(*term); 90 if (*(term + 1)) { 91 aOutputStr.Append(" "); 92 } 93 } 94 } 95 96 // GetInitialResultSet :: (as) → (as) 97 // GetSubsearchResultSet :: (as,as) → (as) 98 void DBusHandleResultSet(RefPtr<nsGNOMEShellHistorySearchResult> aSearchResult, 99 GVariant* aParameters, bool aInitialSearch, 100 GDBusMethodInvocation* aReply) { 101 // Inital search has params (as), any following one has (as,as) and we want 102 // the second string array. 103 fprintf(stderr, "%s\n", g_variant_get_type_string(aParameters)); 104 RefPtr<GVariant> variant = dont_AddRef( 105 g_variant_get_child_value(aParameters, aInitialSearch ? 0 : 1)); 106 const char** stringArray = g_variant_get_strv(variant, nullptr); 107 if (!stringArray) { 108 g_dbus_method_invocation_return_error( 109 aReply, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, "Wrong params!"); 110 return; 111 } 112 113 aSearchResult->SetReply(aReply); 114 nsAutoCString searchTerm; 115 ConcatArray(searchTerm, stringArray); 116 aSearchResult->SetSearchTerm(searchTerm.get()); 117 GetGNOMEShellHistoryService()->QueryHistory(aSearchResult); 118 // DBus reply will be send asynchronously by 119 // nsGNOMEShellHistorySearchResult::SendDBusSearchResultReply() 120 // when GetGNOMEShellHistoryService() has the results. 121 122 g_free(stringArray); 123 } 124 125 /* 126 "icon-data": a tuple of type (iiibiiay) describing a pixbuf with width, 127 height, rowstride, has-alpha, 128 bits-per-sample, channels, 129 image data 130 */ 131 static void DBusAppendIcon(GVariantBuilder* aBuilder, GnomeHistoryIcon* aIcon) { 132 GVariantBuilder b; 133 g_variant_builder_init(&b, G_VARIANT_TYPE("(iiibiiay)")); 134 g_variant_builder_add_value(&b, g_variant_new_int32(aIcon->GetWidth())); 135 g_variant_builder_add_value(&b, g_variant_new_int32(aIcon->GetHeight())); 136 g_variant_builder_add_value(&b, g_variant_new_int32(aIcon->GetWidth() * 4)); 137 g_variant_builder_add_value(&b, g_variant_new_boolean(true)); 138 g_variant_builder_add_value(&b, g_variant_new_int32(8)); 139 g_variant_builder_add_value(&b, g_variant_new_int32(4)); 140 g_variant_builder_add_value( 141 &b, g_variant_new_fixed_array(G_VARIANT_TYPE("y"), aIcon->GetData(), 142 aIcon->GetWidth() * aIcon->GetHeight() * 4, 143 sizeof(char))); 144 g_variant_builder_add(aBuilder, "{sv}", "icon-data", 145 g_variant_builder_end(&b)); 146 } 147 148 /* Appends history search results to the DBUS reply. 149 150 We can return those fields at GetResultMetas: 151 152 "id": the result ID 153 "name": the display name for the result 154 "icon": a serialized GIcon (see g_icon_serialize()), or alternatively, 155 "gicon": a textual representation of a GIcon (see g_icon_to_string()), 156 or alternativly, 157 "icon-data": a tuple of type (iiibiiay) describing a pixbuf with width, 158 height, rowstride, has-alpha, bits-per-sample, and image data 159 "description": an optional short description (1-2 lines) 160 */ 161 static already_AddRefed<GVariant> DBusAppendResultID( 162 nsGNOMEShellHistorySearchResult* aSearchResult, const char* aID) { 163 nsCOMPtr<nsINavHistoryContainerResultNode> container = 164 aSearchResult->GetSearchResultContainer(); 165 166 int index = DBusGetIndexFromIDKey(aID); 167 char state = DBusGetStateFromIDKey(aID); 168 nsCOMPtr<nsINavHistoryResultNode> child; 169 container->GetChild(index, getter_AddRefs(child)); 170 nsAutoCString title; 171 if (!child || NS_FAILED(child->GetTitle(title))) { 172 return nullptr; 173 } 174 175 if (title.IsEmpty()) { 176 if (NS_FAILED(child->GetUri(title)) || title.IsEmpty()) { 177 return nullptr; 178 } 179 } 180 181 // Check if the URI state is "open tab". If so, mark it with an asterisk to 182 // indicate this to the user. 183 if (state == 'o') { 184 title = "(*) "_ns + title; 185 } 186 187 GVariantBuilder b; 188 g_variant_builder_init(&b, G_VARIANT_TYPE("a{sv}")); 189 190 const char* titleStr = title.get(); 191 g_variant_builder_add(&b, "{sv}", "id", g_variant_new_string(aID)); 192 g_variant_builder_add(&b, "{sv}", "name", g_variant_new_string(titleStr)); 193 194 GnomeHistoryIcon* icon = aSearchResult->GetHistoryIcon(index); 195 if (icon) { 196 DBusAppendIcon(&b, icon); 197 } else { 198 g_variant_builder_add(&b, "{sv}", "gicon", 199 g_variant_new_string("text-html")); 200 } 201 return dont_AddRef(g_variant_ref_sink(g_variant_builder_end(&b))); 202 } 203 204 // Search the web for: "searchTerm" to the DBUS reply. 205 static already_AddRefed<GVariant> DBusAppendSearchID(const char* aID) { 206 /* aID contains: 207 208 KEYWORD_SEARCH_STRING:ssssss 209 210 KEYWORD_SEARCH_STRING is a 'special:search' keyword 211 ssssss is a searched term, must be at least one character long 212 */ 213 214 // aID contains only 'KEYWORD_SEARCH_STRING:' so we're missing searched 215 // string. 216 if (strlen(aID) <= KEYWORD_SEARCH_STRING_LEN + 1) { 217 return nullptr; 218 } 219 220 GVariantBuilder b; 221 g_variant_builder_init(&b, G_VARIANT_TYPE("a{sv}")); 222 g_variant_builder_add(&b, "{sv}", "id", 223 g_variant_new_string(KEYWORD_SEARCH_STRING)); 224 225 // Extract ssssss part from aID 226 nsAutoCString searchTerm(aID + KEYWORD_SEARCH_STRING_LEN + 1); 227 nsAutoCString gnomeSearchTitle; 228 if (GetGnomeSearchTitle(searchTerm.get(), gnomeSearchTitle)) { 229 g_variant_builder_add(&b, "{sv}", "name", 230 g_variant_new_string(gnomeSearchTitle.get())); 231 // TODO: When running on flatpak/snap we may need to use 232 // icon like org.mozilla.Firefox or so. 233 g_variant_builder_add(&b, "{sv}", "gicon", g_variant_new_string("firefox")); 234 } 235 236 return dont_AddRef(g_variant_ref_sink(g_variant_builder_end(&b))); 237 } 238 239 // GetResultMetas :: (as) → (aa{sv}) 240 void DBusHandleResultMetas( 241 RefPtr<nsGNOMEShellHistorySearchResult> aSearchResult, 242 GVariant* aParameters, GDBusMethodInvocation* aReply) { 243 RefPtr<GVariant> variant = 244 dont_AddRef(g_variant_get_child_value(aParameters, 0)); 245 gsize elements; 246 const char** stringArray = g_variant_get_strv(variant, &elements); 247 if (!stringArray) { 248 g_dbus_method_invocation_return_error( 249 aReply, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, "Wrong params!"); 250 return; 251 } 252 253 GVariantBuilder b; 254 g_variant_builder_init(&b, G_VARIANT_TYPE("aa{sv}")); 255 for (gsize i = 0; i < elements; i++) { 256 RefPtr<GVariant> value; 257 if (strncmp(stringArray[i], KEYWORD_SEARCH_STRING, 258 KEYWORD_SEARCH_STRING_LEN) == 0) { 259 value = DBusAppendSearchID(stringArray[i]); 260 } else { 261 value = DBusAppendResultID(aSearchResult, stringArray[i]); 262 } 263 if (value) { 264 g_variant_builder_add_value(&b, value); 265 } 266 } 267 268 GVariant* v = g_variant_builder_end(&b); 269 g_dbus_method_invocation_return_value(aReply, g_variant_new_tuple(&v, 1)); 270 271 g_free(stringArray); 272 } // namespace mozilla 273 274 static void ActivateResultID( 275 RefPtr<nsGNOMEShellHistorySearchResult> aSearchResult, 276 const char* aResultID, uint32_t aTimeStamp) { 277 mozilla::UniquePtr<char[]> commandLine; 278 int len; 279 280 if (strncmp(aResultID, KEYWORD_SEARCH_STRING, KEYWORD_SEARCH_STRING_LEN) == 281 0) { 282 const char* urlList[3] = {"unused", "--search", 283 aSearchResult->GetSearchTerm().get()}; 284 commandLine = 285 ConstructCommandLine(std::size(urlList), urlList, nullptr, &len); 286 } else { 287 int keyIndex = atoi(aResultID); 288 char state = DBusGetStateFromIDKey(aResultID); 289 nsCOMPtr<nsINavHistoryResultNode> child; 290 aSearchResult->GetSearchResultContainer()->GetChild(keyIndex, 291 getter_AddRefs(child)); 292 if (!child) { 293 return; 294 } 295 296 nsAutoCString uri; 297 nsresult rv = child->GetUri(uri); 298 if (NS_FAILED(rv)) { 299 return; 300 } 301 302 // If the state of the URI is 'o'pen, we send it along to JS and let 303 // it switch the tab accordingly 304 if (state == 'o') { 305 // If we can't successfully switch to an open tab, use the existing 306 // 'open in a new tab'-mechanism as a fallback. 307 nsresult rv; 308 nsCOMPtr<nsIOpenTabsProvider> provider = do_ImportESModule( 309 "moz-src:///browser/components/shell/OpenTabsProvider.sys.mjs", &rv); 310 if (NS_SUCCEEDED(rv)) { 311 rv = provider->SwitchToOpenTab(uri); 312 if (NS_SUCCEEDED(rv)) { 313 return; 314 } 315 } 316 } 317 318 const char* urlList[2] = {"unused", uri.get()}; 319 commandLine = 320 ConstructCommandLine(std::size(urlList), urlList, nullptr, &len); 321 } 322 323 if (commandLine) { 324 aSearchResult->HandleCommandLine(mozilla::Span(commandLine.get(), len), 325 aTimeStamp); 326 } 327 } 328 329 static void DBusLaunchWithAllResults( 330 RefPtr<nsGNOMEShellHistorySearchResult> aSearchResult, 331 uint32_t aTimeStamp) { 332 uint32_t childCount = 0; 333 nsresult rv = 334 aSearchResult->GetSearchResultContainer()->GetChildCount(&childCount); 335 if (NS_FAILED(rv) || childCount == 0) { 336 return; 337 } 338 339 if (childCount > MAX_SEARCH_RESULTS_NUM) { 340 childCount = MAX_SEARCH_RESULTS_NUM; 341 } 342 343 // Allocate space for all found results, "unused", "--search" and 344 // potential search request. 345 const char** urlList = 346 (const char**)moz_xmalloc(sizeof(char*) * (childCount + 3)); 347 int urlListElements = 0; 348 349 urlList[urlListElements++] = strdup("unused"); 350 351 for (uint32_t i = 0; i < childCount; i++) { 352 nsCOMPtr<nsINavHistoryResultNode> child; 353 aSearchResult->GetSearchResultContainer()->GetChild(i, 354 getter_AddRefs(child)); 355 356 if (!IsHistoryResultNodeURI(child)) { 357 continue; 358 } 359 360 nsAutoCString uri; 361 nsresult rv = child->GetUri(uri); 362 if (NS_FAILED(rv)) { 363 continue; 364 } 365 urlList[urlListElements++] = strdup(uri.get()); 366 } 367 368 // When there isn't any uri to open pass search at least. 369 if (!childCount) { 370 urlList[urlListElements++] = strdup("--search"); 371 urlList[urlListElements++] = strdup(aSearchResult->GetSearchTerm().get()); 372 } 373 374 int len; 375 mozilla::UniquePtr<char[]> commandLine = 376 ConstructCommandLine(urlListElements, urlList, nullptr, &len); 377 if (commandLine) { 378 aSearchResult->HandleCommandLine(mozilla::Span(commandLine.get(), len), 379 aTimeStamp); 380 } 381 382 for (int i = 0; i < urlListElements; i++) { 383 free((void*)urlList[i]); 384 } 385 free(urlList); 386 } 387 388 // ActivateResult :: (s,as,u) → () 389 void DBusActivateResult(RefPtr<nsGNOMEShellHistorySearchResult> aSearchResult, 390 GVariant* aParameters, GDBusMethodInvocation* aReply) { 391 const char* resultID; 392 393 // aParameters is "(s,as,u)" type 394 RefPtr<GVariant> r = dont_AddRef(g_variant_get_child_value(aParameters, 0)); 395 if (!(resultID = g_variant_get_string(r, nullptr))) { 396 g_dbus_method_invocation_return_error( 397 aReply, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, "Wrong params!"); 398 return; 399 } 400 RefPtr<GVariant> t = dont_AddRef(g_variant_get_child_value(aParameters, 2)); 401 uint32_t timestamp = g_variant_get_uint32(t); 402 403 ActivateResultID(aSearchResult, resultID, timestamp); 404 g_dbus_method_invocation_return_value(aReply, nullptr); 405 } 406 407 // LaunchSearch :: (as,u) → () 408 void DBusLaunchSearch(RefPtr<nsGNOMEShellHistorySearchResult> aSearchResult, 409 GVariant* aParameters, GDBusMethodInvocation* aReply) { 410 RefPtr<GVariant> variant = 411 dont_AddRef(g_variant_get_child_value(aParameters, 1)); 412 if (!variant) { 413 g_dbus_method_invocation_return_error( 414 aReply, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, "Wrong params!"); 415 return; 416 } 417 DBusLaunchWithAllResults(aSearchResult, g_variant_get_uint32(variant)); 418 g_dbus_method_invocation_return_value(aReply, nullptr); 419 } 420 421 bool IsHistoryResultNodeURI(nsINavHistoryResultNode* aHistoryNode) { 422 uint32_t type; 423 nsresult rv = aHistoryNode->GetType(&type); 424 if (NS_FAILED(rv) || type != nsINavHistoryResultNode::RESULT_TYPE_URI) 425 return false; 426 427 nsAutoCString title; 428 rv = aHistoryNode->GetTitle(title); 429 if (NS_SUCCEEDED(rv) && !title.IsEmpty()) { 430 return true; 431 } 432 433 rv = aHistoryNode->GetUri(title); 434 return NS_SUCCEEDED(rv) && !title.IsEmpty(); 435 }