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