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/renderer/guest_view/guest_view_container.h"
6
7#include "content/public/renderer/browser_plugin_delegate.h"
8#include "content/public/renderer/render_frame.h"
9#include "content/public/renderer/render_view.h"
10#include "extensions/common/extension_messages.h"
11#include "extensions/common/guest_view/guest_view_constants.h"
12#include "third_party/WebKit/public/web/WebLocalFrame.h"
13#include "third_party/WebKit/public/web/WebScopedMicrotaskSuppression.h"
14#include "third_party/WebKit/public/web/WebView.h"
15
16namespace {
17typedef std::pair<int, int> GuestViewID;
18typedef std::map<GuestViewID, extensions::GuestViewContainer*>
19    GuestViewContainerMap;
20static base::LazyInstance<GuestViewContainerMap> g_guest_view_container_map =
21    LAZY_INSTANCE_INITIALIZER;
22}  // namespace
23
24namespace extensions {
25
26GuestViewContainer::GuestViewContainer(
27    content::RenderFrame* render_frame,
28    const std::string& mime_type)
29    : content::BrowserPluginDelegate(render_frame, mime_type),
30      content::RenderFrameObserver(render_frame),
31      mime_type_(mime_type),
32      element_instance_id_(guestview::kInstanceIDNone),
33      render_view_routing_id_(render_frame->GetRenderView()->GetRoutingID()),
34      attached_(false),
35      attach_pending_(false),
36      isolate_(NULL) {
37}
38
39GuestViewContainer::~GuestViewContainer() {
40  if (element_instance_id_ != guestview::kInstanceIDNone) {
41    g_guest_view_container_map.Get().erase(
42        GuestViewID(render_view_routing_id_, element_instance_id_));
43  }
44}
45
46GuestViewContainer* GuestViewContainer::FromID(int render_view_routing_id,
47                                               int element_instance_id) {
48  GuestViewContainerMap* guest_view_containers =
49      g_guest_view_container_map.Pointer();
50  GuestViewContainerMap::iterator it = guest_view_containers->find(
51      GuestViewID(render_view_routing_id, element_instance_id));
52  return it == guest_view_containers->end() ? NULL : it->second;
53}
54
55
56void GuestViewContainer::AttachGuest(int element_instance_id,
57                                     int guest_instance_id,
58                                     scoped_ptr<base::DictionaryValue> params,
59                                     v8::Handle<v8::Function> callback,
60                                     v8::Isolate* isolate) {
61  // GuestViewContainer supports reattachment (i.e. attached_ == true) but not
62  // while a current attach process is pending.
63  if (attach_pending_)
64    return;
65
66  // Step 1, send the attach params to chrome/.
67  render_frame()->Send(new ExtensionHostMsg_AttachGuest(render_view_routing_id_,
68                                                        element_instance_id,
69                                                        guest_instance_id,
70                                                        *params));
71
72  // Step 2, attach plugin through content/.
73  render_frame()->AttachGuest(element_instance_id);
74
75  callback_.reset(callback);
76  isolate_ = isolate;
77  attach_pending_ = true;
78}
79
80void GuestViewContainer::SetElementInstanceID(int element_instance_id) {
81  GuestViewID guest_view_id(render_view_routing_id_, element_instance_id);
82  DCHECK_EQ(element_instance_id_, guestview::kInstanceIDNone);
83  DCHECK(g_guest_view_container_map.Get().find(guest_view_id) ==
84            g_guest_view_container_map.Get().end());
85  element_instance_id_ = element_instance_id;
86  g_guest_view_container_map.Get().insert(std::make_pair(guest_view_id, this));
87}
88
89void GuestViewContainer::DidFinishLoading() {
90  if (mime_type_.empty())
91    return;
92
93  DCHECK_NE(element_instance_id_, guestview::kInstanceIDNone);
94  render_frame()->Send(new ExtensionHostMsg_CreateMimeHandlerViewGuest(
95      routing_id(), html_string_, mime_type_, element_instance_id_));
96}
97
98void GuestViewContainer::DidReceiveData(const char* data, int data_length) {
99  std::string value(data, data_length);
100  html_string_ += value;
101}
102
103void GuestViewContainer::OnDestruct() {
104  // GuestViewContainer's lifetime is managed by BrowserPlugin so don't let
105  // RenderFrameObserver self-destruct here.
106}
107
108bool GuestViewContainer::OnMessageReceived(const IPC::Message& message) {
109  if (!ShouldHandleMessage(message))
110    return false;
111
112  DCHECK_NE(element_instance_id_, guestview::kInstanceIDNone);
113  int element_instance_id = guestview::kInstanceIDNone;
114  PickleIterator iter(message);
115  bool success = iter.ReadInt(&element_instance_id);
116  DCHECK(success);
117  if (element_instance_id != element_instance_id_)
118    return false;
119
120  bool handled = true;
121  IPC_BEGIN_MESSAGE_MAP(GuestViewContainer, message)
122    IPC_MESSAGE_HANDLER(ExtensionMsg_CreateMimeHandlerViewGuestACK,
123                        OnCreateMimeHandlerViewGuestACK)
124    IPC_MESSAGE_HANDLER(ExtensionMsg_GuestAttached, OnGuestAttached)
125    IPC_MESSAGE_UNHANDLED(handled = false)
126  IPC_END_MESSAGE_MAP()
127  return handled;
128}
129
130void GuestViewContainer::OnCreateMimeHandlerViewGuestACK(
131    int element_instance_id) {
132  DCHECK_NE(element_instance_id_, guestview::kInstanceIDNone);
133  DCHECK_EQ(element_instance_id_, element_instance_id);
134  DCHECK(!mime_type_.empty());
135  render_frame()->AttachGuest(element_instance_id);
136}
137
138void GuestViewContainer::OnGuestAttached(int element_instance_id,
139                                         int guest_routing_id) {
140  attached_ = true;
141  attach_pending_ = false;
142
143  // If we don't have a callback then there's nothing more to do.
144  if (callback_.IsEmpty())
145    return;
146
147  content::RenderView* guest_proxy_render_view =
148      content::RenderView::FromRoutingID(guest_routing_id);
149  // TODO(fsamuel): Should we be reporting an error to JavaScript or DCHECKing?
150  if (!guest_proxy_render_view)
151    return;
152
153  v8::HandleScope handle_scope(isolate_);
154  v8::Handle<v8::Function> callback = callback_.NewHandle(isolate_);
155  v8::Handle<v8::Context> context = callback->CreationContext();
156  if (context.IsEmpty())
157    return;
158
159  blink::WebFrame* frame = guest_proxy_render_view->GetWebView()->mainFrame();
160  v8::Local<v8::Value> window = frame->mainWorldScriptContext()->Global();
161
162  const int argc = 1;
163  v8::Handle<v8::Value> argv[argc] = { window };
164
165  v8::Context::Scope context_scope(context);
166  blink::WebScopedMicrotaskSuppression suppression;
167
168  // Call the AttachGuest API's callback with the guest proxy as the first
169  // parameter.
170  callback->Call(context->Global(), argc, argv);
171  callback_.reset();
172}
173
174// static
175bool GuestViewContainer::ShouldHandleMessage(const IPC::Message& message) {
176  switch (message.type()) {
177    case ExtensionMsg_CreateMimeHandlerViewGuestACK::ID:
178    case ExtensionMsg_GuestAttached::ID:
179      return true;
180    default:
181      break;
182  }
183  return false;
184}
185
186}  // namespace extensions
187