1 // Copyright (c) 2006-2008 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.
5 #include "chrome/browser/gears_integration.h"
7 #include "base/gfx/png_encoder.h"
8 #include "base/logging.h"
9 #include "base/message_loop.h"
10 #include "base/ref_counted.h"
11 #include "base/string_util.h"
12 #include "chrome/browser/chrome_plugin_host.h"
13 #include "chrome/common/chrome_plugin_util.h"
14 #include "chrome/common/gears_api.h"
15 #include "googleurl/src/gurl.h"
16 #include "net/base/base64.h"
17 #include "skia/include/SkBitmap.h"
18 #include "webkit/glue/dom_operations.h"
20 // The following 2 helpers are borrowed from the Gears codebase.
22 const size_t kUserPathComponentMaxChars
= 64;
24 // Returns true if and only if the char meets the following criteria:
27 // - None of the following characters: / \ : * ? " < > | ; ,
29 // This function is a heuristic that should identify most strings that are
30 // invalid pathnames on popular OSes. It's both overinclusive and
31 // underinclusive, though.
33 inline bool IsCharValidInPathComponent(CharT c
) {
35 // Note: the Gears version of this function excludes spaces (32) as well. We
36 // allow them for file names.
37 if (c
< 32 || c
>= 127) {
41 // Illegal characters?
61 // Modifies a string, replacing characters that are not valid in a file path
62 // component with the '_' character. Also replaces leading and trailing dots
63 // with the '_' character.
64 // See IsCharValidInPathComponent
65 template<class StringT
>
66 inline void EnsureStringValidPathComponent(StringT
&s
) {
71 typename
StringT::iterator iter
= s
.begin();
72 typename
StringT::iterator end
= s
.end();
74 // Does it start with a dot?
77 ++iter
; // skip it in the loop below
79 // Is every char valid?
81 if (!IsCharValidInPathComponent(*iter
)) {
86 // Does it end with a dot?
93 if (s
.size() > kUserPathComponentMaxChars
)
94 s
.resize(kUserPathComponentMaxChars
);
97 void GearsSettingsPressed(HWND parent_hwnd
) {
98 CPBrowsingContext context
= static_cast<CPBrowsingContext
>(
99 reinterpret_cast<uintptr_t>(parent_hwnd
));
100 CPHandleCommand(GEARSPLUGINCOMMAND_SHOW_SETTINGS
, NULL
, context
);
103 // Gears only supports certain icon sizes.
104 enum GearsIconSizes
{
112 // Helper function to convert a 16x16 favicon to a data: URL with the icon
114 static GURL
ConvertSkBitmapToDataURL(const SkBitmap
& icon
) {
115 DCHECK(!icon
.isNull());
116 DCHECK(icon
.width() == 16 && icon
.height() == 16);
118 // Get the FavIcon data.
119 std::vector
<unsigned char> icon_data
;
121 SkAutoLockPixels
icon_lock(icon
);
122 PNGEncoder::Encode(static_cast<unsigned char*>(icon
.getPixels()),
123 PNGEncoder::FORMAT_BGRA
, icon
.width(),
124 icon
.height(), icon
.width()* 4, false,
128 // Base64-encode it (to make it a data URL).
129 std::string
icon_data_str(reinterpret_cast<char*>(&icon_data
[0]),
131 std::string icon_base64_encoded
;
132 net::Base64Encode(icon_data_str
, &icon_base64_encoded
);
133 GURL
icon_url("data:image/png;base64," + icon_base64_encoded
);
138 // This class holds and manages the data passed to the
139 // GEARSPLUGINCOMMAND_CREATE_SHORTCUT plugin command.
140 class CreateShortcutCommand
: public CPCommandInterface
{
142 CreateShortcutCommand(
143 const std::string
& name
, const std::string
& orig_name
,
144 const std::string
& url
, const std::string
& description
,
145 const std::vector
<webkit_glue::WebApplicationInfo::IconInfo
> &icons
,
146 const SkBitmap
& fallback_icon
,
147 GearsCreateShortcutCallback
* callback
)
148 : name_(name
), url_(url
), description_(description
),
149 orig_name_(orig_name
), callback_(callback
),
150 calling_loop_(MessageLoop::current()) {
151 // shortcut_data_ has the same lifetime as our strings, so we just point it
152 // at their internal data.
153 memset(&shortcut_data_
, 0, sizeof(shortcut_data_
));
154 shortcut_data_
.name
= name_
.c_str();
155 shortcut_data_
.url
= url_
.c_str();
156 shortcut_data_
.description
= description_
.c_str();
157 shortcut_data_
.orig_name
= orig_name_
.c_str();
159 // Search the icons array for Gears-supported sizes and copy the strings.
160 bool has_icon
= false;
162 for (size_t i
= 0; i
< icons
.size(); ++i
) {
163 const webkit_glue::WebApplicationInfo::IconInfo
& icon
= icons
[i
];
164 if (icon
.width
== 16 && icon
.height
== 16) {
166 InitIcon(SIZE_16x16
, icon
.url
, 16, 16);
167 } else if (icon
.width
== 32 && icon
.height
== 32) {
169 InitIcon(SIZE_32x32
, icon
.url
, 32, 32);
170 } else if (icon
.width
== 48 && icon
.height
== 48) {
172 InitIcon(SIZE_48x48
, icon
.url
, 48, 48);
173 } else if (icon
.width
== 128 && icon
.height
== 128) {
175 InitIcon(SIZE_128x128
, icon
.url
, 128, 128);
180 // Fall back to the favicon only if the site provides no icons at all. We
181 // assume if a site provides any icons, it wants to override default
183 InitIcon(SIZE_16x16
, ConvertSkBitmapToDataURL(fallback_icon
), 16, 16);
186 shortcut_data_
.command_interface
= this;
188 virtual ~CreateShortcutCommand() { }
190 // CPCommandInterface
191 virtual void* GetData() { return &shortcut_data_
; }
192 virtual void OnCommandInvoked(CPError retval
) {
193 if (retval
!= CPERR_IO_PENDING
) {
194 // Older versions of Gears don't send a response, so don't wait for one.
195 OnCommandResponse(CPERR_FAILURE
);
198 virtual void OnCommandResponse(CPError retval
) {
199 calling_loop_
->PostTask(FROM_HERE
, NewRunnableMethod(
200 this, &CreateShortcutCommand::ReportResults
, retval
));
204 void ReportResults(CPError retval
) {
205 // Other code only knows about the original GearsShortcutData. Pass our
206 // GearsShortcutData2 off as one of those - but use the unmodified name.
207 // TODO(mpcomplete): this means that Gears will have stored its sanitized
208 // filename, but not expose it to us. We will use the unsanitized version,
209 // so our name will potentially differ. This is relevant because we store
210 // some prefs keyed off the webapp name.
211 shortcut_data_
.name
= shortcut_data_
.orig_name
;
212 callback_
->Run(*reinterpret_cast<GearsShortcutData
*>(&shortcut_data_
),
213 retval
== CPERR_SUCCESS
);
217 void InitIcon(GearsIconSizes size
, const GURL
& url
, int width
, int height
) {
218 icon_urls_
[size
] = url
.spec(); // keeps the string memory in scope
219 shortcut_data_
.icons
[size
].url
= icon_urls_
[size
].c_str();
220 shortcut_data_
.icons
[size
].width
= width
;
221 shortcut_data_
.icons
[size
].height
= height
;
224 GearsCreateShortcutData shortcut_data_
;
227 std::string description_
;
228 std::string icon_urls_
[NUM_GEARS_ICONS
];
229 std::string orig_name_
;
230 scoped_ptr
<GearsCreateShortcutCallback
> callback_
;
231 MessageLoop
* calling_loop_
;
234 // Allows InvokeLater without adding refcounting. The object is only deleted
235 // when its last InvokeLater is run anyway.
236 void RunnableMethodTraits
<CreateShortcutCommand
>::RetainCallee(
237 CreateShortcutCommand
* remover
) {
239 void RunnableMethodTraits
<CreateShortcutCommand
>::ReleaseCallee(
240 CreateShortcutCommand
* remover
) {
243 void GearsCreateShortcut(
244 const webkit_glue::WebApplicationInfo
& app_info
,
245 const std::wstring
& fallback_name
,
246 const GURL
& fallback_url
,
247 const SkBitmap
& fallback_icon
,
248 GearsCreateShortcutCallback
* callback
) {
250 !app_info
.title
.empty() ? app_info
.title
: fallback_name
;
251 std::string orig_name_utf8
= WideToUTF8(name
);
252 EnsureStringValidPathComponent(name
);
254 std::string name_utf8
= WideToUTF8(name
);
255 std::string description_utf8
= WideToUTF8(app_info
.description
);
257 !app_info
.app_url
.is_empty() ? app_info
.app_url
: fallback_url
;
259 CreateShortcutCommand
* command
=
260 new CreateShortcutCommand(name_utf8
, orig_name_utf8
, url
.spec(),
262 app_info
.icons
, fallback_icon
, callback
);
263 CPHandleCommand(GEARSPLUGINCOMMAND_CREATE_SHORTCUT
, command
, NULL
);
266 // This class holds and manages the data passed to the
267 // GEARSPLUGINCOMMAND_GET_SHORTCUT_LIST plugin command. When the command is
268 // invoked, we proxy the results over to the calling thread.
269 class QueryShortcutsCommand
: public CPCommandInterface
{
271 explicit QueryShortcutsCommand(GearsQueryShortcutsCallback
* callback
) :
272 callback_(callback
), calling_loop_(MessageLoop::current()) {
273 shortcut_list_
.shortcuts
= NULL
;
274 shortcut_list_
.num_shortcuts
= 0;
276 virtual ~QueryShortcutsCommand() { }
277 virtual void* GetData() { return &shortcut_list_
; }
278 virtual void OnCommandInvoked(CPError retval
) {
279 calling_loop_
->PostTask(FROM_HERE
, NewRunnableMethod(
280 this, &QueryShortcutsCommand::ReportResults
, retval
));
284 void ReportResults(CPError retval
) {
285 callback_
->Run(retval
== CPERR_SUCCESS
? &shortcut_list_
: NULL
);
286 FreeGearsShortcutList();
290 void FreeGearsShortcutList() {
291 for (size_t i
= 0; i
< shortcut_list_
.num_shortcuts
; ++i
) {
292 CPB_Free(const_cast<char*>(shortcut_list_
.shortcuts
[i
].description
));
293 CPB_Free(const_cast<char*>(shortcut_list_
.shortcuts
[i
].name
));
294 CPB_Free(const_cast<char*>(shortcut_list_
.shortcuts
[i
].url
));
295 for (size_t j
= 0; j
< 4; ++j
)
296 CPB_Free(const_cast<char*>(shortcut_list_
.shortcuts
[i
].icons
[j
].url
));
298 CPB_Free(shortcut_list_
.shortcuts
);
301 GearsShortcutList shortcut_list_
;
302 scoped_ptr
<GearsQueryShortcutsCallback
> callback_
;
303 MessageLoop
* calling_loop_
;
306 // Allows InvokeLater without adding refcounting. The object is only deleted
307 // when its last InvokeLater is run anyway.
308 void RunnableMethodTraits
<QueryShortcutsCommand
>::RetainCallee(
309 QueryShortcutsCommand
* remover
) {
311 void RunnableMethodTraits
<QueryShortcutsCommand
>::ReleaseCallee(
312 QueryShortcutsCommand
* remover
) {
315 void GearsQueryShortcuts(GearsQueryShortcutsCallback
* callback
) {
316 CPHandleCommand(GEARSPLUGINCOMMAND_GET_SHORTCUT_LIST
,
317 new QueryShortcutsCommand(callback
),