| 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 |