diff --git a/mobile/scripts/buildApp.sh b/mobile/scripts/buildApp.sh
index 0a988d81d1..ff3aeb9453 100755
--- a/mobile/scripts/buildApp.sh
+++ b/mobile/scripts/buildApp.sh
@@ -38,6 +38,12 @@ fi
if [[ "${OS}" == "android" ]]; then
[[ -z "${JAVA_HOME}" ]] && { echo "JAVA_HOME is not set"; exit 1; }
+ STATUSQ_DIR=${STATUSQ:-"${STATUS_DESKTOP}/ui/StatusQ"}
+ MOBILEWEBVIEW_JAVA_DIR_FILE="${STATUSQ_DIR}/build/${OS}/StatusQ/mobilewebview_java_dir.txt"
+ if [[ -z "${MOBILEWEBVIEW_ANDROID_JAVA_SRC}" && -f "${MOBILEWEBVIEW_JAVA_DIR_FILE}" ]]; then
+ MOBILEWEBVIEW_ANDROID_JAVA_SRC="$(<"${MOBILEWEBVIEW_JAVA_DIR_FILE}")"
+ fi
+
# Export BUILD_VARIANT for build.gradle to pick up
export BUILD_VARIANT
@@ -62,6 +68,12 @@ if [[ "${OS}" == "android" ]]; then
rsync -a --exclude='libs.xml' "$CWD/../android/qt${QT_MAJOR}/res/" "$BUILD_DIR/android-build/res/" 2>/dev/null || true
rsync -a "$CWD/../android/qt${QT_MAJOR}/src/" "$BUILD_DIR/android-build/src/" 2>/dev/null || true
+ if [[ -n "${MOBILEWEBVIEW_ANDROID_JAVA_SRC}" && \
+ -f "${MOBILEWEBVIEW_ANDROID_JAVA_SRC}/org/mobilewebview/MobileWebView.java" ]]; then
+ echo "Including MobileWebView Java sources from: ${MOBILEWEBVIEW_ANDROID_JAVA_SRC}"
+ rsync -a "${MOBILEWEBVIEW_ANDROID_JAVA_SRC}/org/" "$BUILD_DIR/android-build/src/org/"
+ fi
+
cd "$BUILD_DIR/android-build"
BIN_DIR=${BIN_DIR:-"$CWD/../bin/android/qt6"}
diff --git a/mobile/scripts/buildStatusQ.sh b/mobile/scripts/buildStatusQ.sh
index cf717f4758..5177fd5c53 100755
--- a/mobile/scripts/buildStatusQ.sh
+++ b/mobile/scripts/buildStatusQ.sh
@@ -34,6 +34,12 @@ cmake -S "${STATUSQ}" -B "${BUILD_DIR}" \
make -C "${BUILD_DIR}" SCodes -j "$(nproc)"
make -C "${BUILD_DIR}" StatusQ -j "$(nproc)"
+MOBILEWEBVIEW_JAVA_DIR_FILE="${BUILD_DIR}/mobilewebview_java_dir.txt"
+if [[ -f "${MOBILEWEBVIEW_JAVA_DIR_FILE}" ]]; then
+ export MOBILEWEBVIEW_ANDROID_JAVA_SRC
+ MOBILEWEBVIEW_ANDROID_JAVA_SRC="$(<"${MOBILEWEBVIEW_JAVA_DIR_FILE}")"
+fi
+
mkdir -p "${LIB_DIR}"
STATUSQ_LIB=$(find "${BUILD_DIR}" -name "libStatusQ${LIB_SUFFIX}${LIB_EXT}")
diff --git a/src/nim_status_client.nim b/src/nim_status_client.nim
index 86ce974695..66b71a30b5 100644
--- a/src/nim_status_client.nim
+++ b/src/nim_status_client.nim
@@ -68,6 +68,16 @@ proc determineStatusAppIconPath(): string =
return "/../status-dev.png"
+proc ensureQmlSelector(selector: string) =
+ let current = getEnv("QT_FILE_SELECTORS")
+ if current.len == 0:
+ putEnv("QT_FILE_SELECTORS", selector)
+ return
+
+ let selectors = current.split(",")
+ if selector notin selectors:
+ putEnv("QT_FILE_SELECTORS", current & "," & selector)
+
proc prepareLogging() =
# Outputs logs in the node tab
for output in defaultChroniclesStream.outputs.fields():
@@ -180,6 +190,10 @@ proc mainProc() =
ensureDirectories(DATADIR, TMPDIR, LOGDIR)
+ # Mobile builds use the mobile selector for browser WebView adapter replacement.
+ when main_constants.IS_MOBILE:
+ ensureQmlSelector("mobile")
+
let isExperimental = isExperimental()
let resourcesPath = determineResourcePath()
let openUri = determineOpenUri()
diff --git a/storybook/pages/BrowserLayoutPage.qml b/storybook/pages/BrowserLayoutPage.qml
index 49d5569e8a..a538175be6 100644
--- a/storybook/pages/BrowserLayoutPage.qml
+++ b/storybook/pages/BrowserLayoutPage.qml
@@ -33,6 +33,7 @@ SplitView {
userUID: "0xdeadbeef"
transactionStore: TransactionStoreMock {}
thirdpartyServicesEnabled: true
+ dappsEnabled: true
connectorController: null
platformOS: ctrlPlatformOS.currentValue
diff --git a/storybook/validator_main.cpp b/storybook/validator_main.cpp
index 6ea00a0431..48199d622e 100644
--- a/storybook/validator_main.cpp
+++ b/storybook/validator_main.cpp
@@ -13,10 +13,6 @@ int main(int argc, char *argv[])
QGuiApplication app(argc, argv);
- // FIXME: revert when full integration is done
- // (https://github.com/status-im/status-app/pull/20178)
- qputenv("QT_FILE_SELECTORS", "noWebEngine");
-
QGuiApplication::setOrganizationName(u"Status"_s);
QGuiApplication::setOrganizationDomain(u"status.im"_s);
diff --git a/ui/StatusQ/CMakeLists.txt b/ui/StatusQ/CMakeLists.txt
index c4dd9e8b5a..3c0c2ba8a1 100644
--- a/ui/StatusQ/CMakeLists.txt
+++ b/ui/StatusQ/CMakeLists.txt
@@ -294,7 +294,7 @@ if(IOS OR ANDROID OR CMAKE_SYSTEM_NAME MATCHES "Darwin")
MobileWebView
GIT_REPOSITORY https://github.com/status-im/mobilewebview.git
- GIT_TAG 551b1610deceec99b5b99e9612ab4947faa8c332
+ GIT_TAG feb2db5a5374ef75c1c11162b6892189c199f954
SOURCE_SUBDIR mobilewebview
)
FetchContent_MakeAvailable(MobileWebView)
@@ -302,6 +302,10 @@ if(IOS OR ANDROID OR CMAKE_SYSTEM_NAME MATCHES "Darwin")
if(TARGET MobileWebView)
target_link_libraries(StatusQ PRIVATE MobileWebView)
target_compile_definitions(StatusQ PRIVATE STATUSQ_HAS_MOBILEWEBVIEW=1)
+ if(ANDROID AND DEFINED mobilewebview_SOURCE_DIR)
+ file(WRITE "${CMAKE_BINARY_DIR}/mobilewebview_java_dir.txt"
+ "${mobilewebview_SOURCE_DIR}/mobilewebview/android/src")
+ endif()
install(TARGETS MobileWebView
RUNTIME DESTINATION StatusQ
LIBRARY DESTINATION StatusQ
diff --git a/ui/StatusQ/src/StatusQ/Components/WebEngineLoader.qml b/ui/StatusQ/src/StatusQ/Components/WebEngineLoader.qml
deleted file mode 100644
index 6aba0d5647..0000000000
--- a/ui/StatusQ/src/StatusQ/Components/WebEngineLoader.qml
+++ /dev/null
@@ -1,102 +0,0 @@
-import QtQuick
-
-import QtWebEngine
-import QtWebChannel
-
-import StatusQ
-
-// Helper to load and setup an instance of \c WebEngineView
-//
-// The \c webChannelObjects property is used to register specific objects
-//
-// Loading qrc:/StatusQ/Components/private/qwebchannel/qwebchannel.js and
-// qrc:/StatusQ/Components/private/qwebchannel/helpers.js will provide
-// access to window.statusq APIs used to exchange data between the internal
-// web engine and the QML application
-//
-// It doesn't load the web engine until NetworkChecker detects and active internet
-// connection to avoid the corner case of initializing the web engine without
-// network connectivity. If the web engine is initialized without network connectivity
-// it won't restore the connectivity when it's available on Mac OS
-Item {
- id: root
-
- required property url url
- required property var webChannelObjects
- property string profileName: "Default"
-
- // Used to control the loading of the web engine
- property bool active: false
- // Useful to monitor the loading state of the web engine (depends on active and internet connectivity)
- readonly property bool isActive: loader.active
- property alias instance: loader.item
- property bool waitForInternet: true
-
- signal engineLoaded(WebEngineView instance)
- signal engineUnloaded()
- signal pageLoaded()
- signal pageLoadingError(string errorString)
-
- Component {
- id: webEngineViewComponent
-
- WebEngineView {
- id: webEngineView
-
- anchors.fill: parent
-
- visible: false
-
- url: root.url
- webChannel: statusChannel
- profile.storageName: root.profileName
-
- onLoadingChanged: function(loadRequest) {
- switch(loadRequest.status) {
- case WebEngineView.LoadSucceededStatus:
- root.pageLoaded()
- break
- case WebEngineView.LoadFailedStatus:
- root.pageLoadingError(loadRequest.errorString)
- break
- }
- }
-
- WebChannel {
- id: statusChannel
- registeredObjects: root.webChannelObjects
- }
- }
- }
-
- Loader {
- id: loader
-
- active: root.active && (!root.waitForInternet || (d.passedFirstTimeInitialization || networkChecker.isOnline))
-
- onStatusChanged: function() {
- if (status === Loader.Ready) {
- root.engineLoaded(loader.item)
- d.passedFirstTimeInitialization = true
- } else if (status === Loader.Null) {
- root.engineUnloaded()
- }
- }
-
- sourceComponent: webEngineViewComponent
- }
-
- NetworkChecker {
- id: networkChecker
-
- // Deactivate searching for network connectivity after the web engine is loaded
- active: !d.passedFirstTimeInitialization
- }
-
- QtObject {
- id: d
-
- // Used to hold the loading of the web engine until internet connectivity is available
- property bool passedFirstTimeInitialization: false
- }
-}
\ No newline at end of file
diff --git a/ui/StatusQ/src/StatusQ/Components/qmldir b/ui/StatusQ/src/StatusQ/Components/qmldir
index 99004e6477..f78e9bd25d 100644
--- a/ui/StatusQ/src/StatusQ/Components/qmldir
+++ b/ui/StatusQ/src/StatusQ/Components/qmldir
@@ -68,5 +68,4 @@ StatusToastMessage 0.1 StatusToastMessage.qml
StatusToolBar 0.1 StatusToolBar.qml
StatusUserImage 0.1 StatusUserImage.qml
StatusVideo 0.1 StatusVideo.qml
-WebEngineLoader 0.1 WebEngineLoader.qml
StatusLoadingPageIndicator 0.1 StatusLoadingPageIndicator.qml
diff --git a/ui/StatusQ/src/StatusQ/Core/Utils/Utils.qml b/ui/StatusQ/src/StatusQ/Core/Utils/Utils.qml
index 7f56943a45..bb6c8146e2 100644
--- a/ui/StatusQ/src/StatusQ/Core/Utils/Utils.qml
+++ b/ui/StatusQ/src/StatusQ/Core/Utils/Utils.qml
@@ -332,4 +332,17 @@ QtObject {
return content.length > 0 ? (content + tidSuffix) : tidSuffix
}
+
+ function hasPopups(overlayChildren) {
+ if (!overlayChildren)
+ return false
+
+ return overlayChildren.filter(
+ item => {
+ if (!item)
+ return false
+ const str = item.toString()
+ return str.includes("QQuickPopupItem") && !str.includes("StatusToolTip")
+ }).length > 0
+ }
}
diff --git a/ui/StatusQ/src/statusq.qrc b/ui/StatusQ/src/statusq.qrc
index 065d621984..6105068fb7 100644
--- a/ui/StatusQ/src/statusq.qrc
+++ b/ui/StatusQ/src/statusq.qrc
@@ -67,7 +67,6 @@
StatusQ/Components/StatusToolBar.qml
StatusQ/Components/StatusUserImage.qml
StatusQ/Components/StatusVideo.qml
- StatusQ/Components/WebEngineLoader.qml
StatusQ/Components/private/LoadingDotItem.qml
StatusQ/Components/private/StatusComboboxBackground.qml
StatusQ/Components/private/StatusComboboxIndicator.qml
diff --git a/ui/StatusQ/tests/TestComponents/WebEngineLoader/test.html b/ui/StatusQ/tests/TestComponents/WebEngineLoader/test.html
deleted file mode 100644
index 9a13c3b6b1..0000000000
--- a/ui/StatusQ/tests/TestComponents/WebEngineLoader/test.html
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-
- Test Page
-
-
-
-
- Test Page
-
-
diff --git a/ui/StatusQ/tests/TestComponents/tst_WebEngineLoader.qml b/ui/StatusQ/tests/TestComponents/tst_WebEngineLoader.qml
deleted file mode 100644
index cad04cab4d..0000000000
--- a/ui/StatusQ/tests/TestComponents/tst_WebEngineLoader.qml
+++ /dev/null
@@ -1,102 +0,0 @@
-import QtQuick
-import QtTest
-
-import QtWebEngine
-import QtWebChannel
-
-import StatusQ.Components
-import StatusQ.TestHelpers
-
-TestCase {
- id: root
- name: "TestWebEngineLoader"
-
- QtObject {
- id: testObject
-
- WebChannel.id: "testObject"
-
- signal webChannelInitOk()
- signal webChannelError()
- function signalWebChannelInitResult(error) {
- if(error) {
- webChannelError()
- } else {
- webChannelInitOk()
- }
- }
- }
-
- Loader {
- id: loader
-
- active: false
-
- sourceComponent: WebEngineLoader {
- url: Qt.resolvedUrl("./WebEngineLoader/test.html")
- webChannelObjects: [testObject]
-
- waitForInternet: false
- }
- }
- SignalSpy { id: loadedSpy; target: loader; signalName: "loaded" }
-
- SignalSpy { id: webEngineLoadedSpy; target: loader.item; signalName: "engineLoaded" }
- SignalSpy { id: pageLoadedSpy; target: loader.item; signalName: "pageLoaded" }
- SignalSpy { id: engineUnloadedSpy; target: loader.item; signalName: "engineUnloaded" }
- SignalSpy { id: pageLoadingErrorSpy; target: loader.item; signalName: "onPageLoadingError" }
-
- function init() {
- for (var i = 0; i < root.children.length; i++) {
- const child = root.children[i]
- if(child.hasOwnProperty("signalName")) {
- child.clear()
- }
- }
- loader.active = true
- loadedSpy.wait(1000);
- }
-
- function cleanup() {
- loader.active = false
- }
-
- function test_loadUnload() {
- const webEngine = loader.item
- compare(webEngine.instance, null, "By default the engine is not loaded")
- webEngine.active = true
-
- webEngineLoadedSpy.wait(1000)
- verify(webEngine.instance !== null , "The WebEngineView should be available")
-
- pageLoadedSpy.wait(1000)
- webEngine.active = false
- engineUnloadedSpy.wait(1000);
-
- verify(webEngine.instance === null , "The WebEngineView should be unavailable")
- }
-
- SignalSpy { id: wcInitOkSpy; target: testObject; signalName: "webChannelInitOk" }
- SignalSpy { id: wcInitErrorSpy; target: testObject; signalName: "webChannelError" }
- function test_executeCode() {
- const webEngine = loader.item
- webEngine.active = true
- pageLoadedSpy.wait(1000);
-
- let errorResult = null
- webEngine.instance.runJavaScript(`
- window.testError = window.statusq.error;
- try {
- window.statusq.channel.objects.testObject.signalWebChannelInitResult("");
- } catch (e) {
- window.testError = e.message;
- }
- window.testError
- `, function(result) {
- errorResult = result
- })
-
- wcInitOkSpy.wait(1000);
- compare(errorResult, "", "Expected empty error string if all good")
- }
-}
diff --git a/ui/app/AppLayouts/Browser/BrowserLayout.qml b/ui/app/AppLayouts/Browser/BrowserLayout.qml
index d03758e724..b1de3938b4 100644
--- a/ui/app/AppLayouts/Browser/BrowserLayout.qml
+++ b/ui/app/AppLayouts/Browser/BrowserLayout.qml
@@ -1,7 +1,6 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
-import QtWebEngine
import QtModelsToolkit
@@ -26,6 +25,7 @@ import AppLayouts.Browser.popups
import AppLayouts.Browser.controls
import AppLayouts.Browser.views
import AppLayouts.Browser.panels
+import AppLayouts.Browser.webview
// Code based on https://code.qt.io/cgit/qt/qtwebengine.git/tree/examples/webengine/quicknanobrowser/BrowserWindow.qml?h=5.15
// Licensed under BSD
@@ -35,6 +35,7 @@ StatusSectionLayout {
required property bool isMobile
required property string userUID
required property bool thirdpartyServicesEnabled
+ required property bool dappsEnabled
required property TransactionStore transactionStore
@@ -49,23 +50,20 @@ StatusSectionLayout {
property bool isDebugEnabled: false
property string platformOS: Qt.platform.os
- // FIXME: restore in the next PR
- property bool useWebViewAdapter: false
- readonly property Component webViewAdapterComponent: webEngineAdapterComponent
signal sendToRecipientRequested(string address)
function openUrlInNewTab(url) {
var tab = _internal.addNewTab()
- tab.url = _internal.determineRealURL(url)
+ tab.url = root.browserRootStore.determineRealURL(url)
}
function reloadCurrentTab() {
- _internal.currentWebView?.reload()
+ webViewContext.reloadCurrent()
}
Component.onCompleted: {
- var tab = webStackView.createEmptyTab(connectorBridge.defaultProfileParams, true);
+ var tab = webViewContext.createEmptyTab(connectorBridge.defaultProfileParams, true);
// For Devs: Uncomment the next line if you want to use the simpledapp on first load
// tab.url = root.browserRootStore.determineRealURL("https://simpledapp.eth");
}
@@ -96,91 +94,25 @@ StatusSectionLayout {
QtObject {
id: _internal
- property Item currentWebView: tabs.currentIndex < tabs.count ? webStackView.getCurrentWebView() : null
- readonly property bool currentTabIncognito: webStackView.getCurrentWebView()?.offTheRecord ?? false
+ property Item currentWebView: webViewContext.currentWebView
+ readonly property bool currentTabIncognito: currentWebView?.offTheRecord ?? false
property bool webViewHidden: false
property Component jsDialogComponent: JSDialogWindow {}
- property Component accessDialogComponent: BrowserConnectionModal {
- browserRootStore: root.browserRootStore
- browserWalletStore: root.browserWalletStore
-
- parent: mainView
- x: mainView.width - width - Theme.halfPadding
- y: mainView.y + browserToolbar.height + Theme.halfPadding
- }
-
- property Component sendTransactionModalComponent: SendModal {
- anchors.centerIn: parent
- preSelectedHoldingID: "ETH"
- preSelectedHoldingType: Constants.TokenType.ERC20
- store: root.transactionStore
- }
-
- property Component signMessageModalComponent: SignMessageModal {
- browserRootStore: root.browserRootStore
- signingPhrase: root.browserWalletStore.signingPhrase
- }
-
- property StatusMessageDialog sendingError: StatusMessageDialog {
- title: qsTr("Error sending the transaction")
- icon: StatusMessageDialog.StandardIcon.Critical
- standardButtons: Dialog.Ok
- }
-
- property StatusMessageDialog signingError: StatusMessageDialog {
- title: qsTr("Error signing message")
- icon: StatusMessageDialog.StandardIcon.Critical
- standardButtons: Dialog.Ok
- }
-
function addNewDownloadTab() {
- webStackView.createDownloadTab(tabs.count !== 0 ? currentWebView.profileParams : connectorBridge.defaultProfileParams);
+ webViewContext.createDownloadTab(tabs.count !== 0 ? currentWebView.profileParams : connectorBridge.defaultProfileParams);
tabs.currentIndex = tabs.count - 1;
}
function addNewTab() {
- var tab = webStackView.createEmptyTab(tabs.count !== 0 ? currentWebView.profileParams : connectorBridge.defaultProfileParams);
+ var tab = webViewContext.createEmptyTab(tabs.count !== 0 ? currentWebView.profileParams : connectorBridge.defaultProfileParams);
browserToolbar.activateAddressBar()
return tab;
}
- function onDownloadRequested(download) {
- download.accept();
- root.downloadsStore.addDownload(download)
- root.showFooter = true
-
- // close the tab launched only for starting download
- if (!download.view)
- return
-
- // find tab for this view
- for (var i = 0; i < tabs.count; ++i) {
- var tab = webStackView.getWebView(i)
- // close the “download-only” tab
- if (tab === download.view &&
- !tab.htmlPageLoaded &&
- tab.title === "") {
- webStackView.removeView(i)
- break
- }
- }
- }
-
- function determineRealURL(url) {
- return root.browserRootStore.determineRealURL(url)
- }
-
onCurrentWebViewChanged: () => findBar.reset()
- readonly property var currentViewBookmarkEntry: ModelEntry {
- sourceModel: root.bookmarksStore.bookmarksModel
- key: "url"
- value: (_internal.currentWebView && _internal.currentWebView.url)
- ? _internal.currentWebView.url.toString()
- : ""
- }
}
invertedLayout: root.isMobile
@@ -188,6 +120,34 @@ StatusSectionLayout {
headerPadding: 0
backgroundColor: Theme.palette.statusAppNavBar.backgroundColor
+ BrowserFavoritesContext {
+ id: favoritesContext
+ currentWebView: _internal.currentWebView
+ bookmarksStore: root.bookmarksStore
+ shouldShowFavoritesBar: localAccountSensitiveSettings.shouldShowFavoritesBar
+ openPopupFn: (popup, params) => Global.openPopup(popup, params)
+ addFavoriteModal: addFavoriteModal
+ }
+
+ BrowserDialogsContext {
+ id: dialogsContext
+ networksStore: root.networksStore
+ browserActivityStore: root.browserActivityStore
+ browserWalletStore: root.browserWalletStore
+ openPopupFn: (popup, params) => Global.openPopup(popup, params)
+ jsDialogComponent: _internal.jsDialogComponent
+ dialogParent: root
+ }
+
+ BrowserDownloadsContext {
+ id: downloadsContext
+ downloadsStore: root.downloadsStore
+ tabsModel: tabs
+ getWebViewFn: (index) => webViewContext.getWebView(index)
+ removeViewFn: (index) => webViewContext.removeView(index)
+ setFooterVisibleFn: (visible) => root.showFooter = visible
+ }
+
// TODO: move this to a single browser header qml file
headerContent: ColumnLayout {
spacing: 0
@@ -200,14 +160,14 @@ StatusSectionLayout {
currentTabIncognito: _internal.currentTabIncognito
determineRealURL: function(url) {
- return _internal.determineRealURL(url)
+ return root.browserRootStore.determineRealURL(url)
}
onOpenNewTabTriggered: _internal.addNewTab()
fnGetWebView: (index) => {
- return webStackView.getWebView(index)
+ return webViewContext.getWebView(index)
}
onRemoveView: (index) => {
- webStackView.removeView(index)
+ webViewContext.removeView(index)
}
}
@@ -226,87 +186,61 @@ StatusSectionLayout {
walletAccountsBtnAvailable: !root.isMobile
openTabsCount: tabs.count
- currentTabIncognito: _internal.currentWebView?.profile.offTheRecord ?? false
- currentTabIsBookmark: _internal.currentViewBookmarkEntry.available &&_internal.currentViewBookmarkEntry.item
+ currentTabIncognito: _internal.currentWebView?.offTheRecord ?? false
+ currentTabIsBookmark: favoritesContext.currentTabIsBookmark
currentTabLoading: (!!_internal.currentWebView && _internal.currentWebView.loading)
browserDappsModel: browserDappsProvider.model
- onRequestHistoryPopup: () => historyMenu.open()
- onRequestGoBack: () => _internal.currentWebView.goBack()
- onRequestGoForward: () => _internal.currentWebView.goForward()
- onRequestReloadPage: () => _internal.currentWebView.reload()
- onRequestStopLoadingPage: () => _internal.currentWebView.stop()
+ onRequestHistoryPopup: () => dialogsContext.openHistoryMenu(historyMenu)
+ onRequestGoBack: () => webViewContext.goBackCurrent()
+ onRequestGoForward: () => webViewContext.goForwardCurrent()
+ onRequestReloadPage: () => webViewContext.reloadCurrent()
+ onRequestStopLoadingPage: () => webViewContext.stopCurrent()
onRequestOpenDapp: (url) => {
if (_internal.currentWebView) {
- _internal.currentWebView.url = _internal.determineRealURL(url)
+ webViewContext.setCurrentWebUrl(url)
}
}
onRequestDisconnectDapp: (dappUrl) => {
connectorBridge.disconnect(dappUrl)
}
onAddBookmarkRequested: () => {
- Global.openPopup(addFavoriteModal,
- {
- modifiyModal: !!browserToolbar.currentTabIsBookmark,
- toolbarMode: true,
- ogUrl: _internal.currentViewBookmarkEntry.item && _internal.currentViewBookmarkEntry.available ?
- _internal.currentViewBookmarkEntry.item.url : _internal.currentWebView.url,
- ogName: _internal.currentViewBookmarkEntry.item && _internal.currentViewBookmarkEntry.available ?
- _internal.currentViewBookmarkEntry.item.name : _internal.currentWebView.title
- })
+ favoritesContext.openAddFavoritePopup(!!browserToolbar.currentTabIsBookmark)
}
onRequestLaunchInBrowser: (url) => {
if (localAccountSensitiveSettings.useBrowserEthereumExplorer !== Constants.browserEthereumExplorerNone && url.startsWith("0x")) {
- _internal.currentWebView.url = root.browserRootStore.get0xFormedUrl(localAccountSensitiveSettings.useBrowserEthereumExplorer, url)
+ webViewContext.setCurrentWebUrl(root.browserRootStore.get0xFormedUrl(localAccountSensitiveSettings.useBrowserEthereumExplorer, url))
return
}
if (localAccountSensitiveSettings.selectedBrowserSearchEngineId !== SearchEnginesConfig.browserSearchEngineNone && !Utils.isURL(url) && !Utils.isURLWithOptionalProtocol(url)) {
- _internal.currentWebView.url = root.browserRootStore.getFormedUrl(localAccountSensitiveSettings.selectedBrowserSearchEngineId, url)
+ webViewContext.setCurrentWebUrl(root.browserRootStore.getFormedUrl(localAccountSensitiveSettings.selectedBrowserSearchEngineId, url))
return
} else if (Utils.isURLWithOptionalProtocol(url)) {
url = "https://" + url
}
- _internal.currentWebView.url = _internal.determineRealURL(url);
+ webViewContext.setCurrentWebUrl(url);
}
- onRequestWalletMenu: () => {
- // Initialize activity filters before opening popup
- const activeChainIds = SQUtils.ModelUtils.modelToFlatArray(
- root.networksStore.activeNetworks, "chainId")
- if (activeChainIds.length > 0) {
- root.browserActivityStore.activityController.setFilterChainsJson(
- JSON.stringify(activeChainIds), true)
- }
- const currentAddress = root.browserWalletStore.dappBrowserAccount.address
- root.browserActivityStore.activityController.setFilterAddressesJson(
- JSON.stringify([currentAddress]))
-
- Global.openPopup(browserWalletMenu)
- }
+ onRequestWalletMenu: () => dialogsContext.openWalletMenu(browserWalletMenu)
onRequestAllOpenTabsView: () => {
// TODO: Launch All Tabs View
// https://github.com/status-im/status-app/issues/19569
}
- onOpenSettingMenu: () => {
- settingsMenu.open()
- }
+ onOpenSettingMenu: () => dialogsContext.openSettingsMenu(settingsMenu)
}
Loader {
id: favoritesBarLoader
Layout.fillWidth: true
- Layout.preferredHeight: active ? 38: 0
- active: localAccountSensitiveSettings.shouldShowFavoritesBar &&
- root.bookmarksStore.bookmarksModel.ModelCount.count > 0
+ Layout.preferredHeight: favoritesContext.favoritesBarActive ? 38 : 0
+ active: favoritesContext.favoritesBarActive
sourceComponent: FavoritesBar {
currentTabIncognito: _internal.currentTabIncognito
bookmarkModel: root.bookmarksStore.bookmarksModel
favoritesMenu: favoriteMenu
- onSetAsCurrentWebUrl: (url) => _internal.currentWebView.url = _internal.determineRealURL(url)
+ onSetAsCurrentWebUrl: (url) => webViewContext.setCurrentWebUrl(url)
onOpenInNewTab: (url) => root.openUrlInNewTab(url)
onAddFavModalRequested: {
- Global.openPopup(addFavoriteModal, {toolbarMode: true,
- ogUrl: _internal.currentViewBookmarkEntry.item ? _internal.currentViewBookmarkEntry.item.url : _internal.currentWebView.url,
- ogName: _internal.currentViewBookmarkEntry.item ? _internal.currentViewBookmarkEntry.item.name : _internal.currentWebView.title})
+ favoritesContext.openAddFavoritePopup(false)
}
}
}
@@ -323,13 +257,13 @@ StatusSectionLayout {
onFindNext: {
if (text)
- _internal.currentWebView && _internal.currentWebView.findText(text);
+ webViewContext.findTextCurrent(text)
else if (!visible)
visible = true;
}
onFindPrevious: {
if (text)
- _internal.currentWebView && _internal.currentWebView.findText(text, WebEngineView.FindBackward);
+ webViewContext.findTextCurrent(text, true)
else if (!visible)
visible = true;
}
@@ -340,6 +274,33 @@ StatusSectionLayout {
sourceComponent: downloadBar
}
+ BrowserWebViewContext {
+ id: webViewContext
+ thirdpartyServicesEnabled: root.thirdpartyServicesEnabled
+ isDebugEnabled: root.isDebugEnabled
+ isMobile: root.isMobile
+ browserSettings: localAccountSensitiveSettings
+ webChannel: connectorBridge.channel
+ hostStackLayout: webStackView
+ tabsModel: tabs
+ defaultProfileParams: connectorBridge.defaultProfileParams
+ bookmarksStore: root.bookmarksStore
+ downloadsStore: root.downloadsStore
+ determineRealURLFn: (url) => root.browserRootStore.determineRealURL(url)
+ downloadRequestHandler: (download) => downloadsContext.handleDownloadRequest(download)
+ sslErrorHandler: (error) => {
+ error.defer()
+ sslDialog.enqueue(error)
+ }
+ jsDialogHandler: (request) => dialogsContext.openJsDialog(request)
+ findTextFinishedHandler: (result) => {
+ if (!findBar.visible)
+ findBar.visible = true
+ findBar.numberOfMatches = result.numberOfMatches
+ findBar.activeMatch = result.activeMatch
+ }
+ }
+
centerPanel: ColumnLayout {
id: mainView
spacing: 0
@@ -350,54 +311,6 @@ StatusSectionLayout {
Layout.fillHeight: true
Layout.fillWidth: true
-
- function createEmptyTab(profileParams, createAsStartPage = false, focusOnNewTab = true, url = undefined) {
- focusOnNewTab = focusOnNewTab && !createAsStartPage
-
- var webview = root.webViewAdapterComponent.createObject(webStackView, {
- profileParams: profileParams,
- isDownloadView: false
- })
-
- tabs.createEmptyTab(createAsStartPage, focusOnNewTab, webview)
-
- if (createAsStartPage && root.thirdpartyServicesEnabled) {
- webview.url = Constants.browserDefaultHomepage
- } else if (url !== undefined) {
- webview.url = url;
- } else if (!!localAccountSensitiveSettings.browserHomepage) {
- webview.url = _internal.determineRealURL(localAccountSensitiveSettings.browserHomepage)
- }
-
- return webview;
- }
-
- function createDownloadTab(profileParams) {
- var webview = root.webViewAdapterComponent.createObject(webStackView, {
- profileParams: profileParams,
- isDownloadView: true
- })
- tabs.createDownloadTab()
- return webview;
- }
-
- function getCurrentWebView() { // -> WebEngineView/WebView
- return getWebView(tabs.currentIndex)
- }
-
- function getWebView(index) { // -> WebEngineView/WebView
- return webStackView.children[index]
- }
-
- function removeView(index) {
- if (tabs.count <= 1) {
- createEmptyTab(_internal.currentWebView.profileParams, true)
- }
- tabs.removeTab(index)
- var view = getWebView(index)
- view.stop()
- view.destroy()
- }
}
// Overlay for DownloadView and EmptyWebPage
@@ -405,12 +318,13 @@ StatusSectionLayout {
id: overlayLoader
anchors.fill: parent
z: 53
-
- readonly property bool showDownloadView: _internal.currentWebView?.isDownloadView ?? false
- readonly property bool showEmptyPage: !showDownloadView && (!_internal.currentWebView?.url?.toString())
-
- active: showDownloadView || showEmptyPage
- sourceComponent: showDownloadView ? downloadViewComponent : emptyPageComponent
+
+ readonly property int contentMode: webViewContext.currentContentMode
+
+ active: contentMode !== BrowserWebViewContext.ContentMode.WebContent
+ sourceComponent: contentMode === BrowserWebViewContext.ContentMode.DownloadContent
+ ? downloadViewComponent
+ : emptyPageComponent
}
// Non UI component
@@ -439,52 +353,13 @@ StatusSectionLayout {
}
}
- Component {
- id: webEngineAdapterComponent
- WebEngineAdapter {
- id: webEngineAdapterItem
- anchors.fill: parent
- webChannel: connectorBridge.channel
- enableJsLogs: root.isDebugEnabled
- devToolsEnabled: localAccountSensitiveSettings.devToolsEnabled
-
- onWindowCloseRequested: webStackView.removeView(StackLayout.index)
- onNewWindowRequested: (makeCurrent, requestedUrl, callback) => {
- var tab = webStackView.createEmptyTab(_internal.currentWebView.profileParams, false, makeCurrent, requestedUrl);
- callback(tab);
- }
- onDownloadRequested: (download) => {
- _internal.onDownloadRequested(download)
- }
- onCertificateError: (error) => {
- error.defer()
- sslDialog.enqueue(error)
- }
- onJavaScriptDialogRequested: (request) => {
- request.accepted = true;
- var dialog = _internal.jsDialogComponent.createObject(root, {"request": request})
- dialog.open()
- }
- onFindTextFinished: (result) => {
- if (!findBar.visible)
- findBar.visible = true
- findBar.numberOfMatches = result.numberOfMatches;
- findBar.activeMatch = result.activeMatch;
- }
- }
- }
-
-
Component {
id: downloadViewComponent
DownloadView {
downloadsModel: root.downloadsStore.downloadModel
downloadsMenu: downloadMenuInst
onOpenDownloadClicked: function(downloadComplete, index) {
- if (downloadComplete) {
- return root.downloadsStore.openFile(index)
- }
- root.downloadsStore.openDirectory(index)
+ downloadsContext.openDownloadFromList(downloadComplete, index)
}
}
}
@@ -496,11 +371,9 @@ StatusSectionLayout {
favMenu: favoriteMenu
addFavModal: addFavoriteModal
determineRealURLFn: function(url) {
- return _internal.determineRealURL(url)
+ return root.browserRootStore.determineRealURL(url)
}
- onSetCurrentWebUrl: (url) => {
- _internal.currentWebView.url = url
- }
+ onSetCurrentWebUrl: (url) => webViewContext.setCurrentWebUrl(url)
Component.onCompleted: {
// Add fav button at the end of the grid
var index = root.bookmarksStore.getBookmarkIndexByUrl(Constants.newBookmark)
@@ -513,6 +386,8 @@ StatusSectionLayout {
Component {
id: browserWalletMenu
BrowserWalletMenu {
+ id: walletMenu
+
parent: browserToolbar
x: browserToolbar.width - width - Theme.halfPadding
y: browserToolbar.height + 4
@@ -528,7 +403,7 @@ StatusSectionLayout {
onAccountChanged: (newAddress) => connectorBridge.connectorManager.changeAccount(newAddress)
onReload: {
for (let i = 0; i < tabs.count; ++i){
- webStackView.getWebView(i).reload();
+ webViewContext.getWebView(i).reload();
}
}
@@ -550,6 +425,7 @@ StatusSectionLayout {
root.browserActivityStore.currentActivityFiltersStore.updateRecipientsModel()
}
}
+
}
}
@@ -564,20 +440,10 @@ StatusSectionLayout {
zoomFactor: _internal.currentWebView ? _internal.currentWebView.zoomFactor : 1
onAddNewTab: _internal.addNewTab()
onAddNewDownloadTab: _internal.addNewDownloadTab()
- onGoIncognito: function (checked) {
- if (_internal.currentWebView) {
- _internal.currentWebView.offTheRecord = checked;
- }
- }
- onZoomIn: {
- const newZoom = _internal.currentWebView.zoomFactor + 0.1
- _internal.currentWebView.changeZoomFactor(newZoom)
- }
- onZoomOut: {
- const newZoom = _internal.currentWebView.zoomFactor - 0.1
- _internal.currentWebView.changeZoomFactor(newZoom)
- }
- onResetZoomFactor: _internal.currentWebView.changeZoomFactor(1.0)
+ onGoIncognito: (checked) => webViewContext.setIncognitoCurrent(checked)
+ onZoomIn: webViewContext.changeZoomCurrent(0.1)
+ onZoomOut: webViewContext.changeZoomCurrent(-0.1)
+ onResetZoomFactor: webViewContext.resetZoomCurrent()
onLaunchFindBar: {
if (!findBar.visible) {
findBar.visible = true;
@@ -586,18 +452,19 @@ StatusSectionLayout {
}
onToggleCompatibilityMode: function(checked) {
for (let i = 0; i < tabs.count; ++i){
- webStackView.getWebView(i).stop() // Stop all loading tabs
+ webViewContext.getWebView(i).stop() // Stop all loading tabs
}
localAccountSensitiveSettings.compatibilityMode = checked;
for (let i = 0; i < tabs.count; ++i){
- webStackView.getWebView(i).reload() // Reload them with new user agent
+ webViewContext.getWebView(i).reload() // Reload them with new user agent
}
}
onLaunchBrowserSettings: {
Global.changeAppSectionBySectionType(Constants.appSection.profile, Constants.settingsSubsection.browserSettings);
}
+
}
Component {
@@ -649,11 +516,9 @@ StatusSectionLayout {
bookmarksStore: root.bookmarksStore
onOpenInNewTab: (url) => root.openUrlInNewTab(url)
onEditFavoriteTriggered: {
- Global.openPopup(addFavoriteModal, {
- modifiyModal: true,
- ogUrl: favoriteMenu.currentFavorite ? favoriteMenu.currentFavorite.url : _internal.currentWebView.url,
- ogName: favoriteMenu.currentFavorite ? favoriteMenu.currentFavorite.name : _internal.currentWebView.title})
+ favoritesContext.openAddFavoritePopup(true, favoriteMenu.currentFavorite)
}
+
}
StatusMenu {
@@ -680,6 +545,7 @@ StatusSectionLayout {
historyMenu.removeItem(object)
}
}
+
}
Component {
@@ -687,23 +553,10 @@ StatusSectionLayout {
FavoritesBar {
bookmarkModel: root.bookmarksStore.bookmarksModel
favoritesMenu: favoriteMenu
- onSetAsCurrentWebUrl: (url) => {
- if (!_internal.currentWebView) {
- console.error("[Browser] currentWebView is null, cannot set URL")
- return
- }
- const newUrl = _internal.determineRealURL(url)
- Qt.callLater(function() {
- if (_internal.currentWebView) {
- _internal.currentWebView.url = newUrl
- }
- })
- }
+ onSetAsCurrentWebUrl: (url) => webViewContext.setCurrentWebUrl(url)
onOpenInNewTab: (url) => root.openUrlInNewTab(url)
onAddFavModalRequested: {
- Global.openPopup(addFavoriteModal, {toolbarMode: true,
- ogUrl: browserHeader.currentFavorite ? browserHeader.currentFavorite.url : _internal.currentWebView.url,
- ogName: browserHeader.currentFavorite ? browserHeader.currentFavorite.name : _internal.currentWebView.title})
+ favoritesContext.openAddFavoritePopup(false)
}
}
}
@@ -712,7 +565,8 @@ StatusSectionLayout {
id: connectorBridge
userUID: root.userUID
- connectorController: root.connectorController
+ featureEnabled: root.dappsEnabled
+ connectorController: root.dappsEnabled ? root.connectorController : null
httpUserAgent: {
if (localAccountSensitiveSettings.compatibilityMode) {
// Google doesn't let you connect if the user agent is Chrome-ish and doesn't satisfy some sort of hidden requirement
@@ -739,7 +593,7 @@ StatusSectionLayout {
BCBrowserDappsProvider {
id: browserDappsProvider
- connectorController: root.connectorController
+ connectorController: root.dappsEnabled ? root.connectorController : null
clientId: connectorBridge.clientId
clientIdFilter: connectorBridge.clientId
}
@@ -750,39 +604,13 @@ StatusSectionLayout {
downloadsModel: root.downloadsStore.downloadModel
downloadsMenu: downloadMenuInst
onOpenDownloadClicked: function (downloadComplete, index) {
- if (downloadComplete) {
- return root.downloadsStore.openFile(index)
- }
- root.downloadsStore.openDirectory(index)
+ downloadsContext.openDownloadFromList(downloadComplete, index)
}
onAddNewDownloadTab: _internal.addNewDownloadTab()
onClose: root.showFooter = false
}
}
- Connections {
- target: _internal.currentWebView
- function onUrlChanged() {
- browserHeader.addressBar.text = root.browserRootStore.obtainAddress(_internal.currentWebView.url)
-
- // Update ConnectorBridge with current dApp metadata
- if (_internal.currentWebView && _internal.currentWebView.url) {
- connectorBridge.connectorManager.updateDAppUrl(
- _internal.currentWebView.url,
- _internal.currentWebView.title,
- _internal.currentWebView.icon
- )
- }
- }
- }
-
- Connections {
- target: root.bookmarksStore.bookmarksModel
- function onModelChanged() {
- browserHeader.currentFavorite = Qt.binding(function () {return root.bookmarksStore.getCurrentFavorite(_internal.currentWebView.url)})
- }
- }
-
Connections {
target: typeof browserSection !== "undefined" ? browserSection : null
function onOpenUrl(url: string) {
diff --git a/ui/app/AppLayouts/Browser/adapters/+mobile/ProfileManager.qml b/ui/app/AppLayouts/Browser/adapters/+mobile/ProfileManager.qml
new file mode 100644
index 0000000000..b06b9e2276
--- /dev/null
+++ b/ui/app/AppLayouts/Browser/adapters/+mobile/ProfileManager.qml
@@ -0,0 +1,8 @@
+pragma Singleton
+import QtQuick
+
+QtObject {
+ function getProfile(profileParams) {
+ return null
+ }
+}
diff --git a/ui/app/AppLayouts/Browser/adapters/MobileWebViewAdapter.qml b/ui/app/AppLayouts/Browser/adapters/+mobile/WebViewAdapter.qml
similarity index 83%
rename from ui/app/AppLayouts/Browser/adapters/MobileWebViewAdapter.qml
rename to ui/app/AppLayouts/Browser/adapters/+mobile/WebViewAdapter.qml
index 6c6cb2ba25..304c87b1f7 100644
--- a/ui/app/AppLayouts/Browser/adapters/MobileWebViewAdapter.qml
+++ b/ui/app/AppLayouts/Browser/adapters/+mobile/WebViewAdapter.qml
@@ -9,6 +9,7 @@ AbstractWebView {
required property BrowserStores.BookmarksStore bookmarksStore
required property BrowserStores.DownloadsStore downloadsStore
+ required property var localAccountSensitiveSettings
property var findBarComp
property var favMenu
@@ -52,15 +53,15 @@ AbstractWebView {
}
function goBack() {
- console.warn("MobileWebViewAdapter: goBack not supported yet")
+ console.warn("WebViewAdapter: goBack not supported yet")
}
function goForward() {
- console.warn("MobileWebViewAdapter: goForward not supported yet")
+ console.warn("WebViewAdapter: goForward not supported yet")
}
function goBackOrForward(offset) {
- console.warn("MobileWebViewAdapter: goBackOrForward not supported yet")
+ console.warn("WebViewAdapter: goBackOrForward not supported yet")
}
function reload() {
@@ -70,7 +71,7 @@ AbstractWebView {
}
function stop() {
- console.warn("MobileWebViewAdapter: stop not supported yet")
+ console.warn("WebViewAdapter: stop not supported yet")
}
function findText(text, flags) {
@@ -82,7 +83,10 @@ AbstractWebView {
}
function acceptAsNewWindow(request) {
- console.warn("MobileWebViewAdapter: acceptAsNewWindow not supported")
+ console.warn("WebViewAdapter: acceptAsNewWindow not supported")
+ }
+
+ function detachView() {
}
function triggerWebAction(action) {
@@ -100,7 +104,7 @@ AbstractWebView {
reload()
break
default:
- console.warn("MobileWebViewAdapter: Web action not supported:", action)
+ console.warn("WebViewAdapter: Web action not supported:", action)
}
}
}
diff --git a/ui/app/AppLayouts/Browser/adapters/AbstractWebView.qml b/ui/app/AppLayouts/Browser/adapters/AbstractWebView.qml
index 05bc67b552..b7887d3586 100644
--- a/ui/app/AppLayouts/Browser/adapters/AbstractWebView.qml
+++ b/ui/app/AppLayouts/Browser/adapters/AbstractWebView.qml
@@ -52,6 +52,25 @@ Item {
RequestClose = 35
}
+ // === Download States (constants for cross-platform compatibility) ===
+ // These map to WebEngineDownloadRequest.DownloadState enum on desktop
+ enum DownloadState {
+ DownloadRequested = 0,
+ DownloadInProgress = 1,
+ DownloadCompleted = 2,
+ DownloadCancelled = 3,
+ DownloadInterrupted = 4
+ }
+
+ // === JavaScript Dialog Types (constants for cross-platform compatibility) ===
+ // These map to JavaScriptDialogRequest.DialogType enum on desktop
+ enum JavaScriptDialogType {
+ DialogTypeAlert = 0,
+ DialogTypeConfirm = 1,
+ DialogTypePrompt = 2,
+ DialogTypeUnload = 3
+ }
+
signal linkHovered(string hoveredUrl)
signal windowCloseRequested()
signal downloadRequested(var download)
@@ -76,6 +95,7 @@ Item {
function findText(text, flags) {}
function changeZoomFactor(factor) {}
function acceptAsNewWindow(request) {}
+ function detachView() {}
function triggerWebAction(action) { console.warn("AbstractWebView: triggerWebAction not implemented") }
}
diff --git a/ui/app/AppLayouts/Browser/provider/qml/ProfileManager.qml b/ui/app/AppLayouts/Browser/adapters/ProfileManager.qml
similarity index 90%
rename from ui/app/AppLayouts/Browser/provider/qml/ProfileManager.qml
rename to ui/app/AppLayouts/Browser/adapters/ProfileManager.qml
index c21bb895bc..c03d26dfe2 100644
--- a/ui/app/AppLayouts/Browser/provider/qml/ProfileManager.qml
+++ b/ui/app/AppLayouts/Browser/adapters/ProfileManager.qml
@@ -20,11 +20,14 @@ QtObject {
sourceUrl: path,
injectionPoint: WebEngineScript.DocumentCreation,
worldId: WebEngineScript.MainWorld,
- runsOnSubframes: runOnSubFrames
+ runsOnSubFrames: runOnSubFrames
}
}
function _getProfilePrototype(storageName, offTheRecord) {
+ const storageNameProp = offTheRecord
+ ? ""
+ : `storageName: "${storageName.replace(/"/g, '\\"')}"`
const persistentCookiesPolicy = offTheRecord
? "persistentCookiesPolicy: WebEngineProfile.NoPersistentCookies"
: ""
@@ -32,7 +35,7 @@ QtObject {
return Qt.createQmlObject(`
import QtWebEngine
WebEngineProfilePrototype {
- storageName: "${storageName.replace(/"/g, '\\"')}"
+ ${storageNameProp}
${persistentCookiesPolicy}
}
`, root, "ProfilePrototype_" + storageName)
diff --git a/ui/app/AppLayouts/Browser/adapters/WebEngineAdapter.qml b/ui/app/AppLayouts/Browser/adapters/WebViewAdapter.qml
similarity index 54%
rename from ui/app/AppLayouts/Browser/adapters/WebEngineAdapter.qml
rename to ui/app/AppLayouts/Browser/adapters/WebViewAdapter.qml
index fbb71868d2..a2300cf5a3 100644
--- a/ui/app/AppLayouts/Browser/adapters/WebEngineAdapter.qml
+++ b/ui/app/AppLayouts/Browser/adapters/WebViewAdapter.qml
@@ -4,16 +4,18 @@ import QtWebEngine
import StatusQ.Core.Theme
import AppLayouts.Browser.views
-import AppLayouts.Browser.provider.qml
AbstractWebView {
id: root
property bool enableJsLogs: false
+ required property var localAccountSensitiveSettings
+ property var bookmarksStore
+ property var downloadsStore
property var profile: ProfileManager.getProfile(root.profileParams)
- // Expose BrowserWebEngineView properties
+ // Expose internal WebEngineView properties
property alias url: webView.url
readonly property alias title: webView.title
readonly property alias loading: webView.loading
@@ -42,6 +44,16 @@ AbstractWebView {
function findText(text, flags) { webView.findText(text, flags) }
function changeZoomFactor(factor) { webView.changeZoomFactor(factor) }
function acceptAsNewWindow(request) { request.openIn(webView) }
+ function detachView() {
+ // Detach internal views from scene graph before destroy.
+ webView.webChannel = null
+ devToolsView.inspectedView = null
+ webView.stop()
+ webView.visible = false
+ webView.parent = null
+ devToolsView.visible = false
+ devToolsView.parent = null
+ }
function triggerWebAction(action) {
// Map AbstractWebView.WebAction to WebEngineView.WebAction
switch (action) {
@@ -72,26 +84,95 @@ AbstractWebView {
case AbstractWebView.WebAction.PasteAndMatchStyle:
webView.triggerWebAction(WebEngineView.PasteAndMatchStyle); break
default:
- console.warn("WebEngineAdapter: Unknown web action:", action)
+ console.warn("WebViewAdapter: Unknown web action:", action)
}
}
- BrowserWebEngineView {
+ WebEngineView {
id: webView
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.bottom: root.devToolsEnabled ? devToolsView.top : parent.bottom
+ focus: true
+
+ property bool htmlPageLoaded: false
+ backgroundColor: Theme.palette.background
+
+ settings.autoLoadImages: root.localAccountSensitiveSettings.autoLoadImages
+ settings.javascriptEnabled: root.localAccountSensitiveSettings.javaScriptEnabled
+ settings.errorPageEnabled: root.localAccountSensitiveSettings.errorPageEnabled
+ settings.pluginsEnabled: root.localAccountSensitiveSettings.pluginsEnabled
+ settings.autoLoadIconsForPage: root.localAccountSensitiveSettings.autoLoadIconsForPage
+ settings.touchIconsEnabled: root.localAccountSensitiveSettings.touchIconsEnabled
+ settings.webRTCPublicInterfacesOnly: root.localAccountSensitiveSettings.webRTCPublicInterfacesOnly
+ settings.pdfViewerEnabled: root.localAccountSensitiveSettings.pdfViewerEnabled
+ settings.focusOnNavigationEnabled: true
+ settings.forceDarkMode: Application.styleHints.colorScheme === Qt.ColorScheme.Dark
webChannel: root.webChannel
profile: root.profile
- enableJsLogs: root.enableJsLogs
+ onQuotaRequested: function(request) {
+ if (request.requestedSize <= 5 * 1024 * 1024)
+ request.accept()
+ else
+ request.reject()
+ }
+ onRegisterProtocolHandlerRequested: function(request) {
+ console.log("accepting registerProtocolHandler request for "
+ + request.scheme + " from " + request.origin)
+ request.accept()
+ }
+ onRenderProcessTerminated: function(terminationStatus, exitCode) {
+ var status = ""
+ switch (terminationStatus) {
+ case WebEngineView.NormalTerminationStatus:
+ status = "(normal exit)"
+ break
+ case WebEngineView.AbnormalTerminationStatus:
+ status = "(abnormal exit)"
+ break
+ case WebEngineView.CrashedTerminationStatus:
+ status = "(crashed)"
+ break
+ case WebEngineView.KilledTerminationStatus:
+ status = "(killed)"
+ break
+ }
+ console.warn("Render process exited with code " + exitCode + " " + status)
+ }
+ onSelectClientCertificate: function(selection) {
+ selection.certificates[0].select()
+ }
+ onLoadingChanged: function(loadRequest) {
+ if (loadRequest.status === WebEngineView.LoadStartedStatus) {
+ webView.htmlPageLoaded = false
+ }
+ if (loadRequest.status === WebEngineView.LoadSucceededStatus) {
+ webView.htmlPageLoaded = true
+ }
+ }
+ onLoadProgressChanged: function(progress) {
+ if (progress >= 10)
+ webView.htmlPageLoaded = true
+ }
+ onNavigationRequested: function(request) {
+ if (request.url.toString().startsWith("file:/")) {
+ console.log("Local file browsing is disabled")
+ request.reject()
+ }
+ }
+ onJavaScriptConsoleMessage: function(level, message, lineNumber, sourceID) {
+ const isOurScript = ScriptUtils.isOurInjectedScript(sourceID, root.profile)
+ if (isOurScript || root.enableJsLogs)
+ console.log("[WebEngine]", sourceID + ":" + lineNumber, message)
+ }
onLinkHovered: (hoveredUrl) => root.linkHovered(hoveredUrl)
onWindowCloseRequested: root.windowCloseRequested()
onNewWindowRequested: (request) => {
if (!request.userInitiated) {
- console.warn("Warning: Blocked a popup window.");
+ console.warn("Warning: Blocked a popup window.")
} else {
const makeCurrent = request.destination !== WebEngineNewWindowRequest.InNewBackgroundTab
root.newWindowRequested(makeCurrent, request.requestedUrl, (tab) => tab.acceptAsNewWindow(request))
@@ -100,7 +181,19 @@ AbstractWebView {
onCertificateError: (error) => root.certificateError(error)
onJavaScriptDialogRequested: (request) => root.javaScriptDialogRequested(request)
onFindTextFinished: (result) => root.findTextFinished(result)
- onShowFindBar: (numberOfMatches, activeMatch) => root.findTextFinished({numberOfMatches, activeMatch})
+ }
+
+ Connections {
+ target: root.profile
+ function onDownloadRequested(download) {
+ // Profile emits for all tabs sharing it; forward only owner view.
+ if (download?.view && download.view !== webView)
+ return
+ // For viewless downloads, only visible adapter forwards to avoid fan-out.
+ if (!download?.view && !root.visible)
+ return
+ root.downloadRequested(download)
+ }
}
WebEngineView {
@@ -118,18 +211,20 @@ AbstractWebView {
}
}
- Connections {
- target: root.profile
- function onDownloadRequested(download) {
- root.downloadRequested(download)
- }
- }
-
Connections {
// This connection is needed because changing profileParams.offTheRecord doesn't trigger the root.profile update
target: root.profileParams
function onOffTheRecordChanged() {
root.profile = ProfileManager.getProfile(root.profileParams)
}
+ function onUserAgentChanged() {
+ root.profile = ProfileManager.getProfile(root.profileParams)
+ }
+ function onScriptsChanged() {
+ root.profile = ProfileManager.getProfile(root.profileParams)
+ }
+ function onUserIdChanged() {
+ root.profile = ProfileManager.getProfile(root.profileParams)
+ }
}
}
diff --git a/ui/app/AppLayouts/Browser/adapters/qmldir b/ui/app/AppLayouts/Browser/adapters/qmldir
index 5b9beeb5ff..ad52f5e32a 100644
--- a/ui/app/AppLayouts/Browser/adapters/qmldir
+++ b/ui/app/AppLayouts/Browser/adapters/qmldir
@@ -1,4 +1,4 @@
+singleton ProfileManager 1.0 ProfileManager.qml
AbstractWebView 1.0 AbstractWebView.qml
ProfileParams 1.0 ProfileParams.qml
-WebEngineAdapter 1.0 WebEngineAdapter.qml
-MobileWebViewAdapter 1.0 MobileWebViewAdapter.qml
+WebViewAdapter 1.0 WebViewAdapter.qml
diff --git a/ui/app/AppLayouts/Browser/panels/DownloadBar.qml b/ui/app/AppLayouts/Browser/panels/DownloadBar.qml
index 4399db2da5..df391e428d 100644
--- a/ui/app/AppLayouts/Browser/panels/DownloadBar.qml
+++ b/ui/app/AppLayouts/Browser/panels/DownloadBar.qml
@@ -1,6 +1,5 @@
import QtQuick
import QtQuick.Layouts
-import QtWebEngine
import StatusQ.Core
import StatusQ.Core.Theme
@@ -8,6 +7,7 @@ import StatusQ.Controls
import utils
+import AppLayouts.Browser.adapters
import AppLayouts.Browser.controls
Rectangle {
@@ -44,8 +44,8 @@ Rectangle {
readonly property var downloadItem: downloadsModel.downloads[index]
isPaused: downloadItem?.isPaused ?? false
- isCanceled: downloadItem?.state === WebEngineDownloadRequest.DownloadCancelled ?? false
- downloadComplete: downloadItem?.state === WebEngineDownloadRequest.DownloadCompleted ?? false
+ isCanceled: downloadItem?.state === AbstractWebView.DownloadState.DownloadCancelled ?? false
+ downloadComplete: downloadItem?.state === AbstractWebView.DownloadState.DownloadCompleted ?? false
primaryText: downloadItem?.downloadFileName ?? ""
downloadText: {
if (isCanceled) {
diff --git a/ui/app/AppLayouts/Browser/panels/DownloadView.qml b/ui/app/AppLayouts/Browser/panels/DownloadView.qml
index c36365ef67..b693c9e440 100644
--- a/ui/app/AppLayouts/Browser/panels/DownloadView.qml
+++ b/ui/app/AppLayouts/Browser/panels/DownloadView.qml
@@ -1,11 +1,11 @@
import QtQuick
-import QtWebEngine
import StatusQ.Core
import StatusQ.Core.Theme
import utils
+import AppLayouts.Browser.adapters
import AppLayouts.Browser.controls
Rectangle {
@@ -40,7 +40,7 @@ Rectangle {
width: parent.width
isPaused: downloadItem?.isPaused ?? false
- isCanceled: downloadItem?.state === WebEngineDownloadRequest.DownloadCancelled ?? false
+ isCanceled: downloadItem?.state === AbstractWebView.DownloadState.DownloadCancelled ?? false
primaryText: downloadItem?.downloadFileName ?? ""
downloadText: {
if (isCanceled) {
@@ -52,7 +52,7 @@ Rectangle {
return "%1/%2".arg(Qt.locale().formattedDataSize(downloadItem?.receivedBytes ?? 0, 2, Locale.DataSizeTraditionalFormat)) //e.g. 14.4/109 MB
.arg(Qt.locale().formattedDataSize(downloadItem?.totalBytes ?? 0, 2, Locale.DataSizeTraditionalFormat))
}
- downloadComplete: downloadItem?.state === WebEngineDownloadRequest.DownloadCompleted ?? false
+ downloadComplete: downloadItem?.state === AbstractWebView.DownloadState.DownloadCompleted ?? false
onItemClicked: {
openDownloadClicked(downloadComplete, index)
}
diff --git a/ui/app/AppLayouts/Browser/popups/DownloadMenu.qml b/ui/app/AppLayouts/Browser/popups/DownloadMenu.qml
index aa8e217969..402bec7bdf 100644
--- a/ui/app/AppLayouts/Browser/popups/DownloadMenu.qml
+++ b/ui/app/AppLayouts/Browser/popups/DownloadMenu.qml
@@ -1,9 +1,9 @@
import QtQuick
import QtQuick.Controls
-import QtWebEngine
import StatusQ.Popups
+import AppLayouts.Browser.adapters
import AppLayouts.Browser.stores as BrowserStores
StatusMenu {
@@ -14,8 +14,8 @@ StatusMenu {
property int index: -1
property var download: root.downloadsStore.getDownload(index)
- readonly property bool downloadCancelled: download?.state === WebEngineDownloadRequest.DownloadCancelled ?? false
- readonly property bool downloadComplete: download?.state === WebEngineDownloadRequest.DownloadCompleted ?? false
+ readonly property bool downloadCancelled: download?.state === AbstractWebView.DownloadState.DownloadCancelled ?? false
+ readonly property bool downloadComplete: download?.state === AbstractWebView.DownloadState.DownloadCompleted ?? false
signal cancelClicked()
diff --git a/ui/app/AppLayouts/Browser/popups/JSDialogWindow.qml b/ui/app/AppLayouts/Browser/popups/JSDialogWindow.qml
index 1eb942cef2..773a9bc984 100644
--- a/ui/app/AppLayouts/Browser/popups/JSDialogWindow.qml
+++ b/ui/app/AppLayouts/Browser/popups/JSDialogWindow.qml
@@ -1,7 +1,6 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
-import QtWebEngine
import StatusQ.Core
import StatusQ.Core.Theme
@@ -12,6 +11,8 @@ import shared.popups
import utils
+import AppLayouts.Browser.adapters
+
// TODO: replace with StatusDialog
ModalPopup {
id: root
@@ -28,10 +29,10 @@ ModalPopup {
Component.onCompleted: {
root.title = request.securityOrigin;
message.text = request.message;
- if(request.type === JavaScriptDialogRequest.DialogTypeAlert) {
+ if(request.type === AbstractWebView.JavaScriptDialogType.DialogTypeAlert) {
cancelButton.visible = false;
}
- if(request.type === JavaScriptDialogRequest.DialogTypePrompt) {
+ if(request.type === AbstractWebView.JavaScriptDialogType.DialogTypePrompt) {
prompt.text = request.defaultText;
prompt.visible = true;
svMessage.height = 75;
diff --git a/ui/app/AppLayouts/Browser/provider/js/ethereum_wrapper.js b/ui/app/AppLayouts/Browser/provider/js/ethereum_wrapper.js
index 451399c077..9c244b6a6b 100644
--- a/ui/app/AppLayouts/Browser/provider/js/ethereum_wrapper.js
+++ b/ui/app/AppLayouts/Browser/provider/js/ethereum_wrapper.js
@@ -18,7 +18,9 @@ const EthereumWrapper = (function() {
this.listeners = new Map(); // event -> Set
this.nativeEthereum = nativeEthereum;
- this.requestIdCounter = 1; // async requests
+ // Start from a per-instance random base to avoid requestId collisions
+ // between different frames/tabs that share connector callbacks.
+ this.requestIdCounter = Math.floor(Math.random() * 1000000000) + 1;
this.pendingRequests = new Map(); // requestId -> { resolve, reject, timestamp }
this.requestTimeout = 600000; // 10min timeout for pending requests. (nim side has it's own timeouts)
this.timeoutCheckInterval = 10000;
diff --git a/ui/app/AppLayouts/Browser/provider/qml/ConnectorBridge.qml b/ui/app/AppLayouts/Browser/provider/qml/ConnectorBridge.qml
index 89b411de81..3ec22a929a 100644
--- a/ui/app/AppLayouts/Browser/provider/qml/ConnectorBridge.qml
+++ b/ui/app/AppLayouts/Browser/provider/qml/ConnectorBridge.qml
@@ -17,6 +17,7 @@ QtObject {
id: root
required property string userUID
+ property bool featureEnabled: true
required property var connectorController
property string httpUserAgent: "" // Custom user agent for web profiles
@@ -34,12 +35,12 @@ QtObject {
offTheRecord: true
}
- readonly property var scriptPaths: [
+ readonly property var scriptPaths: root.featureEnabled ? [
{ path: Qt.resolvedUrl("../js/qwebchannel.js"), runOnSubFrames: true },
{ path: Qt.resolvedUrl("../js/ethereum_wrapper.js"), runOnSubFrames: true },
{ path: Qt.resolvedUrl("../js/eip6963_announcer.js"), runOnSubFrames: false },
{ path: Qt.resolvedUrl("../js/ethereum_injector.js"), runOnSubFrames: true }
- ]
+ ] : []
readonly property alias dappUrl: connectorManager.dappUrl
readonly property alias dappOrigin: connectorManager.dappOrigin
diff --git a/ui/app/AppLayouts/Browser/provider/qml/ConnectorManager.qml b/ui/app/AppLayouts/Browser/provider/qml/ConnectorManager.qml
index 77ed2b8fcd..e3addc9b57 100644
--- a/ui/app/AppLayouts/Browser/provider/qml/ConnectorManager.qml
+++ b/ui/app/AppLayouts/Browser/provider/qml/ConnectorManager.qml
@@ -133,12 +133,12 @@ QtObject {
}
}
- function changeAccount(newAccount) {
- if (connectorController) {
- connectorController.disconnect(dappOrigin, clientId)
- connectorController.changeAccount(dappOrigin, clientId, newAccount)
- }
- }
+ function changeAccount(newAccount) {
+ if (connectorController) {
+ connectorController.disconnect(dappOrigin, clientId)
+ connectorController.changeAccount(dappOrigin, clientId, newAccount)
+ }
+ }
function updateDAppUrl(url, name, iconUrl) {
if (!url) return
diff --git a/ui/app/AppLayouts/Browser/provider/qml/qmldir b/ui/app/AppLayouts/Browser/provider/qml/qmldir
index d0859bc40a..f3fff6bc37 100644
--- a/ui/app/AppLayouts/Browser/provider/qml/qmldir
+++ b/ui/app/AppLayouts/Browser/provider/qml/qmldir
@@ -1,4 +1,3 @@
-singleton ProfileManager 1.0 ProfileManager.qml
ConnectorBridge 1.0 ConnectorBridge.qml
ConnectorManager 1.0 ConnectorManager.qml
Eip1193ProviderAdapter 1.0 Eip1193ProviderAdapter.qml
diff --git a/ui/app/AppLayouts/Browser/views/BrowserShortcutActions.qml b/ui/app/AppLayouts/Browser/views/BrowserShortcutActions.qml
index c1f62475c3..85233f7e19 100644
--- a/ui/app/AppLayouts/Browser/views/BrowserShortcutActions.qml
+++ b/ui/app/AppLayouts/Browser/views/BrowserShortcutActions.qml
@@ -58,6 +58,11 @@ QObject {
property var currentWebView
property var findBarComponent
property var browserHeaderComponent
+ function triggerWebAction(action) {
+ if (!currentWebView)
+ return
+ currentWebView.triggerWebAction(action)
+ }
Shortcut {
sequences: ["Ctrl+L", "F6"]
@@ -68,16 +73,13 @@ QObject {
Shortcut {
sequences: [StandardKey.Refresh]
onActivated: {
- if (currentWebView)
- currentWebView.triggerWebAction(AbstractWebView.WebAction.Reload)
+ triggerWebAction(AbstractWebView.WebAction.Reload)
}
}
Shortcut {
sequences: [StandardKey.Close]
onActivated: {
- if (currentWebView) {
- currentWebView.triggerWebAction(AbstractWebView.WebAction.RequestClose)
- }
+ triggerWebAction(AbstractWebView.WebAction.RequestClose)
}
}
Shortcut {
@@ -85,45 +87,44 @@ QObject {
onActivated: {
if (findBarComponent.visible)
findBarComponent.visible = false;
- if (currentWebView)
- currentWebView.triggerWebAction(AbstractWebView.WebAction.Stop)
+ triggerWebAction(AbstractWebView.WebAction.Stop)
}
}
Shortcut {
sequences: [StandardKey.Copy]
- onActivated: currentWebView.triggerWebAction(AbstractWebView.WebAction.Copy)
+ onActivated: triggerWebAction(AbstractWebView.WebAction.Copy)
}
Shortcut {
sequences: [StandardKey.Cut]
- onActivated: currentWebView.triggerWebAction(AbstractWebView.WebAction.Cut)
+ onActivated: triggerWebAction(AbstractWebView.WebAction.Cut)
}
Shortcut {
sequences: [StandardKey.Paste]
- onActivated: currentWebView.triggerWebAction(AbstractWebView.WebAction.Paste)
+ onActivated: triggerWebAction(AbstractWebView.WebAction.Paste)
}
Shortcut {
sequence: "Shift+"+StandardKey.Paste
- onActivated: currentWebView.triggerWebAction(AbstractWebView.WebAction.PasteAndMatchStyle)
+ onActivated: triggerWebAction(AbstractWebView.WebAction.PasteAndMatchStyle)
}
Shortcut {
sequences: [StandardKey.SelectAll]
- onActivated: currentWebView.triggerWebAction(AbstractWebView.WebAction.SelectAll)
+ onActivated: triggerWebAction(AbstractWebView.WebAction.SelectAll)
}
Shortcut {
sequences: [StandardKey.Undo]
- onActivated: currentWebView.triggerWebAction(AbstractWebView.WebAction.Undo)
+ onActivated: triggerWebAction(AbstractWebView.WebAction.Undo)
}
Shortcut {
sequences: [StandardKey.Redo]
- onActivated: currentWebView.triggerWebAction(AbstractWebView.WebAction.Redo)
+ onActivated: triggerWebAction(AbstractWebView.WebAction.Redo)
}
Shortcut {
sequences: [StandardKey.Back]
- onActivated: currentWebView.triggerWebAction(AbstractWebView.WebAction.Back)
+ onActivated: triggerWebAction(AbstractWebView.WebAction.Back)
}
Shortcut {
sequences: [StandardKey.Forward]
- onActivated: currentWebView.triggerWebAction(AbstractWebView.WebAction.Forward)
+ onActivated: triggerWebAction(AbstractWebView.WebAction.Forward)
}
Shortcut {
sequences: [StandardKey.FindNext]
diff --git a/ui/app/AppLayouts/Browser/views/BrowserWebEngineView.qml b/ui/app/AppLayouts/Browser/views/BrowserWebEngineView.qml
deleted file mode 100644
index 9d8a161f49..0000000000
--- a/ui/app/AppLayouts/Browser/views/BrowserWebEngineView.qml
+++ /dev/null
@@ -1,108 +0,0 @@
-import QtQuick
-import QtWebEngine
-
-import StatusQ.Core.Theme
-
-import "ScriptUtils.js" as ScriptUtils
-
-WebEngineView {
- id: root
-
- property bool enableJsLogs: false
- property bool htmlPageLoaded: false
-
- signal showFindBar(int numberOfMatches, int activeMatch)
- signal resetFindBar()
-
- focus: true
-
- function changeZoomFactor(newFactor) {
- zoomFactor = newFactor
- }
-
- backgroundColor: Theme.palette.background
-
- settings.autoLoadImages: localAccountSensitiveSettings.autoLoadImages
- settings.javascriptEnabled: localAccountSensitiveSettings.javaScriptEnabled
- settings.errorPageEnabled: localAccountSensitiveSettings.errorPageEnabled
- settings.pluginsEnabled: localAccountSensitiveSettings.pluginsEnabled
- settings.autoLoadIconsForPage: localAccountSensitiveSettings.autoLoadIconsForPage
- settings.touchIconsEnabled: localAccountSensitiveSettings.touchIconsEnabled
- settings.webRTCPublicInterfacesOnly: localAccountSensitiveSettings.webRTCPublicInterfacesOnly
- settings.pdfViewerEnabled: localAccountSensitiveSettings.pdfViewerEnabled
- settings.focusOnNavigationEnabled: true
- settings.forceDarkMode: Application.styleHints.colorScheme === Qt.ColorScheme.Dark
-
- onQuotaRequested: function(request) {
- if (request.requestedSize <= 5 * 1024 * 1024)
- request.accept();
- else
- request.reject();
- }
-
- onRegisterProtocolHandlerRequested: function(request) {
- console.log("accepting registerProtocolHandler request for "
- + request.scheme + " from " + request.origin);
- request.accept();
- }
-
- onRenderProcessTerminated: function(terminationStatus, exitCode) {
- var status = "";
- switch (terminationStatus) {
- case WebEngineView.NormalTerminationStatus:
- status = "(normal exit)";
- break;
- case WebEngineView.AbnormalTerminationStatus:
- status = "(abnormal exit)";
- break;
- case WebEngineView.CrashedTerminationStatus:
- status = "(crashed)";
- break;
- case WebEngineView.KilledTerminationStatus:
- status = "(killed)";
- break;
- }
-
- console.warn("Render process exited with code " + exitCode + " " + status);
- }
-
- onSelectClientCertificate: function(selection) {
- selection.certificates[0].select();
- }
-
- onFindTextFinished: function(result) {
- root.showFindBar(result.numberOfMatches, result.activeMatch)
- }
-
- onLoadingChanged: function(loadRequest) {
- if (loadRequest.status === WebEngineView.LoadStartedStatus) {
- root.htmlPageLoaded = false
- root.resetFindBar()
- }
- if (loadRequest.status === WebEngineView.LoadSucceededStatus) {
- root.htmlPageLoaded = true
- }
- }
-
- onLoadProgressChanged: function(progress) {
- if (progress >= 10) {
- // Some real content rendered
- htmlPageLoaded = true
- }
- }
-
- onNavigationRequested: function (request) {
- if(request.url.toString().startsWith("file:/")){
- console.log("Local file browsing is disabled" )
- request.reject()
- }
- }
-
- onJavaScriptConsoleMessage: function(level, message, lineNumber, sourceID) {
- // Check if the message is from our injected scripts
- const isOurScript = ScriptUtils.isOurInjectedScript(sourceID, root.profile);
- if (isOurScript || root.enableJsLogs) {
- console.log("[WebEngine]", sourceID + ":" + lineNumber, message);
- }
- }
-}
diff --git a/ui/app/AppLayouts/Browser/views/qmldir b/ui/app/AppLayouts/Browser/views/qmldir
index 404ff84c20..e991d46145 100644
--- a/ui/app/AppLayouts/Browser/views/qmldir
+++ b/ui/app/AppLayouts/Browser/views/qmldir
@@ -1,4 +1,4 @@
-BrowserWebEngineView 1.0 BrowserWebEngineView.qml
WebViewContainer 1.0 WebViewContainer.qml
BrowserShortcutActions 1.0 BrowserShortcutActions.qml
EmptyWebPage 1.0 EmptyWebPage.qml
+ScriptUtils 1.0 ScriptUtils.js
diff --git a/ui/app/AppLayouts/Browser/webview/BrowserDialogsContext.qml b/ui/app/AppLayouts/Browser/webview/BrowserDialogsContext.qml
new file mode 100644
index 0000000000..13cec1a0de
--- /dev/null
+++ b/ui/app/AppLayouts/Browser/webview/BrowserDialogsContext.qml
@@ -0,0 +1,43 @@
+import QtQuick
+import StatusQ.Core.Utils as SQUtils
+
+QtObject {
+ id: root
+
+ required property var networksStore
+ required property var browserActivityStore
+ required property var browserWalletStore
+ required property var openPopupFn
+ required property Component jsDialogComponent
+ required property Item dialogParent
+
+ function openHistoryMenu(historyMenu) {
+ historyMenu.open()
+ }
+
+ function openSettingsMenu(settingsMenu) {
+ settingsMenu.open()
+ }
+
+ function openWalletMenu(browserWalletMenu) {
+ // Initialize activity filters before opening popup.
+ const activeChainIds = SQUtils.ModelUtils.modelToFlatArray(
+ networksStore.activeNetworks, "chainId")
+ if (activeChainIds.length > 0) {
+ browserActivityStore.activityController.setFilterChainsJson(
+ JSON.stringify(activeChainIds), true)
+ }
+
+ const currentAddress = browserWalletStore.dappBrowserAccount.address
+ browserActivityStore.activityController.setFilterAddressesJson(
+ JSON.stringify([currentAddress]))
+ openPopupFn(browserWalletMenu)
+ }
+
+ function openJsDialog(request) {
+ request.accepted = true
+ var dialog = jsDialogComponent.createObject(dialogParent, {"request": request})
+ if (dialog)
+ dialog.open()
+ }
+}
diff --git a/ui/app/AppLayouts/Browser/webview/BrowserDownloadsContext.qml b/ui/app/AppLayouts/Browser/webview/BrowserDownloadsContext.qml
new file mode 100644
index 0000000000..798b67deb8
--- /dev/null
+++ b/ui/app/AppLayouts/Browser/webview/BrowserDownloadsContext.qml
@@ -0,0 +1,39 @@
+import QtQuick
+
+QtObject {
+ id: root
+
+ required property var downloadsStore
+ required property var tabsModel
+ required property var getWebViewFn
+ required property var removeViewFn
+ required property var setFooterVisibleFn
+
+ function handleDownloadRequest(download) {
+ if (!download)
+ return
+
+ downloadsStore.addDownload(download)
+ download.accept()
+ setFooterVisibleFn(true)
+
+ // Close tabs that were opened only to trigger a download.
+ if (!download.view)
+ return
+
+ for (var i = 0; i < tabsModel.count; ++i) {
+ var tab = getWebViewFn(i)
+ if (tab === download.view && !tab.htmlPageLoaded && tab.title === "") {
+ removeViewFn(i)
+ break
+ }
+ }
+ }
+
+ function openDownloadFromList(downloadComplete, index) {
+ if (downloadComplete)
+ return downloadsStore.openFile(index)
+
+ downloadsStore.openDirectory(index)
+ }
+}
diff --git a/ui/app/AppLayouts/Browser/webview/BrowserFavoritesContext.qml b/ui/app/AppLayouts/Browser/webview/BrowserFavoritesContext.qml
new file mode 100644
index 0000000000..866e29fbaf
--- /dev/null
+++ b/ui/app/AppLayouts/Browser/webview/BrowserFavoritesContext.qml
@@ -0,0 +1,47 @@
+import QtQuick
+
+import QtModelsToolkit
+
+QtObject {
+ id: root
+
+ required property var currentWebView
+ required property var bookmarksStore
+ required property bool shouldShowFavoritesBar
+ required property var openPopupFn
+ required property var addFavoriteModal
+
+ readonly property bool favoritesBarActive: shouldShowFavoritesBar &&
+ bookmarksStore.bookmarksModel.ModelCount.count > 0
+ readonly property var currentUrl: (currentWebView && currentWebView.url)
+ ? currentWebView.url
+ : ""
+ readonly property string currentTitle: currentWebView ? currentWebView.title : ""
+
+ readonly property var currentViewBookmarkEntry: ModelEntry {
+ sourceModel: root.bookmarksStore.bookmarksModel
+ key: "url"
+ value: root.currentUrl ? root.currentUrl.toString() : ""
+ }
+
+ readonly property bool currentTabIsBookmark: currentViewBookmarkEntry.available &&
+ !!currentViewBookmarkEntry.item
+
+ function buildAddFavoritePopupParams(modifyModal = false, fallbackItem = null) {
+ var bookmarkItem = currentViewBookmarkEntry.available ? currentViewBookmarkEntry.item : null
+ var sourceItem = fallbackItem || bookmarkItem
+ var sourceUrl = sourceItem ? sourceItem.url : currentUrl
+ var sourceName = sourceItem ? sourceItem.name : currentTitle
+ return {
+ modifiyModal: modifyModal,
+ toolbarMode: true,
+ ogUrl: sourceUrl,
+ ogName: sourceName
+ }
+ }
+
+ function openAddFavoritePopup(modifyModal = false, fallbackItem = null) {
+ openPopupFn(addFavoriteModal, buildAddFavoritePopupParams(modifyModal, fallbackItem))
+ }
+
+}
diff --git a/ui/app/AppLayouts/Browser/webview/BrowserWebViewContext.qml b/ui/app/AppLayouts/Browser/webview/BrowserWebViewContext.qml
new file mode 100644
index 0000000000..cc4fc39082
--- /dev/null
+++ b/ui/app/AppLayouts/Browser/webview/BrowserWebViewContext.qml
@@ -0,0 +1,200 @@
+import QtQuick
+import QtQuick.Controls
+
+import utils
+import StatusQ.Core.Utils as SQUtils
+
+import AppLayouts.Browser.adapters
+
+Item {
+ id: root
+ visible: false
+
+ required property bool thirdpartyServicesEnabled
+ required property bool isDebugEnabled
+ required property bool isMobile
+
+ required property var browserSettings
+ required property var webChannel
+
+ required property Item hostStackLayout
+ required property var tabsModel
+ required property ProfileParams defaultProfileParams
+
+ required property var bookmarksStore
+ required property var downloadsStore
+
+ required property var determineRealURLFn
+ required property var downloadRequestHandler
+ required property var sslErrorHandler
+ required property var jsDialogHandler
+ required property var findTextFinishedHandler
+
+ enum ContentMode {
+ WebContent = 0,
+ DownloadContent,
+ EmptyContent
+ }
+
+ readonly property Item currentWebView: tabsModel.currentIndex < tabsModel.count ? getCurrentWebView() : null
+ readonly property int currentContentMode: {
+ if (!currentWebView)
+ return BrowserWebViewContext.ContentMode.EmptyContent
+ if (currentWebView.isDownloadView)
+ return BrowserWebViewContext.ContentMode.DownloadContent
+ if (!currentWebView.url?.toString())
+ return BrowserWebViewContext.ContentMode.EmptyContent
+ return BrowserWebViewContext.ContentMode.WebContent
+ }
+
+ function createEmptyTab(profileParams, createAsStartPage = false, focusOnNewTab = true, url = undefined) {
+ focusOnNewTab = focusOnNewTab && !createAsStartPage
+
+ var webview = webViewAdapterComponent.createObject(hostStackLayout, {
+ profileParams: profileParams,
+ isDownloadView: false
+ })
+
+ tabsModel.createEmptyTab(createAsStartPage, focusOnNewTab, webview)
+
+ if (createAsStartPage && thirdpartyServicesEnabled) {
+ webview.url = Constants.browserDefaultHomepage
+ } else if (url !== undefined) {
+ webview.url = url
+ } else if (!!browserSettings.browserHomepage) {
+ webview.url = determineRealURLFn(browserSettings.browserHomepage)
+ }
+
+ return webview
+ }
+
+ function createDownloadTab(profileParams) {
+ var webview = webViewAdapterComponent.createObject(hostStackLayout, {
+ profileParams: profileParams,
+ isDownloadView: true
+ })
+ tabsModel.createDownloadTab()
+ return webview
+ }
+
+ function getCurrentWebView() { // -> WebEngineView/WebView
+ return getWebView(tabsModel.currentIndex)
+ }
+
+ function getWebView(index) { // -> WebEngineView/WebView
+ return hostStackLayout.children[index]
+ }
+
+ function setCurrentWebUrl(url) {
+ if (!currentWebView) {
+ console.error("[Browser] currentWebView is null, cannot set URL")
+ return
+ }
+
+ const newUrl = determineRealURLFn(url)
+ Qt.callLater(function() {
+ if (currentWebView)
+ currentWebView.url = newUrl
+ })
+ }
+
+ function goBackCurrent() {
+ if (!currentWebView)
+ return
+ currentWebView.goBack()
+ }
+
+ function goForwardCurrent() {
+ if (!currentWebView)
+ return
+ currentWebView.goForward()
+ }
+
+ function reloadCurrent() {
+ if (!currentWebView)
+ return
+ currentWebView.reload()
+ }
+
+ function stopCurrent() {
+ if (!currentWebView)
+ return
+ currentWebView.stop()
+ }
+
+ function findTextCurrent(text, backward = false) {
+ if (!currentWebView || !text)
+ return
+
+ if (backward) {
+ currentWebView.findText(text, currentWebView.findBackward)
+ return
+ }
+
+ currentWebView.findText(text)
+ }
+
+ function setIncognitoCurrent(checked) {
+ if (!currentWebView)
+ return
+ currentWebView.profileParams.offTheRecord = checked
+ }
+
+ function changeZoomCurrent(delta) {
+ if (!currentWebView)
+ return
+ currentWebView.changeZoomFactor(currentWebView.zoomFactor + delta)
+ }
+
+ function resetZoomCurrent() {
+ if (!currentWebView)
+ return
+ currentWebView.changeZoomFactor(1.0)
+ }
+
+ function removeView(index) {
+ if (index < 0 || index >= tabsModel.count)
+ return
+
+ var view = getWebView(index)
+ if (tabsModel.count <= 1) {
+ var fallbackProfileParams = currentWebView ? currentWebView.profileParams : defaultProfileParams
+ createEmptyTab(fallbackProfileParams, true)
+ }
+ tabsModel.removeTab(index)
+ if (!view)
+ return
+ view.visible = false
+ view.enabled = false
+ view.focus = false
+ view.detachView()
+ view.parent = null
+ view.destroy()
+ }
+
+ Component {
+ id: webViewAdapterComponent
+ WebViewAdapter {
+ visible: !SQUtils.Utils.hasPopups(Overlay.overlay.children) || !root.isMobile
+ enabled: visible
+
+ bookmarksStore: root.bookmarksStore
+ downloadsStore: root.downloadsStore
+ webChannel: root.webChannel
+ enableJsLogs: root.isDebugEnabled
+ localAccountSensitiveSettings: root.browserSettings
+ devToolsEnabled: root.browserSettings.devToolsEnabled
+
+ onWindowCloseRequested: root.removeView(StackLayout.index)
+ onNewWindowRequested: (makeCurrent, requestedUrl, callback) => {
+ var profileParams = root.currentWebView ? root.currentWebView.profileParams : root.defaultProfileParams
+ var tab = root.createEmptyTab(profileParams, false, makeCurrent, requestedUrl)
+ callback(tab)
+ }
+ onDownloadRequested: (download) => root.downloadRequestHandler(download)
+ onCertificateError: (error) => root.sslErrorHandler(error)
+ onJavaScriptDialogRequested: (request) => root.jsDialogHandler(request)
+ onFindTextFinished: (result) => root.findTextFinishedHandler(result)
+ }
+ }
+}
diff --git a/ui/app/AppLayouts/Browser/webview/qmldir b/ui/app/AppLayouts/Browser/webview/qmldir
new file mode 100644
index 0000000000..8dee24f94e
--- /dev/null
+++ b/ui/app/AppLayouts/Browser/webview/qmldir
@@ -0,0 +1,5 @@
+BrowserWebViewContext 1.0 BrowserWebViewContext.qml
+BrowserOverlayContext 1.0 BrowserOverlayContext.qml
+BrowserFavoritesContext 1.0 BrowserFavoritesContext.qml
+BrowserDialogsContext 1.0 BrowserDialogsContext.qml
+BrowserDownloadsContext 1.0 BrowserDownloadsContext.qml
diff --git a/ui/app/mainui/AppMain.qml b/ui/app/mainui/AppMain.qml
index 99ed1fd2c9..8d49085146 100644
--- a/ui/app/mainui/AppMain.qml
+++ b/ui/app/mainui/AppMain.qml
@@ -1504,7 +1504,12 @@ Item {
Item {
anchors.fill: parent
- anchors.leftMargin: sidebar.alwaysVisible ? sidebar.width : undefined
+ readonly property bool offsetBySidebar: sidebar.alwaysVisible
+ || d.activeSectionType === Constants.appSection.browser
+ readonly property real sidebarOffset: offsetBySidebar
+ ? sidebar.width * (sidebar.alwaysVisible ? 1.0 : sidebar.position)
+ : 0
+ anchors.leftMargin: sidebarOffset
StackLayout {
id: appView
@@ -1851,9 +1856,7 @@ Item {
restoreMode: Binding.RestoreNone
}
- sourceComponent: (appMain.rootStore.thirdpartyServicesEnabled && !SQUtils.Utils.isMobile)
- ? browserLayout
- : browserPrivacyWall
+ sourceComponent: appMain.rootStore.thirdpartyServicesEnabled ? browserLayout: browserPrivacyWall
Component {
id: browserPrivacyWall
@@ -1873,6 +1876,7 @@ Item {
isMobile: SQUtils.Utils.isMobile
userUID: appMain.profileStore.pubKey
thirdpartyServicesEnabled: appMain.rootStore.thirdpartyServicesEnabled
+ dappsEnabled: featureFlagsStore.dappsEnabled
bookmarksStore: BrowserStores.BookmarksStore {}
downloadsStore: BrowserStores.DownloadsStore {}
browserRootStore: BrowserStores.BrowserRootStore {}
@@ -2932,7 +2936,7 @@ Item {
accountsModel: WalletStores.RootStore.nonWatchAccounts
}
bcSdk: DappsConnectorSDK {
- enabled: featureFlagsStore.connectorEnabled && WalletStores.RootStore.walletSectionInst.walletReady
+ enabled: featureFlagsStore.dappsEnabled && WalletStores.RootStore.walletSectionInst.walletReady
excludeClientIds: ["walletconnect"]
store: SharedStores.BrowserConnectStore {
controller: WalletStores.RootStore.dappsConnectorController
diff --git a/ui/app/mainui/panels/PrimaryNavSidebar.qml b/ui/app/mainui/panels/PrimaryNavSidebar.qml
index 478c465cde..e761b53466 100644
--- a/ui/app/mainui/panels/PrimaryNavSidebar.qml
+++ b/ui/app/mainui/panels/PrimaryNavSidebar.qml
@@ -109,11 +109,7 @@ Control {
: root.Theme.palette.privacyColors.primary
readonly property int containerBgRadius: root.Theme.defaultPadding
- readonly property bool hasPopups: root.Overlay.overlay.children.filter(
- item => {
- const str = item.toString()
- return str.includes("QQuickPopupItem") && !str.includes("StatusToolTip")
- }).length
+ readonly property bool hasPopups: SQUtils.Utils.hasPopups(root.Overlay.overlay.children)
onHasPopupsChanged: {
if (d.hasPopups) {
diff --git a/ui/imports/shared/popups/walletconnect/panels/IntentionPanel.qml b/ui/imports/shared/popups/walletconnect/panels/IntentionPanel.qml
deleted file mode 100644
index 4891356695..0000000000
--- a/ui/imports/shared/popups/walletconnect/panels/IntentionPanel.qml
+++ /dev/null
@@ -1,112 +0,0 @@
-import QtQuick
-import QtQuick.Layouts
-
-import StatusQ.Components
-import StatusQ.Core
-import StatusQ.Core.Theme
-
-import utils
-
-ColumnLayout {
- id: root
-
- spacing: 8
-
- required property string dappName
- required property url dappIcon
- required property var account
-
- property string userDisplayNaming
-
- // Icons
- Item {
- Layout.fillWidth: true
- Layout.preferredHeight: 40
- Layout.alignment: Qt.AlignHCenter
- Layout.bottomMargin: 8
-
- StatusRoundedImage {
- width: height
- height: parent.height
-
- anchors.horizontalCenter: parent.horizontalCenter
- anchors.horizontalCenterOffset: -16
- anchors.verticalCenter: parent.verticalCenter
-
- image.source: root.dappIcon
- }
- StatusRoundIcon {
- anchors.horizontalCenter: parent.horizontalCenter
- anchors.horizontalCenterOffset: 16
- anchors.verticalCenter: parent.verticalCenter
-
- asset: StatusAssetSettings {
- width: 24
- height: 24
- color: root.Theme.palette.primaryColor1
- bgWidth: 40
- bgHeight: 40
- bgColor: root.Theme.palette.desktopBlue10
- bgRadius: bgWidth / 2
- bgBorderWidth: 2
- bgBorderColor: root.Theme.palette.statusAppLayout.backgroundColor
- source: Assets.svg("sign")
- }
- }
- }
-
- // Names and intentions
- StatusBaseText {
- text: qsTr("%1 wants you to %2 with %3").arg(dappName).arg(root.userDisplayNaming).arg(account.name)
-
- Layout.preferredWidth: 400
- Layout.alignment: Qt.AlignHCenter
-
- font.pixelSize: Theme.primaryTextFontSize
- font.weight: Font.DemiBold
-
- wrapMode: Text.WordWrap
- horizontalAlignment: Text.AlignHCenter
- }
-
- // TODO #14762: externalize as a InfoPill and merge base implementation with
- // the existing IssuePill reusable component
- Rectangle {
- Layout.preferredWidth: operationStatusLayout.implicitWidth + 24
- Layout.preferredHeight: operationStatusLayout.implicitHeight + 14
-
- Layout.alignment: Qt.AlignHCenter
-
- visible: true
-
- border.color: Theme.palette.successColor2
- border.width: 1
- color: "transparent"
- radius: height / 2
-
- RowLayout {
- id: operationStatusLayout
-
- spacing: 8
-
- anchors.centerIn: parent
-
- StatusIcon {
- Layout.preferredWidth: 16
- Layout.preferredHeight: 16
-
- visible: true
-
- color: Theme.palette.directColor1
- icon: "info"
- }
-
- StatusBaseText {
- text: qsTr("Only sign if you trust the dApp")
-
- font.pixelSize: Theme.tertiaryTextFontSize
- color: Theme.palette.directColor1
- }
- }
- }
-}
diff --git a/ui/imports/shared/popups/walletconnect/panels/MaxFeesDisplay.qml b/ui/imports/shared/popups/walletconnect/panels/MaxFeesDisplay.qml
deleted file mode 100644
index 39b58ed581..0000000000
--- a/ui/imports/shared/popups/walletconnect/panels/MaxFeesDisplay.qml
+++ /dev/null
@@ -1,36 +0,0 @@
-import QtQuick
-import QtQuick.Layouts
-
-import StatusQ.Core
-import StatusQ.Core.Theme
-
-ColumnLayout {
- id: root
-
- property alias maxFeesText: maxFeesDisplay.text
- property alias feesTextColor: maxFeesDisplay.color
-
- StatusBaseText {
- text: qsTr("Max fees:")
-
- font.pixelSize: Theme.tertiaryTextFontSize
- color: Theme.palette.directColor1
- }
- StatusBaseText {
- id: maxFeesDisplay
- text: root.maxFeesText
-
- visible: !!text
-
- font.pixelSize: Theme.fontSize(16)
-
- }
- StatusBaseText {
- text: qsTr("No fees")
-
- visible: !maxFeesDisplay.visible
-
- font.pixelSize: maxFeesDisplay.font.pixelSize
- font.weight: maxFeesDisplay.font.weight
- }
-}
diff --git a/ui/imports/shared/popups/walletconnect/panels/qmldir b/ui/imports/shared/popups/walletconnect/panels/qmldir
index 16db2d9470..a725f4b940 100644
--- a/ui/imports/shared/popups/walletconnect/panels/qmldir
+++ b/ui/imports/shared/popups/walletconnect/panels/qmldir
@@ -1,3 +1 @@
-MaxFeesDisplay 1.0 MaxFeesDisplay.qml
-IntentionPanel 1.0 IntentionPanel.qml
-ContentPanel 1.0 ContentPanel.qml
+ContentPanel 1.0 ContentPanel.qml