Backed out changeset cfe0bbc666b8 (bug 1784757) in order to wait some more for a...
[gecko.git] / docshell / base / BaseHistory.cpp
bloba38fb716575954436534772c737226eb0b98178b
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "BaseHistory.h"
8 #include "nsThreadUtils.h"
9 #include "mozilla/dom/ContentParent.h"
10 #include "mozilla/dom/Document.h"
11 #include "mozilla/dom/Link.h"
12 #include "mozilla/dom/Element.h"
13 #include "mozilla/StaticPrefs_browser.h"
14 #include "mozilla/StaticPrefs_layout.h"
16 namespace mozilla {
18 using mozilla::dom::ContentParent;
19 using mozilla::dom::Link;
21 BaseHistory::BaseHistory() : mTrackedURIs(kTrackedUrisInitialSize) {}
23 BaseHistory::~BaseHistory() = default;
25 static constexpr nsLiteralCString kDisallowedSchemes[] = {
26 "about"_ns, "blob"_ns, "data"_ns, "chrome"_ns,
27 "imap"_ns, "javascript"_ns, "mailbox"_ns, "moz-anno"_ns,
28 "news"_ns, "page-icon"_ns, "resource"_ns, "view-source"_ns,
29 "moz-extension"_ns,
32 bool BaseHistory::CanStore(nsIURI* aURI) {
33 nsAutoCString scheme;
34 if (NS_WARN_IF(NS_FAILED(aURI->GetScheme(scheme)))) {
35 return false;
38 if (!scheme.EqualsLiteral("http") && !scheme.EqualsLiteral("https")) {
39 for (const nsLiteralCString& disallowed : kDisallowedSchemes) {
40 if (scheme.Equals(disallowed)) {
41 return false;
46 nsAutoCString spec;
47 aURI->GetSpec(spec);
48 return spec.Length() <= StaticPrefs::browser_history_maxUrlLength();
51 void BaseHistory::ScheduleVisitedQuery(nsIURI* aURI,
52 dom::ContentParent* aForProcess) {
53 mPendingQueries.WithEntryHandle(aURI, [&](auto&& entry) {
54 auto& set = entry.OrInsertWith([] { return ContentParentSet(); });
55 if (aForProcess) {
56 set.Insert(aForProcess);
58 });
59 if (mStartPendingVisitedQueriesScheduled) {
60 return;
62 mStartPendingVisitedQueriesScheduled =
63 NS_SUCCEEDED(NS_DispatchToMainThreadQueue(
64 NS_NewRunnableFunction(
65 "BaseHistory::StartPendingVisitedQueries",
66 [self = RefPtr<BaseHistory>(this)] {
67 self->mStartPendingVisitedQueriesScheduled = false;
68 auto queries = std::move(self->mPendingQueries);
69 self->StartPendingVisitedQueries(std::move(queries));
70 MOZ_DIAGNOSTIC_ASSERT(self->mPendingQueries.IsEmpty());
71 }),
72 EventQueuePriority::Idle));
75 void BaseHistory::CancelVisitedQueryIfPossible(nsIURI* aURI) {
76 mPendingQueries.Remove(aURI);
77 // TODO(bug 1591393): It could be worth to make this virtual and allow places
78 // to stop the existing database query? Needs some measurement.
81 void BaseHistory::RegisterVisitedCallback(nsIURI* aURI, Link* aLink) {
82 MOZ_ASSERT(NS_IsMainThread());
83 MOZ_ASSERT(aURI, "Must pass a non-null URI!");
84 MOZ_ASSERT(aLink, "Must pass a non-null Link!");
86 if (!CanStore(aURI)) {
87 aLink->VisitedQueryFinished(/* visited = */ false);
88 return;
91 // Obtain our array of observers for this URI.
92 auto* const links =
93 mTrackedURIs.WithEntryHandle(aURI, [&](auto&& entry) -> ObservingLinks* {
94 MOZ_DIAGNOSTIC_ASSERT(!entry || !entry->mLinks.IsEmpty(),
95 "An empty key was kept around in our hashtable!");
96 if (!entry) {
97 ScheduleVisitedQuery(aURI, nullptr);
100 return &entry.OrInsertWith([] { return ObservingLinks{}; });
103 if (!links) {
104 return;
107 // Sanity check that Links are not registered more than once for a given URI.
108 // This will not catch a case where it is registered for two different URIs.
109 MOZ_DIAGNOSTIC_ASSERT(!links->mLinks.Contains(aLink),
110 "Already tracking this Link object!");
112 links->mLinks.AppendElement(aLink);
114 // If this link has already been queried and we should notify, do so now.
115 switch (links->mStatus) {
116 case VisitedStatus::Unknown:
117 break;
118 case VisitedStatus::Unvisited:
119 [[fallthrough]];
120 case VisitedStatus::Visited:
121 aLink->VisitedQueryFinished(links->mStatus == VisitedStatus::Visited);
122 break;
126 void BaseHistory::UnregisterVisitedCallback(nsIURI* aURI, Link* aLink) {
127 MOZ_ASSERT(NS_IsMainThread());
128 MOZ_ASSERT(aURI, "Must pass a non-null URI!");
129 MOZ_ASSERT(aLink, "Must pass a non-null Link object!");
131 // Get the array, and remove the item from it.
132 auto entry = mTrackedURIs.Lookup(aURI);
133 if (!entry) {
134 MOZ_ASSERT(!CanStore(aURI),
135 "Trying to unregister URI that wasn't registered, "
136 "and that could be visited!");
137 return;
140 ObserverArray& observers = entry->mLinks;
141 if (!observers.RemoveElement(aLink)) {
142 MOZ_ASSERT_UNREACHABLE("Trying to unregister node that wasn't registered!");
143 return;
146 // If the array is now empty, we should remove it from the hashtable.
147 if (observers.IsEmpty()) {
148 entry.Remove();
149 CancelVisitedQueryIfPossible(aURI);
153 void BaseHistory::NotifyVisited(
154 nsIURI* aURI, VisitedStatus aStatus,
155 const ContentParentSet* aListOfProcessesToNotify) {
156 MOZ_ASSERT(NS_IsMainThread());
157 MOZ_ASSERT(aStatus != VisitedStatus::Unknown);
159 NotifyVisitedInThisProcess(aURI, aStatus);
160 if (XRE_IsParentProcess()) {
161 NotifyVisitedFromParent(aURI, aStatus, aListOfProcessesToNotify);
165 void BaseHistory::NotifyVisitedInThisProcess(nsIURI* aURI,
166 VisitedStatus aStatus) {
167 if (NS_WARN_IF(!aURI)) {
168 return;
171 auto entry = mTrackedURIs.Lookup(aURI);
172 if (!entry) {
173 // If we have no observers for this URI, we have nothing to notify about.
174 return;
177 ObservingLinks& links = entry.Data();
178 links.mStatus = aStatus;
180 // If we have a key, it should have at least one observer.
181 MOZ_ASSERT(!links.mLinks.IsEmpty());
183 // Dispatch an event to each document which has a Link observing this URL.
184 // These will fire asynchronously in the correct DocGroup.
186 const bool visited = aStatus == VisitedStatus::Visited;
187 for (Link* link : links.mLinks.BackwardRange()) {
188 link->VisitedQueryFinished(visited);
192 void BaseHistory::SendPendingVisitedResultsToChildProcesses() {
193 MOZ_ASSERT(!mPendingResults.IsEmpty());
195 mStartPendingResultsScheduled = false;
197 auto results = std::move(mPendingResults);
198 MOZ_ASSERT(mPendingResults.IsEmpty());
200 nsTArray<ContentParent*> cplist;
201 nsTArray<dom::VisitedQueryResult> resultsForProcess;
202 ContentParent::GetAll(cplist);
203 for (ContentParent* cp : cplist) {
204 resultsForProcess.ClearAndRetainStorage();
205 for (auto& result : results) {
206 if (result.mProcessesToNotify.IsEmpty() ||
207 result.mProcessesToNotify.Contains(cp)) {
208 resultsForProcess.AppendElement(result.mResult);
211 if (!resultsForProcess.IsEmpty()) {
212 Unused << NS_WARN_IF(!cp->SendNotifyVisited(resultsForProcess));
217 void BaseHistory::NotifyVisitedFromParent(
218 nsIURI* aURI, VisitedStatus aStatus,
219 const ContentParentSet* aListOfProcessesToNotify) {
220 MOZ_ASSERT(XRE_IsParentProcess());
222 if (aListOfProcessesToNotify && aListOfProcessesToNotify->IsEmpty()) {
223 return;
226 auto& result = *mPendingResults.AppendElement();
227 result.mResult.visited() = aStatus == VisitedStatus::Visited;
228 result.mResult.uri() = aURI;
229 if (aListOfProcessesToNotify) {
230 for (auto* entry : *aListOfProcessesToNotify) {
231 result.mProcessesToNotify.Insert(entry);
235 if (mStartPendingResultsScheduled) {
236 return;
239 mStartPendingResultsScheduled = NS_SUCCEEDED(NS_DispatchToMainThreadQueue(
240 NewRunnableMethod(
241 "BaseHistory::SendPendingVisitedResultsToChildProcesses", this,
242 &BaseHistory::SendPendingVisitedResultsToChildProcesses),
243 EventQueuePriority::Idle));
246 } // namespace mozilla