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 "nsFrameLoaderOwner.h"
8 #include "mozilla/dom/BrowserParent.h"
9 #include "nsFrameLoader.h"
10 #include "nsFocusManager.h"
11 #include "nsNetUtil.h"
12 #include "nsSubDocumentFrame.h"
13 #include "nsQueryObject.h"
14 #include "mozilla/AsyncEventDispatcher.h"
15 #include "mozilla/Logging.h"
16 #include "mozilla/dom/CanonicalBrowsingContext.h"
17 #include "mozilla/dom/BrowsingContext.h"
18 #include "mozilla/dom/FrameLoaderBinding.h"
19 #include "mozilla/dom/HTMLIFrameElement.h"
20 #include "mozilla/dom/MozFrameLoaderOwnerBinding.h"
21 #include "mozilla/ScopeExit.h"
22 #include "mozilla/dom/BrowserBridgeChild.h"
23 #include "mozilla/dom/ContentParent.h"
24 #include "mozilla/dom/BrowserBridgeHost.h"
25 #include "mozilla/dom/BrowserHost.h"
26 #include "mozilla/StaticPrefs_fission.h"
27 #include "mozilla/EventStateManager.h"
29 extern mozilla::LazyLogModule gSHIPBFCacheLog
;
31 using namespace mozilla
;
32 using namespace mozilla::dom
;
34 already_AddRefed
<nsFrameLoader
> nsFrameLoaderOwner::GetFrameLoader() {
35 return do_AddRef(mFrameLoader
);
38 void nsFrameLoaderOwner::SetFrameLoader(nsFrameLoader
* aNewFrameLoader
) {
39 mFrameLoader
= aNewFrameLoader
;
42 mozilla::dom::BrowsingContext
* nsFrameLoaderOwner::GetBrowsingContext() {
44 return mFrameLoader
->GetBrowsingContext();
49 mozilla::dom::BrowsingContext
* nsFrameLoaderOwner::GetExtantBrowsingContext() {
51 return mFrameLoader
->GetExtantBrowsingContext();
56 bool nsFrameLoaderOwner::UseRemoteSubframes() {
57 RefPtr
<Element
> owner
= do_QueryObject(this);
59 nsILoadContext
* loadContext
= owner
->OwnerDoc()->GetLoadContext();
60 MOZ_DIAGNOSTIC_ASSERT(loadContext
);
62 return loadContext
->UseRemoteSubframes();
65 nsFrameLoaderOwner::ChangeRemotenessContextType
66 nsFrameLoaderOwner::ShouldPreserveBrowsingContext(
67 bool aIsRemote
, bool aReplaceBrowsingContext
) {
68 if (aReplaceBrowsingContext
) {
69 return ChangeRemotenessContextType::DONT_PRESERVE
;
72 if (XRE_IsParentProcess()) {
73 // Don't preserve for remote => parent loads.
75 return ChangeRemotenessContextType::DONT_PRESERVE
;
78 // Don't preserve for parent => remote loads.
79 if (mFrameLoader
&& !mFrameLoader
->IsRemoteFrame()) {
80 return ChangeRemotenessContextType::DONT_PRESERVE
;
84 // We will preserve our browsing context if either fission is enabled, or the
85 // `preserve_browsing_contexts` pref is active.
86 if (UseRemoteSubframes() ||
87 StaticPrefs::fission_preserve_browsing_contexts()) {
88 return ChangeRemotenessContextType::PRESERVE
;
90 return ChangeRemotenessContextType::DONT_PRESERVE
;
93 void nsFrameLoaderOwner::ChangeRemotenessCommon(
94 const ChangeRemotenessContextType
& aContextType
,
95 const NavigationIsolationOptions
& aOptions
, bool aSwitchingInProgressLoad
,
96 bool aIsRemote
, BrowsingContextGroup
* aGroup
,
97 std::function
<void()>& aFrameLoaderInit
, mozilla::ErrorResult
& aRv
) {
98 MOZ_ASSERT_IF(aGroup
, aContextType
!= ChangeRemotenessContextType::PRESERVE
);
100 RefPtr
<mozilla::dom::BrowsingContext
> bc
;
101 bool networkCreated
= false;
103 // In this case, we're not reparenting a frameloader, we're just destroying
104 // our current one and creating a new one, so we can use ourselves as the
106 RefPtr
<Element
> owner
= do_QueryObject(this);
109 // When we destroy the original frameloader, it will stop blocking the parent
110 // document's load event, and immediately trigger the load event if there are
111 // no other blockers. Since we're going to be adding a new blocker as soon as
112 // we recreate the frame loader, this is not what we want, so add our own
113 // blocker until the process is complete.
114 Document
* doc
= owner
->OwnerDoc();
116 auto cleanup
= MakeScopeExit([&]() { doc
->UnblockOnload(false); });
118 // If we store the previous nsFrameLoader in the bfcache, this will be filled
119 // with the SessionHistoryEntry which now owns the frame.
120 RefPtr
<SessionHistoryEntry
> bfcacheEntry
;
123 // Introduce a script blocker to ensure no JS is executed during the
124 // nsFrameLoader teardown & recreation process. Unload listeners will be run
125 // for the previous document, and the load will be started for the new one,
126 // at the end of this block.
127 nsAutoScriptBlocker sb
;
129 // If we already have a Frameloader, destroy it, possibly preserving its
132 // Calling `GetBrowsingContext` here will force frameloader
133 // initialization if it hasn't already happened, which we neither need
134 // or want, so we use the initial (possibly pending) browsing context
135 // directly, instead.
136 bc
= mFrameLoader
->GetMaybePendingBrowsingContext();
137 networkCreated
= mFrameLoader
->IsNetworkCreated();
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();
146 MOZ_LOG(gSHIPBFCacheLog
, LogLevel::Debug
,
147 ("nsFrameLoaderOwner::ChangeRemotenessCommon: store the old "
149 Unused
<< bc
->SetIsInBFCache(true);
150 bfcacheEntry
->SetFrameLoader(mFrameLoader
);
151 // Session history owns now the frameloader.
152 mFrameLoader
= nullptr;
157 if (aContextType
== ChangeRemotenessContextType::PRESERVE
) {
158 mFrameLoader
->SetWillChangeProcess();
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;
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
);
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.
180 if (NS_WARN_IF(aRv
.Failed())) {
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();
192 gSHIPBFCacheLog
, LogLevel::Debug
,
193 ("Previous frameLoader not entering BFCache - not retaining paint data"
194 "(bfcacheEntry=%p, isRemoteFrame=%d)",
195 bfcacheEntry
.get(), mFrameLoader
->IsRemoteFrame()));
198 ChangeFrameLoaderCommon(owner
, retainPaint
);
200 UpdateFocusAndMouseEnterStateAfterFrameLoaderChange(owner
);
203 void nsFrameLoaderOwner::ChangeFrameLoaderCommon(Element
* aOwner
,
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
);
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
219 mozilla::AsyncEventDispatcher::RunDOMEventWhenSafe(
220 *aOwner
, u
"XULFrameLoaderCreated"_ns
, mozilla::CanBubble::eYes
,
221 mozilla::ChromeOnlyDispatch::eYes
);
225 mFrameLoader
->PropagateIsUnderHiddenEmbedderElement(
226 !aOwner
->GetPrimaryFrame() ||
227 !aOwner
->GetPrimaryFrame()->StyleVisibility()->IsVisible());
231 void nsFrameLoaderOwner::UpdateFocusAndMouseEnterStateAfterFrameLoaderChange() {
232 RefPtr
<Element
> owner
= do_QueryObject(this);
233 UpdateFocusAndMouseEnterStateAfterFrameLoaderChange(owner
);
236 void nsFrameLoaderOwner::UpdateFocusAndMouseEnterStateAfterFrameLoaderChange(
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 (nsFocusManager
* fm
= nsFocusManager::GetFocusManager()) {
241 if (fm
->GetFocusedElement() == aOwner
) {
242 fm
->ActivateRemoteFrameIfNeeded(*aOwner
,
243 nsFocusManager::GenerateFocusActionId());
247 if (aOwner
->GetPrimaryFrame()) {
248 EventStateManager
* eventManager
=
249 aOwner
->GetPrimaryFrame()->PresContext()->EventStateManager();
250 eventManager
->RecomputeMouseEnterStateForRemoteFrame(*aOwner
);
254 void nsFrameLoaderOwner::ChangeRemoteness(
255 const mozilla::dom::RemotenessOptions
& aOptions
, mozilla::ErrorResult
& rv
) {
256 bool isRemote
= !aOptions
.mRemoteType
.IsEmpty();
258 std::function
<void()> frameLoaderInit
= [&] {
260 mFrameLoader
->ConfigRemoteProcess(aOptions
.mRemoteType
, nullptr);
263 if (aOptions
.mPendingSwitchID
.WasPassed()) {
264 mFrameLoader
->ResumeLoad(aOptions
.mPendingSwitchID
.Value());
266 mFrameLoader
->LoadFrame(false);
270 auto shouldPreserve
= ShouldPreserveBrowsingContext(
271 isRemote
, /* replaceBrowsingContext */ false);
272 NavigationIsolationOptions options
;
273 ChangeRemotenessCommon(shouldPreserve
, options
,
274 aOptions
.mSwitchingInProgressLoad
, isRemote
,
275 /* group */ nullptr, frameLoaderInit
, rv
);
278 void nsFrameLoaderOwner::ChangeRemotenessWithBridge(BrowserBridgeChild
* aBridge
,
279 mozilla::ErrorResult
& rv
) {
280 MOZ_ASSERT(XRE_IsContentProcess());
281 if (NS_WARN_IF(!mFrameLoader
)) {
282 rv
.Throw(NS_ERROR_UNEXPECTED
);
286 std::function
<void()> frameLoaderInit
= [&] {
287 MOZ_DIAGNOSTIC_ASSERT(!mFrameLoader
->mInitialized
);
288 RefPtr
<BrowserBridgeHost
> host
= aBridge
->FinishInit(mFrameLoader
);
289 mFrameLoader
->mPendingBrowsingContext
->SetEmbedderElement(
290 mFrameLoader
->GetOwnerContent());
291 mFrameLoader
->mRemoteBrowser
= host
;
292 mFrameLoader
->mInitialized
= true;
295 NavigationIsolationOptions options
;
296 ChangeRemotenessCommon(ChangeRemotenessContextType::PRESERVE
, options
,
297 /* inProgress */ true,
298 /* isRemote */ true, /* group */ nullptr,
299 frameLoaderInit
, rv
);
302 void nsFrameLoaderOwner::ChangeRemotenessToProcess(
303 ContentParent
* aContentParent
, const NavigationIsolationOptions
& aOptions
,
304 BrowsingContextGroup
* aGroup
, mozilla::ErrorResult
& rv
) {
305 MOZ_ASSERT(XRE_IsParentProcess());
306 MOZ_ASSERT_IF(aGroup
, aOptions
.mReplaceBrowsingContext
);
307 bool isRemote
= aContentParent
!= nullptr;
309 std::function
<void()> frameLoaderInit
= [&] {
311 mFrameLoader
->ConfigRemoteProcess(aContentParent
->GetRemoteType(),
316 auto shouldPreserve
=
317 ShouldPreserveBrowsingContext(isRemote
, aOptions
.mReplaceBrowsingContext
);
318 ChangeRemotenessCommon(shouldPreserve
, aOptions
, /* inProgress */ true,
319 isRemote
, aGroup
, frameLoaderInit
, rv
);
322 void nsFrameLoaderOwner::SubframeCrashed() {
323 MOZ_ASSERT(XRE_IsContentProcess());
325 std::function
<void()> frameLoaderInit
= [&] {
326 RefPtr
<nsFrameLoader
> frameLoader
= mFrameLoader
;
327 nsContentUtils::AddScriptRunner(NS_NewRunnableFunction(
328 "nsFrameLoaderOwner::SubframeCrashed", [frameLoader
]() {
329 nsCOMPtr
<nsIURI
> uri
;
330 nsresult rv
= NS_NewURI(getter_AddRefs(uri
), "about:blank");
331 if (NS_WARN_IF(NS_FAILED(rv
))) {
335 RefPtr
<nsDocShell
> docShell
=
336 frameLoader
->GetDocShell(IgnoreErrors());
337 if (NS_WARN_IF(!docShell
)) {
340 bool displayed
= false;
341 docShell
->DisplayLoadError(NS_ERROR_FRAME_CRASHED
, uri
,
342 u
"about:blank", nullptr, &displayed
);
346 NavigationIsolationOptions options
;
347 ChangeRemotenessCommon(ChangeRemotenessContextType::PRESERVE
, options
,
348 /* inProgress */ false, /* isRemote */ false,
349 /* group */ nullptr, frameLoaderInit
, IgnoreErrors());
352 void nsFrameLoaderOwner::RestoreFrameLoaderFromBFCache(
353 nsFrameLoader
* aNewFrameLoader
) {
354 MOZ_LOG(gSHIPBFCacheLog
, LogLevel::Debug
,
355 ("nsFrameLoaderOwner::RestoreFrameLoaderFromBFCache: Replace "
358 Maybe
<bool> renderLayers
;
360 if (auto* oldParent
= mFrameLoader
->GetBrowserParent()) {
361 renderLayers
.emplace(oldParent
->GetRenderLayers());
365 mFrameLoader
= aNewFrameLoader
;
367 if (auto* browserParent
= mFrameLoader
->GetBrowserParent()) {
368 browserParent
->AddWindowListeners();
369 if (renderLayers
.isSome()) {
370 browserParent
->SetRenderLayers(renderLayers
.value());
374 RefPtr
<Element
> owner
= do_QueryObject(this);
375 ChangeFrameLoaderCommon(owner
, /* aRetainPaint = */ false);
378 void nsFrameLoaderOwner::AttachFrameLoader(nsFrameLoader
* aFrameLoader
) {
379 mFrameLoaderList
.insertBack(aFrameLoader
);
382 void nsFrameLoaderOwner::DetachFrameLoader(nsFrameLoader
* aFrameLoader
) {
383 if (aFrameLoader
->isInList()) {
384 MOZ_ASSERT(mFrameLoaderList
.contains(aFrameLoader
));
385 aFrameLoader
->remove();
389 void nsFrameLoaderOwner::FrameLoaderDestroying(nsFrameLoader
* aFrameLoader
) {
390 if (aFrameLoader
== mFrameLoader
) {
391 while (!mFrameLoaderList
.isEmpty()) {
392 RefPtr
<nsFrameLoader
> loader
= mFrameLoaderList
.popFirst();
393 if (loader
!= mFrameLoader
) {
398 DetachFrameLoader(aFrameLoader
);