mirror of
				https://bitbucket.org/chromiumembedded/cef
				synced 2025-06-05 21:39:12 +02:00 
			
		
		
		
	- Add new API to retrieve/observe configuration values. - cefclient: Add https://tests/config to inspect configuration values in real time.
		
			
				
	
	
		
			335 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			335 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
// Copyright (c) 2025 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/config_test.h"
 | 
						|
 | 
						|
#include <map>
 | 
						|
#include <sstream>
 | 
						|
#include <string>
 | 
						|
#include <vector>
 | 
						|
 | 
						|
#include "include/base/cef_logging.h"
 | 
						|
#include "include/cef_parser.h"
 | 
						|
#include "include/cef_request_context.h"
 | 
						|
#include "tests/cefclient/browser/test_runner.h"
 | 
						|
 | 
						|
namespace client::config_test {
 | 
						|
 | 
						|
namespace {
 | 
						|
 | 
						|
const char kTestUrlPath[] = "/config";
 | 
						|
 | 
						|
// Application-specific error codes.
 | 
						|
const int kMessageFormatError = 1;
 | 
						|
const int kRequestFailedError = 2;
 | 
						|
 | 
						|
// Common to all messages.
 | 
						|
const char kNameKey[] = "name";
 | 
						|
const char kNameGlobalConfig[] = "global_config";
 | 
						|
const char kNameSubscribe[] = "subscribe";
 | 
						|
 | 
						|
// Convert a dictionary value to a JSON string.
 | 
						|
CefString GetJSON(CefRefPtr<CefDictionaryValue> dictionary) {
 | 
						|
  CefRefPtr<CefValue> value = CefValue::Create();
 | 
						|
  value->SetDictionary(dictionary);
 | 
						|
  return CefWriteJSON(value, JSON_WRITER_DEFAULT);
 | 
						|
}
 | 
						|
 | 
						|
using CallbackType = CefMessageRouterBrowserSide::Callback;
 | 
						|
 | 
						|
void SendSuccess(CefRefPtr<CallbackType> callback,
 | 
						|
                 CefRefPtr<CefDictionaryValue> result) {
 | 
						|
  callback->Success(GetJSON(result));
 | 
						|
}
 | 
						|
 | 
						|
void SendFailure(CefRefPtr<CallbackType> callback,
 | 
						|
                 int error_code,
 | 
						|
                 const std::string& error_message) {
 | 
						|
  callback->Failure(error_code, error_message);
 | 
						|
}
 | 
						|
 | 
						|
class PreferenceObserver : public CefPreferenceObserver {
 | 
						|
 public:
 | 
						|
  PreferenceObserver(CefRefPtr<CefPreferenceManager> manager,
 | 
						|
                     bool global,
 | 
						|
                     CefRefPtr<CallbackType> callback)
 | 
						|
      : manager_(manager), global_(global), callback_(callback) {}
 | 
						|
 | 
						|
  PreferenceObserver(const PreferenceObserver&) = delete;
 | 
						|
  PreferenceObserver& operator=(const PreferenceObserver&) = delete;
 | 
						|
 | 
						|
  void OnPreferenceChanged(const CefString& name) override {
 | 
						|
    CEF_REQUIRE_UI_THREAD();
 | 
						|
    auto value = manager_->GetPreference(name);
 | 
						|
 | 
						|
    auto payload = CefDictionaryValue::Create();
 | 
						|
    payload->SetString("type", "preference");
 | 
						|
    payload->SetBool("global", global_);
 | 
						|
    payload->SetString("name", name);
 | 
						|
    if (value) {
 | 
						|
      payload->SetInt("value_type", value->GetType());
 | 
						|
      payload->SetValue("value", value);
 | 
						|
    } else {
 | 
						|
      payload->SetInt("value_type", VTYPE_NULL);
 | 
						|
      payload->SetNull("value");
 | 
						|
    }
 | 
						|
 | 
						|
    SendSuccess(callback_, payload);
 | 
						|
  }
 | 
						|
 | 
						|
 private:
 | 
						|
  const CefRefPtr<CefPreferenceManager> manager_;
 | 
						|
  const bool global_;
 | 
						|
  const CefRefPtr<CallbackType> callback_;
 | 
						|
 | 
						|
  IMPLEMENT_REFCOUNTING(PreferenceObserver);
 | 
						|
};
 | 
						|
 | 
						|
class SettingObserver : public CefSettingObserver {
 | 
						|
 public:
 | 
						|
  SettingObserver(CefRefPtr<CefRequestContext> context,
 | 
						|
                  CefRefPtr<CallbackType> callback)
 | 
						|
      : context_(context), callback_(callback) {}
 | 
						|
 | 
						|
  SettingObserver(const SettingObserver&) = delete;
 | 
						|
  SettingObserver& operator=(const SettingObserver&) = delete;
 | 
						|
 | 
						|
  void OnSettingChanged(const CefString& requesting_url,
 | 
						|
                        const CefString& top_level_url,
 | 
						|
                        cef_content_setting_types_t content_type) override {
 | 
						|
    CEF_REQUIRE_UI_THREAD();
 | 
						|
    auto value = context_->GetWebsiteSetting(requesting_url, top_level_url,
 | 
						|
                                             content_type);
 | 
						|
 | 
						|
    auto payload = CefDictionaryValue::Create();
 | 
						|
    payload->SetString("type", "setting");
 | 
						|
    payload->SetString("requesting_url", requesting_url);
 | 
						|
    payload->SetString("top_level_url", top_level_url);
 | 
						|
    payload->SetInt("content_type", static_cast<int>(content_type));
 | 
						|
    if (value) {
 | 
						|
      payload->SetInt("value_type", value->GetType());
 | 
						|
      payload->SetValue("value", value);
 | 
						|
    } else {
 | 
						|
      payload->SetInt("value_type", VTYPE_NULL);
 | 
						|
      payload->SetNull("value");
 | 
						|
    }
 | 
						|
 | 
						|
    SendSuccess(callback_, payload);
 | 
						|
  }
 | 
						|
 | 
						|
 private:
 | 
						|
  const CefRefPtr<CefRequestContext> context_;
 | 
						|
  const CefRefPtr<CallbackType> callback_;
 | 
						|
 | 
						|
  IMPLEMENT_REFCOUNTING(SettingObserver);
 | 
						|
};
 | 
						|
 | 
						|
// 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(); }
 | 
						|
 | 
						|
  Handler(const Handler&) = delete;
 | 
						|
  Handler& operator=(const Handler&) = delete;
 | 
						|
 | 
						|
  ~Handler() override {
 | 
						|
    for (auto& pair : subscription_state_map_) {
 | 
						|
      delete pair.second;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  // Called due to cefQuery execution in config.html.
 | 
						|
  bool OnQuery(CefRefPtr<CefBrowser> browser,
 | 
						|
               CefRefPtr<CefFrame> frame,
 | 
						|
               int64_t 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 == kNameGlobalConfig) {
 | 
						|
      // JavaScript is requesting a JSON representation of the global config.
 | 
						|
 | 
						|
      SendGlobalConfig(callback);
 | 
						|
      return true;
 | 
						|
    } else if (message_name == kNameSubscribe) {
 | 
						|
      // Subscribe to notifications from observers.
 | 
						|
 | 
						|
      if (!persistent) {
 | 
						|
        SendFailure(callback, kMessageFormatError,
 | 
						|
                    "Subscriptions must be persistent");
 | 
						|
        return true;
 | 
						|
      }
 | 
						|
 | 
						|
      if (!CreateSubscription(browser, query_id, callback)) {
 | 
						|
        SendFailure(callback, kRequestFailedError,
 | 
						|
                    "Browser is already subscribed");
 | 
						|
      }
 | 
						|
      return true;
 | 
						|
    }
 | 
						|
 | 
						|
    return false;
 | 
						|
  }
 | 
						|
 | 
						|
  void OnQueryCanceled(CefRefPtr<CefBrowser> browser,
 | 
						|
                       CefRefPtr<CefFrame> frame,
 | 
						|
                       int64_t query_id) override {
 | 
						|
    CEF_REQUIRE_UI_THREAD();
 | 
						|
    RemoveSubscription(browser->GetIdentifier(), query_id);
 | 
						|
  }
 | 
						|
 | 
						|
 private:
 | 
						|
  // 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;
 | 
						|
  }
 | 
						|
 | 
						|
  // 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;
 | 
						|
  }
 | 
						|
 | 
						|
  static CefRefPtr<CefListValue> MakeListValue(std::vector<CefString> vec) {
 | 
						|
    if (vec.empty()) {
 | 
						|
      return nullptr;
 | 
						|
    }
 | 
						|
    auto list = CefListValue::Create();
 | 
						|
    list->SetSize(vec.size());
 | 
						|
    size_t idx = 0;
 | 
						|
    for (const auto& val : vec) {
 | 
						|
      list->SetString(idx++, val);
 | 
						|
    }
 | 
						|
    return list;
 | 
						|
  }
 | 
						|
 | 
						|
  void SendGlobalConfig(CefRefPtr<Callback> callback) {
 | 
						|
    std::vector<CefString> switches;
 | 
						|
    CefPreferenceManager::GetChromeVariationsAsSwitches(switches);
 | 
						|
    std::vector<CefString> strings;
 | 
						|
    CefPreferenceManager::GetChromeVariationsAsStrings(strings);
 | 
						|
 | 
						|
    auto payload = CefDictionaryValue::Create();
 | 
						|
 | 
						|
    if (auto list = MakeListValue(switches)) {
 | 
						|
      payload->SetList("switches", list);
 | 
						|
    } else {
 | 
						|
      payload->SetNull("switches");
 | 
						|
    }
 | 
						|
 | 
						|
    if (auto list = MakeListValue(strings)) {
 | 
						|
      payload->SetList("strings", list);
 | 
						|
    } else {
 | 
						|
      payload->SetNull("strings");
 | 
						|
    }
 | 
						|
 | 
						|
    SendSuccess(callback, payload);
 | 
						|
  }
 | 
						|
 | 
						|
  // Subscription state associated with a single browser.
 | 
						|
  struct SubscriptionState {
 | 
						|
    int64_t query_id;
 | 
						|
    CefRefPtr<PreferenceObserver> global_pref_observer;
 | 
						|
    CefRefPtr<CefRegistration> global_pref_registration;
 | 
						|
    CefRefPtr<PreferenceObserver> context_pref_observer;
 | 
						|
    CefRefPtr<CefRegistration> context_pref_registration;
 | 
						|
    CefRefPtr<SettingObserver> context_setting_observer;
 | 
						|
    CefRefPtr<CefRegistration> context_setting_registration;
 | 
						|
  };
 | 
						|
 | 
						|
  bool CreateSubscription(CefRefPtr<CefBrowser> browser,
 | 
						|
                          int64_t query_id,
 | 
						|
                          CefRefPtr<Callback> callback) {
 | 
						|
    const int browser_id = browser->GetIdentifier();
 | 
						|
    if (subscription_state_map_.find(browser_id) !=
 | 
						|
        subscription_state_map_.end()) {
 | 
						|
      // An subscription already exists for this browser.
 | 
						|
      return false;
 | 
						|
    }
 | 
						|
 | 
						|
    auto global_pref_manager =
 | 
						|
        CefPreferenceManager::GetGlobalPreferenceManager();
 | 
						|
    auto request_context = browser->GetHost()->GetRequestContext();
 | 
						|
 | 
						|
    SubscriptionState* state = new SubscriptionState();
 | 
						|
    state->query_id = query_id;
 | 
						|
 | 
						|
    state->global_pref_observer =
 | 
						|
        new PreferenceObserver(global_pref_manager, /*global=*/true, callback);
 | 
						|
    state->global_pref_registration =
 | 
						|
        global_pref_manager->AddPreferenceObserver(CefString(),
 | 
						|
                                                   state->global_pref_observer);
 | 
						|
 | 
						|
    state->context_pref_observer =
 | 
						|
        new PreferenceObserver(request_context, /*global=*/false, callback);
 | 
						|
    state->context_pref_registration = request_context->AddPreferenceObserver(
 | 
						|
        CefString(), state->context_pref_observer);
 | 
						|
 | 
						|
    state->context_setting_observer =
 | 
						|
        new SettingObserver(request_context, callback);
 | 
						|
    state->context_setting_registration =
 | 
						|
        request_context->AddSettingObserver(state->context_setting_observer);
 | 
						|
 | 
						|
    subscription_state_map_[browser_id] = state;
 | 
						|
 | 
						|
    return true;
 | 
						|
  }
 | 
						|
 | 
						|
  void RemoveSubscription(int browser_id, int64_t query_id) {
 | 
						|
    SubscriptionStateMap::iterator it =
 | 
						|
        subscription_state_map_.find(browser_id);
 | 
						|
    if (it != subscription_state_map_.end() &&
 | 
						|
        it->second->query_id == query_id) {
 | 
						|
      delete it->second;
 | 
						|
      subscription_state_map_.erase(it);
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  // Map of browser ID to SubscriptionState object.
 | 
						|
  typedef std::map<int, SubscriptionState*> SubscriptionStateMap;
 | 
						|
  SubscriptionStateMap subscription_state_map_;
 | 
						|
};
 | 
						|
 | 
						|
}  // namespace
 | 
						|
 | 
						|
void CreateMessageHandlers(test_runner::MessageHandlerSet& handlers) {
 | 
						|
  handlers.insert(new Handler());
 | 
						|
}
 | 
						|
 | 
						|
}  // namespace client::config_test
 |