| // 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 |