Home | History | Annotate | Download | only in browser_plugin
      1 // Copyright (c) 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 "content/browser/browser_plugin/browser_plugin_guest_manager.h"
      6 
      7 #include "base/command_line.h"
      8 #include "content/browser/browser_plugin/browser_plugin_guest.h"
      9 #include "content/browser/browser_plugin/browser_plugin_host_factory.h"
     10 #include "content/browser/renderer_host/render_view_host_impl.h"
     11 #include "content/browser/web_contents/web_contents_impl.h"
     12 #include "content/common/browser_plugin/browser_plugin_constants.h"
     13 #include "content/common/browser_plugin/browser_plugin_messages.h"
     14 #include "content/common/content_export.h"
     15 #include "content/public/browser/user_metrics.h"
     16 #include "content/public/common/content_switches.h"
     17 #include "content/public/common/result_codes.h"
     18 #include "content/public/common/url_constants.h"
     19 #include "net/base/escape.h"
     20 #include "ui/base/keycodes/keyboard_codes.h"
     21 
     22 namespace content {
     23 
     24 // static
     25 BrowserPluginHostFactory* BrowserPluginGuestManager::factory_ = NULL;
     26 
     27 BrowserPluginGuestManager::BrowserPluginGuestManager()
     28     : next_instance_id_(browser_plugin::kInstanceIDNone) {
     29 }
     30 
     31 BrowserPluginGuestManager::~BrowserPluginGuestManager() {
     32 }
     33 
     34 // static
     35 BrowserPluginGuestManager* BrowserPluginGuestManager::Create() {
     36   if (factory_)
     37     return factory_->CreateBrowserPluginGuestManager();
     38   return new BrowserPluginGuestManager();
     39 }
     40 
     41 BrowserPluginGuest* BrowserPluginGuestManager::CreateGuest(
     42     SiteInstance* embedder_site_instance,
     43     int instance_id,
     44     const BrowserPluginHostMsg_Attach_Params& params,
     45     scoped_ptr<base::DictionaryValue> extra_params) {
     46   SiteInstance* guest_site_instance = NULL;
     47   // Validate that the partition id coming from the renderer is valid UTF-8,
     48   // since we depend on this in other parts of the code, such as FilePath
     49   // creation. If the validation fails, treat it as a bad message and kill the
     50   // renderer process.
     51   if (!IsStringUTF8(params.storage_partition_id)) {
     52     content::RecordAction(UserMetricsAction("BadMessageTerminate_BPGM"));
     53     base::KillProcess(
     54         embedder_site_instance->GetProcess()->GetHandle(),
     55         content::RESULT_CODE_KILLED_BAD_MESSAGE, false);
     56     return NULL;
     57   }
     58 
     59   const CommandLine& command_line = *CommandLine::ForCurrentProcess();
     60   if (command_line.HasSwitch(switches::kSitePerProcess)) {
     61     // When --site-per-process is specified, the behavior of BrowserPlugin
     62     // as <webview> is broken and we use it for rendering out-of-process
     63     // iframes instead. We use the src URL sent by the renderer to find the
     64     // right process in which to place this instance.
     65     // Note: Since BrowserPlugin doesn't support cross-process navigation,
     66     // the instance will stay in the initially assigned process, regardless
     67     // of the site it is navigated to.
     68     // TODO(nasko): Fix this, and such that cross-process navigations are
     69     // supported.
     70     guest_site_instance =
     71         embedder_site_instance->GetRelatedSiteInstance(GURL(params.src));
     72   } else {
     73     const std::string& host = embedder_site_instance->GetSiteURL().host();
     74 
     75     std::string url_encoded_partition = net::EscapeQueryParamValue(
     76         params.storage_partition_id, false);
     77     // The SiteInstance of a given webview tag is based on the fact that it's
     78     // a guest process in addition to which platform application the tag
     79     // belongs to and what storage partition is in use, rather than the URL
     80     // that the tag is being navigated to.
     81     GURL guest_site(
     82         base::StringPrintf("%s://%s/%s?%s", chrome::kGuestScheme,
     83                             host.c_str(),
     84                             params.persist_storage ? "persist" : "",
     85                             url_encoded_partition.c_str()));
     86 
     87     // If we already have a webview tag in the same app using the same storage
     88     // partition, we should use the same SiteInstance so the existing tag and
     89     // the new tag can script each other.
     90     guest_site_instance = GetGuestSiteInstance(guest_site);
     91     if (!guest_site_instance) {
     92       // Create the SiteInstance in a new BrowsingInstance, which will ensure
     93       // that webview tags are also not allowed to send messages across
     94       // different partitions.
     95       guest_site_instance = SiteInstance::CreateForURL(
     96           embedder_site_instance->GetBrowserContext(), guest_site);
     97     }
     98   }
     99 
    100   return WebContentsImpl::CreateGuest(
    101       embedder_site_instance->GetBrowserContext(),
    102       guest_site_instance,
    103       instance_id,
    104       extra_params.Pass());
    105 }
    106 
    107 BrowserPluginGuest* BrowserPluginGuestManager::GetGuestByInstanceID(
    108     int instance_id,
    109     int embedder_render_process_id) const {
    110   if (!CanEmbedderAccessInstanceIDMaybeKill(embedder_render_process_id,
    111                                             instance_id)) {
    112     return NULL;
    113   }
    114   GuestInstanceMap::const_iterator it =
    115       guest_web_contents_by_instance_id_.find(instance_id);
    116   if (it == guest_web_contents_by_instance_id_.end())
    117     return NULL;
    118   return static_cast<WebContentsImpl*>(it->second)->GetBrowserPluginGuest();
    119 }
    120 
    121 void BrowserPluginGuestManager::AddGuest(int instance_id,
    122                                          WebContentsImpl* guest_web_contents) {
    123   DCHECK(guest_web_contents_by_instance_id_.find(instance_id) ==
    124          guest_web_contents_by_instance_id_.end());
    125   guest_web_contents_by_instance_id_[instance_id] = guest_web_contents;
    126 }
    127 
    128 void BrowserPluginGuestManager::RemoveGuest(int instance_id) {
    129   DCHECK(guest_web_contents_by_instance_id_.find(instance_id) !=
    130          guest_web_contents_by_instance_id_.end());
    131   guest_web_contents_by_instance_id_.erase(instance_id);
    132 }
    133 
    134 bool BrowserPluginGuestManager::CanEmbedderAccessInstanceIDMaybeKill(
    135     int embedder_render_process_id,
    136     int instance_id) const {
    137   if (!CanEmbedderAccessInstanceID(embedder_render_process_id, instance_id)) {
    138     // The embedder process is trying to access a guest it does not own.
    139     content::RecordAction(UserMetricsAction("BadMessageTerminate_BPGM"));
    140     base::KillProcess(
    141         RenderProcessHost::FromID(embedder_render_process_id)->GetHandle(),
    142         content::RESULT_CODE_KILLED_BAD_MESSAGE, false);
    143     return false;
    144   }
    145   return true;
    146 }
    147 
    148 void BrowserPluginGuestManager::OnMessageReceived(const IPC::Message& message,
    149                                                   int render_process_id) {
    150   if (BrowserPluginGuest::ShouldForwardToBrowserPluginGuest(message)) {
    151     int instance_id = 0;
    152     // All allowed messages must have instance_id as their first parameter.
    153     PickleIterator iter(message);
    154     bool success = iter.ReadInt(&instance_id);
    155     DCHECK(success);
    156     BrowserPluginGuest* guest =
    157         GetGuestByInstanceID(instance_id, render_process_id);
    158     if (guest && guest->OnMessageReceivedFromEmbedder(message))
    159       return;
    160   }
    161   IPC_BEGIN_MESSAGE_MAP(BrowserPluginGuestManager, message)
    162     IPC_MESSAGE_HANDLER(BrowserPluginHostMsg_BuffersSwappedACK,
    163                         OnUnhandledSwapBuffersACK)
    164   IPC_END_MESSAGE_MAP()
    165 }
    166 
    167 // static
    168 bool BrowserPluginGuestManager::CanEmbedderAccessGuest(
    169     int embedder_render_process_id,
    170     BrowserPluginGuest* guest) {
    171   // The embedder can access the guest if it has not been attached and its
    172   // opener's embedder lives in the same process as the given embedder.
    173   if (!guest->attached()) {
    174     if (!guest->opener())
    175       return false;
    176 
    177     return embedder_render_process_id ==
    178         guest->opener()->embedder_web_contents()->GetRenderProcessHost()->
    179             GetID();
    180   }
    181 
    182   return embedder_render_process_id ==
    183       guest->embedder_web_contents()->GetRenderProcessHost()->GetID();
    184 }
    185 
    186 bool BrowserPluginGuestManager::CanEmbedderAccessInstanceID(
    187     int embedder_render_process_id,
    188     int instance_id) const {
    189   // The embedder is trying to access a guest with a negative or zero
    190   // instance ID.
    191   if (instance_id <= browser_plugin::kInstanceIDNone)
    192     return false;
    193 
    194   // The embedder is trying to access an instance ID that has not yet been
    195   // allocated by BrowserPluginGuestManager. This could cause instance ID
    196   // collisions in the future, and potentially give one embedder access to a
    197   // guest it does not own.
    198   if (instance_id > next_instance_id_)
    199     return false;
    200 
    201   GuestInstanceMap::const_iterator it =
    202       guest_web_contents_by_instance_id_.find(instance_id);
    203   if (it == guest_web_contents_by_instance_id_.end())
    204     return true;
    205   BrowserPluginGuest* guest =
    206       static_cast<WebContentsImpl*>(it->second)->GetBrowserPluginGuest();
    207 
    208   return CanEmbedderAccessGuest(embedder_render_process_id, guest);
    209 }
    210 
    211 SiteInstance* BrowserPluginGuestManager::GetGuestSiteInstance(
    212     const GURL& guest_site) {
    213   for (GuestInstanceMap::const_iterator it =
    214        guest_web_contents_by_instance_id_.begin();
    215        it != guest_web_contents_by_instance_id_.end(); ++it) {
    216     if (it->second->GetSiteInstance()->GetSiteURL() == guest_site)
    217       return it->second->GetSiteInstance();
    218   }
    219   return NULL;
    220 }
    221 
    222 // We only get here during teardown if we have one last buffer pending,
    223 // otherwise the ACK is handled by the guest.
    224 void BrowserPluginGuestManager::OnUnhandledSwapBuffersACK(
    225     int instance_id,
    226     int route_id,
    227     int gpu_host_id,
    228     const std::string& mailbox_name,
    229     uint32 sync_point) {
    230   BrowserPluginGuest::AcknowledgeBufferPresent(route_id,
    231                                                gpu_host_id,
    232                                                mailbox_name,
    233                                                sync_point);
    234 }
    235 
    236 void BrowserPluginGuestManager::DidSendScreenRects(
    237     WebContentsImpl* embedder_web_contents) {
    238   // TODO(lazyboy): Generalize iterating over guest instances and performing
    239   // actions on the guests.
    240   for (GuestInstanceMap::iterator it =
    241            guest_web_contents_by_instance_id_.begin();
    242                it != guest_web_contents_by_instance_id_.end(); ++it) {
    243     BrowserPluginGuest* guest = it->second->GetBrowserPluginGuest();
    244     if (embedder_web_contents == guest->embedder_web_contents()) {
    245       static_cast<RenderViewHostImpl*>(
    246           guest->GetWebContents()->GetRenderViewHost())->SendScreenRects();
    247     }
    248   }
    249 }
    250 
    251 bool BrowserPluginGuestManager::UnlockMouseIfNecessary(
    252     WebContentsImpl* embedder_web_contents,
    253     const NativeWebKeyboardEvent& event) {
    254   if ((event.type != WebKit::WebInputEvent::RawKeyDown) ||
    255       (event.windowsKeyCode != ui::VKEY_ESCAPE) ||
    256       (event.modifiers & WebKit::WebInputEvent::InputModifiers)) {
    257     return false;
    258   }
    259 
    260   // TODO(lazyboy): Generalize iterating over guest instances and performing
    261   // actions on the guests.
    262   for (GuestInstanceMap::iterator it =
    263            guest_web_contents_by_instance_id_.begin();
    264                it != guest_web_contents_by_instance_id_.end(); ++it) {
    265     BrowserPluginGuest* guest = it->second->GetBrowserPluginGuest();
    266     if (embedder_web_contents == guest->embedder_web_contents()) {
    267       if (guest->UnlockMouseIfNecessary(event))
    268         return true;
    269     }
    270   }
    271   return false;
    272 }
    273 
    274 }  // namespace content
    275