• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright (c) 2012 The Chromium Embedded Framework Authors. All rights
2 // reserved. Use of this source code is governed by a BSD-style license that can
3 // be found in the LICENSE file.
4 
5 #include "libcef/browser/browser_info.h"
6 
7 #include "libcef/browser/browser_host_base.h"
8 #include "libcef/browser/thread_util.h"
9 #include "libcef/common/frame_util.h"
10 #include "libcef/common/values_impl.h"
11 
12 #include "base/logging.h"
13 #include "content/browser/renderer_host/frame_tree_node.h"
14 #include "content/browser/renderer_host/render_frame_host_impl.h"
15 #include "content/public/browser/render_process_host.h"
16 #include "ipc/ipc_message.h"
17 
~FrameInfo()18 CefBrowserInfo::FrameInfo::~FrameInfo() {
19 #if DCHECK_IS_ON()
20   if (frame_ && !IsCurrentMainFrame()) {
21     // Should already be Detached.
22     DCHECK(!frame_->GetRenderFrameHost());
23   }
24 #endif
25 }
26 
CefBrowserInfo(int browser_id,bool is_popup,bool is_windowless,CefRefPtr<CefDictionaryValue> extra_info)27 CefBrowserInfo::CefBrowserInfo(int browser_id,
28                                bool is_popup,
29                                bool is_windowless,
30                                CefRefPtr<CefDictionaryValue> extra_info)
31     : browser_id_(browser_id),
32       is_popup_(is_popup),
33       is_windowless_(is_windowless),
34       extra_info_(extra_info) {
35   DCHECK_GT(browser_id, 0);
36 }
37 
~CefBrowserInfo()38 CefBrowserInfo::~CefBrowserInfo() {
39   DCHECK(frame_info_set_.empty());
40 }
41 
browser() const42 CefRefPtr<CefBrowserHostBase> CefBrowserInfo::browser() const {
43   base::AutoLock lock_scope(lock_);
44   if (!is_closing_)
45     return browser_;
46   return nullptr;
47 }
48 
SetBrowser(CefRefPtr<CefBrowserHostBase> browser)49 void CefBrowserInfo::SetBrowser(CefRefPtr<CefBrowserHostBase> browser) {
50   NotificationStateLock lock_scope(this);
51 
52   if (browser) {
53     DCHECK(!browser_);
54 
55     // Cache the associated frame handler.
56     if (auto client = browser->GetClient()) {
57       frame_handler_ = client->GetFrameHandler();
58     }
59   } else {
60     DCHECK(browser_);
61   }
62 
63   auto old_browser = browser_;
64   browser_ = browser;
65 
66   if (!browser_) {
67     RemoveAllFrames(old_browser);
68 
69     // Any future calls to MaybeExecuteFrameNotification will now fail.
70     // NotificationStateLock already took a reference for the delivery of any
71     // notifications that are currently queued due to RemoveAllFrames.
72     frame_handler_ = nullptr;
73   }
74 }
75 
SetClosing()76 void CefBrowserInfo::SetClosing() {
77   base::AutoLock lock_scope(lock_);
78   DCHECK(!is_closing_);
79   is_closing_ = true;
80 }
81 
MaybeCreateFrame(content::RenderFrameHost * host,bool is_guest_view)82 void CefBrowserInfo::MaybeCreateFrame(content::RenderFrameHost* host,
83                                       bool is_guest_view) {
84   CEF_REQUIRE_UIT();
85 
86   const auto global_id = host->GetGlobalId();
87   const bool is_main_frame = (host->GetParent() == nullptr);
88 
89   // A speculative RFH will be created in response to a browser-initiated
90   // cross-origin navigation (e.g. via LoadURL) and eventually either discarded
91   // or swapped in based on whether the navigation is committed. We'll create a
92   // frame object for the speculative RFH so that it can be found by
93   // frame/routing ID. However, we won't replace the main frame with a
94   // speculative RFH until after it's swapped in, and we'll generally prefer to
95   // return a non-speculative RFH for the same node ID if one exists.
96   const bool is_speculative = (static_cast<content::RenderFrameHostImpl*>(host)
97                                    ->frame_tree_node()
98                                    ->render_manager()
99                                    ->current_frame_host() != host);
100 
101   NotificationStateLock lock_scope(this);
102   DCHECK(browser_);
103 
104   const auto it = frame_id_map_.find(global_id);
105   if (it != frame_id_map_.end()) {
106     auto info = it->second;
107 
108 #if DCHECK_IS_ON()
109     // Check that the frame info hasn't changed unexpectedly.
110     DCHECK_EQ(info->global_id_, global_id);
111     DCHECK_EQ(info->is_guest_view_, is_guest_view);
112     DCHECK_EQ(info->is_main_frame_, is_main_frame);
113 #endif
114 
115     if (!info->is_guest_view_ && info->is_speculative_ && !is_speculative) {
116       // Upgrade the frame info from speculative to non-speculative.
117       if (info->is_main_frame_) {
118         // Set the main frame object.
119         SetMainFrame(browser_, info->frame_);
120       }
121       info->is_speculative_ = false;
122     }
123     return;
124   }
125 
126   auto frame_info = new FrameInfo;
127   frame_info->host_ = host;
128   frame_info->global_id_ = global_id;
129   frame_info->is_guest_view_ = is_guest_view;
130   frame_info->is_main_frame_ = is_main_frame;
131   frame_info->is_speculative_ = is_speculative;
132 
133   // Guest views don't get their own CefBrowser or CefFrame objects.
134   if (!is_guest_view) {
135     // Create a new frame object.
136     frame_info->frame_ = new CefFrameHostImpl(this, host);
137     MaybeNotifyFrameCreated(frame_info->frame_);
138     if (is_main_frame && !is_speculative) {
139       SetMainFrame(browser_, frame_info->frame_);
140     }
141 
142 #if DCHECK_IS_ON()
143     // Check that the frame info hasn't changed unexpectedly.
144     DCHECK_EQ(frame_util::MakeFrameId(global_id),
145               frame_info->frame_->GetIdentifier());
146     DCHECK_EQ(frame_info->is_main_frame_, frame_info->frame_->IsMain());
147 #endif
148   }
149 
150   browser_->request_context()->OnRenderFrameCreated(global_id, is_main_frame,
151                                                     is_guest_view);
152 
153   // Populate the lookup maps.
154   frame_id_map_.insert(std::make_pair(global_id, frame_info));
155 
156   // And finally set the ownership.
157   frame_info_set_.insert(base::WrapUnique(frame_info));
158 }
159 
FrameHostStateChanged(content::RenderFrameHost * host,content::RenderFrameHost::LifecycleState old_state,content::RenderFrameHost::LifecycleState new_state)160 void CefBrowserInfo::FrameHostStateChanged(
161     content::RenderFrameHost* host,
162     content::RenderFrameHost::LifecycleState old_state,
163     content::RenderFrameHost::LifecycleState new_state) {
164   CEF_REQUIRE_UIT();
165 
166   if ((old_state == content::RenderFrameHost::LifecycleState::kPrerendering ||
167        old_state ==
168            content::RenderFrameHost::LifecycleState::kInBackForwardCache) &&
169       new_state == content::RenderFrameHost::LifecycleState::kActive) {
170     if (auto frame = GetFrameForHost(host)) {
171       // Should only occur for the main frame.
172       CHECK(frame->IsMain());
173 
174       // Update the associated RFH, which may have changed.
175       frame->MaybeReAttach(this, host);
176 
177       {
178         // Update the main frame object.
179         NotificationStateLock lock_scope(this);
180         SetMainFrame(browser_, frame);
181       }
182 
183       // Update draggable regions.
184       frame->MaybeSendDidStopLoading();
185     }
186   }
187 
188   // Update BackForwardCache state.
189   bool added_to_bfcache =
190       new_state ==
191       content::RenderFrameHost::LifecycleState::kInBackForwardCache;
192   bool removed_from_bfcache =
193       old_state ==
194       content::RenderFrameHost::LifecycleState::kInBackForwardCache;
195   if (!added_to_bfcache && !removed_from_bfcache)
196     return;
197 
198   base::AutoLock lock_scope(lock_);
199 
200   auto it = frame_id_map_.find(host->GetGlobalId());
201   DCHECK(it != frame_id_map_.end());
202   DCHECK((!it->second->is_in_bfcache_ && added_to_bfcache) ||
203          (it->second->is_in_bfcache_ && removed_from_bfcache));
204   it->second->is_in_bfcache_ = added_to_bfcache;
205 }
206 
RemoveFrame(content::RenderFrameHost * host)207 void CefBrowserInfo::RemoveFrame(content::RenderFrameHost* host) {
208   CEF_REQUIRE_UIT();
209 
210   NotificationStateLock lock_scope(this);
211 
212   const auto global_id = host->GetGlobalId();
213   auto it = frame_id_map_.find(global_id);
214   DCHECK(it != frame_id_map_.end());
215 
216   auto frame_info = it->second;
217 
218   browser_->request_context()->OnRenderFrameDeleted(
219       global_id, frame_info->is_main_frame_, frame_info->is_guest_view_);
220 
221   // Remove from the lookup maps.
222   frame_id_map_.erase(it);
223 
224   // And finally delete the frame info.
225   {
226     auto it2 = frame_info_set_.find(frame_info);
227 
228     // Explicitly Detach everything but the current main frame.
229     const auto& frame_info = *it2;
230     if (frame_info->frame_ && !frame_info->IsCurrentMainFrame()) {
231       if (frame_info->frame_->Detach())
232         MaybeNotifyFrameDetached(browser_, frame_info->frame_);
233     }
234 
235     frame_info_set_.erase(it2);
236   }
237 }
238 
GetMainFrame()239 CefRefPtr<CefFrameHostImpl> CefBrowserInfo::GetMainFrame() {
240   base::AutoLock lock_scope(lock_);
241   // Early exit if called post-destruction.
242   if (!browser_ || is_closing_)
243     return nullptr;
244 
245   CHECK(main_frame_);
246   return main_frame_;
247 }
248 
CreateTempSubFrame(const content::GlobalRenderFrameHostId & parent_global_id)249 CefRefPtr<CefFrameHostImpl> CefBrowserInfo::CreateTempSubFrame(
250     const content::GlobalRenderFrameHostId& parent_global_id) {
251   CefRefPtr<CefFrameHostImpl> parent = GetFrameForGlobalId(parent_global_id);
252   if (!parent)
253     parent = GetMainFrame();
254   // Intentionally not notifying for temporary frames.
255   return new CefFrameHostImpl(this, parent->GetIdentifier());
256 }
257 
GetFrameForHost(const content::RenderFrameHost * host,bool * is_guest_view,bool prefer_speculative) const258 CefRefPtr<CefFrameHostImpl> CefBrowserInfo::GetFrameForHost(
259     const content::RenderFrameHost* host,
260     bool* is_guest_view,
261     bool prefer_speculative) const {
262   if (is_guest_view)
263     *is_guest_view = false;
264 
265   if (!host)
266     return nullptr;
267 
268   return GetFrameForGlobalId(
269       const_cast<content::RenderFrameHost*>(host)->GetGlobalId(), is_guest_view,
270       prefer_speculative);
271 }
272 
GetFrameForGlobalId(const content::GlobalRenderFrameHostId & global_id,bool * is_guest_view,bool prefer_speculative) const273 CefRefPtr<CefFrameHostImpl> CefBrowserInfo::GetFrameForGlobalId(
274     const content::GlobalRenderFrameHostId& global_id,
275     bool* is_guest_view,
276     bool prefer_speculative) const {
277   if (is_guest_view)
278     *is_guest_view = false;
279 
280   if (!frame_util::IsValidGlobalId(global_id))
281     return nullptr;
282 
283   base::AutoLock lock_scope(lock_);
284 
285   const auto it = frame_id_map_.find(global_id);
286   if (it != frame_id_map_.end()) {
287     const auto info = it->second;
288 
289     if (info->is_guest_view_) {
290       if (is_guest_view)
291         *is_guest_view = true;
292       return nullptr;
293     }
294 
295     if (info->is_speculative_ && !prefer_speculative) {
296       if (info->is_main_frame_ && main_frame_) {
297         // Always prefer the non-speculative main frame.
298         return main_frame_;
299       }
300 
301       LOG(WARNING) << "Returning a speculative frame for "
302                    << frame_util::GetFrameDebugString(global_id);
303     }
304 
305     DCHECK(info->frame_);
306     return info->frame_;
307   }
308 
309   return nullptr;
310 }
311 
GetAllFrames() const312 CefBrowserInfo::FrameHostList CefBrowserInfo::GetAllFrames() const {
313   base::AutoLock lock_scope(lock_);
314   FrameHostList frames;
315   for (const auto& info : frame_info_set_) {
316     if (info->frame_ && !info->is_speculative_ && !info->is_in_bfcache_) {
317       frames.insert(info->frame_);
318     }
319   }
320   return frames;
321 }
322 
NavigationLock()323 CefBrowserInfo::NavigationLock::NavigationLock() : weak_ptr_factory_(this) {}
324 
~NavigationLock()325 CefBrowserInfo::NavigationLock::~NavigationLock() {
326   CEF_REQUIRE_UIT();
327   if (pending_action_) {
328     CEF_POST_TASK(CEF_UIT, std::move(pending_action_));
329   }
330 }
331 
332 scoped_refptr<CefBrowserInfo::NavigationLock>
CreateNavigationLock()333 CefBrowserInfo::CreateNavigationLock() {
334   CEF_REQUIRE_UIT();
335   scoped_refptr<NavigationLock> lock;
336   if (!navigation_lock_) {
337     lock = new NavigationLock();
338     navigation_lock_ = lock->weak_ptr_factory_.GetWeakPtr();
339   } else {
340     lock = navigation_lock_.get();
341   }
342   return lock;
343 }
344 
IsNavigationLocked(base::OnceClosure pending_action)345 bool CefBrowserInfo::IsNavigationLocked(base::OnceClosure pending_action) {
346   CEF_REQUIRE_UIT();
347   if (navigation_lock_) {
348     navigation_lock_->pending_action_ = std::move(pending_action);
349     return true;
350   }
351   return false;
352 }
353 
MaybeExecuteFrameNotification(FrameNotifyOnceAction pending_action)354 void CefBrowserInfo::MaybeExecuteFrameNotification(
355     FrameNotifyOnceAction pending_action) {
356   CefRefPtr<CefFrameHandler> frame_handler;
357 
358   {
359     base::AutoLock lock_scope_(notification_lock_);
360     if (!frame_handler_) {
361       // No notifications will be executed.
362       return;
363     }
364 
365     if (notification_state_lock_) {
366       // Queue the notification until the lock is released.
367       notification_state_lock_->queue_.push(std::move(pending_action));
368       return;
369     }
370 
371     frame_handler = frame_handler_;
372   }
373 
374   // Execute immediately if not locked.
375   std::move(pending_action).Run(frame_handler);
376 }
377 
MaybeNotifyDraggableRegionsChanged(CefRefPtr<CefBrowserHostBase> browser,CefRefPtr<CefFrameHostImpl> frame,std::vector<CefDraggableRegion> draggable_regions)378 void CefBrowserInfo::MaybeNotifyDraggableRegionsChanged(
379     CefRefPtr<CefBrowserHostBase> browser,
380     CefRefPtr<CefFrameHostImpl> frame,
381     std::vector<CefDraggableRegion> draggable_regions) {
382   CEF_REQUIRE_UIT();
383   DCHECK(frame->IsMain());
384 
385   if (draggable_regions == draggable_regions_)
386     return;
387 
388   draggable_regions_ = std::move(draggable_regions);
389 
390   if (auto client = browser->GetClient()) {
391     if (auto handler = client->GetDragHandler()) {
392       handler->OnDraggableRegionsChanged(browser.get(), frame,
393                                          draggable_regions_);
394     }
395   }
396 }
397 
398 // Passing in |browser| here because |browser_| may already be cleared.
SetMainFrame(CefRefPtr<CefBrowserHostBase> browser,CefRefPtr<CefFrameHostImpl> frame)399 void CefBrowserInfo::SetMainFrame(CefRefPtr<CefBrowserHostBase> browser,
400                                   CefRefPtr<CefFrameHostImpl> frame) {
401   lock_.AssertAcquired();
402   DCHECK(browser);
403   DCHECK(!frame || frame->IsMain());
404 
405   if (frame && main_frame_ &&
406       frame->GetIdentifier() == main_frame_->GetIdentifier()) {
407     // Nothing to do.
408     return;
409   }
410 
411   CefRefPtr<CefFrameHostImpl> old_frame;
412   if (main_frame_) {
413     old_frame = main_frame_;
414     if (old_frame->Detach())
415       MaybeNotifyFrameDetached(browser, old_frame);
416   }
417 
418   main_frame_ = frame;
419 
420   MaybeNotifyMainFrameChanged(browser, old_frame, main_frame_);
421 }
422 
MaybeNotifyFrameCreated(CefRefPtr<CefFrameHostImpl> frame)423 void CefBrowserInfo::MaybeNotifyFrameCreated(
424     CefRefPtr<CefFrameHostImpl> frame) {
425   CEF_REQUIRE_UIT();
426 
427   // Never notify for temporary objects.
428   DCHECK(!frame->is_temporary());
429 
430   MaybeExecuteFrameNotification(base::BindOnce(
431       [](scoped_refptr<CefBrowserInfo> self, CefRefPtr<CefFrameHostImpl> frame,
432          CefRefPtr<CefFrameHandler> handler) {
433         if (auto browser = self->browser()) {
434           handler->OnFrameCreated(browser, frame);
435         }
436       },
437       scoped_refptr<CefBrowserInfo>(this), frame));
438 }
439 
440 // Passing in |browser| here because |browser_| may already be cleared.
MaybeNotifyFrameDetached(CefRefPtr<CefBrowserHostBase> browser,CefRefPtr<CefFrameHostImpl> frame)441 void CefBrowserInfo::MaybeNotifyFrameDetached(
442     CefRefPtr<CefBrowserHostBase> browser,
443     CefRefPtr<CefFrameHostImpl> frame) {
444   CEF_REQUIRE_UIT();
445 
446   // Never notify for temporary objects.
447   DCHECK(!frame->is_temporary());
448 
449   MaybeExecuteFrameNotification(base::BindOnce(
450       [](CefRefPtr<CefBrowserHostBase> browser,
451          CefRefPtr<CefFrameHostImpl> frame,
452          CefRefPtr<CefFrameHandler> handler) {
453         handler->OnFrameDetached(browser, frame);
454       },
455       browser, frame));
456 }
457 
458 // Passing in |browser| here because |browser_| may already be cleared.
MaybeNotifyMainFrameChanged(CefRefPtr<CefBrowserHostBase> browser,CefRefPtr<CefFrameHostImpl> old_frame,CefRefPtr<CefFrameHostImpl> new_frame)459 void CefBrowserInfo::MaybeNotifyMainFrameChanged(
460     CefRefPtr<CefBrowserHostBase> browser,
461     CefRefPtr<CefFrameHostImpl> old_frame,
462     CefRefPtr<CefFrameHostImpl> new_frame) {
463   CEF_REQUIRE_UIT();
464 
465   // Never notify for temporary objects.
466   DCHECK(!old_frame || !old_frame->is_temporary());
467   DCHECK(!new_frame || !new_frame->is_temporary());
468 
469   MaybeExecuteFrameNotification(base::BindOnce(
470       [](CefRefPtr<CefBrowserHostBase> browser,
471          CefRefPtr<CefFrameHostImpl> old_frame,
472          CefRefPtr<CefFrameHostImpl> new_frame,
473          CefRefPtr<CefFrameHandler> handler) {
474         handler->OnMainFrameChanged(browser, old_frame, new_frame);
475       },
476       browser, old_frame, new_frame));
477 }
478 
RemoveAllFrames(CefRefPtr<CefBrowserHostBase> old_browser)479 void CefBrowserInfo::RemoveAllFrames(
480     CefRefPtr<CefBrowserHostBase> old_browser) {
481   lock_.AssertAcquired();
482 
483   // Make sure any callbacks will see the correct state (e.g. like
484   // CefBrowser::GetMainFrame returning nullptr and CefBrowser::IsValid
485   // returning false).
486   DCHECK(!browser_);
487   DCHECK(old_browser);
488 
489   // Clear the lookup maps.
490   frame_id_map_.clear();
491 
492   // Explicitly Detach everything but the current main frame.
493   for (auto& info : frame_info_set_) {
494     if (info->frame_ && !info->IsCurrentMainFrame()) {
495       if (info->frame_->Detach())
496         MaybeNotifyFrameDetached(old_browser, info->frame_);
497     }
498   }
499 
500   if (main_frame_)
501     SetMainFrame(old_browser, nullptr);
502 
503   // And finally delete the frame info.
504   frame_info_set_.clear();
505 }
506 
NotificationStateLock(CefBrowserInfo * browser_info)507 CefBrowserInfo::NotificationStateLock::NotificationStateLock(
508     CefBrowserInfo* browser_info)
509     : browser_info_(browser_info) {
510   CEF_REQUIRE_UIT();
511 
512   // Take the navigation state lock.
513   {
514     base::AutoLock lock_scope_(browser_info_->notification_lock_);
515     CHECK(!browser_info_->notification_state_lock_);
516     browser_info_->notification_state_lock_ = this;
517     // We may need this on destruction, and the original might be cleared.
518     frame_handler_ = browser_info_->frame_handler_;
519   }
520 
521   // Take the browser info state lock.
522   browser_info_lock_scope_.reset(new base::AutoLock(browser_info_->lock_));
523 }
524 
~NotificationStateLock()525 CefBrowserInfo::NotificationStateLock::~NotificationStateLock() {
526   CEF_REQUIRE_UIT();
527 
528   // Unlock in reverse order.
529   browser_info_lock_scope_.reset();
530 
531   {
532     base::AutoLock lock_scope_(browser_info_->notification_lock_);
533     CHECK_EQ(this, browser_info_->notification_state_lock_);
534     browser_info_->notification_state_lock_ = nullptr;
535   }
536 
537   if (!queue_.empty()) {
538     DCHECK(frame_handler_);
539 
540     // Don't navigate while inside callbacks.
541     auto nav_lock = browser_info_->CreateNavigationLock();
542 
543     // Empty the queue of pending actions. Any of these actions might result in
544     // the acquisition of a new NotificationStateLock.
545     while (!queue_.empty()) {
546       std::move(queue_.front()).Run(frame_handler_);
547       queue_.pop();
548     }
549   }
550 }
551