diff options
author | jam@chromium.org <jam@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-02-23 03:55:40 +0000 |
---|---|---|
committer | jam@chromium.org <jam@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-02-23 03:55:40 +0000 |
commit | a0421736a8a437ff97eb7deb6050f08b75810343 (patch) | |
tree | 0df2ac04070f1fa41b1a3dfbd5582f0f9bb9817e /content | |
parent | a6d8357a9702c6ce48e15914760708c1970a03e2 (diff) | |
download | chromium_src-a0421736a8a437ff97eb7deb6050f08b75810343.zip chromium_src-a0421736a8a437ff97eb7deb6050f08b75810343.tar.gz chromium_src-a0421736a8a437ff97eb7deb6050f08b75810343.tar.bz2 |
Move the rest of the core files in chrome\browser to content\browser.
TBR=avi
Review URL: http://codereview.chromium.org/6538111
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@75711 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'content')
28 files changed, 5342 insertions, 0 deletions
diff --git a/content/DEPS b/content/DEPS index bd39303..ffab3e2 100644 --- a/content/DEPS +++ b/content/DEPS @@ -13,6 +13,7 @@ include_rules = [ "+net", "+ppapi", "+printing", + "+sandbox", "+skia", # Don't allow inclusion of these other libs we shouldn't be calling directly. diff --git a/content/browser/gpu.sb b/content/browser/gpu.sb new file mode 100644 index 0000000..346bcfa --- /dev/null +++ b/content/browser/gpu.sb @@ -0,0 +1,18 @@ +;; +;; 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. +;; + +; *** The contents of chrome/common/common.sb are implicitly included here. *** + +; The GPU process opens a shared memory file to communicate with the renderer. +; This is backed by a file in /var/folders. +; TODO(thakis): Let the browser allocated the pipe and hand the handles to +; renderer and GPU process and remove this: http://crbug.com/65344 +(allow file-read* file-write* (regex "^/(private/)?(tmp|var)(/|$)")) + +; Allow communication between the GPU process and the UI server. +(allow mach-lookup (global-name "com.apple.tsm.uiserver")) + +(allow file-read-metadata (literal "/")) diff --git a/content/browser/gpu_blacklist.cc b/content/browser/gpu_blacklist.cc new file mode 100644 index 0000000..544a656 --- /dev/null +++ b/content/browser/gpu_blacklist.cc @@ -0,0 +1,577 @@ +// 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/gpu_blacklist.h" + +#include "base/json/json_reader.h" +#include "base/logging.h" +#include "base/string_number_conversions.h" +#include "base/string_util.h" +#include "base/stringprintf.h" +#include "base/sys_info.h" +#include "base/values.h" +#include "base/version.h" +#include "chrome/common/gpu_info.h" + +GpuBlacklist::VersionInfo::VersionInfo(const std::string& version_op, + const std::string& version_string, + const std::string& version_string2) { + op_ = StringToOp(version_op); + if (op_ == kUnknown || op_ == kAny) + return; + version_.reset(Version::GetVersionFromString(version_string)); + if (version_.get() == NULL) { + op_ = kUnknown; + return; + } + if (op_ == kBetween) { + version2_.reset(Version::GetVersionFromString(version_string2)); + if (version2_.get() == NULL) + op_ = kUnknown; + } +} + +GpuBlacklist::VersionInfo::~VersionInfo() { +} + +bool GpuBlacklist::VersionInfo::Contains(const Version& version) const { + if (op_ == kUnknown) + return false; + if (op_ == kAny) + return true; + if (op_ == kEQ) { + // Handles cases where 10.6 is considered as containing 10.6.*. + const std::vector<uint16>& components_reference = version_->components(); + const std::vector<uint16>& components = version.components(); + for (size_t i = 0; i < components_reference.size(); ++i) { + if (i >= components.size() && components_reference[i] != 0) + return false; + if (components[i] != components_reference[i]) + return false; + } + return true; + } + int relation = version.CompareTo(*version_); + if (op_ == kEQ) + return (relation == 0); + else if (op_ == kLT) + return (relation < 0); + else if (op_ == kLE) + return (relation <= 0); + else if (op_ == kGT) + return (relation > 0); + else if (op_ == kGE) + return (relation >= 0); + // op_ == kBetween + if (relation < 0) + return false; + return version.CompareTo(*version2_) <= 0; +} + +bool GpuBlacklist::VersionInfo::IsValid() const { + return op_ != kUnknown; +} + +GpuBlacklist::VersionInfo::Op GpuBlacklist::VersionInfo::StringToOp( + const std::string& version_op) { + if (version_op == "=") + return kEQ; + else if (version_op == "<") + return kLT; + else if (version_op == "<=") + return kLE; + else if (version_op == ">") + return kGT; + else if (version_op == ">=") + return kGE; + else if (version_op == "any") + return kAny; + else if (version_op == "between") + return kBetween; + return kUnknown; +} + +GpuBlacklist::OsInfo::OsInfo(const std::string& os, + const std::string& version_op, + const std::string& version_string, + const std::string& version_string2) { + type_ = StringToOsType(os); + if (type_ != kOsUnknown) { + version_info_.reset( + new VersionInfo(version_op, version_string, version_string2)); + } +} + +GpuBlacklist::OsInfo::~OsInfo() {} + +bool GpuBlacklist::OsInfo::Contains(OsType type, + const Version& version) const { + if (!IsValid()) + return false; + if (type_ != type && type_ != kOsAny) + return false; + return version_info_->Contains(version); +} + +bool GpuBlacklist::OsInfo::IsValid() const { + return type_ != kOsUnknown && version_info_->IsValid(); +} + +GpuBlacklist::OsType GpuBlacklist::OsInfo::type() const { + return type_; +} + +GpuBlacklist::OsType GpuBlacklist::OsInfo::StringToOsType( + const std::string& os) { + if (os == "win") + return kOsWin; + else if (os == "macosx") + return kOsMacosx; + else if (os == "linux") + return kOsLinux; + else if (os == "any") + return kOsAny; + return kOsUnknown; +} + +GpuBlacklist::StringInfo::StringInfo(const std::string& string_op, + const std::string& string_value) { + op_ = StringToOp(string_op); + value_ = StringToLowerASCII(string_value); +} + +bool GpuBlacklist::StringInfo::Contains(const std::string& value) const { + std::string my_value = StringToLowerASCII(value); + switch (op_) { + case kContains: + return strstr(my_value.c_str(), value_.c_str()) != NULL; + case kBeginWith: + return StartsWithASCII(my_value, value_, false); + case kEndWith: + return EndsWith(my_value, value_, false); + case kEQ: + return value_ == my_value; + default: + return false; + } +} + +bool GpuBlacklist::StringInfo::IsValid() const { + return op_ != kUnknown; +} + +GpuBlacklist::StringInfo::Op GpuBlacklist::StringInfo::StringToOp( + const std::string& string_op) { + if (string_op == "=") + return kEQ; + else if (string_op == "contains") + return kContains; + else if (string_op == "beginwith") + return kBeginWith; + else if (string_op == "endwith") + return kEndWith; + return kUnknown; +} + +GpuBlacklist::GpuBlacklistEntry* +GpuBlacklist::GpuBlacklistEntry::GetGpuBlacklistEntryFromValue( + DictionaryValue* value) { + if (value == NULL) + return NULL; + + GpuBlacklistEntry* entry = new GpuBlacklistEntry(); + + std::string id; + if (!value->GetString("id", &id) || !entry->SetId(id)) { + delete entry; + return NULL; + } + + DictionaryValue* os_value = NULL; + if (value->GetDictionary("os", &os_value)) { + std::string os_type; + std::string os_version_op = "any"; + std::string os_version_string; + std::string os_version_string2; + os_value->GetString("type", &os_type); + DictionaryValue* os_version_value = NULL; + if (os_value->GetDictionary("version", &os_version_value)) { + os_version_value->GetString("op", &os_version_op); + os_version_value->GetString("number", &os_version_string); + os_version_value->GetString("number2", &os_version_string2); + } + if (!entry->SetOsInfo(os_type, os_version_op, os_version_string, + os_version_string2)) { + delete entry; + return NULL; + } + } + + std::string vendor_id; + if (value->GetString("vendor_id", &vendor_id)) { + if (!entry->SetVendorId(vendor_id)) { + delete entry; + return NULL; + } + } + + std::string device_id; + if (value->GetString("device_id", &device_id)) { + if (!entry->SetDeviceId(device_id)) { + delete entry; + return NULL; + } + } + + DictionaryValue* driver_vendor_value = NULL; + if (value->GetDictionary("driver_vendor", &driver_vendor_value)) { + std::string vendor_op; + std::string vendor_value; + driver_vendor_value->GetString("op", &vendor_op); + driver_vendor_value->GetString("value", &vendor_value); + if (!entry->SetDriverVendorInfo(vendor_op, vendor_value)) { + delete entry; + return NULL; + } + } + + DictionaryValue* driver_version_value = NULL; + if (value->GetDictionary("driver_version", &driver_version_value)) { + std::string driver_version_op = "any"; + std::string driver_version_string; + std::string driver_version_string2; + driver_version_value->GetString("op", &driver_version_op); + driver_version_value->GetString("number", &driver_version_string); + driver_version_value->GetString("number2", &driver_version_string2); + if (!entry->SetDriverVersionInfo(driver_version_op, driver_version_string, + driver_version_string2)) { + delete entry; + return NULL; + } + } + + DictionaryValue* gl_renderer_value = NULL; + if (value->GetDictionary("gl_renderer", &gl_renderer_value)) { + std::string renderer_op; + std::string renderer_value; + gl_renderer_value->GetString("op", &renderer_op); + gl_renderer_value->GetString("value", &renderer_value); + if (!entry->SetGLRendererInfo(renderer_op, renderer_value)) { + delete entry; + return NULL; + } + } + + ListValue* blacklist_value = NULL; + if (!value->GetList("blacklist", &blacklist_value)) { + delete entry; + return NULL; + } + std::vector<std::string> blacklist; + for (size_t i = 0; i < blacklist_value->GetSize(); ++i) { + std::string feature; + if (blacklist_value->GetString(i, &feature)) { + blacklist.push_back(feature); + } else { + delete entry; + return NULL; + } + } + if (!entry->SetBlacklistedFeatures(blacklist)) { + delete entry; + return NULL; + } + + return entry; +} + +GpuBlacklist::GpuBlacklistEntry::~GpuBlacklistEntry() {} + +GpuBlacklist::GpuBlacklistEntry::GpuBlacklistEntry() + : id_(0), + vendor_id_(0), + device_id_(0) { +} + +bool GpuBlacklist::GpuBlacklistEntry::SetId( + const std::string& id_string) { + int my_id; + if (base::HexStringToInt(id_string, &my_id) && my_id != 0) { + id_ = static_cast<uint32>(my_id); + return true; + } + return false; +} + +bool GpuBlacklist::GpuBlacklistEntry::SetOsInfo( + const std::string& os, + const std::string& version_op, + const std::string& version_string, + const std::string& version_string2) { + os_info_.reset(new OsInfo(os, version_op, version_string, version_string2)); + return os_info_->IsValid(); +} + +bool GpuBlacklist::GpuBlacklistEntry::SetVendorId( + const std::string& vendor_id_string) { + vendor_id_ = 0; + return base::HexStringToInt(vendor_id_string, + reinterpret_cast<int*>(&vendor_id_)); +} + +bool GpuBlacklist::GpuBlacklistEntry::SetDeviceId( + const std::string& device_id_string) { + device_id_ = 0; + return base::HexStringToInt(device_id_string, + reinterpret_cast<int*>(&device_id_)); +} + +bool GpuBlacklist::GpuBlacklistEntry::SetDriverVendorInfo( + const std::string& vendor_op, + const std::string& vendor_value) { + driver_vendor_info_.reset( + new StringInfo(vendor_op, vendor_value)); + return driver_vendor_info_->IsValid(); +} + +bool GpuBlacklist::GpuBlacklistEntry::SetDriverVersionInfo( + const std::string& version_op, + const std::string& version_string, + const std::string& version_string2) { + driver_version_info_.reset( + new VersionInfo(version_op, version_string, version_string2)); + return driver_version_info_->IsValid(); +} + +bool GpuBlacklist::GpuBlacklistEntry::SetGLRendererInfo( + const std::string& renderer_op, + const std::string& renderer_value) { + gl_renderer_info_.reset( + new StringInfo(renderer_op, renderer_value)); + return gl_renderer_info_->IsValid(); +} + +bool GpuBlacklist::GpuBlacklistEntry::SetBlacklistedFeatures( + const std::vector<std::string>& blacklisted_features) { + size_t size = blacklisted_features.size(); + if (size == 0) + return false; + uint32 flags = 0; + for (size_t i = 0; i < size; ++i) { + GpuFeatureFlags::GpuFeatureType type = + GpuFeatureFlags::StringToGpuFeatureType(blacklisted_features[i]); + switch (type) { + case GpuFeatureFlags::kGpuFeatureAccelerated2dCanvas: + case GpuFeatureFlags::kGpuFeatureAcceleratedCompositing: + case GpuFeatureFlags::kGpuFeatureWebgl: + case GpuFeatureFlags::kGpuFeatureAll: + flags |= type; + break; + case GpuFeatureFlags::kGpuFeatureUnknown: + return false; + } + } + feature_flags_.reset(new GpuFeatureFlags()); + feature_flags_->set_flags(flags); + return true; +} + +bool GpuBlacklist::GpuBlacklistEntry::Contains( + OsType os_type, const Version& os_version, const GPUInfo& gpu_info) const { + DCHECK(os_type != kOsAny); + if (os_info_.get() != NULL && !os_info_->Contains(os_type, os_version)) + return false; + if (vendor_id_ != 0 && vendor_id_ != gpu_info.vendor_id()) + return false; + if (device_id_ != 0 && device_id_ != gpu_info.device_id()) + return false; + if (driver_vendor_info_.get() != NULL && + !driver_vendor_info_->Contains(gpu_info.driver_vendor())) + return false; + if (driver_version_info_.get() != NULL) { + scoped_ptr<Version> driver_version( + Version::GetVersionFromString(gpu_info.driver_version())); + if (driver_version.get() == NULL || + !driver_version_info_->Contains(*driver_version)) + return false; + } + if (gl_renderer_info_.get() != NULL && + !gl_renderer_info_->Contains(gpu_info.gl_renderer())) + return false; + return true; +} + +GpuBlacklist::OsType GpuBlacklist::GpuBlacklistEntry::GetOsType() const { + if (os_info_.get() == NULL) + return kOsAny; + return os_info_->type(); +} + +uint32 GpuBlacklist::GpuBlacklistEntry::id() const { + return id_; +} + +GpuFeatureFlags GpuBlacklist::GpuBlacklistEntry::GetGpuFeatureFlags() const { + return *feature_flags_; +} + +GpuBlacklist::GpuBlacklist() + : max_entry_id_(0) { +} + +GpuBlacklist::~GpuBlacklist() { + Clear(); +} + +bool GpuBlacklist::LoadGpuBlacklist(const std::string& json_context, + bool current_os_only) { + std::vector<GpuBlacklistEntry*> entries; + scoped_ptr<Value> root; + root.reset(base::JSONReader::Read(json_context, false)); + if (root.get() == NULL || !root->IsType(Value::TYPE_DICTIONARY)) + return false; + + DictionaryValue* root_dictionary = static_cast<DictionaryValue*>(root.get()); + DCHECK(root_dictionary); + std::string version_string; + root_dictionary->GetString("version", &version_string); + version_.reset(Version::GetVersionFromString(version_string)); + if (version_.get() == NULL) + return false; + + ListValue* list = NULL; + root_dictionary->GetList("entries", &list); + if (list == NULL) + return false; + + uint32 max_entry_id = 0; + for (size_t i = 0; i < list->GetSize(); ++i) { + DictionaryValue* list_item = NULL; + bool valid = list->GetDictionary(i, &list_item); + if (!valid) + break; + GpuBlacklistEntry* entry = + GpuBlacklistEntry::GetGpuBlacklistEntryFromValue(list_item); + if (entry == NULL) + break; + if (entry->id() > max_entry_id) + max_entry_id = entry->id(); + entries.push_back(entry); + } + + if (entries.size() < list->GetSize()) { + for (size_t i = 0; i < entries.size(); ++i) + delete entries[i]; + return false; + } + + Clear(); + // Don't apply GPU blacklist for a non-registered OS. + OsType os_filter = GetOsType(); + if (os_filter != kOsUnknown) { + for (size_t i = 0; i < entries.size(); ++i) { + OsType entry_os = entries[i]->GetOsType(); + if (!current_os_only || + entry_os == kOsAny || entry_os == os_filter) + blacklist_.push_back(entries[i]); + else + delete entries[i]; + } + } + max_entry_id_ = max_entry_id; + return true; +} + +GpuFeatureFlags GpuBlacklist::DetermineGpuFeatureFlags( + GpuBlacklist::OsType os, + Version* os_version, + const GPUInfo& gpu_info) { + active_entries_.clear(); + GpuFeatureFlags flags; + // No need to go through blacklist entries if GPUInfo isn't available. + if (gpu_info.level() == GPUInfo::kUninitialized) + return flags; + + if (os == kOsAny) + os = GetOsType(); + scoped_ptr<Version> my_os_version; + if (os_version == NULL) { + std::string version_string; +#if defined(OS_MACOSX) + // Seems like base::SysInfo::OperatingSystemVersion() returns the wrong + // version in MacOsx. + int32 version_major, version_minor, version_bugfix; + base::SysInfo::OperatingSystemVersionNumbers( + &version_major, &version_minor, &version_bugfix); + version_string = base::StringPrintf("%d.%d.%d", + version_major, + version_minor, + version_bugfix); +#else + version_string = base::SysInfo::OperatingSystemVersion(); + size_t pos = version_string.find_first_not_of("0123456789."); + if (pos != std::string::npos) + version_string = version_string.substr(0, pos); +#endif + my_os_version.reset(Version::GetVersionFromString(version_string)); + os_version = my_os_version.get(); + } + DCHECK(os_version != NULL); + + for (size_t i = 0; i < blacklist_.size(); ++i) { + if (blacklist_[i]->Contains(os, *os_version, gpu_info)) { + flags.Combine(blacklist_[i]->GetGpuFeatureFlags()); + active_entries_.push_back(blacklist_[i]); + } + } + return flags; +} + +void GpuBlacklist::GetGpuFeatureFlagEntries( + GpuFeatureFlags::GpuFeatureType feature, + std::vector<uint32>& entry_ids) const { + entry_ids.clear(); + for (size_t i = 0; i < active_entries_.size(); ++i) { + if ((feature & active_entries_[i]->GetGpuFeatureFlags().flags()) != 0) + entry_ids.push_back(active_entries_[i]->id()); + } +} + +uint32 GpuBlacklist::max_entry_id() const { + return max_entry_id_; +} + +bool GpuBlacklist::GetVersion(uint16* major, uint16* minor) const { + DCHECK(major && minor); + *major = 0; + *minor = 0; + if (version_.get() == NULL) + return false; + const std::vector<uint16>& components_reference = version_->components(); + if (components_reference.size() != 2) + return false; + *major = components_reference[0]; + *minor = components_reference[1]; + return true; +} + +GpuBlacklist::OsType GpuBlacklist::GetOsType() { +#if defined(OS_WIN) + return kOsWin; +#elif defined(OS_LINUX) + return kOsLinux; +#elif defined(OS_MACOSX) + return kOsMacosx; +#else + return kOsUnknown; +#endif +} + +void GpuBlacklist::Clear() { + for (size_t i = 0; i < blacklist_.size(); ++i) + delete blacklist_[i]; + blacklist_.clear(); + active_entries_.clear(); +} + diff --git a/content/browser/gpu_blacklist.h b/content/browser/gpu_blacklist.h new file mode 100644 index 0000000..f732af9 --- /dev/null +++ b/content/browser/gpu_blacklist.h @@ -0,0 +1,227 @@ +// 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_GPU_BLACKLIST_H_ +#define CONTENT_BROWSER_GPU_BLACKLIST_H_ +#pragma once + +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "base/scoped_ptr.h" +#include "chrome/common/gpu_feature_flags.h" + +class DictionaryValue; +class GPUInfo; +class Version; + +class GpuBlacklist { + public: + enum OsType { + kOsLinux, + kOsMacosx, + kOsWin, + kOsAny, + kOsUnknown + }; + + GpuBlacklist(); + ~GpuBlacklist(); + + // Loads blacklist information from a json file. + // current_os_only==true indicates all blacklist entries that don't belong to + // the current OS are discarded; current_os_only==false should only be used + // for testing purpose. + // If failed, the current GpuBlacklist is un-touched. + bool LoadGpuBlacklist(const std::string& json_context, + bool current_os_only); + + // Collects system information and combines them with gpu_info and blacklist + // information to determine gpu feature flags. + // If os is kOsAny, use the current OS; if os_version is null, use the + // current OS version. + GpuFeatureFlags DetermineGpuFeatureFlags(OsType os, + Version* os_version, + const GPUInfo& gpu_info); + + // Collects the entries that set the "feature" flag from the last + // DetermineGpuFeatureFlags() call. This tells which entries are responsible + // for raising a certain flag, i.e, for blacklisting a certain feature. + // Examples of "feature": + // kGpuFeatureAll - any of the supported features; + // kGpuFeatureWebgl - a single feature; + // kGpuFeatureWebgl | kGpuFeatureAcceleratedCompositing - two features. + void GetGpuFeatureFlagEntries(GpuFeatureFlags::GpuFeatureType feature, + std::vector<uint32>& entry_ids) const; + + // Return the largest entry id. This is used for histogramming. + uint32 max_entry_id() const; + + // Collects the version of the current blacklist. Returns false and sets + // major and minor to 0 on failure. + bool GetVersion(uint16* major, uint16* monir) const; + + private: + class VersionInfo { + public: + VersionInfo(const std::string& version_op, + const std::string& version_string, + const std::string& version_string2); + ~VersionInfo(); + + // Determines if a given version is included in the VersionInfo range. + bool Contains(const Version& version) const; + + // Determines if the VersionInfo contains valid information. + bool IsValid() const; + + private: + enum Op { + kBetween, // <= * <= + kEQ, // = + kLT, // < + kLE, // <= + kGT, // > + kGE, // >= + kAny, + kUnknown // Indicates VersionInfo data is invalid. + }; + + // Maps string to Op; returns kUnknown if it's not a valid Op. + static Op StringToOp(const std::string& version_op); + + Op op_; + scoped_ptr<Version> version_; + scoped_ptr<Version> version2_; + }; + + class OsInfo { + public: + OsInfo(const std::string& os, + const std::string& version_op, + const std::string& version_string, + const std::string& version_string2); + ~OsInfo(); + + // Determines if a given os/version is included in the OsInfo set. + bool Contains(OsType type, const Version& version) const; + + // Determines if the VersionInfo contains valid information. + bool IsValid() const; + + OsType type() const; + + // Maps string to OsType; returns kOsUnknown if it's not a valid os. + static OsType StringToOsType(const std::string& os); + + private: + OsType type_; + scoped_ptr<VersionInfo> version_info_; + }; + + class StringInfo { + public: + StringInfo(const std::string& string_op, const std::string& string_value); + + // Determines if a given string is included in the StringInfo. + bool Contains(const std::string& value) const; + + // Determines if the StringInfo contains valid information. + bool IsValid() const; + + private: + enum Op { + kContains, + kBeginWith, + kEndWith, + kEQ, // = + kUnknown // Indicates StringInfo data is invalid. + }; + + // Maps string to Op; returns kUnknown if it's not a valid Op. + static Op StringToOp(const std::string& string_op); + + Op op_; + std::string value_; + }; + + class GpuBlacklistEntry { + public: + // Constructs GpuBlacklistEntry from DictionaryValue loaded from json. + static GpuBlacklistEntry* GetGpuBlacklistEntryFromValue( + DictionaryValue* value); + + // Determines if a given os/gc/driver is included in the Entry set. + bool Contains(OsType os_type, + const Version& os_version, + const GPUInfo& gpu_info) const; + + // Returns the OsType. + OsType GetOsType() const; + + // Returns the entry's unique id. 0 is reserved. + uint32 id() const; + + // Returns the GpuFeatureFlags. + GpuFeatureFlags GetGpuFeatureFlags() const; + + ~GpuBlacklistEntry(); + + private: + GpuBlacklistEntry(); + + bool SetId(const std::string& id_string); + + bool SetOsInfo(const std::string& os, + const std::string& version_op, + const std::string& version_string, + const std::string& version_string2); + + bool SetVendorId(const std::string& vendor_id_string); + + bool SetDeviceId(const std::string& device_id_string); + + bool SetDriverVendorInfo(const std::string& vendor_op, + const std::string& vendor_value); + + bool SetDriverVersionInfo(const std::string& version_op, + const std::string& version_string, + const std::string& version_string2); + + bool SetGLRendererInfo(const std::string& renderer_op, + const std::string& renderer_value); + + bool SetBlacklistedFeatures( + const std::vector<std::string>& blacklisted_features); + + uint32 id_; + scoped_ptr<OsInfo> os_info_; + uint32 vendor_id_; + uint32 device_id_; + scoped_ptr<StringInfo> driver_vendor_info_; + scoped_ptr<VersionInfo> driver_version_info_; + scoped_ptr<StringInfo> gl_renderer_info_; + scoped_ptr<GpuFeatureFlags> feature_flags_; + }; + + // Gets the current OS type. + static OsType GetOsType(); + + void Clear(); + + scoped_ptr<Version> version_; + std::vector<GpuBlacklistEntry*> blacklist_; + + // This records all the blacklist entries that are appliable to the current + // user machine. It is updated everytime DetermineGpuFeatureFlags() is + // called and is used later by GetGpuFeatureFlagEntries(). + std::vector<GpuBlacklistEntry*> active_entries_; + + uint32 max_entry_id_; + + DISALLOW_COPY_AND_ASSIGN(GpuBlacklist); +}; + +#endif // CONTENT_BROWSER_GPU_BLACKLIST_H_ diff --git a/content/browser/gpu_blacklist_unittest.cc b/content/browser/gpu_blacklist_unittest.cc new file mode 100644 index 0000000..e2b496d --- /dev/null +++ b/content/browser/gpu_blacklist_unittest.cc @@ -0,0 +1,176 @@ +// 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 <vector> + +#include "base/version.h" +#include "chrome/common/gpu_info.h" +#include "content/browser/gpu_blacklist.h" +#include "testing/gtest/include/gtest/gtest.h" + +TEST(GpuBlacklistTest, BlacklistLogic) { + GPUInfo gpu_info; + gpu_info.SetVideoCardInfo(0x10de, // Vendor ID + 0x0640); // Device ID + gpu_info.SetDriverInfo("NVIDIA", // Driver vendor + "1.6.18"); // Driver Version + gpu_info.SetLevel(GPUInfo::kComplete); + scoped_ptr<Version> os_version(Version::GetVersionFromString("10.6.4")); + + GpuBlacklist blacklist; + + // Default blacklist settings: all feature are allowed. + GpuFeatureFlags flags = blacklist.DetermineGpuFeatureFlags( + GpuBlacklist::kOsMacosx, os_version.get(), gpu_info); + EXPECT_EQ(flags.flags(), 0u); + + // Empty list: all features are allowed. + const std::string empty_list_json = + "{\n" + " \"name\": \"gpu blacklist\",\n" + " \"version\": \"2.5\",\n" + " \"entries\": [\n" + " ]\n" + "}"; + EXPECT_TRUE(blacklist.LoadGpuBlacklist(empty_list_json, false)); + uint16 major, minor; + EXPECT_TRUE(blacklist.GetVersion(&major, &minor)); + EXPECT_EQ(major, 2u); + EXPECT_EQ(minor, 5u); + flags = blacklist.DetermineGpuFeatureFlags( + GpuBlacklist::kOsMacosx, os_version.get(), gpu_info); + EXPECT_EQ(flags.flags(), 0u); + + // Blacklist accelerated_compositing with exact setting. + const std::string exact_list_json = + "{\n" + " \"name\": \"gpu blacklist\",\n" + " \"version\": \"0.1\",\n" + " \"entries\": [\n" + " {\n" + " \"id\": \"5\",\n" + " \"os\": {\n" + " \"type\": \"macosx\",\n" + " \"version\": {\n" + " \"op\": \"=\",\n" + " \"number\": \"10.6.4\"\n" + " }\n" + " },\n" + " \"vendor_id\": \"0x10de\",\n" + " \"device_id\": \"0x0640\",\n" + " \"driver_version\": {\n" + " \"op\": \"=\",\n" + " \"number\": \"1.6.18\"\n" + " },\n" + " \"blacklist\": [\n" + " \"accelerated_compositing\"\n" + " ]\n" + " }\n" + " ]\n" + "}"; + EXPECT_TRUE(blacklist.LoadGpuBlacklist(exact_list_json, false)); + flags = blacklist.DetermineGpuFeatureFlags( + GpuBlacklist::kOsMacosx, os_version.get(), gpu_info); + EXPECT_EQ( + flags.flags(), + static_cast<uint32>(GpuFeatureFlags::kGpuFeatureAcceleratedCompositing)); + + // Invalid json input should not change the current blacklist settings. + const std::string invalid_json = "invalid"; + EXPECT_FALSE(blacklist.LoadGpuBlacklist(invalid_json, false)); + flags = blacklist.DetermineGpuFeatureFlags( + GpuBlacklist::kOsMacosx, os_version.get(), gpu_info); + EXPECT_EQ( + flags.flags(), + static_cast<uint32>(GpuFeatureFlags::kGpuFeatureAcceleratedCompositing)); + std::vector<uint32> entries; + blacklist.GetGpuFeatureFlagEntries( + GpuFeatureFlags::kGpuFeatureAcceleratedCompositing, entries); + EXPECT_EQ(entries.size(), 1u); + EXPECT_EQ(entries[0], 5u); + blacklist.GetGpuFeatureFlagEntries( + GpuFeatureFlags::kGpuFeatureAll, entries); + EXPECT_EQ(entries.size(), 1u); + EXPECT_EQ(entries[0], 5u); + EXPECT_EQ(blacklist.max_entry_id(), 5u); + + // Blacklist a vendor on all OS. + const std::string vendor_json = + "{\n" + " \"name\": \"gpu blacklist\",\n" + " \"version\": \"0.1\",\n" + " \"entries\": [\n" + " {\n" + " \"id\": \"1\",\n" + " \"vendor_id\": \"0x10de\",\n" + " \"blacklist\": [\n" + " \"webgl\"\n" + " ]\n" + " }\n" + " ]\n" + "}"; + // Blacklist entries won't be filtered to the current OS only upon loading. + EXPECT_TRUE(blacklist.LoadGpuBlacklist(vendor_json, false)); + flags = blacklist.DetermineGpuFeatureFlags( + GpuBlacklist::kOsMacosx, os_version.get(), gpu_info); + EXPECT_EQ(flags.flags(), + static_cast<uint32>(GpuFeatureFlags::kGpuFeatureWebgl)); + flags = blacklist.DetermineGpuFeatureFlags( + GpuBlacklist::kOsWin, os_version.get(), gpu_info); + EXPECT_EQ(flags.flags(), + static_cast<uint32>(GpuFeatureFlags::kGpuFeatureWebgl)); + flags = blacklist.DetermineGpuFeatureFlags( + GpuBlacklist::kOsLinux, os_version.get(), gpu_info); + EXPECT_EQ(flags.flags(), + static_cast<uint32>(GpuFeatureFlags::kGpuFeatureWebgl)); +#if defined(OS_WIN) || defined(OS_LINUX) || defined(OS_MACOSX) + // Blacklist entries will be filtered to the current OS only upon loading. + EXPECT_TRUE(blacklist.LoadGpuBlacklist(vendor_json, true)); + flags = blacklist.DetermineGpuFeatureFlags( + GpuBlacklist::kOsMacosx, os_version.get(), gpu_info); + EXPECT_EQ(flags.flags(), + static_cast<uint32>(GpuFeatureFlags::kGpuFeatureWebgl)); + flags = blacklist.DetermineGpuFeatureFlags( + GpuBlacklist::kOsWin, os_version.get(), gpu_info); + EXPECT_EQ(flags.flags(), + static_cast<uint32>(GpuFeatureFlags::kGpuFeatureWebgl)); + flags = blacklist.DetermineGpuFeatureFlags( + GpuBlacklist::kOsLinux, os_version.get(), gpu_info); + EXPECT_EQ(flags.flags(), + static_cast<uint32>(GpuFeatureFlags::kGpuFeatureWebgl)); +#endif + + + // Blacklist a vendor on Linux only. + const std::string vendor_linux_json = + "{\n" + " \"name\": \"gpu blacklist\",\n" + " \"version\": \"0.1\",\n" + " \"entries\": [\n" + " {\n" + " \"id\": \"1\",\n" + " \"os\": {\n" + " \"type\": \"linux\"\n" + " },\n" + " \"vendor_id\": \"0x10de\",\n" + " \"blacklist\": [\n" + " \"accelerated_2d_canvas\"\n" + " ]\n" + " }\n" + " ]\n" + "}"; + EXPECT_TRUE(blacklist.LoadGpuBlacklist(vendor_linux_json, false)); + flags = blacklist.DetermineGpuFeatureFlags( + GpuBlacklist::kOsMacosx, os_version.get(), gpu_info); + EXPECT_EQ(flags.flags(), 0u); + flags = blacklist.DetermineGpuFeatureFlags( + GpuBlacklist::kOsWin, os_version.get(), gpu_info); + EXPECT_EQ(flags.flags(), 0u); + flags = blacklist.DetermineGpuFeatureFlags( + GpuBlacklist::kOsLinux, os_version.get(), gpu_info); + EXPECT_EQ( + flags.flags(), + static_cast<uint32>(GpuFeatureFlags::kGpuFeatureAccelerated2dCanvas)); +} + diff --git a/content/browser/gpu_process_host.cc b/content/browser/gpu_process_host.cc new file mode 100644 index 0000000..4ef366a --- /dev/null +++ b/content/browser/gpu_process_host.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/gpu_process_host.h" + +#include "app/app_switches.h" +#include "base/metrics/histogram.h" +#include "base/ref_counted.h" +#include "base/string_piece.h" +#include "base/threading/thread.h" +#include "chrome/browser/gpu_process_host_ui_shim.h" +#include "chrome/browser/tab_contents/render_view_host_delegate_helper.h" +#include "chrome/common/chrome_switches.h" +#include "chrome/common/gpu_feature_flags.h" +#include "chrome/common/gpu_info.h" +#include "chrome/common/gpu_messages.h" +#include "chrome/common/render_messages.h" +#include "chrome/gpu/gpu_thread.h" +#include "content/browser/browser_thread.h" +#include "content/browser/renderer_host/render_widget_host.h" +#include "content/browser/renderer_host/render_widget_host_view.h" +#include "ipc/ipc_channel_handle.h" +#include "ipc/ipc_switches.h" +#include "media/base/media_switches.h" + +#if defined(OS_LINUX) +#include "ui/gfx/gtk_native_view_id_manager.h" +#endif // defined(OS_LINUX) + +namespace { + +enum GPUProcessLifetimeEvent { + LAUNCHED, + DIED_FIRST_TIME, + DIED_SECOND_TIME, + DIED_THIRD_TIME, + DIED_FOURTH_TIME, + GPU_PROCESS_LIFETIME_EVENT_MAX + }; + +class RouteOnUIThreadTask : public Task { + public: + RouteOnUIThreadTask(int host_id, const IPC::Message& msg) + : host_id_(host_id), + msg_(msg) { + } + + private: + virtual void Run() { + GpuProcessHostUIShim* ui_shim = GpuProcessHostUIShim::FromID(host_id_); + if (ui_shim) + ui_shim->OnMessageReceived(msg_); + } + + int host_id_; + IPC::Message msg_; +}; + +// A global map from GPU process host ID to GpuProcessHost. +static IDMap<GpuProcessHost> g_hosts_by_id; + +// Number of times the gpu process has crashed in the current browser session. +static int g_gpu_crash_count = 0; + +// Maximum number of times the gpu process is allowed to crash in a session. +// Once this limit is reached, any request to launch the gpu process will fail. +static const int kGpuMaxCrashCount = 3; + +} // anonymous namespace + +class GpuMainThread : public base::Thread { + public: + explicit GpuMainThread(const std::string& channel_id) + : base::Thread("CrGpuMain"), + channel_id_(channel_id) { + } + + ~GpuMainThread() { + Stop(); + } + + protected: + virtual void Init() { + // Must be created on GPU thread. + gpu_thread_.reset(new GpuThread(channel_id_)); + gpu_thread_->Init(base::Time::Now()); + } + + virtual void CleanUp() { + // Must be destroyed on GPU thread. + gpu_thread_.reset(); + } + + private: + scoped_ptr<GpuThread> gpu_thread_; + std::string channel_id_; + DISALLOW_COPY_AND_ASSIGN(GpuMainThread); +}; + +// static +GpuProcessHost* GpuProcessHost::Create(int host_id) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + + GpuProcessHost* host = new GpuProcessHost(host_id); + if (!host->Init()) { + delete host; + return NULL; + } + + return host; +} + +// static +GpuProcessHost* GpuProcessHost::FromID(int host_id) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + + if (host_id == 0) + return NULL; + + return g_hosts_by_id.Lookup(host_id); +} + +GpuProcessHost::GpuProcessHost(int host_id) + : BrowserChildProcessHost(GPU_PROCESS, NULL), + host_id_(host_id) { + g_hosts_by_id.AddWithID(this, host_id_); +} + +GpuProcessHost::~GpuProcessHost() { + + DCHECK(CalledOnValidThread()); + + g_hosts_by_id.Remove(host_id_); + + BrowserThread::PostTask(BrowserThread::UI, + FROM_HERE, + NewRunnableFunction(GpuProcessHostUIShim::Destroy, + host_id_)); +} + +bool GpuProcessHost::Init() { + if (!CreateChannel()) + return false; + + if (!CanLaunchGpuProcess()) + return false; + + if (!LaunchGpuProcess()) + return false; + + return Send(new GpuMsg_Initialize()); +} + +void GpuProcessHost::RouteOnUIThread(const IPC::Message& message) { + BrowserThread::PostTask(BrowserThread::UI, + FROM_HERE, + new RouteOnUIThreadTask(host_id_, message)); +} + +bool GpuProcessHost::Send(IPC::Message* msg) { + DCHECK(CalledOnValidThread()); + return BrowserChildProcessHost::Send(msg); +} + +bool GpuProcessHost::OnMessageReceived(const IPC::Message& message) { + DCHECK(CalledOnValidThread()); + RouteOnUIThread(message); + return true; +} + +bool GpuProcessHost::CanShutdown() { + return true; +} + +namespace { + +void SendOutstandingRepliesDispatcher(int host_id) { + GpuProcessHostUIShim *ui_shim = GpuProcessHostUIShim::FromID(host_id); + DCHECK(ui_shim); + ui_shim->SendOutstandingReplies(); +} + +void SendOutstandingReplies(int host_id) { + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + NewRunnableFunction(&SendOutstandingRepliesDispatcher, host_id)); +} + +} // namespace + +void GpuProcessHost::OnChildDied() { + SendOutstandingReplies(host_id_); + // Located in OnChildDied because OnProcessCrashed suffers from a race + // condition on Linux. + UMA_HISTOGRAM_ENUMERATION("GPU.GPUProcessLifetimeEvents", + DIED_FIRST_TIME + g_gpu_crash_count, + GPU_PROCESS_LIFETIME_EVENT_MAX); + BrowserChildProcessHost::OnChildDied(); +} + +void GpuProcessHost::OnProcessCrashed(int exit_code) { + SendOutstandingReplies(host_id_); + if (++g_gpu_crash_count >= kGpuMaxCrashCount) { + // The gpu process is too unstable to use. Disable it for current session. + RenderViewHostDelegateHelper::set_gpu_enabled(false); + } + BrowserChildProcessHost::OnProcessCrashed(exit_code); +} + +bool GpuProcessHost::CanLaunchGpuProcess() const { + return RenderViewHostDelegateHelper::gpu_enabled(); +} + +bool GpuProcessHost::LaunchGpuProcess() { + if (g_gpu_crash_count >= kGpuMaxCrashCount) + return false; + + const CommandLine& browser_command_line = *CommandLine::ForCurrentProcess(); + + // If the single-process switch is present, just launch the GPU service in a + // new thread in the browser process. + if (browser_command_line.HasSwitch(switches::kSingleProcess)) { + GpuMainThread* thread = new GpuMainThread(channel_id()); + + base::Thread::Options options; +#if defined(OS_LINUX) + options.message_loop_type = MessageLoop::TYPE_IO; +#else + options.message_loop_type = MessageLoop::TYPE_UI; +#endif + + if (!thread->StartWithOptions(options)) + return false; + + return true; + } + + CommandLine::StringType gpu_launcher = + browser_command_line.GetSwitchValueNative(switches::kGpuLauncher); + + FilePath exe_path = ChildProcessHost::GetChildPath(gpu_launcher.empty()); + if (exe_path.empty()) + return false; + + CommandLine* cmd_line = new CommandLine(exe_path); + cmd_line->AppendSwitchASCII(switches::kProcessType, switches::kGpuProcess); + cmd_line->AppendSwitchASCII(switches::kProcessChannelID, channel_id()); + + SetCrashReporterCommandLine(cmd_line); + + // Propagate relevant command line switches. + static const char* const kSwitchNames[] = { + switches::kUseGL, + switches::kDisableGpuVsync, + switches::kDisableGpuWatchdog, + switches::kDisableLogging, + switches::kEnableAcceleratedDecoding, + switches::kEnableLogging, +#if defined(OS_MACOSX) + switches::kEnableSandboxLogging, +#endif + switches::kGpuStartupDialog, + switches::kLoggingLevel, + switches::kNoGpuSandbox, + switches::kNoSandbox, + }; + cmd_line->CopySwitchesFrom(browser_command_line, kSwitchNames, + arraysize(kSwitchNames)); + + // If specified, prepend a launcher program to the command line. + if (!gpu_launcher.empty()) + cmd_line->PrependWrapper(gpu_launcher); + + Launch( +#if defined(OS_WIN) + FilePath(), +#elif defined(OS_POSIX) + false, // Never use the zygote (GPU plugin can't be sandboxed). + base::environment_vector(), +#endif + cmd_line); + + UMA_HISTOGRAM_ENUMERATION("GPU.GPUProcessLifetimeEvents", + LAUNCHED, GPU_PROCESS_LIFETIME_EVENT_MAX); + return true; +} diff --git a/content/browser/gpu_process_host.h b/content/browser/gpu_process_host.h new file mode 100644 index 0000000..9881bee --- /dev/null +++ b/content/browser/gpu_process_host.h @@ -0,0 +1,54 @@ +// 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_GPU_PROCESS_HOST_H_ +#define CONTENT_BROWSER_GPU_PROCESS_HOST_H_ +#pragma once + +#include "base/threading/non_thread_safe.h" +#include "content/browser/browser_child_process_host.h" + +namespace IPC { +class Message; +} + +class GpuProcessHost : public BrowserChildProcessHost, + public base::NonThreadSafe { + public: + + // Create a GpuProcessHost with the given ID. The object can be found using + // FromID with the same id. + static GpuProcessHost* Create(int host_id); + + // Get the GPU process host for the GPU process with the given ID. Returns + // null if the process no longer exists. + static GpuProcessHost* FromID(int host_id); + + virtual bool Send(IPC::Message* msg); + + // IPC::Channel::Listener implementation. + virtual bool OnMessageReceived(const IPC::Message& message); + + private: + explicit GpuProcessHost(int host_id); + virtual ~GpuProcessHost(); + bool Init(); + + // Post an IPC message to the UI shim's message handler on the UI thread. + void RouteOnUIThread(const IPC::Message& message); + + virtual bool CanShutdown(); + virtual void OnChildDied(); + virtual void OnProcessCrashed(int exit_code); + + bool CanLaunchGpuProcess() const; + bool LaunchGpuProcess(); + + // The serial number of the GpuProcessHost / GpuProcessHostUIShim pair. + int host_id_; + + DISALLOW_COPY_AND_ASSIGN(GpuProcessHost); +}; + +#endif // CONTENT_BROWSER_GPU_PROCESS_HOST_H_ diff --git a/content/browser/host_zoom_map.cc b/content/browser/host_zoom_map.cc new file mode 100644 index 0000000..0581036 --- /dev/null +++ b/content/browser/host_zoom_map.cc @@ -0,0 +1,260 @@ +// 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 <cmath> + +#include "content/browser/host_zoom_map.h" + +#include "base/string_piece.h" +#include "base/utf_string_conversions.h" +#include "chrome/browser/browser_thread.h" +#include "chrome/browser/prefs/pref_service.h" +#include "chrome/browser/prefs/scoped_pref_update.h" +#include "chrome/browser/profiles/profile.h" +#include "chrome/common/notification_details.h" +#include "chrome/common/notification_service.h" +#include "chrome/common/notification_source.h" +#include "chrome/common/notification_type.h" +#include "chrome/common/pref_names.h" +#include "content/browser/renderer_host/render_process_host.h" +#include "content/browser/renderer_host/render_view_host.h" +#include "googleurl/src/gurl.h" +#include "net/base/net_util.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebView.h" + +using WebKit::WebView; + +HostZoomMap::HostZoomMap(Profile* profile) + : profile_(profile), + updating_preferences_(false) { + Load(); + default_zoom_level_ = + profile_->GetPrefs()->GetDouble(prefs::kDefaultZoomLevel); + registrar_.Add(this, NotificationType::PROFILE_DESTROYED, + Source<Profile>(profile)); + // Don't observe pref changes (e.g. from sync) in Incognito; once we create + // the incognito window it should have no further connection to the main + // profile/prefs. + if (!profile_->IsOffTheRecord()) { + pref_change_registrar_.Init(profile_->GetPrefs()); + pref_change_registrar_.Add(prefs::kPerHostZoomLevels, this); + pref_change_registrar_.Add(prefs::kDefaultZoomLevel, this); + } + + registrar_.Add( + this, NotificationType::RENDER_VIEW_HOST_WILL_CLOSE_RENDER_VIEW, + NotificationService::AllSources()); +} + +void HostZoomMap::Load() { + if (!profile_) + return; + + base::AutoLock auto_lock(lock_); + host_zoom_levels_.clear(); + const DictionaryValue* host_zoom_dictionary = + profile_->GetPrefs()->GetDictionary(prefs::kPerHostZoomLevels); + // Careful: The returned value could be NULL if the pref has never been set. + if (host_zoom_dictionary != NULL) { + for (DictionaryValue::key_iterator i(host_zoom_dictionary->begin_keys()); + i != host_zoom_dictionary->end_keys(); ++i) { + const std::string& host(*i); + double zoom_level = 0; + + bool success = host_zoom_dictionary->GetDoubleWithoutPathExpansion( + host, &zoom_level); + if (!success) { + // The data used to be stored as ints, so try that. + int int_zoom_level; + success = host_zoom_dictionary->GetIntegerWithoutPathExpansion( + host, &int_zoom_level); + if (success) { + zoom_level = static_cast<double>(int_zoom_level); + // Since the values were once stored as non-clamped, clamp now. + double zoom_factor = WebView::zoomLevelToZoomFactor(zoom_level); + if (zoom_factor < WebView::minTextSizeMultiplier) { + zoom_level = + WebView::zoomFactorToZoomLevel(WebView::minTextSizeMultiplier); + } else if (zoom_factor > WebView::maxTextSizeMultiplier) { + zoom_level = + WebView::zoomFactorToZoomLevel(WebView::maxTextSizeMultiplier); + } + } + } + DCHECK(success); + host_zoom_levels_[host] = zoom_level; + } + } +} + +// static +void HostZoomMap::RegisterUserPrefs(PrefService* prefs) { + prefs->RegisterDictionaryPref(prefs::kPerHostZoomLevels); +} + +double HostZoomMap::GetZoomLevel(const GURL& url) const { + std::string host(net::GetHostOrSpecFromURL(url)); + base::AutoLock auto_lock(lock_); + HostZoomLevels::const_iterator i(host_zoom_levels_.find(host)); + return (i == host_zoom_levels_.end()) ? default_zoom_level_ : i->second; +} + +void HostZoomMap::SetZoomLevel(const GURL& url, double level) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + if (!profile_) + return; + + std::string host(net::GetHostOrSpecFromURL(url)); + + { + base::AutoLock auto_lock(lock_); + if (level == default_zoom_level_) + host_zoom_levels_.erase(host); + else + host_zoom_levels_[host] = level; + } + + NotificationService::current()->Notify(NotificationType::ZOOM_LEVEL_CHANGED, + Source<Profile>(profile_), + NotificationService::NoDetails()); + + // If we're in incognito mode, don't persist changes to the prefs. We'll keep + // them in memory only so they will be forgotten on exiting incognito. + if (profile_->IsOffTheRecord()) + return; + + updating_preferences_ = true; + { + ScopedPrefUpdate update(profile_->GetPrefs(), prefs::kPerHostZoomLevels); + DictionaryValue* host_zoom_dictionary = + profile_->GetPrefs()->GetMutableDictionary(prefs::kPerHostZoomLevels); + if (level == default_zoom_level_) { + host_zoom_dictionary->RemoveWithoutPathExpansion(host, NULL); + } else { + host_zoom_dictionary->SetWithoutPathExpansion( + host, Value::CreateDoubleValue(level)); + } + } + updating_preferences_ = false; +} + +double HostZoomMap::GetTemporaryZoomLevel(int render_process_id, + int render_view_id) const { + base::AutoLock auto_lock(lock_); + for (size_t i = 0; i < temporary_zoom_levels_.size(); ++i) { + if (temporary_zoom_levels_[i].render_process_id == render_process_id && + temporary_zoom_levels_[i].render_view_id == render_view_id) { + return temporary_zoom_levels_[i].zoom_level; + } + } + return 0; +} + +void HostZoomMap::SetTemporaryZoomLevel(int render_process_id, + int render_view_id, + double level) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + if (!profile_) + return; + + { + base::AutoLock auto_lock(lock_); + size_t i; + for (i = 0; i < temporary_zoom_levels_.size(); ++i) { + if (temporary_zoom_levels_[i].render_process_id == render_process_id && + temporary_zoom_levels_[i].render_view_id == render_view_id) { + if (level) { + temporary_zoom_levels_[i].zoom_level = level; + } else { + temporary_zoom_levels_.erase(temporary_zoom_levels_.begin() + i); + } + break; + } + } + + if (level && i == temporary_zoom_levels_.size()) { + TemporaryZoomLevel temp; + temp.render_process_id = render_process_id; + temp.render_view_id = render_view_id; + temp.zoom_level = level; + temporary_zoom_levels_.push_back(temp); + } + } + + NotificationService::current()->Notify(NotificationType::ZOOM_LEVEL_CHANGED, + Source<Profile>(profile_), + NotificationService::NoDetails()); +} + +void HostZoomMap::ResetToDefaults() { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + if (!profile_) + return; + + { + base::AutoLock auto_lock(lock_); + host_zoom_levels_.clear(); + } + + updating_preferences_ = true; + profile_->GetPrefs()->ClearPref(prefs::kPerHostZoomLevels); + updating_preferences_ = false; +} + +void HostZoomMap::Shutdown() { + if (!profile_) + return; + + registrar_.RemoveAll(); + if (!profile_->IsOffTheRecord()) + pref_change_registrar_.RemoveAll(); + profile_ = NULL; +} + +void HostZoomMap::Observe( + NotificationType type, + const NotificationSource& source, + const NotificationDetails& details) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + + switch (type.value) { + case NotificationType::PROFILE_DESTROYED: + // If the profile is going away, we need to stop using it. + Shutdown(); + break; + case NotificationType::RENDER_VIEW_HOST_WILL_CLOSE_RENDER_VIEW: { + base::AutoLock auto_lock(lock_); + int render_view_id = Source<RenderViewHost>(source)->routing_id(); + int render_process_id = Source<RenderViewHost>(source)->process()->id(); + + for (size_t i = 0; i < temporary_zoom_levels_.size(); ++i) { + if (temporary_zoom_levels_[i].render_process_id == render_process_id && + temporary_zoom_levels_[i].render_view_id == render_view_id) { + temporary_zoom_levels_.erase(temporary_zoom_levels_.begin() + i); + break; + } + } + break; + } + case NotificationType::PREF_CHANGED: { + // If we are updating our own preference, don't reload. + if (!updating_preferences_) { + std::string* name = Details<std::string>(details).ptr(); + if (prefs::kPerHostZoomLevels == *name) + Load(); + else if (prefs::kDefaultZoomLevel == *name) { + default_zoom_level_ = + profile_->GetPrefs()->GetDouble(prefs::kDefaultZoomLevel); + } + } + break; + } + default: + NOTREACHED() << "Unexpected preference observed."; + } +} + +HostZoomMap::~HostZoomMap() { + Shutdown(); +} diff --git a/content/browser/host_zoom_map.h b/content/browser/host_zoom_map.h new file mode 100644 index 0000000..4951995 --- /dev/null +++ b/content/browser/host_zoom_map.h @@ -0,0 +1,127 @@ +// 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. + +// Maps hostnames to custom zoom levels. Written on the UI thread and read on +// any thread. One instance per profile. + +#ifndef CONTENT_BROWSER_HOST_ZOOM_MAP_H_ +#define CONTENT_BROWSER_HOST_ZOOM_MAP_H_ +#pragma once + +#include <map> +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "base/ref_counted.h" +#include "base/synchronization/lock.h" +#include "chrome/browser/prefs/pref_change_registrar.h" +#include "chrome/common/notification_observer.h" +#include "chrome/common/notification_registrar.h" +#include "content/browser/browser_thread.h" + +class GURL; +class PrefService; +class Profile; + +// HostZoomMap needs to be deleted on the UI thread because it listens +// to notifications on there (and holds a NotificationRegistrar). +class HostZoomMap : + public NotificationObserver, + public base::RefCountedThreadSafe<HostZoomMap, + BrowserThread::DeleteOnUIThread> { + public: + explicit HostZoomMap(Profile* profile); + + static void RegisterUserPrefs(PrefService* prefs); + + // Returns the zoom level for a given url. The zoom level is determined by + // the host portion of the URL, or (in the absence of a host) the complete + // spec of the URL. In most cases, there is no custom zoom level, and this + // returns the user's default zoom level. Otherwise, returns the saved zoom + // level, which may be positive (to zoom in) or negative (to zoom out). + // + // This may be called on any thread. + double GetZoomLevel(const GURL& url) const; + + // Sets the zoom level for a given url to |level|. If the level matches the + // current default zoom level, the host is erased from the saved preferences; + // otherwise the new value is written out. + // + // This should only be called on the UI thread. + void SetZoomLevel(const GURL& url, double level); + + // Returns the temporary zoom level that's only valid for the lifetime of + // the given tab (i.e. isn't saved and doesn't affect other tabs) if it + // exists, the default zoom level otherwise. + // + // This may be called on any thread. + double GetTemporaryZoomLevel(int render_process_id, + int render_view_id) const; + + // Sets the temporary zoom level that's only valid for the lifetime of this + // tab. + // + // This should only be called on the UI thread. + void SetTemporaryZoomLevel(int render_process_id, + int render_view_id, + double level); + + // Resets all zoom levels. + // + // This should only be called on the UI thread. + void ResetToDefaults(); + + // NotificationObserver implementation. + virtual void Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details); + + private: + friend struct BrowserThread::DeleteOnThread<BrowserThread::UI>; + friend class DeleteTask<HostZoomMap>; + + typedef std::map<std::string, double> HostZoomLevels; + + ~HostZoomMap(); + + // Reads the zoom levels from the preferences service. + void Load(); + + // Removes dependencies on the profile so we can live longer than + // the profile without crashing. + void Shutdown(); + + // The profile we're associated with. + Profile* profile_; + + // Copy of the pref data, so that we can read it on the IO thread. + HostZoomLevels host_zoom_levels_; + double default_zoom_level_; + + struct TemporaryZoomLevel { + int render_process_id; + int render_view_id; + double zoom_level; + }; + + // Don't expect more than a couple of tabs that are using a temporary zoom + // level, so vector is fine for now. + std::vector<TemporaryZoomLevel> temporary_zoom_levels_; + + // Used around accesses to |host_zoom_levels_|, |default_zoom_level_| and + // |temporary_zoom_levels_| to guarantee thread safety. + mutable base::Lock lock_; + + // Whether we are currently updating preferences, this is used to ignore + // notifications from the preference service that we triggered ourself. + bool updating_preferences_; + + NotificationRegistrar registrar_; + PrefChangeRegistrar pref_change_registrar_; + + DISALLOW_COPY_AND_ASSIGN(HostZoomMap); +}; + +#endif // CONTENT_BROWSER_HOST_ZOOM_MAP_H_ diff --git a/content/browser/host_zoom_map_unittest.cc b/content/browser/host_zoom_map_unittest.cc new file mode 100644 index 0000000..82e0e58 --- /dev/null +++ b/content/browser/host_zoom_map_unittest.cc @@ -0,0 +1,132 @@ +// 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/ref_counted.h" +#include "base/utf_string_conversions.h" +#include "base/values.h" +#include "chrome/browser/browser_thread.h" +#include "chrome/browser/host_zoom_map.h" +#include "chrome/browser/prefs/pref_service.h" +#include "chrome/common/notification_details.h" +#include "chrome/common/notification_observer_mock.h" +#include "chrome/common/notification_source.h" +#include "chrome/common/notification_type.h" +#include "chrome/common/pref_names.h" +#include "chrome/test/testing_profile.h" +#include "googleurl/src/gurl.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +using testing::_; +using testing::Pointee; +using testing::Property; + +class HostZoomMapTest : public testing::Test { + public: + static const double kZoomLevel; + static const double kDefaultZoomLevel; + HostZoomMapTest() + : ui_thread_(BrowserThread::UI, &message_loop_), + prefs_(profile_.GetPrefs()), + per_host_zoom_levels_pref_(prefs::kPerHostZoomLevels), + url_("http://example.com/test"), + host_("example.com") {} + + protected: + void SetPrefObserverExpectation() { + EXPECT_CALL( + pref_observer_, + Observe(NotificationType(NotificationType::PREF_CHANGED), + _, + Property(&Details<std::string>::ptr, + Pointee(per_host_zoom_levels_pref_)))); + } + + MessageLoopForUI message_loop_; + BrowserThread ui_thread_; + TestingProfile profile_; + PrefService* prefs_; + std::string per_host_zoom_levels_pref_; // For the observe matcher. + GURL url_; + std::string host_; + NotificationObserverMock pref_observer_; +}; +const double HostZoomMapTest::kZoomLevel = 4; +const double HostZoomMapTest::kDefaultZoomLevel = -2; + +TEST_F(HostZoomMapTest, LoadNoPrefs) { + scoped_refptr<HostZoomMap> map(new HostZoomMap(&profile_)); + EXPECT_EQ(0, map->GetZoomLevel(url_)); +} + +TEST_F(HostZoomMapTest, Load) { + DictionaryValue* dict = + prefs_->GetMutableDictionary(prefs::kPerHostZoomLevels); + dict->SetWithoutPathExpansion(host_, Value::CreateDoubleValue(kZoomLevel)); + scoped_refptr<HostZoomMap> map(new HostZoomMap(&profile_)); + EXPECT_EQ(kZoomLevel, map->GetZoomLevel(url_)); +} + +TEST_F(HostZoomMapTest, SetZoomLevel) { + scoped_refptr<HostZoomMap> map(new HostZoomMap(&profile_)); + PrefChangeRegistrar registrar; + registrar.Init(prefs_); + registrar.Add(prefs::kPerHostZoomLevels, &pref_observer_); + SetPrefObserverExpectation(); + map->SetZoomLevel(url_, kZoomLevel); + EXPECT_EQ(kZoomLevel, map->GetZoomLevel(url_)); + const DictionaryValue* dict = + prefs_->GetDictionary(prefs::kPerHostZoomLevels); + double zoom_level = 0; + EXPECT_TRUE(dict->GetDoubleWithoutPathExpansion(host_, &zoom_level)); + EXPECT_EQ(kZoomLevel, zoom_level); + + SetPrefObserverExpectation(); + map->SetZoomLevel(url_, 0); + EXPECT_EQ(0, map->GetZoomLevel(url_)); + EXPECT_FALSE(dict->HasKey(host_)); +} + +TEST_F(HostZoomMapTest, ResetToDefaults) { + scoped_refptr<HostZoomMap> map(new HostZoomMap(&profile_)); + map->SetZoomLevel(url_, kZoomLevel); + + PrefChangeRegistrar registrar; + registrar.Init(prefs_); + registrar.Add(prefs::kPerHostZoomLevels, &pref_observer_); + SetPrefObserverExpectation(); + map->ResetToDefaults(); + EXPECT_EQ(0, map->GetZoomLevel(url_)); + DictionaryValue empty; + EXPECT_TRUE( + Value::Equals(&empty, prefs_->GetDictionary(prefs::kPerHostZoomLevels))); +} + +TEST_F(HostZoomMapTest, ReloadOnPrefChange) { + scoped_refptr<HostZoomMap> map(new HostZoomMap(&profile_)); + map->SetZoomLevel(url_, kZoomLevel); + + DictionaryValue dict; + dict.SetWithoutPathExpansion(host_, Value::CreateDoubleValue(0)); + prefs_->Set(prefs::kPerHostZoomLevels, dict); + EXPECT_EQ(0, map->GetZoomLevel(url_)); +} + +TEST_F(HostZoomMapTest, NoHost) { + scoped_refptr<HostZoomMap> map(new HostZoomMap(&profile_)); + GURL file_url1_("file:///tmp/test.html"); + GURL file_url2_("file:///tmp/other.html"); + map->SetZoomLevel(file_url1_, kZoomLevel); + + EXPECT_EQ(kZoomLevel, map->GetZoomLevel(file_url1_)); + EXPECT_EQ(0, map->GetZoomLevel(file_url2_)); +} + +TEST_F(HostZoomMapTest, ChangeDefaultZoomLevel) { + FundamentalValue zoom_level(kDefaultZoomLevel); + prefs_->Set(prefs::kDefaultZoomLevel, zoom_level); + scoped_refptr<HostZoomMap> map(new HostZoomMap(&profile_)); + EXPECT_EQ(kDefaultZoomLevel, map->GetZoomLevel(url_)); +} diff --git a/content/browser/mime_registry_message_filter.cc b/content/browser/mime_registry_message_filter.cc new file mode 100644 index 0000000..3268b84 --- /dev/null +++ b/content/browser/mime_registry_message_filter.cc @@ -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. + +#include "content/browser/mime_registry_message_filter.h" + +#include "chrome/common/mime_registry_messages.h" +#include "net/base/mime_util.h" + +MimeRegistryMessageFilter::MimeRegistryMessageFilter() { +} + +MimeRegistryMessageFilter::~MimeRegistryMessageFilter() { +} + +void MimeRegistryMessageFilter::OverrideThreadForMessage( + const IPC::Message& message, + BrowserThread::ID* thread) { + if (IPC_MESSAGE_CLASS(message) == MimeRegistryMsgStart) + *thread = BrowserThread::FILE; +} + +bool MimeRegistryMessageFilter::OnMessageReceived(const IPC::Message& message, + bool* message_was_ok) { + bool handled = true; + IPC_BEGIN_MESSAGE_MAP_EX(MimeRegistryMessageFilter, message, *message_was_ok) + IPC_MESSAGE_HANDLER(MimeRegistryMsg_GetMimeTypeFromExtension, + OnGetMimeTypeFromExtension) + IPC_MESSAGE_HANDLER(MimeRegistryMsg_GetMimeTypeFromFile, + OnGetMimeTypeFromFile) + IPC_MESSAGE_HANDLER(MimeRegistryMsg_GetPreferredExtensionForMimeType, + OnGetPreferredExtensionForMimeType) + IPC_MESSAGE_UNHANDLED(handled = false) + IPC_END_MESSAGE_MAP() + return handled; +} + +void MimeRegistryMessageFilter::OnGetMimeTypeFromExtension( + const FilePath::StringType& ext, std::string* mime_type) { + net::GetMimeTypeFromExtension(ext, mime_type); +} + +void MimeRegistryMessageFilter::OnGetMimeTypeFromFile( + const FilePath& file_path, std::string* mime_type) { + net::GetMimeTypeFromFile(file_path, mime_type); +} + +void MimeRegistryMessageFilter::OnGetPreferredExtensionForMimeType( + const std::string& mime_type, FilePath::StringType* extension) { + net::GetPreferredExtensionForMimeType(mime_type, extension); +} diff --git a/content/browser/mime_registry_message_filter.h b/content/browser/mime_registry_message_filter.h new file mode 100644 index 0000000..87b5b24 --- /dev/null +++ b/content/browser/mime_registry_message_filter.h @@ -0,0 +1,31 @@ +// 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_MIME_REGISTRY_MESSAGE_FILTER_H_ +#define CONTENT_BROWSER_MIME_REGISTRY_MESSAGE_FILTER_H_ + +#include "base/file_path.h" +#include "chrome/browser/browser_message_filter.h" + +class MimeRegistryMessageFilter : public BrowserMessageFilter { + public: + MimeRegistryMessageFilter(); + + virtual void OverrideThreadForMessage(const IPC::Message& message, + BrowserThread::ID* thread); + virtual bool OnMessageReceived(const IPC::Message& message, + bool* message_was_ok); + + private: + ~MimeRegistryMessageFilter(); + + void OnGetMimeTypeFromExtension(const FilePath::StringType& ext, + std::string* mime_type); + void OnGetMimeTypeFromFile(const FilePath& file_path, + std::string* mime_type); + void OnGetPreferredExtensionForMimeType(const std::string& mime_type, + FilePath::StringType* extension); +}; + +#endif // CONTENT_BROWSER_MIME_REGISTRY_MESSAGE_FILTER_H_ diff --git a/content/browser/modal_html_dialog_delegate.cc b/content/browser/modal_html_dialog_delegate.cc new file mode 100644 index 0000000..0f2ca4d --- /dev/null +++ b/content/browser/modal_html_dialog_delegate.cc @@ -0,0 +1,77 @@ +// 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/modal_html_dialog_delegate.h" + +#include <string> + +#include "chrome/browser/browser_list.h" +#include "chrome/common/notification_source.h" +#include "content/browser/renderer_host/render_view_host.h" +#include "content/browser/tab_contents/tab_contents.h" +#include "ui/gfx/size.h" + +ModalHtmlDialogDelegate::ModalHtmlDialogDelegate( + const GURL& url, int width, int height, const std::string& json_arguments, + IPC::Message* sync_result, TabContents* contents) + : contents_(contents), + sync_response_(sync_result) { + // Listen for when the TabContents or its renderer dies. + registrar_.Add(this, NotificationType::TAB_CONTENTS_DISCONNECTED, + Source<TabContents>(contents_)); + + // This information is needed to show the dialog HTML content. + params_.url = url; + params_.height = height; + params_.width = width; + params_.json_input = json_arguments; +} + +ModalHtmlDialogDelegate::~ModalHtmlDialogDelegate() { +} + +void ModalHtmlDialogDelegate::Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details) { + DCHECK(type == NotificationType::TAB_CONTENTS_DISCONNECTED); + DCHECK(Source<TabContents>(source).ptr() == contents_); + registrar_.RemoveAll(); + contents_ = NULL; +} + +bool ModalHtmlDialogDelegate::IsDialogModal() const { + return true; +} + +std::wstring ModalHtmlDialogDelegate::GetDialogTitle() const { + return L"Gears"; +} + +GURL ModalHtmlDialogDelegate::GetDialogContentURL() const { + return params_.url; +} + +void ModalHtmlDialogDelegate::GetDialogSize(gfx::Size* size) const { + size->set_width(params_.width); + size->set_height(params_.height); +} + +std::string ModalHtmlDialogDelegate::GetDialogArgs() const { + return params_.json_input; +} + +void ModalHtmlDialogDelegate::OnDialogClosed(const std::string& json_retval) { + // Our TabContents may have died before this point. + if (contents_ && contents_->render_view_host()) { + contents_->render_view_host()->ModalHTMLDialogClosed(sync_response_, + json_retval); + } + + // We are done with this request, so delete us. + delete this; +} + +bool ModalHtmlDialogDelegate::ShouldShowDialogTitle() const { + return true; +} diff --git a/content/browser/modal_html_dialog_delegate.h b/content/browser/modal_html_dialog_delegate.h new file mode 100644 index 0000000..642acd8 --- /dev/null +++ b/content/browser/modal_html_dialog_delegate.h @@ -0,0 +1,68 @@ +// 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_MODAL_HTML_DIALOG_DELEGATE_H_ +#define CONTENT_BROWSER_MODAL_HTML_DIALOG_DELEGATE_H_ +#pragma once + +#include <vector> + +#include "chrome/browser/webui/html_dialog_ui.h" +#include "chrome/common/notification_observer.h" +#include "chrome/common/notification_registrar.h" + +namespace gfx { +class Size; +} + +namespace IPC { +class Message; +} + +// This class can only be used on the UI thread. +class ModalHtmlDialogDelegate + : public HtmlDialogUIDelegate, + public NotificationObserver { + public: + ModalHtmlDialogDelegate(const GURL& url, + int width, int height, + const std::string& json_arguments, + IPC::Message* sync_result, + TabContents* contents); + ~ModalHtmlDialogDelegate(); + + // Notification service callback. + virtual void Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details); + + // HTMLDialogUIDelegate implementation: + virtual bool IsDialogModal() const; + virtual std::wstring GetDialogTitle() const; + virtual GURL GetDialogContentURL() const; + virtual void GetWebUIMessageHandlers( + std::vector<WebUIMessageHandler*>* handlers) const { } + virtual void GetDialogSize(gfx::Size* size) const; + virtual std::string GetDialogArgs() const; + virtual void OnDialogClosed(const std::string& json_retval); + virtual void OnCloseContents(TabContents* source, bool* out_close_dialog) { } + virtual bool ShouldShowDialogTitle() const; + + private: + NotificationRegistrar registrar_; + + // The TabContents that opened the dialog. + TabContents* contents_; + + // The parameters needed to display a modal HTML dialog. + HtmlDialogUI::HtmlDialogParams params_; + + // Once we get our reply in OnModalDialogResponse we'll need to respond to the + // plugin using this |sync_result| pointer so we store it between calls. + IPC::Message* sync_response_; + + DISALLOW_COPY_AND_ASSIGN(ModalHtmlDialogDelegate); +}; + +#endif // CONTENT_BROWSER_MODAL_HTML_DIALOG_DELEGATE_H_ diff --git a/content/browser/plugin_process_host.cc b/content/browser/plugin_process_host.cc new file mode 100644 index 0000000..215f653 --- /dev/null +++ b/content/browser/plugin_process_host.cc @@ -0,0 +1,478 @@ +// 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/plugin_process_host.h" + +#if defined(OS_WIN) +#include <windows.h> +#elif defined(OS_POSIX) +#include <utility> // for pair<> +#endif + +#include <vector> + +#include "app/app_switches.h" +#include "base/command_line.h" +#include "base/file_path.h" +#include "base/file_util.h" +#include "base/logging.h" +#include "base/path_service.h" +#include "base/string_util.h" +#include "base/utf_string_conversions.h" +#include "chrome/browser/chrome_plugin_browsing_context.h" +#include "chrome/browser/net/url_request_tracking.h" +#include "chrome/browser/plugin_download_helper.h" +#include "chrome/browser/plugin_service.h" +#include "chrome/browser/profiles/profile.h" +#include "chrome/common/chrome_paths.h" +#include "chrome/common/chrome_plugin_lib.h" +#include "chrome/common/chrome_switches.h" +#include "chrome/common/logging_chrome.h" +#include "chrome/common/net/url_request_context_getter.h" +#include "chrome/common/plugin_messages.h" +#include "chrome/common/render_messages.h" +#include "chrome/common/render_messages_params.h" +#include "content/browser/browser_thread.h" +#include "content/browser/child_process_security_policy.h" +#include "content/browser/renderer_host/resource_dispatcher_host.h" +#include "content/browser/renderer_host/resource_message_filter.h" +#include "ipc/ipc_switches.h" +#include "net/base/cookie_store.h" +#include "net/base/io_buffer.h" +#include "net/url_request/url_request.h" +#include "net/url_request/url_request_context.h" +#include "ui/base/ui_base_switches.h" +#include "ui/gfx/native_widget_types.h" + +#if defined(USE_X11) +#include "ui/gfx/gtk_native_view_id_manager.h" +#endif + +#if defined(OS_MACOSX) +#include "base/mac/mac_util.h" +#include "chrome/common/plugin_carbon_interpose_constants_mac.h" +#include "ui/gfx/rect.h" +#endif + +static const char kDefaultPluginFinderURL[] = + "https://dl-ssl.google.com/edgedl/chrome/plugins/plugins2.xml"; + +namespace { + +// Helper class that we pass to ResourceMessageFilter so that it can find the +// right net::URLRequestContext for a request. +class PluginURLRequestContextOverride + : public ResourceMessageFilter::URLRequestContextOverride { + public: + PluginURLRequestContextOverride() { + } + + virtual net::URLRequestContext* GetRequestContext( + const ViewHostMsg_Resource_Request& resource_request) { + return CPBrowsingContextManager::GetInstance()->ToURLRequestContext( + resource_request.request_context); + } + + private: + virtual ~PluginURLRequestContextOverride() {} +}; + +} // namespace + +#if defined(OS_WIN) +void PluginProcessHost::OnPluginWindowDestroyed(HWND window, HWND parent) { + // The window is destroyed at this point, we just care about its parent, which + // is the intermediate window we created. + std::set<HWND>::iterator window_index = + plugin_parent_windows_set_.find(parent); + if (window_index == plugin_parent_windows_set_.end()) + return; + + plugin_parent_windows_set_.erase(window_index); + PostMessage(parent, WM_CLOSE, 0, 0); +} + +void PluginProcessHost::OnDownloadUrl(const std::string& url, + int source_pid, + gfx::NativeWindow caller_window) { + PluginDownloadUrlHelper* download_url_helper = + new PluginDownloadUrlHelper(url, source_pid, caller_window, NULL); + download_url_helper->InitiateDownload( + Profile::GetDefaultRequestContext()->GetURLRequestContext()); +} + +void PluginProcessHost::AddWindow(HWND window) { + plugin_parent_windows_set_.insert(window); +} + +#endif // defined(OS_WIN) + +#if defined(TOOLKIT_USES_GTK) +void PluginProcessHost::OnMapNativeViewId(gfx::NativeViewId id, + gfx::PluginWindowHandle* output) { + *output = 0; + GtkNativeViewManager::GetInstance()->GetXIDForId(output, id); +} +#endif // defined(TOOLKIT_USES_GTK) + +PluginProcessHost::PluginProcessHost() + : BrowserChildProcessHost( + PLUGIN_PROCESS, + PluginService::GetInstance()->resource_dispatcher_host(), + new PluginURLRequestContextOverride()), + ALLOW_THIS_IN_INITIALIZER_LIST(resolve_proxy_msg_helper_(this, NULL)) +#if defined(OS_MACOSX) + , plugin_cursor_visible_(true) +#endif +{ +} + +PluginProcessHost::~PluginProcessHost() { +#if defined(OS_WIN) + // We erase HWNDs from the plugin_parent_windows_set_ when we receive a + // notification that the window is being destroyed. If we don't receive this + // notification and the PluginProcessHost instance is being destroyed, it + // means that the plugin process crashed. We paint a sad face in this case in + // the renderer process. To ensure that the sad face shows up, and we don't + // leak HWNDs, we should destroy existing plugin parent windows. + std::set<HWND>::iterator window_index; + for (window_index = plugin_parent_windows_set_.begin(); + window_index != plugin_parent_windows_set_.end(); + window_index++) { + PostMessage(*window_index, WM_CLOSE, 0, 0); + } +#elif defined(OS_MACOSX) + // If the plugin process crashed but had fullscreen windows open at the time, + // make sure that the menu bar is visible. + std::set<uint32>::iterator window_index; + for (window_index = plugin_fullscreen_windows_set_.begin(); + window_index != plugin_fullscreen_windows_set_.end(); + window_index++) { + if (BrowserThread::CurrentlyOn(BrowserThread::UI)) { + base::mac::ReleaseFullScreen(base::mac::kFullScreenModeHideAll); + } else { + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + NewRunnableFunction(base::mac::ReleaseFullScreen, + base::mac::kFullScreenModeHideAll)); + } + } + // If the plugin hid the cursor, reset that. + if (!plugin_cursor_visible_) { + if (BrowserThread::CurrentlyOn(BrowserThread::UI)) { + base::mac::SetCursorVisibility(true); + } else { + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + NewRunnableFunction(base::mac::SetCursorVisibility, + true)); + } + } +#endif + // Cancel all pending and sent requests. + CancelRequests(); +} + +bool PluginProcessHost::Init(const webkit::npapi::WebPluginInfo& info, + const std::string& locale) { + info_ = info; + set_name(UTF16ToWideHack(info_.name)); + set_version(UTF16ToWideHack(info_.version)); + + if (!CreateChannel()) + return false; + + // Build command line for plugin. When we have a plugin launcher, we can't + // allow "self" on linux and we need the real file path. + const CommandLine& browser_command_line = *CommandLine::ForCurrentProcess(); + CommandLine::StringType plugin_launcher = + browser_command_line.GetSwitchValueNative(switches::kPluginLauncher); + FilePath exe_path = GetChildPath(plugin_launcher.empty()); + if (exe_path.empty()) + return false; + + CommandLine* cmd_line = new CommandLine(exe_path); + // Put the process type and plugin path first so they're easier to see + // in process listings using native process management tools. + cmd_line->AppendSwitchASCII(switches::kProcessType, switches::kPluginProcess); + cmd_line->AppendSwitchPath(switches::kPluginPath, info.path); + + if (logging::DialogsAreSuppressed()) + cmd_line->AppendSwitch(switches::kNoErrorDialogs); + + // Propagate the following switches to the plugin command line (along with + // any associated values) if present in the browser command line + static const char* const kSwitchNames[] = { + switches::kPluginStartupDialog, + switches::kNoSandbox, + switches::kSafePlugins, + switches::kTestSandbox, + switches::kUserAgent, + switches::kDisableBreakpad, + switches::kFullMemoryCrashReport, + switches::kEnableLogging, + switches::kDisableLogging, + switches::kLoggingLevel, + switches::kLogPluginMessages, + switches::kUserDataDir, + switches::kEnableDCHECK, + switches::kSilentDumpOnDCHECK, + switches::kMemoryProfiling, + switches::kUseLowFragHeapCrt, + switches::kEnableStatsTable, + switches::kEnableGPUPlugin, + switches::kUseGL, +#if defined(OS_CHROMEOS) + switches::kLoginProfile, +#endif + }; + + cmd_line->CopySwitchesFrom(browser_command_line, kSwitchNames, + arraysize(kSwitchNames)); + + // If specified, prepend a launcher program to the command line. + if (!plugin_launcher.empty()) + cmd_line->PrependWrapper(plugin_launcher); + + if (!locale.empty()) { + // Pass on the locale so the null plugin will use the right language in the + // prompt to install the desired plugin. + cmd_line->AppendSwitchASCII(switches::kLang, locale); + } + + // Gears requires the data dir to be available on startup. + FilePath data_dir = + PluginService::GetInstance()->GetChromePluginDataDir(); + DCHECK(!data_dir.empty()); + cmd_line->AppendSwitchPath(switches::kPluginDataDir, data_dir); + + cmd_line->AppendSwitchASCII(switches::kProcessChannelID, channel_id()); + + SetCrashReporterCommandLine(cmd_line); + +#if defined(OS_POSIX) + base::environment_vector env; +#if defined(OS_MACOSX) && !defined(__LP64__) + // Add our interposing library for Carbon. This is stripped back out in + // plugin_main.cc, so changes here should be reflected there. + std::string interpose_list(plugin_interpose_strings::kInterposeLibraryPath); + const char* existing_list = + getenv(plugin_interpose_strings::kDYLDInsertLibrariesKey); + if (existing_list) { + interpose_list.insert(0, ":"); + interpose_list.insert(0, existing_list); + } + env.push_back(std::pair<std::string, std::string>( + plugin_interpose_strings::kDYLDInsertLibrariesKey, + interpose_list)); +#endif +#endif + + Launch( +#if defined(OS_WIN) + FilePath(), +#elif defined(OS_POSIX) + false, + env, +#endif + cmd_line); + + return true; +} + +void PluginProcessHost::ForceShutdown() { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + Send(new PluginProcessMsg_NotifyRenderersOfPendingShutdown()); + BrowserChildProcessHost::ForceShutdown(); +} + +void PluginProcessHost::OnProcessLaunched() { + FilePath gears_path; + if (PathService::Get(chrome::FILE_GEARS_PLUGIN, &gears_path)) { + FilePath::StringType gears_path_lc = StringToLowerASCII(gears_path.value()); + FilePath::StringType plugin_path_lc = + StringToLowerASCII(info_.path.value()); + if (plugin_path_lc == gears_path_lc) { + // Give Gears plugins "background" priority. See http://b/1280317. + SetProcessBackgrounded(); + } + } +} + +bool PluginProcessHost::OnMessageReceived(const IPC::Message& msg) { + bool handled = true; + IPC_BEGIN_MESSAGE_MAP(PluginProcessHost, msg) + IPC_MESSAGE_HANDLER(PluginProcessHostMsg_ChannelCreated, OnChannelCreated) + IPC_MESSAGE_HANDLER(PluginProcessHostMsg_GetPluginFinderUrl, + OnGetPluginFinderUrl) + IPC_MESSAGE_HANDLER(PluginProcessHostMsg_PluginMessage, OnPluginMessage) + IPC_MESSAGE_HANDLER(PluginProcessHostMsg_GetCookies, OnGetCookies) + IPC_MESSAGE_HANDLER(PluginProcessHostMsg_AccessFiles, OnAccessFiles) + IPC_MESSAGE_HANDLER_DELAY_REPLY(PluginProcessHostMsg_ResolveProxy, + OnResolveProxy) +#if defined(OS_WIN) + IPC_MESSAGE_HANDLER(PluginProcessHostMsg_PluginWindowDestroyed, + OnPluginWindowDestroyed) + IPC_MESSAGE_HANDLER(PluginProcessHostMsg_DownloadUrl, OnDownloadUrl) +#endif +#if defined(TOOLKIT_USES_GTK) + IPC_MESSAGE_HANDLER(PluginProcessHostMsg_MapNativeViewId, + OnMapNativeViewId) +#endif +#if defined(OS_MACOSX) + IPC_MESSAGE_HANDLER(PluginProcessHostMsg_PluginSelectWindow, + OnPluginSelectWindow) + IPC_MESSAGE_HANDLER(PluginProcessHostMsg_PluginShowWindow, + OnPluginShowWindow) + IPC_MESSAGE_HANDLER(PluginProcessHostMsg_PluginHideWindow, + OnPluginHideWindow) + IPC_MESSAGE_HANDLER(PluginProcessHostMsg_PluginSetCursorVisibility, + OnPluginSetCursorVisibility) +#endif + IPC_MESSAGE_UNHANDLED(handled = false) + IPC_END_MESSAGE_MAP() + + DCHECK(handled); + return handled; +} + +void PluginProcessHost::OnChannelConnected(int32 peer_pid) { + for (size_t i = 0; i < pending_requests_.size(); ++i) { + RequestPluginChannel(pending_requests_[i]); + } + + pending_requests_.clear(); +} + +void PluginProcessHost::OnChannelError() { + CancelRequests(); +} + +bool PluginProcessHost::CanShutdown() { + return sent_requests_.empty(); +} + +void PluginProcessHost::CancelRequests() { + for (size_t i = 0; i < pending_requests_.size(); ++i) + pending_requests_[i]->OnError(); + pending_requests_.clear(); + + while (!sent_requests_.empty()) { + sent_requests_.front()->OnError(); + sent_requests_.pop(); + } +} + +void PluginProcessHost::OpenChannelToPlugin(Client* client) { + InstanceCreated(); + client->SetPluginInfo(info_); + if (opening_channel()) { + // The channel is already in the process of being opened. Put + // this "open channel" request into a queue of requests that will + // be run once the channel is open. + pending_requests_.push_back(client); + return; + } + + // We already have an open channel, send a request right away to plugin. + RequestPluginChannel(client); +} + +void PluginProcessHost::OnGetCookies(uint32 request_context, + const GURL& url, + std::string* cookies) { + net::URLRequestContext* context = CPBrowsingContextManager::GetInstance()-> + ToURLRequestContext(request_context); + // TODO(mpcomplete): remove fallback case when Gears support is prevalent. + if (!context) + context = Profile::GetDefaultRequestContext()->GetURLRequestContext(); + + // Note: We don't have a first_party_for_cookies check because plugins bypass + // third-party cookie blocking. + if (context && context->cookie_store()) { + *cookies = context->cookie_store()->GetCookies(url); + } else { + DLOG(ERROR) << "Could not serve plugin cookies request."; + cookies->clear(); + } +} + +void PluginProcessHost::OnAccessFiles(int renderer_id, + const std::vector<std::string>& files, + bool* allowed) { + ChildProcessSecurityPolicy* policy = + ChildProcessSecurityPolicy::GetInstance(); + + for (size_t i = 0; i < files.size(); ++i) { + const FilePath path = FilePath::FromWStringHack(UTF8ToWide(files[i])); + if (!policy->CanReadFile(renderer_id, path)) { + VLOG(1) << "Denied unauthorized request for file " << files[i]; + *allowed = false; + return; + } + } + + *allowed = true; +} + +void PluginProcessHost::OnResolveProxy(const GURL& url, + IPC::Message* reply_msg) { + resolve_proxy_msg_helper_.Start(url, reply_msg); +} + +void PluginProcessHost::OnResolveProxyCompleted(IPC::Message* reply_msg, + int result, + const std::string& proxy_list) { + PluginProcessHostMsg_ResolveProxy::WriteReplyParams( + reply_msg, result, proxy_list); + Send(reply_msg); +} + +void PluginProcessHost::RequestPluginChannel(Client* client) { + // We can't send any sync messages from the browser because it might lead to + // a hang. However this async messages must be answered right away by the + // plugin process (i.e. unblocks a Send() call like a sync message) otherwise + // a deadlock can occur if the plugin creation request from the renderer is + // a result of a sync message by the plugin process. + PluginProcessMsg_CreateChannel* msg = + new PluginProcessMsg_CreateChannel(client->ID(), + client->OffTheRecord()); + msg->set_unblock(true); + if (Send(msg)) { + sent_requests_.push(client); + } else { + client->OnError(); + } +} + +void PluginProcessHost::OnChannelCreated( + const IPC::ChannelHandle& channel_handle) { + Client* client = sent_requests_.front(); + + client->OnChannelOpened(channel_handle); + sent_requests_.pop(); +} + +void PluginProcessHost::OnGetPluginFinderUrl(std::string* plugin_finder_url) { + if (!plugin_finder_url) { + NOTREACHED(); + return; + } + + // TODO(iyengar) Add the plumbing to retrieve the default + // plugin finder URL. + *plugin_finder_url = kDefaultPluginFinderURL; +} + +void PluginProcessHost::OnPluginMessage( + const std::vector<uint8>& data) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + + ChromePluginLib *chrome_plugin = ChromePluginLib::Find(info_.path); + if (chrome_plugin) { + void *data_ptr = const_cast<void*>(reinterpret_cast<const void*>(&data[0])); + uint32 data_len = static_cast<uint32>(data.size()); + chrome_plugin->functions().on_message(data_ptr, data_len); + } +} diff --git a/content/browser/plugin_process_host.h b/content/browser/plugin_process_host.h new file mode 100644 index 0000000..ad44aae --- /dev/null +++ b/content/browser/plugin_process_host.h @@ -0,0 +1,177 @@ +// 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_PLUGIN_PROCESS_HOST_H_ +#define CONTENT_BROWSER_PLUGIN_PROCESS_HOST_H_ +#pragma once + +#include "build/build_config.h" + +#include <queue> +#include <set> +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "base/ref_counted.h" +#include "chrome/browser/net/resolve_proxy_msg_helper.h" +#include "content/browser/browser_child_process_host.h" +#include "ui/gfx/native_widget_types.h" +#include "webkit/plugins/npapi/webplugininfo.h" + +namespace gfx { +class Rect; +} + +namespace IPC { +struct ChannelHandle; +} + +class GURL; + +// Represents the browser side of the browser <--> plugin communication +// channel. Different plugins run in their own process, but multiple instances +// of the same plugin run in the same process. There will be one +// PluginProcessHost per plugin process, matched with a corresponding +// PluginProcess running in the plugin process. The browser is responsible for +// starting the plugin process when a plugin is created that doesn't already +// have a process. After that, most of the communication is directly between +// the renderer and plugin processes. +class PluginProcessHost : public BrowserChildProcessHost, + public ResolveProxyMsgHelper::Delegate { + public: + class Client { + public: + // Returns a opaque unique identifier for the process requesting + // the channel. + virtual int ID() = 0; + virtual bool OffTheRecord() = 0; + virtual void SetPluginInfo(const webkit::npapi::WebPluginInfo& info) = 0; + // The client should delete itself when one of these methods is called. + virtual void OnChannelOpened(const IPC::ChannelHandle& handle) = 0; + virtual void OnError() = 0; + + protected: + virtual ~Client() {} + }; + + PluginProcessHost(); + virtual ~PluginProcessHost(); + + // Initialize the new plugin process, returning true on success. This must + // be called before the object can be used. + bool Init(const webkit::npapi::WebPluginInfo& info, const std::string& locale); + + // Force the plugin process to shutdown (cleanly). + virtual void ForceShutdown(); + + virtual bool OnMessageReceived(const IPC::Message& msg); + virtual void OnChannelConnected(int32 peer_pid); + virtual void OnChannelError(); + + // ResolveProxyMsgHelper::Delegate implementation: + virtual void OnResolveProxyCompleted(IPC::Message* reply_msg, + int result, + const std::string& proxy_list); + + // Tells the plugin process to create a new channel for communication with a + // renderer. When the plugin process responds with the channel name, + // OnChannelOpened in the client is called. + void OpenChannelToPlugin(Client* client); + + // This function is called on the IO thread once we receive a reply from the + // modal HTML dialog (in the form of a JSON string). This function forwards + // that reply back to the plugin that requested the dialog. + void OnModalDialogResponse(const std::string& json_retval, + IPC::Message* sync_result); + +#if defined(OS_MACOSX) + // This function is called on the IO thread when the browser becomes the + // active application. + void OnAppActivation(); +#endif + + const webkit::npapi::WebPluginInfo& info() const { return info_; } + +#if defined(OS_WIN) + // Tracks plugin parent windows created on the browser UI thread. + void AddWindow(HWND window); +#endif + + private: + friend class PluginResolveProxyHelper; + + // Sends a message to the plugin process to request creation of a new channel + // for the given mime type. + void RequestPluginChannel(Client* client); + + virtual void OnProcessLaunched(); + + // Message handlers. + void OnChannelCreated(const IPC::ChannelHandle& channel_handle); + void OnGetPluginFinderUrl(std::string* plugin_finder_url); + void OnGetCookies(uint32 request_context, const GURL& url, + std::string* cookies); + void OnAccessFiles(int renderer_id, const std::vector<std::string>& files, + bool* allowed); + void OnResolveProxy(const GURL& url, IPC::Message* reply_msg); + void OnPluginMessage(const std::vector<uint8>& data); + +#if defined(OS_WIN) + void OnPluginWindowDestroyed(HWND window, HWND parent); + void OnDownloadUrl(const std::string& url, int source_child_unique_id, + gfx::NativeWindow caller_window); +#endif + +#if defined(USE_X11) + void OnMapNativeViewId(gfx::NativeViewId id, gfx::PluginWindowHandle* output); +#endif + +#if defined(OS_MACOSX) + void OnPluginSelectWindow(uint32 window_id, gfx::Rect window_rect, + bool modal); + void OnPluginShowWindow(uint32 window_id, gfx::Rect window_rect, + bool modal); + void OnPluginHideWindow(uint32 window_id, gfx::Rect window_rect); + void OnPluginSetCursorVisibility(bool visible); +#endif + + virtual bool CanShutdown(); + + void CancelRequests(); + + // These are channel requests that we are waiting to send to the + // plugin process once the channel is opened. + std::vector<Client*> pending_requests_; + + // These are the channel requests that we have already sent to + // the plugin process, but haven't heard back about yet. + std::queue<Client*> sent_requests_; + + // Information about the plugin. + webkit::npapi::WebPluginInfo info_; + + // Helper class for handling PluginProcessHost_ResolveProxy messages (manages + // the requests to the proxy service). + ResolveProxyMsgHelper resolve_proxy_msg_helper_; + +#if defined(OS_WIN) + // Tracks plugin parent windows created on the UI thread. + std::set<HWND> plugin_parent_windows_set_; +#endif +#if defined(OS_MACOSX) + // Tracks plugin windows currently visible. + std::set<uint32> plugin_visible_windows_set_; + // Tracks full screen windows currently visible. + std::set<uint32> plugin_fullscreen_windows_set_; + // Tracks modal windows currently visible. + std::set<uint32> plugin_modal_windows_set_; + // Tracks the current visibility of the cursor. + bool plugin_cursor_visible_; +#endif + + DISALLOW_COPY_AND_ASSIGN(PluginProcessHost); +}; + +#endif // CONTENT_BROWSER_PLUGIN_PROCESS_HOST_H_ diff --git a/content/browser/plugin_process_host_mac.cc b/content/browser/plugin_process_host_mac.cc new file mode 100644 index 0000000..b329f2b --- /dev/null +++ b/content/browser/plugin_process_host_mac.cc @@ -0,0 +1,111 @@ +// 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 <Carbon/Carbon.h> + +#include "build/build_config.h" + +#include <vector> + +#include "base/logging.h" +#include "base/mac/mac_util.h" +#include "chrome/common/plugin_messages.h" +#include "content/browser/browser_thread.h" +#include "content/browser/plugin_process_host.h" +#include "ui/gfx/rect.h" + +void PluginProcessHost::OnPluginSelectWindow(uint32 window_id, + gfx::Rect window_rect, + bool modal) { + plugin_visible_windows_set_.insert(window_id); + if (modal) + plugin_modal_windows_set_.insert(window_id); +} + +void PluginProcessHost::OnPluginShowWindow(uint32 window_id, + gfx::Rect window_rect, + bool modal) { + plugin_visible_windows_set_.insert(window_id); + if (modal) + plugin_modal_windows_set_.insert(window_id); + CGRect window_bounds = { + { window_rect.x(), window_rect.y() }, + { window_rect.width(), window_rect.height() } + }; + CGRect main_display_bounds = CGDisplayBounds(CGMainDisplayID()); + if (CGRectEqualToRect(window_bounds, main_display_bounds) && + (plugin_fullscreen_windows_set_.find(window_id) == + plugin_fullscreen_windows_set_.end())) { + plugin_fullscreen_windows_set_.insert(window_id); + // If the plugin has just shown a window that's the same dimensions as + // the main display, hide the menubar so that it has the whole screen. + // (but only if we haven't already seen this fullscreen window, since + // otherwise our refcounting can get skewed). + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + NewRunnableFunction(base::mac::RequestFullScreen, + base::mac::kFullScreenModeHideAll)); + } +} + +// Must be called on the UI thread. +// If plugin_pid is -1, the browser will be the active process on return, +// otherwise that process will be given focus back before this function returns. +static void ReleasePluginFullScreen(pid_t plugin_pid) { + // Releasing full screen only works if we are the frontmost process; grab + // focus, but give it back to the plugin process if requested. + base::mac::ActivateProcess(base::GetCurrentProcId()); + base::mac::ReleaseFullScreen(base::mac::kFullScreenModeHideAll); + if (plugin_pid != -1) { + base::mac::ActivateProcess(plugin_pid); + } +} + +void PluginProcessHost::OnPluginHideWindow(uint32 window_id, + gfx::Rect window_rect) { + bool had_windows = !plugin_visible_windows_set_.empty(); + plugin_visible_windows_set_.erase(window_id); + bool browser_needs_activation = had_windows && + plugin_visible_windows_set_.empty(); + + plugin_modal_windows_set_.erase(window_id); + if (plugin_fullscreen_windows_set_.find(window_id) != + plugin_fullscreen_windows_set_.end()) { + plugin_fullscreen_windows_set_.erase(window_id); + pid_t plugin_pid = browser_needs_activation ? -1 : handle(); + browser_needs_activation = false; + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + NewRunnableFunction(ReleasePluginFullScreen, plugin_pid)); + } + + if (browser_needs_activation) { + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + NewRunnableFunction(base::mac::ActivateProcess, + base::GetCurrentProcId())); + } +} + +void PluginProcessHost::OnAppActivation() { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + + // If our plugin process has any modal windows up, we need to bring it forward + // so that they act more like an in-process modal window would. + if (!plugin_modal_windows_set_.empty()) { + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + NewRunnableFunction(base::mac::ActivateProcess, handle())); + } +} + +void PluginProcessHost::OnPluginSetCursorVisibility(bool visible) { + if (plugin_cursor_visible_ != visible) { + plugin_cursor_visible_ = visible; + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + NewRunnableFunction(base::mac::SetCursorVisibility, + visible)); + } +} diff --git a/content/browser/plugin_service.cc b/content/browser/plugin_service.cc new file mode 100644 index 0000000..756dbac --- /dev/null +++ b/content/browser/plugin_service.cc @@ -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. + +#include "content/browser/plugin_service.h" + +#include <vector> + +#include "base/command_line.h" +#include "base/path_service.h" +#include "base/string_util.h" +#include "base/threading/thread.h" +#include "base/utf_string_conversions.h" +#include "base/values.h" +#include "base/synchronization/waitable_event.h" +#include "chrome/browser/browser_process.h" +#include "chrome/browser/chrome_plugin_host.h" +#include "chrome/browser/extensions/extension_service.h" +#include "chrome/browser/plugin_updater.h" +#include "chrome/browser/ppapi_plugin_process_host.h" +#include "chrome/browser/profiles/profile.h" +#include "chrome/common/chrome_plugin_lib.h" +#include "chrome/common/chrome_paths.h" +#include "chrome/common/chrome_switches.h" +#include "chrome/common/default_plugin.h" +#include "chrome/common/extensions/extension.h" +#include "chrome/common/gpu_plugin.h" +#include "chrome/common/logging_chrome.h" +#include "chrome/common/notification_type.h" +#include "chrome/common/notification_service.h" +#include "chrome/common/pepper_plugin_registry.h" +#include "chrome/common/plugin_messages.h" +#include "chrome/common/render_messages.h" +#include "content/browser/browser_thread.h" +#include "content/browser/renderer_host/render_process_host.h" +#include "content/browser/renderer_host/render_view_host.h" +#include "webkit/plugins/npapi/plugin_constants_win.h" +#include "webkit/plugins/npapi/plugin_list.h" +#include "webkit/plugins/npapi/webplugininfo.h" + +#if defined(OS_CHROMEOS) +#include "chrome/browser/chromeos/plugin_selection_policy.h" +#endif + +#if defined(OS_MACOSX) +static void NotifyPluginsOfActivation() { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + + for (BrowserChildProcessHost::Iterator iter(ChildProcessInfo::PLUGIN_PROCESS); + !iter.Done(); ++iter) { + PluginProcessHost* plugin = static_cast<PluginProcessHost*>(*iter); + plugin->OnAppActivation(); + } +} +#endif + +static void PurgePluginListCache(bool reload_pages) { + for (RenderProcessHost::iterator it = RenderProcessHost::AllHostsIterator(); + !it.IsAtEnd(); it.Advance()) { + it.GetCurrentValue()->Send(new ViewMsg_PurgePluginListCache(reload_pages)); + } +} + +#if defined(OS_LINUX) +// Delegate class for monitoring directories. +class PluginDirWatcherDelegate : public FilePathWatcher::Delegate { + virtual void OnFilePathChanged(const FilePath& path) { + VLOG(1) << "Watched path changed: " << path.value(); + // Make the plugin list update itself + webkit::npapi::PluginList::Singleton()->RefreshPlugins(); + } + virtual void OnError() { + // TODO(pastarmovj): Add some sensible error handling. Maybe silently + // stopping the watcher would be enough. Or possibly restart it. + NOTREACHED(); + } +}; +#endif + +// static +bool PluginService::enable_chrome_plugins_ = true; + +// static +void PluginService::InitGlobalInstance(Profile* profile) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + + // We first group the plugins and then figure out which groups to disable. + PluginUpdater::GetInstance()->DisablePluginGroupsFromPrefs(profile); + + // Have Chrome plugins write their data to the profile directory. + GetInstance()->SetChromePluginDataDir(profile->GetPath()); +} + +// static +PluginService* PluginService::GetInstance() { + return Singleton<PluginService>::get(); +} + +// static +void PluginService::EnableChromePlugins(bool enable) { + enable_chrome_plugins_ = enable; +} + +PluginService::PluginService() + : main_message_loop_(MessageLoop::current()), + resource_dispatcher_host_(NULL), + ui_locale_(g_browser_process->GetApplicationLocale()) { + RegisterPepperPlugins(); + + // Have the NPAPI plugin list search for Chrome plugins as well. + ChromePluginLib::RegisterPluginsWithNPAPI(); + + // Load any specified on the command line as well. + const CommandLine* command_line = CommandLine::ForCurrentProcess(); + FilePath path = command_line->GetSwitchValuePath(switches::kLoadPlugin); + if (!path.empty()) + webkit::npapi::PluginList::Singleton()->AddExtraPluginPath(path); + path = command_line->GetSwitchValuePath(switches::kExtraPluginDir); + if (!path.empty()) + webkit::npapi::PluginList::Singleton()->AddExtraPluginDir(path); + + chrome::RegisterInternalDefaultPlugin(); + + // Register the internal Flash and PDF, if available. + if (!CommandLine::ForCurrentProcess()->HasSwitch( + switches::kDisableInternalFlash) && + PathService::Get(chrome::FILE_FLASH_PLUGIN, &path)) { + webkit::npapi::PluginList::Singleton()->AddExtraPluginPath(path); + } + +#if defined(OS_CHROMEOS) + plugin_selection_policy_ = new chromeos::PluginSelectionPolicy; + plugin_selection_policy_->StartInit(); +#endif + + chrome::RegisterInternalGPUPlugin(); + + // Start watching for changes in the plugin list. This means watching + // for changes in the Windows registry keys and on both Windows and POSIX + // watch for changes in the paths that are expected to contain plugins. +#if defined(OS_WIN) + hkcu_key_.Create( + HKEY_CURRENT_USER, webkit::npapi::kRegistryMozillaPlugins, KEY_NOTIFY); + hklm_key_.Create( + HKEY_LOCAL_MACHINE, webkit::npapi::kRegistryMozillaPlugins, KEY_NOTIFY); + if (hkcu_key_.StartWatching() == ERROR_SUCCESS) { + hkcu_event_.reset(new base::WaitableEvent(hkcu_key_.watch_event())); + hkcu_watcher_.StartWatching(hkcu_event_.get(), this); + } + + if (hklm_key_.StartWatching() == ERROR_SUCCESS) { + hklm_event_.reset(new base::WaitableEvent(hklm_key_.watch_event())); + hklm_watcher_.StartWatching(hklm_event_.get(), this); + } +#elif defined(OS_POSIX) && !defined(OS_MACOSX) + // Also find plugins in a user-specific plugins dir, + // e.g. ~/.config/chromium/Plugins. + FilePath user_data_dir; + if (PathService::Get(chrome::DIR_USER_DATA, &user_data_dir)) { + webkit::npapi::PluginList::Singleton()->AddExtraPluginDir( + user_data_dir.Append("Plugins")); + } +#endif +// The FilePathWatcher produces too many false positives on MacOS (access time +// updates?) which will lead to enforcing updates of the plugins way too often. +// On ChromeOS the user can't install plugins anyway and on Windows all +// important plugins register themselves in the registry so no need to do that. +#if defined(OS_LINUX) + file_watcher_delegate_ = new PluginDirWatcherDelegate(); + // Get the list of all paths for registering the FilePathWatchers + // that will track and if needed reload the list of plugins on runtime. + std::vector<FilePath> plugin_dirs; + webkit::npapi::PluginList::Singleton()->GetPluginDirectories( + &plugin_dirs); + + for (size_t i = 0; i < plugin_dirs.size(); ++i) { + FilePathWatcher* watcher = new FilePathWatcher(); + // FilePathWatcher can not handle non-absolute paths under windows. + // We don't watch for file changes in windows now but if this should ever + // be extended to Windows these lines might save some time of debugging. +#if defined(OS_WIN) + if (!plugin_dirs[i].IsAbsolute()) + continue; +#endif + VLOG(1) << "Watching for changes in: " << plugin_dirs[i].value(); + BrowserThread::PostTask( + BrowserThread::FILE, FROM_HERE, + NewRunnableFunction( + &PluginService::RegisterFilePathWatcher, + watcher, plugin_dirs[i], file_watcher_delegate_)); + file_watchers_.push_back(watcher); + } +#endif + registrar_.Add(this, NotificationType::EXTENSION_LOADED, + NotificationService::AllSources()); + registrar_.Add(this, NotificationType::EXTENSION_UNLOADED, + NotificationService::AllSources()); +#if defined(OS_MACOSX) + // We need to know when the browser comes forward so we can bring modal plugin + // windows forward too. + registrar_.Add(this, NotificationType::APP_ACTIVATED, + NotificationService::AllSources()); +#endif + registrar_.Add(this, NotificationType::PLUGIN_ENABLE_STATUS_CHANGED, + NotificationService::AllSources()); + registrar_.Add(this, + NotificationType::RENDERER_PROCESS_CLOSED, + NotificationService::AllSources()); +} + +PluginService::~PluginService() { +#if defined(OS_WIN) + // Release the events since they're owned by RegKey, not WaitableEvent. + hkcu_watcher_.StopWatching(); + hklm_watcher_.StopWatching(); + if (hkcu_event_.get()) + hkcu_event_->Release(); + if (hklm_event_.get()) + hklm_event_->Release(); +#endif +} + +void PluginService::LoadChromePlugins( + ResourceDispatcherHost* resource_dispatcher_host) { + if (!enable_chrome_plugins_) + return; + + resource_dispatcher_host_ = resource_dispatcher_host; + ChromePluginLib::LoadChromePlugins(GetCPBrowserFuncsForBrowser()); +} + +void PluginService::SetChromePluginDataDir(const FilePath& data_dir) { + chrome_plugin_data_dir_ = data_dir; +} + +const FilePath& PluginService::GetChromePluginDataDir() { + return chrome_plugin_data_dir_; +} + +const std::string& PluginService::GetUILocale() { + return ui_locale_; +} + +PluginProcessHost* PluginService::FindNpapiPluginProcess( + const FilePath& plugin_path) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + + for (BrowserChildProcessHost::Iterator iter(ChildProcessInfo::PLUGIN_PROCESS); + !iter.Done(); ++iter) { + PluginProcessHost* plugin = static_cast<PluginProcessHost*>(*iter); + if (plugin->info().path == plugin_path) + return plugin; + } + + return NULL; +} + +PpapiPluginProcessHost* PluginService::FindPpapiPluginProcess( + const FilePath& plugin_path) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + + for (BrowserChildProcessHost::Iterator iter( + ChildProcessInfo::PPAPI_PLUGIN_PROCESS); + !iter.Done(); ++iter) { + PpapiPluginProcessHost* plugin = + static_cast<PpapiPluginProcessHost*>(*iter); + if (plugin->plugin_path() == plugin_path) + return plugin; + } + + return NULL; +} + +PluginProcessHost* PluginService::FindOrStartNpapiPluginProcess( + const FilePath& plugin_path) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + + PluginProcessHost* plugin_host = FindNpapiPluginProcess(plugin_path); + if (plugin_host) + return plugin_host; + + webkit::npapi::WebPluginInfo info; + if (!webkit::npapi::PluginList::Singleton()->GetPluginInfoByPath( + plugin_path, &info)) { + return NULL; + } + + // This plugin isn't loaded by any plugin process, so create a new process. + scoped_ptr<PluginProcessHost> new_host(new PluginProcessHost()); + if (!new_host->Init(info, ui_locale_)) { + NOTREACHED(); // Init is not expected to fail. + return NULL; + } + return new_host.release(); +} + +PpapiPluginProcessHost* PluginService::FindOrStartPpapiPluginProcess( + const FilePath& plugin_path) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + + PpapiPluginProcessHost* plugin_host = FindPpapiPluginProcess(plugin_path); + if (plugin_host) + return plugin_host; + + // Validate that the plugin is actually registered. There should generally + // be very few plugins so a brute-force search is fine. + PepperPluginInfo* info = NULL; + for (size_t i = 0; i < ppapi_plugins_.size(); i++) { + if (ppapi_plugins_[i].path == plugin_path) { + info = &ppapi_plugins_[i]; + break; + } + } + if (!info) + return NULL; + + // This plugin isn't loaded by any plugin process, so create a new process. + scoped_ptr<PpapiPluginProcessHost> new_host(new PpapiPluginProcessHost); + if (!new_host->Init(plugin_path)) { + NOTREACHED(); // Init is not expected to fail. + return NULL; + } + return new_host.release(); +} + +void PluginService::OpenChannelToNpapiPlugin( + int render_process_id, + int render_view_id, + const GURL& url, + const std::string& mime_type, + PluginProcessHost::Client* client) { + // The PluginList::GetFirstAllowedPluginInfo may need to load the + // plugins. Don't do it on the IO thread. + BrowserThread::PostTask( + BrowserThread::FILE, FROM_HERE, + NewRunnableMethod( + this, &PluginService::GetAllowedPluginForOpenChannelToPlugin, + render_process_id, render_view_id, url, mime_type, client)); +} + +void PluginService::OpenChannelToPpapiPlugin( + const FilePath& path, + PpapiPluginProcessHost::Client* client) { + PpapiPluginProcessHost* plugin_host = FindOrStartPpapiPluginProcess(path); + if (plugin_host) + plugin_host->OpenChannelToPlugin(client); + else // Send error. + client->OnChannelOpened(base::kNullProcessHandle, IPC::ChannelHandle()); +} + +void PluginService::GetAllowedPluginForOpenChannelToPlugin( + int render_process_id, + int render_view_id, + const GURL& url, + const std::string& mime_type, + PluginProcessHost::Client* client) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); + webkit::npapi::WebPluginInfo info; + bool found = GetFirstAllowedPluginInfo( + render_process_id, render_view_id, url, mime_type, &info, NULL); + FilePath plugin_path; + if (found && webkit::npapi::IsPluginEnabled(info)) + plugin_path = FilePath(info.path); + + // Now we jump back to the IO thread to finish opening the channel. + BrowserThread::PostTask( + BrowserThread::IO, FROM_HERE, + NewRunnableMethod( + this, &PluginService::FinishOpenChannelToPlugin, + plugin_path, client)); +} + +void PluginService::FinishOpenChannelToPlugin( + const FilePath& plugin_path, + PluginProcessHost::Client* client) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + + PluginProcessHost* plugin_host = FindOrStartNpapiPluginProcess(plugin_path); + if (plugin_host) + plugin_host->OpenChannelToPlugin(client); + else + client->OnError(); +} + +bool PluginService::GetFirstAllowedPluginInfo( + int render_process_id, + int render_view_id, + const GURL& url, + const std::string& mime_type, + webkit::npapi::WebPluginInfo* info, + std::string* actual_mime_type) { + // GetPluginInfoArray may need to load the plugins, so we need to be + // on the FILE thread. + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); + bool allow_wildcard = true; +#if defined(OS_CHROMEOS) + std::vector<webkit::npapi::WebPluginInfo> info_array; + std::vector<std::string> actual_mime_types; + webkit::npapi::PluginList::Singleton()->GetPluginInfoArray( + url, mime_type, allow_wildcard, &info_array, &actual_mime_types); + + // Now we filter by the plugin selection policy. + int allowed_index = plugin_selection_policy_->FindFirstAllowed(url, + info_array); + if (!info_array.empty() && allowed_index >= 0) { + *info = info_array[allowed_index]; + if (actual_mime_type) + *actual_mime_type = actual_mime_types[allowed_index]; + return true; + } + return false; +#else + { + base::AutoLock auto_lock(overridden_plugins_lock_); + for (size_t i = 0; i < overridden_plugins_.size(); ++i) { + if (overridden_plugins_[i].render_process_id == render_process_id && + overridden_plugins_[i].render_view_id == render_view_id && + overridden_plugins_[i].url == url) { + if (actual_mime_type) + *actual_mime_type = mime_type; + *info = overridden_plugins_[i].plugin; + return true; + } + } + } + return webkit::npapi::PluginList::Singleton()->GetPluginInfo( + url, mime_type, allow_wildcard, info, actual_mime_type); +#endif +} + +void PluginService::OnWaitableEventSignaled( + base::WaitableEvent* waitable_event) { +#if defined(OS_WIN) + if (waitable_event == hkcu_event_.get()) { + hkcu_key_.StartWatching(); + } else { + hklm_key_.StartWatching(); + } + + webkit::npapi::PluginList::Singleton()->RefreshPlugins(); + PurgePluginListCache(true); +#else + // This event should only get signaled on a Windows machine. + NOTREACHED(); +#endif // defined(OS_WIN) +} + +static void ForceShutdownPlugin(const FilePath& plugin_path) { + PluginProcessHost* plugin = + PluginService::GetInstance()->FindNpapiPluginProcess(plugin_path); + if (plugin) + plugin->ForceShutdown(); +} + +void PluginService::Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details) { + switch (type.value) { + case NotificationType::EXTENSION_LOADED: { + const Extension* extension = Details<const Extension>(details).ptr(); + bool plugins_changed = false; + for (size_t i = 0; i < extension->plugins().size(); ++i) { + const Extension::PluginInfo& plugin = extension->plugins()[i]; + webkit::npapi::PluginList::Singleton()->RefreshPlugins(); + webkit::npapi::PluginList::Singleton()->AddExtraPluginPath(plugin.path); + plugins_changed = true; + if (!plugin.is_public) + private_plugins_[plugin.path] = extension->url(); + } + if (plugins_changed) + PurgePluginListCache(false); + break; + } + + case NotificationType::EXTENSION_UNLOADED: { + const Extension* extension = + Details<UnloadedExtensionInfo>(details)->extension; + bool plugins_changed = false; + for (size_t i = 0; i < extension->plugins().size(); ++i) { + const Extension::PluginInfo& plugin = extension->plugins()[i]; + BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, + NewRunnableFunction(&ForceShutdownPlugin, + plugin.path)); + webkit::npapi::PluginList::Singleton()->RefreshPlugins(); + webkit::npapi::PluginList::Singleton()->RemoveExtraPluginPath( + plugin.path); + plugins_changed = true; + if (!plugin.is_public) + private_plugins_.erase(plugin.path); + } + if (plugins_changed) + PurgePluginListCache(false); + break; + } + +#if defined(OS_MACOSX) + case NotificationType::APP_ACTIVATED: { + BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, + NewRunnableFunction(&NotifyPluginsOfActivation)); + break; + } +#endif + + case NotificationType::PLUGIN_ENABLE_STATUS_CHANGED: { + webkit::npapi::PluginList::Singleton()->RefreshPlugins(); + PurgePluginListCache(false); + break; + } + case NotificationType::RENDERER_PROCESS_CLOSED: { + int render_process_id = Source<RenderProcessHost>(source).ptr()->id(); + + base::AutoLock auto_lock(overridden_plugins_lock_); + for (size_t i = 0; i < overridden_plugins_.size(); ++i) { + if (overridden_plugins_[i].render_process_id == render_process_id) { + overridden_plugins_.erase(overridden_plugins_.begin() + i); + break; + } + } + break; + } + default: + NOTREACHED(); + } +} + +bool PluginService::PrivatePluginAllowedForURL(const FilePath& plugin_path, + const GURL& url) { + if (url.is_empty()) + return true; // Caller wants all plugins. + + PrivatePluginMap::iterator it = private_plugins_.find(plugin_path); + if (it == private_plugins_.end()) + return true; // This plugin is not private, so it's allowed everywhere. + + // We do a dumb compare of scheme and host, rather than using the domain + // service, since we only care about this for extensions. + const GURL& required_url = it->second; + return (url.scheme() == required_url.scheme() && + url.host() == required_url.host()); +} + +void PluginService::OverridePluginForTab(OverriddenPlugin plugin) { + base::AutoLock auto_lock(overridden_plugins_lock_); + overridden_plugins_.push_back(plugin); +} + +void PluginService::RegisterPepperPlugins() { + PepperPluginRegistry::ComputeList(&ppapi_plugins_); + for (size_t i = 0; i < ppapi_plugins_.size(); ++i) { + webkit::npapi::WebPluginInfo info; + info.path = ppapi_plugins_[i].path; + info.name = ppapi_plugins_[i].name.empty() ? + ppapi_plugins_[i].path.BaseName().LossyDisplayName() : + ASCIIToUTF16(ppapi_plugins_[i].name); + info.desc = ASCIIToUTF16(ppapi_plugins_[i].description); + info.version = ASCIIToUTF16(ppapi_plugins_[i].version); + info.enabled = webkit::npapi::WebPluginInfo::USER_ENABLED_POLICY_UNMANAGED; + + // TODO(evan): Pepper shouldn't require us to parse strings to get + // the list of mime types out. + if (!webkit::npapi::PluginList::ParseMimeTypes( + JoinString(ppapi_plugins_[i].mime_types, '|'), + ppapi_plugins_[i].file_extensions, + ASCIIToUTF16(ppapi_plugins_[i].type_descriptions), + &info.mime_types)) { + LOG(ERROR) << "Error parsing mime types for " + << ppapi_plugins_[i].path.LossyDisplayName(); + return; + } + + webkit::npapi::PluginList::Singleton()->RegisterInternalPlugin(info); + } +} + +#if defined(OS_LINUX) +// static +void PluginService::RegisterFilePathWatcher( + FilePathWatcher *watcher, + const FilePath& path, + FilePathWatcher::Delegate* delegate) { + bool result = watcher->Watch(path, delegate); + DCHECK(result); +} +#endif diff --git a/content/browser/plugin_service.h b/content/browser/plugin_service.h new file mode 100644 index 0000000..f9bc49cd --- /dev/null +++ b/content/browser/plugin_service.h @@ -0,0 +1,232 @@ +// 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 class responds to requests from renderers for the list of plugins, and +// also a proxy object for plugin instances. + +#ifndef CONTENT_BROWSER_PLUGIN_SERVICE_H_ +#define CONTENT_BROWSER_PLUGIN_SERVICE_H_ +#pragma once + +#include <string> + +#include "base/basictypes.h" +#include "base/file_path.h" +#include "base/hash_tables.h" +#include "base/scoped_vector.h" +#include "base/singleton.h" +#include "base/synchronization/lock.h" +#include "base/synchronization/waitable_event_watcher.h" +#include "build/build_config.h" +#include "chrome/common/notification_observer.h" +#include "chrome/common/notification_registrar.h" +#include "content/browser/plugin_process_host.h" +#include "content/browser/ppapi_plugin_process_host.h" +#include "googleurl/src/gurl.h" +#include "ipc/ipc_channel_handle.h" +#include "webkit/plugins/npapi/webplugininfo.h" + +#if defined(OS_WIN) +#include "base/scoped_ptr.h" +#include "base/win/registry.h" +#endif + +#if defined(OS_LINUX) +#include "chrome/browser/file_path_watcher/file_path_watcher.h" +#endif + +#if defined(OS_CHROMEOS) +namespace chromeos { +class PluginSelectionPolicy; +} +#endif + +namespace IPC { +class Message; +} + +class MessageLoop; +struct PepperPluginInfo; +class PluginDirWatcherDelegate; +class Profile; +class ResourceDispatcherHost; + +namespace net { +class URLRequestContext; +} // namespace net + +// This must be created on the main thread but it's only called on the IO/file +// thread. +class PluginService + : public base::WaitableEventWatcher::Delegate, + public NotificationObserver { + public: + struct OverriddenPlugin { + int render_process_id; + int render_view_id; + GURL url; + webkit::npapi::WebPluginInfo plugin; + }; + + // Initializes the global instance; should be called on startup from the main + // thread. + static void InitGlobalInstance(Profile* profile); + + // Returns the PluginService singleton. + static PluginService* GetInstance(); + + // Load all the plugins that should be loaded for the lifetime of the browser + // (ie, with the LoadOnStartup flag set). + void LoadChromePlugins(ResourceDispatcherHost* resource_dispatcher_host); + + // Sets/gets the data directory that Chrome plugins should use to store + // persistent data. + void SetChromePluginDataDir(const FilePath& data_dir); + const FilePath& GetChromePluginDataDir(); + + // Gets the browser's UI locale. + const std::string& GetUILocale(); + + // Returns the plugin process host corresponding to the plugin process that + // has been started by this service. Returns NULL if no process has been + // started. + PluginProcessHost* FindNpapiPluginProcess(const FilePath& plugin_path); + PpapiPluginProcessHost* FindPpapiPluginProcess(const FilePath& plugin_path); + + // Returns the plugin process host corresponding to the plugin process that + // has been started by this service. This will start a process to host the + // 'plugin_path' if needed. If the process fails to start, the return value + // is NULL. Must be called on the IO thread. + PluginProcessHost* FindOrStartNpapiPluginProcess( + const FilePath& plugin_path); + PpapiPluginProcessHost* FindOrStartPpapiPluginProcess( + const FilePath& plugin_path); + + // Opens a channel to a plugin process for the given mime type, starting + // a new plugin process if necessary. This must be called on the IO thread + // or else a deadlock can occur. + void OpenChannelToNpapiPlugin(int render_process_id, + int render_view_id, + const GURL& url, + const std::string& mime_type, + PluginProcessHost::Client* client); + void OpenChannelToPpapiPlugin(const FilePath& path, + PpapiPluginProcessHost::Client* client); + + // Gets the first allowed plugin in the list of plugins that matches + // the given url and mime type. Must be called on the FILE thread. + bool GetFirstAllowedPluginInfo(int render_process_id, + int render_view_id, + const GURL& url, + const std::string& mime_type, + webkit::npapi::WebPluginInfo* info, + std::string* actual_mime_type); + + // Returns true if the given plugin is allowed to be used by a page with + // the given URL. + bool PrivatePluginAllowedForURL(const FilePath& plugin_path, const GURL& url); + + // Safe to be called from any thread. + void OverridePluginForTab(OverriddenPlugin plugin); + + // The UI thread's message loop + MessageLoop* main_message_loop() { return main_message_loop_; } + + ResourceDispatcherHost* resource_dispatcher_host() const { + return resource_dispatcher_host_; + } + + static void EnableChromePlugins(bool enable); + + private: + friend struct DefaultSingletonTraits<PluginService>; + + // Creates the PluginService object, but doesn't actually build the plugin + // list yet. It's generated lazily. + PluginService(); + ~PluginService(); + + // base::WaitableEventWatcher::Delegate implementation. + virtual void OnWaitableEventSignaled(base::WaitableEvent* waitable_event); + + // NotificationObserver implementation + virtual void Observe(NotificationType type, const NotificationSource& source, + const NotificationDetails& details); + + void RegisterPepperPlugins(); + + // Helper so we can do the plugin lookup on the FILE thread. + void GetAllowedPluginForOpenChannelToPlugin( + int render_process_id, + int render_view_id, + const GURL& url, + const std::string& mime_type, + PluginProcessHost::Client* client); + + // Helper so we can finish opening the channel after looking up the + // plugin. + void FinishOpenChannelToPlugin( + const FilePath& plugin_path, + PluginProcessHost::Client* client); + +#if defined(OS_LINUX) + // Registers a new FilePathWatcher for a given path. + static void RegisterFilePathWatcher( + FilePathWatcher* watcher, + const FilePath& path, + FilePathWatcher::Delegate* delegate); +#endif + + // The main thread's message loop. + MessageLoop* main_message_loop_; + + // The IO thread's resource dispatcher host. + ResourceDispatcherHost* resource_dispatcher_host_; + + // The data directory that Chrome plugins should use to store persistent data. + FilePath chrome_plugin_data_dir_; + + // The browser's UI locale. + const std::string ui_locale_; + + // Map of plugin paths to the origin they are restricted to. Used for + // extension-only plugins. + typedef base::hash_map<FilePath, GURL> PrivatePluginMap; + PrivatePluginMap private_plugins_; + + NotificationRegistrar registrar_; + +#if defined(OS_CHROMEOS) + scoped_refptr<chromeos::PluginSelectionPolicy> plugin_selection_policy_; +#endif + +#if defined(OS_WIN) + // Registry keys for getting notifications when new plugins are installed. + base::win::RegKey hkcu_key_; + base::win::RegKey hklm_key_; + scoped_ptr<base::WaitableEvent> hkcu_event_; + scoped_ptr<base::WaitableEvent> hklm_event_; + base::WaitableEventWatcher hkcu_watcher_; + base::WaitableEventWatcher hklm_watcher_; +#endif + +#if defined(OS_LINUX) + ScopedVector<FilePathWatcher> file_watchers_; + scoped_refptr<PluginDirWatcherDelegate> file_watcher_delegate_; +#endif + + std::vector<PepperPluginInfo> ppapi_plugins_; + + // Set to true if chrome plugins are enabled. Defaults to true. + static bool enable_chrome_plugins_; + + std::vector<OverriddenPlugin> overridden_plugins_; + base::Lock overridden_plugins_lock_; + + DISALLOW_COPY_AND_ASSIGN(PluginService); +}; + +DISABLE_RUNNABLE_METHOD_REFCOUNT(PluginService); + +#endif // CONTENT_BROWSER_PLUGIN_SERVICE_H_ diff --git a/content/browser/plugin_service_browsertest.cc b/content/browser/plugin_service_browsertest.cc new file mode 100644 index 0000000..2a0edb5 --- /dev/null +++ b/content/browser/plugin_service_browsertest.cc @@ -0,0 +1,107 @@ +// 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/plugin_service.h" + +#include "base/auto_reset.h" +#include "base/command_line.h" +#include "chrome/test/in_process_browser_test.h" +#include "chrome/test/testing_profile.h" +#include "content/browser/browser_thread.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "webkit/plugins/npapi/plugin_list.h" + +namespace { + +// We have to mock the Client class up in order to be able to test the +// OpenChannelToPlugin function. The only really needed function of this mockup +// is SetPluginInfo, which gets called in +// PluginService::FinishOpenChannelToPlugin. +class MockPluginProcessHostClient : public PluginProcessHost::Client { + public: + MockPluginProcessHostClient() {} + virtual ~MockPluginProcessHostClient() {} + + MOCK_METHOD0(ID, int()); + MOCK_METHOD0(OffTheRecord, bool()); + MOCK_METHOD1(SetPluginInfo, void(const webkit::npapi::WebPluginInfo& info)); + MOCK_METHOD1(OnChannelOpened, void(const IPC::ChannelHandle& handle)); + MOCK_METHOD0(OnError, void()); + + private: + DISALLOW_COPY_AND_ASSIGN(MockPluginProcessHostClient); +}; + +class PluginServiceTest : public testing::Test { + public: + PluginServiceTest() + : message_loop_(MessageLoop::TYPE_IO), + ui_thread_(BrowserThread::UI, &message_loop_), + file_thread_(BrowserThread::FILE, &message_loop_), + io_thread_(BrowserThread::IO, &message_loop_) {} + + virtual ~PluginServiceTest() {} + + virtual void SetUp() { + profile_.reset(new TestingProfile()); + + PluginService::InitGlobalInstance(profile_.get()); + plugin_service_ = PluginService::GetInstance(); + ASSERT_TRUE(plugin_service_); + } + + protected: + MessageLoop message_loop_; + PluginService* plugin_service_; + + private: + BrowserThread ui_thread_; + BrowserThread file_thread_; + BrowserThread io_thread_; + scoped_ptr<TestingProfile> profile_; + + DISALLOW_COPY_AND_ASSIGN(PluginServiceTest); +}; + +// These tests need to be implemented as in process tests because on mac os the +// plugin loading mechanism checks whether plugin paths are in the bundle path +// and the test fails this check when run outside of the browser process. +IN_PROC_BROWSER_TEST_F(PluginServiceTest, StartAndFindPluginProcess) { + // Try to load the default plugin and if this is successful consecutive + // calls to FindPluginProcess should return non-zero values. + PluginProcessHost* default_plugin_process_host = + plugin_service_->FindOrStartNpapiPluginProcess( + FilePath(webkit::npapi::kDefaultPluginLibraryName)); + + EXPECT_EQ(default_plugin_process_host, + plugin_service_->FindNpapiPluginProcess( + FilePath(webkit::npapi::kDefaultPluginLibraryName))); +} + +IN_PROC_BROWSER_TEST_F(PluginServiceTest, OpenChannelToPlugin) { + MockPluginProcessHostClient mock_client; + EXPECT_CALL(mock_client, SetPluginInfo(testing::_)).Times(1); + plugin_service_->OpenChannelToNpapiPlugin(0, 0, GURL("http://google.com/"), + "audio/mp3", &mock_client); + message_loop_.RunAllPending(); +} + +IN_PROC_BROWSER_TEST_F(PluginServiceTest, GetFirstAllowedPluginInfo) { + // on ChromeOS the plugin policy gets loaded on the FILE thread and the + // GetFirstAllowedPluginInfo will fail if we don't allow it to finish. + message_loop_.RunAllPending(); + // We should always get a positive response no matter whether we really have + // a plugin to support that particular mime type because the Default plugin + // supports all mime types. + webkit::npapi::WebPluginInfo plugin_info; + std::string plugin_mime_type; + plugin_service_->GetFirstAllowedPluginInfo(0, 0, GURL("http://google.com/"), + "application/pdf", + &plugin_info, + &plugin_mime_type); + EXPECT_EQ("application/pdf", plugin_mime_type); +} + +} // namespace diff --git a/content/browser/plugin_service_unittest.cc b/content/browser/plugin_service_unittest.cc new file mode 100644 index 0000000..0fbde87 --- /dev/null +++ b/content/browser/plugin_service_unittest.cc @@ -0,0 +1,62 @@ +// 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/plugin_service.h" + +#include "base/auto_reset.h" +#include "base/command_line.h" +#include "chrome/test/testing_profile.h" +#include "content/browser/browser_thread.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +class PluginServiceTest : public testing::Test { + public: + PluginServiceTest() + : message_loop_(MessageLoop::TYPE_IO), + ui_thread_(BrowserThread::UI, &message_loop_), + file_thread_(BrowserThread::FILE, &message_loop_), + io_thread_(BrowserThread::IO, &message_loop_) {} + + virtual ~PluginServiceTest() {} + + virtual void SetUp() { + profile_.reset(new TestingProfile()); + + PluginService::InitGlobalInstance(profile_.get()); + plugin_service_ = PluginService::GetInstance(); + ASSERT_TRUE(plugin_service_); + } + + protected: + MessageLoop message_loop_; + PluginService* plugin_service_; + + private: + BrowserThread ui_thread_; + BrowserThread file_thread_; + BrowserThread io_thread_; + scoped_ptr<TestingProfile> profile_; + + DISALLOW_COPY_AND_ASSIGN(PluginServiceTest); +}; + +TEST_F(PluginServiceTest, SetGetChromePluginDataDir) { + // Check that after setting the same plugin dir we just read it is set + // correctly. + FilePath plugin_data_dir = plugin_service_->GetChromePluginDataDir(); + FilePath new_plugin_data_dir(FILE_PATH_LITERAL("/a/bogus/dir")); + plugin_service_->SetChromePluginDataDir(new_plugin_data_dir); + EXPECT_EQ(new_plugin_data_dir, plugin_service_->GetChromePluginDataDir()); + plugin_service_->SetChromePluginDataDir(plugin_data_dir); + EXPECT_EQ(plugin_data_dir, plugin_service_->GetChromePluginDataDir()); +} + +TEST_F(PluginServiceTest, GetUILocale) { + // Check for a non-empty locale string. + EXPECT_NE("", plugin_service_->GetUILocale()); +} + +} // namespace diff --git a/content/browser/ppapi_plugin_process_host.cc b/content/browser/ppapi_plugin_process_host.cc new file mode 100644 index 0000000..def01b7d --- /dev/null +++ b/content/browser/ppapi_plugin_process_host.cc @@ -0,0 +1,174 @@ +// 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/ppapi_plugin_process_host.h" + +#include "base/command_line.h" +#include "base/file_path.h" +#include "base/process_util.h" +#include "chrome/common/chrome_switches.h" +#include "chrome/common/render_messages.h" +#include "content/browser/plugin_service.h" +#include "content/browser/renderer_host/render_message_filter.h" +#include "ipc/ipc_switches.h" +#include "ppapi/proxy/ppapi_messages.h" + +PpapiPluginProcessHost::PpapiPluginProcessHost() + : BrowserChildProcessHost( + ChildProcessInfo::PPAPI_PLUGIN_PROCESS, + PluginService::GetInstance()->resource_dispatcher_host()) { +} + +PpapiPluginProcessHost::~PpapiPluginProcessHost() { + CancelRequests(); +} + +bool PpapiPluginProcessHost::Init(const FilePath& path) { + plugin_path_ = path; + + if (!CreateChannel()) + return false; + + const CommandLine& browser_command_line = *CommandLine::ForCurrentProcess(); + CommandLine::StringType plugin_launcher = + browser_command_line.GetSwitchValueNative(switches::kPpapiPluginLauncher); + + FilePath exe_path = ChildProcessHost::GetChildPath(plugin_launcher.empty()); + if (exe_path.empty()) + return false; + + CommandLine* cmd_line = new CommandLine(exe_path); + cmd_line->AppendSwitchASCII(switches::kProcessType, + switches::kPpapiPluginProcess); + cmd_line->AppendSwitchASCII(switches::kProcessChannelID, channel_id()); + + if (!plugin_launcher.empty()) + cmd_line->PrependWrapper(plugin_launcher); + + // On posix, having a plugin launcher means we need to use another process + // instead of just forking the zygote. + Launch( +#if defined(OS_WIN) + FilePath(), +#elif defined(OS_POSIX) + plugin_launcher.empty(), + base::environment_vector(), +#endif + cmd_line); + return true; +} + +void PpapiPluginProcessHost::OpenChannelToPlugin(Client* client) { + if (opening_channel()) { + // The channel is already in the process of being opened. Put + // this "open channel" request into a queue of requests that will + // be run once the channel is open. + pending_requests_.push_back(client); + return; + } + + // We already have an open channel, send a request right away to plugin. + RequestPluginChannel(client); +} + +void PpapiPluginProcessHost::RequestPluginChannel(Client* client) { + base::ProcessHandle process_handle; + int renderer_id; + client->GetChannelInfo(&process_handle, &renderer_id); + + // We can't send any sync messages from the browser because it might lead to + // a hang. See the similar code in PluginProcessHost for more description. + PpapiMsg_CreateChannel* msg = new PpapiMsg_CreateChannel(process_handle, + renderer_id); + msg->set_unblock(true); + if (Send(msg)) + sent_requests_.push(client); + else + client->OnChannelOpened(base::kNullProcessHandle, IPC::ChannelHandle()); +} + +bool PpapiPluginProcessHost::CanShutdown() { + return true; +} + +void PpapiPluginProcessHost::OnProcessLaunched() { +} + +bool PpapiPluginProcessHost::OnMessageReceived(const IPC::Message& msg) { + bool handled = true; + IPC_BEGIN_MESSAGE_MAP(PpapiPluginProcessHost, msg) + IPC_MESSAGE_HANDLER(PpapiHostMsg_ChannelCreated, + OnRendererPluginChannelCreated) + IPC_MESSAGE_UNHANDLED(handled = false) + IPC_END_MESSAGE_MAP() + DCHECK(handled); + return handled; +} + +// Called when the browser <--> plugin channel has been established. +void PpapiPluginProcessHost::OnChannelConnected(int32 peer_pid) { + // This will actually load the plugin. Errors will actually not be reported + // back at this point. Instead, the plugin will fail to establish the + // connections when we request them on behalf of the renderer(s). + Send(new PpapiMsg_LoadPlugin(plugin_path_)); + + // Process all pending channel requests from the renderers. + for (size_t i = 0; i < pending_requests_.size(); i++) + RequestPluginChannel(pending_requests_[i]); + pending_requests_.clear(); +} + +// Called when the browser <--> plugin channel has an error. This normally +// means the plugin has crashed. +void PpapiPluginProcessHost::OnChannelError() { + // We don't need to notify the renderers that were communicating with the + // plugin since they have their own channels which will go into the error + // state at the same time. Instead, we just need to notify any renderers + // that have requested a connection but have not yet received one. + CancelRequests(); +} + +void PpapiPluginProcessHost::CancelRequests() { + for (size_t i = 0; i < pending_requests_.size(); i++) { + pending_requests_[i]->OnChannelOpened(base::kNullProcessHandle, + IPC::ChannelHandle()); + } + pending_requests_.clear(); + + while (!sent_requests_.empty()) { + sent_requests_.front()->OnChannelOpened(base::kNullProcessHandle, + IPC::ChannelHandle()); + sent_requests_.pop(); + } +} + +// Called when a new plugin <--> renderer channel has been created. +void PpapiPluginProcessHost::OnRendererPluginChannelCreated( + const IPC::ChannelHandle& channel_handle) { + if (sent_requests_.empty()) + return; + + // All requests should be processed FIFO, so the next item in the + // sent_requests_ queue should be the one that the plugin just created. + Client* client = sent_requests_.front(); + sent_requests_.pop(); + + // Prepare the handle to send to the renderer. + base::ProcessHandle plugin_process = GetChildProcessHandle(); +#if defined(OS_WIN) + base::ProcessHandle renderer_process; + int renderer_id; + client->GetChannelInfo(&renderer_process, &renderer_id); + + base::ProcessHandle renderers_plugin_handle = NULL; + ::DuplicateHandle(::GetCurrentProcess(), plugin_process, + renderer_process, &renderers_plugin_handle, + 0, FALSE, DUPLICATE_SAME_ACCESS); +#elif defined(OS_POSIX) + // Don't need to duplicate anything on POSIX since it's just a PID. + base::ProcessHandle renderers_plugin_handle = plugin_process; +#endif + + client->OnChannelOpened(renderers_plugin_handle, channel_handle); +} diff --git a/content/browser/ppapi_plugin_process_host.h b/content/browser/ppapi_plugin_process_host.h new file mode 100644 index 0000000..baeaf3b --- /dev/null +++ b/content/browser/ppapi_plugin_process_host.h @@ -0,0 +1,78 @@ +// 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_PPAPI_PLUGIN_PROCESS_HOST_H_ +#define CONTENT_BROWSER_PPAPI_PLUGIN_PROCESS_HOST_H_ +#pragma once + +#include <queue> + +#include "base/basictypes.h" +#include "base/file_path.h" +#include "content/browser/browser_child_process_host.h" + +class PpapiPluginProcessHost : public BrowserChildProcessHost { + public: + class Client { + public: + // Gets the information about the renderer that's requesting the channel. + virtual void GetChannelInfo(base::ProcessHandle* renderer_handle, + int* renderer_id) = 0; + + // Called when the channel is asynchronously opened to the plugin or on + // error. On error, the parameters should be: + // base::kNullProcessHandle + // IPC::ChannelHandle() + virtual void OnChannelOpened(base::ProcessHandle plugin_process_handle, + const IPC::ChannelHandle& channel_handle) = 0; + }; + + // You must call init before doing anything else. + explicit PpapiPluginProcessHost(); + virtual ~PpapiPluginProcessHost(); + + // Actually launches the process with the given plugin path. Returns true + // on success (the process was spawned). + bool Init(const FilePath& path); + + // Opens a new channel to the plugin. The client will be notified when the + // channel is ready or if there's an error. + void OpenChannelToPlugin(Client* client); + + const FilePath& plugin_path() const { return plugin_path_; } + + // The client pointer must remain valid until its callback is issued. + + private: + + void RequestPluginChannel(Client* client); + + virtual bool CanShutdown(); + virtual void OnProcessLaunched(); + + virtual bool OnMessageReceived(const IPC::Message& msg); + virtual void OnChannelConnected(int32 peer_pid); + virtual void OnChannelError(); + + void CancelRequests(); + + // IPC message handlers. + void OnRendererPluginChannelCreated(const IPC::ChannelHandle& handle); + + // Channel requests that we are waiting to send to the plugin process once + // the channel is opened. + std::vector<Client*> pending_requests_; + + // Channel requests that we have already sent to the plugin process, but + // haven't heard back about yet. + std::queue<Client*> sent_requests_; + + // Path to the plugin library. + FilePath plugin_path_; + + DISALLOW_COPY_AND_ASSIGN(PpapiPluginProcessHost); +}; + +#endif // CONTENT_BROWSER_PPAPI_PLUGIN_PROCESS_HOST_H_ + diff --git a/content/browser/worker.sb b/content/browser/worker.sb new file mode 100644 index 0000000..c984670 --- /dev/null +++ b/content/browser/worker.sb @@ -0,0 +1,12 @@ +;; +;; 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. +;; +; This is the Sandbox configuration file used for safeguarding the worker +; process which is used to run web workers in a sandboxed environment. +; +; This is the most restrictive sandbox profile and only enables just enough +; to allow basic use of Cocoa. + +; *** The contents of chrome/common/common.sb are implicitly included here. ***
\ No newline at end of file diff --git a/content/browser/zygote_host_linux.cc b/content/browser/zygote_host_linux.cc new file mode 100644 index 0000000..3b6f1fb --- /dev/null +++ b/content/browser/zygote_host_linux.cc @@ -0,0 +1,362 @@ +// 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/zygote_host_linux.h" + +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +#include "base/command_line.h" +#include "base/eintr_wrapper.h" +#include "base/environment.h" +#include "base/linux_util.h" +#include "base/logging.h" +#include "base/path_service.h" +#include "base/pickle.h" +#include "base/process_util.h" +#include "base/string_number_conversions.h" +#include "base/string_util.h" +#include "base/scoped_ptr.h" +#include "base/utf_string_conversions.h" +#include "chrome/common/chrome_constants.h" +#include "chrome/common/chrome_switches.h" +#include "chrome/common/process_watcher.h" +#include "chrome/common/result_codes.h" +#include "chrome/common/unix_domain_socket_posix.h" +#include "content/browser/renderer_host/render_sandbox_host_linux.h" +#include "sandbox/linux/suid/suid_unsafe_environment_variables.h" + +static void SaveSUIDUnsafeEnvironmentVariables() { + // The ELF loader will clear many environment variables so we save them to + // different names here so that the SUID sandbox can resolve them for the + // renderer. + + for (unsigned i = 0; kSUIDUnsafeEnvironmentVariables[i]; ++i) { + const char* const envvar = kSUIDUnsafeEnvironmentVariables[i]; + char* const saved_envvar = SandboxSavedEnvironmentVariable(envvar); + if (!saved_envvar) + continue; + + scoped_ptr<base::Environment> env(base::Environment::Create()); + std::string value; + if (env->GetVar(envvar, &value)) + env->SetVar(saved_envvar, value); + else + env->UnSetVar(saved_envvar); + + free(saved_envvar); + } +} + +ZygoteHost::ZygoteHost() + : control_fd_(-1), + pid_(-1), + init_(false), + using_suid_sandbox_(false), + have_read_sandbox_status_word_(false), + sandbox_status_(0) { +} + +ZygoteHost::~ZygoteHost() { + if (init_) + close(control_fd_); +} + +// static +ZygoteHost* ZygoteHost::GetInstance() { + return Singleton<ZygoteHost>::get(); +} + +void ZygoteHost::Init(const std::string& sandbox_cmd) { + DCHECK(!init_); + init_ = true; + + FilePath chrome_path; + CHECK(PathService::Get(base::FILE_EXE, &chrome_path)); + CommandLine cmd_line(chrome_path); + + cmd_line.AppendSwitchASCII(switches::kProcessType, switches::kZygoteProcess); + + int fds[2]; + CHECK(socketpair(PF_UNIX, SOCK_SEQPACKET, 0, fds) == 0); + base::file_handle_mapping_vector fds_to_map; + fds_to_map.push_back(std::make_pair(fds[1], 3)); + + const CommandLine& browser_command_line = *CommandLine::ForCurrentProcess(); + if (browser_command_line.HasSwitch(switches::kZygoteCmdPrefix)) { + cmd_line.PrependWrapper( + browser_command_line.GetSwitchValueNative(switches::kZygoteCmdPrefix)); + } + // Append any switches from the browser process that need to be forwarded on + // to the zygote/renderers. + // Should this list be obtained from browser_render_process_host.cc? + static const char* kForwardSwitches[] = { + switches::kAllowSandboxDebugging, + switches::kLoggingLevel, + switches::kEnableLogging, // Support, e.g., --enable-logging=stderr. + switches::kV, + switches::kVModule, + switches::kUserDataDir, // Make logs go to the right file. + // Load (in-process) Pepper plugins in-process in the zygote pre-sandbox. + switches::kRegisterPepperPlugins, + switches::kDisableSeccompSandbox, + switches::kEnableSeccompSandbox, + }; + cmd_line.CopySwitchesFrom(browser_command_line, kForwardSwitches, + arraysize(kForwardSwitches)); + + sandbox_binary_ = sandbox_cmd.c_str(); + struct stat st; + + if (!sandbox_cmd.empty() && stat(sandbox_binary_.c_str(), &st) == 0) { + if (access(sandbox_binary_.c_str(), X_OK) == 0 && + (st.st_uid == 0) && + (st.st_mode & S_ISUID) && + (st.st_mode & S_IXOTH)) { + using_suid_sandbox_ = true; + cmd_line.PrependWrapper(sandbox_binary_); + + SaveSUIDUnsafeEnvironmentVariables(); + } else { + LOG(FATAL) << "The SUID sandbox helper binary was found, but is not " + "configured correctly. Rather than run without sandboxing " + "I'm aborting now. You need to make sure that " + << sandbox_binary_ << " is mode 4755 and owned by root."; + } + } + + // Start up the sandbox host process and get the file descriptor for the + // renderers to talk to it. + const int sfd = RenderSandboxHostLinux::GetInstance()->GetRendererSocket(); + fds_to_map.push_back(std::make_pair(sfd, 5)); + + int dummy_fd = -1; + if (using_suid_sandbox_) { + dummy_fd = socket(PF_UNIX, SOCK_DGRAM, 0); + CHECK(dummy_fd >= 0); + fds_to_map.push_back(std::make_pair(dummy_fd, 7)); + } + + base::ProcessHandle process; + base::LaunchApp(cmd_line.argv(), fds_to_map, false, &process); + CHECK(process != -1) << "Failed to launch zygote process"; + + if (using_suid_sandbox_) { + // In the SUID sandbox, the real zygote is forked from the sandbox. + // We need to look for it. + // But first, wait for the zygote to tell us it's running. + // The sending code is in chrome/browser/zygote_main_linux.cc. + std::vector<int> fds_vec; + const int kExpectedLength = sizeof(kZygoteMagic); + char buf[kExpectedLength]; + const ssize_t len = UnixDomainSocket::RecvMsg(fds[0], buf, sizeof(buf), + &fds_vec); + CHECK(len == kExpectedLength) << "Incorrect zygote magic length"; + CHECK(0 == strcmp(buf, kZygoteMagic)) << "Incorrect zygote magic"; + + std::string inode_output; + ino_t inode = 0; + // Figure out the inode for |dummy_fd|, close |dummy_fd| on our end, + // and find the zygote process holding |dummy_fd|. + if (base::FileDescriptorGetInode(&inode, dummy_fd)) { + close(dummy_fd); + std::vector<std::string> get_inode_cmdline; + get_inode_cmdline.push_back(sandbox_binary_); + get_inode_cmdline.push_back(base::kFindInodeSwitch); + get_inode_cmdline.push_back(base::Int64ToString(inode)); + CommandLine get_inode_cmd(get_inode_cmdline); + if (base::GetAppOutput(get_inode_cmd, &inode_output)) { + base::StringToInt(inode_output, &pid_); + } + } + CHECK(pid_ > 0) << "Did not find zygote process (using sandbox binary " + << sandbox_binary_ << ")"; + + if (process != pid_) { + // Reap the sandbox. + ProcessWatcher::EnsureProcessGetsReaped(process); + } + } else { + // Not using the SUID sandbox. + pid_ = process; + } + + close(fds[1]); + control_fd_ = fds[0]; + + Pickle pickle; + pickle.WriteInt(kCmdGetSandboxStatus); + std::vector<int> empty_fds; + if (!UnixDomainSocket::SendMsg(control_fd_, pickle.data(), pickle.size(), + empty_fds)) + LOG(FATAL) << "Cannot communicate with zygote"; + // We don't wait for the reply. We'll read it in ReadReply. +} + +ssize_t ZygoteHost::ReadReply(void* buf, size_t buf_len) { + // At startup we send a kCmdGetSandboxStatus request to the zygote, but don't + // wait for the reply. Thus, the first time that we read from the zygote, we + // get the reply to that request. + if (!have_read_sandbox_status_word_) { + if (HANDLE_EINTR(read(control_fd_, &sandbox_status_, + sizeof(sandbox_status_))) != + sizeof(sandbox_status_)) { + return -1; + } + have_read_sandbox_status_word_ = true; + } + + return HANDLE_EINTR(read(control_fd_, buf, buf_len)); +} + +pid_t ZygoteHost::ForkRenderer( + const std::vector<std::string>& argv, + const base::GlobalDescriptors::Mapping& mapping) { + DCHECK(init_); + Pickle pickle; + + pickle.WriteInt(kCmdFork); + pickle.WriteInt(argv.size()); + for (std::vector<std::string>::const_iterator + i = argv.begin(); i != argv.end(); ++i) + pickle.WriteString(*i); + + pickle.WriteInt(mapping.size()); + + std::vector<int> fds; + for (base::GlobalDescriptors::Mapping::const_iterator + i = mapping.begin(); i != mapping.end(); ++i) { + pickle.WriteUInt32(i->first); + fds.push_back(i->second); + } + + pid_t pid; + { + base::AutoLock lock(control_lock_); + if (!UnixDomainSocket::SendMsg(control_fd_, pickle.data(), pickle.size(), + fds)) + return base::kNullProcessHandle; + + if (ReadReply(&pid, sizeof(pid)) != sizeof(pid)) + return base::kNullProcessHandle; + if (pid <= 0) + return base::kNullProcessHandle; + } + + const int kRendererScore = 5; + AdjustRendererOOMScore(pid, kRendererScore); + + return pid; +} + +void ZygoteHost::AdjustRendererOOMScore(base::ProcessHandle pid, int score) { + // 1) You can't change the oom_adj of a non-dumpable process (EPERM) unless + // you're root. Because of this, we can't set the oom_adj from the browser + // process. + // + // 2) We can't set the oom_adj before entering the sandbox because the + // zygote is in the sandbox and the zygote is as critical as the browser + // process. Its oom_adj value shouldn't be changed. + // + // 3) A non-dumpable process can't even change its own oom_adj because it's + // root owned 0644. The sandboxed processes don't even have /proc, but one + // could imagine passing in a descriptor from outside. + // + // So, in the normal case, we use the SUID binary to change it for us. + // However, Fedora (and other SELinux systems) don't like us touching other + // process's oom_adj values + // (https://bugzilla.redhat.com/show_bug.cgi?id=581256). + // + // The offical way to get the SELinux mode is selinux_getenforcemode, but I + // don't want to add another library to the build as it's sure to cause + // problems with other, non-SELinux distros. + // + // So we just check for /selinux. This isn't foolproof, but it's not bad + // and it's easy. + + static bool selinux; + static bool selinux_valid = false; + + if (!selinux_valid) { + selinux = access("/selinux", X_OK) == 0; + selinux_valid = true; + } + + if (using_suid_sandbox_ && !selinux) { + base::ProcessHandle sandbox_helper_process; + std::vector<std::string> adj_oom_score_cmdline; + + adj_oom_score_cmdline.push_back(sandbox_binary_); + adj_oom_score_cmdline.push_back(base::kAdjustOOMScoreSwitch); + adj_oom_score_cmdline.push_back(base::Int64ToString(pid)); + adj_oom_score_cmdline.push_back(base::IntToString(score)); + CommandLine adj_oom_score_cmd(adj_oom_score_cmdline); + if (base::LaunchApp(adj_oom_score_cmd, false, true, + &sandbox_helper_process)) { + ProcessWatcher::EnsureProcessGetsReaped(sandbox_helper_process); + } + } else if (!using_suid_sandbox_) { + if (!base::AdjustOOMScore(pid, score)) + PLOG(ERROR) << "Failed to adjust OOM score of renderer with pid " << pid; + } +} + +void ZygoteHost::EnsureProcessTerminated(pid_t process) { + DCHECK(init_); + Pickle pickle; + + pickle.WriteInt(kCmdReap); + pickle.WriteInt(process); + + if (HANDLE_EINTR(write(control_fd_, pickle.data(), pickle.size())) < 0) + PLOG(ERROR) << "write"; +} + +base::TerminationStatus ZygoteHost::GetTerminationStatus( + base::ProcessHandle handle, + int* exit_code) { + DCHECK(init_); + Pickle pickle; + pickle.WriteInt(kCmdGetTerminationStatus); + pickle.WriteInt(handle); + + // Set this now to handle the early termination cases. + if (exit_code) + *exit_code = ResultCodes::NORMAL_EXIT; + + static const unsigned kMaxMessageLength = 128; + char buf[kMaxMessageLength]; + ssize_t len; + { + base::AutoLock lock(control_lock_); + if (HANDLE_EINTR(write(control_fd_, pickle.data(), pickle.size())) < 0) + PLOG(ERROR) << "write"; + + len = ReadReply(buf, sizeof(buf)); + } + + if (len == -1) { + LOG(WARNING) << "Error reading message from zygote: " << errno; + return base::TERMINATION_STATUS_NORMAL_TERMINATION; + } else if (len == 0) { + LOG(WARNING) << "Socket closed prematurely."; + return base::TERMINATION_STATUS_NORMAL_TERMINATION; + } + + Pickle read_pickle(buf, len); + int status, tmp_exit_code; + void* iter = NULL; + if (!read_pickle.ReadInt(&iter, &status) || + !read_pickle.ReadInt(&iter, &tmp_exit_code)) { + LOG(WARNING) << "Error parsing GetTerminationStatus response from zygote."; + return base::TERMINATION_STATUS_NORMAL_TERMINATION; + } + + if (exit_code) + *exit_code = tmp_exit_code; + + return static_cast<base::TerminationStatus>(status); +} diff --git a/content/browser/zygote_host_linux.h b/content/browser/zygote_host_linux.h new file mode 100644 index 0000000..5ead5f5 --- /dev/null +++ b/content/browser/zygote_host_linux.h @@ -0,0 +1,98 @@ +// 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_ZYGOTE_HOST_LINUX_H_ +#define CONTENT_BROWSER_ZYGOTE_HOST_LINUX_H_ +#pragma once + +#include <unistd.h> + +#include <string> +#include <vector> + +#include "base/global_descriptors_posix.h" +#include "base/process.h" +#include "base/process_util.h" +#include "base/synchronization/lock.h" + +template<typename Type> +struct DefaultSingletonTraits; + +static const char kZygoteMagic[] = "ZYGOTE_OK"; + +// http://code.google.com/p/chromium/wiki/LinuxZygote + +// The zygote host is the interface, in the browser process, to the zygote +// process. +class ZygoteHost { + public: + // Returns the singleton instance. + static ZygoteHost* GetInstance(); + + void Init(const std::string& sandbox_cmd); + + // Tries to start a renderer process. Returns its pid on success, otherwise + // base::kNullProcessHandle; + pid_t ForkRenderer(const std::vector<std::string>& command_line, + const base::GlobalDescriptors::Mapping& mapping); + void EnsureProcessTerminated(pid_t process); + + // Get the termination status (and, optionally, the exit code) of + // the process. |exit_code| is set to the exit code of the child + // process. (|exit_code| may be NULL.) + base::TerminationStatus GetTerminationStatus(base::ProcessHandle handle, + int* exit_code); + + // These are the command codes used on the wire between the browser and the + // zygote. + enum { + kCmdFork = 0, // Fork off a new renderer. + kCmdReap = 1, // Reap a renderer child. + kCmdGetTerminationStatus = 2, // Check what happend to a child process. + kCmdGetSandboxStatus = 3, // Read a bitmask of kSandbox* + }; + + // These form a bitmask which describes the conditions of the sandbox that + // the zygote finds itself in. + enum { + kSandboxSUID = 1 << 0, // SUID sandbox active + kSandboxPIDNS = 1 << 1, // SUID sandbox is using the PID namespace + kSandboxNetNS = 1 << 2, // SUID sandbox is using the network namespace + kSandboxSeccomp = 1 << 3, // seccomp sandbox active. + }; + + pid_t pid() const { return pid_; } + + // Returns an int which is a bitmask of kSandbox* values. Only valid after + // the first render has been forked. + int sandbox_status() const { + if (have_read_sandbox_status_word_) + return sandbox_status_; + return 0; + } + + // Adjust the OOM score of the given renderer's PID. + void AdjustRendererOOMScore(base::ProcessHandle process_handle, int score); + + private: + friend struct DefaultSingletonTraits<ZygoteHost>; + ZygoteHost(); + ~ZygoteHost(); + + ssize_t ReadReply(void* buf, size_t buflen); + + int control_fd_; // the socket to the zygote + // A lock protecting all communication with the zygote. This lock must be + // acquired before sending a command and released after the result has been + // received. + base::Lock control_lock_; + pid_t pid_; + bool init_; + bool using_suid_sandbox_; + std::string sandbox_binary_; + bool have_read_sandbox_status_word_; + int sandbox_status_; +}; + +#endif // CONTENT_BROWSER_ZYGOTE_HOST_LINUX_H_ diff --git a/content/browser/zygote_main_linux.cc b/content/browser/zygote_main_linux.cc new file mode 100644 index 0000000..188ad34 --- /dev/null +++ b/content/browser/zygote_main_linux.cc @@ -0,0 +1,753 @@ +// 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 <dlfcn.h> +#include <fcntl.h> +#include <pthread.h> +#include <sys/epoll.h> +#include <sys/prctl.h> +#include <sys/signal.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <unistd.h> + +#if defined(CHROMIUM_SELINUX) +#include <selinux/selinux.h> +#include <selinux/context.h> +#endif + +#include "content/browser/zygote_host_linux.h" + +#include "base/basictypes.h" +#include "base/command_line.h" +#include "base/eintr_wrapper.h" +#include "base/file_path.h" +#include "base/global_descriptors_posix.h" +#include "base/hash_tables.h" +#include "base/linux_util.h" +#include "base/path_service.h" +#include "base/pickle.h" +#include "base/process_util.h" +#include "base/rand_util.h" +#include "base/scoped_ptr.h" +#include "base/sys_info.h" +#include "build/build_config.h" +#include "chrome/common/chrome_descriptors.h" +#include "chrome/common/chrome_switches.h" +#include "chrome/common/font_config_ipc_linux.h" +#include "chrome/common/main_function_params.h" +#include "chrome/common/pepper_plugin_registry.h" +#include "chrome/common/process_watcher.h" +#include "chrome/common/result_codes.h" +#include "chrome/common/sandbox_methods_linux.h" +#include "chrome/common/set_process_title.h" +#include "chrome/common/unix_domain_socket_posix.h" +#include "media/base/media.h" +#include "seccompsandbox/sandbox.h" +#include "skia/ext/SkFontHost_fontconfig_control.h" +#include "unicode/timezone.h" + +#if defined(ARCH_CPU_X86_FAMILY) && !defined(CHROMIUM_SELINUX) && \ + !defined(__clang__) +// The seccomp sandbox is enabled on all ia32 and x86-64 processor as long as +// we aren't using SELinux or clang. +#define SECCOMP_SANDBOX +#endif + +// http://code.google.com/p/chromium/wiki/LinuxZygote + +static const int kBrowserDescriptor = 3; +static const int kMagicSandboxIPCDescriptor = 5; +static const int kZygoteIdDescriptor = 7; +static bool g_suid_sandbox_active = false; +#if defined(SECCOMP_SANDBOX) +// |g_proc_fd| is used only by the seccomp sandbox. +static int g_proc_fd = -1; +#endif + +#if defined(CHROMIUM_SELINUX) +static void SELinuxTransitionToTypeOrDie(const char* type) { + security_context_t security_context; + if (getcon(&security_context)) + LOG(FATAL) << "Cannot get SELinux context"; + + context_t context = context_new(security_context); + context_type_set(context, type); + const int r = setcon(context_str(context)); + context_free(context); + freecon(security_context); + + if (r) { + LOG(FATAL) << "dynamic transition to type '" << type << "' failed. " + "(this binary has been built with SELinux support, but maybe " + "the policies haven't been loaded into the kernel?)"; + } +} +#endif // CHROMIUM_SELINUX + +// This is the object which implements the zygote. The ZygoteMain function, +// which is called from ChromeMain, simply constructs one of these objects and +// runs it. +class Zygote { + public: + explicit Zygote(int sandbox_flags) + : sandbox_flags_(sandbox_flags) { + } + + bool ProcessRequests() { + // A SOCK_SEQPACKET socket is installed in fd 3. We get commands from the + // browser on it. + // A SOCK_DGRAM is installed in fd 5. This is the sandbox IPC channel. + // See http://code.google.com/p/chromium/wiki/LinuxSandboxIPC + + // We need to accept SIGCHLD, even though our handler is a no-op because + // otherwise we cannot wait on children. (According to POSIX 2001.) + struct sigaction action; + memset(&action, 0, sizeof(action)); + action.sa_handler = SIGCHLDHandler; + CHECK(sigaction(SIGCHLD, &action, NULL) == 0); + + if (g_suid_sandbox_active) { + // Let the ZygoteHost know we are ready to go. + // The receiving code is in chrome/browser/zygote_host_linux.cc. + std::vector<int> empty; + bool r = UnixDomainSocket::SendMsg(kBrowserDescriptor, kZygoteMagic, + sizeof(kZygoteMagic), empty); + CHECK(r) << "Sending zygote magic failed"; + } + + for (;;) { + // This function call can return multiple times, once per fork(). + if (HandleRequestFromBrowser(kBrowserDescriptor)) + return true; + } + } + + private: + // See comment below, where sigaction is called. + static void SIGCHLDHandler(int signal) { } + + // --------------------------------------------------------------------------- + // Requests from the browser... + + // Read and process a request from the browser. Returns true if we are in a + // new process and thus need to unwind back into ChromeMain. + bool HandleRequestFromBrowser(int fd) { + std::vector<int> fds; + static const unsigned kMaxMessageLength = 1024; + char buf[kMaxMessageLength]; + const ssize_t len = UnixDomainSocket::RecvMsg(fd, buf, sizeof(buf), &fds); + + if (len == 0 || (len == -1 && errno == ECONNRESET)) { + // EOF from the browser. We should die. + _exit(0); + return false; + } + + if (len == -1) { + PLOG(ERROR) << "Error reading message from browser"; + return false; + } + + Pickle pickle(buf, len); + void* iter = NULL; + + int kind; + if (pickle.ReadInt(&iter, &kind)) { + switch (kind) { + case ZygoteHost::kCmdFork: + // This function call can return multiple times, once per fork(). + return HandleForkRequest(fd, pickle, iter, fds); + case ZygoteHost::kCmdReap: + if (!fds.empty()) + break; + HandleReapRequest(fd, pickle, iter); + return false; + case ZygoteHost::kCmdGetTerminationStatus: + if (!fds.empty()) + break; + HandleGetTerminationStatus(fd, pickle, iter); + return false; + case ZygoteHost::kCmdGetSandboxStatus: + HandleGetSandboxStatus(fd, pickle, iter); + return false; + default: + NOTREACHED(); + break; + } + } + + LOG(WARNING) << "Error parsing message from browser"; + for (std::vector<int>::const_iterator + i = fds.begin(); i != fds.end(); ++i) + close(*i); + return false; + } + + void HandleReapRequest(int fd, const Pickle& pickle, void* iter) { + base::ProcessId child; + base::ProcessId actual_child; + + if (!pickle.ReadInt(&iter, &child)) { + LOG(WARNING) << "Error parsing reap request from browser"; + return; + } + + if (g_suid_sandbox_active) { + actual_child = real_pids_to_sandbox_pids[child]; + if (!actual_child) + return; + real_pids_to_sandbox_pids.erase(child); + } else { + actual_child = child; + } + + ProcessWatcher::EnsureProcessTerminated(actual_child); + } + + void HandleGetTerminationStatus(int fd, const Pickle& pickle, void* iter) { + base::ProcessHandle child; + + if (!pickle.ReadInt(&iter, &child)) { + LOG(WARNING) << "Error parsing GetTerminationStatus request " + << "from browser"; + return; + } + + base::TerminationStatus status; + int exit_code; + if (g_suid_sandbox_active) + child = real_pids_to_sandbox_pids[child]; + if (child) { + status = base::GetTerminationStatus(child, &exit_code); + } else { + // Assume that if we can't find the child in the sandbox, then + // it terminated normally. + status = base::TERMINATION_STATUS_NORMAL_TERMINATION; + exit_code = ResultCodes::NORMAL_EXIT; + } + + Pickle write_pickle; + write_pickle.WriteInt(static_cast<int>(status)); + write_pickle.WriteInt(exit_code); + ssize_t written = + HANDLE_EINTR(write(fd, write_pickle.data(), write_pickle.size())); + if (written != static_cast<ssize_t>(write_pickle.size())) + PLOG(ERROR) << "write"; + } + + // This is equivalent to fork(), except that, when using the SUID + // sandbox, it returns the real PID of the child process as it + // appears outside the sandbox, rather than returning the PID inside + // the sandbox. + int ForkWithRealPid() { + if (!g_suid_sandbox_active) + return fork(); + + int dummy_fd; + ino_t dummy_inode; + int pipe_fds[2] = { -1, -1 }; + base::ProcessId pid = 0; + + dummy_fd = socket(PF_UNIX, SOCK_DGRAM, 0); + if (dummy_fd < 0) { + LOG(ERROR) << "Failed to create dummy FD"; + goto error; + } + if (!base::FileDescriptorGetInode(&dummy_inode, dummy_fd)) { + LOG(ERROR) << "Failed to get inode for dummy FD"; + goto error; + } + if (pipe(pipe_fds) != 0) { + LOG(ERROR) << "Failed to create pipe"; + goto error; + } + + pid = fork(); + if (pid < 0) { + goto error; + } else if (pid == 0) { + // In the child process. + close(pipe_fds[1]); + char buffer[1]; + // Wait until the parent process has discovered our PID. We + // should not fork any child processes (which the seccomp + // sandbox does) until then, because that can interfere with the + // parent's discovery of our PID. + if (HANDLE_EINTR(read(pipe_fds[0], buffer, 1)) != 1 || + buffer[0] != 'x') { + LOG(FATAL) << "Failed to synchronise with parent zygote process"; + } + close(pipe_fds[0]); + close(dummy_fd); + return 0; + } else { + // In the parent process. + close(dummy_fd); + dummy_fd = -1; + close(pipe_fds[0]); + pipe_fds[0] = -1; + uint8_t reply_buf[512]; + Pickle request; + request.WriteInt(LinuxSandbox::METHOD_GET_CHILD_WITH_INODE); + request.WriteUInt64(dummy_inode); + + const ssize_t r = UnixDomainSocket::SendRecvMsg( + kMagicSandboxIPCDescriptor, reply_buf, sizeof(reply_buf), NULL, + request); + if (r == -1) { + LOG(ERROR) << "Failed to get child process's real PID"; + goto error; + } + + base::ProcessId real_pid; + Pickle reply(reinterpret_cast<char*>(reply_buf), r); + void* iter2 = NULL; + if (!reply.ReadInt(&iter2, &real_pid)) + goto error; + if (real_pid <= 0) { + // METHOD_GET_CHILD_WITH_INODE failed. Did the child die already? + LOG(ERROR) << "METHOD_GET_CHILD_WITH_INODE failed"; + goto error; + } + real_pids_to_sandbox_pids[real_pid] = pid; + if (HANDLE_EINTR(write(pipe_fds[1], "x", 1)) != 1) { + LOG(ERROR) << "Failed to synchronise with child process"; + goto error; + } + close(pipe_fds[1]); + return real_pid; + } + + error: + if (pid > 0) { + if (waitpid(pid, NULL, WNOHANG) == -1) + LOG(ERROR) << "Failed to wait for process"; + } + if (dummy_fd >= 0) + close(dummy_fd); + if (pipe_fds[0] >= 0) + close(pipe_fds[0]); + if (pipe_fds[1] >= 0) + close(pipe_fds[1]); + return -1; + } + + // Handle a 'fork' request from the browser: this means that the browser + // wishes to start a new renderer. + bool HandleForkRequest(int fd, const Pickle& pickle, void* iter, + std::vector<int>& fds) { + std::vector<std::string> args; + int argc, numfds; + base::GlobalDescriptors::Mapping mapping; + base::ProcessId child; + + if (!pickle.ReadInt(&iter, &argc)) + goto error; + + for (int i = 0; i < argc; ++i) { + std::string arg; + if (!pickle.ReadString(&iter, &arg)) + goto error; + args.push_back(arg); + } + + if (!pickle.ReadInt(&iter, &numfds)) + goto error; + if (numfds != static_cast<int>(fds.size())) + goto error; + + for (int i = 0; i < numfds; ++i) { + base::GlobalDescriptors::Key key; + if (!pickle.ReadUInt32(&iter, &key)) + goto error; + mapping.push_back(std::make_pair(key, fds[i])); + } + + mapping.push_back(std::make_pair( + static_cast<uint32_t>(kSandboxIPCChannel), kMagicSandboxIPCDescriptor)); + + child = ForkWithRealPid(); + + if (!child) { +#if defined(SECCOMP_SANDBOX) + // Try to open /proc/self/maps as the seccomp sandbox needs access to it + if (g_proc_fd >= 0) { + int proc_self_maps = openat(g_proc_fd, "self/maps", O_RDONLY); + if (proc_self_maps >= 0) { + SeccompSandboxSetProcSelfMaps(proc_self_maps); + } + close(g_proc_fd); + g_proc_fd = -1; + } +#endif + + close(kBrowserDescriptor); // our socket from the browser + if (g_suid_sandbox_active) + close(kZygoteIdDescriptor); // another socket from the browser + base::GlobalDescriptors::GetInstance()->Reset(mapping); + +#if defined(CHROMIUM_SELINUX) + SELinuxTransitionToTypeOrDie("chromium_renderer_t"); +#endif + + // Reset the process-wide command line to our new command line. + CommandLine::Reset(); + CommandLine::Init(0, NULL); + CommandLine::ForCurrentProcess()->InitFromArgv(args); + + // Update the process title. The argv was already cached by the call to + // SetProcessTitleFromCommandLine in ChromeMain, so we can pass NULL here + // (we don't have the original argv at this point). + SetProcessTitleFromCommandLine(NULL); + + // The fork() request is handled further up the call stack. + return true; + } else if (child < 0) { + LOG(ERROR) << "Zygote could not fork: " << errno; + goto error; + } + + for (std::vector<int>::const_iterator + i = fds.begin(); i != fds.end(); ++i) + close(*i); + + if (HANDLE_EINTR(write(fd, &child, sizeof(child))) < 0) + PLOG(ERROR) << "write"; + return false; + + error: + LOG(ERROR) << "Error parsing fork request from browser"; + for (std::vector<int>::const_iterator + i = fds.begin(); i != fds.end(); ++i) + close(*i); + return false; + } + + bool HandleGetSandboxStatus(int fd, const Pickle& pickle, void* iter) { + if (HANDLE_EINTR(write(fd, &sandbox_flags_, sizeof(sandbox_flags_)) != + sizeof(sandbox_flags_))) { + PLOG(ERROR) << "write"; + } + + return false; + } + + // In the SUID sandbox, we try to use a new PID namespace. Thus the PIDs + // fork() returns are not the real PIDs, so we need to map the Real PIDS + // into the sandbox PID namespace. + typedef base::hash_map<base::ProcessHandle, base::ProcessHandle> ProcessMap; + ProcessMap real_pids_to_sandbox_pids; + + const int sandbox_flags_; +}; + +// With SELinux we can carve out a precise sandbox, so we don't have to play +// with intercepting libc calls. +#if !defined(CHROMIUM_SELINUX) + +static void ProxyLocaltimeCallToBrowser(time_t input, struct tm* output, + char* timezone_out, + size_t timezone_out_len) { + Pickle request; + request.WriteInt(LinuxSandbox::METHOD_LOCALTIME); + request.WriteString( + std::string(reinterpret_cast<char*>(&input), sizeof(input))); + + uint8_t reply_buf[512]; + const ssize_t r = UnixDomainSocket::SendRecvMsg( + kMagicSandboxIPCDescriptor, reply_buf, sizeof(reply_buf), NULL, request); + if (r == -1) { + memset(output, 0, sizeof(struct tm)); + return; + } + + Pickle reply(reinterpret_cast<char*>(reply_buf), r); + void* iter = NULL; + std::string result, timezone; + if (!reply.ReadString(&iter, &result) || + !reply.ReadString(&iter, &timezone) || + result.size() != sizeof(struct tm)) { + memset(output, 0, sizeof(struct tm)); + return; + } + + memcpy(output, result.data(), sizeof(struct tm)); + if (timezone_out_len) { + const size_t copy_len = std::min(timezone_out_len - 1, timezone.size()); + memcpy(timezone_out, timezone.data(), copy_len); + timezone_out[copy_len] = 0; + output->tm_zone = timezone_out; + } else { + output->tm_zone = NULL; + } +} + +static bool g_am_zygote_or_renderer = false; + +// Sandbox interception of libc calls. +// +// Because we are running in a sandbox certain libc calls will fail (localtime +// being the motivating example - it needs to read /etc/localtime). We need to +// intercept these calls and proxy them to the browser. However, these calls +// may come from us or from our libraries. In some cases we can't just change +// our code. +// +// It's for these cases that we have the following setup: +// +// We define global functions for those functions which we wish to override. +// Since we will be first in the dynamic resolution order, the dynamic linker +// will point callers to our versions of these functions. However, we have the +// same binary for both the browser and the renderers, which means that our +// overrides will apply in the browser too. +// +// The global |g_am_zygote_or_renderer| is true iff we are in a zygote or +// renderer process. It's set in ZygoteMain and inherited by the renderers when +// they fork. (This means that it'll be incorrect for global constructor +// functions and before ZygoteMain is called - beware). +// +// Our replacement functions can check this global and either proxy +// the call to the browser over the sandbox IPC +// (http://code.google.com/p/chromium/wiki/LinuxSandboxIPC) or they can use +// dlsym with RTLD_NEXT to resolve the symbol, ignoring any symbols in the +// current module. +// +// Other avenues: +// +// Our first attempt involved some assembly to patch the GOT of the current +// module. This worked, but was platform specific and doesn't catch the case +// where a library makes a call rather than current module. +// +// We also considered patching the function in place, but this would again by +// platform specific and the above technique seems to work well enough. + +typedef struct tm* (*LocaltimeFunction)(const time_t* timep); +typedef struct tm* (*LocaltimeRFunction)(const time_t* timep, + struct tm* result); + +static pthread_once_t g_libc_localtime_funcs_guard = PTHREAD_ONCE_INIT; +static LocaltimeFunction g_libc_localtime; +static LocaltimeRFunction g_libc_localtime_r; + +static void InitLibcLocaltimeFunctions() { + g_libc_localtime = reinterpret_cast<LocaltimeFunction>( + dlsym(RTLD_NEXT, "localtime")); + g_libc_localtime_r = reinterpret_cast<LocaltimeRFunction>( + dlsym(RTLD_NEXT, "localtime_r")); + + if (!g_libc_localtime || !g_libc_localtime_r) { + // http://code.google.com/p/chromium/issues/detail?id=16800 + // + // Nvidia's libGL.so overrides dlsym for an unknown reason and replaces + // it with a version which doesn't work. In this case we'll get a NULL + // result. There's not a lot we can do at this point, so we just bodge it! + LOG(ERROR) << "Your system is broken: dlsym doesn't work! This has been " + "reported to be caused by Nvidia's libGL. You should expect" + " time related functions to misbehave. " + "http://code.google.com/p/chromium/issues/detail?id=16800"; + } + + if (!g_libc_localtime) + g_libc_localtime = gmtime; + if (!g_libc_localtime_r) + g_libc_localtime_r = gmtime_r; +} + +struct tm* localtime(const time_t* timep) { + if (g_am_zygote_or_renderer) { + static struct tm time_struct; + static char timezone_string[64]; + ProxyLocaltimeCallToBrowser(*timep, &time_struct, timezone_string, + sizeof(timezone_string)); + return &time_struct; + } else { + CHECK_EQ(0, pthread_once(&g_libc_localtime_funcs_guard, + InitLibcLocaltimeFunctions)); + return g_libc_localtime(timep); + } +} + +struct tm* localtime_r(const time_t* timep, struct tm* result) { + if (g_am_zygote_or_renderer) { + ProxyLocaltimeCallToBrowser(*timep, result, NULL, 0); + return result; + } else { + CHECK_EQ(0, pthread_once(&g_libc_localtime_funcs_guard, + InitLibcLocaltimeFunctions)); + return g_libc_localtime_r(timep, result); + } +} + +#endif // !CHROMIUM_SELINUX + +// This function triggers the static and lazy construction of objects that need +// to be created before imposing the sandbox. +static void PreSandboxInit() { + base::RandUint64(); + + base::SysInfo::MaxSharedMemorySize(); + + // ICU DateFormat class (used in base/time_format.cc) needs to get the + // Olson timezone ID by accessing the zoneinfo files on disk. After + // TimeZone::createDefault is called once here, the timezone ID is + // cached and there's no more need to access the file system. + scoped_ptr<icu::TimeZone> zone(icu::TimeZone::createDefault()); + + FilePath module_path; + if (PathService::Get(base::DIR_MODULE, &module_path)) + media::InitializeMediaLibrary(module_path); + + // Ensure access to the Pepper plugins before the sandbox is turned on. + PepperPluginRegistry::PreloadModules(); +} + +#if !defined(CHROMIUM_SELINUX) +static bool EnterSandbox() { + // The SUID sandbox sets this environment variable to a file descriptor + // over which we can signal that we have completed our startup and can be + // chrooted. + const char* const sandbox_fd_string = getenv("SBX_D"); + + if (sandbox_fd_string) { + // Use the SUID sandbox. This still allows the seccomp sandbox to + // be enabled by the process later. + g_suid_sandbox_active = true; + + char* endptr; + const long fd_long = strtol(sandbox_fd_string, &endptr, 10); + if (!*sandbox_fd_string || *endptr || fd_long < 0 || fd_long > INT_MAX) + return false; + const int fd = fd_long; + + PreSandboxInit(); + + static const char kMsgChrootMe = 'C'; + static const char kMsgChrootSuccessful = 'O'; + + if (HANDLE_EINTR(write(fd, &kMsgChrootMe, 1)) != 1) { + LOG(ERROR) << "Failed to write to chroot pipe: " << errno; + return false; + } + + // We need to reap the chroot helper process in any event: + wait(NULL); + + char reply; + if (HANDLE_EINTR(read(fd, &reply, 1)) != 1) { + LOG(ERROR) << "Failed to read from chroot pipe: " << errno; + return false; + } + + if (reply != kMsgChrootSuccessful) { + LOG(ERROR) << "Error code reply from chroot helper"; + return false; + } + + SkiaFontConfigSetImplementation( + new FontConfigIPC(kMagicSandboxIPCDescriptor)); + + // Previously, we required that the binary be non-readable. This causes the + // kernel to mark the process as non-dumpable at startup. The thinking was + // that, although we were putting the renderers into a PID namespace (with + // the SUID sandbox), they would nonetheless be in the /same/ PID + // namespace. So they could ptrace each other unless they were non-dumpable. + // + // If the binary was readable, then there would be a window between process + // startup and the point where we set the non-dumpable flag in which a + // compromised renderer could ptrace attach. + // + // However, now that we have a zygote model, only the (trusted) zygote + // exists at this point and we can set the non-dumpable flag which is + // inherited by all our renderer children. + // + // Note: a non-dumpable process can't be debugged. To debug sandbox-related + // issues, one can specify --allow-sandbox-debugging to let the process be + // dumpable. + const CommandLine& command_line = *CommandLine::ForCurrentProcess(); + if (!command_line.HasSwitch(switches::kAllowSandboxDebugging)) { + prctl(PR_SET_DUMPABLE, 0, 0, 0, 0); + if (prctl(PR_GET_DUMPABLE, 0, 0, 0, 0)) { + LOG(ERROR) << "Failed to set non-dumpable flag"; + return false; + } + } + } else if (switches::SeccompSandboxEnabled()) { + PreSandboxInit(); + SkiaFontConfigSetImplementation( + new FontConfigIPC(kMagicSandboxIPCDescriptor)); + } else { + SkiaFontConfigUseDirectImplementation(); + } + + return true; +} +#else // CHROMIUM_SELINUX + +static bool EnterSandbox() { + PreSandboxInit(); + SkiaFontConfigUseIPCImplementation(kMagicSandboxIPCDescriptor); + return true; +} + +#endif // CHROMIUM_SELINUX + +bool ZygoteMain(const MainFunctionParams& params) { +#if !defined(CHROMIUM_SELINUX) + g_am_zygote_or_renderer = true; +#endif + +#if defined(SECCOMP_SANDBOX) + // The seccomp sandbox needs access to files in /proc, which might be denied + // after one of the other sandboxes have been started. So, obtain a suitable + // file handle in advance. + if (switches::SeccompSandboxEnabled()) { + g_proc_fd = open("/proc", O_DIRECTORY | O_RDONLY); + if (g_proc_fd < 0) { + LOG(ERROR) << "WARNING! Cannot access \"/proc\". Disabling seccomp " + "sandboxing."; + } + } +#endif // SECCOMP_SANDBOX + + // Turn on the SELinux or SUID sandbox + if (!EnterSandbox()) { + LOG(FATAL) << "Failed to enter sandbox. Fail safe abort. (errno: " + << errno << ")"; + return false; + } + + int sandbox_flags = 0; + if (getenv("SBX_D")) + sandbox_flags |= ZygoteHost::kSandboxSUID; + if (getenv("SBX_PID_NS")) + sandbox_flags |= ZygoteHost::kSandboxPIDNS; + if (getenv("SBX_NET_NS")) + sandbox_flags |= ZygoteHost::kSandboxNetNS; + +#if defined(SECCOMP_SANDBOX) + // The seccomp sandbox will be turned on when the renderers start. But we can + // already check if sufficient support is available so that we only need to + // print one error message for the entire browser session. + if (g_proc_fd >= 0 && switches::SeccompSandboxEnabled()) { + if (!SupportsSeccompSandbox(g_proc_fd)) { + // There are a good number of users who cannot use the seccomp sandbox + // (e.g. because their distribution does not enable seccomp mode by + // default). While we would prefer to deny execution in this case, it + // seems more realistic to continue in degraded mode. + LOG(ERROR) << "WARNING! This machine lacks support needed for the " + "Seccomp sandbox. Running renderers with Seccomp " + "sandboxing disabled."; + } else { + VLOG(1) << "Enabling experimental Seccomp sandbox."; + sandbox_flags |= ZygoteHost::kSandboxSeccomp; + } + } +#endif // SECCOMP_SANDBOX + + Zygote zygote(sandbox_flags); + // This function call can return multiple times, once per fork(). + return zygote.ProcessRequests(); +} diff --git a/content/content_browser.gypi b/content/content_browser.gypi index 4a5a09a..f497bf5 100644 --- a/content/content_browser.gypi +++ b/content/content_browser.gypi @@ -46,6 +46,23 @@ 'browser/cross_site_request_manager.h', 'browser/disposition_utils.cc', 'browser/disposition_utils.h', + 'browser/gpu_blacklist.cc', + 'browser/gpu_blacklist.h', + 'browser/gpu_process_host.cc', + 'browser/gpu_process_host.h', + 'browser/host_zoom_map.cc', + 'browser/host_zoom_map.h', + 'browser/mime_registry_message_filter.cc', + 'browser/mime_registry_message_filter.h', + 'browser/modal_html_dialog_delegate.cc', + 'browser/modal_html_dialog_delegate.h', + 'browser/ppapi_plugin_process_host.cc', + 'browser/ppapi_plugin_process_host.h', + 'browser/plugin_process_host.cc', + 'browser/plugin_process_host.h', + 'browser/plugin_process_host_mac.cc', + 'browser/plugin_service.cc', + 'browser/plugin_service.h', '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', @@ -156,6 +173,9 @@ 'browser/tab_contents/tab_contents_observer.h', 'browser/tab_contents/tab_contents_view.cc', 'browser/tab_contents/tab_contents_view.h', + 'browser/zygote_host_linux.cc', + 'browser/zygote_host_linux.h', + 'browser/zygote_main_linux.cc', ], 'conditions': [ ['OS=="win"', { @@ -177,6 +197,14 @@ 'browser/certificate_manager_model.h', ], }], + ['OS=="mac"', { + 'link_settings': { + 'mac_bundle_resources': [ + 'browser/gpu.sb', + 'browser/worker.sb', + ], + }, + }], ], }, ], |