1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273
|
// 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 <unordered_set>
#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_
|