// Copyright (c) 2011 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_web_ui.h" #include <set> #include <vector> #include "base/string_util.h" #include "base/utf_string_conversions.h" #include "chrome/browser/extensions/extension_bookmark_manager_api.h" #include "chrome/browser/extensions/extension_service.h" #include "chrome/browser/extensions/image_loading_tracker.h" #include "chrome/browser/prefs/pref_service.h" #include "chrome/browser/prefs/scoped_user_pref_update.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/ui/browser.h" #include "chrome/browser/ui/browser_list.h" #include "chrome/browser/ui/tab_contents/tab_contents_wrapper.h" #include "chrome/common/chrome_switches.h" #include "chrome/common/extensions/extension.h" #include "chrome/common/extensions/extension_constants.h" #include "chrome/common/extensions/extension_icon_set.h" #include "chrome/common/extensions/extension_resource.h" #include "chrome/common/url_constants.h" #include "content/browser/renderer_host/render_widget_host_view.h" #include "content/browser/tab_contents/tab_contents.h" #include "content/common/bindings_policy.h" #include "content/common/page_transition_types.h" #include "net/base/file_stream.h" #include "third_party/skia/include/core/SkBitmap.h" #include "ui/gfx/codec/png_codec.h" #include "ui/gfx/favicon_size.h" namespace { // De-dupes the items in |list|. Assumes the values are strings. void CleanUpDuplicates(ListValue* list) { std::set<std::string> seen_values; // Loop backwards as we may be removing items. for (size_t i = list->GetSize() - 1; (i + 1) > 0; --i) { std::string value; if (!list->GetString(i, &value)) { NOTREACHED(); continue; } if (seen_values.find(value) == seen_values.end()) seen_values.insert(value); else list->Remove(i, NULL); } } // Helper class that is used to track the loading of the favicon of an // extension. class ExtensionWebUIImageLoadingTracker : public ImageLoadingTracker::Observer { public: ExtensionWebUIImageLoadingTracker(Profile* profile, FaviconService::GetFaviconRequest* request, const GURL& page_url) : ALLOW_THIS_IN_INITIALIZER_LIST(tracker_(this)), request_(request), extension_(NULL) { // Even when the extensions service is enabled by default, it's still // disabled in incognito mode. ExtensionService* service = profile->GetExtensionService(); if (service) extension_ = service->GetExtensionByURL(page_url); } void Init() { if (extension_) { ExtensionResource icon_resource = extension_->GetIconResource(Extension::EXTENSION_ICON_BITTY, ExtensionIconSet::MATCH_EXACTLY); tracker_.LoadImage(extension_, icon_resource, gfx::Size(kFaviconSize, kFaviconSize), ImageLoadingTracker::DONT_CACHE); } else { ForwardResult(NULL); } } virtual void OnImageLoaded(SkBitmap* image, const ExtensionResource& resource, int index) { if (image) { std::vector<unsigned char> image_data; if (!gfx::PNGCodec::EncodeBGRASkBitmap(*image, false, &image_data)) { NOTREACHED() << "Could not encode extension favicon"; } ForwardResult(RefCountedBytes::TakeVector(&image_data)); } else { ForwardResult(NULL); } } private: ~ExtensionWebUIImageLoadingTracker() {} // Forwards the result on the request. If no favicon was available then // |icon_data| may be backed by NULL. Once the result has been forwarded the // instance is deleted. void ForwardResult(scoped_refptr<RefCountedMemory> icon_data) { history::FaviconData favicon; favicon.known_icon = icon_data.get() != NULL && icon_data->size() > 0; favicon.image_data = icon_data; favicon.icon_type = history::FAVICON; request_->ForwardResultAsync( FaviconService::FaviconDataCallback::TupleType(request_->handle(), favicon)); delete this; } ImageLoadingTracker tracker_; scoped_refptr<FaviconService::GetFaviconRequest> request_; const Extension* extension_; DISALLOW_COPY_AND_ASSIGN(ExtensionWebUIImageLoadingTracker); }; } // namespace const char ExtensionWebUI::kExtensionURLOverrides[] = "extensions.chrome_url_overrides"; ExtensionWebUI::ExtensionWebUI(TabContents* tab_contents, const GURL& url) : WebUI(tab_contents), url_(url) { ExtensionService* service = tab_contents->profile()->GetExtensionService(); const Extension* extension = service->GetExtensionByURL(url); if (!extension) extension = service->GetExtensionByWebExtent(url); DCHECK(extension); // Only hide the url for internal pages (e.g. chrome-extension or packaged // component apps like bookmark manager. should_hide_url_ = !extension->is_hosted_app(); bindings_ = BindingsPolicy::EXTENSION; // Bind externalHost to Extension WebUI loaded in Chrome Frame. const CommandLine& browser_command_line = *CommandLine::ForCurrentProcess(); if (browser_command_line.HasSwitch(switches::kChromeFrame)) bindings_ |= BindingsPolicy::EXTERNAL_HOST; // For chrome:// overrides, some of the defaults are a little different. GURL effective_url = tab_contents->GetURL(); if (effective_url.SchemeIs(chrome::kChromeUIScheme) && effective_url.host() == chrome::kChromeUINewTabHost) { focus_location_bar_by_default_ = true; } } ExtensionWebUI::~ExtensionWebUI() {} void ExtensionWebUI::ResetExtensionFunctionDispatcher( RenderViewHost* render_view_host) { // TODO(jcivelli): http://crbug.com/60608 we should get the URL out of the // active entry of the navigation controller. extension_function_dispatcher_.reset( ExtensionFunctionDispatcher::Create(render_view_host, this, url_)); DCHECK(extension_function_dispatcher_.get()); } void ExtensionWebUI::ResetExtensionBookmarkManagerEventRouter() { // Hack: A few things we specialize just for the bookmark manager. if (extension_function_dispatcher_->extension_id() == extension_misc::kBookmarkManagerId) { extension_bookmark_manager_event_router_.reset( new ExtensionBookmarkManagerEventRouter(GetProfile(), tab_contents())); link_transition_type_ = PageTransition::AUTO_BOOKMARK; } } void ExtensionWebUI::RenderViewCreated(RenderViewHost* render_view_host) { ResetExtensionFunctionDispatcher(render_view_host); ResetExtensionBookmarkManagerEventRouter(); } void ExtensionWebUI::RenderViewReused(RenderViewHost* render_view_host) { ResetExtensionFunctionDispatcher(render_view_host); ResetExtensionBookmarkManagerEventRouter(); } void ExtensionWebUI::ProcessWebUIMessage( const ExtensionHostMsg_DomMessage_Params& params) { extension_function_dispatcher_->HandleRequest(params); } Browser* ExtensionWebUI::GetBrowser() { TabContents* contents = tab_contents(); TabContentsIterator tab_iterator; for (; !tab_iterator.done(); ++tab_iterator) { if (contents == (*tab_iterator)->tab_contents()) return tab_iterator.browser(); } return NULL; } TabContents* ExtensionWebUI::associated_tab_contents() const { return tab_contents(); } ExtensionBookmarkManagerEventRouter* ExtensionWebUI::extension_bookmark_manager_event_router() { return extension_bookmark_manager_event_router_.get(); } gfx::NativeWindow ExtensionWebUI::GetCustomFrameNativeWindow() { if (GetBrowser()) return NULL; // If there was no browser associated with the function dispatcher delegate, // then this WebUI may be hosted in an ExternalTabContainer, and a framing // window will be accessible through the tab_contents. TabContentsDelegate* tab_contents_delegate = tab_contents()->delegate(); if (tab_contents_delegate) return tab_contents_delegate->GetFrameNativeWindow(); else return NULL; } gfx::NativeView ExtensionWebUI::GetNativeViewOfHost() { RenderWidgetHostView* rwhv = tab_contents()->GetRenderWidgetHostView(); return rwhv ? rwhv->GetNativeView() : NULL; } //////////////////////////////////////////////////////////////////////////////// // chrome:// URL overrides // static void ExtensionWebUI::RegisterUserPrefs(PrefService* prefs) { prefs->RegisterDictionaryPref(kExtensionURLOverrides); } // static bool ExtensionWebUI::HandleChromeURLOverride(GURL* url, Profile* profile) { if (!url->SchemeIs(chrome::kChromeUIScheme)) return false; const DictionaryValue* overrides = profile->GetPrefs()->GetDictionary(kExtensionURLOverrides); std::string page = url->host(); ListValue* url_list; if (!overrides || !overrides->GetList(page, &url_list)) return false; ExtensionService* service = profile->GetExtensionService(); size_t i = 0; while (i < url_list->GetSize()) { Value* val = NULL; url_list->Get(i, &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. const 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; } // We can't handle chrome-extension URLs in incognito mode unless the // extension uses split mode. bool incognito_override_allowed = extension->incognito_split_mode() && service->IsIncognitoEnabled(extension->id()); if (profile->IsOffTheRecord() && !incognito_override_allowed) { ++i; continue; } *url = extension_url; return true; } return false; } // static void ExtensionWebUI::RegisterChromeURLOverrides( Profile* profile, const Extension::URLOverrideMap& overrides) { if (overrides.empty()) return; PrefService* prefs = profile->GetPrefs(); DictionaryPrefUpdate update(prefs, kExtensionURLOverrides); DictionaryValue* all_overrides = update.Get(); // For each override provided by the extension, add it to the front of // the override list if it's not already in the list. Extension::URLOverrideMap::const_iterator iter = overrides.begin(); for (; iter != overrides.end(); ++iter) { const std::string& key = iter->first; ListValue* page_overrides; if (!all_overrides->GetList(key, &page_overrides)) { page_overrides = new ListValue(); all_overrides->Set(key, page_overrides); } else { CleanUpDuplicates(page_overrides); // 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 == iter->second.spec()) 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, new StringValue(iter->second.spec())); } } // static void ExtensionWebUI::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)->tab_contents(); 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 ExtensionWebUI::UnregisterChromeURLOverride(const std::string& page, Profile* profile, Value* override) { if (!override) return; PrefService* prefs = profile->GetPrefs(); DictionaryPrefUpdate update(prefs, kExtensionURLOverrides); DictionaryValue* all_overrides = update.Get(); ListValue* page_overrides; if (!all_overrides->GetList(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 ExtensionWebUI::UnregisterChromeURLOverrides( Profile* profile, const Extension::URLOverrideMap& overrides) { if (overrides.empty()) return; PrefService* prefs = profile->GetPrefs(); DictionaryPrefUpdate update(prefs, kExtensionURLOverrides); DictionaryValue* all_overrides = update.Get(); Extension::URLOverrideMap::const_iterator iter = overrides.begin(); for (; iter != overrides.end(); ++iter) { const std::string& page = iter->first; ListValue* page_overrides; if (!all_overrides->GetList(page, &page_overrides)) { // If it's being unregistered, it should already be in the list. NOTREACHED(); continue; } else { StringValue override(iter->second.spec()); UnregisterAndReplaceOverride(iter->first, profile, page_overrides, &override); } } } // static void ExtensionWebUI::GetFaviconForURL(Profile* profile, FaviconService::GetFaviconRequest* request, const GURL& page_url) { // tracker deletes itself when done. ExtensionWebUIImageLoadingTracker* tracker = new ExtensionWebUIImageLoadingTracker(profile, request, page_url); tracker->Init(); }