// Copyright (c) 2011 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/extensions/extension_idle_api.h" #include #include #include #include "base/bind.h" #include "base/callback.h" #include "base/json/json_writer.h" #include "base/message_loop.h" #include "base/stl_util.h" #include "base/task.h" #include "base/time.h" #include "chrome/browser/extensions/extension_event_router.h" #include "chrome/browser/extensions/extension_host.h" #include "chrome/browser/extensions/extension_idle_api_constants.h" #include "chrome/browser/extensions/extension_service.h" #include "chrome/browser/profiles/profile.h" #include "chrome/common/extensions/extension.h" #include "content/browser/renderer_host/render_view_host.h" namespace keys = extension_idle_api_constants; namespace { const int kIdlePollInterval = 1; // Number of seconds between status checks // when polling for active. const int kThrottleInterval = 1; // Number of seconds to throttle idle checks // for. Return the previously checked idle // state if the next check is faster than this const int kMinThreshold = 15; // In seconds. Set >1 sec for security concerns. const int kMaxThreshold = 4*60*60; // Four hours, in seconds. Not set // arbitrarily high for security concerns. const unsigned int kMaxCacheSize = 100; // Number of state queries to cache. // Calculates the error query interval has in respect to idle interval. // The error is defined as amount of the query interval that is not part of the // idle interval. double QueryErrorFromIdle(double idle_start, double idle_end, double query_start, double query_end) { return query_end - idle_end + std::max(0., idle_start - query_start); } // Internal class which is used to poll for changes in the system idle state. class ExtensionIdlePollingTask { public: explicit ExtensionIdlePollingTask(int threshold, IdleState last_state, Profile* profile) : threshold_(threshold), last_state_(last_state), profile_(profile) {} // Check if we're active; then restart the polling task. Do this till we are // are in active state. void CheckIdleState(); void IdleStateCallback(IdleState state); // Create a poll task to check for Idle state static void CreateNewPollTask(int threshold, IdleState state, Profile* profile); private: int threshold_; IdleState last_state_; Profile* profile_; static bool poll_task_running_; DISALLOW_COPY_AND_ASSIGN(ExtensionIdlePollingTask); }; // Implementation of ExtensionIdlePollingTask. bool ExtensionIdlePollingTask::poll_task_running_ = false; void ExtensionIdlePollingTask::IdleStateCallback(IdleState current_state) { // If we just came into an active state, notify the extension. if (IDLE_STATE_ACTIVE == current_state && last_state_ != current_state) ExtensionIdleEventRouter::OnIdleStateChange(profile_, current_state); ExtensionIdlePollingTask::poll_task_running_ = false; ExtensionIdleCache::UpdateCache(threshold_, current_state); // Startup another polling task as we exit. if (current_state != IDLE_STATE_ACTIVE) ExtensionIdlePollingTask::CreateNewPollTask(threshold_, current_state, profile_); // This instance won't be needed anymore. delete this; } void ExtensionIdlePollingTask::CheckIdleState() { CalculateIdleState(threshold_, base::Bind(&ExtensionIdlePollingTask::IdleStateCallback, base::Unretained(this))); } // static void ExtensionIdlePollingTask::CreateNewPollTask(int threshold, IdleState state, Profile* profile) { if (ExtensionIdlePollingTask::poll_task_running_) return; ExtensionIdlePollingTask::poll_task_running_ = true; MessageLoop::current()->PostDelayedTask( FROM_HERE, base::Bind(&ExtensionIdlePollingTask::CheckIdleState, base::Unretained( new ExtensionIdlePollingTask(threshold, state, profile))), kIdlePollInterval * 1000); } const char* IdleStateToDescription(IdleState state) { if (IDLE_STATE_ACTIVE == state) return keys::kStateActive; if (IDLE_STATE_IDLE == state) return keys::kStateIdle; return keys::kStateLocked; }; // Helper function for reporting the idle state. The lifetime of the object // returned is controlled by the caller. StringValue* CreateIdleValue(IdleState idle_state) { StringValue* result = new StringValue(IdleStateToDescription(idle_state)); return result; } int CheckThresholdBounds(int timeout) { if (timeout < kMinThreshold) return kMinThreshold; if (timeout > kMaxThreshold) return kMaxThreshold; return timeout; } }; // namespace void ExtensionIdleQueryStateFunction::IdleStateCallback(int threshold, IdleState state) { // If our state is not active, make sure we're running a polling task to check // for active state and report it when it changes to active. if (state != IDLE_STATE_ACTIVE) { ExtensionIdlePollingTask::CreateNewPollTask(threshold, state, profile_); } result_.reset(CreateIdleValue(state)); ExtensionIdleCache::UpdateCache(threshold, state); SendResponse(true); } bool ExtensionIdleQueryStateFunction::RunImpl() { int threshold; EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &threshold)); threshold = CheckThresholdBounds(threshold); IdleState state = ExtensionIdleCache::CalculateIdleState(threshold); if (state != IDLE_STATE_UNKNOWN) { result_.reset(CreateIdleValue(state)); SendResponse(true); return true; } CalculateIdleState(threshold, base::Bind(&ExtensionIdleQueryStateFunction::IdleStateCallback, this, threshold)); // Don't send the response, it'll be sent by our callback return true; } void ExtensionIdleEventRouter::OnIdleStateChange(Profile* profile, IdleState state) { // Prepare the single argument of the current state. ListValue args; args.Append(CreateIdleValue(state)); std::string json_args; base::JSONWriter::Write(&args, false, &json_args); profile->GetExtensionEventRouter()->DispatchEventToRenderers( keys::kOnStateChanged, json_args, profile, GURL()); } ExtensionIdleCache::CacheData ExtensionIdleCache::cached_data = {-1, -1, -1, -1}; IdleState ExtensionIdleCache::CalculateIdleState(int threshold) { return CalculateState(threshold, base::Time::Now().ToDoubleT()); } IdleState ExtensionIdleCache::CalculateState(int threshold, double now) { if (threshold < kMinThreshold) return IDLE_STATE_UNKNOWN; double threshold_moment = now - static_cast(threshold); double throttle_interval = static_cast(kThrottleInterval); // We test for IDEL_STATE_LOCKED first, because the result should be // independent of the data for idle and active state. If last state was // LOCKED and test for LOCKED is satisfied we should always return LOCKED. if (cached_data.latest_locked > 0 && now - cached_data.latest_locked < throttle_interval) return IDLE_STATE_LOCKED; // If thershold moment is beyond the moment after whih we are certain we have // been active, return active state. We allow kThrottleInterval error. if (cached_data.latest_known_active > 0 && threshold_moment - cached_data.latest_known_active < throttle_interval) return IDLE_STATE_ACTIVE; // If total error that query interval has in respect to last recorded idle // interval is less than kThrottleInterval, return IDLE state. // query interval is the interval [now, now - threshold] and the error is // defined as amount of query interval that is outside of idle interval. double error_from_idle = QueryErrorFromIdle(cached_data.idle_interval_start, cached_data.idle_interval_end, threshold_moment, now); if (cached_data.idle_interval_end > 0 && error_from_idle < throttle_interval) return IDLE_STATE_IDLE; return IDLE_STATE_UNKNOWN; } void ExtensionIdleCache::UpdateCache(int threshold, IdleState state) { Update(threshold, state, base::Time::Now().ToDoubleT()); } void ExtensionIdleCache::Update(int threshold, IdleState state, double now) { if (threshold < kMinThreshold) return; double threshold_moment = now - static_cast(threshold); switch (state) { case IDLE_STATE_IDLE: if (threshold_moment > cached_data.idle_interval_end) { // Cached and new interval don't overlap. We disregard the cached one. cached_data.idle_interval_start = threshold_moment; } else { // Cached and new interval overlap, so we can combine them. We set // the cached interval begining to less recent one. cached_data.idle_interval_start = std::min(cached_data.idle_interval_start, threshold_moment); } cached_data.idle_interval_end = now; // Reset data for LOCKED state, since the last query result is not // LOCKED. cached_data.latest_locked = -1; break; case IDLE_STATE_ACTIVE: if (threshold_moment > cached_data.latest_known_active) cached_data.latest_known_active = threshold_moment; // Reset data for LOCKED state, since the last query result is not // LOCKED. cached_data.latest_locked = -1; break; case IDLE_STATE_LOCKED: if (threshold_moment > cached_data.latest_locked) cached_data.latest_locked = now; break; default: return; } } int ExtensionIdleCache::get_min_threshold() { return kMinThreshold; } double ExtensionIdleCache::get_throttle_interval() { return static_cast(kThrottleInterval); }