diff options
author | hbono@chromium.org <hbono@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-09-03 06:39:01 +0000 |
---|---|---|
committer | hbono@chromium.org <hbono@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-09-03 06:39:01 +0000 |
commit | 19176d47a9da4dd0d25470360564762fcf0061be (patch) | |
tree | a53124707356a628931c44bd1407d48c0dfeebd9 | |
parent | f46a4015b76c47e69b869c53dec4f2d812a111de (diff) | |
download | chromium_src-19176d47a9da4dd0d25470360564762fcf0061be.zip chromium_src-19176d47a9da4dd0d25470360564762fcf0061be.tar.gz chromium_src-19176d47a9da4dd0d25470360564762fcf0061be.tar.bz2 |
In-te-grate hy-phen-ator to con-tent.
This change integrates the Hyphenator class to content so Chrome can use it. This change basically consists of two parts:
* Adds a couple of IPC messages so a renderer asks a browser to open hyphenation dictionaries;
* Adds a HyphenatorMessageFilter class, which opens hyphenation dictionaries in a browser process;
* Changes the Hyphenator class to send IPC messages to open hyphenation dictionaries.
BUG=47083
TEST=HyphenatorTest.SetDictionary,HyphenatorMessageFilterTest.OpenDictionary
Review URL: https://chromiumcodereview.appspot.com/10854245
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@154663 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r-- | content/browser/hyphenator/hyphenator_message_filter.cc | 110 | ||||
-rw-r--r-- | content/browser/hyphenator/hyphenator_message_filter.h | 77 | ||||
-rw-r--r-- | content/browser/hyphenator/hyphenator_message_filter_unittest.cc | 154 | ||||
-rw-r--r-- | content/browser/renderer_host/render_process_host_impl.cc | 4 | ||||
-rw-r--r-- | content/common/content_message_generator.h | 1 | ||||
-rw-r--r-- | content/common/hyphenator_messages.h | 24 | ||||
-rw-r--r-- | content/content_browser.gypi | 2 | ||||
-rw-r--r-- | content/content_tests.gypi | 1 | ||||
-rw-r--r-- | content/public/test/mock_render_process_host.cc | 4 | ||||
-rw-r--r-- | content/public/test/mock_render_thread.cc | 13 | ||||
-rw-r--r-- | content/public/test/mock_render_thread.h | 3 | ||||
-rw-r--r-- | content/renderer/hyphenator/hyphenator.cc | 51 | ||||
-rw-r--r-- | content/renderer/hyphenator/hyphenator.h | 20 | ||||
-rw-r--r-- | content/renderer/hyphenator/hyphenator_unittest.cc | 100 | ||||
-rw-r--r-- | content/renderer/renderer_webkitplatformsupport_impl.cc | 33 | ||||
-rw-r--r-- | content/renderer/renderer_webkitplatformsupport_impl.h | 8 | ||||
-rw-r--r-- | ipc/ipc_message_utils.h | 1 |
17 files changed, 599 insertions, 7 deletions
diff --git a/content/browser/hyphenator/hyphenator_message_filter.cc b/content/browser/hyphenator/hyphenator_message_filter.cc new file mode 100644 index 0000000..b8e13ab --- /dev/null +++ b/content/browser/hyphenator/hyphenator_message_filter.cc @@ -0,0 +1,110 @@ +// Copyright (c) 2012 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/base_paths.h" +#include "base/bind.h" +#include "base/logging.h" +#include "base/path_service.h" +#include "base/string16.h" +#include "content/browser/hyphenator/hyphenator_message_filter.h" +#include "content/common/hyphenator_messages.h" +#include "content/public/browser/browser_thread.h" +#include "content/public/browser/render_process_host.h" +#include "content/public/common/content_paths.h" + +namespace { + +// A helper function that closes the specified file in the FILE thread. This +// function may be called after the HyphenatorMessageFilter object that owns the +// specified file is deleted, i.e. this function must not depend on the object. +void CloseDictionary(base::PlatformFile file) { + DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::FILE)); + base::ClosePlatformFile(file); +} + +} // namespace + +namespace content { + +HyphenatorMessageFilter::HyphenatorMessageFilter( + content::RenderProcessHost* render_process_host) + : render_process_host_(render_process_host), + dictionary_file_(base::kInvalidPlatformFileValue), + weak_factory_(ALLOW_THIS_IN_INITIALIZER_LIST(this)) { +} + +HyphenatorMessageFilter::~HyphenatorMessageFilter() { + // Post a FILE task that deletes the dictionary file. This message filter is + // usually deleted on the IO thread, which does not allow file operations. + if (dictionary_file_ != base::kInvalidPlatformFileValue) { + content::BrowserThread::PostTask( + BrowserThread::FILE, + FROM_HERE, + base::Bind(&CloseDictionary, dictionary_file_)); + } +} + +void HyphenatorMessageFilter::SetDictionaryBase(const FilePath& base) { + dictionary_base_ = base; +} + +void HyphenatorMessageFilter::OverrideThreadForMessage( + const IPC::Message& message, + BrowserThread::ID* thread) { + if (message.type() == HyphenatorHostMsg_OpenDictionary::ID) + *thread = BrowserThread::UI; +} + +bool HyphenatorMessageFilter::OnMessageReceived( + const IPC::Message& message, + bool* message_was_ok) { + bool handled = true; + IPC_BEGIN_MESSAGE_MAP_EX(HyphenatorMessageFilter, + message, + *message_was_ok) + IPC_MESSAGE_HANDLER(HyphenatorHostMsg_OpenDictionary, OnOpenDictionary) + IPC_MESSAGE_UNHANDLED(handled = false) + IPC_END_MESSAGE_MAP_EX() + return handled; +} + +void HyphenatorMessageFilter::OnOpenDictionary(const string16& locale) { + if (dictionary_file_ != base::kInvalidPlatformFileValue) { + SendDictionary(); + return; + } + content::BrowserThread::PostTaskAndReply( + content::BrowserThread::FILE, + FROM_HERE, + base::Bind(&HyphenatorMessageFilter::OpenDictionary, this, locale), + base::Bind(&HyphenatorMessageFilter::SendDictionary, + weak_factory_.GetWeakPtr())); +} + +void HyphenatorMessageFilter::OpenDictionary(const string16& locale) { + DCHECK(dictionary_file_ == base::kInvalidPlatformFileValue); + + if (dictionary_base_.empty()) + PathService::Get(base::DIR_EXE, &dictionary_base_); + std::string rule_file = locale.empty() ? "en-US" : UTF16ToASCII(locale); + rule_file.append("-1-0.dic"); + FilePath rule_path = dictionary_base_.AppendASCII(rule_file); + dictionary_file_ = base::CreatePlatformFile( + rule_path, + base::PLATFORM_FILE_READ | base::PLATFORM_FILE_OPEN, + NULL, NULL); +} + +void HyphenatorMessageFilter::SendDictionary() { + IPC::PlatformFileForTransit file = IPC::InvalidPlatformFileForTransit(); + if (dictionary_file_ != base::kInvalidPlatformFileValue) { + file = IPC::GetFileHandleForProcess( + dictionary_file_, + render_process_host_->GetHandle(), + false); + } + Send(new HyphenatorMsg_SetDictionary(file)); +} + +} // namespace content diff --git a/content/browser/hyphenator/hyphenator_message_filter.h b/content/browser/hyphenator/hyphenator_message_filter.h new file mode 100644 index 0000000..2421610 --- /dev/null +++ b/content/browser/hyphenator/hyphenator_message_filter.h @@ -0,0 +1,77 @@ +// Copyright (c) 2012 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_HYPHENATOR_HYPHENATOR_MESSAGE_FILTER_H_ +#define CONTENT_BROWSER_HYPHENATOR_HYPHENATOR_MESSAGE_FILTER_H_ + +#include "base/compiler_specific.h" +#include "base/file_path.h" +#include "base/memory/weak_ptr.h" +#include "base/platform_file.h" +#include "content/common/content_export.h" +#include "content/public/browser/browser_message_filter.h" + +namespace content { +class RenderProcessHost; + +// This class is a message filter that handles a HyphenatorHost message. When +// this class receives a HyphenatorHostMsg_OpenDictionary message, it opens the +// specified dictionary and sends its file handle. +class CONTENT_EXPORT HyphenatorMessageFilter + : public content::BrowserMessageFilter { + public: + explicit HyphenatorMessageFilter( + content::RenderProcessHost* render_process_host); + + // Changes the directory that includes dictionary files. This function + // provides a method that allows applications to change the directory + // containing hyphenation dictionaries. When a renderer requests a hyphnation + // dictionary, this class appends a file name (which consists of a locale, a + // version number, and an extension) and use it as a dictionary file. + void SetDictionaryBase(const FilePath& directory); + + // content::BrowserMessageFilter implementation. + virtual void OverrideThreadForMessage( + const IPC::Message& message, + content::BrowserThread::ID* thread) OVERRIDE; + virtual bool OnMessageReceived(const IPC::Message& message, + bool* message_was_ok) OVERRIDE; + + private: + friend class TestHyphenatorMessageFilter; + + virtual ~HyphenatorMessageFilter(); + + virtual void OnOpenDictionary(const string16& locale); + + // Opens a hyphenation dictionary for the specified locale. When this locale + // is an empty string, this function uses US English ("en-US"). + void OpenDictionary(const string16& locale); + + // Sends the hyphenation dictionary file to a renderer in response to its + // request. If this class cannot open the specified dictionary file, this + // function sends an IPC::InvalidPlatformFileForTransit value to tell the + // renderer that a browser cannot open the file. + void SendDictionary(); + + // The RenderProcessHost object that owns this filter. This class uses this + // object to retrieve the process handle used for creating + // PlatformFileForTransit objects. + content::RenderProcessHost* render_process_host_; + + // The directory that includes dictionary files. The default value is the + // directory containing the executable file. + FilePath dictionary_base_; + + // A cached dictionary file. + base::PlatformFile dictionary_file_; + + base::WeakPtrFactory<HyphenatorMessageFilter> weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(HyphenatorMessageFilter); +}; + +} // namespace content + +#endif // CONTENT_BROWSER_HYPHENATOR_HYPHENATOR_MESSAGE_FILTER_H_ diff --git a/content/browser/hyphenator/hyphenator_message_filter_unittest.cc b/content/browser/hyphenator/hyphenator_message_filter_unittest.cc new file mode 100644 index 0000000..e8b5467 --- /dev/null +++ b/content/browser/hyphenator/hyphenator_message_filter_unittest.cc @@ -0,0 +1,154 @@ +// Copyright (c) 2012 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/hyphenator/hyphenator_message_filter.h" + +#include "base/base_paths.h" +#include "base/file_path.h" +#include "base/path_service.h" +#include "base/string16.h" +#include "base/utf_string_conversions.h" +#include "content/common/hyphenator_messages.h" +#include "content/public/test/mock_render_process_host.h" +#include "content/public/test/test_browser_context.h" +#include "ipc/ipc_message_utils.h" +#include "ipc/ipc_platform_file.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace content { + +// A class derived from the HyphenatorMessageFilter class used in unit tests. +// This class overrides some methods so we can test the HyphenatorMessageFilter +// class without posting tasks. +class TestHyphenatorMessageFilter : public content::HyphenatorMessageFilter { + public: + explicit TestHyphenatorMessageFilter(content::RenderProcessHost* host) + : content::HyphenatorMessageFilter(host), + type_(0), + file_(base::kInvalidPlatformFileValue) { + } + + const string16& locale() const { return locale_; } + uint32 type() const { return type_; } + base::PlatformFile file() const { return file_; } + + // content::BrowserMessageFilter implementation. + virtual bool Send(IPC::Message* message) OVERRIDE { + if (message->type() != HyphenatorMsg_SetDictionary::ID) + return false; + + // Read the PlatformFileForTransit object and check if its value is + // kInvalidPlatformFileValue. Close the incoming file if it is not + // kInvalidPlatformFileValue to prevent leaving the dictionary file open. + type_ = message->type(); + PickleIterator iter(*message); + IPC::PlatformFileForTransit file; + IPC::ParamTraits<IPC::PlatformFileForTransit>::Read(message, &iter, &file); + file_ = IPC::PlatformFileForTransitToPlatformFile(file); + return true; + } + + void SetDictionary(base::PlatformFile file) { + dictionary_file_ = file; + } + + void Reset() { + if (dictionary_file_ != base::kInvalidPlatformFileValue) { + base::ClosePlatformFile(dictionary_file_); + dictionary_file_ = base::kInvalidPlatformFileValue; + } + locale_.clear(); + type_ = 0; + if (file_ != base::kInvalidPlatformFileValue) { + base::ClosePlatformFile(file_); + file_ = base::kInvalidPlatformFileValue; + } + } + + private: + virtual ~TestHyphenatorMessageFilter() { + } + + // content::HyphenatorMessageFilter implementation. This function emulates the + // original implementation without posting a task. + virtual void OnOpenDictionary(const string16& locale) OVERRIDE { + locale_ = locale; + if (dictionary_file_ == base::kInvalidPlatformFileValue) + OpenDictionary(locale); + SendDictionary(); + } + + string16 locale_; + uint32 type_; + base::PlatformFile file_; +}; + +} // namespace content + +class HyphenatorMessageFilterTest : public testing::Test { + public: + HyphenatorMessageFilterTest() { + context_.reset(new content::TestBrowserContext); + host_.reset(new content::MockRenderProcessHost(context_.get())); + filter_ = new content::TestHyphenatorMessageFilter(host_.get()); + } + + virtual ~HyphenatorMessageFilterTest() {} + + scoped_ptr<content::TestBrowserContext> context_; + scoped_ptr<content::MockRenderProcessHost> host_; + scoped_refptr<content::TestHyphenatorMessageFilter> filter_; +}; + +// Verifies IPC messages sent by the HyphenatorMessageFilter class when it +// receives IPC messages (HyphenatorHostMsg_OpenDictionary). +TEST_F(HyphenatorMessageFilterTest, OpenDictionary) { + // Send a HyphenatorHostMsg_OpenDictionary message with an invalid locale and + // verify it sends a HyphenatorMsg_SetDictionary message with an invalid file. + string16 invalid_locale(ASCIIToUTF16("xx-xx")); + IPC::Message invalid_message( + 0, HyphenatorHostMsg_OpenDictionary::ID, IPC::Message::PRIORITY_NORMAL); + invalid_message.WriteString16(invalid_locale); + + bool message_was_ok = false; + filter_->OnMessageReceived(invalid_message, &message_was_ok); + EXPECT_TRUE(message_was_ok); + EXPECT_EQ(invalid_locale, filter_->locale()); + EXPECT_EQ(HyphenatorMsg_SetDictionary::ID, filter_->type()); + EXPECT_EQ(base::kInvalidPlatformFileValue, filter_->file()); + + filter_->Reset(); + + // Open a sample dictionary file and attach it to the + // HyphenatorMessageFilter class so it can return a valid file. + FilePath path; + PathService::Get(base::DIR_SOURCE_ROOT, &path); + path = path.Append(FILE_PATH_LITERAL("third_party")); + path = path.Append(FILE_PATH_LITERAL("hyphen")); + path = path.Append(FILE_PATH_LITERAL("hyph_en_US.dic")); + base::PlatformFile file = base::CreatePlatformFile( + path, base::PLATFORM_FILE_OPEN | base::PLATFORM_FILE_READ, + NULL, NULL); + EXPECT_NE(base::kInvalidPlatformFileValue, file); + filter_->SetDictionary(file); + + // Send a HyphenatorHostMsg_OpenDictionary message with an empty locale and + // verify it sends a HyphenatorMsg_SetDictionary message with a valid file. + string16 empty_locale; + IPC::Message valid_message( + 0, HyphenatorHostMsg_OpenDictionary::ID, IPC::Message::PRIORITY_NORMAL); + valid_message.WriteString16(empty_locale); + + message_was_ok = false; + filter_->OnMessageReceived(valid_message, &message_was_ok); + EXPECT_TRUE(message_was_ok); + EXPECT_EQ(empty_locale, filter_->locale()); + EXPECT_EQ(HyphenatorMsg_SetDictionary::ID, filter_->type()); + EXPECT_NE(base::kInvalidPlatformFileValue, filter_->file()); + + // Delete all resources used by this test. + filter_->Reset(); + if (file != base::kInvalidPlatformFileValue) + base::ClosePlatformFile(file); +} diff --git a/content/browser/renderer_host/render_process_host_impl.cc b/content/browser/renderer_host/render_process_host_impl.cc index 8d55699..694091f 100644 --- a/content/browser/renderer_host/render_process_host_impl.cc +++ b/content/browser/renderer_host/render_process_host_impl.cc @@ -55,6 +55,7 @@ #include "content/browser/gpu/gpu_data_manager_impl.h" #include "content/browser/gpu/gpu_process_host.h" #include "content/browser/histogram_message_filter.h" +#include "content/browser/hyphenator/hyphenator_message_filter.h" #include "content/browser/in_process_webkit/indexed_db_context_impl.h" #include "content/browser/in_process_webkit/indexed_db_dispatcher_host.h" #include "content/browser/mime_registry_message_filter.h" @@ -608,7 +609,8 @@ void RenderProcessHostImpl::CreateMessageFilters() { GetContentClient()->browser()->CreateQuotaPermissionContext())); channel_->AddFilter(new GamepadBrowserMessageFilter(this)); channel_->AddFilter(new ProfilerMessageFilter(PROCESS_TYPE_RENDERER)); - channel_->AddFilter(new content::HistogramMessageFilter()); + channel_->AddFilter(new HistogramMessageFilter()); + channel_->AddFilter(new HyphenatorMessageFilter(this)); } int RenderProcessHostImpl::GetNextRoutingID() { diff --git a/content/common/content_message_generator.h b/content/common/content_message_generator.h index 741152d..1bce78a 100644 --- a/content/common/content_message_generator.h +++ b/content/common/content_message_generator.h @@ -23,6 +23,7 @@ #include "content/common/gamepad_messages.h" #include "content/common/geolocation_messages.h" #include "content/common/gpu/gpu_messages.h" +#include "content/common/hyphenator_messages.h" #include "content/common/indexed_db/indexed_db_messages.h" #include "content/common/intents_messages.h" #include "content/common/java_bridge_messages.h" diff --git a/content/common/hyphenator_messages.h b/content/common/hyphenator_messages.h new file mode 100644 index 0000000..e68985d --- /dev/null +++ b/content/common/hyphenator_messages.h @@ -0,0 +1,24 @@ +// Copyright (c) 2012 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. + +// IPC messages for hyphenation. +// Message definition file, included multiple times, hence no include guard. + +#include "ipc/ipc_message_macros.h" +#include "ipc/ipc_platform_file.h" + +#define IPC_MESSAGE_START HyphenatorMsgStart + +// Opens the specified hyphenation dictionary. This message is expected to be +// sent when WebKit calls the canHyphenate function, i.e. when it starts +// layouting text. At this time, WebKit does not actually need this dictionary +// to hyphenate words. Therefore, a renderer does not need to wait for a browser +// to open the specified dictionary. +IPC_MESSAGE_CONTROL1(HyphenatorHostMsg_OpenDictionary, + string16 /* locale */) + +// Sends the hyphenation dictionary to the renderer. This messages is sent in +// response to a HyphenatorHostMsg_OpenDictionary message. +IPC_MESSAGE_CONTROL1(HyphenatorMsg_SetDictionary, + IPC::PlatformFileForTransit /* dict_file */) diff --git a/content/content_browser.gypi b/content/content_browser.gypi index a975075..76d4fab 100644 --- a/content/content_browser.gypi +++ b/content/content_browser.gypi @@ -448,6 +448,8 @@ 'browser/histogram_synchronizer.h', 'browser/host_zoom_map_impl.cc', 'browser/host_zoom_map_impl.h', + 'browser/hyphenator/hyphenator_message_filter.cc', + 'browser/hyphenator/hyphenator_message_filter.h', 'browser/in_process_webkit/browser_webkitplatformsupport_impl.cc', 'browser/in_process_webkit/browser_webkitplatformsupport_impl.h', 'browser/in_process_webkit/indexed_db_callbacks.cc', diff --git a/content/content_tests.gypi b/content/content_tests.gypi index ecb6977..d4590b5 100644 --- a/content/content_tests.gypi +++ b/content/content_tests.gypi @@ -273,6 +273,7 @@ 'browser/geolocation/win7_location_provider_unittest_win.cc', 'browser/gpu/gpu_data_manager_impl_unittest.cc', 'browser/host_zoom_map_impl_unittest.cc', + 'browser/hyphenator/hyphenator_message_filter_unittest.cc', 'browser/in_process_webkit/indexed_db_quota_client_unittest.cc', 'browser/in_process_webkit/indexed_db_unittest.cc', 'browser/in_process_webkit/webkit_thread_unittest.cc', diff --git a/content/public/test/mock_render_process_host.cc b/content/public/test/mock_render_process_host.cc index cc53cdb..39dfaf5 100644 --- a/content/public/test/mock_render_process_host.cc +++ b/content/public/test/mock_render_process_host.cc @@ -101,7 +101,9 @@ void MockRenderProcessHost::DumpHandles() { } base::ProcessHandle MockRenderProcessHost::GetHandle() { - return base::kNullProcessHandle; + // Return the current-process handle for the IPC::GetFileHandleForProcess + // function. + return base::Process::Current().handle(); } bool MockRenderProcessHost::Send(IPC::Message* msg) { diff --git a/content/public/test/mock_render_thread.cc b/content/public/test/mock_render_thread.cc index 50d9182..50d3968 100644 --- a/content/public/test/mock_render_thread.cc +++ b/content/public/test/mock_render_thread.cc @@ -98,10 +98,21 @@ int MockRenderThread::GenerateRoutingID() { void MockRenderThread::AddFilter(IPC::ChannelProxy::MessageFilter* filter) { filter->OnFilterAdded(&sink()); + // Add this filter to a vector so the MockRenderThread::RemoveFilter function + // can check if this filter is added. + filters_.push_back(make_scoped_refptr(filter)); } void MockRenderThread::RemoveFilter(IPC::ChannelProxy::MessageFilter* filter) { - filter->OnFilterRemoved(); + // Emulate the IPC::ChannelProxy::OnRemoveFilter function. + for (size_t i = 0; i < filters_.size(); ++i) { + if (filters_[i].get() == filter) { + filter->OnFilterRemoved(); + filters_.erase(filters_.begin() + i); + return; + } + } + NOTREACHED() << "filter to be removed not found"; } void MockRenderThread::SetOutgoingMessageFilter( diff --git a/content/public/test/mock_render_thread.h b/content/public/test/mock_render_thread.h index a2444eb..c0b3b90 100644 --- a/content/public/test/mock_render_thread.h +++ b/content/public/test/mock_render_thread.h @@ -148,6 +148,9 @@ class MockRenderThread : public RenderThread { // The last known good deserializer for sync messages. scoped_ptr<IPC::MessageReplyDeserializer> reply_deserializer_; + + // A list of message filters added to this thread. + std::vector<scoped_refptr<IPC::ChannelProxy::MessageFilter> > filters_; }; } // namespace content diff --git a/content/renderer/hyphenator/hyphenator.cc b/content/renderer/hyphenator/hyphenator.cc index da92f9e..ef029d2 100644 --- a/content/renderer/hyphenator/hyphenator.cc +++ b/content/renderer/hyphenator/hyphenator.cc @@ -9,6 +9,8 @@ #include "base/memory/scoped_ptr.h" #include "base/string_util.h" #include "base/utf_string_conversions.h" +#include "content/common/hyphenator_messages.h" +#include "content/public/renderer/render_thread.h" #include "third_party/hyphen/hyphen.h" #include "unicode/uscript.h" @@ -189,23 +191,42 @@ Hyphenator::Hyphenator(base::PlatformFile file) Hyphenator::~Hyphenator() { if (dictionary_) hnj_hyphen_free(dictionary_); + if (rule_file_ != base::kInvalidPlatformFileValue) + base::ClosePlatformFile(rule_file_); } bool Hyphenator::Initialize() { if (dictionary_) return true; + // Attach the dictionary file to the MemoryMappedFile object. When it + // succeeds, this class does not have to close this file because it is closed + // by the MemoryMappedFile class. To prevent this class from closing this + // file, we reset its handle. rule_map_.reset(new file_util::MemoryMappedFile); if (!rule_map_->Initialize(rule_file_)) return false; + rule_file_ = base::kInvalidPlatformFileValue; dictionary_ = hnj_hyphen_load(rule_map_->data(), rule_map_->length()); return !!dictionary_; } +bool Hyphenator::Attach(content::RenderThread* thread, const string16& locale) { + if (!thread) + return false; + locale_.assign(locale); + thread->AddObserver(this); + return thread->Send(new HyphenatorHostMsg_OpenDictionary(locale)); +} + +bool Hyphenator::CanHyphenate(const string16& locale) { + return !locale_.compare(locale); +} + size_t Hyphenator::ComputeLastHyphenLocation(const string16& word, size_t before_index) { - if (!dictionary_ || word.empty()) + if (!Initialize() || word.empty()) return 0; // Call the hyphen library to get all hyphenation points, i.e. positions where @@ -228,4 +249,32 @@ size_t Hyphenator::ComputeLastHyphenLocation(const string16& word, return 0; } +bool Hyphenator::OnControlMessageReceived(const IPC::Message& message) { + bool handled = true; + IPC_BEGIN_MESSAGE_MAP(Hyphenator, message) + IPC_MESSAGE_HANDLER(HyphenatorMsg_SetDictionary, OnSetDictionary) + IPC_MESSAGE_UNHANDLED(handled = false) + IPC_END_MESSAGE_MAP() + return handled; +} + +void Hyphenator::OnSetDictionary(IPC::PlatformFileForTransit file) { + base::PlatformFile rule_file = + IPC::PlatformFileForTransitToPlatformFile(file); + if (rule_file == base::kInvalidPlatformFileValue) + return; + // Delete the current dictionary and save the given file to this object. We + // initialize the hyphen library the first time when WebKit actually + // hyphenates a word, i.e. when WebKit calls the ComputeLastHyphenLocation + // function. (WebKit does not always hyphenate words even when it calls the + // CanHyphenate function, e.g. WebKit does not have to hyphenate words when it + // does not have to break text into lines.) + if (dictionary_) { + hnj_hyphen_free(dictionary_); + dictionary_ = NULL; + } + rule_map_.reset(); + rule_file_ = rule_file; +} + } // namespace content diff --git a/content/renderer/hyphenator/hyphenator.h b/content/renderer/hyphenator/hyphenator.h index 561af80..0777364 100644 --- a/content/renderer/hyphenator/hyphenator.h +++ b/content/renderer/hyphenator/hyphenator.h @@ -11,6 +11,8 @@ #include "base/platform_file.h" #include "base/string16.h" #include "content/common/content_export.h" +#include "content/public/renderer/render_process_observer.h" +#include "ipc/ipc_platform_file.h" namespace file_util { class MemoryMappedFile; @@ -19,33 +21,47 @@ class MemoryMappedFile; typedef struct _HyphenDict HyphenDict; namespace content { +class RenderThread; // A class that hyphenates a word. This class encapsulates the hyphen library // and manages resources used by the library. When this class uses a huge // dictionary, it takes lots of memory (~1.3MB for English). A renderer should // create this object only when it renders a page that needs hyphenation and // deletes it when it moves to a page that does not need hyphenation. -class CONTENT_EXPORT Hyphenator { +class CONTENT_EXPORT Hyphenator : public RenderProcessObserver { public: explicit Hyphenator(base::PlatformFile file); - ~Hyphenator(); + virtual ~Hyphenator(); // Initializes the hyphen library and allocates resources needed for // hyphenation. bool Initialize(); + bool Attach(content::RenderThread* thread, const string16& locale); + + // Returns whether this object can hyphenate words. When this object does not + // have a dictionary file attached, this function sends an IPC request to open + // the file. + bool CanHyphenate(const string16& locale); + // Returns the last hyphenation point, the position where we can insert a // hyphen, before the given position. If there are not any hyphenation points, // this function returns 0. size_t ComputeLastHyphenLocation(const string16& word, size_t before_index); + // RenderProcessObserver implementation. + virtual bool OnControlMessageReceived(const IPC::Message& message) OVERRIDE; + private: + void OnSetDictionary(IPC::PlatformFileForTransit rule_file); + // The dictionary used by the hyphen library. HyphenDict* dictionary_; // The dictionary file and its memory-mapping object. (Our copy of the hyphen // library uses a memory-mapped file opened by a browser so renderers can use // it without opening the file.) + string16 locale_; base::PlatformFile rule_file_; scoped_ptr<file_util::MemoryMappedFile> rule_map_; diff --git a/content/renderer/hyphenator/hyphenator_unittest.cc b/content/renderer/hyphenator/hyphenator_unittest.cc index 84c1ce1..1a830e2 100644 --- a/content/renderer/hyphenator/hyphenator_unittest.cc +++ b/content/renderer/hyphenator/hyphenator_unittest.cc @@ -7,15 +7,79 @@ #include "base/path_service.h" #include "base/platform_file.h" #include "base/utf_string_conversions.h" +#include "content/common/hyphenator_messages.h" +#include "content/public/test/mock_render_thread.h" +#include "ipc/ipc_listener.h" #include "testing/gtest/include/gtest/gtest.h" #include "third_party/hyphen/hyphen.h" +namespace { + +// A mock message listener that listens for HyphenatorHost messages. This class +// intercepts a HyphenatorHostMsg_OpenDictionary message sent to an +// IPC::TestSink object and emulates the HyphenatorMessageFilter class. +class MockListener : public IPC::Listener { + public: + MockListener(content::Hyphenator* hyphenator, const string16& locale) + : hyphenator_(hyphenator), + locale_(locale) { + } + virtual ~MockListener() { + } + + // IPC::ChannelProxy::MessageFilter implementation. + virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE { + if (message.type() != HyphenatorHostMsg_OpenDictionary::ID) + return false; + + // Retrieve the locale parameter directly because HyphenatorHost messages + // are internal messages and unit tests cannot access its member functions, + // i.e. unit tests cannot call the HyphenatorHostMsg_OpenDictionary::Read + // function. + PickleIterator iter(message); + string16 locale; + EXPECT_TRUE(message.ReadString16(&iter, &locale)); + EXPECT_EQ(locale_, locale); + + // Open the default dictionary and call the OnControllMessageReceived + // function with a HyphenatorMsg_SetDictionary message. + FilePath dictionary_path; + if (!PathService::Get(base::DIR_SOURCE_ROOT, &dictionary_path)) + return false; + dictionary_path = dictionary_path.AppendASCII("third_party"); + dictionary_path = dictionary_path.AppendASCII("hyphen"); + dictionary_path = dictionary_path.AppendASCII("hyph_en_US.dic"); + base::PlatformFile file = base::CreatePlatformFile( + dictionary_path, base::PLATFORM_FILE_OPEN | base::PLATFORM_FILE_READ, + NULL, NULL); + EXPECT_NE(base::kInvalidPlatformFileValue, file); + + IPC::Message response( + 0, HyphenatorMsg_SetDictionary::ID, IPC::Message::PRIORITY_NORMAL); + IPC::PlatformFileForTransit transit = IPC::GetFileHandleForProcess( + file, GetHandle(), false); + IPC::ParamTraits<IPC::PlatformFileForTransit>::Write(&response, transit); + hyphenator_->OnControlMessageReceived(response); + base::ClosePlatformFile(file); + return true; + } + + private: + base::ProcessHandle GetHandle() const { + return base::Process::Current().handle(); + } + + content::Hyphenator* hyphenator_; + string16 locale_; +}; + +} // namespace + // A unit test for our hyphenator. This class loads a sample hyphenation // dictionary and hyphenates words. class HyphenatorTest : public testing::Test { public: HyphenatorTest() { - Initialize(); } bool Initialize() { @@ -49,8 +113,22 @@ class HyphenatorTest : public testing::Test { return hyphenated_word; } + bool OpenDictionary(const string16& locale) { + hyphenator_.reset(new content::Hyphenator(base::kInvalidPlatformFileValue)); + thread_.reset(new content::MockRenderThread()); + listener_.reset(new MockListener(hyphenator_.get(), locale)); + thread_->sink().AddFilter(listener_.get()); + return hyphenator_->Attach(thread_.get(), locale); + } + + size_t GetMessageCount() const { + return thread_->sink().message_count(); + } + private: scoped_ptr<content::Hyphenator> hyphenator_; + scoped_ptr<content::MockRenderThread> thread_; + scoped_ptr<MockListener> listener_; }; // Verifies that our hyphenator yields the same hyphenated words as the original @@ -82,9 +160,29 @@ TEST_F(HyphenatorTest, HyphenateWords) { { "undone.", "un-done." }, { "unnecessary", "un-nec-es-sary" }, }; + Initialize(); for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kTestCases); ++i) { string16 input = ASCIIToUTF16(kTestCases[i].input); string16 expected = ASCIIToUTF16(kTestCases[i].expected); EXPECT_EQ(expected, Hyphenate(input)); } } + +// Verifies that our hyphenator sends a HyphenatorHostMsg_OpenDictionary +// message to ask a browser to open a dictionary. Also, this test verifies that +// our hyphenator can hyphnate words when the Hyphenator::SetDictionary function +// is called. +TEST_F(HyphenatorTest, openDictionary) { + // Send a HyphenatorHostMsg_OpenDictionary message and verify it is handled by + // our MockListner class. + EXPECT_TRUE(OpenDictionary(string16())); + EXPECT_EQ(0U, GetMessageCount()); + + // Verify that we can now hyphenate words. When the MockListener class + // receives a HyphenatorHostMsg_OpenDictionary message, it calls the + // OnControlMessageReceived function with a HyphenatorMsg_SetDictionary + // message. So, the Hyphenate function should be able to hyphenate words now. + string16 input = ASCIIToUTF16("hyphenation"); + string16 expected = ASCIIToUTF16("hy-phen-ation"); + EXPECT_EQ(expected, Hyphenate(input)); +} diff --git a/content/renderer/renderer_webkitplatformsupport_impl.cc b/content/renderer/renderer_webkitplatformsupport_impl.cc index b2096ac..5f05a8f 100644 --- a/content/renderer/renderer_webkitplatformsupport_impl.cc +++ b/content/renderer/renderer_webkitplatformsupport_impl.cc @@ -25,6 +25,7 @@ #include "content/public/renderer/content_renderer_client.h" #include "content/renderer/dom_storage/webstoragenamespace_impl.h" #include "content/renderer/gamepad_shared_memory_reader.h" +#include "content/renderer/hyphenator/hyphenator.h" #include "content/renderer/media/audio_hardware.h" #include "content/renderer/media/renderer_webaudiodevice_impl.h" #include "content/renderer/render_thread_impl.h" @@ -708,3 +709,35 @@ GpuChannelHostFactory* RendererWebKitPlatformSupportImpl::GetGpuChannelHostFactory() { return RenderThreadImpl::current(); } + +//------------------------------------------------------------------------------ + +bool RendererWebKitPlatformSupportImpl::canHyphenate( + const WebKit::WebString& locale) { + // Return false unless WebKit asks for US English dictionaries because WebKit + // can currently hyphenate only English words. + if (!locale.isEmpty() && !locale.equals("en-US")) + return false; + + // Create a hyphenator object and attach it to the render thread so it can + // receive a dictionary file opened by a browser. + if (!hyphenator_.get()) { + hyphenator_.reset(new content::Hyphenator(base::kInvalidPlatformFileValue)); + if (!hyphenator_.get()) + return false; + return hyphenator_->Attach(RenderThreadImpl::current(), locale); + } + return hyphenator_->CanHyphenate(locale); +} + +size_t RendererWebKitPlatformSupportImpl::computeLastHyphenLocation( + const char16* characters, + size_t length, + size_t before_index, + const WebKit::WebString& locale) { + // Crash if WebKit calls this function when canHyphenate returns false. + DCHECK(locale.isEmpty() || locale.equals("en-US")); + DCHECK(hyphenator_.get()); + return hyphenator_->ComputeLastHyphenLocation(string16(characters, length), + before_index); +} diff --git a/content/renderer/renderer_webkitplatformsupport_impl.h b/content/renderer/renderer_webkitplatformsupport_impl.h index 62bd512..b4f798b 100644 --- a/content/renderer/renderer_webkitplatformsupport_impl.h +++ b/content/renderer/renderer_webkitplatformsupport_impl.h @@ -18,6 +18,7 @@ class WebFileSystemImpl; namespace content { class GamepadSharedMemoryReader; +class Hyphenator; } namespace webkit_glue { @@ -84,6 +85,11 @@ class CONTENT_EXPORT RendererWebKitPlatformSupportImpl WebKit::WebPeerConnection00HandlerClient* client) OVERRIDE; virtual WebKit::WebMediaStreamCenter* createMediaStreamCenter( WebKit::WebMediaStreamCenterClient* client) OVERRIDE; + virtual bool canHyphenate(const WebKit::WebString& locale) OVERRIDE; + virtual size_t computeLastHyphenLocation(const char16* characters, + size_t length, + size_t before_index, + const WebKit::WebString& locale) OVERRIDE; // Disables the WebSandboxSupport implementation for testing. // Tests that do not set up a full sandbox environment should call @@ -132,6 +138,8 @@ class CONTENT_EXPORT RendererWebKitPlatformSupportImpl scoped_ptr<WebKit::WebBlobRegistry> blob_registry_; scoped_ptr<content::GamepadSharedMemoryReader> gamepad_shared_memory_reader_; + + scoped_ptr<content::Hyphenator> hyphenator_; }; #endif // CONTENT_RENDERER_RENDERER_WEBKITPLATFORMSUPPORT_IMPL_H_ diff --git a/ipc/ipc_message_utils.h b/ipc/ipc_message_utils.h index 0d92f67..37f3afe 100644 --- a/ipc/ipc_message_utils.h +++ b/ipc/ipc_message_utils.h @@ -104,6 +104,7 @@ enum IPCMessageStart { ChromotingMsgStart, OldBrowserPluginMsgStart, BrowserPluginMsgStart, + HyphenatorMsgStart, AndroidWebViewMsgStart, LastIPCMsgStart // Must come last. }; |