// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "chrome/browser/ui/webui/chromeos/user_image_source.h" #include "base/memory/ref_counted_memory.h" #include "base/strings/string_number_conversions.h" #include "base/strings/string_split.h" #include "chrome/browser/chromeos/login/users/default_user_image/default_user_images.h" #include "chrome/common/url_constants.h" #include "components/account_id/account_id.h" #include "components/user_manager/known_user.h" #include "components/user_manager/user_manager.h" #include "net/base/escape.h" #include "ui/base/resource/resource_bundle.h" #include "ui/chromeos/resources/grit/ui_chromeos_resources.h" #include "ui/display/display.h" #include "ui/display/screen.h" #include "ui/gfx/codec/png_codec.h" #include "url/third_party/mozilla/url_parse.h" namespace { // URL parameter specifying frame index. const char kFrameIndex[] = "frame"; // Parses the user image URL, which looks like // "chrome://userimage/serialized-user-id?key1=value1&...&key_n=value_n", // to user email and frame. void ParseRequest(const GURL& url, std::string* email, int* frame) { DCHECK(url.is_valid()); const std::string serialized_account_id = net::UnescapeURLComponent( url.path().substr(1), net::UnescapeRule::URL_SPECIAL_CHARS_EXCEPT_PATH_SEPARATORS | net::UnescapeRule::PATH_SEPARATORS | net::UnescapeRule::SPACES); AccountId account_id(EmptyAccountId()); const bool status = AccountId::Deserialize(serialized_account_id, &account_id); // TODO(alemate): DCHECK(status) - should happen after options page is // migrated. if (!status) { LOG(WARNING) << "Failed to deserialize account_id."; account_id = user_manager::known_user::GetAccountId( serialized_account_id, std::string() /* id */, AccountType::UNKNOWN); } *email = account_id.GetUserEmail(); *frame = -1; base::StringPairs parameters; base::SplitStringIntoKeyValuePairs(url.query(), '=', '&', ¶meters); for (base::StringPairs::const_iterator iter = parameters.begin(); iter != parameters.end(); ++iter) { if (iter->first == kFrameIndex) { unsigned value = 0; if (!base::StringToUint(iter->second, &value)) { LOG(WARNING) << "Invalid frame format: " << iter->second; continue; } *frame = static_cast(value); break; } } } scoped_refptr LoadUserImageFrameForScaleFactor( int resource_id, int frame, ui::ScaleFactor scale_factor) { // Load all frames. if (frame == -1) { return ui::ResourceBundle::GetSharedInstance() .LoadDataResourceBytesForScale(resource_id, scale_factor); } // TODO(reveman): Add support for frames beyond 0 (crbug.com/750064). if (frame) { NOTIMPLEMENTED() << "Unsupported frame: " << frame; return nullptr; } gfx::ImageSkia* image = ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(resource_id); float scale = ui::GetScaleForScaleFactor(scale_factor); scoped_refptr data(new base::RefCountedBytes); gfx::PNGCodec::EncodeBGRASkBitmap(image->GetRepresentation(scale).GetBitmap(), false /* discard transparency */, &data->data()); return data; } scoped_refptr GetUserImageFrame( scoped_refptr image_bytes, user_manager::UserImage::ImageFormat image_format, int frame) { // Return all frames. if (frame == -1) return image_bytes; // TODO(reveman): Add support for frames beyond 0 (crbug.com/750064). if (frame) { NOTIMPLEMENTED() << "Unsupported frame: " << frame; return nullptr; } // Only PNGs can be animated. if (image_format != user_manager::UserImage::FORMAT_PNG) return image_bytes; // Extract first frame by re-encoding image. SkBitmap bitmap; if (!gfx::PNGCodec::Decode(image_bytes->front(), image_bytes->size(), &bitmap)) { return nullptr; } scoped_refptr data(new base::RefCountedBytes); gfx::PNGCodec::EncodeBGRASkBitmap(bitmap, false /* discard transparency */, &data->data()); return data; } scoped_refptr GetUserImageInternal( const AccountId& account_id, int frame) { const user_manager::User* user = user_manager::UserManager::Get()->FindUser(account_id); ui::ScaleFactor scale_factor = ui::SCALE_FACTOR_100P; // Use the scaling that matches primary display. These source images are // 96x96 and often used at that size in WebUI pages. display::Screen* screen = display::Screen::GetScreen(); if (screen) { scale_factor = ui::GetSupportedScaleFactor( screen->GetPrimaryDisplay().device_scale_factor()); } if (user) { if (user->has_image_bytes()) { if (user->image_format() == user_manager::UserImage::FORMAT_PNG) { return GetUserImageFrame(user->image_bytes(), user->image_format(), frame); } else { scoped_refptr data(new base::RefCountedBytes); gfx::PNGCodec::EncodeBGRASkBitmap(*user->GetImage().bitmap(), false /* discard transparency */, &data->data()); return data; } } if (user->image_is_stub()) { return LoadUserImageFrameForScaleFactor(IDR_LOGIN_DEFAULT_USER, frame, scale_factor); } if (user->HasDefaultImage()) { return LoadUserImageFrameForScaleFactor( chromeos::default_user_image::kDefaultImageResourceIDs [user->image_index()], frame, scale_factor); } NOTREACHED() << "User with custom image missing data bytes"; } else { LOG(ERROR) << "User not found: " << account_id.GetUserEmail(); } return LoadUserImageFrameForScaleFactor(IDR_LOGIN_DEFAULT_USER, frame, scale_factor); } } // namespace namespace chromeos { // Static. scoped_refptr UserImageSource::GetUserImage( const AccountId& account_id) { return GetUserImageInternal(account_id, -1); } UserImageSource::UserImageSource() {} UserImageSource::~UserImageSource() {} std::string UserImageSource::GetSource() { return chrome::kChromeUIUserImageHost; } void UserImageSource::StartDataRequest( const std::string& path, const content::WebContents::Getter& wc_getter, const content::URLDataSource::GotDataCallback& callback) { std::string email; int frame = -1; GURL url(chrome::kChromeUIUserImageURL + path); ParseRequest(url, &email, &frame); const AccountId account_id(AccountId::FromUserEmail(email)); callback.Run(GetUserImageInternal(account_id, frame)); } std::string UserImageSource::GetMimeType(const std::string& path) { // We need to explicitly return a mime type, otherwise if the user tries to // drag the image they get no extension. return "image/png"; } } // namespace chromeos