diff options
author | jam@chromium.org <jam@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-02-19 07:11:52 +0000 |
---|---|---|
committer | jam@chromium.org <jam@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-02-19 07:11:52 +0000 |
commit | 1d8a3d1fe0e07d5aaf1f0a5527097b9e313d23b6 (patch) | |
tree | b298f570c373f95b55cdfb8334ab7b510b5942c4 /content | |
parent | 3e45d8f9c4c6eabec2458cc5e0b481e73f2705ab (diff) | |
download | chromium_src-1d8a3d1fe0e07d5aaf1f0a5527097b9e313d23b6.zip chromium_src-1d8a3d1fe0e07d5aaf1f0a5527097b9e313d23b6.tar.gz chromium_src-1d8a3d1fe0e07d5aaf1f0a5527097b9e313d23b6.tar.bz2 |
Move core pieces of browser\renderer_host to src\content.
TBR=avi
Review URL: http://codereview.chromium.org/6532073
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@75489 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'content')
94 files changed, 24377 insertions, 0 deletions
diff --git a/content/browser/renderer_host/accelerated_surface_container_mac.cc b/content/browser/renderer_host/accelerated_surface_container_mac.cc new file mode 100644 index 0000000..a661c92 --- /dev/null +++ b/content/browser/renderer_host/accelerated_surface_container_mac.cc @@ -0,0 +1,233 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "content/browser/renderer_host/accelerated_surface_container_mac.h" + +#include "app/surface/io_surface_support_mac.h" +#include "base/logging.h" +#include "content/browser/renderer_host/accelerated_surface_container_manager_mac.h" +#include "webkit/plugins/npapi/webplugin.h" + +AcceleratedSurfaceContainerMac::AcceleratedSurfaceContainerMac( + AcceleratedSurfaceContainerManagerMac* manager, + bool opaque) + : manager_(manager), + opaque_(opaque), + surface_id_(0), + width_(0), + height_(0), + texture_(0), + texture_needs_upload_(true), + texture_pending_deletion_(0), + visible_(false), + was_painted_to_(false) { +} + +AcceleratedSurfaceContainerMac::~AcceleratedSurfaceContainerMac() { +} + +void AcceleratedSurfaceContainerMac::SetSizeAndIOSurface( + int32 width, + int32 height, + uint64 io_surface_identifier) { + // Ignore |io_surface_identifier|: The surface hasn't been painted to and + // only contains garbage data. Update the surface in |set_was_painted_to()| + // instead. + width_ = width; + height_ = height; +} + +void AcceleratedSurfaceContainerMac::SetSizeAndTransportDIB( + int32 width, + int32 height, + TransportDIB::Handle transport_dib) { + if (TransportDIB::is_valid(transport_dib)) { + transport_dib_.reset(TransportDIB::Map(transport_dib)); + EnqueueTextureForDeletion(); + width_ = width; + height_ = height; + } +} + +void AcceleratedSurfaceContainerMac::SetGeometry( + const webkit::npapi::WebPluginGeometry& geom) { + visible_ = geom.visible; + if (geom.rects_valid) + clip_rect_ = geom.clip_rect; +} + +void AcceleratedSurfaceContainerMac::Draw(CGLContextObj context) { + IOSurfaceSupport* io_surface_support = IOSurfaceSupport::Initialize(); + GLenum target = GL_TEXTURE_RECTANGLE_ARB; + if (texture_pending_deletion_) { + // Clean up an old texture object. This is essentially a pre-emptive + // cleanup, as the resources will be released when the OpenGL context + // associated with our containing NSView is destroyed. However, if we + // resize a plugin often, we might generate a lot of textures, so we + // should try to eagerly reclaim their resources. Note also that the + // OpenGL context must be current when performing the deletion, and it + // seems risky to make the OpenGL context current at an arbitrary point + // in time, which is why the deletion does not occur in the container's + // destructor. + glDeleteTextures(1, &texture_pending_deletion_); + texture_pending_deletion_ = 0; + } + if (!texture_) { + if ((io_surface_support && !surface_.get()) || + (!io_surface_support && !transport_dib_.get())) + return; + glGenTextures(1, &texture_); + glBindTexture(target, texture_); + glTexParameterf(target, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameterf(target, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + if (io_surface_support) { + texture_needs_upload_ = true; + } else { + // Reserve space on the card for the actual texture upload, done with the + // glTexSubImage2D() call, below. + glTexImage2D(target, + 0, // mipmap level 0 + GL_RGBA, // internal format + width_, + height_, + 0, // no border + GL_BGRA, // The GPU plugin read BGRA pixels + GL_UNSIGNED_INT_8_8_8_8_REV, + NULL); // No data, this call just reserves room. + } + } + + // When using an IOSurface, the texture does not need to be repeatedly + // uploaded, just when we've been told we have to. + if (io_surface_support && texture_needs_upload_) { + DCHECK(surface_.get()); + glBindTexture(target, texture_); + // Don't think we need to identify a plane. + GLuint plane = 0; + io_surface_support->CGLTexImageIOSurface2D(context, + target, + GL_RGBA, + surface_width_, + surface_height_, + GL_BGRA, + GL_UNSIGNED_INT_8_8_8_8_REV, + surface_.get(), + plane); + texture_needs_upload_ = false; + } + // If using TransportDIBs, the texture needs to be uploaded every frame. + if (transport_dib_.get() != NULL) { + void* pixel_memory = transport_dib_->memory(); + if (pixel_memory) { + glBindTexture(target, texture_); + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); // Needed for NPOT textures. + glTexSubImage2D(target, + 0, // mipmap level 0 + 0, // x-offset + 0, // y-offset + width_, + height_, + GL_BGRA, // The GPU plugin gave us BGRA pixels + GL_UNSIGNED_INT_8_8_8_8_REV, + pixel_memory); + } + } + + if (texture_) { + int texture_width = io_surface_support ? surface_width_ : width_; + int texture_height = io_surface_support ? surface_height_ : height_; + + // TODO(kbr): convert this to use only OpenGL ES 2.0 functionality. + + // TODO(kbr): may need to pay attention to cutout rects. + int clipX = clip_rect_.x(); + int clipY = clip_rect_.y(); + int clipWidth = clip_rect_.width(); + int clipHeight = clip_rect_.height(); + + if (clipX + clipWidth > texture_width) + clipWidth = texture_width - clipX; + if (clipY + clipHeight > texture_height) + clipHeight = texture_height - clipY; + + if (opaque_) { + // Pepper 3D's output is currently considered opaque even if the + // program draws pixels with alpha less than 1. In order to have + // this effect, we need to drop the alpha channel of the input, + // replacing it with alpha = 1. + + // First fill the rectangle with alpha=1. + glColorMask(false, false, false, true); + glColor4f(0.0f, 0.0f, 0.0f, 1.0f); + glBegin(GL_TRIANGLE_STRIP); + glVertex3f(0, 0, 0); + glVertex3f(clipWidth, 0, 0); + glVertex3f(0, clipHeight, 0); + glVertex3f(clipWidth, clipHeight, 0); + glEnd(); + + // Now draw the color channels from the incoming texture. + glColorMask(true, true, true, false); + // This call shouldn't be necessary -- we are using the GL_REPLACE + // texture environment mode -- but it appears to be. + glColor4f(1.0f, 1.0f, 1.0f, 1.0f); + } else { + glColorMask(true, true, true, true); + } + + // Draw the color channels from the incoming texture. + glBindTexture(target, texture_); + glEnable(target); + glBegin(GL_TRIANGLE_STRIP); + + glTexCoord2f(clipX, texture_height - clipY); + glVertex3f(0, 0, 0); + + glTexCoord2f(clipX + clipWidth, texture_height - clipY); + glVertex3f(clipWidth, 0, 0); + + glTexCoord2f(clipX, texture_height - clipY - clipHeight); + glVertex3f(0, clipHeight, 0); + + glTexCoord2f(clipX + clipWidth, texture_height - clipY - clipHeight); + glVertex3f(clipWidth, clipHeight, 0); + + glEnd(); + glDisable(target); + } +} + +bool AcceleratedSurfaceContainerMac::ShouldBeVisible() const { + return visible_ && was_painted_to_ && !clip_rect_.IsEmpty(); +} + +void AcceleratedSurfaceContainerMac::set_was_painted_to(uint64 surface_id) { + if (surface_id && (!surface_ || surface_id != surface_id_)) { + // Keep the surface that was most recently painted to around. + if (IOSurfaceSupport* io_surface_support = IOSurfaceSupport::Initialize()) { + CFTypeRef surface = io_surface_support->IOSurfaceLookup( + static_cast<uint32>(surface_id)); + // Can fail if IOSurface with that ID was already released by the + // gpu process or the plugin process. We will get a |set_was_painted_to()| + // message with a new surface soon in that case. + if (surface) { + surface_.reset(surface); + surface_id_ = surface_id; + surface_width_ = io_surface_support->IOSurfaceGetWidth(surface_); + surface_height_ = io_surface_support->IOSurfaceGetHeight(surface_); + EnqueueTextureForDeletion(); + } + } + } + was_painted_to_ = true; +} + +void AcceleratedSurfaceContainerMac::EnqueueTextureForDeletion() { + if (texture_) { + DCHECK(texture_pending_deletion_ == 0); + texture_pending_deletion_ = texture_; + texture_ = 0; + } +} + diff --git a/content/browser/renderer_host/accelerated_surface_container_mac.h b/content/browser/renderer_host/accelerated_surface_container_mac.h new file mode 100644 index 0000000..2bffb32 --- /dev/null +++ b/content/browser/renderer_host/accelerated_surface_container_mac.h @@ -0,0 +1,152 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CONTENT_BROWSER_RENDERER_HOST_GPU_PLUGIN_CONTAINER_MAC_H_ +#define CONTENT_BROWSER_RENDERER_HOST_GPU_PLUGIN_CONTAINER_MAC_H_ +#pragma once + +// The "GPU plugin" is currently implemented as a special kind of +// NPAPI plugin to provide high-performance on-screen 3D rendering for +// Pepper 3D. +// +// On Windows and X11 platforms the GPU plugin relies on cross-process +// parenting of windows, which is not supported via any public APIs in +// the Mac OS X window system. +// +// To achieve full hardware acceleration we use the new IOSurface APIs +// introduced in Mac OS X 10.6. The GPU plugin's process produces an +// IOSurface and renders into it using OpenGL. It uses the +// IOSurfaceGetID and IOSurfaceLookup APIs to pass a reference to this +// surface to the browser process for on-screen rendering. The GPU +// plugin essentially looks like a windowless plugin; the browser +// process gets all of the mouse events, because the plugin process +// does not have an on-screen window. +// +// This class encapsulates some of the management of these data +// structures, in conjunction with the AcceleratedSurfaceContainerManagerMac. + +#include <CoreFoundation/CoreFoundation.h> +#include <OpenGL/OpenGL.h> + +#include "app/surface/transport_dib.h" +#include "base/basictypes.h" +#include "base/mac/scoped_cftyperef.h" +#include "base/scoped_ptr.h" +#include "ui/gfx/native_widget_types.h" +#include "ui/gfx/rect.h" + +namespace webkit { +namespace npapi { +struct WebPluginGeometry; +} +} + +class AcceleratedSurfaceContainerManagerMac; + +class AcceleratedSurfaceContainerMac { + public: + AcceleratedSurfaceContainerMac( + AcceleratedSurfaceContainerManagerMac* manager, + bool opaque); + virtual ~AcceleratedSurfaceContainerMac(); + + // Sets the backing store and size of this accelerated surface container. + // There are two versions: the IOSurface version is used on systems where the + // IOSurface API is supported (Mac OS X 10.6 and later); the TransportDIB is + // used on Mac OS X 10.5 and earlier. + void SetSizeAndIOSurface(int32 width, + int32 height, + uint64 io_surface_identifier); + void SetSizeAndTransportDIB(int32 width, + int32 height, + TransportDIB::Handle transport_dib); + + // Tells the accelerated surface container that its geometry has changed, + // for example because of a scroll event. (Note that the container + // currently only pays attention to the clip width and height, since the + // view in which it is hosted is responsible for positioning it on the + // page.) + void SetGeometry(const webkit::npapi::WebPluginGeometry& geom); + + // Draws this accelerated surface's contents, texture mapped onto a quad in + // the given OpenGL context. TODO(kbr): figure out and define exactly how the + // coordinate system will work out. + void Draw(CGLContextObj context); + + // Causes the next Draw call to trigger a texture upload. Should be called any + // time the drawing context has changed. + void ForceTextureReload() { texture_needs_upload_ = true; } + + // Returns if the surface should be shown. + bool ShouldBeVisible() const; + + // Notifies the the container that its surface was painted to. + void set_was_painted_to(uint64 surface_id); + + // Notifies the container that its surface is invalid. + void set_surface_invalid() { was_painted_to_ = false; } + private: + // The manager of this accelerated surface container. + AcceleratedSurfaceContainerManagerMac* manager_; + + // Whether this accelerated surface's content is supposed to be opaque. + bool opaque_; + + // The IOSurfaceRef, if any, that has been handed from the GPU + // plugin process back to the browser process for drawing. + // This is held as a CFTypeRef because we can't refer to the + // IOSurfaceRef type when building on 10.5. + base::mac::ScopedCFTypeRef<CFTypeRef> surface_; + + // The id of |surface_|, or 0 if |surface_| is NULL. + uint64 surface_id_; + + // The width and height of the io surface. During resizing, this is different + // from |width_| and |height_|. + int32 surface_width_; + int32 surface_height_; + + // The TransportDIB which is used in pre-10.6 systems where the IOSurface + // API is not supported. This is a weak reference to the actual TransportDIB + // whic is owned by the GPU process. + scoped_ptr<TransportDIB> transport_dib_; + + // The width and height of the container. + int32 width_; + int32 height_; + + // The clip rectangle, relative to the (x_, y_) origin. + gfx::Rect clip_rect_; + + // The "live" OpenGL texture referring to this IOSurfaceRef. Note + // that per the CGLTexImageIOSurface2D API we do not need to + // explicitly update this texture's contents once created. All we + // need to do is ensure it is re-bound before attempting to draw + // with it. + GLuint texture_; + + // True if we need to upload the texture again during the next draw. + bool texture_needs_upload_; + + // This may refer to an old version of the texture if the container is + // resized, for example. + GLuint texture_pending_deletion_; + + // Stores if the plugin has a visible state. + bool visible_; + + // Stores if the plugin's IOSurface has been swapped before. Used to not show + // it before it hasn't been painted to at least once. + bool was_painted_to_; + + // Releases the IOSurface reference, if any, retained by this object. + void ReleaseIOSurface(); + + // Enqueue our texture for later deletion. + void EnqueueTextureForDeletion(); + + DISALLOW_COPY_AND_ASSIGN(AcceleratedSurfaceContainerMac); +}; + +#endif // CONTENT_BROWSER_RENDERER_HOST_GPU_PLUGIN_CONTAINER_MAC_H_ diff --git a/content/browser/renderer_host/accelerated_surface_container_manager_mac.cc b/content/browser/renderer_host/accelerated_surface_container_manager_mac.cc new file mode 100644 index 0000000..9c14b14 --- /dev/null +++ b/content/browser/renderer_host/accelerated_surface_container_manager_mac.cc @@ -0,0 +1,173 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "content/browser/renderer_host/accelerated_surface_container_manager_mac.h" + +#include "base/logging.h" +#include "content/browser/renderer_host/accelerated_surface_container_mac.h" +#include "webkit/plugins/npapi/webplugin.h" + +AcceleratedSurfaceContainerManagerMac::AcceleratedSurfaceContainerManagerMac() + : current_id_(0), + root_container_(NULL), + root_container_handle_(gfx::kNullPluginWindow), + gpu_rendering_active_(false) { +} + +gfx::PluginWindowHandle +AcceleratedSurfaceContainerManagerMac::AllocateFakePluginWindowHandle( + bool opaque, bool root) { + base::AutoLock lock(lock_); + + AcceleratedSurfaceContainerMac* container = + new AcceleratedSurfaceContainerMac(this, opaque); + gfx::PluginWindowHandle res = + static_cast<gfx::PluginWindowHandle>(++current_id_); + plugin_window_to_container_map_.insert(std::make_pair(res, container)); + if (root) { + root_container_ = container; + root_container_handle_ = res; + } + return res; +} + +void AcceleratedSurfaceContainerManagerMac::DestroyFakePluginWindowHandle( + gfx::PluginWindowHandle id) { + base::AutoLock lock(lock_); + + AcceleratedSurfaceContainerMac* container = MapIDToContainer(id); + if (container) { + if (container == root_container_) { + root_container_ = NULL; + root_container_handle_ = gfx::kNullPluginWindow; + } + delete container; + } + plugin_window_to_container_map_.erase(id); +} + +bool AcceleratedSurfaceContainerManagerMac::IsRootContainer( + gfx::PluginWindowHandle id) const { + return root_container_handle_ != gfx::kNullPluginWindow && + root_container_handle_ == id; +} + +void AcceleratedSurfaceContainerManagerMac:: + set_gpu_rendering_active(bool active) { + if (gpu_rendering_active_ && !active) + SetRootSurfaceInvalid(); + gpu_rendering_active_ = active; +} + +void AcceleratedSurfaceContainerManagerMac::SetSizeAndIOSurface( + gfx::PluginWindowHandle id, + int32 width, + int32 height, + uint64 io_surface_identifier) { + base::AutoLock lock(lock_); + + AcceleratedSurfaceContainerMac* container = MapIDToContainer(id); + if (container) { + container->SetSizeAndIOSurface(width, height, io_surface_identifier); + } +} + +void AcceleratedSurfaceContainerManagerMac::SetSizeAndTransportDIB( + gfx::PluginWindowHandle id, + int32 width, + int32 height, + TransportDIB::Handle transport_dib) { + base::AutoLock lock(lock_); + + AcceleratedSurfaceContainerMac* container = MapIDToContainer(id); + if (container) + container->SetSizeAndTransportDIB(width, height, transport_dib); +} + +void AcceleratedSurfaceContainerManagerMac::SetPluginContainerGeometry( + const webkit::npapi::WebPluginGeometry& move) { + base::AutoLock lock(lock_); + + AcceleratedSurfaceContainerMac* container = MapIDToContainer(move.window); + if (container) + container->SetGeometry(move); +} + +void AcceleratedSurfaceContainerManagerMac::Draw(CGLContextObj context, + gfx::PluginWindowHandle id) { + base::AutoLock lock(lock_); + + glColorMask(true, true, true, true); + // Should match the clear color of RenderWidgetHostViewMac. + glClearColor(1.0f, 1.0f, 1.0f, 1.0f); + // TODO(thakis): Clearing the whole color buffer is wasteful, since most of + // it is overwritten by the surface. + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + glDisable(GL_DEPTH_TEST); + glDisable(GL_BLEND); + + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + + AcceleratedSurfaceContainerMac* container = MapIDToContainer(id); + CHECK(container); + container->Draw(context); + + // Unbind any texture from the texture target to ensure that the + // next time through we will have to re-bind the texture and thereby + // pick up modifications from the other process. + GLenum target = GL_TEXTURE_RECTANGLE_ARB; + glBindTexture(target, 0); + + glFlush(); +} + +void AcceleratedSurfaceContainerManagerMac::ForceTextureReload() { + base::AutoLock lock(lock_); + + for (PluginWindowToContainerMap::const_iterator i = + plugin_window_to_container_map_.begin(); + i != plugin_window_to_container_map_.end(); ++i) { + AcceleratedSurfaceContainerMac* container = i->second; + container->ForceTextureReload(); + } +} + +void AcceleratedSurfaceContainerManagerMac::SetSurfaceWasPaintedTo( + gfx::PluginWindowHandle id, uint64 surface_id) { + base::AutoLock lock(lock_); + + AcceleratedSurfaceContainerMac* container = MapIDToContainer(id); + if (container) + container->set_was_painted_to(surface_id); +} + +void AcceleratedSurfaceContainerManagerMac::SetRootSurfaceInvalid() { + base::AutoLock lock(lock_); + if (root_container_) + root_container_->set_surface_invalid(); +} + +bool AcceleratedSurfaceContainerManagerMac::SurfaceShouldBeVisible( + gfx::PluginWindowHandle id) const { + base::AutoLock lock(lock_); + + if (IsRootContainer(id) && !gpu_rendering_active_) + return false; + + AcceleratedSurfaceContainerMac* container = MapIDToContainer(id); + return container && container->ShouldBeVisible(); +} + +AcceleratedSurfaceContainerMac* + AcceleratedSurfaceContainerManagerMac::MapIDToContainer( + gfx::PluginWindowHandle id) const { + PluginWindowToContainerMap::const_iterator i = + plugin_window_to_container_map_.find(id); + if (i != plugin_window_to_container_map_.end()) + return i->second; + + LOG(ERROR) << "Request for plugin container for unknown window id " << id; + + return NULL; +} diff --git a/content/browser/renderer_host/accelerated_surface_container_manager_mac.h b/content/browser/renderer_host/accelerated_surface_container_manager_mac.h new file mode 100644 index 0000000..316ff58 --- /dev/null +++ b/content/browser/renderer_host/accelerated_surface_container_manager_mac.h @@ -0,0 +1,124 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CONTENT_BROWSER_RENDERER_HOST_GPU_PLUGIN_CONTAINER_MANAGER_MAC_H_ +#define CONTENT_BROWSER_RENDERER_HOST_GPU_PLUGIN_CONTAINER_MANAGER_MAC_H_ +#pragma once + +#include <OpenGL/OpenGL.h> +#include <map> + +#include "app/surface/transport_dib.h" +#include "base/basictypes.h" +#include "base/synchronization/lock.h" +#include "ui/gfx/native_widget_types.h" + +namespace webkit { +namespace npapi { +struct WebPluginGeometry; +} +} + +class AcceleratedSurfaceContainerMac; + +// Helper class that manages the backing store and on-screen rendering +// of instances of the GPU plugin on the Mac. +class AcceleratedSurfaceContainerManagerMac { + public: + AcceleratedSurfaceContainerManagerMac(); + + // Allocates a new "fake" PluginWindowHandle, which is used as the + // key for the other operations. + gfx::PluginWindowHandle AllocateFakePluginWindowHandle(bool opaque, + bool root); + + // Destroys a fake PluginWindowHandle and associated storage. + void DestroyFakePluginWindowHandle(gfx::PluginWindowHandle id); + + // Indicates whether the given PluginWindowHandle is "root", which + // means that we are using accelerated compositing and that this one + // contains the compositor's output. + bool IsRootContainer(gfx::PluginWindowHandle id) const; + + // Returns the handle of the compositor surface, or kNullPluginWindow if no + // compositor surface is active. + gfx::PluginWindowHandle root_container_handle() const { + return root_container_handle_; + } + + // Informs the manager if gpu rendering is active. + void set_gpu_rendering_active(bool active); + + // Sets the size and backing store of the plugin instance. There are two + // versions: the IOSurface version is used on systems where the IOSurface + // API is supported (Mac OS X 10.6 and later); the TransportDIB is used on + // Mac OS X 10.5 and earlier. + void SetSizeAndIOSurface(gfx::PluginWindowHandle id, + int32 width, + int32 height, + uint64 io_surface_identifier); + void SetSizeAndTransportDIB(gfx::PluginWindowHandle id, + int32 width, + int32 height, + TransportDIB::Handle transport_dib); + + // Takes an update from WebKit about a plugin's position and size and moves + // the plugin accordingly. + void SetPluginContainerGeometry( + const webkit::npapi::WebPluginGeometry& move); + + // Draws the plugin container associated with the given id into the given + // OpenGL context, which must already be current. + void Draw(CGLContextObj context, gfx::PluginWindowHandle id); + + // Causes the next Draw call on each container to trigger a texture upload. + // Should be called any time the drawing context has changed. + void ForceTextureReload(); + + // Notifies a surface that it has been painted to. + void SetSurfaceWasPaintedTo(gfx::PluginWindowHandle id, uint64 surface_id); + + // Notifies the root container that its surface is invalid. + void SetRootSurfaceInvalid(); + + // Returns if a given surface should be shown. + bool SurfaceShouldBeVisible(gfx::PluginWindowHandle id) const; + private: + uint32 current_id_; + + // Maps a "fake" plugin window handle to the corresponding container. + AcceleratedSurfaceContainerMac* + MapIDToContainer(gfx::PluginWindowHandle id) const; + + // A map that associates plugin window handles with their containers. + typedef std::map<gfx::PluginWindowHandle, AcceleratedSurfaceContainerMac*> + PluginWindowToContainerMap; + PluginWindowToContainerMap plugin_window_to_container_map_; + + // The "root" container, which is only used to draw the output of + // the accelerated compositor if it is active. Currently, + // accelerated plugins (Core Animation and Pepper 3D) are drawn on + // top of the page's contents rather than transformed and composited + // with the rest of the page. At some point we would like them to be + // treated uniformly with other page elements; when this is done, + // the separate treatment of the root container can go away because + // there will only be one container active when the accelerated + // compositor is active. + AcceleratedSurfaceContainerMac* root_container_; + gfx::PluginWindowHandle root_container_handle_; + + // True if gpu rendering is active. The root container is created on demand + // and destroyed only when a renderer process exits. When the compositor was + // created, this is set to |false| while the compositor is not needed. + bool gpu_rendering_active_; + + // Both |plugin_window_to_container_map_| and the + // AcceleratedSurfaceContainerMac in it are not threadsafe, but accessed from + // multiple threads. All these accesses are guarded by this lock. + mutable base::Lock lock_; + + DISALLOW_COPY_AND_ASSIGN(AcceleratedSurfaceContainerManagerMac); +}; + +#endif // CONTENT_BROWSER_RENDERER_HOST_GPU_PLUGIN_CONTAINER_MANAGER_MAC_H_ diff --git a/content/browser/renderer_host/async_resource_handler.cc b/content/browser/renderer_host/async_resource_handler.cc new file mode 100644 index 0000000..937f120 --- /dev/null +++ b/content/browser/renderer_host/async_resource_handler.cc @@ -0,0 +1,265 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "content/browser/renderer_host/async_resource_handler.h" + +#include <algorithm> +#include <vector> + +#include "base/hash_tables.h" +#include "base/logging.h" +#include "base/shared_memory.h" +#include "chrome/browser/debugger/devtools_netlog_observer.h" +#include "chrome/browser/net/chrome_url_request_context.h" +#include "chrome/browser/net/load_timing_observer.h" +#include "chrome/common/render_messages.h" +#include "chrome/common/resource_response.h" +#include "content/browser/renderer_host/global_request_id.h" +#include "content/browser/renderer_host/resource_dispatcher_host.h" +#include "content/browser/renderer_host/resource_dispatcher_host_request_info.h" +#include "content/browser/renderer_host/resource_message_filter.h" +#include "net/base/io_buffer.h" +#include "net/base/load_flags.h" +#include "net/base/net_log.h" +#include "webkit/glue/resource_loader_bridge.h" + +using base::Time; +using base::TimeTicks; + +namespace { + +// When reading, we don't know if we are going to get EOF (0 bytes read), so +// we typically have a buffer that we allocated but did not use. We keep +// this buffer around for the next read as a small optimization. +SharedIOBuffer* g_spare_read_buffer = NULL; + +// The initial size of the shared memory buffer. (32 kilobytes). +const int kInitialReadBufSize = 32768; + +// The maximum size of the shared memory buffer. (512 kilobytes). +const int kMaxReadBufSize = 524288; + +} // namespace + +// Our version of IOBuffer that uses shared memory. +class SharedIOBuffer : public net::IOBuffer { + public: + explicit SharedIOBuffer(int buffer_size) + : net::IOBuffer(), + ok_(false), + buffer_size_(buffer_size) {} + + bool Init() { + if (shared_memory_.CreateAndMapAnonymous(buffer_size_)) { + data_ = reinterpret_cast<char*>(shared_memory_.memory()); + DCHECK(data_); + ok_ = true; + } + return ok_; + } + + base::SharedMemory* shared_memory() { return &shared_memory_; } + bool ok() { return ok_; } + int buffer_size() { return buffer_size_; } + + private: + ~SharedIOBuffer() { + DCHECK(g_spare_read_buffer != this); + data_ = NULL; + } + + base::SharedMemory shared_memory_; + bool ok_; + int buffer_size_; +}; + +AsyncResourceHandler::AsyncResourceHandler( + ResourceMessageFilter* filter, + int routing_id, + const GURL& url, + ResourceDispatcherHost* resource_dispatcher_host) + : filter_(filter), + routing_id_(routing_id), + rdh_(resource_dispatcher_host), + next_buffer_size_(kInitialReadBufSize) { +} + +AsyncResourceHandler::~AsyncResourceHandler() { +} + +bool AsyncResourceHandler::OnUploadProgress(int request_id, + uint64 position, + uint64 size) { + return filter_->Send(new ViewMsg_Resource_UploadProgress(routing_id_, + request_id, + position, size)); +} + +bool AsyncResourceHandler::OnRequestRedirected(int request_id, + const GURL& new_url, + ResourceResponse* response, + bool* defer) { + *defer = true; + net::URLRequest* request = rdh_->GetURLRequest( + GlobalRequestID(filter_->child_id(), request_id)); + LoadTimingObserver::PopulateTimingInfo(request, response); + DevToolsNetLogObserver::PopulateResponseInfo(request, response); + return filter_->Send(new ViewMsg_Resource_ReceivedRedirect( + routing_id_, request_id, new_url, response->response_head)); +} + +bool AsyncResourceHandler::OnResponseStarted(int request_id, + ResourceResponse* response) { + // For changes to the main frame, inform the renderer of the new URL's + // per-host settings before the request actually commits. This way the + // renderer will be able to set these precisely at the time the + // request commits, avoiding the possibility of e.g. zooming the old content + // or of having to layout the new content twice. + net::URLRequest* request = rdh_->GetURLRequest( + GlobalRequestID(filter_->child_id(), request_id)); + + LoadTimingObserver::PopulateTimingInfo(request, response); + DevToolsNetLogObserver::PopulateResponseInfo(request, response); + + ResourceDispatcherHostRequestInfo* info = rdh_->InfoForRequest(request); + if (info->resource_type() == ResourceType::MAIN_FRAME) { + GURL request_url(request->url()); + ChromeURLRequestContext* context = + static_cast<ChromeURLRequestContext*>(request->context()); + if (context) { + filter_->Send(new ViewMsg_SetContentSettingsForLoadingURL( + info->route_id(), request_url, + context->host_content_settings_map()->GetContentSettings( + request_url))); + filter_->Send(new ViewMsg_SetZoomLevelForLoadingURL(info->route_id(), + request_url, context->host_zoom_map()->GetZoomLevel(request_url))); + } + } + + filter_->Send(new ViewMsg_Resource_ReceivedResponse( + routing_id_, request_id, response->response_head)); + + if (request->response_info().metadata) { + std::vector<char> copy(request->response_info().metadata->data(), + request->response_info().metadata->data() + + request->response_info().metadata->size()); + filter_->Send(new ViewMsg_Resource_ReceivedCachedMetadata( + routing_id_, request_id, copy)); + } + + return true; +} + +bool AsyncResourceHandler::OnWillStart(int request_id, + const GURL& url, + bool* defer) { + return true; +} + +bool AsyncResourceHandler::OnWillRead(int request_id, net::IOBuffer** buf, + int* buf_size, int min_size) { + DCHECK_EQ(-1, min_size); + + if (g_spare_read_buffer) { + DCHECK(!read_buffer_); + read_buffer_.swap(&g_spare_read_buffer); + DCHECK(read_buffer_->data()); + + *buf = read_buffer_.get(); + *buf_size = read_buffer_->buffer_size(); + } else { + read_buffer_ = new SharedIOBuffer(next_buffer_size_); + if (!read_buffer_->Init()) { + DLOG(ERROR) << "Couldn't allocate shared io buffer"; + read_buffer_ = NULL; + return false; + } + DCHECK(read_buffer_->data()); + *buf = read_buffer_.get(); + *buf_size = next_buffer_size_; + } + + return true; +} + +bool AsyncResourceHandler::OnReadCompleted(int request_id, int* bytes_read) { + if (!*bytes_read) + return true; + DCHECK(read_buffer_.get()); + + if (read_buffer_->buffer_size() == *bytes_read) { + // The network layer has saturated our buffer. Next time, we should give it + // a bigger buffer for it to fill, to minimize the number of round trips we + // do with the renderer process. + next_buffer_size_ = std::min(next_buffer_size_ * 2, kMaxReadBufSize); + } + + if (!rdh_->WillSendData(filter_->child_id(), request_id)) { + // We should not send this data now, we have too many pending requests. + return true; + } + + base::SharedMemoryHandle handle; + if (!read_buffer_->shared_memory()->GiveToProcess( + filter_->peer_handle(), &handle)) { + // We wrongfully incremented the pending data count. Fake an ACK message + // to fix this. We can't move this call above the WillSendData because + // it's killing our read_buffer_, and we don't want that when we pause + // the request. + rdh_->DataReceivedACK(filter_->child_id(), request_id); + // We just unmapped the memory. + read_buffer_ = NULL; + return false; + } + // We just unmapped the memory. + read_buffer_ = NULL; + + filter_->Send(new ViewMsg_Resource_DataReceived( + routing_id_, request_id, handle, *bytes_read)); + + return true; +} + +void AsyncResourceHandler::OnDataDownloaded( + int request_id, int bytes_downloaded) { + filter_->Send(new ViewMsg_Resource_DataDownloaded( + routing_id_, request_id, bytes_downloaded)); +} + +bool AsyncResourceHandler::OnResponseCompleted( + int request_id, + const net::URLRequestStatus& status, + const std::string& security_info) { + Time completion_time = Time::Now(); + filter_->Send(new ViewMsg_Resource_RequestComplete(routing_id_, + request_id, + status, + security_info, + completion_time)); + + // If we still have a read buffer, then see about caching it for later... + // Note that we have to make sure the buffer is not still being used, so we + // have to perform an explicit check on the status code. + if (g_spare_read_buffer || + net::URLRequestStatus::SUCCESS != status.status()) { + read_buffer_ = NULL; + } else if (read_buffer_.get()) { + DCHECK(read_buffer_->data()); + read_buffer_.swap(&g_spare_read_buffer); + } + return true; +} + +void AsyncResourceHandler::OnRequestClosed() { +} + +// static +void AsyncResourceHandler::GlobalCleanup() { + if (g_spare_read_buffer) { + // Avoid the CHECK in SharedIOBuffer::~SharedIOBuffer(). + SharedIOBuffer* tmp = g_spare_read_buffer; + g_spare_read_buffer = NULL; + tmp->Release(); + } +} diff --git a/content/browser/renderer_host/async_resource_handler.h b/content/browser/renderer_host/async_resource_handler.h new file mode 100644 index 0000000..8e61f8a --- /dev/null +++ b/content/browser/renderer_host/async_resource_handler.h @@ -0,0 +1,61 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CONTENT_BROWSER_RENDERER_HOST_ASYNC_RESOURCE_HANDLER_H_ +#define CONTENT_BROWSER_RENDERER_HOST_ASYNC_RESOURCE_HANDLER_H_ +#pragma once + +#include <string> + +#include "chrome/browser/renderer_host/resource_handler.h" + +class ResourceDispatcherHost; +class ResourceMessageFilter; +class SharedIOBuffer; + +// Used to complete an asynchronous resource request in response to resource +// load events from the resource dispatcher host. +class AsyncResourceHandler : public ResourceHandler { + public: + AsyncResourceHandler(ResourceMessageFilter* filter, + int routing_id, + const GURL& url, + ResourceDispatcherHost* resource_dispatcher_host); + + // ResourceHandler implementation: + virtual bool OnUploadProgress(int request_id, uint64 position, uint64 size); + virtual bool OnRequestRedirected(int request_id, const GURL& new_url, + ResourceResponse* response, bool* defer); + virtual bool OnResponseStarted(int request_id, ResourceResponse* response); + virtual bool OnWillStart(int request_id, const GURL& url, bool* defer); + virtual bool OnWillRead(int request_id, net::IOBuffer** buf, int* buf_size, + int min_size); + virtual bool OnReadCompleted(int request_id, int* bytes_read); + virtual bool OnResponseCompleted(int request_id, + const net::URLRequestStatus& status, + const std::string& security_info); + virtual void OnRequestClosed(); + virtual void OnDataDownloaded(int request_id, int bytes_downloaded); + + static void GlobalCleanup(); + + private: + virtual ~AsyncResourceHandler(); + + scoped_refptr<SharedIOBuffer> read_buffer_; + ResourceMessageFilter* filter_; + int routing_id_; + ResourceDispatcherHost* rdh_; + + // |next_buffer_size_| is the size of the buffer to be allocated on the next + // OnWillRead() call. We exponentially grow the size of the buffer allocated + // when our owner fills our buffers. On the first OnWillRead() call, we + // allocate a buffer of 32k and double it in OnReadCompleted() if the buffer + // was filled, up to a maximum size of 512k. + int next_buffer_size_; + + DISALLOW_COPY_AND_ASSIGN(AsyncResourceHandler); +}; + +#endif // CONTENT_BROWSER_RENDERER_HOST_ASYNC_RESOURCE_HANDLER_H_ diff --git a/content/browser/renderer_host/audio_renderer_host.cc b/content/browser/renderer_host/audio_renderer_host.cc new file mode 100644 index 0000000..4fac389 --- /dev/null +++ b/content/browser/renderer_host/audio_renderer_host.cc @@ -0,0 +1,480 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "content/browser/renderer_host/audio_renderer_host.h" + +#include "base/metrics/histogram.h" +#include "base/process.h" +#include "base/shared_memory.h" +#include "chrome/common/render_messages.h" +#include "chrome/common/render_messages_params.h" +#include "content/browser/renderer_host/audio_sync_reader.h" +#include "ipc/ipc_logging.h" + +// The minimum number of samples in a hardware packet. +// This value is selected so that we can handle down to 5khz sample rate. +static const int kMinSamplesPerHardwarePacket = 1024; + +// The maximum number of samples in a hardware packet. +// This value is selected so that we can handle up to 192khz sample rate. +static const int kMaxSamplesPerHardwarePacket = 64 * 1024; + +// This constant governs the hardware audio buffer size, this value should be +// chosen carefully. +// This value is selected so that we have 8192 samples for 48khz streams. +static const int kMillisecondsPerHardwarePacket = 170; + +static uint32 SelectSamplesPerPacket(AudioParameters params) { + // Select the number of samples that can provide at least + // |kMillisecondsPerHardwarePacket| worth of audio data. + int samples = kMinSamplesPerHardwarePacket; + while (samples <= kMaxSamplesPerHardwarePacket && + samples * base::Time::kMillisecondsPerSecond < + params.sample_rate * kMillisecondsPerHardwarePacket) { + samples *= 2; + } + return samples; +} + +AudioRendererHost::AudioEntry::AudioEntry() + : render_view_id(0), + stream_id(0), + pending_buffer_request(false), + pending_close(false) { +} + +AudioRendererHost::AudioEntry::~AudioEntry() {} + +/////////////////////////////////////////////////////////////////////////////// +// AudioRendererHost implementations. +AudioRendererHost::AudioRendererHost() { +} + +AudioRendererHost::~AudioRendererHost() { + DCHECK(audio_entries_.empty()); +} + +void AudioRendererHost::OnChannelClosing() { + BrowserMessageFilter::OnChannelClosing(); + + // Since the IPC channel is gone, close all requested audio streams. + DeleteEntries(); +} + +void AudioRendererHost::OnDestruct() const { + BrowserThread::DeleteOnIOThread::Destruct(this); +} + +/////////////////////////////////////////////////////////////////////////////// +// media::AudioOutputController::EventHandler implementations. +void AudioRendererHost::OnCreated(media::AudioOutputController* controller) { + BrowserThread::PostTask( + BrowserThread::IO, + FROM_HERE, + NewRunnableMethod( + this, + &AudioRendererHost::DoCompleteCreation, + make_scoped_refptr(controller))); +} + +void AudioRendererHost::OnPlaying(media::AudioOutputController* controller) { + BrowserThread::PostTask( + BrowserThread::IO, + FROM_HERE, + NewRunnableMethod( + this, + &AudioRendererHost::DoSendPlayingMessage, + make_scoped_refptr(controller))); +} + +void AudioRendererHost::OnPaused(media::AudioOutputController* controller) { + BrowserThread::PostTask( + BrowserThread::IO, + FROM_HERE, + NewRunnableMethod( + this, + &AudioRendererHost::DoSendPausedMessage, + make_scoped_refptr(controller))); +} + +void AudioRendererHost::OnError(media::AudioOutputController* controller, + int error_code) { + BrowserThread::PostTask( + BrowserThread::IO, + FROM_HERE, + NewRunnableMethod(this, + &AudioRendererHost::DoHandleError, + make_scoped_refptr(controller), + error_code)); +} + +void AudioRendererHost::OnMoreData(media::AudioOutputController* controller, + AudioBuffersState buffers_state) { + BrowserThread::PostTask( + BrowserThread::IO, + FROM_HERE, + NewRunnableMethod(this, + &AudioRendererHost::DoRequestMoreData, + make_scoped_refptr(controller), + buffers_state)); +} + +void AudioRendererHost::DoCompleteCreation( + media::AudioOutputController* controller) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + + AudioEntry* entry = LookupByController(controller); + if (!entry) + return; + + if (!peer_handle()) { + NOTREACHED() << "Renderer process handle is invalid."; + DeleteEntryOnError(entry); + return; + } + + // Once the audio stream is created then complete the creation process by + // mapping shared memory and sharing with the renderer process. + base::SharedMemoryHandle foreign_memory_handle; + if (!entry->shared_memory.ShareToProcess(peer_handle(), + &foreign_memory_handle)) { + // If we failed to map and share the shared memory then close the audio + // stream and send an error message. + DeleteEntryOnError(entry); + return; + } + + if (entry->controller->LowLatencyMode()) { + AudioSyncReader* reader = + static_cast<AudioSyncReader*>(entry->reader.get()); + +#if defined(OS_WIN) + base::SyncSocket::Handle foreign_socket_handle; +#else + base::FileDescriptor foreign_socket_handle; +#endif + + // If we failed to prepare the sync socket for the renderer then we fail + // the construction of audio stream. + if (!reader->PrepareForeignSocketHandle(peer_handle(), + &foreign_socket_handle)) { + DeleteEntryOnError(entry); + return; + } + + Send(new ViewMsg_NotifyLowLatencyAudioStreamCreated( + entry->render_view_id, entry->stream_id, foreign_memory_handle, + foreign_socket_handle, entry->shared_memory.created_size())); + return; + } + + // The normal audio stream has created, send a message to the renderer + // process. + Send(new ViewMsg_NotifyAudioStreamCreated( + entry->render_view_id, entry->stream_id, foreign_memory_handle, + entry->shared_memory.created_size())); +} + +void AudioRendererHost::DoSendPlayingMessage( + media::AudioOutputController* controller) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + + AudioEntry* entry = LookupByController(controller); + if (!entry) + return; + + ViewMsg_AudioStreamState_Params params; + params.state = ViewMsg_AudioStreamState_Params::kPlaying; + Send(new ViewMsg_NotifyAudioStreamStateChanged( + entry->render_view_id, entry->stream_id, params)); +} + +void AudioRendererHost::DoSendPausedMessage( + media::AudioOutputController* controller) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + + AudioEntry* entry = LookupByController(controller); + if (!entry) + return; + + ViewMsg_AudioStreamState_Params params; + params.state = ViewMsg_AudioStreamState_Params::kPaused; + Send(new ViewMsg_NotifyAudioStreamStateChanged( + entry->render_view_id, entry->stream_id, params)); +} + +void AudioRendererHost::DoRequestMoreData( + media::AudioOutputController* controller, + AudioBuffersState buffers_state) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + + // If we already have a pending request then return. + AudioEntry* entry = LookupByController(controller); + if (!entry || entry->pending_buffer_request) + return; + + DCHECK(!entry->controller->LowLatencyMode()); + entry->pending_buffer_request = true; + Send(new ViewMsg_RequestAudioPacket( + entry->render_view_id, entry->stream_id, buffers_state)); +} + +void AudioRendererHost::DoHandleError(media::AudioOutputController* controller, + int error_code) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + + AudioEntry* entry = LookupByController(controller); + if (!entry) + return; + + DeleteEntryOnError(entry); +} + +/////////////////////////////////////////////////////////////////////////////// +// IPC Messages handler +bool AudioRendererHost::OnMessageReceived(const IPC::Message& message, + bool* message_was_ok) { + bool handled = true; + IPC_BEGIN_MESSAGE_MAP_EX(AudioRendererHost, message, *message_was_ok) + IPC_MESSAGE_HANDLER(ViewHostMsg_CreateAudioStream, OnCreateStream) + IPC_MESSAGE_HANDLER(ViewHostMsg_PlayAudioStream, OnPlayStream) + IPC_MESSAGE_HANDLER(ViewHostMsg_PauseAudioStream, OnPauseStream) + IPC_MESSAGE_HANDLER(ViewHostMsg_FlushAudioStream, OnFlushStream) + IPC_MESSAGE_HANDLER(ViewHostMsg_CloseAudioStream, OnCloseStream) + IPC_MESSAGE_HANDLER(ViewHostMsg_NotifyAudioPacketReady, OnNotifyPacketReady) + IPC_MESSAGE_HANDLER(ViewHostMsg_GetAudioVolume, OnGetVolume) + IPC_MESSAGE_HANDLER(ViewHostMsg_SetAudioVolume, OnSetVolume) + IPC_MESSAGE_UNHANDLED(handled = false) + IPC_END_MESSAGE_MAP_EX() + + return handled; +} + +void AudioRendererHost::OnCreateStream( + const IPC::Message& msg, int stream_id, + const ViewHostMsg_Audio_CreateStream_Params& params, bool low_latency) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + DCHECK(LookupById(msg.routing_id(), stream_id) == NULL); + + AudioParameters audio_params(params.params); + + // Select the hardware packet size if not specified. + if (!audio_params.samples_per_packet) { + audio_params.samples_per_packet = SelectSamplesPerPacket(audio_params); + } + uint32 packet_size = audio_params.GetPacketSize(); + + scoped_ptr<AudioEntry> entry(new AudioEntry()); + // Create the shared memory and share with the renderer process. + if (!entry->shared_memory.CreateAndMapAnonymous(packet_size)) { + // If creation of shared memory failed then send an error message. + SendErrorMessage(msg.routing_id(), stream_id); + return; + } + + if (low_latency) { + // If this is the low latency mode, we need to construct a SyncReader first. + scoped_ptr<AudioSyncReader> reader( + new AudioSyncReader(&entry->shared_memory)); + + // Then try to initialize the sync reader. + if (!reader->Init()) { + SendErrorMessage(msg.routing_id(), stream_id); + return; + } + + // If we have successfully created the SyncReader then assign it to the + // entry and construct an AudioOutputController. + entry->reader.reset(reader.release()); + entry->controller = + media::AudioOutputController::CreateLowLatency(this, audio_params, + entry->reader.get()); + } else { + // The choice of buffer capacity is based on experiment. + entry->controller = + media::AudioOutputController::Create(this, audio_params, + 3 * packet_size); + } + + if (!entry->controller) { + SendErrorMessage(msg.routing_id(), stream_id); + return; + } + + // If we have created the controller successfully create a entry and add it + // to the map. + entry->render_view_id = msg.routing_id(); + entry->stream_id = stream_id; + + audio_entries_.insert(std::make_pair( + AudioEntryId(msg.routing_id(), stream_id), + entry.release())); +} + +void AudioRendererHost::OnPlayStream(const IPC::Message& msg, int stream_id) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + + AudioEntry* entry = LookupById(msg.routing_id(), stream_id); + if (!entry) { + SendErrorMessage(msg.routing_id(), stream_id); + return; + } + + entry->controller->Play(); +} + +void AudioRendererHost::OnPauseStream(const IPC::Message& msg, int stream_id) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + + AudioEntry* entry = LookupById(msg.routing_id(), stream_id); + if (!entry) { + SendErrorMessage(msg.routing_id(), stream_id); + return; + } + + entry->controller->Pause(); +} + +void AudioRendererHost::OnFlushStream(const IPC::Message& msg, int stream_id) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + + AudioEntry* entry = LookupById(msg.routing_id(), stream_id); + if (!entry) { + SendErrorMessage(msg.routing_id(), stream_id); + return; + } + + entry->controller->Flush(); +} + +void AudioRendererHost::OnCloseStream(const IPC::Message& msg, int stream_id) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + + AudioEntry* entry = LookupById(msg.routing_id(), stream_id); + + if (entry) + CloseAndDeleteStream(entry); +} + +void AudioRendererHost::OnSetVolume(const IPC::Message& msg, int stream_id, + double volume) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + + AudioEntry* entry = LookupById(msg.routing_id(), stream_id); + if (!entry) { + SendErrorMessage(msg.routing_id(), stream_id); + return; + } + + // Make sure the volume is valid. + if (volume < 0 || volume > 1.0) + return; + entry->controller->SetVolume(volume); +} + +void AudioRendererHost::OnGetVolume(const IPC::Message& msg, int stream_id) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + NOTREACHED() << "This message shouldn't be received"; +} + +void AudioRendererHost::OnNotifyPacketReady( + const IPC::Message& msg, int stream_id, uint32 packet_size) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + + AudioEntry* entry = LookupById(msg.routing_id(), stream_id); + if (!entry) { + SendErrorMessage(msg.routing_id(), stream_id); + return; + } + + DCHECK(!entry->controller->LowLatencyMode()); + CHECK(packet_size <= entry->shared_memory.created_size()); + + if (!entry->pending_buffer_request) { + NOTREACHED() << "Buffer received but no such pending request"; + } + entry->pending_buffer_request = false; + + // Enqueue the data to media::AudioOutputController. + entry->controller->EnqueueData( + reinterpret_cast<uint8*>(entry->shared_memory.memory()), + packet_size); +} + +void AudioRendererHost::SendErrorMessage(int32 render_view_id, + int32 stream_id) { + ViewMsg_AudioStreamState_Params state; + state.state = ViewMsg_AudioStreamState_Params::kError; + Send(new ViewMsg_NotifyAudioStreamStateChanged( + render_view_id, stream_id, state)); +} + +void AudioRendererHost::DeleteEntries() { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + + for (AudioEntryMap::iterator i = audio_entries_.begin(); + i != audio_entries_.end(); ++i) { + CloseAndDeleteStream(i->second); + } +} + +void AudioRendererHost::CloseAndDeleteStream(AudioEntry* entry) { + if (!entry->pending_close) { + entry->controller->Close( + NewRunnableMethod(this, &AudioRendererHost::OnStreamClosed, entry)); + entry->pending_close = true; + } +} + +void AudioRendererHost::OnStreamClosed(AudioEntry* entry) { + // Delete the entry after we've closed the stream. + BrowserThread::PostTask( + BrowserThread::IO, FROM_HERE, + NewRunnableMethod(this, &AudioRendererHost::DeleteEntry, entry)); +} + +void AudioRendererHost::DeleteEntry(AudioEntry* entry) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + + // Delete the entry when this method goes out of scope. + scoped_ptr<AudioEntry> entry_deleter(entry); + + // Erase the entry from the map. + audio_entries_.erase( + AudioEntryId(entry->render_view_id, entry->stream_id)); +} + +void AudioRendererHost::DeleteEntryOnError(AudioEntry* entry) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + + // Sends the error message first before we close the stream because + // |entry| is destroyed in DeleteEntry(). + SendErrorMessage(entry->render_view_id, entry->stream_id); + CloseAndDeleteStream(entry); +} + +AudioRendererHost::AudioEntry* AudioRendererHost::LookupById( + int route_id, int stream_id) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + + AudioEntryMap::iterator i = audio_entries_.find( + AudioEntryId(route_id, stream_id)); + if (i != audio_entries_.end() && !i->second->pending_close) + return i->second; + return NULL; +} + +AudioRendererHost::AudioEntry* AudioRendererHost::LookupByController( + media::AudioOutputController* controller) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + + // Iterate the map of entries. + // TODO(hclam): Implement a faster look up method. + for (AudioEntryMap::iterator i = audio_entries_.begin(); + i != audio_entries_.end(); ++i) { + if (!i->second->pending_close && controller == i->second->controller.get()) + return i->second; + } + return NULL; +} diff --git a/content/browser/renderer_host/audio_renderer_host.h b/content/browser/renderer_host/audio_renderer_host.h new file mode 100644 index 0000000..128723c --- /dev/null +++ b/content/browser/renderer_host/audio_renderer_host.h @@ -0,0 +1,223 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// AudioRendererHost serves audio related requests from AudioRenderer which +// lives inside the render process and provide access to audio hardware. +// +// This class is owned by BrowserRenderProcessHost, and instantiated on UI +// thread, but all other operations and method calls happen on IO thread, so we +// need to be extra careful about the lifetime of this object. AudioManager is a +// singleton and created in IO thread, audio output streams are also created in +// the IO thread, so we need to destroy them also in IO thread. After this class +// is created, a task of OnInitialized() is posted on IO thread in which +// singleton of AudioManager is created and. +// +// Here's an example of a typical IPC dialog for audio: +// +// Renderer AudioRendererHost +// | | +// | CreateStream > | +// | < Created | +// | | +// | Play > | +// | < Playing | time +// | | +// | < RequestAudioPacket | +// | AudioPacketReady > | +// | ... | +// | < RequestAudioPacket | +// | AudioPacketReady > | +// | | +// | ... | +// | < RequestAudioPacket | +// | AudioPacketReady > | +// | ... | +// | Pause > | +// | < Paused | +// | ... | +// | Start > | +// | < Started | +// | ... | +// | Close > | +// v v + +// The above mode of operation uses relatively big buffers and has latencies +// of 50 ms or more. There is a second mode of operation which is low latency. +// For low latency audio, the picture above is modified by not having the +// RequestAudioPacket and the AudioPacketReady messages, instead a SyncSocket +// pair is used to signal buffer readiness without having to route messages +// using the IO thread. + +#ifndef CONTENT_BROWSER_RENDERER_HOST_AUDIO_RENDERER_HOST_H_ +#define CONTENT_BROWSER_RENDERER_HOST_AUDIO_RENDERER_HOST_H_ +#pragma once + +#include <map> + +#include "base/gtest_prod_util.h" +#include "base/process.h" +#include "base/ref_counted.h" +#include "base/scoped_ptr.h" +#include "base/shared_memory.h" +#include "chrome/browser/browser_message_filter.h" +#include "chrome/browser/browser_thread.h" +#include "ipc/ipc_message.h" +#include "media/audio/audio_io.h" +#include "media/audio/audio_output_controller.h" +#include "media/audio/simple_sources.h" + +class AudioManager; +struct ViewHostMsg_Audio_CreateStream_Params; + +class AudioRendererHost : public BrowserMessageFilter, + public media::AudioOutputController::EventHandler { + public: + typedef std::pair<int32, int> AudioEntryId; + + struct AudioEntry { + AudioEntry(); + ~AudioEntry(); + + // The AudioOutputController that manages the audio stream. + scoped_refptr<media::AudioOutputController> controller; + + // Render view ID that requested the audio stream. + int32 render_view_id; + + // The audio stream ID in the render view. + int stream_id; + + // Shared memory for transmission of the audio data. + base::SharedMemory shared_memory; + + // The synchronous reader to be used by the controller. We have the + // ownership of the reader. + scoped_ptr<media::AudioOutputController::SyncReader> reader; + + bool pending_buffer_request; + + // Set to true after we called Close() for the controller. + bool pending_close; + }; + + typedef std::map<AudioEntryId, AudioEntry*> AudioEntryMap; + + // Called from UI thread from the owner of this object. + AudioRendererHost(); + + + // BrowserMessageFilter implementation. + virtual void OnChannelClosing(); + virtual void OnDestruct() const; + virtual bool OnMessageReceived(const IPC::Message& message, + bool* message_was_ok); + + ///////////////////////////////////////////////////////////////////////////// + // AudioOutputController::EventHandler implementations. + virtual void OnCreated(media::AudioOutputController* controller); + virtual void OnPlaying(media::AudioOutputController* controller); + virtual void OnPaused(media::AudioOutputController* controller); + virtual void OnError(media::AudioOutputController* controller, + int error_code); + virtual void OnMoreData(media::AudioOutputController* controller, + AudioBuffersState buffers_state); + + private: + friend class AudioRendererHostTest; + friend class BrowserThread; + friend class DeleteTask<AudioRendererHost>; + friend class MockAudioRendererHost; + FRIEND_TEST_ALL_PREFIXES(AudioRendererHostTest, CreateMockStream); + FRIEND_TEST_ALL_PREFIXES(AudioRendererHostTest, MockStreamDataConversation); + + virtual ~AudioRendererHost(); + + //////////////////////////////////////////////////////////////////////////// + // Methods called on IO thread. + // Returns true if the message is an audio related message and should be + // handled by this class. + bool IsAudioRendererHostMessage(const IPC::Message& message); + + // Audio related IPC message handlers. + // Creates an audio output stream with the specified format. If this call is + // successful this object would keep an internal entry of the stream for the + // required properties. + void OnCreateStream(const IPC::Message& msg, int stream_id, + const ViewHostMsg_Audio_CreateStream_Params& params, + bool low_latency); + + // Play the audio stream referenced by |stream_id|. + void OnPlayStream(const IPC::Message& msg, int stream_id); + + // Pause the audio stream referenced by |stream_id|. + void OnPauseStream(const IPC::Message& msg, int stream_id); + + // Discard all audio data in stream referenced by |stream_id|. + void OnFlushStream(const IPC::Message& msg, int stream_id); + + // Close the audio stream referenced by |stream_id|. + void OnCloseStream(const IPC::Message& msg, int stream_id); + + // Set the volume of the audio stream referenced by |stream_id|. + void OnSetVolume(const IPC::Message& msg, int stream_id, double volume); + + // Get the volume of the audio stream referenced by |stream_id|. + void OnGetVolume(const IPC::Message& msg, int stream_id); + + // Notify packet has been prepared for the audio stream. + void OnNotifyPacketReady(const IPC::Message& msg, int stream_id, + uint32 packet_size); + + // Complete the process of creating an audio stream. This will set up the + // shared memory or shared socket in low latency mode. + void DoCompleteCreation(media::AudioOutputController* controller); + + // Send a state change message to the renderer. + void DoSendPlayingMessage(media::AudioOutputController* controller); + void DoSendPausedMessage(media::AudioOutputController* controller); + + // Request more data from the renderer. This method is used only in normal + // latency mode. + void DoRequestMoreData(media::AudioOutputController* controller, + AudioBuffersState buffers_state); + + // Handle error coming from audio stream. + void DoHandleError(media::AudioOutputController* controller, int error_code); + + // Send an error message to the renderer. + void SendErrorMessage(int32 render_view_id, int32 stream_id); + + // Delete all audio entry and all audio streams + void DeleteEntries(); + + // Closes the stream. The stream is then deleted in DeleteEntry() after it + // is closed. + void CloseAndDeleteStream(AudioEntry* entry); + + // Called on the audio thread after the audio stream is closed. + void OnStreamClosed(AudioEntry* entry); + + // Delete an audio entry and close the related audio stream. + void DeleteEntry(AudioEntry* entry); + + // Delete audio entry and close the related audio stream due to an error, + // and error message is send to the renderer. + void DeleteEntryOnError(AudioEntry* entry); + + // A helper method to look up a AudioEntry with a tuple of render view + // id and stream id. Returns NULL if not found. + AudioEntry* LookupById(int render_view_id, int stream_id); + + // Search for a AudioEntry having the reference to |controller|. + // This method is used to look up an AudioEntry after a controller + // event is received. + AudioEntry* LookupByController(media::AudioOutputController* controller); + + // A map of id to audio sources. + AudioEntryMap audio_entries_; + + DISALLOW_COPY_AND_ASSIGN(AudioRendererHost); +}; + +#endif // CONTENT_BROWSER_RENDERER_HOST_AUDIO_RENDERER_HOST_H_ diff --git a/content/browser/renderer_host/audio_renderer_host_unittest.cc b/content/browser/renderer_host/audio_renderer_host_unittest.cc new file mode 100644 index 0000000..4f6c3cd --- /dev/null +++ b/content/browser/renderer_host/audio_renderer_host_unittest.cc @@ -0,0 +1,488 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/environment.h" +#include "base/message_loop.h" +#include "base/process_util.h" +#include "base/scoped_ptr.h" +#include "base/sync_socket.h" +#include "chrome/browser/browser_thread.h" +#include "chrome/common/render_messages.h" +#include "chrome/common/render_messages_params.h" +#include "content/browser/renderer_host/audio_renderer_host.h" +#include "ipc/ipc_message_utils.h" +#include "media/audio/audio_manager.h" +#include "media/audio/fake_audio_output_stream.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +using ::testing::_; +using ::testing::DoAll; +using ::testing::InSequence; +using ::testing::InvokeWithoutArgs; +using ::testing::Return; +using ::testing::SaveArg; +using ::testing::SetArgumentPointee; + +static const int kInvalidId = -1; +static const int kRouteId = 200; +static const int kStreamId = 50; + +static bool IsRunningHeadless() { + scoped_ptr<base::Environment> env(base::Environment::Create()); + if (env->HasVar("CHROME_HEADLESS")) + return true; + return false; +} + +class MockAudioRendererHost : public AudioRendererHost { + public: + MockAudioRendererHost() : shared_memory_length_(0) { + } + + virtual ~MockAudioRendererHost() { + } + + // A list of mock methods. + MOCK_METHOD3(OnRequestPacket, + void(int routing_id, int stream_id, + AudioBuffersState buffers_state)); + MOCK_METHOD3(OnStreamCreated, + void(int routing_id, int stream_id, int length)); + MOCK_METHOD3(OnLowLatencyStreamCreated, + void(int routing_id, int stream_id, int length)); + MOCK_METHOD2(OnStreamPlaying, void(int routing_id, int stream_id)); + MOCK_METHOD2(OnStreamPaused, void(int routing_id, int stream_id)); + MOCK_METHOD2(OnStreamError, void(int routing_id, int stream_id)); + MOCK_METHOD3(OnStreamVolume, + void(int routing_id, int stream_id, double volume)); + + base::SharedMemory* shared_memory() { return shared_memory_.get(); } + uint32 shared_memory_length() { return shared_memory_length_; } + + base::SyncSocket* sync_socket() { return sync_socket_.get(); } + + private: + // This method is used to dispatch IPC messages to the renderer. We intercept + // these messages here and dispatch to our mock methods to verify the + // conversation between this object and the renderer. + virtual bool Send(IPC::Message* message) { + CHECK(message); + + // In this method we dispatch the messages to the according handlers as if + // we are the renderer. + bool handled = true; + IPC_BEGIN_MESSAGE_MAP(MockAudioRendererHost, *message) + IPC_MESSAGE_HANDLER(ViewMsg_RequestAudioPacket, OnRequestPacket) + IPC_MESSAGE_HANDLER(ViewMsg_NotifyAudioStreamCreated, OnStreamCreated) + IPC_MESSAGE_HANDLER(ViewMsg_NotifyLowLatencyAudioStreamCreated, + OnLowLatencyStreamCreated) + IPC_MESSAGE_HANDLER(ViewMsg_NotifyAudioStreamStateChanged, + OnStreamStateChanged) + IPC_MESSAGE_HANDLER(ViewMsg_NotifyAudioStreamVolume, OnStreamVolume) + IPC_MESSAGE_UNHANDLED(handled = false) + IPC_END_MESSAGE_MAP() + EXPECT_TRUE(handled); + + delete message; + return true; + } + + // These handler methods do minimal things and delegate to the mock methods. + void OnRequestPacket(const IPC::Message& msg, int stream_id, + AudioBuffersState buffers_state) { + OnRequestPacket(msg.routing_id(), stream_id, buffers_state); + } + + void OnStreamCreated(const IPC::Message& msg, int stream_id, + base::SharedMemoryHandle handle, uint32 length) { + // Maps the shared memory. + shared_memory_.reset(new base::SharedMemory(handle, false)); + ASSERT_TRUE(shared_memory_->Map(length)); + ASSERT_TRUE(shared_memory_->memory()); + shared_memory_length_ = length; + + // And then delegate the call to the mock method. + OnStreamCreated(msg.routing_id(), stream_id, length); + } + + void OnLowLatencyStreamCreated(const IPC::Message& msg, int stream_id, + base::SharedMemoryHandle handle, +#if defined(OS_WIN) + base::SyncSocket::Handle socket_handle, +#else + base::FileDescriptor socket_descriptor, +#endif + uint32 length) { + // Maps the shared memory. + shared_memory_.reset(new base::SharedMemory(handle, false)); + CHECK(shared_memory_->Map(length)); + CHECK(shared_memory_->memory()); + shared_memory_length_ = length; + + // Create the SyncSocket using the handle. + base::SyncSocket::Handle sync_socket_handle; +#if defined(OS_WIN) + sync_socket_handle = socket_handle; +#else + sync_socket_handle = socket_descriptor.fd; +#endif + sync_socket_.reset(new base::SyncSocket(sync_socket_handle)); + + // And then delegate the call to the mock method. + OnLowLatencyStreamCreated(msg.routing_id(), stream_id, length); + } + + void OnStreamStateChanged(const IPC::Message& msg, int stream_id, + const ViewMsg_AudioStreamState_Params& params) { + if (params.state == ViewMsg_AudioStreamState_Params::kPlaying) { + OnStreamPlaying(msg.routing_id(), stream_id); + } else if (params.state == ViewMsg_AudioStreamState_Params::kPaused) { + OnStreamPaused(msg.routing_id(), stream_id); + } else if (params.state == ViewMsg_AudioStreamState_Params::kError) { + OnStreamError(msg.routing_id(), stream_id); + } else { + FAIL() << "Unknown stream state"; + } + } + + void OnStreamVolume(const IPC::Message& msg, int stream_id, double volume) { + OnStreamVolume(msg.routing_id(), stream_id, volume); + } + + scoped_ptr<base::SharedMemory> shared_memory_; + scoped_ptr<base::SyncSocket> sync_socket_; + uint32 shared_memory_length_; + + DISALLOW_COPY_AND_ASSIGN(MockAudioRendererHost); +}; + +ACTION_P(QuitMessageLoop, message_loop) { + message_loop->PostTask(FROM_HERE, new MessageLoop::QuitTask()); +} + +class AudioRendererHostTest : public testing::Test { + public: + AudioRendererHostTest() + : mock_stream_(true) { + } + + protected: + virtual void SetUp() { + // Create a message loop so AudioRendererHost can use it. + message_loop_.reset(new MessageLoop(MessageLoop::TYPE_IO)); + io_thread_.reset(new BrowserThread(BrowserThread::IO, message_loop_.get())); + host_ = new MockAudioRendererHost(); + + // Simulate IPC channel connected. + host_->OnChannelConnected(base::GetCurrentProcId()); + } + + virtual void TearDown() { + // Simulate closing the IPC channel. + host_->OnChannelClosing(); + + // Release the reference to the mock object. The object will be destructed + // on message_loop_. + host_ = NULL; + + // We need to continue running message_loop_ to complete all destructions. + SyncWithAudioThread(); + + io_thread_.reset(); + } + + void Create() { + InSequence s; + // 1. We will first receive a OnStreamCreated() signal. + EXPECT_CALL(*host_, + OnStreamCreated(kRouteId, kStreamId, _)); + + // 2. First packet request will arrive. + EXPECT_CALL(*host_, OnRequestPacket(kRouteId, kStreamId, _)) + .WillOnce(QuitMessageLoop(message_loop_.get())); + + IPC::Message msg; + msg.set_routing_id(kRouteId); + + ViewHostMsg_Audio_CreateStream_Params params; + if (mock_stream_) + params.params.format = AudioParameters::AUDIO_MOCK; + else + params.params.format = AudioParameters::AUDIO_PCM_LINEAR; + params.params.channels = 2; + params.params.sample_rate = AudioParameters::kAudioCDSampleRate; + params.params.bits_per_sample = 16; + params.params.samples_per_packet = 0; + + // Send a create stream message to the audio output stream and wait until + // we receive the created message. + host_->OnCreateStream(msg, kStreamId, params, false); + message_loop_->Run(); + } + + void CreateLowLatency() { + InSequence s; + // We will first receive a OnLowLatencyStreamCreated() signal. + EXPECT_CALL(*host_, + OnLowLatencyStreamCreated(kRouteId, kStreamId, _)) + .WillOnce(QuitMessageLoop(message_loop_.get())); + + IPC::Message msg; + msg.set_routing_id(kRouteId); + + ViewHostMsg_Audio_CreateStream_Params params; + if (mock_stream_) + params.params.format = AudioParameters::AUDIO_MOCK; + else + params.params.format = AudioParameters::AUDIO_PCM_LINEAR; + params.params.channels = 2; + params.params.sample_rate = AudioParameters::kAudioCDSampleRate; + params.params.bits_per_sample = 16; + params.params.samples_per_packet = 0; + + // Send a create stream message to the audio output stream and wait until + // we receive the created message. + host_->OnCreateStream(msg, kStreamId, params, true); + message_loop_->Run(); + } + + void Close() { + // Send a message to AudioRendererHost to tell it we want to close the + // stream. + IPC::Message msg; + msg.set_routing_id(kRouteId); + host_->OnCloseStream(msg, kStreamId); + message_loop_->RunAllPending(); + } + + void Play() { + EXPECT_CALL(*host_, OnStreamPlaying(kRouteId, kStreamId)) + .WillOnce(QuitMessageLoop(message_loop_.get())); + + IPC::Message msg; + msg.set_routing_id(kRouteId); + host_->OnPlayStream(msg, kStreamId); + message_loop_->Run(); + } + + void Pause() { + EXPECT_CALL(*host_, OnStreamPaused(kRouteId, kStreamId)) + .WillOnce(QuitMessageLoop(message_loop_.get())); + + IPC::Message msg; + msg.set_routing_id(kRouteId); + host_->OnPauseStream(msg, kStreamId); + message_loop_->Run(); + } + + void SetVolume(double volume) { + IPC::Message msg; + msg.set_routing_id(kRouteId); + host_->OnSetVolume(msg, kStreamId, volume); + message_loop_->RunAllPending(); + } + + void NotifyPacketReady() { + EXPECT_CALL(*host_, OnRequestPacket(kRouteId, kStreamId, _)) + .WillOnce(QuitMessageLoop(message_loop_.get())); + + IPC::Message msg; + msg.set_routing_id(kRouteId); + memset(host_->shared_memory()->memory(), 0, host_->shared_memory_length()); + host_->OnNotifyPacketReady(msg, kStreamId, + host_->shared_memory_length()); + message_loop_->Run(); + } + + void SimulateError() { + // Find the first AudioOutputController in the AudioRendererHost. + CHECK(host_->audio_entries_.size()) + << "Calls Create() before calling this method"; + media::AudioOutputController* controller = + host_->audio_entries_.begin()->second->controller; + CHECK(controller) << "AudioOutputController not found"; + + // Expect an error signal sent through IPC. + EXPECT_CALL(*host_, OnStreamError(kRouteId, kStreamId)); + + // Simulate an error sent from the audio device. + host_->OnError(controller, 0); + SyncWithAudioThread(); + + // Expect the audio stream record is removed. + EXPECT_EQ(0u, host_->audio_entries_.size()); + } + + // Called on the audio thread. + static void PostQuitMessageLoop(MessageLoop* message_loop) { + message_loop->PostTask(FROM_HERE, new MessageLoop::QuitTask()); + } + + // Called on the main thread. + static void PostQuitOnAudioThread(MessageLoop* message_loop) { + AudioManager::GetAudioManager()->GetMessageLoop()->PostTask( + FROM_HERE, NewRunnableFunction(&PostQuitMessageLoop, message_loop)); + } + + // SyncWithAudioThread() waits until all pending tasks on the audio thread + // are executed while also processing pending task in message_loop_ on the + // current thread. It is used to synchronize with the audio thread when we are + // closing an audio stream. + void SyncWithAudioThread() { + message_loop_->PostTask( + FROM_HERE, NewRunnableFunction(&PostQuitOnAudioThread, + message_loop_.get())); + message_loop_->Run(); + } + + MessageLoop* message_loop() { return message_loop_.get(); } + MockAudioRendererHost* host() { return host_; } + void EnableRealDevice() { mock_stream_ = false; } + + private: + bool mock_stream_; + scoped_refptr<MockAudioRendererHost> host_; + scoped_ptr<MessageLoop> message_loop_; + scoped_ptr<BrowserThread> io_thread_; + + DISALLOW_COPY_AND_ASSIGN(AudioRendererHostTest); +}; + +TEST_F(AudioRendererHostTest, CreateAndClose) { + if (!IsRunningHeadless()) + EnableRealDevice(); + + Create(); + Close(); +} + +TEST_F(AudioRendererHostTest, CreatePlayAndClose) { + if (!IsRunningHeadless()) + EnableRealDevice(); + + Create(); + Play(); + Close(); +} + +TEST_F(AudioRendererHostTest, CreatePlayPauseAndClose) { + if (!IsRunningHeadless()) + EnableRealDevice(); + + Create(); + Play(); + Pause(); + Close(); +} + +TEST_F(AudioRendererHostTest, SetVolume) { + if (!IsRunningHeadless()) + EnableRealDevice(); + + Create(); + SetVolume(0.5); + Play(); + Pause(); + Close(); + + // Expect the volume is set. + if (IsRunningHeadless()) { + EXPECT_EQ(0.5, FakeAudioOutputStream::GetLastFakeStream()->volume()); + } +} + +// Simulate the case where a stream is not properly closed. +TEST_F(AudioRendererHostTest, CreateAndShutdown) { + if (!IsRunningHeadless()) + EnableRealDevice(); + + Create(); +} + +// Simulate the case where a stream is not properly closed. +TEST_F(AudioRendererHostTest, CreatePlayAndShutdown) { + if (!IsRunningHeadless()) + EnableRealDevice(); + + Create(); + Play(); +} + +// Simulate the case where a stream is not properly closed. +TEST_F(AudioRendererHostTest, CreatePlayPauseAndShutdown) { + if (!IsRunningHeadless()) + EnableRealDevice(); + + Create(); + Play(); + Pause(); +} + +TEST_F(AudioRendererHostTest, DataConversationMockStream) { + Create(); + + // Note that we only do notify three times because the buffer capacity is + // triple of one packet size. + NotifyPacketReady(); + NotifyPacketReady(); + NotifyPacketReady(); + Close(); +} + +TEST_F(AudioRendererHostTest, DataConversationRealStream) { + if (IsRunningHeadless()) + return; + EnableRealDevice(); + Create(); + Play(); + + // If this is a real audio device, the data conversation is not limited + // to the buffer capacity of AudioOutputController. So we do 5 exchanges + // before we close the device. + for (int i = 0; i < 5; ++i) { + NotifyPacketReady(); + } + Close(); +} + +TEST_F(AudioRendererHostTest, SimulateError) { + if (!IsRunningHeadless()) + EnableRealDevice(); + + Create(); + Play(); + SimulateError(); +} + +// Simulate the case when an error is generated on the browser process, +// the audio device is closed but the render process try to close the +// audio stream again. +TEST_F(AudioRendererHostTest, SimulateErrorAndClose) { + if (!IsRunningHeadless()) + EnableRealDevice(); + + Create(); + Play(); + SimulateError(); + Close(); +} + +TEST_F(AudioRendererHostTest, CreateLowLatencyAndClose) { + if (!IsRunningHeadless()) + EnableRealDevice(); + + CreateLowLatency(); + Close(); +} + +// Simulate the case where a stream is not properly closed. +TEST_F(AudioRendererHostTest, CreateLowLatencyAndShutdown) { + if (!IsRunningHeadless()) + EnableRealDevice(); + + CreateLowLatency(); +} + +// TODO(hclam): Add tests for data conversation in low latency mode. diff --git a/content/browser/renderer_host/audio_sync_reader.cc b/content/browser/renderer_host/audio_sync_reader.cc new file mode 100644 index 0000000..261bec0 --- /dev/null +++ b/content/browser/renderer_host/audio_sync_reader.cc @@ -0,0 +1,64 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "content/browser/renderer_host/audio_sync_reader.h" + +#include "base/process_util.h" +#include "base/shared_memory.h" + +AudioSyncReader::AudioSyncReader(base::SharedMemory* shared_memory) + : shared_memory_(shared_memory) { +} + +AudioSyncReader::~AudioSyncReader() { +} + +// media::AudioOutputController::SyncReader implementations. +void AudioSyncReader::UpdatePendingBytes(uint32 bytes) { + socket_->Send(&bytes, sizeof(bytes)); +} + +uint32 AudioSyncReader::Read(void* data, uint32 size) { + uint32 read_size = std::min(size, shared_memory_->created_size()); + memcpy(data, shared_memory_->memory(), read_size); + memset(shared_memory_->memory(), 0, shared_memory_->created_size()); + return read_size; +} + +void AudioSyncReader::Close() { + socket_->Close(); +} + +bool AudioSyncReader::Init() { + base::SyncSocket* sockets[2] = {0}; + if (base::SyncSocket::CreatePair(sockets)) { + socket_.reset(sockets[0]); + foreign_socket_.reset(sockets[1]); + return true; + } + return false; +} + +#if defined(OS_WIN) +bool AudioSyncReader::PrepareForeignSocketHandle( + base::ProcessHandle process_handle, + base::SyncSocket::Handle* foreign_handle) { + ::DuplicateHandle(GetCurrentProcess(), foreign_socket_->handle(), + process_handle, foreign_handle, + 0, FALSE, DUPLICATE_SAME_ACCESS); + if (*foreign_handle != 0) + return true; + return false; +} +#else +bool AudioSyncReader::PrepareForeignSocketHandle( + base::ProcessHandle process_handle, + base::FileDescriptor* foreign_handle) { + foreign_handle->fd = foreign_socket_->handle(); + foreign_handle->auto_close = false; + if (foreign_handle->fd != -1) + return true; + return false; +} +#endif diff --git a/content/browser/renderer_host/audio_sync_reader.h b/content/browser/renderer_host/audio_sync_reader.h new file mode 100644 index 0000000..054bb52 --- /dev/null +++ b/content/browser/renderer_host/audio_sync_reader.h @@ -0,0 +1,56 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CONTENT_BROWSER_RENDERER_HOST_AUDIO_SYNC_READER_H_ +#define CONTENT_BROWSER_RENDERER_HOST_AUDIO_SYNC_READER_H_ +#pragma once + +#include "base/file_descriptor_posix.h" +#include "base/process.h" +#include "base/sync_socket.h" +#include "media/audio/audio_output_controller.h" + +namespace base { + +class SharedMemory; + +} + +// A AudioOutputController::SyncReader implementation using SyncSocket. This +// is used by AudioOutputController to provide a low latency data source for +// transmitting audio packets between the browser process and the renderer +// process. +class AudioSyncReader : public media::AudioOutputController::SyncReader { + public: + explicit AudioSyncReader(base::SharedMemory* shared_memory); + + virtual ~AudioSyncReader(); + + // media::AudioOutputController::SyncReader implementations. + virtual void UpdatePendingBytes(uint32 bytes); + virtual uint32 Read(void* data, uint32 size); + virtual void Close(); + + bool Init(); + bool PrepareForeignSocketHandle(base::ProcessHandle process_handle, +#if defined(OS_WIN) + base::SyncSocket::Handle* foreign_handle); +#else + base::FileDescriptor* foreign_handle); +#endif + + private: + base::SharedMemory* shared_memory_; + + // A pair of SyncSocket for transmitting audio data. + scoped_ptr<base::SyncSocket> socket_; + + // SyncSocket to be used by the renderer. The reference is released after + // PrepareForeignSocketHandle() is called and ran successfully. + scoped_ptr<base::SyncSocket> foreign_socket_; + + DISALLOW_COPY_AND_ASSIGN(AudioSyncReader); +}; + +#endif // CONTENT_BROWSER_RENDERER_HOST_AUDIO_SYNC_READER_H_ diff --git a/content/browser/renderer_host/backing_store.cc b/content/browser/renderer_host/backing_store.cc new file mode 100644 index 0000000..2c6edfc --- /dev/null +++ b/content/browser/renderer_host/backing_store.cc @@ -0,0 +1,17 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "content/browser/renderer_host/backing_store.h" + +BackingStore::BackingStore(RenderWidgetHost* widget, const gfx::Size& size) + : render_widget_host_(widget), + size_(size) { +} + +BackingStore::~BackingStore() { +} + +size_t BackingStore::MemorySize() { + return size_.GetArea() * 4; +} diff --git a/content/browser/renderer_host/backing_store.h b/content/browser/renderer_host/backing_store.h new file mode 100644 index 0000000..5043b3e --- /dev/null +++ b/content/browser/renderer_host/backing_store.h @@ -0,0 +1,75 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CONTENT_BROWSER_RENDERER_HOST_BACKING_STORE_H_ +#define CONTENT_BROWSER_RENDERER_HOST_BACKING_STORE_H_ +#pragma once + +#include <vector> + +#include "app/surface/transport_dib.h" +#include "base/basictypes.h" +#include "ui/gfx/size.h" + +class RenderProcessHost; +class RenderWidgetHost; + +namespace gfx { +class Rect; +} + +namespace skia { +class PlatformCanvas; +} + +// Represents a backing store for the pixels in a RenderWidgetHost. +class BackingStore { + public: + virtual ~BackingStore(); + + RenderWidgetHost* render_widget_host() const { return render_widget_host_; } + const gfx::Size& size() { return size_; } + + // The number of bytes that this backing store consumes. The default + // implementation just assumes there's 32 bits per pixel over the current + // size of the screen. Implementations may override this if they have more + // information about the color depth. + virtual size_t MemorySize(); + + // Paints the bitmap from the renderer onto the backing store. bitmap_rect + // gives the location of bitmap, and copy_rects specifies the subregion(s) of + // the backingstore to be painted from the bitmap. + virtual void PaintToBackingStore( + RenderProcessHost* process, + TransportDIB::Id bitmap, + const gfx::Rect& bitmap_rect, + const std::vector<gfx::Rect>& copy_rects) = 0; + + // Extracts the gives subset of the backing store and copies it to the given + // PlatformCanvas. The PlatformCanvas should not be initialized. This function + // will call initialize() with the correct size. The return value indicates + // success. + virtual bool CopyFromBackingStore(const gfx::Rect& rect, + skia::PlatformCanvas* output) = 0; + + // Scrolls the contents of clip_rect in the backing store by dx or dy (but dx + // and dy cannot both be non-zero). + virtual void ScrollBackingStore(int dx, int dy, + const gfx::Rect& clip_rect, + const gfx::Size& view_size) = 0; + protected: + // Can only be constructed via subclasses. + BackingStore(RenderWidgetHost* widget, const gfx::Size& size); + + private: + // The owner of this backing store. + RenderWidgetHost* render_widget_host_; + + // The size of the backing store. + gfx::Size size_; + + DISALLOW_COPY_AND_ASSIGN(BackingStore); +}; + +#endif // CONTENT_BROWSER_RENDERER_HOST_BACKING_STORE_H_ diff --git a/content/browser/renderer_host/backing_store_mac.h b/content/browser/renderer_host/backing_store_mac.h new file mode 100644 index 0000000..b9ac17f --- /dev/null +++ b/content/browser/renderer_host/backing_store_mac.h @@ -0,0 +1,56 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CONTENT_BROWSER_RENDERER_HOST_BACKING_STORE_MAC_H_ +#define CONTENT_BROWSER_RENDERER_HOST_BACKING_STORE_MAC_H_ +#pragma once + +#include "base/basictypes.h" +#include "base/mac/scoped_cftyperef.h" +#include "content/browser/renderer_host/backing_store.h" + +class BackingStoreMac : public BackingStore { + public: + BackingStoreMac(RenderWidgetHost* widget, const gfx::Size& size); + virtual ~BackingStoreMac(); + + // A CGLayer that stores the contents of the backing store, cached in GPU + // memory if possible. + CGLayerRef cg_layer() { return cg_layer_; } + + // A CGBitmapContext that stores the contents of the backing store if the + // corresponding Cocoa view has not been inserted into an NSWindow yet. + CGContextRef cg_bitmap() { return cg_bitmap_; } + + // BackingStore implementation. + virtual void PaintToBackingStore( + RenderProcessHost* process, + TransportDIB::Id bitmap, + const gfx::Rect& bitmap_rect, + const std::vector<gfx::Rect>& copy_rects); + virtual bool CopyFromBackingStore(const gfx::Rect& rect, + skia::PlatformCanvas* output); + virtual void ScrollBackingStore(int dx, int dy, + const gfx::Rect& clip_rect, + const gfx::Size& view_size); + + private: + // Creates a CGLayer associated with its owner view's window's graphics + // context, sized properly for the backing store. Returns NULL if the owner + // is not in a window with a CGContext. cg_layer_ is assigned this method's + // result. + CGLayerRef CreateCGLayer(); + + // Creates a CGBitmapContext sized properly for the backing store. The + // owner view need not be in a window. cg_bitmap_ is assigned this method's + // result. + CGContextRef CreateCGBitmapContext(); + + base::mac::ScopedCFTypeRef<CGContextRef> cg_bitmap_; + base::mac::ScopedCFTypeRef<CGLayerRef> cg_layer_; + + DISALLOW_COPY_AND_ASSIGN(BackingStoreMac); +}; + +#endif // CONTENT_BROWSER_RENDERER_HOST_BACKING_STORE_MAC_H_ diff --git a/content/browser/renderer_host/backing_store_mac.mm b/content/browser/renderer_host/backing_store_mac.mm new file mode 100644 index 0000000..424a7b8 --- /dev/null +++ b/content/browser/renderer_host/backing_store_mac.mm @@ -0,0 +1,242 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#import <Cocoa/Cocoa.h> + +#include "content/browser/renderer_host/backing_store_mac.h" + +#include "app/surface/transport_dib.h" +#include "base/logging.h" +#include "base/mac/mac_util.h" +#include "base/mac/scoped_cftyperef.h" +#include "base/sys_info.h" +#include "content/browser/renderer_host/render_process_host.h" +#include "content/browser/renderer_host/render_widget_host.h" +#include "content/browser/renderer_host/render_widget_host_view.h" +#include "skia/ext/platform_canvas.h" +#include "third_party/skia/include/core/SkBitmap.h" +#include "third_party/skia/include/core/SkCanvas.h" +#include "ui/gfx/rect.h" + +// Mac Backing Stores: +// +// Since backing stores are only ever written to or drawn into windows, we keep +// our backing store in a CGLayer that can get cached in GPU memory. This +// allows acclerated drawing into the layer and lets scrolling and such happen +// all or mostly on the GPU, which is good for performance. + +namespace { + +// Returns whether this version of OS X has broken CGLayers, see +// http://crbug.com/45553 , comments 5 and 6. +bool NeedsLayerWorkaround() { + int32 os_major, os_minor, os_bugfix; + base::SysInfo::OperatingSystemVersionNumbers( + &os_major, &os_minor, &os_bugfix); + return os_major == 10 && os_minor == 5; +} + +} // namespace + +BackingStoreMac::BackingStoreMac(RenderWidgetHost* widget, + const gfx::Size& size) + : BackingStore(widget, size) { + cg_layer_.reset(CreateCGLayer()); + if (!cg_layer_) { + // The view isn't in a window yet. Use a CGBitmapContext for now. + cg_bitmap_.reset(CreateCGBitmapContext()); + } +} + +BackingStoreMac::~BackingStoreMac() { +} + +void BackingStoreMac::PaintToBackingStore( + RenderProcessHost* process, + TransportDIB::Id bitmap, + const gfx::Rect& bitmap_rect, + const std::vector<gfx::Rect>& copy_rects) { + DCHECK_NE(static_cast<bool>(cg_layer()), static_cast<bool>(cg_bitmap())); + + TransportDIB* dib = process->GetTransportDIB(bitmap); + if (!dib) + return; + + base::mac::ScopedCFTypeRef<CGDataProviderRef> data_provider( + CGDataProviderCreateWithData(NULL, dib->memory(), + bitmap_rect.width() * bitmap_rect.height() * 4, NULL)); + + base::mac::ScopedCFTypeRef<CGImageRef> bitmap_image( + CGImageCreate(bitmap_rect.width(), bitmap_rect.height(), 8, 32, + 4 * bitmap_rect.width(), base::mac::GetSystemColorSpace(), + kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host, + data_provider, NULL, false, kCGRenderingIntentDefault)); + + for (size_t i = 0; i < copy_rects.size(); i++) { + const gfx::Rect& copy_rect = copy_rects[i]; + + // Only the subpixels given by copy_rect have pixels to copy. + base::mac::ScopedCFTypeRef<CGImageRef> image( + CGImageCreateWithImageInRect(bitmap_image, CGRectMake( + copy_rect.x() - bitmap_rect.x(), + copy_rect.y() - bitmap_rect.y(), + copy_rect.width(), + copy_rect.height()))); + + if (!cg_layer()) { + // The view may have moved to a window. Try to get a CGLayer. + cg_layer_.reset(CreateCGLayer()); + if (cg_layer()) { + // now that we have a layer, copy the cached image into it + base::mac::ScopedCFTypeRef<CGImageRef> bitmap_image( + CGBitmapContextCreateImage(cg_bitmap_)); + CGContextDrawImage(CGLayerGetContext(cg_layer()), + CGRectMake(0, 0, size().width(), size().height()), + bitmap_image); + // Discard the cache bitmap, since we no longer need it. + cg_bitmap_.reset(NULL); + } + } + + DCHECK_NE(static_cast<bool>(cg_layer()), static_cast<bool>(cg_bitmap())); + + if (cg_layer()) { + // The CGLayer's origin is in the lower left, but flipping the CTM would + // cause the image to get drawn upside down. So we move the rectangle + // to the right position before drawing the image. + CGContextRef layer = CGLayerGetContext(cg_layer()); + gfx::Rect paint_rect = copy_rect; + paint_rect.set_y(size().height() - copy_rect.bottom()); + CGContextDrawImage(layer, paint_rect.ToCGRect(), image); + } else { + // The layer hasn't been created yet, so draw into the cache bitmap. + gfx::Rect paint_rect = copy_rect; + paint_rect.set_y(size().height() - copy_rect.bottom()); + CGContextDrawImage(cg_bitmap_, paint_rect.ToCGRect(), image); + } + } +} + +bool BackingStoreMac::CopyFromBackingStore(const gfx::Rect& rect, + skia::PlatformCanvas* output) { + if (!output->initialize(rect.width(), rect.height(), true)) + return false; + + CGContextRef temp_context = output->beginPlatformPaint(); + CGContextSaveGState(temp_context); + CGContextTranslateCTM(temp_context, 0.0, size().height()); + CGContextScaleCTM(temp_context, 1.0, -1.0); + CGContextDrawLayerAtPoint(temp_context, CGPointMake(rect.x(), rect.y()), + cg_layer()); + CGContextRestoreGState(temp_context); + output->endPlatformPaint(); + return true; +} + +// Scroll the contents of our CGLayer +void BackingStoreMac::ScrollBackingStore(int dx, int dy, + const gfx::Rect& clip_rect, + const gfx::Size& view_size) { + DCHECK_NE(static_cast<bool>(cg_layer()), static_cast<bool>(cg_bitmap())); + + // "Scroll" the contents of the layer by creating a new CGLayer, + // copying the contents of the old one into the new one offset by the scroll + // amount, swapping in the new CGLayer, and then painting in the new data. + // + // The Windows code always sets the whole backing store as the source of the + // scroll. Thus, we only have to worry about pixels which will end up inside + // the clipping rectangle. (Note that the clipping rectangle is not + // translated by the scroll.) + + // We assume |clip_rect| is contained within the backing store. + DCHECK(clip_rect.bottom() <= size().height()); + DCHECK(clip_rect.right() <= size().width()); + + if ((dx || dy) && abs(dx) < size().width() && abs(dy) < size().height()) { + if (cg_layer()) { + // See http://crbug.com/45553 , comments 5 and 6. + static bool needs_layer_workaround = NeedsLayerWorkaround(); + + base::mac::ScopedCFTypeRef<CGLayerRef> new_layer; + CGContextRef layer; + + if (needs_layer_workaround) { + new_layer.reset(CreateCGLayer()); + // If the current view is in a window, the replacement must be too. + DCHECK(new_layer); + + layer = CGLayerGetContext(new_layer); + CGContextDrawLayerAtPoint(layer, CGPointMake(0, 0), cg_layer()); + } else { + layer = CGLayerGetContext(cg_layer()); + } + + CGContextSaveGState(layer); + CGContextClipToRect(layer, + CGRectMake(clip_rect.x(), + size().height() - clip_rect.bottom(), + clip_rect.width(), + clip_rect.height())); + CGContextDrawLayerAtPoint(layer, CGPointMake(dx, -dy), cg_layer()); + CGContextRestoreGState(layer); + + if (needs_layer_workaround) + cg_layer_.swap(new_layer); + } else { + // We don't have a layer, so scroll the contents of the CGBitmapContext. + base::mac::ScopedCFTypeRef<CGImageRef> bitmap_image( + CGBitmapContextCreateImage(cg_bitmap_)); + CGContextSaveGState(cg_bitmap_); + CGContextClipToRect(cg_bitmap_, + CGRectMake(clip_rect.x(), + size().height() - clip_rect.bottom(), + clip_rect.width(), + clip_rect.height())); + CGContextDrawImage(cg_bitmap_, + CGRectMake(dx, -dy, size().width(), size().height()), + bitmap_image); + CGContextRestoreGState(cg_bitmap_); + } + } +} + +CGLayerRef BackingStoreMac::CreateCGLayer() { + // The CGLayer should be optimized for drawing into the containing window, + // so extract a CGContext corresponding to the window to be passed to + // CGLayerCreateWithContext. + NSWindow* window = [render_widget_host()->view()->GetNativeView() window]; + if ([window windowNumber] <= 0) { + // This catches a nil |window|, as well as windows that exist but that + // aren't yet connected to WindowServer. + return NULL; + } + + NSGraphicsContext* ns_context = [window graphicsContext]; + DCHECK(ns_context); + + CGContextRef cg_context = static_cast<CGContextRef>( + [ns_context graphicsPort]); + DCHECK(cg_context); + + CGLayerRef layer = CGLayerCreateWithContext(cg_context, + size().ToCGSize(), + NULL); + DCHECK(layer); + + return layer; +} + +CGContextRef BackingStoreMac::CreateCGBitmapContext() { + // A CGBitmapContext serves as a stand-in for the layer before the view is + // in a containing window. + CGContextRef context = CGBitmapContextCreate(NULL, + size().width(), size().height(), + 8, size().width() * 4, + base::mac::GetSystemColorSpace(), + kCGImageAlphaPremultipliedFirst | + kCGBitmapByteOrder32Host); + DCHECK(context); + + return context; +} diff --git a/content/browser/renderer_host/backing_store_manager.cc b/content/browser/renderer_host/backing_store_manager.cc new file mode 100644 index 0000000..bd50021 --- /dev/null +++ b/content/browser/renderer_host/backing_store_manager.cc @@ -0,0 +1,287 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "content/browser/renderer_host/backing_store_manager.h" + +#include "base/sys_info.h" +#include "base/command_line.h" +#include "chrome/common/chrome_switches.h" +#include "content/browser/renderer_host/backing_store.h" +#include "content/browser/renderer_host/render_widget_host.h" +#include "chrome/common/chrome_constants.h" +#include "chrome/common/mru_cache.h" +#include "chrome/common/notification_service.h" + +namespace { + +// There are two separate caches, |large_cache| and |small_cache|. large_cache +// is meant for large items (tabs, popup windows), while small_cache is meant +// for small items (extension toolstrips and buttons, etc.). The idea is that +// we'll almost always try to evict from large_cache first since small_cache +// items will tend to be visible more of the time. +typedef OwningMRUCache<RenderWidgetHost*, BackingStore*> BackingStoreCache; +static BackingStoreCache* large_cache = NULL; +static BackingStoreCache* small_cache = NULL; + +// Threshold is based on a single large-monitor-width toolstrip. +// (32bpp, 32 pixels high, 1920 pixels wide) +// TODO(erikkay) 32bpp assumption isn't great. +const size_t kSmallThreshold = 4 * 32 * 1920; + +// Pick a large monitor size to use as a multiplier. This is multiplied by the +// max number of large backing stores (usually tabs) to pick a ceiling on the +// max memory to use. +// TODO(erikkay) Perhaps we should actually use monitor size? That way we +// could make an assertion like "worse case, there are two tabs in the cache". +// However, the small_cache might mess up these calculations a bit. +// TODO(erikkay) 32bpp assumption isn't great. +const size_t kMemoryMultiplier = 4 * 1920 * 1200; // ~9MB + +// The maximum number of large BackingStoreCache objects (tabs) to use. +// Use a minimum of 2, and add one for each 256MB of physical memory you have. +// Cap at 5, the thinking being that even if you have a gigantic amount of +// RAM, there's a limit to how much caching helps beyond a certain number +// of tabs. If users *really* want unlimited stores, allow it via the +// --disable-backing-store-limit flag. +static size_t MaxNumberOfBackingStores() { + static bool unlimited = false; + const CommandLine& command = *CommandLine::ForCurrentProcess(); + unlimited = command.HasSwitch(switches::kDisableBackingStoreLimit); + + + if (unlimited) { + // 100 isn't truly unlimited, but given that backing stores count against + // GDI memory, it's well past any reasonable number. Many systems will + // begin to fail in strange ways well before they hit 100 stores. + return 100; + } else { + return std::min(5, 2 + (base::SysInfo::AmountOfPhysicalMemoryMB() / 256)); + } +} + +// The maximum about of memory to use for all BackingStoreCache object combined. +static size_t MaxBackingStoreMemory() { + // Compute in terms of the number of large monitor's worth of backing-store. + return MaxNumberOfBackingStores() * kMemoryMultiplier; +} + +// Expires the given |backing_store| from |cache|. +void ExpireBackingStoreAt(BackingStoreCache* cache, + BackingStoreCache::iterator backing_store) { + NotificationService::current()->Notify( + NotificationType::RENDER_WIDGET_HOST_WILL_DESTROY_BACKING_STORE, + Source<RenderWidgetHost>(backing_store->first), + Details<BackingStore>(backing_store->second)); + cache->Erase(backing_store); +} + +size_t ExpireLastBackingStore(BackingStoreCache* cache) { + if (cache->size() < 1) + return 0; + + // Crazy C++ alert: rbegin.base() is a forward iterator pointing to end(), + // so we need to do -- to move one back to the actual last item. + BackingStoreCache::iterator entry = --cache->rbegin().base(); + size_t entry_size = entry->second->MemorySize(); + ExpireBackingStoreAt(cache, entry); + return entry_size; +} + +void CreateCacheSpace(size_t size) { + // Given a request for |size|, first free from the large cache (until there's + // only one item left) and then do the same from the small cache if we still + // don't have enough. + while (size > 0 && (large_cache->size() > 1 || small_cache->size() > 1)) { + BackingStoreCache* cache = + (large_cache->size() > 1) ? large_cache : small_cache; + while (size > 0 && cache->size() > 1) { + size_t entry_size = ExpireLastBackingStore(cache); + if (size > entry_size) + size -= entry_size; + else + size = 0; + } + } + DCHECK(size == 0); +} + +// Creates the backing store for the host based on the dimensions passed in. +// Removes the existing backing store if there is one. +BackingStore* CreateBackingStore(RenderWidgetHost* host, + const gfx::Size& backing_store_size) { + // Remove any existing backing store in case we're replacing it. + BackingStoreManager::RemoveBackingStore(host); + + if (!large_cache) { + large_cache = new BackingStoreCache(BackingStoreCache::NO_AUTO_EVICT); + small_cache = new BackingStoreCache(BackingStoreCache::NO_AUTO_EVICT); + } + + // TODO(erikkay) 32bpp is not always accurate + size_t new_mem = backing_store_size.GetArea() * 4; + size_t current_mem = BackingStoreManager::MemorySize(); + size_t max_mem = MaxBackingStoreMemory(); + DCHECK(new_mem < max_mem); + if (current_mem + new_mem > max_mem) { + // Need to remove old backing stores to make room for the new one. We + // don't want to do this when the backing store is being replace by a new + // one for the same tab, but this case won't get called then: we'll have + // removed the old one in the RemoveBackingStore above, and the cache + // won't be over-sized. + CreateCacheSpace((current_mem + new_mem) - max_mem); + } + DCHECK((BackingStoreManager::MemorySize() + new_mem) <= max_mem); + + BackingStoreCache* cache; + if (new_mem > kSmallThreshold) { + // Limit the number of large backing stores (tabs) to the memory tier number + // (between 2-5). While we allow a larger amount of memory for people who + // have large windows, this means that those who use small browser windows + // won't ever cache more than 5 tabs, so they pay a smaller memory cost. + if (large_cache->size() >= MaxNumberOfBackingStores()) + ExpireLastBackingStore(large_cache); + cache = large_cache; + } else { + cache = small_cache; + } + BackingStore* backing_store = host->AllocBackingStore(backing_store_size); + if (backing_store) + cache->Put(host, backing_store); + return backing_store; +} + +int ComputeTotalArea(const std::vector<gfx::Rect>& rects) { + // We assume that the given rects are non-overlapping, which is a property of + // the paint rects generated by the PaintAggregator. +#ifndef NDEBUG + for (size_t i = 0; i < rects.size(); ++i) { + for (size_t j = 0; j < rects.size(); ++j) { + if (i != j) + DCHECK(!rects[i].Intersects(rects[j])); + } + } +#endif + int area = 0; + for (size_t i = 0; i < rects.size(); ++i) + area += rects[i].size().GetArea(); + return area; +} + +} // namespace + +// BackingStoreManager --------------------------------------------------------- + +// static +BackingStore* BackingStoreManager::GetBackingStore( + RenderWidgetHost* host, + const gfx::Size& desired_size) { + BackingStore* backing_store = Lookup(host); + if (backing_store) { + // If we already have a backing store, then make sure it is the correct + // size. + if (backing_store->size() == desired_size) + return backing_store; + backing_store = NULL; + } + + return backing_store; +} + +// static +void BackingStoreManager::PrepareBackingStore( + RenderWidgetHost* host, + const gfx::Size& backing_store_size, + TransportDIB::Id bitmap, + const gfx::Rect& bitmap_rect, + const std::vector<gfx::Rect>& copy_rects, + bool* needs_full_paint) { + BackingStore* backing_store = GetBackingStore(host, backing_store_size); + if (!backing_store) { + // We need to get Webkit to generate a new paint here, as we + // don't have a previous snapshot. + if (bitmap_rect.size() != backing_store_size || + bitmap_rect.x() != 0 || bitmap_rect.y() != 0 || + ComputeTotalArea(copy_rects) != backing_store_size.GetArea() || + !(backing_store = CreateBackingStore(host, backing_store_size))) { + DCHECK(needs_full_paint != NULL); + *needs_full_paint = true; + // Makes no sense to paint the transport dib if we are going + // to request a full paint. + return; + } + } + + backing_store->PaintToBackingStore(host->process(), bitmap, + bitmap_rect, copy_rects); +} + +// static +BackingStore* BackingStoreManager::Lookup(RenderWidgetHost* host) { + if (large_cache) { + BackingStoreCache::iterator it = large_cache->Get(host); + if (it != large_cache->end()) + return it->second; + + // This moves host to the front of the MRU. + it = small_cache->Get(host); + if (it != small_cache->end()) + return it->second; + } + return NULL; +} + +// static +void BackingStoreManager::RemoveBackingStore(RenderWidgetHost* host) { + if (!large_cache) + return; + + BackingStoreCache* cache = large_cache; + BackingStoreCache::iterator it = cache->Peek(host); + if (it == cache->end()) { + cache = small_cache; + it = cache->Peek(host); + if (it == cache->end()) + return; + } + cache->Erase(it); +} + +// static +void BackingStoreManager::RemoveAllBackingStores() { + if (large_cache) { + large_cache->Clear(); + small_cache->Clear(); + } +} + +// static +bool BackingStoreManager::ExpireBackingStoreForTest(RenderWidgetHost* host) { + BackingStoreCache* cache = large_cache; + + BackingStoreCache::iterator it = cache->Peek(host); + if (it == cache->end()) { + cache = small_cache; + it = cache->Peek(host); + if (it == cache->end()) + return false; + } + ExpireBackingStoreAt(cache, it); + return true; +} + +// static +size_t BackingStoreManager::MemorySize() { + if (!large_cache) + return 0; + + size_t mem = 0; + BackingStoreCache::iterator it; + for (it = large_cache->begin(); it != large_cache->end(); ++it) + mem += it->second->MemorySize(); + + for (it = small_cache->begin(); it != small_cache->end(); ++it) + mem += it->second->MemorySize(); + + return mem; +} diff --git a/content/browser/renderer_host/backing_store_manager.h b/content/browser/renderer_host/backing_store_manager.h new file mode 100644 index 0000000..bc09fc7 --- /dev/null +++ b/content/browser/renderer_host/backing_store_manager.h @@ -0,0 +1,79 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CONTENT_BROWSER_RENDERER_HOST_BACKING_STORE_MANAGER_H_ +#define CONTENT_BROWSER_RENDERER_HOST_BACKING_STORE_MANAGER_H_ +#pragma once + +#include <vector> + +#include "app/surface/transport_dib.h" +#include "base/basictypes.h" +#include "base/process.h" +#include "ui/gfx/rect.h" +#include "ui/gfx/size.h" + +class BackingStore; +class RenderWidgetHost; + +// This class manages backing stores in the browsr. Every RenderWidgetHost is +// associated with a backing store which it requests from this class. The +// hosts don't maintain any references to the backing stores. These backing +// stores are maintained in a cache which can be trimmed as needed. +class BackingStoreManager { + public: + // Returns a backing store which matches the desired dimensions. + // + // backing_store_rect + // The desired backing store dimensions. + // Returns a pointer to the backing store on success, NULL on failure. + static BackingStore* GetBackingStore(RenderWidgetHost* host, + const gfx::Size& desired_size); + + // Makes a backing store which is fully ready for consumption, i.e. the + // bitmap from the renderer has been copied into the backing store. + // + // backing_store_size + // The desired backing store dimensions. + // bitmap_section + // The bitmap section from the renderer. + // bitmap_rect + // The rect to be painted into the backing store + // needs_full_paint + // Set if we need to send out a request to paint the view + // to the renderer. + static void PrepareBackingStore( + RenderWidgetHost* host, + const gfx::Size& backing_store_size, + TransportDIB::Id bitmap, + const gfx::Rect& bitmap_rect, + const std::vector<gfx::Rect>& copy_rects, + bool* needs_full_paint); + + // Returns a matching backing store for the host. + // Returns NULL if we fail to find one. + static BackingStore* Lookup(RenderWidgetHost* host); + + // Removes the backing store for the host. + static void RemoveBackingStore(RenderWidgetHost* host); + + // Removes all backing stores. + static void RemoveAllBackingStores(); + + // Expires the given backing store. This emulates something getting evicted + // from the cache for the purpose of testing. Returns true if the host was + // removed, false if it wasn't found. + static bool ExpireBackingStoreForTest(RenderWidgetHost* host); + + // Current size in bytes of the backing store cache. + static size_t MemorySize(); + + private: + // Not intended for instantiation. + BackingStoreManager() {} + + DISALLOW_COPY_AND_ASSIGN(BackingStoreManager); +}; + +#endif // CONTENT_BROWSER_RENDERER_HOST_BACKING_STORE_MANAGER_H_ diff --git a/content/browser/renderer_host/backing_store_skia.cc b/content/browser/renderer_host/backing_store_skia.cc new file mode 100644 index 0000000..f756504 --- /dev/null +++ b/content/browser/renderer_host/backing_store_skia.cc @@ -0,0 +1,105 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "content/browser/renderer_host/backing_store_skia.h" + +#include "content/browser/renderer_host/render_process_host.h" +#include "skia/ext/platform_canvas.h" +#include "third_party/skia/include/core/SkCanvas.h" +#include "ui/gfx/canvas.h" +#include "ui/gfx/canvas_skia.h" +#include "ui/gfx/rect.h" + +// Assume that somewhere along the line, someone will do width * height * 4 +// with signed numbers. If the maximum value is 2**31, then 2**31 / 4 = +// 2**29 and floor(sqrt(2**29)) = 23170. + +// Max height and width for layers +static const int kMaxVideoLayerSize = 23170; + +BackingStoreSkia::BackingStoreSkia(RenderWidgetHost* widget, + const gfx::Size& size) + : BackingStore(widget, size) { + bitmap_.setConfig(SkBitmap::kARGB_8888_Config, size.width(), size.height()); + bitmap_.allocPixels(); + canvas_.reset(new SkCanvas(bitmap_)); +} + +BackingStoreSkia::~BackingStoreSkia() { +} + +void BackingStoreSkia::SkiaShowRect(const gfx::Point& point, + gfx::Canvas* canvas) { + canvas->AsCanvasSkia()->drawBitmap(bitmap_, + SkIntToScalar(point.x()), SkIntToScalar(point.y())); +} + +size_t BackingStoreSkia::MemorySize() { + // NOTE: The computation may be different when the canvas is a subrectangle of + // a larger bitmap. + return size().GetArea() * 4; +} + +void BackingStoreSkia::PaintToBackingStore( + RenderProcessHost* process, + TransportDIB::Id bitmap, + const gfx::Rect& bitmap_rect, + const std::vector<gfx::Rect>& copy_rects) { + if (bitmap_rect.IsEmpty()) + return; + + const int width = bitmap_rect.width(); + const int height = bitmap_rect.height(); + + if (width <= 0 || width > kMaxVideoLayerSize || + height <= 0 || height > kMaxVideoLayerSize) + return; + + TransportDIB* dib = process->GetTransportDIB(bitmap); + if (!dib) + return; + + scoped_ptr<skia::PlatformCanvas> p_canvas( + dib->GetPlatformCanvas(width, height)); + for (size_t i = 0; i < copy_rects.size(); i++) { + const gfx::Rect& copy_rect = copy_rects[i]; + int x = copy_rect.x() - bitmap_rect.x(); + int y = copy_rect.y() - bitmap_rect.y(); + int w = copy_rect.width(); + int h = copy_rect.height(); + SkIRect srcrect = SkIRect::MakeXYWH(x, y, w, h); + SkRect dstrect = SkRect::MakeXYWH( + SkIntToScalar(copy_rect.x()), SkIntToScalar(copy_rect.y()), + SkIntToScalar(w), SkIntToScalar(h)); + SkBitmap b = p_canvas->getTopPlatformDevice().accessBitmap(false); + canvas_.get()->drawBitmapRect(b, &srcrect, dstrect); + } +} + +void BackingStoreSkia::ScrollBackingStore(int dx, int dy, + const gfx::Rect& clip_rect, + const gfx::Size& view_size) { + int x = std::min(clip_rect.x(), clip_rect.x() - dx); + int y = std::min(clip_rect.y(), clip_rect.y() - dy); + int w = clip_rect.width() + abs(dx); + int h = clip_rect.height() + abs(dy); + SkIRect rect = SkIRect::MakeXYWH(x, y, w, h); + bitmap_.scrollRect(&rect, dx, dy); +} + +bool BackingStoreSkia::CopyFromBackingStore(const gfx::Rect& rect, + skia::PlatformCanvas* output) { + const int width = std::min(size().width(), rect.width()); + const int height = std::min(size().height(), rect.height()); + if (!output->initialize(width, height, true)) + return false; + + SkBitmap bitmap = output->getTopPlatformDevice().accessBitmap(true); + SkIRect skrect = SkIRect::MakeXYWH(rect.x(), rect.y(), width, height); + SkBitmap b; + if (!canvas_->readPixels(skrect, &b)) + return false; + output->writePixels(b, rect.x(), rect.y()); + return true; +} diff --git a/content/browser/renderer_host/backing_store_skia.h b/content/browser/renderer_host/backing_store_skia.h new file mode 100644 index 0000000..9f71fb0 --- /dev/null +++ b/content/browser/renderer_host/backing_store_skia.h @@ -0,0 +1,51 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CONTENT_BROWSER_RENDERER_HOST_BACKING_STORE_SKIA_H_ +#define CONTENT_BROWSER_RENDERER_HOST_BACKING_STORE_SKIA_H_ +#pragma once + +#include "base/scoped_ptr.h" +#include "content/browser/renderer_host/backing_store.h" +#include "third_party/skia/include/core/SkBitmap.h" + +class SkCanvas; + +namespace gfx { +class Point; +class Canvas; +} + +// A backing store that uses skia. This is a temporary backing store used by +// RenderWidgetHostViewViews. In time, only GPU rendering will be used for +// RWHVV, and then this backing store will be removed. +class BackingStoreSkia : public BackingStore { + public: + BackingStoreSkia(RenderWidgetHost* widget, const gfx::Size& size); + virtual ~BackingStoreSkia(); + + void SkiaShowRect(const gfx::Point& point, gfx::Canvas* canvas); + + // BackingStore implementation. + virtual size_t MemorySize(); + virtual void PaintToBackingStore( + RenderProcessHost* process, + TransportDIB::Id bitmap, + const gfx::Rect& bitmap_rect, + const std::vector<gfx::Rect>& copy_rects); + virtual bool CopyFromBackingStore(const gfx::Rect& rect, + skia::PlatformCanvas* output); + virtual void ScrollBackingStore(int dx, int dy, + const gfx::Rect& clip_rect, + const gfx::Size& view_size); + + private: + SkBitmap bitmap_; + + scoped_ptr<SkCanvas> canvas_; + + DISALLOW_COPY_AND_ASSIGN(BackingStoreSkia); +}; + +#endif // CONTENT_BROWSER_RENDERER_HOST_BACKING_STORE_SKIA_H_ diff --git a/content/browser/renderer_host/backing_store_win.cc b/content/browser/renderer_host/backing_store_win.cc new file mode 100644 index 0000000..0023b51 --- /dev/null +++ b/content/browser/renderer_host/backing_store_win.cc @@ -0,0 +1,175 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "content/browser/renderer_host/backing_store_win.h" + +#include "app/surface/transport_dib.h" +#include "base/command_line.h" +#include "chrome/common/chrome_switches.h" +#include "content/browser/renderer_host/render_process_host.h" +#include "content/browser/renderer_host/render_widget_host.h" +#include "skia/ext/platform_canvas.h" +#include "ui/gfx/gdi_util.h" + +namespace { + +// Creates a dib conforming to the height/width/section parameters passed in. +HANDLE CreateDIB(HDC dc, int width, int height, int color_depth) { + BITMAPV5HEADER hdr = {0}; + ZeroMemory(&hdr, sizeof(BITMAPV5HEADER)); + + // These values are shared with gfx::PlatformDevice + hdr.bV5Size = sizeof(BITMAPINFOHEADER); + hdr.bV5Width = width; + hdr.bV5Height = -height; // minus means top-down bitmap + hdr.bV5Planes = 1; + hdr.bV5BitCount = color_depth; + hdr.bV5Compression = BI_RGB; // no compression + hdr.bV5SizeImage = 0; + hdr.bV5XPelsPerMeter = 1; + hdr.bV5YPelsPerMeter = 1; + hdr.bV5ClrUsed = 0; + hdr.bV5ClrImportant = 0; + + if (BackingStoreWin::ColorManagementEnabled()) { + hdr.bV5CSType = LCS_sRGB; + hdr.bV5Intent = LCS_GM_IMAGES; + } + + void* data = NULL; + HANDLE dib = CreateDIBSection(dc, reinterpret_cast<BITMAPINFO*>(&hdr), + 0, &data, NULL, 0); + DCHECK(data); + return dib; +} + +void CallStretchDIBits(HDC hdc, int dest_x, int dest_y, int dest_w, int dest_h, + int src_x, int src_y, int src_w, int src_h, void* pixels, + const BITMAPINFO* bitmap_info) { + // When blitting a rectangle that touches the bottom, left corner of the + // bitmap, StretchDIBits looks at it top-down! For more details, see + // http://wiki.allegro.cc/index.php?title=StretchDIBits. + int rv; + int bitmap_h = -bitmap_info->bmiHeader.biHeight; + int bottom_up_src_y = bitmap_h - src_y - src_h; + if (bottom_up_src_y == 0 && src_x == 0 && src_h != bitmap_h) { + rv = StretchDIBits(hdc, + dest_x, dest_h + dest_y - 1, dest_w, -dest_h, + src_x, bitmap_h - src_y + 1, src_w, -src_h, + pixels, bitmap_info, DIB_RGB_COLORS, SRCCOPY); + } else { + rv = StretchDIBits(hdc, + dest_x, dest_y, dest_w, dest_h, + src_x, bottom_up_src_y, src_w, src_h, + pixels, bitmap_info, DIB_RGB_COLORS, SRCCOPY); + } + DCHECK(rv != GDI_ERROR); +} + +} // namespace + +BackingStoreWin::BackingStoreWin(RenderWidgetHost* widget, const gfx::Size& size) + : BackingStore(widget, size), + backing_store_dib_(NULL), + original_bitmap_(NULL) { + HDC screen_dc = ::GetDC(NULL); + color_depth_ = ::GetDeviceCaps(screen_dc, BITSPIXEL); + // Color depths less than 16 bpp require a palette to be specified. Instead, + // we specify the desired color depth as 16 which lets the OS to come up + // with an approximation. + if (color_depth_ < 16) + color_depth_ = 16; + hdc_ = CreateCompatibleDC(screen_dc); + ReleaseDC(NULL, screen_dc); +} + +BackingStoreWin::~BackingStoreWin() { + DCHECK(hdc_); + if (original_bitmap_) { + SelectObject(hdc_, original_bitmap_); + } + if (backing_store_dib_) { + DeleteObject(backing_store_dib_); + backing_store_dib_ = NULL; + } + DeleteDC(hdc_); +} + +// static +bool BackingStoreWin::ColorManagementEnabled() { + static bool enabled = false; + static bool checked = false; + if (!checked) { + checked = true; + const CommandLine& command = *CommandLine::ForCurrentProcess(); + enabled = command.HasSwitch(switches::kEnableMonitorProfile); + } + return enabled; +} + +size_t BackingStoreWin::MemorySize() { + return size().GetArea() * (color_depth_ / 8); +} + +void BackingStoreWin::PaintToBackingStore( + RenderProcessHost* process, + TransportDIB::Id bitmap, + const gfx::Rect& bitmap_rect, + const std::vector<gfx::Rect>& copy_rects) { + if (!backing_store_dib_) { + backing_store_dib_ = CreateDIB(hdc_, size().width(), + size().height(), color_depth_); + if (!backing_store_dib_) { + NOTREACHED(); + return; + } + original_bitmap_ = SelectObject(hdc_, backing_store_dib_); + } + + TransportDIB* dib = process->GetTransportDIB(bitmap); + if (!dib) + return; + + BITMAPINFOHEADER hdr; + gfx::CreateBitmapHeader(bitmap_rect.width(), bitmap_rect.height(), &hdr); + // Account for a bitmap_rect that exceeds the bounds of our view + gfx::Rect view_rect(size()); + + for (size_t i = 0; i < copy_rects.size(); i++) { + gfx::Rect paint_rect = view_rect.Intersect(copy_rects[i]); + CallStretchDIBits(hdc_, + paint_rect.x(), + paint_rect.y(), + paint_rect.width(), + paint_rect.height(), + paint_rect.x() - bitmap_rect.x(), + paint_rect.y() - bitmap_rect.y(), + paint_rect.width(), + paint_rect.height(), + dib->memory(), + reinterpret_cast<BITMAPINFO*>(&hdr)); + } +} + +bool BackingStoreWin::CopyFromBackingStore(const gfx::Rect& rect, + skia::PlatformCanvas* output) { + if (!output->initialize(rect.width(), rect.height(), true)) + return false; + + HDC temp_dc = output->beginPlatformPaint(); + BitBlt(temp_dc, 0, 0, rect.width(), rect.height(), + hdc(), rect.x(), rect.y(), SRCCOPY); + output->endPlatformPaint(); + return true; +} + +void BackingStoreWin::ScrollBackingStore(int dx, int dy, + const gfx::Rect& clip_rect, + const gfx::Size& view_size) { + RECT damaged_rect, r = clip_rect.ToRECT(); + ScrollDC(hdc_, dx, dy, NULL, &r, NULL, &damaged_rect); + + // TODO(darin): this doesn't work if dx and dy are both non-zero! + DCHECK(dx == 0 || dy == 0); +} diff --git a/content/browser/renderer_host/backing_store_win.h b/content/browser/renderer_host/backing_store_win.h new file mode 100644 index 0000000..197de6bc --- /dev/null +++ b/content/browser/renderer_host/backing_store_win.h @@ -0,0 +1,52 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CONTENT_BROWSER_RENDERER_HOST_BACKING_STORE_WIN_H_ +#define CONTENT_BROWSER_RENDERER_HOST_BACKING_STORE_WIN_H_ +#pragma once + +#include <windows.h> + +#include "base/basictypes.h" +#include "content/browser/renderer_host/backing_store.h" + +class BackingStoreWin : public BackingStore { + public: + BackingStoreWin(RenderWidgetHost* widget, const gfx::Size& size); + virtual ~BackingStoreWin(); + + HDC hdc() { return hdc_; } + + // Returns true if we should convert to the monitor profile when painting. + static bool ColorManagementEnabled(); + + // BackingStore implementation. + virtual size_t MemorySize(); + virtual void PaintToBackingStore(RenderProcessHost* process, + TransportDIB::Id bitmap, + const gfx::Rect& bitmap_rect, + const std::vector<gfx::Rect>& copy_rects); + virtual bool CopyFromBackingStore(const gfx::Rect& rect, + skia::PlatformCanvas* output); + virtual void ScrollBackingStore(int dx, int dy, + const gfx::Rect& clip_rect, + const gfx::Size& view_size); + + private: + // The backing store dc. + HDC hdc_; + + // Handle to the backing store dib. + HANDLE backing_store_dib_; + + // Handle to the original bitmap in the dc. + HANDLE original_bitmap_; + + // Number of bits per pixel of the screen. + int color_depth_; + + DISALLOW_COPY_AND_ASSIGN(BackingStoreWin); +}; + +#endif // CONTENT_BROWSER_RENDERER_HOST_BACKING_STORE_WIN_H_ diff --git a/content/browser/renderer_host/backing_store_x.cc b/content/browser/renderer_host/backing_store_x.cc new file mode 100644 index 0000000..5e1e33a --- /dev/null +++ b/content/browser/renderer_host/backing_store_x.cc @@ -0,0 +1,477 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "content/browser/renderer_host/backing_store_x.h" + +#include <cairo-xlib.h> +#include <gtk/gtk.h> +#include <stdlib.h> +#include <sys/ipc.h> +#include <sys/shm.h> + +#if defined(OS_OPENBSD) || defined(OS_FREEBSD) +#include <sys/endian.h> +#endif + +#include <algorithm> +#include <utility> +#include <limits> + +#include "app/surface/transport_dib.h" +#include "base/compiler_specific.h" +#include "base/logging.h" +#include "base/metrics/histogram.h" +#include "base/time.h" +#include "content/browser/renderer_host/render_process_host.h" +#include "skia/ext/platform_canvas.h" +#include "third_party/skia/include/core/SkBitmap.h" +#include "ui/base/x/x11_util.h" +#include "ui/base/x/x11_util_internal.h" +#include "ui/gfx/rect.h" + +// Assume that somewhere along the line, someone will do width * height * 4 +// with signed numbers. If the maximum value is 2**31, then 2**31 / 4 = +// 2**29 and floor(sqrt(2**29)) = 23170. + +// Max height and width for layers +static const int kMaxVideoLayerSize = 23170; + + +// X Backing Stores: +// +// Unlike Windows, where the backing store is kept in heap memory, we keep our +// backing store in the X server, as a pixmap. Thus expose events just require +// instructing the X server to copy from the backing store to the window. +// +// The backing store is in the same format as the visual which our main window +// is using. Bitmaps from the renderer are uploaded to the X server, either via +// shared memory or over the wire, and XRENDER is used to convert them to the +// correct format for the backing store. + +// Destroys the image and the associated shared memory structures. This is a +// helper function for code using shared memory. +static void DestroySharedImage(Display* display, + XImage* image, + XShmSegmentInfo* shminfo) { + XShmDetach(display, shminfo); + XDestroyImage(image); + shmdt(shminfo->shmaddr); +} + +BackingStoreX::BackingStoreX(RenderWidgetHost* widget, + const gfx::Size& size, + void* visual, + int depth) + : BackingStore(widget, size), + display_(ui::GetXDisplay()), + shared_memory_support_(ui::QuerySharedMemorySupport(display_)), + use_render_(ui::QueryRenderSupport(display_)), + visual_(visual), + visual_depth_(depth), + root_window_(ui::GetX11RootWindow()) { +#if defined(OS_OPENBSD) || defined(OS_FREEBSD) + COMPILE_ASSERT(_BYTE_ORDER == _LITTLE_ENDIAN, assumes_little_endian); +#else + COMPILE_ASSERT(__BYTE_ORDER == __LITTLE_ENDIAN, assumes_little_endian); +#endif + + pixmap_ = XCreatePixmap(display_, root_window_, + size.width(), size.height(), depth); + + if (use_render_) { + picture_ = XRenderCreatePicture( + display_, pixmap_, + ui::GetRenderVisualFormat(display_, + static_cast<Visual*>(visual)), + 0, NULL); + pixmap_bpp_ = 0; + } else { + picture_ = 0; + pixmap_bpp_ = ui::BitsPerPixelForPixmapDepth(display_, depth); + } + + pixmap_gc_ = XCreateGC(display_, pixmap_, 0, NULL); +} + +BackingStoreX::BackingStoreX(RenderWidgetHost* widget, const gfx::Size& size) + : BackingStore(widget, size), + display_(NULL), + shared_memory_support_(ui::SHARED_MEMORY_NONE), + use_render_(false), + pixmap_bpp_(0), + visual_(NULL), + visual_depth_(-1), + root_window_(0), + pixmap_(0), + picture_(0), + pixmap_gc_(NULL) { +} + +BackingStoreX::~BackingStoreX() { + // In unit tests, display_ may be NULL. + if (!display_) + return; + + XRenderFreePicture(display_, picture_); + XFreePixmap(display_, pixmap_); + XFreeGC(display_, static_cast<GC>(pixmap_gc_)); +} + +size_t BackingStoreX::MemorySize() { + if (!use_render_) + return size().GetArea() * (pixmap_bpp_ / 8); + else + return size().GetArea() * 4; +} + +void BackingStoreX::PaintRectWithoutXrender( + TransportDIB* bitmap, + const gfx::Rect& bitmap_rect, + const std::vector<gfx::Rect>& copy_rects) { + const int width = bitmap_rect.width(); + const int height = bitmap_rect.height(); + Pixmap pixmap = XCreatePixmap(display_, root_window_, width, height, + visual_depth_); + + // Draw ARGB transport DIB onto our pixmap. + ui::PutARGBImage(display_, visual_, visual_depth_, pixmap, + pixmap_gc_, static_cast<uint8*>(bitmap->memory()), + width, height); + + for (size_t i = 0; i < copy_rects.size(); i++) { + const gfx::Rect& copy_rect = copy_rects[i]; + XCopyArea(display_, + pixmap, // src + pixmap_, // dest + static_cast<GC>(pixmap_gc_), // gc + copy_rect.x() - bitmap_rect.x(), // src_x + copy_rect.y() - bitmap_rect.y(), // src_y + copy_rect.width(), // width + copy_rect.height(), // height + copy_rect.x(), // dest_x + copy_rect.y()); // dest_y + } + + XFreePixmap(display_, pixmap); +} + +void BackingStoreX::PaintToBackingStore( + RenderProcessHost* process, + TransportDIB::Id bitmap, + const gfx::Rect& bitmap_rect, + const std::vector<gfx::Rect>& copy_rects) { + if (!display_) + return; + + if (bitmap_rect.IsEmpty()) + return; + + const int width = bitmap_rect.width(); + const int height = bitmap_rect.height(); + + if (width <= 0 || width > kMaxVideoLayerSize || + height <= 0 || height > kMaxVideoLayerSize) + return; + + TransportDIB* dib = process->GetTransportDIB(bitmap); + if (!dib) + return; + + if (!use_render_) + return PaintRectWithoutXrender(dib, bitmap_rect, copy_rects); + + Picture picture; + Pixmap pixmap; + + if (shared_memory_support_ == ui::SHARED_MEMORY_PIXMAP) { + XShmSegmentInfo shminfo = {0}; + shminfo.shmseg = dib->MapToX(display_); + + // The NULL in the following is the |data| pointer: this is an artifact of + // Xlib trying to be helpful, rather than just exposing the X protocol. It + // assumes that we have the shared memory segment mapped into our memory, + // which we don't, and it's trying to calculate an offset by taking the + // difference between the |data| pointer and the address of the mapping in + // |shminfo|. Since both are NULL, the offset will be calculated to be 0, + // which is correct for us. + pixmap = XShmCreatePixmap(display_, root_window_, NULL, &shminfo, + width, height, 32); + } else { + // We don't have shared memory pixmaps. Fall back to creating a pixmap + // ourselves and putting an image on it. + pixmap = XCreatePixmap(display_, root_window_, width, height, 32); + GC gc = XCreateGC(display_, pixmap, 0, NULL); + + if (shared_memory_support_ == ui::SHARED_MEMORY_PUTIMAGE) { + const XID shmseg = dib->MapToX(display_); + + XShmSegmentInfo shminfo; + memset(&shminfo, 0, sizeof(shminfo)); + shminfo.shmseg = shmseg; + shminfo.shmaddr = static_cast<char*>(dib->memory()); + + XImage* image = XShmCreateImage(display_, static_cast<Visual*>(visual_), + 32, ZPixmap, + shminfo.shmaddr, &shminfo, + width, height); + + // This code path is important for performance and we have found that + // different techniques work better on different platforms. See + // http://code.google.com/p/chromium/issues/detail?id=44124. + // + // Checking for ARM is an approximation, but it seems to be a good one so + // far. +#if defined(ARCH_CPU_ARM_FAMILY) + for (size_t i = 0; i < copy_rects.size(); i++) { + const gfx::Rect& copy_rect = copy_rects[i]; + XShmPutImage(display_, pixmap, gc, image, + copy_rect.x() - bitmap_rect.x(), /* source x */ + copy_rect.y() - bitmap_rect.y(), /* source y */ + copy_rect.x() - bitmap_rect.x(), /* dest x */ + copy_rect.y() - bitmap_rect.y(), /* dest y */ + copy_rect.width(), copy_rect.height(), + False /* send_event */); + } +#else + XShmPutImage(display_, pixmap, gc, image, + 0, 0 /* source x, y */, 0, 0 /* dest x, y */, + width, height, False /* send_event */); +#endif + XDestroyImage(image); + } else { // case SHARED_MEMORY_NONE + // No shared memory support, we have to copy the bitmap contents + // to the X server. Xlib wraps the underlying PutImage call + // behind several layers of functions which try to convert the + // image into the format which the X server expects. The + // following values hopefully disable all conversions. + XImage image; + memset(&image, 0, sizeof(image)); + + image.width = width; + image.height = height; + image.depth = 32; + image.bits_per_pixel = 32; + image.format = ZPixmap; + image.byte_order = LSBFirst; + image.bitmap_unit = 8; + image.bitmap_bit_order = LSBFirst; + image.bytes_per_line = width * 4; + image.red_mask = 0xff; + image.green_mask = 0xff00; + image.blue_mask = 0xff0000; + image.data = static_cast<char*>(dib->memory()); + + XPutImage(display_, pixmap, gc, &image, + 0, 0 /* source x, y */, 0, 0 /* dest x, y */, + width, height); + } + XFreeGC(display_, gc); + } + + picture = ui::CreatePictureFromSkiaPixmap(display_, pixmap); + + for (size_t i = 0; i < copy_rects.size(); i++) { + const gfx::Rect& copy_rect = copy_rects[i]; + XRenderComposite(display_, + PictOpSrc, // op + picture, // src + 0, // mask + picture_, // dest + copy_rect.x() - bitmap_rect.x(), // src_x + copy_rect.y() - bitmap_rect.y(), // src_y + 0, // mask_x + 0, // mask_y + copy_rect.x(), // dest_x + copy_rect.y(), // dest_y + copy_rect.width(), // width + copy_rect.height()); // height + } + + // In the case of shared memory, we wait for the composite to complete so that + // we are sure that the X server has finished reading from the shared memory + // segment. + if (shared_memory_support_ != ui::SHARED_MEMORY_NONE) + XSync(display_, False); + + XRenderFreePicture(display_, picture); + XFreePixmap(display_, pixmap); +} + +bool BackingStoreX::CopyFromBackingStore(const gfx::Rect& rect, + skia::PlatformCanvas* output) { + base::TimeTicks begin_time = base::TimeTicks::Now(); + + if (visual_depth_ < 24) { + // CopyFromBackingStore() copies pixels out of the XImage + // in a way that assumes that each component (red, green, + // blue) is a byte. This doesn't work on visuals which + // encode a pixel color with less than a byte per color. + return false; + } + + const int width = std::min(size().width(), rect.width()); + const int height = std::min(size().height(), rect.height()); + + XImage* image; + XShmSegmentInfo shminfo; // Used only when shared memory is enabled. + if (shared_memory_support_ != ui::SHARED_MEMORY_NONE) { + // Use shared memory for faster copies when it's available. + Visual* visual = static_cast<Visual*>(visual_); + memset(&shminfo, 0, sizeof(shminfo)); + image = XShmCreateImage(display_, visual, 32, + ZPixmap, NULL, &shminfo, width, height); + if (!image) { + return false; + } + // Create the shared memory segment for the image and map it. + if (image->bytes_per_line == 0 || image->height == 0 || + static_cast<size_t>(image->height) > + (std::numeric_limits<size_t>::max() / image->bytes_per_line)) { + XDestroyImage(image); + return false; + } + shminfo.shmid = shmget(IPC_PRIVATE, image->bytes_per_line * image->height, + IPC_CREAT|0666); + if (shminfo.shmid == -1) { + XDestroyImage(image); + return false; + } + + void* mapped_memory = shmat(shminfo.shmid, NULL, SHM_RDONLY); + shmctl(shminfo.shmid, IPC_RMID, 0); + if (mapped_memory == (void*)-1) { + XDestroyImage(image); + return false; + } + shminfo.shmaddr = image->data = static_cast<char*>(mapped_memory); + + if (!XShmAttach(display_, &shminfo) || + !XShmGetImage(display_, pixmap_, image, rect.x(), rect.y(), + AllPlanes)) { + DestroySharedImage(display_, image, &shminfo); + return false; + } + } else { + // Non-shared memory case just copy the image from the server. + image = XGetImage(display_, pixmap_, + rect.x(), rect.y(), width, height, + AllPlanes, ZPixmap); + } + + // TODO(jhawkins): Need to convert the image data if the image bits per pixel + // is not 32. + // Note that this also initializes the output bitmap as opaque. + if (!output->initialize(width, height, true) || + image->bits_per_pixel != 32) { + if (shared_memory_support_ != ui::SHARED_MEMORY_NONE) + DestroySharedImage(display_, image, &shminfo); + else + XDestroyImage(image); + return false; + } + + // The X image might have a different row stride, so iterate through + // it and copy each row out, only up to the pixels we're actually + // using. This code assumes a visual mode where a pixel is + // represented using a 32-bit unsigned int, with a byte per component. + SkBitmap bitmap = output->getTopPlatformDevice().accessBitmap(true); + for (int y = 0; y < height; y++) { + const uint32* src_row = reinterpret_cast<uint32*>( + &image->data[image->bytes_per_line * y]); + uint32* dest_row = bitmap.getAddr32(0, y); + for (int x = 0; x < width; ++x, ++dest_row) { + // Force alpha to be 0xff, because otherwise it causes rendering problems. + *dest_row = src_row[x] | 0xff000000; + } + } + + if (shared_memory_support_ != ui::SHARED_MEMORY_NONE) + DestroySharedImage(display_, image, &shminfo); + else + XDestroyImage(image); + + HISTOGRAM_TIMES("BackingStore.RetrievalFromX", + base::TimeTicks::Now() - begin_time); + return true; +} + +void BackingStoreX::ScrollBackingStore(int dx, int dy, + const gfx::Rect& clip_rect, + const gfx::Size& view_size) { + if (!display_) + return; + + // We only support scrolling in one direction at a time. + DCHECK(dx == 0 || dy == 0); + + if (dy) { + // Positive values of |dy| scroll up + if (abs(dy) < clip_rect.height()) { + XCopyArea(display_, pixmap_, pixmap_, static_cast<GC>(pixmap_gc_), + clip_rect.x() /* source x */, + std::max(clip_rect.y(), clip_rect.y() - dy), + clip_rect.width(), + clip_rect.height() - abs(dy), + clip_rect.x() /* dest x */, + std::max(clip_rect.y(), clip_rect.y() + dy) /* dest y */); + } + } else if (dx) { + // Positive values of |dx| scroll right + if (abs(dx) < clip_rect.width()) { + XCopyArea(display_, pixmap_, pixmap_, static_cast<GC>(pixmap_gc_), + std::max(clip_rect.x(), clip_rect.x() - dx), + clip_rect.y() /* source y */, + clip_rect.width() - abs(dx), + clip_rect.height(), + std::max(clip_rect.x(), clip_rect.x() + dx) /* dest x */, + clip_rect.y() /* dest x */); + } + } +} + +void BackingStoreX::XShowRect(const gfx::Point &origin, + const gfx::Rect& rect, XID target) { + XCopyArea(display_, pixmap_, target, static_cast<GC>(pixmap_gc_), + rect.x(), rect.y(), rect.width(), rect.height(), + rect.x() + origin.x(), rect.y() + origin.y()); +} + +void BackingStoreX::CairoShowRect(const gfx::Rect& rect, + GdkDrawable* drawable) { + cairo_surface_t* surface = cairo_xlib_surface_create( + display_, pixmap_, static_cast<Visual*>(visual_), + size().width(), size().height()); + cairo_t* cr = gdk_cairo_create(drawable); + cairo_set_source_surface(cr, surface, 0, 0); + + cairo_rectangle(cr, rect.x(), rect.y(), rect.width(), rect.height()); + cairo_fill(cr); + cairo_destroy(cr); + cairo_surface_destroy(surface); +} + +#if defined(TOOLKIT_GTK) +void BackingStoreX::PaintToRect(const gfx::Rect& rect, GdkDrawable* target) { + cairo_surface_t* surface = cairo_xlib_surface_create( + display_, pixmap_, static_cast<Visual*>(visual_), + size().width(), size().height()); + cairo_t* cr = gdk_cairo_create(target); + + cairo_translate(cr, rect.x(), rect.y()); + double x_scale = static_cast<double>(rect.width()) / size().width(); + double y_scale = static_cast<double>(rect.height()) / size().height(); + cairo_scale(cr, x_scale, y_scale); + + cairo_pattern_t* pattern = cairo_pattern_create_for_surface(surface); + cairo_pattern_set_filter(pattern, CAIRO_FILTER_BEST); + cairo_set_source(cr, pattern); + cairo_pattern_destroy(pattern); + + cairo_identity_matrix(cr); + + cairo_rectangle(cr, rect.x(), rect.y(), rect.width(), rect.height()); + cairo_fill(cr); + cairo_destroy(cr); +} +#endif diff --git a/content/browser/renderer_host/backing_store_x.h b/content/browser/renderer_host/backing_store_x.h new file mode 100644 index 0000000..f911b1f --- /dev/null +++ b/content/browser/renderer_host/backing_store_x.h @@ -0,0 +1,101 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CONTENT_BROWSER_RENDERER_HOST_BACKING_STORE_X_H_ +#define CONTENT_BROWSER_RENDERER_HOST_BACKING_STORE_X_H_ +#pragma once + +#include "base/basictypes.h" +#include "build/build_config.h" +#include "content/browser/renderer_host/backing_store.h" +#include "ui/base/x/x11_util.h" + +namespace gfx { +class Point; +class Rect; +} // namespace gfx + +typedef struct _GdkDrawable GdkDrawable; +class SkBitmap; + +class BackingStoreX : public BackingStore { + public: + // Create a backing store on the X server. The visual is an Xlib Visual + // describing the format of the target window and the depth is the color + // depth of the X window which will be drawn into. + BackingStoreX(RenderWidgetHost* widget, + const gfx::Size& size, + void* visual, + int depth); + + // This is for unittesting only. An object constructed using this constructor + // will silently ignore all paints + BackingStoreX(RenderWidgetHost* widget, const gfx::Size& size); + + virtual ~BackingStoreX(); + + Display* display() const { return display_; } + XID root_window() const { return root_window_; } + + // Copy from the server-side backing store to the target window + // origin: the destination rectangle origin + // damage: the area to copy + // target: the X id of the target window + void XShowRect(const gfx::Point &origin, const gfx::Rect& damage, + XID target); + + // As above, but use Cairo instead of Xlib. + void CairoShowRect(const gfx::Rect& damage, GdkDrawable* drawable); + +#if defined(TOOLKIT_GTK) + // Paint the backing store into the target's |dest_rect|. + void PaintToRect(const gfx::Rect& dest_rect, GdkDrawable* target); +#endif + + // BackingStore implementation. + virtual size_t MemorySize(); + virtual void PaintToBackingStore( + RenderProcessHost* process, + TransportDIB::Id bitmap, + const gfx::Rect& bitmap_rect, + const std::vector<gfx::Rect>& copy_rects); + virtual bool CopyFromBackingStore(const gfx::Rect& rect, + skia::PlatformCanvas* output); + virtual void ScrollBackingStore(int dx, int dy, + const gfx::Rect& clip_rect, + const gfx::Size& view_size); + + private: + // Paints the bitmap from the renderer onto the backing store without + // using Xrender to composite the pixmaps. + void PaintRectWithoutXrender(TransportDIB* bitmap, + const gfx::Rect& bitmap_rect, + const std::vector<gfx::Rect>& copy_rects); + + // This is the connection to the X server where this backing store will be + // displayed. + Display* const display_; + // What flavor, if any, MIT-SHM (X shared memory) support we have. + const ui::SharedMemorySupport shared_memory_support_; + // If this is true, then we can use Xrender to composite our pixmaps. + const bool use_render_; + // If |use_render_| is false, this is the number of bits-per-pixel for |depth| + int pixmap_bpp_; + // if |use_render_| is false, we need the Visual to get the RGB masks. + void* const visual_; + // This is the depth of the target window. + const int visual_depth_; + // The parent window (probably a GtkDrawingArea) for this backing store. + const XID root_window_; + // This is a handle to the server side pixmap which is our backing store. + XID pixmap_; + // This is the RENDER picture pointing at |pixmap_|. + XID picture_; + // This is a default graphic context, used in XCopyArea + void* pixmap_gc_; + + DISALLOW_COPY_AND_ASSIGN(BackingStoreX); +}; + +#endif // CONTENT_BROWSER_RENDERER_HOST_BACKING_STORE_X_H_ diff --git a/content/browser/renderer_host/blob_message_filter.cc b/content/browser/renderer_host/blob_message_filter.cc new file mode 100644 index 0000000..433fe72 --- /dev/null +++ b/content/browser/renderer_host/blob_message_filter.cc @@ -0,0 +1,85 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "content/browser/renderer_host/blob_message_filter.h" + +#include "chrome/browser/child_process_security_policy.h" +#include "chrome/browser/chrome_blob_storage_context.h" +#include "chrome/common/render_messages.h" +#include "googleurl/src/gurl.h" +#include "webkit/blob/blob_data.h" +#include "webkit/blob/blob_storage_controller.h" + +BlobMessageFilter::BlobMessageFilter( + int process_id, + ChromeBlobStorageContext* blob_storage_context) + : process_id_(process_id), + blob_storage_context_(blob_storage_context) { +} + +BlobMessageFilter::~BlobMessageFilter() { +} + +void BlobMessageFilter::OnChannelClosing() { + BrowserMessageFilter::OnChannelClosing(); + + // Unregister all the blob URLs that are previously registered in this + // process. + for (base::hash_set<std::string>::const_iterator iter = blob_urls_.begin(); + iter != blob_urls_.end(); ++iter) { + blob_storage_context_->controller()->UnregisterBlobUrl(GURL(*iter)); + } +} + +bool BlobMessageFilter::OnMessageReceived(const IPC::Message& message, + bool* message_was_ok) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + + bool handled = true; + IPC_BEGIN_MESSAGE_MAP_EX(BlobMessageFilter, message, *message_was_ok) + IPC_MESSAGE_HANDLER(ViewHostMsg_RegisterBlobUrl, OnRegisterBlobUrl) + IPC_MESSAGE_HANDLER(ViewHostMsg_RegisterBlobUrlFrom, OnRegisterBlobUrlFrom) + IPC_MESSAGE_HANDLER(ViewHostMsg_UnregisterBlobUrl, OnUnregisterBlobUrl) + IPC_MESSAGE_UNHANDLED(handled = false) + IPC_END_MESSAGE_MAP() + return handled; +} + +// Check if the child process has been granted permission to register the files. +bool BlobMessageFilter::CheckPermission( + webkit_blob::BlobData* blob_data) const { + ChildProcessSecurityPolicy* policy = + ChildProcessSecurityPolicy::GetInstance(); + for (std::vector<webkit_blob::BlobData::Item>::const_iterator iter = + blob_data->items().begin(); + iter != blob_data->items().end(); ++iter) { + if (iter->type() == webkit_blob::BlobData::TYPE_FILE) { + if (!policy->CanReadFile(process_id_, iter->file_path())) + return false; + } + } + return true; +} + +void BlobMessageFilter::OnRegisterBlobUrl( + const GURL& url, const scoped_refptr<webkit_blob::BlobData>& blob_data) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + if (!CheckPermission(blob_data.get())) + return; + blob_storage_context_->controller()->RegisterBlobUrl(url, blob_data); + blob_urls_.insert(url.spec()); +} + +void BlobMessageFilter::OnRegisterBlobUrlFrom( + const GURL& url, const GURL& src_url) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + blob_storage_context_->controller()->RegisterBlobUrlFrom(url, src_url); + blob_urls_.insert(url.spec()); +} + +void BlobMessageFilter::OnUnregisterBlobUrl(const GURL& url) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + blob_storage_context_->controller()->UnregisterBlobUrl(url); + blob_urls_.erase(url.spec()); +} diff --git a/content/browser/renderer_host/blob_message_filter.h b/content/browser/renderer_host/blob_message_filter.h new file mode 100644 index 0000000..aa1cdc6 --- /dev/null +++ b/content/browser/renderer_host/blob_message_filter.h @@ -0,0 +1,51 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CONTENT_BROWSER_RENDERER_HOST_BLOB_MESSAGE_FILTER_H_ +#define CONTENT_BROWSER_RENDERER_HOST_BLOB_MESSAGE_FILTER_H_ + +#include "base/hash_tables.h" +#include "chrome/browser/browser_message_filter.h" + +class ChromeBlobStorageContext; +class GURL; + +namespace IPC { +class Message; +} + +namespace webkit_blob { +class BlobData; +} + +class BlobMessageFilter : public BrowserMessageFilter { + public: + BlobMessageFilter(int process_id, + ChromeBlobStorageContext* blob_storage_context); + ~BlobMessageFilter(); + + // BrowserMessageFilter implementation. + virtual void OnChannelClosing(); + virtual bool OnMessageReceived(const IPC::Message& message, + bool* message_was_ok); + + private: + void OnRegisterBlobUrl(const GURL& url, + const scoped_refptr<webkit_blob::BlobData>& blob_data); + void OnRegisterBlobUrlFrom(const GURL& url, const GURL& src_url); + void OnUnregisterBlobUrl(const GURL& url); + + bool CheckPermission(webkit_blob::BlobData* blob_data) const; + + int process_id_; + scoped_refptr<ChromeBlobStorageContext> blob_storage_context_; + + // Keep track of blob URLs registered in this process. Need to unregister + // all of them when the renderer process dies. + base::hash_set<std::string> blob_urls_; + + DISALLOW_IMPLICIT_CONSTRUCTORS(BlobMessageFilter); +}; + +#endif // CONTENT_BROWSER_RENDERER_HOST_BLOB_MESSAGE_FILTER_H_ diff --git a/content/browser/renderer_host/buffered_resource_handler.cc b/content/browser/renderer_host/buffered_resource_handler.cc new file mode 100644 index 0000000..2e302f8 --- /dev/null +++ b/content/browser/renderer_host/buffered_resource_handler.cc @@ -0,0 +1,491 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "content/browser/renderer_host/buffered_resource_handler.h" + +#include <vector> + +#include "base/logging.h" +#include "base/metrics/histogram.h" +#include "base/string_util.h" +#include "chrome/browser/browser_thread.h" +#include "chrome/browser/renderer_host/download_throttling_resource_handler.h" +#include "chrome/common/extensions/user_script.h" +#include "chrome/common/resource_response.h" +#include "chrome/common/url_constants.h" +#include "content/browser/renderer_host/resource_dispatcher_host.h" +#include "content/browser/renderer_host/resource_dispatcher_host_request_info.h" +#include "content/browser/renderer_host/x509_user_cert_resource_handler.h" +#include "net/base/io_buffer.h" +#include "net/base/mime_sniffer.h" +#include "net/base/mime_util.h" +#include "net/base/net_errors.h" +#include "net/http/http_response_headers.h" +#include "webkit/plugins/npapi/plugin_list.h" + +namespace { + +void RecordSnifferMetrics(bool sniffing_blocked, + bool we_would_like_to_sniff, + const std::string& mime_type) { + static scoped_refptr<base::Histogram> nosniff_usage = + base::BooleanHistogram::FactoryGet( + "nosniff.usage", base::Histogram::kUmaTargetedHistogramFlag); + nosniff_usage->AddBoolean(sniffing_blocked); + + if (sniffing_blocked) { + static scoped_refptr<base::Histogram> nosniff_otherwise = + base::BooleanHistogram::FactoryGet( + "nosniff.otherwise", base::Histogram::kUmaTargetedHistogramFlag); + nosniff_otherwise->AddBoolean(we_would_like_to_sniff); + + static scoped_refptr<base::Histogram> nosniff_empty_mime_type = + base::BooleanHistogram::FactoryGet( + "nosniff.empty_mime_type", + base::Histogram::kUmaTargetedHistogramFlag); + nosniff_empty_mime_type->AddBoolean(mime_type.empty()); + } +} + +} // namespace + +BufferedResourceHandler::BufferedResourceHandler(ResourceHandler* handler, + ResourceDispatcherHost* host, + net::URLRequest* request) + : real_handler_(handler), + host_(host), + request_(request), + read_buffer_size_(0), + bytes_read_(0), + sniff_content_(false), + should_buffer_(false), + wait_for_plugins_(false), + buffering_(false), + finished_(false) { +} + +bool BufferedResourceHandler::OnUploadProgress(int request_id, + uint64 position, + uint64 size) { + return real_handler_->OnUploadProgress(request_id, position, size); +} + +bool BufferedResourceHandler::OnRequestRedirected(int request_id, + const GURL& new_url, + ResourceResponse* response, + bool* defer) { + return real_handler_->OnRequestRedirected( + request_id, new_url, response, defer); +} + +bool BufferedResourceHandler::OnResponseStarted(int request_id, + ResourceResponse* response) { + response_ = response; + if (!DelayResponse()) + return CompleteResponseStarted(request_id, false); + return true; +} + +bool BufferedResourceHandler::OnResponseCompleted( + int request_id, + const net::URLRequestStatus& status, + const std::string& security_info) { + return real_handler_->OnResponseCompleted(request_id, status, security_info); +} + +void BufferedResourceHandler::OnRequestClosed() { + request_ = NULL; + real_handler_->OnRequestClosed(); +} + +bool BufferedResourceHandler::OnWillStart(int request_id, + const GURL& url, + bool* defer) { + return real_handler_->OnWillStart(request_id, url, defer); +} + +// We'll let the original event handler provide a buffer, and reuse it for +// subsequent reads until we're done buffering. +bool BufferedResourceHandler::OnWillRead(int request_id, net::IOBuffer** buf, + int* buf_size, int min_size) { + if (buffering_) { + DCHECK(!my_buffer_.get()); + my_buffer_ = new net::IOBuffer(net::kMaxBytesToSniff); + *buf = my_buffer_.get(); + *buf_size = net::kMaxBytesToSniff; + return true; + } + + if (finished_) + return false; + + if (!real_handler_->OnWillRead(request_id, buf, buf_size, min_size)) { + return false; + } + read_buffer_ = *buf; + read_buffer_size_ = *buf_size; + DCHECK_GE(read_buffer_size_, net::kMaxBytesToSniff * 2); + bytes_read_ = 0; + return true; +} + +bool BufferedResourceHandler::OnReadCompleted(int request_id, int* bytes_read) { + if (sniff_content_ || should_buffer_) { + if (KeepBuffering(*bytes_read)) + return true; + + *bytes_read = bytes_read_; + + // Done buffering, send the pending ResponseStarted event. + if (!CompleteResponseStarted(request_id, true)) + return false; + } else if (wait_for_plugins_) { + return true; + } + + // Release the reference that we acquired at OnWillRead. + read_buffer_ = NULL; + return real_handler_->OnReadCompleted(request_id, bytes_read); +} + +BufferedResourceHandler::~BufferedResourceHandler() {} + +bool BufferedResourceHandler::DelayResponse() { + std::string mime_type; + request_->GetMimeType(&mime_type); + + std::string content_type_options; + request_->GetResponseHeaderByName("x-content-type-options", + &content_type_options); + + const bool sniffing_blocked = + LowerCaseEqualsASCII(content_type_options, "nosniff"); + const bool not_modified_status = + response_->response_head.headers && + response_->response_head.headers->response_code() == 304; + const bool we_would_like_to_sniff = not_modified_status ? + false : net::ShouldSniffMimeType(request_->url(), mime_type); + + RecordSnifferMetrics(sniffing_blocked, we_would_like_to_sniff, mime_type); + + if (!sniffing_blocked && we_would_like_to_sniff) { + // We're going to look at the data before deciding what the content type + // is. That means we need to delay sending the ResponseStarted message + // over the IPC channel. + sniff_content_ = true; + VLOG(1) << "To buffer: " << request_->url().spec(); + return true; + } + + if (sniffing_blocked && mime_type.empty() && !not_modified_status) { + // Ugg. The server told us not to sniff the content but didn't give us a + // mime type. What's a browser to do? Turns out, we're supposed to treat + // the response as "text/plain". This is the most secure option. + mime_type.assign("text/plain"); + response_->response_head.mime_type.assign(mime_type); + } + + if (mime_type == "application/rss+xml" || + mime_type == "application/atom+xml") { + // Sad face. The server told us that they wanted us to treat the response + // as RSS or Atom. Unfortunately, we don't have a built-in feed previewer + // like other browsers. We can't just render the content as XML because + // web sites let third parties inject arbitrary script into their RSS + // feeds. That leaves us with little choice but to practically ignore the + // response. In the future, when we have an RSS feed previewer, we can + // remove this logic. + mime_type.assign("text/plain"); + response_->response_head.mime_type.assign(mime_type); + } + + if (ShouldBuffer(request_->url(), mime_type)) { + // This is a temporary fix for the fact that webkit expects to have + // enough data to decode the doctype in order to select the rendering + // mode. + should_buffer_ = true; + return true; + } + + if (!not_modified_status && ShouldWaitForPlugins()) { + wait_for_plugins_ = true; + return true; + } + + return false; +} + +bool BufferedResourceHandler::ShouldBuffer(const GURL& url, + const std::string& mime_type) { + // We are willing to buffer for HTTP and HTTPS. + bool sniffable_scheme = url.is_empty() || + url.SchemeIs(chrome::kHttpScheme) || + url.SchemeIs(chrome::kHttpsScheme); + if (!sniffable_scheme) + return false; + + // Today, the only reason to buffer the request is to fix the doctype decoding + // performed by webkit: if there is not enough data it will go to quirks mode. + // We only expect the doctype check to apply to html documents. + return mime_type == "text/html"; +} + +bool BufferedResourceHandler::DidBufferEnough(int bytes_read) { + const int kRequiredLength = 256; + + return bytes_read >= kRequiredLength; +} + +bool BufferedResourceHandler::KeepBuffering(int bytes_read) { + DCHECK(read_buffer_); + if (my_buffer_) { + // We are using our own buffer to read, update the main buffer. + // TODO(darin): We should handle the case where read_buffer_size_ is small! + // See RedirectToFileResourceHandler::BufIsFull to see how this impairs + // downstream ResourceHandler implementations. + CHECK_LT(bytes_read + bytes_read_, read_buffer_size_); + memcpy(read_buffer_->data() + bytes_read_, my_buffer_->data(), bytes_read); + my_buffer_ = NULL; + } + bytes_read_ += bytes_read; + finished_ = (bytes_read == 0); + + if (sniff_content_) { + std::string type_hint, new_type; + request_->GetMimeType(&type_hint); + + if (!net::SniffMimeType(read_buffer_->data(), bytes_read_, + request_->url(), type_hint, &new_type)) { + // SniffMimeType() returns false if there is not enough data to determine + // the mime type. However, even if it returns false, it returns a new type + // that is probably better than the current one. + DCHECK_LT(bytes_read_, net::kMaxBytesToSniff); + if (!finished_) { + buffering_ = true; + return true; + } + } + sniff_content_ = false; + response_->response_head.mime_type.assign(new_type); + + // We just sniffed the mime type, maybe there is a doctype to process. + if (ShouldBuffer(request_->url(), new_type)) { + should_buffer_ = true; + } else if (ShouldWaitForPlugins()) { + wait_for_plugins_ = true; + } + } + + if (should_buffer_) { + if (!finished_ && !DidBufferEnough(bytes_read_)) { + buffering_ = true; + return true; + } + + should_buffer_ = false; + if (ShouldWaitForPlugins()) + wait_for_plugins_ = true; + } + + buffering_ = false; + + if (wait_for_plugins_) + return true; + + return false; +} + +bool BufferedResourceHandler::CompleteResponseStarted(int request_id, + bool in_complete) { + ResourceDispatcherHostRequestInfo* info = + ResourceDispatcherHost::InfoForRequest(request_); + std::string mime_type; + request_->GetMimeType(&mime_type); + + // Check if this is an X.509 certificate, if yes, let it be handled + // by X509UserCertResourceHandler. + if (mime_type == "application/x-x509-user-cert") { + // This is entirely similar to how DownloadThrottlingResourceHandler + // works except we are doing it for an X.509 client certificates. + + if (response_->response_head.headers && // Can be NULL if FTP. + response_->response_head.headers->response_code() / 100 != 2) { + // The response code indicates that this is an error page, but we are + // expecting an X.509 user certificate. We follow Firefox here and show + // our own error page instead of handling the error page as a + // certificate. + // TODO(abarth): We should abstract the response_code test, but this kind + // of check is scattered throughout our codebase. + request_->SimulateError(net::ERR_FILE_NOT_FOUND); + return false; + } + + X509UserCertResourceHandler* x509_cert_handler = + new X509UserCertResourceHandler(host_, request_, + info->child_id(), info->route_id()); + UseAlternateResourceHandler(request_id, x509_cert_handler); + } + + // Check to see if we should forward the data from this request to the + // download thread. + // TODO(paulg): Only download if the context from the renderer allows it. + if (info->allow_download() && ShouldDownload(NULL)) { + if (response_->response_head.headers && // Can be NULL if FTP. + response_->response_head.headers->response_code() / 100 != 2) { + // The response code indicates that this is an error page, but we don't + // know how to display the content. We follow Firefox here and show our + // own error page instead of triggering a download. + // TODO(abarth): We should abstract the response_code test, but this kind + // of check is scattered throughout our codebase. + request_->SimulateError(net::ERR_FILE_NOT_FOUND); + return false; + } + + info->set_is_download(true); + + DownloadThrottlingResourceHandler* download_handler = + new DownloadThrottlingResourceHandler(host_, + request_, + request_->url(), + info->child_id(), + info->route_id(), + request_id, + in_complete); + UseAlternateResourceHandler(request_id, download_handler); + } + return real_handler_->OnResponseStarted(request_id, response_); +} + +bool BufferedResourceHandler::ShouldWaitForPlugins() { + bool need_plugin_list; + if (!ShouldDownload(&need_plugin_list) || !need_plugin_list) + return false; + + // We don't want to keep buffering as our buffer will fill up. + ResourceDispatcherHostRequestInfo* info = + ResourceDispatcherHost::InfoForRequest(request_); + host_->PauseRequest(info->child_id(), info->request_id(), true); + + // Schedule plugin loading on the file thread. + BrowserThread::PostTask( + BrowserThread::FILE, FROM_HERE, + NewRunnableMethod(this, &BufferedResourceHandler::LoadPlugins)); + return true; +} + +// This test mirrors the decision that WebKit makes in +// WebFrameLoaderClient::dispatchDecidePolicyForMIMEType. +bool BufferedResourceHandler::ShouldDownload(bool* need_plugin_list) { + if (need_plugin_list) + *need_plugin_list = false; + std::string type = StringToLowerASCII(response_->response_head.mime_type); + std::string disposition; + request_->GetResponseHeaderByName("content-disposition", &disposition); + disposition = StringToLowerASCII(disposition); + + // First, examine content-disposition. + if (!disposition.empty()) { + bool should_download = true; + + // Some broken sites just send ... + // Content-Disposition: ; filename="file" + // ... screen those out here. + if (disposition[0] == ';') + should_download = false; + + if (disposition.compare(0, 6, "inline") == 0) + should_download = false; + + // Some broken sites just send ... + // Content-Disposition: filename="file" + // ... without a disposition token... Screen those out. + if (disposition.compare(0, 8, "filename") == 0) + should_download = false; + + // Also in use is Content-Disposition: name="file" + if (disposition.compare(0, 4, "name") == 0) + should_download = false; + + // We have a content-disposition of "attachment" or unknown. + // RFC 2183, section 2.8 says that an unknown disposition + // value should be treated as "attachment". + if (should_download) + return true; + } + + // Special-case user scripts to get downloaded instead of viewed. + if (UserScript::HasUserScriptFileExtension(request_->url())) + return true; + + // MIME type checking. + if (net::IsSupportedMimeType(type)) + return false; + + if (need_plugin_list) { + if (!webkit::npapi::PluginList::Singleton()->PluginsLoaded()) { + *need_plugin_list = true; + return true; + } + } else { + DCHECK(webkit::npapi::PluginList::Singleton()->PluginsLoaded()); + } + + // Finally, check the plugin list. + webkit::npapi::WebPluginInfo info; + bool allow_wildcard = false; + return !webkit::npapi::PluginList::Singleton()->GetPluginInfo( + GURL(), type, allow_wildcard, &info, NULL) || + !webkit::npapi::IsPluginEnabled(info); +} + +void BufferedResourceHandler::UseAlternateResourceHandler( + int request_id, + ResourceHandler* handler) { + ResourceDispatcherHostRequestInfo* info = + ResourceDispatcherHost::InfoForRequest(request_); + if (bytes_read_) { + // A Read has already occured and we need to copy the data into the new + // ResourceHandler. + net::IOBuffer* buf = NULL; + int buf_len = 0; + handler->OnWillRead(request_id, &buf, &buf_len, bytes_read_); + CHECK((buf_len >= bytes_read_) && (bytes_read_ >= 0)); + memcpy(buf->data(), read_buffer_->data(), bytes_read_); + } + + // Inform the original ResourceHandler that this will be handled entirely by + // the new ResourceHandler. + real_handler_->OnResponseStarted(info->request_id(), response_); + net::URLRequestStatus status(net::URLRequestStatus::HANDLED_EXTERNALLY, 0); + real_handler_->OnResponseCompleted(info->request_id(), status, std::string()); + + // Remove the non-owning pointer to the CrossSiteResourceHandler, if any, + // from the extra request info because the CrossSiteResourceHandler (part of + // the original ResourceHandler chain) will be deleted by the next statement. + info->set_cross_site_handler(NULL); + + // This is handled entirely within the new ResourceHandler, so just reset the + // original ResourceHandler. + real_handler_ = handler; +} + +void BufferedResourceHandler::LoadPlugins() { + std::vector<webkit::npapi::WebPluginInfo> plugins; + webkit::npapi::PluginList::Singleton()->GetPlugins(false, &plugins); + + BrowserThread::PostTask( + BrowserThread::IO, FROM_HERE, + NewRunnableMethod(this, &BufferedResourceHandler::OnPluginsLoaded)); +} + +void BufferedResourceHandler::OnPluginsLoaded() { + wait_for_plugins_ = false; + if (!request_) + return; + + ResourceDispatcherHostRequestInfo* info = + ResourceDispatcherHost::InfoForRequest(request_); + host_->PauseRequest(info->child_id(), info->request_id(), false); + if (!CompleteResponseStarted(info->request_id(), false)) + host_->CancelRequest(info->child_id(), info->request_id(), false); +} diff --git a/content/browser/renderer_host/buffered_resource_handler.h b/content/browser/renderer_host/buffered_resource_handler.h new file mode 100644 index 0000000..ab5393e --- /dev/null +++ b/content/browser/renderer_host/buffered_resource_handler.h @@ -0,0 +1,99 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CONTENT_BROWSER_RENDERER_HOST_BUFFERED_RESOURCE_HANDLER_H_ +#define CONTENT_BROWSER_RENDERER_HOST_BUFFERED_RESOURCE_HANDLER_H_ +#pragma once + +#include <string> + +#include "content/browser/renderer_host/resource_handler.h" + +class MessageLoop; +class ResourceDispatcherHost; + +namespace net { +class URLRequest; +} // namespace net + +// Used to buffer a request until enough data has been received. +class BufferedResourceHandler : public ResourceHandler { + public: + BufferedResourceHandler(ResourceHandler* handler, + ResourceDispatcherHost* host, + net::URLRequest* request); + + // ResourceHandler implementation: + virtual bool OnUploadProgress(int request_id, uint64 position, uint64 size); + virtual bool OnRequestRedirected(int request_id, const GURL& new_url, + ResourceResponse* response, bool* defer); + virtual bool OnResponseStarted(int request_id, ResourceResponse* response); + virtual bool OnWillStart(int request_id, const GURL& url, bool* defer); + virtual bool OnWillRead(int request_id, net::IOBuffer** buf, int* buf_size, + int min_size); + virtual bool OnReadCompleted(int request_id, int* bytes_read); + virtual bool OnResponseCompleted(int request_id, + const net::URLRequestStatus& status, + const std::string& security_info); + virtual void OnRequestClosed(); + + private: + virtual ~BufferedResourceHandler(); + + // Returns true if we should delay OnResponseStarted forwarding. + bool DelayResponse(); + + // Returns true if there will be a need to parse the DocType of the document + // to determine the right way to handle it. + bool ShouldBuffer(const GURL& url, const std::string& mime_type); + + // Returns true if there is enough information to process the DocType. + bool DidBufferEnough(int bytes_read); + + // Returns true if we have to keep buffering data. + bool KeepBuffering(int bytes_read); + + // Sends a pending OnResponseStarted notification. |in_complete| is true if + // this is invoked from |OnResponseCompleted|. + bool CompleteResponseStarted(int request_id, bool in_complete); + + // Returns true if we have to wait until the plugin list is generated. + bool ShouldWaitForPlugins(); + + // A test to determining whether the request should be forwarded to the + // download thread. If need_plugin_list was passed in and was set to true, + // that means that the check couldn't be fully done because the plugins aren't + // loaded. The function should be called again after the plugin list is + // loaded. + bool ShouldDownload(bool* need_plugin_list); + + // Informs the original ResourceHandler |real_handler_| that the response will + // be handled entirely by the new ResourceHandler |handler|. + // A reference to |handler| is acquired. + void UseAlternateResourceHandler(int request_id, ResourceHandler* handler); + + // Called on the file thread to load the list of plugins. + void LoadPlugins(); + + // Called on the IO thread once the list of plugins has been loaded. + void OnPluginsLoaded(); + + scoped_refptr<ResourceHandler> real_handler_; + scoped_refptr<ResourceResponse> response_; + ResourceDispatcherHost* host_; + net::URLRequest* request_; + scoped_refptr<net::IOBuffer> read_buffer_; + scoped_refptr<net::IOBuffer> my_buffer_; + int read_buffer_size_; + int bytes_read_; + bool sniff_content_; + bool should_buffer_; + bool wait_for_plugins_; + bool buffering_; + bool finished_; + + DISALLOW_COPY_AND_ASSIGN(BufferedResourceHandler); +}; + +#endif // CONTENT_BROWSER_RENDERER_HOST_BUFFERED_RESOURCE_HANDLER_H_ diff --git a/content/browser/renderer_host/cross_site_resource_handler.cc b/content/browser/renderer_host/cross_site_resource_handler.cc new file mode 100644 index 0000000..ceb5554 --- /dev/null +++ b/content/browser/renderer_host/cross_site_resource_handler.cc @@ -0,0 +1,219 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <string> + +#include "content/browser/renderer_host/cross_site_resource_handler.h" + +#include "base/logging.h" +#include "content/browser/renderer_host/global_request_id.h" +#include "content/browser/renderer_host/render_view_host.h" +#include "content/browser/renderer_host/render_view_host_delegate.h" +#include "content/browser/renderer_host/render_view_host_notification_task.h" +#include "content/browser/renderer_host/resource_dispatcher_host.h" +#include "content/browser/renderer_host/resource_dispatcher_host_request_info.h" +#include "net/base/io_buffer.h" + +CrossSiteResourceHandler::CrossSiteResourceHandler( + ResourceHandler* handler, + int render_process_host_id, + int render_view_id, + ResourceDispatcherHost* resource_dispatcher_host) + : next_handler_(handler), + render_process_host_id_(render_process_host_id), + render_view_id_(render_view_id), + has_started_response_(false), + in_cross_site_transition_(false), + request_id_(-1), + completed_during_transition_(false), + completed_status_(), + response_(NULL), + rdh_(resource_dispatcher_host) {} + +bool CrossSiteResourceHandler::OnUploadProgress(int request_id, + uint64 position, + uint64 size) { + return next_handler_->OnUploadProgress(request_id, position, size); +} + +bool CrossSiteResourceHandler::OnRequestRedirected(int request_id, + const GURL& new_url, + ResourceResponse* response, + bool* defer) { + // We should not have started the transition before being redirected. + DCHECK(!in_cross_site_transition_); + return next_handler_->OnRequestRedirected( + request_id, new_url, response, defer); +} + +bool CrossSiteResourceHandler::OnResponseStarted(int request_id, + ResourceResponse* response) { + // At this point, we know that the response is safe to send back to the + // renderer: it is not a download, and it has passed the SSL and safe + // browsing checks. + // We should not have already started the transition before now. + DCHECK(!in_cross_site_transition_); + has_started_response_ = true; + + // Look up the request and associated info. + GlobalRequestID global_id(render_process_host_id_, request_id); + net::URLRequest* request = rdh_->GetURLRequest(global_id); + if (!request) { + DLOG(WARNING) << "Request wasn't found"; + return false; + } + ResourceDispatcherHostRequestInfo* info = + ResourceDispatcherHost::InfoForRequest(request); + + // If this is a download, just pass the response through without doing a + // cross-site check. The renderer will see it is a download and abort the + // request. + if (info->is_download()) { + return next_handler_->OnResponseStarted(request_id, response); + } + + // Tell the renderer to run the onunload event handler, and wait for the + // reply. + StartCrossSiteTransition(request_id, response, global_id); + return true; +} + +bool CrossSiteResourceHandler::OnWillStart(int request_id, + const GURL& url, + bool* defer) { + return next_handler_->OnWillStart(request_id, url, defer); +} + +bool CrossSiteResourceHandler::OnWillRead(int request_id, net::IOBuffer** buf, + int* buf_size, int min_size) { + return next_handler_->OnWillRead(request_id, buf, buf_size, min_size); +} + +bool CrossSiteResourceHandler::OnReadCompleted(int request_id, + int* bytes_read) { + if (!in_cross_site_transition_) { + return next_handler_->OnReadCompleted(request_id, bytes_read); + } + return true; +} + +bool CrossSiteResourceHandler::OnResponseCompleted( + int request_id, + const net::URLRequestStatus& status, + const std::string& security_info) { + if (!in_cross_site_transition_) { + if (has_started_response_) { + // We've already completed the transition, so just pass it through. + return next_handler_->OnResponseCompleted(request_id, status, + security_info); + } else { + // An error occured, we should wait now for the cross-site transition, + // so that the error message (e.g., 404) can be displayed to the user. + // Also continue with the logic below to remember that we completed + // during the cross-site transition. + GlobalRequestID global_id(render_process_host_id_, request_id); + StartCrossSiteTransition(request_id, NULL, global_id); + } + } + + // We have to buffer the call until after the transition completes. + completed_during_transition_ = true; + completed_status_ = status; + completed_security_info_ = security_info; + + // Return false to tell RDH not to notify the world or clean up the + // pending request. We will do so in ResumeResponse. + return false; +} + +void CrossSiteResourceHandler::OnRequestClosed() { + next_handler_->OnRequestClosed(); +} + +// We can now send the response to the new renderer, which will cause +// TabContents to swap in the new renderer and destroy the old one. +void CrossSiteResourceHandler::ResumeResponse() { + DCHECK(request_id_ != -1); + DCHECK(in_cross_site_transition_); + in_cross_site_transition_ = false; + + // Find the request for this response. + GlobalRequestID global_id(render_process_host_id_, request_id_); + net::URLRequest* request = rdh_->GetURLRequest(global_id); + if (!request) { + DLOG(WARNING) << "Resuming a request that wasn't found"; + return; + } + + if (has_started_response_) { + // Send OnResponseStarted to the new renderer. + DCHECK(response_); + next_handler_->OnResponseStarted(request_id_, response_); + + // Unpause the request to resume reading. Any further reads will be + // directed toward the new renderer. + rdh_->PauseRequest(render_process_host_id_, request_id_, false); + } + + // Remove ourselves from the ExtraRequestInfo. + ResourceDispatcherHostRequestInfo* info = + ResourceDispatcherHost::InfoForRequest(request); + info->set_cross_site_handler(NULL); + + // If the response completed during the transition, notify the next + // event handler. + if (completed_during_transition_) { + next_handler_->OnResponseCompleted(request_id_, completed_status_, + completed_security_info_); + + // Since we didn't notify the world or clean up the pending request in + // RDH::OnResponseCompleted during the transition, we should do it now. + rdh_->NotifyResponseCompleted(request, render_process_host_id_); + rdh_->RemovePendingRequest(render_process_host_id_, request_id_); + } +} + +CrossSiteResourceHandler::~CrossSiteResourceHandler() {} + +// Prepare to render the cross-site response in a new RenderViewHost, by +// telling the old RenderViewHost to run its onunload handler. +void CrossSiteResourceHandler::StartCrossSiteTransition( + int request_id, + ResourceResponse* response, + const GlobalRequestID& global_id) { + in_cross_site_transition_ = true; + request_id_ = request_id; + response_ = response; + + // Store this handler on the ExtraRequestInfo, so that RDH can call our + // ResumeResponse method when the close ACK is received. + net::URLRequest* request = rdh_->GetURLRequest(global_id); + if (!request) { + DLOG(WARNING) << "Cross site response for a request that wasn't found"; + return; + } + ResourceDispatcherHostRequestInfo* info = + ResourceDispatcherHost::InfoForRequest(request); + info->set_cross_site_handler(this); + + if (has_started_response_) { + // Pause the request until the old renderer is finished and the new + // renderer is ready. + rdh_->PauseRequest(render_process_host_id_, request_id, true); + } + // If our OnResponseStarted wasn't called, then we're being called by + // OnResponseCompleted after a failure. We don't need to pause, because + // there will be no reads. + + // Tell the tab responsible for this request that a cross-site response is + // starting, so that it can tell its old renderer to run its onunload + // handler now. We will wait to hear the corresponding ClosePage_ACK. + CallRenderViewHostRendererManagementDelegate( + render_process_host_id_, render_view_id_, + &RenderViewHostDelegate::RendererManagement::OnCrossSiteResponse, + render_process_host_id_, request_id); + + // TODO(creis): If the above call should fail, then we need to notify the IO + // thread to proceed anyway, using ResourceDispatcherHost::OnClosePageACK. +} diff --git a/content/browser/renderer_host/cross_site_resource_handler.h b/content/browser/renderer_host/cross_site_resource_handler.h new file mode 100644 index 0000000..9fee1b9 --- /dev/null +++ b/content/browser/renderer_host/cross_site_resource_handler.h @@ -0,0 +1,71 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CONTENT_BROWSER_RENDERER_HOST_CROSS_SITE_RESOURCE_HANDLER_H_ +#define CONTENT_BROWSER_RENDERER_HOST_CROSS_SITE_RESOURCE_HANDLER_H_ +#pragma once + +#include "content/browser/renderer_host/resource_handler.h" +#include "net/url_request/url_request_status.h" + +class ResourceDispatcherHost; +struct GlobalRequestID; + +// Ensures that cross-site responses are delayed until the onunload handler of +// the previous page is allowed to run. This handler wraps an +// AsyncEventHandler, and it sits inside SafeBrowsing and Buffered event +// handlers. This is important, so that it can intercept OnResponseStarted +// after we determine that a response is safe and not a download. +class CrossSiteResourceHandler : public ResourceHandler { + public: + CrossSiteResourceHandler(ResourceHandler* handler, + int render_process_host_id, + int render_view_id, + ResourceDispatcherHost* resource_dispatcher_host); + + // ResourceHandler implementation: + virtual bool OnUploadProgress(int request_id, uint64 position, uint64 size); + virtual bool OnRequestRedirected(int request_id, const GURL& new_url, + ResourceResponse* response, bool* defer); + virtual bool OnResponseStarted(int request_id, + ResourceResponse* response); + virtual bool OnWillStart(int request_id, const GURL& url, bool* defer); + virtual bool OnWillRead(int request_id, net::IOBuffer** buf, int* buf_size, + int min_size); + virtual bool OnReadCompleted(int request_id, int* bytes_read); + virtual bool OnResponseCompleted(int request_id, + const net::URLRequestStatus& status, + const std::string& security_info); + virtual void OnRequestClosed(); + + // We can now send the response to the new renderer, which will cause + // TabContents to swap in the new renderer and destroy the old one. + void ResumeResponse(); + + private: + virtual ~CrossSiteResourceHandler(); + + // Prepare to render the cross-site response in a new RenderViewHost, by + // telling the old RenderViewHost to run its onunload handler. + void StartCrossSiteTransition( + int request_id, + ResourceResponse* response, + const GlobalRequestID& global_id); + + scoped_refptr<ResourceHandler> next_handler_; + int render_process_host_id_; + int render_view_id_; + bool has_started_response_; + bool in_cross_site_transition_; + int request_id_; + bool completed_during_transition_; + net::URLRequestStatus completed_status_; + std::string completed_security_info_; + ResourceResponse* response_; + ResourceDispatcherHost* rdh_; + + DISALLOW_COPY_AND_ASSIGN(CrossSiteResourceHandler); +}; + +#endif // CONTENT_BROWSER_RENDERER_HOST_CROSS_SITE_RESOURCE_HANDLER_H_ diff --git a/content/browser/renderer_host/database_message_filter.cc b/content/browser/renderer_host/database_message_filter.cc new file mode 100644 index 0000000..1e8cdb0 --- /dev/null +++ b/content/browser/renderer_host/database_message_filter.cc @@ -0,0 +1,323 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "content/browser/renderer_host/database_message_filter.h" + +#include <string> + +#include "base/string_util.h" +#include "base/threading/thread.h" +#include "chrome/browser/content_settings/host_content_settings_map.h" +#include "chrome/browser/metrics/user_metrics.h" +#include "chrome/browser/net/chrome_url_request_context.h" +#include "chrome/common/database_messages.h" +#include "chrome/common/result_codes.h" +#include "googleurl/src/gurl.h" +#include "third_party/sqlite/sqlite3.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebSecurityOrigin.h" +#include "webkit/database/database_util.h" +#include "webkit/database/vfs_backend.h" + +#if defined(OS_POSIX) +#include "base/file_descriptor_posix.h" +#endif + +using WebKit::WebSecurityOrigin; +using webkit_database::DatabaseTracker; +using webkit_database::DatabaseUtil; +using webkit_database::VfsBackend; + +const int kNumDeleteRetries = 2; +const int kDelayDeleteRetryMs = 100; + +DatabaseMessageFilter::DatabaseMessageFilter( + webkit_database::DatabaseTracker* db_tracker, + HostContentSettingsMap *host_content_settings_map) + : db_tracker_(db_tracker), + observer_added_(false), + host_content_settings_map_(host_content_settings_map) { + DCHECK(db_tracker_); +} + +void DatabaseMessageFilter::OnChannelClosing() { + BrowserMessageFilter::OnChannelClosing(); + if (observer_added_) { + observer_added_ = false; + BrowserThread::PostTask( + BrowserThread::FILE, FROM_HERE, + NewRunnableMethod(this, &DatabaseMessageFilter::RemoveObserver)); + } +} + +void DatabaseMessageFilter::AddObserver() { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); + db_tracker_->AddObserver(this); +} + +void DatabaseMessageFilter::RemoveObserver() { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); + + // If the renderer process died without closing all databases, + // then we need to manually close those connections + db_tracker_->CloseDatabases(database_connections_); + database_connections_.RemoveAllConnections(); + + db_tracker_->RemoveObserver(this); +} + +void DatabaseMessageFilter::OverrideThreadForMessage( + const IPC::Message& message, + BrowserThread::ID* thread) { + if (IPC_MESSAGE_CLASS(message) == DatabaseMsgStart && + message.type() != DatabaseHostMsg_Allow::ID) { + *thread = BrowserThread::FILE; + } + + if (message.type() == DatabaseHostMsg_OpenFile::ID && !observer_added_) { + observer_added_ = true; + BrowserThread::PostTask( + BrowserThread::FILE, FROM_HERE, + NewRunnableMethod(this, &DatabaseMessageFilter::AddObserver)); + } +} + +bool DatabaseMessageFilter::OnMessageReceived( + const IPC::Message& message, + bool* message_was_ok) { + bool handled = true; + IPC_BEGIN_MESSAGE_MAP_EX(DatabaseMessageFilter, message, *message_was_ok) + IPC_MESSAGE_HANDLER_DELAY_REPLY(DatabaseHostMsg_OpenFile, + OnDatabaseOpenFile) + IPC_MESSAGE_HANDLER_DELAY_REPLY(DatabaseHostMsg_DeleteFile, + OnDatabaseDeleteFile) + IPC_MESSAGE_HANDLER_DELAY_REPLY(DatabaseHostMsg_GetFileAttributes, + OnDatabaseGetFileAttributes) + IPC_MESSAGE_HANDLER_DELAY_REPLY(DatabaseHostMsg_GetFileSize, + OnDatabaseGetFileSize) + IPC_MESSAGE_HANDLER(DatabaseHostMsg_Opened, OnDatabaseOpened) + IPC_MESSAGE_HANDLER(DatabaseHostMsg_Modified, OnDatabaseModified) + IPC_MESSAGE_HANDLER(DatabaseHostMsg_Closed, OnDatabaseClosed) + IPC_MESSAGE_HANDLER_DELAY_REPLY(DatabaseHostMsg_Allow, OnAllowDatabase) + IPC_MESSAGE_UNHANDLED(handled = false) + IPC_END_MESSAGE_MAP_EX() + return handled; +} + +DatabaseMessageFilter::~DatabaseMessageFilter() { +} + +void DatabaseMessageFilter::OnDatabaseOpenFile(const string16& vfs_file_name, + int desired_flags, + IPC::Message* reply_msg) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); + base::PlatformFile file_handle = base::kInvalidPlatformFileValue; + base::PlatformFile target_handle = base::kInvalidPlatformFileValue; + string16 origin_identifier; + string16 database_name; + + // When in incognito mode, we want to make sure that all DB files are + // removed when the incognito profile goes away, so we add the + // SQLITE_OPEN_DELETEONCLOSE flag when opening all files, and keep + // open handles to them in the database tracker to make sure they're + // around for as long as needed. + if (vfs_file_name.empty()) { + VfsBackend::OpenTempFileInDirectory(db_tracker_->DatabaseDirectory(), + desired_flags, &file_handle); + } else if (DatabaseUtil::CrackVfsFileName(vfs_file_name, &origin_identifier, + &database_name, NULL) && + !db_tracker_->IsDatabaseScheduledForDeletion(origin_identifier, + database_name)) { + FilePath db_file = + DatabaseUtil::GetFullFilePathForVfsFile(db_tracker_, vfs_file_name); + if (!db_file.empty()) { + if (db_tracker_->IsIncognitoProfile()) { + db_tracker_->GetIncognitoFileHandle(vfs_file_name, &file_handle); + if (file_handle == base::kInvalidPlatformFileValue) { + VfsBackend::OpenFile(db_file, + desired_flags | SQLITE_OPEN_DELETEONCLOSE, + &file_handle); + if (VfsBackend::FileTypeIsMainDB(desired_flags) || + VfsBackend::FileTypeIsJournal(desired_flags)) + db_tracker_->SaveIncognitoFileHandle(vfs_file_name, file_handle); + } + } else { + VfsBackend::OpenFile(db_file, desired_flags, &file_handle); + } + } + } + + // Then we duplicate the file handle to make it useable in the renderer + // process. The original handle is closed, unless we saved it in the + // database tracker. + bool auto_close = !db_tracker_->HasSavedIncognitoFileHandle(vfs_file_name); + VfsBackend::GetFileHandleForProcess(peer_handle(), file_handle, + &target_handle, auto_close); + + DatabaseHostMsg_OpenFile::WriteReplyParams( + reply_msg, +#if defined(OS_WIN) + target_handle +#elif defined(OS_POSIX) + base::FileDescriptor(target_handle, auto_close) +#endif + ); + Send(reply_msg); +} + +void DatabaseMessageFilter::OnDatabaseDeleteFile(const string16& vfs_file_name, + const bool& sync_dir, + IPC::Message* reply_msg) { + DatabaseDeleteFile(vfs_file_name, sync_dir, reply_msg, kNumDeleteRetries); +} + +void DatabaseMessageFilter::DatabaseDeleteFile(const string16& vfs_file_name, + bool sync_dir, + IPC::Message* reply_msg, + int reschedule_count) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); + + // Return an error if the file name is invalid or if the file could not + // be deleted after kNumDeleteRetries attempts. + int error_code = SQLITE_IOERR_DELETE; + FilePath db_file = + DatabaseUtil::GetFullFilePathForVfsFile(db_tracker_, vfs_file_name); + if (!db_file.empty()) { + // In order to delete a journal file in incognito mode, we only need to + // close the open handle to it that's stored in the database tracker. + if (db_tracker_->IsIncognitoProfile()) { + if (db_tracker_->CloseIncognitoFileHandle(vfs_file_name)) + error_code = SQLITE_OK; + } else { + error_code = VfsBackend::DeleteFile(db_file, sync_dir); + } + + if ((error_code == SQLITE_IOERR_DELETE) && reschedule_count) { + // If the file could not be deleted, try again. + BrowserThread::PostDelayedTask( + BrowserThread::FILE, FROM_HERE, + NewRunnableMethod(this, + &DatabaseMessageFilter::DatabaseDeleteFile, + vfs_file_name, + sync_dir, + reply_msg, + reschedule_count - 1), + kDelayDeleteRetryMs); + return; + } + } + + DatabaseHostMsg_DeleteFile::WriteReplyParams(reply_msg, error_code); + Send(reply_msg); +} + +void DatabaseMessageFilter::OnDatabaseGetFileAttributes( + const string16& vfs_file_name, + IPC::Message* reply_msg) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); + int32 attributes = -1; + FilePath db_file = + DatabaseUtil::GetFullFilePathForVfsFile(db_tracker_, vfs_file_name); + if (!db_file.empty()) + attributes = VfsBackend::GetFileAttributes(db_file); + + DatabaseHostMsg_GetFileAttributes::WriteReplyParams( + reply_msg, attributes); + Send(reply_msg); +} + +void DatabaseMessageFilter::OnDatabaseGetFileSize( + const string16& vfs_file_name, IPC::Message* reply_msg) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); + int64 size = 0; + FilePath db_file = + DatabaseUtil::GetFullFilePathForVfsFile(db_tracker_, vfs_file_name); + if (!db_file.empty()) + size = VfsBackend::GetFileSize(db_file); + + DatabaseHostMsg_GetFileSize::WriteReplyParams(reply_msg, size); + Send(reply_msg); +} + +void DatabaseMessageFilter::OnDatabaseOpened(const string16& origin_identifier, + const string16& database_name, + const string16& description, + int64 estimated_size) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); + int64 database_size = 0; + int64 space_available = 0; + database_connections_.AddConnection(origin_identifier, database_name); + db_tracker_->DatabaseOpened(origin_identifier, database_name, description, + estimated_size, &database_size, &space_available); + Send(new DatabaseMsg_UpdateSize(origin_identifier, database_name, + database_size, space_available)); +} + +void DatabaseMessageFilter::OnDatabaseModified( + const string16& origin_identifier, + const string16& database_name) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); + if (!database_connections_.IsDatabaseOpened( + origin_identifier, database_name)) { + UserMetrics::RecordAction(UserMetricsAction("BadMessageTerminate_DBMF")); + BadMessageReceived(); + return; + } + + db_tracker_->DatabaseModified(origin_identifier, database_name); +} + +void DatabaseMessageFilter::OnDatabaseClosed(const string16& origin_identifier, + const string16& database_name) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); + if (!database_connections_.IsDatabaseOpened( + origin_identifier, database_name)) { + UserMetrics::RecordAction(UserMetricsAction("BadMessageTerminate_DBMF")); + BadMessageReceived(); + return; + } + + db_tracker_->DatabaseClosed(origin_identifier, database_name); + database_connections_.RemoveConnection(origin_identifier, database_name); +} + +void DatabaseMessageFilter::OnAllowDatabase(const std::string& origin_url, + const string16& name, + const string16& display_name, + unsigned long estimated_size, + IPC::Message* reply_msg) { + GURL url = GURL(origin_url); + ContentSetting content_setting = + host_content_settings_map_->GetContentSetting( + url, CONTENT_SETTINGS_TYPE_COOKIES, ""); + AllowDatabaseResponse(reply_msg, content_setting); +} + +void DatabaseMessageFilter::AllowDatabaseResponse( + IPC::Message* reply_msg, ContentSetting content_setting) { + DCHECK((content_setting == CONTENT_SETTING_ALLOW) || + (content_setting == CONTENT_SETTING_BLOCK) || + (content_setting == CONTENT_SETTING_SESSION_ONLY)); + DatabaseHostMsg_Allow::WriteReplyParams( + reply_msg, content_setting != CONTENT_SETTING_BLOCK); + Send(reply_msg); +} + +void DatabaseMessageFilter::OnDatabaseSizeChanged( + const string16& origin_identifier, + const string16& database_name, + int64 database_size, + int64 space_available) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); + if (database_connections_.IsOriginUsed(origin_identifier)) { + Send(new DatabaseMsg_UpdateSize(origin_identifier, database_name, + database_size, space_available)); + } +} + +void DatabaseMessageFilter::OnDatabaseScheduledForDeletion( + const string16& origin_identifier, + const string16& database_name) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); + Send(new DatabaseMsg_CloseImmediately(origin_identifier, database_name)); +} diff --git a/content/browser/renderer_host/database_message_filter.h b/content/browser/renderer_host/database_message_filter.h new file mode 100644 index 0000000..7d432e8 --- /dev/null +++ b/content/browser/renderer_host/database_message_filter.h @@ -0,0 +1,103 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CONTENT_BROWSER_RENDERER_HOST_DATABASE_MESSAGE_FILTER_H_ +#define CONTENT_BROWSER_RENDERER_HOST_DATABASE_MESSAGE_FILTER_H_ +#pragma once + +#include "base/hash_tables.h" +#include "base/string16.h" +#include "chrome/browser/browser_message_filter.h" +#include "chrome/common/content_settings.h" +#include "webkit/database/database_connections.h" +#include "webkit/database/database_tracker.h" + +class HostContentSettingsMap; + +class DatabaseMessageFilter + : public BrowserMessageFilter, + public webkit_database::DatabaseTracker::Observer { + public: + DatabaseMessageFilter( + webkit_database::DatabaseTracker* db_tracker, + HostContentSettingsMap *host_content_settings_map); + + // BrowserMessageFilter implementation. + virtual void OnChannelClosing(); + virtual void OverrideThreadForMessage(const IPC::Message& message, + BrowserThread::ID* thread); + virtual bool OnMessageReceived(const IPC::Message& message, + bool* message_was_ok); + + webkit_database::DatabaseTracker* database_tracker() const { + return db_tracker_.get(); + } + + private: + virtual ~DatabaseMessageFilter(); + + class PromptDelegate; + + void AddObserver(); + void RemoveObserver(); + + // VFS message handlers (file thread) + void OnDatabaseOpenFile(const string16& vfs_file_name, + int desired_flags, + IPC::Message* reply_msg); + void OnDatabaseDeleteFile(const string16& vfs_file_name, + const bool& sync_dir, + IPC::Message* reply_msg); + void OnDatabaseGetFileAttributes(const string16& vfs_file_name, + IPC::Message* reply_msg); + void OnDatabaseGetFileSize(const string16& vfs_file_name, + IPC::Message* reply_msg); + + // Database tracker message handlers (file thread) + void OnDatabaseOpened(const string16& origin_identifier, + const string16& database_name, + const string16& description, + int64 estimated_size); + void OnDatabaseModified(const string16& origin_identifier, + const string16& database_name); + void OnDatabaseClosed(const string16& origin_identifier, + const string16& database_name); + void OnAllowDatabase(const std::string& origin_url, + const string16& name, + const string16& display_name, + unsigned long estimated_size, + IPC::Message* reply_msg); + + // DatabaseTracker::Observer callbacks (file thread) + virtual void OnDatabaseSizeChanged(const string16& origin_identifier, + const string16& database_name, + int64 database_size, + int64 space_available); + virtual void OnDatabaseScheduledForDeletion(const string16& origin_identifier, + const string16& database_name); + + void DatabaseDeleteFile(const string16& vfs_file_name, + bool sync_dir, + IPC::Message* reply_msg, + int reschedule_count); + + // CookiePromptModalDialog response handler (io thread) + void AllowDatabaseResponse(IPC::Message* reply_msg, + ContentSetting content_setting); + + // The database tracker for the current profile. + scoped_refptr<webkit_database::DatabaseTracker> db_tracker_; + + // True if and only if this instance was added as an observer + // to DatabaseTracker. + bool observer_added_; + + // Keeps track of all DB connections opened by this renderer + webkit_database::DatabaseConnections database_connections_; + + // Used to look up permissions at database creation time. + scoped_refptr<HostContentSettingsMap> host_content_settings_map_; +}; + +#endif // CONTENT_BROWSER_RENDERER_HOST_DATABASE_MESSAGE_FILTER_H_ diff --git a/content/browser/renderer_host/file_utilities_message_filter.cc b/content/browser/renderer_host/file_utilities_message_filter.cc new file mode 100644 index 0000000..67377d6 --- /dev/null +++ b/content/browser/renderer_host/file_utilities_message_filter.cc @@ -0,0 +1,106 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "content/browser/renderer_host/file_utilities_message_filter.h" + +#include "base/file_util.h" +#include "chrome/browser/child_process_security_policy.h" +#include "chrome/common/file_utilities_messages.h" + + +FileUtilitiesMessageFilter::FileUtilitiesMessageFilter(int process_id) + : process_id_(process_id) { +} + +FileUtilitiesMessageFilter::~FileUtilitiesMessageFilter() { +} + +void FileUtilitiesMessageFilter::OverrideThreadForMessage( + const IPC::Message& message, + BrowserThread::ID* thread) { + if (IPC_MESSAGE_CLASS(message) == FileUtilitiesMsgStart) + *thread = BrowserThread::FILE; +} + +bool FileUtilitiesMessageFilter::OnMessageReceived(const IPC::Message& message, + bool* message_was_ok) { + bool handled = true; + IPC_BEGIN_MESSAGE_MAP_EX(FileUtilitiesMessageFilter, message, *message_was_ok) + IPC_MESSAGE_HANDLER(FileUtilitiesMsg_GetFileSize, OnGetFileSize) + IPC_MESSAGE_HANDLER(FileUtilitiesMsg_GetFileModificationTime, + OnGetFileModificationTime) + IPC_MESSAGE_HANDLER(FileUtilitiesMsg_OpenFile, OnOpenFile) + IPC_MESSAGE_UNHANDLED(handled = false) + IPC_END_MESSAGE_MAP() + return handled; +} + +void FileUtilitiesMessageFilter::OnGetFileSize(const FilePath& path, + int64* result) { + // Get file size only when the child process has been granted permission to + // upload the file. + if (!ChildProcessSecurityPolicy::GetInstance()->CanReadFile( + process_id_, path)) { + *result = -1; + return; + } + + base::PlatformFileInfo file_info; + file_info.size = 0; + file_util::GetFileInfo(path, &file_info); + *result = file_info.size; +} + +void FileUtilitiesMessageFilter::OnGetFileModificationTime( + const FilePath& path, base::Time* result) { + // Get file modification time only when the child process has been granted + // permission to upload the file. + if (!ChildProcessSecurityPolicy::GetInstance()->CanReadFile( + process_id_, path)) { + *result = base::Time(); + return; + } + + base::PlatformFileInfo file_info; + file_info.size = 0; + file_util::GetFileInfo(path, &file_info); + *result = file_info.last_modified; +} + +void FileUtilitiesMessageFilter::OnOpenFile( + const FilePath& path, + int mode, + IPC::PlatformFileForTransit* result) { + // Open the file only when the child process has been granted permission to + // upload the file. + // TODO(jianli): Do we need separate permission to control opening the file? + if (!ChildProcessSecurityPolicy::GetInstance()->CanReadFile( + process_id_, path)) { +#if defined(OS_WIN) + *result = base::kInvalidPlatformFileValue; +#elif defined(OS_POSIX) + *result = base::FileDescriptor(base::kInvalidPlatformFileValue, true); +#endif + return; + } + + base::PlatformFile file_handle = base::CreatePlatformFile( + path, + (mode == 0) ? (base::PLATFORM_FILE_OPEN | base::PLATFORM_FILE_READ) + : (base::PLATFORM_FILE_CREATE_ALWAYS | + base::PLATFORM_FILE_WRITE), + NULL, NULL); + +#if defined(OS_WIN) + // Duplicate the file handle so that the renderer process can access the file. + if (!DuplicateHandle(GetCurrentProcess(), file_handle, + peer_handle(), result, 0, false, + DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS)) { + // file_handle is closed whether or not DuplicateHandle succeeds. + *result = INVALID_HANDLE_VALUE; + } +#else + *result = base::FileDescriptor(file_handle, true); +#endif +} diff --git a/content/browser/renderer_host/file_utilities_message_filter.h b/content/browser/renderer_host/file_utilities_message_filter.h new file mode 100644 index 0000000..92cd2f5 --- /dev/null +++ b/content/browser/renderer_host/file_utilities_message_filter.h @@ -0,0 +1,48 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CONTENT_BROWSER_RENDERER_HOST_FILE_UTILITIES_MESSAGE_FILTER_H_ +#define CONTENT_BROWSER_RENDERER_HOST_FILE_UTILITIES_MESSAGE_FILTER_H_ + +#include "base/basictypes.h" +#include "base/file_path.h" +#include "chrome/browser/browser_message_filter.h" +#include "ipc/ipc_platform_file.h" + +namespace base { +struct PlatformFileInfo; +} + +namespace IPC { +class Message; +} + +class FileUtilitiesMessageFilter : public BrowserMessageFilter { + public: + explicit FileUtilitiesMessageFilter(int process_id); + + // BrowserMessageFilter implementation. + virtual void OverrideThreadForMessage(const IPC::Message& message, + BrowserThread::ID* thread); + virtual bool OnMessageReceived(const IPC::Message& message, + bool* message_was_ok); + private: + ~FileUtilitiesMessageFilter(); + + typedef void (*FileInfoWriteFunc)(IPC::Message* reply_msg, + const base::PlatformFileInfo& file_info); + + void OnGetFileSize(const FilePath& path, int64* result); + void OnGetFileModificationTime(const FilePath& path, base::Time* result); + void OnOpenFile(const FilePath& path, + int mode, + IPC::PlatformFileForTransit* result); + + // The ID of this process. + int process_id_; + + DISALLOW_IMPLICIT_CONSTRUCTORS(FileUtilitiesMessageFilter); +}; + +#endif // CONTENT_BROWSER_RENDERER_HOST_FILE_UTILITIES_MESSAGE_FILTER_H_ diff --git a/content/browser/renderer_host/global_request_id.h b/content/browser/renderer_host/global_request_id.h new file mode 100644 index 0000000..031026f --- /dev/null +++ b/content/browser/renderer_host/global_request_id.h @@ -0,0 +1,32 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CONTENT_BROWSER_RENDERER_HOST_GLOBAL_REQUEST_ID_H_ +#define CONTENT_BROWSER_RENDERER_HOST_GLOBAL_REQUEST_ID_H_ +#pragma once + +// Uniquely identifies a net::URLRequest. +struct GlobalRequestID { + GlobalRequestID() : child_id(-1), request_id(-1) { + } + + GlobalRequestID(int child_id, int request_id) + : child_id(child_id), + request_id(request_id) { + } + + // The unique ID of the child process (different from OS's PID). + int child_id; + + // The request ID (unique for the child). + int request_id; + + bool operator<(const GlobalRequestID& other) const { + if (child_id == other.child_id) + return request_id < other.request_id; + return child_id < other.child_id; + } +}; + +#endif // CHROME_BROWSER_RENDERER_HOST_GLOBAL_REQUEST_ID_H_ diff --git a/content/browser/renderer_host/gpu_message_filter.cc b/content/browser/renderer_host/gpu_message_filter.cc new file mode 100644 index 0000000..157b96d --- /dev/null +++ b/content/browser/renderer_host/gpu_message_filter.cc @@ -0,0 +1,151 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "content/browser/renderer_host/gpu_message_filter.h" + +#include "base/callback.h" +#include "chrome/browser/gpu_process_host_ui_shim.h" +#include "chrome/common/gpu_create_command_buffer_config.h" +#include "chrome/common/gpu_messages.h" +#include "chrome/common/render_messages.h" + +GpuMessageFilter::GpuMessageFilter(int render_process_id) + : render_process_id_(render_process_id) { +} + +// WeakPtrs to a GpuMessageFilter need to be Invalidated from +// the same thread from which they were created. +GpuMessageFilter::~GpuMessageFilter() { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); +} + +void GpuMessageFilter::OverrideThreadForMessage( + const IPC::Message& message, + BrowserThread::ID* thread) { + if (IPC_MESSAGE_CLASS(message) == GpuMsgStart) + *thread = BrowserThread::UI; +} + +bool GpuMessageFilter::OnMessageReceived( + const IPC::Message& message, + bool* message_was_ok) { + bool handled = true; + IPC_BEGIN_MESSAGE_MAP_EX(GpuMessageFilter, message, *message_was_ok) + IPC_MESSAGE_HANDLER(GpuHostMsg_EstablishGpuChannel, + OnEstablishGpuChannel) + IPC_MESSAGE_HANDLER_DELAY_REPLY(GpuHostMsg_SynchronizeGpu, + OnSynchronizeGpu) + IPC_MESSAGE_HANDLER_DELAY_REPLY(GpuHostMsg_CreateViewCommandBuffer, + OnCreateViewCommandBuffer) + IPC_MESSAGE_UNHANDLED(handled = false) + IPC_END_MESSAGE_MAP_EX() + return handled; +} + +void GpuMessageFilter::OnDestruct() const { + BrowserThread::DeleteOnUIThread::Destruct(this); +} + +// Callbacks used in this file. +namespace { + +class EstablishChannelCallback + : public CallbackRunner<Tuple2<const IPC::ChannelHandle&, + const GPUInfo&> > { + public: + explicit EstablishChannelCallback(GpuMessageFilter* filter): + filter_(filter->AsWeakPtr()) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + } + + virtual void RunWithParams(const TupleType& params) { + DispatchToMethod(this, &EstablishChannelCallback::Send, params); + } + + void Send(const IPC::ChannelHandle& channel, + const GPUInfo& gpu_info) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + ViewMsg_GpuChannelEstablished* reply = + new ViewMsg_GpuChannelEstablished(channel, gpu_info); + // If the renderer process is performing synchronous initialization, + // it needs to handle this message before receiving the reply for + // the synchronous GpuHostMsg_SynchronizeGpu message. + reply->set_unblock(true); + + if (filter_) + filter_->Send(reply); + } + + private: + base::WeakPtr<GpuMessageFilter> filter_; +}; + +class SynchronizeCallback : public CallbackRunner<Tuple0> { + public: + SynchronizeCallback(GpuMessageFilter* filter, IPC::Message* reply): + filter_(filter->AsWeakPtr()), + reply_(reply) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + } + + virtual void RunWithParams(const TupleType& params) { + DispatchToMethod(this, &SynchronizeCallback::Send, params); + } + + void Send() { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + if (filter_) + filter_->Send(reply_); + } + + private: + base::WeakPtr<GpuMessageFilter> filter_; + IPC::Message* reply_; +}; + +class CreateCommandBufferCallback : public CallbackRunner<Tuple1<int32> > { + public: + CreateCommandBufferCallback(GpuMessageFilter* filter, + IPC::Message* reply) : + filter_(filter->AsWeakPtr()), + reply_(reply) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + } + + virtual void RunWithParams(const TupleType& params) { + DispatchToMethod(this, &CreateCommandBufferCallback::Send, params); + } + + void Send(int32 route_id) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + GpuHostMsg_CreateViewCommandBuffer::WriteReplyParams(reply_, route_id); + if (filter_) + filter_->Send(reply_); + } + + private: + base::WeakPtr<GpuMessageFilter> filter_; + IPC::Message* reply_; +}; + +} // namespace + +void GpuMessageFilter::OnEstablishGpuChannel() { + GpuProcessHostUIShim::GetInstance()->EstablishGpuChannel( + render_process_id_, new EstablishChannelCallback(this)); +} + +void GpuMessageFilter::OnSynchronizeGpu(IPC::Message* reply) { + GpuProcessHostUIShim::GetInstance()-> + Synchronize(new SynchronizeCallback(this, reply)); +} + +void GpuMessageFilter::OnCreateViewCommandBuffer( + int32 render_view_id, + const GPUCreateCommandBufferConfig& init_params, + IPC::Message* reply) { + GpuProcessHostUIShim::GetInstance()->CreateViewCommandBuffer( + render_view_id, render_process_id_, init_params, + new CreateCommandBufferCallback(this, reply)); +} diff --git a/content/browser/renderer_host/gpu_message_filter.h b/content/browser/renderer_host/gpu_message_filter.h new file mode 100644 index 0000000..a81fa2a --- /dev/null +++ b/content/browser/renderer_host/gpu_message_filter.h @@ -0,0 +1,51 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CONTENT_BROWSER_RENDERER_HOST_GPU_MESSAGE_FILTER_H_ +#define CONTENT_BROWSER_RENDERER_HOST_GPU_MESSAGE_FILTER_H_ +#pragma once + +#include "chrome/browser/browser_message_filter.h" + +struct GPUCreateCommandBufferConfig; +class GPUInfo; + +namespace IPC { +struct ChannelHandle; +} + +// A message filter for messages from the renderer to the GpuProcessHost +// in the browser. Such messages are typically destined for the GPU process, +// but need to be mediated by the browser. +class GpuMessageFilter : public BrowserMessageFilter, + public base::SupportsWeakPtr<GpuMessageFilter> { + public: + explicit GpuMessageFilter(int render_process_id); + + // BrowserMessageFilter methods: + virtual void OverrideThreadForMessage(const IPC::Message& message, + BrowserThread::ID* thread); + virtual bool OnMessageReceived(const IPC::Message& message, + bool* message_was_ok); + virtual void OnDestruct() const; + + private: + friend class BrowserThread; + friend class DeleteTask<GpuMessageFilter>; + virtual ~GpuMessageFilter(); + + // Message handlers called on the browser IO thread: + void OnEstablishGpuChannel(); + void OnSynchronizeGpu(IPC::Message* reply); + void OnCreateViewCommandBuffer( + int32 render_view_id, + const GPUCreateCommandBufferConfig& init_params, + IPC::Message* reply); + + int render_process_id_; + + DISALLOW_COPY_AND_ASSIGN(GpuMessageFilter); +}; + +#endif // CONTENT_BROWSER_RENDERER_HOST_GPU_MESSAGE_FILTER_H_ diff --git a/content/browser/renderer_host/mock_render_process_host.cc b/content/browser/renderer_host/mock_render_process_host.cc new file mode 100644 index 0000000..e0a2fb0 --- /dev/null +++ b/content/browser/renderer_host/mock_render_process_host.cc @@ -0,0 +1,156 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "content/browser/renderer_host/mock_render_process_host.h" + +#include "chrome/browser/child_process_security_policy.h" + +MockRenderProcessHost::MockRenderProcessHost(Profile* profile) + : RenderProcessHost(profile), + transport_dib_(NULL), + bad_msg_count_(0), + factory_(NULL) { + // Child process security operations can't be unit tested unless we add + // ourselves as an existing child process. + ChildProcessSecurityPolicy::GetInstance()->Add(id()); +} + +MockRenderProcessHost::~MockRenderProcessHost() { + ChildProcessSecurityPolicy::GetInstance()->Remove(id()); + delete transport_dib_; + if (factory_) + factory_->Remove(this); +} + +bool MockRenderProcessHost::Init( + bool is_accessibility_enabled, bool is_extensions_process) { + return true; +} + +int MockRenderProcessHost::GetNextRoutingID() { + static int prev_routing_id = 0; + return ++prev_routing_id; +} + +void MockRenderProcessHost::CancelResourceRequests(int render_widget_id) { +} + +void MockRenderProcessHost::CrossSiteClosePageACK( + const ViewMsg_ClosePage_Params& params) { +} + +bool MockRenderProcessHost::WaitForUpdateMsg(int render_widget_id, + const base::TimeDelta& max_delay, + IPC::Message* msg) { + return false; +} + +void MockRenderProcessHost::ReceivedBadMessage() { + ++bad_msg_count_; +} + +void MockRenderProcessHost::WidgetRestored() { +} + +void MockRenderProcessHost::WidgetHidden() { +} + +void MockRenderProcessHost::ViewCreated() { +} + +void MockRenderProcessHost::AddWord(const string16& word) { +} + +void MockRenderProcessHost::SendVisitedLinkTable( + base::SharedMemory* table_memory) { +} + +void MockRenderProcessHost::AddVisitedLinks( + const VisitedLinkCommon::Fingerprints& links) { +} + +void MockRenderProcessHost::ResetVisitedLinks() { +} + +bool MockRenderProcessHost::FastShutdownIfPossible() { + // We aren't actually going to do anything, but set |fast_shutdown_started_| + // to true so that tests know we've been called. + fast_shutdown_started_ = true; + return true; +} + +bool MockRenderProcessHost::SendWithTimeout(IPC::Message* msg, int timeout_ms) { + // Save the message in the sink. Just ignore timeout_ms. + sink_.OnMessageReceived(*msg); + delete msg; + return true; +} + +base::ProcessHandle MockRenderProcessHost::GetHandle() { + return base::kNullProcessHandle; +} + +bool MockRenderProcessHost::Send(IPC::Message* msg) { + // Save the message in the sink. + sink_.OnMessageReceived(*msg); + delete msg; + return true; +} + +TransportDIB* MockRenderProcessHost::GetTransportDIB(TransportDIB::Id dib_id) { + if (transport_dib_) + return transport_dib_; +#if defined(OS_WIN) + HANDLE duped; + DuplicateHandle(GetCurrentProcess(), dib_id.handle, GetCurrentProcess(), + &duped, 0, TRUE, DUPLICATE_SAME_ACCESS); + transport_dib_ = TransportDIB::Map(duped); +#elif defined(OS_MACOSX) + // On Mac, TransportDIBs are always created in the browser, so we cannot map + // one from a dib_id. + transport_dib_ = TransportDIB::Create(100 * 100 * 4, 0); +#elif defined(OS_POSIX) + transport_dib_ = TransportDIB::Map(dib_id); +#endif + + return transport_dib_; +} + +bool MockRenderProcessHost::OnMessageReceived(const IPC::Message& msg) { + return false; +} + +void MockRenderProcessHost::OnChannelConnected(int32 peer_pid) { +} + +MockRenderProcessHostFactory::MockRenderProcessHostFactory() {} + +MockRenderProcessHostFactory::~MockRenderProcessHostFactory() { + // Detach this object from MockRenderProcesses to prevent STLDeleteElements() + // from calling MockRenderProcessHostFactory::Remove(). + for (ScopedVector<MockRenderProcessHost>::iterator it = processes_.begin(); + it != processes_.end(); ++it) { + (*it)->SetFactory(NULL); + } +} + +RenderProcessHost* MockRenderProcessHostFactory::CreateRenderProcessHost( + Profile* profile) const { + MockRenderProcessHost* host = new MockRenderProcessHost(profile); + if (host) { + processes_.push_back(host); + host->SetFactory(this); + } + return host; +} + +void MockRenderProcessHostFactory::Remove(MockRenderProcessHost* host) const { + for (ScopedVector<MockRenderProcessHost>::iterator it = processes_.begin(); + it != processes_.end(); ++it) { + if (*it == host) { + processes_.weak_erase(it); + break; + } + } +} diff --git a/content/browser/renderer_host/mock_render_process_host.h b/content/browser/renderer_host/mock_render_process_host.h new file mode 100644 index 0000000..abe0f82 --- /dev/null +++ b/content/browser/renderer_host/mock_render_process_host.h @@ -0,0 +1,102 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CONTENT_BROWSER_RENDERER_HOST_MOCK_RENDER_PROCESS_HOST_H_ +#define CONTENT_BROWSER_RENDERER_HOST_MOCK_RENDER_PROCESS_HOST_H_ +#pragma once + +#include "base/basictypes.h" +#include "base/scoped_vector.h" +#include "content/browser/renderer_host/render_process_host.h" +#include "ipc/ipc_test_sink.h" + +class MockRenderProcessHostFactory; +class TransportDIB; +class URLRequestContextGetter; + +// A mock render process host that has no corresponding renderer process. All +// IPC messages are sent into the message sink for inspection by tests. +class MockRenderProcessHost : public RenderProcessHost { + public: + explicit MockRenderProcessHost(Profile* profile); + virtual ~MockRenderProcessHost(); + + // Provides access to all IPC messages that would have been sent to the + // renderer via this RenderProcessHost. + IPC::TestSink& sink() { return sink_; } + + // Provides tests access to the max page ID currently used for this process. + int max_page_id() const { return max_page_id_; } + + // Provides test access to how many times a bad message has been received. + int bad_msg_count() const { return bad_msg_count_; } + + // RenderProcessHost implementation (public portion). + virtual bool Init(bool is_accessibility_enabled, bool is_extensions_process); + virtual int GetNextRoutingID(); + virtual void CancelResourceRequests(int render_widget_id); + virtual void CrossSiteClosePageACK(const ViewMsg_ClosePage_Params& params); + virtual bool WaitForUpdateMsg(int render_widget_id, + const base::TimeDelta& max_delay, + IPC::Message* msg); + virtual void ReceivedBadMessage(); + virtual void WidgetRestored(); + virtual void WidgetHidden(); + virtual void ViewCreated(); + virtual void AddWord(const string16& word); + virtual void SendVisitedLinkTable(base::SharedMemory* table_memory); + virtual void AddVisitedLinks( + const VisitedLinkCommon::Fingerprints& visited_links); + virtual void ResetVisitedLinks(); + virtual bool FastShutdownIfPossible(); + virtual bool SendWithTimeout(IPC::Message* msg, int timeout_ms); + virtual base::ProcessHandle GetHandle(); + + virtual TransportDIB* GetTransportDIB(TransportDIB::Id dib_id); + + // IPC::Channel::Sender via RenderProcessHost. + virtual bool Send(IPC::Message* msg); + + // IPC::Channel::Listener via RenderProcessHost. + virtual bool OnMessageReceived(const IPC::Message& msg); + virtual void OnChannelConnected(int32 peer_pid); + + // Attaches the factory object so we can remove this object in its destructor + // and prevent MockRenderProcessHostFacotry from deleting it. + void SetFactory(const MockRenderProcessHostFactory* factory) { + factory_ = factory; + } + + private: + // Stores IPC messages that would have been sent to the renderer. + IPC::TestSink sink_; + TransportDIB* transport_dib_; + int bad_msg_count_; + const MockRenderProcessHostFactory* factory_; + + DISALLOW_COPY_AND_ASSIGN(MockRenderProcessHost); +}; + +class MockRenderProcessHostFactory : public RenderProcessHostFactory { + public: + MockRenderProcessHostFactory(); + virtual ~MockRenderProcessHostFactory(); + + virtual RenderProcessHost* CreateRenderProcessHost(Profile* profile) const; + + // Removes the given MockRenderProcessHost from the MockRenderProcessHost list + // without deleting it. When a test deletes a MockRenderProcessHost, we need + // to remove it from |processes_| to prevent it from being deleted twice. + void Remove(MockRenderProcessHost* host) const; + + private: + // A list of MockRenderProcessHosts created by this object. This list is used + // for deleting all MockRenderProcessHosts that have not deleted by a test in + // the destructor and prevent them from being leaked. + mutable ScopedVector<MockRenderProcessHost> processes_; + + DISALLOW_COPY_AND_ASSIGN(MockRenderProcessHostFactory); +}; + +#endif // CONTENT_BROWSER_RENDERER_HOST_MOCK_RENDER_PROCESS_HOST_H_ diff --git a/content/browser/renderer_host/pepper_file_message_filter.cc b/content/browser/renderer_host/pepper_file_message_filter.cc new file mode 100644 index 0000000..9f7f9d9 --- /dev/null +++ b/content/browser/renderer_host/pepper_file_message_filter.cc @@ -0,0 +1,201 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "content/browser/renderer_host/pepper_file_message_filter.h" + +#include "base/callback.h" +#include "base/file_util.h" +#include "base/file_path.h" +#include "base/process_util.h" +#include "chrome/browser/browser_thread.h" +#include "chrome/browser/profiles/profile.h" +#include "chrome/browser/renderer_host/browser_render_process_host.h" +#include "chrome/common/child_process_host.h" +#include "chrome/common/pepper_file_messages.h" +#include "ipc/ipc_platform_file.h" + +#if defined(OS_POSIX) +#include "base/file_descriptor_posix.h" +#endif + +PepperFileMessageFilter::PepperFileMessageFilter( + int child_id, Profile* profile) { + pepper_path_ = profile->GetPath().Append(FILE_PATH_LITERAL("Pepper Data")); +} + +PepperFileMessageFilter::~PepperFileMessageFilter() { + // This function should be called on the IO thread. + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); +} + +void PepperFileMessageFilter::OverrideThreadForMessage( + const IPC::Message& message, + BrowserThread::ID* thread) { + if (IPC_MESSAGE_CLASS(message) == PepperFileMsgStart) + *thread = BrowserThread::FILE; +} + +bool PepperFileMessageFilter::OnMessageReceived( + const IPC::Message& message, + bool* message_was_ok) { + bool handled = true; + IPC_BEGIN_MESSAGE_MAP_EX(PepperFileMessageFilter, message, *message_was_ok) + IPC_MESSAGE_HANDLER(PepperFileMsg_OpenFile, OnPepperOpenFile) + IPC_MESSAGE_HANDLER(PepperFileMsg_RenameFile, OnPepperRenameFile) + IPC_MESSAGE_HANDLER(PepperFileMsg_DeleteFileOrDir, OnPepperDeleteFileOrDir) + IPC_MESSAGE_HANDLER(PepperFileMsg_CreateDir, OnPepperCreateDir) + IPC_MESSAGE_HANDLER(PepperFileMsg_QueryFile, OnPepperQueryFile) + IPC_MESSAGE_HANDLER(PepperFileMsg_GetDirContents, OnPepperGetDirContents) + IPC_MESSAGE_UNHANDLED(handled = false) + IPC_END_MESSAGE_MAP_EX() + return handled; +} + +void PepperFileMessageFilter::OnDestruct() const { + BrowserThread::DeleteOnIOThread::Destruct(this); +} + +// Called on the FILE thread: +void PepperFileMessageFilter::OnPepperOpenFile( + const FilePath& path, + int flags, + base::PlatformFileError* error, + IPC::PlatformFileForTransit* file) { + FilePath full_path = MakePepperPath(path); + if (full_path.empty()) { + *error = base::PLATFORM_FILE_ERROR_ACCESS_DENIED; + *file = IPC::InvalidPlatformFileForTransit(); + return; + } + + base::PlatformFile file_handle = base::CreatePlatformFile( + full_path, flags, NULL, error); + + if (*error != base::PLATFORM_FILE_OK) { + *file = IPC::InvalidPlatformFileForTransit(); + return; + } + + // Make sure we didn't try to open a directory: directory fd shouldn't pass + // to untrusted processes because they open security holes. + base::PlatformFileInfo info; + if (!base::GetPlatformFileInfo(file_handle, &info) || info.is_directory) { + // When in doubt, throw it out. + *error = base::PLATFORM_FILE_ERROR_ACCESS_DENIED; + *file = IPC::InvalidPlatformFileForTransit(); + return; + } + +#if defined(OS_WIN) + // Duplicate the file handle so that the renderer process can access the file. + if (!DuplicateHandle(GetCurrentProcess(), file_handle, + peer_handle(), file, 0, false, + DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS)) { + // file_handle is closed whether or not DuplicateHandle succeeds. + *error = base::PLATFORM_FILE_ERROR_ACCESS_DENIED; + *file = INVALID_HANDLE_VALUE; + } +#else + *file = base::FileDescriptor(file_handle, true); +#endif +} + +void PepperFileMessageFilter::OnPepperRenameFile( + const FilePath& path_from, + const FilePath& path_to, + base::PlatformFileError* error) { + FilePath full_path_from = MakePepperPath(path_from); + FilePath full_path_to = MakePepperPath(path_to); + if (full_path_from.empty() || full_path_to.empty()) { + *error = base::PLATFORM_FILE_ERROR_ACCESS_DENIED; + return; + } + + bool result = file_util::Move(full_path_from, full_path_to); + *error = result ? base::PLATFORM_FILE_OK + : base::PLATFORM_FILE_ERROR_ACCESS_DENIED; +} + +void PepperFileMessageFilter::OnPepperDeleteFileOrDir( + const FilePath& path, + bool recursive, + base::PlatformFileError* error) { + FilePath full_path = MakePepperPath(path); + if (full_path.empty()) { + *error = base::PLATFORM_FILE_ERROR_ACCESS_DENIED; + return; + } + + bool result = file_util::Delete(full_path, recursive); + *error = result ? base::PLATFORM_FILE_OK + : base::PLATFORM_FILE_ERROR_ACCESS_DENIED; +} + +void PepperFileMessageFilter::OnPepperCreateDir( + const FilePath& path, + base::PlatformFileError* error) { + FilePath full_path = MakePepperPath(path); + if (full_path.empty()) { + *error = base::PLATFORM_FILE_ERROR_ACCESS_DENIED; + return; + } + + bool result = file_util::CreateDirectory(full_path); + *error = result ? base::PLATFORM_FILE_OK + : base::PLATFORM_FILE_ERROR_ACCESS_DENIED; +} + +void PepperFileMessageFilter::OnPepperQueryFile( + const FilePath& path, + base::PlatformFileInfo* info, + base::PlatformFileError* error) { + FilePath full_path = MakePepperPath(path); + if (full_path.empty()) { + *error = base::PLATFORM_FILE_ERROR_ACCESS_DENIED; + return; + } + + bool result = file_util::GetFileInfo(full_path, info); + *error = result ? base::PLATFORM_FILE_OK + : base::PLATFORM_FILE_ERROR_ACCESS_DENIED; +} + +void PepperFileMessageFilter::OnPepperGetDirContents( + const FilePath& path, + webkit::ppapi::DirContents* contents, + base::PlatformFileError* error) { + FilePath full_path = MakePepperPath(path); + if (full_path.empty()) { + *error = base::PLATFORM_FILE_ERROR_ACCESS_DENIED; + return; + } + + contents->clear(); + + file_util::FileEnumerator enumerator( + full_path, false, + static_cast<file_util::FileEnumerator::FILE_TYPE>( + file_util::FileEnumerator::FILES | + file_util::FileEnumerator::DIRECTORIES | + file_util::FileEnumerator::INCLUDE_DOT_DOT)); + + while (!enumerator.Next().empty()) { + file_util::FileEnumerator::FindInfo info; + enumerator.GetFindInfo(&info); + webkit::ppapi::DirEntry entry = { + file_util::FileEnumerator::GetFilename(info), + file_util::FileEnumerator::IsDirectory(info) + }; + contents->push_back(entry); + } + + *error = base::PLATFORM_FILE_OK; +} + +FilePath PepperFileMessageFilter::MakePepperPath(const FilePath& base_path) { + if (base_path.IsAbsolute() || base_path.ReferencesParent()) { + return FilePath(); + } + return pepper_path_.Append(base_path); +} diff --git a/content/browser/renderer_host/pepper_file_message_filter.h b/content/browser/renderer_host/pepper_file_message_filter.h new file mode 100644 index 0000000..a06d9a4 --- /dev/null +++ b/content/browser/renderer_host/pepper_file_message_filter.h @@ -0,0 +1,72 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CONTENT_BROWSER_RENDERER_HOST_PEPPER_FILE_MESSAGE_FILTER_H_ +#define CONTENT_BROWSER_RENDERER_HOST_PEPPER_FILE_MESSAGE_FILTER_H_ +#pragma once + +#include <string> +#include <vector> + +#include "base/file_path.h" +#include "base/process.h" +#include "base/ref_counted.h" +#include "base/task.h" +#include "build/build_config.h" +#include "chrome/browser/browser_message_filter.h" +#include "ipc/ipc_platform_file.h" +#include "webkit/plugins/ppapi/dir_contents.h" + +class Profile; + +// A message filter for Pepper-specific File I/O messages. +class PepperFileMessageFilter : public BrowserMessageFilter { + public: + PepperFileMessageFilter(int child_id, Profile* profile); + + // BrowserMessageFilter methods: + virtual void OverrideThreadForMessage(const IPC::Message& message, + BrowserThread::ID* thread); + virtual bool OnMessageReceived(const IPC::Message& message, + bool* message_was_ok); + virtual void OnDestruct() const; + + private: + friend class BrowserThread; + friend class DeleteTask<PepperFileMessageFilter>; + virtual ~PepperFileMessageFilter(); + + // Called on the FILE thread: + void OnPepperOpenFile(const FilePath& path, + int flags, + base::PlatformFileError* error, + IPC::PlatformFileForTransit* file); + void OnPepperRenameFile(const FilePath& path_from, + const FilePath& path_to, + base::PlatformFileError* error); + void OnPepperDeleteFileOrDir(const FilePath& path, + bool recursive, + base::PlatformFileError* error); + void OnPepperCreateDir(const FilePath& path, + base::PlatformFileError* error); + void OnPepperQueryFile(const FilePath& path, + base::PlatformFileInfo* info, + base::PlatformFileError* error); + void OnPepperGetDirContents(const FilePath& path, + webkit::ppapi::DirContents* contents, + base::PlatformFileError* error); + + FilePath MakePepperPath(const FilePath& base_path); + + // The channel associated with the renderer connection. This pointer is not + // owned by this class. + IPC::Channel* channel_; + + // The base path for the pepper data. + FilePath pepper_path_; + + DISALLOW_COPY_AND_ASSIGN(PepperFileMessageFilter); +}; + +#endif // CONTENT_BROWSER_RENDERER_HOST_PEPPER_FILE_MESSAGE_FILTER_H_ diff --git a/content/browser/renderer_host/pepper_message_filter.cc b/content/browser/renderer_host/pepper_message_filter.cc new file mode 100644 index 0000000..6a77171 --- /dev/null +++ b/content/browser/renderer_host/pepper_message_filter.cc @@ -0,0 +1,280 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "content/browser/renderer_host/pepper_message_filter.h" + +#include "base/basictypes.h" +#include "base/process_util.h" +#include "base/threading/worker_pool.h" +#include "chrome/browser/browser_thread.h" +#include "chrome/browser/net/chrome_url_request_context.h" +#include "chrome/browser/profiles/profile.h" +#include "chrome/browser/renderer_host/browser_render_process_host.h" +#include "chrome/common/pepper_messages.h" +#include "net/base/address_list.h" +#include "net/base/host_port_pair.h" +#include "net/base/host_resolver.h" +#include "net/url_request/url_request_context.h" +#include "webkit/plugins/ppapi/ppb_flash_impl.h" + +#if defined(ENABLE_FLAPPER_HACKS) +#include <netdb.h> +#include <string.h> +#include <sys/socket.h> +#include <sys/types.h> +#include <unistd.h> + +// Make sure the storage in |PP_Flash_NetAddress| is big enough. (Do it here +// since the data is opaque elsewhere.) +COMPILE_ASSERT(sizeof(reinterpret_cast<PP_Flash_NetAddress*>(0)->data) >= + sizeof(sockaddr_storage), PP_Flash_NetAddress_data_too_small); +#endif // ENABLE_FLAPPER_HACKS + +const PP_Flash_NetAddress kInvalidNetAddress = { 0 }; + +PepperMessageFilter::PepperMessageFilter(Profile* profile) + : profile_(profile), + request_context_(profile_->GetRequestContext()) { +} + +PepperMessageFilter::~PepperMessageFilter() {} + +bool PepperMessageFilter::OnMessageReceived(const IPC::Message& msg, + bool* message_was_ok) { +#if defined(ENABLE_FLAPPER_HACKS) + bool handled = true; + IPC_BEGIN_MESSAGE_MAP_EX(PepperMessageFilter, msg, *message_was_ok) + IPC_MESSAGE_HANDLER(PepperMsg_ConnectTcp, OnConnectTcp) + IPC_MESSAGE_HANDLER(PepperMsg_ConnectTcpAddress, OnConnectTcpAddress) + IPC_MESSAGE_UNHANDLED(handled = false) + IPC_END_MESSAGE_MAP_EX() + return handled; +#else + return false; +#endif // ENABLE_FLAPPER_HACKS +} + +#if defined(ENABLE_FLAPPER_HACKS) + +namespace { + +bool ValidateNetAddress(const PP_Flash_NetAddress& addr) { + if (addr.size < sizeof(sa_family_t)) + return false; + + // TODO(viettrungluu): more careful validation? + // Just do a size check for AF_INET. + if (reinterpret_cast<const sockaddr*>(addr.data)->sa_family == AF_INET && + addr.size >= sizeof(sockaddr_in)) + return true; + + // Ditto for AF_INET6. + if (reinterpret_cast<const sockaddr*>(addr.data)->sa_family == AF_INET6 && + addr.size >= sizeof(sockaddr_in6)) + return true; + + // Reject everything else. + return false; +} + +PP_Flash_NetAddress SockaddrToNetAddress(const struct sockaddr* sa, + socklen_t sa_length) { + PP_Flash_NetAddress addr; + CHECK_LE(sa_length, sizeof(addr.data)); + addr.size = sa_length; + memcpy(addr.data, sa, addr.size); + return addr; +} + +int ConnectTcpSocket(const PP_Flash_NetAddress& addr, + PP_Flash_NetAddress* local_addr_out, + PP_Flash_NetAddress* remote_addr_out) { + *local_addr_out = kInvalidNetAddress; + *remote_addr_out = kInvalidNetAddress; + + const struct sockaddr* sa = + reinterpret_cast<const struct sockaddr*>(addr.data); + socklen_t sa_len = addr.size; + int fd = socket(sa->sa_family, SOCK_STREAM, IPPROTO_TCP); + if (fd == -1) + return -1; + if (connect(fd, sa, sa_len) != 0) { + close(fd); + return -1; + } + + // Get the local address. + socklen_t local_length = sizeof(local_addr_out->data); + if (getsockname(fd, reinterpret_cast<struct sockaddr*>(local_addr_out->data), + &local_length) == -1 || + local_length > sizeof(local_addr_out->data)) { + close(fd); + return -1; + } + + // The remote address is just the address we connected to. + *remote_addr_out = addr; + + return fd; +} + +} // namespace + +class PepperMessageFilter::LookupRequest { + public: + LookupRequest(PepperMessageFilter* pepper_message_filter, + net::HostResolver* resolver, + int routing_id, + int request_id, + const net::HostResolver::RequestInfo& request_info) + : ALLOW_THIS_IN_INITIALIZER_LIST( + net_callback_(this, &LookupRequest::OnLookupFinished)), + pepper_message_filter_(pepper_message_filter), + resolver_(resolver), + routing_id_(routing_id), + request_id_(request_id), + request_info_(request_info) { + } + + void Start() { + int result = resolver_.Resolve(request_info_, &addresses_, &net_callback_, + net::BoundNetLog()); + if (result != net::ERR_IO_PENDING) + OnLookupFinished(result); + } + + private: + void OnLookupFinished(int /*result*/) { + pepper_message_filter_->ConnectTcpLookupFinished( + routing_id_, request_id_, addresses_); + delete this; + } + + // HostResolver will call us using this callback when resolution is complete. + net::CompletionCallbackImpl<LookupRequest> net_callback_; + + PepperMessageFilter* pepper_message_filter_; + net::SingleRequestHostResolver resolver_; + + int routing_id_; + int request_id_; + net::HostResolver::RequestInfo request_info_; + + net::AddressList addresses_; + + DISALLOW_COPY_AND_ASSIGN(LookupRequest); +}; + +void PepperMessageFilter::OnConnectTcp(int routing_id, + int request_id, + const std::string& host, + uint16 port) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + + net::URLRequestContext* req_context = + request_context_->GetURLRequestContext(); + net::HostResolver::RequestInfo request_info(net::HostPortPair(host, port)); + + // The lookup request will delete itself on completion. + LookupRequest* lookup_request = + new LookupRequest(this, req_context->host_resolver(), + routing_id, request_id, request_info); + lookup_request->Start(); +} + +void PepperMessageFilter::OnConnectTcpAddress(int routing_id, + int request_id, + const PP_Flash_NetAddress& addr) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + + // Validate the address and then continue (doing |connect()|) on a worker + // thread. + if (!ValidateNetAddress(addr) || + !base::WorkerPool::PostTask(FROM_HERE, + NewRunnableMethod( + this, + &PepperMessageFilter::ConnectTcpAddressOnWorkerThread, + routing_id, request_id, addr), + true)) { + SendConnectTcpACKError(routing_id, request_id); + } +} + +bool PepperMessageFilter::SendConnectTcpACKError(int routing_id, + int request_id) { + return Send( + new PepperMsg_ConnectTcpACK(routing_id, request_id, + IPC::InvalidPlatformFileForTransit(), + kInvalidNetAddress, kInvalidNetAddress)); +} + +void PepperMessageFilter::ConnectTcpLookupFinished( + int routing_id, + int request_id, + const net::AddressList& addresses) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + + // If the lookup returned addresses, continue (doing |connect()|) on a worker + // thread. + if (!addresses.head() || + !base::WorkerPool::PostTask(FROM_HERE, + NewRunnableMethod( + this, + &PepperMessageFilter::ConnectTcpOnWorkerThread, + routing_id, request_id, addresses), + true)) { + SendConnectTcpACKError(routing_id, request_id); + } +} + +void PepperMessageFilter::ConnectTcpOnWorkerThread(int routing_id, + int request_id, + net::AddressList addresses) { + IPC::PlatformFileForTransit socket_for_transit = + IPC::InvalidPlatformFileForTransit(); + PP_Flash_NetAddress local_addr = kInvalidNetAddress; + PP_Flash_NetAddress remote_addr = kInvalidNetAddress; + + for (const struct addrinfo* ai = addresses.head(); ai; ai = ai->ai_next) { + PP_Flash_NetAddress addr = SockaddrToNetAddress(ai->ai_addr, + ai->ai_addrlen); + int fd = ConnectTcpSocket(addr, &local_addr, &remote_addr); + if (fd != -1) { + socket_for_transit = base::FileDescriptor(fd, true); + break; + } + } + + BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, + NewRunnableMethod( + this, &PepperMessageFilter::Send, + new PepperMsg_ConnectTcpACK( + routing_id, request_id, + socket_for_transit, local_addr, remote_addr))); +} + +// TODO(vluu): Eliminate duplication between this and +// |ConnectTcpOnWorkerThread()|. +void PepperMessageFilter::ConnectTcpAddressOnWorkerThread( + int routing_id, + int request_id, + PP_Flash_NetAddress addr) { + IPC::PlatformFileForTransit socket_for_transit = + IPC::InvalidPlatformFileForTransit(); + PP_Flash_NetAddress local_addr = kInvalidNetAddress; + PP_Flash_NetAddress remote_addr = kInvalidNetAddress; + + int fd = ConnectTcpSocket(addr, &local_addr, &remote_addr); + if (fd != -1) + socket_for_transit = base::FileDescriptor(fd, true); + + BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, + NewRunnableMethod( + this, &PepperMessageFilter::Send, + new PepperMsg_ConnectTcpACK( + routing_id, request_id, + socket_for_transit, local_addr, remote_addr))); +} + +#endif // ENABLE_FLAPPER_HACKS diff --git a/content/browser/renderer_host/pepper_message_filter.h b/content/browser/renderer_host/pepper_message_filter.h new file mode 100644 index 0000000..31f920c --- /dev/null +++ b/content/browser/renderer_host/pepper_message_filter.h @@ -0,0 +1,70 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CONTENT_BROWSER_RENDERER_HOST_PEPPER_MESSAGE_FILTER_H_ +#define CONTENT_BROWSER_RENDERER_HOST_PEPPER_MESSAGE_FILTER_H_ +#pragma once + +#include <string> + +#include "base/basictypes.h" +#include "base/process.h" +#include "chrome/browser/browser_message_filter.h" +#include "ipc/ipc_channel_proxy.h" +#include "ppapi/c/private/ppb_flash.h" + +class Profile; +class URLRequestContextGetter; + +namespace net { +class AddressList; +} + +class PepperMessageFilter : public BrowserMessageFilter { + public: + explicit PepperMessageFilter(Profile* profile); + virtual ~PepperMessageFilter(); + + private: + // BrowserMessageFilter methods. + virtual bool OnMessageReceived(const IPC::Message& message, + bool* message_was_ok); + +#if defined(ENABLE_FLAPPER_HACKS) + // Message handlers. + void OnConnectTcp(int routing_id, + int request_id, + const std::string& host, + uint16 port); + void OnConnectTcpAddress(int routing_id, + int request_id, + const PP_Flash_NetAddress& address); + + // |Send()| a |PepperMsg_ConnectTcpACK|, which reports an error. + bool SendConnectTcpACKError(int routing_id, + int request_id); + + // Used by |OnConnectTcp()| (below). + class LookupRequest; + friend class LookupRequest; + + // Continuation of |OnConnectTcp()|. + void ConnectTcpLookupFinished(int routing_id, + int request_id, + const net::AddressList& addresses); + void ConnectTcpOnWorkerThread(int routing_id, + int request_id, + net::AddressList addresses); + + // Continuation of |OnConnectTcpAddress()|. + void ConnectTcpAddressOnWorkerThread(int routing_id, + int request_id, + PP_Flash_NetAddress addr); +#endif // ENABLE_FLAPPER_HACKS + + Profile* profile_; + scoped_refptr<URLRequestContextGetter> request_context_; +}; + +#endif // CONTENT_BROWSER_RENDERER_HOST_PEPPER_MESSAGE_FILTER_H_ diff --git a/content/browser/renderer_host/redirect_to_file_resource_handler.cc b/content/browser/renderer_host/redirect_to_file_resource_handler.cc new file mode 100644 index 0000000..bb18a4a --- /dev/null +++ b/content/browser/renderer_host/redirect_to_file_resource_handler.cc @@ -0,0 +1,221 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "content/browser/renderer_host/redirect_to_file_resource_handler.h" + +#include "base/file_util.h" +#include "base/file_util_proxy.h" +#include "base/logging.h" +#include "base/platform_file.h" +#include "base/task.h" +#include "chrome/common/resource_response.h" +#include "content/browser/renderer_host/resource_dispatcher_host.h" +#include "net/base/file_stream.h" +#include "net/base/io_buffer.h" +#include "net/base/mime_sniffer.h" +#include "net/base/net_errors.h" +#include "webkit/blob/deletable_file_reference.h" + +using webkit_blob::DeletableFileReference; + +// TODO(darin): Use the buffer sizing algorithm from AsyncResourceHandler. +static const int kReadBufSize = 32768; + +RedirectToFileResourceHandler::RedirectToFileResourceHandler( + ResourceHandler* next_handler, + int process_id, + ResourceDispatcherHost* host) + : callback_factory_(ALLOW_THIS_IN_INITIALIZER_LIST(this)), + host_(host), + next_handler_(next_handler), + process_id_(process_id), + request_id_(-1), + buf_(new net::GrowableIOBuffer()), + buf_write_pending_(false), + write_cursor_(0), + write_callback_(ALLOW_THIS_IN_INITIALIZER_LIST(this), + &RedirectToFileResourceHandler::DidWriteToFile), + write_callback_pending_(false) { +} + +bool RedirectToFileResourceHandler::OnUploadProgress(int request_id, + uint64 position, + uint64 size) { + return next_handler_->OnUploadProgress(request_id, position, size); +} + +bool RedirectToFileResourceHandler::OnRequestRedirected( + int request_id, + const GURL& new_url, + ResourceResponse* response, + bool* defer) { + return next_handler_->OnRequestRedirected(request_id, new_url, response, + defer); +} + +bool RedirectToFileResourceHandler::OnResponseStarted( + int request_id, + ResourceResponse* response) { + if (response->response_head.status.is_success()) { + DCHECK(deletable_file_ && !deletable_file_->path().empty()); + response->response_head.download_file_path = deletable_file_->path(); + } + return next_handler_->OnResponseStarted(request_id, response); +} + +bool RedirectToFileResourceHandler::OnWillStart(int request_id, + const GURL& url, + bool* defer) { + request_id_ = request_id; + if (!deletable_file_) { + // Defer starting the request until we have created the temporary file. + // TODO(darin): This is sub-optimal. We should not delay starting the + // network request like this. + *defer = true; + base::FileUtilProxy::CreateTemporary( + BrowserThread::GetMessageLoopProxyForThread(BrowserThread::FILE), + callback_factory_.NewCallback( + &RedirectToFileResourceHandler::DidCreateTemporaryFile)); + return true; + } + return next_handler_->OnWillStart(request_id, url, defer); +} + +bool RedirectToFileResourceHandler::OnWillRead(int request_id, + net::IOBuffer** buf, + int* buf_size, + int min_size) { + DCHECK(min_size == -1); + + if (!buf_->capacity()) + buf_->SetCapacity(kReadBufSize); + + // We should have paused this network request already if the buffer is full. + DCHECK(!BufIsFull()); + + *buf = buf_; + *buf_size = buf_->RemainingCapacity(); + + buf_write_pending_ = true; + return true; +} + +bool RedirectToFileResourceHandler::OnReadCompleted(int request_id, + int* bytes_read) { + if (!buf_write_pending_) { + // Ignore spurious OnReadCompleted! PauseRequest(true) called from within + // OnReadCompleted tells the ResourceDispatcherHost that we did not consume + // the data. PauseRequest(false) then repeats the last OnReadCompleted + // call. We pause the request so that we can copy our buffer to disk, so + // we need to consume the data now. The ResourceDispatcherHost pause + // mechanism does not fit our use case very well. + // TODO(darin): Fix the ResourceDispatcherHost to avoid this hack! + return true; + } + + buf_write_pending_ = false; + + // We use the buffer's offset field to record the end of the buffer. + + int new_offset = buf_->offset() + *bytes_read; + DCHECK(new_offset <= buf_->capacity()); + buf_->set_offset(new_offset); + + if (BufIsFull()) + host_->PauseRequest(process_id_, request_id, true); + + if (*bytes_read > 0) + next_handler_->OnDataDownloaded(request_id, *bytes_read); + + return WriteMore(); +} + +bool RedirectToFileResourceHandler::OnResponseCompleted( + int request_id, + const net::URLRequestStatus& status, + const std::string& security_info) { + return next_handler_->OnResponseCompleted(request_id, status, security_info); +} + +void RedirectToFileResourceHandler::OnRequestClosed() { + // We require this explicit call to Close since file_stream_ was constructed + // directly from a PlatformFile. + file_stream_->Close(); + file_stream_.reset(); + deletable_file_ = NULL; + + next_handler_->OnRequestClosed(); +} + +RedirectToFileResourceHandler::~RedirectToFileResourceHandler() { + DCHECK(!file_stream_.get()); +} + +void RedirectToFileResourceHandler::DidCreateTemporaryFile( + base::PlatformFileError /*error_code*/, + base::PassPlatformFile file_handle, + FilePath file_path) { + deletable_file_ = DeletableFileReference::GetOrCreate( + file_path, + BrowserThread::GetMessageLoopProxyForThread(BrowserThread::FILE)); + file_stream_.reset(new net::FileStream(file_handle.ReleaseValue(), + base::PLATFORM_FILE_WRITE | + base::PLATFORM_FILE_ASYNC)); + host_->RegisterDownloadedTempFile( + process_id_, request_id_, deletable_file_.get()); + host_->StartDeferredRequest(process_id_, request_id_); +} + +void RedirectToFileResourceHandler::DidWriteToFile(int result) { + write_callback_pending_ = false; + + bool failed = false; + if (result > 0) { + write_cursor_ += result; + failed = !WriteMore(); + } else { + failed = true; + } + + if (failed) + host_->CancelRequest(process_id_, request_id_, false); +} + +bool RedirectToFileResourceHandler::WriteMore() { + DCHECK(file_stream_.get()); + for (;;) { + if (write_cursor_ == buf_->offset()) { + // We've caught up to the network load, but it may be in the process of + // appending more data to the buffer. + if (!buf_write_pending_) { + if (BufIsFull()) + host_->PauseRequest(process_id_, request_id_, false); + buf_->set_offset(0); + write_cursor_ = 0; + } + return true; + } + if (write_callback_pending_) + return true; + DCHECK(write_cursor_ < buf_->offset()); + int rv = file_stream_->Write(buf_->StartOfBuffer() + write_cursor_, + buf_->offset() - write_cursor_, + &write_callback_); + if (rv == net::ERR_IO_PENDING) { + write_callback_pending_ = true; + return true; + } + if (rv < 0) + return false; + write_cursor_ += rv; + } +} + +bool RedirectToFileResourceHandler::BufIsFull() const { + // This is a hack to workaround BufferedResourceHandler's inability to + // deal with a ResourceHandler that returns a buffer size of less than + // 2 * net::kMaxBytesToSniff from its OnWillRead method. + // TODO(darin): Fix this retardation! + return buf_->RemainingCapacity() <= (2 * net::kMaxBytesToSniff); +} diff --git a/content/browser/renderer_host/redirect_to_file_resource_handler.h b/content/browser/renderer_host/redirect_to_file_resource_handler.h new file mode 100644 index 0000000..34fb082 --- /dev/null +++ b/content/browser/renderer_host/redirect_to_file_resource_handler.h @@ -0,0 +1,89 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CONTENT_BROWSER_RENDERER_HOST_REDIRECT_TO_FILE_RESOURCE_HANDLER_H_ +#define CONTENT_BROWSER_RENDERER_HOST_REDIRECT_TO_FILE_RESOURCE_HANDLER_H_ + +#include "base/file_path.h" +#include "base/platform_file.h" +#include "base/ref_counted.h" +#include "base/scoped_callback_factory.h" +#include "base/scoped_ptr.h" +#include "chrome/browser/renderer_host/resource_handler.h" +#include "net/base/completion_callback.h" + +class RefCountedPlatformFile; +class ResourceDispatcherHost; + +namespace net { +class FileStream; +class GrowableIOBuffer; +} + +namespace webkit_blob { +class DeletableFileReference; +} + +// Redirects network data to a file. This is intended to be layered in front +// of either the AsyncResourceHandler or the SyncResourceHandler. +class RedirectToFileResourceHandler : public ResourceHandler { + public: + RedirectToFileResourceHandler( + ResourceHandler* next_handler, + int process_id, + ResourceDispatcherHost* resource_dispatcher_host); + + // ResourceHandler implementation: + virtual bool OnUploadProgress(int request_id, uint64 position, uint64 size); + virtual bool OnRequestRedirected(int request_id, const GURL& new_url, + ResourceResponse* response, bool* defer); + virtual bool OnResponseStarted(int request_id, ResourceResponse* response); + virtual bool OnWillStart(int request_id, const GURL& url, bool* defer); + virtual bool OnWillRead(int request_id, net::IOBuffer** buf, int* buf_size, + int min_size); + virtual bool OnReadCompleted(int request_id, int* bytes_read); + virtual bool OnResponseCompleted(int request_id, + const net::URLRequestStatus& status, + const std::string& security_info); + virtual void OnRequestClosed(); + + private: + virtual ~RedirectToFileResourceHandler(); + void DidCreateTemporaryFile(base::PlatformFileError error_code, + base::PassPlatformFile file_handle, + FilePath file_path); + void DidWriteToFile(int result); + bool WriteMore(); + bool BufIsFull() const; + + base::ScopedCallbackFactory<RedirectToFileResourceHandler> callback_factory_; + + ResourceDispatcherHost* host_; + scoped_refptr<ResourceHandler> next_handler_; + int process_id_; + int request_id_; + + // We allocate a single, fixed-size IO buffer (buf_) used to read from the + // network (buf_write_pending_ is true while the system is copying data into + // buf_), and then write this buffer out to disk (write_callback_pending_ is + // true while writing to disk). Reading from the network is suspended while + // the buffer is full (BufIsFull returns true). The write_cursor_ member + // tracks the offset into buf_ that we are writing to disk. + + scoped_refptr<net::GrowableIOBuffer> buf_; + bool buf_write_pending_; + int write_cursor_; + + scoped_ptr<net::FileStream> file_stream_; + net::CompletionCallbackImpl<RedirectToFileResourceHandler> write_callback_; + bool write_callback_pending_; + + // We create a DeletableFileReference for the temp file created as + // a result of the download. + scoped_refptr<webkit_blob::DeletableFileReference> deletable_file_; + + DISALLOW_COPY_AND_ASSIGN(RedirectToFileResourceHandler); +}; + +#endif // CONTENT_BROWSER_RENDERER_HOST_REDIRECT_TO_FILE_RESOURCE_HANDLER_H_ diff --git a/content/browser/renderer_host/render_message_filter.cc b/content/browser/renderer_host/render_message_filter.cc new file mode 100644 index 0000000..388bddb --- /dev/null +++ b/content/browser/renderer_host/render_message_filter.cc @@ -0,0 +1,1718 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "content/browser/renderer_host/render_message_filter.h" + +#include <map> + +#include "base/command_line.h" +#include "base/file_util.h" +#include "base/metrics/histogram.h" +#include "base/process_util.h" +#include "base/shared_memory.h" +#include "base/sys_string_conversions.h" +#include "base/threading/worker_pool.h" +#include "base/threading/thread.h" +#include "base/utf_string_conversions.h" +#include "chrome/browser/automation/automation_resource_message_filter.h" +#include "chrome/browser/browser_process.h" +#include "chrome/browser/browser_thread.h" +#include "chrome/browser/child_process_security_policy.h" +#include "chrome/browser/chrome_plugin_browsing_context.h" +#include "chrome/browser/clipboard_dispatcher.h" +#include "chrome/browser/download/download_types.h" +#include "chrome/browser/extensions/extension_message_service.h" +#include "chrome/browser/host_zoom_map.h" +#include "chrome/browser/metrics/histogram_synchronizer.h" +#include "chrome/browser/metrics/user_metrics.h" +#include "chrome/browser/nacl_host/nacl_process_host.h" +#include "chrome/browser/net/chrome_url_request_context.h" +#include "chrome/browser/net/predictor_api.h" +#include "chrome/browser/notifications/desktop_notification_service.h" +#include "chrome/browser/notifications/notifications_prefs_cache.h" +#include "chrome/browser/platform_util.h" +#include "chrome/browser/plugin_process_host.h" +#include "chrome/browser/plugin_service.h" +#include "chrome/browser/ppapi_plugin_process_host.h" +#include "chrome/browser/printing/print_job_manager.h" +#include "chrome/browser/printing/printer_query.h" +#include "chrome/browser/profiles/profile.h" +#include "chrome/browser/renderer_host/browser_render_process_host.h" +#include "chrome/browser/spellchecker_platform_engine.h" +#include "chrome/browser/task_manager/task_manager.h" +#include "chrome/common/chrome_switches.h" +#include "chrome/common/extensions/extension_file_util.h" +#include "chrome/common/extensions/extension_message_bundle.h" +#include "chrome/common/notification_service.h" +#include "chrome/common/render_messages.h" +#include "chrome/common/render_messages_params.h" +#include "chrome/common/url_constants.h" +#include "content/browser/renderer_host/render_view_host_delegate.h" +#include "content/browser/renderer_host/render_view_host_notification_task.h" +#include "content/browser/renderer_host/render_widget_helper.h" +#include "ipc/ipc_channel_handle.h" +#include "net/base/cookie_monster.h" +#include "net/base/io_buffer.h" +#include "net/base/keygen_handler.h" +#include "net/base/mime_util.h" +#include "net/base/net_errors.h" +#include "net/disk_cache/disk_cache.h" +#include "net/http/http_cache.h" +#include "net/http/http_network_layer.h" +#include "net/url_request/url_request_context.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebNotificationPresenter.h" +#include "webkit/glue/context_menu.h" +#include "webkit/glue/webcookie.h" +#include "webkit/glue/webkit_glue.h" +#include "webkit/plugins/npapi/plugin_group.h" +#include "webkit/plugins/npapi/plugin_list.h" +#include "webkit/plugins/npapi/webplugin.h" +#include "webkit/plugins/npapi/webplugininfo.h" + +#if defined(OS_CHROMEOS) +#include "chrome/browser/chromeos/plugin_selection_policy.h" +#endif +#if defined(OS_MACOSX) +#include "chrome/common/font_descriptor_mac.h" +#include "chrome/common/font_loader_mac.h" +#endif +#if defined(OS_POSIX) +#include "base/file_descriptor_posix.h" +#endif +#if defined(OS_WIN) +#include "chrome/common/child_process_host.h" +#endif +#if defined(USE_NSS) +#include "chrome/browser/ui/crypto_module_password_dialog.h" +#endif +#if defined(USE_TCMALLOC) +#include "chrome/browser/browser_about_handler.h" +#endif + +using net::CookieStore; +using WebKit::WebCache; + +namespace { + +const int kPluginsRefreshThresholdInSeconds = 3; + +// Context menus are somewhat complicated. We need to intercept them here on +// the I/O thread to add any spelling suggestions to them. After that's done, +// we need to forward the modified message to the UI thread and the normal +// message forwarding isn't set up for sending modified messages. +// +// Therefore, this class dispatches the IPC message to the RenderProcessHost +// with the given ID (if possible) to emulate the normal dispatch. +class ContextMenuMessageDispatcher : public Task { + public: + ContextMenuMessageDispatcher( + int render_process_id, + const ViewHostMsg_ContextMenu& context_menu_message) + : render_process_id_(render_process_id), + context_menu_message_(context_menu_message) { + } + + void Run() { + RenderProcessHost* host = + RenderProcessHost::FromID(render_process_id_); + if (host) + host->OnMessageReceived(context_menu_message_); + } + + private: + int render_process_id_; + const ViewHostMsg_ContextMenu context_menu_message_; + + DISALLOW_COPY_AND_ASSIGN(ContextMenuMessageDispatcher); +}; + +// Completes a clipboard write initiated by the renderer. The write must be +// performed on the UI thread because the clipboard service from the IO thread +// cannot create windows so it cannot be the "owner" of the clipboard's +// contents. +class WriteClipboardTask : public Task { + public: + explicit WriteClipboardTask(ui::Clipboard::ObjectMap* objects) + : objects_(objects) {} + ~WriteClipboardTask() {} + + void Run() { + g_browser_process->clipboard()->WriteObjects(*objects_.get()); + } + + private: + scoped_ptr<ui::Clipboard::ObjectMap> objects_; +}; + +void RenderParamsFromPrintSettings(const printing::PrintSettings& settings, + ViewMsg_Print_Params* params) { + DCHECK(params); + params->page_size = settings.page_setup_device_units().physical_size(); + params->printable_size.SetSize( + settings.page_setup_device_units().content_area().width(), + settings.page_setup_device_units().content_area().height()); + params->margin_top = settings.page_setup_device_units().content_area().x(); + params->margin_left = settings.page_setup_device_units().content_area().y(); + params->dpi = settings.dpi(); + // Currently hardcoded at 1.25. See PrintSettings' constructor. + params->min_shrink = settings.min_shrink; + // Currently hardcoded at 2.0. See PrintSettings' constructor. + params->max_shrink = settings.max_shrink; + // Currently hardcoded at 72dpi. See PrintSettings' constructor. + params->desired_dpi = settings.desired_dpi; + // Always use an invalid cookie. + params->document_cookie = 0; + params->selection_only = settings.selection_only; + params->supports_alpha_blend = settings.supports_alpha_blend(); +} + +// Common functionality for converting a sync renderer message to a callback +// function in the browser. Derive from this, create it on the heap when +// issuing your callback. When done, write your reply parameters into +// reply_msg(), and then call SendReplyAndDeleteThis(). +class RenderMessageCompletionCallback { + public: + RenderMessageCompletionCallback(RenderMessageFilter* filter, + IPC::Message* reply_msg) + : filter_(filter), + reply_msg_(reply_msg) { + } + + virtual ~RenderMessageCompletionCallback() { + } + + RenderMessageFilter* filter() { return filter_.get(); } + IPC::Message* reply_msg() { return reply_msg_; } + + void SendReplyAndDeleteThis() { + filter_->Send(reply_msg_); + delete this; + } + + private: + scoped_refptr<RenderMessageFilter> filter_; + IPC::Message* reply_msg_; +}; + +class ClearCacheCompletion : public RenderMessageCompletionCallback, + public net::CompletionCallback { + public: + ClearCacheCompletion(RenderMessageFilter* filter, + IPC::Message* reply_msg) + : RenderMessageCompletionCallback(filter, reply_msg) { + } + + virtual void RunWithParams(const Tuple1<int>& params) { + ViewHostMsg_ClearCache::WriteReplyParams(reply_msg(), params.a); + SendReplyAndDeleteThis(); + } +}; + +class OpenChannelToNpapiPluginCallback : public RenderMessageCompletionCallback, + public PluginProcessHost::Client { + public: + OpenChannelToNpapiPluginCallback(RenderMessageFilter* filter, + IPC::Message* reply_msg) + : RenderMessageCompletionCallback(filter, reply_msg) { + } + + virtual int ID() { + return filter()->render_process_id(); + } + + virtual bool OffTheRecord() { + return filter()->off_the_record(); + } + + virtual void SetPluginInfo(const webkit::npapi::WebPluginInfo& info) { + info_ = info; + } + + virtual void OnChannelOpened(const IPC::ChannelHandle& handle) { + WriteReplyAndDeleteThis(handle); + } + + virtual void OnError() { + WriteReplyAndDeleteThis(IPC::ChannelHandle()); + } + + private: + void WriteReplyAndDeleteThis(const IPC::ChannelHandle& handle) { + ViewHostMsg_OpenChannelToPlugin::WriteReplyParams(reply_msg(), + handle, info_); + SendReplyAndDeleteThis(); + } + + webkit::npapi::WebPluginInfo info_; +}; + +class OpenChannelToPpapiPluginCallback : public RenderMessageCompletionCallback, + public PpapiPluginProcessHost::Client { + public: + OpenChannelToPpapiPluginCallback(RenderMessageFilter* filter, + IPC::Message* reply_msg) + : RenderMessageCompletionCallback(filter, reply_msg) { + } + + virtual void GetChannelInfo(base::ProcessHandle* renderer_handle, + int* renderer_id) { + *renderer_handle = filter()->peer_handle(); + *renderer_id = filter()->render_process_id(); + } + + virtual void OnChannelOpened(base::ProcessHandle plugin_process_handle, + const IPC::ChannelHandle& channel_handle) { + ViewHostMsg_OpenChannelToPepperPlugin::WriteReplyParams( + reply_msg(), plugin_process_handle, channel_handle); + SendReplyAndDeleteThis(); + } +}; + +// Class to assist with clearing out the cache when we want to preserve +// the sslhostinfo entries. It's not very efficient, but its just for debug. +class DoomEntriesHelper { + public: + explicit DoomEntriesHelper(disk_cache::Backend* backend) + : backend_(backend), + entry_(NULL), + iter_(NULL), + ALLOW_THIS_IN_INITIALIZER_LIST(callback_(this, + &DoomEntriesHelper::CacheCallback)), + user_callback_(NULL) { + } + + void ClearCache(ClearCacheCompletion* callback) { + user_callback_ = callback; + return CacheCallback(net::OK); // Start clearing the cache. + } + + private: + void CacheCallback(int result) { + do { + if (result != net::OK) { + user_callback_->RunWithParams(Tuple1<int>(result)); + delete this; + return; + } + + if (entry_) { + // Doom all entries except those with snapstart information. + std::string key = entry_->GetKey(); + if (key.find("sslhostinfo:") != 0) { + entry_->Doom(); + backend_->EndEnumeration(&iter_); + iter_ = NULL; // We invalidated our iterator - start from the top! + } + entry_->Close(); + entry_ = NULL; + } + result = backend_->OpenNextEntry(&iter_, &entry_, &callback_); + } while (result != net::ERR_IO_PENDING); + } + + disk_cache::Backend* backend_; + disk_cache::Entry* entry_; + void* iter_; + net::CompletionCallbackImpl<DoomEntriesHelper> callback_; + ClearCacheCompletion* user_callback_; +}; + +} // namespace + +RenderMessageFilter::RenderMessageFilter( + int render_process_id, + PluginService* plugin_service, + Profile* profile, + RenderWidgetHelper* render_widget_helper) + : resource_dispatcher_host_(g_browser_process->resource_dispatcher_host()), + plugin_service_(plugin_service), + print_job_manager_(g_browser_process->print_job_manager()), + profile_(profile), + content_settings_(profile->GetHostContentSettingsMap()), + ALLOW_THIS_IN_INITIALIZER_LIST(resolve_proxy_msg_helper_(this, NULL)), + extensions_request_context_(profile->GetRequestContextForExtensions()), + render_widget_helper_(render_widget_helper), + notification_prefs_( + profile->GetDesktopNotificationService()->prefs_cache()), + host_zoom_map_(profile->GetHostZoomMap()), + off_the_record_(profile->IsOffTheRecord()), + webkit_context_(profile->GetWebKitContext()), + render_process_id_(render_process_id) { + request_context_ = profile_->GetRequestContext(); + DCHECK(request_context_); + + render_widget_helper_->Init(render_process_id_, resource_dispatcher_host_); +#if defined(OS_CHROMEOS) + cloud_print_enabled_ = true; +#else + cloud_print_enabled_ = CommandLine::ForCurrentProcess()->HasSwitch( + switches::kEnableCloudPrint); +#endif +} + +RenderMessageFilter::~RenderMessageFilter() { + // This function should be called on the IO thread. + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); +} + +// Called on the IPC thread: +bool RenderMessageFilter::OnMessageReceived(const IPC::Message& message, + bool* message_was_ok) { + bool handled = true; + IPC_BEGIN_MESSAGE_MAP_EX(RenderMessageFilter, message, *message_was_ok) + // On Linux we need to dispatch these messages to the UI2 thread + // because we cannot make X calls from the IO thread. Mac + // doesn't have windowed plug-ins so we handle the messages in + // the UI thread. On Windows, we intercept the messages and + // handle them directly. +#if !defined(OS_MACOSX) + IPC_MESSAGE_HANDLER_DELAY_REPLY(ViewHostMsg_GetScreenInfo, OnGetScreenInfo) + IPC_MESSAGE_HANDLER_DELAY_REPLY(ViewHostMsg_GetWindowRect, OnGetWindowRect) + IPC_MESSAGE_HANDLER_DELAY_REPLY(ViewHostMsg_GetRootWindowRect, + OnGetRootWindowRect) +#endif + + IPC_MESSAGE_HANDLER(ViewHostMsg_GenerateRoutingID, OnGenerateRoutingID) + + IPC_MESSAGE_HANDLER(ViewHostMsg_CreateWindow, OnMsgCreateWindow) + IPC_MESSAGE_HANDLER(ViewHostMsg_CreateWidget, OnMsgCreateWidget) + IPC_MESSAGE_HANDLER(ViewHostMsg_CreateFullscreenWidget, + OnMsgCreateFullscreenWidget) + IPC_MESSAGE_HANDLER(ViewHostMsg_SetCookie, OnSetCookie) + IPC_MESSAGE_HANDLER_DELAY_REPLY(ViewHostMsg_GetCookies, OnGetCookies) + IPC_MESSAGE_HANDLER_DELAY_REPLY(ViewHostMsg_GetRawCookies, OnGetRawCookies) + IPC_MESSAGE_HANDLER(ViewHostMsg_DeleteCookie, OnDeleteCookie) + IPC_MESSAGE_HANDLER_DELAY_REPLY(ViewHostMsg_CookiesEnabled, + OnCookiesEnabled) +#if defined(OS_MACOSX) + IPC_MESSAGE_HANDLER(ViewHostMsg_LoadFont, OnLoadFont) +#endif +#if defined(OS_WIN) // This hack is Windows-specific. + IPC_MESSAGE_HANDLER(ViewHostMsg_PreCacheFont, OnPreCacheFont) +#endif + IPC_MESSAGE_HANDLER_DELAY_REPLY(ViewHostMsg_GetPlugins, OnGetPlugins) + IPC_MESSAGE_HANDLER_DELAY_REPLY(ViewHostMsg_GetPluginInfo, OnGetPluginInfo) + IPC_MESSAGE_HANDLER(ViewHostMsg_DownloadUrl, OnDownloadUrl) + IPC_MESSAGE_HANDLER_GENERIC(ViewHostMsg_ContextMenu, + OnReceiveContextMenuMsg(message)) + IPC_MESSAGE_HANDLER_DELAY_REPLY(ViewHostMsg_OpenChannelToPlugin, + OnOpenChannelToPlugin) + IPC_MESSAGE_HANDLER_DELAY_REPLY(ViewHostMsg_OpenChannelToPepperPlugin, + OnOpenChannelToPepperPlugin) + IPC_MESSAGE_HANDLER_DELAY_REPLY(ViewHostMsg_LaunchNaCl, OnLaunchNaCl) + IPC_MESSAGE_HANDLER(ViewHostMsg_SpellChecker_PlatformCheckSpelling, + OnPlatformCheckSpelling) + IPC_MESSAGE_HANDLER(ViewHostMsg_SpellChecker_PlatformFillSuggestionList, + OnPlatformFillSuggestionList) + IPC_MESSAGE_HANDLER_DELAY_REPLY(ViewHostMsg_GetDocumentTag, + OnGetDocumentTag) + IPC_MESSAGE_HANDLER(ViewHostMsg_DocumentWithTagClosed, + OnDocumentWithTagClosed) + IPC_MESSAGE_HANDLER(ViewHostMsg_ShowSpellingPanel, OnShowSpellingPanel) + IPC_MESSAGE_HANDLER(ViewHostMsg_UpdateSpellingPanelWithMisspelledWord, + OnUpdateSpellingPanelWithMisspelledWord) + IPC_MESSAGE_HANDLER(ViewHostMsg_DnsPrefetch, OnDnsPrefetch) + IPC_MESSAGE_HANDLER(ViewHostMsg_RendererHistograms, OnRendererHistograms) + IPC_MESSAGE_HANDLER_GENERIC(ViewHostMsg_UpdateRect, + render_widget_helper_->DidReceiveUpdateMsg(message)) + IPC_MESSAGE_HANDLER(ViewHostMsg_ClipboardWriteObjectsAsync, + OnClipboardWriteObjectsAsync) + IPC_MESSAGE_HANDLER(ViewHostMsg_ClipboardWriteObjectsSync, + OnClipboardWriteObjectsSync) + IPC_MESSAGE_HANDLER_DELAY_REPLY(ViewHostMsg_ClipboardIsFormatAvailable, + OnClipboardIsFormatAvailable) + IPC_MESSAGE_HANDLER_DELAY_REPLY(ViewHostMsg_ClipboardReadText, + OnClipboardReadText) + IPC_MESSAGE_HANDLER_DELAY_REPLY(ViewHostMsg_ClipboardReadAsciiText, + OnClipboardReadAsciiText) + IPC_MESSAGE_HANDLER_DELAY_REPLY(ViewHostMsg_ClipboardReadHTML, + OnClipboardReadHTML) +#if defined(OS_MACOSX) + IPC_MESSAGE_HANDLER(ViewHostMsg_ClipboardFindPboardWriteStringAsync, + OnClipboardFindPboardWriteString) +#endif + IPC_MESSAGE_HANDLER_DELAY_REPLY(ViewHostMsg_ClipboardReadAvailableTypes, + OnClipboardReadAvailableTypes) + IPC_MESSAGE_HANDLER_DELAY_REPLY(ViewHostMsg_ClipboardReadData, + OnClipboardReadData) + IPC_MESSAGE_HANDLER_DELAY_REPLY(ViewHostMsg_ClipboardReadFilenames, + OnClipboardReadFilenames) + IPC_MESSAGE_HANDLER(ViewHostMsg_CheckNotificationPermission, + OnCheckNotificationPermission) + IPC_MESSAGE_HANDLER(ViewHostMsg_RevealFolderInOS, OnRevealFolderInOS) + IPC_MESSAGE_HANDLER(ViewHostMsg_GetCPBrowsingContext, + OnGetCPBrowsingContext) +#if defined(OS_WIN) + IPC_MESSAGE_HANDLER(ViewHostMsg_DuplicateSection, OnDuplicateSection) +#endif +#if defined(OS_POSIX) + IPC_MESSAGE_HANDLER(ViewHostMsg_AllocateSharedMemoryBuffer, + OnAllocateSharedMemoryBuffer) +#endif +#if defined(OS_CHROMEOS) + IPC_MESSAGE_HANDLER_DELAY_REPLY(ViewHostMsg_AllocateTempFileForPrinting, + OnAllocateTempFileForPrinting) + IPC_MESSAGE_HANDLER(ViewHostMsg_TempFileForPrintingWritten, + OnTempFileForPrintingWritten) +#endif + IPC_MESSAGE_HANDLER(ViewHostMsg_ResourceTypeStats, OnResourceTypeStats) + IPC_MESSAGE_HANDLER(ViewHostMsg_V8HeapStats, OnV8HeapStats) + IPC_MESSAGE_HANDLER(ViewHostMsg_DidZoomURL, OnDidZoomURL) + IPC_MESSAGE_HANDLER_DELAY_REPLY(ViewHostMsg_ResolveProxy, OnResolveProxy) + IPC_MESSAGE_HANDLER_DELAY_REPLY(ViewHostMsg_GetDefaultPrintSettings, + OnGetDefaultPrintSettings) + IPC_MESSAGE_HANDLER_DELAY_REPLY(ViewHostMsg_ScriptedPrint, OnScriptedPrint) +#if defined(OS_MACOSX) + IPC_MESSAGE_HANDLER(ViewHostMsg_AllocTransportDIB, OnAllocTransportDIB) + IPC_MESSAGE_HANDLER(ViewHostMsg_FreeTransportDIB, OnFreeTransportDIB) +#endif + IPC_MESSAGE_HANDLER(ViewHostMsg_OpenChannelToExtension, + OnOpenChannelToExtension) + IPC_MESSAGE_HANDLER(ViewHostMsg_OpenChannelToTab, OnOpenChannelToTab) + IPC_MESSAGE_HANDLER(ViewHostMsg_CloseCurrentConnections, + OnCloseCurrentConnections) + IPC_MESSAGE_HANDLER(ViewHostMsg_SetCacheMode, OnSetCacheMode) + IPC_MESSAGE_HANDLER_DELAY_REPLY(ViewHostMsg_ClearCache, OnClearCache) + IPC_MESSAGE_HANDLER(ViewHostMsg_DidGenerateCacheableMetadata, + OnCacheableMetadataAvailable) + IPC_MESSAGE_HANDLER(ViewHostMsg_EnableSpdy, OnEnableSpdy) + IPC_MESSAGE_HANDLER_DELAY_REPLY(ViewHostMsg_Keygen, OnKeygen) + IPC_MESSAGE_HANDLER_DELAY_REPLY(ViewHostMsg_GetExtensionMessageBundle, + OnGetExtensionMessageBundle) +#if defined(USE_TCMALLOC) + IPC_MESSAGE_HANDLER(ViewHostMsg_RendererTcmalloc, OnRendererTcmalloc) +#endif + IPC_MESSAGE_HANDLER(ViewHostMsg_AsyncOpenFile, OnAsyncOpenFile) + IPC_MESSAGE_UNHANDLED(handled = false) + IPC_END_MESSAGE_MAP_EX() + + return handled; +} + +void RenderMessageFilter::OnRevealFolderInOS(const FilePath& path) { +#if defined(OS_MACOSX) + const BrowserThread::ID kThreadID = BrowserThread::UI; +#else + const BrowserThread::ID kThreadID = BrowserThread::FILE; +#endif + if (!BrowserThread::CurrentlyOn(kThreadID)) { + // Only honor the request if appropriate persmissions are granted. + if (ChildProcessSecurityPolicy::GetInstance()->CanReadFile( + render_process_id_, path)) { + BrowserThread::PostTask( + kThreadID, FROM_HERE, + NewRunnableMethod( + this, &RenderMessageFilter::OnRevealFolderInOS, path)); + } + return; + } + + DCHECK(BrowserThread::CurrentlyOn(kThreadID)); + platform_util::OpenItem(path); +} + +void RenderMessageFilter::OnDestruct() const { + BrowserThread::DeleteOnIOThread::Destruct(this); +} + +void RenderMessageFilter::OnReceiveContextMenuMsg(const IPC::Message& msg) { + void* iter = NULL; + ContextMenuParams params; + if (!IPC::ParamTraits<ContextMenuParams>::Read(&msg, &iter, ¶ms)) + return; + + // Create a new ViewHostMsg_ContextMenu message. + const ViewHostMsg_ContextMenu context_menu_message(msg.routing_id(), params); + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + new ContextMenuMessageDispatcher( + render_process_id_, context_menu_message)); +} + +void RenderMessageFilter::OnMsgCreateWindow( + const ViewHostMsg_CreateWindow_Params& params, + int* route_id, int64* cloned_session_storage_namespace_id) { + // If the opener is trying to create a background window but doesn't have + // the appropriate permission, fail the attempt. + if (params.window_container_type == WINDOW_CONTAINER_TYPE_BACKGROUND) { + ChromeURLRequestContext* context = + GetRequestContextForURL(params.opener_url); + if (!context->extension_info_map()->CheckURLAccessToExtensionPermission( + params.opener_url, Extension::kBackgroundPermission)) { + *route_id = MSG_ROUTING_NONE; + return; + } + } + + *cloned_session_storage_namespace_id = + webkit_context_->dom_storage_context()->CloneSessionStorage( + params.session_storage_namespace_id); + render_widget_helper_->CreateNewWindow(params, + peer_handle(), + route_id); +} + +void RenderMessageFilter::OnMsgCreateWidget(int opener_id, + WebKit::WebPopupType popup_type, + int* route_id) { + render_widget_helper_->CreateNewWidget(opener_id, popup_type, route_id); +} + +void RenderMessageFilter::OnMsgCreateFullscreenWidget(int opener_id, + int* route_id) { + render_widget_helper_->CreateNewFullscreenWidget(opener_id, route_id); +} + +void RenderMessageFilter::OnSetCookie(const IPC::Message& message, + const GURL& url, + const GURL& first_party_for_cookies, + const std::string& cookie) { + ChromeURLRequestContext* context = GetRequestContextForURL(url); + + SetCookieCompletion* callback = new SetCookieCompletion( + render_process_id_, message.routing_id(), url, cookie, context); + + // If this render view is associated with an automation channel, aka + // ChromeFrame then we need to set cookies in the external host. + if (!AutomationResourceMessageFilter::SetCookiesForUrl(url, + cookie, + callback)) { + int policy = net::OK; + if (context->cookie_policy()) { + policy = context->cookie_policy()->CanSetCookie( + url, first_party_for_cookies, cookie, callback); + if (policy == net::ERR_IO_PENDING) + return; + } + callback->Run(policy); + } +} + +void RenderMessageFilter::OnGetCookies(const GURL& url, + const GURL& first_party_for_cookies, + IPC::Message* reply_msg) { + ChromeURLRequestContext* context = GetRequestContextForURL(url); + + GetCookiesCompletion* callback = new GetCookiesCompletion( + render_process_id_, reply_msg->routing_id(), url, reply_msg, this, + context, false); + + // If this render view is associated with an automation channel, aka + // ChromeFrame then we need to retrieve cookies from the external host. + if (!AutomationResourceMessageFilter::GetCookiesForUrl(url, callback)) { + int policy = net::OK; + if (context->cookie_policy()) { + policy = context->cookie_policy()->CanGetCookies( + url, first_party_for_cookies, callback); + if (policy == net::ERR_IO_PENDING) { + Send(new ViewMsg_SignalCookiePromptEvent()); + return; + } + } + callback->Run(policy); + } +} + +void RenderMessageFilter::OnGetRawCookies( + const GURL& url, + const GURL& first_party_for_cookies, + IPC::Message* reply_msg) { + + ChromeURLRequestContext* context = GetRequestContextForURL(url); + + // Only return raw cookies to trusted renderers or if this request is + // not targeted to an an external host like ChromeFrame. + // TODO(ananta) We need to support retreiving raw cookies from external + // hosts. + if (!ChildProcessSecurityPolicy::GetInstance()->CanReadRawCookies( + render_process_id_)) { + ViewHostMsg_GetRawCookies::WriteReplyParams( + reply_msg, + std::vector<webkit_glue::WebCookie>()); + Send(reply_msg); + return; + } + + GetCookiesCompletion* callback = new GetCookiesCompletion( + render_process_id_, reply_msg->routing_id(), url, reply_msg, this, + context, true); + + // We check policy here to avoid sending back cookies that would not normally + // be applied to outbound requests for the given URL. Since this cookie info + // is visible in the developer tools, it is helpful to make it match reality. + int policy = net::OK; + if (context->cookie_policy()) { + policy = context->cookie_policy()->CanGetCookies( + url, first_party_for_cookies, callback); + if (policy == net::ERR_IO_PENDING) { + Send(new ViewMsg_SignalCookiePromptEvent()); + return; + } + } + callback->Run(policy); +} + +void RenderMessageFilter::OnDeleteCookie(const GURL& url, + const std::string& cookie_name) { + net::URLRequestContext* context = GetRequestContextForURL(url); + context->cookie_store()->DeleteCookie(url, cookie_name); +} + +void RenderMessageFilter::OnCookiesEnabled( + const GURL& url, + const GURL& first_party_for_cookies, + IPC::Message* reply_msg) { + net::URLRequestContext* context = GetRequestContextForURL(url); + CookiesEnabledCompletion* callback = + new CookiesEnabledCompletion(reply_msg, this); + int policy = net::OK; + // TODO(ananta): If this render view is associated with an automation channel, + // aka ChromeFrame then we need to retrieve cookie settings from the external + // host. + if (context->cookie_policy()) { + policy = context->cookie_policy()->CanGetCookies( + url, first_party_for_cookies, callback); + if (policy == net::ERR_IO_PENDING) { + Send(new ViewMsg_SignalCookiePromptEvent()); + return; // CanGetCookies will call our callback in this case. + } + } + callback->Run(policy); +} + +#if defined(OS_MACOSX) +void RenderMessageFilter::OnLoadFont(const FontDescriptor& font, + uint32* handle_size, + base::SharedMemoryHandle* handle) { + base::SharedMemory font_data; + uint32 font_data_size = 0; + bool ok = FontLoader::LoadFontIntoBuffer(font.nsFont(), &font_data, + &font_data_size); + if (!ok || font_data_size == 0) { + LOG(ERROR) << "Couldn't load font data for " << font.font_name << + " ok=" << ok << " font_data_size=" << font_data_size; + *handle_size = 0; + *handle = base::SharedMemory::NULLHandle(); + return; + } + + *handle_size = font_data_size; + font_data.GiveToProcess(base::GetCurrentProcessHandle(), handle); +} +#endif // OS_MACOSX + +#if defined(OS_WIN) // This hack is Windows-specific. +void RenderMessageFilter::OnPreCacheFont(LOGFONT font) { + ChildProcessHost::PreCacheFont(font); +} +#endif // OS_WIN + +void RenderMessageFilter::OnGetPlugins(bool refresh, + IPC::Message* reply_msg) { + // Don't refresh if the specified threshold has not been passed. Note that + // this check is performed before off-loading to the file thread. The reason + // we do this is that some pages tend to request that the list of plugins be + // refreshed at an excessive rate. This instigates disk scanning, as the list + // is accumulated by doing multiple reads from disk. This effect is + // multiplied when we have several pages requesting this operation. + if (refresh) { + const base::TimeDelta threshold = base::TimeDelta::FromSeconds( + kPluginsRefreshThresholdInSeconds); + const base::TimeTicks now = base::TimeTicks::Now(); + if (now - last_plugin_refresh_time_ < threshold) + refresh = false; // Ignore refresh request; threshold not exceeded yet. + else + last_plugin_refresh_time_ = now; + } + + // Can't load plugins on IO thread, so go to the FILE thread. + BrowserThread::PostTask( + BrowserThread::FILE, FROM_HERE, + NewRunnableMethod( + this, &RenderMessageFilter::OnGetPluginsOnFileThread, refresh, + reply_msg)); +} + +void RenderMessageFilter::OnGetPluginsOnFileThread( + bool refresh, IPC::Message* reply_msg) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); + std::vector<webkit::npapi::WebPluginInfo> plugins; + webkit::npapi::PluginList::Singleton()->GetEnabledPlugins(refresh, &plugins); + ViewHostMsg_GetPlugins::WriteReplyParams(reply_msg, plugins); + BrowserThread::PostTask( + BrowserThread::IO, FROM_HERE, + NewRunnableMethod(this, &RenderMessageFilter::Send, reply_msg)); +} + +void RenderMessageFilter::OnGetPluginInfo(int routing_id, + const GURL& url, + const GURL& policy_url, + const std::string& mime_type, + IPC::Message* reply_msg) { + // The PluginService::GetFirstAllowedPluginInfo may need to load the + // plugins. Don't do it on the IO thread. + BrowserThread::PostTask( + BrowserThread::FILE, FROM_HERE, + NewRunnableMethod( + this, &RenderMessageFilter::OnGetPluginInfoOnFileThread, + routing_id, url, policy_url, mime_type, reply_msg)); +} + +void RenderMessageFilter::OnGetPluginInfoOnFileThread( + int render_view_id, + const GURL& url, + const GURL& policy_url, + const std::string& mime_type, + IPC::Message* reply_msg) { + std::string actual_mime_type; + webkit::npapi::WebPluginInfo info; + bool found = plugin_service_->GetFirstAllowedPluginInfo( + render_process_id_, render_view_id, url, mime_type, &info, + &actual_mime_type); + BrowserThread::PostTask( + BrowserThread::IO, FROM_HERE, + NewRunnableMethod( + this, &RenderMessageFilter::OnGotPluginInfo, + found, info, actual_mime_type, policy_url, reply_msg)); +} + +void RenderMessageFilter::OnGotPluginInfo( + bool found, + const webkit::npapi::WebPluginInfo& info, + const std::string& actual_mime_type, + const GURL& policy_url, + IPC::Message* reply_msg) { + ContentSetting setting = CONTENT_SETTING_DEFAULT; + webkit::npapi::WebPluginInfo info_copy = info; + if (found) { + // TODO(mpcomplete): The plugin service should do this check. We should + // not be calling the PluginList directly. + if (!plugin_service_->PrivatePluginAllowedForURL( + info_copy.path, policy_url)) + info_copy.enabled |= webkit::npapi::WebPluginInfo::POLICY_DISABLED; + std::string resource = + webkit::npapi::PluginList::Singleton()->GetPluginGroupIdentifier( + info_copy); + setting = content_settings_->GetContentSetting( + policy_url, + CONTENT_SETTINGS_TYPE_PLUGINS, + resource); + } + + ViewHostMsg_GetPluginInfo::WriteReplyParams( + reply_msg, found, info_copy, setting, actual_mime_type); + Send(reply_msg); +} + +void RenderMessageFilter::OnOpenChannelToPlugin(int routing_id, + const GURL& url, + const std::string& mime_type, + IPC::Message* reply_msg) { + plugin_service_->OpenChannelToNpapiPlugin( + render_process_id_, routing_id, url, mime_type, + new OpenChannelToNpapiPluginCallback(this, reply_msg)); +} + +void RenderMessageFilter::OnOpenChannelToPepperPlugin( + const FilePath& path, + IPC::Message* reply_msg) { + plugin_service_->OpenChannelToPpapiPlugin( + path, new OpenChannelToPpapiPluginCallback(this, reply_msg)); +} + +void RenderMessageFilter::OnLaunchNaCl( + const std::wstring& url, int channel_descriptor, IPC::Message* reply_msg) { + NaClProcessHost* host = new NaClProcessHost(resource_dispatcher_host_, url); + host->Launch(this, channel_descriptor, reply_msg); +} + +void RenderMessageFilter::OnGenerateRoutingID(int* route_id) { + *route_id = render_widget_helper_->GetNextRoutingID(); +} + +void RenderMessageFilter::OnDownloadUrl(const IPC::Message& message, + const GURL& url, + const GURL& referrer) { + net::URLRequestContext* context = request_context_->GetURLRequestContext(); + + // Don't show "Save As" UI. + bool prompt_for_save_location = false; + resource_dispatcher_host_->BeginDownload(url, + referrer, + DownloadSaveInfo(), + prompt_for_save_location, + render_process_id_, + message.routing_id(), + context); +} + +void RenderMessageFilter::OnClipboardWriteObjectsSync( + const ui::Clipboard::ObjectMap& objects, + base::SharedMemoryHandle bitmap_handle) { + DCHECK(base::SharedMemory::IsHandleValid(bitmap_handle)) + << "Bad bitmap handle"; + // We cannot write directly from the IO thread, and cannot service the IPC + // on the UI thread. We'll copy the relevant data and get a handle to any + // shared memory so it doesn't go away when we resume the renderer, and post + // a task to perform the write on the UI thread. + ui::Clipboard::ObjectMap* long_living_objects = + new ui::Clipboard::ObjectMap(objects); + + // Splice the shared memory handle into the clipboard data. + ui::Clipboard::ReplaceSharedMemHandle(long_living_objects, bitmap_handle, + peer_handle()); + + BrowserThread::PostTask( + BrowserThread::UI, + FROM_HERE, + new WriteClipboardTask(long_living_objects)); +} + +void RenderMessageFilter::OnClipboardWriteObjectsAsync( + const ui::Clipboard::ObjectMap& objects) { + // We cannot write directly from the IO thread, and cannot service the IPC + // on the UI thread. We'll copy the relevant data and post a task to preform + // the write on the UI thread. + ui::Clipboard::ObjectMap* long_living_objects = + new ui::Clipboard::ObjectMap(objects); + + // This async message doesn't support shared-memory based bitmaps; they must + // be removed otherwise we might dereference a rubbish pointer. + long_living_objects->erase(ui::Clipboard::CBF_SMBITMAP); + + BrowserThread::PostTask( + BrowserThread::UI, + FROM_HERE, + new WriteClipboardTask(long_living_objects)); +} + +#if !defined(USE_X11) +// On non-X11 platforms, clipboard actions can be performed on the IO thread. +// On X11, since the clipboard is linked with GTK, we either have to do this +// with GTK on the UI thread, or with Xlib on the BACKGROUND_X11 thread. In an +// ideal world, we would do the latter. However, for now we're going to +// terminate these calls on the UI thread. This risks deadlock in the case of +// plugins, but it's better than crashing which is what doing on the IO thread +// gives us. +// +// See resource_message_filter_gtk.cc for the Linux implementation of these +// functions. + +void RenderMessageFilter::OnClipboardIsFormatAvailable( + ui::Clipboard::FormatType format, ui::Clipboard::Buffer buffer, + IPC::Message* reply) { + const bool result = GetClipboard()->IsFormatAvailable(format, buffer); + ViewHostMsg_ClipboardIsFormatAvailable::WriteReplyParams(reply, result); + Send(reply); +} + +void RenderMessageFilter::OnClipboardReadText(ui::Clipboard::Buffer buffer, + IPC::Message* reply) { + string16 result; + GetClipboard()->ReadText(buffer, &result); + ViewHostMsg_ClipboardReadText::WriteReplyParams(reply, result); + Send(reply); +} + +void RenderMessageFilter::OnClipboardReadAsciiText(ui::Clipboard::Buffer buffer, + IPC::Message* reply) { + std::string result; + GetClipboard()->ReadAsciiText(buffer, &result); + ViewHostMsg_ClipboardReadAsciiText::WriteReplyParams(reply, result); + Send(reply); +} + +void RenderMessageFilter::OnClipboardReadHTML(ui::Clipboard::Buffer buffer, + IPC::Message* reply) { + std::string src_url_str; + string16 markup; + GetClipboard()->ReadHTML(buffer, &markup, &src_url_str); + const GURL src_url = GURL(src_url_str); + + ViewHostMsg_ClipboardReadHTML::WriteReplyParams(reply, markup, src_url); + Send(reply); +} + +void RenderMessageFilter::OnClipboardReadAvailableTypes( + ui::Clipboard::Buffer buffer, IPC::Message* reply) { + std::vector<string16> types; + bool contains_filenames = false; + bool result = ClipboardDispatcher::ReadAvailableTypes( + buffer, &types, &contains_filenames); + ViewHostMsg_ClipboardReadAvailableTypes::WriteReplyParams( + reply, result, types, contains_filenames); + Send(reply); +} + +void RenderMessageFilter::OnClipboardReadData( + ui::Clipboard::Buffer buffer, const string16& type, IPC::Message* reply) { + string16 data; + string16 metadata; + bool result = ClipboardDispatcher::ReadData(buffer, type, &data, &metadata); + ViewHostMsg_ClipboardReadData::WriteReplyParams( + reply, result, data, metadata); + Send(reply); +} + +void RenderMessageFilter::OnClipboardReadFilenames( + ui::Clipboard::Buffer buffer, IPC::Message* reply) { + std::vector<string16> filenames; + bool result = ClipboardDispatcher::ReadFilenames(buffer, &filenames); + ViewHostMsg_ClipboardReadFilenames::WriteReplyParams( + reply, result, filenames); + Send(reply); +} + +#endif + +void RenderMessageFilter::OnCheckNotificationPermission( + const GURL& source_url, int* result) { + *result = WebKit::WebNotificationPresenter::PermissionNotAllowed; + + ChromeURLRequestContext* context = GetRequestContextForURL(source_url); + if (context->extension_info_map()->CheckURLAccessToExtensionPermission( + source_url, Extension::kNotificationPermission)) { + *result = WebKit::WebNotificationPresenter::PermissionAllowed; + return; + } + + // Fall back to the regular notification preferences, which works on an + // origin basis. + *result = notification_prefs_->HasPermission(source_url.GetOrigin()); +} + +void RenderMessageFilter::OnGetCPBrowsingContext(uint32* context) { + // Always allocate a new context when a plugin requests one, since it needs to + // be unique for that plugin instance. + *context = CPBrowsingContextManager::GetInstance()->Allocate( + request_context_->GetURLRequestContext()); +} + +#if defined(OS_WIN) +void RenderMessageFilter::OnDuplicateSection( + base::SharedMemoryHandle renderer_handle, + base::SharedMemoryHandle* browser_handle) { + // Duplicate the handle in this process right now so the memory is kept alive + // (even if it is not mapped) + base::SharedMemory shared_buf(renderer_handle, true, peer_handle()); + shared_buf.GiveToProcess(base::GetCurrentProcessHandle(), browser_handle); +} +#endif + +#if defined(OS_POSIX) +void RenderMessageFilter::OnAllocateSharedMemoryBuffer( + uint32 buffer_size, + base::SharedMemoryHandle* handle) { + base::SharedMemory shared_buf; + if (!shared_buf.CreateAndMapAnonymous(buffer_size)) { + *handle = base::SharedMemory::NULLHandle(); + NOTREACHED() << "Cannot map shared memory buffer"; + return; + } + shared_buf.GiveToProcess(base::GetCurrentProcessHandle(), handle); +} +#endif + +void RenderMessageFilter::OnResourceTypeStats( + const WebCache::ResourceTypeStats& stats) { + HISTOGRAM_COUNTS("WebCoreCache.ImagesSizeKB", + static_cast<int>(stats.images.size / 1024)); + HISTOGRAM_COUNTS("WebCoreCache.CSSStylesheetsSizeKB", + static_cast<int>(stats.cssStyleSheets.size / 1024)); + HISTOGRAM_COUNTS("WebCoreCache.ScriptsSizeKB", + static_cast<int>(stats.scripts.size / 1024)); + HISTOGRAM_COUNTS("WebCoreCache.XSLStylesheetsSizeKB", + static_cast<int>(stats.xslStyleSheets.size / 1024)); + HISTOGRAM_COUNTS("WebCoreCache.FontsSizeKB", + static_cast<int>(stats.fonts.size / 1024)); + // We need to notify the TaskManager of these statistics from the UI + // thread. + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + NewRunnableFunction( + &RenderMessageFilter::OnResourceTypeStatsOnUIThread, + stats, + base::GetProcId(peer_handle()))); +} + +void RenderMessageFilter::OnResourceTypeStatsOnUIThread( + const WebCache::ResourceTypeStats& stats, base::ProcessId renderer_id) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + TaskManager::GetInstance()->model()->NotifyResourceTypeStats( + renderer_id, stats); +} + + +void RenderMessageFilter::OnV8HeapStats(int v8_memory_allocated, + int v8_memory_used) { + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + NewRunnableFunction(&RenderMessageFilter::OnV8HeapStatsOnUIThread, + v8_memory_allocated, + v8_memory_used, + base::GetProcId(peer_handle()))); +} + +// static +void RenderMessageFilter::OnV8HeapStatsOnUIThread( + int v8_memory_allocated, int v8_memory_used, base::ProcessId renderer_id) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + TaskManager::GetInstance()->model()->NotifyV8HeapStats( + renderer_id, + static_cast<size_t>(v8_memory_allocated), + static_cast<size_t>(v8_memory_used)); +} + +void RenderMessageFilter::OnDidZoomURL(const IPC::Message& message, + double zoom_level, + bool remember, + const GURL& url) { + BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, + NewRunnableMethod(this, + &RenderMessageFilter::UpdateHostZoomLevelsOnUIThread, + zoom_level, remember, url, render_process_id_, message.routing_id())); +} + +void RenderMessageFilter::UpdateHostZoomLevelsOnUIThread( + double zoom_level, + bool remember, + const GURL& url, + int render_process_id, + int render_view_id) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + if (remember) { + host_zoom_map_->SetZoomLevel(url, zoom_level); + // Notify renderers from this profile. + for (RenderProcessHost::iterator i(RenderProcessHost::AllHostsIterator()); + !i.IsAtEnd(); i.Advance()) { + RenderProcessHost* render_process_host = i.GetCurrentValue(); + if (render_process_host->profile() == profile_) { + render_process_host->Send( + new ViewMsg_SetZoomLevelForCurrentURL(url, zoom_level)); + } + } + } else { + host_zoom_map_->SetTemporaryZoomLevel( + render_process_id, render_view_id, zoom_level); + } +} + +void RenderMessageFilter::OnResolveProxy(const GURL& url, + IPC::Message* reply_msg) { + resolve_proxy_msg_helper_.Start(url, reply_msg); +} + +void RenderMessageFilter::OnResolveProxyCompleted( + IPC::Message* reply_msg, + int result, + const std::string& proxy_list) { + ViewHostMsg_ResolveProxy::WriteReplyParams(reply_msg, result, proxy_list); + Send(reply_msg); +} + +void RenderMessageFilter::OnGetDefaultPrintSettings(IPC::Message* reply_msg) { + scoped_refptr<printing::PrinterQuery> printer_query; + if (!print_job_manager_->printing_enabled()) { + // Reply with NULL query. + OnGetDefaultPrintSettingsReply(printer_query, reply_msg); + return; + } + + print_job_manager_->PopPrinterQuery(0, &printer_query); + if (!printer_query.get()) { + printer_query = new printing::PrinterQuery; + } + + CancelableTask* task = NewRunnableMethod( + this, + &RenderMessageFilter::OnGetDefaultPrintSettingsReply, + printer_query, + reply_msg); + // Loads default settings. This is asynchronous, only the IPC message sender + // will hang until the settings are retrieved. + printer_query->GetSettings(printing::PrinterQuery::DEFAULTS, + NULL, + 0, + false, + true, + task); +} + +void RenderMessageFilter::OnGetDefaultPrintSettingsReply( + scoped_refptr<printing::PrinterQuery> printer_query, + IPC::Message* reply_msg) { + ViewMsg_Print_Params params; + if (!printer_query.get() || + printer_query->last_status() != printing::PrintingContext::OK) { + memset(¶ms, 0, sizeof(params)); + } else { + RenderParamsFromPrintSettings(printer_query->settings(), ¶ms); + params.document_cookie = printer_query->cookie(); + } + ViewHostMsg_GetDefaultPrintSettings::WriteReplyParams(reply_msg, params); + Send(reply_msg); + // If printing was enabled. + if (printer_query.get()) { + // If user hasn't cancelled. + if (printer_query->cookie() && printer_query->settings().dpi()) { + print_job_manager_->QueuePrinterQuery(printer_query.get()); + } else { + printer_query->StopWorker(); + } + } +} + +void RenderMessageFilter::OnScriptedPrint( + const ViewHostMsg_ScriptedPrint_Params& params, + IPC::Message* reply_msg) { + gfx::NativeView host_view = + gfx::NativeViewFromIdInBrowser(params.host_window_id); + + scoped_refptr<printing::PrinterQuery> printer_query; + print_job_manager_->PopPrinterQuery(params.cookie, &printer_query); + if (!printer_query.get()) { + printer_query = new printing::PrinterQuery; + } + + CancelableTask* task = NewRunnableMethod( + this, + &RenderMessageFilter::OnScriptedPrintReply, + printer_query, + params.routing_id, + reply_msg); + + printer_query->GetSettings(printing::PrinterQuery::ASK_USER, + host_view, + params.expected_pages_count, + params.has_selection, + params.use_overlays, + task); +} + +void RenderMessageFilter::OnScriptedPrintReply( + scoped_refptr<printing::PrinterQuery> printer_query, + int routing_id, + IPC::Message* reply_msg) { + ViewMsg_PrintPages_Params params; + if (printer_query->last_status() != printing::PrintingContext::OK || + !printer_query->settings().dpi()) { + memset(¶ms, 0, sizeof(params)); + } else { + RenderParamsFromPrintSettings(printer_query->settings(), ¶ms.params); + params.params.document_cookie = printer_query->cookie(); + params.pages = + printing::PageRange::GetPages(printer_query->settings().ranges); + } + ViewHostMsg_ScriptedPrint::WriteReplyParams(reply_msg, params); + Send(reply_msg); + if (params.params.dpi && params.params.document_cookie) { + print_job_manager_->QueuePrinterQuery(printer_query.get()); + } else { + printer_query->StopWorker(); + } +} + +// static +ui::Clipboard* RenderMessageFilter::GetClipboard() { + // We have a static instance of the clipboard service for use by all message + // filters. This instance lives for the life of the browser processes. + static ui::Clipboard* clipboard = new ui::Clipboard; + + return clipboard; +} + +ChromeURLRequestContext* RenderMessageFilter::GetRequestContextForURL( + const GURL& url) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + URLRequestContextGetter* context_getter = + url.SchemeIs(chrome::kExtensionScheme) ? + extensions_request_context_ : request_context_; + return static_cast<ChromeURLRequestContext*>( + context_getter->GetURLRequestContext()); +} + +void RenderMessageFilter::OnPlatformCheckSpelling(const string16& word, + int tag, + bool* correct) { + *correct = SpellCheckerPlatform::CheckSpelling(word, tag); +} + +void RenderMessageFilter::OnPlatformFillSuggestionList( + const string16& word, + std::vector<string16>* suggestions) { + SpellCheckerPlatform::FillSuggestionList(word, suggestions); +} + +void RenderMessageFilter::OnGetDocumentTag(IPC::Message* reply_msg) { + int tag = SpellCheckerPlatform::GetDocumentTag(); + ViewHostMsg_GetDocumentTag::WriteReplyParams(reply_msg, tag); + Send(reply_msg); + return; +} + +void RenderMessageFilter::OnDocumentWithTagClosed(int tag) { + SpellCheckerPlatform::CloseDocumentWithTag(tag); +} + +void RenderMessageFilter::OnShowSpellingPanel(bool show) { + SpellCheckerPlatform::ShowSpellingPanel(show); +} + +void RenderMessageFilter::OnUpdateSpellingPanelWithMisspelledWord( + const string16& word) { + SpellCheckerPlatform::UpdateSpellingPanelWithMisspelledWord(word); +} + +void RenderMessageFilter::OnDnsPrefetch( + const std::vector<std::string>& hostnames) { + chrome_browser_net::DnsPrefetchList(hostnames); +} + +void RenderMessageFilter::OnRendererHistograms( + int sequence_number, + const std::vector<std::string>& histograms) { + HistogramSynchronizer::DeserializeHistogramList(sequence_number, histograms); +} + +#if defined(OS_MACOSX) +void RenderMessageFilter::OnAllocTransportDIB( + size_t size, bool cache_in_browser, TransportDIB::Handle* handle) { + render_widget_helper_->AllocTransportDIB(size, cache_in_browser, handle); +} + +void RenderMessageFilter::OnFreeTransportDIB( + TransportDIB::Id dib_id) { + render_widget_helper_->FreeTransportDIB(dib_id); +} +#endif + +void RenderMessageFilter::OnOpenChannelToExtension( + int routing_id, const std::string& source_extension_id, + const std::string& target_extension_id, + const std::string& channel_name, int* port_id) { + int port2_id; + ExtensionMessageService::AllocatePortIdPair(port_id, &port2_id); + + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + NewRunnableMethod( + this, &RenderMessageFilter::OpenChannelToExtensionOnUIThread, + render_process_id_, routing_id, port2_id, source_extension_id, + target_extension_id, channel_name)); +} + +void RenderMessageFilter::OpenChannelToExtensionOnUIThread( + int source_process_id, int source_routing_id, + int receiver_port_id, + const std::string& source_extension_id, + const std::string& target_extension_id, + const std::string& channel_name) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + profile_->GetExtensionMessageService()->OpenChannelToExtension( + source_process_id, source_routing_id, receiver_port_id, + source_extension_id, target_extension_id, channel_name); +} + +void RenderMessageFilter::OnOpenChannelToTab( + int routing_id, int tab_id, const std::string& extension_id, + const std::string& channel_name, int* port_id) { + int port2_id; + ExtensionMessageService::AllocatePortIdPair(port_id, &port2_id); + + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + NewRunnableMethod( + this, &RenderMessageFilter::OpenChannelToTabOnUIThread, + render_process_id_, routing_id, port2_id, tab_id, extension_id, + channel_name)); +} + +void RenderMessageFilter::OpenChannelToTabOnUIThread( + int source_process_id, int source_routing_id, + int receiver_port_id, + int tab_id, + const std::string& extension_id, + const std::string& channel_name) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + profile_->GetExtensionMessageService()->OpenChannelToTab( + source_process_id, source_routing_id, receiver_port_id, + tab_id, extension_id, channel_name); +} + +bool RenderMessageFilter::CheckBenchmarkingEnabled() const { + static bool checked = false; + static bool result = false; + if (!checked) { + const CommandLine& command_line = *CommandLine::ForCurrentProcess(); + result = command_line.HasSwitch(switches::kEnableBenchmarking); + checked = true; + } + return result; +} + +void RenderMessageFilter::OnCloseCurrentConnections() { + // This function is disabled unless the user has enabled + // benchmarking extensions. + if (!CheckBenchmarkingEnabled()) + return; + request_context_->GetURLRequestContext()-> + http_transaction_factory()->GetCache()->CloseCurrentConnections(); +} + +void RenderMessageFilter::OnSetCacheMode(bool enabled) { + // This function is disabled unless the user has enabled + // benchmarking extensions. + if (!CheckBenchmarkingEnabled()) + return; + + net::HttpCache::Mode mode = enabled ? + net::HttpCache::NORMAL : net::HttpCache::DISABLE; + net::HttpCache* http_cache = request_context_->GetURLRequestContext()-> + http_transaction_factory()->GetCache(); + http_cache->set_mode(mode); +} + +void RenderMessageFilter::OnClearCache(bool preserve_ssl_host_info, + IPC::Message* reply_msg) { + // This function is disabled unless the user has enabled + // benchmarking extensions. + int rv = -1; + if (CheckBenchmarkingEnabled()) { + disk_cache::Backend* backend = request_context_->GetURLRequestContext()-> + http_transaction_factory()->GetCache()->GetCurrentBackend(); + if (backend) { + ClearCacheCompletion* callback = + new ClearCacheCompletion(this, reply_msg); + if (preserve_ssl_host_info) { + DoomEntriesHelper* helper = new DoomEntriesHelper(backend); + helper->ClearCache(callback); // Will self clean. + return; + } else { + rv = backend->DoomAllEntries(callback); + if (rv == net::ERR_IO_PENDING) { + // The callback will send the reply. + return; + } + // Completed synchronously, no need for the callback. + delete callback; + } + } + } + ViewHostMsg_ClearCache::WriteReplyParams(reply_msg, rv); + Send(reply_msg); +} + +bool RenderMessageFilter::CheckPreparsedJsCachingEnabled() const { + static bool checked = false; + static bool result = false; + if (!checked) { + const CommandLine& command_line = *CommandLine::ForCurrentProcess(); + result = command_line.HasSwitch(switches::kEnablePreparsedJsCaching); + checked = true; + } + return result; +} + +void RenderMessageFilter::OnCacheableMetadataAvailable( + const GURL& url, + double expected_response_time, + const std::vector<char>& data) { + if (!CheckPreparsedJsCachingEnabled()) + return; + + net::HttpCache* cache = request_context_->GetURLRequestContext()-> + http_transaction_factory()->GetCache(); + DCHECK(cache); + + scoped_refptr<net::IOBuffer> buf(new net::IOBuffer(data.size())); + memcpy(buf->data(), &data.front(), data.size()); + cache->WriteMetadata( + url, base::Time::FromDoubleT(expected_response_time), buf, data.size()); +} + +// TODO(lzheng): This only enables spdy over ssl. Enable spdy for http +// when needed. +void RenderMessageFilter::OnEnableSpdy(bool enable) { + if (enable) { + net::HttpNetworkLayer::EnableSpdy("npn,force-alt-protocols"); + } else { + net::HttpNetworkLayer::EnableSpdy("npn-http"); + } +} + +void RenderMessageFilter::OnKeygen(uint32 key_size_index, + const std::string& challenge_string, + const GURL& url, + IPC::Message* reply_msg) { + // Map displayed strings indicating level of keysecurity in the <keygen> + // menu to the key size in bits. (See SSLKeyGeneratorChromium.cpp in WebCore.) + int key_size_in_bits; + switch (key_size_index) { + case 0: + key_size_in_bits = 2048; + break; + case 1: + key_size_in_bits = 1024; + break; + default: + DCHECK(false) << "Illegal key_size_index " << key_size_index; + ViewHostMsg_Keygen::WriteReplyParams(reply_msg, std::string()); + Send(reply_msg); + return; + } + + VLOG(1) << "Dispatching keygen task to worker pool."; + // Dispatch to worker pool, so we do not block the IO thread. + if (!base::WorkerPool::PostTask( + FROM_HERE, + NewRunnableMethod( + this, &RenderMessageFilter::OnKeygenOnWorkerThread, + key_size_in_bits, challenge_string, url, reply_msg), + true)) { + NOTREACHED() << "Failed to dispatch keygen task to worker pool"; + ViewHostMsg_Keygen::WriteReplyParams(reply_msg, std::string()); + Send(reply_msg); + return; + } +} + +void RenderMessageFilter::OnKeygenOnWorkerThread( + int key_size_in_bits, + const std::string& challenge_string, + const GURL& url, + IPC::Message* reply_msg) { + DCHECK(reply_msg); + + // Generate a signed public key and challenge, then send it back. + net::KeygenHandler keygen_handler(key_size_in_bits, challenge_string, url); + +#if defined(USE_NSS) + // Attach a password delegate so we can authenticate. + keygen_handler.set_crypto_module_password_delegate( + browser::NewCryptoModuleBlockingDialogDelegate( + browser::kCryptoModulePasswordKeygen, url.host())); +#endif // defined(USE_NSS) + + ViewHostMsg_Keygen::WriteReplyParams( + reply_msg, + keygen_handler.GenKeyAndSignChallenge()); + + BrowserThread::PostTask( + BrowserThread::IO, FROM_HERE, + NewRunnableMethod(this, &RenderMessageFilter::Send, reply_msg)); +} + +#if defined(USE_TCMALLOC) +void RenderMessageFilter::OnRendererTcmalloc(base::ProcessId pid, + const std::string& output) { + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + NewRunnableFunction(AboutTcmallocRendererCallback, pid, output)); +} +#endif + +void RenderMessageFilter::OnGetExtensionMessageBundle( + const std::string& extension_id, IPC::Message* reply_msg) { + ChromeURLRequestContext* context = static_cast<ChromeURLRequestContext*>( + request_context_->GetURLRequestContext()); + + FilePath extension_path = + context->extension_info_map()->GetPathForExtension(extension_id); + std::string default_locale = + context->extension_info_map()->GetDefaultLocaleForExtension(extension_id); + + BrowserThread::PostTask( + BrowserThread::FILE, FROM_HERE, + NewRunnableMethod( + this, &RenderMessageFilter::OnGetExtensionMessageBundleOnFileThread, + extension_path, extension_id, default_locale, reply_msg)); +} + +void RenderMessageFilter::OnGetExtensionMessageBundleOnFileThread( + const FilePath& extension_path, + const std::string& extension_id, + const std::string& default_locale, + IPC::Message* reply_msg) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); + + std::map<std::string, std::string> dictionary_map; + if (!default_locale.empty()) { + // Touch disk only if extension is localized. + std::string error; + scoped_ptr<ExtensionMessageBundle> bundle( + extension_file_util::LoadExtensionMessageBundle( + extension_path, default_locale, &error)); + + if (bundle.get()) + dictionary_map = *bundle->dictionary(); + } + + // Add @@extension_id reserved message here, so it's available to + // non-localized extensions too. + dictionary_map.insert( + std::make_pair(ExtensionMessageBundle::kExtensionIdKey, extension_id)); + + ViewHostMsg_GetExtensionMessageBundle::WriteReplyParams( + reply_msg, dictionary_map); + + BrowserThread::PostTask( + BrowserThread::IO, FROM_HERE, + NewRunnableMethod(this, &RenderMessageFilter::Send, reply_msg)); +} + +void RenderMessageFilter::OnAsyncOpenFile(const IPC::Message& msg, + const FilePath& path, + int flags, + int message_id) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + + if (!ChildProcessSecurityPolicy::GetInstance()->HasPermissionsForFile( + render_process_id_, path, flags)) { + DLOG(ERROR) << "Bad flags in ViewMsgHost_AsyncOpenFile message: " << flags; + UserMetrics::RecordAction(UserMetricsAction("BadMessageTerminate_AOF")); + BadMessageReceived(); + return; + } + + BrowserThread::PostTask( + BrowserThread::FILE, FROM_HERE, NewRunnableMethod( + this, &RenderMessageFilter::AsyncOpenFileOnFileThread, + path, flags, message_id, msg.routing_id())); +} + +void RenderMessageFilter::AsyncOpenFileOnFileThread(const FilePath& path, + int flags, + int message_id, + int routing_id) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); + base::PlatformFileError error_code = base::PLATFORM_FILE_OK; + base::PlatformFile file = base::CreatePlatformFile( + path, flags, NULL, &error_code); + IPC::PlatformFileForTransit file_for_transit = + IPC::InvalidPlatformFileForTransit(); + if (file != base::kInvalidPlatformFileValue) { +#if defined(OS_WIN) + ::DuplicateHandle(::GetCurrentProcess(), file, peer_handle(), + &file_for_transit, 0, false, DUPLICATE_SAME_ACCESS); +#else + file_for_transit = base::FileDescriptor(file, true); +#endif + } + + IPC::Message* reply = new ViewMsg_AsyncOpenFile_ACK( + routing_id, error_code, file_for_transit, message_id); + BrowserThread::PostTask( + BrowserThread::IO, FROM_HERE, NewRunnableMethod( + this, &RenderMessageFilter::Send, reply)); +} + +SetCookieCompletion::SetCookieCompletion(int render_process_id, + int render_view_id, + const GURL& url, + const std::string& cookie_line, + ChromeURLRequestContext* context) + : render_process_id_(render_process_id), + render_view_id_(render_view_id), + url_(url), + cookie_line_(cookie_line), + context_(context) { +} + +SetCookieCompletion::~SetCookieCompletion() {} + +void SetCookieCompletion::RunWithParams(const Tuple1<int>& params) { + int result = params.a; + bool blocked_by_policy = true; + net::CookieOptions options; + if (result == net::OK || + result == net::OK_FOR_SESSION_ONLY) { + blocked_by_policy = false; + if (result == net::OK_FOR_SESSION_ONLY) + options.set_force_session(); + context_->cookie_store()->SetCookieWithOptions(url_, cookie_line_, + options); + } + CallRenderViewHostContentSettingsDelegate( + render_process_id_, render_view_id_, + &RenderViewHostDelegate::ContentSettings::OnCookieChanged, + url_, cookie_line_, options, blocked_by_policy); + delete this; +} + +GetCookiesCompletion::GetCookiesCompletion(int render_process_id, + int render_view_id, + const GURL& url, + IPC::Message* reply_msg, + RenderMessageFilter* filter, + ChromeURLRequestContext* context, + bool raw_cookies) + : url_(url), + reply_msg_(reply_msg), + filter_(filter), + context_(context), + render_process_id_(render_process_id), + render_view_id_(render_view_id), + raw_cookies_(raw_cookies) { + set_cookie_store(context_->cookie_store()); +} + +GetCookiesCompletion::~GetCookiesCompletion() {} + +void GetCookiesCompletion::RunWithParams(const Tuple1<int>& params) { + if (!raw_cookies_) { + int result = params.a; + std::string cookies; + if (result == net::OK) + cookies = cookie_store()->GetCookies(url_); + ViewHostMsg_GetCookies::WriteReplyParams(reply_msg_, cookies); + filter_->Send(reply_msg_); + net::CookieMonster* cookie_monster = + context_->cookie_store()->GetCookieMonster(); + net::CookieList cookie_list = + cookie_monster->GetAllCookiesForURLWithOptions( + url_, net::CookieOptions()); + CallRenderViewHostContentSettingsDelegate( + render_process_id_, render_view_id_, + &RenderViewHostDelegate::ContentSettings::OnCookiesRead, + url_, cookie_list, result != net::OK); + delete this; + } else { + // Ignore the policy result. We only waited on the policy result so that + // any pending 'set-cookie' requests could be flushed. The intent of + // querying the raw cookies is to reveal the contents of the cookie DB, so + // it important that we don't read the cookie db ahead of pending writes. + net::CookieMonster* cookie_monster = + context_->cookie_store()->GetCookieMonster(); + net::CookieList cookie_list = cookie_monster->GetAllCookiesForURL(url_); + + std::vector<webkit_glue::WebCookie> cookies; + for (size_t i = 0; i < cookie_list.size(); ++i) { + cookies.push_back(webkit_glue::WebCookie(cookie_list[i])); + } + + ViewHostMsg_GetRawCookies::WriteReplyParams(reply_msg_, cookies); + filter_->Send(reply_msg_); + delete this; + } +} + +void GetCookiesCompletion::set_cookie_store(CookieStore* cookie_store) { + cookie_store_ = cookie_store; +} + +CookiesEnabledCompletion::CookiesEnabledCompletion( + IPC::Message* reply_msg, + RenderMessageFilter* filter) + : reply_msg_(reply_msg), + filter_(filter) { +} + +CookiesEnabledCompletion::~CookiesEnabledCompletion() {} + +void CookiesEnabledCompletion::RunWithParams(const Tuple1<int>& params) { + bool result = params.a != net::ERR_ACCESS_DENIED; + ViewHostMsg_CookiesEnabled::WriteReplyParams(reply_msg_, result); + filter_->Send(reply_msg_); + delete this; +} diff --git a/content/browser/renderer_host/render_message_filter.h b/content/browser/renderer_host/render_message_filter.h new file mode 100644 index 0000000..b821466 --- /dev/null +++ b/content/browser/renderer_host/render_message_filter.h @@ -0,0 +1,498 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CONTENT_BROWSER_RENDERER_HOST_RENDER_MESSAGE_FILTER_H_ +#define CONTENT_BROWSER_RENDERER_HOST_RENDER_MESSAGE_FILTER_H_ +#pragma once + +#if defined(OS_WIN) +#include <windows.h> +#endif + +#include <string> +#include <vector> + +#include "app/surface/transport_dib.h" +#include "base/file_path.h" +#include "base/linked_ptr.h" +#include "base/string16.h" +#include "base/task.h" +#include "build/build_config.h" +#include "chrome/browser/browser_message_filter.h" +#include "chrome/browser/in_process_webkit/webkit_context.h" +#include "chrome/browser/net/resolve_proxy_msg_helper.h" +#include "content/browser/renderer_host/resource_dispatcher_host.h" +#include "chrome/common/content_settings.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebCache.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebPopupType.h" +#include "ui/base/clipboard/clipboard.h" +#include "ui/gfx/native_widget_types.h" + +class ChromeURLRequestContext; +struct FontDescriptor; +class HostContentSettingsMap; +class HostZoomMap; +class NotificationsPrefsCache; +class Profile; +class RenderWidgetHelper; +class URLRequestContextGetter; +struct ViewHostMsg_CreateWindow_Params; +struct ViewHostMsg_CreateWorker_Params; + +namespace webkit { +namespace npapi { +struct WebPluginInfo; +} +} + +namespace base { +class SharedMemory; +} + +namespace net { +class CookieStore; +} + +namespace printing { +class PrinterQuery; +class PrintJobManager; +} + +struct ViewHostMsg_ScriptedPrint_Params; + +// This class filters out incoming IPC messages for the renderer process on the +// IPC thread. +class RenderMessageFilter : public BrowserMessageFilter, + public ResolveProxyMsgHelper::Delegate { + public: + // Create the filter. + RenderMessageFilter(int render_process_id, + PluginService* plugin_service, + Profile* profile, + RenderWidgetHelper* render_widget_helper); + + // BrowserMessageFilter methods: + virtual bool OnMessageReceived(const IPC::Message& message, + bool* message_was_ok); + virtual void OnDestruct() const; + + int render_process_id() const { return render_process_id_; } + ResourceDispatcherHost* resource_dispatcher_host() { + return resource_dispatcher_host_; + } + bool off_the_record() { return off_the_record_; } + + // Returns either the extension net::URLRequestContext or regular + // net::URLRequestContext depending on whether |url| is an extension URL. + // Only call on the IO thread. + ChromeURLRequestContext* GetRequestContextForURL(const GURL& url); + + private: + friend class BrowserThread; + friend class DeleteTask<RenderMessageFilter>; + + virtual ~RenderMessageFilter(); + + void OnMsgCreateWindow(const ViewHostMsg_CreateWindow_Params& params, + int* route_id, + int64* cloned_session_storage_namespace_id); + void OnMsgCreateWidget(int opener_id, + WebKit::WebPopupType popup_type, + int* route_id); + void OnMsgCreateFullscreenWidget(int opener_id, int* route_id); + void OnSetCookie(const IPC::Message& message, + const GURL& url, + const GURL& first_party_for_cookies, + const std::string& cookie); + void OnGetCookies(const GURL& url, + const GURL& first_party_for_cookies, + IPC::Message* reply_msg); + void OnGetRawCookies(const GURL& url, + const GURL& first_party_for_cookies, + IPC::Message* reply_msg); + void OnDeleteCookie(const GURL& url, + const std::string& cookieName); + void OnCookiesEnabled(const GURL& url, + const GURL& first_party_for_cookies, + IPC::Message* reply_msg); + void OnPluginFileDialog(const IPC::Message& msg, + bool multiple_files, + const std::wstring& title, + const std::wstring& filter, + uint32 user_data); + +#if defined(OS_MACOSX) + void OnLoadFont(const FontDescriptor& font, + uint32* handle_size, + base::SharedMemoryHandle* handle); +#endif + +#if defined(OS_WIN) // This hack is Windows-specific. + // Cache fonts for the renderer. See RenderMessageFilter::OnPreCacheFont + // implementation for more details. + void OnPreCacheFont(LOGFONT font); +#endif + +#if !defined(OS_MACOSX) + // Not handled in the IO thread on Mac. + void OnGetScreenInfo(gfx::NativeViewId window, IPC::Message* reply); +#endif + void OnGetPlugins(bool refresh, IPC::Message* reply_msg); + void OnGetPluginsOnFileThread(bool refresh, IPC::Message* reply_msg); + void OnGetPluginInfo(int routing_id, + const GURL& url, + const GURL& policy_url, + const std::string& mime_type, + IPC::Message* reply_msg); + void OnGetPluginInfoOnFileThread(int render_view_id, + const GURL& url, + const GURL& policy_url, + const std::string& mime_type, + IPC::Message* reply_msg); + void OnGotPluginInfo(bool found, + const webkit::npapi::WebPluginInfo& info, + const std::string& actual_mime_type, + const GURL& policy_url, + IPC::Message* reply_msg); + void OnOpenChannelToPlugin(int routing_id, + const GURL& url, + const std::string& mime_type, + IPC::Message* reply_msg); + void OnOpenChannelToPepperPlugin(const FilePath& path, + IPC::Message* reply_msg); + void OnLaunchNaCl(const std::wstring& url, + int channel_descriptor, + IPC::Message* reply_msg); + void OnGenerateRoutingID(int* route_id); + void OnDownloadUrl(const IPC::Message& message, + const GURL& url, + const GURL& referrer); + void OnPlatformCheckSpelling(const string16& word, int tag, bool* correct); + void OnPlatformFillSuggestionList(const string16& word, + std::vector<string16>* suggestions); + void OnGetDocumentTag(IPC::Message* reply_msg); + void OnDocumentWithTagClosed(int tag); + void OnShowSpellingPanel(bool show); + void OnUpdateSpellingPanelWithMisspelledWord(const string16& word); + void OnDnsPrefetch(const std::vector<std::string>& hostnames); + void OnRendererHistograms(int sequence_number, + const std::vector<std::string>& histogram_info); +#if defined(USE_TCMALLOC) + void OnRendererTcmalloc(base::ProcessId pid, const std::string& output); +#endif + void OnReceiveContextMenuMsg(const IPC::Message& msg); + // Clipboard messages + void OnClipboardWriteObjectsAsync(const ui::Clipboard::ObjectMap& objects); + void OnClipboardWriteObjectsSync(const ui::Clipboard::ObjectMap& objects, + base::SharedMemoryHandle bitmap_handle); + + void OnClipboardIsFormatAvailable(ui::Clipboard::FormatType format, + ui::Clipboard::Buffer buffer, + IPC::Message* reply); + void OnClipboardReadText(ui::Clipboard::Buffer buffer, IPC::Message* reply); + void OnClipboardReadAsciiText(ui::Clipboard::Buffer buffer, + IPC::Message* reply); + void OnClipboardReadHTML(ui::Clipboard::Buffer buffer, IPC::Message* reply); +#if defined(OS_MACOSX) + void OnClipboardFindPboardWriteString(const string16& text); +#endif + void OnClipboardReadAvailableTypes(ui::Clipboard::Buffer buffer, + IPC::Message* reply); + void OnClipboardReadData(ui::Clipboard::Buffer buffer, const string16& type, + IPC::Message* reply); + void OnClipboardReadFilenames(ui::Clipboard::Buffer buffer, + IPC::Message* reply); + + void OnCheckNotificationPermission(const GURL& source_url, + int* permission_level); + +#if !defined(OS_MACOSX) + // Not handled in the IO thread on Mac. + void OnGetWindowRect(gfx::NativeViewId window, IPC::Message* reply); + void OnGetRootWindowRect(gfx::NativeViewId window, IPC::Message* reply); +#endif + + void OnRevealFolderInOS(const FilePath& path); + void OnGetCPBrowsingContext(uint32* context); + +#if defined(OS_WIN) + // Used to pass resulting EMF from renderer to browser in printing. + void OnDuplicateSection(base::SharedMemoryHandle renderer_handle, + base::SharedMemoryHandle* browser_handle); +#endif + +#if defined(OS_CHROMEOS) + // Used to ask the browser allocate a temporary file for the renderer + // to fill in resulting PDF in renderer. + void OnAllocateTempFileForPrinting(IPC::Message* reply_msg); + void OnTempFileForPrintingWritten(int sequence_number); +#endif + +#if defined(OS_POSIX) + // Used to ask the browser to allocate a block of shared memory for the + // renderer to send back data in, since shared memory can't be created + // in the renderer on POSIX due to the sandbox. + void OnAllocateSharedMemoryBuffer(uint32 buffer_size, + base::SharedMemoryHandle* handle); +#endif + + void OnResourceTypeStats(const WebKit::WebCache::ResourceTypeStats& stats); + static void OnResourceTypeStatsOnUIThread( + const WebKit::WebCache::ResourceTypeStats&, + base::ProcessId renderer_id); + + void OnV8HeapStats(int v8_memory_allocated, int v8_memory_used); + static void OnV8HeapStatsOnUIThread(int v8_memory_allocated, + int v8_memory_used, + base::ProcessId renderer_id); + + void OnDidZoomURL(const IPC::Message& message, + double zoom_level, + bool remember, + const GURL& url); + void UpdateHostZoomLevelsOnUIThread(double zoom_level, + bool remember, + const GURL& url, + int render_process_id, + int render_view_id); + + void OnResolveProxy(const GURL& url, IPC::Message* reply_msg); + + // ResolveProxyMsgHelper::Delegate implementation: + virtual void OnResolveProxyCompleted(IPC::Message* reply_msg, + int result, + const std::string& proxy_list); + + // A javascript code requested to print the current page. This is done in two + // steps and this is the first step. Get the print setting right here + // synchronously. It will hang the I/O completely. + void OnGetDefaultPrintSettings(IPC::Message* reply_msg); + void OnGetDefaultPrintSettingsReply( + scoped_refptr<printing::PrinterQuery> printer_query, + IPC::Message* reply_msg); + + // A javascript code requested to print the current page. The renderer host + // have to show to the user the print dialog and returns the selected print + // settings. + void OnScriptedPrint(const ViewHostMsg_ScriptedPrint_Params& params, + IPC::Message* reply_msg); + void OnScriptedPrintReply( + scoped_refptr<printing::PrinterQuery> printer_query, + int routing_id, + IPC::Message* reply_msg); + + // Browser side transport DIB allocation + void OnAllocTransportDIB(size_t size, + bool cache_in_browser, + TransportDIB::Handle* result); + void OnFreeTransportDIB(TransportDIB::Id dib_id); + + void OnOpenChannelToExtension(int routing_id, + const std::string& source_extension_id, + const std::string& target_extension_id, + const std::string& channel_name, int* port_id); + void OpenChannelToExtensionOnUIThread(int source_process_id, + int source_routing_id, + int receiver_port_id, + const std::string& source_extension_id, + const std::string& target_extension_id, + const std::string& channel_name); + void OnOpenChannelToTab(int routing_id, int tab_id, + const std::string& extension_id, + const std::string& channel_name, int* port_id); + void OpenChannelToTabOnUIThread(int source_process_id, int source_routing_id, + int receiver_port_id, + int tab_id, const std::string& extension_id, + const std::string& channel_name); + + void OnCloseCurrentConnections(); + void OnSetCacheMode(bool enabled); + void OnClearCache(bool preserve_ssl_host_info, IPC::Message* reply_msg); + void OnCacheableMetadataAvailable(const GURL& url, + double expected_response_time, + const std::vector<char>& data); + void OnEnableSpdy(bool enable); + void OnKeygen(uint32 key_size_index, const std::string& challenge_string, + const GURL& url, IPC::Message* reply_msg); + void OnKeygenOnWorkerThread( + int key_size_in_bits, + const std::string& challenge_string, + const GURL& url, + IPC::Message* reply_msg); + void OnGetExtensionMessageBundle(const std::string& extension_id, + IPC::Message* reply_msg); + void OnGetExtensionMessageBundleOnFileThread( + const FilePath& extension_path, + const std::string& extension_id, + const std::string& default_locale, + IPC::Message* reply_msg); + void OnAsyncOpenFile(const IPC::Message& msg, + const FilePath& path, + int flags, + int message_id); + void AsyncOpenFileOnFileThread(const FilePath& path, + int flags, + int message_id, + int routing_id); + +#if defined(USE_X11) + void DoOnGetScreenInfo(gfx::NativeViewId view, IPC::Message* reply_msg); + void DoOnGetWindowRect(gfx::NativeViewId view, IPC::Message* reply_msg); + void DoOnGetRootWindowRect(gfx::NativeViewId view, IPC::Message* reply_msg); + void DoOnClipboardIsFormatAvailable(ui::Clipboard::FormatType format, + ui::Clipboard::Buffer buffer, + IPC::Message* reply_msg); + void DoOnClipboardReadText(ui::Clipboard::Buffer buffer, + IPC::Message* reply_msg); + void DoOnClipboardReadAsciiText(ui::Clipboard::Buffer buffer, + IPC::Message* reply_msg); + void DoOnClipboardReadHTML(ui::Clipboard::Buffer buffer, + IPC::Message* reply_msg); + void DoOnClipboardReadAvailableTypes(ui::Clipboard::Buffer buffer, + IPC::Message* reply_msg); + void DoOnClipboardReadData(ui::Clipboard::Buffer buffer, const string16& type, + IPC::Message* reply_msg); + void DoOnClipboardReadFilenames(ui::Clipboard::Buffer buffer, + IPC::Message* reply_msg); + void DoOnAllocateTempFileForPrinting(IPC::Message* reply_msg); + void DoOnTempFileForPrintingWritten(int sequence_number); +#endif + + bool CheckBenchmarkingEnabled() const; + bool CheckPreparsedJsCachingEnabled() const; + + // We have our own clipboard because we want to access the clipboard on the + // IO thread instead of forwarding (possibly synchronous) messages to the UI + // thread. This instance of the clipboard should be accessed only on the IO + // thread. + static ui::Clipboard* GetClipboard(); + + // Cached resource request dispatcher host and plugin service, guaranteed to + // be non-null if Init succeeds. We do not own the objects, they are managed + // by the BrowserProcess, which has a wider scope than we do. + ResourceDispatcherHost* resource_dispatcher_host_; + PluginService* plugin_service_; + printing::PrintJobManager* print_job_manager_; + + // The Profile associated with our renderer process. This should only be + // accessed on the UI thread! + Profile* profile_; + + // The host content settings map. Stored separately from the profile so we can + // access it on other threads. + HostContentSettingsMap* content_settings_; + + // Helper class for handling PluginProcessHost_ResolveProxy messages (manages + // the requests to the proxy service). + ResolveProxyMsgHelper resolve_proxy_msg_helper_; + + // Contextual information to be used for requests created here. + scoped_refptr<URLRequestContextGetter> request_context_; + + // A request context that holds a cookie store for chrome-extension URLs. + scoped_refptr<URLRequestContextGetter> extensions_request_context_; + + scoped_refptr<RenderWidgetHelper> render_widget_helper_; + + // A cache of notifications preferences which is used to handle + // Desktop Notifications permission messages. + scoped_refptr<NotificationsPrefsCache> notification_prefs_; + + // Handles zoom-related messages. + scoped_refptr<HostZoomMap> host_zoom_map_; + + // Whether this process is used for off the record tabs. + bool off_the_record_; + + bool cloud_print_enabled_; + + base::TimeTicks last_plugin_refresh_time_; // Initialized to 0. + + scoped_refptr<WebKitContext> webkit_context_; + + int render_process_id_; + + DISALLOW_COPY_AND_ASSIGN(RenderMessageFilter); +}; + +// These classes implement completion callbacks for getting and setting +// cookies. +class SetCookieCompletion : public net::CompletionCallback { + public: + SetCookieCompletion(int render_process_id, + int render_view_id, + const GURL& url, + const std::string& cookie_line, + ChromeURLRequestContext* context); + virtual ~SetCookieCompletion(); + + virtual void RunWithParams(const Tuple1<int>& params); + + int render_process_id() const { + return render_process_id_; + } + + int render_view_id() const { + return render_view_id_; + } + + private: + int render_process_id_; + int render_view_id_; + GURL url_; + std::string cookie_line_; + scoped_refptr<ChromeURLRequestContext> context_; +}; + +class GetCookiesCompletion : public net::CompletionCallback { + public: + GetCookiesCompletion(int render_process_id, + int render_view_id, + const GURL& url, IPC::Message* reply_msg, + RenderMessageFilter* filter, + ChromeURLRequestContext* context, + bool raw_cookies); + virtual ~GetCookiesCompletion(); + + virtual void RunWithParams(const Tuple1<int>& params); + + int render_process_id() const { + return render_process_id_; + } + + int render_view_id() const { + return render_view_id_; + } + + void set_cookie_store(net::CookieStore* cookie_store); + + net::CookieStore* cookie_store() { + return cookie_store_.get(); + } + + private: + GURL url_; + IPC::Message* reply_msg_; + scoped_refptr<RenderMessageFilter> filter_; + scoped_refptr<ChromeURLRequestContext> context_; + int render_process_id_; + int render_view_id_; + bool raw_cookies_; + scoped_refptr<net::CookieStore> cookie_store_; +}; + +class CookiesEnabledCompletion : public net::CompletionCallback { + public: + CookiesEnabledCompletion(IPC::Message* reply_msg, + RenderMessageFilter* filter); + virtual ~CookiesEnabledCompletion(); + + virtual void RunWithParams(const Tuple1<int>& params); + + private: + IPC::Message* reply_msg_; + scoped_refptr<RenderMessageFilter> filter_; +}; + +#endif // CONTENT_BROWSER_RENDERER_HOST_RENDER_MESSAGE_FILTER_H_ diff --git a/content/browser/renderer_host/render_message_filter_gtk.cc b/content/browser/renderer_host/render_message_filter_gtk.cc new file mode 100644 index 0000000..ce260c4 --- /dev/null +++ b/content/browser/renderer_host/render_message_filter_gtk.cc @@ -0,0 +1,346 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "content/browser/renderer_host/render_message_filter.h" + +#include <fcntl.h> +#include <map> + +#include "base/file_util.h" +#include "base/lazy_instance.h" +#include "base/path_service.h" +#include "chrome/browser/browser_thread.h" +#include "chrome/common/chrome_paths.h" +#include "chrome/common/render_messages.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebScreenInfo.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/x11/WebScreenInfoFactory.h" +#include "ui/base/clipboard/clipboard.h" +#include "ui/base/x/x11_util.h" +#include "ui/gfx/gtk_native_view_id_manager.h" + +#if defined(OS_CHROMEOS) +#include "chrome/browser/printing/print_dialog_cloud.h" +#endif + +using WebKit::WebScreenInfo; +using WebKit::WebScreenInfoFactory; + +namespace { + +typedef std::map<int, FilePath> SequenceToPathMap; + +struct PrintingSequencePathMap { + SequenceToPathMap map; + int sequence; +}; + +// No locking, only access on the FILE thread. +static base::LazyInstance<PrintingSequencePathMap> + g_printing_file_descriptor_map(base::LINKER_INITIALIZED); + +} // namespace + +// We get null window_ids passed into the two functions below; please see +// http://crbug.com/9060 for more details. + +void RenderMessageFilter::DoOnGetScreenInfo(gfx::NativeViewId view, + IPC::Message* reply_msg) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::BACKGROUND_X11)); + Display* display = ui::GetSecondaryDisplay(); + int screen = ui::GetDefaultScreen(display); + WebScreenInfo results = WebScreenInfoFactory::screenInfo(display, screen); + ViewHostMsg_GetScreenInfo::WriteReplyParams(reply_msg, results); + Send(reply_msg); +} + +void RenderMessageFilter::DoOnGetWindowRect(gfx::NativeViewId view, + IPC::Message* reply_msg) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::BACKGROUND_X11)); + // This is called to get the x, y offset (in screen coordinates) of the given + // view and its width and height. + gfx::Rect rect; + XID window; + + base::AutoLock lock(GtkNativeViewManager::GetInstance()->unrealize_lock()); + if (GtkNativeViewManager::GetInstance()->GetXIDForId(&window, view)) { + if (window) { + int x, y; + unsigned width, height; + if (ui::GetWindowGeometry(&x, &y, &width, &height, window)) + rect = gfx::Rect(x, y, width, height); + } + } + + ViewHostMsg_GetWindowRect::WriteReplyParams(reply_msg, rect); + Send(reply_msg); +} + +// Return the top-level parent of the given window. Called on the +// BACKGROUND_X11 thread. +static XID GetTopLevelWindow(XID window) { + bool parent_is_root; + XID parent_window; + + if (!ui::GetWindowParent(&parent_window, &parent_is_root, window)) + return 0; + if (parent_is_root) + return window; + + return GetTopLevelWindow(parent_window); +} + +void RenderMessageFilter::DoOnGetRootWindowRect(gfx::NativeViewId view, + IPC::Message* reply_msg) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::BACKGROUND_X11)); + // This is called to get the screen coordinates and size of the browser + // window itself. + gfx::Rect rect; + XID window; + + base::AutoLock lock(GtkNativeViewManager::GetInstance()->unrealize_lock()); + if (GtkNativeViewManager::GetInstance()->GetXIDForId(&window, view)) { + if (window) { + const XID toplevel = GetTopLevelWindow(window); + if (toplevel) { + int x, y; + unsigned width, height; + if (ui::GetWindowGeometry(&x, &y, &width, &height, toplevel)) + rect = gfx::Rect(x, y, width, height); + } + } + } + + ViewHostMsg_GetRootWindowRect::WriteReplyParams(reply_msg, rect); + Send(reply_msg); +} + +void RenderMessageFilter::DoOnClipboardIsFormatAvailable( + ui::Clipboard::FormatType format, ui::Clipboard::Buffer buffer, + IPC::Message* reply_msg) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + const bool result = GetClipboard()->IsFormatAvailable(format, buffer); + + ViewHostMsg_ClipboardIsFormatAvailable::WriteReplyParams(reply_msg, result); + Send(reply_msg); +} + +void RenderMessageFilter::DoOnClipboardReadText(ui::Clipboard::Buffer buffer, + IPC::Message* reply_msg) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + string16 result; + GetClipboard()->ReadText(buffer, &result); + + ViewHostMsg_ClipboardReadText::WriteReplyParams(reply_msg, result); + Send(reply_msg); +} + +void RenderMessageFilter::DoOnClipboardReadAsciiText( + ui::Clipboard::Buffer buffer, IPC::Message* reply_msg) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + std::string result; + GetClipboard()->ReadAsciiText(buffer, &result); + + ViewHostMsg_ClipboardReadAsciiText::WriteReplyParams(reply_msg, result); + Send(reply_msg); +} + +void RenderMessageFilter::DoOnClipboardReadHTML(ui::Clipboard::Buffer buffer, + IPC::Message* reply_msg) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + std::string src_url_str; + string16 markup; + GetClipboard()->ReadHTML(buffer, &markup, &src_url_str); + const GURL src_url = GURL(src_url_str); + + ViewHostMsg_ClipboardReadHTML::WriteReplyParams(reply_msg, markup, src_url); + Send(reply_msg); +} + +void RenderMessageFilter::DoOnClipboardReadAvailableTypes( + ui::Clipboard::Buffer buffer, IPC::Message* reply_msg) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + Send(reply_msg); +} + +void RenderMessageFilter::DoOnClipboardReadData(ui::Clipboard::Buffer buffer, + const string16& type, + IPC::Message* reply_msg) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + Send(reply_msg); +} +void RenderMessageFilter::DoOnClipboardReadFilenames( + ui::Clipboard::Buffer buffer, IPC::Message* reply_msg) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + Send(reply_msg); +} + +#if defined(OS_CHROMEOS) +void RenderMessageFilter::DoOnAllocateTempFileForPrinting( + IPC::Message* reply_msg) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); + base::FileDescriptor temp_file_fd(-1, false); + SequenceToPathMap* map = &g_printing_file_descriptor_map.Get().map; + const int sequence_number = g_printing_file_descriptor_map.Get().sequence++; + + FilePath path; + if (file_util::CreateTemporaryFile(&path)) { + int fd = open(path.value().c_str(), O_WRONLY); + if (fd >= 0) { + SequenceToPathMap::iterator it = map->find(sequence_number); + if (it != map->end()) { + NOTREACHED() << "Sequence number already in use. seq=" << + sequence_number; + } else { + (*map)[sequence_number] = path; + temp_file_fd.fd = fd; + temp_file_fd.auto_close = true; + } + } + } + + ViewHostMsg_AllocateTempFileForPrinting::WriteReplyParams( + reply_msg, temp_file_fd, sequence_number); + Send(reply_msg); +} + +void RenderMessageFilter::DoOnTempFileForPrintingWritten(int sequence_number) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); + SequenceToPathMap* map = &g_printing_file_descriptor_map.Get().map; + SequenceToPathMap::iterator it = map->find(sequence_number); + if (it == map->end()) { + NOTREACHED() << "Got a sequence that we didn't pass to the " + "renderer: " << sequence_number; + return; + } + + if (cloud_print_enabled_) + PrintDialogCloud::CreatePrintDialogForPdf(it->second, string16(), true); + else + NOTIMPLEMENTED(); + + // Erase the entry in the map. + map->erase(it); +} + +#endif // defined(OS_CHROMEOS) + +void RenderMessageFilter::OnGetScreenInfo(gfx::NativeViewId view, + IPC::Message* reply_msg) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + BrowserThread::PostTask( + BrowserThread::BACKGROUND_X11, FROM_HERE, + NewRunnableMethod( + this, &RenderMessageFilter::DoOnGetScreenInfo, view, reply_msg)); +} + +void RenderMessageFilter::OnGetWindowRect(gfx::NativeViewId view, + IPC::Message* reply_msg) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + BrowserThread::PostTask( + BrowserThread::BACKGROUND_X11, FROM_HERE, + NewRunnableMethod( + this, &RenderMessageFilter::DoOnGetWindowRect, view, reply_msg)); +} + +void RenderMessageFilter::OnGetRootWindowRect(gfx::NativeViewId view, + IPC::Message* reply_msg) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + BrowserThread::PostTask( + BrowserThread::BACKGROUND_X11, FROM_HERE, + NewRunnableMethod( + this, &RenderMessageFilter::DoOnGetRootWindowRect, view, reply_msg)); +} + +void RenderMessageFilter::OnClipboardIsFormatAvailable( + ui::Clipboard::FormatType format, ui::Clipboard::Buffer buffer, + IPC::Message* reply_msg) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + NewRunnableMethod( + this, &RenderMessageFilter::DoOnClipboardIsFormatAvailable, format, + buffer, reply_msg)); +} + +void RenderMessageFilter::OnClipboardReadText(ui::Clipboard::Buffer buffer, + IPC::Message* reply_msg) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + NewRunnableMethod( + this, &RenderMessageFilter::DoOnClipboardReadText, buffer, + reply_msg)); +} + +void RenderMessageFilter::OnClipboardReadAsciiText(ui::Clipboard::Buffer buffer, + IPC::Message* reply_msg) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + NewRunnableMethod( + this, &RenderMessageFilter::DoOnClipboardReadAsciiText, buffer, + reply_msg)); +} + +void RenderMessageFilter::OnClipboardReadHTML(ui::Clipboard::Buffer buffer, + IPC::Message* reply_msg) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + NewRunnableMethod( + this, &RenderMessageFilter::DoOnClipboardReadHTML, buffer, + reply_msg)); +} + +void RenderMessageFilter::OnClipboardReadAvailableTypes( + ui::Clipboard::Buffer buffer, IPC::Message* reply_msg) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + NewRunnableMethod( + this, &RenderMessageFilter::DoOnClipboardReadAvailableTypes, buffer, + reply_msg)); +} + +void RenderMessageFilter::OnClipboardReadData(ui::Clipboard::Buffer buffer, + const string16& type, + IPC::Message* reply_msg) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + NewRunnableMethod( + this, &RenderMessageFilter::DoOnClipboardReadData, buffer, type, + reply_msg)); +} + +void RenderMessageFilter::OnClipboardReadFilenames( + ui::Clipboard::Buffer buffer, IPC::Message* reply_msg) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + NewRunnableMethod( + this, &RenderMessageFilter::DoOnClipboardReadFilenames, buffer, + reply_msg)); +} + +#if defined(OS_CHROMEOS) +void RenderMessageFilter::OnAllocateTempFileForPrinting( + IPC::Message* reply_msg) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + BrowserThread::PostTask( + BrowserThread::FILE, FROM_HERE, + NewRunnableMethod( + this, &RenderMessageFilter::DoOnAllocateTempFileForPrinting, + reply_msg)); +} + +void RenderMessageFilter::OnTempFileForPrintingWritten(int sequence_number) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + BrowserThread::PostTask( + BrowserThread::FILE, FROM_HERE, + NewRunnableMethod( + this, &RenderMessageFilter::DoOnTempFileForPrintingWritten, + sequence_number)); +} +#endif // defined(OS_CHROMEOS) diff --git a/content/browser/renderer_host/render_message_filter_mac.mm b/content/browser/renderer_host/render_message_filter_mac.mm new file mode 100644 index 0000000..a7d7c9f --- /dev/null +++ b/content/browser/renderer_host/render_message_filter_mac.mm @@ -0,0 +1,43 @@ +// Copyright (c) 2006-2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "content/browser/renderer_host/render_message_filter.h" + +#import <Cocoa/Cocoa.h> + +#include "base/message_loop.h" +#include "base/sys_string_conversions.h" +#include "chrome/browser/browser_thread.h" +#import "chrome/browser/ui/cocoa/find_pasteboard.h" + +// The number of utf16 code units that will be written to the find pasteboard, +// longer texts are silently ignored. This is to prevent that a compromised +// renderer can write unlimited amounts of data into the find pasteboard. +static const size_t kMaxFindPboardStringLength = 4096; + +class WriteFindPboardTask : public Task { + public: + explicit WriteFindPboardTask(NSString* text) + : text_([text retain]) {} + + void Run() { + [[FindPasteboard sharedInstance] setFindText:text_]; + } + + private: + scoped_nsobject<NSString> text_; +}; + +// Called on the IO thread. +void RenderMessageFilter::OnClipboardFindPboardWriteString( + const string16& text) { + if (text.length() <= kMaxFindPboardStringLength) { + NSString* nsText = base::SysUTF16ToNSString(text); + if (nsText) { + // FindPasteboard must be used on the UI thread. + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, new WriteFindPboardTask(nsText)); + } + } +} diff --git a/content/browser/renderer_host/render_message_filter_win.cc b/content/browser/renderer_host/render_message_filter_win.cc new file mode 100644 index 0000000..4f305fde --- /dev/null +++ b/content/browser/renderer_host/render_message_filter_win.cc @@ -0,0 +1,49 @@ +// Copyright (c) 2006-2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "content/browser/renderer_host/render_message_filter.h" + +#include "chrome/common/render_messages.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebScreenInfo.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/win/WebScreenInfoFactory.h" + +using WebKit::WebScreenInfo; +using WebKit::WebScreenInfoFactory; + +// We get null window_ids passed into the two functions below; please see +// http://crbug.com/9060 for more details. + +// TODO(shess): Provide a mapping from reply_msg->routing_id() to HWND +// so that we can eliminate the NativeViewId parameter. + +void RenderMessageFilter::OnGetWindowRect(gfx::NativeViewId window_id, + IPC::Message* reply_msg) { + HWND window = gfx::NativeViewFromId(window_id); + RECT window_rect = {0}; + GetWindowRect(window, &window_rect); + gfx::Rect rect(window_rect); + + ViewHostMsg_GetWindowRect::WriteReplyParams(reply_msg, rect); + Send(reply_msg); +} + +void RenderMessageFilter::OnGetRootWindowRect(gfx::NativeViewId window_id, + IPC::Message* reply_msg) { + HWND window = gfx::NativeViewFromId(window_id); + RECT window_rect = {0}; + HWND root_window = ::GetAncestor(window, GA_ROOT); + GetWindowRect(root_window, &window_rect); + gfx::Rect rect(window_rect); + + ViewHostMsg_GetRootWindowRect::WriteReplyParams(reply_msg, rect); + Send(reply_msg); +} + +void RenderMessageFilter::OnGetScreenInfo(gfx::NativeViewId view, + IPC::Message* reply_msg) { + WebScreenInfo results = + WebScreenInfoFactory::screenInfo(gfx::NativeViewFromId(view)); + ViewHostMsg_GetScreenInfo::WriteReplyParams(reply_msg, results); + Send(reply_msg); +} diff --git a/content/browser/renderer_host/render_process_host.cc b/content/browser/renderer_host/render_process_host.cc new file mode 100644 index 0000000..aed6d08 --- /dev/null +++ b/content/browser/renderer_host/render_process_host.cc @@ -0,0 +1,204 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "content/browser/renderer_host/render_process_host.h" + +#include "base/rand_util.h" +#include "base/sys_info.h" +#include "chrome/browser/child_process_security_policy.h" +#include "chrome/common/child_process_info.h" +#include "chrome/common/chrome_constants.h" +#include "chrome/common/notification_service.h" + +namespace { + +size_t max_renderer_count_override = 0; + +size_t GetMaxRendererProcessCount() { + if (max_renderer_count_override) + return max_renderer_count_override; + + // Defines the maximum number of renderer processes according to the + // amount of installed memory as reported by the OS. The table + // values are calculated by assuming that you want the renderers to + // use half of the installed ram and assuming that each tab uses + // ~40MB, however the curve is not linear but piecewise linear with + // interleaved slopes of 3 and 2. + // If you modify this table you need to adjust browser\browser_uitest.cc + // to match the expected number of processes. + + static const size_t kMaxRenderersByRamTier[] = { + 3, // less than 256MB + 6, // 256MB + 9, // 512MB + 12, // 768MB + 14, // 1024MB + 18, // 1280MB + 20, // 1536MB + 22, // 1792MB + 24, // 2048MB + 26, // 2304MB + 29, // 2560MB + 32, // 2816MB + 35, // 3072MB + 38, // 3328MB + 40 // 3584MB + }; + + static size_t max_count = 0; + if (!max_count) { + size_t memory_tier = base::SysInfo::AmountOfPhysicalMemoryMB() / 256; + if (memory_tier >= arraysize(kMaxRenderersByRamTier)) + max_count = chrome::kMaxRendererProcessCount; + else + max_count = kMaxRenderersByRamTier[memory_tier]; + } + return max_count; +} + +// Returns true if the given host is suitable for launching a new view +// associated with the given profile. +static bool IsSuitableHost(RenderProcessHost* host, Profile* profile, + RenderProcessHost::Type type) { + if (host->profile() != profile) + return false; + + RenderProcessHost::Type host_type = RenderProcessHost::TYPE_NORMAL; + if (ChildProcessSecurityPolicy::GetInstance()->HasWebUIBindings(host->id())) + host_type = RenderProcessHost::TYPE_WEBUI; + if (ChildProcessSecurityPolicy::GetInstance()-> + HasExtensionBindings(host->id())) + host_type = RenderProcessHost::TYPE_EXTENSION; + + return host_type == type; +} + +// the global list of all renderer processes +IDMap<RenderProcessHost> all_hosts; + +} // namespace + +extern bool g_log_bug53991; + +// static +bool RenderProcessHost::run_renderer_in_process_ = false; + +// static +void RenderProcessHost::SetMaxRendererProcessCount(size_t count) { + max_renderer_count_override = count; +} + +RenderProcessHost::RenderProcessHost(Profile* profile) + : max_page_id_(-1), + fast_shutdown_started_(false), + deleting_soon_(false), + id_(ChildProcessInfo::GenerateChildProcessUniqueId()), + profile_(profile), + sudden_termination_allowed_(true), + ignore_input_events_(false) { + all_hosts.AddWithID(this, id()); + all_hosts.set_check_on_null_data(true); + // Initialize |child_process_activity_time_| to a reasonable value. + mark_child_process_activity_time(); +} + +RenderProcessHost::~RenderProcessHost() { + // In unit tests, Release() might not have been called. + if (all_hosts.Lookup(id())) + all_hosts.Remove(id()); +} + +void RenderProcessHost::Attach(IPC::Channel::Listener* listener, + int routing_id) { + VLOG_IF(1, g_log_bug53991) << "AddListener: (" << this << "): " << routing_id; + listeners_.AddWithID(listener, routing_id); +} + +void RenderProcessHost::Release(int listener_id) { + VLOG_IF(1, g_log_bug53991) << "RemListener: (" << this << "): " + << listener_id; + DCHECK(listeners_.Lookup(listener_id) != NULL); + listeners_.Remove(listener_id); + + // Make sure that all associated resource requests are stopped. + CancelResourceRequests(listener_id); + + // When no other owners of this object, we can delete ourselves + if (listeners_.IsEmpty()) { + NotificationService::current()->Notify( + NotificationType::RENDERER_PROCESS_TERMINATED, + Source<RenderProcessHost>(this), NotificationService::NoDetails()); + MessageLoop::current()->DeleteSoon(FROM_HERE, this); + deleting_soon_ = true; + + // Remove ourself from the list of renderer processes so that we can't be + // reused in between now and when the Delete task runs. + all_hosts.Remove(id()); + } +} + +void RenderProcessHost::ReportExpectingClose(int32 listener_id) { + listeners_expecting_close_.insert(listener_id); +} + +void RenderProcessHost::UpdateMaxPageID(int32 page_id) { + if (page_id > max_page_id_) + max_page_id_ = page_id; +} + +bool RenderProcessHost::FastShutdownForPageCount(size_t count) { + if (listeners_.size() == count) + return FastShutdownIfPossible(); + return false; +} + +// static +RenderProcessHost::iterator RenderProcessHost::AllHostsIterator() { + return iterator(&all_hosts); +} + +// static +RenderProcessHost* RenderProcessHost::FromID(int render_process_id) { + return all_hosts.Lookup(render_process_id); +} + +// static +bool RenderProcessHost::ShouldTryToUseExistingProcessHost() { + size_t renderer_process_count = all_hosts.size(); + + // NOTE: Sometimes it's necessary to create more render processes than + // GetMaxRendererProcessCount(), for instance when we want to create + // a renderer process for a profile that has no existing renderers. + // This is OK in moderation, since the GetMaxRendererProcessCount() + // is conservative. + + return run_renderer_in_process() || + (renderer_process_count >= GetMaxRendererProcessCount()); +} + +// static +RenderProcessHost* RenderProcessHost::GetExistingProcessHost(Profile* profile, + Type type) { + // First figure out which existing renderers we can use. + std::vector<RenderProcessHost*> suitable_renderers; + suitable_renderers.reserve(all_hosts.size()); + + iterator iter(AllHostsIterator()); + while (!iter.IsAtEnd()) { + if (run_renderer_in_process() || + IsSuitableHost(iter.GetCurrentValue(), profile, type)) + suitable_renderers.push_back(iter.GetCurrentValue()); + + iter.Advance(); + } + + // Now pick a random suitable renderer, if we have any. + if (!suitable_renderers.empty()) { + int suitable_count = static_cast<int>(suitable_renderers.size()); + int random_index = base::RandInt(0, suitable_count - 1); + return suitable_renderers[random_index]; + } + + return NULL; +} diff --git a/content/browser/renderer_host/render_process_host.h b/content/browser/renderer_host/render_process_host.h new file mode 100644 index 0000000..cc469fa --- /dev/null +++ b/content/browser/renderer_host/render_process_host.h @@ -0,0 +1,327 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CONTENT_BROWSER_RENDERER_HOST_RENDER_PROCESS_HOST_H_ +#define CONTENT_BROWSER_RENDERER_HOST_RENDER_PROCESS_HOST_H_ +#pragma once + +#include <set> + +#include "app/surface/transport_dib.h" +#include "base/id_map.h" +#include "base/process.h" +#include "base/process_util.h" +#include "base/scoped_ptr.h" +#include "base/time.h" +#include "chrome/common/visitedlink_common.h" +#include "ipc/ipc_sync_channel.h" + +class Profile; +class URLRequestContextGetter; +struct ViewMsg_ClosePage_Params; + +namespace base { +class SharedMemory; +} + +// Virtual interface that represents the browser side of the browser <-> +// renderer communication channel. There will generally be one +// RenderProcessHost per renderer process. +// +// The concrete implementation of this class for normal use is the +// BrowserRenderProcessHost. It may also be implemented by a testing interface +// for mocking purposes. +class RenderProcessHost : public IPC::Channel::Sender, + public IPC::Channel::Listener { + public: + typedef IDMap<RenderProcessHost>::iterator iterator; + + // We classify renderers according to their highest privilege, and try + // to group pages into renderers with similar privileges. + // Note: it may be possible for a renderer to have multiple privileges, + // in which case we call it an "extension" renderer. + enum Type { + TYPE_NORMAL, // Normal renderer, no extra privileges. + TYPE_WEBUI, // Renderer with WebUI privileges, like New Tab. + TYPE_EXTENSION, // Renderer with extension privileges. + }; + + // Details for RENDERER_PROCESS_CLOSED notifications. + struct RendererClosedDetails { + RendererClosedDetails(base::TerminationStatus status, + int exit_code, + bool was_extension_renderer) { + this->status = status; + this->exit_code = exit_code; + this->was_extension_renderer = was_extension_renderer; + } + base::TerminationStatus status; + int exit_code; + bool was_extension_renderer; + }; + + explicit RenderProcessHost(Profile* profile); + virtual ~RenderProcessHost(); + + // Returns the user profile associated with this renderer process. + Profile* profile() const { return profile_; } + + // Returns the unique ID for this child process. This can be used later in + // a call to FromID() to get back to this object (this is used to avoid + // sending non-threadsafe pointers to other threads). + // + // This ID will be unique for all child processes, including workers, plugins, + // etc. It is generated by ChildProcessInfo. + int id() const { return id_; } + + // Returns true iff channel_ has been set to non-NULL. Use this for checking + // if there is connection or not. + bool HasConnection() { return channel_.get() != NULL; } + + bool sudden_termination_allowed() const { + return sudden_termination_allowed_; + } + void set_sudden_termination_allowed(bool enabled) { + sudden_termination_allowed_ = enabled; + } + + // Used for refcounting, each holder of this object must Attach and Release + // just like it would for a COM object. This object should be allocated on + // the heap; when no listeners own it any more, it will delete itself. + void Attach(IPC::Channel::Listener* listener, int routing_id); + + // See Attach() + void Release(int listener_id); + + // Listeners should call this when they've sent a "Close" message and + // they're waiting for a "Close_ACK", so that if the renderer process + // goes away we'll know that it was intentional rather than a crash. + void ReportExpectingClose(int32 listener_id); + + // Allows iteration over this RenderProcessHost's RenderViewHost listeners. + // Use from UI thread only. + typedef IDMap<IPC::Channel::Listener>::const_iterator listeners_iterator; + + listeners_iterator ListenersIterator() { + return listeners_iterator(&listeners_); + } + + IPC::Channel::Listener* GetListenerByID(int routing_id) { + return listeners_.Lookup(routing_id); + } + + // Called to inform the render process host of a new "max page id" for a + // render view host. The render process host computes the largest page id + // across all render view hosts and uses the value when it needs to + // initialize a new renderer in place of the current one. + void UpdateMaxPageID(int32 page_id); + + void set_ignore_input_events(bool ignore_input_events) { + ignore_input_events_ = ignore_input_events; + } + bool ignore_input_events() { + return ignore_input_events_; + } + + // Returns how long the child has been idle. The definition of idle + // depends on when a derived class calls mark_child_process_activity_time(). + // This is a rough indicator and its resolution should not be better than + // 10 milliseconds. + base::TimeDelta get_child_process_idle_time() const { + return base::TimeTicks::Now() - child_process_activity_time_; + } + + // Call this function when it is evident that the child process is actively + // performing some operation, for example if we just received an IPC message. + void mark_child_process_activity_time() { + child_process_activity_time_ = base::TimeTicks::Now(); + } + + // Try to shutdown the associated render process as fast as possible, but + // only if |count| matches the number of render widgets that this process + // controls. + bool FastShutdownForPageCount(size_t count); + + bool fast_shutdown_started() { + return fast_shutdown_started_; + } + + // Virtual interface --------------------------------------------------------- + + // Initialize the new renderer process, returning true on success. This must + // be called once before the object can be used, but can be called after + // that with no effect. Therefore, if the caller isn't sure about whether + // the process has been created, it should just call Init(). + virtual bool Init( + bool is_accessibility_enabled, bool is_extensions_process) = 0; + + // Gets the next available routing id. + virtual int GetNextRoutingID() = 0; + + // Called on the UI thread to cancel any outstanding resource requests for + // the specified render widget. + virtual void CancelResourceRequests(int render_widget_id) = 0; + + // Called on the UI thread to simulate a ClosePage_ACK message to the + // ResourceDispatcherHost. Necessary for a cross-site request, in the case + // that the original RenderViewHost is not live and thus cannot run an + // onunload handler. + virtual void CrossSiteClosePageACK( + const ViewMsg_ClosePage_Params& params) = 0; + + // Called on the UI thread to wait for the next UpdateRect message for the + // specified render widget. Returns true if successful, and the msg out- + // param will contain a copy of the received UpdateRect message. + virtual bool WaitForUpdateMsg(int render_widget_id, + const base::TimeDelta& max_delay, + IPC::Message* msg) = 0; + + // Called when a received message cannot be decoded. + virtual void ReceivedBadMessage() = 0; + + // Track the count of visible widgets. Called by listeners to register and + // unregister visibility. + virtual void WidgetRestored() = 0; + virtual void WidgetHidden() = 0; + + // Called when RenderView is created by a listener. + virtual void ViewCreated() = 0; + + // Informs the renderer about a new visited link table. + virtual void SendVisitedLinkTable(base::SharedMemory* table_memory) = 0; + + // Notify the renderer that a link was visited. + virtual void AddVisitedLinks( + const VisitedLinkCommon::Fingerprints& links) = 0; + + // Clear internal visited links buffer and ask the renderer to update link + // coloring state for all of its links. + virtual void ResetVisitedLinks() = 0; + + // Try to shutdown the associated renderer process as fast as possible. + // If this renderer has any RenderViews with unload handlers, then this + // function does nothing. The current implementation uses TerminateProcess. + // Returns True if it was able to do fast shutdown. + virtual bool FastShutdownIfPossible() = 0; + + // Synchronously sends the message, waiting for the specified timeout. The + // implementor takes ownership of the given Message regardless of whether or + // not this method succeeds. Returns true on success. + virtual bool SendWithTimeout(IPC::Message* msg, int timeout_ms) = 0; + + // Returns the process object associated with the child process. In certain + // tests or single-process mode, this will actually represent the current + // process. + // + // NOTE: this is not necessarily valid immediately after calling Init, as + // Init starts the process asynchronously. It's guaranteed to be valid after + // the first IPC arrives. + virtual base::ProcessHandle GetHandle() = 0; + + // Transport DIB functions --------------------------------------------------- + + // Return the TransportDIB for the given id. On Linux, this can involve + // mapping shared memory. On Mac, the shared memory is created in the browser + // process and the cached metadata is returned. On Windows, this involves + // duplicating the handle from the remote process. The RenderProcessHost + // still owns the returned DIB. + virtual TransportDIB* GetTransportDIB(TransportDIB::Id dib_id) = 0; + + // Static management functions ----------------------------------------------- + + // Flag to run the renderer in process. This is primarily + // for debugging purposes. When running "in process", the + // browser maintains a single RenderProcessHost which communicates + // to a RenderProcess which is instantiated in the same process + // with the Browser. All IPC between the Browser and the + // Renderer is the same, it's just not crossing a process boundary. + static bool run_renderer_in_process() { + return run_renderer_in_process_; + } + static void set_run_renderer_in_process(bool value) { + run_renderer_in_process_ = value; + } + + // Allows iteration over all the RenderProcessHosts in the browser. Note + // that each host may not be active, and therefore may have NULL channels. + static iterator AllHostsIterator(); + + // Returns the RenderProcessHost given its ID. Returns NULL if the ID does + // not correspond to a live RenderProcessHost. + static RenderProcessHost* FromID(int render_process_id); + + // Returns true if the caller should attempt to use an existing + // RenderProcessHost rather than creating a new one. + static bool ShouldTryToUseExistingProcessHost(); + + // Get an existing RenderProcessHost associated with the given profile, if + // possible. The renderer process is chosen randomly from suitable renderers + // that share the same profile and type. + // Returns NULL if no suitable renderer process is available, in which case + // the caller is free to create a new renderer. + static RenderProcessHost* GetExistingProcessHost(Profile* profile, Type type); + + // Overrides the default heuristic for limiting the max renderer process + // count. This is useful for unit testing process limit behaviors. + // A value of zero means to use the default heuristic. + static void SetMaxRendererProcessCount(size_t count); + + protected: + // A proxy for our IPC::Channel that lives on the IO thread (see + // browser_process.h) + scoped_ptr<IPC::SyncChannel> channel_; + + // The registered listeners. When this list is empty or all NULL, we should + // delete ourselves + IDMap<IPC::Channel::Listener> listeners_; + + // The maximum page ID we've ever seen from the renderer process. + int32 max_page_id_; + + // True if fast shutdown has been performed on this RPH. + bool fast_shutdown_started_; + + // True if we've posted a DeleteTask and will be deleted soon. + bool deleting_soon_; + + private: + // The globally-unique identifier for this RPH. + int id_; + + Profile* profile_; + + // set of listeners that expect the renderer process to close + std::set<int> listeners_expecting_close_; + + // True if the process can be shut down suddenly. If this is true, then we're + // sure that all the RenderViews in the process can be shutdown suddenly. If + // it's false, then specific RenderViews might still be allowed to be shutdown + // suddenly by checking their SuddenTerminationAllowed() flag. This can occur + // if one tab has an unload event listener but another tab in the same process + // doesn't. + bool sudden_termination_allowed_; + + // Set to true if we shouldn't send input events. We actually do the + // filtering for this at the render widget level. + bool ignore_input_events_; + + // See getter above. + static bool run_renderer_in_process_; + + // Records the last time we regarded the child process active. + base::TimeTicks child_process_activity_time_; + + DISALLOW_COPY_AND_ASSIGN(RenderProcessHost); +}; + +// Factory object for RenderProcessHosts. Using this factory allows tests to +// swap out a different one to use a TestRenderProcessHost. +class RenderProcessHostFactory { + public: + virtual ~RenderProcessHostFactory() {} + virtual RenderProcessHost* CreateRenderProcessHost( + Profile* profile) const = 0; +}; + +#endif // CONTENT_BROWSER_RENDERER_HOST_RENDER_PROCESS_HOST_H_ diff --git a/content/browser/renderer_host/render_sandbox_host_linux.cc b/content/browser/renderer_host/render_sandbox_host_linux.cc new file mode 100644 index 0000000..8d05790 --- /dev/null +++ b/content/browser/renderer_host/render_sandbox_host_linux.cc @@ -0,0 +1,687 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "content/browser/renderer_host/render_sandbox_host_linux.h" + +#include <fcntl.h> +#include <fontconfig/fontconfig.h> +#include <stdint.h> +#include <unistd.h> +#include <sys/uio.h> +#include <sys/stat.h> +#include <sys/socket.h> +#include <sys/poll.h> +#include <time.h> + +#include <vector> + +#include "base/command_line.h" +#include "base/eintr_wrapper.h" +#include "base/linux_util.h" +#include "base/pickle.h" +#include "base/process_util.h" +#include "base/scoped_ptr.h" +#include "base/shared_memory.h" +#include "base/singleton.h" +#include "base/string_number_conversions.h" +#include "base/string_util.h" +#include "chrome/common/font_config_ipc_linux.h" +#include "chrome/common/sandbox_methods_linux.h" +#include "chrome/common/unix_domain_socket_posix.h" +#include "skia/ext/SkFontHost_fontconfig_direct.h" +#include "third_party/npapi/bindings/npapi_extensions.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/gtk/WebFontInfo.h" + +using WebKit::WebCString; +using WebKit::WebFontInfo; +using WebKit::WebUChar; + +// http://code.google.com/p/chromium/wiki/LinuxSandboxIPC + +// BEWARE: code in this file run across *processes* (not just threads). + +// This code runs in a child process +class SandboxIPCProcess { + public: + // lifeline_fd: this is the read end of a pipe which the browser process + // holds the other end of. If the browser process dies, its descriptors are + // closed and we will noticed an EOF on the pipe. That's our signal to exit. + // browser_socket: the browser's end of the sandbox IPC socketpair. From the + // point of view of the renderer, it's talking to the browser but this + // object actually services the requests. + // sandbox_cmd: the path of the sandbox executable + SandboxIPCProcess(int lifeline_fd, int browser_socket, + std::string sandbox_cmd) + : lifeline_fd_(lifeline_fd), + browser_socket_(browser_socket), + font_config_(new FontConfigDirect()) { + base::InjectiveMultimap multimap; + multimap.push_back(base::InjectionArc(0, lifeline_fd, false)); + multimap.push_back(base::InjectionArc(0, browser_socket, false)); + + base::CloseSuperfluousFds(multimap); + + if (!sandbox_cmd.empty()) { + sandbox_cmd_.push_back(sandbox_cmd); + sandbox_cmd_.push_back(base::kFindInodeSwitch); + } + } + + void Run() { + struct pollfd pfds[2]; + pfds[0].fd = lifeline_fd_; + pfds[0].events = POLLIN; + pfds[1].fd = browser_socket_; + pfds[1].events = POLLIN; + + int failed_polls = 0; + for (;;) { + const int r = HANDLE_EINTR(poll(pfds, 2, -1)); + if (r < 1) { + LOG(WARNING) << "poll errno:" << errno; + if (failed_polls++ == 3) { + LOG(FATAL) << "poll failing. Sandbox host aborting."; + return; + } + continue; + } + + failed_polls = 0; + + if (pfds[0].revents) { + // our parent died so we should too. + _exit(0); + } + + if (pfds[1].revents) { + HandleRequestFromRenderer(browser_socket_); + } + } + } + + private: + // --------------------------------------------------------------------------- + // Requests from the renderer... + + void HandleRequestFromRenderer(int fd) { + std::vector<int> fds; + + // A FontConfigIPC::METHOD_MATCH message could be kMaxFontFamilyLength + // bytes long (this is the largest message type). + // 128 bytes padding are necessary so recvmsg() does not return MSG_TRUNC + // error for a maximum length message. + char buf[FontConfigInterface::kMaxFontFamilyLength + 128]; + + const ssize_t len = UnixDomainSocket::RecvMsg(fd, buf, sizeof(buf), &fds); + if (len == -1) { + // TODO: should send an error reply, or the sender might block forever. + NOTREACHED() + << "Sandbox host message is larger than kMaxFontFamilyLength"; + return; + } + if (fds.size() == 0) + return; + + Pickle pickle(buf, len); + void* iter = NULL; + + int kind; + if (!pickle.ReadInt(&iter, &kind)) + goto error; + + if (kind == FontConfigIPC::METHOD_MATCH) { + HandleFontMatchRequest(fd, pickle, iter, fds); + } else if (kind == FontConfigIPC::METHOD_OPEN) { + HandleFontOpenRequest(fd, pickle, iter, fds); + } else if (kind == LinuxSandbox::METHOD_GET_FONT_FAMILY_FOR_CHARS) { + HandleGetFontFamilyForChars(fd, pickle, iter, fds); + } else if (kind == LinuxSandbox::METHOD_LOCALTIME) { + HandleLocaltime(fd, pickle, iter, fds); + } else if (kind == LinuxSandbox::METHOD_GET_CHILD_WITH_INODE) { + HandleGetChildWithInode(fd, pickle, iter, fds); + } else if (kind == LinuxSandbox::METHOD_GET_STYLE_FOR_STRIKE) { + HandleGetStyleForStrike(fd, pickle, iter, fds); + } else if (kind == LinuxSandbox::METHOD_MAKE_SHARED_MEMORY_SEGMENT) { + HandleMakeSharedMemorySegment(fd, pickle, iter, fds); + } else if (kind == LinuxSandbox::METHOD_MATCH_WITH_FALLBACK) { + HandleMatchWithFallback(fd, pickle, iter, fds); + } + + error: + for (std::vector<int>::const_iterator + i = fds.begin(); i != fds.end(); ++i) { + close(*i); + } + } + + void HandleFontMatchRequest(int fd, const Pickle& pickle, void* iter, + std::vector<int>& fds) { + bool filefaceid_valid; + uint32_t filefaceid; + + if (!pickle.ReadBool(&iter, &filefaceid_valid)) + return; + if (filefaceid_valid) { + if (!pickle.ReadUInt32(&iter, &filefaceid)) + return; + } + bool is_bold, is_italic; + if (!pickle.ReadBool(&iter, &is_bold) || + !pickle.ReadBool(&iter, &is_italic)) { + return; + } + + uint32_t characters_bytes; + if (!pickle.ReadUInt32(&iter, &characters_bytes)) + return; + const char* characters = NULL; + if (characters_bytes > 0) { + const uint32_t kMaxCharactersBytes = 1 << 10; + if (characters_bytes % 2 != 0 || // We expect UTF-16. + characters_bytes > kMaxCharactersBytes || + !pickle.ReadBytes(&iter, &characters, characters_bytes)) + return; + } + + std::string family; + if (!pickle.ReadString(&iter, &family)) + return; + + std::string result_family; + unsigned result_filefaceid; + const bool r = font_config_->Match( + &result_family, &result_filefaceid, filefaceid_valid, filefaceid, + family, characters, characters_bytes, &is_bold, &is_italic); + + Pickle reply; + if (!r) { + reply.WriteBool(false); + } else { + reply.WriteBool(true); + reply.WriteUInt32(result_filefaceid); + reply.WriteString(result_family); + reply.WriteBool(is_bold); + reply.WriteBool(is_italic); + } + SendRendererReply(fds, reply, -1); + } + + void HandleFontOpenRequest(int fd, const Pickle& pickle, void* iter, + std::vector<int>& fds) { + uint32_t filefaceid; + if (!pickle.ReadUInt32(&iter, &filefaceid)) + return; + const int result_fd = font_config_->Open(filefaceid); + + Pickle reply; + if (result_fd == -1) { + reply.WriteBool(false); + } else { + reply.WriteBool(true); + } + + SendRendererReply(fds, reply, result_fd); + + if (result_fd >= 0) + close(result_fd); + } + + void HandleGetFontFamilyForChars(int fd, const Pickle& pickle, void* iter, + std::vector<int>& fds) { + // The other side of this call is + // chrome/renderer/renderer_sandbox_support_linux.cc + + int num_chars; + if (!pickle.ReadInt(&iter, &num_chars)) + return; + + // We don't want a corrupt renderer asking too much of us, it might + // overflow later in the code. + static const int kMaxChars = 4096; + if (num_chars < 1 || num_chars > kMaxChars) { + LOG(WARNING) << "HandleGetFontFamilyForChars: too many chars: " + << num_chars; + return; + } + + scoped_array<WebUChar> chars(new WebUChar[num_chars]); + + for (int i = 0; i < num_chars; ++i) { + uint32_t c; + if (!pickle.ReadUInt32(&iter, &c)) { + return; + } + + chars[i] = c; + } + + WebCString family = WebFontInfo::familyForChars(chars.get(), num_chars); + + Pickle reply; + if (family.data()) { + reply.WriteString(family.data()); + } else { + reply.WriteString(""); + } + SendRendererReply(fds, reply, -1); + } + + void HandleGetStyleForStrike(int fd, const Pickle& pickle, void* iter, + std::vector<int>& fds) { + std::string family; + int sizeAndStyle; + + if (!pickle.ReadString(&iter, &family) || + !pickle.ReadInt(&iter, &sizeAndStyle)) { + return; + } + + WebKit::WebFontRenderStyle style; + WebFontInfo::renderStyleForStrike(family.c_str(), sizeAndStyle, &style); + + Pickle reply; + reply.WriteInt(style.useBitmaps); + reply.WriteInt(style.useAutoHint); + reply.WriteInt(style.useHinting); + reply.WriteInt(style.hintStyle); + reply.WriteInt(style.useAntiAlias); + reply.WriteInt(style.useSubpixel); + + SendRendererReply(fds, reply, -1); + } + + void HandleLocaltime(int fd, const Pickle& pickle, void* iter, + std::vector<int>& fds) { + // The other side of this call is in zygote_main_linux.cc + + std::string time_string; + if (!pickle.ReadString(&iter, &time_string) || + time_string.size() != sizeof(time_t)) { + return; + } + + time_t time; + memcpy(&time, time_string.data(), sizeof(time)); + // We use localtime here because we need the tm_zone field to be filled + // out. Since we are a single-threaded process, this is safe. + const struct tm* expanded_time = localtime(&time); + + std::string result_string; + const char* time_zone_string = ""; + if (expanded_time != NULL) { + result_string = std::string(reinterpret_cast<const char*>(expanded_time), + sizeof(struct tm)); + time_zone_string = expanded_time->tm_zone; + } + + Pickle reply; + reply.WriteString(result_string); + reply.WriteString(time_zone_string); + SendRendererReply(fds, reply, -1); + } + + void HandleGetChildWithInode(int fd, const Pickle& pickle, void* iter, + std::vector<int>& fds) { + // The other side of this call is in zygote_main_linux.cc + if (sandbox_cmd_.empty()) { + LOG(ERROR) << "Not in the sandbox, this should not be called"; + return; + } + + uint64_t inode; + if (!pickle.ReadUInt64(&iter, &inode)) + return; + + base::ProcessId pid = 0; + std::string inode_output; + + std::vector<std::string> sandbox_cmd = sandbox_cmd_; + sandbox_cmd.push_back(base::Int64ToString(inode)); + CommandLine get_inode_cmd(sandbox_cmd); + if (base::GetAppOutput(get_inode_cmd, &inode_output)) + base::StringToInt(inode_output, &pid); + + if (!pid) { + // Even though the pid is invalid, we still need to reply to the zygote + // and not just return here. + LOG(ERROR) << "Could not get pid"; + } + + Pickle reply; + reply.WriteInt(pid); + SendRendererReply(fds, reply, -1); + } + + void HandleMakeSharedMemorySegment(int fd, const Pickle& pickle, void* iter, + std::vector<int>& fds) { + uint32_t shm_size; + if (!pickle.ReadUInt32(&iter, &shm_size)) + return; + int shm_fd = -1; + base::SharedMemory shm; + if (shm.CreateAnonymous(shm_size)) + shm_fd = shm.handle().fd; + Pickle reply; + SendRendererReply(fds, reply, shm_fd); + } + + void HandleMatchWithFallback(int fd, const Pickle& pickle, void* iter, + std::vector<int>& fds) { + // Unlike the other calls, for which we are an indirection in front of + // WebKit or Skia, this call is always made via this sandbox helper + // process. Therefore the fontconfig code goes in here directly. + + std::string face; + bool is_bold, is_italic; + uint32 charset; + + if (!pickle.ReadString(&iter, &face) || + face.empty() || + !pickle.ReadBool(&iter, &is_bold) || + !pickle.ReadBool(&iter, &is_italic) || + !pickle.ReadUInt32(&iter, &charset)) { + return; + } + + FcLangSet* langset = FcLangSetCreate(); + MSCharSetToFontconfig(langset, charset); + + FcPattern* pattern = FcPatternCreate(); + // TODO(agl): FC_FAMILy needs to change + FcPatternAddString(pattern, FC_FAMILY, (FcChar8*) face.c_str()); + if (is_bold) + FcPatternAddInteger(pattern, FC_WEIGHT, FC_WEIGHT_BOLD); + if (is_italic) + FcPatternAddInteger(pattern, FC_SLANT, FC_SLANT_ITALIC); + FcPatternAddLangSet(pattern, FC_LANG, langset); + FcPatternAddBool(pattern, FC_SCALABLE, FcTrue); + FcConfigSubstitute(NULL, pattern, FcMatchPattern); + FcDefaultSubstitute(pattern); + + FcResult result; + FcFontSet* font_set = FcFontSort(0, pattern, 0, 0, &result); + int font_fd = -1; + int good_enough_index = -1; + bool good_enough_index_set = false; + + if (font_set) { + for (int i = 0; i < font_set->nfont; ++i) { + FcPattern* current = font_set->fonts[i]; + + // Older versions of fontconfig have a bug where they cannot select + // only scalable fonts so we have to manually filter the results. + FcBool is_scalable; + if (FcPatternGetBool(current, FC_SCALABLE, 0, + &is_scalable) != FcResultMatch || + !is_scalable) { + continue; + } + + FcChar8* c_filename; + if (FcPatternGetString(current, FC_FILE, 0, &c_filename) != + FcResultMatch) { + continue; + } + + // We only want to return sfnt (TrueType) based fonts. We don't have a + // very good way of detecting this so we'll filter based on the + // filename. + bool is_sfnt = false; + static const char kSFNTExtensions[][5] = { + ".ttf", ".otc", ".TTF", ".ttc", "" + }; + const size_t filename_len = strlen(reinterpret_cast<char*>(c_filename)); + for (unsigned j = 0; ; j++) { + if (kSFNTExtensions[j][0] == 0) { + // None of the extensions matched. + break; + } + const size_t ext_len = strlen(kSFNTExtensions[j]); + if (filename_len > ext_len && + memcmp(c_filename + filename_len - ext_len, + kSFNTExtensions[j], ext_len) == 0) { + is_sfnt = true; + break; + } + } + + if (!is_sfnt) + continue; + + // This font is good enough to pass muster, but we might be able to do + // better with subsequent ones. + if (!good_enough_index_set) { + good_enough_index = i; + good_enough_index_set = true; + } + + FcValue matrix; + bool have_matrix = FcPatternGet(current, FC_MATRIX, 0, &matrix) == 0; + + if (is_italic && have_matrix) { + // we asked for an italic font, but fontconfig is giving us a + // non-italic font with a transformation matrix. + continue; + } + + FcValue embolden; + const bool have_embolden = + FcPatternGet(current, FC_EMBOLDEN, 0, &embolden) == 0; + + if (is_bold && have_embolden) { + // we asked for a bold font, but fontconfig gave us a non-bold font + // and asked us to apply fake bolding. + continue; + } + + font_fd = open(reinterpret_cast<char*>(c_filename), O_RDONLY); + if (font_fd >= 0) + break; + } + } + + if (font_fd == -1 && good_enough_index_set) { + // We didn't find a font that we liked, so we fallback to something + // acceptable. + FcPattern* current = font_set->fonts[good_enough_index]; + FcChar8* c_filename; + FcPatternGetString(current, FC_FILE, 0, &c_filename); + font_fd = open(reinterpret_cast<char*>(c_filename), O_RDONLY); + } + + if (font_set) + FcFontSetDestroy(font_set); + FcPatternDestroy(pattern); + + Pickle reply; + SendRendererReply(fds, reply, font_fd); + + if (font_fd >= 0) { + if (HANDLE_EINTR(close(font_fd)) < 0) + PLOG(ERROR) << "close"; + } + } + + // MSCharSetToFontconfig translates a Microsoft charset identifier to a + // fontconfig language set by appending to |langset|. + static void MSCharSetToFontconfig(FcLangSet* langset, unsigned fdwCharSet) { + // We have need to translate raw fdwCharSet values into terms that + // fontconfig can understand. (See the description of fdwCharSet in the MSDN + // documentation for CreateFont: + // http://msdn.microsoft.com/en-us/library/dd183499(VS.85).aspx ) + // + // Although the argument is /called/ 'charset', the actual values conflate + // character sets (which are sets of Unicode code points) and character + // encodings (which are algorithms for turning a series of bits into a + // series of code points.) Sometimes the values will name a language, + // sometimes they'll name an encoding. In the latter case I'm assuming that + // they mean the set of code points in the domain of that encoding. + // + // fontconfig deals with ISO 639-1 language codes: + // http://en.wikipedia.org/wiki/List_of_ISO_639-1_codes + // + // So, for each of the documented fdwCharSet values I've had to take a + // guess at the set of ISO 639-1 languages intended. + + switch (fdwCharSet) { + case NPCharsetAnsi: + // These values I don't really know what to do with, so I'm going to map + // them to English also. + case NPCharsetDefault: + case NPCharsetMac: + case NPCharsetOEM: + case NPCharsetSymbol: + FcLangSetAdd(langset, reinterpret_cast<const FcChar8*>("en")); + break; + case NPCharsetBaltic: + // The three baltic languages. + FcLangSetAdd(langset, reinterpret_cast<const FcChar8*>("et")); + FcLangSetAdd(langset, reinterpret_cast<const FcChar8*>("lv")); + FcLangSetAdd(langset, reinterpret_cast<const FcChar8*>("lt")); + break; + // TODO(jungshik): Would we be better off mapping Big5 to zh-tw + // and GB2312 to zh-cn? Fontconfig has 4 separate orthography + // files (zh-{cn,tw,hk,mo}. + case NPCharsetChineseBIG5: + case NPCharsetGB2312: + FcLangSetAdd(langset, reinterpret_cast<const FcChar8*>("zh")); + break; + case NPCharsetEastEurope: + // A scattering of eastern European languages. + FcLangSetAdd(langset, reinterpret_cast<const FcChar8*>("pl")); + FcLangSetAdd(langset, reinterpret_cast<const FcChar8*>("cs")); + FcLangSetAdd(langset, reinterpret_cast<const FcChar8*>("sk")); + FcLangSetAdd(langset, reinterpret_cast<const FcChar8*>("hu")); + FcLangSetAdd(langset, reinterpret_cast<const FcChar8*>("hr")); + break; + case NPCharsetGreek: + FcLangSetAdd(langset, reinterpret_cast<const FcChar8*>("el")); + break; + case NPCharsetHangul: + case NPCharsetJohab: + // Korean + FcLangSetAdd(langset, reinterpret_cast<const FcChar8*>("ko")); + break; + case NPCharsetRussian: + FcLangSetAdd(langset, reinterpret_cast<const FcChar8*>("ru")); + break; + case NPCharsetShiftJIS: + // Japanese + FcLangSetAdd(langset, reinterpret_cast<const FcChar8*>("ja")); + break; + case NPCharsetTurkish: + FcLangSetAdd(langset, reinterpret_cast<const FcChar8*>("tr")); + break; + case NPCharsetVietnamese: + FcLangSetAdd(langset, reinterpret_cast<const FcChar8*>("vi")); + break; + case NPCharsetArabic: + FcLangSetAdd(langset, reinterpret_cast<const FcChar8*>("ar")); + break; + case NPCharsetHebrew: + FcLangSetAdd(langset, reinterpret_cast<const FcChar8*>("he")); + break; + case NPCharsetThai: + FcLangSetAdd(langset, reinterpret_cast<const FcChar8*>("th")); + break; + // default: + // Don't add any languages in that case that we don't recognise the + // constant. + } + } + + void SendRendererReply(const std::vector<int>& fds, const Pickle& reply, + int reply_fd) { + struct msghdr msg; + memset(&msg, 0, sizeof(msg)); + struct iovec iov = {const_cast<void*>(reply.data()), reply.size()}; + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + + char control_buffer[CMSG_SPACE(sizeof(int))]; + + if (reply_fd != -1) { + struct stat st; + if (fstat(reply_fd, &st) == 0 && S_ISDIR(st.st_mode)) { + LOG(FATAL) << "Tried to send a directory descriptor over sandbox IPC"; + // We must never send directory descriptors to a sandboxed process + // because they can use openat with ".." elements in the path in order + // to escape the sandbox and reach the real filesystem. + } + + struct cmsghdr *cmsg; + msg.msg_control = control_buffer; + msg.msg_controllen = sizeof(control_buffer); + cmsg = CMSG_FIRSTHDR(&msg); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + cmsg->cmsg_len = CMSG_LEN(sizeof(int)); + memcpy(CMSG_DATA(cmsg), &reply_fd, sizeof(reply_fd)); + msg.msg_controllen = cmsg->cmsg_len; + } + + if (HANDLE_EINTR(sendmsg(fds[0], &msg, MSG_DONTWAIT)) < 0) + PLOG(ERROR) << "sendmsg"; + } + + // --------------------------------------------------------------------------- + + const int lifeline_fd_; + const int browser_socket_; + FontConfigDirect* const font_config_; + std::vector<std::string> sandbox_cmd_; +}; + +// ----------------------------------------------------------------------------- + +// Runs on the main thread at startup. +RenderSandboxHostLinux::RenderSandboxHostLinux() + : initialized_(false), + renderer_socket_(0), + childs_lifeline_fd_(0), + pid_(0) { +} + +// static +RenderSandboxHostLinux* RenderSandboxHostLinux::GetInstance() { + return Singleton<RenderSandboxHostLinux>::get(); +} + +void RenderSandboxHostLinux::Init(const std::string& sandbox_path) { + DCHECK(!initialized_); + initialized_ = true; + + int fds[2]; + // We use SOCK_SEQPACKET rather than SOCK_DGRAM to prevent the renderer from + // sending datagrams to other sockets on the system. The sandbox may prevent + // the renderer from calling socket() to create new sockets, but it'll still + // inherit some sockets. With PF_UNIX+SOCK_DGRAM, it can call sendmsg to send + // a datagram to any (abstract) socket on the same system. With + // SOCK_SEQPACKET, this is prevented. + CHECK(socketpair(AF_UNIX, SOCK_SEQPACKET, 0, fds) == 0); + + renderer_socket_ = fds[0]; + const int browser_socket = fds[1]; + + int pipefds[2]; + CHECK(0 == pipe(pipefds)); + const int child_lifeline_fd = pipefds[0]; + childs_lifeline_fd_ = pipefds[1]; + + pid_ = fork(); + if (pid_ == 0) { + SandboxIPCProcess handler(child_lifeline_fd, browser_socket, sandbox_path); + handler.Run(); + _exit(0); + } +} + +RenderSandboxHostLinux::~RenderSandboxHostLinux() { + if (initialized_) { + if (HANDLE_EINTR(close(renderer_socket_)) < 0) + PLOG(ERROR) << "close"; + if (HANDLE_EINTR(close(childs_lifeline_fd_)) < 0) + PLOG(ERROR) << "close"; + } +} diff --git a/content/browser/renderer_host/render_sandbox_host_linux.h b/content/browser/renderer_host/render_sandbox_host_linux.h new file mode 100644 index 0000000..8a4d0aa11 --- /dev/null +++ b/content/browser/renderer_host/render_sandbox_host_linux.h @@ -0,0 +1,52 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// http://code.google.com/p/chromium/wiki/LinuxSandboxIPC + +#ifndef CONTENT_BROWSER_RENDERER_HOST_RENDER_SANDBOX_HOST_LINUX_H_ +#define CONTENT_BROWSER_RENDERER_HOST_RENDER_SANDBOX_HOST_LINUX_H_ +#pragma once + +#include <string> + +#include "base/logging.h" + +template <typename T> struct DefaultSingletonTraits; + +// This is a singleton object which handles sandbox requests from the +// renderers. +class RenderSandboxHostLinux { + public: + // Returns the singleton instance. + static RenderSandboxHostLinux* GetInstance(); + + // Get the file descriptor which renderers should be given in order to signal + // crashes to the browser. + int GetRendererSocket() const { + DCHECK(initialized_); + return renderer_socket_; + } + pid_t pid() const { + DCHECK(initialized_); + return pid_; + } + void Init(const std::string& sandbox_path); + + private: + friend struct DefaultSingletonTraits<RenderSandboxHostLinux>; + // This object must be constructed on the main thread. + RenderSandboxHostLinux(); + ~RenderSandboxHostLinux(); + + // Whether Init() has been called yet. + bool initialized_; + + int renderer_socket_; + int childs_lifeline_fd_; + pid_t pid_; + + DISALLOW_COPY_AND_ASSIGN(RenderSandboxHostLinux); +}; + +#endif // CONTENT_BROWSER_RENDERER_HOST_RENDER_SANDBOX_HOST_LINUX_H_ diff --git a/content/browser/renderer_host/render_view_host.cc b/content/browser/renderer_host/render_view_host.cc new file mode 100644 index 0000000..58d3768 --- /dev/null +++ b/content/browser/renderer_host/render_view_host.cc @@ -0,0 +1,1712 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "content/browser/renderer_host/render_view_host.h" + +#include <string> +#include <utility> +#include <vector> + +#include "base/command_line.h" +#include "base/i18n/rtl.h" +#include "base/json/json_reader.h" +#include "base/string_util.h" +#include "base/time.h" +#include "base/utf_string_conversions.h" +#include "base/values.h" +#include "chrome/browser/child_process_security_policy.h" +#include "chrome/browser/cross_site_request_manager.h" +#include "chrome/browser/debugger/devtools_manager.h" +#include "chrome/browser/dom_operation_notification_details.h" +#include "chrome/browser/extensions/extension_message_service.h" +#include "chrome/browser/in_process_webkit/session_storage_namespace.h" +#include "chrome/browser/metrics/user_metrics.h" +#include "chrome/browser/net/predictor_api.h" +#include "chrome/browser/profiles/profile.h" +#include "chrome/browser/renderer_host/site_instance.h" +#include "chrome/common/bindings_policy.h" +#include "chrome/common/chrome_constants.h" +#include "chrome/common/chrome_switches.h" +#include "chrome/common/native_web_keyboard_event.h" +#include "chrome/common/net/url_request_context_getter.h" +#include "chrome/common/notification_details.h" +#include "chrome/common/notification_service.h" +#include "chrome/common/notification_type.h" +#include "chrome/common/render_messages.h" +#include "chrome/common/render_messages_params.h" +#include "chrome/common/result_codes.h" +#include "chrome/common/thumbnail_score.h" +#include "chrome/common/translate_errors.h" +#include "chrome/common/url_constants.h" +#include "chrome/common/web_apps.h" +#include "content/browser/renderer_host/render_process_host.h" +#include "content/browser/renderer_host/render_view_host_delegate.h" +#include "content/browser/renderer_host/render_widget_host.h" +#include "content/browser/renderer_host/render_widget_host_view.h" +#include "net/base/net_util.h" +#include "printing/native_metafile.h" +#include "third_party/skia/include/core/SkBitmap.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebFindOptions.h" +#include "ui/gfx/native_widget_types.h" +#include "webkit/glue/context_menu.h" +#include "webkit/glue/webaccessibility.h" +#include "webkit/glue/webdropdata.h" + +using base::TimeDelta; +using WebKit::WebConsoleMessage; +using WebKit::WebDragOperation; +using WebKit::WebDragOperationNone; +using WebKit::WebDragOperationsMask; +using WebKit::WebFindOptions; +using WebKit::WebInputEvent; +using WebKit::WebMediaPlayerAction; +using WebKit::WebTextDirection; + +namespace { + +// Delay to wait on closing the tab for a beforeunload/unload handler to fire. +const int kUnloadTimeoutMS = 1000; + +} // namespace + +/////////////////////////////////////////////////////////////////////////////// +// RenderViewHost, public: + +// static +RenderViewHost* RenderViewHost::FromID(int render_process_id, + int render_view_id) { + RenderProcessHost* process = RenderProcessHost::FromID(render_process_id); + if (!process) + return NULL; + RenderWidgetHost* widget = static_cast<RenderWidgetHost*>( + process->GetListenerByID(render_view_id)); + if (!widget || !widget->IsRenderView()) + return NULL; + return static_cast<RenderViewHost*>(widget); +} + +RenderViewHost::RenderViewHost(SiteInstance* instance, + RenderViewHostDelegate* delegate, + int routing_id, + SessionStorageNamespace* session_storage) + : RenderWidgetHost(instance->GetProcess(), routing_id), + instance_(instance), + delegate_(delegate), + waiting_for_drag_context_response_(false), + enabled_bindings_(0), + pending_request_id_(0), + navigations_suspended_(false), + suspended_nav_message_(NULL), + run_modal_reply_msg_(NULL), + is_waiting_for_beforeunload_ack_(false), + is_waiting_for_unload_ack_(false), + unload_ack_is_for_cross_site_transition_(false), + are_javascript_messages_suppressed_(false), + sudden_termination_allowed_(false), + session_storage_namespace_(session_storage), + is_extension_process_(false), + save_accessibility_tree_for_testing_(false), + render_view_termination_status_(base::TERMINATION_STATUS_STILL_RUNNING) { + if (!session_storage_namespace_) { + session_storage_namespace_ = + new SessionStorageNamespace(process()->profile()); + } + + DCHECK(instance_); + DCHECK(delegate_); +} + +RenderViewHost::~RenderViewHost() { + delegate()->RenderViewDeleted(this); + + // Be sure to clean up any leftover state from cross-site requests. + CrossSiteRequestManager::GetInstance()->SetHasPendingCrossSiteRequest( + process()->id(), routing_id(), false); +} + +bool RenderViewHost::CreateRenderView(const string16& frame_name) { + DCHECK(!IsRenderViewLive()) << "Creating view twice"; + + // The process may (if we're sharing a process with another host that already + // initialized it) or may not (we have our own process or the old process + // crashed) have been initialized. Calling Init multiple times will be + // ignored, so this is safe. + if (!process()->Init(renderer_accessible(), is_extension_process_)) + return false; + DCHECK(process()->HasConnection()); + DCHECK(process()->profile()); + + if (BindingsPolicy::is_web_ui_enabled(enabled_bindings_)) { + ChildProcessSecurityPolicy::GetInstance()->GrantWebUIBindings( + process()->id()); + } + + if (BindingsPolicy::is_extension_enabled(enabled_bindings_)) { + ChildProcessSecurityPolicy::GetInstance()->GrantExtensionBindings( + process()->id()); + + // Extensions may have permission to access chrome:// URLs. + ChildProcessSecurityPolicy::GetInstance()->GrantScheme( + process()->id(), chrome::kChromeUIScheme); + } + + renderer_initialized_ = true; + + ViewMsg_New_Params params; + params.parent_window = GetNativeViewId(); + params.renderer_preferences = + delegate_->GetRendererPrefs(process()->profile()); + params.web_preferences = delegate_->GetWebkitPrefs(); + params.view_id = routing_id(); + params.session_storage_namespace_id = session_storage_namespace_->id(); + params.frame_name = frame_name; + Send(new ViewMsg_New(params)); + + // Set the alternate error page, which is profile specific, in the renderer. + GURL url = delegate_->GetAlternateErrorPageURL(); + SetAlternateErrorPageURL(url); + + // If it's enabled, tell the renderer to set up the Javascript bindings for + // sending messages back to the browser. + Send(new ViewMsg_AllowBindings(routing_id(), enabled_bindings_)); + UpdateBrowserWindowId(delegate_->GetBrowserWindowID()); + Send(new ViewMsg_NotifyRenderViewType(routing_id(), + delegate_->GetRenderViewType())); + // Let our delegate know that we created a RenderView. + delegate_->RenderViewCreated(this); + process()->ViewCreated(); + + return true; +} + +bool RenderViewHost::IsRenderViewLive() const { + return process()->HasConnection() && renderer_initialized_; +} + +void RenderViewHost::SyncRendererPrefs() { + Send(new ViewMsg_SetRendererPrefs(routing_id(), + delegate_->GetRendererPrefs( + process()->profile()))); +} + +void RenderViewHost::Navigate(const ViewMsg_Navigate_Params& params) { + ChildProcessSecurityPolicy::GetInstance()->GrantRequestURL( + process()->id(), params.url); + + ViewMsg_Navigate* nav_message = new ViewMsg_Navigate(routing_id(), params); + + // Only send the message if we aren't suspended at the start of a cross-site + // request. + if (navigations_suspended_) { + // Shouldn't be possible to have a second navigation while suspended, since + // navigations will only be suspended during a cross-site request. If a + // second navigation occurs, TabContents will cancel this pending RVH + // create a new pending RVH. + DCHECK(!suspended_nav_message_.get()); + suspended_nav_message_.reset(nav_message); + } else { + // Unset this, otherwise if true and the hang monitor fires we'll + // incorrectly close the tab. + is_waiting_for_unload_ack_ = false; + + Send(nav_message); + + // Force the throbber to start. We do this because WebKit's "started + // loading" message will be received asynchronously from the UI of the + // browser. But we want to keep the throbber in sync with what's happening + // in the UI. For example, we want to start throbbing immediately when the + // user naivgates even if the renderer is delayed. There is also an issue + // with the throbber starting because the WebUI (which controls whether the + // favicon is displayed) happens synchronously. If the start loading + // messages was asynchronous, then the default favicon would flash in. + // + // WebKit doesn't send throb notifications for JavaScript URLs, so we + // don't want to either. + if (!params.url.SchemeIs(chrome::kJavaScriptScheme)) + delegate_->DidStartLoading(); + } + const GURL& url = params.url; + if (!delegate_->IsExternalTabContainer() && + (url.SchemeIs("http") || url.SchemeIs("https"))) + chrome_browser_net::PreconnectUrlAndSubresources(url); +} + +void RenderViewHost::NavigateToURL(const GURL& url) { + ViewMsg_Navigate_Params params; + params.page_id = -1; + params.url = url; + params.transition = PageTransition::LINK; + params.navigation_type = ViewMsg_Navigate_Params::NORMAL; + Navigate(params); +} + +void RenderViewHost::SetNavigationsSuspended(bool suspend) { + // This should only be called to toggle the state. + DCHECK(navigations_suspended_ != suspend); + + navigations_suspended_ = suspend; + if (!suspend && suspended_nav_message_.get()) { + // There's a navigation message waiting to be sent. Now that we're not + // suspended anymore, resume navigation by sending it. + Send(suspended_nav_message_.release()); + } +} + +void RenderViewHost::FirePageBeforeUnload(bool for_cross_site_transition) { + if (!IsRenderViewLive()) { + // This RenderViewHost doesn't have a live renderer, so just skip running + // the onbeforeunload handler. + is_waiting_for_beforeunload_ack_ = true; // Checked by OnMsgShouldCloseACK. + unload_ack_is_for_cross_site_transition_ = for_cross_site_transition; + OnMsgShouldCloseACK(true); + return; + } + + // This may be called more than once (if the user clicks the tab close button + // several times, or if she clicks the tab close button then the browser close + // button), and we only send the message once. + if (is_waiting_for_beforeunload_ack_) { + // Some of our close messages could be for the tab, others for cross-site + // transitions. We always want to think it's for closing the tab if any + // of the messages were, since otherwise it might be impossible to close + // (if there was a cross-site "close" request pending when the user clicked + // the close button). We want to keep the "for cross site" flag only if + // both the old and the new ones are also for cross site. + unload_ack_is_for_cross_site_transition_ = + unload_ack_is_for_cross_site_transition_ && for_cross_site_transition; + } else { + // Start the hang monitor in case the renderer hangs in the beforeunload + // handler. + is_waiting_for_beforeunload_ack_ = true; + unload_ack_is_for_cross_site_transition_ = for_cross_site_transition; + StartHangMonitorTimeout(TimeDelta::FromMilliseconds(kUnloadTimeoutMS)); + Send(new ViewMsg_ShouldClose(routing_id())); + } +} + +void RenderViewHost::ClosePage(bool for_cross_site_transition, + int new_render_process_host_id, + int new_request_id) { + // In most cases, this will not be set to false afterward. Either the tab + // will be closed, or a pending RenderViewHost will replace this one. + is_waiting_for_unload_ack_ = true; + // Start the hang monitor in case the renderer hangs in the unload handler. + StartHangMonitorTimeout(TimeDelta::FromMilliseconds(kUnloadTimeoutMS)); + + ViewMsg_ClosePage_Params params; + params.closing_process_id = process()->id(); + params.closing_route_id = routing_id(); + params.for_cross_site_transition = for_cross_site_transition; + params.new_render_process_host_id = new_render_process_host_id; + params.new_request_id = new_request_id; + if (IsRenderViewLive()) { + NotificationService::current()->Notify( + NotificationType::RENDER_VIEW_HOST_WILL_CLOSE_RENDER_VIEW, + Source<RenderViewHost>(this), + NotificationService::NoDetails()); + + Send(new ViewMsg_ClosePage(routing_id(), params)); + } else { + // This RenderViewHost doesn't have a live renderer, so just skip closing + // the page. We must notify the ResourceDispatcherHost on the IO thread, + // which we will do through the RenderProcessHost's widget helper. + process()->CrossSiteClosePageACK(params); + } +} + +void RenderViewHost::ClosePageIgnoringUnloadEvents() { + StopHangMonitorTimeout(); + is_waiting_for_beforeunload_ack_ = false; + is_waiting_for_unload_ack_ = false; + + sudden_termination_allowed_ = true; + delegate_->Close(this); +} + +void RenderViewHost::SetHasPendingCrossSiteRequest(bool has_pending_request, + int request_id) { + CrossSiteRequestManager::GetInstance()->SetHasPendingCrossSiteRequest( + process()->id(), routing_id(), has_pending_request); + pending_request_id_ = request_id; +} + +int RenderViewHost::GetPendingRequestId() { + return pending_request_id_; +} + +RenderViewHost::CommandState RenderViewHost::GetStateForCommand( + RenderViewCommand command) const { + if (command != RENDER_VIEW_COMMAND_TOGGLE_SPELL_CHECK) + LOG(DFATAL) << "Unknown command " << command; + + std::map<RenderViewCommand, CommandState>::const_iterator it = + command_states_.find(command); + if (it == command_states_.end()) { + CommandState state; + state.is_enabled = false; + state.checked_state = RENDER_VIEW_COMMAND_CHECKED_STATE_UNCHECKED; + return state; + } + return it->second; +} + +void RenderViewHost::Stop() { + Send(new ViewMsg_Stop(routing_id())); +} + +void RenderViewHost::ReloadFrame() { + Send(new ViewMsg_ReloadFrame(routing_id())); +} + +bool RenderViewHost::PrintPages() { + return Send(new ViewMsg_PrintPages(routing_id())); +} + +bool RenderViewHost::PrintPreview() { + return Send(new ViewMsg_PrintPreview(routing_id())); +} + +void RenderViewHost::PrintingDone(int document_cookie, bool success) { + Send(new ViewMsg_PrintingDone(routing_id(), document_cookie, success)); +} + +void RenderViewHost::StartFinding(int request_id, + const string16& search_text, + bool forward, + bool match_case, + bool find_next) { + if (search_text.empty()) + return; + + WebFindOptions options; + options.forward = forward; + options.matchCase = match_case; + options.findNext = find_next; + Send(new ViewMsg_Find(routing_id(), request_id, search_text, options)); + + // This call is asynchronous and returns immediately. + // The result of the search is sent as a notification message by the renderer. +} + +void RenderViewHost::StopFinding( + FindBarController::SelectionAction selection_action) { + ViewMsg_StopFinding_Params params; + + switch (selection_action) { + case FindBarController::kClearSelection: + params.action = ViewMsg_StopFinding_Params::kClearSelection; + break; + case FindBarController::kKeepSelection: + params.action = ViewMsg_StopFinding_Params::kKeepSelection; + break; + case FindBarController::kActivateSelection: + params.action = ViewMsg_StopFinding_Params::kActivateSelection; + break; + default: + NOTREACHED(); + params.action = ViewMsg_StopFinding_Params::kKeepSelection; + } + Send(new ViewMsg_StopFinding(routing_id(), params)); +} + +void RenderViewHost::Zoom(PageZoom::Function function) { + Send(new ViewMsg_Zoom(routing_id(), function)); +} + +void RenderViewHost::SetZoomLevel(double zoom_level) { + Send(new ViewMsg_SetZoomLevel(routing_id(), zoom_level)); +} + +void RenderViewHost::SetPageEncoding(const std::string& encoding_name) { + Send(new ViewMsg_SetPageEncoding(routing_id(), encoding_name)); +} + +void RenderViewHost::ResetPageEncodingToDefault() { + Send(new ViewMsg_ResetPageEncodingToDefault(routing_id())); +} + +void RenderViewHost::SetAlternateErrorPageURL(const GURL& url) { + Send(new ViewMsg_SetAltErrorPageURL(routing_id(), url)); +} + +void RenderViewHost::DragTargetDragEnter( + const WebDropData& drop_data, + const gfx::Point& client_pt, + const gfx::Point& screen_pt, + WebDragOperationsMask operations_allowed) { + // Grant the renderer the ability to load the drop_data. + ChildProcessSecurityPolicy* policy = + ChildProcessSecurityPolicy::GetInstance(); + policy->GrantRequestURL(process()->id(), drop_data.url); + for (std::vector<string16>::const_iterator iter(drop_data.filenames.begin()); + iter != drop_data.filenames.end(); ++iter) { + FilePath path = FilePath::FromWStringHack(UTF16ToWideHack(*iter)); + policy->GrantRequestURL(process()->id(), + net::FilePathToFileURL(path)); + policy->GrantReadFile(process()->id(), path); + } + Send(new ViewMsg_DragTargetDragEnter(routing_id(), drop_data, client_pt, + screen_pt, operations_allowed)); +} + +void RenderViewHost::DragTargetDragOver( + const gfx::Point& client_pt, const gfx::Point& screen_pt, + WebDragOperationsMask operations_allowed) { + Send(new ViewMsg_DragTargetDragOver(routing_id(), client_pt, screen_pt, + operations_allowed)); +} + +void RenderViewHost::DragTargetDragLeave() { + Send(new ViewMsg_DragTargetDragLeave(routing_id())); +} + +void RenderViewHost::DragTargetDrop( + const gfx::Point& client_pt, const gfx::Point& screen_pt) { + Send(new ViewMsg_DragTargetDrop(routing_id(), client_pt, screen_pt)); +} + +void RenderViewHost::ReservePageIDRange(int size) { + Send(new ViewMsg_ReservePageIDRange(routing_id(), size)); +} + +void RenderViewHost::ExecuteJavascriptInWebFrame( + const string16& frame_xpath, + const string16& jscript) { + Send(new ViewMsg_ScriptEvalRequest(routing_id(), frame_xpath, jscript, + 0, false)); +} + +int RenderViewHost::ExecuteJavascriptInWebFrameNotifyResult( + const string16& frame_xpath, + const string16& jscript) { + static int next_id = 1; + Send(new ViewMsg_ScriptEvalRequest(routing_id(), frame_xpath, jscript, + next_id, true)); + return next_id++; +} + +void RenderViewHost::InsertCSSInWebFrame( + const std::wstring& frame_xpath, + const std::string& css, + const std::string& id) { + Send(new ViewMsg_CSSInsertRequest(routing_id(), frame_xpath, css, id)); +} + +void RenderViewHost::AddMessageToConsole( + const string16& frame_xpath, + const string16& message, + const WebConsoleMessage::Level& level) { + Send(new ViewMsg_AddMessageToConsole( + routing_id(), frame_xpath, message, level)); +} + +void RenderViewHost::Undo() { + Send(new ViewMsg_Undo(routing_id())); +} + +void RenderViewHost::Redo() { + Send(new ViewMsg_Redo(routing_id())); +} + +void RenderViewHost::Cut() { + Send(new ViewMsg_Cut(routing_id())); +} + +void RenderViewHost::Copy() { + Send(new ViewMsg_Copy(routing_id())); +} + +void RenderViewHost::CopyToFindPboard() { +#if defined(OS_MACOSX) + // Windows/Linux don't have the concept of a find pasteboard. + Send(new ViewMsg_CopyToFindPboard(routing_id())); +#endif +} + +void RenderViewHost::Paste() { + Send(new ViewMsg_Paste(routing_id())); +} + +void RenderViewHost::ToggleSpellCheck() { + Send(new ViewMsg_ToggleSpellCheck(routing_id())); +} + +void RenderViewHost::Delete() { + Send(new ViewMsg_Delete(routing_id())); +} + +void RenderViewHost::SelectAll() { + Send(new ViewMsg_SelectAll(routing_id())); +} + +void RenderViewHost::ToggleSpellPanel(bool is_currently_visible) { + Send(new ViewMsg_ToggleSpellPanel(routing_id(), is_currently_visible)); +} + +int RenderViewHost::DownloadFavIcon(const GURL& url, int image_size) { + if (!url.is_valid()) { + NOTREACHED(); + return 0; + } + static int next_id = 1; + int id = next_id++; + Send(new ViewMsg_DownloadFavIcon(routing_id(), id, url, image_size)); + return id; +} + +void RenderViewHost::GetApplicationInfo(int32 page_id) { + Send(new ViewMsg_GetApplicationInfo(routing_id(), page_id)); +} + +void RenderViewHost::CaptureThumbnail() { + Send(new ViewMsg_CaptureThumbnail(routing_id())); +} + +void RenderViewHost::CaptureSnapshot() { + Send(new ViewMsg_CaptureSnapshot(routing_id())); +} + +void RenderViewHost::JavaScriptMessageBoxClosed(IPC::Message* reply_msg, + bool success, + const std::wstring& prompt) { + process()->set_ignore_input_events(false); + bool is_waiting = + is_waiting_for_beforeunload_ack_ || is_waiting_for_unload_ack_; + if (is_waiting) + StartHangMonitorTimeout(TimeDelta::FromMilliseconds(kUnloadTimeoutMS)); + + ViewHostMsg_RunJavaScriptMessage::WriteReplyParams(reply_msg, + success, prompt); + Send(reply_msg); + + // If we are waiting for an unload or beforeunload ack and the user has + // suppressed messages, kill the tab immediately; a page that's spamming + // alerts in onbeforeunload is presumably malicious, so there's no point in + // continuing to run its script and dragging out the process. + // This must be done after sending the reply since RenderView can't close + // correctly while waiting for a response. + if (is_waiting && are_javascript_messages_suppressed_) + delegate_->RendererUnresponsive(this, is_waiting); +} + +void RenderViewHost::ModalHTMLDialogClosed(IPC::Message* reply_msg, + const std::string& json_retval) { + if (is_waiting_for_beforeunload_ack_ || is_waiting_for_unload_ack_) + StartHangMonitorTimeout(TimeDelta::FromMilliseconds(kUnloadTimeoutMS)); + + ViewHostMsg_ShowModalHTMLDialog::WriteReplyParams(reply_msg, json_retval); + Send(reply_msg); +} + +void RenderViewHost::CopyImageAt(int x, int y) { + Send(new ViewMsg_CopyImageAt(routing_id(), x, y)); +} + +void RenderViewHost::DragSourceEndedAt( + int client_x, int client_y, int screen_x, int screen_y, + WebDragOperation operation) { + Send(new ViewMsg_DragSourceEndedOrMoved( + routing_id(), + gfx::Point(client_x, client_y), + gfx::Point(screen_x, screen_y), + true, operation)); +} + +void RenderViewHost::DragSourceMovedTo( + int client_x, int client_y, int screen_x, int screen_y) { + Send(new ViewMsg_DragSourceEndedOrMoved( + routing_id(), + gfx::Point(client_x, client_y), + gfx::Point(screen_x, screen_y), + false, WebDragOperationNone)); +} + +void RenderViewHost::DragSourceSystemDragEnded() { + Send(new ViewMsg_DragSourceSystemDragEnded(routing_id())); +} + +void RenderViewHost::AllowBindings(int bindings_flags) { + DCHECK(!renderer_initialized_); + enabled_bindings_ |= bindings_flags; +} + +void RenderViewHost::SetWebUIProperty(const std::string& name, + const std::string& value) { + DCHECK(BindingsPolicy::is_web_ui_enabled(enabled_bindings_)); + Send(new ViewMsg_SetWebUIProperty(routing_id(), name, value)); +} + +void RenderViewHost::GotFocus() { + RenderWidgetHost::GotFocus(); // Notifies the renderer it got focus. + + RenderViewHostDelegate::View* view = delegate_->GetViewDelegate(); + if (view) + view->GotFocus(); +} + +void RenderViewHost::LostCapture() { + RenderWidgetHost::LostCapture(); + + RenderViewHostDelegate::View* view = delegate_->GetViewDelegate(); + if (view) + view->LostCapture(); +} + +void RenderViewHost::SetInitialFocus(bool reverse) { + Send(new ViewMsg_SetInitialFocus(routing_id(), reverse)); +} + +void RenderViewHost::ClearFocusedNode() { + Send(new ViewMsg_ClearFocusedNode(routing_id())); +} + +void RenderViewHost::ScrollFocusedEditableNodeIntoView() { + Send(new ViewMsg_ScrollFocusedEditableNodeIntoView(routing_id())); +} + +void RenderViewHost::UpdateWebPreferences(const WebPreferences& prefs) { + Send(new ViewMsg_UpdateWebPreferences(routing_id(), prefs)); +} + +void RenderViewHost::InstallMissingPlugin() { + Send(new ViewMsg_InstallMissingPlugin(routing_id())); +} + +void RenderViewHost::LoadBlockedPlugins() { + Send(new ViewMsg_LoadBlockedPlugins(routing_id())); +} + +void RenderViewHost::FilesSelectedInChooser( + const std::vector<FilePath>& files) { + // Grant the security access requested to the given files. + for (std::vector<FilePath>::const_iterator file = files.begin(); + file != files.end(); ++file) { + ChildProcessSecurityPolicy::GetInstance()->GrantReadFile( + process()->id(), *file); + } + Send(new ViewMsg_RunFileChooserResponse(routing_id(), files)); +} + +void RenderViewHost::LoadStateChanged(const GURL& url, + net::LoadState load_state, + uint64 upload_position, + uint64 upload_size) { + delegate_->LoadStateChanged(url, load_state, upload_position, upload_size); +} + +bool RenderViewHost::SuddenTerminationAllowed() const { + return sudden_termination_allowed_ || process()->sudden_termination_allowed(); +} + +/////////////////////////////////////////////////////////////////////////////// +// RenderViewHost, IPC message handlers: + +bool RenderViewHost::OnMessageReceived(const IPC::Message& msg) { +#if defined(OS_WIN) + // On Windows there's a potential deadlock with sync messsages going in + // a circle from browser -> plugin -> renderer -> browser. + // On Linux we can avoid this by avoiding sync messages from browser->plugin. + // On Mac we avoid this by not supporting windowed plugins. + if (msg.is_sync() && !msg.is_caller_pumping_messages()) { + // NOTE: IF YOU HIT THIS ASSERT, THE SOLUTION IS ALMOST NEVER TO RUN A + // NESTED MESSAGE LOOP IN THE RENDERER!!! + // That introduces reentrancy which causes hard to track bugs. You should + // find a way to either turn this into an asynchronous message, or one + // that can be answered on the IO thread. + NOTREACHED() << "Can't send sync messages to UI thread without pumping " + "messages in the renderer or else deadlocks can occur if the page " + "has windowed plugins! (message type " << msg.type() << ")"; + IPC::Message* reply = IPC::SyncMessage::GenerateReply(&msg); + reply->set_reply_error(); + Send(reply); + return true; + } +#endif + + if (delegate_->OnMessageReceived(msg)) + return true; + + bool handled = true; + bool msg_is_ok = true; + IPC_BEGIN_MESSAGE_MAP_EX(RenderViewHost, msg, msg_is_ok) + IPC_MESSAGE_HANDLER(ViewHostMsg_ShowView, OnMsgShowView) + IPC_MESSAGE_HANDLER(ViewHostMsg_ShowWidget, OnMsgShowWidget) + IPC_MESSAGE_HANDLER(ViewHostMsg_ShowFullscreenWidget, + OnMsgShowFullscreenWidget) + IPC_MESSAGE_HANDLER_DELAY_REPLY(ViewHostMsg_RunModal, OnMsgRunModal) + IPC_MESSAGE_HANDLER(ViewHostMsg_RenderViewReady, OnMsgRenderViewReady) + IPC_MESSAGE_HANDLER(ViewHostMsg_RenderViewGone, OnMsgRenderViewGone) + IPC_MESSAGE_HANDLER_GENERIC(ViewHostMsg_FrameNavigate, OnMsgNavigate(msg)) + IPC_MESSAGE_HANDLER(ViewHostMsg_UpdateState, OnMsgUpdateState) + IPC_MESSAGE_HANDLER(ViewHostMsg_UpdateTitle, OnMsgUpdateTitle) + IPC_MESSAGE_HANDLER(ViewHostMsg_UpdateEncoding, OnMsgUpdateEncoding) + IPC_MESSAGE_HANDLER(ViewHostMsg_UpdateTargetURL, OnMsgUpdateTargetURL) + IPC_MESSAGE_HANDLER(ViewHostMsg_Thumbnail, OnMsgThumbnail) + IPC_MESSAGE_HANDLER(ViewHostMsg_Snapshot, OnMsgScreenshot) + IPC_MESSAGE_HANDLER(ViewHostMsg_UpdateInspectorSetting, + OnUpdateInspectorSetting) + IPC_MESSAGE_HANDLER(ViewHostMsg_Close, OnMsgClose) + IPC_MESSAGE_HANDLER(ViewHostMsg_RequestMove, OnMsgRequestMove) + IPC_MESSAGE_HANDLER(ViewHostMsg_DidStartLoading, OnMsgDidStartLoading) + IPC_MESSAGE_HANDLER(ViewHostMsg_DidStopLoading, OnMsgDidStopLoading) + IPC_MESSAGE_HANDLER(ViewHostMsg_DidChangeLoadProgress, + OnMsgDidChangeLoadProgress) + IPC_MESSAGE_HANDLER(ViewHostMsg_DocumentAvailableInMainFrame, + OnMsgDocumentAvailableInMainFrame) + IPC_MESSAGE_HANDLER(ViewHostMsg_DocumentOnLoadCompletedInMainFrame, + OnMsgDocumentOnLoadCompletedInMainFrame) + IPC_MESSAGE_HANDLER(ViewMsg_ExecuteCodeFinished, + OnExecuteCodeFinished) + IPC_MESSAGE_HANDLER(ViewHostMsg_ContextMenu, OnMsgContextMenu) + IPC_MESSAGE_HANDLER(ViewHostMsg_OpenURL, OnMsgOpenURL) + IPC_MESSAGE_HANDLER(ViewHostMsg_DidContentsPreferredSizeChange, + OnMsgDidContentsPreferredSizeChange) + IPC_MESSAGE_HANDLER(ViewHostMsg_DomOperationResponse, + OnMsgDomOperationResponse) + IPC_MESSAGE_HANDLER(ViewHostMsg_WebUISend, OnMsgWebUISend) + IPC_MESSAGE_HANDLER(ViewHostMsg_ForwardMessageToExternalHost, + OnMsgForwardMessageToExternalHost) + IPC_MESSAGE_HANDLER(ViewHostMsg_SetTooltipText, OnMsgSetTooltipText) + IPC_MESSAGE_HANDLER_DELAY_REPLY(ViewHostMsg_RunJavaScriptMessage, + OnMsgRunJavaScriptMessage) + IPC_MESSAGE_HANDLER_DELAY_REPLY(ViewHostMsg_RunBeforeUnloadConfirm, + OnMsgRunBeforeUnloadConfirm) + IPC_MESSAGE_HANDLER_DELAY_REPLY(ViewHostMsg_ShowModalHTMLDialog, + OnMsgShowModalHTMLDialog) + IPC_MESSAGE_HANDLER(ViewHostMsg_StartDragging, OnMsgStartDragging) + IPC_MESSAGE_HANDLER(ViewHostMsg_UpdateDragCursor, OnUpdateDragCursor) + IPC_MESSAGE_HANDLER(ViewHostMsg_TakeFocus, OnTakeFocus) + IPC_MESSAGE_HANDLER(ViewHostMsg_AddMessageToConsole, OnAddMessageToConsole) + IPC_MESSAGE_HANDLER(ViewHostMsg_ForwardToDevToolsAgent, + OnForwardToDevToolsAgent) + IPC_MESSAGE_HANDLER(ViewHostMsg_ForwardToDevToolsClient, + OnForwardToDevToolsClient) + IPC_MESSAGE_HANDLER(ViewHostMsg_ActivateDevToolsWindow, + OnActivateDevToolsWindow) + IPC_MESSAGE_HANDLER(ViewHostMsg_CloseDevToolsWindow, + OnCloseDevToolsWindow) + IPC_MESSAGE_HANDLER(ViewHostMsg_RequestDockDevToolsWindow, + OnRequestDockDevToolsWindow) + IPC_MESSAGE_HANDLER(ViewHostMsg_RequestUndockDevToolsWindow, + OnRequestUndockDevToolsWindow) + IPC_MESSAGE_HANDLER(ViewHostMsg_DevToolsRuntimePropertyChanged, + OnDevToolsRuntimePropertyChanged) + IPC_MESSAGE_HANDLER(ViewHostMsg_ShouldClose_ACK, OnMsgShouldCloseACK) + IPC_MESSAGE_HANDLER(ViewHostMsg_ExtensionRequest, OnExtensionRequest) + IPC_MESSAGE_HANDLER(ViewHostMsg_SelectionChanged, OnMsgSelectionChanged) + IPC_MESSAGE_HANDLER(ViewHostMsg_ExtensionPostMessage, + OnExtensionPostMessage) + IPC_MESSAGE_HANDLER(ViewHostMsg_AccessibilityNotifications, + OnAccessibilityNotifications) + IPC_MESSAGE_HANDLER(ViewHostMsg_OnCSSInserted, OnCSSInserted) + IPC_MESSAGE_HANDLER(ViewHostMsg_ContentBlocked, OnContentBlocked) + IPC_MESSAGE_HANDLER(ViewHostMsg_AppCacheAccessed, OnAppCacheAccessed) + IPC_MESSAGE_HANDLER(ViewHostMsg_WebDatabaseAccessed, OnWebDatabaseAccessed) + IPC_MESSAGE_HANDLER(ViewHostMsg_FocusedNodeChanged, OnMsgFocusedNodeChanged) + IPC_MESSAGE_HANDLER(ViewHostMsg_UpdateZoomLimits, OnUpdateZoomLimits) + IPC_MESSAGE_HANDLER(ViewHostMsg_ScriptEvalResponse, OnScriptEvalResponse) +#if defined(OS_MACOSX) + IPC_MESSAGE_HANDLER(ViewHostMsg_ShowPopup, OnMsgShowPopup) +#endif + IPC_MESSAGE_HANDLER(ViewHostMsg_CommandStateChanged, + OnCommandStateChanged) + // Have the super handle all other messages. + IPC_MESSAGE_UNHANDLED(handled = RenderWidgetHost::OnMessageReceived(msg)) + IPC_END_MESSAGE_MAP_EX() + + if (!msg_is_ok) { + // The message had a handler, but its de-serialization failed. + // Kill the renderer. + UserMetrics::RecordAction(UserMetricsAction("BadMessageTerminate_RVH")); + process()->ReceivedBadMessage(); + } + + return handled; +} + +void RenderViewHost::Shutdown() { + // If we are being run modally (see RunModal), then we need to cleanup. + if (run_modal_reply_msg_) { + Send(run_modal_reply_msg_); + run_modal_reply_msg_ = NULL; + } + + DevToolsManager* devtools_manager = DevToolsManager::GetInstance(); + if (devtools_manager) // NULL in tests + devtools_manager->UnregisterDevToolsClientHostFor(this); + + RenderWidgetHost::Shutdown(); +} + +bool RenderViewHost::IsRenderView() const { + return true; +} + +void RenderViewHost::CreateNewWindow( + int route_id, + const ViewHostMsg_CreateWindow_Params& params) { + RenderViewHostDelegate::View* view = delegate_->GetViewDelegate(); + if (!view) + return; + + view->CreateNewWindow(route_id, params); +} + +void RenderViewHost::CreateNewWidget(int route_id, + WebKit::WebPopupType popup_type) { + RenderViewHostDelegate::View* view = delegate_->GetViewDelegate(); + if (view) + view->CreateNewWidget(route_id, popup_type); +} + +void RenderViewHost::CreateNewFullscreenWidget(int route_id) { + RenderViewHostDelegate::View* view = delegate_->GetViewDelegate(); + if (view) + view->CreateNewFullscreenWidget(route_id); +} + +void RenderViewHost::OnMsgShowView(int route_id, + WindowOpenDisposition disposition, + const gfx::Rect& initial_pos, + bool user_gesture) { + RenderViewHostDelegate::View* view = delegate_->GetViewDelegate(); + if (view) { + view->ShowCreatedWindow(route_id, disposition, initial_pos, user_gesture); + Send(new ViewMsg_Move_ACK(route_id)); + } +} + +void RenderViewHost::OnMsgShowWidget(int route_id, + const gfx::Rect& initial_pos) { + RenderViewHostDelegate::View* view = delegate_->GetViewDelegate(); + if (view) { + view->ShowCreatedWidget(route_id, initial_pos); + Send(new ViewMsg_Move_ACK(route_id)); + } +} + +void RenderViewHost::OnMsgShowFullscreenWidget(int route_id) { + RenderViewHostDelegate::View* view = delegate_->GetViewDelegate(); + if (view) { + view->ShowCreatedFullscreenWidget(route_id); + Send(new ViewMsg_Move_ACK(route_id)); + } +} + +void RenderViewHost::OnMsgRunModal(IPC::Message* reply_msg) { + DCHECK(!run_modal_reply_msg_); + run_modal_reply_msg_ = reply_msg; + + // TODO(darin): Bug 1107929: Need to inform our delegate to show this view in + // an app-modal fashion. +} + +void RenderViewHost::OnMsgRenderViewReady() { + render_view_termination_status_ = base::TERMINATION_STATUS_STILL_RUNNING; + WasResized(); + delegate_->RenderViewReady(this); +} + +void RenderViewHost::OnMsgRenderViewGone(int status, int exit_code) { + // Keep the termination status so we can get at it later when we + // need to know why it died. + render_view_termination_status_ = + static_cast<base::TerminationStatus>(status); + + // Our base class RenderWidgetHost needs to reset some stuff. + RendererExited(render_view_termination_status_, exit_code); + + delegate_->RenderViewGone(this, + static_cast<base::TerminationStatus>(status), + exit_code); +} + +// Called when the renderer navigates. For every frame loaded, we'll get this +// notification containing parameters identifying the navigation. +// +// Subframes are identified by the page transition type. For subframes loaded +// as part of a wider page load, the page_id will be the same as for the top +// level frame. If the user explicitly requests a subframe navigation, we will +// get a new page_id because we need to create a new navigation entry for that +// action. +void RenderViewHost::OnMsgNavigate(const IPC::Message& msg) { + // Read the parameters out of the IPC message directly to avoid making another + // copy when we filter the URLs. + void* iter = NULL; + ViewHostMsg_FrameNavigate_Params validated_params; + if (!IPC::ParamTraits<ViewHostMsg_FrameNavigate_Params>:: + Read(&msg, &iter, &validated_params)) + return; + + // If we're waiting for a beforeunload ack from this renderer and we receive + // a Navigate message from the main frame, then the renderer was navigating + // before it received the request. If it is during a cross-site navigation, + // then we should forget about the beforeunload, because the navigation will + // now be canceled. (If it is instead during an attempt to close the page, + // we should be sure to keep waiting for the ack, which the new page will + // send.) + // + // If we did not clear this state, an unresponsiveness timer might think we + // are waiting for an ack but are not in a cross-site navigation, and would + // close the tab. TODO(creis): That timer code should be refactored to only + // close the tab if we explicitly know the user tried to close the tab, and + // not just check for the absence of a cross-site navigation. Once that's + // fixed, this check can go away. + if (is_waiting_for_beforeunload_ack_ && + unload_ack_is_for_cross_site_transition_ && + PageTransition::IsMainFrame(validated_params.transition)) { + is_waiting_for_beforeunload_ack_ = false; + StopHangMonitorTimeout(); + } + + // If we're waiting for an unload ack from this renderer and we receive a + // Navigate message, then the renderer was navigating before it received the + // unload request. It will either respond to the unload request soon or our + // timer will expire. Either way, we should ignore this message, because we + // have already committed to closing this renderer. + if (is_waiting_for_unload_ack_) + return; + + const int renderer_id = process()->id(); + ChildProcessSecurityPolicy* policy = + ChildProcessSecurityPolicy::GetInstance(); + // Without this check, an evil renderer can trick the browser into creating + // a navigation entry for a banned URL. If the user clicks the back button + // followed by the forward button (or clicks reload, or round-trips through + // session restore, etc), we'll think that the browser commanded the + // renderer to load the URL and grant the renderer the privileges to request + // the URL. To prevent this attack, we block the renderer from inserting + // banned URLs into the navigation controller in the first place. + FilterURL(policy, renderer_id, &validated_params.url); + FilterURL(policy, renderer_id, &validated_params.referrer); + for (std::vector<GURL>::iterator it(validated_params.redirects.begin()); + it != validated_params.redirects.end(); ++it) { + FilterURL(policy, renderer_id, &(*it)); + } + FilterURL(policy, renderer_id, &validated_params.searchable_form_url); + FilterURL(policy, renderer_id, &validated_params.password_form.origin); + FilterURL(policy, renderer_id, &validated_params.password_form.action); + + delegate_->DidNavigate(this, validated_params); +} + +void RenderViewHost::OnMsgUpdateState(int32 page_id, + const std::string& state) { + delegate_->UpdateState(this, page_id, state); +} + +void RenderViewHost::OnMsgUpdateTitle(int32 page_id, + const std::wstring& title) { + if (title.length() > chrome::kMaxTitleChars) { + NOTREACHED() << "Renderer sent too many characters in title."; + return; + } + delegate_->UpdateTitle(this, page_id, title); +} + +void RenderViewHost::OnMsgUpdateEncoding(const std::string& encoding_name) { + delegate_->UpdateEncoding(this, encoding_name); +} + +void RenderViewHost::OnMsgUpdateTargetURL(int32 page_id, + const GURL& url) { + delegate_->UpdateTargetURL(page_id, url); + + // Send a notification back to the renderer that we are ready to + // receive more target urls. + Send(new ViewMsg_UpdateTargetURL_ACK(routing_id())); +} + +void RenderViewHost::OnMsgThumbnail(const GURL& url, + const ThumbnailScore& score, + const SkBitmap& bitmap) { + delegate_->UpdateThumbnail(url, bitmap, score); +} + +void RenderViewHost::OnMsgScreenshot(const SkBitmap& bitmap) { + NotificationService::current()->Notify( + NotificationType::TAB_SNAPSHOT_TAKEN, + Source<RenderViewHost>(this), + Details<const SkBitmap>(&bitmap)); +} + +void RenderViewHost::OnUpdateInspectorSetting( + const std::string& key, const std::string& value) { + delegate_->UpdateInspectorSetting(key, value); +} + +void RenderViewHost::OnMsgClose() { + // If the renderer is telling us to close, it has already run the unload + // events, and we can take the fast path. + ClosePageIgnoringUnloadEvents(); +} + +void RenderViewHost::OnMsgRequestMove(const gfx::Rect& pos) { + delegate_->RequestMove(pos); + Send(new ViewMsg_Move_ACK(routing_id())); +} + +void RenderViewHost::OnMsgDidStartLoading() { + delegate_->DidStartLoading(); +} + +void RenderViewHost::OnMsgDidStopLoading() { + delegate_->DidStopLoading(); +} + +void RenderViewHost::OnMsgDidChangeLoadProgress(double load_progress) { + delegate_->DidChangeLoadProgress(load_progress); +} + +void RenderViewHost::OnMsgDocumentAvailableInMainFrame() { + delegate_->DocumentAvailableInMainFrame(this); +} + +void RenderViewHost::OnMsgDocumentOnLoadCompletedInMainFrame(int32 page_id) { + delegate_->DocumentOnLoadCompletedInMainFrame(this, page_id); +} + +void RenderViewHost::OnExecuteCodeFinished(int request_id, bool success) { + std::pair<int, bool> result_details(request_id, success); + NotificationService::current()->Notify( + NotificationType::TAB_CODE_EXECUTED, + NotificationService::AllSources(), + Details<std::pair<int, bool> >(&result_details)); +} + +void RenderViewHost::OnMsgContextMenu(const ContextMenuParams& params) { + RenderViewHostDelegate::View* view = delegate_->GetViewDelegate(); + if (!view) + return; + + // Validate the URLs in |params|. If the renderer can't request the URLs + // directly, don't show them in the context menu. + ContextMenuParams validated_params(params); + int renderer_id = process()->id(); + ChildProcessSecurityPolicy* policy = + ChildProcessSecurityPolicy::GetInstance(); + + // We don't validate |unfiltered_link_url| so that this field can be used + // when users want to copy the original link URL. + FilterURL(policy, renderer_id, &validated_params.link_url); + FilterURL(policy, renderer_id, &validated_params.src_url); + FilterURL(policy, renderer_id, &validated_params.page_url); + FilterURL(policy, renderer_id, &validated_params.frame_url); + + view->ShowContextMenu(validated_params); +} + +void RenderViewHost::OnMsgOpenURL(const GURL& url, + const GURL& referrer, + WindowOpenDisposition disposition) { + GURL validated_url(url); + FilterURL(ChildProcessSecurityPolicy::GetInstance(), + process()->id(), &validated_url); + + delegate_->RequestOpenURL(validated_url, referrer, disposition); +} + +void RenderViewHost::OnMsgDidContentsPreferredSizeChange( + const gfx::Size& new_size) { + RenderViewHostDelegate::View* view = delegate_->GetViewDelegate(); + if (!view) + return; + view->UpdatePreferredSize(new_size); +} + +void RenderViewHost::OnMsgDomOperationResponse( + const std::string& json_string, int automation_id) { + delegate_->DomOperationResponse(json_string, automation_id); + + // We also fire a notification for more loosely-coupled use cases. + DomOperationNotificationDetails details(json_string, automation_id); + NotificationService::current()->Notify( + NotificationType::DOM_OPERATION_RESPONSE, Source<RenderViewHost>(this), + Details<DomOperationNotificationDetails>(&details)); +} + +void RenderViewHost::OnMsgWebUISend( + const GURL& source_url, const std::string& message, + const std::string& content) { + if (!ChildProcessSecurityPolicy::GetInstance()-> + HasWebUIBindings(process()->id())) { + NOTREACHED() << "Blocked unauthorized use of WebUIBindings."; + return; + } + + scoped_ptr<Value> value; + if (!content.empty()) { + value.reset(base::JSONReader::Read(content, false)); + if (!value.get() || !value->IsType(Value::TYPE_LIST)) { + // The page sent us something that we didn't understand. + // This probably indicates a programming error. + NOTREACHED() << "Invalid JSON argument in OnMsgWebUISend."; + return; + } + } + + ViewHostMsg_DomMessage_Params params; + params.name = message; + if (value.get()) + params.arguments.Swap(static_cast<ListValue*>(value.get())); + params.source_url = source_url; + // WebUI doesn't use these values yet. + // TODO(aa): When WebUI is ported to ExtensionFunctionDispatcher, send real + // values here. + params.request_id = -1; + params.has_callback = false; + params.user_gesture = false; + delegate_->ProcessWebUIMessage(params); +} + +void RenderViewHost::OnMsgForwardMessageToExternalHost( + const std::string& message, const std::string& origin, + const std::string& target) { + delegate_->ProcessExternalHostMessage(message, origin, target); +} + +void RenderViewHost::DisassociateFromPopupCount() { + Send(new ViewMsg_DisassociateFromPopupCount(routing_id())); +} + +void RenderViewHost::AllowScriptToClose(bool script_can_close) { + Send(new ViewMsg_AllowScriptToClose(routing_id(), script_can_close)); +} + +void RenderViewHost::OnMsgSetTooltipText( + const std::wstring& tooltip_text, + WebTextDirection text_direction_hint) { + // First, add directionality marks around tooltip text if necessary. + // A naive solution would be to simply always wrap the text. However, on + // windows, Unicode directional embedding characters can't be displayed on + // systems that lack RTL fonts and are instead displayed as empty squares. + // + // To get around this we only wrap the string when we deem it necessary i.e. + // when the locale direction is different than the tooltip direction hint. + // + // Currently, we use element's directionality as the tooltip direction hint. + // An alternate solution would be to set the overall directionality based on + // trying to detect the directionality from the tooltip text rather than the + // element direction. One could argue that would be a preferable solution + // but we use the current approach to match Fx & IE's behavior. + std::wstring wrapped_tooltip_text = tooltip_text; + if (!tooltip_text.empty()) { + if (text_direction_hint == WebKit::WebTextDirectionLeftToRight) { + // Force the tooltip to have LTR directionality. + wrapped_tooltip_text = UTF16ToWide( + base::i18n::GetDisplayStringInLTRDirectionality( + WideToUTF16(wrapped_tooltip_text))); + } else if (text_direction_hint == WebKit::WebTextDirectionRightToLeft && + !base::i18n::IsRTL()) { + // Force the tooltip to have RTL directionality. + base::i18n::WrapStringWithRTLFormatting(&wrapped_tooltip_text); + } + } + if (view()) + view()->SetTooltipText(wrapped_tooltip_text); +} + +void RenderViewHost::OnMsgSelectionChanged(const std::string& text) { + if (view()) + view()->SelectionChanged(text); +} + +void RenderViewHost::OnMsgRunJavaScriptMessage( + const std::wstring& message, + const std::wstring& default_prompt, + const GURL& frame_url, + const int flags, + IPC::Message* reply_msg) { + // While a JS message dialog is showing, tabs in the same process shouldn't + // process input events. + process()->set_ignore_input_events(true); + StopHangMonitorTimeout(); + delegate_->RunJavaScriptMessage(message, default_prompt, frame_url, flags, + reply_msg, + &are_javascript_messages_suppressed_); +} + +void RenderViewHost::OnMsgRunBeforeUnloadConfirm(const GURL& frame_url, + const std::wstring& message, + IPC::Message* reply_msg) { + // While a JS before unload dialog is showing, tabs in the same process + // shouldn't process input events. + process()->set_ignore_input_events(true); + StopHangMonitorTimeout(); + delegate_->RunBeforeUnloadConfirm(message, reply_msg); +} + +void RenderViewHost::OnMsgShowModalHTMLDialog( + const GURL& url, int width, int height, const std::string& json_arguments, + IPC::Message* reply_msg) { + StopHangMonitorTimeout(); + delegate_->ShowModalHTMLDialog(url, width, height, json_arguments, reply_msg); +} + +void RenderViewHost::MediaPlayerActionAt(const gfx::Point& location, + const WebMediaPlayerAction& action) { + // TODO(ajwong): Which thread should run this? Does it matter? + Send(new ViewMsg_MediaPlayerActionAt(routing_id(), location, action)); +} + +void RenderViewHost::ContextMenuClosed( + const webkit_glue::CustomContextMenuContext& custom_context) { + Send(new ViewMsg_ContextMenuClosed(routing_id(), custom_context)); +} + +void RenderViewHost::PrintNodeUnderContextMenu() { + Send(new ViewMsg_PrintNodeUnderContextMenu(routing_id())); +} + +void RenderViewHost::PrintForPrintPreview() { + Send(new ViewMsg_PrintForPrintPreview(routing_id())); +} + +void RenderViewHost::OnMsgStartDragging( + const WebDropData& drop_data, + WebDragOperationsMask drag_operations_mask, + const SkBitmap& image, + const gfx::Point& image_offset) { + RenderViewHostDelegate::View* view = delegate_->GetViewDelegate(); + if (view) + view->StartDragging(drop_data, drag_operations_mask, image, image_offset); +} + +void RenderViewHost::OnUpdateDragCursor(WebDragOperation current_op) { + RenderViewHostDelegate::View* view = delegate_->GetViewDelegate(); + if (view) + view->UpdateDragCursor(current_op); +} + +void RenderViewHost::OnTakeFocus(bool reverse) { + RenderViewHostDelegate::View* view = delegate_->GetViewDelegate(); + if (view) + view->TakeFocus(reverse); +} + +void RenderViewHost::OnAddMessageToConsole(const std::wstring& message, + int32 line_no, + const std::wstring& source_id) { + std::wstring msg = StringPrintf(L"\"%ls,\" source: %ls (%d)", message.c_str(), + source_id.c_str(), line_no); + logging::LogMessage("CONSOLE", 0).stream() << msg; +} + +void RenderViewHost::OnForwardToDevToolsAgent(const IPC::Message& message) { + DevToolsManager::GetInstance()->ForwardToDevToolsAgent(this, message); +} + +void RenderViewHost::OnForwardToDevToolsClient(const IPC::Message& message) { + DevToolsManager::GetInstance()->ForwardToDevToolsClient(this, message); +} + +void RenderViewHost::OnActivateDevToolsWindow() { + DevToolsManager::GetInstance()->ActivateWindow(this); +} + +void RenderViewHost::OnCloseDevToolsWindow() { + DevToolsManager::GetInstance()->CloseWindow(this); +} + +void RenderViewHost::OnRequestDockDevToolsWindow() { + DevToolsManager::GetInstance()->RequestDockWindow(this); +} + +void RenderViewHost::OnRequestUndockDevToolsWindow() { + DevToolsManager::GetInstance()->RequestUndockWindow(this); +} + +void RenderViewHost::OnDevToolsRuntimePropertyChanged( + const std::string& name, + const std::string& value) { + DevToolsManager::GetInstance()-> + RuntimePropertyChanged(this, name, value); +} + +bool RenderViewHost::PreHandleKeyboardEvent( + const NativeWebKeyboardEvent& event, bool* is_keyboard_shortcut) { + RenderViewHostDelegate::View* view = delegate_->GetViewDelegate(); + return view && view->PreHandleKeyboardEvent(event, is_keyboard_shortcut); +} + +void RenderViewHost::UnhandledKeyboardEvent( + const NativeWebKeyboardEvent& event) { + RenderViewHostDelegate::View* view = delegate_->GetViewDelegate(); + if (view) + view->HandleKeyboardEvent(event); +} + +void RenderViewHost::OnUserGesture() { + delegate_->OnUserGesture(); +} + +void RenderViewHost::GetMalwareDOMDetails() { + Send(new ViewMsg_GetMalwareDOMDetails(routing_id())); +} + +void RenderViewHost::GetAllSavableResourceLinksForCurrentPage( + const GURL& page_url) { + Send(new ViewMsg_GetAllSavableResourceLinksForCurrentPage(routing_id(), + page_url)); +} + +void RenderViewHost::GetSerializedHtmlDataForCurrentPageWithLocalLinks( + const std::vector<GURL>& links, + const std::vector<FilePath>& local_paths, + const FilePath& local_directory_name) { + Send(new ViewMsg_GetSerializedHtmlDataForCurrentPageWithLocalLinks( + routing_id(), links, local_paths, local_directory_name)); +} + +void RenderViewHost::OnMsgShouldCloseACK(bool proceed) { + StopHangMonitorTimeout(); + // If this renderer navigated while the beforeunload request was in flight, we + // may have cleared this state in OnMsgNavigate, in which case we can ignore + // this message. + if (!is_waiting_for_beforeunload_ack_) + return; + + is_waiting_for_beforeunload_ack_ = false; + + RenderViewHostDelegate::RendererManagement* management_delegate = + delegate_->GetRendererManagementDelegate(); + if (management_delegate) { + management_delegate->ShouldClosePage( + unload_ack_is_for_cross_site_transition_, proceed); + } +} + +void RenderViewHost::WindowMoveOrResizeStarted() { + Send(new ViewMsg_MoveOrResizeStarted(routing_id())); +} + +void RenderViewHost::NotifyRendererUnresponsive() { + delegate_->RendererUnresponsive( + this, is_waiting_for_beforeunload_ack_ || is_waiting_for_unload_ack_); +} + +void RenderViewHost::NotifyRendererResponsive() { + delegate_->RendererResponsive(this); +} + +void RenderViewHost::OnMsgFocusedNodeChanged(bool is_editable_node) { + delegate_->FocusedNodeChanged(is_editable_node); +} + +void RenderViewHost::OnMsgFocus() { + RenderViewHostDelegate::View* view = delegate_->GetViewDelegate(); + if (view) + view->Activate(); +} + +void RenderViewHost::OnMsgBlur() { + RenderViewHostDelegate::View* view = delegate_->GetViewDelegate(); + if (view) + view->Deactivate(); +} + +void RenderViewHost::ForwardMouseEvent( + const WebKit::WebMouseEvent& mouse_event) { + + // We make a copy of the mouse event because + // RenderWidgetHost::ForwardMouseEvent will delete |mouse_event|. + WebKit::WebMouseEvent event_copy(mouse_event); + RenderWidgetHost::ForwardMouseEvent(event_copy); + + RenderViewHostDelegate::View* view = delegate_->GetViewDelegate(); + if (view) { + switch (event_copy.type) { + case WebInputEvent::MouseMove: + view->HandleMouseMove(); + break; + case WebInputEvent::MouseLeave: + view->HandleMouseLeave(); + break; + case WebInputEvent::MouseDown: + view->HandleMouseDown(); + break; + case WebInputEvent::MouseWheel: + if (ignore_input_events() && delegate_) + delegate_->OnIgnoredUIEvent(); + break; + case WebInputEvent::MouseUp: + view->HandleMouseUp(); + default: + // For now, we don't care about the rest. + break; + } + } +} + +void RenderViewHost::OnMouseActivate() { + RenderViewHostDelegate::View* view = delegate_->GetViewDelegate(); + if (view) + view->HandleMouseActivate(); +} + +void RenderViewHost::ForwardKeyboardEvent( + const NativeWebKeyboardEvent& key_event) { + if (ignore_input_events()) { + if (key_event.type == WebInputEvent::RawKeyDown && delegate_) + delegate_->OnIgnoredUIEvent(); + return; + } + RenderWidgetHost::ForwardKeyboardEvent(key_event); +} + +void RenderViewHost::ForwardEditCommand(const std::string& name, + const std::string& value) { + IPC::Message* message = new ViewMsg_ExecuteEditCommand(routing_id(), + name, + value); + Send(message); +} + +void RenderViewHost::ForwardEditCommandsForNextKeyEvent( + const EditCommands& edit_commands) { + IPC::Message* message = new ViewMsg_SetEditCommandsForNextKeyEvent( + routing_id(), edit_commands); + Send(message); +} + +void RenderViewHost::ForwardMessageFromExternalHost(const std::string& message, + const std::string& origin, + const std::string& target) { + Send(new ViewMsg_HandleMessageFromExternalHost(routing_id(), message, origin, + target)); +} + +void RenderViewHost::OnExtensionRequest( + const ViewHostMsg_DomMessage_Params& params) { + if (!ChildProcessSecurityPolicy::GetInstance()-> + HasExtensionBindings(process()->id())) { + // This can happen if someone uses window.open() to open an extension URL + // from a non-extension context. + BlockExtensionRequest(params.request_id); + return; + } + + delegate_->ProcessWebUIMessage(params); +} + +void RenderViewHost::SendExtensionResponse(int request_id, bool success, + const std::string& response, + const std::string& error) { + Send(new ViewMsg_ExtensionResponse(routing_id(), request_id, success, + response, error)); +} + +void RenderViewHost::BlockExtensionRequest(int request_id) { + SendExtensionResponse(request_id, false, "", + "Access to extension API denied."); +} + +void RenderViewHost::UpdateBrowserWindowId(int window_id) { + Send(new ViewMsg_UpdateBrowserWindowId(routing_id(), window_id)); +} + +void RenderViewHost::PerformCustomContextMenuAction( + const webkit_glue::CustomContextMenuContext& custom_context, + unsigned action) { + Send(new ViewMsg_CustomContextMenuAction(routing_id(), + custom_context, + action)); +} + +void RenderViewHost::SendContentSettings(const GURL& url, + const ContentSettings& settings) { + Send(new ViewMsg_SetContentSettingsForCurrentURL(url, settings)); +} + +void RenderViewHost::EnablePreferredSizeChangedMode(int flags) { + Send(new ViewMsg_EnablePreferredSizeChangedMode(routing_id(), flags)); +} + +#if defined(OS_MACOSX) +void RenderViewHost::DidSelectPopupMenuItem(int selected_index) { + Send(new ViewMsg_SelectPopupMenuItem(routing_id(), selected_index)); +} + +void RenderViewHost::DidCancelPopupMenu() { + Send(new ViewMsg_SelectPopupMenuItem(routing_id(), -1)); +} +#endif + +void RenderViewHost::SearchBoxChange(const string16& value, + bool verbatim, + int selection_start, + int selection_end) { + Send(new ViewMsg_SearchBoxChange( + routing_id(), value, verbatim, selection_start, selection_end)); +} + +void RenderViewHost::SearchBoxSubmit(const string16& value, + bool verbatim) { + Send(new ViewMsg_SearchBoxSubmit(routing_id(), value, verbatim)); +} + +void RenderViewHost::SearchBoxCancel() { + Send(new ViewMsg_SearchBoxCancel(routing_id())); +} + +void RenderViewHost::SearchBoxResize(const gfx::Rect& search_box_bounds) { + Send(new ViewMsg_SearchBoxResize(routing_id(), search_box_bounds)); +} + +void RenderViewHost::DetermineIfPageSupportsInstant(const string16& value, + bool verbatim, + int selection_start, + int selection_end) { + Send(new ViewMsg_DetermineIfPageSupportsInstant( + routing_id(), value, verbatim, selection_start, selection_end)); +} + +void RenderViewHost::FilterURL(ChildProcessSecurityPolicy* policy, + int renderer_id, + GURL* url) { + if (!url->is_valid()) + return; // We don't need to block invalid URLs. + + if (url->SchemeIs(chrome::kAboutScheme)) { + // The renderer treats all URLs in the about: scheme as being about:blank. + // Canonicalize about: URLs to about:blank. + *url = GURL(chrome::kAboutBlankURL); + } + + if (!policy->CanRequestURL(renderer_id, *url)) { + // If this renderer is not permitted to request this URL, we invalidate the + // URL. This prevents us from storing the blocked URL and becoming confused + // later. + VLOG(1) << "Blocked URL " << url->spec(); + *url = GURL(); + } +} + +void RenderViewHost::JavaScriptStressTestControl(int cmd, int param) { + Send(new ViewMsg_JavaScriptStressTestControl(routing_id(), cmd, param)); +} + +void RenderViewHost::OnExtensionPostMessage( + int port_id, const std::string& message) { + if (process()->profile()->GetExtensionMessageService()) { + process()->profile()->GetExtensionMessageService()-> + PostMessageFromRenderer(port_id, message); + } +} + +void RenderViewHost::OnAccessibilityNotifications( + const std::vector<ViewHostMsg_AccessibilityNotification_Params>& params) { + if (view()) + view()->OnAccessibilityNotifications(params); + + if (params.size() > 0) { + for (unsigned i = 0; i < params.size(); i++) { + const ViewHostMsg_AccessibilityNotification_Params& param = params[i]; + + if (param.notification_type == + ViewHostMsg_AccessibilityNotification_Params:: + NOTIFICATION_TYPE_LOAD_COMPLETE) { + // TODO(ctguil): Remove when mac processes OnAccessibilityNotifications. + if (view()) + view()->UpdateAccessibilityTree(param.acc_obj); + + if (save_accessibility_tree_for_testing_) + accessibility_tree_ = param.acc_obj; + } + } + + NotificationService::current()->Notify( + NotificationType::RENDER_VIEW_HOST_ACCESSIBILITY_TREE_UPDATED, + Source<RenderViewHost>(this), + NotificationService::NoDetails()); + } + + AccessibilityNotificationsAck(); +} + +void RenderViewHost::OnCSSInserted() { + delegate_->DidInsertCSS(); +} + +void RenderViewHost::OnContentBlocked(ContentSettingsType type, + const std::string& resource_identifier) { + RenderViewHostDelegate::ContentSettings* content_settings_delegate = + delegate_->GetContentSettingsDelegate(); + if (content_settings_delegate) + content_settings_delegate->OnContentBlocked(type, resource_identifier); +} + +void RenderViewHost::OnAppCacheAccessed(const GURL& manifest_url, + bool blocked_by_policy) { + RenderViewHostDelegate::ContentSettings* content_settings_delegate = + delegate_->GetContentSettingsDelegate(); + if (content_settings_delegate) + content_settings_delegate->OnAppCacheAccessed(manifest_url, + blocked_by_policy); +} + +void RenderViewHost::OnWebDatabaseAccessed(const GURL& url, + const string16& name, + const string16& display_name, + unsigned long estimated_size, + bool blocked_by_policy) { + RenderViewHostDelegate::ContentSettings* content_settings_delegate = + delegate_->GetContentSettingsDelegate(); + if (content_settings_delegate) + content_settings_delegate->OnWebDatabaseAccessed( + url, name, display_name, estimated_size, blocked_by_policy); +} + +void RenderViewHost::OnUpdateZoomLimits(int minimum_percent, + int maximum_percent, + bool remember) { + delegate_->UpdateZoomLimits(minimum_percent, maximum_percent, remember); +} + +void RenderViewHost::OnScriptEvalResponse(int id, const ListValue& result) { + Value* result_value; + result.Get(0, &result_value); + std::pair<int, Value*> details(id, result_value); + NotificationService::current()->Notify( + NotificationType::EXECUTE_JAVASCRIPT_RESULT, + Source<RenderViewHost>(this), + Details<std::pair<int, Value*> >(&details)); +} + +#if defined(OS_MACOSX) +void RenderViewHost::OnMsgShowPopup( + const ViewHostMsg_ShowPopup_Params& params) { + RenderViewHostDelegate::View* view = delegate_->GetViewDelegate(); + if (view) { + view->ShowPopupMenu(params.bounds, + params.item_height, + params.item_font_size, + params.selected_item, + params.popup_items, + params.right_aligned); + } +} +#endif + +void RenderViewHost::OnCommandStateChanged(int command, + bool is_enabled, + int checked_state) { + if (command != RENDER_VIEW_COMMAND_TOGGLE_SPELL_CHECK) { + LOG(DFATAL) << "Unknown command " << command; + return; + } + + if (checked_state != RENDER_VIEW_COMMAND_CHECKED_STATE_UNCHECKED && + checked_state != RENDER_VIEW_COMMAND_CHECKED_STATE_CHECKED && + checked_state != RENDER_VIEW_COMMAND_CHECKED_STATE_MIXED) { + LOG(DFATAL) << "Invalid checked state " << checked_state; + return; + } + + CommandState state; + state.is_enabled = is_enabled; + state.checked_state = + static_cast<RenderViewCommandCheckedState>(checked_state); + command_states_[static_cast<RenderViewCommand>(command)] = state; +} diff --git a/content/browser/renderer_host/render_view_host.h b/content/browser/renderer_host/render_view_host.h new file mode 100644 index 0000000..cc3bfac --- /dev/null +++ b/content/browser/renderer_host/render_view_host.h @@ -0,0 +1,741 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CONTENT_BROWSER_RENDERER_HOST_RENDER_VIEW_HOST_H_ +#define CONTENT_BROWSER_RENDERER_HOST_RENDER_VIEW_HOST_H_ +#pragma once + +#include <string> +#include <vector> + +#include "base/process_util.h" +#include "base/scoped_ptr.h" +#include "chrome/browser/ui/find_bar/find_bar_controller.h" +#include "chrome/common/content_settings_types.h" +#include "chrome/common/page_zoom.h" +#include "chrome/common/render_view_commands.h" +#include "chrome/common/translate_errors.h" +#include "chrome/common/view_types.h" +#include "chrome/common/window_container_type.h" +#include "content/browser/renderer_host/render_widget_host.h" +#include "net/base/load_states.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebConsoleMessage.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebDragOperation.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebPopupType.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebTextDirection.h" +#include "webkit/glue/webaccessibility.h" +#include "webkit/glue/window_open_disposition.h" + +class ChildProcessSecurityPolicy; +class FilePath; +class GURL; +class ListValue; +class RenderViewHostDelegate; +class SessionStorageNamespace; +class SiteInstance; +class SkBitmap; +class ViewMsg_Navigate; +struct ContentSettings; +struct ContextMenuParams; +struct MediaPlayerAction; +struct ThumbnailScore; +struct ViewHostMsg_AccessibilityNotification_Params; +struct ViewHostMsg_CreateWindow_Params; +struct ViewHostMsg_DomMessage_Params; +struct ViewHostMsg_ShowPopup_Params; +struct ViewMsg_Navigate_Params; +struct WebApplicationInfo; +struct WebDropData; +struct WebPreferences; +struct UserMetricsAction; + +namespace gfx { +class Point; +} // namespace gfx + +namespace webkit_glue { +struct CustomContextMenuContext; +struct WebAccessibility; +} // namespace webkit_glue + +namespace WebKit { +struct WebMediaPlayerAction; +} // namespace WebKit + +class URLRequestContextGetter; + +// +// RenderViewHost +// +// A RenderViewHost is responsible for creating and talking to a RenderView +// object in a child process. It exposes a high level API to users, for things +// like loading pages, adjusting the display and other browser functionality, +// which it translates into IPC messages sent over the IPC channel with the +// RenderView. It responds to all IPC messages sent by that RenderView and +// cracks them, calling a delegate object back with higher level types where +// possible. +// +// The intent of this class is to provide a view-agnostic communication +// conduit with a renderer. This is so we can build HTML views not only as +// TabContents (see TabContents for an example) but also as views, etc. +// +// The exact API of this object needs to be more thoroughly designed. Right +// now it mimics what TabContents exposed, which is a fairly large API and may +// contain things that are not relevant to a common subset of views. See also +// the comment in render_view_host_delegate.h about the size and scope of the +// delegate API. +// +// Right now, the concept of page navigation (both top level and frame) exists +// in the TabContents still, so if you instantiate one of these elsewhere, you +// will not be able to traverse pages back and forward. We need to determine +// if we want to bring that and other functionality down into this object so +// it can be shared by others. +// +class RenderViewHost : public RenderWidgetHost { + public: + // Returns the RenderViewHost given its ID and the ID of its render process. + // Returns NULL if the IDs do not correspond to a live RenderViewHost. + static RenderViewHost* FromID(int render_process_id, int render_view_id); + + // routing_id could be a valid route id, or it could be MSG_ROUTING_NONE, in + // which case RenderWidgetHost will create a new one. + // + // The session storage namespace parameter allows multiple render views and + // tab contentses to share the same session storage (part of the WebStorage + // spec) space. This is useful when restoring tabs, but most callers should + // pass in NULL which will cause a new SessionStorageNamespace to be created. + RenderViewHost(SiteInstance* instance, + RenderViewHostDelegate* delegate, + int routing_id, + SessionStorageNamespace* session_storage_namespace); + virtual ~RenderViewHost(); + + SiteInstance* site_instance() const { return instance_; } + RenderViewHostDelegate* delegate() const { return delegate_; } + void set_delegate(RenderViewHostDelegate* d) { delegate_ = d; } + + // Set up the RenderView child process. Virtual because it is overridden by + // TestRenderViewHost. If the |frame_name| parameter is non-empty, it is used + // as the name of the new top-level frame. + virtual bool CreateRenderView(const string16& frame_name); + + // Returns true if the RenderView is active and has not crashed. Virtual + // because it is overridden by TestRenderViewHost. + virtual bool IsRenderViewLive() const; + + base::TerminationStatus render_view_termination_status() const { + return render_view_termination_status_; + } + + // Send the renderer process the current preferences supplied by the + // RenderViewHostDelegate. + void SyncRendererPrefs(); + + // Sends the given navigation message. Use this rather than sending it + // yourself since this does the internal bookkeeping described below. This + // function takes ownership of the provided message pointer. + // + // If a cross-site request is in progress, we may be suspended while waiting + // for the onbeforeunload handler, so this function might buffer the message + // rather than sending it. + void Navigate(const ViewMsg_Navigate_Params& message); + + // Load the specified URL, this is a shortcut for Navigate(). + void NavigateToURL(const GURL& url); + + // Returns whether navigation messages are currently suspended for this + // RenderViewHost. Only true during a cross-site navigation, while waiting + // for the onbeforeunload handler. + bool are_navigations_suspended() const { return navigations_suspended_; } + + // Suspends (or unsuspends) any navigation messages from being sent from this + // RenderViewHost. This is called when a pending RenderViewHost is created + // for a cross-site navigation, because we must suspend any navigations until + // we hear back from the old renderer's onbeforeunload handler. Note that it + // is important that only one navigation event happen after calling this + // method with |suspend| equal to true. If |suspend| is false and there is + // a suspended_nav_message_, this will send the message. This function + // should only be called to toggle the state; callers should check + // are_navigations_suspended() first. + void SetNavigationsSuspended(bool suspend); + + // Causes the renderer to invoke the onbeforeunload event handler. The + // result will be returned via ViewMsg_ShouldClose. See also ClosePage which + // will fire the PageUnload event. + // + // Set bool for_cross_site_transition when this close is just for the current + // RenderView in the case of a cross-site transition. False means we're + // closing the entire tab. + void FirePageBeforeUnload(bool for_cross_site_transition); + + // Causes the renderer to close the current page, including running its + // onunload event handler. A ClosePage_ACK message will be sent to the + // ResourceDispatcherHost when it is finished. + // + // Please see ViewMsg_ClosePage in resource_messages_internal.h for a + // description of the parameters. + void ClosePage(bool for_cross_site_transition, + int new_render_process_host_id, + int new_request_id); + + // Close the page ignoring whether it has unload events registers. + // This is called after the beforeunload and unload events have fired + // and the user has agreed to continue with closing the page. + void ClosePageIgnoringUnloadEvents(); + + // Sets whether this RenderViewHost has an outstanding cross-site request, + // for which another renderer will need to run an onunload event handler. + // This is called before the first navigation event for this RenderViewHost, + // and again after the corresponding OnCrossSiteResponse. + void SetHasPendingCrossSiteRequest(bool has_pending_request, int request_id); + + // Returns the request_id for the pending cross-site request. + // This is just needed in case the unload of the current page + // hangs, in which case we need to swap to the pending RenderViewHost. + int GetPendingRequestId(); + + struct CommandState { + bool is_enabled; + RenderViewCommandCheckedState checked_state; + }; + CommandState GetStateForCommand(RenderViewCommand command) const; + + // Stops the current load. + void Stop(); + + // Reloads the current frame. + void ReloadFrame(); + + // Asks the renderer to "render" printed pages and initiate printing on our + // behalf. + bool PrintPages(); + + // Asks the renderer to render pages for print preview. + bool PrintPreview(); + + // Notify renderer of success/failure of print job. + void PrintingDone(int document_cookie, bool success); + + // Start looking for a string within the content of the page, with the + // specified options. + void StartFinding(int request_id, + const string16& search_string, + bool forward, + bool match_case, + bool find_next); + + // Cancel a pending find operation. + void StopFinding(FindBarController::SelectionAction selection_action); + + // Increment, decrement, or reset the zoom level of a page. + void Zoom(PageZoom::Function function); + + // Change the zoom level of a page to a specific value. + void SetZoomLevel(double zoom_level); + + // Change the encoding of the page. + void SetPageEncoding(const std::string& encoding); + + // Reset any override encoding on the page and change back to default. + void ResetPageEncodingToDefault(); + + // Change the alternate error page URL. An empty GURL disables the use of + // alternate error pages. + void SetAlternateErrorPageURL(const GURL& url); + + // D&d drop target messages that get sent to WebKit. + void DragTargetDragEnter(const WebDropData& drop_data, + const gfx::Point& client_pt, + const gfx::Point& screen_pt, + WebKit::WebDragOperationsMask operations_allowed); + void DragTargetDragOver(const gfx::Point& client_pt, + const gfx::Point& screen_pt, + WebKit::WebDragOperationsMask operations_allowed); + void DragTargetDragLeave(); + void DragTargetDrop(const gfx::Point& client_pt, + const gfx::Point& screen_pt); + + // Tell the RenderView to reserve a range of page ids of the given size. + void ReservePageIDRange(int size); + + // Runs some javascript within the context of a frame in the page. + void ExecuteJavascriptInWebFrame(const string16& frame_xpath, + const string16& jscript); + + // Runs some javascript within the context of a frame in the page. The result + // is sent back via the notification EXECUTE_JAVASCRIPT_RESULT. + int ExecuteJavascriptInWebFrameNotifyResult(const string16& frame_xpath, + const string16& jscript); + + // Insert some css into a frame in the page. |id| is optional, and specifies + // the element id given when inserting/replacing the style element. + void InsertCSSInWebFrame(const std::wstring& frame_xpath, + const std::string& css, + const std::string& id); + + // Logs a message to the console of a frame in the page. + void AddMessageToConsole(const string16& frame_xpath, + const string16& message, + const WebKit::WebConsoleMessage::Level&); + + // Edit operations. + void Undo(); + void Redo(); + void Cut(); + void Copy(); + void CopyToFindPboard(); + void Paste(); + void ToggleSpellCheck(); + void Delete(); + void SelectAll(); + void ToggleSpellPanel(bool is_currently_visible); + + // Downloads an image notifying the FavIcon delegate appropriately. The + // returned integer uniquely identifies the download for the lifetime of the + // browser. + int DownloadFavIcon(const GURL& url, int image_size); + + // Requests application info for the specified page. This is an asynchronous + // request. The delegate is notified by way of OnDidGetApplicationInfo when + // the data is available. + void GetApplicationInfo(int32 page_id); + + // Captures a thumbnail representation of the page. + void CaptureThumbnail(); + + // Captures a snapshot of the page. + void CaptureSnapshot(); + + // Notifies the RenderView that the JavaScript message that was shown was + // closed by the user. + void JavaScriptMessageBoxClosed(IPC::Message* reply_msg, + bool success, + const std::wstring& prompt); + + // Notifies the RenderView that the modal html dialog has been closed. + void ModalHTMLDialogClosed(IPC::Message* reply_msg, + const std::string& json_retval); + + // Send an action to the media player element located at |location|. + void MediaPlayerActionAt(const gfx::Point& location, + const WebKit::WebMediaPlayerAction& action); + + // Notifies the renderer that the context menu has closed. + void ContextMenuClosed( + const webkit_glue::CustomContextMenuContext& custom_context); + + // Prints the node that's under the context menu. + void PrintNodeUnderContextMenu(); + + // Triggers printing of the preview PDF. + void PrintForPrintPreview(); + + // Copies the image at the specified point. + void CopyImageAt(int x, int y); + + // Notifies the renderer that a a drag operation that it started has ended, + // either in a drop or by being cancelled. + void DragSourceEndedAt( + int client_x, int client_y, int screen_x, int screen_y, + WebKit::WebDragOperation operation); + + // Notifies the renderer that a drag and drop operation is in progress, with + // droppable items positioned over the renderer's view. + void DragSourceMovedTo( + int client_x, int client_y, int screen_x, int screen_y); + + // Notifies the renderer that we're done with the drag and drop operation. + // This allows the renderer to reset some state. + void DragSourceSystemDragEnded(); + + // Tell the render view to enable a set of javascript bindings. The argument + // should be a combination of values from BindingsPolicy. + void AllowBindings(int binding_flags); + + // Returns a bitwise OR of bindings types that have been enabled for this + // RenderView. See BindingsPolicy for details. + int enabled_bindings() const { return enabled_bindings_; } + + // See variable comment. + bool is_extension_process() const { return is_extension_process_; } + void set_is_extension_process(bool is_extension_process) { + is_extension_process_ = is_extension_process; + } + + // Sets a property with the given name and value on the Web UI binding object. + // Must call AllowWebUIBindings() on this renderer first. + void SetWebUIProperty(const std::string& name, const std::string& value); + + // Tells the renderer view to focus the first (last if reverse is true) node. + void SetInitialFocus(bool reverse); + + // Clears the node that is currently focused (if any). + void ClearFocusedNode(); + + // Tells the renderer view to scroll to the focused node. + void ScrollFocusedEditableNodeIntoView(); + + // Update render view specific (WebKit) preferences. + void UpdateWebPreferences(const WebPreferences& prefs); + + // Request the Renderer to ask the default plugin to start installation of + // missing plugin. Called by PluginInstallerInfoBarDelegate. + void InstallMissingPlugin(); + + // Load all blocked plugins in the RenderView. + void LoadBlockedPlugins(); + + // Get all script and frame urls from all frames in the current document. + // Called when a malware interstitial page is shown. + void GetMalwareDOMDetails(); + + // Get all savable resource links from current webpage, include main + // frame and sub-frame. + void GetAllSavableResourceLinksForCurrentPage(const GURL& page_url); + + // Get html data by serializing all frames of current page with lists + // which contain all resource links that have local copy. + // The parameter links contain original URLs of all saved links. + // The parameter local_paths contain corresponding local file paths of + // all saved links, which matched with vector:links one by one. + // The parameter local_directory_name is relative path of directory which + // contain all saved auxiliary files included all sub frames and resouces. + void GetSerializedHtmlDataForCurrentPageWithLocalLinks( + const std::vector<GURL>& links, + const std::vector<FilePath>& local_paths, + const FilePath& local_directory_name); + + // Notifies the Listener that one or more files have been chosen by the user + // from an Open File dialog for the form. + void FilesSelectedInChooser(const std::vector<FilePath>& files); + + // Notifies the RenderViewHost that its load state changed. + void LoadStateChanged(const GURL& url, net::LoadState load_state, + uint64 upload_position, uint64 upload_size); + + bool SuddenTerminationAllowed() const; + void set_sudden_termination_allowed(bool enabled) { + sudden_termination_allowed_ = enabled; + } + + // Forward a message from external host to chrome renderer. + void ForwardMessageFromExternalHost(const std::string& message, + const std::string& origin, + const std::string& target); + + // Message the renderer that we should be counted as a new document and not + // as a popup. + void DisassociateFromPopupCount(); + + // Tells the renderer whether it should allow window.close. This is initially + // set to false when creating a renderer-initiated window via window.open. + void AllowScriptToClose(bool visible); + + // Notifies the Renderer that a move or resize of its containing window has + // started (this is used to hide the autocomplete popups if any). + void WindowMoveOrResizeStarted(); + + // RenderWidgetHost public overrides. + virtual void Shutdown(); + virtual bool IsRenderView() const; + virtual bool OnMessageReceived(const IPC::Message& msg); + virtual void GotFocus(); + virtual void LostCapture(); + virtual void ForwardMouseEvent(const WebKit::WebMouseEvent& mouse_event); + virtual void OnMouseActivate(); + virtual void ForwardKeyboardEvent(const NativeWebKeyboardEvent& key_event); + virtual void ForwardEditCommand(const std::string& name, + const std::string& value); + virtual void ForwardEditCommandsForNextKeyEvent( + const EditCommands& edit_commands); + + // Creates a new RenderView with the given route id. + void CreateNewWindow(int route_id, + const ViewHostMsg_CreateWindow_Params& params); + + // Creates a new RenderWidget with the given route id. |popup_type| indicates + // if this widget is a popup and what kind of popup it is (select, autofill). + void CreateNewWidget(int route_id, WebKit::WebPopupType popup_type); + + // Creates a full screen RenderWidget. + void CreateNewFullscreenWidget(int route_id); + + // Sends the response to an extension api call. + void SendExtensionResponse(int request_id, bool success, + const std::string& response, + const std::string& error); + + // Sends a response to an extension api call that it was blocked for lack of + // permission. + void BlockExtensionRequest(int request_id); + + // Tells the renderer which browser window it is being attached to. + void UpdateBrowserWindowId(int window_id); + + // Tells the render view that a custom context action has been selected. + void PerformCustomContextMenuAction( + const webkit_glue::CustomContextMenuContext& custom_context, + unsigned action); + + // Informs renderer of updated content settings. + void SendContentSettings(const GURL& url, + const ContentSettings& settings); + + // Tells the renderer to notify us when the page contents preferred size + // changed. |flags| is a combination of + // |ViewHostMsg_EnablePreferredSizeChangedMode_Flags| values, which is defined + // in render_messages.h. + void EnablePreferredSizeChangedMode(int flags); + +#if defined(OS_MACOSX) + // Select popup menu related methods (for external popup menus). + void DidSelectPopupMenuItem(int selected_index); + void DidCancelPopupMenu(); +#endif + + // SearchBox notifications. + void SearchBoxChange(const string16& value, + bool verbatim, + int selection_start, + int selection_end); + void SearchBoxSubmit(const string16& value, + bool verbatim); + void SearchBoxCancel(); + void SearchBoxResize(const gfx::Rect& search_box_bounds); + void DetermineIfPageSupportsInstant(const string16& value, + bool verbatim, + int selection_start, + int selection_end); + + // Send a notification to the V8 JavaScript engine to change its parameters + // while performing stress testing. |cmd| is one of the values defined by + // |ViewHostMsg_JavaScriptStressTestControl_Commands|, which is defined + // in render_messages.h. + void JavaScriptStressTestControl(int cmd, int param); + +#if defined(UNIT_TEST) + // These functions shouldn't be necessary outside of testing. + + void set_save_accessibility_tree_for_testing(bool save) { + save_accessibility_tree_for_testing_ = save; + } + + const webkit_glue::WebAccessibility& accessibility_tree() { + return accessibility_tree_; + } + + bool is_waiting_for_unload_ack() { return is_waiting_for_unload_ack_; } +#endif + + // Checks that the given renderer can request |url|, if not it sets it to an + // empty url. + static void FilterURL(ChildProcessSecurityPolicy* policy, + int renderer_id, + GURL* url); + + protected: + // RenderWidgetHost protected overrides. + virtual bool PreHandleKeyboardEvent(const NativeWebKeyboardEvent& event, + bool* is_keyboard_shortcut); + virtual void UnhandledKeyboardEvent(const NativeWebKeyboardEvent& event); + virtual void OnUserGesture(); + virtual void NotifyRendererUnresponsive(); + virtual void NotifyRendererResponsive(); + virtual void OnMsgFocusedNodeChanged(bool is_editable_node); + virtual void OnMsgFocus(); + virtual void OnMsgBlur(); + + // IPC message handlers. + void OnMsgShowView(int route_id, + WindowOpenDisposition disposition, + const gfx::Rect& initial_pos, + bool user_gesture); + void OnMsgShowWidget(int route_id, const gfx::Rect& initial_pos); + void OnMsgShowFullscreenWidget(int route_id); + void OnMsgRunModal(IPC::Message* reply_msg); + void OnMsgRenderViewReady(); + void OnMsgRenderViewGone(int status, int error_code); + void OnMsgNavigate(const IPC::Message& msg); + void OnMsgUpdateState(int32 page_id, + const std::string& state); + void OnMsgUpdateTitle(int32 page_id, const std::wstring& title); + void OnMsgUpdateEncoding(const std::string& encoding); + void OnMsgUpdateTargetURL(int32 page_id, const GURL& url); + void OnMsgThumbnail(const GURL& url, + const ThumbnailScore& score, + const SkBitmap& bitmap); + void OnMsgScreenshot(const SkBitmap& bitmap); + void OnMsgClose(); + void OnMsgRequestMove(const gfx::Rect& pos); + void OnMsgDidStartLoading(); + void OnMsgDidStopLoading(); + void OnMsgDidChangeLoadProgress(double load_progress); + void OnMsgDocumentAvailableInMainFrame(); + void OnMsgDocumentOnLoadCompletedInMainFrame(int32 page_id); + void OnExecuteCodeFinished(int request_id, bool success); + void OnMsgUpdateFavIconURL(int32 page_id, const GURL& icon_url); + void OnMsgDidDownloadFavIcon(int id, + const GURL& image_url, + bool errored, + const SkBitmap& image_data); + void OnMsgContextMenu(const ContextMenuParams& params); + void OnMsgOpenURL(const GURL& url, const GURL& referrer, + WindowOpenDisposition disposition); + void OnMsgDidContentsPreferredSizeChange(const gfx::Size& new_size); + void OnMsgDomOperationResponse(const std::string& json_string, + int automation_id); + void OnMsgWebUISend(const GURL& source_url, + const std::string& message, + const std::string& content); + void OnMsgForwardMessageToExternalHost(const std::string& message, + const std::string& origin, + const std::string& target); + void OnMsgSetTooltipText(const std::wstring& tooltip_text, + WebKit::WebTextDirection text_direction_hint); + void OnMsgSelectionChanged(const std::string& text); + void OnMsgPasteFromSelectionClipboard(); + void OnMsgRunJavaScriptMessage(const std::wstring& message, + const std::wstring& default_prompt, + const GURL& frame_url, + const int flags, + IPC::Message* reply_msg); + void OnMsgRunBeforeUnloadConfirm(const GURL& frame_url, + const std::wstring& message, + IPC::Message* reply_msg); + void OnMsgShowModalHTMLDialog(const GURL& url, int width, int height, + const std::string& json_arguments, + IPC::Message* reply_msg); + void OnMsgStartDragging(const WebDropData& drop_data, + WebKit::WebDragOperationsMask operations_allowed, + const SkBitmap& image, + const gfx::Point& image_offset); + void OnUpdateDragCursor(WebKit::WebDragOperation drag_operation); + void OnTakeFocus(bool reverse); + void OnAddMessageToConsole(const std::wstring& message, + int32 line_no, + const std::wstring& source_id); + void OnUpdateInspectorSetting(const std::string& key, + const std::string& value); + void OnForwardToDevToolsAgent(const IPC::Message& message); + void OnForwardToDevToolsClient(const IPC::Message& message); + void OnActivateDevToolsWindow(); + void OnCloseDevToolsWindow(); + void OnRequestDockDevToolsWindow(); + void OnRequestUndockDevToolsWindow(); + void OnDevToolsRuntimePropertyChanged(const std::string& name, + const std::string& value); + void OnMsgShouldCloseACK(bool proceed); + + void OnExtensionRequest(const ViewHostMsg_DomMessage_Params& params); + void OnExtensionPostMessage(int port_id, const std::string& message); + void OnAccessibilityNotifications( + const std::vector<ViewHostMsg_AccessibilityNotification_Params>& params); + void OnCSSInserted(); + void OnContentBlocked(ContentSettingsType type, + const std::string& resource_identifier); + void OnAppCacheAccessed(const GURL& manifest_url, bool blocked_by_policy); + void OnWebDatabaseAccessed(const GURL& url, + const string16& name, + const string16& display_name, + unsigned long estimated_size, + bool blocked_by_policy); + void OnUpdateZoomLimits(int minimum_percent, + int maximum_percent, + bool remember); + void OnScriptEvalResponse(int id, const ListValue& result); + void OnCommandStateChanged(int command, + bool is_enabled, + int checked_state); + +#if defined(OS_MACOSX) + void OnMsgShowPopup(const ViewHostMsg_ShowPopup_Params& params); +#endif + + private: + friend class TestRenderViewHost; + + // The SiteInstance associated with this RenderViewHost. All pages drawn + // in this RenderViewHost are part of this SiteInstance. Should not change + // over time. + scoped_refptr<SiteInstance> instance_; + + // Our delegate, which wants to know about changes in the RenderView. + RenderViewHostDelegate* delegate_; + + // true if we are currently waiting for a response for drag context + // information. + bool waiting_for_drag_context_response_; + + // A bitwise OR of bindings types that have been enabled for this RenderView. + // See BindingsPolicy for details. + int enabled_bindings_; + + // The request_id for the pending cross-site request. Set to -1 if + // there is a pending request, but we have not yet started the unload + // for the current page. Set to the request_id value of the pending + // request once we have gotten the some data for the pending page + // and thus started the unload process. + int pending_request_id_; + + // Whether we should buffer outgoing Navigate messages rather than sending + // them. This will be true when a RenderViewHost is created for a cross-site + // request, until we hear back from the onbeforeunload handler of the old + // RenderViewHost. + bool navigations_suspended_; + + // We only buffer a suspended navigation message while we a pending RVH for a + // TabContents. There will only ever be one suspended navigation, because + // TabContents will destroy the pending RVH and create a new one if a second + // navigation occurs. + scoped_ptr<ViewMsg_Navigate> suspended_nav_message_; + + // If we were asked to RunModal, then this will hold the reply_msg that we + // must return to the renderer to unblock it. + IPC::Message* run_modal_reply_msg_; + + // Set to true when there is a pending ViewMsg_ShouldClose message. This + // ensures we don't spam the renderer with multiple beforeunload requests. + // When either this value or is_waiting_for_unload_ack_ is true, the value of + // unload_ack_is_for_cross_site_transition_ indicates whether this is for a + // cross-site transition or a tab close attempt. + bool is_waiting_for_beforeunload_ack_; + + // Set to true when there is a pending ViewMsg_Close message. Also see + // is_waiting_for_beforeunload_ack_, unload_ack_is_for_cross_site_transition_. + bool is_waiting_for_unload_ack_; + + // Valid only when is_waiting_for_beforeunload_ack_ or + // is_waiting_for_unload_ack_ is true. This tells us if the unload request + // is for closing the entire tab ( = false), or only this RenderViewHost in + // the case of a cross-site transition ( = true). + bool unload_ack_is_for_cross_site_transition_; + + bool are_javascript_messages_suppressed_; + + // True if the render view can be shut down suddenly. + bool sudden_termination_allowed_; + + // The session storage namespace to be used by the associated render view. + scoped_refptr<SessionStorageNamespace> session_storage_namespace_; + + // Whether this render view will get extension api bindings. This controls + // what process type we use. + bool is_extension_process_; + + // Whether the accessibility tree should be saved, for unit testing. + bool save_accessibility_tree_for_testing_; + + // The most recently received accessibility tree - for unit testing only. + webkit_glue::WebAccessibility accessibility_tree_; + + // The termination status of the last render view that terminated. + base::TerminationStatus render_view_termination_status_; + + // The enabled/disabled states of various commands. + std::map<RenderViewCommand, CommandState> command_states_; + + DISALLOW_COPY_AND_ASSIGN(RenderViewHost); +}; + +#endif // CONTENT_BROWSER_RENDERER_HOST_RENDER_VIEW_HOST_H_ diff --git a/content/browser/renderer_host/render_view_host_delegate.cc b/content/browser/renderer_host/render_view_host_delegate.cc new file mode 100644 index 0000000..f169205 --- /dev/null +++ b/content/browser/renderer_host/render_view_host_delegate.cc @@ -0,0 +1,74 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "content/browser/renderer_host/render_view_host_delegate.h" + +#include "base/singleton.h" +#include "chrome/common/render_messages.h" +#include "chrome/common/render_messages_params.h" +#include "chrome/common/renderer_preferences.h" +#include "googleurl/src/gurl.h" +#include "ui/gfx/rect.h" +#include "webkit/glue/webpreferences.h" + +#if defined(TOOLKIT_USES_GTK) +#include "chrome/browser/ui/gtk/gtk_util.h" +#endif + +RenderViewHostDelegate::View* RenderViewHostDelegate::GetViewDelegate() { + return NULL; +} + +RenderViewHostDelegate::RendererManagement* +RenderViewHostDelegate::GetRendererManagementDelegate() { + return NULL; +} + +RenderViewHostDelegate::ContentSettings* +RenderViewHostDelegate::GetContentSettingsDelegate() { + return NULL; +} + +RenderViewHostDelegate::BookmarkDrag* +RenderViewHostDelegate::GetBookmarkDragDelegate() { + return NULL; +} + +RenderViewHostDelegate::SSL* +RenderViewHostDelegate::GetSSLDelegate() { + return NULL; +} + +AutomationResourceRoutingDelegate* +RenderViewHostDelegate::GetAutomationResourceRoutingDelegate() { + return NULL; +} + +bool RenderViewHostDelegate::OnMessageReceived(const IPC::Message& message) { + return false; +} + +const GURL& RenderViewHostDelegate::GetURL() const { + return GURL::EmptyGURL(); +} + +TabContents* RenderViewHostDelegate::GetAsTabContents() { + return NULL; +} + +BackgroundContents* RenderViewHostDelegate::GetAsBackgroundContents() { + return NULL; +} + +GURL RenderViewHostDelegate::GetAlternateErrorPageURL() const { + return GURL(); +} + +WebPreferences RenderViewHostDelegate::GetWebkitPrefs() { + return WebPreferences(); +} + +bool RenderViewHostDelegate::IsExternalTabContainer() const { + return false; +} diff --git a/content/browser/renderer_host/render_view_host_delegate.h b/content/browser/renderer_host/render_view_host_delegate.h new file mode 100644 index 0000000..e4c5f46 --- /dev/null +++ b/content/browser/renderer_host/render_view_host_delegate.h @@ -0,0 +1,584 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CONTENT_BROWSER_RENDERER_HOST_RENDER_VIEW_HOST_DELEGATE_H_ +#define CONTENT_BROWSER_RENDERER_HOST_RENDER_VIEW_HOST_DELEGATE_H_ +#pragma once + +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "base/process_util.h" +#include "base/ref_counted.h" +#include "base/string16.h" +#include "chrome/common/content_settings_types.h" +#include "chrome/common/dom_storage_common.h" +#include "chrome/common/translate_errors.h" +#include "chrome/common/view_types.h" +#include "chrome/common/window_container_type.h" +#include "ipc/ipc_channel.h" +#include "net/base/load_states.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebDragOperation.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebPopupType.h" +#include "webkit/glue/window_open_disposition.h" + + +class AutomationResourceRoutingDelegate; +class BackgroundContents; +struct BookmarkNodeData; +class BookmarkNode; +struct ContextMenuParams; +class FilePath; +class GURL; +class ListValue; +struct NativeWebKeyboardEvent; +class NavigationEntry; +class Profile; +struct RendererPreferences; +class RenderProcessHost; +class RenderViewHost; +class ResourceRedirectDetails; +class ResourceRequestDetails; +class SkBitmap; +class SSLClientAuthHandler; +class SSLAddCertHandler; +class TabContents; +struct ThumbnailScore; +struct ViewHostMsg_CreateWindow_Params; +struct ViewHostMsg_DomMessage_Params; +struct ViewHostMsg_FrameNavigate_Params; +struct WebApplicationInfo; +struct WebDropData; +struct WebMenuItem; +class WebKeyboardEvent; +struct WebPreferences; + +namespace base { +class WaitableEvent; +} + +namespace gfx { +class Point; +class Rect; +class Size; +} + +namespace IPC { +class Message; +} + +namespace net { +class CookieList; +class CookieOptions; +} + +namespace webkit_glue { +struct FormData; +class FormField; +struct PasswordForm; +} + +// +// RenderViewHostDelegate +// +// An interface implemented by an object interested in knowing about the state +// of the RenderViewHost. +// +// This interface currently encompasses every type of message that was +// previously being sent by TabContents itself. Some of these notifications +// may not be relevant to all users of RenderViewHost and we should consider +// exposing a more generic Send function on RenderViewHost and a response +// listener here to serve that need. +// +class RenderViewHostDelegate : public IPC::Channel::Listener { + public: + // View ---------------------------------------------------------------------- + // Functions that can be routed directly to a view-specific class. + + class View { + public: + // The page is trying to open a new page (e.g. a popup window). The window + // should be created associated with the given route, but it should not be + // shown yet. That should happen in response to ShowCreatedWindow. + // |params.window_container_type| describes the type of RenderViewHost + // container that is requested -- in particular, the window.open call may + // have specified 'background' and 'persistent' in the feature string. + // + // The passed |params.frame_name| parameter is the name parameter that was + // passed to window.open(), and will be empty if none was passed. + // + // Note: this is not called "CreateWindow" because that will clash with + // the Windows function which is actually a #define. + // + // NOTE: this takes ownership of @modal_dialog_event + virtual void CreateNewWindow( + int route_id, + const ViewHostMsg_CreateWindow_Params& params) = 0; + + // The page is trying to open a new widget (e.g. a select popup). The + // widget should be created associated with the given route, but it should + // not be shown yet. That should happen in response to ShowCreatedWidget. + // |popup_type| indicates if the widget is a popup and what kind of popup it + // is (select, autofill...). + virtual void CreateNewWidget(int route_id, + WebKit::WebPopupType popup_type) = 0; + + // Creates a full screen RenderWidget. Similar to above. + virtual void CreateNewFullscreenWidget(int route_id) = 0; + + // Show a previously created page with the specified disposition and bounds. + // The window is identified by the route_id passed to CreateNewWindow. + // + // Note: this is not called "ShowWindow" because that will clash with + // the Windows function which is actually a #define. + virtual void ShowCreatedWindow(int route_id, + WindowOpenDisposition disposition, + const gfx::Rect& initial_pos, + bool user_gesture) = 0; + + // Show the newly created widget with the specified bounds. + // The widget is identified by the route_id passed to CreateNewWidget. + virtual void ShowCreatedWidget(int route_id, + const gfx::Rect& initial_pos) = 0; + + // Show the newly created full screen widget. Similar to above. + virtual void ShowCreatedFullscreenWidget(int route_id) = 0; + + // A context menu should be shown, to be built using the context information + // provided in the supplied params. + virtual void ShowContextMenu(const ContextMenuParams& params) = 0; + + // Shows a popup menu with the specified items. + // This method should call RenderViewHost::DidSelectPopupMenuItemAt() or + // RenderViewHost::DidCancelPopupMenu() ased on the user action. + virtual void ShowPopupMenu(const gfx::Rect& bounds, + int item_height, + double item_font_size, + int selected_item, + const std::vector<WebMenuItem>& items, + bool right_aligned) = 0; + + // The user started dragging content of the specified type within the + // RenderView. Contextual information about the dragged content is supplied + // by WebDropData. + virtual void StartDragging(const WebDropData& drop_data, + WebKit::WebDragOperationsMask allowed_ops, + const SkBitmap& image, + const gfx::Point& image_offset) = 0; + + // The page wants to update the mouse cursor during a drag & drop operation. + // |operation| describes the current operation (none, move, copy, link.) + virtual void UpdateDragCursor(WebKit::WebDragOperation operation) = 0; + + // Notification that view for this delegate got the focus. + virtual void GotFocus() = 0; + + // Callback to inform the browser that the page is returning the focus to + // the browser's chrome. If reverse is true, it means the focus was + // retrieved by doing a Shift-Tab. + virtual void TakeFocus(bool reverse) = 0; + + // Notification that the view has lost capture. + virtual void LostCapture() = 0; + + // The page wants the hosting window to activate/deactivate itself (it + // called the JavaScript window.focus()/blur() method). + virtual void Activate() = 0; + virtual void Deactivate() = 0; + + // Callback to give the browser a chance to handle the specified keyboard + // event before sending it to the renderer. + // Returns true if the |event| was handled. Otherwise, if the |event| would + // be handled in HandleKeyboardEvent() method as a normal keyboard shortcut, + // |*is_keyboard_shortcut| should be set to true. + virtual bool PreHandleKeyboardEvent(const NativeWebKeyboardEvent& event, + bool* is_keyboard_shortcut) = 0; + + // Callback to inform the browser that the renderer did not process the + // specified events. This gives an opportunity to the browser to process the + // event (used for keyboard shortcuts). + virtual void HandleKeyboardEvent(const NativeWebKeyboardEvent& event) = 0; + + // Notifications about mouse events in this view. This is useful for + // implementing global 'on hover' features external to the view. + virtual void HandleMouseMove() = 0; + virtual void HandleMouseDown() = 0; + virtual void HandleMouseLeave() = 0; + virtual void HandleMouseUp() = 0; + virtual void HandleMouseActivate() = 0; + + // The contents' preferred size changed. + virtual void UpdatePreferredSize(const gfx::Size& pref_size) = 0; + + protected: + virtual ~View() {} + }; + + // RendererManagerment ------------------------------------------------------- + // Functions for managing switching of Renderers. For TabContents, this is + // implemented by the RenderViewHostManager + + class RendererManagement { + public: + // Notification whether we should close the page, after an explicit call to + // AttemptToClosePage. This is called before a cross-site request or before + // a tab/window is closed (as indicated by the first parameter) to allow the + // appropriate renderer to approve or deny the request. |proceed| indicates + // whether the user chose to proceed. + virtual void ShouldClosePage(bool for_cross_site_transition, + bool proceed) = 0; + + // Called by ResourceDispatcherHost when a response for a pending cross-site + // request is received. The ResourceDispatcherHost will pause the response + // until the onunload handler of the previous renderer is run. + virtual void OnCrossSiteResponse(int new_render_process_host_id, + int new_request_id) = 0; + + // Called the ResourceDispatcherHost's associate CrossSiteRequestHandler + // when a cross-site navigation has been canceled. + virtual void OnCrossSiteNavigationCanceled() = 0; + + protected: + virtual ~RendererManagement() {} + }; + + // ContentSettings------------------------------------------------------------ + // Interface for content settings related events. + + class ContentSettings { + public: + // Called when content in the current page was blocked due to the user's + // content settings. + virtual void OnContentBlocked(ContentSettingsType type, + const std::string& resource_identifier) = 0; + + // Called when cookies for the given URL were read either from within the + // current page or while loading it. |blocked_by_policy| should be true, if + // reading cookies was blocked due to the user's content settings. In that + // case, this function should invoke OnContentBlocked. + virtual void OnCookiesRead( + const GURL& url, + const net::CookieList& cookie_list, + bool blocked_by_policy) = 0; + + // Called when a specific cookie in the current page was changed. + // |blocked_by_policy| should be true, if the cookie was blocked due to the + // user's content settings. In that case, this function should invoke + // OnContentBlocked. + virtual void OnCookieChanged(const GURL& url, + const std::string& cookie_line, + const net::CookieOptions& options, + bool blocked_by_policy) = 0; + + // Called when a specific indexed db factory in the current page was + // accessed. If access was blocked due to the user's content settings, + // |blocked_by_policy| should be true, and this function should invoke + // OnContentBlocked. + virtual void OnIndexedDBAccessed(const GURL& url, + const string16& description, + bool blocked_by_policy) = 0; + + // Called when a specific local storage area in the current page was + // accessed. If access was blocked due to the user's content settings, + // |blocked_by_policy| should be true, and this function should invoke + // OnContentBlocked. + virtual void OnLocalStorageAccessed(const GURL& url, + DOMStorageType storage_type, + bool blocked_by_policy) = 0; + + // Called when a specific Web database in the current page was accessed. If + // access was blocked due to the user's content settings, + // |blocked_by_policy| should eb true, and this function should invoke + // OnContentBlocked. + virtual void OnWebDatabaseAccessed(const GURL& url, + const string16& name, + const string16& display_name, + unsigned long estimated_size, + bool blocked_by_policy) = 0; + + // Called when a specific appcache in the current page was accessed. If + // access was blocked due to the user's content settings, + // |blocked_by_policy| should eb true, and this function should invoke + // OnContentBlocked. + virtual void OnAppCacheAccessed(const GURL& manifest_url, + bool blocked_by_policy) = 0; + + // Called when geolocation permission was set in a frame on the current + // page. + virtual void OnGeolocationPermissionSet(const GURL& requesting_frame, + bool allowed) = 0; + + protected: + virtual ~ContentSettings() {} + }; + + // BookmarkDrag -------------------------------------------------------------- + // Interface for forwarding bookmark drag and drop to extenstions. + + class BookmarkDrag { + public: + virtual void OnDragEnter(const BookmarkNodeData& data) = 0; + virtual void OnDragOver(const BookmarkNodeData& data) = 0; + virtual void OnDragLeave(const BookmarkNodeData& data) = 0; + virtual void OnDrop(const BookmarkNodeData& data) = 0; + + protected: + virtual ~BookmarkDrag() {} + }; + + // SSL ----------------------------------------------------------------------- + // Interface for UI and other RenderViewHost-specific interactions with SSL. + + class SSL { + public: + // Displays a dialog to select client certificates from |request_info|, + // returning them to |handler|. + virtual void ShowClientCertificateRequestDialog( + scoped_refptr<SSLClientAuthHandler> handler) = 0; + + // Called when |handler| encounters an error in verifying a + // received client certificate. Note that, because CAs often will + // not send us intermediate certificates, the verification we can + // do is minimal: we verify the certificate is parseable, that we + // have the corresponding private key, and that the certificate + // has not expired. + virtual void OnVerifyClientCertificateError( + scoped_refptr<SSLAddCertHandler> handler, int error_code) = 0; + + // Called when |handler| requests the user's confirmation in adding a + // client certificate. + virtual void AskToAddClientCertificate( + scoped_refptr<SSLAddCertHandler> handler) = 0; + + // Called when |handler| successfully adds a client certificate. + virtual void OnAddClientCertificateSuccess( + scoped_refptr<SSLAddCertHandler> handler) = 0; + + // Called when |handler| encounters an error adding a client certificate. + virtual void OnAddClientCertificateError( + scoped_refptr<SSLAddCertHandler> handler, int error_code) = 0; + + // Called when |handler| has completed, so the delegate may release any + // state accumulated. + virtual void OnAddClientCertificateFinished( + scoped_refptr<SSLAddCertHandler> handler) = 0; + + protected: + virtual ~SSL() {} + }; + + // --------------------------------------------------------------------------- + + // Returns the current delegate associated with a feature. May return NULL if + // there is no corresponding delegate. + virtual View* GetViewDelegate(); + virtual RendererManagement* GetRendererManagementDelegate(); + virtual ContentSettings* GetContentSettingsDelegate(); + + virtual BookmarkDrag* GetBookmarkDragDelegate(); + virtual SSL* GetSSLDelegate(); + + // Return the delegate for registering RenderViewHosts for automation resource + // routing. + virtual AutomationResourceRoutingDelegate* + GetAutomationResourceRoutingDelegate(); + + // IPC::Channel::Listener implementation. + // This is used to give the delegate a chance to filter IPC messages. + virtual bool OnMessageReceived(const IPC::Message& message); + + // Gets the URL that is currently being displayed, if there is one. + virtual const GURL& GetURL() const; + + // Return this object cast to a TabContents, if it is one. If the object is + // not a TabContents, returns NULL. DEPRECATED: Be sure to include brettw and + // jam as reviewers before you use this method. + virtual TabContents* GetAsTabContents(); + + // Return this object cast to a BackgroundContents, if it is one. If the + // object is not a BackgroundContents, returns NULL. + virtual BackgroundContents* GetAsBackgroundContents(); + + // Return id number of browser window which this object is attached to. If no + // browser window is attached to, just return -1. + virtual int GetBrowserWindowID() const = 0; + + // Return type of RenderView which is attached with this object. + virtual ViewType::Type GetRenderViewType() const = 0; + + // The RenderView is being constructed (message sent to the renderer process + // to construct a RenderView). Now is a good time to send other setup events + // to the RenderView. This precedes any other commands to the RenderView. + virtual void RenderViewCreated(RenderViewHost* render_view_host) {} + + // The RenderView has been constructed. + virtual void RenderViewReady(RenderViewHost* render_view_host) {} + + // The RenderView died somehow (crashed or was killed by the user). + virtual void RenderViewGone(RenderViewHost* render_view_host, + base::TerminationStatus status, + int error_code) {} + + // The RenderView is going to be deleted. This is called when each + // RenderView is going to be destroyed + virtual void RenderViewDeleted(RenderViewHost* render_view_host) {} + + // The RenderView was navigated to a different page. + virtual void DidNavigate(RenderViewHost* render_view_host, + const ViewHostMsg_FrameNavigate_Params& params) {} + + // The state for the page changed and should be updated. + virtual void UpdateState(RenderViewHost* render_view_host, + int32 page_id, + const std::string& state) {} + + // The page's title was changed and should be updated. + virtual void UpdateTitle(RenderViewHost* render_view_host, + int32 page_id, + const std::wstring& title) {} + + // The page's encoding was changed and should be updated. + virtual void UpdateEncoding(RenderViewHost* render_view_host, + const std::string& encoding) {} + + // The destination URL has changed should be updated + virtual void UpdateTargetURL(int32 page_id, const GURL& url) {} + + // The thumbnail representation of the page changed and should be updated. + virtual void UpdateThumbnail(const GURL& url, + const SkBitmap& bitmap, + const ThumbnailScore& score) {} + + // Inspector setting was changed and should be persisted. + virtual void UpdateInspectorSetting(const std::string& key, + const std::string& value) = 0; + + virtual void ClearInspectorSettings() = 0; + + // The page is trying to close the RenderView's representation in the client. + virtual void Close(RenderViewHost* render_view_host) {} + + // The page is trying to move the RenderView's representation in the client. + virtual void RequestMove(const gfx::Rect& new_bounds) {} + + // The RenderView began loading a new page. This corresponds to WebKit's + // notion of the throbber starting. + virtual void DidStartLoading() {} + + // The RenderView stopped loading a page. This corresponds to WebKit's + // notion of the throbber stopping. + virtual void DidStopLoading() {} + + // The RenderView made progress loading a page's top frame. + // |progress| is a value between 0 (nothing loaded) to 1.0 (top frame + // entirely loaded). + virtual void DidChangeLoadProgress(double progress) {} + + // The RenderView's main frame document element is ready. This happens when + // the document has finished parsing. + virtual void DocumentAvailableInMainFrame(RenderViewHost* render_view_host) {} + + // The onload handler in the RenderView's main frame has completed. + virtual void DocumentOnLoadCompletedInMainFrame( + RenderViewHost* render_view_host, + int32 page_id) {} + + // The page wants to open a URL with the specified disposition. + virtual void RequestOpenURL(const GURL& url, + const GURL& referrer, + WindowOpenDisposition disposition) {} + + // A DOM automation operation completed. The result of the operation is + // expressed in a json string. + virtual void DomOperationResponse(const std::string& json_string, + int automation_id) {} + + // A message was sent from HTML-based UI. + // By default we ignore such messages. + virtual void ProcessWebUIMessage( + const ViewHostMsg_DomMessage_Params& params) {} + + // A message for external host. By default we ignore such messages. + // |receiver| can be a receiving script and |message| is any + // arbitrary string that makes sense to the receiver. + virtual void ProcessExternalHostMessage(const std::string& message, + const std::string& origin, + const std::string& target) {} + + // A javascript message, confirmation or prompt should be shown. + virtual void RunJavaScriptMessage(const std::wstring& message, + const std::wstring& default_prompt, + const GURL& frame_url, + const int flags, + IPC::Message* reply_msg, + bool* did_suppress_message) {} + + virtual void RunBeforeUnloadConfirm(const std::wstring& message, + IPC::Message* reply_msg) {} + + virtual void ShowModalHTMLDialog(const GURL& url, int width, int height, + const std::string& json_arguments, + IPC::Message* reply_msg) {} + + // |url| is assigned to a server that can provide alternate error pages. If + // the returned URL is empty, the default error page built into WebKit will + // be used. + virtual GURL GetAlternateErrorPageURL() const; + + // Return a dummy RendererPreferences object that will be used by the renderer + // associated with the owning RenderViewHost. + virtual RendererPreferences GetRendererPrefs(Profile* profile) const = 0; + + // Returns a WebPreferences object that will be used by the renderer + // associated with the owning render view host. + virtual WebPreferences GetWebkitPrefs(); + + // Notification the user has made a gesture while focus was on the + // page. This is used to avoid uninitiated user downloads (aka carpet + // bombing), see DownloadRequestLimiter for details. + virtual void OnUserGesture() {} + + // Notification from the renderer host that blocked UI event occurred. + // This happens when there are tab-modal dialogs. In this case, the + // notification is needed to let us draw attention to the dialog (i.e. + // refocus on the modal dialog, flash title etc). + virtual void OnIgnoredUIEvent() {} + + // Notification that the renderer has become unresponsive. The + // delegate can use this notification to show a warning to the user. + virtual void RendererUnresponsive(RenderViewHost* render_view_host, + bool is_during_unload) {} + + // Notification that a previously unresponsive renderer has become + // responsive again. The delegate can use this notification to end the + // warning shown to the user. + virtual void RendererResponsive(RenderViewHost* render_view_host) {} + + // Notification that the RenderViewHost's load state changed. + virtual void LoadStateChanged(const GURL& url, net::LoadState load_state, + uint64 upload_position, uint64 upload_size) {} + + // Returns true if this view is used to host an external tab container. + virtual bool IsExternalTabContainer() const; + + // The RenderView has inserted one css file into page. + virtual void DidInsertCSS() {} + + // A different node in the page got focused. + virtual void FocusedNodeChanged(bool is_editable_node) {} + + // Updates the minimum and maximum zoom percentages. + virtual void UpdateZoomLimits(int minimum_percent, + int maximum_percent, + bool remember) {} + + // Notification that a worker process has crashed. + void WorkerCrashed() {} + + protected: + virtual ~RenderViewHostDelegate() {} +}; + +#endif // CONTENT_BROWSER_RENDERER_HOST_RENDER_VIEW_HOST_DELEGATE_H_ diff --git a/content/browser/renderer_host/render_view_host_factory.cc b/content/browser/renderer_host/render_view_host_factory.cc new file mode 100644 index 0000000..6fc01cf --- /dev/null +++ b/content/browser/renderer_host/render_view_host_factory.cc @@ -0,0 +1,37 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "content/browser/renderer_host/render_view_host_factory.h" + +#include "base/logging.h" +#include "content/browser/renderer_host/render_view_host.h" + +// static +RenderViewHostFactory* RenderViewHostFactory::factory_ = NULL; + +// static +RenderViewHost* RenderViewHostFactory::Create( + SiteInstance* instance, + RenderViewHostDelegate* delegate, + int routing_id, + SessionStorageNamespace* session_storage_namespace) { + if (factory_) { + return factory_->CreateRenderViewHost(instance, delegate, routing_id, + session_storage_namespace); + } + return new RenderViewHost(instance, delegate, routing_id, + session_storage_namespace); +} + +// static +void RenderViewHostFactory::RegisterFactory(RenderViewHostFactory* factory) { + DCHECK(!factory_) << "Can't register two factories at once."; + factory_ = factory; +} + +// static +void RenderViewHostFactory::UnregisterFactory() { + DCHECK(factory_) << "No factory to unregister."; + factory_ = NULL; +} diff --git a/content/browser/renderer_host/render_view_host_factory.h b/content/browser/renderer_host/render_view_host_factory.h new file mode 100644 index 0000000..5d0f58d --- /dev/null +++ b/content/browser/renderer_host/render_view_host_factory.h @@ -0,0 +1,68 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CONTENT_BROWSER_RENDERER_HOST_RENDER_VIEW_HOST_FACTORY_H_ +#define CONTENT_BROWSER_RENDERER_HOST_RENDER_VIEW_HOST_FACTORY_H_ +#pragma once + +#include "base/basictypes.h" + +class RenderViewHost; +class RenderViewHostDelegate; +class SessionStorageNamespace; +class SiteInstance; + +namespace base { +class WaitableEvent; +} // namespace base + +// A factory for creating RenderViewHosts. There is a global factory function +// that can be installed for the purposes of testing to provide a specialized +// RenderViewHost class. +class RenderViewHostFactory { + public: + // Creates a RenderViewHost using the currently registered factory, or the + // default one if no factory is registered. Ownership of the returned + // pointer will be passed to the caller. + static RenderViewHost* Create(SiteInstance* instance, + RenderViewHostDelegate* delegate, + int routing_id, + SessionStorageNamespace* session_storage); + + // Returns true if there is currently a globally-registered factory. + static bool has_factory() { + return !!factory_; + } + + protected: + RenderViewHostFactory() {} + virtual ~RenderViewHostFactory() {} + + // You can derive from this class and specify an implementation for this + // function to create a different kind of RenderViewHost for testing. + virtual RenderViewHost* CreateRenderViewHost( + SiteInstance* instance, + RenderViewHostDelegate* delegate, + int routing_id, + SessionStorageNamespace* session_storage_namespace) = 0; + + // Registers your factory to be called when new RenderViewHosts are created. + // We have only one global factory, so there must be no factory registered + // before the call. This class does NOT take ownership of the pointer. + static void RegisterFactory(RenderViewHostFactory* factory); + + // Unregister the previously registered factory. With no factory registered, + // the default RenderViewHosts will be created. + static void UnregisterFactory(); + + private: + // The current globally registered factory. This is NULL when we should + // create the default RenderViewHosts. + static RenderViewHostFactory* factory_; + + DISALLOW_COPY_AND_ASSIGN(RenderViewHostFactory); +}; + + +#endif // CONTENT_BROWSER_RENDERER_HOST_RENDER_VIEW_HOST_FACTORY_H_ diff --git a/content/browser/renderer_host/render_view_host_notification_task.h b/content/browser/renderer_host/render_view_host_notification_task.h new file mode 100644 index 0000000..f9051c4 --- /dev/null +++ b/content/browser/renderer_host/render_view_host_notification_task.h @@ -0,0 +1,337 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file defines utility functions for sending notifications (calling +// methods that return void and do not have out params) to the RenderViewHost +// or one of its delegate interfaces. The notifications are dispatched +// asynchronously, and only if the specified RenderViewHost still exists. + +#ifndef CONTENT_BROWSER_RENDERER_HOST_RENDER_VIEW_HOST_NOTIFICATION_TASK_H_ +#define CONTENT_BROWSER_RENDERER_HOST_RENDER_VIEW_HOST_NOTIFICATION_TASK_H_ +#pragma once + +#include "base/callback.h" +#include "base/task.h" +#include "chrome/browser/browser_thread.h" +#include "content/browser/renderer_host/render_view_host.h" +#include "content/browser/renderer_host/render_view_host_delegate.h" + +// ---------------------------------------------------------------------------- + +namespace internal { + +// The simplest Mapper, used when proxying calls to a RenderViewHost. +class RenderViewHostIdentityMapper { + public: + typedef RenderViewHost MappedType; + static MappedType* Map(RenderViewHost* rvh) { return rvh; } +}; + +template <typename Method, typename Params, + typename Mapper = RenderViewHostIdentityMapper> +class RenderViewHostNotificationTask : public Task { + public: + RenderViewHostNotificationTask(int render_process_id, + int render_view_id, + Method method, + const Params& params) + : render_process_id_(render_process_id), + render_view_id_(render_view_id), + unbound_method_(method, params) { + } + + virtual void Run() { + RenderViewHost* rvh = RenderViewHost::FromID(render_process_id_, + render_view_id_); + typename Mapper::MappedType* obj = Mapper::Map(rvh); + if (obj) + unbound_method_.Run(obj); + } + + private: + int render_process_id_; + int render_view_id_; + UnboundMethod<typename Mapper::MappedType, Method, Params> unbound_method_; + + DISALLOW_COPY_AND_ASSIGN(RenderViewHostNotificationTask); +}; + +// For proxying calls to RenderViewHost + +template <typename Method, typename Params> +inline void CallRenderViewHostHelper(int render_process_id, int render_view_id, + Method method, const Params& params) { + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + new RenderViewHostNotificationTask<Method, Params>(render_process_id, + render_view_id, + method, + params)); +} + +// For proxying calls to RenderViewHostDelegate::ContentSettings + +class RenderViewHostToContentSettingsDelegate { + public: + typedef RenderViewHostDelegate::ContentSettings MappedType; + static MappedType* Map(RenderViewHost* rvh) { + return rvh ? rvh->delegate()->GetContentSettingsDelegate() : NULL; + } +}; + +template <typename Method, typename Params> +inline void CallRenderViewHostContentSettingsDelegateHelper( + int render_process_id, + int render_view_id, + Method method, + const Params& params) { + + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + new RenderViewHostNotificationTask< + Method, Params, RenderViewHostToContentSettingsDelegate>( + render_process_id, + render_view_id, + method, + params)); +} + +// For proxying calls to RenderViewHostDelegate::RendererManagement + +class RenderViewHostToRendererManagementDelegate { + public: + typedef RenderViewHostDelegate::RendererManagement MappedType; + static MappedType* Map(RenderViewHost* rvh) { + return rvh ? rvh->delegate()->GetRendererManagementDelegate() : NULL; + } +}; + +template <typename Method, typename Params> +inline void CallRenderViewHostRendererManagementDelegateHelper( + int render_process_id, + int render_view_id, + Method method, + const Params& params) { + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + new RenderViewHostNotificationTask< + Method, Params, RenderViewHostToRendererManagementDelegate>( + render_process_id, + render_view_id, + method, + params)); +} + +// For proxying calls to RenderViewHostDelegate::SSL + +class RenderViewHostToSSLDelegate { + public: + typedef RenderViewHostDelegate::SSL MappedType; + static MappedType* Map(RenderViewHost* rvh) { + return rvh ? rvh->delegate()->GetSSLDelegate() : NULL; + } +}; + +template <typename Method, typename Params> +inline void CallRenderViewHostSSLDelegateHelper( + int render_process_id, + int render_view_id, + Method method, + const Params& params) { + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + new RenderViewHostNotificationTask< + Method, Params, RenderViewHostToSSLDelegate>( + render_process_id, + render_view_id, + method, + params)); +} + +} // namespace internal + +// ---------------------------------------------------------------------------- +// Proxy calls to the specified RenderViewHost. + +template <typename Method> +inline void CallRenderViewHost(int render_process_id, + int render_view_id, + Method method) { + internal::CallRenderViewHostHelper(render_process_id, + render_view_id, + method, + MakeTuple()); +} + +template <typename Method, typename A> +inline void CallRenderViewHost(int render_process_id, + int render_view_id, + Method method, + const A& a) { + internal::CallRenderViewHostHelper(render_process_id, + render_view_id, + method, + MakeTuple(a)); +} + +template <typename Method, typename A, typename B> +inline void CallRenderViewHost(int render_process_id, + int render_view_id, + Method method, + const A& a, + const B& b) { + internal::CallRenderViewHostHelper(render_process_id, + render_view_id, + method, + MakeTuple(a, b)); +} + +// ---------------------------------------------------------------------------- +// Proxy calls to the specified RenderViewHost's ContentSettings delegate. + +template <typename Method> +inline void CallRenderViewHostContentSettingsDelegate(int render_process_id, + int render_view_id, + Method method) { + internal::CallRenderViewHostContentSettingsDelegateHelper(render_process_id, + render_view_id, + method, + MakeTuple()); +} + +template <typename Method, typename A> +inline void CallRenderViewHostContentSettingsDelegate(int render_process_id, + int render_view_id, + Method method, + const A& a) { + internal::CallRenderViewHostContentSettingsDelegateHelper(render_process_id, + render_view_id, + method, + MakeTuple(a)); + } + +template <typename Method, typename A, typename B> +inline void CallRenderViewHostContentSettingsDelegate(int render_process_id, + int render_view_id, + Method method, + const A& a, + const B& b) { + internal::CallRenderViewHostContentSettingsDelegateHelper(render_process_id, + render_view_id, + method, + MakeTuple(a, b)); +} + +template <typename Method, typename A, typename B, typename C> +inline void CallRenderViewHostContentSettingsDelegate(int render_process_id, + int render_view_id, + Method method, + const A& a, + const B& b, + const C& c) { + internal::CallRenderViewHostContentSettingsDelegateHelper(render_process_id, + render_view_id, + method, + MakeTuple(a, b, c)); +} + +template <typename Method, typename A, typename B, typename C, typename D> +inline void CallRenderViewHostContentSettingsDelegate(int render_process_id, + int render_view_id, + Method method, + const A& a, + const B& b, + const C& c, + const D& d) { + internal::CallRenderViewHostContentSettingsDelegateHelper( + render_process_id, + render_view_id, + method, + MakeTuple(a, b, c, d)); +} + +template <typename Method, + typename A, typename B, typename C, typename D, typename E> +inline void CallRenderViewHostContentSettingsDelegate(int render_process_id, + int render_view_id, + Method method, + const A& a, + const B& b, + const C& c, + const D& d, + const E& e) { + internal::CallRenderViewHostContentSettingsDelegateHelper( + render_process_id, render_view_id, method, MakeTuple(a, b, c, d, e)); +} + +// ---------------------------------------------------------------------------- +// Proxy calls to the specified RenderViewHost's RendererManagement delegate. + +template <typename Method> +inline void CallRenderViewHostRendererManagementDelegate(int render_process_id, + int render_view_id, + Method method) { + internal::CallRenderViewHostRendererManagementDelegateHelper( + render_process_id, + render_view_id, + method, + MakeTuple()); +} + +template <typename Method, typename A> +inline void CallRenderViewHostRendererManagementDelegate(int render_process_id, + int render_view_id, + Method method, + const A& a) { + internal::CallRenderViewHostRendererManagementDelegateHelper( + render_process_id, + render_view_id, + method, + MakeTuple(a)); +} + +template <typename Method, typename A, typename B> +inline void CallRenderViewHostRendererManagementDelegate(int render_process_id, + int render_view_id, + Method method, + const A& a, + const B& b) { + internal::CallRenderViewHostRendererManagementDelegateHelper( + render_process_id, + render_view_id, + method, + MakeTuple(a, b)); +} + +// ---------------------------------------------------------------------------- +// Proxy calls to the specified RenderViewHost's SSL delegate. + +template <typename Method, typename A> +inline void CallRenderViewHostSSLDelegate(int render_process_id, + int render_view_id, + Method method, + const A& a) { + internal::CallRenderViewHostSSLDelegateHelper( + render_process_id, + render_view_id, + method, + MakeTuple(a)); +} + +template <typename Method, typename A, typename B> +inline void CallRenderViewHostSSLDelegate(int render_process_id, + int render_view_id, + Method method, + const A& a, + const B& b) { + internal::CallRenderViewHostSSLDelegateHelper( + render_process_id, + render_view_id, + method, + MakeTuple(a, b)); +} + +// ---------------------------------------------------------------------------- + +#endif // CONTENT_BROWSER_RENDERER_HOST_RENDER_VIEW_HOST_NOTIFICATION_TASK_H_ diff --git a/content/browser/renderer_host/render_widget_fullscreen_host.cc b/content/browser/renderer_host/render_widget_fullscreen_host.cc new file mode 100644 index 0000000..4fbd140 --- /dev/null +++ b/content/browser/renderer_host/render_widget_fullscreen_host.cc @@ -0,0 +1,10 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "content/browser/renderer_host/render_widget_fullscreen_host.h" + +RenderWidgetFullscreenHost::RenderWidgetFullscreenHost( + RenderProcessHost* process, int routing_id) + : RenderWidgetHost(process, routing_id) { +} diff --git a/content/browser/renderer_host/render_widget_fullscreen_host.h b/content/browser/renderer_host/render_widget_fullscreen_host.h new file mode 100644 index 0000000..fce2200 --- /dev/null +++ b/content/browser/renderer_host/render_widget_fullscreen_host.h @@ -0,0 +1,15 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CONTENT_BROWSER_RENDERER_HOST_RENDER_WIDGET_FULLSCREEN_HOST_H_ +#define CONTENT_BROWSER_RENDERER_HOST_RENDER_WIDGET_FULLSCREEN_HOST_H_ + +#include "content/browser/renderer_host/render_widget_host.h" + +class RenderWidgetFullscreenHost : public RenderWidgetHost { + public: + RenderWidgetFullscreenHost(RenderProcessHost* process, int routing_id); +}; + +#endif // CONTENT_BROWSER_RENDERER_HOST_RENDER_WIDGET_FULLSCREEN_HOST_H_ diff --git a/content/browser/renderer_host/render_widget_helper.cc b/content/browser/renderer_host/render_widget_helper.cc new file mode 100644 index 0000000..c3bb7de --- /dev/null +++ b/content/browser/renderer_host/render_widget_helper.cc @@ -0,0 +1,335 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "content/browser/renderer_host/render_widget_helper.h" + +#include "base/eintr_wrapper.h" +#include "base/threading/thread.h" +#include "chrome/browser/browser_thread.h" +#include "chrome/common/render_messages_params.h" +#include "content/browser/renderer_host/render_process_host.h" +#include "content/browser/renderer_host/render_view_host.h" +#include "content/browser/renderer_host/resource_dispatcher_host.h" + +// A Task used with InvokeLater that we hold a pointer to in pending_paints_. +// Instances are deleted by MessageLoop after it calls their Run method. +class RenderWidgetHelper::UpdateMsgProxy : public Task { + public: + UpdateMsgProxy(RenderWidgetHelper* h, const IPC::Message& m) + : helper(h), + message(m), + cancelled(false) { + } + + ~UpdateMsgProxy() { + // If the paint message was never dispatched, then we need to let the + // helper know that we are going away. + if (!cancelled && helper) + helper->OnDiscardUpdateMsg(this); + } + + virtual void Run() { + if (!cancelled) { + helper->OnDispatchUpdateMsg(this); + helper = NULL; + } + } + + scoped_refptr<RenderWidgetHelper> helper; + IPC::Message message; + bool cancelled; // If true, then the message will not be dispatched. + + DISALLOW_COPY_AND_ASSIGN(UpdateMsgProxy); +}; + +RenderWidgetHelper::RenderWidgetHelper() + : render_process_id_(-1), +#if defined(OS_WIN) + event_(CreateEvent(NULL, FALSE /* auto-reset */, FALSE, NULL)), +#elif defined(OS_POSIX) + event_(false /* auto-reset */, false), +#endif + resource_dispatcher_host_(NULL) { +} + +RenderWidgetHelper::~RenderWidgetHelper() { + // The elements of pending_paints_ each hold an owning reference back to this + // object, so we should not be destroyed unless pending_paints_ is empty! + DCHECK(pending_paints_.empty()); + +#if defined(OS_MACOSX) + ClearAllocatedDIBs(); +#endif +} + +void RenderWidgetHelper::Init( + int render_process_id, + ResourceDispatcherHost* resource_dispatcher_host) { + render_process_id_ = render_process_id; + resource_dispatcher_host_ = resource_dispatcher_host; +} + +int RenderWidgetHelper::GetNextRoutingID() { + return next_routing_id_.GetNext() + 1; +} + +void RenderWidgetHelper::CancelResourceRequests(int render_widget_id) { + if (render_process_id_ == -1) + return; + + BrowserThread::PostTask( + BrowserThread::IO, FROM_HERE, + NewRunnableMethod(this, + &RenderWidgetHelper::OnCancelResourceRequests, + render_widget_id)); +} + +void RenderWidgetHelper::CrossSiteClosePageACK( + const ViewMsg_ClosePage_Params& params) { + BrowserThread::PostTask( + BrowserThread::IO, FROM_HERE, + NewRunnableMethod(this, + &RenderWidgetHelper::OnCrossSiteClosePageACK, + params)); +} + +bool RenderWidgetHelper::WaitForUpdateMsg(int render_widget_id, + const base::TimeDelta& max_delay, + IPC::Message* msg) { + base::TimeTicks time_start = base::TimeTicks::Now(); + + for (;;) { + UpdateMsgProxy* proxy = NULL; + { + base::AutoLock lock(pending_paints_lock_); + + UpdateMsgProxyMap::iterator it = pending_paints_.find(render_widget_id); + if (it != pending_paints_.end()) { + proxy = it->second; + + // Flag the proxy as cancelled so that when it is run as a task it will + // do nothing. + proxy->cancelled = true; + + pending_paints_.erase(it); + } + } + + if (proxy) { + *msg = proxy->message; + DCHECK(msg->routing_id() == render_widget_id); + return true; + } + + // Calculate the maximum amount of time that we are willing to sleep. + base::TimeDelta max_sleep_time = + max_delay - (base::TimeTicks::Now() - time_start); + if (max_sleep_time <= base::TimeDelta::FromMilliseconds(0)) + break; + + event_.TimedWait(max_sleep_time); + } + + return false; +} + +void RenderWidgetHelper::DidReceiveUpdateMsg(const IPC::Message& msg) { + int render_widget_id = msg.routing_id(); + + UpdateMsgProxy* proxy = NULL; + { + base::AutoLock lock(pending_paints_lock_); + + // Visual Studio 2010 has problems converting NULL to the null pointer for + // std::pair. See http://connect.microsoft.com/VisualStudio/feedback/details/520043/error-converting-from-null-to-a-pointer-type-in-std-pair + // It will work if we pass nullptr. +#if defined(_MSC_VER) && _MSC_VER >= 1600 + RenderWidgetHelper::UpdateMsgProxy* null_proxy = nullptr; +#else + RenderWidgetHelper::UpdateMsgProxy* null_proxy = NULL; +#endif + UpdateMsgProxyMap::value_type new_value(render_widget_id, null_proxy); + + // We expect only a single PaintRect message at a time. Optimize for the + // case that we don't already have an entry by using the 'insert' method. + std::pair<UpdateMsgProxyMap::iterator, bool> result = + pending_paints_.insert(new_value); + if (!result.second) { + NOTREACHED() << "Unexpected PaintRect message!"; + return; + } + + result.first->second = (proxy = new UpdateMsgProxy(this, msg)); + } + + // Notify anyone waiting on the UI thread that there is a new entry in the + // proxy map. If they don't find the entry they are looking for, then they + // will just continue waiting. + event_.Signal(); + + // The proxy will be deleted when it is run as a task. + BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, proxy); +} + +void RenderWidgetHelper::OnDiscardUpdateMsg(UpdateMsgProxy* proxy) { + const IPC::Message& msg = proxy->message; + + // Remove the proxy from the map now that we are going to handle it normally. + { + base::AutoLock lock(pending_paints_lock_); + + UpdateMsgProxyMap::iterator it = pending_paints_.find(msg.routing_id()); + DCHECK(it != pending_paints_.end()); + DCHECK(it->second == proxy); + + pending_paints_.erase(it); + } +} + +void RenderWidgetHelper::OnDispatchUpdateMsg(UpdateMsgProxy* proxy) { + OnDiscardUpdateMsg(proxy); + + // It is reasonable for the host to no longer exist. + RenderProcessHost* host = RenderProcessHost::FromID(render_process_id_); + if (host) + host->OnMessageReceived(proxy->message); +} + +void RenderWidgetHelper::OnCancelResourceRequests( + int render_widget_id) { + resource_dispatcher_host_->CancelRequestsForRoute( + render_process_id_, render_widget_id); +} + +void RenderWidgetHelper::OnCrossSiteClosePageACK( + const ViewMsg_ClosePage_Params& params) { + resource_dispatcher_host_->OnClosePageACK(params); +} + +void RenderWidgetHelper::CreateNewWindow( + const ViewHostMsg_CreateWindow_Params& params, + base::ProcessHandle render_process, + int* route_id) { + *route_id = GetNextRoutingID(); + // Block resource requests until the view is created, since the HWND might be + // needed if a response ends up creating a plugin. + resource_dispatcher_host_->BlockRequestsForRoute( + render_process_id_, *route_id); + + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + NewRunnableMethod( + this, &RenderWidgetHelper::OnCreateWindowOnUI, params, *route_id)); +} + +void RenderWidgetHelper::OnCreateWindowOnUI( + const ViewHostMsg_CreateWindow_Params& params, + int route_id) { + RenderViewHost* host = + RenderViewHost::FromID(render_process_id_, params.opener_id); + if (host) + host->CreateNewWindow(route_id, params); + + BrowserThread::PostTask( + BrowserThread::IO, FROM_HERE, + NewRunnableMethod(this, &RenderWidgetHelper::OnCreateWindowOnIO, + route_id)); +} + +void RenderWidgetHelper::OnCreateWindowOnIO(int route_id) { + resource_dispatcher_host_->ResumeBlockedRequestsForRoute( + render_process_id_, route_id); +} + +void RenderWidgetHelper::CreateNewWidget(int opener_id, + WebKit::WebPopupType popup_type, + int* route_id) { + *route_id = GetNextRoutingID(); + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + NewRunnableMethod( + this, &RenderWidgetHelper::OnCreateWidgetOnUI, opener_id, *route_id, + popup_type)); +} + +void RenderWidgetHelper::CreateNewFullscreenWidget(int opener_id, + int* route_id) { + *route_id = GetNextRoutingID(); + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + NewRunnableMethod( + this, &RenderWidgetHelper::OnCreateFullscreenWidgetOnUI, + opener_id, *route_id)); +} + +void RenderWidgetHelper::OnCreateWidgetOnUI( + int opener_id, int route_id, WebKit::WebPopupType popup_type) { + RenderViewHost* host = RenderViewHost::FromID(render_process_id_, opener_id); + if (host) + host->CreateNewWidget(route_id, popup_type); +} + +void RenderWidgetHelper::OnCreateFullscreenWidgetOnUI(int opener_id, + int route_id) { + RenderViewHost* host = RenderViewHost::FromID(render_process_id_, opener_id); + if (host) + host->CreateNewFullscreenWidget(route_id); +} + +#if defined(OS_MACOSX) +TransportDIB* RenderWidgetHelper::MapTransportDIB(TransportDIB::Id dib_id) { + base::AutoLock locked(allocated_dibs_lock_); + + const std::map<TransportDIB::Id, int>::iterator + i = allocated_dibs_.find(dib_id); + if (i == allocated_dibs_.end()) + return NULL; + + base::FileDescriptor fd(dup(i->second), true); + return TransportDIB::Map(fd); +} + +void RenderWidgetHelper::AllocTransportDIB( + size_t size, bool cache_in_browser, TransportDIB::Handle* result) { + scoped_ptr<base::SharedMemory> shared_memory(new base::SharedMemory()); + if (!shared_memory->CreateAnonymous(size)) { + result->fd = -1; + result->auto_close = false; + return; + } + + shared_memory->GiveToProcess(0 /* pid, not needed */, result); + + if (cache_in_browser) { + // Keep a copy of the file descriptor around + base::AutoLock locked(allocated_dibs_lock_); + allocated_dibs_[shared_memory->id()] = dup(result->fd); + } +} + +void RenderWidgetHelper::FreeTransportDIB(TransportDIB::Id dib_id) { + base::AutoLock locked(allocated_dibs_lock_); + + const std::map<TransportDIB::Id, int>::iterator + i = allocated_dibs_.find(dib_id); + + if (i != allocated_dibs_.end()) { + if (HANDLE_EINTR(close(i->second)) < 0) + PLOG(ERROR) << "close"; + allocated_dibs_.erase(i); + } else { + DLOG(WARNING) << "Renderer asked us to free unknown transport DIB"; + } +} + +void RenderWidgetHelper::ClearAllocatedDIBs() { + for (std::map<TransportDIB::Id, int>::iterator + i = allocated_dibs_.begin(); i != allocated_dibs_.end(); ++i) { + if (HANDLE_EINTR(close(i->second)) < 0) + PLOG(ERROR) << "close: " << i->first; + } + + allocated_dibs_.clear(); +} +#endif diff --git a/content/browser/renderer_host/render_widget_helper.h b/content/browser/renderer_host/render_widget_helper.h new file mode 100644 index 0000000..0dd25d7 --- /dev/null +++ b/content/browser/renderer_host/render_widget_helper.h @@ -0,0 +1,216 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CONTENT_BROWSER_RENDERER_HOST_RENDER_WIDGET_HELPER_H_ +#define CONTENT_BROWSER_RENDERER_HOST_RENDER_WIDGET_HELPER_H_ +#pragma once + +#include <map> + +#include "app/surface/transport_dib.h" +#include "base/atomic_sequence_num.h" +#include "base/hash_tables.h" +#include "base/process.h" +#include "base/ref_counted.h" +#include "base/synchronization/lock.h" +#include "base/synchronization/waitable_event.h" +#include "chrome/common/window_container_type.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebPopupType.h" + +namespace IPC { +class Message; +} + +namespace base { +class TimeDelta; +} + +class ResourceDispatcherHost; +struct ViewHostMsg_CreateWindow_Params; +struct ViewMsg_ClosePage_Params; + + +// Instantiated per RenderProcessHost to provide various optimizations on +// behalf of a RenderWidgetHost. This class bridges between the IO thread +// where the RenderProcessHost's MessageFilter lives and the UI thread where +// the RenderWidgetHost lives. +// +// +// OPTIMIZED RESIZE +// +// RenderWidgetHelper is used to implement optimized resize. When the +// RenderWidgetHost is resized, it sends a Resize message to its RenderWidget +// counterpart in the renderer process. The RenderWidget generates a +// UpdateRect message in response to the Resize message, and it sets the +// IS_RESIZE_ACK flag in the UpdateRect message to true. +// +// Back in the browser process, when the RenderProcessHost's MessageFilter +// sees a UpdateRect message, it directs it to the RenderWidgetHelper by +// calling the DidReceiveUpdateMsg method. That method stores the data for +// the UpdateRect message in a map, where it can be directly accessed by the +// RenderWidgetHost on the UI thread during a call to RenderWidgetHost's +// GetBackingStore method. +// +// When the RenderWidgetHost's GetBackingStore method is called, it first +// checks to see if it is waiting for a resize ack. If it is, then it calls +// the RenderWidgetHelper's WaitForUpdateMsg to check if there is already a +// resulting UpdateRect message (or to wait a short amount of time for one to +// arrive). The main goal of this mechanism is to short-cut the usual way in +// which IPC messages are proxied over to the UI thread via InvokeLater. +// This approach is necessary since window resize is followed up immediately +// by a request to repaint the window. +// +// +// OPTIMIZED TAB SWITCHING +// +// When a RenderWidgetHost is in a background tab, it is flagged as hidden. +// This causes the corresponding RenderWidget to stop sending UpdateRect +// messages. The RenderWidgetHost also discards its backingstore when it is +// hidden, which helps free up memory. As a result, when a RenderWidgetHost +// is restored, it can be momentarily without a backingstore. (Restoring a +// RenderWidgetHost results in a WasRestored message being sent to the +// RenderWidget, which triggers a full UpdateRect message.) This can lead to +// an observed rendering glitch as the TabContents will just have to fill +// white overtop the RenderWidgetHost until the RenderWidgetHost receives a +// UpdateRect message to refresh its backingstore. +// +// To avoid this 'white flash', the RenderWidgetHost again makes use of the +// RenderWidgetHelper's WaitForUpdateMsg method. When the RenderWidgetHost's +// GetBackingStore method is called, it will call WaitForUpdateMsg if it has +// no backingstore. +// +// TRANSPORT DIB CREATION +// +// On some platforms (currently the Mac) the renderer cannot create transport +// DIBs because of sandbox limitations. Thus, it has to make synchronous IPCs +// to the browser for them. Since these requests are synchronous, they cannot +// terminate on the UI thread. Thus, in this case, this object performs the +// allocation and maintains the set of allocated transport DIBs which the +// renderers can refer to. +// +class RenderWidgetHelper + : public base::RefCountedThreadSafe<RenderWidgetHelper> { + public: + RenderWidgetHelper(); + + void Init(int render_process_id, + ResourceDispatcherHost* resource_dispatcher_host); + + // Gets the next available routing id. This is thread safe. + int GetNextRoutingID(); + + + // UI THREAD ONLY ----------------------------------------------------------- + + // These three functions provide the backend implementation of the + // corresponding functions in RenderProcessHost. See those declarations + // for documentation. + void CancelResourceRequests(int render_widget_id); + void CrossSiteClosePageACK(const ViewMsg_ClosePage_Params& params); + bool WaitForUpdateMsg(int render_widget_id, + const base::TimeDelta& max_delay, + IPC::Message* msg); + +#if defined(OS_MACOSX) + // Given the id of a transport DIB, return a mapping to it or NULL on error. + TransportDIB* MapTransportDIB(TransportDIB::Id dib_id); +#endif + + + // IO THREAD ONLY ----------------------------------------------------------- + + // Called on the IO thread when a UpdateRect message is received. + void DidReceiveUpdateMsg(const IPC::Message& msg); + + void CreateNewWindow(const ViewHostMsg_CreateWindow_Params& params, + base::ProcessHandle render_process, + int* route_id); + void CreateNewWidget(int opener_id, + WebKit::WebPopupType popup_type, + int* route_id); + void CreateNewFullscreenWidget(int opener_id, int* route_id); + +#if defined(OS_MACOSX) + // Called on the IO thread to handle the allocation of a TransportDIB. If + // |cache_in_browser| is |true|, then a copy of the shmem is kept by the + // browser, and it is the caller's repsonsibility to call + // FreeTransportDIB(). In all cases, the caller is responsible for deleting + // the resulting TransportDIB. + void AllocTransportDIB(size_t size, + bool cache_in_browser, + TransportDIB::Handle* result); + + // Called on the IO thread to handle the freeing of a transport DIB + void FreeTransportDIB(TransportDIB::Id dib_id); +#endif + + private: + // A class used to proxy a paint message. PaintMsgProxy objects are created + // on the IO thread and destroyed on the UI thread. + class UpdateMsgProxy; + friend class UpdateMsgProxy; + friend class base::RefCountedThreadSafe<RenderWidgetHelper>; + + // Map from render_widget_id to live PaintMsgProxy instance. + typedef base::hash_map<int, UpdateMsgProxy*> UpdateMsgProxyMap; + + ~RenderWidgetHelper(); + + // Called on the UI thread to discard a paint message. + void OnDiscardUpdateMsg(UpdateMsgProxy* proxy); + + // Called on the UI thread to dispatch a paint message if necessary. + void OnDispatchUpdateMsg(UpdateMsgProxy* proxy); + + // Called on the UI thread to finish creating a window. + void OnCreateWindowOnUI(const ViewHostMsg_CreateWindow_Params& params, + int route_id); + + // Called on the IO thread after a window was created on the UI thread. + void OnCreateWindowOnIO(int route_id); + + // Called on the UI thread to finish creating a widget. + void OnCreateWidgetOnUI(int opener_id, + int route_id, + WebKit::WebPopupType popup_type); + + // Called on the UI thread to create a fullscreen widget. + void OnCreateFullscreenWidgetOnUI(int opener_id, int route_id); + + // Called on the IO thread to cancel resource requests for the render widget. + void OnCancelResourceRequests(int render_widget_id); + + // Called on the IO thread to resume a cross-site response. + void OnCrossSiteClosePageACK(const ViewMsg_ClosePage_Params& params); + +#if defined(OS_MACOSX) + // Called on destruction to release all allocated transport DIBs + void ClearAllocatedDIBs(); + + // On OSX we keep file descriptors to all the allocated DIBs around until + // the renderer frees them. + base::Lock allocated_dibs_lock_; + std::map<TransportDIB::Id, int> allocated_dibs_; +#endif + + // A map of live paint messages. Must hold pending_paints_lock_ to access. + // The UpdateMsgProxy objects are not owned by this map. (See UpdateMsgProxy + // for details about how the lifetime of instances are managed.) + UpdateMsgProxyMap pending_paints_; + base::Lock pending_paints_lock_; + + int render_process_id_; + + // Event used to implement WaitForUpdateMsg. + base::WaitableEvent event_; + + // The next routing id to use. + base::AtomicSequenceNumber next_routing_id_; + + ResourceDispatcherHost* resource_dispatcher_host_; + + DISALLOW_COPY_AND_ASSIGN(RenderWidgetHelper); +}; + +#endif // CONTENT_BROWSER_RENDERER_HOST_RENDER_WIDGET_HELPER_H_ diff --git a/content/browser/renderer_host/render_widget_host.cc b/content/browser/renderer_host/render_widget_host.cc new file mode 100644 index 0000000..5fbd2df --- /dev/null +++ b/content/browser/renderer_host/render_widget_host.cc @@ -0,0 +1,1287 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "content/browser/renderer_host/render_widget_host.h" + +#include "base/auto_reset.h" +#include "base/command_line.h" +#include "base/message_loop.h" +#include "base/metrics/histogram.h" +#include "chrome/browser/accessibility/browser_accessibility_state.h" +#include "chrome/browser/metrics/user_metrics.h" +#include "chrome/common/chrome_switches.h" +#include "chrome/common/result_codes.h" +#include "chrome/common/native_web_keyboard_event.h" +#include "chrome/common/notification_service.h" +#include "chrome/common/render_messages.h" +#include "chrome/common/render_messages_params.h" +#include "content/browser/renderer_host/backing_store.h" +#include "content/browser/renderer_host/backing_store_manager.h" +#include "content/browser/renderer_host/render_process_host.h" +#include "content/browser/renderer_host/render_widget_helper.h" +#include "content/browser/renderer_host/render_widget_host_view.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebCompositionUnderline.h" +#include "ui/base/keycodes/keyboard_codes.h" +#include "webkit/glue/webcursor.h" +#include "webkit/plugins/npapi/webplugin.h" + +#if defined(TOOLKIT_VIEWS) +#include "views/view.h" +#endif + +#if defined (OS_MACOSX) +#include "third_party/WebKit/Source/WebKit/chromium/public/WebScreenInfo.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/mac/WebScreenInfoFactory.h" +#endif + +using base::Time; +using base::TimeDelta; +using base::TimeTicks; + +using WebKit::WebInputEvent; +using WebKit::WebKeyboardEvent; +using WebKit::WebMouseEvent; +using WebKit::WebMouseWheelEvent; +using WebKit::WebTextDirection; + +#if defined (OS_MACOSX) +using WebKit::WebScreenInfo; +using WebKit::WebScreenInfoFactory; +#endif + +// How long to (synchronously) wait for the renderer to respond with a +// PaintRect message, when our backing-store is invalid, before giving up and +// returning a null or incorrectly sized backing-store from GetBackingStore. +// This timeout impacts the "choppiness" of our window resize perf. +static const int kPaintMsgTimeoutMS = 40; + +// How long to wait before we consider a renderer hung. +static const int kHungRendererDelayMs = 20000; + +// The maximum time between wheel messages while coalescing. This trades off +// smoothness of scrolling with a risk of falling behind the events, resulting +// in trailing scrolls after the user ends their input. +static const int kMaxTimeBetweenWheelMessagesMs = 250; + +/////////////////////////////////////////////////////////////////////////////// +// RenderWidgetHost + +RenderWidgetHost::RenderWidgetHost(RenderProcessHost* process, + int routing_id) + : renderer_initialized_(false), + renderer_accessible_(false), + view_(NULL), + process_(process), + routing_id_(routing_id), + is_loading_(false), + is_hidden_(false), + is_accelerated_compositing_active_(false), + repaint_ack_pending_(false), + resize_ack_pending_(false), + mouse_move_pending_(false), + mouse_wheel_pending_(false), + needs_repainting_on_restore_(false), + is_unresponsive_(false), + in_get_backing_store_(false), + view_being_painted_(false), + ignore_input_events_(false), + text_direction_updated_(false), + text_direction_(WebKit::WebTextDirectionLeftToRight), + text_direction_canceled_(false), + suppress_next_char_events_(false) { + if (routing_id_ == MSG_ROUTING_NONE) + routing_id_ = process_->GetNextRoutingID(); + + process_->Attach(this, routing_id_); + // Because the widget initializes as is_hidden_ == false, + // tell the process host that we're alive. + process_->WidgetRestored(); + + if (CommandLine::ForCurrentProcess()->HasSwitch( + switches::kForceRendererAccessibility) || + BrowserAccessibilityState::GetInstance()->IsAccessibleBrowser()) { + EnableRendererAccessibility(); + } +} + +RenderWidgetHost::~RenderWidgetHost() { + // Clear our current or cached backing store if either remains. + BackingStoreManager::RemoveBackingStore(this); + + process_->Release(routing_id_); +} + +gfx::NativeViewId RenderWidgetHost::GetNativeViewId() { + if (view_) + return gfx::IdFromNativeView(view_->GetNativeView()); + return 0; +} + +bool RenderWidgetHost::PreHandleKeyboardEvent( + const NativeWebKeyboardEvent& event, + bool* is_keyboard_shortcut) { + return false; +} + +void RenderWidgetHost::Init() { + DCHECK(process_->HasConnection()); + + renderer_initialized_ = true; + + // Send the ack along with the information on placement. + Send(new ViewMsg_CreatingNew_ACK(routing_id_, GetNativeViewId())); + WasResized(); +} + +void RenderWidgetHost::Shutdown() { + if (process_->HasConnection()) { + // Tell the renderer object to close. + process_->ReportExpectingClose(routing_id_); + bool rv = Send(new ViewMsg_Close(routing_id_)); + DCHECK(rv); + } + + Destroy(); +} + +bool RenderWidgetHost::IsRenderView() const { + return false; +} + +bool RenderWidgetHost::OnMessageReceived(const IPC::Message &msg) { + bool handled = true; + bool msg_is_ok = true; + IPC_BEGIN_MESSAGE_MAP_EX(RenderWidgetHost, msg, msg_is_ok) + IPC_MESSAGE_HANDLER(ViewHostMsg_RenderViewReady, OnMsgRenderViewReady) + IPC_MESSAGE_HANDLER(ViewHostMsg_RenderViewGone, OnMsgRenderViewGone) + IPC_MESSAGE_HANDLER(ViewHostMsg_Close, OnMsgClose) + IPC_MESSAGE_HANDLER(ViewHostMsg_RequestMove, OnMsgRequestMove) + IPC_MESSAGE_HANDLER(ViewHostMsg_PaintAtSize_ACK, OnMsgPaintAtSizeAck) + IPC_MESSAGE_HANDLER(ViewHostMsg_UpdateRect, OnMsgUpdateRect) + IPC_MESSAGE_HANDLER(ViewHostMsg_HandleInputEvent_ACK, OnMsgInputEventAck) + IPC_MESSAGE_HANDLER(ViewHostMsg_Focus, OnMsgFocus) + IPC_MESSAGE_HANDLER(ViewHostMsg_Blur, OnMsgBlur) + IPC_MESSAGE_HANDLER(ViewHostMsg_SetCursor, OnMsgSetCursor) + IPC_MESSAGE_HANDLER(ViewHostMsg_ImeUpdateTextInputState, + OnMsgImeUpdateTextInputState) + IPC_MESSAGE_HANDLER(ViewHostMsg_ImeCancelComposition, + OnMsgImeCancelComposition) + IPC_MESSAGE_HANDLER(ViewHostMsg_DidActivateAcceleratedCompositing, + OnMsgDidActivateAcceleratedCompositing) +#if defined(OS_MACOSX) + IPC_MESSAGE_HANDLER(ViewHostMsg_GetScreenInfo, OnMsgGetScreenInfo) + IPC_MESSAGE_HANDLER(ViewHostMsg_GetWindowRect, OnMsgGetWindowRect) + IPC_MESSAGE_HANDLER(ViewHostMsg_GetRootWindowRect, OnMsgGetRootWindowRect) + IPC_MESSAGE_HANDLER(ViewHostMsg_PluginFocusChanged, + OnMsgPluginFocusChanged) + IPC_MESSAGE_HANDLER(ViewHostMsg_StartPluginIme, + OnMsgStartPluginIme) + IPC_MESSAGE_HANDLER(ViewHostMsg_AllocateFakePluginWindowHandle, + OnAllocateFakePluginWindowHandle) + IPC_MESSAGE_HANDLER(ViewHostMsg_DestroyFakePluginWindowHandle, + OnDestroyFakePluginWindowHandle) + IPC_MESSAGE_HANDLER(ViewHostMsg_AcceleratedSurfaceSetIOSurface, + OnAcceleratedSurfaceSetIOSurface) + IPC_MESSAGE_HANDLER(ViewHostMsg_AcceleratedSurfaceSetTransportDIB, + OnAcceleratedSurfaceSetTransportDIB) + IPC_MESSAGE_HANDLER(ViewHostMsg_AcceleratedSurfaceBuffersSwapped, + OnAcceleratedSurfaceBuffersSwapped) +#elif defined(OS_POSIX) + IPC_MESSAGE_HANDLER(ViewHostMsg_CreatePluginContainer, + OnMsgCreatePluginContainer) + IPC_MESSAGE_HANDLER(ViewHostMsg_DestroyPluginContainer, + OnMsgDestroyPluginContainer) +#endif + IPC_MESSAGE_UNHANDLED(handled = false) + IPC_END_MESSAGE_MAP_EX() + + if (!msg_is_ok) { + // The message de-serialization failed. Kill the renderer process. + UserMetrics::RecordAction(UserMetricsAction("BadMessageTerminate_RWH")); + process()->ReceivedBadMessage(); + } + return handled; +} + +bool RenderWidgetHost::Send(IPC::Message* msg) { + return process_->Send(msg); +} + +void RenderWidgetHost::WasHidden() { + is_hidden_ = true; + + // Don't bother reporting hung state when we aren't the active tab. + StopHangMonitorTimeout(); + + // If we have a renderer, then inform it that we are being hidden so it can + // reduce its resource utilization. + Send(new ViewMsg_WasHidden(routing_id_)); + + // TODO(darin): what about constrained windows? it doesn't look like they + // see a message when their parent is hidden. maybe there is something more + // generic we can do at the TabContents API level instead of relying on + // Windows messages. + + // Tell the RenderProcessHost we were hidden. + process_->WidgetHidden(); + + bool is_visible = false; + NotificationService::current()->Notify( + NotificationType::RENDER_WIDGET_VISIBILITY_CHANGED, + Source<RenderWidgetHost>(this), + Details<bool>(&is_visible)); +} + +void RenderWidgetHost::WasRestored() { + // When we create the widget, it is created as *not* hidden. + if (!is_hidden_) + return; + is_hidden_ = false; + + BackingStore* backing_store = BackingStoreManager::Lookup(this); + // If we already have a backing store for this widget, then we don't need to + // repaint on restore _unless_ we know that our backing store is invalid. + // When accelerated compositing is on, we must always repaint, even when + // the backing store exists. + bool needs_repainting; + if (needs_repainting_on_restore_ || !backing_store || + is_accelerated_compositing_active()) { + needs_repainting = true; + needs_repainting_on_restore_ = false; + } else { + needs_repainting = false; + } + Send(new ViewMsg_WasRestored(routing_id_, needs_repainting)); + + process_->WidgetRestored(); + + bool is_visible = true; + NotificationService::current()->Notify( + NotificationType::RENDER_WIDGET_VISIBILITY_CHANGED, + Source<RenderWidgetHost>(this), + Details<bool>(&is_visible)); + + // It's possible for our size to be out of sync with the renderer. The + // following is one case that leads to this: + // 1. WasResized -> Send ViewMsg_Resize to render + // 2. WasResized -> do nothing as resize_ack_pending_ is true + // 3. WasHidden + // 4. OnMsgUpdateRect from (1) processed. Does NOT invoke WasResized as view + // is hidden. Now renderer/browser out of sync with what they think size + // is. + // By invoking WasResized the renderer is updated as necessary. WasResized + // does nothing if the sizes are already in sync. + // + // TODO: ideally ViewMsg_WasRestored would take a size. This way, the renderer + // could handle both the restore and resize at once. This isn't that big a + // deal as RenderWidget::WasRestored delays updating, so that the resize from + // WasResized is usually processed before the renderer is painted. + WasResized(); +} + +void RenderWidgetHost::WasResized() { + if (resize_ack_pending_ || !process_->HasConnection() || !view_ || + !renderer_initialized_) { + return; + } + +#if !defined(OS_MACOSX) + gfx::Size new_size = view_->GetViewBounds().size(); +#else + // When UI scaling is enabled on OS X, allocate a smaller bitmap and + // pixel-scale it up. + // TODO(thakis): Use pixel size on mac and set UI scale in renderer. + // http://crbug.com/31960 + gfx::Size new_size(view_->GetViewCocoaBounds().size()); +#endif + gfx::Rect reserved_rect = view_->reserved_contents_rect(); + + // Avoid asking the RenderWidget to resize to its current size, since it + // won't send us a PaintRect message in that case, unless reserved area is + // changed, but even in this case PaintRect message won't be sent. + if (new_size == current_size_ && reserved_rect == current_reserved_rect_) + return; + + if (in_flight_size_ != gfx::Size() && new_size == in_flight_size_ && + in_flight_reserved_rect_ == reserved_rect) { + return; + } + + // We don't expect to receive an ACK when the requested size is empty or + // only reserved area is changed. + resize_ack_pending_ = !new_size.IsEmpty() && new_size != current_size_; + + if (!Send(new ViewMsg_Resize(routing_id_, new_size, reserved_rect))) { + resize_ack_pending_ = false; + } else { + if (resize_ack_pending_) { + in_flight_size_ = new_size; + in_flight_reserved_rect_ = reserved_rect; + } else { + // Message was sent successfully, but we do not expect to receive an ACK, + // so update current values right away. + current_size_ = new_size; + // TODO(alekseys): send a message from renderer to ack a reserved rect + // changes only. + current_reserved_rect_ = reserved_rect; + } + } +} + +void RenderWidgetHost::GotFocus() { + Focus(); +} + +void RenderWidgetHost::Focus() { + Send(new ViewMsg_SetFocus(routing_id_, true)); +} + +void RenderWidgetHost::Blur() { + Send(new ViewMsg_SetFocus(routing_id_, false)); +} + +void RenderWidgetHost::LostCapture() { + Send(new ViewMsg_MouseCaptureLost(routing_id_)); +} + +void RenderWidgetHost::ViewDestroyed() { + // TODO(evanm): tracking this may no longer be necessary; + // eliminate this function if so. + view_ = NULL; +} + +void RenderWidgetHost::SetIsLoading(bool is_loading) { + is_loading_ = is_loading; + if (!view_) + return; + view_->SetIsLoading(is_loading); +} + +void RenderWidgetHost::PaintAtSize(TransportDIB::Handle dib_handle, + int tag, + const gfx::Size& page_size, + const gfx::Size& desired_size) { + // Ask the renderer to create a bitmap regardless of whether it's + // hidden, being resized, redrawn, etc. It resizes the web widget + // to the page_size and then scales it to the desired_size. + Send(new ViewMsg_PaintAtSize(routing_id_, dib_handle, tag, + page_size, desired_size)); +} + +BackingStore* RenderWidgetHost::GetBackingStore(bool force_create) { + // We should not be asked to paint while we are hidden. If we are hidden, + // then it means that our consumer failed to call WasRestored. If we're not + // force creating the backing store, it's OK since we can feel free to give + // out our cached one if we have it. + DCHECK(!is_hidden_ || !force_create) << + "GetBackingStore called while hidden!"; + + // We should never be called recursively; this can theoretically lead to + // infinite recursion and almost certainly leads to lower performance. + DCHECK(!in_get_backing_store_) << "GetBackingStore called recursively!"; + AutoReset<bool> auto_reset_in_get_backing_store(&in_get_backing_store_, true); + + // We might have a cached backing store that we can reuse! + BackingStore* backing_store = + BackingStoreManager::GetBackingStore(this, current_size_); + if (!force_create) + return backing_store; + + // If we fail to find a backing store in the cache, send out a request + // to the renderer to paint the view if required. + if (!backing_store && !repaint_ack_pending_ && !resize_ack_pending_ && + !view_being_painted_) { + repaint_start_time_ = TimeTicks::Now(); + repaint_ack_pending_ = true; + Send(new ViewMsg_Repaint(routing_id_, current_size_)); + } + + // When we have asked the RenderWidget to resize, and we are still waiting on + // a response, block for a little while to see if we can't get a response + // before returning the old (incorrectly sized) backing store. + if (resize_ack_pending_ || !backing_store) { + IPC::Message msg; + TimeDelta max_delay = TimeDelta::FromMilliseconds(kPaintMsgTimeoutMS); + if (process_->WaitForUpdateMsg(routing_id_, max_delay, &msg)) { + OnMessageReceived(msg); + backing_store = BackingStoreManager::GetBackingStore(this, current_size_); + } + } + + return backing_store; +} + +BackingStore* RenderWidgetHost::AllocBackingStore(const gfx::Size& size) { + if (!view_) + return NULL; + return view_->AllocBackingStore(size); +} + +void RenderWidgetHost::DonePaintingToBackingStore() { + Send(new ViewMsg_UpdateRect_ACK(routing_id())); +} + +void RenderWidgetHost::ScheduleComposite() { + if (is_hidden_ || !is_accelerated_compositing_active_) { + return; + } + + // Send out a request to the renderer to paint the view if required. + if (!repaint_ack_pending_ && !resize_ack_pending_ && !view_being_painted_) { + repaint_start_time_ = TimeTicks::Now(); + repaint_ack_pending_ = true; + Send(new ViewMsg_Repaint(routing_id_, current_size_)); + } + + // When we have asked the RenderWidget to resize, and we are still waiting on + // a response, block for a little while to see if we can't get a response. + // We always block on response because we do not have a backing store. + IPC::Message msg; + TimeDelta max_delay = TimeDelta::FromMilliseconds(kPaintMsgTimeoutMS); + if (process_->WaitForUpdateMsg(routing_id_, max_delay, &msg)) + OnMessageReceived(msg); +} + +void RenderWidgetHost::StartHangMonitorTimeout(TimeDelta delay) { + if (CommandLine::ForCurrentProcess()->HasSwitch( + switches::kDisableHangMonitor)) { + return; + } + + // If we already have a timer that will expire at or before the given delay, + // then we have nothing more to do now. If we have set our end time to null + // by calling StopHangMonitorTimeout, though, we will need to restart the + // timer. + if (hung_renderer_timer_.IsRunning() && + hung_renderer_timer_.GetCurrentDelay() <= delay && + !time_when_considered_hung_.is_null()) { + return; + } + + // Either the timer is not yet running, or we need to adjust the timer to + // fire sooner. + time_when_considered_hung_ = Time::Now() + delay; + hung_renderer_timer_.Stop(); + hung_renderer_timer_.Start(delay, this, + &RenderWidgetHost::CheckRendererIsUnresponsive); +} + +void RenderWidgetHost::RestartHangMonitorTimeout() { + // Setting to null will cause StartHangMonitorTimeout to restart the timer. + time_when_considered_hung_ = Time(); + StartHangMonitorTimeout(TimeDelta::FromMilliseconds(kHungRendererDelayMs)); +} + +void RenderWidgetHost::StopHangMonitorTimeout() { + time_when_considered_hung_ = Time(); + RendererIsResponsive(); + + // We do not bother to stop the hung_renderer_timer_ here in case it will be + // started again shortly, which happens to be the common use case. +} + +void RenderWidgetHost::SystemThemeChanged() { + Send(new ViewMsg_ThemeChanged(routing_id_)); +} + +void RenderWidgetHost::ForwardMouseEvent(const WebMouseEvent& mouse_event) { + if (ignore_input_events_ || process_->ignore_input_events()) + return; + + // Avoid spamming the renderer with mouse move events. It is important + // to note that WM_MOUSEMOVE events are anyways synthetic, but since our + // thread is able to rapidly consume WM_MOUSEMOVE events, we may get way + // more WM_MOUSEMOVE events than we wish to send to the renderer. + if (mouse_event.type == WebInputEvent::MouseMove) { + if (mouse_move_pending_) { + next_mouse_move_.reset(new WebMouseEvent(mouse_event)); + return; + } + mouse_move_pending_ = true; + } else if (mouse_event.type == WebInputEvent::MouseDown) { + OnUserGesture(); + } + + ForwardInputEvent(mouse_event, sizeof(WebMouseEvent), false); +} + +void RenderWidgetHost::OnMouseActivate() { +} + +void RenderWidgetHost::ForwardWheelEvent( + const WebMouseWheelEvent& wheel_event) { + if (ignore_input_events_ || process_->ignore_input_events()) + return; + + // If there's already a mouse wheel event waiting to be sent to the renderer, + // add the new deltas to that event. Not doing so (e.g., by dropping the old + // event, as for mouse moves) results in very slow scrolling on the Mac (on + // which many, very small wheel events are sent). + if (mouse_wheel_pending_) { + if (coalesced_mouse_wheel_events_.empty() || + coalesced_mouse_wheel_events_.back().modifiers + != wheel_event.modifiers || + coalesced_mouse_wheel_events_.back().scrollByPage + != wheel_event.scrollByPage) { + coalesced_mouse_wheel_events_.push_back(wheel_event); + } else { + WebMouseWheelEvent* last_wheel_event = + &coalesced_mouse_wheel_events_.back(); + last_wheel_event->deltaX += wheel_event.deltaX; + last_wheel_event->deltaY += wheel_event.deltaY; + DCHECK_GE(wheel_event.timeStampSeconds, + last_wheel_event->timeStampSeconds); + last_wheel_event->timeStampSeconds = wheel_event.timeStampSeconds; + } + return; + } + mouse_wheel_pending_ = true; + + HISTOGRAM_COUNTS_100("MPArch.RWH_WheelQueueSize", + coalesced_mouse_wheel_events_.size()); + + ForwardInputEvent(wheel_event, sizeof(WebMouseWheelEvent), false); +} + +void RenderWidgetHost::ForwardKeyboardEvent( + const NativeWebKeyboardEvent& key_event) { + if (ignore_input_events_ || process_->ignore_input_events()) + return; + + if (key_event.type == WebKeyboardEvent::Char && + (key_event.windowsKeyCode == ui::VKEY_RETURN || + key_event.windowsKeyCode == ui::VKEY_SPACE)) { + OnUserGesture(); + } + + // Double check the type to make sure caller hasn't sent us nonsense that + // will mess up our key queue. + if (WebInputEvent::isKeyboardEventType(key_event.type)) { + if (suppress_next_char_events_) { + // If preceding RawKeyDown event was handled by the browser, then we need + // suppress all Char events generated by it. Please note that, one + // RawKeyDown event may generate multiple Char events, so we can't reset + // |suppress_next_char_events_| until we get a KeyUp or a RawKeyDown. + if (key_event.type == WebKeyboardEvent::Char) + return; + // We get a KeyUp or a RawKeyDown event. + suppress_next_char_events_ = false; + } + + bool is_keyboard_shortcut = false; + // Only pre-handle the key event if it's not handled by the input method. + if (!key_event.skip_in_browser) { + // We need to set |suppress_next_char_events_| to true if + // PreHandleKeyboardEvent() returns true, but |this| may already be + // destroyed at that time. So set |suppress_next_char_events_| true here, + // then revert it afterwards when necessary. + if (key_event.type == WebKeyboardEvent::RawKeyDown) + suppress_next_char_events_ = true; + + // Tab switching/closing accelerators aren't sent to the renderer to avoid + // a hung/malicious renderer from interfering. + if (PreHandleKeyboardEvent(key_event, &is_keyboard_shortcut)) + return; + + if (key_event.type == WebKeyboardEvent::RawKeyDown) + suppress_next_char_events_ = false; + } + + // Don't add this key to the queue if we have no way to send the message... + if (!process_->HasConnection()) + return; + + // Put all WebKeyboardEvent objects in a queue since we can't trust the + // renderer and we need to give something to the UnhandledInputEvent + // handler. + key_queue_.push_back(key_event); + HISTOGRAM_COUNTS_100("Renderer.KeyboardQueueSize", key_queue_.size()); + + // Only forward the non-native portions of our event. + ForwardInputEvent(key_event, sizeof(WebKeyboardEvent), + is_keyboard_shortcut); + } +} + +void RenderWidgetHost::ForwardInputEvent(const WebInputEvent& input_event, + int event_size, + bool is_keyboard_shortcut) { + if (!process_->HasConnection()) + return; + + DCHECK(!process_->ignore_input_events()); + + IPC::Message* message = new ViewMsg_HandleInputEvent(routing_id_); + message->WriteData( + reinterpret_cast<const char*>(&input_event), event_size); + // |is_keyboard_shortcut| only makes sense for RawKeyDown events. + if (input_event.type == WebInputEvent::RawKeyDown) + message->WriteBool(is_keyboard_shortcut); + input_event_start_time_ = TimeTicks::Now(); + Send(message); + + // Any non-wheel input event cancels pending wheel events. + if (input_event.type != WebInputEvent::MouseWheel) + coalesced_mouse_wheel_events_.clear(); + + // Any input event cancels a pending mouse move event. Note that + // |next_mouse_move_| possibly owns |input_event|, so don't use |input_event| + // after this line. + next_mouse_move_.reset(); + + StartHangMonitorTimeout(TimeDelta::FromMilliseconds(kHungRendererDelayMs)); +} + +void RenderWidgetHost::ForwardEditCommand(const std::string& name, + const std::string& value) { + // We don't need an implementation of this function here since the + // only place we use this is for the case of dropdown menus and other + // edge cases for which edit commands don't make sense. +} + +void RenderWidgetHost::ForwardEditCommandsForNextKeyEvent( + const EditCommands& edit_commands) { + // We don't need an implementation of this function here since this message is + // only handled by RenderView. +} + +#if defined(TOUCH_UI) +void RenderWidgetHost::ForwardTouchEvent( + const WebKit::WebTouchEvent& touch_event) { + ForwardInputEvent(touch_event, sizeof(WebKit::WebTouchEvent), false); +} +#endif + +void RenderWidgetHost::RendererExited(base::TerminationStatus status, + int exit_code) { + // Clearing this flag causes us to re-create the renderer when recovering + // from a crashed renderer. + renderer_initialized_ = false; + + // Must reset these to ensure that mouse move/wheel events work with a new + // renderer. + mouse_move_pending_ = false; + next_mouse_move_.reset(); + mouse_wheel_pending_ = false; + coalesced_mouse_wheel_events_.clear(); + + // Must reset these to ensure that keyboard events work with a new renderer. + key_queue_.clear(); + suppress_next_char_events_ = false; + + // Reset some fields in preparation for recovering from a crash. + resize_ack_pending_ = false; + repaint_ack_pending_ = false; + + in_flight_size_.SetSize(0, 0); + in_flight_reserved_rect_.SetRect(0, 0, 0, 0); + current_size_.SetSize(0, 0); + current_reserved_rect_.SetRect(0, 0, 0, 0); + is_hidden_ = false; + is_accelerated_compositing_active_ = false; + + if (view_) { + view_->RenderViewGone(status, exit_code); + view_ = NULL; // The View should be deleted by RenderViewGone. + } + + BackingStoreManager::RemoveBackingStore(this); +} + +void RenderWidgetHost::UpdateTextDirection(WebTextDirection direction) { + text_direction_updated_ = true; + text_direction_ = direction; +} + +void RenderWidgetHost::CancelUpdateTextDirection() { + if (text_direction_updated_) + text_direction_canceled_ = true; +} + +void RenderWidgetHost::NotifyTextDirection() { + if (text_direction_updated_) { + if (!text_direction_canceled_) + Send(new ViewMsg_SetTextDirection(routing_id(), text_direction_)); + text_direction_updated_ = false; + text_direction_canceled_ = false; + } +} + +void RenderWidgetHost::SetInputMethodActive(bool activate) { + Send(new ViewMsg_SetInputMethodActive(routing_id(), activate)); +} + +void RenderWidgetHost::ImeSetComposition( + const string16& text, + const std::vector<WebKit::WebCompositionUnderline>& underlines, + int selection_start, + int selection_end) { + Send(new ViewMsg_ImeSetComposition( + routing_id(), text, underlines, selection_start, selection_end)); +} + +void RenderWidgetHost::ImeConfirmComposition(const string16& text) { + Send(new ViewMsg_ImeConfirmComposition(routing_id(), text)); +} + +void RenderWidgetHost::ImeConfirmComposition() { + Send(new ViewMsg_ImeConfirmComposition(routing_id(), string16())); +} + +void RenderWidgetHost::ImeCancelComposition() { + Send(new ViewMsg_ImeSetComposition(routing_id(), string16(), + std::vector<WebKit::WebCompositionUnderline>(), 0, 0)); +} + +void RenderWidgetHost::SetActive(bool active) { + Send(new ViewMsg_SetActive(routing_id(), active)); +} + +void RenderWidgetHost::Destroy() { + NotificationService::current()->Notify( + NotificationType::RENDER_WIDGET_HOST_DESTROYED, + Source<RenderWidgetHost>(this), + NotificationService::NoDetails()); + + // Tell the view to die. + // Note that in the process of the view shutting down, it can call a ton + // of other messages on us. So if you do any other deinitialization here, + // do it after this call to view_->Destroy(). + if (view_) + view_->Destroy(); + + delete this; +} + +void RenderWidgetHost::CheckRendererIsUnresponsive() { + // If we received a call to StopHangMonitorTimeout. + if (time_when_considered_hung_.is_null()) + return; + + // If we have not waited long enough, then wait some more. + Time now = Time::Now(); + if (now < time_when_considered_hung_) { + StartHangMonitorTimeout(time_when_considered_hung_ - now); + return; + } + + // OK, looks like we have a hung renderer! + NotificationService::current()->Notify( + NotificationType::RENDERER_PROCESS_HANG, + Source<RenderWidgetHost>(this), + NotificationService::NoDetails()); + is_unresponsive_ = true; + NotifyRendererUnresponsive(); +} + +void RenderWidgetHost::RendererIsResponsive() { + if (is_unresponsive_) { + is_unresponsive_ = false; + NotifyRendererResponsive(); + } +} + +void RenderWidgetHost::OnMsgRenderViewReady() { + WasResized(); +} + +void RenderWidgetHost::OnMsgRenderViewGone(int status, int exit_code) { + // TODO(evanm): This synchronously ends up calling "delete this". + // Is that really what we want in response to this message? I'm matching + // previous behavior of the code here. + Destroy(); +} + +void RenderWidgetHost::OnMsgClose() { + Shutdown(); +} + +void RenderWidgetHost::OnMsgRequestMove(const gfx::Rect& pos) { + // Note that we ignore the position. + if (view_) { + view_->SetSize(pos.size()); + Send(new ViewMsg_Move_ACK(routing_id_)); + } +} + +void RenderWidgetHost::OnMsgPaintAtSizeAck(int tag, const gfx::Size& size) { + PaintAtSizeAckDetails details = {tag, size}; + gfx::Size size_details = size; + NotificationService::current()->Notify( + NotificationType::RENDER_WIDGET_HOST_DID_RECEIVE_PAINT_AT_SIZE_ACK, + Source<RenderWidgetHost>(this), + Details<PaintAtSizeAckDetails>(&details)); +} + +void RenderWidgetHost::OnMsgUpdateRect( + const ViewHostMsg_UpdateRect_Params& params) { + TimeTicks paint_start = TimeTicks::Now(); + + NotificationService::current()->Notify( + NotificationType::RENDER_WIDGET_HOST_WILL_PAINT, + Source<RenderWidgetHost>(this), + NotificationService::NoDetails()); + + // Update our knowledge of the RenderWidget's size. + current_size_ = params.view_size; + // Update our knowledge of the RenderWidget's scroll offset. + last_scroll_offset_ = params.scroll_offset; + + bool is_resize_ack = + ViewHostMsg_UpdateRect_Flags::is_resize_ack(params.flags); + + // resize_ack_pending_ needs to be cleared before we call DidPaintRect, since + // that will end up reaching GetBackingStore. + if (is_resize_ack) { + DCHECK(resize_ack_pending_); + resize_ack_pending_ = false; + in_flight_size_.SetSize(0, 0); + in_flight_reserved_rect_.SetRect(0, 0, 0, 0); + // Update our knowledge of the RenderWidget's resizer rect. + // ViewMsg_Resize is acknowledged only when view size is actually changed, + // otherwise current_reserved_rect_ is updated immediately after sending + // ViewMsg_Resize to the RenderWidget and can be clobbered by + // OnMsgUpdateRect called for a paint that was initiated before the resize + // message was sent. + current_reserved_rect_ = params.resizer_rect; + } + + bool is_repaint_ack = + ViewHostMsg_UpdateRect_Flags::is_repaint_ack(params.flags); + if (is_repaint_ack) { + repaint_ack_pending_ = false; + TimeDelta delta = TimeTicks::Now() - repaint_start_time_; + UMA_HISTOGRAM_TIMES("MPArch.RWH_RepaintDelta", delta); + } + + DCHECK(!params.bitmap_rect.IsEmpty()); + DCHECK(!params.view_size.IsEmpty()); + + if (!is_accelerated_compositing_active_) { + const size_t size = params.bitmap_rect.height() * + params.bitmap_rect.width() * 4; + TransportDIB* dib = process_->GetTransportDIB(params.bitmap); + + // If gpu process does painting, scroll_rect and copy_rects are always empty + // and backing store is never used. + if (dib) { + if (dib->size() < size) { + DLOG(WARNING) << "Transport DIB too small for given rectangle"; + UserMetrics::RecordAction(UserMetricsAction( + "BadMessageTerminate_RWH1")); + process()->ReceivedBadMessage(); + } else { + // Scroll the backing store. + if (!params.scroll_rect.IsEmpty()) { + ScrollBackingStoreRect(params.dx, params.dy, + params.scroll_rect, + params.view_size); + } + + // Paint the backing store. This will update it with the + // renderer-supplied bits. The view will read out of the backing store + // later to actually draw to the screen. + PaintBackingStoreRect(params.bitmap, params.bitmap_rect, + params.copy_rects, params.view_size); + } + } + } + + // ACK early so we can prefetch the next PaintRect if there is a next one. + // This must be done AFTER we're done painting with the bitmap supplied by the + // renderer. This ACK is a signal to the renderer that the backing store can + // be re-used, so the bitmap may be invalid after this call. + Send(new ViewMsg_UpdateRect_ACK(routing_id_)); + + // We don't need to update the view if the view is hidden. We must do this + // early return after the ACK is sent, however, or the renderer will not send + // us more data. + if (is_hidden_) + return; + + // Now paint the view. Watch out: it might be destroyed already. + if (view_) { + view_->MovePluginWindows(params.plugin_window_moves); + // The view_ pointer could be destroyed in the context of MovePluginWindows + // which attempts to move the plugin windows and in the process could + // dispatch other window messages which could cause the view to be + // destroyed. + if (view_ && !is_accelerated_compositing_active_) { + view_being_painted_ = true; + view_->DidUpdateBackingStore(params.scroll_rect, params.dx, params.dy, + params.copy_rects); + view_being_painted_ = false; + } + } + + NotificationService::current()->Notify( + NotificationType::RENDER_WIDGET_HOST_DID_PAINT, + Source<RenderWidgetHost>(this), + NotificationService::NoDetails()); + + // If we got a resize ack, then perhaps we have another resize to send? + if (is_resize_ack && view_) { + // WasResized checks the current size and sends the resize update only + // when something was actually changed. + WasResized(); + } + + NotificationService::current()->Notify( + NotificationType::RENDER_WIDGET_HOST_DID_UPDATE_BACKING_STORE, + Source<RenderWidgetHost>(this), + NotificationService::NoDetails()); + + // Log the time delta for processing a paint message. + TimeDelta delta = TimeTicks::Now() - paint_start; + UMA_HISTOGRAM_TIMES("MPArch.RWH_OnMsgUpdateRect", delta); +} + +void RenderWidgetHost::OnMsgInputEventAck(const IPC::Message& message) { + // Log the time delta for processing an input event. + TimeDelta delta = TimeTicks::Now() - input_event_start_time_; + UMA_HISTOGRAM_TIMES("MPArch.RWH_InputEventDelta", delta); + + // Cancel pending hung renderer checks since the renderer is responsive. + StopHangMonitorTimeout(); + + void* iter = NULL; + int type = 0; + if (!message.ReadInt(&iter, &type) || (type < WebInputEvent::Undefined)) { + UserMetrics::RecordAction(UserMetricsAction("BadMessageTerminate_RWH2")); + process()->ReceivedBadMessage(); + } else if (type == WebInputEvent::MouseMove) { + mouse_move_pending_ = false; + + // now, we can send the next mouse move event + if (next_mouse_move_.get()) { + DCHECK(next_mouse_move_->type == WebInputEvent::MouseMove); + ForwardMouseEvent(*next_mouse_move_); + } + } else if (type == WebInputEvent::MouseWheel) { + ProcessWheelAck(); + } else if (WebInputEvent::isKeyboardEventType(type)) { + bool processed = false; + if (!message.ReadBool(&iter, &processed)) { + UserMetrics::RecordAction(UserMetricsAction("BadMessageTerminate_RWH3")); + process()->ReceivedBadMessage(); + } + + ProcessKeyboardEventAck(type, processed); + } + // This is used only for testing. + NotificationService::current()->Notify( + NotificationType::RENDER_WIDGET_HOST_DID_RECEIVE_INPUT_EVENT_ACK, + Source<RenderWidgetHost>(this), + Details<int>(&type)); +} + +void RenderWidgetHost::ProcessWheelAck() { + mouse_wheel_pending_ = false; + + // Now send the next (coalesced) mouse wheel event. + if (!coalesced_mouse_wheel_events_.empty()) { + WebMouseWheelEvent next_wheel_event = + coalesced_mouse_wheel_events_.front(); + coalesced_mouse_wheel_events_.pop_front(); + ForwardWheelEvent(next_wheel_event); + } +} + +void RenderWidgetHost::OnMsgFocus() { + // Only RenderViewHost can deal with that message. + UserMetrics::RecordAction(UserMetricsAction("BadMessageTerminate_RWH4")); + process()->ReceivedBadMessage(); +} + +void RenderWidgetHost::OnMsgBlur() { + // Only RenderViewHost can deal with that message. + UserMetrics::RecordAction(UserMetricsAction("BadMessageTerminate_RWH5")); + process()->ReceivedBadMessage(); +} + +void RenderWidgetHost::OnMsgSetCursor(const WebCursor& cursor) { + if (!view_) { + return; + } + view_->UpdateCursor(cursor); +} + +void RenderWidgetHost::OnMsgImeUpdateTextInputState( + WebKit::WebTextInputType type, + const gfx::Rect& caret_rect) { + if (view_) + view_->ImeUpdateTextInputState(type, caret_rect); +} + +void RenderWidgetHost::OnMsgImeCancelComposition() { + if (view_) + view_->ImeCancelComposition(); +} + +void RenderWidgetHost::OnMsgDidActivateAcceleratedCompositing(bool activated) { +#if defined(OS_MACOSX) + bool old_state = is_accelerated_compositing_active_; +#endif + is_accelerated_compositing_active_ = activated; +#if defined(OS_MACOSX) + if (old_state != is_accelerated_compositing_active_ && view_) + view_->GpuRenderingStateDidChange(); +#elif defined(OS_WIN) + if (view_) + view_->ShowCompositorHostWindow(is_accelerated_compositing_active_); +#elif defined(TOOLKIT_USES_GTK) + if (view_) + view_->AcceleratedCompositingActivated(activated); +#endif +} + +#if defined(OS_MACOSX) + +void RenderWidgetHost::OnMsgGetScreenInfo(gfx::NativeViewId view, + WebScreenInfo* results) { + gfx::NativeView native_view = view_ ? view_->GetNativeView() : NULL; + *results = WebScreenInfoFactory::screenInfo(native_view); +} + +void RenderWidgetHost::OnMsgGetWindowRect(gfx::NativeViewId window_id, + gfx::Rect* results) { + if (view_) { + *results = view_->GetViewBounds(); + } +} + +void RenderWidgetHost::OnMsgGetRootWindowRect(gfx::NativeViewId window_id, + gfx::Rect* results) { + if (view_) { + *results = view_->GetRootWindowRect(); + } +} + +void RenderWidgetHost::OnMsgPluginFocusChanged(bool focused, int plugin_id) { + if (view_) + view_->PluginFocusChanged(focused, plugin_id); +} + +void RenderWidgetHost::OnMsgStartPluginIme() { + if (view_) + view_->StartPluginIme(); +} + +void RenderWidgetHost::OnAllocateFakePluginWindowHandle( + bool opaque, + bool root, + gfx::PluginWindowHandle* id) { + // TODO(kbr): similar potential issue here as in OnMsgCreatePluginContainer. + // Possibly less of an issue because this is only used for the GPU plugin. + if (view_) { + *id = view_->AllocateFakePluginWindowHandle(opaque, root); + } else { + NOTIMPLEMENTED(); + } +} + +void RenderWidgetHost::OnDestroyFakePluginWindowHandle( + gfx::PluginWindowHandle id) { + if (view_) { + view_->DestroyFakePluginWindowHandle(id); + } else { + NOTIMPLEMENTED(); + } +} + +void RenderWidgetHost::OnAcceleratedSurfaceSetIOSurface( + gfx::PluginWindowHandle window, + int32 width, + int32 height, + uint64 mach_port) { + if (view_) { + view_->AcceleratedSurfaceSetIOSurface(window, width, height, mach_port); + } +} + +void RenderWidgetHost::OnAcceleratedSurfaceSetTransportDIB( + gfx::PluginWindowHandle window, + int32 width, + int32 height, + TransportDIB::Handle transport_dib) { + if (view_) { + view_->AcceleratedSurfaceSetTransportDIB(window, width, height, + transport_dib); + } +} + +void RenderWidgetHost::OnAcceleratedSurfaceBuffersSwapped( + gfx::PluginWindowHandle window, uint64 surface_id) { + if (view_) { + // This code path could be updated to implement flow control for + // updating of accelerated plugins as well. However, if we add support + // for composited plugins then this is not necessary. + view_->AcceleratedSurfaceBuffersSwapped(window, surface_id, + 0, 0, 0); + } +} +#elif defined(OS_POSIX) + +void RenderWidgetHost::OnMsgCreatePluginContainer(gfx::PluginWindowHandle id) { + // TODO(piman): view_ can only be NULL with delayed view creation in + // extensions (see ExtensionHost::CreateRenderViewSoon). Figure out how to + // support plugins in that case. + if (view_) { + view_->CreatePluginContainer(id); + } else { + deferred_plugin_handles_.push_back(id); + } +} + +void RenderWidgetHost::OnMsgDestroyPluginContainer(gfx::PluginWindowHandle id) { + if (view_) { + view_->DestroyPluginContainer(id); + } else { + for (int i = 0; + i < static_cast<int>(deferred_plugin_handles_.size()); + i++) { + if (deferred_plugin_handles_[i] == id) { + deferred_plugin_handles_.erase(deferred_plugin_handles_.begin() + i); + i--; + } + } + } +} +#endif + +void RenderWidgetHost::PaintBackingStoreRect( + TransportDIB::Id bitmap, + const gfx::Rect& bitmap_rect, + const std::vector<gfx::Rect>& copy_rects, + const gfx::Size& view_size) { + // The view may be destroyed already. + if (!view_) + return; + + if (is_hidden_) { + // Don't bother updating the backing store when we're hidden. Just mark it + // as being totally invalid. This will cause a complete repaint when the + // view is restored. + needs_repainting_on_restore_ = true; + return; + } + + bool needs_full_paint = false; + BackingStoreManager::PrepareBackingStore(this, view_size, bitmap, bitmap_rect, + copy_rects, &needs_full_paint); + if (needs_full_paint) { + repaint_start_time_ = TimeTicks::Now(); + repaint_ack_pending_ = true; + Send(new ViewMsg_Repaint(routing_id_, view_size)); + } +} + +void RenderWidgetHost::ScrollBackingStoreRect(int dx, int dy, + const gfx::Rect& clip_rect, + const gfx::Size& view_size) { + if (is_hidden_) { + // Don't bother updating the backing store when we're hidden. Just mark it + // as being totally invalid. This will cause a complete repaint when the + // view is restored. + needs_repainting_on_restore_ = true; + return; + } + + // TODO(darin): do we need to do something else if our backing store is not + // the same size as the advertised view? maybe we just assume there is a + // full paint on its way? + BackingStore* backing_store = BackingStoreManager::Lookup(this); + if (!backing_store || (backing_store->size() != view_size)) + return; + backing_store->ScrollBackingStore(dx, dy, clip_rect, view_size); +} + +void RenderWidgetHost::ToggleSpellPanel(bool is_currently_visible) { + Send(new ViewMsg_ToggleSpellPanel(routing_id(), is_currently_visible)); +} + +void RenderWidgetHost::Replace(const string16& word) { + Send(new ViewMsg_Replace(routing_id_, word)); +} + +void RenderWidgetHost::AdvanceToNextMisspelling() { + Send(new ViewMsg_AdvanceToNextMisspelling(routing_id_)); +} + +void RenderWidgetHost::EnableRendererAccessibility() { + if (renderer_accessible_) + return; + + if (CommandLine::ForCurrentProcess()->HasSwitch( + switches::kDisableRendererAccessibility)) { + return; + } + + renderer_accessible_ = true; + + if (process_->HasConnection()) { + // Renderer accessibility wasn't enabled on process launch. Enable it now. + Send(new ViewMsg_EnableAccessibility(routing_id())); + } +} + +void RenderWidgetHost::SetAccessibilityFocus(int acc_obj_id) { + Send(new ViewMsg_SetAccessibilityFocus(routing_id(), acc_obj_id)); +} + +void RenderWidgetHost::AccessibilityDoDefaultAction(int acc_obj_id) { + Send(new ViewMsg_AccessibilityDoDefaultAction(routing_id(), acc_obj_id)); +} + +void RenderWidgetHost::AccessibilityNotificationsAck() { + Send(new ViewMsg_AccessibilityNotifications_ACK(routing_id())); +} + +void RenderWidgetHost::ProcessKeyboardEventAck(int type, bool processed) { + if (key_queue_.size() == 0) { + LOG(ERROR) << "Got a KeyEvent back from the renderer but we " + << "don't seem to have sent it to the renderer!"; + } else if (key_queue_.front().type != type) { + LOG(ERROR) << "We seem to have a different key type sent from " + << "the renderer. (" << key_queue_.front().type << " vs. " + << type << "). Ignoring event."; + + // Something must be wrong. Clear the |key_queue_| and + // |suppress_next_char_events_| so that we can resume from the error. + key_queue_.clear(); + suppress_next_char_events_ = false; + } else { + NativeWebKeyboardEvent front_item = key_queue_.front(); + key_queue_.pop_front(); + +#if defined(OS_MACOSX) + if (!is_hidden_ && view_->PostProcessEventForPluginIme(front_item)) + return; +#endif + + // We only send unprocessed key event upwards if we are not hidden, + // because the user has moved away from us and no longer expect any effect + // of this key event. + if (!processed && !is_hidden_ && !front_item.skip_in_browser) { + UnhandledKeyboardEvent(front_item); + + // WARNING: This RenderWidgetHost can be deallocated at this point + // (i.e. in the case of Ctrl+W, where the call to + // UnhandledKeyboardEvent destroys this RenderWidgetHost). + } + } +} + +void RenderWidgetHost::ActivateDeferredPluginHandles() { + if (view_ == NULL) + return; + + for (int i = 0; i < static_cast<int>(deferred_plugin_handles_.size()); i++) { +#if defined(TOOLKIT_USES_GTK) + view_->CreatePluginContainer(deferred_plugin_handles_[i]); +#endif + } + + deferred_plugin_handles_.clear(); +} diff --git a/content/browser/renderer_host/render_widget_host.h b/content/browser/renderer_host/render_widget_host.h new file mode 100644 index 0000000..d3598dd --- /dev/null +++ b/content/browser/renderer_host/render_widget_host.h @@ -0,0 +1,677 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CONTENT_BROWSER_RENDERER_HOST_RENDER_WIDGET_HOST_H_ +#define CONTENT_BROWSER_RENDERER_HOST_RENDER_WIDGET_HOST_H_ +#pragma once + +#include <deque> +#include <string> +#include <vector> + +#include "app/surface/transport_dib.h" +#include "base/gtest_prod_util.h" +#include "base/process_util.h" +#include "base/scoped_ptr.h" +#include "base/string16.h" +#include "base/timer.h" +#include "chrome/common/edit_command.h" +#include "chrome/common/native_web_keyboard_event.h" +#include "chrome/common/property_bag.h" +#include "ipc/ipc_channel.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebInputEvent.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebTextDirection.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebTextInputType.h" +#include "ui/gfx/native_widget_types.h" +#include "ui/gfx/rect.h" +#include "ui/gfx/size.h" + +namespace gfx { +class Rect; +} + +namespace WebKit { +class WebInputEvent; +class WebMouseEvent; +struct WebCompositionUnderline; +struct WebScreenInfo; +} + +class BackingStore; +class PaintObserver; +class RenderProcessHost; +class RenderWidgetHostView; +class TransportDIB; +class WebCursor; +struct ViewHostMsg_UpdateRect_Params; + +// This class manages the browser side of a browser<->renderer HWND connection. +// The HWND lives in the browser process, and windows events are sent over +// IPC to the corresponding object in the renderer. The renderer paints into +// shared memory, which we transfer to a backing store and blit to the screen +// when Windows sends us a WM_PAINT message. +// +// How Shutdown Works +// +// There are two situations in which this object, a RenderWidgetHost, can be +// instantiated: +// +// 1. By a TabContents as the communication conduit for a rendered web page. +// The TabContents instantiates a derived class: RenderViewHost. +// 2. By a TabContents as the communication conduit for a select widget. The +// TabContents instantiates the RenderWidgetHost directly. +// +// For every TabContents there are several objects in play that need to be +// properly destroyed or cleaned up when certain events occur. +// +// - TabContents - the TabContents itself, and its associated HWND. +// - RenderViewHost - representing the communication conduit with the child +// process. +// - RenderWidgetHostView - the view of the web page content, message handler, +// and plugin root. +// +// Normally, the TabContents contains a child RenderWidgetHostView that renders +// the contents of the loaded page. It has a WS_CLIPCHILDREN style so that it +// does no painting of its own. +// +// The lifetime of the RenderWidgetHostView is tied to the render process. If +// the render process dies, the RenderWidgetHostView goes away and all +// references to it must become NULL. If the TabContents finds itself without a +// RenderWidgetHostView, it paints Sad Tab instead. +// +// RenderViewHost (a RenderWidgetHost subclass) is the conduit used to +// communicate with the RenderView and is owned by the TabContents. If the +// render process crashes, the RenderViewHost remains and restarts the render +// process if needed to continue navigation. +// +// The TabContents is itself owned by the NavigationController in which it +// resides. +// +// Some examples of how shutdown works: +// +// When a tab is closed (either by the user, the web page calling window.close, +// etc) the TabStrip destroys the associated NavigationController, which calls +// Destroy on each TabContents it owns. +// +// For a TabContents, its Destroy method tells the RenderViewHost to +// shut down the render process and die. +// +// When the render process is destroyed it destroys the View: the +// RenderWidgetHostView, which destroys its HWND and deletes that object. +// +// For select popups, the situation is a little different. The RenderWidgetHost +// associated with the select popup owns the view and itself (is responsible +// for destroying itself when the view is closed). The TabContents's only +// responsibility is to select popups is to create them when it is told to. When +// the View is destroyed via an IPC message (for when WebCore destroys the +// popup, e.g. if the user selects one of the options), or because +// WM_CANCELMODE is received by the view, the View schedules the destruction of +// the render process. However in this case since there's no TabContents +// container, when the render process is destroyed, the RenderWidgetHost just +// deletes itself, which is safe because no one else should have any references +// to it (the TabContents does not). +// +// It should be noted that the RenderViewHost, not the RenderWidgetHost, +// handles IPC messages relating to the render process going away, since the +// way a RenderViewHost (TabContents) handles the process dying is different to +// the way a select popup does. As such the RenderWidgetHostView handles these +// messages for select popups. This placement is more out of convenience than +// anything else. When the view is live, these messages are forwarded to it by +// the RenderWidgetHost's IPC message map. +// +class RenderWidgetHost : public IPC::Channel::Listener, + public IPC::Channel::Sender { + public: + // Used as the details object for a + // RENDER_WIDGET_HOST_DID_RECEIVE_PAINT_AT_SIZE_ACK notification. + struct PaintAtSizeAckDetails { + // The tag that was passed to the PaintAtSize() call that triggered this + // ack. + int tag; + gfx::Size size; + }; + + // routing_id can be MSG_ROUTING_NONE, in which case the next available + // routing id is taken from the RenderProcessHost. + RenderWidgetHost(RenderProcessHost* process, int routing_id); + virtual ~RenderWidgetHost(); + + // Gets/Sets the View of this RenderWidgetHost. Can be NULL, e.g. if the + // RenderWidget is being destroyed or the render process crashed. You should + // never cache this pointer since it can become NULL if the renderer crashes, + // instead you should always ask for it using the accessor. + void set_view(RenderWidgetHostView* view) { view_ = view; } + RenderWidgetHostView* view() const { return view_; } + + RenderProcessHost* process() const { return process_; } + int routing_id() const { return routing_id_; } + bool renderer_accessible() { return renderer_accessible_; } + + // Returns the property bag for this widget, where callers can add extra data + // they may wish to associate with it. Returns a pointer rather than a + // reference since the PropertyAccessors expect this. + const PropertyBag* property_bag() const { return &property_bag_; } + PropertyBag* property_bag() { return &property_bag_; } + + // Called when a renderer object already been created for this host, and we + // just need to be attached to it. Used for window.open, <select> dropdown + // menus, and other times when the renderer initiates creating an object. + void Init(); + + // Tells the renderer to die and then calls Destroy(). + virtual void Shutdown(); + + // Manual RTTI FTW. We are not hosting a web page. + virtual bool IsRenderView() const; + + // IPC::Channel::Listener + virtual bool OnMessageReceived(const IPC::Message& msg); + + // Sends a message to the corresponding object in the renderer. + virtual bool Send(IPC::Message* msg); + + // Called to notify the RenderWidget that it has been hidden or restored from + // having been hidden. + void WasHidden(); + void WasRestored(); + + // Called to notify the RenderWidget that it has been resized. + void WasResized(); + + // Called to notify the RenderWidget that its associated native window got + // focused. + virtual void GotFocus(); + + // Tells the renderer it got/lost focus. + void Focus(); + void Blur(); + virtual void LostCapture(); + + // Tells us whether the page is rendered directly via the GPU process. + bool is_accelerated_compositing_active() { + return is_accelerated_compositing_active_; + } + + // Notifies the RenderWidgetHost that the View was destroyed. + void ViewDestroyed(); + + // Indicates if the page has finished loading. + void SetIsLoading(bool is_loading); + + // This tells the renderer to paint into a bitmap and return it, + // regardless of whether the tab is hidden or not. It resizes the + // web widget to match the |page_size| and then returns the bitmap + // scaled so it matches the |desired_size|, so that the scaling + // happens on the rendering thread. When the bitmap is ready, the + // renderer sends a PaintAtSizeACK to this host, and a + // RENDER_WIDGET_HOST_DID_RECEIVE_PAINT_AT_SIZE_ACK notification is issued. + // Note that this bypasses most of the update logic that is normally invoked, + // and doesn't put the results into the backing store. + void PaintAtSize(TransportDIB::Handle dib_handle, + int tag, + const gfx::Size& page_size, + const gfx::Size& desired_size); + + // Get access to the widget's backing store. If a resize is in progress, + // then the current size of the backing store may be less than the size of + // the widget's view. If you pass |force_create| as true, then the backing + // store will be created if it doesn't exist. Otherwise, NULL will be returned + // if the backing store doesn't already exist. It will also return NULL if the + // backing store could not be created. + BackingStore* GetBackingStore(bool force_create); + + // Allocate a new backing store of the given size. Returns NULL on failure + // (for example, if we don't currently have a RenderWidgetHostView.) + BackingStore* AllocBackingStore(const gfx::Size& size); + + // When a backing store does asynchronous painting, it will call this function + // when it is done with the DIB. We will then forward a message to the + // renderer to send another paint. + void DonePaintingToBackingStore(); + + // GPU accelerated version of GetBackingStore function. This will + // trigger a re-composite to the view. If a resize is pending, it will + // block briefly waiting for an ack from the renderer. + void ScheduleComposite(); + + // Starts a hang monitor timeout. If there's already a hang monitor timeout + // the new one will only fire if it has a shorter delay than the time + // left on the existing timeouts. + void StartHangMonitorTimeout(base::TimeDelta delay); + + // Restart the active hang monitor timeout. Clears all existing timeouts and + // starts with a new one. This can be because the renderer has become + // active, the tab is being hidden, or the user has chosen to wait some more + // to give the tab a chance to become active and we don't want to display a + // warning too soon. + void RestartHangMonitorTimeout(); + + // Stops all existing hang monitor timeouts and assumes the renderer is + // responsive. + void StopHangMonitorTimeout(); + + // Called when the system theme changes. At this time all existing native + // theme handles are invalid and the renderer must obtain new ones and + // repaint. + void SystemThemeChanged(); + + // Forwards the given message to the renderer. These are called by the view + // when it has received a message. + virtual void ForwardMouseEvent(const WebKit::WebMouseEvent& mouse_event); + // Called when a mouse click activates the renderer. + virtual void OnMouseActivate(); + void ForwardWheelEvent(const WebKit::WebMouseWheelEvent& wheel_event); + virtual void ForwardKeyboardEvent(const NativeWebKeyboardEvent& key_event); + virtual void ForwardEditCommand(const std::string& name, + const std::string& value); + virtual void ForwardEditCommandsForNextKeyEvent( + const EditCommands& edit_commands); +#if defined(TOUCH_UI) + virtual void ForwardTouchEvent(const WebKit::WebTouchEvent& touch_event); +#endif + + + // Update the text direction of the focused input element and notify it to a + // renderer process. + // These functions have two usage scenarios: changing the text direction + // from a menu (as Safari does), and; changing the text direction when a user + // presses a set of keys (as IE and Firefox do). + // 1. Change the text direction from a menu. + // In this scenario, we receive a menu event only once and we should update + // the text direction immediately when a user chooses a menu item. So, we + // should call both functions at once as listed in the following snippet. + // void RenderViewHost::SetTextDirection(WebTextDirection direction) { + // UpdateTextDirection(direction); + // NotifyTextDirection(); + // } + // 2. Change the text direction when pressing a set of keys. + // Because of auto-repeat, we may receive the same key-press event many + // times while we presses the keys and it is nonsense to send the same IPC + // message every time when we receive a key-press event. + // To suppress the number of IPC messages, we just update the text direction + // when receiving a key-press event and send an IPC message when we release + // the keys as listed in the following snippet. + // if (key_event.type == WebKeyboardEvent::KEY_DOWN) { + // if (key_event.windows_key_code == 'A' && + // key_event.modifiers == WebKeyboardEvent::CTRL_KEY) { + // UpdateTextDirection(dir); + // } else { + // CancelUpdateTextDirection(); + // } + // } else if (key_event.type == WebKeyboardEvent::KEY_UP) { + // NotifyTextDirection(); + // } + // Once we cancel updating the text direction, we have to ignore all + // succeeding UpdateTextDirection() requests until calling + // NotifyTextDirection(). (We may receive keydown events even after we + // canceled updating the text direction because of auto-repeat.) + // Note: we cannot undo this change for compatibility with Firefox and IE. + void UpdateTextDirection(WebKit::WebTextDirection direction); + void CancelUpdateTextDirection(); + void NotifyTextDirection(); + + // Notifies the renderer whether or not the input method attached to this + // process is activated. + // When the input method is activated, a renderer process sends IPC messages + // to notify the status of its composition node. (This message is mainly used + // for notifying the position of the input cursor so that the browser can + // display input method windows under the cursor.) + void SetInputMethodActive(bool activate); + + // Update the composition node of the renderer (or WebKit). + // WebKit has a special node (a composition node) for input method to change + // its text without affecting any other DOM nodes. When the input method + // (attached to the browser) updates its text, the browser sends IPC messages + // to update the composition node of the renderer. + // (Read the comments of each function for its detail.) + + // Sets the text of the composition node. + // This function can also update the cursor position and mark the specified + // range in the composition node. + // A browser should call this function: + // * when it receives a WM_IME_COMPOSITION message with a GCS_COMPSTR flag + // (on Windows); + // * when it receives a "preedit_changed" signal of GtkIMContext (on Linux); + // * when markedText of NSTextInput is called (on Mac). + void ImeSetComposition( + const string16& text, + const std::vector<WebKit::WebCompositionUnderline>& underlines, + int selection_start, + int selection_end); + + // Finishes an ongoing composition with the specified text. + // A browser should call this function: + // * when it receives a WM_IME_COMPOSITION message with a GCS_RESULTSTR flag + // (on Windows); + // * when it receives a "commit" signal of GtkIMContext (on Linux); + // * when insertText of NSTextInput is called (on Mac). + void ImeConfirmComposition(const string16& text); + + // Finishes an ongoing composition with the composition text set by last + // SetComposition() call. + void ImeConfirmComposition(); + + // Cancels an ongoing composition. + void ImeCancelComposition(); + + // Makes an IPC call to toggle the spelling panel. + void ToggleSpellPanel(bool is_currently_visible); + + // Makes an IPC call to tell webkit to replace the currently selected word + // or a word around the cursor. + void Replace(const string16& word); + + // Makes an IPC call to tell webkit to advance to the next misspelling. + void AdvanceToNextMisspelling(); + + // Enable renderer accessibility. This should only be called when a + // screenreader is detected. + void EnableRendererAccessibility(); + + // Relays a request from assistive technology to set focus to the + // node with this accessibility object id. + void SetAccessibilityFocus(int acc_obj_id); + + // Relays a request from assistive technology to perform the default action + // on a node with this accessibility object id. + void AccessibilityDoDefaultAction(int acc_obj_id); + + // Acknowledges a ViewHostMsg_AccessibilityNotifications message. + void AccessibilityNotificationsAck(); + + // Sets the active state (i.e., control tints). + virtual void SetActive(bool active); + + void set_ignore_input_events(bool ignore_input_events) { + ignore_input_events_ = ignore_input_events; + } + bool ignore_input_events() const { + return ignore_input_events_; + } + + // Activate deferred plugin handles. + void ActivateDeferredPluginHandles(); + + const gfx::Point& last_scroll_offset() const { return last_scroll_offset_; } + + protected: + // Internal implementation of the public Forward*Event() methods. + void ForwardInputEvent(const WebKit::WebInputEvent& input_event, + int event_size, bool is_keyboard_shortcut); + + // Called when we receive a notification indicating that the renderer + // process has gone. This will reset our state so that our state will be + // consistent if a new renderer is created. + void RendererExited(base::TerminationStatus status, int exit_code); + + // Retrieves an id the renderer can use to refer to its view. + // This is used for various IPC messages, including plugins. + gfx::NativeViewId GetNativeViewId(); + + // Called to handled a keyboard event before sending it to the renderer. + // This is overridden by RenderView to send upwards to its delegate. + // Returns true if the event was handled, and then the keyboard event will + // not be sent to the renderer anymore. Otherwise, if the |event| would + // be handled in HandleKeyboardEvent() method as a normal keyboard shortcut, + // |*is_keyboard_shortcut| should be set to true. + virtual bool PreHandleKeyboardEvent(const NativeWebKeyboardEvent& event, + bool* is_keyboard_shortcut); + + // Called when a keyboard event was not processed by the renderer. This is + // overridden by RenderView to send upwards to its delegate. + virtual void UnhandledKeyboardEvent(const NativeWebKeyboardEvent& event) {} + + // Notification that the user has made some kind of input that could + // perform an action. The render view host overrides this to forward the + // information to its delegate (see corresponding function in + // RenderViewHostDelegate). The gestures that count are 1) any mouse down + // event and 2) enter or space key presses. + virtual void OnUserGesture() {} + + // Callbacks for notification when the renderer becomes unresponsive to user + // input events, and subsequently responsive again. RenderViewHost overrides + // these to tell its delegate to show the user a warning. + virtual void NotifyRendererUnresponsive() {} + virtual void NotifyRendererResponsive() {} + + protected: + // true if a renderer has once been valid. We use this flag to display a sad + // tab only when we lose our renderer and not if a paint occurs during + // initialization. + bool renderer_initialized_; + + private: + FRIEND_TEST_ALL_PREFIXES(RenderWidgetHostTest, Resize); + FRIEND_TEST_ALL_PREFIXES(RenderWidgetHostTest, ResizeThenCrash); + FRIEND_TEST_ALL_PREFIXES(RenderWidgetHostTest, HiddenPaint); + FRIEND_TEST_ALL_PREFIXES(RenderWidgetHostTest, PaintAtSize); + + // Tell this object to destroy itself. + void Destroy(); + + // Checks whether the renderer is hung and calls NotifyRendererUnresponsive + // if it is. + void CheckRendererIsUnresponsive(); + + // Called if we know the renderer is responsive. When we currently think the + // renderer is unresponsive, this will clear that state and call + // NotifyRendererResponsive. + void RendererIsResponsive(); + + // IPC message handlers + void OnMsgRenderViewReady(); + void OnMsgRenderViewGone(int status, int error_code); + void OnMsgClose(); + void OnMsgRequestMove(const gfx::Rect& pos); + void OnMsgPaintAtSizeAck(int tag, const gfx::Size& size); + void OnMsgUpdateRect(const ViewHostMsg_UpdateRect_Params& params); + void OnMsgInputEventAck(const IPC::Message& message); + virtual void OnMsgFocus(); + virtual void OnMsgBlur(); + + void OnMsgSetCursor(const WebCursor& cursor); + void OnMsgImeUpdateTextInputState(WebKit::WebTextInputType type, + const gfx::Rect& caret_rect); + void OnMsgImeCancelComposition(); + + void OnMsgDidActivateAcceleratedCompositing(bool activated); + +#if defined(OS_MACOSX) + void OnMsgGetScreenInfo(gfx::NativeViewId view, + WebKit::WebScreenInfo* results); + void OnMsgGetWindowRect(gfx::NativeViewId window_id, gfx::Rect* results); + void OnMsgGetRootWindowRect(gfx::NativeViewId window_id, gfx::Rect* results); + void OnMsgPluginFocusChanged(bool focused, int plugin_id); + void OnMsgStartPluginIme(); + void OnAllocateFakePluginWindowHandle(bool opaque, + bool root, + gfx::PluginWindowHandle* id); + void OnDestroyFakePluginWindowHandle(gfx::PluginWindowHandle id); + void OnAcceleratedSurfaceSetIOSurface(gfx::PluginWindowHandle window, + int32 width, + int32 height, + uint64 mach_port); + void OnAcceleratedSurfaceSetTransportDIB(gfx::PluginWindowHandle window, + int32 width, + int32 height, + TransportDIB::Handle transport_dib); + void OnAcceleratedSurfaceBuffersSwapped(gfx::PluginWindowHandle window, + uint64 surface_id); +#elif defined(OS_POSIX) + void OnMsgCreatePluginContainer(gfx::PluginWindowHandle id); + void OnMsgDestroyPluginContainer(gfx::PluginWindowHandle id); +#endif + + // Paints the given bitmap to the current backing store at the given location. + void PaintBackingStoreRect(TransportDIB::Id bitmap, + const gfx::Rect& bitmap_rect, + const std::vector<gfx::Rect>& copy_rects, + const gfx::Size& view_size); + + // Scrolls the given |clip_rect| in the backing by the given dx/dy amount. The + // |dib| and its corresponding location |bitmap_rect| in the backing store + // is the newly painted pixels by the renderer. + void ScrollBackingStoreRect(int dx, int dy, const gfx::Rect& clip_rect, + const gfx::Size& view_size); + + // Called by OnMsgInputEventAck() to process a keyboard event ack message. + void ProcessKeyboardEventAck(int type, bool processed); + + // Called by OnMsgInputEventAck() to process a wheel event ack message. + // This could result in a task being posted to allow additional wheel + // input messages to be coalesced. + void ProcessWheelAck(); + + // True if renderer accessibility is enabled. This should only be set when a + // screenreader is detected as it can potentially slow down Chrome. + bool renderer_accessible_; + + // The View associated with the RenderViewHost. The lifetime of this object + // is associated with the lifetime of the Render process. If the Renderer + // crashes, its View is destroyed and this pointer becomes NULL, even though + // render_view_host_ lives on to load another URL (creating a new View while + // doing so). + RenderWidgetHostView* view_; + + // Created during construction but initialized during Init*(). Therefore, it + // is guaranteed never to be NULL, but its channel may be NULL if the + // renderer crashed, so you must always check that. + RenderProcessHost* process_; + + // Stores random bits of data for others to associate with this object. + PropertyBag property_bag_; + + // The ID of the corresponding object in the Renderer Instance. + int routing_id_; + + // Indicates whether a page is loading or not. + bool is_loading_; + + // Indicates whether a page is hidden or not. + bool is_hidden_; + + // True when a page is rendered directly via the GPU process. + bool is_accelerated_compositing_active_; + + // Set if we are waiting for a repaint ack for the view. + bool repaint_ack_pending_; + + // True when waiting for RESIZE_ACK. + bool resize_ack_pending_; + + // The current size of the RenderWidget. + gfx::Size current_size_; + + // The current reserved area of the RenderWidget where contents should not be + // rendered to draw the resize corner, sidebar mini tabs etc. + gfx::Rect current_reserved_rect_; + + // The size we last sent as requested size to the renderer. |current_size_| + // is only updated once the resize message has been ack'd. This on the other + // hand is updated when the resize message is sent. This is very similar to + // |resize_ack_pending_|, but the latter is not set if the new size has width + // or height zero, which is why we need this too. + gfx::Size in_flight_size_; + + // The reserved area we last sent to the renderer. |current_reserved_rect_| + // is only updated once the resize message has been ack'd. This on the other + // hand is updated when the resize message is sent. + gfx::Rect in_flight_reserved_rect_; + + // True if a mouse move event was sent to the render view and we are waiting + // for a corresponding ViewHostMsg_HandleInputEvent_ACK message. + bool mouse_move_pending_; + + // The next mouse move event to send (only non-null while mouse_move_pending_ + // is true). + scoped_ptr<WebKit::WebMouseEvent> next_mouse_move_; + + // (Similar to |mouse_move_pending_|.) True if a mouse wheel event was sent + // and we are waiting for a corresponding ack. + bool mouse_wheel_pending_; + + typedef std::deque<WebKit::WebMouseWheelEvent> WheelEventQueue; + + // (Similar to |next_mouse_move_|.) The next mouse wheel events to send. + // Unlike mouse moves, mouse wheel events received while one is pending are + // coalesced (by accumulating deltas) if they match the previous event in + // modifiers. On the Mac, in particular, mouse wheel events are received at a + // high rate; not waiting for the ack results in jankiness, and using the same + // mechanism as for mouse moves (just dropping old events when multiple ones + // would be queued) results in very slow scrolling. + WheelEventQueue coalesced_mouse_wheel_events_; + + // The time when an input event was sent to the RenderWidget. + base::TimeTicks input_event_start_time_; + + // If true, then we should repaint when restoring even if we have a + // backingstore. This flag is set to true if we receive a paint message + // while is_hidden_ to true. Even though we tell the render widget to hide + // itself, a paint message could already be in flight at that point. + bool needs_repainting_on_restore_; + + // This is true if the renderer is currently unresponsive. + bool is_unresponsive_; + + // The following value indicates a time in the future when we would consider + // the renderer hung if it does not generate an appropriate response message. + base::Time time_when_considered_hung_; + + // This timer runs to check if time_when_considered_hung_ has past. + base::OneShotTimer<RenderWidgetHost> hung_renderer_timer_; + + // Flag to detect recursive calls to GetBackingStore(). + bool in_get_backing_store_; + + // Set when we call DidPaintRect/DidScrollRect on the view. + bool view_being_painted_; + + // Used for UMA histogram logging to measure the time for a repaint view + // operation to finish. + base::TimeTicks repaint_start_time_; + + // Queue of keyboard events that we need to track. + typedef std::deque<NativeWebKeyboardEvent> KeyQueue; + + // A queue of keyboard events. We can't trust data from the renderer so we + // stuff key events into a queue and pop them out on ACK, feeding our copy + // back to whatever unhandled handler instead of the returned version. + KeyQueue key_queue_; + + // Set to true if we shouldn't send input events from the render widget. + bool ignore_input_events_; + + // Set when we update the text direction of the selected input element. + bool text_direction_updated_; + WebKit::WebTextDirection text_direction_; + + // Set when we cancel updating the text direction. + // This flag also ignores succeeding update requests until we call + // NotifyTextDirection(). + bool text_direction_canceled_; + + // Indicates if the next sequence of Char events should be suppressed or not. + // System may translate a RawKeyDown event into zero or more Char events, + // usually we send them to the renderer directly in sequence. However, If a + // RawKeyDown event was not handled by the renderer but was handled by + // our UnhandledKeyboardEvent() method, e.g. as an accelerator key, then we + // shall not send the following sequence of Char events, which was generated + // by this RawKeyDown event, to the renderer. Otherwise the renderer may + // handle the Char events and cause unexpected behavior. + // For example, pressing alt-2 may let the browser switch to the second tab, + // but the Char event generated by alt-2 may also activate a HTML element + // if its accesskey happens to be "2", then the user may get confused when + // switching back to the original tab, because the content may already be + // changed. + bool suppress_next_char_events_; + + std::vector<gfx::PluginWindowHandle> deferred_plugin_handles_; + + // The last scroll offset of the render widget. + gfx::Point last_scroll_offset_; + + DISALLOW_COPY_AND_ASSIGN(RenderWidgetHost); +}; + +#endif // CONTENT_BROWSER_RENDERER_HOST_RENDER_WIDGET_HOST_H_ diff --git a/content/browser/renderer_host/render_widget_host_unittest.cc b/content/browser/renderer_host/render_widget_host_unittest.cc new file mode 100644 index 0000000..546854e --- /dev/null +++ b/content/browser/renderer_host/render_widget_host_unittest.cc @@ -0,0 +1,734 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/basictypes.h" +#include "base/scoped_ptr.h" +#include "base/shared_memory.h" +#include "base/timer.h" +#include "build/build_config.h" +#include "chrome/browser/renderer_host/backing_store.h" +#include "chrome/browser/renderer_host/test/test_render_view_host.h" +#include "chrome/common/notification_details.h" +#include "chrome/common/notification_source.h" +#include "chrome/common/render_messages.h" +#include "chrome/common/render_messages_params.h" +#include "chrome/test/testing_profile.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/base/keycodes/keyboard_codes.h" +#include "ui/gfx/canvas_skia.h" + +using base::TimeDelta; + +using WebKit::WebInputEvent; +using WebKit::WebMouseWheelEvent; + +namespace gfx { +class Size; +} + +// RenderWidgetHostProcess ----------------------------------------------------- + +class RenderWidgetHostProcess : public MockRenderProcessHost { + public: + explicit RenderWidgetHostProcess(Profile* profile) + : MockRenderProcessHost(profile), + current_update_buf_(NULL), + update_msg_should_reply_(false), + update_msg_reply_flags_(0) { + // DANGER! This is a hack. The RenderWidgetHost checks the channel to see + // if the process is still alive, but it doesn't actually dereference it. + // An IPC::SyncChannel is nontrivial, so we just fake it here. If you end up + // crashing by dereferencing 1, then you'll have to make a real channel. + channel_.reset(reinterpret_cast<IPC::SyncChannel*>(0x1)); + } + ~RenderWidgetHostProcess() { + // We don't want to actually delete the channel, since it's not a real + // pointer. + ignore_result(channel_.release()); + delete current_update_buf_; + } + + void set_update_msg_should_reply(bool reply) { + update_msg_should_reply_ = reply; + } + void set_update_msg_reply_flags(int flags) { + update_msg_reply_flags_ = flags; + } + + // Fills the given update parameters with resonable default values. + void InitUpdateRectParams(ViewHostMsg_UpdateRect_Params* params); + + protected: + virtual bool WaitForUpdateMsg(int render_widget_id, + const base::TimeDelta& max_delay, + IPC::Message* msg); + + TransportDIB* current_update_buf_; + + // Set to true when WaitForUpdateMsg should return a successful update message + // reply. False implies timeout. + bool update_msg_should_reply_; + + // Indicates the flags that should be sent with a the repaint request. This + // only has an effect when update_msg_should_reply_ is true. + int update_msg_reply_flags_; + + DISALLOW_COPY_AND_ASSIGN(RenderWidgetHostProcess); +}; + +void RenderWidgetHostProcess::InitUpdateRectParams( + ViewHostMsg_UpdateRect_Params* params) { + // Create the shared backing store. + const int w = 100, h = 100; + const size_t pixel_size = w * h * 4; + + if (!current_update_buf_) + current_update_buf_ = TransportDIB::Create(pixel_size, 0); + params->bitmap = current_update_buf_->id(); + params->bitmap_rect = gfx::Rect(0, 0, w, h); + params->dx = 0; + params->dy = 0; + params->copy_rects.push_back(params->bitmap_rect); + params->view_size = gfx::Size(w, h); + params->flags = update_msg_reply_flags_; +} + +bool RenderWidgetHostProcess::WaitForUpdateMsg(int render_widget_id, + const base::TimeDelta& max_delay, + IPC::Message* msg) { + if (!update_msg_should_reply_) + return false; + + // Construct a fake update reply. + ViewHostMsg_UpdateRect_Params params; + InitUpdateRectParams(¶ms); + + ViewHostMsg_UpdateRect message(render_widget_id, params); + *msg = message; + return true; +} + +// TestView -------------------------------------------------------------------- + +// This test view allows us to specify the size. +class TestView : public TestRenderWidgetHostView { + public: + explicit TestView(RenderWidgetHost* rwh) : TestRenderWidgetHostView(rwh) {} + + // Sets the bounds returned by GetViewBounds. + void set_bounds(const gfx::Rect& bounds) { + bounds_ = bounds; + } + + // RenderWidgetHostView override. + virtual gfx::Rect GetViewBounds() const { + return bounds_; + } + +#if defined(OS_MACOSX) + virtual gfx::Rect GetViewCocoaBounds() const { + return bounds_; + } +#endif + + protected: + gfx::Rect bounds_; + DISALLOW_COPY_AND_ASSIGN(TestView); +}; + +// MockRenderWidgetHost ---------------------------------------------------- + +class MockRenderWidgetHost : public RenderWidgetHost { + public: + MockRenderWidgetHost(RenderProcessHost* process, int routing_id) + : RenderWidgetHost(process, routing_id), + prehandle_keyboard_event_(false), + prehandle_keyboard_event_called_(false), + prehandle_keyboard_event_type_(WebInputEvent::Undefined), + unhandled_keyboard_event_called_(false), + unhandled_keyboard_event_type_(WebInputEvent::Undefined), + unresponsive_timer_fired_(false) { + } + + // Tests that make sure we ignore keyboard event acknowledgments to events we + // didn't send work by making sure we didn't call UnhandledKeyboardEvent(). + bool unhandled_keyboard_event_called() const { + return unhandled_keyboard_event_called_; + } + + WebInputEvent::Type unhandled_keyboard_event_type() const { + return unhandled_keyboard_event_type_; + } + + bool prehandle_keyboard_event_called() const { + return prehandle_keyboard_event_called_; + } + + WebInputEvent::Type prehandle_keyboard_event_type() const { + return prehandle_keyboard_event_type_; + } + + void set_prehandle_keyboard_event(bool handle) { + prehandle_keyboard_event_ = handle; + } + + bool unresponsive_timer_fired() const { + return unresponsive_timer_fired_; + } + + protected: + virtual bool PreHandleKeyboardEvent(const NativeWebKeyboardEvent& event, + bool* is_keyboard_shortcut) { + prehandle_keyboard_event_type_ = event.type; + prehandle_keyboard_event_called_ = true; + return prehandle_keyboard_event_; + } + + virtual void UnhandledKeyboardEvent(const NativeWebKeyboardEvent& event) { + unhandled_keyboard_event_type_ = event.type; + unhandled_keyboard_event_called_ = true; + } + + virtual void NotifyRendererUnresponsive() { + unresponsive_timer_fired_ = true; + } + + private: + bool prehandle_keyboard_event_; + bool prehandle_keyboard_event_called_; + WebInputEvent::Type prehandle_keyboard_event_type_; + + bool unhandled_keyboard_event_called_; + WebInputEvent::Type unhandled_keyboard_event_type_; + + bool unresponsive_timer_fired_; +}; + +// MockPaintingObserver -------------------------------------------------------- + +class MockPaintingObserver : public NotificationObserver { + public: + void WidgetDidReceivePaintAtSizeAck(RenderWidgetHost* host, + int tag, + const gfx::Size& size) { + host_ = reinterpret_cast<MockRenderWidgetHost*>(host); + tag_ = tag; + size_ = size; + } + + void Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details) { + if (type == + NotificationType::RENDER_WIDGET_HOST_DID_RECEIVE_PAINT_AT_SIZE_ACK) { + RenderWidgetHost::PaintAtSizeAckDetails* size_ack_details = + Details<RenderWidgetHost::PaintAtSizeAckDetails>(details).ptr(); + WidgetDidReceivePaintAtSizeAck( + Source<RenderWidgetHost>(source).ptr(), + size_ack_details->tag, + size_ack_details->size); + } + } + + MockRenderWidgetHost* host() const { return host_; } + int tag() const { return tag_; } + gfx::Size size() const { return size_; } + + private: + MockRenderWidgetHost* host_; + int tag_; + gfx::Size size_; +}; + + +// RenderWidgetHostTest -------------------------------------------------------- + +class RenderWidgetHostTest : public testing::Test { + public: + RenderWidgetHostTest() : process_(NULL) { + } + ~RenderWidgetHostTest() { + } + + protected: + // testing::Test + void SetUp() { + profile_.reset(new TestingProfile()); + process_ = new RenderWidgetHostProcess(profile_.get()); + host_.reset(new MockRenderWidgetHost(process_, 1)); + view_.reset(new TestView(host_.get())); + host_->set_view(view_.get()); + host_->Init(); + } + void TearDown() { + view_.reset(); + host_.reset(); + process_ = NULL; + profile_.reset(); + + // Process all pending tasks to avoid leaks. + MessageLoop::current()->RunAllPending(); + } + + void SendInputEventACK(WebInputEvent::Type type, bool processed) { + scoped_ptr<IPC::Message> response( + new ViewHostMsg_HandleInputEvent_ACK(0)); + response->WriteInt(type); + response->WriteBool(processed); + host_->OnMessageReceived(*response); + } + + void SimulateKeyboardEvent(WebInputEvent::Type type) { + NativeWebKeyboardEvent key_event; + key_event.type = type; + key_event.windowsKeyCode = ui::VKEY_L; // non-null made up value. + host_->ForwardKeyboardEvent(key_event); + } + + void SimulateWheelEvent(float dX, float dY, int modifiers) { + WebMouseWheelEvent wheel_event; + wheel_event.type = WebInputEvent::MouseWheel; + wheel_event.deltaX = dX; + wheel_event.deltaY = dY; + wheel_event.modifiers = modifiers; + host_->ForwardWheelEvent(wheel_event); + } + + MessageLoopForUI message_loop_; + + scoped_ptr<TestingProfile> profile_; + RenderWidgetHostProcess* process_; // Deleted automatically by the widget. + scoped_ptr<MockRenderWidgetHost> host_; + scoped_ptr<TestView> view_; + + DISALLOW_COPY_AND_ASSIGN(RenderWidgetHostTest); +}; + +// ----------------------------------------------------------------------------- + +TEST_F(RenderWidgetHostTest, Resize) { + // The initial bounds is the empty rect, so setting it to the same thing + // should do nothing. + view_->set_bounds(gfx::Rect()); + host_->WasResized(); + EXPECT_FALSE(host_->resize_ack_pending_); + EXPECT_EQ(gfx::Size(), host_->in_flight_size_); + EXPECT_FALSE(process_->sink().GetUniqueMessageMatching(ViewMsg_Resize::ID)); + + // Setting the bounds to a "real" rect should send out the notification. + gfx::Rect original_size(0, 0, 100, 100); + process_->sink().ClearMessages(); + view_->set_bounds(original_size); + host_->WasResized(); + EXPECT_TRUE(host_->resize_ack_pending_); + EXPECT_EQ(original_size.size(), host_->in_flight_size_); + EXPECT_TRUE(process_->sink().GetUniqueMessageMatching(ViewMsg_Resize::ID)); + + // Send out a update that's not a resize ack. This should not clean the + // resize ack pending flag. + ViewHostMsg_UpdateRect_Params params; + process_->InitUpdateRectParams(¶ms); + host_->OnMsgUpdateRect(params); + EXPECT_TRUE(host_->resize_ack_pending_); + EXPECT_EQ(original_size.size(), host_->in_flight_size_); + + // Sending out a new notification should NOT send out a new IPC message since + // a resize ACK is pending. + gfx::Rect second_size(0, 0, 90, 90); + process_->sink().ClearMessages(); + view_->set_bounds(second_size); + host_->WasResized(); + EXPECT_TRUE(host_->resize_ack_pending_); + EXPECT_EQ(original_size.size(), host_->in_flight_size_); + EXPECT_FALSE(process_->sink().GetUniqueMessageMatching(ViewMsg_Resize::ID)); + + // Send a update that's a resize ack, but for the original_size we sent. Since + // this isn't the second_size, the message handler should immediately send + // a new resize message for the new size to the renderer. + process_->sink().ClearMessages(); + params.flags = ViewHostMsg_UpdateRect_Flags::IS_RESIZE_ACK; + params.view_size = original_size.size(); + host_->OnMsgUpdateRect(params); + EXPECT_TRUE(host_->resize_ack_pending_); + EXPECT_EQ(second_size.size(), host_->in_flight_size_); + ASSERT_TRUE(process_->sink().GetUniqueMessageMatching(ViewMsg_Resize::ID)); + + // Send the resize ack for the latest size. + process_->sink().ClearMessages(); + params.view_size = second_size.size(); + host_->OnMsgUpdateRect(params); + EXPECT_FALSE(host_->resize_ack_pending_); + EXPECT_EQ(gfx::Size(), host_->in_flight_size_); + ASSERT_FALSE(process_->sink().GetFirstMessageMatching(ViewMsg_Resize::ID)); + + // Now clearing the bounds should send out a notification but we shouldn't + // expect a resize ack (since the renderer won't ack empty sizes). The message + // should contain the new size (0x0) and not the previous one that we skipped + process_->sink().ClearMessages(); + view_->set_bounds(gfx::Rect()); + host_->WasResized(); + EXPECT_FALSE(host_->resize_ack_pending_); + EXPECT_EQ(gfx::Size(), host_->in_flight_size_); + EXPECT_EQ(gfx::Size(), host_->current_size_); + EXPECT_TRUE(process_->sink().GetUniqueMessageMatching(ViewMsg_Resize::ID)); + + // Send a rect that has no area but has either width or height set. + // since we do not expect ACK, current_size_ should be updated right away. + process_->sink().ClearMessages(); + view_->set_bounds(gfx::Rect(0, 0, 0, 30)); + host_->WasResized(); + EXPECT_FALSE(host_->resize_ack_pending_); + EXPECT_EQ(gfx::Size(), host_->in_flight_size_); + EXPECT_EQ(gfx::Size(0, 30), host_->current_size_); + EXPECT_TRUE(process_->sink().GetUniqueMessageMatching(ViewMsg_Resize::ID)); + + // Set the same size again. It should not be sent again. + process_->sink().ClearMessages(); + host_->WasResized(); + EXPECT_FALSE(host_->resize_ack_pending_); + EXPECT_EQ(gfx::Size(), host_->in_flight_size_); + EXPECT_EQ(gfx::Size(0, 30), host_->current_size_); + EXPECT_FALSE(process_->sink().GetFirstMessageMatching(ViewMsg_Resize::ID)); + + // A different size should be sent again, however. + view_->set_bounds(gfx::Rect(0, 0, 0, 31)); + host_->WasResized(); + EXPECT_FALSE(host_->resize_ack_pending_); + EXPECT_EQ(gfx::Size(), host_->in_flight_size_); + EXPECT_EQ(gfx::Size(0, 31), host_->current_size_); + EXPECT_TRUE(process_->sink().GetUniqueMessageMatching(ViewMsg_Resize::ID)); +} + +// Test for crbug.com/25097. If a renderer crashes between a resize and the +// corresponding update message, we must be sure to clear the resize ack logic. +TEST_F(RenderWidgetHostTest, ResizeThenCrash) { + // Setting the bounds to a "real" rect should send out the notification. + gfx::Rect original_size(0, 0, 100, 100); + view_->set_bounds(original_size); + host_->WasResized(); + EXPECT_TRUE(host_->resize_ack_pending_); + EXPECT_EQ(original_size.size(), host_->in_flight_size_); + EXPECT_TRUE(process_->sink().GetUniqueMessageMatching(ViewMsg_Resize::ID)); + + // Simulate a renderer crash before the update message. Ensure all the + // resize ack logic is cleared. Must clear the view first so it doesn't get + // deleted. + host_->set_view(NULL); + host_->RendererExited(base::TERMINATION_STATUS_PROCESS_CRASHED, -1); + EXPECT_FALSE(host_->resize_ack_pending_); + EXPECT_EQ(gfx::Size(), host_->in_flight_size_); + + // Reset the view so we can exit the test cleanly. + host_->set_view(view_.get()); +} + +// Tests setting custom background +TEST_F(RenderWidgetHostTest, Background) { +#if defined(OS_WIN) || defined(OS_LINUX) + scoped_ptr<RenderWidgetHostView> view( + RenderWidgetHostView::CreateViewForWidget(host_.get())); + host_->set_view(view.get()); + + // Create a checkerboard background to test with. + gfx::CanvasSkia canvas(4, 4, true); + canvas.FillRectInt(SK_ColorBLACK, 0, 0, 2, 2); + canvas.FillRectInt(SK_ColorWHITE, 2, 0, 2, 2); + canvas.FillRectInt(SK_ColorWHITE, 0, 2, 2, 2); + canvas.FillRectInt(SK_ColorBLACK, 2, 2, 2, 2); + const SkBitmap& background = canvas.getDevice()->accessBitmap(false); + + // Set the background and make sure we get back a copy. + view->SetBackground(background); + EXPECT_EQ(4, view->background().width()); + EXPECT_EQ(4, view->background().height()); + EXPECT_EQ(background.getSize(), view->background().getSize()); + EXPECT_TRUE(0 == memcmp(background.getPixels(), + view->background().getPixels(), + background.getSize())); + +#if defined(OS_WIN) + // A message should have been dispatched telling the renderer about the new + // background. + const IPC::Message* set_background = + process_->sink().GetUniqueMessageMatching(ViewMsg_SetBackground::ID); + ASSERT_TRUE(set_background); + Tuple1<SkBitmap> sent_background; + ViewMsg_SetBackground::Read(set_background, &sent_background); + EXPECT_EQ(background.getSize(), sent_background.a.getSize()); + EXPECT_TRUE(0 == memcmp(background.getPixels(), + sent_background.a.getPixels(), + background.getSize())); +#else + // TODO(port): When custom backgrounds are implemented for other ports, this + // test should work (assuming the background must still be copied into the + // renderer -- if not, then maybe the test doesn't apply?). +#endif + +#else + // TODO(port): Mac does not have gfx::Canvas. Maybe we can just change this + // test to use SkCanvas directly? +#endif + + // TODO(aa): It would be nice to factor out the painting logic so that we + // could test that, but it appears that would mean painting everything twice + // since windows HDC structures are opaque. +} + +// Tests getting the backing store with the renderer not setting repaint ack +// flags. +TEST_F(RenderWidgetHostTest, GetBackingStore_NoRepaintAck) { + // We don't currently have a backing store, and if the renderer doesn't send + // one in time, we should get nothing. + process_->set_update_msg_should_reply(false); + BackingStore* backing = host_->GetBackingStore(true); + EXPECT_FALSE(backing); + // The widget host should have sent a request for a repaint, and there should + // be no paint ACK. + EXPECT_TRUE(process_->sink().GetUniqueMessageMatching(ViewMsg_Repaint::ID)); + EXPECT_FALSE(process_->sink().GetUniqueMessageMatching( + ViewMsg_UpdateRect_ACK::ID)); + + // Allowing the renderer to reply in time should give is a backing store. + process_->sink().ClearMessages(); + process_->set_update_msg_should_reply(true); + process_->set_update_msg_reply_flags(0); + backing = host_->GetBackingStore(true); + EXPECT_TRUE(backing); + // The widget host should NOT have sent a request for a repaint, since there + // was an ACK already pending. + EXPECT_FALSE(process_->sink().GetUniqueMessageMatching(ViewMsg_Repaint::ID)); + EXPECT_TRUE(process_->sink().GetUniqueMessageMatching( + ViewMsg_UpdateRect_ACK::ID)); +} + +// Tests getting the backing store with the renderer sending a repaint ack. +TEST_F(RenderWidgetHostTest, GetBackingStore_RepaintAck) { + // Doing a request request with the update message allowed should work and + // the repaint ack should work. + process_->set_update_msg_should_reply(true); + process_->set_update_msg_reply_flags( + ViewHostMsg_UpdateRect_Flags::IS_REPAINT_ACK); + BackingStore* backing = host_->GetBackingStore(true); + EXPECT_TRUE(backing); + // We still should not have sent out a repaint request since the last flags + // didn't have the repaint ack set, and the pending flag will still be set. + EXPECT_TRUE(process_->sink().GetUniqueMessageMatching(ViewMsg_Repaint::ID)); + EXPECT_TRUE(process_->sink().GetUniqueMessageMatching( + ViewMsg_UpdateRect_ACK::ID)); + + // Asking again for the backing store should just re-use the existing one + // and not send any messagse. + process_->sink().ClearMessages(); + backing = host_->GetBackingStore(true); + EXPECT_TRUE(backing); + EXPECT_FALSE(process_->sink().GetUniqueMessageMatching(ViewMsg_Repaint::ID)); + EXPECT_FALSE(process_->sink().GetUniqueMessageMatching( + ViewMsg_UpdateRect_ACK::ID)); +} + +// Test that we don't paint when we're hidden, but we still send the ACK. Most +// of the rest of the painting is tested in the GetBackingStore* ones. +TEST_F(RenderWidgetHostTest, HiddenPaint) { + // Hide the widget, it should have sent out a message to the renderer. + EXPECT_FALSE(host_->is_hidden_); + host_->WasHidden(); + EXPECT_TRUE(host_->is_hidden_); + EXPECT_TRUE(process_->sink().GetUniqueMessageMatching(ViewMsg_WasHidden::ID)); + + // Send it an update as from the renderer. + process_->sink().ClearMessages(); + ViewHostMsg_UpdateRect_Params params; + process_->InitUpdateRectParams(¶ms); + host_->OnMsgUpdateRect(params); + + // It should have sent out the ACK. + EXPECT_TRUE(process_->sink().GetUniqueMessageMatching( + ViewMsg_UpdateRect_ACK::ID)); + + // Now unhide. + process_->sink().ClearMessages(); + host_->WasRestored(); + EXPECT_FALSE(host_->is_hidden_); + + // It should have sent out a restored message with a request to paint. + const IPC::Message* restored = process_->sink().GetUniqueMessageMatching( + ViewMsg_WasRestored::ID); + ASSERT_TRUE(restored); + Tuple1<bool> needs_repaint; + ViewMsg_WasRestored::Read(restored, &needs_repaint); + EXPECT_TRUE(needs_repaint.a); +} + +TEST_F(RenderWidgetHostTest, PaintAtSize) { + const int kPaintAtSizeTag = 42; + host_->PaintAtSize(TransportDIB::GetFakeHandleForTest(), kPaintAtSizeTag, + gfx::Size(40, 60), gfx::Size(20, 30)); + EXPECT_TRUE( + process_->sink().GetUniqueMessageMatching(ViewMsg_PaintAtSize::ID)); + + NotificationRegistrar registrar; + MockPaintingObserver observer; + registrar.Add( + &observer, + NotificationType::RENDER_WIDGET_HOST_DID_RECEIVE_PAINT_AT_SIZE_ACK, + Source<RenderWidgetHost>(host_.get())); + + host_->OnMsgPaintAtSizeAck(kPaintAtSizeTag, gfx::Size(20, 30)); + EXPECT_EQ(host_.get(), observer.host()); + EXPECT_EQ(kPaintAtSizeTag, observer.tag()); + EXPECT_EQ(20, observer.size().width()); + EXPECT_EQ(30, observer.size().height()); +} + +TEST_F(RenderWidgetHostTest, HandleKeyEventsWeSent) { + // Simulate a keyboard event. + SimulateKeyboardEvent(WebInputEvent::RawKeyDown); + + // Make sure we sent the input event to the renderer. + EXPECT_TRUE(process_->sink().GetUniqueMessageMatching( + ViewMsg_HandleInputEvent::ID)); + process_->sink().ClearMessages(); + + // Send the simulated response from the renderer back. + SendInputEventACK(WebInputEvent::RawKeyDown, false); + + EXPECT_TRUE(host_->unhandled_keyboard_event_called()); + EXPECT_EQ(WebInputEvent::RawKeyDown, host_->unhandled_keyboard_event_type()); +} + +TEST_F(RenderWidgetHostTest, IgnoreKeyEventsWeDidntSend) { + // Send a simulated, unrequested key response. We should ignore this. + SendInputEventACK(WebInputEvent::RawKeyDown, false); + + EXPECT_FALSE(host_->unhandled_keyboard_event_called()); +} + +TEST_F(RenderWidgetHostTest, IgnoreKeyEventsHandledByRenderer) { + // Simulate a keyboard event. + SimulateKeyboardEvent(WebInputEvent::RawKeyDown); + + // Make sure we sent the input event to the renderer. + EXPECT_TRUE(process_->sink().GetUniqueMessageMatching( + ViewMsg_HandleInputEvent::ID)); + process_->sink().ClearMessages(); + + // Send the simulated response from the renderer back. + SendInputEventACK(WebInputEvent::RawKeyDown, true); + EXPECT_FALSE(host_->unhandled_keyboard_event_called()); +} + +TEST_F(RenderWidgetHostTest, PreHandleRawKeyDownEvent) { + // Simluate the situation that the browser handled the key down event during + // pre-handle phrase. + host_->set_prehandle_keyboard_event(true); + process_->sink().ClearMessages(); + + // Simulate a keyboard event. + SimulateKeyboardEvent(WebInputEvent::RawKeyDown); + + EXPECT_TRUE(host_->prehandle_keyboard_event_called()); + EXPECT_EQ(WebInputEvent::RawKeyDown, host_->prehandle_keyboard_event_type()); + + // Make sure the RawKeyDown event is not sent to the renderer. + EXPECT_EQ(0U, process_->sink().message_count()); + + // The browser won't pre-handle a Char event. + host_->set_prehandle_keyboard_event(false); + + // Forward the Char event. + SimulateKeyboardEvent(WebInputEvent::Char); + + // Make sure the Char event is suppressed. + EXPECT_EQ(0U, process_->sink().message_count()); + + // Forward the KeyUp event. + SimulateKeyboardEvent(WebInputEvent::KeyUp); + + // Make sure only KeyUp was sent to the renderer. + EXPECT_EQ(1U, process_->sink().message_count()); + EXPECT_EQ(ViewMsg_HandleInputEvent::ID, + process_->sink().GetMessageAt(0)->type()); + process_->sink().ClearMessages(); + + // Send the simulated response from the renderer back. + SendInputEventACK(WebInputEvent::KeyUp, false); + + EXPECT_TRUE(host_->unhandled_keyboard_event_called()); + EXPECT_EQ(WebInputEvent::KeyUp, host_->unhandled_keyboard_event_type()); +} + +TEST_F(RenderWidgetHostTest, CoalescesWheelEvents) { + process_->sink().ClearMessages(); + + // Simulate wheel events. + SimulateWheelEvent(0, -5, 0); // sent directly + SimulateWheelEvent(0, -10, 0); // enqueued + SimulateWheelEvent(8, -6, 0); // coalesced into previous event + SimulateWheelEvent(9, -7, 1); // enqueued, different modifiers + + // Check that only the first event was sent. + EXPECT_EQ(1U, process_->sink().message_count()); + EXPECT_TRUE(process_->sink().GetUniqueMessageMatching( + ViewMsg_HandleInputEvent::ID)); + process_->sink().ClearMessages(); + + // Check that the ACK sends the second message. + SendInputEventACK(WebInputEvent::MouseWheel, true); + // The coalesced events can queue up a delayed ack + // so that additional input events can be processed before + // we turn off coalescing. + MessageLoop::current()->RunAllPending(); + EXPECT_EQ(1U, process_->sink().message_count()); + EXPECT_TRUE(process_->sink().GetUniqueMessageMatching( + ViewMsg_HandleInputEvent::ID)); + process_->sink().ClearMessages(); + + // One more time. + SendInputEventACK(WebInputEvent::MouseWheel, true); + MessageLoop::current()->RunAllPending(); + EXPECT_EQ(1U, process_->sink().message_count()); + EXPECT_TRUE(process_->sink().GetUniqueMessageMatching( + ViewMsg_HandleInputEvent::ID)); + process_->sink().ClearMessages(); + + // After the final ack, the queue should be empty. + SendInputEventACK(WebInputEvent::MouseWheel, true); + MessageLoop::current()->RunAllPending(); + EXPECT_EQ(0U, process_->sink().message_count()); +} + +// Test that the hang monitor timer expires properly if a new timer is started +// while one is in progress (see crbug.com/11007). +TEST_F(RenderWidgetHostTest, DontPostponeHangMonitorTimeout) { + // Start with a short timeout. + host_->StartHangMonitorTimeout(TimeDelta::FromMilliseconds(10)); + + // Immediately try to add a long 30 second timeout. + EXPECT_FALSE(host_->unresponsive_timer_fired()); + host_->StartHangMonitorTimeout(TimeDelta::FromMilliseconds(30000)); + + // Wait long enough for first timeout and see if it fired. + MessageLoop::current()->PostDelayedTask(FROM_HERE, + new MessageLoop::QuitTask(), 10); + MessageLoop::current()->Run(); + EXPECT_TRUE(host_->unresponsive_timer_fired()); +} + +// Test that the hang monitor timer expires properly if it is started, stopped, +// and then started again. +TEST_F(RenderWidgetHostTest, StopAndStartHangMonitorTimeout) { + // Start with a short timeout, then stop it. + host_->StartHangMonitorTimeout(TimeDelta::FromMilliseconds(10)); + host_->StopHangMonitorTimeout(); + + // Start it again to ensure it still works. + EXPECT_FALSE(host_->unresponsive_timer_fired()); + host_->StartHangMonitorTimeout(TimeDelta::FromMilliseconds(10)); + + // Wait long enough for first timeout and see if it fired. + MessageLoop::current()->PostDelayedTask(FROM_HERE, + new MessageLoop::QuitTask(), 10); + MessageLoop::current()->Run(); + EXPECT_TRUE(host_->unresponsive_timer_fired()); +} diff --git a/content/browser/renderer_host/render_widget_host_view.cc b/content/browser/renderer_host/render_widget_host_view.cc new file mode 100644 index 0000000..a3b4d8c --- /dev/null +++ b/content/browser/renderer_host/render_widget_host_view.cc @@ -0,0 +1,11 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "content/browser/renderer_host/render_widget_host_view.h" + +RenderWidgetHostView::~RenderWidgetHostView() {} + +void RenderWidgetHostView::SetBackground(const SkBitmap& background) { + background_ = background; +} diff --git a/content/browser/renderer_host/render_widget_host_view.h b/content/browser/renderer_host/render_widget_host_view.h new file mode 100644 index 0000000..a3f7031 --- /dev/null +++ b/content/browser/renderer_host/render_widget_host_view.h @@ -0,0 +1,326 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CONTENT_BROWSER_RENDERER_HOST_RENDER_WIDGET_HOST_VIEW_H_ +#define CONTENT_BROWSER_RENDERER_HOST_RENDER_WIDGET_HOST_VIEW_H_ +#pragma once + +#if defined(OS_MACOSX) +#include <OpenGL/OpenGL.h> +#endif + +#include <string> +#include <vector> + +#include "app/surface/transport_dib.h" +#include "base/process_util.h" +#include "third_party/skia/include/core/SkBitmap.h" +#include "third_party/skia/include/core/SkColor.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebPopupType.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebTextInputType.h" +#include "ui/gfx/native_widget_types.h" +#include "ui/gfx/rect.h" + +namespace gfx { +class Rect; +class Size; +} +namespace IPC { +class Message; +} + +class BackingStore; +class RenderProcessHost; +class RenderWidgetHost; +class WebCursor; +struct NativeWebKeyboardEvent; +struct ViewHostMsg_AccessibilityNotification_Params; + +namespace webkit_glue { +struct WebAccessibility; +} + +namespace webkit { +namespace npapi { +struct WebPluginGeometry; +} +} + +// RenderWidgetHostView is an interface implemented by an object that acts as +// the "View" portion of a RenderWidgetHost. The RenderWidgetHost and its +// associated RenderProcessHost own the "Model" in this case which is the +// child renderer process. The View is responsible for receiving events from +// the surrounding environment and passing them to the RenderWidgetHost, and +// for actually displaying the content of the RenderWidgetHost when it +// changes. +class RenderWidgetHostView { + public: + virtual ~RenderWidgetHostView(); + + // Platform-specific creator. Use this to construct new RenderWidgetHostViews + // rather than using RenderWidgetHostViewWin & friends. + // + // This function must NOT size it, because the RenderView in the renderer + // wounldn't have been created yet. The widget would set its "waiting for + // resize ack" flag, and the ack would never come becasue no RenderView + // received it. + // + // The RenderWidgetHost must already be created (because we can't know if it's + // going to be a regular RenderWidgetHost or a RenderViewHost (a subclass). + static RenderWidgetHostView* CreateViewForWidget(RenderWidgetHost* widget); + + // Retrieves the RenderWidgetHostView corresponding to the specified + // |native_view|, or NULL if there is no such instance. + static RenderWidgetHostView* GetRenderWidgetHostViewFromNativeView( + gfx::NativeView native_view); + + // Perform all the initialization steps necessary for this object to represent + // a popup (such as a <select> dropdown), then shows the popup at |pos|. + virtual void InitAsPopup(RenderWidgetHostView* parent_host_view, + const gfx::Rect& pos) = 0; + + // Perform all the initialization steps necessary for this object to represent + // a full screen window. + virtual void InitAsFullscreen() = 0; + + // Returns the associated RenderWidgetHost. + virtual RenderWidgetHost* GetRenderWidgetHost() const = 0; + + // Notifies the View that it has become visible. + virtual void DidBecomeSelected() = 0; + + // Notifies the View that it has been hidden. + virtual void WasHidden() = 0; + + // Tells the View to size itself to the specified size. + virtual void SetSize(const gfx::Size& size) = 0; + + // Retrieves the native view used to contain plugins and identify the + // renderer in IPC messages. + virtual gfx::NativeView GetNativeView() = 0; + + // Moves all plugin windows as described in the given list. + virtual void MovePluginWindows( + const std::vector<webkit::npapi::WebPluginGeometry>& moves) = 0; + + // Actually set/take focus to/from the associated View component. + virtual void Focus() = 0; + virtual void Blur() = 0; + + // Returns true if the View currently has the focus. + virtual bool HasFocus() = 0; + + // Shows/hides the view. These must always be called together in pairs. + // It is not legal to call Hide() multiple times in a row. + virtual void Show() = 0; + virtual void Hide() = 0; + + // Whether the view is showing. + virtual bool IsShowing() = 0; + + // Retrieve the bounds of the View, in screen coordinates. + virtual gfx::Rect GetViewBounds() const = 0; + + // Sets the cursor to the one associated with the specified cursor_type + virtual void UpdateCursor(const WebCursor& cursor) = 0; + + // Indicates whether the page has finished loading. + virtual void SetIsLoading(bool is_loading) = 0; + + // Updates the state of the input method attached to the view. + virtual void ImeUpdateTextInputState(WebKit::WebTextInputType type, + const gfx::Rect& caret_rect) = 0; + + // Cancel the ongoing composition of the input method attached to the view. + virtual void ImeCancelComposition() = 0; + + // Informs the view that a portion of the widget's backing store was scrolled + // and/or painted. The view should ensure this gets copied to the screen. + // + // If the scroll_rect is non-empty, then a portion of the widget's backing + // store was scrolled by dx pixels horizontally and dy pixels vertically. + // The exposed rect from the scroll operation is included in copy_rects. + // + // There are subtle performance implications here. The RenderWidget gets sent + // a paint ack after this returns, so if the view only ever invalidates in + // response to this, then on Windows, where WM_PAINT has lower priority than + // events which can cause renderer resizes/paint rect updates, e.g. + // drag-resizing can starve painting; this function thus provides the view its + // main chance to ensure it stays painted and not just invalidated. On the + // other hand, if this always blindly paints, then if we're already in the + // midst of a paint on the callstack, we can double-paint unnecessarily. + // (Worse, we might recursively call RenderWidgetHost::GetBackingStore().) + // Thus implementers should generally paint as much of |rect| as possible + // synchronously with as little overpainting as possible. + virtual void DidUpdateBackingStore( + const gfx::Rect& scroll_rect, int scroll_dx, int scroll_dy, + const std::vector<gfx::Rect>& copy_rects) = 0; + + // Notifies the View that the renderer has ceased to exist. + virtual void RenderViewGone(base::TerminationStatus status, + int error_code) = 0; + + // Notifies the View that the renderer will be delete soon. + virtual void WillDestroyRenderWidget(RenderWidgetHost* rwh) = 0; + + // Tells the View to destroy itself. + virtual void Destroy() = 0; + + // Tells the View that the tooltip text for the current mouse position over + // the page has changed. + virtual void SetTooltipText(const std::wstring& tooltip_text) = 0; + + // Notifies the View that the renderer text selection has changed. + virtual void SelectionChanged(const std::string& text) {} + + // Tells the View whether the context menu is showing. This is used on Linux + // to suppress updates to webkit focus for the duration of the show. + virtual void ShowingContextMenu(bool showing) {} + + // Allocate a backing store for this view + virtual BackingStore* AllocBackingStore(const gfx::Size& size) = 0; + +#if defined(OS_MACOSX) + // Tells the view whether or not to accept first responder status. If |flag| + // is true, the view does not accept first responder status and instead + // manually becomes first responder when it receives a mouse down event. If + // |flag| is false, the view participates in the key-view chain as normal. + virtual void SetTakesFocusOnlyOnMouseDown(bool flag) = 0; + + // Retrieve the bounds of the view, in cocoa view coordinates. + // If the UI scale factor is 2, |GetViewBounds()| will return a size of e.g. + // (400, 300) in pixels, while this method will return (200, 150). + // Even though this returns an gfx::Rect, the result is NOT IN PIXELS. + virtual gfx::Rect GetViewCocoaBounds() const = 0; + + // Get the view's window's position on the screen. + virtual gfx::Rect GetRootWindowRect() = 0; + + // Set the view's active state (i.e., tint state of controls). + virtual void SetActive(bool active) = 0; + + // Notifies the view that its enclosing window has changed visibility + // (minimized/unminimized, app hidden/unhidden, etc). + // TODO(stuartmorgan): This is a temporary plugin-specific workaround for + // <http://crbug.com/34266>. Once that is fixed, this (and the corresponding + // message and renderer-side handling) can be removed in favor of using + // WasHidden/DidBecomeSelected. + virtual void SetWindowVisibility(bool visible) = 0; + + // Informs the view that its containing window's frame changed. + virtual void WindowFrameChanged() = 0; + + // Informs the view that a plugin gained or lost focus. + virtual void PluginFocusChanged(bool focused, int plugin_id) = 0; + + // Start plugin IME. + virtual void StartPluginIme() = 0; + + // Does any event handling necessary for plugin IME; should be called after + // the plugin has already had a chance to process the event. If plugin IME is + // not enabled, this is a no-op, so it is always safe to call. + // Returns true if the event was handled by IME. + virtual bool PostProcessEventForPluginIme( + const NativeWebKeyboardEvent& event) = 0; + + // Methods associated with GPU-accelerated plug-in instances. + virtual gfx::PluginWindowHandle AllocateFakePluginWindowHandle( + bool opaque, bool root) = 0; + virtual void DestroyFakePluginWindowHandle( + gfx::PluginWindowHandle window) = 0; + virtual void AcceleratedSurfaceSetIOSurface( + gfx::PluginWindowHandle window, + int32 width, + int32 height, + uint64 io_surface_identifier) = 0; + virtual void AcceleratedSurfaceSetTransportDIB( + gfx::PluginWindowHandle window, + int32 width, + int32 height, + TransportDIB::Handle transport_dib) = 0; + // |window| and |surface_id| indicate which accelerated surface's + // buffers swapped. |renderer_id|, |route_id| and + // |swap_buffers_count| are used to formulate a reply to the GPU + // process to prevent it from getting too far ahead. They may all be + // zero, in which case no flow control is enforced; this case is + // currently used for accelerated plugins. + virtual void AcceleratedSurfaceBuffersSwapped( + gfx::PluginWindowHandle window, + uint64 surface_id, + int renderer_id, + int32 route_id, + uint64 swap_buffers_count) = 0; + virtual void GpuRenderingStateDidChange() = 0; +#endif + +#if defined(TOOLKIT_USES_GTK) + virtual void CreatePluginContainer(gfx::PluginWindowHandle id) = 0; + virtual void DestroyPluginContainer(gfx::PluginWindowHandle id) = 0; + virtual void AcceleratedCompositingActivated(bool activated) = 0; +#endif + +#if defined(OS_WIN) + virtual void WillWmDestroy() = 0; + virtual void ShowCompositorHostWindow(bool show) = 0; +#endif + + virtual gfx::PluginWindowHandle AcquireCompositingSurface() = 0; + virtual void ReleaseCompositingSurface(gfx::PluginWindowHandle surface) = 0; + + // Toggles visual muting of the render view area. This is on when a + // constrained window is showing, for example. |color| is the shade of + // the overlay that covers the render view. If |animate| is true, the overlay + // gradually fades in; otherwise it takes effect immediately. To remove the + // fade effect, pass a NULL value for |color|. In this case, |animate| is + // ignored. + virtual void SetVisuallyDeemphasized(const SkColor* color, bool animate) = 0; + + void set_popup_type(WebKit::WebPopupType popup_type) { + popup_type_ = popup_type; + } + WebKit::WebPopupType popup_type() const { return popup_type_; } + + // Subclasses should override this method to do what is appropriate to set + // the custom background for their platform. + virtual void SetBackground(const SkBitmap& background); + const SkBitmap& background() const { return background_; } + + // Returns true if the native view, |native_view|, is contained within in the + // widget associated with this RenderWidgetHostView. + virtual bool ContainsNativeView(gfx::NativeView native_view) const = 0; + + virtual void UpdateAccessibilityTree( + const webkit_glue::WebAccessibility& tree) { } + virtual void OnAccessibilityNotifications( + const std::vector<ViewHostMsg_AccessibilityNotification_Params>& params) { + } + + gfx::Rect reserved_contents_rect() const { + return reserved_rect_; + } + void set_reserved_contents_rect(const gfx::Rect& reserved_rect) { + reserved_rect_ = reserved_rect; + } + + protected: + // Interface class only, do not construct. + RenderWidgetHostView() : popup_type_(WebKit::WebPopupTypeNone) {} + + // Whether this view is a popup and what kind of popup it is (select, + // autofill...). + WebKit::WebPopupType popup_type_; + + // A custom background to paint behind the web content. This will be tiled + // horizontally. Can be null, in which case we fall back to painting white. + SkBitmap background_; + + // The current reserved area in view coordinates where contents should not be + // rendered to draw the resize corner, sidebar mini tabs etc. + gfx::Rect reserved_rect_; + + private: + DISALLOW_COPY_AND_ASSIGN(RenderWidgetHostView); +}; + +#endif // CONTENT_BROWSER_RENDERER_HOST_RENDER_WIDGET_HOST_VIEW_H_ diff --git a/content/browser/renderer_host/resource_dispatcher_host.cc b/content/browser/renderer_host/resource_dispatcher_host.cc new file mode 100644 index 0000000..94f5c5e --- /dev/null +++ b/content/browser/renderer_host/resource_dispatcher_host.cc @@ -0,0 +1,1952 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// See http://dev.chromium.org/developers/design-documents/multi-process-resource-loading + +#include "content/browser/renderer_host/resource_dispatcher_host.h" + +#include <set> +#include <vector> + +#include "base/logging.h" +#include "base/command_line.h" +#include "base/message_loop.h" +#include "base/metrics/histogram.h" +#include "base/scoped_ptr.h" +#include "base/shared_memory.h" +#include "base/stl_util-inl.h" +#include "base/time.h" +#include "chrome/browser/cert_store.h" +#include "chrome/browser/child_process_security_policy.h" +#include "chrome/browser/chrome_blob_storage_context.h" +#include "chrome/browser/cross_site_request_manager.h" +#include "chrome/browser/download/download_file_manager.h" +#include "chrome/browser/download/download_manager.h" +#include "chrome/browser/download/download_request_limiter.h" +#include "chrome/browser/download/download_util.h" +#include "chrome/browser/download/save_file_manager.h" +#include "chrome/browser/extensions/user_script_listener.h" +#include "chrome/browser/external_protocol_handler.h" +#include "chrome/browser/in_process_webkit/webkit_thread.h" +#include "chrome/browser/net/chrome_url_request_context.h" +#include "chrome/browser/net/url_request_tracking.h" +#include "chrome/browser/plugin_service.h" +#include "chrome/browser/prerender/prerender_manager.h" +#include "chrome/browser/prerender/prerender_resource_handler.h" +#include "chrome/browser/profiles/profile.h" +#include "chrome/browser/renderer_host/download_resource_handler.h" +#include "chrome/browser/renderer_host/safe_browsing_resource_handler.h" +#include "chrome/browser/renderer_host/save_file_resource_handler.h" +#include "chrome/browser/safe_browsing/safe_browsing_service.h" +#include "chrome/browser/ssl/ssl_client_auth_handler.h" +#include "chrome/browser/ssl/ssl_manager.h" +#include "chrome/browser/ui/login/login_prompt.h" +#include "chrome/browser/worker_host/worker_service.h" +#include "chrome/common/chrome_switches.h" +#include "chrome/common/notification_service.h" +#include "chrome/common/render_messages.h" +#include "chrome/common/render_messages_params.h" +#include "chrome/common/url_constants.h" +#include "content/browser/renderer_host/async_resource_handler.h" +#include "content/browser/renderer_host/buffered_resource_handler.h" +#include "content/browser/renderer_host/cross_site_resource_handler.h" +#include "content/browser/renderer_host/global_request_id.h" +#include "content/browser/renderer_host/redirect_to_file_resource_handler.h" +#include "content/browser/renderer_host/render_view_host.h" +#include "content/browser/renderer_host/render_view_host_delegate.h" +#include "content/browser/renderer_host/render_view_host_notification_task.h" +#include "content/browser/renderer_host/resource_dispatcher_host_request_info.h" +#include "content/browser/renderer_host/resource_message_filter.h" +#include "content/browser/renderer_host/resource_queue.h" +#include "content/browser/renderer_host/resource_request_details.h" +#include "content/browser/renderer_host/sync_resource_handler.h" +#include "net/base/auth.h" +#include "net/base/cert_status_flags.h" +#include "net/base/cookie_monster.h" +#include "net/base/load_flags.h" +#include "net/base/mime_util.h" +#include "net/base/net_errors.h" +#include "net/base/request_priority.h" +#include "net/base/ssl_cert_request_info.h" +#include "net/base/upload_data.h" +#include "net/http/http_response_headers.h" +#include "net/url_request/url_request.h" +#include "net/url_request/url_request_context.h" +#include "webkit/appcache/appcache_interceptor.h" +#include "webkit/appcache/appcache_interfaces.h" +#include "webkit/blob/blob_storage_controller.h" +#include "webkit/blob/deletable_file_reference.h" + +// TODO(oshima): Enable this for other platforms. +#if defined(OS_CHROMEOS) +#include "chrome/browser/renderer_host/offline_resource_handler.h" +#endif + +using base::Time; +using base::TimeDelta; +using base::TimeTicks; +using webkit_blob::DeletableFileReference; + +// ---------------------------------------------------------------------------- + +// A ShutdownTask proxies a shutdown task from the UI thread to the IO thread. +// It should be constructed on the UI thread and run in the IO thread. +class ResourceDispatcherHost::ShutdownTask : public Task { + public: + explicit ShutdownTask(ResourceDispatcherHost* resource_dispatcher_host) + : rdh_(resource_dispatcher_host) { + } + + void Run() { + rdh_->OnShutdown(); + } + + private: + ResourceDispatcherHost* rdh_; +}; + +namespace { + +// The interval for calls to ResourceDispatcherHost::UpdateLoadStates +const int kUpdateLoadStatesIntervalMsec = 100; + +// Maximum number of pending data messages sent to the renderer at any +// given time for a given request. +const int kMaxPendingDataMessages = 20; + +// Maximum byte "cost" of all the outstanding requests for a renderer. +// See delcaration of |max_outstanding_requests_cost_per_process_| for details. +// This bound is 25MB, which allows for around 6000 outstanding requests. +const int kMaxOutstandingRequestsCostPerProcess = 26214400; + +// Consults the RendererSecurity policy to determine whether the +// ResourceDispatcherHost should service this request. A request might be +// disallowed if the renderer is not authorized to retrieve the request URL or +// if the renderer is attempting to upload an unauthorized file. +bool ShouldServiceRequest(ChildProcessInfo::ProcessType process_type, + int child_id, + const ViewHostMsg_Resource_Request& request_data) { + if (process_type == ChildProcessInfo::PLUGIN_PROCESS) + return true; + + if (request_data.resource_type == ResourceType::PREFETCH) { + prerender::PrerenderManager::RecordPrefetchTagObserved(); + if (!ResourceDispatcherHost::is_prefetch_enabled()) + return false; + } + + ChildProcessSecurityPolicy* policy = + ChildProcessSecurityPolicy::GetInstance(); + + // Check if the renderer is permitted to request the requested URL. + if (!policy->CanRequestURL(child_id, request_data.url)) { + VLOG(1) << "Denied unauthorized request for " + << request_data.url.possibly_invalid_spec(); + return false; + } + + // Check if the renderer is permitted to upload the requested files. + if (request_data.upload_data) { + const std::vector<net::UploadData::Element>* uploads = + request_data.upload_data->elements(); + std::vector<net::UploadData::Element>::const_iterator iter; + for (iter = uploads->begin(); iter != uploads->end(); ++iter) { + if (iter->type() == net::UploadData::TYPE_FILE && + !policy->CanReadFile(child_id, iter->file_path())) { + NOTREACHED() << "Denied unauthorized upload of " + << iter->file_path().value(); + return false; + } + } + } + + return true; +} + +void PopulateResourceResponse(net::URLRequest* request, + bool replace_extension_localization_templates, + ResourceResponse* response) { + response->response_head.status = request->status(); + response->response_head.request_time = request->request_time(); + response->response_head.response_time = request->response_time(); + response->response_head.headers = request->response_headers(); + request->GetCharset(&response->response_head.charset); + response->response_head.replace_extension_localization_templates = + replace_extension_localization_templates; + response->response_head.content_length = request->GetExpectedContentSize(); + request->GetMimeType(&response->response_head.mime_type); + response->response_head.was_fetched_via_spdy = + request->was_fetched_via_spdy(); + response->response_head.was_npn_negotiated = request->was_npn_negotiated(); + response->response_head.was_alternate_protocol_available = + request->was_alternate_protocol_available(); + response->response_head.was_fetched_via_proxy = + request->was_fetched_via_proxy(); + appcache::AppCacheInterceptor::GetExtraResponseInfo( + request, + &response->response_head.appcache_id, + &response->response_head.appcache_manifest_url); +} + +// Returns a list of all the possible error codes from the network module. +// Note that the error codes are all positive (since histograms expect positive +// sample values). +std::vector<int> GetAllNetErrorCodes() { + std::vector<int> all_error_codes; +#define NET_ERROR(label, value) all_error_codes.push_back(-(value)); +#include "net/base/net_error_list.h" +#undef NET_ERROR + return all_error_codes; +} + +#if defined(OS_WIN) +#pragma warning(disable: 4748) +#pragma optimize("", off) +#endif + +#if defined(OS_WIN) +#pragma optimize("", on) +#pragma warning(default: 4748) +#endif + +} // namespace + +ResourceDispatcherHost::ResourceDispatcherHost() + : ALLOW_THIS_IN_INITIALIZER_LIST( + download_file_manager_(new DownloadFileManager(this))), + download_request_limiter_(new DownloadRequestLimiter()), + ALLOW_THIS_IN_INITIALIZER_LIST( + save_file_manager_(new SaveFileManager(this))), + user_script_listener_(new UserScriptListener(&resource_queue_)), + safe_browsing_(SafeBrowsingService::CreateSafeBrowsingService()), + webkit_thread_(new WebKitThread), + request_id_(-1), + ALLOW_THIS_IN_INITIALIZER_LIST(method_runner_(this)), + is_shutdown_(false), + max_outstanding_requests_cost_per_process_( + kMaxOutstandingRequestsCostPerProcess), + filter_(NULL) { + ResourceQueue::DelegateSet resource_queue_delegates; + resource_queue_delegates.insert(user_script_listener_.get()); + resource_queue_.Initialize(resource_queue_delegates); +} + +ResourceDispatcherHost::~ResourceDispatcherHost() { + AsyncResourceHandler::GlobalCleanup(); + STLDeleteValues(&pending_requests_); + + user_script_listener_->ShutdownMainThread(); +} + +void ResourceDispatcherHost::Initialize() { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + webkit_thread_->Initialize(); + safe_browsing_->Initialize(); + BrowserThread::PostTask( + BrowserThread::IO, FROM_HERE, + NewRunnableFunction(&appcache::AppCacheInterceptor::EnsureRegistered)); +} + +void ResourceDispatcherHost::Shutdown() { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, new ShutdownTask(this)); +} + +void ResourceDispatcherHost::SetRequestInfo( + net::URLRequest* request, + ResourceDispatcherHostRequestInfo* info) { + request->SetUserData(NULL, info); +} + +void ResourceDispatcherHost::OnShutdown() { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + is_shutdown_ = true; + resource_queue_.Shutdown(); + STLDeleteValues(&pending_requests_); + // Make sure we shutdown the timer now, otherwise by the time our destructor + // runs if the timer is still running the Task is deleted twice (once by + // the MessageLoop and the second time by RepeatingTimer). + update_load_states_timer_.Stop(); + + // Clear blocked requests if any left. + // Note that we have to do this in 2 passes as we cannot call + // CancelBlockedRequestsForRoute while iterating over + // blocked_requests_map_, as it modifies it. + std::set<ProcessRouteIDs> ids; + for (BlockedRequestMap::const_iterator iter = blocked_requests_map_.begin(); + iter != blocked_requests_map_.end(); ++iter) { + std::pair<std::set<ProcessRouteIDs>::iterator, bool> result = + ids.insert(iter->first); + // We should not have duplicates. + DCHECK(result.second); + } + for (std::set<ProcessRouteIDs>::const_iterator iter = ids.begin(); + iter != ids.end(); ++iter) { + CancelBlockedRequestsForRoute(iter->first, iter->second); + } +} + +bool ResourceDispatcherHost::HandleExternalProtocol(int request_id, + int child_id, + int route_id, + const GURL& url, + ResourceType::Type type, + ResourceHandler* handler) { + if (!ResourceType::IsFrame(type) || net::URLRequest::IsHandledURL(url)) + return false; + + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + NewRunnableFunction( + &ExternalProtocolHandler::LaunchUrl, url, child_id, route_id)); + + handler->OnResponseCompleted( + request_id, + net::URLRequestStatus(net::URLRequestStatus::FAILED, net::ERR_ABORTED), + std::string()); // No security info necessary. + return true; +} + +bool ResourceDispatcherHost::OnMessageReceived(const IPC::Message& message, + ResourceMessageFilter* filter, + bool* message_was_ok) { + filter_ = filter; + bool handled = true; + IPC_BEGIN_MESSAGE_MAP_EX(ResourceDispatcherHost, message, *message_was_ok) + IPC_MESSAGE_HANDLER(ViewHostMsg_RequestResource, OnRequestResource) + IPC_MESSAGE_HANDLER_DELAY_REPLY(ViewHostMsg_SyncLoad, OnSyncLoad) + IPC_MESSAGE_HANDLER(ViewHostMsg_ReleaseDownloadedFile, + OnReleaseDownloadedFile) + IPC_MESSAGE_HANDLER(ViewHostMsg_DataReceived_ACK, OnDataReceivedACK) + IPC_MESSAGE_HANDLER(ViewHostMsg_DataDownloaded_ACK, OnDataDownloadedACK) + IPC_MESSAGE_HANDLER(ViewHostMsg_UploadProgress_ACK, OnUploadProgressACK) + IPC_MESSAGE_HANDLER(ViewHostMsg_CancelRequest, OnCancelRequest) + IPC_MESSAGE_HANDLER(ViewHostMsg_FollowRedirect, OnFollowRedirect) + IPC_MESSAGE_HANDLER(ViewHostMsg_ClosePage_ACK, OnClosePageACK) + IPC_MESSAGE_UNHANDLED(handled = false) + IPC_END_MESSAGE_MAP_EX() + + filter_ = NULL; + return handled; +} + +void ResourceDispatcherHost::OnRequestResource( + const IPC::Message& message, + int request_id, + const ViewHostMsg_Resource_Request& request_data) { + BeginRequest(request_id, request_data, NULL, message.routing_id()); +} + +// Begins a resource request with the given params on behalf of the specified +// child process. Responses will be dispatched through the given receiver. The +// process ID is used to lookup TabContents from routing_id's in the case of a +// request from a renderer. request_context is the cookie/cache context to be +// used for this request. +// +// If sync_result is non-null, then a SyncLoad reply will be generated, else +// a normal asynchronous set of response messages will be generated. +void ResourceDispatcherHost::OnSyncLoad( + int request_id, + const ViewHostMsg_Resource_Request& request_data, + IPC::Message* sync_result) { + BeginRequest(request_id, request_data, sync_result, + sync_result->routing_id()); +} + +void ResourceDispatcherHost::BeginRequest( + int request_id, + const ViewHostMsg_Resource_Request& request_data, + IPC::Message* sync_result, // only valid for sync + int route_id) { + ChildProcessInfo::ProcessType process_type = filter_->process_type(); + int child_id = filter_->child_id(); + + ChromeURLRequestContext* context = filter_->GetURLRequestContext( + request_data); + + // Might need to resolve the blob references in the upload data. + if (request_data.upload_data && context) { + context->blob_storage_context()->controller()-> + ResolveBlobReferencesInUploadData(request_data.upload_data.get()); + } + + if (is_shutdown_ || + !ShouldServiceRequest(process_type, child_id, request_data)) { + net::URLRequestStatus status(net::URLRequestStatus::FAILED, + net::ERR_ABORTED); + if (sync_result) { + SyncLoadResult result; + result.status = status; + ViewHostMsg_SyncLoad::WriteReplyParams(sync_result, result); + filter_->Send(sync_result); + } else { + // Tell the renderer that this request was disallowed. + filter_->Send(new ViewMsg_Resource_RequestComplete( + route_id, + request_id, + status, + std::string(), // No security info needed, connection was not + base::Time())); // established. + } + return; + } + + // Ensure the Chrome plugins are loaded, as they may intercept network + // requests. Does nothing if they are already loaded. + // TODO(mpcomplete): This takes 200 ms! Investigate parallelizing this by + // starting the load earlier in a BG thread. + PluginService::GetInstance()->LoadChromePlugins(this); + + // Construct the event handler. + scoped_refptr<ResourceHandler> handler; + if (sync_result) { + handler = new SyncResourceHandler( + filter_, request_data.url, sync_result, this); + } else { + handler = new AsyncResourceHandler( + filter_, route_id, request_data.url, this); + } + + // The RedirectToFileResourceHandler depends on being next in the chain. + if (request_data.download_to_file) + handler = new RedirectToFileResourceHandler(handler, child_id, this); + + if (HandleExternalProtocol(request_id, child_id, route_id, + request_data.url, request_data.resource_type, + handler)) { + return; + } + + // Construct the request. + net::URLRequest* request = new net::URLRequest(request_data.url, this); + request->set_method(request_data.method); + request->set_first_party_for_cookies(request_data.first_party_for_cookies); + request->set_referrer(CommandLine::ForCurrentProcess()->HasSwitch( + switches::kNoReferrers) ? std::string() : request_data.referrer.spec()); + net::HttpRequestHeaders headers; + headers.AddHeadersFromString(request_data.headers); + request->SetExtraRequestHeaders(headers); + + int load_flags = request_data.load_flags; + // Although EV status is irrelevant to sub-frames and sub-resources, we have + // to perform EV certificate verification on all resources because an HTTP + // keep-alive connection created to load a sub-frame or a sub-resource could + // be reused to load a main frame. + load_flags |= net::LOAD_VERIFY_EV_CERT; + if (request_data.resource_type == ResourceType::MAIN_FRAME) { + load_flags |= net::LOAD_MAIN_FRAME; + } else if (request_data.resource_type == ResourceType::SUB_FRAME) { + load_flags |= net::LOAD_SUB_FRAME; + } else if (request_data.resource_type == ResourceType::PREFETCH) { + load_flags |= net::LOAD_PREFETCH; + } + // Raw headers are sensitive, as they inclide Cookie/Set-Cookie, so only + // allow requesting them if requestor has ReadRawCookies permission. + if ((load_flags & net::LOAD_REPORT_RAW_HEADERS) + && !ChildProcessSecurityPolicy::GetInstance()-> + CanReadRawCookies(child_id)) { + VLOG(1) << "Denied unathorized request for raw headers"; + load_flags &= ~net::LOAD_REPORT_RAW_HEADERS; + } + + request->set_load_flags(load_flags); + request->set_context(context); + request->set_priority(DetermineRequestPriority(request_data.resource_type)); + + // Set upload data. + uint64 upload_size = 0; + if (request_data.upload_data) { + request->set_upload(request_data.upload_data); + upload_size = request_data.upload_data->GetContentLength(); + } + + // Install a PrerenderResourceHandler if the requested URL could + // be prerendered. This should be in front of the [a]syncResourceHandler, + // but after the BufferedResourceHandler since it depends on the MIME + // sniffing capabilities in the BufferedResourceHandler. + prerender::PrerenderResourceHandler* pre_handler = + prerender::PrerenderResourceHandler::MaybeCreate(*request, + context, + handler); + if (pre_handler) + handler = pre_handler; + + // Install a CrossSiteResourceHandler if this request is coming from a + // RenderViewHost with a pending cross-site request. We only check this for + // MAIN_FRAME requests. Unblock requests only come from a blocked page, do + // not count as cross-site, otherwise it gets blocked indefinitely. + if (request_data.resource_type == ResourceType::MAIN_FRAME && + process_type == ChildProcessInfo::RENDER_PROCESS && + CrossSiteRequestManager::GetInstance()-> + HasPendingCrossSiteRequest(child_id, route_id)) { + // Wrap the event handler to be sure the current page's onunload handler + // has a chance to run before we render the new page. + handler = new CrossSiteResourceHandler(handler, + child_id, + route_id, + this); + } + + // Insert a buffered event handler before the actual one. + handler = new BufferedResourceHandler(handler, this, request); + + // Insert safe browsing at the front of the chain, so it gets to decide + // on policies first. + if (safe_browsing_->enabled()) { + handler = CreateSafeBrowsingResourceHandler(handler, child_id, route_id, + request_data.resource_type); + } + +#if defined(OS_CHROMEOS) + // We check offline first, then check safe browsing so that we still can block + // unsafe site after we remove offline page. + handler = + new OfflineResourceHandler(handler, child_id, route_id, this, request); +#endif + + // Make extra info and read footer (contains request ID). + ResourceDispatcherHostRequestInfo* extra_info = + new ResourceDispatcherHostRequestInfo( + handler, + process_type, + child_id, + route_id, + request_id, + request_data.resource_type, + upload_size, + false, // is download + ResourceType::IsFrame(request_data.resource_type), // allow_download + request_data.has_user_gesture, + request_data.host_renderer_id, + request_data.host_render_view_id); + ApplyExtensionLocalizationFilter(request_data.url, request_data.resource_type, + extra_info); + SetRequestInfo(request, extra_info); // Request takes ownership. + chrome_browser_net::SetOriginPIDForRequest( + request_data.origin_pid, request); + + if (request->url().SchemeIs(chrome::kBlobScheme) && context) { + // Hang on to a reference to ensure the blob is not released prior + // to the job being started. + webkit_blob::BlobStorageController* controller = + context->blob_storage_context()->controller(); + extra_info->set_requested_blob_data( + controller->GetBlobDataFromUrl(request->url())); + } + + // Have the appcache associate its extra info with the request. + appcache::AppCacheInterceptor::SetExtraRequestInfo( + request, context ? context->appcache_service() : NULL, child_id, + request_data.appcache_host_id, request_data.resource_type); + + BeginRequestInternal(request); +} + +void ResourceDispatcherHost::OnReleaseDownloadedFile(int request_id) { + DCHECK(pending_requests_.end() == + pending_requests_.find( + GlobalRequestID(filter_->child_id(), request_id))); + UnregisterDownloadedTempFile(filter_->child_id(), request_id); +} + +void ResourceDispatcherHost::OnDataReceivedACK(int request_id) { + DataReceivedACK(filter_->child_id(), request_id); +} + +void ResourceDispatcherHost::DataReceivedACK(int child_id, + int request_id) { + PendingRequestList::iterator i = pending_requests_.find( + GlobalRequestID(child_id, request_id)); + if (i == pending_requests_.end()) + return; + + ResourceDispatcherHostRequestInfo* info = InfoForRequest(i->second); + + // Decrement the number of pending data messages. + info->DecrementPendingDataCount(); + + // If the pending data count was higher than the max, resume the request. + if (info->pending_data_count() == kMaxPendingDataMessages) { + // Decrement the pending data count one more time because we also + // incremented it before pausing the request. + info->DecrementPendingDataCount(); + + // Resume the request. + PauseRequest(child_id, request_id, false); + } +} + +void ResourceDispatcherHost::OnDataDownloadedACK(int request_id) { + // TODO(michaeln): maybe throttle DataDownloaded messages +} + +void ResourceDispatcherHost::RegisterDownloadedTempFile( + int child_id, int request_id, DeletableFileReference* reference) { + registered_temp_files_[child_id][request_id] = reference; + ChildProcessSecurityPolicy::GetInstance()->GrantReadFile( + child_id, reference->path()); +} + +void ResourceDispatcherHost::UnregisterDownloadedTempFile( + int child_id, int request_id) { + DeletableFilesMap& map = registered_temp_files_[child_id]; + DeletableFilesMap::iterator found = map.find(request_id); + if (found == map.end()) + return; + + ChildProcessSecurityPolicy::GetInstance()->RevokeAllPermissionsForFile( + child_id, found->second->path()); + map.erase(found); +} + +bool ResourceDispatcherHost::Send(IPC::Message* message) { + delete message; + return false; +} + +void ResourceDispatcherHost::OnUploadProgressACK(int request_id) { + int child_id = filter_->child_id(); + PendingRequestList::iterator i = pending_requests_.find( + GlobalRequestID(child_id, request_id)); + if (i == pending_requests_.end()) + return; + + ResourceDispatcherHostRequestInfo* info = InfoForRequest(i->second); + info->set_waiting_for_upload_progress_ack(false); +} + +void ResourceDispatcherHost::OnCancelRequest(int request_id) { + CancelRequest(filter_->child_id(), request_id, true); +} + +void ResourceDispatcherHost::OnFollowRedirect( + int request_id, + bool has_new_first_party_for_cookies, + const GURL& new_first_party_for_cookies) { + FollowDeferredRedirect(filter_->child_id(), request_id, + has_new_first_party_for_cookies, + new_first_party_for_cookies); +} + +ResourceHandler* ResourceDispatcherHost::CreateSafeBrowsingResourceHandler( + ResourceHandler* handler, int child_id, int route_id, + ResourceType::Type resource_type) { + return new SafeBrowsingResourceHandler( + handler, child_id, route_id, resource_type, safe_browsing_, this); +} + +ResourceDispatcherHostRequestInfo* +ResourceDispatcherHost::CreateRequestInfoForBrowserRequest( + ResourceHandler* handler, int child_id, int route_id, bool download) { + return new ResourceDispatcherHostRequestInfo(handler, + ChildProcessInfo::RENDER_PROCESS, + child_id, + route_id, + request_id_, + ResourceType::SUB_RESOURCE, + 0, // upload_size + download, // is_download + download, // allow_download + false, // has_user_gesture + -1, // host renderer id + -1); // host render view id +} + +void ResourceDispatcherHost::OnClosePageACK( + const ViewMsg_ClosePage_Params& params) { + if (params.for_cross_site_transition) { + // Closes for cross-site transitions are handled such that the cross-site + // transition continues. + GlobalRequestID global_id(params.new_render_process_host_id, + params.new_request_id); + PendingRequestList::iterator i = pending_requests_.find(global_id); + if (i != pending_requests_.end()) { + // The response we were meant to resume could have already been canceled. + ResourceDispatcherHostRequestInfo* info = InfoForRequest(i->second); + if (info->cross_site_handler()) + info->cross_site_handler()->ResumeResponse(); + } + } else { + // This is a tab close, so just forward the message to close it. + DCHECK(params.new_render_process_host_id == -1); + DCHECK(params.new_request_id == -1); + CallRenderViewHost(params.closing_process_id, + params.closing_route_id, + &RenderViewHost::ClosePageIgnoringUnloadEvents); + } +} + +// We are explicitly forcing the download of 'url'. +void ResourceDispatcherHost::BeginDownload( + const GURL& url, + const GURL& referrer, + const DownloadSaveInfo& save_info, + bool prompt_for_save_location, + int child_id, + int route_id, + net::URLRequestContext* request_context) { + if (is_shutdown_) + return; + + // Check if the renderer is permitted to request the requested URL. + if (!ChildProcessSecurityPolicy::GetInstance()-> + CanRequestURL(child_id, url)) { + VLOG(1) << "Denied unauthorized download request for " + << url.possibly_invalid_spec(); + return; + } + + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + NewRunnableFunction(&download_util::NotifyDownloadInitiated, + child_id, route_id)); + + // Ensure the Chrome plugins are loaded, as they may intercept network + // requests. Does nothing if they are already loaded. + PluginService::GetInstance()->LoadChromePlugins(this); + net::URLRequest* request = new net::URLRequest(url, this); + + request_id_--; + + scoped_refptr<ResourceHandler> handler( + new DownloadResourceHandler(this, + child_id, + route_id, + request_id_, + url, + download_file_manager_.get(), + request, + prompt_for_save_location, + save_info)); + + if (safe_browsing_->enabled()) { + handler = CreateSafeBrowsingResourceHandler(handler, child_id, route_id, + ResourceType::MAIN_FRAME); + } + + if (!net::URLRequest::IsHandledURL(url)) { + VLOG(1) << "Download request for unsupported protocol: " + << url.possibly_invalid_spec(); + return; + } + + request->set_method("GET"); + request->set_referrer(CommandLine::ForCurrentProcess()->HasSwitch( + switches::kNoReferrers) ? std::string() : referrer.spec()); + request->set_context(request_context); + request->set_load_flags(request->load_flags() | + net::LOAD_IS_DOWNLOAD); + + ResourceDispatcherHostRequestInfo* extra_info = + CreateRequestInfoForBrowserRequest(handler, child_id, route_id, true); + SetRequestInfo(request, extra_info); // Request takes ownership. + + BeginRequestInternal(request); +} + +// This function is only used for saving feature. +void ResourceDispatcherHost::BeginSaveFile( + const GURL& url, + const GURL& referrer, + int child_id, + int route_id, + net::URLRequestContext* request_context) { + if (is_shutdown_) + return; + + // Ensure the Chrome plugins are loaded, as they may intercept network + // requests. Does nothing if they are already loaded. + PluginService::GetInstance()->LoadChromePlugins(this); + + scoped_refptr<ResourceHandler> handler( + new SaveFileResourceHandler(child_id, + route_id, + url, + save_file_manager_.get())); + request_id_--; + + bool known_proto = net::URLRequest::IsHandledURL(url); + if (!known_proto) { + // Since any URLs which have non-standard scheme have been filtered + // by save manager(see GURL::SchemeIsStandard). This situation + // should not happen. + NOTREACHED(); + return; + } + + net::URLRequest* request = new net::URLRequest(url, this); + request->set_method("GET"); + request->set_referrer(CommandLine::ForCurrentProcess()->HasSwitch( + switches::kNoReferrers) ? std::string() : referrer.spec()); + // So far, for saving page, we need fetch content from cache, in the + // future, maybe we can use a configuration to configure this behavior. + request->set_load_flags(net::LOAD_PREFERRING_CACHE); + request->set_context(request_context); + + // Since we're just saving some resources we need, disallow downloading. + ResourceDispatcherHostRequestInfo* extra_info = + CreateRequestInfoForBrowserRequest(handler, child_id, route_id, false); + SetRequestInfo(request, extra_info); // Request takes ownership. + + BeginRequestInternal(request); +} + +void ResourceDispatcherHost::FollowDeferredRedirect( + int child_id, + int request_id, + bool has_new_first_party_for_cookies, + const GURL& new_first_party_for_cookies) { + PendingRequestList::iterator i = pending_requests_.find( + GlobalRequestID(child_id, request_id)); + if (i == pending_requests_.end() || !i->second->status().is_success()) { + DLOG(WARNING) << "FollowDeferredRedirect for invalid request"; + return; + } + + if (has_new_first_party_for_cookies) + i->second->set_first_party_for_cookies(new_first_party_for_cookies); + i->second->FollowDeferredRedirect(); +} + +void ResourceDispatcherHost::StartDeferredRequest(int process_unique_id, + int request_id) { + GlobalRequestID global_id(process_unique_id, request_id); + PendingRequestList::iterator i = pending_requests_.find(global_id); + if (i == pending_requests_.end()) { + // The request may have been destroyed + LOG(WARNING) << "Trying to resume a non-existent request (" + << process_unique_id << ", " << request_id << ")"; + return; + } + + // TODO(eroman): are there other considerations for paused or blocked + // requests? + + net::URLRequest* request = i->second; + InsertIntoResourceQueue(request, *InfoForRequest(request)); +} + +bool ResourceDispatcherHost::WillSendData(int child_id, + int request_id) { + PendingRequestList::iterator i = pending_requests_.find( + GlobalRequestID(child_id, request_id)); + if (i == pending_requests_.end()) { + NOTREACHED() << "WillSendData for invalid request"; + return false; + } + + ResourceDispatcherHostRequestInfo* info = InfoForRequest(i->second); + + info->IncrementPendingDataCount(); + if (info->pending_data_count() > kMaxPendingDataMessages) { + // We reached the max number of data messages that can be sent to + // the renderer for a given request. Pause the request and wait for + // the renderer to start processing them before resuming it. + PauseRequest(child_id, request_id, true); + return false; + } + + return true; +} + +void ResourceDispatcherHost::PauseRequest(int child_id, + int request_id, + bool pause) { + GlobalRequestID global_id(child_id, request_id); + PendingRequestList::iterator i = pending_requests_.find(global_id); + if (i == pending_requests_.end()) { + DLOG(WARNING) << "Pausing a request that wasn't found"; + return; + } + + ResourceDispatcherHostRequestInfo* info = InfoForRequest(i->second); + int pause_count = info->pause_count() + (pause ? 1 : -1); + if (pause_count < 0) { + NOTREACHED(); // Unbalanced call to pause. + return; + } + info->set_pause_count(pause_count); + + VLOG(1) << "To pause (" << pause << "): " << i->second->url().spec(); + + // If we're resuming, kick the request to start reading again. Run the read + // asynchronously to avoid recursion problems. + if (info->pause_count() == 0) { + MessageLoop::current()->PostTask(FROM_HERE, + method_runner_.NewRunnableMethod( + &ResourceDispatcherHost::ResumeRequest, global_id)); + } +} + +int ResourceDispatcherHost::GetOutstandingRequestsMemoryCost( + int child_id) const { + OutstandingRequestsMemoryCostMap::const_iterator entry = + outstanding_requests_memory_cost_map_.find(child_id); + return (entry == outstanding_requests_memory_cost_map_.end()) ? + 0 : entry->second; +} + +// The object died, so cancel and detach all requests associated with it except +// for downloads, which belong to the browser process even if initiated via a +// renderer. +void ResourceDispatcherHost::CancelRequestsForProcess(int child_id) { + CancelRequestsForRoute(child_id, -1 /* cancel all */); + registered_temp_files_.erase(child_id); +} + +void ResourceDispatcherHost::CancelRequestsForRoute(int child_id, + int route_id) { + // Since pending_requests_ is a map, we first build up a list of all of the + // matching requests to be cancelled, and then we cancel them. Since there + // may be more than one request to cancel, we cannot simply hold onto the map + // iterators found in the first loop. + + // Find the global ID of all matching elements. + std::vector<GlobalRequestID> matching_requests; + for (PendingRequestList::const_iterator i = pending_requests_.begin(); + i != pending_requests_.end(); ++i) { + if (i->first.child_id == child_id) { + ResourceDispatcherHostRequestInfo* info = InfoForRequest(i->second); + if (!info->is_download() && + (route_id == -1 || route_id == info->route_id())) { + matching_requests.push_back( + GlobalRequestID(child_id, i->first.request_id)); + } + } + } + + // Remove matches. + for (size_t i = 0; i < matching_requests.size(); ++i) { + PendingRequestList::iterator iter = + pending_requests_.find(matching_requests[i]); + // Although every matching request was in pending_requests_ when we built + // matching_requests, it is normal for a matching request to be not found + // in pending_requests_ after we have removed some matching requests from + // pending_requests_. For example, deleting a net::URLRequest that has + // exclusive (write) access to an HTTP cache entry may unblock another + // net::URLRequest that needs exclusive access to the same cache entry, and + // that net::URLRequest may complete and remove itself from + // pending_requests_. So we need to check that iter is not equal to + // pending_requests_.end(). + if (iter != pending_requests_.end()) + RemovePendingRequest(iter); + } + + // Now deal with blocked requests if any. + if (route_id != -1) { + if (blocked_requests_map_.find(std::pair<int, int>(child_id, route_id)) != + blocked_requests_map_.end()) { + CancelBlockedRequestsForRoute(child_id, route_id); + } + } else { + // We have to do all render views for the process |child_id|. + // Note that we have to do this in 2 passes as we cannot call + // CancelBlockedRequestsForRoute while iterating over + // blocked_requests_map_, as it modifies it. + std::set<int> route_ids; + for (BlockedRequestMap::const_iterator iter = blocked_requests_map_.begin(); + iter != blocked_requests_map_.end(); ++iter) { + if (iter->first.first == child_id) + route_ids.insert(iter->first.second); + } + for (std::set<int>::const_iterator iter = route_ids.begin(); + iter != route_ids.end(); ++iter) { + CancelBlockedRequestsForRoute(child_id, *iter); + } + } +} + +// Cancels the request and removes it from the list. +void ResourceDispatcherHost::RemovePendingRequest(int child_id, + int request_id) { + PendingRequestList::iterator i = pending_requests_.find( + GlobalRequestID(child_id, request_id)); + if (i == pending_requests_.end()) { + NOTREACHED() << "Trying to remove a request that's not here"; + return; + } + RemovePendingRequest(i); +} + +void ResourceDispatcherHost::RemovePendingRequest( + const PendingRequestList::iterator& iter) { + ResourceDispatcherHostRequestInfo* info = InfoForRequest(iter->second); + + // Remove the memory credit that we added when pushing the request onto + // the pending list. + IncrementOutstandingRequestsMemoryCost(-1 * info->memory_cost(), + info->child_id()); + + // Notify interested parties that the request object is going away. + if (info->login_handler()) + info->login_handler()->OnRequestCancelled(); + if (info->ssl_client_auth_handler()) + info->ssl_client_auth_handler()->OnRequestCancelled(); + resource_queue_.RemoveRequest(iter->first); + + delete iter->second; + pending_requests_.erase(iter); + + // If we have no more pending requests, then stop the load state monitor + if (pending_requests_.empty()) + update_load_states_timer_.Stop(); +} + +// net::URLRequest::Delegate --------------------------------------------------- + +void ResourceDispatcherHost::OnReceivedRedirect(net::URLRequest* request, + const GURL& new_url, + bool* defer_redirect) { + VLOG(1) << "OnReceivedRedirect: " << request->url().spec(); + ResourceDispatcherHostRequestInfo* info = InfoForRequest(request); + + DCHECK(request->status().is_success()); + + if (info->process_type() != ChildProcessInfo::PLUGIN_PROCESS && + !ChildProcessSecurityPolicy::GetInstance()-> + CanRequestURL(info->child_id(), new_url)) { + VLOG(1) << "Denied unauthorized request for " + << new_url.possibly_invalid_spec(); + + // Tell the renderer that this request was disallowed. + CancelRequestInternal(request, false); + return; + } + + NotifyReceivedRedirect(request, info->child_id(), new_url); + + if (HandleExternalProtocol(info->request_id(), info->child_id(), + info->route_id(), new_url, + info->resource_type(), info->resource_handler())) { + // The request is complete so we can remove it. + RemovePendingRequest(info->child_id(), info->request_id()); + return; + } + + scoped_refptr<ResourceResponse> response(new ResourceResponse); + PopulateResourceResponse(request, + info->replace_extension_localization_templates(), response); + if (!info->resource_handler()->OnRequestRedirected(info->request_id(), + new_url, + response, defer_redirect)) + CancelRequestInternal(request, false); +} + +void ResourceDispatcherHost::OnAuthRequired( + net::URLRequest* request, + net::AuthChallengeInfo* auth_info) { + if (request->load_flags() & net::LOAD_PREFETCH) { + request->CancelAuth(); + return; + } + // Create a login dialog on the UI thread to get authentication data, + // or pull from cache and continue on the IO thread. + // TODO(mpcomplete): We should block the parent tab while waiting for + // authentication. + // That would also solve the problem of the net::URLRequest being cancelled + // before we receive authentication. + ResourceDispatcherHostRequestInfo* info = InfoForRequest(request); + DCHECK(!info->login_handler()) << + "OnAuthRequired called with login_handler pending"; + info->set_login_handler(CreateLoginPrompt(auth_info, request)); +} + +void ResourceDispatcherHost::OnCertificateRequested( + net::URLRequest* request, + net::SSLCertRequestInfo* cert_request_info) { + DCHECK(request); + + if (cert_request_info->client_certs.empty()) { + // No need to query the user if there are no certs to choose from. + request->ContinueWithCertificate(NULL); + return; + } + + ResourceDispatcherHostRequestInfo* info = InfoForRequest(request); + DCHECK(!info->ssl_client_auth_handler()) << + "OnCertificateRequested called with ssl_client_auth_handler pending"; + info->set_ssl_client_auth_handler( + new SSLClientAuthHandler(request, cert_request_info)); + info->ssl_client_auth_handler()->SelectCertificate(); +} + +void ResourceDispatcherHost::OnSSLCertificateError( + net::URLRequest* request, + int cert_error, + net::X509Certificate* cert) { + DCHECK(request); + SSLManager::OnSSLCertificateError(this, request, cert_error, cert); +} + +void ResourceDispatcherHost::OnGetCookies( + net::URLRequest* request, + bool blocked_by_policy) { + VLOG(1) << "OnGetCookies: " << request->url().spec(); + + int render_process_id, render_view_id; + if (!RenderViewForRequest(request, &render_process_id, &render_view_id)) + return; + + net::URLRequestContext* context = request->context(); + + net::CookieMonster* cookie_monster = + context->cookie_store()->GetCookieMonster(); + net::CookieList cookie_list = + cookie_monster->GetAllCookiesForURL(request->url()); + CallRenderViewHostContentSettingsDelegate( + render_process_id, render_view_id, + &RenderViewHostDelegate::ContentSettings::OnCookiesRead, + request->url(), cookie_list, blocked_by_policy); +} + +void ResourceDispatcherHost::OnSetCookie(net::URLRequest* request, + const std::string& cookie_line, + const net::CookieOptions& options, + bool blocked_by_policy) { + VLOG(1) << "OnSetCookie: " << request->url().spec(); + + int render_process_id, render_view_id; + if (!RenderViewForRequest(request, &render_process_id, &render_view_id)) + return; + + CallRenderViewHostContentSettingsDelegate( + render_process_id, render_view_id, + &RenderViewHostDelegate::ContentSettings::OnCookieChanged, + request->url(), cookie_line, options, blocked_by_policy); +} + +void ResourceDispatcherHost::OnResponseStarted(net::URLRequest* request) { + VLOG(1) << "OnResponseStarted: " << request->url().spec(); + ResourceDispatcherHostRequestInfo* info = InfoForRequest(request); + if (PauseRequestIfNeeded(info)) { + VLOG(1) << "OnResponseStarted pausing: " << request->url().spec(); + return; + } + + if (request->status().is_success()) { + // We want to send a final upload progress message prior to sending + // the response complete message even if we're waiting for an ack to + // to a previous upload progress message. + info->set_waiting_for_upload_progress_ack(false); + MaybeUpdateUploadProgress(info, request); + + if (!CompleteResponseStarted(request)) { + CancelRequestInternal(request, false); + } else { + // Check if the handler paused the request in their OnResponseStarted. + if (PauseRequestIfNeeded(info)) { + VLOG(1) << "OnResponseStarted pausing2: " << request->url().spec(); + return; + } + + StartReading(request); + } + } else { + OnResponseCompleted(request); + } +} + +bool ResourceDispatcherHost::CompleteResponseStarted(net::URLRequest* request) { + ResourceDispatcherHostRequestInfo* info = InfoForRequest(request); + + scoped_refptr<ResourceResponse> response(new ResourceResponse); + PopulateResourceResponse(request, + info->replace_extension_localization_templates(), response); + + if (request->ssl_info().cert) { + int cert_id = + CertStore::GetInstance()->StoreCert(request->ssl_info().cert, + info->child_id()); + response->response_head.security_info = + SSLManager::SerializeSecurityInfo( + cert_id, request->ssl_info().cert_status, + request->ssl_info().security_bits, + request->ssl_info().connection_status); + } else { + // We should not have any SSL state. + DCHECK(!request->ssl_info().cert_status && + request->ssl_info().security_bits == -1 && + !request->ssl_info().connection_status); + } + + NotifyResponseStarted(request, info->child_id()); + info->set_called_on_response_started(true); + return info->resource_handler()->OnResponseStarted(info->request_id(), + response.get()); +} + +void ResourceDispatcherHost::CancelRequest(int child_id, + int request_id, + bool from_renderer) { + PendingRequestList::iterator i = pending_requests_.find( + GlobalRequestID(child_id, request_id)); + if (i == pending_requests_.end()) { + // We probably want to remove this warning eventually, but I wanted to be + // able to notice when this happens during initial development since it + // should be rare and may indicate a bug. + DLOG(WARNING) << "Canceling a request that wasn't found"; + return; + } + CancelRequestInternal(i->second, from_renderer); +} + +void ResourceDispatcherHost::CancelRequestInternal(net::URLRequest* request, + bool from_renderer) { + VLOG(1) << "CancelRequest: " << request->url().spec(); + + // WebKit will send us a cancel for downloads since it no longer handles them. + // In this case, ignore the cancel since we handle downloads in the browser. + ResourceDispatcherHostRequestInfo* info = InfoForRequest(request); + if (!from_renderer || !info->is_download()) { + if (info->login_handler()) { + info->login_handler()->OnRequestCancelled(); + info->set_login_handler(NULL); + } + if (info->ssl_client_auth_handler()) { + info->ssl_client_auth_handler()->OnRequestCancelled(); + info->set_ssl_client_auth_handler(NULL); + } + request->Cancel(); + // Our callers assume |request| is valid after we return. + DCHECK(IsValidRequest(request)); + } + + // Do not remove from the pending requests, as the request will still + // call AllDataReceived, and may even have more data before it does + // that. +} + +int ResourceDispatcherHost::IncrementOutstandingRequestsMemoryCost( + int cost, + int child_id) { + // Retrieve the previous value (defaulting to 0 if not found). + OutstandingRequestsMemoryCostMap::iterator prev_entry = + outstanding_requests_memory_cost_map_.find(child_id); + int new_cost = 0; + if (prev_entry != outstanding_requests_memory_cost_map_.end()) + new_cost = prev_entry->second; + + // Insert/update the total; delete entries when their value reaches 0. + new_cost += cost; + CHECK(new_cost >= 0); + if (new_cost == 0) + outstanding_requests_memory_cost_map_.erase(prev_entry); + else + outstanding_requests_memory_cost_map_[child_id] = new_cost; + + return new_cost; +} + +// static +int ResourceDispatcherHost::CalculateApproximateMemoryCost( + net::URLRequest* request) { + // The following fields should be a minor size contribution (experimentally + // on the order of 100). However since they are variable length, it could + // in theory be a sizeable contribution. + int strings_cost = request->extra_request_headers().ToString().size() + + request->original_url().spec().size() + + request->referrer().size() + + request->method().size(); + + int upload_cost = 0; + + // TODO(eroman): don't enable the upload throttling until we have data + // showing what a reasonable limit is (limiting to 25MB of uploads may + // be too restrictive). +#if 0 + // Sum all the (non-file) upload data attached to the request, if any. + if (request->has_upload()) { + const std::vector<net::UploadData::Element>& uploads = + request->get_upload()->elements(); + std::vector<net::UploadData::Element>::const_iterator iter; + for (iter = uploads.begin(); iter != uploads.end(); ++iter) { + if (iter->type() == net::UploadData::TYPE_BYTES) { + int64 element_size = iter->GetContentLength(); + // This cast should not result in truncation. + upload_cost += static_cast<int>(element_size); + } + } + } +#endif + + // Note that this expression will typically be dominated by: + // |kAvgBytesPerOutstandingRequest|. + return kAvgBytesPerOutstandingRequest + strings_cost + upload_cost; +} + +void ResourceDispatcherHost::BeginRequestInternal(net::URLRequest* request) { + DCHECK(!request->is_pending()); + ResourceDispatcherHostRequestInfo* info = InfoForRequest(request); + + // Add the memory estimate that starting this request will consume. + info->set_memory_cost(CalculateApproximateMemoryCost(request)); + int memory_cost = IncrementOutstandingRequestsMemoryCost(info->memory_cost(), + info->child_id()); + + // If enqueing/starting this request will exceed our per-process memory + // bound, abort it right away. + if (memory_cost > max_outstanding_requests_cost_per_process_) { + // We call "SimulateError()" as a way of setting the net::URLRequest's + // status -- it has no effect beyond this, since the request hasn't started. + request->SimulateError(net::ERR_INSUFFICIENT_RESOURCES); + + // TODO(eroman): this is kinda funky -- we insert the unstarted request into + // |pending_requests_| simply to please OnResponseCompleted(). + GlobalRequestID global_id(info->child_id(), info->request_id()); + pending_requests_[global_id] = request; + OnResponseCompleted(request); + return; + } + + std::pair<int, int> pair_id(info->child_id(), info->route_id()); + BlockedRequestMap::const_iterator iter = blocked_requests_map_.find(pair_id); + if (iter != blocked_requests_map_.end()) { + // The request should be blocked. + iter->second->push_back(request); + return; + } + + GlobalRequestID global_id(info->child_id(), info->request_id()); + pending_requests_[global_id] = request; + + // Give the resource handlers an opportunity to delay the net::URLRequest from + // being started. + // + // There are three cases: + // + // (1) if OnWillStart() returns false, the request is cancelled (regardless + // of whether |defer_start| was set). + // (2) If |defer_start| was set to true, then the request is not added + // into the resource queue, and will only be started in response to + // calling StartDeferredRequest(). + // (3) If |defer_start| is not set, then the request is inserted into + // the resource_queue_ (which may pause it further, or start it). + bool defer_start = false; + if (!info->resource_handler()->OnWillStart( + info->request_id(), request->url(), + &defer_start)) { + CancelRequestInternal(request, false); + return; + } + + if (!defer_start) { + InsertIntoResourceQueue(request, *info); + } +} + +void ResourceDispatcherHost::InsertIntoResourceQueue( + net::URLRequest* request, + const ResourceDispatcherHostRequestInfo& request_info) { + resource_queue_.AddRequest(request, request_info); + + // Make sure we have the load state monitor running + if (!update_load_states_timer_.IsRunning()) { + update_load_states_timer_.Start( + TimeDelta::FromMilliseconds(kUpdateLoadStatesIntervalMsec), + this, &ResourceDispatcherHost::UpdateLoadStates); + } +} + +bool ResourceDispatcherHost::PauseRequestIfNeeded( + ResourceDispatcherHostRequestInfo* info) { + if (info->pause_count() > 0) + info->set_is_paused(true); + return info->is_paused(); +} + +void ResourceDispatcherHost::ResumeRequest(const GlobalRequestID& request_id) { + PendingRequestList::iterator i = pending_requests_.find(request_id); + if (i == pending_requests_.end()) // The request may have been destroyed + return; + + net::URLRequest* request = i->second; + ResourceDispatcherHostRequestInfo* info = InfoForRequest(request); + if (!info->is_paused()) + return; + + VLOG(1) << "Resuming: " << i->second->url().spec(); + + info->set_is_paused(false); + + if (info->called_on_response_started()) { + if (info->has_started_reading()) { + OnReadCompleted(i->second, info->paused_read_bytes()); + } else { + StartReading(request); + } + } else { + OnResponseStarted(i->second); + } +} + +void ResourceDispatcherHost::StartReading(net::URLRequest* request) { + // Start reading. + int bytes_read = 0; + if (Read(request, &bytes_read)) { + OnReadCompleted(request, bytes_read); + } else if (!request->status().is_io_pending()) { + DCHECK(!InfoForRequest(request)->is_paused()); + // If the error is not an IO pending, then we're done reading. + OnResponseCompleted(request); + } +} + +bool ResourceDispatcherHost::Read(net::URLRequest* request, int* bytes_read) { + ResourceDispatcherHostRequestInfo* info = InfoForRequest(request); + DCHECK(!info->is_paused()); + + net::IOBuffer* buf; + int buf_size; + if (!info->resource_handler()->OnWillRead(info->request_id(), + &buf, &buf_size, -1)) { + return false; + } + + DCHECK(buf); + DCHECK(buf_size > 0); + + info->set_has_started_reading(true); + return request->Read(buf, buf_size, bytes_read); +} + +void ResourceDispatcherHost::OnReadCompleted(net::URLRequest* request, + int bytes_read) { + DCHECK(request); + VLOG(1) << "OnReadCompleted: " << request->url().spec(); + ResourceDispatcherHostRequestInfo* info = InfoForRequest(request); + + // OnReadCompleted can be called without Read (e.g., for chrome:// URLs). + // Make sure we know that a read has begun. + info->set_has_started_reading(true); + + if (PauseRequestIfNeeded(info)) { + info->set_paused_read_bytes(bytes_read); + VLOG(1) << "OnReadCompleted pausing: " << request->url().spec(); + return; + } + + if (request->status().is_success() && CompleteRead(request, &bytes_read)) { + // The request can be paused if we realize that the renderer is not + // servicing messages fast enough. + if (info->pause_count() == 0 && + Read(request, &bytes_read) && + request->status().is_success()) { + if (bytes_read == 0) { + CompleteRead(request, &bytes_read); + } else { + // Force the next CompleteRead / Read pair to run as a separate task. + // This avoids a fast, large network request from monopolizing the IO + // thread and starving other IO operations from running. + info->set_paused_read_bytes(bytes_read); + info->set_is_paused(true); + GlobalRequestID id(info->child_id(), info->request_id()); + MessageLoop::current()->PostTask( + FROM_HERE, + method_runner_.NewRunnableMethod( + &ResourceDispatcherHost::ResumeRequest, id)); + return; + } + } + } + + if (PauseRequestIfNeeded(info)) { + info->set_paused_read_bytes(bytes_read); + VLOG(1) << "OnReadCompleted (CompleteRead) pausing: " + << request->url().spec(); + return; + } + + // If the status is not IO pending then we've either finished (success) or we + // had an error. Either way, we're done! + if (!request->status().is_io_pending()) + OnResponseCompleted(request); +} + +bool ResourceDispatcherHost::CompleteRead(net::URLRequest* request, + int* bytes_read) { + if (!request || !request->status().is_success()) { + NOTREACHED(); + return false; + } + + ResourceDispatcherHostRequestInfo* info = InfoForRequest(request); + if (!info->resource_handler()->OnReadCompleted(info->request_id(), + bytes_read)) { + CancelRequestInternal(request, false); + return false; + } + + return *bytes_read != 0; +} + +void ResourceDispatcherHost::OnResponseCompleted(net::URLRequest* request) { + VLOG(1) << "OnResponseCompleted: " << request->url().spec(); + ResourceDispatcherHostRequestInfo* info = InfoForRequest(request); + + // If the load for a main frame has failed, track it in a histogram, + // since it will probably cause the user to see an error page. + if (!request->status().is_success() && + info->resource_type() == ResourceType::MAIN_FRAME && + request->status().os_error() != net::ERR_ABORTED) { + UMA_HISTOGRAM_CUSTOM_ENUMERATION("Net.ErrorCodesForMainFrame", + -request->status().os_error(), + GetAllNetErrorCodes()); + } + + std::string security_info; + const net::SSLInfo& ssl_info = request->ssl_info(); + if (ssl_info.cert != NULL) { + int cert_id = CertStore::GetInstance()->StoreCert(ssl_info.cert, + info->child_id()); + security_info = SSLManager::SerializeSecurityInfo( + cert_id, ssl_info.cert_status, ssl_info.security_bits, + ssl_info.connection_status); + } + + if (info->resource_handler()->OnResponseCompleted(info->request_id(), + request->status(), + security_info)) { + NotifyResponseCompleted(request, info->child_id()); + + // The request is complete so we can remove it. + RemovePendingRequest(info->child_id(), info->request_id()); + } + // If the handler's OnResponseCompleted returns false, we are deferring the + // call until later. We will notify the world and clean up when we resume. +} + +// static +ResourceDispatcherHostRequestInfo* ResourceDispatcherHost::InfoForRequest( + net::URLRequest* request) { + // Avoid writing this function twice by casting the cosnt version. + const net::URLRequest* const_request = request; + return const_cast<ResourceDispatcherHostRequestInfo*>( + InfoForRequest(const_request)); +} + +// static +const ResourceDispatcherHostRequestInfo* ResourceDispatcherHost::InfoForRequest( + const net::URLRequest* request) { + const ResourceDispatcherHostRequestInfo* info = + static_cast<const ResourceDispatcherHostRequestInfo*>( + request->GetUserData(NULL)); + DLOG_IF(WARNING, !info) << "Request doesn't seem to have our data"; + return info; +} + +// static +bool ResourceDispatcherHost::RenderViewForRequest( + const net::URLRequest* request, + int* render_process_host_id, + int* render_view_host_id) { + const ResourceDispatcherHostRequestInfo* info = InfoForRequest(request); + if (!info) { + *render_process_host_id = -1; + *render_view_host_id = -1; + return false; + } + + // If the request is from the worker process, find a tab that owns the worker. + if (info->process_type() == ChildProcessInfo::WORKER_PROCESS) { + // Need to display some related UI for this network request - pick an + // arbitrary parent to do so. + if (!WorkerService::GetInstance()->GetRendererForWorker( + info->child_id(), render_process_host_id, render_view_host_id)) { + *render_process_host_id = -1; + *render_view_host_id = -1; + return false; + } + } else { + *render_process_host_id = info->child_id(); + *render_view_host_id = info->route_id(); + } + return true; +} + +void ResourceDispatcherHost::AddObserver(Observer* obs) { + observer_list_.AddObserver(obs); +} + +void ResourceDispatcherHost::RemoveObserver(Observer* obs) { + observer_list_.RemoveObserver(obs); +} + +net::URLRequest* ResourceDispatcherHost::GetURLRequest( + const GlobalRequestID& request_id) const { + // This should be running in the IO loop. + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + + PendingRequestList::const_iterator i = pending_requests_.find(request_id); + if (i == pending_requests_.end()) + return NULL; + + return i->second; +} + +static int GetCertID(net::URLRequest* request, int child_id) { + if (request->ssl_info().cert) { + return CertStore::GetInstance()->StoreCert(request->ssl_info().cert, + child_id); + } + return 0; +} + +void ResourceDispatcherHost::NotifyResponseStarted(net::URLRequest* request, + int child_id) { + // Notify the observers on the IO thread. + FOR_EACH_OBSERVER(Observer, observer_list_, OnRequestStarted(this, request)); + + int render_process_id, render_view_id; + if (!RenderViewForRequest(request, &render_process_id, &render_view_id)) + return; + + // Notify the observers on the UI thread. + ResourceRequestDetails* detail = new ResourceRequestDetails( + request, GetCertID(request, child_id)); + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + NewRunnableFunction( + &ResourceDispatcherHost::NotifyOnUI<ResourceRequestDetails>, + NotificationType::RESOURCE_RESPONSE_STARTED, + render_process_id, render_view_id, detail)); +} + +void ResourceDispatcherHost::NotifyResponseCompleted(net::URLRequest* request, + int child_id) { + // Notify the observers on the IO thread. + FOR_EACH_OBSERVER(Observer, observer_list_, + OnResponseCompleted(this, request)); +} + +void ResourceDispatcherHost::NotifyReceivedRedirect(net::URLRequest* request, + int child_id, + const GURL& new_url) { + // Notify the observers on the IO thread. + FOR_EACH_OBSERVER(Observer, observer_list_, + OnReceivedRedirect(this, request, new_url)); + + int render_process_id, render_view_id; + if (!RenderViewForRequest(request, &render_process_id, &render_view_id)) + return; + + // Notify the observers on the UI thread. + ResourceRedirectDetails* detail = new ResourceRedirectDetails( + request, GetCertID(request, child_id), new_url); + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + NewRunnableFunction( + &ResourceDispatcherHost::NotifyOnUI<ResourceRedirectDetails>, + NotificationType::RESOURCE_RECEIVED_REDIRECT, + render_process_id, render_view_id, detail)); +} + +template <class T> +void ResourceDispatcherHost::NotifyOnUI(NotificationType type, + int render_process_id, + int render_view_id, + T* detail) { + RenderViewHost* rvh = + RenderViewHost::FromID(render_process_id, render_view_id); + if (rvh) { + RenderViewHostDelegate* rvhd = rvh->delegate(); + NotificationService::current()->Notify( + type, Source<RenderViewHostDelegate>(rvhd), Details<T>(detail)); + } + delete detail; +} + +namespace { + +// This function attempts to return the "more interesting" load state of |a| +// and |b|. We don't have temporal information about these load states +// (meaning we don't know when we transitioned into these states), so we just +// rank them according to how "interesting" the states are. +// +// We take advantage of the fact that the load states are an enumeration listed +// in the order in which they occur during the lifetime of a request, so we can +// regard states with larger numeric values as being further along toward +// completion. We regard those states as more interesting to report since they +// represent progress. +// +// For example, by this measure "tranferring data" is a more interesting state +// than "resolving host" because when we are transferring data we are actually +// doing something that corresponds to changes that the user might observe, +// whereas waiting for a host name to resolve implies being stuck. +// +net::LoadState MoreInterestingLoadState(net::LoadState a, net::LoadState b) { + return (a < b) ? b : a; +} + +// Carries information about a load state change. +struct LoadInfo { + GURL url; + net::LoadState load_state; + uint64 upload_position; + uint64 upload_size; +}; + +// Map from ProcessID+ViewID pair to LoadState +typedef std::map<std::pair<int, int>, LoadInfo> LoadInfoMap; + +// Used to marshall calls to LoadStateChanged from the IO to UI threads. We do +// them all as a single task to avoid spamming the UI thread. +class LoadInfoUpdateTask : public Task { + public: + virtual void Run() { + LoadInfoMap::const_iterator i; + for (i = info_map.begin(); i != info_map.end(); ++i) { + RenderViewHost* view = + RenderViewHost::FromID(i->first.first, i->first.second); + if (view) // The view could be gone at this point. + view->LoadStateChanged(i->second.url, i->second.load_state, + i->second.upload_position, + i->second.upload_size); + } + } + LoadInfoMap info_map; +}; + +} // namespace + +void ResourceDispatcherHost::UpdateLoadStates() { + // Populate this map with load state changes, and then send them on to the UI + // thread where they can be passed along to the respective RVHs. + LoadInfoMap info_map; + + PendingRequestList::const_iterator i; + + // Determine the largest upload size of all requests + // in each View (good chance it's zero). + std::map<std::pair<int, int>, uint64> largest_upload_size; + for (i = pending_requests_.begin(); i != pending_requests_.end(); ++i) { + net::URLRequest* request = i->second; + ResourceDispatcherHostRequestInfo* info = InfoForRequest(request); + uint64 upload_size = info->upload_size(); + if (request->GetLoadState() != net::LOAD_STATE_SENDING_REQUEST) + upload_size = 0; + std::pair<int, int> key(info->child_id(), info->route_id()); + if (upload_size && largest_upload_size[key] < upload_size) + largest_upload_size[key] = upload_size; + } + + for (i = pending_requests_.begin(); i != pending_requests_.end(); ++i) { + net::URLRequest* request = i->second; + net::LoadState load_state = request->GetLoadState(); + ResourceDispatcherHostRequestInfo* info = InfoForRequest(request); + + // We also poll for upload progress on this timer and send upload + // progress ipc messages to the plugin process. + bool update_upload_progress = MaybeUpdateUploadProgress(info, request); + + if (info->last_load_state() != load_state || update_upload_progress) { + std::pair<int, int> key(info->child_id(), info->route_id()); + + // If a request is uploading data, ignore all other requests so that the + // upload progress takes priority for being shown in the status bar. + if (largest_upload_size.find(key) != largest_upload_size.end() && + info->upload_size() < largest_upload_size[key]) + continue; + + info->set_last_load_state(load_state); + + net::LoadState to_insert; + LoadInfoMap::iterator existing = info_map.find(key); + if (existing == info_map.end()) { + to_insert = load_state; + } else { + to_insert = + MoreInterestingLoadState(existing->second.load_state, load_state); + if (to_insert == existing->second.load_state) + continue; + } + LoadInfo& load_info = info_map[key]; + load_info.url = request->url(); + load_info.load_state = to_insert; + load_info.upload_size = info->upload_size(); + load_info.upload_position = request->GetUploadProgress(); + } + } + + if (info_map.empty()) + return; + + LoadInfoUpdateTask* task = new LoadInfoUpdateTask; + task->info_map.swap(info_map); + BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, task); +} + +// Calls the ResourceHandler to send upload progress messages to the renderer. +// Returns true iff an upload progress message should be sent to the UI thread. +bool ResourceDispatcherHost::MaybeUpdateUploadProgress( + ResourceDispatcherHostRequestInfo *info, + net::URLRequest *request) { + + if (!info->upload_size() || info->waiting_for_upload_progress_ack()) + return false; + + uint64 size = info->upload_size(); + uint64 position = request->GetUploadProgress(); + if (position == info->last_upload_position()) + return false; // no progress made since last time + + const uint64 kHalfPercentIncrements = 200; + const TimeDelta kOneSecond = TimeDelta::FromMilliseconds(1000); + + uint64 amt_since_last = position - info->last_upload_position(); + TimeDelta time_since_last = TimeTicks::Now() - info->last_upload_ticks(); + + bool is_finished = (size == position); + bool enough_new_progress = (amt_since_last > (size / kHalfPercentIncrements)); + bool too_much_time_passed = time_since_last > kOneSecond; + + if (is_finished || enough_new_progress || too_much_time_passed) { + if (request->load_flags() & net::LOAD_ENABLE_UPLOAD_PROGRESS) { + info->resource_handler()->OnUploadProgress(info->request_id(), + position, size); + info->set_waiting_for_upload_progress_ack(true); + } + info->set_last_upload_ticks(TimeTicks::Now()); + info->set_last_upload_position(position); + return true; + } + return false; +} + +void ResourceDispatcherHost::BlockRequestsForRoute(int child_id, int route_id) { + std::pair<int, int> key(child_id, route_id); + DCHECK(blocked_requests_map_.find(key) == blocked_requests_map_.end()) << + "BlockRequestsForRoute called multiple time for the same RVH"; + blocked_requests_map_[key] = new BlockedRequestsList(); +} + +void ResourceDispatcherHost::ResumeBlockedRequestsForRoute(int child_id, + int route_id) { + ProcessBlockedRequestsForRoute(child_id, route_id, false); +} + +void ResourceDispatcherHost::CancelBlockedRequestsForRoute(int child_id, + int route_id) { + ProcessBlockedRequestsForRoute(child_id, route_id, true); +} + +void ResourceDispatcherHost::ProcessBlockedRequestsForRoute( + int child_id, + int route_id, + bool cancel_requests) { + BlockedRequestMap::iterator iter = blocked_requests_map_.find( + std::pair<int, int>(child_id, route_id)); + if (iter == blocked_requests_map_.end()) { + // It's possible to reach here if the renderer crashed while an interstitial + // page was showing. + return; + } + + BlockedRequestsList* requests = iter->second; + + // Removing the vector from the map unblocks any subsequent requests. + blocked_requests_map_.erase(iter); + + for (BlockedRequestsList::iterator req_iter = requests->begin(); + req_iter != requests->end(); ++req_iter) { + // Remove the memory credit that we added when pushing the request onto + // the blocked list. + net::URLRequest* request = *req_iter; + ResourceDispatcherHostRequestInfo* info = InfoForRequest(request); + IncrementOutstandingRequestsMemoryCost(-1 * info->memory_cost(), + info->child_id()); + if (cancel_requests) + delete request; + else + BeginRequestInternal(request); + } + + delete requests; +} + +bool ResourceDispatcherHost::IsValidRequest(net::URLRequest* request) { + if (!request) + return false; + ResourceDispatcherHostRequestInfo* info = InfoForRequest(request); + return pending_requests_.find( + GlobalRequestID(info->child_id(), info->request_id())) != + pending_requests_.end(); +} + +// static +void ResourceDispatcherHost::ApplyExtensionLocalizationFilter( + const GURL& url, + const ResourceType::Type& resource_type, + ResourceDispatcherHostRequestInfo* request_info) { + // Apply filter to chrome extension CSS files. + if (url.SchemeIs(chrome::kExtensionScheme) && + resource_type == ResourceType::STYLESHEET) + request_info->set_replace_extension_localization_templates(); +} + +// static +net::RequestPriority ResourceDispatcherHost::DetermineRequestPriority( + ResourceType::Type type) { + // Determine request priority based on how critical this resource typically + // is to user-perceived page load performance. Important considerations are: + // * Can this resource block the download of other resources. + // * Can this resource block the rendering of the page. + // * How useful is the page to the user if this resource is not loaded yet. + switch (type) { + // Main frames are the highest priority because they can block nearly every + // type of other resource and there is no useful display without them. + // Sub frames are a close second, however it is a common pattern to wrap + // ads in an iframe or even in multiple nested iframes. It is worth + // investigating if there is a better priority for them. + case ResourceType::MAIN_FRAME: + case ResourceType::SUB_FRAME: + return net::HIGHEST; + + // Stylesheets and scripts can block rendering and loading of other + // resources. Fonts can block text from rendering. + case ResourceType::STYLESHEET: + case ResourceType::SCRIPT: + case ResourceType::FONT_RESOURCE: + return net::MEDIUM; + + // Sub resources, objects and media are lower priority than potentially + // blocking stylesheets, scripts and fonts, but are higher priority than + // images because if they exist they are probably more central to the page + // focus than images on the page. + case ResourceType::SUB_RESOURCE: + case ResourceType::OBJECT: + case ResourceType::MEDIA: + case ResourceType::WORKER: + case ResourceType::SHARED_WORKER: + return net::LOW; + + // Images are the "lowest" priority because they typically do not block + // downloads or rendering and most pages have some useful content without + // them. + case ResourceType::IMAGE: + return net::LOWEST; + + // Prefetches are at a lower priority than even LOWEST, since they + // are not even required for rendering of the current page. + case ResourceType::PREFETCH: + return net::IDLE; + + default: + // When new resource types are added, their priority must be considered. + NOTREACHED(); + return net::LOW; + } +} + +// static +bool ResourceDispatcherHost::is_prefetch_enabled() { + return is_prefetch_enabled_; +} + +// static +void ResourceDispatcherHost::set_is_prefetch_enabled(bool value) { + is_prefetch_enabled_ = value; +} + +// static +bool ResourceDispatcherHost::is_prefetch_enabled_ = false; diff --git a/content/browser/renderer_host/resource_dispatcher_host.h b/content/browser/renderer_host/resource_dispatcher_host.h new file mode 100644 index 0000000..fb78dcc --- /dev/null +++ b/content/browser/renderer_host/resource_dispatcher_host.h @@ -0,0 +1,516 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This is the browser side of the resource dispatcher, it receives requests +// from the child process (i.e. [Renderer, Plugin, Worker]ProcessHost), and +// dispatches them to URLRequests. It then forwards the messages from the +// URLRequests back to the correct process for handling. +// +// See http://dev.chromium.org/developers/design-documents/multi-process-resource-loading + +#ifndef CONTENT_BROWSER_RENDERER_HOST_RESOURCE_DISPATCHER_HOST_H_ +#define CONTENT_BROWSER_RENDERER_HOST_RESOURCE_DISPATCHER_HOST_H_ +#pragma once + +#include <map> +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "base/gtest_prod_util.h" +#include "base/observer_list.h" +#include "base/scoped_ptr.h" +#include "base/timer.h" +#include "chrome/common/child_process_info.h" +#include "chrome/common/notification_type.h" +#include "content/browser/renderer_host/resource_queue.h" +#include "ipc/ipc_message.h" +#include "net/url_request/url_request.h" +#include "webkit/glue/resource_type.h" + +class CrossSiteResourceHandler; +class DownloadFileManager; +class DownloadRequestLimiter; +class LoginHandler; +class NotificationDetails; +class PluginService; +class ResourceDispatcherHostRequestInfo; +class ResourceHandler; +class ResourceMessageFilter; +class SafeBrowsingService; +class SaveFileManager; +class SSLClientAuthHandler; +class UserScriptListener; +class WebKitThread; +struct DownloadSaveInfo; +struct GlobalRequestID; +struct ViewHostMsg_Resource_Request; +struct ViewMsg_ClosePage_Params; + +namespace net { +class URLRequestContext; +} // namespace net + +namespace webkit_blob { +class DeletableFileReference; +} + +class ResourceDispatcherHost : public net::URLRequest::Delegate { + public: + class Observer { + public: + virtual ~Observer() {} + virtual void OnRequestStarted(ResourceDispatcherHost* resource_dispatcher, + net::URLRequest* request) = 0; + virtual void OnResponseCompleted( + ResourceDispatcherHost* resource_dispatcher, + net::URLRequest* request) = 0; + virtual void OnReceivedRedirect(ResourceDispatcherHost* resource_dispatcher, + net::URLRequest* request, + const GURL& new_url) = 0; + }; + + ResourceDispatcherHost(); + ~ResourceDispatcherHost(); + + void Initialize(); + + // Puts the resource dispatcher host in an inactive state (unable to begin + // new requests). Cancels all pending requests. + void Shutdown(); + + // Returns true if the message was a resource message that was processed. + // If it was, message_was_ok will be false iff the message was corrupt. + bool OnMessageReceived(const IPC::Message& message, + ResourceMessageFilter* filter, + bool* message_was_ok); + + // Initiates a download from the browser process (as opposed to a resource + // request from the renderer or another child process). + void BeginDownload(const GURL& url, + const GURL& referrer, + const DownloadSaveInfo& save_info, + bool prompt_for_save_location, + int process_unique_id, + int route_id, + net::URLRequestContext* request_context); + + // Initiates a save file from the browser process (as opposed to a resource + // request from the renderer or another child process). + void BeginSaveFile(const GURL& url, + const GURL& referrer, + int process_unique_id, + int route_id, + net::URLRequestContext* request_context); + + // Cancels the given request if it still exists. We ignore cancels from the + // renderer in the event of a download. + void CancelRequest(int process_unique_id, + int request_id, + bool from_renderer); + + // Follows a deferred redirect for the given request. + // new_first_party_for_cookies, if non-empty, is the new cookie policy URL + // for the redirected URL. If the cookie policy URL needs changing, pass + // true as has_new_first_party_for_cookies and the new cookie policy URL as + // new_first_party_for_cookies. Otherwise, pass false as + // has_new_first_party_for_cookies, and new_first_party_for_cookies will not + // be used. + void FollowDeferredRedirect(int process_unique_id, + int request_id, + bool has_new_first_party_for_cookies, + const GURL& new_first_party_for_cookies); + + // Starts a request that was deferred during ResourceHandler::OnWillStart(). + void StartDeferredRequest(int process_unique_id, int request_id); + + // Returns true if it's ok to send the data. If there are already too many + // data messages pending, it pauses the request and returns false. In this + // case the caller should not send the data. + bool WillSendData(int process_unique_id, int request_id); + + // Pauses or resumes network activity for a particular request. + void PauseRequest(int process_unique_id, int request_id, bool pause); + + // Returns the number of pending requests. This is designed for the unittests + int pending_requests() const { + return static_cast<int>(pending_requests_.size()); + } + + // Intended for unit-tests only. Returns the memory cost of all the + // outstanding requests (pending and blocked) for |process_unique_id|. + int GetOutstandingRequestsMemoryCost(int process_unique_id) const; + + // Intended for unit-tests only. Overrides the outstanding requests bound. + void set_max_outstanding_requests_cost_per_process(int limit) { + max_outstanding_requests_cost_per_process_ = limit; + } + + // The average private bytes increase of the browser for each new pending + // request. Experimentally obtained. + static const int kAvgBytesPerOutstandingRequest = 4400; + + DownloadFileManager* download_file_manager() const { + return download_file_manager_; + } + + DownloadRequestLimiter* download_request_limiter() const { + return download_request_limiter_.get(); + } + + SaveFileManager* save_file_manager() const { + return save_file_manager_; + } + + SafeBrowsingService* safe_browsing_service() const { + return safe_browsing_; + } + + WebKitThread* webkit_thread() const { + return webkit_thread_.get(); + } + + // Called when the onunload handler for a cross-site request has finished. + void OnClosePageACK(const ViewMsg_ClosePage_Params& params); + + // Force cancels any pending requests for the given process. + void CancelRequestsForProcess(int process_unique_id); + + // Force cancels any pending requests for the given route id. This method + // acts like CancelRequestsForProcess when route_id is -1. + void CancelRequestsForRoute(int process_unique_id, int route_id); + + // net::URLRequest::Delegate + virtual void OnReceivedRedirect(net::URLRequest* request, + const GURL& new_url, + bool* defer_redirect); + virtual void OnAuthRequired(net::URLRequest* request, + net::AuthChallengeInfo* auth_info); + virtual void OnCertificateRequested( + net::URLRequest* request, + net::SSLCertRequestInfo* cert_request_info); + virtual void OnSSLCertificateError(net::URLRequest* request, + int cert_error, + net::X509Certificate* cert); + virtual void OnGetCookies(net::URLRequest* request, + bool blocked_by_policy); + virtual void OnSetCookie(net::URLRequest* request, + const std::string& cookie_line, + const net::CookieOptions& options, + bool blocked_by_policy); + virtual void OnResponseStarted(net::URLRequest* request); + virtual void OnReadCompleted(net::URLRequest* request, int bytes_read); + void OnResponseCompleted(net::URLRequest* request); + + // Helper functions to get our extra data out of a request. The given request + // must have been one we created so that it has the proper extra data pointer. + static ResourceDispatcherHostRequestInfo* InfoForRequest( + net::URLRequest* request); + static const ResourceDispatcherHostRequestInfo* InfoForRequest( + const net::URLRequest* request); + + // Extracts the render view/process host's identifiers from the given request + // and places them in the given out params (both required). If there are no + // such IDs associated with the request (such as non-page-related requests), + // this function will return false and both out params will be -1. + static bool RenderViewForRequest(const net::URLRequest* request, + int* render_process_host_id, + int* render_view_host_id); + + // Adds an observer. The observer will be called on the IO thread. To + // observe resource events on the UI thread, subscribe to the + // NOTIFY_RESOURCE_* notifications of the notification service. + void AddObserver(Observer* obs); + + // Removes an observer. + void RemoveObserver(Observer* obs); + + // Retrieves a net::URLRequest. Must be called from the IO thread. + net::URLRequest* GetURLRequest(const GlobalRequestID& request_id) const; + + // Notifies our observers that a request has been cancelled. + void NotifyResponseCompleted(net::URLRequest* request, int process_unique_id); + + void RemovePendingRequest(int process_unique_id, int request_id); + + // Causes all new requests for the route identified by + // |process_unique_id| and |route_id| to be blocked (not being + // started) until ResumeBlockedRequestsForRoute or + // CancelBlockedRequestsForRoute is called. + void BlockRequestsForRoute(int process_unique_id, int route_id); + + // Resumes any blocked request for the specified route id. + void ResumeBlockedRequestsForRoute(int process_unique_id, int route_id); + + // Cancels any blocked request for the specified route id. + void CancelBlockedRequestsForRoute(int process_unique_id, int route_id); + + // Decrements the pending_data_count for the request and resumes + // the request if it was paused due to too many pending data + // messages sent. + void DataReceivedACK(int process_unique_id, int request_id); + + // Maintains a collection of temp files created in support of + // the download_to_file capability. Used to grant access to the + // child process and to defer deletion of the file until it's + // no longer needed. + void RegisterDownloadedTempFile( + int child_id, int request_id, + webkit_blob::DeletableFileReference* reference); + void UnregisterDownloadedTempFile(int child_id, int request_id); + + // Needed for the sync IPC message dispatcher macros. + bool Send(IPC::Message* message); + + // Controls if we launch or squash prefetch requests as they arrive + // from renderers. + static bool is_prefetch_enabled(); + static void set_is_prefetch_enabled(bool value); + + private: + FRIEND_TEST_ALL_PREFIXES(ResourceDispatcherHostTest, + TestBlockedRequestsProcessDies); + FRIEND_TEST_ALL_PREFIXES(ResourceDispatcherHostTest, + IncrementOutstandingRequestsMemoryCost); + FRIEND_TEST_ALL_PREFIXES(ResourceDispatcherHostTest, + CalculateApproximateMemoryCost); + FRIEND_TEST_ALL_PREFIXES(ApplyExtensionLocalizationFilterTest, WrongScheme); + FRIEND_TEST_ALL_PREFIXES(ApplyExtensionLocalizationFilterTest, GoodScheme); + FRIEND_TEST_ALL_PREFIXES(ApplyExtensionLocalizationFilterTest, + GoodSchemeWrongResourceType); + + class ShutdownTask; + + friend class ShutdownTask; + + // Associates the given info with the given request. The info will then be + // owned by the request. + void SetRequestInfo(net::URLRequest* request, + ResourceDispatcherHostRequestInfo* info); + + // A shutdown helper that runs on the IO thread. + void OnShutdown(); + + // Returns true if the request is paused. + bool PauseRequestIfNeeded(ResourceDispatcherHostRequestInfo* info); + + // Resumes the given request by calling OnResponseStarted or OnReadCompleted. + void ResumeRequest(const GlobalRequestID& request_id); + + // Internal function to start reading for the first time. + void StartReading(net::URLRequest* request); + + // Reads data from the response using our internal buffer as async IO. + // Returns true if data is available immediately, false otherwise. If the + // return value is false, we will receive a OnReadComplete() callback later. + bool Read(net::URLRequest* request, int* bytes_read); + + // Internal function to finish an async IO which has completed. Returns + // true if there is more data to read (e.g. we haven't read EOF yet and + // no errors have occurred). + bool CompleteRead(net::URLRequest*, int* bytes_read); + + // Internal function to finish handling the ResponseStarted message. Returns + // true on success. + bool CompleteResponseStarted(net::URLRequest* request); + + // Helper function for regular and download requests. + void BeginRequestInternal(net::URLRequest* request); + + // Helper function that cancels |request|. + void CancelRequestInternal(net::URLRequest* request, bool from_renderer); + + // Helper function that inserts |request| into the resource queue. + void InsertIntoResourceQueue( + net::URLRequest* request, + const ResourceDispatcherHostRequestInfo& request_info); + + // Updates the "cost" of outstanding requests for |process_unique_id|. + // The "cost" approximates how many bytes are consumed by all the in-memory + // data structures supporting this request (net::URLRequest object, + // HttpNetworkTransaction, etc...). + // The value of |cost| is added to the running total, and the resulting + // sum is returned. + int IncrementOutstandingRequestsMemoryCost(int cost, + int process_unique_id); + + // Estimate how much heap space |request| will consume to run. + static int CalculateApproximateMemoryCost(net::URLRequest* request); + + // The list of all requests that we have pending. This list is not really + // optimized, and assumes that we have relatively few requests pending at once + // since some operations require brute-force searching of the list. + // + // It may be enhanced in the future to provide some kind of prioritization + // mechanism. We should also consider a hashtable or binary tree if it turns + // out we have a lot of things here. + typedef std::map<GlobalRequestID, net::URLRequest*> PendingRequestList; + + // Deletes the pending request identified by the iterator passed in. + // This function will invalidate the iterator passed in. Callers should + // not rely on this iterator being valid on return. + void RemovePendingRequest(const PendingRequestList::iterator& iter); + + // Notify our observers that we started receiving a response for a request. + void NotifyResponseStarted(net::URLRequest* request, int process_unique_id); + + // Notify our observers that a request has been redirected. + void NotifyReceivedRedirect(net::URLRequest* request, + int process_unique_id, + const GURL& new_url); + + // Tries to handle the url with an external protocol. If the request is + // handled, the function returns true. False otherwise. + bool HandleExternalProtocol(int request_id, + int process_unique_id, + int route_id, + const GURL& url, + ResourceType::Type resource_type, + ResourceHandler* handler); + + // Checks all pending requests and updates the load states and upload + // progress if necessary. + void UpdateLoadStates(); + + // Checks the upload state and sends an update if one is necessary. + bool MaybeUpdateUploadProgress(ResourceDispatcherHostRequestInfo *info, + net::URLRequest *request); + + // Resumes or cancels (if |cancel_requests| is true) any blocked requests. + void ProcessBlockedRequestsForRoute(int process_unique_id, + int route_id, + bool cancel_requests); + + void OnRequestResource(const IPC::Message& msg, + int request_id, + const ViewHostMsg_Resource_Request& request_data); + void OnSyncLoad(int request_id, + const ViewHostMsg_Resource_Request& request_data, + IPC::Message* sync_result); + void BeginRequest(int request_id, + const ViewHostMsg_Resource_Request& request_data, + IPC::Message* sync_result, // only valid for sync + int route_id); // only valid for async + void OnDataReceivedACK(int request_id); + void OnDataDownloadedACK(int request_id); + void OnUploadProgressACK(int request_id); + void OnCancelRequest(int request_id); + void OnFollowRedirect(int request_id, + bool has_new_first_party_for_cookies, + const GURL& new_first_party_for_cookies); + void OnReleaseDownloadedFile(int request_id); + + ResourceHandler* CreateSafeBrowsingResourceHandler( + ResourceHandler* handler, int child_id, int route_id, + ResourceType::Type resource_type); + + // Creates ResourceDispatcherHostRequestInfo for a browser-initiated request + // (a download or a page save). |download| should be true if the request + // is a file download. + ResourceDispatcherHostRequestInfo* CreateRequestInfoForBrowserRequest( + ResourceHandler* handler, int child_id, int route_id, bool download); + + // Returns true if |request| is in |pending_requests_|. + bool IsValidRequest(net::URLRequest* request); + + // Sets replace_extension_localization_templates on all text/css requests that + // have "chrome-extension://" scheme. + static void ApplyExtensionLocalizationFilter( + const GURL& url, + const ResourceType::Type& resource_type, + ResourceDispatcherHostRequestInfo* request_info); + + // Determine request priority based on how critical this resource typically + // is to user-perceived page load performance. + static net::RequestPriority DetermineRequestPriority(ResourceType::Type type); + + // Sends the given notification on the UI thread. The RenderViewHost's + // controller is used as the source. + template <class T> + static void NotifyOnUI(NotificationType type, + int render_process_id, + int render_view_id, + T* detail); + + PendingRequestList pending_requests_; + + // Collection of temp files downloaded for child processes via + // the download_to_file mechanism. We avoid deleting them until + // the client no longer needs them. + typedef std::map<int, scoped_refptr<webkit_blob::DeletableFileReference> > + DeletableFilesMap; // key is request id + typedef std::map<int, DeletableFilesMap> + RegisteredTempFiles; // key is child process id + RegisteredTempFiles registered_temp_files_; + + // A timer that periodically calls UpdateLoadStates while pending_requests_ + // is not empty. + base::RepeatingTimer<ResourceDispatcherHost> update_load_states_timer_; + + // Handles the resource requests from the moment we want to start them. + ResourceQueue resource_queue_; + + // We own the download file writing thread and manager + scoped_refptr<DownloadFileManager> download_file_manager_; + + // Determines whether a download is allowed. + scoped_refptr<DownloadRequestLimiter> download_request_limiter_; + + // We own the save file manager. + scoped_refptr<SaveFileManager> save_file_manager_; + + scoped_refptr<UserScriptListener> user_script_listener_; + + scoped_refptr<SafeBrowsingService> safe_browsing_; + + // We own the WebKit thread and see to its destruction. + scoped_ptr<WebKitThread> webkit_thread_; + + // Request ID for browser initiated requests. request_ids generated by + // child processes are counted up from 0, while browser created requests + // start at -2 and go down from there. (We need to start at -2 because -1 is + // used as a special value all over the resource_dispatcher_host for + // uninitialized variables.) This way, we no longer have the unlikely (but + // observed in the real world!) event where we have two requests with the same + // request_id_. + int request_id_; + + // List of objects observing resource dispatching. + ObserverList<Observer> observer_list_; + + // For running tasks. + ScopedRunnableMethodFactory<ResourceDispatcherHost> method_runner_; + + // True if the resource dispatcher host has been shut down. + bool is_shutdown_; + + typedef std::vector<net::URLRequest*> BlockedRequestsList; + typedef std::pair<int, int> ProcessRouteIDs; + typedef std::map<ProcessRouteIDs, BlockedRequestsList*> BlockedRequestMap; + BlockedRequestMap blocked_requests_map_; + + // Maps the process_unique_ids to the approximate number of bytes + // being used to service its resource requests. No entry implies 0 cost. + typedef std::map<int, int> OutstandingRequestsMemoryCostMap; + OutstandingRequestsMemoryCostMap outstanding_requests_memory_cost_map_; + + // |max_outstanding_requests_cost_per_process_| is the upper bound on how + // many outstanding requests can be issued per child process host. + // The constraint is expressed in terms of bytes (where the cost of + // individual requests is given by CalculateApproximateMemoryCost). + // The total number of outstanding requests is roughly: + // (max_outstanding_requests_cost_per_process_ / + // kAvgBytesPerOutstandingRequest) + int max_outstanding_requests_cost_per_process_; + + // Used during IPC message dispatching so that the handlers can get a pointer + // to the source of the message. + ResourceMessageFilter* filter_; + + static bool is_prefetch_enabled_; + + DISALLOW_COPY_AND_ASSIGN(ResourceDispatcherHost); +}; + +#endif // CONTENT_BROWSER_RENDERER_HOST_RESOURCE_DISPATCHER_HOST_H_ diff --git a/content/browser/renderer_host/resource_dispatcher_host_request_info.cc b/content/browser/renderer_host/resource_dispatcher_host_request_info.cc new file mode 100644 index 0000000..695889e --- /dev/null +++ b/content/browser/renderer_host/resource_dispatcher_host_request_info.cc @@ -0,0 +1,67 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "content/browser/renderer_host/resource_dispatcher_host_request_info.h" + +#include "chrome/browser/ssl/ssl_client_auth_handler.h" +#include "chrome/browser/ui/login/login_prompt.h" +#include "content/browser/renderer_host/resource_handler.h" +#include "webkit/blob/blob_data.h" + +ResourceDispatcherHostRequestInfo::ResourceDispatcherHostRequestInfo( + ResourceHandler* handler, + ChildProcessInfo::ProcessType process_type, + int child_id, + int route_id, + int request_id, + ResourceType::Type resource_type, + uint64 upload_size, + bool is_download, + bool allow_download, + bool has_user_gesture, + int host_renderer_id, + int host_render_view_id) + : resource_handler_(handler), + cross_site_handler_(NULL), + process_type_(process_type), + child_id_(child_id), + route_id_(route_id), + request_id_(request_id), + pending_data_count_(0), + is_download_(is_download), + allow_download_(allow_download), + has_user_gesture_(has_user_gesture), + pause_count_(0), + resource_type_(resource_type), + replace_extension_localization_templates_(false), + last_load_state_(net::LOAD_STATE_IDLE), + upload_size_(upload_size), + last_upload_position_(0), + waiting_for_upload_progress_ack_(false), + memory_cost_(0), + is_paused_(false), + called_on_response_started_(false), + has_started_reading_(false), + paused_read_bytes_(0), + host_renderer_id_(host_renderer_id), + host_render_view_id_(host_render_view_id) { +} + +ResourceDispatcherHostRequestInfo::~ResourceDispatcherHostRequestInfo() { + resource_handler_->OnRequestClosed(); +} + +void ResourceDispatcherHostRequestInfo::set_login_handler(LoginHandler* lh) { + login_handler_ = lh; +} + +void ResourceDispatcherHostRequestInfo::set_ssl_client_auth_handler( + SSLClientAuthHandler* s) { + ssl_client_auth_handler_ = s; +} + +void ResourceDispatcherHostRequestInfo::set_requested_blob_data( + webkit_blob::BlobData* data) { + requested_blob_data_ = data; +} diff --git a/content/browser/renderer_host/resource_dispatcher_host_request_info.h b/content/browser/renderer_host/resource_dispatcher_host_request_info.h new file mode 100644 index 0000000..c05c4f9 --- /dev/null +++ b/content/browser/renderer_host/resource_dispatcher_host_request_info.h @@ -0,0 +1,241 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CONTENT_BROWSER_RENDERER_HOST_RESOURCE_DISPATCHER_HOST_REQUEST_INFO_H_ +#define CONTENT_BROWSER_RENDERER_HOST_RESOURCE_DISPATCHER_HOST_REQUEST_INFO_H_ +#pragma once + +#include <string> + +#include "base/basictypes.h" +#include "base/time.h" +#include "chrome/common/child_process_info.h" +#include "net/base/load_states.h" +#include "net/url_request/url_request.h" +#include "webkit/glue/resource_type.h" + +class CrossSiteResourceHandler; +class LoginHandler; +class ResourceDispatcherHost; +class ResourceHandler; +class SSLClientAuthHandler; + +namespace webkit_blob { +class BlobData; +} + +// Holds the data ResourceDispatcherHost associates with each request. +// Retrieve this data by calling ResourceDispatcherHost::InfoForRequest. +class ResourceDispatcherHostRequestInfo : public net::URLRequest::UserData { + public: + // This will take a reference to the handler. + ResourceDispatcherHostRequestInfo( + ResourceHandler* handler, + ChildProcessInfo::ProcessType process_type, + int child_id, + int route_id, + int request_id, + ResourceType::Type resource_type, + uint64 upload_size, + bool is_download, + bool allow_download, + bool has_user_gesture, + int host_renderer_id, + int host_render_view_id); + virtual ~ResourceDispatcherHostRequestInfo(); + + // Top-level ResourceHandler servicing this request. + ResourceHandler* resource_handler() { return resource_handler_.get(); } + + // CrossSiteResourceHandler for this request, if it is a cross-site request. + // (NULL otherwise.) This handler is part of the chain of ResourceHandlers + // pointed to by resource_handler, and is not owned by this class. + CrossSiteResourceHandler* cross_site_handler() { + return cross_site_handler_; + } + void set_cross_site_handler(CrossSiteResourceHandler* h) { + cross_site_handler_ = h; + } + + // Pointer to the login handler, or NULL if there is none for this request. + LoginHandler* login_handler() const { return login_handler_.get(); } + void set_login_handler(LoginHandler* lh); + + // Pointer to the SSL auth, or NULL if there is none for this request. + SSLClientAuthHandler* ssl_client_auth_handler() const { + return ssl_client_auth_handler_.get(); + } + void set_ssl_client_auth_handler(SSLClientAuthHandler* s); + + // Identifies the type of process (renderer, plugin, etc.) making the request. + ChildProcessInfo::ProcessType process_type() const { + return process_type_; + } + + // The child process unique ID of the requestor. This duplicates the value + // stored on the request by SetChildProcessUniqueIDForRequest in + // url_request_tracking. + int child_id() const { return child_id_; } + + // The IPC route identifier for this request (this identifies the RenderView + // or like-thing in the renderer that the request gets routed to). + int route_id() const { return route_id_; } + + // Unique identifier for this resource request. + int request_id() const { return request_id_; } + + // Number of messages we've sent to the renderer that we haven't gotten an + // ACK for. This allows us to avoid having too many messages in flight. + int pending_data_count() const { return pending_data_count_; } + void IncrementPendingDataCount() { pending_data_count_++; } + void DecrementPendingDataCount() { pending_data_count_--; } + + // Downloads are allowed only as a top level request. + bool allow_download() const { return allow_download_; } + + bool has_user_gesture() const { return has_user_gesture_; } + + // Whether this is a download. + bool is_download() const { return is_download_; } + void set_is_download(bool download) { is_download_ = download; } + + // The number of clients that have called pause on this request. + int pause_count() const { return pause_count_; } + void set_pause_count(int count) { pause_count_ = count; } + + // Identifies the type of resource, such as subframe, media, etc. + ResourceType::Type resource_type() const { return resource_type_; } + + // Whether we should apply a filter to this resource that replaces + // localization templates with the appropriate localized strings. This is set + // for CSS resources used by extensions. + bool replace_extension_localization_templates() const { + return replace_extension_localization_templates_; + } + void set_replace_extension_localization_templates() { + replace_extension_localization_templates_ = true; + } + + // Returns the last updated state of the load. This is updated periodically + // by the ResourceDispatcherHost and tracked here so we don't send out + // unnecessary state change notifications. + net::LoadState last_load_state() const { return last_load_state_; } + void set_last_load_state(net::LoadState s) { last_load_state_ = s; } + + // When there is upload data, this is the byte count of that data. When there + // is no upload, this will be 0. + uint64 upload_size() const { return upload_size_; } + void set_upload_size(uint64 upload_size) { upload_size_ = upload_size; } + + // When we're uploading data, this is the the byte offset into the uploaded + // data that we've uploaded that we've send an upload progress update about. + // The ResourceDispatcherHost will periodically update this value to track + // upload progress and make sure it doesn't sent out duplicate updates. + uint64 last_upload_position() const { return last_upload_position_; } + void set_last_upload_position(uint64 p) { last_upload_position_ = p; } + + // Indicates when the ResourceDispatcherHost last update the upload + // position. This is used to make sure we don't send too many updates. + base::TimeTicks last_upload_ticks() const { return last_upload_ticks_; } + void set_last_upload_ticks(base::TimeTicks t) { last_upload_ticks_ = t; } + + // Set when the ResourceDispatcherHost has sent out an upload progress, and + // cleared whtn the ACK is received. This is used to throttle updates so + // multiple updates aren't in flight at once. + bool waiting_for_upload_progress_ack() const { + return waiting_for_upload_progress_ack_; + } + void set_waiting_for_upload_progress_ack(bool waiting) { + waiting_for_upload_progress_ack_ = waiting; + } + + // The approximate in-memory size (bytes) that we credited this request + // as consuming in |outstanding_requests_memory_cost_map_|. + int memory_cost() const { return memory_cost_; } + void set_memory_cost(int cost) { memory_cost_ = cost; } + + int host_renderer_id() const { return host_renderer_id_; } + int host_render_view_id() const { return host_render_view_id_; } + + // We hold a reference to the requested blob data to ensure it doesn't + // get finally released prior to the net::URLRequestJob being started. + webkit_blob::BlobData* requested_blob_data() const { + return requested_blob_data_.get(); + } + void set_requested_blob_data(webkit_blob::BlobData* data); + + private: + friend class ResourceDispatcherHost; + + // Request is temporarily not handling network data. Should be used only + // by the ResourceDispatcherHost, not the event handlers (accessors are + // provided for consistency with the rest of the interface). + bool is_paused() const { return is_paused_; } + void set_is_paused(bool paused) { is_paused_ = paused; } + + // Whether we called OnResponseStarted for this request or not. Should be used + // only by the ResourceDispatcherHost, not the event handlers (accessors are + // provided for consistency with the rest of the interface). + bool called_on_response_started() const { + return called_on_response_started_; + } + void set_called_on_response_started(bool called) { + called_on_response_started_ = called; + } + + // Whether this request has started reading any bytes from the response + // yet. Will be true after the first (unpaused) call to Read. Should be used + // only by the ResourceDispatcherHost, not the event handlers (accessors are + // provided for consistency with the rest of the interface). + bool has_started_reading() const { return has_started_reading_; } + void set_has_started_reading(bool reading) { has_started_reading_ = reading; } + + // How many bytes have been read while this request has been paused. Should be + // used only by the ResourceDispatcherHost, not the event handlers (accessors + // are provided for consistency with the rest of the interface). + int paused_read_bytes() const { return paused_read_bytes_; } + void set_paused_read_bytes(int bytes) { paused_read_bytes_ = bytes; } + + scoped_refptr<ResourceHandler> resource_handler_; + CrossSiteResourceHandler* cross_site_handler_; // Non-owning, may be NULL. + scoped_refptr<LoginHandler> login_handler_; + scoped_refptr<SSLClientAuthHandler> ssl_client_auth_handler_; + ChildProcessInfo::ProcessType process_type_; + int child_id_; + int route_id_; + int request_id_; + int pending_data_count_; + bool is_download_; + bool allow_download_; + bool has_user_gesture_; + int pause_count_; + ResourceType::Type resource_type_; + bool replace_extension_localization_templates_; + net::LoadState last_load_state_; + uint64 upload_size_; + uint64 last_upload_position_; + base::TimeTicks last_upload_ticks_; + bool waiting_for_upload_progress_ack_; + int memory_cost_; + scoped_refptr<webkit_blob::BlobData> requested_blob_data_; + + // "Private" data accessible only to ResourceDispatcherHost (use the + // accessors above for consistency). + bool is_paused_; + bool called_on_response_started_; + bool has_started_reading_; + int paused_read_bytes_; + + // The following two members are specified if the request is initiated by + // a plugin like Gears. + + // Contains the id of the host renderer. + int host_renderer_id_; + // Contains the id of the host render view. + int host_render_view_id_; + + DISALLOW_COPY_AND_ASSIGN(ResourceDispatcherHostRequestInfo); +}; + +#endif // CONTENT_BROWSER_RENDERER_HOST_RESOURCE_DISPATCHER_HOST_REQUEST_INFO_H_ diff --git a/content/browser/renderer_host/resource_dispatcher_host_uitest.cc b/content/browser/renderer_host/resource_dispatcher_host_uitest.cc new file mode 100644 index 0000000..5271595 --- /dev/null +++ b/content/browser/renderer_host/resource_dispatcher_host_uitest.cc @@ -0,0 +1,335 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <sstream> +#include <string> + +#include "base/command_line.h" +#include "base/file_path.h" +#include "base/path_service.h" +#include "base/string_util.h" +#include "base/test/test_timeouts.h" +#include "chrome/browser/net/url_request_failed_dns_job.h" +#include "chrome/browser/net/url_request_mock_http_job.h" +#include "chrome/common/url_constants.h" +#include "chrome/test/automation/browser_proxy.h" +#include "chrome/test/automation/tab_proxy.h" +#include "chrome/test/ui/ui_test.h" +#include "net/base/net_util.h" +#include "net/test/test_server.h" + +namespace { + +class ResourceDispatcherTest : public UITest { + public: + void CheckTitleTest(const std::wstring& file, + const std::wstring& expected_title, + int expected_navigations) { + NavigateToURLBlockUntilNavigationsComplete( + URLRequestMockHTTPJob::GetMockUrl(FilePath::FromWStringHack(file)), + expected_navigations); + EXPECT_EQ(expected_title, GetActiveTabTitle()); + } + + protected: + ResourceDispatcherTest() : UITest() { + dom_automation_enabled_ = true; + } +}; + +TEST_F(ResourceDispatcherTest, SniffHTMLWithNoContentType) { + CheckTitleTest(L"content-sniffer-test0.html", + L"Content Sniffer Test 0", 1); +} + +TEST_F(ResourceDispatcherTest, RespectNoSniffDirective) { + CheckTitleTest(L"nosniff-test.html", L"", 1); +} + +TEST_F(ResourceDispatcherTest, DoNotSniffHTMLFromTextPlain) { + CheckTitleTest(L"content-sniffer-test1.html", L"", 1); +} + +TEST_F(ResourceDispatcherTest, DoNotSniffHTMLFromImageGIF) { + CheckTitleTest(L"content-sniffer-test2.html", L"", 1); +} + +TEST_F(ResourceDispatcherTest, SniffNoContentTypeNoData) { + CheckTitleTest(L"content-sniffer-test3.html", + L"Content Sniffer Test 3", 1); + EXPECT_EQ(1, GetTabCount()); + + // Make sure the download shelf is not showing. + scoped_refptr<BrowserProxy> browser(automation()->GetBrowserWindow(0)); + ASSERT_TRUE(browser.get()); + bool visible = false; + ASSERT_TRUE(browser->IsShelfVisible(&visible)); + EXPECT_FALSE(visible); +} + +TEST_F(ResourceDispatcherTest, ContentDispositionEmpty) { + CheckTitleTest(L"content-disposition-empty.html", L"success", 1); +} + +TEST_F(ResourceDispatcherTest, ContentDispositionInline) { + CheckTitleTest(L"content-disposition-inline.html", L"success", 1); +} + +// Test for bug #1091358. +// Flaky: http://crbug.com/62595 +TEST_F(ResourceDispatcherTest, FLAKY_SyncXMLHttpRequest) { + net::TestServer test_server(net::TestServer::TYPE_HTTP, + FilePath(FILE_PATH_LITERAL("chrome/test/data"))); + ASSERT_TRUE(test_server.Start()); + + scoped_refptr<BrowserProxy> browser_proxy(automation()->GetBrowserWindow(0)); + ASSERT_TRUE(browser_proxy.get()); + scoped_refptr<TabProxy> tab(browser_proxy->GetActiveTab()); + ASSERT_TRUE(tab.get()); + ASSERT_EQ(AUTOMATION_MSG_NAVIGATION_SUCCESS, + tab->NavigateToURL(test_server.GetURL( + "files/sync_xmlhttprequest.html"))); + + // Let's check the XMLHttpRequest ran successfully. + bool success = false; + EXPECT_TRUE(tab->ExecuteAndExtractBool(L"", + L"window.domAutomationController.send(DidSyncRequestSucceed());", + &success)); + EXPECT_TRUE(success); +} + +// http://code.google.com/p/chromium/issues/detail?id=62776 +TEST_F(ResourceDispatcherTest, FLAKY_SyncXMLHttpRequest_Disallowed) { + net::TestServer test_server(net::TestServer::TYPE_HTTP, + FilePath(FILE_PATH_LITERAL("chrome/test/data"))); + ASSERT_TRUE(test_server.Start()); + + scoped_refptr<BrowserProxy> browser_proxy(automation()->GetBrowserWindow(0)); + ASSERT_TRUE(browser_proxy.get()); + scoped_refptr<TabProxy> tab(browser_proxy->GetActiveTab()); + ASSERT_TRUE(tab.get()); + ASSERT_EQ(AUTOMATION_MSG_NAVIGATION_SUCCESS, + tab->NavigateToURL(test_server.GetURL( + "files/sync_xmlhttprequest_disallowed.html"))); + + // Let's check the XMLHttpRequest ran successfully. + bool success = false; + EXPECT_TRUE(tab->ExecuteAndExtractBool(L"", + L"window.domAutomationController.send(DidSucceed());", + &success)); + EXPECT_TRUE(success); +} + +// Test for bug #1159553 -- A synchronous xhr (whose content-type is +// downloadable) would trigger download and hang the renderer process, +// if executed while navigating to a new page. +// Disabled -- http://code.google.com/p/chromium/issues/detail?id=56264 +TEST_F(ResourceDispatcherTest, DISABLED_SyncXMLHttpRequest_DuringUnload) { + net::TestServer test_server(net::TestServer::TYPE_HTTP, + FilePath(FILE_PATH_LITERAL("chrome/test/data"))); + ASSERT_TRUE(test_server.Start()); + + scoped_refptr<BrowserProxy> browser_proxy(automation()->GetBrowserWindow(0)); + ASSERT_TRUE(browser_proxy.get()); + scoped_refptr<TabProxy> tab(browser_proxy->GetActiveTab()); + ASSERT_TRUE(tab.get()); + + ASSERT_EQ(AUTOMATION_MSG_NAVIGATION_SUCCESS, + tab->NavigateToURL(test_server.GetURL( + "files/sync_xmlhttprequest_during_unload.html"))); + + // Confirm that the page has loaded (since it changes its title during load). + std::wstring tab_title; + EXPECT_TRUE(tab->GetTabTitle(&tab_title)); + EXPECT_EQ(L"sync xhr on unload", tab_title); + + // Navigate to a new page, to dispatch unload event and trigger xhr. + // (the bug would make this step hang the renderer). + ASSERT_EQ(AUTOMATION_MSG_NAVIGATION_SUCCESS, + tab->NavigateToURL(test_server.GetURL("files/title2.html"))); + + // Check that the new page got loaded, and that no download was triggered. + EXPECT_TRUE(tab->GetTabTitle(&tab_title)); + EXPECT_EQ(L"Title Of Awesomeness", tab_title); + + bool shelf_is_visible = false; + scoped_refptr<BrowserProxy> browser(automation()->GetBrowserWindow(0)); + ASSERT_TRUE(browser.get()); + EXPECT_TRUE(browser->IsShelfVisible(&shelf_is_visible)); + EXPECT_FALSE(shelf_is_visible); +} + +// Tests that onunload is run for cross-site requests. (Bug 1114994) +TEST_F(ResourceDispatcherTest, CrossSiteOnunloadCookie) { + net::TestServer test_server(net::TestServer::TYPE_HTTP, + FilePath(FILE_PATH_LITERAL("chrome/test/data"))); + ASSERT_TRUE(test_server.Start()); + + scoped_refptr<BrowserProxy> browser_proxy(automation()->GetBrowserWindow(0)); + ASSERT_TRUE(browser_proxy.get()); + scoped_refptr<TabProxy> tab(browser_proxy->GetActiveTab()); + ASSERT_TRUE(tab.get()); + + GURL url(test_server.GetURL("files/onunload_cookie.html")); + ASSERT_EQ(AUTOMATION_MSG_NAVIGATION_SUCCESS, tab->NavigateToURL(url)); + + // Confirm that the page has loaded (since it changes its title during load). + std::wstring tab_title; + EXPECT_TRUE(tab->GetTabTitle(&tab_title)); + EXPECT_EQ(L"set cookie on unload", tab_title); + + // Navigate to a new cross-site page, to dispatch unload event and set the + // cookie. + CheckTitleTest(L"content-sniffer-test0.html", + L"Content Sniffer Test 0", 1); + + // Check that the cookie was set. + std::string value_result; + ASSERT_TRUE(tab->GetCookieByName(url, "onunloadCookie", &value_result)); + ASSERT_FALSE(value_result.empty()); + ASSERT_STREQ("foo", value_result.c_str()); +} + +#if !defined(OS_MACOSX) +// Tests that the onbeforeunload and onunload logic is shortcutted if the old +// renderer is gone. In that case, we don't want to wait for the old renderer +// to run the handlers. +// We need to disable this on Mac because the crash causes the OS CrashReporter +// process to kick in to analyze the poor dead renderer. Unfortunately, if the +// app isn't stripped of debug symbols, this takes about five minutes to +// complete and isn't conducive to quick turnarounds. As we don't currently +// strip the app on the build bots, this is bad times. +TEST_F(ResourceDispatcherTest, CrossSiteAfterCrash) { + // This test only works in multi-process mode + if (ProxyLauncher::in_process_renderer()) + return; + + scoped_refptr<BrowserProxy> browser_proxy(automation()->GetBrowserWindow(0)); + ASSERT_TRUE(browser_proxy.get()); + scoped_refptr<TabProxy> tab(browser_proxy->GetActiveTab()); + ASSERT_TRUE(tab.get()); + + // Cause the renderer to crash. +#if defined(OS_WIN) || defined(USE_LINUX_BREAKPAD) + expected_crashes_ = 1; +#endif + ASSERT_TRUE(tab->NavigateToURLAsync(GURL(chrome::kAboutCrashURL))); + // Wait for browser to notice the renderer crash. + base::PlatformThread::Sleep(TestTimeouts::action_timeout_ms()); + + // Navigate to a new cross-site page. The browser should not wait around for + // the old renderer's on{before}unload handlers to run. + CheckTitleTest(L"content-sniffer-test0.html", + L"Content Sniffer Test 0", 1); +} +#endif // !defined(OS_MACOSX) + +// Tests that cross-site navigations work when the new page does not go through +// the BufferedEventHandler (e.g., non-http{s} URLs). (Bug 1225872) +TEST_F(ResourceDispatcherTest, CrossSiteNavigationNonBuffered) { + scoped_refptr<BrowserProxy> browser_proxy(automation()->GetBrowserWindow(0)); + ASSERT_TRUE(browser_proxy.get()); + scoped_refptr<TabProxy> tab(browser_proxy->GetActiveTab()); + ASSERT_TRUE(tab.get()); + + // Start with an HTTP page. + CheckTitleTest(L"content-sniffer-test0.html", + L"Content Sniffer Test 0", 1); + + // Now load a file:// page, which does not use the BufferedEventHandler. + // Make sure that the page loads and displays a title, and doesn't get stuck. + FilePath test_file(test_data_directory_); + test_file = test_file.AppendASCII("title2.html"); + ASSERT_EQ(AUTOMATION_MSG_NAVIGATION_SUCCESS, + tab->NavigateToURL(net::FilePathToFileURL(test_file))); + EXPECT_EQ(L"Title Of Awesomeness", GetActiveTabTitle()); +} + +// Tests that a cross-site navigation to an error page (resulting in the link +// doctor page) still runs the onunload handler and can support navigations +// away from the link doctor page. (Bug 1235537) +TEST_F(ResourceDispatcherTest, CrossSiteNavigationErrorPage) { + net::TestServer test_server(net::TestServer::TYPE_HTTP, + FilePath(FILE_PATH_LITERAL("chrome/test/data"))); + ASSERT_TRUE(test_server.Start()); + + scoped_refptr<BrowserProxy> browser_proxy(automation()->GetBrowserWindow(0)); + ASSERT_TRUE(browser_proxy.get()); + scoped_refptr<TabProxy> tab(browser_proxy->GetActiveTab()); + ASSERT_TRUE(tab.get()); + + GURL url(test_server.GetURL("files/onunload_cookie.html")); + ASSERT_EQ(AUTOMATION_MSG_NAVIGATION_SUCCESS, tab->NavigateToURL(url)); + + // Confirm that the page has loaded (since it changes its title during load). + std::wstring tab_title; + EXPECT_TRUE(tab->GetTabTitle(&tab_title)); + EXPECT_EQ(L"set cookie on unload", tab_title); + + // Navigate to a new cross-site URL that results in an error page. + // TODO(creis): If this causes crashes or hangs, it might be for the same + // reason as ErrorPageTest::DNSError. See bug 1199491 and + // http://crbug.com/22877. + ASSERT_EQ(AUTOMATION_MSG_NAVIGATION_SUCCESS, + tab->NavigateToURLBlockUntilNavigationsComplete( + GURL(URLRequestFailedDnsJob::kTestUrl), 2)); + EXPECT_NE(L"set cookie on unload", GetActiveTabTitle()); + + // Check that the cookie was set, meaning that the onunload handler ran. + std::string value_result; + EXPECT_TRUE(tab->GetCookieByName(url, "onunloadCookie", &value_result)); + EXPECT_FALSE(value_result.empty()); + EXPECT_STREQ("foo", value_result.c_str()); + + // Check that renderer-initiated navigations still work. In a previous bug, + // the ResourceDispatcherHost would think that such navigations were + // cross-site, because we didn't clean up from the previous request. Since + // TabContents was in the NORMAL state, it would ignore the attempt to run + // the onunload handler, and the navigation would fail. + // (Test by redirecting to javascript:window.location='someURL'.) + GURL test_url(test_server.GetURL("files/title2.html")); + std::string redirect_url = "javascript:window.location='" + + test_url.possibly_invalid_spec() + "'"; + ASSERT_EQ(AUTOMATION_MSG_NAVIGATION_SUCCESS, + tab->NavigateToURL(GURL(redirect_url))); + EXPECT_TRUE(tab->GetTabTitle(&tab_title)); + EXPECT_EQ(L"Title Of Awesomeness", tab_title); +} + +TEST_F(ResourceDispatcherTest, CrossOriginRedirectBlocked) { + // We expect the following URL requests from this test: + // 1- http://mock.http/cross-origin-redirect-blocked.html + // 2- http://mock.http/redirect-to-title2.html + // 3- http://mock.http/title2.html + // + // If the redirect in #2 were not blocked, we'd also see a request + // for http://mock.http:4000/title2.html, and the title would be different. + CheckTitleTest(L"cross-origin-redirect-blocked.html", + L"Title Of More Awesomeness", 2); +} + +// Tests that ResourceDispatcherHostRequestInfo is updated correctly on failed +// requests, to prevent calling Read on a request that has already failed. +// See bug 40250. +TEST_F(ResourceDispatcherTest, CrossSiteFailedRequest) { + scoped_refptr<BrowserProxy> browser_proxy(automation()->GetBrowserWindow(0)); + ASSERT_TRUE(browser_proxy.get()); + scoped_refptr<TabProxy> tab(browser_proxy->GetActiveTab()); + ASSERT_TRUE(tab.get()); + + // Visit another URL first to trigger a cross-site navigation. + GURL url(chrome::kChromeUINewTabURL); + ASSERT_EQ(AUTOMATION_MSG_NAVIGATION_SUCCESS, tab->NavigateToURL(url)); + + // Visit a URL that fails without calling ResourceDispatcherHost::Read. + GURL broken_url("chrome://theme"); + ASSERT_EQ(AUTOMATION_MSG_NAVIGATION_SUCCESS, tab->NavigateToURL(broken_url)); + + // Make sure the navigation finishes. + std::wstring tab_title; + EXPECT_TRUE(tab->GetTabTitle(&tab_title)); + EXPECT_EQ(L"chrome://theme/ is not available", tab_title); +} + +} // namespace diff --git a/content/browser/renderer_host/resource_dispatcher_host_unittest.cc b/content/browser/renderer_host/resource_dispatcher_host_unittest.cc new file mode 100644 index 0000000..e68c3b9 --- /dev/null +++ b/content/browser/renderer_host/resource_dispatcher_host_unittest.cc @@ -0,0 +1,1026 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <vector> + +#include "base/file_path.h" +#include "base/message_loop.h" +#include "base/process_util.h" +#include "chrome/browser/browser_thread.h" +#include "chrome/browser/child_process_security_policy.h" +#include "chrome/common/chrome_plugin_lib.h" +#include "chrome/common/render_messages.h" +#include "chrome/common/render_messages_params.h" +#include "chrome/common/resource_response.h" +#include "content/browser/renderer_host/resource_dispatcher_host.h" +#include "content/browser/renderer_host/resource_dispatcher_host_request_info.h" +#include "content/browser/renderer_host/resource_handler.h" +#include "content/browser/renderer_host/resource_message_filter.h" +#include "net/base/net_errors.h" +#include "net/base/upload_data.h" +#include "net/http/http_util.h" +#include "net/url_request/url_request.h" +#include "net/url_request/url_request_job.h" +#include "net/url_request/url_request_test_job.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "webkit/appcache/appcache_interfaces.h" + +// TODO(eroman): Write unit tests for SafeBrowsing that exercise +// SafeBrowsingResourceHandler. + +namespace { + +// Returns the resource response header structure for this request. +void GetResponseHead(const std::vector<IPC::Message>& messages, + ResourceResponseHead* response_head) { + ASSERT_GE(messages.size(), 2U); + + // The first messages should be received response. + ASSERT_EQ(ViewMsg_Resource_ReceivedResponse::ID, messages[0].type()); + + void* iter = NULL; + int request_id; + ASSERT_TRUE(IPC::ReadParam(&messages[0], &iter, &request_id)); + ASSERT_TRUE(IPC::ReadParam(&messages[0], &iter, response_head)); +} + +} // namespace + +static int RequestIDForMessage(const IPC::Message& msg) { + int request_id = -1; + switch (msg.type()) { + case ViewMsg_Resource_UploadProgress::ID: + case ViewMsg_Resource_ReceivedResponse::ID: + case ViewMsg_Resource_ReceivedRedirect::ID: + case ViewMsg_Resource_DataReceived::ID: + case ViewMsg_Resource_RequestComplete::ID: + request_id = IPC::MessageIterator(msg).NextInt(); + break; + } + return request_id; +} + +static ViewHostMsg_Resource_Request CreateResourceRequest( + const char* method, + ResourceType::Type type, + const GURL& url) { + ViewHostMsg_Resource_Request request; + request.method = std::string(method); + request.url = url; + request.first_party_for_cookies = url; // bypass third-party cookie blocking + request.load_flags = 0; + request.origin_pid = 0; + request.resource_type = type; + request.request_context = 0; + request.appcache_host_id = appcache::kNoHostId; + request.download_to_file = false; + request.host_renderer_id = -1; + request.host_render_view_id = -1; + return request; +} + +// Spin up the message loop to kick off the request. +static void KickOffRequest() { + MessageLoop::current()->RunAllPending(); +} + +// We may want to move this to a shared space if it is useful for something else +class ResourceIPCAccumulator { + public: + void AddMessage(const IPC::Message& msg) { + messages_.push_back(msg); + } + + // This groups the messages by their request ID. The groups will be in order + // that the first message for each request ID was received, and the messages + // within the groups will be in the order that they appeared. + // Note that this clears messages_. + typedef std::vector< std::vector<IPC::Message> > ClassifiedMessages; + void GetClassifiedMessages(ClassifiedMessages* msgs); + + std::vector<IPC::Message> messages_; +}; + +// This is very inefficient as a result of repeatedly extracting the ID, use +// only for tests! +void ResourceIPCAccumulator::GetClassifiedMessages(ClassifiedMessages* msgs) { + while (!messages_.empty()) { + std::vector<IPC::Message> cur_requests; + cur_requests.push_back(messages_[0]); + int cur_id = RequestIDForMessage(messages_[0]); + + // find all other messages with this ID + for (int i = 1; i < static_cast<int>(messages_.size()); i++) { + int id = RequestIDForMessage(messages_[i]); + if (id == cur_id) { + cur_requests.push_back(messages_[i]); + messages_.erase(messages_.begin() + i); + i--; + } + } + messages_.erase(messages_.begin()); + msgs->push_back(cur_requests); + } +} + +// This class forwards the incoming messages to the ResourceDispatcherHostTest. +// This is used to emulate different sub-processes, since this filter will +// have a different ID than the original. For the test, we want all the incoming +// messages to go to the same place, which is why this forwards. +class ForwardingFilter : public ResourceMessageFilter { + public: + explicit ForwardingFilter(IPC::Message::Sender* dest) + : ResourceMessageFilter(ChildProcessInfo::GenerateChildProcessUniqueId(), + ChildProcessInfo::RENDER_PROCESS, + NULL), + dest_(dest) { + OnChannelConnected(base::GetCurrentProcId()); + } + + // ResourceMessageFilter override + virtual bool Send(IPC::Message* msg) { + if (!dest_) + return false; + return dest_->Send(msg); + } + + private: + IPC::Message::Sender* dest_; + + DISALLOW_COPY_AND_ASSIGN(ForwardingFilter); +}; + +class ResourceDispatcherHostTest : public testing::Test, + public IPC::Message::Sender { + public: + ResourceDispatcherHostTest() + : ALLOW_THIS_IN_INITIALIZER_LIST(filter_(new ForwardingFilter(this))), + ui_thread_(BrowserThread::UI, &message_loop_), + io_thread_(BrowserThread::IO, &message_loop_), + old_factory_(NULL), + resource_type_(ResourceType::SUB_RESOURCE) { + } + // IPC::Message::Sender implementation + virtual bool Send(IPC::Message* msg) { + accum_.AddMessage(*msg); + delete msg; + return true; + } + + protected: + // testing::Test + virtual void SetUp() { + DCHECK(!test_fixture_); + test_fixture_ = this; + ChildProcessSecurityPolicy::GetInstance()->Add(0); + net::URLRequest::RegisterProtocolFactory( + "test", + &ResourceDispatcherHostTest::Factory); + EnsureTestSchemeIsAllowed(); + } + + virtual void TearDown() { + net::URLRequest::RegisterProtocolFactory("test", NULL); + if (!scheme_.empty()) + net::URLRequest::RegisterProtocolFactory(scheme_, old_factory_); + + DCHECK(test_fixture_ == this); + test_fixture_ = NULL; + + host_.Shutdown(); + + ChildProcessSecurityPolicy::GetInstance()->Remove(0); + + // The plugin lib is automatically loaded during these test + // and we want a clean environment for other tests. + ChromePluginLib::UnloadAllPlugins(); + + // Flush the message loop to make Purify happy. + message_loop_.RunAllPending(); + } + + // Creates a request using the current test object as the filter. + void MakeTestRequest(int render_view_id, + int request_id, + const GURL& url); + + // Generates a request using the given filter. This will probably be a + // ForwardingFilter. + void MakeTestRequest(ResourceMessageFilter* filter, + int render_view_id, + int request_id, + const GURL& url); + + void MakeCancelRequest(int request_id); + + void EnsureTestSchemeIsAllowed() { + static bool have_white_listed_test_scheme = false; + + if (!have_white_listed_test_scheme) { + ChildProcessSecurityPolicy::GetInstance()->RegisterWebSafeScheme("test"); + have_white_listed_test_scheme = true; + } + } + + // Sets a particular response for any request from now on. To switch back to + // the default bahavior, pass an empty |headers|. |headers| should be raw- + // formatted (NULLs instead of EOLs). + void SetResponse(const std::string& headers, const std::string& data) { + response_headers_ = headers; + response_data_ = data; + } + + // Sets a particular resource type for any request from now on. + void SetResourceType(ResourceType::Type type) { + resource_type_ = type; + } + + // Intercepts requests for the given protocol. + void HandleScheme(const std::string& scheme) { + DCHECK(scheme_.empty()); + DCHECK(!old_factory_); + scheme_ = scheme; + old_factory_ = net::URLRequest::RegisterProtocolFactory( + scheme_, &ResourceDispatcherHostTest::Factory); + } + + // Our own net::URLRequestJob factory. + static net::URLRequestJob* Factory(net::URLRequest* request, + const std::string& scheme) { + if (test_fixture_->response_headers_.empty()) { + return new net::URLRequestTestJob(request); + } else { + return new net::URLRequestTestJob(request, + test_fixture_->response_headers_, + test_fixture_->response_data_, + false); + } + } + + scoped_refptr<ForwardingFilter> filter_; + MessageLoopForIO message_loop_; + BrowserThread ui_thread_; + BrowserThread io_thread_; + ResourceDispatcherHost host_; + ResourceIPCAccumulator accum_; + std::string response_headers_; + std::string response_data_; + std::string scheme_; + net::URLRequest::ProtocolFactory* old_factory_; + ResourceType::Type resource_type_; + static ResourceDispatcherHostTest* test_fixture_; +}; +// Static. +ResourceDispatcherHostTest* ResourceDispatcherHostTest::test_fixture_ = NULL; + +void ResourceDispatcherHostTest::MakeTestRequest(int render_view_id, + int request_id, + const GURL& url) { + MakeTestRequest(filter_.get(), render_view_id, request_id, url); +} + +void ResourceDispatcherHostTest::MakeTestRequest( + ResourceMessageFilter* filter, + int render_view_id, + int request_id, + const GURL& url) { + ViewHostMsg_Resource_Request request = + CreateResourceRequest("GET", resource_type_, url); + ViewHostMsg_RequestResource msg(render_view_id, request_id, request); + bool msg_was_ok; + host_.OnMessageReceived(msg, filter, &msg_was_ok); + KickOffRequest(); +} + +void ResourceDispatcherHostTest::MakeCancelRequest(int request_id) { + host_.CancelRequest(filter_->child_id(), request_id, false); +} + +void CheckSuccessfulRequest(const std::vector<IPC::Message>& messages, + const std::string& reference_data) { + // A successful request will have received 4 messages: + // ReceivedResponse (indicates headers received) + // DataReceived (data) + // XXX DataReceived (0 bytes remaining from a read) + // RequestComplete (request is done) + // + // This function verifies that we received 4 messages and that they + // are appropriate. + ASSERT_EQ(3U, messages.size()); + + // The first messages should be received response + ASSERT_EQ(ViewMsg_Resource_ReceivedResponse::ID, messages[0].type()); + + // followed by the data, currently we only do the data in one chunk, but + // should probably test multiple chunks later + ASSERT_EQ(ViewMsg_Resource_DataReceived::ID, messages[1].type()); + + void* iter = NULL; + int request_id; + ASSERT_TRUE(IPC::ReadParam(&messages[1], &iter, &request_id)); + base::SharedMemoryHandle shm_handle; + ASSERT_TRUE(IPC::ReadParam(&messages[1], &iter, &shm_handle)); + uint32 data_len; + ASSERT_TRUE(IPC::ReadParam(&messages[1], &iter, &data_len)); + + ASSERT_EQ(reference_data.size(), data_len); + base::SharedMemory shared_mem(shm_handle, true); // read only + shared_mem.Map(data_len); + const char* data = static_cast<char*>(shared_mem.memory()); + ASSERT_EQ(0, memcmp(reference_data.c_str(), data, data_len)); + + // followed by a 0-byte read + //ASSERT_EQ(ViewMsg_Resource_DataReceived::ID, messages[2].type()); + + // the last message should be all data received + ASSERT_EQ(ViewMsg_Resource_RequestComplete::ID, messages[2].type()); +} + +// Tests whether many messages get dispatched properly. +TEST_F(ResourceDispatcherHostTest, TestMany) { + EXPECT_EQ(0, host_.GetOutstandingRequestsMemoryCost(0)); + + MakeTestRequest(0, 1, net::URLRequestTestJob::test_url_1()); + MakeTestRequest(0, 2, net::URLRequestTestJob::test_url_2()); + MakeTestRequest(0, 3, net::URLRequestTestJob::test_url_3()); + + // flush all the pending requests + while (net::URLRequestTestJob::ProcessOnePendingMessage()) {} + + EXPECT_EQ(0, host_.GetOutstandingRequestsMemoryCost(0)); + + // sorts out all the messages we saw by request + ResourceIPCAccumulator::ClassifiedMessages msgs; + accum_.GetClassifiedMessages(&msgs); + + // there are three requests, so we should have gotten them classified as such + ASSERT_EQ(3U, msgs.size()); + + CheckSuccessfulRequest(msgs[0], net::URLRequestTestJob::test_data_1()); + CheckSuccessfulRequest(msgs[1], net::URLRequestTestJob::test_data_2()); + CheckSuccessfulRequest(msgs[2], net::URLRequestTestJob::test_data_3()); +} + +// Tests whether messages get canceled properly. We issue three requests, +// cancel one of them, and make sure that each sent the proper notifications. +TEST_F(ResourceDispatcherHostTest, Cancel) { + EXPECT_EQ(0, host_.GetOutstandingRequestsMemoryCost(0)); + + MakeTestRequest(0, 1, net::URLRequestTestJob::test_url_1()); + MakeTestRequest(0, 2, net::URLRequestTestJob::test_url_2()); + MakeTestRequest(0, 3, net::URLRequestTestJob::test_url_3()); + MakeCancelRequest(2); + + // flush all the pending requests + while (net::URLRequestTestJob::ProcessOnePendingMessage()) {} + MessageLoop::current()->RunAllPending(); + + EXPECT_EQ(0, host_.GetOutstandingRequestsMemoryCost(0)); + + ResourceIPCAccumulator::ClassifiedMessages msgs; + accum_.GetClassifiedMessages(&msgs); + + // there are three requests, so we should have gotten them classified as such + ASSERT_EQ(3U, msgs.size()); + + CheckSuccessfulRequest(msgs[0], net::URLRequestTestJob::test_data_1()); + CheckSuccessfulRequest(msgs[2], net::URLRequestTestJob::test_data_3()); + + // Check that request 2 got canceled. + ASSERT_EQ(2U, msgs[1].size()); + ASSERT_EQ(ViewMsg_Resource_ReceivedResponse::ID, msgs[1][0].type()); + ASSERT_EQ(ViewMsg_Resource_RequestComplete::ID, msgs[1][1].type()); + + int request_id; + net::URLRequestStatus status; + + void* iter = NULL; + ASSERT_TRUE(IPC::ReadParam(&msgs[1][1], &iter, &request_id)); + ASSERT_TRUE(IPC::ReadParam(&msgs[1][1], &iter, &status)); + + EXPECT_EQ(net::URLRequestStatus::CANCELED, status.status()); +} + +// The host delegate acts as a second one so we can have some requests +// pending and some canceled. +class TestFilter : public ForwardingFilter { + public: + TestFilter() + : ForwardingFilter(NULL), + has_canceled_(false), + received_after_canceled_(0) { + } + + // ForwardingFilter override + virtual bool Send(IPC::Message* msg) { + // no messages should be received when the process has been canceled + if (has_canceled_) + received_after_canceled_++; + delete msg; + return true; + } + bool has_canceled_; + int received_after_canceled_; +}; + +// Tests CancelRequestsForProcess +TEST_F(ResourceDispatcherHostTest, TestProcessCancel) { + scoped_refptr<TestFilter> test_filter = new TestFilter(); + + // request 1 goes to the test delegate + ViewHostMsg_Resource_Request request = CreateResourceRequest( + "GET", ResourceType::SUB_RESOURCE, net::URLRequestTestJob::test_url_1()); + + EXPECT_EQ(0, host_.GetOutstandingRequestsMemoryCost(0)); + + MakeTestRequest(test_filter.get(), 0, 1, + net::URLRequestTestJob::test_url_1()); + + // request 2 goes to us + MakeTestRequest(0, 2, net::URLRequestTestJob::test_url_2()); + + // request 3 goes to the test delegate + MakeTestRequest(test_filter.get(), 0, 3, + net::URLRequestTestJob::test_url_3()); + + // TODO(mbelshe): + // Now that the async IO path is in place, the IO always completes on the + // initial call; so the requests have already completed. This basically + // breaks the whole test. + //EXPECT_EQ(3, host_.pending_requests()); + + // Process each request for one level so one callback is called. + for (int i = 0; i < 3; i++) + EXPECT_TRUE(net::URLRequestTestJob::ProcessOnePendingMessage()); + + // Cancel the requests to the test process. + host_.CancelRequestsForProcess(filter_->child_id()); + test_filter->has_canceled_ = true; + + // Flush all the pending requests. + while (net::URLRequestTestJob::ProcessOnePendingMessage()) {} + + EXPECT_EQ(0, host_.pending_requests()); + EXPECT_EQ(0, host_.GetOutstandingRequestsMemoryCost(filter_->child_id())); + + // The test delegate should not have gotten any messages after being canceled. + ASSERT_EQ(0, test_filter->received_after_canceled_); + + // We should have gotten exactly one result. + ResourceIPCAccumulator::ClassifiedMessages msgs; + accum_.GetClassifiedMessages(&msgs); + ASSERT_EQ(1U, msgs.size()); + CheckSuccessfulRequest(msgs[0], net::URLRequestTestJob::test_data_2()); +} + +// Tests blocking and resuming requests. +TEST_F(ResourceDispatcherHostTest, TestBlockingResumingRequests) { + EXPECT_EQ(0, host_.GetOutstandingRequestsMemoryCost(filter_->child_id())); + + host_.BlockRequestsForRoute(filter_->child_id(), 1); + host_.BlockRequestsForRoute(filter_->child_id(), 2); + host_.BlockRequestsForRoute(filter_->child_id(), 3); + + MakeTestRequest(0, 1, net::URLRequestTestJob::test_url_1()); + MakeTestRequest(1, 2, net::URLRequestTestJob::test_url_2()); + MakeTestRequest(0, 3, net::URLRequestTestJob::test_url_3()); + MakeTestRequest(1, 4, net::URLRequestTestJob::test_url_1()); + MakeTestRequest(2, 5, net::URLRequestTestJob::test_url_2()); + MakeTestRequest(3, 6, net::URLRequestTestJob::test_url_3()); + + // Flush all the pending requests + while (net::URLRequestTestJob::ProcessOnePendingMessage()) {} + + // Sort out all the messages we saw by request + ResourceIPCAccumulator::ClassifiedMessages msgs; + accum_.GetClassifiedMessages(&msgs); + + // All requests but the 2 for the RVH 0 should have been blocked. + ASSERT_EQ(2U, msgs.size()); + + CheckSuccessfulRequest(msgs[0], net::URLRequestTestJob::test_data_1()); + CheckSuccessfulRequest(msgs[1], net::URLRequestTestJob::test_data_3()); + + // Resume requests for RVH 1 and flush pending requests. + host_.ResumeBlockedRequestsForRoute(filter_->child_id(), 1); + KickOffRequest(); + while (net::URLRequestTestJob::ProcessOnePendingMessage()) {} + + msgs.clear(); + accum_.GetClassifiedMessages(&msgs); + ASSERT_EQ(2U, msgs.size()); + CheckSuccessfulRequest(msgs[0], net::URLRequestTestJob::test_data_2()); + CheckSuccessfulRequest(msgs[1], net::URLRequestTestJob::test_data_1()); + + // Test that new requests are not blocked for RVH 1. + MakeTestRequest(1, 7, net::URLRequestTestJob::test_url_1()); + while (net::URLRequestTestJob::ProcessOnePendingMessage()) {} + msgs.clear(); + accum_.GetClassifiedMessages(&msgs); + ASSERT_EQ(1U, msgs.size()); + CheckSuccessfulRequest(msgs[0], net::URLRequestTestJob::test_data_1()); + + // Now resumes requests for all RVH (2 and 3). + host_.ResumeBlockedRequestsForRoute(filter_->child_id(), 2); + host_.ResumeBlockedRequestsForRoute(filter_->child_id(), 3); + KickOffRequest(); + while (net::URLRequestTestJob::ProcessOnePendingMessage()) {} + + EXPECT_EQ(0, host_.GetOutstandingRequestsMemoryCost(filter_->child_id())); + + msgs.clear(); + accum_.GetClassifiedMessages(&msgs); + ASSERT_EQ(2U, msgs.size()); + CheckSuccessfulRequest(msgs[0], net::URLRequestTestJob::test_data_2()); + CheckSuccessfulRequest(msgs[1], net::URLRequestTestJob::test_data_3()); +} + +// Tests blocking and canceling requests. +TEST_F(ResourceDispatcherHostTest, TestBlockingCancelingRequests) { + EXPECT_EQ(0, host_.GetOutstandingRequestsMemoryCost(filter_->child_id())); + + host_.BlockRequestsForRoute(filter_->child_id(), 1); + + MakeTestRequest(0, 1, net::URLRequestTestJob::test_url_1()); + MakeTestRequest(1, 2, net::URLRequestTestJob::test_url_2()); + MakeTestRequest(0, 3, net::URLRequestTestJob::test_url_3()); + MakeTestRequest(1, 4, net::URLRequestTestJob::test_url_1()); + + // Flush all the pending requests. + while (net::URLRequestTestJob::ProcessOnePendingMessage()) {} + + // Sort out all the messages we saw by request. + ResourceIPCAccumulator::ClassifiedMessages msgs; + accum_.GetClassifiedMessages(&msgs); + + // The 2 requests for the RVH 0 should have been processed. + ASSERT_EQ(2U, msgs.size()); + + CheckSuccessfulRequest(msgs[0], net::URLRequestTestJob::test_data_1()); + CheckSuccessfulRequest(msgs[1], net::URLRequestTestJob::test_data_3()); + + // Cancel requests for RVH 1. + host_.CancelBlockedRequestsForRoute(filter_->child_id(), 1); + KickOffRequest(); + while (net::URLRequestTestJob::ProcessOnePendingMessage()) {} + + EXPECT_EQ(0, host_.GetOutstandingRequestsMemoryCost(filter_->child_id())); + + msgs.clear(); + accum_.GetClassifiedMessages(&msgs); + ASSERT_EQ(0U, msgs.size()); +} + +// Tests that blocked requests are canceled if their associated process dies. +TEST_F(ResourceDispatcherHostTest, TestBlockedRequestsProcessDies) { + // This second filter is used to emulate a second process. + scoped_refptr<ForwardingFilter> second_filter = new ForwardingFilter(this); + + EXPECT_EQ(0, host_.GetOutstandingRequestsMemoryCost(filter_->child_id())); + EXPECT_EQ(0, + host_.GetOutstandingRequestsMemoryCost(second_filter->child_id())); + + host_.BlockRequestsForRoute(second_filter->child_id(), 0); + + MakeTestRequest(filter_.get(), 0, 1, net::URLRequestTestJob::test_url_1()); + MakeTestRequest(second_filter.get(), 0, 2, + net::URLRequestTestJob::test_url_2()); + MakeTestRequest(filter_.get(), 0, 3, net::URLRequestTestJob::test_url_3()); + MakeTestRequest(second_filter.get(), 0, 4, + net::URLRequestTestJob::test_url_1()); + + // Simulate process death. + host_.CancelRequestsForProcess(second_filter->child_id()); + + // Flush all the pending requests. + while (net::URLRequestTestJob::ProcessOnePendingMessage()) {} + + EXPECT_EQ(0, host_.GetOutstandingRequestsMemoryCost(filter_->child_id())); + EXPECT_EQ(0, + host_.GetOutstandingRequestsMemoryCost(second_filter->child_id())); + + // Sort out all the messages we saw by request. + ResourceIPCAccumulator::ClassifiedMessages msgs; + accum_.GetClassifiedMessages(&msgs); + + // The 2 requests for the RVH 0 should have been processed. + ASSERT_EQ(2U, msgs.size()); + + CheckSuccessfulRequest(msgs[0], net::URLRequestTestJob::test_data_1()); + CheckSuccessfulRequest(msgs[1], net::URLRequestTestJob::test_data_3()); + + EXPECT_TRUE(host_.blocked_requests_map_.empty()); +} + +// Tests that blocked requests don't leak when the ResourceDispatcherHost goes +// away. Note that we rely on Purify for finding the leaks if any. +// If this test turns the Purify bot red, check the ResourceDispatcherHost +// destructor to make sure the blocked requests are deleted. +TEST_F(ResourceDispatcherHostTest, TestBlockedRequestsDontLeak) { + // This second filter is used to emulate a second process. + scoped_refptr<ForwardingFilter> second_filter = new ForwardingFilter(this); + + host_.BlockRequestsForRoute(filter_->child_id(), 1); + host_.BlockRequestsForRoute(filter_->child_id(), 2); + host_.BlockRequestsForRoute(second_filter->child_id(), 1); + + MakeTestRequest(filter_.get(), 0, 1, net::URLRequestTestJob::test_url_1()); + MakeTestRequest(filter_.get(), 1, 2, net::URLRequestTestJob::test_url_2()); + MakeTestRequest(filter_.get(), 0, 3, net::URLRequestTestJob::test_url_3()); + MakeTestRequest(second_filter.get(), 1, 4, + net::URLRequestTestJob::test_url_1()); + MakeTestRequest(filter_.get(), 2, 5, net::URLRequestTestJob::test_url_2()); + MakeTestRequest(filter_.get(), 2, 6, net::URLRequestTestJob::test_url_3()); + + // Flush all the pending requests. + while (net::URLRequestTestJob::ProcessOnePendingMessage()) {} +} + +// Test the private helper method "CalculateApproximateMemoryCost()". +TEST_F(ResourceDispatcherHostTest, CalculateApproximateMemoryCost) { + net::URLRequest req(GURL("http://www.google.com"), NULL); + EXPECT_EQ(4427, ResourceDispatcherHost::CalculateApproximateMemoryCost(&req)); + + // Add 9 bytes of referrer. + req.set_referrer("123456789"); + EXPECT_EQ(4436, ResourceDispatcherHost::CalculateApproximateMemoryCost(&req)); + + // Add 33 bytes of upload content. + std::string upload_content; + upload_content.resize(33); + std::fill(upload_content.begin(), upload_content.end(), 'x'); + req.AppendBytesToUpload(upload_content.data(), upload_content.size()); + + // Since the upload throttling is disabled, this has no effect on the cost. + EXPECT_EQ(4436, ResourceDispatcherHost::CalculateApproximateMemoryCost(&req)); + + // Add a file upload -- should have no effect. + req.AppendFileToUpload(FilePath(FILE_PATH_LITERAL("does-not-exist.png"))); + EXPECT_EQ(4436, ResourceDispatcherHost::CalculateApproximateMemoryCost(&req)); +} + +// Test the private helper method "IncrementOutstandingRequestsMemoryCost()". +TEST_F(ResourceDispatcherHostTest, IncrementOutstandingRequestsMemoryCost) { + // Add some counts for render_process_host=7 + EXPECT_EQ(0, host_.GetOutstandingRequestsMemoryCost(7)); + EXPECT_EQ(1, host_.IncrementOutstandingRequestsMemoryCost(1, 7)); + EXPECT_EQ(2, host_.IncrementOutstandingRequestsMemoryCost(1, 7)); + EXPECT_EQ(3, host_.IncrementOutstandingRequestsMemoryCost(1, 7)); + + // Add some counts for render_process_host=3 + EXPECT_EQ(0, host_.GetOutstandingRequestsMemoryCost(3)); + EXPECT_EQ(1, host_.IncrementOutstandingRequestsMemoryCost(1, 3)); + EXPECT_EQ(2, host_.IncrementOutstandingRequestsMemoryCost(1, 3)); + + // Remove all the counts for render_process_host=7 + EXPECT_EQ(3, host_.GetOutstandingRequestsMemoryCost(7)); + EXPECT_EQ(2, host_.IncrementOutstandingRequestsMemoryCost(-1, 7)); + EXPECT_EQ(1, host_.IncrementOutstandingRequestsMemoryCost(-1, 7)); + EXPECT_EQ(0, host_.IncrementOutstandingRequestsMemoryCost(-1, 7)); + EXPECT_EQ(0, host_.GetOutstandingRequestsMemoryCost(7)); + + // Remove all the counts for render_process_host=3 + EXPECT_EQ(2, host_.GetOutstandingRequestsMemoryCost(3)); + EXPECT_EQ(1, host_.IncrementOutstandingRequestsMemoryCost(-1, 3)); + EXPECT_EQ(0, host_.IncrementOutstandingRequestsMemoryCost(-1, 3)); + EXPECT_EQ(0, host_.GetOutstandingRequestsMemoryCost(3)); + + // When an entry reaches 0, it should be deleted. + EXPECT_TRUE(host_.outstanding_requests_memory_cost_map_.end() == + host_.outstanding_requests_memory_cost_map_.find(7)); + EXPECT_TRUE(host_.outstanding_requests_memory_cost_map_.end() == + host_.outstanding_requests_memory_cost_map_.find(3)); +} + +// Test that when too many requests are outstanding for a particular +// render_process_host_id, any subsequent request from it fails. +TEST_F(ResourceDispatcherHostTest, TooManyOutstandingRequests) { + EXPECT_EQ(0, host_.GetOutstandingRequestsMemoryCost(filter_->child_id())); + + // Expected cost of each request as measured by + // ResourceDispatcherHost::CalculateApproximateMemoryCost(). + int kMemoryCostOfTest2Req = + ResourceDispatcherHost::kAvgBytesPerOutstandingRequest + + std::string("GET").size() + + net::URLRequestTestJob::test_url_2().spec().size(); + + // Tighten the bound on the ResourceDispatcherHost, to speed things up. + int kMaxCostPerProcess = 440000; + host_.set_max_outstanding_requests_cost_per_process(kMaxCostPerProcess); + + // Determine how many instance of test_url_2() we can request before + // throttling kicks in. + size_t kMaxRequests = kMaxCostPerProcess / kMemoryCostOfTest2Req; + + // This second filter is used to emulate a second process. + scoped_refptr<ForwardingFilter> second_filter = new ForwardingFilter(this); + + // Saturate the number of outstanding requests for our process. + for (size_t i = 0; i < kMaxRequests; ++i) { + MakeTestRequest(filter_.get(), 0, i + 1, + net::URLRequestTestJob::test_url_2()); + } + + // Issue two more requests for our process -- these should fail immediately. + MakeTestRequest(filter_.get(), 0, kMaxRequests + 1, + net::URLRequestTestJob::test_url_2()); + MakeTestRequest(filter_.get(), 0, kMaxRequests + 2, + net::URLRequestTestJob::test_url_2()); + + // Issue two requests for the second process -- these should succeed since + // it is just process 0 that is saturated. + MakeTestRequest(second_filter.get(), 0, kMaxRequests + 3, + net::URLRequestTestJob::test_url_2()); + MakeTestRequest(second_filter.get(), 0, kMaxRequests + 4, + net::URLRequestTestJob::test_url_2()); + + // Flush all the pending requests. + while (net::URLRequestTestJob::ProcessOnePendingMessage()) {} + MessageLoop::current()->RunAllPending(); + + EXPECT_EQ(0, host_.GetOutstandingRequestsMemoryCost(filter_->child_id())); + + // Sorts out all the messages we saw by request. + ResourceIPCAccumulator::ClassifiedMessages msgs; + accum_.GetClassifiedMessages(&msgs); + + // We issued (kMaxRequests + 4) total requests. + ASSERT_EQ(kMaxRequests + 4, msgs.size()); + + // Check that the first kMaxRequests succeeded. + for (size_t i = 0; i < kMaxRequests; ++i) + CheckSuccessfulRequest(msgs[i], net::URLRequestTestJob::test_data_2()); + + // Check that the subsequent two requests (kMaxRequests + 1) and + // (kMaxRequests + 2) were failed, since the per-process bound was reached. + for (int i = 0; i < 2; ++i) { + // Should have sent a single RequestComplete message. + int index = kMaxRequests + i; + EXPECT_EQ(1U, msgs[index].size()); + EXPECT_EQ(ViewMsg_Resource_RequestComplete::ID, msgs[index][0].type()); + + // The RequestComplete message should have had status + // (CANCELLED, ERR_INSUFFICIENT_RESOURCES). + int request_id; + net::URLRequestStatus status; + + void* iter = NULL; + EXPECT_TRUE(IPC::ReadParam(&msgs[index][0], &iter, &request_id)); + EXPECT_TRUE(IPC::ReadParam(&msgs[index][0], &iter, &status)); + + EXPECT_EQ(index + 1, request_id); + EXPECT_EQ(net::URLRequestStatus::CANCELED, status.status()); + EXPECT_EQ(net::ERR_INSUFFICIENT_RESOURCES, status.os_error()); + } + + // The final 2 requests should have succeeded. + CheckSuccessfulRequest(msgs[kMaxRequests + 2], + net::URLRequestTestJob::test_data_2()); + CheckSuccessfulRequest(msgs[kMaxRequests + 3], + net::URLRequestTestJob::test_data_2()); +} + +// Tests that we sniff the mime type for a simple request. +TEST_F(ResourceDispatcherHostTest, MimeSniffed) { + EXPECT_EQ(0, host_.GetOutstandingRequestsMemoryCost(0)); + + std::string response("HTTP/1.1 200 OK\n\n"); + std::string raw_headers(net::HttpUtil::AssembleRawHeaders(response.data(), + response.size())); + std::string response_data("<html><title>Test One</title></html>"); + SetResponse(raw_headers, response_data); + + HandleScheme("http"); + MakeTestRequest(0, 1, GURL("http:bla")); + + // Flush all pending requests. + while (net::URLRequestTestJob::ProcessOnePendingMessage()) {} + + EXPECT_EQ(0, host_.GetOutstandingRequestsMemoryCost(0)); + + // Sorts out all the messages we saw by request. + ResourceIPCAccumulator::ClassifiedMessages msgs; + accum_.GetClassifiedMessages(&msgs); + ASSERT_EQ(1U, msgs.size()); + + ResourceResponseHead response_head; + GetResponseHead(msgs[0], &response_head); + ASSERT_EQ("text/html", response_head.mime_type); +} + +// Tests that we don't sniff the mime type when the server provides one. +TEST_F(ResourceDispatcherHostTest, MimeNotSniffed) { + EXPECT_EQ(0, host_.GetOutstandingRequestsMemoryCost(0)); + + std::string response("HTTP/1.1 200 OK\n" + "Content-type: image/jpeg\n\n"); + std::string raw_headers(net::HttpUtil::AssembleRawHeaders(response.data(), + response.size())); + std::string response_data("<html><title>Test One</title></html>"); + SetResponse(raw_headers, response_data); + + HandleScheme("http"); + MakeTestRequest(0, 1, GURL("http:bla")); + + // Flush all pending requests. + while (net::URLRequestTestJob::ProcessOnePendingMessage()) {} + + EXPECT_EQ(0, host_.GetOutstandingRequestsMemoryCost(0)); + + // Sorts out all the messages we saw by request. + ResourceIPCAccumulator::ClassifiedMessages msgs; + accum_.GetClassifiedMessages(&msgs); + ASSERT_EQ(1U, msgs.size()); + + ResourceResponseHead response_head; + GetResponseHead(msgs[0], &response_head); + ASSERT_EQ("image/jpeg", response_head.mime_type); +} + +// Tests that we don't sniff the mime type when there is no message body. +TEST_F(ResourceDispatcherHostTest, MimeNotSniffed2) { + EXPECT_EQ(0, host_.GetOutstandingRequestsMemoryCost(0)); + + std::string response("HTTP/1.1 304 Not Modified\n\n"); + std::string raw_headers(net::HttpUtil::AssembleRawHeaders(response.data(), + response.size())); + std::string response_data; + SetResponse(raw_headers, response_data); + + HandleScheme("http"); + MakeTestRequest(0, 1, GURL("http:bla")); + + // Flush all pending requests. + while (net::URLRequestTestJob::ProcessOnePendingMessage()) {} + + EXPECT_EQ(0, host_.GetOutstandingRequestsMemoryCost(0)); + + // Sorts out all the messages we saw by request. + ResourceIPCAccumulator::ClassifiedMessages msgs; + accum_.GetClassifiedMessages(&msgs); + ASSERT_EQ(1U, msgs.size()); + + ResourceResponseHead response_head; + GetResponseHead(msgs[0], &response_head); + ASSERT_EQ("", response_head.mime_type); +} + +TEST_F(ResourceDispatcherHostTest, MimeSniff204) { + EXPECT_EQ(0, host_.GetOutstandingRequestsMemoryCost(0)); + + std::string response("HTTP/1.1 204 No Content\n\n"); + std::string raw_headers(net::HttpUtil::AssembleRawHeaders(response.data(), + response.size())); + std::string response_data; + SetResponse(raw_headers, response_data); + + HandleScheme("http"); + MakeTestRequest(0, 1, GURL("http:bla")); + + // Flush all pending requests. + while (net::URLRequestTestJob::ProcessOnePendingMessage()) {} + + EXPECT_EQ(0, host_.GetOutstandingRequestsMemoryCost(0)); + + // Sorts out all the messages we saw by request. + ResourceIPCAccumulator::ClassifiedMessages msgs; + accum_.GetClassifiedMessages(&msgs); + ASSERT_EQ(1U, msgs.size()); + + ResourceResponseHead response_head; + GetResponseHead(msgs[0], &response_head); + ASSERT_EQ("text/plain", response_head.mime_type); +} + +// Tests for crbug.com/31266 (Non-2xx + application/octet-stream). +TEST_F(ResourceDispatcherHostTest, ForbiddenDownload) { + EXPECT_EQ(0, host_.GetOutstandingRequestsMemoryCost(0)); + + std::string response("HTTP/1.1 403 Forbidden\n" + "Content-disposition: attachment; filename=blah\n" + "Content-type: application/octet-stream\n\n"); + std::string raw_headers(net::HttpUtil::AssembleRawHeaders(response.data(), + response.size())); + std::string response_data("<html><title>Test One</title></html>"); + SetResponse(raw_headers, response_data); + + // Only MAIN_FRAMEs can trigger a download. + SetResourceType(ResourceType::MAIN_FRAME); + + HandleScheme("http"); + MakeTestRequest(0, 1, GURL("http:bla")); + + // Flush all pending requests. + while (net::URLRequestTestJob::ProcessOnePendingMessage()) {} + + EXPECT_EQ(0, host_.GetOutstandingRequestsMemoryCost(0)); + + // Sorts out all the messages we saw by request. + ResourceIPCAccumulator::ClassifiedMessages msgs; + accum_.GetClassifiedMessages(&msgs); + + // We should have gotten one RequestComplete message. + ASSERT_EQ(1U, msgs[0].size()); + EXPECT_EQ(ViewMsg_Resource_RequestComplete::ID, msgs[0][0].type()); + + // The RequestComplete message should have had status + // (CANCELED, ERR_FILE_NOT_FOUND). + int request_id; + net::URLRequestStatus status; + + void* iter = NULL; + EXPECT_TRUE(IPC::ReadParam(&msgs[0][0], &iter, &request_id)); + EXPECT_TRUE(IPC::ReadParam(&msgs[0][0], &iter, &status)); + + EXPECT_EQ(1, request_id); + EXPECT_EQ(net::URLRequestStatus::CANCELED, status.status()); + EXPECT_EQ(net::ERR_FILE_NOT_FOUND, status.os_error()); +} + +class DummyResourceHandler : public ResourceHandler { + public: + DummyResourceHandler() {} + + // Called as upload progress is made. + bool OnUploadProgress(int request_id, uint64 position, uint64 size) { + return true; + } + + bool OnRequestRedirected(int request_id, const GURL& url, + ResourceResponse* response, bool* defer) { + return true; + } + + bool OnResponseStarted(int request_id, ResourceResponse* response) { + return true; + } + + bool OnWillStart(int request_id, const GURL& url, bool* defer) { + return true; + } + + bool OnWillRead( + int request_id, net::IOBuffer** buf, int* buf_size, int min_size) { + return true; + } + + bool OnReadCompleted(int request_id, int* bytes_read) { return true; } + + bool OnResponseCompleted( + int request_id, + const net::URLRequestStatus& status, + const std::string& info) { + return true; + } + + void OnRequestClosed() {} + + private: + DISALLOW_COPY_AND_ASSIGN(DummyResourceHandler); +}; + +class ApplyExtensionLocalizationFilterTest : public testing::Test { + protected: + void SetUp() { + url_.reset(new GURL( + "chrome-extension://behllobkkfkfnphdnhnkndlbkcpglgmj/popup.html")); + resource_type_ = ResourceType::STYLESHEET; + resource_handler_.reset(new DummyResourceHandler()); + request_info_.reset(CreateNewResourceRequestInfo()); + } + + ResourceDispatcherHostRequestInfo* CreateNewResourceRequestInfo() { + return new ResourceDispatcherHostRequestInfo( + resource_handler_.get(), ChildProcessInfo::RENDER_PROCESS, 0, 0, 0, + ResourceType::STYLESHEET, 0U, false, false, false, -1, -1); + } + + scoped_ptr<GURL> url_; + ResourceType::Type resource_type_; + scoped_ptr<DummyResourceHandler> resource_handler_; + scoped_ptr<ResourceDispatcherHostRequestInfo> request_info_; +}; + +TEST_F(ApplyExtensionLocalizationFilterTest, WrongScheme) { + url_.reset(new GURL("html://behllobkkfkfnphdnhnkndlbkcpglgmj/popup.html")); + ResourceDispatcherHost::ApplyExtensionLocalizationFilter(*url_, + resource_type_, request_info_.get()); + + EXPECT_FALSE(request_info_->replace_extension_localization_templates()); +} + +TEST_F(ApplyExtensionLocalizationFilterTest, GoodScheme) { + ResourceDispatcherHost::ApplyExtensionLocalizationFilter(*url_, + resource_type_, request_info_.get()); + + EXPECT_TRUE(request_info_->replace_extension_localization_templates()); +} + +TEST_F(ApplyExtensionLocalizationFilterTest, GoodSchemeWrongResourceType) { + resource_type_ = ResourceType::MAIN_FRAME; + ResourceDispatcherHost::ApplyExtensionLocalizationFilter(*url_, + resource_type_, request_info_.get()); + + EXPECT_FALSE(request_info_->replace_extension_localization_templates()); +} diff --git a/content/browser/renderer_host/resource_handler.h b/content/browser/renderer_host/resource_handler.h new file mode 100644 index 0000000..c4de36e --- /dev/null +++ b/content/browser/renderer_host/resource_handler.h @@ -0,0 +1,96 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This is the browser side of the resource dispatcher, it receives requests +// from the RenderProcessHosts, and dispatches them to URLRequests. It then +// fowards the messages from the URLRequests back to the correct process for +// handling. +// +// See http://dev.chromium.org/developers/design-documents/multi-process-resource-loading + +#ifndef CONTENT_BROWSER_RENDERER_HOST_RESOURCE_HANDLER_H_ +#define CONTENT_BROWSER_RENDERER_HOST_RESOURCE_HANDLER_H_ +#pragma once + +#include <string> + +#include "chrome/browser/browser_thread.h" + +namespace net { +class IOBuffer; +class URLRequestStatus; +} // namespace net + +struct ResourceResponse; +class GURL; + +// The resource dispatcher host uses this interface to push load events to the +// renderer, allowing for differences in the types of IPC messages generated. +// See the implementations of this interface defined below. +class ResourceHandler + : public base::RefCountedThreadSafe< + ResourceHandler, BrowserThread::DeleteOnIOThread> { + public: + // Called as upload progress is made. + virtual bool OnUploadProgress(int request_id, + uint64 position, + uint64 size) = 0; + + // The request was redirected to a new URL. |*defer| has an initial value of + // false. Set |*defer| to true to defer the redirect. The redirect may be + // followed later on via ResourceDispatcherHost::FollowDeferredRedirect. + virtual bool OnRequestRedirected(int request_id, const GURL& url, + ResourceResponse* response, + bool* defer) = 0; + + // Response headers and meta data are available. + virtual bool OnResponseStarted(int request_id, + ResourceResponse* response) = 0; + + // Called before the net::URLRequest for |request_id| (whose url is |url|) is + // to be started. If the handler returns false, then the request is cancelled. + // Otherwise if the return value is true, the ResourceHandler can delay the + // request from starting by setting |*defer = true|. A deferred request will + // not have called net::URLRequest::Start(), and will not resume until someone + // calls ResourceDispatcherHost::StartDeferredRequest(). + virtual bool OnWillStart(int request_id, const GURL& url, bool* defer) = 0; + + // Data will be read for the response. Upon success, this method places the + // size and address of the buffer where the data is to be written in its + // out-params. This call will be followed by either OnReadCompleted or + // OnResponseCompleted, at which point the buffer may be recycled. + virtual bool OnWillRead(int request_id, + net::IOBuffer** buf, + int* buf_size, + int min_size) = 0; + + // Data (*bytes_read bytes) was written into the buffer provided by + // OnWillRead. A return value of false cancels the request, true continues + // reading data. + virtual bool OnReadCompleted(int request_id, int* bytes_read) = 0; + + // The response is complete. The final response status is given. + // Returns false if the handler is deferring the call to a later time. + virtual bool OnResponseCompleted(int request_id, + const net::URLRequestStatus& status, + const std::string& security_info) = 0; + + // Signals that the request is closed (i.e. finished successfully, cancelled). + // This is a signal that the associated net::URLRequest isn't valid anymore. + virtual void OnRequestClosed() = 0; + + // This notification is synthesized by the RedirectToFileResourceHandler + // to indicate progress of 'download_to_file' requests. OnReadCompleted + // calls are consumed by the RedirectToFileResourceHandler and replaced + // with OnDataDownloaded calls. + virtual void OnDataDownloaded(int request_id, int bytes_downloaded) {} + + protected: + friend class BrowserThread; + friend class DeleteTask<ResourceHandler>; + + virtual ~ResourceHandler() {} +}; + +#endif // CONTENT_BROWSER_RENDERER_HOST_RESOURCE_HANDLER_H_ diff --git a/content/browser/renderer_host/resource_message_filter.cc b/content/browser/renderer_host/resource_message_filter.cc new file mode 100644 index 0000000..7b3229b --- /dev/null +++ b/content/browser/renderer_host/resource_message_filter.cc @@ -0,0 +1,52 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "content/browser/renderer_host/resource_message_filter.h" + +#include "chrome/browser/net/chrome_url_request_context.h" +#include "chrome/browser/profiles/profile.h" +#include "chrome/common/render_messages.h" +#include "content/browser/renderer_host/resource_dispatcher_host.h" + +ResourceMessageFilter::ResourceMessageFilter( + int child_id, + ChildProcessInfo::ProcessType process_type, + ResourceDispatcherHost* resource_dispatcher_host) + : child_id_(child_id), + process_type_(process_type), + resource_dispatcher_host_(resource_dispatcher_host) { +} + +ResourceMessageFilter::~ResourceMessageFilter() { +} + +void ResourceMessageFilter::OnChannelClosing() { + BrowserMessageFilter::OnChannelClosing(); + + // Unhook us from all pending network requests so they don't get sent to a + // deleted object. + resource_dispatcher_host_->CancelRequestsForProcess(child_id_); +} + +bool ResourceMessageFilter::OnMessageReceived(const IPC::Message& message, + bool* message_was_ok) { + return resource_dispatcher_host_->OnMessageReceived( + message, this, message_was_ok); +} + +ChromeURLRequestContext* ResourceMessageFilter::GetURLRequestContext( + const ViewHostMsg_Resource_Request& resource_request) { + net::URLRequestContext* rv = NULL; + if (url_request_context_override_.get()) + rv = url_request_context_override_->GetRequestContext(resource_request); + + if (!rv) { + URLRequestContextGetter* context_getter = + Profile::GetDefaultRequestContext(); + if (context_getter) + rv = context_getter->GetURLRequestContext(); + } + + return static_cast<ChromeURLRequestContext*>(rv); +} diff --git a/content/browser/renderer_host/resource_message_filter.h b/content/browser/renderer_host/resource_message_filter.h new file mode 100644 index 0000000..7e8fb93 --- /dev/null +++ b/content/browser/renderer_host/resource_message_filter.h @@ -0,0 +1,81 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CONTENT_BROWSER_RENDERER_HOST_RESOURCE_MESSAGE_FILTER_H_ +#define CONTENT_BROWSER_RENDERER_HOST_RESOURCE_MESSAGE_FILTER_H_ + +#include "base/scoped_ptr.h" +#include "chrome/browser/browser_message_filter.h" +#include "chrome/common/child_process_info.h" + +class ChromeURLRequestContext; +class ResourceDispatcherHost; +struct ViewHostMsg_Resource_Request; + +namespace net { +class URLRequestContext; +} // namespace net + +// This class filters out incoming IPC messages for network requests and +// processes them on the IPC thread. As a result, network requests are not +// delayed by costly UI processing that may be occuring on the main thread of +// the browser. It also means that any hangs in starting a network request +// will not interfere with browser UI. +class ResourceMessageFilter : public BrowserMessageFilter { + public: + // Allows overriding the net::URLRequestContext used to service requests. + class URLRequestContextOverride + : public base::RefCountedThreadSafe<URLRequestContextOverride> { + public: + URLRequestContextOverride() {} + + virtual net::URLRequestContext* GetRequestContext( + const ViewHostMsg_Resource_Request& resource_request) = 0; + + protected: + friend class base::RefCountedThreadSafe<URLRequestContextOverride>; + virtual ~URLRequestContextOverride() {} + + DISALLOW_COPY_AND_ASSIGN(URLRequestContextOverride); + }; + + ResourceMessageFilter(int child_id, + ChildProcessInfo::ProcessType process_type, + ResourceDispatcherHost* resource_dispatcher_host); + + // BrowserMessageFilter implementation. + virtual void OnChannelClosing(); + virtual bool OnMessageReceived(const IPC::Message& message, + bool* message_was_ok); + + // Returns the net::URLRequestContext for the given request. + ChromeURLRequestContext* GetURLRequestContext( + const ViewHostMsg_Resource_Request& resource_request); + + void set_url_request_context_override(URLRequestContextOverride* u) { + url_request_context_override_ = u; + } + + int child_id() const { return child_id_; } + ChildProcessInfo::ProcessType process_type() const { return process_type_; } + + protected: + // Protected destructor so that we can be overriden in tests. + virtual ~ResourceMessageFilter(); + + private: + // The ID of the child process. + int child_id_; + + ChildProcessInfo::ProcessType process_type_; + + // Owned by BrowserProcess, which is guaranteed to outlive us. + ResourceDispatcherHost* resource_dispatcher_host_; + + scoped_refptr<URLRequestContextOverride> url_request_context_override_; + + DISALLOW_IMPLICIT_CONSTRUCTORS(ResourceMessageFilter); +}; + +#endif // CONTENT_BROWSER_RENDERER_HOST_RESOURCE_MESSAGE_FILTER_H_ diff --git a/content/browser/renderer_host/resource_queue.cc b/content/browser/renderer_host/resource_queue.cc new file mode 100644 index 0000000..ef58791 --- /dev/null +++ b/content/browser/renderer_host/resource_queue.cc @@ -0,0 +1,93 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "content/browser/renderer_host/resource_queue.h" + +#include "base/stl_util-inl.h" +#include "chrome/browser/browser_thread.h" +#include "content/browser/renderer_host/global_request_id.h" +#include "content/browser/renderer_host/resource_dispatcher_host_request_info.h" + +ResourceQueueDelegate::~ResourceQueueDelegate() { +} + +ResourceQueue::ResourceQueue() : shutdown_(false) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); +} + +ResourceQueue::~ResourceQueue() { + DCHECK(shutdown_); +} + +void ResourceQueue::Initialize(const DelegateSet& delegates) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + DCHECK(delegates_.empty()); + delegates_ = delegates; +} + +void ResourceQueue::Shutdown() { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + + shutdown_ = true; + for (DelegateSet::iterator i = delegates_.begin(); + i != delegates_.end(); ++i) { + (*i)->WillShutdownResourceQueue(); + } +} + +void ResourceQueue::AddRequest( + net::URLRequest* request, + const ResourceDispatcherHostRequestInfo& request_info) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + DCHECK(!shutdown_); + + GlobalRequestID request_id(request_info.child_id(), + request_info.request_id()); + + DCHECK(!ContainsKey(requests_, request_id)) + << "child_id:" << request_info.child_id() + << ", request_id:" << request_info.request_id(); + requests_[request_id] = request; + + DelegateSet interested_delegates; + + for (DelegateSet::iterator i = delegates_.begin(); + i != delegates_.end(); ++i) { + if ((*i)->ShouldDelayRequest(request, request_info, request_id)) + interested_delegates.insert(*i); + } + + if (interested_delegates.empty()) { + request->Start(); + return; + } + + DCHECK(!ContainsKey(interested_delegates_, request_id)); + interested_delegates_[request_id] = interested_delegates; +} + +void ResourceQueue::RemoveRequest(const GlobalRequestID& request_id) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + requests_.erase(request_id); +} + +void ResourceQueue::StartDelayedRequest(ResourceQueueDelegate* delegate, + const GlobalRequestID& request_id) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + DCHECK(!shutdown_); + + DCHECK(ContainsKey(interested_delegates_, request_id)); + DCHECK(ContainsKey(interested_delegates_[request_id], delegate)); + interested_delegates_[request_id].erase(delegate); + if (interested_delegates_[request_id].empty()) { + interested_delegates_.erase(request_id); + + if (ContainsKey(requests_, request_id)) { + net::URLRequest* request = requests_[request_id]; + // The request shouldn't have started (SUCCESS is the initial state). + DCHECK_EQ(net::URLRequestStatus::SUCCESS, request->status().status()); + request->Start(); + } + } +} diff --git a/content/browser/renderer_host/resource_queue.h b/content/browser/renderer_host/resource_queue.h new file mode 100644 index 0000000..4bf45d9 --- /dev/null +++ b/content/browser/renderer_host/resource_queue.h @@ -0,0 +1,100 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CONTENT_BROWSER_RENDERER_HOST_RESOURCE_QUEUE_H_ +#define CONTENT_BROWSER_RENDERER_HOST_RESOURCE_QUEUE_H_ +#pragma once + +#include <map> +#include <set> + +#include "base/basictypes.h" + +namespace net { +class URLRequest; +} // namespace net + +class ResourceDispatcherHostRequestInfo; +struct GlobalRequestID; + +// Makes decisions about delaying or not each net::URLRequest in the queue. +// All methods are called on the IO thread. +class ResourceQueueDelegate { + public: + // Should return true if it wants the |request| to not be started at this + // point. To start the delayed request, ResourceQueue::StartDelayedRequest + // should be used. + virtual bool ShouldDelayRequest( + net::URLRequest* request, + const ResourceDispatcherHostRequestInfo& request_info, + const GlobalRequestID& request_id) = 0; + + // Called just before ResourceQueue shutdown. After that, the delegate + // should not use the ResourceQueue. + virtual void WillShutdownResourceQueue() = 0; + + protected: + virtual ~ResourceQueueDelegate(); +}; + +// Makes it easy to delay starting URL requests until specified conditions are +// met. +class ResourceQueue { + public: + typedef std::set<ResourceQueueDelegate*> DelegateSet; + + // UI THREAD ONLY ------------------------------------------------------------ + + // Construct the queue. You must initialize it using Initialize. + ResourceQueue(); + ~ResourceQueue(); + + // Initialize the queue with set of delegates it should ask for each incoming + // request. + void Initialize(const DelegateSet& delegates); + + // IO THREAD ONLY ------------------------------------------------------------ + + // Must be called before destroying the queue. No other methods can be called + // after that. + void Shutdown(); + + // Takes care to start the |request| after all delegates allow that. If no + // delegate demands delaying the request it will be started immediately. + void AddRequest(net::URLRequest* request, + const ResourceDispatcherHostRequestInfo& request_info); + + // Tells the queue that the net::URLRequest object associated with + // |request_id| is no longer valid. + void RemoveRequest(const GlobalRequestID& request_id); + + // A delegate should call StartDelayedRequest when it wants to allow the + // request to start. If it was the last delegate that demanded the request + // to be delayed, the request will be started. + void StartDelayedRequest(ResourceQueueDelegate* delegate, + const GlobalRequestID& request_id); + + private: + typedef std::map<GlobalRequestID, net::URLRequest*> RequestMap; + typedef std::map<GlobalRequestID, DelegateSet> InterestedDelegatesMap; + + // The registered delegates. Will not change after the queue has been + // initialized. + DelegateSet delegates_; + + // Stores net::URLRequest objects associated with each GlobalRequestID. This + // helps decoupling the queue from ResourceDispatcherHost. + RequestMap requests_; + + // Maps a GlobalRequestID to the set of delegates that want to prevent the + // associated request from starting yet. + InterestedDelegatesMap interested_delegates_; + + // True when we are shutting down. + bool shutdown_; + + DISALLOW_COPY_AND_ASSIGN(ResourceQueue); +}; + +#endif // CONTENT_BROWSER_RENDERER_HOST_RESOURCE_QUEUE_H_ diff --git a/content/browser/renderer_host/resource_queue_unittest.cc b/content/browser/renderer_host/resource_queue_unittest.cc new file mode 100644 index 0000000..7041bed --- /dev/null +++ b/content/browser/renderer_host/resource_queue_unittest.cc @@ -0,0 +1,289 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/message_loop.h" +#include "base/scoped_ptr.h" +#include "chrome/browser/browser_thread.h" +#include "content/browser/renderer_host/global_request_id.h" +#include "content/browser/renderer_host/resource_dispatcher_host_request_info.h" +#include "content/browser/renderer_host/resource_handler.h" +#include "content/browser/renderer_host/resource_queue.h" +#include "googleurl/src/gurl.h" +#include "net/url_request/url_request.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +const char kTestUrl[] = "data:text/plain,Hello World!"; + +class DummyResourceHandler : public ResourceHandler { + public: + DummyResourceHandler() { + } + + bool OnUploadProgress(int request_id, uint64 position, uint64 size) { + NOTREACHED(); + return true; + } + + virtual bool OnRequestRedirected(int request_id, const GURL& url, + ResourceResponse* response, + bool* defer) { + NOTREACHED(); + return true; + } + + virtual bool OnResponseStarted(int request_id, + ResourceResponse* response) { + NOTREACHED(); + return true; + } + + virtual bool OnWillStart(int request_id, const GURL& url, bool* defer) { + NOTREACHED(); + return true; + } + + virtual bool OnWillRead(int request_id, + net::IOBuffer** buf, + int* buf_size, + int min_size) { + NOTREACHED(); + return true; + } + + virtual bool OnReadCompleted(int request_id, int* bytes_read) { + NOTREACHED(); + return true; + } + + virtual bool OnResponseCompleted(int request_id, + const net::URLRequestStatus& status, + const std::string& security_info) { + NOTREACHED(); + return true; + } + + virtual void OnRequestClosed() { + } + + private: + DISALLOW_COPY_AND_ASSIGN(DummyResourceHandler); +}; + +ResourceDispatcherHostRequestInfo* GetRequestInfo(int request_id) { + return new ResourceDispatcherHostRequestInfo( + new DummyResourceHandler(), ChildProcessInfo::RENDER_PROCESS, 0, 0, + request_id, ResourceType::MAIN_FRAME, 0, false, false, false, -1, -1); +} + +void InitializeQueue(ResourceQueue* queue, ResourceQueueDelegate* delegate) { + ResourceQueue::DelegateSet delegate_set; + delegate_set.insert(delegate); + queue->Initialize(delegate_set); +} + +void InitializeQueue(ResourceQueue* queue, + ResourceQueueDelegate* delegate1, + ResourceQueueDelegate* delegate2) { + ResourceQueue::DelegateSet delegate_set; + delegate_set.insert(delegate1); + delegate_set.insert(delegate2); + queue->Initialize(delegate_set); +} + +class NeverDelayingDelegate : public ResourceQueueDelegate { + public: + NeverDelayingDelegate() { + } + + virtual bool ShouldDelayRequest( + net::URLRequest* request, + const ResourceDispatcherHostRequestInfo& request_info, + const GlobalRequestID& request_id) { + return false; + } + + virtual void WillShutdownResourceQueue() { + } + + private: + DISALLOW_COPY_AND_ASSIGN(NeverDelayingDelegate); +}; + +class AlwaysDelayingDelegate : public ResourceQueueDelegate { + public: + explicit AlwaysDelayingDelegate(ResourceQueue* resource_queue) + : resource_queue_(resource_queue) { + } + + virtual bool ShouldDelayRequest( + net::URLRequest* request, + const ResourceDispatcherHostRequestInfo& request_info, + const GlobalRequestID& request_id) { + delayed_requests_.push_back(request_id); + return true; + } + + virtual void WillShutdownResourceQueue() { + resource_queue_ = NULL; + } + + void StartDelayedRequests() { + if (!resource_queue_) + return; + + for (RequestList::iterator i = delayed_requests_.begin(); + i != delayed_requests_.end(); ++i) { + resource_queue_->StartDelayedRequest(this, *i); + } + } + + private: + typedef std::vector<GlobalRequestID> RequestList; + + ResourceQueue* resource_queue_; + + RequestList delayed_requests_; + + DISALLOW_COPY_AND_ASSIGN(AlwaysDelayingDelegate); +}; + +class ResourceQueueTest : public testing::Test, + public net::URLRequest::Delegate { + public: + ResourceQueueTest() + : response_started_count_(0), + message_loop_(MessageLoop::TYPE_IO), + ui_thread_(BrowserThread::UI, &message_loop_), + io_thread_(BrowserThread::IO, &message_loop_) { + } + + virtual void OnResponseStarted(net::URLRequest* request) { + response_started_count_++; + // We're not going to do anything more with the request. Cancel it now + // to avoid leaking net::URLRequestJob. + request->Cancel(); + } + + virtual void OnReadCompleted(net::URLRequest* request, int bytes_read) { + } + + protected: + int response_started_count_; + + private: + MessageLoop message_loop_; + BrowserThread ui_thread_; + BrowserThread io_thread_; +}; + +TEST_F(ResourceQueueTest, Basic) { + // Test the simplest lifycycle of ResourceQueue. + ResourceQueue queue; + queue.Initialize(ResourceQueue::DelegateSet()); + queue.Shutdown(); +} + +TEST_F(ResourceQueueTest, NeverDelayingDelegate) { + ResourceQueue queue; + + NeverDelayingDelegate delegate; + InitializeQueue(&queue, &delegate); + + net::URLRequest request(GURL(kTestUrl), this); + scoped_ptr<ResourceDispatcherHostRequestInfo> request_info(GetRequestInfo(0)); + EXPECT_EQ(0, response_started_count_); + queue.AddRequest(&request, *request_info.get()); + MessageLoop::current()->RunAllPending(); + EXPECT_EQ(1, response_started_count_); + + queue.Shutdown(); +} + +TEST_F(ResourceQueueTest, AlwaysDelayingDelegate) { + ResourceQueue queue; + + AlwaysDelayingDelegate delegate(&queue); + InitializeQueue(&queue, &delegate); + + net::URLRequest request(GURL(kTestUrl), this); + scoped_ptr<ResourceDispatcherHostRequestInfo> request_info(GetRequestInfo(0)); + EXPECT_EQ(0, response_started_count_); + queue.AddRequest(&request, *request_info.get()); + MessageLoop::current()->RunAllPending(); + EXPECT_EQ(0, response_started_count_); + delegate.StartDelayedRequests(); + MessageLoop::current()->RunAllPending(); + EXPECT_EQ(1, response_started_count_); + + queue.Shutdown(); +} + +TEST_F(ResourceQueueTest, AlwaysDelayingDelegateAfterShutdown) { + ResourceQueue queue; + + AlwaysDelayingDelegate delegate(&queue); + InitializeQueue(&queue, &delegate); + + net::URLRequest request(GURL(kTestUrl), this); + scoped_ptr<ResourceDispatcherHostRequestInfo> request_info(GetRequestInfo(0)); + EXPECT_EQ(0, response_started_count_); + queue.AddRequest(&request, *request_info.get()); + MessageLoop::current()->RunAllPending(); + EXPECT_EQ(0, response_started_count_); + + queue.Shutdown(); + + delegate.StartDelayedRequests(); + MessageLoop::current()->RunAllPending(); + EXPECT_EQ(0, response_started_count_); +} + +TEST_F(ResourceQueueTest, TwoDelegates) { + ResourceQueue queue; + + AlwaysDelayingDelegate always_delaying_delegate(&queue); + NeverDelayingDelegate never_delaying_delegate; + InitializeQueue(&queue, &always_delaying_delegate, &never_delaying_delegate); + + net::URLRequest request(GURL(kTestUrl), this); + scoped_ptr<ResourceDispatcherHostRequestInfo> request_info(GetRequestInfo(0)); + EXPECT_EQ(0, response_started_count_); + queue.AddRequest(&request, *request_info.get()); + MessageLoop::current()->RunAllPending(); + EXPECT_EQ(0, response_started_count_); + always_delaying_delegate.StartDelayedRequests(); + MessageLoop::current()->RunAllPending(); + EXPECT_EQ(1, response_started_count_); + + queue.Shutdown(); +} + +TEST_F(ResourceQueueTest, RemoveRequest) { + ResourceQueue queue; + + AlwaysDelayingDelegate delegate(&queue); + InitializeQueue(&queue, &delegate); + + net::URLRequest request(GURL(kTestUrl), this); + scoped_ptr<ResourceDispatcherHostRequestInfo> request_info(GetRequestInfo(0)); + GlobalRequestID request_id(request_info->child_id(), + request_info->request_id()); + EXPECT_EQ(0, response_started_count_); + queue.AddRequest(&request, *request_info.get()); + MessageLoop::current()->RunAllPending(); + EXPECT_EQ(0, response_started_count_); + queue.RemoveRequest(request_id); + delegate.StartDelayedRequests(); + MessageLoop::current()->RunAllPending(); + EXPECT_EQ(0, response_started_count_); + + queue.Shutdown(); + + MessageLoop::current()->RunAllPending(); + EXPECT_EQ(0, response_started_count_); +} + +} // namespace diff --git a/content/browser/renderer_host/resource_request_details.cc b/content/browser/renderer_host/resource_request_details.cc new file mode 100644 index 0000000..5570181 --- /dev/null +++ b/content/browser/renderer_host/resource_request_details.cc @@ -0,0 +1,50 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "content/browser/renderer_host/resource_request_details.h" + +#include "content/browser/renderer_host/resource_dispatcher_host.h" +#include "content/browser/renderer_host/resource_dispatcher_host_request_info.h" +#include "chrome/browser/worker_host/worker_service.h" + +ResourceRequestDetails::ResourceRequestDetails(const net::URLRequest* request, + int cert_id) + : url_(request->url()), + original_url_(request->original_url()), + method_(request->method()), + referrer_(request->referrer()), + has_upload_(request->has_upload()), + load_flags_(request->load_flags()), + status_(request->status()), + ssl_cert_id_(cert_id), + ssl_cert_status_(request->ssl_info().cert_status) { + const ResourceDispatcherHostRequestInfo* info = + ResourceDispatcherHost::InfoForRequest(request); + DCHECK(info); + resource_type_ = info->resource_type(); + + // If request is from the worker process on behalf of a renderer, use + // the renderer process id, since it consumes the notification response + // such as ssl state etc. + // TODO(atwilson): need to notify all associated renderers in the case + // of ssl state change (http://crbug.com/25357). For now, just notify + // the first one (works for dedicated workers and shared workers with + // a single process). + int temp; + if (!WorkerService::GetInstance()->GetRendererForWorker( + info->child_id(), &origin_child_id_, &temp)) { + origin_child_id_ = info->child_id(); + } +} + +ResourceRequestDetails::~ResourceRequestDetails() {} + +ResourceRedirectDetails::ResourceRedirectDetails(const net::URLRequest* request, + int cert_id, + const GURL& new_url) + : ResourceRequestDetails(request, cert_id), + new_url_(new_url) { +} + +ResourceRedirectDetails::~ResourceRedirectDetails() {} diff --git a/content/browser/renderer_host/resource_request_details.h b/content/browser/renderer_host/resource_request_details.h new file mode 100644 index 0000000..06c915f --- /dev/null +++ b/content/browser/renderer_host/resource_request_details.h @@ -0,0 +1,71 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// The ResourceRequestDetails object contains additional details about a +// resource request. It copies many of the publicly accessible member variables +// of net::URLRequest, but exists on the UI thread. + +#ifndef CONTENT_BROWSER_RENDERER_HOST_RESOURCE_REQUEST_DETAILS_H_ +#define CONTENT_BROWSER_RENDERER_HOST_RESOURCE_REQUEST_DETAILS_H_ +#pragma once + +#include <string> + +#include "googleurl/src/gurl.h" +#include "net/url_request/url_request_status.h" +#include "webkit/glue/resource_type.h" + +namespace net { +class URLRequest; +} // namespace net + +// Details about a resource request notification. +class ResourceRequestDetails { + public: + ResourceRequestDetails(const net::URLRequest* request, int cert_id); + + virtual ~ResourceRequestDetails(); + + const GURL& url() const { return url_; } + const GURL& original_url() const { return original_url_; } + const std::string& method() const { return method_; } + const std::string& referrer() const { return referrer_; } + bool has_upload() const { return has_upload_; } + int load_flags() const { return load_flags_; } + int origin_child_id() const { return origin_child_id_; } + const net::URLRequestStatus& status() const { return status_; } + int ssl_cert_id() const { return ssl_cert_id_; } + int ssl_cert_status() const { return ssl_cert_status_; } + ResourceType::Type resource_type() const { return resource_type_; } + + private: + GURL url_; + GURL original_url_; + std::string method_; + std::string referrer_; + bool has_upload_; + int load_flags_; + int origin_child_id_; + net::URLRequestStatus status_; + int ssl_cert_id_; + int ssl_cert_status_; + ResourceType::Type resource_type_; +}; + +// Details about a redirection of a resource request. +class ResourceRedirectDetails : public ResourceRequestDetails { + public: + ResourceRedirectDetails(const net::URLRequest* request, + int cert_id, + const GURL& new_url); + virtual ~ResourceRedirectDetails(); + + // The URL to which we are being redirected. + const GURL& new_url() const { return new_url_; } + + private: + GURL new_url_; +}; + +#endif // CONTENT_BROWSER_RENDERER_HOST_RESOURCE_REQUEST_DETAILS_H_ diff --git a/content/browser/renderer_host/socket_stream_dispatcher_host.cc b/content/browser/renderer_host/socket_stream_dispatcher_host.cc new file mode 100644 index 0000000..4b88ff0 --- /dev/null +++ b/content/browser/renderer_host/socket_stream_dispatcher_host.cc @@ -0,0 +1,164 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "content/browser/renderer_host/socket_stream_dispatcher_host.h" + +#include "base/logging.h" +#include "chrome/browser/profiles/profile.h" +#include "chrome/common/render_messages.h" +#include "chrome/common/render_messages_params.h" +#include "chrome/common/net/socket_stream.h" +#include "chrome/common/net/url_request_context_getter.h" +#include "content/browser/renderer_host/socket_stream_host.h" +#include "net/websockets/websocket_job.h" +#include "net/websockets/websocket_throttle.h" + +SocketStreamDispatcherHost::SocketStreamDispatcherHost() { + net::WebSocketJob::EnsureInit(); +} + +SocketStreamDispatcherHost::~SocketStreamDispatcherHost() { + // TODO(ukai): Implement IDMap::RemoveAll(). + for (IDMap<SocketStreamHost>::const_iterator iter(&hosts_); + !iter.IsAtEnd(); + iter.Advance()) { + int socket_id = iter.GetCurrentKey(); + const SocketStreamHost* socket_stream_host = iter.GetCurrentValue(); + delete socket_stream_host; + hosts_.Remove(socket_id); + } +} + +bool SocketStreamDispatcherHost::OnMessageReceived(const IPC::Message& message, + bool* message_was_ok) { + bool handled = true; + IPC_BEGIN_MESSAGE_MAP_EX(SocketStreamDispatcherHost, message, *message_was_ok) + IPC_MESSAGE_HANDLER(ViewHostMsg_SocketStream_Connect, OnConnect) + IPC_MESSAGE_HANDLER(ViewHostMsg_SocketStream_SendData, OnSendData) + IPC_MESSAGE_HANDLER(ViewHostMsg_SocketStream_Close, OnCloseReq) + IPC_MESSAGE_UNHANDLED(handled = false) + IPC_END_MESSAGE_MAP_EX() + return handled; +} + +// SocketStream::Delegate methods implementations. +void SocketStreamDispatcherHost::OnConnected(net::SocketStream* socket, + int max_pending_send_allowed) { + int socket_id = SocketStreamHost::SocketIdFromSocketStream(socket); + DVLOG(1) << "SocketStreamDispatcherHost::OnConnected socket_id=" << socket_id + << " max_pending_send_allowed=" << max_pending_send_allowed; + if (socket_id == chrome_common_net::kNoSocketId) { + LOG(ERROR) << "NoSocketId in OnConnected"; + return; + } + if (!Send(new ViewMsg_SocketStream_Connected( + socket_id, max_pending_send_allowed))) { + LOG(ERROR) << "ViewMsg_SocketStream_Connected failed."; + DeleteSocketStreamHost(socket_id); + } +} + +void SocketStreamDispatcherHost::OnSentData(net::SocketStream* socket, + int amount_sent) { + int socket_id = SocketStreamHost::SocketIdFromSocketStream(socket); + DVLOG(1) << "SocketStreamDispatcherHost::OnSentData socket_id=" << socket_id + << " amount_sent=" << amount_sent; + if (socket_id == chrome_common_net::kNoSocketId) { + LOG(ERROR) << "NoSocketId in OnReceivedData"; + return; + } + if (!Send(new ViewMsg_SocketStream_SentData(socket_id, amount_sent))) { + LOG(ERROR) << "ViewMsg_SocketStream_SentData failed."; + DeleteSocketStreamHost(socket_id); + } +} + +void SocketStreamDispatcherHost::OnReceivedData( + net::SocketStream* socket, const char* data, int len) { + int socket_id = SocketStreamHost::SocketIdFromSocketStream(socket); + DVLOG(1) << "SocketStreamDispatcherHost::OnReceiveData socket_id=" + << socket_id; + if (socket_id == chrome_common_net::kNoSocketId) { + LOG(ERROR) << "NoSocketId in OnReceivedData"; + return; + } + if (!Send(new ViewMsg_SocketStream_ReceivedData( + socket_id, std::vector<char>(data, data + len)))) { + LOG(ERROR) << "ViewMsg_SocketStream_ReceivedData failed."; + DeleteSocketStreamHost(socket_id); + } +} + +void SocketStreamDispatcherHost::OnClose(net::SocketStream* socket) { + int socket_id = SocketStreamHost::SocketIdFromSocketStream(socket); + DVLOG(1) << "SocketStreamDispatcherHost::OnClosed socket_id=" << socket_id; + if (socket_id == chrome_common_net::kNoSocketId) { + LOG(ERROR) << "NoSocketId in OnClose"; + return; + } + DeleteSocketStreamHost(socket_id); +} + +// Message handlers called by OnMessageReceived. +void SocketStreamDispatcherHost::OnConnect(const GURL& url, int socket_id) { + DVLOG(1) << "SocketStreamDispatcherHost::OnConnect url=" << url + << " socket_id=" << socket_id; + DCHECK_NE(chrome_common_net::kNoSocketId, socket_id); + if (hosts_.Lookup(socket_id)) { + LOG(ERROR) << "socket_id=" << socket_id << " already registered."; + return; + } + SocketStreamHost* socket_stream_host = new SocketStreamHost(this, socket_id); + hosts_.AddWithID(socket_stream_host, socket_id); + socket_stream_host->Connect(url, GetURLRequestContext()); + DVLOG(1) << "SocketStreamDispatcherHost::OnConnect -> " << socket_id; +} + +void SocketStreamDispatcherHost::OnSendData( + int socket_id, const std::vector<char>& data) { + DVLOG(1) << "SocketStreamDispatcherHost::OnSendData socket_id=" << socket_id; + SocketStreamHost* socket_stream_host = hosts_.Lookup(socket_id); + if (!socket_stream_host) { + LOG(ERROR) << "socket_id=" << socket_id << " already closed."; + return; + } + if (!socket_stream_host->SendData(data)) { + // Cannot accept more data to send. + socket_stream_host->Close(); + } +} + +void SocketStreamDispatcherHost::OnCloseReq(int socket_id) { + DVLOG(1) << "SocketStreamDispatcherHost::OnCloseReq socket_id=" << socket_id; + SocketStreamHost* socket_stream_host = hosts_.Lookup(socket_id); + if (!socket_stream_host) + return; + socket_stream_host->Close(); +} + +void SocketStreamDispatcherHost::DeleteSocketStreamHost(int socket_id) { + SocketStreamHost* socket_stream_host = hosts_.Lookup(socket_id); + DCHECK(socket_stream_host); + delete socket_stream_host; + hosts_.Remove(socket_id); + if (!Send(new ViewMsg_SocketStream_Closed(socket_id))) { + LOG(ERROR) << "ViewMsg_SocketStream_Closed failed."; + } +} + +net::URLRequestContext* SocketStreamDispatcherHost::GetURLRequestContext() { + net::URLRequestContext* rv = NULL; + if (url_request_context_override_.get()) { + ViewHostMsg_Resource_Request request; + rv = url_request_context_override_->GetRequestContext(request); + } + if (!rv) { + URLRequestContextGetter* context_getter = + Profile::GetDefaultRequestContext(); + if (context_getter) + rv = context_getter->GetURLRequestContext(); + } + + return rv; +} diff --git a/content/browser/renderer_host/socket_stream_dispatcher_host.h b/content/browser/renderer_host/socket_stream_dispatcher_host.h new file mode 100644 index 0000000..0089e0c --- /dev/null +++ b/content/browser/renderer_host/socket_stream_dispatcher_host.h @@ -0,0 +1,65 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CONTENT_BROWSER_RENDERER_HOST_SOCKET_STREAM_DISPATCHER_HOST_H_ +#define CONTENT_BROWSER_RENDERER_HOST_SOCKET_STREAM_DISPATCHER_HOST_H_ +#pragma once + +#include <vector> + +#include "base/id_map.h" +#include "chrome/browser/browser_message_filter.h" +#include "content/browser/renderer_host/resource_message_filter.h" +#include "net/socket_stream/socket_stream.h" + +class GURL; +class SocketStreamHost; + +// Dispatches ViewHostMsg_SocketStream_* messages sent from renderer. +// It also acts as SocketStream::Delegate so that it sends +// ViewMsg_SocketStream_* messages back to renderer. +class SocketStreamDispatcherHost : public BrowserMessageFilter, + public net::SocketStream::Delegate { + public: + SocketStreamDispatcherHost(); + virtual ~SocketStreamDispatcherHost(); + + // BrowserMessageFilter methods. + virtual bool OnMessageReceived(const IPC::Message& message, + bool* message_was_ok); + + // The object died, so cancel and detach all requests associated with it. + void CancelRequestsForProcess(int host_id); + + // SocketStream::Delegate methods. + virtual void OnConnected(net::SocketStream* socket, + int max_pending_send_allowed); + virtual void OnSentData(net::SocketStream* socket, int amount_sent); + virtual void OnReceivedData(net::SocketStream* socket, + const char* data, int len); + virtual void OnClose(net::SocketStream* socket); + + void set_url_request_context_override( + ResourceMessageFilter::URLRequestContextOverride* u) { + url_request_context_override_ = u; + } + + private: + // Message handlers called by OnMessageReceived. + void OnConnect(const GURL& url, int socket_id); + void OnSendData(int socket_id, const std::vector<char>& data); + void OnCloseReq(int socket_id); + + void DeleteSocketStreamHost(int socket_id); + + net::URLRequestContext* GetURLRequestContext(); + + IDMap<SocketStreamHost> hosts_; + scoped_refptr<ResourceMessageFilter::URLRequestContextOverride> + url_request_context_override_; + + DISALLOW_COPY_AND_ASSIGN(SocketStreamDispatcherHost); +}; + +#endif // CONTENT_BROWSER_RENDERER_HOST_SOCKET_STREAM_DISPATCHER_HOST_H_ diff --git a/content/browser/renderer_host/socket_stream_host.cc b/content/browser/renderer_host/socket_stream_host.cc new file mode 100644 index 0000000..e98f7db --- /dev/null +++ b/content/browser/renderer_host/socket_stream_host.cc @@ -0,0 +1,66 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "content/browser/renderer_host/socket_stream_host.h" + +#include "base/logging.h" +#include "chrome/common/net/socket_stream.h" +#include "net/socket_stream/socket_stream_job.h" + +static const char* kSocketIdKey = "socketId"; + +class SocketStreamId : public net::SocketStream::UserData { + public: + explicit SocketStreamId(int socket_id) : socket_id_(socket_id) {} + virtual ~SocketStreamId() {} + int socket_id() const { return socket_id_; } + + private: + int socket_id_; +}; + +SocketStreamHost::SocketStreamHost( + net::SocketStream::Delegate* delegate, + int socket_id) + : delegate_(delegate), + socket_id_(socket_id) { + DCHECK_NE(socket_id_, chrome_common_net::kNoSocketId); + VLOG(1) << "SocketStreamHost: socket_id=" << socket_id_; +} + +/* static */ +int SocketStreamHost::SocketIdFromSocketStream(net::SocketStream* socket) { + net::SocketStream::UserData* d = socket->GetUserData(kSocketIdKey); + if (d) { + SocketStreamId* socket_stream_id = static_cast<SocketStreamId*>(d); + return socket_stream_id->socket_id(); + } + return chrome_common_net::kNoSocketId; +} + +SocketStreamHost::~SocketStreamHost() { + VLOG(1) << "SocketStreamHost destructed socket_id=" << socket_id_; + socket_->DetachDelegate(); +} + +void SocketStreamHost::Connect(const GURL& url, + net::URLRequestContext* request_context) { + VLOG(1) << "SocketStreamHost::Connect url=" << url; + socket_ = net::SocketStreamJob::CreateSocketStreamJob(url, delegate_); + socket_->set_context(request_context); + socket_->SetUserData(kSocketIdKey, new SocketStreamId(socket_id_)); + socket_->Connect(); +} + +bool SocketStreamHost::SendData(const std::vector<char>& data) { + VLOG(1) << "SocketStreamHost::SendData"; + return socket_ && socket_->SendData(&data[0], data.size()); +} + +void SocketStreamHost::Close() { + VLOG(1) << "SocketStreamHost::Close"; + if (!socket_) + return; + socket_->Close(); +} diff --git a/content/browser/renderer_host/socket_stream_host.h b/content/browser/renderer_host/socket_stream_host.h new file mode 100644 index 0000000..249b35c --- /dev/null +++ b/content/browser/renderer_host/socket_stream_host.h @@ -0,0 +1,61 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CONTENT_BROWSER_RENDERER_HOST_SOCKET_STREAM_HOST_H_ +#define CONTENT_BROWSER_RENDERER_HOST_SOCKET_STREAM_HOST_H_ +#pragma once + +#include <vector> + +#include "base/ref_counted.h" +#include "net/socket_stream/socket_stream.h" + +class GURL; + +namespace net { +class SocketStreamJob; +class URLRequestContext; +} // namespace net + +// Host of SocketStreamHandle. +// Each SocketStreamHandle will have an unique socket_id assigned by +// SocketStreamHost constructor. If socket id is chrome_common_net::kNoSocketId, +// there is no SocketStreamHost. +// Each SocketStreamHost has SocketStream to manage bi-directional +// communication over socket stream. +// The lifetime of an instance of this class is completely controlled by the +// SocketStreamDispatcherHost. +class SocketStreamHost { + public: + SocketStreamHost(net::SocketStream::Delegate* delegate, int socket_id); + ~SocketStreamHost(); + + // Gets socket_id associated with |socket|. + static int SocketIdFromSocketStream(net::SocketStream* socket); + + int socket_id() const { return socket_id_; } + + // Starts to open connection to |url|. + void Connect(const GURL& url, net::URLRequestContext* request_context); + + // Sends |data| over the socket stream. + // socket stream must be open to send data. + // Returns true if the data is put in transmit buffer in socket stream. + // Returns false otherwise (transmit buffer exceeds limit, or socket + // stream is closed). + bool SendData(const std::vector<char>& data); + + // Closes the socket stream. + void Close(); + + private: + net::SocketStream::Delegate* delegate_; + int socket_id_; + + scoped_refptr<net::SocketStreamJob> socket_; + + DISALLOW_COPY_AND_ASSIGN(SocketStreamHost); +}; + +#endif // CONTENT_BROWSER_RENDERER_HOST_SOCKET_STREAM_HOST_H_ diff --git a/content/browser/renderer_host/sync_resource_handler.cc b/content/browser/renderer_host/sync_resource_handler.cc new file mode 100644 index 0000000..1b9a8f6 --- /dev/null +++ b/content/browser/renderer_host/sync_resource_handler.cc @@ -0,0 +1,117 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "content/browser/renderer_host/sync_resource_handler.h" + +#include "base/logging.h" +#include "chrome/browser/debugger/devtools_netlog_observer.h" +#include "chrome/browser/net/load_timing_observer.h" +#include "chrome/common/render_messages.h" +#include "content/browser/renderer_host/global_request_id.h" +#include "content/browser/renderer_host/resource_dispatcher_host.h" +#include "content/browser/renderer_host/resource_message_filter.h" +#include "net/base/io_buffer.h" +#include "net/http/http_response_headers.h" + +SyncResourceHandler::SyncResourceHandler( + ResourceMessageFilter* filter, + const GURL& url, + IPC::Message* result_message, + ResourceDispatcherHost* resource_dispatcher_host) + : read_buffer_(new net::IOBuffer(kReadBufSize)), + filter_(filter), + result_message_(result_message), + rdh_(resource_dispatcher_host) { + result_.final_url = url; +} + +SyncResourceHandler::~SyncResourceHandler() { +} + +bool SyncResourceHandler::OnUploadProgress(int request_id, + uint64 position, + uint64 size) { + return true; +} + +bool SyncResourceHandler::OnRequestRedirected(int request_id, + const GURL& new_url, + ResourceResponse* response, + bool* defer) { + net::URLRequest* request = rdh_->GetURLRequest( + GlobalRequestID(filter_->child_id(), request_id)); + LoadTimingObserver::PopulateTimingInfo(request, response); + DevToolsNetLogObserver::PopulateResponseInfo(request, response); + // TODO(darin): It would be much better if this could live in WebCore, but + // doing so requires API changes at all levels. Similar code exists in + // WebCore/platform/network/cf/ResourceHandleCFNet.cpp :-( + if (new_url.GetOrigin() != result_.final_url.GetOrigin()) { + LOG(ERROR) << "Cross origin redirect denied"; + return false; + } + result_.final_url = new_url; + return true; +} + +bool SyncResourceHandler::OnResponseStarted(int request_id, + ResourceResponse* response) { + net::URLRequest* request = rdh_->GetURLRequest( + GlobalRequestID(filter_->child_id(), request_id)); + LoadTimingObserver::PopulateTimingInfo(request, response); + DevToolsNetLogObserver::PopulateResponseInfo(request, response); + + // We don't care about copying the status here. + result_.headers = response->response_head.headers; + result_.mime_type = response->response_head.mime_type; + result_.charset = response->response_head.charset; + result_.download_file_path = response->response_head.download_file_path; + result_.request_time = response->response_head.request_time; + result_.response_time = response->response_head.response_time; + result_.connection_id = response->response_head.connection_id; + result_.connection_reused = response->response_head.connection_reused; + result_.load_timing = response->response_head.load_timing; + result_.devtools_info = response->response_head.devtools_info; + return true; +} + +bool SyncResourceHandler::OnWillStart(int request_id, + const GURL& url, + bool* defer) { + return true; +} + +bool SyncResourceHandler::OnWillRead(int request_id, net::IOBuffer** buf, + int* buf_size, int min_size) { + DCHECK(min_size == -1); + *buf = read_buffer_.get(); + *buf_size = kReadBufSize; + return true; +} + +bool SyncResourceHandler::OnReadCompleted(int request_id, int* bytes_read) { + if (!*bytes_read) + return true; + result_.data.append(read_buffer_->data(), *bytes_read); + return true; +} + +bool SyncResourceHandler::OnResponseCompleted( + int request_id, + const net::URLRequestStatus& status, + const std::string& security_info) { + result_.status = status; + + ViewHostMsg_SyncLoad::WriteReplyParams(result_message_, result_); + filter_->Send(result_message_); + result_message_ = NULL; + return true; +} + +void SyncResourceHandler::OnRequestClosed() { + if (!result_message_) + return; + + result_message_->set_reply_error(); + filter_->Send(result_message_); +} diff --git a/content/browser/renderer_host/sync_resource_handler.h b/content/browser/renderer_host/sync_resource_handler.h new file mode 100644 index 0000000..95e8bfe --- /dev/null +++ b/content/browser/renderer_host/sync_resource_handler.h @@ -0,0 +1,60 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CONTENT_BROWSER_RENDERER_HOST_SYNC_RESOURCE_HANDLER_H_ +#define CONTENT_BROWSER_RENDERER_HOST_SYNC_RESOURCE_HANDLER_H_ +#pragma once + +#include <string> + +#include "content/browser/renderer_host/resource_handler.h" +#include "chrome/common/resource_response.h" + +class ResourceDispatcherHost; +class ResourceMessageFilter; + +namespace IPC { +class Message; +} + +namespace net { +class IOBuffer; +} + +// Used to complete a synchronous resource request in response to resource load +// events from the resource dispatcher host. +class SyncResourceHandler : public ResourceHandler { + public: + SyncResourceHandler(ResourceMessageFilter* filter, + const GURL& url, + IPC::Message* result_message, + ResourceDispatcherHost* resource_dispatcher_host); + + virtual bool OnUploadProgress(int request_id, uint64 position, uint64 size); + virtual bool OnRequestRedirected(int request_id, const GURL& new_url, + ResourceResponse* response, bool* defer); + virtual bool OnResponseStarted(int request_id, ResourceResponse* response); + virtual bool OnWillStart(int request_id, const GURL& url, bool* defer); + virtual bool OnWillRead(int request_id, net::IOBuffer** buf, int* buf_size, + int min_size); + virtual bool OnReadCompleted(int request_id, int* bytes_read); + virtual bool OnResponseCompleted(int request_id, + const net::URLRequestStatus& status, + const std::string& security_info); + virtual void OnRequestClosed(); + + private: + enum { kReadBufSize = 3840 }; + + ~SyncResourceHandler(); + + scoped_refptr<net::IOBuffer> read_buffer_; + + SyncLoadResult result_; + ResourceMessageFilter* filter_; + IPC::Message* result_message_; + ResourceDispatcherHost* rdh_; +}; + +#endif // CONTENT_BROWSER_RENDERER_HOST_SYNC_RESOURCE_HANDLER_H_ diff --git a/content/browser/renderer_host/x509_user_cert_resource_handler.cc b/content/browser/renderer_host/x509_user_cert_resource_handler.cc new file mode 100644 index 0000000..7393e73 --- /dev/null +++ b/content/browser/renderer_host/x509_user_cert_resource_handler.cc @@ -0,0 +1,130 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "content/browser/renderer_host/x509_user_cert_resource_handler.h" + +#include "base/string_util.h" +#include "chrome/browser/download/download_types.h" +#include "chrome/browser/ssl/ssl_add_cert_handler.h" +#include "chrome/common/resource_response.h" +#include "chrome/common/url_constants.h" +#include "content/browser/renderer_host/resource_dispatcher_host.h" +#include "content/browser/renderer_host/resource_dispatcher_host_request_info.h" +#include "net/base/io_buffer.h" +#include "net/base/mime_sniffer.h" +#include "net/base/mime_util.h" +#include "net/base/x509_certificate.h" +#include "net/http/http_response_headers.h" +#include "net/url_request/url_request.h" +#include "net/url_request/url_request_status.h" + +X509UserCertResourceHandler::X509UserCertResourceHandler( + ResourceDispatcherHost* host, net::URLRequest* request, + int render_process_host_id, int render_view_id) + : host_(host), + request_(request), + content_length_(0), + buffer_(new DownloadBuffer), + read_buffer_(NULL), + resource_buffer_(NULL), + render_process_host_id_(render_process_host_id), + render_view_id_(render_view_id) { +} + +bool X509UserCertResourceHandler::OnUploadProgress(int request_id, + uint64 position, + uint64 size) { + return true; +} + +bool X509UserCertResourceHandler::OnRequestRedirected(int request_id, + const GURL& url, + ResourceResponse* resp, + bool* defer) { + url_ = url; + return true; +} + +bool X509UserCertResourceHandler::OnResponseStarted(int request_id, + ResourceResponse* resp) { + return (resp->response_head.mime_type == "application/x-x509-user-cert"); +} + +bool X509UserCertResourceHandler::OnWillStart(int request_id, + const GURL& url, + bool* defer) { + return true; +} + +bool X509UserCertResourceHandler::OnWillRead(int request_id, + net::IOBuffer** buf, + int* buf_size, + int min_size) { + // TODO(gauravsh): Should we use 'min_size' here? + DCHECK(buf && buf_size); + if (!read_buffer_) { + read_buffer_ = new net::IOBuffer(kReadBufSize); + } + *buf = read_buffer_.get(); + *buf_size = kReadBufSize; + + return true; +} + +bool X509UserCertResourceHandler::OnReadCompleted(int request_id, + int* bytes_read) { + if (!*bytes_read) + return true; + + // We have more data to read. + DCHECK(read_buffer_); + content_length_ += *bytes_read; + + // Release the ownership of the buffer, and store a reference + // to it. A new one will be allocated in OnWillRead(). + net::IOBuffer* buffer = NULL; + read_buffer_.swap(&buffer); + // TODO(gauravsh): Should this be handled by a separate thread? + buffer_->contents.push_back(std::make_pair(buffer, *bytes_read)); + + return true; +} + +bool X509UserCertResourceHandler::OnResponseCompleted( + int request_id, + const net::URLRequestStatus& urs, + const std::string& sec_info) { + if (urs.status() != net::URLRequestStatus::SUCCESS) + return false; + + // TODO(gauravsh): Verify that 'request_id' was actually a keygen form post + // and only then import the certificate. + AssembleResource(); + scoped_refptr<net::X509Certificate> cert( + net::X509Certificate::CreateFromBytes(resource_buffer_->data(), + content_length_)); + // The handler will run the UI and delete itself when it's finished. + new SSLAddCertHandler(request_, cert, render_process_host_id_, + render_view_id_); + return true; +} + +void X509UserCertResourceHandler::OnRequestClosed() { +} + +X509UserCertResourceHandler::~X509UserCertResourceHandler() { +} + +void X509UserCertResourceHandler::AssembleResource() { + size_t bytes_copied = 0; + resource_buffer_ = new net::IOBuffer(content_length_); + + for (size_t i = 0; i < buffer_->contents.size(); ++i) { + net::IOBuffer* data = buffer_->contents[i].first; + const int data_len = buffer_->contents[i].second; + DCHECK(bytes_copied + data_len <= content_length_); + memcpy(resource_buffer_->data() + bytes_copied, data->data(), data_len); + bytes_copied += data_len; + } +} diff --git a/content/browser/renderer_host/x509_user_cert_resource_handler.h b/content/browser/renderer_host/x509_user_cert_resource_handler.h new file mode 100644 index 0000000..0d2df61 --- /dev/null +++ b/content/browser/renderer_host/x509_user_cert_resource_handler.h @@ -0,0 +1,80 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CONTENT_BROWSER_RENDERER_HOST_X509_USER_CERT_RESOURCE_HANDLER_H_ +#define CONTENT_BROWSER_RENDERER_HOST_X509_USER_CERT_RESOURCE_HANDLER_H_ +#pragma once + +#include <string> + +#include "base/scoped_ptr.h" +#include "content/browser/renderer_host/resource_handler.h" +#include "googleurl/src/gurl.h" + +namespace net { +class URLRequest; +class URLRequestStatus; +} // namespace net + +class ResourceDispatcherHost; +struct DownloadBuffer; + +// This class handles the "application/x-x509-user-cert" mime-type +// which is a certificate generated by a CA after a previous +// <keygen> form post. + +class X509UserCertResourceHandler : public ResourceHandler { + public: + X509UserCertResourceHandler(ResourceDispatcherHost* host, + net::URLRequest* request, + int render_process_host_id, int render_view_id); + + virtual bool OnUploadProgress(int request_id, uint64 position, uint64 size); + + // Not needed, as this event handler ought to be the final resource. + virtual bool OnRequestRedirected(int request_id, const GURL& url, + ResourceResponse* resp, bool* defer); + + // Check if this indeed an X509 cert. + virtual bool OnResponseStarted(int request_id, ResourceResponse* resp); + + // Pass-through implementation. + virtual bool OnWillStart(int request_id, const GURL& url, bool* defer); + + // Create a new buffer to store received data. + virtual bool OnWillRead(int request_id, net::IOBuffer** buf, int* buf_size, + int min_size); + + // A read was completed, maybe allocate a new buffer for further data. + virtual bool OnReadCompleted(int request_id, int* bytes_read); + + // Done downloading the certificate. + virtual bool OnResponseCompleted(int request_id, + const net::URLRequestStatus& urs, + const std::string& sec_info); + + virtual void OnRequestClosed(); + + private: + virtual ~X509UserCertResourceHandler(); + + void AssembleResource(); + + GURL url_; + ResourceDispatcherHost* host_; + net::URLRequest* request_; + size_t content_length_; + scoped_ptr<DownloadBuffer> buffer_; + scoped_refptr<net::IOBuffer> read_buffer_; + scoped_refptr<net::IOBuffer> resource_buffer_; // Downloaded certificate. + static const int kReadBufSize = 32768; + // The id of the |RenderProcessHost| which started the download. + int render_process_host_id_; + // The id of the |RenderView| which started the download. + int render_view_id_; + + DISALLOW_COPY_AND_ASSIGN(X509UserCertResourceHandler); +}; + +#endif // CONTENT_BROWSER_RENDERER_HOST_X509_USER_CERT_RESOURCE_HANDLER_H_ diff --git a/content/browser/site_instance.cc b/content/browser/site_instance.cc new file mode 100644 index 0000000..642a6b7 --- /dev/null +++ b/content/browser/site_instance.cc @@ -0,0 +1,236 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "content/browser/site_instance.h" + +#include "chrome/browser/browsing_instance.h" +#include "chrome/browser/extensions/extension_service.h" +#include "chrome/browser/webui/web_ui_factory.h" +#include "chrome/browser/renderer_host/browser_render_process_host.h" +#include "chrome/common/notification_service.h" +#include "chrome/common/url_constants.h" +#include "net/base/registry_controlled_domain.h" + +// We treat javascript:, about:crash, about:hang, and about:shorthang as the +// same site as any URL since they are actually modifiers on existing pages. +static bool IsURLSameAsAnySiteInstance(const GURL& url) { + if (!url.is_valid()) + return false; + return url.SchemeIs(chrome::kJavaScriptScheme) || + url.spec() == chrome::kAboutCrashURL || + url.spec() == chrome::kAboutKillURL || + url.spec() == chrome::kAboutHangURL || + url.spec() == chrome::kAboutShorthangURL; +} + +SiteInstance::SiteInstance(BrowsingInstance* browsing_instance) + : browsing_instance_(browsing_instance), + render_process_host_factory_(NULL), + process_(NULL), + max_page_id_(-1), + has_site_(false) { + DCHECK(browsing_instance); + + registrar_.Add(this, NotificationType::RENDERER_PROCESS_TERMINATED, + NotificationService::AllSources()); +} + +SiteInstance::~SiteInstance() { + // Now that no one is referencing us, we can safely remove ourselves from + // the BrowsingInstance. Any future visits to a page from this site + // (within the same BrowsingInstance) can safely create a new SiteInstance. + if (has_site_) + browsing_instance_->UnregisterSiteInstance(this); +} + +bool SiteInstance::HasProcess() const { + return (process_ != NULL); +} + +RenderProcessHost* SiteInstance::GetProcess() { + // TODO(erikkay) It would be nice to ensure that the renderer type had been + // properly set before we get here. The default tab creation case winds up + // with no site set at this point, so it will default to TYPE_NORMAL. This + // may not be correct, so we'll wind up potentially creating a process that + // we then throw away, or worse sharing a process with the wrong process type. + // See crbug.com/43448. + + // Create a new process if ours went away or was reused. + if (!process_) { + // See if we should reuse an old process + if (RenderProcessHost::ShouldTryToUseExistingProcessHost()) + process_ = RenderProcessHost::GetExistingProcessHost( + browsing_instance_->profile(), GetRendererType()); + + // Otherwise (or if that fails), create a new one. + if (!process_) { + if (render_process_host_factory_) { + process_ = render_process_host_factory_->CreateRenderProcessHost( + browsing_instance_->profile()); + } else { + process_ = new BrowserRenderProcessHost(browsing_instance_->profile()); + } + } + + // Make sure the process starts at the right max_page_id + process_->UpdateMaxPageID(max_page_id_); + } + DCHECK(process_); + + return process_; +} + +void SiteInstance::SetSite(const GURL& url) { + // A SiteInstance's site should not change. + // TODO(creis): When following links or script navigations, we can currently + // render pages from other sites in this SiteInstance. This will eventually + // be fixed, but until then, we should still not set the site of a + // SiteInstance more than once. + DCHECK(!has_site_); + + // Remember that this SiteInstance has been used to load a URL, even if the + // URL is invalid. + has_site_ = true; + site_ = GetSiteForURL(browsing_instance_->profile(), url); + + // Now that we have a site, register it with the BrowsingInstance. This + // ensures that we won't create another SiteInstance for this site within + // the same BrowsingInstance, because all same-site pages within a + // BrowsingInstance can script each other. + browsing_instance_->RegisterSiteInstance(this); +} + +bool SiteInstance::HasRelatedSiteInstance(const GURL& url) { + return browsing_instance_->HasSiteInstance(url); +} + +SiteInstance* SiteInstance::GetRelatedSiteInstance(const GURL& url) { + return browsing_instance_->GetSiteInstanceForURL(url); +} + +/*static*/ +SiteInstance* SiteInstance::CreateSiteInstance(Profile* profile) { + return new SiteInstance(new BrowsingInstance(profile)); +} + +/*static*/ +SiteInstance* SiteInstance::CreateSiteInstanceForURL(Profile* profile, + const GURL& url) { + // This BrowsingInstance may be deleted if it returns an existing + // SiteInstance. + scoped_refptr<BrowsingInstance> instance(new BrowsingInstance(profile)); + return instance->GetSiteInstanceForURL(url); +} + +/*static*/ +GURL SiteInstance::GetSiteForURL(Profile* profile, const GURL& real_url) { + GURL url = GetEffectiveURL(profile, real_url); + + // URLs with no host should have an empty site. + GURL site; + + // TODO(creis): For many protocols, we should just treat the scheme as the + // site, since there is no host. e.g., file:, about:, chrome: + + // If the url has a host, then determine the site. + if (url.has_host()) { + // Only keep the scheme and registered domain as given by GetOrigin. This + // may also include a port, which we need to drop. + site = url.GetOrigin(); + + // Remove port, if any. + if (site.has_port()) { + GURL::Replacements rep; + rep.ClearPort(); + site = site.ReplaceComponents(rep); + } + + // If this URL has a registered domain, we only want to remember that part. + std::string domain = + net::RegistryControlledDomainService::GetDomainAndRegistry(url); + if (!domain.empty()) { + GURL::Replacements rep; + rep.SetHostStr(domain); + site = site.ReplaceComponents(rep); + } + } + return site; +} + +/*static*/ +bool SiteInstance::IsSameWebSite(Profile* profile, + const GURL& real_url1, const GURL& real_url2) { + GURL url1 = GetEffectiveURL(profile, real_url1); + GURL url2 = GetEffectiveURL(profile, real_url2); + + // We infer web site boundaries based on the registered domain name of the + // top-level page and the scheme. We do not pay attention to the port if + // one is present, because pages served from different ports can still + // access each other if they change their document.domain variable. + + // Some special URLs will match the site instance of any other URL. This is + // done before checking both of them for validity, since we want these URLs + // to have the same site instance as even an invalid one. + if (IsURLSameAsAnySiteInstance(url1) || IsURLSameAsAnySiteInstance(url2)) + return true; + + // If either URL is invalid, they aren't part of the same site. + if (!url1.is_valid() || !url2.is_valid()) + return false; + + // If the schemes differ, they aren't part of the same site. + if (url1.scheme() != url2.scheme()) + return false; + + return net::RegistryControlledDomainService::SameDomainOrHost(url1, url2); +} + +/*static*/ +GURL SiteInstance::GetEffectiveURL(Profile* profile, const GURL& url) { + if (!profile || !profile->GetExtensionService()) + return url; + + const Extension* extension = + profile->GetExtensionService()->GetExtensionByWebExtent(url); + if (extension) { + // If the URL is part of an extension's web extent, convert it to an + // extension URL. + return extension->GetResourceURL(url.path()); + } else { + return url; + } +} + +/*static*/ +RenderProcessHost::Type SiteInstance::RendererTypeForURL(const GURL& url) { + if (!url.is_valid()) + return RenderProcessHost::TYPE_NORMAL; + + if (url.SchemeIs(chrome::kExtensionScheme)) + return RenderProcessHost::TYPE_EXTENSION; + + // TODO(erikkay) creis recommends using UseWebUIForURL instead. + if (WebUIFactory::HasWebUIScheme(url)) + return RenderProcessHost::TYPE_WEBUI; + + return RenderProcessHost::TYPE_NORMAL; +} + +RenderProcessHost::Type SiteInstance::GetRendererType() { + // We may not have a site at this point, which generally means this is a + // normal navigation. + if (!has_site_) + return RenderProcessHost::TYPE_NORMAL; + + return RendererTypeForURL(site_); +} + +void SiteInstance::Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details) { + DCHECK(type == NotificationType::RENDERER_PROCESS_TERMINATED); + RenderProcessHost* rph = Source<RenderProcessHost>(source).ptr(); + if (rph == process_) + process_ = NULL; +} diff --git a/content/browser/site_instance.h b/content/browser/site_instance.h new file mode 100644 index 0000000..058cc54 --- /dev/null +++ b/content/browser/site_instance.h @@ -0,0 +1,197 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CONTENT_BROWSER_RENDERER_HOST_SITE_INSTANCE_H_ +#define CONTENT_BROWSER_RENDERER_HOST_SITE_INSTANCE_H_ +#pragma once + +#include "chrome/common/notification_observer.h" +#include "chrome/common/notification_registrar.h" +#include "content/browser/renderer_host/render_process_host.h" +#include "googleurl/src/gurl.h" + +class BrowsingInstance; + +/////////////////////////////////////////////////////////////////////////////// +// +// SiteInstance class +// +// A SiteInstance is a data structure that is associated with all pages in a +// given instance of a web site. Here, a web site is identified by its +// registered domain name and scheme. An instance includes all pages +// that are connected (i.e., either a user or a script navigated from one +// to the other). We represent instances using the BrowsingInstance class. +// +// In --process-per-tab, one SiteInstance is created for each tab (i.e., in the +// TabContents constructor), unless the tab is created by script (i.e., in +// TabContents::CreateNewView). This corresponds to one process per +// BrowsingInstance. +// +// In process-per-site-instance (the current default process model), +// SiteInstances are created (1) when the user manually creates a new tab +// (which also creates a new BrowsingInstance), and (2) when the user navigates +// across site boundaries (which uses the same BrowsingInstance). If the user +// navigates within a site, or opens links in new tabs within a site, the same +// SiteInstance is used. +// +// In --process-per-site, we consolidate all SiteInstances for a given site, +// throughout the entire profile. This ensures that only one process will be +// dedicated to each site. +// +// Each NavigationEntry for a TabContents points to the SiteInstance that +// rendered it. Each RenderViewHost also points to the SiteInstance that it is +// associated with. A SiteInstance keeps track of the number of these +// references and deletes itself when the count goes to zero. This means that +// a SiteInstance is only live as long as it is accessible, either from new +// tabs with no NavigationEntries or in NavigationEntries in the history. +// +/////////////////////////////////////////////////////////////////////////////// +class SiteInstance : public base::RefCounted<SiteInstance>, + public NotificationObserver { + public: + // Get the BrowsingInstance to which this SiteInstance belongs. + BrowsingInstance* browsing_instance() { return browsing_instance_; } + + // Sets the factory used to create new RenderProcessHosts. This will also be + // passed on to SiteInstances spawned by this one. + // + // The factory must outlive the SiteInstance; ownership is not transferred. It + // may be NULL, in which case the default BrowserRenderProcessHost will be + // created (this is the behavior if you don't call this function). + void set_render_process_host_factory(RenderProcessHostFactory* rph_factory) { + render_process_host_factory_ = rph_factory; + } + + // Update / Get the max page ID for this SiteInstance. + void UpdateMaxPageID(int32 page_id) { + if (page_id > max_page_id_) + max_page_id_ = page_id; + } + int32 max_page_id() const { return max_page_id_; } + + // Whether this SiteInstance has a running process associated with it. + bool HasProcess() const; + + // Returns the current process being used to render pages in this + // SiteInstance. If the process has crashed or otherwise gone away, then + // this method will create a new process and update our host ID accordingly. + RenderProcessHost* GetProcess(); + + // Set / Get the web site that this SiteInstance is rendering pages for. + // This includes the scheme and registered domain, but not the port. If the + // URL does not have a valid registered domain, then the full hostname is + // stored. + void SetSite(const GURL& url); + const GURL& site() const { return site_; } + bool has_site() const { return has_site_; } + + // Returns whether there is currently a related SiteInstance (registered with + // BrowsingInstance) for the site of the given url. If so, we should try to + // avoid dedicating an unused SiteInstance to it (e.g., in a new tab). + bool HasRelatedSiteInstance(const GURL& url); + + // Gets a SiteInstance for the given URL that shares the current + // BrowsingInstance, creating a new SiteInstance if necessary. This ensures + // that a BrowsingInstance only has one SiteInstance per site, so that pages + // in a BrowsingInstance have the ability to script each other. Callers + // should ensure that this SiteInstance becomes ref counted, by storing it in + // a scoped_refptr. (By having this method, we can hide the BrowsingInstance + // class from the rest of the codebase.) + // TODO(creis): This may be an argument to build a pass_refptr<T> class, as + // Darin suggests. + SiteInstance* GetRelatedSiteInstance(const GURL& url); + + // Factory method to create a new SiteInstance. This will create a new + // new BrowsingInstance, so it should only be used when creating a new tab + // from scratch (or similar circumstances). Callers should ensure that + // this SiteInstance becomes ref counted, by storing it in a scoped_refptr. + // + // The render process host factory may be NULL. See SiteInstance constructor. + // + // TODO(creis): This may be an argument to build a pass_refptr<T> class, as + // Darin suggests. + static SiteInstance* CreateSiteInstance(Profile* profile); + + // Factory method to get the appropriate SiteInstance for the given URL, in + // a new BrowsingInstance. Use this instead of CreateSiteInstance when you + // know the URL, since it allows special site grouping rules to be applied + // (for example, to group chrome-ui pages into the same instance). + static SiteInstance* CreateSiteInstanceForURL(Profile* profile, + const GURL& url); + + // Returns the site for the given URL, which includes only the scheme and + // registered domain. Returns an empty GURL if the URL has no host. + static GURL GetSiteForURL(Profile* profile, const GURL& url); + + // Return whether both URLs are part of the same web site, for the purpose of + // assigning them to processes accordingly. The decision is currently based + // on the registered domain of the URLs (google.com, bbc.co.uk), as well as + // the scheme (https, http). This ensures that two pages will be in + // the same process if they can communicate with other via JavaScript. + // (e.g., docs.google.com and mail.google.com have DOM access to each other + // if they both set their document.domain properties to google.com.) + static bool IsSameWebSite(Profile* profile, + const GURL& url1, const GURL& url2); + + // Returns the renderer type for this URL. + static RenderProcessHost::Type RendererTypeForURL(const GURL& url); + + protected: + friend class base::RefCounted<SiteInstance>; + friend class BrowsingInstance; + + // Virtual to allow tests to extend it. + virtual ~SiteInstance(); + + // Create a new SiteInstance. Protected to give access to BrowsingInstance + // and tests; most callers should use CreateSiteInstance or + // GetRelatedSiteInstance instead. + explicit SiteInstance(BrowsingInstance* browsing_instance); + + // Get the effective URL for the given actual URL. If the URL is part of an + // installed app, the effective URL is an extension URL with the ID of that + // extension as the host. This has the effect of grouping apps together in + // a common SiteInstance. + static GURL GetEffectiveURL(Profile* profile, const GURL& url); + + // Returns the type of renderer process this instance belongs in, for grouping + // purposes. + RenderProcessHost::Type GetRendererType(); + + private: + // NotificationObserver implementation. + virtual void Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details); + + NotificationRegistrar registrar_; + + // BrowsingInstance to which this SiteInstance belongs. + scoped_refptr<BrowsingInstance> browsing_instance_; + + // Factory for new RenderProcessHosts, not owned by this class. NULL indiactes + // that the default BrowserRenderProcessHost should be created. + const RenderProcessHostFactory* render_process_host_factory_; + + // Current RenderProcessHost that is rendering pages for this SiteInstance. + // This pointer will only change once the RenderProcessHost is destructed. It + // will still remain the same even if the process crashes, since in that + // scenario the RenderProcessHost remains the same. + RenderProcessHost* process_; + + // The current max_page_id in the SiteInstance's RenderProcessHost. If the + // rendering process dies, its replacement should start issuing page IDs that + // are larger than this value. + int32 max_page_id_; + + // The web site that this SiteInstance is rendering pages for. + GURL site_; + + // Whether SetSite has been called. + bool has_site_; + + DISALLOW_COPY_AND_ASSIGN(SiteInstance); +}; + +#endif // CONTENT_BROWSER_RENDERER_HOST_SITE_INSTANCE_H_ diff --git a/content/content_browser.gypi b/content/content_browser.gypi index 8abb4ab..82085d1 100644 --- a/content/content_browser.gypi +++ b/content/content_browser.gypi @@ -12,6 +12,7 @@ #'content_common', '../app/app.gyp:app_resources', '../skia/skia.gyp:skia', + # TabContents uses zoom constants and functions from WebKit::WebView. '../third_party/WebKit/Source/WebKit/chromium/WebKit.gyp:webkit', '../ui/ui.gyp:ui_base', ], @@ -19,6 +20,92 @@ '..', ], 'sources': [ + 'browser/renderer_host/accelerated_surface_container_mac.cc', + 'browser/renderer_host/accelerated_surface_container_mac.h', + 'browser/renderer_host/accelerated_surface_container_manager_mac.cc', + 'browser/renderer_host/accelerated_surface_container_manager_mac.h', + 'browser/renderer_host/async_resource_handler.cc', + 'browser/renderer_host/async_resource_handler.h', + 'browser/renderer_host/audio_renderer_host.cc', + 'browser/renderer_host/audio_renderer_host.h', + 'browser/renderer_host/audio_sync_reader.cc', + 'browser/renderer_host/audio_sync_reader.h', + 'browser/renderer_host/backing_store.cc', + 'browser/renderer_host/backing_store.h', + 'browser/renderer_host/backing_store_mac.h', + 'browser/renderer_host/backing_store_mac.mm', + 'browser/renderer_host/backing_store_manager.cc', + 'browser/renderer_host/backing_store_manager.h', + 'browser/renderer_host/backing_store_skia.cc', + 'browser/renderer_host/backing_store_skia.h', + 'browser/renderer_host/backing_store_win.cc', + 'browser/renderer_host/backing_store_win.h', + 'browser/renderer_host/backing_store_x.cc', + 'browser/renderer_host/backing_store_x.h', + 'browser/renderer_host/blob_message_filter.cc', + 'browser/renderer_host/blob_message_filter.h', + 'browser/renderer_host/buffered_resource_handler.cc', + 'browser/renderer_host/buffered_resource_handler.h', + 'browser/renderer_host/cross_site_resource_handler.cc', + 'browser/renderer_host/cross_site_resource_handler.h', + 'browser/renderer_host/database_message_filter.cc', + 'browser/renderer_host/database_message_filter.h', + 'browser/renderer_host/file_utilities_message_filter.cc', + 'browser/renderer_host/file_utilities_message_filter.h', + 'browser/renderer_host/global_request_id.h', + 'browser/renderer_host/gpu_message_filter.cc', + 'browser/renderer_host/gpu_message_filter.h', + 'browser/renderer_host/pepper_file_message_filter.cc', + 'browser/renderer_host/pepper_file_message_filter.h', + 'browser/renderer_host/pepper_message_filter.cc', + 'browser/renderer_host/pepper_message_filter.h', + 'browser/renderer_host/redirect_to_file_resource_handler.cc', + 'browser/renderer_host/redirect_to_file_resource_handler.h', + 'browser/renderer_host/render_message_filter.cc', + 'browser/renderer_host/render_message_filter.h', + 'browser/renderer_host/render_message_filter_gtk.cc', + 'browser/renderer_host/render_message_filter_mac.mm', + 'browser/renderer_host/render_message_filter_win.cc', + 'browser/renderer_host/render_process_host.cc', + 'browser/renderer_host/render_process_host.h', + 'browser/renderer_host/render_sandbox_host_linux.cc', + 'browser/renderer_host/render_sandbox_host_linux.h', + 'browser/renderer_host/render_view_host.cc', + 'browser/renderer_host/render_view_host.h', + 'browser/renderer_host/render_view_host_delegate.cc', + 'browser/renderer_host/render_view_host_delegate.h', + 'browser/renderer_host/render_view_host_factory.cc', + 'browser/renderer_host/render_view_host_factory.h', + 'browser/renderer_host/render_view_host_notification_task.h', + 'browser/renderer_host/render_widget_fullscreen_host.cc', + 'browser/renderer_host/render_widget_fullscreen_host.h', + 'browser/renderer_host/render_widget_helper.cc', + 'browser/renderer_host/render_widget_helper.h', + 'browser/renderer_host/render_widget_host.cc', + 'browser/renderer_host/render_widget_host.h', + 'browser/renderer_host/render_widget_host_view.cc', + 'browser/renderer_host/render_widget_host_view.h', + 'browser/renderer_host/resource_dispatcher_host.cc', + 'browser/renderer_host/resource_dispatcher_host.h', + 'browser/renderer_host/resource_dispatcher_host_request_info.cc', + 'browser/renderer_host/resource_dispatcher_host_request_info.h', + 'browser/renderer_host/resource_handler.h', + 'browser/renderer_host/resource_message_filter.cc', + 'browser/renderer_host/resource_message_filter.h', + 'browser/renderer_host/resource_queue.cc', + 'browser/renderer_host/resource_queue.h', + 'browser/renderer_host/resource_request_details.cc', + 'browser/renderer_host/resource_request_details.h', + 'browser/renderer_host/socket_stream_dispatcher_host.cc', + 'browser/renderer_host/socket_stream_dispatcher_host.h', + 'browser/renderer_host/socket_stream_host.cc', + 'browser/renderer_host/socket_stream_host.h', + 'browser/renderer_host/sync_resource_handler.cc', + 'browser/renderer_host/sync_resource_handler.h', + 'browser/renderer_host/x509_user_cert_resource_handler.cc', + 'browser/renderer_host/x509_user_cert_resource_handler.h', + 'browser/site_instance.cc', + 'browser/site_instance.h', 'browser/tab_contents/background_contents.cc', 'browser/tab_contents/background_contents.h', 'browser/tab_contents/constrained_window.h', |