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