summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorerikkay@chromium.org <erikkay@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-08-28 20:26:05 +0000
committererikkay@chromium.org <erikkay@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-08-28 20:26:05 +0000
commit86c008e8a7da9c00c5a676eb201ba5d0c976748e (patch)
tree8e58aeeab8564a396ccf67807d5bddfcdaa05807
parent5ec8d59c7e79d1a7aae4137051ffc184ec51096c (diff)
downloadchromium_src-86c008e8a7da9c00c5a676eb201ba5d0c976748e.zip
chromium_src-86c008e8a7da9c00c5a676eb201ba5d0c976748e.tar.gz
chromium_src-86c008e8a7da9c00c5a676eb201ba5d0c976748e.tar.bz2
override chrome:// URLs via extensions.
Overrides are declared in an extension's manifest. The last one installed wins. However, we keep a list of those installed per page so that priority is preserved and so that uninstall will revert to a previous state. Review URL: http://codereview.chromium.org/174277 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@24791 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--base/values.cc15
-rw-r--r--base/values.h9
-rw-r--r--chrome/browser/browser_about_handler.cc2
-rw-r--r--chrome/browser/browser_about_handler.h3
-rw-r--r--chrome/browser/browser_prefs.cc2
-rw-r--r--chrome/browser/browser_url_handler.cc11
-rw-r--r--chrome/browser/browser_url_handler.h5
-rw-r--r--chrome/browser/extensions/extension_apitest.cc55
-rw-r--r--chrome/browser/extensions/extension_apitest.h8
-rw-r--r--chrome/browser/extensions/extension_browsertest.cc23
-rw-r--r--chrome/browser/extensions/extension_dom_ui.cc222
-rw-r--r--chrome/browser/extensions/extension_dom_ui.h34
-rw-r--r--chrome/browser/extensions/extension_host.cc22
-rw-r--r--chrome/browser/extensions/extension_override_apitest.cc13
-rw-r--r--chrome/browser/extensions/extensions_service.cc13
-rw-r--r--chrome/browser/tab_contents/navigation_controller.cc2
-rw-r--r--chrome/chrome.gyp2
-rw-r--r--chrome/common/extensions/extension.cc31
-rw-r--r--chrome/common/extensions/extension.h9
-rw-r--r--chrome/common/extensions/extension_constants.cc3
-rw-r--r--chrome/common/extensions/extension_constants.h2
-rw-r--r--chrome/common/notification_type.h4
-rwxr-xr-xchrome/test/data/extensions/api_test/override1/api_test.js107
-rw-r--r--chrome/test/data/extensions/api_test/override1/background.html2
-rw-r--r--chrome/test/data/extensions/api_test/override1/downloads.html6
-rw-r--r--chrome/test/data/extensions/api_test/override1/history.html4
-rw-r--r--chrome/test/data/extensions/api_test/override1/manifest.json12
-rw-r--r--chrome/test/data/extensions/api_test/override1/newtab.html4
-rw-r--r--chrome/test/data/extensions/api_test/override1/nonexistant.html4
-rw-r--r--chrome/test/data/extensions/api_test/override1/test.js11
30 files changed, 582 insertions, 58 deletions
diff --git a/base/values.cc b/base/values.cc
index 3b5bd38..716fdf3 100644
--- a/base/values.cc
+++ b/base/values.cc
@@ -610,13 +610,15 @@ bool ListValue::Remove(size_t index, Value** out_value) {
return true;
}
-void ListValue::Remove(const Value& value) {
+int ListValue::Remove(const Value& value) {
for (ValueVector::iterator i(list_.begin()); i != list_.end(); ++i) {
if ((*i)->Equals(&value)) {
+ size_t index = i - list_.begin();
list_.erase(i);
- break;
+ return index;
}
}
+ return -1;
}
void ListValue::Append(Value* in_value) {
@@ -624,6 +626,15 @@ void ListValue::Append(Value* in_value) {
list_.push_back(in_value);
}
+bool ListValue::Insert(size_t index, Value* in_value) {
+ DCHECK(in_value);
+ if (index < 0 || index > list_.size())
+ return false;
+
+ list_.insert(list_.begin() + index, in_value);
+ return true;
+}
+
Value* ListValue::DeepCopy() const {
ListValue* result = new ListValue;
diff --git a/base/values.h b/base/values.h
index cd68b10..482ffa0 100644
--- a/base/values.h
+++ b/base/values.h
@@ -334,12 +334,17 @@ class ListValue : public Value {
// it will return false and the ListValue object will be unchanged.
bool Remove(size_t index, Value** out_value);
- // Removes the first instance of |value| found in the list, if any.
- void Remove(const Value& value);
+ // Removes the first instance of |value| found in the list, if any, returning
+ // the index that it was located at (-1 for not present).
+ int Remove(const Value& value);
// Appends a Value to the end of the list.
void Append(Value* in_value);
+ // Insert a Value at index.
+ // Returns true if successful, or false if the index was out of range.
+ bool Insert(size_t index, Value* in_value);
+
// Iteration
typedef ValueVector::iterator iterator;
typedef ValueVector::const_iterator const_iterator;
diff --git a/chrome/browser/browser_about_handler.cc b/chrome/browser/browser_about_handler.cc
index 9db4260..6cc4491 100644
--- a/chrome/browser/browser_about_handler.cc
+++ b/chrome/browser/browser_about_handler.cc
@@ -698,7 +698,7 @@ void AboutMemoryHandler::OnDetailsAvailable() {
// -----------------------------------------------------------------------------
-bool WillHandleBrowserAboutURL(GURL* url) {
+bool WillHandleBrowserAboutURL(GURL* url, Profile* profile) {
// We only handle about: schemes.
if (!url->SchemeIs(chrome::kAboutScheme))
return false;
diff --git a/chrome/browser/browser_about_handler.h b/chrome/browser/browser_about_handler.h
index dedf929..87e5e75 100644
--- a/chrome/browser/browser_about_handler.h
+++ b/chrome/browser/browser_about_handler.h
@@ -8,6 +8,7 @@
#define CHROME_BROWSER_BROWSER_ABOUT_HANDLER_H_
class GURL;
+class Profile;
// Decides whether the given URL will be handled by the browser about handler
// and returns true if so. On true, it may also modify the given URL to be the
@@ -15,7 +16,7 @@ class GURL;
// handles all "about:" URLs as "about:blank.
//
// This is used by BrowserURLHandler.
-bool WillHandleBrowserAboutURL(GURL* url);
+bool WillHandleBrowserAboutURL(GURL* url, Profile* profile);
// We have a few magic commands that don't cause navigations, but rather pop up
// dialogs. This function handles those cases, and returns true if so. In this
diff --git a/chrome/browser/browser_prefs.cc b/chrome/browser/browser_prefs.cc
index 74e9b65..cb1a8cc 100644
--- a/chrome/browser/browser_prefs.cc
+++ b/chrome/browser/browser_prefs.cc
@@ -12,6 +12,7 @@
#include "chrome/browser/debugger/devtools_manager.h"
#include "chrome/browser/dom_ui/new_tab_ui.h"
#include "chrome/browser/download/download_manager.h"
+#include "chrome/browser/extensions/extension_dom_ui.h"
#include "chrome/browser/external_protocol_handler.h"
#include "chrome/browser/google_url_tracker.h"
#include "chrome/browser/metrics/metrics_service.h"
@@ -75,6 +76,7 @@ void RegisterAllPrefs(PrefService* user_prefs, PrefService* local_state) {
AutofillManager::RegisterUserPrefs(user_prefs);
TabContents::RegisterUserPrefs(user_prefs);
TemplateURLPrepopulateData::RegisterUserPrefs(user_prefs);
+ ExtensionDOMUI::RegisterUserPrefs(user_prefs);
NewTabUI::RegisterUserPrefs(user_prefs);
BlockedPopupContainer::RegisterUserPrefs(user_prefs);
DevToolsManager::RegisterUserPrefs(user_prefs);
diff --git a/chrome/browser/browser_url_handler.cc b/chrome/browser/browser_url_handler.cc
index e42e34e..506ccfd 100644
--- a/chrome/browser/browser_url_handler.cc
+++ b/chrome/browser/browser_url_handler.cc
@@ -7,11 +7,13 @@
#include "base/string_util.h"
#include "chrome/browser/browser_about_handler.h"
#include "chrome/browser/dom_ui/dom_ui_factory.h"
+#include "chrome/browser/extensions/extension_dom_ui.h"
+#include "chrome/browser/profile.h"
#include "chrome/common/url_constants.h"
#include "googleurl/src/gurl.h"
// Handles rewriting view-source URLs for what we'll actually load.
-static bool HandleViewSource(GURL* url) {
+static bool HandleViewSource(GURL* url, Profile* profile) {
if (url->SchemeIs(chrome::kViewSourceScheme)) {
// Load the inner URL instead.
*url = GURL(url->path());
@@ -21,7 +23,7 @@ static bool HandleViewSource(GURL* url) {
}
// Handles URLs for DOM UI. These URLs need no rewriting.
-static bool HandleDOMUI(GURL* url) {
+static bool HandleDOMUI(GURL* url, Profile* profile) {
if (!DOMUIFactory::UseDOMUIForURL(*url))
return false;
return true;
@@ -35,17 +37,18 @@ void BrowserURLHandler::InitURLHandlers() {
return;
// Add the default URL handlers.
+ url_handlers_.push_back(&ExtensionDOMUI::HandleChromeURLOverride);
url_handlers_.push_back(&WillHandleBrowserAboutURL); // about:
url_handlers_.push_back(&HandleDOMUI); // chrome: & friends.
url_handlers_.push_back(&HandleViewSource); // view-source:
}
// static
-void BrowserURLHandler::RewriteURLIfNecessary(GURL* url) {
+void BrowserURLHandler::RewriteURLIfNecessary(GURL* url, Profile* profile) {
if (url_handlers_.empty())
InitURLHandlers();
for (size_t i = 0; i < url_handlers_.size(); ++i) {
- if ((*url_handlers_[i])(url))
+ if ((*url_handlers_[i])(url, profile))
return;
}
}
diff --git a/chrome/browser/browser_url_handler.h b/chrome/browser/browser_url_handler.h
index 8200c8a..593408e 100644
--- a/chrome/browser/browser_url_handler.h
+++ b/chrome/browser/browser_url_handler.h
@@ -15,6 +15,7 @@
#include <vector>
class GURL;
+class Profile;
// BrowserURLHandler manages the list of all special URLs and manages
// dispatching the URL handling to registered handlers.
@@ -26,11 +27,11 @@ class BrowserURLHandler {
// - optionally set |dispatcher| to the necessary DOMMessageDispatcher
// - return true.
// If the URL is not handled by a handler, it should return false.
- typedef bool (*URLHandler)(GURL* url);
+ typedef bool (*URLHandler)(GURL* url, Profile* profile);
// HandleBrowserURL gives all registered URLHandlers a shot at processing
// the given URL, and modifies it in place.
- static void RewriteURLIfNecessary(GURL* url);
+ static void RewriteURLIfNecessary(GURL* url, Profile* profile);
// We initialize the list of url_handlers_ lazily the first time MaybeHandle
// is called.
diff --git a/chrome/browser/extensions/extension_apitest.cc b/chrome/browser/extensions/extension_apitest.cc
index 3c45524..415d1c9 100644
--- a/chrome/browser/extensions/extension_apitest.cc
+++ b/chrome/browser/extensions/extension_apitest.cc
@@ -14,34 +14,48 @@ static const int kTimeoutMs = 60 * 1000; // 1 minute
// Load an extension and wait for it to notify of PASSED or FAILED.
bool ExtensionApiTest::RunExtensionTest(const char* extension_name) {
- bool result;
- completed_ = false;
{
NotificationRegistrar registrar;
registrar.Add(this, NotificationType::EXTENSION_TEST_PASSED,
NotificationService::AllSources());
registrar.Add(this, NotificationType::EXTENSION_TEST_FAILED,
NotificationService::AllSources());
- result = LoadExtension(test_data_dir_.AppendASCII(extension_name));
- // If the test runs quickly, we may get the notification while waiting
- // for the Load to finish.
- if (completed_) {
- result = passed_;
- } else {
- result = WaitForPassFail();
+ if (!LoadExtension(test_data_dir_.AppendASCII(extension_name))) {
+ message_ = "Failed to load extension.";
+ return false;
}
}
- return result;
+
+ // TODO(erikkay) perhaps we shouldn't do this implicitly.
+ return WaitForPassFail();
}
bool ExtensionApiTest::WaitForPassFail() {
- completed_ = false;
- passed_ = false;
- MessageLoop::current()->PostDelayedTask(
- FROM_HERE, new MessageLoop::QuitTask, kTimeoutMs);
- ui_test_utils::RunMessageLoop();
- return passed_;
+ NotificationRegistrar registrar;
+ registrar.Add(this, NotificationType::EXTENSION_TEST_PASSED,
+ NotificationService::AllSources());
+ registrar.Add(this, NotificationType::EXTENSION_TEST_FAILED,
+ NotificationService::AllSources());
+
+ // Depending on the tests, multiple results can come in from a single call
+ // to RunMessageLoop(), so we maintain a queue of results and just pull them
+ // off as the test calls this, going to the run loop only when the queue is
+ // empty.
+ if (!results_.size()) {
+ MessageLoop::current()->PostDelayedTask(
+ FROM_HERE, new MessageLoop::QuitTask, kTimeoutMs);
+ ui_test_utils::RunMessageLoop();
+ }
+ if (results_.size()) {
+ bool ret = results_.front();
+ results_.pop_front();
+ message_ = messages_.front();
+ messages_.pop_front();
+ return ret;
+ }
+ message_ = "No response from message loop.";
+ return false;
}
void ExtensionApiTest::SetUpCommandLine(CommandLine* command_line) {
@@ -55,16 +69,15 @@ void ExtensionApiTest::Observe(NotificationType type,
switch (type.value) {
case NotificationType::EXTENSION_TEST_PASSED:
std::cout << "Got EXTENSION_TEST_PASSED notification.\n";
- completed_ = true;
- passed_ = true;
+ results_.push_back(true);
+ messages_.push_back("");
MessageLoopForUI::current()->Quit();
break;
case NotificationType::EXTENSION_TEST_FAILED:
std::cout << "Got EXTENSION_TEST_FAILED notification.\n";
- completed_ = true;
- passed_ = false;
- message_ = *(Details<std::string>(details).ptr());
+ results_.push_back(false);
+ messages_.push_back(*(Details<std::string>(details).ptr()));
MessageLoopForUI::current()->Quit();
break;
diff --git a/chrome/browser/extensions/extension_apitest.h b/chrome/browser/extensions/extension_apitest.h
index af2dd28..466735d 100644
--- a/chrome/browser/extensions/extension_apitest.h
+++ b/chrome/browser/extensions/extension_apitest.h
@@ -33,13 +33,11 @@ class ExtensionApiTest : public ExtensionBrowserTest {
void Observe(NotificationType type, const NotificationSource& source,
const NotificationDetails& details);
- // Did the extension side of the unit test complete?
- bool completed_;
-
- // Did the extension side of the unit test pass?
- bool passed_;
+ // A sequential list of pass/fail notifications from the test extension(s).
+ std::deque<bool> results_;
// If it failed, what was the error message?
+ std::deque<std::string> messages_;
std::string message_;
};
diff --git a/chrome/browser/extensions/extension_browsertest.cc b/chrome/browser/extensions/extension_browsertest.cc
index 27e6311..cf7a513 100644
--- a/chrome/browser/extensions/extension_browsertest.cc
+++ b/chrome/browser/extensions/extension_browsertest.cc
@@ -149,22 +149,20 @@ bool ExtensionBrowserTest::WaitForExtensionHostsToLoad() {
// Wait for all the extension hosts that exist to finish loading.
// NOTE: This assumes that the extension host list is not changing while
// this method is running.
+
+ NotificationRegistrar registrar;
+ registrar.Add(this, NotificationType::EXTENSION_HOST_DID_STOP_LOADING,
+ NotificationService::AllSources());
+
ExtensionProcessManager* manager =
browser()->profile()->GetExtensionProcessManager();
base::Time start_time = base::Time::Now();
+ MessageLoop::current()->PostDelayedTask(FROM_HERE,
+ new MessageLoop::QuitTask, kTimeoutMs);
for (ExtensionProcessManager::const_iterator iter = manager->begin();
iter != manager->end(); ++iter) {
- while (!(*iter)->did_stop_loading()) {
- if ((base::Time::Now() - start_time).InMilliseconds() > kTimeoutMs) {
- std::cout << "Extension host did not load for URL: "
- << (*iter)->GetURL().spec();
- return false;
- }
-
- MessageLoop::current()->PostDelayedTask(FROM_HERE,
- new MessageLoop::QuitTask, 200);
+ if (!(*iter)->did_stop_loading())
ui_test_utils::RunMessageLoop();
- }
}
return true;
@@ -184,6 +182,11 @@ void ExtensionBrowserTest::Observe(NotificationType type,
MessageLoopForUI::current()->Quit();
break;
+ case NotificationType::EXTENSION_HOST_DID_STOP_LOADING:
+ std::cout << "Got EXTENSION_HOST_DID_STOP_LOADING notification.\n";
+ MessageLoopForUI::current()->Quit();
+ break;
+
default:
NOTREACHED();
break;
diff --git a/chrome/browser/extensions/extension_dom_ui.cc b/chrome/browser/extensions/extension_dom_ui.cc
index ed5abb9..044a28a 100644
--- a/chrome/browser/extensions/extension_dom_ui.cc
+++ b/chrome/browser/extensions/extension_dom_ui.cc
@@ -5,8 +5,16 @@
#include "chrome/browser/extensions/extension_dom_ui.h"
#include "chrome/browser/browser.h"
+#include "chrome/browser/browser_list.h"
+#include "chrome/browser/profile.h"
#include "chrome/browser/tab_contents/tab_contents.h"
#include "chrome/common/bindings_policy.h"
+#include "chrome/common/pref_service.h"
+#include "chrome/common/url_constants.h"
+
+namespace {
+const wchar_t kExtensionURLOverrides[] = L"extensions.chrome_url_overrides";
+}
ExtensionDOMUI::ExtensionDOMUI(TabContents* tab_contents)
: DOMUI(tab_contents) {
@@ -14,18 +22,35 @@ ExtensionDOMUI::ExtensionDOMUI(TabContents* tab_contents)
hide_favicon_ = true;
should_hide_url_ = true;
bindings_ = BindingsPolicy::EXTENSION;
+
+ // For chrome:// overrides, some of the defaults are a little different.
+ GURL url = tab_contents->GetURL();
+ if (url.SchemeIs(chrome::kChromeUIScheme)) {
+ if (url.host() == chrome::kChromeUINewTabHost) {
+ focus_location_bar_by_default_ = true;
+ } else {
+ // Current behavior of other chrome:// pages is to display the URL.
+ should_hide_url_ = false;
+ }
+ }
}
-void ExtensionDOMUI::RenderViewCreated(RenderViewHost* render_view_host) {
+void ExtensionDOMUI::ResetExtensionFunctionDispatcher(
+ RenderViewHost* render_view_host) {
+ // Use the NavigationController to get the URL rather than the TabContents
+ // since this is the real underlying URL (see HandleChromeURLOverride).
+ NavigationController& controller = tab_contents()->controller();
+ const GURL& url = controller.pending_entry()->url();
extension_function_dispatcher_.reset(
- new ExtensionFunctionDispatcher(render_view_host, this,
- tab_contents()->GetURL()));
+ new ExtensionFunctionDispatcher(render_view_host, this, url));
+}
+
+void ExtensionDOMUI::RenderViewCreated(RenderViewHost* render_view_host) {
+ ResetExtensionFunctionDispatcher(render_view_host);
}
void ExtensionDOMUI::RenderViewReused(RenderViewHost* render_view_host) {
- extension_function_dispatcher_.reset(
- new ExtensionFunctionDispatcher(render_view_host, this,
- tab_contents()->GetURL()));
+ ResetExtensionFunctionDispatcher(render_view_host);
}
void ExtensionDOMUI::ProcessDOMUIMessage(const std::string& message,
@@ -39,3 +64,188 @@ void ExtensionDOMUI::ProcessDOMUIMessage(const std::string& message,
Browser* ExtensionDOMUI::GetBrowser() {
return static_cast<Browser*>(tab_contents()->delegate());
}
+
+////////////////////////////////////////////////////////////////////////////////
+// chrome:// URL overrides
+
+// static
+void ExtensionDOMUI::RegisterUserPrefs(PrefService* prefs) {
+ prefs->RegisterDictionaryPref(kExtensionURLOverrides);
+}
+
+// static
+bool ExtensionDOMUI::HandleChromeURLOverride(GURL* url, Profile* profile) {
+ if (!url->SchemeIs(chrome::kChromeUIScheme))
+ return false;
+
+ // Even when the extensions service is enabled by default, it's still
+ // disabled in incognito mode.
+ ExtensionsService* service = profile->GetExtensionsService();
+ if (!service)
+ return false;
+
+ const DictionaryValue* overrides =
+ profile->GetPrefs()->GetDictionary(kExtensionURLOverrides);
+ std::string page = url->host();
+ ListValue* url_list;
+ if (!overrides || !overrides->GetList(UTF8ToWide(page), &url_list))
+ return false;
+
+ if (!service->is_ready()) {
+ // TODO(erikkay) So far, it looks like extensions load before the new tab
+ // page. I don't know if we have anything that enforces this, so add this
+ // check for safety.
+ NOTREACHED() << "Chrome URL override requested before extensions loaded";
+ return false;
+ }
+
+ while (url_list->GetSize()) {
+ Value* val;
+ url_list->Get(0, &val);
+
+ // Verify that the override value is good. If not, unregister it and find
+ // the next one.
+ std::string override;
+ if (!val->GetAsString(&override)) {
+ NOTREACHED();
+ UnregisterChromeURLOverride(page, profile, val);
+ continue;
+ }
+ GURL extension_url(override);
+ if (!extension_url.is_valid()) {
+ NOTREACHED();
+ UnregisterChromeURLOverride(page, profile, val);
+ continue;
+ }
+
+ // Verify that the extension that's being referred to actually exists.
+ Extension* extension = service->GetExtensionByURL(extension_url);
+ if (!extension) {
+ // This can currently happen if you use --load-extension one run, and
+ // then don't use it the next. It could also happen if an extension
+ // were deleted directly from the filesystem, etc.
+ LOG(WARNING) << "chrome URL override present for non-existant extension";
+ UnregisterChromeURLOverride(page, profile, val);
+ continue;
+ }
+
+ *url = extension_url;
+ return true;
+ }
+ return false;
+}
+
+// static
+void ExtensionDOMUI::RegisterChromeURLOverrides(
+ Profile* profile, const DictionaryValue* new_overrides) {
+ if (!new_overrides)
+ return;
+
+ PrefService* prefs = profile->GetPrefs();
+ DictionaryValue* all_overrides =
+ prefs->GetMutableDictionary(kExtensionURLOverrides);
+
+ // For each override provided by the extension, add it to the front of
+ // the override list if it's not already in the list.
+ DictionaryValue::key_iterator iter = new_overrides->begin_keys();
+ for (;iter != new_overrides->end_keys(); ++iter) {
+ Value* val;
+ new_overrides->Get(*iter, &val);
+ std::string string_val;
+ if (!val->GetAsString(&string_val)) {
+ NOTREACHED();
+ continue;
+ }
+ ListValue* page_overrides;
+ if (!all_overrides->GetList(*iter, &page_overrides)) {
+ page_overrides = new ListValue();
+ all_overrides->Set(*iter, page_overrides);
+ } else {
+ // Verify that the override isn't already in the list.
+ ListValue::iterator i = page_overrides->begin();
+ for (; i != page_overrides->end(); ++i) {
+ std::string override_val;
+ if (!(*i)->GetAsString(&override_val)) {
+ NOTREACHED();
+ continue;
+ }
+ if (override_val == string_val)
+ break;
+ }
+ // This value is already in the list, leave it alone.
+ if (i != page_overrides->end())
+ continue;
+ }
+ // Insert the override at the front of the list. Last registered override
+ // wins.
+ page_overrides->Insert(0, val->DeepCopy());
+ }
+}
+
+// static
+void ExtensionDOMUI::UnregisterAndReplaceOverride(const std::string& page,
+ Profile* profile, ListValue* list, Value* override) {
+ int index = list->Remove(*override);
+ if (index == 0) {
+ // This is the active override, so we need to find all existing
+ // tabs for this override and get them to reload the original URL.
+ for (TabContentsIterator iterator; !iterator.done(); iterator++) {
+ TabContents* tab = *iterator;
+ if (tab->profile() != profile)
+ continue;
+
+ GURL url = tab->GetURL();
+ if (!url.SchemeIs(chrome::kChromeUIScheme) || url.host() != page)
+ continue;
+
+ // Don't use Reload() since |url| isn't the same as the internal URL
+ // that NavigationController has.
+ tab->controller().LoadURL(url, url, PageTransition::RELOAD);
+ }
+ }
+}
+
+// static
+void ExtensionDOMUI::UnregisterChromeURLOverride(const std::string& page,
+ Profile* profile, Value* override) {
+ if (!override)
+ return;
+ PrefService* prefs = profile->GetPrefs();
+ DictionaryValue* all_overrides =
+ prefs->GetMutableDictionary(kExtensionURLOverrides);
+ ListValue* page_overrides;
+ if (!all_overrides->GetList(UTF8ToWide(page), &page_overrides)) {
+ // If it's being unregistered, it should already be in the list.
+ NOTREACHED();
+ return;
+ } else {
+ UnregisterAndReplaceOverride(page, profile, page_overrides, override);
+ }
+}
+
+// static
+void ExtensionDOMUI::UnregisterChromeURLOverrides(
+ Profile* profile, const DictionaryValue* new_overrides) {
+ if (!new_overrides)
+ return;
+ PrefService* prefs = profile->GetPrefs();
+ DictionaryValue* all_overrides =
+ prefs->GetMutableDictionary(kExtensionURLOverrides);
+ DictionaryValue::key_iterator iter = new_overrides->begin_keys();
+ for (; iter != new_overrides->end_keys(); ++iter) {
+ Value* val;
+ if (!new_overrides->Get(*iter, &val)) {
+ NOTREACHED();
+ return;
+ }
+ ListValue* page_overrides;
+ if (!all_overrides->GetList(*iter, &page_overrides)) {
+ // If it's being unregistered, it should already be in the list.
+ NOTREACHED();
+ continue;
+ } else {
+ UnregisterAndReplaceOverride(WideToUTF8(*iter), profile, page_overrides,
+ val);
+ }
+ }
+}
diff --git a/chrome/browser/extensions/extension_dom_ui.h b/chrome/browser/extensions/extension_dom_ui.h
index ff64a9d..b8ef8dc 100644
--- a/chrome/browser/extensions/extension_dom_ui.h
+++ b/chrome/browser/extensions/extension_dom_ui.h
@@ -9,6 +9,10 @@
#include "chrome/browser/dom_ui/dom_ui.h"
#include "chrome/browser/extensions/extension_function_dispatcher.h"
+class ListValue;
+class PrefService;
+class TabContents;
+
// This class implements DOMUI for extensions and allows extensions to put UI in
// the main tab contents area.
class ExtensionDOMUI
@@ -16,6 +20,7 @@ class ExtensionDOMUI
public ExtensionFunctionDispatcher::Delegate {
public:
explicit ExtensionDOMUI(TabContents* tab_contents);
+
ExtensionFunctionDispatcher* extension_function_dispatcher() const {
return extension_function_dispatcher_.get();
}
@@ -31,7 +36,36 @@ class ExtensionDOMUI
// ExtensionFunctionDispatcher::Delegate
virtual Browser* GetBrowser();
+ // BrowserURLHandler
+ static bool HandleChromeURLOverride(GURL* url, Profile* profile);
+
+ // Register and unregister a dictionary of one or more overrides.
+ // Page names are the keys, and chrome-extension: URLs are the values.
+ // (e.g. { "newtab": "chrome-extension://<id>/my_new_tab.html" }
+ static void RegisterChromeURLOverrides(Profile* profile,
+ const DictionaryValue* overrides);
+ static void UnregisterChromeURLOverrides(Profile* profile,
+ const DictionaryValue* overrides);
+ static void UnregisterChromeURLOverride(const std::string& page,
+ Profile* profile,
+ Value* override);
+
+ // Called from BrowserPrefs
+ static void RegisterUserPrefs(PrefService* prefs);
+
private:
+ // Unregister the specified override, and if it's the currently active one,
+ // ensure that something takes its place.
+ static void UnregisterAndReplaceOverride(const std::string& page,
+ Profile* profile,
+ ListValue* list,
+ Value* override);
+
+ // When the RenderViewHost changes (RenderViewCreated and RenderViewReused),
+ // we need to reset the ExtensionFunctionDispatcher so it's talking to the
+ // right one, as well as being linked to the correct URL.
+ void ResetExtensionFunctionDispatcher(RenderViewHost* render_view_host);
+
scoped_ptr<ExtensionFunctionDispatcher> extension_function_dispatcher_;
};
diff --git a/chrome/browser/extensions/extension_host.cc b/chrome/browser/extensions/extension_host.cc
index bc03f8f..60aa0d3 100644
--- a/chrome/browser/extensions/extension_host.cc
+++ b/chrome/browser/extensions/extension_host.cc
@@ -174,7 +174,13 @@ void ExtensionHost::DidStopLoading(RenderViewHost* render_view_host) {
}
#endif
- did_stop_loading_ = true;
+ if (!did_stop_loading_) {
+ NotificationService::current()->Notify(
+ NotificationType::EXTENSION_HOST_DID_STOP_LOADING,
+ Source<Profile>(profile_),
+ Details<ExtensionHost>(this));
+ did_stop_loading_ = true;
+ }
}
void ExtensionHost::DocumentAvailableInMainFrame(RenderViewHost* rvh) {
@@ -303,8 +309,18 @@ Browser* ExtensionHost::GetBrowser() {
if (view_.get())
return view_->browser();
#endif
- Browser* browser = BrowserList::GetLastActiveWithProfile(
- render_view_host()->process()->profile());
+ Profile* profile = render_view_host()->process()->profile();
+ Browser* browser = BrowserList::GetLastActiveWithProfile(profile);
+
+ // It's possible for a browser to exist, but to have never been active.
+ // This can happen if you launch the browser on a machine without an active
+ // desktop (a headless buildbot) or if you quickly give another app focus
+ // at launch time. This is easy to do with browser_tests.
+ if (!browser)
+ browser = BrowserList::FindBrowserWithProfile(profile);
+
+ // TODO(erikkay): can this still return NULL? Is Rafael's comment still
+ // valid here?
// NOTE(rafaelw): This can return NULL in some circumstances. In particular,
// a toolstrip or background_page onload chrome.tabs api call can make it
// into here before the browser is sufficiently initialized to return here.
diff --git a/chrome/browser/extensions/extension_override_apitest.cc b/chrome/browser/extensions/extension_override_apitest.cc
new file mode 100644
index 0000000..7b10e33
--- /dev/null
+++ b/chrome/browser/extensions/extension_override_apitest.cc
@@ -0,0 +1,13 @@
+// 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_apitest.h"
+
+IN_PROC_BROWSER_TEST_F(ExtensionApiTest, Overrides) {
+ ASSERT_TRUE(RunExtensionTest("override1")) << message_; // new tab
+ EXPECT_EQ(results_.size(), 0U);
+
+ // TODO(erikkay) load a second override and verify behavior, then unload
+ // the first and verify behavior, etc.
+}
diff --git a/chrome/browser/extensions/extensions_service.cc b/chrome/browser/extensions/extensions_service.cc
index acb74cd..b53f243 100644
--- a/chrome/browser/extensions/extensions_service.cc
+++ b/chrome/browser/extensions/extensions_service.cc
@@ -11,6 +11,7 @@
#include "chrome/browser/chrome_thread.h"
#include "chrome/browser/extensions/crx_installer.h"
#include "chrome/browser/extensions/extension_browser_event_router.h"
+#include "chrome/browser/extensions/extension_dom_ui.h"
#include "chrome/browser/extensions/extension_file_util.h"
#include "chrome/browser/extensions/extension_updater.h"
#include "chrome/browser/extensions/external_extension_provider.h"
@@ -195,6 +196,9 @@ void ExtensionsService::UninstallExtension(const std::string& extension_id,
install_directory_));
}
+ ExtensionDOMUI::UnregisterChromeURLOverrides(profile_,
+ extension->GetChromeURLOverrides());
+
UnloadExtension(extension_id);
}
@@ -213,6 +217,9 @@ void ExtensionsService::EnableExtension(const std::string& extension_id) {
extension);
disabled_extensions_.erase(iter);
+ ExtensionDOMUI::RegisterChromeURLOverrides(profile_,
+ extension->GetChromeURLOverrides());
+
NotificationService::current()->Notify(
NotificationType::EXTENSION_LOADED,
Source<ExtensionsService>(this),
@@ -314,6 +321,9 @@ void ExtensionsService::UnloadExtension(const std::string& extension_id) {
// Callers should not send us nonexistant extensions.
CHECK(extension.get());
+ ExtensionDOMUI::UnregisterChromeURLOverrides(profile_,
+ extension->GetChromeURLOverrides());
+
ExtensionList::iterator iter = std::find(disabled_extensions_.begin(),
disabled_extensions_.end(),
extension.get());
@@ -426,6 +436,9 @@ void ExtensionsService::OnExtensionLoaded(Extension* extension,
NotificationType::THEME_INSTALLED,
Source<ExtensionsService>(this),
Details<Extension>(extension));
+ } else {
+ ExtensionDOMUI::RegisterChromeURLOverrides(profile_,
+ extension->GetChromeURLOverrides());
}
break;
case Extension::DISABLED:
diff --git a/chrome/browser/tab_contents/navigation_controller.cc b/chrome/browser/tab_contents/navigation_controller.cc
index 244f251..3704169 100644
--- a/chrome/browser/tab_contents/navigation_controller.cc
+++ b/chrome/browser/tab_contents/navigation_controller.cc
@@ -358,7 +358,7 @@ NavigationEntry* NavigationController::CreateNavigationEntry(
// will actually be loaded. This real URL won't be shown to the user, just
// used internally.
GURL loaded_url(url);
- BrowserURLHandler::RewriteURLIfNecessary(&loaded_url);
+ BrowserURLHandler::RewriteURLIfNecessary(&loaded_url, profile_);
NavigationEntry* entry = new NavigationEntry(NULL, -1, loaded_url, referrer,
string16(), transition);
diff --git a/chrome/chrome.gyp b/chrome/chrome.gyp
index c1d3a25..8c768ad 100644
--- a/chrome/chrome.gyp
+++ b/chrome/chrome.gyp
@@ -47,6 +47,7 @@
'browser/extensions/extension_browsertest.cc',
'browser/extensions/extension_browsertest.h',
'browser/extensions/extension_browsertests_misc.cc',
+ 'browser/extensions/extension_override_apitest.cc',
'browser/extensions/extension_startup_unittest.cc',
],
'browser_tests_sources_win_specific': [
@@ -71,6 +72,7 @@
'browser/extensions/extension_browsertest.cc',
'browser/extensions/extension_browsertest.h',
'browser/extensions/extension_browsertests_misc.cc',
+ 'browser/extensions/extension_override_apitest.cc',
'browser/extensions/extension_startup_unittest.cc',
],
# TODO(jcampan): move these vars to views.gyp.
diff --git a/chrome/common/extensions/extension.cc b/chrome/common/extensions/extension.cc
index c826c07..3f3c8539 100644
--- a/chrome/common/extensions/extension.cc
+++ b/chrome/common/extensions/extension.cc
@@ -998,6 +998,37 @@ bool Extension::InitFromValue(const DictionaryValue& source, bool require_id,
set_default_locale(default_locale);
}
+ // Chrome URL overrides (optional)
+ if (source.HasKey(keys::kChromeURLOverrides)) {
+ DictionaryValue* overrides;
+ if (!source.GetDictionary(keys::kChromeURLOverrides, &overrides)) {
+ *error = errors::kInvalidChromeURLOverrides;
+ return false;
+ }
+ // Validate that the overrides are all strings
+ DictionaryValue::key_iterator iter = overrides->begin_keys();
+ while (iter != overrides->end_keys()) {
+ // For now, only allow the new tab page. Others will work when we remove
+ // this check, but let's keep it simple for now.
+ // TODO(erikkay) enable other pages as well
+ if (WideToUTF8(*iter) != chrome::kChromeUINewTabHost) {
+ *error = errors::kInvalidChromeURLOverrides;
+ return false;
+ }
+ std::string val;
+ if (!overrides->GetString(*iter, &val)) {
+ *error = errors::kInvalidChromeURLOverrides;
+ return false;
+ }
+ // Replace the entry with a fully qualified chrome-extension:// URL.
+ GURL url = GetResourceURL(val);
+ overrides->SetString(*iter, url.spec());
+ ++iter;
+ }
+ chrome_url_overrides_.reset(
+ static_cast<DictionaryValue*>(overrides->DeepCopy()));
+ }
+
return true;
}
diff --git a/chrome/common/extensions/extension.h b/chrome/common/extensions/extension.h
index 6e7215f..e8cdd10 100644
--- a/chrome/common/extensions/extension.h
+++ b/chrome/common/extensions/extension.h
@@ -262,6 +262,11 @@ class Extension {
default_locale_ = default_locale;
}
+ // Chrome URL overrides (see ExtensionOverrideUI).
+ DictionaryValue* GetChromeURLOverrides() const {
+ return chrome_url_overrides_.get();
+ }
+
// Runtime data:
// Put dynamic data about the state of a running extension below.
@@ -382,6 +387,10 @@ class Extension {
// Default locale, used for fallback.
std::string default_locale_;
+ // A map of chrome:// hostnames (newtab, downloads, etc.) to Extension URLs
+ // which override the handling of those URLs.
+ scoped_ptr<DictionaryValue> chrome_url_overrides_;
+
// Runtime data:
// True if the background page is ready.
diff --git a/chrome/common/extensions/extension_constants.cc b/chrome/common/extensions/extension_constants.cc
index d887678..d72fbcf 100644
--- a/chrome/common/extensions/extension_constants.cc
+++ b/chrome/common/extensions/extension_constants.cc
@@ -7,6 +7,7 @@
namespace extension_manifest_keys {
const wchar_t* kBackground = L"background_page";
+const wchar_t* kChromeURLOverrides = L"chrome_url_overrides";
const wchar_t* kContentScripts = L"content_scripts";
const wchar_t* kCss = L"css";
const wchar_t* kDefaultLocale = L"default_locale";
@@ -52,6 +53,8 @@ const char* kPageActionTypePermanent = "permanent";
// printf because we want to unit test them and scanf is hard to make
// cross-platform.
namespace extension_manifest_errors {
+const char* kInvalidChromeURLOverrides =
+ "Invalid value for 'chrome_url_overrides'.";
const char* kInvalidContentScript =
"Invalid value for 'content_scripts[*]'.";
const char* kInvalidContentScriptsList =
diff --git a/chrome/common/extensions/extension_constants.h b/chrome/common/extensions/extension_constants.h
index b808665..09429a2 100644
--- a/chrome/common/extensions/extension_constants.h
+++ b/chrome/common/extensions/extension_constants.h
@@ -8,6 +8,7 @@
// Keys used in JSON representation of extensions.
namespace extension_manifest_keys {
extern const wchar_t* kBackground;
+ extern const wchar_t* kChromeURLOverrides;
extern const wchar_t* kContentScripts;
extern const wchar_t* kCss;
extern const wchar_t* kDefaultLocale;
@@ -51,6 +52,7 @@ namespace extension_manifest_values {
// Error messages returned from Extension::InitFromValue().
namespace extension_manifest_errors {
+ extern const char* kInvalidChromeURLOverrides;
extern const char* kInvalidContentScript;
extern const char* kInvalidContentScriptsList;
extern const char* kInvalidCss;
diff --git a/chrome/common/notification_type.h b/chrome/common/notification_type.h
index ba74687..7c7140f 100644
--- a/chrome/common/notification_type.h
+++ b/chrome/common/notification_type.h
@@ -631,6 +631,10 @@ class NotificationType {
// an ExtensionHost* and the source is a Profile*.
EXTENSION_HOST_DESTROYED,
+ // Send by an ExtensionHost when it finished its initial page load.
+ // The details are an ExtensionHost* and the source is a Profile*.
+ EXTENSION_HOST_DID_STOP_LOADING,
+
// Sent after an extension render process is created and fully functional.
// The details are an ExtensionHost*.
EXTENSION_PROCESS_CREATED,
diff --git a/chrome/test/data/extensions/api_test/override1/api_test.js b/chrome/test/data/extensions/api_test/override1/api_test.js
new file mode 100755
index 0000000..84d0bb1
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/override1/api_test.js
@@ -0,0 +1,107 @@
+// api_test.js
+// mini-framework for ExtensionApiTest browser tests
+// TODO(erikkay) - figure out a way to share this code across extensions
+
+var completed = false;
+var tests;
+var currentTest;
+
+function complete() {
+ completed = true;
+
+ // a bit of a hack just to try to get the script to stop running at this point
+ throw "completed";
+}
+
+function fail(message) {
+ if (completed) throw "completed";
+
+ var stack;
+ try {
+ crash.me += 0; // intentional exception to get the stack trace
+ } catch (e) {
+ stack = e.stack.split("\n");
+ stack = stack.slice(2); // remove title and fail()
+ stack = stack.join("\n");
+ }
+
+ if (!message) {
+ message = "FAIL (no message)";
+ }
+ message += "\n" + stack;
+ console.log("[FAIL] " + currentTest.name + ": " + message);
+ chrome.test.fail(message);
+ complete();
+}
+
+function allTestsSucceeded() {
+ console.log("All tests succeeded");
+ if (completed) throw "completed";
+
+ chrome.test.pass();
+ complete();
+}
+
+function runNextTest() {
+ currentTest = tests.shift();
+ if (!currentTest) {
+ allTestsSucceeded();
+ return;
+ }
+ currentTest.call();
+}
+
+function succeed() {
+ console.log("[SUCCESS] " + currentTest.name);
+ runNextTest();
+}
+
+window.onerror = function(message, url, code) {
+ if (completed) return;
+
+ fail(message);
+};
+
+function assertTrue(test, message) {
+ if (test !== true) {
+ if (typeof(test) == "string") {
+ if (message) {
+ message = test + "\n" + message;
+ } else {
+ message = test;
+ }
+ }
+ fail(message);
+ }
+}
+
+function assertNoLastError() {
+ if (chrome.extension.lastError != undefined) {
+ fail("lastError.message == " + chrome.extension.lastError.message);
+ }
+}
+
+// Wrapper for generating test functions, that takes care of calling
+// assertNoLastError() and succeed() for you.
+function testFunction(func) {
+ return function() {
+ assertNoLastError();
+ try {
+ func.apply(null, arguments);
+ } catch (e) {
+ var stack = null;
+ if (typeof(e.stack) != "undefined") {
+ stack = e.stack.toString()
+ }
+ var msg = "Exception during execution of testFunction in " +
+ currentTest.name;
+ if (stack) {
+ msg += "\n" + stack;
+ } else {
+ msg += "\n(no stack available)";
+ }
+ fail(msg);
+ }
+ succeed();
+ };
+}
diff --git a/chrome/test/data/extensions/api_test/override1/background.html b/chrome/test/data/extensions/api_test/override1/background.html
new file mode 100644
index 0000000..0f02a821
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/override1/background.html
@@ -0,0 +1,2 @@
+<script src="api_test.js"></script>
+<script src="test.js"></script>
diff --git a/chrome/test/data/extensions/api_test/override1/downloads.html b/chrome/test/data/extensions/api_test/override1/downloads.html
new file mode 100644
index 0000000..565343c
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/override1/downloads.html
@@ -0,0 +1,6 @@
+<title>download1</title>
+<script>
+console.log("download1");
+chrome.test.pass();
+</script>
+Downloads Override
diff --git a/chrome/test/data/extensions/api_test/override1/history.html b/chrome/test/data/extensions/api_test/override1/history.html
new file mode 100644
index 0000000..ba1e65b
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/override1/history.html
@@ -0,0 +1,4 @@
+<script>
+chrome.test.pass();
+</script>
+History Override
diff --git a/chrome/test/data/extensions/api_test/override1/manifest.json b/chrome/test/data/extensions/api_test/override1/manifest.json
new file mode 100644
index 0000000..1fd8311
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/override1/manifest.json
@@ -0,0 +1,12 @@
+{
+ "name": "Override Test 1",
+ "version": "0.1",
+ "description": "Test chrome:// overrides",
+ "background_page": "background.html",
+ "permissions": [
+ "tabs"
+ ],
+ "chrome_url_overrides": {
+ "newtab": "newtab.html"
+ }
+}
diff --git a/chrome/test/data/extensions/api_test/override1/newtab.html b/chrome/test/data/extensions/api_test/override1/newtab.html
new file mode 100644
index 0000000..a1028a2
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/override1/newtab.html
@@ -0,0 +1,4 @@
+<script>
+chrome.test.pass();
+</script>
+New Tab Override
diff --git a/chrome/test/data/extensions/api_test/override1/nonexistant.html b/chrome/test/data/extensions/api_test/override1/nonexistant.html
new file mode 100644
index 0000000..66cfbd0
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/override1/nonexistant.html
@@ -0,0 +1,4 @@
+<script>
+chrome.test.pass();
+</script>
+Nonexistant Override
diff --git a/chrome/test/data/extensions/api_test/override1/test.js b/chrome/test/data/extensions/api_test/override1/test.js
new file mode 100644
index 0000000..1684f2f
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/override1/test.js
@@ -0,0 +1,11 @@
+var tests = [
+ function newtab() {
+ chrome.tabs.create({"url": "chrome://newtab/"},
+ testFunction(function(response) {
+ console.log("AFTER");
+ }));
+ }
+];
+
+runNextTest();
+