ChildSHistory.cpp (12847B)
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 "mozilla/dom/ChildSHistory.h" 8 #include "mozilla/dom/ChildSHistoryBinding.h" 9 #include "mozilla/dom/CanonicalBrowsingContext.h" 10 #include "mozilla/dom/ContentChild.h" 11 #include "mozilla/dom/ContentFrameMessageManager.h" 12 #include "mozilla/StaticPrefs_browser.h" 13 #include "nsIXULRuntime.h" 14 #include "nsComponentManagerUtils.h" 15 #include "nsSHEntry.h" 16 #include "nsSHistory.h" 17 #include "nsDocShell.h" 18 #include "nsXULAppAPI.h" 19 20 extern mozilla::LazyLogModule gSHLog; 21 22 namespace mozilla { 23 namespace dom { 24 25 ChildSHistory::PendingAsyncHistoryNavigation::PendingAsyncHistoryNavigation( 26 ChildSHistory* aHistory, int32_t aOffset, bool aRequireUserInteraction, 27 bool aUserActivation) 28 : Runnable("PendingAsyncHistoryNavigation"), 29 mHistory(aHistory), 30 mRequireUserInteraction(aRequireUserInteraction), 31 mUserActivation(aUserActivation), 32 mOffset(aOffset) {} 33 ChildSHistory::PendingAsyncHistoryNavigation::PendingAsyncHistoryNavigation( 34 ChildSHistory* aHistory, const nsID& aKey, 35 BrowsingContext* aBrowsingContext, bool aRequireUserInteraction, 36 bool aUserActivation, bool aCheckForCancelation, 37 std::function<void(nsresult)>&& aResolver) 38 : Runnable("PendingAsyncHistoryNavigation"), 39 mHistory(aHistory), 40 mRequireUserInteraction(aRequireUserInteraction), 41 mUserActivation(aUserActivation), 42 mCheckForCancelation(aCheckForCancelation), 43 mOffset(std::numeric_limits<int32_t>::max()), 44 mKey(Some(aKey)), 45 mBrowsingContext(aBrowsingContext), 46 mResolver(Some(aResolver)) {} 47 48 nsresult ChildSHistory::PendingAsyncHistoryNavigation::Run() { 49 if (isInList()) { 50 remove(); 51 if (mKey) { 52 RefPtr browsingContext = mBrowsingContext; 53 mHistory->GotoKey(*mKey, browsingContext, mRequireUserInteraction, 54 mUserActivation, mCheckForCancelation, *mResolver, 55 IgnoreErrors()); 56 } else { 57 mHistory->Go(mOffset, mRequireUserInteraction, mUserActivation, 58 IgnoreErrors()); 59 } 60 } 61 return NS_OK; 62 } 63 64 ChildSHistory::ChildSHistory(BrowsingContext* aBrowsingContext) 65 : mBrowsingContext(aBrowsingContext) {} 66 67 ChildSHistory::~ChildSHistory() { 68 if (mHistory) { 69 static_cast<nsSHistory*>(mHistory.get())->SetBrowsingContext(nullptr); 70 } 71 } 72 73 void ChildSHistory::SetBrowsingContext(BrowsingContext* aBrowsingContext) { 74 mBrowsingContext = aBrowsingContext; 75 } 76 77 void ChildSHistory::SetIsInProcess(bool aIsInProcess) { 78 if (!aIsInProcess) { 79 MOZ_ASSERT_IF(mozilla::SessionHistoryInParent(), !mHistory); 80 if (!mozilla::SessionHistoryInParent()) { 81 RemovePendingHistoryNavigations(); 82 if (mHistory) { 83 static_cast<nsSHistory*>(mHistory.get())->SetBrowsingContext(nullptr); 84 mHistory = nullptr; 85 } 86 } 87 88 return; 89 } 90 91 if (mHistory || mozilla::SessionHistoryInParent()) { 92 return; 93 } 94 95 mHistory = new nsSHistory(mBrowsingContext); 96 } 97 98 int32_t ChildSHistory::Count() { 99 if (mozilla::SessionHistoryInParent()) { 100 uint32_t length = mLength; 101 for (uint32_t i = 0; i < mPendingSHistoryChanges.Length(); ++i) { 102 length += mPendingSHistoryChanges[i].mLengthDelta; 103 } 104 105 return length; 106 } 107 return mHistory->GetCount(); 108 } 109 110 int32_t ChildSHistory::Index() { 111 if (mozilla::SessionHistoryInParent()) { 112 uint32_t index = mIndex; 113 for (uint32_t i = 0; i < mPendingSHistoryChanges.Length(); ++i) { 114 index += mPendingSHistoryChanges[i].mIndexDelta; 115 } 116 117 return index; 118 } 119 int32_t index; 120 mHistory->GetIndex(&index); 121 return index; 122 } 123 124 nsID ChildSHistory::AddPendingHistoryChange() { 125 int32_t indexDelta = 1; 126 int32_t lengthDelta = (Index() + indexDelta) - (Count() - 1); 127 return AddPendingHistoryChange(indexDelta, lengthDelta); 128 } 129 130 nsID ChildSHistory::AddPendingHistoryChange(int32_t aIndexDelta, 131 int32_t aLengthDelta) { 132 nsID changeID = nsID::GenerateUUID(); 133 PendingSHistoryChange change = {changeID, aIndexDelta, aLengthDelta}; 134 mPendingSHistoryChanges.AppendElement(change); 135 return changeID; 136 } 137 138 void ChildSHistory::SetIndexAndLength(uint32_t aIndex, uint32_t aLength, 139 const nsID& aChangeID) { 140 mIndex = aIndex; 141 mLength = aLength; 142 mPendingSHistoryChanges.RemoveElementsBy( 143 [aChangeID](const PendingSHistoryChange& aChange) { 144 return aChange.mChangeID == aChangeID; 145 }); 146 } 147 148 void ChildSHistory::Reload(uint32_t aReloadFlags, ErrorResult& aRv) { 149 if (mozilla::SessionHistoryInParent()) { 150 if (XRE_IsParentProcess()) { 151 nsCOMPtr<nsISHistory> shistory = 152 mBrowsingContext->Canonical()->GetSessionHistory(); 153 if (shistory) { 154 aRv = shistory->Reload(aReloadFlags); 155 } 156 } else { 157 ContentChild::GetSingleton()->SendHistoryReload(mBrowsingContext, 158 aReloadFlags); 159 } 160 161 return; 162 } 163 nsCOMPtr<nsISHistory> shistory = mHistory; 164 aRv = shistory->Reload(aReloadFlags); 165 } 166 167 bool ChildSHistory::CanGo(int32_t aOffset, bool aRequireUserInteraction) { 168 CheckedInt<int32_t> index = Index(); 169 index += aOffset; 170 if (!index.isValid()) { 171 return false; 172 } 173 174 if (!mHistory || aOffset >= 0) { 175 return index.value() < Count() && index.value() >= 0; 176 } 177 178 if (!aRequireUserInteraction) { 179 return index.value() >= 0; 180 } 181 182 bool canGoBack; 183 mHistory->CanGoBackFromEntryAtIndex(Index(), &canGoBack); 184 return canGoBack; 185 } 186 187 void ChildSHistory::Go(int32_t aOffset, bool aRequireUserInteraction, 188 bool aUserActivation, ErrorResult& aRv) { 189 CheckedInt<int32_t> index = Index(); 190 MOZ_LOG( 191 gSHLog, LogLevel::Debug, 192 ("ChildSHistory::Go(%d), current index = %d", aOffset, index.value())); 193 if (aRequireUserInteraction && aOffset != -1 && aOffset != 1) { 194 NS_ERROR( 195 "aRequireUserInteraction may only be used with an offset of -1 or 1"); 196 aRv.Throw(NS_ERROR_INVALID_ARG); 197 return; 198 } 199 200 while (true) { 201 index += aOffset; 202 if (!index.isValid()) { 203 aRv.Throw(NS_ERROR_FAILURE); 204 return; 205 } 206 207 // Check for user interaction if desired, except for the first and last 208 // history entries. We compare with >= to account for the case where 209 // aOffset >= Count(). 210 if (!StaticPrefs::browser_navigation_requireUserInteraction() || 211 !aRequireUserInteraction || index.value() >= Count() - 1 || 212 index.value() <= 0) { 213 break; 214 } 215 if (mHistory && mHistory->HasUserInteractionAtIndex(index.value())) { 216 break; 217 } 218 } 219 220 GotoIndex(index.value(), aOffset, aRequireUserInteraction, aUserActivation, 221 aRv); 222 } 223 224 void ChildSHistory::AsyncGo(int32_t aOffset, bool aRequireUserInteraction, 225 bool aUserActivation) { 226 CheckedInt<int32_t> index = Index(); 227 MOZ_LOG(gSHLog, LogLevel::Debug, 228 ("ChildSHistory::AsyncGo(%d), current index = %d", aOffset, 229 index.value())); 230 231 RefPtr<PendingAsyncHistoryNavigation> asyncNav = 232 new PendingAsyncHistoryNavigation(this, aOffset, aRequireUserInteraction, 233 aUserActivation); 234 mPendingNavigations.insertBack(asyncNav); 235 NS_DispatchToCurrentThread(asyncNav.forget()); 236 } 237 238 void ChildSHistory::AsyncGo(const nsID& aKey, BrowsingContext* aNavigable, 239 bool aRequireUserInteraction, bool aUserActivation, 240 bool aCheckForCancelation, 241 std::function<void(nsresult)>&& aResolver) { 242 CheckedInt<int32_t> index = Index(); 243 MOZ_LOG_FMT(gSHLog, LogLevel::Debug, 244 "ChildSHistory::AsyncGo({}), current index = {}", 245 aKey.ToString().get(), index.value()); 246 247 RefPtr<PendingAsyncHistoryNavigation> asyncNav = 248 new PendingAsyncHistoryNavigation( 249 this, aKey, aNavigable, aRequireUserInteraction, aUserActivation, 250 aCheckForCancelation, std::move(aResolver)); 251 mPendingNavigations.insertBack(asyncNav); 252 NS_DispatchToCurrentThread(asyncNav.forget()); 253 } 254 255 void ChildSHistory::GotoIndex(int32_t aIndex, int32_t aOffset, 256 bool aRequireUserInteraction, 257 bool aUserActivation, ErrorResult& aRv) { 258 MOZ_LOG(gSHLog, LogLevel::Debug, 259 ("ChildSHistory::GotoIndex(%d, %d), epoch %" PRIu64, aIndex, aOffset, 260 mHistoryEpoch)); 261 if (mozilla::SessionHistoryInParent()) { 262 if (!mPendingEpoch) { 263 mPendingEpoch = true; 264 RefPtr<ChildSHistory> self(this); 265 NS_DispatchToCurrentThread( 266 NS_NewRunnableFunction("UpdateEpochRunnable", [self] { 267 self->mHistoryEpoch++; 268 self->mPendingEpoch = false; 269 })); 270 } 271 272 nsCOMPtr<nsISHistory> shistory = mHistory; 273 RefPtr<BrowsingContext> bc = mBrowsingContext; 274 bc->HistoryGo( 275 aOffset, mHistoryEpoch, aRequireUserInteraction, aUserActivation, 276 [shistory](Maybe<int32_t>&& aRequestedIndex) { 277 // FIXME Should probably only do this for non-fission. 278 if (aRequestedIndex.isSome() && shistory) { 279 shistory->InternalSetRequestedIndex(aRequestedIndex.value()); 280 } 281 }); 282 } else { 283 nsCOMPtr<nsISHistory> shistory = mHistory; 284 aRv = shistory->GotoIndex(aIndex, aUserActivation); 285 } 286 } 287 288 void ChildSHistory::GotoKey(const nsID& aKey, BrowsingContext* aNavigable, 289 bool aRequireUserInteraction, bool aUserActivation, 290 bool aCheckForCancelation, 291 const std::function<void(nsresult)>& aResolver, 292 ErrorResult& aRv) { 293 MOZ_DIAGNOSTIC_ASSERT(mozilla::SessionHistoryInParent()); 294 295 if (!mPendingEpoch) { 296 mPendingEpoch = true; 297 RefPtr<ChildSHistory> self(this); 298 NS_DispatchToCurrentThread( 299 NS_NewRunnableFunction("UpdateEpochRunnable", [self] { 300 self->mHistoryEpoch++; 301 self->mPendingEpoch = false; 302 })); 303 } 304 305 nsCOMPtr<nsISHistory> shistory = mHistory; 306 aNavigable->NavigationTraverse( 307 aKey, mHistoryEpoch, aUserActivation, 308 /* aCheckForCancelation */ aCheckForCancelation, 309 [shistory, aResolver](nsresult aResult) { aResolver(aResult); }); 310 } 311 312 void ChildSHistory::RemovePendingHistoryNavigations() { 313 // Per the spec, this generally shouldn't remove all navigations - it 314 // depends if they're in the same document family or not. We don't do 315 // that. Also with SessionHistoryInParent, this can only abort AsyncGo's 316 // that have not yet been sent to the parent - see discussion of point 317 // 2.2 in comments in nsDocShell::UpdateURLAndHistory() 318 MOZ_LOG(gSHLog, LogLevel::Debug, 319 ("ChildSHistory::RemovePendingHistoryNavigations: %zu", 320 mPendingNavigations.length())); 321 mPendingNavigations.clear(); 322 } 323 324 void ChildSHistory::EvictLocalDocumentViewers() { 325 if (!mozilla::SessionHistoryInParent()) { 326 mHistory->EvictAllDocumentViewers(); 327 } 328 } 329 330 nsISHistory* ChildSHistory::GetLegacySHistory(ErrorResult& aError) { 331 if (mozilla::SessionHistoryInParent()) { 332 aError.ThrowTypeError( 333 "legacySHistory is not available with session history in the parent."); 334 return nullptr; 335 } 336 337 MOZ_RELEASE_ASSERT(mHistory); 338 return mHistory; 339 } 340 341 nsISHistory* ChildSHistory::LegacySHistory() { 342 IgnoredErrorResult ignore; 343 nsISHistory* shistory = GetLegacySHistory(ignore); 344 MOZ_RELEASE_ASSERT(shistory); 345 return shistory; 346 } 347 348 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ChildSHistory) 349 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY 350 NS_INTERFACE_MAP_ENTRY(nsISupports) 351 NS_INTERFACE_MAP_END 352 353 NS_IMPL_CYCLE_COLLECTING_ADDREF(ChildSHistory) 354 NS_IMPL_CYCLE_COLLECTING_RELEASE(ChildSHistory) 355 356 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(ChildSHistory) 357 358 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(ChildSHistory) 359 if (tmp->mHistory) { 360 static_cast<nsSHistory*>(tmp->mHistory.get())->SetBrowsingContext(nullptr); 361 } 362 NS_IMPL_CYCLE_COLLECTION_UNLINK(mBrowsingContext, mHistory) 363 NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER 364 NS_IMPL_CYCLE_COLLECTION_UNLINK_END 365 366 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(ChildSHistory) 367 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mBrowsingContext, mHistory) 368 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END 369 370 JSObject* ChildSHistory::WrapObject(JSContext* cx, 371 JS::Handle<JSObject*> aGivenProto) { 372 return ChildSHistory_Binding::Wrap(cx, this, aGivenProto); 373 } 374 375 nsISupports* ChildSHistory::GetParentObject() const { 376 return xpc::NativeGlobal(xpc::PrivilegedJunkScope()); 377 } 378 379 } // namespace dom 380 } // namespace mozilla