1/* 2 * Copyright (C) 2012 Igalia S.L. 3 * 4 * This library is free software; you can redistribute it and/or 5 * modify it under the terms of the GNU Lesser General Public 6 * License as published by the Free Software Foundation; either 7 * version 2,1 of the License, or (at your option) any later version. 8 * 9 * This library is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 * Library General Public License for more details. 13 * 14 * You should have received a copy of the GNU Library General Public License 15 * along with this library; see the file COPYING.LIB. If not, write to 16 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 17 * Boston, MA 02110-1301, USA. 18 */ 19 20#include "config.h" 21#include "WebKitFindController.h" 22 23#include "WebKitEnumTypes.h" 24#include "WebKitPrivate.h" 25#include "WebKitWebView.h" 26#include "WebKitWebViewBasePrivate.h" 27#include <glib/gi18n-lib.h> 28#include <wtf/gobject/GRefPtr.h> 29#include <wtf/text/CString.h> 30 31using namespace WebKit; 32using namespace WebCore; 33 34/** 35 * SECTION: WebKitFindController 36 * @Short_description: Controls text search in a #WebKitWebView 37 * @Title: WebKitFindController 38 * 39 * A #WebKitFindController is used to search text in a #WebKitWebView. You 40 * can get a #WebKitWebView<!-- -->'s #WebKitFindController with 41 * webkit_web_view_get_find_controller(), and later use it to search 42 * for text using webkit_find_controller_search(), or get the 43 * number of matches using webkit_find_controller_count_matches(). The 44 * operations are asynchronous and trigger signals when ready, such as 45 * #WebKitFindController::found-text, 46 * #WebKitFindController::failed-to-find-text or 47 * #WebKitFindController::counted-matches<!-- -->. 48 * 49 */ 50 51enum { 52 FOUND_TEXT, 53 FAILED_TO_FIND_TEXT, 54 COUNTED_MATCHES, 55 56 LAST_SIGNAL 57}; 58 59enum { 60 PROP_0, 61 62 PROP_TEXT, 63 PROP_OPTIONS, 64 PROP_MAX_MATCH_COUNT, 65 PROP_WEB_VIEW 66}; 67 68typedef enum { 69 FindOperation, 70 FindNextPrevOperation, 71 CountOperation 72} WebKitFindControllerOperation; 73 74struct _WebKitFindControllerPrivate { 75 CString searchText; 76 // Interpreted as WebKit::FindOptions. 77 uint32_t findOptions; 78 unsigned maxMatchCount; 79 WebKitWebView* webView; 80}; 81 82static guint signals[LAST_SIGNAL] = { 0, }; 83 84WEBKIT_DEFINE_TYPE(WebKitFindController, webkit_find_controller, G_TYPE_OBJECT) 85 86static inline WebKit::FindOptions toWebFindOptions(uint32_t findOptions) 87{ 88 return static_cast<WebKit::FindOptions>((findOptions & WEBKIT_FIND_OPTIONS_CASE_INSENSITIVE ? FindOptionsCaseInsensitive : 0) 89 | (findOptions & WEBKIT_FIND_OPTIONS_AT_WORD_STARTS ? FindOptionsAtWordStarts : 0) 90 | (findOptions & WEBKIT_FIND_OPTIONS_TREAT_MEDIAL_CAPITAL_AS_WORD_START ? FindOptionsTreatMedialCapitalAsWordStart : 0) 91 | (findOptions & WEBKIT_FIND_OPTIONS_BACKWARDS ? FindOptionsBackwards : 0) 92 | (findOptions & WEBKIT_FIND_OPTIONS_WRAP_AROUND ? FindOptionsWrapAround : 0)); 93} 94 95static inline WebKitFindOptions toWebKitFindOptions(uint32_t findOptions) 96{ 97 return static_cast<WebKitFindOptions>((findOptions & FindOptionsCaseInsensitive ? WEBKIT_FIND_OPTIONS_CASE_INSENSITIVE : 0) 98 | (findOptions & FindOptionsAtWordStarts ? WEBKIT_FIND_OPTIONS_AT_WORD_STARTS : 0) 99 | (findOptions & FindOptionsTreatMedialCapitalAsWordStart ? WEBKIT_FIND_OPTIONS_TREAT_MEDIAL_CAPITAL_AS_WORD_START : 0) 100 | (findOptions & FindOptionsBackwards ? WEBKIT_FIND_OPTIONS_BACKWARDS : 0) 101 | (findOptions & FindOptionsWrapAround ? WEBKIT_FIND_OPTIONS_WRAP_AROUND : 0)); 102} 103 104static void didFindString(WKPageRef, WKStringRef, unsigned matchCount, const void* clientInfo) 105{ 106 g_signal_emit(WEBKIT_FIND_CONTROLLER(clientInfo), signals[FOUND_TEXT], 0, matchCount); 107} 108 109static void didFailToFindString(WKPageRef, WKStringRef, const void* clientInfo) 110{ 111 g_signal_emit(WEBKIT_FIND_CONTROLLER(clientInfo), signals[FAILED_TO_FIND_TEXT], 0); 112} 113 114static void didCountStringMatches(WKPageRef, WKStringRef, unsigned matchCount, const void* clientInfo) 115{ 116 g_signal_emit(WEBKIT_FIND_CONTROLLER(clientInfo), signals[COUNTED_MATCHES], 0, matchCount); 117} 118 119static inline WebPageProxy* getPage(WebKitFindController* findController) 120{ 121 return webkitWebViewBaseGetPage(reinterpret_cast<WebKitWebViewBase*>(findController->priv->webView)); 122} 123 124static void webkitFindControllerConstructed(GObject* object) 125{ 126 WebKitFindController* findController = WEBKIT_FIND_CONTROLLER(object); 127 WKPageFindClientV0 wkFindClient = { 128 { 129 0, // version 130 findController, // clientInfo 131 }, 132 didFindString, 133 didFailToFindString, 134 didCountStringMatches 135 }; 136 137 WKPageSetPageFindClient(toAPI(getPage(findController)), &wkFindClient.base); 138} 139 140static void webkitFindControllerGetProperty(GObject* object, guint propId, GValue* value, GParamSpec* paramSpec) 141{ 142 WebKitFindController* findController = WEBKIT_FIND_CONTROLLER(object); 143 144 switch (propId) { 145 case PROP_TEXT: 146 g_value_set_string(value, webkit_find_controller_get_search_text(findController)); 147 break; 148 case PROP_OPTIONS: 149 g_value_set_uint(value, webkit_find_controller_get_options(findController)); 150 break; 151 case PROP_MAX_MATCH_COUNT: 152 g_value_set_uint(value, webkit_find_controller_get_max_match_count(findController)); 153 break; 154 case PROP_WEB_VIEW: 155 g_value_set_object(value, webkit_find_controller_get_web_view(findController)); 156 break; 157 default: 158 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, propId, paramSpec); 159 } 160} 161 162static void webkitFindControllerSetProperty(GObject* object, guint propId, const GValue* value, GParamSpec* paramSpec) 163{ 164 WebKitFindController* findController = WEBKIT_FIND_CONTROLLER(object); 165 166 switch (propId) { 167 case PROP_WEB_VIEW: 168 findController->priv->webView = WEBKIT_WEB_VIEW(g_value_get_object(value)); 169 break; 170 default: 171 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, propId, paramSpec); 172 } 173} 174 175static void webkit_find_controller_class_init(WebKitFindControllerClass* findClass) 176{ 177 GObjectClass* gObjectClass = G_OBJECT_CLASS(findClass); 178 gObjectClass->constructed = webkitFindControllerConstructed; 179 gObjectClass->get_property = webkitFindControllerGetProperty; 180 gObjectClass->set_property = webkitFindControllerSetProperty; 181 182 /** 183 * WebKitFindController:text: 184 * 185 * The current search text for this #WebKitFindController. 186 */ 187 g_object_class_install_property(gObjectClass, 188 PROP_TEXT, 189 g_param_spec_string("text", 190 _("Search text"), 191 _("Text to search for in the view"), 192 0, 193 WEBKIT_PARAM_READABLE)); 194 195 /** 196 * WebKitFindController:options: 197 * 198 * The options to be used in the search operation. 199 */ 200 g_object_class_install_property(gObjectClass, 201 PROP_OPTIONS, 202 g_param_spec_flags("options", 203 _("Search Options"), 204 _("Search options to be used in the search operation"), 205 WEBKIT_TYPE_FIND_OPTIONS, 206 WEBKIT_FIND_OPTIONS_NONE, 207 WEBKIT_PARAM_READABLE)); 208 209 /** 210 * WebKitFindController:max-match-count: 211 * 212 * The maximum number of matches to report for a given search. 213 */ 214 g_object_class_install_property(gObjectClass, 215 PROP_MAX_MATCH_COUNT, 216 g_param_spec_uint("max-match-count", 217 _("Maximum matches count"), 218 _("The maximum number of matches in a given text to report"), 219 0, G_MAXUINT, 0, 220 WEBKIT_PARAM_READABLE)); 221 222 /** 223 * WebKitFindController:web-view: 224 * 225 * The #WebKitWebView this controller is associated to. 226 */ 227 g_object_class_install_property(gObjectClass, 228 PROP_WEB_VIEW, 229 g_param_spec_object("web-view", 230 _("WebView"), 231 _("The WebView associated with this find controller"), 232 WEBKIT_TYPE_WEB_VIEW, 233 static_cast<GParamFlags>(WEBKIT_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY))); 234 235 /** 236 * WebKitFindController::found-text: 237 * @find_controller: the #WebKitFindController 238 * @match_count: the number of matches found of the search text 239 * 240 * This signal is emitted when a given text is found in the web 241 * page text. It will be issued if the text is found 242 * asynchronously after a call to webkit_find_controller_search(), 243 * webkit_find_controller_search_next() or 244 * webkit_find_controller_search_previous(). 245 */ 246 signals[FOUND_TEXT] = 247 g_signal_new("found-text", 248 G_TYPE_FROM_CLASS(gObjectClass), 249 G_SIGNAL_RUN_LAST, 250 0, 0, 0, 251 g_cclosure_marshal_VOID__UINT, 252 G_TYPE_NONE, 1, G_TYPE_UINT); 253 254 /** 255 * WebKitFindController::failed-to-find-text: 256 * @find_controller: the #WebKitFindController 257 * 258 * This signal is emitted when a search operation does not find 259 * any result for the given text. It will be issued if the text 260 * is not found asynchronously after a call to 261 * webkit_find_controller_search(), webkit_find_controller_search_next() 262 * or webkit_find_controller_search_previous(). 263 */ 264 signals[FAILED_TO_FIND_TEXT] = 265 g_signal_new("failed-to-find-text", 266 G_TYPE_FROM_CLASS(gObjectClass), 267 G_SIGNAL_RUN_LAST, 268 0, 0, 0, 269 g_cclosure_marshal_VOID__VOID, 270 G_TYPE_NONE, 0); 271 272 /** 273 * WebKitFindController::counted-matches: 274 * @find_controller: the #WebKitFindController 275 * @match_count: the number of matches of the search text 276 * 277 * This signal is emitted when the #WebKitFindController has 278 * counted the number of matches for a given text after a call 279 * to webkit_find_controller_count_matches(). 280 */ 281 signals[COUNTED_MATCHES] = 282 g_signal_new("counted-matches", 283 G_TYPE_FROM_CLASS(gObjectClass), 284 G_SIGNAL_RUN_LAST, 285 0, 0, 0, 286 g_cclosure_marshal_VOID__UINT, 287 G_TYPE_NONE, 1, G_TYPE_UINT); 288} 289 290/** 291 * webkit_find_controller_get_search_text: 292 * @find_controller: the #WebKitFindController 293 * 294 * Gets the text that @find_controller is currently searching 295 * for. This text is passed to either 296 * webkit_find_controller_search() or 297 * webkit_find_controller_count_matches(). 298 * 299 * Returns: the text to look for in the #WebKitWebView. 300 */ 301const char* webkit_find_controller_get_search_text(WebKitFindController* findController) 302{ 303 g_return_val_if_fail(WEBKIT_IS_FIND_CONTROLLER(findController), 0); 304 305 return findController->priv->searchText.data(); 306} 307 308/** 309 * webkit_find_controller_get_options: 310 * @find_controller: the #WebKitFindController 311 * 312 * Gets a bitmask containing the #WebKitFindOptions associated with 313 * the current search. 314 * 315 * Returns: a bitmask containing the #WebKitFindOptions associated 316 * with the current search. 317 */ 318guint32 webkit_find_controller_get_options(WebKitFindController* findController) 319{ 320 g_return_val_if_fail(WEBKIT_IS_FIND_CONTROLLER(findController), WEBKIT_FIND_OPTIONS_NONE); 321 322 return toWebKitFindOptions(findController->priv->findOptions); 323} 324 325/** 326 * webkit_find_controller_get_max_match_count: 327 * @find_controller: the #WebKitFindController 328 * 329 * Gets the maximum number of matches to report during a text 330 * lookup. This number is passed as the last argument of 331 * webkit_find_controller_search() or 332 * webkit_find_controller_count_matches(). 333 * 334 * Returns: the maximum number of matches to report. 335 */ 336guint webkit_find_controller_get_max_match_count(WebKitFindController* findController) 337{ 338 g_return_val_if_fail(WEBKIT_IS_FIND_CONTROLLER(findController), 0); 339 340 return findController->priv->maxMatchCount; 341} 342 343/** 344 * webkit_find_controller_get_web_view: 345 * @find_controller: the #WebKitFindController 346 * 347 * Gets the #WebKitWebView this find controller is associated to. Do 348 * not unref the returned instance as it belongs to the 349 * #WebKitFindController. 350 * 351 * Returns: (transfer none): the #WebKitWebView. 352 */ 353WebKitWebView* webkit_find_controller_get_web_view(WebKitFindController* findController) 354{ 355 g_return_val_if_fail(WEBKIT_IS_FIND_CONTROLLER(findController), 0); 356 357 return findController->priv->webView; 358} 359 360static void webKitFindControllerPerform(WebKitFindController* findController, WebKitFindControllerOperation operation) 361{ 362 WebKitFindControllerPrivate* priv = findController->priv; 363 if (operation == CountOperation) { 364 getPage(findController)->countStringMatches(String::fromUTF8(priv->searchText.data()), 365 static_cast<WebKit::FindOptions>(priv->findOptions), priv->maxMatchCount); 366 return; 367 } 368 369 uint32_t findOptions = priv->findOptions; 370 if (operation == FindOperation) 371 // Unconditionally highlight text matches when the search 372 // starts. WK1 API was forcing clients to enable/disable 373 // highlighting. Since most of them (all?) where using that 374 // feature we decided to simplify the WK2 API and 375 // unconditionally show highlights. Both search_next() and 376 // search_prev() should not enable highlighting to avoid an 377 // extra unmarkAllTextMatches() + markAllTextMatches() 378 findOptions |= FindOptionsShowHighlight; 379 380 getPage(findController)->findString(String::fromUTF8(priv->searchText.data()), static_cast<WebKit::FindOptions>(findOptions), 381 priv->maxMatchCount); 382} 383 384static inline void webKitFindControllerSetSearchData(WebKitFindController* findController, const gchar* searchText, guint32 findOptions, guint maxMatchCount) 385{ 386 findController->priv->searchText = searchText; 387 findController->priv->findOptions = findOptions; 388 findController->priv->maxMatchCount = maxMatchCount; 389} 390 391/** 392 * webkit_find_controller_search: 393 * @find_controller: the #WebKitFindController 394 * @search_text: the text to look for 395 * @find_options: a bitmask with the #WebKitFindOptions used in the search 396 * @max_match_count: the maximum number of matches allowed in the search 397 * 398 * Looks for @search_text in the #WebKitWebView associated with 399 * @find_controller since the beginning of the document highlighting 400 * up to @max_match_count matches. The outcome of the search will be 401 * asynchronously provided by the #WebKitFindController::found-text 402 * and #WebKitFindController::failed-to-find-text signals. 403 * 404 * To look for the next or previous occurrences of the same text 405 * with the same find options use webkit_find_controller_search_next() 406 * and/or webkit_find_controller_search_previous(). The 407 * #WebKitFindController will use the same text and options for the 408 * following searches unless they are modified by another call to this 409 * method. 410 * 411 * Note that if the number of matches is higher than @max_match_count 412 * then #WebKitFindController::found-text will report %G_MAXUINT matches 413 * instead of the actual number. 414 * 415 * Callers should call webkit_find_controller_search_finish() to 416 * finish the current search operation. 417 */ 418void webkit_find_controller_search(WebKitFindController* findController, const gchar* searchText, guint findOptions, guint maxMatchCount) 419{ 420 g_return_if_fail(WEBKIT_IS_FIND_CONTROLLER(findController)); 421 g_return_if_fail(searchText); 422 webKitFindControllerSetSearchData(findController, searchText, toWebFindOptions(findOptions), maxMatchCount); 423 webKitFindControllerPerform(findController, FindOperation); 424} 425 426/** 427 * webkit_find_controller_search_next: 428 * @find_controller: the #WebKitFindController 429 * 430 * Looks for the next occurrence of the search text. 431 * 432 * Calling this method before webkit_find_controller_search() or 433 * webkit_find_controller_count_matches() is a programming error. 434 */ 435void webkit_find_controller_search_next(WebKitFindController* findController) 436{ 437 g_return_if_fail(WEBKIT_IS_FIND_CONTROLLER(findController)); 438 439 findController->priv->findOptions &= ~FindOptionsBackwards; 440 findController->priv->findOptions &= ~FindOptionsShowHighlight; 441 webKitFindControllerPerform(findController, FindNextPrevOperation); 442} 443 444/** 445 * webkit_find_controller_search_previous: 446 * @find_controller: the #WebKitFindController 447 * 448 * Looks for the previous occurrence of the search text. 449 * 450 * Calling this method before webkit_find_controller_search() or 451 * webkit_find_controller_count_matches() is a programming error. 452 */ 453void webkit_find_controller_search_previous(WebKitFindController* findController) 454{ 455 g_return_if_fail(WEBKIT_IS_FIND_CONTROLLER(findController)); 456 457 findController->priv->findOptions |= FindOptionsBackwards; 458 findController->priv->findOptions &= ~FindOptionsShowHighlight; 459 webKitFindControllerPerform(findController, FindNextPrevOperation); 460} 461 462/** 463 * webkit_find_controller_count_matches: 464 * @find_controller: the #WebKitFindController 465 * @search_text: the text to look for 466 * @find_options: a bitmask with the #WebKitFindOptions used in the search 467 * @max_match_count: the maximum number of matches allowed in the search 468 * 469 * Counts the number of matches for @search_text found in the 470 * #WebKitWebView with the provided @find_options. The number of 471 * matches will be provided by the 472 * #WebKitFindController::counted-matches signal. 473 */ 474void webkit_find_controller_count_matches(WebKitFindController* findController, const gchar* searchText, guint32 findOptions, guint maxMatchCount) 475{ 476 g_return_if_fail(WEBKIT_IS_FIND_CONTROLLER(findController)); 477 g_return_if_fail(searchText); 478 479 webKitFindControllerSetSearchData(findController, searchText, toWebFindOptions(findOptions), maxMatchCount); 480 webKitFindControllerPerform(findController, CountOperation); 481} 482 483/** 484 * webkit_find_controller_search_finish: 485 * @find_controller: a #WebKitFindController 486 * 487 * Finishes a find operation started by 488 * webkit_find_controller_search(). It will basically unhighlight 489 * every text match found. 490 * 491 * This method will be typically called when the search UI is 492 * closed/hidden by the client application. 493 */ 494void webkit_find_controller_search_finish(WebKitFindController* findController) 495{ 496 g_return_if_fail(WEBKIT_IS_FIND_CONTROLLER(findController)); 497 498 getPage(findController)->hideFindUI(); 499} 500