Bug 2061013 - Part 3: Fire prefetches for moderate eagerness speculation rules on hover. r=jjaschke
Differential Revision: https://phabricator.services.mozilla.com/D321440
This commit is contained in:
committed by
avandolder@mozilla.com
parent
961ce625e0
commit
5ef9925313
@@ -5,7 +5,9 @@
|
||||
#include "mozilla/dom/SpeculationRules.h"
|
||||
|
||||
#include "mozilla/CycleCollectedJSContext.h"
|
||||
#include "mozilla/StaticPrefs_dom.h"
|
||||
#include "mozilla/dom/Document.h"
|
||||
#include "mozilla/dom/Element.h"
|
||||
#include "mozilla/dom/PrefetchCandidates.h"
|
||||
#include "mozilla/dom/PrefetchLog.h"
|
||||
#include "mozilla/dom/ReferrerPolicyBinding.h"
|
||||
@@ -14,8 +16,10 @@
|
||||
#include "mozilla/dom/speculationrules_ffi_generated.h"
|
||||
#include "nsContentUtils.h"
|
||||
#include "nsCycleCollectionParticipant.h"
|
||||
#include "nsIContentInlines.h"
|
||||
#include "nsIFrame.h"
|
||||
#include "nsIScriptElement.h"
|
||||
#include "nsITimer.h"
|
||||
#include "nsIURI.h"
|
||||
#include "nsNetUtil.h"
|
||||
#include "nsTArray.h"
|
||||
@@ -72,6 +76,7 @@ NS_IMPL_CYCLE_COLLECTION_CLASS(SpeculationRules)
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(SpeculationRules)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocument)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mHoverLink)
|
||||
for (const auto& entry : tmp->mRuleSetsFromScript) {
|
||||
NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mRuleSetsFromScript key");
|
||||
cb.NoteXPCOMChild(entry.GetKey());
|
||||
@@ -79,13 +84,17 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(SpeculationRules)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(SpeculationRules)
|
||||
tmp->CancelHoverTimer();
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocument)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mRuleSetsFromScript)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mHoverLink)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
|
||||
|
||||
SpeculationRules::SpeculationRules(Document* aDocument)
|
||||
: mDocument(aDocument) {}
|
||||
|
||||
SpeculationRules::~SpeculationRules() { CancelHoverTimer(); }
|
||||
|
||||
// https://html.spec.whatwg.org/#register-speculation-rules
|
||||
void SpeculationRules::RegisterFromScript(
|
||||
nsIScriptElement* aScriptElement, UniquePtr<SpeculationRuleSet> aRuleSet) {
|
||||
@@ -260,4 +269,70 @@ void SpeculationRules::FindMatchingLinks(nsTArray<const Element*>& aLinks) {
|
||||
// 3. Return links.
|
||||
}
|
||||
|
||||
Element* SpeculationRules::FindInterestedLink(nsIContent* aContent) const {
|
||||
for (nsIContent* content = aContent; content;
|
||||
content = content->GetFlattenedTreeParent()) {
|
||||
if (content->IsElement() && mLinks.Contains(content->AsElement())) {
|
||||
return content->AsElement();
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void SpeculationRules::HoverContentChanged(nsIContent* aContent) {
|
||||
if (mCandidateGroups.IsEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
RefPtr<Element> link = FindInterestedLink(aContent);
|
||||
if (link == mHoverLink) {
|
||||
// The cursor moved within the same link, so it has been hovered
|
||||
// continuously: let the timer keep running, or stay expired if the link has
|
||||
// already been enacted.
|
||||
return;
|
||||
}
|
||||
|
||||
CancelHoverTimer();
|
||||
mHoverLink = link;
|
||||
if (!mHoverLink) {
|
||||
return;
|
||||
}
|
||||
|
||||
// The timer holds no reference to us, so it must not outlive us; both the
|
||||
// destructor and the cycle collector cancel it.
|
||||
NS_NewTimerWithFuncCallback(
|
||||
getter_AddRefs(mHoverTimer), HoverTimerFired, this,
|
||||
StaticPrefs::dom_speculation_rules_moderate_hover_delay_ms(),
|
||||
nsITimer::TYPE_ONE_SHOT, "SpeculationRules::HoverTimerFired"_ns);
|
||||
}
|
||||
|
||||
void SpeculationRules::CancelHoverTimer() {
|
||||
if (mHoverTimer) {
|
||||
mHoverTimer->Cancel();
|
||||
mHoverTimer = nullptr;
|
||||
}
|
||||
mHoverLink = nullptr;
|
||||
}
|
||||
|
||||
/* static */
|
||||
void SpeculationRules::HoverTimerFired(nsITimer* aTimer, void* aClosure) {
|
||||
RefPtr speculationRules = static_cast<SpeculationRules*>(aClosure);
|
||||
speculationRules->mHoverTimer = nullptr;
|
||||
|
||||
// mHoverLink is deliberately left set, so that moving the cursor around
|
||||
// within the link it names doesn't arm the timer all over again. It is
|
||||
// cleared once the cursor moves on to a different link, or off of links
|
||||
// entirely.
|
||||
RefPtr<Element> link = speculationRules->mHoverLink;
|
||||
if (!link || !link->IsInComposedDoc()) {
|
||||
return;
|
||||
}
|
||||
nsCOMPtr<nsIURI> uri = link->GetHrefURI();
|
||||
if (uri) {
|
||||
// TODO(avandolder): Currently, this is also how Eager eagerness rules will
|
||||
// be fired. We will eventually move them to a shorter timer.
|
||||
speculationRules->EnactCandidates(uri, Eagerness::Moderate);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace mozilla::dom
|
||||
|
||||
@@ -7,13 +7,16 @@
|
||||
|
||||
#include "mozilla/UniquePtr.h"
|
||||
#include "mozilla/dom/speculationrules_ffi_generated.h"
|
||||
#include "nsCOMPtr.h"
|
||||
#include "nsClassHashtable.h"
|
||||
#include "nsCycleCollectionParticipant.h"
|
||||
#include "nsHashKeys.h"
|
||||
#include "nsTArrayForwardDeclare.h"
|
||||
#include "nsTHashSet.h"
|
||||
|
||||
class nsIContent;
|
||||
class nsIScriptElement;
|
||||
class nsITimer;
|
||||
class nsIURI;
|
||||
|
||||
namespace mozilla::dom {
|
||||
@@ -41,8 +44,14 @@ class SpeculationRules final {
|
||||
|
||||
void FindMatchingLinks(nsTArray<const Element*>& aLinks);
|
||||
|
||||
void HoverContentChanged(nsIContent* aContent);
|
||||
|
||||
private:
|
||||
virtual ~SpeculationRules() = default;
|
||||
virtual ~SpeculationRules();
|
||||
|
||||
// The innermost inclusive flat tree ancestor of aContent that is a link this
|
||||
// document is tracking, or nullptr if there is none.
|
||||
Element* FindInterestedLink(nsIContent* aContent) const;
|
||||
|
||||
// https://html.spec.whatwg.org/#inner-consider-speculative-loads-steps
|
||||
// Step 7, for those candidate groups that the user's behaviour has shown to
|
||||
@@ -52,6 +61,9 @@ class SpeculationRules final {
|
||||
// collected from every candidate the user's behaviour justifies.
|
||||
void EnactCandidates(nsIURI* aURL, Eagerness aTriggerLevel);
|
||||
|
||||
void CancelHoverTimer();
|
||||
static void HoverTimerFired(nsITimer* aTimer, void* aClosure);
|
||||
|
||||
RefPtr<Document> mDocument;
|
||||
|
||||
// https://html.spec.whatwg.org/#document-sr-sets
|
||||
@@ -73,6 +85,11 @@ class SpeculationRules final {
|
||||
// InnerConsiderLoads. Immediate groups have already been enacted; the rest
|
||||
// are kept so a later signal of user interest can enact them.
|
||||
nsTArray<PrefetchCandidate> mCandidateGroups;
|
||||
|
||||
// The link the hover timer is waiting on. Owning, as nothing else keeps the
|
||||
// element alive for the duration of the timer.
|
||||
RefPtr<Element> mHoverLink;
|
||||
nsCOMPtr<nsITimer> mHoverTimer;
|
||||
};
|
||||
|
||||
} // namespace mozilla::dom
|
||||
|
||||
@@ -72,6 +72,7 @@
|
||||
#include "mozilla/dom/PopoverData.h"
|
||||
#include "mozilla/dom/Record.h"
|
||||
#include "mozilla/dom/Selection.h"
|
||||
#include "mozilla/dom/SpeculationRules.h"
|
||||
#include "mozilla/dom/UIEvent.h"
|
||||
#include "mozilla/dom/UIEventBinding.h"
|
||||
#include "mozilla/dom/UserActivation.h"
|
||||
@@ -6920,6 +6921,7 @@ bool EventStateManager::SetContentState(nsIContent* aContent,
|
||||
if (newHover != mHoverContent) {
|
||||
notifyContent1 = newHover;
|
||||
notifyContent2 = mHoverContent;
|
||||
NotifySpeculationRulesOfHover(newHover);
|
||||
mHoverContent = newHover;
|
||||
}
|
||||
}
|
||||
@@ -6984,6 +6986,20 @@ bool EventStateManager::SetContentState(nsIContent* aContent,
|
||||
return true;
|
||||
}
|
||||
|
||||
// Hovering a link is a signal of user interest that can enact a speculation
|
||||
// rules prefetch candidate. This is notified on hover chain changes rather than
|
||||
// from mouseover/mouseout so that moving the cursor between the children of a
|
||||
// link doesn't read as leaving and re-entering the link itself.
|
||||
void EventStateManager::NotifySpeculationRulesOfHover(nsIContent* aNewHover) {
|
||||
nsIContent* content = aNewHover ? aNewHover : mHoverContent.get();
|
||||
if (!content) {
|
||||
return;
|
||||
}
|
||||
if (auto* speculationRules = content->OwnerDoc()->GetSpeculationRules()) {
|
||||
speculationRules->HoverContentChanged(aNewHover);
|
||||
}
|
||||
}
|
||||
|
||||
void EventStateManager::RemoveNodeFromChainIfNeeded(ElementState aState,
|
||||
nsIContent* aContentRemoved,
|
||||
bool aNotify) {
|
||||
|
||||
@@ -1337,6 +1337,11 @@ class EventStateManager : public nsSupportsWeakReference, public nsIObserver {
|
||||
void RemoveNodeFromChainIfNeeded(ElementState aState,
|
||||
nsIContent* aContentRemoved, bool aNotify);
|
||||
|
||||
// Tells the hovered document's speculation rules that the deepest hovered
|
||||
// node is about to become aNewHover, so that it can start or cancel the
|
||||
// hover delay for a moderate eagerness prefetch.
|
||||
void NotifySpeculationRulesOfHover(nsIContent* aNewHover);
|
||||
|
||||
[[nodiscard]] bool IsEventOutsideDragThreshold(
|
||||
const WidgetInputEvent& aEvent) const;
|
||||
|
||||
|
||||
@@ -4934,6 +4934,13 @@
|
||||
value: true
|
||||
mirror: always
|
||||
|
||||
# How long (ms) the cursor has to rest on a link before a "moderate" eagerness
|
||||
# prefetch candidate matching it is enacted.
|
||||
- name: dom.speculation_rules.moderate_hover_delay_ms
|
||||
type: RelaxedAtomicUint32
|
||||
value: 200
|
||||
mirror: always
|
||||
|
||||
- name: dom.securecontext.allowlist_onions
|
||||
type: bool
|
||||
value: false
|
||||
|
||||
Reference in New Issue
Block a user