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 }