Warning, /network/angelfish/lib/contents/ui/WebView.qml is written in an unsupported language. File is not indexed.

0001 // SPDX-FileCopyrightText: 2014-2015 Sebastian Kügler <sebas@kde.org>
0002 //
0003 // SPDX-License-Identifier: GPL-2.0-or-later
0004 
0005 import QtQuick 2.15
0006 import QtQuick.Controls 2.15 as QQC2
0007 import QtQuick.Layouts 1.15
0008 import QtWebEngine
0009 
0010 import org.kde.kirigami 2.19 as Kirigami
0011 
0012 import org.kde.angelfish 1.0
0013 
0014 WebEngineView {
0015     id: webEngineView
0016 
0017     property string errorCode: ""
0018     property string errorString: ""
0019 
0020     property bool privateMode: false
0021 
0022     property alias userAgent: userAgent
0023 
0024     // loadingActive property is set to true when loading is started
0025     // and turned to false only after succesful or failed loading. It
0026     // is possible to set it to false by calling stopLoading method.
0027     //
0028     // The property was introduced as it triggers visibility of the webEngineView
0029     // in the other parts of the code. When using loading that is linked
0030     // to visibility, stop/start loading was observed in some conditions. It looked as if
0031     // there is an internal optimization of webengine in the case of parallel
0032     // loading of several pages that could use visibility as one of the decision
0033     // making parameters.
0034     property bool loadingActive: false
0035 
0036     // reloadOnVisible property ensures that the view has been always
0037     // loaded at least once while it is visible. When the view is loaded
0038     // while visible is set to false, there, what appears to be Chromium
0039     // optimizations that can disturb the loading.
0040     property bool reloadOnVisible: true
0041 
0042     // Profiles of WebViews are shared among all views of the same type (regular or
0043     // private). However, within each group of tabs, we can have some tabs that are
0044     // using mobile or desktop user agent. To avoid loading a page with the wrong
0045     // user agent, the agent is checked in the beginning of the loading at onLoadingChanged
0046     // handler. If the user agent is wrong, loading is stopped and reloadOnMatchingAgents
0047     // property is set to true. As soon as the agent is correct, the page is loaded.
0048     property bool reloadOnMatchingAgents: false
0049 
0050     // Used to follow whether agents match
0051     property bool agentsMatch: profile.httpUserAgent === userAgent.userAgent
0052 
0053     // URL that was requested and should be used
0054     // as a base for user interaction. It reflects
0055     // last request (successful or failed)
0056     property url requestedUrl: url
0057 
0058     property int findInPageResultIndex
0059     property int findInPageResultCount
0060 
0061     // Used to hide certain context menu items
0062     property bool isAppView: false
0063 
0064     // Used to track reader mode switch
0065     property bool readerMode: false
0066 
0067     // url to keep last url to return from reader mode
0068     property url readerSourceUrl
0069 
0070     // string to keep last title to return from reader mode
0071     property string readerTitle
0072 
0073     // Used for pdf generated to preview before print
0074     property url printPreviewUrl: ""
0075     property bool generatingPdf: false
0076     property int printedPageOrientation: WebEngineView.Portrait
0077     property int printedPageSizeId: WebEngineView.A4
0078 
0079     Shortcut {
0080         enabled: webEngineView.isFullScreen
0081         sequence: "Esc"
0082         onActivated: webEngineView.fullScreenCancelled();
0083     }
0084 
0085     // helper function to apply DomDistiller
0086     function readerDistillerRun() {
0087         readerSourceUrl = url
0088         runJavaScript(DomDistiller.script, function() {
0089                 runJavaScript(DomDistiller.applyScript, function(result) {
0090                     loadHtml(result[2][1])
0091                     readerTitle = result[1]
0092                 })
0093         })
0094     }
0095 
0096     // method to switch reader mode
0097     function readerModeSwitch() {
0098         if (readerMode) {
0099             url = readerSourceUrl
0100         } else {
0101             readerDistillerRun()
0102         }
0103     }
0104 
0105     UserAgentGenerator {
0106         id: userAgent
0107         onUserAgentChanged: webEngineView.reload()
0108     }
0109 
0110     settings {
0111         autoLoadImages: Settings.webAutoLoadImages
0112         javascriptEnabled: Settings.webJavaScriptEnabled
0113         // Disable builtin error pages in favor of our own
0114         errorPageEnabled: false
0115         // Load larger touch icons
0116         touchIconsEnabled: true
0117         // Disable scrollbars on mobile
0118         showScrollBars: !Kirigami.Settings.isMobile
0119         // Generally allow screen sharing, still needs permission from the user
0120         screenCaptureEnabled: true
0121         // Enables a web page to request that one of its HTML elements be made to occupy the user's entire screen
0122         fullScreenSupportEnabled: true
0123         // Turns on printing of CSS backgrounds when printing a web page
0124         printElementBackgrounds: false
0125     }
0126 
0127     focus: true
0128     onLoadingChanged: loadRequest => {
0129         //print("Loading: " + loading);
0130         print("    url: " + loadRequest.url + " " + loadRequest.status)
0131         //print("   icon: " + webEngineView.icon)
0132         //print("  title: " + webEngineView.title)
0133 
0134         /* Handle
0135         *  - WebEngineView::LoadStartedStatus,
0136         *  - WebEngineView::LoadStoppedStatus,
0137         *  - WebEngineView::LoadSucceededStatus and
0138         *  - WebEngineView::LoadFailedStatus
0139         */
0140         var ec = "";
0141         var es = "";
0142         if (loadRequest.status === WebEngineView.LoadStartedStatus) {
0143             if (profile.httpUserAgent !== userAgent.userAgent) {
0144                 //print("Mismatch of user agents, will load later " + loadRequest.url);
0145                 reloadOnMatchingAgents = true;
0146                 stopLoading();
0147             } else {
0148                 loadingActive = true;
0149             }
0150         }
0151         if (loadRequest.status === WebEngineView.LoadSucceededStatus) {
0152             if (!privateMode) {
0153                 const request = {
0154                     url: currentWebView.url,
0155                     title: currentWebView.title,
0156                     icon: currentWebView.icon
0157                 }
0158 
0159                 BrowserManager.addToHistory(request);
0160                 BrowserManager.updateLastVisited(currentWebView.url);
0161             }
0162 
0163             if (typeof AdblockUrlInterceptor === "undefined" || !AdblockUrlInterceptor.adblockSupported) {
0164                 return;
0165             }
0166 
0167             let script = AdblockUrlInterceptor.getInjectedScript(webEngineView.url)
0168             if (script !== "") {
0169                 webEngineView.runJavaScript(script)
0170             }
0171 
0172             webEngineView.runJavaScript(
0173 `var elements = document.querySelectorAll("*[id]");
0174 var ids = [];
0175 for (var i in elements) {
0176     if (elements[i].id) {
0177         ids.push(elements[i].id)
0178     }
0179 }
0180 ids
0181 `, (ids) => {
0182                 webEngineView.runJavaScript(
0183 `var elements = document.querySelectorAll("*[class]");
0184 var classes = [];
0185 for (var i in elements) {
0186     if (elements[i].className) {
0187         classes.push(elements[i].className);
0188     }
0189 }
0190 classes
0191 `, (classes) => {
0192                     let selectors = AdblockUrlInterceptor.getCosmeticFilters(webEngineView.url, classes, ids)
0193 
0194                     for (var i = 0; i < selectors.length; i++) {
0195                         webEngineView.runJavaScript(
0196 `{
0197     let adblockStyleElement = document.createElement("style")
0198     adblockStyleElement.type = "text/css"
0199     adblockStyleElement.textContent = '${selectors[i]} { display: none !important; }'
0200     document.head.appendChild(adblockStyleElement);
0201 }`)
0202                     }
0203                 })
0204             })
0205             loadingActive = false;
0206         }
0207         if (loadRequest.status === WebEngineView.LoadFailedStatus) {
0208             print("Load failed: " + loadRequest.errorCode + " " + loadRequest.errorString);
0209             print("Load failed url: " + loadRequest.url + " " + url);
0210             ec = loadRequest.errorCode;
0211             es = loadRequest.errorString;
0212             loadingActive = false;
0213 
0214             // update requested URL only after its clear that it fails.
0215             // Otherwise, its updated as a part of url property update.
0216             if (requestedUrl !== loadRequest.url)
0217                 requestedUrl = loadRequest.url;
0218         }
0219         errorCode = ec;
0220         errorString = es;
0221     }
0222 
0223     Component.onCompleted: {
0224         print("WebView completed.");
0225         print("Settings: " + webEngineView.settings);
0226     }
0227 
0228     onIconChanged: {
0229         if (icon && !privateMode) {
0230             BrowserManager.updateIcon(url, icon)
0231         }
0232     }
0233     onNewWindowRequested: request => {
0234         if (request.userInitiated) {
0235             tabsModel.newTab(request.requestedUrl.toString())
0236             showPassiveNotification(i18n("Website was opened in a new tab"))
0237         } else {
0238             questionLoader.setSource("NewTabQuestion.qml")
0239             questionLoader.item.url = request.requestedUrl
0240             questionLoader.item.visible = true
0241         }
0242     }
0243     onUrlChanged: {
0244         if (requestedUrl !== url) {
0245             requestedUrl = url;
0246             // poor heuristics to update readerMode accordingly:
0247             readerMode = url.toString().startsWith("data:text/html")
0248         }
0249     }
0250 
0251     onFullScreenRequested: request => {
0252         if (request.toggleOn) {
0253             webBrowser.showFullScreen()
0254             const message = i18n("Entered Full Screen Mode")
0255             const actionText = i18n("Exit Full Screen (Esc)")
0256             showPassiveNotification(message, "short", actionText, function() { webEngineView.fullScreenCancelled() });
0257         } else {
0258             webBrowser.showNormal()
0259         }
0260 
0261         request.accept()
0262     }
0263 
0264     onContextMenuRequested: request => {
0265         request.accepted = true // Make sure QtWebEngine doesn't show its own context menu.
0266         contextMenu.request = request
0267         contextMenu.x = request.position.x
0268         contextMenu.y = request.position.y
0269         contextMenu.open()
0270     }
0271 
0272     onAuthenticationDialogRequested: request => {
0273         request.accepted = true
0274         sheetLoader.setSource("AuthSheet.qml")
0275         sheetLoader.item.request = request
0276         sheetLoader.item.open()
0277     }
0278 
0279     onFeaturePermissionRequested: (securityOrigin, feature) => {
0280         let newQuestion = rootPage.questions.newPermissionQuestion()
0281         newQuestion.permission = feature
0282         newQuestion.origin = securityOrigin
0283         newQuestion.visible = true
0284     }
0285 
0286     onJavaScriptDialogRequested: request => {
0287         request.accepted = true;
0288         sheetLoader.setSource("JavaScriptDialogSheet.qml");
0289         sheetLoader.item.request = request;
0290         sheetLoader.item.open();
0291     }
0292 
0293     onFindTextFinished: result => {
0294         findInPageResultIndex = result.activeMatch;
0295         findInPageResultCount = result.numberOfMatches;
0296     }
0297 
0298     onVisibleChanged: {
0299         if (visible && reloadOnVisible) {
0300             // see description of reloadOnVisible above for reasoning
0301             reloadOnVisible = false;
0302             reload();
0303         }
0304     }
0305 
0306     onAgentsMatchChanged: {
0307         if (agentsMatch && reloadOnMatchingAgents) {
0308             // see description of reloadOnMatchingAgents above for reasoning
0309             reloadOnMatchingAgents = false;
0310             reload();
0311         }
0312     }
0313 
0314     onCertificateError: error => {
0315         error.defer();
0316         errorHandler.enqueue(error);
0317     }
0318 
0319     function findInPageForward(text) {
0320         findText(text);
0321     }
0322 
0323     function stopLoading() {
0324         loadingActive = false;
0325         stop();
0326     }
0327 
0328     onPrintRequested: {
0329         printPreviewUrl = "";
0330         generatingPdf = true;
0331         const filePath = BrowserManager.tempDirectory() + "/print-preview.pdf";
0332         printToPdf(filePath, printedPageSizeId, printedPageOrientation);
0333 
0334         if (!printPreview.sheetOpen) {
0335             printPreview.open();
0336         }
0337     }
0338 
0339     onPdfPrintingFinished: (filePath, success) => {
0340         generatingPdf = false;
0341         printPreviewUrl = "file://" + filePath + "#toolbar=0&view=Fit";
0342     }
0343 
0344     PrintPreview {
0345         id: printPreview
0346     }
0347 
0348     onLinkHovered: hoveredUrl => hoveredLink.text = hoveredUrl
0349 
0350     QQC2.Label {
0351         id: hoveredLink
0352         visible: text.length > 0
0353         z: 2
0354         anchors.bottom: parent.bottom
0355         anchors.left: parent.left
0356         leftPadding: Kirigami.Units.smallSpacing
0357         rightPadding: Kirigami.Units.smallSpacing
0358         color: Kirigami.Theme.textColor
0359         font.pointSize: Kirigami.Theme.defaultFont.pointSize - 1
0360 
0361         background: Rectangle {
0362             anchors.fill: parent
0363             color: Kirigami.Theme.backgroundColor
0364         }
0365     }
0366 
0367     QQC2.Menu {
0368         id: contextMenu
0369         property ContextMenuRequest request
0370         property bool isValidUrl: contextMenu.request && contextMenu.request.linkUrl != "" // not strict equality
0371         property bool isAudio: contextMenu.request && contextMenu.request.mediaType === ContextMenuRequest.MediaTypeAudio
0372         property bool isImage: contextMenu.request && contextMenu.request.mediaType === ContextMenuRequest.MediaTypeImage
0373         property bool isVideo: contextMenu.request && contextMenu.request.mediaType === ContextMenuRequest.MediaTypeVideo
0374         property real playbackRate: 100
0375 
0376         onAboutToShow: {
0377             if (webEngineView.settings.javascriptEnabled && (contextMenu.isAudio || contextMenu.isVideo)) {
0378                 const point = contextMenu.request.x + ', ' + contextMenu.request.y
0379                 const js = 'document.elementFromPoint(' + point + ').playbackRate * 100;'
0380                 webEngineView.runJavaScript(js, function(result) { contextMenu.playbackRate = result })
0381             }
0382         }
0383 
0384         QQC2.MenuItem {
0385             visible: contextMenu.isAudio || contextMenu.isVideo
0386             height: visible ? implicitHeight : 0
0387             text: contextMenu.request && contextMenu.request.mediaFlags & ContextMenuRequest.MediaPaused
0388             ? i18n("Play")
0389             : i18n("Pause")
0390             onTriggered: webEngineView.triggerWebAction(WebEngineView.ToggleMediaPlayPause)
0391         }
0392         QQC2.MenuItem {
0393             visible: contextMenu.request && contextMenu.request.mediaFlags & ContextMenuRequest.MediaHasAudio
0394             height: visible ? implicitHeight : 0
0395             text:  contextMenu.request && contextMenu.request.mediaFlags & ContextMenuRequest.MediaMuted
0396             ? i18n("Unmute")
0397             : i18n("Mute")
0398             onTriggered: webEngineView.triggerWebAction(WebEngineView.ToggleMediaMute)
0399         }
0400         QQC2.MenuItem {
0401             visible: webEngineView.settings.javascriptEnabled && (contextMenu.isAudio || contextMenu.isVideo)
0402             height: visible ? implicitHeight : 0
0403             contentItem: RowLayout {
0404                 QQC2.Label {
0405                     Layout.leftMargin: Kirigami.Units.largeSpacing
0406                     Layout.fillWidth: true
0407                     text: i18n("Speed")
0408                 }
0409                 QQC2.SpinBox {
0410                     Layout.rightMargin: Kirigami.Units.largeSpacing
0411                     value: contextMenu.playbackRate
0412                     from: 25
0413                     to: 1000
0414                     stepSize: 25
0415                     onValueModified: {
0416                         contextMenu.playbackRate = value
0417                         const point = contextMenu.request.x + ', ' + contextMenu.request.y
0418                         const js = 'document.elementFromPoint(' + point + ').playbackRate = ' + contextMenu.playbackRate / 100 + ';'
0419                         webEngineView.runJavaScript(js)
0420                     }
0421                     textFromValue: function(value, locale) {
0422                         return Number(value / 100).toLocaleString(locale, 'f', 2)
0423                     }
0424                 }
0425             }
0426         }
0427         QQC2.MenuItem {
0428             visible: contextMenu.isAudio || contextMenu.isVideo
0429             height: visible ? implicitHeight : 0
0430             text: i18n("Loop")
0431             checked: contextMenu.request && contextMenu.request.mediaFlags & ContextMenuRequest.MediaLoop
0432             onTriggered: webEngineView.triggerWebAction(WebEngineView.ToggleMediaLoop)
0433         }
0434         QQC2.MenuItem {
0435             visible: webEngineView.settings.javascriptEnabled && contextMenu.isVideo
0436             height: visible ? implicitHeight : 0
0437             text: webEngineView.isFullScreen ? i18n("Exit fullscreen") : i18n("Fullscreen")
0438             onTriggered: {
0439                 const point = contextMenu.request.x + ', ' + contextMenu.request.y
0440                 const js = webEngineView.isFullScreen
0441                     ? 'document.exitFullscreen()'
0442                     : 'document.elementFromPoint(' + point + ').requestFullscreen()'
0443                 webEngineView.runJavaScript(js)
0444             }
0445         }
0446         QQC2.MenuItem {
0447             visible: webEngineView.settings.javascriptEnabled && (contextMenu.isAudio || contextMenu.isVideo)
0448             height: visible ? implicitHeight : 0
0449             text: contextMenu.request && contextMenu.request.mediaFlags & ContextMenuRequest.MediaControls
0450             ? i18n("Hide controls")
0451             : i18n("Show controls")
0452             onTriggered: webEngineView.triggerWebAction(WebEngineView.ToggleMediaControls)
0453         }
0454         QQC2.MenuSeparator { visible: contextMenu.isAudio || contextMenu.isVideo }
0455         QQC2.MenuItem {
0456             visible: (contextMenu.isAudio || contextMenu.isVideo) && contextMenu.request.mediaUrl !== currentWebView.url
0457             height: visible ? implicitHeight : 0
0458             text: webEngineView.isAppView
0459                 ? contextMenu.isVideo ? i18n("Open video") : i18n("Open audio")
0460                 : contextMenu.isVideo ? i18n("Open video in new Tab") : i18n("Open audio in new Tab")
0461             onTriggered: {
0462                 if (webEngineView.isAppView) {
0463                     Qt.openUrlExternally(contextMenu.request.mediaUrl);
0464                 } else {
0465                     tabsModel.newTab(contextMenu.request.mediaUrl)
0466                 }
0467             }
0468         }
0469         QQC2.MenuItem {
0470             visible: contextMenu.isVideo
0471             height: visible ? implicitHeight : 0
0472             text: i18n("Save video")
0473             onTriggered: webEngineView.triggerWebAction(WebEngineView.DownloadMediaToDisk)
0474         }
0475         QQC2.MenuItem {
0476             visible: contextMenu.isVideo
0477             height: visible ? implicitHeight : 0
0478             text: i18n("Copy video Link")
0479             onTriggered: webEngineView.triggerWebAction(WebEngineView.CopyMediaUrlToClipboard)
0480         }
0481         QQC2.MenuItem {
0482             visible: contextMenu.isImage && contextMenu.request.mediaUrl !== currentWebView.url
0483             height: visible ? implicitHeight : 0
0484             text: webEngineView.isAppView ? i18n("Open image") : i18n("Open image in new Tab")
0485             onTriggered: {
0486                 if (webEngineView.isAppView) {
0487                     Qt.openUrlExternally(contextMenu.request.mediaUrl);
0488                 } else {
0489                     tabsModel.newTab(contextMenu.request.mediaUrl)
0490                 }
0491             }
0492         }
0493         QQC2.MenuItem {
0494             visible: contextMenu.isImage
0495             height: visible ? implicitHeight : 0
0496             text: i18n("Save image")
0497             onTriggered: webEngineView.triggerWebAction(WebEngineView.DownloadImageToDisk)
0498         }
0499         QQC2.MenuItem {
0500             visible: contextMenu.isImage
0501             height: visible ? implicitHeight : 0
0502             text: i18n("Copy image")
0503             onTriggered: webEngineView.triggerWebAction(WebEngineView.CopyImageToClipboard)
0504         }
0505         QQC2.MenuItem {
0506             visible: contextMenu.isImage
0507             height: visible ? implicitHeight : 0
0508             text: i18n("Copy image link")
0509             onTriggered: webEngineView.triggerWebAction(WebEngineView.CopyImageUrlToClipboard)
0510         }
0511         QQC2.MenuItem {
0512             visible: contextMenu.request && contextMenu.isValidUrl
0513             height: visible ? implicitHeight : 0
0514             text: webEngineView.isAppView ? i18n("Open link") : i18n("Open link in new Tab")
0515             onTriggered: {
0516                 if (webEngineView.isAppView) {
0517                     Qt.openUrlExternally(contextMenu.request.linkUrl);
0518                 } else {
0519                     webEngineView.triggerWebAction(WebEngineView.OpenLinkInNewTab)
0520                 }
0521             }
0522         }
0523         QQC2.MenuItem {
0524             visible: contextMenu.request && contextMenu.isValidUrl
0525             height: visible ? implicitHeight : 0
0526             text: i18n("Bookmark link")
0527             onTriggered: {
0528                 const bookmark = {
0529                     url: contextMenu.request.linkUrl,
0530                     title: contextMenu.request.linkText
0531                 }
0532                 BrowserManager.addBookmark(bookmark)
0533             }
0534         }
0535         QQC2.MenuItem {
0536             visible: contextMenu.request && contextMenu.isValidUrl
0537             height: visible ? implicitHeight : 0
0538             text: i18n("Save link")
0539             onTriggered: webEngineView.triggerWebAction(WebEngineView.DownloadLinkToDisk)
0540         }
0541         QQC2.MenuItem {
0542             visible: contextMenu.request && contextMenu.isValidUrl
0543             height: visible ? implicitHeight : 0
0544             text: i18n("Copy link")
0545             onTriggered: webEngineView.triggerWebAction(WebEngineView.CopyLinkToClipboard)
0546         }
0547         QQC2.MenuSeparator { visible: contextMenu.request && contextMenu.isValidUrl }
0548         QQC2.MenuItem {
0549             visible: contextMenu.request && (contextMenu.request.editFlags & ContextMenuRequest.CanCopy) && contextMenu.request.mediaUrl == ""
0550             height: visible ? implicitHeight : 0
0551             text: i18n("Copy")
0552             onTriggered: webEngineView.triggerWebAction(WebEngineView.Copy)
0553         }
0554         QQC2.MenuItem {
0555             visible: contextMenu.request && (contextMenu.request.editFlags & ContextMenuRequest.CanCut)
0556             height: visible ? implicitHeight : 0
0557             text: i18n("Cut")
0558             onTriggered: webEngineView.triggerWebAction(WebEngineView.Cut)
0559         }
0560         QQC2.MenuItem {
0561             visible: contextMenu.request && (contextMenu.request.editFlags & ContextMenuRequest.CanPaste)
0562             height: visible ? implicitHeight : 0
0563             text: i18n("Paste")
0564             onTriggered: webEngineView.triggerWebAction(WebEngineView.Paste)
0565         }
0566         QQC2.MenuItem {
0567             property string fullText: contextMenu.request ? contextMenu.request.selectedText || contextMenu.request.linkText : ""
0568             property string elidedText: fullText.length > 25 ? fullText.slice(0, 25) + "..." : fullText
0569             visible: contextMenu.request && fullText
0570             height: visible ? implicitHeight : 0
0571             text: contextMenu.request && fullText ? i18n('Search for "%1"', elidedText) : ""
0572             onTriggered: {
0573                 if (webEngineView.isAppView) {
0574                     Qt.openUrlExternally(UrlUtils.urlFromUserInput(Settings.searchBaseUrl + fullText));
0575                 } else {
0576                     tabsModel.newTab(UrlUtils.urlFromUserInput(Settings.searchBaseUrl + fullText));
0577                 }
0578             }
0579         }
0580         QQC2.MenuSeparator { visible: !webEngineView.isAppView && contextMenu.request && contextMenu.request.mediaUrl != "" && !contextMenu.isValidUrl }
0581         QQC2.MenuItem {
0582             visible: !webEngineView.isAppView && contextMenu.request && contextMenu.request.selectedText === ""
0583             height: visible ? implicitHeight : 0
0584             text: i18n("Share page")
0585             onTriggered: {
0586                 sheetLoader.setSource("ShareSheet.qml")
0587                 sheetLoader.item.url = currentWebView.url
0588                 sheetLoader.item.inputTitle = currentWebView.title
0589                 Qt.callLater(sheetLoader.item.open)
0590             }
0591         }
0592         QQC2.MenuSeparator { visible: !webEngineView.isAppView }
0593         QQC2.MenuItem {
0594             visible: !webEngineView.isAppView
0595             height: visible ? implicitHeight : 0
0596             text: i18n("View page source")
0597             onTriggered: tabsModel.newTab("view-source:" + webEngineView.url)
0598         }
0599     }
0600 }