Source code

Revision control

Copy as Markdown

Other Tools

/* -*- 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"
#include "nsImportModule.h"
#include "nsIOpenTabsProvider.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<nsIStringBundle> bundle;
if (!bundle) {
nsCOMPtr<nsIStringBundleService> 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<nsString, 1> 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:S:URL where NN is index to our current history
// result container.
char tmp[] = {aIDKey[0], aIDKey[1], '\0'};
return atoi(tmp);
}
char DBusGetStateFromIDKey(const char* aIDKey) {
// ID is NN:S:URL where NN is index to our current history
// result container, and S is the state, which can be 'o'pen or 'h'istory
if (std::strlen(aIDKey) > 3) {
return aIDKey[3];
}
// Should never happen, but just to avoid any possible segfault, we
// default to state 'history'.
return 'h';
}
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<nsGNOMEShellHistorySearchResult> 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<GVariant> 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<GVariant> DBusAppendResultID(
nsGNOMEShellHistorySearchResult* aSearchResult, const char* aID) {
nsCOMPtr<nsINavHistoryContainerResultNode> container =
aSearchResult->GetSearchResultContainer();
int index = DBusGetIndexFromIDKey(aID);
char state = DBusGetStateFromIDKey(aID);
nsCOMPtr<nsINavHistoryResultNode> 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;
}
}
// Check if the URI state is "open tab". If so, mark it with an asterisk to
// indicate this to the user.
if (state == 'o') {
title = "(*) "_ns + title;
}
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<GVariant> 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<nsGNOMEShellHistorySearchResult> aSearchResult,
GVariant* aParameters, GDBusMethodInvocation* aReply) {
RefPtr<GVariant> 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<GVariant> 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<nsGNOMEShellHistorySearchResult> aSearchResult,
const char* aResultID, uint32_t aTimeStamp) {
char* commandLine = nullptr;
int len;
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), urlList, nullptr, &len);
} else {
int keyIndex = atoi(aResultID);
char state = DBusGetStateFromIDKey(aResultID);
nsCOMPtr<nsINavHistoryResultNode> child;
aSearchResult->GetSearchResultContainer()->GetChild(keyIndex,
getter_AddRefs(child));
if (!child) {
return;
}
nsAutoCString uri;
nsresult rv = child->GetUri(uri);
if (NS_FAILED(rv)) {
return;
}
// If the state of the URI is 'o'pen, we send it along to JS and let
// it switch the tab accordingly
if (state == 'o') {
// If we can't successfully switch to an open tab, use the existing
// 'open in a new tab'-mechanism as a fallback.
nsresult rv;
nsCOMPtr<nsIOpenTabsProvider> provider = do_ImportESModule(
"resource:///modules/OpenTabsProvider.sys.mjs", &rv);
if (NS_SUCCEEDED(rv)) {
rv = provider->SwitchToOpenTab(uri);
if (NS_SUCCEEDED(rv)) {
return;
}
}
}
const char* urlList[2] = {"unused", uri.get()};
commandLine =
ConstructCommandLine(std::size(urlList), urlList, nullptr, &len);
}
if (commandLine) {
aSearchResult->HandleCommandLine(mozilla::Span(commandLine, len),
aTimeStamp);
free(commandLine);
}
}
static void DBusLaunchWithAllResults(
RefPtr<nsGNOMEShellHistorySearchResult> 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.
const char** urlList =
(const char**)moz_xmalloc(sizeof(char*) * (childCount + 3));
int urlListElements = 0;
urlList[urlListElements++] = strdup("unused");
for (uint32_t i = 0; i < childCount; i++) {
nsCOMPtr<nsINavHistoryResultNode> 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 len;
char* commandLine =
ConstructCommandLine(urlListElements, urlList, nullptr, &len);
if (commandLine) {
aSearchResult->HandleCommandLine(mozilla::Span(commandLine, len),
aTimeStamp);
free(commandLine);
}
for (int i = 0; i < urlListElements; i++) {
free((void*)urlList[i]);
}
free(urlList);
}
// ActivateResult :: (s,as,u) → ()
void DBusActivateResult(RefPtr<nsGNOMEShellHistorySearchResult> aSearchResult,
GVariant* aParameters, GDBusMethodInvocation* aReply) {
const char* resultID;
// aParameters is "(s,as,u)" type
RefPtr<GVariant> 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<GVariant> 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<nsGNOMEShellHistorySearchResult> aSearchResult,
GVariant* aParameters, GDBusMethodInvocation* aReply) {
RefPtr<GVariant> 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();
}