1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "extensions/browser/guest_view/guest_view_base.h"
7 #include "base/lazy_instance.h"
8 #include "base/strings/utf_string_conversions.h"
9 #include "components/ui/zoom/page_zoom.h"
10 #include "components/ui/zoom/zoom_controller.h"
11 #include "content/public/browser/navigation_details.h"
12 #include "content/public/browser/render_frame_host.h"
13 #include "content/public/browser/render_process_host.h"
14 #include "content/public/browser/render_view_host.h"
15 #include "content/public/browser/web_contents.h"
16 #include "content/public/common/page_zoom.h"
17 #include "content/public/common/url_constants.h"
18 #include "extensions/browser/api/extensions_api_client.h"
19 #include "extensions/browser/event_router.h"
20 #include "extensions/browser/extension_registry.h"
21 #include "extensions/browser/guest_view/app_view/app_view_guest.h"
22 #include "extensions/browser/guest_view/extension_options/extension_options_guest.h"
23 #include "extensions/browser/guest_view/guest_view_manager.h"
24 #include "extensions/browser/guest_view/mime_handler_view/mime_handler_view_guest.h"
25 #include "extensions/browser/guest_view/surface_worker/surface_worker_guest.h"
26 #include "extensions/browser/guest_view/web_view/web_view_guest.h"
27 #include "extensions/browser/process_manager.h"
28 #include "extensions/browser/process_map.h"
29 #include "extensions/common/extension_messages.h"
30 #include "extensions/common/features/feature.h"
31 #include "extensions/common/features/feature_provider.h"
32 #include "extensions/common/guest_view/guest_view_constants.h"
33 #include "third_party/WebKit/public/web/WebInputEvent.h"
35 using content::WebContents
;
38 struct FrameNavigateParams
;
41 namespace extensions
{
45 using GuestViewCreationMap
=
46 std::map
<std::string
, GuestViewBase::GuestCreationCallback
>;
47 static base::LazyInstance
<GuestViewCreationMap
> guest_view_registry
=
48 LAZY_INSTANCE_INITIALIZER
;
50 using WebContentsGuestViewMap
= std::map
<WebContents
*, GuestViewBase
*>;
51 static base::LazyInstance
<WebContentsGuestViewMap
> webcontents_guestview_map
=
52 LAZY_INSTANCE_INITIALIZER
;
56 GuestViewBase::Event::Event(const std::string
& name
,
57 scoped_ptr
<base::DictionaryValue
> args
)
58 : name_(name
), args_(args
.Pass()) {
61 GuestViewBase::Event::~Event() {
64 scoped_ptr
<base::DictionaryValue
> GuestViewBase::Event::GetArguments() {
68 // This observer ensures that the GuestViewBase destroys itself when its
69 // embedder goes away.
70 class GuestViewBase::OwnerLifetimeObserver
: public WebContentsObserver
{
72 OwnerLifetimeObserver(GuestViewBase
* guest
,
73 content::WebContents
* embedder_web_contents
)
74 : WebContentsObserver(embedder_web_contents
),
78 ~OwnerLifetimeObserver() override
{}
80 // WebContentsObserver implementation.
81 void WebContentsDestroyed() override
{
82 // If the embedder is destroyed then destroy the guest.
86 void DidNavigateMainFrame(
87 const content::LoadCommittedDetails
& details
,
88 const content::FrameNavigateParams
& params
) override
{
89 // If the embedder navigates to a different page then destroy the guest.
90 if (details
.is_navigation_to_different_page())
94 void RenderProcessGone(base::TerminationStatus status
) override
{
95 // If the embedder crashes, then destroy the guest.
101 GuestViewBase
* guest_
;
108 guest_
->EmbedderWillBeDestroyed();
112 DISALLOW_COPY_AND_ASSIGN(OwnerLifetimeObserver
);
115 // This observer ensures that the GuestViewBase destroys itself when its
116 // embedder goes away.
117 class GuestViewBase::OpenerLifetimeObserver
: public WebContentsObserver
{
119 OpenerLifetimeObserver(GuestViewBase
* guest
)
120 : WebContentsObserver(guest
->GetOpener()->web_contents()),
123 ~OpenerLifetimeObserver() override
{}
125 // WebContentsObserver implementation.
126 void WebContentsDestroyed() override
{
127 if (guest_
->attached())
130 // If the opener is destroyed then destroy the guest.
135 GuestViewBase
* guest_
;
137 DISALLOW_COPY_AND_ASSIGN(OpenerLifetimeObserver
);
140 GuestViewBase::GuestViewBase(content::WebContents
* owner_web_contents
,
141 int guest_instance_id
)
142 : owner_web_contents_(owner_web_contents
),
143 browser_context_(owner_web_contents
->GetBrowserContext()),
144 guest_instance_id_(guest_instance_id
),
145 view_instance_id_(guestview::kInstanceIDNone
),
146 element_instance_id_(guestview::kInstanceIDNone
),
148 is_being_destroyed_(false),
149 guest_sizer_(nullptr),
150 auto_size_enabled_(false),
151 is_full_page_plugin_(false),
152 weak_ptr_factory_(this) {
155 void GuestViewBase::Init(const base::DictionaryValue
& create_params
,
156 const WebContentsCreatedCallback
& callback
) {
161 const Feature
* feature
= FeatureProvider::GetAPIFeature(GetAPINamespace());
164 ProcessMap
* process_map
= ProcessMap::Get(browser_context());
167 const Extension
* owner_extension
=
168 ProcessManager::Get(owner_web_contents()->GetBrowserContext())->
169 GetExtensionForRenderViewHost(
170 owner_web_contents()->GetRenderViewHost());
171 owner_extension_id_
= owner_extension
? owner_extension
->id() : std::string();
173 // Ok for |owner_extension| to be nullptr, the embedder might be WebUI.
174 Feature::Availability availability
= feature
->IsAvailableToContext(
176 process_map
->GetMostLikelyContextType(
178 owner_web_contents()->GetRenderProcessHost()->GetID()),
180 if (!availability
.is_available()) {
181 // The derived class did not create a WebContents so this class serves no
182 // purpose. Let's self-destruct.
184 callback
.Run(nullptr);
188 scoped_ptr
<base::DictionaryValue
> params(create_params
.DeepCopy());
189 CreateWebContents(create_params
,
190 base::Bind(&GuestViewBase::CompleteInit
,
191 weak_ptr_factory_
.GetWeakPtr(),
192 base::Passed(¶ms
),
196 void GuestViewBase::InitWithWebContents(
197 const base::DictionaryValue
& create_params
,
198 content::WebContents
* guest_web_contents
) {
199 DCHECK(guest_web_contents
);
201 // At this point, we have just created the guest WebContents, we need to add
202 // an observer to the owner WebContents. This observer will be responsible
203 // for destroying the guest WebContents if the owner goes away.
204 owner_lifetime_observer_
.reset(
205 new OwnerLifetimeObserver(this, owner_web_contents_
));
207 WebContentsObserver::Observe(guest_web_contents
);
208 guest_web_contents
->SetDelegate(this);
209 webcontents_guestview_map
.Get().insert(
210 std::make_pair(guest_web_contents
, this));
211 GuestViewManager::FromBrowserContext(browser_context_
)->
212 AddGuest(guest_instance_id_
, guest_web_contents
);
214 // Create a ZoomController to allow the guest's contents to be zoomed.
215 ui_zoom::ZoomController::CreateForWebContents(guest_web_contents
);
217 // Populate the view instance ID if we have it on creation.
218 create_params
.GetInteger(guestview::kParameterInstanceId
,
221 if (CanRunInDetachedState())
222 SetUpAutoSize(create_params
);
224 // Give the derived class an opportunity to perform additional initialization.
225 DidInitialize(create_params
);
228 void GuestViewBase::DispatchOnResizeEvent(const gfx::Size
& old_size
,
229 const gfx::Size
& new_size
) {
230 if (new_size
== old_size
)
233 // Dispatch the onResize event.
234 scoped_ptr
<base::DictionaryValue
> args(new base::DictionaryValue());
235 args
->SetInteger(guestview::kOldWidth
, old_size
.width());
236 args
->SetInteger(guestview::kOldHeight
, old_size
.height());
237 args
->SetInteger(guestview::kNewWidth
, new_size
.width());
238 args
->SetInteger(guestview::kNewHeight
, new_size
.height());
239 DispatchEventToGuestProxy(new Event(guestview::kEventResize
, args
.Pass()));
242 void GuestViewBase::SetAutoSize(bool enabled
,
243 const gfx::Size
& min_size
,
244 const gfx::Size
& max_size
) {
245 min_auto_size_
= min_size
;
246 min_auto_size_
.SetToMin(max_size
);
247 max_auto_size_
= max_size
;
248 max_auto_size_
.SetToMax(min_size
);
250 enabled
&= !min_auto_size_
.IsEmpty() && !max_auto_size_
.IsEmpty() &&
251 IsAutoSizeSupported();
252 if (!enabled
&& !auto_size_enabled_
)
255 auto_size_enabled_
= enabled
;
257 if (!attached() && !CanRunInDetachedState())
260 content::RenderViewHost
* rvh
= web_contents()->GetRenderViewHost();
261 if (auto_size_enabled_
) {
262 rvh
->EnableAutoResize(min_auto_size_
, max_auto_size_
);
264 rvh
->DisableAutoResize(element_size_
);
265 DispatchOnResizeEvent(guest_size_
, element_size_
);
266 GuestSizeChangedDueToAutoSize(guest_size_
, element_size_
);
267 guest_size_
= element_size_
;
272 void GuestViewBase::RegisterGuestViewType(
273 const std::string
& view_type
,
274 const GuestCreationCallback
& callback
) {
275 auto it
= guest_view_registry
.Get().find(view_type
);
276 DCHECK(it
== guest_view_registry
.Get().end());
277 guest_view_registry
.Get()[view_type
] = callback
;
281 GuestViewBase
* GuestViewBase::Create(
282 content::WebContents
* owner_web_contents
,
283 int guest_instance_id
,
284 const std::string
& view_type
) {
285 if (guest_view_registry
.Get().empty())
286 RegisterGuestViewTypes();
288 auto it
= guest_view_registry
.Get().find(view_type
);
289 if (it
== guest_view_registry
.Get().end()) {
293 return it
->second
.Run(owner_web_contents
, guest_instance_id
);
297 GuestViewBase
* GuestViewBase::FromWebContents(WebContents
* web_contents
) {
298 WebContentsGuestViewMap
* guest_map
= webcontents_guestview_map
.Pointer();
299 auto it
= guest_map
->find(web_contents
);
300 return it
== guest_map
->end() ? nullptr : it
->second
;
304 GuestViewBase
* GuestViewBase::From(int owner_process_id
,
305 int guest_instance_id
) {
306 auto host
= content::RenderProcessHost::FromID(owner_process_id
);
310 content::WebContents
* guest_web_contents
=
311 GuestViewManager::FromBrowserContext(host
->GetBrowserContext())->
312 GetGuestByInstanceIDSafely(guest_instance_id
, owner_process_id
);
313 if (!guest_web_contents
)
316 return GuestViewBase::FromWebContents(guest_web_contents
);
320 bool GuestViewBase::IsGuest(WebContents
* web_contents
) {
321 return !!GuestViewBase::FromWebContents(web_contents
);
324 bool GuestViewBase::IsAutoSizeSupported() const {
328 bool GuestViewBase::IsPreferredSizeModeEnabled() const {
332 bool GuestViewBase::IsDragAndDropEnabled() const {
336 bool GuestViewBase::ZoomPropagatesFromEmbedderToGuest() const {
340 content::WebContents
* GuestViewBase::CreateNewGuestWindow(
341 const content::WebContents::CreateParams
& create_params
) {
342 auto guest_manager
= GuestViewManager::FromBrowserContext(browser_context());
343 return guest_manager
->CreateGuestWithWebContentsParams(
345 owner_web_contents(),
349 void GuestViewBase::DidAttach(int guest_proxy_routing_id
) {
350 opener_lifetime_observer_
.reset();
352 SetUpAutoSize(*attach_params());
354 // Give the derived class an opportunity to perform some actions.
355 DidAttachToEmbedder();
357 // Inform the associated GuestViewContainer that the contentWindow is ready.
358 embedder_web_contents()->Send(new ExtensionMsg_GuestAttached(
359 element_instance_id_
,
360 guest_proxy_routing_id
));
365 void GuestViewBase::DidDetach() {
366 GuestViewManager::FromBrowserContext(browser_context_
)->DetachGuest(
367 this, element_instance_id_
);
368 StopTrackingEmbedderZoomLevel();
369 owner_web_contents()->Send(new ExtensionMsg_GuestDetached(
370 element_instance_id_
));
371 element_instance_id_
= guestview::kInstanceIDNone
;
374 void GuestViewBase::ElementSizeChanged(const gfx::Size
& size
) {
375 element_size_
= size
;
377 // Only resize if needed.
378 if (!size
.IsEmpty()) {
379 guest_sizer_
->SizeContents(size
);
384 WebContents
* GuestViewBase::GetOwnerWebContents() const {
385 return owner_web_contents_
;
388 void GuestViewBase::GuestSizeChanged(const gfx::Size
& old_size
,
389 const gfx::Size
& new_size
) {
390 DispatchOnResizeEvent(old_size
, new_size
);
391 if (!auto_size_enabled_
)
393 guest_size_
= new_size
;
394 GuestSizeChangedDueToAutoSize(old_size
, new_size
);
397 const GURL
& GuestViewBase::GetOwnerSiteURL() const {
398 return owner_web_contents()->GetLastCommittedURL();
401 void GuestViewBase::Destroy() {
402 if (is_being_destroyed_
)
405 is_being_destroyed_
= true;
407 guest_sizer_
= nullptr;
409 // It is important to clear owner_web_contents_ after the call to
410 // StopTrackingEmbedderZoomLevel(), but before the rest of
411 // the statements in this function.
412 StopTrackingEmbedderZoomLevel();
413 owner_web_contents_
= nullptr;
415 DCHECK(web_contents());
417 // Give the derived class an opportunity to perform some cleanup.
420 // Invalidate weak pointers now so that bound callbacks cannot be called late
421 // into destruction. We must call this after WillDestroy because derived types
422 // may wish to access their openers.
423 weak_ptr_factory_
.InvalidateWeakPtrs();
425 // Give the content module an opportunity to perform some cleanup.
426 if (!destruction_callback_
.is_null())
427 destruction_callback_
.Run();
429 webcontents_guestview_map
.Get().erase(web_contents());
430 GuestViewManager::FromBrowserContext(browser_context_
)->
431 RemoveGuest(guest_instance_id_
);
432 pending_events_
.clear();
434 delete web_contents();
437 void GuestViewBase::SetAttachParams(const base::DictionaryValue
& params
) {
438 attach_params_
.reset(params
.DeepCopy());
439 attach_params_
->GetInteger(guestview::kParameterInstanceId
,
443 void GuestViewBase::SetOpener(GuestViewBase
* guest
) {
444 if (guest
&& guest
->IsViewType(GetViewType())) {
445 opener_
= guest
->weak_ptr_factory_
.GetWeakPtr();
447 opener_lifetime_observer_
.reset(new OpenerLifetimeObserver(this));
450 opener_
= base::WeakPtr
<GuestViewBase
>();
451 opener_lifetime_observer_
.reset();
454 void GuestViewBase::RegisterDestructionCallback(
455 const DestructionCallback
& callback
) {
456 destruction_callback_
= callback
;
459 void GuestViewBase::SetGuestSizer(content::GuestSizer
* guest_sizer
) {
460 guest_sizer_
= guest_sizer
;
463 void GuestViewBase::WillAttach(content::WebContents
* embedder_web_contents
,
464 int element_instance_id
,
465 bool is_full_page_plugin
) {
466 if (owner_web_contents_
!= embedder_web_contents
) {
467 DCHECK_EQ(owner_lifetime_observer_
->web_contents(), owner_web_contents_
);
468 // Stop tracking the old embedder's zoom level.
469 StopTrackingEmbedderZoomLevel();
470 owner_web_contents_
= embedder_web_contents
;
471 owner_lifetime_observer_
.reset(
472 new OwnerLifetimeObserver(this, embedder_web_contents
));
475 // Start tracking the new embedder's zoom level.
476 StartTrackingEmbedderZoomLevel();
477 element_instance_id_
= element_instance_id
;
478 is_full_page_plugin_
= is_full_page_plugin
;
480 WillAttachToEmbedder();
483 void GuestViewBase::DidStopLoading(content::RenderViewHost
* render_view_host
) {
484 if (IsPreferredSizeModeEnabled()) {
485 render_view_host
->EnablePreferredSizeMode();
487 if (!IsDragAndDropEnabled()) {
488 const char script
[] = "window.addEventListener('dragstart', function() { "
489 " window.event.preventDefault(); "
491 render_view_host
->GetMainFrame()->ExecuteJavaScript(
492 base::ASCIIToUTF16(script
));
497 void GuestViewBase::RenderViewReady() {
501 void GuestViewBase::WebContentsDestroyed() {
502 // Let the derived class know that its WebContents is in the process of
503 // being destroyed. web_contents() is still valid at this point.
504 // TODO(fsamuel): This allows for reentrant code into WebContents during
505 // destruction. This could potentially lead to bugs. Perhaps we should get rid
513 void GuestViewBase::ActivateContents(WebContents
* web_contents
) {
514 if (!attached() || !embedder_web_contents()->GetDelegate())
517 embedder_web_contents()->GetDelegate()->ActivateContents(
518 embedder_web_contents());
521 void GuestViewBase::DeactivateContents(WebContents
* web_contents
) {
522 if (!attached() || !embedder_web_contents()->GetDelegate())
525 embedder_web_contents()->GetDelegate()->DeactivateContents(
526 embedder_web_contents());
529 void GuestViewBase::ContentsZoomChange(bool zoom_in
) {
530 ui_zoom::PageZoom::Zoom(
531 embedder_web_contents(),
532 zoom_in
? content::PAGE_ZOOM_IN
: content::PAGE_ZOOM_OUT
);
535 void GuestViewBase::HandleKeyboardEvent(
537 const content::NativeWebKeyboardEvent
& event
) {
541 // Send the keyboard events back to the embedder to reprocess them.
542 embedder_web_contents()->GetDelegate()->HandleKeyboardEvent(web_contents(),
546 void GuestViewBase::RunFileChooser(WebContents
* web_contents
,
547 const content::FileChooserParams
& params
) {
548 if (!attached() || !embedder_web_contents()->GetDelegate())
551 embedder_web_contents()->GetDelegate()->RunFileChooser(web_contents
, params
);
554 bool GuestViewBase::ShouldFocusPageAfterCrash() {
555 // Focus is managed elsewhere.
559 bool GuestViewBase::PreHandleGestureEvent(content::WebContents
* source
,
560 const blink::WebGestureEvent
& event
) {
561 return event
.type
== blink::WebGestureEvent::GesturePinchBegin
||
562 event
.type
== blink::WebGestureEvent::GesturePinchUpdate
||
563 event
.type
== blink::WebGestureEvent::GesturePinchEnd
;
566 void GuestViewBase::UpdatePreferredSize(
567 content::WebContents
* target_web_contents
,
568 const gfx::Size
& pref_size
) {
569 // In theory it's not necessary to check IsPreferredSizeModeEnabled() because
570 // there will only be events if it was enabled in the first place. However,
571 // something else may have turned on preferred size mode, so double check.
572 DCHECK_EQ(web_contents(), target_web_contents
);
573 if (IsPreferredSizeModeEnabled()) {
574 OnPreferredSizeChanged(pref_size
);
578 GuestViewBase::~GuestViewBase() {
581 void GuestViewBase::OnZoomChanged(
582 const ui_zoom::ZoomController::ZoomChangedEventData
& data
) {
583 auto guest_zoom_controller
=
584 ui_zoom::ZoomController::FromWebContents(web_contents());
585 if (content::ZoomValuesEqual(data
.new_zoom_level
,
586 guest_zoom_controller
->GetZoomLevel())) {
589 // When the embedder's zoom level doesn't match the guest's, then update the
590 // guest's zoom level to match.
591 guest_zoom_controller
->SetZoomLevel(data
.new_zoom_level
);
594 void GuestViewBase::DispatchEventToGuestProxy(Event
* event
) {
595 DispatchEvent(event
, guest_instance_id_
);
598 void GuestViewBase::DispatchEventToView(Event
* event
) {
600 (!CanRunInDetachedState() || !can_owner_receive_events())) {
601 pending_events_
.push_back(linked_ptr
<Event
>(event
));
605 DispatchEvent(event
, view_instance_id_
);
608 void GuestViewBase::DispatchEvent(Event
* event
, int instance_id
) {
609 scoped_ptr
<Event
> event_ptr(event
);
611 EventFilteringInfo info
;
612 info
.SetInstanceID(instance_id
);
613 scoped_ptr
<base::ListValue
> args(new base::ListValue());
614 args
->Append(event
->GetArguments().release());
616 EventRouter::DispatchEvent(
622 EventRouter::USER_GESTURE_UNKNOWN
,
626 void GuestViewBase::SendQueuedEvents() {
629 while (!pending_events_
.empty()) {
630 linked_ptr
<Event
> event_ptr
= pending_events_
.front();
631 pending_events_
.pop_front();
632 DispatchEvent(event_ptr
.release(), view_instance_id_
);
636 void GuestViewBase::CompleteInit(
637 scoped_ptr
<base::DictionaryValue
> create_params
,
638 const WebContentsCreatedCallback
& callback
,
639 content::WebContents
* guest_web_contents
) {
640 if (!guest_web_contents
) {
641 // The derived class did not create a WebContents so this class serves no
642 // purpose. Let's self-destruct.
644 callback
.Run(nullptr);
647 InitWithWebContents(*create_params
, guest_web_contents
);
648 callback
.Run(guest_web_contents
);
651 void GuestViewBase::SetUpAutoSize(const base::DictionaryValue
& params
) {
652 // Read the autosize parameters passed in from the embedder.
653 bool auto_size_enabled
= false;
654 params
.GetBoolean(guestview::kAttributeAutoSize
, &auto_size_enabled
);
658 params
.GetInteger(guestview::kAttributeMaxHeight
, &max_height
);
659 params
.GetInteger(guestview::kAttributeMaxWidth
, &max_width
);
663 params
.GetInteger(guestview::kAttributeMinHeight
, &min_height
);
664 params
.GetInteger(guestview::kAttributeMinWidth
, &min_width
);
666 // Call SetAutoSize to apply all the appropriate validation and clipping of
668 SetAutoSize(auto_size_enabled
,
669 gfx::Size(min_width
, min_height
),
670 gfx::Size(max_width
, max_height
));
673 void GuestViewBase::StartTrackingEmbedderZoomLevel() {
674 if (!ZoomPropagatesFromEmbedderToGuest())
677 auto embedder_zoom_controller
=
678 ui_zoom::ZoomController::FromWebContents(owner_web_contents());
679 // Chrome Apps do not have a ZoomController.
680 if (!embedder_zoom_controller
)
682 // Listen to the embedder's zoom changes.
683 embedder_zoom_controller
->AddObserver(this);
684 // Set the guest's initial zoom level to be equal to the embedder's.
685 ui_zoom::ZoomController::FromWebContents(web_contents())
686 ->SetZoomLevel(embedder_zoom_controller
->GetZoomLevel());
689 void GuestViewBase::StopTrackingEmbedderZoomLevel() {
690 if (!attached() || !ZoomPropagatesFromEmbedderToGuest())
693 auto embedder_zoom_controller
=
694 ui_zoom::ZoomController::FromWebContents(owner_web_contents());
695 // Chrome Apps do not have a ZoomController.
696 if (!embedder_zoom_controller
)
698 embedder_zoom_controller
->RemoveObserver(this);
702 void GuestViewBase::RegisterGuestViewTypes() {
703 AppViewGuest::Register();
704 ExtensionOptionsGuest::Register();
705 MimeHandlerViewGuest::Register();
706 SurfaceWorkerGuest::Register();
707 WebViewGuest::Register();
710 } // namespace extensions