diff options
135 files changed, 3837 insertions, 714 deletions
diff --git a/config.tests/libvpx/libvpx.cpp b/config.tests/libvpx/libvpx.cpp index ec261cd5a..06c34476b 100644 --- a/config.tests/libvpx/libvpx.cpp +++ b/config.tests/libvpx/libvpx.cpp @@ -26,15 +26,16 @@ ** ****************************************************************************/ -#include <vpx/svc_context.h> -#include <vpx/vpx_frame_buffer.h> -#include <vpx/vp8dx.h> - -#ifndef VPX_CTRL_VPXD_GET_LAST_QUANTIZER -#error "This version of libvpx is too old, it is missing VPX_CTRL_VPXD_GET_LAST_QUANTIZER define" -#endif +#include <vpx/vpx_encoder.h> int main(int, char **) { + vpx_codec_cx_pkt pkt; + // Members added (as ints) by + // https://chromium-review.googlesource.com/c/webm/libvpx/+/798222 + // And changed to int arrays by + // https://chromium-review.googlesource.com/c/webm/libvpx/+/879089 + pkt.data.frame.width[0] = 0u; + pkt.data.frame.height[0] = 0u; return 0; } diff --git a/config.tests/libvpx/libvpx.pro b/config.tests/libvpx/libvpx.pro index aff6d1857..13ce13647 100644 --- a/config.tests/libvpx/libvpx.pro +++ b/config.tests/libvpx/libvpx.pro @@ -1,3 +1,4 @@ SOURCES += libvpx.cpp -PKGCONFIG += libvpx +CONFIG += link_pkgconfig +PKGCONFIG += vpx CONFIG -= qt diff --git a/config.tests/xml2/xml2.cpp b/config.tests/xml2/xml2.cpp index 93bc80c97..497653d9c 100644 --- a/config.tests/xml2/xml2.cpp +++ b/config.tests/xml2/xml2.cpp @@ -27,9 +27,6 @@ ****************************************************************************/ #include <libxml/xmlversion.h> -#if defined(LIBXML_CATALOG_ENABLED) -#error "libxml catalog enabled" -#endif #if !defined(LIBXML_ICU_ENABLED) #error "libxml icu not enabled" #endif diff --git a/dist/changes-5.11.2 b/dist/changes-5.11.2 new file mode 100644 index 000000000..753e83fa7 --- /dev/null +++ b/dist/changes-5.11.2 @@ -0,0 +1,78 @@ +Qt 5.11.2 is a bug-fix release. It maintains both forward and backward +compatibility (source and binary) with Qt 5.11.0 through 5.11.1. + +For more details, refer to the online documentation included in this +distribution. The documentation is also available online: + +http://doc.qt.io/qt-5/index.html + +The Qt version 5.11 series is binary compatible with the 5.10.x series. +Applications compiled for 5.10 will continue to run with 5.11. + +Some of the changes listed in this file include issue tracking numbers +corresponding to tasks in the Qt Bug Tracker: + +https://bugreports.qt.io/ + +Each of these identifiers can be entered in the bug tracker to obtain more +information about a particular change. + +**************************************************************************** +* Qt 5.11.2 Changes * +**************************************************************************** + +Chromium +-------- + + - Security fixes from Chromium up to version 68.0.3440.75, including: + * CVE-2018-4117 + * CVE-2018-6150 + * CVE-2018-6152 + * CVE-2018-6155 + * CVE-2018-6158 + * CVE-2018-6159 + * CVE-2018-6161 + * CVE-2018-6162 + * CVE-2018-6163 + * CVE-2018-6164 + * CVE-2018-6165 + * CVE-2018-6167 + * CVE-2018-6168 + * CVE-2018-6172 + * CVE-2018-6175 + * CVE-2018-6177 + * Security Bug 683418 + * Security Bug 831117 + * Security Bug 838886 + * Security Bug 839197 + * Security Bug 840695 + * Security Bug 854887 + * Security Bug 860721 + * Security Bug 861571 + + +General +------- + + * [QTBUG-59891] Popups are now closed when parent window is moved. + * [QTBUG-67801] Fixed assert with some AMD CPUs on Windows. + * [QTBUG-68699] Fixed another crash with non-integer Qt scaling. + * [QTBUG-69231] Fixed keyboard modifiers on drop events. + * [QTBUG-69236] Fixed --enable-webgl-software-rendering to ignore blacklist + of software OpenGL. + * [QTBUG-69359] Fixed crash in DevTools openInNewTab handling + * [QTBUG-69372] QWebEngineUrlRequestJob::initiator() now differs + between unique origins and unknown origins. + * [QTBUG-69605] HSTS state is now persistent with appropriate profiles. + * [QTBUG-69639] Work around MSVC compiler error affecting printing on + 32-bit Windows. + * [QTBUG-69904] JavaScript worldID can now be up to 256 (up from 11), + and the limit is now documented. + * [QTBUG-70034] Fixed qtwebengine_convert_dict failing on certain + dictionaries. + +Build +----- + + * [QTBUG-50554] Update libvpx check to match standard versions. + * [QTBUG-69121] Fixed shadow builds on read-only sources. diff --git a/examples/webengine/minimal/main.qml b/examples/webengine/minimal/main.qml index 5abc50069..75082f9c5 100644 --- a/examples/webengine/minimal/main.qml +++ b/examples/webengine/minimal/main.qml @@ -58,6 +58,6 @@ Window { visible: true WebEngineView { anchors.fill: parent - url: "http://www.qt.io" + url: "https://www.qt.io" } } diff --git a/examples/webengine/quicknanobrowser/BrowserWindow.qml b/examples/webengine/quicknanobrowser/BrowserWindow.qml index f60d6ad1f..7b8767b8d 100644 --- a/examples/webengine/quicknanobrowser/BrowserWindow.qml +++ b/examples/webengine/quicknanobrowser/BrowserWindow.qml @@ -505,6 +505,11 @@ ApplicationWindow { 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); + } } MessageDialog { id: sslDialog diff --git a/examples/webengine/quicknanobrowser/doc/src/quicknanobrowser.qdoc b/examples/webengine/quicknanobrowser/doc/src/quicknanobrowser.qdoc index 190604fc4..3188bb299 100644 --- a/examples/webengine/quicknanobrowser/doc/src/quicknanobrowser.qdoc +++ b/examples/webengine/quicknanobrowser/doc/src/quicknanobrowser.qdoc @@ -136,4 +136,14 @@ \printuntil Action \skipto Escape \printuntil /^\ {4}\}/ + + \section1 Files and Attributions + + The example uses icons from the Tango Icon Library: + + \table + \row + \li \l{quicknanobrowser-tango}{Tango Icon Library} + \li Public Domain + \endtable */ diff --git a/examples/webengine/quicknanobrowser/icons/3rdparty/COPYING b/examples/webengine/quicknanobrowser/icons/3rdparty/COPYING new file mode 100644 index 000000000..220881da6 --- /dev/null +++ b/examples/webengine/quicknanobrowser/icons/3rdparty/COPYING @@ -0,0 +1 @@ +The icons in this repository are herefore released into the Public Domain. diff --git a/examples/webengine/quicknanobrowser/icons/go-next.png b/examples/webengine/quicknanobrowser/icons/3rdparty/go-next.png Binary files differindex 6f3f65d33..6f3f65d33 100644 --- a/examples/webengine/quicknanobrowser/icons/go-next.png +++ b/examples/webengine/quicknanobrowser/icons/3rdparty/go-next.png diff --git a/examples/webengine/quicknanobrowser/icons/go-previous.png b/examples/webengine/quicknanobrowser/icons/3rdparty/go-previous.png Binary files differindex 93be3d1ee..93be3d1ee 100644 --- a/examples/webengine/quicknanobrowser/icons/go-previous.png +++ b/examples/webengine/quicknanobrowser/icons/3rdparty/go-previous.png diff --git a/examples/webengine/quicknanobrowser/icons/process-stop.png b/examples/webengine/quicknanobrowser/icons/3rdparty/process-stop.png Binary files differindex b68290bf1..b68290bf1 100644 --- a/examples/webengine/quicknanobrowser/icons/process-stop.png +++ b/examples/webengine/quicknanobrowser/icons/3rdparty/process-stop.png diff --git a/examples/webengine/quicknanobrowser/icons/3rdparty/qt_attribution.json b/examples/webengine/quicknanobrowser/icons/3rdparty/qt_attribution.json new file mode 100644 index 000000000..4e5a44448 --- /dev/null +++ b/examples/webengine/quicknanobrowser/icons/3rdparty/qt_attribution.json @@ -0,0 +1,24 @@ +{ + "Id": "quicknanobrowser-tango", + "Name": "Tango Icon Library", + "QDocModule": "qtwebengine", + "QtUsage": "Used in WebEngine Quick Nano Browser example.", + + "QtParts": [ "examples" ], + "Description": "Selected icons from the Tango Icon Library", + "Homepage": "http://tango.freedesktop.org/Tango_Icon_Library", + "Version": "0.8.90", + "DownloadLocation": "http://tango.freedesktop.org/releases/tango-icon-theme-0.8.90.tar.gz", + "LicenseId": "DocumentRef-PublicDomain", + "License": "Public Domain", + "LicenseFile": "COPYING", + "Copyright": "Ulisse Perusin <uli.peru@gmail.com> +Steven Garrity <sgarrity@silverorange.com> +Lapo Calamandrei <calamandrei@gmail.com> +Ryan Collier <rcollier@novell.com> +Rodney Dawes <dobey@novell.com> +Andreas Nilsson <nisses.mail@home.se> +Tuomas Kuosmanen <tigert@tigert.com> +Garrett LeSage <garrett@novell.com> +Jakub Steiner <jimmac@novell.com>" +} diff --git a/examples/webengine/quicknanobrowser/icons/view-refresh.png b/examples/webengine/quicknanobrowser/icons/3rdparty/view-refresh.png Binary files differindex cab4d02c7..cab4d02c7 100644 --- a/examples/webengine/quicknanobrowser/icons/view-refresh.png +++ b/examples/webengine/quicknanobrowser/icons/3rdparty/view-refresh.png diff --git a/examples/webengine/quicknanobrowser/main.cpp b/examples/webengine/quicknanobrowser/main.cpp index b1b80106b..63725d3e2 100644 --- a/examples/webengine/quicknanobrowser/main.cpp +++ b/examples/webengine/quicknanobrowser/main.cpp @@ -73,7 +73,7 @@ static QUrl startupUrl() if (ret.isValid()) return ret; } - return QUrl(QStringLiteral("http://qt.io/")); + return QUrl(QStringLiteral("https://www.qt.io")); } int main(int argc, char **argv) diff --git a/examples/webengine/quicknanobrowser/resources.qrc b/examples/webengine/quicknanobrowser/resources.qrc index 694f8d19b..c6270897d 100644 --- a/examples/webengine/quicknanobrowser/resources.qrc +++ b/examples/webengine/quicknanobrowser/resources.qrc @@ -7,9 +7,9 @@ <file>FullScreenNotification.qml</file> </qresource> <qresource prefix="icons"> - <file alias="go-next.png">icons/go-next.png</file> - <file alias="go-previous.png">icons/go-previous.png</file> - <file alias="process-stop.png">icons/process-stop.png</file> - <file alias="view-refresh.png">icons/view-refresh.png</file> + <file alias="go-next.png">icons/3rdparty/go-next.png</file> + <file alias="go-previous.png">icons/3rdparty/go-previous.png</file> + <file alias="process-stop.png">icons/3rdparty/process-stop.png</file> + <file alias="view-refresh.png">icons/3rdparty/view-refresh.png</file> </qresource> </RCC> diff --git a/examples/webengine/recipebrowser/resources/pages/assets/3rdparty/MARKED-LICENSE.txt b/examples/webengine/recipebrowser/resources/pages/assets/3rdparty/MARKED-LICENSE.txt index a7b812ed6..8e3ba0e0a 100644 --- a/examples/webengine/recipebrowser/resources/pages/assets/3rdparty/MARKED-LICENSE.txt +++ b/examples/webengine/recipebrowser/resources/pages/assets/3rdparty/MARKED-LICENSE.txt @@ -1,4 +1,4 @@ -Copyright (c) 2011-2014, Christopher Jeffrey (https://github.com/chjj/) +Copyright (c) 2011-2018, Christopher Jeffrey (https://github.com/chjj/) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/examples/webengine/recipebrowser/resources/pages/assets/3rdparty/marked.js b/examples/webengine/recipebrowser/resources/pages/assets/3rdparty/marked.js new file mode 100644 index 000000000..33c02d9cf --- /dev/null +++ b/examples/webengine/recipebrowser/resources/pages/assets/3rdparty/marked.js @@ -0,0 +1,1514 @@ +/** + * marked - a markdown parser + * Copyright (c) 2011-2014, Christopher Jeffrey. (MIT Licensed) + * https://github.com/markedjs/marked + */ + +;(function(root) { +'use strict'; + +/** + * Block-Level Grammar + */ + +var block = { + newline: /^\n+/, + code: /^( {4}[^\n]+\n*)+/, + fences: noop, + hr: /^ {0,3}((?:- *){3,}|(?:_ *){3,}|(?:\* *){3,})(?:\n+|$)/, + heading: /^ *(#{1,6}) *([^\n]+?) *(?:#+ *)?(?:\n+|$)/, + nptable: noop, + blockquote: /^( {0,3}> ?(paragraph|[^\n]*)(?:\n|$))+/, + list: /^( *)(bull) [\s\S]+?(?:hr|def|\n{2,}(?! )(?!\1bull )\n*|\s*$)/, + html: '^ {0,3}(?:' // optional indentation + + '<(script|pre|style)[\\s>][\\s\\S]*?(?:</\\1>[^\\n]*\\n+|$)' // (1) + + '|comment[^\\n]*(\\n+|$)' // (2) + + '|<\\?[\\s\\S]*?\\?>\\n*' // (3) + + '|<![A-Z][\\s\\S]*?>\\n*' // (4) + + '|<!\\[CDATA\\[[\\s\\S]*?\\]\\]>\\n*' // (5) + + '|</?(tag)(?: +|\\n|/?>)[\\s\\S]*?(?:\\n{2,}|$)' // (6) + + '|<(?!script|pre|style)([a-z][\\w-]*)(?:attribute)*? */?>(?=\\h*\\n)[\\s\\S]*?(?:\\n{2,}|$)' // (7) open tag + + '|</(?!script|pre|style)[a-z][\\w-]*\\s*>(?=\\h*\\n)[\\s\\S]*?(?:\\n{2,}|$)' // (7) closing tag + + ')', + def: /^ {0,3}\[(label)\]: *\n? *<?([^\s>]+)>?(?:(?: +\n? *| *\n *)(title))? *(?:\n+|$)/, + table: noop, + lheading: /^([^\n]+)\n *(=|-){2,} *(?:\n+|$)/, + paragraph: /^([^\n]+(?:\n(?!hr|heading|lheading| {0,3}>|<\/?(?:tag)(?: +|\n|\/?>)|<(?:script|pre|style|!--))[^\n]+)*)/, + text: /^[^\n]+/ +}; + +block._label = /(?!\s*\])(?:\\[\[\]]|[^\[\]])+/; +block._title = /(?:"(?:\\"?|[^"\\])*"|'[^'\n]*(?:\n[^'\n]+)*\n?'|\([^()]*\))/; +block.def = edit(block.def) + .replace('label', block._label) + .replace('title', block._title) + .getRegex(); + +block.bullet = /(?:[*+-]|\d+\.)/; +block.item = /^( *)(bull) [^\n]*(?:\n(?!\1bull )[^\n]*)*/; +block.item = edit(block.item, 'gm') + .replace(/bull/g, block.bullet) + .getRegex(); + +block.list = edit(block.list) + .replace(/bull/g, block.bullet) + .replace('hr', '\\n+(?=\\1?(?:(?:- *){3,}|(?:_ *){3,}|(?:\\* *){3,})(?:\\n+|$))') + .replace('def', '\\n+(?=' + block.def.source + ')') + .getRegex(); + +block._tag = 'address|article|aside|base|basefont|blockquote|body|caption' + + '|center|col|colgroup|dd|details|dialog|dir|div|dl|dt|fieldset|figcaption' + + '|figure|footer|form|frame|frameset|h[1-6]|head|header|hr|html|iframe' + + '|legend|li|link|main|menu|menuitem|meta|nav|noframes|ol|optgroup|option' + + '|p|param|section|source|summary|table|tbody|td|tfoot|th|thead|title|tr' + + '|track|ul'; +block._comment = /<!--(?!-?>)[\s\S]*?-->/; +block.html = edit(block.html, 'i') + .replace('comment', block._comment) + .replace('tag', block._tag) + .replace('attribute', / +[a-zA-Z:_][\w.:-]*(?: *= *"[^"\n]*"| *= *'[^'\n]*'| *= *[^\s"'=<>`]+)?/) + .getRegex(); + +block.paragraph = edit(block.paragraph) + .replace('hr', block.hr) + .replace('heading', block.heading) + .replace('lheading', block.lheading) + .replace('tag', block._tag) // pars can be interrupted by type (6) html blocks + .getRegex(); + +block.blockquote = edit(block.blockquote) + .replace('paragraph', block.paragraph) + .getRegex(); + +/** + * Normal Block Grammar + */ + +block.normal = merge({}, block); + +/** + * GFM Block Grammar + */ + +block.gfm = merge({}, block.normal, { + fences: /^ *(`{3,}|~{3,})[ \.]*(\S+)? *\n([\s\S]*?)\n? *\1 *(?:\n+|$)/, + paragraph: /^/, + heading: /^ *(#{1,6}) +([^\n]+?) *#* *(?:\n+|$)/ +}); + +block.gfm.paragraph = edit(block.paragraph) + .replace('(?!', '(?!' + + block.gfm.fences.source.replace('\\1', '\\2') + '|' + + block.list.source.replace('\\1', '\\3') + '|') + .getRegex(); + +/** + * GFM + Tables Block Grammar + */ + +block.tables = merge({}, block.gfm, { + nptable: /^ *([^|\n ].*\|.*)\n *([-:]+ *\|[-| :]*)(?:\n((?:.*[^>\n ].*(?:\n|$))*)\n*|$)/, + table: /^ *\|(.+)\n *\|?( *[-:]+[-| :]*)(?:\n((?: *[^>\n ].*(?:\n|$))*)\n*|$)/ +}); + +/** + * Pedantic grammar + */ + +block.pedantic = merge({}, block.normal, { + html: edit( + '^ *(?:comment *(?:\\n|\\s*$)' + + '|<(tag)[\\s\\S]+?</\\1> *(?:\\n{2,}|\\s*$)' // closed tag + + '|<tag(?:"[^"]*"|\'[^\']*\'|\\s[^\'"/>\\s]*)*?/?> *(?:\\n{2,}|\\s*$))') + .replace('comment', block._comment) + .replace(/tag/g, '(?!(?:' + + 'a|em|strong|small|s|cite|q|dfn|abbr|data|time|code|var|samp|kbd|sub' + + '|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo|span|br|wbr|ins|del|img)' + + '\\b)\\w+(?!:|[^\\w\\s@]*@)\\b') + .getRegex(), + def: /^ *\[([^\]]+)\]: *<?([^\s>]+)>?(?: +(["(][^\n]+[")]))? *(?:\n+|$)/ +}); + +/** + * Block Lexer + */ + +function Lexer(options) { + this.tokens = []; + this.tokens.links = {}; + this.options = options || marked.defaults; + this.rules = block.normal; + + if (this.options.pedantic) { + this.rules = block.pedantic; + } else if (this.options.gfm) { + if (this.options.tables) { + this.rules = block.tables; + } else { + this.rules = block.gfm; + } + } +} + +/** + * Expose Block Rules + */ + +Lexer.rules = block; + +/** + * Static Lex Method + */ + +Lexer.lex = function(src, options) { + var lexer = new Lexer(options); + return lexer.lex(src); +}; + +/** + * Preprocessing + */ + +Lexer.prototype.lex = function(src) { + src = src + .replace(/\r\n|\r/g, '\n') + .replace(/\t/g, ' ') + .replace(/\u00a0/g, ' ') + .replace(/\u2424/g, '\n'); + + return this.token(src, true); +}; + +/** + * Lexing + */ + +Lexer.prototype.token = function(src, top) { + src = src.replace(/^ +$/gm, ''); + var next, + loose, + cap, + bull, + b, + item, + space, + i, + tag, + l, + isordered, + istask, + ischecked; + + while (src) { + // newline + if (cap = this.rules.newline.exec(src)) { + src = src.substring(cap[0].length); + if (cap[0].length > 1) { + this.tokens.push({ + type: 'space' + }); + } + } + + // code + if (cap = this.rules.code.exec(src)) { + src = src.substring(cap[0].length); + cap = cap[0].replace(/^ {4}/gm, ''); + this.tokens.push({ + type: 'code', + text: !this.options.pedantic + ? cap.replace(/\n+$/, '') + : cap + }); + continue; + } + + // fences (gfm) + if (cap = this.rules.fences.exec(src)) { + src = src.substring(cap[0].length); + this.tokens.push({ + type: 'code', + lang: cap[2], + text: cap[3] || '' + }); + continue; + } + + // heading + if (cap = this.rules.heading.exec(src)) { + src = src.substring(cap[0].length); + this.tokens.push({ + type: 'heading', + depth: cap[1].length, + text: cap[2] + }); + continue; + } + + // table no leading pipe (gfm) + if (top && (cap = this.rules.nptable.exec(src))) { + item = { + type: 'table', + header: splitCells(cap[1].replace(/^ *| *\| *$/g, '')), + align: cap[2].replace(/^ *|\| *$/g, '').split(/ *\| */), + cells: cap[3] ? cap[3].replace(/\n$/, '').split('\n') : [] + }; + + if (item.header.length === item.align.length) { + src = src.substring(cap[0].length); + + for (i = 0; i < item.align.length; i++) { + if (/^ *-+: *$/.test(item.align[i])) { + item.align[i] = 'right'; + } else if (/^ *:-+: *$/.test(item.align[i])) { + item.align[i] = 'center'; + } else if (/^ *:-+ *$/.test(item.align[i])) { + item.align[i] = 'left'; + } else { + item.align[i] = null; + } + } + + for (i = 0; i < item.cells.length; i++) { + item.cells[i] = splitCells(item.cells[i], item.header.length); + } + + this.tokens.push(item); + + continue; + } + } + + // hr + if (cap = this.rules.hr.exec(src)) { + src = src.substring(cap[0].length); + this.tokens.push({ + type: 'hr' + }); + continue; + } + + // blockquote + if (cap = this.rules.blockquote.exec(src)) { + src = src.substring(cap[0].length); + + this.tokens.push({ + type: 'blockquote_start' + }); + + cap = cap[0].replace(/^ *> ?/gm, ''); + + // Pass `top` to keep the current + // "toplevel" state. This is exactly + // how markdown.pl works. + this.token(cap, top); + + this.tokens.push({ + type: 'blockquote_end' + }); + + continue; + } + + // list + if (cap = this.rules.list.exec(src)) { + src = src.substring(cap[0].length); + bull = cap[2]; + isordered = bull.length > 1; + + this.tokens.push({ + type: 'list_start', + ordered: isordered, + start: isordered ? +bull : '' + }); + + // Get each top-level item. + cap = cap[0].match(this.rules.item); + + next = false; + l = cap.length; + i = 0; + + for (; i < l; i++) { + item = cap[i]; + + // Remove the list item's bullet + // so it is seen as the next token. + space = item.length; + item = item.replace(/^ *([*+-]|\d+\.) +/, ''); + + // Outdent whatever the + // list item contains. Hacky. + if (~item.indexOf('\n ')) { + space -= item.length; + item = !this.options.pedantic + ? item.replace(new RegExp('^ {1,' + space + '}', 'gm'), '') + : item.replace(/^ {1,4}/gm, ''); + } + + // Determine whether the next list item belongs here. + // Backpedal if it does not belong in this list. + if (this.options.smartLists && i !== l - 1) { + b = block.bullet.exec(cap[i + 1])[0]; + if (bull !== b && !(bull.length > 1 && b.length > 1)) { + src = cap.slice(i + 1).join('\n') + src; + i = l - 1; + } + } + + // Determine whether item is loose or not. + // Use: /(^|\n)(?! )[^\n]+\n\n(?!\s*$)/ + // for discount behavior. + loose = next || /\n\n(?!\s*$)/.test(item); + if (i !== l - 1) { + next = item.charAt(item.length - 1) === '\n'; + if (!loose) loose = next; + } + + // Check for task list items + istask = /^\[[ xX]\] /.test(item); + ischecked = undefined; + if (istask) { + ischecked = item[1] !== ' '; + item = item.replace(/^\[[ xX]\] +/, ''); + } + + this.tokens.push({ + type: loose + ? 'loose_item_start' + : 'list_item_start', + task: istask, + checked: ischecked + }); + + // Recurse. + this.token(item, false); + + this.tokens.push({ + type: 'list_item_end' + }); + } + + this.tokens.push({ + type: 'list_end' + }); + + continue; + } + + // html + if (cap = this.rules.html.exec(src)) { + src = src.substring(cap[0].length); + this.tokens.push({ + type: this.options.sanitize + ? 'paragraph' + : 'html', + pre: !this.options.sanitizer + && (cap[1] === 'pre' || cap[1] === 'script' || cap[1] === 'style'), + text: cap[0] + }); + continue; + } + + // def + if (top && (cap = this.rules.def.exec(src))) { + src = src.substring(cap[0].length); + if (cap[3]) cap[3] = cap[3].substring(1, cap[3].length - 1); + tag = cap[1].toLowerCase().replace(/\s+/g, ' '); + if (!this.tokens.links[tag]) { + this.tokens.links[tag] = { + href: cap[2], + title: cap[3] + }; + } + continue; + } + + // table (gfm) + if (top && (cap = this.rules.table.exec(src))) { + item = { + type: 'table', + header: splitCells(cap[1].replace(/^ *| *\| *$/g, '')), + align: cap[2].replace(/^ *|\| *$/g, '').split(/ *\| */), + cells: cap[3] ? cap[3].replace(/(?: *\| *)?\n$/, '').split('\n') : [] + }; + + if (item.header.length === item.align.length) { + src = src.substring(cap[0].length); + + for (i = 0; i < item.align.length; i++) { + if (/^ *-+: *$/.test(item.align[i])) { + item.align[i] = 'right'; + } else if (/^ *:-+: *$/.test(item.align[i])) { + item.align[i] = 'center'; + } else if (/^ *:-+ *$/.test(item.align[i])) { + item.align[i] = 'left'; + } else { + item.align[i] = null; + } + } + + for (i = 0; i < item.cells.length; i++) { + item.cells[i] = splitCells( + item.cells[i].replace(/^ *\| *| *\| *$/g, ''), + item.header.length); + } + + this.tokens.push(item); + + continue; + } + } + + // lheading + if (cap = this.rules.lheading.exec(src)) { + src = src.substring(cap[0].length); + this.tokens.push({ + type: 'heading', + depth: cap[2] === '=' ? 1 : 2, + text: cap[1] + }); + continue; + } + + // top-level paragraph + if (top && (cap = this.rules.paragraph.exec(src))) { + src = src.substring(cap[0].length); + this.tokens.push({ + type: 'paragraph', + text: cap[1].charAt(cap[1].length - 1) === '\n' + ? cap[1].slice(0, -1) + : cap[1] + }); + continue; + } + + // text + if (cap = this.rules.text.exec(src)) { + // Top-level should never reach here. + src = src.substring(cap[0].length); + this.tokens.push({ + type: 'text', + text: cap[0] + }); + continue; + } + + if (src) { + throw new Error('Infinite loop on byte: ' + src.charCodeAt(0)); + } + } + + return this.tokens; +}; + +/** + * Inline-Level Grammar + */ + +var inline = { + escape: /^\\([!"#$%&'()*+,\-./:;<=>?@\[\]\\^_`{|}~])/, + autolink: /^<(scheme:[^\s\x00-\x1f<>]*|email)>/, + url: noop, + tag: '^comment' + + '|^</[a-zA-Z][\\w:-]*\\s*>' // self-closing tag + + '|^<[a-zA-Z][\\w-]*(?:attribute)*?\\s*/?>' // open tag + + '|^<\\?[\\s\\S]*?\\?>' // processing instruction, e.g. <?php ?> + + '|^<![a-zA-Z]+\\s[\\s\\S]*?>' // declaration, e.g. <!DOCTYPE html> + + '|^<!\\[CDATA\\[[\\s\\S]*?\\]\\]>', // CDATA section + link: /^!?\[(label)\]\(href(?:\s+(title))?\s*\)/, + reflink: /^!?\[(label)\]\[(?!\s*\])((?:\\[\[\]]?|[^\[\]\\])+)\]/, + nolink: /^!?\[(?!\s*\])((?:\[[^\[\]]*\]|\\[\[\]]|[^\[\]])*)\](?:\[\])?/, + strong: /^__([^\s][\s\S]*?[^\s])__(?!_)|^\*\*([^\s][\s\S]*?[^\s])\*\*(?!\*)|^__([^\s])__(?!_)|^\*\*([^\s])\*\*(?!\*)/, + em: /^_([^\s][\s\S]*?[^\s_])_(?!_)|^_([^\s_][\s\S]*?[^\s])_(?!_)|^\*([^\s][\s\S]*?[^\s*])\*(?!\*)|^\*([^\s*][\s\S]*?[^\s])\*(?!\*)|^_([^\s_])_(?!_)|^\*([^\s*])\*(?!\*)/, + code: /^(`+)\s*([\s\S]*?[^`]?)\s*\1(?!`)/, + br: /^ {2,}\n(?!\s*$)/, + del: noop, + text: /^[\s\S]+?(?=[\\<!\[`*]|\b_| {2,}\n|$)/ +}; + +inline._escapes = /\\([!"#$%&'()*+,\-./:;<=>?@\[\]\\^_`{|}~])/g; + +inline._scheme = /[a-zA-Z][a-zA-Z0-9+.-]{1,31}/; +inline._email = /[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+(@)[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+(?![-_])/; +inline.autolink = edit(inline.autolink) + .replace('scheme', inline._scheme) + .replace('email', inline._email) + .getRegex(); + +inline._attribute = /\s+[a-zA-Z:_][\w.:-]*(?:\s*=\s*"[^"]*"|\s*=\s*'[^']*'|\s*=\s*[^\s"'=<>`]+)?/; + +inline.tag = edit(inline.tag) + .replace('comment', block._comment) + .replace('attribute', inline._attribute) + .getRegex(); + +inline._label = /(?:\[[^\[\]]*\]|\\[\[\]]?|`[^`]*`|[^\[\]\\])*?/; +inline._href = /\s*(<(?:\\[<>]?|[^\s<>\\])*>|(?:\\[()]?|\([^\s\x00-\x1f()\\]*\)|[^\s\x00-\x1f()\\])*?)/; +inline._title = /"(?:\\"?|[^"\\])*"|'(?:\\'?|[^'\\])*'|\((?:\\\)?|[^)\\])*\)/; + +inline.link = edit(inline.link) + .replace('label', inline._label) + .replace('href', inline._href) + .replace('title', inline._title) + .getRegex(); + +inline.reflink = edit(inline.reflink) + .replace('label', inline._label) + .getRegex(); + +/** + * Normal Inline Grammar + */ + +inline.normal = merge({}, inline); + +/** + * Pedantic Inline Grammar + */ + +inline.pedantic = merge({}, inline.normal, { + strong: /^__(?=\S)([\s\S]*?\S)__(?!_)|^\*\*(?=\S)([\s\S]*?\S)\*\*(?!\*)/, + em: /^_(?=\S)([\s\S]*?\S)_(?!_)|^\*(?=\S)([\s\S]*?\S)\*(?!\*)/, + link: edit(/^!?\[(label)\]\((.*?)\)/) + .replace('label', inline._label) + .getRegex(), + reflink: edit(/^!?\[(label)\]\s*\[([^\]]*)\]/) + .replace('label', inline._label) + .getRegex() +}); + +/** + * GFM Inline Grammar + */ + +inline.gfm = merge({}, inline.normal, { + escape: edit(inline.escape).replace('])', '~|])').getRegex(), + url: edit(/^((?:ftp|https?):\/\/|www\.)(?:[a-zA-Z0-9\-]+\.?)+[^\s<]*|^email/) + .replace('email', inline._email) + .getRegex(), + _backpedal: /(?:[^?!.,:;*_~()&]+|\([^)]*\)|&(?![a-zA-Z0-9]+;$)|[?!.,:;*_~)]+(?!$))+/, + del: /^~~(?=\S)([\s\S]*?\S)~~/, + text: edit(inline.text) + .replace(']|', '~]|') + .replace('|', '|https?://|ftp://|www\\.|[a-zA-Z0-9.!#$%&\'*+/=?^_`{\\|}~-]+@|') + .getRegex() +}); + +/** + * GFM + Line Breaks Inline Grammar + */ + +inline.breaks = merge({}, inline.gfm, { + br: edit(inline.br).replace('{2,}', '*').getRegex(), + text: edit(inline.gfm.text).replace('{2,}', '*').getRegex() +}); + +/** + * Inline Lexer & Compiler + */ + +function InlineLexer(links, options) { + this.options = options || marked.defaults; + this.links = links; + this.rules = inline.normal; + this.renderer = this.options.renderer || new Renderer(); + this.renderer.options = this.options; + + if (!this.links) { + throw new Error('Tokens array requires a `links` property.'); + } + + if (this.options.pedantic) { + this.rules = inline.pedantic; + } else if (this.options.gfm) { + if (this.options.breaks) { + this.rules = inline.breaks; + } else { + this.rules = inline.gfm; + } + } +} + +/** + * Expose Inline Rules + */ + +InlineLexer.rules = inline; + +/** + * Static Lexing/Compiling Method + */ + +InlineLexer.output = function(src, links, options) { + var inline = new InlineLexer(links, options); + return inline.output(src); +}; + +/** + * Lexing/Compiling + */ + +InlineLexer.prototype.output = function(src) { + var out = '', + link, + text, + href, + title, + cap; + + while (src) { + // escape + if (cap = this.rules.escape.exec(src)) { + src = src.substring(cap[0].length); + out += cap[1]; + continue; + } + + // autolink + if (cap = this.rules.autolink.exec(src)) { + src = src.substring(cap[0].length); + if (cap[2] === '@') { + text = escape(this.mangle(cap[1])); + href = 'mailto:' + text; + } else { + text = escape(cap[1]); + href = text; + } + out += this.renderer.link(href, null, text); + continue; + } + + // url (gfm) + if (!this.inLink && (cap = this.rules.url.exec(src))) { + cap[0] = this.rules._backpedal.exec(cap[0])[0]; + src = src.substring(cap[0].length); + if (cap[2] === '@') { + text = escape(cap[0]); + href = 'mailto:' + text; + } else { + text = escape(cap[0]); + if (cap[1] === 'www.') { + href = 'http://' + text; + } else { + href = text; + } + } + out += this.renderer.link(href, null, text); + continue; + } + + // tag + if (cap = this.rules.tag.exec(src)) { + if (!this.inLink && /^<a /i.test(cap[0])) { + this.inLink = true; + } else if (this.inLink && /^<\/a>/i.test(cap[0])) { + this.inLink = false; + } + src = src.substring(cap[0].length); + out += this.options.sanitize + ? this.options.sanitizer + ? this.options.sanitizer(cap[0]) + : escape(cap[0]) + : cap[0] + continue; + } + + // link + if (cap = this.rules.link.exec(src)) { + src = src.substring(cap[0].length); + this.inLink = true; + href = cap[2]; + if (this.options.pedantic) { + link = /^([^'"]*[^\s])\s+(['"])(.*)\2/.exec(href); + + if (link) { + href = link[1]; + title = link[3]; + } else { + title = ''; + } + } else { + title = cap[3] ? cap[3].slice(1, -1) : ''; + } + href = href.trim().replace(/^<([\s\S]*)>$/, '$1'); + out += this.outputLink(cap, { + href: InlineLexer.escapes(href), + title: InlineLexer.escapes(title) + }); + this.inLink = false; + continue; + } + + // reflink, nolink + if ((cap = this.rules.reflink.exec(src)) + || (cap = this.rules.nolink.exec(src))) { + src = src.substring(cap[0].length); + link = (cap[2] || cap[1]).replace(/\s+/g, ' '); + link = this.links[link.toLowerCase()]; + if (!link || !link.href) { + out += cap[0].charAt(0); + src = cap[0].substring(1) + src; + continue; + } + this.inLink = true; + out += this.outputLink(cap, link); + this.inLink = false; + continue; + } + + // strong + if (cap = this.rules.strong.exec(src)) { + src = src.substring(cap[0].length); + out += this.renderer.strong(this.output(cap[4] || cap[3] || cap[2] || cap[1])); + continue; + } + + // em + if (cap = this.rules.em.exec(src)) { + src = src.substring(cap[0].length); + out += this.renderer.em(this.output(cap[6] || cap[5] || cap[4] || cap[3] || cap[2] || cap[1])); + continue; + } + + // code + if (cap = this.rules.code.exec(src)) { + src = src.substring(cap[0].length); + out += this.renderer.codespan(escape(cap[2].trim(), true)); + continue; + } + + // br + if (cap = this.rules.br.exec(src)) { + src = src.substring(cap[0].length); + out += this.renderer.br(); + continue; + } + + // del (gfm) + if (cap = this.rules.del.exec(src)) { + src = src.substring(cap[0].length); + out += this.renderer.del(this.output(cap[1])); + continue; + } + + // text + if (cap = this.rules.text.exec(src)) { + src = src.substring(cap[0].length); + out += this.renderer.text(escape(this.smartypants(cap[0]))); + continue; + } + + if (src) { + throw new Error('Infinite loop on byte: ' + src.charCodeAt(0)); + } + } + + return out; +}; + +InlineLexer.escapes = function(text) { + return text ? text.replace(InlineLexer.rules._escapes, '$1') : text; +} + +/** + * Compile Link + */ + +InlineLexer.prototype.outputLink = function(cap, link) { + var href = link.href, + title = link.title ? escape(link.title) : null; + + return cap[0].charAt(0) !== '!' + ? this.renderer.link(href, title, this.output(cap[1])) + : this.renderer.image(href, title, escape(cap[1])); +}; + +/** + * Smartypants Transformations + */ + +InlineLexer.prototype.smartypants = function(text) { + if (!this.options.smartypants) return text; + return text + // em-dashes + .replace(/---/g, '\u2014') + // en-dashes + .replace(/--/g, '\u2013') + // opening singles + .replace(/(^|[-\u2014/(\[{"\s])'/g, '$1\u2018') + // closing singles & apostrophes + .replace(/'/g, '\u2019') + // opening doubles + .replace(/(^|[-\u2014/(\[{\u2018\s])"/g, '$1\u201c') + // closing doubles + .replace(/"/g, '\u201d') + // ellipses + .replace(/\.{3}/g, '\u2026'); +}; + +/** + * Mangle Links + */ + +InlineLexer.prototype.mangle = function(text) { + if (!this.options.mangle) return text; + var out = '', + l = text.length, + i = 0, + ch; + + for (; i < l; i++) { + ch = text.charCodeAt(i); + if (Math.random() > 0.5) { + ch = 'x' + ch.toString(16); + } + out += '&#' + ch + ';'; + } + + return out; +}; + +/** + * Renderer + */ + +function Renderer(options) { + this.options = options || marked.defaults; +} + +Renderer.prototype.code = function(code, lang, escaped) { + if (this.options.highlight) { + var out = this.options.highlight(code, lang); + if (out != null && out !== code) { + escaped = true; + code = out; + } + } + + if (!lang) { + return '<pre><code>' + + (escaped ? code : escape(code, true)) + + '</code></pre>'; + } + + return '<pre><code class="' + + this.options.langPrefix + + escape(lang, true) + + '">' + + (escaped ? code : escape(code, true)) + + '</code></pre>\n'; +}; + +Renderer.prototype.blockquote = function(quote) { + return '<blockquote>\n' + quote + '</blockquote>\n'; +}; + +Renderer.prototype.html = function(html) { + return html; +}; + +Renderer.prototype.heading = function(text, level, raw) { + if (this.options.headerIds) { + return '<h' + + level + + ' id="' + + this.options.headerPrefix + + raw.toLowerCase().replace(/[^\w]+/g, '-') + + '">' + + text + + '</h' + + level + + '>\n'; + } + // ignore IDs + return '<h' + level + '>' + text + '</h' + level + '>\n'; +}; + +Renderer.prototype.hr = function() { + return this.options.xhtml ? '<hr/>\n' : '<hr>\n'; +}; + +Renderer.prototype.list = function(body, ordered, start) { + var type = ordered ? 'ol' : 'ul', + startatt = (ordered && start !== 1) ? (' start="' + start + '"') : ''; + return '<' + type + startatt + '>\n' + body + '</' + type + '>\n'; +}; + +Renderer.prototype.listitem = function(text) { + return '<li>' + text + '</li>\n'; +}; + +Renderer.prototype.checkbox = function(checked) { + return '<input ' + + (checked ? 'checked="" ' : '') + + 'disabled="" type="checkbox"' + + (this.options.xhtml ? ' /' : '') + + '> '; +} + +Renderer.prototype.paragraph = function(text) { + return '<p>' + text + '</p>\n'; +}; + +Renderer.prototype.table = function(header, body) { + if (body) body = '<tbody>' + body + '</tbody>'; + + return '<table>\n' + + '<thead>\n' + + header + + '</thead>\n' + + body + + '</table>\n'; +}; + +Renderer.prototype.tablerow = function(content) { + return '<tr>\n' + content + '</tr>\n'; +}; + +Renderer.prototype.tablecell = function(content, flags) { + var type = flags.header ? 'th' : 'td'; + var tag = flags.align + ? '<' + type + ' align="' + flags.align + '">' + : '<' + type + '>'; + return tag + content + '</' + type + '>\n'; +}; + +// span level renderer +Renderer.prototype.strong = function(text) { + return '<strong>' + text + '</strong>'; +}; + +Renderer.prototype.em = function(text) { + return '<em>' + text + '</em>'; +}; + +Renderer.prototype.codespan = function(text) { + return '<code>' + text + '</code>'; +}; + +Renderer.prototype.br = function() { + return this.options.xhtml ? '<br/>' : '<br>'; +}; + +Renderer.prototype.del = function(text) { + return '<del>' + text + '</del>'; +}; + +Renderer.prototype.link = function(href, title, text) { + if (this.options.sanitize) { + try { + var prot = decodeURIComponent(unescape(href)) + .replace(/[^\w:]/g, '') + .toLowerCase(); + } catch (e) { + return text; + } + if (prot.indexOf('javascript:') === 0 || prot.indexOf('vbscript:') === 0 || prot.indexOf('data:') === 0) { + return text; + } + } + if (this.options.baseUrl && !originIndependentUrl.test(href)) { + href = resolveUrl(this.options.baseUrl, href); + } + try { + href = encodeURI(href).replace(/%25/g, '%'); + } catch (e) { + return text; + } + var out = '<a href="' + escape(href) + '"'; + if (title) { + out += ' title="' + title + '"'; + } + out += '>' + text + '</a>'; + return out; +}; + +Renderer.prototype.image = function(href, title, text) { + if (this.options.baseUrl && !originIndependentUrl.test(href)) { + href = resolveUrl(this.options.baseUrl, href); + } + var out = '<img src="' + href + '" alt="' + text + '"'; + if (title) { + out += ' title="' + title + '"'; + } + out += this.options.xhtml ? '/>' : '>'; + return out; +}; + +Renderer.prototype.text = function(text) { + return text; +}; + +/** + * TextRenderer + * returns only the textual part of the token + */ + +function TextRenderer() {} + +// no need for block level renderers + +TextRenderer.prototype.strong = +TextRenderer.prototype.em = +TextRenderer.prototype.codespan = +TextRenderer.prototype.del = +TextRenderer.prototype.text = function (text) { + return text; +} + +TextRenderer.prototype.link = +TextRenderer.prototype.image = function(href, title, text) { + return '' + text; +} + +TextRenderer.prototype.br = function() { + return ''; +} + +/** + * Parsing & Compiling + */ + +function Parser(options) { + this.tokens = []; + this.token = null; + this.options = options || marked.defaults; + this.options.renderer = this.options.renderer || new Renderer(); + this.renderer = this.options.renderer; + this.renderer.options = this.options; +} + +/** + * Static Parse Method + */ + +Parser.parse = function(src, options) { + var parser = new Parser(options); + return parser.parse(src); +}; + +/** + * Parse Loop + */ + +Parser.prototype.parse = function(src) { + this.inline = new InlineLexer(src.links, this.options); + // use an InlineLexer with a TextRenderer to extract pure text + this.inlineText = new InlineLexer( + src.links, + merge({}, this.options, {renderer: new TextRenderer()}) + ); + this.tokens = src.reverse(); + + var out = ''; + while (this.next()) { + out += this.tok(); + } + + return out; +}; + +/** + * Next Token + */ + +Parser.prototype.next = function() { + return this.token = this.tokens.pop(); +}; + +/** + * Preview Next Token + */ + +Parser.prototype.peek = function() { + return this.tokens[this.tokens.length - 1] || 0; +}; + +/** + * Parse Text Tokens + */ + +Parser.prototype.parseText = function() { + var body = this.token.text; + + while (this.peek().type === 'text') { + body += '\n' + this.next().text; + } + + return this.inline.output(body); +}; + +/** + * Parse Current Token + */ + +Parser.prototype.tok = function() { + switch (this.token.type) { + case 'space': { + return ''; + } + case 'hr': { + return this.renderer.hr(); + } + case 'heading': { + return this.renderer.heading( + this.inline.output(this.token.text), + this.token.depth, + unescape(this.inlineText.output(this.token.text))); + } + case 'code': { + return this.renderer.code(this.token.text, + this.token.lang, + this.token.escaped); + } + case 'table': { + var header = '', + body = '', + i, + row, + cell, + j; + + // header + cell = ''; + for (i = 0; i < this.token.header.length; i++) { + cell += this.renderer.tablecell( + this.inline.output(this.token.header[i]), + { header: true, align: this.token.align[i] } + ); + } + header += this.renderer.tablerow(cell); + + for (i = 0; i < this.token.cells.length; i++) { + row = this.token.cells[i]; + + cell = ''; + for (j = 0; j < row.length; j++) { + cell += this.renderer.tablecell( + this.inline.output(row[j]), + { header: false, align: this.token.align[j] } + ); + } + + body += this.renderer.tablerow(cell); + } + return this.renderer.table(header, body); + } + case 'blockquote_start': { + body = ''; + + while (this.next().type !== 'blockquote_end') { + body += this.tok(); + } + + return this.renderer.blockquote(body); + } + case 'list_start': { + body = ''; + var ordered = this.token.ordered, + start = this.token.start; + + while (this.next().type !== 'list_end') { + body += this.tok(); + } + + return this.renderer.list(body, ordered, start); + } + case 'list_item_start': { + body = ''; + + if (this.token.task) { + body += this.renderer.checkbox(this.token.checked); + } + + while (this.next().type !== 'list_item_end') { + body += this.token.type === 'text' + ? this.parseText() + : this.tok(); + } + + return this.renderer.listitem(body); + } + case 'loose_item_start': { + body = ''; + + while (this.next().type !== 'list_item_end') { + body += this.tok(); + } + + return this.renderer.listitem(body); + } + case 'html': { + // TODO parse inline content if parameter markdown=1 + return this.renderer.html(this.token.text); + } + case 'paragraph': { + return this.renderer.paragraph(this.inline.output(this.token.text)); + } + case 'text': { + return this.renderer.paragraph(this.parseText()); + } + } +}; + +/** + * Helpers + */ + +function escape(html, encode) { + return html + .replace(!encode ? /&(?!#?\w+;)/g : /&/g, '&') + .replace(/</g, '<') + .replace(/>/g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); +} + +function unescape(html) { + // explicitly match decimal, hex, and named HTML entities + return html.replace(/&(#(?:\d+)|(?:#x[0-9A-Fa-f]+)|(?:\w+));?/ig, function(_, n) { + n = n.toLowerCase(); + if (n === 'colon') return ':'; + if (n.charAt(0) === '#') { + return n.charAt(1) === 'x' + ? String.fromCharCode(parseInt(n.substring(2), 16)) + : String.fromCharCode(+n.substring(1)); + } + return ''; + }); +} + +function edit(regex, opt) { + regex = regex.source || regex; + opt = opt || ''; + return { + replace: function(name, val) { + val = val.source || val; + val = val.replace(/(^|[^\[])\^/g, '$1'); + regex = regex.replace(name, val); + return this; + }, + getRegex: function() { + return new RegExp(regex, opt); + } + }; +} + +function resolveUrl(base, href) { + if (!baseUrls[' ' + base]) { + // we can ignore everything in base after the last slash of its path component, + // but we might need to add _that_ + // https://tools.ietf.org/html/rfc3986#section-3 + if (/^[^:]+:\/*[^/]*$/.test(base)) { + baseUrls[' ' + base] = base + '/'; + } else { + baseUrls[' ' + base] = base.replace(/[^/]*$/, ''); + } + } + base = baseUrls[' ' + base]; + + if (href.slice(0, 2) === '//') { + return base.replace(/:[\s\S]*/, ':') + href; + } else if (href.charAt(0) === '/') { + return base.replace(/(:\/*[^/]*)[\s\S]*/, '$1') + href; + } else { + return base + href; + } +} +var baseUrls = {}; +var originIndependentUrl = /^$|^[a-z][a-z0-9+.-]*:|^[?#]/i; + +function noop() {} +noop.exec = noop; + +function merge(obj) { + var i = 1, + target, + key; + + for (; i < arguments.length; i++) { + target = arguments[i]; + for (key in target) { + if (Object.prototype.hasOwnProperty.call(target, key)) { + obj[key] = target[key]; + } + } + } + + return obj; +} + +function splitCells(tableRow, count) { + var cells = tableRow.replace(/([^\\])\|/g, '$1 |').split(/ +\| */), + i = 0; + + if (cells.length > count) { + cells.splice(count); + } else { + while (cells.length < count) cells.push(''); + } + + for (; i < cells.length; i++) { + cells[i] = cells[i].replace(/\\\|/g, '|'); + } + return cells; +} + +/** + * Marked + */ + +function marked(src, opt, callback) { + // throw error in case of non string input + if (typeof src === 'undefined' || src === null) { + throw new Error('marked(): input parameter is undefined or null'); + } + if (typeof src !== 'string') { + throw new Error('marked(): input parameter is of type ' + + Object.prototype.toString.call(src) + ', string expected'); + } + + if (callback || typeof opt === 'function') { + if (!callback) { + callback = opt; + opt = null; + } + + opt = merge({}, marked.defaults, opt || {}); + + var highlight = opt.highlight, + tokens, + pending, + i = 0; + + try { + tokens = Lexer.lex(src, opt) + } catch (e) { + return callback(e); + } + + pending = tokens.length; + + var done = function(err) { + if (err) { + opt.highlight = highlight; + return callback(err); + } + + var out; + + try { + out = Parser.parse(tokens, opt); + } catch (e) { + err = e; + } + + opt.highlight = highlight; + + return err + ? callback(err) + : callback(null, out); + }; + + if (!highlight || highlight.length < 3) { + return done(); + } + + delete opt.highlight; + + if (!pending) return done(); + + for (; i < tokens.length; i++) { + (function(token) { + if (token.type !== 'code') { + return --pending || done(); + } + return highlight(token.text, token.lang, function(err, code) { + if (err) return done(err); + if (code == null || code === token.text) { + return --pending || done(); + } + token.text = code; + token.escaped = true; + --pending || done(); + }); + })(tokens[i]); + } + + return; + } + try { + if (opt) opt = merge({}, marked.defaults, opt); + return Parser.parse(Lexer.lex(src, opt), opt); + } catch (e) { + e.message += '\nPlease report this to https://github.com/markedjs/marked.'; + if ((opt || marked.defaults).silent) { + return '<p>An error occurred:</p><pre>' + + escape(e.message + '', true) + + '</pre>'; + } + throw e; + } +} + +/** + * Options + */ + +marked.options = +marked.setOptions = function(opt) { + merge(marked.defaults, opt); + return marked; +}; + +marked.getDefaults = function () { + return { + baseUrl: null, + breaks: false, + gfm: true, + headerIds: true, + headerPrefix: '', + highlight: null, + langPrefix: 'language-', + mangle: true, + pedantic: false, + renderer: new Renderer(), + sanitize: false, + sanitizer: null, + silent: false, + smartLists: false, + smartypants: false, + tables: true, + xhtml: false + }; +} + +marked.defaults = marked.getDefaults(); + +/** + * Expose + */ + +marked.Parser = Parser; +marked.parser = Parser.parse; + +marked.Renderer = Renderer; +marked.TextRenderer = TextRenderer; + +marked.Lexer = Lexer; +marked.lexer = Lexer.lex; + +marked.InlineLexer = InlineLexer; +marked.inlineLexer = InlineLexer.output; + +marked.parse = marked; + +if (typeof module !== 'undefined' && typeof exports === 'object') { + module.exports = marked; +} else if (typeof define === 'function' && define.amd) { + define(function() { return marked; }); +} else { + root.marked = marked; +} +})(this || (typeof window !== 'undefined' ? window : global)); diff --git a/examples/webengine/recipebrowser/resources/pages/assets/3rdparty/marked.min.js b/examples/webengine/recipebrowser/resources/pages/assets/3rdparty/marked.min.js deleted file mode 100644 index f679a4776..000000000 --- a/examples/webengine/recipebrowser/resources/pages/assets/3rdparty/marked.min.js +++ /dev/null @@ -1,6 +0,0 @@ -/** - * marked - a markdown parser - * Copyright (c) 2011-2014, Christopher Jeffrey. (MIT Licensed) - * https://github.com/chjj/marked - */ -(function(){var block={newline:/^\n+/,code:/^( {4}[^\n]+\n*)+/,fences:noop,hr:/^( *[-*_]){3,} *(?:\n+|$)/,heading:/^ *(#{1,6}) *([^\n]+?) *#* *(?:\n+|$)/,nptable:noop,lheading:/^([^\n]+)\n *(=|-){2,} *(?:\n+|$)/,blockquote:/^( *>[^\n]+(\n(?!def)[^\n]+)*\n*)+/,list:/^( *)(bull) [\s\S]+?(?:hr|def|\n{2,}(?! )(?!\1bull )\n*|\s*$)/,html:/^ *(?:comment *(?:\n|\s*$)|closed *(?:\n{2,}|\s*$)|closing *(?:\n{2,}|\s*$))/,def:/^ *\[([^\]]+)\]: *<?([^\s>]+)>?(?: +["(]([^\n]+)[")])? *(?:\n+|$)/,table:noop,paragraph:/^((?:[^\n]+\n?(?!hr|heading|lheading|blockquote|tag|def))+)\n*/,text:/^[^\n]+/};block.bullet=/(?:[*+-]|\d+\.)/;block.item=/^( *)(bull) [^\n]*(?:\n(?!\1bull )[^\n]*)*/;block.item=replace(block.item,"gm")(/bull/g,block.bullet)();block.list=replace(block.list)(/bull/g,block.bullet)("hr","\\n+(?=\\1?(?:[-*_] *){3,}(?:\\n+|$))")("def","\\n+(?="+block.def.source+")")();block.blockquote=replace(block.blockquote)("def",block.def)();block._tag="(?!(?:"+"a|em|strong|small|s|cite|q|dfn|abbr|data|time|code"+"|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo"+"|span|br|wbr|ins|del|img)\\b)\\w+(?!:/|[^\\w\\s@]*@)\\b";block.html=replace(block.html)("comment",/<!--[\s\S]*?-->/)("closed",/<(tag)[\s\S]+?<\/\1>/)("closing",/<tag(?:"[^"]*"|'[^']*'|[^'">])*?>/)(/tag/g,block._tag)();block.paragraph=replace(block.paragraph)("hr",block.hr)("heading",block.heading)("lheading",block.lheading)("blockquote",block.blockquote)("tag","<"+block._tag)("def",block.def)();block.normal=merge({},block);block.gfm=merge({},block.normal,{fences:/^ *(`{3,}|~{3,})[ \.]*(\S+)? *\n([\s\S]*?)\s*\1 *(?:\n+|$)/,paragraph:/^/,heading:/^ *(#{1,6}) +([^\n]+?) *#* *(?:\n+|$)/});block.gfm.paragraph=replace(block.paragraph)("(?!","(?!"+block.gfm.fences.source.replace("\\1","\\2")+"|"+block.list.source.replace("\\1","\\3")+"|")();block.tables=merge({},block.gfm,{nptable:/^ *(\S.*\|.*)\n *([-:]+ *\|[-| :]*)\n((?:.*\|.*(?:\n|$))*)\n*/,table:/^ *\|(.+)\n *\|( *[-:]+[-| :]*)\n((?: *\|.*(?:\n|$))*)\n*/});function Lexer(options){this.tokens=[];this.tokens.links={};this.options=options||marked.defaults;this.rules=block.normal;if(this.options.gfm){if(this.options.tables){this.rules=block.tables}else{this.rules=block.gfm}}}Lexer.rules=block;Lexer.lex=function(src,options){var lexer=new Lexer(options);return lexer.lex(src)};Lexer.prototype.lex=function(src){src=src.replace(/\r\n|\r/g,"\n").replace(/\t/g," ").replace(/\u00a0/g," ").replace(/\u2424/g,"\n");return this.token(src,true)};Lexer.prototype.token=function(src,top,bq){var src=src.replace(/^ +$/gm,""),next,loose,cap,bull,b,item,space,i,l;while(src){if(cap=this.rules.newline.exec(src)){src=src.substring(cap[0].length);if(cap[0].length>1){this.tokens.push({type:"space"})}}if(cap=this.rules.code.exec(src)){src=src.substring(cap[0].length);cap=cap[0].replace(/^ {4}/gm,"");this.tokens.push({type:"code",text:!this.options.pedantic?cap.replace(/\n+$/,""):cap});continue}if(cap=this.rules.fences.exec(src)){src=src.substring(cap[0].length);this.tokens.push({type:"code",lang:cap[2],text:cap[3]||""});continue}if(cap=this.rules.heading.exec(src)){src=src.substring(cap[0].length);this.tokens.push({type:"heading",depth:cap[1].length,text:cap[2]});continue}if(top&&(cap=this.rules.nptable.exec(src))){src=src.substring(cap[0].length);item={type:"table",header:cap[1].replace(/^ *| *\| *$/g,"").split(/ *\| */),align:cap[2].replace(/^ *|\| *$/g,"").split(/ *\| */),cells:cap[3].replace(/\n$/,"").split("\n")};for(i=0;i<item.align.length;i++){if(/^ *-+: *$/.test(item.align[i])){item.align[i]="right"}else if(/^ *:-+: *$/.test(item.align[i])){item.align[i]="center"}else if(/^ *:-+ *$/.test(item.align[i])){item.align[i]="left"}else{item.align[i]=null}}for(i=0;i<item.cells.length;i++){item.cells[i]=item.cells[i].split(/ *\| */)}this.tokens.push(item);continue}if(cap=this.rules.lheading.exec(src)){src=src.substring(cap[0].length);this.tokens.push({type:"heading",depth:cap[2]==="="?1:2,text:cap[1]});continue}if(cap=this.rules.hr.exec(src)){src=src.substring(cap[0].length);this.tokens.push({type:"hr"});continue}if(cap=this.rules.blockquote.exec(src)){src=src.substring(cap[0].length);this.tokens.push({type:"blockquote_start"});cap=cap[0].replace(/^ *> ?/gm,"");this.token(cap,top,true);this.tokens.push({type:"blockquote_end"});continue}if(cap=this.rules.list.exec(src)){src=src.substring(cap[0].length);bull=cap[2];this.tokens.push({type:"list_start",ordered:bull.length>1});cap=cap[0].match(this.rules.item);next=false;l=cap.length;i=0;for(;i<l;i++){item=cap[i];space=item.length;item=item.replace(/^ *([*+-]|\d+\.) +/,"");if(~item.indexOf("\n ")){space-=item.length;item=!this.options.pedantic?item.replace(new RegExp("^ {1,"+space+"}","gm"),""):item.replace(/^ {1,4}/gm,"")}if(this.options.smartLists&&i!==l-1){b=block.bullet.exec(cap[i+1])[0];if(bull!==b&&!(bull.length>1&&b.length>1)){src=cap.slice(i+1).join("\n")+src;i=l-1}}loose=next||/\n\n(?!\s*$)/.test(item);if(i!==l-1){next=item.charAt(item.length-1)==="\n";if(!loose)loose=next}this.tokens.push({type:loose?"loose_item_start":"list_item_start"});this.token(item,false,bq);this.tokens.push({type:"list_item_end"})}this.tokens.push({type:"list_end"});continue}if(cap=this.rules.html.exec(src)){src=src.substring(cap[0].length);this.tokens.push({type:this.options.sanitize?"paragraph":"html",pre:!this.options.sanitizer&&(cap[1]==="pre"||cap[1]==="script"||cap[1]==="style"),text:cap[0]});continue}if(!bq&&top&&(cap=this.rules.def.exec(src))){src=src.substring(cap[0].length);this.tokens.links[cap[1].toLowerCase()]={href:cap[2],title:cap[3]};continue}if(top&&(cap=this.rules.table.exec(src))){src=src.substring(cap[0].length);item={type:"table",header:cap[1].replace(/^ *| *\| *$/g,"").split(/ *\| */),align:cap[2].replace(/^ *|\| *$/g,"").split(/ *\| */),cells:cap[3].replace(/(?: *\| *)?\n$/,"").split("\n")};for(i=0;i<item.align.length;i++){if(/^ *-+: *$/.test(item.align[i])){item.align[i]="right"}else if(/^ *:-+: *$/.test(item.align[i])){item.align[i]="center"}else if(/^ *:-+ *$/.test(item.align[i])){item.align[i]="left"}else{item.align[i]=null}}for(i=0;i<item.cells.length;i++){item.cells[i]=item.cells[i].replace(/^ *\| *| *\| *$/g,"").split(/ *\| */)}this.tokens.push(item);continue}if(top&&(cap=this.rules.paragraph.exec(src))){src=src.substring(cap[0].length);this.tokens.push({type:"paragraph",text:cap[1].charAt(cap[1].length-1)==="\n"?cap[1].slice(0,-1):cap[1]});continue}if(cap=this.rules.text.exec(src)){src=src.substring(cap[0].length);this.tokens.push({type:"text",text:cap[0]});continue}if(src){throw new Error("Infinite loop on byte: "+src.charCodeAt(0))}}return this.tokens};var inline={escape:/^\\([\\`*{}\[\]()#+\-.!_>])/,autolink:/^<([^ >]+(@|:\/)[^ >]+)>/,url:noop,tag:/^<!--[\s\S]*?-->|^<\/?\w+(?:"[^"]*"|'[^']*'|[^'">])*?>/,link:/^!?\[(inside)\]\(href\)/,reflink:/^!?\[(inside)\]\s*\[([^\]]*)\]/,nolink:/^!?\[((?:\[[^\]]*\]|[^\[\]])*)\]/,strong:/^__([\s\S]+?)__(?!_)|^\*\*([\s\S]+?)\*\*(?!\*)/,em:/^\b_((?:[^_]|__)+?)_\b|^\*((?:\*\*|[\s\S])+?)\*(?!\*)/,code:/^(`+)\s*([\s\S]*?[^`])\s*\1(?!`)/,br:/^ {2,}\n(?!\s*$)/,del:noop,text:/^[\s\S]+?(?=[\\<!\[_*`]| {2,}\n|$)/};inline._inside=/(?:\[[^\]]*\]|[^\[\]]|\](?=[^\[]*\]))*/;inline._href=/\s*<?([\s\S]*?)>?(?:\s+['"]([\s\S]*?)['"])?\s*/;inline.link=replace(inline.link)("inside",inline._inside)("href",inline._href)();inline.reflink=replace(inline.reflink)("inside",inline._inside)();inline.normal=merge({},inline);inline.pedantic=merge({},inline.normal,{strong:/^__(?=\S)([\s\S]*?\S)__(?!_)|^\*\*(?=\S)([\s\S]*?\S)\*\*(?!\*)/,em:/^_(?=\S)([\s\S]*?\S)_(?!_)|^\*(?=\S)([\s\S]*?\S)\*(?!\*)/});inline.gfm=merge({},inline.normal,{escape:replace(inline.escape)("])","~|])")(),url:/^(https?:\/\/[^\s<]+[^<.,:;"')\]\s])/,del:/^~~(?=\S)([\s\S]*?\S)~~/,text:replace(inline.text)("]|","~]|")("|","|https?://|")()});inline.breaks=merge({},inline.gfm,{br:replace(inline.br)("{2,}","*")(),text:replace(inline.gfm.text)("{2,}","*")()});function InlineLexer(links,options){this.options=options||marked.defaults;this.links=links;this.rules=inline.normal;this.renderer=this.options.renderer||new Renderer;this.renderer.options=this.options;if(!this.links){throw new Error("Tokens array requires a `links` property.")}if(this.options.gfm){if(this.options.breaks){this.rules=inline.breaks}else{this.rules=inline.gfm}}else if(this.options.pedantic){this.rules=inline.pedantic}}InlineLexer.rules=inline;InlineLexer.output=function(src,links,options){var inline=new InlineLexer(links,options);return inline.output(src)};InlineLexer.prototype.output=function(src){var out="",link,text,href,cap;while(src){if(cap=this.rules.escape.exec(src)){src=src.substring(cap[0].length);out+=cap[1];continue}if(cap=this.rules.autolink.exec(src)){src=src.substring(cap[0].length);if(cap[2]==="@"){text=cap[1].charAt(6)===":"?this.mangle(cap[1].substring(7)):this.mangle(cap[1]);href=this.mangle("mailto:")+text}else{text=escape(cap[1]);href=text}out+=this.renderer.link(href,null,text);continue}if(!this.inLink&&(cap=this.rules.url.exec(src))){src=src.substring(cap[0].length);text=escape(cap[1]);href=text;out+=this.renderer.link(href,null,text);continue}if(cap=this.rules.tag.exec(src)){if(!this.inLink&&/^<a /i.test(cap[0])){this.inLink=true}else if(this.inLink&&/^<\/a>/i.test(cap[0])){this.inLink=false}src=src.substring(cap[0].length);out+=this.options.sanitize?this.options.sanitizer?this.options.sanitizer(cap[0]):escape(cap[0]):cap[0];continue}if(cap=this.rules.link.exec(src)){src=src.substring(cap[0].length);this.inLink=true;out+=this.outputLink(cap,{href:cap[2],title:cap[3]});this.inLink=false;continue}if((cap=this.rules.reflink.exec(src))||(cap=this.rules.nolink.exec(src))){src=src.substring(cap[0].length);link=(cap[2]||cap[1]).replace(/\s+/g," ");link=this.links[link.toLowerCase()];if(!link||!link.href){out+=cap[0].charAt(0);src=cap[0].substring(1)+src;continue}this.inLink=true;out+=this.outputLink(cap,link);this.inLink=false;continue}if(cap=this.rules.strong.exec(src)){src=src.substring(cap[0].length);out+=this.renderer.strong(this.output(cap[2]||cap[1]));continue}if(cap=this.rules.em.exec(src)){src=src.substring(cap[0].length);out+=this.renderer.em(this.output(cap[2]||cap[1]));continue}if(cap=this.rules.code.exec(src)){src=src.substring(cap[0].length);out+=this.renderer.codespan(escape(cap[2],true));continue}if(cap=this.rules.br.exec(src)){src=src.substring(cap[0].length);out+=this.renderer.br();continue}if(cap=this.rules.del.exec(src)){src=src.substring(cap[0].length);out+=this.renderer.del(this.output(cap[1]));continue}if(cap=this.rules.text.exec(src)){src=src.substring(cap[0].length);out+=this.renderer.text(escape(this.smartypants(cap[0])));continue}if(src){throw new Error("Infinite loop on byte: "+src.charCodeAt(0))}}return out};InlineLexer.prototype.outputLink=function(cap,link){var href=escape(link.href),title=link.title?escape(link.title):null;return cap[0].charAt(0)!=="!"?this.renderer.link(href,title,this.output(cap[1])):this.renderer.image(href,title,escape(cap[1]))};InlineLexer.prototype.smartypants=function(text){if(!this.options.smartypants)return text;return text.replace(/---/g,"—").replace(/--/g,"–").replace(/(^|[-\u2014/(\[{"\s])'/g,"$1‘").replace(/'/g,"’").replace(/(^|[-\u2014/(\[{\u2018\s])"/g,"$1“").replace(/"/g,"”").replace(/\.{3}/g,"…")};InlineLexer.prototype.mangle=function(text){if(!this.options.mangle)return text;var out="",l=text.length,i=0,ch;for(;i<l;i++){ch=text.charCodeAt(i);if(Math.random()>.5){ch="x"+ch.toString(16)}out+="&#"+ch+";"}return out};function Renderer(options){this.options=options||{}}Renderer.prototype.code=function(code,lang,escaped){if(this.options.highlight){var out=this.options.highlight(code,lang);if(out!=null&&out!==code){escaped=true;code=out}}if(!lang){return"<pre><code>"+(escaped?code:escape(code,true))+"\n</code></pre>"}return'<pre><code class="'+this.options.langPrefix+escape(lang,true)+'">'+(escaped?code:escape(code,true))+"\n</code></pre>\n"};Renderer.prototype.blockquote=function(quote){return"<blockquote>\n"+quote+"</blockquote>\n"};Renderer.prototype.html=function(html){return html};Renderer.prototype.heading=function(text,level,raw){return"<h"+level+' id="'+this.options.headerPrefix+raw.toLowerCase().replace(/[^\w]+/g,"-")+'">'+text+"</h"+level+">\n"};Renderer.prototype.hr=function(){return this.options.xhtml?"<hr/>\n":"<hr>\n"};Renderer.prototype.list=function(body,ordered){var type=ordered?"ol":"ul";return"<"+type+">\n"+body+"</"+type+">\n"};Renderer.prototype.listitem=function(text){return"<li>"+text+"</li>\n"};Renderer.prototype.paragraph=function(text){return"<p>"+text+"</p>\n"};Renderer.prototype.table=function(header,body){return"<table>\n"+"<thead>\n"+header+"</thead>\n"+"<tbody>\n"+body+"</tbody>\n"+"</table>\n"};Renderer.prototype.tablerow=function(content){return"<tr>\n"+content+"</tr>\n"};Renderer.prototype.tablecell=function(content,flags){var type=flags.header?"th":"td";var tag=flags.align?"<"+type+' style="text-align:'+flags.align+'">':"<"+type+">";return tag+content+"</"+type+">\n"};Renderer.prototype.strong=function(text){return"<strong>"+text+"</strong>"};Renderer.prototype.em=function(text){return"<em>"+text+"</em>"};Renderer.prototype.codespan=function(text){return"<code>"+text+"</code>"};Renderer.prototype.br=function(){return this.options.xhtml?"<br/>":"<br>"};Renderer.prototype.del=function(text){return"<del>"+text+"</del>"};Renderer.prototype.link=function(href,title,text){if(this.options.sanitize){try{var prot=decodeURIComponent(unescape(href)).replace(/[^\w:]/g,"").toLowerCase()}catch(e){return""}if(prot.indexOf("javascript:")===0||prot.indexOf("vbscript:")===0){return""}}var out='<a href="'+href+'"';if(title){out+=' title="'+title+'"'}out+=">"+text+"</a>";return out};Renderer.prototype.image=function(href,title,text){var out='<img src="'+href+'" alt="'+text+'"';if(title){out+=' title="'+title+'"'}out+=this.options.xhtml?"/>":">";return out};Renderer.prototype.text=function(text){return text};function Parser(options){this.tokens=[];this.token=null;this.options=options||marked.defaults;this.options.renderer=this.options.renderer||new Renderer;this.renderer=this.options.renderer;this.renderer.options=this.options}Parser.parse=function(src,options,renderer){var parser=new Parser(options,renderer);return parser.parse(src)};Parser.prototype.parse=function(src){this.inline=new InlineLexer(src.links,this.options,this.renderer);this.tokens=src.reverse();var out="";while(this.next()){out+=this.tok()}return out};Parser.prototype.next=function(){return this.token=this.tokens.pop()};Parser.prototype.peek=function(){return this.tokens[this.tokens.length-1]||0};Parser.prototype.parseText=function(){var body=this.token.text;while(this.peek().type==="text"){body+="\n"+this.next().text}return this.inline.output(body)};Parser.prototype.tok=function(){switch(this.token.type){case"space":{return""}case"hr":{return this.renderer.hr()}case"heading":{return this.renderer.heading(this.inline.output(this.token.text),this.token.depth,this.token.text)}case"code":{return this.renderer.code(this.token.text,this.token.lang,this.token.escaped)}case"table":{var header="",body="",i,row,cell,flags,j;cell="";for(i=0;i<this.token.header.length;i++){flags={header:true,align:this.token.align[i]};cell+=this.renderer.tablecell(this.inline.output(this.token.header[i]),{header:true,align:this.token.align[i]})}header+=this.renderer.tablerow(cell);for(i=0;i<this.token.cells.length;i++){row=this.token.cells[i];cell="";for(j=0;j<row.length;j++){cell+=this.renderer.tablecell(this.inline.output(row[j]),{header:false,align:this.token.align[j]})}body+=this.renderer.tablerow(cell)}return this.renderer.table(header,body)}case"blockquote_start":{var body="";while(this.next().type!=="blockquote_end"){body+=this.tok()}return this.renderer.blockquote(body)}case"list_start":{var body="",ordered=this.token.ordered;while(this.next().type!=="list_end"){body+=this.tok()}return this.renderer.list(body,ordered)}case"list_item_start":{var body="";while(this.next().type!=="list_item_end"){body+=this.token.type==="text"?this.parseText():this.tok()}return this.renderer.listitem(body)}case"loose_item_start":{var body="";while(this.next().type!=="list_item_end"){body+=this.tok()}return this.renderer.listitem(body)}case"html":{var html=!this.token.pre&&!this.options.pedantic?this.inline.output(this.token.text):this.token.text;return this.renderer.html(html)}case"paragraph":{return this.renderer.paragraph(this.inline.output(this.token.text))}case"text":{return this.renderer.paragraph(this.parseText())}}};function escape(html,encode){return html.replace(!encode?/&(?!#?\w+;)/g:/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'")}function unescape(html){return html.replace(/&([#\w]+);/g,function(_,n){n=n.toLowerCase();if(n==="colon")return":";if(n.charAt(0)==="#"){return n.charAt(1)==="x"?String.fromCharCode(parseInt(n.substring(2),16)):String.fromCharCode(+n.substring(1))}return""})}function replace(regex,opt){regex=regex.source;opt=opt||"";return function self(name,val){if(!name)return new RegExp(regex,opt);val=val.source||val;val=val.replace(/(^|[^\[])\^/g,"$1");regex=regex.replace(name,val);return self}}function noop(){}noop.exec=noop;function merge(obj){var i=1,target,key;for(;i<arguments.length;i++){target=arguments[i];for(key in target){if(Object.prototype.hasOwnProperty.call(target,key)){obj[key]=target[key]}}}return obj}function marked(src,opt,callback){if(callback||typeof opt==="function"){if(!callback){callback=opt;opt=null}opt=merge({},marked.defaults,opt||{});var highlight=opt.highlight,tokens,pending,i=0;try{tokens=Lexer.lex(src,opt)}catch(e){return callback(e)}pending=tokens.length;var done=function(err){if(err){opt.highlight=highlight;return callback(err)}var out;try{out=Parser.parse(tokens,opt)}catch(e){err=e}opt.highlight=highlight;return err?callback(err):callback(null,out)};if(!highlight||highlight.length<3){return done()}delete opt.highlight;if(!pending)return done();for(;i<tokens.length;i++){(function(token){if(token.type!=="code"){return--pending||done()}return highlight(token.text,token.lang,function(err,code){if(err)return done(err);if(code==null||code===token.text){return--pending||done()}token.text=code;token.escaped=true;--pending||done()})})(tokens[i])}return}try{if(opt)opt=merge({},marked.defaults,opt);return Parser.parse(Lexer.lex(src,opt),opt)}catch(e){e.message+="\nPlease report this to https://github.com/chjj/marked.";if((opt||marked.defaults).silent){return"<p>An error occurred:</p><pre>"+escape(e.message+"",true)+"</pre>"}throw e}}marked.options=marked.setOptions=function(opt){merge(marked.defaults,opt);return marked};marked.defaults={gfm:true,tables:true,breaks:false,pedantic:false,sanitize:false,sanitizer:null,mangle:true,smartLists:false,silent:false,highlight:null,langPrefix:"lang-",smartypants:false,headerPrefix:"",renderer:new Renderer,xhtml:false};marked.Parser=Parser;marked.parser=Parser.parse;marked.Renderer=Renderer;marked.Lexer=Lexer;marked.lexer=Lexer.lex;marked.InlineLexer=InlineLexer;marked.inlineLexer=InlineLexer.output;marked.parse=marked;if(typeof module!=="undefined"&&typeof exports==="object"){module.exports=marked}else if(typeof define==="function"&&define.amd){define(function(){return marked})}else{this.marked=marked}}).call(function(){return this||(typeof window!=="undefined"?window:global)}()); diff --git a/examples/webengine/recipebrowser/resources/pages/assets/3rdparty/qt_attribution.json b/examples/webengine/recipebrowser/resources/pages/assets/3rdparty/qt_attribution.json index 42947ca0b..a6c68f46f 100644 --- a/examples/webengine/recipebrowser/resources/pages/assets/3rdparty/qt_attribution.json +++ b/examples/webengine/recipebrowser/resources/pages/assets/3rdparty/qt_attribution.json @@ -5,12 +5,12 @@ "QDocModule": "qtwebengine", "QtUsage": "Marked is used in the WebEngine RecipeBrowser example", "QtParts": [ "examples" ], - "Files": "marked.min.js", + "Files": "marked.js", "Description": "A full-featured markdown parser and compiler, written in JavaScript. Built for speed.", "Homepage": "https://github.com/chjj/marked", - "Version": "0.3.6", - "DownloadLocation": "https://github.com/chjj/marked/blob/v0.3.6/marked.min.js", - "Copyright": "Copyright (c) 2011-2014, Christopher Jeffrey", + "Version": "0.4.0", + "DownloadLocation": "https://github.com/markedjs/marked/blob/0.4.0/lib/marked.js", + "Copyright": "Copyright (c) 2011-2018, Christopher Jeffrey", "License": "MIT License", "LicenseId": "MIT", "LicenseFile": "MARKED-LICENSE.txt" diff --git a/examples/webengine/recipebrowser/resources/pages/burger.html b/examples/webengine/recipebrowser/resources/pages/burger.html index 315c0a866..b10170cbd 100644 --- a/examples/webengine/recipebrowser/resources/pages/burger.html +++ b/examples/webengine/recipebrowser/resources/pages/burger.html @@ -67,7 +67,7 @@ give it a minute to go gorgeous and sloppy. </div><!--End of content--> - <script src="assets/3rdparty/marked.min.js"></script> + <script src="assets/3rdparty/marked.js"></script> <script src="assets/custom.js"></script> </body> </html> diff --git a/examples/webengine/recipebrowser/resources/pages/cupcakes.html b/examples/webengine/recipebrowser/resources/pages/cupcakes.html index c169196f2..e21247825 100644 --- a/examples/webengine/recipebrowser/resources/pages/cupcakes.html +++ b/examples/webengine/recipebrowser/resources/pages/cupcakes.html @@ -45,7 +45,7 @@ Cupcakes **Enjoy!** </div><!--End of content--> - <script src="assets/3rdparty/marked.min.js"></script> + <script src="assets/3rdparty/marked.js"></script> <script src="assets/custom.js"></script> </body> </html> diff --git a/examples/webengine/recipebrowser/resources/pages/pasta.html b/examples/webengine/recipebrowser/resources/pages/pasta.html index c94b0df0a..4de65292e 100644 --- a/examples/webengine/recipebrowser/resources/pages/pasta.html +++ b/examples/webengine/recipebrowser/resources/pages/pasta.html @@ -48,7 +48,7 @@ Pasta **Enjoy!** </div><!--End of content--> - <script src="assets/3rdparty/marked.min.js"></script> + <script src="assets/3rdparty/marked.js"></script> <script src="assets/custom.js"></script> </body> </html> diff --git a/examples/webengine/recipebrowser/resources/pages/pizza.html b/examples/webengine/recipebrowser/resources/pages/pizza.html index a1ebfa18e..c63102934 100644 --- a/examples/webengine/recipebrowser/resources/pages/pizza.html +++ b/examples/webengine/recipebrowser/resources/pages/pizza.html @@ -39,7 +39,7 @@ Pizza Diavola **Enjoy!** </div><!--End of content--> - <script src="assets/3rdparty/marked.min.js"></script> + <script src="assets/3rdparty/marked.js"></script> <script src="assets/custom.js"></script> </body> </html> diff --git a/examples/webengine/recipebrowser/resources/pages/skewers.html b/examples/webengine/recipebrowser/resources/pages/skewers.html index 63d85f7e1..6fbf90cb4 100644 --- a/examples/webengine/recipebrowser/resources/pages/skewers.html +++ b/examples/webengine/recipebrowser/resources/pages/skewers.html @@ -45,7 +45,7 @@ Grilled skewers </div><!--End of content--> - <script src="assets/3rdparty/marked.min.js"></script> + <script src="assets/3rdparty/marked.js"></script> <script src="assets/custom.js"></script> </body> </html> diff --git a/examples/webengine/recipebrowser/resources/pages/soup.html b/examples/webengine/recipebrowser/resources/pages/soup.html index c7537d94c..e99f016cf 100644 --- a/examples/webengine/recipebrowser/resources/pages/soup.html +++ b/examples/webengine/recipebrowser/resources/pages/soup.html @@ -33,7 +33,7 @@ Soup **Enjoy!** </div><!--End of content--> - <script src="assets/3rdparty/marked.min.js"></script> + <script src="assets/3rdparty/marked.js"></script> <script src="assets/custom.js"></script> </body> </html> diff --git a/examples/webengine/recipebrowser/resources/pages/steak.html b/examples/webengine/recipebrowser/resources/pages/steak.html index 1871f0fe8..c58395d10 100644 --- a/examples/webengine/recipebrowser/resources/pages/steak.html +++ b/examples/webengine/recipebrowser/resources/pages/steak.html @@ -59,7 +59,7 @@ Grilled steak and rice </div><!--End of content--> - <script src="assets/3rdparty/marked.min.js"></script> + <script src="assets/3rdparty/marked.js"></script> <script src="assets/custom.js"></script> </body> </html> diff --git a/examples/webengine/recipebrowser/resources/resources.qrc b/examples/webengine/recipebrowser/resources/resources.qrc index 88919159b..bd13dcfae 100644 --- a/examples/webengine/recipebrowser/resources/resources.qrc +++ b/examples/webengine/recipebrowser/resources/resources.qrc @@ -11,7 +11,7 @@ <file>pages/skewers.html</file> <file>pages/cupcakes.html</file> - <file>pages/assets/3rdparty/marked.min.js</file> + <file>pages/assets/3rdparty/marked.js</file> <file>pages/assets/3rdparty/markdown.css</file> <file>pages/assets/custom.css</file> <file>pages/assets/custom.js</file> diff --git a/examples/webenginewidgets/cookiebrowser/3rdparty/COPYING b/examples/webenginewidgets/cookiebrowser/3rdparty/COPYING new file mode 100644 index 000000000..220881da6 --- /dev/null +++ b/examples/webenginewidgets/cookiebrowser/3rdparty/COPYING @@ -0,0 +1 @@ +The icons in this repository are herefore released into the Public Domain. diff --git a/examples/webenginewidgets/cookiebrowser/3rdparty/qt_attribution.json b/examples/webenginewidgets/cookiebrowser/3rdparty/qt_attribution.json new file mode 100644 index 000000000..91a0899c6 --- /dev/null +++ b/examples/webenginewidgets/cookiebrowser/3rdparty/qt_attribution.json @@ -0,0 +1,24 @@ +{ + "Id": "cookiebrowser-tango", + "Name": "Tango Icon Library", + "QDocModule": "qtwebengine", + "QtUsage": "Used in WebEngine Cookie Browser example.", + + "QtParts": [ "examples" ], + "Description": "Selected icons from the Tango Icon Library", + "Homepage": "http://tango.freedesktop.org/Tango_Icon_Library", + "Version": "0.8.90", + "DownloadLocation": "http://tango.freedesktop.org/releases/tango-icon-theme-0.8.90.tar.gz", + "LicenseId": "DocumentRef-PublicDomain", + "License": "Public Domain", + "LicenseFile": "COPYING", + "Copyright": "Ulisse Perusin <uli.peru@gmail.com> +Steven Garrity <sgarrity@silverorange.com> +Lapo Calamandrei <calamandrei@gmail.com> +Ryan Collier <rcollier@novell.com> +Rodney Dawes <dobey@novell.com> +Andreas Nilsson <nisses.mail@home.se> +Tuomas Kuosmanen <tigert@tigert.com> +Garrett LeSage <garrett@novell.com> +Jakub Steiner <jimmac@novell.com>" +} diff --git a/examples/webenginewidgets/cookiebrowser/view-refresh.png b/examples/webenginewidgets/cookiebrowser/3rdparty/view-refresh.png Binary files differindex cab4d02c7..cab4d02c7 100644 --- a/examples/webenginewidgets/cookiebrowser/view-refresh.png +++ b/examples/webenginewidgets/cookiebrowser/3rdparty/view-refresh.png diff --git a/examples/webenginewidgets/cookiebrowser/cookiebrowser.qrc b/examples/webenginewidgets/cookiebrowser/cookiebrowser.qrc index 8805f2c53..a1cebd6a7 100644 --- a/examples/webenginewidgets/cookiebrowser/cookiebrowser.qrc +++ b/examples/webenginewidgets/cookiebrowser/cookiebrowser.qrc @@ -1,5 +1,5 @@ <RCC> <qresource prefix="/"> - <file>view-refresh.png</file> + <file alias="view-refresh.png">3rdparty/view-refresh.png</file> </qresource> </RCC> diff --git a/examples/webenginewidgets/cookiebrowser/doc/src/cookiebrowser.qdoc b/examples/webenginewidgets/cookiebrowser/doc/src/cookiebrowser.qdoc index 55ccd33d5..a1b4eeb18 100644 --- a/examples/webenginewidgets/cookiebrowser/doc/src/cookiebrowser.qdoc +++ b/examples/webenginewidgets/cookiebrowser/doc/src/cookiebrowser.qdoc @@ -38,4 +38,14 @@ well as delete cookies and add new cookies. \include examples-run.qdocinc + + \section1 Files and Attributions + + The example uses icons from the Tango Icon Library: + + \table + \row + \li \l{cookiebrowser-tango}{Tango Icon Library} + \li Public Domain + \endtable */ diff --git a/examples/webenginewidgets/markdowneditor/doc/src/markdowneditor.qdoc b/examples/webenginewidgets/markdowneditor/doc/src/markdowneditor.qdoc index b06d4ee84..9ff8f0615 100644 --- a/examples/webenginewidgets/markdowneditor/doc/src/markdowneditor.qdoc +++ b/examples/webenginewidgets/markdowneditor/doc/src/markdowneditor.qdoc @@ -154,7 +154,7 @@ In the \e index.html, we load a custom stylesheet and two JavaScript libraries. \l{http://kevinburke.bitbucket.org/markdowncss/}{markdown.css} is a markdown-friendly stylesheet created by Kevin Burke. - \l{https://github.com/chjj/marked}{marked.min.js} is a markdown parser and + \l{https://github.com/chjj/marked}{marked.js} is a markdown parser and compiler designed for speed written by Christopher Jeffrey and \e qwebchannel.js is part of the \l{QWebChannel} module. diff --git a/examples/webenginewidgets/markdowneditor/resources/3rdparty/marked.js b/examples/webenginewidgets/markdowneditor/resources/3rdparty/marked.js index 5552616ea..33c02d9cf 100644 --- a/examples/webenginewidgets/markdowneditor/resources/3rdparty/marked.js +++ b/examples/webenginewidgets/markdowneditor/resources/3rdparty/marked.js @@ -16,20 +16,29 @@ var block = { code: /^( {4}[^\n]+\n*)+/, fences: noop, hr: /^ {0,3}((?:- *){3,}|(?:_ *){3,}|(?:\* *){3,})(?:\n+|$)/, - heading: /^ *(#{1,6}) *([^\n]+?) *#* *(?:\n+|$)/, + heading: /^ *(#{1,6}) *([^\n]+?) *(?:#+ *)?(?:\n+|$)/, nptable: noop, blockquote: /^( {0,3}> ?(paragraph|[^\n]*)(?:\n|$))+/, list: /^( *)(bull) [\s\S]+?(?:hr|def|\n{2,}(?! )(?!\1bull )\n*|\s*$)/, - html: /^ *(?:comment *(?:\n|\s*$)|closed *(?:\n{2,}|\s*$)|closing *(?:\n{2,}|\s*$))/, + html: '^ {0,3}(?:' // optional indentation + + '<(script|pre|style)[\\s>][\\s\\S]*?(?:</\\1>[^\\n]*\\n+|$)' // (1) + + '|comment[^\\n]*(\\n+|$)' // (2) + + '|<\\?[\\s\\S]*?\\?>\\n*' // (3) + + '|<![A-Z][\\s\\S]*?>\\n*' // (4) + + '|<!\\[CDATA\\[[\\s\\S]*?\\]\\]>\\n*' // (5) + + '|</?(tag)(?: +|\\n|/?>)[\\s\\S]*?(?:\\n{2,}|$)' // (6) + + '|<(?!script|pre|style)([a-z][\\w-]*)(?:attribute)*? */?>(?=\\h*\\n)[\\s\\S]*?(?:\\n{2,}|$)' // (7) open tag + + '|</(?!script|pre|style)[a-z][\\w-]*\\s*>(?=\\h*\\n)[\\s\\S]*?(?:\\n{2,}|$)' // (7) closing tag + + ')', def: /^ {0,3}\[(label)\]: *\n? *<?([^\s>]+)>?(?:(?: +\n? *| *\n *)(title))? *(?:\n+|$)/, table: noop, lheading: /^([^\n]+)\n *(=|-){2,} *(?:\n+|$)/, - paragraph: /^([^\n]+(?:\n?(?!hr|heading|lheading| {0,3}>|tag)[^\n]+)+)/, + paragraph: /^([^\n]+(?:\n(?!hr|heading|lheading| {0,3}>|<\/?(?:tag)(?: +|\n|\/?>)|<(?:script|pre|style|!--))[^\n]+)*)/, text: /^[^\n]+/ }; -block._label = /(?:\\[\[\]]|[^\[\]])+/; -block._title = /(?:"(?:\\"|[^"]|"[^"\n]*")*"|'\n?(?:[^'\n]+\n?)*'|\([^()]*\))/; +block._label = /(?!\s*\])(?:\\[\[\]]|[^\[\]])+/; +block._title = /(?:"(?:\\"?|[^"\\])*"|'[^'\n]*(?:\n[^'\n]+)*\n?'|\([^()]*\))/; block.def = edit(block.def) .replace('label', block._label) .replace('title', block._title) @@ -47,23 +56,24 @@ block.list = edit(block.list) .replace('def', '\\n+(?=' + block.def.source + ')') .getRegex(); -block._tag = '(?!(?:' - + 'a|em|strong|small|s|cite|q|dfn|abbr|data|time|code' - + '|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo' - + '|span|br|wbr|ins|del|img)\\b)\\w+(?!:|[^\\w\\s@]*@)\\b'; - -block.html = edit(block.html) - .replace('comment', /<!--[\s\S]*?-->/) - .replace('closed', /<(tag)[\s\S]+?<\/\1>/) - .replace('closing', /<tag(?:"[^"]*"|'[^']*'|\s[^'"\/>\s]*)*?\/?>/) - .replace(/tag/g, block._tag) +block._tag = 'address|article|aside|base|basefont|blockquote|body|caption' + + '|center|col|colgroup|dd|details|dialog|dir|div|dl|dt|fieldset|figcaption' + + '|figure|footer|form|frame|frameset|h[1-6]|head|header|hr|html|iframe' + + '|legend|li|link|main|menu|menuitem|meta|nav|noframes|ol|optgroup|option' + + '|p|param|section|source|summary|table|tbody|td|tfoot|th|thead|title|tr' + + '|track|ul'; +block._comment = /<!--(?!-?>)[\s\S]*?-->/; +block.html = edit(block.html, 'i') + .replace('comment', block._comment) + .replace('tag', block._tag) + .replace('attribute', / +[a-zA-Z:_][\w.:-]*(?: *= *"[^"\n]*"| *= *'[^'\n]*'| *= *[^\s"'=<>`]+)?/) .getRegex(); block.paragraph = edit(block.paragraph) .replace('hr', block.hr) .replace('heading', block.heading) .replace('lheading', block.lheading) - .replace('tag', '<' + block._tag) + .replace('tag', block._tag) // pars can be interrupted by type (6) html blocks .getRegex(); block.blockquote = edit(block.blockquote) @@ -97,8 +107,26 @@ block.gfm.paragraph = edit(block.paragraph) */ block.tables = merge({}, block.gfm, { - nptable: /^ *(\S.*\|.*)\n *([-:]+ *\|[-| :]*)\n((?:.*\|.*(?:\n|$))*)\n*/, - table: /^ *\|(.+)\n *\|( *[-:]+[-| :]*)\n((?: *\|.*(?:\n|$))*)\n*/ + nptable: /^ *([^|\n ].*\|.*)\n *([-:]+ *\|[-| :]*)(?:\n((?:.*[^>\n ].*(?:\n|$))*)\n*|$)/, + table: /^ *\|(.+)\n *\|?( *[-:]+[-| :]*)(?:\n((?: *[^>\n ].*(?:\n|$))*)\n*|$)/ +}); + +/** + * Pedantic grammar + */ + +block.pedantic = merge({}, block.normal, { + html: edit( + '^ *(?:comment *(?:\\n|\\s*$)' + + '|<(tag)[\\s\\S]+?</\\1> *(?:\\n{2,}|\\s*$)' // closed tag + + '|<tag(?:"[^"]*"|\'[^\']*\'|\\s[^\'"/>\\s]*)*?/?> *(?:\\n{2,}|\\s*$))') + .replace('comment', block._comment) + .replace(/tag/g, '(?!(?:' + + 'a|em|strong|small|s|cite|q|dfn|abbr|data|time|code|var|samp|kbd|sub' + + '|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo|span|br|wbr|ins|del|img)' + + '\\b)\\w+(?!:|[^\\w\\s@]*@)\\b') + .getRegex(), + def: /^ *\[([^\]]+)\]: *<?([^\s>]+)>?(?: +(["(][^\n]+[")]))? *(?:\n+|$)/ }); /** @@ -111,7 +139,9 @@ function Lexer(options) { this.options = options || marked.defaults; this.rules = block.normal; - if (this.options.gfm) { + if (this.options.pedantic) { + this.rules = block.pedantic; + } else if (this.options.gfm) { if (this.options.tables) { this.rules = block.tables; } else { @@ -165,7 +195,9 @@ Lexer.prototype.token = function(src, top) { i, tag, l, - isordered; + isordered, + istask, + ischecked; while (src) { // newline @@ -215,34 +247,36 @@ Lexer.prototype.token = function(src, top) { // table no leading pipe (gfm) if (top && (cap = this.rules.nptable.exec(src))) { - src = src.substring(cap[0].length); - item = { type: 'table', - header: cap[1].replace(/^ *| *\| *$/g, '').split(/ *\| */), + header: splitCells(cap[1].replace(/^ *| *\| *$/g, '')), align: cap[2].replace(/^ *|\| *$/g, '').split(/ *\| */), - cells: cap[3].replace(/\n$/, '').split('\n') + cells: cap[3] ? cap[3].replace(/\n$/, '').split('\n') : [] }; - for (i = 0; i < item.align.length; i++) { - if (/^ *-+: *$/.test(item.align[i])) { - item.align[i] = 'right'; - } else if (/^ *:-+: *$/.test(item.align[i])) { - item.align[i] = 'center'; - } else if (/^ *:-+ *$/.test(item.align[i])) { - item.align[i] = 'left'; - } else { - item.align[i] = null; + if (item.header.length === item.align.length) { + src = src.substring(cap[0].length); + + for (i = 0; i < item.align.length; i++) { + if (/^ *-+: *$/.test(item.align[i])) { + item.align[i] = 'right'; + } else if (/^ *:-+: *$/.test(item.align[i])) { + item.align[i] = 'center'; + } else if (/^ *:-+ *$/.test(item.align[i])) { + item.align[i] = 'left'; + } else { + item.align[i] = null; + } } - } - for (i = 0; i < item.cells.length; i++) { - item.cells[i] = item.cells[i].split(/ *\| */); - } + for (i = 0; i < item.cells.length; i++) { + item.cells[i] = splitCells(item.cells[i], item.header.length); + } - this.tokens.push(item); + this.tokens.push(item); - continue; + continue; + } } // hr @@ -331,10 +365,20 @@ Lexer.prototype.token = function(src, top) { if (!loose) loose = next; } + // Check for task list items + istask = /^\[[ xX]\] /.test(item); + ischecked = undefined; + if (istask) { + ischecked = item[1] !== ' '; + item = item.replace(/^\[[ xX]\] +/, ''); + } + this.tokens.push({ type: loose ? 'loose_item_start' - : 'list_item_start' + : 'list_item_start', + task: istask, + checked: ischecked }); // Recurse. @@ -370,7 +414,7 @@ Lexer.prototype.token = function(src, top) { if (top && (cap = this.rules.def.exec(src))) { src = src.substring(cap[0].length); if (cap[3]) cap[3] = cap[3].substring(1, cap[3].length - 1); - tag = cap[1].toLowerCase(); + tag = cap[1].toLowerCase().replace(/\s+/g, ' '); if (!this.tokens.links[tag]) { this.tokens.links[tag] = { href: cap[2], @@ -382,36 +426,38 @@ Lexer.prototype.token = function(src, top) { // table (gfm) if (top && (cap = this.rules.table.exec(src))) { - src = src.substring(cap[0].length); - item = { type: 'table', - header: cap[1].replace(/^ *| *\| *$/g, '').split(/ *\| */), + header: splitCells(cap[1].replace(/^ *| *\| *$/g, '')), align: cap[2].replace(/^ *|\| *$/g, '').split(/ *\| */), - cells: cap[3].replace(/(?: *\| *)?\n$/, '').split('\n') + cells: cap[3] ? cap[3].replace(/(?: *\| *)?\n$/, '').split('\n') : [] }; - for (i = 0; i < item.align.length; i++) { - if (/^ *-+: *$/.test(item.align[i])) { - item.align[i] = 'right'; - } else if (/^ *:-+: *$/.test(item.align[i])) { - item.align[i] = 'center'; - } else if (/^ *:-+ *$/.test(item.align[i])) { - item.align[i] = 'left'; - } else { - item.align[i] = null; + if (item.header.length === item.align.length) { + src = src.substring(cap[0].length); + + for (i = 0; i < item.align.length; i++) { + if (/^ *-+: *$/.test(item.align[i])) { + item.align[i] = 'right'; + } else if (/^ *:-+: *$/.test(item.align[i])) { + item.align[i] = 'center'; + } else if (/^ *:-+ *$/.test(item.align[i])) { + item.align[i] = 'left'; + } else { + item.align[i] = null; + } } - } - for (i = 0; i < item.cells.length; i++) { - item.cells[i] = item.cells[i] - .replace(/^ *\| *| *\| *$/g, '') - .split(/ *\| */); - } + for (i = 0; i < item.cells.length; i++) { + item.cells[i] = splitCells( + item.cells[i].replace(/^ *\| *| *\| *$/g, ''), + item.header.length); + } - this.tokens.push(item); + this.tokens.push(item); - continue; + continue; + } } // lheading @@ -461,39 +507,54 @@ Lexer.prototype.token = function(src, top) { */ var inline = { - escape: /^\\([\\`*{}\[\]()#+\-.!_>])/, + escape: /^\\([!"#$%&'()*+,\-./:;<=>?@\[\]\\^_`{|}~])/, autolink: /^<(scheme:[^\s\x00-\x1f<>]*|email)>/, url: noop, - tag: /^<!--[\s\S]*?-->|^<\/?[a-zA-Z0-9\-]+(?:"[^"]*"|'[^']*'|\s[^<'">\/\s]*)*?\/?>/, - link: /^!?\[(inside)\]\(href\)/, - reflink: /^!?\[(inside)\]\s*\[([^\]]*)\]/, - nolink: /^!?\[((?:\[[^\[\]]*\]|\\[\[\]]|[^\[\]])*)\]/, - strong: /^__([\s\S]+?)__(?!_)|^\*\*([\s\S]+?)\*\*(?!\*)/, - em: /^_([^\s_](?:[^_]|__)+?[^\s_])_\b|^\*((?:\*\*|[^*])+?)\*(?!\*)/, + tag: '^comment' + + '|^</[a-zA-Z][\\w:-]*\\s*>' // self-closing tag + + '|^<[a-zA-Z][\\w-]*(?:attribute)*?\\s*/?>' // open tag + + '|^<\\?[\\s\\S]*?\\?>' // processing instruction, e.g. <?php ?> + + '|^<![a-zA-Z]+\\s[\\s\\S]*?>' // declaration, e.g. <!DOCTYPE html> + + '|^<!\\[CDATA\\[[\\s\\S]*?\\]\\]>', // CDATA section + link: /^!?\[(label)\]\(href(?:\s+(title))?\s*\)/, + reflink: /^!?\[(label)\]\[(?!\s*\])((?:\\[\[\]]?|[^\[\]\\])+)\]/, + nolink: /^!?\[(?!\s*\])((?:\[[^\[\]]*\]|\\[\[\]]|[^\[\]])*)\](?:\[\])?/, + strong: /^__([^\s][\s\S]*?[^\s])__(?!_)|^\*\*([^\s][\s\S]*?[^\s])\*\*(?!\*)|^__([^\s])__(?!_)|^\*\*([^\s])\*\*(?!\*)/, + em: /^_([^\s][\s\S]*?[^\s_])_(?!_)|^_([^\s_][\s\S]*?[^\s])_(?!_)|^\*([^\s][\s\S]*?[^\s*])\*(?!\*)|^\*([^\s*][\s\S]*?[^\s])\*(?!\*)|^_([^\s_])_(?!_)|^\*([^\s*])\*(?!\*)/, code: /^(`+)\s*([\s\S]*?[^`]?)\s*\1(?!`)/, br: /^ {2,}\n(?!\s*$)/, del: noop, text: /^[\s\S]+?(?=[\\<!\[`*]|\b_| {2,}\n|$)/ }; +inline._escapes = /\\([!"#$%&'()*+,\-./:;<=>?@\[\]\\^_`{|}~])/g; + inline._scheme = /[a-zA-Z][a-zA-Z0-9+.-]{1,31}/; inline._email = /[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+(@)[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+(?![-_])/; - inline.autolink = edit(inline.autolink) .replace('scheme', inline._scheme) .replace('email', inline._email) - .getRegex() + .getRegex(); + +inline._attribute = /\s+[a-zA-Z:_][\w.:-]*(?:\s*=\s*"[^"]*"|\s*=\s*'[^']*'|\s*=\s*[^\s"'=<>`]+)?/; + +inline.tag = edit(inline.tag) + .replace('comment', block._comment) + .replace('attribute', inline._attribute) + .getRegex(); -inline._inside = /(?:\[[^\[\]]*\]|\\[\[\]]|[^\[\]]|\](?=[^\[]*\]))*/; -inline._href = /\s*<?([\s\S]*?)>?(?:\s+['"]([\s\S]*?)['"])?\s*/; +inline._label = /(?:\[[^\[\]]*\]|\\[\[\]]?|`[^`]*`|[^\[\]\\])*?/; +inline._href = /\s*(<(?:\\[<>]?|[^\s<>\\])*>|(?:\\[()]?|\([^\s\x00-\x1f()\\]*\)|[^\s\x00-\x1f()\\])*?)/; +inline._title = /"(?:\\"?|[^"\\])*"|'(?:\\'?|[^'\\])*'|\((?:\\\)?|[^)\\])*\)/; inline.link = edit(inline.link) - .replace('inside', inline._inside) + .replace('label', inline._label) .replace('href', inline._href) + .replace('title', inline._title) .getRegex(); inline.reflink = edit(inline.reflink) - .replace('inside', inline._inside) + .replace('label', inline._label) .getRegex(); /** @@ -508,7 +569,13 @@ inline.normal = merge({}, inline); inline.pedantic = merge({}, inline.normal, { strong: /^__(?=\S)([\s\S]*?\S)__(?!_)|^\*\*(?=\S)([\s\S]*?\S)\*\*(?!\*)/, - em: /^_(?=\S)([\s\S]*?\S)_(?!_)|^\*(?=\S)([\s\S]*?\S)\*(?!\*)/ + em: /^_(?=\S)([\s\S]*?\S)_(?!_)|^\*(?=\S)([\s\S]*?\S)\*(?!\*)/, + link: edit(/^!?\[(label)\]\((.*?)\)/) + .replace('label', inline._label) + .getRegex(), + reflink: edit(/^!?\[(label)\]\s*\[([^\]]*)\]/) + .replace('label', inline._label) + .getRegex() }); /** @@ -552,14 +619,14 @@ function InlineLexer(links, options) { throw new Error('Tokens array requires a `links` property.'); } - if (this.options.gfm) { + if (this.options.pedantic) { + this.rules = inline.pedantic; + } else if (this.options.gfm) { if (this.options.breaks) { this.rules = inline.breaks; } else { this.rules = inline.gfm; } - } else if (this.options.pedantic) { - this.rules = inline.pedantic; } } @@ -587,6 +654,7 @@ InlineLexer.prototype.output = function(src) { link, text, href, + title, cap; while (src) { @@ -650,9 +718,23 @@ InlineLexer.prototype.output = function(src) { if (cap = this.rules.link.exec(src)) { src = src.substring(cap[0].length); this.inLink = true; + href = cap[2]; + if (this.options.pedantic) { + link = /^([^'"]*[^\s])\s+(['"])(.*)\2/.exec(href); + + if (link) { + href = link[1]; + title = link[3]; + } else { + title = ''; + } + } else { + title = cap[3] ? cap[3].slice(1, -1) : ''; + } + href = href.trim().replace(/^<([\s\S]*)>$/, '$1'); out += this.outputLink(cap, { - href: cap[2], - title: cap[3] + href: InlineLexer.escapes(href), + title: InlineLexer.escapes(title) }); this.inLink = false; continue; @@ -678,14 +760,14 @@ InlineLexer.prototype.output = function(src) { // strong if (cap = this.rules.strong.exec(src)) { src = src.substring(cap[0].length); - out += this.renderer.strong(this.output(cap[2] || cap[1])); + out += this.renderer.strong(this.output(cap[4] || cap[3] || cap[2] || cap[1])); continue; } // em if (cap = this.rules.em.exec(src)) { src = src.substring(cap[0].length); - out += this.renderer.em(this.output(cap[2] || cap[1])); + out += this.renderer.em(this.output(cap[6] || cap[5] || cap[4] || cap[3] || cap[2] || cap[1])); continue; } @@ -725,12 +807,16 @@ InlineLexer.prototype.output = function(src) { return out; }; +InlineLexer.escapes = function(text) { + return text ? text.replace(InlineLexer.rules._escapes, '$1') : text; +} + /** * Compile Link */ InlineLexer.prototype.outputLink = function(cap, link) { - var href = escape(link.href), + var href = link.href, title = link.title ? escape(link.title) : null; return cap[0].charAt(0) !== '!' @@ -788,7 +874,7 @@ InlineLexer.prototype.mangle = function(text) { */ function Renderer(options) { - this.options = options || {}; + this.options = options || marked.defaults; } Renderer.prototype.code = function(code, lang, escaped) { @@ -803,7 +889,7 @@ Renderer.prototype.code = function(code, lang, escaped) { if (!lang) { return '<pre><code>' + (escaped ? code : escape(code, true)) - + '\n</code></pre>'; + + '</code></pre>'; } return '<pre><code class="' @@ -811,7 +897,7 @@ Renderer.prototype.code = function(code, lang, escaped) { + escape(lang, true) + '">' + (escaped ? code : escape(code, true)) - + '\n</code></pre>\n'; + + '</code></pre>\n'; }; Renderer.prototype.blockquote = function(quote) { @@ -823,16 +909,20 @@ Renderer.prototype.html = function(html) { }; Renderer.prototype.heading = function(text, level, raw) { - return '<h' - + level - + ' id="' - + this.options.headerPrefix - + raw.toLowerCase().replace(/[^\w]+/g, '-') - + '">' - + text - + '</h' - + level - + '>\n'; + if (this.options.headerIds) { + return '<h' + + level + + ' id="' + + this.options.headerPrefix + + raw.toLowerCase().replace(/[^\w]+/g, '-') + + '">' + + text + + '</h' + + level + + '>\n'; + } + // ignore IDs + return '<h' + level + '>' + text + '</h' + level + '>\n'; }; Renderer.prototype.hr = function() { @@ -849,18 +939,26 @@ Renderer.prototype.listitem = function(text) { return '<li>' + text + '</li>\n'; }; +Renderer.prototype.checkbox = function(checked) { + return '<input ' + + (checked ? 'checked="" ' : '') + + 'disabled="" type="checkbox"' + + (this.options.xhtml ? ' /' : '') + + '> '; +} + Renderer.prototype.paragraph = function(text) { return '<p>' + text + '</p>\n'; }; Renderer.prototype.table = function(header, body) { + if (body) body = '<tbody>' + body + '</tbody>'; + return '<table>\n' + '<thead>\n' + header + '</thead>\n' - + '<tbody>\n' + body - + '</tbody>\n' + '</table>\n'; }; @@ -871,7 +969,7 @@ Renderer.prototype.tablerow = function(content) { Renderer.prototype.tablecell = function(content, flags) { var type = flags.header ? 'th' : 'td'; var tag = flags.align - ? '<' + type + ' style="text-align:' + flags.align + '">' + ? '<' + type + ' align="' + flags.align + '">' : '<' + type + '>'; return tag + content + '</' + type + '>\n'; }; @@ -913,7 +1011,12 @@ Renderer.prototype.link = function(href, title, text) { if (this.options.baseUrl && !originIndependentUrl.test(href)) { href = resolveUrl(this.options.baseUrl, href); } - var out = '<a href="' + href + '"'; + try { + href = encodeURI(href).replace(/%25/g, '%'); + } catch (e) { + return text; + } + var out = '<a href="' + escape(href) + '"'; if (title) { out += ' title="' + title + '"'; } @@ -1115,6 +1218,10 @@ Parser.prototype.tok = function() { case 'list_item_start': { body = ''; + if (this.token.task) { + body += this.renderer.checkbox(this.token.checked); + } + while (this.next().type !== 'list_item_end') { body += this.token.type === 'text' ? this.parseText() @@ -1133,10 +1240,8 @@ Parser.prototype.tok = function() { return this.renderer.listitem(body); } case 'html': { - var html = !this.token.pre && !this.options.pedantic - ? this.inline.output(this.token.text) - : this.token.text; - return this.renderer.html(html); + // TODO parse inline content if parameter markdown=1 + return this.renderer.html(this.token.text); } case 'paragraph': { return this.renderer.paragraph(this.inline.output(this.token.text)); @@ -1175,7 +1280,7 @@ function unescape(html) { } function edit(regex, opt) { - regex = regex.source; + regex = regex.source || regex; opt = opt || ''; return { replace: function(name, val) { @@ -1234,6 +1339,22 @@ function merge(obj) { return obj; } +function splitCells(tableRow, count) { + var cells = tableRow.replace(/([^\\])\|/g, '$1 |').split(/ +\| */), + i = 0; + + if (cells.length > count) { + cells.splice(count); + } else { + while (cells.length < count) cells.push(''); + } + + for (; i < cells.length; i++) { + cells[i] = cells[i].replace(/\\\|/g, '|'); + } + return cells; +} + /** * Marked */ @@ -1341,24 +1462,29 @@ marked.setOptions = function(opt) { return marked; }; -marked.defaults = { - gfm: true, - tables: true, - breaks: false, - pedantic: false, - sanitize: false, - sanitizer: null, - mangle: true, - smartLists: false, - silent: false, - highlight: null, - langPrefix: 'lang-', - smartypants: false, - headerPrefix: '', - renderer: new Renderer(), - xhtml: false, - baseUrl: null -}; +marked.getDefaults = function () { + return { + baseUrl: null, + breaks: false, + gfm: true, + headerIds: true, + headerPrefix: '', + highlight: null, + langPrefix: 'language-', + mangle: true, + pedantic: false, + renderer: new Renderer(), + sanitize: false, + sanitizer: null, + silent: false, + smartLists: false, + smartypants: false, + tables: true, + xhtml: false + }; +} + +marked.defaults = marked.getDefaults(); /** * Expose diff --git a/examples/webenginewidgets/markdowneditor/resources/3rdparty/qt_attribution.json b/examples/webenginewidgets/markdowneditor/resources/3rdparty/qt_attribution.json index 70f84b993..de5458eff 100644 --- a/examples/webenginewidgets/markdowneditor/resources/3rdparty/qt_attribution.json +++ b/examples/webenginewidgets/markdowneditor/resources/3rdparty/qt_attribution.json @@ -8,8 +8,8 @@ "Files": "marked.js", "Description": "A full-featured markdown parser and compiler, written in JavaScript. Built for speed.", "Homepage": "https://github.com/chjj/marked", - "Version": "0.3.19", - "DownloadLocation": "https://github.com/chjj/marked/blob/v0.3.19/lib/marked.js", + "Version": "0.4.0", + "DownloadLocation": "https://github.com/markedjs/marked/blob/0.4.0/lib/marked.js", "Copyright": "Copyright (c) 2011-2018, Christopher Jeffrey", "License": "MIT License", "LicenseId": "MIT", diff --git a/examples/webenginewidgets/simplebrowser/data/3rdparty/COPYING b/examples/webenginewidgets/simplebrowser/data/3rdparty/COPYING new file mode 100644 index 000000000..220881da6 --- /dev/null +++ b/examples/webenginewidgets/simplebrowser/data/3rdparty/COPYING @@ -0,0 +1 @@ +The icons in this repository are herefore released into the Public Domain. diff --git a/examples/webenginewidgets/simplebrowser/data/dialog-error.png b/examples/webenginewidgets/simplebrowser/data/3rdparty/dialog-error.png Binary files differindex cdd95bade..cdd95bade 100644 --- a/examples/webenginewidgets/simplebrowser/data/dialog-error.png +++ b/examples/webenginewidgets/simplebrowser/data/3rdparty/dialog-error.png diff --git a/examples/webenginewidgets/simplebrowser/data/edit-clear.png b/examples/webenginewidgets/simplebrowser/data/3rdparty/edit-clear.png Binary files differindex 5542948bc..5542948bc 100644 --- a/examples/webenginewidgets/simplebrowser/data/edit-clear.png +++ b/examples/webenginewidgets/simplebrowser/data/3rdparty/edit-clear.png diff --git a/examples/webenginewidgets/simplebrowser/data/go-bottom.png b/examples/webenginewidgets/simplebrowser/data/3rdparty/go-bottom.png Binary files differindex bf973fedc..bf973fedc 100644 --- a/examples/webenginewidgets/simplebrowser/data/go-bottom.png +++ b/examples/webenginewidgets/simplebrowser/data/3rdparty/go-bottom.png diff --git a/examples/webenginewidgets/simplebrowser/data/go-next.png b/examples/webenginewidgets/simplebrowser/data/3rdparty/go-next.png Binary files differindex a68e2db77..a68e2db77 100644 --- a/examples/webenginewidgets/simplebrowser/data/go-next.png +++ b/examples/webenginewidgets/simplebrowser/data/3rdparty/go-next.png diff --git a/examples/webenginewidgets/simplebrowser/data/go-previous.png b/examples/webenginewidgets/simplebrowser/data/3rdparty/go-previous.png Binary files differindex c37bc0414..c37bc0414 100644 --- a/examples/webenginewidgets/simplebrowser/data/go-previous.png +++ b/examples/webenginewidgets/simplebrowser/data/3rdparty/go-previous.png diff --git a/examples/webenginewidgets/simplebrowser/data/process-stop.png b/examples/webenginewidgets/simplebrowser/data/3rdparty/process-stop.png Binary files differindex e7a8d1722..e7a8d1722 100644 --- a/examples/webenginewidgets/simplebrowser/data/process-stop.png +++ b/examples/webenginewidgets/simplebrowser/data/3rdparty/process-stop.png diff --git a/examples/webenginewidgets/simplebrowser/data/3rdparty/qt_attribution.json b/examples/webenginewidgets/simplebrowser/data/3rdparty/qt_attribution.json new file mode 100644 index 000000000..853adeb20 --- /dev/null +++ b/examples/webenginewidgets/simplebrowser/data/3rdparty/qt_attribution.json @@ -0,0 +1,24 @@ +{ + "Id": "simplebrowser-tango", + "Name": "Tango Icon Library", + "QDocModule": "qtwebengine", + "QtUsage": "Used in WebEngine SimpleBrowser example.", + + "QtParts": [ "examples" ], + "Description": "Selected icons from the Tango Icon Library", + "Homepage": "http://tango.freedesktop.org/Tango_Icon_Library", + "Version": "0.8.90", + "DownloadLocation": "http://tango.freedesktop.org/releases/tango-icon-theme-0.8.90.tar.gz", + "LicenseId": "DocumentRef-PublicDomain", + "License": "Public Domain", + "LicenseFile": "COPYING", + "Copyright": "Ulisse Perusin <uli.peru@gmail.com> +Steven Garrity <sgarrity@silverorange.com> +Lapo Calamandrei <calamandrei@gmail.com> +Ryan Collier <rcollier@novell.com> +Rodney Dawes <dobey@novell.com> +Andreas Nilsson <nisses.mail@home.se> +Tuomas Kuosmanen <tigert@tigert.com> +Garrett LeSage <garrett@novell.com> +Jakub Steiner <jimmac@novell.com>" +} diff --git a/examples/webenginewidgets/simplebrowser/data/text-html.png b/examples/webenginewidgets/simplebrowser/data/3rdparty/text-html.png Binary files differindex a896697d7..a896697d7 100644 --- a/examples/webenginewidgets/simplebrowser/data/text-html.png +++ b/examples/webenginewidgets/simplebrowser/data/3rdparty/text-html.png diff --git a/examples/webenginewidgets/simplebrowser/data/view-refresh.png b/examples/webenginewidgets/simplebrowser/data/3rdparty/view-refresh.png Binary files differindex 606ea9eba..606ea9eba 100644 --- a/examples/webenginewidgets/simplebrowser/data/view-refresh.png +++ b/examples/webenginewidgets/simplebrowser/data/3rdparty/view-refresh.png diff --git a/examples/webenginewidgets/simplebrowser/data/simplebrowser.qrc b/examples/webenginewidgets/simplebrowser/data/simplebrowser.qrc index d81b08a64..eda8e3f3d 100644 --- a/examples/webenginewidgets/simplebrowser/data/simplebrowser.qrc +++ b/examples/webenginewidgets/simplebrowser/data/simplebrowser.qrc @@ -1,14 +1,16 @@ <RCC> <qresource prefix="/"> <file>AppLogoColor.png</file> - <file>dialog-error.png</file> - <file>edit-clear.png</file> - <file>go-bottom.png</file> - <file>go-next.png</file> - <file>go-previous.png</file> - <file>process-stop.png</file> - <file>text-html.png</file> - <file>view-refresh.png</file> <file>ninja.png</file> </qresource> + <qresource prefix="/"> + <file alias="dialog-error.png">3rdparty/dialog-error.png</file> + <file alias="edit-clear.png">3rdparty/edit-clear.png</file> + <file alias="go-bottom.png">3rdparty/go-bottom.png</file> + <file alias="go-next.png">3rdparty/go-next.png</file> + <file alias="go-previous.png">3rdparty/go-previous.png</file> + <file alias="process-stop.png">3rdparty/process-stop.png</file> + <file alias="text-html.png">3rdparty/text-html.png</file> + <file alias="view-refresh.png">3rdparty/view-refresh.png</file> + </qresource> </RCC> diff --git a/examples/webenginewidgets/simplebrowser/doc/src/simplebrowser.qdoc b/examples/webenginewidgets/simplebrowser/doc/src/simplebrowser.qdoc index 2231d9667..b0c426232 100644 --- a/examples/webenginewidgets/simplebrowser/doc/src/simplebrowser.qdoc +++ b/examples/webenginewidgets/simplebrowser/doc/src/simplebrowser.qdoc @@ -327,9 +327,13 @@ finished or when an error occurs. See \c downloadmanagerwidget.cpp for an example of how these signals can be handled. - \section1 Licensing + \section1 Files and Attributions - All icons used in the example, with the exception of \c{AppLogoColor.png}, - originate from the public domain - \l{http://tango.freedesktop.org/Tango_Icon_Library}{Tango Icon Library}. + The example uses icons from the Tango Icon Library: + + \table + \row + \li \l{simplebrowser-tango}{Tango Icon Library} + \li Public Domain + \endtable */ diff --git a/examples/webenginewidgets/simplebrowser/webpage.cpp b/examples/webenginewidgets/simplebrowser/webpage.cpp index 1859bad97..ab9dce4b8 100644 --- a/examples/webenginewidgets/simplebrowser/webpage.cpp +++ b/examples/webenginewidgets/simplebrowser/webpage.cpp @@ -198,7 +198,7 @@ void WebPage::handleRegisterProtocolHandlerRequested(QWebEngineRegisterProtocolH //! [registerProtocolHandlerRequested] #if QT_CONFIG(ssl) -void WebPage::handleSelectClientCertificate(QWebEngineClientCertSelection selection) +void WebPage::handleSelectClientCertificate(QWebEngineClientCertificateSelection selection) { // Just select one. selection.select(selection.certificates().at(0)); diff --git a/examples/webenginewidgets/simplebrowser/webpage.h b/examples/webenginewidgets/simplebrowser/webpage.h index 72cea5304..0b351ab31 100644 --- a/examples/webenginewidgets/simplebrowser/webpage.h +++ b/examples/webenginewidgets/simplebrowser/webpage.h @@ -70,7 +70,7 @@ private slots: void handleProxyAuthenticationRequired(const QUrl &requestUrl, QAuthenticator *auth, const QString &proxyHost); void handleRegisterProtocolHandlerRequested(QWebEngineRegisterProtocolHandlerRequest request); #if QT_CONFIG(ssl) - void handleSelectClientCertificate(QWebEngineClientCertSelection clientCertSelection); + void handleSelectClientCertificate(QWebEngineClientCertificateSelection clientCertSelection); #endif }; diff --git a/examples/webenginewidgets/stylesheetbrowser/3rdparty/COPYING b/examples/webenginewidgets/stylesheetbrowser/3rdparty/COPYING new file mode 100644 index 000000000..220881da6 --- /dev/null +++ b/examples/webenginewidgets/stylesheetbrowser/3rdparty/COPYING @@ -0,0 +1 @@ +The icons in this repository are herefore released into the Public Domain. diff --git a/examples/webenginewidgets/stylesheetbrowser/3rdparty/qt_attribution.json b/examples/webenginewidgets/stylesheetbrowser/3rdparty/qt_attribution.json new file mode 100644 index 000000000..ffca1812b --- /dev/null +++ b/examples/webenginewidgets/stylesheetbrowser/3rdparty/qt_attribution.json @@ -0,0 +1,24 @@ +{ + "Id": "stylesheetbrowser-tango", + "Name": "Tango Icon Library", + "QDocModule": "qtwebengine", + "QtUsage": "Used in WebEngine StyleSheet Browser example.", + + "QtParts": [ "examples" ], + "Description": "Selected icons from the Tango Icon Library", + "Homepage": "http://tango.freedesktop.org/Tango_Icon_Library", + "Version": "0.8.90", + "DownloadLocation": "http://tango.freedesktop.org/releases/tango-icon-theme-0.8.90.tar.gz", + "LicenseId": "DocumentRef-PublicDomain", + "License": "Public Domain", + "LicenseFile": "COPYING", + "Copyright": "Ulisse Perusin <uli.peru@gmail.com> +Steven Garrity <sgarrity@silverorange.com> +Lapo Calamandrei <calamandrei@gmail.com> +Ryan Collier <rcollier@novell.com> +Rodney Dawes <dobey@novell.com> +Andreas Nilsson <nisses.mail@home.se> +Tuomas Kuosmanen <tigert@tigert.com> +Garrett LeSage <garrett@novell.com> +Jakub Steiner <jimmac@novell.com>" +} diff --git a/examples/webenginewidgets/stylesheetbrowser/view-refresh.png b/examples/webenginewidgets/stylesheetbrowser/3rdparty/view-refresh.png Binary files differindex cab4d02c7..cab4d02c7 100644 --- a/examples/webenginewidgets/stylesheetbrowser/view-refresh.png +++ b/examples/webenginewidgets/stylesheetbrowser/3rdparty/view-refresh.png diff --git a/examples/webenginewidgets/stylesheetbrowser/doc/src/stylesheetbrowser.qdoc b/examples/webenginewidgets/stylesheetbrowser/doc/src/stylesheetbrowser.qdoc index b4db10836..5b271bafb 100644 --- a/examples/webenginewidgets/stylesheetbrowser/doc/src/stylesheetbrowser.qdoc +++ b/examples/webenginewidgets/stylesheetbrowser/doc/src/stylesheetbrowser.qdoc @@ -55,4 +55,14 @@ \quotefromfile webenginewidgets/stylesheetbrowser/mainwindow.cpp \skipto MainWindow::removeStyleSheet \printuntil /^\}/ + + \section1 Files and Attributions + + The example uses icons from the Tango Icon Library: + + \table + \row + \li \l{stylesheetbrowser-tango}{Tango Icon Library} + \li Public Domain + \endtable */ diff --git a/examples/webenginewidgets/stylesheetbrowser/stylesheetbrowser.qrc b/examples/webenginewidgets/stylesheetbrowser/stylesheetbrowser.qrc index 8805f2c53..a1cebd6a7 100644 --- a/examples/webenginewidgets/stylesheetbrowser/stylesheetbrowser.qrc +++ b/examples/webenginewidgets/stylesheetbrowser/stylesheetbrowser.qrc @@ -1,5 +1,5 @@ <RCC> <qresource prefix="/"> - <file>view-refresh.png</file> + <file alias="view-refresh.png">3rdparty/view-refresh.png</file> </qresource> </RCC> diff --git a/examples/webenginewidgets/webenginewidgets.pro b/examples/webenginewidgets/webenginewidgets.pro index e98503e09..20c7ead50 100644 --- a/examples/webenginewidgets/webenginewidgets.pro +++ b/examples/webenginewidgets/webenginewidgets.pro @@ -10,10 +10,11 @@ SUBDIRS += \ html2pdf \ simplebrowser \ stylesheetbrowser \ - videoplayer + videoplayer \ + webui qtConfig(webengine-geolocation): SUBDIRS += maps -qtCondif(webengine-webchannel): SUBDIRS += markdowneditor +qtConfig(webengine-webchannel): SUBDIRS += markdowneditor qtConfig(webengine-spellchecker):!qtConfig(webengine-native-spellchecker):!cross_compile { SUBDIRS += spellchecker diff --git a/examples/webenginewidgets/webui/about.html b/examples/webenginewidgets/webui/about.html new file mode 100644 index 000000000..7b5a58969 --- /dev/null +++ b/examples/webenginewidgets/webui/about.html @@ -0,0 +1,129 @@ +<!DOCTYPE html> +<html> + <head> + <title>Qt WebEngine WebUI Example</title> + <style> + html { + background: #f0f0f0; + color: #303030; + font: 16px system-ui; + height: 100%; + } + + body { + margin: 0; + padding: 0; + height: 100%; + display: flex; + flex-direction: column; + align-items: stretch; + } + + body > * { + padding-left: 20px; + padding-right: 20px; + } + + header { + flex: none; + display: flex; + align-items: center; + background: #f0fff0; + border-bottom: 1px solid #e0e0e0; + padding-top: 20px; + padding-bottom: 20px; + } + + header > h1 { + font: bold 20px system-ui; + margin-left: 18px; + } + + main { + flex: auto; + } + + footer { + flex: none; + display: flex; + justify-content: center; + padding-bottom: 20px; + } + + button { + background: #41cd52; + color: #f0f0f0; + font: 16px system-ui; + border: 0; + box-shadow: 0px 1px 3px rgb(0,0,0,0.5); + cursor: pointer; + margin: 0 0 1px; + padding: 10px 24px; + } + + button:hover { + background: #50dc61; + } + + button:active { + background: #50dc61; + box-shadow: 0px 1px 2px rgb(0,0,0,0.5); + margin: 1px 0 0; + } + + button:focus { + outline: 0; + } + + </style> + </head> + <body> + <header> + <img width="48px" height="48px" + src="qrc:/qt-project.org/qmessagebox/images/qtlogo-64.png"> + <h1>WebEngine Widgets<br>WebUI Example</h1> + </header> + <main> + <p> + Aside from the built-in schemes, such as <code>http</code> and + <code>qrc</code>, Qt WebEngine may be extended with <em>custom + schemes</em> by creating <em>custom scheme handlers</em>. + </p> + + <p> + This is a simple HTML page loaded from a custom scheme and + displayed by a <code>QWebEngineView</code>. Even the Quit button + below is a standard HTML <code><button></code> element. + </p> + + <p> + Read the documentation to find out + </p> + <ul> + <li> + <p> + How to create a custom scheme handler which serves HTML + and handles HTML form submissions. + </p> + </li> + <li> + <p> + How to prevent ordinary web content from accessing the + custom scheme. + </p> + </li> + <li> + <p> + How to prevent any other scheme from submitting HTML + form data. + </p> + </li> + </ul> + </main> + <footer> + <form action="" method="post"> + <button name="quit">Quit</button> + </form> + </footer> + </body> +</html> diff --git a/examples/webenginewidgets/webui/doc/images/webui-example.png b/examples/webenginewidgets/webui/doc/images/webui-example.png Binary files differnew file mode 100644 index 000000000..84e2c7fc3 --- /dev/null +++ b/examples/webenginewidgets/webui/doc/images/webui-example.png diff --git a/examples/webenginewidgets/webui/doc/src/webui.qdoc b/examples/webenginewidgets/webui/doc/src/webui.qdoc new file mode 100644 index 000000000..47d9fbad1 --- /dev/null +++ b/examples/webenginewidgets/webui/doc/src/webui.qdoc @@ -0,0 +1,165 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the documentation of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:FDL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Free Documentation License Usage +** Alternatively, this file may be used under the terms of the GNU Free +** Documentation License version 1.3 as published by the Free Software +** Foundation and appearing in the file included in the packaging of +** this file. Please review the following information to ensure +** the GNU Free Documentation License version 1.3 requirements +** will be met: https://www.gnu.org/licenses/fdl-1.3.html. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/*! + \example webenginewidgets/webui + \title WebEngine Widgets WebUI Example + \ingroup webengine-widgetexamples + \brief Displays HTML over a custom scheme. + + \image webui-example.png + + \e {WebUI} demonstrates how to implement a custom scheme in a secure way. + + Aside from the built-in URL schemes, such as \c {http} and \c {qrc}, Qt + WebEngine may be extended with \e {custom schemes} by creating \e {custom + scheme handlers}. This example shows: + + \list + \li How to create a custom scheme handler which serves HTML and handles + HTML form submissions. + \li How to prevent ordinary web content from accessing the custom scheme. + \li How to prevent any other scheme from submitting HTML form data. + \endlist + + \include examples-run.qdocinc + + \section1 Overview + + The example program consists of a single \l {QWebEngineView} showing a + simple HTML page loaded from the URL \c {webui:about}, over our custom + scheme. Pressing the button at the bottom of the page will trigger an HTML + form submission via POST to the same URL, at which point our custom scheme + handler will cause the application to exit. + + The program is divided into two parts, the \c {main} function for setting + everything up, and the \c {WebUiHandler} class for implementing our custom + scheme handler. The \c {main} function is quite short: + + \quotefromfile webenginewidgets/webui/main.cpp + \skipto int main + \printuntil /^\}/ + + Aside from the relatively standard setup of widgets, two points are + noteworthy. First, we call the static method \c + {WebUiHandler::registerUrlScheme()} to register our custom scheme with the + web engine. Second, we create and install our custom scheme handler \c + {WebUiHandler} using \l + {QWebEngineProfile::installUrlSchemeHandler()}{installUrlSchemeHandler()}. + The following sections describe these aspects in more detail. + + \section1 Registering the Scheme + + As custom schemes are integrated directly into the web engine, they do not + necessarily need to follow the standard security rules which apply to + ordinary web content. Depending on the chosen configuration, content served + over a custom scheme may be given access to local resources, be set to + ignore Content-Security-Policy rules, or conversely, be denied access to any + other content entirely. + + In order to take advantage of these possibilities, the custom scheme must + first be registered. This means creating and configuring a \l + {QWebEngineUrlScheme} object and then handing it over to \l + {QWebEngineUrlScheme::registerScheme()}. The example program does exactly this in + the static method \c {WebUiHandler::registerUrlScheme()}: + + \quotefromfile webenginewidgets/webui/webuihandler.cpp + \skipto void WebUiHandler::registerUrlScheme + \printuntil /^\}/ + + A custom scheme needs a name, which can be set by passing it to + the constructor of \c {QWebEngineUrlScheme} or by calling \l + {QWebEngineUrlScheme::setName}. In the above, the name \c {webui} is set + through the constructor. Additionally, we activate the flags \l + {QWebEngineUrlScheme::SecureScheme}{SecureScheme}, \l + {QWebEngineUrlScheme::LocalScheme}{LocalScheme} and \l + {QWebEngineUrlScheme::LocalAccessAllowed}{LocalAccessAllowed}. Since our + custom scheme handler will not deliver resources received from insecure + network connections, we can safely mark it as a \c {SecureScheme}. The \c {LocalScheme} + flag prevents content from non-local schemes (such as \c {http}) from + interacting with our custom scheme. Without this flag it would be possible, + for example, to embed the \c {webui:about} page in an \c <iframe> element on + a remotely loaded HTML page, perhaps to attempt a phishing attack. We also + need the \c {LocalAccessAllowed} flag without which we would not be able to + access the \c {webui} scheme from our \c {webui:about} page. + + Earlier we saw that the call to \c {WebUiHandler::registerUrlScheme()} is + made already at the top of the \c {main} function. This is so because custom + schemes need to be registered as early as possible so that that they can be + passed to all subprocesses. Specifically, custom schemes need to be registered + before any other Qt WebEngine classes are instantiated by the application. + + \section1 Handling Requests + + A custom scheme handler is, broadly speaking, similar to a web application + served over HTTP. However, because custom schemes are integrated directly + into the web engine, they have the advantage in terms of efficiency: there's + no need for generating and parsing HTTP messages or for transferring data + over sockets. + + Implementing a handler means creating a subclass of \l + {QWebEngineUrlSchemeHandler}, which is just what is done by the \c + {WebUiHandler} class of the example program: + + \quotefromfile webenginewidgets/webui/webuihandler.h + \skipto class WebUiHandler + \printuntil /^\}/ + + For each request to a \c {webui} URL, the \c + {WebUiHandler::requestStarted()} method will be called: + + \quotefromfile webenginewidgets/webui/webuihandler.cpp + \skipto void WebUiHandler::requestStarted + \printuntil /^\}/ + + The \l {QWebEngineUrlRequestJob} object \c {job} contains the request's + attributes and provides methods for replying to the request with a response. + Responses are generated asynchronously by reading them from the \l + {QIODevice} that the application passes to \l + {QWebEngineUrlRequestJob::reply()}{reply()}. + + \warning The \c requestStarted() method is not called from the main thread, + but from the web engine's IO thread. Care must be taken to synchronize + access to any resources on the main thread. + + Aside from the usual fare of \l + {QWebEngineUrlRequestJob::requestMethod()}{requestMethod} and \l + {QWebEngineUrlRequestJob::requestUrl()}{requestUrl}, there is also the \l + {QWebEngineUrlRequestJob::initiator()}{initiator}, holding the origin of the + content which initiated the request. An empty \c initiator means the request + was initiated directly by the application (via \l + {QWebEnginePage::setUrl()}, for example). The special value \c "null" + corresponds to an opaque origin (a sandboxed \c {<iframe>} element, for + example). Otherwise, the \c initiator will contain the URL scheme, hostname, + and port of the content which initiated the request. + + In this example, the \c initiator is used to ensure that \c {POST} requests + to \c {webui:about} will only trigger the application's exit if they + originate from the \c {webui} scheme. This prevents content loaded over + other schemes from triggering the application's exit. + +*/ diff --git a/examples/webenginewidgets/webui/main.cpp b/examples/webenginewidgets/webui/main.cpp new file mode 100644 index 000000000..3e9f61fe4 --- /dev/null +++ b/examples/webenginewidgets/webui/main.cpp @@ -0,0 +1,81 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "webuihandler.h" + +#include <QApplication> +#include <QWebEnginePage> +#include <QWebEngineProfile> +#include <QWebEngineView> + +int main(int argc, char *argv[]) +{ + QApplication::setAttribute(Qt::AA_EnableHighDpiScaling); + + WebUiHandler::registerUrlScheme(); + + QApplication app(argc, argv); + + QWebEngineProfile profile; + + WebUiHandler handler; + profile.installUrlSchemeHandler(WebUiHandler::schemeName, &handler); + + QWebEnginePage page(&profile); + page.load(WebUiHandler::aboutUrl); + + QWebEngineView view; + view.setPage(&page); + view.setContextMenuPolicy(Qt::NoContextMenu); + view.resize(500, 600); + view.show(); + + return app.exec(); +} diff --git a/examples/webenginewidgets/webui/webui.pro b/examples/webenginewidgets/webui/webui.pro new file mode 100644 index 000000000..714833587 --- /dev/null +++ b/examples/webenginewidgets/webui/webui.pro @@ -0,0 +1,16 @@ +TEMPLATE = app + +QT += webenginewidgets + +HEADERS += \ + webuihandler.h + +SOURCES += \ + main.cpp \ + webuihandler.cpp + +RESOURCES += \ + webui.qrc + +target.path = $$[QT_INSTALL_EXAMPLES]/webenginewidgets/webui +INSTALLS += target diff --git a/examples/webenginewidgets/webui/webui.qrc b/examples/webenginewidgets/webui/webui.qrc new file mode 100644 index 000000000..6ddf01fa2 --- /dev/null +++ b/examples/webenginewidgets/webui/webui.qrc @@ -0,0 +1,5 @@ +<RCC> + <qresource prefix="/"> + <file>about.html</file> + </qresource> +</RCC> diff --git a/examples/webenginewidgets/webui/webuihandler.cpp b/examples/webenginewidgets/webui/webuihandler.cpp new file mode 100644 index 000000000..63c249368 --- /dev/null +++ b/examples/webenginewidgets/webui/webuihandler.cpp @@ -0,0 +1,98 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "webuihandler.h" + +#include <QApplication> +#include <QFile> +#include <QWebEngineUrlRequestJob> +#include <QWebEngineUrlScheme> + +#define SCHEMENAME "webui" + +const QByteArray WebUiHandler::schemeName = QByteArrayLiteral(SCHEMENAME); +const QUrl WebUiHandler::aboutUrl = QUrl(QStringLiteral(SCHEMENAME ":about")); + +WebUiHandler::WebUiHandler(QObject *parent) + : QWebEngineUrlSchemeHandler(parent) +{ +} + +void WebUiHandler::requestStarted(QWebEngineUrlRequestJob *job) +{ + static const QUrl webUiOrigin(QStringLiteral(SCHEMENAME ":")); + static const QByteArray GET(QByteArrayLiteral("GET")); + static const QByteArray POST(QByteArrayLiteral("POST")); + + QByteArray method = job->requestMethod(); + QUrl url = job->requestUrl(); + QUrl initiator = job->initiator(); + + if (method == GET && url == aboutUrl) { + QFile *file = new QFile(QStringLiteral(":/about.html"), job); + file->open(QIODevice::ReadOnly); + job->reply(QByteArrayLiteral("text/html"), file); + } else if (method == POST && url == aboutUrl && initiator == webUiOrigin) { + job->fail(QWebEngineUrlRequestJob::RequestAborted); + QApplication::exit(); + } else { + job->fail(QWebEngineUrlRequestJob::UrlNotFound); + } +} + +// static +void WebUiHandler::registerUrlScheme() +{ + QWebEngineUrlScheme webUiScheme(schemeName); + webUiScheme.setFlags(QWebEngineUrlScheme::SecureScheme | + QWebEngineUrlScheme::LocalScheme | + QWebEngineUrlScheme::LocalAccessAllowed); + QWebEngineUrlScheme::registerScheme(webUiScheme); +} diff --git a/examples/webenginewidgets/webui/webuihandler.h b/examples/webenginewidgets/webui/webuihandler.h new file mode 100644 index 000000000..f2ac0dcef --- /dev/null +++ b/examples/webenginewidgets/webui/webuihandler.h @@ -0,0 +1,70 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef WEBUIHANDLER_H +#define WEBUIHANDLER_H + +#include <QWebEngineUrlSchemeHandler> + +class WebUiHandler : public QWebEngineUrlSchemeHandler +{ + Q_OBJECT +public: + explicit WebUiHandler(QObject *parent = nullptr); + + void requestStarted(QWebEngineUrlRequestJob *job) override; + + static void registerUrlScheme(); + + const static QByteArray schemeName; + const static QUrl aboutUrl; +}; + +#endif // !WEBUIHANDLER_H diff --git a/mkspecs/features/platform.prf b/mkspecs/features/platform.prf index bf235a9aa..f36b506d8 100644 --- a/mkspecs/features/platform.prf +++ b/mkspecs/features/platform.prf @@ -89,6 +89,7 @@ defineTest(isArchSupported) { } defineTest(isGCCVersionSupported) { + # Keep in sync with src/webengine/doc/src/qtwebengine-platform-notes.qdoc greaterThan(QT_GCC_MAJOR_VERSION, 4):return(true) skipBuild("Using gcc version "$$QT_GCC_MAJOR_VERSION"."$$QT_GCC_MINOR_VERSION", but at least gcc version 5 is required to build Qt WebEngine.") @@ -127,6 +128,26 @@ defineTest(isMinOSXSDKVersion) { return(false) } +defineTest(isMinXcodeVersion) { + requested_major = $$1 + requested_minor = $$2 + requested_patch = $$3 + isEmpty(requested_minor): requested_minor = 0 + isEmpty(requested_patch): requested_patch = 0 + target_var = QMAKE_XCODE_VERSION + major_version = $$section($$target_var, ., 0, 0) + minor_version = $$section($$target_var, ., 1, 1) + patch_version = $$section($$target_var, ., 2, 2) + isEmpty(minor_version): minor_version = 0 + isEmpty(patch_version): patch_version = 0 + + greaterThan(major_version, $$requested_major):return(true) + equals(major_version, $$requested_major):greaterThan(minor_version, $$requested_minor):return(true) + equals(major_version, $$requested_major):equals(minor_version, $$requested_minor):!lessThan(patch_version, $$requested_patch):return(true) + + return(false) +} + defineTest(isMinWinSDKVersion) { requested_major = $$1 requested_minor = $$2 diff --git a/src/3rdparty b/src/3rdparty -Subproject d4ae420c54b0e8b4660b5c41ec0c38049115f9a +Subproject 79e2dad9ede84883b66feb83a7334febf20f274 diff --git a/src/core/api/qwebengineurlrequestjob.cpp b/src/core/api/qwebengineurlrequestjob.cpp index c028a1167..c3541598b 100644 --- a/src/core/api/qwebengineurlrequestjob.cpp +++ b/src/core/api/qwebengineurlrequestjob.cpp @@ -115,9 +115,24 @@ QByteArray QWebEngineUrlRequestJob::requestMethod() const /*! \since 5.11 - Returns the origin URL of the content that initiated the request. If the - request was not initiated by web content the function will return an - empty QUrl. + Returns the serialized origin of the content that initiated the request. + + Generally, the origin consists of a scheme, hostname, and port. For example, + \c "http://localhost:8080" would be a valid origin. The port is omitted if + it is the scheme's default port (80 for \c http, 443 for \c https). The + hostname is omitted for non-network schemes such as \c file and \c qrc. + + However, there is also the special value \c "null" representing a unique + origin. It is, for example, the origin of a sandboxed iframe. The purpose of + this special origin is to be always different from all other origins in the + same-origin check. In other words, content with a unique origin should never + have privileged access to any other content. + + Finally, if the request was not initiated by web content, the function will + return an empty QUrl. This happens, for example, when you call \l + QWebEnginePage::setUrl(). + + This value can be used for implementing secure cross-origin checks. */ QUrl QWebEngineUrlRequestJob::initiator() const { @@ -136,9 +151,10 @@ QUrl QWebEngineUrlRequestJob::initiator() const The device should remain available at least as long as the job exists. When calling this method with a newly constructed device, one solution is to - make the device delete itself when closed, like this: + make the device as a child of the job or delete itself when job is deleted, + like this: \code - connect(device, &QIODevice::aboutToClose, device, &QObject::deleteLater); + connect(job, &QObject::destroyed, device, &QObject::deleteLater); \endcode */ void QWebEngineUrlRequestJob::reply(const QByteArray &contentType, QIODevice *device) diff --git a/src/core/api/qwebengineurlscheme.cpp b/src/core/api/qwebengineurlscheme.cpp index 24bcae195..f36f3335b 100644 --- a/src/core/api/qwebengineurlscheme.cpp +++ b/src/core/api/qwebengineurlscheme.cpp @@ -43,16 +43,16 @@ QT_BEGIN_NAMESPACE -ASSERT_ENUMS_MATCH(QWebEngineUrlScheme::PathSyntax, url::SCHEME_WITHOUT_AUTHORITY) -ASSERT_ENUMS_MATCH(QWebEngineUrlScheme::HostSyntax, url::SCHEME_WITH_HOST) -ASSERT_ENUMS_MATCH(QWebEngineUrlScheme::HostAndPortSyntax, url::SCHEME_WITH_HOST_AND_PORT) -ASSERT_ENUMS_MATCH(QWebEngineUrlScheme::HostPortAndUserInformationSyntax, +ASSERT_ENUMS_MATCH(QWebEngineUrlScheme::Syntax::Path, url::SCHEME_WITHOUT_AUTHORITY) +ASSERT_ENUMS_MATCH(QWebEngineUrlScheme::Syntax::Host, url::SCHEME_WITH_HOST) +ASSERT_ENUMS_MATCH(QWebEngineUrlScheme::Syntax::HostAndPort, url::SCHEME_WITH_HOST_AND_PORT) +ASSERT_ENUMS_MATCH(QWebEngineUrlScheme::Syntax::HostPortAndUserInformation, url::SCHEME_WITH_HOST_PORT_AND_USER_INFORMATION) -ASSERT_ENUMS_MATCH(QWebEngineUrlScheme::PortUnspecified, url::PORT_UNSPECIFIED); +ASSERT_ENUMS_MATCH(QWebEngineUrlScheme::PortUnspecified, url::PORT_UNSPECIFIED) -ASSERT_ENUMS_MATCH(QWebEngineUrlScheme::Secure, url::CustomScheme::Secure) -ASSERT_ENUMS_MATCH(QWebEngineUrlScheme::Local, url::CustomScheme::Local) +ASSERT_ENUMS_MATCH(QWebEngineUrlScheme::SecureScheme, url::CustomScheme::Secure) +ASSERT_ENUMS_MATCH(QWebEngineUrlScheme::LocalScheme, url::CustomScheme::Local) ASSERT_ENUMS_MATCH(QWebEngineUrlScheme::LocalAccessAllowed, url::CustomScheme::LocalAccessAllowed) ASSERT_ENUMS_MATCH(QWebEngineUrlScheme::NoAccessAllowed, url::CustomScheme::NoAccessAllowed) ASSERT_ENUMS_MATCH(QWebEngineUrlScheme::ServiceWorkersAllowed, url::CustomScheme::ServiceWorkersAllowed) @@ -91,10 +91,10 @@ public: int main(int argc, char **argv) { QWebEngineUrlScheme scheme("myscheme"); - scheme.setSyntax(QWebEngineUrlScheme::HostAndPortSyntax); + scheme.setSyntax(QWebEngineUrlScheme::Syntax::HostAndPort); scheme.setDefaultPort(2345); - scheme.setFlags(QWebEngineUrlScheme::Secure); - QWebEngineUrlScheme::addScheme(scheme); + scheme.setFlags(QWebEngineUrlScheme::SecureScheme); + QWebEngineUrlScheme::registerScheme(scheme); ... } \endcode @@ -113,25 +113,25 @@ public: To apply the same-origin policy to a custom URL scheme, WebEngine must be able to compute the origin (host and port combination) of a URL. The \c {Host...} options indicate that the URL scheme conforms to the standard URL syntax (like - \c http) and automatically enable the same-origin policy. The \c {PathSyntax} + \c http) and automatically enable the same-origin policy. The \c {Path} option indicates that the URL scheme uses a non-standard syntax and that the same-origin policy cannot be applied. - \value HostPortAndUserInformationSyntax + \value HostPortAndUserInformation The authority component of a URL of this type has all of the standard elements: host, port, user name, and password. A URL without a port will use the \l defaultPort (which \e must not be \l PortUnspecified). - \value HostAndPortSyntax + \value HostAndPort The authority component of a URL of this type has only the host and port elements. A URL without a port will use the \l defaultPort (which \e must not be \l PortUnspecified). - \value HostSyntax + \value Host The authority component of a URL of this type has only the host part and no port. The \l defaultPort \e must be set to \l PortUnspecified. - \value PathSyntax + \value Path A URL of this type has no authority component at all. Everything after scheme name and separator character (:) will be preserved as is without validation or canonicalization. All URLs of such a scheme will be considered as having @@ -152,7 +152,7 @@ public: This enum type specifies security options that should apply to a URL scheme. - \value Secure + \value SecureScheme Indicates that the URL scheme is \l{https://www.w3.org/TR/powerful-features/#is-origin-trustworthy}{potentially trustworthy}. This flag should only be applied to URL schemes which ensure @@ -161,7 +161,7 @@ public: (authenticated and encrypted) and \c qrc (local resources only), whereas \c http is an example of an insecure scheme. - \value Local + \value LocalScheme Indicates that the URL scheme provides access to local resources. The purpose of this flag is to prevent network content from accessing local resources. Only schemes with the \c LocalAccessAllowed flag may load resources from a @@ -237,7 +237,7 @@ QWebEngineUrlScheme::~QWebEngineUrlScheme() = default; /*! Returns \c true if this and \a that object are equal. */ -bool QWebEngineUrlScheme::operator==(const QWebEngineUrlScheme &that) +bool QWebEngineUrlScheme::operator==(const QWebEngineUrlScheme &that) const { return (d == that.d) || (d->name == that.d->name @@ -247,7 +247,7 @@ bool QWebEngineUrlScheme::operator==(const QWebEngineUrlScheme &that) } /*! - \fn bool QWebEngineUrlScheme::operator!=(const QWebEngineUrlScheme &that) + \fn bool QWebEngineUrlScheme::operator!=(const QWebEngineUrlScheme &that) const Returns \c true if this and \a that object are not equal. */ @@ -279,7 +279,7 @@ void QWebEngineUrlScheme::setName(const QByteArray &newValue) /*! Returns the syntax type of this URL scheme. - The default value is \c PathSyntax. + The default value is \c Path. \sa Syntax, setSyntax() */ @@ -351,9 +351,9 @@ void QWebEngineUrlScheme::setFlags(Flags newValue) \warning This function must be called early at application startup, before creating any WebEngine classes. Late calls will be ignored. - \sa findScheme() + \sa schemeByName() */ -void QWebEngineUrlScheme::addScheme(const QWebEngineUrlScheme &scheme) +void QWebEngineUrlScheme::registerScheme(const QWebEngineUrlScheme &scheme) { url::CustomScheme::AddScheme(*scheme.d); } @@ -362,9 +362,9 @@ void QWebEngineUrlScheme::addScheme(const QWebEngineUrlScheme &scheme) Returns the web engine URL scheme with the given \a name or the default-constructed scheme. - \sa addScheme() + \sa registerScheme() */ -QWebEngineUrlScheme QWebEngineUrlScheme::findScheme(const QByteArray &name) +QWebEngineUrlScheme QWebEngineUrlScheme::schemeByName(const QByteArray &name) { base::StringPiece namePiece{name.data(), static_cast<size_t>(name.size())}; if (const url::CustomScheme *cs = url::CustomScheme::FindScheme(namePiece)) diff --git a/src/core/api/qwebengineurlscheme.h b/src/core/api/qwebengineurlscheme.h index ee5bf3c3c..88a8f5065 100644 --- a/src/core/api/qwebengineurlscheme.h +++ b/src/core/api/qwebengineurlscheme.h @@ -43,6 +43,7 @@ #include <QtWebEngineCore/qtwebenginecoreglobal.h> #include <QtCore/qbytearray.h> +#include <QtCore/qobjectdefs.h> #include <QtCore/qshareddata.h> QT_BEGIN_NAMESPACE @@ -50,12 +51,13 @@ QT_BEGIN_NAMESPACE class QWebEngineUrlSchemePrivate; class QWEBENGINECORE_EXPORT QWebEngineUrlScheme { + Q_GADGET public: - enum Syntax { - HostPortAndUserInformationSyntax, - HostAndPortSyntax, - HostSyntax, - PathSyntax, + enum class Syntax { + HostPortAndUserInformation, + HostAndPort, + Host, + Path, }; enum SpecialPort { @@ -63,8 +65,8 @@ public: }; enum Flag { - Secure = 0x1, - Local = 0x2, + SecureScheme = 0x1, + LocalScheme = 0x2, LocalAccessAllowed = 0x4, NoAccessAllowed = 0x8, ServiceWorkersAllowed = 0x10, @@ -72,9 +74,10 @@ public: ContentSecurityPolicyIgnored = 0x40, }; Q_DECLARE_FLAGS(Flags, Flag) + Q_FLAG(Flags) QWebEngineUrlScheme(); - QWebEngineUrlScheme(const QByteArray &name); + explicit QWebEngineUrlScheme(const QByteArray &name); QWebEngineUrlScheme(const QWebEngineUrlScheme &that); QWebEngineUrlScheme &operator=(const QWebEngineUrlScheme &that); @@ -84,8 +87,8 @@ public: ~QWebEngineUrlScheme(); - bool operator==(const QWebEngineUrlScheme &that); - bool operator!=(const QWebEngineUrlScheme &that) { return !(*this == that); } + bool operator==(const QWebEngineUrlScheme &that) const; + bool operator!=(const QWebEngineUrlScheme &that) const { return !(*this == that); } QByteArray name() const; void setName(const QByteArray &newValue); @@ -99,8 +102,8 @@ public: Flags flags() const; void setFlags(Flags newValue); - static void addScheme(const QWebEngineUrlScheme &scheme); - static QWebEngineUrlScheme findScheme(const QByteArray &name); + static void registerScheme(const QWebEngineUrlScheme &scheme); + static QWebEngineUrlScheme schemeByName(const QByteArray &name); private: QWebEngineUrlScheme(QWebEngineUrlSchemePrivate *d); diff --git a/src/core/compositor.cpp b/src/core/compositor.cpp new file mode 100644 index 000000000..20d956ab4 --- /dev/null +++ b/src/core/compositor.cpp @@ -0,0 +1,170 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtWebEngine module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "compositor.h" + +#include "delegated_frame_node.h" + +#include <components/viz/common/resources/returned_resource.h> +#include <content/public/browser/browser_thread.h> +#include <services/viz/public/interfaces/compositing/compositor_frame_sink.mojom.h> + +namespace QtWebEngineCore { + +Compositor::Compositor() + : m_chromiumCompositorData(new ChromiumCompositorData) +{ + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + + base::SingleThreadTaskRunner *taskRunner = + content::BrowserThread::GetTaskRunnerForThread(content::BrowserThread::UI).get(); + m_beginFrameSource = + std::make_unique<viz::DelayBasedBeginFrameSource>( + std::make_unique<viz::DelayBasedTimeSource>(taskRunner), + viz::BeginFrameSource::kNotRestartableId); +} + +Compositor::~Compositor() +{ + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); +} + +void Compositor::setViewDelegate(RenderWidgetHostViewQtDelegate *viewDelegate) +{ + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + + m_viewDelegate = viewDelegate; +} + +void Compositor::setFrameSinkClient(viz::mojom::CompositorFrameSinkClient *frameSinkClient) +{ + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + + if (m_frameSinkClient == frameSinkClient) + return; + + // Accumulated resources belong to the old RendererCompositorFrameSink and + // should not be returned. + // + // TODO(juvaldma): Can there be a pending frame from the old client? + m_resourcesToRelease.clear(); + m_frameSinkClient = frameSinkClient; +} + +void Compositor::setNeedsBeginFrames(bool needsBeginFrames) +{ + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + + if (m_needsBeginFrames == needsBeginFrames) + return; + + if (needsBeginFrames) + m_beginFrameSource->AddObserver(this); + else + m_beginFrameSource->RemoveObserver(this); + + m_needsBeginFrames = needsBeginFrames; +} + +void Compositor::submitFrame(viz::CompositorFrame frame) +{ + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + DCHECK(!m_havePendingFrame); + + m_chromiumCompositorData->frameDevicePixelRatio = frame.metadata.device_scale_factor; + m_chromiumCompositorData->previousFrameData = std::move(m_chromiumCompositorData->frameData); + m_chromiumCompositorData->frameData = std::move(frame); + m_havePendingFrame = true; + + // Tell viewDelegate to call updatePaintNode() soon. + m_viewDelegate->update(); +} + +QSGNode *Compositor::updatePaintNode(QSGNode *oldNode) +{ + // DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + // + // This might be called from a Qt Quick render thread, but the UI thread + // will still be blocked for the duration of this call. + + DelegatedFrameNode *frameNode = static_cast<DelegatedFrameNode *>(oldNode); + if (!frameNode) + frameNode = new DelegatedFrameNode; + + frameNode->commit(m_chromiumCompositorData.data(), &m_resourcesToRelease, m_viewDelegate); + + if (m_havePendingFrame) { + m_havePendingFrame = false; + content::BrowserThread::PostTask( + content::BrowserThread::UI, FROM_HERE, + base::BindOnce(&Compositor::notifyFrameCommitted, m_weakPtrFactory.GetWeakPtr())); + } + + return frameNode; +} + +void Compositor::notifyFrameCommitted() +{ + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + + m_beginFrameSource->DidFinishFrame(this); + if (m_frameSinkClient) + m_frameSinkClient->DidReceiveCompositorFrameAck(m_resourcesToRelease); + m_resourcesToRelease.clear(); +} + +bool Compositor::OnBeginFrameDerivedImpl(const viz::BeginFrameArgs &args) +{ + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + + m_beginFrameSource->OnUpdateVSyncParameters(args.frame_time, args.interval); + if (m_frameSinkClient) + m_frameSinkClient->OnBeginFrame(args); + + return true; +} + +void Compositor::OnBeginFrameSourcePausedChanged(bool) +{ + // Ignored for now. If the begin frame source is paused, the renderer + // doesn't need to be informed about it and will just not receive more + // begin frames. +} + +} // namespace QtWebEngineCore diff --git a/src/core/compositor.h b/src/core/compositor.h new file mode 100644 index 000000000..4456c648b --- /dev/null +++ b/src/core/compositor.h @@ -0,0 +1,120 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtWebEngine module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef COMPOSITOR_H +#define COMPOSITOR_H + +#include <base/memory/weak_ptr.h> +#include <components/viz/common/frame_sinks/begin_frame_source.h> + +#include <QtCore/qglobal.h> +#include <QtCore/qshareddata.h> + +QT_BEGIN_NAMESPACE +class QSGNode; +QT_END_NAMESPACE + +namespace viz { +class CompositorFrame; +struct ReturnedResource; +namespace mojom { +class CompositorFrameSinkClient; +} // namespace mojom +} // namespace viz + +namespace QtWebEngineCore { + +class RenderWidgetHostViewQtDelegate; +class ChromiumCompositorData; + +// Receives viz::CompositorFrames from child compositors and provides QSGNodes +// to the Qt Quick renderer. +// +// The life cycle of a frame: +// +// Step 1. A new CompositorFrame is received from child compositors and handed +// off to submitFrame(). The new frame will start off in a pending state. +// +// Step 2. Once the new frame is ready to be rendered, Compositor will call +// update() on the delegate. +// +// Step 3. Once the delegate is ready to render, updatePaintNode() should be +// called to receive the scene graph for the new frame. This call will commit +// the pending frame. Until the next frame is ready, all subsequent calls to +// updatePaintNode() will keep using this same committed frame. +// +// Step 4. The Compositor will return unneeded resources back to the child +// compositors. Go to step 1. +class Compositor final : private viz::BeginFrameObserverBase +{ +public: + explicit Compositor(); + ~Compositor() override; + + void setViewDelegate(RenderWidgetHostViewQtDelegate *viewDelegate); + void setFrameSinkClient(viz::mojom::CompositorFrameSinkClient *frameSinkClient); + void setNeedsBeginFrames(bool needsBeginFrames); + + void submitFrame(viz::CompositorFrame frame); + + QSGNode *updatePaintNode(QSGNode *oldNode); + +private: + void notifyFrameCommitted(); + + // viz::BeginFrameObserverBase + bool OnBeginFrameDerivedImpl(const viz::BeginFrameArgs &args) override; + void OnBeginFrameSourcePausedChanged(bool paused) override; + + std::vector<viz::ReturnedResource> m_resourcesToRelease; + QExplicitlySharedDataPointer<ChromiumCompositorData> m_chromiumCompositorData; + RenderWidgetHostViewQtDelegate *m_viewDelegate = nullptr; + std::unique_ptr<viz::SyntheticBeginFrameSource> m_beginFrameSource; + viz::mojom::CompositorFrameSinkClient *m_frameSinkClient = nullptr; + bool m_havePendingFrame = false; + bool m_needsBeginFrames = false; + + base::WeakPtrFactory<Compositor> m_weakPtrFactory{this}; + + DISALLOW_COPY_AND_ASSIGN(Compositor); +}; + +} // namespace QtWebEngineCore + +#endif // !COMPOSITOR_H diff --git a/src/core/config/linux.pri b/src/core/config/linux.pri index f06679a3b..f0755f9cb 100644 --- a/src/core/config/linux.pri +++ b/src/core/config/linux.pri @@ -60,6 +60,7 @@ contains(QT_ARCH, "arm") { !isEmpty(MFLOAT): gn_args += arm_float_abi=\"$$MFLOAT\" MARCH = $$extractCFlag("-march=.*") + !isEmpty(MARCH): gn_args += arm_arch=\"$$MARCH\" MARMV = $$replace(MARCH, "armv",) !isEmpty(MARMV) { diff --git a/src/core/config/windows.pri b/src/core/config/windows.pri index b3e4cf77d..5aa511da3 100644 --- a/src/core/config/windows.pri +++ b/src/core/config/windows.pri @@ -57,7 +57,7 @@ msvc { equals(MSVC_VER, 15.0) { MSVS_VERSION = 2017 } else { - fatal("Visual Studio compiler version \"$$MSVC_VER\" is not supported by Qt WebEngine") + error("Visual Studio compiler version \"$$MSVC_VER\" is not supported by Qt WebEngine") } gn_args += visual_studio_version=$$MSVS_VERSION @@ -71,5 +71,5 @@ msvc { gn_args += target_cpu=\"$$GN_TARGET_CPU\" } else { - fatal("Qt WebEngine for Windows can only be built with the Microsoft Visual Studio C++ compiler") + error("Qt WebEngine for Windows can only be built with the Microsoft Visual Studio C++ compiler") } diff --git a/src/core/configure.json b/src/core/configure.json index aaebcc4a8..e546cf899 100644 --- a/src/core/configure.json +++ b/src/core/configure.json @@ -250,11 +250,6 @@ "test": "snappy", "type": "compile" }, - "webengine-srtp": { - "label": "srtp", - "test": "srtp", - "type": "compile" - }, "webengine-winversion": { "label": "winversion", "test": "winversion", @@ -474,11 +469,6 @@ "condition": "config.unix && tests.webengine-snappy", "output": [ "privateFeature" ] }, - "webengine-system-libsrtp" : { - "label": "libsrtp", - "condition": "config.unix && tests.webengine-srtp", - "output": [ "privateFeature" ] - }, "webengine-winversion" : { "label": "winversion", "condition": "config.win32 && tests.webengine-winversion", @@ -751,7 +741,6 @@ "webengine-system-ffmpeg", "webengine-system-libvpx", "webengine-system-snappy", - "webengine-system-libsrtp", "webengine-system-glib", "webengine-system-zlib", "webengine-system-minizip", diff --git a/src/core/core_chromium.pri b/src/core/core_chromium.pri index a3f9f5c80..92ab80c6c 100644 --- a/src/core/core_chromium.pri +++ b/src/core/core_chromium.pri @@ -53,6 +53,7 @@ SOURCES = \ common/qt_ipc_logging.cpp \ common/qt_messages.cpp \ common/user_script_data.cpp \ + compositor.cpp \ content_client_qt.cpp \ content_browser_client_qt.cpp \ content_main_delegate_qt.cpp \ @@ -139,6 +140,7 @@ HEADERS = \ color_chooser_controller.h \ common/qt_messages.h \ common/user_script_data.h \ + compositor.h \ content_client_qt.h \ content_browser_client_qt.h \ content_main_delegate_qt.h \ diff --git a/src/core/core_common.pri b/src/core/core_common.pri index 0c3e69e15..ce0eecce2 100644 --- a/src/core/core_common.pri +++ b/src/core/core_common.pri @@ -7,3 +7,6 @@ QT_PRIVATE += quick-private gui-private core-private webenginecoreheaders-privat qtConfig(webengine-geolocation): QT += positioning qtConfig(webengine-webchannel): QT += webchannel + +# LTO does not work for Chromium at the moment, so disable it completely for core. +CONFIG -= ltcg diff --git a/src/core/delegated_frame_node.cpp b/src/core/delegated_frame_node.cpp index 736ff1ab7..a3afc2002 100644 --- a/src/core/delegated_frame_node.cpp +++ b/src/core/delegated_frame_node.cpp @@ -154,7 +154,6 @@ public: ResourceHolder(const viz::TransferableResource &resource); QSharedPointer<QSGTexture> initTexture(bool quadIsAllOpaque, RenderWidgetHostViewQtDelegate *apiDelegate = 0); QSGTexture *texture() const { return m_texture.data(); } - viz::TransferableResource &transferableResource() { return m_resource; } viz::ReturnedResource returnResource(); void incImportCount() { ++m_importCount; } bool needsToFetch() const { return !m_resource.is_software && m_texture && !m_texture.data()->textureId(); } @@ -185,11 +184,10 @@ public: virtual void setupRenderPassNode(QSGTexture *, const QRect &, QSGNode *) = 0; virtual void setupTextureContentNode(QSGTexture *, const QRect &, const QRectF &, - QSGTexture::Filtering, QSGTextureNode::TextureCoordinatesTransformMode, QSGNode *) = 0; virtual void setupTiledContentNode(QSGTexture *, const QRect &, const QRectF &, - QSGTexture::Filtering, QSGNode *) = 0; + QSGNode *) = 0; virtual void setupSolidColorNode(const QRect &, const QColor &, QSGNode *) = 0; #ifndef QT_NO_OPENGL @@ -228,7 +226,6 @@ public: } void setupTextureContentNode(QSGTexture *texture, const QRect &rect, const QRectF &sourceRect, - QSGTexture::Filtering filtering, QSGTextureNode::TextureCoordinatesTransformMode texCoordTransForm, QSGNode *) override { @@ -245,11 +242,11 @@ public: textureNode->setRect(rect); if (textureNode->sourceRect() != sourceRect) textureNode->setSourceRect(sourceRect); - if (textureNode->filtering() != filtering) - textureNode->setFiltering(filtering); + if (textureNode->filtering() != texture->filtering()) + textureNode->setFiltering(texture->filtering()); } void setupTiledContentNode(QSGTexture *texture, const QRect &rect, const QRectF &sourceRect, - QSGTexture::Filtering filtering, QSGNode *) override + QSGNode *) override { Q_ASSERT(m_nodeIterator != m_sceneGraphNodes->end()); QSGTextureNode *textureNode = static_cast<QSGTextureNode*>(*m_nodeIterator++); @@ -262,8 +259,8 @@ public: textureNode->setRect(rect); if (textureNode->sourceRect() != sourceRect) textureNode->setSourceRect(sourceRect); - if (textureNode->filtering() != filtering) - textureNode->setFiltering(filtering); + if (textureNode->filtering() != texture->filtering()) + textureNode->setFiltering(texture->filtering()); } void setupSolidColorNode(const QRect &rect, const QColor &color, QSGNode *) override { @@ -332,7 +329,6 @@ public: } void setupTextureContentNode(QSGTexture *texture, const QRect &rect, const QRectF &sourceRect, - QSGTexture::Filtering filtering, QSGTextureNode::TextureCoordinatesTransformMode texCoordTransForm, QSGNode *layerChain) override { @@ -341,20 +337,19 @@ public: textureNode->setRect(rect); textureNode->setSourceRect(sourceRect); textureNode->setTexture(texture); - textureNode->setFiltering(filtering); + textureNode->setFiltering(texture->filtering()); layerChain->appendChildNode(textureNode); m_sceneGraphNodes->append(textureNode); } void setupTiledContentNode(QSGTexture *texture, const QRect &rect, const QRectF &sourceRect, - QSGTexture::Filtering filtering, QSGNode *layerChain) override { QSGTextureNode *textureNode = m_apiDelegate->createTextureNode(); textureNode->setRect(rect); textureNode->setSourceRect(sourceRect); - textureNode->setFiltering(filtering); + textureNode->setFiltering(texture->filtering()); textureNode->setTexture(texture); layerChain->appendChildNode(textureNode); @@ -674,6 +669,7 @@ QSharedPointer<QSGTexture> ResourceHolder::initTexture(bool quadNeedsBlending, R Q_UNREACHABLE(); #endif } + texture->setFiltering(m_resource.filter == GL_LINEAR ? QSGTexture::Linear : QSGTexture::Nearest); m_texture = texture; } // All quads using a resource should request the same blending state. @@ -1128,8 +1124,6 @@ void DelegatedFrameNode::handleQuad( nodeHandler->setupTextureContentNode( texture, toQt(quad->rect), toQt(uv_rect), - resource->transferableResource().filter == GL_LINEAR ? QSGTexture::Linear - : QSGTexture::Nearest, tquad->y_flipped ? QSGTextureNode::MirrorVertically : QSGTextureNode::NoTransform, currentLayerChain); break; @@ -1173,8 +1167,6 @@ void DelegatedFrameNode::handleQuad( nodeHandler->setupTiledContentNode( initAndHoldTexture(resource, quad->ShouldDrawWithBlending(), apiDelegate), toQt(quad->rect), toQt(tquad->tex_coord_rect), - resource->transferableResource().filter == GL_LINEAR ? QSGTexture::Linear - : QSGTexture::Nearest, currentLayerChain); break; #ifndef QT_NO_OPENGL diff --git a/src/core/devtools_frontend_qt.cpp b/src/core/devtools_frontend_qt.cpp index 154b275b5..bd9e0ebe7 100644 --- a/src/core/devtools_frontend_qt.cpp +++ b/src/core/devtools_frontend_qt.cpp @@ -446,7 +446,16 @@ void DevToolsFrontendQt::HandleMessageFromDevToolsFrontend(const std::string &me WindowOpenDisposition::NEW_FOREGROUND_TAB, ui::PAGE_TRANSITION_LINK, false); - m_frontendDelegate->OpenURLFromTab(nullptr, openParams); + // OpenURL will (via WebContentsDelegateQt::OpenURLFromTab) call + // application code, which may decide to close this devtools view (see + // quicknanobrowser for example). + // + // Chromium always calls SendMessageAck through a callback bound to a + // WeakPtr, we do the same here, except without the callback. + base::WeakPtr<DevToolsFrontendQt> weakThis = m_weakFactory.GetWeakPtr(); + web_contents()->OpenURL(openParams); + if (!weakThis) + return; } else if (method == "bringToFront") { Activate(); } else { diff --git a/src/core/doc/src/qtwebenginecore-module.qdoc b/src/core/doc/src/qtwebenginecore-module.qdoc index 2ed0a4c06..65e0766de 100644 --- a/src/core/doc/src/qtwebenginecore-module.qdoc +++ b/src/core/doc/src/qtwebenginecore-module.qdoc @@ -43,9 +43,11 @@ indirectly included through the \l{Qt WebEngine QML Types}{Qt WebEngine} or \l{Qt WebEngine Widgets C++ Classes}{Qt WebEngine Widgets} modules. + \if !defined(qtforpython) To link against the module, add this line to your qmake project file: \snippet qtwebenginecore_build_snippet.qdoc 0 However, \c webenginecore is implied by adding \c webengine or \c webenginewidgets. + \endif */ diff --git a/src/core/download_manager_delegate_qt.cpp b/src/core/download_manager_delegate_qt.cpp index 4ed77688e..9fe233577 100644 --- a/src/core/download_manager_delegate_qt.cpp +++ b/src/core/download_manager_delegate_qt.cpp @@ -43,6 +43,7 @@ #include "content/public/browser/download_manager.h" #include "content/public/browser/save_page_type.h" #include "content/public/browser/web_contents.h" +#include "net/base/net_string_util.h" #include "net/http/http_content_disposition.h" #include <QDir> @@ -136,7 +137,7 @@ bool DownloadManagerDelegateQt::DetermineDownloadTarget(download::DownloadItem* } if (suggestedFilename.isEmpty()) - suggestedFilename = toQt(net::HttpContentDisposition(item->GetContentDisposition(), std::string()).filename()); + suggestedFilename = toQt(net::HttpContentDisposition(item->GetContentDisposition(), net::kCharsetLatin1).filename()); if (suggestedFilename.isEmpty()) suggestedFilename = toQt(item->GetTargetFilePath().AsUTF8Unsafe()); diff --git a/src/core/media_capture_devices_dispatcher.cpp b/src/core/media_capture_devices_dispatcher.cpp index 5298e29cf..b6e59f3bb 100644 --- a/src/core/media_capture_devices_dispatcher.cpp +++ b/src/core/media_capture_devices_dispatcher.cpp @@ -114,7 +114,16 @@ void getDevicesForDesktopCapture( content::DesktopMediaID getDefaultScreenId() { -#if QT_CONFIG(webengine_webrtc) + // While this function is executing another thread may also want to create a + // DesktopCapturer [1]. Unfortunately, creating a DesktopCapturer is not + // thread safe on X11 due to the use of webrtc::XErrorTrap. It's safe to + // disable this code on X11 since we don't actually need to create a + // DesktopCapturer to get the screen id anyway + // (ScreenCapturerLinux::GetSourceList always returns 0 as the id). + // + // [1]: webrtc::InProcessVideoCaptureDeviceLauncher::DoStartDesktopCaptureOnDeviceThread + +#if QT_CONFIG(webengine_webrtc) && !defined(USE_X11) // Source id patterns are different across platforms. // On Linux, the hardcoded value "0" is used. // On Windows, the screens are enumerated consecutively in increasing order from 0. diff --git a/src/core/net/url_request_custom_job_proxy.cpp b/src/core/net/url_request_custom_job_proxy.cpp index 5280318ad..b5f10388c 100644 --- a/src/core/net/url_request_custom_job_proxy.cpp +++ b/src/core/net/url_request_custom_job_proxy.cpp @@ -159,7 +159,7 @@ void URLRequestCustomJobProxy::initialize(GURL url, std::string method, base::Op QUrl initiatorOrigin; if (initiator.has_value()) - initiatorOrigin = toQt(initiator.value().GetURL()); + initiatorOrigin = QUrl::fromEncoded(QByteArray::fromStdString(initiator.value().Serialize())); QWebEngineUrlSchemeHandler *schemeHandler = nullptr; diff --git a/src/core/render_widget_host_view_qt.cpp b/src/core/render_widget_host_view_qt.cpp index 3e5d9bc4b..a3c5b06d1 100644 --- a/src/core/render_widget_host_view_qt.cpp +++ b/src/core/render_widget_host_view_qt.cpp @@ -43,7 +43,7 @@ #include "browser_accessibility_manager_qt.h" #include "browser_accessibility_qt.h" #include "chromium_overrides.h" -#include "delegated_frame_node.h" +#include "compositor.h" #include "qtwebenginecoreglobal_p.h" #include "render_widget_host_view_qt_delegate.h" #include "type_conversion.h" @@ -321,21 +321,15 @@ bool isAccessibilityEnabled() { RenderWidgetHostViewQt::RenderWidgetHostViewQt(content::RenderWidgetHost *widget) : content::RenderWidgetHostViewBase::RenderWidgetHostViewBase(widget) - , m_host(content::RenderWidgetHostImpl::From(widget)) , m_gestureProvider(QtGestureProviderConfig(), this) , m_sendMotionActionDown(false) , m_touchMotionStarted(false) - , m_chromiumCompositorData(new ChromiumCompositorData) - , m_needsDelegatedFrameAck(false) + , m_compositor(new Compositor) , m_loadVisuallyCommittedState(NotCommitted) , m_adapterClient(0) - , m_rendererCompositorFrameSink(0) , m_imeInProgress(false) , m_receivedEmptyImeEvent(false) , m_initPending(false) - , m_beginFrameSource(nullptr) - , m_needsBeginFrames(false) - , m_addedFrameObserver(false) , m_backgroundColor(SK_ColorWHITE) , m_imState(0) , m_anchorPositionWithinSelection(-1) @@ -344,11 +338,7 @@ RenderWidgetHostViewQt::RenderWidgetHostViewQt(content::RenderWidgetHost *widget , m_emptyPreviousSelection(true) , m_wheelAckPending(false) { - auto* task_runner = base::ThreadTaskRunnerHandle::Get().get(); - m_beginFrameSource.reset(new viz::DelayBasedBeginFrameSource( - std::make_unique<viz::DelayBasedTimeSource>(task_runner), 0)); - - m_host->SetView(this); + host()->SetView(this); #ifndef QT_NO_ACCESSIBILITY if (isAccessibilityEnabled()) { QAccessible::installActivationObserver(this); @@ -378,6 +368,7 @@ RenderWidgetHostViewQt::~RenderWidgetHostViewQt() void RenderWidgetHostViewQt::setDelegate(RenderWidgetHostViewQtDelegate* delegate) { m_delegate.reset(delegate); + m_compositor->setViewDelegate(delegate); } void RenderWidgetHostViewQt::setAdapterClient(WebContentsAdapterClient *adapterClient) @@ -477,7 +468,7 @@ void RenderWidgetHostViewQt::Focus() { if (!IsPopup()) m_delegate->setKeyboardFocus(); - m_host->Focus(); + host()->Focus(); } bool RenderWidgetHostViewQt::HasFocus() const @@ -528,8 +519,8 @@ void RenderWidgetHostViewQt::SetBackgroundColor(SkColor color) // Set the background of the compositor if necessary m_delegate->setClearColor(toQt(color)); // Set the background of the blink::FrameView - m_host->SetBackgroundOpaque(SkColorGetA(color) == SK_AlphaOPAQUE); - m_host->Send(new RenderViewObserverQt_SetBackgroundColor(m_host->GetRoutingID(), color)); + host()->SetBackgroundOpaque(SkColorGetA(color) == SK_AlphaOPAQUE); + host()->Send(new RenderViewObserverQt_SetBackgroundColor(host()->GetRoutingID(), color)); } // Return value indicates whether the mouse is locked successfully or not. @@ -547,7 +538,7 @@ void RenderWidgetHostViewQt::UnlockMouse() mouse_locked_ = false; m_delegate->unlockMouse(); qApp->restoreOverrideCursor(); - m_host->LostMouseLock(); + host()->LostMouseLock(); } void RenderWidgetHostViewQt::UpdateCursor(const content::WebCursor &webCursor) @@ -752,10 +743,7 @@ void RenderWidgetHostViewQt::DisplayTooltipText(const base::string16 &tooltip_te void RenderWidgetHostViewQt::DidCreateNewRendererCompositorFrameSink(viz::mojom::CompositorFrameSinkClient *frameSink) { - // Accumulated resources belong to the old RendererCompositorFrameSink and - // should not be returned. - m_resourcesToRelease.clear(); - m_rendererCompositorFrameSink = frameSink; + m_compositor->setFrameSinkClient(frameSink); } void RenderWidgetHostViewQt::SubmitCompositorFrame(const viz::LocalSurfaceId &local_surface_id, viz::CompositorFrame frame, viz::mojom::HitTestRegionListPtr) @@ -770,11 +758,6 @@ void RenderWidgetHostViewQt::SubmitCompositorFrame(const viz::LocalSurfaceId &lo // FIXME: update frame_size and device_scale_factor? // FIXME: showPrimarySurface()? } - Q_ASSERT(!m_needsDelegatedFrameAck); - m_needsDelegatedFrameAck = true; - m_chromiumCompositorData->previousFrameData = std::move(m_chromiumCompositorData->frameData); - m_chromiumCompositorData->frameDevicePixelRatio = frame.metadata.device_scale_factor; - m_chromiumCompositorData->frameData = std::move(frame); // Force to process swap messages uint32_t frame_token = frame.metadata.frame_token; @@ -784,9 +767,9 @@ void RenderWidgetHostViewQt::SubmitCompositorFrame(const viz::LocalSurfaceId &lo // Support experimental.viewport.devicePixelRatio, see GetScreenInfo implementation below. float dpiScale = this->dpiScale(); if (dpiScale != 0 && dpiScale != 1) - m_chromiumCompositorData->frameDevicePixelRatio /= dpiScale; + frame.metadata.device_scale_factor /= dpiScale; - m_delegate->update(); + m_compositor->submitFrame(std::move(frame)); if (m_loadVisuallyCommittedState == NotCommitted) { m_loadVisuallyCommittedState = DidFirstCompositorFrameSwap; @@ -973,60 +956,46 @@ void RenderWidgetHostViewQt::selectionChanged() void RenderWidgetHostViewQt::OnGestureEvent(const ui::GestureEventData& gesture) { - m_host->ForwardGestureEvent(ui::CreateWebGestureEventFromGestureEventData(gesture)); + host()->ForwardGestureEvent(ui::CreateWebGestureEventFromGestureEventData(gesture)); } QSGNode *RenderWidgetHostViewQt::updatePaintNode(QSGNode *oldNode) { - DelegatedFrameNode *frameNode = static_cast<DelegatedFrameNode *>(oldNode); - if (!frameNode) - frameNode = new DelegatedFrameNode; - - frameNode->commit(m_chromiumCompositorData.data(), &m_resourcesToRelease, m_delegate.get()); - - // This is possibly called from the Qt render thread, post the ack back to the UI - // to tell the child compositors to release resources and trigger a new frame. - if (m_needsDelegatedFrameAck) { - m_needsDelegatedFrameAck = false; - content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE, - base::Bind(&RenderWidgetHostViewQt::sendDelegatedFrameAck, AsWeakPtr())); - } - - return frameNode; + return m_compositor->updatePaintNode(oldNode); } void RenderWidgetHostViewQt::notifyResize() { - m_host->WasResized(); - m_host->SendScreenRects(); + host()->WasResized(); + host()->SendScreenRects(); } void RenderWidgetHostViewQt::notifyShown() { - m_host->WasShown(ui::LatencyInfo()); + host()->WasShown(ui::LatencyInfo()); } void RenderWidgetHostViewQt::notifyHidden() { - m_host->WasHidden(); + host()->WasHidden(); } void RenderWidgetHostViewQt::windowBoundsChanged() { - m_host->SendScreenRects(); + host()->SendScreenRects(); if (m_delegate->window()) - m_host->NotifyScreenInfoChanged(); + host()->NotifyScreenInfoChanged(); } void RenderWidgetHostViewQt::windowChanged() { if (m_delegate->window()) - m_host->NotifyScreenInfoChanged(); + host()->NotifyScreenInfoChanged(); } bool RenderWidgetHostViewQt::forwardEvent(QEvent *event) { - Q_ASSERT(m_host->GetView()); + Q_ASSERT(host()->GetView()); switch (event->type()) { case QEvent::ShortcutOverride: { @@ -1073,7 +1042,11 @@ bool RenderWidgetHostViewQt::forwardEvent(QEvent *event) case QEvent::MouseMove: // Skip second MouseMove event when a window is being adopted, so that Chromium // can properly handle further move events. - if (m_adapterClient->isBeingAdopted()) + // Also make sure the adapter client exists to prevent a null pointer dereference, + // because it's possible for a QWebEnginePagePrivate (adapter) instance to be destroyed, + // and then the OS (observed on Windows) might still send mouse move events to a still + // existing popup RWHVQDW instance. + if (m_adapterClient && m_adapterClient->isBeingAdopted()) return false; handleMouseEvent(static_cast<QMouseEvent*>(event)); break; @@ -1091,12 +1064,14 @@ bool RenderWidgetHostViewQt::forwardEvent(QEvent *event) case QEvent::TouchCancel: handleTouchEvent(static_cast<QTouchEvent*>(event)); break; +#if QT_CONFIG(tabletevent) case QEvent::TabletPress: Focus(); // Fall through. case QEvent::TabletRelease: case QEvent::TabletMove: handleTabletEvent(static_cast<QTabletEvent*>(event)); break; +#endif #ifndef QT_NO_GESTURES case QEvent::NativeGesture: handleGestureEvent(static_cast<QNativeGestureEvent *>(event)); @@ -1117,7 +1092,7 @@ bool RenderWidgetHostViewQt::forwardEvent(QEvent *event) break; case QEvent::HoverLeave: case QEvent::Leave: - m_host->ForwardMouseEvent(WebEventFactory::toWebMouseEvent(event)); + host()->ForwardMouseEvent(WebEventFactory::toWebMouseEvent(event)); break; default: return false; @@ -1172,21 +1147,21 @@ QVariant RenderWidgetHostViewQt::inputMethodQuery(Qt::InputMethodQuery query) } } +void RenderWidgetHostViewQt::closePopup() +{ + // We notify the popup to be closed by telling it that it lost focus. WebKit does the rest + // (hiding the widget and automatic memory cleanup via + // RenderWidget::CloseWidgetSoon() -> RenderWidgetHostImpl::ShutdownAndDestroyWidget(true). + host()->SetActive(false); + host()->Blur(); +} + void RenderWidgetHostViewQt::ProcessAckedTouchEvent(const content::TouchEventWithLatencyInfo &touch, content::InputEventAckState ack_result) { Q_UNUSED(touch); const bool eventConsumed = ack_result == content::INPUT_EVENT_ACK_STATE_CONSUMED; m_gestureProvider.OnTouchEventAck(touch.event.unique_touch_event_id, eventConsumed, /*fixme: ?? */false); } -void RenderWidgetHostViewQt::sendDelegatedFrameAck() -{ - m_beginFrameSource->DidFinishFrame(this); - std::vector<viz::ReturnedResource> resources; - m_resourcesToRelease.swap(resources); - if (m_rendererCompositorFrameSink) - m_rendererCompositorFrameSink->DidReceiveCompositorFrameAck(resources); -} - void RenderWidgetHostViewQt::processMotionEvent(const ui::MotionEvent &motionEvent) { auto result = m_gestureProvider.OnTouchEvent(motionEvent); @@ -1196,7 +1171,7 @@ void RenderWidgetHostViewQt::processMotionEvent(const ui::MotionEvent &motionEve blink::WebTouchEvent touchEvent = ui::CreateWebTouchEventFromMotionEvent(motionEvent, result.moved_beyond_slop_region, false /*hovering, FIXME ?*/); - m_host->ForwardTouchEventWithLatencyInfo(touchEvent, CreateLatencyInfo(touchEvent)); + host()->ForwardTouchEventWithLatencyInfo(touchEvent, CreateLatencyInfo(touchEvent)); } QList<QTouchEvent::TouchPoint> RenderWidgetHostViewQt::mapTouchPointIds(const QList<QTouchEvent::TouchPoint> &inputPoints) @@ -1249,11 +1224,11 @@ void RenderWidgetHostViewQt::handleKeyEvent(QKeyEvent *ev) // We're getting the composition result in a key event. if (ev->key() != 0) { // The key event is not a result of an IME composition. Cancel IME. - m_host->ImeCancelComposition(); + host()->ImeCancelComposition(); m_receivedEmptyImeEvent = false; } else { if (ev->type() == QEvent::KeyRelease) { - m_host->ImeCommitText(toString16(ev->text()), + host()->ImeCommitText(toString16(ev->text()), std::vector<ui::ImeTextSpan>(), gfx::Range::InvalidRange(), 0); @@ -1271,13 +1246,13 @@ void RenderWidgetHostViewQt::handleKeyEvent(QKeyEvent *ev) content::EditCommands commands; commands.emplace_back(m_editCommand, ""); m_editCommand.clear(); - m_host->ForwardKeyboardEventWithCommands(webEvent, latency, &commands, nullptr); + host()->ForwardKeyboardEventWithCommands(webEvent, latency, &commands, nullptr); return; } bool keyDownTextInsertion = webEvent.GetType() == blink::WebInputEvent::kRawKeyDown && webEvent.text[0]; webEvent.skip_in_browser = keyDownTextInsertion; - m_host->ForwardKeyboardEvent(webEvent); + host()->ForwardKeyboardEvent(webEvent); if (keyDownTextInsertion) { // Blink won't consume the RawKeyDown, but rather the Char event in this case. @@ -1285,7 +1260,7 @@ void RenderWidgetHostViewQt::handleKeyEvent(QKeyEvent *ev) // The same os_event will be set on both NativeWebKeyboardEvents. webEvent.skip_in_browser = false; webEvent.SetType(blink::WebInputEvent::kChar); - m_host->ForwardKeyboardEvent(webEvent); + host()->ForwardKeyboardEvent(webEvent); } } @@ -1294,7 +1269,7 @@ void RenderWidgetHostViewQt::handleInputMethodEvent(QInputMethodEvent *ev) // Reset input manager state m_imState = 0; - if (!m_host) + if (!host()) return; QString commitString = ev->commitString(); @@ -1408,7 +1383,7 @@ void RenderWidgetHostViewQt::handleInputMethodEvent(QInputMethodEvent *ev) m_receivedEmptyImeEvent = false; if (m_imeInProgress) { m_imeInProgress = false; - m_host->ImeCancelComposition(); + host()->ImeCancelComposition(); } } @@ -1419,7 +1394,7 @@ void RenderWidgetHostViewQt::handleInputMethodEvent(QInputMethodEvent *ev) // Finish compostion: insert or erase text. if (!commitString.isEmpty() || replacementLength > 0) { - m_host->ImeCommitText(toString16(commitString), + host()->ImeCommitText(toString16(commitString), underlines, replacementRange, 0); @@ -1430,7 +1405,7 @@ void RenderWidgetHostViewQt::handleInputMethodEvent(QInputMethodEvent *ev) // Be aware of that, we might get a commit string and a pre-edit string in a single event and // this means a new composition. if (!preeditString.isEmpty()) { - m_host->ImeSetComposition(toString16(preeditString), + host()->ImeSetComposition(toString16(preeditString), underlines, replacementRange, selectionRange.start(), @@ -1467,7 +1442,7 @@ void RenderWidgetHostViewQt::handleWheelEvent(QWheelEvent *ev) if (!m_wheelAckPending) { Q_ASSERT(m_pendingWheelEvents.isEmpty()); m_wheelAckPending = true; - m_host->ForwardWheelEvent(WebEventFactory::toWebWheelEvent(ev, dpiScale())); + host()->ForwardWheelEvent(WebEventFactory::toWebWheelEvent(ev, dpiScale())); return; } if (!m_pendingWheelEvents.isEmpty()) { @@ -1483,7 +1458,7 @@ void RenderWidgetHostViewQt::WheelEventAck(const blink::WebMouseWheelEvent &/*ev m_wheelAckPending = false; if (!m_pendingWheelEvents.isEmpty()) { m_wheelAckPending = true; - m_host->ForwardWheelEvent(m_pendingWheelEvents.takeFirst()); + host()->ForwardWheelEvent(m_pendingWheelEvents.takeFirst()); } // TODO: We could forward unhandled wheelevents to our parent. } @@ -1500,7 +1475,7 @@ void RenderWidgetHostViewQt::handleGestureEvent(QNativeGestureEvent *ev) const Qt::NativeGestureType type = ev->gestureType(); // These are the only supported gestures by Chromium so far. if (type == Qt::ZoomNativeGesture || type == Qt::SmartZoomNativeGesture) { - m_host->ForwardGestureEvent(WebEventFactory::toWebGestureEvent( + host()->ForwardGestureEvent(WebEventFactory::toWebGestureEvent( ev, static_cast<double>(dpiScale()))); } @@ -1617,10 +1592,12 @@ void RenderWidgetHostViewQt::handleTouchEvent(QTouchEvent *ev) } } +#if QT_CONFIG(tabletevent) void RenderWidgetHostViewQt::handleTabletEvent(QTabletEvent *event) { handlePointerEvent<QTabletEvent>(event); } +#endif template<class T> void RenderWidgetHostViewQt::handlePointerEvent(T *event) @@ -1668,20 +1645,20 @@ void RenderWidgetHostViewQt::handlePointerEvent(T *event) #endif } - m_host->ForwardMouseEvent(webEvent); + host()->ForwardMouseEvent(webEvent); } void RenderWidgetHostViewQt::handleHoverEvent(QHoverEvent *ev) { - m_host->ForwardMouseEvent(WebEventFactory::toWebMouseEvent(ev, dpiScale())); + host()->ForwardMouseEvent(WebEventFactory::toWebMouseEvent(ev, dpiScale())); } void RenderWidgetHostViewQt::handleFocusEvent(QFocusEvent *ev) { if (ev->gotFocus()) { - m_host->GotFocus(); - m_host->SetActive(true); - content::RenderViewHostImpl *viewHost = content::RenderViewHostImpl::From(m_host); + host()->GotFocus(); + host()->SetActive(true); + content::RenderViewHostImpl *viewHost = content::RenderViewHostImpl::From(host()); Q_ASSERT(viewHost); if (ev->reason() == Qt::TabFocusReason) viewHost->SetInitialFocus(false); @@ -1689,50 +1666,20 @@ void RenderWidgetHostViewQt::handleFocusEvent(QFocusEvent *ev) viewHost->SetInitialFocus(true); ev->accept(); } else if (ev->lostFocus()) { - m_host->SetActive(false); - m_host->Blur(); + host()->SetActive(false); + host()->Blur(); ev->accept(); } } void RenderWidgetHostViewQt::SetNeedsBeginFrames(bool needs_begin_frames) { - m_needsBeginFrames = needs_begin_frames; - updateNeedsBeginFramesInternal(); -} - -void RenderWidgetHostViewQt::updateNeedsBeginFramesInternal() -{ - Q_ASSERT(m_beginFrameSource); - - if (m_addedFrameObserver == m_needsBeginFrames) - return; - - if (m_needsBeginFrames) - m_beginFrameSource->AddObserver(this); - else - m_beginFrameSource->RemoveObserver(this); - m_addedFrameObserver = m_needsBeginFrames; -} - -bool RenderWidgetHostViewQt::OnBeginFrameDerivedImpl(const viz::BeginFrameArgs& args) -{ - m_beginFrameSource->OnUpdateVSyncParameters(args.frame_time, args.interval); - if (m_rendererCompositorFrameSink) - m_rendererCompositorFrameSink->OnBeginFrame(args); - return true; -} - -void RenderWidgetHostViewQt::OnBeginFrameSourcePausedChanged(bool paused) -{ - // Ignored for now. If the begin frame source is paused, the renderer - // doesn't need to be informed about it and will just not receive more - // begin frames. + m_compositor->setNeedsBeginFrames(needs_begin_frames); } content::RenderFrameHost *RenderWidgetHostViewQt::getFocusedFrameHost() { - content::RenderViewHostImpl *viewHost = content::RenderViewHostImpl::From(m_host); + content::RenderViewHostImpl *viewHost = content::RenderViewHostImpl::From(host()); if (!viewHost) return nullptr; diff --git a/src/core/render_widget_host_view_qt.h b/src/core/render_widget_host_view_qt.h index 0779136f0..909b3bd84 100644 --- a/src/core/render_widget_host_view_qt.h +++ b/src/core/render_widget_host_view_qt.h @@ -58,16 +58,7 @@ #include <QtGui/qaccessible.h> #include <QtGui/QTouchEvent> -#include "delegated_frame_node.h" - QT_BEGIN_NAMESPACE -class QEvent; -class QFocusEvent; -class QHoverEvent; -class QKeyEvent; -class QMouseEvent; -class QVariant; -class QWheelEvent; class QAccessibleInterface; QT_END_NAMESPACE @@ -78,6 +69,8 @@ class RenderWidgetHostImpl; namespace QtWebEngineCore { +class Compositor; + struct MultipleMouseClickHelper { QPoint lastPressPosition; @@ -99,7 +92,6 @@ class RenderWidgetHostViewQt , public ui::GestureProviderClient , public RenderWidgetHostViewQtDelegateClient , public base::SupportsWeakPtr<RenderWidgetHostViewQt> - , public viz::BeginFrameObserverBase #ifndef QT_NO_ACCESSIBILITY , public QAccessible::ActivationObserver #endif // QT_NO_ACCESSIBILITY @@ -173,21 +165,20 @@ public: void windowChanged() override; bool forwardEvent(QEvent *) override; QVariant inputMethodQuery(Qt::InputMethodQuery query) override; + void closePopup() override; // Overridden from content::TextInputManager::Observer void OnUpdateTextInputStateCalled(content::TextInputManager *text_input_manager, RenderWidgetHostViewBase *updated_view, bool did_update_state) override; void OnSelectionBoundsChanged(content::TextInputManager *text_input_manager, RenderWidgetHostViewBase *updated_view) override; void OnTextSelectionChanged(content::TextInputManager *text_input_manager, RenderWidgetHostViewBase *updated_view) override; - // cc::BeginFrameObserverBase implementation. - bool OnBeginFrameDerivedImpl(const viz::BeginFrameArgs& args) override; - void OnBeginFrameSourcePausedChanged(bool paused) override; - void handleMouseEvent(QMouseEvent*); void handleKeyEvent(QKeyEvent*); void handleWheelEvent(QWheelEvent*); void handleTouchEvent(QTouchEvent*); +#if QT_CONFIG(tabletevent) void handleTabletEvent(QTabletEvent *ev); +#endif #ifndef QT_NO_GESTURES void handleGestureEvent(QNativeGestureEvent *); #endif @@ -217,7 +208,6 @@ public: gfx::Vector2dF lastScrollOffset() const { return m_lastScrollOffset; } private: - void sendDelegatedFrameAck(); void processMotionEvent(const ui::MotionEvent &motionEvent); void clearPreviousTouchMotionState(); QList<QTouchEvent::TouchPoint> mapTouchPointIds(const QList<QTouchEvent::TouchPoint> &inputPoints); @@ -230,7 +220,6 @@ private: content::RenderFrameHost *getFocusedFrameHost(); ui::TextInputType getTextInputType() const; - content::RenderWidgetHostImpl *m_host; ui::FilteredGestureProvider m_gestureProvider; base::TimeDelta m_eventsToNowDelta; bool m_sendMotionActionDown; @@ -239,15 +228,12 @@ private: QList<QTouchEvent::TouchPoint> m_previousTouchPoints; std::unique_ptr<RenderWidgetHostViewQtDelegate> m_delegate; - QExplicitlySharedDataPointer<ChromiumCompositorData> m_chromiumCompositorData; - std::vector<viz::ReturnedResource> m_resourcesToRelease; - bool m_needsDelegatedFrameAck; + std::unique_ptr<Compositor> m_compositor; LoadVisuallyCommittedState m_loadVisuallyCommittedState; QMetaObject::Connection m_adapterClientDestroyedConnection; WebContentsAdapterClient *m_adapterClient; MultipleMouseClickHelper m_clickHelper; - viz::mojom::CompositorFrameSinkClient *m_rendererCompositorFrameSink; bool m_imeInProgress; bool m_receivedEmptyImeEvent; @@ -255,10 +241,6 @@ private: bool m_initPending; - std::unique_ptr<viz::SyntheticBeginFrameSource> m_beginFrameSource; - bool m_needsBeginFrames; - bool m_addedFrameObserver; - gfx::Vector2dF m_lastScrollOffset; gfx::SizeF m_lastContentsSize; SkColor m_backgroundColor; diff --git a/src/core/render_widget_host_view_qt_delegate.h b/src/core/render_widget_host_view_qt_delegate.h index 55dd1923a..72f54dcb6 100644 --- a/src/core/render_widget_host_view_qt_delegate.h +++ b/src/core/render_widget_host_view_qt_delegate.h @@ -57,15 +57,12 @@ #include <QtGui/qwindowdefs.h> QT_BEGIN_NAMESPACE -class QCursor; class QEvent; -class QPainter; class QSGLayer; class QSGNode; class QSGRectangleNode; class QSGTexture; class QVariant; -class QWindow; class QInputMethodEvent; class QSGInternalImageNode; @@ -89,6 +86,7 @@ public: virtual void windowChanged() = 0; virtual bool forwardEvent(QEvent *) = 0; virtual QVariant inputMethodQuery(Qt::InputMethodQuery query) = 0; + virtual void closePopup() = 0; }; class QWEBENGINECORE_PRIVATE_EXPORT RenderWidgetHostViewQtDelegate { diff --git a/src/core/user_script.h b/src/core/user_script.h index 7743521f2..5ad53fdc1 100644 --- a/src/core/user_script.h +++ b/src/core/user_script.h @@ -53,7 +53,6 @@ #include "qtwebenginecoreglobal_p.h" -#include <QtCore/QAtomicInt> #include <QtCore/QScopedPointer> #include <QtCore/QSharedData> #include <QtCore/QString> diff --git a/src/core/web_contents_adapter.cpp b/src/core/web_contents_adapter.cpp index 84b97412b..2e3f3785a 100644 --- a/src/core/web_contents_adapter.cpp +++ b/src/core/web_contents_adapter.cpp @@ -638,18 +638,19 @@ void WebContentsAdapter::load(const QWebEngineHttpRequest &request) } } - auto navigate = [this, loadParams = std::move(params)]() { - webContents()->GetController().LoadURLWithParams(loadParams); + auto navigate = [](WebContentsAdapter *adapter, const content::NavigationController::LoadURLParams ¶ms) { + adapter->webContents()->GetController().LoadURLWithParams(params); // Follow chrome::Navigate and invalidate the URL immediately. - m_webContentsDelegate->NavigationStateChanged(webContents(), content::INVALIDATE_TYPE_URL); - focusIfNecessary(); + adapter->m_webContentsDelegate->NavigationStateChanged(adapter->webContents(), content::INVALIDATE_TYPE_URL); + adapter->focusIfNecessary(); }; if (resizeNeeded) { // Schedule navigation on the event loop. - QTimer::singleShot(0, std::move(navigate)); + content::BrowserThread::PostTask( + content::BrowserThread::UI, FROM_HERE, base::BindOnce(navigate, this, std::move(params))); } else { - navigate(); + navigate(this, params); } } @@ -1265,11 +1266,11 @@ void WebContentsAdapter::dpiScaleChanged() impl->NotifyScreenInfoChanged(); } -void WebContentsAdapter::backgroundColorChanged() +void WebContentsAdapter::setBackgroundColor(const QColor &color) { CHECK_INITIALIZED(); if (content::RenderWidgetHostView *rwhv = m_webContents->GetRenderWidgetHostView()) - rwhv->SetBackgroundColor(toSk(m_adapterClient->backgroundColor())); + rwhv->SetBackgroundColor(toSk(color)); } content::WebContents *WebContentsAdapter::webContents() const @@ -1450,23 +1451,6 @@ static void fillDropDataFromMimeData(content::DropData *dropData, const QMimeDat } } -void WebContentsAdapter::enterDrag(QDragEnterEvent *e, const QPointF &screenPos) -{ - CHECK_INITIALIZED(); - - if (!m_currentDropData) { - // The drag originated outside the WebEngineView. - m_currentDropData.reset(new content::DropData); - fillDropDataFromMimeData(m_currentDropData.get(), e->mimeData()); - } - - content::RenderViewHost *rvh = m_webContents->GetRenderViewHost(); - rvh->GetWidget()->FilterDropData(m_currentDropData.get()); - rvh->GetWidget()->DragTargetDragEnter(*m_currentDropData, toGfx(e->posF()), toGfx(screenPos), - toWeb(e->possibleActions()), - flagsFromModifiers(e->keyboardModifiers())); -} - Qt::DropAction toQt(blink::WebDragOperation op) { if (op & blink::kWebDragOperationCopy) @@ -1504,6 +1488,23 @@ static int toWeb(Qt::KeyboardModifiers modifiers) return result; } +void WebContentsAdapter::enterDrag(QDragEnterEvent *e, const QPointF &screenPos) +{ + CHECK_INITIALIZED(); + + if (!m_currentDropData) { + // The drag originated outside the WebEngineView. + m_currentDropData.reset(new content::DropData); + fillDropDataFromMimeData(m_currentDropData.get(), e->mimeData()); + } + + content::RenderViewHost *rvh = m_webContents->GetRenderViewHost(); + rvh->GetWidget()->FilterDropData(m_currentDropData.get()); + rvh->GetWidget()->DragTargetDragEnter(*m_currentDropData, toGfx(e->posF()), toGfx(screenPos), + toWeb(e->possibleActions()), + toWeb(e->mouseButtons()) | toWeb(e->keyboardModifiers())); +} + Qt::DropAction WebContentsAdapter::updateDragPosition(QDragMoveEvent *e, const QPointF &screenPos) { CHECK_INITIALIZED(Qt::DropAction()); @@ -1545,14 +1546,16 @@ void WebContentsAdapter::updateDragAction(int action) m_currentDropAction = static_cast<blink::WebDragOperation>(action); } -void WebContentsAdapter::endDragging(const QPointF &clientPos, const QPointF &screenPos) +void WebContentsAdapter::endDragging(QDropEvent *e, const QPointF &screenPos) { CHECK_INITIALIZED(); content::RenderViewHost *rvh = m_webContents->GetRenderViewHost(); rvh->GetWidget()->FilterDropData(m_currentDropData.get()); - m_lastDragClientPos = clientPos; + m_lastDragClientPos = e->posF(); m_lastDragScreenPos = screenPos; - rvh->GetWidget()->DragTargetDrop(*m_currentDropData, toGfx(m_lastDragClientPos), toGfx(m_lastDragScreenPos), 0); + rvh->GetWidget()->DragTargetDrop(*m_currentDropData, toGfx(m_lastDragClientPos), toGfx(m_lastDragScreenPos), + toWeb(e->mouseButtons()) | toWeb(e->keyboardModifiers())); + m_currentDropData.reset(); } diff --git a/src/core/web_contents_adapter.h b/src/core/web_contents_adapter.h index bd2ca4b23..fcdac94b8 100644 --- a/src/core/web_contents_adapter.h +++ b/src/core/web_contents_adapter.h @@ -73,6 +73,7 @@ QT_BEGIN_NAMESPACE class QAccessibleInterface; class QDragEnterEvent; class QDragMoveEvent; +class QDropEvent; class QMimeData; class QPageLayout; class QString; @@ -186,7 +187,7 @@ public: void grantMouseLockPermission(bool granted); void dpiScaleChanged(); - void backgroundColorChanged(); + void setBackgroundColor(const QColor &color); QAccessibleInterface *browserAccessible(); ProfileQt* profile(); ProfileAdapter* profileAdapter(); @@ -205,7 +206,7 @@ public: void enterDrag(QDragEnterEvent *e, const QPointF &screenPos); Qt::DropAction updateDragPosition(QDragMoveEvent *e, const QPointF &screenPos); void updateDragAction(int action); - void endDragging(const QPointF &clientPos, const QPointF &screenPos); + void endDragging(QDropEvent *e, const QPointF &screenPos); void leaveDrag(); #endif // QT_CONFIG(draganddrop) void printToPDF(const QPageLayout&, const QString&); diff --git a/src/core/web_contents_delegate_qt.cpp b/src/core/web_contents_delegate_qt.cpp index 7f1604162..36703c225 100644 --- a/src/core/web_contents_delegate_qt.cpp +++ b/src/core/web_contents_delegate_qt.cpp @@ -376,7 +376,11 @@ void WebContentsDelegateQt::DidFinishLoad(content::RenderFrameHost* render_frame if (!m_faviconManager->hasCandidate()) m_viewClient->iconChanged(QUrl()); - EmitLoadFinished(true, toQt(validated_url)); + content::NavigationEntry *entry = web_contents()->GetController().GetActiveEntry(); + int http_statuscode = 0; + if (entry) + http_statuscode = entry->GetHttpStatusCode(); + EmitLoadFinished(true /* success */ , toQt(validated_url), false /* isErrorPage */, http_statuscode); } void WebContentsDelegateQt::DidUpdateFaviconURL(const std::vector<content::FaviconURL> &candidates) diff --git a/src/core/web_contents_delegate_qt.h b/src/core/web_contents_delegate_qt.h index 674e75fcd..a2bef7cc7 100644 --- a/src/core/web_contents_delegate_qt.h +++ b/src/core/web_contents_delegate_qt.h @@ -56,10 +56,8 @@ QT_FORWARD_DECLARE_CLASS(CertificateErrorController) QT_FORWARD_DECLARE_CLASS(ClientCertSelectController) namespace content { - class BrowserContext; class ColorChooser; class SiteInstance; - class RenderViewHost; class JavaScriptDialogManager; class WebContents; struct WebPreferences; diff --git a/src/core/web_engine_context.cpp b/src/core/web_engine_context.cpp index a7301e02d..410e13837 100644 --- a/src/core/web_engine_context.cpp +++ b/src/core/web_engine_context.cpp @@ -170,7 +170,7 @@ bool usingSoftwareDynamicGL() { if (QCoreApplication::testAttribute(Qt::AA_UseSoftwareOpenGL)) return true; -#if defined(Q_OS_WIN) +#if defined(Q_OS_WIN) && !defined(QT_NO_OPENGL) HMODULE handle = static_cast<HMODULE>(QOpenGLContext::openGLModuleHandle()); wchar_t path[MAX_PATH]; DWORD size = GetModuleFileName(handle, path, MAX_PATH); @@ -329,10 +329,10 @@ WebEngineContext::WebEngineContext() #endif QWebEngineUrlScheme qrcScheme(QByteArrayLiteral("qrc")); - qrcScheme.setFlags(QWebEngineUrlScheme::Secure + qrcScheme.setFlags(QWebEngineUrlScheme::SecureScheme | QWebEngineUrlScheme::LocalAccessAllowed | QWebEngineUrlScheme::ViewSourceAllowed); - QWebEngineUrlScheme::addScheme(qrcScheme); + QWebEngineUrlScheme::registerScheme(qrcScheme); // Allow us to inject javascript like any webview toolkit. content::RenderFrameHost::AllowInjectingJavaScriptForAndroidWebView(); @@ -516,8 +516,10 @@ WebEngineContext::WebEngineContext() parsedCommandLine->AppendSwitchASCII(switches::kUseGL, glType); parsedCommandLine->AppendSwitch(switches::kInProcessGPU); #ifdef Q_OS_WIN - if (enableWebGLSoftwareRendering) + if (enableWebGLSoftwareRendering) { parsedCommandLine->AppendSwitch(switches::kDisableGpuRasterization); + parsedCommandLine->AppendSwitch(switches::kIgnoreGpuBlacklist); + } #endif } else { parsedCommandLine->AppendSwitch(switches::kDisableGpu); diff --git a/src/core/web_event_factory.cpp b/src/core/web_event_factory.cpp index b8e8b1689..6358f6830 100644 --- a/src/core/web_event_factory.cpp +++ b/src/core/web_event_factory.cpp @@ -69,6 +69,9 @@ #include "ui/events/keycodes/dom/dom_code.h" #include "ui/events/keycodes/dom/dom_key.h" #include "ui/events/keycodes/dom/keycode_converter.h" +#include "ui/events/keycodes/keyboard_code_conversion.h" + +#include <QtGui/private/qtgui-config_p.h> #include <QCoreApplication> #include <QElapsedTimer> @@ -76,16 +79,141 @@ #include <QKeyEvent> #include <QMouseEvent> #include <QStyleHints> +#if QT_CONFIG(tabletevent) #include <QTabletEvent> +#endif #include <QWheelEvent> using namespace blink; -static int windowsKeyCodeForKeyEvent(unsigned int keycode, bool isKeypad) +enum class KeyboardDriver { Unknown, Windows, Cocoa, Xkb, Evdev }; + +static KeyboardDriver keyboardDriverImpl() +{ + QString platformName = QGuiApplication::platformName(); + + if (platformName == QLatin1Literal("windows")) + return KeyboardDriver::Windows; + + if (platformName == QLatin1Literal("cocoa")) + return KeyboardDriver::Cocoa; + + if (platformName == QLatin1Literal("xcb") || platformName == QLatin1Literal("wayland")) + return KeyboardDriver::Xkb; + +#if QT_CONFIG(libinput) && QT_CONFIG(xkbcommon_evdev) + // Based on QEglFSIntegration::createInputHandlers and QLibInputKeyboard::processKey. + if (platformName == QLatin1Literal("eglfs") && !qEnvironmentVariableIntValue("QT_QPA_EGLFS_NO_LIBINPUT")) + return KeyboardDriver::Xkb; +#endif + +#if QT_CONFIG(evdev) + // Based on QEglFSIntegration::createInputHandlers. + if (platformName == QLatin1Literal("eglfs")) + return KeyboardDriver::Evdev; +#endif + + return KeyboardDriver::Unknown; +} + +static KeyboardDriver keyboardDriver() +{ + static KeyboardDriver cached = keyboardDriverImpl(); + return cached; +} + +// Qt swaps the Control and Meta keys on macOS (unless the attribute +// AA_MacDontSwapCtrlAndMeta is set). To preserve compatibility with Chromium we +// want to unswap them when forwarding events. The following two functions, +// qtKeyForKeyEvent and qtModifiersForEvent, should be used for accessing the +// key() and modifiers() properties to ensure that the unswapping is done +// consistently. +static int qtKeyForKeyEvent(const QKeyEvent *ev) +{ + int key = ev->key(); + if (keyboardDriver() == KeyboardDriver::Cocoa && !qApp->testAttribute(Qt::AA_MacDontSwapCtrlAndMeta)) { + if (key == Qt::Key_Control) + return Qt::Key_Meta; + if (key == Qt::Key_Meta) + return Qt::Key_Control; + } + return key; +} + +// See above +static Qt::KeyboardModifiers qtModifiersForEvent(const QInputEvent *ev) +{ + Qt::KeyboardModifiers modifiers = ev->modifiers(); + if (keyboardDriver() == KeyboardDriver::Cocoa && !qApp->testAttribute(Qt::AA_MacDontSwapCtrlAndMeta)) { + bool controlModifier = modifiers.testFlag(Qt::ControlModifier); + bool metaModifier = modifiers.testFlag(Qt::MetaModifier); + modifiers.setFlag(Qt::ControlModifier, metaModifier); + modifiers.setFlag(Qt::MetaModifier, controlModifier); + } + return modifiers; +} + +// QKeyEvent::text() has some limits that we need to work around. +// +// On Linux, the Control modifier transformation is applied [1]. For example, +// pressing Ctrl+@ generates the text "\u0000". We would like "@" instead. +// +// [1]: https://www.x.org/releases/current/doc/kbproto/xkbproto.html#Interpreting_the_Control_Modifier +// +// On macOS, if the Control modifier is used, then no text is generated at all. +// We need some text. +// +// The workaround is to use QKeyEvent::key() instead, when needed. Contrary to +// the documentation, QKeyEvent::key() is not at all limited to the values +// listed in the Qt::Key enum: it can actually contain any Unicode codepoint. +// The only drawback is that letters are always upper cased. +static QString qtTextForKeyEvent(const QKeyEvent *ev, int qtKey, Qt::KeyboardModifiers qtModifiers) +{ + QString text = ev->text(); + + if ((qtModifiers & Qt::ControlModifier) && keyboardDriver() == KeyboardDriver::Xkb) + text.clear(); + + if (!text.isEmpty() || qtKey >= Qt::Key_Escape) + return text; + + QChar ch(qtKey); + if (!(qtModifiers & Qt::ShiftModifier)) // No way to check for caps lock + ch = ch.toLower(); + + text.append(ch); + return text; +} + +// The 'native key code' in Chromium refers to +// +// - On Windows: the Windows OEM scancode. +// - On macOS: the NSEvent's keyCode. +// - On Linux: The XKB keycode. +static quint32 nativeKeyCodeForKeyEvent(const QKeyEvent *ev) +{ + // Ifdefs here should match <ui/events/keycodes/dom/keycode_converter.cc>, + // since that is where the native key code is eventually used. + // + // Note that Xkb key codes are only supported under Linux (no BSDs, + // Cygwin/X, etc). Also evdev key codes are *not* supported for the same + // reason. +#if defined(Q_OS_WINDOWS) + return keyboardDriver() == KeyboardDriver::Windows ? ev->nativeScanCode() : 0; +#elif defined(Q_OS_MACOS) + return keyboardDriver() == KeyboardDriver::Cocoa ? ev->nativeVirtualKey() : 0; +#elif defined(Q_OS_LINUX) + return keyboardDriver() == KeyboardDriver::Xkb ? ev->nativeScanCode() : 0; +#else + return 0; // 0 means unknown, KeyboardEvent.code will be empty string. +#endif +} + +static int windowsKeyCodeForQtKey(int qtKey, bool isKeypad) { // Determine wheter the event comes from the keypad if (isKeypad) { - switch (keycode) { + switch (qtKey) { case Qt::Key_0: return VK_NUMPAD0; // (60) Numeric keypad 0 key case Qt::Key_1: @@ -152,7 +280,7 @@ static int windowsKeyCodeForKeyEvent(unsigned int keycode, bool isKeypad) } } else { - switch (keycode) { + switch (qtKey) { case Qt::Key_Backspace: return VK_BACK; // (08) BACKSPACE key case Qt::Key_Backtab: @@ -645,12 +773,10 @@ static int windowsKeyCodeForKeyEvent(unsigned int keycode, bool isKeypad) * - ui::DomKey::VIDEO_MODE_NEXT * - ui::DomKey::WINK */ -static ui::DomKey getDomKeyFromQKeyEvent(QKeyEvent *ev) +static ui::DomKey domKeyForQtKey(int qtKey) { - if (!ev->text().isEmpty()) - return ui::DomKey::FromCharacter(ev->text().toUcs4().first()); - - switch (ev->key()) { + Q_ASSERT(qtKey >= Qt::Key_Escape); + switch (qtKey) { case Qt::Key_Backspace: return ui::DomKey::BACKSPACE; case Qt::Key_Tab: @@ -1083,17 +1209,10 @@ static inline WebInputEvent::Modifiers modifierForKeyCode(int key) return WebInputEvent::kShiftKey; case Qt::Key_Alt: return WebInputEvent::kAltKey; -#if defined(Q_OS_OSX) - case Qt::Key_Control: - return (!qApp->testAttribute(Qt::AA_MacDontSwapCtrlAndMeta)) ? WebInputEvent::kMetaKey : WebInputEvent::kControlKey; - case Qt::Key_Meta: - return (!qApp->testAttribute(Qt::AA_MacDontSwapCtrlAndMeta)) ? WebInputEvent::kControlKey : WebInputEvent::kMetaKey; -#else case Qt::Key_Control: return WebInputEvent::kControlKey; case Qt::Key_Meta: return WebInputEvent::kMetaKey; -#endif default: return static_cast<WebInputEvent::Modifiers>(0); } @@ -1102,22 +1221,11 @@ static inline WebInputEvent::Modifiers modifierForKeyCode(int key) static inline WebInputEvent::Modifiers modifiersForEvent(const QInputEvent* event) { unsigned result = 0; - Qt::KeyboardModifiers modifiers = event->modifiers(); -#if defined(Q_OS_OSX) - if (!qApp->testAttribute(Qt::AA_MacDontSwapCtrlAndMeta)) { - if (modifiers & Qt::ControlModifier) - result |= WebInputEvent::kMetaKey; - if (modifiers & Qt::MetaModifier) - result |= WebInputEvent::kControlKey; - } else -#endif - { - if (modifiers & Qt::ControlModifier) - result |= WebInputEvent::kControlKey; - if (modifiers & Qt::MetaModifier) - result |= WebInputEvent::kMetaKey; - } - + Qt::KeyboardModifiers modifiers = qtModifiersForEvent(event); + if (modifiers & Qt::ControlModifier) + result |= WebInputEvent::kControlKey; + if (modifiers & Qt::MetaModifier) + result |= WebInputEvent::kMetaKey; if (modifiers & Qt::ShiftModifier) result |= WebInputEvent::kShiftKey; if (modifiers & Qt::AltModifier) @@ -1139,7 +1247,7 @@ static inline WebInputEvent::Modifiers modifiersForEvent(const QInputEvent* even const QKeyEvent *keyEvent = static_cast<const QKeyEvent*>(event); if (keyEvent->isAutoRepeat()) result |= WebInputEvent::kIsAutoRepeat; - result |= modifierForKeyCode(keyEvent->key()); + result |= modifierForKeyCode(qtKeyForKeyEvent(keyEvent)); } default: break; @@ -1186,6 +1294,7 @@ static WebInputEvent::Type webEventTypeForEvent(const QEvent* event) } } +#if QT_CONFIG(tabletevent) static WebPointerProperties::PointerType pointerTypeForTabletEvent(const QTabletEvent *ev) { switch (ev->pointerType()) { @@ -1199,6 +1308,7 @@ static WebPointerProperties::PointerType pointerTypeForTabletEvent(const QTablet return WebPointerProperties::PointerType::kMouse; } } +#endif WebMouseEvent WebEventFactory::toWebMouseEvent(QMouseEvent *ev, double dpiScale) { @@ -1230,6 +1340,7 @@ WebMouseEvent WebEventFactory::toWebMouseEvent(QHoverEvent *ev, double dpiScale) return webKitEvent; } +#if QT_CONFIG(tabletevent) WebMouseEvent WebEventFactory::toWebMouseEvent(QTabletEvent *ev, double dpiScale) { WebMouseEvent webKitEvent(webEventTypeForEvent(ev), @@ -1248,6 +1359,7 @@ WebMouseEvent WebEventFactory::toWebMouseEvent(QTabletEvent *ev, double dpiScale webKitEvent.pointer_type = pointerTypeForTabletEvent(ev); return webKitEvent; } +#endif WebMouseEvent WebEventFactory::toWebMouseEvent(QEvent *ev) { @@ -1374,19 +1486,36 @@ content::NativeWebKeyboardEvent WebEventFactory::toWebKeyboardEvent(QKeyEvent *e webKitEvent.SetModifiers(modifiersForEvent(ev)); webKitEvent.SetType(webEventTypeForEvent(ev)); - webKitEvent.native_key_code = ev->nativeVirtualKey(); - webKitEvent.windows_key_code = windowsKeyCodeForKeyEvent(ev->key(), ev->modifiers() & Qt::KeypadModifier); - webKitEvent.dom_key = getDomKeyFromQKeyEvent(ev); - - ui::DomCode domCode = ui::DomCode::NONE; - int scanCode = ev->nativeScanCode(); - if (scanCode) - domCode = ui::KeycodeConverter::NativeKeycodeToDomCode(scanCode); - webKitEvent.dom_code = static_cast<int>(domCode); - - const ushort* text = ev->text().utf16(); - memcpy(&webKitEvent.text, text, std::min(sizeof(webKitEvent.text), size_t(ev->text().length() * 2))); - memcpy(&webKitEvent.unmodified_text, text, std::min(sizeof(webKitEvent.unmodified_text), size_t(ev->text().length() * 2))); + int qtKey = qtKeyForKeyEvent(ev); + Qt::KeyboardModifiers qtModifiers = qtModifiersForEvent(ev); + QString qtText = qtTextForKeyEvent(ev, qtKey, qtModifiers); + + webKitEvent.native_key_code = nativeKeyCodeForKeyEvent(ev); + webKitEvent.windows_key_code = windowsKeyCodeForQtKey(qtKey, qtModifiers & Qt::KeypadModifier); + + if (qtKey >= Qt::Key_Escape) + webKitEvent.dom_key = domKeyForQtKey(qtKey); + else if (!qtText.isEmpty()) + webKitEvent.dom_key = ui::DomKey::FromCharacter(qtText.toUcs4().first()); + + // The dom_code field should contain the USB keycode of the *physical* key + // that was pressed. Physical meaning independent of layout and modifiers. + // + // Since this information is not available from QKeyEvent in portable form, + // we try to compute it from the native key code. If there's no native key + // code available either, then we assume a US layout and convert it from + // windows_key_code. The result will be incorrect on non-US layouts. + if (webKitEvent.native_key_code) + webKitEvent.dom_code = static_cast<int>( + ui::KeycodeConverter::NativeKeycodeToDomCode(webKitEvent.native_key_code)); + else + webKitEvent.dom_code = static_cast<int>( + ui::UsLayoutKeyboardCodeToDomCode(static_cast<ui::KeyboardCode>(webKitEvent.windows_key_code))); + + const ushort* text = qtText.utf16(); + size_t textSize = std::min(sizeof(webKitEvent.text), size_t(qtText.length() * 2)); + memcpy(&webKitEvent.text, text, textSize); + memcpy(&webKitEvent.unmodified_text, text, textSize); if (webKitEvent.windows_key_code == VK_RETURN) { // This is the same behavior as GTK: @@ -1465,10 +1594,7 @@ bool WebEventFactory::getEditCommand(QKeyEvent *event, std::string *editCommand) } #ifdef Q_OS_MACOS - Qt::KeyboardModifier cmdKey = qApp->testAttribute(Qt::AA_MacDontSwapCtrlAndMeta) ? - Qt::MetaModifier : - Qt::ControlModifier; - if ((event->modifiers() & ~Qt::ShiftModifier) == cmdKey) { + if ((qtModifiersForEvent(event) & ~Qt::ShiftModifier) == Qt::MetaModifier) { if (event->key() == Qt::Key_Backspace) { *editCommand = "DeleteToBeginningOfLine"; return true; diff --git a/src/core/web_event_factory.h b/src/core/web_event_factory.h index dc29970a7..4b22de7d7 100644 --- a/src/core/web_event_factory.h +++ b/src/core/web_event_factory.h @@ -54,7 +54,9 @@ class QEvent; class QHoverEvent; class QKeyEvent; class QMouseEvent; +#ifndef QT_NO_TABLETEVENT class QTabletEvent; +#endif class QWheelEvent; #ifndef QT_NO_GESTURES class QNativeGestureEvent; @@ -66,7 +68,9 @@ class WebEventFactory { public: static blink::WebMouseEvent toWebMouseEvent(QMouseEvent*, double dpiScale); static blink::WebMouseEvent toWebMouseEvent(QHoverEvent*, double dpiScale); +#ifndef QT_NO_TABLETEVENT static blink::WebMouseEvent toWebMouseEvent(QTabletEvent*, double dpiScale); +#endif static blink::WebMouseEvent toWebMouseEvent(QEvent *); #ifndef QT_NO_GESTURES static blink::WebGestureEvent toWebGestureEvent(QNativeGestureEvent *, double dpiScale); diff --git a/src/tools/qwebengine_convert_dict/main.cpp b/src/tools/qwebengine_convert_dict/main.cpp index 3a1a1ff64..2a82e5945 100644 --- a/src/tools/qwebengine_convert_dict/main.cpp +++ b/src/tools/qwebengine_convert_dict/main.cpp @@ -17,6 +17,7 @@ ******************************************************************************/ #include <base/at_exit.h> +#include <base/containers/span.h> #include <base/files/file_path.h> #include <base/files/file_util.h> #include <base/i18n/icu_util.h> @@ -61,6 +62,20 @@ inline QString toQt(const std::string &string) return QString::fromStdString(string); } +template<class T> +QTextStream &operator<<(QTextStream &out, base::span<T> span) +{ + out << '['; + QString prefix; + for (const auto &element : span) { + out << prefix; + out << element; + prefix = QStringLiteral(","); + } + out << ']'; + return out; +} + // Compares the given word list with the serialized trie to make sure they // are the same. inline bool VerifyWords(const convert_dict::DicReader::WordList& org_words, @@ -86,22 +101,23 @@ inline bool VerifyWords(const convert_dict::DicReader::WordList& org_words, } if (org_words[i].first != buf) { - out << "Word doesn't match, word #" << buf << endl; + out << "Word does not match!\n" + << " Index: " << i << "\n" + << " Expected: " << QString::fromStdString(org_words[i].first) << "\n" + << " Actual: " << QString::fromUtf8(buf) << endl; return false; } - if (affix_matches != static_cast<int>(org_words[i].second.size())) { - out << "Different number of affix indices, word #" << buf << endl; - return false; - } + base::span<const int> expectedAffixes(org_words[i].second); + base::span<const int> actualAffixes(affix_ids, affix_matches); - // Check the individual affix indices. - for (size_t affix_index = 0; affix_index < org_words[i].second.size(); - affix_index++) { - if (affix_ids[affix_index] != org_words[i].second[affix_index]) { - out << "Index doesn't match, word #" << buf << endl; - return false; - } + if (expectedAffixes != actualAffixes) { + out << "Affixes do not match!\n" + << " Index: " << i << "\n" + << " Word: " << QString::fromUtf8(buf) << "\n" + << " Expected: " << expectedAffixes << "\n" + << " Actual: " << actualAffixes << endl; + return false; } } diff --git a/src/webengine/api/qquickwebengineaction.cpp b/src/webengine/api/qquickwebengineaction.cpp index ce2fe6357..a0be20b54 100644 --- a/src/webengine/api/qquickwebengineaction.cpp +++ b/src/webengine/api/qquickwebengineaction.cpp @@ -134,7 +134,7 @@ QString QQuickWebEngineAction::text() const /*! \qmlproperty string WebEngineAction::iconText - This property holds the action's descriptive icon text + This property holds the action's descriptive icon text. */ QString QQuickWebEngineAction::iconText() const { diff --git a/src/webengine/api/qquickwebengineprofile.cpp b/src/webengine/api/qquickwebengineprofile.cpp index da84af8d0..f536695ef 100644 --- a/src/webengine/api/qquickwebengineprofile.cpp +++ b/src/webengine/api/qquickwebengineprofile.cpp @@ -173,7 +173,7 @@ QQuickWebEngineProfilePrivate::~QQuickWebEngineProfilePrivate() m_ongoingDownloads.clear(); - if (q_ptr != QQuickWebEngineProfile::defaultProfile()) + if (m_profileAdapter != QtWebEngineCore::ProfileAdapter::defaultProfileAdapter()) delete m_profileAdapter; } @@ -341,8 +341,6 @@ QQuickWebEngineProfile::QQuickWebEngineProfile(QObject *parent) : QObject(parent), d_ptr(new QQuickWebEngineProfilePrivate(new QtWebEngineCore::ProfileAdapter())) { - // Sets up the global WebEngineContext - QQuickWebEngineProfile::defaultProfile(); d_ptr->q_ptr = this; } @@ -838,7 +836,7 @@ static bool checkInternalScheme(const QByteArray &scheme) Registers a handler \a handler for custom URL scheme \a scheme in the profile. It is recommended to first register the scheme with \l - QWebEngineUrlScheme::addScheme at application startup. + QWebEngineUrlScheme::registerScheme at application startup. */ void QQuickWebEngineProfile::installUrlSchemeHandler(const QByteArray &scheme, QWebEngineUrlSchemeHandler *handler) { diff --git a/src/webengine/api/qquickwebengineprofile.h b/src/webengine/api/qquickwebengineprofile.h index 4f1053924..9fc4f9eca 100644 --- a/src/webengine/api/qquickwebengineprofile.h +++ b/src/webengine/api/qquickwebengineprofile.h @@ -168,6 +168,7 @@ private: friend class QQuickWebEngineViewPrivate; friend class QQuickWebEngineDownloadItem; friend class QQuickWebEngineDownloadItemPrivate; + friend class QQuickWebEngineView; QScopedPointer<QQuickWebEngineProfilePrivate> d_ptr; }; diff --git a/src/webengine/api/qquickwebengineprofile_p.h b/src/webengine/api/qquickwebengineprofile_p.h index 9ed8b89de..d31ded0ec 100644 --- a/src/webengine/api/qquickwebengineprofile_p.h +++ b/src/webengine/api/qquickwebengineprofile_p.h @@ -90,7 +90,7 @@ public: static void userScripts_clear(QQmlListProperty<QQuickWebEngineScript> *p); private: - friend class QQuickWebEngineViewPrivate; + friend class QQuickWebEngineView; QQuickWebEngineProfile *q_ptr; QScopedPointer<QQuickWebEngineSettings> m_settings; QPointer<QtWebEngineCore::ProfileAdapter> m_profileAdapter; diff --git a/src/webengine/api/qquickwebenginescript.cpp b/src/webengine/api/qquickwebenginescript.cpp index acc5cedb7..ab7aee4cb 100644 --- a/src/webengine/api/qquickwebenginescript.cpp +++ b/src/webengine/api/qquickwebenginescript.cpp @@ -86,7 +86,8 @@ QT_BEGIN_NAMESPACE /*! \enum QQuickWebEngineScript::ScriptWorldId - The world ID defining which isolated world the script is executed in. + The world ID defining which isolated world the script is executed in. Besides these predefined + IDs custom IDs can be used, but must be integers between \c 0 and \c 256. \value MainWorld The world used by the page's web contents. It can be useful in order to expose custom diff --git a/src/webengine/api/qquickwebenginesettings_p.h b/src/webengine/api/qquickwebenginesettings_p.h index 179416b54..6e1aaca39 100644 --- a/src/webengine/api/qquickwebenginesettings_p.h +++ b/src/webengine/api/qquickwebenginesettings_p.h @@ -204,7 +204,7 @@ private: Q_DISABLE_COPY(QQuickWebEngineSettings) friend class QQuickWebEngineProfilePrivate; friend class QQuickWebEngineViewPrivate; - + friend class QQuickWebEngineView; void setParentSettings(QQuickWebEngineSettings *parentSettings); QScopedPointer<QtWebEngineCore::WebEngineSettings> d_ptr; diff --git a/src/webengine/api/qquickwebengineview.cpp b/src/webengine/api/qquickwebengineview.cpp index 02666d363..6bf23ea7b 100644 --- a/src/webengine/api/qquickwebengineview.cpp +++ b/src/webengine/api/qquickwebengineview.cpp @@ -107,11 +107,12 @@ static QAccessibleInterface *webAccessibleFactory(const QString &, QObject *obje } #endif // QT_NO_ACCESSIBILITY +static QLatin1String defaultMimeType("text/html;charset=UTF-8"); + QQuickWebEngineViewPrivate::QQuickWebEngineViewPrivate() - : m_profile(QQuickWebEngineProfile::defaultProfile()) + : m_profile(nullptr) , adapter(QSharedPointer<WebContentsAdapter>::create()) , m_history(new QQuickWebEngineHistory(this)) - , m_settings(new QQuickWebEngineSettings(m_profile->settings())) #if QT_CONFIG(webengine_testsupport) , m_testSupport(0) #endif @@ -127,11 +128,12 @@ QQuickWebEngineViewPrivate::QQuickWebEngineViewPrivate() , m_isBeingAdopted(false) , m_dpiScale(1.0) , m_backgroundColor(Qt::white) - , m_defaultZoomFactor(1.0) + , m_zoomFactor(1.0) , m_ui2Enabled(false) + , m_profileInitialized(false) { - m_profile->d_ptr->addWebContentsAdapterClient(this); memset(actions, 0, sizeof(actions)); + QString platform = qApp->platformName().toLower(); if (platform == QLatin1Literal("eglfs")) m_ui2Enabled = true; @@ -159,12 +161,31 @@ QQuickWebEngineViewPrivate::QQuickWebEngineViewPrivate() QQuickWebEngineViewPrivate::~QQuickWebEngineViewPrivate() { + Q_ASSERT(m_profileInitialized); m_profile->d_ptr->removeWebContentsAdapterClient(this); adapter->stopFinding(); if (faviconProvider) faviconProvider->detach(q_ptr); } +void QQuickWebEngineViewPrivate::initializeProfile() +{ + if (!m_profileInitialized) { + Q_ASSERT(!adapter->isInitialized()); + m_profileInitialized = true; + if (!m_profile) + m_profile = QQuickWebEngineProfile::defaultProfile(); + m_profile->d_ptr->addWebContentsAdapterClient(this); + m_settings.reset(new QQuickWebEngineSettings(m_profile->settings())); + adapter->setClient(this); + } +} + +bool QQuickWebEngineViewPrivate::profileInitialized() const +{ + return m_profileInitialized; +} + void QQuickWebEngineViewPrivate::destroy() { // the profile for this web contens is about to be @@ -327,7 +348,6 @@ void QQuickWebEngineViewPrivate::urlChanged(const QUrl &url) { Q_Q(QQuickWebEngineView); Q_UNUSED(url); - explicitUrl = QUrl(); Q_EMIT q->urlChanged(); } @@ -452,9 +472,8 @@ void QQuickWebEngineViewPrivate::loadFinished(bool success, const QUrl &url, boo return; } if (success) { - explicitUrl = QUrl(); - QTimer::singleShot(0, q, [q, url]() { - QQuickWebEngineLoadRequest loadRequest(url, QQuickWebEngineView::LoadSucceededStatus); + QTimer::singleShot(0, q, [q, url, errorDescription, errorCode]() { + QQuickWebEngineLoadRequest loadRequest(url, QQuickWebEngineView::LoadSucceededStatus, errorDescription, errorCode); emit q->loadingChanged(&loadRequest); }); return; @@ -753,8 +772,6 @@ QQuickWebEngineView::QQuickWebEngineView(QQuickItem *parent) { Q_D(QQuickWebEngineView); d->q_ptr = this; - d->adapter->setClient(d); - this->setActiveFocusOnTab(true); this->setFlags(QQuickItem::ItemIsFocusScope | QQuickItem::ItemAcceptsDrops); } @@ -765,9 +782,12 @@ QQuickWebEngineView::~QQuickWebEngineView() void QQuickWebEngineViewPrivate::ensureContentsAdapter() { + initializeProfile(); if (!adapter->isInitialized()) { - if (explicitUrl.isValid()) - adapter->load(explicitUrl); + if (!m_html.isEmpty()) + adapter->setContent(m_html.toUtf8(), defaultMimeType, m_url); + else if (m_url.isValid()) + adapter->load(m_url); else adapter->loadDefault(); } @@ -777,14 +797,21 @@ void QQuickWebEngineViewPrivate::initializationFinished() { Q_Q(QQuickWebEngineView); - if (m_backgroundColor != Qt::white) - adapter->backgroundColorChanged(); + Q_ASSERT(m_profileInitialized); + if (m_backgroundColor != Qt::white) { + adapter->setBackgroundColor(m_backgroundColor); + emit q->backgroundColorChanged(); + } + + if (!qFuzzyCompare(adapter->currentZoomFactor(), m_zoomFactor)) { + adapter->setZoomFactor(m_zoomFactor); + emit q->zoomFactorChanged(m_zoomFactor); + } + #if QT_CONFIG(webengine_webchannel) if (m_webChannel) adapter->setWebChannel(m_webChannel, m_webChannelWorld); #endif - if (!qFuzzyCompare(adapter->currentZoomFactor(), m_defaultZoomFactor)) - q->setZoomFactor(m_defaultZoomFactor); if (devToolsView && devToolsView->d_ptr->adapter) adapter->openDevToolsFrontend(devToolsView->d_ptr->adapter); @@ -855,20 +882,25 @@ void QQuickWebEngineViewPrivate::updateAction(QQuickWebEngineView::WebAction act QUrl QQuickWebEngineView::url() const { Q_D(const QQuickWebEngineView); - return d->explicitUrl.isValid() ? d->explicitUrl : d->adapter->activeUrl(); + if (d->adapter->isInitialized()) + return d->adapter->activeUrl(); + else + return d->m_url; } void QQuickWebEngineView::setUrl(const QUrl& url) { + Q_D(QQuickWebEngineView); if (url.isEmpty()) return; - Q_D(QQuickWebEngineView); - d->explicitUrl = url; - if (d->adapter->isInitialized()) + if (d->adapter->isInitialized()) { d->adapter->load(url); - if (!qmlEngine(this) || isComponentComplete()) - d->ensureContentsAdapter(); + return; + } + + d->m_url = url; + d->m_html.clear(); } QUrl QQuickWebEngineView::icon() const @@ -880,11 +912,12 @@ QUrl QQuickWebEngineView::icon() const void QQuickWebEngineView::loadHtml(const QString &html, const QUrl &baseUrl) { Q_D(QQuickWebEngineView); - d->explicitUrl = QUrl(); - if (!qmlEngine(this) || isComponentComplete()) - d->ensureContentsAdapter(); - if (d->adapter->isInitialized()) - d->adapter->setContent(html.toUtf8(), QStringLiteral("text/html;charset=UTF-8"), baseUrl); + d->m_url = baseUrl; + d->m_html = html; + if (d->adapter->isInitialized()) { + d->adapter->setContent(html.toUtf8(), defaultMimeType, baseUrl); + return; + } } void QQuickWebEngineView::goBack() @@ -920,31 +953,48 @@ void QQuickWebEngineView::stop() void QQuickWebEngineView::setZoomFactor(qreal arg) { Q_D(QQuickWebEngineView); - d->m_defaultZoomFactor = arg; - - qreal oldFactor = d->adapter->currentZoomFactor(); - d->adapter->setZoomFactor(arg); - if (qFuzzyCompare(oldFactor, d->adapter->currentZoomFactor())) - return; - - emit zoomFactorChanged(arg); + if (d->adapter->isInitialized() && !qFuzzyCompare(d->m_zoomFactor, d->adapter->currentZoomFactor())) { + d->adapter->setZoomFactor(arg); + emit zoomFactorChanged(arg); + } else { + d->m_zoomFactor = arg; + } } -QQuickWebEngineProfile *QQuickWebEngineView::profile() const +QQuickWebEngineProfile *QQuickWebEngineView::profile() { - Q_D(const QQuickWebEngineView); + Q_D(QQuickWebEngineView); + d->initializeProfile(); return d->m_profile; } void QQuickWebEngineView::setProfile(QQuickWebEngineProfile *profile) { Q_D(QQuickWebEngineView); - d->setProfile(profile); + + if (d->m_profile == profile) + return; + + if (!d->profileInitialized()) { + d->m_profile = profile; + return; + } + + if (d->m_profile) + d->m_profile->d_ptr->removeWebContentsAdapterClient(d); + + d->m_profile = profile; + d->m_profile->d_ptr->addWebContentsAdapterClient(d); + d->m_settings->setParentSettings(profile->settings()); + + d->updateAdapter(); + Q_EMIT profileChanged(); } -QQuickWebEngineSettings *QQuickWebEngineView::settings() const +QQuickWebEngineSettings *QQuickWebEngineView::settings() { - Q_D(const QQuickWebEngineView); + Q_D(QQuickWebEngineView); + d->initializeProfile(); return d->m_settings.data(); } @@ -958,32 +1008,22 @@ QQmlListProperty<QQuickWebEngineScript> QQuickWebEngineView::userScripts() d->userScripts_clear); } -void QQuickWebEngineViewPrivate::setProfile(QQuickWebEngineProfile *profile) +void QQuickWebEngineViewPrivate::updateAdapter() { - Q_Q(QQuickWebEngineView); - - if (profile == m_profile) - return; - m_profile->d_ptr->removeWebContentsAdapterClient(this); - m_profile = profile; - m_profile->d_ptr->addWebContentsAdapterClient(this); - Q_EMIT q->profileChanged(); - m_settings->setParentSettings(profile->settings()); - - if (adapter->profile() != profileAdapter()->profile()) { - // When the profile changes we need to create a new WebContentAdapter and reload the active URL. - bool wasInitialized = adapter->isInitialized(); - QUrl activeUrl = adapter->activeUrl(); - adapter = QSharedPointer<WebContentsAdapter>::create(); - adapter->setClient(this); - if (wasInitialized) { - if (explicitUrl.isValid()) - adapter->load(explicitUrl); - else if (activeUrl.isValid()) - adapter->load(activeUrl); - else - adapter->loadDefault(); - } + // When the profile changes we need to create a new WebContentAdapter and reload the active URL. + bool wasInitialized = adapter->isInitialized(); + QUrl activeUrl = adapter->activeUrl(); + adapter = QSharedPointer<WebContentsAdapter>::create(); + adapter->setClient(this); + if (wasInitialized) { + if (!m_html.isEmpty()) + adapter->setContent(m_html.toUtf8(), defaultMimeType, m_url); + else if (m_url.isValid()) + adapter->load(m_url); + else if (activeUrl.isValid()) + adapter->load(activeUrl); + else + adapter->loadDefault(); } } @@ -1148,7 +1188,7 @@ qreal QQuickWebEngineView::zoomFactor() const { Q_D(const QQuickWebEngineView); if (!d->adapter->isInitialized()) - return d->m_defaultZoomFactor; + return d->m_zoomFactor; return d->adapter->currentZoomFactor(); } @@ -1164,8 +1204,10 @@ void QQuickWebEngineView::setBackgroundColor(const QColor &color) if (color == d->m_backgroundColor) return; d->m_backgroundColor = color; - d->adapter->backgroundColorChanged(); - emit backgroundColorChanged(); + if (d->adapter->isInitialized()) { + d->adapter->setBackgroundColor(color); + emit backgroundColorChanged(); + } } /*! @@ -1281,9 +1323,7 @@ QQmlWebChannel *QQuickWebEngineView::webChannel() Q_D(QQuickWebEngineView); if (!d->m_webChannel) { d->m_webChannel = new QQmlWebChannel(this); - d->adapter->setWebChannel(d->m_webChannel, d->m_webChannelWorld); } - return d->m_webChannel; #endif qWarning("WebEngine compiled without webchannel support"); @@ -1297,7 +1337,8 @@ void QQuickWebEngineView::setWebChannel(QQmlWebChannel *webChannel) if (d->m_webChannel == webChannel) return; d->m_webChannel = webChannel; - d->adapter->setWebChannel(webChannel, d->m_webChannelWorld); + if (d->profileInitialized()) + d->adapter->setWebChannel(webChannel, d->m_webChannelWorld); Q_EMIT webChannelChanged(); #else Q_UNUSED(webChannel) @@ -1318,7 +1359,8 @@ void QQuickWebEngineView::setWebChannelWorld(uint webChannelWorld) if (d->m_webChannelWorld == webChannelWorld) return; d->m_webChannelWorld = webChannelWorld; - d->adapter->setWebChannel(d->m_webChannel, d->m_webChannelWorld); + if (d->profileInitialized()) + d->adapter->setWebChannel(d->m_webChannel, d->m_webChannelWorld); Q_EMIT webChannelWorldChanged(webChannelWorld); #else Q_UNUSED(webChannelWorld) @@ -1353,6 +1395,7 @@ QQuickWebEngineView *QQuickWebEngineView::devToolsView() const return d->devToolsView; } + void QQuickWebEngineView::setDevToolsView(QQuickWebEngineView *devToolsView) { Q_D(QQuickWebEngineView); @@ -1365,7 +1408,7 @@ void QQuickWebEngineView::setDevToolsView(QQuickWebEngineView *devToolsView) d->devToolsView = devToolsView; if (devToolsView) devToolsView->setInspectedView(this); - if (d->adapter->isInitialized()) { + if (d->profileInitialized() && d->adapter->isInitialized()) { if (devToolsView) d->adapter->openDevToolsFrontend(devToolsView->d_ptr->adapter); else @@ -1452,7 +1495,8 @@ void QQuickWebEngineView::geometryChanged(const QRectF &newGeometry, const QRect void QQuickWebEngineView::itemChange(ItemChange change, const ItemChangeData &value) { Q_D(QQuickWebEngineView); - if (d && d->adapter->isInitialized() && (change == ItemSceneChange || change == ItemVisibleHasChanged)) { + if (d && d->profileInitialized() && d->adapter->isInitialized() + && (change == ItemSceneChange || change == ItemVisibleHasChanged)) { if (window() && isVisible()) d->adapter->wasShown(); else @@ -1497,7 +1541,7 @@ void QQuickWebEngineView::dropEvent(QDropEvent *e) { Q_D(QQuickWebEngineView); e->accept(); - d->adapter->endDragging(e->pos(), mapToScreen(this, e->pos())); + d->adapter->endDragging(e, mapToScreen(this, e->pos())); } #endif // QT_CONFIG(draganddrop) @@ -1929,7 +1973,8 @@ void QQuickWebEngineViewPrivate::userScripts_clear(QQmlListProperty<QQuickWebEng void QQuickWebEngineView::componentComplete() { QQuickItem::componentComplete(); - + Q_D(QQuickWebEngineView); + d->initializeProfile(); #ifndef QT_NO_ACCESSIBILITY // Enable accessibility via a dynamic QQmlProperty, instead of using private API call // QQuickAccessibleAttached::qmlAttachedProperties(this). The qmlContext is required, otherwise diff --git a/src/webengine/api/qquickwebengineview_p.h b/src/webengine/api/qquickwebengineview_p.h index f60fdcddf..ae92b6df0 100644 --- a/src/webengine/api/qquickwebengineview_p.h +++ b/src/webengine/api/qquickwebengineview_p.h @@ -461,11 +461,11 @@ public: // QmlParserStatus void componentComplete() override; - QQuickWebEngineProfile *profile() const; + QQuickWebEngineProfile *profile(); void setProfile(QQuickWebEngineProfile *); QQmlListProperty<QQuickWebEngineScript> userScripts(); - QQuickWebEngineSettings *settings() const; + QQuickWebEngineSettings *settings(); QQmlWebChannel *webChannel(); void setWebChannel(QQmlWebChannel *); QQuickWebEngineHistory *navigationHistory() const; diff --git a/src/webengine/api/qquickwebengineview_p_p.h b/src/webengine/api/qquickwebengineview_p_p.h index b030b2a29..ee38ece6b 100644 --- a/src/webengine/api/qquickwebengineview_p_p.h +++ b/src/webengine/api/qquickwebengineview_p_p.h @@ -90,7 +90,7 @@ public: QQuickWebEngineViewPrivate(); ~QQuickWebEngineViewPrivate(); void destroy(); - + void initializeProfile(); QtWebEngineCore::UIDelegatesManager *ui(); QtWebEngineCore::RenderWidgetHostViewQtDelegate* CreateRenderWidgetHostViewQtDelegate(QtWebEngineCore::RenderWidgetHostViewQtDelegateClient *client) override; @@ -160,6 +160,7 @@ public: void updateAction(QQuickWebEngineView::WebAction) const; void adoptWebContents(QtWebEngineCore::WebContentsAdapter *webContents); void setProfile(QQuickWebEngineProfile *profile); + void updateAdapter(); void ensureContentsAdapter(); void setFullScreenMode(bool); @@ -178,7 +179,8 @@ public: #endif QQmlComponent *contextMenuExtraItems; QtWebEngineCore::WebEngineContextMenuData m_contextMenuData; - QUrl explicitUrl; + QUrl m_url; + QString m_html; QUrl iconUrl; QQuickWebEngineFaviconProvider *faviconProvider; int loadProgress; @@ -196,13 +198,16 @@ public: bool m_isBeingAdopted; mutable QQuickWebEngineAction *actions[QQuickWebEngineView::WebActionCount]; + bool profileInitialized() const; + private: QScopedPointer<QtWebEngineCore::UIDelegatesManager> m_uIDelegatesManager; QList<QQuickWebEngineScript *> m_userScripts; qreal m_dpiScale; QColor m_backgroundColor; - qreal m_defaultZoomFactor; + qreal m_zoomFactor; bool m_ui2Enabled; + bool m_profileInitialized; }; #ifndef QT_NO_ACCESSIBILITY diff --git a/src/webengine/doc/src/qtwebengine-module.qdoc b/src/webengine/doc/src/qtwebengine-module.qdoc index 97657f6a9..2d4d1b8da 100644 --- a/src/webengine/doc/src/qtwebengine-module.qdoc +++ b/src/webengine/doc/src/qtwebengine-module.qdoc @@ -38,7 +38,9 @@ \snippet qtwebengine_build_snippet.qdoc 1 + \if !defined(qtforpython) To link against the module, add the following to your qmake project file: \snippet qtwebengine_build_snippet.qdoc 0 + \endif */ diff --git a/src/webengine/doc/src/qtwebengine-platform-notes.qdoc b/src/webengine/doc/src/qtwebengine-platform-notes.qdoc index 403cdf330..6c8703fe4 100644 --- a/src/webengine/doc/src/qtwebengine-platform-notes.qdoc +++ b/src/webengine/doc/src/qtwebengine-platform-notes.qdoc @@ -73,7 +73,7 @@ \section2 Linux - On Linux, Clang or GCC version 4.7 or later is required. + On Linux, Clang or GCC version 5 or later is required. Supported configurations are \c linux-g++ and \c{linux-clang}. Qt WebEngine requires \c pkg-config to detect most of its dependencies. The diff --git a/src/webengine/doc/src/qtwebengine-qmlmodule.qdoc b/src/webengine/doc/src/qtwebengine-qmlmodule.qdoc index 1c6cb53c7..17c29a126 100644 --- a/src/webengine/doc/src/qtwebengine-qmlmodule.qdoc +++ b/src/webengine/doc/src/qtwebengine-qmlmodule.qdoc @@ -26,7 +26,7 @@ ****************************************************************************/ /*! - \qmlmodule QtWebEngine 1.7 + \qmlmodule QtWebEngine 1.8 \title Qt WebEngine QML Types \brief Provides QML types for rendering web content within a QML application \ingroup qtwebengine-modules @@ -36,7 +36,7 @@ your .qml file: \badcode - import QtWebEngine 1.7 + import QtWebEngine 1.8 \endcode To link against the module, add the following QT variable to your qmake .pro diff --git a/src/webengine/doc/src/webengineview_lgpl.qdoc b/src/webengine/doc/src/webengineview_lgpl.qdoc index cc826a0d9..87094c266 100644 --- a/src/webengine/doc/src/webengineview_lgpl.qdoc +++ b/src/webengine/doc/src/webengineview_lgpl.qdoc @@ -365,7 +365,7 @@ \since QtWebEngine 1.3 JavaScript world that the web channel instance used by this view is - installed in. + installed in. The world must be a number between \c 0 and \c 256. */ /*! diff --git a/src/webengine/plugin/plugin.pro b/src/webengine/plugin/plugin.pro index 0bbe83c26..b6652fa26 100644 --- a/src/webengine/plugin/plugin.pro +++ b/src/webengine/plugin/plugin.pro @@ -1,7 +1,7 @@ CXX_MODULE = qml TARGET = qtwebengineplugin TARGETPATH = QtWebEngine -IMPORT_VERSION = 1.7 +IMPORT_VERSION = 1.8 QT += webengine qml quick QT_PRIVATE += core-private webenginecore-private webengine-private diff --git a/src/webengine/plugin/plugins.qmltypes b/src/webengine/plugin/plugins.qmltypes index 44c85bd8e..a17bcfbab 100644 --- a/src/webengine/plugin/plugins.qmltypes +++ b/src/webengine/plugin/plugins.qmltypes @@ -20,14 +20,6 @@ Module { Signal { name: "toggled" } Signal { name: "triggered" } Signal { - name: "textChanged" - Parameter { name: "text"; type: "string" } - } - Signal { - name: "iconTextChanged" - Parameter { name: "iconText"; type: "string" } - } - Signal { name: "enabledChanged" Parameter { name: "enabled"; type: "bool" } } @@ -180,10 +172,11 @@ Module { "QtWebEngine/WebEngineDownloadItem 1.4", "QtWebEngine/WebEngineDownloadItem 1.5", "QtWebEngine/WebEngineDownloadItem 1.6", - "QtWebEngine/WebEngineDownloadItem 1.7" + "QtWebEngine/WebEngineDownloadItem 1.7", + "QtWebEngine/WebEngineDownloadItem 1.8" ] isCreatable: false - exportMetaObjectRevisions: [0, 1, 2, 3, 4, 5, 6] + exportMetaObjectRevisions: [0, 1, 2, 3, 4, 5, 6, 7] Enum { name: "DownloadState" values: { @@ -259,6 +252,13 @@ Module { Property { name: "isFinished"; revision: 5; type: "bool"; isReadonly: true } Property { name: "isPaused"; revision: 5; type: "bool"; isReadonly: true } Property { name: "isSavePageDownload"; revision: 6; type: "bool"; isReadonly: true } + Property { + name: "view" + revision: 7 + type: "QQuickWebEngineView" + isReadonly: true + isPointer: true + } Signal { name: "savePageFormatChanged"; revision: 2 } Signal { name: "mimeTypeChanged"; revision: 1 } Signal { name: "typeChanged"; revision: 3 } @@ -579,10 +579,11 @@ Module { "QtWebEngine/WebEngineSettings 1.4", "QtWebEngine/WebEngineSettings 1.5", "QtWebEngine/WebEngineSettings 1.6", - "QtWebEngine/WebEngineSettings 1.7" + "QtWebEngine/WebEngineSettings 1.7", + "QtWebEngine/WebEngineSettings 1.8" ] isCreatable: false - exportMetaObjectRevisions: [0, 1, 2, 3, 4, 5, 6] + exportMetaObjectRevisions: [0, 1, 2, 3, 4, 5, 6, 7] Enum { name: "UnknownUrlSchemePolicy" values: { @@ -620,6 +621,7 @@ Module { Property { name: "playbackRequiresUserGesture"; revision: 6; type: "bool" } Property { name: "webRTCPublicInterfacesOnly"; revision: 6; type: "bool" } Property { name: "javascriptCanPaste"; revision: 6; type: "bool" } + Property { name: "dnsPrefetchEnabled"; revision: 7; type: "bool" } Signal { name: "fullScreenSupportEnabledChanged"; revision: 1 } Signal { name: "screenCaptureEnabledChanged"; revision: 2 } Signal { name: "webGLEnabledChanged"; revision: 2 } @@ -636,6 +638,7 @@ Module { Signal { name: "playbackRequiresUserGestureChanged"; revision: 6 } Signal { name: "webRTCPublicInterfacesOnlyChanged"; revision: 6 } Signal { name: "javascriptCanPasteChanged"; revision: 6 } + Signal { name: "dnsPrefetchEnabledChanged"; revision: 7 } } Component { name: "QQuickWebEngineSingleton" @@ -1132,6 +1135,7 @@ Module { revision: 7 Parameter { name: "request"; type: "QWebEngineRegisterProtocolHandlerRequest" } } + Signal { name: "printRequested"; revision: 8 } Method { name: "runJavaScript" Parameter { type: "string" } diff --git a/src/webenginewidgets/api/qwebengineclientcertselection.cpp b/src/webenginewidgets/api/qwebengineclientcertificateselection.cpp index de1e101f6..8b5f49d6b 100644 --- a/src/webenginewidgets/api/qwebengineclientcertselection.cpp +++ b/src/webenginewidgets/api/qwebengineclientcertificateselection.cpp @@ -37,7 +37,7 @@ ** ****************************************************************************/ -#include "qwebengineclientcertselection.h" +#include "qwebengineclientcertificateselection.h" #if QT_CONFIG(ssl) @@ -46,12 +46,12 @@ QT_BEGIN_NAMESPACE /*! - \class QWebEngineClientCertSelection + \class QWebEngineClientCertificateSelection \brief The QWebEngineClientCertSelection class wraps a client certificate selection. \since 5.12 \inmodule QtWebEngineWidgets - Provides access to the certicates to choose from, and a method for selecting one. + Provides access to the certificates to choose from, and a method for selecting one. The selection is asynchronous. If no certificate is selected and no copy of the object is kept alive, loading will continue without a certificate. @@ -59,21 +59,21 @@ QT_BEGIN_NAMESPACE /*! \internal */ -QWebEngineClientCertSelection::QWebEngineClientCertSelection(QSharedPointer<ClientCertSelectController> selectController) +QWebEngineClientCertificateSelection::QWebEngineClientCertificateSelection(QSharedPointer<ClientCertSelectController> selectController) : d_ptr(selectController) {} -QWebEngineClientCertSelection::QWebEngineClientCertSelection(const QWebEngineClientCertSelection &other) +QWebEngineClientCertificateSelection::QWebEngineClientCertificateSelection(const QWebEngineClientCertificateSelection &other) : d_ptr(other.d_ptr) {} -QWebEngineClientCertSelection &QWebEngineClientCertSelection::operator=(const QWebEngineClientCertSelection &other) +QWebEngineClientCertificateSelection &QWebEngineClientCertificateSelection::operator=(const QWebEngineClientCertificateSelection &other) { d_ptr = other.d_ptr; return *this; } -QWebEngineClientCertSelection::~QWebEngineClientCertSelection() +QWebEngineClientCertificateSelection::~QWebEngineClientCertificateSelection() { } @@ -82,7 +82,7 @@ QWebEngineClientCertSelection::~QWebEngineClientCertSelection() \sa select() */ -QVector<QSslCertificate> QWebEngineClientCertSelection::certificates() const +QVector<QSslCertificate> QWebEngineClientCertificateSelection::certificates() const { return d_ptr->certificates(); } @@ -93,7 +93,7 @@ QVector<QSslCertificate> QWebEngineClientCertSelection::certificates() const \sa certificates(), selectNone() */ -void QWebEngineClientCertSelection::select(const QSslCertificate &certificate) +void QWebEngineClientCertificateSelection::select(const QSslCertificate &certificate) { d_ptr->select(certificate); } @@ -105,7 +105,7 @@ void QWebEngineClientCertSelection::select(const QSslCertificate &certificate) \sa select() */ -void QWebEngineClientCertSelection::selectNone() +void QWebEngineClientCertificateSelection::selectNone() { d_ptr->selectNone(); } @@ -113,7 +113,7 @@ void QWebEngineClientCertSelection::selectNone() /*! Returns the host and port of the server requesting the client certificate. */ -QUrl QWebEngineClientCertSelection::host() const +QUrl QWebEngineClientCertificateSelection::host() const { return d_ptr->hostAndPort(); } diff --git a/src/webenginewidgets/api/qwebengineclientcertselection.h b/src/webenginewidgets/api/qwebengineclientcertificateselection.h index 84de56a67..15b8a47c6 100644 --- a/src/webenginewidgets/api/qwebengineclientcertselection.h +++ b/src/webenginewidgets/api/qwebengineclientcertificateselection.h @@ -45,19 +45,19 @@ #if QT_CONFIG(ssl) -#include <QtCore/QScopedPointer> -#include <QtCore/QVector> -#include <QtNetwork/QSslCertificate> +#include <QtCore/qscopedpointer.h> +#include <QtCore/qvector.h> +#include <QtNetwork/qsslcertificate.h> QT_BEGIN_NAMESPACE class ClientCertSelectController; -class QWEBENGINEWIDGETS_EXPORT QWebEngineClientCertSelection { +class QWEBENGINEWIDGETS_EXPORT QWebEngineClientCertificateSelection { public: - QWebEngineClientCertSelection(const QWebEngineClientCertSelection &); - ~QWebEngineClientCertSelection(); + QWebEngineClientCertificateSelection(const QWebEngineClientCertificateSelection &); + ~QWebEngineClientCertificateSelection(); - QWebEngineClientCertSelection &operator=(const QWebEngineClientCertSelection &); + QWebEngineClientCertificateSelection &operator=(const QWebEngineClientCertificateSelection &); QUrl host() const; @@ -68,7 +68,7 @@ public: private: friend class QWebEnginePagePrivate; - QWebEngineClientCertSelection(QSharedPointer<ClientCertSelectController>); + QWebEngineClientCertificateSelection(QSharedPointer<ClientCertSelectController>); QSharedPointer<ClientCertSelectController> d_ptr; }; diff --git a/src/webenginewidgets/api/qwebenginepage.cpp b/src/webenginewidgets/api/qwebenginepage.cpp index 6a4554b57..3d8f9389a 100644 --- a/src/webenginewidgets/api/qwebenginepage.cpp +++ b/src/webenginewidgets/api/qwebenginepage.cpp @@ -281,7 +281,7 @@ RenderWidgetHostViewQtDelegate *QWebEnginePagePrivate::CreateRenderWidgetHostVie void QWebEnginePagePrivate::initializationFinished() { if (m_backgroundColor != Qt::white) - adapter->backgroundColorChanged(); + adapter->setBackgroundColor(m_backgroundColor); #if QT_CONFIG(webengine_webchannel) if (webChannel) adapter->setWebChannel(webChannel, webChannelWorldId); @@ -902,7 +902,7 @@ QWebEnginePage::~QWebEnginePage() Q_D(QWebEnginePage); setDevToolsPage(nullptr); d->adapter->stopFinding(); - QWebEngineViewPrivate::bind(d->view, 0); + QWebEngineViewPrivate::bind(nullptr, this, true); } QWebEngineHistory *QWebEnginePage::history() const @@ -1007,7 +1007,7 @@ void QWebEnginePage::setBackgroundColor(const QColor &color) if (d->m_backgroundColor == color) return; d->m_backgroundColor = color; - d->adapter->backgroundColorChanged(); + d->adapter->setBackgroundColor(color); } /*! @@ -1679,7 +1679,7 @@ void QWebEnginePagePrivate::selectClientCert(const QSharedPointer<ClientCertSele { #if QT_CONFIG(ssl) Q_Q(QWebEnginePage); - QWebEngineClientCertSelection certSelection(controller); + QWebEngineClientCertificateSelection certSelection(controller); Q_EMIT q->selectClientCertificate(certSelection); #else @@ -1689,19 +1689,19 @@ void QWebEnginePagePrivate::selectClientCert(const QSharedPointer<ClientCertSele #if QT_CONFIG(ssl) /*! - \fn void QWebEnginePage::selectClientCertificate(QWebEngineClientCertSelection clientCertSelection) + \fn void QWebEnginePage::selectClientCertificate(QWebEngineClientCertificateSelection clientCertificateSelection) \since 5.12 This signal is emitted when a web site requests an SSL client certificate, and one or more were found in system's client certificate store. Handling the signal is asynchronous, and loading will be waiting until a certificate is selected, - or the last copy of \a clientCertSelection is destroyed. + or the last copy of \a clientCertificateSelection is destroyed. - If the signal is not handled, \a clientCertSelection is automatically destroyed, and loading + If the signal is not handled, \a clientCertificateSelection is automatically destroyed, and loading will continue without a client certificate. - \sa QWebEngineClientCertSelection + \sa QWebEngineClientCertificateSelection */ #endif @@ -2365,6 +2365,11 @@ void QWebEnginePage::printToPdf(const QWebEngineCallback<const QByteArray&> &res It is the users responsibility to ensure the \a printer remains valid until \a resultCallback has been called. + \note The rendering of the current content into a temporary PDF document is asynchronous and does + not block the main thread. However, the subsequent rendering of PDF into \a printer runs on the + main thread and will therefore block the event loop. Moreover, printing runs on the browser + process, which is by default not sandboxed. + The \a resultCallback must take a boolean as parameter. If printing was successful, this boolean will have the value \c true, otherwise, its value will be \c false. \since 5.8 diff --git a/src/webenginewidgets/api/qwebenginepage.h b/src/webenginewidgets/api/qwebenginepage.h index b8bf61700..6dd2da21c 100644 --- a/src/webenginewidgets/api/qwebenginepage.h +++ b/src/webenginewidgets/api/qwebenginepage.h @@ -41,7 +41,7 @@ #define QWEBENGINEPAGE_H #include <QtWebEngineWidgets/qtwebenginewidgetsglobal.h> -#include <QtWebEngineWidgets/qwebengineclientcertselection.h> +#include <QtWebEngineWidgets/qwebengineclientcertificateselection.h> #include <QtWebEngineWidgets/qwebenginedownloaditem.h> #include <QtWebEngineCore/qwebenginecallback.h> #include <QtWebEngineCore/qwebenginehttprequest.h> @@ -60,7 +60,7 @@ class QPrinter; class QContextMenuBuilder; class QWebChannel; class QWebEngineCertificateError; -class QWebEngineClientCertSelection; +class QWebEngineClientCertificateSelection; class QWebEngineContextMenuData; class QWebEngineFullScreenRequest; class QWebEngineHistory; @@ -323,7 +323,7 @@ Q_SIGNALS: void quotaRequested(QWebEngineQuotaRequest quotaRequest); void registerProtocolHandlerRequested(QWebEngineRegisterProtocolHandlerRequest request); #if QT_CONFIG(ssl) - void selectClientCertificate(QWebEngineClientCertSelection clientCertSelection); + void selectClientCertificate(QWebEngineClientCertificateSelection clientCertSelection); #endif void authenticationRequired(const QUrl &requestUrl, QAuthenticator *authenticator); diff --git a/src/webenginewidgets/api/qwebengineprofile.cpp b/src/webenginewidgets/api/qwebengineprofile.cpp index b66a1aa53..b2fe49da3 100644 --- a/src/webenginewidgets/api/qwebengineprofile.cpp +++ b/src/webenginewidgets/api/qwebengineprofile.cpp @@ -682,7 +682,7 @@ static bool checkInternalScheme(const QByteArray &scheme) Registers a handler \a handler for custom URL scheme \a scheme in the profile. It is recommended to first register the scheme with \l - QWebEngineUrlScheme::addScheme at application startup. + QWebEngineUrlScheme::registerScheme at application startup. */ void QWebEngineProfile::installUrlSchemeHandler(const QByteArray &scheme, QWebEngineUrlSchemeHandler *handler) { diff --git a/src/webenginewidgets/api/qwebenginescript.cpp b/src/webenginewidgets/api/qwebenginescript.cpp index d5247cde1..d1e996b3a 100644 --- a/src/webenginewidgets/api/qwebenginescript.cpp +++ b/src/webenginewidgets/api/qwebenginescript.cpp @@ -224,6 +224,8 @@ quint32 QWebEngineScript::worldId() const /*! Sets the world ID of the isolated world to \a id when running this script. + + Must be between \c 0 and \c 256. */ void QWebEngineScript::setWorldId(quint32 id) { diff --git a/src/webenginewidgets/api/qwebengineview.cpp b/src/webenginewidgets/api/qwebengineview.cpp index 369c6b02b..aa51e5b0e 100644 --- a/src/webenginewidgets/api/qwebengineview.cpp +++ b/src/webenginewidgets/api/qwebengineview.cpp @@ -55,7 +55,7 @@ QT_BEGIN_NAMESPACE -void QWebEngineViewPrivate::bind(QWebEngineView *view, QWebEnginePage *page) +void QWebEngineViewPrivate::bind(QWebEngineView *view, QWebEnginePage *page, bool pageBeingDeleted) { if (view && page == view->d_func()->page) return; @@ -64,20 +64,22 @@ void QWebEngineViewPrivate::bind(QWebEngineView *view, QWebEnginePage *page) // Un-bind page from its current view. if (QWebEngineView *oldView = page->d_func()->view) { page->disconnect(oldView); - oldView->d_func()->page = 0; + oldView->d_func()->page = nullptr; } page->d_func()->view = view; - page->d_func()->adapter->reattachRWHV(); + if (!pageBeingDeleted) + page->d_func()->adapter->reattachRWHV(); } if (view) { // Un-bind view from its current page. if (QWebEnginePage *oldPage = view->d_func()->page) { oldPage->disconnect(view); - oldPage->d_func()->view = 0; - oldPage->d_func()->adapter->reattachRWHV(); + oldPage->d_func()->view = nullptr; if (oldPage->parent() == view) delete oldPage; + else + oldPage->d_func()->adapter->reattachRWHV(); } view->d_func()->page = page; } @@ -147,8 +149,7 @@ QWebEngineView::QWebEngineView(QWidget *parent) QWebEngineView::~QWebEngineView() { - Q_D(QWebEngineView); - QWebEngineViewPrivate::bind(0, d->page); + QWebEngineViewPrivate::bind(this, nullptr); } QWebEnginePage* QWebEngineView::page() const @@ -423,7 +424,7 @@ void QWebEngineView::dropEvent(QDropEvent *e) if (!d->m_dragEntered) return; e->accept(); - d->page->d_ptr->adapter->endDragging(e->pos(), mapToGlobal(e->pos())); + d->page->d_ptr->adapter->endDragging(e, mapToGlobal(e->pos())); d->m_dragEntered = false; } #endif // QT_CONFIG(draganddrop) diff --git a/src/webenginewidgets/api/qwebengineview_p.h b/src/webenginewidgets/api/qwebengineview_p.h index 7f0cdac45..bfb44bec5 100644 --- a/src/webenginewidgets/api/qwebengineview_p.h +++ b/src/webenginewidgets/api/qwebengineview_p.h @@ -65,7 +65,7 @@ public: Q_DECLARE_PUBLIC(QWebEngineView) QWebEngineView *q_ptr; - static void bind(QWebEngineView *view, QWebEnginePage *page); + static void bind(QWebEngineView *view, QWebEnginePage *page, bool pageBeingDeleted = false); QWebEngineViewPrivate(); diff --git a/src/webenginewidgets/doc/src/qtwebenginewidgets-module.qdoc b/src/webenginewidgets/doc/src/qtwebenginewidgets-module.qdoc index e4f259882..35fed802c 100644 --- a/src/webenginewidgets/doc/src/qtwebenginewidgets-module.qdoc +++ b/src/webenginewidgets/doc/src/qtwebenginewidgets-module.qdoc @@ -41,7 +41,9 @@ \snippet qtwebenginewidgets_build_snippet.qdoc 1 + \if !defined(qtforpython) To link against the module, add the following to your qmake project file: \snippet qtwebenginewidgets_build_snippet.qdoc 0 + \endif */ diff --git a/src/webenginewidgets/doc/src/qwebenginepage_lgpl.qdoc b/src/webenginewidgets/doc/src/qwebenginepage_lgpl.qdoc index 3013903c8..f91b71ea1 100644 --- a/src/webenginewidgets/doc/src/qwebenginepage_lgpl.qdoc +++ b/src/webenginewidgets/doc/src/qwebenginepage_lgpl.qdoc @@ -718,9 +718,9 @@ \since 5.7 Runs the JavaScript code contained in \a scriptSource in the world specified by \a worldId. - The world ID values are the same as provided by QWebEngineScript::ScriptWorldId. Using the - \e runJavaScript() versions without the world ID is the same as running the script in the - \c MainWorld. + The world ID values are the same as provided by QWebEngineScript::ScriptWorldId, and between \c 0 + and \c 256. Using the \e runJavaScript() versions without the world ID is the same as running the + script in the \c MainWorld. When the script has been executed, \a resultCallback is called with the result of the last executed statement. \c resultCallback can be any of a function pointer, a functor or a lambda, diff --git a/src/webenginewidgets/render_widget_host_view_qt_delegate_widget.cpp b/src/webenginewidgets/render_widget_host_view_qt_delegate_widget.cpp index 9497ba0fa..d7bcd0ae3 100644 --- a/src/webenginewidgets/render_widget_host_view_qt_delegate_widget.cpp +++ b/src/webenginewidgets/render_widget_host_view_qt_delegate_widget.cpp @@ -218,20 +218,24 @@ void RenderWidgetHostViewQtDelegateWidget::initAsPopup(const QRect& screenRect) // to be destroyed. setAttribute(Qt::WA_ShowWithoutActivating); setFocusPolicy(Qt::NoFocus); - -#ifdef Q_OS_MACOS - // macOS doesn't like Qt::ToolTip when QWebEngineView is inside a modal dialog, specifically by - // not forwarding click events to the popup. So we use Qt::Tool which behaves the same way, but - // works on macOS too. - setWindowFlags(Qt::Tool | Qt::FramelessWindowHint | Qt::WindowDoesNotAcceptFocus); -#else - setWindowFlags(Qt::ToolTip | Qt::FramelessWindowHint | Qt::WindowDoesNotAcceptFocus); -#endif + setWindowFlags(Qt::Popup | Qt::FramelessWindowHint | Qt::WindowDoesNotAcceptFocus); setGeometry(screenRect); show(); } +void RenderWidgetHostViewQtDelegateWidget::closeEvent(QCloseEvent *event) +{ + Q_UNUSED(event); + + // If a close event was received from the window manager (e.g. when moving the parent window, + // clicking outside the popup area) + // make sure to notify the Chromium WebUI popup and its underlying + // RenderWidgetHostViewQtDelegate instance to be closed. + if (m_isPopup) + m_client->closePopup(); +} + QRectF RenderWidgetHostViewQtDelegateWidget::screenRect() const { return QRectF(x(), y(), width(), height()); diff --git a/src/webenginewidgets/render_widget_host_view_qt_delegate_widget.h b/src/webenginewidgets/render_widget_host_view_qt_delegate_widget.h index 79958132c..42b454bc5 100644 --- a/src/webenginewidgets/render_widget_host_view_qt_delegate_widget.h +++ b/src/webenginewidgets/render_widget_host_view_qt_delegate_widget.h @@ -48,6 +48,12 @@ namespace QtWebEngineCore { +// Useful information keyboard and mouse QEvent propagation. +// A RenderWidgetHostViewQtDelegateWidget instance initialized as a popup will receive +// no keyboard focus (so all keyboard QEvents will be sent to the parent RWHVQD instance), +// but will still receive mouse input (all mouse QEvent moves and clicks will be given to the popup +// RWHVQD instance, and the mouse interaction area covers the surface of the whole parent +// QWebEngineView, and not only the smaller surface that an HTML select popup would occupy). class RenderWidgetHostViewQtDelegateWidget : public QQuickWidget, public RenderWidgetHostViewQtDelegate { Q_OBJECT public: @@ -83,6 +89,7 @@ protected: void resizeEvent(QResizeEvent *resizeEvent) override; void showEvent(QShowEvent *) override; void hideEvent(QHideEvent *) override; + void closeEvent(QCloseEvent *event) override; QVariant inputMethodQuery(Qt::InputMethodQuery query) const override; diff --git a/src/webenginewidgets/webenginewidgets.pro b/src/webenginewidgets/webenginewidgets.pro index 1e24f35e9..e61575d3a 100644 --- a/src/webenginewidgets/webenginewidgets.pro +++ b/src/webenginewidgets/webenginewidgets.pro @@ -14,7 +14,7 @@ INCLUDEPATH += $$PWD api ../core ../core/api ../webengine/api SOURCES = \ api/qtwebenginewidgetsglobal.cpp \ api/qwebenginecertificateerror.cpp \ - api/qwebengineclientcertselection.cpp \ + api/qwebengineclientcertificateselection.cpp \ api/qwebenginecontextmenudata.cpp \ api/qwebenginedownloaditem.cpp \ api/qwebenginefullscreenrequest.cpp \ @@ -30,7 +30,7 @@ SOURCES = \ HEADERS = \ api/qtwebenginewidgetsglobal.h \ api/qwebenginecertificateerror.h \ - api/qwebengineclientcertselection.h \ + api/qwebengineclientcertificateselection.h \ api/qwebenginecontextmenudata.h \ api/qwebenginedownloaditem.h \ api/qwebenginedownloaditem_p.h \ diff --git a/tests/auto/quick/dialogs/tst_dialogs.cpp b/tests/auto/quick/dialogs/tst_dialogs.cpp index e1032f16c..ecc2764fd 100644 --- a/tests/auto/quick/dialogs/tst_dialogs.cpp +++ b/tests/auto/quick/dialogs/tst_dialogs.cpp @@ -175,7 +175,7 @@ void tst_Dialogs::authenticationDialogRequested() QCOMPARE(dialog->type(), type); QCOMPARE(dialog->realm(),realm); QCOMPARE(dialog->url(), url); - QCOMPARE(dialog->proxyHost(), "localhost"); + QCOMPARE(dialog->proxyHost(), QStringLiteral("localhost")); } void tst_Dialogs::javaScriptDialogRequested_data() diff --git a/tests/auto/quick/qtbug-70248/qtbug-70248.pro b/tests/auto/quick/qtbug-70248/qtbug-70248.pro new file mode 100644 index 000000000..e1b18bc16 --- /dev/null +++ b/tests/auto/quick/qtbug-70248/qtbug-70248.pro @@ -0,0 +1,5 @@ +include(../tests.pri) +QT += webengine webengine-private + +RESOURCES += \ + test.qrc diff --git a/tests/auto/quick/qtbug-70248/test.qml b/tests/auto/quick/qtbug-70248/test.qml new file mode 100644 index 000000000..35962aff5 --- /dev/null +++ b/tests/auto/quick/qtbug-70248/test.qml @@ -0,0 +1,16 @@ +import QtQuick 2.9 +import QtQuick.Window 2.2 +import QtWebEngine 1.3 + +Window { + visible: true + width: 640 + height: 480 + + property var url: view && view.url + + WebEngineView { + id: view + anchors.fill: parent + } +} diff --git a/tests/auto/quick/qtbug-70248/test.qrc b/tests/auto/quick/qtbug-70248/test.qrc new file mode 100644 index 000000000..83fea5eb0 --- /dev/null +++ b/tests/auto/quick/qtbug-70248/test.qrc @@ -0,0 +1,5 @@ +<RCC> + <qresource prefix="/"> + <file>test.qml</file> + </qresource> +</RCC> diff --git a/tests/auto/quick/qtbug-70248/tst_qtbug-70248.cpp b/tests/auto/quick/qtbug-70248/tst_qtbug-70248.cpp new file mode 100644 index 000000000..3dffa1d84 --- /dev/null +++ b/tests/auto/quick/qtbug-70248/tst_qtbug-70248.cpp @@ -0,0 +1,57 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtWebEngine module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qtwebengineglobal.h" +#include <QQuickWebEngineProfile> +#include <QQmlApplicationEngine> +#include <QQuickWindow> +#include <QTest> +#include <QSignalSpy> + +class tst_qtbug_70248: public QObject { + Q_OBJECT +public: + tst_qtbug_70248(){} +private slots: + void test(); +}; + +void tst_qtbug_70248::test() +{ + QtWebEngine::initialize(); + QScopedPointer<QQmlApplicationEngine> engine; + QQuickWebEngineProfile::defaultProfile()->setOffTheRecord(true); + engine.reset(new QQmlApplicationEngine()); + engine->load(QUrl(QStringLiteral("qrc:/test.qml"))); + QQuickWindow *widnow = qobject_cast<QQuickWindow*>(engine->rootObjects().first()); + QVERIFY(widnow); +} + +#include "tst_qtbug-70248.moc" +QTEST_MAIN(tst_qtbug_70248) + diff --git a/tests/auto/quick/quick.pro b/tests/auto/quick/quick.pro index 02ce59f17..50a6a8587 100644 --- a/tests/auto/quick/quick.pro +++ b/tests/auto/quick/quick.pro @@ -8,7 +8,8 @@ SUBDIRS += \ inspectorserver \ publicapi \ qquickwebenginedefaultsurfaceformat \ - qquickwebengineview + qquickwebengineview \ + qtbug-70248 qtConfig(webengine-testsupport) { SUBDIRS += \ @@ -17,4 +18,4 @@ qtConfig(webengine-testsupport) { } # QTBUG-66055 -boot2qt: SUBDIRS -= inspectorserver qquickwebenginedefaultsurfaceformat qquickwebengineview qmltests dialogs +boot2qt: SUBDIRS -= inspectorserver qquickwebenginedefaultsurfaceformat qquickwebengineview qmltests dialogs qtbug-70248 diff --git a/tests/auto/widgets/faviconmanager/tst_faviconmanager.cpp b/tests/auto/widgets/faviconmanager/tst_faviconmanager.cpp index 14786e5b0..606d05d9e 100644 --- a/tests/auto/widgets/faviconmanager/tst_faviconmanager.cpp +++ b/tests/auto/widgets/faviconmanager/tst_faviconmanager.cpp @@ -30,6 +30,7 @@ #include "../util.h" #include <qwebenginepage.h> +#include <qwebengineprofile.h> #include <qwebenginesettings.h> #include <qwebengineview.h> @@ -65,13 +66,16 @@ private Q_SLOTS: private: QWebEngineView *m_view; QWebEnginePage *m_page; + QWebEngineProfile *m_profile; }; void tst_FaviconManager::init() { + m_profile = new QWebEngineProfile(this); m_view = new QWebEngineView(); - m_page = m_view->page(); + m_page = new QWebEnginePage(m_profile, m_view); + m_view->setPage(m_page); } @@ -87,6 +91,7 @@ void tst_FaviconManager::cleanupTestCase() void tst_FaviconManager::cleanup() { delete m_view; + delete m_profile; } void tst_FaviconManager::faviconLoad() diff --git a/tests/auto/widgets/origins/tst_origins.cpp b/tests/auto/widgets/origins/tst_origins.cpp index 364c83c3e..d867ff98d 100644 --- a/tests/auto/widgets/origins/tst_origins.cpp +++ b/tests/auto/widgets/origins/tst_origins.cpp @@ -51,76 +51,76 @@ void registerSchemes() { { QWebEngineUrlScheme scheme(QBAL("PathSyntax")); - QWebEngineUrlScheme::addScheme(scheme); + QWebEngineUrlScheme::registerScheme(scheme); } { QWebEngineUrlScheme scheme(QBAL("PathSyntax-Secure")); - scheme.setFlags(QWebEngineUrlScheme::Secure); - QWebEngineUrlScheme::addScheme(scheme); + scheme.setFlags(QWebEngineUrlScheme::SecureScheme); + QWebEngineUrlScheme::registerScheme(scheme); } { QWebEngineUrlScheme scheme(QBAL("PathSyntax-Secure-ServiceWorkersAllowed")); - scheme.setFlags(QWebEngineUrlScheme::Secure | QWebEngineUrlScheme::ServiceWorkersAllowed); - QWebEngineUrlScheme::addScheme(scheme); + scheme.setFlags(QWebEngineUrlScheme::SecureScheme | QWebEngineUrlScheme::ServiceWorkersAllowed); + QWebEngineUrlScheme::registerScheme(scheme); } { QWebEngineUrlScheme scheme(QBAL("PathSyntax-Local")); - scheme.setFlags(QWebEngineUrlScheme::Local); - QWebEngineUrlScheme::addScheme(scheme); + scheme.setFlags(QWebEngineUrlScheme::LocalScheme); + QWebEngineUrlScheme::registerScheme(scheme); } { QWebEngineUrlScheme scheme(QBAL("PathSyntax-LocalAccessAllowed")); scheme.setFlags(QWebEngineUrlScheme::LocalAccessAllowed); - QWebEngineUrlScheme::addScheme(scheme); + QWebEngineUrlScheme::registerScheme(scheme); } { QWebEngineUrlScheme scheme(QBAL("PathSyntax-NoAccessAllowed")); scheme.setFlags(QWebEngineUrlScheme::NoAccessAllowed); - QWebEngineUrlScheme::addScheme(scheme); + QWebEngineUrlScheme::registerScheme(scheme); } { QWebEngineUrlScheme scheme(QBAL("PathSyntax-ServiceWorkersAllowed")); scheme.setFlags(QWebEngineUrlScheme::ServiceWorkersAllowed); - QWebEngineUrlScheme::addScheme(scheme); + QWebEngineUrlScheme::registerScheme(scheme); } { QWebEngineUrlScheme scheme(QBAL("PathSyntax-ViewSourceAllowed")); scheme.setFlags(QWebEngineUrlScheme::ViewSourceAllowed); - QWebEngineUrlScheme::addScheme(scheme); + QWebEngineUrlScheme::registerScheme(scheme); } { QWebEngineUrlScheme scheme(QBAL("HostSyntax")); - scheme.setSyntax(QWebEngineUrlScheme::HostSyntax); - QWebEngineUrlScheme::addScheme(scheme); + scheme.setSyntax(QWebEngineUrlScheme::Syntax::Host); + QWebEngineUrlScheme::registerScheme(scheme); } { QWebEngineUrlScheme scheme(QBAL("HostSyntax-ContentSecurityPolicyIgnored")); - scheme.setSyntax(QWebEngineUrlScheme::HostSyntax); + scheme.setSyntax(QWebEngineUrlScheme::Syntax::Host); scheme.setFlags(QWebEngineUrlScheme::ContentSecurityPolicyIgnored); - QWebEngineUrlScheme::addScheme(scheme); + QWebEngineUrlScheme::registerScheme(scheme); } { QWebEngineUrlScheme scheme(QBAL("HostAndPortSyntax")); - scheme.setSyntax(QWebEngineUrlScheme::HostAndPortSyntax); + scheme.setSyntax(QWebEngineUrlScheme::Syntax::HostAndPort); scheme.setDefaultPort(42); - QWebEngineUrlScheme::addScheme(scheme); + QWebEngineUrlScheme::registerScheme(scheme); } { QWebEngineUrlScheme scheme(QBAL("HostPortAndUserInformationSyntax")); - scheme.setSyntax(QWebEngineUrlScheme::HostPortAndUserInformationSyntax); + scheme.setSyntax(QWebEngineUrlScheme::Syntax::HostPortAndUserInformation); scheme.setDefaultPort(42); - QWebEngineUrlScheme::addScheme(scheme); + QWebEngineUrlScheme::registerScheme(scheme); } } Q_CONSTRUCTOR_FUNCTION(registerSchemes) @@ -664,22 +664,22 @@ void tst_Origins::viewSource() { QVERIFY(load(QSL("view-source:file:" THIS_DIR "resources/viewSource.html"))); #ifdef Q_OS_WIN - QCOMPARE(m_page->requestedUrl(), QSL("file:///" THIS_DIR "resources/viewSource.html")); + QCOMPARE(m_page->requestedUrl().toString(), QSL("file:///" THIS_DIR "resources/viewSource.html")); #else - QCOMPARE(m_page->requestedUrl(), QSL("file:" THIS_DIR "resources/viewSource.html")); + QCOMPARE(m_page->requestedUrl().toString(), QSL("file://" THIS_DIR "resources/viewSource.html")); #endif QVERIFY(load(QSL("view-source:qrc:/resources/viewSource.html"))); - QCOMPARE(m_page->requestedUrl(), QSL("qrc:/resources/viewSource.html")); + QCOMPARE(m_page->requestedUrl().toString(), QSL("qrc:/resources/viewSource.html")); QVERIFY(load(QSL("view-source:tst:/resources/viewSource.html"))); - QCOMPARE(m_page->requestedUrl(), QSL("about:blank")); + QCOMPARE(m_page->requestedUrl().toString(), QSL("about:blank")); QVERIFY(load(QSL("view-source:PathSyntax:/resources/viewSource.html"))); - QCOMPARE(m_page->requestedUrl(), QSL("about:blank")); + QCOMPARE(m_page->requestedUrl().toString(), QSL("about:blank")); QVERIFY(load(QSL("view-source:PathSyntax-ViewSourceAllowed:/resources/viewSource.html"))); - QCOMPARE(m_page->requestedUrl(), QSL("pathsyntax-viewsourceallowed:/resources/viewSource.html")); + QCOMPARE(m_page->requestedUrl().toString(), QSL("pathsyntax-viewsourceallowed:/resources/viewSource.html")); } QTEST_MAIN(tst_Origins) diff --git a/tests/auto/widgets/qwebenginepage/BLACKLIST b/tests/auto/widgets/qwebenginepage/BLACKLIST index 50f275eb4..9c7572b40 100644 --- a/tests/auto/widgets/qwebenginepage/BLACKLIST +++ b/tests/auto/widgets/qwebenginepage/BLACKLIST @@ -9,5 +9,3 @@ osx [mouseMovementProperties] windows -osx-10.11 -osx-10.12 diff --git a/tests/auto/widgets/qwebenginepage/tst_qwebenginepage.cpp b/tests/auto/widgets/qwebenginepage/tst_qwebenginepage.cpp index 95a46fbc2..8b36d5a6f 100644 --- a/tests/auto/widgets/qwebenginepage/tst_qwebenginepage.cpp +++ b/tests/auto/widgets/qwebenginepage/tst_qwebenginepage.cpp @@ -128,6 +128,8 @@ private Q_SLOTS: void getUserMediaRequest(); void getUserMediaRequestDesktopAudio(); void getUserMediaRequestSettingDisabled(); + void getUserMediaRequestDesktopVideoManyPages(); + void getUserMediaRequestDesktopVideoManyRequests(); void savePage(); void crashTests_LazyInitializationOfMainFrame(); @@ -2470,6 +2472,44 @@ void tst_QWebEnginePage::getUserMediaRequestSettingDisabled() QTRY_VERIFY(!page.jsPromiseFulfilled() && page.jsPromiseRejected()); } +// Try to trigger any possible race condition between the UI thread (permission +// management) and the audio/device thread (desktop capture initialization). +void tst_QWebEnginePage::getUserMediaRequestDesktopVideoManyPages() +{ + const QString constraints = QStringLiteral("{video: { mandatory: { chromeMediaSource: 'desktop' }}}"); + const QWebEnginePage::Feature feature = QWebEnginePage::DesktopVideoCapture; + std::vector<GetUserMediaTestPage> pages(10); + for (GetUserMediaTestPage &page : pages) + QTRY_VERIFY_WITH_TIMEOUT(page.loadSucceeded(), 20000); + for (GetUserMediaTestPage &page : pages) + page.settings()->setAttribute(QWebEngineSettings::ScreenCaptureEnabled, true); + for (GetUserMediaTestPage &page : pages) + page.jsGetUserMedia(constraints); + for (GetUserMediaTestPage &page : pages) + QTRY_VERIFY(page.gotFeatureRequest(feature)); + for (GetUserMediaTestPage &page : pages) + page.acceptPendingRequest(); + for (GetUserMediaTestPage &page : pages) + QTRY_VERIFY(page.jsPromiseFulfilled() || page.jsPromiseRejected()); +} + +// Try to trigger any possible race condition between the UI or audio/device +// threads and the desktop capture thread, where the capture actually happens. +void tst_QWebEnginePage::getUserMediaRequestDesktopVideoManyRequests() +{ + const QString constraints = QStringLiteral("{video: { mandatory: { chromeMediaSource: 'desktop' }}}"); + const QWebEnginePage::Feature feature = QWebEnginePage::DesktopVideoCapture; + GetUserMediaTestPage page; + QTRY_VERIFY_WITH_TIMEOUT(page.loadSucceeded(), 20000); + page.settings()->setAttribute(QWebEngineSettings::ScreenCaptureEnabled, true); + for (int i = 0; i != 100; ++i) { + page.jsGetUserMedia(constraints); + QTRY_VERIFY(page.gotFeatureRequest(feature)); + page.acceptPendingRequest(); + QTRY_VERIFY(page.jsPromiseFulfilled() || page.jsPromiseRejected()); + } +} + void tst_QWebEnginePage::savePage() { QWebEngineView view; @@ -4078,6 +4118,7 @@ void tst_QWebEnginePage::mouseMovementProperties() ConsolePage page; view.setPage(&page); view.resize(640, 480); + QTest::mouseMove(&view, QPoint(10, 10)); view.show(); QVERIFY(QTest::qWaitForWindowExposed(&view)); diff --git a/tests/auto/widgets/qwebengineprofile/tst_qwebengineprofile.cpp b/tests/auto/widgets/qwebengineprofile/tst_qwebengineprofile.cpp index a04a1ac6f..1fe0f0304 100644 --- a/tests/auto/widgets/qwebengineprofile/tst_qwebengineprofile.cpp +++ b/tests/auto/widgets/qwebengineprofile/tst_qwebengineprofile.cpp @@ -56,6 +56,7 @@ private Q_SLOTS: void httpAcceptLanguage(); void downloadItem(); void changePersistentPath(); + void initiator(); }; void tst_QWebEngineProfile::init() @@ -528,5 +529,44 @@ void tst_QWebEngineProfile::changePersistentPath() QVERIFY(newPath.endsWith(QStringLiteral("Test2"))); } +class InitiatorSpy : public QWebEngineUrlSchemeHandler +{ +public: + QUrl initiator; + void requestStarted(QWebEngineUrlRequestJob *job) override + { + initiator = job->initiator(); + job->fail(QWebEngineUrlRequestJob::RequestDenied); + } +}; + +void tst_QWebEngineProfile::initiator() +{ + InitiatorSpy handler; + QWebEngineProfile profile; + profile.installUrlSchemeHandler("foo", &handler); + QWebEnginePage page(&profile); + QSignalSpy loadFinishedSpy(&page, SIGNAL(loadFinished(bool))); + + // about:blank has a unique origin, so initiator should be QUrl("null") + evaluateJavaScriptSync(&page, "window.location = 'foo:bar'"); + QVERIFY(loadFinishedSpy.wait()); + QCOMPARE(handler.initiator, QUrl("null")); + + page.setHtml("", QUrl("http://test:123/foo%20bar")); + QVERIFY(loadFinishedSpy.wait()); + + // baseUrl determines the origin, so QUrl("http://test:123") + evaluateJavaScriptSync(&page, "window.location = 'foo:bar'"); + QVERIFY(loadFinishedSpy.wait()); + QCOMPARE(handler.initiator, QUrl("http://test:123")); + + // Directly calling load/setUrl should have initiator QUrl(), meaning + // browser-initiated, trusted. + page.load(QUrl("foo:bar")); + QVERIFY(loadFinishedSpy.wait()); + QCOMPARE(handler.initiator, QUrl()); +} + QTEST_MAIN(tst_QWebEngineProfile) #include "tst_qwebengineprofile.moc" diff --git a/tests/auto/widgets/qwebengineview/tst_qwebengineview.cpp b/tests/auto/widgets/qwebengineview/tst_qwebengineview.cpp index 286efca56..7bf1b5c42 100644 --- a/tests/auto/widgets/qwebengineview/tst_qwebengineview.cpp +++ b/tests/auto/widgets/qwebengineview/tst_qwebengineview.cpp @@ -187,6 +187,8 @@ private Q_SLOTS: void webUIURLs_data(); void webUIURLs(); void visibilityState(); + void jsKeyboardEvent(); + void deletePage(); }; // This will be called before the first test function is executed. @@ -375,7 +377,7 @@ void tst_QWebEngineView::microFocusCoordinates() QVariant initialMicroFocus = webView.focusProxy()->inputMethodQuery(Qt::ImMicroFocus); evaluateJavaScriptSync(webView.page(), "window.scrollBy(0, 50)"); - QVERIFY(scrollSpy.wait()); + QTRY_VERIFY(scrollSpy.count() > 0); QTRY_VERIFY(webView.focusProxy()->inputMethodQuery(Qt::ImMicroFocus).isValid()); QVariant currentMicroFocus = webView.focusProxy()->inputMethodQuery(Qt::ImMicroFocus); @@ -2346,9 +2348,9 @@ void tst_QWebEngineView::imeJSInputEvents() // Simply committing text should not trigger any JS composition event. QTRY_COMPARE(logLines().count(), 3); - QCOMPARE(logLines()[0], "[object InputEvent] beforeinput commit"); - QCOMPARE(logLines()[1], "[object TextEvent] textInput commit"); - QCOMPARE(logLines()[2], "[object InputEvent] input commit"); + QCOMPARE(logLines()[0], QStringLiteral("[object InputEvent] beforeinput commit")); + QCOMPARE(logLines()[1], QStringLiteral("[object TextEvent] textInput commit")); + QCOMPARE(logLines()[2], QStringLiteral("[object InputEvent] input commit")); evaluateJavaScriptSync(view.page(), "clear()"); QTRY_VERIFY(evaluateJavaScriptSync(view.page(), "log.textContent + input.textContent").toString().isEmpty()); @@ -2362,10 +2364,10 @@ void tst_QWebEngineView::imeJSInputEvents() } QTRY_COMPARE(logLines().count(), 4); - QCOMPARE(logLines()[0], "[object CompositionEvent] compositionstart "); - QCOMPARE(logLines()[1], "[object InputEvent] beforeinput preedit"); - QCOMPARE(logLines()[2], "[object CompositionEvent] compositionupdate preedit"); - QCOMPARE(logLines()[3], "[object InputEvent] input preedit"); + QCOMPARE(logLines()[0], QStringLiteral("[object CompositionEvent] compositionstart ")); + QCOMPARE(logLines()[1], QStringLiteral("[object InputEvent] beforeinput preedit")); + QCOMPARE(logLines()[2], QStringLiteral("[object CompositionEvent] compositionupdate preedit")); + QCOMPARE(logLines()[3], QStringLiteral("[object InputEvent] input preedit")); { QList<QInputMethodEvent::Attribute> attributes; @@ -2376,11 +2378,11 @@ void tst_QWebEngineView::imeJSInputEvents() } QTRY_COMPARE(logLines().count(), 9); - QCOMPARE(logLines()[4], "[object InputEvent] beforeinput commit"); - QCOMPARE(logLines()[5], "[object CompositionEvent] compositionupdate commit"); - QCOMPARE(logLines()[6], "[object TextEvent] textInput commit"); - QCOMPARE(logLines()[7], "[object InputEvent] input commit"); - QCOMPARE(logLines()[8], "[object CompositionEvent] compositionend commit"); + QCOMPARE(logLines()[4], QStringLiteral("[object InputEvent] beforeinput commit")); + QCOMPARE(logLines()[5], QStringLiteral("[object CompositionEvent] compositionupdate commit")); + QCOMPARE(logLines()[6], QStringLiteral("[object TextEvent] textInput commit")); + QCOMPARE(logLines()[7], QStringLiteral("[object InputEvent] input commit")); + QCOMPARE(logLines()[8], QStringLiteral("[object CompositionEvent] compositionend commit")); evaluateJavaScriptSync(view.page(), "clear()"); QTRY_VERIFY(evaluateJavaScriptSync(view.page(), "log.textContent + input.textContent").toString().isEmpty()); @@ -2394,10 +2396,10 @@ void tst_QWebEngineView::imeJSInputEvents() } QTRY_COMPARE(logLines().count(), 4); - QCOMPARE(logLines()[0], "[object CompositionEvent] compositionstart "); - QCOMPARE(logLines()[1], "[object InputEvent] beforeinput preedit"); - QCOMPARE(logLines()[2], "[object CompositionEvent] compositionupdate preedit"); - QCOMPARE(logLines()[3], "[object InputEvent] input preedit"); + QCOMPARE(logLines()[0], QStringLiteral("[object CompositionEvent] compositionstart ")); + QCOMPARE(logLines()[1], QStringLiteral("[object InputEvent] beforeinput preedit")); + QCOMPARE(logLines()[2], QStringLiteral("[object CompositionEvent] compositionupdate preedit")); + QCOMPARE(logLines()[3], QStringLiteral("[object InputEvent] input preedit")); { QList<QInputMethodEvent::Attribute> attributes; @@ -2407,11 +2409,11 @@ void tst_QWebEngineView::imeJSInputEvents() } QTRY_COMPARE(logLines().count(), 9); - QCOMPARE(logLines()[4], "[object InputEvent] beforeinput "); - QCOMPARE(logLines()[5], "[object CompositionEvent] compositionupdate "); - QCOMPARE(logLines()[6], "[object TextEvent] textInput "); - QCOMPARE(logLines()[7], "[object InputEvent] input null"); - QCOMPARE(logLines()[8], "[object CompositionEvent] compositionend "); + QCOMPARE(logLines()[4], QStringLiteral("[object InputEvent] beforeinput ")); + QCOMPARE(logLines()[5], QStringLiteral("[object CompositionEvent] compositionupdate ")); + QCOMPARE(logLines()[6], QStringLiteral("[object TextEvent] textInput ")); + QCOMPARE(logLines()[7], QStringLiteral("[object InputEvent] input null")); + QCOMPARE(logLines()[8], QStringLiteral("[object CompositionEvent] compositionend ")); evaluateJavaScriptSync(view.page(), "clear()"); QTRY_VERIFY(evaluateJavaScriptSync(view.page(), "log.textContent + input.textContent").toString().isEmpty()); @@ -2767,5 +2769,44 @@ void tst_QWebEngineView::visibilityState() QCOMPARE(evaluateJavaScriptSync(view.page(), "document.visibilityState").toString(), QStringLiteral("visible")); } +void tst_QWebEngineView::jsKeyboardEvent() +{ + QWebEngineView view; + evaluateJavaScriptSync( + view.page(), + "var log = '';" + "addEventListener('keydown', (ev) => {" + " log += [ev.keyCode, ev.code, ev.key, ev.ctrlKey, ev.shiftKey, ev.altKey].join(',') + ';';" + "});"); + // Note that this only tests the fallback code path where native scan codes are not used. +#if defined(Q_OS_MACOS) + // See Qt::AA_MacDontSwapCtrlAndMeta + QTest::keyClick(view.focusProxy(), 'A', Qt::MetaModifier | Qt::ShiftModifier); +#else + QTest::keyClick(view.focusProxy(), 'A', Qt::ControlModifier | Qt::ShiftModifier); +#endif + QString expected = QStringLiteral( + "16,ShiftLeft,Shift,false,true,false;" + "17,ControlLeft,Control,true,true,false;" + "65,KeyA,A,true,true,false;" + ); + QTRY_VERIFY(evaluateJavaScriptSync(view.page(), "log") != QVariant(QString())); + QCOMPARE(evaluateJavaScriptSync(view.page(), "log"), expected); +} + +void tst_QWebEngineView::deletePage() +{ + QWebEngineView view; + QWebEnginePage *page = view.page(); + QVERIFY(page); + QCOMPARE(page->parent(), &view); + delete page; + // Test that a new page is created and that it is useful: + QVERIFY(view.page()); + QSignalSpy spy(view.page(), &QWebEnginePage::loadFinished); + view.page()->load(QStringLiteral("about:blank")); + QTRY_VERIFY(spy.count()); +} + QTEST_MAIN(tst_QWebEngineView) #include "tst_qwebengineview.moc" diff --git a/tests/manual/widgets/inputmethods/webview.cpp b/tests/manual/widgets/inputmethods/webview.cpp index ac5dadce1..62e210ecf 100644 --- a/tests/manual/widgets/inputmethods/webview.cpp +++ b/tests/manual/widgets/inputmethods/webview.cpp @@ -27,6 +27,7 @@ ****************************************************************************/ #include "webview.h" +#include <QWebEngineSettings> WebView::WebView(QWidget *parent) : QWebEngineView(parent) @@ -46,5 +47,6 @@ WebView::WebView(QWidget *parent) " <input type='text' id='input1' />" "</body></html>"); + settings()->setAttribute(QWebEngineSettings::FocusOnNavigationEnabled, true); setHtml(html); } diff --git a/tests/quicktestbrowser/resources.qrc b/tests/quicktestbrowser/resources.qrc index b1652852b..63ff051de 100644 --- a/tests/quicktestbrowser/resources.qrc +++ b/tests/quicktestbrowser/resources.qrc @@ -11,9 +11,9 @@ </qresource> <qresource prefix="icons"> <!-- To the risk of this breaking more often, do not duplicate the resources since this application won't be deployed --> - <file alias="go-next.png">../../examples/webengine/quicknanobrowser/icons/go-next.png</file> - <file alias="go-previous.png">../../examples/webengine/quicknanobrowser/icons/go-previous.png</file> - <file alias="process-stop.png">../../examples/webengine/quicknanobrowser/icons/process-stop.png</file> - <file alias="view-refresh.png">../../examples/webengine/quicknanobrowser/icons/view-refresh.png</file> + <file alias="go-next.png">../../examples/webengine/quicknanobrowser/icons/3rdparty/go-next.png</file> + <file alias="go-previous.png">../../examples/webengine/quicknanobrowser/icons/3rdparty/go-previous.png</file> + <file alias="process-stop.png">../../examples/webengine/quicknanobrowser/icons/3rdparty/process-stop.png</file> + <file alias="view-refresh.png">../../examples/webengine/quicknanobrowser/icons/3rdparty/view-refresh.png</file> </qresource> </RCC> |