diff options
author | erikkay@google.com <erikkay@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-04-17 23:09:10 +0000 |
---|---|---|
committer | erikkay@google.com <erikkay@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-04-17 23:09:10 +0000 |
commit | 73404a373b7c8ca68793d58a0750d086dc49fdda (patch) | |
tree | eb9a0569b42fd3235913754df7a2baf79abb9e4f | |
parent | bc5987b6febaa0562c1f21e91197937a53116a30 (diff) | |
download | chromium_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.vcproj | 8 | ||||
-rw-r--r-- | chrome/browser/extensions/extension_bookmarks_module.cc | 325 | ||||
-rw-r--r-- | chrome/browser/extensions/extension_bookmarks_module.h | 35 | ||||
-rw-r--r-- | chrome/browser/extensions/extension_function.cc | 11 | ||||
-rw-r--r-- | chrome/browser/extensions/extension_function.h | 22 | ||||
-rw-r--r-- | chrome/browser/extensions/extension_function_dispatcher.cc | 274 | ||||
-rw-r--r-- | chrome/browser/extensions/extension_function_dispatcher.h | 8 | ||||
-rw-r--r-- | chrome/chrome.gyp | 2 | ||||
-rw-r--r-- | chrome/renderer/resources/extension_process_bindings.js | 113 | ||||
-rw-r--r-- | chrome/test/data/extensions/bookmarks/bookmark_api.html | 117 | ||||
-rw-r--r-- | chrome/test/data/extensions/bookmarks/bookmark_view.html | 104 | ||||
-rw-r--r-- | chrome/test/data/extensions/bookmarks/manifest.json | 6 |
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"]
+}
|