chromium/third_party/blink/renderer/core/speculation_rules/speculation_rule_set_test.cc

// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "third_party/blink/renderer/core/speculation_rules/speculation_rule_set.h"

#include "base/ranges/algorithm.h"
#include "base/run_loop.h"
#include "base/test/bind.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/mock_callback.h"
#include "base/types/strong_alias.h"
#include "services/network/public/mojom/no_vary_search.mojom-blink.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/mojom/speculation_rules/speculation_rules.mojom-blink.h"
#include "third_party/blink/public/platform/browser_interface_broker_proxy.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_union_urlpatterninit_usvstring.h"
#include "third_party/blink/renderer/core/css/style_rule.h"
#include "third_party/blink/renderer/core/dom/shadow_root.h"
#include "third_party/blink/renderer/core/execution_context/agent.h"
#include "third_party/blink/renderer/core/frame/local_dom_window.h"
#include "third_party/blink/renderer/core/frame/local_frame.h"
#include "third_party/blink/renderer/core/frame/settings.h"
#include "third_party/blink/renderer/core/frame/web_feature.h"
#include "third_party/blink/renderer/core/html/html_anchor_element.h"
#include "third_party/blink/renderer/core/html/html_area_element.h"
#include "third_party/blink/renderer/core/html/html_base_element.h"
#include "third_party/blink/renderer/core/html/html_div_element.h"
#include "third_party/blink/renderer/core/html/html_head_element.h"
#include "third_party/blink/renderer/core/html/html_meta_element.h"
#include "third_party/blink/renderer/core/html/html_script_element.h"
#include "third_party/blink/renderer/core/inspector/console_message_storage.h"
#include "third_party/blink/renderer/core/loader/empty_clients.h"
#include "third_party/blink/renderer/core/speculation_rules/document_rule_predicate.h"
#include "third_party/blink/renderer/core/speculation_rules/document_speculation_rules.h"
#include "third_party/blink/renderer/core/speculation_rules/speculation_rules_metrics.h"
#include "third_party/blink/renderer/core/speculation_rules/stub_speculation_host.h"
#include "third_party/blink/renderer/core/testing/dummy_page_holder.h"
#include "third_party/blink/renderer/core/testing/null_execution_context.h"
#include "third_party/blink/renderer/core/url_pattern/url_pattern.h"
#include "third_party/blink/renderer/platform/scheduler/public/event_loop.h"
#include "third_party/blink/renderer/platform/testing/runtime_enabled_features_test_helpers.h"
#include "third_party/blink/renderer/platform/testing/task_environment.h"
#include "third_party/blink/renderer/platform/weborigin/kurl.h"
#include "third_party/blink/renderer/platform/wtf/vector.h"

namespace blink {
namespace {

AllOf;
ElementsAre;
Not;
PrintToString;

// Convenience matcher for list rules that sub-matches on their URLs.
class ListRuleMatcher {};

class URLPatternMatcher {};

template <typename... Matchers>
auto MatchesListOfURLs(Matchers&&... matchers) {}

MATCHER(RequiresAnonymousClientIPWhenCrossOrigin,
        negation ? "doesn't require anonymous client IP when cross origin"
                 : "requires anonymous client IP when cross origin") {}

MATCHER(SetsReferrerPolicy,
        std::string(negation ? "doesn't set" : "sets") + " a referrer policy") {}

MATCHER_P(ReferrerPolicyIs,
          policy,
          std::string(negation ? "doesn't have" : "has") + " " +
              PrintToString(policy) + " as the referrer policy") {}

class SpeculationRuleSetTest : public ::testing::Test {};

// Matches a SpeculationCandidatePtr list with a KURL list (without requiring
// candidates to be in a specific order).
template <typename... Matchers>
auto HasURLs(Matchers&&... urls) {}

// Matches a SpeculationCandidatePtr with an Eagerness.
auto HasEagerness(
    ::testing::Matcher<blink::mojom::SpeculationEagerness> matcher) {}

// Matches a SpeculationCandidatePtr with a KURL.
auto HasURL(::testing::Matcher<KURL> matcher) {}

// Matches a SpeculationCandidatePtr with a SpeculationAction.
auto HasAction(::testing::Matcher<mojom::blink::SpeculationAction> matcher) {}

// Matches a SpeculationCandidatePtr with a SpeculationTargetHint.
auto HasTargetHint(
    ::testing::Matcher<mojom::blink::SpeculationTargetHint> matcher) {}

// Matches a SpeculationCandidatePtr with a ReferrerPolicy.
auto HasReferrerPolicy(
    ::testing::Matcher<network::mojom::ReferrerPolicy> matcher) {}

auto HasNoVarySearchHint() {}

auto NVSVariesOnKeyOrder() {}

template <typename... Matchers>
auto NVSHasNoVaryParams(Matchers&&... params) {}

TEST_F(SpeculationRuleSetTest, Empty) {}

void AssertParseError(const SpeculationRuleSet* rule_set) {}

TEST_F(SpeculationRuleSetTest, RejectsInvalidJSON) {}

TEST_F(SpeculationRuleSetTest, RejectsNonObject) {}

TEST_F(SpeculationRuleSetTest, RejectsComments) {}

TEST_F(SpeculationRuleSetTest, SimplePrefetchRule) {}

TEST_F(SpeculationRuleSetTest, SimplePrerenderRule) {}

TEST_F(SpeculationRuleSetTest, SimplePrefetchWithSubresourcesRule) {}

TEST_F(SpeculationRuleSetTest, ResolvesURLs) {}

TEST_F(SpeculationRuleSetTest, ResolvesURLsWithRelativeTo) {}

TEST_F(SpeculationRuleSetTest, RequiresAnonymousClientIPWhenCrossOrigin) {}

TEST_F(SpeculationRuleSetTest, IgnoresUnknownOrDifferentlyTypedTopLevelKeys) {}

TEST_F(SpeculationRuleSetTest, DropUnrecognizedRules) {}

// Test that only prerender rule can process a "_blank" target hint.
TEST_F(SpeculationRuleSetTest, RulesWithTargetHint_Blank) {}

// Test that only prerender rule can process a "_self" target hint.
TEST_F(SpeculationRuleSetTest, RulesWithTargetHint_Self) {}

// Test that only prerender rule can process a "_parent" target hint but treat
// it as no hint.
// TODO(https://crbug.com/1354049): Support the "_parent" keyword for
// prerendering.
TEST_F(SpeculationRuleSetTest, RulesWithTargetHint_Parent) {}

// Test that only prerender rule can process a "_top" target hint but treat it
// as no hint.
// Test that rules with a "_top" hint are ignored.
// TODO(https://crbug.com/1354049): Support the "_top" keyword for prerendering.
TEST_F(SpeculationRuleSetTest, RulesWithTargetHint_Top) {}

// Test that rules with an empty target hint are ignored.
TEST_F(SpeculationRuleSetTest, RulesWithTargetHint_EmptyString) {}

// Test that only prerender rule can process a browsing context name target hint
// but treat it as no hint.
// TODO(https://crbug.com/1354049): Support valid browsing context names.
TEST_F(SpeculationRuleSetTest, RulesWithTargetHint_ValidBrowsingContextName) {}

// Test that rules with an invalid browsing context name target hint are
// ignored.
TEST_F(SpeculationRuleSetTest, RulesWithTargetHint_InvalidBrowsingContextName) {}

// Test that the the validation of the browsing context keywords runs an ASCII
// case-insensitive match.
TEST_F(SpeculationRuleSetTest, RulesWithTargetHint_CaseInsensitive) {}

// Test that only prefetch rule supports "anonymous-client-ip-when-cross-origin"
// requirement.
TEST_F(SpeculationRuleSetTest,
       RulesWithRequiresAnonymousClientIpWhenCrossOrigin) {}

TEST_F(SpeculationRuleSetTest, ReferrerPolicy) {}

TEST_F(SpeculationRuleSetTest, EmptyReferrerPolicy) {}

TEST_F(SpeculationRuleSetTest, PropagatesToDocument) {}

HTMLScriptElement* InsertSpeculationRules(Document& document,
                                          const String& speculation_script) {}

IncludesStyleUpdate;

// This runs the functor while observing any speculation rules sent by it.
// Since updates may be queued in a microtask or be blocked by style update,
// those are also awaited.
// At least one update is expected.
template <typename F>
void PropagateRulesToStubSpeculationHost(
    DummyPageHolder& page_holder,
    StubSpeculationHost& speculation_host,
    const F& functor,
    IncludesStyleUpdate includes_style_update = IncludesStyleUpdate{}

void PropagateRulesToStubSpeculationHost(DummyPageHolder& page_holder,
                                         StubSpeculationHost& speculation_host,
                                         const String& speculation_script) {}

template <typename F>
testing::AssertionResult NoRulesPropagatedToStubSpeculationHost(
    DummyPageHolder& page_holder,
    StubSpeculationHost& speculation_host,
    const F& functor,
    IncludesStyleUpdate includes_style_update = IncludesStyleUpdate{}

TEST_F(SpeculationRuleSetTest, PropagatesAllRulesToBrowser) {}

// Tests that prefetch rules are ignored unless SpeculationRulesPrefetchProxy
// is enabled.
TEST_F(SpeculationRuleSetTest, PrerenderIgnorePrefetchRules) {}

// Tests that prerender rules are ignored unless Prerender2 is enabled.
TEST_F(SpeculationRuleSetTest, PrefetchIgnorePrerenderRules) {}

// Tests that the presence of a speculationrules script is recorded.
TEST_F(SpeculationRuleSetTest, UseCounter) {}

// Tests that the presence of a speculationrules No-Vary-Search hint is
// recorded.
TEST_F(SpeculationRuleSetTest, NoVarySearchHintUseCounter) {}

// Tests that the document's URL is excluded from candidates.
TEST_F(SpeculationRuleSetTest, ExcludesFragmentLinks) {}

// Tests that the document's URL is excluded from candidates, even when its
// changes do not affect the base URL.
TEST_F(SpeculationRuleSetTest, ExcludesFragmentLinksWithBase) {}

// Tests that rules removed before the task to update speculation candidates
// runs are not reported.
TEST_F(SpeculationRuleSetTest, AddAndRemoveInSameTask) {}

// Tests that rules removed after being previously reported are reported as
// removed.
TEST_F(SpeculationRuleSetTest, AddAndRemoveAfterReport) {}

// Tests that removed candidates are reported in a microtask.
// This is somewhat difficult to observe in practice, but most sharply visible
// if a removal occurs and then in a subsequent microtask an addition occurs.
TEST_F(SpeculationRuleSetTest, RemoveInMicrotask) {}

class ConsoleCapturingChromeClient : public EmptyChromeClient {};

// Tests that parse errors are logged to the console.
TEST_F(SpeculationRuleSetTest, ConsoleWarning) {}

// Tests that errors of individual rules which cause them to be ignored are
// logged to the console.
TEST_F(SpeculationRuleSetTest, ConsoleWarningForInvalidRule) {}

// Tests that a warning is shown when speculation rules are added using the
// innerHTML setter, which doesn't currently do what the author meant.
TEST_F(SpeculationRuleSetTest, ConsoleWarningForSetInnerHTML) {}

// Tests that a console warning mentions that child modifications are
// ineffective.
TEST_F(SpeculationRuleSetTest, ConsoleWarningForChildModification) {}

// Tests that a console warning mentions duplicate keys.
TEST_F(SpeculationRuleSetTest, ConsoleWarningForDuplicateKey) {}
TEST_F(SpeculationRuleSetTest, DropNotArrayAtRuleSetPosition) {}

TEST_F(SpeculationRuleSetTest, DropNotObjectAtRulePosition) {}

MATCHER_P(MatchesPredicate,
          matcher,
          ::testing::DescribeMatcher<DocumentRulePredicate>(matcher)) {}

String GetTypeString(DocumentRulePredicate::Type type) {}

template <typename ItemType>
class PredicateMatcher {};

template <typename ItemType>
auto MakePredicateMatcher(
    Vector<::testing::Matcher<ItemType>> matchers,
    DocumentRulePredicate::Type type,
    typename PredicateMatcher<ItemType>::DocumentRulePredicateGetter getter) {}

auto MakeConditionMatcher(
    Vector<::testing::Matcher<DocumentRulePredicate>> matchers,
    DocumentRulePredicate::Type type) {}

auto And(Vector<::testing::Matcher<DocumentRulePredicate>> matchers = {}

auto Or(Vector<::testing::Matcher<DocumentRulePredicate>> matchers = {}

auto Neg(::testing::Matcher<DocumentRulePredicate> matcher) {}

auto Href(Vector<::testing::Matcher<URLPattern>> pattern_matchers = {}

auto Selector(Vector<::testing::Matcher<StyleRule>> style_rule_matchers = {}

class StyleRuleMatcher {};

auto StyleRuleWithSelectorText(String selector_text) {}

class DocumentRulesTest : public SpeculationRuleSetTest {};

TEST_F(DocumentRulesTest, ParseAnd) {}

TEST_F(DocumentRulesTest, ParseOr) {}

TEST_F(DocumentRulesTest, ParseNot) {}

TEST_F(DocumentRulesTest, ParseHref) {}

TEST_F(DocumentRulesTest, ParseHref_AllUrlPatternKeys) {}

TEST_F(DocumentRulesTest, HrefMatchesWithBaseURL) {}

// Testing on http://bar.com requesting a ruleset from http://foo.com.
TEST_F(DocumentRulesTest, HrefMatchesWithBaseURLAndRelativeTo) {}

TEST_F(DocumentRulesTest, DropInvalidRules) {}

// Tests that errors of individual rules which cause them to be ignored are
// logged to the console.
TEST_F(DocumentRulesTest, ConsoleWarningForInvalidRule) {}

TEST_F(DocumentRulesTest, DocumentRuleParseErrors) {}

TEST_F(DocumentRulesTest, DocumentRulePredicateParseErrors) {}

TEST_F(DocumentRulesTest, DefaultPredicate) {}

TEST_F(DocumentRulesTest, EvaluateCombinators) {}

TEST_F(DocumentRulesTest, EvaluateHrefMatches) {}

HTMLAnchorElement* AddAnchor(ContainerNode& parent, const String& href) {}

HTMLAreaElement* AddAreaElement(ContainerNode& parent, const String& href) {}

// Tests that speculation candidates based of existing links are reported after
// a document rule is inserted.
TEST_F(DocumentRulesTest, SpeculationCandidatesReportedAfterInitialization) {}

// Tests that speculation candidates based of existing links are reported after
// a document rule is inserted. Test that the speculation candidates include
// No-Vary-Search hint.
TEST_F(DocumentRulesTest,
       SpeculationCandidatesReportedAfterInitializationWithNVS) {}

// Tests that a new speculation candidate is reported after different
// modifications to a link.
TEST_F(DocumentRulesTest, SpeculationCandidatesUpdatedAfterLinkModifications) {}

// Tests that a new list of speculation candidates is reported after a rule set
// is added/removed.
TEST_F(DocumentRulesTest, SpeculationCandidatesUpdatedAfterRuleSetsChanged) {}

// Tests that list and document speculation rules work in combination correctly.
TEST_F(DocumentRulesTest, ListRuleCombinedWithDocumentRule) {}

// Tests that candidates created for document rules are correct when
// "anonymous-client-ip-when-cross-origin" is specified.
TEST_F(DocumentRulesTest, RequiresAnonymousClientIPWhenCrossOrigin) {}

// Tests that a link inside a shadow tree is included when creating
// document-rule based speculation candidates. Also tests that an "unslotted"
// link (link inside shadow host that isn't assigned to a slot) is not included.
TEST_F(DocumentRulesTest, LinkInShadowTreeIncluded) {}

// Tests that an anchor element with no href attribute is handled correctly.
TEST_F(DocumentRulesTest, LinkWithNoHrefAttribute) {}

// Tests that links with non-HTTP(s) urls are ignored.
TEST_F(DocumentRulesTest, LinkWithNonHttpHref) {}

// Tests a couple of edge cases:
// 1) Removing a link that doesn't match any rules
// 2) Adding and removing a link before running microtasks (i.e. before calling
// UpdateSpeculationCandidates).
TEST_F(DocumentRulesTest, RemovingUnmatchedAndPendingLinks) {}

// Tests if things still work if we use <area> instead of <a>.
TEST_F(DocumentRulesTest, AreaElement) {}

// Test that adding a link to an element that isn't connected doesn't DCHECK.
TEST_F(DocumentRulesTest, DisconnectedLink) {}

// Similar to test above, but now inside a shadow tree.
TEST_F(DocumentRulesTest, DisconnectedLinkInShadowTree) {}

// Tests that a document rule's specified referrer policy is used.
TEST_F(DocumentRulesTest, ReferrerPolicy) {}

// Tests that a link's referrer-policy value is used if one is not specified
// in the document rule.
TEST_F(DocumentRulesTest, LinkReferrerPolicy) {}

// Tests that changing the "referrerpolicy" attribute results in the
// corresponding speculation candidate updating.
TEST_F(DocumentRulesTest, ReferrerPolicyAttributeChangeCausesLinkInvalidation) {}

// Tests that changing the "rel" attribute results in the corresponding
// speculation candidate updating. Also tests that "rel=noreferrer" overrides
// the referrerpolicy attribute.
TEST_F(DocumentRulesTest, RelAttributeChangeCausesLinkInvalidation) {}

TEST_F(DocumentRulesTest, ReferrerMetaChangeShouldInvalidateCandidates) {}

TEST_F(DocumentRulesTest, BaseURLChanged) {}

TEST_F(DocumentRulesTest, TargetHintFromLink) {}

TEST_F(DocumentRulesTest, TargetHintFromSpeculationRuleOverridesLinkTarget) {}

TEST_F(DocumentRulesTest, TargetHintFromLinkDynamic) {}

TEST_F(DocumentRulesTest, ParseSelectorMatches) {}

TEST_F(DocumentRulesTest, GetStyleRules) {}

TEST_F(DocumentRulesTest, SelectorMatchesAddsCandidates) {}

TEST_F(DocumentRulesTest, SelectorMatchesIsDynamic) {}

TEST_F(DocumentRulesTest, AddingDocumentRulesInvalidatesStyle) {}

TEST_F(DocumentRulesTest, BasicStyleInvalidation) {}

TEST_F(DocumentRulesTest, IrrelevantDOMChangeShouldNotInvalidateCandidateList) {}

TEST_F(DocumentRulesTest, SelectorMatchesInsideShadowTree) {}

TEST_F(DocumentRulesTest, SelectorMatchesWithScopePseudoSelector) {}

// Basic test to check that we wait for UpdateStyle before sending a list of
// updated candidates to the browser process when "selector_matches" is
// enabled.
TEST_F(DocumentRulesTest, UpdateQueueingWithSelectorMatches_1) {}

// This tests that we don't need to wait for a style update if an operation
// does not invalidate style.
TEST_F(DocumentRulesTest, UpdateQueueingWithSelectorMatches_2) {}

// This tests a scenario where we queue an update microtask, invalidate style,
// update style, and then run the microtask.
TEST_F(DocumentRulesTest, UpdateQueueingWithSelectorMatches_3) {}

// This tests a scenario where we queue a microtask update, invalidate style,
// and then run the microtask.
TEST_F(DocumentRulesTest, UpdateQueueingWithSelectorMatches_4) {}

// Tests update queueing after making a DOM modification that doesn't directly
// affect a link.
TEST_F(DocumentRulesTest, UpdateQueueingWithSelectorMatches_5) {}

TEST_F(DocumentRulesTest, LinksWithoutComputedStyle) {}

TEST_F(DocumentRulesTest, LinksWithoutComputedStyle_HrefMatches) {}

TEST_F(DocumentRulesTest, LinkInsideDisplayLockedElement) {}

TEST_F(DocumentRulesTest, LinkInsideNestedDisplayLockedElement) {}

TEST_F(DocumentRulesTest, DisplayLockedLink) {}

TEST_F(DocumentRulesTest, AddLinkToDisplayLockedContainer) {}

TEST_F(DocumentRulesTest, DisplayLockedContainerTracking) {}

// Similar to SpeculationRulesTest.RemoveInMicrotask, but with relevant changes
// to style/layout which necessitate forcing a style update after removal.
TEST_F(DocumentRulesTest, RemoveForcesStyleUpdate) {}

// Checks a subtle case, wherein a ruleset is removed while speculation
// candidate update is waiting for clean style. In this case there is a race
// between the style update and the new microtask. In the case where the
// microtask wins, care is needed to avoid re-entrantly updating speculation
// candidates once it forces style clean.
TEST_F(DocumentRulesTest, RemoveWhileWaitingForStyle) {}

// Regression test, since the universal select sets rule set flags indicating
// that the rule set potentially invalidates all elements.
TEST_F(DocumentRulesTest, UniversalSelector) {}

TEST_F(SpeculationRuleSetTest, Eagerness) {}

TEST_F(SpeculationRuleSetTest, InvalidUseOfEagerness1) {}

TEST_F(SpeculationRuleSetTest, InvalidUseOfEagerness2) {}

TEST_F(SpeculationRuleSetTest, InvalidEagernessValue) {}

// Test that a valid No-Vary-Search hint will generate a speculation
// candidate.
TEST_F(SpeculationRuleSetTest, ValidNoVarySearchHintValueGeneratesCandidate) {}

TEST_F(SpeculationRuleSetTest, InvalidNoVarySearchHintValueGeneratesCandidate) {}

// Test that an empty but valid No-Vary-Search hint will generate a speculation
// candidate.
TEST_F(SpeculationRuleSetTest, EmptyNoVarySearchHintValueGeneratesCandidate) {}

// Test that a No-Vary-Search hint equivalent to the default
// will generate a speculation candidate.
TEST_F(SpeculationRuleSetTest, DefaultNoVarySearchHintValueGeneratesCandidate) {}

// Tests that No-Vary-Search errors that cause the speculation rules to be
// skipped are logged to the console.
TEST_F(SpeculationRuleSetTest, ConsoleWarningForNoVarySearchHintNotAString) {}

// Tests that No-Vary-Search errors that cause the speculation rules to be
// skipped are logged to the console.
TEST_F(SpeculationRuleSetTest, NoVarySearchHintParseErrorRuleSkipped) {}

// Tests that No-Vary-Search parsing errors that cause the speculation rules
// to still be accepted are logged to the console.
TEST_F(SpeculationRuleSetTest, NoVarySearchHintParseErrorRuleAccepted) {}

TEST_F(SpeculationRuleSetTest, ValidNoVarySearchHintNoErrorOrWarningMessages) {}

TEST_F(SpeculationRuleSetTest, DocumentReportsSuccessMetric) {}

TEST_F(SpeculationRuleSetTest, DocumentReportsParseErrorFromScript) {}

TEST_F(SpeculationRuleSetTest, DocumentReportsParseErrorFromRequest) {}

TEST_F(SpeculationRuleSetTest, DocumentReportsParseErrorFromBrowserInjection) {}

TEST_F(SpeculationRuleSetTest, ImplicitSource) {}

}  // namespace
}  // namespace blink