| // Copyright (c) 2012 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/command_line.h" |
| #include "base/prefs/pref_service.h" |
| #include "base/prefs/scoped_user_pref_update.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "chrome/browser/extensions/api/bookmark_manager_private/bookmark_manager_private_api.h" |
| #include "chrome/browser/extensions/extension_service.h" |
| #include "chrome/browser/extensions/extension_tab_util.h" |
| #include "chrome/browser/extensions/extension_util.h" |
| #include "chrome/browser/extensions/image_loader.h" |
| #include "chrome/browser/favicon/favicon_util.h" |
| #include "chrome/browser/profiles/profile.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/incognito_handler.h" |
| #include "chrome/common/extensions/manifest_handlers/icons_handler.h" |
| #include "chrome/common/url_constants.h" |
| #include "components/user_prefs/pref_registry_syncable.h" |
| #include "content/public/browser/navigation_controller.h" |
| #include "content/public/browser/web_contents.h" |
| #include "content/public/browser/web_ui.h" |
| #include "content/public/common/bindings_policy.h" |
| #include "content/public/common/page_transition_types.h" |
| #include "extensions/common/extension_resource.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" |
| #include "ui/gfx/image/image_skia.h" |
| |
| using content::WebContents; |
| using extensions::Extension; |
| using extensions::URLOverrides; |
| |
| namespace { |
| |
| // De-dupes the items in |list|. Assumes the values are strings. |
| void CleanUpDuplicates(base::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); |
| } |
| } |
| |
| // Reloads the page in |web_contents| if it uses the same profile as |profile| |
| // and if the current URL is a chrome URL. |
| void UnregisterAndReplaceOverrideForWebContents( |
| const std::string& page, Profile* profile, WebContents* web_contents) { |
| if (Profile::FromBrowserContext(web_contents->GetBrowserContext()) != profile) |
| return; |
| |
| GURL url = web_contents->GetURL(); |
| if (!url.SchemeIs(chrome::kChromeUIScheme) || url.host() != page) |
| return; |
| |
| // Don't use Reload() since |url| isn't the same as the internal URL that |
| // NavigationController has. |
| web_contents->GetController().LoadURL( |
| url, content::Referrer(url, WebKit::WebReferrerPolicyDefault), |
| content::PAGE_TRANSITION_RELOAD, std::string()); |
| } |
| |
| // Run favicon callbck with image result. If no favicon was available then |
| // |image| will be empty. |
| void RunFaviconCallbackAsync( |
| const FaviconService::FaviconResultsCallback& callback, |
| const gfx::Image& image) { |
| std::vector<chrome::FaviconBitmapResult>* favicon_bitmap_results = |
| new std::vector<chrome::FaviconBitmapResult>(); |
| |
| const std::vector<gfx::ImageSkiaRep>& image_reps = |
| image.AsImageSkia().image_reps(); |
| for (size_t i = 0; i < image_reps.size(); ++i) { |
| const gfx::ImageSkiaRep& image_rep = image_reps[i]; |
| scoped_refptr<base::RefCountedBytes> bitmap_data( |
| new base::RefCountedBytes()); |
| if (gfx::PNGCodec::EncodeBGRASkBitmap(image_rep.sk_bitmap(), |
| false, |
| &bitmap_data->data())) { |
| chrome::FaviconBitmapResult bitmap_result; |
| bitmap_result.bitmap_data = bitmap_data; |
| bitmap_result.pixel_size = gfx::Size(image_rep.pixel_width(), |
| image_rep.pixel_height()); |
| // Leave |bitmap_result|'s icon URL as the default of GURL(). |
| bitmap_result.icon_type = chrome::FAVICON; |
| |
| favicon_bitmap_results->push_back(bitmap_result); |
| } else { |
| NOTREACHED() << "Could not encode extension favicon"; |
| } |
| } |
| |
| base::MessageLoopProxy::current()->PostTask( |
| FROM_HERE, |
| base::Bind(&FaviconService::FaviconResultsCallbackRunner, |
| callback, |
| base::Owned(favicon_bitmap_results))); |
| } |
| |
| } // namespace |
| |
| const char ExtensionWebUI::kExtensionURLOverrides[] = |
| "extensions.chrome_url_overrides"; |
| |
| ExtensionWebUI::ExtensionWebUI(content::WebUI* web_ui, const GURL& url) |
| : WebUIController(web_ui), |
| url_(url) { |
| Profile* profile = Profile::FromWebUI(web_ui); |
| ExtensionService* service = profile->GetExtensionService(); |
| const Extension* extension = |
| service->extensions()->GetExtensionOrAppByURL(url); |
| DCHECK(extension); |
| |
| // The base class defaults to enabling WebUI bindings, but we don't need |
| // those (this is also reflected in ChromeWebUIControllerFactory:: |
| // UseWebUIBindingsForURL). |
| int bindings = 0; |
| |
| // Bind externalHost to Extension WebUI loaded in Chrome Frame. |
| const CommandLine& browser_command_line = *CommandLine::ForCurrentProcess(); |
| if (browser_command_line.HasSwitch(switches::kChromeFrame)) |
| bindings |= content::BINDINGS_POLICY_EXTERNAL_HOST; |
| web_ui->SetBindings(bindings); |
| |
| // Hack: A few things we specialize just for the bookmark manager. |
| if (extension->id() == extension_misc::kBookmarkManagerId) { |
| bookmark_manager_private_event_router_.reset( |
| new extensions::BookmarkManagerPrivateEventRouter( |
| profile, web_ui->GetWebContents())); |
| |
| web_ui->SetLinkTransitionType(content::PAGE_TRANSITION_AUTO_BOOKMARK); |
| } |
| } |
| |
| ExtensionWebUI::~ExtensionWebUI() {} |
| |
| extensions::BookmarkManagerPrivateEventRouter* |
| ExtensionWebUI::bookmark_manager_private_event_router() { |
| return bookmark_manager_private_event_router_.get(); |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // chrome:// URL overrides |
| |
| // static |
| void ExtensionWebUI::RegisterProfilePrefs( |
| user_prefs::PrefRegistrySyncable* registry) { |
| registry->RegisterDictionaryPref( |
| kExtensionURLOverrides, |
| user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF); |
| } |
| |
| // static |
| bool ExtensionWebUI::HandleChromeURLOverride( |
| GURL* url, content::BrowserContext* browser_context) { |
| if (!url->SchemeIs(chrome::kChromeUIScheme)) |
| return false; |
| |
| Profile* profile = Profile::FromBrowserContext(browser_context); |
| const base::DictionaryValue* overrides = |
| profile->GetPrefs()->GetDictionary(kExtensionURLOverrides); |
| std::string page = url->host(); |
| const base::ListValue* url_list = NULL; |
| if (!overrides || !overrides->GetList(page, &url_list)) |
| return false; |
| |
| ExtensionService* service = profile->GetExtensionService(); |
| |
| size_t i = 0; |
| while (i < url_list->GetSize()) { |
| const 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; |
| } |
| |
| if (!url->query().empty()) |
| override += "?" + url->query(); |
| if (!url->ref().empty()) |
| override += "#" + url->ref(); |
| 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->extensions()->GetByID(extension_url.host()); |
| 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 = |
| extensions::IncognitoInfo::IsSplitMode(extension) && |
| extension_util::IsIncognitoEnabled(extension->id(), service); |
| if (profile->IsOffTheRecord() && !incognito_override_allowed) { |
| ++i; |
| continue; |
| } |
| |
| *url = extension_url; |
| return true; |
| } |
| return false; |
| } |
| |
| // static |
| bool ExtensionWebUI::HandleChromeURLOverrideReverse( |
| GURL* url, content::BrowserContext* browser_context) { |
| Profile* profile = Profile::FromBrowserContext(browser_context); |
| const base::DictionaryValue* overrides = |
| profile->GetPrefs()->GetDictionary(kExtensionURLOverrides); |
| if (!overrides) |
| return false; |
| |
| // Find the reverse mapping based on the given URL. For example this maps the |
| // internal URL |
| // chrome-extension://eemcgdkfndhakfknompkggombfjjjeno/main.html#1 to |
| // chrome://bookmarks/#1 for display in the omnibox. |
| for (base::DictionaryValue::Iterator it(*overrides); !it.IsAtEnd(); |
| it.Advance()) { |
| const base::ListValue* url_list = NULL; |
| if (!it.value().GetAsList(&url_list)) |
| continue; |
| |
| for (base::ListValue::const_iterator it2 = url_list->begin(); |
| it2 != url_list->end(); ++it2) { |
| std::string override; |
| if (!(*it2)->GetAsString(&override)) |
| continue; |
| if (StartsWithASCII(url->spec(), override, true)) { |
| GURL original_url(chrome::kChromeUIScheme + std::string("://") + |
| it.key() + url->spec().substr(override.length())); |
| *url = original_url; |
| return true; |
| } |
| } |
| } |
| |
| return false; |
| } |
| |
| // static |
| void ExtensionWebUI::RegisterChromeURLOverrides( |
| Profile* profile, const URLOverrides::URLOverrideMap& overrides) { |
| if (overrides.empty()) |
| return; |
| |
| PrefService* prefs = profile->GetPrefs(); |
| DictionaryPrefUpdate update(prefs, kExtensionURLOverrides); |
| base::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. |
| URLOverrides::URLOverrideMap::const_iterator iter = overrides.begin(); |
| for (; iter != overrides.end(); ++iter) { |
| const std::string& key = iter->first; |
| base::ListValue* page_overrides = NULL; |
| if (!all_overrides->GetList(key, &page_overrides)) { |
| page_overrides = new base::ListValue(); |
| all_overrides->Set(key, page_overrides); |
| } else { |
| CleanUpDuplicates(page_overrides); |
| |
| // Verify that the override isn't already in the list. |
| base::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, |
| base::ListValue* list, |
| const Value* override) { |
| size_t index = 0; |
| bool found = list->Remove(*override, &index); |
| if (found && 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. |
| base::Callback<void(WebContents*)> callback = |
| base::Bind(&UnregisterAndReplaceOverrideForWebContents, page, profile); |
| ExtensionTabUtil::ForEachTab(callback); |
| } |
| } |
| |
| // static |
| void ExtensionWebUI::UnregisterChromeURLOverride(const std::string& page, |
| Profile* profile, |
| const Value* override) { |
| if (!override) |
| return; |
| PrefService* prefs = profile->GetPrefs(); |
| DictionaryPrefUpdate update(prefs, kExtensionURLOverrides); |
| base::DictionaryValue* all_overrides = update.Get(); |
| base::ListValue* page_overrides = NULL; |
| 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 URLOverrides::URLOverrideMap& overrides) { |
| if (overrides.empty()) |
| return; |
| PrefService* prefs = profile->GetPrefs(); |
| DictionaryPrefUpdate update(prefs, kExtensionURLOverrides); |
| base::DictionaryValue* all_overrides = update.Get(); |
| URLOverrides::URLOverrideMap::const_iterator iter = overrides.begin(); |
| for (; iter != overrides.end(); ++iter) { |
| const std::string& page = iter->first; |
| base::ListValue* page_overrides = NULL; |
| 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, |
| const GURL& page_url, |
| const FaviconService::FaviconResultsCallback& callback) { |
| // Even when the extensions service is enabled by default, it's still |
| // disabled in incognito mode. |
| ExtensionService* service = profile->GetExtensionService(); |
| if (!service) { |
| RunFaviconCallbackAsync(callback, gfx::Image()); |
| return; |
| } |
| const Extension* extension = service->extensions()->GetByID(page_url.host()); |
| if (!extension) { |
| RunFaviconCallbackAsync(callback, gfx::Image()); |
| return; |
| } |
| |
| // Fetch resources for all supported scale factors for which there are |
| // resources. Load image reps for all supported scale factors (in addition to |
| // 1x) immediately instead of in an as needed fashion to be consistent with |
| // how favicons are requested for chrome:// and page URLs. |
| const std::vector<ui::ScaleFactor>& scale_factors = |
| FaviconUtil::GetFaviconScaleFactors(); |
| std::vector<extensions::ImageLoader::ImageRepresentation> info_list; |
| for (size_t i = 0; i < scale_factors.size(); ++i) { |
| float scale = ui::GetImageScale(scale_factors[i]); |
| int pixel_size = static_cast<int>(gfx::kFaviconSize * scale); |
| extensions::ExtensionResource icon_resource = |
| extensions::IconsInfo::GetIconResource(extension, |
| pixel_size, |
| ExtensionIconSet::MATCH_BIGGER); |
| |
| info_list.push_back( |
| extensions::ImageLoader::ImageRepresentation( |
| icon_resource, |
| extensions::ImageLoader::ImageRepresentation::ALWAYS_RESIZE, |
| gfx::Size(pixel_size, pixel_size), |
| scale_factors[i])); |
| } |
| |
| // LoadImagesAsync actually can run callback synchronously. We want to force |
| // async. |
| extensions::ImageLoader::Get(profile)->LoadImagesAsync( |
| extension, info_list, base::Bind(&RunFaviconCallbackAsync, callback)); |
| } |