text:
using content::BrowserThread;
using content::DomOperationNotificationDetails;
using content::DownloadItem;
using content::DownloadManager;
using content::NavigationController;
using content::RenderViewHost;
using content::WebContents;

// Holds onto start and stop timestamps for a particular tab
class InitialLoadObserver::TabTime {
 public:
  explicit TabTime(base::TimeTicks started)
      : load_start_time_(started) {
  }
  void set_stop_time(base::TimeTicks stopped) {
    load_stop_time_ = stopped;
  }
  base::TimeTicks stop_time() const {
    return load_stop_time_;
  }
  base::TimeTicks start_time() const {
    return load_start_time_;
  }
 private:
  base::TimeTicks load_start_time_;
  base::TimeTicks load_stop_time_;
};

InitialLoadObserver::InitialLoadObserver(size_t tab_count,
                                         AutomationProvider* automation)
    : automation_(automation->AsWeakPtr()),
      crashed_tab_count_(0),
      outstanding_tab_count_(tab_count),
      init_time_(base::TimeTicks::Now()) {
  if (outstanding_tab_count_ > 0) {
    registrar_.Add(this, content::NOTIFICATION_LOAD_START,
                   content::NotificationService::AllSources());
    registrar_.Add(this, content::NOTIFICATION_LOAD_STOP,
                   content::NotificationService::AllSources());
    registrar_.Add(this, content::NOTIFICATION_RENDERER_PROCESS_CLOSED,
                   content::NotificationService::AllSources());
  }
}

InitialLoadObserver::~InitialLoadObserver() {
}

void InitialLoadObserver::Observe(int type,
                                  const content::NotificationSource& source,
                                  const content::NotificationDetails& details) {
  if (type == content::NOTIFICATION_LOAD_START) {
    if (outstanding_tab_count_ > loading_tabs_.size())
      loading_tabs_.insert(TabTimeMap::value_type(
          source.map_key(),
          TabTime(base::TimeTicks::Now())));
  } else if (type == content::NOTIFICATION_LOAD_STOP) {
    if (outstanding_tab_count_ > finished_tabs_.size()) {
      TabTimeMap::iterator iter = loading_tabs_.find(source.map_key());
      if (iter != loading_tabs_.end()) {
        finished_tabs_.insert(source.map_key());
        iter->second.set_stop_time(base::TimeTicks::Now());
      }
    }
  } else if (type == content::NOTIFICATION_RENDERER_PROCESS_CLOSED) {
    base::TerminationStatus status =
        content::Details<content::RenderProcessHost::RendererClosedDetails>(
            details)->status;
    switch (status) {
      case base::TERMINATION_STATUS_NORMAL_TERMINATION:
        break;

      case base::TERMINATION_STATUS_ABNORMAL_TERMINATION:
      case base::TERMINATION_STATUS_PROCESS_WAS_KILLED:
      case base::TERMINATION_STATUS_PROCESS_CRASHED:
        crashed_tab_count_++;
        break;

      case base::TERMINATION_STATUS_STILL_RUNNING:
        LOG(ERROR) << "Got RENDERER_PROCESS_CLOSED notification, "
                   << "but the process is still running. We may miss further "
                   << "crash notification, resulting in hangs.";
        break;

      default:
        LOG(ERROR) << "Unhandled termination status " << status;
        NOTREACHED();
        break;
    }
  } else {
    NOTREACHED();
  }

  if (finished_tabs_.size() + crashed_tab_count_ >= outstanding_tab_count_)
    ConditionMet();
}

DictionaryValue* InitialLoadObserver::GetTimingInformation() const {
  ListValue* items = new ListValue;
  for (TabTimeMap::const_iterator it = loading_tabs_.begin();
       it != loading_tabs_.end();
       ++it) {
    DictionaryValue* item = new DictionaryValue;
    base::TimeDelta delta_start = it->second.start_time() - init_time_;

    item->SetDouble("load_start_ms", delta_start.InMillisecondsF());
    if (it->second.stop_time().is_null()) {
      item->Set("load_stop_ms", Value::CreateNullValue());
    } else {
      base::TimeDelta delta_stop = it->second.stop_time() - init_time_;
      item->SetDouble("load_stop_ms", delta_stop.InMillisecondsF());
    }
    items->Append(item);
  }
  DictionaryValue* return_value = new DictionaryValue;
  return_value->Set("tabs", items);
  return return_value;
}

void InitialLoadObserver::ConditionMet() {
  registrar_.RemoveAll();
  if (automation_.get())
    automation_->OnInitialTabLoadsComplete();
}

NewTabUILoadObserver::NewTabUILoadObserver(AutomationProvider* automation,
                                           Profile* profile)
    : automation_(automation->AsWeakPtr()) {
  registrar_.Add(this, chrome::NOTIFICATION_INITIAL_NEW_TAB_UI_LOAD,
                 content::Source<Profile>(profile));
}

NewTabUILoadObserver::~NewTabUILoadObserver() {
}

void NewTabUILoadObserver::Observe(int type,
                                   const content::NotificationSource& source,
                                   const content::NotificationDetails& details) {
  if (type == chrome::NOTIFICATION_INITIAL_NEW_TAB_UI_LOAD) {
    content::Details<int> load_time(details);
    if (automation_.get()) {
      automation_->Send(
          new AutomationMsg_InitialNewTabUILoadComplete(*load_time.ptr()));
    }
  } else {
    NOTREACHED();
  }
}

NavigationControllerRestoredObserver::NavigationControllerRestoredObserver(
    AutomationProvider* automation,
    NavigationController* controller,
    IPC::Message* reply_message)
    : automation_(automation->AsWeakPtr()),
      controller_(controller),
      reply_message_(reply_message) {
  if (FinishedRestoring()) {
    SendDone();
  } else {
    registrar_.Add(this, content::NOTIFICATION_LOAD_STOP,
                   content::NotificationService::AllSources());
  }
}

NavigationControllerRestoredObserver::~NavigationControllerRestoredObserver() {
}

void NavigationControllerRestoredObserver::Observe(
    int type, const content::NotificationSource& source,
    const content::NotificationDetails& details) {
  if (FinishedRestoring()) {
    registrar_.RemoveAll();
    SendDone();
  }
}

bool NavigationControllerRestoredObserver::FinishedRestoring() {
  return (!controller_->NeedsReload() && !controller_->GetPendingEntry() &&
          !controller_->GetWebContents()->IsLoading());
}

void NavigationControllerRestoredObserver::SendDone() {
  if (automation_.get()) {
    AutomationJSONReply(automation_.get(), reply_message_.release())
        .SendSuccess(NULL);
  }
  delete this;
}

NavigationNotificationObserver::NavigationNotificationObserver(
    NavigationController* controller,
    AutomationProvider* automation,
    IPC::Message* reply_message,
    int number_of_navigations,
    bool include_current_navigation,
    bool use_json_interface)
    : automation_(automation->AsWeakPtr()),
      reply_message_(reply_message),
      controller_(controller),
      navigations_remaining_(number_of_navigations),
      navigation_started_(false),
      use_json_interface_(use_json_interface) {
  if (number_of_navigations == 0) {
    ConditionMet(AUTOMATION_MSG_NAVIGATION_SUCCESS);
    return;
  }
  DCHECK_LT(0, navigations_remaining_);
  content::Source<NavigationController> source(controller_);
  registrar_.Add(this, content::NOTIFICATION_NAV_ENTRY_COMMITTED, source);
  registrar_.Add(this, content::NOTIFICATION_LOAD_START, source);
  registrar_.Add(this, content::NOTIFICATION_LOAD_STOP, source);
  registrar_.Add(this, chrome::NOTIFICATION_AUTH_NEEDED, source);
  registrar_.Add(this, chrome::NOTIFICATION_AUTH_SUPPLIED, source);
  registrar_.Add(this, chrome::NOTIFICATION_AUTH_CANCELLED, source);
  registrar_.Add(this, chrome::NOTIFICATION_APP_MODAL_DIALOG_SHOWN,
                 content::NotificationService::AllSources());

  if (include_current_navigation && controller->GetWebContents()->IsLoading())
    navigation_started_ = true;
}

NavigationNotificationObserver::~NavigationNotificationObserver() {
}

void NavigationNotificationObserver::Observe(
    int type, const content::NotificationSource& source,
    const content::NotificationDetails& details) {
  if (!automation_.get()) {
    delete this;
    return;
  }

  // We listen for 2 events to determine when the navigation started because:
  // - when this is used by the WaitForNavigation method, we might be invoked
  // afer the load has started (but not after the entry was committed, as
  // WaitForNavigation compares times of the last navigation).
  // - when this is used with a page requiring authentication, we will not get
  // a chrome::NAV_ENTRY_COMMITTED until after we authenticate, so
  // we need the chrome::LOAD_START.
  if (type == content::NOTIFICATION_NAV_ENTRY_COMMITTED ||
      type == content::NOTIFICATION_LOAD_START) {
    navigation_started_ = true;
  } else if (type == content::NOTIFICATION_LOAD_STOP) {
    if (navigation_started_) {
      navigation_started_ = false;
      if (--navigations_remaining_ == 0)
        ConditionMet(AUTOMATION_MSG_NAVIGATION_SUCCESS);
    }
  } else if (type == chrome::NOTIFICATION_AUTH_SUPPLIED ||
             type == chrome::NOTIFICATION_AUTH_CANCELLED) {
    // Treat this as if navigation started again, since load start/stop don't
    // occur while authentication is ongoing.
    navigation_started_ = true;
  } else if (type == chrome::NOTIFICATION_AUTH_NEEDED) {
    // Respond that authentication is needed.
    navigation_started_ = false;
    ConditionMet(AUTOMATION_MSG_NAVIGATION_AUTH_NEEDED);
  } else if (type == chrome::NOTIFICATION_APP_MODAL_DIALOG_SHOWN) {
    ConditionMet(AUTOMATION_MSG_NAVIGATION_BLOCKED_BY_MODAL_DIALOG);
  } else {
    NOTREACHED();
  }
}

void NavigationNotificationObserver::ConditionMet(
    AutomationMsg_NavigationResponseValues navigation_result) {
  if (automation_.get()) {
    if (use_json_interface_) {
      if (navigation_result == AUTOMATION_MSG_NAVIGATION_SUCCESS) {
        DictionaryValue dict;
        dict.SetInteger("result", navigation_result);
        AutomationJSONReply(automation_.get(), reply_message_.release())
            .SendSuccess(&dict);
      } else {
        AutomationJSONReply(automation_.get(), reply_message_.release())
            .SendError(base::StringPrintf(
                 "Navigation failed with error code=%d.", navigation_result));
      }
    } else {
      IPC::ParamTraits<int>::Write(
          reply_message_.get(), navigation_result);
      automation_->Send(reply_message_.release());
    }
  }

  delete this;
}

TabStripNotificationObserver::TabStripNotificationObserver(
    int notification, AutomationProvider* automation)
    : automation_(automation->AsWeakPtr()),
      notification_(notification) {
  registrar_.Add(this, notification_,
                 content::NotificationService::AllSources());
}

TabStripNotificationObserver::~TabStripNotificationObserver() {
}

void TabStripNotificationObserver::Observe(
    int type,
    const content::NotificationSource& source,
    const content::NotificationDetails& details) {
  DCHECK_EQ(notification_, type);
  if (type == chrome::NOTIFICATION_TAB_PARENTED) {
    ObserveTab(&content::Source<content::WebContents>(source)->GetController());
  } else if (type == content::NOTIFICATION_WEB_CONTENTS_DESTROYED) {
    ObserveTab(&content::Source<content::WebContents>(source)->GetController());
  } else {
    ObserveTab(content::Source<NavigationController>(source).ptr());
  }
  delete this;
}

TabAppendedNotificationObserver::TabAppendedNotificationObserver(
    Browser* parent,
    AutomationProvider* automation,
    IPC::Message* reply_message,
    bool use_json_interface)
    : TabStripNotificationObserver(chrome::NOTIFICATION_TAB_PARENTED,
                                   automation),
      parent_(parent),
      reply_message_(reply_message),
      use_json_interface_(use_json_interface) {
}

TabAppendedNotificationObserver::~TabAppendedNotificationObserver() {}

void TabAppendedNotificationObserver::ObserveTab(
    NavigationController* controller) {
  if (!automation_.get() || !reply_message_.get())
    return;

  if (automation_->GetIndexForNavigationController(controller, parent_) ==
      TabStripModel::kNoTab) {
    // This tab notification doesn't belong to the parent_.
    return;
  }

  new NavigationNotificationObserver(controller,
                                     automation_.get(),
                                     reply_message_.release(),
                                     1,
                                     false,
                                     use_json_interface_);
}

IPC::Message* TabAppendedNotificationObserver::ReleaseReply() {
  return reply_message_.release();
}

TabClosedNotificationObserver::TabClosedNotificationObserver(
    AutomationProvider* automation,
    bool wait_until_closed,
    IPC::Message* reply_message,
    bool use_json_interface)
    : TabStripNotificationObserver((wait_until_closed ?
          static_cast<int>(content::NOTIFICATION_WEB_CONTENTS_DESTROYED) :
          static_cast<int>(chrome::NOTIFICATION_TAB_CLOSING)), automation),
      reply_message_(reply_message),
      use_json_interface_(use_json_interface),
      for_browser_command_(false) {
}

TabClosedNotificationObserver::~TabClosedNotificationObserver() {}

void TabClosedNotificationObserver::ObserveTab(
    NavigationController* controller) {
  if (!automation_.get())
    return;

  if (use_json_interface_) {
    AutomationJSONReply(automation_.get(), reply_message_.release())
        .SendSuccess(NULL);
  } else {
    if (for_browser_command_) {
      AutomationMsg_WindowExecuteCommand::WriteReplyParams(reply_message_.get(),
                                                           true);
    } else {
      AutomationMsg_CloseTab::WriteReplyParams(reply_message_.get(), true);
    }
    automation_->Send(reply_message_.release());
  }
}

void TabClosedNotificationObserver::set_for_browser_command(
    bool for_browser_command) {
  for_browser_command_ = for_browser_command;
}

TabCountChangeObserver::TabCountChangeObserver(AutomationProvider* automation,
                                               Browser* browser,
                                               IPC::Message* reply_message,
                                               int target_tab_count)
    : automation_(automation->AsWeakPtr()),
      reply_message_(reply_message),
      tab_strip_model_(browser->tab_strip_model()),
      target_tab_count_(target_tab_count) {
  tab_strip_model_->AddObserver(this);
  CheckTabCount();
}

TabCountChangeObserver::~TabCountChangeObserver() {
  tab_strip_model_->RemoveObserver(this);
}

void TabCountChangeObserver::TabInsertedAt(WebContents* contents,
                                           int index,
                                           bool foreground) {
  CheckTabCount();
}

void TabCountChangeObserver::TabDetachedAt(WebContents* contents,
                                           int index) {
  CheckTabCount();
}

void TabCountChangeObserver::TabStripModelDeleted() {
  if (automation_.get()) {
    AutomationMsg_WaitForTabCountToBecome::WriteReplyParams(
        reply_message_.get(), false);
    automation_->Send(reply_message_.release());
  }

  delete this;
}

void TabCountChangeObserver::CheckTabCount() {
  if (tab_strip_model_->count() != target_tab_count_)
    return;

  if (automation_.get()) {
    AutomationMsg_WaitForTabCountToBecome::WriteReplyParams(
        reply_message_.get(), true);
    automation_->Send(reply_message_.release());
  }

  delete this;
}

bool DidExtensionViewsStopLoading(ExtensionProcessManager* manager) {
  ExtensionProcessManager::ViewSet all_views = manager->GetAllViews();
  for (ExtensionProcessManager::ViewSet::const_iterator iter =
           all_views.begin();
       iter != all_views.end(); ++iter) {
    if ((*iter)->IsLoading())
      return false;
  }
  return true;
}

ExtensionUninstallObserver::ExtensionUninstallObserver(
    AutomationProvider* automation,
    IPC::Message* reply_message,
    const std::string& id)
    : automation_(automation->AsWeakPtr()),
      reply_message_(reply_message),
      id_(id) {
  registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_UNINSTALLED,
                 content::NotificationService::AllSources());
  registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_UNINSTALL_NOT_ALLOWED,
                 content::NotificationService::AllSources());
}

ExtensionUninstallObserver::~ExtensionUninstallObserver() {
}

void ExtensionUninstallObserver::Observe(
    int type,
    const content::NotificationSource& source,
    const content::NotificationDetails& details) {
  if (!automation_.get()) {
    delete this;
    return;
  }

  switch (type) {
    case chrome::NOTIFICATION_EXTENSION_UNINSTALLED: {
      if (id_ == content::Details<extensions::Extension>(details).ptr()->id()) {
        scoped_ptr<DictionaryValue> return_value(new DictionaryValue);
        return_value->SetBoolean("success", true);
        AutomationJSONReply(automation_.get(), reply_message_.release())
            .SendSuccess(return_value.get());
        delete this;
        return;
      }
      break;
    }

    case chrome::NOTIFICATION_EXTENSION_UNINSTALL_NOT_ALLOWED: {
      const extensions::Extension* extension =
          content::Details<extensions::Extension>(details).ptr();
      if (id_ == extension->id()) {
        scoped_ptr<DictionaryValue> return_value(new DictionaryValue);
        return_value->SetBoolean("success", false);
        AutomationJSONReply(automation_.get(), reply_message_.release())
            .SendSuccess(return_value.get());
        delete this;
        return;
      }
      break;
    }

    default:
      NOTREACHED();
  }
}

ExtensionReadyNotificationObserver::ExtensionReadyNotificationObserver(
    ExtensionProcessManager* manager, ExtensionService* service,
    AutomationProvider* automation, IPC::Message* reply_message)
    : manager_(manager),
      service_(service),
      automation_(automation->AsWeakPtr()),
      reply_message_(reply_message),
      extension_(NULL) {
  Init();
}

ExtensionReadyNotificationObserver::~ExtensionReadyNotificationObserver() {
}

void ExtensionReadyNotificationObserver::Init() {
  registrar_.Add(this, content::NOTIFICATION_LOAD_STOP,
                 content::NotificationService::AllSources());
  registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_LOADED,
                 content::NotificationService::AllSources());
  registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_LOAD_ERROR,
                 content::NotificationService::AllSources());
  registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_INSTALL_ERROR,
                 content::NotificationService::AllSources());
  registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_UPDATE_DISABLED,
                 content::NotificationService::AllSources());
}

void ExtensionReadyNotificationObserver::Observe(
    int type, const content::NotificationSource& source,
    const content::NotificationDetails& details) {
  if (!automation_.get()) {
    delete this;
    return;
  }

  switch (type) {
    case content::NOTIFICATION_LOAD_STOP:
      // Only continue on with this method if our extension has been loaded
      // and all the extension views have stopped loading.
      if (!extension_ || !DidExtensionViewsStopLoading(manager_))
        return;
      break;
    case chrome::NOTIFICATION_EXTENSION_LOADED: {
      const extensions::Extension* loaded_extension =
          content::Details<const extensions::Extension>(details).ptr();
      // Only track an internal or unpacked extension load.
      extensions::Manifest::Location location = loaded_extension->location();
      if (location != extensions::Manifest::INTERNAL &&
          !extensions::Manifest::IsUnpackedLocation(location))
        return;
      extension_ = loaded_extension;
      if (!DidExtensionViewsStopLoading(manager_))
        return;
      // For some reason, the background extension view is not yet
      // created at this point so just checking whether all extension views
      // are loaded is not sufficient. If background page is not ready,
      // we wait for NOTIFICATION_LOAD_STOP.
      if (!service_->IsBackgroundPageReady(extension_))
        return;
      break;
    }
    case chrome::NOTIFICATION_EXTENSION_INSTALL_ERROR:
    case chrome::NOTIFICATION_EXTENSION_LOAD_ERROR:
    case chrome::NOTIFICATION_EXTENSION_UPDATE_DISABLED:
      break;
    default:
      NOTREACHED();
      break;
  }

  AutomationJSONReply reply(automation_.get(), reply_message_.release());
  if (extension_) {
    DictionaryValue dict;
    dict.SetString("id", extension_->id());
    reply.SendSuccess(&dict);
  } else {
    reply.SendError("Extension could not be installed");
  }
  delete this;
}

ExtensionUnloadNotificationObserver::ExtensionUnloadNotificationObserver()
    : did_receive_unload_notification_(false) {
  registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_UNLOADED,
                 content::NotificationService::AllSources());
}

ExtensionUnloadNotificationObserver::~ExtensionUnloadNotificationObserver() {
}

void ExtensionUnloadNotificationObserver::Observe(
    int type, const content::NotificationSource& source,
    const content::NotificationDetails& details) {
  if (type == chrome::NOTIFICATION_EXTENSION_UNLOADED) {
    did_receive_unload_notification_ = true;
  } else {
    NOTREACHED();
  }
}

ExtensionsUpdatedObserver::ExtensionsUpdatedObserver(
    ExtensionProcessManager* manager, AutomationProvider* automation,
    IPC::Message* reply_message)
    : manager_(manager), automation_(automation->AsWeakPtr()),
      reply_message_(reply_message), updater_finished_(false) {
  registrar_.Add(this, content::NOTIFICATION_LOAD_STOP,
                 content::NotificationService::AllSources());
}

ExtensionsUpdatedObserver::~ExtensionsUpdatedObserver() {
}

void ExtensionsUpdatedObserver::Observe(
    int type, const content::NotificationSource& source,
    const content::NotificationDetails& details) {
  if (!automation_.get()) {
    delete this;
    return;
  }

  DCHECK(type == content::NOTIFICATION_LOAD_STOP);
  MaybeReply();
}

void ExtensionsUpdatedObserver::UpdateCheckFinished() {
  if (!automation_.get()) {
    delete this;
    return;
  }

  // Extension updater has completed updating all extensions.
  updater_finished_ = true;
  MaybeReply();
}

void ExtensionsUpdatedObserver::MaybeReply() {
  // Send the reply if (1) the extension updater has finished updating all
  // extensions; and (2) all extension views have stopped loading.
  if (updater_finished_ && DidExtensionViewsStopLoading(manager_)) {
    AutomationJSONReply reply(automation_.get(), reply_message_.release());
    reply.SendSuccess(NULL);
    delete this;
  }
}

BrowserOpenedNotificationObserver::BrowserOpenedNotificationObserver(
    AutomationProvider* automation,
    IPC::Message* reply_message,
    bool use_json_interface)
    : automation_(automation->AsWeakPtr()),
      reply_message_(reply_message),
      new_window_id_(extension_misc::kUnknownWindowId),
      use_json_interface_(use_json_interface),
      for_browser_command_(false) {
  registrar_.Add(this, chrome::NOTIFICATION_BROWSER_OPENED,
                 content::NotificationService::AllBrowserContextsAndSources());
  registrar_.Add(this, content::NOTIFICATION_LOAD_STOP,
                 content::NotificationService::AllBrowserContextsAndSources());
}

BrowserOpenedNotificationObserver::~BrowserOpenedNotificationObserver() {
}

void BrowserOpenedNotificationObserver::Observe(
    int type,
    const content::NotificationSource& source,
    const content::NotificationDetails& details) {
  if (!automation_.get()) {
    delete this;
    return;
  }

  if (type == chrome::NOTIFICATION_BROWSER_OPENED) {
    // Store the new browser ID and continue waiting for a new tab within it
    // to stop loading.
    new_window_id_ = ExtensionTabUtil::GetWindowId(
        content::Source<Browser>(source).ptr());
  } else {
    DCHECK_EQ(content::NOTIFICATION_LOAD_STOP, type);
    // Only send the result if the loaded tab is in the new window.
    NavigationController* controller =
        content::Source<NavigationController>(source).ptr();
    SessionTabHelper* session_tab_helper =
        SessionTabHelper::FromWebContents(controller->GetWebContents());
    int window_id = session_tab_helper ? session_tab_helper->window_id().id()
                                       : -1;
    if (window_id == new_window_id_) {
      if (use_json_interface_) {
        AutomationJSONReply(automation_.get(), reply_message_.release())
            .SendSuccess(NULL);
      } else {
        if (for_browser_command_) {
          AutomationMsg_WindowExecuteCommand::WriteReplyParams(
              reply_message_.get(), true);
        }
        automation_->Send(reply_message_.release());
      }
      delete this;
      return;
    }
  }
}

void BrowserOpenedNotificationObserver::set_for_browser_command(
    bool for_browser_command) {
  for_browser_command_ = for_browser_command;
}

BrowserClosedNotificationObserver::BrowserClosedNotificationObserver(
    Browser* browser,
    AutomationProvider* automation,
    IPC::Message* reply_message,
    bool use_json_interface)
    : automation_(automation->AsWeakPtr()),
      reply_message_(reply_message),
      use_json_interface_(use_json_interface),
      for_browser_command_(false) {
  registrar_.Add(this, chrome::NOTIFICATION_BROWSER_CLOSED,
                 content::Source<Browser>(browser));
}

BrowserClosedNotificationObserver::~BrowserClosedNotificationObserver() {}

void BrowserClosedNotificationObserver::Observe(
    int type, const content::NotificationSource& source,
    const content::NotificationDetails& details) {
  DCHECK_EQ(chrome::NOTIFICATION_BROWSER_CLOSED, type);

  if (!automation_.get()) {
    delete this;
    return;
  }

  // The automation layer doesn't support non-native desktops.
  int browser_count = static_cast<int>(BrowserList::GetInstance(
                          chrome::HOST_DESKTOP_TYPE_NATIVE)->size());
  // We get the notification before the browser is removed from the BrowserList.
  bool app_closing = browser_count == 1;

  if (use_json_interface_) {
    AutomationJSONReply(automation_.get(), reply_message_.release())
        .SendSuccess(NULL);
  } else {
    if (for_browser_command_) {
      AutomationMsg_WindowExecuteCommand::WriteReplyParams(reply_message_.get(),
                                                           true);
    } else {
      AutomationMsg_CloseBrowser::WriteReplyParams(reply_message_.get(), true,
                                                   app_closing);
    }
    automation_->Send(reply_message_.release());
  }
  delete this;
}

void BrowserClosedNotificationObserver::set_for_browser_command(
    bool for_browser_command) {
  for_browser_command_ = for_browser_command;
}

BrowserCountChangeNotificationObserver::BrowserCountChangeNotificationObserver(
    int target_count,
    AutomationProvider* automation,
    IPC::Message* reply_message)
    : target_count_(target_count),
      automation_(automation->AsWeakPtr()),
      reply_message_(reply_message) {
  registrar_.Add(this, chrome::NOTIFICATION_BROWSER_OPENED,
                 content::NotificationService::AllBrowserContextsAndSources());
  registrar_.Add(this, chrome::NOTIFICATION_BROWSER_CLOSED,
                 content::NotificationService::AllBrowserContextsAndSources());
}

BrowserCountChangeNotificationObserver::
    ~BrowserCountChangeNotificationObserver() {}

void BrowserCountChangeNotificationObserver::Observe(
    int type,
    const content::NotificationSource& source,
    const content::NotificationDetails& details) {
  DCHECK(type == chrome::NOTIFICATION_BROWSER_OPENED ||
         type == chrome::NOTIFICATION_BROWSER_CLOSED);

  // The automation layer doesn't support non-native desktops.
  int current_count = static_cast<int>(BrowserList::GetInstance(
                          chrome::HOST_DESKTOP_TYPE_NATIVE)->size());
  if (type == chrome::NOTIFICATION_BROWSER_CLOSED) {
    // At the time of the notification the browser being closed is not removed
    // from the list. The real count is one less than the reported count.
    DCHECK_LT(0, current_count);
    current_count--;
  }

  if (!automation_.get()) {
    delete this;
    return;
  }

  if (current_count == target_count_) {
    AutomationMsg_WaitForBrowserWindowCountToBecome::WriteReplyParams(
        reply_message_.get(), true);
    automation_->Send(reply_message_.release());
    delete this;
  }
}

namespace {

// Define mapping from command to notification
struct CommandNotification {
  int command;
  int notification_type;
};

const struct CommandNotification command_notifications[] = {
  {IDC_DUPLICATE_TAB, chrome::NOTIFICATION_TAB_PARENTED},

  // Returns as soon as the restored tab is created. To further wait until
  // the content page is loaded, use WaitForTabToBeRestored.
  {IDC_RESTORE_TAB, chrome::NOTIFICATION_TAB_PARENTED},

  // For the following commands, we need to wait for a new tab to be created,
  // load to finish, and title to change.
  {IDC_MANAGE_EXTENSIONS, content::NOTIFICATION_WEB_CONTENTS_TITLE_UPDATED},
  {IDC_OPTIONS, content::NOTIFICATION_WEB_CONTENTS_TITLE_UPDATED},
  {IDC_PRINT, content::NOTIFICATION_WEB_CONTENTS_TITLE_UPDATED},
  {IDC_SHOW_DOWNLOADS, content::NOTIFICATION_WEB_CONTENTS_TITLE_UPDATED},
  {IDC_SHOW_HISTORY, content::NOTIFICATION_WEB_CONTENTS_TITLE_UPDATED},
};

}  // namespace

ExecuteBrowserCommandObserver::~ExecuteBrowserCommandObserver() {
}

// static
bool ExecuteBrowserCommandObserver::CreateAndRegisterObserver(
    AutomationProvider* automation,
    Browser* browser,
    int command,
    IPC::Message* reply_message,
    bool use_json_interface) {
  bool result = true;
  switch (command) {
    case IDC_NEW_TAB: {
      new NewTabObserver(automation, reply_message, use_json_interface);
      break;
    }
    case IDC_NEW_WINDOW:
    case IDC_NEW_INCOGNITO_WINDOW: {
      BrowserOpenedNotificationObserver* observer =
          new BrowserOpenedNotificationObserver(automation, reply_message,
                                                use_json_interface);
      observer->set_for_browser_command(true);
      break;
    }
    case IDC_CLOSE_WINDOW: {
      BrowserClosedNotificationObserver* observer =
          new BrowserClosedNotificationObserver(browser, automation,
                                                reply_message,
                                                use_json_interface);
      observer->set_for_browser_command(true);
      break;
    }
    case IDC_CLOSE_TAB: {
      TabClosedNotificationObserver* observer =
          new TabClosedNotificationObserver(automation, true, reply_message,
                                            use_json_interface);
      observer->set_for_browser_command(true);
      break;
    }
    case IDC_BACK:
    case IDC_FORWARD:
    case IDC_RELOAD: {
      new NavigationNotificationObserver(
          &browser->tab_strip_model()->GetActiveWebContents()->GetController(),
          automation, reply_message, 1, false, use_json_interface);
      break;
    }
    default: {
      ExecuteBrowserCommandObserver* observer =
          new ExecuteBrowserCommandObserver(automation, reply_message,
                                            use_json_interface);
      if (!observer->Register(command)) {
        observer->ReleaseReply();
        delete observer;
        result = false;
      }
      break;
    }
  }
  return result;
}

void ExecuteBrowserCommandObserver::Observe(
    int type, const content::NotificationSource& source,
    const content::NotificationDetails& details) {
  if (type == notification_type_) {
    if (automation_.get()) {
      if (use_json_interface_) {
        AutomationJSONReply(automation_.get(), reply_message_.release())
            .SendSuccess(NULL);
      } else {
        AutomationMsg_WindowExecuteCommand::WriteReplyParams(
            reply_message_.get(), true);
        automation_->Send(reply_message_.release());
      }
    }
    delete this;
  } else {
    NOTREACHED();
  }
}

ExecuteBrowserCommandObserver::ExecuteBrowserCommandObserver(
    AutomationProvider* automation,
    IPC::Message* reply_message,
    bool use_json_interface)
    : automation_(automation->AsWeakPtr()),
      notification_type_(content::NOTIFICATION_ALL),
      reply_message_(reply_message),
      use_json_interface_(use_json_interface) {
}

bool ExecuteBrowserCommandObserver::Register(int command) {
  if (!Getint(command, &notification_type_))
    return false;
  registrar_.Add(this, notification_type_,
                 content::NotificationService::AllSources());
  return true;
}

bool ExecuteBrowserCommandObserver::Getint(
    int command, int* type) {
  if (!type)
    return false;
  bool found = false;
  for (unsigned int i = 0; i < arraysize(command_notifications); i++) {
    if (command_notifications[i].command == command) {
      *type = command_notifications[i].notification_type;
      found = true;
      break;
    }
  }
  return found;
}

IPC::Message* ExecuteBrowserCommandObserver::ReleaseReply() {
  return reply_message_.release();
}

FindInPageNotificationObserver::FindInPageNotificationObserver(
    AutomationProvider* automation, WebContents* parent_tab,
    bool reply_with_json, IPC::Message* reply_message)
    : automation_(automation->AsWeakPtr()),
      active_match_ordinal_(-1),
      reply_with_json_(reply_with_json),
      reply_message_(reply_message) {
  registrar_.Add(this, chrome::NOTIFICATION_FIND_RESULT_AVAILABLE,
                 content::Source<WebContents>(parent_tab));
}

FindInPageNotificationObserver::~FindInPageNotificationObserver() {
}

void FindInPageNotificationObserver::Observe(
    int type, const content::NotificationSource& source,
    const content::NotificationDetails& details) {
  content::Details<FindNotificationDetails> find_details(details);
  if (!(find_details->final_update() && reply_message_ != NULL)) {
    DVLOG(1) << "Ignoring, since we only care about the final message";
    return;
  }

  if (!automation_.get()) {
    delete this;
    return;
  }

  // We get multiple responses and one of those will contain the ordinal.
  // This message comes to us before the final update is sent.
  if (find_details->request_id() == kFindInPageRequestId) {
    if (reply_with_json_) {
      scoped_ptr<DictionaryValue> return_value(new DictionaryValue);
      return_value->SetInteger("match_count",
          find_details->number_of_matches());
      gfx::Rect rect = find_details->selection_rect();
      // If MatchCount is > 0, then rect should not be Empty.
      // We dont guard it here because we want to let the test
      // code catch this invalid case if needed.
      if (!rect.IsEmpty()) {
        return_value->SetInteger("match_left", rect.x());
        return_value->SetInteger("match_top", rect.y());
        return_value->SetInteger("match_right", rect.right());
        return_value->SetInteger("match_bottom", rect.bottom());
      }
      AutomationJSONReply(automation_.get(), reply_message_.release())
          .SendSuccess(return_value.get());
      delete this;
    } else {
      if (find_details->active_match_ordinal() > -1) {
        active_match_ordinal_ = find_details->active_match_ordinal();
        AutomationMsg_Find::WriteReplyParams(reply_message_.get(),
            active_match_ordinal_, find_details->number_of_matches());
        automation_->Send(reply_message_.release());
      }
    }
  }
}

// static
const int FindInPageNotificationObserver::kFindInPageRequestId = -1;

DomOperationObserver::DomOperationObserver(int automation_id)
    : automation_id_(automation_id) {
  registrar_.Add(this, content::NOTIFICATION_DOM_OPERATION_RESPONSE,
                 content::NotificationService::AllSources());
  registrar_.Add(this, chrome::NOTIFICATION_APP_MODAL_DIALOG_SHOWN,
                 content::NotificationService::AllSources());
  registrar_.Add(this, chrome::NOTIFICATION_WEB_CONTENT_SETTINGS_CHANGED,
                 content::NotificationService::AllSources());
}

DomOperationObserver::~DomOperationObserver() {}

void DomOperationObserver::Observe(
    int type, const content::NotificationSource& source,
    const content::NotificationDetails& details) {
  if (type == content::NOTIFICATION_DOM_OPERATION_RESPONSE) {
    content::Details<DomOperationNotificationDetails> dom_op_details(details);
    if (dom_op_details->automation_id == automation_id_)
      OnDomOperationCompleted(dom_op_details->json);
  } else if (type == chrome::NOTIFICATION_APP_MODAL_DIALOG_SHOWN) {
    OnModalDialogShown();
  } else {
    DCHECK_EQ(chrome::NOTIFICATION_WEB_CONTENT_SETTINGS_CHANGED, type);
    WebContents* web_contents = content::Source<WebContents>(source).ptr();
    if (web_contents) {
      TabSpecificContentSettings* tab_content_settings =
          TabSpecificContentSettings::FromWebContents(web_contents);
      if (tab_content_settings &&
          tab_content_settings->IsContentBlocked(
              CONTENT_SETTINGS_TYPE_JAVASCRIPT))
        OnJavascriptBlocked();
    }
  }
}

DomOperationMessageSender::DomOperationMessageSender(
    AutomationProvider* automation,
    IPC::Message* reply_message,
    bool use_json_interface)
    : DomOperationObserver(0),
      automation_(automation->AsWeakPtr()),
      reply_message_(reply_message),
      use_json_interface_(use_json_interface) {
}

DomOperationMessageSender::~DomOperationMessageSender() {}

void DomOperationMessageSender::OnDomOperationCompleted(
    const std::string& json) {
  if (automation_.get()) {
    if (use_json_interface_) {
      DictionaryValue dict;
      dict.SetString("result", json);
      AutomationJSONReply(automation_.get(), reply_message_.release())
          .SendSuccess(&dict);
    } else {
      AutomationMsg_DomOperation::WriteReplyParams(reply_message_.get(), json);
      automation_->Send(reply_message_.release());
    }
  }
  delete this;
}

void DomOperationMessageSender::OnModalDialogShown() {
  if (automation_.get() && use_json_interface_) {
    AutomationJSONReply(automation_.get(), reply_message_.release())
        .SendErrorCode(automation::kBlockedByModalDialog);
    delete this;
  }
}

void DomOperationMessageSender::OnJavascriptBlocked() {
  if (automation_.get() && use_json_interface_) {
    AutomationJSONReply(automation_.get(), reply_message_.release())
        .SendError("Javascript execution was blocked");
    delete this;
  }
}

MetricEventDurationObserver::MetricEventDurationObserver() {
  registrar_.Add(this, chrome::NOTIFICATION_METRIC_EVENT_DURATION,
                 content::NotificationService::AllSources());
}

MetricEventDurationObserver::~MetricEventDurationObserver() {}

int MetricEventDurationObserver::GetEventDurationMs(
    const std::string& event_name) {
  EventDurationMap::const_iterator it = durations_.find(event_name);
  if (it == durations_.end())
    return -1;
  return it->second;
}

void MetricEventDurationObserver::Observe(
    int type,
    const content::NotificationSource& source,
    const content::NotificationDetails& details) {
  if (type != chrome::NOTIFICATION_METRIC_EVENT_DURATION) {
    NOTREACHED();
    return;
  }
  MetricEventDurationDetails* metric_event_duration =
      content::Details<MetricEventDurationDetails>(details).ptr();
  durations_[metric_event_duration->event_name] =
      metric_event_duration->duration_ms;
}

InfoBarCountObserver::InfoBarCountObserver(AutomationProvider* automation,
                                           IPC::Message* reply_message,
                                           WebContents* web_contents,
                                           size_t target_count)
    : automation_(automation->AsWeakPtr()),
      reply_message_(reply_message),
      web_contents_(web_contents),
      target_count_(target_count) {
  content::Source<InfoBarService> source(
      InfoBarService::FromWebContents(web_contents));
  registrar_.Add(this, chrome::NOTIFICATION_TAB_CONTENTS_INFOBAR_ADDED,
                 source);
  registrar_.Add(this, chrome::NOTIFICATION_TAB_CONTENTS_INFOBAR_REMOVED,
                 source);
  CheckCount();
}

InfoBarCountObserver::~InfoBarCountObserver() {}

void InfoBarCountObserver::Observe(
    int type,
    const content::NotificationSource& source,
    const content::NotificationDetails& details) {
  DCHECK(type == chrome::NOTIFICATION_TAB_CONTENTS_INFOBAR_ADDED ||
         type == chrome::NOTIFICATION_TAB_CONTENTS_INFOBAR_REMOVED);
  CheckCount();
}

void InfoBarCountObserver::CheckCount() {
  if (InfoBarService::FromWebContents(web_contents_)->infobar_count() !=
      target_count_)
    return;

  if (automation_.get()) {
    AutomationMsg_WaitForInfoBarCount::WriteReplyParams(reply_message_.get(),
                                                        true);
    automation_->Send(reply_message_.release());
  }
  delete this;
}

AutomationProviderBookmarkModelObserver::
AutomationProviderBookmarkModelObserver(
    AutomationProvider* provider,
    IPC::Message* reply_message,
    BookmarkModel* model,
    bool use_json_interface)
    : automation_provider_(provider->AsWeakPtr()),
      reply_message_(reply_message),
      model_(model),
      use_json_interface_(use_json_interface) {
  model_->AddObserver(this);
}

AutomationProviderBookmarkModelObserver::
    ~AutomationProviderBookmarkModelObserver() {
  model_->RemoveObserver(this);
}

void AutomationProviderBookmarkModelObserver::Loaded(BookmarkModel* model,
                                                     bool ids_reassigned) {
  ReplyAndDelete(true);
}

void AutomationProviderBookmarkModelObserver::BookmarkModelBeingDeleted(
    BookmarkModel* model) {
  ReplyAndDelete(false);
}

IPC::Message* AutomationProviderBookmarkModelObserver::ReleaseReply() {
  return reply_message_.release();
}

void AutomationProviderBookmarkModelObserver::ReplyAndDelete(bool success) {
  if (automation_provider_.get()) {
    if (use_json_interface_) {
      AutomationJSONReply(automation_provider_.get(), reply_message_.release())
          .SendSuccess(NULL);
    } else {
      AutomationMsg_WaitForBookmarkModelToLoad::WriteReplyParams(
          reply_message_.get(), success);
      automation_provider_->Send(reply_message_.release());
    }
  }
  delete this;
}

AutomationProviderDownloadUpdatedObserver::
AutomationProviderDownloadUpdatedObserver(
    AutomationProvider* provider,
    IPC::Message* reply_message,
    bool wait_for_open,
    bool incognito)
    : provider_(provider->AsWeakPtr()),
      reply_message_(reply_message),
      wait_for_open_(wait_for_open),
      incognito_(incognito) {
}

AutomationProviderDownloadUpdatedObserver::
    ~AutomationProviderDownloadUpdatedObserver() {}

void AutomationProviderDownloadUpdatedObserver::OnDownloadUpdated(
    DownloadItem* download) {
  // If this observer is watching for open, only send the reply if the download
  // has been auto-opened.
  if (wait_for_open_ && !download->GetAutoOpened())
    return;

  download->RemoveObserver(this);

  if (provider_.get()) {
    scoped_ptr<DictionaryValue> return_value(
        provider_->GetDictionaryFromDownloadItem(download, incognito_));
    AutomationJSONReply(provider_.get(), reply_message_.release())
        .SendSuccess(return_value.get());
  }
  delete this;
}

void AutomationProviderDownloadUpdatedObserver::OnDownloadOpened(
    DownloadItem* download) {
  download->RemoveObserver(this);

  if (provider_.get()) {
    scoped_ptr<DictionaryValue> return_value(
        provider_->GetDictionaryFromDownloadItem(download, incognito_));
    AutomationJSONReply(provider_.get(), reply_message_.release())
        .SendSuccess(return_value.get());
  }
  delete this;
}

AutomationProviderDownloadModelChangedObserver::
AutomationProviderDownloadModelChangedObserver(
    AutomationProvider* provider,
    IPC::Message* reply_message,
    DownloadManager* download_manager)
    : provider_(provider->AsWeakPtr()),
      reply_message_(reply_message),
      notifier_(download_manager, this) {
}

AutomationProviderDownloadModelChangedObserver::
    ~AutomationProviderDownloadModelChangedObserver() {}

void AutomationProviderDownloadModelChangedObserver::ModelChanged() {
  if (provider_.get())
    AutomationJSONReply(provider_.get(), reply_message_.release())
        .SendSuccess(NULL);
  delete this;
}

void AutomationProviderDownloadModelChangedObserver::OnDownloadCreated(
    DownloadManager* manager, DownloadItem* item) {
  ModelChanged();
}

void AutomationProviderDownloadModelChangedObserver::OnDownloadRemoved(
    DownloadManager* manager, DownloadItem* item) {
  ModelChanged();
}

AllDownloadsCompleteObserver::AllDownloadsCompleteObserver(
    AutomationProvider* provider,
    IPC::Message* reply_message,
    DownloadManager* download_manager,
    ListValue* pre_download_ids)
    : provider_(provider->AsWeakPtr()),
      reply_message_(reply_message),
      download_manager_(download_manager) {
  for (ListValue::iterator it = pre_download_ids->begin();
       it != pre_download_ids->end(); ++it) {
    int val = 0;
    if ((*it)->GetAsInteger(&val)) {
      pre_download_ids_.insert(val);
    } else {
      AutomationJSONReply(provider_.get(), reply_message_.release())
          .SendError("Cannot convert ID of prior download to integer.");
      delete this;
      return;
    }
  }
  download_manager_->AddObserver(this);
  DownloadManager::DownloadVector all_items;
  download_manager->GetAllDownloads(&all_items);
  for (DownloadManager::DownloadVector::const_iterator
       it = all_items.begin(); it != all_items.end(); ++it) {
    OnDownloadCreated(download_manager_, *it);
  }
  ReplyIfNecessary();
}

AllDownloadsCompleteObserver::~AllDownloadsCompleteObserver() {
  if (download_manager_) {
    download_manager_->RemoveObserver(this);
    download_manager_ = NULL;
  }
  for (std::set<DownloadItem*>::const_iterator it = pending_downloads_.begin();
       it != pending_downloads_.end(); ++it) {
    (*it)->RemoveObserver(this);
  }
  pending_downloads_.clear();
}

void AllDownloadsCompleteObserver::ManagerGoingDown(DownloadManager* manager) {
  DCHECK_EQ(manager, download_manager_);
  download_manager_->RemoveObserver(this);
  download_manager_ = NULL;
}

void AllDownloadsCompleteObserver::OnDownloadCreated(
    DownloadManager* manager, DownloadItem* item) {
  // This method is also called in the c-tor for previously existing items.
  if (pre_download_ids_.find(item->GetId()) == pre_download_ids_.end() &&
      item->GetState() == DownloadItem::IN_PROGRESS) {
    item->AddObserver(this);
    pending_downloads_.insert(item);
  }
}

void AllDownloadsCompleteObserver::OnDownloadUpdated(DownloadItem* download) {
  // If the current download's status has changed to a final state (not state
  // "in progress"), remove it from the pending list.
  if (download->GetState() != DownloadItem::IN_PROGRESS) {
    download->RemoveObserver(this);
    pending_downloads_.erase(download);
    ReplyIfNecessary();
  }
}

void AllDownloadsCompleteObserver::ReplyIfNecessary() {
  if (!pending_downloads_.empty())
    return;

  download_manager_->RemoveObserver(this);
  if (provider_.get())
    AutomationJSONReply(provider_.get(), reply_message_.release())
        .SendSuccess(NULL);
  delete this;
}

AutomationProviderSearchEngineObserver::AutomationProviderSearchEngineObserver(
    AutomationProvider* provider,
    Profile* profile,
    IPC::Message* reply_message)
    : provider_(provider->AsWeakPtr()),
      profile_(profile),
      reply_message_(reply_message) {
}

AutomationProviderSearchEngineObserver::
    ~AutomationProviderSearchEngineObserver() {}

void AutomationProviderSearchEngineObserver::OnTemplateURLServiceChanged() {
  if (provider_.get()) {
    TemplateURLService* url_service =
        TemplateURLServiceFactory::GetForProfile(profile_);
    url_service->RemoveObserver(this);
    AutomationJSONReply(provider_.get(), reply_message_.release())
        .SendSuccess(NULL);
  }
  delete this;
}

AutomationProviderHistoryObserver::AutomationProviderHistoryObserver(
    AutomationProvider* provider,
    IPC::Message* reply_message)
    : provider_(provider->AsWeakPtr()),
      reply_message_(reply_message) {
}

AutomationProviderHistoryObserver::~AutomationProviderHistoryObserver() {}

void AutomationProviderHistoryObserver::HistoryQueryComplete(
    HistoryService::Handle request_handle,
    history::QueryResults* results) {
  if (!provider_.get()) {
    delete this;
    return;
  }

  scoped_ptr<DictionaryValue> return_value(new DictionaryValue);

  ListValue* history_list = new ListValue;
  for (size_t i = 0; i < results->size(); ++i) {
    DictionaryValue* page_value = new DictionaryValue;
    history::URLResult const &page = (*results)[i];
    page_value->SetString("title", page.title());
    page_value->SetString("url", page.url().spec());
    page_value->SetDouble("time",
                          static_cast<double>(page.visit_time().ToDoubleT()));
    page_value->SetString("snippet", page.snippet().text());
    page_value->SetBoolean(
        "starred",
        BookmarkModelFactory::GetForProfile(
            provider_->profile())->IsBookmarked(page.url()));
    history_list->Append(page_value);
  }

  return_value->Set("history", history_list);
  // Return history info.
  AutomationJSONReply reply(provider_.get(), reply_message_.release());
  reply.SendSuccess(return_value.get());
  delete this;
}

AutomationProviderImportSettingsObserver::
AutomationProviderImportSettingsObserver(
    AutomationProvider* provider,
    IPC::Message* reply_message)
    : provider_(provider->AsWeakPtr()),
      reply_message_(reply_message) {
}

AutomationProviderImportSettingsObserver::
    ~AutomationProviderImportSettingsObserver() {}

void AutomationProviderImportSettingsObserver::ImportStarted() {
}

void AutomationProviderImportSettingsObserver::ImportItemStarted(
    importer::ImportItem item) {
}

void AutomationProviderImportSettingsObserver::ImportItemEnded(
    importer::ImportItem item) {
}

void AutomationProviderImportSettingsObserver::ImportEnded() {
  if (provider_.get())
    AutomationJSONReply(provider_.get(), reply_message_.release())
        .SendSuccess(NULL);
  delete this;
}

AutomationProviderGetPasswordsObserver::AutomationProviderGetPasswordsObserver(
    AutomationProvider* provider,
    IPC::Message* reply_message)
    : provider_(provider->AsWeakPtr()),
      reply_message_(reply_message) {
}

AutomationProviderGetPasswordsObserver::
    ~AutomationProviderGetPasswordsObserver() {}

void AutomationProviderGetPasswordsObserver::OnPasswordStoreRequestDone(
    CancelableRequestProvider::Handle handle,
    const std::vector<content::PasswordForm*>& result) {
  if (!provider_.get()) {
    delete this;
    return;
  }

  scoped_ptr<DictionaryValue> return_value(new DictionaryValue);

  ListValue* passwords = new ListValue;
  for (std::vector<content::PasswordForm*>::const_iterator it =
           result.begin(); it != result.end(); ++it) {
    DictionaryValue* password_val = new DictionaryValue;
    content::PasswordForm* password_form = *it;
    password_val->SetString("username_value", password_form->username_value);
    password_val->SetString("password_value", password_form->password_value);
    password_val->SetString("signon_realm", password_form->signon_realm);
    password_val->SetDouble(
        "time", static_cast<double>(password_form->date_created.ToDoubleT()));
    password_val->SetString("origin_url", password_form->origin.spec());
    password_val->SetString("username_element",
                            password_form->username_element);
    password_val->SetString("password_element",
                            password_form->password_element);
    password_val->SetString("submit_element", password_form->submit_element);
    password_val->SetString("action_target", password_form->action.spec());
    password_val->SetBoolean("blacklist", password_form->blacklisted_by_user);
    passwords->Append(password_val);
  }

  return_value->Set("passwords", passwords);
  AutomationJSONReply(provider_.get(), reply_message_.release())
      .SendSuccess(return_value.get());
  delete this;
}

void AutomationProviderGetPasswordsObserver::OnGetPasswordStoreResults(
    const std::vector<content::PasswordForm*>& results) {
  // TODO(kaiwang): Implement when I refactor
  // PasswordManager::GetAutofillableLogins.
  NOTIMPLEMENTED();
}

PasswordStoreLoginsChangedObserver::PasswordStoreLoginsChangedObserver(
    AutomationProvider* automation,
    IPC::Message* reply_message,
    PasswordStoreChange::Type expected_type,
    const std::string& result_key)
    : automation_(automation->AsWeakPtr()),
      reply_message_(reply_message),
      expected_type_(expected_type),
      result_key_(result_key),
      done_event_(false, false) {
  AddRef();
}

PasswordStoreLoginsChangedObserver::~PasswordStoreLoginsChangedObserver() {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
}

void PasswordStoreLoginsChangedObserver::Init() {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  BrowserThread::PostTask(
      BrowserThread::DB,
      FROM_HERE,
      base::Bind(&PasswordStoreLoginsChangedObserver::RegisterObserversTask,
                 this));
  done_event_.Wait();
}

void PasswordStoreLoginsChangedObserver::RegisterObserversTask() {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
  registrar_.reset(new content::NotificationRegistrar);
  registrar_->Add(this, chrome::NOTIFICATION_LOGINS_CHANGED,
                  content::NotificationService::AllSources());
  done_event_.Signal();
}

void PasswordStoreLoginsChangedObserver::Observe(
    int type,
    const content::NotificationSource& source,
    const content::NotificationDetails& details) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
  DCHECK(type == chrome::NOTIFICATION_LOGINS_CHANGED);
  registrar_.reset();  // Must be done from the DB thread.
  PasswordStoreChangeList* change_details =
      content::Details<PasswordStoreChangeList>(details).ptr();
  if (change_details->size() != 1 ||
      change_details->front().type() != expected_type_) {
    // Notify the UI thread that there's an error.
    std::string error = "Unexpected password store login change details.";
    BrowserThread::PostTask(
        BrowserThread::UI,
        FROM_HERE,
        base::Bind(&PasswordStoreLoginsChangedObserver::IndicateError, this,
                   error));
    return;
  }

  // Notify the UI thread that we're done listening.
  BrowserThread::PostTask(
      BrowserThread::UI,
      FROM_HERE,
      base::Bind(&PasswordStoreLoginsChangedObserver::IndicateDone, this));
}

void PasswordStoreLoginsChangedObserver::IndicateDone() {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  if (automation_.get()) {
    if (result_key_.empty()) {
      AutomationJSONReply(automation_.get(), reply_message_.release())
          .SendSuccess(NULL);
    } else {
      scoped_ptr<DictionaryValue> return_value(new DictionaryValue);
      return_value->SetBoolean(result_key_, true);
      AutomationJSONReply(automation_.get(), reply_message_.release())
          .SendSuccess(return_value.get());
    }
  }
  Release();
}

void PasswordStoreLoginsChangedObserver::IndicateError(
    const std::string& error) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  if (automation_.get())
    AutomationJSONReply(automation_.get(), reply_message_.release())
        .SendError(error);
  Release();
}

OmniboxAcceptNotificationObserver::OmniboxAcceptNotificationObserver(
    NavigationController* controller,
    AutomationProvider* automation,
    IPC::Message* reply_message)
    : automation_(automation->AsWeakPtr()),
      reply_message_(reply_message),
      controller_(controller) {
  content::Source<NavigationController> source(controller_);
  registrar_.Add(this, content::NOTIFICATION_LOAD_STOP, source);
  // Pages requiring auth don't send LOAD_STOP.
  registrar_.Add(this, chrome::NOTIFICATION_AUTH_NEEDED, source);
}

OmniboxAcceptNotificationObserver::~OmniboxAcceptNotificationObserver() {
}

void OmniboxAcceptNotificationObserver::Observe(
    int type,
    const content::NotificationSource& source,
    const content::NotificationDetails& details) {
  if (type == content::NOTIFICATION_LOAD_STOP ||
      type == chrome::NOTIFICATION_AUTH_NEEDED) {
    if (automation_.get()) {
      AutomationJSONReply(automation_.get(), reply_message_.release())
          .SendSuccess(NULL);
    }
    delete this;
  } else {
    NOTREACHED();
  }
}

SavePackageNotificationObserver::SavePackageNotificationObserver(
    content::DownloadManager* download_manager,
    AutomationProvider* automation,
    IPC::Message* reply_message)
    : download_manager_(download_manager),
      automation_(automation->AsWeakPtr()),
      reply_message_(reply_message) {
  download_manager_->AddObserver(this);
}

SavePackageNotificationObserver::~SavePackageNotificationObserver() {
  download_manager_->RemoveObserver(this);
}

void SavePackageNotificationObserver::OnSavePackageSuccessfullyFinished(
    content::DownloadManager* manager, content::DownloadItem* item) {
  if (automation_.get()) {
    AutomationJSONReply(automation_.get(), reply_message_.release())
        .SendSuccess(NULL);
  }
  delete this;
}

void SavePackageNotificationObserver::ManagerGoingDown(
    content::DownloadManager* manager) {
  delete this;
}

namespace {

// Returns a vector of dictionaries containing information about installed apps,
// as identified from a given list of extensions.  The caller takes ownership
// of the created vector.
std::vector<DictionaryValue*>* GetAppInfoFromExtensions(
    const ExtensionSet* extensions,
    ExtensionService* ext_service) {
  std::vector<DictionaryValue*>* apps_list =
      new std::vector<DictionaryValue*>();
  for (ExtensionSet::const_iterator ext = extensions->begin();
       ext != extensions->end(); ++ext) {
    // Only return information about extensions that are actually apps.
    if ((*ext)->is_app()) {
      DictionaryValue* app_info = new DictionaryValue();
      AppLauncherHandler::CreateAppInfo(ext->get(), ext_service, app_info);
      app_info->SetBoolean(
          "is_component_extension",
          (*ext)->location() == extensions::Manifest::COMPONENT);

      // Convert the launch_type integer into a more descriptive string.
      int launch_type;
      const char* kLaunchType = "launch_type";
      if (!app_info->GetInteger(kLaunchType, &launch_type)) {
        NOTREACHED() << "Can't get integer from key " << kLaunchType;
        continue;
      }
      if (launch_type == extensions::ExtensionPrefs::LAUNCH_PINNED) {
        app_info->SetString(kLaunchType, "pinned");
      } else if (launch_type == extensions::ExtensionPrefs::LAUNCH_REGULAR) {
        app_info->SetString(kLaunchType, "regular");
      } else if (launch_type == extensions::ExtensionPrefs::LAUNCH_FULLSCREEN) {
        app_info->SetString(kLaunchType, "fullscreen");
      } else if (launch_type == extensions::ExtensionPrefs::LAUNCH_WINDOW) {
        app_info->SetString(kLaunchType, "window");
      } else {
        app_info->SetString(kLaunchType, "unknown");
      }

      apps_list->push_back(app_info);
    }
  }
  return apps_list;
}

}  // namespace

NTPInfoObserver::NTPInfoObserver(AutomationProvider* automation,
                                 IPC::Message* reply_message)
    : automation_(automation->AsWeakPtr()),
      reply_message_(reply_message),
      request_(0),
      ntp_info_(new DictionaryValue) {
  top_sites_ = automation_->profile()->GetTopSites();
  if (!top_sites_) {
    AutomationJSONReply(automation_.get(), reply_message_.release())
        .SendError("Profile does not have service for querying the top sites.");
    return;
  }
  TabRestoreService* service =
      TabRestoreServiceFactory::GetForProfile(automation_->profile());
  if (!service) {
    AutomationJSONReply(automation_.get(), reply_message_.release())
        .SendError("No TabRestoreService.");
    return;
  }

  // Collect information about the apps in the new tab page.
  ExtensionService* ext_service = extensions::ExtensionSystem::Get(
      automation_->profile())->extension_service();
  if (!ext_service) {
    AutomationJSONReply(automation_.get(), reply_message_.release())
        .SendError("No ExtensionService.");
    return;
  }
  // Process enabled extensions.
  ListValue* apps_list = new ListValue();
  const ExtensionSet* extensions = ext_service->extensions();
  std::vector<DictionaryValue*>* enabled_apps = GetAppInfoFromExtensions(
      extensions, ext_service);
  for (std::vector<DictionaryValue*>::const_iterator app =
       enabled_apps->begin(); app != enabled_apps->end(); ++app) {
    (*app)->SetBoolean("is_disabled", false);
    apps_list->Append(*app);
  }
  delete enabled_apps;
  // Process disabled extensions.
  const ExtensionSet* disabled_extensions = ext_service->disabled_extensions();
  std::vector<DictionaryValue*>* disabled_apps = GetAppInfoFromExtensions(
      disabled_extensions, ext_service);
  for (std::vector<DictionaryValue*>::const_iterator app =
       disabled_apps->begin(); app != disabled_apps->end(); ++app) {
    (*app)->SetBoolean("is_disabled", true);
    apps_list->Append(*app);
  }
  delete disabled_apps;
  // Process terminated extensions.
  const ExtensionSet* terminated_extensions =
      ext_service->terminated_extensions();
  std::vector<DictionaryValue*>* terminated_apps = GetAppInfoFromExtensions(
      terminated_extensions, ext_service);
  for (std::vector<DictionaryValue*>::const_iterator app =
       terminated_apps->begin(); app != terminated_apps->end(); ++app) {
    (*app)->SetBoolean("is_disabled", true);
    apps_list->Append(*app);
  }
  delete terminated_apps;
  ntp_info_->Set("apps", apps_list);

  // Get the info that would be displayed in the recently closed section.
  ListValue* recently_closed_list = new ListValue;
  RecentlyClosedTabsHandler::CreateRecentlyClosedValues(service->entries(),
                                                        recently_closed_list);
  ntp_info_->Set("recently_closed", recently_closed_list);

  // Add default site URLs.
  ListValue* default_sites_list = new ListValue;
  history::MostVisitedURLList urls = top_sites_->GetPrepopulatePages();
  for (size_t i = 0; i < urls.size(); ++i) {
    default_sites_list->Append(Value::CreateStringValue(
        urls[i].url.possibly_invalid_spec()));
  }
  ntp_info_->Set("default_sites", default_sites_list);

  registrar_.Add(this, chrome::NOTIFICATION_TOP_SITES_UPDATED,
                 content::Source<history::TopSites>(top_sites_));
  if (top_sites_->loaded()) {
    OnTopSitesLoaded();
  } else {
    registrar_.Add(this, chrome::NOTIFICATION_TOP_SITES_LOADED,
                   content::Source<Profile>(automation_->profile()));
  }
}

NTPInfoObserver::~NTPInfoObserver() {}

void NTPInfoObserver::Observe(int type,
                              const content::NotificationSource& source,
                              const content::NotificationDetails& details) {
  if (type == chrome::NOTIFICATION_TOP_SITES_LOADED) {
    OnTopSitesLoaded();
  } else if (type == chrome::NOTIFICATION_TOP_SITES_UPDATED) {
    content::Details<CancelableRequestProvider::Handle> request_details(
          details);
    if (request_ == *request_details.ptr()) {
      top_sites_->GetMostVisitedURLs(
          base::Bind(&NTPInfoObserver::OnTopSitesReceived,
                     base::Unretained(this)));
    }
  }
}

void NTPInfoObserver::OnTopSitesLoaded() {
  request_ = top_sites_->StartQueryForMostVisited();
}

void NTPInfoObserver::OnTopSitesReceived(
    const history::MostVisitedURLList& visited_list) {
  if (!automation_.get()) {
    delete this;
    return;
  }

  ListValue* list_value = new ListValue;
  for (size_t i = 0; i < visited_list.size(); ++i) {
    const history::MostVisitedURL& visited = visited_list[i];
    if (visited.url.spec().empty())
      break;  // This is the signal that there are no more real visited sites.
    DictionaryValue* dict = new DictionaryValue;
    dict->SetString("url", visited.url.spec());
    dict->SetString("title", visited.title);
    list_value->Append(dict);
  }
  ntp_info_->Set("most_visited", list_value);
  AutomationJSONReply(automation_.get(), reply_message_.release())
      .SendSuccess(ntp_info_.get());
  delete this;
}

AppLaunchObserver::AppLaunchObserver(
    NavigationController* controller,
    AutomationProvider* automation,
    IPC::Message* reply_message,
    extension_misc::LaunchContainer launch_container)
    : controller_(controller),
      automation_(automation->AsWeakPtr()),
      reply_message_(reply_message),
      launch_container_(launch_container),
      new_window_id_(extension_misc::kUnknownWindowId) {
  if (launch_container_ == extension_misc::LAUNCH_TAB) {
    // Need to wait for the currently-active tab to reload.
    content::Source<NavigationController> source(controller_);
    registrar_.Add(this, content::NOTIFICATION_LOAD_STOP, source);
  } else {
    // Need to wait for a new tab in a new window to load.
    registrar_.Add(this, content::NOTIFICATION_LOAD_STOP,
                   content::NotificationService::AllSources());
    registrar_.Add(this, chrome::NOTIFICATION_BROWSER_WINDOW_READY,
                   content::NotificationService::AllSources());
  }
}

AppLaunchObserver::~AppLaunchObserver() {}

void AppLaunchObserver::Observe(int type,
                                const content::NotificationSource& source,
                                const content::NotificationDetails& details) {
  if (type == chrome::NOTIFICATION_BROWSER_WINDOW_READY) {
    new_window_id_ =
        ExtensionTabUtil::GetWindowId(content::Source<Browser>(source).ptr());
    return;
  }

  DCHECK_EQ(content::NOTIFICATION_LOAD_STOP, type);
  SessionTabHelper* session_tab_helper = SessionTabHelper::FromWebContents(
      content::Source<NavigationController>(source)->GetWebContents());
  if ((launch_container_ == extension_misc::LAUNCH_TAB) ||
      (session_tab_helper &&
          (session_tab_helper->window_id().id() == new_window_id_))) {
    if (automation_.get()) {
      AutomationJSONReply(automation_.get(), reply_message_.release())
          .SendSuccess(NULL);
    }
    delete this;
  }
}

namespace {

// Returns whether all active notifications have an associated process ID.
bool AreActiveNotificationProcessesReady() {
  BalloonNotificationUIManager* manager =
      BalloonNotificationUIManager::GetInstanceForTesting();
  const BalloonCollection::Balloons& balloons =
      manager->balloon_collection()->GetActiveBalloons();
  BalloonCollection::Balloons::const_iterator iter;
  for (iter = balloons.begin(); iter != balloons.end(); ++iter) {
    BalloonHost* balloon_host = (*iter)->balloon_view()->GetHost();
    if (balloon_host && !balloon_host->IsRenderViewReady())
      return false;
  }
  return true;
}

}  // namespace

GetAllNotificationsObserver::GetAllNotificationsObserver(
    AutomationProvider* automation,
    IPC::Message* reply_message)
    : automation_(automation->AsWeakPtr()),
      reply_message_(reply_message) {
  if (AreActiveNotificationProcessesReady()) {
    SendMessage();
  } else {
    registrar_.Add(this, chrome::NOTIFICATION_NOTIFY_BALLOON_CONNECTED,
                   content::NotificationService::AllSources());
  }
}

GetAllNotificationsObserver::~GetAllNotificationsObserver() {}

void GetAllNotificationsObserver::Observe(
    int type,
    const content::NotificationSource& source,
    const content::NotificationDetails& details) {
  if (!automation_.get()) {
    delete this;
    return;
  }
  if (AreActiveNotificationProcessesReady())
    SendMessage();
}

base::DictionaryValue* GetAllNotificationsObserver::NotificationToJson(
    const Notification* note) {
  DictionaryValue* dict = new base::DictionaryValue();
  dict->SetString("content_url", note->content_url().spec());
  dict->SetString("origin_url", note->origin_url().spec());
  dict->SetString("display_source", note->display_source());
  dict->SetString("id", note->notification_id());
  return dict;
}

void GetAllNotificationsObserver::SendMessage() {
  BalloonNotificationUIManager* manager =
      BalloonNotificationUIManager::GetInstanceForTesting();
  const BalloonCollection::Balloons& balloons =
      manager->balloon_collection()->GetActiveBalloons();
  DictionaryValue return_value;
  ListValue* list = new ListValue;
  return_value.Set("notifications", list);
  BalloonCollection::Balloons::const_iterator balloon_iter;
  for (balloon_iter = balloons.begin(); balloon_iter != balloons.end();
       ++balloon_iter) {
    base::DictionaryValue* note = NotificationToJson(
        &(*balloon_iter)->notification());
    BalloonHost* balloon_host = (*balloon_iter)->balloon_view()->GetHost();
    if (balloon_host) {
      int pid = base::GetProcId(balloon_host->web_contents()->
                                GetRenderViewHost()->GetProcess()->GetHandle());
      note->SetInteger("pid", pid);
    }
    list->Append(note);
  }
  std::vector<const Notification*> queued_notes;
  manager->GetQueuedNotificationsForTesting(&queued_notes);
  std::vector<const Notification*>::const_iterator queued_iter;
  for (queued_iter = queued_notes.begin(); queued_iter != queued_notes.end();
       ++queued_iter) {
    list->Append(NotificationToJson(*queued_iter));
  }
  AutomationJSONReply(automation_.get(), reply_message_.release())
      .SendSuccess(&return_value);
  delete this;
}

NewNotificationBalloonObserver::NewNotificationBalloonObserver(
    AutomationProvider* provider,
    IPC::Message* reply_message)
    : automation_(provider->AsWeakPtr()),
      reply_message_(reply_message) {
  registrar_.Add(this, chrome::NOTIFICATION_NOTIFY_BALLOON_CONNECTED,
                 content::NotificationService::AllSources());
}

NewNotificationBalloonObserver::~NewNotificationBalloonObserver() { }

void NewNotificationBalloonObserver::Observe(
    int type,
    const content::NotificationSource& source,
    const content::NotificationDetails& details) {
  if (automation_.get()) {
    AutomationJSONReply(automation_.get(), reply_message_.release())
        .SendSuccess(NULL);
  }
  delete this;
}

OnNotificationBalloonCountObserver::OnNotificationBalloonCountObserver(
    AutomationProvider* provider,
    IPC::Message* reply_message,
    int count)
    : automation_(provider->AsWeakPtr()),
      reply_message_(reply_message),
      collection_(BalloonNotificationUIManager::GetInstanceForTesting()->
          balloon_collection()),
      count_(count) {
  registrar_.Add(this, chrome::NOTIFICATION_NOTIFY_BALLOON_CONNECTED,
                 content::NotificationService::AllSources());
  collection_->set_on_collection_changed_callback(
      base::Bind(&OnNotificationBalloonCountObserver::CheckBalloonCount,
                 base::Unretained(this)));
  CheckBalloonCount();
}

OnNotificationBalloonCountObserver::~OnNotificationBalloonCountObserver() {
}

void OnNotificationBalloonCountObserver::Observe(
    int type,
    const content::NotificationSource& source,
    const content::NotificationDetails& details) {
  CheckBalloonCount();
}

void OnNotificationBalloonCountObserver::CheckBalloonCount() {
  bool balloon_count_met = AreActiveNotificationProcessesReady() &&
      static_cast<int>(collection_->GetActiveBalloons().size()) == count_;

  if (balloon_count_met && automation_.get()) {
    AutomationJSONReply(automation_.get(), reply_message_.release())
        .SendSuccess(NULL);
  }

  if (balloon_count_met || !automation_.get()) {
    collection_->set_on_collection_changed_callback(base::Closure());
    delete this;
  }
}

RendererProcessClosedObserver::RendererProcessClosedObserver(
    AutomationProvider* automation,
    IPC::Message* reply_message)
    : automation_(automation->AsWeakPtr()),
      reply_message_(reply_message) {
  registrar_.Add(this, content::NOTIFICATION_RENDERER_PROCESS_CLOSED,
                 content::NotificationService::AllSources());
}

RendererProcessClosedObserver::~RendererProcessClosedObserver() {}

void RendererProcessClosedObserver::Observe(
    int type,
    const content::NotificationSource& source,
    const content::NotificationDetails& details) {
  if (automation_.get()) {
    AutomationJSONReply(automation_.get(), reply_message_.release())
        .SendSuccess(NULL);
  }
  delete this;
}

InputEventAckNotificationObserver::InputEventAckNotificationObserver(
    AutomationProvider* automation,
    IPC::Message* reply_message,
    int event_type,
    int count)
    : automation_(automation->AsWeakPtr()),
      reply_message_(reply_message),
      event_type_(event_type),
      count_(count) {
  DCHECK(1 <= count);
  registrar_.Add(
      this,
      content::NOTIFICATION_RENDER_WIDGET_HOST_DID_RECEIVE_INPUT_EVENT_ACK,
      content::NotificationService::AllSources());
  registrar_.Add(
      this,
      chrome::NOTIFICATION_APP_MODAL_DIALOG_SHOWN,
      content::NotificationService::AllSources());
}

InputEventAckNotificationObserver::~InputEventAckNotificationObserver() {}

void InputEventAckNotificationObserver::Observe(
    int type,
    const content::NotificationSource& source,
    const content::NotificationDetails& details) {
  if (type == chrome::NOTIFICATION_APP_MODAL_DIALOG_SHOWN) {
    AutomationJSONReply(automation_.get(), reply_message_.release())
        .SendSuccess(NULL);
    delete this;
    return;
  }

  content::Details<int> request_details(details);
  // If the event type matches for |count_| times, replies with a JSON message.
  if (event_type_ == *request_details.ptr()) {
    if (--count_ == 0 && automation_.get()) {
      AutomationJSONReply(automation_.get(), reply_message_.release())
          .SendSuccess(NULL);
      delete this;
    }
  } else {
    LOG(WARNING) << "Ignoring unexpected event type: "
                 << *request_details.ptr() << " (expected: " << event_type_
                 << ")";
  }
}

NewTabObserver::NewTabObserver(AutomationProvider* automation,
                               IPC::Message* reply_message,
                               bool use_json_interface)
    : automation_(automation->AsWeakPtr()),
      reply_message_(reply_message),
      use_json_interface_(use_json_interface) {
  // Use TAB_PARENTED to detect the new tab.
  registrar_.Add(this,
                 chrome::NOTIFICATION_TAB_PARENTED,
                 content::NotificationService::AllSources());
}

void NewTabObserver::Observe(int type,
                             const content::NotificationSource& source,
                             const content::NotificationDetails& details) {
  DCHECK_EQ(chrome::NOTIFICATION_TAB_PARENTED, type);
  NavigationController* controller =
      &(content::Source<content::WebContents>(source).ptr()->GetController());
  if (automation_.get()) {
    // TODO(phajdan.jr): Clean up this hack. We write the correct return type
    // here, but don't send the message. NavigationNotificationObserver
    // will wait properly for the load to finish, and send the message,
    // but it will also append its own return value at the end of the reply.
    if (!use_json_interface_)
      AutomationMsg_WindowExecuteCommand::WriteReplyParams(reply_message_.get(),
                                                           true);
    new NavigationNotificationObserver(controller,
                                       automation_.get(),
                                       reply_message_.release(),
                                       1,
                                       false,
                                       use_json_interface_);
  }
  delete this;
}

NewTabObserver::~NewTabObserver() {
}

WaitForProcessLauncherThreadToGoIdleObserver::
WaitForProcessLauncherThreadToGoIdleObserver(
    AutomationProvider* automation, IPC::Message* reply_message)
    : automation_(automation->AsWeakPtr()),
      reply_message_(reply_message) {
  // Balanced in RunOnUIThread.
  AddRef();
  BrowserThread::PostTask(
      BrowserThread::PROCESS_LAUNCHER, FROM_HERE,
      base::Bind(
          &WaitForProcessLauncherThreadToGoIdleObserver::
              RunOnProcessLauncherThread,
          this));
}

WaitForProcessLauncherThreadToGoIdleObserver::
    ~WaitForProcessLauncherThreadToGoIdleObserver() {
}

void WaitForProcessLauncherThreadToGoIdleObserver::
RunOnProcessLauncherThread() {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::PROCESS_LAUNCHER));
  BrowserThread::PostTask(
      BrowserThread::PROCESS_LAUNCHER, FROM_HERE,
      base::Bind(
          &WaitForProcessLauncherThreadToGoIdleObserver::
              RunOnProcessLauncherThread2,
          this));
}

void WaitForProcessLauncherThreadToGoIdleObserver::
RunOnProcessLauncherThread2() {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::PROCESS_LAUNCHER));
  BrowserThread::PostTask(
      BrowserThread::UI, FROM_HERE,
      base::Bind(&WaitForProcessLauncherThreadToGoIdleObserver::RunOnUIThread,
                 this));
}

void WaitForProcessLauncherThreadToGoIdleObserver::RunOnUIThread() {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  if (automation_.get())
    automation_->Send(reply_message_.release());
  Release();
}

DragTargetDropAckNotificationObserver::DragTargetDropAckNotificationObserver(
    AutomationProvider* automation,
    IPC::Message* reply_message)
    : automation_(automation->AsWeakPtr()),
      reply_message_(reply_message) {
  registrar_.Add(
      this,
      content::NOTIFICATION_RENDER_VIEW_HOST_DID_RECEIVE_DRAG_TARGET_DROP_ACK,
      content::NotificationService::AllSources());
  registrar_.Add(
      this,
      chrome::NOTIFICATION_APP_MODAL_DIALOG_SHOWN,
      content::NotificationService::AllSources());
}

DragTargetDropAckNotificationObserver::
    ~DragTargetDropAckNotificationObserver() {}

void DragTargetDropAckNotificationObserver::Observe(
    int type,
    const content::NotificationSource& source,
    const content::NotificationDetails& details) {
  if (automation_.get()) {
    AutomationJSONReply(automation_.get(), reply_message_.release())
        .SendSuccess(NULL);
  }
  delete this;
}

ProcessInfoObserver::ProcessInfoObserver(
    AutomationProvider* automation,
    IPC::Message* reply_message)
    : automation_(automation->AsWeakPtr()),
      reply_message_(reply_message) {}

ProcessInfoObserver::~ProcessInfoObserver() {}

void ProcessInfoObserver::OnDetailsAvailable() {
  scoped_ptr<DictionaryValue> return_value(new DictionaryValue);
  ListValue* browser_proc_list = new ListValue();
  const std::vector<ProcessData>& all_processes = processes();
  for (size_t index = 0; index < all_processes.size(); ++index) {
    DictionaryValue* browser_data = new DictionaryValue();
    browser_data->SetString("name", all_processes[index].name);
    browser_data->SetString("process_name", all_processes[index].process_name);

    ListValue* proc_list = new ListValue();
    for (ProcessMemoryInformationList::const_iterator iterator =
             all_processes[index].processes.begin();
         iterator != all_processes[index].processes.end(); ++iterator) {
      DictionaryValue* proc_data = new DictionaryValue();

      proc_data->SetInteger("pid", iterator->pid);

      // Working set (resident) memory usage, in KBytes.
      DictionaryValue* working_set = new DictionaryValue();
      working_set->SetInteger("priv", iterator->working_set.priv);
      working_set->SetInteger("shareable", iterator->working_set.shareable);
      working_set->SetInteger("shared", iterator->working_set.shared);
      proc_data->Set("working_set_mem", working_set);

      // Committed (resident + paged) memory usage, in KBytes.
      DictionaryValue* committed = new DictionaryValue();
      committed->SetInteger("priv", iterator->committed.priv);
      committed->SetInteger("mapped", iterator->committed.mapped);
      committed->SetInteger("image", iterator->committed.image);
      proc_data->Set("committed_mem", committed);

      proc_data->SetString("version", iterator->version);
      proc_data->SetString("product_name", iterator->product_name);
      proc_data->SetInteger("num_processes", iterator->num_processes);
      proc_data->SetBoolean("is_diagnostics", iterator->is_diagnostics);

      // Process type, if this is a child process of Chrome (e.g., 'plugin').
      std::string process_type = "Unknown";
      // The following condition avoids a DCHECK in debug builds when the
      // process type passed to |GetTypeNameInEnglish| is unknown.
      if (iterator->process_type != content::PROCESS_TYPE_UNKNOWN) {
        process_type =
            content::GetProcessTypeNameInEnglish(iterator->process_type);
      }
      proc_data->SetString("child_process_type", process_type);

      // Renderer type, if this is a renderer process.
      std::string renderer_type = "Unknown";
      if (iterator->renderer_type !=
          ProcessMemoryInformation::RENDERER_UNKNOWN) {
        renderer_type = ProcessMemoryInformation::GetRendererTypeNameInEnglish(
            iterator->renderer_type);
      }
      proc_data->SetString("renderer_type", renderer_type);

      // Titles associated with this process.
      ListValue* titles = new ListValue();
      for (size_t title_index = 0; title_index < iterator->titles.size();
           ++title_index)
        titles->Append(Value::CreateStringValue(iterator->titles[title_index]));
      proc_data->Set("titles", titles);

      proc_list->Append(proc_data);
    }
    browser_data->Set("processes", proc_list);

    browser_proc_list->Append(browser_data);
  }
  return_value->Set("browsers", browser_proc_list);

  if (automation_.get()) {
    AutomationJSONReply(automation_.get(), reply_message_.release())
        .SendSuccess(return_value.get());
  }
}

V8HeapStatsObserver::V8HeapStatsObserver(
    AutomationProvider* automation,
    IPC::Message* reply_message,
    base::ProcessId renderer_id)
    : automation_(automation->AsWeakPtr()),
      reply_message_(reply_message),
      renderer_id_(renderer_id) {
  registrar_.Add(
      this,
      chrome::NOTIFICATION_RENDERER_V8_HEAP_STATS_COMPUTED,
      content::NotificationService::AllSources());
}

V8HeapStatsObserver::~V8HeapStatsObserver() {}

void V8HeapStatsObserver::Observe(
    int type,
    const content::NotificationSource& source,
    const content::NotificationDetails& details) {
  DCHECK(type == chrome::NOTIFICATION_RENDERER_V8_HEAP_STATS_COMPUTED);

  base::ProcessId updated_renderer_id =
      *(content::Source<base::ProcessId>(source).ptr());
  // Only return information for the renderer ID we're interested in.
  if (renderer_id_ != updated_renderer_id)
    return;

  ChromeRenderMessageFilter::V8HeapStatsDetails* v8_heap_details =
      content::Details<ChromeRenderMessageFilter::V8HeapStatsDetails>(details)
          .ptr();
  scoped_ptr<DictionaryValue> return_value(new DictionaryValue);
  return_value->SetInteger("renderer_id", updated_renderer_id);
  return_value->SetInteger("v8_memory_allocated",
                           v8_heap_details->v8_memory_allocated());
  return_value->SetInteger("v8_memory_used",
                           v8_heap_details->v8_memory_used());

  if (automation_.get()) {
    AutomationJSONReply(automation_.get(), reply_message_.release())
        .SendSuccess(return_value.get());
  }
  delete this;
}

FPSObserver::FPSObserver(
    AutomationProvider* automation,
    IPC::Message* reply_message,
    base::ProcessId renderer_id,
    int routing_id)
    : automation_(automation->AsWeakPtr()),
      reply_message_(reply_message),
      renderer_id_(renderer_id),
      routing_id_(routing_id) {
  registrar_.Add(
      this,
      chrome::NOTIFICATION_RENDERER_FPS_COMPUTED,
      content::NotificationService::AllSources());
}

FPSObserver::~FPSObserver() {}

void FPSObserver::Observe(
    int type,
    const content::NotificationSource& source,
    const content::NotificationDetails& details) {
  DCHECK(type == chrome::NOTIFICATION_RENDERER_FPS_COMPUTED);

  base::ProcessId updated_renderer_id =
      *(content::Source<base::ProcessId>(source).ptr());
  // Only return information for the renderer ID we're interested in.
  if (renderer_id_ != updated_renderer_id)
    return;

  ChromeRenderMessageFilter::FPSDetails* fps_details =
      content::Details<ChromeRenderMessageFilter::FPSDetails>(details).ptr();
  // Only return information for the routing id of the host render view we're
  // interested in.
  if (routing_id_ != fps_details->routing_id())
    return;

  scoped_ptr<DictionaryValue> return_value(new DictionaryValue);
  return_value->SetInteger("renderer_id", updated_renderer_id);
  return_value->SetInteger("routing_id", fps_details->routing_id());
  return_value->SetDouble("fps", fps_details->fps());
  if (automation_.get()) {
    AutomationJSONReply(automation_.get(), reply_message_.release())
        .SendSuccess(return_value.get());
  }
  delete this;
}

BrowserOpenedWithNewProfileNotificationObserver::
    BrowserOpenedWithNewProfileNotificationObserver(
        AutomationProvider* automation,
        IPC::Message* reply_message)
        : automation_(automation->AsWeakPtr()),
          reply_message_(reply_message),
          new_window_id_(extension_misc::kUnknownWindowId) {
  registrar_.Add(this, chrome::NOTIFICATION_PROFILE_CREATED,
                 content::NotificationService::AllBrowserContextsAndSources());
  registrar_.Add(this, chrome::NOTIFICATION_BROWSER_OPENED,
                 content::NotificationService::AllBrowserContextsAndSources());
  registrar_.Add(this, content::NOTIFICATION_LOAD_STOP,
                 content::NotificationService::AllBrowserContextsAndSources());
}

BrowserOpenedWithNewProfileNotificationObserver::
    ~BrowserOpenedWithNewProfileNotificationObserver() {
}

void BrowserOpenedWithNewProfileNotificationObserver::Observe(
    int type,
    const content::NotificationSource& source,
    const content::NotificationDetails& details) {
  if (!automation_.get()) {
    delete this;
    return;
  }

  if (type == chrome::NOTIFICATION_PROFILE_CREATED) {
    // As part of multi-profile creation, a new browser window will
    // automatically be opened.
    Profile* profile = content::Source<Profile>(source).ptr();
    if (!profile) {
      AutomationJSONReply(automation_.get(), reply_message_.release())
          .SendError("Profile could not be created.");
      return;
    }
  } else if (type == chrome::NOTIFICATION_BROWSER_OPENED) {
    // Store the new browser ID and continue waiting for a new tab within it
    // to stop loading.
    new_window_id_ = ExtensionTabUtil::GetWindowId(
        content::Source<Browser>(source).ptr());
  } else {
    DCHECK_EQ(content::NOTIFICATION_LOAD_STOP, type);
    // Only send the result if the loaded tab is in the new window.
    NavigationController* controller =
        content::Source<NavigationController>(source).ptr();
    SessionTabHelper* session_tab_helper =
        SessionTabHelper::FromWebContents(controller->GetWebContents());
    int window_id = session_tab_helper ? session_tab_helper->window_id().id()
                                       : -1;
    if (window_id == new_window_id_) {
      if (automation_.get()) {
        AutomationJSONReply(automation_.get(), reply_message_.release())
            .SendSuccess(NULL);
      }
      delete this;
    }
  }
}

ExtensionPopupObserver::ExtensionPopupObserver(
    AutomationProvider* automation,
    IPC::Message* reply_message,
    const std::string& extension_id)
    : automation_(automation->AsWeakPtr()),
      reply_message_(reply_message),
      extension_id_(extension_id) {
  registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_HOST_DID_STOP_LOADING,
                 content::NotificationService::AllSources());
}

ExtensionPopupObserver::~ExtensionPopupObserver() {
}

void ExtensionPopupObserver::Observe(
    int type,
    const content::NotificationSource& source,
    const content::NotificationDetails& details) {
  if (!automation_.get()) {
    delete this;
    return;
  }

  extensions::ExtensionHost* host =
      content::Details<extensions::ExtensionHost>(details).ptr();
  if (host->extension_id() == extension_id_ &&
      host->extension_host_type() == extensions::VIEW_TYPE_EXTENSION_POPUP) {
    AutomationJSONReply(automation_.get(), reply_message_.release())
        .SendSuccess(NULL);
    delete this;
  }
}

#if defined(OS_LINUX)
WindowMaximizedObserver::WindowMaximizedObserver(
    AutomationProvider* automation,
    IPC::Message* reply_message)
    : automation_(automation->AsWeakPtr()),
      reply_message_(reply_message) {
  registrar_.Add(this, chrome::NOTIFICATION_BROWSER_WINDOW_MAXIMIZED,
                 content::NotificationService::AllSources());
}

WindowMaximizedObserver::~WindowMaximizedObserver() {}

void WindowMaximizedObserver::Observe(
    int type,
    const content::NotificationSource& source,
    const content::NotificationDetails& details) {
  DCHECK_EQ(chrome::NOTIFICATION_BROWSER_WINDOW_MAXIMIZED, type);

  if (automation_.get()) {
    AutomationJSONReply(automation_.get(), reply_message_.release())
        .SendSuccess(NULL);
  }
  delete this;
}
#endif  // defined(OS_LINUX)

BrowserOpenedWithExistingProfileNotificationObserver::
    BrowserOpenedWithExistingProfileNotificationObserver(
        AutomationProvider* automation,
        IPC::Message* reply_message,
        int num_loads)
        : automation_(automation->AsWeakPtr()),
          reply_message_(reply_message),
          new_window_id_(extension_misc::kUnknownWindowId),
          num_loads_(num_loads) {
  registrar_.Add(this, chrome::NOTIFICATION_BROWSER_OPENED,
                 content::NotificationService::AllBrowserContextsAndSources());
  registrar_.Add(this, content::NOTIFICATION_LOAD_STOP,
                 content::NotificationService::AllBrowserContextsAndSources());
}

BrowserOpenedWithExistingProfileNotificationObserver::
    ~BrowserOpenedWithExistingProfileNotificationObserver() {
}

void BrowserOpenedWithExistingProfileNotificationObserver::Observe(
    int type,
    const content::NotificationSource& source,
    const content::NotificationDetails& details) {
  if (!automation_.get()) {
    delete this;
    return;
  }

  if (type == chrome::NOTIFICATION_BROWSER_OPENED) {
    // Store the new browser ID and continue waiting for NOTIFICATION_LOAD_STOP.
    new_window_id_ = ExtensionTabUtil::GetWindowId(
        content::Source<Browser>(source).ptr());
  } else if (type == content::NOTIFICATION_LOAD_STOP) {
    // Only consider if the loaded tab is in the new window.
    NavigationController* controller =
        content::Source<NavigationController>(source).ptr();
    SessionTabHelper* session_tab_helper =
        SessionTabHelper::FromWebContents(controller->GetWebContents());
    int window_id = session_tab_helper ? session_tab_helper->window_id().id()
                                       : -1;
    if (window_id == new_window_id_ && --num_loads_ == 0) {
      if (automation_.get()) {
        AutomationJSONReply(automation_.get(), reply_message_.release())
            .SendSuccess(NULL);
      }
      delete this;
    }
  } else {
    NOTREACHED();
  }
}


text:
<!DOCTYPE HTML>

		<title>skelJS Example</title>
		<meta http-equiv="content-type" content="text/html; charset=utf-8" />
		<link href="http://fonts.googleapis.com/css?family=Source+Sans+Pro:300,300italic,600|Source+Code+Pro" rel="stylesheet" />

My Converter

						Home
						About Me
						Blog
						Contact

Welcome to my converter!

Imperial Weight in Ounces

						<input id="ounceweight" type="number">

Metric Weight

						<input id="metricweight" type="number">

Converted Value

						<button id="calculatebtn">Calculate</button>

Heading

Lorem ipsum dolor sit amet sed magna etiam adipiscing elit.
							adipiscing elit nec fringilla urna tempor in. Cras et sodales
							consectetur viverra lorem ipsum. Lorem ipsum dolor sit amet
							sed magna etiam adipiscing elit adipiscing elit nec fringilla
							urna tempor in. Cras et sodales consectetur viverra.

Heading

Lorem ipsum dolor sit amet sed magna etiam adipiscing elit.
							adipiscing elit nec fringilla urna tempor in. Cras et sodales
							consectetur viverra lorem ipsum.

					© Example. All rights reserved.

redpajama_set_name: RedPajamaGithub


text:
<resources>
    <string name="app_name">Gruveo SDK example kotlin</string>
</resources>


text:
layout: post
date: 2015-03-10 14:08:00
title: Seoul National University
tags: [life]
---

So I'm finally here on exchange in South Korea at Seoul National University. It's my first winter and the weather is freezing. My friend insists that it's spring though; if so, I don't know how long I'm gonna take to adjust to this kind of weather.

Posting after quite a long time because I haven't started coding anything. Ever since I arrived I've been trying to settle immigration, administrative, financial and personal issues. Moreover, though school started a week ago, I haven't had to do any work yet.

Most of the day my mind is preoccupied with thoughts on how to keep warm. I even had to google it; just doesn't come naturally to someone who has been living in a tropical country all his life where the lowest temperature is 25 degree Celsius. Walking down the street, I have to clench my teeth to prevent audible chattering from shivering. I wonder if I'm doing something wrong. Anyway, weather is expected to only get warmer from now on so I'm hoping that happens soon.

Courses I'm taking here:
Database
Image Processing
Multi-core Computing
Programming Language

As I mentioned, there's nothing much to post about so I'll talk about one of the more interesting classes I'm taking here.


text:
PandocPublish
=============

Small tool feeding Pandoc with Markdown, and generating HTML, TeX and PDF, and
uploading everything online.

This is a really early version of something bigger. I mean, totally different,
it's currently for playing with Pandoc power for personnal reports.

Requirements
============

I'm using Pandoc with Pandoc-citeproc for processing data. In Ubuntu 12.04 LTS,
the versions in the repositories are too old, so you will need some time to do:

1. `sudo apt-get install haskell-platform`
2. `cabal install pandoc pandoc-citeproc`
3. Drink some tea, it's good for you

In case you upload online, you will need PHP for having the index of the files,
but you could manually create an index.html file aside if you want or don't
want PHP.

Configuration
=============

Currently, there is not much to configure. Just look for `# CONFIGURE HERE` in
the `generate.sh` file. If you do not need upload on a server, you can remove
everything from `echo "<?php` to the end.

Usgae
=====

Just open a terminal to the working directory and run the script
`./generate.sh SourceFile.md`. That's all.

TODO
====

1. Check script parameters
2. Move all that to a nice web interface
3. Let people change templates and stuff
4. Upload images


text:
package com.jxtech.tag.fullcalendar;

import com.jxtech.jbo.JboSetIFace;
import com.jxtech.tag.comm.JxBaseUIBean;
import com.jxtech.util.DateUtil;
import com.jxtech.util.StrUtil;
import com.opensymphony.xwork2.util.ValueStack;
import org.apache.struts2.views.annotations.StrutsTag;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * JQuery 之 fullCalendar控件再包装
 *
 * @author wmzsoft@gmail.com
 * @date 2014.04
 */
@StrutsTag(name = "fullCalendar", tldTagClass = "com.jxtech.tag.fullcalendar.FullCalendarTag", description = "FullCalendar")
public class FullCalendar extends JxBaseUIBean {
    protected String calendarEvent;// JSON格式的内容{日历属性1:字段名称1,日历属性2:字段名称2}
    protected String jboname;
    protected String wherecause;
    protected String startDate;// 开始时间,这个主要用来进行数据加载条件
    private JboSetIFace jboset;
    private String loadjs;
    protected String month;// 查询几月的数据,格式:yyyy-mm

    public FullCalendar(ValueStack stack, HttpServletRequest request, HttpServletResponse response) {
        super(stack, request, response);
    }

    @Override
    protected String getDefaultTemplate() {
        return "fullCalendar/close";
    }

    @Override
    public String getDefaultOpenTemplate() {
        return "fullCalendar/open";
    }

    @Override
    public void evaluateParams() {
        super.evaluateParams();
        if (jboname != null) {
            addParameter("jboname", findString(jboname));
        }
        addParameter("startDate", startDate);
        if (StrUtil.isNull(month)) {
            month = DateUtil.dateToString(null, "yyyy-MM");
            addParameter("month", month);
        } else {
            addParameter("month", findString(month));
        }
        if (calendarEvent != null) {
            addParameter("calendarEvent", calendarEvent);
        }
        if (loadjs != null) {
            addParameter("loadjs", findValue(loadjs, Boolean.class));
        }
        addParameter("jboset", jboset);
    }

    public void setCalendarEvent(String calendarEvent) {
        this.calendarEvent = calendarEvent;
    }

    public void setJboname(String jboname) {
        this.jboname = jboname;
    }

    public void setWherecause(String wherecause) {
        this.wherecause = wherecause;
    }

    public void setStartDate(String startDate) {
        this.startDate = startDate;
    }

    public void setJboset(JboSetIFace jboset) {
        this.jboset = jboset;
    }

    public void setMonth(String month) {
        this.month = month;
    }

    public void setLoadjs(String loadjs) {
        this.loadjs = loadjs;
    }
}


text:
namespace yarrar
{

struct MarkerValue
{
    bool valid;
    int id;
    Rotation90 zRotation;
};

class MarkerParser
{
public:
    virtual ~MarkerParser(){};
    virtual MarkerValue getData(const cv::Mat& image) = 0;
};
}


text:
import os

from django.conf import settings

def configure_settings():
    """
    Configures settings for manage.py and for run_tests.py.
    """
    if not settings.configured:
        # Determine the database settings depending on if a test_db var is set in CI mode or not
        test_db = os.environ.get('DB', None)
        if test_db is None:
            db_config = {
                'ENGINE': 'django.db.backends.postgresql_psycopg2',
                'NAME': 'postgres',
                'USER': 'postgres',
                'PASSWORD': '',
                'HOST': 'db'
            }
        elif test_db == 'postgres':
            db_config = {
                'ENGINE': 'django.db.backends.postgresql_psycopg2',
                'USER': 'postgres',
                'NAME': 'entity_history',
            }
        elif test_db == 'local':
            db_config = {
                'ENGINE': 'django.db.backends.postgresql_psycopg2',
                'USER': 'postgres',
                'NAME': 'entity_history',
                'HOST': 'db'
            }
        elif test_db == 'sqlite':
            db_config = {
                'ENGINE': 'django.db.backends.sqlite3',
                'NAME': 'entity_history',
            }
        else:
            raise RuntimeError('Unsupported test DB {0}'.format(test_db))

        settings.configure(
            TEST_RUNNER='django_nose.NoseTestSuiteRunner',
            NOSE_ARGS=['--nocapture', '--nologcapture', '--verbosity=1'],
            MIDDLEWARE_CLASSES=(),
            DATABASES={
                'default': db_config,
            },
            INSTALLED_APPS=(
                'django.contrib.auth',
                'django.contrib.contenttypes',
                'django.contrib.sessions',
                'django.contrib.admin',
                'entity',
                'entity_history',
                'entity_history.tests',
            ),
            ROOT_URLCONF='entity_history.urls',
            DEBUG=False,
        )