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 #include "nsFrameLoader.h"
9 #include "nsFocusManager.h"
10 #include "nsNetUtil.h"
11 #include "nsSubDocumentFrame.h"
12 #include "nsQueryObject.h"
13 #include "mozilla/AsyncEventDispatcher.h"
14 #include "mozilla/Logging.h"
15 #include "mozilla/dom/CanonicalBrowsingContext.h"
16 #include "mozilla/dom/BrowsingContext.h"
17 #include "mozilla/dom/FrameLoaderBinding.h"
18 #include "mozilla/dom/HTMLIFrameElement.h"
19 #include "mozilla/dom/MozFrameLoaderOwnerBinding.h"
20 #include "mozilla/ScopeExit.h"
21 #include "mozilla/dom/BrowserBridgeChild.h"
22 #include "mozilla/dom/ContentParent.h"
23 #include "mozilla/dom/BrowserBridgeHost.h"
24 #include "mozilla/dom/BrowserHost.h"
25 #include "mozilla/StaticPrefs_fission.h"
26 #include "mozilla/EventStateManager.h"
27 
28 extern mozilla::LazyLogModule gSHIPBFCacheLog;
29 
30 using namespace mozilla;
31 using namespace mozilla::dom;
32 
GetFrameLoader()33 already_AddRefed<nsFrameLoader> nsFrameLoaderOwner::GetFrameLoader() {
34   return do_AddRef(mFrameLoader);
35 }
36 
SetFrameLoader(nsFrameLoader * aNewFrameLoader)37 void nsFrameLoaderOwner::SetFrameLoader(nsFrameLoader* aNewFrameLoader) {
38   mFrameLoader = aNewFrameLoader;
39 }
40 
GetBrowsingContext()41 mozilla::dom::BrowsingContext* nsFrameLoaderOwner::GetBrowsingContext() {
42   if (mFrameLoader) {
43     return mFrameLoader->GetBrowsingContext();
44   }
45   return nullptr;
46 }
47 
GetExtantBrowsingContext()48 mozilla::dom::BrowsingContext* nsFrameLoaderOwner::GetExtantBrowsingContext() {
49   if (mFrameLoader) {
50     return mFrameLoader->GetExtantBrowsingContext();
51   }
52   return nullptr;
53 }
54 
UseRemoteSubframes()55 bool nsFrameLoaderOwner::UseRemoteSubframes() {
56   RefPtr<Element> owner = do_QueryObject(this);
57 
58   nsILoadContext* loadContext = owner->OwnerDoc()->GetLoadContext();
59   MOZ_DIAGNOSTIC_ASSERT(loadContext);
60 
61   return loadContext->UseRemoteSubframes();
62 }
63 
64 nsFrameLoaderOwner::ChangeRemotenessContextType
ShouldPreserveBrowsingContext(bool aIsRemote,bool aReplaceBrowsingContext)65 nsFrameLoaderOwner::ShouldPreserveBrowsingContext(
66     bool aIsRemote, bool aReplaceBrowsingContext) {
67   if (aReplaceBrowsingContext) {
68     return ChangeRemotenessContextType::DONT_PRESERVE;
69   }
70 
71   if (XRE_IsParentProcess()) {
72     // Don't preserve for remote => parent loads.
73     if (!aIsRemote) {
74       return ChangeRemotenessContextType::DONT_PRESERVE;
75     }
76 
77     // Don't preserve for parent => remote loads.
78     if (mFrameLoader && !mFrameLoader->IsRemoteFrame()) {
79       return ChangeRemotenessContextType::DONT_PRESERVE;
80     }
81   }
82 
83   // We will preserve our browsing context if either fission is enabled, or the
84   // `preserve_browsing_contexts` pref is active.
85   if (UseRemoteSubframes() ||
86       StaticPrefs::fission_preserve_browsing_contexts()) {
87     return ChangeRemotenessContextType::PRESERVE;
88   }
89   return ChangeRemotenessContextType::DONT_PRESERVE;
90 }
91 
ChangeRemotenessCommon(const ChangeRemotenessContextType & aContextType,const RemotenessChangeOptions & aOptions,bool aSwitchingInProgressLoad,bool aIsRemote,BrowsingContextGroup * aGroup,std::function<void ()> & aFrameLoaderInit,mozilla::ErrorResult & aRv)92 void nsFrameLoaderOwner::ChangeRemotenessCommon(
93     const ChangeRemotenessContextType& aContextType,
94     const RemotenessChangeOptions& aOptions, bool aSwitchingInProgressLoad,
95     bool aIsRemote, BrowsingContextGroup* aGroup,
96     std::function<void()>& aFrameLoaderInit, mozilla::ErrorResult& aRv) {
97   MOZ_ASSERT_IF(aGroup, aContextType != ChangeRemotenessContextType::PRESERVE);
98 
99   RefPtr<mozilla::dom::BrowsingContext> bc;
100   bool networkCreated = false;
101 
102   // In this case, we're not reparenting a frameloader, we're just destroying
103   // our current one and creating a new one, so we can use ourselves as the
104   // owner.
105   RefPtr<Element> owner = do_QueryObject(this);
106   MOZ_ASSERT(owner);
107 
108   // When we destroy the original frameloader, it will stop blocking the parent
109   // document's load event, and immediately trigger the load event if there are
110   // no other blockers. Since we're going to be adding a new blocker as soon as
111   // we recreate the frame loader, this is not what we want, so add our own
112   // blocker until the process is complete.
113   Document* doc = owner->OwnerDoc();
114   doc->BlockOnload();
115   auto cleanup = MakeScopeExit([&]() { doc->UnblockOnload(false); });
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       networkCreated = mFrameLoader->IsNetworkCreated();
133 
134       MOZ_ASSERT_IF(aOptions.mTryUseBFCache, aOptions.mReplaceBrowsingContext);
135       if (aOptions.mTryUseBFCache && bc) {
136         SessionHistoryEntry* she =
137             bc->Canonical()->GetActiveSessionHistoryEntry();
138         bool useBFCache = she && she == aOptions.mActiveSessionHistoryEntry &&
139                           !she->GetFrameLoader();
140         if (useBFCache) {
141           MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug,
142                   ("nsFrameLoaderOwner::ChangeRemotenessCommon: store the old "
143                    "page in bfcache"));
144           Unused << bc->SetIsInBFCache(true);
145           she->SetFrameLoader(mFrameLoader);
146           // Session history owns now the frameloader.
147           mFrameLoader = nullptr;
148         }
149       }
150 
151       if (mFrameLoader) {
152         if (aContextType == ChangeRemotenessContextType::PRESERVE) {
153           mFrameLoader->SetWillChangeProcess();
154         }
155 
156         // Preserve the networkCreated status, as nsDocShells created after a
157         // process swap may shouldn't change their dynamically-created status.
158         mFrameLoader->Destroy(aSwitchingInProgressLoad);
159         mFrameLoader = nullptr;
160       }
161     }
162 
163     mFrameLoader = nsFrameLoader::Recreate(
164         owner, bc, aGroup, aOptions, aIsRemote, networkCreated,
165         aContextType == ChangeRemotenessContextType::PRESERVE);
166     if (NS_WARN_IF(!mFrameLoader)) {
167       aRv.Throw(NS_ERROR_FAILURE);
168       return;
169     }
170 
171     // Invoke the frame loader initialization callback to perform setup on our
172     // new nsFrameLoader. This may cause our ErrorResult to become errored, so
173     // double-check after calling.
174     aFrameLoaderInit();
175     if (NS_WARN_IF(aRv.Failed())) {
176       return;
177     }
178   }
179 
180   ChangeFrameLoaderCommon(owner);
181 }
182 
ChangeFrameLoaderCommon(Element * aOwner)183 void nsFrameLoaderOwner::ChangeFrameLoaderCommon(Element* aOwner) {
184   // Now that we've got a new FrameLoader, we need to reset our
185   // nsSubDocumentFrame to use the new FrameLoader.
186   if (nsSubDocumentFrame* ourFrame = do_QueryFrame(aOwner->GetPrimaryFrame())) {
187     ourFrame->ResetFrameLoader();
188   }
189 
190   // If the element is focused, or the current mouse over target then
191   // we need to update that state for the new BrowserParent too.
192   if (nsFocusManager* fm = nsFocusManager::GetFocusManager()) {
193     if (fm->GetFocusedElement() == aOwner) {
194       fm->ActivateRemoteFrameIfNeeded(*aOwner,
195                                       nsFocusManager::GenerateFocusActionId());
196     }
197   }
198 
199   if (aOwner->GetPrimaryFrame()) {
200     EventStateManager* eventManager =
201         aOwner->GetPrimaryFrame()->PresContext()->EventStateManager();
202     eventManager->RecomputeMouseEnterStateForRemoteFrame(*aOwner);
203   }
204 
205   if (aOwner->IsXULElement()) {
206     // Assuming this element is a XULFrameElement, once we've reset our
207     // FrameLoader, fire an event to act like we've recreated ourselves, similar
208     // to what XULFrameElement does after rebinding to the tree.
209     // ChromeOnlyDispatch is turns on to make sure this isn't fired into
210     // content.
211     (new mozilla::AsyncEventDispatcher(aOwner, u"XULFrameLoaderCreated"_ns,
212                                        mozilla::CanBubble::eYes,
213                                        mozilla::ChromeOnlyDispatch::eYes))
214         ->RunDOMEventWhenSafe();
215   }
216 
217   mFrameLoader->PropagateIsUnderHiddenEmbedderElement(
218       !aOwner->GetPrimaryFrame() ||
219       !aOwner->GetPrimaryFrame()->StyleVisibility()->IsVisible());
220 }
221 
ChangeRemoteness(const mozilla::dom::RemotenessOptions & aOptions,mozilla::ErrorResult & rv)222 void nsFrameLoaderOwner::ChangeRemoteness(
223     const mozilla::dom::RemotenessOptions& aOptions, mozilla::ErrorResult& rv) {
224   bool isRemote = !aOptions.mRemoteType.IsEmpty();
225 
226   std::function<void()> frameLoaderInit = [&] {
227     if (isRemote) {
228       mFrameLoader->ConfigRemoteProcess(aOptions.mRemoteType, nullptr);
229     }
230 
231     if (aOptions.mPendingSwitchID.WasPassed()) {
232       mFrameLoader->ResumeLoad(aOptions.mPendingSwitchID.Value());
233     } else {
234       mFrameLoader->LoadFrame(false);
235     }
236   };
237 
238   auto shouldPreserve = ShouldPreserveBrowsingContext(
239       isRemote, /* replaceBrowsingContext */ false);
240   RemotenessChangeOptions options;
241   ChangeRemotenessCommon(shouldPreserve, options,
242                          aOptions.mSwitchingInProgressLoad, isRemote,
243                          /* group */ nullptr, frameLoaderInit, rv);
244 }
245 
ChangeRemotenessWithBridge(BrowserBridgeChild * aBridge,mozilla::ErrorResult & rv)246 void nsFrameLoaderOwner::ChangeRemotenessWithBridge(BrowserBridgeChild* aBridge,
247                                                     mozilla::ErrorResult& rv) {
248   MOZ_ASSERT(XRE_IsContentProcess());
249   if (NS_WARN_IF(!mFrameLoader)) {
250     rv.Throw(NS_ERROR_UNEXPECTED);
251     return;
252   }
253 
254   std::function<void()> frameLoaderInit = [&] {
255     RefPtr<BrowserBridgeHost> host = aBridge->FinishInit(mFrameLoader);
256     mFrameLoader->mPendingBrowsingContext->SetEmbedderElement(
257         mFrameLoader->GetOwnerContent());
258     mFrameLoader->mRemoteBrowser = host;
259   };
260 
261   RemotenessChangeOptions options;
262   ChangeRemotenessCommon(ChangeRemotenessContextType::PRESERVE, options,
263                          /* inProgress */ true,
264                          /* isRemote */ true, /* group */ nullptr,
265                          frameLoaderInit, rv);
266 }
267 
ChangeRemotenessToProcess(ContentParent * aContentParent,const RemotenessChangeOptions & aOptions,BrowsingContextGroup * aGroup,mozilla::ErrorResult & rv)268 void nsFrameLoaderOwner::ChangeRemotenessToProcess(
269     ContentParent* aContentParent, const RemotenessChangeOptions& aOptions,
270     BrowsingContextGroup* aGroup, mozilla::ErrorResult& rv) {
271   MOZ_ASSERT(XRE_IsParentProcess());
272   MOZ_ASSERT_IF(aGroup, aOptions.mReplaceBrowsingContext);
273   bool isRemote = aContentParent != nullptr;
274 
275   std::function<void()> frameLoaderInit = [&] {
276     if (isRemote) {
277       mFrameLoader->ConfigRemoteProcess(aContentParent->GetRemoteType(),
278                                         aContentParent);
279     }
280   };
281 
282   auto shouldPreserve =
283       ShouldPreserveBrowsingContext(isRemote, aOptions.mReplaceBrowsingContext);
284   ChangeRemotenessCommon(shouldPreserve, aOptions, /* inProgress */ true,
285                          isRemote, aGroup, frameLoaderInit, rv);
286 }
287 
SubframeCrashed()288 void nsFrameLoaderOwner::SubframeCrashed() {
289   MOZ_ASSERT(XRE_IsContentProcess());
290 
291   std::function<void()> frameLoaderInit = [&] {
292     RefPtr<nsFrameLoader> frameLoader = mFrameLoader;
293     nsContentUtils::AddScriptRunner(NS_NewRunnableFunction(
294         "nsFrameLoaderOwner::SubframeCrashed", [frameLoader]() {
295           nsCOMPtr<nsIURI> uri;
296           nsresult rv = NS_NewURI(getter_AddRefs(uri), "about:blank");
297           if (NS_WARN_IF(NS_FAILED(rv))) {
298             return;
299           }
300 
301           RefPtr<nsDocShell> docShell =
302               frameLoader->GetDocShell(IgnoreErrors());
303           if (NS_WARN_IF(!docShell)) {
304             return;
305           }
306           bool displayed = false;
307           docShell->DisplayLoadError(NS_ERROR_FRAME_CRASHED, uri,
308                                      u"about:blank", nullptr, &displayed);
309         }));
310   };
311 
312   RemotenessChangeOptions options;
313   ChangeRemotenessCommon(ChangeRemotenessContextType::PRESERVE, options,
314                          /* inProgress */ false, /* isRemote */ false,
315                          /* group */ nullptr, frameLoaderInit, IgnoreErrors());
316 }
317 
ReplaceFrameLoader(nsFrameLoader * aNewFrameLoader)318 void nsFrameLoaderOwner::ReplaceFrameLoader(nsFrameLoader* aNewFrameLoader) {
319   MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug,
320           ("nsFrameLoaderOwner::ReplaceFrameLoader: Replace frameloader"));
321 
322   mFrameLoader = aNewFrameLoader;
323 
324   if (auto* browserParent = mFrameLoader->GetBrowserParent()) {
325     browserParent->AddWindowListeners();
326   }
327 
328   RefPtr<Element> owner = do_QueryObject(this);
329   ChangeFrameLoaderCommon(owner);
330 }
331 
AttachFrameLoader(nsFrameLoader * aFrameLoader)332 void nsFrameLoaderOwner::AttachFrameLoader(nsFrameLoader* aFrameLoader) {
333   mFrameLoaderList.insertBack(aFrameLoader);
334 }
335 
DetachFrameLoader(nsFrameLoader * aFrameLoader)336 void nsFrameLoaderOwner::DetachFrameLoader(nsFrameLoader* aFrameLoader) {
337   if (aFrameLoader->isInList()) {
338     MOZ_ASSERT(mFrameLoaderList.contains(aFrameLoader));
339     aFrameLoader->remove();
340   }
341 }
342 
FrameLoaderDestroying(nsFrameLoader * aFrameLoader)343 void nsFrameLoaderOwner::FrameLoaderDestroying(nsFrameLoader* aFrameLoader) {
344   if (aFrameLoader == mFrameLoader) {
345     while (!mFrameLoaderList.isEmpty()) {
346       RefPtr<nsFrameLoader> loader = mFrameLoaderList.popFirst();
347       if (loader != mFrameLoader) {
348         loader->Destroy();
349       }
350     }
351   } else {
352     DetachFrameLoader(aFrameLoader);
353   }
354 }
355