1 // Copyright (c) 2011 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "chrome/browser/extensions/extension_web_ui.h"
6
7 #include <set>
8 #include <vector>
9
10 #include "base/string_util.h"
11 #include "base/utf_string_conversions.h"
12 #include "chrome/browser/extensions/extension_bookmark_manager_api.h"
13 #include "chrome/browser/extensions/extension_service.h"
14 #include "chrome/browser/extensions/image_loading_tracker.h"
15 #include "chrome/browser/prefs/pref_service.h"
16 #include "chrome/browser/prefs/scoped_user_pref_update.h"
17 #include "chrome/browser/profiles/profile.h"
18 #include "chrome/browser/ui/browser.h"
19 #include "chrome/browser/ui/browser_list.h"
20 #include "chrome/browser/ui/tab_contents/tab_contents_wrapper.h"
21 #include "chrome/common/chrome_switches.h"
22 #include "chrome/common/extensions/extension.h"
23 #include "chrome/common/extensions/extension_constants.h"
24 #include "chrome/common/extensions/extension_icon_set.h"
25 #include "chrome/common/extensions/extension_resource.h"
26 #include "chrome/common/url_constants.h"
27 #include "content/browser/renderer_host/render_widget_host_view.h"
28 #include "content/browser/tab_contents/tab_contents.h"
29 #include "content/common/bindings_policy.h"
30 #include "content/common/page_transition_types.h"
31 #include "net/base/file_stream.h"
32 #include "third_party/skia/include/core/SkBitmap.h"
33 #include "ui/gfx/codec/png_codec.h"
34 #include "ui/gfx/favicon_size.h"
35
36 namespace {
37
38 // De-dupes the items in |list|. Assumes the values are strings.
CleanUpDuplicates(ListValue * list)39 void CleanUpDuplicates(ListValue* list) {
40 std::set<std::string> seen_values;
41
42 // Loop backwards as we may be removing items.
43 for (size_t i = list->GetSize() - 1; (i + 1) > 0; --i) {
44 std::string value;
45 if (!list->GetString(i, &value)) {
46 NOTREACHED();
47 continue;
48 }
49
50 if (seen_values.find(value) == seen_values.end())
51 seen_values.insert(value);
52 else
53 list->Remove(i, NULL);
54 }
55 }
56
57 // Helper class that is used to track the loading of the favicon of an
58 // extension.
59 class ExtensionWebUIImageLoadingTracker : public ImageLoadingTracker::Observer {
60 public:
ExtensionWebUIImageLoadingTracker(Profile * profile,FaviconService::GetFaviconRequest * request,const GURL & page_url)61 ExtensionWebUIImageLoadingTracker(Profile* profile,
62 FaviconService::GetFaviconRequest* request,
63 const GURL& page_url)
64 : ALLOW_THIS_IN_INITIALIZER_LIST(tracker_(this)),
65 request_(request),
66 extension_(NULL) {
67 // Even when the extensions service is enabled by default, it's still
68 // disabled in incognito mode.
69 ExtensionService* service = profile->GetExtensionService();
70 if (service)
71 extension_ = service->GetExtensionByURL(page_url);
72 }
73
Init()74 void Init() {
75 if (extension_) {
76 ExtensionResource icon_resource =
77 extension_->GetIconResource(Extension::EXTENSION_ICON_BITTY,
78 ExtensionIconSet::MATCH_EXACTLY);
79
80 tracker_.LoadImage(extension_, icon_resource,
81 gfx::Size(kFaviconSize, kFaviconSize),
82 ImageLoadingTracker::DONT_CACHE);
83 } else {
84 ForwardResult(NULL);
85 }
86 }
87
OnImageLoaded(SkBitmap * image,const ExtensionResource & resource,int index)88 virtual void OnImageLoaded(SkBitmap* image, const ExtensionResource& resource,
89 int index) {
90 if (image) {
91 std::vector<unsigned char> image_data;
92 if (!gfx::PNGCodec::EncodeBGRASkBitmap(*image, false, &image_data)) {
93 NOTREACHED() << "Could not encode extension favicon";
94 }
95 ForwardResult(RefCountedBytes::TakeVector(&image_data));
96 } else {
97 ForwardResult(NULL);
98 }
99 }
100
101 private:
~ExtensionWebUIImageLoadingTracker()102 ~ExtensionWebUIImageLoadingTracker() {}
103
104 // Forwards the result on the request. If no favicon was available then
105 // |icon_data| may be backed by NULL. Once the result has been forwarded the
106 // instance is deleted.
ForwardResult(scoped_refptr<RefCountedMemory> icon_data)107 void ForwardResult(scoped_refptr<RefCountedMemory> icon_data) {
108 history::FaviconData favicon;
109 favicon.known_icon = icon_data.get() != NULL && icon_data->size() > 0;
110 favicon.image_data = icon_data;
111 favicon.icon_type = history::FAVICON;
112 request_->ForwardResultAsync(
113 FaviconService::FaviconDataCallback::TupleType(request_->handle(),
114 favicon));
115 delete this;
116 }
117
118 ImageLoadingTracker tracker_;
119 scoped_refptr<FaviconService::GetFaviconRequest> request_;
120 const Extension* extension_;
121
122 DISALLOW_COPY_AND_ASSIGN(ExtensionWebUIImageLoadingTracker);
123 };
124
125 } // namespace
126
127 const char ExtensionWebUI::kExtensionURLOverrides[] =
128 "extensions.chrome_url_overrides";
129
ExtensionWebUI(TabContents * tab_contents,const GURL & url)130 ExtensionWebUI::ExtensionWebUI(TabContents* tab_contents, const GURL& url)
131 : WebUI(tab_contents),
132 url_(url) {
133 ExtensionService* service = tab_contents->profile()->GetExtensionService();
134 const Extension* extension = service->GetExtensionByURL(url);
135 if (!extension)
136 extension = service->GetExtensionByWebExtent(url);
137 DCHECK(extension);
138 // Only hide the url for internal pages (e.g. chrome-extension or packaged
139 // component apps like bookmark manager.
140 should_hide_url_ = !extension->is_hosted_app();
141
142 bindings_ = BindingsPolicy::EXTENSION;
143 // Bind externalHost to Extension WebUI loaded in Chrome Frame.
144 const CommandLine& browser_command_line = *CommandLine::ForCurrentProcess();
145 if (browser_command_line.HasSwitch(switches::kChromeFrame))
146 bindings_ |= BindingsPolicy::EXTERNAL_HOST;
147 // For chrome:// overrides, some of the defaults are a little different.
148 GURL effective_url = tab_contents->GetURL();
149 if (effective_url.SchemeIs(chrome::kChromeUIScheme) &&
150 effective_url.host() == chrome::kChromeUINewTabHost) {
151 focus_location_bar_by_default_ = true;
152 }
153 }
154
~ExtensionWebUI()155 ExtensionWebUI::~ExtensionWebUI() {}
156
ResetExtensionFunctionDispatcher(RenderViewHost * render_view_host)157 void ExtensionWebUI::ResetExtensionFunctionDispatcher(
158 RenderViewHost* render_view_host) {
159 // TODO(jcivelli): http://crbug.com/60608 we should get the URL out of the
160 // active entry of the navigation controller.
161 extension_function_dispatcher_.reset(
162 ExtensionFunctionDispatcher::Create(render_view_host, this, url_));
163 DCHECK(extension_function_dispatcher_.get());
164 }
165
ResetExtensionBookmarkManagerEventRouter()166 void ExtensionWebUI::ResetExtensionBookmarkManagerEventRouter() {
167 // Hack: A few things we specialize just for the bookmark manager.
168 if (extension_function_dispatcher_->extension_id() ==
169 extension_misc::kBookmarkManagerId) {
170 extension_bookmark_manager_event_router_.reset(
171 new ExtensionBookmarkManagerEventRouter(GetProfile(), tab_contents()));
172
173 link_transition_type_ = PageTransition::AUTO_BOOKMARK;
174 }
175 }
176
RenderViewCreated(RenderViewHost * render_view_host)177 void ExtensionWebUI::RenderViewCreated(RenderViewHost* render_view_host) {
178 ResetExtensionFunctionDispatcher(render_view_host);
179 ResetExtensionBookmarkManagerEventRouter();
180 }
181
RenderViewReused(RenderViewHost * render_view_host)182 void ExtensionWebUI::RenderViewReused(RenderViewHost* render_view_host) {
183 ResetExtensionFunctionDispatcher(render_view_host);
184 ResetExtensionBookmarkManagerEventRouter();
185 }
186
ProcessWebUIMessage(const ExtensionHostMsg_DomMessage_Params & params)187 void ExtensionWebUI::ProcessWebUIMessage(
188 const ExtensionHostMsg_DomMessage_Params& params) {
189 extension_function_dispatcher_->HandleRequest(params);
190 }
191
GetBrowser()192 Browser* ExtensionWebUI::GetBrowser() {
193 TabContents* contents = tab_contents();
194 TabContentsIterator tab_iterator;
195 for (; !tab_iterator.done(); ++tab_iterator) {
196 if (contents == (*tab_iterator)->tab_contents())
197 return tab_iterator.browser();
198 }
199
200 return NULL;
201 }
202
associated_tab_contents() const203 TabContents* ExtensionWebUI::associated_tab_contents() const {
204 return tab_contents();
205 }
206
207 ExtensionBookmarkManagerEventRouter*
extension_bookmark_manager_event_router()208 ExtensionWebUI::extension_bookmark_manager_event_router() {
209 return extension_bookmark_manager_event_router_.get();
210 }
211
GetCustomFrameNativeWindow()212 gfx::NativeWindow ExtensionWebUI::GetCustomFrameNativeWindow() {
213 if (GetBrowser())
214 return NULL;
215
216 // If there was no browser associated with the function dispatcher delegate,
217 // then this WebUI may be hosted in an ExternalTabContainer, and a framing
218 // window will be accessible through the tab_contents.
219 TabContentsDelegate* tab_contents_delegate = tab_contents()->delegate();
220 if (tab_contents_delegate)
221 return tab_contents_delegate->GetFrameNativeWindow();
222 else
223 return NULL;
224 }
225
GetNativeViewOfHost()226 gfx::NativeView ExtensionWebUI::GetNativeViewOfHost() {
227 RenderWidgetHostView* rwhv = tab_contents()->GetRenderWidgetHostView();
228 return rwhv ? rwhv->GetNativeView() : NULL;
229 }
230
231 ////////////////////////////////////////////////////////////////////////////////
232 // chrome:// URL overrides
233
234 // static
RegisterUserPrefs(PrefService * prefs)235 void ExtensionWebUI::RegisterUserPrefs(PrefService* prefs) {
236 prefs->RegisterDictionaryPref(kExtensionURLOverrides);
237 }
238
239 // static
HandleChromeURLOverride(GURL * url,Profile * profile)240 bool ExtensionWebUI::HandleChromeURLOverride(GURL* url, Profile* profile) {
241 if (!url->SchemeIs(chrome::kChromeUIScheme))
242 return false;
243
244 const DictionaryValue* overrides =
245 profile->GetPrefs()->GetDictionary(kExtensionURLOverrides);
246 std::string page = url->host();
247 ListValue* url_list;
248 if (!overrides || !overrides->GetList(page, &url_list))
249 return false;
250
251 ExtensionService* service = profile->GetExtensionService();
252
253 size_t i = 0;
254 while (i < url_list->GetSize()) {
255 Value* val = NULL;
256 url_list->Get(i, &val);
257
258 // Verify that the override value is good. If not, unregister it and find
259 // the next one.
260 std::string override;
261 if (!val->GetAsString(&override)) {
262 NOTREACHED();
263 UnregisterChromeURLOverride(page, profile, val);
264 continue;
265 }
266 GURL extension_url(override);
267 if (!extension_url.is_valid()) {
268 NOTREACHED();
269 UnregisterChromeURLOverride(page, profile, val);
270 continue;
271 }
272
273 // Verify that the extension that's being referred to actually exists.
274 const Extension* extension = service->GetExtensionByURL(extension_url);
275 if (!extension) {
276 // This can currently happen if you use --load-extension one run, and
277 // then don't use it the next. It could also happen if an extension
278 // were deleted directly from the filesystem, etc.
279 LOG(WARNING) << "chrome URL override present for non-existant extension";
280 UnregisterChromeURLOverride(page, profile, val);
281 continue;
282 }
283
284 // We can't handle chrome-extension URLs in incognito mode unless the
285 // extension uses split mode.
286 bool incognito_override_allowed =
287 extension->incognito_split_mode() &&
288 service->IsIncognitoEnabled(extension->id());
289 if (profile->IsOffTheRecord() && !incognito_override_allowed) {
290 ++i;
291 continue;
292 }
293
294 *url = extension_url;
295 return true;
296 }
297 return false;
298 }
299
300 // static
RegisterChromeURLOverrides(Profile * profile,const Extension::URLOverrideMap & overrides)301 void ExtensionWebUI::RegisterChromeURLOverrides(
302 Profile* profile, const Extension::URLOverrideMap& overrides) {
303 if (overrides.empty())
304 return;
305
306 PrefService* prefs = profile->GetPrefs();
307 DictionaryPrefUpdate update(prefs, kExtensionURLOverrides);
308 DictionaryValue* all_overrides = update.Get();
309
310 // For each override provided by the extension, add it to the front of
311 // the override list if it's not already in the list.
312 Extension::URLOverrideMap::const_iterator iter = overrides.begin();
313 for (; iter != overrides.end(); ++iter) {
314 const std::string& key = iter->first;
315 ListValue* page_overrides;
316 if (!all_overrides->GetList(key, &page_overrides)) {
317 page_overrides = new ListValue();
318 all_overrides->Set(key, page_overrides);
319 } else {
320 CleanUpDuplicates(page_overrides);
321
322 // Verify that the override isn't already in the list.
323 ListValue::iterator i = page_overrides->begin();
324 for (; i != page_overrides->end(); ++i) {
325 std::string override_val;
326 if (!(*i)->GetAsString(&override_val)) {
327 NOTREACHED();
328 continue;
329 }
330 if (override_val == iter->second.spec())
331 break;
332 }
333 // This value is already in the list, leave it alone.
334 if (i != page_overrides->end())
335 continue;
336 }
337 // Insert the override at the front of the list. Last registered override
338 // wins.
339 page_overrides->Insert(0, new StringValue(iter->second.spec()));
340 }
341 }
342
343 // static
UnregisterAndReplaceOverride(const std::string & page,Profile * profile,ListValue * list,Value * override)344 void ExtensionWebUI::UnregisterAndReplaceOverride(const std::string& page,
345 Profile* profile, ListValue* list, Value* override) {
346 int index = list->Remove(*override);
347 if (index == 0) {
348 // This is the active override, so we need to find all existing
349 // tabs for this override and get them to reload the original URL.
350 for (TabContentsIterator iterator; !iterator.done(); ++iterator) {
351 TabContents* tab = (*iterator)->tab_contents();
352 if (tab->profile() != profile)
353 continue;
354
355 GURL url = tab->GetURL();
356 if (!url.SchemeIs(chrome::kChromeUIScheme) || url.host() != page)
357 continue;
358
359 // Don't use Reload() since |url| isn't the same as the internal URL
360 // that NavigationController has.
361 tab->controller().LoadURL(url, url, PageTransition::RELOAD);
362 }
363 }
364 }
365
366 // static
UnregisterChromeURLOverride(const std::string & page,Profile * profile,Value * override)367 void ExtensionWebUI::UnregisterChromeURLOverride(const std::string& page,
368 Profile* profile, Value* override) {
369 if (!override)
370 return;
371 PrefService* prefs = profile->GetPrefs();
372 DictionaryPrefUpdate update(prefs, kExtensionURLOverrides);
373 DictionaryValue* all_overrides = update.Get();
374 ListValue* page_overrides;
375 if (!all_overrides->GetList(page, &page_overrides)) {
376 // If it's being unregistered, it should already be in the list.
377 NOTREACHED();
378 return;
379 } else {
380 UnregisterAndReplaceOverride(page, profile, page_overrides, override);
381 }
382 }
383
384 // static
UnregisterChromeURLOverrides(Profile * profile,const Extension::URLOverrideMap & overrides)385 void ExtensionWebUI::UnregisterChromeURLOverrides(
386 Profile* profile, const Extension::URLOverrideMap& overrides) {
387 if (overrides.empty())
388 return;
389 PrefService* prefs = profile->GetPrefs();
390 DictionaryPrefUpdate update(prefs, kExtensionURLOverrides);
391 DictionaryValue* all_overrides = update.Get();
392 Extension::URLOverrideMap::const_iterator iter = overrides.begin();
393 for (; iter != overrides.end(); ++iter) {
394 const std::string& page = iter->first;
395 ListValue* page_overrides;
396 if (!all_overrides->GetList(page, &page_overrides)) {
397 // If it's being unregistered, it should already be in the list.
398 NOTREACHED();
399 continue;
400 } else {
401 StringValue override(iter->second.spec());
402 UnregisterAndReplaceOverride(iter->first, profile,
403 page_overrides, &override);
404 }
405 }
406 }
407
408 // static
GetFaviconForURL(Profile * profile,FaviconService::GetFaviconRequest * request,const GURL & page_url)409 void ExtensionWebUI::GetFaviconForURL(Profile* profile,
410 FaviconService::GetFaviconRequest* request, const GURL& page_url) {
411 // tracker deletes itself when done.
412 ExtensionWebUIImageLoadingTracker* tracker =
413 new ExtensionWebUIImageLoadingTracker(profile, request, page_url);
414 tracker->Init();
415 }
416