OLD | NEW |
| (Empty) |
1 // Copyright (c) 2009 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/gears_integration.h" | |
6 | |
7 #include <string> | |
8 #include <vector> | |
9 | |
10 #include "base/base64.h" | |
11 #include "base/logging.h" | |
12 #include "base/message_loop.h" | |
13 #include "base/scoped_ptr.h" | |
14 #include "base/utf_string_conversions.h" | |
15 #include "chrome/browser/chrome_plugin_host.h" | |
16 #include "chrome/common/chrome_plugin_util.h" | |
17 #include "chrome/common/gears_api.h" | |
18 #include "chrome/common/web_apps.h" | |
19 #include "googleurl/src/gurl.h" | |
20 #include "third_party/skia/include/core/SkBitmap.h" | |
21 #include "ui/gfx/codec/png_codec.h" | |
22 | |
23 // The following 2 helpers are borrowed from the Gears codebase. | |
24 | |
25 const size_t kUserPathComponentMaxChars = 64; | |
26 | |
27 // Returns true if and only if the char meets the following criteria: | |
28 // | |
29 // - visible ASCII | |
30 // - None of the following characters: / \ : * ? " < > | ; , | |
31 // | |
32 // This function is a heuristic that should identify most strings that are | |
33 // invalid pathnames on popular OSes. It's both overinclusive and | |
34 // underinclusive, though. | |
35 template<class CharT> | |
36 inline bool IsCharValidInPathComponent(CharT c) { | |
37 // Not visible ASCII? | |
38 // Note: the Gears version of this function excludes spaces (32) as well. We | |
39 // allow them for file names. | |
40 if (c < 32 || c >= 127) { | |
41 return false; | |
42 } | |
43 | |
44 // Illegal characters? | |
45 switch (c) { | |
46 case '/': | |
47 case '\\': | |
48 case ':': | |
49 case '*': | |
50 case '?': | |
51 case '"': | |
52 case '<': | |
53 case '>': | |
54 case '|': | |
55 case ';': | |
56 case ',': | |
57 return false; | |
58 | |
59 default: | |
60 return true; | |
61 } | |
62 } | |
63 | |
64 // Modifies a string, replacing characters that are not valid in a file path | |
65 // component with the '_' character. Also replaces leading and trailing dots | |
66 // with the '_' character. | |
67 // See IsCharValidInPathComponent | |
68 template<class StringT> | |
69 inline void EnsureStringValidPathComponent(StringT &s) { | |
70 if (s.empty()) { | |
71 return; | |
72 } | |
73 | |
74 typename StringT::iterator iter = s.begin(); | |
75 typename StringT::iterator end = s.end(); | |
76 | |
77 // Does it start with a dot? | |
78 if (*iter == '.') { | |
79 *iter = '_'; | |
80 ++iter; // skip it in the loop below | |
81 } | |
82 // Is every char valid? | |
83 while (iter != end) { | |
84 if (!IsCharValidInPathComponent(*iter)) { | |
85 *iter = '_'; | |
86 } | |
87 ++iter; | |
88 } | |
89 // Does it end with a dot? | |
90 --iter; | |
91 if (*iter == '.') { | |
92 *iter = '_'; | |
93 } | |
94 | |
95 // Is it too long? | |
96 if (s.size() > kUserPathComponentMaxChars) | |
97 s.resize(kUserPathComponentMaxChars); | |
98 } | |
99 | |
100 void GearsSettingsPressed(gfx::NativeWindow parent_wnd) { | |
101 CPBrowsingContext context = static_cast<CPBrowsingContext>( | |
102 reinterpret_cast<uintptr_t>(parent_wnd)); | |
103 CPHandleCommand(GEARSPLUGINCOMMAND_SHOW_SETTINGS, NULL, context); | |
104 } | |
105 | |
106 // Gears only supports certain icon sizes. | |
107 enum GearsIconSizes { | |
108 SIZE_16x16 = 0, | |
109 SIZE_32x32, | |
110 SIZE_48x48, | |
111 SIZE_128x128, | |
112 NUM_GEARS_ICONS, | |
113 }; | |
114 | |
115 // Helper function to convert a 16x16 favicon to a data: URL with the icon | |
116 // encoded as a PNG. | |
117 static GURL ConvertSkBitmapToDataURL(const SkBitmap& icon) { | |
118 DCHECK(!icon.isNull()); | |
119 DCHECK(icon.width() == 16 && icon.height() == 16); | |
120 | |
121 // Get the FavIcon data. | |
122 std::vector<unsigned char> icon_data; | |
123 gfx::PNGCodec::EncodeBGRASkBitmap(icon, false, &icon_data); | |
124 | |
125 // Base64-encode it (to make it a data URL). | |
126 std::string icon_data_str(reinterpret_cast<char*>(&icon_data[0]), | |
127 icon_data.size()); | |
128 std::string icon_base64_encoded; | |
129 base::Base64Encode(icon_data_str, &icon_base64_encoded); | |
130 GURL icon_url("data:image/png;base64," + icon_base64_encoded); | |
131 | |
132 return icon_url; | |
133 } | |
134 | |
135 // This class holds and manages the data passed to the | |
136 // GEARSPLUGINCOMMAND_CREATE_SHORTCUT plugin command. | |
137 class CreateShortcutCommand : public CPCommandInterface { | |
138 public: | |
139 CreateShortcutCommand( | |
140 const std::string& name, const std::string& orig_name, | |
141 const std::string& url, const std::string& description, | |
142 const std::vector<WebApplicationInfo::IconInfo> &icons, | |
143 const SkBitmap& fallback_icon, | |
144 GearsCreateShortcutCallback* callback) | |
145 : name_(name), url_(url), description_(description), | |
146 orig_name_(orig_name), callback_(callback), | |
147 calling_loop_(MessageLoop::current()) { | |
148 // shortcut_data_ has the same lifetime as our strings, so we just | |
149 // point it at their internal data. | |
150 memset(&shortcut_data_, 0, sizeof(shortcut_data_)); | |
151 shortcut_data_.name = name_.c_str(); | |
152 shortcut_data_.url = url_.c_str(); | |
153 shortcut_data_.description = description_.c_str(); | |
154 shortcut_data_.orig_name = orig_name_.c_str(); | |
155 | |
156 // Search the icons array for Gears-supported sizes and copy the strings. | |
157 bool has_icon = false; | |
158 | |
159 for (size_t i = 0; i < icons.size(); ++i) { | |
160 const WebApplicationInfo::IconInfo& icon = icons[i]; | |
161 if (icon.width == 16 && icon.height == 16) { | |
162 has_icon = true; | |
163 InitIcon(SIZE_16x16, icon.url, 16, 16); | |
164 } else if (icon.width == 32 && icon.height == 32) { | |
165 has_icon = true; | |
166 InitIcon(SIZE_32x32, icon.url, 32, 32); | |
167 } else if (icon.width == 48 && icon.height == 48) { | |
168 has_icon = true; | |
169 InitIcon(SIZE_48x48, icon.url, 48, 48); | |
170 } else if (icon.width == 128 && icon.height == 128) { | |
171 has_icon = true; | |
172 InitIcon(SIZE_128x128, icon.url, 128, 128); | |
173 } | |
174 } | |
175 | |
176 if (!has_icon) { | |
177 // Fall back to the favicon only if the site provides no icons at all. We | |
178 // assume if a site provides any icons, it wants to override default | |
179 // behavior. | |
180 InitIcon(SIZE_16x16, ConvertSkBitmapToDataURL(fallback_icon), 16, 16); | |
181 } | |
182 | |
183 shortcut_data_.command_interface = this; | |
184 } | |
185 virtual ~CreateShortcutCommand() { } | |
186 | |
187 // CPCommandInterface | |
188 virtual void* GetData() { return &shortcut_data_; } | |
189 virtual void OnCommandInvoked(CPError retval) { | |
190 if (retval != CPERR_IO_PENDING) { | |
191 // Older versions of Gears don't send a response, so don't wait for one. | |
192 OnCommandResponse(CPERR_FAILURE); | |
193 } | |
194 } | |
195 virtual void OnCommandResponse(CPError retval) { | |
196 calling_loop_->PostTask(FROM_HERE, NewRunnableMethod( | |
197 this, &CreateShortcutCommand::ReportResults, retval)); | |
198 } | |
199 | |
200 private: | |
201 void ReportResults(CPError retval) { | |
202 // Other code only knows about the original GearsShortcutData. Pass our | |
203 // GearsShortcutData2 off as one of those - but use the unmodified name. | |
204 // TODO(mpcomplete): this means that Gears will have stored its sanitized | |
205 // filename, but not expose it to us. We will use the unsanitized version, | |
206 // so our name will potentially differ. This is relevant because we store | |
207 // some prefs keyed off the webapp name. | |
208 shortcut_data_.name = shortcut_data_.orig_name; | |
209 callback_->Run(shortcut_data_, retval == CPERR_SUCCESS); | |
210 delete this; | |
211 } | |
212 | |
213 void InitIcon(GearsIconSizes size, const GURL& url, int width, int height) { | |
214 icon_urls_[size] = url.spec(); // keeps the string memory in scope | |
215 shortcut_data_.icons[size].url = icon_urls_[size].c_str(); | |
216 shortcut_data_.icons[size].width = width; | |
217 shortcut_data_.icons[size].height = height; | |
218 } | |
219 | |
220 GearsCreateShortcutData shortcut_data_; | |
221 std::string name_; | |
222 std::string url_; | |
223 std::string description_; | |
224 std::string icon_urls_[NUM_GEARS_ICONS]; | |
225 std::string orig_name_; | |
226 scoped_ptr<GearsCreateShortcutCallback> callback_; | |
227 MessageLoop* calling_loop_; | |
228 }; | |
229 | |
230 // Allows InvokeLater without adding refcounting. The object is only deleted | |
231 // when its last InvokeLater is run anyway. | |
232 DISABLE_RUNNABLE_METHOD_REFCOUNT(CreateShortcutCommand); | |
233 | |
234 void GearsCreateShortcut( | |
235 const WebApplicationInfo& app_info, | |
236 const string16& fallback_name, | |
237 const GURL& fallback_url, | |
238 const SkBitmap& fallback_icon, | |
239 GearsCreateShortcutCallback* callback) { | |
240 string16 name = | |
241 !app_info.title.empty() ? app_info.title : fallback_name; | |
242 std::string orig_name_utf8 = UTF16ToUTF8(name); | |
243 EnsureStringValidPathComponent(name); | |
244 | |
245 std::string name_utf8 = UTF16ToUTF8(name); | |
246 std::string description_utf8 = UTF16ToUTF8(app_info.description); | |
247 const GURL& url = | |
248 !app_info.app_url.is_empty() ? app_info.app_url : fallback_url; | |
249 | |
250 CreateShortcutCommand* command = | |
251 new CreateShortcutCommand(name_utf8, orig_name_utf8, url.spec(), | |
252 description_utf8, | |
253 app_info.icons, fallback_icon, callback); | |
254 CPHandleCommand(GEARSPLUGINCOMMAND_CREATE_SHORTCUT, command, 0); | |
255 } | |
256 | |
257 // This class holds and manages the data passed to the | |
258 // GEARSPLUGINCOMMAND_GET_SHORTCUT_LIST plugin command. When the command is | |
259 // invoked, we proxy the results over to the calling thread. | |
260 class QueryShortcutsCommand : public CPCommandInterface { | |
261 public: | |
262 explicit QueryShortcutsCommand(GearsQueryShortcutsCallback* callback) : | |
263 callback_(callback), calling_loop_(MessageLoop::current()) { | |
264 shortcut_list_.shortcuts = NULL; | |
265 shortcut_list_.num_shortcuts = 0; | |
266 } | |
267 virtual ~QueryShortcutsCommand() { } | |
268 virtual void* GetData() { return &shortcut_list_; } | |
269 virtual void OnCommandInvoked(CPError retval) { | |
270 calling_loop_->PostTask(FROM_HERE, NewRunnableMethod( | |
271 this, &QueryShortcutsCommand::ReportResults, retval)); | |
272 } | |
273 | |
274 private: | |
275 void ReportResults(CPError retval) { | |
276 callback_->Run(retval == CPERR_SUCCESS ? &shortcut_list_ : NULL); | |
277 FreeGearsShortcutList(); | |
278 delete this; | |
279 } | |
280 | |
281 void FreeGearsShortcutList() { | |
282 for (size_t i = 0; i < shortcut_list_.num_shortcuts; ++i) { | |
283 CPB_Free(const_cast<char*>(shortcut_list_.shortcuts[i].description)); | |
284 CPB_Free(const_cast<char*>(shortcut_list_.shortcuts[i].name)); | |
285 CPB_Free(const_cast<char*>(shortcut_list_.shortcuts[i].url)); | |
286 for (size_t j = 0; j < 4; ++j) | |
287 CPB_Free(const_cast<char*>(shortcut_list_.shortcuts[i].icons[j].url)); | |
288 } | |
289 CPB_Free(shortcut_list_.shortcuts); | |
290 } | |
291 | |
292 GearsShortcutList shortcut_list_; | |
293 scoped_ptr<GearsQueryShortcutsCallback> callback_; | |
294 MessageLoop* calling_loop_; | |
295 }; | |
296 | |
297 // Allows InvokeLater without adding refcounting. The object is only deleted | |
298 // when its last InvokeLater is run anyway. | |
299 template <> | |
300 struct RunnableMethodTraits<QueryShortcutsCommand> { | |
301 void RetainCallee(QueryShortcutsCommand* command) {} | |
302 void ReleaseCallee(QueryShortcutsCommand* command) {} | |
303 }; | |
304 | |
305 void GearsQueryShortcuts(GearsQueryShortcutsCallback* callback) { | |
306 CPHandleCommand(GEARSPLUGINCOMMAND_GET_SHORTCUT_LIST, | |
307 new QueryShortcutsCommand(callback), | |
308 0); | |
309 } | |
OLD | NEW |