BaseHistory.cpp (8309B)
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 "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" 15 16 namespace mozilla { 17 18 using mozilla::dom::ContentParent; 19 using mozilla::dom::Link; 20 21 BaseHistory::BaseHistory() : mTrackedURIs(kTrackedUrisInitialSize) {} 22 23 BaseHistory::~BaseHistory() = default; 24 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, "moz-src"_ns, 31 "x-moz-ews"_ns, 32 }; 33 34 bool BaseHistory::CanStore(nsIURI* aURI) { 35 nsAutoCString scheme; 36 if (NS_WARN_IF(NS_FAILED(aURI->GetScheme(scheme)))) { 37 return false; 38 } 39 40 if (!scheme.EqualsLiteral("http") && !scheme.EqualsLiteral("https")) { 41 for (const nsLiteralCString& disallowed : kDisallowedSchemes) { 42 if (scheme.Equals(disallowed)) { 43 return false; 44 } 45 } 46 } 47 48 nsAutoCString spec; 49 aURI->GetSpec(spec); 50 return spec.Length() <= StaticPrefs::browser_history_maxUrlLength(); 51 } 52 53 void BaseHistory::ScheduleVisitedQuery(nsIURI* aURI, 54 dom::ContentParent* aForProcess) { 55 mPendingQueries.WithEntryHandle(aURI, [&](auto&& entry) { 56 auto& set = entry.OrInsertWith([] { return ContentParentSet(); }); 57 if (aForProcess) { 58 set.Insert(aForProcess); 59 } 60 }); 61 if (mStartPendingVisitedQueriesScheduled) { 62 return; 63 } 64 mStartPendingVisitedQueriesScheduled = 65 NS_SUCCEEDED(NS_DispatchToMainThreadQueue( 66 NS_NewRunnableFunction( 67 "BaseHistory::StartPendingVisitedQueries", 68 [self = RefPtr<BaseHistory>(this)] { 69 self->mStartPendingVisitedQueriesScheduled = false; 70 auto queries = std::move(self->mPendingQueries); 71 self->StartPendingVisitedQueries(std::move(queries)); 72 MOZ_DIAGNOSTIC_ASSERT(self->mPendingQueries.IsEmpty()); 73 }), 74 EventQueuePriority::Idle)); 75 } 76 77 void BaseHistory::CancelVisitedQueryIfPossible(nsIURI* aURI) { 78 mPendingQueries.Remove(aURI); 79 // TODO(bug 1591393): It could be worth to make this virtual and allow places 80 // to stop the existing database query? Needs some measurement. 81 } 82 83 void BaseHistory::RegisterVisitedCallback(nsIURI* aURI, Link* aLink) { 84 MOZ_ASSERT(NS_IsMainThread()); 85 MOZ_ASSERT(aURI, "Must pass a non-null URI!"); 86 MOZ_ASSERT(aLink, "Must pass a non-null Link!"); 87 88 if (!CanStore(aURI)) { 89 aLink->VisitedQueryFinished(/* visited = */ false); 90 return; 91 } 92 93 // Obtain our array of observers for this URI. 94 auto* const links = 95 mTrackedURIs.WithEntryHandle(aURI, [&](auto&& entry) -> ObservingLinks* { 96 MOZ_DIAGNOSTIC_ASSERT(!entry || !entry->mLinks.IsEmpty(), 97 "An empty key was kept around in our hashtable!"); 98 99 if (!entry) { 100 // If the URI has userpass, skip the visit query scheduling, because 101 // these URIs are not stored by history, and their status is only 102 // updated at the time of a visit. 103 bool hasUserPass; 104 if (NS_FAILED(aURI->GetHasUserPass(&hasUserPass)) || !hasUserPass) { 105 ScheduleVisitedQuery(aURI, nullptr); 106 } 107 } 108 109 return &entry.OrInsertWith([] { return ObservingLinks{}; }); 110 }); 111 112 if (!links) { 113 return; 114 } 115 116 // Sanity check that Links are not registered more than once for a given URI. 117 // This will not catch a case where it is registered for two different URIs. 118 MOZ_DIAGNOSTIC_ASSERT(!links->mLinks.Contains(aLink), 119 "Already tracking this Link object!"); 120 121 links->mLinks.AppendElement(aLink); 122 123 // If this link has already been queried and we should notify, do so now. 124 switch (links->mStatus) { 125 case VisitedStatus::Unknown: 126 break; 127 case VisitedStatus::Unvisited: 128 [[fallthrough]]; 129 case VisitedStatus::Visited: 130 aLink->VisitedQueryFinished(links->mStatus == VisitedStatus::Visited); 131 break; 132 } 133 } 134 135 void BaseHistory::UnregisterVisitedCallback(nsIURI* aURI, Link* aLink) { 136 MOZ_ASSERT(NS_IsMainThread()); 137 MOZ_ASSERT(aURI, "Must pass a non-null URI!"); 138 MOZ_ASSERT(aLink, "Must pass a non-null Link object!"); 139 140 // Get the array, and remove the item from it. 141 auto entry = mTrackedURIs.Lookup(aURI); 142 if (!entry) { 143 MOZ_ASSERT(!CanStore(aURI), 144 "Trying to unregister URI that wasn't registered, " 145 "and that could be visited!"); 146 return; 147 } 148 149 ObserverArray& observers = entry->mLinks; 150 if (!observers.RemoveElement(aLink)) { 151 MOZ_ASSERT_UNREACHABLE("Trying to unregister node that wasn't registered!"); 152 return; 153 } 154 155 // If the array is now empty, we should remove it from the hashtable. 156 if (observers.IsEmpty()) { 157 entry.Remove(); 158 CancelVisitedQueryIfPossible(aURI); 159 } 160 } 161 162 void BaseHistory::NotifyVisited( 163 nsIURI* aURI, VisitedStatus aStatus, 164 const ContentParentSet* aListOfProcessesToNotify) { 165 MOZ_ASSERT(NS_IsMainThread()); 166 MOZ_ASSERT(aStatus != VisitedStatus::Unknown); 167 168 NotifyVisitedInThisProcess(aURI, aStatus); 169 if (XRE_IsParentProcess()) { 170 NotifyVisitedFromParent(aURI, aStatus, aListOfProcessesToNotify); 171 } 172 } 173 174 void BaseHistory::NotifyVisitedInThisProcess(nsIURI* aURI, 175 VisitedStatus aStatus) { 176 if (NS_WARN_IF(!aURI)) { 177 return; 178 } 179 180 auto entry = mTrackedURIs.Lookup(aURI); 181 if (!entry) { 182 // If we have no observers for this URI, we have nothing to notify about. 183 return; 184 } 185 186 ObservingLinks& links = entry.Data(); 187 links.mStatus = aStatus; 188 189 // If we have a key, it should have at least one observer. 190 MOZ_ASSERT(!links.mLinks.IsEmpty()); 191 192 // Dispatch an event to each document which has a Link observing this URL. 193 // These will fire asynchronously in the correct DocGroup. 194 195 const bool visited = aStatus == VisitedStatus::Visited; 196 for (Link* link : links.mLinks.BackwardRange()) { 197 link->VisitedQueryFinished(visited); 198 } 199 } 200 201 void BaseHistory::SendPendingVisitedResultsToChildProcesses() { 202 MOZ_ASSERT(!mPendingResults.IsEmpty()); 203 204 mStartPendingResultsScheduled = false; 205 206 auto results = std::move(mPendingResults); 207 MOZ_ASSERT(mPendingResults.IsEmpty()); 208 209 nsTArray<ContentParent*> cplist; 210 nsTArray<dom::VisitedQueryResult> resultsForProcess; 211 ContentParent::GetAll(cplist); 212 for (ContentParent* cp : cplist) { 213 resultsForProcess.ClearAndRetainStorage(); 214 for (auto& result : results) { 215 if (result.mProcessesToNotify.IsEmpty() || 216 result.mProcessesToNotify.Contains(cp)) { 217 resultsForProcess.AppendElement(result.mResult); 218 } 219 } 220 if (!resultsForProcess.IsEmpty()) { 221 (void)NS_WARN_IF(!cp->SendNotifyVisited(resultsForProcess)); 222 } 223 } 224 } 225 226 void BaseHistory::NotifyVisitedFromParent( 227 nsIURI* aURI, VisitedStatus aStatus, 228 const ContentParentSet* aListOfProcessesToNotify) { 229 MOZ_ASSERT(XRE_IsParentProcess()); 230 231 if (aListOfProcessesToNotify && aListOfProcessesToNotify->IsEmpty()) { 232 return; 233 } 234 235 auto& result = *mPendingResults.AppendElement(); 236 result.mResult.visited() = aStatus == VisitedStatus::Visited; 237 result.mResult.uri() = aURI; 238 if (aListOfProcessesToNotify) { 239 for (auto* entry : *aListOfProcessesToNotify) { 240 result.mProcessesToNotify.Insert(entry); 241 } 242 } 243 244 if (mStartPendingResultsScheduled) { 245 return; 246 } 247 248 mStartPendingResultsScheduled = NS_SUCCEEDED(NS_DispatchToMainThreadQueue( 249 NewRunnableMethod( 250 "BaseHistory::SendPendingVisitedResultsToChildProcesses", this, 251 &BaseHistory::SendPendingVisitedResultsToChildProcesses), 252 EventQueuePriority::Idle)); 253 } 254 255 } // namespace mozilla