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