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
36namespace {
37
38// De-dupes the items in |list|. Assumes the values are strings.
39void 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.
59class ExtensionWebUIImageLoadingTracker : public ImageLoadingTracker::Observer {
60 public:
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
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
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:
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.
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
127const char ExtensionWebUI::kExtensionURLOverrides[] =
128    "extensions.chrome_url_overrides";
129
130ExtensionWebUI::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
155ExtensionWebUI::~ExtensionWebUI() {}
156
157void 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
166void 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
177void ExtensionWebUI::RenderViewCreated(RenderViewHost* render_view_host) {
178  ResetExtensionFunctionDispatcher(render_view_host);
179  ResetExtensionBookmarkManagerEventRouter();
180}
181
182void ExtensionWebUI::RenderViewReused(RenderViewHost* render_view_host) {
183  ResetExtensionFunctionDispatcher(render_view_host);
184  ResetExtensionBookmarkManagerEventRouter();
185}
186
187void ExtensionWebUI::ProcessWebUIMessage(
188    const ExtensionHostMsg_DomMessage_Params& params) {
189  extension_function_dispatcher_->HandleRequest(params);
190}
191
192Browser* 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
203TabContents* ExtensionWebUI::associated_tab_contents() const {
204  return tab_contents();
205}
206
207ExtensionBookmarkManagerEventRouter*
208ExtensionWebUI::extension_bookmark_manager_event_router() {
209  return extension_bookmark_manager_event_router_.get();
210}
211
212gfx::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
226gfx::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
235void ExtensionWebUI::RegisterUserPrefs(PrefService* prefs) {
236  prefs->RegisterDictionaryPref(kExtensionURLOverrides);
237}
238
239// static
240bool 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
301void 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
344void 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
367void 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
385void 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
409void 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