// Copyright (c) 2010 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.
// This implementation supposes a single extension thread and synchronized
// method invokation.
#include "chrome/browser/extensions/extension_idle_api.h"
#include <string>
#include "base/json/json_writer.h"
#include "base/message_loop.h"
#include "base/stl_util-inl.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 = 15; // Number of seconds between status checks
// when polling for active.
const int kMinThreshold = 15; // In seconds. Set >1 sec for security concerns.
const int kMaxThreshold = 60*60; // One hours, in seconds. Not set arbitrarily
// high for security concerns.
struct ExtensionIdlePollingData {
IdleState state;
double timestamp;
};
// Static variables shared between instances of polling.
static ExtensionIdlePollingData polling_data;
// Forward declaration of utility methods.
static const char* IdleStateToDescription(IdleState state);
static StringValue* CreateIdleValue(IdleState idle_state);
static int CheckThresholdBounds(int timeout);
static IdleState CalculateIdleStateAndUpdateTimestamp(int threshold);
static void CreateNewPollTask(Profile* profile);
static IdleState ThrottledCalculateIdleState(int threshold, Profile* profile);
// Internal object which watches for changes in the system idle state.
class ExtensionIdlePollingTask : public Task {
public:
explicit ExtensionIdlePollingTask(Profile* profile) : profile_(profile) {}
virtual ~ExtensionIdlePollingTask() {}
// Overridden from Task.
virtual void Run();
private:
Profile* profile_;
DISALLOW_COPY_AND_ASSIGN(ExtensionIdlePollingTask);
};
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;
}
IdleState CalculateIdleStateAndUpdateTimestamp(int threshold) {
polling_data.timestamp = base::Time::Now().ToDoubleT();
return CalculateIdleState(threshold);
}
void CreateNewPollTask(Profile* profile) {
MessageLoop::current()->PostDelayedTask(
FROM_HERE,
new ExtensionIdlePollingTask(profile),
kIdlePollInterval * 1000);
}
IdleState ThrottledCalculateIdleState(int threshold, Profile* profile) {
// If we are not active we should be polling.
if (IDLE_STATE_ACTIVE != polling_data.state)
return polling_data.state;
// Only allow one check per threshold.
double time_now = base::Time::Now().ToDoubleT();
double delta = time_now - polling_data.timestamp;
if (delta < threshold)
return polling_data.state;
// Update the new state with a poll. Note this updates time of last check.
polling_data.state = CalculateIdleStateAndUpdateTimestamp(threshold);
if (IDLE_STATE_ACTIVE != polling_data.state)
CreateNewPollTask(profile);
return polling_data.state;
}
void ExtensionIdlePollingTask::Run() {
IdleState state = CalculateIdleStateAndUpdateTimestamp(
kIdlePollInterval);
if (state != polling_data.state) {
polling_data.state = state;
// Inform of change if the current state is IDLE_STATE_ACTIVE.
if (IDLE_STATE_ACTIVE == polling_data.state)
ExtensionIdleEventRouter::OnIdleStateChange(profile_, state);
}
// Create a secondary polling task until an active state is reached.
if (IDLE_STATE_ACTIVE != polling_data.state)
CreateNewPollTask(profile_);
}
}; // namespace
bool ExtensionIdleQueryStateFunction::RunImpl() {
int threshold;
EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &threshold));
threshold = CheckThresholdBounds(threshold);
IdleState state = ThrottledCalculateIdleState(threshold, profile());
result_.reset(CreateIdleValue(state));
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());
}