blob: ce215056d3092f8eb6595057a5b17a455277bc2c [file] [log] [blame]
// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef COMPONENTS_OMNIBOX_BROWSER_AUTOCOMPLETE_CONTROLLER_H_
#define COMPONENTS_OMNIBOX_BROWSER_AUTOCOMPLETE_CONTROLLER_H_
#include <memory>
#include <optional>
#include <string>
#include <vector>
#include "base/compiler_specific.h"
#include "base/gtest_prod_util.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/ref_counted.h"
#include "base/observer_list.h"
#include "base/observer_list_types.h"
#include "base/task/cancelable_task_tracker.h"
#include "base/time/time.h"
#include "base/timer/elapsed_timer.h"
#include "base/timer/timer.h"
#include "base/trace_event/memory_dump_provider.h"
#include "build/build_config.h"
#include "components/omnibox/browser/autocomplete_controller_metrics.h"
#include "components/omnibox/browser/autocomplete_input.h"
#include "components/omnibox/browser/autocomplete_provider.h"
#include "components/omnibox/browser/autocomplete_provider_client.h"
#include "components/omnibox/browser/autocomplete_provider_debouncer.h"
#include "components/omnibox/browser/autocomplete_provider_listener.h"
#include "components/omnibox/browser/autocomplete_result.h"
#include "components/omnibox/browser/autocomplete_scoring_signals_annotator.h"
#include "components/omnibox/browser/bookmark_provider.h"
#include "components/omnibox/browser/omnibox_log.h"
#include "components/omnibox/browser/open_tab_provider.h"
#include "components/optimization_guide/machine_learning_tflite_buildflags.h"
#include "third_party/omnibox_proto/types.pb.h"
#if BUILDFLAG(BUILD_WITH_TFLITE_LIB)
#include "components/omnibox/browser/autocomplete_scoring_model_service.h"
#endif // BUILDFLAG(BUILD_WITH_TFLITE_LIB)
class ClipboardProvider;
class DocumentProvider;
class HistoryFuzzyProvider;
class HistoryQuickProvider;
class HistoryURLProvider;
class KeywordProvider;
class OmniboxTriggeredFeatureService;
class OnDeviceHeadProvider;
class SearchProvider;
class TemplateURLService;
class VoiceSuggestProvider;
class ZeroSuggestProvider;
// The header used to report whether a navigation to google.com is coming from
// omnibox. Only set when the navigation is initiated from the Gemini
// built-in keyword.
inline constexpr char kOmniboxGeminiHeader[] = "X-Omnibox-Gemini";
// The AutocompleteController is the center of the autocomplete system. A
// class creates an instance of the controller, which in turn creates a set of
// AutocompleteProviders to serve it. The owning class can ask the controller
// to Start() a query; the controller in turn passes this call down to the
// providers, each of which keeps track of its own matches and whether it has
// finished processing the query. When a provider gets more matches or finishes
// processing, it notifies the controller, which merges the combined matches
// together and makes the result available to interested observers.
//
// The owner may also cancel the current query by calling Stop(), which the
// controller will in turn communicate to all the providers. No callbacks will
// happen after a request has been stopped.
//
// IMPORTANT: There is NO THREAD SAFETY built into this portion of the
// autocomplete system. All calls to and from the AutocompleteController should
// happen on the same thread. AutocompleteProviders are responsible for doing
// their own thread management when they need to return matches asynchronously.
//
// The coordinator for autocomplete queries, responsible for combining the
// matches from a series of providers into one AutocompleteResult.
class AutocompleteController : public AutocompleteProviderListener,
public base::trace_event::MemoryDumpProvider {
public:
// Describes an autocomplete pass.
enum class UpdateType {
// The 'null' update; used by `last_update_type_` to indicate no update has
// occurred for the current input.
kNone,
// An update triggered by the initial sync pass completing with all
// providers being done. I.e. no async pass will follow; e.g. because the
// user deleted text.
kSyncPassOnly,
// An update triggered by the initial sync pass completing without all
// providers being done. I.e., async passes will follow if not interrupted.
kSyncPass,
// An update triggered by an async pass completing without all providers
// being done.
kAsyncPass,
// An update triggered by an async pass completing with all providers except
// the doc provider being done.
kLastAsyncPassExceptDoc,
// An update triggered by the expire timer to clear transferred matches.
kExpirePass,
// An update triggered by an async pass completing with all providers being
// done.
kLastAsyncPass,
// An update caused by stopping autocompletion either due to user
// interaction or user inactivity.
kStop,
// An update triggered by the user deleting a match.
kMatchDeletion,
};
typedef std::vector<scoped_refptr<AutocompleteProvider>> Providers;
class Observer : public base::CheckedObserver {
public:
// Invoked when the |controller| Start() is called with an |input| that
// wants asynchronous matches. This is meant to exclude text classification
// requests. The |controller| parameter is only useful for observers that
// are observing multiple AutocompleteController instances.
virtual void OnStart(AutocompleteController* controller,
const AutocompleteInput& input) {}
// Invoked when the result set of |controller| changes. If
// |default_match_changed| is true, the default match of the result set has
// changed. The |controller| parameter is only useful for observers that
// are observing multiple AutocompleteController instances.
virtual void OnResultChanged(AutocompleteController* controller,
bool default_match_changed) {}
// Invoked when a ML scoring batch completes, i.e. `OnUrlScoringModelDone()`
// completes.
virtual void OnMlScored(AutocompleteController* controller,
const AutocompleteResult& result) {}
};
// Converts `UpdateType` to string.
static std::string UpdateTypeToDebugString(UpdateType update_type);
// Given a match, returns zero or more subtypes corresponding to SuggestType
// and SuggestSubtype enums in //third_party/omnibox_proto/types.proto.
// This is needed to update Chrome's native types/subtypes to those expected
// by the server. For more details, see go/chrome-suggest-logging.
// Note: `subtypes` may be prepopulated with server-reported subtypes.
static void ExtendMatchSubtypes(
const AutocompleteMatch& match,
base::flat_set<omnibox::SuggestSubtype>* subtypes);
// `provider_types` is a bitmap containing AutocompleteProvider::Type values
// that will (potentially, depending on platform, flags, etc.) be
// instantiated. `provider_client` is passed to all those providers, and
// is used to get access to the template URL service. `disable_ml` forces ML
// scoring off regardless of its feature state; this is useful for
// chrome://omnibox/ml.
AutocompleteController(
std::unique_ptr<AutocompleteProviderClient> provider_client,
int provider_types,
bool is_cros_launcher = false,
bool disable_ml = false);
~AutocompleteController() override;
AutocompleteController(const AutocompleteController&) = delete;
AutocompleteController& operator=(const AutocompleteController&) = delete;
// UI elements that need to be notified when the results get updated should
// be added as an |observer|.
void AddObserver(Observer* observer);
void RemoveObserver(Observer* observer);
// Starts an autocomplete query, which continues until all providers are
// done or the query is Stop()ed. It is safe to Start() a new query without
// Stop()ing the previous one.
//
// See AutocompleteInput::AutocompleteInput(...) for more details regarding
// |input| params.
//
// The controller calls AutocompleteController::Observer::OnResultChanged()
// from inside this call at least once. If matches are available later on that
// result in changing the result set the observers is notified again. When the
// controller is done the notification AUTOCOMPLETE_CONTROLLER_RESULT_READY is
// sent.
// Made virtual for mocking in tests.
virtual void Start(const AutocompleteInput& input);
// Calls StartPrefetch() on all eligible providers so that they can optionally
// perform a prefetch request to warm up their underlying service(s) and/or
// optionally cache their otherwise async response.
// Made virtual for mocking in tests.
virtual void StartPrefetch(const AutocompleteInput& input);
// Cancels the current query, ensuring there will be no future notifications
// fired. If new matches have come in since the most recent notification was
// fired, they will be discarded. If `clear_result` is true, the controller
// will also erase the result set. `due_to_user_inactivity` means this call
// was triggered by a user's idleness, i.e., not an explicit user action.
void Stop(bool clear_result, bool due_to_user_inactivity = false);
// Asks the relevant provider to delete |match|, and ensures observers are
// notified of resulting changes immediately. This should only be called when
// no query is running.
void DeleteMatch(const AutocompleteMatch& match);
// Asks the relevant provider to partially delete match, and ensures observers
// are notified of resulting changes immediately. This should only be called
// when no query is running.
// Calling this method does not imply removal of the AutocompleteMatch.
// |element_index| parameter specifies which part of the match should be
// deleted. For cases where the entire AutocompleteMatch should be removed,
// please see |DeleteMatch| method.
void DeleteMatchElement(const AutocompleteMatch& match, size_t element_index);
// AutocompleteProviderListener:
void OnProviderUpdate(bool updated_matches,
const AutocompleteProvider* provider) override;
// Called when an omnibox event log entry is generated. Populates
// `log.provider_info` with diagnostic information about the status of various
// providers and `log.features_triggered[_in_session]` with triggered
// features.
void AddProviderAndTriggeringLogs(OmniboxLog* logs) const;
// Called when a new omnibox session starts.
// We start a new session when the user first begins modifying the omnibox
// content; see |OmniboxEditModel::user_input_in_progress_|.
void ResetSession();
// Updates the destination URL for the given match with the final searchbox
// stats parameter using additional information otherwise not available at
// initial construction time iff the provider's TemplateURL supports searchbox
// stats.
// This method should be called right before the user navigates to the match.
void UpdateMatchDestinationURLWithAdditionalSearchboxStats(
base::TimeDelta query_formulation_time,
AutocompleteMatch* match) const;
// Constructs and sets the final destination URL on the given match.
void SetMatchDestinationURL(AutocompleteMatch* match) const;
HistoryURLProvider* history_url_provider() const {
return history_url_provider_;
}
KeywordProvider* keyword_provider() const { return keyword_provider_; }
SearchProvider* search_provider() const { return search_provider_; }
ClipboardProvider* clipboard_provider() const { return clipboard_provider_; }
VoiceSuggestProvider* voice_suggest_provider() const {
return voice_suggest_provider_;
}
OpenTabProvider* open_tab_provider() const { return open_tab_provider_; }
const AutocompleteInput& input() const { return input_; }
const AutocompleteResult& result() const { return published_result_; }
// Groups `published_result_` by search vs URL.
// See also `AutocompleteResult::GroupSuggestionsBySearchVsURL()`.
void GroupSuggestionsBySearchVsURL(size_t begin, size_t end);
bool done() const {
return last_update_type_ == UpdateType::kNone ||
last_update_type_ == UpdateType::kSyncPassOnly ||
last_update_type_ == UpdateType::kLastAsyncPass ||
last_update_type_ == UpdateType::kStop ||
last_update_type_ == UpdateType::kMatchDeletion;
}
UpdateType last_update_type() const { return last_update_type_; }
const Providers& providers() const { return providers_; }
const base::TimeTicks& last_time_default_match_changed() const {
return last_time_default_match_changed_;
}
// Sets the provider timeout duration for future calls to |Start()|.
void SetStartStopTimerDurationForTesting(base::TimeDelta duration);
// Returns the AutocompleteProviderClient owned by the controller.
AutocompleteProviderClient* autocomplete_provider_client() const {
return provider_client_.get();
}
// This is a deprecated method of injecting an externally sourced
// match into the result set, currently still needed only by iOS.
size_t InjectAdHocMatch(AutocompleteMatch match);
// Sets the position of the omnibox when it's in steady state (unfocused).
// Only used on iOS for logging purposes.
void SetSteadyStateOmniboxPosition(
metrics::OmniboxEventProto::OmniboxPosition position);
private:
friend class FakeAutocompleteController;
friend class AutocompleteProviderTest;
friend class OmniboxSuggestionButtonRowBrowserTest;
friend class ZeroSuggestPrefetchTabHelperBrowserTest;
FRIEND_TEST_ALL_PREFIXES(AutocompleteControllerTest,
FilterMatchesForInstantKeywordWithBareAt);
FRIEND_TEST_ALL_PREFIXES(AutocompleteProviderTest,
RedundantKeywordsIgnoredInResult);
FRIEND_TEST_ALL_PREFIXES(AutocompleteProviderTest, UpdateSearchboxStats);
FRIEND_TEST_ALL_PREFIXES(AutocompleteProviderPrefetchTest,
SupportedProvider_NonPrefetch);
FRIEND_TEST_ALL_PREFIXES(AutocompleteProviderPrefetchTest,
SupportedProvider_Prefetch);
FRIEND_TEST_ALL_PREFIXES(AutocompleteProviderPrefetchTest,
SupportedProvider_OngoingNonPrefetch);
FRIEND_TEST_ALL_PREFIXES(AutocompleteProviderPrefetchTest,
UnsupportedProvider_Prefetch);
FRIEND_TEST_ALL_PREFIXES(OmniboxPopupViewViewsTest, EmitAccessibilityEvents);
FRIEND_TEST_ALL_PREFIXES(OmniboxPopupViewViewsTest,
EmitAccessibilityEventsOnButtonFocusHint);
FRIEND_TEST_ALL_PREFIXES(OmniboxPopupViewViewsTest, DeleteSuggestion);
FRIEND_TEST_ALL_PREFIXES(OmniboxViewTest, DoesNotUpdateAutocompleteOnBlur);
FRIEND_TEST_ALL_PREFIXES(OmniboxViewViewsTest, CloseOmniboxPopupOnTextDrag);
FRIEND_TEST_ALL_PREFIXES(OmniboxViewViewsTest, FriendlyAccessibleLabel);
FRIEND_TEST_ALL_PREFIXES(OmniboxViewViewsTest, AccessiblePopup);
FRIEND_TEST_ALL_PREFIXES(OmniboxViewViewsTest, MaintainCursorAfterFocusCycle);
#if BUILDFLAG(IS_WIN)
FRIEND_TEST_ALL_PREFIXES(OmniboxViewViewsUIATest, AccessibleOmnibox);
#endif
FRIEND_TEST_ALL_PREFIXES(OmniboxEditModelPopupTest, SetSelectedLine);
FRIEND_TEST_ALL_PREFIXES(OmniboxEditModelPopupTest,
SetSelectedLineWithNoDefaultMatches);
FRIEND_TEST_ALL_PREFIXES(OmniboxEditModelPopupTest, TestFocusFixing);
FRIEND_TEST_ALL_PREFIXES(OmniboxEditModelPopupTest, PopupPositionChanging);
FRIEND_TEST_ALL_PREFIXES(OmniboxEditModelPopupTest, PopupStepSelection);
FRIEND_TEST_ALL_PREFIXES(OmniboxEditModelPopupTest,
PopupStepSelectionWithHiddenGroupIds);
FRIEND_TEST_ALL_PREFIXES(OmniboxEditModelPopupTest,
PopupStepSelectionWithActions);
FRIEND_TEST_ALL_PREFIXES(OmniboxEditModelPopupTest,
PopupInlineAutocompleteAndTemporaryText);
FRIEND_TEST_ALL_PREFIXES(OmniboxPopupViewViewsTest,
EmitSelectedChildrenChangedAccessibilityEvent);
FRIEND_TEST_ALL_PREFIXES(OmniboxEditModelPopupTest,
OpenActionSelectionLogsOmniboxEvent);
// A minimal representation of the previous `AutocompleteResult`. Used by
// `UpdateResult()`'s helper methods.
struct OldResult {
OldResult(UpdateType update_type,
AutocompleteInput input,
AutocompleteResult* result);
~OldResult();
std::optional<AutocompleteMatch> last_default_match;
std::u16string last_default_associated_keyword;
AutocompleteResult matches_to_transfer;
std::optional<AutocompleteMatch> default_match_to_preserve;
};
// Helpers called by the constructor. These initialize the specified providers
// and add them `providers_`. Split into 2 methods to avoid accidentally
// adding providers in the wrong order (async providers should be added first
// so that they run first).
void InitializeAsyncProviders(int provider_types);
void InitializeSyncProviders(int provider_types);
// Updates `internal_result_` to reflect the current provider state and fires
// notifications.
void UpdateResult(UpdateType update_type);
// `UpdateResult()` helper. Aggregates matches from `providers_` into
// `internal_result_`.
void AggregateNewMatches();
// `UpdateResult()` helper. Gets ML scores for eligible matches in
// `internal_result_` and then sorts `internal_result_` accordingly.
void MlRerank(OldResult& old_result);
// `UpdateResult()` helper. Calls multiple other helpers (see implementation).
void PostProcessMatches();
// `UpdateResult()` helper. Returns whether the default match changed.
bool CheckWhetherDefaultMatchChanged(
std::optional<AutocompleteMatch> last_default_match,
std::u16string last_default_associated_keyword);
// Attaches actions to matches: pedals, history clusters, tab switch, etc.
void AttachActions();
// Updates `result` to populate each match's `associated_keyword` if that
// match can show a keyword hint. `result` should be sorted by relevance
// before this is called.
void UpdateAssociatedKeywords(AutocompleteResult* result);
// For each group of contiguous matches from the same TemplateURL, show the
// provider name as a description on the first match in the group. Starter
// Pack matches show their URLs as descriptions instead of the provider name.
void UpdateKeywordDescriptions(AutocompleteResult* result);
// For each AutocompleteMatch in `result`, updates the searchbox stats iff the
// provider's TemplateURL supports it.
void UpdateSearchboxStats(AutocompleteResult* result);
// Update the tail suggestions' `tail_suggest_common_prefix`.
void UpdateTailSuggestPrefix(AutocompleteResult* result);
// Calls AutocompleteController::Observer::OnResultChanged() and if done sends
// AUTOCOMPLETE_CONTROLLER_RESULT_READY.
void NotifyChanged();
// Invokes `NotifyChanged()` through `notify_changed_debouncer_`.
void RequestNotifyChanged(bool notify_default_match, bool delayed);
// Cancels any pending `NotifyChanged()` invocation through
// `notify_changed_debouncer_`.
void CancelNotifyChangedRequest();
// Returns which of the providers that should run are done.
enum class ProviderDoneState {
kNotDone,
kAllExceptDocDone,
kAllDone,
};
ProviderDoneState GetProviderDoneState();
// Starts |expire_timer_|.
void StartExpireTimer();
// Starts |stop_timer_|.
void StartStopTimer();
// MemoryDumpProvider:
bool OnMemoryDump(
const base::trace_event::MemoryDumpArgs& args,
base::trace_event::ProcessMemoryDump* process_memory_dump) override;
// Returns whether the given provider should be ran based on whether we're in
// keyword mode and which keyword we're searching. Currently runs all enabled
// providers unless in a Starter Pack scope, except for OpenTabProvider which
// only runs on Lacros and the @tabs scope.
bool ShouldRunProvider(AutocompleteProvider* provider) const;
#if BUILDFLAG(BUILD_WITH_TFLITE_LIB)
// Runs the batch scoring for all the eligible matches in `results_.matches_`.
// `*WithStableSearches` avoids swapping the default suggestion from a search
// to a URL or vice versa. See `stable_search_blending`'s comment for details.
void RunBatchUrlScoringModel(OldResult& old_result);
void RunBatchUrlScoringModelWithStableSearches(OldResult& old_result);
void RunBatchUrlScoringModelMappedSearchBlending(OldResult& old_result);
#endif // BUILDFLAG(BUILD_WITH_TFLITE_LIB)
// Constructs a destination URL from supplied search terms args.
// TODO(1418077): look for a way to dissolve this function into direct
// application where it's needed.
GURL ComputeURLFromSearchTermsArgs(
const TemplateURL* template_url,
const TemplateURLRef::SearchTermsArgs& args) const;
// May remove company entity images if omnibox::kCompanyEntityIconAdjustment
// feature is enabled.
void MaybeRemoveCompanyEntityImages(AutocompleteResult* result);
// May remove actions from default suggestion to avoid interference with
// keyword mode refresh interaction. May clear some match text that is
// repeated across multiple consecutive matches.
void MaybeCleanSuggestionsForKeywordMode(const AutocompleteInput& input,
AutocompleteResult* result);
// Get the experiment stats v2 entry for the omnibox position. Used on iOS.
const omnibox::metrics::ChromeSearchboxStats::ExperimentStatsV2
GetOmniboxPositionExperimentStatsV2() const;
base::ObserverList<Observer> observers_;
// The client passed to the providers.
std::unique_ptr<AutocompleteProviderClient> provider_client_;
// A list of all providers.
Providers providers_;
raw_ptr<BookmarkProvider> bookmark_provider_;
raw_ptr<HistoryQuickProvider> history_quick_provider_;
raw_ptr<DocumentProvider> document_provider_;
raw_ptr<HistoryURLProvider> history_url_provider_;
raw_ptr<KeywordProvider> keyword_provider_;
raw_ptr<SearchProvider> search_provider_;
raw_ptr<ZeroSuggestProvider> zero_suggest_provider_;
raw_ptr<OnDeviceHeadProvider> on_device_head_provider_;
raw_ptr<ClipboardProvider> clipboard_provider_;
raw_ptr<VoiceSuggestProvider> voice_suggest_provider_;
raw_ptr<HistoryFuzzyProvider> history_fuzzy_provider_;
raw_ptr<OpenTabProvider> open_tab_provider_;
// A vector of scoring signals annotators for URL suggestions.
// Unlike the other existing annotators (e.g., pedals and keywords), these
// signal annotations should be done before the sort and cull pass.
std::vector<std::unique_ptr<AutocompleteScoringSignalsAnnotator>>
url_scoring_signals_annotators_;
// Input passed to Start.
AutocompleteInput input_;
// Data from the autocomplete query.
AutocompleteResult internal_result_;
// A snapshot of `internal_result_` when `NotifyChanged()` is called. Because
// it's debounced, `internal_result_` may change without invoking
// `NotifyChanged()`. `published_result_` ensures observers get a stable
// result.
AutocompleteResult published_result_;
// Used for logging the changes between updates.
std::vector<AutocompleteResult::MatchDedupComparator>
last_result_for_logging_;
// The most recent time the default match (inline match) changed. This may
// be earlier than the most recent keystroke if the recent keystrokes didn't
// change the suggested match in the omnibox. (For instance, if
// a user typed "mail.goog" and the match https://mail.google.com/ was
// the destination match ever since the user typed "ma" then this is
// the time that URL first appeared as the default match.) This may
// also be more recent than the last keystroke if there was an
// asynchronous provider that returned and changed the default
// match. See UpdateResult() for details on when we consider a
// match to have changed.
// This is very similar to `metrics_.last_default_change_time_`, but whereas
// that is reset on `::Start()`, this is not.
base::TimeTicks last_time_default_match_changed_;
// Timer used to remove any matches copied from the last result. When run
// invokes |ExpireCopiedEntries|.
base::OneShotTimer expire_timer_;
// Timer used to tell the providers to Stop() searching for matches.
base::OneShotTimer stop_timer_;
// Amount of time between when the user stops typing and when we send Stop()
// to every provider. This is intended to avoid the disruptive effect of
// belated omnibox updates, updates that come after the user has had to time
// to read the whole dropdown and doesn't expect it to change.
base::TimeDelta stop_timer_duration_ = base::Milliseconds(1500);
// Debouncer to avoid invoking `NotifyChange()` after updating results in
// quick succession. The last call, i.e. when all providers complete and
// `done_` is set true; and the 1st call, i.e. the sync update, are immune to
// this restriction. Calls not succeeding a result update (i.e. a call from
// closing the popup) bypass the delay as well.
AutocompleteProviderDebouncer notify_changed_debouncer_;
// Tracks if any delayed `RequestNotifyChanged()` call since the last
// `NotifyChanged()` call changed the default match. Otherwise, if there have
// been 2 delayed calls, the 1st having changed the default, the latter not,
// `NotifyChanged()` couldn't know of the former.
bool notify_changed_default_match_ = false;
// Represents the reason of the last `UpdateResult()` call.
UpdateType last_update_type_ = UpdateType::kNone;
// True if this instance of AutocompleteController is owned by the CrOS
// launcher. This is currently used to determine whether to enable the Open
// Tab provider always (CrOS launcher) or just in keyword mode (!launcher).
bool is_cros_launcher_;
// Logs stability and timing metrics for updates.
AutocompleteControllerMetrics metrics_{*this};
// True if the signal predicting a likely search has already been sent to the
// service worker context during the current input session. False on
// controller creation and after |ResetSession| is called.
bool search_service_worker_signal_sent_;
// Used for chrome://omnibox/ml to force disable the ML feature state.
bool disable_ml_ = true;
raw_ptr<TemplateURLService> template_url_service_;
raw_ptr<OmniboxTriggeredFeatureService> triggered_feature_service_;
// The preferred steady state (unfocused) omnibox position.
metrics::OmniboxEventProto::OmniboxPosition steady_state_omnibox_position_;
};
#endif // COMPONENTS_OMNIBOX_BROWSER_AUTOCOMPLETE_CONTROLLER_H_