| OLD | NEW |
| 1 // Copyright 2014 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "chrome/renderer/extensions/chrome_extensions_renderer_client.h" | 5 #include "chrome/renderer/extensions/chrome_extensions_renderer_client.h" |
| 6 | 6 |
| 7 #include "base/command_line.h" |
| 7 #include "base/lazy_instance.h" | 8 #include "base/lazy_instance.h" |
| 8 #include "chrome/common/chrome_isolated_world_ids.h" | 9 #include "chrome/common/chrome_isolated_world_ids.h" |
| 10 #include "chrome/common/chrome_switches.h" |
| 11 #include "chrome/common/extensions/extension_constants.h" |
| 12 #include "chrome/common/extensions/extension_metrics.h" |
| 13 #include "chrome/common/extensions/extension_process_policy.h" |
| 14 #include "chrome/common/url_constants.h" |
| 9 #include "chrome/renderer/chrome_render_process_observer.h" | 15 #include "chrome/renderer/chrome_render_process_observer.h" |
| 16 #include "chrome/renderer/extensions/chrome_extensions_dispatcher_delegate.h" |
| 17 #include "chrome/renderer/extensions/renderer_permissions_policy_delegate.h" |
| 18 #include "chrome/renderer/extensions/resource_request_policy.h" |
| 19 #include "chrome/renderer/media/cast_ipc_dispatcher.h" |
| 20 #include "content/public/common/content_constants.h" |
| 21 #include "content/public/common/content_switches.h" |
| 22 #include "content/public/renderer/render_thread.h" |
| 23 #include "extensions/common/constants.h" |
| 24 #include "extensions/common/extension.h" |
| 25 #include "extensions/common/extension_set.h" |
| 26 #include "extensions/common/switches.h" |
| 27 #include "extensions/renderer/dispatcher.h" |
| 28 #include "extensions/renderer/extension_frame_helper.h" |
| 29 #include "extensions/renderer/extension_helper.h" |
| 30 #include "extensions/renderer/extensions_render_frame_observer.h" |
| 31 #include "extensions/renderer/guest_view/extensions_guest_view_container.h" |
| 32 #include "extensions/renderer/guest_view/extensions_guest_view_container_dispatc
her.h" |
| 33 #include "extensions/renderer/guest_view/mime_handler_view/mime_handler_view_con
tainer.h" |
| 34 #include "extensions/renderer/script_context.h" |
| 35 #include "third_party/WebKit/public/platform/WebURL.h" |
| 36 #include "third_party/WebKit/public/web/WebDocument.h" |
| 37 #include "third_party/WebKit/public/web/WebLocalFrame.h" |
| 38 #include "third_party/WebKit/public/web/WebPluginParams.h" |
| 39 |
| 40 using extensions::Extension; |
| 41 |
| 42 namespace { |
| 43 |
| 44 bool IsStandaloneExtensionProcess() { |
| 45 return base::CommandLine::ForCurrentProcess()->HasSwitch( |
| 46 extensions::switches::kExtensionProcess); |
| 47 } |
| 48 |
| 49 void IsGuestViewApiAvailableToScriptContext( |
| 50 bool* api_is_available, |
| 51 extensions::ScriptContext* context) { |
| 52 if (context->GetAvailability("guestViewInternal").is_available()) { |
| 53 *api_is_available = true; |
| 54 } |
| 55 } |
| 56 |
| 57 // Returns true if the frame is navigating to an URL either into or out of an |
| 58 // extension app's extent. |
| 59 bool CrossesExtensionExtents(blink::WebLocalFrame* frame, |
| 60 const GURL& new_url, |
| 61 bool is_extension_url, |
| 62 bool is_initial_navigation) { |
| 63 DCHECK(!frame->parent()); |
| 64 GURL old_url(frame->document().url()); |
| 65 |
| 66 extensions::RendererExtensionRegistry* extension_registry = |
| 67 extensions::RendererExtensionRegistry::Get(); |
| 68 |
| 69 // If old_url is still empty and this is an initial navigation, then this is |
| 70 // a window.open operation. We should look at the opener URL. Note that the |
| 71 // opener is a local frame in this case. |
| 72 if (is_initial_navigation && old_url.is_empty() && frame->opener()) { |
| 73 blink::WebLocalFrame* opener_frame = frame->opener()->toWebLocalFrame(); |
| 74 |
| 75 // If we're about to open a normal web page from a same-origin opener stuck |
| 76 // in an extension process, we want to keep it in process to allow the |
| 77 // opener to script it. |
| 78 blink::WebDocument opener_document = opener_frame->document(); |
| 79 blink::WebSecurityOrigin opener_origin = opener_document.securityOrigin(); |
| 80 bool opener_is_extension_url = !opener_origin.isUnique() && |
| 81 extension_registry->GetExtensionOrAppByURL( |
| 82 opener_document.url()) != nullptr; |
| 83 if (!is_extension_url && !opener_is_extension_url && |
| 84 IsStandaloneExtensionProcess() && |
| 85 opener_origin.canRequest(blink::WebURL(new_url))) |
| 86 return false; |
| 87 |
| 88 // In all other cases, we want to compare against the URL that determines |
| 89 // the type of process. In default Chrome, that's the URL of the opener's |
| 90 // top frame and not the opener frame itself. In --site-per-process, we |
| 91 // can use the opener frame itself. |
| 92 // TODO(nick): Either wire this up to SiteIsolationPolicy, or to state on |
| 93 // |opener_frame|/its ancestors. |
| 94 if (base::CommandLine::ForCurrentProcess()->HasSwitch( |
| 95 switches::kSitePerProcess) || |
| 96 base::CommandLine::ForCurrentProcess()->HasSwitch( |
| 97 switches::kIsolateExtensions)) |
| 98 old_url = opener_frame->document().url(); |
| 99 else |
| 100 old_url = opener_frame->top()->document().url(); |
| 101 } |
| 102 |
| 103 // Only consider keeping non-app URLs in an app process if this window |
| 104 // has an opener (in which case it might be an OAuth popup that tries to |
| 105 // script an iframe within the app). |
| 106 bool should_consider_workaround = !!frame->opener(); |
| 107 |
| 108 return extensions::CrossesExtensionProcessBoundary( |
| 109 *extension_registry->GetMainThreadExtensionSet(), old_url, new_url, |
| 110 should_consider_workaround); |
| 111 } |
| 112 |
| 113 } // namespace |
| 10 | 114 |
| 11 ChromeExtensionsRendererClient::ChromeExtensionsRendererClient() {} | 115 ChromeExtensionsRendererClient::ChromeExtensionsRendererClient() {} |
| 12 | 116 |
| 13 ChromeExtensionsRendererClient::~ChromeExtensionsRendererClient() {} | 117 ChromeExtensionsRendererClient::~ChromeExtensionsRendererClient() {} |
| 14 | 118 |
| 15 // static | 119 // static |
| 16 ChromeExtensionsRendererClient* ChromeExtensionsRendererClient::GetInstance() { | 120 ChromeExtensionsRendererClient* ChromeExtensionsRendererClient::GetInstance() { |
| 17 static base::LazyInstance<ChromeExtensionsRendererClient> client = | 121 static base::LazyInstance<ChromeExtensionsRendererClient> client = |
| 18 LAZY_INSTANCE_INITIALIZER; | 122 LAZY_INSTANCE_INITIALIZER; |
| 19 return client.Pointer(); | 123 return client.Pointer(); |
| 20 } | 124 } |
| 21 | 125 |
| 22 bool ChromeExtensionsRendererClient::IsIncognitoProcess() const { | 126 bool ChromeExtensionsRendererClient::IsIncognitoProcess() const { |
| 23 return ChromeRenderProcessObserver::is_incognito_process(); | 127 return ChromeRenderProcessObserver::is_incognito_process(); |
| 24 } | 128 } |
| 25 | 129 |
| 26 int ChromeExtensionsRendererClient::GetLowestIsolatedWorldId() const { | 130 int ChromeExtensionsRendererClient::GetLowestIsolatedWorldId() const { |
| 27 return chrome::ISOLATED_WORLD_ID_EXTENSIONS; | 131 return chrome::ISOLATED_WORLD_ID_EXTENSIONS; |
| 28 } | 132 } |
| 133 |
| 134 void ChromeExtensionsRendererClient::RenderThreadStarted() { |
| 135 content::RenderThread* thread = content::RenderThread::Get(); |
| 136 extension_dispatcher_delegate_.reset( |
| 137 new ChromeExtensionsDispatcherDelegate()); |
| 138 // ChromeRenderViewTest::SetUp() creates its own ExtensionDispatcher and |
| 139 // injects it using SetExtensionDispatcher(). Don't overwrite it. |
| 140 if (!extension_dispatcher_) { |
| 141 extension_dispatcher_.reset( |
| 142 new extensions::Dispatcher(extension_dispatcher_delegate_.get())); |
| 143 } |
| 144 permissions_policy_delegate_.reset( |
| 145 new extensions::RendererPermissionsPolicyDelegate( |
| 146 extension_dispatcher_.get())); |
| 147 resource_request_policy_.reset( |
| 148 new extensions::ResourceRequestPolicy(extension_dispatcher_.get())); |
| 149 guest_view_container_dispatcher_.reset( |
| 150 new extensions::ExtensionsGuestViewContainerDispatcher()); |
| 151 |
| 152 thread->AddObserver(extension_dispatcher_.get()); |
| 153 thread->AddObserver(guest_view_container_dispatcher_.get()); |
| 154 thread->AddFilter(new CastIPCDispatcher(thread->GetIOMessageLoopProxy())); |
| 155 } |
| 156 |
| 157 void ChromeExtensionsRendererClient::RenderFrameCreated( |
| 158 content::RenderFrame* render_frame) { |
| 159 new extensions::ExtensionsRenderFrameObserver(render_frame); |
| 160 new extensions::ExtensionFrameHelper(render_frame, |
| 161 extension_dispatcher_.get()); |
| 162 extension_dispatcher_->OnRenderFrameCreated(render_frame); |
| 163 } |
| 164 |
| 165 void ChromeExtensionsRendererClient::RenderViewCreated( |
| 166 content::RenderView* render_view) { |
| 167 new extensions::ExtensionHelper(render_view, extension_dispatcher_.get()); |
| 168 } |
| 169 |
| 170 bool ChromeExtensionsRendererClient::OverrideCreatePlugin( |
| 171 content::RenderFrame* render_frame, |
| 172 const blink::WebPluginParams& params) { |
| 173 if (params.mimeType.utf8() != content::kBrowserPluginMimeType) |
| 174 return true; |
| 175 |
| 176 bool guest_view_api_available = false; |
| 177 extension_dispatcher_->script_context_set().ForEach( |
| 178 render_frame, base::Bind(&IsGuestViewApiAvailableToScriptContext, |
| 179 &guest_view_api_available)); |
| 180 return !guest_view_api_available; |
| 181 } |
| 182 |
| 183 bool ChromeExtensionsRendererClient::AllowPopup() { |
| 184 extensions::ScriptContext* current_context = |
| 185 extension_dispatcher_->script_context_set().GetCurrent(); |
| 186 if (!current_context || !current_context->extension()) |
| 187 return false; |
| 188 |
| 189 // See http://crbug.com/117446 for the subtlety of this check. |
| 190 switch (current_context->context_type()) { |
| 191 case extensions::Feature::UNSPECIFIED_CONTEXT: |
| 192 case extensions::Feature::WEB_PAGE_CONTEXT: |
| 193 case extensions::Feature::UNBLESSED_EXTENSION_CONTEXT: |
| 194 case extensions::Feature::WEBUI_CONTEXT: |
| 195 case extensions::Feature::SERVICE_WORKER_CONTEXT: |
| 196 return false; |
| 197 case extensions::Feature::BLESSED_EXTENSION_CONTEXT: |
| 198 case extensions::Feature::CONTENT_SCRIPT_CONTEXT: |
| 199 return true; |
| 200 case extensions::Feature::BLESSED_WEB_PAGE_CONTEXT: |
| 201 return !current_context->web_frame()->parent(); |
| 202 default: |
| 203 NOTREACHED(); |
| 204 return false; |
| 205 } |
| 206 } |
| 207 |
| 208 bool ChromeExtensionsRendererClient::WillSendRequest( |
| 209 blink::WebFrame* frame, |
| 210 ui::PageTransition transition_type, |
| 211 const GURL& url, |
| 212 GURL* new_url) { |
| 213 if (url.SchemeIs(extensions::kExtensionScheme) && |
| 214 !resource_request_policy_->CanRequestResource(url, frame, |
| 215 transition_type)) { |
| 216 *new_url = GURL(chrome::kExtensionInvalidRequestURL); |
| 217 return true; |
| 218 } |
| 219 |
| 220 if (url.SchemeIs(extensions::kExtensionResourceScheme) && |
| 221 !resource_request_policy_->CanRequestExtensionResourceScheme(url, |
| 222 frame)) { |
| 223 *new_url = GURL(chrome::kExtensionResourceInvalidRequestURL); |
| 224 return true; |
| 225 } |
| 226 |
| 227 return false; |
| 228 } |
| 229 |
| 230 void ChromeExtensionsRendererClient::SetExtensionDispatcherForTest( |
| 231 scoped_ptr<extensions::Dispatcher> extension_dispatcher) { |
| 232 extension_dispatcher_ = extension_dispatcher.Pass(); |
| 233 permissions_policy_delegate_.reset( |
| 234 new extensions::RendererPermissionsPolicyDelegate( |
| 235 extension_dispatcher_.get())); |
| 236 content::RenderThread::Get()->RegisterExtension( |
| 237 extensions::SafeBuiltins::CreateV8Extension()); |
| 238 } |
| 239 |
| 240 extensions::Dispatcher* |
| 241 ChromeExtensionsRendererClient::GetExtensionDispatcherForTest() { |
| 242 return extension_dispatcher(); |
| 243 } |
| 244 |
| 245 // static |
| 246 bool ChromeExtensionsRendererClient::ShouldFork(blink::WebLocalFrame* frame, |
| 247 const GURL& url, |
| 248 bool is_initial_navigation, |
| 249 bool is_server_redirect, |
| 250 bool* send_referrer) { |
| 251 const extensions::RendererExtensionRegistry* extension_registry = |
| 252 extensions::RendererExtensionRegistry::Get(); |
| 253 |
| 254 // Determine if the new URL is an extension (excluding bookmark apps). |
| 255 const Extension* new_url_extension = extensions::GetNonBookmarkAppExtension( |
| 256 *extension_registry->GetMainThreadExtensionSet(), url); |
| 257 bool is_extension_url = !!new_url_extension; |
| 258 |
| 259 // If the navigation would cross an app extent boundary, we also need |
| 260 // to defer to the browser to ensure process isolation. This is not necessary |
| 261 // for server redirects, which will be transferred to a new process by the |
| 262 // browser process when they are ready to commit. It is necessary for client |
| 263 // redirects, which won't be transferred in the same way. |
| 264 if (!is_server_redirect && |
| 265 CrossesExtensionExtents(frame, url, is_extension_url, |
| 266 is_initial_navigation)) { |
| 267 // Include the referrer in this case since we're going from a hosted web |
| 268 // page. (the packaged case is handled previously by the extension |
| 269 // navigation test) |
| 270 *send_referrer = true; |
| 271 |
| 272 const Extension* extension = |
| 273 extension_registry->GetExtensionOrAppByURL(url); |
| 274 if (extension && extension->is_app()) { |
| 275 extensions::RecordAppLaunchType( |
| 276 extension_misc::APP_LAUNCH_CONTENT_NAVIGATION, extension->GetType()); |
| 277 } |
| 278 return true; |
| 279 } |
| 280 |
| 281 // If this is a reload, check whether it has the wrong process type. We |
| 282 // should send it to the browser if it's an extension URL (e.g., hosted app) |
| 283 // in a normal process, or if it's a process for an extension that has been |
| 284 // uninstalled. Without --site-per-process mode, we never fork processes for |
| 285 // subframes, so this check only makes sense for top-level frames. |
| 286 // TODO(alexmos,nasko): Figure out how this check should work when reloading |
| 287 // subframes in --site-per-process mode. |
| 288 if (!frame->parent() && frame->document().url() == url) { |
| 289 if (is_extension_url != IsStandaloneExtensionProcess()) |
| 290 return true; |
| 291 } |
| 292 return false; |
| 293 } |
| 294 |
| 295 // static |
| 296 content::BrowserPluginDelegate* |
| 297 ChromeExtensionsRendererClient::CreateBrowserPluginDelegate( |
| 298 content::RenderFrame* render_frame, |
| 299 const std::string& mime_type, |
| 300 const GURL& original_url) { |
| 301 if (mime_type == content::kBrowserPluginMimeType) |
| 302 return new extensions::ExtensionsGuestViewContainer(render_frame); |
| 303 return new extensions::MimeHandlerViewContainer(render_frame, mime_type, |
| 304 original_url); |
| 305 } |
| OLD | NEW |