status-desktop/ui/app/AppLayouts/Browser/BrowserLayout.qml

835 lines
30 KiB
QML
Raw Normal View History

2020-09-22 15:12:48 +00:00
import QtQuick 2.13
import QtQuick.Controls 2.13
import QtQuick.Controls 1.0 as QQC1
2020-09-22 15:12:48 +00:00
import QtQuick.Layouts 1.13
import QtWebEngine 1.10
import QtWebChannel 1.13
import Qt.labs.settings 1.0
import QtQuick.Controls.Styles 1.0
import QtQuick.Dialogs 1.2
import "../../../shared"
import "../../../shared/status"
import "../../../imports"
import "../Chat/ChatColumn/ChatComponents"
2020-10-27 20:53:22 +00:00
import "./components"
2020-09-22 15:12:48 +00:00
// Code based on https://code.qt.io/cgit/qt/qtwebengine.git/tree/examples/webengine/quicknanobrowser/BrowserWindow.qml?h=5.15
// Licensed under BSD
Rectangle {
id: browserWindow
color: Style.current.inputBackground
border.width: 0
property Item currentWebView: tabs.currentIndex < tabs.count ? tabs.getTab(tabs.currentIndex).item : null
property Component browserDialogComponent: BrowserDialog {
onClosing: destroy()
}
2020-11-16 16:44:23 +00:00
property Component jsDialogComponent: JSDialogWindow {}
property bool currentTabConnected: false
ListModel {
id: downloadModel
property var downloads: []
}
function removeDownloadFromModel(index) {
downloadModel.downloads = downloadModel.downloads.filter(function (el) {
return el.id !== downloadModel.downloads[index].id;
});
downloadModel.remove(index);
}
2020-09-22 15:12:48 +00:00
Layout.fillHeight: true
Layout.fillWidth: true
2020-09-30 18:59:36 +00:00
property var urlENSDictionary: ({})
function determineRealURL(text){
var url = _utilsModel.urlFromUserInput(text);
var host = _web3Provider.getHost(url);
if(host.endsWith(".eth")){
var ensResource = _web3Provider.ensResourceURL(host, url);
if(/^https\:\/\/swarm\-gateways\.net\/bzz:\/([0-9a-fA-F]{64}|.+\.eth)(\/?)/.test(ensResource)){
// TODO: populate urlENSDictionary for prettier url instead of swarm-gateway big URL
return ensResource;
} else {
urlENSDictionary[_web3Provider.getHost(ensResource)] = host;
}
2020-09-30 18:59:36 +00:00
url = ensResource;
}
return url;
}
function openUrlInNewTab(url) {
browserWindow.addNewTab()
currentWebView.url = determineRealURL(url)
}
2020-10-19 19:00:43 +00:00
property Component accessDialogComponent: BrowserConnectionModal {
currentTab: tabs.getTab(tabs.currentIndex) && tabs.getTab(tabs.currentIndex).item
x: browserWindow.width - width - Style.current.halfPadding
y: browserHeader.y + browserHeader.height + Style.current.halfPadding
}
// TODO we'll need a new dialog at one point because this one is not using the same call, but it's good for now
2021-07-05 12:34:56 +00:00
property Component sendTransactionModalComponent: SignTransactionModal {}
2020-10-05 16:24:43 +00:00
property Component signMessageModalComponent: SignMessageModal {}
property MessageDialog sendingError: MessageDialog {
id: sendingError
//% "Error sending the transaction"
title: qsTrId("error-sending-the-transaction")
icon: StandardIcon.Critical
standardButtons: StandardButton.Ok
}
2020-10-05 16:24:43 +00:00
property MessageDialog signingError: MessageDialog {
id: signingError
2021-02-18 16:36:05 +00:00
//% "Error signing message"
title: qsTrId("error-signing-message")
2020-10-05 16:24:43 +00:00
icon: StandardIcon.Critical
standardButtons: StandardButton.Ok
}
2020-10-29 20:07:52 +00:00
function getCurrentFavorite(url) {
if (!url) {
return null
}
const index = browserModel.bookmarks.getBookmarkIndexByUrl(url)
if (index === -1) {
return null
}
2020-10-29 20:07:52 +00:00
return {
url: url,
name: browserModel.bookmarks.rowData(index, 'name'),
image: browserModel.bookmarks.rowData(index, 'imageUrl')
2020-10-29 20:07:52 +00:00
}
}
2020-10-29 15:19:27 +00:00
AddFavoriteModal {
id: addFavoriteModal
}
2020-10-29 20:07:52 +00:00
FavoriteMenu {
id: favoriteMenu
openInNewTab: function (url) {
browserWindow.openUrlInNewTab(url)
2020-10-29 20:07:52 +00:00
}
}
QtObject {
id: provider
WebChannel.id: "backend"
signal web3Response(string data);
2020-10-27 16:49:55 +00:00
function signValue(input){
if(Utils.isHex(input) && Utils.startsWith0x(input)){
return input
}
return utilsModel.ascii2Hex(input)
}
function postMessage(data) {
var request;
try {
request = JSON.parse(data)
} catch (e) {
console.error("Error parsing the message data", e)
return;
}
2020-09-30 18:59:36 +00:00
var ensAddr = urlENSDictionary[request.hostname];
if (ensAddr) {
2020-09-30 18:59:36 +00:00
request.hostname = ensAddr;
}
if (request.type === Constants.api_request) {
if (!_web3Provider.hasPermission(request.hostname, request.permission)) {
browserWindow.currentTabConnected = false
var dialog = accessDialogComponent.createObject(browserWindow);
dialog.request = request;
dialog.open();
} else {
browserWindow.currentTabConnected = true
request.isAllowed = true;
web3Response(_web3Provider.postMessage(JSON.stringify(request)));
}
} else if (request.type === Constants.web3SendAsyncReadOnly &&
request.payload.method === "eth_sendTransaction") {
var acc = walletModel.dappBrowserView.dappBrowserAccount
const value = utilsModel.wei2Eth(request.payload.params[0].value, 18);
const sendDialog = sendTransactionModalComponent.createObject(browserWindow, {
2020-12-09 16:01:51 +00:00
trxData: request.payload.params[0].data || "",
selectedAccount: {
name: acc.name,
address: request.payload.params[0].from,
iconColor: acc.iconColor,
assets: acc.assets
},
selectedRecipient: {
address: request.payload.params[0].to,
identicon: utilsModel.generateIdenticon(request.payload.params[0].to),
name: chatsModel.channelView.activeChannel.name,
type: RecipientSelector.Type.Address
},
selectedAsset: {
name: "ETH",
symbol: "ETH",
address: Constants.zeroAddress
},
selectedFiatAmount: "42", // TODO calculate that
selectedAmount: value
});
// TODO change sendTransaction function to the postMessage one
2021-07-05 12:34:56 +00:00
sendDialog.sendTransaction = function (selectedGasLimit, selectedGasPrice, selectedTipLimit, selectedOverallLimit, enteredPassword) {
request.payload.selectedGasLimit = selectedGasLimit
request.payload.selectedGasPrice = selectedGasPrice
2021-07-05 12:34:56 +00:00
request.payload.selectedTipLimit = selectedTipLimit
request.payload.selectedOverallLimit = selectedOverallLimit
request.payload.password = enteredPassword
request.payload.params[0].value = value
const response = _web3Provider.postMessage(JSON.stringify(request))
provider.web3Response(response)
let responseObj
try {
responseObj = JSON.parse(response)
if (responseObj.error) {
throw new Error(responseObj.error)
}
//% "Transaction pending..."
toastMessage.title = qsTrId("ens-transaction-pending")
toastMessage.source = "../../img/loading.svg"
toastMessage.iconColor = Style.current.primary
toastMessage.iconRotates = true
refactor wallet views add getSettings methods to src/status fix issue with calling getSettings; document issue remove most direct references to libstatus; document some common issues remove most references to libstatus wallet add mailserver layer to status lib; remove references to libstatus mailservers remove libstatus accounts references move types out of libstatus; remove libstatus types references remove libstatus browser references refactor libstatus utils references remove more references to libstatus stickers remove references to libstatus constants from src/app remove more libstatus references from src/app refactor token_list usage of libstatus refactor stickers usage of libstatus refactor chat usage of libstatus remove libstatus references from the wallet view remove logic from ens manager view fix issue with import & namespace conflict remove unnecessary imports refactor provider view to not depend on libstatus refactor provider view refactor: move accounts specific code to its own section fix account selection move collectibles to their own module update references to wallet transactions refactor: move gas methods to their own file refactor: extract tokens into their own file refactor: extract ens to its own file refactor: extract dappbrowser code to its own file refactor: extract history related code to its own file refactor: extract balance to its own file refactor: extract utils to its own file clean up wallet imports fix: identicon for transaction commands Fixes #2533
2021-06-08 12:48:31 +00:00
toastMessage.link = `${_walletModel.utilsView.etherscanLink}/${responseObj.result.result}`
toastMessage.open()
} catch (e) {
fix: prevent crash on generate account wrong password Fixes #2448. Currently, if a wrong password is entered when generating a wallet account, the app will crash due to attempting to decode a `GeneratedAccount ` from an rpc response containing only an error. With this PR, we are detecting if an error is returned in the response, and if so, raising a StatusGoException. This exception is caught in the call chain, and translated in to a `StatusGoError` which is serialised and sent to the QML view, where it is parsed and displayed as an invalid password error in the input box. refactor: remove string return values as error messages in wallet model In the wallet model, we were passing back empty strings for no error, or an error as a string. This is not only confusing, but does not benefit from leaning on the compiler and strong types. One has to read the entire code to understand if a string result is returned when there is no error instead of implicitly being able to understand there is no return type. To alleviate this, account creation fundtions that do not need to return a value have been changed to a void return type, and raise `StatusGoException` if there is an error encountered. This can be caught in the call chain and used as necessary (ie to pass to QML). refactor: move invalid password string detection to Utils Currently, we are reading returned view model values and checking to see if they include a known string from Status Go that means there was an invalid password used. This string was placed in the codebased in mulitple locations. This PR moves the string check to a Utils function and updates all the references to use the function in Utils.
2021-05-13 04:41:48 +00:00
if (Utils.isInvalidPasswordMessage(e.message)){
//% "Wrong password"
sendDialog.transactionSigner.validationError = qsTrId("wrong-password")
return
}
sendingError.text = e.message
return sendingError.open()
}
sendDialog.close()
sendDialog.destroy()
}
sendDialog.open();
2021-07-05 12:34:56 +00:00
walletModel.gasView.getGasPrice()
2020-10-05 16:24:43 +00:00
} else if (request.type === Constants.web3SendAsyncReadOnly && ["eth_sign", "personal_sign", "eth_signTypedData", "eth_signTypedData_v3"].indexOf(request.payload.method) > -1) {
const signDialog = signMessageModalComponent.createObject(browserWindow, {
request,
selectedAccount: {
name: walletModel.dappBrowserView.dappBrowserAccount.name,
iconColor: walletModel.dappBrowserView.dappBrowserAccount.iconColor
}
});
2020-10-05 16:24:43 +00:00
signDialog.web3Response = web3Response
signDialog.signMessage = function (enteredPassword) {
signDialog.interactedWith = true;
request.payload.password = enteredPassword;
2020-10-27 16:49:55 +00:00
switch(request.payload.method){
case Constants.personal_sign:
request.payload.params[0] = signValue(request.payload.params[0]);
case Constants.eth_sign:
request.payload.params[1] = signValue(request.payload.params[1]);
}
2020-10-05 16:24:43 +00:00
const response = web3Provider.postMessage(JSON.stringify(request));
provider.web3Response(response);
try {
let responseObj = JSON.parse(response)
if (responseObj.error) {
throw new Error(responseObj.error)
}
} catch (e) {
fix: prevent crash on generate account wrong password Fixes #2448. Currently, if a wrong password is entered when generating a wallet account, the app will crash due to attempting to decode a `GeneratedAccount ` from an rpc response containing only an error. With this PR, we are detecting if an error is returned in the response, and if so, raising a StatusGoException. This exception is caught in the call chain, and translated in to a `StatusGoError` which is serialised and sent to the QML view, where it is parsed and displayed as an invalid password error in the input box. refactor: remove string return values as error messages in wallet model In the wallet model, we were passing back empty strings for no error, or an error as a string. This is not only confusing, but does not benefit from leaning on the compiler and strong types. One has to read the entire code to understand if a string result is returned when there is no error instead of implicitly being able to understand there is no return type. To alleviate this, account creation fundtions that do not need to return a value have been changed to a void return type, and raise `StatusGoException` if there is an error encountered. This can be caught in the call chain and used as necessary (ie to pass to QML). refactor: move invalid password string detection to Utils Currently, we are reading returned view model values and checking to see if they include a known string from Status Go that means there was an invalid password used. This string was placed in the codebased in mulitple locations. This PR moves the string check to a Utils function and updates all the references to use the function in Utils.
2021-05-13 04:41:48 +00:00
if (Utils.isInvalidPasswordMessage(e.message)){
2020-10-05 16:24:43 +00:00
//% "Wrong password"
signDialog.transactionSigner.validationError = qsTrId("wrong-password")
return
}
signingError.text = e.message
return signingError.open()
}
signDialog.close()
signDialog.destroy()
}
signDialog.open();
2020-12-14 20:43:46 +00:00
} else if (request.type === Constants.web3DisconnectAccount) {
web3Response(data);
} else {
web3Response(_web3Provider.postMessage(data));
}
}
2020-10-26 17:10:14 +00:00
property int networkId: (_web3Provider && _web3Provider.networkId) || -1
}
WebChannel {
id: channel
registeredObjects: [provider]
}
property QtObject defaultProfile: WebEngineProfile {
storageName: "Profile"
offTheRecord: false
2020-10-20 20:24:41 +00:00
httpUserAgent: {
if (appSettings.compatibilityMode) {
2020-10-20 20:24:41 +00:00
// Google doesn't let you connect if the user agent is Chrome-ish and doesn't satisfy some sort of hidden requirement
return "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:81.0) Gecko/20100101 Firefox/81.0"
}
return ""
}
useForGlobalCertificateVerification: true
userScripts: [
WebEngineScript {
injectionPoint: WebEngineScript.DocumentCreation
sourceUrl: Qt.resolvedUrl("provider.js")
worldId: WebEngineScript.MainWorld // TODO: check https://doc.qt.io/qt-5/qml-qtwebengine-webenginescript.html#worldId-prop
}
]
}
property QtObject otrProfile: WebEngineProfile {
offTheRecord: true
persistentCookiesPolicy: WebEngineProfile.NoPersistentCookies
httpUserAgent: defaultProfile.httpUserAgent
userScripts: [
WebEngineScript {
injectionPoint: WebEngineScript.DocumentCreation
sourceUrl: Qt.resolvedUrl("provider.js")
worldId: WebEngineScript.MainWorld // TODO: check https://doc.qt.io/qt-5/qml-qtwebengine-webenginescript.html#worldId-prop
}
]
}
function obtainAddress(){
var ensAddr = urlENSDictionary[web3Provider.getHost(currentWebView.url)];
browserHeader.addressBar.text = ensAddr ? web3Provider.replaceHostByENS(currentWebView.url, ensAddr) : currentWebView.url;
}
onCurrentWebViewChanged: {
findBar.reset();
obtainAddress();
}
Action {
shortcut: "Ctrl+D"
onTriggered: {
addNewDownloadTab()
}
}
function addNewDownloadTab() {
tabs.createDownloadTab(tabs.count !== 0 ? currentWebView.profile : defaultProfile);
tabs.currentIndex = tabs.count - 1;
}
Action {
id: focus
shortcut: "Ctrl+L"
onTriggered: {
browserHeader.addressBar.forceActiveFocus();
browserHeader.addressBar.selectAll();
}
}
Action {
shortcut: StandardKey.Refresh
onTriggered: {
if (currentWebView)
currentWebView.reload();
}
}
2020-10-08 18:36:44 +00:00
function addNewTab() {
tabs.createEmptyTab(tabs.count !== 0 ? currentWebView.profile : defaultProfile);
2020-10-08 18:36:44 +00:00
tabs.currentIndex = tabs.count - 1;
browserHeader.addressBar.forceActiveFocus();
browserHeader.addressBar.selectAll();
}
Action {
shortcut: "Ctrl+W"
onTriggered: {
tabs.removeView(tabs.currentIndex)
}
}
2020-10-09 17:56:42 +00:00
Action {
shortcut: StandardKey.Close
onTriggered: {
currentWebView.triggerWebAction(WebEngineView.RequestClose);
}
}
Action {
shortcut: "Escape"
onTriggered: {
if (findBar.visible)
findBar.visible = false;
}
}
2020-10-09 17:56:42 +00:00
Action {
shortcut: StandardKey.Copy
onTriggered: currentWebView.triggerWebAction(WebEngineView.Copy)
}
Action {
shortcut: StandardKey.Cut
onTriggered: currentWebView.triggerWebAction(WebEngineView.Cut)
}
Action {
shortcut: StandardKey.Paste
onTriggered: currentWebView.triggerWebAction(WebEngineView.Paste)
}
Action {
shortcut: "Shift+"+StandardKey.Paste
onTriggered: currentWebView.triggerWebAction(WebEngineView.PasteAndMatchStyle)
}
Action {
shortcut: StandardKey.SelectAll
onTriggered: currentWebView.triggerWebAction(WebEngineView.SelectAll)
}
Action {
shortcut: StandardKey.Undo
onTriggered: currentWebView.triggerWebAction(WebEngineView.Undo)
}
Action {
shortcut: StandardKey.Redo
onTriggered: currentWebView.triggerWebAction(WebEngineView.Redo)
}
Action {
shortcut: StandardKey.Back
onTriggered: currentWebView.triggerWebAction(WebEngineView.Back)
}
Action {
shortcut: StandardKey.Forward
onTriggered: currentWebView.triggerWebAction(WebEngineView.Forward)
}
Action {
shortcut: StandardKey.FindNext
onTriggered: findBar.findNext()
}
Action {
shortcut: StandardKey.FindPrevious
onTriggered: findBar.findPrevious()
}
BrowserHeader {
id: browserHeader
anchors.top: parent.top
anchors.topMargin: tabs.tabHeight + tabs.anchors.topMargin
z: 52
2020-10-09 17:56:42 +00:00
addNewTab: browserWindow.addNewTab
}
QQC1.TabView {
property int tabHeight: 40
id: tabs
function createEmptyTab(profile, createAsStartPage) {
var tab = addTab("", tabComponent);
// We must do this first to make sure that tab.active gets set so that tab.item gets instantiated immediately.
tab.active = true;
createAsStartPage = createAsStartPage || tabs.count === 1
tab.title = Qt.binding(function() {
if (tab.item.title) {
return tab.item.title
}
if (createAsStartPage) {
2021-02-18 16:36:05 +00:00
//% "Start Page"
return qsTrId("start-page")
}
2021-02-18 16:36:05 +00:00
//% "New Tab"
return qsTrId("new-tab")
})
if (createAsStartPage) {
tab.item.url = "https://dap.ps"
}
tab.item.profile = profile;
if (appSettings.browserHomepage !== "") {
tab.item.url = appSettings.browserHomepage
}
return tab;
}
function createDownloadTab(profile) {
var tab = addTab("", tabComponent);
tab.active = true;
2021-02-18 16:36:05 +00:00
//% "Downloads Page"
tab.title = qsTrId("downloads-page")
tab.item.profile = profile
tab.item.url = "status://downloads";
}
function indexOfView(view) {
for (let i = 0; i < tabs.count; ++i)
if (tabs.getTab(i).item === view)
return i
return -1
}
function removeView(index) {
if (tabs.count === 1) {
tabs.createEmptyTab(currentWebView.profile, true)
}
tabs.removeTab(index)
}
z: 50
anchors.top: parent.top
anchors.topMargin: Style.current.halfPadding
anchors.bottom: devToolsView.top
anchors.bottomMargin: browserHeader.height
anchors.left: parent.left
anchors.right: parent.right
Component.onCompleted: {
2020-10-19 20:16:02 +00:00
defaultProfile.downloadRequested.connect(onDownloadRequested);
otrProfile.downloadRequested.connect(onDownloadRequested);
var tab = createEmptyTab(defaultProfile);
// For Devs: Uncomment the next lien if you want to use the simpeldapp on first load
// tab.item.url = determineRealURL("https://simpledapp.eth");
}
style: BrowserTabStyle {}
Component {
id: tabComponent
WebEngineView {
id: webEngineView
anchors.top: parent.top
2020-10-08 18:36:44 +00:00
anchors.topMargin: browserHeader.height
focus: true
2021-07-05 12:34:56 +00:00
url: "https://dap.ps"
webChannel: channel
onLinkHovered: function(hoveredUrl) {
if (hoveredUrl === "")
hideStatusText.start();
else {
statusText.text = hoveredUrl;
statusBubble.visible = true;
hideStatusText.stop();
}
}
2020-10-09 17:56:42 +00:00
function changeZoomFactor(newFactor) {
// FIXME there seems to be a bug in the WebEngine where the zoomFactor only update 1/2 times
zoomFactor = newFactor
zoomFactor = newFactor
zoomFactor = newFactor
}
settings.autoLoadImages: appSettings.autoLoadImages
settings.javascriptEnabled: appSettings.javaScriptEnabled
settings.errorPageEnabled: appSettings.errorPageEnabled
settings.pluginsEnabled: appSettings.pluginsEnabled
settings.autoLoadIconsForPage: appSettings.autoLoadIconsForPage
settings.touchIconsEnabled: appSettings.touchIconsEnabled
settings.webRTCPublicInterfacesOnly: appSettings.webRTCPublicInterfacesOnly
settings.pdfViewerEnabled: appSettings.pdfViewerEnabled
settings.focusOnNavigationEnabled: true
onCertificateError: function(error) {
error.defer();
sslDialog.enqueue(error);
}
2020-11-16 16:44:23 +00:00
onJavaScriptDialogRequested: function(request) {
request.accepted = true;
var dialog = jsDialogComponent.createObject(browserWindow, {"request": request});
dialog.open();
}
onNewViewRequested: function(request) {
2020-10-08 20:08:20 +00:00
if (!request.userInitiated) {
print("Warning: Blocked a popup window.");
2020-10-08 20:08:20 +00:00
} else if (request.destination === WebEngineView.NewViewInTab) {
var tab = tabs.createEmptyTab(currentWebView.profile);
tabs.currentIndex = tabs.count - 1;
request.openIn(tab.item);
} else if (request.destination === WebEngineView.NewViewInBackgroundTab) {
var backgroundTab = tabs.createEmptyTab(currentWebView.profile);
request.openIn(backgroundTab.item);
// Disabling popups temporarily since we need to set that webengineview settings / channel and other properties
2020-12-02 15:48:04 +00:00
/*} else if (request.destination === WebEngineView.NewViewInDialog) {
var dialog = browserDialogComponent.createObject();
dialog.currentWebView.profile = currentWebView.profile;
2020-12-02 15:48:04 +00:00
dialog.currentWebView.webChannel = channel;
request.openIn(dialog.currentWebView);*/
} else {
// Instead of opening a new window, we open a new tab
// TODO: remove "open in new window" from context menu
var tab = tabs.createEmptyTab(currentWebView.profile);
tabs.currentIndex = tabs.count - 1;
request.openIn(tab.item);
}
}
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;
}
print("Render process exited with code " + exitCode + " " + status);
reloadTimer.running = true;
}
onWindowCloseRequested: tabs.removeView(tabs.indexOfView(webEngineView))
onSelectClientCertificate: function(selection) {
selection.certificates[0].select();
}
onFindTextFinished: function(result) {
if (!findBar.visible)
findBar.visible = true;
findBar.numberOfMatches = result.numberOfMatches;
findBar.activeMatch = result.activeMatch;
}
onLoadingChanged: function(loadRequest) {
if (loadRequest.status === WebEngineView.LoadStartedStatus)
findBar.reset();
}
2020-12-02 15:48:04 +00:00
onNavigationRequested: {
if(request.url.toString().startsWith("file://")){
console.log("Local file browsing is disabled" )
request.action = WebEngineNavigationRequest.IgnoreRequest;
}
}
Loader {
active: webEngineView.url.toString() === "status://downloads"
width: parent.width
height: parent.height
z: 54
sourceComponent: DownloadView {
id: downloadView
}
}
2020-10-08 20:08:20 +00:00
Loader {
active: !webEngineView.url.toString()
width: parent.width
height: parent.height
z: 54
sourceComponent: Item {
width: parent.width
height: parent.height
Image {
id: emptyPageImage
source: "../../img/browser/compass.png"
width: 294
height: 294
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
anchors.topMargin: 60
}
FavoritesList {
2020-10-29 20:07:52 +00:00
id: bookmarkListContainer
2020-10-08 20:08:20 +00:00
anchors.horizontalCenter: emptyPageImage.horizontalCenter
anchors.top: emptyPageImage.bottom
anchors.topMargin: 30
width: (parent.width < 700) ? (Math.floor(parent.width/cellWidth)*cellWidth) : 700
height: parent.height - emptyPageImage.height - 20
2020-10-08 20:08:20 +00:00
}
}
}
Timer {
id: reloadTimer
interval: 0
running: false
repeat: false
onTriggered: currentWebView.reload()
}
}
}
}
Connections {
target: currentWebView
onUrlChanged: obtainAddress()
}
ProgressBar {
id: progressBar
height: 3
from: 0
to: 100
visible: value != 0 && value != 100
value: (currentWebView && currentWebView.loadProgress < 100) ? currentWebView.loadProgress : 0
anchors.bottom: parent.bottom
anchors.bottomMargin: Style.current.padding
anchors.right: parent.right
anchors.rightMargin: Style.current.padding
}
WebEngineView {
id: devToolsView
visible: appSettings.devToolsEnabled
height: visible ? 400 : 0
inspectedView: visible && tabs.currentIndex < tabs.count ? tabs.getTab(tabs.currentIndex).item : null
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
onNewViewRequested: function(request) {
var tab = tabs.createEmptyTab(currentWebView.profile);
tabs.currentIndex = tabs.count - 1;
request.openIn(tab.item);
}
z: 100
}
2020-10-19 20:16:02 +00:00
function onDownloadRequested(download) {
downloadBar.isVisible = true
2020-10-19 20:16:02 +00:00
download.accept();
downloadModel.append(download);
downloadModel.downloads.push(download);
2020-10-19 20:16:02 +00:00
}
MessageDialog {
id: sslDialog
property var certErrors: []
icon: StandardIcon.Warning
standardButtons: StandardButton.No | StandardButton.Yes
2021-02-18 16:36:05 +00:00
//% "Server's certificate not trusted"
title: qsTrId("server-s-certificate-not-trusted")
//% "Do you wish to continue?"
text: qsTrId("do-you-wish-to-continue-")
//% "If you wish so, you may continue with an unverified certificate. Accepting an unverified certificate means you may not be connected with the host you tried to connect to.\nDo you wish to override the security check and continue?"
detailedText: qsTrId("if-you-wish-so--you-may-continue-with-an-unverified-certificate--accepting-an-unverified-certificate-means-you-may-not-be-connected-with-the-host-you-tried-to-connect-to--ndo-you-wish-to-override-the-security-check-and-continue-")
onYes: {
certErrors.shift().ignoreCertificateError();
presentError();
}
onNo: reject()
onRejected: reject()
function reject(){
certErrors.shift().rejectCertificate();
presentError();
}
function enqueue(error){
certErrors.push(error);
presentError();
}
function presentError(){
visible = certErrors.length > 0
}
}
DownloadBar {
id: downloadBar
anchors.bottom: parent.bottom
z: 60
}
FindBar {
id: findBar
visible: false
anchors.right: parent.right
2020-10-09 17:56:42 +00:00
anchors.top: browserHeader.bottom
z: 60
onFindNext: {
if (text)
currentWebView && currentWebView.findText(text);
else if (!visible)
visible = true;
}
onFindPrevious: {
if (text)
currentWebView && currentWebView.findText(text, WebEngineView.FindBackward);
else if (!visible)
visible = true;
}
}
Rectangle {
id: statusBubble
color: "oldlace"
property int padding: 8
visible: false
anchors.left: parent.left
anchors.bottom: parent.bottom
width: statusText.paintedWidth + padding
height: statusText.paintedHeight + padding
Text {
id: statusText
anchors.centerIn: statusBubble
elide: Qt.ElideMiddle
Timer {
id: hideStatusText
interval: 750
onTriggered: {
statusText.text = "";
statusBubble.visible = false;
}
}
}
2020-09-22 15:12:48 +00:00
}
}
/*##^##
Designer {
D{i:0;autoSize:true;height:480;width:640}
}
##^##*/