diff options
Diffstat (limited to 'chrome/browser/extensions')
-rw-r--r-- | chrome/browser/extensions/extension_apitest.cc | 55 | ||||
-rw-r--r-- | chrome/browser/extensions/extension_apitest.h | 8 | ||||
-rw-r--r-- | chrome/browser/extensions/extension_browsertest.cc | 23 | ||||
-rw-r--r-- | chrome/browser/extensions/extension_dom_ui.cc | 222 | ||||
-rw-r--r-- | chrome/browser/extensions/extension_dom_ui.h | 34 | ||||
-rw-r--r-- | chrome/browser/extensions/extension_host.cc | 22 | ||||
-rw-r--r-- | chrome/browser/extensions/extension_override_apitest.cc | 13 | ||||
-rw-r--r-- | chrome/browser/extensions/extensions_service.cc | 13 |
8 files changed, 345 insertions, 45 deletions
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: |