summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorerikkay@google.com <erikkay@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2009-04-17 23:09:10 +0000
committererikkay@google.com <erikkay@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2009-04-17 23:09:10 +0000
commit73404a373b7c8ca68793d58a0750d086dc49fdda (patch)
treeeb9a0569b42fd3235913754df7a2baf79abb9e4f
parentbc5987b6febaa0562c1f21e91197937a53116a30 (diff)
downloadchromium_src-73404a373b7c8ca68793d58a0750d086dc49fdda.zip
chromium_src-73404a373b7c8ca68793d58a0750d086dc49fdda.tar.gz
chromium_src-73404a373b7c8ca68793d58a0750d086dc49fdda.tar.bz2
A subset of the bookmarks API- missing events- missing unit tests- missing ability to change URL
Review URL: http://codereview.chromium.org/77003 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@13977 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--chrome/browser/browser.vcproj8
-rw-r--r--chrome/browser/extensions/extension_bookmarks_module.cc325
-rw-r--r--chrome/browser/extensions/extension_bookmarks_module.h35
-rw-r--r--chrome/browser/extensions/extension_function.cc11
-rw-r--r--chrome/browser/extensions/extension_function.h22
-rw-r--r--chrome/browser/extensions/extension_function_dispatcher.cc274
-rw-r--r--chrome/browser/extensions/extension_function_dispatcher.h8
-rw-r--r--chrome/chrome.gyp2
-rw-r--r--chrome/renderer/resources/extension_process_bindings.js113
-rw-r--r--chrome/test/data/extensions/bookmarks/bookmark_api.html117
-rw-r--r--chrome/test/data/extensions/bookmarks/bookmark_view.html104
-rw-r--r--chrome/test/data/extensions/bookmarks/manifest.json6
12 files changed, 903 insertions, 122 deletions
diff --git a/chrome/browser/browser.vcproj b/chrome/browser/browser.vcproj
index 1e84abe..cacc5a3 100644
--- a/chrome/browser/browser.vcproj
+++ b/chrome/browser/browser.vcproj
@@ -1902,6 +1902,14 @@
>
</File>
<File
+ RelativePath=".\extensions\extension_bookmarks_module.cc"
+ >
+ </File>
+ <File
+ RelativePath=".\extensions\extension_bookmarks_module.h"
+ >
+ </File>
+ <File
RelativePath=".\extensions\extension_browser_event_router.cc"
>
</File>
diff --git a/chrome/browser/extensions/extension_bookmarks_module.cc b/chrome/browser/extensions/extension_bookmarks_module.cc
new file mode 100644
index 0000000..aa720ca
--- /dev/null
+++ b/chrome/browser/extensions/extension_bookmarks_module.cc
@@ -0,0 +1,325 @@
+// 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/extensions/extension_bookmarks_module.h"
+
+#include "chrome/browser/bookmarks/bookmark_codec.h"
+#include "chrome/browser/bookmarks/bookmark_model.h"
+#include "chrome/browser/bookmarks/bookmark_utils.h"
+#include "chrome/browser/profile.h"
+
+namespace {
+// keys
+const wchar_t* kIdKey = L"id";
+const wchar_t* kIndexKey = L"index";
+const wchar_t* kParentIdKey = L"parentId";
+const wchar_t* kUrlKey = L"url";
+const wchar_t* kTitleKey = L"title";
+const wchar_t* kChildrenIdsKey = L"childrenIds";
+const wchar_t* kChildrenKey = L"childrenIds";
+const wchar_t* kRecursiveKey = L"recursive";
+
+// errors
+const char* kNoNodeError = "Can't find bookmark for id.";
+const char* kNoParentError = "Can't find parent bookmark for id.";
+const char* kFolderNotEmptyError =
+ "Can't remove non-empty folder (use recursive to force).";
+const char* kInvalidIndexError = "Index out of bounds.";
+const char* kInvalidUrlError = "Invalid URL.";
+const char* kModifySpecialError = "Can't modify the root bookmark folders.";
+};
+
+// Helper functions.
+class ExtensionBookmarks {
+ public:
+ // Convert |node| into a JSON value
+ static DictionaryValue* GetNodeDictionary(BookmarkNode* node, bool recurse) {
+ DictionaryValue* dict = new DictionaryValue();
+ dict->SetInteger(kIdKey, node->id());
+
+ BookmarkNode* parent = node->GetParent();
+ if (parent)
+ dict->SetInteger(kParentIdKey, parent->id());
+
+ if (!node->is_folder())
+ dict->SetString(kUrlKey, node->GetURL().spec());
+
+ dict->SetString(kTitleKey, node->GetTitle());
+
+ int childCount = node->GetChildCount();
+ ListValue* children = new ListValue();
+ for (int i = 0; i < childCount; ++i) {
+ BookmarkNode* child = node->GetChild(i);
+ if (recurse) {
+ DictionaryValue* dict = GetNodeDictionary(child, true);
+ children->Append(dict);
+ } else {
+ Value* child_id = new FundamentalValue(child->id());
+ children->Append(child_id);
+ }
+ }
+ if (recurse)
+ dict->Set(kChildrenKey, children);
+ else
+ dict->Set(kChildrenIdsKey, children);
+ return dict;
+ }
+
+ // Add a JSON representation of |node| to the JSON |list|.
+ static void AddNode(BookmarkNode* node, ListValue* list, bool recurse) {
+ DictionaryValue* dict = GetNodeDictionary(node, recurse);
+ list->Append(dict);
+ }
+
+ private:
+ ExtensionBookmarks();
+};
+
+// TODO(erikkay): add a recursive version
+bool GetBookmarksFunction::RunImpl() {
+ // TODO(erikkay): the JSON schema doesn't support the TYPE_INTEGER
+ // variant yet.
+ EXTENSION_FUNCTION_VALIDATE(args_->IsType(Value::TYPE_LIST) ||
+ args_->IsType(Value::TYPE_INTEGER) ||
+ args_->IsType(Value::TYPE_NULL));
+ BookmarkModel* model = profile()->GetBookmarkModel();
+ scoped_ptr<ListValue> json(new ListValue());
+ if (args_->IsType(Value::TYPE_INTEGER)) {
+ int id;
+ EXTENSION_FUNCTION_VALIDATE(args_->GetAsInteger(&id));
+ BookmarkNode* node = model->GetNodeByID(id);
+ if (!node) {
+ error_ = kNoNodeError;
+ return false;
+ }
+ ExtensionBookmarks::AddNode(node, json.get(), false);
+ } else {
+ ListValue* ids = NULL;
+ size_t count = 0;
+ if (args_->IsType(Value::TYPE_LIST)) {
+ ids = static_cast<ListValue*>(args_);
+ count = ids->GetSize();
+ }
+ if (count == 0) {
+ // If no ids are passed in, then we default to returning the root node.
+ BookmarkNode* node = model->root_node();
+ ExtensionBookmarks::AddNode(node, json.get(), false);
+ } else {
+ for (size_t i = 0; i < count; ++i) {
+ int id = 0;
+ EXTENSION_FUNCTION_VALIDATE(ids->GetInteger(i, &id));
+ BookmarkNode* node = model->GetNodeByID(id);
+ if (!node) {
+ error_ = kNoNodeError;
+ return false;
+ } else {
+ ExtensionBookmarks::AddNode(node, json.get(), false);
+ }
+ }
+ if (error_.size() && json->GetSize() == 0) {
+ return false;
+ }
+ }
+ }
+
+ result_.reset(json.release());
+ return true;
+}
+
+bool SearchBookmarksFunction::RunImpl() {
+ EXTENSION_FUNCTION_VALIDATE(args_->IsType(Value::TYPE_STRING));
+
+ std::wstring query;
+ EXTENSION_FUNCTION_VALIDATE(args_->GetAsString(&query));
+
+ BookmarkModel* model = profile()->GetBookmarkModel();
+ ListValue* json = new ListValue();
+ std::vector<BookmarkNode*> nodes;
+ bookmark_utils::GetBookmarksContainingText(model, query, 50, &nodes);
+ std::vector<BookmarkNode*>::iterator i = nodes.begin();
+ for (; i != nodes.end(); ++i) {
+ BookmarkNode* node = *i;
+ ExtensionBookmarks::AddNode(node, json, false);
+ }
+
+ result_.reset(json);
+ return true;
+}
+
+bool RemoveBookmarkFunction::RunImpl() {
+ EXTENSION_FUNCTION_VALIDATE(args_->IsType(Value::TYPE_DICTIONARY));
+ DictionaryValue* json = static_cast<DictionaryValue*>(args_);
+
+ // TODO(erikkay): it would be cool to take a list here as well.
+ int id;
+ EXTENSION_FUNCTION_VALIDATE(json->GetInteger(kIdKey, &id));
+
+ bool recursive = false;
+ json->GetBoolean(kRecursiveKey, &recursive); // optional
+
+ BookmarkModel* model = profile()->GetBookmarkModel();
+ BookmarkNode* node = model->GetNodeByID(id);
+ if (!node) {
+ error_ = kNoNodeError;
+ return false;
+ }
+ if (node == model->root_node() ||
+ node == model->other_node() ||
+ node == model->GetBookmarkBarNode()) {
+ error_ = kModifySpecialError;
+ return false;
+ }
+ if (node->is_folder() && node->GetChildCount() > 0 && !recursive) {
+ error_ = kFolderNotEmptyError;
+ return false;
+ }
+
+ BookmarkNode* parent = node->GetParent();
+ if (!parent) {
+ error_ = kNoParentError;
+ return false;
+ }
+ int index = parent->IndexOfChild(node);
+ model->Remove(parent, index);
+ return true;
+}
+
+bool CreateBookmarkFunction::RunImpl() {
+ EXTENSION_FUNCTION_VALIDATE(args_->IsType(Value::TYPE_DICTIONARY));
+ DictionaryValue* json = static_cast<DictionaryValue*>(args_);
+
+ BookmarkModel* model = profile()->GetBookmarkModel();
+ int parentId;
+ if (!json->HasKey(kParentIdKey)) { // optional, default to "other bookmarks"
+ parentId = model->other_node()->id();
+ } else {
+ EXTENSION_FUNCTION_VALIDATE(json->GetInteger(kParentIdKey, &parentId));
+ }
+ BookmarkNode* parent = model->GetNodeByID(parentId);
+ if (!parent) {
+ error_ = kNoParentError;
+ return false;
+ }
+ if (parent->GetParent() == NULL) { // can't create children of the root
+ error_ = kNoParentError;
+ return false;
+ }
+
+ int index;
+ if (!json->HasKey(kIndexKey)) { // optional (defaults to end)
+ index = parent->GetChildCount();
+ } else {
+ EXTENSION_FUNCTION_VALIDATE(json->GetInteger(kIndexKey, &index));
+ if (index > parent->GetChildCount() || index < 0) {
+ error_ = kInvalidIndexError;
+ return false;
+ }
+ }
+
+ std::wstring title;
+ json->GetString(kTitleKey, &title); // optional
+ std::string url_string;
+ json->GetString(kUrlKey, &url_string); // optional
+ GURL url(url_string);
+ if (!url.is_empty() && !url.is_valid()) {
+ error_ = kInvalidUrlError;
+ return false;
+ }
+
+ BookmarkNode* node;
+ if (url_string.length())
+ node = model->AddURL(parent, index, title, url);
+ else
+ node = model->AddGroup(parent, index, title);
+ DCHECK(node);
+ if (!node) {
+ error_ = kNoNodeError;
+ return false;
+ }
+
+ DictionaryValue* ret = ExtensionBookmarks::GetNodeDictionary(node, false);
+ result_.reset(ret);
+
+ return true;
+}
+
+bool MoveBookmarkFunction::RunImpl() {
+ EXTENSION_FUNCTION_VALIDATE(args_->IsType(Value::TYPE_DICTIONARY));
+ DictionaryValue* json = static_cast<DictionaryValue*>(args_);
+
+ // TODO(erikkay) it would be cool if this could be a list of ids as well
+ int id = 0;
+ EXTENSION_FUNCTION_VALIDATE(json->GetInteger(kIdKey, &id));
+
+ BookmarkModel* model = profile()->GetBookmarkModel();
+ BookmarkNode* node = model->GetNodeByID(id);
+ if (!node) {
+ error_ = kNoNodeError;
+ return false;
+ }
+ if (node == model->root_node() ||
+ node == model->other_node() ||
+ node == model->GetBookmarkBarNode()) {
+ error_ = kModifySpecialError;
+ return false;
+ }
+
+ BookmarkNode* parent;
+ if (!json->HasKey(kParentIdKey)) { // optional, defaults to current parent
+ parent = node->GetParent();
+ } else {
+ int parentId;
+ EXTENSION_FUNCTION_VALIDATE(json->GetInteger(kParentIdKey, &parentId));
+ parent = model->GetNodeByID(parentId);
+ }
+ if (!parent) {
+ error_ = kNoParentError;
+ // TODO(erikkay) return an error message
+ return false;
+ }
+ if (parent == model->root_node()) {
+ error_ = kModifySpecialError;
+ return false;
+ }
+
+ int index;
+ if (json->HasKey(kIndexKey)) { // optional (defaults to end)
+ EXTENSION_FUNCTION_VALIDATE(json->GetInteger(kIndexKey, &index));
+ if (index > parent->GetChildCount() || index < 0) {
+ error_ = kInvalidIndexError;
+ return false;
+ }
+ } else {
+ index = parent->GetChildCount();
+ }
+
+ model->Move(node, parent, index);
+ return true;
+}
+
+bool SetBookmarkTitleFunction::RunImpl() {
+ EXTENSION_FUNCTION_VALIDATE(args_->IsType(Value::TYPE_DICTIONARY));
+ DictionaryValue* json = static_cast<DictionaryValue*>(args_);
+
+ std::wstring title;
+ json->GetString(kTitleKey, &title); // optional (empty is clear)
+
+ BookmarkModel* model = profile()->GetBookmarkModel();
+ int id = 0;
+ EXTENSION_FUNCTION_VALIDATE(json->GetInteger(kIdKey, &id));
+ BookmarkNode* node = model->GetNodeByID(id);
+ if (!node) {
+ error_ = kNoNodeError;
+ return false;
+ }
+ if (node == model->root_node() ||
+ node == model->other_node() ||
+ node == model->GetBookmarkBarNode()) {
+ error_ = kModifySpecialError;
+ return false;
+ }
+ model->SetTitle(node, title);
+ return true;
+}
+
diff --git a/chrome/browser/extensions/extension_bookmarks_module.h b/chrome/browser/extensions/extension_bookmarks_module.h
new file mode 100644
index 0000000..b7b38d8
--- /dev/null
+++ b/chrome/browser/extensions/extension_bookmarks_module.h
@@ -0,0 +1,35 @@
+// 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_EXTENSIONS_EXTENSION_BOOKMARKS_MODULE_H__
+#define CHROME_BROWSER_EXTENSIONS_EXTENSION_BOOKMARKS_MODULE_H__
+
+#include "chrome/browser/extensions/extension_function.h"
+
+class GetBookmarksFunction : public SyncExtensionFunction {
+ virtual bool RunImpl();
+};
+
+class SearchBookmarksFunction : public SyncExtensionFunction {
+ virtual bool RunImpl();
+};
+
+class RemoveBookmarkFunction : public SyncExtensionFunction {
+ virtual bool RunImpl();
+};
+
+class CreateBookmarkFunction : public SyncExtensionFunction {
+ virtual bool RunImpl();
+};
+
+class MoveBookmarkFunction : public SyncExtensionFunction {
+ virtual bool RunImpl();
+};
+
+class SetBookmarkTitleFunction : public SyncExtensionFunction {
+ virtual bool RunImpl();
+};
+
+#endif // CHROME_BROWSER_EXTENSIONS_EXTENSION_BOOKMARKS_MODULE_H__
+
diff --git a/chrome/browser/extensions/extension_function.cc b/chrome/browser/extensions/extension_function.cc
index 000c1d5..8de21df 100644
--- a/chrome/browser/extensions/extension_function.cc
+++ b/chrome/browser/extensions/extension_function.cc
@@ -4,15 +4,24 @@
#include "chrome/browser/extensions/extension_function.h"
+#include "base/logging.h"
#include "chrome/browser/extensions/extension_function_dispatcher.h"
void ExtensionFunction::SendResponse(bool success) {
- if (success) {
+ if (bad_message_) {
+ dispatcher_->HandleBadMessage(this);
+ } else if (success) {
if (has_callback()) {
dispatcher_->SendResponse(this);
}
} else {
// TODO(aa): In case of failure, send the error message to an error
// callback.
+ LOG(WARNING) << error_;
}
}
+
+Profile* ExtensionFunction::profile() {
+ return dispatcher_->profile();
+}
+
diff --git a/chrome/browser/extensions/extension_function.h b/chrome/browser/extensions/extension_function.h
index 421df81..ed8fe9e 100644
--- a/chrome/browser/extensions/extension_function.h
+++ b/chrome/browser/extensions/extension_function.h
@@ -11,12 +11,21 @@
#include "base/scoped_ptr.h"
class ExtensionFunctionDispatcher;
+class Profile;
+
+#define EXTENSION_FUNCTION_VALIDATE(test) do { \
+ if (!test) { \
+ bad_message_ = true; \
+ return false; \
+ } \
+ } while (0)
// Base class for an extension function.
// TODO(aa): This will have to become reference counted when we introduce APIs
// that live beyond a single stack frame.
class ExtensionFunction {
public:
+ ExtensionFunction() : bad_message_(false) {}
virtual ~ExtensionFunction() {}
void set_dispatcher(ExtensionFunctionDispatcher* dispatcher) {
@@ -43,6 +52,8 @@ class ExtensionFunction {
protected:
void SendResponse(bool success);
+ Profile* profile();
+
// The arguments to the API. Only non-null if argument were specfied.
Value* args_;
@@ -54,9 +65,15 @@ class ExtensionFunction {
// class before Run() returns.
std::string error_;
+ // Any class that gets a malformed message should set this to true before
+ // returning. The calling renderer process will be killed.
+ bool bad_message_;
+
private:
ExtensionFunctionDispatcher* dispatcher_;
int callback_id_;
+
+ DISALLOW_COPY_AND_ASSIGN(ExtensionFunction);
};
@@ -69,6 +86,8 @@ class ExtensionFunction {
// need to interact with things on the browser UI thread.
class SyncExtensionFunction : public ExtensionFunction {
public:
+ SyncExtensionFunction() {}
+
// Derived classes should implement this method to do their work and return
// success/failure.
virtual bool RunImpl() = 0;
@@ -76,6 +95,9 @@ class SyncExtensionFunction : public ExtensionFunction {
virtual void Run() {
SendResponse(RunImpl());
}
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(SyncExtensionFunction);
};
#endif // CHROME_BROWSER_EXTENSIONS_EXTENSION_FUNCTION_H_
diff --git a/chrome/browser/extensions/extension_function_dispatcher.cc b/chrome/browser/extensions/extension_function_dispatcher.cc
index e2b05f0..195b99e 100644
--- a/chrome/browser/extensions/extension_function_dispatcher.cc
+++ b/chrome/browser/extensions/extension_function_dispatcher.cc
@@ -1,120 +1,154 @@
-// 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/extensions/extension_function_dispatcher.h"
-
-#include "base/json_reader.h"
-#include "base/json_writer.h"
-#include "base/singleton.h"
-#include "base/values.h"
-#include "chrome/browser/extensions/extension_function.h"
-#include "chrome/browser/extensions/extension_tabs_module.h"
-#include "chrome/browser/renderer_host/render_view_host.h"
-
-// FactoryRegistry -------------------------------------------------------------
-
-namespace {
-
-// A pointer to a function that create an instance of an ExtensionFunction.
-typedef ExtensionFunction* (*ExtensionFunctionFactory)();
-
-// Contains a list of all known extension functions and allows clients to create
-// instances of them.
-class FactoryRegistry {
- public:
- static FactoryRegistry* instance();
- FactoryRegistry();
- void GetAllNames(std::vector<std::string>* names);
- ExtensionFunction* NewFunction(const std::string& name);
-
- private:
- typedef std::map<std::string, ExtensionFunctionFactory> FactoryMap;
- FactoryMap factories_;
-};
-
-// Template for defining ExtensionFunctionFactory.
-template<class T>
-ExtensionFunction* NewExtensionFunction() {
- return new T();
-}
-
-FactoryRegistry* FactoryRegistry::instance() {
- return Singleton<FactoryRegistry>::get();
-}
-
-FactoryRegistry::FactoryRegistry() {
- // Register all functions here.
- factories_["GetWindows"] = &NewExtensionFunction<GetWindowsFunction>;
- factories_["GetTabsForWindow"] =
- &NewExtensionFunction<GetTabsForWindowFunction>;
- factories_["GetTab"] = &NewExtensionFunction<GetTabFunction>;
- factories_["CreateTab"] = &NewExtensionFunction<CreateTabFunction>;
- factories_["UpdateTab"] = &NewExtensionFunction<UpdateTabFunction>;
- factories_["MoveTab"] = &NewExtensionFunction<MoveTabFunction>;
- factories_["RemoveTab"] = &NewExtensionFunction<RemoveTabFunction>;
-}
-
-void FactoryRegistry::GetAllNames(
- std::vector<std::string>* names) {
- for (FactoryMap::iterator iter = factories_.begin(); iter != factories_.end();
- ++iter) {
- names->push_back(iter->first);
- }
-}
-
-ExtensionFunction* FactoryRegistry::NewFunction(const std::string& name) {
- FactoryMap::iterator iter = factories_.find(name);
- DCHECK(iter != factories_.end());
- return iter->second();
-}
-
-};
-
-
-// ExtensionFunctionDispatcher -------------------------------------------------
-
-void ExtensionFunctionDispatcher::GetAllFunctionNames(
- std::vector<std::string>* names) {
- FactoryRegistry::instance()->GetAllNames(names);
-}
-
-ExtensionFunctionDispatcher::ExtensionFunctionDispatcher(
- RenderViewHost* render_view_host)
- : render_view_host_(render_view_host) {}
-
-void ExtensionFunctionDispatcher::HandleRequest(const std::string& name,
- const std::string& args,
- int callback_id) {
- scoped_ptr<Value> value;
- if (!args.empty()) {
- JSONReader reader;
- value.reset(reader.JsonToValue(args, false, false));
-
- // Since we do the serialization in the v8 extension, we should always get
- // valid JSON.
- if (!value.get()) {
- DCHECK(false);
- return;
- }
- }
-
- // TODO(aa): This will get a bit more complicated when we support functions
- // that live longer than the stack frame.
- scoped_ptr<ExtensionFunction> function(
- FactoryRegistry::instance()->NewFunction(name));
- function->set_dispatcher(this);
- function->set_args(value.get());
- function->set_callback_id(callback_id);
- function->Run();
-}
-
-void ExtensionFunctionDispatcher::SendResponse(ExtensionFunction* function) {
- std::string json;
-
- // Some functions might not need to return any results.
- if (function->result())
- JSONWriter::Write(function->result(), false, &json);
-
- render_view_host_->SendExtensionResponse(function->callback_id(), json);
-}
+// 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/extensions/extension_function_dispatcher.h"
+
+#include "base/json_reader.h"
+#include "base/json_writer.h"
+#include "base/process_util.h"
+#include "base/singleton.h"
+#include "base/values.h"
+#include "chrome/browser/extensions/extension_bookmarks_module.h"
+#include "chrome/browser/extensions/extension_function.h"
+#include "chrome/browser/extensions/extension_tabs_module.h"
+#include "chrome/browser/renderer_host/render_process_host.h"
+#include "chrome/browser/renderer_host/render_view_host.h"
+#include "chrome/common/result_codes.h"
+
+// FactoryRegistry -------------------------------------------------------------
+
+namespace {
+
+// A pointer to a function that create an instance of an ExtensionFunction.
+typedef ExtensionFunction* (*ExtensionFunctionFactory)();
+
+// Contains a list of all known extension functions and allows clients to create
+// instances of them.
+class FactoryRegistry {
+ public:
+ static FactoryRegistry* instance();
+ FactoryRegistry();
+ void GetAllNames(std::vector<std::string>* names);
+ ExtensionFunction* NewFunction(const std::string& name);
+
+ private:
+ typedef std::map<std::string, ExtensionFunctionFactory> FactoryMap;
+ FactoryMap factories_;
+};
+
+// Template for defining ExtensionFunctionFactory.
+template<class T>
+ExtensionFunction* NewExtensionFunction() {
+ return new T();
+}
+
+FactoryRegistry* FactoryRegistry::instance() {
+ return Singleton<FactoryRegistry>::get();
+}
+
+FactoryRegistry::FactoryRegistry() {
+ // Register all functions here.
+
+ // Tabs
+ factories_["GetWindows"] = &NewExtensionFunction<GetWindowsFunction>;
+ factories_["GetTabsForWindow"] =
+ &NewExtensionFunction<GetTabsForWindowFunction>;
+ factories_["GetTab"] = &NewExtensionFunction<GetTabFunction>;
+ factories_["CreateTab"] = &NewExtensionFunction<CreateTabFunction>;
+ factories_["UpdateTab"] = &NewExtensionFunction<UpdateTabFunction>;
+ factories_["MoveTab"] = &NewExtensionFunction<MoveTabFunction>;
+ factories_["RemoveTab"] = &NewExtensionFunction<RemoveTabFunction>;
+
+ // Bookmarks
+ factories_["GetBookmarks"] = &NewExtensionFunction<GetBookmarksFunction>;
+ factories_["SearchBookmarks"] =
+ &NewExtensionFunction<SearchBookmarksFunction>;
+ factories_["RemoveBookmark"] = &NewExtensionFunction<RemoveBookmarkFunction>;
+ factories_["CreateBookmark"] = &NewExtensionFunction<CreateBookmarkFunction>;
+ factories_["MoveBookmark"] = &NewExtensionFunction<MoveBookmarkFunction>;
+ factories_["SetBookmarkTitle"] =
+ &NewExtensionFunction<SetBookmarkTitleFunction>;
+}
+
+void FactoryRegistry::GetAllNames(
+ std::vector<std::string>* names) {
+ for (FactoryMap::iterator iter = factories_.begin(); iter != factories_.end();
+ ++iter) {
+ names->push_back(iter->first);
+ }
+}
+
+ExtensionFunction* FactoryRegistry::NewFunction(const std::string& name) {
+ FactoryMap::iterator iter = factories_.find(name);
+ DCHECK(iter != factories_.end());
+ return iter->second();
+}
+
+};
+
+
+// ExtensionFunctionDispatcher -------------------------------------------------
+
+void ExtensionFunctionDispatcher::GetAllFunctionNames(
+ std::vector<std::string>* names) {
+ FactoryRegistry::instance()->GetAllNames(names);
+}
+
+ExtensionFunctionDispatcher::ExtensionFunctionDispatcher(
+ RenderViewHost* render_view_host)
+ : render_view_host_(render_view_host) {}
+
+void ExtensionFunctionDispatcher::HandleRequest(const std::string& name,
+ const std::string& args,
+ int callback_id) {
+ scoped_ptr<Value> value;
+ if (!args.empty()) {
+ JSONReader reader;
+ value.reset(reader.JsonToValue(args, false, false));
+
+ // Since we do the serialization in the v8 extension, we should always get
+ // valid JSON.
+ if (!value.get()) {
+ DCHECK(false);
+ return;
+ }
+ }
+
+ // TODO(aa): This will get a bit more complicated when we support functions
+ // that live longer than the stack frame.
+ scoped_ptr<ExtensionFunction> function(
+ FactoryRegistry::instance()->NewFunction(name));
+ function->set_dispatcher(this);
+ function->set_args(value.get());
+ function->set_callback_id(callback_id);
+ function->Run();
+}
+
+void ExtensionFunctionDispatcher::SendResponse(ExtensionFunction* function) {
+ std::string json;
+
+ // Some functions might not need to return any results.
+ if (function->result())
+ JSONWriter::Write(function->result(), false, &json);
+
+ render_view_host_->SendExtensionResponse(function->callback_id(), json);
+}
+
+void ExtensionFunctionDispatcher::HandleBadMessage(ExtensionFunction* api) {
+ LOG(ERROR) << "bad extension message " << // TODO(erikkay) name?
+ " : terminating renderer.";
+ if (RenderProcessHost::run_renderer_in_process()) {
+ // In single process mode it is better if we don't suicide but just crash.
+ CHECK(false);
+ } else {
+ NOTREACHED();
+ base::KillProcess(render_view_host_->process()->process().handle(),
+ ResultCodes::KILLED_BAD_MESSAGE, false);
+ }
+}
+
+Profile* ExtensionFunctionDispatcher::profile() {
+ return render_view_host_->process()->profile();
+}
+
diff --git a/chrome/browser/extensions/extension_function_dispatcher.h b/chrome/browser/extensions/extension_function_dispatcher.h
index 2b9c49e..13057d8 100644
--- a/chrome/browser/extensions/extension_function_dispatcher.h
+++ b/chrome/browser/extensions/extension_function_dispatcher.h
@@ -11,6 +11,7 @@
#include "base/values.h"
class ExtensionFunction;
+class Profile;
class RenderViewHost;
// ExtensionFunctionDispatcher receives requests to execute functions from
@@ -30,6 +31,13 @@ class ExtensionFunctionDispatcher {
// Send a response to a function.
void SendResponse(ExtensionFunction* api);
+ // Handle a malformed message. Possibly the result of an attack, so kill
+ // the renderer.
+ void HandleBadMessage(ExtensionFunction* api);
+
+ // The profile that this dispatcher is associated with.
+ Profile* profile();
+
private:
RenderViewHost* render_view_host_;
};
diff --git a/chrome/chrome.gyp b/chrome/chrome.gyp
index 746f99d..a0f142f 100644
--- a/chrome/chrome.gyp
+++ b/chrome/chrome.gyp
@@ -672,6 +672,8 @@
'browser/encoding_menu_controller_delegate.h',
'browser/extensions/extension.cc',
'browser/extensions/extension.h',
+ 'browser/extensions/extension_bookmarks_module.cc',
+ 'browser/extensions/extension_bookmarks_module.h',
'browser/extensions/extension_error_reporter.cc',
'browser/extensions/extension_error_reporter.h',
'browser/extensions/extension_function.cc',
diff --git a/chrome/renderer/resources/extension_process_bindings.js b/chrome/renderer/resources/extension_process_bindings.js
index 7bcba35..cae9212 100644
--- a/chrome/renderer/resources/extension_process_bindings.js
+++ b/chrome/renderer/resources/extension_process_bindings.js
@@ -86,6 +86,7 @@ var chromium;
validate(arguments, arguments.callee.params);
sendRequest(GetWindows, windowQuery, callback);
};
+
chromium.tabs.getWindows.params = [
{
type: "object",
@@ -107,6 +108,7 @@ var chromium;
validate(arguments, arguments.callee.params);
sendRequest(GetTabsForWindow, null, callback);
};
+
chromium.tabs.getTabsForWindow.params = [
chromium.types.optFun
];
@@ -115,6 +117,7 @@ var chromium;
validate(arguments, arguments.callee.params);
sendRequest(GetTab, tabId, callback);
};
+
chromium.tabs.getTab.params = [
chromium.types.pInt,
chromium.types.optFun
@@ -124,6 +127,7 @@ var chromium;
validate(arguments, arguments.callee.params);
sendRequest(CreateTab, tab, callback);
};
+
chromium.tabs.createTab.params = [
{
type: "object",
@@ -141,6 +145,7 @@ var chromium;
validate(arguments, arguments.callee.params);
sendRequest(UpdateTab, tab);
};
+
chromium.tabs.updateTab.params = [
{
type: "object",
@@ -158,6 +163,7 @@ var chromium;
validate(arguments, arguments.callee.params);
sendRequest(MoveTab, tab);
};
+
chromium.tabs.moveTab.params = [
{
type: "object",
@@ -174,16 +180,121 @@ var chromium;
validate(arguments, arguments.callee.params);
sendRequest(RemoveTab, tabId);
};
+
chromium.tabs.removeTab.params = [
chromium.types.pInt
];
-
+
// onTabMoved sends ({tabId, windowId, fromIndex, toIndex}) as named
// arguments.
chromium.tabs.onTabMoved = new chromium.Event("tab-moved");
//----------------------------------------------------------------------------
+ // Bookmarks
+ chromium.bookmarks = {};
+
+ chromium.bookmarks.get = function(ids, callback) {
+ native function GetBookmarks();
+ sendRequest(GetBookmarks, ids, callback);
+ };
+
+ chromium.bookmarks.get.params = [
+ {
+ type: "array",
+ items: {
+ type: chromium.types.pInt
+ },
+ minItems: 1,
+ optional: true,
+ additionalProperties: false
+ },
+ chromium.types.optFun
+ ];
+
+ chromium.bookmarks.search = function(query, callback) {
+ native function SearchBookmarks();
+ sendRequest(SearchBookmarks, query, callback);
+ };
+
+ chromium.bookmarks.search.params = [
+ chromium.types.string,
+ chromium.types.optFun
+ ];
+
+ chromium.bookmarks.remove = function(bookmark, callback) {
+ native function RemoveBookmark();
+ sendRequest(RemoveBookmark, bookmark, callback);
+ };
+
+ chromium.bookmarks.remove.params = [
+ {
+ type: "object",
+ properties: {
+ id: chromium.types.pInt,
+ recursive: chromium.types.bool
+ },
+ additionalProperties: false
+ },
+ chromium.types.optFun
+ ];
+
+ chromium.bookmarks.create = function(bookmark, callback) {
+ native function CreateBookmark();
+ sendRequest(CreateBookmark, bookmark, callback);
+ };
+
+ chromium.bookmarks.create.params = [
+ {
+ type: "object",
+ properties: {
+ parentId: chromium.types.optPInt,
+ index: chromium.types.optPInt,
+ title: chromium.types.optString,
+ url: chromium.types.optString,
+ },
+ additionalProperties: false
+ },
+ chromium.types.optFun
+ ];
+
+ chromium.bookmarks.move = function(obj, callback) {
+ native function MoveBookmark();
+ sendRequest(MoveBookmark, obj, callback);
+ };
+
+ chromium.bookmarks.move.params = [
+ {
+ type: "object",
+ properties: {
+ id: chromium.types.pInt,
+ parentId: chromium.types.optPInt,
+ index: chromium.types.optPInt
+ },
+ additionalProperties: false
+ },
+ chromium.types.optFun
+ ];
+
+ chromium.bookmarks.setTitle = function(bookmark, callback) {
+ native function SetBookmarkTitle();
+ sendRequest(SetBookmarkTitle, bookmark, callback);
+ };
+
+ chromium.bookmarks.setTitle.params = [
+ {
+ type: "object",
+ properties: {
+ id: chromium.types.pInt,
+ title: chromium.types.optString
+ },
+ additionalProperties: false
+ },
+ chromium.types.optFun
+ ];
+
+ //----------------------------------------------------------------------------
+
// Self
chromium.self = {};
chromium.self.onConnect = new chromium.Event("channel-connect");
diff --git a/chrome/test/data/extensions/bookmarks/bookmark_api.html b/chrome/test/data/extensions/bookmarks/bookmark_api.html
new file mode 100644
index 0000000..0e46bbb
--- /dev/null
+++ b/chrome/test/data/extensions/bookmarks/bookmark_api.html
@@ -0,0 +1,117 @@
+<html>
+<style>
+body {
+ overflow: hidden;
+ margin: 0 0 0 0;
+}
+
+/* TODO: put the background style into body when
+ https://bugs.webkit.org/show_bug.cgi?id=18445 is fixed. */
+.content {
+ background: -webkit-gradient(linear, left top, left bottom, from(rgb(222, 234, 248)), to(rgb(237, 244, 252)));
+ padding: 1;
+ white-space: nowrap;
+}
+
+.button {
+ display: inline;
+ border: 1px solid silver;
+ padding: 2px;
+ margin: 1px 3px;
+ height: 100%;
+}
+</style>
+<script>
+
+var dump = function(obj, indent) {
+ if (indent === undefined)
+ indent = "";
+ if (typeof obj == "object") {
+ var ret = "{<br/>";
+ var child_indent = indent + " ";
+ for (var item in obj) {
+ var child = null;
+ try {
+ child = obj[item];
+ } catch (e) {
+ child = '<error>';
+ }
+ ret += child_indent + item + ": " + dump(child, indent + " ");
+ }
+ return ret + indent + "}" + "<br/>";
+ } else {
+ return obj.toString() + "<br/>";
+ }
+}
+
+var testMoveBookmarks = function(event) {
+ if (event.shiftKey) {
+ // TODO - it would be nice to have a mechanism to do this built-in to a
+ // context menu.
+ window.location.reload();
+ return;
+ }
+ console.log("testMoveBookmarks");
+ chromium.bookmarks.get([], function(root) {
+ chromium.bookmarks.get(root[0].childrenIds, function(root_children) {
+ var bookmark_bar = root_children[0]; // bookmarks bar is always first
+ chromium.bookmarks.get(bookmark_bar.childrenIds,
+ function(bar_children) {
+ var folder_search = [];
+ bar_children.forEach(function(child) {
+ if (child.title == "folder" && child.url == undefined) {
+ folder_search.push(child);
+ }
+ });
+ if (folder_search.length == 1) {
+ console.log('moving children out of "folder"');
+ var folder = folder_search[0];
+ folder.childrenIds.forEach(function(folder_child_id) {
+ chromium.bookmarks.move({'id': folder_child_id,
+ 'parentId': bookmark_bar.id});
+ });
+ chromium.bookmarks.remove({'id': folder.id});
+ } else if (folder_search.length == 0) {
+ console.log('creating "folder" and moving children into it');
+ chromium.bookmarks.create({'parentId': bookmark_bar.id,
+ 'title': 'folder'},
+ function(folder) {
+ chromium.bookmarks.search("oog", function(oog_search) {
+ oog_search.forEach(function(oog_match) {
+ chromium.bookmarks.move({'id': oog_match.id,
+ 'parentId': folder.id})
+ });
+ });
+ });
+ } else {
+ console.log("my puny code wasn't written to handle this");
+ }
+ });
+ });
+ });
+};
+
+var dumpBookmarks = function(event) {
+ window.open("bookmark_view.html");
+ /*
+ console.dir(results);
+ var win = window.open();
+ win.document.write("<html><body><pre>");
+ win.document.write(dump(results));
+ win.document.write("</pre></body></html>");
+ win.document.title = "Bookmarks";
+ });
+ */
+};
+</script>
+<body>
+<div class="content">
+<div class="button" onclick="dumpBookmarks(window.event);">
+Dump Bookmarks
+</div>
+<div class="button" onclick="testMoveBookmarks(window.event);">
+Test Move
+</div>
+</div>
+</body>
+</html>
diff --git a/chrome/test/data/extensions/bookmarks/bookmark_view.html b/chrome/test/data/extensions/bookmarks/bookmark_view.html
new file mode 100644
index 0000000..f3f45b5
--- /dev/null
+++ b/chrome/test/data/extensions/bookmarks/bookmark_view.html
@@ -0,0 +1,104 @@
+<!DOCTYPE HTML>
+<title>Bookmark View</title>
+<style>
+
+.bookmark {
+ margin-left: 5px;
+ padding: 2px;
+}
+
+.bookmark_title {
+ display: inline;
+ border: 1px solid white;
+ padding: 0px 3px;
+}
+
+.bookmark_title:hover {
+ background-color: silver;
+ border: 1px solid black;
+}
+</style>
+<script>
+
+var prefix = "bookmark_";
+
+var toggleBookmark = function(event) {
+ event.stopPropagation();
+ var node = event.currentTarget;
+ var id_str = node.id;
+ if (id_str < prefix.length)
+ return;
+ var id = parseInt(id_str.substring(prefix.length));
+ if (id == NaN)
+ return;
+ console.log("toggle: " + id);
+ //console.dir(event);
+ chromium.bookmarks.get([id], function(bookmark) {
+ //console.log("toggle get");
+ console.dir(bookmark[0]);
+ if (bookmark[0].childrenIds && bookmark[0].childrenIds.length) {
+ if (node.childNodes.length != 1) {
+ //console.log("toggle collapse");
+ node.removeChild(node.childNodes[1]);
+ } else {
+ //console.log("before");
+ chromium.bookmarks.get(bookmark[0].childrenIds, function(children) {
+ //console.log("toggle expand");
+ if (children && children.length) {
+ console.dir(children);
+ addBookmarks(children, node);
+ }
+ });
+ }
+ }
+ });
+};
+
+var addBookmark = function(bookmark, parent) {
+ //console.log("addBookmark " + bookmark.id);
+ var child = document.createElement('li');
+ child.className = 'bookmark';
+ child.id = prefix + bookmark.id;
+ child.addEventListener('click', toggleBookmark, false);
+ if (bookmark.url && bookmark.url.length) {
+ var link = document.createElement('a');
+ link.href = bookmark.url;
+ link.innerHTML = bookmark.title;
+ link.className = 'bookmark_title';
+ child.appendChild(link);
+ } else {
+ var title = document.createElement('div');
+ title.innerHTML = bookmark.title;
+ title.className = 'bookmark_title';
+ child.appendChild(title);
+ }
+ parent.appendChild(child);
+};
+
+var addBookmarks = function(bookmarks, parent) {
+ console.log("addBookmarks " + parent.id);
+ var list = document.createElement("ul");
+ parent.appendChild(list);
+ bookmarks.forEach(function(bookmark) { addBookmark(bookmark, list); });
+};
+
+var loadBookmarks = function() {
+ var container = document.getElementById('container');
+ chromium.bookmarks.get([], function(results) {
+ var root = results[0];
+ console.dir(root);
+ var rootElement = document.createElement("div");
+ rootElement.id = prefix + root.id;
+ // root element is empty / invisible, just an id to be looked up
+ container.appendChild(rootElement);
+ chromium.bookmarks.get(root.childrenIds, function(children) {
+ addBookmarks(children, rootElement);
+ });
+ });
+};
+
+</script>
+<body onload="loadBookmarks()">
+<div id="container">
+</div>
+</body>
diff --git a/chrome/test/data/extensions/bookmarks/manifest.json b/chrome/test/data/extensions/bookmarks/manifest.json
new file mode 100644
index 0000000..1580ca7
--- /dev/null
+++ b/chrome/test/data/extensions/bookmarks/manifest.json
@@ -0,0 +1,6 @@
+{
+ "name": "BookmarksAPI",
+ "description": "Bookmarks API test",
+ "version": "0.1",
+ "toolstrips": ["bookmark_api.html"]
+}