<commit_before>// Copyright (c) 2009 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/chromeos/status/language_menu_button.h"

#include <string>

#include "app/l10n_util.h"
#include "app/resource_bundle.h"
#include "base/time.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/chromeos/status/status_area_host.h"
#include "grit/generated_resources.h"
#include "grit/theme_resources.h"

// The language menu consists of 3 parts (in this order):
//
//   (1) XKB layout names and IME languages names. The size of the list is
//       always >= 1.
//   (2) IME properties. This list might be empty.
//   (3) "Configure IME..." button.
//
// Example of the menu (Japanese):
//
// ============================== (border of the popup window)
// [ ] US                         (|index| in the following functions is 0)
// [*] Anthy
// [ ] PinYin
// ------------------------------ (separator)
// [*] Hiragana                   (index = 5, The property has 2 radio groups)
// [ ] Katakana
// [ ] HalfWidthKatakana
// [*] Roman
// [ ] Kana
// ------------------------------ (separator)
// Configure IME...               (index = 11)
// ============================== (border of the popup window)
//
// Example of the menu (Simplified Chinese):
//
// ============================== (border of the popup window)
// [ ] US
// [ ] Anthy
// [*] PinYin
// ------------------------------ (separator)
// Switch to full letter mode     (The property has 2 command buttons)
// Switch to half punctuation mode
// ------------------------------ (separator)
// Configure IME...
// ============================== (border of the popup window)
//

namespace {

// Constants to specify the type of items in |model_|.
enum {
  COMMAND_ID_LANGUAGES = 0,  // US, Anthy, PinYin, ...
  COMMAND_ID_IME_PROPERTIES,  // Hiragana, Katakana, ...
  COMMAND_ID_CONFIGURE_IME,  // The "Configure IME..." button.
};

// A group ID for IME properties starts from 0. We use the huge value for the
// XKB/IME language list to avoid conflict.
const int kRadioGroupLanguage = 1 << 16;
const int kRadioGroupNone = -1;
const size_t kMaxLanguageNameLen = 7;
const wchar_t kSpacer[] = L"MMMMMMM";

// Returns the language name for the given |language|. Instead of input
// method names like "Pinyin" and "Anthy", we'll show language names like
// "Chinese (Simplified)" and "Japanese".
std::string GetLanguageName(const chromeos::InputLanguage& language) {
  std::string language_code = language.language_code;
  if (language.id == "pinyin") {
    // The pinyin input method returns "zh_CN" as language_code, but
    // l10n_util expects "zh-CN".
    language_code = "zh-CN";
  } else if (language.id == "chewing") {
    // Likewise, the chewing input method returns "zh" as language_code,
    // which is ambiguous. We use zh-TW instead.
    language_code = "zh-TW";
  } else if (language_code == "t") {
    // "t" is used by input methods that do not associate with a
    // particular language. Returns the display name as-is.
    return language.display_name;
  }
  const string16 language_name = l10n_util::GetDisplayNameForLocale(
      language_code,
      g_browser_process->GetApplicationLocale(),
      true);
  // TODO(satorux): We should add input method names if multiple input
  // methods are available for one input language.
  return UTF16ToUTF8(language_name);
}

// Converts chromeos::InputLanguage object into human readable string. Returns
// a string for the drop-down menu if |for_menu| is true. Otherwise, returns a
// string for the status area.
std::string FormatInputLanguage(
    const chromeos::InputLanguage& language, bool for_menu) {
  std::string formatted = GetLanguageName(language);
  if (formatted.empty()) {
    formatted = language.id;
  }
  if (for_menu) {
    switch (language.category) {
      case chromeos::LANGUAGE_CATEGORY_XKB:
        // TODO(yusukes): Use message catalog.
        formatted += " (Layout)";
        break;
      case chromeos::LANGUAGE_CATEGORY_IME:
        // TODO(yusukes): Use message catalog.
        formatted += " (IME)";
        break;
    }
  } else {
    // For status area. Trim the string.
    formatted = formatted.substr(0, kMaxLanguageNameLen);
    // TODO(yusukes): Simple substr() does not work for non-ASCII string.
    // TODO(yusukes): How can we ensure that the trimmed string does not
    // overflow the area?
  }
  return formatted;
}

}  // namespace

namespace chromeos {

////////////////////////////////////////////////////////////////////////////////
// LanguageMenuButton

LanguageMenuButton::LanguageMenuButton(StatusAreaHost* host)
    : MenuButton(NULL, std::wstring(), this, false),
      language_list_(LanguageLibrary::Get()->GetActiveLanguages()),
      model_(NULL),
      // Be aware that the constructor of |language_menu_| calls GetItemCount()
      // in this class. Therefore, GetItemCount() have to return 0 when
      // |model_| is NULL.
      ALLOW_THIS_IN_INITIALIZER_LIST(language_menu_(this)),
      host_(host) {
  DCHECK(language_list_.get() && !language_list_->empty());
  // Update the model
  RebuildModel();
  // Grab the real estate.
  UpdateIcon(kSpacer);
  // Display the default XKB name (usually "US").
  const std::string name = FormatInputLanguage(language_list_->at(0), false);
  UpdateIcon(UTF8ToWide(name));
  LanguageLibrary::Get()->AddObserver(this);
}

LanguageMenuButton::~LanguageMenuButton() {
  LanguageLibrary::Get()->RemoveObserver(this);
}

////////////////////////////////////////////////////////////////////////////////
// LanguageMenuButton, menus::MenuModel implementation:

int LanguageMenuButton::GetCommandIdAt(int index) const {
  return 0;  // dummy
}

bool LanguageMenuButton::IsLabelDynamicAt(int index) const {
  // Menu content for the language button could change time by time.
  return true;
}

bool LanguageMenuButton::GetAcceleratorAt(
    int index, menus::Accelerator* accelerator) const {
  // Views for Chromium OS does not support accelerators yet.
  return false;
}

bool LanguageMenuButton::IsItemCheckedAt(int index) const {
  DCHECK_GE(index, 0);
  DCHECK(language_list_.get());

  if (IndexIsInLanguageList(index)) {
    const InputLanguage& language = language_list_->at(index);
    return language == LanguageLibrary::Get()->current_language();
  }

  if (GetPropertyIndex(index, &index)) {
    const ImePropertyList& property_list
        = LanguageLibrary::Get()->current_ime_properties();
    return property_list.at(index).is_selection_item_checked;
  }

  // Separator(s) or the "Configure IME" button.
  return false;
}

int LanguageMenuButton::GetGroupIdAt(int index) const {
  DCHECK_GE(index, 0);

  if (IndexIsInLanguageList(index)) {
    return kRadioGroupLanguage;
  }

  if (GetPropertyIndex(index, &index)) {
    const ImePropertyList& property_list
        = LanguageLibrary::Get()->current_ime_properties();
    return property_list.at(index).selection_item_id;
  }

  return kRadioGroupNone;
}

bool LanguageMenuButton::HasIcons() const  {
  // We don't support IME nor keyboard icons on Chrome OS.
  return false;
}

bool LanguageMenuButton::GetIconAt(int index, SkBitmap* icon) const {
  return false;
}

bool LanguageMenuButton::IsEnabledAt(int index) const {
  // Just return true so all IMEs, XKB layouts, and IME properties could be
  // clicked.
  return true;
}

menus::MenuModel* LanguageMenuButton::GetSubmenuModelAt(int index) const {
  // We don't use nested menus.
  return NULL;
}

void LanguageMenuButton::HighlightChangedTo(int index) {
  // Views for Chromium OS does not support this interface yet.
}

void LanguageMenuButton::MenuWillShow() {
  // Views for Chromium OS does not support this interface yet.
}

int LanguageMenuButton::GetItemCount() const {
  if (!model_.get()) {
    // Model is not constructed yet. This means that LanguageMenuButton is
    // being constructed. Return zero.
    return 0;
  }
  return model_->GetItemCount();
}

menus::MenuModel::ItemType LanguageMenuButton::GetTypeAt(int index) const {
  DCHECK_GE(index, 0);

  if (IndexPointsToConfigureImeMenuItem(index)) {
    return menus::MenuModel::TYPE_COMMAND;  // "Configure IME"
  }

  if (IndexIsInLanguageList(index)) {
    return menus::MenuModel::TYPE_RADIO;
  }

  if (GetPropertyIndex(index, &index)) {
    const ImePropertyList& property_list
        = LanguageLibrary::Get()->current_ime_properties();
    if (property_list.at(index).is_selection_item) {
      return menus::MenuModel::TYPE_RADIO;
    }
    return menus::MenuModel::TYPE_COMMAND;
  }

  return menus::MenuModel::TYPE_SEPARATOR;
}

string16 LanguageMenuButton::GetLabelAt(int index) const {
  DCHECK_GE(index, 0);
  DCHECK(language_list_.get());

  if (IndexPointsToConfigureImeMenuItem(index)) {
    return l10n_util::GetStringUTF16(IDS_STATUSBAR_IME_CONFIGURE);
  }

  std::string name;
  if (IndexIsInLanguageList(index)) {
    name = FormatInputLanguage(language_list_->at(index), true);
  } else if (GetPropertyIndex(index, &index)) {
    const ImePropertyList& property_list
        = LanguageLibrary::Get()->current_ime_properties();
    name = property_list.at(index).label;
  }

  return UTF8ToUTF16(name);
}

void LanguageMenuButton::ActivatedAt(int index) {
  DCHECK_GE(index, 0);
  DCHECK(language_list_.get());

  if (IndexPointsToConfigureImeMenuItem(index)) {
    host_->OpenButtonOptions(this);
    return;
  }

  if (IndexIsInLanguageList(index)) {
    // Inter-IME switching or IME-XKB switching.
    const InputLanguage& language = language_list_->at(index);
    LanguageLibrary::Get()->ChangeLanguage(language.category, language.id);
    return;
  }

  if (GetPropertyIndex(index, &index)) {
    // Intra-IME switching (e.g. Japanese-Hiragana to Japanese-Katakana).
    const ImePropertyList& property_list
        = LanguageLibrary::Get()->current_ime_properties();
    const std::string key = property_list.at(index).key;
    if (property_list.at(index).is_selection_item) {
      // Radio button is clicked.
      const int id = property_list.at(index).selection_item_id;
      // First, deactivate all other properties in the same radio group.
      for (int i = 0; i < static_cast<int>(property_list.size()); ++i) {
        if (i != index && id == property_list.at(i).selection_item_id) {
          LanguageLibrary::Get()->DeactivateImeProperty(
              property_list.at(i).key);
        }
      }
      // Then, activate the property clicked.
      LanguageLibrary::Get()->ActivateImeProperty(key);
    } else {
      // Command button like "Switch to half punctuation mode" is clicked.
      // We can always use "Deactivate" for command buttons.
      LanguageLibrary::Get()->DeactivateImeProperty(key);
    }
    return;
  }

  // Separators are not clickable.
  NOTREACHED();
}

////////////////////////////////////////////////////////////////////////////////
// LanguageMenuButton, views::ViewMenuDelegate implementation:

void LanguageMenuButton::RunMenu(views::View* source, const gfx::Point& pt) {
  language_list_.reset(LanguageLibrary::Get()->GetActiveLanguages());
  RebuildModel();
  language_menu_.Rebuild();
  language_menu_.UpdateStates();
  language_menu_.RunMenuAt(pt, views::Menu2::ALIGN_TOPRIGHT);
}

////////////////////////////////////////////////////////////////////////////////
// LanguageLibrary::Observer implementation:

void LanguageMenuButton::LanguageChanged(LanguageLibrary* obj) {
  const std::string name = FormatInputLanguage(obj->current_language(), false);
  UpdateIcon(UTF8ToWide(name));

  // This is necessary to remove IME properties when the current language is
  // switched to XKB.
  RebuildModel();
}

void LanguageMenuButton::ImePropertiesChanged(LanguageLibrary* obj) {
  RebuildModel();
}

void LanguageMenuButton::UpdateIcon(const std::wstring& name) {
  set_border(NULL);
  SetFont(ResourceBundle::GetSharedInstance().GetFont(
      ResourceBundle::BaseFont).DeriveFont(0, gfx::Font::BOLD));
  SetEnabledColor(SK_ColorWHITE);
  SetShowHighlighted(false);
  SetText(name);
  set_alignment(TextButton::ALIGN_RIGHT);
  SchedulePaint();
}

void LanguageMenuButton::RebuildModel() {
  model_.reset(new menus::SimpleMenuModel(NULL));
  string16 dummy_label = UTF8ToUTF16("");
  // Indicates if separator's needed before each section.
  bool need_separator = false;

  if (!language_list_->empty()) {
    // We "abuse" the command_id and group_id arguments of AddRadioItem method.
    // A COMMAND_ID_XXX enum value is passed as command_id, and array index of
    // |language_list_| or |property_list| is passed as group_id.
    for (size_t i = 0; i < language_list_->size(); ++i) {
      model_->AddRadioItem(COMMAND_ID_LANGUAGES, dummy_label, i);
    }
    need_separator = true;
  }

  const ImePropertyList& property_list
      = LanguageLibrary::Get()->current_ime_properties();
  const InputLanguage& current_language
      = LanguageLibrary::Get()->current_language();
  if ((!property_list.empty()) &&
      (current_language.category == chromeos::LANGUAGE_CATEGORY_IME)) {
    if (need_separator)
      model_->AddSeparator();
    for (size_t i = 0; i < property_list.size(); ++i) {
      model_->AddRadioItem(COMMAND_ID_IME_PROPERTIES, dummy_label, i);
    }
    need_separator = true;
  }

  if (host_->ShouldOpenButtonOptions(this)) {
    // Note: We use AddSeparator() for separators, and AddRadioItem() for all
    // other items even if an item is not actually a radio item.
    if (need_separator)
      model_->AddSeparator();
    model_->AddRadioItem(COMMAND_ID_CONFIGURE_IME, dummy_label, 0 /* dummy */);
  }
}

bool LanguageMenuButton::IndexIsInLanguageList(int index) const {
  DCHECK_GE(index, 0);
  DCHECK(model_.get());

  return ((model_->GetTypeAt(index) == menus::MenuModel::TYPE_RADIO) &&
          (model_->GetCommandIdAt(index) == COMMAND_ID_LANGUAGES));
}

bool LanguageMenuButton::GetPropertyIndex(
    int index, int* property_index) const {
  DCHECK_GE(index, 0);
  DCHECK(property_index);
  DCHECK(model_.get());

  if ((model_->GetTypeAt(index) == menus::MenuModel::TYPE_RADIO) &&
      (model_->GetCommandIdAt(index) == COMMAND_ID_IME_PROPERTIES)) {
    *property_index = model_->GetGroupIdAt(index);
    return true;
  }
  return false;
}

bool LanguageMenuButton::IndexPointsToConfigureImeMenuItem(int index) const {
  DCHECK_GE(index, 0);
  DCHECK(model_.get());

  return ((model_->GetTypeAt(index) == menus::MenuModel::TYPE_RADIO) &&
          (model_->GetCommandIdAt(index) == COMMAND_ID_CONFIGURE_IME));
}

// TODO(yusukes): Register and handle hotkeys for IME and XKB switching?

}  // namespace chromeos
<commit_msg>Remove "(IME)" and "(Layout)" strings from the language status menu. I think we no longer need these debug print strings.<commit_after>// Copyright (c) 2009 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/chromeos/status/language_menu_button.h"

#include <string>

#include "app/l10n_util.h"
#include "app/resource_bundle.h"
#include "base/time.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/chromeos/status/status_area_host.h"
#include "grit/generated_resources.h"
#include "grit/theme_resources.h"

// The language menu consists of 3 parts (in this order):
//
//   (1) XKB layout names and IME languages names. The size of the list is
//       always >= 1.
//   (2) IME properties. This list might be empty.
//   (3) "Configure IME..." button.
//
// Example of the menu (Japanese):
//
// ============================== (border of the popup window)
// [ ] US                         (|index| in the following functions is 0)
// [*] Anthy
// [ ] PinYin
// ------------------------------ (separator)
// [*] Hiragana                   (index = 5, The property has 2 radio groups)
// [ ] Katakana
// [ ] HalfWidthKatakana
// [*] Roman
// [ ] Kana
// ------------------------------ (separator)
// Configure IME...               (index = 11)
// ============================== (border of the popup window)
//
// Example of the menu (Simplified Chinese):
//
// ============================== (border of the popup window)
// [ ] US
// [ ] Anthy
// [*] PinYin
// ------------------------------ (separator)
// Switch to full letter mode     (The property has 2 command buttons)
// Switch to half punctuation mode
// ------------------------------ (separator)
// Configure IME...
// ============================== (border of the popup window)
//

namespace {

// Constants to specify the type of items in |model_|.
enum {
  COMMAND_ID_LANGUAGES = 0,  // US, Anthy, PinYin, ...
  COMMAND_ID_IME_PROPERTIES,  // Hiragana, Katakana, ...
  COMMAND_ID_CONFIGURE_IME,  // The "Configure IME..." button.
};

// A group ID for IME properties starts from 0. We use the huge value for the
// XKB/IME language list to avoid conflict.
const int kRadioGroupLanguage = 1 << 16;
const int kRadioGroupNone = -1;
const size_t kMaxLanguageNameLen = 7;
const wchar_t kSpacer[] = L"MMMMMMM";

// Returns the language name for the given |language|. Instead of input
// method names like "Pinyin" and "Anthy", we'll show language names like
// "Chinese (Simplified)" and "Japanese".
std::string GetLanguageName(const chromeos::InputLanguage& language) {
  std::string language_code = language.language_code;
  if (language.id == "pinyin") {
    // The pinyin input method returns "zh_CN" as language_code, but
    // l10n_util expects "zh-CN".
    language_code = "zh-CN";
  } else if (language.id == "chewing") {
    // Likewise, the chewing input method returns "zh" as language_code,
    // which is ambiguous. We use zh-TW instead.
    language_code = "zh-TW";
  } else if (language_code == "t") {
    // "t" is used by input methods that do not associate with a
    // particular language. Returns the display name as-is.
    return language.display_name;
  }
  const string16 language_name = l10n_util::GetDisplayNameForLocale(
      language_code,
      g_browser_process->GetApplicationLocale(),
      true);
  // TODO(satorux): We should add input method names if multiple input
  // methods are available for one input language.
  return UTF16ToUTF8(language_name);
}

// Converts chromeos::InputLanguage object into human readable string. Returns
// a string for the drop-down menu if |for_menu| is true. Otherwise, returns a
// string for the status area.
std::string FormatInputLanguage(
    const chromeos::InputLanguage& language, bool for_menu) {
  std::string formatted = GetLanguageName(language);
  if (formatted.empty()) {
    formatted = language.id;
  }
  if (!for_menu) {
    // For status area. Trim the string.
    formatted = formatted.substr(0, kMaxLanguageNameLen);
    // TODO(yusukes): Simple substr() does not work for non-ASCII string.
    // TODO(yusukes): How can we ensure that the trimmed string does not
    // overflow the area?
  }
  return formatted;
}

}  // namespace

namespace chromeos {

////////////////////////////////////////////////////////////////////////////////
// LanguageMenuButton

LanguageMenuButton::LanguageMenuButton(StatusAreaHost* host)
    : MenuButton(NULL, std::wstring(), this, false),
      language_list_(LanguageLibrary::Get()->GetActiveLanguages()),
      model_(NULL),
      // Be aware that the constructor of |language_menu_| calls GetItemCount()
      // in this class. Therefore, GetItemCount() have to return 0 when
      // |model_| is NULL.
      ALLOW_THIS_IN_INITIALIZER_LIST(language_menu_(this)),
      host_(host) {
  DCHECK(language_list_.get() && !language_list_->empty());
  // Update the model
  RebuildModel();
  // Grab the real estate.
  UpdateIcon(kSpacer);
  // Display the default XKB name (usually "US").
  const std::string name = FormatInputLanguage(language_list_->at(0), false);
  UpdateIcon(UTF8ToWide(name));
  LanguageLibrary::Get()->AddObserver(this);
}

LanguageMenuButton::~LanguageMenuButton() {
  LanguageLibrary::Get()->RemoveObserver(this);
}

////////////////////////////////////////////////////////////////////////////////
// LanguageMenuButton, menus::MenuModel implementation:

int LanguageMenuButton::GetCommandIdAt(int index) const {
  return 0;  // dummy
}

bool LanguageMenuButton::IsLabelDynamicAt(int index) const {
  // Menu content for the language button could change time by time.
  return true;
}

bool LanguageMenuButton::GetAcceleratorAt(
    int index, menus::Accelerator* accelerator) const {
  // Views for Chromium OS does not support accelerators yet.
  return false;
}

bool LanguageMenuButton::IsItemCheckedAt(int index) const {
  DCHECK_GE(index, 0);
  DCHECK(language_list_.get());

  if (IndexIsInLanguageList(index)) {
    const InputLanguage& language = language_list_->at(index);
    return language == LanguageLibrary::Get()->current_language();
  }

  if (GetPropertyIndex(index, &index)) {
    const ImePropertyList& property_list
        = LanguageLibrary::Get()->current_ime_properties();
    return property_list.at(index).is_selection_item_checked;
  }

  // Separator(s) or the "Configure IME" button.
  return false;
}

int LanguageMenuButton::GetGroupIdAt(int index) const {
  DCHECK_GE(index, 0);

  if (IndexIsInLanguageList(index)) {
    return kRadioGroupLanguage;
  }

  if (GetPropertyIndex(index, &index)) {
    const ImePropertyList& property_list
        = LanguageLibrary::Get()->current_ime_properties();
    return property_list.at(index).selection_item_id;
  }

  return kRadioGroupNone;
}

bool LanguageMenuButton::HasIcons() const  {
  // We don't support IME nor keyboard icons on Chrome OS.
  return false;
}

bool LanguageMenuButton::GetIconAt(int index, SkBitmap* icon) const {
  return false;
}

bool LanguageMenuButton::IsEnabledAt(int index) const {
  // Just return true so all IMEs, XKB layouts, and IME properties could be
  // clicked.
  return true;
}

menus::MenuModel* LanguageMenuButton::GetSubmenuModelAt(int index) const {
  // We don't use nested menus.
  return NULL;
}

void LanguageMenuButton::HighlightChangedTo(int index) {
  // Views for Chromium OS does not support this interface yet.
}

void LanguageMenuButton::MenuWillShow() {
  // Views for Chromium OS does not support this interface yet.
}

int LanguageMenuButton::GetItemCount() const {
  if (!model_.get()) {
    // Model is not constructed yet. This means that LanguageMenuButton is
    // being constructed. Return zero.
    return 0;
  }
  return model_->GetItemCount();
}

menus::MenuModel::ItemType LanguageMenuButton::GetTypeAt(int index) const {
  DCHECK_GE(index, 0);

  if (IndexPointsToConfigureImeMenuItem(index)) {
    return menus::MenuModel::TYPE_COMMAND;  // "Configure IME"
  }

  if (IndexIsInLanguageList(index)) {
    return menus::MenuModel::TYPE_RADIO;
  }

  if (GetPropertyIndex(index, &index)) {
    const ImePropertyList& property_list
        = LanguageLibrary::Get()->current_ime_properties();
    if (property_list.at(index).is_selection_item) {
      return menus::MenuModel::TYPE_RADIO;
    }
    return menus::MenuModel::TYPE_COMMAND;
  }

  return menus::MenuModel::TYPE_SEPARATOR;
}

string16 LanguageMenuButton::GetLabelAt(int index) const {
  DCHECK_GE(index, 0);
  DCHECK(language_list_.get());

  if (IndexPointsToConfigureImeMenuItem(index)) {
    return l10n_util::GetStringUTF16(IDS_STATUSBAR_IME_CONFIGURE);
  }

  std::string name;
  if (IndexIsInLanguageList(index)) {
    name = FormatInputLanguage(language_list_->at(index), true);
  } else if (GetPropertyIndex(index, &index)) {
    const ImePropertyList& property_list
        = LanguageLibrary::Get()->current_ime_properties();
    name = property_list.at(index).label;
  }

  return UTF8ToUTF16(name);
}

void LanguageMenuButton::ActivatedAt(int index) {
  DCHECK_GE(index, 0);
  DCHECK(language_list_.get());

  if (IndexPointsToConfigureImeMenuItem(index)) {
    host_->OpenButtonOptions(this);
    return;
  }

  if (IndexIsInLanguageList(index)) {
    // Inter-IME switching or IME-XKB switching.
    const InputLanguage& language = language_list_->at(index);
    LanguageLibrary::Get()->ChangeLanguage(language.category, language.id);
    return;
  }

  if (GetPropertyIndex(index, &index)) {
    // Intra-IME switching (e.g. Japanese-Hiragana to Japanese-Katakana).
    const ImePropertyList& property_list
        = LanguageLibrary::Get()->current_ime_properties();
    const std::string key = property_list.at(index).key;
    if (property_list.at(index).is_selection_item) {
      // Radio button is clicked.
      const int id = property_list.at(index).selection_item_id;
      // First, deactivate all other properties in the same radio group.
      for (int i = 0; i < static_cast<int>(property_list.size()); ++i) {
        if (i != index && id == property_list.at(i).selection_item_id) {
          LanguageLibrary::Get()->DeactivateImeProperty(
              property_list.at(i).key);
        }
      }
      // Then, activate the property clicked.
      LanguageLibrary::Get()->ActivateImeProperty(key);
    } else {
      // Command button like "Switch to half punctuation mode" is clicked.
      // We can always use "Deactivate" for command buttons.
      LanguageLibrary::Get()->DeactivateImeProperty(key);
    }
    return;
  }

  // Separators are not clickable.
  NOTREACHED();
}

////////////////////////////////////////////////////////////////////////////////
// LanguageMenuButton, views::ViewMenuDelegate implementation:

void LanguageMenuButton::RunMenu(views::View* source, const gfx::Point& pt) {
  language_list_.reset(LanguageLibrary::Get()->GetActiveLanguages());
  RebuildModel();
  language_menu_.Rebuild();
  language_menu_.UpdateStates();
  language_menu_.RunMenuAt(pt, views::Menu2::ALIGN_TOPRIGHT);
}

////////////////////////////////////////////////////////////////////////////////
// LanguageLibrary::Observer implementation:

void LanguageMenuButton::LanguageChanged(LanguageLibrary* obj) {
  const std::string name = FormatInputLanguage(obj->current_language(), false);
  UpdateIcon(UTF8ToWide(name));

  // This is necessary to remove IME properties when the current language is
  // switched to XKB.
  RebuildModel();
}

void LanguageMenuButton::ImePropertiesChanged(LanguageLibrary* obj) {
  RebuildModel();
}

void LanguageMenuButton::UpdateIcon(const std::wstring& name) {
  set_border(NULL);
  SetFont(ResourceBundle::GetSharedInstance().GetFont(
      ResourceBundle::BaseFont).DeriveFont(0, gfx::Font::BOLD));
  SetEnabledColor(SK_ColorWHITE);
  SetShowHighlighted(false);
  SetText(name);
  set_alignment(TextButton::ALIGN_RIGHT);
  SchedulePaint();
}

void LanguageMenuButton::RebuildModel() {
  model_.reset(new menus::SimpleMenuModel(NULL));
  string16 dummy_label = UTF8ToUTF16("");
  // Indicates if separator's needed before each section.
  bool need_separator = false;

  if (!language_list_->empty()) {
    // We "abuse" the command_id and group_id arguments of AddRadioItem method.
    // A COMMAND_ID_XXX enum value is passed as command_id, and array index of
    // |language_list_| or |property_list| is passed as group_id.
    for (size_t i = 0; i < language_list_->size(); ++i) {
      model_->AddRadioItem(COMMAND_ID_LANGUAGES, dummy_label, i);
    }
    need_separator = true;
  }

  const ImePropertyList& property_list
      = LanguageLibrary::Get()->current_ime_properties();
  const InputLanguage& current_language
      = LanguageLibrary::Get()->current_language();
  if ((!property_list.empty()) &&
      (current_language.category == chromeos::LANGUAGE_CATEGORY_IME)) {
    if (need_separator)
      model_->AddSeparator();
    for (size_t i = 0; i < property_list.size(); ++i) {
      model_->AddRadioItem(COMMAND_ID_IME_PROPERTIES, dummy_label, i);
    }
    need_separator = true;
  }

  if (host_->ShouldOpenButtonOptions(this)) {
    // Note: We use AddSeparator() for separators, and AddRadioItem() for all
    // other items even if an item is not actually a radio item.
    if (need_separator)
      model_->AddSeparator();
    model_->AddRadioItem(COMMAND_ID_CONFIGURE_IME, dummy_label, 0 /* dummy */);
  }
}

bool LanguageMenuButton::IndexIsInLanguageList(int index) const {
  DCHECK_GE(index, 0);
  DCHECK(model_.get());

  return ((model_->GetTypeAt(index) == menus::MenuModel::TYPE_RADIO) &&
          (model_->GetCommandIdAt(index) == COMMAND_ID_LANGUAGES));
}

bool LanguageMenuButton::GetPropertyIndex(
    int index, int* property_index) const {
  DCHECK_GE(index, 0);
  DCHECK(property_index);
  DCHECK(model_.get());

  if ((model_->GetTypeAt(index) == menus::MenuModel::TYPE_RADIO) &&
      (model_->GetCommandIdAt(index) == COMMAND_ID_IME_PROPERTIES)) {
    *property_index = model_->GetGroupIdAt(index);
    return true;
  }
  return false;
}

bool LanguageMenuButton::IndexPointsToConfigureImeMenuItem(int index) const {
  DCHECK_GE(index, 0);
  DCHECK(model_.get());

  return ((model_->GetTypeAt(index) == menus::MenuModel::TYPE_RADIO) &&
          (model_->GetCommandIdAt(index) == COMMAND_ID_CONFIGURE_IME));
}

// TODO(yusukes): Register and handle hotkeys for IME and XKB switching?

}  // namespace chromeos
<|endoftext|>


<commit_before>#include "shader_program.h"
#include "pathos/engine.h"
#include "pathos/console.h"
#include "pathos/render/render_device.h"
#include "pathos/util/log.h"
#include "pathos/util/resource_finder.h"

#include "badger/assertion/assertion.h"

#include <fstream>
#include <sstream>

#define IGNORE_SAME_SHADERS_ON_RECOMPILE 1

namespace pathos {

	// Turn this on EngineConfig.ini as shaders are compiled on startup and you don't have a chance to change this through console.
	static ConsoleVariable<int32> cvar_dumpShaders("r.dumpShaderSources", 0, "Dump shader sources to log/shader_dump");

	static struct InitRecompileShaders {
		InitRecompileShaders() {
			Engine::internal_registerGlobalRenderRoutine(InitRecompileShaders::recompileShaders, nullptr);
		}
		// Dirty but works anyway
		static void recompileShaders(OpenGLDevice* device, RenderCommandList& cmdList) {
			gEngine->registerExec("recompile_shaders", [](const std::string& command) -> void {
				LOG(LogInfo, "Begin reloading shaders...");
				ENQUEUE_RENDER_COMMAND([](RenderCommandList& cmdList) {
					ShaderDB::get().forEach([](ShaderProgram* program) -> void {
						program->reload();
					});
				});
				FLUSH_RENDER_COMMAND();
				LOG(LogInfo, "End reloading shaders.");
			});
		}
	} internal_recompileShaders;

	ShaderProgram::ShaderProgram(const char* inDebugName, uint32 inProgramHash)
		: debugName(inDebugName)
		, programHash(inProgramHash)
		, glName(0xffffffff)
		, firstLoad(true)
		, internal_justInstantiated(true)
	{
		ShaderDB::get().registerProgram(inProgramHash, this);
	}

	ShaderProgram::~ShaderProgram()
	{
		CHECK(isValid());
		ShaderDB::get().unregisterProgram(programHash);
	}

	void ShaderProgram::addShaderStage(ShaderStage* shaderStage)
	{
		shaderStages.push_back(shaderStage);
	}

	void ShaderProgram::reload()
	{
		GLuint oldGLName = glName;
		bool oldValid = isValid();

		// Try to compile shader stages
		bool allCompiled = true;
		bool allNotChanged = true;
		for (ShaderStage* shaderStage : shaderStages) {
			ShaderStage::CompileResponse response = shaderStage->tryCompile();
			allCompiled = allCompiled && response != ShaderStage::CompileResponse::Failed;
			allNotChanged = allNotChanged && response == ShaderStage::CompileResponse::NotChanged;
		}
		if (allNotChanged) {
#if IGNORE_SAME_SHADERS_ON_RECOMPILE == 0
			LOG(LogDebug, "%s: All shader stages are not changed, won't recompile.", debugName);
#endif
			return;
		}
		if (!allCompiled) {
			LOG(LogDebug, "%s: Failed to compile some shader stages.", debugName);
			return;
		}

		// Finalize shader compilation
		for (ShaderStage* shaderStage : shaderStages) {
			shaderStage->finishCompile();
		}

		// Attach and link shader stages
		glName = glCreateProgram();
		glObjectLabel(GL_PROGRAM, glName, -1, debugName);
		for (ShaderStage* shaderStage : shaderStages) {
			glAttachShader(glName, shaderStage->getGLName());
		}
		glLinkProgram(glName);

		GLint isLinked = 0;
		glGetProgramiv(glName, GL_LINK_STATUS, &isLinked);

		if (isLinked == GL_FALSE) {
			// If linking failed, leave the old program as is.
			GLint maxLength = 0;
			glGetProgramiv(glName, GL_INFO_LOG_LENGTH, &maxLength);
			if (maxLength == 0) maxLength = 1024;
			std::vector<GLchar> infoLog(maxLength);
			glGetProgramInfoLog(glName, maxLength, &maxLength, infoLog.data());

			LOG(LogError, "program link error(code=%d): %s", glGetError(), infoLog.data());

			glDeleteProgram(glName);
			glName = oldGLName;
		} else {
			// If linked, replace old program with new one.
			if (oldValid) {
				glDeleteProgram(oldGLName);
			}
			if (!firstLoad) {
				LOG(LogDebug, "%s: Recompiled the shader program.", debugName);
			}
		}
	}

	void ShaderProgram::checkFirstLoad()
	{
		if (firstLoad) {
			reload();
			firstLoad = false;
		}
	}

	////////////////////////////////////////////////////////////

	ShaderStage::ShaderStage(GLenum inShaderType, const char* inDebugName)
		: shaderType(inShaderType)
		, debugName(inDebugName)
		, glName(0)
		, pendingGLName(0)
		, filepath(nullptr)
	{
		CHECK( inShaderType == GL_VERTEX_SHADER
			|| inShaderType == GL_GEOMETRY_SHADER
			|| inShaderType == GL_TESS_CONTROL_SHADER
			|| inShaderType == GL_TESS_EVALUATION_SHADER
			|| inShaderType == GL_FRAGMENT_SHADER
			|| inShaderType == GL_COMPUTE_SHADER);
		CHECK(inDebugName != nullptr);
	}

	ShaderStage::~ShaderStage()
	{
		if (glName != 0) {
			glDeleteShader(glName);
		}
	}

	void ShaderStage::addDefine(const char* define, int32 value) {
		CHECK(define != nullptr);
		char msg[256];
		sprintf_s(msg, "%s %d", define, value);
		addDefine(msg);
	}

	bool ShaderStage::loadSource()
	{
		CHECK(filepath != nullptr);

		std::string fullFilepath = ResourceFinder::get().find(filepath);

		if (fullFilepath.size() == 0) {
			LOG(LogError, "[%s]: Couldn't find file: %s", __FUNCTION__, filepath);
			return false;
		}

		std::ifstream fileStream(fullFilepath);
		if (fileStream.is_open() == false) {
			LOG(LogError, "[%s]: Couldn't open file: %s", __FUNCTION__, filepath);
			return false;
		}

		std::ostringstream codeStream;
		codeStream << fileStream.rdbuf();
		std::string fullCode = std::move(codeStream.str());

		size_t version_start = fullCode.find("#version");
		if (version_start == std::string::npos) {
			LOG(LogError, "[%s]: GLSL source file should contain '#version' statement", __FUNCTION__);
			return false;
		}

		size_t version_end = fullCode.find_first_of('\n', version_start);

		// Add defines
		if (defines.size() > 0) {
			codeStream.clear();
			codeStream.str("");
			// Put #version back
			codeStream << fullCode.substr(version_start, version_end - version_start + 1);
			for (const std::string& def : defines) {
				codeStream << "#define " << def << '\n';
			}
			codeStream << fullCode.substr(version_end + 1);
			fullCode = std::move(codeStream.str());
		}

		sourceCode.clear();

		size_t find_offset = 0u;
		while (true) {
			// #todo-shader: Need to skip '#include' in comments
			size_t include_start = fullCode.find("#include", find_offset);
			if (include_start == std::string::npos) {
				break;
			}

			size_t include_end = fullCode.find_first_of('\n', include_start);
			sourceCode.emplace_back(fullCode.substr(0, include_start));
			std::string include_line = fullCode.substr(include_start, include_end - include_start);

			size_t quote_start = include_line.find('"');
			size_t quote_end = include_line.find('"', quote_start + 1);
			CHECK(quote_start != std::string::npos && quote_end != std::string::npos);

			// #todo-shader: Support recursive include?
			std::string include_file = include_line.substr(quote_start + 1, quote_end - quote_start - 1);
			std::string include_filepath = ResourceFinder::get().find(include_file);
			std::ifstream subfile(include_filepath);
			if (!subfile.is_open()) {
				LOG(LogError, "Couldn't open a #include file: %s", include_filepath.c_str());
				return false;
			}
			std::ostringstream substream;
			substream << subfile.rdbuf();
			sourceCode.emplace_back(substream.str());

			fullCode = fullCode.substr(include_end + 1);
		}

		sourceCode.emplace_back(fullCode);

		return true;
	}

	ShaderStage::CompileResponse ShaderStage::tryCompile() {
		std::vector<std::string> sourceCodeBackup = sourceCode;
		loadSource();

		bool sourceChanged = false;
		if (sourceCode.size() == sourceCodeBackup.size()) {
			for (uint32 i = 0; i < sourceCode.size(); ++i) {
				if (sourceCode[i] != sourceCodeBackup[i]) {
					sourceChanged = true;
					break;
				}
			}
		} else {
			sourceChanged = true;
		}
		if (sourceChanged == false) {
#if IGNORE_SAME_SHADERS_ON_RECOMPILE == 0
			LOG(LogDebug, "%s: Source code is same.", debugName);
#endif
			return ShaderStage::CompileResponse::NotChanged;
		}

		if (pendingGLName != 0) {
			glDeleteShader(pendingGLName);
		}

		// Dump final shader source to log/shader_dump/
		if (cvar_dumpShaders.getInt() != 0) {
			std::string basedir = pathos::getSolutionDir();
			basedir += "log/shader_dump/";
			pathos::createDirectory(basedir.c_str());

			std::string dumpPath = basedir + filepath;

			std::fstream fs(dumpPath, std::fstream::out);
			if (fs.is_open()) {
				for (uint32 i = 0; i < sourceCode.size(); ++i) {
					fs << sourceCode[i] << std::endl;
				}
				fs.close();
			}
		}

		std::vector<const char*> sourceList;
		for (const auto& s : sourceCode) {
			sourceList.push_back(s.c_str());
		}

		pendingGLName = glCreateShader(shaderType);
		glShaderSource(pendingGLName, (GLsizei)sourceList.size(), sourceList.data(), NULL);
		glCompileShader(pendingGLName);

		GLint success;
		glGetShaderiv(pendingGLName, GL_COMPILE_STATUS, &success);

		if (success == GL_FALSE) {
			GLint logSize;
			glGetShaderiv(pendingGLName, GL_INFO_LOG_LENGTH, &logSize);

			std::string errorLog;
			errorLog.resize(logSize);
			glGetShaderInfoLog(pendingGLName, logSize, NULL, &errorLog[0]);
			LOG(LogError, "Failed to compile shader (%s) : %s", debugName, errorLog.c_str());

			glDeleteShader(pendingGLName);
			pendingGLName = 0;

			return ShaderStage::CompileResponse::Failed;
		}

		return ShaderStage::CompileResponse::Compiled;
	}

	bool ShaderStage::finishCompile()
	{
		if (pendingGLName == 0) {
			return false;
		}

		if (glName != 0) {
			glDeleteShader(glName);
		}

		glName = pendingGLName;
		pendingGLName = 0;

		glObjectLabel(GL_SHADER, glName, -1, debugName);

		return true;
	}

}
<commit_msg>Skip #include in comments<commit_after>#include "shader_program.h"
#include "pathos/engine.h"
#include "pathos/console.h"
#include "pathos/render/render_device.h"
#include "pathos/util/log.h"
#include "pathos/util/resource_finder.h"

#include "badger/assertion/assertion.h"

#include <fstream>
#include <sstream>

#define IGNORE_SAME_SHADERS_ON_RECOMPILE 1

namespace pathos {

	// Turn this on EngineConfig.ini as shaders are compiled on startup and you don't have a chance to change this through console.
	static ConsoleVariable<int32> cvar_dumpShaders("r.dumpShaderSources", 0, "Dump shader sources to log/shader_dump");

	static struct InitRecompileShaders {
		InitRecompileShaders() {
			Engine::internal_registerGlobalRenderRoutine(InitRecompileShaders::recompileShaders, nullptr);
		}
		// Dirty but works anyway
		static void recompileShaders(OpenGLDevice* device, RenderCommandList& cmdList) {
			gEngine->registerExec("recompile_shaders", [](const std::string& command) -> void {
				LOG(LogInfo, "Begin reloading shaders...");
				ENQUEUE_RENDER_COMMAND([](RenderCommandList& cmdList) {
					ShaderDB::get().forEach([](ShaderProgram* program) -> void {
						program->reload();
					});
				});
				FLUSH_RENDER_COMMAND();
				LOG(LogInfo, "End reloading shaders.");
			});
		}
	} internal_recompileShaders;

	ShaderProgram::ShaderProgram(const char* inDebugName, uint32 inProgramHash)
		: debugName(inDebugName)
		, programHash(inProgramHash)
		, glName(0xffffffff)
		, firstLoad(true)
		, internal_justInstantiated(true)
	{
		ShaderDB::get().registerProgram(inProgramHash, this);
	}

	ShaderProgram::~ShaderProgram()
	{
		CHECK(isValid());
		ShaderDB::get().unregisterProgram(programHash);
	}

	void ShaderProgram::addShaderStage(ShaderStage* shaderStage)
	{
		shaderStages.push_back(shaderStage);
	}

	void ShaderProgram::reload()
	{
		CHECK(isInRenderThread());

		GLuint oldGLName = glName;
		bool oldValid = isValid();

		// Try to compile shader stages
		bool allCompiled = true;
		bool allNotChanged = true;
		for (ShaderStage* shaderStage : shaderStages) {
			ShaderStage::CompileResponse response = shaderStage->tryCompile();
			allCompiled = allCompiled && response != ShaderStage::CompileResponse::Failed;
			allNotChanged = allNotChanged && response == ShaderStage::CompileResponse::NotChanged;
		}
		if (allNotChanged) {
#if IGNORE_SAME_SHADERS_ON_RECOMPILE == 0
			LOG(LogDebug, "%s: All shader stages are not changed, won't recompile.", debugName);
#endif
			return;
		}
		if (!allCompiled) {
			LOG(LogDebug, "%s: Failed to compile some shader stages.", debugName);
			return;
		}

		// Finalize shader compilation
		for (ShaderStage* shaderStage : shaderStages) {
			shaderStage->finishCompile();
		}

		// Attach and link shader stages
		glName = glCreateProgram();
		glObjectLabel(GL_PROGRAM, glName, -1, debugName);
		for (ShaderStage* shaderStage : shaderStages) {
			glAttachShader(glName, shaderStage->getGLName());
		}
		glLinkProgram(glName);

		GLint isLinked = 0;
		glGetProgramiv(glName, GL_LINK_STATUS, &isLinked);

		if (isLinked == GL_FALSE) {
			// If linking failed, leave the old program as is.
			GLint maxLength = 0;
			glGetProgramiv(glName, GL_INFO_LOG_LENGTH, &maxLength);
			if (maxLength == 0) maxLength = 1024;
			std::vector<GLchar> infoLog(maxLength);
			glGetProgramInfoLog(glName, maxLength, &maxLength, infoLog.data());

			LOG(LogError, "program link error(code=%d): %s", glGetError(), infoLog.data());

			glDeleteProgram(glName);
			glName = oldGLName;
		} else {
			// If linked, replace old program with new one.
			if (oldValid) {
				glDeleteProgram(oldGLName);
			}
			if (!firstLoad) {
				LOG(LogDebug, "%s: Recompiled the shader program.", debugName);
			}
		}
	}

	void ShaderProgram::checkFirstLoad()
	{
		if (firstLoad) {
			reload();
			firstLoad = false;
		}
	}

	////////////////////////////////////////////////////////////

	ShaderStage::ShaderStage(GLenum inShaderType, const char* inDebugName)
		: shaderType(inShaderType)
		, debugName(inDebugName)
		, glName(0)
		, pendingGLName(0)
		, filepath(nullptr)
	{
		CHECK( inShaderType == GL_VERTEX_SHADER
			|| inShaderType == GL_GEOMETRY_SHADER
			|| inShaderType == GL_TESS_CONTROL_SHADER
			|| inShaderType == GL_TESS_EVALUATION_SHADER
			|| inShaderType == GL_FRAGMENT_SHADER
			|| inShaderType == GL_COMPUTE_SHADER);
		CHECK(inDebugName != nullptr);
	}

	ShaderStage::~ShaderStage()
	{
		CHECK(isInRenderThread());

		if (glName != 0) {
			glDeleteShader(glName);
		}
	}

	void ShaderStage::addDefine(const char* define, int32 value) {
		CHECK(define != nullptr);
		char msg[256];
		sprintf_s(msg, "%s %d", define, value);
		addDefine(msg);
	}

	// #todo-shader: This is getting too dirty
	bool ShaderStage::loadSource()
	{
		CHECK(filepath != nullptr);

		std::string fullFilepath = ResourceFinder::get().find(filepath);

		if (fullFilepath.size() == 0) {
			LOG(LogError, "[%s]: Couldn't find file: %s", __FUNCTION__, filepath);
			return false;
		}

		std::ifstream fileStream(fullFilepath);
		if (fileStream.is_open() == false) {
			LOG(LogError, "[%s]: Couldn't open file: %s", __FUNCTION__, filepath);
			return false;
		}

		std::ostringstream codeStream;
		codeStream << fileStream.rdbuf();
		std::string fullCode = std::move(codeStream.str());

		size_t version_start = fullCode.find("#version");
		if (version_start == std::string::npos) {
			LOG(LogError, "[%s]: GLSL source file should contain '#version' statement", __FUNCTION__);
			return false;
		}

		size_t version_end = fullCode.find_first_of('\n', version_start);

		// Add defines
		if (defines.size() > 0) {
			codeStream.clear();
			codeStream.str("");
			// Put #version back
			codeStream << fullCode.substr(version_start, version_end - version_start + 1);
			for (const std::string& def : defines) {
				codeStream << "#define " << def << '\n';
			}
			codeStream << fullCode.substr(version_end + 1);
			fullCode = std::move(codeStream.str());
		}

		sourceCode.clear();

		// Parse include statements
		size_t find_offset = 0u;
		while (true) {
			size_t include_start = fullCode.find("#include", find_offset);
			if (include_start == std::string::npos) {
				break;
			}

			bool isComment = false;
			size_t include_line_start = include_start;
			while (include_line_start > 0 && fullCode[include_line_start - 1] != '\n') {
				include_line_start -= 1;
			}
			// Same as std::isspace() https://en.cppreference.com/w/cpp/string/byte/isspace
			include_line_start = fullCode.find_first_not_of(" \t\n\r\f\v", include_line_start);
			isComment = (fullCode[include_line_start] == '/' && fullCode[include_line_start + 1] == '/');

			size_t include_end = fullCode.find_first_of('\n', include_start);

			if (isComment) {
				sourceCode.emplace_back(fullCode.substr(0, include_end));
				fullCode = fullCode.substr(include_end + 1);
				continue;
			}

			sourceCode.emplace_back(fullCode.substr(0, include_start));
			std::string include_line = fullCode.substr(include_start, include_end - include_start);

			size_t quote_start = include_line.find('"');
			size_t quote_end = include_line.find('"', quote_start + 1);
			CHECK(quote_start != std::string::npos && quote_end != std::string::npos);

			// #todo-shader: Support recursive include?
			std::string include_file = include_line.substr(quote_start + 1, quote_end - quote_start - 1);
			std::string include_filepath = ResourceFinder::get().find(include_file);
			std::ifstream subfile(include_filepath);
			if (!subfile.is_open()) {
				LOG(LogError, "Couldn't open a #include file: %s", include_filepath.c_str());
				return false;
			}
			std::ostringstream substream;
			substream << subfile.rdbuf();
			sourceCode.emplace_back(substream.str());

			fullCode = fullCode.substr(include_end + 1);
		}

		sourceCode.emplace_back(fullCode);

		return true;
	}

	ShaderStage::CompileResponse ShaderStage::tryCompile() {
		std::vector<std::string> sourceCodeBackup = sourceCode;
		loadSource();

		bool sourceChanged = false;
		if (sourceCode.size() == sourceCodeBackup.size()) {
			for (size_t i = 0; i < sourceCode.size(); ++i) {
				if (sourceCode[i] != sourceCodeBackup[i]) {
					sourceChanged = true;
					break;
				}
			}
		} else {
			sourceChanged = true;
		}
		if (sourceChanged == false) {
#if IGNORE_SAME_SHADERS_ON_RECOMPILE == 0
			LOG(LogDebug, "%s: Source code is same.", debugName);
#endif
			return ShaderStage::CompileResponse::NotChanged;
		}

		if (pendingGLName != 0) {
			glDeleteShader(pendingGLName);
		}

		// Dump final shader source to log/shader_dump/
		if (cvar_dumpShaders.getInt() != 0) {
			std::string basedir = pathos::getSolutionDir();
			basedir += "log/shader_dump/";
			pathos::createDirectory(basedir.c_str());

			std::string dumpPath = basedir + filepath;

			std::fstream fs(dumpPath, std::fstream::out);
			if (fs.is_open()) {
				for (uint32 i = 0; i < sourceCode.size(); ++i) {
					fs << sourceCode[i] << std::endl;
				}
				fs.close();
			}
		}

		std::vector<const char*> sourceList;
		for (const auto& s : sourceCode) {
			sourceList.push_back(s.c_str());
		}

		pendingGLName = glCreateShader(shaderType);
		glShaderSource(pendingGLName, (GLsizei)sourceList.size(), sourceList.data(), NULL);
		glCompileShader(pendingGLName);

		GLint success;
		glGetShaderiv(pendingGLName, GL_COMPILE_STATUS, &success);

		if (success == GL_FALSE) {
			GLint logSize;
			glGetShaderiv(pendingGLName, GL_INFO_LOG_LENGTH, &logSize);

			std::string errorLog;
			errorLog.resize(logSize);
			glGetShaderInfoLog(pendingGLName, logSize, NULL, &errorLog[0]);
			LOG(LogError, "Failed to compile shader (%s) : %s", debugName, errorLog.c_str());

			glDeleteShader(pendingGLName);
			pendingGLName = 0;

			return ShaderStage::CompileResponse::Failed;
		}

		return ShaderStage::CompileResponse::Compiled;
	}

	bool ShaderStage::finishCompile()
	{
		if (pendingGLName == 0) {
			return false;
		}

		if (glName != 0) {
			glDeleteShader(glName);
		}

		glName = pendingGLName;
		pendingGLName = 0;

		glObjectLabel(GL_SHADER, glName, -1, debugName);

		return true;
	}

}
<|endoftext|>


<commit_before>/*
 *  Copyright (c) 2015, Facebook, Inc.
 *  All rights reserved.
 *
 *  This source code is licensed under the BSD-style license found in the
 *  LICENSE file in the root directory of this source tree. An additional grant
 *  of patent rights can be found in the PATENTS file in the same directory.
 *
 */
#include <folly/Conv.h>
#include <folly/ScopeGuard.h>
#include <folly/io/IOBuf.h>

#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <proxygen/httpserver/filters/ZlibServerFilter.h>
#include <proxygen/lib/utils/ZlibStreamCompressor.h>
#include <proxygen/httpserver/Mocks.h>
#include <proxygen/httpserver/ResponseBuilder.h>

using namespace proxygen;
using namespace testing;

MATCHER_P(IOBufEquals,
          expected,
          folly::to<std::string>(
              "IOBuf is ", negation ? "not " : "", "'", expected, "'")) {
  auto iob = arg->clone();
  auto br = iob->coalesce();
  std::string actual(br.begin(), br.end());
  *result_listener << "'" << actual << "'";
  return actual == expected;
}

class ZlibServerFilterTest : public Test {
 public:
  void SetUp() override {
    // requesthandler is the server, responsehandler is the client
    requestHandler_ = new MockRequestHandler();
    responseHandler_ = folly::make_unique<MockResponseHandler>(requestHandler_);
    zd_ = folly::make_unique<ZlibStreamDecompressor>(ZlibCompressionType::GZIP);
  }

  void TearDown() override {
    Mock::VerifyAndClear(requestHandler_);
    Mock::VerifyAndClear(responseHandler_.get());

    delete requestHandler_;
  }

 protected:
  ZlibServerFilter* filter_{nullptr};
  MockRequestHandler* requestHandler_;
  unique_ptr<MockResponseHandler> responseHandler_;
  unique_ptr<ZlibStreamDecompressor> zd_;
  ResponseHandler* downstream_{nullptr};

  void exercise_compression(bool expectCompression,
                            std::string url,
                            std::string acceptedEncoding,
                            std::string expectedEncoding,
                            std::string originalRequestBody,
                            std::string responseContentType,
                            unique_ptr<folly::IOBuf> originalResponseBody,
                            int32_t compressionLevel = 4,
                            uint32_t minimumCompressionSize = 1) {

    // If there is only one IOBuf, then it's not chunked.
    bool isResponseChunked = originalResponseBody->isChained();
    size_t chunkCount = originalResponseBody->countChainElements();

    // Chunked and compressed responses will have an extra block
    if (isResponseChunked && expectCompression) {
      chunkCount += 1;
    }

    // Request Handler Expectations
    EXPECT_CALL(*requestHandler_, onBody(_)).Times(1);
    EXPECT_CALL(*requestHandler_, onEOM()).Times(1);

    // Need to capture whatever the filter is for ResponseBuilder later
    EXPECT_CALL(*requestHandler_, setResponseHandler(_))
        .WillOnce(DoAll(SaveArg<0>(&downstream_), Return()));

    // Response Handler Expectations
    // Headers are only sent once
    EXPECT_CALL(*responseHandler_, sendHeaders(_)).WillOnce(DoAll(
        Invoke([&](HTTPMessage& msg) {
          auto& headers = msg.getHeaders();
          if (expectCompression) {
            EXPECT_TRUE(msg.checkForHeaderToken(
                HTTP_HEADER_CONTENT_ENCODING, expectedEncoding.c_str(), false));
          }

          if (msg.getIsChunked()) {
            EXPECT_FALSE(headers.exists("Content-Length"));
          } else {
            //Content-Length is not set on chunked messages
            EXPECT_TRUE(headers.exists("Content-Length"));
          }
        }),
        Return()));

    if (isResponseChunked) {
      // The final chunk has 0 body
      EXPECT_CALL(*responseHandler_, sendChunkHeader(_)).Times(chunkCount);
      EXPECT_CALL(*responseHandler_, sendChunkTerminator()).Times(chunkCount);
    } else {
      EXPECT_CALL(*responseHandler_, sendChunkHeader(_)).Times(0);
      EXPECT_CALL(*responseHandler_, sendChunkTerminator()).Times(0);
    }

    // Accumulate the body, decompressing it if it's compressed
    std::unique_ptr<folly::IOBuf> responseBody;
    EXPECT_CALL(*responseHandler_, sendBody(_))
        .Times(chunkCount)
        .WillRepeatedly(DoAll(
            Invoke([&](std::shared_ptr<folly::IOBuf> body) {

              std::unique_ptr<folly::IOBuf> processedBody;

              if (expectCompression) {
                processedBody = zd_->decompress(body.get());
                ASSERT_FALSE(zd_->hasError())
                    << "Failed to decompress body. r=" << zd_->getStatus();
              } else {
                processedBody = folly::IOBuf::copyBuffer(
                    body->data(), body->length(), 0, 0);
              }

              if (responseBody) {
                responseBody->prependChain(std::move(processedBody));
              } else {
                responseBody = std::move(processedBody);
              }
            }),
            Return()));

    EXPECT_CALL(*responseHandler_, sendEOM()).Times(1);

    /* Simulate Request/Response  */

    HTTPMessage msg;
    msg.setURL(url);
    msg.getHeaders().set(HTTP_HEADER_ACCEPT_ENCODING, acceptedEncoding);

    std::set<std::string> compressibleTypes = {"text/html"};
    auto filterFactory = folly::make_unique<ZlibServerFilterFactory>(
        compressionLevel, minimumCompressionSize, compressibleTypes);

    auto filter = filterFactory->onRequest(requestHandler_, &msg);
    filter->setResponseHandler(responseHandler_.get());

    // Send fake request
    filter->onBody(folly::IOBuf::copyBuffer(originalRequestBody));
    filter->onEOM();

    // Send a fake Response
    if (isResponseChunked) {

      ResponseBuilder(downstream_)
        .status(200, "OK")
        .header(HTTP_HEADER_CONTENT_TYPE, responseContentType)
        .send();

      folly::IOBuf* crtBuf;
      crtBuf = originalResponseBody.get();

      do {
        ResponseBuilder(downstream_).body(std::move(crtBuf->cloneOne())).send();
        crtBuf = crtBuf->next();
      } while (crtBuf != originalResponseBody.get());

      ResponseBuilder(downstream_).sendWithEOM();

    } else {

      // Send unchunked response
      ResponseBuilder(downstream_)
          .status(200, "OK")
          .header(HTTP_HEADER_CONTENT_TYPE, responseContentType)
          .body(std::move(originalResponseBody->clone()))
          .sendWithEOM();
    }

    filter->requestComplete();

    EXPECT_THAT(responseBody, IOBufEquals(originalRequestBody));
  }

  // Helper method to convert a vector of strings to an IOBuf chain
  // specificaly create a chain because the chain pieces are chunks
  unique_ptr<folly::IOBuf> createResponseChain(
      std::vector<std::string> const& bodyStrings) {

    std::unique_ptr<folly::IOBuf> responseBodyChain;

    for (auto& s : bodyStrings) {
      auto nextBody = folly::IOBuf::copyBuffer(s.c_str());
      if (responseBodyChain) {
        responseBodyChain->prependChain(std::move(nextBody));
      } else {
        responseBodyChain = std::move(nextBody);
      }
    }

    return std::move(responseBodyChain);
  }
};

// Basic smoke test
TEST_F(ZlibServerFilterTest, nonchunked_compression) {
  ASSERT_NO_FATAL_FAILURE({
    exercise_compression(true,
                         std::string("http://locahost/foo.compressme"),
                         std::string("gzip"),
                         std::string("gzip"),
                         std::string("Hello World"),
                         std::string("text/html"),
                         std::move(folly::IOBuf::copyBuffer("Hello World")));
  });
}

TEST_F(ZlibServerFilterTest, chunked_compression) {
  std::vector<std::string> chunks = {"Hello", " World"};
  ASSERT_NO_FATAL_FAILURE({
    exercise_compression(true,
                         std::string("http://locahost/foo.compressme"),
                         std::string("gzip"),
                         std::string("gzip"),
                         std::string("Hello World"),
                         std::string("text/html"),
                         std::move(createResponseChain(chunks)));
  });
}

TEST_F(ZlibServerFilterTest, parameterized_contenttype) {
  ASSERT_NO_FATAL_FAILURE({
    exercise_compression(true,
                         std::string("http://locahost/foo.compressme"),
                         std::string("gzip"),
                         std::string("gzip"),
                         std::string("Hello World"),
                         std::string("text/html; param1"),
                         std::move(folly::IOBuf::copyBuffer("Hello World")));
  });
}

TEST_F(ZlibServerFilterTest, mixedcase_contenttype) {
  ASSERT_NO_FATAL_FAILURE({
    exercise_compression(true,
                         std::string("http://locahost/foo.compressme"),
                         std::string("gzip"),
                         std::string("gzip"),
                         std::string("Hello World"),
                         std::string("Text/Html; param1"),
                         std::move(folly::IOBuf::copyBuffer("Hello World")));
  });
}

// Client supports multiple possible compression encodings
TEST_F(ZlibServerFilterTest, multiple_accepted_encodings) {
  ASSERT_NO_FATAL_FAILURE({
    exercise_compression(true,
                         std::string("http://locahost/foo.compressme"),
                         std::string("gzip, identity, deflate"),
                         std::string("gzip"),
                         std::string("Hello World"),
                         std::string("text/html"),
                         std::move(folly::IOBuf::copyBuffer("Hello World")));
  });
}

TEST_F(ZlibServerFilterTest, multiple_accepted_encodings_qvalues) {
  ASSERT_NO_FATAL_FAILURE({
    exercise_compression(true,
                         std::string("http://locahost/foo.compressme"),
                         std::string("gzip; q=.7;, identity"),
                         std::string("gzip"),
                         std::string("Hello World"),
                         std::string("text/html"),
                         std::move(folly::IOBuf::copyBuffer("Hello World")));
  });
}

TEST_F(ZlibServerFilterTest, no_compressible_accepted_encodings) {
  ASSERT_NO_FATAL_FAILURE({
    exercise_compression(false,
                         std::string("http://locahost/foo.compressme"),
                         std::string("identity; q=.7;"),
                         std::string(""),
                         std::string("Hello World"),
                         std::string("text/html"),
                         std::move(folly::IOBuf::copyBuffer("Hello World")));
  });
}

TEST_F(ZlibServerFilterTest, missing_accepted_encodings) {
  ASSERT_NO_FATAL_FAILURE({
    exercise_compression(false,
                         std::string("http://locahost/foo.compressme"),
                         std::string(""),
                         std::string(""),
                         std::string("Hello World"),
                         std::string("text/html"),
                         std::move(folly::IOBuf::copyBuffer("Hello World")));
  });
}

// Content is of an-uncompressible content-type
TEST_F(ZlibServerFilterTest, uncompressible_contenttype) {
  ASSERT_NO_FATAL_FAILURE({
    exercise_compression(false,
                         std::string("http://locahost/foo.nocompress"),
                         std::string("gzip"),
                         std::string(""),
                         std::string("Hello World"),
                         std::string("image/jpeg"),
                         std::move(folly::IOBuf::copyBuffer("Hello World")));
  });
}

TEST_F(ZlibServerFilterTest, uncompressible_contenttype_param) {
  ASSERT_NO_FATAL_FAILURE({
    exercise_compression(false,
                         std::string("http://locahost/foo.nocompress"),
                         std::string("gzip"),
                         std::string(""),
                         std::string("Hello World"),
                         std::string("application/jpeg; param1"),
                         std::move(folly::IOBuf::copyBuffer("Hello World")));
  });
}

// Content is under the minimum compression size
TEST_F(ZlibServerFilterTest, too_small_to_compress) {
  ASSERT_NO_FATAL_FAILURE({
    exercise_compression(false,
                         std::string("http://locahost/foo.smallfry"),
                         std::string("gzip"),
                         std::string(""),
                         std::string("Hello World"),
                         std::string("text/html"),
                         std::move(folly::IOBuf::copyBuffer("Hello World")),
                         4,
                         1000);
  });
}

TEST_F(ZlibServerFilterTest, small_chunks_compress) {
  // Expect this to compress despite being small because can't tell the content
  // length when we're chunked
  std::vector<std::string> chunks = {"Hello", " World"};
  ASSERT_NO_FATAL_FAILURE({
    exercise_compression(true,
                         std::string("http://locahost/foo.compressme"),
                         std::string("gzip"),
                         std::string("gzip"),
                         std::string("Hello World"),
                         std::string("text/html"),
                         std::move(createResponseChain(chunks)),
                         4,
                         1000);
  });
}
<commit_msg>Remove evil using statements from AsyncSSLSocket.h<commit_after>/*
 *  Copyright (c) 2015, Facebook, Inc.
 *  All rights reserved.
 *
 *  This source code is licensed under the BSD-style license found in the
 *  LICENSE file in the root directory of this source tree. An additional grant
 *  of patent rights can be found in the PATENTS file in the same directory.
 *
 */
#include <folly/Conv.h>
#include <folly/ScopeGuard.h>
#include <folly/io/IOBuf.h>

#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <proxygen/httpserver/filters/ZlibServerFilter.h>
#include <proxygen/lib/utils/ZlibStreamCompressor.h>
#include <proxygen/httpserver/Mocks.h>
#include <proxygen/httpserver/ResponseBuilder.h>

using namespace proxygen;
using namespace testing;

MATCHER_P(IOBufEquals,
          expected,
          folly::to<std::string>(
              "IOBuf is ", negation ? "not " : "", "'", expected, "'")) {
  auto iob = arg->clone();
  auto br = iob->coalesce();
  std::string actual(br.begin(), br.end());
  *result_listener << "'" << actual << "'";
  return actual == expected;
}

class ZlibServerFilterTest : public Test {
 public:
  void SetUp() override {
    // requesthandler is the server, responsehandler is the client
    requestHandler_ = new MockRequestHandler();
    responseHandler_ = folly::make_unique<MockResponseHandler>(requestHandler_);
    zd_ = folly::make_unique<ZlibStreamDecompressor>(ZlibCompressionType::GZIP);
  }

  void TearDown() override {
    Mock::VerifyAndClear(requestHandler_);
    Mock::VerifyAndClear(responseHandler_.get());

    delete requestHandler_;
  }

 protected:
  ZlibServerFilter* filter_{nullptr};
  MockRequestHandler* requestHandler_;
  std::unique_ptr<MockResponseHandler> responseHandler_;
  std::unique_ptr<ZlibStreamDecompressor> zd_;
  ResponseHandler* downstream_{nullptr};

  void exercise_compression(bool expectCompression,
                            std::string url,
                            std::string acceptedEncoding,
                            std::string expectedEncoding,
                            std::string originalRequestBody,
                            std::string responseContentType,
                            std::unique_ptr<folly::IOBuf> originalResponseBody,
                            int32_t compressionLevel = 4,
                            uint32_t minimumCompressionSize = 1) {

    // If there is only one IOBuf, then it's not chunked.
    bool isResponseChunked = originalResponseBody->isChained();
    size_t chunkCount = originalResponseBody->countChainElements();

    // Chunked and compressed responses will have an extra block
    if (isResponseChunked && expectCompression) {
      chunkCount += 1;
    }

    // Request Handler Expectations
    EXPECT_CALL(*requestHandler_, onBody(_)).Times(1);
    EXPECT_CALL(*requestHandler_, onEOM()).Times(1);

    // Need to capture whatever the filter is for ResponseBuilder later
    EXPECT_CALL(*requestHandler_, setResponseHandler(_))
        .WillOnce(DoAll(SaveArg<0>(&downstream_), Return()));

    // Response Handler Expectations
    // Headers are only sent once
    EXPECT_CALL(*responseHandler_, sendHeaders(_)).WillOnce(DoAll(
        Invoke([&](HTTPMessage& msg) {
          auto& headers = msg.getHeaders();
          if (expectCompression) {
            EXPECT_TRUE(msg.checkForHeaderToken(
                HTTP_HEADER_CONTENT_ENCODING, expectedEncoding.c_str(), false));
          }

          if (msg.getIsChunked()) {
            EXPECT_FALSE(headers.exists("Content-Length"));
          } else {
            //Content-Length is not set on chunked messages
            EXPECT_TRUE(headers.exists("Content-Length"));
          }
        }),
        Return()));

    if (isResponseChunked) {
      // The final chunk has 0 body
      EXPECT_CALL(*responseHandler_, sendChunkHeader(_)).Times(chunkCount);
      EXPECT_CALL(*responseHandler_, sendChunkTerminator()).Times(chunkCount);
    } else {
      EXPECT_CALL(*responseHandler_, sendChunkHeader(_)).Times(0);
      EXPECT_CALL(*responseHandler_, sendChunkTerminator()).Times(0);
    }

    // Accumulate the body, decompressing it if it's compressed
    std::unique_ptr<folly::IOBuf> responseBody;
    EXPECT_CALL(*responseHandler_, sendBody(_))
        .Times(chunkCount)
        .WillRepeatedly(DoAll(
            Invoke([&](std::shared_ptr<folly::IOBuf> body) {

              std::unique_ptr<folly::IOBuf> processedBody;

              if (expectCompression) {
                processedBody = zd_->decompress(body.get());
                ASSERT_FALSE(zd_->hasError())
                    << "Failed to decompress body. r=" << zd_->getStatus();
              } else {
                processedBody = folly::IOBuf::copyBuffer(
                    body->data(), body->length(), 0, 0);
              }

              if (responseBody) {
                responseBody->prependChain(std::move(processedBody));
              } else {
                responseBody = std::move(processedBody);
              }
            }),
            Return()));

    EXPECT_CALL(*responseHandler_, sendEOM()).Times(1);

    /* Simulate Request/Response  */

    HTTPMessage msg;
    msg.setURL(url);
    msg.getHeaders().set(HTTP_HEADER_ACCEPT_ENCODING, acceptedEncoding);

    std::set<std::string> compressibleTypes = {"text/html"};
    auto filterFactory = folly::make_unique<ZlibServerFilterFactory>(
        compressionLevel, minimumCompressionSize, compressibleTypes);

    auto filter = filterFactory->onRequest(requestHandler_, &msg);
    filter->setResponseHandler(responseHandler_.get());

    // Send fake request
    filter->onBody(folly::IOBuf::copyBuffer(originalRequestBody));
    filter->onEOM();

    // Send a fake Response
    if (isResponseChunked) {

      ResponseBuilder(downstream_)
        .status(200, "OK")
        .header(HTTP_HEADER_CONTENT_TYPE, responseContentType)
        .send();

      folly::IOBuf* crtBuf;
      crtBuf = originalResponseBody.get();

      do {
        ResponseBuilder(downstream_).body(std::move(crtBuf->cloneOne())).send();
        crtBuf = crtBuf->next();
      } while (crtBuf != originalResponseBody.get());

      ResponseBuilder(downstream_).sendWithEOM();

    } else {

      // Send unchunked response
      ResponseBuilder(downstream_)
          .status(200, "OK")
          .header(HTTP_HEADER_CONTENT_TYPE, responseContentType)
          .body(std::move(originalResponseBody->clone()))
          .sendWithEOM();
    }

    filter->requestComplete();

    EXPECT_THAT(responseBody, IOBufEquals(originalRequestBody));
  }

  // Helper method to convert a vector of strings to an IOBuf chain
  // specificaly create a chain because the chain pieces are chunks
  std::unique_ptr<folly::IOBuf> createResponseChain(
      std::vector<std::string> const& bodyStrings) {

    std::unique_ptr<folly::IOBuf> responseBodyChain;

    for (auto& s : bodyStrings) {
      auto nextBody = folly::IOBuf::copyBuffer(s.c_str());
      if (responseBodyChain) {
        responseBodyChain->prependChain(std::move(nextBody));
      } else {
        responseBodyChain = std::move(nextBody);
      }
    }

    return std::move(responseBodyChain);
  }
};

// Basic smoke test
TEST_F(ZlibServerFilterTest, nonchunked_compression) {
  ASSERT_NO_FATAL_FAILURE({
    exercise_compression(true,
                         std::string("http://locahost/foo.compressme"),
                         std::string("gzip"),
                         std::string("gzip"),
                         std::string("Hello World"),
                         std::string("text/html"),
                         std::move(folly::IOBuf::copyBuffer("Hello World")));
  });
}

TEST_F(ZlibServerFilterTest, chunked_compression) {
  std::vector<std::string> chunks = {"Hello", " World"};
  ASSERT_NO_FATAL_FAILURE({
    exercise_compression(true,
                         std::string("http://locahost/foo.compressme"),
                         std::string("gzip"),
                         std::string("gzip"),
                         std::string("Hello World"),
                         std::string("text/html"),
                         std::move(createResponseChain(chunks)));
  });
}

TEST_F(ZlibServerFilterTest, parameterized_contenttype) {
  ASSERT_NO_FATAL_FAILURE({
    exercise_compression(true,
                         std::string("http://locahost/foo.compressme"),
                         std::string("gzip"),
                         std::string("gzip"),
                         std::string("Hello World"),
                         std::string("text/html; param1"),
                         std::move(folly::IOBuf::copyBuffer("Hello World")));
  });
}

TEST_F(ZlibServerFilterTest, mixedcase_contenttype) {
  ASSERT_NO_FATAL_FAILURE({
    exercise_compression(true,
                         std::string("http://locahost/foo.compressme"),
                         std::string("gzip"),
                         std::string("gzip"),
                         std::string("Hello World"),
                         std::string("Text/Html; param1"),
                         std::move(folly::IOBuf::copyBuffer("Hello World")));
  });
}

// Client supports multiple possible compression encodings
TEST_F(ZlibServerFilterTest, multiple_accepted_encodings) {
  ASSERT_NO_FATAL_FAILURE({
    exercise_compression(true,
                         std::string("http://locahost/foo.compressme"),
                         std::string("gzip, identity, deflate"),
                         std::string("gzip"),
                         std::string("Hello World"),
                         std::string("text/html"),
                         std::move(folly::IOBuf::copyBuffer("Hello World")));
  });
}

TEST_F(ZlibServerFilterTest, multiple_accepted_encodings_qvalues) {
  ASSERT_NO_FATAL_FAILURE({
    exercise_compression(true,
                         std::string("http://locahost/foo.compressme"),
                         std::string("gzip; q=.7;, identity"),
                         std::string("gzip"),
                         std::string("Hello World"),
                         std::string("text/html"),
                         std::move(folly::IOBuf::copyBuffer("Hello World")));
  });
}

TEST_F(ZlibServerFilterTest, no_compressible_accepted_encodings) {
  ASSERT_NO_FATAL_FAILURE({
    exercise_compression(false,
                         std::string("http://locahost/foo.compressme"),
                         std::string("identity; q=.7;"),
                         std::string(""),
                         std::string("Hello World"),
                         std::string("text/html"),
                         std::move(folly::IOBuf::copyBuffer("Hello World")));
  });
}

TEST_F(ZlibServerFilterTest, missing_accepted_encodings) {
  ASSERT_NO_FATAL_FAILURE({
    exercise_compression(false,
                         std::string("http://locahost/foo.compressme"),
                         std::string(""),
                         std::string(""),
                         std::string("Hello World"),
                         std::string("text/html"),
                         std::move(folly::IOBuf::copyBuffer("Hello World")));
  });
}

// Content is of an-uncompressible content-type
TEST_F(ZlibServerFilterTest, uncompressible_contenttype) {
  ASSERT_NO_FATAL_FAILURE({
    exercise_compression(false,
                         std::string("http://locahost/foo.nocompress"),
                         std::string("gzip"),
                         std::string(""),
                         std::string("Hello World"),
                         std::string("image/jpeg"),
                         std::move(folly::IOBuf::copyBuffer("Hello World")));
  });
}

TEST_F(ZlibServerFilterTest, uncompressible_contenttype_param) {
  ASSERT_NO_FATAL_FAILURE({
    exercise_compression(false,
                         std::string("http://locahost/foo.nocompress"),
                         std::string("gzip"),
                         std::string(""),
                         std::string("Hello World"),
                         std::string("application/jpeg; param1"),
                         std::move(folly::IOBuf::copyBuffer("Hello World")));
  });
}

// Content is under the minimum compression size
TEST_F(ZlibServerFilterTest, too_small_to_compress) {
  ASSERT_NO_FATAL_FAILURE({
    exercise_compression(false,
                         std::string("http://locahost/foo.smallfry"),
                         std::string("gzip"),
                         std::string(""),
                         std::string("Hello World"),
                         std::string("text/html"),
                         std::move(folly::IOBuf::copyBuffer("Hello World")),
                         4,
                         1000);
  });
}

TEST_F(ZlibServerFilterTest, small_chunks_compress) {
  // Expect this to compress despite being small because can't tell the content
  // length when we're chunked
  std::vector<std::string> chunks = {"Hello", " World"};
  ASSERT_NO_FATAL_FAILURE({
    exercise_compression(true,
                         std::string("http://locahost/foo.compressme"),
                         std::string("gzip"),
                         std::string("gzip"),
                         std::string("Hello World"),
                         std::string("text/html"),
                         std::move(createResponseChain(chunks)),
                         4,
                         1000);
  });
}
<|endoftext|>


<commit_before>// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/chromeos/power/renderer_freezer.h"

#include <cstring>  // needed for strlen()

#include "base/bind.h"
#include "base/files/file_util.h"
#include "base/logging.h"
#include "base/message_loop/message_loop.h"
#include "chromeos/dbus/dbus_thread_manager.h"

namespace chromeos {

namespace {
const char kFreezerStatePath[] =
    "/sys/fs/cgroup/freezer/chrome_renderers/freezer.state";
const char kFreezeCommand[] = "FROZEN";
const char kThawCommand[] = "THAWED";

}  // namespace

void RendererFreezer::SuspendImminent() {
  suspend_readiness_callback_ = DBusThreadManager::Get()
                                    ->GetPowerManagerClient()
                                    ->GetSuspendReadinessCallback();

  base::MessageLoop::current()->PostTask(
      FROM_HERE,
      base::Bind(&RendererFreezer::OnReadyToSuspend,
                 weak_factory_.GetWeakPtr()));
}

void RendererFreezer::SuspendDone(const base::TimeDelta& sleep_duration) {
  if (!frozen_)
    return;

  if (base::WriteFile(state_path_, kThawCommand, strlen(kThawCommand)) !=
      static_cast<int>(strlen(kThawCommand))) {
    // We failed to write the thaw command and the renderers are still frozen.
    // We are in big trouble because none of the tabs will be responsive so
    // let's crash the browser instead.
    PLOG(FATAL) << "Unable to thaw processes in the cgroup freezer.";
  }

  frozen_ = false;
}

void RendererFreezer::OnReadyToSuspend() {
  if (base::WriteFile(state_path_, kFreezeCommand, strlen(kFreezeCommand)) !=
      static_cast<int>(strlen(kFreezeCommand))) {
    PLOG(WARNING) << "Unable to freeze processes in the cgroup freezer.";
  } else {
    frozen_ = true;
  }

  DCHECK(!suspend_readiness_callback_.is_null());
  suspend_readiness_callback_.Run();
  suspend_readiness_callback_.Reset();
}

RendererFreezer::RendererFreezer()
    : state_path_(base::FilePath(kFreezerStatePath)),
      enabled_(base::PathExists(state_path_) &&
               base::PathIsWritable(state_path_)),
      frozen_(false),
      weak_factory_(this) {
  if (enabled_) {
    DBusThreadManager::Get()->GetPowerManagerClient()->AddObserver(this);
  } else {
    LOG(WARNING) << "Cgroup freezer does not exist or is not writable. "
                 << "Processes will not be frozen during suspend.";
  }
}

RendererFreezer::~RendererFreezer() {
  if (enabled_)
    DBusThreadManager::Get()->GetPowerManagerClient()->RemoveObserver(this);
}

}  // namespace chromeos
<commit_msg>Fix crash on suspend for chrome os<commit_after>// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/chromeos/power/renderer_freezer.h"

#include <cstring>  // needed for strlen()

#include "base/bind.h"
#include "base/files/file_util.h"
#include "base/logging.h"
#include "base/message_loop/message_loop.h"
#include "chromeos/dbus/dbus_thread_manager.h"

namespace chromeos {

namespace {
const char kFreezerStatePath[] =
    "/sys/fs/cgroup/freezer/chrome_renderers/freezer.state";
const char kFreezeCommand[] = "FROZEN";
const char kThawCommand[] = "THAWED";

}  // namespace

void RendererFreezer::SuspendImminent() {
  // SuspendImminent() might end up being called multiple times before we run
  // OnReadyToSuspend() (crbug.com/414396).  In case a callback is already
  // pending, we only store the new callback and do nothing else.
  if (suspend_readiness_callback_.is_null()) {
    // There is no callback pending so post the task.
    base::MessageLoop::current()->PostTask(
        FROM_HERE,
        base::Bind(&RendererFreezer::OnReadyToSuspend,
                   weak_factory_.GetWeakPtr()));
  }

  // Always update the callback because only the most recent one matters.
  suspend_readiness_callback_ = DBusThreadManager::Get()
                                    ->GetPowerManagerClient()
                                    ->GetSuspendReadinessCallback();
}

void RendererFreezer::SuspendDone(const base::TimeDelta& sleep_duration) {
  if (!frozen_)
    return;

  if (base::WriteFile(state_path_, kThawCommand, strlen(kThawCommand)) !=
      static_cast<int>(strlen(kThawCommand))) {
    // We failed to write the thaw command and the renderers are still frozen.
    // We are in big trouble because none of the tabs will be responsive so
    // let's crash the browser instead.
    PLOG(FATAL) << "Unable to thaw processes in the cgroup freezer.";
  }

  frozen_ = false;
}

void RendererFreezer::OnReadyToSuspend() {
  if (base::WriteFile(state_path_, kFreezeCommand, strlen(kFreezeCommand)) !=
      static_cast<int>(strlen(kFreezeCommand))) {
    PLOG(WARNING) << "Unable to freeze processes in the cgroup freezer.";
  } else {
    frozen_ = true;
  }

  CHECK(!suspend_readiness_callback_.is_null());  // crbug.com/414396
  suspend_readiness_callback_.Run();
  suspend_readiness_callback_.Reset();
}

RendererFreezer::RendererFreezer()
    : state_path_(base::FilePath(kFreezerStatePath)),
      enabled_(base::PathExists(state_path_) &&
               base::PathIsWritable(state_path_)),
      frozen_(false),
      weak_factory_(this) {
  if (enabled_) {
    DBusThreadManager::Get()->GetPowerManagerClient()->AddObserver(this);
  } else {
    LOG(WARNING) << "Cgroup freezer does not exist or is not writable. "
                 << "Processes will not be frozen during suspend.";
  }
}

RendererFreezer::~RendererFreezer() {
  if (enabled_)
    DBusThreadManager::Get()->GetPowerManagerClient()->RemoveObserver(this);
}

}  // namespace chromeos
<|endoftext|>


<commit_before>// Copyright (c) 2010 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/extensions/extension_rlz_module.h"

#include "base/scoped_ptr.h"
#include "base/values.h"
#include "chrome/common/extensions/extension.h"
#include "rlz/win/lib/lib_values.h"

namespace {

bool GetProductFromName(const std::string& product_name,
                        rlz_lib::Product* product) {
  bool success = true;
  switch (product_name[0]) {
    case 'B':
      *product = rlz_lib::FF_TOOLBAR;
      break;
    case 'C':
      *product = rlz_lib::CHROME;
      break;
    case 'D':
      *product = rlz_lib::DESKTOP;
      break;
    case 'K':
      *product = rlz_lib::QSB_WIN;
      break;
    case 'N':
      *product = rlz_lib::PINYIN_IME;
      break;
    case 'P':
      *product = rlz_lib::TOOLBAR_NOTIFIER;
      break;
    case 'T':
      *product = rlz_lib::IE_TOOLBAR;
      break;
    case 'U':
      *product = rlz_lib::PACK;
      break;
    case 'W':
      *product = rlz_lib::WEBAPPS;
      break;
    default:
      success = false;
      break;
  }

  return success;
}

bool GetEventFromName(const std::string& event_name,
                      rlz_lib::Event* event_id) {
  *event_id = rlz_lib::INVALID_EVENT;

  if (event_name == "install") {
    *event_id = rlz_lib::INSTALL;
  } else if (event_name == "set-to-google") {
    *event_id = rlz_lib::SET_TO_GOOGLE;
  } else if (event_name == "first-search") {
    *event_id = rlz_lib::FIRST_SEARCH;
  } else if (event_name == "activate") {
    *event_id = rlz_lib::ACTIVATE;
  }

  return *event_id != rlz_lib::INVALID_EVENT;
}

}  // namespace

bool RlzRecordProductEventFunction::RunImpl() {
  std::string product_name;
  EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &product_name));
  rlz_lib::Product product;
  EXTENSION_FUNCTION_VALIDATE(GetProductFromName(product_name, &product));

  std::string ap_name;
  EXTENSION_FUNCTION_VALIDATE(args_->GetString(1, &ap_name));
  rlz_lib::AccessPoint access_point;
  EXTENSION_FUNCTION_VALIDATE(rlz_lib::GetAccessPointFromName(ap_name.c_str(),
                                                              &access_point));

  std::string event_name;
  EXTENSION_FUNCTION_VALIDATE(args_->GetString(2, &event_name));
  rlz_lib::Event event_id;
  EXTENSION_FUNCTION_VALIDATE(GetEventFromName(event_name, &event_id));

  return rlz_lib::RecordProductEvent(product, access_point, event_id);
}

bool RlzGetAccessPointRlzFunction::RunImpl() {
  std::string ap_name;
  EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &ap_name));
  rlz_lib::AccessPoint access_point;
  EXTENSION_FUNCTION_VALIDATE(rlz_lib::GetAccessPointFromName(ap_name.c_str(),
                                                              &access_point));

  char rlz[rlz_lib::kMaxRlzLength + 1];
  rlz_lib::GetAccessPointRlz(access_point, rlz, rlz_lib::kMaxRlzLength);
  result_.reset(Value::CreateStringValue(rlz));
  return true;
}

bool RlzSendFinancialPingFunction::RunImpl() {
  std::string product_name;
  EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &product_name));
  rlz_lib::Product product;
  EXTENSION_FUNCTION_VALIDATE(GetProductFromName(product_name, &product));

  ListValue* access_points_list;
  EXTENSION_FUNCTION_VALIDATE(args_->GetList(1, &access_points_list));
  if (access_points_list->GetSize() < 1) {
    EXTENSION_FUNCTION_ERROR("Access point array should not be empty.");
  }

  // Allocate an access point array to pass to ClearProductState().  The array
  // must be termindated with the value rlz_lib::NO_ACCESS_POINT, hence + 1
  // when allocating the array.
  scoped_array<rlz_lib::AccessPoint> access_points(
      new rlz_lib::AccessPoint[access_points_list->GetSize() + 1]);

  size_t i;
  for (i = 0; i < access_points_list->GetSize(); ++i) {
    std::string ap_name;
    EXTENSION_FUNCTION_VALIDATE(access_points_list->GetString(i, &ap_name));
    EXTENSION_FUNCTION_VALIDATE(rlz_lib::GetAccessPointFromName(
        ap_name.c_str(), &access_points[i]));
  }
  access_points[i] = rlz_lib::NO_ACCESS_POINT;

  std::string signature;
  EXTENSION_FUNCTION_VALIDATE(args_->GetString(2, &signature));

  std::string brand;
  EXTENSION_FUNCTION_VALIDATE(args_->GetString(3, &brand));

  std::string id;
  EXTENSION_FUNCTION_VALIDATE(args_->GetString(4, &id));

  std::string lang;
  EXTENSION_FUNCTION_VALIDATE(args_->GetString(5, &lang));

  bool exclude_machine_id;
  EXTENSION_FUNCTION_VALIDATE(args_->GetBoolean(6, &exclude_machine_id));

  // rlz_lib::SendFinancialPing() will not send a ping more often than once in
  // any 24-hour period.  Calling it more often has no effect.  If a ping is
  // not sent false is returned, but this is not an error, so we should not
  // use the return value of rlz_lib::SendFinancialPing() as the return value
  // of this function.  Callers interested in the return value can register
  // an optional callback function.
  bool sent = rlz_lib::SendFinancialPing(product, access_points.get(),
                                         signature.c_str(), brand.c_str(),
                                         id.c_str(), lang.c_str(),
                                         exclude_machine_id);
  result_.reset(Value::CreateBooleanValue(sent));
  return true;
}

bool RlzClearProductStateFunction::RunImpl() {
  std::string product_name;
  EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &product_name));
  rlz_lib::Product product;
  EXTENSION_FUNCTION_VALIDATE(GetProductFromName(product_name, &product));

  ListValue* access_points_list;
  EXTENSION_FUNCTION_VALIDATE(args_->GetList(1, &access_points_list));
  if (access_points_list->GetSize() < 1) {
    EXTENSION_FUNCTION_ERROR("Access point array should not be empty.");
  }

  // Allocate an access point array to pass to ClearProductState().  The array
  // must be termindated with the value rlz_lib::NO_ACCESS_POINT, hence + 1
  // when allocating the array.
  scoped_array<rlz_lib::AccessPoint> access_points(
      new rlz_lib::AccessPoint[access_points_list->GetSize() + 1]);

  size_t i;
  for (i = 0; i < access_points_list->GetSize(); ++i) {
    std::string ap_name;
    EXTENSION_FUNCTION_VALIDATE(access_points_list->GetString(i, &ap_name));
    EXTENSION_FUNCTION_VALIDATE(rlz_lib::GetAccessPointFromName(
        ap_name.c_str(), &access_points[i]));
  }
  access_points[i] = rlz_lib::NO_ACCESS_POINT;

  rlz_lib::ClearProductState(product, access_points.get());
  return true;
}
<commit_msg>Allow RLZ API calls from extensions to access the registry. Temporary fix until these IO operations are moved to the File thread.<commit_after>// Copyright (c) 2010 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/extensions/extension_rlz_module.h"

#include "base/scoped_ptr.h"
#include "base/thread_restrictions.h"
#include "base/values.h"
#include "chrome/common/extensions/extension.h"
#include "rlz/win/lib/lib_values.h"

namespace {

bool GetProductFromName(const std::string& product_name,
                        rlz_lib::Product* product) {
  bool success = true;
  switch (product_name[0]) {
    case 'B':
      *product = rlz_lib::FF_TOOLBAR;
      break;
    case 'C':
      *product = rlz_lib::CHROME;
      break;
    case 'D':
      *product = rlz_lib::DESKTOP;
      break;
    case 'K':
      *product = rlz_lib::QSB_WIN;
      break;
    case 'N':
      *product = rlz_lib::PINYIN_IME;
      break;
    case 'P':
      *product = rlz_lib::TOOLBAR_NOTIFIER;
      break;
    case 'T':
      *product = rlz_lib::IE_TOOLBAR;
      break;
    case 'U':
      *product = rlz_lib::PACK;
      break;
    case 'W':
      *product = rlz_lib::WEBAPPS;
      break;
    default:
      success = false;
      break;
  }

  return success;
}

bool GetEventFromName(const std::string& event_name,
                      rlz_lib::Event* event_id) {
  *event_id = rlz_lib::INVALID_EVENT;

  if (event_name == "install") {
    *event_id = rlz_lib::INSTALL;
  } else if (event_name == "set-to-google") {
    *event_id = rlz_lib::SET_TO_GOOGLE;
  } else if (event_name == "first-search") {
    *event_id = rlz_lib::FIRST_SEARCH;
  } else if (event_name == "activate") {
    *event_id = rlz_lib::ACTIVATE;
  }

  return *event_id != rlz_lib::INVALID_EVENT;
}

}  // namespace

bool RlzRecordProductEventFunction::RunImpl() {
  // This can be slow if registry access goes to disk. Should preferably
  // perform registry operations on the File thread.
  //   http://code.google.com/p/chromium/issues/detail?id=62098
  base::ThreadRestrictions::ScopedAllowIO allow_io;

  std::string product_name;
  EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &product_name));
  rlz_lib::Product product;
  EXTENSION_FUNCTION_VALIDATE(GetProductFromName(product_name, &product));

  std::string ap_name;
  EXTENSION_FUNCTION_VALIDATE(args_->GetString(1, &ap_name));
  rlz_lib::AccessPoint access_point;
  EXTENSION_FUNCTION_VALIDATE(rlz_lib::GetAccessPointFromName(ap_name.c_str(),
                                                              &access_point));

  std::string event_name;
  EXTENSION_FUNCTION_VALIDATE(args_->GetString(2, &event_name));
  rlz_lib::Event event_id;
  EXTENSION_FUNCTION_VALIDATE(GetEventFromName(event_name, &event_id));

  return rlz_lib::RecordProductEvent(product, access_point, event_id);
}

bool RlzGetAccessPointRlzFunction::RunImpl() {
  // This can be slow if registry access goes to disk. Should preferably
  // perform registry operations on the File thread.
  //   http://code.google.com/p/chromium/issues/detail?id=62098
  base::ThreadRestrictions::ScopedAllowIO allow_io;

  std::string ap_name;
  EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &ap_name));
  rlz_lib::AccessPoint access_point;
  EXTENSION_FUNCTION_VALIDATE(rlz_lib::GetAccessPointFromName(ap_name.c_str(),
                                                              &access_point));

  char rlz[rlz_lib::kMaxRlzLength + 1];
  rlz_lib::GetAccessPointRlz(access_point, rlz, rlz_lib::kMaxRlzLength);
  result_.reset(Value::CreateStringValue(rlz));
  return true;
}

bool RlzSendFinancialPingFunction::RunImpl() {
  // This can be slow if registry access goes to disk. Should preferably
  // perform registry operations on the File thread.
  //   http://code.google.com/p/chromium/issues/detail?id=62098
  base::ThreadRestrictions::ScopedAllowIO allow_io;

  std::string product_name;
  EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &product_name));
  rlz_lib::Product product;
  EXTENSION_FUNCTION_VALIDATE(GetProductFromName(product_name, &product));

  ListValue* access_points_list;
  EXTENSION_FUNCTION_VALIDATE(args_->GetList(1, &access_points_list));
  if (access_points_list->GetSize() < 1) {
    EXTENSION_FUNCTION_ERROR("Access point array should not be empty.");
  }

  // Allocate an access point array to pass to ClearProductState().  The array
  // must be termindated with the value rlz_lib::NO_ACCESS_POINT, hence + 1
  // when allocating the array.
  scoped_array<rlz_lib::AccessPoint> access_points(
      new rlz_lib::AccessPoint[access_points_list->GetSize() + 1]);

  size_t i;
  for (i = 0; i < access_points_list->GetSize(); ++i) {
    std::string ap_name;
    EXTENSION_FUNCTION_VALIDATE(access_points_list->GetString(i, &ap_name));
    EXTENSION_FUNCTION_VALIDATE(rlz_lib::GetAccessPointFromName(
        ap_name.c_str(), &access_points[i]));
  }
  access_points[i] = rlz_lib::NO_ACCESS_POINT;

  std::string signature;
  EXTENSION_FUNCTION_VALIDATE(args_->GetString(2, &signature));

  std::string brand;
  EXTENSION_FUNCTION_VALIDATE(args_->GetString(3, &brand));

  std::string id;
  EXTENSION_FUNCTION_VALIDATE(args_->GetString(4, &id));

  std::string lang;
  EXTENSION_FUNCTION_VALIDATE(args_->GetString(5, &lang));

  bool exclude_machine_id;
  EXTENSION_FUNCTION_VALIDATE(args_->GetBoolean(6, &exclude_machine_id));

  // rlz_lib::SendFinancialPing() will not send a ping more often than once in
  // any 24-hour period.  Calling it more often has no effect.  If a ping is
  // not sent false is returned, but this is not an error, so we should not
  // use the return value of rlz_lib::SendFinancialPing() as the return value
  // of this function.  Callers interested in the return value can register
  // an optional callback function.
  bool sent = rlz_lib::SendFinancialPing(product, access_points.get(),
                                         signature.c_str(), brand.c_str(),
                                         id.c_str(), lang.c_str(),
                                         exclude_machine_id);
  result_.reset(Value::CreateBooleanValue(sent));
  return true;
}

bool RlzClearProductStateFunction::RunImpl() {
  // This can be slow if registry access goes to disk. Should preferably
  // perform registry operations on the File thread.
  //   http://code.google.com/p/chromium/issues/detail?id=62098
  base::ThreadRestrictions::ScopedAllowIO allow_io;

  std::string product_name;
  EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &product_name));
  rlz_lib::Product product;
  EXTENSION_FUNCTION_VALIDATE(GetProductFromName(product_name, &product));

  ListValue* access_points_list;
  EXTENSION_FUNCTION_VALIDATE(args_->GetList(1, &access_points_list));
  if (access_points_list->GetSize() < 1) {
    EXTENSION_FUNCTION_ERROR("Access point array should not be empty.");
  }

  // Allocate an access point array to pass to ClearProductState().  The array
  // must be termindated with the value rlz_lib::NO_ACCESS_POINT, hence + 1
  // when allocating the array.
  scoped_array<rlz_lib::AccessPoint> access_points(
      new rlz_lib::AccessPoint[access_points_list->GetSize() + 1]);

  size_t i;
  for (i = 0; i < access_points_list->GetSize(); ++i) {
    std::string ap_name;
    EXTENSION_FUNCTION_VALIDATE(access_points_list->GetString(i, &ap_name));
    EXTENSION_FUNCTION_VALIDATE(rlz_lib::GetAccessPointFromName(
        ap_name.c_str(), &access_points[i]));
  }
  access_points[i] = rlz_lib::NO_ACCESS_POINT;

  rlz_lib::ClearProductState(product, access_points.get());
  return true;
}
<|endoftext|>