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