blob: 5659a81594edd49fcf2f39bb9391b0d34e93afeb [file] [log] [blame]
// Copyright (c) 2015 The Chromium Embedded Framework 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 "tests/cefclient/browser/preferences_test.h"
#include <sstream>
#include <string>
#include <vector>
#include "include/base/cef_logging.h"
#include "include/cef_command_line.h"
#include "include/cef_parser.h"
#include "tests/cefclient/browser/test_runner.h"
namespace client {
namespace preferences_test {
namespace {
const char kTestUrlPath[] = "/preferences";
// Application-specific error codes.
const int kMessageFormatError = 1;
const int kPreferenceApplicationError = 1;
// Common to all messages.
const char kNameKey[] = "name";
const char kNameValueGet[] = "preferences_get";
const char kNameValueSet[] = "preferences_set";
const char kNameValueState[] = "preferences_state";
// Used with "preferences_get" messages.
const char kIncludeDefaultsKey[] = "include_defaults";
// Used with "preferences_set" messages.
const char kPreferencesKey[] = "preferences";
// Handle messages in the browser process. Only accessed on the UI thread.
class Handler : public CefMessageRouterBrowserSide::Handler {
public:
typedef std::vector<std::string> NameVector;
Handler() { CEF_REQUIRE_UI_THREAD(); }
// Called due to cefQuery execution in preferences.html.
bool OnQuery(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
int64 query_id,
const CefString& request,
bool persistent,
CefRefPtr<Callback> callback) OVERRIDE {
CEF_REQUIRE_UI_THREAD();
// Only handle messages from the test URL.
const std::string& url = frame->GetURL();
if (!test_runner::IsTestURL(url, kTestUrlPath))
return false;
// Parse |request| as a JSON dictionary.
CefRefPtr<CefDictionaryValue> request_dict = ParseJSON(request);
if (!request_dict) {
callback->Failure(kMessageFormatError, "Incorrect message format");
return true;
}
// Verify the "name" key.
if (!VerifyKey(request_dict, kNameKey, VTYPE_STRING, callback))
return true;
const std::string& message_name = request_dict->GetString(kNameKey);
if (message_name == kNameValueGet) {
// JavaScript is requesting a JSON representation of the preferences tree.
// Verify the "include_defaults" key.
if (!VerifyKey(request_dict, kIncludeDefaultsKey, VTYPE_BOOL, callback))
return true;
const bool include_defaults = request_dict->GetBool(kIncludeDefaultsKey);
OnPreferencesGet(browser, include_defaults, callback);
return true;
} else if (message_name == kNameValueSet) {
// JavaScript is requesting that preferences be updated to match the
// specified JSON representation.
// Verify the "preferences" key.
if (!VerifyKey(request_dict, kPreferencesKey, VTYPE_DICTIONARY, callback))
return true;
CefRefPtr<CefDictionaryValue> preferences =
request_dict->GetDictionary(kPreferencesKey);
OnPreferencesSet(browser, preferences, callback);
return true;
} else if (message_name == kNameValueState) {
// JavaScript is requesting global state information.
OnPreferencesState(browser, callback);
return true;
}
return false;
}
private:
// Execute |callback| with the preferences dictionary as a JSON string.
static void OnPreferencesGet(CefRefPtr<CefBrowser> browser,
bool include_defaults,
CefRefPtr<Callback> callback) {
CefRefPtr<CefRequestContext> context =
browser->GetHost()->GetRequestContext();
// Retrieve all preference values.
CefRefPtr<CefDictionaryValue> prefs =
context->GetAllPreferences(include_defaults);
// Serialize the preferences to JSON and return to the JavaScript caller.
callback->Success(GetJSON(prefs));
}
// Set preferences based on the contents of |preferences|. Execute |callback|
// with a descriptive result message.
static void OnPreferencesSet(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefDictionaryValue> preferences,
CefRefPtr<Callback> callback) {
CefRefPtr<CefRequestContext> context =
browser->GetHost()->GetRequestContext();
CefRefPtr<CefValue> value = CefValue::Create();
value->SetDictionary(preferences);
std::string error;
NameVector changed_names;
// Apply preferences. This may result in errors.
const bool success =
ApplyPrefs(context, std::string(), value, error, changed_names);
// Create a message that accurately represents the result.
std::string message;
if (!changed_names.empty()) {
std::stringstream ss;
ss << "Successfully changed " << changed_names.size() << " preferences; ";
for (size_t i = 0; i < changed_names.size(); ++i) {
ss << changed_names[i];
if (i < changed_names.size() - 1)
ss << ", ";
}
message = ss.str();
}
if (!success) {
DCHECK(!error.empty());
if (!message.empty())
message += "\n";
message += error;
}
if (changed_names.empty()) {
if (!message.empty())
message += "\n";
message += "No preferences changed.";
}
// Return the message to the JavaScript caller.
if (success)
callback->Success(message);
else
callback->Failure(kPreferenceApplicationError, message);
}
// Execute |callback| with the global state dictionary as a JSON string.
static void OnPreferencesState(CefRefPtr<CefBrowser> browser,
CefRefPtr<Callback> callback) {
CefRefPtr<CefCommandLine> command_line =
CefCommandLine::GetGlobalCommandLine();
CefRefPtr<CefDictionaryValue> dict = CefDictionaryValue::Create();
// If spell checking is disabled via the command-line then it cannot be
// enabled via preferences.
dict->SetBool("spellcheck_disabled",
command_line->HasSwitch("disable-spell-checking"));
// If proxy settings are configured via the command-line then they cannot
// be modified via preferences.
dict->SetBool("proxy_configured",
command_line->HasSwitch("no-proxy-server") ||
command_line->HasSwitch("proxy-auto-detect") ||
command_line->HasSwitch("proxy-pac-url") ||
command_line->HasSwitch("proxy-server"));
// If allow running insecure content is enabled via the command-line then it
// cannot be enabled via preferences.
dict->SetBool("allow_running_insecure_content",
command_line->HasSwitch("allow-running-insecure-content"));
// Serialize the state to JSON and return to the JavaScript caller.
callback->Success(GetJSON(dict));
}
// Convert a JSON string to a dictionary value.
static CefRefPtr<CefDictionaryValue> ParseJSON(const CefString& string) {
CefRefPtr<CefValue> value = CefParseJSON(string, JSON_PARSER_RFC);
if (value.get() && value->GetType() == VTYPE_DICTIONARY)
return value->GetDictionary();
return nullptr;
}
// Convert a dictionary value to a JSON string.
static CefString GetJSON(CefRefPtr<CefDictionaryValue> dictionary) {
CefRefPtr<CefValue> value = CefValue::Create();
value->SetDictionary(dictionary);
return CefWriteJSON(value, JSON_WRITER_DEFAULT);
}
// Verify that |key| exists in |dictionary| and has type |value_type|. Fails
// |callback| and returns false on failure.
static bool VerifyKey(CefRefPtr<CefDictionaryValue> dictionary,
const char* key,
cef_value_type_t value_type,
CefRefPtr<Callback> callback) {
if (!dictionary->HasKey(key) || dictionary->GetType(key) != value_type) {
callback->Failure(
kMessageFormatError,
"Missing or incorrectly formatted message key: " + std::string(key));
return false;
}
return true;
}
// Apply preferences. Returns true on success. Returns false and sets |error|
// to a descriptive error string on failure. |changed_names| is the list of
// preferences that were successfully changed.
static bool ApplyPrefs(CefRefPtr<CefRequestContext> context,
const std::string& name,
CefRefPtr<CefValue> value,
std::string& error,
NameVector& changed_names) {
if (!name.empty() && context->HasPreference(name)) {
// The preference exists. Set the value.
return SetPref(context, name, value, error, changed_names);
}
if (value->GetType() == VTYPE_DICTIONARY) {
// A dictionary type value that is not an existing preference. Try to set
// each of the elements individually.
CefRefPtr<CefDictionaryValue> dict = value->GetDictionary();
CefDictionaryValue::KeyList keys;
dict->GetKeys(keys);
for (size_t i = 0; i < keys.size(); ++i) {
const std::string& key = keys[i];
const std::string& current_name = name.empty() ? key : name + "." + key;
if (!ApplyPrefs(context, current_name, dict->GetValue(key), error,
changed_names)) {
return false;
}
}
return true;
}
error = "Trying to create an unregistered preference: " + name;
return false;
}
// Set a specific preference value. Returns true if the value is set
// successfully or has not changed. If the value has changed then |name| will
// be added to |changed_names|. Returns false and sets |error| to a
// descriptive error string on failure.
static bool SetPref(CefRefPtr<CefRequestContext> context,
const std::string& name,
CefRefPtr<CefValue> value,
std::string& error,
NameVector& changed_names) {
CefRefPtr<CefValue> existing_value = context->GetPreference(name);
DCHECK(existing_value);
if (value->GetType() == VTYPE_STRING &&
existing_value->GetType() != VTYPE_STRING) {
// Since |value| is coming from JSON all basic types will be represented
// as strings. Convert to the expected data type.
const std::string& string_val = value->GetString();
switch (existing_value->GetType()) {
case VTYPE_BOOL:
if (string_val == "true" || string_val == "1")
value->SetBool(true);
else if (string_val == "false" || string_val == "0")
value->SetBool(false);
break;
case VTYPE_INT:
value->SetInt(atoi(string_val.c_str()));
break;
case VTYPE_DOUBLE:
value->SetInt(atof(string_val.c_str()));
break;
default:
// Other types cannot be converted.
break;
}
}
// Nothing to do if the value hasn't changed.
if (existing_value->IsEqual(value))
return true;
// Attempt to set the preference.
CefString error_str;
if (!context->SetPreference(name, value, error_str)) {
error = error_str.ToString() + ": " + name;
return false;
}
// The preference was set successfully.
changed_names.push_back(name);
return true;
}
DISALLOW_COPY_AND_ASSIGN(Handler);
};
} // namespace
void CreateMessageHandlers(test_runner::MessageHandlerSet& handlers) {
handlers.insert(new Handler());
}
} // namespace preferences_test
} // namespace client