blob: 51e93931b73b9533f5645e5e963a4508f78f562a [file] [log] [blame]
// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_FRAGMENT_DIRECTIVE_TEXT_FRAGMENT_SELECTOR_GENERATOR_H_
#define THIRD_PARTY_BLINK_RENDERER_CORE_FRAGMENT_DIRECTIVE_TEXT_FRAGMENT_SELECTOR_GENERATOR_H_
#include "base/gtest_prod_util.h"
#include "base/time/time.h"
#include "components/shared_highlighting/core/common/shared_highlighting_metrics.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
#include "third_party/blink/renderer/core/core_export.h"
#include "third_party/blink/renderer/core/editing/forward.h"
#include "third_party/blink/renderer/core/fragment_directive/same_block_word_iterator.h"
#include "third_party/blink/renderer/core/fragment_directive/text_fragment_finder.h"
#include "third_party/blink/renderer/platform/mojo/heap_mojo_receiver.h"
namespace blink {
class LocalFrame;
class RangeInFlatTree;
class TextFragmentSelector;
// TextFragmentSelectorGenerator is used to generate a TextFragmentSelector,
// given a range of DOM in a document. The TextFragmentSelector provides the
// necessary portions of a text fragment URL such that it scrolls to the given
// range when navigated. For more details, see:
// https://github.com/WICG/scroll-to-text-fragment#proposed-solution.
//
// TextFragmentSelectorGenerator works by starting with a candidate selector
// and repeatedly trying it against the page content to ensure the correct and
// unique match. While the match isn't unique, repeatedly add context/range to
// the selector until the correct match is uniquely identified or no new
// context/range can be added.
class CORE_EXPORT TextFragmentSelectorGenerator final
: public GarbageCollected<TextFragmentSelectorGenerator>,
public TextFragmentFinder::Client {
using GenerateCallback =
base::OnceCallback<void(const TextFragmentSelector&,
shared_highlighting::LinkGenerationError)>;
public:
explicit TextFragmentSelectorGenerator(LocalFrame* main_frame);
TextFragmentSelectorGenerator(const TextFragmentSelectorGenerator&) = delete;
TextFragmentSelectorGenerator& operator=(
const TextFragmentSelectorGenerator&) = delete;
// Requests a TextFragmentSelector be generated for the selection of DOM
// specified by |range|. Will be generated asynchronously and returned by
// invoking |callback|.
void Generate(const RangeInFlatTree& range, GenerateCallback callback);
// Resets generator state to initial values and cancels any existing async
// tasks.
void Reset();
void SetCallbackForTesting(GenerateCallback callback) {
pending_generate_selector_callback_ = std::move(callback);
}
void Trace(Visitor*) const;
// Temporary diagnostic metric recorded to help explain discrepancies in
// other metrics.
void RecordSelectorStateUma() const;
LocalFrame* GetFrame() { return frame_; }
// Returns the text value of the range this generator is attempting to
// generate a selector for. Returns empty string if the range is invalid or
// if called before calling Generate().
String GetSelectorTargetText() const;
private:
friend class TextFragmentSelectorGeneratorTest;
FRIEND_TEST_ALL_PREFIXES(TextFragmentSelectorGeneratorTest,
GetPreviousTextEndPosition_PrevNode);
FRIEND_TEST_ALL_PREFIXES(TextFragmentSelectorGeneratorTest,
GetPreviousTextEndPosition_PrevTextNode);
FRIEND_TEST_ALL_PREFIXES(TextFragmentSelectorGeneratorTest,
GetPreviousTextEndPosition_ParentNode);
FRIEND_TEST_ALL_PREFIXES(TextFragmentSelectorGeneratorTest,
GetPreviousTextEndPosition_SpacesBeforeSelection);
FRIEND_TEST_ALL_PREFIXES(TextFragmentSelectorGeneratorTest,
GetPreviousTextEndPosition_InvisibleBeforeSelection);
FRIEND_TEST_ALL_PREFIXES(TextFragmentSelectorGeneratorTest,
GetPreviousTextEndPosition_NoPrevious);
FRIEND_TEST_ALL_PREFIXES(TextFragmentSelectorGeneratorTest,
GetNextTextStartPosition_NextNode);
FRIEND_TEST_ALL_PREFIXES(TextFragmentSelectorGeneratorTest,
GetNextTextStartPosition_NextNode_WithComment);
FRIEND_TEST_ALL_PREFIXES(TextFragmentSelectorGeneratorTest,
GetNextTextStartPosition_NextTextNode);
FRIEND_TEST_ALL_PREFIXES(TextFragmentSelectorGeneratorTest,
GetNextTextStartPosition_ParentNode);
FRIEND_TEST_ALL_PREFIXES(TextFragmentSelectorGeneratorTest,
GetNextTextStartPosition_SpacesAfterSelection);
FRIEND_TEST_ALL_PREFIXES(TextFragmentSelectorGeneratorTest,
GetNextTextStartPosition_InvisibleAfterSelection);
FRIEND_TEST_ALL_PREFIXES(TextFragmentSelectorGeneratorTest,
RangeSelector_RangeMultipleNonBlockNodes);
FRIEND_TEST_ALL_PREFIXES(TextFragmentSelectorGeneratorTest,
GetNextTextStartPosition_NoNextNode);
FRIEND_TEST_ALL_PREFIXES(TextFragmentSelectorGeneratorTest,
ExactTextSelector_Long);
FRIEND_TEST_ALL_PREFIXES(
TextFragmentSelectorGeneratorTest,
GetPreviousTextEndPosition_ShouldSkipNodesWithNoLayoutObject);
FRIEND_TEST_ALL_PREFIXES(TextFragmentSelectorGeneratorTest,
RemoveLayoutObjectAsync);
// Used for determining the next step of selector generation.
enum GenerationStep { kExact, kRange, kContext };
// Used for determining the current state of |selector_|.
enum SelectorState {
// Search for candidate selector didn't start.
kNotStarted,
// Candidate selector should be generated or extended.
kNeedsNewCandidate,
// Candidate selector generation was successful and selector is ready to be
// tested for uniqueness and accuracy by running against the page's content.
kTestCandidate,
// Candidate selector generation was unsuccessful. No further attempts are
// necessary.
kFailure,
// Selector is found. No further attempts are necessary.
kSuccess,
kMaxValue = kSuccess
};
// TextFragmentFinder::Client interface
void DidFindMatch(const RangeInFlatTree& match, bool is_unique) override;
void NoMatchFound() override;
// Adjust the selection start/end to a valid position. That includes skipping
// non text start/end nodes and extending selection from start and end to
// contain full words.
void AdjustSelection();
// Generates selector for current selection.
void StartGeneration();
void GenerateSelectorCandidate();
void ResolveSelectorState();
void RunTextFinder();
// Returns first position for the text following given position.
PositionInFlatTree GetNextTextStartPosition(
const PositionInFlatTree& position);
// Returns last position for the text preceding given position.
PositionInFlatTree GetPreviousTextEndPosition(
const PositionInFlatTree& position);
void GenerateExactSelector();
void ExtendRangeSelector();
void ExtendContext();
void RecordAllMetrics(const TextFragmentSelector& selector);
// Called when selector generation is complete.
void OnSelectorReady(const TextFragmentSelector& selector);
// Called by tests to change default parameters. A negative value will reset
// the override.
static void OverrideExactTextMaxCharsForTesting(int value);
unsigned GetExactTextMaxChars();
Member<LocalFrame> frame_;
// This is the Range for which we're generating a selector.
Member<RangeInFlatTree> range_;
std::unique_ptr<TextFragmentSelector> selector_;
GenerateCallback pending_generate_selector_callback_;
// Callback invoked each time DidFindMatch is called; for testing only.
// Allows inserting code to run between asynchronous generation steps.
base::OnceCallback<void(bool is_unique)> did_find_match_callback_for_testing_;
GenerationStep step_ = kExact;
SelectorState state_ = kNeedsNewCandidate;
shared_highlighting::LinkGenerationError error_;
// Iterators for gradually forming prefix, suffix and range
Member<ForwardSameBlockWordIterator> suffix_iterator_;
Member<BackwardSameBlockWordIterator> prefix_iterator_;
Member<ForwardSameBlockWordIterator> range_start_iterator_;
Member<BackwardSameBlockWordIterator> range_end_iterator_;
// Indicates a number of words used from |max_available_prefix_| and
// |max_available_suffix_| for the current |selector_|.
int num_context_words_ = 0;
int num_range_words_ = 0;
// Indicates the Max Context Words allowed for the
// SharedHighlightsMaxContextWords experiment
int max_context_words_ = 10;
int iteration_ = 0;
base::TimeTicks generation_start_time_;
Member<TextFragmentFinder> finder_;
};
} // namespace blink
#endif // THIRD_PARTY_BLINK_RENDERER_CORE_FRAGMENT_DIRECTIVE_TEXT_FRAGMENT_SELECTOR_GENERATOR_H_