1 // Copyright 2016 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/media/webrtc/tab_desktop_media_list.h"
6 
7 #include <utility>
8 
9 #include "base/bind.h"
10 #include "base/callback_helpers.h"
11 #include "base/hash/hash.h"
12 #include "base/task/post_task.h"
13 #include "base/task/thread_pool.h"
14 #include "chrome/browser/profiles/profile_manager.h"
15 #include "chrome/browser/ui/browser.h"
16 #include "chrome/browser/ui/browser_list.h"
17 #include "chrome/browser/ui/tabs/tab_strip_model.h"
18 #include "components/favicon/content/content_favicon_driver.h"
19 #include "content/public/browser/browser_thread.h"
20 #include "content/public/browser/render_frame_host.h"
21 #include "content/public/browser/render_process_host.h"
22 #include "media/base/video_util.h"
23 #include "third_party/skia/include/core/SkCanvas.h"
24 #include "third_party/skia/include/core/SkImage.h"
25 #include "ui/gfx/favicon_size.h"
26 #include "ui/gfx/image/image.h"
27 
28 using content::BrowserThread;
29 using content::DesktopMediaID;
30 
31 namespace {
32 
CreateEnclosedFaviconImage(gfx::Size size,const gfx::ImageSkia & favicon)33 gfx::ImageSkia CreateEnclosedFaviconImage(gfx::Size size,
34                                           const gfx::ImageSkia& favicon) {
35   DCHECK_GE(size.width(), gfx::kFaviconSize);
36   DCHECK_GE(size.height(), gfx::kFaviconSize);
37 
38   // Create a bitmap.
39   SkBitmap result;
40   result.allocN32Pixels(size.width(), size.height(), false);
41   SkCanvas canvas(result, SkSurfaceProps{});
42   canvas.clear(SK_ColorTRANSPARENT);
43 
44   // Draw the favicon image into the center of result image. If the favicon is
45   // too big, scale it down.
46   gfx::Size fill_size = favicon.size();
47   if (result.width() < favicon.width() || result.height() < favicon.height())
48     fill_size = media::ScaleSizeToFitWithinTarget(favicon.size(), size);
49 
50   gfx::Rect center_rect(result.width(), result.height());
51   center_rect.ClampToCenteredSize(fill_size);
52   SkRect dest_rect =
53       SkRect::MakeLTRB(center_rect.x(), center_rect.y(), center_rect.right(),
54                        center_rect.bottom());
55   canvas.drawBitmapRect(*favicon.bitmap(), dest_rect, nullptr);
56 
57   return gfx::ImageSkia::CreateFrom1xBitmap(result);
58 }
59 
60 // Update the list once per second.
61 const int kDefaultTabDesktopMediaListUpdatePeriod = 1000;
62 
63 }  // namespace
64 
TabDesktopMediaList()65 TabDesktopMediaList::TabDesktopMediaList()
66     : DesktopMediaListBase(base::TimeDelta::FromMilliseconds(
67           kDefaultTabDesktopMediaListUpdatePeriod)) {
68   type_ = DesktopMediaID::TYPE_WEB_CONTENTS;
69   thumbnail_task_runner_ = base::ThreadPool::CreateSequencedTaskRunner(
70       {base::MayBlock(), base::TaskPriority::USER_VISIBLE});
71 }
72 
~TabDesktopMediaList()73 TabDesktopMediaList::~TabDesktopMediaList() {}
74 
Refresh(bool update_thumnails)75 void TabDesktopMediaList::Refresh(bool update_thumnails) {
76   DCHECK(can_refresh());
77   DCHECK_CURRENTLY_ON(BrowserThread::UI);
78 
79   Profile* profile = ProfileManager::GetLastUsedProfileAllowedByPolicy();
80   if (!profile) {
81     OnRefreshComplete();
82     return;
83   }
84 
85   std::vector<Browser*> browsers;
86   for (auto* browser : *BrowserList::GetInstance()) {
87     if (browser->profile()->GetOriginalProfile() ==
88         profile->GetOriginalProfile()) {
89       browsers.push_back(browser);
90     }
91   }
92 
93   ImageHashesMap new_favicon_hashes;
94   std::vector<SourceDescription> sources;
95   std::map<base::TimeTicks, SourceDescription> tab_map;
96   std::vector<std::pair<DesktopMediaID, gfx::ImageSkia>> favicon_pairs;
97 
98   // Enumerate all tabs with their titles and favicons for a user profile.
99   for (auto* browser : browsers) {
100     const TabStripModel* tab_strip_model = browser->tab_strip_model();
101     DCHECK(tab_strip_model);
102 
103     for (int i = 0; i < tab_strip_model->count(); i++) {
104       // Create id for tab.
105       content::WebContents* contents = tab_strip_model->GetWebContentsAt(i);
106       DCHECK(contents);
107       content::RenderFrameHost* main_frame = contents->GetMainFrame();
108       DCHECK(main_frame);
109       DesktopMediaID media_id(
110           DesktopMediaID::TYPE_WEB_CONTENTS, DesktopMediaID::kNullId,
111           content::WebContentsMediaCaptureId(main_frame->GetProcess()->GetID(),
112                                              main_frame->GetRoutingID()));
113 
114       // Get tab's last active time stamp.
115       const base::TimeTicks t = contents->GetLastActiveTime();
116       tab_map.insert(
117           std::make_pair(t, SourceDescription(media_id, contents->GetTitle())));
118 
119       // Get favicon for tab.
120       favicon::FaviconDriver* favicon_driver =
121           favicon::ContentFaviconDriver::FromWebContents(contents);
122       if (!favicon_driver)
123         continue;
124 
125       gfx::Image favicon = favicon_driver->GetFavicon();
126       if (favicon.IsEmpty())
127         continue;
128 
129       // Only new or changed favicon need update.
130       new_favicon_hashes[media_id] = GetImageHash(favicon);
131       if (!favicon_hashes_.count(media_id) ||
132           (favicon_hashes_[media_id] != new_favicon_hashes[media_id])) {
133         gfx::ImageSkia image = favicon.AsImageSkia();
134         image.MakeThreadSafe();
135         favicon_pairs.push_back(std::make_pair(media_id, image));
136       }
137     }
138   }
139   favicon_hashes_ = new_favicon_hashes;
140 
141   // Sort tab sources by time. Most recent one first. Then update sources list.
142   for (auto it = tab_map.rbegin(); it != tab_map.rend(); ++it)
143     sources.push_back(it->second);
144 
145   UpdateSourcesList(sources);
146 
147   for (const auto& it : favicon_pairs) {
148     // Create a thumbail in a different thread and update the thumbnail in
149     // current thread.
150     base::PostTaskAndReplyWithResult(
151         thumbnail_task_runner_.get(), FROM_HERE,
152         base::BindOnce(&CreateEnclosedFaviconImage, thumbnail_size_, it.second),
153         base::BindOnce(&TabDesktopMediaList::UpdateSourceThumbnail,
154                        weak_factory_.GetWeakPtr(), it.first));
155   }
156 
157   // OnRefreshComplete() needs to be called after all calls for
158   // UpdateSourceThumbnail() have done. Therefore, a DoNothing task is posted to
159   // the same sequenced task runner that CreateEnlargedFaviconImag() is posted.
160   thumbnail_task_runner_.get()->PostTaskAndReply(
161       FROM_HERE, base::DoNothing(),
162       base::BindOnce(&TabDesktopMediaList::OnRefreshComplete,
163                      weak_factory_.GetWeakPtr()));
164 }
165