From db9e3d8dac4e6aa63948e392eafc1901955b9c49 Mon Sep 17 00:00:00 2001 From: Utkarsh Patel Date: Thu, 22 Feb 2024 19:38:26 +0000 Subject: [PATCH 01/16] LLAMA-13554 : HDMI input with allm sets wrong low latency mode Reason for change: HDMI input with allm sets wrong low latency mode Test Procedure: As described in ticket Risks: None Priority: P1 Signed-off-by: Utkarsh Patel --- AVOutput/AVOutputTV.cpp | 43 ++++++++++++++++++++--------------------- AVOutput/AVOutputTV.h | 2 +- 2 files changed, 22 insertions(+), 23 deletions(-) diff --git a/AVOutput/AVOutputTV.cpp b/AVOutput/AVOutputTV.cpp index 992edb1177..2c71d8dfcb 100644 --- a/AVOutput/AVOutputTV.cpp +++ b/AVOutput/AVOutputTV.cpp @@ -3377,6 +3377,7 @@ namespace Plugin { std::string source; std::string format; int lowLatencyIndex = 0; + int params[3]={0}; tvError_t ret = tvERROR_NONE; value = parameters.HasLabel("LowLatencyState") ? parameters["LowLatencyState"].String() : ""; @@ -3396,35 +3397,33 @@ namespace Plugin { } if( !isCapablityCheckPassed(pqmode, source, format, "LowLatencyState" )) - { + { LOGERR("%s: CapablityCheck failed for LowLatencyState\n", __FUNCTION__); returnResponse(false); } - if( isSetRequired(pqmode,source,format) ) - { - LOGINFO("Proceed with setLowLatencyState\n"); - ret = SetLowLatencyState( lowLatencyIndex ); - } - - if(ret != tvERROR_NONE) - { - LOGERR("Failed to setLowLatency\n"); - returnResponse(false); + params[0]=lowLatencyIndex; + int retval= UpdateAVoutputTVParam("set","LowLatencyState",pqmode,source,format,PQ_PARAM_LOWLATENCY_STATE,params); + if(retval != 0 ) + { + LOGERR("Failed to SaveLowLatency to ssm_data\n"); + returnResponse(false); } - else - { - int params[3]={0}; - params[0]=lowLatencyIndex; - int retval= UpdateAVoutputTVParam("set","LowLatencyState",pqmode,source,format,PQ_PARAM_LOWLATENCY_STATE,params); - if(retval != 0 ) - { - LOGERR("Failed to SaveLowLatency to ssm_data\n"); - returnResponse(false); + else + { + if( isSetRequired(pqmode,source,format) ) + { + LOGINFO("Proceed with setLowLatencyState\n"); + ret = SetLowLatencyState( lowLatencyIndex ); + if(ret != tvERROR_NONE) + { + LOGERR("Failed to setLowLatency\n"); + returnResponse(false); + } } - LOGINFO("Exit : setLowLatency successful to value: %d\n", lowLatencyIndex); - returnResponse(true); } + LOGINFO("Exit : setLowLatency successful to value: %d\n", lowLatencyIndex); + returnResponse(true); } uint32_t AVOutputTV::getLowLatencyState(const JsonObject& parameters, JsonObject& response) diff --git a/AVOutput/AVOutputTV.h b/AVOutput/AVOutputTV.h index 059cbbb99f..d2e452809e 100644 --- a/AVOutput/AVOutputTV.h +++ b/AVOutput/AVOutputTV.h @@ -25,10 +25,10 @@ #include "tvTypes.h" #include "tvSettings.h" +#include "tvSettingsExtODM.h" #include #include "Module.h" #include "tvError.h" -#include "tvTypes.h" #include "tr181api.h" #include "AVOutputBase.h" #include "libIARM.h" From c787780cbf4e0c7b1299eab088367479508c65a1 Mon Sep 17 00:00:00 2001 From: Utkarsh Patel Date: Fri, 23 Feb 2024 15:21:22 +0000 Subject: [PATCH 02/16] LLAMA-13554 : HDMI input with allm sets wrong low latency mode Reason for change: HDMI input with allm sets wrong low latency mode Test Procedure: As described in ticket Risks: None Priority: P1 Signed-off-by: Utkarsh Patel --- AVOutput/CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/AVOutput/CHANGELOG.md b/AVOutput/CHANGELOG.md index e6d56faa25..f5b9a44ac2 100644 --- a/AVOutput/CHANGELOG.md +++ b/AVOutput/CHANGELOG.md @@ -16,6 +16,10 @@ All notable changes to this RDK Service will be documented in this file. * For more details, refer to [versioning](https://github.com/rdkcentral/rdkservices#versioning) section under Main README. +## [1.0.3] - 2024-02-23 +### Fixed +- all set after sync as it requires pq reload + ## [1.0.2] - 2024-02-15 ### Fixed - DolbyMode index issue From 27da0f9544515aa6aabba56b61f0ab5d4c564b6f Mon Sep 17 00:00:00 2001 From: Utkarsh Patel Date: Fri, 23 Feb 2024 15:23:17 +0000 Subject: [PATCH 03/16] LLAMA-13554 : HDMI input with allm sets wrong low latency mode Reason for change: Change Log update Test Procedure: As described in ticket Risks: None Priority: P1 Signed-off-by: Utkarsh Patel --- AVOutput/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AVOutput/CHANGELOG.md b/AVOutput/CHANGELOG.md index f5b9a44ac2..4ce6a99ad6 100644 --- a/AVOutput/CHANGELOG.md +++ b/AVOutput/CHANGELOG.md @@ -18,7 +18,7 @@ All notable changes to this RDK Service will be documented in this file. ## [1.0.3] - 2024-02-23 ### Fixed -- all set after sync as it requires pq reload +- allm set after sync as it requires pq reload ## [1.0.2] - 2024-02-15 ### Fixed From 17bba6f28add5e113d4eb95cbec28b70c0388a9e Mon Sep 17 00:00:00 2001 From: Utkarsh Patel Date: Tue, 5 Mar 2024 15:44:56 +0000 Subject: [PATCH 04/16] LLAMA-13554 : Fallback to previous low latency state Set Fails --- AVOutput/AVOutputTV.cpp | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/AVOutput/AVOutputTV.cpp b/AVOutput/AVOutputTV.cpp index 2c71d8dfcb..1b5a2f25ef 100644 --- a/AVOutput/AVOutputTV.cpp +++ b/AVOutput/AVOutputTV.cpp @@ -3377,6 +3377,7 @@ namespace Plugin { std::string source; std::string format; int lowLatencyIndex = 0; + int prevLowLatencyIndex = 0; int params[3]={0}; tvError_t ret = tvERROR_NONE; @@ -3402,6 +3403,14 @@ namespace Plugin { returnResponse(false); } +/* Usually low latency is enabled with Game mode but when setLowLatency is done seperatly, it requires PQ Mode reload. + To allow pq reload to fetch latest low latency values, save low latency before set is done. */ + ret = GetLowLatencyState(&prevLowLatencyIndex); + if(ret != tvERROR_NONE) { + LOGERR("Get previous low latency state failed\n"); + returnResponse(false, getErrorString(ret).c_str()); + } + params[0]=lowLatencyIndex; int retval= UpdateAVoutputTVParam("set","LowLatencyState",pqmode,source,format,PQ_PARAM_LOWLATENCY_STATE,params); if(retval != 0 ) @@ -3412,12 +3421,19 @@ namespace Plugin { else { if( isSetRequired(pqmode,source,format) ) - { + { LOGINFO("Proceed with setLowLatencyState\n"); ret = SetLowLatencyState( lowLatencyIndex ); if(ret != tvERROR_NONE) - { - LOGERR("Failed to setLowLatency\n"); + { + params[0]=prevLowLatencyIndex; + LOGERR("Failed to set low latency. Fallback to previous state %d\n", prevLowLatencyIndex); + + retval= UpdateAVoutputTVParam("set","LowLatencyState",pqmode,source,format,PQ_PARAM_LOWLATENCY_STATE,params); + if(retval != 0 ){ + LOGERR("Fallback to previous low latency state %d failed.\n", prevLowLatencyIndex); + } + returnResponse(false); } } From 54b58b4871f47970f5d3059cbc1de7a76f8ae8c2 Mon Sep 17 00:00:00 2001 From: hgfell683 <107510770+hgfell683@users.noreply.github.com> Date: Tue, 19 Mar 2024 14:59:22 -0400 Subject: [PATCH 05/16] DELIA-62029 - Detached threads may not end properly. Reason for change: Update detached threads, such that we wait for them to finish before de-initiailizing the system. Test Procedure: None Risks: Low Priority: P1 Signed-off-by:Hayden Gfeller --- DisplaySettings/DisplaySettings.cpp | 6 +++++- XCast/XCast.cpp | 7 ++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/DisplaySettings/DisplaySettings.cpp b/DisplaySettings/DisplaySettings.cpp index 73be3c84d0..836c2424e2 100644 --- a/DisplaySettings/DisplaySettings.cpp +++ b/DisplaySettings/DisplaySettings.cpp @@ -91,6 +91,7 @@ static bool isCecEnabled = false; static int hdmiArcPortId = -1; static int retryPowerRequestCount = 0; static int hdmiArcVolumeLevel = 0; +std::thread audioPortInitThread; std::vector sad_list; #ifdef USE_IARM namespace @@ -561,6 +562,9 @@ namespace WPEFramework { if (m_sendMsgThread.joinable()) m_sendMsgThread.join(); } + if(audioPortInitThread.joinable()){ + audioPortInitThread.join() + } catch(const std::system_error& e) { LOGERR("system_error exception in thread join %s", e.what()); @@ -4677,7 +4681,7 @@ namespace WPEFramework { try { LOGWARN("creating worker thread for initAudioPortsWorker "); - std::thread audioPortInitThread = std::thread(initAudioPortsWorker); + audioPortInitThread = std::thread(initAudioPortsWorker); audioPortInitThread.detach(); } catch(const std::system_error& e) diff --git a/XCast/XCast.cpp b/XCast/XCast.cpp index 82ec6eb715..cbb67a244a 100644 --- a/XCast/XCast.cpp +++ b/XCast/XCast.cpp @@ -107,6 +107,7 @@ bool XCast::m_standbyBehavior = false; bool XCast::m_enableStatus = false; IARM_Bus_PWRMgr_PowerState_t XCast::m_powerState = IARM_BUS_PWRMGR_POWERSTATE_STANDBY; +std::thread powerModeChangeThread; XCast::XCast() : PluginHost::JSONRPC() , m_apiVersionNumber(1), m_isDynamicRegistrationsRequired(false) @@ -183,7 +184,7 @@ void XCast::powerModeChange(const char *owner, IARM_EventId_t eventId, void *dat param->data.state.curState, param->data.state.newState); m_powerState = param->data.state.newState; LOGWARN("creating worker thread for threadPowerModeChangeEvent m_powerState :%d",m_powerState); - std::thread powerModeChangeThread = std::thread(threadPowerModeChangeEvent); + powerModeChangeThread = std::thread(threadPowerModeChangeEvent); powerModeChangeThread.detach(); } } @@ -224,6 +225,10 @@ const string XCast::Initialize(PluginHost::IShell *service) void XCast::Deinitialize(PluginHost::IShell* /* service */) { LOGINFO("XCast::Deinitialize called \n "); + if(powerModeChangeThread.joinable()) + { + powerModeChangeThread.join(); + } if ( m_locateCastTimer.isActive()) { m_locateCastTimer.stop(); From bd0e69300a8c051f7d1f45a2f3c8d1f29e5fc816 Mon Sep 17 00:00:00 2001 From: gururaajar <83449026+gururaajar@users.noreply.github.com> Date: Tue, 19 Mar 2024 16:12:09 -0400 Subject: [PATCH 06/16] RDK-46636 - Network Manager changes done to fix issues with GUI (#5034) * RDK-47625 - Implement all the network API Reason for change: Added GetWifiState API to fix the issue of Network manager plugin not getting activated with desktop Test Procedure: Build and verified with desktop Risks: Low Priority: P1 Signed-off-by: Gururaaja ESR * RDK-46636 - Network Manager changes done to fix issues with GUI Reason for change: Network Manager modification done to get the proper events in GUI Test Procedure: Build and verified with GUI Risks: Low Priority: P1 Signed-off-by: Gururaaja ESR Gururaja_ErodeSriranganRamlingham@comcast.com --- NetworkManager/interface/INetworkManager.h | 7 +- NetworkManager/service/NetworkManager.h | 3 +- NetworkManager/service/NetworkManager.json | 79 +++++++++ .../service/NetworkManagerGnomeProxy.cpp | 41 +++-- .../service/NetworkManagerImplementation.h | 3 +- .../service/NetworkManagerJsonRpc.cpp | 29 ++- .../service/NetworkManagerLegacy.cpp | 6 +- .../service/NetworkManagerRDKProxy.cpp | 36 +++- docs/api/NetworkManagerPlugin.md | 167 ++++++++++++++++++ 9 files changed, 347 insertions(+), 24 deletions(-) diff --git a/NetworkManager/interface/INetworkManager.h b/NetworkManager/interface/INetworkManager.h index 4c5eedad6b..eba176c327 100644 --- a/NetworkManager/interface/INetworkManager.h +++ b/NetworkManager/interface/INetworkManager.h @@ -206,9 +206,10 @@ namespace WPEFramework /* @brief Set the Primary Interface used for external world communication */ virtual uint32_t SetPrimaryInterface (const string& interface/* @in */) = 0; - /* @brief Set the active Interface used for external world communication */ - virtual uint32_t SetInterfaceEnabled (const string& interface/* @in */, const bool& isEnabled /* @in */) = 0; - + /* @brief Enable the active Interface used for external world communication */ + virtual uint32_t EnableInterface (const string& interface/* @in */) = 0; + /* @brief Disable the Interface passed */ + virtual uint32_t DisableInterface (const string& interface/* @in */) = 0; /* @brief Get IP Address Of the Interface */ virtual uint32_t GetIPSettings(const string& interface /* @in */, const string &ipversion /* @in */, IPAddressInfo& result /* @out */) = 0; diff --git a/NetworkManager/service/NetworkManager.h b/NetworkManager/service/NetworkManager.h index 95975fc149..06e394cc9d 100644 --- a/NetworkManager/service/NetworkManager.h +++ b/NetworkManager/service/NetworkManager.h @@ -352,7 +352,8 @@ namespace WPEFramework uint32_t GetAvailableInterfaces (const JsonObject& parameters, JsonObject& response); uint32_t GetPrimaryInterface (const JsonObject& parameters, JsonObject& response); uint32_t SetPrimaryInterface (const JsonObject& parameters, JsonObject& response); - uint32_t SetInterfaceEnabled (const JsonObject& parameters, JsonObject& response); + uint32_t EnableInterface (const JsonObject& parameters, JsonObject& response); + uint32_t DisableInterface (const JsonObject& parameters, JsonObject& response); uint32_t GetIPSettings(const JsonObject& parameters, JsonObject& response); uint32_t SetIPSettings(const JsonObject& parameters, JsonObject& response); uint32_t GetStunEndpoint(const JsonObject& parameters, JsonObject& response); diff --git a/NetworkManager/service/NetworkManager.json b/NetworkManager/service/NetworkManager.json index 4de42f885a..cf6f0e0ad2 100644 --- a/NetworkManager/service/NetworkManager.json +++ b/NetworkManager/service/NetworkManager.json @@ -1225,8 +1225,87 @@ "success" ] } + }, + "EnableInterface":{ + "summary": "Enable the specified interface", + "events":{ + "onInterfaceStateChange" : "Triggered when interface’s status changes to enabled." + }, + "params": { + "type": "object", + "properties": { + "interface": { + "summary": "Enable the specified interface", + "type": "string", + "example": "wlan0" + } + }, + "required": [ + "interface" + ] + }, + "result": { + "type": "object", + "properties": { + "success": { + "$ref": "#/common/success" + } + }, + "required": [ + "success" + ] + } + }, + "DisableInterface":{ + "summary": "Disable the specified interface", + "events":{ + "onInterfaceStateChange" : "Triggered when interface’s status changes to disabled." + }, + "params": { + "type": "object", + "properties": { + "interface": { + "summary": "Disable the specified interface", + "type": "string", + "example": "wlan0" + } + }, + "required": [ + "interface" + ] + }, + "result": { + "type": "object", + "properties": { + "success": { + "$ref": "#/common/success" + } + }, + "required": [ + "success" + ] + } + }, + "GetWifiState": { + "summary": "Returns the current Wifi State. The possible Wifi states are as follows. \n**Wifi States** \n* `0`: UNINSTALLED - The device was in an installed state and was uninstalled; or, the device does not have a Wifi radio installed \n* `1`: DISABLED - The device is installed but not yet enabled \n* `2`: DISCONNECTED - The device is installed and enabled, but not yet connected to a network \n* `3`: PAIRING - The device is in the process of pairing, but not yet connected to a network \n* `4`: CONNECTING - The device is attempting to connect to a network \n* `5`: CONNECTED - The device is successfully connected to a network \n* `6`: SSID_NOT_FOUND - The requested SSID to connect is not found \n* `7`: SSID_CHANGED - The device connected SSID is changed \n* `8`: CONNECTION_LOST - The device network connection is lost \n* `9`: CONNECTION_FAILED - The device connection got failed \n* `10`: CONNECTION_INTERRUPTED - The device connection is interrupted \n* `11`: INVALID_CREDENTIALS - The credentials provided to connect is not valid \n* `12`: AUTHENTICATION_FAILED - Authentication process as a whole could not be successfully completed \n* `13`: ERROR - The device has encountered an unrecoverable error with the Wifi adapter.", + "result": { + "type": "object", + "properties": { + "state": { + "$ref": "#/definitions/state" + }, + "success":{ + "$ref": "#/common/success" + } + }, + "required": [ + "state", + "success" + ] + } } }, + "events": { "onInterfaceStateChange":{ "summary": "Triggered when an interface state is changed. The possible states are \n* 'INTERFACE_ADDED' \n* 'INTERFACE_LINK_UP' \n* 'INTERFACE_LINK_DOWN' \n* 'INTERFACE_ACQUIRING_IP' \n* 'INTERFACE_REMOVED' \n* 'INTERFACE_DISABLED' \n", diff --git a/NetworkManager/service/NetworkManagerGnomeProxy.cpp b/NetworkManager/service/NetworkManagerGnomeProxy.cpp index 4334d5bc0d..31795adf6b 100755 --- a/NetworkManager/service/NetworkManagerGnomeProxy.cpp +++ b/NetworkManager/service/NetworkManagerGnomeProxy.cpp @@ -173,17 +173,39 @@ namespace WPEFramework return rc; } - uint32_t NetworkManagerImplementation::SetInterfaceEnabled (const string& interface/* @in */, const bool& isEnabled /* @in */) + uint32_t NetworkManagerImplementation::EnableInterface (const string& interface/* @in */) + { + uint32_t rc = Core::ERROR_NONE; + const GPtrArray *devices = nm_client_get_devices(client); + NMDevice *device = NULL; + + for (guint i = 0; i < devices->len; ++i) { + device = NM_DEVICE(g_ptr_array_index(devices, i)); + + // Get the device details + const char *name = nm_device_get_iface(device); + + // Check if the device name matches + if (g_strcmp0(name, interface.c_str()) == 0) { + nm_device_set_managed(device, true); + + NMLOG_TRACE("Interface %s status set to enabled\n", + interface.c_str()); + } + } + + // Cleanup + if(device) + g_clear_object(&device); + return rc; + } + + uint32_t NetworkManagerImplementation::DisableInterface (const string& interface/* @in */) { uint32_t rc = Core::ERROR_NONE; const GPtrArray *devices = nm_client_get_devices(client); - NMDeviceState new_state; NMDevice *device = NULL; - if(isEnabled) - new_state = NM_DEVICE_STATE_ACTIVATED; - else - new_state = NM_DEVICE_STATE_DEACTIVATING; for (guint i = 0; i < devices->len; ++i) { device = NM_DEVICE(g_ptr_array_index(devices, i)); @@ -192,11 +214,10 @@ namespace WPEFramework // Check if the device name matches if (g_strcmp0(name, interface.c_str()) == 0) { - nm_device_set_managed(device, isEnabled); + nm_device_set_managed(device, false); - NMLOG_TRACE("Interface %s status set to %s\n", - interface.c_str(), - (new_state == NM_DEVICE_STATE_ACTIVATED) ? "enabled" : "disabled"); + NMLOG_TRACE("Interface %s status set to disabled\n", + interface.c_str()); } } diff --git a/NetworkManager/service/NetworkManagerImplementation.h b/NetworkManager/service/NetworkManagerImplementation.h index f9228fa5ec..20b018e631 100644 --- a/NetworkManager/service/NetworkManagerImplementation.h +++ b/NetworkManager/service/NetworkManagerImplementation.h @@ -151,7 +151,8 @@ namespace WPEFramework /* @brief Set the active Interface used for external world communication */ uint32_t SetPrimaryInterface (const string& interface/* @in */) override; - uint32_t SetInterfaceEnabled (const string& interface/* @in */, const bool& isEnabled /* @in */) override; + uint32_t EnableInterface (const string& interface/* @in */) override; + uint32_t DisableInterface (const string& interface/* @in */) override; /* @brief Get IP Address Of the Interface */ uint32_t GetIPSettings(const string& interface /* @in */, const string &ipversion /* @in */, IPAddressInfo& result /* @out */) override; /* @brief Set IP Address Of the Interface */ diff --git a/NetworkManager/service/NetworkManagerJsonRpc.cpp b/NetworkManager/service/NetworkManagerJsonRpc.cpp index ed64e0ee9a..049add3993 100644 --- a/NetworkManager/service/NetworkManagerJsonRpc.cpp +++ b/NetworkManager/service/NetworkManagerJsonRpc.cpp @@ -43,7 +43,8 @@ namespace WPEFramework Register("GetAvailableInterfaces", &NetworkManager::GetAvailableInterfaces, this); Register("GetPrimaryInterface", &NetworkManager::GetPrimaryInterface, this); Register("SetPrimaryInterface", &NetworkManager::SetPrimaryInterface, this); - Register("SetInterfaceEnabled", &NetworkManager::SetInterfaceEnabled, this); + Register("EnableInterface", &NetworkManager::EnableInterface, this); + Register("DisableInterface", &NetworkManager::DisableInterface, this); Register("GetIPSettings", &NetworkManager::GetIPSettings, this); Register("SetIPSettings", &NetworkManager::SetIPSettings, this); Register("GetStunEndpoint", &NetworkManager::GetStunEndpoint, this); @@ -81,7 +82,8 @@ namespace WPEFramework Unregister("GetAvailableInterfaces"); Unregister("GetPrimaryInterface"); Unregister("SetPrimaryInterface"); - Unregister("SetInterfaceEnabled"); + Unregister("EnableInterface"); + Unregister("DisableInterface"); Unregister("GetIPSettings"); Unregister("SetIPSettings"); Unregister("GetStunEndpoint"); @@ -209,14 +211,31 @@ namespace WPEFramework return rc; } - uint32_t NetworkManager::SetInterfaceEnabled (const JsonObject& parameters, JsonObject& response) + uint32_t NetworkManager::EnableInterface (const JsonObject& parameters, JsonObject& response) { LOGINFOMETHOD(); uint32_t rc = Core::ERROR_GENERAL; string interface = parameters["interface"].String(); - bool isEnabled = parameters["enable"].Boolean(); if (_NetworkManager) - rc = _NetworkManager->SetInterfaceEnabled(interface, isEnabled); + rc = _NetworkManager->EnableInterface(interface); + else + rc = Core::ERROR_UNAVAILABLE; + + if (Core::ERROR_NONE == rc) + { + response["success"] = true; + } + LOGTRACEMETHODFIN(); + return rc; + } + + uint32_t NetworkManager::DisableInterface (const JsonObject& parameters, JsonObject& response) + { + LOGINFOMETHOD(); + uint32_t rc = Core::ERROR_GENERAL; + string interface = parameters["interface"].String(); + if (_NetworkManager) + rc = _NetworkManager->DisableInterface(interface); else rc = Core::ERROR_UNAVAILABLE; diff --git a/NetworkManager/service/NetworkManagerLegacy.cpp b/NetworkManager/service/NetworkManagerLegacy.cpp index 2055878ce0..4c6fa019fc 100644 --- a/NetworkManager/service/NetworkManagerLegacy.cpp +++ b/NetworkManager/service/NetworkManagerLegacy.cpp @@ -184,8 +184,10 @@ const string CIDR_PREFIXES[CIDR_NETMASK_IP_LEN] = { interface = "eth0"; tmpParameters["interface"] = interface; - tmpParameters["enabled"] = parameters["enabled"]; - rc = SetInterfaceEnabled(tmpParameters, response); + if(parameters["enabled"].Boolean()) + rc = EnableInterface(tmpParameters, response); + else + rc = DisableInterface(tmpParameters, response); LOGTRACEMETHODFIN(); return rc; diff --git a/NetworkManager/service/NetworkManagerRDKProxy.cpp b/NetworkManager/service/NetworkManagerRDKProxy.cpp index e6177fd27d..2f9ac021cb 100644 --- a/NetworkManager/service/NetworkManagerRDKProxy.cpp +++ b/NetworkManager/service/NetworkManagerRDKProxy.cpp @@ -621,7 +621,7 @@ namespace WPEFramework return rc; } - uint32_t NetworkManagerImplementation::SetInterfaceEnabled (const string& interface/* @in */, const bool& isEnabled /* @in */) + uint32_t NetworkManagerImplementation::EnableInterface (const string& interface/* @in */) { LOG_ENTRY_FUNCTION(); uint32_t rc = Core::ERROR_RPC_CALL_FAILED; @@ -639,7 +639,39 @@ namespace WPEFramework return rc; } - iarmData.isInterfaceEnabled = isEnabled; + iarmData.isInterfaceEnabled = true; + iarmData.persist = true; + if (IARM_RESULT_SUCCESS == IARM_Bus_Call (IARM_BUS_NM_SRV_MGR_NAME, IARM_BUS_NETSRVMGR_API_setInterfaceEnabled, (void *)&iarmData, sizeof(iarmData))) + { + NMLOG_INFO ("Call to %s for %s success", IARM_BUS_NM_SRV_MGR_NAME, IARM_BUS_NETSRVMGR_API_setInterfaceEnabled); + rc = Core::ERROR_NONE; + } + else + { + NMLOG_ERROR ("Call to %s for %s failed", IARM_BUS_NM_SRV_MGR_NAME, IARM_BUS_NETSRVMGR_API_setInterfaceEnabled); + } + return rc; + } + + uint32_t NetworkManagerImplementation::DisableInterface (const string& interface/* @in */) + { + LOG_ENTRY_FUNCTION(); + uint32_t rc = Core::ERROR_RPC_CALL_FAILED; + IARM_BUS_NetSrvMgr_Iface_EventData_t iarmData = { 0 }; + + /* Netsrvmgr returns eth0 & wlan0 as primary interface but when we want to set., we must set ETHERNET or WIFI*/ + //TODO: Fix netsrvmgr to accept eth0 & wlan0 + if ("wlan0" == interface) + strncpy(iarmData.setInterface, "WIFI", INTERFACE_SIZE); + else if ("eth0" == interface) + strncpy(iarmData.setInterface, "ETHERNET", INTERFACE_SIZE); + else + { + rc = Core::ERROR_BAD_REQUEST; + return rc; + } + + iarmData.isInterfaceEnabled = false; iarmData.persist = true; if (IARM_RESULT_SUCCESS == IARM_Bus_Call (IARM_BUS_NM_SRV_MGR_NAME, IARM_BUS_NETSRVMGR_API_setInterfaceEnabled, (void *)&iarmData, sizeof(iarmData))) { diff --git a/docs/api/NetworkManagerPlugin.md b/docs/api/NetworkManagerPlugin.md index 58a93f7be7..39f2d2d1e2 100644 --- a/docs/api/NetworkManagerPlugin.md +++ b/docs/api/NetworkManagerPlugin.md @@ -84,6 +84,9 @@ NetworkManager interface methods: | [GetWiFiSignalStrength](#method.GetWiFiSignalStrength) | Get WiFiSignalStrength of connected SSID | | [GetSupportedSecurityModes](#method.GetSupportedSecurityModes) | Returns the Wifi security modes that the device supports | | [SetLogLevel](#method.SetLogLevel) | Set Log level for more information | +| [EnableInterface](#method.EnableInterface) | Enable the interface | +| [DisableInterface](#method.DisableInterface) | Disable the interface | +| [GetWifiState](#method.GetWifiState) | Returns the current Wifi State | @@ -1693,6 +1696,170 @@ No Events } ``` + +## *EnableInterface [method](#head.Methods)* + +Enable the specified interface. + +### Events + +| Event | Description | +| :-------- | :-------- | +| [onInterfaceStateChange](#event.onInterfaceStateChange) | Triggered when interface’s status changes to enabled. | + +### Parameters + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| params | object | | +| params.interface | string | An interface, such as `eth0` or `wlan0`, depending upon availability of the given interface in `GetAvailableInterfaces` | + +### Result + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| result | object | | +| result.success | boolean | Whether the request succeeded | + +### Example + +#### Request + +```json +{ + "jsonrpc": "2.0", + "id": 42, + "method": "org.rdk.NetworkManager.EnableInterface", + "params": { + "interface": "wlan0" + } +} +``` + +#### Response + +```json +{ + "jsonrpc": "2.0", + "id": 42, + "result": { + "success": true + } +} +``` + + +## *DisableInterface [method](#head.Methods)* + +Disable the specified interface. + +### Events + +| Event | Description | +| :-------- | :-------- | +| [onInterfaceStateChange](#event.onInterfaceStateChange) | Triggered when interface’s status changes to disabled. | + +### Parameters + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| params | object | | +| params.interface | string | An interface, such as `eth0` or `wlan0`, depending upon availability of the given interface in `GetAvailableInterfaces` | + +### Result + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| result | object | | +| result.success | boolean | Whether the request succeeded | + +### Example + +#### Request + +```json +{ + "jsonrpc": "2.0", + "id": 42, + "method": "org.rdk.NetworkManager.DisableInterface", + "params": { + "interface": "wlan0" + } +} +``` + +#### Response + +```json +{ + "jsonrpc": "2.0", + "id": 42, + "result": { + "success": true + } +} +``` + + +## *GetWifiState [method](#head.Methods)* + +Returns the current Wifi State. The possible Wifi states are as follows. +* `0`: UNINSTALLED - The device was in an installed state and was uninstalled; or, the device does not have a Wifi radio installed +* `1`: DISABLED - The device is installed but not yet enabled +* `2`: DISCONNECTED - The device is installed and enabled, but not yet connected to a network +* `3`: PAIRING - The device is in the process of pairing, but not yet connected to a network +* `4`: CONNECTING - The device is attempting to connect to a network +* `5`: CONNECTED - The device is successfully connected to a network +* `6`: SSID_NOT_FOUND - The requested SSID to connect is not found +* `7`: SSID_CHANGED - The device connected SSID is changed +* `8`: CONNECTION_LOST - The device network connection is lost +* `9`: CONNECTION_FAILED - The device network connection got failed +* `10`: CONNECTION_INTERRUPTED - The device connection is interrupted +* `11`: INVALID_CREDENTIALS - The credentials provided to connect is not valid +* `12`: AUTHENTICATION_FAILED - Authentication process as a whole could not be successfully completed +* `13`: ERROR - The device has encountered an unrecoverable error with the Wifi adapter + +### Events + +No Events + +### Parameters + +This method takes no parameters. + +### Result + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| result | object | | +| result.state | integer | The Wifi operational state | +| result.success | boolean | Whether the request succeeded | + +### Example + +#### Request + +```json +{ + "jsonrpc": "2.0", + "id": 42, + "method": "org.rdk.Wifi.GetWifiState" +} +``` + +#### Response + +```json +{ + "jsonrpc": "2.0", + "id": 42, + "result": { + "state": 2, + "success": true + } +} +``` + # Notifications From 257e32989cd3efdb1ffb5cf088249f912687d1cb Mon Sep 17 00:00:00 2001 From: hgfell683 <107510770+hgfell683@users.noreply.github.com> Date: Wed, 20 Mar 2024 10:53:23 -0400 Subject: [PATCH 07/16] DELIA-62029 - Detached threads may not end properly. DELIA-62029 - Detached threads may not end properly. Reason for change: Update detached threads, such that we wait for them to finish before de-initiailizing the system. Test Procedure: None Risks: Low Priority: P1 Signed-off-by:Hayden Gfeller --- DisplaySettings/DisplaySettings.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/DisplaySettings/DisplaySettings.cpp b/DisplaySettings/DisplaySettings.cpp index 836c2424e2..5eee43c38c 100644 --- a/DisplaySettings/DisplaySettings.cpp +++ b/DisplaySettings/DisplaySettings.cpp @@ -561,10 +561,10 @@ namespace WPEFramework { { if (m_sendMsgThread.joinable()) m_sendMsgThread.join(); + if(audioPortInitThread.joinable()){ + audioPortInitThread.join() + } } - if(audioPortInitThread.joinable()){ - audioPortInitThread.join() - } catch(const std::system_error& e) { LOGERR("system_error exception in thread join %s", e.what()); From 3f54b5fa324af86c68dd126680bd43aba9ae0c27 Mon Sep 17 00:00:00 2001 From: Nikita Poltorapavlo Date: Wed, 20 Mar 2024 17:03:01 +0200 Subject: [PATCH 08/16] RDK-47994 : Return ERROR_GENERAL if call with wrong scope Reason for change: Calling apis unsupported for account scope did use device scope. Test Procedure: None Risks: None Signed-off-by: Nikita Poltorapavlo --- PersistentStore/grpc/Store2.h | 14 +++++++++++++- PersistentStore/sqlite/Store2.h | 12 ++++++++++++ PersistentStore/sqlite/StoreInspector.h | 9 +++++++++ PersistentStore/sqlite/StoreLimit.h | 6 ++++++ 4 files changed, 40 insertions(+), 1 deletion(-) diff --git a/PersistentStore/grpc/Store2.h b/PersistentStore/grpc/Store2.h index 9caf1684e6..c7ef3912af 100644 --- a/PersistentStore/grpc/Store2.h +++ b/PersistentStore/grpc/Store2.h @@ -145,6 +145,9 @@ namespace Plugin { uint32_t SetValue(const ScopeType scope, const string& ns, const string& key, const string& value, const uint32_t ttl) override { ASSERT(scope == ScopeType::ACCOUNT); + if (scope != ScopeType::ACCOUNT) { + return Core::ERROR_GENERAL; + } uint32_t result; @@ -186,6 +189,9 @@ namespace Plugin { uint32_t GetValue(const ScopeType scope, const string& ns, const string& key, string& value, uint32_t& ttl) override { ASSERT(scope == ScopeType::ACCOUNT); + if (scope != ScopeType::ACCOUNT) { + return Core::ERROR_GENERAL; + } uint32_t result; @@ -239,6 +245,9 @@ namespace Plugin { uint32_t DeleteKey(const ScopeType scope, const string& ns, const string& key) override { ASSERT(scope == ScopeType::ACCOUNT); + if (scope != ScopeType::ACCOUNT) { + return Core::ERROR_GENERAL; + } uint32_t result; @@ -271,6 +280,9 @@ namespace Plugin { uint32_t DeleteNamespace(const ScopeType scope, const string& ns) override { ASSERT(scope == ScopeType::ACCOUNT); + if (scope != ScopeType::ACCOUNT) { + return Core::ERROR_GENERAL; + } uint32_t result; @@ -307,7 +319,7 @@ namespace Plugin { index(_clients.begin()); while (index != _clients.end()) { - (*index)->ValueChanged(ScopeType::DEVICE, ns, key, value); + (*index)->ValueChanged(ScopeType::ACCOUNT, ns, key, value); index++; } } diff --git a/PersistentStore/sqlite/Store2.h b/PersistentStore/sqlite/Store2.h index c106b088b8..d5412da91d 100644 --- a/PersistentStore/sqlite/Store2.h +++ b/PersistentStore/sqlite/Store2.h @@ -145,6 +145,9 @@ namespace Plugin { uint32_t SetValue(const ScopeType scope, const string& ns, const string& key, const string& value, const uint32_t ttl) override { ASSERT(scope == ScopeType::DEVICE); + if (scope != ScopeType::DEVICE) { + return Core::ERROR_GENERAL; + } uint32_t result; @@ -195,6 +198,9 @@ namespace Plugin { uint32_t GetValue(const ScopeType scope, const string& ns, const string& key, string& value, uint32_t& ttl) override { ASSERT(scope == ScopeType::DEVICE); + if (scope != ScopeType::DEVICE) { + return Core::ERROR_GENERAL; + } uint32_t result; @@ -253,6 +259,9 @@ namespace Plugin { uint32_t DeleteKey(const ScopeType scope, const string& ns, const string& key) override { ASSERT(scope == ScopeType::DEVICE); + if (scope != ScopeType::DEVICE) { + return Core::ERROR_GENERAL; + } uint32_t result; @@ -279,6 +288,9 @@ namespace Plugin { uint32_t DeleteNamespace(const ScopeType scope, const string& ns) override { ASSERT(scope == ScopeType::DEVICE); + if (scope != ScopeType::DEVICE) { + return Core::ERROR_GENERAL; + } uint32_t result; diff --git a/PersistentStore/sqlite/StoreInspector.h b/PersistentStore/sqlite/StoreInspector.h index e1c03a99a8..94cf92a043 100644 --- a/PersistentStore/sqlite/StoreInspector.h +++ b/PersistentStore/sqlite/StoreInspector.h @@ -84,6 +84,9 @@ namespace Plugin { uint32_t GetKeys(const ScopeType scope, const string& ns, RPC::IStringIterator*& keys) override { ASSERT(scope == ScopeType::DEVICE); + if (scope != ScopeType::DEVICE) { + return Core::ERROR_GENERAL; + } uint32_t result; @@ -114,6 +117,9 @@ namespace Plugin { uint32_t GetNamespaces(const ScopeType scope, RPC::IStringIterator*& namespaces) override { ASSERT(scope == ScopeType::DEVICE); + if (scope != ScopeType::DEVICE) { + return Core::ERROR_GENERAL; + } uint32_t result; @@ -139,6 +145,9 @@ namespace Plugin { uint32_t GetStorageSizes(const ScopeType scope, INamespaceSizeIterator*& storageList) override { ASSERT(scope == ScopeType::DEVICE); + if (scope != ScopeType::DEVICE) { + return Core::ERROR_GENERAL; + } uint32_t result; diff --git a/PersistentStore/sqlite/StoreLimit.h b/PersistentStore/sqlite/StoreLimit.h index 86f9300a7f..145b71d174 100644 --- a/PersistentStore/sqlite/StoreLimit.h +++ b/PersistentStore/sqlite/StoreLimit.h @@ -91,6 +91,9 @@ namespace Plugin { uint32_t SetNamespaceStorageLimit(const ScopeType scope, const string& ns, const uint32_t size) override { ASSERT(scope == ScopeType::DEVICE); + if (scope != ScopeType::DEVICE) { + return Core::ERROR_GENERAL; + } uint32_t result; @@ -129,6 +132,9 @@ namespace Plugin { uint32_t GetNamespaceStorageLimit(const ScopeType scope, const string& ns, uint32_t& size) override { ASSERT(scope == ScopeType::DEVICE); + if (scope != ScopeType::DEVICE) { + return Core::ERROR_GENERAL; + } uint32_t result; From 0470144fac6e3cc94160e0b9d02c9bffd3df162f Mon Sep 17 00:00:00 2001 From: hgfell683 <107510770+hgfell683@users.noreply.github.com> Date: Wed, 20 Mar 2024 12:01:49 -0400 Subject: [PATCH 09/16] DELIA-62029 - Detached threads may not end properly. DELIA-62029 - Detached threads may not end properly. Reason for change: Update detached threads, such that we wait for them to finish before de-initiailizing the system. Test Procedure: None Risks: Low Priority: P1 Signed-off-by:Hayden Gfeller --- DisplaySettings/DisplaySettings.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DisplaySettings/DisplaySettings.cpp b/DisplaySettings/DisplaySettings.cpp index 5eee43c38c..14c4495a58 100644 --- a/DisplaySettings/DisplaySettings.cpp +++ b/DisplaySettings/DisplaySettings.cpp @@ -562,7 +562,7 @@ namespace WPEFramework { if (m_sendMsgThread.joinable()) m_sendMsgThread.join(); if(audioPortInitThread.joinable()){ - audioPortInitThread.join() + audioPortInitThread.join(); } } catch(const std::system_error& e) From 82e044d75332bf91b99d64fe1b616842506d85a8 Mon Sep 17 00:00:00 2001 From: Utkarsh Patel Date: Wed, 20 Mar 2024 16:22:58 +0000 Subject: [PATCH 10/16] LLAMA-13554 : fix return response for low latency --- AVOutput/AVOutputTV.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AVOutput/AVOutputTV.cpp b/AVOutput/AVOutputTV.cpp index 1b5a2f25ef..7bfbb3a41d 100644 --- a/AVOutput/AVOutputTV.cpp +++ b/AVOutput/AVOutputTV.cpp @@ -3408,7 +3408,7 @@ namespace Plugin { ret = GetLowLatencyState(&prevLowLatencyIndex); if(ret != tvERROR_NONE) { LOGERR("Get previous low latency state failed\n"); - returnResponse(false, getErrorString(ret).c_str()); + returnResponse(false); } params[0]=lowLatencyIndex; From 8f6657fe0ff7e3b0ed8acf92f492d5a1b9750be5 Mon Sep 17 00:00:00 2001 From: "Vivek.A" Date: Thu, 21 Mar 2024 06:32:37 +0000 Subject: [PATCH 11/16] Update LoggingUtils to work on cgroup v2 as well --- WebKitBrowser/LoggingUtils.cpp | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/WebKitBrowser/LoggingUtils.cpp b/WebKitBrowser/LoggingUtils.cpp index 65fa91eeaa..d17a5e4156 100644 --- a/WebKitBrowser/LoggingUtils.cpp +++ b/WebKitBrowser/LoggingUtils.cpp @@ -38,6 +38,7 @@ bool RedirectAllLogsToService(const string& target_service) fprintf(stderr, "RedirectLog: Invalid argument. Logs target service expected\n"); return false; } + const string kServiceExt = ".service"; if (target_service.size() <= kServiceExt.size() || target_service.substr(target_service.size() - kServiceExt.size(), kServiceExt.size()) != kServiceExt) { @@ -45,10 +46,15 @@ bool RedirectAllLogsToService(const string& target_service) return false; } + const bool isCGroupV2 = g_file_test("/sys/fs/cgroup/unified", G_FILE_TEST_EXISTS); + const string targetHierarchy = isCGroupV2 ? "unified" : "systemd"; + const string targetPIDList = isCGroupV2 ? "cgroup.procs" : "tasks"; + const string targetServiceName = target_service.substr(0, target_service.size() - kServiceExt.size()); - const string kSystemdCgroupTargetTasksFilePath = "/sys/fs/cgroup/systemd/system.slice/" + target_service + "/tasks"; + const string kSystemdCgroupTargetTasksFilePath = "/sys/fs/cgroup/" + targetHierarchy + "/system.slice/" + target_service + "/" + targetPIDList; + if (!g_file_test(kSystemdCgroupTargetTasksFilePath.c_str(), G_FILE_TEST_EXISTS)) { - fprintf(stderr, "RedirectLog: %s unit cgroup doesn't exist\n", target_service.c_str()); + fprintf(stderr, "RedirectLog: %s doesn't exist for unit %s\n", kSystemdCgroupTargetTasksFilePath.c_str(), target_service.c_str()); return false; } @@ -57,10 +63,17 @@ bool RedirectAllLogsToService(const string& target_service) while (taskDir.Next() == true) { if (taskDir.Name() == "." || taskDir.Name() == "..") continue; + FILE* f = fopen(kSystemdCgroupTargetTasksFilePath.c_str(), "a"); if (f) { fprintf(f, "%s", taskDir.Name().c_str()); fclose(f); + + // In case of cgroup v2, it is enough to move just one task/thread + // to achieve migration of entire process + if (isCGroupV2) + break; + } else { fprintf(stderr, "RedirectLog: cannot move %s thread to '%s': %s\n", taskDir.Name().c_str(), kSystemdCgroupTargetTasksFilePath.c_str(), strerror(errno)); From b8a3111169174ee695ef5822e968b80ade9ad79e Mon Sep 17 00:00:00 2001 From: cmuhammedrafi <103924677+cmuhammedrafi@users.noreply.github.com> Date: Thu, 21 Mar 2024 20:57:54 +0530 Subject: [PATCH 12/16] DELIA-64762 connectivity monitor thread termination changed. (#5022) * connectivity monitor thread termination changed * connectivity monitor log line updated --- Network/NetworkConnectivity.cpp | 55 ++++++++++++++++----------------- 1 file changed, 27 insertions(+), 28 deletions(-) diff --git a/Network/NetworkConnectivity.cpp b/Network/NetworkConnectivity.cpp index baf41af7e8..64de3552da 100644 --- a/Network/NetworkConnectivity.cpp +++ b/Network/NetworkConnectivity.cpp @@ -333,8 +333,8 @@ namespace WPEFramework { } } } - //else - // LOGERR("endpoint = <%s> error = %d (%s)", endpoint, msg->data.result, curl_easy_strerror(msg->data.result)); + else + LOGERR("endpoint = <%s> curl error = %d (%s)", endpoint, msg->data.result, curl_easy_strerror(msg->data.result)); http_responses.push_back(response_code); } time_earlier = time_now; @@ -429,7 +429,11 @@ namespace WPEFramework { break; default: InternetConnectionState = NO_INTERNET; - LOGINFO("Internet State: NO_INTERNET Response code: <%d> %.1f%%", static_cast(http_response_code), (percentage*100)); + if(http_response_code == -1) + LOGERR("Internet State: NO_INTERNET curl error"); + else + LOGWARN("Internet State: NO_INTERNET Received http response code: <%d> %.1f%%", static_cast(http_response_code), percentage * 100); + break; } } @@ -513,29 +517,31 @@ namespace WPEFramework { bool ConnectivityMonitor::stopInitialConnectivityMonitoring() { - if (isMonitorThreadRunning()) + + if(isContinuesMonitoringNeeded) + { + LOGWARN("Continuous Connectivity Monitor is running"); + return true; + } + else { - if(isContinuesMonitoringNeeded) + if (!isMonitorThreadRunning()) { - LOGWARN("Continuous Connectivity Monitor is running"); - return true; + LOGWARN("Connectivity monitor not running"); } - else - { - stopFlag = true; - cv_.notify_all(); - if (thread_.joinable()) { - thread_.join(); - threadRunning = false; - LOGINFO("Stoping Initial Connectivity Monitor"); - } - else - LOGWARN("thread not joinable !"); + stopFlag = true; + cv_.notify_all(); + + if (thread_.joinable()) + { + thread_.join(); + threadRunning = false; + LOGINFO("Stoping Initial Connectivity Monitor"); } + else + LOGWARN("thread not joinable !"); } - else - LOGWARN("Continuous Connectivity Monitor not running"); return true; } @@ -545,11 +551,9 @@ namespace WPEFramework { if (!isMonitorThreadRunning()) { LOGWARN("Connectivity monitor not running"); - return false; } cv_.notify_all(); stopFlag = true; - LOGINFO("stoping connectivityMonitor..."); if (thread_.joinable()) { @@ -595,17 +599,12 @@ namespace WPEFramework { if(stopFlag) { - LOGWARN("stopFlag true exiting"); threadRunning = false; break; } //wait for next timout or conditon signal std::unique_lock lock(mutex_); - if (cv_.wait_for(lock, std::chrono::seconds(timeout.load())) == std::cv_status::timeout) - { - LOGINFO("Connectivity monitor thread timeout"); - } - else + if (cv_.wait_for(lock, std::chrono::seconds(timeout.load())) != std::cv_status::timeout) { if(!stopFlag) { From a0f060c9af740313dbc59a97760c327ca976b1fa Mon Sep 17 00:00:00 2001 From: Andrzej Surdej Date: Tue, 24 Oct 2023 10:56:15 +0200 Subject: [PATCH 13/16] WebKitBrowser: Add simple testing framework This change intorduces a simple testing framework that allows a web page to open child pages (in separate WPEWebProcess) and listen to child page results. It consist of: 1) custom JS APIS (Extension part): - window.testRunner.runTestCase() that opens test case child page -- tc.addEventListener() listen for events -- tc.destroy() destro child page - window.testCase.reportResult() for test case to report its result 2) Native code runs in the main process that helps to create/destroy subviews. Implemented via WebKit user messages IPC Each sub page is run in separate webkit context that makes it different from generic window.open() but it allows to crash a test case without crashing whole instance. Hidden under compile and runtime flags. Disabled by default --- WebKitBrowser/CMakeLists.txt | 8 + WebKitBrowser/Extension/CMakeLists.txt | 6 + WebKitBrowser/Extension/main.cpp | 26 +- .../Testing/Extension/TestRunnerJS.h | 35 +++ .../Testing/Extension/testrunnerextension.cpp | 274 ++++++++++++++++++ WebKitBrowser/Testing/tags.h | 35 +++ WebKitBrowser/Testing/testrunner.cpp | 205 +++++++++++++ WebKitBrowser/Testing/testrunner.h | 39 +++ WebKitBrowser/WebKitImplementation.cpp | 20 +- 9 files changed, 641 insertions(+), 7 deletions(-) create mode 100644 WebKitBrowser/Testing/Extension/TestRunnerJS.h create mode 100644 WebKitBrowser/Testing/Extension/testrunnerextension.cpp create mode 100644 WebKitBrowser/Testing/tags.h create mode 100644 WebKitBrowser/Testing/testrunner.cpp create mode 100644 WebKitBrowser/Testing/testrunner.h diff --git a/WebKitBrowser/CMakeLists.txt b/WebKitBrowser/CMakeLists.txt index 5788496e73..7e455f7946 100644 --- a/WebKitBrowser/CMakeLists.txt +++ b/WebKitBrowser/CMakeLists.txt @@ -50,6 +50,7 @@ option(PLUGIN_APPS_ENABLE_JIT "Enable the use of JIT javascript optimalization" option(PLUGIN_APPS_ENABLE_DFG "Enable the use of DFG javascript optimalization" OFF) option(PLUGIN_WEBKITBROWSER_CLOUD_COOKIEJAR "Enable support for exporting/importing cookie jar" OFF) option(PLUGIN_WEBKITBROWSER_LOGGING_UTILS "Enable possibility to redirect stdout/err to specific systemd service" OFF) +option(PLUGIN_WEBKITBROWSER_TESTING "Enable testing framework with custom JS APIs" OFF) set(PLUGIN_WEBKITBROWSER_IMPLEMENTATION "${MODULE_NAME}Impl" CACHE STRING "Specify a library with a webkit implementation." ) @@ -277,6 +278,13 @@ if (PLUGIN_WEBKITBROWSER_LOGGING_UTILS) target_sources(${PLUGIN_WEBKITBROWSER_IMPLEMENTATION} PRIVATE LoggingUtils.cpp) endif() +if (PLUGIN_WEBKITBROWSER_TESTING AND WEBKIT_GLIB_API) + # Enable for GLIB Api only + target_compile_definitions(${PLUGIN_WEBKITBROWSER_IMPLEMENTATION} PRIVATE ENABLE_TESTING) + target_include_directories(${PLUGIN_WEBKITBROWSER_IMPLEMENTATION} PRIVATE Testing) + target_sources(${PLUGIN_WEBKITBROWSER_IMPLEMENTATION} PRIVATE Testing/testrunner.cpp) +endif() + if(WPE_WEBKIT_DEPRECATED_API) target_compile_definitions(${PLUGIN_WEBKITBROWSER_IMPLEMENTATION} PRIVATE WPE_WEBKIT_DEPRECATED_API) endif() diff --git a/WebKitBrowser/Extension/CMakeLists.txt b/WebKitBrowser/Extension/CMakeLists.txt index 3af94589ca..86108ea9b2 100644 --- a/WebKitBrowser/Extension/CMakeLists.txt +++ b/WebKitBrowser/Extension/CMakeLists.txt @@ -69,6 +69,12 @@ if(PLUGIN_WEBKITBROWSER_CUSTOM_PROCESS_INFO) target_compile_definitions(${MODULE_NAME} PRIVATE ENABLE_CUSTOM_PROCESS_INFO) endif() +if (PLUGIN_WEBKITBROWSER_TESTING) + target_compile_definitions(${MODULE_NAME} PRIVATE ENABLE_TESTING) + target_include_directories(${MODULE_NAME} PRIVATE ../Testing/Extension) + target_sources(${MODULE_NAME} PRIVATE ../Testing/Extension/testrunnerextension.cpp) +endif() + set_target_properties(${MODULE_NAME} PROPERTIES CXX_STANDARD 11 CXX_STANDARD_REQUIRED YES) diff --git a/WebKitBrowser/Extension/main.cpp b/WebKitBrowser/Extension/main.cpp index 58d28d2b45..5adf09e013 100644 --- a/WebKitBrowser/Extension/main.cpp +++ b/WebKitBrowser/Extension/main.cpp @@ -53,6 +53,10 @@ #include "ProcessInfo.h" #endif +#if defined(ENABLE_TESTING) +#include +#endif + using namespace WPEFramework; static Core::NodeId GetConnectionNode() @@ -97,10 +101,11 @@ static class PluginHost { _extension = WEBKIT_WEB_EXTENSION(g_object_ref(extension)); _logToSystemConsoleEnabled = FALSE; - const char *uid; - const char *whitelist; + const char *uid = nullptr; + const char *whitelist = nullptr; - g_variant_get((GVariant*) userData, "(&sm&sb)", &uid, &whitelist, &_logToSystemConsoleEnabled); + if (userData) + g_variant_get((GVariant*) userData, "(&sm&sbb)", &uid, &whitelist, &_logToSystemConsoleEnabled, &_enableTesting); if (_logToSystemConsoleEnabled && Core::SystemInfo::GetEnvironment(string(_T("CLIENT_IDENTIFIER")), _consoleLogPrefix)) _consoleLogPrefix = _consoleLogPrefix.substr(0, _consoleLogPrefix.find(',')); @@ -109,7 +114,7 @@ static class PluginHost { webkit_script_world_get_default(), "window-object-cleared", G_CALLBACK(windowObjectClearedCallback), - nullptr); + this); g_signal_connect( extension, @@ -149,7 +154,7 @@ static class PluginHost { } private: - static void windowObjectClearedCallback(WebKitScriptWorld* world, WebKitWebPage* page, WebKitFrame* frame) + static void windowObjectClearedCallback(WebKitScriptWorld* world, WebKitWebPage* page, WebKitFrame* frame, PluginHost* host) { JavaScript::Milestone::InjectJS(world, frame); JavaScript::NotifyWPEFramework::InjectJS(world, frame); @@ -166,6 +171,11 @@ static class PluginHost { JavaScript::AAMP::LoadJSBindings(world, frame); #endif +#ifdef ENABLE_TESTING + if (host->_enableTesting) + JavaScript::TestRunner::InjectJS(world, page, frame); +#endif // ENABLE_TESTING + } static void pageCreatedCallback(VARIABLE_IS_NOT_USED WebKitWebExtension* webExtension, WebKitWebPage* page, @@ -203,6 +213,11 @@ static class PluginHost { || (g_strcmp0(name, Tags::BridgeObjectEvent) == 0)) { JavaScript::BridgeObject::HandleMessageToPage(page, name, message); } +#endif +#ifdef ENABLE_TESTING + else if (g_str_has_prefix(name, Testing::Tags::TestRunnerPrefix)) { + JavaScript::TestRunner::HandleMessageToPage(page, message); + } #endif return TRUE; } @@ -231,6 +246,7 @@ static class PluginHost { string _consoleLogPrefix; gboolean _logToSystemConsoleEnabled; + gboolean _enableTesting; WebKitWebExtension* _extension; } _wpeFrameworkClient; diff --git a/WebKitBrowser/Testing/Extension/TestRunnerJS.h b/WebKitBrowser/Testing/Extension/TestRunnerJS.h new file mode 100644 index 0000000000..4a25fe7106 --- /dev/null +++ b/WebKitBrowser/Testing/Extension/TestRunnerJS.h @@ -0,0 +1,35 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2020 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include "../tags.h" + +namespace WPEFramework { +namespace JavaScript { +namespace TestRunner { + +void InjectJS(WebKitScriptWorld *world, WebKitWebPage *page, WebKitFrame *frame); + +bool HandleMessageToPage(WebKitWebPage* page, WebKitUserMessage* message); + +} // TestRunner +} // JavaScript +} // WPEFramework diff --git a/WebKitBrowser/Testing/Extension/testrunnerextension.cpp b/WebKitBrowser/Testing/Extension/testrunnerextension.cpp new file mode 100644 index 0000000000..f80e9a9c56 --- /dev/null +++ b/WebKitBrowser/Testing/Extension/testrunnerextension.cpp @@ -0,0 +1,274 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2020 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "../tags.h" +#include +#include +#include + +namespace { + +// map of event listeners for current test case +static std::map eventListeners; +// current test case JS object +static JSCValue* jsTestCaseObj = nullptr; + +static void throwJSException(JSCContext *jsContext, const char *format, ...) +{ + va_list args; + + va_start(args, format); + JSCException* jsException = jsc_exception_new_vprintf(jsContext, format, args); + va_end(args); + + g_warning("TestRunnerJS: JSC exception thrown - %s", jsc_exception_get_message(jsException)); + jsc_context_throw_exception(jsContext, jsException); + g_object_unref(jsException); +} + + +static bool callEventListener(const std::string& eventName, JSCValue* event) +{ + auto it = eventListeners.find(eventName); + if (it == eventListeners.end()) { + g_warning("TestRunnerJS: Event listener %s not registered", eventName.c_str()); + return false; + } + jsc_value_function_call(it->second, JSC_TYPE_VALUE, event, G_TYPE_NONE); + return true; +} + +static bool sendEndedEvent(WebKitWebPage* page, bool success, const std::string& message) +{ + WebKitFrame* frame = webkit_web_page_get_main_frame(page); + + JSCContext* jsContext = webkit_frame_get_js_context(frame); + JSCValue* jsEventObject = jsc_value_new_object(jsContext, nullptr, nullptr); + + JSCValue* jsSuccessVal = jsc_value_new_boolean(jsContext, success); + jsc_value_object_define_property_data(jsEventObject, + "success", + JSC_VALUE_PROPERTY_CONFIGURABLE, + jsSuccessVal); + + JSCValue* jsMessageVal = jsc_value_new_string(jsContext, message.c_str()); + jsc_value_object_define_property_data(jsEventObject, + "message", + JSC_VALUE_PROPERTY_CONFIGURABLE, + jsMessageVal); + + callEventListener("ended", jsEventObject); + + g_object_unref(jsMessageVal); + g_object_unref(jsSuccessVal); + g_object_unref(jsEventObject); + g_object_unref(jsContext); + return true; +} + +static void onRunTestCaseReply(GObject *object, GAsyncResult *result, + gpointer userData) +{ + WebKitWebPage* page = (WebKitWebPage*) userData; + GError *error = nullptr; + WebKitUserMessage* reply = webkit_web_page_send_message_to_view_finish(page, result, &error); + if (error) { + g_warning("TestRunnerJS: Failed to run test case %s", error->message); + sendEndedEvent(page, false, error->message); + g_error_free(error); + return; + } + + gboolean success = FALSE; + auto replyParams = webkit_user_message_get_parameters(reply); + g_variant_get(replyParams, "b", &success); + if (!success) + sendEndedEvent(page, false, "Failed to create view"); + + g_object_unref(reply); +} + +static void onTestCaseDestroy(WebKitWebPage *page) +{ + jsTestCaseObj = nullptr; + eventListeners.clear(); + auto msgName = std::string(Testing::Tags::TestRunnerPrefix) + Testing::Tags::TestRunnerDestroyView; + WebKitUserMessage* message = webkit_user_message_new(msgName.c_str(), + nullptr); + webkit_web_page_send_message_to_view(page, + message, + nullptr, + nullptr, + nullptr); +} + +static void onTestCaseAddEventListener(const char* event_name, JSCValue* function, gpointer userData) +{ + if (!jsc_value_is_function(function)) { + g_warning("TestRunnerJS: addEventListener receive a non-function param."); + return; + } + + eventListeners[event_name] = (JSCValue*)g_object_ref(function); +} + +static JSCValue* onTestRunnerRunTestCase(GPtrArray* array, WebKitWebPage *page) +{ + JSCContext *jsContext = jsc_context_get_current(); + + // check an url string was supplied + if (array->len < 1) { + throwJSException(jsContext, "missing url argument"); + return jsc_value_new_null(jsContext); + } + + gpointer arg0 = g_ptr_array_index(array, 0); + if (!JSC_IS_VALUE(arg0) || !jsc_value_is_string(JSC_VALUE(arg0))) { + throwJSException(jsContext, "invalid url argument"); + return jsc_value_new_null(jsContext); + } + + if (jsTestCaseObj) { + throwJSException(jsContext, "Another test case in progress"); + return jsc_value_new_null(jsContext); + } + + gchar *str = jsc_value_to_string(JSC_VALUE(arg0)); + const std::string url(str); + g_free(str); + + std::string msgName = std::string(Testing::Tags::TestRunnerPrefix) + Testing::Tags::TestRunnerCreateView; + WebKitUserMessage *message = webkit_user_message_new(msgName.c_str(), + g_variant_new("s", url.c_str())); + webkit_web_page_send_message_to_view(page, + message, + nullptr, + onRunTestCaseReply, + page); + + jsTestCaseObj = jsc_value_new_object(jsContext, nullptr, nullptr); + + JSCValue* jsAddEventListenerFunction = jsc_value_new_function( + jsContext, + "addEventListener", + G_CALLBACK(onTestCaseAddEventListener), + nullptr, + nullptr, + G_TYPE_NONE, + 2, G_TYPE_STRING, JSC_TYPE_VALUE); + jsc_value_object_set_property(jsTestCaseObj, "addEventListener", jsAddEventListenerFunction); + g_object_unref(jsAddEventListenerFunction); + + JSCValue* jsDestroyFunction = jsc_value_new_function( + jsContext, + "destroy", + G_CALLBACK(onTestCaseDestroy), + page, + nullptr, + G_TYPE_NONE, + 0, G_TYPE_NONE); + jsc_value_object_set_property(jsTestCaseObj, "destroy", jsDestroyFunction); + g_object_unref(jsDestroyFunction); + + return jsTestCaseObj; +} + +static void onTestCaseReportResult(gboolean success, const char* msg, WebKitWebPage* page) +{ + auto msgName = std::string(Testing::Tags::TestCasePrefix) + Testing::Tags::TestCaseReportResult; + WebKitUserMessage *message = webkit_user_message_new(msgName.c_str(), + g_variant_new("(bs)", + success, msg)); + webkit_web_page_send_message_to_view(page, + message, + nullptr, + nullptr, + nullptr); +} + +bool endsWith(const std::string& str, const std::string& suffix) { + if (str.length() < suffix.length()) + return false; + return str.compare(str.length() - suffix.length(), suffix.length(), suffix) == 0; +} + +} // namespace + +namespace WPEFramework { +namespace JavaScript { +namespace TestRunner { + +void InjectJS(WebKitScriptWorld *world, WebKitWebPage *page, WebKitFrame *frame) +{ + if (!world || !page || !frame) { + g_warning("Invalid args: %p %p %p", world, page, frame); + return; + } + + if (!webkit_frame_is_main_frame(frame)) { + return; + } + + JSCContext* jsContext = webkit_frame_get_js_context_for_script_world(frame, world); + + // window.testRunner.runTestCase + JSCValue* jsTestRunnerObject = jsc_value_new_object(jsContext, nullptr, nullptr); + JSCValue* jsRunTestCaseFunction = jsc_value_new_function_variadic( + jsContext, nullptr, G_CALLBACK(onTestRunnerRunTestCase), page, nullptr, JSC_TYPE_VALUE); + jsc_value_object_set_property(jsTestRunnerObject, "runTestCase", jsRunTestCaseFunction); + jsc_context_set_value(jsContext, "testRunner", jsTestRunnerObject); + g_object_unref(jsRunTestCaseFunction); + g_object_unref(jsTestRunnerObject); + + // window.testCase.reportTestResult + JSCValue* jsTestCaseObject = jsc_value_new_object(jsContext, nullptr, nullptr); + JSCValue* jsReportResultFunction = jsc_value_new_function( + jsContext, + "reportResult", + G_CALLBACK(onTestCaseReportResult), + page, + nullptr, + G_TYPE_NONE, + 2, G_TYPE_BOOLEAN, G_TYPE_STRING); + jsc_value_object_set_property(jsTestCaseObject, "reportResult", jsReportResultFunction); + jsc_context_set_value(jsContext, "testCase", jsTestCaseObject); + g_object_unref(jsReportResultFunction); + g_object_unref(jsTestCaseObject); + + g_object_unref(jsContext); +} + +bool HandleMessageToPage(WebKitWebPage* page, WebKitUserMessage* message) +{ + // Message from UI process to testRunner web page + const char* name = webkit_user_message_get_name(message); + if (endsWith(name, Testing::Tags::TestRunnerCaseEnded)) { + gboolean success = FALSE; + const char* msg = nullptr; + auto replyParams = webkit_user_message_get_parameters(message); + g_variant_get(replyParams, "(b&s)", &success, &msg); + + sendEndedEvent(page, success, msg); + } + return true; +} + +} // TestRunner +} // JavaScript +} // WPEFramework diff --git a/WebKitBrowser/Testing/tags.h b/WebKitBrowser/Testing/tags.h new file mode 100644 index 0000000000..d9ead12a40 --- /dev/null +++ b/WebKitBrowser/Testing/tags.h @@ -0,0 +1,35 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2020 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +namespace Testing { +namespace Tags { + +const char* const TestRunnerPrefix = "testRunner."; +const char* const TestRunnerCreateView = "runTestCase"; +const char* const TestRunnerCreateViewReply = "runTestReply"; +const char* const TestRunnerDestroyView = "destroy"; +const char* const TestRunnerCaseEnded = "ended"; + +const char* const TestCasePrefix = "testCase."; +const char* const TestCaseReportResult = "reportResult"; + +} +} \ No newline at end of file diff --git a/WebKitBrowser/Testing/testrunner.cpp b/WebKitBrowser/Testing/testrunner.cpp new file mode 100644 index 0000000000..0e5ca7bd63 --- /dev/null +++ b/WebKitBrowser/Testing/testrunner.cpp @@ -0,0 +1,205 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2020 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "testrunner.h" + +#include +#include +#include +#include + +namespace { + +class TestRunnerImpl : public Testing::TestRunner { +public: + TestRunnerImpl() {} + ~TestRunnerImpl() {} + + bool EnsureInitialized(WebKitWebView* parent_view, const char* extension_dir) override; + bool handleUserMessage(WebKitUserMessage *message) override; + +private: + bool handleRunTestCase(WebKitUserMessage *message); + bool handleDestroyTestCase(WebKitUserMessage *message); + + + bool handleUserMessageFromTestCase(WebKitWebView *webView, + WebKitUserMessage *message); + bool handleTestCaseEnded(WebKitUserMessage *message); + + bool createSubView(); + bool prepareExtensionsDir(); + + static void initWebExtensionsCallback(WebKitWebContext *context, + void *userData); + static bool userMessageReceivedCallback(WebKitWebView *webView, + WebKitUserMessage *message, + void *userData); +private: + std::string m_extensionDir; + WebKitWebView* m_parentView = nullptr; + WebKitWebView* m_testCaseView = nullptr; +}; + +bool endsWith(const std::string& str, const std::string& suffix) { + if (str.length() < suffix.length()) + return false; + return str.compare(str.length() - suffix.length(), suffix.length(), suffix) == 0; +} + +bool TestRunnerImpl::EnsureInitialized(WebKitWebView* parent_view, const char* extension_dir) { + if (m_parentView) + return true; + + m_extensionDir = extension_dir; + m_parentView = parent_view; + return true; +} + +bool TestRunnerImpl::handleUserMessage(WebKitUserMessage *message) { + const char* msgName = webkit_user_message_get_name(message); + if (endsWith(msgName, Testing::Tags::TestRunnerCreateView)) { + handleRunTestCase(message); + } else if (endsWith(msgName, Testing::Tags::TestRunnerDestroyView)) { + handleDestroyTestCase(message); + } else { + g_warning("TestRunner: Received unknown user message '%s'", msgName); + } + return true; +} + +bool TestRunnerImpl::handleRunTestCase(WebKitUserMessage *message) { + GVariant *payload = webkit_user_message_get_parameters(message); + if (!payload) { + g_warning("TestRunner: Invalid message payload %p %p", message, payload); + return false; + } + const char* testUrl = nullptr; + g_variant_get(payload, "s", &testUrl); + if (!testUrl) { + g_warning("TestRunner: No test url provided"); + return false; + } + createSubView(); + assert(m_testCaseView); + + // load URL provided + webkit_web_view_load_uri(m_testCaseView, testUrl); + + auto msgName = std::string(Testing::Tags::TestRunnerPrefix) + Testing::Tags::TestRunnerCreateViewReply; + WebKitUserMessage* reply = webkit_user_message_new(msgName.c_str(), g_variant_new("b", true)); + webkit_user_message_send_reply(message, reply); + return true; +} + +bool TestRunnerImpl::createSubView() { + // Use the same settings as parent view to make sure we have the same behavior + WebKitWebsiteDataManager *dataManager = + webkit_web_view_get_website_data_manager(m_parentView); + WebKitWebContext *webkitContext = + webkit_web_context_new_with_website_data_manager(dataManager); + WebKitSettings *settings = webkit_web_view_get_settings (WEBKIT_WEB_VIEW(m_parentView)); + + g_signal_connect(webkitContext, "initialize-web-extensions", + G_CALLBACK(initWebExtensionsCallback), this); + + m_testCaseView = WEBKIT_WEB_VIEW(g_object_new(WEBKIT_TYPE_WEB_VIEW, + "backend", webkit_web_view_backend_new(wpe_view_backend_create(), nullptr, nullptr), + "web-context", webkitContext, + "settings", settings, + "is-controlled-by-automation", FALSE, + nullptr)); + g_object_unref(webkitContext); + + g_signal_connect(m_testCaseView, "user-message-received", G_CALLBACK(userMessageReceivedCallback), this); + // TODO: need to handle those signals + // g_signal_connect(m_testCaseView, "web-process-terminated", G_CALLBACK(webProcessTerminatedCallback), this); + // g_signal_connect(m_testCaseView, "notify::is-web-process-responsive", G_CALLBACK(isWebProcessResponsiveCallback), this); + // g_signal_connect(m_testCaseView, "load-failed", reinterpret_cast(loadFailedCallback), this); + return true; +} + +bool TestRunnerImpl::handleDestroyTestCase(WebKitUserMessage *message) { + g_clear_object(&m_testCaseView); + return true; +} + +bool TestRunnerImpl::handleUserMessageFromTestCase(WebKitWebView *webView, + WebKitUserMessage *message) +{ + // This is a message from test case sub-page WebProcess to UI Process + assert(webView == m_testCaseView); + const char* name = webkit_user_message_get_name(message); + if (!g_str_has_prefix(name, Testing::Tags::TestCasePrefix)) { + return false; + } + + if (endsWith(name, Testing::Tags::TestCaseReportResult)) { + handleTestCaseEnded(message); + } else { + g_warning("TestRunner: Unknown user message received from test case: %s", name); + } + return true; +} + +bool TestRunnerImpl::handleTestCaseEnded(WebKitUserMessage *message) { + GVariant *payload = webkit_user_message_get_parameters(message); + if (!payload) { + g_warning("TestRunner: Failed to get the user-message payload"); + return false; + } + gboolean success = FALSE; + const char* msg = nullptr; + g_variant_get(payload, "(b&s)", &success, &msg); + + auto msgName = std::string(Testing::Tags::TestRunnerPrefix) + Testing::Tags::TestRunnerCaseEnded; + webkit_web_view_send_message_to_page( + m_parentView, + webkit_user_message_new(msgName.c_str(), g_variant_new("(bs)", success, msg)), + nullptr, nullptr, nullptr); + return true; +} + +void TestRunnerImpl::initWebExtensionsCallback(WebKitWebContext *context, + void *userData) +{ + TestRunnerImpl* runner = (TestRunnerImpl*)userData; + webkit_web_context_set_web_extensions_directory(context, runner->m_extensionDir.c_str()); + GVariant* data = g_variant_new("(smsbb)", "", nullptr, true, true); + webkit_web_context_set_web_extensions_initialization_user_data(context, data); +} + +bool TestRunnerImpl::userMessageReceivedCallback(WebKitWebView *webView, + WebKitUserMessage *message, + void *userData) +{ + TestRunnerImpl* runner = (TestRunnerImpl*)userData; + return runner->handleUserMessageFromTestCase(webView, message); +} + +} // namespace + +namespace Testing { + +TestRunner* TestRunner::Instance() { + static TestRunnerImpl testRunner; + return &testRunner; +} + +} // namespace Testing \ No newline at end of file diff --git a/WebKitBrowser/Testing/testrunner.h b/WebKitBrowser/Testing/testrunner.h new file mode 100644 index 0000000000..d322cbad61 --- /dev/null +++ b/WebKitBrowser/Testing/testrunner.h @@ -0,0 +1,39 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2020 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "tags.h" +#include + +namespace Testing { + +class TestRunner { +public: + static TestRunner* Instance(); + + virtual bool EnsureInitialized(WebKitWebView* parent, const char* extensionDir) = 0; + virtual bool handleUserMessage(WebKitUserMessage *message) = 0; + +protected: + TestRunner() = default; + TestRunner(const TestRunner&) = delete; +}; + +} // namespace Testing diff --git a/WebKitBrowser/WebKitImplementation.cpp b/WebKitBrowser/WebKitImplementation.cpp index 076ddf8cf8..8d45555463 100644 --- a/WebKitBrowser/WebKitImplementation.cpp +++ b/WebKitBrowser/WebKitImplementation.cpp @@ -85,6 +85,9 @@ WK_EXPORT void WKPreferencesSetPageCacheEnabled(WKPreferencesRef preferences, bo #define HAS_MEMORY_PRESSURE_SETTINGS_API WEBKIT_CHECK_VERSION(2, 38, 0) #endif +#ifdef ENABLE_TESTING +#include +#endif // ENABLE_TESTING namespace WPEFramework { namespace Plugin { @@ -619,6 +622,7 @@ static GSourceFuncs _handlerIntervention = , ContentFilter() , LoggingTarget() , WebAudioEnabled(false) + , Testing(false) { Add(_T("useragent"), &UserAgent); Add(_T("url"), &URL); @@ -685,6 +689,7 @@ static GSourceFuncs _handlerIntervention = Add(_T("contentfilter"), &ContentFilter); Add(_T("loggingtarget"), &LoggingTarget); Add(_T("webaudio"), &WebAudioEnabled); + Add(_T("testing"), &Testing); } ~Config() { @@ -756,6 +761,7 @@ static GSourceFuncs _handlerIntervention = Core::JSON::String ContentFilter; Core::JSON::String LoggingTarget; Core::JSON::Boolean WebAudioEnabled; + Core::JSON::Boolean Testing; }; class HangDetector @@ -2605,7 +2611,11 @@ static GSourceFuncs _handlerIntervention = { webkit_web_context_set_web_extensions_directory(context, browser->_extensionPath.c_str()); // FIX it - GVariant* data = g_variant_new("(smsb)", std::to_string(browser->_guid).c_str(), !browser->_config.Whitelist.Value().empty() ? browser->_config.Whitelist.Value().c_str() : nullptr, browser->_config.LogToSystemConsoleEnabled.Value()); + GVariant* data = g_variant_new("(smsbb)", + std::to_string(browser->_guid).c_str(), + !browser->_config.Whitelist.Value().empty() ? browser->_config.Whitelist.Value().c_str() : nullptr, + browser->_config.LogToSystemConsoleEnabled.Value(), + browser->_config.Testing.Value()); webkit_web_context_set_web_extensions_initialization_user_data(context, data); } static void wpeNotifyWPEFrameworkMessageReceivedCallback(WebKitUserContentManager*, WebKitJavascriptResult* message, WebKitImplementation* browser) @@ -2715,7 +2725,7 @@ static GSourceFuncs _handlerIntervention = g_signal_connect(session, "create-web-view", reinterpret_cast(createWebViewForAutomationCallback), browser); } - static gboolean userMessageReceivedCallback(WebKitWebView*, WebKitUserMessage* message, WebKitImplementation* browser) + static gboolean userMessageReceivedCallback(WebKitWebView* view, WebKitUserMessage* message, WebKitImplementation* browser) { const char* name = webkit_user_message_get_name(message); if (g_strcmp0(name, Tags::BridgeObjectQuery) == 0) { @@ -2730,6 +2740,12 @@ static GSourceFuncs _handlerIntervention = string payloadStr(payloadPtr); browser->OnBridgeQuery(payloadStr); } +#ifdef ENABLE_TESTING + else if (browser->_config.Testing.Value() && g_str_has_prefix(name, Testing::Tags::TestRunnerPrefix)) { + Testing::TestRunner::Instance()->EnsureInitialized(view, browser->_extensionPath.c_str()); + Testing::TestRunner::Instance()->handleUserMessage(message); + } +#endif // ENABLE_TESTING return true; } #if defined(ENABLE_CLOUD_COOKIE_JAR) From df7358225201da96d19f4555b69b57f43c43b982 Mon Sep 17 00:00:00 2001 From: Andrzej Surdej Date: Fri, 17 Nov 2023 12:27:58 +0000 Subject: [PATCH 14/16] [WebKitBrowser] Improve testing module 1) Handle load-failed and web-process-terminated signals 2) Hide parent page during test case run to be able to see video layer 3) Introduce simple focus manager interface that will conttrol input focus. Add empty implementation to be replaced with device specific one. Signed-off-by: Andrzej Surdej --- WebKitBrowser/CMakeLists.txt | 3 +- WebKitBrowser/Testing/CMakeLists.txt | 22 +++++++ WebKitBrowser/Testing/focusmanager.h | 38 +++++++++++ WebKitBrowser/Testing/mockfocusmanager.cpp | 40 ++++++++++++ WebKitBrowser/Testing/testrunner.cpp | 74 +++++++++++++++++++++- 5 files changed, 172 insertions(+), 5 deletions(-) create mode 100644 WebKitBrowser/Testing/CMakeLists.txt create mode 100644 WebKitBrowser/Testing/focusmanager.h create mode 100644 WebKitBrowser/Testing/mockfocusmanager.cpp diff --git a/WebKitBrowser/CMakeLists.txt b/WebKitBrowser/CMakeLists.txt index 7e455f7946..8d0ecf5b5c 100644 --- a/WebKitBrowser/CMakeLists.txt +++ b/WebKitBrowser/CMakeLists.txt @@ -281,8 +281,7 @@ endif() if (PLUGIN_WEBKITBROWSER_TESTING AND WEBKIT_GLIB_API) # Enable for GLIB Api only target_compile_definitions(${PLUGIN_WEBKITBROWSER_IMPLEMENTATION} PRIVATE ENABLE_TESTING) - target_include_directories(${PLUGIN_WEBKITBROWSER_IMPLEMENTATION} PRIVATE Testing) - target_sources(${PLUGIN_WEBKITBROWSER_IMPLEMENTATION} PRIVATE Testing/testrunner.cpp) + include(Testing/CMakeLists.txt) endif() if(WPE_WEBKIT_DEPRECATED_API) diff --git a/WebKitBrowser/Testing/CMakeLists.txt b/WebKitBrowser/Testing/CMakeLists.txt new file mode 100644 index 0000000000..b18bd6cb6d --- /dev/null +++ b/WebKitBrowser/Testing/CMakeLists.txt @@ -0,0 +1,22 @@ +# If not stated otherwise in this file or this component's LICENSE file the +# following copyright and licenses apply: +# +# Copyright 2020 RDK Management +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +target_include_directories(${PLUGIN_WEBKITBROWSER_IMPLEMENTATION} PRIVATE Testing) +target_sources(${PLUGIN_WEBKITBROWSER_IMPLEMENTATION} PRIVATE + Testing/testrunner.cpp + Testing/mockfocusmanager.cpp +) diff --git a/WebKitBrowser/Testing/focusmanager.h b/WebKitBrowser/Testing/focusmanager.h new file mode 100644 index 0000000000..a961d4b7f0 --- /dev/null +++ b/WebKitBrowser/Testing/focusmanager.h @@ -0,0 +1,38 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2020 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +namespace Testing { + +class FocusManager +{ +public: + static FocusManager* Instance(); + + virtual bool Start() = 0; + virtual bool Stop() = 0; + + virtual ~FocusManager() = default; +protected: + FocusManager() = default; + FocusManager(const FocusManager&) = delete; +}; + +} // namespace Testing \ No newline at end of file diff --git a/WebKitBrowser/Testing/mockfocusmanager.cpp b/WebKitBrowser/Testing/mockfocusmanager.cpp new file mode 100644 index 0000000000..ad0bf4a224 --- /dev/null +++ b/WebKitBrowser/Testing/mockfocusmanager.cpp @@ -0,0 +1,40 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2020 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "focusmanager.h" + +namespace { + +class MockFocusManager : public Testing::FocusManager +{ +public: + MockFocusManager() = default; + + bool Start() override {}; + bool Stop() override {}; +}; + +} // namespace + +namespace Testing { +FocusManager* FocusManager::Instance() { + static MockFocusManager focusManager; + return &focusManager; +} +} // namespace Testing \ No newline at end of file diff --git a/WebKitBrowser/Testing/testrunner.cpp b/WebKitBrowser/Testing/testrunner.cpp index 0e5ca7bd63..a8385f0439 100644 --- a/WebKitBrowser/Testing/testrunner.cpp +++ b/WebKitBrowser/Testing/testrunner.cpp @@ -18,8 +18,10 @@ */ #include "testrunner.h" +#include "focusmanager.h" #include +#include #include #include #include @@ -43,14 +45,22 @@ class TestRunnerImpl : public Testing::TestRunner { WebKitUserMessage *message); bool handleTestCaseEnded(WebKitUserMessage *message); + void sendTestCaseResponse(bool success, const char* msg); bool createSubView(); bool prepareExtensionsDir(); + void setParentVisibility(bool visible); static void initWebExtensionsCallback(WebKitWebContext *context, void *userData); static bool userMessageReceivedCallback(WebKitWebView *webView, WebKitUserMessage *message, void *userData); + static void webProcessTerminatedCallback(WebKitWebView* webView, + WebKitWebProcessTerminationReason reason, + void *userData); + static void loadFailedCallback(WebKitWebView* webView, WebKitLoadEvent loadEvent, + const gchar* failingURI, GError* error, void *userData); + private: std::string m_extensionDir; WebKitWebView* m_parentView = nullptr; @@ -69,6 +79,9 @@ bool TestRunnerImpl::EnsureInitialized(WebKitWebView* parent_view, const char* e m_extensionDir = extension_dir; m_parentView = parent_view; + + Testing::FocusManager::Instance()->Start(); + return true; } @@ -105,6 +118,9 @@ bool TestRunnerImpl::handleRunTestCase(WebKitUserMessage *message) { auto msgName = std::string(Testing::Tags::TestRunnerPrefix) + Testing::Tags::TestRunnerCreateViewReply; WebKitUserMessage* reply = webkit_user_message_new(msgName.c_str(), g_variant_new("b", true)); webkit_user_message_send_reply(message, reply); + + // hide parent page so we are able to see a video layer + setParentVisibility(false); return true; } @@ -128,15 +144,26 @@ bool TestRunnerImpl::createSubView() { g_object_unref(webkitContext); g_signal_connect(m_testCaseView, "user-message-received", G_CALLBACK(userMessageReceivedCallback), this); + g_signal_connect(m_testCaseView, "web-process-terminated", G_CALLBACK(webProcessTerminatedCallback), this); + g_signal_connect(m_testCaseView, "load-failed", G_CALLBACK(loadFailedCallback), this); // TODO: need to handle those signals - // g_signal_connect(m_testCaseView, "web-process-terminated", G_CALLBACK(webProcessTerminatedCallback), this); // g_signal_connect(m_testCaseView, "notify::is-web-process-responsive", G_CALLBACK(isWebProcessResponsiveCallback), this); - // g_signal_connect(m_testCaseView, "load-failed", reinterpret_cast(loadFailedCallback), this); return true; } +void TestRunnerImpl::setParentVisibility(bool visible) { + auto* backend = webkit_web_view_backend_get_wpe_backend(webkit_web_view_get_backend(m_parentView)); + assert(backend); + if (visible) { + wpe_view_backend_add_activity_state(backend, wpe_view_activity_state_in_window); + } else { + wpe_view_backend_remove_activity_state(backend, wpe_view_activity_state_in_window); + } +} + bool TestRunnerImpl::handleDestroyTestCase(WebKitUserMessage *message) { g_clear_object(&m_testCaseView); + setParentVisibility(true); return true; } @@ -168,12 +195,20 @@ bool TestRunnerImpl::handleTestCaseEnded(WebKitUserMessage *message) { const char* msg = nullptr; g_variant_get(payload, "(b&s)", &success, &msg); + sendTestCaseResponse(success, msg); + + return true; +} + +void TestRunnerImpl::sendTestCaseResponse(bool success, const char* msg) { + if (!msg) { + msg = "(no message)"; + } auto msgName = std::string(Testing::Tags::TestRunnerPrefix) + Testing::Tags::TestRunnerCaseEnded; webkit_web_view_send_message_to_page( m_parentView, webkit_user_message_new(msgName.c_str(), g_variant_new("(bs)", success, msg)), nullptr, nullptr, nullptr); - return true; } void TestRunnerImpl::initWebExtensionsCallback(WebKitWebContext *context, @@ -193,6 +228,39 @@ bool TestRunnerImpl::userMessageReceivedCallback(WebKitWebView *webView, return runner->handleUserMessageFromTestCase(webView, message); } +void TestRunnerImpl::webProcessTerminatedCallback(WebKitWebView* webView, + WebKitWebProcessTerminationReason reason, + void *userData) +{ + static auto reasons = []() { + std::map reasons; +#define EMPLACE_ENTRY(entry) reasons.emplace(entry, #entry) + EMPLACE_ENTRY(WEBKIT_WEB_PROCESS_CRASHED); + EMPLACE_ENTRY(WEBKIT_WEB_PROCESS_EXCEEDED_MEMORY_LIMIT); + EMPLACE_ENTRY(WEBKIT_WEB_PROCESS_TERMINATED_BY_API); +#undef EMPLACE_ENTRY + return reasons; + }(); + + g_warning("TestRunner: webProcessTerminatedCallback %d : %s", reason, reasons[reason]); + std::string message = "TERMINATED: "; + message += std::to_string(reason) + "[" + reasons[reason] + "]"; + + TestRunnerImpl* runner = (TestRunnerImpl*)userData; + runner->sendTestCaseResponse(false, message.c_str()); +} + +void TestRunnerImpl::loadFailedCallback(WebKitWebView* webView, WebKitLoadEvent loadEvent, + const gchar* failingURI, GError* error, void *userData) +{ + g_warning("TestRunner: Failed to load URL %s [%s]", failingURI, error->message); + std::string message = "LOAD_FAILED: "; + message += error->message; + + TestRunnerImpl* runner = (TestRunnerImpl*)userData; + runner->sendTestCaseResponse(false, message.c_str()); +} + } // namespace namespace Testing { From c92aae75af4fe54158027dc285c58cfbfca07e3d Mon Sep 17 00:00:00 2001 From: Andrzej Surdej Date: Fri, 17 Nov 2023 12:48:20 +0000 Subject: [PATCH 15/16] [WebKitBrowser/Testing] Westeros focus manager Use Westeros simple shell to manage input focus Inputs will be passed to the most recent surface and restored to previous one on surface destruction. This is needed for test runner to make sure inputs are passed to newly created view (test case) instead of test runner main page. Hidden under compile time option, disabled by default Signed-off-by: Andrzej Surdej --- WebKitBrowser/Testing/CMakeLists.txt | 14 +- WebKitBrowser/Testing/mockfocusmanager.cpp | 4 +- .../Testing/westerosfocusmanager.cpp | 229 ++++++++++++++++++ 3 files changed, 244 insertions(+), 3 deletions(-) create mode 100644 WebKitBrowser/Testing/westerosfocusmanager.cpp diff --git a/WebKitBrowser/Testing/CMakeLists.txt b/WebKitBrowser/Testing/CMakeLists.txt index b18bd6cb6d..18c3c03874 100644 --- a/WebKitBrowser/Testing/CMakeLists.txt +++ b/WebKitBrowser/Testing/CMakeLists.txt @@ -15,8 +15,20 @@ # See the License for the specific language governing permissions and # limitations under the License. +option(PLUGIN_WEBKITBROWSER_TESTING_USE_WESTEROS "Enable westeros simpleshell protocol for input focus stacking" OFF) + target_include_directories(${PLUGIN_WEBKITBROWSER_IMPLEMENTATION} PRIVATE Testing) target_sources(${PLUGIN_WEBKITBROWSER_IMPLEMENTATION} PRIVATE Testing/testrunner.cpp - Testing/mockfocusmanager.cpp ) + +if (PLUGIN_WEBKITBROWSER_TESTING_USE_WESTEROS) + target_sources(${PLUGIN_WEBKITBROWSER_IMPLEMENTATION} PRIVATE Testing/westerosfocusmanager.cpp) + target_link_libraries(${PLUGIN_WEBKITBROWSER_IMPLEMENTATION} PRIVATE + wayland-client + westeros_simpleshell_client + ) +else() + target_sources(${PLUGIN_WEBKITBROWSER_IMPLEMENTATION} PRIVATE Testing/mockfocusmanager.cpp) +endif() + diff --git a/WebKitBrowser/Testing/mockfocusmanager.cpp b/WebKitBrowser/Testing/mockfocusmanager.cpp index ad0bf4a224..d85616a79e 100644 --- a/WebKitBrowser/Testing/mockfocusmanager.cpp +++ b/WebKitBrowser/Testing/mockfocusmanager.cpp @@ -26,8 +26,8 @@ class MockFocusManager : public Testing::FocusManager public: MockFocusManager() = default; - bool Start() override {}; - bool Stop() override {}; + bool Start() override { return true; }; + bool Stop() override { return true; }; }; } // namespace diff --git a/WebKitBrowser/Testing/westerosfocusmanager.cpp b/WebKitBrowser/Testing/westerosfocusmanager.cpp new file mode 100644 index 0000000000..9c86cb0bce --- /dev/null +++ b/WebKitBrowser/Testing/westerosfocusmanager.cpp @@ -0,0 +1,229 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2020 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "focusmanager.h" + +#include +#include +#include +#include +#include + +namespace { + +class WesterosFocusManager : public Testing::FocusManager +{ +public: + WesterosFocusManager() = default; + ~WesterosFocusManager() override; + + bool Start() override; + bool Stop() override; + + void onRegistryGlobal(wl_registry* registry, uint32_t name, const char* interface, uint32_t version); + void onSurfaceCreated(uint32_t surfaceId, const char *name); + void onSurfaceDestroyed(uint32_t surfaceId, const char *name); + void onSurfaceStatus(uint32_t surfaceId, const char *name); + +private: + + static gboolean onWaylandEvent(GIOChannel* channel, GIOCondition condition, gpointer data); + void focusSurface(uint32_t surfaceId); + + struct { + wl_display* display = nullptr; + wl_registry* registry = nullptr; + wl_simple_shell* simpleshell = nullptr; + } m_wl; + GSource *m_waylandSource; + std::deque m_surfaces; +}; + +static void simpleshellSurfaceId(void *data, struct wl_simple_shell *wl_simple_shell, struct wl_surface *surface, uint32_t surfaceId) {} +static void simpleshellSurfaceCreated(void *data, struct wl_simple_shell *wl_simple_shell, uint32_t surfaceId, const char *name) { + WesterosFocusManager* focusManager = static_cast(data); + focusManager->onSurfaceCreated(surfaceId, name); +} +static void simpleshellSurfaceDestroyed(void *data, struct wl_simple_shell *wl_simple_shell, uint32_t surfaceId, const char *name) { + WesterosFocusManager* focusManager = static_cast(data); + focusManager->onSurfaceDestroyed(surfaceId, name); +} +static void simpleshellSurfaceStatus(void *data, struct wl_simple_shell *wl_simple_shell, uint32_t surfaceId, const char *name, uint32_t visible, + int32_t x, int32_t y, int32_t width, int32_t height, wl_fixed_t opacity, wl_fixed_t zorder) { + WesterosFocusManager* focusManager = static_cast(data); + focusManager->onSurfaceStatus(surfaceId, name); +} +static void simpleshellGetSurfacesDone(void* data, wl_simple_shell* simpleshell) {} +static wl_simple_shell_listener s_simpleshell_listener = { + .surface_id = simpleshellSurfaceId, + .surface_created = simpleshellSurfaceCreated, + .surface_destroyed = simpleshellSurfaceDestroyed, + .surface_status = simpleshellSurfaceStatus, + .get_surfaces_done = simpleshellGetSurfacesDone +}; + +static void registryGlobal(void* data, wl_registry* registry, uint32_t name, const char* interface, uint32_t version) { + WesterosFocusManager* focusManager = static_cast(data); + focusManager->onRegistryGlobal(registry, name, interface, version); +} +static void registryGlobalRemove(void* data, wl_registry* registry, uint32_t name) {} +static wl_registry_listener s_registry_listener = { + .global = registryGlobal, + .global_remove = registryGlobalRemove +}; + +WesterosFocusManager::~WesterosFocusManager() { + Stop(); +} + +bool WesterosFocusManager::Start() { + if (m_wl.display) { + g_warning("Already started"); + return false; + } + + m_wl.display = wl_display_connect(nullptr); + if (!m_wl.display) { + g_warning("Failed to connect wayland display"); + return false; + } + + m_wl.registry = wl_display_get_registry(m_wl.display); + wl_registry_add_listener(m_wl.registry, &s_registry_listener, this); + + wl_display_roundtrip(m_wl.display); + if (!m_wl.simpleshell) { + g_warning("Failed to get simpleshell interface"); + return false; + } + + int fd = wl_display_get_fd(m_wl.display); + if (fd < 0) { + g_warning("Failed to get wayland display fd"); + return false; + } + + GIOChannel *waylandChannel = g_io_channel_unix_new(fd); + m_waylandSource = g_io_create_watch(waylandChannel, G_IO_IN); + g_source_set_callback(m_waylandSource, + (GSourceFunc) &WesterosFocusManager::onWaylandEvent, + this, nullptr); + g_source_attach(m_waylandSource, g_main_context_get_thread_default()); + g_io_channel_unref(waylandChannel); + return true; +} + +bool WesterosFocusManager::Stop() { + if (m_waylandSource) { + g_source_destroy(m_waylandSource); + m_waylandSource = nullptr; + } + + if (m_wl.simpleshell) { + wl_simple_shell_destroy(m_wl.simpleshell); + m_wl.simpleshell = nullptr; + } + if (m_wl.registry) { + wl_registry_destroy(m_wl.registry); + m_wl.registry = nullptr; + } + if (m_wl.display) { + wl_display_disconnect(m_wl.display); + m_wl.display = nullptr; + } + m_surfaces.clear(); + + return true; +} + +void WesterosFocusManager::onRegistryGlobal(wl_registry* registry, uint32_t name, const char* interface, uint32_t version) { + if (strcmp(interface, wl_simple_shell_interface.name) == 0) { + m_wl.simpleshell = (wl_simple_shell*)wl_registry_bind(registry, name, &wl_simple_shell_interface, version); + wl_simple_shell_add_listener(m_wl.simpleshell, &s_simpleshell_listener, this); + wl_simple_shell_get_surfaces(m_wl.simpleshell); + wl_display_flush(m_wl.display); + } +} + +void WesterosFocusManager::onSurfaceCreated(uint32_t surfaceId, const char *name) { + if (std::find(m_surfaces.begin(), m_surfaces.end(), surfaceId) != m_surfaces.end()) { + g_warning("Surface already present %u %s", surfaceId, name); + return; + } + m_surfaces.push_back(surfaceId); + // focus new surface + focusSurface(surfaceId); +} + +void WesterosFocusManager::onSurfaceDestroyed(uint32_t surfaceId, const char *name) { + // Expecting the last surface removal + if (m_surfaces.empty()) { + g_warning("Unexpected surface destroyed %u %s", surfaceId, name); + return; + } + if (m_surfaces.back() != surfaceId) { + g_warning("Unexpected surface destroyed %u %s", surfaceId, name); + std::remove(m_surfaces.begin(), m_surfaces.end(), surfaceId); + return; + } + m_surfaces.pop_back(); + if (m_surfaces.empty()) + return; + + // move focus back to previous surface + focusSurface(m_surfaces.back()); +} + +void WesterosFocusManager::onSurfaceStatus(uint32_t surfaceId, const char *name) { + if (std::find(m_surfaces.begin(), m_surfaces.end(), surfaceId) != m_surfaces.end()) { + // we already have know this surface + return; + } + // For surfaces recognized before we started listening. + // It is expected to receive only one surface here for parent page. + m_surfaces.push_back(surfaceId); +} + +void WesterosFocusManager::focusSurface(uint32_t surfaceId) { + g_message("WesterosFocusManager: Focus surface %u", surfaceId); + wl_simple_shell_set_focus(m_wl.simpleshell, surfaceId); + wl_display_flush(m_wl.display); +} + +gboolean WesterosFocusManager::onWaylandEvent( + GIOChannel* channel, GIOCondition condition, gpointer data) +{ + WesterosFocusManager& focusManager = *static_cast(data); + if (focusManager.m_wl.display) { + if (wl_display_dispatch(focusManager.m_wl.display) < 0) { + g_warning("Failed to dispatch wayland events"); + return G_SOURCE_REMOVE; + } + } + return G_SOURCE_CONTINUE; +} + +} // namespace + +namespace Testing { +FocusManager* FocusManager::Instance() { + static WesterosFocusManager focusManager; + return &focusManager; +} +} // namespace Testing From 72e6552b3402b4661d8e30c39069461672b8fa29 Mon Sep 17 00:00:00 2001 From: Eugene Mutavchi Date: Thu, 21 Dec 2023 20:07:28 +0000 Subject: [PATCH 16/16] WebKitBrowser: update change log --- WebKitBrowser/CHANGELOG.md | 4 ++++ WebKitBrowser/WebKitBrowser.cpp | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/WebKitBrowser/CHANGELOG.md b/WebKitBrowser/CHANGELOG.md index 4afdbd7997..afcab35ef5 100644 --- a/WebKitBrowser/CHANGELOG.md +++ b/WebKitBrowser/CHANGELOG.md @@ -16,6 +16,10 @@ All notable changes to this RDK Service will be documented in this file. * For more details, refer to [versioning](https://github.com/rdkcentral/rdkservices#versioning) section under Main README. +## [1.1.20] - 2023-12-21 +### Changed +- Add test runner support + ## [1.1.19] - 2023-11-22 ### Changed - Fix memory pressure settings config generation diff --git a/WebKitBrowser/WebKitBrowser.cpp b/WebKitBrowser/WebKitBrowser.cpp index 4574adc0ca..f12ea5e40f 100644 --- a/WebKitBrowser/WebKitBrowser.cpp +++ b/WebKitBrowser/WebKitBrowser.cpp @@ -21,7 +21,7 @@ #define API_VERSION_NUMBER_MAJOR 1 #define API_VERSION_NUMBER_MINOR 1 -#define API_VERSION_NUMBER_PATCH 19 +#define API_VERSION_NUMBER_PATCH 20 namespace WPEFramework {