335 lines
11 KiB
C++
335 lines
11 KiB
C++
// 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
|