1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim:expandtab:shiftwidth=2:tabstop=2:
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/. */
8 #include "nsGNOMEShellSearchProvider.h"
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"
19 #define DBUS_BUS_NAME_TEMPLATE "org.mozilla.%s.SearchProvider"
20 #define DBUS_OBJECT_PATH_TEMPLATE "/org/mozilla/%s/SearchProvider"
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
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
42 static bool GetGnomeSearchTitle(const char* aSearchedTerm
,
43 nsAutoCString
& aGnomeSearchTitle
) {
44 static nsCOMPtr
<nsIStringBundle
> bundle
;
46 nsCOMPtr
<nsIStringBundleService
> sbs
=
47 do_GetService(NS_STRINGBUNDLE_CONTRACTID
);
48 if (NS_WARN_IF(!sbs
)) {
52 sbs
->CreateBundle("chrome://browser/locale/browser.properties",
53 getter_AddRefs(bundle
));
54 if (NS_WARN_IF(!bundle
)) {
59 AutoTArray
<nsString
, 1> formatStrings
;
60 CopyUTF8toUTF16(nsCString(aSearchedTerm
), *formatStrings
.AppendElement());
62 nsAutoString gnomeSearchTitle
;
63 bundle
->FormatStringFromName("gnomeSearchProviderSearchWeb", formatStrings
,
65 AppendUTF16toUTF8(gnomeSearchTitle
, aGnomeSearchTitle
);
69 int DBusGetIndexFromIDKey(const char* aIDKey
) {
70 // ID is NN:S:URL where NN is index to our current history
72 char tmp
[] = {aIDKey
[0], aIDKey
[1], '\0'};
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) {
82 // Should never happen, but just to avoid any possible segfault, we
83 // default to state 'history'.
87 static void ConcatArray(nsACString
& aOutputStr
, const char** aStringArray
) {
88 for (const char** term
= aStringArray
; *term
; term
++) {
89 aOutputStr
.Append(*term
);
91 aOutputStr
.Append(" ");
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);
108 g_dbus_method_invocation_return_error(
109 aReply
, G_DBUS_ERROR
, G_DBUS_ERROR_INVALID_ARGS
, "Wrong params!");
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.
126 "icon-data": a tuple of type (iiibiiay) describing a pixbuf with width,
127 height, rowstride, has-alpha,
128 bits-per-sample, channels,
131 static void DBusAppendIcon(GVariantBuilder
* aBuilder
, GnomeHistoryIcon
* aIcon
) {
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,
144 g_variant_builder_add(aBuilder
, "{sv}", "icon-data",
145 g_variant_builder_end(&b
));
148 /* Appends history search results to the DBUS reply.
150 We can return those fields at GetResultMetas:
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()),
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)
161 static already_AddRefed
<GVariant
> DBusAppendResultID(
162 nsGNOMEShellHistorySearchResult
* aSearchResult
, const char* aID
) {
163 nsCOMPtr
<nsINavHistoryContainerResultNode
> container
=
164 aSearchResult
->GetSearchResultContainer();
166 int index
= DBusGetIndexFromIDKey(aID
);
167 char state
= DBusGetStateFromIDKey(aID
);
168 nsCOMPtr
<nsINavHistoryResultNode
> child
;
169 container
->GetChild(index
, getter_AddRefs(child
));
171 if (!child
|| NS_FAILED(child
->GetTitle(title
))) {
175 if (title
.IsEmpty()) {
176 if (NS_FAILED(child
->GetUri(title
)) || title
.IsEmpty()) {
181 // Check if the URI state is "open tab". If so, mark it with an asterisk to
182 // indicate this to the user.
184 title
= "(*) "_ns
+ title
;
188 g_variant_builder_init(&b
, G_VARIANT_TYPE("a{sv}"));
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
));
194 GnomeHistoryIcon
* icon
= aSearchResult
->GetHistoryIcon(index
);
196 DBusAppendIcon(&b
, icon
);
198 g_variant_builder_add(&b
, "{sv}", "gicon",
199 g_variant_new_string("text-html"));
201 return dont_AddRef(g_variant_ref_sink(g_variant_builder_end(&b
)));
204 // Search the web for: "searchTerm" to the DBUS reply.
205 static already_AddRefed
<GVariant
> DBusAppendSearchID(const char* aID
) {
208 KEYWORD_SEARCH_STRING:ssssss
210 KEYWORD_SEARCH_STRING is a 'special:search' keyword
211 ssssss is a searched term, must be at least one character long
214 // aID contains only 'KEYWORD_SEARCH_STRING:' so we're missing searched
216 if (strlen(aID
) <= KEYWORD_SEARCH_STRING_LEN
+ 1) {
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
));
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"));
236 return dont_AddRef(g_variant_ref_sink(g_variant_builder_end(&b
)));
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));
246 const char** stringArray
= g_variant_get_strv(variant
, &elements
);
248 g_dbus_method_invocation_return_error(
249 aReply
, G_DBUS_ERROR
, G_DBUS_ERROR_INVALID_ARGS
, "Wrong params!");
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
]);
261 value
= DBusAppendResultID(aSearchResult
, stringArray
[i
]);
264 g_variant_builder_add_value(&b
, value
);
268 GVariant
* v
= g_variant_builder_end(&b
);
269 g_dbus_method_invocation_return_value(aReply
, g_variant_new_tuple(&v
, 1));
272 } // namespace mozilla
274 static void ActivateResultID(
275 RefPtr
<nsGNOMEShellHistorySearchResult
> aSearchResult
,
276 const char* aResultID
, uint32_t aTimeStamp
) {
277 char* commandLine
= nullptr;
280 if (strncmp(aResultID
, KEYWORD_SEARCH_STRING
, KEYWORD_SEARCH_STRING_LEN
) ==
282 const char* urlList
[3] = {"unused", "--search",
283 aSearchResult
->GetSearchTerm().get()};
285 ConstructCommandLine(std::size(urlList
), urlList
, nullptr, &len
);
287 int keyIndex
= atoi(aResultID
);
288 char state
= DBusGetStateFromIDKey(aResultID
);
289 nsCOMPtr
<nsINavHistoryResultNode
> child
;
290 aSearchResult
->GetSearchResultContainer()->GetChild(keyIndex
,
291 getter_AddRefs(child
));
297 nsresult rv
= child
->GetUri(uri
);
302 // If the state of the URI is 'o'pen, we send it along to JS and let
303 // it switch the tab accordingly
305 // If we can't successfully switch to an open tab, use the existing
306 // 'open in a new tab'-mechanism as a fallback.
308 nsCOMPtr
<nsIOpenTabsProvider
> provider
= do_ImportESModule(
309 "resource:///modules/OpenTabsProvider.sys.mjs", &rv
);
310 if (NS_SUCCEEDED(rv
)) {
311 rv
= provider
->SwitchToOpenTab(uri
);
312 if (NS_SUCCEEDED(rv
)) {
318 const char* urlList
[2] = {"unused", uri
.get()};
320 ConstructCommandLine(std::size(urlList
), urlList
, nullptr, &len
);
324 aSearchResult
->HandleCommandLine(mozilla::Span(commandLine
, len
),
330 static void DBusLaunchWithAllResults(
331 RefPtr
<nsGNOMEShellHistorySearchResult
> aSearchResult
,
332 uint32_t aTimeStamp
) {
333 uint32_t childCount
= 0;
335 aSearchResult
->GetSearchResultContainer()->GetChildCount(&childCount
);
336 if (NS_FAILED(rv
) || childCount
== 0) {
340 if (childCount
> MAX_SEARCH_RESULTS_NUM
) {
341 childCount
= MAX_SEARCH_RESULTS_NUM
;
344 // Allocate space for all found results, "unused", "--search" and
345 // potential search request.
346 const char** urlList
=
347 (const char**)moz_xmalloc(sizeof(char*) * (childCount
+ 3));
348 int urlListElements
= 0;
350 urlList
[urlListElements
++] = strdup("unused");
352 for (uint32_t i
= 0; i
< childCount
; i
++) {
353 nsCOMPtr
<nsINavHistoryResultNode
> child
;
354 aSearchResult
->GetSearchResultContainer()->GetChild(i
,
355 getter_AddRefs(child
));
357 if (!IsHistoryResultNodeURI(child
)) {
362 nsresult rv
= child
->GetUri(uri
);
366 urlList
[urlListElements
++] = strdup(uri
.get());
369 // When there isn't any uri to open pass search at least.
371 urlList
[urlListElements
++] = strdup("--search");
372 urlList
[urlListElements
++] = strdup(aSearchResult
->GetSearchTerm().get());
377 ConstructCommandLine(urlListElements
, urlList
, nullptr, &len
);
379 aSearchResult
->HandleCommandLine(mozilla::Span(commandLine
, len
),
384 for (int i
= 0; i
< urlListElements
; i
++) {
385 free((void*)urlList
[i
]);
390 // ActivateResult :: (s,as,u) → ()
391 void DBusActivateResult(RefPtr
<nsGNOMEShellHistorySearchResult
> aSearchResult
,
392 GVariant
* aParameters
, GDBusMethodInvocation
* aReply
) {
393 const char* resultID
;
395 // aParameters is "(s,as,u)" type
396 RefPtr
<GVariant
> r
= dont_AddRef(g_variant_get_child_value(aParameters
, 0));
397 if (!(resultID
= g_variant_get_string(r
, nullptr))) {
398 g_dbus_method_invocation_return_error(
399 aReply
, G_DBUS_ERROR
, G_DBUS_ERROR_INVALID_ARGS
, "Wrong params!");
402 RefPtr
<GVariant
> t
= dont_AddRef(g_variant_get_child_value(aParameters
, 2));
403 uint32_t timestamp
= g_variant_get_uint32(t
);
405 ActivateResultID(aSearchResult
, resultID
, timestamp
);
406 g_dbus_method_invocation_return_value(aReply
, nullptr);
409 // LaunchSearch :: (as,u) → ()
410 void DBusLaunchSearch(RefPtr
<nsGNOMEShellHistorySearchResult
> aSearchResult
,
411 GVariant
* aParameters
, GDBusMethodInvocation
* aReply
) {
412 RefPtr
<GVariant
> variant
=
413 dont_AddRef(g_variant_get_child_value(aParameters
, 1));
415 g_dbus_method_invocation_return_error(
416 aReply
, G_DBUS_ERROR
, G_DBUS_ERROR_INVALID_ARGS
, "Wrong params!");
419 DBusLaunchWithAllResults(aSearchResult
, g_variant_get_uint32(variant
));
420 g_dbus_method_invocation_return_value(aReply
, nullptr);
423 bool IsHistoryResultNodeURI(nsINavHistoryResultNode
* aHistoryNode
) {
425 nsresult rv
= aHistoryNode
->GetType(&type
);
426 if (NS_FAILED(rv
) || type
!= nsINavHistoryResultNode::RESULT_TYPE_URI
)
430 rv
= aHistoryNode
->GetTitle(title
);
431 if (NS_SUCCEEDED(rv
) && !title
.IsEmpty()) {
435 rv
= aHistoryNode
->GetUri(title
);
436 return NS_SUCCEEDED(rv
) && !title
.IsEmpty();