nsGNOMEShellSearchProvider.cpp (19233B)
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 "nsToolkitCompsCID.h" 11 #include "base/message_loop.h" // for MessageLoop 12 #include "base/task.h" // for NewRunnableMethod, etc 13 #include "mozilla/gfx/2D.h" 14 #include "nsComponentManagerUtils.h" 15 #include "nsIIOService.h" 16 #include "nsIURI.h" 17 #include "nsNetCID.h" 18 #include "nsPrintfCString.h" 19 #include "nsServiceManagerUtils.h" 20 #include "mozilla/GUniquePtr.h" 21 #include "nsImportModule.h" 22 #include "nsIOpenTabsProvider.h" 23 #include "imgIContainer.h" 24 #include "imgITools.h" 25 #include "mozilla/places/nsFaviconService.h" 26 27 using namespace mozilla; 28 using namespace mozilla::gfx; 29 30 // Mozilla has old GIO version in build roots 31 #define G_BUS_NAME_OWNER_FLAGS_DO_NOT_QUEUE GBusNameOwnerFlags(1 << 2) 32 33 static const char* introspect_template = 34 "<!DOCTYPE node PUBLIC \"-//freedesktop//DTD D-BUS Object Introspection " 35 "1.0//EN\"\n" 36 "\"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd\">\n" 37 "<node>\n" 38 " <interface name=\"org.gnome.Shell.SearchProvider2\">\n" 39 " <method name=\"GetInitialResultSet\">\n" 40 " <arg type=\"as\" name=\"terms\" direction=\"in\" />\n" 41 " <arg type=\"as\" name=\"results\" direction=\"out\" />\n" 42 " </method>\n" 43 " <method name=\"GetSubsearchResultSet\">\n" 44 " <arg type=\"as\" name=\"previous_results\" direction=\"in\" />\n" 45 " <arg type=\"as\" name=\"terms\" direction=\"in\" />\n" 46 " <arg type=\"as\" name=\"results\" direction=\"out\" />\n" 47 " </method>\n" 48 " <method name=\"GetResultMetas\">\n" 49 " <arg type=\"as\" name=\"identifiers\" direction=\"in\" />\n" 50 " <arg type=\"aa{sv}\" name=\"metas\" direction=\"out\" />\n" 51 " </method>\n" 52 " <method name=\"ActivateResult\">\n" 53 " <arg type=\"s\" name=\"identifier\" direction=\"in\" />\n" 54 " <arg type=\"as\" name=\"terms\" direction=\"in\" />\n" 55 " <arg type=\"u\" name=\"timestamp\" direction=\"in\" />\n" 56 " </method>\n" 57 " <method name=\"LaunchSearch\">\n" 58 " <arg type=\"as\" name=\"terms\" direction=\"in\" />\n" 59 " <arg type=\"u\" name=\"timestamp\" direction=\"in\" />\n" 60 " </method>\n" 61 "</interface>\n" 62 "</node>\n"; 63 64 // Inspired by SurfaceToPackedBGRA 65 static UniquePtr<uint8_t[]> SurfaceToPackedRGBA(DataSourceSurface* aSurface) { 66 IntSize size = aSurface->GetSize(); 67 CheckedInt<size_t> bufferSize = 68 CheckedInt<size_t>(size.width * 4) * CheckedInt<size_t>(size.height); 69 if (!bufferSize.isValid()) { 70 return nullptr; 71 } 72 UniquePtr<uint8_t[]> imageBuffer(new (std::nothrow) 73 uint8_t[bufferSize.value()]); 74 if (!imageBuffer) { 75 return nullptr; 76 } 77 78 DataSourceSurface::MappedSurface map; 79 if (!aSurface->Map(DataSourceSurface::MapType::READ, &map)) { 80 return nullptr; 81 } 82 83 // Convert BGRA to RGBA 84 uint32_t* aSrc = (uint32_t*)map.mData; 85 uint32_t* aDst = (uint32_t*)imageBuffer.get(); 86 for (int i = 0; i < size.width * size.height; i++, aDst++, aSrc++) { 87 *aDst = *aSrc & 0xff00ff00; 88 *aDst |= (*aSrc & 0xff) << 16; 89 *aDst |= (*aSrc & 0xff0000) >> 16; 90 } 91 92 aSurface->Unmap(); 93 94 return imageBuffer; 95 } 96 97 static nsresult UpdateHistoryIcon( 98 const places::FaviconPromise::ResolveOrRejectValue& aPromiseResult, 99 const RefPtr<nsGNOMEShellHistorySearchResult>& aSearchResult, 100 int aIconIndex, int aTimeStamp) { 101 // This is a callback from some previous search so we don't want it 102 if (aTimeStamp != aSearchResult->GetTimeStamp()) { 103 return NS_ERROR_FAILURE; 104 } 105 106 nsCOMPtr<nsIFavicon> favicon = 107 aPromiseResult.IsResolve() ? aPromiseResult.ResolveValue() : nullptr; 108 if (!favicon) { 109 return NS_ERROR_FAILURE; 110 } 111 112 // Get favicon content. 113 nsTArray<uint8_t> rawData; 114 nsresult rv = favicon->GetRawData(rawData); 115 NS_ENSURE_SUCCESS(rv, rv); 116 nsAutoCString mimeType; 117 rv = favicon->GetMimeType(mimeType); 118 NS_ENSURE_SUCCESS(rv, rv); 119 120 // Decode the image from the format it was returned to us in (probably PNG) 121 nsCOMPtr<imgIContainer> container; 122 nsCOMPtr<imgITools> imgtool = do_CreateInstance("@mozilla.org/image/tools;1"); 123 rv = imgtool->DecodeImageFromBuffer( 124 reinterpret_cast<const char*>(rawData.Elements()), rawData.Length(), 125 mimeType, getter_AddRefs(container)); 126 NS_ENSURE_SUCCESS(rv, rv); 127 128 RefPtr<SourceSurface> surface = container->GetFrame( 129 imgIContainer::FRAME_FIRST, 130 imgIContainer::FLAG_SYNC_DECODE | imgIContainer::FLAG_ASYNC_NOTIFY); 131 132 if (!surface || surface->GetFormat() != SurfaceFormat::B8G8R8A8) { 133 return NS_ERROR_FAILURE; 134 } 135 136 // Allocate a new buffer that we own. 137 RefPtr<DataSourceSurface> dataSurface = surface->GetDataSurface(); 138 UniquePtr<uint8_t[]> data = SurfaceToPackedRGBA(dataSurface); 139 if (!data) { 140 return NS_ERROR_OUT_OF_MEMORY; 141 } 142 143 aSearchResult->SetHistoryIcon(aTimeStamp, std::move(data), 144 surface->GetSize().width, 145 surface->GetSize().height, aIconIndex); 146 return NS_OK; 147 } 148 149 void nsGNOMEShellSearchProvider::HandleSearchResultSet( 150 GVariant* aParameters, GDBusMethodInvocation* aInvocation, 151 bool aInitialSearch) { 152 // Discard any existing search results. 153 mSearchResult = nullptr; 154 155 RefPtr<nsGNOMEShellHistorySearchResult> newSearch = 156 new nsGNOMEShellHistorySearchResult(this, mConnection, 157 mSearchResultTimeStamp); 158 mSearchResultTimeStamp++; 159 newSearch->SetTimeStamp(mSearchResultTimeStamp); 160 161 // Send the search request over DBus. We'll get reply over DBus it will be 162 // set to mSearchResult by nsGNOMEShellSearchProvider::SetSearchResult(). 163 DBusHandleResultSet(newSearch.forget(), aParameters, aInitialSearch, 164 aInvocation); 165 } 166 167 void nsGNOMEShellSearchProvider::HandleResultMetas( 168 GVariant* aParameters, GDBusMethodInvocation* aInvocation) { 169 if (mSearchResult) { 170 DBusHandleResultMetas(mSearchResult, aParameters, aInvocation); 171 } 172 } 173 174 void nsGNOMEShellSearchProvider::ActivateResult( 175 GVariant* aParameters, GDBusMethodInvocation* aInvocation) { 176 if (mSearchResult) { 177 DBusActivateResult(mSearchResult, aParameters, aInvocation); 178 } 179 } 180 181 void nsGNOMEShellSearchProvider::LaunchSearch( 182 GVariant* aParameters, GDBusMethodInvocation* aInvocation) { 183 if (mSearchResult) { 184 DBusLaunchSearch(mSearchResult, aParameters, aInvocation); 185 } 186 } 187 188 static void HandleMethodCall(GDBusConnection* aConnection, const gchar* aSender, 189 const gchar* aObjectPath, 190 const gchar* aInterfaceName, 191 const gchar* aMethodName, GVariant* aParameters, 192 GDBusMethodInvocation* aInvocation, 193 gpointer aUserData) { 194 MOZ_ASSERT(aUserData); 195 MOZ_ASSERT(NS_IsMainThread()); 196 197 if (strcmp("org.gnome.Shell.SearchProvider2", aInterfaceName) == 0) { 198 if (strcmp("GetInitialResultSet", aMethodName) == 0) { 199 static_cast<nsGNOMEShellSearchProvider*>(aUserData) 200 ->HandleSearchResultSet(aParameters, aInvocation, 201 /* aInitialSearch */ true); 202 } else if (strcmp("GetSubsearchResultSet", aMethodName) == 0) { 203 static_cast<nsGNOMEShellSearchProvider*>(aUserData) 204 ->HandleSearchResultSet(aParameters, aInvocation, 205 /* aInitialSearch */ false); 206 } else if (strcmp("GetResultMetas", aMethodName) == 0) { 207 static_cast<nsGNOMEShellSearchProvider*>(aUserData)->HandleResultMetas( 208 aParameters, aInvocation); 209 } else if (strcmp("ActivateResult", aMethodName) == 0) { 210 static_cast<nsGNOMEShellSearchProvider*>(aUserData)->ActivateResult( 211 aParameters, aInvocation); 212 } else if (strcmp("LaunchSearch", aMethodName) == 0) { 213 static_cast<nsGNOMEShellSearchProvider*>(aUserData)->LaunchSearch( 214 aParameters, aInvocation); 215 } else { 216 g_warning( 217 "nsGNOMEShellSearchProvider: HandleMethodCall() wrong method %s", 218 aMethodName); 219 } 220 } 221 } 222 223 static GVariant* HandleGetProperty(GDBusConnection* aConnection, 224 const gchar* aSender, 225 const gchar* aObjectPath, 226 const gchar* aInterfaceName, 227 const gchar* aPropertyName, GError** aError, 228 gpointer aUserData) { 229 MOZ_ASSERT(aUserData); 230 MOZ_ASSERT(NS_IsMainThread()); 231 g_set_error(aError, G_IO_ERROR, G_IO_ERROR_FAILED, 232 "%s:%s setting is not supported", aInterfaceName, aPropertyName); 233 return nullptr; 234 } 235 236 static gboolean HandleSetProperty(GDBusConnection* aConnection, 237 const gchar* aSender, 238 const gchar* aObjectPath, 239 const gchar* aInterfaceName, 240 const gchar* aPropertyName, GVariant* aValue, 241 GError** aError, gpointer aUserData) { 242 MOZ_ASSERT(aUserData); 243 MOZ_ASSERT(NS_IsMainThread()); 244 g_set_error(aError, G_IO_ERROR, G_IO_ERROR_FAILED, 245 "%s:%s setting is not supported", aInterfaceName, aPropertyName); 246 return false; 247 } 248 249 static const GDBusInterfaceVTable gInterfaceVTable = { 250 HandleMethodCall, HandleGetProperty, HandleSetProperty}; 251 252 void nsGNOMEShellSearchProvider::OnBusAcquired(GDBusConnection* aConnection) { 253 GUniquePtr<GError> error; 254 mIntrospectionData = dont_AddRef(g_dbus_node_info_new_for_xml( 255 introspect_template, getter_Transfers(error))); 256 if (!mIntrospectionData) { 257 g_warning( 258 "nsGNOMEShellSearchProvider: g_dbus_node_info_new_for_xml() failed! %s", 259 error->message); 260 return; 261 } 262 263 mRegistrationId = g_dbus_connection_register_object( 264 aConnection, GetDBusObjectPath(), mIntrospectionData->interfaces[0], 265 &gInterfaceVTable, this, /* user_data */ 266 nullptr, /* user_data_free_func */ 267 getter_Transfers(error)); /* GError** */ 268 269 if (mRegistrationId == 0) { 270 g_warning( 271 "nsGNOMEShellSearchProvider: g_dbus_connection_register_object() " 272 "failed! %s", 273 error->message); 274 return; 275 } 276 } 277 278 void nsGNOMEShellSearchProvider::OnNameAcquired(GDBusConnection* aConnection) { 279 mConnection = aConnection; 280 } 281 282 void nsGNOMEShellSearchProvider::OnNameLost(GDBusConnection* aConnection) { 283 mConnection = nullptr; 284 if (!mRegistrationId) { 285 return; 286 } 287 if (g_dbus_connection_unregister_object(aConnection, mRegistrationId)) { 288 mRegistrationId = 0; 289 } 290 } 291 292 nsresult nsGNOMEShellSearchProvider::Startup() { 293 if (mDBusID) { 294 // We're already connected so we don't need to reconnect 295 return NS_ERROR_ALREADY_INITIALIZED; 296 } 297 298 mDBusID = g_bus_own_name( 299 G_BUS_TYPE_SESSION, GetDBusBusName(), G_BUS_NAME_OWNER_FLAGS_DO_NOT_QUEUE, 300 [](GDBusConnection* aConnection, const gchar*, 301 gpointer aUserData) -> void { 302 static_cast<nsGNOMEShellSearchProvider*>(aUserData)->OnBusAcquired( 303 aConnection); 304 }, 305 [](GDBusConnection* aConnection, const gchar*, 306 gpointer aUserData) -> void { 307 static_cast<nsGNOMEShellSearchProvider*>(aUserData)->OnNameAcquired( 308 aConnection); 309 }, 310 [](GDBusConnection* aConnection, const gchar*, 311 gpointer aUserData) -> void { 312 static_cast<nsGNOMEShellSearchProvider*>(aUserData)->OnNameLost( 313 aConnection); 314 }, 315 this, nullptr); 316 317 if (!mDBusID) { 318 g_warning("nsGNOMEShellSearchProvider: g_bus_own_name() failed!"); 319 return NS_ERROR_FAILURE; 320 } 321 322 mSearchResultTimeStamp = 0; 323 return NS_OK; 324 } 325 326 void nsGNOMEShellSearchProvider::Shutdown() { 327 OnNameLost(mConnection); 328 if (mDBusID) { 329 g_bus_unown_name(mDBusID); 330 mDBusID = 0; 331 } 332 mIntrospectionData = nullptr; 333 } 334 335 bool nsGNOMEShellSearchProvider::SetSearchResult( 336 RefPtr<nsGNOMEShellHistorySearchResult> aSearchResult) { 337 MOZ_ASSERT(!mSearchResult); 338 339 if (mSearchResultTimeStamp != aSearchResult->GetTimeStamp()) { 340 NS_WARNING("Time stamp mismatch."); 341 return false; 342 } 343 mSearchResult = aSearchResult; 344 return true; 345 } 346 347 static void DispatchSearchResults( 348 RefPtr<nsGNOMEShellHistorySearchResult> aSearchResult, 349 nsCOMPtr<nsINavHistoryContainerResultNode> aHistResultContainer) { 350 aSearchResult->ReceiveSearchResultContainer(aHistResultContainer); 351 } 352 353 nsresult nsGNOMEShellHistoryService::QueryHistory( 354 RefPtr<nsGNOMEShellHistorySearchResult> aSearchResult) { 355 if (!mHistoryService) { 356 mHistoryService = do_GetService(NS_NAVHISTORYSERVICE_CONTRACTID); 357 if (!mHistoryService) { 358 return NS_ERROR_FAILURE; 359 } 360 } 361 362 nsresult rv; 363 nsCOMPtr<nsINavHistoryQuery> histQuery; 364 rv = mHistoryService->GetNewQuery(getter_AddRefs(histQuery)); 365 NS_ENSURE_SUCCESS(rv, rv); 366 367 rv = histQuery->SetSearchTerms( 368 NS_ConvertUTF8toUTF16(aSearchResult->GetSearchTerm())); 369 NS_ENSURE_SUCCESS(rv, rv); 370 371 nsCOMPtr<nsINavHistoryQueryOptions> histQueryOpts; 372 rv = mHistoryService->GetNewQueryOptions(getter_AddRefs(histQueryOpts)); 373 NS_ENSURE_SUCCESS(rv, rv); 374 375 rv = histQueryOpts->SetSortingMode( 376 nsINavHistoryQueryOptions::SORT_BY_FRECENCY_DESCENDING); 377 NS_ENSURE_SUCCESS(rv, rv); 378 379 rv = histQueryOpts->SetMaxResults(MAX_SEARCH_RESULTS_NUM); 380 NS_ENSURE_SUCCESS(rv, rv); 381 382 nsCOMPtr<nsINavHistoryResult> histResult; 383 rv = mHistoryService->ExecuteQuery(histQuery, histQueryOpts, 384 getter_AddRefs(histResult)); 385 NS_ENSURE_SUCCESS(rv, rv); 386 387 nsCOMPtr<nsINavHistoryContainerResultNode> resultContainer; 388 389 rv = histResult->GetRoot(getter_AddRefs(resultContainer)); 390 NS_ENSURE_SUCCESS(rv, rv); 391 392 rv = resultContainer->SetContainerOpen(true); 393 NS_ENSURE_SUCCESS(rv, rv); 394 395 // Simulate async searching by delayed reply. This search API will 396 // likely become async in the future and we want to be sure to not rely on 397 // its current synchronous behavior. 398 MOZ_ASSERT(MessageLoop::current()); 399 MessageLoop::current()->PostTask( 400 NewRunnableFunction("Gnome shell search results", &DispatchSearchResults, 401 aSearchResult, resultContainer)); 402 403 return NS_OK; 404 } 405 406 static void DBusGetIDKeyForURI(int aIndex, bool aIsOpen, nsAutoCString& aUri, 407 nsAutoCString& aIDKey) { 408 // Compose ID as NN:S:URL where NN is index to our current history 409 // result container and S is the state, which can be 'o'pen or 'h'istory 410 aIDKey = 411 nsPrintfCString("%.2d:%c:%s", aIndex, aIsOpen ? 'o' : 'h', aUri.get()); 412 } 413 414 // Send (as) rearch result reply 415 void nsGNOMEShellHistorySearchResult::HandleSearchResultReply() { 416 MOZ_ASSERT(mReply); 417 MOZ_ASSERT(mHistResultContainer); 418 419 GVariantBuilder b; 420 g_variant_builder_init(&b, G_VARIANT_TYPE("as")); 421 422 uint32_t childCount = 0; 423 nsresult rv = mHistResultContainer->GetChildCount(&childCount); 424 if (NS_SUCCEEDED(rv) && childCount > 0) { 425 // Obtain the favicon service and get the favicon for the specified page 426 auto* favIconSvc = nsFaviconService::GetFaviconService(); 427 nsCOMPtr<nsIIOService> ios(do_GetService(NS_IOSERVICE_CONTRACTID)); 428 429 if (childCount > MAX_SEARCH_RESULTS_NUM) { 430 childCount = MAX_SEARCH_RESULTS_NUM; 431 } 432 433 for (uint32_t i = 0; i < childCount; i++) { 434 nsCOMPtr<nsINavHistoryResultNode> child; 435 rv = mHistResultContainer->GetChild(i, getter_AddRefs(child)); 436 if (NS_WARN_IF(NS_FAILED(rv))) { 437 continue; 438 } 439 if (!IsHistoryResultNodeURI(child)) { 440 continue; 441 } 442 443 nsAutoCString uri; 444 child->GetUri(uri); 445 446 RefPtr<nsGNOMEShellHistorySearchResult> self = this; 447 nsCOMPtr<nsIURI> iconIri; 448 ios->NewURI(uri, nullptr, nullptr, getter_AddRefs(iconIri)); 449 favIconSvc->AsyncGetFaviconForPage(iconIri)->Then( 450 GetMainThreadSerialEventTarget(), __func__, 451 [self, iconIndex = i, timeStamp = mTimeStamp]( 452 const places::FaviconPromise::ResolveOrRejectValue& aResult) { 453 UpdateHistoryIcon(aResult, self, iconIndex, timeStamp); 454 }); 455 456 bool isOpen = false; 457 for (const auto& openuri : mOpenTabs) { 458 if (openuri.Equals(uri)) { 459 isOpen = true; 460 break; 461 } 462 } 463 nsAutoCString idKey; 464 DBusGetIDKeyForURI(i, isOpen, uri, idKey); 465 466 g_variant_builder_add(&b, "s", idKey.get()); 467 } 468 } 469 470 nsPrintfCString searchString("%s:%s", KEYWORD_SEARCH_STRING, 471 mSearchTerm.get()); 472 g_variant_builder_add(&b, "s", searchString.get()); 473 474 GVariant* v = g_variant_builder_end(&b); 475 g_dbus_method_invocation_return_value(mReply, g_variant_new_tuple(&v, 1)); 476 mReply = nullptr; 477 } 478 479 void nsGNOMEShellHistorySearchResult::ReceiveSearchResultContainer( 480 nsCOMPtr<nsINavHistoryContainerResultNode> aHistResultContainer) { 481 // Propagate search results to nsGNOMEShellSearchProvider. 482 // SetSearchResult() checks this is up-to-date search (our time stamp matches 483 // latest requested search timestamp). 484 if (!mSearchProvider->SetSearchResult(this)) { 485 return; 486 } 487 488 mHistResultContainer = aHistResultContainer; 489 490 // Getting the currently open tabs to mark them accordingly 491 nsresult rv; 492 nsCOMPtr<nsIOpenTabsProvider> provider = do_ImportESModule( 493 "moz-src:///browser/components/shell/OpenTabsProvider.sys.mjs", &rv); 494 if (NS_FAILED(rv)) { 495 // Don't fail, just log an error message 496 NS_WARNING("Failed to determine currently open tabs. Using history only."); 497 } 498 499 nsTArray<nsCString> openTabs; 500 if (provider) { 501 rv = provider->GetOpenTabs(openTabs); 502 if (NS_FAILED(rv)) { 503 // Don't fail, just log an error message 504 NS_WARNING( 505 "Failed to determine currently open tabs. Using history only."); 506 } 507 } 508 // In case of error, we just clear out mOpenTabs with an empty new array 509 mOpenTabs = std::move(openTabs); 510 511 HandleSearchResultReply(); 512 } 513 514 void nsGNOMEShellHistorySearchResult::SetHistoryIcon(int aTimeStamp, 515 UniquePtr<uint8_t[]> aData, 516 int aWidth, int aHeight, 517 int aIconIndex) { 518 MOZ_ASSERT(mTimeStamp == aTimeStamp); 519 MOZ_RELEASE_ASSERT(aIconIndex < MAX_SEARCH_RESULTS_NUM); 520 mHistoryIcons[aIconIndex].Set(mTimeStamp, std::move(aData), aWidth, aHeight); 521 } 522 523 GnomeHistoryIcon* nsGNOMEShellHistorySearchResult::GetHistoryIcon( 524 int aIconIndex) { 525 MOZ_RELEASE_ASSERT(aIconIndex < MAX_SEARCH_RESULTS_NUM); 526 if (mHistoryIcons[aIconIndex].GetTimeStamp() == mTimeStamp && 527 mHistoryIcons[aIconIndex].IsLoaded()) { 528 return mHistoryIcons + aIconIndex; 529 } 530 return nullptr; 531 } 532 533 nsGNOMEShellHistoryService* GetGNOMEShellHistoryService() { 534 static nsGNOMEShellHistoryService gGNOMEShellHistoryService; 535 return &gGNOMEShellHistoryService; 536 }