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
, "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
,
32 bool BaseHistory::CanStore(nsIURI
* aURI
) {
34 if (NS_WARN_IF(NS_FAILED(aURI
->GetScheme(scheme
)))) {
38 if (!scheme
.EqualsLiteral("http") && !scheme
.EqualsLiteral("https")) {
39 for (const nsLiteralCString
& disallowed
: kDisallowedSchemes
) {
40 if (scheme
.Equals(disallowed
)) {
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(); });
56 set
.Insert(aForProcess
);
59 if (mStartPendingVisitedQueriesScheduled
) {
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());
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);
91 // Obtain our array of observers for this URI.
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!");
97 ScheduleVisitedQuery(aURI
, nullptr);
100 return &entry
.OrInsertWith([] { return ObservingLinks
{}; });
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
:
118 case VisitedStatus::Unvisited
:
120 case VisitedStatus::Visited
:
121 aLink
->VisitedQueryFinished(links
->mStatus
== VisitedStatus::Visited
);
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
);
134 MOZ_ASSERT(!CanStore(aURI
),
135 "Trying to unregister URI that wasn't registered, "
136 "and that could be visited!");
140 ObserverArray
& observers
= entry
->mLinks
;
141 if (!observers
.RemoveElement(aLink
)) {
142 MOZ_ASSERT_UNREACHABLE("Trying to unregister node that wasn't registered!");
146 // If the array is now empty, we should remove it from the hashtable.
147 if (observers
.IsEmpty()) {
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
)) {
171 auto entry
= mTrackedURIs
.Lookup(aURI
);
173 // If we have no observers for this URI, we have nothing to notify about.
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()) {
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
) {
239 mStartPendingResultsScheduled
= NS_SUCCEEDED(NS_DispatchToMainThreadQueue(
241 "BaseHistory::SendPendingVisitedResultsToChildProcesses", this,
242 &BaseHistory::SendPendingVisitedResultsToChildProcesses
),
243 EventQueuePriority::Idle
));
246 } // namespace mozilla