1 // Copyright 2014 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 "extensions/browser/app_window/app_window_geometry_cache.h"
6
7 #include <stdint.h>
8
9 #include <utility>
10
11 #include "base/bind.h"
12 #include "base/stl_util.h"
13 #include "base/strings/string_number_conversions.h"
14 #include "components/keyed_service/content/browser_context_dependency_manager.h"
15 #include "extensions/browser/extension_prefs.h"
16 #include "extensions/browser/extension_prefs_factory.h"
17 #include "extensions/browser/extensions_browser_client.h"
18 #include "extensions/common/extension.h"
19
20 namespace {
21
22 // The timeout in milliseconds before we'll persist window geometry to the
23 // StateStore.
24 const int kSyncTimeoutMilliseconds = 1000;
25
26 } // namespace
27
28 namespace extensions {
29
AppWindowGeometryCache(content::BrowserContext * context,ExtensionPrefs * prefs)30 AppWindowGeometryCache::AppWindowGeometryCache(content::BrowserContext* context,
31 ExtensionPrefs* prefs)
32 : prefs_(prefs),
33 sync_delay_(base::TimeDelta::FromMilliseconds(kSyncTimeoutMilliseconds)) {
34 extension_registry_observer_.Add(ExtensionRegistry::Get(context));
35 }
36
~AppWindowGeometryCache()37 AppWindowGeometryCache::~AppWindowGeometryCache() {}
38
39 // static
Get(content::BrowserContext * context)40 AppWindowGeometryCache* AppWindowGeometryCache::Get(
41 content::BrowserContext* context) {
42 return Factory::GetForContext(context, true /* create */);
43 }
44
SaveGeometry(const std::string & extension_id,const std::string & window_id,const gfx::Rect & bounds,const gfx::Rect & screen_bounds,ui::WindowShowState window_state)45 void AppWindowGeometryCache::SaveGeometry(const std::string& extension_id,
46 const std::string& window_id,
47 const gfx::Rect& bounds,
48 const gfx::Rect& screen_bounds,
49 ui::WindowShowState window_state) {
50 ExtensionData& extension_data = cache_[extension_id];
51
52 // If we don't have any unsynced changes and this is a duplicate of what's
53 // already in the cache, just ignore it.
54 if (extension_data[window_id].bounds == bounds &&
55 extension_data[window_id].window_state == window_state &&
56 extension_data[window_id].screen_bounds == screen_bounds &&
57 !base::Contains(unsynced_extensions_, extension_id))
58 return;
59
60 base::Time now = base::Time::Now();
61
62 extension_data[window_id].bounds = bounds;
63 extension_data[window_id].screen_bounds = screen_bounds;
64 extension_data[window_id].window_state = window_state;
65 extension_data[window_id].last_change = now;
66
67 if (extension_data.size() > kMaxCachedWindows) {
68 auto oldest = extension_data.end();
69 // Too many windows in the cache, find the oldest one to remove.
70 for (auto it = extension_data.begin(); it != extension_data.end(); ++it) {
71 // Don't expunge the window that was just added.
72 if (it->first == window_id)
73 continue;
74
75 // If time is in the future, reset it to now to minimize weirdness.
76 if (it->second.last_change > now)
77 it->second.last_change = now;
78
79 if (oldest == extension_data.end() ||
80 it->second.last_change < oldest->second.last_change)
81 oldest = it;
82 }
83 extension_data.erase(oldest);
84 }
85
86 unsynced_extensions_.insert(extension_id);
87
88 // We don't use Reset() because the timer may not yet be running.
89 // (In that case Stop() is a no-op.)
90 sync_timer_.Stop();
91 sync_timer_.Start(
92 FROM_HERE, sync_delay_, this, &AppWindowGeometryCache::SyncToStorage);
93 }
94
SyncToStorage()95 void AppWindowGeometryCache::SyncToStorage() {
96 std::set<std::string> tosync;
97 tosync.swap(unsynced_extensions_);
98 for (auto it = tosync.cbegin(), eit = tosync.cend(); it != eit; ++it) {
99 const std::string& extension_id = *it;
100 const ExtensionData& extension_data = cache_[extension_id];
101
102 std::unique_ptr<base::DictionaryValue> dict(new base::DictionaryValue);
103 for (auto it = extension_data.cbegin(), eit = extension_data.cend();
104 it != eit; ++it) {
105 std::unique_ptr<base::DictionaryValue> value =
106 std::make_unique<base::DictionaryValue>();
107 const gfx::Rect& bounds = it->second.bounds;
108 const gfx::Rect& screen_bounds = it->second.screen_bounds;
109 DCHECK(!bounds.IsEmpty());
110 DCHECK(!screen_bounds.IsEmpty());
111 DCHECK(it->second.window_state != ui::SHOW_STATE_DEFAULT);
112 value->SetInteger("x", bounds.x());
113 value->SetInteger("y", bounds.y());
114 value->SetInteger("w", bounds.width());
115 value->SetInteger("h", bounds.height());
116 value->SetInteger("screen_bounds_x", screen_bounds.x());
117 value->SetInteger("screen_bounds_y", screen_bounds.y());
118 value->SetInteger("screen_bounds_w", screen_bounds.width());
119 value->SetInteger("screen_bounds_h", screen_bounds.height());
120 value->SetInteger("state", it->second.window_state);
121 value->SetString(
122 "ts", base::NumberToString(it->second.last_change.ToInternalValue()));
123 dict->SetWithoutPathExpansion(it->first, std::move(value));
124
125 for (auto& observer : observers_)
126 observer.OnGeometryCacheChanged(extension_id, it->first, bounds);
127 }
128
129 prefs_->SetGeometryCache(extension_id, std::move(dict));
130 }
131 }
132
GetGeometry(const std::string & extension_id,const std::string & window_id,gfx::Rect * bounds,gfx::Rect * screen_bounds,ui::WindowShowState * window_state)133 bool AppWindowGeometryCache::GetGeometry(const std::string& extension_id,
134 const std::string& window_id,
135 gfx::Rect* bounds,
136 gfx::Rect* screen_bounds,
137 ui::WindowShowState* window_state) {
138 std::map<std::string, ExtensionData>::const_iterator extension_data_it =
139 cache_.find(extension_id);
140
141 // Not in the map means loading data for the extension didn't finish yet or
142 // the cache was not constructed until after the extension was loaded.
143 // Attempt to load from sync to address the latter case.
144 if (extension_data_it == cache_.end()) {
145 LoadGeometryFromStorage(extension_id);
146 extension_data_it = cache_.find(extension_id);
147 DCHECK(extension_data_it != cache_.end());
148 }
149
150 auto window_data_it = extension_data_it->second.find(window_id);
151
152 if (window_data_it == extension_data_it->second.end())
153 return false;
154
155 const WindowData& window_data = window_data_it->second;
156
157 // Check for and do not return corrupt data.
158 if ((bounds && window_data.bounds.IsEmpty()) ||
159 (screen_bounds && window_data.screen_bounds.IsEmpty()) ||
160 (window_state && window_data.window_state == ui::SHOW_STATE_DEFAULT))
161 return false;
162
163 if (bounds)
164 *bounds = window_data.bounds;
165 if (screen_bounds)
166 *screen_bounds = window_data.screen_bounds;
167 if (window_state)
168 *window_state = window_data.window_state;
169 return true;
170 }
171
Shutdown()172 void AppWindowGeometryCache::Shutdown() { SyncToStorage(); }
173
WindowData()174 AppWindowGeometryCache::WindowData::WindowData()
175 : window_state(ui::SHOW_STATE_DEFAULT) {}
176
~WindowData()177 AppWindowGeometryCache::WindowData::~WindowData() {}
178
OnExtensionLoaded(content::BrowserContext * browser_context,const Extension * extension)179 void AppWindowGeometryCache::OnExtensionLoaded(
180 content::BrowserContext* browser_context,
181 const Extension* extension) {
182 LoadGeometryFromStorage(extension->id());
183 }
184
OnExtensionUnloaded(content::BrowserContext * browser_context,const Extension * extension,UnloadedExtensionReason reason)185 void AppWindowGeometryCache::OnExtensionUnloaded(
186 content::BrowserContext* browser_context,
187 const Extension* extension,
188 UnloadedExtensionReason reason) {
189 SyncToStorage();
190 cache_.erase(extension->id());
191 }
192
SetSyncDelayForTests(int timeout_ms)193 void AppWindowGeometryCache::SetSyncDelayForTests(int timeout_ms) {
194 sync_delay_ = base::TimeDelta::FromMilliseconds(timeout_ms);
195 }
196
LoadGeometryFromStorage(const std::string & extension_id)197 void AppWindowGeometryCache::LoadGeometryFromStorage(
198 const std::string& extension_id) {
199 ExtensionData& extension_data = cache_[extension_id];
200
201 const base::DictionaryValue* stored_windows =
202 prefs_->GetGeometryCache(extension_id);
203 if (!stored_windows)
204 return;
205
206 for (base::DictionaryValue::Iterator it(*stored_windows); !it.IsAtEnd();
207 it.Advance()) {
208 // If the cache already contains geometry for this window, don't
209 // overwrite that information since it is probably the result of an
210 // application starting up very quickly.
211 const std::string& window_id = it.key();
212 auto cached_window = extension_data.find(window_id);
213 if (cached_window == extension_data.end()) {
214 const base::DictionaryValue* stored_window;
215 if (it.value().GetAsDictionary(&stored_window)) {
216 WindowData& window_data = extension_data[it.key()];
217
218 int i;
219 if (stored_window->GetInteger("x", &i))
220 window_data.bounds.set_x(i);
221 if (stored_window->GetInteger("y", &i))
222 window_data.bounds.set_y(i);
223 if (stored_window->GetInteger("w", &i))
224 window_data.bounds.set_width(i);
225 if (stored_window->GetInteger("h", &i))
226 window_data.bounds.set_height(i);
227 if (stored_window->GetInteger("screen_bounds_x", &i))
228 window_data.screen_bounds.set_x(i);
229 if (stored_window->GetInteger("screen_bounds_y", &i))
230 window_data.screen_bounds.set_y(i);
231 if (stored_window->GetInteger("screen_bounds_w", &i))
232 window_data.screen_bounds.set_width(i);
233 if (stored_window->GetInteger("screen_bounds_h", &i))
234 window_data.screen_bounds.set_height(i);
235 if (stored_window->GetInteger("state", &i)) {
236 window_data.window_state = static_cast<ui::WindowShowState>(i);
237 }
238 std::string ts_as_string;
239 if (stored_window->GetString("ts", &ts_as_string)) {
240 int64_t ts;
241 if (base::StringToInt64(ts_as_string, &ts)) {
242 window_data.last_change = base::Time::FromInternalValue(ts);
243 }
244 }
245 }
246 }
247 }
248 }
249
250 ///////////////////////////////////////////////////////////////////////////////
251 // Factory boilerplate
252
253 // static
GetForContext(content::BrowserContext * context,bool create)254 AppWindowGeometryCache* AppWindowGeometryCache::Factory::GetForContext(
255 content::BrowserContext* context,
256 bool create) {
257 return static_cast<AppWindowGeometryCache*>(
258 GetInstance()->GetServiceForBrowserContext(context, create));
259 }
260
261 AppWindowGeometryCache::Factory*
GetInstance()262 AppWindowGeometryCache::Factory::GetInstance() {
263 return base::Singleton<AppWindowGeometryCache::Factory>::get();
264 }
265
Factory()266 AppWindowGeometryCache::Factory::Factory()
267 : BrowserContextKeyedServiceFactory(
268 "AppWindowGeometryCache",
269 BrowserContextDependencyManager::GetInstance()) {
270 DependsOn(ExtensionPrefsFactory::GetInstance());
271 }
272
~Factory()273 AppWindowGeometryCache::Factory::~Factory() {}
274
BuildServiceInstanceFor(content::BrowserContext * context) const275 KeyedService* AppWindowGeometryCache::Factory::BuildServiceInstanceFor(
276 content::BrowserContext* context) const {
277 return new AppWindowGeometryCache(context, ExtensionPrefs::Get(context));
278 }
279
280 content::BrowserContext*
GetBrowserContextToUse(content::BrowserContext * context) const281 AppWindowGeometryCache::Factory::GetBrowserContextToUse(
282 content::BrowserContext* context) const {
283 return ExtensionsBrowserClient::Get()->GetOriginalContext(context);
284 }
285
AddObserver(Observer * observer)286 void AppWindowGeometryCache::AddObserver(Observer* observer) {
287 observers_.AddObserver(observer);
288 }
289
RemoveObserver(Observer * observer)290 void AppWindowGeometryCache::RemoveObserver(Observer* observer) {
291 observers_.RemoveObserver(observer);
292 }
293
294 } // namespace extensions
295