Home | History | Annotate | Download | only in webview
      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 
     24 using content::WebContents;
     25 
     26 namespace {
     27 
     28 static 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 
     46 static 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 
     68 void 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 
     80 WebViewGuest::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
     96 WebViewGuest* 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 
    104 void 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 
    113 GuestView::Type WebViewGuest::GetViewType() const {
    114   return GuestView::WEBVIEW;
    115 }
    116 
    117 WebViewGuest* WebViewGuest::AsWebView() {
    118   return this;
    119 }
    120 
    121 AdViewGuest* WebViewGuest::AsAdView() {
    122   return NULL;
    123 }
    124 
    125 void 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 
    139 void WebViewGuest::Close() {
    140   scoped_ptr<DictionaryValue> args(new DictionaryValue());
    141   DispatchEvent(new GuestView::Event(webview::kEventClose, args.Pass()));
    142 }
    143 
    144 void 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 
    153 bool 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.
    187 void 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 
    194 void 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 
    201 bool 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 
    231 void 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 
    260 void WebViewGuest::Go(int relative_index) {
    261   guest_web_contents()->GetController().GoToOffset(relative_index);
    262 }
    263 
    264 void 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 
    271 bool 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 
    285 void WebViewGuest::Stop() {
    286   guest_web_contents()->Stop();
    287 }
    288 
    289 void 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 
    297 WebViewGuest::~WebViewGuest() {
    298 }
    299 
    300 void 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 
    318 void 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 
    336 void 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 
    350 void 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 
    355 void 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 
    367 void WebViewGuest::LoadHandlerCalled() {
    368   scoped_ptr<DictionaryValue> args(new DictionaryValue());
    369   DispatchEvent(new GuestView::Event(webview::kEventContentLoad, args.Pass()));
    370 }
    371 
    372 void 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 
    382 void 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
    399 void 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