From 8f9aefdd9324680937c83b2137ecba8584322245 Mon Sep 17 00:00:00 2001
From: "bulach@chromium.org"
 <bulach@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>
Date: Mon, 25 Jan 2010 13:50:48 +0000
Subject: Adds local storage nodes to cookie tree model and cookies view.

BUG=none
TEST=The show cookie dialog box should have a new node "local storage" when appropriate. When selected, it should display details of local storage (name, size on disk, last modified) in the details frame.
Review URL: http://codereview.chromium.org/523139

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@37001 0039d316-1c4b-4281-b951-d872f2087c98
---
 chrome/app/generated_resources.grd                 |   9 +
 .../browser/browsing_data_local_storage_helper.cc  | 125 +++++++
 .../browser/browsing_data_local_storage_helper.h   |  98 ++++++
 .../browsing_data_local_storage_helper_unittest.cc | 170 ++++++++++
 chrome/browser/cocoa/cookies_window_controller.h   |   4 +-
 chrome/browser/cocoa/cookies_window_controller.mm  |   6 +-
 .../cocoa/cookies_window_controller_unittest.mm    |  32 +-
 .../browser/cocoa/preferences_window_controller.mm |   5 +-
 chrome/browser/cookies_tree_model.cc               | 124 ++++++-
 chrome/browser/cookies_tree_model.h                | 105 +++++-
 chrome/browser/cookies_tree_model_unittest.cc      | 230 ++++++++-----
 .../browser/gtk/options/advanced_contents_gtk.cc   |   4 +-
 chrome/browser/gtk/options/cookies_view.cc         | 152 +++++++--
 chrome/browser/gtk/options/cookies_view.h          |  35 +-
 .../browser/gtk/options/cookies_view_unittest.cc   | 369 +++++++++++++++++----
 .../in_process_webkit/dom_storage_context.cc       |  48 ++-
 .../in_process_webkit/dom_storage_context.h        |  12 +
 .../mock_browsing_data_local_storage_helper.cc     |  47 +++
 .../mock_browsing_data_local_storage_helper.h      |  37 +++
 chrome/browser/views/options/cookies_view.cc       | 166 ++++++++-
 chrome/browser/views/options/cookies_view.h        |  57 +++-
 chrome/chrome_browser.gypi                         |   2 +
 chrome/chrome_tests.gypi                           |   5 +-
 23 files changed, 1610 insertions(+), 232 deletions(-)
 create mode 100644 chrome/browser/browsing_data_local_storage_helper.cc
 create mode 100644 chrome/browser/browsing_data_local_storage_helper.h
 create mode 100644 chrome/browser/browsing_data_local_storage_helper_unittest.cc
 create mode 100644 chrome/browser/mock_browsing_data_local_storage_helper.cc
 create mode 100644 chrome/browser/mock_browsing_data_local_storage_helper.h

(limited to 'chrome')

diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd
index 3ca3859..0f7dca4 100644
--- a/chrome/app/generated_resources.grd
+++ b/chrome/app/generated_resources.grd
@@ -5193,6 +5193,15 @@ Keep your key file in a safe place. You will need it to create new versions of y
       <message name="IDS_COOKIES_DOMAIN_COLUMN_HEADER" desc="The label of the Domain header in the Cookies table">
         Site
       </message>
+      <message name="IDS_COOKIES_LOCAL_STORAGE_ORIGIN_LABEL" desc="The Local Storage Origin label">
+        Origin:
+      </message>
+      <message name="IDS_COOKIES_LOCAL_STORAGE_SIZE_ON_DISK_LABEL" desc="The Local Storage Size on disk label">
+        Size on disk:
+      </message>
+      <message name="IDS_COOKIES_LOCAL_STORAGE_LAST_MODIFIED_LABEL" desc="The Local Storage Last modified label">
+        Last modified:
+      </message>
       <message name="IDS_COOKIES_NAME_COLUMN_HEADER" desc="The label of the Cookie Name header in the Cookies table">
         Cookie Name
       </message>
diff --git a/chrome/browser/browsing_data_local_storage_helper.cc b/chrome/browser/browsing_data_local_storage_helper.cc
new file mode 100644
index 0000000..d203563
--- /dev/null
+++ b/chrome/browser/browsing_data_local_storage_helper.cc
@@ -0,0 +1,125 @@
+// 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 "chrome/browser/browsing_data_local_storage_helper.h"
+
+#include "base/file_util.h"
+#include "base/message_loop.h"
+#include "chrome/browser/chrome_thread.h"
+#include "chrome/browser/in_process_webkit/webkit_context.h"
+#include "chrome/browser/profile.h"
+#include "third_party/WebKit/WebKit/chromium/public/WebSecurityOrigin.h"
+#include "third_party/WebKit/WebKit/chromium/public/WebString.h"
+#include "webkit/glue/glue_util.h"
+#include "webkit/glue/webkit_glue.h"
+
+BrowsingDataLocalStorageHelper::BrowsingDataLocalStorageHelper(
+    Profile* profile)
+    : profile_(profile),
+      completion_callback_(NULL),
+      is_fetching_(false) {
+  DCHECK(profile_);
+}
+
+void BrowsingDataLocalStorageHelper::StartFetching(
+    Callback1<const std::vector<LocalStorageInfo>& >::Type* callback) {
+  DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
+  DCHECK(!is_fetching_);
+  DCHECK(callback);
+  is_fetching_ = true;
+  completion_callback_.reset(callback);
+  ChromeThread::PostTask(
+      ChromeThread::WEBKIT, FROM_HERE,
+      NewRunnableMethod(
+          this,
+          &BrowsingDataLocalStorageHelper::
+              FetchLocalStorageInfoInWebKitThread));
+}
+
+void BrowsingDataLocalStorageHelper::CancelNotification() {
+  DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
+  completion_callback_.reset(NULL);
+}
+
+void BrowsingDataLocalStorageHelper::DeleteLocalStorageFile(
+    const FilePath& file_path) {
+  DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
+  ChromeThread::PostTask(
+      ChromeThread::WEBKIT, FROM_HERE,
+       NewRunnableMethod(
+           this,
+           &BrowsingDataLocalStorageHelper::
+              DeleteLocalStorageFileInWebKitThread,
+           file_path));
+}
+
+void BrowsingDataLocalStorageHelper::DeleteAllLocalStorageFiles() {
+  DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
+  ChromeThread::PostTask(
+      ChromeThread::WEBKIT, FROM_HERE,
+      NewRunnableMethod(
+          this,
+          &BrowsingDataLocalStorageHelper::
+              DeleteAllLocalStorageFilesInWebKitThread));
+}
+
+void BrowsingDataLocalStorageHelper::FetchLocalStorageInfoInWebKitThread() {
+  DCHECK(ChromeThread::CurrentlyOn(ChromeThread::WEBKIT));
+  file_util::FileEnumerator file_enumerator(
+      profile_->GetWebKitContext()->data_path().Append(
+          DOMStorageContext::kLocalStorageDirectory),
+      false, file_util::FileEnumerator::FILES);
+  for (FilePath file_path = file_enumerator.Next(); !file_path.empty();
+       file_path = file_enumerator.Next()) {
+    if (file_path.Extension() == DOMStorageContext::kLocalStorageExtension) {
+      scoped_ptr<WebKit::WebSecurityOrigin> web_security_origin(
+          WebKit::WebSecurityOrigin::createFromDatabaseIdentifier(
+              webkit_glue::FilePathToWebString(file_path.BaseName())));
+      file_util::FileInfo file_info;
+      bool ret = file_util::GetFileInfo(file_path, &file_info);
+      if (ret) {
+        local_storage_info_.push_back(LocalStorageInfo(
+            webkit_glue::WebStringToStdString(web_security_origin->protocol()),
+            webkit_glue::WebStringToStdString(web_security_origin->host()),
+            web_security_origin->port(),
+            webkit_glue::WebStringToStdString(
+                web_security_origin->databaseIdentifier()),
+            webkit_glue::WebStringToStdString(
+                web_security_origin->toString()),
+            file_path,
+            file_info.size,
+            file_info.last_modified));
+      }
+    }
+  }
+
+  ChromeThread::PostTask(
+      ChromeThread::UI, FROM_HERE,
+      NewRunnableMethod(
+          this, &BrowsingDataLocalStorageHelper::NotifyInUIThread));
+}
+
+void BrowsingDataLocalStorageHelper::NotifyInUIThread() {
+  DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
+  DCHECK(is_fetching_);
+  // Note: completion_callback_ mutates only in the UI thread, so it's safe to
+  // test it here.
+  if (completion_callback_ != NULL)
+    completion_callback_->Run(local_storage_info_);
+  is_fetching_ = false;
+}
+
+void BrowsingDataLocalStorageHelper::DeleteLocalStorageFileInWebKitThread(
+    const FilePath& file_path) {
+  DCHECK(ChromeThread::CurrentlyOn(ChromeThread::WEBKIT));
+  profile_->GetWebKitContext()->dom_storage_context()->DeleteLocalStorageFile(
+      file_path);
+}
+
+void
+    BrowsingDataLocalStorageHelper::DeleteAllLocalStorageFilesInWebKitThread() {
+  DCHECK(ChromeThread::CurrentlyOn(ChromeThread::WEBKIT));
+  profile_->GetWebKitContext()->dom_storage_context()
+      ->DeleteAllLocalStorageFiles();
+}
diff --git a/chrome/browser/browsing_data_local_storage_helper.h b/chrome/browser/browsing_data_local_storage_helper.h
new file mode 100644
index 0000000..f9d9398d
--- /dev/null
+++ b/chrome/browser/browsing_data_local_storage_helper.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 CHROME_BROWSER_BROWSING_DATA_LOCAL_STORAGE_HELPER_H_
+#define CHROME_BROWSER_BROWSING_DATA_LOCAL_STORAGE_HELPER_H_
+
+#include <string>
+
+#include "base/file_path.h"
+#include "base/scoped_ptr.h"
+#include "base/task.h"
+
+class Profile;
+
+// This class fetches local storage information in the WebKit thread, and
+// notifies the UI thread upon completion.
+// A client of this class need to call StartFetching from the UI thread to
+// initiate the flow, and it'll be notified by the callback in its UI
+// thread at some later point.
+// The client must call CancelNotification() if it's destroyed before the
+// callback is notified.
+class BrowsingDataLocalStorageHelper
+    : public base::RefCountedThreadSafe<BrowsingDataLocalStorageHelper> {
+ public:
+  // Contains detailed information about local storage.
+  struct LocalStorageInfo {
+    LocalStorageInfo(
+        const std::string& protocol,
+        const std::string& host,
+        unsigned short port,
+        const std::string& database_identifier,
+        const std::string& origin,
+        const FilePath& file_path,
+        int64 size,
+        base::Time last_modified)
+        : protocol(protocol),
+          host(host),
+          port(port),
+          database_identifier(database_identifier),
+          origin(origin),
+          file_path(file_path),
+          size(size),
+          last_modified(last_modified) {
+    }
+
+    std::string protocol;
+    std::string host;
+    unsigned short port;
+    std::string database_identifier;
+    std::string origin;
+    FilePath file_path;
+    int64 size;
+    base::Time last_modified;
+  };
+
+  explicit BrowsingDataLocalStorageHelper(Profile* profile);
+
+  // Starts the fetching process, which will notify its completion via
+  // callback.
+  // This must be called only in the UI thread.
+  virtual void StartFetching(
+      Callback1<const std::vector<LocalStorageInfo>& >::Type* callback);
+  // Cancels the notification callback (i.e., the window that created it no
+  // longer exists).
+  // This must be called only in the UI thread.
+  virtual void CancelNotification();
+  // Requests a single local storage file to be deleted in the WEBKIT thread.
+  virtual void DeleteLocalStorageFile(const FilePath& file_path);
+  // Requests all local storage files to be deleted in the WEBKIT thread.
+  virtual void DeleteAllLocalStorageFiles();
+
+ private:
+  // Enumerates all local storage files in the WEBKIT thread.
+  void FetchLocalStorageInfoInWebKitThread();
+  // Notifies the completion callback in the UI thread.
+  void NotifyInUIThread();
+  // Delete a single local storage file in the WEBKIT thread.
+  void DeleteLocalStorageFileInWebKitThread(const FilePath& file_path);
+  // Delete all local storage files in the WEBKIT thread.
+  void DeleteAllLocalStorageFilesInWebKitThread();
+
+  Profile* profile_;
+  // This only mutates on the UI thread.
+  scoped_ptr<Callback1<const std::vector<LocalStorageInfo>& >::Type >
+      completion_callback_;
+  // Indicates whether or not we're currently fetching information:
+  // it's true when StartFetching() is called in the UI thread, and it's reset
+  // after we notified the callback in the UI thread.
+  // This only mutates on the UI thread.
+  bool is_fetching_;
+  // This only mutates in the WEBKIT thread.
+  std::vector<LocalStorageInfo> local_storage_info_;
+
+  DISALLOW_COPY_AND_ASSIGN(BrowsingDataLocalStorageHelper);
+};
+
+#endif  // CHROME_BROWSER_BROWSING_DATA_LOCAL_STORAGE_HELPER_H_
diff --git a/chrome/browser/browsing_data_local_storage_helper_unittest.cc b/chrome/browser/browsing_data_local_storage_helper_unittest.cc
new file mode 100644
index 0000000..f36b724
--- /dev/null
+++ b/chrome/browser/browsing_data_local_storage_helper_unittest.cc
@@ -0,0 +1,170 @@
+// 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 <string>
+
+#include "base/basictypes.h"
+#include "base/file_path.h"
+#include "base/ref_counted.h"
+#include "chrome/browser/in_process_webkit/webkit_context.h"
+#include "chrome/browser/in_process_webkit/webkit_thread.h"
+#include "chrome/browser/browsing_data_local_storage_helper.h"
+#include "chrome/test/in_process_browser_test.h"
+#include "chrome/test/testing_profile.h"
+#include "chrome/test/ui_test_utils.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+static const FilePath::CharType kTestFile0[] =
+    FILE_PATH_LITERAL("http_www.chromium.org_0.localstorage");
+
+static const FilePath::CharType kTestFile1[] =
+    FILE_PATH_LITERAL("http_www.google.com_0.localstorage");
+
+static const FilePath::CharType kTestFileInvalid[] =
+    FILE_PATH_LITERAL("http_www.google.com_localstorage_0.foo");
+
+class BrowsingDataLocalStorageHelperTest : public InProcessBrowserTest {
+ protected:
+  void CreateLocalStorageFilesForTest() {
+    FilePath storage_path = GetLocalStoragePathForTestingProfile();
+    file_util::CreateDirectory(storage_path);
+    const FilePath::CharType* kFilesToCreate[] = {
+        kTestFile0, kTestFile1, kTestFileInvalid,
+    };
+    for (size_t i = 0; i < arraysize(kFilesToCreate); ++i) {
+      FilePath file_path = storage_path.Append(kFilesToCreate[i]);
+      file_util::WriteFile(file_path, NULL, 0);
+    }
+  }
+
+  FilePath GetLocalStoragePathForTestingProfile() {
+    FilePath storage_path(testing_profile_.GetPath());
+    storage_path = storage_path.Append(
+        DOMStorageContext::kLocalStorageDirectory);
+    return storage_path;
+  }
+  TestingProfile testing_profile_;
+};
+
+// This class is notified by BrowsingDataLocalStorageHelper on the UI thread
+// once it finishes fetching the local storage data.
+class StopTestOnCallback {
+ public:
+  explicit StopTestOnCallback(
+      BrowsingDataLocalStorageHelper* local_storage_helper)
+      : local_storage_helper_(local_storage_helper) {
+    DCHECK(local_storage_helper_);
+  }
+
+  void Callback(
+      const std::vector<BrowsingDataLocalStorageHelper::LocalStorageInfo>&
+      local_storage_info) {
+    DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
+    // There's no guarantee on the order, ensure these files are there.
+    const char* const kTestHosts[] = {"www.chromium.org", "www.google.com"};
+    bool test_hosts_found[arraysize(kTestHosts)] = {false, false};
+    ASSERT_EQ(arraysize(kTestHosts), local_storage_info.size());
+    for (size_t i = 0; i < arraysize(kTestHosts); ++i) {
+      for (size_t j = 0; j < local_storage_info.size(); ++j) {
+        BrowsingDataLocalStorageHelper::LocalStorageInfo info =
+            local_storage_info.at(j);
+        ASSERT_EQ("http", info.protocol);
+        if (info.host == kTestHosts[i]) {
+          ASSERT_FALSE(test_hosts_found[i]);
+          test_hosts_found[i] = true;
+        }
+      }
+    }
+    for (size_t i = 0; i < arraysize(kTestHosts); ++i) {
+      ASSERT_TRUE(test_hosts_found[i]) << kTestHosts[i];
+    }
+    MessageLoop::current()->Quit();
+  }
+
+ private:
+  BrowsingDataLocalStorageHelper* local_storage_helper_;
+};
+
+IN_PROC_BROWSER_TEST_F(BrowsingDataLocalStorageHelperTest, CallbackCompletes) {
+  scoped_refptr<BrowsingDataLocalStorageHelper> local_storage_helper(
+      new BrowsingDataLocalStorageHelper(&testing_profile_));
+  CreateLocalStorageFilesForTest();
+  StopTestOnCallback stop_test_on_callback(local_storage_helper);
+  local_storage_helper->StartFetching(
+      NewCallback(&stop_test_on_callback, &StopTestOnCallback::Callback));
+  // Blocks until StopTestOnCallback::Callback is notified.
+  ui_test_utils::RunMessageLoop();
+}
+
+class WaitForWebKitThread
+    : public base::RefCountedThreadSafe<WaitForWebKitThread> {
+ public:
+  void QuitUiMessageLoopAfterWebKitThreadNotified() {
+    ChromeThread::PostTask(ChromeThread::WEBKIT,
+                           FROM_HERE,
+                           NewRunnableMethod(
+                               this, &WaitForWebKitThread::RunInWebKitThread));
+  }
+
+ private:
+  void RunInWebKitThread() {
+    ChromeThread::PostTask(ChromeThread::UI,
+                           FROM_HERE,
+                           NewRunnableMethod(
+                               this, &WaitForWebKitThread::RunInUiThread));
+  }
+
+  void RunInUiThread() {
+    MessageLoop::current()->Quit();
+  }
+};
+
+IN_PROC_BROWSER_TEST_F(BrowsingDataLocalStorageHelperTest, DeleteSingleFile) {
+  scoped_refptr<BrowsingDataLocalStorageHelper> local_storage_helper(
+      new BrowsingDataLocalStorageHelper(&testing_profile_));
+  CreateLocalStorageFilesForTest();
+  local_storage_helper->DeleteLocalStorageFile(
+      GetLocalStoragePathForTestingProfile().Append(FilePath(kTestFile0)));
+  scoped_refptr<WaitForWebKitThread> wait_for_webkit_thread(
+      new WaitForWebKitThread);
+  wait_for_webkit_thread->QuitUiMessageLoopAfterWebKitThreadNotified();
+  // Blocks until WaitForWebKitThread is notified.
+  ui_test_utils::RunMessageLoop();
+  // Ensure the file has been deleted.
+  file_util::FileEnumerator file_enumerator(
+      GetLocalStoragePathForTestingProfile(),
+      false,
+      file_util::FileEnumerator::FILES);
+  int num_files = 0;
+  for (FilePath file_path = file_enumerator.Next();
+       !file_path.empty();
+       file_path = file_enumerator.Next()) {
+    ASSERT_FALSE(FilePath(kTestFile0) == file_path.BaseName());
+    ++num_files;
+  }
+  ASSERT_EQ(2, num_files);
+}
+
+IN_PROC_BROWSER_TEST_F(BrowsingDataLocalStorageHelperTest, DeleteAllFiles) {
+  scoped_refptr<BrowsingDataLocalStorageHelper> local_storage_helper(
+      new BrowsingDataLocalStorageHelper(&testing_profile_));
+  CreateLocalStorageFilesForTest();
+  local_storage_helper->DeleteAllLocalStorageFiles();
+  scoped_refptr<WaitForWebKitThread> wait_for_webkit_thread(
+      new WaitForWebKitThread);
+  wait_for_webkit_thread->QuitUiMessageLoopAfterWebKitThreadNotified();
+  // Blocks until WaitForWebKitThread is notified.
+  ui_test_utils::RunMessageLoop();
+  // Ensure the alls files but the one without local storage extension have been
+  // deleted.
+  file_util::FileEnumerator file_enumerator(
+      GetLocalStoragePathForTestingProfile(),
+      false,
+      file_util::FileEnumerator::FILES);
+  for (FilePath file_path = file_enumerator.Next();
+       !file_path.empty();
+       file_path = file_enumerator.Next()) {
+    ASSERT_TRUE(FilePath(kTestFileInvalid) == file_path.BaseName());
+  }
+}
diff --git a/chrome/browser/cocoa/cookies_window_controller.h b/chrome/browser/cocoa/cookies_window_controller.h
index fac226a..0ddd6c6 100644
--- a/chrome/browser/cocoa/cookies_window_controller.h
+++ b/chrome/browser/cocoa/cookies_window_controller.h
@@ -87,12 +87,14 @@ class CookiesTreeModelObserverBridge : public TreeModelObserver {
   IBOutlet NSTreeController* treeController_;
 
   Profile* profile_;  // weak
+  BrowsingDataLocalStorageHelper* storageHelper_;  // weak
 }
 @property (assign, nonatomic) BOOL removeButtonEnabled;
 @property (readonly, nonatomic) NSTreeController* treeController;
 
 // Designated initializer. Profile cannot be NULL.
-- (id)initWithProfile:(Profile*)profile;
+- (id)initWithProfile:(Profile*)profile
+        storageHelper:(BrowsingDataLocalStorageHelper*)storageHelper;
 
 // Shows the cookies window as a modal sheet attached to |window|.
 - (void)attachSheetTo:(NSWindow*)window;
diff --git a/chrome/browser/cocoa/cookies_window_controller.mm b/chrome/browser/cocoa/cookies_window_controller.mm
index 227c0b3..998edf4 100644
--- a/chrome/browser/cocoa/cookies_window_controller.mm
+++ b/chrome/browser/cocoa/cookies_window_controller.mm
@@ -131,12 +131,14 @@ CocoaCookieTreeNode* CookiesTreeModelObserverBridge::FindCocoaNode(
 @synthesize removeButtonEnabled = removeButtonEnabled_;
 @synthesize treeController = treeController_;
 
-- (id)initWithProfile:(Profile*)profile {
+- (id)initWithProfile:(Profile*)profile
+        storageHelper:(BrowsingDataLocalStorageHelper*)storageHelper {
   DCHECK(profile);
   NSString* nibpath = [mac_util::MainAppBundle() pathForResource:@"Cookies"
                                                           ofType:@"nib"];
   if ((self = [super initWithWindowNibPath:nibpath owner:self])) {
     profile_ = profile;
+    storageHelper_ = storageHelper;
 
     [self loadTreeModelFromProfile];
 
@@ -305,7 +307,7 @@ CocoaCookieTreeNode* CookiesTreeModelObserverBridge::FindCocoaNode(
 // to rebuild after the user clears browsing data. Because the models get
 // clobbered, we rebuild the icon cache for safety (though they do not change).
 - (void)loadTreeModelFromProfile {
-  treeModel_.reset(new CookiesTreeModel(profile_));
+  treeModel_.reset(new CookiesTreeModel(profile_, storageHelper_));
   modelObserver_.reset(new CookiesTreeModelObserverBridge(self));
   treeModel_->SetObserver(modelObserver_.get());
 
diff --git a/chrome/browser/cocoa/cookies_window_controller_unittest.mm b/chrome/browser/cocoa/cookies_window_controller_unittest.mm
index e6fc929..de009c4 100644
--- a/chrome/browser/cocoa/cookies_window_controller_unittest.mm
+++ b/chrome/browser/cocoa/cookies_window_controller_unittest.mm
@@ -12,6 +12,7 @@
 #include "chrome/browser/cocoa/clear_browsing_data_controller.h"
 #import "chrome/browser/cocoa/cookies_window_controller.h"
 #include "chrome/browser/cocoa/cocoa_test_helper.h"
+#include "chrome/browser/mock_browsing_data_local_storage_helper.h"
 #include "chrome/browser/net/url_request_context_getter.h"
 #include "chrome/browser/cookies_tree_model.h"
 #include "chrome/test/testing_profile.h"
@@ -65,8 +66,11 @@ class CookiesWindowControllerTest : public CocoaTest {
     CocoaTest::SetUp();
     TestingProfile* profile = browser_helper_.profile();
     profile->CreateRequestContext();
+    local_storage_helper_ = new MockBrowsingDataLocalStorageHelper(profile);
     controller_.reset(
-        [[CookiesWindowController alloc] initWithProfile:profile]);
+        [[CookiesWindowController alloc] initWithProfile:profile
+                                           storageHelper:local_storage_helper_]
+    );
   }
 
   virtual void TearDown() {
@@ -86,6 +90,7 @@ class CookiesWindowControllerTest : public CocoaTest {
  protected:
   BrowserTestHelper browser_helper_;
   scoped_nsobject<CookiesWindowController> controller_;
+  BrowsingDataLocalStorageHelper* local_storage_helper_;
 };
 
 TEST_F(CookiesWindowControllerTest, Construction) {
@@ -137,7 +142,7 @@ TEST_F(CookiesWindowControllerTest, FindCocoaNodeRecursive) {
 TEST_F(CookiesWindowControllerTest, CocoaNodeFromTreeNodeCookie) {
   net::CookieMonster* cm = browser_helper_.profile()->GetCookieMonster();
   cm->SetCookie(GURL("http://foo.com"), "A=B");
-  CookiesTreeModel model(browser_helper_.profile());
+  CookiesTreeModel model(browser_helper_.profile(), local_storage_helper_);
 
   // Root --> foo.com --> Cookies --> A. Create node for 'A'.
   TreeModelNode* node = model.GetRoot()->GetChild(0)->GetChild(0)->GetChild(0);
@@ -158,7 +163,7 @@ TEST_F(CookiesWindowControllerTest, CocoaNodeFromTreeNodeCookie) {
 TEST_F(CookiesWindowControllerTest, CocoaNodeFromTreeNodeRecursive) {
   net::CookieMonster* cm = browser_helper_.profile()->GetCookieMonster();
   cm->SetCookie(GURL("http://foo.com"), "A=B");
-  CookiesTreeModel model(browser_helper_.profile());
+  CookiesTreeModel model(browser_helper_.profile(), local_storage_helper_);
 
   // Root --> foo.com --> Cookies --> A. Create node for 'foo.com'.
   CookieTreeNode* node = model.GetRoot()->GetChild(0);
@@ -200,7 +205,8 @@ TEST_F(CookiesWindowControllerTest, TreeNodesAdded) {
   cm->SetCookie(url, "A=B");
 
   controller_.reset(
-      [[CookiesWindowController alloc] initWithProfile:profile]);
+      [[CookiesWindowController alloc] initWithProfile:profile
+                                         storageHelper:local_storage_helper_]);
 
   // Root --> foo.com --> Cookies.
   NSMutableArray* cocoa_children =
@@ -241,7 +247,8 @@ TEST_F(CookiesWindowControllerTest, TreeNodesRemoved) {
   cm->SetCookie(url, "E=F");
 
   controller_.reset(
-      [[CookiesWindowController alloc] initWithProfile:profile]);
+      [[CookiesWindowController alloc] initWithProfile:profile
+                                         storageHelper:local_storage_helper_]);
 
   // Root --> foo.com --> Cookies.
   NSMutableArray* cocoa_children =
@@ -271,7 +278,8 @@ TEST_F(CookiesWindowControllerTest, TreeNodeChildrenReordered) {
   cm->SetCookie(url, "E=F");
 
   controller_.reset(
-      [[CookiesWindowController alloc] initWithProfile:profile]);
+      [[CookiesWindowController alloc] initWithProfile:profile
+                                         storageHelper:local_storage_helper_]);
 
   // Root --> foo.com --> Cookies.
   NSMutableArray* cocoa_children =
@@ -316,7 +324,8 @@ TEST_F(CookiesWindowControllerTest, TreeNodeChanged) {
   cm->SetCookie(url, "A=B");
 
   controller_.reset(
-      [[CookiesWindowController alloc] initWithProfile:profile]);
+      [[CookiesWindowController alloc] initWithProfile:profile
+                                         storageHelper:local_storage_helper_]);
 
   CookiesTreeModel* model = [controller_ treeModel];
   // Root --> foo.com --> Cookies.
@@ -348,7 +357,8 @@ TEST_F(CookiesWindowControllerTest, TestDeleteCookie) {
   // This will clean itself up when we call |-closeSheet:|. If we reset the
   // scoper, we'd get a double-free.
   CookiesWindowController* controller =
-      [[CookiesWindowController alloc] initWithProfile:profile];
+      [[CookiesWindowController alloc] initWithProfile:profile
+                                         storageHelper:local_storage_helper_];
   [controller attachSheetTo:test_window()];
   NSTreeController* treeController = [controller treeController];
 
@@ -378,7 +388,8 @@ TEST_F(CookiesWindowControllerTest, TestDidExpandItem) {
   cm->SetCookie(url, "C=D");
 
   controller_.reset(
-      [[CookiesWindowController alloc] initWithProfile:profile]);
+      [[CookiesWindowController alloc] initWithProfile:profile
+                                         storageHelper:local_storage_helper_]);
 
   // Root --> foo.com.
   CocoaCookieTreeNode* foo =
@@ -444,7 +455,8 @@ TEST_F(CookiesWindowControllerTest, RemoveButtonEnabled) {
   // This will clean itself up when we call |-closeSheet:|. If we reset the
   // scoper, we'd get a double-free.
   CookiesWindowController* controller =
-      [[CookiesWindowController alloc] initWithProfile:profile];
+      [[CookiesWindowController alloc] initWithProfile:profile
+                                         storageHelper:local_storage_helper_];
   [controller attachSheetTo:test_window()];
 
   // Nothing should be selected right now.
diff --git a/chrome/browser/cocoa/preferences_window_controller.mm b/chrome/browser/cocoa/preferences_window_controller.mm
index 50f49cf..c4ad9ac 100644
--- a/chrome/browser/cocoa/preferences_window_controller.mm
+++ b/chrome/browser/cocoa/preferences_window_controller.mm
@@ -1315,8 +1315,11 @@ const int kDisabledIndex = 1;
 // Shows the cookies controller.
 - (IBAction)showCookies:(id)sender {
   // The controller will clean itself up.
+  BrowsingDataLocalStorageHelper* storageHelper =
+      new BrowsingDataLocalStorageHelper(profile_);
   CookiesWindowController* controller =
-      [[CookiesWindowController alloc] initWithProfile:profile_];
+      [[CookiesWindowController alloc] initWithProfile:profile_
+                                         storageHelper:storageHelper];
   [controller attachSheetTo:[self window]];
 }
 
diff --git a/chrome/browser/cookies_tree_model.cc b/chrome/browser/cookies_tree_model.cc
index 71932e8..aa699bf 100644
--- a/chrome/browser/cookies_tree_model.cc
+++ b/chrome/browser/cookies_tree_model.cc
@@ -14,6 +14,7 @@
 #include "app/tree_node_model.h"
 #include "base/linked_ptr.h"
 #include "base/string_util.h"
+#include "chrome/browser/in_process_webkit/webkit_context.h"
 #include "chrome/browser/net/chrome_url_request_context.h"
 #include "chrome/browser/profile.h"
 #include "grit/app_resources.h"
@@ -114,6 +115,22 @@ class OriginNodeComparator {
 }  // namespace
 
 ///////////////////////////////////////////////////////////////////////////////
+// CookieTreeLocalStorageNode, public:
+
+CookieTreeLocalStorageNode::CookieTreeLocalStorageNode(
+    BrowsingDataLocalStorageHelper::LocalStorageInfo* local_storage_info)
+    : CookieTreeNode(UTF8ToWide(
+        !local_storage_info->origin.empty() ?
+            local_storage_info->origin :
+            local_storage_info->database_identifier)),
+      local_storage_info_(local_storage_info) {
+}
+
+void CookieTreeLocalStorageNode::DeleteStoredObjects() {
+  GetModel()->DeleteLocalStorage(local_storage_info_->file_path);
+}
+
+///////////////////////////////////////////////////////////////////////////////
 // CookieTreeRootNode, public:
 CookieTreeOriginNode* CookieTreeRootNode::GetOrCreateOriginNode(
     const std::wstring& origin) {
@@ -155,6 +172,17 @@ CookieTreeCookiesNode* CookieTreeOriginNode::GetOrCreateCookiesNode() {
   return retval;
 }
 
+CookieTreeLocalStoragesNode*
+    CookieTreeOriginNode::GetOrCreateLocalStoragesNode() {
+  if (local_storages_child_)
+    return local_storages_child_;
+  // need to make a LocalStorages node, add it to the tree, and return it
+  CookieTreeLocalStoragesNode* retval = new CookieTreeLocalStoragesNode;
+  GetModel()->Add(this, cookies_child_ ? 1 : 0, retval);
+  local_storages_child_ = retval;
+  return retval;
+}
+
 ///////////////////////////////////////////////////////////////////////////////
 // CookieTreeCookiesNode, public:
 
@@ -173,24 +201,67 @@ void CookieTreeCookiesNode::AddCookieNode(
 }
 
 ///////////////////////////////////////////////////////////////////////////////
+// CookieTreeLocalStoragesNode, public:
+
+CookieTreeLocalStoragesNode::CookieTreeLocalStoragesNode()
+    : CookieTreeNode(l10n_util::GetString(IDS_COOKIES_LOCAL_STORAGE)) {
+}
+
+void CookieTreeLocalStoragesNode::AddLocalStorageNode(
+    CookieTreeLocalStorageNode* new_child) {
+  std::vector<CookieTreeNode*>::iterator local_storage_iterator =
+      lower_bound(children().begin(),
+                  children().end(),
+                  new_child,
+                  CookieTreeLocalStorageNode::CookieNodeComparator());
+  GetModel()->Add(this,
+                  (local_storage_iterator - children().begin()),
+                  new_child);
+}
+
+///////////////////////////////////////////////////////////////////////////////
 // CookieTreeCookieNode, private
 
 bool CookieTreeCookieNode::CookieNodeComparator::operator() (
     const CookieTreeNode* lhs, const CookieTreeNode* rhs) {
-  return (static_cast<const CookieTreeCookieNode*>(lhs)->
-          cookie_->second.Name() <
-          static_cast<const CookieTreeCookieNode*>(rhs)->
-          cookie_->second.Name());
+  const CookieTreeCookieNode* left =
+      static_cast<const CookieTreeCookieNode*>(lhs);
+  const CookieTreeCookieNode* right =
+      static_cast<const CookieTreeCookieNode*>(rhs);
+  return (left->cookie_->second.Name() < right->cookie_->second.Name());
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// CookieTreeLocalStorageNode, private
+
+bool CookieTreeLocalStorageNode::CookieNodeComparator::operator() (
+    const CookieTreeNode* lhs, const CookieTreeNode* rhs) {
+  const CookieTreeLocalStorageNode* left  =
+      static_cast<const CookieTreeLocalStorageNode*>(lhs);
+  const CookieTreeLocalStorageNode* right =
+      static_cast<const CookieTreeLocalStorageNode*>(rhs);
+  return (left->local_storage_info_->origin <
+          right->local_storage_info_->origin);
 }
 
 ///////////////////////////////////////////////////////////////////////////////
 // CookiesTreeModel, public:
 
-CookiesTreeModel::CookiesTreeModel(Profile* profile)
+CookiesTreeModel::CookiesTreeModel(
+    Profile* profile,
+    BrowsingDataLocalStorageHelper* local_storage_helper)
     : ALLOW_THIS_IN_INITIALIZER_LIST(TreeNodeModel<CookieTreeNode>(
           new CookieTreeRootNode(this))),
-      profile_(profile) {
+      profile_(profile),
+      local_storage_helper_(local_storage_helper) {
   LoadCookies();
+  DCHECK(local_storage_helper_);
+  local_storage_helper_->StartFetching(NewCallback(
+      this, &CookiesTreeModel::OnStorageModelInfoLoaded));
+}
+
+CookiesTreeModel::~CookiesTreeModel() {
+  local_storage_helper_->CancelNotification();
 }
 
 ///////////////////////////////////////////////////////////////////////////////
@@ -218,6 +289,8 @@ int CookiesTreeModel::GetIconIndex(TreeModelNode* node) {
     case CookieTreeNode::DetailedInfo::TYPE_COOKIE:
       return COOKIE;
       break;
+    case CookieTreeNode::DetailedInfo::TYPE_LOCAL_STORAGE:
+      // TODO(bulach): add an icon for local storage.
     default:
       return -1;
   }
@@ -283,11 +356,50 @@ void CookiesTreeModel::DeleteCookieNode(CookieTreeNode* cookie_node) {
   delete Remove(parent_node, cookie_node_index);
 }
 
+void CookiesTreeModel::DeleteLocalStorage(const FilePath& file_path) {
+  local_storage_helper_->DeleteLocalStorageFile(file_path);
+}
+
+void CookiesTreeModel::DeleteAllLocalStorage() {
+  local_storage_helper_->DeleteAllLocalStorageFiles();
+}
+
 void CookiesTreeModel::UpdateSearchResults(const std::wstring& filter) {
   CookieTreeNode* root = GetRoot();
   int num_children = root->GetChildCount();
   for (int i = num_children - 1; i >= 0; --i)
     delete Remove(root, i);
   LoadCookiesWithFilter(filter);
+  PopulateLocalStorageInfoWithFilter(filter);
+  NotifyObserverTreeNodeChanged(root);
+}
+
+void CookiesTreeModel::OnStorageModelInfoLoaded(
+    const LocalStorageInfoList& local_storage_info) {
+  local_storage_info_list_ = local_storage_info;
+  PopulateLocalStorageInfoWithFilter(L"");
+}
+
+void CookiesTreeModel::PopulateLocalStorageInfoWithFilter(
+    const std::wstring& filter) {
+  CookieTreeRootNode* root = static_cast<CookieTreeRootNode*>(GetRoot());
+  for (LocalStorageInfoList::iterator local_storage_info =
+       local_storage_info_list_.begin();
+       local_storage_info != local_storage_info_list_.end();
+       ++local_storage_info) {
+    std::string origin =
+        !local_storage_info->host.empty() ?
+            local_storage_info->host :
+            local_storage_info->database_identifier;
+    if (!filter.size() ||
+        (UTF8ToWide(origin).find(filter) != std::wstring::npos)) {
+      CookieTreeOriginNode* origin_node = root->GetOrCreateOriginNode(
+          UTF8ToWide(local_storage_info->host));
+      CookieTreeLocalStoragesNode* local_storages_node =
+          origin_node->GetOrCreateLocalStoragesNode();
+      local_storages_node->AddLocalStorageNode(
+          new CookieTreeLocalStorageNode(&(*local_storage_info)));
+    }
+  }
   NotifyObserverTreeNodeChanged(root);
 }
diff --git a/chrome/browser/cookies_tree_model.h b/chrome/browser/cookies_tree_model.h
index 11b7f8a..8a4bd60 100644
--- a/chrome/browser/cookies_tree_model.h
+++ b/chrome/browser/cookies_tree_model.h
@@ -10,9 +10,12 @@
 
 #include "app/tree_node_model.h"
 #include "base/scoped_ptr.h"
+#include "chrome/browser/browsing_data_local_storage_helper.h"
 #include "net/base/cookie_monster.h"
 
 class CookiesTreeModel;
+class CookieTreeLocalStorageNode;
+class CookieTreeLocalStoragesNode;
 class CookieTreeCookieNode;
 class CookieTreeCookiesNode;
 class CookieTreeOriginNode;
@@ -33,18 +36,27 @@ class CookieTreeNode : public TreeNode<CookieTreeNode> {
       TYPE_ROOT,  // This is used for CookieTreeRootNode nodes.
       TYPE_ORIGIN,  // This is used for CookieTreeOriginNode nodes.
       TYPE_COOKIES,  // This is used for CookieTreeCookiesNode nodes.
-      TYPE_COOKIE  // This is used for CookieTreeCookieNode nodes.
+      TYPE_COOKIE,  // This is used for CookieTreeCookieNode nodes.
+      TYPE_LOCAL_STORAGES,  // This is used for CookieTreeLocalStoragesNode.
+      TYPE_LOCAL_STORAGE,  // This is used for CookieTreeLocalStorageNode.
     };
 
     DetailedInfo(const std::wstring& origin, NodeType node_type,
-        const net::CookieMonster::CookieListPair* cookie)
+        const net::CookieMonster::CookieListPair* cookie,
+        const BrowsingDataLocalStorageHelper::LocalStorageInfo*
+            local_storage_info)
         : origin(origin),
           node_type(node_type),
-          cookie(cookie) {}
+          cookie(cookie),
+          local_storage_info(local_storage_info) {
+      if (node_type == TYPE_LOCAL_STORAGE)
+        DCHECK(local_storage_info);
+    }
 
     std::wstring origin;
     NodeType node_type;
     const net::CookieMonster::CookieListPair* cookie;
+    const BrowsingDataLocalStorageHelper::LocalStorageInfo* local_storage_info;
   };
 
   CookieTreeNode() {}
@@ -79,7 +91,7 @@ class CookieTreeRootNode : public CookieTreeNode {
   // CookieTreeNode methods:
   virtual CookiesTreeModel* GetModel() const { return model_; }
   virtual DetailedInfo GetDetailedInfo() const {
-    return DetailedInfo(std::wstring(), DetailedInfo::TYPE_ROOT, NULL);
+    return DetailedInfo(std::wstring(), DetailedInfo::TYPE_ROOT, NULL, NULL);
   }
  private:
 
@@ -92,16 +104,19 @@ class CookieTreeRootNode : public CookieTreeNode {
 class CookieTreeOriginNode : public CookieTreeNode {
  public:
   explicit CookieTreeOriginNode(const std::wstring& origin)
-      : CookieTreeNode(origin), cookies_child_(NULL) {}
+      : CookieTreeNode(origin), cookies_child_(NULL),
+        local_storages_child_(NULL) {}
   virtual ~CookieTreeOriginNode() {}
 
   // CookieTreeNode methods:
   virtual DetailedInfo GetDetailedInfo() const {
-    return DetailedInfo(GetTitle(), DetailedInfo::TYPE_ORIGIN, NULL);
+    return DetailedInfo(GetTitle(), DetailedInfo::TYPE_ORIGIN, NULL, NULL);
   }
 
   // CookieTreeOriginNode methods:
   CookieTreeCookiesNode* GetOrCreateCookiesNode();
+  CookieTreeLocalStoragesNode* GetOrCreateLocalStoragesNode();
+
  private:
 
   // A pointer to the COOKIES node. Eventually we will also have database,
@@ -111,6 +126,7 @@ class CookieTreeOriginNode : public CookieTreeNode {
   // DATABASES etc node seems less preferable than storing an extra pointer per
   // origin.
   CookieTreeCookiesNode* cookies_child_;
+  CookieTreeLocalStoragesNode* local_storages_child_;
 
   DISALLOW_COPY_AND_ASSIGN(CookieTreeOriginNode);
 };
@@ -124,7 +140,7 @@ class CookieTreeCookiesNode : public CookieTreeNode {
   // CookieTreeNode methods:
   virtual DetailedInfo GetDetailedInfo() const {
     return DetailedInfo(GetParent()->GetTitle(), DetailedInfo::TYPE_COOKIES,
-                        NULL);
+                        NULL, NULL);
   }
 
   // CookieTreeCookiesNode methods:
@@ -147,7 +163,7 @@ class CookieTreeCookieNode : public CookieTreeNode {
   virtual void DeleteStoredObjects();
   virtual DetailedInfo GetDetailedInfo() const {
     return DetailedInfo(GetParent()->GetParent()->GetTitle(),
-                        DetailedInfo::TYPE_COOKIE, cookie_);
+                        DetailedInfo::TYPE_COOKIE, cookie_, NULL);
   }
 
  private:
@@ -165,11 +181,66 @@ class CookieTreeCookieNode : public CookieTreeNode {
   DISALLOW_COPY_AND_ASSIGN(CookieTreeCookieNode);
 };
 
+// CookieTreeLocalStoragesNode -------------------------------------------------
+class CookieTreeLocalStoragesNode : public CookieTreeNode {
+ public:
+  CookieTreeLocalStoragesNode();
+  virtual ~CookieTreeLocalStoragesNode() {}
+
+  // CookieTreeNode methods:
+  virtual DetailedInfo GetDetailedInfo() const {
+    return DetailedInfo(GetParent()->GetTitle(),
+                        DetailedInfo::TYPE_LOCAL_STORAGES,
+                        NULL,
+                        NULL);
+  }
+
+  // CookieTreeStoragesNode methods:
+  void AddLocalStorageNode(CookieTreeLocalStorageNode* child);
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(CookieTreeLocalStoragesNode);
+};
+
+class CookieTreeLocalStorageNode : public CookieTreeNode {
+ public:
+  friend class CookieTreeLocalStoragesNode;
+
+  // Does not take ownership of local_storage_info, and local_storage_info
+  // should remain valid at least as long as the CookieTreeStorageNode is valid.
+  explicit CookieTreeLocalStorageNode(
+      BrowsingDataLocalStorageHelper::LocalStorageInfo* local_storage_info);
+  virtual ~CookieTreeLocalStorageNode() {}
+
+  // CookieTreeStorageNode methods:
+  virtual void DeleteStoredObjects();
+  virtual DetailedInfo GetDetailedInfo() const {
+    return DetailedInfo(GetParent()->GetParent()->GetTitle(),
+                        DetailedInfo::TYPE_LOCAL_STORAGE, NULL,
+                        local_storage_info_);
+  }
+
+ private:
+  // Comparator functor, takes CookieTreeNode so that we can use it in
+  // lower_bound using children()'s iterators, which are CookieTreeNode*.
+  class CookieNodeComparator {
+   public:
+    bool operator() (const CookieTreeNode* lhs, const CookieTreeNode* rhs);
+  };
+
+  // local_storage_info_ is not owned by the node, and is expected to remain
+  // valid as long as the CookieTreeLocalStorageNode is valid.
+  BrowsingDataLocalStorageHelper::LocalStorageInfo* local_storage_info_;
+
+  DISALLOW_COPY_AND_ASSIGN(CookieTreeLocalStorageNode);
+};
 
 class CookiesTreeModel : public TreeNodeModel<CookieTreeNode> {
  public:
-  explicit CookiesTreeModel(Profile* profile);
-  virtual ~CookiesTreeModel() {}
+  CookiesTreeModel(
+      Profile* profile,
+      BrowsingDataLocalStorageHelper* browsing_data_local_storage_helper);
+  virtual ~CookiesTreeModel();
 
   // TreeModel methods:
   // Returns the set of icons for the nodes in the tree. You only need override
@@ -185,6 +256,8 @@ class CookiesTreeModel : public TreeNodeModel<CookieTreeNode> {
   void DeleteCookie(const net::CookieMonster::CookieListPair& cookie);
   void DeleteAllCookies();
   void DeleteCookieNode(CookieTreeNode* cookie_node);
+  void DeleteLocalStorage(const FilePath& file_path);
+  void DeleteAllLocalStorage();
 
   // Filter the origins to only display matched results.
   void UpdateSearchResults(const std::wstring& filter);
@@ -192,18 +265,28 @@ class CookiesTreeModel : public TreeNodeModel<CookieTreeNode> {
  private:
   enum CookieIconIndex {
     ORIGIN = 0,
-    COOKIE = 1
+    COOKIE = 1,
+    LOCAL_STORAGE = 2,
   };
   typedef net::CookieMonster::CookieList CookieList;
   typedef std::vector<net::CookieMonster::CookieListPair*> CookiePtrList;
+  typedef std::vector<BrowsingDataLocalStorageHelper::LocalStorageInfo>
+      LocalStorageInfoList;
 
   void LoadCookies();
   void LoadCookiesWithFilter(const std::wstring& filter);
 
+  void OnStorageModelInfoLoaded(const LocalStorageInfoList& local_storage_info);
+
+  void PopulateLocalStorageInfoWithFilter(const std::wstring& filter);
+
   // The profile from which this model sources cookies.
   Profile* profile_;
   CookieList all_cookies_;
 
+  scoped_refptr<BrowsingDataLocalStorageHelper> local_storage_helper_;
+  LocalStorageInfoList local_storage_info_list_;
+
   DISALLOW_COPY_AND_ASSIGN(CookiesTreeModel);
 };
 
diff --git a/chrome/browser/cookies_tree_model_unittest.cc b/chrome/browser/cookies_tree_model_unittest.cc
index 11c312f..07935d3 100644
--- a/chrome/browser/cookies_tree_model_unittest.cc
+++ b/chrome/browser/cookies_tree_model_unittest.cc
@@ -7,6 +7,7 @@
 #include <string>
 
 #include "app/l10n_util.h"
+#include "chrome/browser/mock_browsing_data_local_storage_helper.h"
 #include "chrome/browser/net/url_request_context_getter.h"
 #include "chrome/test/testing_profile.h"
 #include "net/url_request/url_request_context.h"
@@ -26,6 +27,28 @@ class CookiesTreeModelTest : public testing::Test {
   virtual void SetUp() {
     profile_.reset(new TestingProfile());
     profile_->CreateRequestContext();
+    mock_browsing_data_helper_ =
+      new MockBrowsingDataLocalStorageHelper(profile_.get());
+  }
+
+  CookiesTreeModel* CreateCookiesTreeModelWithInitialSample() {
+    net::CookieMonster* monster = profile_->GetCookieMonster();
+    monster->SetCookie(GURL("http://foo1"), "A=1");
+    monster->SetCookie(GURL("http://foo2"), "B=1");
+    monster->SetCookie(GURL("http://foo3"), "C=1");
+    CookiesTreeModel* cookies_model = new CookiesTreeModel(
+        profile_.get(), mock_browsing_data_helper_);
+    mock_browsing_data_helper_->AddLocalStorageSamples();
+    mock_browsing_data_helper_->Notify();
+    {
+      SCOPED_TRACE("Initial State 3 cookies, 2 local storages");
+      // 16 because there's the root, then foo1 -> cookies -> a,
+      // foo2 -> cookies -> b, foo3 -> cookies -> c,
+      // host1 -> localstorage -> origin1, host2 -> localstorage -> origin2.
+      EXPECT_EQ(16, cookies_model->GetRoot()->GetTotalNodeCount());
+      EXPECT_EQ("origin1,origin2", GetDisplayedLocalStorages(cookies_model));
+    }
+    return cookies_model;
   }
 
   // Get the cookie names in the cookie list, as a comma seperated string.
@@ -42,35 +65,65 @@ class CookiesTreeModelTest : public testing::Test {
     return JoinString(parts, ',');
   }
 
-  std::string GetCookiesOfChildren(const CookieTreeNode* node) {
+  std::string GetNodesOfChildren(
+      const CookieTreeNode* node,
+      CookieTreeNode::DetailedInfo::NodeType node_type) {
     if (node->GetChildCount()) {
       std::string retval;
       for (int i = 0; i < node->GetChildCount(); ++i) {
-        retval += GetCookiesOfChildren(node->GetChild(i));
+        retval += GetNodesOfChildren(node->GetChild(i), node_type);
       }
       return retval;
     } else {
-      if (node->GetDetailedInfo().node_type ==
-          CookieTreeNode::DetailedInfo::TYPE_COOKIE)
-        return node->GetDetailedInfo().cookie->second.Name() + ",";
-      else
+      if (node->GetDetailedInfo().node_type == node_type) {
+        switch (node_type) {
+          case CookieTreeNode::DetailedInfo::TYPE_LOCAL_STORAGE:
+            return node->GetDetailedInfo().local_storage_info->origin + ",";
+          case CookieTreeNode::DetailedInfo::TYPE_COOKIE:
+            return node->GetDetailedInfo().cookie->second.Name() + ",";
+          default:
+            return "";
+        }
+      } else {
         return "";
+      }
     }
   }
-  // Get the cookie names displayed in the view (if we had one) in the order
+
+  std::string GetCookiesOfChildren(const CookieTreeNode* node) {
+    return GetNodesOfChildren(node, CookieTreeNode::DetailedInfo::TYPE_COOKIE);
+  }
+
+  std::string GetLocalStoragesOfChildren(const CookieTreeNode* node) {
+    return GetNodesOfChildren(node,
+                              CookieTreeNode::DetailedInfo::TYPE_LOCAL_STORAGE);
+  }
+
+  // Get the nodes names displayed in the view (if we had one) in the order
   // they are displayed, as a comma seperated string.
-  // Ex: EXPECT_STREQ("X,Y", GetDisplayedCookies(cookies_view).c_str());
-  std::string GetDisplayedCookies(CookiesTreeModel* cookies_model) {
+  // Ex: EXPECT_STREQ("X,Y", GetDisplayedNodes(cookies_view, type).c_str());
+  std::string GetDisplayedNodes(CookiesTreeModel* cookies_model,
+                                CookieTreeNode::DetailedInfo::NodeType type) {
     CookieTreeRootNode* root = static_cast<CookieTreeRootNode*>(
         cookies_model->GetRoot());
-    std::string retval = GetCookiesOfChildren(root);
+    std::string retval = GetNodesOfChildren(root, type);
     if (retval.length() && retval[retval.length() - 1] == ',')
       retval.erase(retval.length() - 1);
     return retval;
   }
 
+  std::string GetDisplayedCookies(CookiesTreeModel* cookies_model) {
+    return GetDisplayedNodes(cookies_model,
+                             CookieTreeNode::DetailedInfo::TYPE_COOKIE);
+  }
+
+  std::string GetDisplayedLocalStorages(CookiesTreeModel* cookies_model) {
+    return GetDisplayedNodes(cookies_model,
+                             CookieTreeNode::DetailedInfo::TYPE_LOCAL_STORAGE);
+  }
+
   // do not call on the root
-  void DeleteCookie(CookieTreeNode* node) {
+  void DeleteStoredObjects(CookieTreeNode* node) {
     node->DeleteStoredObjects();
     // find the parent and index
     CookieTreeNode* parent_node = node->GetParent();
@@ -83,99 +136,114 @@ class CookiesTreeModelTest : public testing::Test {
   ChromeThread io_thread_;
 
   scoped_ptr<TestingProfile> profile_;
+  MockBrowsingDataLocalStorageHelper* mock_browsing_data_helper_;
 };
 
 TEST_F(CookiesTreeModelTest, RemoveAll) {
+  scoped_ptr<CookiesTreeModel> cookies_model(
+      CreateCookiesTreeModelWithInitialSample());
   net::CookieMonster* monster = profile_->GetCookieMonster();
-  monster->SetCookie(GURL("http://foo"), "A=1");
-  monster->SetCookie(GURL("http://foo2"), "B=1");
-  CookiesTreeModel cookies_model(profile_.get());
 
   // Reset the selection of the first row.
   {
     SCOPED_TRACE("Before removing");
-    EXPECT_EQ(GetMonsterCookies(monster), GetDisplayedCookies(&cookies_model));
+    EXPECT_EQ(GetMonsterCookies(monster),
+              GetDisplayedCookies(cookies_model.get()));
+    EXPECT_EQ("origin1,origin2",
+              GetDisplayedLocalStorages(cookies_model.get()));
   }
 
-  cookies_model.DeleteAllCookies();
+  cookies_model->DeleteAllCookies();
+  cookies_model->DeleteAllLocalStorage();
+
   {
     SCOPED_TRACE("After removing");
-    EXPECT_EQ(1, cookies_model.GetRoot()->GetTotalNodeCount());
-    EXPECT_EQ(0, cookies_model.GetRoot()->GetChildCount());
+    EXPECT_EQ(1, cookies_model->GetRoot()->GetTotalNodeCount());
+    EXPECT_EQ(0, cookies_model->GetRoot()->GetChildCount());
     EXPECT_EQ(std::string(""), GetMonsterCookies(monster));
-    EXPECT_EQ(GetMonsterCookies(monster), GetDisplayedCookies(&cookies_model));
+    EXPECT_EQ(GetMonsterCookies(monster),
+              GetDisplayedCookies(cookies_model.get()));
+    EXPECT_TRUE(mock_browsing_data_helper_->delete_all_files_called_);
   }
 }
 
 TEST_F(CookiesTreeModelTest, Remove) {
+  scoped_ptr<CookiesTreeModel> cookies_model(
+      CreateCookiesTreeModelWithInitialSample());
   net::CookieMonster* monster = profile_->GetCookieMonster();
-  monster->SetCookie(GURL("http://foo1"), "A=1");
-  monster->SetCookie(GURL("http://foo2"), "B=1");
-  monster->SetCookie(GURL("http://foo3"), "C=1");
-  CookiesTreeModel cookies_model(profile_.get());
 
+  DeleteStoredObjects(cookies_model->GetRoot()->GetChild(0));
   {
-    SCOPED_TRACE("Initial State 3 cookies");
-    // 10 because there's the root, then foo1 -> cookies -> a,
-    // foo2 -> cookies -> b, foo3 -> cookies -> c
-    EXPECT_EQ(10, cookies_model.GetRoot()->GetTotalNodeCount());
+    SCOPED_TRACE("First cookie origin removed");
+    EXPECT_STREQ("B,C", GetMonsterCookies(monster).c_str());
+    EXPECT_STREQ("B,C", GetDisplayedCookies(cookies_model.get()).c_str());
+    EXPECT_EQ("origin1,origin2", GetDisplayedLocalStorages(cookies_model.get()));
+    EXPECT_EQ(13, cookies_model->GetRoot()->GetTotalNodeCount());
   }
-  DeleteCookie(cookies_model.GetRoot()->GetChild(0));
+
+  DeleteStoredObjects(cookies_model->GetRoot()->GetChild(2));
   {
-    SCOPED_TRACE("First origin removed");
+    SCOPED_TRACE("First local storage origin removed");
     EXPECT_STREQ("B,C", GetMonsterCookies(monster).c_str());
-    EXPECT_STREQ("B,C", GetDisplayedCookies(&cookies_model).c_str());
-    EXPECT_EQ(7, cookies_model.GetRoot()->GetTotalNodeCount());
+    EXPECT_STREQ("B,C", GetDisplayedCookies(cookies_model.get()).c_str());
+    EXPECT_EQ("origin2", GetDisplayedLocalStorages(cookies_model.get()));
+    EXPECT_EQ(10, cookies_model->GetRoot()->GetTotalNodeCount());
   }
 }
 
 TEST_F(CookiesTreeModelTest, RemoveCookiesNode) {
+  scoped_ptr<CookiesTreeModel> cookies_model(
+      CreateCookiesTreeModelWithInitialSample());
   net::CookieMonster* monster = profile_->GetCookieMonster();
-  monster->SetCookie(GURL("http://foo1"), "A=1");
-  monster->SetCookie(GURL("http://foo2"), "B=1");
-  monster->SetCookie(GURL("http://foo3"), "C=1");
-  CookiesTreeModel cookies_model(profile_.get());
 
-  {
-    SCOPED_TRACE("Initial State 3 cookies");
-    // 10 because there's the root, then foo1 -> cookies -> a,
-    // foo2 -> cookies -> b, foo3 -> cookies -> c
-    EXPECT_EQ(10, cookies_model.GetRoot()->GetTotalNodeCount());
-  }
-  DeleteCookie(cookies_model.GetRoot()->GetChild(0)->GetChild(0));
+  DeleteStoredObjects(cookies_model->GetRoot()->GetChild(0)->GetChild(0));
   {
     SCOPED_TRACE("First origin removed");
     EXPECT_STREQ("B,C", GetMonsterCookies(monster).c_str());
-    EXPECT_STREQ("B,C", GetDisplayedCookies(&cookies_model).c_str());
-    // 8 because in this case, the origin remains, although the COOKIES
+    EXPECT_STREQ("B,C", GetDisplayedCookies(cookies_model.get()).c_str());
+    // 14 because in this case, the origin remains, although the COOKIES
     // node beneath it has been deleted. So, we have
     // root -> foo1 -> cookies -> a, foo2, foo3 -> cookies -> c
-    EXPECT_EQ(8, cookies_model.GetRoot()->GetTotalNodeCount());
+    // host1 -> localstorage -> origin1, host2 -> localstorage -> origin2.
+    EXPECT_EQ(14, cookies_model->GetRoot()->GetTotalNodeCount());
+    EXPECT_EQ("origin1,origin2",
+              GetDisplayedLocalStorages(cookies_model.get()));
+  }
+
+  DeleteStoredObjects(cookies_model->GetRoot()->GetChild(3)->GetChild(0));
+  {
+    SCOPED_TRACE("First origin removed");
+    EXPECT_STREQ("B,C", GetMonsterCookies(monster).c_str());
+    EXPECT_STREQ("B,C", GetDisplayedCookies(cookies_model.get()).c_str());
+    EXPECT_EQ("origin2", GetDisplayedLocalStorages(cookies_model.get()));
+    EXPECT_EQ(12, cookies_model->GetRoot()->GetTotalNodeCount());
   }
 }
 
 TEST_F(CookiesTreeModelTest, RemoveCookieNode) {
+  scoped_ptr<CookiesTreeModel> cookies_model(
+      CreateCookiesTreeModelWithInitialSample());
   net::CookieMonster* monster = profile_->GetCookieMonster();
-  monster->SetCookie(GURL("http://foo1"), "A=1");
-  monster->SetCookie(GURL("http://foo2"), "B=1");
-  monster->SetCookie(GURL("http://foo3"), "C=1");
-  CookiesTreeModel cookies_model(profile_.get());
 
-  {
-    SCOPED_TRACE("Initial State 3 cookies");
-    // 10 because there's the root, then foo1 -> cookies -> a,
-    // foo2 -> cookies -> b, foo3 -> cookies -> c
-    EXPECT_EQ(10, cookies_model.GetRoot()->GetTotalNodeCount());
-  }
-  DeleteCookie(cookies_model.GetRoot()->GetChild(1)->GetChild(0));
+  DeleteStoredObjects(cookies_model->GetRoot()->GetChild(1)->GetChild(0));
   {
     SCOPED_TRACE("Second origin COOKIES node removed");
     EXPECT_STREQ("A,C", GetMonsterCookies(monster).c_str());
-    EXPECT_STREQ("A,C", GetDisplayedCookies(&cookies_model).c_str());
-    // 8 because in this case, the origin remains, although the COOKIES
+    EXPECT_STREQ("A,C", GetDisplayedCookies(cookies_model.get()).c_str());
+    // 14 because in this case, the origin remains, although the COOKIES
     // node beneath it has been deleted. So, we have
     // root -> foo1 -> cookies -> a, foo2, foo3 -> cookies -> c
-    EXPECT_EQ(8, cookies_model.GetRoot()->GetTotalNodeCount());
+    // host1 -> localstorage -> origin1, host2 -> localstorage -> origin2.
+    EXPECT_EQ(14, cookies_model->GetRoot()->GetTotalNodeCount());
+  }
+
+  DeleteStoredObjects(cookies_model->GetRoot()->GetChild(3)->GetChild(0));
+  {
+    SCOPED_TRACE("First origin removed");
+    EXPECT_STREQ("A,C", GetMonsterCookies(monster).c_str());
+    EXPECT_STREQ("A,C", GetDisplayedCookies(cookies_model.get()).c_str());
+    EXPECT_EQ("origin2", GetDisplayedLocalStorages(cookies_model.get()));
+    EXPECT_EQ(12, cookies_model->GetRoot()->GetTotalNodeCount());
   }
 }
 
@@ -185,22 +253,27 @@ TEST_F(CookiesTreeModelTest, RemoveSingleCookieNode) {
   monster->SetCookie(GURL("http://foo2"), "B=1");
   monster->SetCookie(GURL("http://foo3"), "C=1");
   monster->SetCookie(GURL("http://foo3"), "D=1");
-  CookiesTreeModel cookies_model(profile_.get());
+  CookiesTreeModel cookies_model(
+      profile_.get(), mock_browsing_data_helper_);
+  mock_browsing_data_helper_->AddLocalStorageSamples();
+  mock_browsing_data_helper_->Notify();
 
   {
-    SCOPED_TRACE("Initial State 4 cookies");
-    // 11 because there's the root, then foo1 -> cookies -> a,
+    SCOPED_TRACE("Initial State 4 cookies, 2 local storages");
+    // 17 because there's the root, then foo1 -> cookies -> a,
     // foo2 -> cookies -> b, foo3 -> cookies -> c,d
-    EXPECT_EQ(11, cookies_model.GetRoot()->GetTotalNodeCount());
+    // host1 -> localstorage -> origin1, host2 -> localstorage -> origin2.
+    EXPECT_EQ(17, cookies_model.GetRoot()->GetTotalNodeCount());
     EXPECT_STREQ("A,B,C,D", GetMonsterCookies(monster).c_str());
     EXPECT_STREQ("A,B,C,D", GetDisplayedCookies(&cookies_model).c_str());
+    EXPECT_EQ("origin1,origin2", GetDisplayedLocalStorages(&cookies_model));
   }
-  DeleteCookie(cookies_model.GetRoot()->GetChild(2));
+  DeleteStoredObjects(cookies_model.GetRoot()->GetChild(2));
   {
     SCOPED_TRACE("Third origin removed");
     EXPECT_STREQ("A,B", GetMonsterCookies(monster).c_str());
     EXPECT_STREQ("A,B", GetDisplayedCookies(&cookies_model).c_str());
-    EXPECT_EQ(7, cookies_model.GetRoot()->GetTotalNodeCount());
+    EXPECT_EQ(13, cookies_model.GetRoot()->GetTotalNodeCount());
   }
 }
 
@@ -211,23 +284,28 @@ TEST_F(CookiesTreeModelTest, RemoveSingleCookieNodeOf3) {
   monster->SetCookie(GURL("http://foo3"), "C=1");
   monster->SetCookie(GURL("http://foo3"), "D=1");
   monster->SetCookie(GURL("http://foo3"), "E=1");
-  CookiesTreeModel cookies_model(profile_.get());
+  CookiesTreeModel cookies_model(profile_.get(), mock_browsing_data_helper_);
+  mock_browsing_data_helper_->AddLocalStorageSamples();
+  mock_browsing_data_helper_->Notify();
 
   {
-    SCOPED_TRACE("Initial State 5 cookies");
-    // 11 because there's the root, then foo1 -> cookies -> a,
+    SCOPED_TRACE("Initial State 5 cookies, 2 local storages");
+    // 17 because there's the root, then foo1 -> cookies -> a,
     // foo2 -> cookies -> b, foo3 -> cookies -> c,d,e
-    EXPECT_EQ(12, cookies_model.GetRoot()->GetTotalNodeCount());
+    // host1 -> localstorage -> origin1, host2 -> localstorage -> origin2.
+    EXPECT_EQ(18, cookies_model.GetRoot()->GetTotalNodeCount());
     EXPECT_STREQ("A,B,C,D,E", GetMonsterCookies(monster).c_str());
     EXPECT_STREQ("A,B,C,D,E", GetDisplayedCookies(&cookies_model).c_str());
+    EXPECT_EQ("origin1,origin2", GetDisplayedLocalStorages(&cookies_model));
   }
-  DeleteCookie(cookies_model.GetRoot()->GetChild(2)->GetChild(0)->
+  DeleteStoredObjects(cookies_model.GetRoot()->GetChild(2)->GetChild(0)->
       GetChild(1));
   {
     SCOPED_TRACE("Middle cookie in third origin removed");
     EXPECT_STREQ("A,B,C,E", GetMonsterCookies(monster).c_str());
     EXPECT_STREQ("A,B,C,E", GetDisplayedCookies(&cookies_model).c_str());
-    EXPECT_EQ(11, cookies_model.GetRoot()->GetTotalNodeCount());
+    EXPECT_EQ(17, cookies_model.GetRoot()->GetTotalNodeCount());
+    EXPECT_EQ("origin1,origin2", GetDisplayedLocalStorages(&cookies_model));
   }
 }
 
@@ -238,8 +316,7 @@ TEST_F(CookiesTreeModelTest, RemoveSecondOrigin) {
   monster->SetCookie(GURL("http://foo3"), "C=1");
   monster->SetCookie(GURL("http://foo3"), "D=1");
   monster->SetCookie(GURL("http://foo3"), "E=1");
-  CookiesTreeModel cookies_model(profile_.get());
-
+  CookiesTreeModel cookies_model(profile_.get(), mock_browsing_data_helper_);
   {
     SCOPED_TRACE("Initial State 5 cookies");
     // 11 because there's the root, then foo1 -> cookies -> a,
@@ -248,7 +325,7 @@ TEST_F(CookiesTreeModelTest, RemoveSecondOrigin) {
     EXPECT_STREQ("A,B,C,D,E", GetMonsterCookies(monster).c_str());
     EXPECT_STREQ("A,B,C,D,E", GetDisplayedCookies(&cookies_model).c_str());
   }
-  DeleteCookie(cookies_model.GetRoot()->GetChild(1));
+  DeleteStoredObjects(cookies_model.GetRoot()->GetChild(1));
   {
     SCOPED_TRACE("Second origin removed");
     EXPECT_STREQ("A,C,D,E", GetMonsterCookies(monster).c_str());
@@ -270,7 +347,8 @@ TEST_F(CookiesTreeModelTest, OriginOrdering) {
   monster->SetCookie(GURL("http://foo3.com"), "G=1");
   monster->SetCookie(GURL("http://foo4.com"), "H=1");
 
-  CookiesTreeModel cookies_model(profile_.get());
+  CookiesTreeModel cookies_model(
+      profile_.get(), new MockBrowsingDataLocalStorageHelper(profile_.get()));
 
   {
     SCOPED_TRACE("Initial State 8 cookies");
@@ -279,7 +357,7 @@ TEST_F(CookiesTreeModelTest, OriginOrdering) {
     EXPECT_STREQ("F,E,C,B,A,G,D,H",
         GetDisplayedCookies(&cookies_model).c_str());
   }
-  DeleteCookie(cookies_model.GetRoot()->GetChild(1));  // Delete "E"
+  DeleteStoredObjects(cookies_model.GetRoot()->GetChild(1));  // Delete "E"
   {
     SCOPED_TRACE("Second origin removed");
     EXPECT_STREQ("D,A,C,F,B,G,H", GetMonsterCookies(monster).c_str());
diff --git a/chrome/browser/gtk/options/advanced_contents_gtk.cc b/chrome/browser/gtk/options/advanced_contents_gtk.cc
index 908f212..d493a39 100644
--- a/chrome/browser/gtk/options/advanced_contents_gtk.cc
+++ b/chrome/browser/gtk/options/advanced_contents_gtk.cc
@@ -761,7 +761,9 @@ void PrivacySection::OnCookieBehaviorChanged(GtkComboBox* combo_box,
 void PrivacySection::OnShowCookiesButtonClicked(
     GtkButton *button, PrivacySection* privacy_section) {
   privacy_section->UserMetricsRecordAction("Options_ShowCookies", NULL);
-  CookiesView::Show(privacy_section->profile());
+  CookiesView::Show(privacy_section->profile(),
+                    new BrowsingDataLocalStorageHelper(
+                        privacy_section->profile()));
 }
 
 void PrivacySection::NotifyPrefChanged(const std::wstring* pref_name) {
diff --git a/chrome/browser/gtk/options/cookies_view.cc b/chrome/browser/gtk/options/cookies_view.cc
index fb9c8c6..896e3b1 100644
--- a/chrome/browser/gtk/options/cookies_view.cc
+++ b/chrome/browser/gtk/options/cookies_view.cc
@@ -35,8 +35,8 @@ enum {
 // The currently open cookie manager, if any.
 CookiesView* instance_ = NULL;
 
-void InitCookieDetailStyle(GtkWidget* entry, GtkStyle* label_style,
-                           GtkStyle* dialog_style) {
+void InitBrowserDetailStyle(GtkWidget* entry, GtkStyle* label_style,
+                            GtkStyle* dialog_style) {
   gtk_widget_modify_fg(entry, GTK_STATE_NORMAL,
                        &label_style->fg[GTK_STATE_NORMAL]);
   gtk_widget_modify_fg(entry, GTK_STATE_INSENSITIVE,
@@ -57,20 +57,26 @@ CookiesView::~CookiesView() {
 }
 
 // static
-void CookiesView::Show(Profile* profile) {
+void CookiesView::Show(
+    Profile* profile,
+    BrowsingDataLocalStorageHelper* browsing_data_local_storage_helper) {
   DCHECK(profile);
+  DCHECK(browsing_data_local_storage_helper);
 
   // If there's already an existing editor window, activate it.
   if (instance_) {
     gtk_window_present(GTK_WINDOW(instance_->dialog_));
   } else {
-    instance_ = new CookiesView(profile);
+    instance_ = new CookiesView(profile, browsing_data_local_storage_helper);
     instance_->InitStylesAndShow();
   }
 }
 
-CookiesView::CookiesView(Profile* profile)
+CookiesView::CookiesView(
+    Profile* profile,
+    BrowsingDataLocalStorageHelper* browsing_data_local_storage_helper)
     : profile_(profile),
+      browsing_data_local_storage_helper_(browsing_data_local_storage_helper),
       filter_update_factory_(this) {
   Init();
 }
@@ -160,7 +166,8 @@ void CookiesView::Init() {
                                       GTK_SHADOW_ETCHED_IN);
   gtk_box_pack_start(GTK_BOX(cookie_list_vbox), scroll_window, TRUE, TRUE, 0);
 
-  cookies_tree_model_.reset(new CookiesTreeModel(profile_));
+  cookies_tree_model_.reset(new CookiesTreeModel(
+      profile_, browsing_data_local_storage_helper_));
   cookies_tree_adapter_.reset(
       new gtk_tree::TreeAdapter(this, cookies_tree_model_.get()));
   tree_ = gtk_tree_view_new_with_model(
@@ -193,31 +200,54 @@ void CookiesView::Init() {
                    G_CALLBACK(OnSelectionChanged), this);
 
   // Cookie details.
-  GtkWidget* details_frame = gtk_frame_new(NULL);
-  gtk_frame_set_shadow_type(GTK_FRAME(details_frame), GTK_SHADOW_ETCHED_IN);
-  gtk_box_pack_start(GTK_BOX(cookie_list_vbox), details_frame,
+  GtkWidget* cookie_details_frame = gtk_frame_new(NULL);
+  gtk_frame_set_shadow_type(GTK_FRAME(cookie_details_frame),
+                            GTK_SHADOW_ETCHED_IN);
+  gtk_box_pack_start(GTK_BOX(cookie_list_vbox), cookie_details_frame,
                      FALSE, FALSE, 0);
   cookie_details_table_ = gtk_table_new(7, 2, FALSE);
-  gtk_container_add(GTK_CONTAINER(details_frame), cookie_details_table_);
+  gtk_container_add(GTK_CONTAINER(cookie_details_frame), cookie_details_table_);
   gtk_table_set_col_spacing(GTK_TABLE(cookie_details_table_), 0,
                             gtk_util::kLabelSpacing);
 
   int row = 0;
-  InitCookieDetailRow(row++, IDS_COOKIES_COOKIE_NAME_LABEL,
-                      &cookie_name_entry_);
-  InitCookieDetailRow(row++, IDS_COOKIES_COOKIE_CONTENT_LABEL,
-                      &cookie_content_entry_);
-  InitCookieDetailRow(row++, IDS_COOKIES_COOKIE_DOMAIN_LABEL,
-                      &cookie_domain_entry_);
-  InitCookieDetailRow(row++, IDS_COOKIES_COOKIE_PATH_LABEL,
-                      &cookie_path_entry_);
-  InitCookieDetailRow(row++, IDS_COOKIES_COOKIE_SENDFOR_LABEL,
-                      &cookie_send_for_entry_);
-  InitCookieDetailRow(row++, IDS_COOKIES_COOKIE_CREATED_LABEL,
-                      &cookie_created_entry_);
-  InitCookieDetailRow(row++, IDS_COOKIES_COOKIE_EXPIRES_LABEL,
-                      &cookie_expires_entry_);
+  InitDetailRow(row++, IDS_COOKIES_COOKIE_NAME_LABEL,
+                cookie_details_table_, &cookie_name_entry_);
+  InitDetailRow(row++, IDS_COOKIES_COOKIE_CONTENT_LABEL,
+                cookie_details_table_, &cookie_content_entry_);
+  InitDetailRow(row++, IDS_COOKIES_COOKIE_DOMAIN_LABEL,
+                cookie_details_table_, &cookie_domain_entry_);
+  InitDetailRow(row++, IDS_COOKIES_COOKIE_PATH_LABEL,
+                cookie_details_table_, &cookie_path_entry_);
+  InitDetailRow(row++, IDS_COOKIES_COOKIE_SENDFOR_LABEL,
+                cookie_details_table_, &cookie_send_for_entry_);
+  InitDetailRow(row++, IDS_COOKIES_COOKIE_CREATED_LABEL,
+                cookie_details_table_, &cookie_created_entry_);
+  InitDetailRow(row++, IDS_COOKIES_COOKIE_EXPIRES_LABEL,
+                cookie_details_table_, &cookie_expires_entry_);
+
+  // Local storage details.
+  GtkWidget* local_storage_details_frame = gtk_frame_new(NULL);
+  gtk_frame_set_shadow_type(GTK_FRAME(local_storage_details_frame),
+                            GTK_SHADOW_ETCHED_IN);
+  gtk_box_pack_start(GTK_BOX(cookie_list_vbox), local_storage_details_frame,
+                     FALSE, FALSE, 0);
+  local_storage_details_table_ = gtk_table_new(3, 2, FALSE);
+  gtk_container_add(GTK_CONTAINER(local_storage_details_frame),
+                    local_storage_details_table_);
+  gtk_table_set_col_spacing(GTK_TABLE(local_storage_details_table_), 0,
+                            gtk_util::kLabelSpacing);
+
+  row = 0;
+  InitDetailRow(row++, IDS_COOKIES_LOCAL_STORAGE_ORIGIN_LABEL,
+                local_storage_details_table_, &local_storage_origin_entry_);
+  InitDetailRow(row++, IDS_COOKIES_LOCAL_STORAGE_SIZE_ON_DISK_LABEL,
+                local_storage_details_table_, &local_storage_size_entry_);
+  InitDetailRow(row++, IDS_COOKIES_LOCAL_STORAGE_LAST_MODIFIED_LABEL,
+                local_storage_details_table_,
+                &local_storage_last_modified_entry_);
 
+  UpdateVisibleDetailedInfo(cookie_details_table_);
   // Populate the view.
   cookies_tree_adapter_->Init();
   SetInitialTreeState();
@@ -231,30 +261,38 @@ void CookiesView::InitStylesAndShow() {
   GtkStyle* label_style = gtk_widget_get_style(description_label_);
   GtkStyle* dialog_style = gtk_widget_get_style(dialog_);
 
-  InitCookieDetailStyle(cookie_name_entry_, label_style, dialog_style);
-  InitCookieDetailStyle(cookie_content_entry_, label_style, dialog_style);
-  InitCookieDetailStyle(cookie_domain_entry_, label_style, dialog_style);
-  InitCookieDetailStyle(cookie_path_entry_, label_style, dialog_style);
-  InitCookieDetailStyle(cookie_send_for_entry_, label_style, dialog_style);
-  InitCookieDetailStyle(cookie_created_entry_, label_style, dialog_style);
-  InitCookieDetailStyle(cookie_expires_entry_, label_style, dialog_style);
+  // Cookie details.
+  InitBrowserDetailStyle(cookie_name_entry_, label_style, dialog_style);
+  InitBrowserDetailStyle(cookie_content_entry_, label_style, dialog_style);
+  InitBrowserDetailStyle(cookie_domain_entry_, label_style, dialog_style);
+  InitBrowserDetailStyle(cookie_path_entry_, label_style, dialog_style);
+  InitBrowserDetailStyle(cookie_send_for_entry_, label_style, dialog_style);
+  InitBrowserDetailStyle(cookie_created_entry_, label_style, dialog_style);
+  InitBrowserDetailStyle(cookie_expires_entry_, label_style, dialog_style);
+
+  // Local storage details.
+  InitBrowserDetailStyle(local_storage_origin_entry_, label_style,
+                         dialog_style);
+  InitBrowserDetailStyle(local_storage_size_entry_, label_style, dialog_style);
+  InitBrowserDetailStyle(local_storage_last_modified_entry_, label_style,
+                         dialog_style);
 
   gtk_widget_show_all(dialog_);
 }
 
-void CookiesView::InitCookieDetailRow(int row, int label_id,
-                                      GtkWidget** entry) {
+void CookiesView::InitDetailRow(int row, int label_id,
+                                GtkWidget* details_table, GtkWidget** entry) {
   GtkWidget* name_label = gtk_label_new(
       l10n_util::GetStringUTF8(label_id).c_str());
   gtk_misc_set_alignment(GTK_MISC(name_label), 1, 0.5);
-  gtk_table_attach(GTK_TABLE(cookie_details_table_), name_label,
+  gtk_table_attach(GTK_TABLE(details_table), name_label,
                    0, 1, row, row + 1, GTK_FILL, GTK_FILL, 0, 0);
 
   *entry = gtk_entry_new();
 
   gtk_entry_set_editable(GTK_ENTRY(*entry), FALSE);
   gtk_entry_set_has_frame(GTK_ENTRY(*entry), FALSE);
-  gtk_table_attach_defaults(GTK_TABLE(cookie_details_table_), *entry,
+  gtk_table_attach_defaults(GTK_TABLE(details_table), *entry,
                             1, 2, row, row + 1);
 }
 
@@ -279,9 +317,15 @@ void CookiesView::EnableControls() {
         static_cast<CookieTreeNode*>(
             cookies_tree_adapter_->GetNode(&iter))->GetDetailedInfo();
     if (detailed_info.node_type == CookieTreeNode::DetailedInfo::TYPE_COOKIE) {
+      UpdateVisibleDetailedInfo(cookie_details_table_);
       PopulateCookieDetails(detailed_info.cookie->first,
                             detailed_info.cookie->second);
+    } else if (detailed_info.node_type ==
+               CookieTreeNode::DetailedInfo::TYPE_LOCAL_STORAGE) {
+      UpdateVisibleDetailedInfo(local_storage_details_table_);
+      PopulateLocalStorageDetails(*detailed_info.local_storage_info);
     } else {
+      UpdateVisibleDetailedInfo(cookie_details_table_);
       ClearCookieDetails();
     }
   } else {
@@ -299,6 +343,12 @@ void CookiesView::SetCookieDetailsSensitivity(gboolean enabled) {
   gtk_widget_set_sensitive(cookie_expires_entry_, enabled);
 }
 
+void CookiesView::SetLocalStorageDetailsSensitivity(gboolean enabled) {
+  gtk_widget_set_sensitive(local_storage_origin_entry_, enabled);
+  gtk_widget_set_sensitive(local_storage_size_entry_, enabled);
+  gtk_widget_set_sensitive(local_storage_last_modified_entry_, enabled);
+}
+
 void CookiesView::PopulateCookieDetails(
     const std::string& domain,
     const net::CookieMonster::CanonicalCookie& cookie) {
@@ -326,6 +376,22 @@ void CookiesView::PopulateCookieDetails(
   SetCookieDetailsSensitivity(TRUE);
 }
 
+void CookiesView::PopulateLocalStorageDetails(
+    const BrowsingDataLocalStorageHelper::LocalStorageInfo&
+    local_storage_info) {
+  gtk_entry_set_text(GTK_ENTRY(local_storage_origin_entry_),
+                     local_storage_info.origin.c_str());
+  gtk_entry_set_text(GTK_ENTRY(local_storage_size_entry_),
+                     WideToUTF8(FormatBytes(
+                         local_storage_info.size,
+                         GetByteDisplayUnits(local_storage_info.size),
+                         true)).c_str());
+  gtk_entry_set_text(GTK_ENTRY(local_storage_last_modified_entry_),
+                     WideToUTF8(base::TimeFormatFriendlyDateAndTime(
+                         local_storage_info.last_modified)).c_str());
+  SetLocalStorageDetailsSensitivity(TRUE);
+}
+
 void CookiesView::ClearCookieDetails() {
   std::string no_cookie =
       l10n_util::GetStringUTF8(IDS_COOKIES_COOKIE_NONESELECTED);
@@ -387,6 +453,7 @@ void CookiesView::OnResponse(GtkDialog* dialog, int response_id,
     window->RemoveSelectedItems();
   } else if (response_id == RESPONSE_REMOVE_ALL) {
     window->cookies_tree_model_->DeleteAllCookies();
+    window->browsing_data_local_storage_helper_->DeleteAllLocalStorageFiles();
   } else {
     gtk_widget_destroy(window->dialog_);
   }
@@ -437,6 +504,21 @@ void CookiesView::UpdateFilterResults() {
   }
 }
 
+void CookiesView::UpdateVisibleDetailedInfo(GtkWidget* table) {
+  // Toggle the parent (the table frame) visibility and sensitivity.
+  gtk_widget_show(gtk_widget_get_parent(table));
+  // Toggle the other tables.
+  if (table == cookie_details_table_) {
+    SetCookieDetailsSensitivity(true);
+    SetLocalStorageDetailsSensitivity(false);
+    gtk_widget_hide(gtk_widget_get_parent(local_storage_details_table_));
+  } else if (table == local_storage_details_table_) {
+    SetCookieDetailsSensitivity(false);
+    SetLocalStorageDetailsSensitivity(true);
+    gtk_widget_hide(gtk_widget_get_parent(cookie_details_table_));
+  }
+}
+
 // static
 void CookiesView::OnFilterEntryActivated(GtkEntry* entry, CookiesView* window) {
   window->filter_update_factory_.RevokeAll();
diff --git a/chrome/browser/gtk/options/cookies_view.h b/chrome/browser/gtk/options/cookies_view.h
index 260e3db..7720e13 100644
--- a/chrome/browser/gtk/options/cookies_view.h
+++ b/chrome/browser/gtk/options/cookies_view.h
@@ -12,6 +12,7 @@
 #include "base/basictypes.h"
 #include "base/scoped_ptr.h"
 #include "base/task.h"
+#include "chrome/browser/browsing_data_local_storage_helper.h"
 #include "chrome/common/gtk_tree.h"
 #include "net/base/cookie_monster.h"
 #include "testing/gtest/include/gtest/gtest_prod.h"
@@ -31,14 +32,18 @@ class CookiesView : public gtk_tree::TreeAdapter::Delegate {
   virtual ~CookiesView();
 
   // Create (if necessary) and show the cookie manager window.
-  static void Show(Profile* profile);
+  static void Show(
+      Profile* profile,
+      BrowsingDataLocalStorageHelper* browsing_data_local_storage_helper);
 
   // gtk_tree::TreeAdapter::Delegate implementation.
   virtual void OnAnyModelUpdateStart();
   virtual void OnAnyModelUpdate();
 
  private:
-  explicit CookiesView(Profile* profile);
+  CookiesView(
+      Profile* profile,
+      BrowsingDataLocalStorageHelper* browsing_data_local_storage_helper);
 
   // Initialize the dialog contents and layout.
   void Init();
@@ -46,8 +51,9 @@ class CookiesView : public gtk_tree::TreeAdapter::Delegate {
   // Initialize the widget styles and display the dialog.
   void InitStylesAndShow();
 
-  // Helper for initializing cookie details table.
-  void InitCookieDetailRow(int row, int label_id, GtkWidget** display_label);
+  // Helper for initializing cookie / local storage details table.
+  void InitDetailRow(int row, int label_id,
+                     GtkWidget* details_table, GtkWidget** display_label);
 
   // Set the initial selection and tree expanded state.
   void SetInitialTreeState();
@@ -58,10 +64,18 @@ class CookiesView : public gtk_tree::TreeAdapter::Delegate {
   // Set sensitivity of cookie details.
   void SetCookieDetailsSensitivity(gboolean enabled);
 
+  // Set sensitivity of local storage details.
+  void SetLocalStorageDetailsSensitivity(gboolean enabled);
+
   // Show the details of the currently selected cookie.
   void PopulateCookieDetails(const std::string& domain,
                              const net::CookieMonster::CanonicalCookie& cookie);
 
+  // Show the details of the currently selected local storage.
+  void PopulateLocalStorageDetails(
+      const BrowsingDataLocalStorageHelper::LocalStorageInfo&
+      local_storage_info);
+
   // Reset the cookie details display.
   void ClearCookieDetails();
 
@@ -90,6 +104,9 @@ class CookiesView : public gtk_tree::TreeAdapter::Delegate {
   // Filter the list against the text in |filter_entry_|.
   void UpdateFilterResults();
 
+  // Sets which of the detailed info table is visible.
+  void UpdateVisibleDetailedInfo(GtkWidget* table);
+
   // Callbacks for user actions filtering the list.
   static void OnFilterEntryActivated(GtkEntry* entry, CookiesView* window);
   static void OnFilterEntryChanged(GtkEditable* editable, CookiesView* window);
@@ -120,9 +137,19 @@ class CookiesView : public gtk_tree::TreeAdapter::Delegate {
   GtkWidget* cookie_created_entry_;
   GtkWidget* cookie_expires_entry_;
 
+  // The local storage details widgets.
+  GtkWidget* local_storage_details_table_;
+  GtkWidget* local_storage_origin_entry_;
+  GtkWidget* local_storage_size_entry_;
+  GtkWidget* local_storage_last_modified_entry_;
+
   // The profile.
   Profile* profile_;
 
+  // Local Storage Helper.
+  scoped_refptr<BrowsingDataLocalStorageHelper>
+      browsing_data_local_storage_helper_;
+
   // A factory to construct Runnable Methods so that we can be called back to
   // re-evaluate the model after the search query string changes.
   ScopedRunnableMethodFactory<CookiesView> filter_update_factory_;
diff --git a/chrome/browser/gtk/options/cookies_view_unittest.cc b/chrome/browser/gtk/options/cookies_view_unittest.cc
index 935f6c2..4092dbf 100644
--- a/chrome/browser/gtk/options/cookies_view_unittest.cc
+++ b/chrome/browser/gtk/options/cookies_view_unittest.cc
@@ -10,6 +10,7 @@
 #include <gtk/gtk.h>
 
 #include "base/string_util.h"
+#include "chrome/browser/mock_browsing_data_local_storage_helper.h"
 #include "chrome/browser/net/url_request_context_getter.h"
 #include "chrome/test/testing_profile.h"
 #include "net/url_request/url_request_context.h"
@@ -26,24 +27,37 @@ class CookiesViewTest : public testing::Test {
   virtual void SetUp() {
     profile_.reset(new TestingProfile());
     profile_->CreateRequestContext();
+    mock_browsing_data_helper_ = new MockBrowsingDataLocalStorageHelper(
+        profile_.get());
   }
 
-  void CheckDetailsSensitivity(gboolean expected,
+  void CheckDetailsSensitivity(gboolean expected_cookies,
+                               gboolean expected_local_storage,
                                const CookiesView& cookies_view) {
-    EXPECT_EQ(expected,
+    // Cookies
+    EXPECT_EQ(expected_cookies,
               GTK_WIDGET_SENSITIVE(cookies_view.cookie_name_entry_));
-    EXPECT_EQ(expected,
+    EXPECT_EQ(expected_cookies,
               GTK_WIDGET_SENSITIVE(cookies_view.cookie_content_entry_));
-    EXPECT_EQ(expected,
+    EXPECT_EQ(expected_cookies,
               GTK_WIDGET_SENSITIVE(cookies_view.cookie_domain_entry_));
-    EXPECT_EQ(expected,
+    EXPECT_EQ(expected_cookies,
               GTK_WIDGET_SENSITIVE(cookies_view.cookie_path_entry_));
-    EXPECT_EQ(expected,
+    EXPECT_EQ(expected_cookies,
               GTK_WIDGET_SENSITIVE(cookies_view.cookie_send_for_entry_));
-    EXPECT_EQ(expected,
+    EXPECT_EQ(expected_cookies,
               GTK_WIDGET_SENSITIVE(cookies_view.cookie_created_entry_));
-    EXPECT_EQ(expected,
+    EXPECT_EQ(expected_cookies,
               GTK_WIDGET_SENSITIVE(cookies_view.cookie_expires_entry_));
+    // Local Storage
+    EXPECT_EQ(expected_local_storage,
+              GTK_WIDGET_SENSITIVE(cookies_view.local_storage_origin_entry_));
+    EXPECT_EQ(expected_local_storage,
+              GTK_WIDGET_SENSITIVE(cookies_view.local_storage_size_entry_));
+    EXPECT_EQ(expected_local_storage,
+              GTK_WIDGET_SENSITIVE(
+                  cookies_view.local_storage_last_modified_entry_));
+
   }
 
   // Get the cookie names in the cookie list, as a comma seperated string.
@@ -148,13 +162,14 @@ class CookiesViewTest : public testing::Test {
   ChromeThread io_thread_;
 
   scoped_ptr<TestingProfile> profile_;
+  scoped_refptr<MockBrowsingDataLocalStorageHelper> mock_browsing_data_helper_;
 };
 
 TEST_F(CookiesViewTest, Empty) {
-  CookiesView cookies_view(profile_.get());
+  CookiesView cookies_view(profile_.get(), mock_browsing_data_helper_);
   EXPECT_EQ(FALSE, GTK_WIDGET_SENSITIVE(cookies_view.remove_all_button_));
   EXPECT_EQ(FALSE, GTK_WIDGET_SENSITIVE(cookies_view.remove_button_));
-  CheckDetailsSensitivity(FALSE, cookies_view);
+  CheckDetailsSensitivity(FALSE, FALSE, cookies_view);
   EXPECT_STREQ("", GetDisplayedCookies(cookies_view).c_str());
 }
 
@@ -167,21 +182,27 @@ TEST_F(CookiesViewTest, Noop) {
   monster->SetCookie(GURL("http://foo1"), "E=1");
   monster->SetCookie(GURL("http://foo2"), "G=1");
   monster->SetCookie(GURL("http://foo2"), "X=1");
-  CookiesView cookies_view(profile_.get());
+  CookiesView cookies_view(profile_.get(), mock_browsing_data_helper_);
+  mock_browsing_data_helper_->AddLocalStorageSamples();
+  mock_browsing_data_helper_->Notify();
   EXPECT_STREQ("foo0,_Cookies,__C,__D,"
                "foo1,_Cookies,__A,__B,__E,"
-               "foo2,_Cookies,__G,__X",
+               "foo2,_Cookies,__G,__X,"
+               "host1,_Local Storage,__origin1,"
+               "host2,_Local Storage,__origin2",
                GetDisplayedCookies(cookies_view).c_str());
   EXPECT_EQ(TRUE, GTK_WIDGET_SENSITIVE(cookies_view.remove_all_button_));
   EXPECT_EQ(TRUE, GTK_WIDGET_SENSITIVE(cookies_view.remove_button_));
-  CheckDetailsSensitivity(FALSE, cookies_view);
+  CheckDetailsSensitivity(FALSE, FALSE, cookies_view);
 }
 
 TEST_F(CookiesViewTest, RemoveAll) {
   net::CookieMonster* monster = profile_->GetCookieMonster();
   monster->SetCookie(GURL("http://foo"), "A=1");
   monster->SetCookie(GURL("http://foo2"), "B=1");
-  CookiesView cookies_view(profile_.get());
+  CookiesView cookies_view(profile_.get(), mock_browsing_data_helper_);
+  mock_browsing_data_helper_->AddLocalStorageSamples();
+  mock_browsing_data_helper_->Notify();
 
   // Reset the selection of the first row.
   gtk_tree_selection_unselect_all(cookies_view.selection_);
@@ -190,8 +211,10 @@ TEST_F(CookiesViewTest, RemoveAll) {
     SCOPED_TRACE("Before removing");
     EXPECT_EQ(TRUE, GTK_WIDGET_SENSITIVE(cookies_view.remove_all_button_));
     EXPECT_EQ(FALSE, GTK_WIDGET_SENSITIVE(cookies_view.remove_button_));
-    CheckDetailsSensitivity(FALSE, cookies_view);
-    EXPECT_STREQ("foo,_Cookies,__A,foo2,_Cookies,__B",
+    CheckDetailsSensitivity(FALSE, FALSE, cookies_view);
+    EXPECT_STREQ("foo,_Cookies,__A,foo2,_Cookies,__B,"
+                 "host1,_Local Storage,__origin1,"
+                 "host2,_Local Storage,__origin2",
                  GetDisplayedCookies(cookies_view).c_str());
   }
 
@@ -201,8 +224,9 @@ TEST_F(CookiesViewTest, RemoveAll) {
     EXPECT_EQ(0u, monster->GetAllCookies().size());
     EXPECT_EQ(FALSE, GTK_WIDGET_SENSITIVE(cookies_view.remove_all_button_));
     EXPECT_EQ(FALSE, GTK_WIDGET_SENSITIVE(cookies_view.remove_button_));
-    CheckDetailsSensitivity(FALSE, cookies_view);
+    CheckDetailsSensitivity(FALSE, FALSE, cookies_view);
     EXPECT_STREQ("", GetDisplayedCookies(cookies_view).c_str());
+    EXPECT_TRUE(mock_browsing_data_helper_->delete_all_files_called_);
   }
 }
 
@@ -210,7 +234,9 @@ TEST_F(CookiesViewTest, RemoveAllWithDefaultSelected) {
   net::CookieMonster* monster = profile_->GetCookieMonster();
   monster->SetCookie(GURL("http://foo"), "A=1");
   monster->SetCookie(GURL("http://foo2"), "B=1");
-  CookiesView cookies_view(profile_.get());
+  CookiesView cookies_view(profile_.get(), mock_browsing_data_helper_);
+  mock_browsing_data_helper_->AddLocalStorageSamples();
+  mock_browsing_data_helper_->Notify();
 
   EXPECT_STREQ("0", GetSelectedPath(cookies_view).c_str());
   EXPECT_EQ(1, gtk_tree_selection_count_selected_rows(cookies_view.selection_));
@@ -218,8 +244,10 @@ TEST_F(CookiesViewTest, RemoveAllWithDefaultSelected) {
     SCOPED_TRACE("Before removing");
     EXPECT_EQ(TRUE, GTK_WIDGET_SENSITIVE(cookies_view.remove_all_button_));
     EXPECT_EQ(TRUE, GTK_WIDGET_SENSITIVE(cookies_view.remove_button_));
-    CheckDetailsSensitivity(FALSE, cookies_view);
-    EXPECT_STREQ("foo,_Cookies,__A,foo2,_Cookies,__B",
+    CheckDetailsSensitivity(FALSE, FALSE, cookies_view);
+    EXPECT_STREQ("foo,_Cookies,__A,foo2,_Cookies,__B,"
+                 "host1,_Local Storage,__origin1,"
+                 "host2,_Local Storage,__origin2",
                  GetDisplayedCookies(cookies_view).c_str());
   }
 
@@ -229,10 +257,11 @@ TEST_F(CookiesViewTest, RemoveAllWithDefaultSelected) {
     EXPECT_EQ(0u, monster->GetAllCookies().size());
     EXPECT_EQ(FALSE, GTK_WIDGET_SENSITIVE(cookies_view.remove_all_button_));
     EXPECT_EQ(FALSE, GTK_WIDGET_SENSITIVE(cookies_view.remove_button_));
-    CheckDetailsSensitivity(FALSE, cookies_view);
+    CheckDetailsSensitivity(FALSE, FALSE, cookies_view);
     EXPECT_STREQ("", GetDisplayedCookies(cookies_view).c_str());
     EXPECT_EQ(0,
               gtk_tree_selection_count_selected_rows(cookies_view.selection_));
+    EXPECT_TRUE(mock_browsing_data_helper_->delete_all_files_called_);
   }
 }
 
@@ -241,7 +270,9 @@ TEST_F(CookiesViewTest, Remove) {
   monster->SetCookie(GURL("http://foo1"), "A=1");
   monster->SetCookie(GURL("http://foo2"), "B=1");
   monster->SetCookie(GURL("http://foo2"), "C=1");
-  CookiesView cookies_view(profile_.get());
+  CookiesView cookies_view(profile_.get(), mock_browsing_data_helper_);
+  mock_browsing_data_helper_->AddLocalStorageSamples();
+  mock_browsing_data_helper_->Notify();
 
   ASSERT_TRUE(ExpandByPath(cookies_view, "1"));
   ASSERT_TRUE(SelectByPath(cookies_view, "1:0:0"));
@@ -250,8 +281,10 @@ TEST_F(CookiesViewTest, Remove) {
     SCOPED_TRACE("First selection");
     EXPECT_EQ(TRUE, GTK_WIDGET_SENSITIVE(cookies_view.remove_all_button_));
     EXPECT_EQ(TRUE, GTK_WIDGET_SENSITIVE(cookies_view.remove_button_));
-    CheckDetailsSensitivity(TRUE, cookies_view);
-    EXPECT_STREQ("foo1,_Cookies,__A,foo2,+Cookies,++B,++C",
+    CheckDetailsSensitivity(TRUE, FALSE, cookies_view);
+    EXPECT_STREQ("foo1,_Cookies,__A,foo2,+Cookies,++B,++C,"
+                 "host1,_Local Storage,__origin1,"
+                 "host2,_Local Storage,__origin2",
                  GetDisplayedCookies(cookies_view).c_str());
   }
 
@@ -260,12 +293,14 @@ TEST_F(CookiesViewTest, Remove) {
   {
     SCOPED_TRACE("First selection removed");
     EXPECT_STREQ("A,C", GetMonsterCookies(monster).c_str());
-    EXPECT_STREQ("foo1,_Cookies,__A,foo2,+Cookies,++C",
+    EXPECT_STREQ("foo1,_Cookies,__A,foo2,+Cookies,++C,"
+                 "host1,_Local Storage,__origin1,"
+                 "host2,_Local Storage,__origin2",
                  GetDisplayedCookies(cookies_view).c_str());
     EXPECT_EQ(TRUE, GTK_WIDGET_SENSITIVE(cookies_view.remove_all_button_));
     EXPECT_EQ(TRUE, GTK_WIDGET_SENSITIVE(cookies_view.remove_button_));
     EXPECT_STREQ("1:0:0", GetSelectedPath(cookies_view).c_str());
-    CheckDetailsSensitivity(TRUE, cookies_view);
+    CheckDetailsSensitivity(TRUE, FALSE, cookies_view);
   }
 
   EXPECT_EQ(TRUE, GTK_WIDGET_SENSITIVE(cookies_view.remove_button_));
@@ -274,16 +309,20 @@ TEST_F(CookiesViewTest, Remove) {
   {
     SCOPED_TRACE("Second selection");
     EXPECT_STREQ("A", GetMonsterCookies(monster).c_str());
-    EXPECT_STREQ("foo1,_Cookies,__A,foo2,+Cookies",
+    EXPECT_STREQ("foo1,_Cookies,__A,foo2,+Cookies,"
+                 "host1,_Local Storage,__origin1,"
+                 "host2,_Local Storage,__origin2",
                  GetDisplayedCookies(cookies_view).c_str());
     EXPECT_EQ(TRUE, GTK_WIDGET_SENSITIVE(cookies_view.remove_all_button_));
     EXPECT_EQ(TRUE, GTK_WIDGET_SENSITIVE(cookies_view.remove_button_));
     EXPECT_STREQ("1:0", GetSelectedPath(cookies_view).c_str());
-    CheckDetailsSensitivity(FALSE, cookies_view);
+    CheckDetailsSensitivity(FALSE, FALSE, cookies_view);
   }
 
   ASSERT_TRUE(ExpandByPath(cookies_view, "0"));
-  EXPECT_STREQ("foo1,+Cookies,++A,foo2,+Cookies",
+  EXPECT_STREQ("foo1,+Cookies,++A,foo2,+Cookies,"
+               "host1,_Local Storage,__origin1,"
+               "host2,_Local Storage,__origin2",
                GetDisplayedCookies(cookies_view).c_str());
   ASSERT_TRUE(SelectByPath(cookies_view, "0:0:0"));
   EXPECT_EQ(TRUE, GTK_WIDGET_SENSITIVE(cookies_view.remove_button_));
@@ -295,9 +334,35 @@ TEST_F(CookiesViewTest, Remove) {
     EXPECT_EQ(TRUE, GTK_WIDGET_SENSITIVE(cookies_view.remove_all_button_));
     EXPECT_EQ(TRUE, GTK_WIDGET_SENSITIVE(cookies_view.remove_button_));
     EXPECT_STREQ("0:0", GetSelectedPath(cookies_view).c_str());
-    CheckDetailsSensitivity(FALSE, cookies_view);
-    EXPECT_STREQ("foo1,+Cookies,foo2,+Cookies",
+    CheckDetailsSensitivity(FALSE, FALSE, cookies_view);
+    EXPECT_STREQ("foo1,+Cookies,foo2,+Cookies,"
+                 "host1,_Local Storage,__origin1,"
+                 "host2,_Local Storage,__origin2",
+                 GetDisplayedCookies(cookies_view).c_str());
+  }
+
+  ASSERT_TRUE(ExpandByPath(cookies_view, "2"));
+  EXPECT_STREQ("foo1,+Cookies,foo2,+Cookies,"
+               "host1,+Local Storage,++origin1,"
+               "host2,_Local Storage,__origin2",
+               GetDisplayedCookies(cookies_view).c_str());
+  ASSERT_TRUE(SelectByPath(cookies_view, "2:0:0"));
+  EXPECT_EQ(TRUE, GTK_WIDGET_SENSITIVE(cookies_view.remove_button_));
+  gtk_button_clicked(GTK_BUTTON(cookies_view.remove_button_));
+
+  {
+    SCOPED_TRACE("Third selection removed");
+    EXPECT_EQ(0u, monster->GetAllCookies().size());
+    EXPECT_EQ(TRUE, GTK_WIDGET_SENSITIVE(cookies_view.remove_all_button_));
+    EXPECT_EQ(TRUE, GTK_WIDGET_SENSITIVE(cookies_view.remove_button_));
+    EXPECT_STREQ("2:0", GetSelectedPath(cookies_view).c_str());
+    CheckDetailsSensitivity(FALSE, FALSE, cookies_view);
+    EXPECT_STREQ("foo1,+Cookies,foo2,+Cookies,"
+                 "host1,+Local Storage,"
+                 "host2,_Local Storage,__origin2",
                  GetDisplayedCookies(cookies_view).c_str());
+    EXPECT_TRUE(mock_browsing_data_helper_->last_deleted_file_ ==
+                FilePath(FILE_PATH_LITERAL("file1")));
   }
 }
 
@@ -310,16 +375,23 @@ TEST_F(CookiesViewTest, RemoveCookiesByDomain) {
   monster->SetCookie(GURL("http://foo1"), "E=1");
   monster->SetCookie(GURL("http://foo2"), "G=1");
   monster->SetCookie(GURL("http://foo2"), "X=1");
-  CookiesView cookies_view(profile_.get());
+  CookiesView cookies_view(profile_.get(), mock_browsing_data_helper_);
+  mock_browsing_data_helper_->AddLocalStorageSamples();
+  mock_browsing_data_helper_->Notify();
+
   EXPECT_STREQ("foo0,_Cookies,__C,__D,"
                "foo1,_Cookies,__A,__B,__E,"
-               "foo2,_Cookies,__G,__X",
+               "foo2,_Cookies,__G,__X,"
+               "host1,_Local Storage,__origin1,"
+               "host2,_Local Storage,__origin2",
                GetDisplayedCookies(cookies_view).c_str());
 
   ASSERT_TRUE(ExpandByPath(cookies_view, "1"));
   EXPECT_STREQ("foo0,_Cookies,__C,__D,"
                "foo1,+Cookies,++A,++B,++E,"
-               "foo2,_Cookies,__G,__X",
+               "foo2,_Cookies,__G,__X,"
+               "host1,_Local Storage,__origin1,"
+               "host2,_Local Storage,__origin2",
                GetDisplayedCookies(cookies_view).c_str());
   ASSERT_TRUE(SelectByPath(cookies_view, "1:0"));
 
@@ -331,7 +403,9 @@ TEST_F(CookiesViewTest, RemoveCookiesByDomain) {
   EXPECT_STREQ("C,D,G,X", GetMonsterCookies(monster).c_str());
   EXPECT_STREQ("foo0,_Cookies,__C,__D,"
                "foo1,"
-               "foo2,_Cookies,__G,__X",
+               "foo2,_Cookies,__G,__X,"
+               "host1,_Local Storage,__origin1,"
+               "host2,_Local Storage,__origin2",
                GetDisplayedCookies(cookies_view).c_str());
   EXPECT_EQ(TRUE, GTK_WIDGET_SENSITIVE(cookies_view.remove_all_button_));
   EXPECT_EQ(TRUE, GTK_WIDGET_SENSITIVE(cookies_view.remove_button_));
@@ -340,7 +414,9 @@ TEST_F(CookiesViewTest, RemoveCookiesByDomain) {
   ASSERT_TRUE(ExpandByPath(cookies_view, "0"));
   EXPECT_STREQ("foo0,+Cookies,++C,++D,"
                "foo1,"
-               "foo2,_Cookies,__G,__X",
+               "foo2,_Cookies,__G,__X,"
+               "host1,_Local Storage,__origin1,"
+               "host2,_Local Storage,__origin2",
                GetDisplayedCookies(cookies_view).c_str());
   ASSERT_TRUE(SelectByPath(cookies_view, "0:0"));
   gtk_button_clicked(GTK_BUTTON(cookies_view.remove_button_));
@@ -348,7 +424,9 @@ TEST_F(CookiesViewTest, RemoveCookiesByDomain) {
   EXPECT_STREQ("G,X", GetMonsterCookies(monster).c_str());
   EXPECT_STREQ("foo0,"
                "foo1,"
-               "foo2,_Cookies,__G,__X",
+               "foo2,_Cookies,__G,__X,"
+               "host1,_Local Storage,__origin1,"
+               "host2,_Local Storage,__origin2",
                GetDisplayedCookies(cookies_view).c_str());
   EXPECT_EQ(TRUE, GTK_WIDGET_SENSITIVE(cookies_view.remove_all_button_));
   EXPECT_EQ(TRUE, GTK_WIDGET_SENSITIVE(cookies_view.remove_button_));
@@ -357,7 +435,9 @@ TEST_F(CookiesViewTest, RemoveCookiesByDomain) {
   ASSERT_TRUE(ExpandByPath(cookies_view, "2"));
   EXPECT_STREQ("foo0,"
                "foo1,"
-               "foo2,+Cookies,++G,++X",
+               "foo2,+Cookies,++G,++X,"
+               "host1,_Local Storage,__origin1,"
+               "host2,_Local Storage,__origin2",
                GetDisplayedCookies(cookies_view).c_str());
   ASSERT_TRUE(SelectByPath(cookies_view, "2:0"));
   gtk_button_clicked(GTK_BUTTON(cookies_view.remove_button_));
@@ -365,11 +445,36 @@ TEST_F(CookiesViewTest, RemoveCookiesByDomain) {
   EXPECT_STREQ("", GetMonsterCookies(monster).c_str());
   EXPECT_STREQ("foo0,"
                "foo1,"
-               "foo2",
+               "foo2,"
+               "host1,_Local Storage,__origin1,"
+               "host2,_Local Storage,__origin2",
                GetDisplayedCookies(cookies_view).c_str());
   EXPECT_EQ(TRUE, GTK_WIDGET_SENSITIVE(cookies_view.remove_all_button_));
   EXPECT_EQ(TRUE, GTK_WIDGET_SENSITIVE(cookies_view.remove_button_));
   EXPECT_STREQ("2", GetSelectedPath(cookies_view).c_str());
+
+  ASSERT_TRUE(ExpandByPath(cookies_view, "3"));
+  EXPECT_STREQ("foo0,"
+               "foo1,"
+               "foo2,"
+               "host1,+Local Storage,++origin1,"
+               "host2,_Local Storage,__origin2",
+               GetDisplayedCookies(cookies_view).c_str());
+  ASSERT_TRUE(SelectByPath(cookies_view, "3:0"));
+  gtk_button_clicked(GTK_BUTTON(cookies_view.remove_button_));
+
+  EXPECT_STREQ("", GetMonsterCookies(monster).c_str());
+  EXPECT_STREQ("foo0,"
+               "foo1,"
+               "foo2,"
+               "host1,"
+               "host2,_Local Storage,__origin2",
+               GetDisplayedCookies(cookies_view).c_str());
+  EXPECT_EQ(TRUE, GTK_WIDGET_SENSITIVE(cookies_view.remove_all_button_));
+  EXPECT_EQ(TRUE, GTK_WIDGET_SENSITIVE(cookies_view.remove_button_));
+  EXPECT_STREQ("3", GetSelectedPath(cookies_view).c_str());
+  EXPECT_TRUE(mock_browsing_data_helper_->last_deleted_file_ ==
+              FilePath(FILE_PATH_LITERAL("file1")));
 }
 
 TEST_F(CookiesViewTest, RemoveByDomain) {
@@ -381,10 +486,15 @@ TEST_F(CookiesViewTest, RemoveByDomain) {
   monster->SetCookie(GURL("http://foo1"), "E=1");
   monster->SetCookie(GURL("http://foo2"), "G=1");
   monster->SetCookie(GURL("http://foo2"), "X=1");
-  CookiesView cookies_view(profile_.get());
+  CookiesView cookies_view(profile_.get(), mock_browsing_data_helper_);
+  mock_browsing_data_helper_->AddLocalStorageSamples();
+  mock_browsing_data_helper_->Notify();
+
   EXPECT_STREQ("foo0,_Cookies,__C,__D,"
                "foo1,_Cookies,__A,__B,__E,"
-               "foo2,_Cookies,__G,__X",
+               "foo2,_Cookies,__G,__X,"
+               "host1,_Local Storage,__origin1,"
+               "host2,_Local Storage,__origin2",
                GetDisplayedCookies(cookies_view).c_str());
 
   ASSERT_TRUE(SelectByPath(cookies_view, "1"));
@@ -396,7 +506,9 @@ TEST_F(CookiesViewTest, RemoveByDomain) {
 
   EXPECT_STREQ("C,D,G,X", GetMonsterCookies(monster).c_str());
   EXPECT_STREQ("foo0,_Cookies,__C,__D,"
-               "foo2,_Cookies,__G,__X",
+               "foo2,_Cookies,__G,__X,"
+               "host1,_Local Storage,__origin1,"
+               "host2,_Local Storage,__origin2",
                GetDisplayedCookies(cookies_view).c_str());
   EXPECT_EQ(TRUE, GTK_WIDGET_SENSITIVE(cookies_view.remove_all_button_));
   EXPECT_EQ(TRUE, GTK_WIDGET_SENSITIVE(cookies_view.remove_button_));
@@ -406,7 +518,9 @@ TEST_F(CookiesViewTest, RemoveByDomain) {
   gtk_button_clicked(GTK_BUTTON(cookies_view.remove_button_));
 
   EXPECT_STREQ("G,X", GetMonsterCookies(monster).c_str());
-  EXPECT_STREQ("foo2,_Cookies,__G,__X",
+  EXPECT_STREQ("foo2,_Cookies,__G,__X,"
+               "host1,_Local Storage,__origin1,"
+               "host2,_Local Storage,__origin2",
                GetDisplayedCookies(cookies_view).c_str());
   EXPECT_EQ(TRUE, GTK_WIDGET_SENSITIVE(cookies_view.remove_all_button_));
   EXPECT_EQ(TRUE, GTK_WIDGET_SENSITIVE(cookies_view.remove_button_));
@@ -415,9 +529,33 @@ TEST_F(CookiesViewTest, RemoveByDomain) {
   gtk_button_clicked(GTK_BUTTON(cookies_view.remove_button_));
 
   EXPECT_STREQ("", GetMonsterCookies(monster).c_str());
-  EXPECT_STREQ("", GetDisplayedCookies(cookies_view).c_str());
+  EXPECT_STREQ("host1,_Local Storage,__origin1,"
+               "host2,_Local Storage,__origin2",
+               GetDisplayedCookies(cookies_view).c_str());
+  EXPECT_EQ(TRUE, GTK_WIDGET_SENSITIVE(cookies_view.remove_all_button_));
+  EXPECT_EQ(TRUE, GTK_WIDGET_SENSITIVE(cookies_view.remove_button_));
+  EXPECT_STREQ("0", GetSelectedPath(cookies_view).c_str());
+
+  gtk_button_clicked(GTK_BUTTON(cookies_view.remove_button_));
+
+  EXPECT_STREQ("", GetMonsterCookies(monster).c_str());
+  EXPECT_STREQ("host2,_Local Storage,__origin2",
+               GetDisplayedCookies(cookies_view).c_str());
+  EXPECT_EQ(TRUE, GTK_WIDGET_SENSITIVE(cookies_view.remove_all_button_));
+  EXPECT_EQ(TRUE, GTK_WIDGET_SENSITIVE(cookies_view.remove_button_));
+  EXPECT_TRUE(mock_browsing_data_helper_->last_deleted_file_ ==
+        FilePath(FILE_PATH_LITERAL("file1")));
+  EXPECT_STREQ("0", GetSelectedPath(cookies_view).c_str());
+
+  gtk_button_clicked(GTK_BUTTON(cookies_view.remove_button_));
+  EXPECT_STREQ("", GetMonsterCookies(monster).c_str());
+  EXPECT_STREQ("",
+               GetDisplayedCookies(cookies_view).c_str());
   EXPECT_EQ(FALSE, GTK_WIDGET_SENSITIVE(cookies_view.remove_all_button_));
   EXPECT_EQ(FALSE, GTK_WIDGET_SENSITIVE(cookies_view.remove_button_));
+  EXPECT_TRUE(mock_browsing_data_helper_->last_deleted_file_ ==
+        FilePath(FILE_PATH_LITERAL("file2")));
+
   EXPECT_EQ(0, gtk_tree_selection_count_selected_rows(cookies_view.selection_));
 }
 
@@ -430,10 +568,15 @@ TEST_F(CookiesViewTest, RemoveDefaultSelection) {
   monster->SetCookie(GURL("http://foo1"), "E=1");
   monster->SetCookie(GURL("http://foo2"), "G=1");
   monster->SetCookie(GURL("http://foo2"), "X=1");
-  CookiesView cookies_view(profile_.get());
+  CookiesView cookies_view(profile_.get(), mock_browsing_data_helper_);
+  mock_browsing_data_helper_->AddLocalStorageSamples();
+  mock_browsing_data_helper_->Notify();
+
   EXPECT_STREQ("foo0,_Cookies,__C,__D,"
                "foo1,_Cookies,__A,__B,__E,"
-               "foo2,_Cookies,__G,__X",
+               "foo2,_Cookies,__G,__X,"
+               "host1,_Local Storage,__origin1,"
+               "host2,_Local Storage,__origin2",
                GetDisplayedCookies(cookies_view).c_str());
 
 
@@ -444,7 +587,9 @@ TEST_F(CookiesViewTest, RemoveDefaultSelection) {
 
   EXPECT_STREQ("B,A,E,G,X", GetMonsterCookies(monster).c_str());
   EXPECT_STREQ("foo1,_Cookies,__A,__B,__E,"
-               "foo2,_Cookies,__G,__X",
+               "foo2,_Cookies,__G,__X,"
+               "host1,_Local Storage,__origin1,"
+               "host2,_Local Storage,__origin2",
                GetDisplayedCookies(cookies_view).c_str());
 
   EXPECT_EQ(TRUE, GTK_WIDGET_SENSITIVE(cookies_view.remove_all_button_));
@@ -453,7 +598,28 @@ TEST_F(CookiesViewTest, RemoveDefaultSelection) {
   gtk_button_clicked(GTK_BUTTON(cookies_view.remove_button_));
 
   EXPECT_STREQ("G,X", GetMonsterCookies(monster).c_str());
-  EXPECT_STREQ("foo2,_Cookies,__G,__X",
+  EXPECT_STREQ("foo2,_Cookies,__G,__X,"
+               "host1,_Local Storage,__origin1,"
+               "host2,_Local Storage,__origin2",
+               GetDisplayedCookies(cookies_view).c_str());
+
+  EXPECT_EQ(TRUE, GTK_WIDGET_SENSITIVE(cookies_view.remove_all_button_));
+  EXPECT_EQ(TRUE, GTK_WIDGET_SENSITIVE(cookies_view.remove_button_));
+
+  gtk_button_clicked(GTK_BUTTON(cookies_view.remove_button_));
+
+  EXPECT_STREQ("", GetMonsterCookies(monster).c_str());
+  EXPECT_STREQ("host1,_Local Storage,__origin1,"
+               "host2,_Local Storage,__origin2",
+               GetDisplayedCookies(cookies_view).c_str());
+
+  EXPECT_EQ(TRUE, GTK_WIDGET_SENSITIVE(cookies_view.remove_all_button_));
+  EXPECT_EQ(TRUE, GTK_WIDGET_SENSITIVE(cookies_view.remove_button_));
+
+  gtk_button_clicked(GTK_BUTTON(cookies_view.remove_button_));
+
+  EXPECT_STREQ("", GetMonsterCookies(monster).c_str());
+  EXPECT_STREQ("host2,_Local Storage,__origin2",
                GetDisplayedCookies(cookies_view).c_str());
 
   EXPECT_EQ(TRUE, GTK_WIDGET_SENSITIVE(cookies_view.remove_all_button_));
@@ -474,11 +640,16 @@ TEST_F(CookiesViewTest, Filter) {
   monster->SetCookie(GURL("http://bar0"), "D=1");
   monster->SetCookie(GURL("http://foo1"), "B=1");
   monster->SetCookie(GURL("http://bar1"), "A=1");
-  CookiesView cookies_view(profile_.get());
+  CookiesView cookies_view(profile_.get(), mock_browsing_data_helper_);
+  mock_browsing_data_helper_->AddLocalStorageSamples();
+  mock_browsing_data_helper_->Notify();
+
   EXPECT_STREQ("bar0,_Cookies,__D,"
                "bar1,_Cookies,__A,"
                "foo0,_Cookies,__C,"
-               "foo1,_Cookies,__B",
+               "foo1,_Cookies,__B,"
+               "host1,_Local Storage,__origin1,"
+               "host2,_Local Storage,__origin2",
                GetDisplayedCookies(cookies_view).c_str());
 
   EXPECT_EQ(FALSE, GTK_WIDGET_SENSITIVE(cookies_view.filter_clear_button_));
@@ -489,7 +660,9 @@ TEST_F(CookiesViewTest, Filter) {
   EXPECT_STREQ("bar0,_Cookies,__D,"
                "bar1,_Cookies,__A,"
                "foo0,_Cookies,__C,"
-               "foo1,_Cookies,__B",
+               "foo1,_Cookies,__B,"
+               "host1,_Local Storage,__origin1,"
+               "host2,_Local Storage,__origin2",
                GetDisplayedCookies(cookies_view).c_str());
 
   // Results are filtered immediately if you activate (hit enter in the entry).
@@ -504,7 +677,15 @@ TEST_F(CookiesViewTest, Filter) {
   EXPECT_STREQ("bar0,_Cookies,__D,"
                "bar1,_Cookies,__A,"
                "foo0,_Cookies,__C,"
-               "foo1,_Cookies,__B",
+               "foo1,_Cookies,__B,"
+               "host1,_Local Storage,__origin1,"
+               "host2,_Local Storage,__origin2",
+               GetDisplayedCookies(cookies_view).c_str());
+
+  gtk_entry_set_text(GTK_ENTRY(cookies_view.filter_entry_), "hos");
+  gtk_widget_activate(cookies_view.filter_entry_);
+  EXPECT_STREQ("host1,_Local Storage,__origin1,"
+               "host2,_Local Storage,__origin2",
                GetDisplayedCookies(cookies_view).c_str());
 }
 
@@ -514,11 +695,16 @@ TEST_F(CookiesViewTest, FilterRemoveAll) {
   monster->SetCookie(GURL("http://bar0"), "D=1");
   monster->SetCookie(GURL("http://foo1"), "B=1");
   monster->SetCookie(GURL("http://bar1"), "A=1");
-  CookiesView cookies_view(profile_.get());
+  CookiesView cookies_view(profile_.get(), mock_browsing_data_helper_);
+  mock_browsing_data_helper_->AddLocalStorageSamples();
+  mock_browsing_data_helper_->Notify();
+
   EXPECT_STREQ("bar0,_Cookies,__D,"
                "bar1,_Cookies,__A,"
                "foo0,_Cookies,__C,"
-               "foo1,_Cookies,__B",
+               "foo1,_Cookies,__B,"
+               "host1,_Local Storage,__origin1,"
+               "host2,_Local Storage,__origin2",
                GetDisplayedCookies(cookies_view).c_str());
 
   EXPECT_EQ(FALSE, GTK_WIDGET_SENSITIVE(cookies_view.filter_clear_button_));
@@ -529,7 +715,9 @@ TEST_F(CookiesViewTest, FilterRemoveAll) {
   EXPECT_STREQ("bar0,_Cookies,__D,"
                "bar1,_Cookies,__A,"
                "foo0,_Cookies,__C,"
-               "foo1,_Cookies,__B",
+               "foo1,_Cookies,__B,"
+               "host1,_Local Storage,__origin1,"
+               "host2,_Local Storage,__origin2",
                GetDisplayedCookies(cookies_view).c_str());
 
   // Results are filtered immediately if you activate (hit enter in the entry).
@@ -550,7 +738,9 @@ TEST_F(CookiesViewTest, FilterRemoveAll) {
   EXPECT_EQ(FALSE, GTK_WIDGET_SENSITIVE(cookies_view.filter_clear_button_));
   EXPECT_STREQ("", gtk_entry_get_text(GTK_ENTRY(cookies_view.filter_entry_)));
   EXPECT_STREQ("foo0,_Cookies,__C,"
-               "foo1,_Cookies,__B",
+               "foo1,_Cookies,__B,"
+               "host1,_Local Storage,__origin1,"
+               "host2,_Local Storage,__origin2",
                GetDisplayedCookies(cookies_view).c_str());
 }
 
@@ -561,11 +751,16 @@ TEST_F(CookiesViewTest, FilterRemove) {
   monster->SetCookie(GURL("http://foo1"), "B=1");
   monster->SetCookie(GURL("http://bar1"), "A=1");
   monster->SetCookie(GURL("http://bar1"), "E=1");
-  CookiesView cookies_view(profile_.get());
+  CookiesView cookies_view(profile_.get(), mock_browsing_data_helper_);
+  mock_browsing_data_helper_->AddLocalStorageSamples();
+  mock_browsing_data_helper_->Notify();
+
   EXPECT_STREQ("bar0,_Cookies,__D,"
                "bar1,_Cookies,__A,__E,"
                "foo0,_Cookies,__C,"
-               "foo1,_Cookies,__B",
+               "foo1,_Cookies,__B,"
+               "host1,_Local Storage,__origin1,"
+               "host2,_Local Storage,__origin2",
                GetDisplayedCookies(cookies_view).c_str());
   EXPECT_STREQ("D,A,E,C,B", GetMonsterCookies(monster).c_str());
 
@@ -577,7 +772,9 @@ TEST_F(CookiesViewTest, FilterRemove) {
   EXPECT_STREQ("bar0,_Cookies,__D,"
                "bar1,_Cookies,__A,__E,"
                "foo0,_Cookies,__C,"
-               "foo1,_Cookies,__B",
+               "foo1,_Cookies,__B,"
+               "host1,_Local Storage,__origin1,"
+               "host2,_Local Storage,__origin2",
                GetDisplayedCookies(cookies_view).c_str());
 
   // Results are filtered immediately if you activate (hit enter in the entry).
@@ -596,7 +793,7 @@ TEST_F(CookiesViewTest, FilterRemove) {
     SCOPED_TRACE("First selection");
     EXPECT_EQ(TRUE, GTK_WIDGET_SENSITIVE(cookies_view.remove_all_button_));
     EXPECT_EQ(TRUE, GTK_WIDGET_SENSITIVE(cookies_view.remove_button_));
-    CheckDetailsSensitivity(TRUE, cookies_view);
+    CheckDetailsSensitivity(TRUE, FALSE, cookies_view);
   }
 
   gtk_button_clicked(GTK_BUTTON(cookies_view.remove_button_));
@@ -610,7 +807,7 @@ TEST_F(CookiesViewTest, FilterRemove) {
     EXPECT_EQ(TRUE, GTK_WIDGET_SENSITIVE(cookies_view.remove_all_button_));
     EXPECT_EQ(TRUE, GTK_WIDGET_SENSITIVE(cookies_view.remove_button_));
     EXPECT_STREQ("1:0:0", GetSelectedPath(cookies_view).c_str());
-    CheckDetailsSensitivity(TRUE, cookies_view);
+    CheckDetailsSensitivity(TRUE, FALSE, cookies_view);
   }
 
   gtk_button_clicked(GTK_BUTTON(cookies_view.remove_button_));
@@ -624,7 +821,7 @@ TEST_F(CookiesViewTest, FilterRemove) {
     EXPECT_EQ(TRUE, GTK_WIDGET_SENSITIVE(cookies_view.remove_all_button_));
     EXPECT_EQ(TRUE, GTK_WIDGET_SENSITIVE(cookies_view.remove_button_));
     EXPECT_STREQ("1:0", GetSelectedPath(cookies_view).c_str());
-    CheckDetailsSensitivity(FALSE, cookies_view);
+    CheckDetailsSensitivity(FALSE, FALSE, cookies_view);
   }
 
   ASSERT_TRUE(ExpandByPath(cookies_view, "0"));
@@ -638,7 +835,7 @@ TEST_F(CookiesViewTest, FilterRemove) {
     EXPECT_EQ(TRUE, GTK_WIDGET_SENSITIVE(cookies_view.remove_all_button_));
     EXPECT_EQ(TRUE, GTK_WIDGET_SENSITIVE(cookies_view.remove_button_));
     EXPECT_STREQ("0:0", GetSelectedPath(cookies_view).c_str());
-    CheckDetailsSensitivity(FALSE, cookies_view);
+    CheckDetailsSensitivity(FALSE, FALSE, cookies_view);
     EXPECT_STREQ("bar0,+Cookies,"
                  "bar1,+Cookies",
                  GetDisplayedCookies(cookies_view).c_str());
@@ -648,6 +845,50 @@ TEST_F(CookiesViewTest, FilterRemove) {
   EXPECT_EQ(FALSE, GTK_WIDGET_SENSITIVE(cookies_view.filter_clear_button_));
   EXPECT_STREQ("", gtk_entry_get_text(GTK_ENTRY(cookies_view.filter_entry_)));
   EXPECT_STREQ("foo0,_Cookies,__C,"
-               "foo1,_Cookies,__B",
+               "foo1,_Cookies,__B,"
+               "host1,_Local Storage,__origin1,"
+               "host2,_Local Storage,__origin2",
                GetDisplayedCookies(cookies_view).c_str());
+
+  gtk_entry_set_text(GTK_ENTRY(cookies_view.filter_entry_), "hos");
+  EXPECT_EQ(TRUE, GTK_WIDGET_SENSITIVE(cookies_view.filter_clear_button_));
+  // Entering text doesn't immediately filter the results.
+  EXPECT_STREQ("foo0,_Cookies,__C,"
+               "foo1,_Cookies,__B,"
+               "host1,_Local Storage,__origin1,"
+               "host2,_Local Storage,__origin2",
+               GetDisplayedCookies(cookies_view).c_str());
+
+  // Results are filtered immediately if you activate (hit enter in the entry).
+  gtk_widget_activate(cookies_view.filter_entry_);
+  EXPECT_STREQ("host1,_Local Storage,__origin1,"
+               "host2,_Local Storage,__origin2",
+               GetDisplayedCookies(cookies_view).c_str());
+
+  ASSERT_TRUE(ExpandByPath(cookies_view, "1"));
+  EXPECT_STREQ("host1,_Local Storage,__origin1,"
+               "host2,+Local Storage,++origin2",
+               GetDisplayedCookies(cookies_view).c_str());
+  ASSERT_TRUE(SelectByPath(cookies_view, "1:0:0"));
+
+  {
+    SCOPED_TRACE("First selection");
+    EXPECT_EQ(TRUE, GTK_WIDGET_SENSITIVE(cookies_view.remove_all_button_));
+    EXPECT_EQ(TRUE, GTK_WIDGET_SENSITIVE(cookies_view.remove_button_));
+    CheckDetailsSensitivity(FALSE, TRUE, cookies_view);
+  }
+
+  gtk_button_clicked(GTK_BUTTON(cookies_view.remove_button_));
+
+  {
+    SCOPED_TRACE("First selection removed");
+    EXPECT_STREQ("C,B", GetMonsterCookies(monster).c_str());
+    EXPECT_STREQ("host1,_Local Storage,__origin1,"
+           "host2,+Local Storage",
+                 GetDisplayedCookies(cookies_view).c_str());
+    EXPECT_EQ(TRUE, GTK_WIDGET_SENSITIVE(cookies_view.remove_all_button_));
+    EXPECT_EQ(TRUE, GTK_WIDGET_SENSITIVE(cookies_view.remove_button_));
+    EXPECT_STREQ("1:0", GetSelectedPath(cookies_view).c_str());
+    CheckDetailsSensitivity(FALSE, FALSE, cookies_view);
+  }
 }
diff --git a/chrome/browser/in_process_webkit/dom_storage_context.cc b/chrome/browser/in_process_webkit/dom_storage_context.cc
index 3997c5f..c19b6cf 100644
--- a/chrome/browser/in_process_webkit/dom_storage_context.cc
+++ b/chrome/browser/in_process_webkit/dom_storage_context.cc
@@ -11,13 +11,22 @@
 #include "chrome/browser/in_process_webkit/dom_storage_namespace.h"
 #include "chrome/browser/in_process_webkit/webkit_context.h"
 #include "chrome/common/dom_storage_common.h"
+#include "webkit/glue/glue_util.h"
 
-static const char* kLocalStorageDirectory = "Local Storage";
+const FilePath::CharType DOMStorageContext::kLocalStorageDirectory[] =
+    FILE_PATH_LITERAL("Local Storage");
+
+const FilePath::CharType DOMStorageContext::kLocalStorageExtension[] =
+    FILE_PATH_LITERAL(".localstorage");
+
+static const FilePath::CharType kLocalStorageOldPath[] =
+    FILE_PATH_LITERAL("localStorage");
 
 // TODO(jorlow): Remove after Chrome 4 ships.
 static void MigrateLocalStorageDirectory(const FilePath& data_path) {
-  FilePath new_path = data_path.AppendASCII(kLocalStorageDirectory);
-  FilePath old_path = data_path.AppendASCII("localStorage");
+  FilePath new_path = data_path.Append(
+      DOMStorageContext::kLocalStorageDirectory);
+  FilePath old_path = data_path.Append(kLocalStorageOldPath);
   if (!file_util::DirectoryExists(new_path) &&
       file_util::DirectoryExists(old_path)) {
     file_util::Move(old_path, new_path);
@@ -148,7 +157,7 @@ void DOMStorageContext::DeleteDataModifiedSince(const base::Time& cutoff) {
   PurgeMemory();
 
   file_util::FileEnumerator file_enumerator(
-      webkit_context_->data_path().AppendASCII(kLocalStorageDirectory), false,
+      webkit_context_->data_path().Append(kLocalStorageDirectory), false,
       file_util::FileEnumerator::FILES);
   for (FilePath path = file_enumerator.Next(); !path.value().empty();
        path = file_enumerator.Next()) {
@@ -159,12 +168,41 @@ void DOMStorageContext::DeleteDataModifiedSince(const base::Time& cutoff) {
   }
 }
 
+void DOMStorageContext::DeleteLocalStorageFile(const FilePath& file_path) {
+  DCHECK(ChromeThread::CurrentlyOn(ChromeThread::WEBKIT));
+
+  // Make sure that we don't delete a database that's currently being accessed
+  // by unloading all of the databases temporarily.
+  // TODO(bulach): both this method and DeleteDataModifiedSince could purge
+  // only the memory used by the specific file instead of all memory at once.
+  // See http://code.google.com/p/chromium/issues/detail?id=32000
+  PurgeMemory();
+  file_util::Delete(file_path, false);
+}
+
+void DOMStorageContext::DeleteAllLocalStorageFiles() {
+  DCHECK(ChromeThread::CurrentlyOn(ChromeThread::WEBKIT));
+
+  // Make sure that we don't delete a database that's currently being accessed
+  // by unloading all of the databases temporarily.
+  PurgeMemory();
+
+  file_util::FileEnumerator file_enumerator(
+      webkit_context_->data_path().Append(kLocalStorageDirectory), false,
+      file_util::FileEnumerator::FILES);
+  for (FilePath file_path = file_enumerator.Next(); !file_path.empty();
+       file_path = file_enumerator.Next()) {
+    if (file_path.Extension() == kLocalStorageExtension)
+      file_util::Delete(file_path, false);
+  }
+}
+
 DOMStorageNamespace* DOMStorageContext::CreateLocalStorage() {
   FilePath data_path = webkit_context_->data_path();
   FilePath dir_path;
   if (!data_path.empty()) {
     MigrateLocalStorageDirectory(data_path);
-    dir_path = data_path.AppendASCII(kLocalStorageDirectory);
+    dir_path = data_path.Append(kLocalStorageDirectory);
   }
   DOMStorageNamespace* new_namespace =
       DOMStorageNamespace::CreateLocalStorageNamespace(this, dir_path);
diff --git a/chrome/browser/in_process_webkit/dom_storage_context.h b/chrome/browser/in_process_webkit/dom_storage_context.h
index fb68f4c9..4981c07 100644
--- a/chrome/browser/in_process_webkit/dom_storage_context.h
+++ b/chrome/browser/in_process_webkit/dom_storage_context.h
@@ -66,6 +66,18 @@ class DOMStorageContext {
   // date that's supplied.
   void DeleteDataModifiedSince(const base::Time& cutoff);
 
+  // Deletes a single local storage file.
+  void DeleteLocalStorageFile(const FilePath& file_path);
+
+  // Deletes all local storage files.
+  void DeleteAllLocalStorageFiles();
+
+  // The local storage directory.
+  static const FilePath::CharType kLocalStorageDirectory[];
+
+  // The local storage file extension.
+  static const FilePath::CharType kLocalStorageExtension[];
+
  private:
   // Get the local storage instance.  The object is owned by this class.
   DOMStorageNamespace* CreateLocalStorage();
diff --git a/chrome/browser/mock_browsing_data_local_storage_helper.cc b/chrome/browser/mock_browsing_data_local_storage_helper.cc
new file mode 100644
index 0000000..b654a37f
--- /dev/null
+++ b/chrome/browser/mock_browsing_data_local_storage_helper.cc
@@ -0,0 +1,47 @@
+// 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 "chrome/browser/mock_browsing_data_local_storage_helper.h"
+
+MockBrowsingDataLocalStorageHelper::MockBrowsingDataLocalStorageHelper(
+  Profile* profile)
+  : BrowsingDataLocalStorageHelper(profile),
+    profile_(profile),
+    callback_(NULL),
+    delete_all_files_called_(false) {
+}
+
+void MockBrowsingDataLocalStorageHelper::StartFetching(
+    Callback1<const std::vector<LocalStorageInfo>& >::Type* callback) {
+  callback_ = callback;
+}
+
+void MockBrowsingDataLocalStorageHelper::CancelNotification() {
+  callback_ = NULL;
+}
+
+void MockBrowsingDataLocalStorageHelper::DeleteLocalStorageFile(
+    const FilePath& file_path) {
+  last_deleted_file_ = file_path;
+}
+
+void MockBrowsingDataLocalStorageHelper::DeleteAllLocalStorageFiles() {
+  delete_all_files_called_ = true;
+}
+
+void MockBrowsingDataLocalStorageHelper::AddLocalStorageSamples() {
+  response_.push_back(
+      BrowsingDataLocalStorageHelper::LocalStorageInfo(
+          "http", "host1", 1, "db1", "origin1",
+          FilePath(FILE_PATH_LITERAL("file1")), 1, base::Time()));
+  response_.push_back(
+      BrowsingDataLocalStorageHelper::LocalStorageInfo(
+          "http", "host2", 2, "db2", "origin2",
+          FilePath(FILE_PATH_LITERAL("file2")), 2, base::Time()));
+}
+
+void MockBrowsingDataLocalStorageHelper::Notify() {
+  CHECK(callback_);
+  callback_->Run(response_);
+}
diff --git a/chrome/browser/mock_browsing_data_local_storage_helper.h b/chrome/browser/mock_browsing_data_local_storage_helper.h
new file mode 100644
index 0000000..e60d772
--- /dev/null
+++ b/chrome/browser/mock_browsing_data_local_storage_helper.h
@@ -0,0 +1,37 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_MOCK_BROWSING_DATA_LOCAL_STORAGE_HELPER_H_
+#define CHROME_BROWSER_MOCK_BROWSING_DATA_LOCAL_STORAGE_HELPER_H_
+
+#include "chrome/browser/browsing_data_local_storage_helper.h"
+
+// Mock for BrowsingDataLocalStorageHelper.
+// Use AddLocalStorageSamples() or add directly to response_ vector, then
+// call Notify().
+class MockBrowsingDataLocalStorageHelper
+    : public BrowsingDataLocalStorageHelper {
+ public:
+  explicit MockBrowsingDataLocalStorageHelper(Profile* profile);
+
+  virtual void StartFetching(
+      Callback1<const std::vector<LocalStorageInfo>& >::Type* callback);
+  virtual void CancelNotification();
+  virtual void DeleteLocalStorageFile(const FilePath& file_path);
+  virtual void DeleteAllLocalStorageFiles();
+
+  // Adds some LocalStorageInfo samples.
+  void AddLocalStorageSamples();
+
+  // Notifies the callback.
+  void Notify();
+
+  Profile* profile_;
+  Callback1<const std::vector<LocalStorageInfo>& >::Type* callback_;
+  FilePath last_deleted_file_;
+  bool delete_all_files_called_;
+  std::vector<LocalStorageInfo> response_;
+};
+
+#endif  // CHROME_BROWSER_MOCK_BROWSING_DATA_LOCAL_STORAGE_HELPER_H_
diff --git a/chrome/browser/views/options/cookies_view.cc b/chrome/browser/views/options/cookies_view.cc
index 7e5f0b4..2500e7e 100644
--- a/chrome/browser/views/options/cookies_view.cc
+++ b/chrome/browser/views/options/cookies_view.cc
@@ -31,7 +31,6 @@ static const int kCookieInfoViewBorderSize = 1;
 static const int kCookieInfoViewInsetSize = 3;
 static const int kSearchFilterDelayMs = 500;
 
-
 ///////////////////////////////////////////////////////////////////////////////
 // CookiesTreeView
 //  Overridden to handle Delete key presses
@@ -49,20 +48,19 @@ class CookiesTreeView : public views::TreeView {
 };
 
 CookiesTreeView::CookiesTreeView(CookiesTreeModel* cookies_model) {
-      SetModel(cookies_model);
-      SetRootShown(false);
-      SetEditable(false);
+  SetModel(cookies_model);
+  SetRootShown(false);
+  SetEditable(false);
 }
 
 void CookiesTreeView::RemoveSelectedItems() {
   TreeModelNode* selected_node = GetSelectedNode();
   if (selected_node) {
     static_cast<CookiesTreeModel*>(model())->DeleteCookieNode(
-        static_cast<CookieTreeCookieNode*>(GetSelectedNode()));
+        static_cast<CookieTreeNode*>(GetSelectedNode()));
   }
 }
 
-
 ///////////////////////////////////////////////////////////////////////////////
 // CookieInfoView, public:
 
@@ -253,6 +251,123 @@ void CookieInfoView::Init() {
 }
 
 ///////////////////////////////////////////////////////////////////////////////
+// LocalStorageInfoView, public:
+
+LocalStorageInfoView::LocalStorageInfoView()
+    : origin_label_(NULL),
+      origin_value_field_(NULL),
+      size_label_(NULL),
+      size_value_field_(NULL),
+      last_modified_label_(NULL),
+      last_modified_value_field_(NULL) {
+}
+
+LocalStorageInfoView::~LocalStorageInfoView() {
+}
+
+void LocalStorageInfoView::SetLocalStorageInfo(
+    const BrowsingDataLocalStorageHelper::LocalStorageInfo&
+    local_storage_info) {
+  origin_value_field_->SetText(UTF8ToWide(local_storage_info.origin));
+  size_value_field_->SetText(
+      FormatBytes(local_storage_info.size,
+                  GetByteDisplayUnits(local_storage_info.size),
+                  true));
+  last_modified_value_field_->SetText(
+      base::TimeFormatFriendlyDateAndTime(local_storage_info.last_modified));
+  EnableLocalStorageDisplay(true);
+}
+
+void LocalStorageInfoView::EnableLocalStorageDisplay(bool enabled) {
+  origin_value_field_->SetEnabled(enabled);
+  size_value_field_->SetEnabled(enabled);
+  last_modified_value_field_->SetEnabled(enabled);
+}
+
+void LocalStorageInfoView::ClearLocalStorageDisplay() {
+  std::wstring no_cookie_string =
+      l10n_util::GetString(IDS_COOKIES_COOKIE_NONESELECTED);
+  origin_value_field_->SetText(no_cookie_string);
+  size_value_field_->SetText(no_cookie_string);
+  last_modified_value_field_->SetText(no_cookie_string);
+  EnableLocalStorageDisplay(false);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// LocalStorageInfoView, views::View overrides:
+
+void LocalStorageInfoView::ViewHierarchyChanged(bool is_add,
+                                                views::View* parent,
+                                                views::View* child) {
+  if (is_add && child == this)
+    Init();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// LocalStorageInfoView, private:
+
+void LocalStorageInfoView::Init() {
+  SkColor border_color = color_utils::GetSysSkColor(COLOR_3DSHADOW);
+  views::Border* border = views::Border::CreateSolidBorder(
+      kCookieInfoViewBorderSize, border_color);
+  set_border(border);
+
+  origin_label_ = new views::Label(
+      l10n_util::GetString(IDS_COOKIES_LOCAL_STORAGE_ORIGIN_LABEL));
+  origin_value_field_ = new views::Textfield;
+  size_label_ = new views::Label(
+      l10n_util::GetString(IDS_COOKIES_LOCAL_STORAGE_SIZE_ON_DISK_LABEL));
+  size_value_field_ = new views::Textfield;
+  last_modified_label_ = new views::Label(
+      l10n_util::GetString(IDS_COOKIES_LOCAL_STORAGE_LAST_MODIFIED_LABEL));
+  last_modified_value_field_ = new views::Textfield;
+
+  using views::GridLayout;
+  using views::ColumnSet;
+
+  GridLayout* layout = new GridLayout(this);
+  layout->SetInsets(kCookieInfoViewInsetSize,
+                    kCookieInfoViewInsetSize,
+                    kCookieInfoViewInsetSize,
+                    kCookieInfoViewInsetSize);
+  SetLayoutManager(layout);
+
+  int three_column_layout_id = 0;
+  ColumnSet* column_set = layout->AddColumnSet(three_column_layout_id);
+  column_set->AddColumn(GridLayout::TRAILING, GridLayout::CENTER, 0,
+                        GridLayout::USE_PREF, 0, 0);
+  column_set->AddPaddingColumn(0, kRelatedControlHorizontalSpacing);
+  column_set->AddColumn(GridLayout::FILL, GridLayout::FILL, 1,
+                        GridLayout::USE_PREF, 0, 0);
+
+  layout->StartRow(0, three_column_layout_id);
+  layout->AddView(origin_label_);
+  layout->AddView(origin_value_field_);
+  layout->AddPaddingRow(0, kRelatedControlSmallVerticalSpacing);
+  layout->StartRow(0, three_column_layout_id);
+  layout->AddView(size_label_);
+  layout->AddView(size_value_field_);
+  layout->AddPaddingRow(0, kRelatedControlSmallVerticalSpacing);
+  layout->StartRow(0, three_column_layout_id);
+  layout->AddView(last_modified_label_);
+  layout->AddView(last_modified_value_field_);
+
+  // Color these borderless text areas the same as the containing dialog.
+  SkColor text_area_background = color_utils::GetSysSkColor(COLOR_3DFACE);
+  // Now that the Textfields are in the view hierarchy, we can initialize them.
+  origin_value_field_->SetReadOnly(true);
+  origin_value_field_->RemoveBorder();
+  origin_value_field_->SetBackgroundColor(text_area_background);
+  size_value_field_->SetReadOnly(true);
+  size_value_field_->RemoveBorder();
+  size_value_field_->SetBackgroundColor(text_area_background);
+  last_modified_value_field_->SetReadOnly(true);
+  last_modified_value_field_->RemoveBorder();
+  last_modified_value_field_->SetBackgroundColor(text_area_background);
+}
+
+
+///////////////////////////////////////////////////////////////////////////////
 // CookiesView, public:
 
 // static
@@ -371,10 +486,17 @@ void CookiesView::OnTreeViewSelectionChanged(views::TreeView* tree_view) {
       static_cast<CookieTreeNode*>(tree_view->GetSelectedNode())->
       GetDetailedInfo();
   if (detailed_info.node_type == CookieTreeNode::DetailedInfo::TYPE_COOKIE) {
-    info_view_->SetCookie(detailed_info.cookie->first,
-                          detailed_info.cookie->second);
+    UpdateVisibleDetailedInfo(cookie_info_view_);
+    cookie_info_view_->SetCookie(detailed_info.cookie->first,
+                                 detailed_info.cookie->second);
+  } else if (detailed_info.node_type ==
+             CookieTreeNode::DetailedInfo::TYPE_LOCAL_STORAGE) {
+    UpdateVisibleDetailedInfo(local_storage_info_view_);
+    local_storage_info_view_->SetLocalStorageInfo(
+        *detailed_info.local_storage_info);
   } else {
-    info_view_->ClearCookieDisplay();
+    UpdateVisibleDetailedInfo(cookie_info_view_);
+    cookie_info_view_->ClearCookieDisplay();
   }
 }
 
@@ -393,7 +515,8 @@ CookiesView::CookiesView(Profile* profile)
       clear_search_button_(NULL),
       description_label_(NULL),
       cookies_tree_(NULL),
-      info_view_(NULL),
+      cookie_info_view_(NULL),
+      local_storage_info_view_(NULL),
       remove_button_(NULL),
       remove_all_button_(NULL),
       profile_(profile),
@@ -420,8 +543,10 @@ void CookiesView::Init() {
   description_label_ = new views::Label(
       l10n_util::GetString(IDS_COOKIES_INFO_LABEL));
   description_label_->SetHorizontalAlignment(views::Label::ALIGN_LEFT);
-  cookies_tree_model_.reset(new CookiesTreeModel(profile_));
-  info_view_ = new CookieInfoView;
+  cookies_tree_model_.reset(new CookiesTreeModel(
+      profile_, new BrowsingDataLocalStorageHelper(profile_)));
+  cookie_info_view_ = new CookieInfoView;
+  local_storage_info_view_ = new LocalStorageInfoView;
   cookies_tree_ = new CookiesTreeView(cookies_tree_model_.get());
   remove_button_ = new views::NativeButton(
       this, l10n_util::GetString(IDS_COOKIES_REMOVE_LABEL));
@@ -469,7 +594,10 @@ void CookiesView::Init() {
 
   layout->AddPaddingRow(0, kRelatedControlVerticalSpacing);
   layout->StartRow(0, single_column_layout_id);
-  layout->AddView(info_view_);
+  layout->AddView(cookie_info_view_, 1, 2);
+
+  layout->StartRow(0, single_column_layout_id);
+  layout->AddView(local_storage_info_view_);
 
   // Add the Remove/Remove All buttons to the ClientView
   View* parent = GetParent();
@@ -477,6 +605,8 @@ void CookiesView::Init() {
   parent->AddChildView(remove_all_button_);
   if (!cookies_tree_model_.get()->GetRoot()->GetChildCount())
     UpdateForEmptyState();
+  else
+    UpdateVisibleDetailedInfo(cookie_info_view_);
 }
 
 void CookiesView::ResetSearchQuery() {
@@ -486,7 +616,15 @@ void CookiesView::ResetSearchQuery() {
 }
 
 void CookiesView::UpdateForEmptyState() {
-  info_view_->ClearCookieDisplay();
+  cookie_info_view_->ClearCookieDisplay();
   remove_button_->SetEnabled(false);
   remove_all_button_->SetEnabled(false);
+  UpdateVisibleDetailedInfo(cookie_info_view_);
+}
+
+void CookiesView::UpdateVisibleDetailedInfo(views::View* view) {
+  view->SetVisible(true);
+  views::View* other = local_storage_info_view_;
+  if (view == local_storage_info_view_) other = cookie_info_view_;
+  other->SetVisible(false);
 }
diff --git a/chrome/browser/views/options/cookies_view.h b/chrome/browser/views/options/cookies_view.h
index d7c1b23..91d6cf4a 100644
--- a/chrome/browser/views/options/cookies_view.h
+++ b/chrome/browser/views/options/cookies_view.h
@@ -8,6 +8,7 @@
 #include <string>
 
 #include "base/task.h"
+#include "chrome/browser/browsing_data_local_storage_helper.h"
 #include "net/base/cookie_monster.h"
 #include "views/controls/button/button.h"
 #include "views/controls/tree/tree_view.h"
@@ -24,9 +25,11 @@ class NativeButton;
 }  // namespace views
 
 
+class BrowsingDataLocalStorageHelper;
 class CookieInfoView;
 class CookiesTreeModel;
 class CookiesTreeView;
+class LocalStorageInfoView;
 class Profile;
 class Timer;
 
@@ -96,13 +99,23 @@ class CookiesView : public views::View,
   // Update the UI when there are no cookies.
   void UpdateForEmptyState();
 
+  // Update the UI when a cookie is selected.
+  void UpdateForCookieState();
+
+  // Update the UI when a local storage is selected.
+  void UpdateForLocalStorageState();
+
+  // Updates view to be visible inside detailed_info_view_;
+  void UpdateVisibleDetailedInfo(views::View* view);
+
   // Assorted dialog controls
   views::Label* search_label_;
   views::Textfield* search_field_;
   views::NativeButton* clear_search_button_;
   views::Label* description_label_;
   CookiesTreeView* cookies_tree_;
-  CookieInfoView* info_view_;
+  CookieInfoView* cookie_info_view_;
+  LocalStorageInfoView* local_storage_info_view_;
   views::NativeButton* remove_button_;
   views::NativeButton* remove_all_button_;
 
@@ -172,4 +185,46 @@ class CookieInfoView : public views::View {
   DISALLOW_COPY_AND_ASSIGN(CookieInfoView);
 };
 
+///////////////////////////////////////////////////////////////////////////////
+// LocalStorageInfoView
+//
+//  Responsible for displaying a tabular grid of Local Storage information.
+class LocalStorageInfoView : public views::View {
+ public:
+  LocalStorageInfoView();
+  virtual ~LocalStorageInfoView();
+
+  // Update the display from the specified Local Storage info.
+  void SetLocalStorageInfo(
+      const BrowsingDataLocalStorageHelper::LocalStorageInfo&
+      local_storage_info);
+
+  // Clears the cookie display to indicate that no or multiple local storages
+  // are selected.
+  void ClearLocalStorageDisplay();
+
+  // Enables or disables the local storate property text fields.
+  void EnableLocalStorageDisplay(bool enabled);
+
+ protected:
+  // views::View overrides:
+  virtual void ViewHierarchyChanged(
+      bool is_add, views::View* parent, views::View* child);
+
+ private:
+  // Set up the view layout
+  void Init();
+
+  // Individual property labels
+  views::Label* origin_label_;
+  views::Textfield* origin_value_field_;
+  views::Label* size_label_;
+  views::Textfield* size_value_field_;
+  views::Label* last_modified_label_;
+  views::Textfield* last_modified_value_field_;
+
+  DISALLOW_COPY_AND_ASSIGN(LocalStorageInfoView);
+};
+
+
 #endif  // CHROME_BROWSER_VIEWS_OPTIONS_COOKIES_VIEW_H_
diff --git a/chrome/chrome_browser.gypi b/chrome/chrome_browser.gypi
index f417554..3fc3a1e 100755
--- a/chrome/chrome_browser.gypi
+++ b/chrome/chrome_browser.gypi
@@ -226,6 +226,8 @@
         'browser/browser_url_handler.cc',
         'browser/browser_url_handler.h',
         'browser/browser_window.h',
+        'browser/browsing_data_local_storage_helper.cc',
+        'browser/browsing_data_local_storage_helper.h',
         'browser/browsing_data_remover.cc',
         'browser/browsing_data_remover.h',
         'browser/browsing_instance.cc',
diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi
index ac53510..779f4dc 100755
--- a/chrome/chrome_tests.gypi
+++ b/chrome/chrome_tests.gypi
@@ -64,12 +64,14 @@
         # The only thing used from browser is Browser::Type.
         'browser/browser.h',
         'browser/cocoa/browser_test_helper.h',
+        'browser/mock_browsing_data_local_storage_helper.h',
+		'browser/mock_browsing_data_local_storage_helper.cc',
         # TODO:  these should live here but are currently used by
         # production code code in libbrowser (in chrome.gyp).
         #'browser/net/url_request_mock_http_job.cc',
         #'browser/net/url_request_mock_http_job.h',
         'browser/net/url_request_mock_net_error_job.cc',
-        'browser/net/url_request_mock_net_error_job.h',
+        'browser/net/url_request_mock_net_error_job.h',		
         'browser/renderer_host/mock_render_process_host.cc',
         'browser/renderer_host/mock_render_process_host.h',
         'browser/renderer_host/test/test_backing_store.cc',
@@ -1084,6 +1086,7 @@
         'browser/autocomplete/autocomplete_browsertest.cc',
         'browser/browser_browsertest.cc',
         'browser/browser_init_browsertest.cc',
+        'browser/browsing_data_local_storage_helper_unittest.cc',
         'browser/crash_recovery_browsertest.cc',
         'browser/download/save_page_browsertest.cc',
         'browser/extensions/autoupdate_interceptor.cc',
-- 
cgit v1.1