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"
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
, "cached-favicon"_ns
,
27 "chrome"_ns
, "data"_ns
, "imap"_ns
,
28 "javascript"_ns
, "mailbox"_ns
, "news"_ns
,
29 "page-icon"_ns
, "resource"_ns
, "view-source"_ns
,
30 "moz-extension"_ns
, "moz-page-thumb"_ns
,
33 bool BaseHistory::CanStore(nsIURI
* aURI
) {
35 if (NS_WARN_IF(NS_FAILED(aURI
->GetScheme(scheme
)))) {
39 if (!scheme
.EqualsLiteral("http") && !scheme
.EqualsLiteral("https")) {
40 for (const nsLiteralCString
& disallowed
: kDisallowedSchemes
) {
41 if (scheme
.Equals(disallowed
)) {
49 return spec
.Length() <= StaticPrefs::browser_history_maxUrlLength();
52 void BaseHistory::ScheduleVisitedQuery(nsIURI
* aURI
,
53 dom::ContentParent
* aForProcess
) {
54 mPendingQueries
.WithEntryHandle(aURI
, [&](auto&& entry
) {
55 auto& set
= entry
.OrInsertWith([] { return ContentParentSet(); });
57 set
.Insert(aForProcess
);
60 if (mStartPendingVisitedQueriesScheduled
) {
63 mStartPendingVisitedQueriesScheduled
=
64 NS_SUCCEEDED(NS_DispatchToMainThreadQueue(
65 NS_NewRunnableFunction(
66 "BaseHistory::StartPendingVisitedQueries",
67 [self
= RefPtr
<BaseHistory
>(this)] {
68 self
->mStartPendingVisitedQueriesScheduled
= false;
69 auto queries
= std::move(self
->mPendingQueries
);
70 self
->StartPendingVisitedQueries(std::move(queries
));
71 MOZ_DIAGNOSTIC_ASSERT(self
->mPendingQueries
.IsEmpty());
73 EventQueuePriority::Idle
));
76 void BaseHistory::CancelVisitedQueryIfPossible(nsIURI
* aURI
) {
77 mPendingQueries
.Remove(aURI
);
78 // TODO(bug 1591393): It could be worth to make this virtual and allow places
79 // to stop the existing database query? Needs some measurement.
82 void BaseHistory::RegisterVisitedCallback(nsIURI
* aURI
, Link
* aLink
) {
83 MOZ_ASSERT(NS_IsMainThread());
84 MOZ_ASSERT(aURI
, "Must pass a non-null URI!");
85 MOZ_ASSERT(aLink
, "Must pass a non-null Link!");
87 if (!CanStore(aURI
)) {
88 aLink
->VisitedQueryFinished(/* visited = */ false);
92 // Obtain our array of observers for this URI.
94 mTrackedURIs
.WithEntryHandle(aURI
, [&](auto&& entry
) -> ObservingLinks
* {
95 MOZ_DIAGNOSTIC_ASSERT(!entry
|| !entry
->mLinks
.IsEmpty(),
96 "An empty key was kept around in our hashtable!");
99 // If the URI has userpass, skip the visit query scheduling, because
100 // these URIs are not stored by history, and their status is only
101 // updated at the time of a visit.
103 if (NS_FAILED(aURI
->GetHasUserPass(&hasUserPass
)) || !hasUserPass
) {
104 ScheduleVisitedQuery(aURI
, nullptr);
108 return &entry
.OrInsertWith([] { return ObservingLinks
{}; });
115 // Sanity check that Links are not registered more than once for a given URI.
116 // This will not catch a case where it is registered for two different URIs.
117 MOZ_DIAGNOSTIC_ASSERT(!links
->mLinks
.Contains(aLink
),
118 "Already tracking this Link object!");
120 links
->mLinks
.AppendElement(aLink
);
122 // If this link has already been queried and we should notify, do so now.
123 switch (links
->mStatus
) {
124 case VisitedStatus::Unknown
:
126 case VisitedStatus::Unvisited
:
128 case VisitedStatus::Visited
:
129 aLink
->VisitedQueryFinished(links
->mStatus
== VisitedStatus::Visited
);
134 void BaseHistory::UnregisterVisitedCallback(nsIURI
* aURI
, Link
* aLink
) {
135 MOZ_ASSERT(NS_IsMainThread());
136 MOZ_ASSERT(aURI
, "Must pass a non-null URI!");
137 MOZ_ASSERT(aLink
, "Must pass a non-null Link object!");
139 // Get the array, and remove the item from it.
140 auto entry
= mTrackedURIs
.Lookup(aURI
);
142 MOZ_ASSERT(!CanStore(aURI
),
143 "Trying to unregister URI that wasn't registered, "
144 "and that could be visited!");
148 ObserverArray
& observers
= entry
->mLinks
;
149 if (!observers
.RemoveElement(aLink
)) {
150 MOZ_ASSERT_UNREACHABLE("Trying to unregister node that wasn't registered!");
154 // If the array is now empty, we should remove it from the hashtable.
155 if (observers
.IsEmpty()) {
157 CancelVisitedQueryIfPossible(aURI
);
161 void BaseHistory::NotifyVisited(
162 nsIURI
* aURI
, VisitedStatus aStatus
,
163 const ContentParentSet
* aListOfProcessesToNotify
) {
164 MOZ_ASSERT(NS_IsMainThread());
165 MOZ_ASSERT(aStatus
!= VisitedStatus::Unknown
);
167 NotifyVisitedInThisProcess(aURI
, aStatus
);
168 if (XRE_IsParentProcess()) {
169 NotifyVisitedFromParent(aURI
, aStatus
, aListOfProcessesToNotify
);
173 void BaseHistory::NotifyVisitedInThisProcess(nsIURI
* aURI
,
174 VisitedStatus aStatus
) {
175 if (NS_WARN_IF(!aURI
)) {
179 auto entry
= mTrackedURIs
.Lookup(aURI
);
181 // If we have no observers for this URI, we have nothing to notify about.
185 ObservingLinks
& links
= entry
.Data();
186 links
.mStatus
= aStatus
;
188 // If we have a key, it should have at least one observer.
189 MOZ_ASSERT(!links
.mLinks
.IsEmpty());
191 // Dispatch an event to each document which has a Link observing this URL.
192 // These will fire asynchronously in the correct DocGroup.
194 const bool visited
= aStatus
== VisitedStatus::Visited
;
195 for (Link
* link
: links
.mLinks
.BackwardRange()) {
196 link
->VisitedQueryFinished(visited
);
200 void BaseHistory::SendPendingVisitedResultsToChildProcesses() {
201 MOZ_ASSERT(!mPendingResults
.IsEmpty());
203 mStartPendingResultsScheduled
= false;
205 auto results
= std::move(mPendingResults
);
206 MOZ_ASSERT(mPendingResults
.IsEmpty());
208 nsTArray
<ContentParent
*> cplist
;
209 nsTArray
<dom::VisitedQueryResult
> resultsForProcess
;
210 ContentParent::GetAll(cplist
);
211 for (ContentParent
* cp
: cplist
) {
212 resultsForProcess
.ClearAndRetainStorage();
213 for (auto& result
: results
) {
214 if (result
.mProcessesToNotify
.IsEmpty() ||
215 result
.mProcessesToNotify
.Contains(cp
)) {
216 resultsForProcess
.AppendElement(result
.mResult
);
219 if (!resultsForProcess
.IsEmpty()) {
220 Unused
<< NS_WARN_IF(!cp
->SendNotifyVisited(resultsForProcess
));
225 void BaseHistory::NotifyVisitedFromParent(
226 nsIURI
* aURI
, VisitedStatus aStatus
,
227 const ContentParentSet
* aListOfProcessesToNotify
) {
228 MOZ_ASSERT(XRE_IsParentProcess());
230 if (aListOfProcessesToNotify
&& aListOfProcessesToNotify
->IsEmpty()) {
234 auto& result
= *mPendingResults
.AppendElement();
235 result
.mResult
.visited() = aStatus
== VisitedStatus::Visited
;
236 result
.mResult
.uri() = aURI
;
237 if (aListOfProcessesToNotify
) {
238 for (auto* entry
: *aListOfProcessesToNotify
) {
239 result
.mProcessesToNotify
.Insert(entry
);
243 if (mStartPendingResultsScheduled
) {
247 mStartPendingResultsScheduled
= NS_SUCCEEDED(NS_DispatchToMainThreadQueue(
249 "BaseHistory::SendPendingVisitedResultsToChildProcesses", this,
250 &BaseHistory::SendPendingVisitedResultsToChildProcesses
),
251 EventQueuePriority::Idle
));
254 } // namespace mozilla