// 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();
}