Source code

Revision control

Other Tools

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/. */
6
7
#include "BaseHistory.h"
8
#include "mozilla/dom/Document.h"
9
#include "mozilla/dom/Link.h"
10
#include "mozilla/dom/Element.h"
11
12
namespace mozilla {
13
14
using mozilla::dom::Document;
15
using mozilla::dom::Element;
16
using mozilla::dom::Link;
17
18
static Document* GetLinkDocument(const Link& aLink) {
19
Element* element = aLink.GetElement();
20
// Element can only be null for mock_Link.
21
return element ? element->OwnerDoc() : nullptr;
22
}
23
24
void BaseHistory::DispatchNotifyVisited(nsIURI* aURI, dom::Document* aDoc,
25
VisitedStatus aStatus) {
26
MOZ_ASSERT(aStatus != VisitedStatus::Unknown);
27
28
nsCOMPtr<nsIRunnable> runnable =
29
NewRunnableMethod<nsCOMPtr<nsIURI>, RefPtr<dom::Document>, VisitedStatus>(
30
"BaseHistory::DispatchNotifyVisited", this,
31
&BaseHistory::NotifyVisitedForDocument, aURI, aDoc, aStatus);
32
if (aDoc) {
33
aDoc->Dispatch(TaskCategory::Other, runnable.forget());
34
} else {
35
NS_DispatchToMainThread(runnable.forget());
36
}
37
}
38
39
void BaseHistory::NotifyVisitedForDocument(nsIURI* aURI, dom::Document* aDoc,
40
VisitedStatus aStatus) {
41
MOZ_ASSERT(NS_IsMainThread());
42
MOZ_ASSERT(aStatus != VisitedStatus::Unknown);
43
44
// Make sure that nothing invalidates our observer array while we're walking
45
// over it.
46
nsAutoScriptBlocker scriptBlocker;
47
48
// If we have no observers for this URI, we have nothing to notify about.
49
auto entry = mTrackedURIs.Lookup(aURI);
50
if (!entry) {
51
return;
52
}
53
54
ObservingLinks& links = entry.Data();
55
56
const bool visited = aStatus == VisitedStatus::Visited;
57
{
58
// Update status of each Link node. We iterate over the array backwards so
59
// we can remove the items as we encounter them.
60
ObserverArray::BackwardIterator iter(links.mLinks);
61
while (iter.HasMore()) {
62
Link* link = iter.GetNext();
63
if (GetLinkDocument(*link) == aDoc) {
64
link->VisitedQueryFinished(visited);
65
if (visited) {
66
iter.Remove();
67
}
68
}
69
}
70
}
71
72
// If we don't have any links left, we can remove the array.
73
if (links.mLinks.IsEmpty()) {
74
entry.Remove();
75
}
76
}
77
78
void BaseHistory::ScheduleVisitedQuery(nsIURI* aURI) {
79
mPendingQueries.PutEntry(aURI);
80
if (mStartPendingVisitedQueriesScheduled) {
81
return;
82
}
83
mStartPendingVisitedQueriesScheduled =
84
NS_SUCCEEDED(NS_DispatchToMainThreadQueue(
85
NS_NewRunnableFunction(
86
"BaseHistory::StartPendingVisitedQueries",
87
[self = RefPtr<BaseHistory>(this)] {
88
self->mStartPendingVisitedQueriesScheduled = false;
89
auto queries = std::move(self->mPendingQueries);
90
self->StartPendingVisitedQueries(queries);
91
MOZ_DIAGNOSTIC_ASSERT(self->mPendingQueries.IsEmpty());
92
}),
93
EventQueuePriority::Idle));
94
}
95
96
void BaseHistory::CancelVisitedQueryIfPossible(nsIURI* aURI) {
97
mPendingQueries.RemoveEntry(aURI);
98
// TODO(bug 1591393): It could be worth to make this virtual and allow places
99
// to stop the existing database query? Needs some measurement.
100
}
101
102
nsresult BaseHistory::RegisterVisitedCallback(nsIURI* aURI, Link* aLink) {
103
MOZ_ASSERT(NS_IsMainThread());
104
MOZ_ASSERT(aURI, "Must pass a non-null URI!");
105
if (XRE_IsContentProcess()) {
106
MOZ_ASSERT(aLink, "Must pass a non-null Link!");
107
}
108
109
// Obtain our array of observers for this URI.
110
auto entry = mTrackedURIs.LookupForAdd(aURI);
111
MOZ_DIAGNOSTIC_ASSERT(!entry || !entry.Data().mLinks.IsEmpty(),
112
"An empty key was kept around in our hashtable!");
113
if (!entry) {
114
ScheduleVisitedQuery(aURI);
115
}
116
117
if (!aLink) {
118
// In IPC builds, we are passed a nullptr Link from
119
// ContentParent::RecvStartVisitedQuery. All of our code after this point
120
// assumes aLink is non-nullptr, so we have to return now.
121
MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess(),
122
"We should only ever get a null Link "
123
"in the parent process!");
124
// We don't want to remove if we're tracking other links.
125
if (!entry) {
126
entry.OrRemove();
127
}
128
return NS_OK;
129
}
130
131
ObservingLinks& links = entry.OrInsert([] { return ObservingLinks{}; });
132
133
// Sanity check that Links are not registered more than once for a given URI.
134
// This will not catch a case where it is registered for two different URIs.
135
MOZ_DIAGNOSTIC_ASSERT(!links.mLinks.Contains(aLink),
136
"Already tracking this Link object!");
137
138
// Start tracking our Link.
139
links.mLinks.AppendElement(aLink);
140
141
// If this link has already been visited, we cannot synchronously mark
142
// ourselves as visited, so instead we fire a runnable into our docgroup,
143
// which will handle it for us.
144
if (links.mStatus != VisitedStatus::Unknown) {
145
DispatchNotifyVisited(aURI, GetLinkDocument(*aLink), links.mStatus);
146
}
147
148
return NS_OK;
149
}
150
151
void BaseHistory::UnregisterVisitedCallback(nsIURI* aURI, Link* aLink) {
152
MOZ_ASSERT(NS_IsMainThread());
153
MOZ_ASSERT(aURI, "Must pass a non-null URI!");
154
MOZ_ASSERT(aLink, "Must pass a non-null Link object!");
155
156
// Get the array, and remove the item from it.
157
auto entry = mTrackedURIs.Lookup(aURI);
158
if (!entry) {
159
MOZ_ASSERT_UNREACHABLE("Trying to unregister URI that wasn't registered!");
160
return;
161
}
162
163
ObserverArray& observers = entry.Data().mLinks;
164
if (!observers.RemoveElement(aLink)) {
165
MOZ_ASSERT_UNREACHABLE("Trying to unregister node that wasn't registered!");
166
return;
167
}
168
169
// If the array is now empty, we should remove it from the hashtable.
170
if (observers.IsEmpty()) {
171
entry.Remove();
172
CancelVisitedQueryIfPossible(aURI);
173
}
174
}
175
176
void BaseHistory::NotifyVisited(nsIURI* aURI, VisitedStatus aStatus) {
177
MOZ_ASSERT(NS_IsMainThread());
178
MOZ_ASSERT(aStatus != VisitedStatus::Unknown);
179
if (NS_WARN_IF(!aURI)) {
180
return;
181
}
182
183
auto entry = mTrackedURIs.Lookup(aURI);
184
if (!entry) {
185
// If we have no observers for this URI, we have nothing to notify about.
186
return;
187
}
188
189
ObservingLinks& links = entry.Data();
190
links.mStatus = aStatus;
191
192
// If we have a key, it should have at least one observer.
193
MOZ_ASSERT(!links.mLinks.IsEmpty());
194
195
// Dispatch an event to each document which has a Link observing this URL.
196
// These will fire asynchronously in the correct DocGroup.
197
198
// TODO(bug 1591090): Maybe a hashtable for this? An array could be bad.
199
nsTArray<Document*> seen; // Don't dispatch duplicate runnables.
200
ObserverArray::BackwardIterator iter(links.mLinks);
201
while (iter.HasMore()) {
202
Link* link = iter.GetNext();
203
Document* doc = GetLinkDocument(*link);
204
if (seen.Contains(doc)) {
205
continue;
206
}
207
seen.AppendElement(doc);
208
DispatchNotifyVisited(aURI, doc, aStatus);
209
}
210
}
211
212
} // namespace mozilla