root/apps/app_window_geometry_cache.cc

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. sync_delay_
  2. Get
  3. SaveGeometry
  4. SyncToStorage
  5. GetGeometry
  6. Shutdown
  7. Observe
  8. SetSyncDelayForTests
  9. LoadGeometryFromStorage
  10. OnExtensionUnloaded
  11. GetForContext
  12. GetInstance
  13. BuildServiceInstanceFor
  14. ServiceIsNULLWhileTesting
  15. GetBrowserContextToUse
  16. AddObserver
  17. RemoveObserver

// Copyright 2014 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 "apps/app_window_geometry_cache.h"

#include "base/bind.h"
#include "base/stl_util.h"
#include "base/strings/string_number_conversions.h"
#include "chrome/browser/chrome_notification_types.h"
#include "chrome/browser/profiles/incognito_helpers.h"
#include "chrome/browser/profiles/profile.h"
#include "components/keyed_service/content/browser_context_dependency_manager.h"
#include "content/public/browser/notification_service.h"
#include "content/public/browser/notification_types.h"
#include "extensions/browser/extension_prefs.h"
#include "extensions/browser/extension_prefs_factory.h"
#include "extensions/browser/extensions_browser_client.h"
#include "extensions/common/extension.h"

namespace {

// The timeout in milliseconds before we'll persist window geometry to the
// StateStore.
const int kSyncTimeoutMilliseconds = 1000;

}  // namespace

namespace apps {

AppWindowGeometryCache::AppWindowGeometryCache(
    Profile* profile,
    extensions::ExtensionPrefs* prefs)
    : prefs_(prefs),
      sync_delay_(base::TimeDelta::FromMilliseconds(kSyncTimeoutMilliseconds)) {
  registrar_.Add(this,
                 chrome::NOTIFICATION_EXTENSION_LOADED,
                 content::Source<Profile>(profile));
  registrar_.Add(this,
                 chrome::NOTIFICATION_EXTENSION_UNLOADED_DEPRECATED,
                 content::Source<Profile>(profile));
}

AppWindowGeometryCache::~AppWindowGeometryCache() {}

// static
AppWindowGeometryCache* AppWindowGeometryCache::Get(
    content::BrowserContext* context) {
  return Factory::GetForContext(context, true /* create */);
}

void AppWindowGeometryCache::SaveGeometry(const std::string& extension_id,
                                          const std::string& window_id,
                                          const gfx::Rect& bounds,
                                          const gfx::Rect& screen_bounds,
                                          ui::WindowShowState window_state) {
  ExtensionData& extension_data = cache_[extension_id];

  // If we don't have any unsynced changes and this is a duplicate of what's
  // already in the cache, just ignore it.
  if (extension_data[window_id].bounds == bounds &&
      extension_data[window_id].window_state == window_state &&
      extension_data[window_id].screen_bounds == screen_bounds &&
      !ContainsKey(unsynced_extensions_, extension_id))
    return;

  base::Time now = base::Time::Now();

  extension_data[window_id].bounds = bounds;
  extension_data[window_id].screen_bounds = screen_bounds;
  extension_data[window_id].window_state = window_state;
  extension_data[window_id].last_change = now;

  if (extension_data.size() > kMaxCachedWindows) {
    ExtensionData::iterator oldest = extension_data.end();
    // Too many windows in the cache, find the oldest one to remove.
    for (ExtensionData::iterator it = extension_data.begin();
         it != extension_data.end();
         ++it) {
      // Don't expunge the window that was just added.
      if (it->first == window_id)
        continue;

      // If time is in the future, reset it to now to minimize weirdness.
      if (it->second.last_change > now)
        it->second.last_change = now;

      if (oldest == extension_data.end() ||
          it->second.last_change < oldest->second.last_change)
        oldest = it;
    }
    extension_data.erase(oldest);
  }

  unsynced_extensions_.insert(extension_id);

  // We don't use Reset() because the timer may not yet be running.
  // (In that case Stop() is a no-op.)
  sync_timer_.Stop();
  sync_timer_.Start(
      FROM_HERE, sync_delay_, this, &AppWindowGeometryCache::SyncToStorage);
}

void AppWindowGeometryCache::SyncToStorage() {
  std::set<std::string> tosync;
  tosync.swap(unsynced_extensions_);
  for (std::set<std::string>::const_iterator it = tosync.begin(),
                                             eit = tosync.end();
       it != eit;
       ++it) {
    const std::string& extension_id = *it;
    const ExtensionData& extension_data = cache_[extension_id];

    scoped_ptr<base::DictionaryValue> dict(new base::DictionaryValue);
    for (ExtensionData::const_iterator it = extension_data.begin(),
                                       eit = extension_data.end();
         it != eit;
         ++it) {
      base::DictionaryValue* value = new base::DictionaryValue;
      const gfx::Rect& bounds = it->second.bounds;
      const gfx::Rect& screen_bounds = it->second.screen_bounds;
      DCHECK(!bounds.IsEmpty());
      DCHECK(!screen_bounds.IsEmpty());
      DCHECK(it->second.window_state != ui::SHOW_STATE_DEFAULT);
      value->SetInteger("x", bounds.x());
      value->SetInteger("y", bounds.y());
      value->SetInteger("w", bounds.width());
      value->SetInteger("h", bounds.height());
      value->SetInteger("screen_bounds_x", screen_bounds.x());
      value->SetInteger("screen_bounds_y", screen_bounds.y());
      value->SetInteger("screen_bounds_w", screen_bounds.width());
      value->SetInteger("screen_bounds_h", screen_bounds.height());
      value->SetInteger("state", it->second.window_state);
      value->SetString(
          "ts", base::Int64ToString(it->second.last_change.ToInternalValue()));
      dict->SetWithoutPathExpansion(it->first, value);

      FOR_EACH_OBSERVER(
          Observer,
          observers_,
          OnGeometryCacheChanged(extension_id, it->first, bounds));
    }

    prefs_->SetGeometryCache(extension_id, dict.Pass());
  }
}

bool AppWindowGeometryCache::GetGeometry(const std::string& extension_id,
                                         const std::string& window_id,
                                         gfx::Rect* bounds,
                                         gfx::Rect* screen_bounds,
                                         ui::WindowShowState* window_state) {

  std::map<std::string, ExtensionData>::const_iterator extension_data_it =
      cache_.find(extension_id);

  // Not in the map means loading data for the extension didn't finish yet or
  // the cache was not constructed until after the extension was loaded.
  // Attempt to load from sync to address the latter case.
  if (extension_data_it == cache_.end()) {
    LoadGeometryFromStorage(extension_id);
    extension_data_it = cache_.find(extension_id);
    DCHECK(extension_data_it != cache_.end());
  }

  ExtensionData::const_iterator window_data_it =
      extension_data_it->second.find(window_id);

  if (window_data_it == extension_data_it->second.end())
    return false;

  const WindowData& window_data = window_data_it->second;

  // Check for and do not return corrupt data.
  if ((bounds && window_data.bounds.IsEmpty()) ||
      (screen_bounds && window_data.screen_bounds.IsEmpty()) ||
      (window_state && window_data.window_state == ui::SHOW_STATE_DEFAULT))
    return false;

  if (bounds)
    *bounds = window_data.bounds;
  if (screen_bounds)
    *screen_bounds = window_data.screen_bounds;
  if (window_state)
    *window_state = window_data.window_state;
  return true;
}

void AppWindowGeometryCache::Shutdown() { SyncToStorage(); }

AppWindowGeometryCache::WindowData::WindowData()
    : window_state(ui::SHOW_STATE_DEFAULT) {}

AppWindowGeometryCache::WindowData::~WindowData() {}

void AppWindowGeometryCache::Observe(
    int type,
    const content::NotificationSource& source,
    const content::NotificationDetails& details) {
  switch (type) {
    case chrome::NOTIFICATION_EXTENSION_LOADED: {
      std::string extension_id =
          content::Details<const extensions::Extension>(details).ptr()->id();
      LoadGeometryFromStorage(extension_id);
      break;
    }
    case chrome::NOTIFICATION_EXTENSION_UNLOADED_DEPRECATED: {
      std::string extension_id =
          content::Details<const extensions::UnloadedExtensionInfo>(details)
              .ptr()
              ->extension->id();
      OnExtensionUnloaded(extension_id);
      break;
    }
    default:
      NOTREACHED();
      return;
  }
}

void AppWindowGeometryCache::SetSyncDelayForTests(int timeout_ms) {
  sync_delay_ = base::TimeDelta::FromMilliseconds(timeout_ms);
}

void AppWindowGeometryCache::LoadGeometryFromStorage(
    const std::string& extension_id) {
  ExtensionData& extension_data = cache_[extension_id];

  const base::DictionaryValue* stored_windows =
      prefs_->GetGeometryCache(extension_id);
  if (!stored_windows)
    return;

  for (base::DictionaryValue::Iterator it(*stored_windows); !it.IsAtEnd();
       it.Advance()) {
    // If the cache already contains geometry for this window, don't
    // overwrite that information since it is probably the result of an
    // application starting up very quickly.
    const std::string& window_id = it.key();
    ExtensionData::iterator cached_window = extension_data.find(window_id);
    if (cached_window == extension_data.end()) {
      const base::DictionaryValue* stored_window;
      if (it.value().GetAsDictionary(&stored_window)) {
        WindowData& window_data = extension_data[it.key()];

        int i;
        if (stored_window->GetInteger("x", &i))
          window_data.bounds.set_x(i);
        if (stored_window->GetInteger("y", &i))
          window_data.bounds.set_y(i);
        if (stored_window->GetInteger("w", &i))
          window_data.bounds.set_width(i);
        if (stored_window->GetInteger("h", &i))
          window_data.bounds.set_height(i);
        if (stored_window->GetInteger("screen_bounds_x", &i))
          window_data.screen_bounds.set_x(i);
        if (stored_window->GetInteger("screen_bounds_y", &i))
          window_data.screen_bounds.set_y(i);
        if (stored_window->GetInteger("screen_bounds_w", &i))
          window_data.screen_bounds.set_width(i);
        if (stored_window->GetInteger("screen_bounds_h", &i))
          window_data.screen_bounds.set_height(i);
        if (stored_window->GetInteger("state", &i)) {
          window_data.window_state = static_cast<ui::WindowShowState>(i);
        }
        std::string ts_as_string;
        if (stored_window->GetString("ts", &ts_as_string)) {
          int64 ts;
          if (base::StringToInt64(ts_as_string, &ts)) {
            window_data.last_change = base::Time::FromInternalValue(ts);
          }
        }
      }
    }
  }
}

void AppWindowGeometryCache::OnExtensionUnloaded(
    const std::string& extension_id) {
  SyncToStorage();
  cache_.erase(extension_id);
}

///////////////////////////////////////////////////////////////////////////////
// Factory boilerplate

// static
AppWindowGeometryCache* AppWindowGeometryCache::Factory::GetForContext(
    content::BrowserContext* context,
    bool create) {
  return static_cast<AppWindowGeometryCache*>(
      GetInstance()->GetServiceForBrowserContext(context, create));
}

AppWindowGeometryCache::Factory*
AppWindowGeometryCache::Factory::GetInstance() {
  return Singleton<AppWindowGeometryCache::Factory>::get();
}

AppWindowGeometryCache::Factory::Factory()
    : BrowserContextKeyedServiceFactory(
          "AppWindowGeometryCache",
          BrowserContextDependencyManager::GetInstance()) {
  DependsOn(extensions::ExtensionPrefsFactory::GetInstance());
}

AppWindowGeometryCache::Factory::~Factory() {}

KeyedService* AppWindowGeometryCache::Factory::BuildServiceInstanceFor(
    content::BrowserContext* context) const {
  Profile* profile = Profile::FromBrowserContext(context);
  return new AppWindowGeometryCache(profile,
                                    extensions::ExtensionPrefs::Get(profile));
}

bool AppWindowGeometryCache::Factory::ServiceIsNULLWhileTesting() const {
  return false;
}

content::BrowserContext*
AppWindowGeometryCache::Factory::GetBrowserContextToUse(
    content::BrowserContext* context) const {
  return extensions::ExtensionsBrowserClient::Get()->GetOriginalContext(
      context);
}

void AppWindowGeometryCache::AddObserver(Observer* observer) {
  observers_.AddObserver(observer);
}

void AppWindowGeometryCache::RemoveObserver(Observer* observer) {
  observers_.RemoveObserver(observer);
}

}  // namespace apps

/* [<][>][^][v][top][bottom][index][help] */