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.
4
5#include "extensions/browser/guest_view/guest_view_base.h"
6
7#include "base/lazy_instance.h"
8#include "base/strings/utf_string_conversions.h"
9#include "content/public/browser/render_frame_host.h"
10#include "content/public/browser/render_process_host.h"
11#include "content/public/browser/render_view_host.h"
12#include "content/public/browser/web_contents.h"
13#include "content/public/common/url_constants.h"
14#include "extensions/browser/api/extensions_api_client.h"
15#include "extensions/browser/event_router.h"
16#include "extensions/browser/extension_registry.h"
17#include "extensions/browser/guest_view/app_view/app_view_guest.h"
18#include "extensions/browser/guest_view/extension_options/extension_options_guest.h"
19#include "extensions/browser/guest_view/guest_view_manager.h"
20#include "extensions/browser/guest_view/mime_handler_view/mime_handler_view_guest.h"
21#include "extensions/browser/guest_view/web_view/web_view_guest.h"
22#include "extensions/browser/process_map.h"
23#include "extensions/common/extension_messages.h"
24#include "extensions/common/features/feature.h"
25#include "extensions/common/features/feature_provider.h"
26#include "extensions/common/guest_view/guest_view_constants.h"
27#include "third_party/WebKit/public/web/WebInputEvent.h"
28
29using content::WebContents;
30
31namespace extensions {
32
33namespace {
34
35typedef std::map<std::string, GuestViewBase::GuestCreationCallback>
36    GuestViewCreationMap;
37static base::LazyInstance<GuestViewCreationMap> guest_view_registry =
38    LAZY_INSTANCE_INITIALIZER;
39
40typedef std::map<WebContents*, GuestViewBase*> WebContentsGuestViewMap;
41static base::LazyInstance<WebContentsGuestViewMap> webcontents_guestview_map =
42    LAZY_INSTANCE_INITIALIZER;
43
44}  // namespace
45
46GuestViewBase::Event::Event(const std::string& name,
47                            scoped_ptr<base::DictionaryValue> args)
48    : name_(name), args_(args.Pass()) {
49}
50
51GuestViewBase::Event::~Event() {
52}
53
54scoped_ptr<base::DictionaryValue> GuestViewBase::Event::GetArguments() {
55  return args_.Pass();
56}
57
58// This observer ensures that the GuestViewBase destroys itself when its
59// embedder goes away.
60class GuestViewBase::EmbedderWebContentsObserver : public WebContentsObserver {
61 public:
62  explicit EmbedderWebContentsObserver(GuestViewBase* guest)
63      : WebContentsObserver(guest->embedder_web_contents()),
64        destroyed_(false),
65        guest_(guest) {
66  }
67
68  virtual ~EmbedderWebContentsObserver() {
69  }
70
71  // WebContentsObserver implementation.
72  virtual void WebContentsDestroyed() OVERRIDE {
73    Destroy();
74  }
75
76  virtual void RenderViewHostChanged(
77      content::RenderViewHost* old_host,
78      content::RenderViewHost* new_host) OVERRIDE {
79    Destroy();
80  }
81
82  virtual void RenderProcessGone(base::TerminationStatus status) OVERRIDE {
83    Destroy();
84  }
85
86 private:
87  bool destroyed_;
88  GuestViewBase* guest_;
89
90  void Destroy() {
91    if (destroyed_)
92      return;
93    destroyed_ = true;
94    guest_->embedder_web_contents_ = NULL;
95    guest_->EmbedderDestroyed();
96    guest_->Destroy();
97  }
98
99  DISALLOW_COPY_AND_ASSIGN(EmbedderWebContentsObserver);
100};
101
102GuestViewBase::GuestViewBase(content::BrowserContext* browser_context,
103                             int guest_instance_id)
104    : embedder_web_contents_(NULL),
105      embedder_render_process_id_(0),
106      browser_context_(browser_context),
107      guest_instance_id_(guest_instance_id),
108      view_instance_id_(guestview::kInstanceIDNone),
109      element_instance_id_(guestview::kInstanceIDNone),
110      initialized_(false),
111      auto_size_enabled_(false),
112      weak_ptr_factory_(this) {
113}
114
115void GuestViewBase::Init(const std::string& embedder_extension_id,
116                         content::WebContents* embedder_web_contents,
117                         const base::DictionaryValue& create_params,
118                         const WebContentsCreatedCallback& callback) {
119  if (initialized_)
120    return;
121  initialized_ = true;
122
123  Feature* feature = FeatureProvider::GetAPIFeatures()->GetFeature(
124      GetAPINamespace());
125  CHECK(feature);
126
127  ProcessMap* process_map = ProcessMap::Get(browser_context());
128  CHECK(process_map);
129
130  const Extension* embedder_extension = ExtensionRegistry::Get(browser_context_)
131          ->enabled_extensions()
132          .GetByID(embedder_extension_id);
133  // Ok for |embedder_extension| to be NULL, the embedder might be WebUI.
134
135  CHECK(embedder_web_contents);
136  int embedder_process_id =
137      embedder_web_contents->GetRenderProcessHost()->GetID();
138
139  const GURL& embedder_site_url = embedder_web_contents->GetLastCommittedURL();
140  Feature::Availability availability = feature->IsAvailableToContext(
141      embedder_extension,
142      process_map->GetMostLikelyContextType(embedder_extension,
143                                            embedder_process_id),
144      embedder_site_url);
145  if (!availability.is_available()) {
146    // The derived class did not create a WebContents so this class serves no
147    // purpose. Let's self-destruct.
148    delete this;
149    callback.Run(NULL);
150    return;
151  }
152
153  CreateWebContents(embedder_extension_id,
154                    embedder_process_id,
155                    embedder_site_url,
156                    create_params,
157                    base::Bind(&GuestViewBase::CompleteInit,
158                               AsWeakPtr(),
159                               embedder_extension_id,
160                               embedder_process_id,
161                               callback));
162}
163
164void GuestViewBase::InitWithWebContents(
165    const std::string& embedder_extension_id,
166    int embedder_render_process_id,
167    content::WebContents* guest_web_contents) {
168  DCHECK(guest_web_contents);
169  content::RenderProcessHost* embedder_render_process_host =
170      content::RenderProcessHost::FromID(embedder_render_process_id);
171
172  embedder_extension_id_ = embedder_extension_id;
173  embedder_render_process_id_ = embedder_render_process_host->GetID();
174  embedder_render_process_host->AddObserver(this);
175
176  WebContentsObserver::Observe(guest_web_contents);
177  guest_web_contents->SetDelegate(this);
178  webcontents_guestview_map.Get().insert(
179      std::make_pair(guest_web_contents, this));
180  GuestViewManager::FromBrowserContext(browser_context_)->
181      AddGuest(guest_instance_id_, guest_web_contents);
182
183  // Give the derived class an opportunity to perform additional initialization.
184  DidInitialize();
185}
186
187void GuestViewBase::SetAutoSize(bool enabled,
188                                const gfx::Size& min_size,
189                                const gfx::Size& max_size) {
190  min_auto_size_ = min_size;
191  min_auto_size_.SetToMin(max_size);
192  max_auto_size_ = max_size;
193  max_auto_size_.SetToMax(min_size);
194
195  enabled &= !min_auto_size_.IsEmpty() && !max_auto_size_.IsEmpty() &&
196      IsAutoSizeSupported();
197  if (!enabled && !auto_size_enabled_)
198    return;
199
200  auto_size_enabled_ = enabled;
201
202  if (!attached())
203    return;
204
205  content::RenderViewHost* rvh = web_contents()->GetRenderViewHost();
206  if (auto_size_enabled_) {
207    rvh->EnableAutoResize(min_auto_size_, max_auto_size_);
208  } else {
209    rvh->DisableAutoResize(element_size_);
210    guest_size_ = element_size_;
211    GuestSizeChangedDueToAutoSize(guest_size_, element_size_);
212  }
213}
214
215// static
216void GuestViewBase::RegisterGuestViewType(
217    const std::string& view_type,
218    const GuestCreationCallback& callback) {
219  GuestViewCreationMap::iterator it =
220      guest_view_registry.Get().find(view_type);
221  DCHECK(it == guest_view_registry.Get().end());
222  guest_view_registry.Get()[view_type] = callback;
223}
224
225// static
226GuestViewBase* GuestViewBase::Create(
227    content::BrowserContext* browser_context,
228    int guest_instance_id,
229    const std::string& view_type) {
230  if (guest_view_registry.Get().empty())
231    RegisterGuestViewTypes();
232
233  GuestViewCreationMap::iterator it =
234      guest_view_registry.Get().find(view_type);
235  if (it == guest_view_registry.Get().end()) {
236    NOTREACHED();
237    return NULL;
238  }
239  return it->second.Run(browser_context, guest_instance_id);
240}
241
242// static
243GuestViewBase* GuestViewBase::FromWebContents(WebContents* web_contents) {
244  WebContentsGuestViewMap* guest_map = webcontents_guestview_map.Pointer();
245  WebContentsGuestViewMap::iterator it = guest_map->find(web_contents);
246  return it == guest_map->end() ? NULL : it->second;
247}
248
249// static
250GuestViewBase* GuestViewBase::From(int embedder_process_id,
251                                   int guest_instance_id) {
252  content::RenderProcessHost* host =
253      content::RenderProcessHost::FromID(embedder_process_id);
254  if (!host)
255    return NULL;
256
257  content::WebContents* guest_web_contents =
258      GuestViewManager::FromBrowserContext(host->GetBrowserContext())->
259          GetGuestByInstanceIDSafely(guest_instance_id, embedder_process_id);
260  if (!guest_web_contents)
261    return NULL;
262
263  return GuestViewBase::FromWebContents(guest_web_contents);
264}
265
266// static
267bool GuestViewBase::IsGuest(WebContents* web_contents) {
268  return !!GuestViewBase::FromWebContents(web_contents);
269}
270
271base::WeakPtr<GuestViewBase> GuestViewBase::AsWeakPtr() {
272  return weak_ptr_factory_.GetWeakPtr();
273}
274
275bool GuestViewBase::IsAutoSizeSupported() const {
276  return false;
277}
278
279bool GuestViewBase::IsDragAndDropEnabled() const {
280  return false;
281}
282
283void GuestViewBase::RenderProcessExited(content::RenderProcessHost* host,
284                                        base::ProcessHandle handle,
285                                        base::TerminationStatus status,
286                                        int exit_code) {
287  // GuestViewBase tracks the lifetime of its embedder render process until it
288  // is attached to a particular embedder WebContents. At that point, its
289  // lifetime is restricted in scope to the lifetime of its embedder
290  // WebContents.
291  CHECK(!attached());
292  CHECK_EQ(host->GetID(), embedder_render_process_id());
293
294  // This code path may be reached if the embedder WebContents is killed for
295  // whatever reason immediately after a called to GuestViewInternal.createGuest
296  // and before attaching the new guest to a frame.
297  Destroy();
298}
299
300void GuestViewBase::Destroy() {
301  DCHECK(web_contents());
302  content::RenderProcessHost* host =
303      content::RenderProcessHost::FromID(embedder_render_process_id());
304  if (host)
305    host->RemoveObserver(this);
306  WillDestroy();
307  if (!destruction_callback_.is_null())
308    destruction_callback_.Run();
309
310  webcontents_guestview_map.Get().erase(web_contents());
311  GuestViewManager::FromBrowserContext(browser_context_)->
312      RemoveGuest(guest_instance_id_);
313  pending_events_.clear();
314
315  delete web_contents();
316}
317
318void GuestViewBase::DidAttach(int guest_proxy_routing_id) {
319  // Give the derived class an opportunity to perform some actions.
320  DidAttachToEmbedder();
321
322  // Inform the associated GuestViewContainer that the contentWindow is ready.
323  embedder_web_contents()->Send(new ExtensionMsg_GuestAttached(
324      embedder_web_contents()->GetMainFrame()->GetRoutingID(),
325      element_instance_id_,
326      guest_proxy_routing_id));
327
328  SendQueuedEvents();
329}
330
331void GuestViewBase::ElementSizeChanged(const gfx::Size& old_size,
332                                       const gfx::Size& new_size) {
333  element_size_ = new_size;
334}
335
336void GuestViewBase::GuestSizeChanged(const gfx::Size& old_size,
337                                     const gfx::Size& new_size) {
338  if (!auto_size_enabled_)
339    return;
340  guest_size_ = new_size;
341  GuestSizeChangedDueToAutoSize(old_size, new_size);
342}
343
344void GuestViewBase::SetAttachParams(const base::DictionaryValue& params) {
345  attach_params_.reset(params.DeepCopy());
346  attach_params_->GetInteger(guestview::kParameterInstanceId,
347                             &view_instance_id_);
348}
349
350void GuestViewBase::SetOpener(GuestViewBase* guest) {
351  if (guest && guest->IsViewType(GetViewType())) {
352    opener_ = guest->AsWeakPtr();
353    return;
354  }
355  opener_ = base::WeakPtr<GuestViewBase>();
356}
357
358void GuestViewBase::RegisterDestructionCallback(
359    const DestructionCallback& callback) {
360  destruction_callback_ = callback;
361}
362
363void GuestViewBase::WillAttach(content::WebContents* embedder_web_contents,
364                               int element_instance_id) {
365  // After attachment, this GuestViewBase's lifetime is restricted to the
366  // lifetime of its embedder WebContents. Observing the RenderProcessHost
367  // of the embedder is no longer necessary.
368  embedder_web_contents->GetRenderProcessHost()->RemoveObserver(this);
369  embedder_web_contents_ = embedder_web_contents;
370  embedder_web_contents_observer_.reset(
371      new EmbedderWebContentsObserver(this));
372  element_instance_id_ = element_instance_id;
373
374  WillAttachToEmbedder();
375}
376
377void GuestViewBase::DidStopLoading(content::RenderViewHost* render_view_host) {
378  if (!IsDragAndDropEnabled()) {
379    const char script[] = "window.addEventListener('dragstart', function() { "
380                          "  window.event.preventDefault(); "
381                          "});";
382    render_view_host->GetMainFrame()->ExecuteJavaScript(
383        base::ASCIIToUTF16(script));
384  }
385  DidStopLoading();
386}
387
388void GuestViewBase::RenderViewReady() {
389  GuestReady();
390}
391
392void GuestViewBase::WebContentsDestroyed() {
393  GuestDestroyed();
394  delete this;
395}
396
397void GuestViewBase::ActivateContents(WebContents* web_contents) {
398  if (!attached() || !embedder_web_contents()->GetDelegate())
399    return;
400
401  embedder_web_contents()->GetDelegate()->ActivateContents(
402      embedder_web_contents());
403}
404
405void GuestViewBase::DeactivateContents(WebContents* web_contents) {
406  if (!attached() || !embedder_web_contents()->GetDelegate())
407    return;
408
409  embedder_web_contents()->GetDelegate()->DeactivateContents(
410      embedder_web_contents());
411}
412
413void GuestViewBase::RunFileChooser(WebContents* web_contents,
414                                   const content::FileChooserParams& params) {
415  if (!attached() || !embedder_web_contents()->GetDelegate())
416    return;
417
418  embedder_web_contents()->GetDelegate()->RunFileChooser(web_contents, params);
419}
420
421bool GuestViewBase::ShouldFocusPageAfterCrash() {
422  // Focus is managed elsewhere.
423  return false;
424}
425
426bool GuestViewBase::PreHandleGestureEvent(content::WebContents* source,
427                                         const blink::WebGestureEvent& event) {
428  return event.type == blink::WebGestureEvent::GesturePinchBegin ||
429      event.type == blink::WebGestureEvent::GesturePinchUpdate ||
430      event.type == blink::WebGestureEvent::GesturePinchEnd;
431}
432
433GuestViewBase::~GuestViewBase() {
434}
435
436void GuestViewBase::DispatchEventToEmbedder(Event* event) {
437  scoped_ptr<Event> event_ptr(event);
438
439  if (!attached()) {
440    pending_events_.push_back(linked_ptr<Event>(event_ptr.release()));
441    return;
442  }
443
444  EventFilteringInfo info;
445  info.SetInstanceID(view_instance_id_);
446  scoped_ptr<base::ListValue> args(new base::ListValue());
447  args->Append(event->GetArguments().release());
448
449  EventRouter::DispatchEvent(
450      embedder_web_contents_,
451      browser_context_,
452      embedder_extension_id_,
453      event->name(),
454      args.Pass(),
455      EventRouter::USER_GESTURE_UNKNOWN,
456      info);
457}
458
459void GuestViewBase::SendQueuedEvents() {
460  if (!attached())
461    return;
462  while (!pending_events_.empty()) {
463    linked_ptr<Event> event_ptr = pending_events_.front();
464    pending_events_.pop_front();
465    DispatchEventToEmbedder(event_ptr.release());
466  }
467}
468
469void GuestViewBase::CompleteInit(const std::string& embedder_extension_id,
470                                 int embedder_render_process_id,
471                                 const WebContentsCreatedCallback& callback,
472                                 content::WebContents* guest_web_contents) {
473  if (!guest_web_contents) {
474    // The derived class did not create a WebContents so this class serves no
475    // purpose. Let's self-destruct.
476    delete this;
477    callback.Run(NULL);
478    return;
479  }
480  InitWithWebContents(embedder_extension_id,
481                      embedder_render_process_id,
482                      guest_web_contents);
483  callback.Run(guest_web_contents);
484}
485
486// static
487void GuestViewBase::RegisterGuestViewTypes() {
488  AppViewGuest::Register();
489  ExtensionOptionsGuest::Register();
490  MimeHandlerViewGuest::Register();
491  WebViewGuest::Register();
492}
493
494}  // namespace extensions
495