blob: 7675c9cb3a5edaf8864d17a179217cb9423365b8 [file] [log] [blame]
/*
* Copyright (C) 2012 Igalia S.L.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2,1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "config.h"
#include "WebKitFindController.h"
#include "APIFindClient.h"
#include "WebKitEnumTypes.h"
#include "WebKitWebViewPrivate.h"
#include <glib/gi18n-lib.h>
#include <wtf/glib/GRefPtr.h>
#include <wtf/glib/WTFGType.h>
#include <wtf/text/CString.h>
using namespace WebKit;
using namespace WebCore;
/**
* SECTION: WebKitFindController
* @Short_description: Controls text search in a #WebKitWebView
* @Title: WebKitFindController
*
* A #WebKitFindController is used to search text in a #WebKitWebView. You
* can get a #WebKitWebView<!-- -->'s #WebKitFindController with
* webkit_web_view_get_find_controller(), and later use it to search
* for text using webkit_find_controller_search(), or get the
* number of matches using webkit_find_controller_count_matches(). The
* operations are asynchronous and trigger signals when ready, such as
* #WebKitFindController::found-text,
* #WebKitFindController::failed-to-find-text or
* #WebKitFindController::counted-matches<!-- -->.
*
*/
enum {
FOUND_TEXT,
FAILED_TO_FIND_TEXT,
COUNTED_MATCHES,
LAST_SIGNAL
};
enum {
PROP_0,
PROP_TEXT,
PROP_OPTIONS,
PROP_MAX_MATCH_COUNT,
PROP_WEB_VIEW
};
typedef enum {
FindOperation,
FindNextPrevOperation,
CountOperation
} WebKitFindControllerOperation;
struct _WebKitFindControllerPrivate {
CString searchText;
// Interpreted as WebKit::FindOptions.
uint32_t findOptions;
unsigned maxMatchCount;
WebKitWebView* webView;
};
static guint signals[LAST_SIGNAL] = { 0, };
WEBKIT_DEFINE_TYPE(WebKitFindController, webkit_find_controller, G_TYPE_OBJECT)
static inline WebKit::FindOptions toWebFindOptions(uint32_t findOptions)
{
return static_cast<WebKit::FindOptions>((findOptions & WEBKIT_FIND_OPTIONS_CASE_INSENSITIVE ? FindOptionsCaseInsensitive : 0)
| (findOptions & WEBKIT_FIND_OPTIONS_AT_WORD_STARTS ? FindOptionsAtWordStarts : 0)
| (findOptions & WEBKIT_FIND_OPTIONS_TREAT_MEDIAL_CAPITAL_AS_WORD_START ? FindOptionsTreatMedialCapitalAsWordStart : 0)
| (findOptions & WEBKIT_FIND_OPTIONS_BACKWARDS ? FindOptionsBackwards : 0)
| (findOptions & WEBKIT_FIND_OPTIONS_WRAP_AROUND ? FindOptionsWrapAround : 0));
}
static inline WebKitFindOptions toWebKitFindOptions(uint32_t findOptions)
{
return static_cast<WebKitFindOptions>((findOptions & FindOptionsCaseInsensitive ? WEBKIT_FIND_OPTIONS_CASE_INSENSITIVE : 0)
| (findOptions & FindOptionsAtWordStarts ? WEBKIT_FIND_OPTIONS_AT_WORD_STARTS : 0)
| (findOptions & FindOptionsTreatMedialCapitalAsWordStart ? WEBKIT_FIND_OPTIONS_TREAT_MEDIAL_CAPITAL_AS_WORD_START : 0)
| (findOptions & FindOptionsBackwards ? WEBKIT_FIND_OPTIONS_BACKWARDS : 0)
| (findOptions & FindOptionsWrapAround ? WEBKIT_FIND_OPTIONS_WRAP_AROUND : 0));
}
static inline WebPageProxy& getPage(WebKitFindController* findController)
{
return webkitWebViewGetPage(findController->priv->webView);
}
class FindClient final : public API::FindClient {
public:
explicit FindClient(WebKitFindController* findController)
: m_findController(findController)
{
}
private:
void didCountStringMatches(WebPageProxy*, const String&, uint32_t matchCount) override
{
g_signal_emit(m_findController, signals[COUNTED_MATCHES], 0, matchCount);
}
void didFindString(WebPageProxy*, const String&, const Vector<IntRect>&, uint32_t matchCount, int32_t, bool /*didWrapAround*/) override
{
g_signal_emit(m_findController, signals[FOUND_TEXT], 0, matchCount);
}
void didFailToFindString(WebPageProxy*, const String&) override
{
g_signal_emit(m_findController, signals[FAILED_TO_FIND_TEXT], 0);
}
WebKitFindController* m_findController;
};
static void webkitFindControllerConstructed(GObject* object)
{
G_OBJECT_CLASS(webkit_find_controller_parent_class)->constructed(object);
WebKitFindController* findController = WEBKIT_FIND_CONTROLLER(object);
getPage(findController).setFindClient(makeUnique<FindClient>(findController));
}
static void webkitFindControllerGetProperty(GObject* object, guint propId, GValue* value, GParamSpec* paramSpec)
{
WebKitFindController* findController = WEBKIT_FIND_CONTROLLER(object);
switch (propId) {
case PROP_TEXT:
g_value_set_string(value, webkit_find_controller_get_search_text(findController));
break;
case PROP_OPTIONS:
g_value_set_uint(value, webkit_find_controller_get_options(findController));
break;
case PROP_MAX_MATCH_COUNT:
g_value_set_uint(value, webkit_find_controller_get_max_match_count(findController));
break;
case PROP_WEB_VIEW:
g_value_set_object(value, webkit_find_controller_get_web_view(findController));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, propId, paramSpec);
}
}
static void webkitFindControllerSetProperty(GObject* object, guint propId, const GValue* value, GParamSpec* paramSpec)
{
WebKitFindController* findController = WEBKIT_FIND_CONTROLLER(object);
switch (propId) {
case PROP_WEB_VIEW:
findController->priv->webView = WEBKIT_WEB_VIEW(g_value_get_object(value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, propId, paramSpec);
}
}
static void webkit_find_controller_class_init(WebKitFindControllerClass* findClass)
{
GObjectClass* gObjectClass = G_OBJECT_CLASS(findClass);
gObjectClass->constructed = webkitFindControllerConstructed;
gObjectClass->get_property = webkitFindControllerGetProperty;
gObjectClass->set_property = webkitFindControllerSetProperty;
/**
* WebKitFindController:text:
*
* The current search text for this #WebKitFindController.
*/
g_object_class_install_property(gObjectClass,
PROP_TEXT,
g_param_spec_string("text",
_("Search text"),
_("Text to search for in the view"),
0,
WEBKIT_PARAM_READABLE));
/**
* WebKitFindController:options:
*
* The options to be used in the search operation.
*/
g_object_class_install_property(gObjectClass,
PROP_OPTIONS,
g_param_spec_flags("options",
_("Search Options"),
_("Search options to be used in the search operation"),
WEBKIT_TYPE_FIND_OPTIONS,
WEBKIT_FIND_OPTIONS_NONE,
WEBKIT_PARAM_READABLE));
/**
* WebKitFindController:max-match-count:
*
* The maximum number of matches to report for a given search.
*/
g_object_class_install_property(gObjectClass,
PROP_MAX_MATCH_COUNT,
g_param_spec_uint("max-match-count",
_("Maximum matches count"),
_("The maximum number of matches in a given text to report"),
0, G_MAXUINT, 0,
WEBKIT_PARAM_READABLE));
/**
* WebKitFindController:web-view:
*
* The #WebKitWebView this controller is associated to.
*/
g_object_class_install_property(gObjectClass,
PROP_WEB_VIEW,
g_param_spec_object("web-view",
_("WebView"),
_("The WebView associated with this find controller"),
WEBKIT_TYPE_WEB_VIEW,
static_cast<GParamFlags>(WEBKIT_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)));
/**
* WebKitFindController::found-text:
* @find_controller: the #WebKitFindController
* @match_count: the number of matches found of the search text
*
* This signal is emitted when a given text is found in the web
* page text. It will be issued if the text is found
* asynchronously after a call to webkit_find_controller_search(),
* webkit_find_controller_search_next() or
* webkit_find_controller_search_previous().
*/
signals[FOUND_TEXT] =
g_signal_new("found-text",
G_TYPE_FROM_CLASS(gObjectClass),
G_SIGNAL_RUN_LAST,
0, 0, 0,
g_cclosure_marshal_VOID__UINT,
G_TYPE_NONE, 1, G_TYPE_UINT);
/**
* WebKitFindController::failed-to-find-text:
* @find_controller: the #WebKitFindController
*
* This signal is emitted when a search operation does not find
* any result for the given text. It will be issued if the text
* is not found asynchronously after a call to
* webkit_find_controller_search(), webkit_find_controller_search_next()
* or webkit_find_controller_search_previous().
*/
signals[FAILED_TO_FIND_TEXT] =
g_signal_new("failed-to-find-text",
G_TYPE_FROM_CLASS(gObjectClass),
G_SIGNAL_RUN_LAST,
0, 0, 0,
g_cclosure_marshal_VOID__VOID,
G_TYPE_NONE, 0);
/**
* WebKitFindController::counted-matches:
* @find_controller: the #WebKitFindController
* @match_count: the number of matches of the search text
*
* This signal is emitted when the #WebKitFindController has
* counted the number of matches for a given text after a call
* to webkit_find_controller_count_matches().
*/
signals[COUNTED_MATCHES] =
g_signal_new("counted-matches",
G_TYPE_FROM_CLASS(gObjectClass),
G_SIGNAL_RUN_LAST,
0, 0, 0,
g_cclosure_marshal_VOID__UINT,
G_TYPE_NONE, 1, G_TYPE_UINT);
}
/**
* webkit_find_controller_get_search_text:
* @find_controller: the #WebKitFindController
*
* Gets the text that @find_controller is currently searching
* for. This text is passed to either
* webkit_find_controller_search() or
* webkit_find_controller_count_matches().
*
* Returns: the text to look for in the #WebKitWebView.
*/
const char* webkit_find_controller_get_search_text(WebKitFindController* findController)
{
g_return_val_if_fail(WEBKIT_IS_FIND_CONTROLLER(findController), 0);
return findController->priv->searchText.data();
}
/**
* webkit_find_controller_get_options:
* @find_controller: the #WebKitFindController
*
* Gets a bitmask containing the #WebKitFindOptions associated with
* the current search.
*
* Returns: a bitmask containing the #WebKitFindOptions associated
* with the current search.
*/
guint32 webkit_find_controller_get_options(WebKitFindController* findController)
{
g_return_val_if_fail(WEBKIT_IS_FIND_CONTROLLER(findController), WEBKIT_FIND_OPTIONS_NONE);
return toWebKitFindOptions(findController->priv->findOptions);
}
/**
* webkit_find_controller_get_max_match_count:
* @find_controller: the #WebKitFindController
*
* Gets the maximum number of matches to report during a text
* lookup. This number is passed as the last argument of
* webkit_find_controller_search() or
* webkit_find_controller_count_matches().
*
* Returns: the maximum number of matches to report.
*/
guint webkit_find_controller_get_max_match_count(WebKitFindController* findController)
{
g_return_val_if_fail(WEBKIT_IS_FIND_CONTROLLER(findController), 0);
return findController->priv->maxMatchCount;
}
/**
* webkit_find_controller_get_web_view:
* @find_controller: the #WebKitFindController
*
* Gets the #WebKitWebView this find controller is associated to. Do
* not dereference the returned instance as it belongs to the
* #WebKitFindController.
*
* Returns: (transfer none): the #WebKitWebView.
*/
WebKitWebView* webkit_find_controller_get_web_view(WebKitFindController* findController)
{
g_return_val_if_fail(WEBKIT_IS_FIND_CONTROLLER(findController), 0);
return findController->priv->webView;
}
static void webKitFindControllerPerform(WebKitFindController* findController, WebKitFindControllerOperation operation)
{
WebKitFindControllerPrivate* priv = findController->priv;
if (operation == CountOperation) {
getPage(findController).countStringMatches(String::fromUTF8(priv->searchText.data()),
static_cast<WebKit::FindOptions>(priv->findOptions), priv->maxMatchCount);
return;
}
uint32_t findOptions = priv->findOptions;
if (operation == FindOperation)
// Unconditionally highlight text matches when the search
// starts. WK1 API was forcing clients to enable/disable
// highlighting. Since most of them (all?) where using that
// feature we decided to simplify the WK2 API and
// unconditionally show highlights. Both search_next() and
// search_prev() should not enable highlighting to avoid an
// extra unmarkAllTextMatches() + markAllTextMatches()
findOptions |= FindOptionsShowHighlight;
getPage(findController).findString(String::fromUTF8(priv->searchText.data()), static_cast<WebKit::FindOptions>(findOptions), priv->maxMatchCount);
}
static inline void webKitFindControllerSetSearchData(WebKitFindController* findController, const gchar* searchText, guint32 findOptions, guint maxMatchCount)
{
findController->priv->searchText = searchText;
findController->priv->findOptions = findOptions;
findController->priv->maxMatchCount = maxMatchCount;
}
/**
* webkit_find_controller_search:
* @find_controller: the #WebKitFindController
* @search_text: the text to look for
* @find_options: a bitmask with the #WebKitFindOptions used in the search
* @max_match_count: the maximum number of matches allowed in the search
*
* Looks for @search_text in the #WebKitWebView associated with
* @find_controller since the beginning of the document highlighting
* up to @max_match_count matches. The outcome of the search will be
* asynchronously provided by the #WebKitFindController::found-text
* and #WebKitFindController::failed-to-find-text signals.
*
* To look for the next or previous occurrences of the same text
* with the same find options use webkit_find_controller_search_next()
* and/or webkit_find_controller_search_previous(). The
* #WebKitFindController will use the same text and options for the
* following searches unless they are modified by another call to this
* method.
*
* Note that if the number of matches is higher than @max_match_count
* then #WebKitFindController::found-text will report %G_MAXUINT matches
* instead of the actual number.
*
* Callers should call webkit_find_controller_search_finish() to
* finish the current search operation.
*/
void webkit_find_controller_search(WebKitFindController* findController, const gchar* searchText, guint findOptions, guint maxMatchCount)
{
g_return_if_fail(WEBKIT_IS_FIND_CONTROLLER(findController));
g_return_if_fail(searchText);
webKitFindControllerSetSearchData(findController, searchText, toWebFindOptions(findOptions), maxMatchCount);
webKitFindControllerPerform(findController, FindOperation);
}
/**
* webkit_find_controller_search_next:
* @find_controller: the #WebKitFindController
*
* Looks for the next occurrence of the search text.
*
* Calling this method before webkit_find_controller_search() or
* webkit_find_controller_count_matches() is a programming error.
*/
void webkit_find_controller_search_next(WebKitFindController* findController)
{
g_return_if_fail(WEBKIT_IS_FIND_CONTROLLER(findController));
findController->priv->findOptions &= ~FindOptionsBackwards;
findController->priv->findOptions &= ~FindOptionsShowHighlight;
webKitFindControllerPerform(findController, FindNextPrevOperation);
}
/**
* webkit_find_controller_search_previous:
* @find_controller: the #WebKitFindController
*
* Looks for the previous occurrence of the search text.
*
* Calling this method before webkit_find_controller_search() or
* webkit_find_controller_count_matches() is a programming error.
*/
void webkit_find_controller_search_previous(WebKitFindController* findController)
{
g_return_if_fail(WEBKIT_IS_FIND_CONTROLLER(findController));
findController->priv->findOptions |= FindOptionsBackwards;
findController->priv->findOptions &= ~FindOptionsShowHighlight;
webKitFindControllerPerform(findController, FindNextPrevOperation);
}
/**
* webkit_find_controller_count_matches:
* @find_controller: the #WebKitFindController
* @search_text: the text to look for
* @find_options: a bitmask with the #WebKitFindOptions used in the search
* @max_match_count: the maximum number of matches allowed in the search
*
* Counts the number of matches for @search_text found in the
* #WebKitWebView with the provided @find_options. The number of
* matches will be provided by the
* #WebKitFindController::counted-matches signal.
*/
void webkit_find_controller_count_matches(WebKitFindController* findController, const gchar* searchText, guint32 findOptions, guint maxMatchCount)
{
g_return_if_fail(WEBKIT_IS_FIND_CONTROLLER(findController));
g_return_if_fail(searchText);
webKitFindControllerSetSearchData(findController, searchText, toWebFindOptions(findOptions), maxMatchCount);
webKitFindControllerPerform(findController, CountOperation);
}
/**
* webkit_find_controller_search_finish:
* @find_controller: a #WebKitFindController
*
* Finishes a find operation started by
* webkit_find_controller_search(). It will basically unhighlight
* every text match found.
*
* This method will be typically called when the search UI is
* closed/hidden by the client application.
*/
void webkit_find_controller_search_finish(WebKitFindController* findController)
{
g_return_if_fail(WEBKIT_IS_FIND_CONTROLLER(findController));
getPage(findController).hideFindUI();
}