• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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