1// Copyright 2013 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 "chrome/browser/guestview/webview/webview_guest.h"
6
7#include "chrome/browser/extensions/api/web_request/web_request_api.h"
8#include "chrome/browser/extensions/extension_renderer_state.h"
9#include "chrome/browser/extensions/script_executor.h"
10#include "chrome/browser/guestview/guestview_constants.h"
11#include "chrome/browser/guestview/webview/webview_constants.h"
12#include "content/public/browser/browser_thread.h"
13#include "content/public/browser/native_web_keyboard_event.h"
14#include "content/public/browser/notification_details.h"
15#include "content/public/browser/notification_source.h"
16#include "content/public/browser/notification_types.h"
17#include "content/public/browser/render_process_host.h"
18#include "content/public/browser/resource_request_details.h"
19#include "content/public/browser/user_metrics.h"
20#include "content/public/browser/web_contents.h"
21#include "content/public/common/result_codes.h"
22#include "net/base/net_errors.h"
23
24using content::WebContents;
25
26namespace {
27
28static std::string TerminationStatusToString(base::TerminationStatus status) {
29  switch (status) {
30    case base::TERMINATION_STATUS_NORMAL_TERMINATION:
31      return "normal";
32    case base::TERMINATION_STATUS_ABNORMAL_TERMINATION:
33    case base::TERMINATION_STATUS_STILL_RUNNING:
34      return "abnormal";
35    case base::TERMINATION_STATUS_PROCESS_WAS_KILLED:
36      return "killed";
37    case base::TERMINATION_STATUS_PROCESS_CRASHED:
38      return "crashed";
39    case base::TERMINATION_STATUS_MAX_ENUM:
40      break;
41  }
42  NOTREACHED() << "Unknown Termination Status.";
43  return "unknown";
44}
45
46static std::string PermissionTypeToString(BrowserPluginPermissionType type) {
47  switch (type) {
48    case BROWSER_PLUGIN_PERMISSION_TYPE_DOWNLOAD:
49      return webview::kPermissionTypeDownload;
50    case BROWSER_PLUGIN_PERMISSION_TYPE_GEOLOCATION:
51      return webview::kPermissionTypeGeolocation;
52    case BROWSER_PLUGIN_PERMISSION_TYPE_MEDIA:
53      return webview::kPermissionTypeMedia;
54    case BROWSER_PLUGIN_PERMISSION_TYPE_NEW_WINDOW:
55      return webview::kPermissionTypeNewWindow;
56    case BROWSER_PLUGIN_PERMISSION_TYPE_POINTER_LOCK:
57      return webview::kPermissionTypePointerLock;
58    case BROWSER_PLUGIN_PERMISSION_TYPE_JAVASCRIPT_DIALOG:
59      return webview::kPermissionTypeDialog;
60    case BROWSER_PLUGIN_PERMISSION_TYPE_UNKNOWN:
61    default:
62      NOTREACHED();
63      break;
64  }
65  return std::string();
66}
67
68void RemoveWebViewEventListenersOnIOThread(
69    void* profile,
70    const std::string& extension_id,
71    int embedder_process_id,
72    int guest_instance_id) {
73  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
74  ExtensionWebRequestEventRouter::GetInstance()->RemoveWebViewEventListeners(
75      profile, extension_id, embedder_process_id, guest_instance_id);
76}
77
78}  // namespace
79
80WebViewGuest::WebViewGuest(WebContents* guest_web_contents)
81    : GuestView(guest_web_contents),
82      WebContentsObserver(guest_web_contents),
83      script_executor_(new extensions::ScriptExecutor(guest_web_contents,
84                                                      &script_observers_)),
85      next_permission_request_id_(0) {
86  notification_registrar_.Add(
87      this, content::NOTIFICATION_LOAD_COMPLETED_MAIN_FRAME,
88      content::Source<WebContents>(guest_web_contents));
89
90  notification_registrar_.Add(
91      this, content::NOTIFICATION_RESOURCE_RECEIVED_REDIRECT,
92      content::Source<WebContents>(guest_web_contents));
93}
94
95// static
96WebViewGuest* WebViewGuest::From(int embedder_process_id,
97                                 int guest_instance_id) {
98  GuestView* guest = GuestView::From(embedder_process_id, guest_instance_id);
99  if (!guest)
100    return NULL;
101  return guest->AsWebView();
102}
103
104void WebViewGuest::Attach(WebContents* embedder_web_contents,
105                          const std::string& extension_id,
106                          const base::DictionaryValue& args) {
107  GuestView::Attach(
108      embedder_web_contents, extension_id, args);
109
110  AddWebViewToExtensionRendererState();
111}
112
113GuestView::Type WebViewGuest::GetViewType() const {
114  return GuestView::WEBVIEW;
115}
116
117WebViewGuest* WebViewGuest::AsWebView() {
118  return this;
119}
120
121AdViewGuest* WebViewGuest::AsAdView() {
122  return NULL;
123}
124
125void WebViewGuest::AddMessageToConsole(int32 level,
126                                       const string16& message,
127                                       int32 line_no,
128                                       const string16& source_id) {
129  scoped_ptr<DictionaryValue> args(new DictionaryValue());
130  // Log levels are from base/logging.h: LogSeverity.
131  args->SetInteger(webview::kLevel, level);
132  args->SetString(webview::kMessage, message);
133  args->SetInteger(webview::kLine, line_no);
134  args->SetString(webview::kSourceId, source_id);
135  DispatchEvent(
136      new GuestView::Event(webview::kEventConsoleMessage, args.Pass()));
137}
138
139void WebViewGuest::Close() {
140  scoped_ptr<DictionaryValue> args(new DictionaryValue());
141  DispatchEvent(new GuestView::Event(webview::kEventClose, args.Pass()));
142}
143
144void WebViewGuest::GuestProcessGone(base::TerminationStatus status) {
145  scoped_ptr<DictionaryValue> args(new DictionaryValue());
146  args->SetInteger(webview::kProcessId,
147                   web_contents()->GetRenderProcessHost()->GetID());
148  args->SetString(webview::kReason, TerminationStatusToString(status));
149  DispatchEvent(
150      new GuestView::Event(webview::kEventExit, args.Pass()));
151}
152
153bool WebViewGuest::HandleKeyboardEvent(
154    const content::NativeWebKeyboardEvent& event) {
155  if (event.type != WebKit::WebInputEvent::RawKeyDown)
156    return false;
157
158#if defined(OS_MACOSX)
159  if (event.modifiers != WebKit::WebInputEvent::MetaKey)
160    return false;
161
162  if (event.windowsKeyCode == ui::VKEY_OEM_4) {
163    Go(-1);
164    return true;
165  }
166
167  if (event.windowsKeyCode == ui::VKEY_OEM_6) {
168    Go(1);
169    return true;
170  }
171#else
172  if (event.windowsKeyCode == ui::VKEY_BROWSER_BACK) {
173    Go(-1);
174    return true;
175  }
176
177  if (event.windowsKeyCode == ui::VKEY_BROWSER_FORWARD) {
178    Go(1);
179    return true;
180  }
181#endif
182  return false;
183}
184
185// TODO(fsamuel): Find a reliable way to test the 'responsive' and
186// 'unresponsive' events.
187void WebViewGuest::RendererResponsive() {
188  scoped_ptr<DictionaryValue> args(new DictionaryValue());
189  args->SetInteger(webview::kProcessId,
190      guest_web_contents()->GetRenderProcessHost()->GetID());
191  DispatchEvent(new GuestView::Event(webview::kEventResponsive, args.Pass()));
192}
193
194void WebViewGuest::RendererUnresponsive() {
195  scoped_ptr<DictionaryValue> args(new DictionaryValue());
196  args->SetInteger(webview::kProcessId,
197      guest_web_contents()->GetRenderProcessHost()->GetID());
198  DispatchEvent(new GuestView::Event(webview::kEventUnresponsive, args.Pass()));
199}
200
201bool WebViewGuest::RequestPermission(
202    BrowserPluginPermissionType permission_type,
203    const base::DictionaryValue& request_info,
204    const PermissionResponseCallback& callback) {
205  int request_id = next_permission_request_id_++;
206  pending_permission_requests_[request_id] = callback;
207  scoped_ptr<base::DictionaryValue> args(request_info.DeepCopy());
208  args->SetInteger(webview::kRequestId, request_id);
209  switch (permission_type) {
210    case BROWSER_PLUGIN_PERMISSION_TYPE_NEW_WINDOW: {
211      DispatchEvent(new GuestView::Event(webview::kEventNewWindow,
212                                         args.Pass()));
213      break;
214    }
215    case BROWSER_PLUGIN_PERMISSION_TYPE_JAVASCRIPT_DIALOG: {
216      DispatchEvent(new GuestView::Event(webview::kEventDialog,
217                                         args.Pass()));
218      break;
219    }
220    default: {
221      args->SetString(webview::kPermission,
222                      PermissionTypeToString(permission_type));
223      DispatchEvent(new GuestView::Event(webview::kEventPermissionRequest,
224                                         args.Pass()));
225      break;
226    }
227  }
228  return true;
229}
230
231void WebViewGuest::Observe(int type,
232                           const content::NotificationSource& source,
233                           const content::NotificationDetails& details) {
234  switch (type) {
235    case content::NOTIFICATION_LOAD_COMPLETED_MAIN_FRAME: {
236      DCHECK_EQ(content::Source<WebContents>(source).ptr(),
237                guest_web_contents());
238      if (content::Source<WebContents>(source).ptr() == guest_web_contents())
239        LoadHandlerCalled();
240      break;
241    }
242    case content::NOTIFICATION_RESOURCE_RECEIVED_REDIRECT: {
243      DCHECK_EQ(content::Source<WebContents>(source).ptr(),
244                guest_web_contents());
245      content::ResourceRedirectDetails* resource_redirect_details =
246          content::Details<content::ResourceRedirectDetails>(details).ptr();
247      bool is_top_level =
248          resource_redirect_details->resource_type == ResourceType::MAIN_FRAME;
249      LoadRedirect(resource_redirect_details->url,
250                   resource_redirect_details->new_url,
251                   is_top_level);
252      break;
253    }
254    default:
255      NOTREACHED() << "Unexpected notification sent.";
256      break;
257  }
258}
259
260void WebViewGuest::Go(int relative_index) {
261  guest_web_contents()->GetController().GoToOffset(relative_index);
262}
263
264void WebViewGuest::Reload() {
265  // TODO(fsamuel): Don't check for repost because we don't want to show
266  // Chromium's repost warning. We might want to implement a separate API
267  // for registering a callback if a repost is about to happen.
268  guest_web_contents()->GetController().Reload(false);
269}
270
271bool WebViewGuest::SetPermission(int request_id,
272                                 bool should_allow,
273                                 const std::string& user_input) {
274  RequestMap::iterator request_itr =
275      pending_permission_requests_.find(request_id);
276
277  if (request_itr == pending_permission_requests_.end())
278    return false;
279
280  request_itr->second.Run(should_allow, user_input);
281  pending_permission_requests_.erase(request_itr);
282  return true;
283}
284
285void WebViewGuest::Stop() {
286  guest_web_contents()->Stop();
287}
288
289void WebViewGuest::Terminate() {
290  content::RecordAction(content::UserMetricsAction("WebView.Guest.Terminate"));
291  base::ProcessHandle process_handle =
292      guest_web_contents()->GetRenderProcessHost()->GetHandle();
293  if (process_handle)
294    base::KillProcess(process_handle, content::RESULT_CODE_KILLED, false);
295}
296
297WebViewGuest::~WebViewGuest() {
298}
299
300void WebViewGuest::DidCommitProvisionalLoadForFrame(
301    int64 frame_id,
302    bool is_main_frame,
303    const GURL& url,
304    content::PageTransition transition_type,
305    content::RenderViewHost* render_view_host) {
306  scoped_ptr<DictionaryValue> args(new DictionaryValue());
307  args->SetString(guestview::kUrl, url.spec());
308  args->SetBoolean(guestview::kIsTopLevel, is_main_frame);
309  args->SetInteger(webview::kInternalCurrentEntryIndex,
310      web_contents()->GetController().GetCurrentEntryIndex());
311  args->SetInteger(webview::kInternalEntryCount,
312      web_contents()->GetController().GetEntryCount());
313  args->SetInteger(webview::kInternalProcessId,
314      web_contents()->GetRenderProcessHost()->GetID());
315  DispatchEvent(new GuestView::Event(webview::kEventLoadCommit, args.Pass()));
316}
317
318void WebViewGuest::DidFailProvisionalLoad(
319    int64 frame_id,
320    bool is_main_frame,
321    const GURL& validated_url,
322    int error_code,
323    const string16& error_description,
324    content::RenderViewHost* render_view_host) {
325  // Translate the |error_code| into an error string.
326  std::string error_type;
327  RemoveChars(net::ErrorToString(error_code), "net::", &error_type);
328
329  scoped_ptr<DictionaryValue> args(new DictionaryValue());
330  args->SetBoolean(guestview::kIsTopLevel, is_main_frame);
331  args->SetString(guestview::kUrl, validated_url.spec());
332  args->SetString(guestview::kReason, error_type);
333  DispatchEvent(new GuestView::Event(webview::kEventLoadAbort, args.Pass()));
334}
335
336void WebViewGuest::DidStartProvisionalLoadForFrame(
337    int64 frame_id,
338    int64 parent_frame_id,
339    bool is_main_frame,
340    const GURL& validated_url,
341    bool is_error_page,
342    bool is_iframe_srcdoc,
343    content::RenderViewHost* render_view_host) {
344  scoped_ptr<DictionaryValue> args(new DictionaryValue());
345  args->SetString(guestview::kUrl, validated_url.spec());
346  args->SetBoolean(guestview::kIsTopLevel, is_main_frame);
347  DispatchEvent(new GuestView::Event(webview::kEventLoadStart, args.Pass()));
348}
349
350void WebViewGuest::DidStopLoading(content::RenderViewHost* render_view_host) {
351  scoped_ptr<DictionaryValue> args(new DictionaryValue());
352  DispatchEvent(new GuestView::Event(webview::kEventLoadStop, args.Pass()));
353}
354
355void WebViewGuest::WebContentsDestroyed(WebContents* web_contents) {
356  RemoveWebViewFromExtensionRendererState(web_contents);
357  content::BrowserThread::PostTask(
358      content::BrowserThread::IO,
359      FROM_HERE,
360      base::Bind(
361          &RemoveWebViewEventListenersOnIOThread,
362          browser_context(), extension_id(),
363          embedder_render_process_id(),
364          view_instance_id()));
365}
366
367void WebViewGuest::LoadHandlerCalled() {
368  scoped_ptr<DictionaryValue> args(new DictionaryValue());
369  DispatchEvent(new GuestView::Event(webview::kEventContentLoad, args.Pass()));
370}
371
372void WebViewGuest::LoadRedirect(const GURL& old_url,
373                                const GURL& new_url,
374                                bool is_top_level) {
375  scoped_ptr<DictionaryValue> args(new DictionaryValue());
376  args->SetBoolean(guestview::kIsTopLevel, is_top_level);
377  args->SetString(webview::kNewURL, new_url.spec());
378  args->SetString(webview::kOldURL, old_url.spec());
379  DispatchEvent(new GuestView::Event(webview::kEventLoadRedirect, args.Pass()));
380}
381
382void WebViewGuest::AddWebViewToExtensionRendererState() {
383  ExtensionRendererState::WebViewInfo webview_info;
384  webview_info.embedder_process_id = embedder_render_process_id();
385  webview_info.embedder_routing_id = embedder_web_contents()->GetRoutingID();
386  webview_info.instance_id = view_instance_id();
387
388  content::BrowserThread::PostTask(
389      content::BrowserThread::IO, FROM_HERE,
390      base::Bind(
391          &ExtensionRendererState::AddWebView,
392          base::Unretained(ExtensionRendererState::GetInstance()),
393          guest_web_contents()->GetRenderProcessHost()->GetID(),
394          guest_web_contents()->GetRoutingID(),
395          webview_info));
396}
397
398// static
399void WebViewGuest::RemoveWebViewFromExtensionRendererState(
400    WebContents* web_contents) {
401  content::BrowserThread::PostTask(
402      content::BrowserThread::IO, FROM_HERE,
403      base::Bind(
404          &ExtensionRendererState::RemoveWebView,
405          base::Unretained(ExtensionRendererState::GetInstance()),
406          web_contents->GetRenderProcessHost()->GetID(),
407          web_contents->GetRoutingID()));
408}
409