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/web_view/web_view_guest.h"
6
7#include "base/message_loop/message_loop.h"
8#include "base/strings/stringprintf.h"
9#include "base/strings/utf_string_conversions.h"
10#include "content/public/browser/browser_context.h"
11#include "content/public/browser/browser_thread.h"
12#include "content/public/browser/child_process_security_policy.h"
13#include "content/public/browser/native_web_keyboard_event.h"
14#include "content/public/browser/navigation_entry.h"
15#include "content/public/browser/notification_details.h"
16#include "content/public/browser/notification_service.h"
17#include "content/public/browser/notification_source.h"
18#include "content/public/browser/notification_types.h"
19#include "content/public/browser/render_process_host.h"
20#include "content/public/browser/render_view_host.h"
21#include "content/public/browser/render_widget_host_view.h"
22#include "content/public/browser/resource_request_details.h"
23#include "content/public/browser/site_instance.h"
24#include "content/public/browser/storage_partition.h"
25#include "content/public/browser/user_metrics.h"
26#include "content/public/browser/web_contents.h"
27#include "content/public/browser/web_contents_delegate.h"
28#include "content/public/common/media_stream_request.h"
29#include "content/public/common/page_zoom.h"
30#include "content/public/common/result_codes.h"
31#include "content/public/common/stop_find_action.h"
32#include "content/public/common/url_constants.h"
33#include "extensions/browser/api/extensions_api_client.h"
34#include "extensions/browser/api/web_view/web_view_internal_api.h"
35#include "extensions/browser/extension_system.h"
36#include "extensions/browser/guest_view/guest_view_manager.h"
37#include "extensions/browser/guest_view/web_view/web_view_constants.h"
38#include "extensions/browser/guest_view/web_view/web_view_permission_helper.h"
39#include "extensions/browser/guest_view/web_view/web_view_permission_types.h"
40#include "extensions/browser/guest_view/web_view/web_view_renderer_state.h"
41#include "extensions/common/constants.h"
42#include "extensions/common/extension_messages.h"
43#include "extensions/common/guest_view/guest_view_constants.h"
44#include "extensions/strings/grit/extensions_strings.h"
45#include "ipc/ipc_message_macros.h"
46#include "net/base/escape.h"
47#include "net/base/net_errors.h"
48#include "ui/base/models/simple_menu_model.h"
49
50using base::UserMetricsAction;
51using content::RenderFrameHost;
52using content::ResourceType;
53using content::WebContents;
54
55namespace extensions {
56
57namespace {
58
59std::string WindowOpenDispositionToString(
60  WindowOpenDisposition window_open_disposition) {
61  switch (window_open_disposition) {
62    case IGNORE_ACTION:
63      return "ignore";
64    case SAVE_TO_DISK:
65      return "save_to_disk";
66    case CURRENT_TAB:
67      return "current_tab";
68    case NEW_BACKGROUND_TAB:
69      return "new_background_tab";
70    case NEW_FOREGROUND_TAB:
71      return "new_foreground_tab";
72    case NEW_WINDOW:
73      return "new_window";
74    case NEW_POPUP:
75      return "new_popup";
76    default:
77      NOTREACHED() << "Unknown Window Open Disposition";
78      return "ignore";
79  }
80}
81
82static std::string TerminationStatusToString(base::TerminationStatus status) {
83  switch (status) {
84    case base::TERMINATION_STATUS_NORMAL_TERMINATION:
85      return "normal";
86    case base::TERMINATION_STATUS_ABNORMAL_TERMINATION:
87    case base::TERMINATION_STATUS_STILL_RUNNING:
88      return "abnormal";
89    case base::TERMINATION_STATUS_PROCESS_WAS_KILLED:
90      return "killed";
91    case base::TERMINATION_STATUS_PROCESS_CRASHED:
92      return "crashed";
93    case base::TERMINATION_STATUS_MAX_ENUM:
94      break;
95  }
96  NOTREACHED() << "Unknown Termination Status.";
97  return "unknown";
98}
99
100std::string GetStoragePartitionIdFromSiteURL(const GURL& site_url) {
101  const std::string& partition_id = site_url.query();
102  bool persist_storage = site_url.path().find("persist") != std::string::npos;
103  return (persist_storage ? webview::kPersistPrefix : "") + partition_id;
104}
105
106void ParsePartitionParam(const base::DictionaryValue& create_params,
107                         std::string* storage_partition_id,
108                         bool* persist_storage) {
109  std::string partition_str;
110  if (!create_params.GetString(webview::kStoragePartitionId, &partition_str)) {
111    return;
112  }
113
114  // Since the "persist:" prefix is in ASCII, StartsWith will work fine on
115  // UTF-8 encoded |partition_id|. If the prefix is a match, we can safely
116  // remove the prefix without splicing in the middle of a multi-byte codepoint.
117  // We can use the rest of the string as UTF-8 encoded one.
118  if (StartsWithASCII(partition_str, "persist:", true)) {
119    size_t index = partition_str.find(":");
120    CHECK(index != std::string::npos);
121    // It is safe to do index + 1, since we tested for the full prefix above.
122    *storage_partition_id = partition_str.substr(index + 1);
123
124    if (storage_partition_id->empty()) {
125      // TODO(lazyboy): Better way to deal with this error.
126      return;
127    }
128    *persist_storage = true;
129  } else {
130    *storage_partition_id = partition_str;
131    *persist_storage = false;
132  }
133}
134
135}  // namespace
136
137// static
138GuestViewBase* WebViewGuest::Create(content::BrowserContext* browser_context,
139                                    int guest_instance_id) {
140  return new WebViewGuest(browser_context, guest_instance_id);
141}
142
143// static
144bool WebViewGuest::GetGuestPartitionConfigForSite(
145    const GURL& site,
146    std::string* partition_domain,
147    std::string* partition_name,
148    bool* in_memory) {
149  if (!site.SchemeIs(content::kGuestScheme))
150    return false;
151
152  // Since guest URLs are only used for packaged apps, there must be an app
153  // id in the URL.
154  CHECK(site.has_host());
155  *partition_domain = site.host();
156  // Since persistence is optional, the path must either be empty or the
157  // literal string.
158  *in_memory = (site.path() != "/persist");
159  // The partition name is user supplied value, which we have encoded when the
160  // URL was created, so it needs to be decoded.
161  *partition_name =
162      net::UnescapeURLComponent(site.query(), net::UnescapeRule::NORMAL);
163  return true;
164}
165
166// static
167const char WebViewGuest::Type[] = "webview";
168
169// static
170int WebViewGuest::GetViewInstanceId(WebContents* contents) {
171  WebViewGuest* guest = FromWebContents(contents);
172  if (!guest)
173    return guestview::kInstanceIDNone;
174
175  return guest->view_instance_id();
176}
177
178const char* WebViewGuest::GetAPINamespace() const {
179  return webview::kAPINamespace;
180}
181
182int WebViewGuest::GetTaskPrefix() const {
183  return IDS_EXTENSION_TASK_MANAGER_WEBVIEW_TAG_PREFIX;
184}
185
186void WebViewGuest::CreateWebContents(
187    const std::string& embedder_extension_id,
188    int embedder_render_process_id,
189    const GURL& embedder_site_url,
190    const base::DictionaryValue& create_params,
191    const WebContentsCreatedCallback& callback) {
192  content::RenderProcessHost* embedder_render_process_host =
193      content::RenderProcessHost::FromID(embedder_render_process_id);
194  std::string storage_partition_id;
195  bool persist_storage = false;
196  std::string storage_partition_string;
197  ParsePartitionParam(create_params, &storage_partition_id, &persist_storage);
198  // Validate that the partition id coming from the renderer is valid UTF-8,
199  // since we depend on this in other parts of the code, such as FilePath
200  // creation. If the validation fails, treat it as a bad message and kill the
201  // renderer process.
202  if (!base::IsStringUTF8(storage_partition_id)) {
203    content::RecordAction(
204        base::UserMetricsAction("BadMessageTerminate_BPGM"));
205    base::KillProcess(
206        embedder_render_process_host->GetHandle(),
207        content::RESULT_CODE_KILLED_BAD_MESSAGE, false);
208    callback.Run(NULL);
209    return;
210  }
211  std::string url_encoded_partition = net::EscapeQueryParamValue(
212      storage_partition_id, false);
213  // The SiteInstance of a given webview tag is based on the fact that it's
214  // a guest process in addition to which platform application or which WebUI
215  // page the tag belongs to and what storage partition is in use, rather than
216  // the URL that the tag is being navigated to.
217  std::string partition_domain;
218  if (embedder_extension_id.empty()) {
219    DCHECK(content::ChildProcessSecurityPolicy::GetInstance()->HasWebUIBindings(
220        embedder_render_process_id));
221    partition_domain = embedder_site_url.host();
222  } else {
223    partition_domain = embedder_extension_id;
224  }
225  GURL guest_site(base::StringPrintf("%s://%s/%s?%s",
226                                     content::kGuestScheme,
227                                     partition_domain.c_str(),
228                                     persist_storage ? "persist" : "",
229                                     url_encoded_partition.c_str()));
230
231  // If we already have a webview tag in the same app using the same storage
232  // partition, we should use the same SiteInstance so the existing tag and
233  // the new tag can script each other.
234  GuestViewManager* guest_view_manager =
235      GuestViewManager::FromBrowserContext(
236          embedder_render_process_host->GetBrowserContext());
237  content::SiteInstance* guest_site_instance =
238      guest_view_manager->GetGuestSiteInstance(guest_site);
239  if (!guest_site_instance) {
240    // Create the SiteInstance in a new BrowsingInstance, which will ensure
241    // that webview tags are also not allowed to send messages across
242    // different partitions.
243    guest_site_instance = content::SiteInstance::CreateForURL(
244        embedder_render_process_host->GetBrowserContext(), guest_site);
245  }
246  WebContents::CreateParams params(
247      embedder_render_process_host->GetBrowserContext(),
248      guest_site_instance);
249  params.guest_delegate = this;
250  callback.Run(WebContents::Create(params));
251}
252
253void WebViewGuest::DidAttachToEmbedder() {
254  SetUpAutoSize();
255
256  std::string name;
257  if (attach_params()->GetString(webview::kAttributeName, &name)) {
258    // If the guest window's name is empty, then the WebView tag's name is
259    // assigned. Otherwise, the guest window's name takes precedence over the
260    // WebView tag's name.
261    if (name_.empty())
262      name_ = name;
263  }
264  ReportFrameNameChange(name_);
265
266  std::string user_agent_override;
267  if (attach_params()->GetString(webview::kParameterUserAgentOverride,
268                                 &user_agent_override)) {
269    SetUserAgentOverride(user_agent_override);
270  } else {
271    SetUserAgentOverride("");
272  }
273
274  std::string src;
275  if (attach_params()->GetString(webview::kAttributeSrc, &src) && !src.empty())
276    NavigateGuest(src);
277
278  if (GetOpener()) {
279    // We need to do a navigation here if the target URL has changed between
280    // the time the WebContents was created and the time it was attached.
281    // We also need to do an initial navigation if a RenderView was never
282    // created for the new window in cases where there is no referrer.
283    PendingWindowMap::iterator it =
284        GetOpener()->pending_new_windows_.find(this);
285    if (it != GetOpener()->pending_new_windows_.end()) {
286      const NewWindowInfo& new_window_info = it->second;
287      if (new_window_info.changed || !web_contents()->HasOpener())
288        NavigateGuest(new_window_info.url.spec());
289    } else {
290      NOTREACHED();
291    }
292
293    // Once a new guest is attached to the DOM of the embedder page, then the
294    // lifetime of the new guest is no longer managed by the opener guest.
295    GetOpener()->pending_new_windows_.erase(this);
296  }
297
298  bool allow_transparency = false;
299  attach_params()->GetBoolean(webview::kAttributeAllowTransparency,
300                              &allow_transparency);
301  // We need to set the background opaque flag after navigation to ensure that
302  // there is a RenderWidgetHostView available.
303  SetAllowTransparency(allow_transparency);
304
305  if (web_view_guest_delegate_)
306    web_view_guest_delegate_->OnDidAttachToEmbedder();
307}
308
309void WebViewGuest::DidInitialize() {
310  script_executor_.reset(
311      new ScriptExecutor(web_contents(), &script_observers_));
312
313  notification_registrar_.Add(this,
314                              content::NOTIFICATION_LOAD_COMPLETED_MAIN_FRAME,
315                              content::Source<WebContents>(web_contents()));
316
317  notification_registrar_.Add(this,
318                              content::NOTIFICATION_RESOURCE_RECEIVED_REDIRECT,
319                              content::Source<WebContents>(web_contents()));
320
321  if (web_view_guest_delegate_)
322    web_view_guest_delegate_->OnDidInitialize();
323  AttachWebViewHelpers(web_contents());
324}
325
326void WebViewGuest::AttachWebViewHelpers(WebContents* contents) {
327  if (web_view_guest_delegate_)
328    web_view_guest_delegate_->OnAttachWebViewHelpers(contents);
329  web_view_permission_helper_.reset(new WebViewPermissionHelper(this));
330}
331
332void WebViewGuest::DidStopLoading() {
333  scoped_ptr<base::DictionaryValue> args(new base::DictionaryValue());
334  DispatchEventToEmbedder(
335      new GuestViewBase::Event(webview::kEventLoadStop, args.Pass()));
336}
337
338void WebViewGuest::EmbedderDestroyed() {
339  if (web_view_guest_delegate_)
340    web_view_guest_delegate_->OnEmbedderDestroyed();
341}
342
343void WebViewGuest::GuestDestroyed() {
344  // Clean up custom context menu items for this guest.
345  if (web_view_guest_delegate_)
346    web_view_guest_delegate_->OnGuestDestroyed();
347  RemoveWebViewStateFromIOThread(web_contents());
348}
349
350void WebViewGuest::GuestReady() {
351  // The guest RenderView should always live in an isolated guest process.
352  CHECK(web_contents()->GetRenderProcessHost()->IsIsolatedGuest());
353  Send(new ExtensionMsg_SetFrameName(web_contents()->GetRoutingID(), name_));
354
355  // We don't want to accidentally set the opacity of an interstitial page.
356  // WebContents::GetRenderWidgetHostView will return the RWHV of an
357  // interstitial page if one is showing at this time. We only want opacity
358  // to apply to web pages.
359  web_contents()->GetRenderViewHost()->GetView()->
360      SetBackgroundOpaque(guest_opaque_);
361}
362
363void WebViewGuest::GuestSizeChangedDueToAutoSize(const gfx::Size& old_size,
364                                                 const gfx::Size& new_size) {
365  scoped_ptr<base::DictionaryValue> args(new base::DictionaryValue());
366  args->SetInteger(webview::kOldHeight, old_size.height());
367  args->SetInteger(webview::kOldWidth, old_size.width());
368  args->SetInteger(webview::kNewHeight, new_size.height());
369  args->SetInteger(webview::kNewWidth, new_size.width());
370  DispatchEventToEmbedder(
371      new GuestViewBase::Event(webview::kEventSizeChanged, args.Pass()));
372}
373
374bool WebViewGuest::IsAutoSizeSupported() const {
375  return true;
376}
377
378bool WebViewGuest::IsDragAndDropEnabled() const {
379  return true;
380}
381
382void WebViewGuest::WillDestroy() {
383  if (!attached() && GetOpener())
384    GetOpener()->pending_new_windows_.erase(this);
385  DestroyUnattachedWindows();
386
387  scoped_ptr<base::DictionaryValue> args(new base::DictionaryValue());
388  DispatchEventToEmbedder(
389      new GuestViewBase::Event(webview::kEventPluginDestroyed, args.Pass()));
390}
391
392bool WebViewGuest::AddMessageToConsole(WebContents* source,
393                                       int32 level,
394                                       const base::string16& message,
395                                       int32 line_no,
396                                       const base::string16& source_id) {
397  scoped_ptr<base::DictionaryValue> args(new base::DictionaryValue());
398  // Log levels are from base/logging.h: LogSeverity.
399  args->SetInteger(webview::kLevel, level);
400  args->SetString(webview::kMessage, message);
401  args->SetInteger(webview::kLine, line_no);
402  args->SetString(webview::kSourceId, source_id);
403  DispatchEventToEmbedder(
404      new GuestViewBase::Event(webview::kEventConsoleMessage, args.Pass()));
405  return true;
406}
407
408void WebViewGuest::CloseContents(WebContents* source) {
409  scoped_ptr<base::DictionaryValue> args(new base::DictionaryValue());
410  DispatchEventToEmbedder(
411      new GuestViewBase::Event(webview::kEventClose, args.Pass()));
412}
413
414void WebViewGuest::FindReply(WebContents* source,
415                             int request_id,
416                             int number_of_matches,
417                             const gfx::Rect& selection_rect,
418                             int active_match_ordinal,
419                             bool final_update) {
420  find_helper_.FindReply(request_id,
421                         number_of_matches,
422                         selection_rect,
423                         active_match_ordinal,
424                         final_update);
425}
426
427bool WebViewGuest::HandleContextMenu(
428    const content::ContextMenuParams& params) {
429  if (!web_view_guest_delegate_)
430    return false;
431  return web_view_guest_delegate_->HandleContextMenu(params);
432}
433
434void WebViewGuest::HandleKeyboardEvent(
435    WebContents* source,
436    const content::NativeWebKeyboardEvent& event) {
437  if (!attached())
438    return;
439
440  if (HandleKeyboardShortcuts(event))
441    return;
442
443  // Send the unhandled keyboard events back to the embedder to reprocess them.
444  // TODO(fsamuel): This introduces the possibility of out-of-order keyboard
445  // events because the guest may be arbitrarily delayed when responding to
446  // keyboard events. In that time, the embedder may have received and processed
447  // additional key events. This needs to be fixed as soon as possible.
448  // See http://crbug.com/229882.
449  embedder_web_contents()->GetDelegate()->HandleKeyboardEvent(
450      web_contents(), event);
451}
452
453void WebViewGuest::LoadProgressChanged(content::WebContents* source,
454                                       double progress) {
455  scoped_ptr<base::DictionaryValue> args(new base::DictionaryValue());
456  args->SetString(guestview::kUrl, web_contents()->GetURL().spec());
457  args->SetDouble(webview::kProgress, progress);
458  DispatchEventToEmbedder(
459      new GuestViewBase::Event(webview::kEventLoadProgress, args.Pass()));
460}
461
462void WebViewGuest::LoadAbort(bool is_top_level,
463                             const GURL& url,
464                             const std::string& error_type) {
465  scoped_ptr<base::DictionaryValue> args(new base::DictionaryValue());
466  args->SetBoolean(guestview::kIsTopLevel, is_top_level);
467  args->SetString(guestview::kUrl, url.possibly_invalid_spec());
468  args->SetString(guestview::kReason, error_type);
469  DispatchEventToEmbedder(
470      new GuestViewBase::Event(webview::kEventLoadAbort, args.Pass()));
471}
472
473void WebViewGuest::OnFrameNameChanged(bool is_top_level,
474                                      const std::string& name) {
475  if (!is_top_level)
476    return;
477
478  if (name_ == name)
479    return;
480
481  ReportFrameNameChange(name);
482}
483
484void WebViewGuest::CreateNewGuestWebViewWindow(
485    const content::OpenURLParams& params) {
486  GuestViewManager* guest_manager =
487      GuestViewManager::FromBrowserContext(browser_context());
488  // Set the attach params to use the same partition as the opener.
489  // We pull the partition information from the site's URL, which is of the
490  // form guest://site/{persist}?{partition_name}.
491  const GURL& site_url = web_contents()->GetSiteInstance()->GetSiteURL();
492  const std::string storage_partition_id =
493      GetStoragePartitionIdFromSiteURL(site_url);
494  base::DictionaryValue create_params;
495  create_params.SetString(webview::kStoragePartitionId, storage_partition_id);
496
497  guest_manager->CreateGuest(WebViewGuest::Type,
498                             embedder_extension_id(),
499                             embedder_web_contents(),
500                             create_params,
501                             base::Bind(&WebViewGuest::NewGuestWebViewCallback,
502                                        base::Unretained(this),
503                                        params));
504}
505
506void WebViewGuest::NewGuestWebViewCallback(
507    const content::OpenURLParams& params,
508    content::WebContents* guest_web_contents) {
509  WebViewGuest* new_guest = WebViewGuest::FromWebContents(guest_web_contents);
510  new_guest->SetOpener(this);
511
512  // Take ownership of |new_guest|.
513  pending_new_windows_.insert(
514      std::make_pair(new_guest, NewWindowInfo(params.url, std::string())));
515
516  // Request permission to show the new window.
517  RequestNewWindowPermission(params.disposition,
518                             gfx::Rect(),
519                             params.user_gesture,
520                             new_guest->web_contents());
521}
522
523// TODO(fsamuel): Find a reliable way to test the 'responsive' and
524// 'unresponsive' events.
525void WebViewGuest::RendererResponsive(content::WebContents* source) {
526  scoped_ptr<base::DictionaryValue> args(new base::DictionaryValue());
527  args->SetInteger(webview::kProcessId,
528                   web_contents()->GetRenderProcessHost()->GetID());
529  DispatchEventToEmbedder(
530      new GuestViewBase::Event(webview::kEventResponsive, args.Pass()));
531}
532
533void WebViewGuest::RendererUnresponsive(content::WebContents* source) {
534  scoped_ptr<base::DictionaryValue> args(new base::DictionaryValue());
535  args->SetInteger(webview::kProcessId,
536                   web_contents()->GetRenderProcessHost()->GetID());
537  DispatchEventToEmbedder(
538      new GuestViewBase::Event(webview::kEventUnresponsive, args.Pass()));
539}
540
541void WebViewGuest::Observe(int type,
542                           const content::NotificationSource& source,
543                           const content::NotificationDetails& details) {
544  switch (type) {
545    case content::NOTIFICATION_LOAD_COMPLETED_MAIN_FRAME: {
546      DCHECK_EQ(content::Source<WebContents>(source).ptr(), web_contents());
547      if (content::Source<WebContents>(source).ptr() == web_contents())
548        LoadHandlerCalled();
549      break;
550    }
551    case content::NOTIFICATION_RESOURCE_RECEIVED_REDIRECT: {
552      DCHECK_EQ(content::Source<WebContents>(source).ptr(), web_contents());
553      content::ResourceRedirectDetails* resource_redirect_details =
554          content::Details<content::ResourceRedirectDetails>(details).ptr();
555      bool is_top_level = resource_redirect_details->resource_type ==
556                          content::RESOURCE_TYPE_MAIN_FRAME;
557      LoadRedirect(resource_redirect_details->url,
558                   resource_redirect_details->new_url,
559                   is_top_level);
560      break;
561    }
562    default:
563      NOTREACHED() << "Unexpected notification sent.";
564      break;
565  }
566}
567
568double WebViewGuest::GetZoom() {
569  if (!web_view_guest_delegate_)
570    return 1.0;
571  return web_view_guest_delegate_->GetZoom();
572}
573
574void WebViewGuest::Find(
575    const base::string16& search_text,
576    const blink::WebFindOptions& options,
577    scoped_refptr<WebViewInternalFindFunction> find_function) {
578  find_helper_.Find(web_contents(), search_text, options, find_function);
579}
580
581void WebViewGuest::StopFinding(content::StopFindAction action) {
582  find_helper_.CancelAllFindSessions();
583  web_contents()->StopFinding(action);
584}
585
586void WebViewGuest::Go(int relative_index) {
587  web_contents()->GetController().GoToOffset(relative_index);
588}
589
590void WebViewGuest::Reload() {
591  // TODO(fsamuel): Don't check for repost because we don't want to show
592  // Chromium's repost warning. We might want to implement a separate API
593  // for registering a callback if a repost is about to happen.
594  web_contents()->GetController().Reload(false);
595}
596
597void WebViewGuest::SetUserAgentOverride(
598    const std::string& user_agent_override) {
599  if (!attached())
600    return;
601  is_overriding_user_agent_ = !user_agent_override.empty();
602  if (is_overriding_user_agent_) {
603    content::RecordAction(UserMetricsAction("WebView.Guest.OverrideUA"));
604  }
605  web_contents()->SetUserAgentOverride(user_agent_override);
606}
607
608void WebViewGuest::Stop() {
609  web_contents()->Stop();
610}
611
612void WebViewGuest::Terminate() {
613  content::RecordAction(UserMetricsAction("WebView.Guest.Terminate"));
614  base::ProcessHandle process_handle =
615      web_contents()->GetRenderProcessHost()->GetHandle();
616  if (process_handle)
617    base::KillProcess(process_handle, content::RESULT_CODE_KILLED, false);
618}
619
620bool WebViewGuest::ClearData(const base::Time remove_since,
621                             uint32 removal_mask,
622                             const base::Closure& callback) {
623  content::RecordAction(UserMetricsAction("WebView.Guest.ClearData"));
624  content::StoragePartition* partition =
625      content::BrowserContext::GetStoragePartition(
626          web_contents()->GetBrowserContext(),
627          web_contents()->GetSiteInstance());
628
629  if (!partition)
630    return false;
631
632  partition->ClearData(
633      removal_mask,
634      content::StoragePartition::QUOTA_MANAGED_STORAGE_MASK_ALL,
635      GURL(),
636      content::StoragePartition::OriginMatcherFunction(),
637      remove_since,
638      base::Time::Now(),
639      callback);
640  return true;
641}
642
643WebViewGuest::WebViewGuest(content::BrowserContext* browser_context,
644                           int guest_instance_id)
645    : GuestView<WebViewGuest>(browser_context, guest_instance_id),
646      find_helper_(this),
647      is_overriding_user_agent_(false),
648      guest_opaque_(true),
649      javascript_dialog_helper_(this) {
650  web_view_guest_delegate_.reset(
651      ExtensionsAPIClient::Get()->CreateWebViewGuestDelegate(this));
652}
653
654WebViewGuest::~WebViewGuest() {
655}
656
657void WebViewGuest::DidCommitProvisionalLoadForFrame(
658    content::RenderFrameHost* render_frame_host,
659    const GURL& url,
660    ui::PageTransition transition_type) {
661  scoped_ptr<base::DictionaryValue> args(new base::DictionaryValue());
662  args->SetString(guestview::kUrl, url.spec());
663  args->SetBoolean(guestview::kIsTopLevel, !render_frame_host->GetParent());
664  args->SetString(webview::kInternalBaseURLForDataURL,
665                  web_contents()
666                      ->GetController()
667                      .GetLastCommittedEntry()
668                      ->GetBaseURLForDataURL()
669                      .spec());
670  args->SetInteger(webview::kInternalCurrentEntryIndex,
671                   web_contents()->GetController().GetCurrentEntryIndex());
672  args->SetInteger(webview::kInternalEntryCount,
673                   web_contents()->GetController().GetEntryCount());
674  args->SetInteger(webview::kInternalProcessId,
675                   web_contents()->GetRenderProcessHost()->GetID());
676  DispatchEventToEmbedder(
677      new GuestViewBase::Event(webview::kEventLoadCommit, args.Pass()));
678
679  find_helper_.CancelAllFindSessions();
680  if (web_view_guest_delegate_) {
681    web_view_guest_delegate_->OnDidCommitProvisionalLoadForFrame(
682        !render_frame_host->GetParent());
683  }
684}
685
686void WebViewGuest::DidFailProvisionalLoad(
687    content::RenderFrameHost* render_frame_host,
688    const GURL& validated_url,
689    int error_code,
690    const base::string16& error_description) {
691  LoadAbort(!render_frame_host->GetParent(), validated_url,
692            net::ErrorToShortString(error_code));
693}
694
695void WebViewGuest::DidStartProvisionalLoadForFrame(
696    content::RenderFrameHost* render_frame_host,
697    const GURL& validated_url,
698    bool is_error_page,
699    bool is_iframe_srcdoc) {
700  scoped_ptr<base::DictionaryValue> args(new base::DictionaryValue());
701  args->SetString(guestview::kUrl, validated_url.spec());
702  args->SetBoolean(guestview::kIsTopLevel, !render_frame_host->GetParent());
703  DispatchEventToEmbedder(
704      new GuestViewBase::Event(webview::kEventLoadStart, args.Pass()));
705}
706
707void WebViewGuest::DocumentLoadedInFrame(
708    content::RenderFrameHost* render_frame_host) {
709  if (web_view_guest_delegate_)
710    web_view_guest_delegate_->OnDocumentLoadedInFrame(render_frame_host);
711}
712
713bool WebViewGuest::OnMessageReceived(const IPC::Message& message,
714                                     RenderFrameHost* render_frame_host) {
715  bool handled = true;
716  IPC_BEGIN_MESSAGE_MAP(WebViewGuest, message)
717    IPC_MESSAGE_HANDLER(ExtensionHostMsg_FrameNameChanged, OnFrameNameChanged)
718    IPC_MESSAGE_UNHANDLED(handled = false)
719  IPC_END_MESSAGE_MAP()
720  return handled;
721}
722
723void WebViewGuest::RenderProcessGone(base::TerminationStatus status) {
724  // Cancel all find sessions in progress.
725  find_helper_.CancelAllFindSessions();
726
727  scoped_ptr<base::DictionaryValue> args(new base::DictionaryValue());
728  args->SetInteger(webview::kProcessId,
729                   web_contents()->GetRenderProcessHost()->GetID());
730  args->SetString(webview::kReason, TerminationStatusToString(status));
731  DispatchEventToEmbedder(
732      new GuestViewBase::Event(webview::kEventExit, args.Pass()));
733}
734
735void WebViewGuest::UserAgentOverrideSet(const std::string& user_agent) {
736  if (!attached())
737    return;
738  content::NavigationController& controller = web_contents()->GetController();
739  content::NavigationEntry* entry = controller.GetVisibleEntry();
740  if (!entry)
741    return;
742  entry->SetIsOverridingUserAgent(!user_agent.empty());
743  web_contents()->GetController().Reload(false);
744}
745
746void WebViewGuest::ReportFrameNameChange(const std::string& name) {
747  name_ = name;
748  scoped_ptr<base::DictionaryValue> args(new base::DictionaryValue());
749  args->SetString(webview::kName, name);
750  DispatchEventToEmbedder(
751      new GuestViewBase::Event(webview::kEventFrameNameChanged, args.Pass()));
752}
753
754void WebViewGuest::LoadHandlerCalled() {
755  scoped_ptr<base::DictionaryValue> args(new base::DictionaryValue());
756  DispatchEventToEmbedder(
757      new GuestViewBase::Event(webview::kEventContentLoad, args.Pass()));
758}
759
760void WebViewGuest::LoadRedirect(const GURL& old_url,
761                                const GURL& new_url,
762                                bool is_top_level) {
763  scoped_ptr<base::DictionaryValue> args(new base::DictionaryValue());
764  args->SetBoolean(guestview::kIsTopLevel, is_top_level);
765  args->SetString(webview::kNewURL, new_url.spec());
766  args->SetString(webview::kOldURL, old_url.spec());
767  DispatchEventToEmbedder(
768      new GuestViewBase::Event(webview::kEventLoadRedirect, args.Pass()));
769}
770
771void WebViewGuest::PushWebViewStateToIOThread() {
772  const GURL& site_url = web_contents()->GetSiteInstance()->GetSiteURL();
773  std::string partition_domain;
774  std::string partition_id;
775  bool in_memory;
776  if (!GetGuestPartitionConfigForSite(
777          site_url, &partition_domain, &partition_id, &in_memory)) {
778    NOTREACHED();
779    return;
780  }
781
782  WebViewRendererState::WebViewInfo web_view_info;
783  web_view_info.embedder_process_id = embedder_render_process_id();
784  web_view_info.instance_id = view_instance_id();
785  web_view_info.partition_id = partition_id;
786  web_view_info.embedder_extension_id = embedder_extension_id();
787
788  content::BrowserThread::PostTask(
789      content::BrowserThread::IO,
790      FROM_HERE,
791      base::Bind(&WebViewRendererState::AddGuest,
792                 base::Unretained(WebViewRendererState::GetInstance()),
793                 web_contents()->GetRenderProcessHost()->GetID(),
794                 web_contents()->GetRoutingID(),
795                 web_view_info));
796}
797
798// static
799void WebViewGuest::RemoveWebViewStateFromIOThread(
800    WebContents* web_contents) {
801  content::BrowserThread::PostTask(
802      content::BrowserThread::IO, FROM_HERE,
803      base::Bind(
804          &WebViewRendererState::RemoveGuest,
805          base::Unretained(WebViewRendererState::GetInstance()),
806          web_contents->GetRenderProcessHost()->GetID(),
807          web_contents->GetRoutingID()));
808}
809
810content::WebContents* WebViewGuest::CreateNewGuestWindow(
811    const content::WebContents::CreateParams& create_params) {
812  GuestViewManager* guest_manager =
813      GuestViewManager::FromBrowserContext(browser_context());
814  return guest_manager->CreateGuestWithWebContentsParams(
815      WebViewGuest::Type,
816      embedder_extension_id(),
817      embedder_web_contents()->GetRenderProcessHost()->GetID(),
818      create_params);
819}
820
821void WebViewGuest::RequestMediaAccessPermission(
822    content::WebContents* source,
823    const content::MediaStreamRequest& request,
824    const content::MediaResponseCallback& callback) {
825  web_view_permission_helper_->RequestMediaAccessPermission(source,
826                                                            request,
827                                                            callback);
828}
829
830bool WebViewGuest::CheckMediaAccessPermission(content::WebContents* source,
831                                              const GURL& security_origin,
832                                              content::MediaStreamType type) {
833  return web_view_permission_helper_->CheckMediaAccessPermission(
834      source, security_origin, type);
835}
836
837void WebViewGuest::CanDownload(
838    content::RenderViewHost* render_view_host,
839    const GURL& url,
840    const std::string& request_method,
841    const base::Callback<void(bool)>& callback) {
842  web_view_permission_helper_->CanDownload(render_view_host,
843                                           url,
844                                           request_method,
845                                           callback);
846}
847
848void WebViewGuest::RequestPointerLockPermission(
849    bool user_gesture,
850    bool last_unlocked_by_target,
851    const base::Callback<void(bool)>& callback) {
852  web_view_permission_helper_->RequestPointerLockPermission(
853      user_gesture,
854      last_unlocked_by_target,
855      callback);
856}
857
858void WebViewGuest::WillAttachToEmbedder() {
859  // We must install the mapping from guests to WebViews prior to resuming
860  // suspended resource loads so that the WebRequest API will catch resource
861  // requests.
862  PushWebViewStateToIOThread();
863}
864
865content::JavaScriptDialogManager*
866    WebViewGuest::GetJavaScriptDialogManager() {
867  return &javascript_dialog_helper_;
868}
869
870content::ColorChooser* WebViewGuest::OpenColorChooser(
871    WebContents* web_contents,
872    SkColor color,
873    const std::vector<content::ColorSuggestion>& suggestions) {
874  if (!attached() || !embedder_web_contents()->GetDelegate())
875    return NULL;
876  return embedder_web_contents()->GetDelegate()->OpenColorChooser(
877      web_contents, color, suggestions);
878}
879
880void WebViewGuest::NavigateGuest(const std::string& src) {
881  if (!attached())
882    return;
883
884  GURL url = ResolveURL(src);
885
886  // Do not allow navigating a guest to schemes other than known safe schemes.
887  // This will block the embedder trying to load unwanted schemes, e.g.
888  // chrome://settings.
889  bool scheme_is_blocked =
890      (!content::ChildProcessSecurityPolicy::GetInstance()->IsWebSafeScheme(
891           url.scheme()) &&
892       !url.SchemeIs(url::kAboutScheme)) ||
893      url.SchemeIs(url::kJavaScriptScheme);
894  if (scheme_is_blocked || !url.is_valid()) {
895    LoadAbort(true /* is_top_level */, url,
896              net::ErrorToShortString(net::ERR_ABORTED));
897    return;
898  }
899
900  GURL validated_url(url);
901  web_contents()->GetRenderProcessHost()->FilterURL(false, &validated_url);
902  // As guests do not swap processes on navigation, only navigations to
903  // normal web URLs are supported.  No protocol handlers are installed for
904  // other schemes (e.g., WebUI or extensions), and no permissions or bindings
905  // can be granted to the guest process.
906  LoadURLWithParams(validated_url,
907                    content::Referrer(),
908                    ui::PAGE_TRANSITION_AUTO_TOPLEVEL,
909                    web_contents());
910}
911
912bool WebViewGuest::HandleKeyboardShortcuts(
913    const content::NativeWebKeyboardEvent& event) {
914  if (event.type != blink::WebInputEvent::RawKeyDown)
915    return false;
916
917  // If the user hits the escape key without any modifiers then unlock the
918  // mouse if necessary.
919  if ((event.windowsKeyCode == ui::VKEY_ESCAPE) &&
920      !(event.modifiers & blink::WebInputEvent::InputModifiers)) {
921    return web_contents()->GotResponseToLockMouseRequest(false);
922  }
923
924#if defined(OS_MACOSX)
925  if (event.modifiers != blink::WebInputEvent::MetaKey)
926    return false;
927
928  if (event.windowsKeyCode == ui::VKEY_OEM_4) {
929    Go(-1);
930    return true;
931  }
932
933  if (event.windowsKeyCode == ui::VKEY_OEM_6) {
934    Go(1);
935    return true;
936  }
937#else
938  if (event.windowsKeyCode == ui::VKEY_BROWSER_BACK) {
939    Go(-1);
940    return true;
941  }
942
943  if (event.windowsKeyCode == ui::VKEY_BROWSER_FORWARD) {
944    Go(1);
945    return true;
946  }
947#endif
948
949  return false;
950}
951
952void WebViewGuest::SetUpAutoSize() {
953  // Read the autosize parameters passed in from the embedder.
954  bool auto_size_enabled = false;
955  attach_params()->GetBoolean(webview::kAttributeAutoSize, &auto_size_enabled);
956
957  int max_height = 0;
958  int max_width = 0;
959  attach_params()->GetInteger(webview::kAttributeMaxHeight, &max_height);
960  attach_params()->GetInteger(webview::kAttributeMaxWidth, &max_width);
961
962  int min_height = 0;
963  int min_width = 0;
964  attach_params()->GetInteger(webview::kAttributeMinHeight, &min_height);
965  attach_params()->GetInteger(webview::kAttributeMinWidth, &min_width);
966
967  // Call SetAutoSize to apply all the appropriate validation and clipping of
968  // values.
969  SetAutoSize(auto_size_enabled,
970              gfx::Size(min_width, min_height),
971              gfx::Size(max_width, max_height));
972}
973
974void WebViewGuest::ShowContextMenu(
975    int request_id,
976    const WebViewGuestDelegate::MenuItemVector* items) {
977  if (web_view_guest_delegate_)
978    web_view_guest_delegate_->OnShowContextMenu(request_id, items);
979}
980
981void WebViewGuest::SetName(const std::string& name) {
982  if (name_ == name)
983    return;
984  name_ = name;
985
986  Send(new ExtensionMsg_SetFrameName(routing_id(), name_));
987}
988
989void WebViewGuest::SetZoom(double zoom_factor) {
990  if (web_view_guest_delegate_)
991    web_view_guest_delegate_->OnSetZoom(zoom_factor);
992}
993
994void WebViewGuest::SetAllowTransparency(bool allow) {
995  if (guest_opaque_ != allow)
996    return;
997
998  guest_opaque_ = !allow;
999  if (!web_contents()->GetRenderViewHost()->GetView())
1000    return;
1001
1002  web_contents()->GetRenderViewHost()->GetView()->SetBackgroundOpaque(!allow);
1003}
1004
1005void WebViewGuest::AddNewContents(content::WebContents* source,
1006                                  content::WebContents* new_contents,
1007                                  WindowOpenDisposition disposition,
1008                                  const gfx::Rect& initial_pos,
1009                                  bool user_gesture,
1010                                  bool* was_blocked) {
1011  if (was_blocked)
1012    *was_blocked = false;
1013  RequestNewWindowPermission(disposition,
1014                             initial_pos,
1015                             user_gesture,
1016                             new_contents);
1017}
1018
1019content::WebContents* WebViewGuest::OpenURLFromTab(
1020    content::WebContents* source,
1021    const content::OpenURLParams& params) {
1022  // If the guest wishes to navigate away prior to attachment then we save the
1023  // navigation to perform upon attachment. Navigation initializes a lot of
1024  // state that assumes an embedder exists, such as RenderWidgetHostViewGuest.
1025  // Navigation also resumes resource loading which we don't want to allow
1026  // until attachment.
1027  if (!attached()) {
1028    WebViewGuest* opener = GetOpener();
1029    PendingWindowMap::iterator it =
1030        opener->pending_new_windows_.find(this);
1031    if (it == opener->pending_new_windows_.end())
1032      return NULL;
1033    const NewWindowInfo& info = it->second;
1034    NewWindowInfo new_window_info(params.url, info.name);
1035    new_window_info.changed = new_window_info.url != info.url;
1036    it->second = new_window_info;
1037    return NULL;
1038  }
1039  if (params.disposition == CURRENT_TAB) {
1040    // This can happen for cross-site redirects.
1041    LoadURLWithParams(params.url, params.referrer, params.transition, source);
1042    return source;
1043  }
1044
1045  CreateNewGuestWebViewWindow(params);
1046  return NULL;
1047}
1048
1049void WebViewGuest::WebContentsCreated(WebContents* source_contents,
1050                                      int opener_render_frame_id,
1051                                      const base::string16& frame_name,
1052                                      const GURL& target_url,
1053                                      content::WebContents* new_contents) {
1054  WebViewGuest* guest = WebViewGuest::FromWebContents(new_contents);
1055  CHECK(guest);
1056  guest->SetOpener(this);
1057  std::string guest_name = base::UTF16ToUTF8(frame_name);
1058  guest->name_ = guest_name;
1059  pending_new_windows_.insert(
1060      std::make_pair(guest, NewWindowInfo(target_url, guest_name)));
1061}
1062
1063void WebViewGuest::LoadURLWithParams(const GURL& url,
1064                                     const content::Referrer& referrer,
1065                                     ui::PageTransition transition_type,
1066                                     content::WebContents* web_contents) {
1067  content::NavigationController::LoadURLParams load_url_params(url);
1068  load_url_params.referrer = referrer;
1069  load_url_params.transition_type = transition_type;
1070  load_url_params.extra_headers = std::string();
1071  if (is_overriding_user_agent_) {
1072    load_url_params.override_user_agent =
1073        content::NavigationController::UA_OVERRIDE_TRUE;
1074  }
1075  web_contents->GetController().LoadURLWithParams(load_url_params);
1076}
1077
1078void WebViewGuest::RequestNewWindowPermission(
1079    WindowOpenDisposition disposition,
1080    const gfx::Rect& initial_bounds,
1081    bool user_gesture,
1082    content::WebContents* new_contents) {
1083  WebViewGuest* guest = WebViewGuest::FromWebContents(new_contents);
1084  if (!guest)
1085    return;
1086  PendingWindowMap::iterator it = pending_new_windows_.find(guest);
1087  if (it == pending_new_windows_.end())
1088    return;
1089  const NewWindowInfo& new_window_info = it->second;
1090
1091  // Retrieve the opener partition info if we have it.
1092  const GURL& site_url = new_contents->GetSiteInstance()->GetSiteURL();
1093  std::string storage_partition_id = GetStoragePartitionIdFromSiteURL(site_url);
1094
1095  base::DictionaryValue request_info;
1096  request_info.SetInteger(webview::kInitialHeight, initial_bounds.height());
1097  request_info.SetInteger(webview::kInitialWidth, initial_bounds.width());
1098  request_info.Set(webview::kTargetURL,
1099                   new base::StringValue(new_window_info.url.spec()));
1100  request_info.Set(webview::kName, new base::StringValue(new_window_info.name));
1101  request_info.SetInteger(webview::kWindowID, guest->guest_instance_id());
1102  // We pass in partition info so that window-s created through newwindow
1103  // API can use it to set their partition attribute.
1104  request_info.Set(webview::kStoragePartitionId,
1105                   new base::StringValue(storage_partition_id));
1106  request_info.Set(
1107      webview::kWindowOpenDisposition,
1108      new base::StringValue(WindowOpenDispositionToString(disposition)));
1109
1110  web_view_permission_helper_->
1111      RequestPermission(WEB_VIEW_PERMISSION_TYPE_NEW_WINDOW,
1112                        request_info,
1113                        base::Bind(&WebViewGuest::OnWebViewNewWindowResponse,
1114                                   base::Unretained(this),
1115                                   guest->guest_instance_id()),
1116                                   false /* allowed_by_default */);
1117}
1118
1119void WebViewGuest::DestroyUnattachedWindows() {
1120  // Destroy() reaches in and removes the WebViewGuest from its opener's
1121  // pending_new_windows_ set. To avoid mutating the set while iterating, we
1122  // create a copy of the pending new windows set and iterate over the copy.
1123  PendingWindowMap pending_new_windows(pending_new_windows_);
1124  // Clean up unattached new windows opened by this guest.
1125  for (PendingWindowMap::const_iterator it = pending_new_windows.begin();
1126       it != pending_new_windows.end(); ++it) {
1127    it->first->Destroy();
1128  }
1129  // All pending windows should be removed from the set after Destroy() is
1130  // called on all of them.
1131  DCHECK(pending_new_windows_.empty());
1132}
1133
1134GURL WebViewGuest::ResolveURL(const std::string& src) {
1135  if (!in_extension()) {
1136    return GURL(src);
1137  }
1138
1139  GURL default_url(base::StringPrintf("%s://%s/",
1140                                      kExtensionScheme,
1141                                      embedder_extension_id().c_str()));
1142  return default_url.Resolve(src);
1143}
1144
1145void WebViewGuest::OnWebViewNewWindowResponse(
1146    int new_window_instance_id,
1147    bool allow,
1148    const std::string& user_input) {
1149  WebViewGuest* guest =
1150      WebViewGuest::From(embedder_render_process_id(), new_window_instance_id);
1151  if (!guest)
1152    return;
1153
1154  if (!allow)
1155    guest->Destroy();
1156}
1157
1158}  // namespace extensions
1159