nsFrameLoaderOwner.cpp (15041B)
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 "nsFrameLoaderOwner.h" 8 9 #include "mozilla/AsyncEventDispatcher.h" 10 #include "mozilla/EventStateManager.h" 11 #include "mozilla/Logging.h" 12 #include "mozilla/ScopeExit.h" 13 #include "mozilla/StaticPrefs_fission.h" 14 #include "mozilla/dom/BrowserBridgeChild.h" 15 #include "mozilla/dom/BrowserBridgeHost.h" 16 #include "mozilla/dom/BrowserHost.h" 17 #include "mozilla/dom/BrowserParent.h" 18 #include "mozilla/dom/BrowsingContext.h" 19 #include "mozilla/dom/CanonicalBrowsingContext.h" 20 #include "mozilla/dom/ContentParent.h" 21 #include "mozilla/dom/FrameLoaderBinding.h" 22 #include "mozilla/dom/HTMLIFrameElement.h" 23 #include "mozilla/dom/MozFrameLoaderOwnerBinding.h" 24 #include "nsFocusManager.h" 25 #include "nsFrameLoader.h" 26 #include "nsNetUtil.h" 27 #include "nsQueryObject.h" 28 #include "nsSubDocumentFrame.h" 29 30 extern mozilla::LazyLogModule gSHIPBFCacheLog; 31 32 using namespace mozilla; 33 using namespace mozilla::dom; 34 35 already_AddRefed<nsFrameLoader> nsFrameLoaderOwner::GetFrameLoader() { 36 return do_AddRef(mFrameLoader); 37 } 38 39 void nsFrameLoaderOwner::SetFrameLoader(nsFrameLoader* aNewFrameLoader) { 40 mFrameLoader = aNewFrameLoader; 41 } 42 43 mozilla::dom::BrowsingContext* nsFrameLoaderOwner::GetBrowsingContext() { 44 if (mFrameLoader) { 45 return mFrameLoader->GetBrowsingContext(); 46 } 47 return nullptr; 48 } 49 50 mozilla::dom::BrowsingContext* nsFrameLoaderOwner::GetExtantBrowsingContext() { 51 if (mFrameLoader) { 52 return mFrameLoader->GetExtantBrowsingContext(); 53 } 54 return nullptr; 55 } 56 57 bool nsFrameLoaderOwner::UseRemoteSubframes() { 58 RefPtr<Element> owner = do_QueryObject(this); 59 60 nsILoadContext* loadContext = owner->OwnerDoc()->GetLoadContext(); 61 MOZ_DIAGNOSTIC_ASSERT(loadContext); 62 63 return loadContext->UseRemoteSubframes(); 64 } 65 66 nsFrameLoaderOwner::ChangeRemotenessContextType 67 nsFrameLoaderOwner::ShouldPreserveBrowsingContext( 68 bool aIsRemote, bool aReplaceBrowsingContext) { 69 if (aReplaceBrowsingContext) { 70 return ChangeRemotenessContextType::DONT_PRESERVE; 71 } 72 73 if (XRE_IsParentProcess()) { 74 // Don't preserve for remote => parent loads. 75 if (!aIsRemote) { 76 return ChangeRemotenessContextType::DONT_PRESERVE; 77 } 78 79 // Don't preserve for parent => remote loads. 80 if (mFrameLoader && !mFrameLoader->IsRemoteFrame()) { 81 return ChangeRemotenessContextType::DONT_PRESERVE; 82 } 83 } 84 85 return ChangeRemotenessContextType::PRESERVE; 86 } 87 88 void nsFrameLoaderOwner::ChangeRemotenessCommon( 89 const ChangeRemotenessContextType& aContextType, 90 const NavigationIsolationOptions& aOptions, bool aSwitchingInProgressLoad, 91 bool aIsRemote, BrowsingContextGroup* aGroup, 92 std::function<void()>& aFrameLoaderInit, mozilla::ErrorResult& aRv) { 93 MOZ_ASSERT_IF(aGroup, aContextType != ChangeRemotenessContextType::PRESERVE); 94 95 RefPtr<mozilla::dom::BrowsingContext> bc; 96 bool networkCreated = false; 97 98 // In this case, we're not reparenting a frameloader, we're just destroying 99 // our current one and creating a new one, so we can use ourselves as the 100 // owner. 101 RefPtr<Element> owner = do_QueryObject(this); 102 MOZ_ASSERT(owner); 103 104 // When we destroy the original frameloader, it will stop blocking the parent 105 // document's load event, and immediately trigger the load event if there are 106 // no other blockers. Since we're going to be adding a new blocker as soon as 107 // we recreate the frame loader, this is not what we want, so add our own 108 // blocker until the process is complete. 109 Document* doc = owner->OwnerDoc(); 110 doc->BlockOnload(); 111 auto cleanup = MakeScopeExit([&]() { doc->UnblockOnload(false); }); 112 113 // If we store the previous nsFrameLoader in the bfcache, this will be filled 114 // with the SessionHistoryEntry which now owns the frame. 115 RefPtr<SessionHistoryEntry> bfcacheEntry; 116 117 { 118 // Introduce a script blocker to ensure no JS is executed during the 119 // nsFrameLoader teardown & recreation process. Unload listeners will be run 120 // for the previous document, and the load will be started for the new one, 121 // at the end of this block. 122 nsAutoScriptBlocker sb; 123 124 // If we already have a Frameloader, destroy it, possibly preserving its 125 // browsing context. 126 if (mFrameLoader) { 127 // Calling `GetBrowsingContext` here will force frameloader 128 // initialization if it hasn't already happened, which we neither need 129 // or want, so we use the initial (possibly pending) browsing context 130 // directly, instead. 131 bc = mFrameLoader->GetMaybePendingBrowsingContext(); 132 133 if (nsFocusManager* fm = nsFocusManager::GetFocusManager()) { 134 fm->FixUpFocusBeforeFrameLoaderChange(*owner, bc); 135 } 136 137 networkCreated = mFrameLoader->IsNetworkCreated(); 138 139 MOZ_ASSERT_IF(aOptions.mTryUseBFCache, aOptions.mReplaceBrowsingContext); 140 if (aOptions.mTryUseBFCache && bc) { 141 bfcacheEntry = bc->Canonical()->GetActiveSessionHistoryEntry(); 142 bool useBFCache = bfcacheEntry && 143 bfcacheEntry == aOptions.mActiveSessionHistoryEntry && 144 !bfcacheEntry->GetFrameLoader(); 145 if (useBFCache) { 146 MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug, 147 ("nsFrameLoaderOwner::ChangeRemotenessCommon: store the old " 148 "page in bfcache")); 149 (void)bc->SetIsInBFCache(true); 150 bfcacheEntry->SetFrameLoader(mFrameLoader); 151 // Session history owns now the frameloader. 152 mFrameLoader = nullptr; 153 } 154 } 155 156 if (mFrameLoader) { 157 if (aContextType == ChangeRemotenessContextType::PRESERVE) { 158 mFrameLoader->SetWillChangeProcess(); 159 } 160 161 // Preserve the networkCreated status, as nsDocShells created after a 162 // process swap may shouldn't change their dynamically-created status. 163 mFrameLoader->Destroy(aSwitchingInProgressLoad); 164 mFrameLoader = nullptr; 165 } 166 } 167 168 mFrameLoader = nsFrameLoader::Recreate( 169 owner, bc, aGroup, aOptions, aIsRemote, networkCreated, 170 aContextType == ChangeRemotenessContextType::PRESERVE); 171 if (NS_WARN_IF(!mFrameLoader)) { 172 aRv.Throw(NS_ERROR_FAILURE); 173 return; 174 } 175 176 // Invoke the frame loader initialization callback to perform setup on our 177 // new nsFrameLoader. This may cause our ErrorResult to become errored, so 178 // double-check after calling. 179 aFrameLoaderInit(); 180 if (NS_WARN_IF(aRv.Failed())) { 181 return; 182 } 183 } 184 185 // Now that we have a new FrameLoader, we'll eventually need to reset 186 // nsSubDocumentFrame to use the new one. We can delay doing this if we're 187 // keeping our old frameloader around in the BFCache and the new frame hasn't 188 // presented yet to continue painting the previous document. 189 const bool retainPaint = bfcacheEntry && mFrameLoader->IsRemoteFrame(); 190 if (!retainPaint) { 191 MOZ_LOG( 192 gSHIPBFCacheLog, LogLevel::Debug, 193 ("Previous frameLoader not entering BFCache - not retaining paint data" 194 "(bfcacheEntry=%p, isRemoteFrame=%d)", 195 bfcacheEntry.get(), mFrameLoader->IsRemoteFrame())); 196 } 197 198 ChangeFrameLoaderCommon(owner, retainPaint); 199 200 UpdateFocusAndMouseEnterStateAfterFrameLoaderChange(owner); 201 } 202 203 void nsFrameLoaderOwner::ChangeFrameLoaderCommon(Element* aOwner, 204 bool aRetainPaint) { 205 // Now that we've got a new FrameLoader, we need to reset our 206 // nsSubDocumentFrame to use the new FrameLoader. 207 if (nsSubDocumentFrame* ourFrame = do_QueryFrame(aOwner->GetPrimaryFrame())) { 208 auto retain = aRetainPaint ? nsSubDocumentFrame::RetainPaintData::Yes 209 : nsSubDocumentFrame::RetainPaintData::No; 210 ourFrame->ResetFrameLoader(retain); 211 } 212 213 if (aOwner->IsXULElement()) { 214 // Assuming this element is a XULFrameElement, once we've reset our 215 // FrameLoader, fire an event to act like we've recreated ourselves, similar 216 // to what XULFrameElement does after rebinding to the tree. 217 // ChromeOnlyDispatch is turns on to make sure this isn't fired into 218 // content. 219 mozilla::AsyncEventDispatcher::RunDOMEventWhenSafe( 220 *aOwner, u"XULFrameLoaderCreated"_ns, mozilla::CanBubble::eYes, 221 mozilla::ChromeOnlyDispatch::eYes); 222 } 223 224 if (mFrameLoader) { 225 mFrameLoader->PropagateIsUnderHiddenEmbedderElement( 226 !aOwner->GetPrimaryFrame() || 227 !aOwner->GetPrimaryFrame()->StyleVisibility()->IsVisible()); 228 } 229 } 230 231 void nsFrameLoaderOwner::UpdateFocusAndMouseEnterStateAfterFrameLoaderChange() { 232 RefPtr<Element> owner = do_QueryObject(this); 233 UpdateFocusAndMouseEnterStateAfterFrameLoaderChange(owner); 234 } 235 236 void nsFrameLoaderOwner::UpdateFocusAndMouseEnterStateAfterFrameLoaderChange( 237 Element* aOwner) { 238 // If the element is focused, or the current mouse over target then 239 // we need to update that state for the new BrowserParent too. 240 if (RefPtr<nsFocusManager> fm = nsFocusManager::GetFocusManager()) { 241 if (fm->GetFocusedElement() == aOwner) { 242 fm->FixUpFocusAfterFrameLoaderChange(*aOwner); 243 } 244 } 245 246 if (aOwner->GetPrimaryFrame()) { 247 EventStateManager* eventManager = 248 aOwner->GetPrimaryFrame()->PresContext()->EventStateManager(); 249 eventManager->RecomputeMouseEnterStateForRemoteFrame(*aOwner); 250 } 251 } 252 253 void nsFrameLoaderOwner::ChangeRemoteness( 254 const mozilla::dom::RemotenessOptions& aOptions, mozilla::ErrorResult& rv) { 255 bool isRemote = !aOptions.mRemoteType.IsEmpty(); 256 257 MOZ_RELEASE_ASSERT(mFrameLoader, "Expecting to have mFrameLoader here."); 258 std::function<void()> frameLoaderInit = [&] { 259 MOZ_RELEASE_ASSERT(mFrameLoader, 260 "Expecting still to have mFrameLoader here."); 261 if (isRemote) { 262 mFrameLoader->ConfigRemoteProcess(aOptions.mRemoteType, nullptr); 263 } 264 265 if (aOptions.mPendingSwitchID.WasPassed()) { 266 mFrameLoader->ResumeLoad(aOptions.mPendingSwitchID.Value()); 267 } else { 268 mFrameLoader->LoadFrame(/* aOriginalSrc */ false, 269 /* aShouldCheckForRecursion */ false); 270 } 271 }; 272 273 auto shouldPreserve = ShouldPreserveBrowsingContext( 274 isRemote, /* replaceBrowsingContext */ false); 275 NavigationIsolationOptions options; 276 ChangeRemotenessCommon(shouldPreserve, options, 277 aOptions.mSwitchingInProgressLoad, isRemote, 278 /* group */ nullptr, frameLoaderInit, rv); 279 } 280 281 void nsFrameLoaderOwner::ChangeRemotenessWithBridge(BrowserBridgeChild* aBridge, 282 mozilla::ErrorResult& rv) { 283 MOZ_ASSERT(XRE_IsContentProcess()); 284 if (NS_WARN_IF(!mFrameLoader)) { 285 rv.Throw(NS_ERROR_UNEXPECTED); 286 return; 287 } 288 289 std::function<void()> frameLoaderInit = [&] { 290 MOZ_DIAGNOSTIC_ASSERT(!mFrameLoader->mInitialized); 291 RefPtr<BrowserBridgeHost> host = aBridge->FinishInit(mFrameLoader); 292 mFrameLoader->mPendingBrowsingContext->SetEmbedderElement( 293 mFrameLoader->GetOwnerContent()); 294 mFrameLoader->mRemoteBrowser = host; 295 mFrameLoader->mInitialized = true; 296 }; 297 298 NavigationIsolationOptions options; 299 ChangeRemotenessCommon(ChangeRemotenessContextType::PRESERVE, options, 300 /* inProgress */ true, 301 /* isRemote */ true, /* group */ nullptr, 302 frameLoaderInit, rv); 303 } 304 305 void nsFrameLoaderOwner::ChangeRemotenessToProcess( 306 ContentParent* aContentParent, const NavigationIsolationOptions& aOptions, 307 BrowsingContextGroup* aGroup, mozilla::ErrorResult& rv) { 308 MOZ_ASSERT(XRE_IsParentProcess()); 309 MOZ_ASSERT_IF(aGroup, aOptions.mReplaceBrowsingContext); 310 bool isRemote = aContentParent != nullptr; 311 312 std::function<void()> frameLoaderInit = [&] { 313 if (isRemote) { 314 mFrameLoader->ConfigRemoteProcess(aContentParent->GetRemoteType(), 315 aContentParent); 316 } 317 }; 318 319 auto shouldPreserve = 320 ShouldPreserveBrowsingContext(isRemote, aOptions.mReplaceBrowsingContext); 321 ChangeRemotenessCommon(shouldPreserve, aOptions, /* inProgress */ true, 322 isRemote, aGroup, frameLoaderInit, rv); 323 } 324 325 void nsFrameLoaderOwner::SubframeCrashed() { 326 MOZ_ASSERT(XRE_IsContentProcess()); 327 328 std::function<void()> frameLoaderInit = [&] { 329 RefPtr<nsFrameLoader> frameLoader = mFrameLoader; 330 nsContentUtils::AddScriptRunner(NS_NewRunnableFunction( 331 "nsFrameLoaderOwner::SubframeCrashed", [frameLoader]() { 332 nsCOMPtr<nsIURI> uri; 333 nsresult rv = NS_NewURI(getter_AddRefs(uri), "about:blank"); 334 if (NS_WARN_IF(NS_FAILED(rv))) { 335 return; 336 } 337 338 RefPtr<nsDocShell> docShell = 339 frameLoader->GetDocShell(IgnoreErrors()); 340 if (NS_WARN_IF(!docShell)) { 341 return; 342 } 343 bool displayed = false; 344 docShell->DisplayLoadError(NS_ERROR_FRAME_CRASHED, uri, 345 u"about:blank", nullptr, &displayed); 346 })); 347 }; 348 349 NavigationIsolationOptions options; 350 ChangeRemotenessCommon(ChangeRemotenessContextType::PRESERVE, options, 351 /* inProgress */ false, /* isRemote */ false, 352 /* group */ nullptr, frameLoaderInit, IgnoreErrors()); 353 } 354 355 void nsFrameLoaderOwner::RestoreFrameLoaderFromBFCache( 356 nsFrameLoader* aNewFrameLoader) { 357 MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug, 358 ("nsFrameLoaderOwner::RestoreFrameLoaderFromBFCache: Replace " 359 "frameloader")); 360 361 Maybe<bool> renderLayers; 362 if (mFrameLoader) { 363 if (auto* oldParent = mFrameLoader->GetBrowserParent()) { 364 renderLayers.emplace(oldParent->GetRenderLayers()); 365 } 366 } 367 368 mFrameLoader = aNewFrameLoader; 369 370 if (auto* browserParent = mFrameLoader->GetBrowserParent()) { 371 browserParent->AddWindowListeners(); 372 if (renderLayers.isSome()) { 373 browserParent->SetRenderLayers(renderLayers.value()); 374 } 375 } 376 377 RefPtr<Element> owner = do_QueryObject(this); 378 ChangeFrameLoaderCommon(owner, /* aRetainPaint = */ false); 379 } 380 381 void nsFrameLoaderOwner::AttachFrameLoader(nsFrameLoader* aFrameLoader) { 382 mFrameLoaderList.insertBack(aFrameLoader); 383 } 384 385 void nsFrameLoaderOwner::DetachFrameLoader(nsFrameLoader* aFrameLoader) { 386 if (aFrameLoader->isInList()) { 387 MOZ_ASSERT(mFrameLoaderList.contains(aFrameLoader)); 388 aFrameLoader->remove(); 389 } 390 } 391 392 void nsFrameLoaderOwner::FrameLoaderDestroying(nsFrameLoader* aFrameLoader, 393 bool aDestroyBFCached) { 394 if (aFrameLoader == mFrameLoader) { 395 if (aDestroyBFCached) { 396 while (!mFrameLoaderList.isEmpty()) { 397 RefPtr<nsFrameLoader> loader = mFrameLoaderList.popFirst(); 398 if (loader != mFrameLoader) { 399 loader->Destroy(); 400 } 401 } 402 } 403 } else { 404 DetachFrameLoader(aFrameLoader); 405 } 406 }