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 return ChangeRemotenessContextType::PRESERVE
;
87 void nsFrameLoaderOwner::ChangeRemotenessCommon(
88 const ChangeRemotenessContextType
& aContextType
,
89 const NavigationIsolationOptions
& aOptions
, bool aSwitchingInProgressLoad
,
90 bool aIsRemote
, BrowsingContextGroup
* aGroup
,
91 std::function
<void()>& aFrameLoaderInit
, mozilla::ErrorResult
& aRv
) {
92 MOZ_ASSERT_IF(aGroup
, aContextType
!= ChangeRemotenessContextType::PRESERVE
);
94 RefPtr
<mozilla::dom::BrowsingContext
> bc
;
95 bool networkCreated
= false;
97 // In this case, we're not reparenting a frameloader, we're just destroying
98 // our current one and creating a new one, so we can use ourselves as the
100 RefPtr
<Element
> owner
= do_QueryObject(this);
103 // When we destroy the original frameloader, it will stop blocking the parent
104 // document's load event, and immediately trigger the load event if there are
105 // no other blockers. Since we're going to be adding a new blocker as soon as
106 // we recreate the frame loader, this is not what we want, so add our own
107 // blocker until the process is complete.
108 Document
* doc
= owner
->OwnerDoc();
110 auto cleanup
= MakeScopeExit([&]() { doc
->UnblockOnload(false); });
112 // If we store the previous nsFrameLoader in the bfcache, this will be filled
113 // with the SessionHistoryEntry which now owns the frame.
114 RefPtr
<SessionHistoryEntry
> bfcacheEntry
;
117 // Introduce a script blocker to ensure no JS is executed during the
118 // nsFrameLoader teardown & recreation process. Unload listeners will be run
119 // for the previous document, and the load will be started for the new one,
120 // at the end of this block.
121 nsAutoScriptBlocker sb
;
123 // If we already have a Frameloader, destroy it, possibly preserving its
126 // Calling `GetBrowsingContext` here will force frameloader
127 // initialization if it hasn't already happened, which we neither need
128 // or want, so we use the initial (possibly pending) browsing context
129 // directly, instead.
130 bc
= mFrameLoader
->GetMaybePendingBrowsingContext();
131 networkCreated
= mFrameLoader
->IsNetworkCreated();
133 MOZ_ASSERT_IF(aOptions
.mTryUseBFCache
, aOptions
.mReplaceBrowsingContext
);
134 if (aOptions
.mTryUseBFCache
&& bc
) {
135 bfcacheEntry
= bc
->Canonical()->GetActiveSessionHistoryEntry();
136 bool useBFCache
= bfcacheEntry
&&
137 bfcacheEntry
== aOptions
.mActiveSessionHistoryEntry
&&
138 !bfcacheEntry
->GetFrameLoader();
140 MOZ_LOG(gSHIPBFCacheLog
, LogLevel::Debug
,
141 ("nsFrameLoaderOwner::ChangeRemotenessCommon: store the old "
143 Unused
<< bc
->SetIsInBFCache(true);
144 bfcacheEntry
->SetFrameLoader(mFrameLoader
);
145 // Session history owns now the frameloader.
146 mFrameLoader
= nullptr;
151 if (aContextType
== ChangeRemotenessContextType::PRESERVE
) {
152 mFrameLoader
->SetWillChangeProcess();
155 // Preserve the networkCreated status, as nsDocShells created after a
156 // process swap may shouldn't change their dynamically-created status.
157 mFrameLoader
->Destroy(aSwitchingInProgressLoad
);
158 mFrameLoader
= nullptr;
162 mFrameLoader
= nsFrameLoader::Recreate(
163 owner
, bc
, aGroup
, aOptions
, aIsRemote
, networkCreated
,
164 aContextType
== ChangeRemotenessContextType::PRESERVE
);
165 if (NS_WARN_IF(!mFrameLoader
)) {
166 aRv
.Throw(NS_ERROR_FAILURE
);
170 // Invoke the frame loader initialization callback to perform setup on our
171 // new nsFrameLoader. This may cause our ErrorResult to become errored, so
172 // double-check after calling.
174 if (NS_WARN_IF(aRv
.Failed())) {
179 // Now that we have a new FrameLoader, we'll eventually need to reset
180 // nsSubDocumentFrame to use the new one. We can delay doing this if we're
181 // keeping our old frameloader around in the BFCache and the new frame hasn't
182 // presented yet to continue painting the previous document.
183 const bool retainPaint
= bfcacheEntry
&& mFrameLoader
->IsRemoteFrame();
186 gSHIPBFCacheLog
, LogLevel::Debug
,
187 ("Previous frameLoader not entering BFCache - not retaining paint data"
188 "(bfcacheEntry=%p, isRemoteFrame=%d)",
189 bfcacheEntry
.get(), mFrameLoader
->IsRemoteFrame()));
192 ChangeFrameLoaderCommon(owner
, retainPaint
);
194 UpdateFocusAndMouseEnterStateAfterFrameLoaderChange(owner
);
197 void nsFrameLoaderOwner::ChangeFrameLoaderCommon(Element
* aOwner
,
199 // Now that we've got a new FrameLoader, we need to reset our
200 // nsSubDocumentFrame to use the new FrameLoader.
201 if (nsSubDocumentFrame
* ourFrame
= do_QueryFrame(aOwner
->GetPrimaryFrame())) {
202 auto retain
= aRetainPaint
? nsSubDocumentFrame::RetainPaintData::Yes
203 : nsSubDocumentFrame::RetainPaintData::No
;
204 ourFrame
->ResetFrameLoader(retain
);
207 if (aOwner
->IsXULElement()) {
208 // Assuming this element is a XULFrameElement, once we've reset our
209 // FrameLoader, fire an event to act like we've recreated ourselves, similar
210 // to what XULFrameElement does after rebinding to the tree.
211 // ChromeOnlyDispatch is turns on to make sure this isn't fired into
213 mozilla::AsyncEventDispatcher::RunDOMEventWhenSafe(
214 *aOwner
, u
"XULFrameLoaderCreated"_ns
, mozilla::CanBubble::eYes
,
215 mozilla::ChromeOnlyDispatch::eYes
);
219 mFrameLoader
->PropagateIsUnderHiddenEmbedderElement(
220 !aOwner
->GetPrimaryFrame() ||
221 !aOwner
->GetPrimaryFrame()->StyleVisibility()->IsVisible());
225 void nsFrameLoaderOwner::UpdateFocusAndMouseEnterStateAfterFrameLoaderChange() {
226 RefPtr
<Element
> owner
= do_QueryObject(this);
227 UpdateFocusAndMouseEnterStateAfterFrameLoaderChange(owner
);
230 void nsFrameLoaderOwner::UpdateFocusAndMouseEnterStateAfterFrameLoaderChange(
232 // If the element is focused, or the current mouse over target then
233 // we need to update that state for the new BrowserParent too.
234 if (nsFocusManager
* fm
= nsFocusManager::GetFocusManager()) {
235 if (fm
->GetFocusedElement() == aOwner
) {
236 fm
->ActivateRemoteFrameIfNeeded(*aOwner
,
237 nsFocusManager::GenerateFocusActionId());
241 if (aOwner
->GetPrimaryFrame()) {
242 EventStateManager
* eventManager
=
243 aOwner
->GetPrimaryFrame()->PresContext()->EventStateManager();
244 eventManager
->RecomputeMouseEnterStateForRemoteFrame(*aOwner
);
248 void nsFrameLoaderOwner::ChangeRemoteness(
249 const mozilla::dom::RemotenessOptions
& aOptions
, mozilla::ErrorResult
& rv
) {
250 bool isRemote
= !aOptions
.mRemoteType
.IsEmpty();
252 std::function
<void()> frameLoaderInit
= [&] {
254 mFrameLoader
->ConfigRemoteProcess(aOptions
.mRemoteType
, nullptr);
257 if (aOptions
.mPendingSwitchID
.WasPassed()) {
258 mFrameLoader
->ResumeLoad(aOptions
.mPendingSwitchID
.Value());
260 mFrameLoader
->LoadFrame(false);
264 auto shouldPreserve
= ShouldPreserveBrowsingContext(
265 isRemote
, /* replaceBrowsingContext */ false);
266 NavigationIsolationOptions options
;
267 ChangeRemotenessCommon(shouldPreserve
, options
,
268 aOptions
.mSwitchingInProgressLoad
, isRemote
,
269 /* group */ nullptr, frameLoaderInit
, rv
);
272 void nsFrameLoaderOwner::ChangeRemotenessWithBridge(BrowserBridgeChild
* aBridge
,
273 mozilla::ErrorResult
& rv
) {
274 MOZ_ASSERT(XRE_IsContentProcess());
275 if (NS_WARN_IF(!mFrameLoader
)) {
276 rv
.Throw(NS_ERROR_UNEXPECTED
);
280 std::function
<void()> frameLoaderInit
= [&] {
281 MOZ_DIAGNOSTIC_ASSERT(!mFrameLoader
->mInitialized
);
282 RefPtr
<BrowserBridgeHost
> host
= aBridge
->FinishInit(mFrameLoader
);
283 mFrameLoader
->mPendingBrowsingContext
->SetEmbedderElement(
284 mFrameLoader
->GetOwnerContent());
285 mFrameLoader
->mRemoteBrowser
= host
;
286 mFrameLoader
->mInitialized
= true;
289 NavigationIsolationOptions options
;
290 ChangeRemotenessCommon(ChangeRemotenessContextType::PRESERVE
, options
,
291 /* inProgress */ true,
292 /* isRemote */ true, /* group */ nullptr,
293 frameLoaderInit
, rv
);
296 void nsFrameLoaderOwner::ChangeRemotenessToProcess(
297 ContentParent
* aContentParent
, const NavigationIsolationOptions
& aOptions
,
298 BrowsingContextGroup
* aGroup
, mozilla::ErrorResult
& rv
) {
299 MOZ_ASSERT(XRE_IsParentProcess());
300 MOZ_ASSERT_IF(aGroup
, aOptions
.mReplaceBrowsingContext
);
301 bool isRemote
= aContentParent
!= nullptr;
303 std::function
<void()> frameLoaderInit
= [&] {
305 mFrameLoader
->ConfigRemoteProcess(aContentParent
->GetRemoteType(),
310 auto shouldPreserve
=
311 ShouldPreserveBrowsingContext(isRemote
, aOptions
.mReplaceBrowsingContext
);
312 ChangeRemotenessCommon(shouldPreserve
, aOptions
, /* inProgress */ true,
313 isRemote
, aGroup
, frameLoaderInit
, rv
);
316 void nsFrameLoaderOwner::SubframeCrashed() {
317 MOZ_ASSERT(XRE_IsContentProcess());
319 std::function
<void()> frameLoaderInit
= [&] {
320 RefPtr
<nsFrameLoader
> frameLoader
= mFrameLoader
;
321 nsContentUtils::AddScriptRunner(NS_NewRunnableFunction(
322 "nsFrameLoaderOwner::SubframeCrashed", [frameLoader
]() {
323 nsCOMPtr
<nsIURI
> uri
;
324 nsresult rv
= NS_NewURI(getter_AddRefs(uri
), "about:blank");
325 if (NS_WARN_IF(NS_FAILED(rv
))) {
329 RefPtr
<nsDocShell
> docShell
=
330 frameLoader
->GetDocShell(IgnoreErrors());
331 if (NS_WARN_IF(!docShell
)) {
334 bool displayed
= false;
335 docShell
->DisplayLoadError(NS_ERROR_FRAME_CRASHED
, uri
,
336 u
"about:blank", nullptr, &displayed
);
340 NavigationIsolationOptions options
;
341 ChangeRemotenessCommon(ChangeRemotenessContextType::PRESERVE
, options
,
342 /* inProgress */ false, /* isRemote */ false,
343 /* group */ nullptr, frameLoaderInit
, IgnoreErrors());
346 void nsFrameLoaderOwner::RestoreFrameLoaderFromBFCache(
347 nsFrameLoader
* aNewFrameLoader
) {
348 MOZ_LOG(gSHIPBFCacheLog
, LogLevel::Debug
,
349 ("nsFrameLoaderOwner::RestoreFrameLoaderFromBFCache: Replace "
352 Maybe
<bool> renderLayers
;
354 if (auto* oldParent
= mFrameLoader
->GetBrowserParent()) {
355 renderLayers
.emplace(oldParent
->GetRenderLayers());
359 mFrameLoader
= aNewFrameLoader
;
361 if (auto* browserParent
= mFrameLoader
->GetBrowserParent()) {
362 browserParent
->AddWindowListeners();
363 if (renderLayers
.isSome()) {
364 browserParent
->SetRenderLayers(renderLayers
.value());
368 RefPtr
<Element
> owner
= do_QueryObject(this);
369 ChangeFrameLoaderCommon(owner
, /* aRetainPaint = */ false);
372 void nsFrameLoaderOwner::AttachFrameLoader(nsFrameLoader
* aFrameLoader
) {
373 mFrameLoaderList
.insertBack(aFrameLoader
);
376 void nsFrameLoaderOwner::DetachFrameLoader(nsFrameLoader
* aFrameLoader
) {
377 if (aFrameLoader
->isInList()) {
378 MOZ_ASSERT(mFrameLoaderList
.contains(aFrameLoader
));
379 aFrameLoader
->remove();
383 void nsFrameLoaderOwner::FrameLoaderDestroying(nsFrameLoader
* aFrameLoader
,
384 bool aDestroyBFCached
) {
385 if (aFrameLoader
== mFrameLoader
) {
386 if (aDestroyBFCached
) {
387 while (!mFrameLoaderList
.isEmpty()) {
388 RefPtr
<nsFrameLoader
> loader
= mFrameLoaderList
.popFirst();
389 if (loader
!= mFrameLoader
) {
395 DetachFrameLoader(aFrameLoader
);