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!");
98 ScheduleVisitedQuery(aURI
, nullptr);
101 return &entry
.OrInsertWith([] { return ObservingLinks
{}; });
108 // Sanity check that Links are not registered more than once for a given URI.
109 // This will not catch a case where it is registered for two different URIs.
110 MOZ_DIAGNOSTIC_ASSERT(!links
->mLinks
.Contains(aLink
),
111 "Already tracking this Link object!");
113 links
->mLinks
.AppendElement(aLink
);
115 // If this link has already been queried and we should notify, do so now.
116 switch (links
->mStatus
) {
117 case VisitedStatus::Unknown
:
119 case VisitedStatus::Unvisited
:
121 case VisitedStatus::Visited
:
122 aLink
->VisitedQueryFinished(links
->mStatus
== VisitedStatus::Visited
);
127 void BaseHistory::UnregisterVisitedCallback(nsIURI
* aURI
, Link
* aLink
) {
128 MOZ_ASSERT(NS_IsMainThread());
129 MOZ_ASSERT(aURI
, "Must pass a non-null URI!");
130 MOZ_ASSERT(aLink
, "Must pass a non-null Link object!");
132 // Get the array, and remove the item from it.
133 auto entry
= mTrackedURIs
.Lookup(aURI
);
135 MOZ_ASSERT(!CanStore(aURI
),
136 "Trying to unregister URI that wasn't registered, "
137 "and that could be visited!");
141 ObserverArray
& observers
= entry
->mLinks
;
142 if (!observers
.RemoveElement(aLink
)) {
143 MOZ_ASSERT_UNREACHABLE("Trying to unregister node that wasn't registered!");
147 // If the array is now empty, we should remove it from the hashtable.
148 if (observers
.IsEmpty()) {
150 CancelVisitedQueryIfPossible(aURI
);
154 void BaseHistory::NotifyVisited(
155 nsIURI
* aURI
, VisitedStatus aStatus
,
156 const ContentParentSet
* aListOfProcessesToNotify
) {
157 MOZ_ASSERT(NS_IsMainThread());
158 MOZ_ASSERT(aStatus
!= VisitedStatus::Unknown
);
160 NotifyVisitedInThisProcess(aURI
, aStatus
);
161 if (XRE_IsParentProcess()) {
162 NotifyVisitedFromParent(aURI
, aStatus
, aListOfProcessesToNotify
);
166 void BaseHistory::NotifyVisitedInThisProcess(nsIURI
* aURI
,
167 VisitedStatus aStatus
) {
168 if (NS_WARN_IF(!aURI
)) {
172 auto entry
= mTrackedURIs
.Lookup(aURI
);
174 // If we have no observers for this URI, we have nothing to notify about.
178 ObservingLinks
& links
= entry
.Data();
179 links
.mStatus
= aStatus
;
181 // If we have a key, it should have at least one observer.
182 MOZ_ASSERT(!links
.mLinks
.IsEmpty());
184 // Dispatch an event to each document which has a Link observing this URL.
185 // These will fire asynchronously in the correct DocGroup.
187 const bool visited
= aStatus
== VisitedStatus::Visited
;
188 for (Link
* link
: links
.mLinks
.BackwardRange()) {
189 link
->VisitedQueryFinished(visited
);
193 void BaseHistory::SendPendingVisitedResultsToChildProcesses() {
194 MOZ_ASSERT(!mPendingResults
.IsEmpty());
196 mStartPendingResultsScheduled
= false;
198 auto results
= std::move(mPendingResults
);
199 MOZ_ASSERT(mPendingResults
.IsEmpty());
201 nsTArray
<ContentParent
*> cplist
;
202 nsTArray
<dom::VisitedQueryResult
> resultsForProcess
;
203 ContentParent::GetAll(cplist
);
204 for (ContentParent
* cp
: cplist
) {
205 resultsForProcess
.ClearAndRetainStorage();
206 for (auto& result
: results
) {
207 if (result
.mProcessesToNotify
.IsEmpty() ||
208 result
.mProcessesToNotify
.Contains(cp
)) {
209 resultsForProcess
.AppendElement(result
.mResult
);
212 if (!resultsForProcess
.IsEmpty()) {
213 Unused
<< NS_WARN_IF(!cp
->SendNotifyVisited(resultsForProcess
));
218 void BaseHistory::NotifyVisitedFromParent(
219 nsIURI
* aURI
, VisitedStatus aStatus
,
220 const ContentParentSet
* aListOfProcessesToNotify
) {
221 MOZ_ASSERT(XRE_IsParentProcess());
223 if (aListOfProcessesToNotify
&& aListOfProcessesToNotify
->IsEmpty()) {
227 auto& result
= *mPendingResults
.AppendElement();
228 result
.mResult
.visited() = aStatus
== VisitedStatus::Visited
;
229 result
.mResult
.uri() = aURI
;
230 if (aListOfProcessesToNotify
) {
231 for (auto* entry
: *aListOfProcessesToNotify
) {
232 result
.mProcessesToNotify
.Insert(entry
);
236 if (mStartPendingResultsScheduled
) {
240 mStartPendingResultsScheduled
= NS_SUCCEEDED(NS_DispatchToMainThreadQueue(
242 "BaseHistory::SendPendingVisitedResultsToChildProcesses", this,
243 &BaseHistory::SendPendingVisitedResultsToChildProcesses
),
244 EventQueuePriority::Idle
));
247 } // namespace mozilla