blob: 164f3832ed915a07e9783a8a123292efe25ac594 [file] [log] [blame]
// Copyright 2018 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_ACTIONS_OMNIBOX_PEDAL_H_
#define COMPONENTS_OMNIBOX_BROWSER_ACTIONS_OMNIBOX_PEDAL_H_
#include <vector>
#include "base/gtest_prod_util.h"
#include "base/values.h"
#include "build/build_config.h"
#include "components/omnibox/browser/actions/omnibox_action.h"
#include "components/omnibox/browser/actions/omnibox_pedal_concepts.h"
#include "components/omnibox/browser/buildflags.h"
#include "url/gurl.h"
// Conceptually, a Pedal is a fixed action that can be taken by the user
// pressing a button or taking a new dedicated suggestion when some
// associated intention is detected in an omnibox match suggestion.
// The typical action is to navigate to an internal Chrome surface
// like settings, but other actions like translation or launching
// an incognito window are possible. The intention is detected by
// checking trigger queries against suggested match queries.
class OmniboxPedal : public OmniboxAction {
public:
struct Token {
// Token identifier from the common token dictionary.
int id;
// Index of the next unconsumed token node. Initially this is set to
// the token's own index, indicating this token is unconsumed. Calls
// to |TokenSequence::Consume| may then update it to greater values,
// indicating that this token is consumed.
size_t link;
};
// This is a specialized container for the sequence matching algorithm.
// It is intended only for single-threaded access by OmniboxPedal classes.
class TokenSequence {
public:
// Construct with reserved size; used when loading real data.
explicit TokenSequence(size_t reserve_size);
// Construct with given sequence of |token_ids|; used by tests.
explicit TokenSequence(std::vector<int> token_ids);
// Don't use copies. They were necessary with old algorithm,
// but this structure is amenable to efficient resets on kept instances.
TokenSequence(const TokenSequence&) = delete;
TokenSequence& operator=(const TokenSequence&) = delete;
TokenSequence(TokenSequence&&);
TokenSequence& operator=(TokenSequence&&);
~TokenSequence();
// Returns true if all tokens are consumed (true for empty sequences).
bool IsFullyConsumed();
// Returns the number of unconsumed tokens remaining. Used by tests.
size_t CountUnconsumed() const;
// Add token with given |id| to sequence.
void Add(int id);
// Clears all tokens from this sequence.
inline void Clear() { tokens_.clear(); }
// Initializes all links in sequence to their own index, indicating
// unconsumed state for all. This is needed after calls to Erase.
void ResetLinks();
// Removes one or more instances of |erase_sequence| from this sequence
// by erasing token items from the |tokens_| container.
// Returns true if this sequence was changed; false if no match is found.
bool Erase(const TokenSequence& erase_sequence, bool erase_only_once);
// Consumes one or more instances of |consume_sequence| from this sequence
// by updating links on matching tokens. The container is not modified,
// only its contained elements may be mutated.
// Returns true if matches were found and consumed; false if no match.
bool Consume(const TokenSequence& consume_sequence, bool consume_only_once);
// Returns the total number of tokens, regardless of consumed status.
inline size_t Size() const { return tokens_.size(); }
// Returns collection memory estimate for tracing.
size_t EstimateMemoryUsage() const;
private:
// Returns true if this sequence, starting at |index|, matches given
// |sequence|. The |index_mask| can be used to disregard consumed status (0)
// or require that all tokens must be unconsumed to match (~0).
bool MatchesAt(const TokenSequence& sequence,
size_t index,
size_t index_mask) const;
// Follows links on tokens starting at |from_index| and returns the first
// unconsumed index. Returns |Size()| if no unconsumed token is found.
size_t WalkToUnconsumedIndexFrom(size_t from_index);
// Storage for tokens.
std::vector<Token> tokens_;
};
struct SynonymGroupSpec {
bool required;
bool match_once;
int message_id;
};
class SynonymGroup {
public:
// Note: synonyms must be specified in decreasing order by length
// so that longest matches will be detected first. For example,
// "incognito window" must come before "incognito" so that the " window"
// part will also be covered by this group -- otherwise it would be left
// intact and wrongly treated as uncovered by the checking algorithm.
// See OmniboxPedal::IsConceptMatch for the logic that necessitates order.
SynonymGroup(bool required, bool match_once, size_t reserve_size);
SynonymGroup(SynonymGroup&&);
~SynonymGroup();
SynonymGroup(const SynonymGroup&) = delete;
SynonymGroup& operator=(const SynonymGroup&) = delete;
SynonymGroup& operator=(SynonymGroup&&);
// Removes one or more matching synonyms from given |remaining| sequence if
// any are found. Returns true if checking may continue; false if no more
// checking is required because what remains cannot be a concept match.
// Note, if |fully_erase| is true and this method returns true, the
// |remaining| container has changed structure so ResetLinks must be called.
// This method doesn't call ResetLinks in that case, for efficiency.
bool EraseMatchesIn(TokenSequence& remaining, bool fully_erase) const;
// Add a synonym token sequence to this group.
void AddSynonym(TokenSequence synonym);
// When runtime data was preprocessed by pedal_processor,
// it avoided the need to sort at runtime in Chromium, but with
// the TC-based l10n technique, data loading needs to be robust
// enough to handle various forms and orders in translation data.
// Hence, a call to `SortSynonyms` is required after all calls
// to `AddSynonym` are complete. We may eliminate this step
// if we implement post-processing of the .xtb translation files.
void SortSynonyms();
// Estimates RAM usage in bytes for this synonym group.
size_t EstimateMemoryUsage() const;
// Erases sequences in ignore group from all synonyms in this group.
void EraseIgnoreGroup(const SynonymGroup& ignore_group);
// Returns true if this synonym group contains nontrivial data that can
// be used by the matching algorithm.
bool IsValid() const;
protected:
// If this is true, a synonym of the group must be present for triggering.
// If false, then presence is simply allowed and does not inhibit triggering
// (any text not covered by groups would stop trigger).
bool required_;
// If this is true, then only the rightmost instance of first synonym found
// will be taken from text being checked, and additional instances will
// inhibit trigger because repetition actually changes meaning. If false,
// then all instances of all synonyms are taken (repetition is meaningless).
bool match_once_;
// The set of interchangeable alternative representations for this group:
// when trying to clear browsing data, a user may think of 'erase', 'clear',
// 'delete', etc. Even though these are not strictly synonymous in natural
// language, they are considered equivalent within the context of intention
// to perform this Pedal's action.
std::vector<TokenSequence> synonyms_;
};
OmniboxPedal(OmniboxPedalId id, LabelStrings strings, GURL url);
// Downcasts the given OmniboxAction to an OmniboxPedal if the supplied
// instance represents one, otherwise returns nullptr.
static const OmniboxPedal* FromAction(const OmniboxAction* action);
// Called after the OmniboxPedalProvider finishes loading all pedals data.
// This can be used to override implementation bits based on flags, etc.
virtual void OnLoaded();
// Sets the destination URL for the Pedal.
void SetNavigationUrl(const GURL& url);
#if defined(SUPPORT_PEDALS_VECTOR_ICONS)
// Returns the default vector icon to use for Pedals that do not specify one.
static const gfx::VectorIcon& GetDefaultVectorIcon();
#endif
// Add a verbatim token sequence for direct matching.
// This can improve user experience of omnibox pedals by ensuring that
// button text entered verbatim is always a sufficient trigger. Since
// button text labels are not always within the specified set of triggers,
// it may be possible to discover a pedal, memorize the button
// label, and then go seeking it out again with the button label, but
// not find it. With the verbatim sequence, learning a pedal by label will
// always make the pedal available with that exact input (ignoring case and
// the common ignore group).
void AddVerbatimSequence(TokenSequence sequence);
// Move a synonym group into this Pedal's collection.
void AddSynonymGroup(SynonymGroup&& group);
// Specify synonym groups to load from localization strings.
// `locale_is_english` provides a hint about which locale is being loaded,
// used to support both synonym-groups and whole-phrase localization.
virtual std::vector<SynonymGroupSpec> SpecifySynonymGroups(
bool locale_is_english) const;
OmniboxPedalId PedalId() const { return id_; }
// Sometimes pedals report different IDs for metrics, either to enable
// feature discrimination (e.g. incognito mode) or to unify metrics
// of closely related pedals (e.g. a ChromeOS specialization of a pedal).
virtual OmniboxPedalId GetMetricsId() const;
// If a sufficient set of triggering synonym groups are present in
// match_sequence then it's a concept match and this returns true. If a
// required group is not present, or if match_sequence contains extraneous
// tokens not covered by any synonym group, then it's not a concept match and
// this returns false. |match_sequence| is consumed/mutated by this method.
bool IsConceptMatch(TokenSequence& match_sequence) const;
// OmniboxAction overrides:
void RecordActionShown(size_t position, bool executed) const override;
#if defined(SUPPORT_PEDALS_VECTOR_ICONS)
const gfx::VectorIcon& GetVectorIcon() const override;
#endif
size_t EstimateMemoryUsage() const override;
OmniboxActionId ActionId() const override;
#if BUILDFLAG(IS_ANDROID)
base::android::ScopedJavaLocalRef<jobject> GetOrCreateJavaObject(
JNIEnv* env) const override;
#endif
protected:
FRIEND_TEST_ALL_PREFIXES(OmniboxPedalTest, SynonymGroupErasesFirstMatchOnly);
FRIEND_TEST_ALL_PREFIXES(OmniboxPedalTest, SynonymGroupsDriveConceptMatches);
FRIEND_TEST_ALL_PREFIXES(OmniboxPedalTest,
VerbatimSynonymGroupDrivesConceptMatches);
FRIEND_TEST_ALL_PREFIXES(OmniboxPedalImplementationsTest,
UnorderedSynonymExpressionsAreConceptMatches);
~OmniboxPedal() override;
OmniboxPedalId id_;
// Before standard synonym group matching, we can check the verbatim
// syonym group for direct matches, e.g. with the button label text.
SynonymGroup verbatim_synonym_group_;
std::vector<SynonymGroup> synonym_groups_;
};
// This is a simple pedal suitable only for use by tests.
class TestOmniboxPedalClearBrowsingData : public OmniboxPedal {
public:
explicit TestOmniboxPedalClearBrowsingData();
std::vector<SynonymGroupSpec> SpecifySynonymGroups(
bool locale_is_english) const override;
protected:
~TestOmniboxPedalClearBrowsingData() override = default;
};
#endif // COMPONENTS_OMNIBOX_BROWSER_ACTIONS_OMNIBOX_PEDAL_H_