mojo: Create a basic clipboard.

This creates a basic clipboard interface and uses it from
html_viewer. This is a minimal implementation and does not actually
interact with the system clipboard.

BUG=411039
First Committed: https://crrev.com/757286d8a2c778fe4622890140c9b9d2afd21063

Review URL: https://codereview.chromium.org/562483002

Cr-Commit-Position: refs/heads/master@{#295182}


CrOS-Libchrome-Original-Commit: b69fbca034e3c2181e0a12cf02baec4de526bf25
diff --git a/mojo/common/common_type_converters.cc b/mojo/common/common_type_converters.cc
index ffc1907..114b409 100644
--- a/mojo/common/common_type_converters.cc
+++ b/mojo/common/common_type_converters.cc
@@ -46,4 +46,20 @@
   return GURL(input.get());
 }
 
+std::string TypeConverter<std::string, Array<uint8_t> >::Convert(
+    const Array<uint8_t>& input) {
+  if (input.is_null())
+    return std::string();
+
+  return std::string(reinterpret_cast<const char*>(&input.front()),
+                     input.size());
+}
+
+Array<uint8_t> TypeConverter<Array<uint8_t>, std::string>::Convert(
+    const std::string& input) {
+  Array<uint8_t> result(input.size());
+  memcpy(&result.front(), input.c_str(), input.size());
+  return result.Pass();
+}
+
 }  // namespace mojo
diff --git a/mojo/common/common_type_converters.h b/mojo/common/common_type_converters.h
index 159325e..7b0260a 100644
--- a/mojo/common/common_type_converters.h
+++ b/mojo/common/common_type_converters.h
@@ -8,6 +8,7 @@
 #include "base/strings/string16.h"
 #include "base/strings/string_piece.h"
 #include "mojo/common/mojo_common_export.h"
+#include "mojo/public/cpp/bindings/array.h"
 #include "mojo/public/cpp/bindings/string.h"
 #include "mojo/public/cpp/bindings/type_converter.h"
 
@@ -45,6 +46,20 @@
   static GURL Convert(const String& input);
 };
 
+// TODO(erg): In the very long term, we will want to remove conversion between
+// std::strings and arrays of unsigned bytes. However, there is too much code
+// across chrome which uses std::string as a bag of bytes that we probably
+// don't want to roll this function at each callsite.
+template <>
+struct MOJO_COMMON_EXPORT TypeConverter<std::string, Array<uint8_t> > {
+  static std::string Convert(const Array<uint8_t>& input);
+};
+
+template <>
+struct MOJO_COMMON_EXPORT TypeConverter<Array<uint8_t>, std::string> {
+  static Array<uint8_t> Convert(const std::string& input);
+};
+
 }  // namespace mojo
 
 #endif  // MOJO_COMMON_COMMON_TYPE_CONVERTERS_H_
diff --git a/mojo/common/common_type_converters_unittest.cc b/mojo/common/common_type_converters_unittest.cc
index 314f180..f97be0a 100644
--- a/mojo/common/common_type_converters_unittest.cc
+++ b/mojo/common/common_type_converters_unittest.cc
@@ -84,6 +84,27 @@
   ASSERT_EQ(0U, string_from_invalid.size());
 }
 
+TEST(CommonTypeConvertersTest, ArrayUint8ToStdString) {
+  Array<uint8_t> data(4);
+  data[0] = 'd';
+  data[1] = 'a';
+  data[2] = 't';
+  data[3] = 'a';
+
+  EXPECT_EQ("data", data.To<std::string>());
+}
+
+TEST(CommonTypeConvertersTest, StdStringToArrayUint8) {
+  std::string input("data");
+  Array<uint8_t> data = Array<uint8_t>::From(input);
+
+  ASSERT_EQ(4ul, data.size());
+  EXPECT_EQ('d', data[0]);
+  EXPECT_EQ('a', data[1]);
+  EXPECT_EQ('t', data[2]);
+  EXPECT_EQ('a', data[3]);
+}
+
 }  // namespace test
 }  // namespace common
 }  // namespace mojo
diff --git a/mojo/mojo.gyp b/mojo/mojo.gyp
index 29a49f0..890af4e 100644
--- a/mojo/mojo.gyp
+++ b/mojo/mojo.gyp
@@ -29,6 +29,8 @@
         'mojo_application_manager_unittests',
         'mojo_apps_js_unittests',
         'mojo_base.gyp:mojo_base',
+        'mojo_clipboard',
+        'mojo_clipboard_unittests',
         'mojo_compositor_app',
         'mojo_content_handler_demo',
         'mojo_echo_client',
diff --git a/mojo/mojo_services.gypi b/mojo/mojo_services.gypi
index e775c00..4b68611 100644
--- a/mojo/mojo_services.gypi
+++ b/mojo/mojo_services.gypi
@@ -5,6 +5,59 @@
 {
   'targets': [
     {
+      # GN version: //mojo/services/clipboard/
+      'target_name': 'mojo_clipboard',
+      'type': 'loadable_module',
+      'dependencies': [
+        '../base/base.gyp:base',
+        'mojo_base.gyp:mojo_common_lib',
+        'mojo_base.gyp:mojo_cpp_bindings',
+        'mojo_base.gyp:mojo_utility',
+        'mojo_base.gyp:mojo_application_chromium',
+        'mojo_clipboard_bindings',
+        '<(mojo_system_for_loadable_module)',
+      ],
+      'sources': [
+        'services/clipboard/clipboard_standalone_impl.cc',
+        'services/clipboard/clipboard_standalone_impl.h',
+        'services/clipboard/main.cc',
+      ],
+    },
+    {
+      # GN version: //mojo/services/public/interfaces/clipboard
+      'target_name': 'mojo_clipboard_bindings',
+      'type': 'static_library',
+      'sources': [
+        'services/public/interfaces/clipboard/clipboard.mojom',
+      ],
+      'includes': [ 'public/tools/bindings/mojom_bindings_generator.gypi' ],
+      'dependencies': [
+        'mojo_base.gyp:mojo_cpp_bindings',
+      ],
+      'export_dependent_settings': [
+        'mojo_base.gyp:mojo_cpp_bindings',
+      ],
+    },
+    {
+      # GN version: //mojo/services/clipboard:mojo_clipboard_unittests
+      'target_name': 'mojo_clipboard_unittests',
+      'type': 'executable',
+      'dependencies': [
+        '../base/base.gyp:base',
+        '../base/base.gyp:test_support_base',
+        '../testing/gtest.gyp:gtest',
+        'mojo_application_manager',
+        'mojo_base.gyp:mojo_application_chromium',
+        'mojo_base.gyp:mojo_run_all_unittests',
+        'mojo_base.gyp:mojo_system_impl',
+        'mojo_clipboard_bindings',
+        'mojo_shell_test_support',
+      ],
+      'sources': [
+        'services/clipboard/clipboard_standalone_unittest.cc',
+      ],
+    },
+    {
       # GN version: //mojo/services/html_viewer
       'target_name': 'mojo_html_viewer',
       'type': 'loadable_module',
@@ -23,6 +76,7 @@
         'mojo_base.gyp:mojo_common_lib',
         'mojo_base.gyp:mojo_cpp_bindings',
         'mojo_base.gyp:mojo_utility',
+        'mojo_clipboard_bindings',
         'mojo_cc_support',
         'mojo_content_handler_bindings',
         'mojo_gpu_bindings',
@@ -47,6 +101,8 @@
         'services/html_viewer/html_viewer.cc',
         'services/html_viewer/html_document_view.cc',
         'services/html_viewer/html_document_view.h',
+        'services/html_viewer/webclipboard_impl.cc',
+        'services/html_viewer/webclipboard_impl.h',
         'services/html_viewer/webcookiejar_impl.cc',
         'services/html_viewer/webcookiejar_impl.h',
         'services/html_viewer/webmediaplayer_factory.cc',
diff --git a/mojo/public/cpp/bindings/array.h b/mojo/public/cpp/bindings/array.h
index 9b5aff8..0ef6261 100644
--- a/mojo/public/cpp/bindings/array.h
+++ b/mojo/public/cpp/bindings/array.h
@@ -68,6 +68,9 @@
 
   bool is_null() const { return is_null_; }
 
+  ConstRefType front() const { return vec_.front(); }
+  RefType front() { return vec_.front(); }
+
   size_t size() const { return vec_.size(); }
 
   ConstRefType at(size_t offset) const { return Traits::at(&vec_, offset); }
diff --git a/mojo/services/clipboard/clipboard_standalone_impl.cc b/mojo/services/clipboard/clipboard_standalone_impl.cc
new file mode 100644
index 0000000..e1c7e2c
--- /dev/null
+++ b/mojo/services/clipboard/clipboard_standalone_impl.cc
@@ -0,0 +1,100 @@
+// Copyright (c) 2014 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 "mojo/services/clipboard/clipboard_standalone_impl.h"
+
+namespace mojo {
+
+typedef std::vector<uint8_t> ByteVector;
+
+// ClipboardData contains data copied to the Clipboard for a variety of formats.
+// It mostly just provides APIs to cleanly access and manipulate this data.
+class ClipboardStandaloneImpl::ClipboardData {
+ public:
+  ClipboardData() {}
+  ~ClipboardData() {}
+
+  std::vector<std::string> GetMimeTypes() const {
+    std::vector<std::string> types;
+    for (std::map<std::string, ByteVector>::const_iterator it =
+             data_types_.begin();
+         it != data_types_.end();
+         ++it) {
+      types.push_back(it->first);
+    }
+
+    return types;
+  }
+
+  void SetData(std::map<std::string, ByteVector>* data) {
+    std::swap(data_types_, *data);
+  }
+
+  bool GetData(const std::string& mime_type, ByteVector* data) const {
+    std::map<std::string, ByteVector>::const_iterator it =
+        data_types_.find(mime_type);
+    if (it != data_types_.end()) {
+      *data = it->second;
+      return true;
+    }
+
+    return false;
+  }
+
+ private:
+  std::map<std::string, ByteVector> data_types_;
+
+  DISALLOW_COPY_AND_ASSIGN(ClipboardData);
+};
+
+ClipboardStandaloneImpl::ClipboardStandaloneImpl() {
+  for (int i = 0; i < kNumClipboards; ++i) {
+    sequence_number_[i] = 0;
+    clipboard_state_[i].reset(new ClipboardData);
+  }
+}
+
+ClipboardStandaloneImpl::~ClipboardStandaloneImpl() {
+}
+
+void ClipboardStandaloneImpl::GetSequenceNumber(
+    Clipboard::Type clipboard_type,
+    const mojo::Callback<void(uint64_t)>& callback) {
+  callback.Run(sequence_number_[clipboard_type]);
+}
+
+void ClipboardStandaloneImpl::GetAvailableMimeTypes(
+    Clipboard::Type clipboard_type,
+    const mojo::Callback<void(mojo::Array<mojo::String>)>& callback) {
+  mojo::Array<mojo::String> types = mojo::Array<mojo::String>::From(
+      clipboard_state_[clipboard_type]->GetMimeTypes());
+  callback.Run(types.Pass());
+}
+
+void ClipboardStandaloneImpl::ReadMimeType(
+    Clipboard::Type clipboard_type,
+    const mojo::String& mime_type,
+    const mojo::Callback<void(mojo::Array<uint8_t>)>& callback) {
+  ByteVector mime_data;
+  if (clipboard_state_[clipboard_type]->GetData(
+          mime_type.To<std::string>(), &mime_data)) {
+    callback.Run(mojo::Array<uint8_t>::From(mime_data).Pass());
+    return;
+  }
+
+  callback.Run(mojo::Array<uint8_t>().Pass());
+}
+
+void ClipboardStandaloneImpl::WriteClipboardData(
+    Clipboard::Type clipboard_type,
+    mojo::Array<MimeTypePairPtr> data) {
+  std::map<std::string, ByteVector> mime_data;
+  for (size_t i = 0; i < data.size(); ++i)
+    mime_data[data[i]->mime_type] = data[i]->data;
+
+  sequence_number_[clipboard_type]++;
+  clipboard_state_[clipboard_type]->SetData(&mime_data);
+}
+
+}  // namespace mojo
diff --git a/mojo/services/clipboard/clipboard_standalone_impl.h b/mojo/services/clipboard/clipboard_standalone_impl.h
new file mode 100644
index 0000000..6909c10
--- /dev/null
+++ b/mojo/services/clipboard/clipboard_standalone_impl.h
@@ -0,0 +1,61 @@
+// Copyright (c) 2014 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 MOJO_SERVICES_CLIPBOARD_CLIPBOARD_STANDALONE_IMPL_H_
+#define MOJO_SERVICES_CLIPBOARD_CLIPBOARD_STANDALONE_IMPL_H_
+
+#include <base/memory/scoped_ptr.h>
+#include <string>
+
+#include "mojo/services/public/interfaces/clipboard/clipboard.mojom.h"
+
+namespace mojo {
+
+// Stub clipboard implementation.
+//
+// Eventually, we'll actually want to interact with the system clipboard, but
+// that's hard today because the system clipboard is asynchronous (on X11), the
+// ui::Clipboard interface is synchronous (which is what we'd use), mojo is
+// asynchronous across processes, and the WebClipboard interface is synchronous
+// (which is at least tractable).
+class ClipboardStandaloneImpl : public InterfaceImpl<mojo::Clipboard> {
+ public:
+  // mojo::Clipboard exposes three possible clipboards.
+  static const int kNumClipboards = 3;
+
+  ClipboardStandaloneImpl();
+  virtual ~ClipboardStandaloneImpl();
+
+  // InterfaceImpl<mojo::Clipboard> implementation.
+  virtual void GetSequenceNumber(Clipboard::Type clipboard_type,
+                                 const mojo::Callback<void(uint64_t)>& callback)
+      MOJO_OVERRIDE;
+  virtual void GetAvailableMimeTypes(
+      Clipboard::Type clipboard_types,
+      const mojo::Callback<void(mojo::Array<mojo::String>)>& callback)
+      MOJO_OVERRIDE;
+  virtual void ReadMimeType(
+      Clipboard::Type clipboard_type,
+      const mojo::String& mime_type,
+      const mojo::Callback<void(mojo::Array<uint8_t>)>& callback)
+      MOJO_OVERRIDE;
+  virtual void WriteClipboardData(Clipboard::Type clipboard_type,
+                                  mojo::Array<MimeTypePairPtr> data)
+      MOJO_OVERRIDE;
+
+ private:
+  uint64_t sequence_number_[kNumClipboards];
+
+  // Internal struct which stores the current state of the clipboard.
+  class ClipboardData;
+
+  // The current clipboard state. This is what is read from.
+  scoped_ptr<ClipboardData> clipboard_state_[kNumClipboards];
+
+  DISALLOW_COPY_AND_ASSIGN(ClipboardStandaloneImpl);
+};
+
+}  // namespace mojo
+
+#endif  // MOJO_SERVICES_CLIPBOARD_CLIPBOARD_STANDALONE_IMPL_H_
diff --git a/mojo/services/clipboard/clipboard_standalone_unittest.cc b/mojo/services/clipboard/clipboard_standalone_unittest.cc
new file mode 100644
index 0000000..60838f1
--- /dev/null
+++ b/mojo/services/clipboard/clipboard_standalone_unittest.cc
@@ -0,0 +1,185 @@
+// Copyright (c) 2014 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/at_exit.h"
+#include "base/bind.h"
+#include "mojo/common/common_type_converters.h"
+#include "mojo/services/public/interfaces/clipboard/clipboard.mojom.h"
+#include "mojo/shell/shell_test_helper.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+void CopyUint64AndEndRunloop(uint64_t* output,
+                             base::RunLoop* run_loop,
+                             uint64_t input) {
+  *output = input;
+  run_loop->Quit();
+}
+
+void CopyStringAndEndRunloop(std::string* output,
+                             bool* string_is_null,
+                             base::RunLoop* run_loop,
+                             const mojo::Array<uint8_t>& input) {
+  *string_is_null = input.is_null();
+  *output = input.is_null() ? "" : input.To<std::string>();
+  run_loop->Quit();
+}
+
+void CopyVectorStringAndEndRunloop(std::vector<std::string>* output,
+                                   base::RunLoop* run_loop,
+                                   const mojo::Array<mojo::String>& input) {
+  *output = input.To<std::vector<std::string> >();
+  run_loop->Quit();
+}
+
+const char* kUninitialized = "Uninitialized data";
+const char* kPlainTextData = "Some plain data";
+const char* kHtmlData = "<html>data</html>";
+
+}  // namespace
+
+namespace mojo {
+namespace service {
+
+class ClipboardStandaloneTest : public testing::Test {
+ public:
+  ClipboardStandaloneTest() {}
+  virtual ~ClipboardStandaloneTest() {}
+
+  virtual void SetUp() OVERRIDE {
+    test_helper_.Init();
+
+    test_helper_.application_manager()->ConnectToService(
+        GURL("mojo:mojo_clipboard"), &clipboard_);
+  }
+
+  uint64_t GetSequenceNumber() {
+    base::RunLoop run_loop;
+    uint64_t sequence_num = 999999;
+    clipboard_->GetSequenceNumber(
+        mojo::Clipboard::TYPE_COPY_PASTE,
+        base::Bind(&CopyUint64AndEndRunloop, &sequence_num, &run_loop));
+    run_loop.Run();
+    return sequence_num;
+  }
+
+  std::vector<std::string> GetAvailableFormatMimeTypes() {
+    base::RunLoop run_loop;
+    std::vector<std::string> types;
+    types.push_back(kUninitialized);
+    clipboard_->GetAvailableMimeTypes(
+        mojo::Clipboard::TYPE_COPY_PASTE,
+        base::Bind(&CopyVectorStringAndEndRunloop, &types, &run_loop));
+    run_loop.Run();
+    return types;
+  }
+
+  bool GetDataOfType(const std::string& mime_type, std::string* data) {
+    base::RunLoop run_loop;
+    bool is_null = false;
+    clipboard_->ReadMimeType(
+        mojo::Clipboard::TYPE_COPY_PASTE,
+        mime_type,
+        base::Bind(&CopyStringAndEndRunloop, data, &is_null, &run_loop));
+    run_loop.Run();
+    return !is_null;
+  }
+
+  void SetStringText(const std::string& data) {
+    Array<MimeTypePairPtr> mime_data;
+    MimeTypePairPtr text_data(MimeTypePair::New());
+    text_data->mime_type = mojo::Clipboard::MIME_TYPE_TEXT;
+    text_data->data = Array<uint8_t>::From(data).Pass();
+    mime_data.push_back(text_data.Pass());
+    clipboard_->WriteClipboardData(mojo::Clipboard::TYPE_COPY_PASTE,
+                                   mime_data.Pass());
+  }
+
+ protected:
+  base::ShadowingAtExitManager at_exit_;
+  shell::ShellTestHelper test_helper_;
+
+  ClipboardPtr clipboard_;
+
+  DISALLOW_COPY_AND_ASSIGN(ClipboardStandaloneTest);
+};
+
+TEST_F(ClipboardStandaloneTest, EmptyClipboardOK) {
+  EXPECT_EQ(0ul, GetSequenceNumber());
+  EXPECT_TRUE(GetAvailableFormatMimeTypes().empty());
+  std::string data;
+  EXPECT_FALSE(GetDataOfType(mojo::Clipboard::MIME_TYPE_TEXT, &data));
+}
+
+TEST_F(ClipboardStandaloneTest, CanReadBackText) {
+  std::string data;
+  EXPECT_FALSE(GetDataOfType(mojo::Clipboard::MIME_TYPE_TEXT, &data));
+  EXPECT_EQ(0ul, GetSequenceNumber());
+
+  SetStringText(kPlainTextData);
+  EXPECT_EQ(1ul, GetSequenceNumber());
+
+  EXPECT_TRUE(GetDataOfType(mojo::Clipboard::MIME_TYPE_TEXT, &data));
+  EXPECT_EQ(kPlainTextData, data);
+}
+
+TEST_F(ClipboardStandaloneTest, CanSetMultipleDataTypesAtOnce) {
+  Array<MimeTypePairPtr> mime_data;
+  MimeTypePairPtr text_data(MimeTypePair::New());
+  text_data->mime_type = mojo::Clipboard::MIME_TYPE_TEXT;
+  text_data->data = Array<uint8_t>::From(std::string(kPlainTextData)).Pass();
+  mime_data.push_back(text_data.Pass());
+
+  MimeTypePairPtr html_data(MimeTypePair::New());
+  html_data->mime_type = mojo::Clipboard::MIME_TYPE_HTML;
+  html_data->data = Array<uint8_t>::From(std::string(kHtmlData)).Pass();
+  mime_data.push_back(html_data.Pass());
+
+  clipboard_->WriteClipboardData(mojo::Clipboard::TYPE_COPY_PASTE,
+                                 mime_data.Pass());
+
+  EXPECT_EQ(1ul, GetSequenceNumber());
+
+  std::string data;
+  EXPECT_TRUE(GetDataOfType(mojo::Clipboard::MIME_TYPE_TEXT, &data));
+  EXPECT_EQ(kPlainTextData, data);
+  EXPECT_TRUE(GetDataOfType(mojo::Clipboard::MIME_TYPE_HTML, &data));
+  EXPECT_EQ(kHtmlData, data);
+}
+
+TEST_F(ClipboardStandaloneTest, CanClearClipboardWithNull) {
+  std::string data;
+  SetStringText(kPlainTextData);
+  EXPECT_EQ(1ul, GetSequenceNumber());
+
+  EXPECT_TRUE(GetDataOfType(mojo::Clipboard::MIME_TYPE_TEXT, &data));
+  EXPECT_EQ(kPlainTextData, data);
+
+  Array<MimeTypePairPtr> mime_data;
+  clipboard_->WriteClipboardData(mojo::Clipboard::TYPE_COPY_PASTE,
+                                 mime_data.Pass());
+
+  EXPECT_EQ(2ul, GetSequenceNumber());
+  EXPECT_FALSE(GetDataOfType(mojo::Clipboard::MIME_TYPE_TEXT, &data));
+}
+
+TEST_F(ClipboardStandaloneTest, CanClearClipboardWithZeroArray) {
+  std::string data;
+  SetStringText(kPlainTextData);
+  EXPECT_EQ(1ul, GetSequenceNumber());
+
+  EXPECT_TRUE(GetDataOfType(mojo::Clipboard::MIME_TYPE_TEXT, &data));
+  EXPECT_EQ(kPlainTextData, data);
+
+  Array<MimeTypePairPtr> mime_data(0);
+  clipboard_->WriteClipboardData(mojo::Clipboard::TYPE_COPY_PASTE,
+                                 mime_data.Pass());
+
+  EXPECT_EQ(2ul, GetSequenceNumber());
+  EXPECT_FALSE(GetDataOfType(mojo::Clipboard::MIME_TYPE_TEXT, &data));
+}
+
+}  // namespace service
+}  // namespace mojo
diff --git a/mojo/services/clipboard/main.cc b/mojo/services/clipboard/main.cc
new file mode 100644
index 0000000..423aca0
--- /dev/null
+++ b/mojo/services/clipboard/main.cc
@@ -0,0 +1,45 @@
+// Copyright 2014 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/at_exit.h"
+#include "base/base_paths.h"
+#include "base/command_line.h"
+#include "base/files/file_path.h"
+#include "base/message_loop/message_loop.h"
+#include "base/path_service.h"
+#include "mojo/application/application_runner_chromium.h"
+#include "mojo/public/c/system/main.h"
+#include "mojo/public/cpp/application/application_connection.h"
+#include "mojo/public/cpp/application/application_delegate.h"
+#include "mojo/public/cpp/application/interface_factory.h"
+#include "mojo/public/cpp/bindings/interface_ptr.h"
+#include "mojo/services/clipboard/clipboard_standalone_impl.h"
+
+class Delegate : public mojo::ApplicationDelegate,
+                 public mojo::InterfaceFactory<mojo::Clipboard> {
+ public:
+  Delegate() {}
+  virtual ~Delegate() {}
+
+  // mojo::ApplicationDelegate implementation.
+  virtual bool ConfigureIncomingConnection(
+      mojo::ApplicationConnection* connection) OVERRIDE {
+    connection->AddService(this);
+    return true;
+  }
+
+  // mojo::InterfaceFactory<mojo::Clipboard> implementation.
+  virtual void Create(
+      mojo::ApplicationConnection* connection,
+      mojo::InterfaceRequest<mojo::Clipboard> request) OVERRIDE {
+    // TODO(erg): Write native implementations of the clipboard. For now, we
+    // just build a clipboard which doesn't interact with the system.
+    mojo::BindToRequest(new mojo::ClipboardStandaloneImpl(), &request);
+  }
+};
+
+MojoResult MojoMain(MojoHandle shell_handle) {
+  mojo::ApplicationRunnerChromium runner(new Delegate);
+  return runner.Run(shell_handle);
+}
diff --git a/mojo/services/html_viewer/blink_basic_type_converters.cc b/mojo/services/html_viewer/blink_basic_type_converters.cc
index 23b9e5f..eb52652 100644
--- a/mojo/services/html_viewer/blink_basic_type_converters.cc
+++ b/mojo/services/html_viewer/blink_basic_type_converters.cc
@@ -21,4 +21,14 @@
   return WebString::fromUTF8(str.get());
 }
 
+// static
+Array<uint8_t> TypeConverter<Array<uint8_t>, blink::WebString>::Convert(
+    const blink::WebString& input) {
+  std::string utf8 = input.utf8();
+  Array<uint8_t> result(utf8.size());
+  for (size_t i = 0; i < utf8.size(); ++i)
+    result[i] = utf8[i];
+  return result.Pass();
+}
+
 }  // namespace mojo
diff --git a/mojo/services/html_viewer/blink_basic_type_converters.h b/mojo/services/html_viewer/blink_basic_type_converters.h
index cb3b803..60ddeee 100644
--- a/mojo/services/html_viewer/blink_basic_type_converters.h
+++ b/mojo/services/html_viewer/blink_basic_type_converters.h
@@ -25,6 +25,10 @@
 struct TypeConverter<blink::WebString, String> {
   static blink::WebString Convert(const String& str);
 };
+template <>
+struct TypeConverter<Array<uint8_t>, blink::WebString> {
+  static Array<uint8_t> Convert(const blink::WebString& input);
+};
 
 template<typename T, typename U>
 struct TypeConverter<Array<T>, blink::WebVector<U> > {
diff --git a/mojo/services/html_viewer/blink_platform_impl.cc b/mojo/services/html_viewer/blink_platform_impl.cc
index 1d19b83..315a850 100644
--- a/mojo/services/html_viewer/blink_platform_impl.cc
+++ b/mojo/services/html_viewer/blink_platform_impl.cc
@@ -11,6 +11,7 @@
 #include "base/synchronization/waitable_event.h"
 #include "base/time/time.h"
 #include "mojo/public/cpp/application/application_impl.h"
+#include "mojo/services/html_viewer/webclipboard_impl.h"
 #include "mojo/services/html_viewer/webcookiejar_impl.h"
 #include "mojo/services/html_viewer/websockethandle_impl.h"
 #include "mojo/services/html_viewer/webthread_impl.h"
@@ -59,6 +60,10 @@
   CookieStorePtr cookie_store;
   network_service_->GetCookieStore(Get(&cookie_store));
   cookie_jar_.reset(new WebCookieJarImpl(cookie_store.Pass()));
+
+  ClipboardPtr clipboard;
+  app->ConnectToService("mojo:mojo_clipboard", &clipboard);
+  clipboard_.reset(new WebClipboardImpl(clipboard.Pass()));
 }
 
 BlinkPlatformImpl::~BlinkPlatformImpl() {
@@ -68,6 +73,10 @@
   return cookie_jar_.get();
 }
 
+blink::WebClipboard* BlinkPlatformImpl::clipboard() {
+  return clipboard_.get();
+}
+
 blink::WebMimeRegistry* BlinkPlatformImpl::mimeRegistry() {
   return &mime_registry_;
 }
diff --git a/mojo/services/html_viewer/blink_platform_impl.h b/mojo/services/html_viewer/blink_platform_impl.h
index aeae757..522f98d 100644
--- a/mojo/services/html_viewer/blink_platform_impl.h
+++ b/mojo/services/html_viewer/blink_platform_impl.h
@@ -18,6 +18,7 @@
 
 namespace mojo {
 class ApplicationImpl;
+class WebClipboardImpl;
 class WebCookieJarImpl;
 
 class BlinkPlatformImpl : public blink::Platform {
@@ -27,6 +28,7 @@
 
   // blink::Platform methods:
   virtual blink::WebCookieJar* cookieJar();
+  virtual blink::WebClipboard* clipboard();
   virtual blink::WebMimeRegistry* mimeRegistry();
   virtual blink::WebThemeEngine* themeEngine();
   virtual blink::WebString defaultLocale();
@@ -78,6 +80,7 @@
   cc_blink::WebCompositorSupportImpl compositor_support_;
   WebThemeEngineImpl theme_engine_;
   scoped_ptr<WebCookieJarImpl> cookie_jar_;
+  scoped_ptr<WebClipboardImpl> clipboard_;
   WebMimeRegistryImpl mime_registry_;
   blink::WebScrollbarBehavior scrollbar_behavior_;
 
diff --git a/mojo/services/html_viewer/webclipboard_impl.cc b/mojo/services/html_viewer/webclipboard_impl.cc
new file mode 100644
index 0000000..94b16ca
--- /dev/null
+++ b/mojo/services/html_viewer/webclipboard_impl.cc
@@ -0,0 +1,222 @@
+// Copyright 2014 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 "mojo/services/html_viewer/webclipboard_impl.h"
+
+#include "base/bind.h"
+#include "mojo/services/html_viewer/blink_basic_type_converters.h"
+
+namespace mojo {
+namespace {
+
+void CopyUint64(uint64_t* output, uint64_t input) {
+  *output = input;
+}
+
+void CopyWebString(blink::WebString* output,
+                   const mojo::Array<uint8_t>& input) {
+  // blink does not differentiate between the requested data type not existing
+  // and the empty string.
+  if (input.is_null()) {
+    output->reset();
+  } else {
+    *output = blink::WebString::fromUTF8(
+        reinterpret_cast<const char*>(&input.front()),
+        input.size());
+  }
+}
+
+void CopyURL(blink::WebURL* pageURL,
+             const mojo::Array<uint8_t>& input) {
+  if (input.is_null()) {
+    *pageURL = blink::WebURL();
+  } else {
+    *pageURL = GURL(std::string(reinterpret_cast<const char*>(&input.front()),
+                                input.size()));
+  }
+}
+
+void CopyVectorString(std::vector<std::string>* output,
+                      const Array<String>& input) {
+  *output = input.To<std::vector<std::string> >();
+}
+
+template <typename T, typename U>
+bool Contains(const std::vector<T>& v, const U& item) {
+  return std::find(v.begin(), v.end(), item) != v.end();
+}
+
+const char kMimeTypeWebkitSmartPaste[] = "chromium/x-webkit-paste";
+
+}  // namespace
+
+WebClipboardImpl::WebClipboardImpl(ClipboardPtr clipboard)
+    : clipboard_(clipboard.Pass()) {
+}
+
+WebClipboardImpl::~WebClipboardImpl() {
+}
+
+uint64_t WebClipboardImpl::sequenceNumber(Buffer buffer) {
+  mojo::Clipboard::Type clipboard_type = ConvertBufferType(buffer);
+
+  uint64_t number = 0;
+  clipboard_->GetSequenceNumber(clipboard_type,
+                                base::Bind(&CopyUint64, &number));
+
+  // Force this to be synchronous.
+  clipboard_.WaitForIncomingMethodCall();
+  return number;
+}
+
+bool WebClipboardImpl::isFormatAvailable(Format format, Buffer buffer) {
+  mojo::Clipboard::Type clipboard_type = ConvertBufferType(buffer);
+
+  std::vector<std::string> types;
+  clipboard_->GetAvailableMimeTypes(
+      clipboard_type, base::Bind(&CopyVectorString, &types));
+
+  // Force this to be synchronous.
+  clipboard_.WaitForIncomingMethodCall();
+
+  switch (format) {
+    case FormatPlainText:
+      return Contains(types, mojo::Clipboard::MIME_TYPE_TEXT);
+    case FormatHTML:
+      return Contains(types, mojo::Clipboard::MIME_TYPE_HTML);
+    case FormatSmartPaste:
+      return Contains(types, kMimeTypeWebkitSmartPaste);
+    case FormatBookmark:
+      // This might be difficult.
+      return false;
+  }
+
+  return false;
+}
+
+blink::WebVector<blink::WebString> WebClipboardImpl::readAvailableTypes(
+    Buffer buffer,
+    bool* containsFilenames) {
+  mojo::Clipboard::Type clipboard_type = ConvertBufferType(buffer);
+
+  std::vector<std::string> types;
+  clipboard_->GetAvailableMimeTypes(
+      clipboard_type, base::Bind(&CopyVectorString, &types));
+
+  // Force this to be synchronous.
+  clipboard_.WaitForIncomingMethodCall();
+
+  // AFAICT, every instance of setting containsFilenames is false.
+  *containsFilenames = false;
+
+  blink::WebVector<blink::WebString> output(types.size());
+  for (size_t i = 0; i < types.size(); ++i) {
+    output[i] = blink::WebString::fromUTF8(types[i]);
+  }
+
+  return output;
+}
+
+blink::WebString WebClipboardImpl::readPlainText(Buffer buffer) {
+  mojo::Clipboard::Type type = ConvertBufferType(buffer);
+
+  blink::WebString text;
+  clipboard_->ReadMimeType(
+      type, mojo::Clipboard::MIME_TYPE_TEXT, base::Bind(&CopyWebString, &text));
+
+  // Force this to be synchronous.
+  clipboard_.WaitForIncomingMethodCall();
+
+  return text;
+}
+
+blink::WebString WebClipboardImpl::readHTML(Buffer buffer,
+                                            blink::WebURL* pageURL,
+                                            unsigned* fragmentStart,
+                                            unsigned* fragmentEnd) {
+  mojo::Clipboard::Type type = ConvertBufferType(buffer);
+
+  blink::WebString html;
+  clipboard_->ReadMimeType(
+      type, mojo::Clipboard::MIME_TYPE_HTML, base::Bind(&CopyWebString, &html));
+  clipboard_.WaitForIncomingMethodCall();
+
+  *fragmentStart = 0;
+  *fragmentEnd = static_cast<unsigned>(html.length());
+
+  clipboard_->ReadMimeType(
+      type, mojo::Clipboard::MIME_TYPE_URL, base::Bind(&CopyURL, pageURL));
+  clipboard_.WaitForIncomingMethodCall();
+
+  return html;
+}
+
+blink::WebString WebClipboardImpl::readCustomData(
+    Buffer buffer,
+    const blink::WebString& mime_type) {
+  mojo::Clipboard::Type clipboard_type = ConvertBufferType(buffer);
+
+  blink::WebString data;
+  clipboard_->ReadMimeType(
+      clipboard_type, mime_type.utf8(), base::Bind(&CopyWebString, &data));
+
+  // Force this to be synchronous.
+  clipboard_.WaitForIncomingMethodCall();
+
+  return data;
+}
+
+void WebClipboardImpl::writePlainText(const blink::WebString& text) {
+  Array<MimeTypePairPtr> data;
+  MimeTypePairPtr text_data(MimeTypePair::New());
+  text_data->mime_type = mojo::Clipboard::MIME_TYPE_TEXT;
+  text_data->data = Array<uint8_t>::From(text).Pass();
+  data.push_back(text_data.Pass());
+
+  clipboard_->WriteClipboardData(mojo::Clipboard::TYPE_COPY_PASTE, data.Pass());
+}
+
+void WebClipboardImpl::writeHTML(const blink::WebString& htmlText,
+                                 const blink::WebURL& url,
+                                 const blink::WebString& plainText,
+                                 bool writeSmartPaste) {
+  Array<MimeTypePairPtr> data;
+  MimeTypePairPtr text_data(MimeTypePair::New());
+  text_data->mime_type = mojo::Clipboard::MIME_TYPE_TEXT;
+  text_data->data = Array<uint8_t>::From(plainText).Pass();
+  data.push_back(text_data.Pass());
+
+  MimeTypePairPtr html_data(MimeTypePair::New());
+  text_data->mime_type = mojo::Clipboard::MIME_TYPE_HTML;
+  text_data->data = Array<uint8_t>::From(htmlText).Pass();
+  data.push_back(html_data.Pass());
+
+  MimeTypePairPtr url_data(MimeTypePair::New());
+  url_data->mime_type = mojo::Clipboard::MIME_TYPE_URL;
+  url_data->data = Array<uint8_t>::From(url.string()).Pass();
+  data.push_back(url_data.Pass());
+
+  if (writeSmartPaste) {
+    MimeTypePairPtr smart_paste(MimeTypePair::New());
+    url_data->mime_type = kMimeTypeWebkitSmartPaste;
+    url_data->data = Array<uint8_t>::From(blink::WebString()).Pass();
+    data.push_back(smart_paste.Pass());
+  }
+
+  clipboard_->WriteClipboardData(mojo::Clipboard::TYPE_COPY_PASTE, data.Pass());
+}
+
+mojo::Clipboard::Type WebClipboardImpl::ConvertBufferType(Buffer buffer) {
+  switch (buffer) {
+    case BufferStandard:
+      return mojo::Clipboard::TYPE_COPY_PASTE;
+    case BufferSelection:
+      return mojo::Clipboard::TYPE_SELECTION;
+  }
+
+  NOTREACHED();
+  return mojo::Clipboard::TYPE_COPY_PASTE;
+}
+
+}  // namespace mojo
diff --git a/mojo/services/html_viewer/webclipboard_impl.h b/mojo/services/html_viewer/webclipboard_impl.h
new file mode 100644
index 0000000..0c307c5
--- /dev/null
+++ b/mojo/services/html_viewer/webclipboard_impl.h
@@ -0,0 +1,48 @@
+// Copyright 2014 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 MOJO_SERVICES_HTML_VIEWER_WEBCLIPBOARD_IMPL_H_
+#define MOJO_SERVICES_HTML_VIEWER_WEBCLIPBOARD_IMPL_H_
+
+#include "mojo/services/public/interfaces/clipboard/clipboard.mojom.h"
+#include "third_party/WebKit/public/platform/WebClipboard.h"
+
+namespace mojo {
+
+class WebClipboardImpl : public blink::WebClipboard {
+ public:
+  WebClipboardImpl(ClipboardPtr clipboard);
+  virtual ~WebClipboardImpl();
+
+  // blink::WebClipboard methods:
+  virtual uint64_t sequenceNumber(Buffer);
+  virtual bool isFormatAvailable(Format, Buffer);
+  virtual blink::WebVector<blink::WebString> readAvailableTypes(
+      Buffer,
+      bool* containsFilenames);
+  virtual blink::WebString readPlainText(Buffer);
+  virtual blink::WebString readHTML(Buffer buffer,
+                                    blink::WebURL* pageURL,
+                                    unsigned* fragmentStart,
+                                    unsigned* fragmentEnd);
+  // TODO(erg): readImage()
+  virtual blink::WebString readCustomData(Buffer, const blink::WebString& type);
+  virtual void writePlainText(const blink::WebString&);
+  virtual void writeHTML(const blink::WebString& htmlText,
+                         const blink::WebURL&,
+                         const blink::WebString& plainText,
+                         bool writeSmartPaste);
+
+ private:
+  // Changes webkit buffers to mojo Clipboard::Types.
+  mojo::Clipboard::Type ConvertBufferType(Buffer buffer);
+
+  ClipboardPtr clipboard_;
+
+  DISALLOW_COPY_AND_ASSIGN(WebClipboardImpl);
+};
+
+}  // namespace mojo
+
+#endif  // MOJO_SERVICES_HTML_VIEWER_WEBCLIPBOARD_IMPL_H_
diff --git a/mojo/services/public/interfaces/clipboard/clipboard.mojom b/mojo/services/public/interfaces/clipboard/clipboard.mojom
new file mode 100644
index 0000000..c1e1be4
--- /dev/null
+++ b/mojo/services/public/interfaces/clipboard/clipboard.mojom
@@ -0,0 +1,52 @@
+// Copyright 2014 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.
+
+module mojo {
+
+// A wrapper type which is just a Key/Value pair. Workaround until we get
+// proper maps in mojom.
+struct MimeTypePair {
+  string mime_type;
+  uint8[] data;
+};
+
+interface Clipboard {
+  enum Type {
+    COPY_PASTE = 0,
+    SELECTION = 1,
+    DRAG = 2
+  };
+
+  // Mime type constants
+  const string MIME_TYPE_TEXT = "text/plain";
+  const string MIME_TYPE_HTML = "text/html";
+  const string MIME_TYPE_URL = "text/url";
+
+  // Returns a sequence number which uniquely identifies clipboard state.
+  // Clients are able to assume that the clipboard contents are unchanged as
+  // long as this number has not changed. This number is monotonically
+  // increasing, is increased when the clipboard state changes, and is
+  // provided by Windows, Linux, and Mac.
+  GetSequenceNumber(Type clipboard_type) => (uint64 sequence);
+
+  // Returns the available mime types. (Note: the chrome interface has a
+  // |contains_filenames| parameter here, but it appears to always be set
+  // to false.)
+  GetAvailableMimeTypes(Type clipboard_types) => (string[] types);
+
+  // Returns the data associated with a Mime type, returning NULL if that data
+  // doesn't exist. Note: because of the inherit raciness of clipboard access,
+  // this may return NULL even if you just verified that it exists with
+  // GetAvailableFormatMimeTypes(). We don't want to provide one API to return
+  // the entire clipboard state because the combined size of the clipboard can
+  // be megabytes, especially when image data is involved.
+  ReadMimeType(Type clipboard_type, string mime_type) => (uint8[]? data);
+
+  // Writes a set of mime types to the clipboard. This will increment the
+  // sequence number. In the case of an empty or NULL list, this will just
+  // clear the clipboard.
+  WriteClipboardData(Type clipboard_type, MimeTypePair[]? data);
+};
+
+}  // module mojo