diff options
224 files changed, 13375 insertions, 884 deletions
diff --git a/.qmake.conf b/.qmake.conf index c9b4a6ddb..a942dacce 100644 --- a/.qmake.conf +++ b/.qmake.conf @@ -5,4 +5,4 @@ QTWEBENGINE_OUT_ROOT = $$shadowed($$PWD) load(qt_build_config) CONFIG += warning_clean -MODULE_VERSION = 5.14.1 +MODULE_VERSION = 5.15.0 diff --git a/LICENSE.FDL b/LICENSE.FDL new file mode 100644 index 000000000..086177c0a --- /dev/null +++ b/LICENSE.FDL @@ -0,0 +1,450 @@ + GNU Free Documentation License + Version 1.3, 3 November 2008 + + + Copyright (C) 2000, 2001, 2002, 2007, 2008 Free Software Foundation, Inc. + <http://fsf.org/> + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +0. PREAMBLE + +The purpose of this License is to make a manual, textbook, or other +functional and useful document "free" in the sense of freedom: to +assure everyone the effective freedom to copy and redistribute it, +with or without modifying it, either commercially or noncommercially. +Secondarily, this License preserves for the author and publisher a way +to get credit for their work, while not being considered responsible +for modifications made by others. + +This License is a kind of "copyleft", which means that derivative +works of the document must themselves be free in the same sense. It +complements the GNU General Public License, which is a copyleft +license designed for free software. + +We have designed this License in order to use it for manuals for free +software, because free software needs free documentation: a free +program should come with manuals providing the same freedoms that the +software does. But this License is not limited to software manuals; +it can be used for any textual work, regardless of subject matter or +whether it is published as a printed book. We recommend this License +principally for works whose purpose is instruction or reference. + + +1. APPLICABILITY AND DEFINITIONS + +This License applies to any manual or other work, in any medium, that +contains a notice placed by the copyright holder saying it can be +distributed under the terms of this License. Such a notice grants a +world-wide, royalty-free license, unlimited in duration, to use that +work under the conditions stated herein. The "Document", below, +refers to any such manual or work. Any member of the public is a +licensee, and is addressed as "you". You accept the license if you +copy, modify or distribute the work in a way requiring permission +under copyright law. + +A "Modified Version" of the Document means any work containing the +Document or a portion of it, either copied verbatim, or with +modifications and/or translated into another language. + +A "Secondary Section" is a named appendix or a front-matter section of +the Document that deals exclusively with the relationship of the +publishers or authors of the Document to the Document's overall +subject (or to related matters) and contains nothing that could fall +directly within that overall subject. (Thus, if the Document is in +part a textbook of mathematics, a Secondary Section may not explain +any mathematics.) The relationship could be a matter of historical +connection with the subject or with related matters, or of legal, +commercial, philosophical, ethical or political position regarding +them. + +The "Invariant Sections" are certain Secondary Sections whose titles +are designated, as being those of Invariant Sections, in the notice +that says that the Document is released under this License. If a +section does not fit the above definition of Secondary then it is not +allowed to be designated as Invariant. The Document may contain zero +Invariant Sections. If the Document does not identify any Invariant +Sections then there are none. + +The "Cover Texts" are certain short passages of text that are listed, +as Front-Cover Texts or Back-Cover Texts, in the notice that says that +the Document is released under this License. A Front-Cover Text may +be at most 5 words, and a Back-Cover Text may be at most 25 words. + +A "Transparent" copy of the Document means a machine-readable copy, +represented in a format whose specification is available to the +general public, that is suitable for revising the document +straightforwardly with generic text editors or (for images composed of +pixels) generic paint programs or (for drawings) some widely available +drawing editor, and that is suitable for input to text formatters or +for automatic translation to a variety of formats suitable for input +to text formatters. A copy made in an otherwise Transparent file +format whose markup, or absence of markup, has been arranged to thwart +or discourage subsequent modification by readers is not Transparent. +An image format is not Transparent if used for any substantial amount +of text. A copy that is not "Transparent" is called "Opaque". + +Examples of suitable formats for Transparent copies include plain +ASCII without markup, Texinfo input format, LaTeX input format, SGML +or XML using a publicly available DTD, and standard-conforming simple +HTML, PostScript or PDF designed for human modification. Examples of +transparent image formats include PNG, XCF and JPG. Opaque formats +include proprietary formats that can be read and edited only by +proprietary word processors, SGML or XML for which the DTD and/or +processing tools are not generally available, and the +machine-generated HTML, PostScript or PDF produced by some word +processors for output purposes only. + +The "Title Page" means, for a printed book, the title page itself, +plus such following pages as are needed to hold, legibly, the material +this License requires to appear in the title page. For works in +formats which do not have any title page as such, "Title Page" means +the text near the most prominent appearance of the work's title, +preceding the beginning of the body of the text. + +The "publisher" means any person or entity that distributes copies of +the Document to the public. + +A section "Entitled XYZ" means a named subunit of the Document whose +title either is precisely XYZ or contains XYZ in parentheses following +text that translates XYZ in another language. (Here XYZ stands for a +specific section name mentioned below, such as "Acknowledgements", +"Dedications", "Endorsements", or "History".) To "Preserve the Title" +of such a section when you modify the Document means that it remains a +section "Entitled XYZ" according to this definition. + +The Document may include Warranty Disclaimers next to the notice which +states that this License applies to the Document. These Warranty +Disclaimers are considered to be included by reference in this +License, but only as regards disclaiming warranties: any other +implication that these Warranty Disclaimers may have is void and has +no effect on the meaning of this License. + +2. VERBATIM COPYING + +You may copy and distribute the Document in any medium, either +commercially or noncommercially, provided that this License, the +copyright notices, and the license notice saying this License applies +to the Document are reproduced in all copies, and that you add no +other conditions whatsoever to those of this License. You may not use +technical measures to obstruct or control the reading or further +copying of the copies you make or distribute. However, you may accept +compensation in exchange for copies. If you distribute a large enough +number of copies you must also follow the conditions in section 3. + +You may also lend copies, under the same conditions stated above, and +you may publicly display copies. + + +3. COPYING IN QUANTITY + +If you publish printed copies (or copies in media that commonly have +printed covers) of the Document, numbering more than 100, and the +Document's license notice requires Cover Texts, you must enclose the +copies in covers that carry, clearly and legibly, all these Cover +Texts: Front-Cover Texts on the front cover, and Back-Cover Texts on +the back cover. Both covers must also clearly and legibly identify +you as the publisher of these copies. The front cover must present +the full title with all words of the title equally prominent and +visible. You may add other material on the covers in addition. +Copying with changes limited to the covers, as long as they preserve +the title of the Document and satisfy these conditions, can be treated +as verbatim copying in other respects. + +If the required texts for either cover are too voluminous to fit +legibly, you should put the first ones listed (as many as fit +reasonably) on the actual cover, and continue the rest onto adjacent +pages. + +If you publish or distribute Opaque copies of the Document numbering +more than 100, you must either include a machine-readable Transparent +copy along with each Opaque copy, or state in or with each Opaque copy +a computer-network location from which the general network-using +public has access to download using public-standard network protocols +a complete Transparent copy of the Document, free of added material. +If you use the latter option, you must take reasonably prudent steps, +when you begin distribution of Opaque copies in quantity, to ensure +that this Transparent copy will remain thus accessible at the stated +location until at least one year after the last time you distribute an +Opaque copy (directly or through your agents or retailers) of that +edition to the public. + +It is requested, but not required, that you contact the authors of the +Document well before redistributing any large number of copies, to +give them a chance to provide you with an updated version of the +Document. + + +4. MODIFICATIONS + +You may copy and distribute a Modified Version of the Document under +the conditions of sections 2 and 3 above, provided that you release +the Modified Version under precisely this License, with the Modified +Version filling the role of the Document, thus licensing distribution +and modification of the Modified Version to whoever possesses a copy +of it. In addition, you must do these things in the Modified Version: + +A. Use in the Title Page (and on the covers, if any) a title distinct + from that of the Document, and from those of previous versions + (which should, if there were any, be listed in the History section + of the Document). You may use the same title as a previous version + if the original publisher of that version gives permission. +B. List on the Title Page, as authors, one or more persons or entities + responsible for authorship of the modifications in the Modified + Version, together with at least five of the principal authors of the + Document (all of its principal authors, if it has fewer than five), + unless they release you from this requirement. +C. State on the Title page the name of the publisher of the + Modified Version, as the publisher. +D. Preserve all the copyright notices of the Document. +E. Add an appropriate copyright notice for your modifications + adjacent to the other copyright notices. +F. Include, immediately after the copyright notices, a license notice + giving the public permission to use the Modified Version under the + terms of this License, in the form shown in the Addendum below. +G. Preserve in that license notice the full lists of Invariant Sections + and required Cover Texts given in the Document's license notice. +H. Include an unaltered copy of this License. +I. Preserve the section Entitled "History", Preserve its Title, and add + to it an item stating at least the title, year, new authors, and + publisher of the Modified Version as given on the Title Page. If + there is no section Entitled "History" in the Document, create one + stating the title, year, authors, and publisher of the Document as + given on its Title Page, then add an item describing the Modified + Version as stated in the previous sentence. +J. Preserve the network location, if any, given in the Document for + public access to a Transparent copy of the Document, and likewise + the network locations given in the Document for previous versions + it was based on. These may be placed in the "History" section. + You may omit a network location for a work that was published at + least four years before the Document itself, or if the original + publisher of the version it refers to gives permission. +K. For any section Entitled "Acknowledgements" or "Dedications", + Preserve the Title of the section, and preserve in the section all + the substance and tone of each of the contributor acknowledgements + and/or dedications given therein. +L. Preserve all the Invariant Sections of the Document, + unaltered in their text and in their titles. Section numbers + or the equivalent are not considered part of the section titles. +M. Delete any section Entitled "Endorsements". Such a section + may not be included in the Modified Version. +N. Do not retitle any existing section to be Entitled "Endorsements" + or to conflict in title with any Invariant Section. +O. Preserve any Warranty Disclaimers. + +If the Modified Version includes new front-matter sections or +appendices that qualify as Secondary Sections and contain no material +copied from the Document, you may at your option designate some or all +of these sections as invariant. To do this, add their titles to the +list of Invariant Sections in the Modified Version's license notice. +These titles must be distinct from any other section titles. + +You may add a section Entitled "Endorsements", provided it contains +nothing but endorsements of your Modified Version by various +parties--for example, statements of peer review or that the text has +been approved by an organization as the authoritative definition of a +standard. + +You may add a passage of up to five words as a Front-Cover Text, and a +passage of up to 25 words as a Back-Cover Text, to the end of the list +of Cover Texts in the Modified Version. Only one passage of +Front-Cover Text and one of Back-Cover Text may be added by (or +through arrangements made by) any one entity. If the Document already +includes a cover text for the same cover, previously added by you or +by arrangement made by the same entity you are acting on behalf of, +you may not add another; but you may replace the old one, on explicit +permission from the previous publisher that added the old one. + +The author(s) and publisher(s) of the Document do not by this License +give permission to use their names for publicity for or to assert or +imply endorsement of any Modified Version. + + +5. COMBINING DOCUMENTS + +You may combine the Document with other documents released under this +License, under the terms defined in section 4 above for modified +versions, provided that you include in the combination all of the +Invariant Sections of all of the original documents, unmodified, and +list them all as Invariant Sections of your combined work in its +license notice, and that you preserve all their Warranty Disclaimers. + +The combined work need only contain one copy of this License, and +multiple identical Invariant Sections may be replaced with a single +copy. If there are multiple Invariant Sections with the same name but +different contents, make the title of each such section unique by +adding at the end of it, in parentheses, the name of the original +author or publisher of that section if known, or else a unique number. +Make the same adjustment to the section titles in the list of +Invariant Sections in the license notice of the combined work. + +In the combination, you must combine any sections Entitled "History" +in the various original documents, forming one section Entitled +"History"; likewise combine any sections Entitled "Acknowledgements", +and any sections Entitled "Dedications". You must delete all sections +Entitled "Endorsements". + + +6. COLLECTIONS OF DOCUMENTS + +You may make a collection consisting of the Document and other +documents released under this License, and replace the individual +copies of this License in the various documents with a single copy +that is included in the collection, provided that you follow the rules +of this License for verbatim copying of each of the documents in all +other respects. + +You may extract a single document from such a collection, and +distribute it individually under this License, provided you insert a +copy of this License into the extracted document, and follow this +License in all other respects regarding verbatim copying of that +document. + + +7. AGGREGATION WITH INDEPENDENT WORKS + +A compilation of the Document or its derivatives with other separate +and independent documents or works, in or on a volume of a storage or +distribution medium, is called an "aggregate" if the copyright +resulting from the compilation is not used to limit the legal rights +of the compilation's users beyond what the individual works permit. +When the Document is included in an aggregate, this License does not +apply to the other works in the aggregate which are not themselves +derivative works of the Document. + +If the Cover Text requirement of section 3 is applicable to these +copies of the Document, then if the Document is less than one half of +the entire aggregate, the Document's Cover Texts may be placed on +covers that bracket the Document within the aggregate, or the +electronic equivalent of covers if the Document is in electronic form. +Otherwise they must appear on printed covers that bracket the whole +aggregate. + + +8. TRANSLATION + +Translation is considered a kind of modification, so you may +distribute translations of the Document under the terms of section 4. +Replacing Invariant Sections with translations requires special +permission from their copyright holders, but you may include +translations of some or all Invariant Sections in addition to the +original versions of these Invariant Sections. You may include a +translation of this License, and all the license notices in the +Document, and any Warranty Disclaimers, provided that you also include +the original English version of this License and the original versions +of those notices and disclaimers. In case of a disagreement between +the translation and the original version of this License or a notice +or disclaimer, the original version will prevail. + +If a section in the Document is Entitled "Acknowledgements", +"Dedications", or "History", the requirement (section 4) to Preserve +its Title (section 1) will typically require changing the actual +title. + + +9. TERMINATION + +You may not copy, modify, sublicense, or distribute the Document +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense, or distribute it is void, and +will automatically terminate your rights under this License. + +However, if you cease all violation of this License, then your license +from a particular copyright holder is reinstated (a) provisionally, +unless and until the copyright holder explicitly and finally +terminates your license, and (b) permanently, if the copyright holder +fails to notify you of the violation by some reasonable means prior to +60 days after the cessation. + +Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + +Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, receipt of a copy of some or all of the same material does +not give you any rights to use it. + + +10. FUTURE REVISIONS OF THIS LICENSE + +The Free Software Foundation may publish new, revised versions of the +GNU Free Documentation License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in +detail to address new problems or concerns. See +http://www.gnu.org/copyleft/. + +Each version of the License is given a distinguishing version number. +If the Document specifies that a particular numbered version of this +License "or any later version" applies to it, you have the option of +following the terms and conditions either of that specified version or +of any later version that has been published (not as a draft) by the +Free Software Foundation. If the Document does not specify a version +number of this License, you may choose any version ever published (not +as a draft) by the Free Software Foundation. If the Document +specifies that a proxy can decide which future versions of this +License can be used, that proxy's public statement of acceptance of a +version permanently authorizes you to choose that version for the +Document. + +11. RELICENSING + +"Massive Multiauthor Collaboration Site" (or "MMC Site") means any +World Wide Web server that publishes copyrightable works and also +provides prominent facilities for anybody to edit those works. A +public wiki that anybody can edit is an example of such a server. A +"Massive Multiauthor Collaboration" (or "MMC") contained in the site +means any set of copyrightable works thus published on the MMC site. + +"CC-BY-SA" means the Creative Commons Attribution-Share Alike 3.0 +license published by Creative Commons Corporation, a not-for-profit +corporation with a principal place of business in San Francisco, +California, as well as future copyleft versions of that license +published by that same organization. + +"Incorporate" means to publish or republish a Document, in whole or in +part, as part of another Document. + +An MMC is "eligible for relicensing" if it is licensed under this +License, and if all works that were first published under this License +somewhere other than this MMC, and subsequently incorporated in whole or +in part into the MMC, (1) had no cover texts or invariant sections, and +(2) were thus incorporated prior to November 1, 2008. + +The operator of an MMC Site may republish an MMC contained in the site +under CC-BY-SA on the same site at any time before August 1, 2009, +provided the MMC is eligible for relicensing. + + +ADDENDUM: How to use this License for your documents + +To use this License in a document you have written, include a copy of +the License in the document and put the following copyright and +license notices just after the title page: + + Copyright (c) YEAR YOUR NAME. + Permission is granted to copy, distribute and/or modify this document + under the terms of the GNU Free Documentation License, Version 1.3 + or any later version published by the Free Software Foundation; + with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. + A copy of the license is included in the section entitled "GNU + Free Documentation License". + +If you have Invariant Sections, Front-Cover Texts and Back-Cover Texts, +replace the "with...Texts." line with this: + + with the Invariant Sections being LIST THEIR TITLES, with the + Front-Cover Texts being LIST, and with the Back-Cover Texts being LIST. + +If you have Invariant Sections without Cover Texts, or some other +combination of the three, merge those two alternatives to suit the +situation. + +If your document contains nontrivial examples of program code, we +recommend releasing these examples in parallel under your choice of +free software license, such as the GNU General Public License, +to permit their use in free software. diff --git a/LICENSE.LGPLv3 b/LICENSE.LGPLv3 new file mode 100644 index 000000000..e200fb5b2 --- /dev/null +++ b/LICENSE.LGPLv3 @@ -0,0 +1,174 @@ + GNU LESSER GENERAL PUBLIC LICENSE + + The Qt Toolkit is Copyright (C) 2015 The Qt Company Ltd. + Contact: http://www.qt.io/licensing/ + + You may use, distribute and copy the Qt Toolkit under the terms of + GNU Lesser General Public License version 3, which is displayed below. + This license makes reference to the version 3 of the GNU General + Public License, which you can find in the LICENSE.GPLv3 file. + +------------------------------------------------------------------------- + + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright © 2007 Free Software Foundation, Inc. <http://fsf.org/> +Everyone is permitted to copy and distribute verbatim copies of this +licensedocument, but changing it is not allowed. + +This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + +0. Additional Definitions. + + As used herein, “this License” refers to version 3 of the GNU Lesser +General Public License, and the “GNU GPL” refers to version 3 of the +GNU General Public License. + + “The Library” refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An “Application” is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A “Combined Work” is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the “Linked +Version”. + + The “Minimal Corresponding Source” for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The “Corresponding Application Code” for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + +1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + +2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort + to ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + +3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this + license document. + +4. Combined Works. + + You may convey a Combined Work under terms of your choice that, taken +together, effectively do not restrict modification of the portions of +the Library contained in the Combined Work and reverse engineering for +debugging such modifications, if you also do each of the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this + license document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of + this License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with + the Library. A suitable mechanism is one that (a) uses at run + time a copy of the Library already present on the user's + computer system, and (b) will operate properly with a modified + version of the Library that is interface-compatible with the + Linked Version. + + e) Provide Installation Information, but only if you would + otherwise be required to provide such information under section 6 + of the GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the Application + with a modified version of the Linked Version. (If you use option + 4d0, the Installation Information must accompany the Minimal + Corresponding Source and Corresponding Application Code. If you + use option 4d1, you must provide the Installation Information in + the manner specified by section 6 of the GNU GPL for conveying + Corresponding Source.) + +5. Combined Libraries. + + You may place library facilities that are a work based on the Library +side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities, conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of + it is a work based on the Library, and explaining where to find + the accompanying uncombined form of the same work. + +6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +as you received it specifies that a certain numbered version of the +GNU Lesser General Public License “or any later version” applies to +it, you have the option of following the terms and conditions either +of that published version or of any later version published by the +Free Software Foundation. If the Library as you received it does not +specify a version number of the GNU Lesser General Public License, +you may choose any version of the GNU Lesser General Public License +ever published by the Free Software Foundation. + +If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the Library. diff --git a/configure.json b/configure.json index 8caad24f0..a5dbad35b 100644 --- a/configure.json +++ b/configure.json @@ -3,6 +3,8 @@ "src/buildtools", "src/core", "src/webengine", - "src/webenginewidgets" + "src/webenginewidgets", + "src/pdf", + "src/pdfwidgets" ] } diff --git a/configure.pri b/configure.pri index 3a144e3f8..f89df5273 100644 --- a/configure.pri +++ b/configure.pri @@ -114,7 +114,7 @@ defineTest(qtwebengine_platformError) { defineTest(qtConfTest_detectPlatform) { QT_FOR_CONFIG += gui-private - !linux:!win32:!macos { + !linux:!win32:!macos:!ios { qtwebengine_platformError("Unknown platform. Qt WebEngine only supports Linux, Windows, and macOS.") } else { linux:qtwebengine_isLinuxPlatformSupported() { @@ -126,6 +126,9 @@ defineTest(qtConfTest_detectPlatform) { macos:qtwebengine_isMacOsPlatformSupported() { $${1}.platform = "macos" } + ios:qtwebengine_isMacOsPlatformSupported() { + $${1}.platform = "ios" + } } !isEmpty(platformError) { @@ -423,21 +426,21 @@ defineTest(qtwebengine_isWindowsPlatformSupported) { defineTest(qtwebengine_isMacOsPlatformSupported) { # FIXME: Try to get it back down to 8.2 for building on OS X 10.11 !qtwebengine_isMinXcodeVersion(8, 3, 3) { - qtwebengine_platformError("Using Xcode version $$QMAKE_XCODE_VERSION, but at least version 8.3.3 is required to build Qt WebEngine.") + qtwebengine_platformError("Using Xcode version $$QMAKE_XCODE_VERSION, but at least version 8.3.3 is required to build Qt WebEngine or Qt Pdf.") return(false) } !clang|intel_icc { - qtwebengine_platformError("Qt WebEngine on macOS requires Clang.") + qtwebengine_platformError("Qt WebEngine and Qt Pdf requires Clang.") return(false) } # We require macOS 10.12 (darwin version 16.0.0) or newer. darwin_major_version = $$section(QMAKE_HOST.version, ., 0, 0) lessThan(darwin_major_version, 16) { - qtwebengine_platformError("Building Qt WebEngine requires macOS version 10.12 or newer.") + qtwebengine_platformError("Building Qt WebEngine or Qt Pdf requires macOS version 10.12 or newer.") return(false) } !qtwebengine_isMinOSXSDKVersion(10, 12): { - qtwebengine_platformError("Building Qt WebEngine requires a macOS SDK version of 10.12 or newer. Current version is $${WEBENGINE_OSX_SDK_PRODUCT_VERSION}.") + qtwebengine_platformError("Building Qt WebEngine or Qt Pdf requires a macOS SDK version of 10.12 or newer. Current version is $${WEBENGINE_OSX_SDK_PRODUCT_VERSION}.") return(false) } return(true) diff --git a/examples/examples.pro b/examples/examples.pro index 3dac9b0b7..b4f411aeb 100644 --- a/examples/examples.pro +++ b/examples/examples.pro @@ -3,3 +3,7 @@ TEMPLATE=subdirs qtHaveModule(webengine): SUBDIRS += webengine qtHaveModule(webenginewidgets): SUBDIRS += webenginewidgets + +qtHaveModule(pdfwidgets): SUBDIRS += pdfwidgets + +qtHaveModule(quick): qtHaveModule(pdf): qtHaveModule(pdfwidgets): SUBDIRS += pdf diff --git a/examples/pdf/pdf.pro b/examples/pdf/pdf.pro new file mode 100644 index 000000000..45df33e46 --- /dev/null +++ b/examples/pdf/pdf.pro @@ -0,0 +1,3 @@ +TEMPLATE=subdirs + +SUBDIRS += pdfviewer diff --git a/examples/pdf/pdfviewer/main.cpp b/examples/pdf/pdfviewer/main.cpp new file mode 100644 index 000000000..6b94a3de1 --- /dev/null +++ b/examples/pdf/pdfviewer/main.cpp @@ -0,0 +1,70 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: http://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 <QApplication> +#include <QQmlApplicationEngine> + +int main(int argc, char* argv[]) +{ + QApplication app(argc, argv); + QCoreApplication::setApplicationName("Qt Quick PDF Viewer Example"); + QCoreApplication::setOrganizationName("QtProject"); + QCoreApplication::setApplicationVersion(QT_VERSION_STR); + QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); + + QQmlApplicationEngine engine; + engine.load(QUrl(QStringLiteral("qrc:///pdfviewer/viewer.qml"))); + if (app.arguments().count() > 1) { + QUrl toLoad = QUrl::fromUserInput(app.arguments().at(1)); + engine.rootObjects().first()->setProperty("source", toLoad); + } + + return app.exec(); +} diff --git a/examples/pdf/pdfviewer/pdfviewer.pro b/examples/pdf/pdfviewer/pdfviewer.pro new file mode 100644 index 000000000..697349cee --- /dev/null +++ b/examples/pdf/pdfviewer/pdfviewer.pro @@ -0,0 +1,14 @@ +TEMPLATE = app + +QT += qml quick pdf widgets + +SOURCES += main.cpp + +RESOURCES += \ + viewer.qrc +EXAMPLE_FILES = \ + viewer.qml + +target.path = $$[QT_INSTALL_EXAMPLES]/pdf/pdfviewer +INSTALLS += target + diff --git a/examples/pdf/pdfviewer/resources/document-open.svg b/examples/pdf/pdfviewer/resources/document-open.svg new file mode 100644 index 000000000..bf23123a3 --- /dev/null +++ b/examples/pdf/pdfviewer/resources/document-open.svg @@ -0,0 +1,13 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"> + <defs id="defs3051"> + <style type="text/css" id="current-color-scheme"> + .ColorScheme-Text { + color:#232629; + } + </style> + </defs> + <path style="fill:currentColor;fill-opacity:1;stroke:none" + d="m4 4v24h24l-1-1h-22v-13h5l3-3h14v16l1 1v-21h-10l-3-3z" + class="ColorScheme-Text" + /> +</svg> diff --git a/examples/pdf/pdfviewer/resources/edit-clear.svg b/examples/pdf/pdfviewer/resources/edit-clear.svg new file mode 100644 index 000000000..1c35aaf04 --- /dev/null +++ b/examples/pdf/pdfviewer/resources/edit-clear.svg @@ -0,0 +1,15 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> + <defs id="defs3051"> + <style type="text/css" id="current-color-scheme"> + .ColorScheme-Text { + color:#232629; + } + </style> + </defs> + <path + style="fill:currentColor;fill-opacity:1;stroke:none" + d="M 8 3 L 0.94335938 10.056641 L 0 11 L 0.94335938 11.943359 L 8 19 L 20.333984 19 L 22 19 L 22 3 L 20.333984 3 L 8 3 z M 11.320312 7 L 14 9.6796875 L 16.679688 7 L 18 8.3203125 L 15.320312 11 L 18 13.679688 L 16.679688 15 L 14 12.320312 L 11.320312 15 L 10 13.679688 L 12.679688 11 L 10 8.3203125 L 11.320312 7 z " + class="ColorScheme-Text" + transform="translate(1,1)" + /> +</svg> diff --git a/examples/pdf/pdfviewer/resources/go-next-view-page.svg b/examples/pdf/pdfviewer/resources/go-next-view-page.svg new file mode 100644 index 000000000..e453ddbec --- /dev/null +++ b/examples/pdf/pdfviewer/resources/go-next-view-page.svg @@ -0,0 +1,13 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> + <defs id="defs3051"> + <style type="text/css" id="current-color-scheme"> + .ColorScheme-Text { + color:#232629; + } + </style> + </defs> + <path style="fill:currentColor;fill-opacity:1;stroke:none" + d="M 8.7070312 4 L 8 4.7070312 L 14.125 10.832031 L 15.292969 12 L 14.125 13.167969 L 8 19.292969 L 8.7070312 20 L 14.832031 13.875 L 16.707031 12 L 14.832031 10.125 L 8.7070312 4 z " + class="ColorScheme-Text" + /> +</svg> diff --git a/examples/pdf/pdfviewer/resources/go-previous-view-page.svg b/examples/pdf/pdfviewer/resources/go-previous-view-page.svg new file mode 100644 index 000000000..b032309e9 --- /dev/null +++ b/examples/pdf/pdfviewer/resources/go-previous-view-page.svg @@ -0,0 +1,13 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> + <defs id="defs3051"> + <style type="text/css" id="current-color-scheme"> + .ColorScheme-Text { + color:#232629; + } + </style> + </defs> + <path style="fill:currentColor;fill-opacity:1;stroke:none" + d="M 15.292969 4 L 9.1679688 10.125 L 7.2929688 12 L 9.1679688 13.875 L 15.292969 20 L 16 19.292969 L 9.875 13.167969 L 8.7070312 12 L 9.875 10.832031 L 16 4.7070312 L 15.292969 4 z " + class="ColorScheme-Text" + /> +</svg> diff --git a/examples/pdf/pdfviewer/resources/rotate-left.svg b/examples/pdf/pdfviewer/resources/rotate-left.svg new file mode 100644 index 000000000..90ce53c9d --- /dev/null +++ b/examples/pdf/pdfviewer/resources/rotate-left.svg @@ -0,0 +1,6 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"> + <g color="#000" font-weight="400" fill="#474747"> + <path d="M2 9v1c0 .265.093.53.281.719l3.72 3.719 3.718-3.72c.188-.187.281-.453.281-.718V9H9c-.265 0-.53.093-.719.281l-2.28 2.281-2.282-2.28A1.015 1.015 0 0 0 3 9z"/> + <path d="M8.5 3A3.515 3.515 0 0 0 5 6.5V12h2V6.5C7 5.66 7.66 5 8.5 5H13V3z"/> + </g> +</svg> diff --git a/examples/pdf/pdfviewer/resources/rotate-right.svg b/examples/pdf/pdfviewer/resources/rotate-right.svg new file mode 100644 index 000000000..7383d1c84 --- /dev/null +++ b/examples/pdf/pdfviewer/resources/rotate-right.svg @@ -0,0 +1,6 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"> + <g font-weight="400" fill="#474747"> + <path d="M3 3v2h4.5C8.34 5 9 5.66 9 6.5V12h2V6.5C11 4.579 9.421 3 7.5 3z"/> + <path d="M6 9h1c.257 0 .529.13.719.313L10 11.592l2.281-2.28C12.471 9.13 12.743 9 13 9h1v1c0 .31-.09.552-.281.75L10 14.406 6.281 10.75C6.091 10.552 6 10.31 6 10z"/> + </g> +</svg> diff --git a/examples/pdf/pdfviewer/resources/zoom-in.svg b/examples/pdf/pdfviewer/resources/zoom-in.svg new file mode 100644 index 000000000..efdc9f17d --- /dev/null +++ b/examples/pdf/pdfviewer/resources/zoom-in.svg @@ -0,0 +1,13 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> + <defs id="defs3051"> + <style type="text/css" id="current-color-scheme"> + .ColorScheme-Text { + color:#232629; + } + </style> + </defs> + <path style="fill:currentColor;fill-opacity:1;stroke:none" + d="M 4 4 L 4 6 L 5 6 L 5 5 L 6 5 L 6 4 L 4 4 z M 9 4 L 9 5 L 11 5 L 11 4 L 9 4 z M 13 4 L 13 5 L 15 5 L 15 4 L 13 4 z M 18 4 L 18 5 L 19 5 L 19 6 L 20 6 L 20 4 L 18 4 z M 12 8 L 12 9 L 14.292969 9 L 11 12.292969 L 11.707031 13 L 15 9.7070312 L 15 12 L 16 12 L 16 8 L 15 8 L 12 8 z M 4 9 L 4 11 L 5 11 L 5 9 L 4 9 z M 19 9 L 19 11 L 20 11 L 20 9 L 19 9 z M 19 13 L 19 15 L 20 15 L 20 13 L 19 13 z M 4 14 L 4 20 L 10 20 L 10 14 L 4 14 z M 5 15 L 9 15 L 9 19 L 5 19 L 5 15 z M 19 18 L 19 19 L 18 19 L 18 20 L 20 20 L 20 18 L 19 18 z " + class="ColorScheme-Text" + /> +</svg> diff --git a/examples/pdf/pdfviewer/resources/zoom-original.svg b/examples/pdf/pdfviewer/resources/zoom-original.svg new file mode 100644 index 000000000..1b4080a03 --- /dev/null +++ b/examples/pdf/pdfviewer/resources/zoom-original.svg @@ -0,0 +1,13 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> + <defs id="defs3051"> + <style type="text/css" id="current-color-scheme"> + .ColorScheme-Text { + color:#232629; + } + </style> + </defs> + <path style="fill:currentColor;fill-opacity:1;stroke:none" + d="M 4 4 L 4 5 L 4 7 L 5 7 L 5 5 L 7 5 L 7 4 L 5 4 L 4 4 z M 17 4 L 17 5 L 19 5 L 19 7 L 20 7 L 20 5 L 20 4 L 19 4 L 17 4 z M 6 6 L 6 18 L 18 18 L 18 6 L 6 6 z M 7 7 L 17 7 L 17 17 L 7 17 L 7 7 z M 4 17 L 4 19 L 4 20 L 7 20 L 7 19 L 5 19 L 5 17 L 4 17 z M 19 17 L 19 19 L 17 19 L 17 20 L 20 20 L 20 17 L 19 17 z " + class="ColorScheme-Text" + /> +</svg> diff --git a/examples/pdf/pdfviewer/resources/zoom-out.svg b/examples/pdf/pdfviewer/resources/zoom-out.svg new file mode 100644 index 000000000..fcde9e526 --- /dev/null +++ b/examples/pdf/pdfviewer/resources/zoom-out.svg @@ -0,0 +1,13 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> + <defs id="defs3051"> + <style type="text/css" id="current-color-scheme"> + .ColorScheme-Text { + color:#232629; + } + </style> + </defs> + <path style="fill:currentColor;fill-opacity:1;stroke:none" + d="M 4 4 L 4 11 L 5 11 L 5 5 L 19 5 L 19 19 L 13 19 L 13 20 L 20 20 L 20 19 L 20 5 L 20 4 L 5 4 L 4 4 z M 15.292969 8 L 12 11.292969 L 12 9 L 11 9 L 11 13 L 12 13 L 15 13 L 15 12 L 12.707031 12 L 16 8.7070312 L 15.292969 8 z M 4 14 L 4 16 L 5 16 L 5 15 L 6 15 L 6 14 L 4 14 z M 8 14 L 8 15 L 9 15 L 9 16 L 10 16 L 10 14 L 8 14 z M 4 18 L 4 20 L 6 20 L 6 19 L 5 19 L 5 18 L 4 18 z M 9 18 L 9 19 L 8 19 L 8 20 L 10 20 L 10 18 L 9 18 z " + class="ColorScheme-Text" + /> +</svg> diff --git a/examples/pdf/pdfviewer/viewer.qml b/examples/pdf/pdfviewer/viewer.qml new file mode 100644 index 000000000..adc2a4b5b --- /dev/null +++ b/examples/pdf/pdfviewer/viewer.qml @@ -0,0 +1,202 @@ +/**************************************************************************** +** +** Copyright (C) 2020 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$ +** +****************************************************************************/ +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Layouts 1.15 +import QtQuick.Pdf 5.15 +import QtQuick.Shapes 1.15 +import QtQuick.Window 2.15 +import Qt.labs.platform 1.1 as Platform + +ApplicationWindow { + id: root + width: 800 + height: 640 + color: "lightgrey" + title: document.title + visible: true + property alias source: document.source // for main.cpp + property real scaleStep: Math.sqrt(2) + + header: ToolBar { + RowLayout { + anchors.fill: parent + anchors.rightMargin: 6 + ToolButton { + action: Action { + shortcut: StandardKey.Open + icon.source: "resources/document-open.svg" + onTriggered: fileDialog.open() + } + } + ToolButton { + action: Action { + shortcut: StandardKey.ZoomIn + enabled: pageView.sourceSize.width < 10000 + icon.source: "resources/zoom-in.svg" + onTriggered: pageView.renderScale *= root.scaleStep + } + } + ToolButton { + action: Action { + shortcut: StandardKey.ZoomOut + enabled: pageView.sourceSize.width > 50 + icon.source: "resources/zoom-out.svg" + onTriggered: pageView.renderScale /= root.scaleStep + } + } + ToolButton { + action: Action { + shortcut: "Ctrl+0" + icon.source: "resources/zoom-original.svg" + onTriggered: pageView.renderScale = 1 + } + } + ToolButton { + action: Action { + shortcut: "Ctrl+L" + icon.source: "resources/rotate-left.svg" + onTriggered: pageView.rotation -= 90 + } + } + ToolButton { + action: Action { + shortcut: "Ctrl+R" + icon.source: "resources/rotate-right.svg" + onTriggered: pageView.rotation += 90 + } + } + ToolButton { + action: Action { + shortcut: StandardKey.MoveToPreviousPage + icon.source: "resources/go-previous-view-page.svg" + enabled: pageView.currentPage > 0 + onTriggered: pageView.currentPage-- + } + } + ToolButton { + action: Action { + shortcut: StandardKey.MoveToNextPage + icon.source: "resources/go-next-view-page.svg" + enabled: pageView.currentPage < pageView.pageCount - 1 + onTriggered: pageView.currentPage++ + } + } + TextField { + id: searchField + placeholderText: "search" + Layout.minimumWidth: 200 + Layout.fillWidth: true + Image { + visible: searchField.text !== "" + source: "resources/edit-clear.svg" + anchors { + right: parent.right + top: parent.top + bottom: parent.bottom + margins: 3 + rightMargin: 5 + } + TapHandler { + onTapped: searchField.clear() + } + } + } + Shortcut { + sequence: StandardKey.Find + onActivated: searchField.forceActiveFocus() + } + Shortcut { + sequence: StandardKey.Quit + onActivated: Qt.quit() + } + } + } + + Platform.FileDialog { + id: fileDialog + title: "Open a PDF file" + nameFilters: [ "PDF files (*.pdf)" ] + onAccepted: document.source = file + } + + Dialog { + id: errorDialog + title: "Error loading " + document.source + standardButtons: Dialog.Ok + modal: true + closePolicy: Popup.CloseOnEscape + anchors.centerIn: parent + width: 300 + + Label { + id: errorField + text: document.error + } + } + + PdfPageView { + id: pageView + document: PdfDocument { + id: document + onStatusChanged: if (status === PdfDocument.Error) errorDialog.open() + } + searchString: searchField.text + } + + footer: Label { + property size implicitPointSize: document.pagePointSize(pageView.currentPage) + text: "page " + (pageView.currentPage + 1) + " of " + pageView.pageCount + + " scale " + pageView.renderScale.toFixed(2) + + " sourceSize " + pageView.sourceSize.width.toFixed(1) + "x" + pageView.sourceSize.height.toFixed(1) + + " original " + implicitPointSize.width.toFixed(1) + "x" + implicitPointSize.height.toFixed(1) + visible: pageView.pageCount > 0 + } +} diff --git a/examples/pdf/pdfviewer/viewer.qrc b/examples/pdf/pdfviewer/viewer.qrc new file mode 100644 index 000000000..78f9c8d30 --- /dev/null +++ b/examples/pdf/pdfviewer/viewer.qrc @@ -0,0 +1,14 @@ +<RCC> + <qresource prefix="/pdfviewer"> + <file>viewer.qml</file> + <file>resources/edit-clear.svg</file> + <file>resources/go-next-view-page.svg</file> + <file>resources/go-previous-view-page.svg</file> + <file>resources/rotate-left.svg</file> + <file>resources/rotate-right.svg</file> + <file>resources/zoom-in.svg</file> + <file>resources/zoom-original.svg</file> + <file>resources/zoom-out.svg</file> + <file>resources/document-open.svg</file> + </qresource> +</RCC> diff --git a/examples/pdfwidgets/pdfviewer/images/busy.png b/examples/pdfwidgets/pdfviewer/images/busy.png Binary files differnew file mode 100644 index 000000000..69056c479 --- /dev/null +++ b/examples/pdfwidgets/pdfviewer/images/busy.png diff --git a/examples/pdfwidgets/pdfviewer/images/fileopen.png b/examples/pdfwidgets/pdfviewer/images/fileopen.png Binary files differnew file mode 100644 index 000000000..33e0d6394 --- /dev/null +++ b/examples/pdfwidgets/pdfviewer/images/fileopen.png diff --git a/examples/pdfwidgets/pdfviewer/images/go-next-24.png b/examples/pdfwidgets/pdfviewer/images/go-next-24.png Binary files differnew file mode 100644 index 000000000..9a55ef3d8 --- /dev/null +++ b/examples/pdfwidgets/pdfviewer/images/go-next-24.png diff --git a/examples/pdfwidgets/pdfviewer/images/go-previous-24.png b/examples/pdfwidgets/pdfviewer/images/go-previous-24.png Binary files differnew file mode 100644 index 000000000..2ea769eb8 --- /dev/null +++ b/examples/pdfwidgets/pdfviewer/images/go-previous-24.png diff --git a/examples/pdfwidgets/pdfviewer/images/zoom-in-24.png b/examples/pdfwidgets/pdfviewer/images/zoom-in-24.png Binary files differnew file mode 100644 index 000000000..d29b142b6 --- /dev/null +++ b/examples/pdfwidgets/pdfviewer/images/zoom-in-24.png diff --git a/examples/pdfwidgets/pdfviewer/images/zoom-in-32.png b/examples/pdfwidgets/pdfviewer/images/zoom-in-32.png Binary files differnew file mode 100644 index 000000000..34d70af37 --- /dev/null +++ b/examples/pdfwidgets/pdfviewer/images/zoom-in-32.png diff --git a/examples/pdfwidgets/pdfviewer/images/zoom-out-24.png b/examples/pdfwidgets/pdfviewer/images/zoom-out-24.png Binary files differnew file mode 100644 index 000000000..19703474f --- /dev/null +++ b/examples/pdfwidgets/pdfviewer/images/zoom-out-24.png diff --git a/examples/pdfwidgets/pdfviewer/images/zoom-out-32.png b/examples/pdfwidgets/pdfviewer/images/zoom-out-32.png Binary files differnew file mode 100644 index 000000000..b83220661 --- /dev/null +++ b/examples/pdfwidgets/pdfviewer/images/zoom-out-32.png diff --git a/examples/pdfwidgets/pdfviewer/main.cpp b/examples/pdfwidgets/pdfviewer/main.cpp new file mode 100644 index 000000000..20fa6f8cd --- /dev/null +++ b/examples/pdfwidgets/pdfviewer/main.cpp @@ -0,0 +1,51 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtPDF module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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.LGPLv3 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.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 later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "mainwindow.h" +#include <QApplication> +#include <QUrl> + +int main(int argc, char *argv[]) +{ + QApplication a(argc, argv); + MainWindow w; + QStringList args = a.arguments(); + w.show(); + if (args.length() > 1) + w.open(QUrl::fromLocalFile(args[1])); + + return a.exec(); +} diff --git a/examples/pdfwidgets/pdfviewer/mainwindow.cpp b/examples/pdfwidgets/pdfviewer/mainwindow.cpp new file mode 100644 index 000000000..5f9bf389f --- /dev/null +++ b/examples/pdfwidgets/pdfviewer/mainwindow.cpp @@ -0,0 +1,162 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtPDF module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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.LGPLv3 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.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 later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "mainwindow.h" +#include "ui_mainwindow.h" + +#include "pageselector.h" +#include "zoomselector.h" + +#include <QFileDialog> +#include <QMessageBox> +#include <QPdfBookmarkModel> +#include <QPdfDocument> +#include <QPdfPageNavigation> +#include <QtMath> + +const qreal zoomMultiplier = qSqrt(2.0); + +Q_LOGGING_CATEGORY(lcExample, "qt.examples.pdfviewer") + +MainWindow::MainWindow(QWidget *parent) + : QMainWindow(parent) + , ui(new Ui::MainWindow) + , m_zoomSelector(new ZoomSelector(this)) + , m_pageSelector(new PageSelector(this)) + , m_document(new QPdfDocument(this)) +{ + ui->setupUi(this); + + m_zoomSelector->setMaximumWidth(150); + ui->mainToolBar->insertWidget(ui->actionZoom_In, m_zoomSelector); + + m_pageSelector->setMaximumWidth(150); + ui->mainToolBar->addWidget(m_pageSelector); + + m_pageSelector->setPageNavigation(ui->pdfView->pageNavigation()); + + connect(m_zoomSelector, &ZoomSelector::zoomModeChanged, ui->pdfView, &QPdfView::setZoomMode); + connect(m_zoomSelector, &ZoomSelector::zoomFactorChanged, ui->pdfView, &QPdfView::setZoomFactor); + m_zoomSelector->reset(); + + QPdfBookmarkModel *bookmarkModel = new QPdfBookmarkModel(this); + bookmarkModel->setDocument(m_document); + + ui->bookmarkView->setModel(bookmarkModel); + connect(ui->bookmarkView, SIGNAL(activated(QModelIndex)), this, SLOT(bookmarkSelected(QModelIndex))); + + ui->tabWidget->setTabEnabled(1, false); // disable 'Pages' tab for now + + ui->pdfView->setDocument(m_document); + + connect(ui->pdfView, &QPdfView::zoomFactorChanged, + m_zoomSelector, &ZoomSelector::setZoomFactor); +} + +MainWindow::~MainWindow() +{ + delete ui; +} + +void MainWindow::open(const QUrl &docLocation) +{ + if (docLocation.isLocalFile()) { + m_document->load(docLocation.toLocalFile()); + const auto documentTitle = m_document->metaData(QPdfDocument::Title).toString(); + setWindowTitle(!documentTitle.isEmpty() ? documentTitle : QStringLiteral("PDF Viewer")); + } else { + qCDebug(lcExample) << docLocation << "is not a valid local file"; + QMessageBox::critical(this, tr("Failed to open"), tr("%1 is not a valid local file").arg(docLocation.toString())); + } + qCDebug(lcExample) << docLocation; +} + +void MainWindow::bookmarkSelected(const QModelIndex &index) +{ + if (!index.isValid()) + return; + + const int page = index.data(QPdfBookmarkModel::PageNumberRole).toInt(); + ui->pdfView->pageNavigation()->setCurrentPage(page); +} + +void MainWindow::on_actionOpen_triggered() +{ + QUrl toOpen = QFileDialog::getOpenFileUrl(this, tr("Choose a PDF"), QUrl(), "Portable Documents (*.pdf)"); + if (toOpen.isValid()) + open(toOpen); +} + +void MainWindow::on_actionQuit_triggered() +{ + QApplication::quit(); +} + +void MainWindow::on_actionAbout_triggered() +{ + QMessageBox::about(this, tr("About PdfViewer"), + tr("An example using QPdfDocument")); +} + +void MainWindow::on_actionAbout_Qt_triggered() +{ + QMessageBox::aboutQt(this); +} + +void MainWindow::on_actionZoom_In_triggered() +{ + ui->pdfView->setZoomFactor(ui->pdfView->zoomFactor() * zoomMultiplier); +} + +void MainWindow::on_actionZoom_Out_triggered() +{ + ui->pdfView->setZoomFactor(ui->pdfView->zoomFactor() / zoomMultiplier); +} + +void MainWindow::on_actionPrevious_Page_triggered() +{ + ui->pdfView->pageNavigation()->goToPreviousPage(); +} + +void MainWindow::on_actionNext_Page_triggered() +{ + ui->pdfView->pageNavigation()->goToNextPage(); +} + +void MainWindow::on_actionContinuous_triggered() +{ + ui->pdfView->setPageMode(ui->actionContinuous->isChecked() ? QPdfView::MultiPage : QPdfView::SinglePage); +} diff --git a/examples/pdfwidgets/pdfviewer/mainwindow.h b/examples/pdfwidgets/pdfviewer/mainwindow.h new file mode 100644 index 000000000..afdfcade4 --- /dev/null +++ b/examples/pdfwidgets/pdfviewer/mainwindow.h @@ -0,0 +1,90 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtPDF module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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.LGPLv3 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.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 later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include <QLoggingCategory> +#include <QMainWindow> + +Q_DECLARE_LOGGING_CATEGORY(lcExample) + +QT_BEGIN_NAMESPACE +namespace Ui { +class MainWindow; +} + +class QPdfDocument; +class QPdfView; +QT_END_NAMESPACE + +class PageSelector; +class ZoomSelector; + +class MainWindow : public QMainWindow +{ + Q_OBJECT + +public: + explicit MainWindow(QWidget *parent = nullptr); + ~MainWindow(); + +public slots: + void open(const QUrl &docLocation); + +private slots: + void bookmarkSelected(const QModelIndex &index); + + // action handlers + void on_actionOpen_triggered(); + void on_actionQuit_triggered(); + void on_actionAbout_triggered(); + void on_actionAbout_Qt_triggered(); + void on_actionZoom_In_triggered(); + void on_actionZoom_Out_triggered(); + void on_actionPrevious_Page_triggered(); + void on_actionNext_Page_triggered(); + void on_actionContinuous_triggered(); + +private: + Ui::MainWindow *ui; + ZoomSelector *m_zoomSelector; + PageSelector *m_pageSelector; + + QPdfDocument *m_document; +}; + +#endif // MAINWINDOW_H diff --git a/examples/pdfwidgets/pdfviewer/mainwindow.ui b/examples/pdfwidgets/pdfviewer/mainwindow.ui new file mode 100644 index 000000000..2651525a5 --- /dev/null +++ b/examples/pdfwidgets/pdfviewer/mainwindow.ui @@ -0,0 +1,281 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>MainWindow</class> + <widget class="QMainWindow" name="MainWindow"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>700</width> + <height>600</height> + </rect> + </property> + <property name="windowTitle"> + <string>PDF Viewer</string> + </property> + <property name="unifiedTitleAndToolBarOnMac"> + <bool>true</bool> + </property> + <widget class="QWidget" name="centralWidget"> + <layout class="QVBoxLayout" name="verticalLayout"> + <property name="spacing"> + <number>0</number> + </property> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <widget class="QWidget" name="widget" native="true"> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <property name="spacing"> + <number>0</number> + </property> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <widget class="QSplitter" name="splitter"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <widget class="QTabWidget" name="tabWidget"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Expanding"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="tabPosition"> + <enum>QTabWidget::West</enum> + </property> + <property name="currentIndex"> + <number>0</number> + </property> + <property name="documentMode"> + <bool>false</bool> + </property> + <widget class="QWidget" name="bookmarkTab"> + <attribute name="title"> + <string>Bookmarks</string> + </attribute> + <layout class="QVBoxLayout" name="verticalLayout_3"> + <property name="spacing"> + <number>0</number> + </property> + <property name="leftMargin"> + <number>2</number> + </property> + <property name="topMargin"> + <number>2</number> + </property> + <property name="rightMargin"> + <number>2</number> + </property> + <property name="bottomMargin"> + <number>2</number> + </property> + <item> + <widget class="QTreeView" name="bookmarkView"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Expanding"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="headerHidden"> + <bool>true</bool> + </property> + </widget> + </item> + </layout> + </widget> + <widget class="QWidget" name="pagesTab"> + <attribute name="title"> + <string>Pages</string> + </attribute> + </widget> + </widget> + <widget class="QPdfView" name="pdfView" native="true"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Expanding"> + <horstretch>10</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + </widget> + </widget> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + <widget class="QMenuBar" name="menuBar"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>700</width> + <height>22</height> + </rect> + </property> + <widget class="QMenu" name="menuFile"> + <property name="title"> + <string>File</string> + </property> + <addaction name="actionOpen"/> + <addaction name="actionQuit"/> + </widget> + <widget class="QMenu" name="menuHelp"> + <property name="title"> + <string>Help</string> + </property> + <addaction name="actionAbout"/> + <addaction name="actionAbout_Qt"/> + </widget> + <widget class="QMenu" name="menuView"> + <property name="title"> + <string>View</string> + </property> + <addaction name="actionZoom_In"/> + <addaction name="actionZoom_Out"/> + <addaction name="actionPrevious_Page"/> + <addaction name="actionNext_Page"/> + <addaction name="separator"/> + <addaction name="actionContinuous"/> + </widget> + <addaction name="menuFile"/> + <addaction name="menuView"/> + <addaction name="menuHelp"/> + </widget> + <widget class="QToolBar" name="mainToolBar"> + <property name="movable"> + <bool>false</bool> + </property> + <property name="floatable"> + <bool>false</bool> + </property> + <attribute name="toolBarArea"> + <enum>TopToolBarArea</enum> + </attribute> + <attribute name="toolBarBreak"> + <bool>false</bool> + </attribute> + <addaction name="actionOpen"/> + <addaction name="separator"/> + <addaction name="actionZoom_Out"/> + <addaction name="actionZoom_In"/> + <addaction name="separator"/> + </widget> + <widget class="QStatusBar" name="statusBar"/> + <action name="actionOpen"> + <property name="icon"> + <iconset resource="resources.qrc"> + <normaloff>:/icons/images/fileopen.png</normaloff>:/icons/images/fileopen.png</iconset> + </property> + <property name="text"> + <string>Open...</string> + </property> + <property name="shortcut"> + <string>Ctrl+O</string> + </property> + </action> + <action name="actionQuit"> + <property name="text"> + <string>Quit</string> + </property> + <property name="shortcut"> + <string>Ctrl+Q</string> + </property> + </action> + <action name="actionAbout"> + <property name="text"> + <string>About</string> + </property> + </action> + <action name="actionAbout_Qt"> + <property name="text"> + <string>About Qt</string> + </property> + </action> + <action name="actionZoom_In"> + <property name="icon"> + <iconset resource="resources.qrc"> + <normaloff>:/icons/images/zoom-in-24.png</normaloff>:/icons/images/zoom-in-24.png</iconset> + </property> + <property name="text"> + <string>Zoom In</string> + </property> + <property name="shortcut"> + <string>Ctrl+=</string> + </property> + </action> + <action name="actionZoom_Out"> + <property name="icon"> + <iconset resource="resources.qrc"> + <normaloff>:/icons/images/zoom-out-24.png</normaloff>:/icons/images/zoom-out-24.png</iconset> + </property> + <property name="text"> + <string>Zoom Out</string> + </property> + <property name="shortcut"> + <string>Ctrl+-</string> + </property> + </action> + <action name="actionPrevious_Page"> + <property name="text"> + <string>Previous Page</string> + </property> + <property name="shortcut"> + <string>PgUp</string> + </property> + </action> + <action name="actionNext_Page"> + <property name="text"> + <string>Next Page</string> + </property> + <property name="shortcut"> + <string>PgDown</string> + </property> + </action> + <action name="actionContinuous"> + <property name="checkable"> + <bool>true</bool> + </property> + <property name="text"> + <string>Continuous</string> + </property> + </action> + </widget> + <layoutdefault spacing="6" margin="11"/> + <customwidgets> + <customwidget> + <class>QPdfView</class> + <extends>QWidget</extends> + <header location="global">qpdfview.h</header> + <container>1</container> + </customwidget> + </customwidgets> + <resources> + <include location="resources.qrc"/> + </resources> + <connections/> +</ui> diff --git a/examples/pdfwidgets/pdfviewer/pageselector.cpp b/examples/pdfwidgets/pdfviewer/pageselector.cpp new file mode 100644 index 000000000..cd3c4ba0e --- /dev/null +++ b/examples/pdfwidgets/pdfviewer/pageselector.cpp @@ -0,0 +1,111 @@ +/**************************************************************************** +** +** Copyright (C) 2017 Klaralvdalens Datakonsult AB (KDAB). +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtPDF module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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.LGPLv3 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.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 later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "pageselector.h" + +#include <QHBoxLayout> +#include <QLabel> +#include <QLineEdit> +#include <QPdfPageNavigation> +#include <QToolButton> + +PageSelector::PageSelector(QWidget *parent) + : QWidget(parent) + , m_pageNavigation(nullptr) +{ + QHBoxLayout *layout = new QHBoxLayout(this); + + m_previousPageButton = new QToolButton(this); + m_previousPageButton->setText("<"); + m_previousPageButton->setEnabled(false); + + m_pageNumberEdit = new QLineEdit(this); + m_pageNumberEdit->setAlignment(Qt::AlignRight); + + m_pageCountLabel = new QLabel(this); + m_pageCountLabel->setText("0"); + + m_nextPageButton = new QToolButton(this); + m_nextPageButton->setText(">"); + m_nextPageButton->setEnabled(false); + + layout->addWidget(m_previousPageButton); + layout->addWidget(m_pageNumberEdit); + layout->addWidget(m_pageCountLabel); + layout->addWidget(m_nextPageButton); +} + +void PageSelector::setPageNavigation(QPdfPageNavigation *pageNavigation) +{ + m_pageNavigation = pageNavigation; + + connect(m_previousPageButton, &QToolButton::clicked, m_pageNavigation, &QPdfPageNavigation::goToPreviousPage); + connect(m_pageNavigation, &QPdfPageNavigation::canGoToPreviousPageChanged, m_previousPageButton, &QToolButton::setEnabled); + + connect(m_pageNavigation, &QPdfPageNavigation::currentPageChanged, this, &PageSelector::onCurrentPageChanged); + connect(m_pageNavigation, &QPdfPageNavigation::pageCountChanged, this, [this](int pageCount){ m_pageCountLabel->setText(QString::fromLatin1("/ %1").arg(pageCount)); }); + + connect(m_pageNumberEdit, &QLineEdit::editingFinished, this, &PageSelector::pageNumberEdited); + + connect(m_nextPageButton, &QToolButton::clicked, m_pageNavigation, &QPdfPageNavigation::goToNextPage); + connect(m_pageNavigation, &QPdfPageNavigation::canGoToNextPageChanged, m_nextPageButton, &QToolButton::setEnabled); + + onCurrentPageChanged(m_pageNavigation->currentPage()); +} + +void PageSelector::onCurrentPageChanged(int page) +{ + if (m_pageNavigation->pageCount() == 0) + m_pageNumberEdit->setText(QString::number(0)); + else + m_pageNumberEdit->setText(QString::number(page + 1)); +} + +void PageSelector::pageNumberEdited() +{ + if (!m_pageNavigation) + return; + + const QString text = m_pageNumberEdit->text(); + + bool ok = false; + const int pageNumber = text.toInt(&ok); + + if (!ok) + onCurrentPageChanged(m_pageNavigation->currentPage()); + else + m_pageNavigation->setCurrentPage(qBound(0, pageNumber - 1, m_pageNavigation->pageCount() - 1)); +} diff --git a/examples/pdfwidgets/pdfviewer/pageselector.h b/examples/pdfwidgets/pdfviewer/pageselector.h new file mode 100644 index 000000000..7a7283f99 --- /dev/null +++ b/examples/pdfwidgets/pdfviewer/pageselector.h @@ -0,0 +1,72 @@ +/**************************************************************************** +** +** Copyright (C) 2017 Klaralvdalens Datakonsult AB (KDAB). +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtPDF module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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.LGPLv3 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.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 later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef PAGESELECTOR_H +#define PAGESELECTOR_H + +#include <QWidget> + +QT_BEGIN_NAMESPACE +class QLabel; +class QLineEdit; +class QPdfDocument; +class QPdfPageNavigation; +class QToolButton; +QT_END_NAMESPACE + +class PageSelector : public QWidget +{ + Q_OBJECT + +public: + explicit PageSelector(QWidget *parent = nullptr); + + void setPageNavigation(QPdfPageNavigation *pageNavigation); + +private slots: + void onCurrentPageChanged(int page); + void pageNumberEdited(); + +private: + QPdfPageNavigation *m_pageNavigation; + + QLineEdit *m_pageNumberEdit; + QLabel *m_pageCountLabel; + QToolButton *m_previousPageButton; + QToolButton *m_nextPageButton; +}; + +#endif // PAGESELECTOR_H diff --git a/examples/pdfwidgets/pdfviewer/pdfviewer.pro b/examples/pdfwidgets/pdfviewer/pdfviewer.pro new file mode 100644 index 000000000..ad0607ea5 --- /dev/null +++ b/examples/pdfwidgets/pdfviewer/pdfviewer.pro @@ -0,0 +1,23 @@ +TEMPLATE = app +TARGET = pdfviewer +QT += core gui widgets pdfwidgets + +SOURCES += \ + main.cpp \ + mainwindow.cpp \ + pageselector.cpp \ + zoomselector.cpp + +HEADERS += \ + mainwindow.h \ + pageselector.h \ + zoomselector.h + +FORMS += \ + mainwindow.ui + +RESOURCES += \ + resources.qrc + +target.path = $$[QT_INSTALL_EXAMPLES]/pdfwidgets/pdfviewer +INSTALLS += target diff --git a/examples/pdfwidgets/pdfviewer/resources.qrc b/examples/pdfwidgets/pdfviewer/resources.qrc new file mode 100644 index 000000000..02d9655b4 --- /dev/null +++ b/examples/pdfwidgets/pdfviewer/resources.qrc @@ -0,0 +1,12 @@ +<RCC> + <qresource prefix="/icons"> + <file>images/fileopen.png</file> + <file>images/go-next-24.png</file> + <file>images/go-previous-24.png</file> + <file>images/zoom-in-24.png</file> + <file>images/zoom-in-32.png</file> + <file>images/zoom-out-24.png</file> + <file>images/zoom-out-32.png</file> + <file>images/busy.png</file> + </qresource> +</RCC> diff --git a/examples/pdfwidgets/pdfviewer/zoomselector.cpp b/examples/pdfwidgets/pdfviewer/zoomselector.cpp new file mode 100644 index 000000000..0205489aa --- /dev/null +++ b/examples/pdfwidgets/pdfviewer/zoomselector.cpp @@ -0,0 +1,97 @@ +/**************************************************************************** +** +** Copyright (C) 2017 Klaralvdalens Datakonsult AB (KDAB). +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtPDF module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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.LGPLv3 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.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 later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "zoomselector.h" + +#include <QLineEdit> + +ZoomSelector::ZoomSelector(QWidget *parent) + : QComboBox(parent) +{ + setEditable(true); + + addItem(QLatin1String("Fit Width")); + addItem(QLatin1String("Fit Page")); + addItem(QLatin1String("12%")); + addItem(QLatin1String("25%")); + addItem(QLatin1String("33%")); + addItem(QLatin1String("50%")); + addItem(QLatin1String("66%")); + addItem(QLatin1String("75%")); + addItem(QLatin1String("100%")); + addItem(QLatin1String("125%")); + addItem(QLatin1String("150%")); + addItem(QLatin1String("200%")); + addItem(QLatin1String("400%")); + + connect(this, static_cast<void(QComboBox::*)(const QString &)>(&QComboBox::currentIndexChanged), + this, &ZoomSelector::onCurrentTextChanged); + + connect(lineEdit(), &QLineEdit::editingFinished, + this, [this](){onCurrentTextChanged(lineEdit()->text()); }); +} + +void ZoomSelector::setZoomFactor(qreal zoomFactor) +{ + setCurrentText(QString::number(qRound(zoomFactor * 100)) + QLatin1String("%")); +} + +void ZoomSelector::reset() +{ + setCurrentIndex(8); // 100% +} + +void ZoomSelector::onCurrentTextChanged(const QString &text) +{ + if (text == QLatin1String("Fit Width")) { + emit zoomModeChanged(QPdfView::FitToWidth); + } else if (text == QLatin1String("Fit Page")) { + emit zoomModeChanged(QPdfView::FitInView); + } else { + qreal factor = 1.0; + + QString withoutPercent(text); + withoutPercent.remove(QLatin1Char('%')); + + bool ok = false; + const int zoomLevel = withoutPercent.toInt(&ok); + if (ok) + factor = zoomLevel / 100.0; + + emit zoomModeChanged(QPdfView::CustomZoom); + emit zoomFactorChanged(factor); + } +} diff --git a/examples/pdfwidgets/pdfviewer/zoomselector.h b/examples/pdfwidgets/pdfviewer/zoomselector.h new file mode 100644 index 000000000..c58d09970 --- /dev/null +++ b/examples/pdfwidgets/pdfviewer/zoomselector.h @@ -0,0 +1,63 @@ +/**************************************************************************** +** +** Copyright (C) 2017 Klaralvdalens Datakonsult AB (KDAB). +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtPDF module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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.LGPLv3 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.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 later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef ZOOMSELECTOR_H +#define ZOOMSELECTOR_H + +#include <QComboBox> +#include <QPdfView> + +class ZoomSelector : public QComboBox +{ + Q_OBJECT + +public: + explicit ZoomSelector(QWidget *parent = nullptr); + +public slots: + void setZoomFactor(qreal zoomFactor); + + void reset(); + +signals: + void zoomModeChanged(QPdfView::ZoomMode zoomMode); + void zoomFactorChanged(qreal zoomFactor); + +private slots: + void onCurrentTextChanged(const QString &text); +}; + +#endif // ZOOMSELECTOR_H diff --git a/examples/pdfwidgets/pdfwidgets.pro b/examples/pdfwidgets/pdfwidgets.pro new file mode 100644 index 000000000..a12dc5b86 --- /dev/null +++ b/examples/pdfwidgets/pdfwidgets.pro @@ -0,0 +1,2 @@ +TEMPLATE = subdirs +SUBDIRS += pdfviewer diff --git a/mkspecs/features/functions.prf b/mkspecs/features/functions.prf index 512e2523b..db0b072a7 100644 --- a/mkspecs/features/functions.prf +++ b/mkspecs/features/functions.prf @@ -74,6 +74,7 @@ defineReplace(gnPath) { defineReplace(gnArgs) { linux: include($$QTWEBENGINE_ROOT/src/buildtools/config/linux.pri) macos: include($$QTWEBENGINE_ROOT/src/buildtools/config/mac_osx.pri) + ios: include($$QTWEBENGINE_ROOT/src/buildtools/config/ios.pri) win32: include($$QTWEBENGINE_ROOT/src/buildtools/config/windows.pri) isEmpty(gn_args): error(No gn_args found please make sure you have valid configuration.) return($$gn_args) diff --git a/mkspecs/features/gn_generator.prf b/mkspecs/features/gn_generator.prf index 863169c4d..6ff09d851 100644 --- a/mkspecs/features/gn_generator.prf +++ b/mkspecs/features/gn_generator.prf @@ -12,13 +12,18 @@ defineReplace(getTargetType) { defineReplace(filter_flag_values) { value_to_check = $$1 - macos:equals(value_to_check, "$(EXPORT_ARCH_ARGS)") { + if (macos|ios):equals(value_to_check, "$(EXPORT_ARCH_ARGS)") { # EXPORT_ARCH_ARGS comes from qtbase/mkspecs/features/mac/default_post.prf which is a way # to figure out the architectures to pass to the compiler at Makefile time. Because this # variable expansion is not supported by GN, we filter it out. GN takes care of assigning # the architecture itself. return("") } + if (ios) { + equals(value_to_check, "$(EXPORT_QMAKE_XARCH_LFLAGS)"): return("") + equals(value_to_check, "$(EXPORT_QMAKE_XARCH_CFLAGS)"): return("") + } + return($$value_to_check) } @@ -183,7 +188,7 @@ GN_CONTENTS += "$${TARGET_TYPE}(\"$$TARGET\") {" } GN_CONTENTS += " configs += [ \":$${TARGET}_config\" ]" -GN_CONTENTS += " configs += [ \"//build/config:precompiled_headers\" ]" +!isEmpty(GN_PRECOMPILED_HEADERS): GN_CONTENTS += " configs += [ \"//build/config:precompiled_headers\" ]" # Source files to compile GN_CONTENTS += " sources = [" @@ -239,11 +244,10 @@ GN_CONTENTS += " \":generate_cpp_mocs\"," GN_CONTENTS += " ]" GN_CONTENTS += " }" GN_CONTENTS += "}" -GN_CONTENTS += "" -GN_CONTENTS += "if (!defined(core_include_dirs)) {"\ - " core_include_dirs = []"\ - "}" !isEmpty(GN_CORE_INCLUDE_DIRS) { + GN_CONTENTS += " if (!defined(core_include_dirs)) {"\ + " core_include_dirs = []"\ + " }" GN_CONTENTS += "core_include_dirs += [" for (inc, GN_CORE_INCLUDE_DIRS): GN_CONTENTS += " \"$$inc\"," GN_CONTENTS += "]" diff --git a/src/3rdparty b/src/3rdparty -Subproject a9a20127e8adeb3f3cd7921b0bec32083103cc5 +Subproject 5c2d377121ce774e3ee2e7a29b78d697733e75e diff --git a/src/buildtools/config/common.pri b/src/buildtools/config/common.pri index 97d39535c..9cd8cb089 100644 --- a/src/buildtools/config/common.pri +++ b/src/buildtools/config/common.pri @@ -39,7 +39,7 @@ greaterThan(QMAKE_JUMBO_MERGE_LIMIT,0) { gn_args += jumbo_build_excluded="[\"browser\"]" } -qtConfig(webengine-printing-and-pdf) { +qtConfig(build-qtwebengine-core):qtConfig(webengine-printing-and-pdf) { gn_args += enable_basic_printing=true enable_print_preview=true gn_args += enable_pdf=true } else { @@ -47,27 +47,31 @@ qtConfig(webengine-printing-and-pdf) { gn_args += enable_pdf=false } -qtConfig(webengine-pepper-plugins) { +qtConfig(build-qtwebengine-core):qtConfig(webengine-pepper-plugins) { gn_args += enable_plugins=true } else { gn_args += enable_plugins=false } -qtConfig(webengine-spellchecker) { +qtConfig(build-qtwebengine-core):qtConfig(webengine-spellchecker) { gn_args += enable_spellcheck=true } else { gn_args += enable_spellcheck=false } -qtConfig(webengine-webrtc) { +qtConfig(build-qtwebengine-core):qtConfig(webengine-webrtc) { gn_args += enable_webrtc=true } else { gn_args += enable_webrtc=false audio_processing_in_audio_service_supported=false } -qtConfig(webengine-proprietary-codecs): gn_args += proprietary_codecs=true ffmpeg_branding=\"Chrome\" +qtConfig(build-qtwebengine-core):qtConfig(webengine-proprietary-codecs) { + gn_args += proprietary_codecs=true ffmpeg_branding=\"Chrome\" +} else { + gn_args += proprietary_codecs=false +} -qtConfig(webengine-extensions) { +qtConfig(build-qtwebengine-core):qtConfig(webengine-extensions) { gn_args += enable_extensions=true } else { gn_args += enable_extensions=false @@ -122,13 +126,13 @@ optimize_size: gn_args += optimize_for_size=true sanitize_undefined: gn_args += is_ubsan=true is_ubsan_vptr=true } -qtConfig(webengine-v8-snapshot):qtConfig(webengine-v8-snapshot-support) { +qtConfig(build-qtwebengine-core):qtConfig(webengine-v8-snapshot):qtConfig(webengine-v8-snapshot-support) { gn_args += v8_use_snapshot=true } else { gn_args += v8_use_snapshot=false } -qtConfig(webengine-kerberos) { +qtConfig(build-qtwebengine-core):qtConfig(webengine-kerberos) { gn_args += use_kerberos=true } else { gn_args += use_kerberos=false @@ -139,3 +143,4 @@ ccache { } qtConfig(force_asserts): gn_args += dcheck_always_on=true + diff --git a/src/buildtools/config/ios.pri b/src/buildtools/config/ios.pri new file mode 100644 index 000000000..5dc7faf9d --- /dev/null +++ b/src/buildtools/config/ios.pri @@ -0,0 +1,66 @@ +load(functions) + +include($$QTWEBENGINE_OUT_ROOT/src/buildtools/qtbuildtools-config.pri) +include($$QTWEBENGINE_OUT_ROOT/src/pdf/qtpdf-config.pri) +QT_FOR_CONFIG += buildtools-private pdf-private + +clang_dir = $$which($${QMAKE_CXX}) +clang_dir = $$clean_path("$$dirname(clang_dir)/../") + +gn_args += \ +use_qt=true \ +closure_compile=false \ +is_component_build=false \ +is_shared=true \ +is_debug=true \ +enable_message_center=false \ +enable_nacl=false \ +enable_remoting=false \ +enable_reporting=false \ +enable_resource_whitelist_generation=false \ +enable_swiftshader=false \ +enable_web_speech=false \ +has_native_accessibility=false \ +enable_debugallocation=false \ +use_allocator_shim=false \ +use_allocator=\"none\" \ +use_custom_libcxx=false \ +v8_use_external_startup_data=false \ +v8_use_snapshot=false \ +toolkit_views=false \ +treat_warnings_as_errors=false \ +safe_browsing_mode=0 \ +optimize_webui=false \ +forbid_non_component_debug_builds=false \ +clang_use_chrome_plugins=false \ +use_xcode_clang=true \ +clang_base_path=\"$${clang_dir}\" \ +ios_enable_code_signing=false \ +target_os=\"ios\" \ +ios_deployment_target=\"$${QMAKE_IOS_DEPLOYMENT_TARGET}\" \ +enable_ios_bitcode=true \ +use_jumbo_build=false + +device:simulator { + # we do fat libray + gn_args+= \ + target_cpu=\"$${QMAKE_APPLE_DEVICE_ARCHS}\" \ + use_qt_fat_lib=true \ + arm_use_neon=false\ + # note this adds one arch of simulator at the moment, see also additional_target_cpus + target_sysroot=\"$$xcodeSDKInfo(Path, $$device.sdk)\" \ + additional_target_sysroot=[\"$$xcodeSDKInfo(Path, $$simulator.sdk)\"] +} else { + simulator { + equals(QMAKE_APPLE_SIMULATOR_ARCHS,"x86_64") { + gn_args+=target_cpu=\"x64\" + } else { + gn_args+=target_cpu=\"$${QMAKE_APPLE_SIMULATOR_ARCHS}\" + } + gn_args+=target_sysroot=\"$$xcodeSDKInfo(Path, $$simulator.sdk)\" + } + device { + gn_args+=target_cpu=\"$${QMAKE_APPLE_DEVICE_ARCHS}\" + gn_args+=target_sysroot=\"$$xcodeSDKInfo(Path, $$device.sdk)\" + } +} diff --git a/src/buildtools/config/linux.pri b/src/buildtools/config/linux.pri index ee08f81bc..add563f28 100644 --- a/src/buildtools/config/linux.pri +++ b/src/buildtools/config/linux.pri @@ -5,7 +5,7 @@ defineReplace(extractCFlag) { return($$qtwebengine_extractCFlag($$1)) } -QT_FOR_CONFIG += gui-private webenginecore-private +QT_FOR_CONFIG += gui-private webenginecore-private pdf-private gn_args += \ use_cups=false \ @@ -26,7 +26,7 @@ gn_args += \ ozone_platform=\"qt\" \ ozone_extra_path=\"$$QTWEBENGINE_ROOT/src/core/ozone/ozone_extra.gni\" -qtConfig(webengine-embedded-build) { +qtConfig(build-qtwebengine-core):qtConfig(webengine-embedded-build) { gn_args += is_desktop_linux=false } @@ -92,7 +92,7 @@ contains(QT_ARCH, "arm") { } } - qtConfig(webengine-arm-thumb) { + qtConfig(build-qtwebengine-core):qtConfig(webengine-arm-thumb) { gn_args += arm_use_thumb=true # this adds -mthumb } else { gn_args += arm_use_thumb=false @@ -158,11 +158,15 @@ host_build { qtConfig(webengine-system-zlib) { qtConfig(webengine-system-minizip): gn_args += use_system_zlib=true use_system_minizip=true - qtConfig(webengine-printing-and-pdf): gn_args += pdfium_use_system_zlib=true + qtConfig(build-qtpdf) || qtConfig(webengine-printing-and-pdf) { + gn_args += pdfium_use_system_zlib=true + } } qtConfig(webengine-system-png) { gn_args += use_system_libpng=true - qtConfig(webengine-printing-and-pdf): gn_args += pdfium_use_system_libpng=true + qtConfig(build-qtpdf) || qtConfig(webengine-printing-and-pdf) { + gn_args += pdfium_use_system_libpng=true + } } qtConfig(webengine-system-jpeg) { gn_args += use_system_libjpeg=true @@ -180,19 +184,19 @@ host_build { gn_args += use_system_harfbuzz=false } gn_args += use_glib=false - qtConfig(webengine-pulseaudio) { + qtConfig(build-qtwebengine-core):qtConfig(webengine-pulseaudio) { gn_args += use_pulseaudio=true } else { gn_args += use_pulseaudio=false } - qtConfig(webengine-alsa) { + qtConfig(build-qtwebengine-core):qtConfig(webengine-alsa) { gn_args += use_alsa=true } else { gn_args += use_alsa=false } !packagesExist(libpci): gn_args += use_libpci=false - qtConfig(webengine-ozone-x11) { + qtConfig(build-qtwebengine-core):qtConfig(webengine-ozone-x11) { gn_args += ozone_platform_x11=true packagesExist(xscrnsaver): gn_args += use_xscrnsaver=true qtConfig(webengine-webrtc): gn_args += rtc_use_x11=true diff --git a/src/buildtools/config/mac_osx.pri b/src/buildtools/config/mac_osx.pri index 3f2fe9c0a..a7ed61214 100644 --- a/src/buildtools/config/mac_osx.pri +++ b/src/buildtools/config/mac_osx.pri @@ -31,7 +31,7 @@ gn_args += \ mac_sdk_min=\"$${QMAKE_MAC_SDK_VERSION}\" \ use_external_popup_menu=false -qtConfig(webengine-spellchecker) { +qtConfig(build-qtwebengine-core):qtConfig(webengine-spellchecker) { qtConfig(webengine-native-spellchecker): gn_args += use_browser_spellchecker=true else: gn_args += use_browser_spellchecker=false } else { diff --git a/src/buildtools/config/pdf.pri b/src/buildtools/config/pdf.pri new file mode 100644 index 000000000..4a1cf08e0 --- /dev/null +++ b/src/buildtools/config/pdf.pri @@ -0,0 +1,36 @@ +include($$QTWEBENGINE_OUT_ROOT/src/pdf/qtpdf-config.pri) +QT_FOR_CONFIG += pdf-private + +qtConfig(build-qtpdf) { + qtConfig(pdf-v8) { + gn_args += pdf_enable_v8=true + } else { + gn_args += pdf_enable_v8=false + } + qtConfig(pdf-xfa) { + gn_args += pdf_enable_xfa=true + } else { + gn_args += pdf_enable_xfa=false + } + qtConfig(pdf-xfa-bmp) { + gn_args += pdf_enable_xfa_bmp=true + } else { + gn_args += pdf_enable_xfa_bmp=false + } + qtConfig(pdf-xfa-gif) { + gn_args += pdf_enable_xfa_gif=true + } else { + gn_args += pdf_enable_xfa_gif=false + } + qtConfig(pdf-xfa-png) { + gn_args += pdf_enable_xfa_png=true + } else { + gn_args += pdf_enable_xfa_png=false + } + qtConfig(pdf-xfa-tiff) { + gn_args += pdf_enable_xfa_tiff=true + } else { + gn_args += pdf_enable_xfa_tiff=false + } +} + diff --git a/src/buildtools/config/support.pri b/src/buildtools/config/support.pri index 5bdd808d4..86ca2f80e 100644 --- a/src/buildtools/config/support.pri +++ b/src/buildtools/config/support.pri @@ -6,11 +6,6 @@ defineTest(qtwebengine_skipBuild) { defineReplace(qtwebengine_checkError) { - static { - qtwebengine_skipBuild("Static builds of QtWebEngine are not supported.") - return(false) - } - !qtHaveModule(gui) { qtwebengine_skipBuild("QtWebEngine requires QtGui.") return(false) @@ -68,6 +63,7 @@ defineReplace(qtwebengine_checkError) { linux:!qtwebengine_checkErrorForLinux():return(false) win:!qtwebengine_checkErrorForWindows():return(false) + macos:!qtwebengine_checkErrorForMacOS():return(false) sanitizer: !qtConfig(webengine-sanitizer) { qtwebengine_skipBuild("Chosen sanitizer configuration is not supported for QtWebEngine. Check config.log for details or use -feature-webengine-sanitizer to force build with the chosen sanitizer configuration.") @@ -77,8 +73,20 @@ defineReplace(qtwebengine_checkError) { return(true) } +defineTest(qtwebengine_checkErrorFoMacOS) { + static { + qtwebengine_skipBuild("Static builds of QtWebEngine are not supported.") + return(false) + } +} + defineTest(qtwebengine_checkErrorForLinux) { + static { + qtwebengine_skipBuild("Static builds of QtWebEngine are not supported.") + return(false) + } + !qtConfig(pkg-config) { qtwebengine_skipBuild("A pkg-config support is required to build QtWebEngine.") return(false) @@ -114,6 +122,12 @@ defineTest(qtwebengine_checkErrorForLinux) { } defineTest(qtwebengine_checkErrorForWindows) { + + static { + qtwebengine_skipBuild("Static builds of QtWebEngine are not supported.") + return(false) + } + !qtConfig(webengine-win-compiler64) { qtwebengine_skipBuild("64-bit cross-building or native toolchain required to build QtWebEngine could not be found.") return(false) diff --git a/src/buildtools/config/windows.pri b/src/buildtools/config/windows.pri index 5d7b7e1f2..2e5ebb876 100644 --- a/src/buildtools/config/windows.pri +++ b/src/buildtools/config/windows.pri @@ -89,7 +89,7 @@ msvc { error("Qt WebEngine for Windows can only be built with a Microsoft Visual Studio C++ compatible compiler") } -qtConfig(webengine-spellchecker) { +qtConfig(build-qtwebengine-core):qtConfig(webengine-spellchecker) { qtConfig(webengine-native-spellchecker): gn_args += use_browser_spellchecker=true else: gn_args += use_browser_spellchecker=false } else { diff --git a/src/buildtools/configure.json b/src/buildtools/configure.json index f04fdae81..939d689fd 100644 --- a/src/buildtools/configure.json +++ b/src/buildtools/configure.json @@ -9,6 +9,7 @@ "commandline": { "options": { "build-qtwebengine-core": "boolean", + "build-qtpdf": "boolean", "webengine-jumbo-build": { "type": "optionalString", "name": "merge_limit"} } }, @@ -389,6 +390,22 @@ && (!config.win32 || features.webengine-winversion)", "output": [ "privateFeature" ] }, + "webengine-qtpdf-support": { + "label": "Support Qt Pdf", + "condition": "module.gui + && features.webengine-python2 + && features.webengine-gperf + && features.webengine-bison + && features.webengine-flex + && features.webengine-submodule + && features.webengine-nowhitespace + && features.webengine-arch-support + && !features.webengine-no-platform-support + && (!config.static || config.ios) + && (!config.linux || features.pkg-config) + && (!config.win32 || features.webengine-winversion)", + "output": [ "privateFeature" ] + }, "build-qtwebengine-core": { "label": "Build Qt WebEngine Core", "purpose": "Provides WebEngine Core support.", @@ -681,7 +698,8 @@ "report": [ { "type": "skipBuildWarning", - "condition": "!features.webengine-core-support && (features.build-qtwebengine-core || features.build-qtpdf)", + "condition": "(!features.webengine-core-support && features.build-qtwebengine-core) || + (!features.webengine-qtpdf-support && features.build-qtpdf)", "message": "qtwebengine_confCheckError" }, { @@ -691,7 +709,7 @@ }, { "type": "note", - "condition": "features.webengine-core-support && !features.build-qtpdf", + "condition": "features.webengine-qtpdf-support && !features.build-qtpdf", "message": "QtPdf build is disabled by user." }, { @@ -701,8 +719,13 @@ }, { "type": "warning", - "condition": "!features.webengine-core-support && features.build-qtpdf", + "condition": "!features.webengine-qtpdf-support && features.build-qtpdf", "message": "QtPdf will not be built." + }, + { + "type": "warning", + "condition": "config.ios && config.simulator && config.device && features.build-qtpdf", + "message": "Building fat libray with device and simulator architectures will disable NEON." } ], "summary": [ @@ -711,14 +734,14 @@ "entries": [ "webengine-system-ninja", "webengine-system-gn", - { + { "message": "Jumbo Build Merge Limit", "type": "jumboBuild" }, "webengine-developer-build", { "section": "QtWebEngine required system libraries", - "condition": "config.unix && !config.macos", + "condition": "config.unix && !config.macos && !config.ios", "entries": [ "webengine-system-fontconfig", "webengine-system-dbus", diff --git a/src/core/api/qwebengineurlrequestinfo.h b/src/core/api/qwebengineurlrequestinfo.h index 75b6e3a81..69033cdfd 100644 --- a/src/core/api/qwebengineurlrequestinfo.h +++ b/src/core/api/qwebengineurlrequestinfo.h @@ -46,6 +46,7 @@ #include <QtCore/qurl.h> namespace QtWebEngineCore { +class InterceptedRequest; class NetworkDelegateQt; class URLRequestNotification; } // namespace QtWebEngineCore @@ -110,6 +111,7 @@ public: private: friend class QtWebEngineCore::NetworkDelegateQt; friend class QtWebEngineCore::URLRequestNotification; + friend class QtWebEngineCore::InterceptedRequest; Q_DISABLE_COPY(QWebEngineUrlRequestInfo) Q_DECLARE_PRIVATE(QWebEngineUrlRequestInfo) diff --git a/src/core/configure.json b/src/core/configure.json index 9059575b9..9f05409ba 100644 --- a/src/core/configure.json +++ b/src/core/configure.json @@ -104,7 +104,6 @@ "webengine-embedded-build": { "label": "Embedded build", "purpose": "Enables the embedded build configuration.", - "section": "WebEngine", "condition": "config.unix", "autoDetect": "tests.webengine-embedded-build", "output": [ "privateFeature" ] @@ -139,14 +138,12 @@ "webengine-pepper-plugins": { "label": "Pepper Plugins", "purpose": "Enables use of Pepper Flash plugins.", - "section": "WebEngine", "autoDetect": "!features.webengine-embedded-build", "output": [ "privateFeature" ] }, "webengine-printing-and-pdf": { "label": "Printing and PDF", "purpose": "Provides printing and output to PDF.", - "section": "WebEngine", "condition": "module.printsupport && features.printer", "autoDetect": "!features.webengine-embedded-build", "output": [ "privateFeature" ] @@ -161,7 +158,6 @@ "webengine-proprietary-codecs": { "label": "Proprietary Codecs", "purpose": "Enables the use of proprietary codecs such as h.264/h.265 and MP3.", - "section": "WebEngine", "autoDetect": false, "output": [ "privateFeature" ] }, @@ -175,13 +171,11 @@ "webengine-spellchecker": { "label": "Spellchecker", "purpose": "Provides a spellchecker.", - "section": "WebEngine", "output": [ "publicFeature" ] }, "webengine-native-spellchecker": { "label": "Native Spellchecker", "purpose": "Use the system's native spellchecking engine.", - "section": "WebEngine", "autoDetect": false, "condition": "config.macos && features.webengine-spellchecker", "output": [ "publicFeature" ] @@ -197,7 +191,6 @@ "webengine-webrtc": { "label": "WebRTC", "purpose": "Provides WebRTC support.", - "section": "WebEngine", "autoDetect": "!features.webengine-embedded-build", "output": [ "privateFeature" ] }, diff --git a/src/core/content_browser_client_qt.cpp b/src/core/content_browser_client_qt.cpp index 29b6e09ed..987934555 100644 --- a/src/core/content_browser_client_qt.cpp +++ b/src/core/content_browser_client_qt.cpp @@ -41,6 +41,7 @@ #include "base/memory/ptr_util.h" #include "base/optional.h" +#include "base/path_service.h" #include "base/strings/utf_string_conversions.h" #include "base/message_loop/message_loop.h" #include "base/task/post_task.h" @@ -49,6 +50,8 @@ #if QT_CONFIG(webengine_spellchecker) #include "chrome/browser/spellchecker/spell_check_host_chrome_impl.h" #endif +#include "components/navigation_interception/intercept_navigation_throttle.h" +#include "components/navigation_interception/navigation_params.h" #include "components/guest_view/browser/guest_view_base.h" #include "components/network_hints/browser/network_hints_message_filter.h" #include "content/browser/renderer_host/render_view_host_delegate.h" @@ -58,6 +61,7 @@ #include "content/public/browser/child_process_security_policy.h" #include "content/public/browser/client_certificate_delegate.h" #include "content/public/browser/media_observer.h" +#include "content/public/browser/network_service_instance.h" #include "content/public/browser/render_frame_host.h" #include "content/public/browser/render_process_host.h" #include "content/public/browser/render_view_host.h" @@ -66,6 +70,7 @@ #include "content/public/browser/storage_partition.h" #include "content/public/browser/web_contents.h" #include "content/public/browser/web_contents_user_data.h" +#include "content/public/browser/web_ui_url_loader_factory.h" #include "content/public/common/content_switches.h" #include "content/public/common/main_function_params.h" #include "content/public/common/service_manager_connection.h" @@ -74,6 +79,8 @@ #include "content/public/common/user_agent.h" #include "media/media_buildflags.h" #include "extensions/buildflags/buildflags.h" +#include "extensions/browser/extension_protocols.h" +#include "extensions/browser/guest_view/web_view/web_view_guest.h" #include "mojo/public/cpp/bindings/binding.h" #include "mojo/public/cpp/bindings/binding_set.h" #include "mojo/public/cpp/bindings/remote.h" @@ -84,6 +91,8 @@ #include "qtwebengine/browser/qtwebengine_renderer_manifest.h" #include "net/ssl/client_cert_identity.h" #include "net/ssl/client_cert_store.h" +#include "services/network/network_service.h" +#include "services/network/public/cpp/features.h" #include "services/service_manager/public/cpp/connector.h" #include "services/service_manager/public/cpp/service.h" #include "services/service_manager/sandbox/switches.h" @@ -109,8 +118,14 @@ #include "devtools_manager_delegate_qt.h" #include "login_delegate_qt.h" #include "media_capture_devices_dispatcher.h" +#include "net/cookie_monster_delegate_qt.h" +#include "net/custom_url_loader_factory.h" #include "net/network_delegate_qt.h" +#include "net/proxying_restricted_cookie_manager_qt.h" +#include "net/proxying_url_loader_factory_qt.h" +#include "net/qrc_url_scheme_handler.h" #include "net/url_request_context_getter_qt.h" +#include "net/system_network_context_manager.h" #include "platform_notification_service_qt.h" #if QT_CONFIG(webengine_printing_and_pdf) #include "printing/printing_message_filter_qt.h" @@ -121,6 +136,7 @@ #include "renderer_host/user_resource_controller_host.h" #include "service/service_qt.h" #include "type_conversion.h" +#include "web_contents_adapter_client.h" #include "web_contents_delegate_qt.h" #include "web_engine_context.h" #include "web_engine_library_info.h" @@ -143,13 +159,16 @@ #endif #if BUILDFLAG(ENABLE_EXTENSIONS) -#include "extensions/extensions_browser_client_qt.h" +#include "content/public/browser/file_url_loader.h" #include "extensions/browser/extension_message_filter.h" #include "extensions/browser/guest_view/extensions_guest_view_message_filter.h" #include "extensions/browser/io_thread_extension_message_filter.h" #include "extensions/common/constants.h" + #include "common/extensions/extensions_client_qt.h" -#include "renderer_host/resource_dispatcher_host_delegate_qt.h" +#include "extensions/extension_web_contents_observer_qt.h" +#include "extensions/extensions_browser_client_qt.h" +#include "net/plugin_response_interceptor_url_loader_throttle.h" #endif #if BUILDFLAG(ENABLE_MOJO_MEDIA_IN_BROWSER_PROCESS) @@ -159,6 +178,7 @@ #include <QGuiApplication> #include <QLocale> +#include <QStandardPaths> #if QT_CONFIG(opengl) # include <QOpenGLContext> # include <QOpenGLExtraFunctions> @@ -276,14 +296,23 @@ void ContentBrowserClientQt::RenderProcessWillLaunch(content::RenderProcessHost* { const int id = host->GetID(); Profile *profile = Profile::FromBrowserContext(host->GetBrowserContext()); - base::PostTaskWithTraitsAndReplyWithResult( - FROM_HERE, {content::BrowserThread::IO}, - base::BindOnce(&net::URLRequestContextGetter::GetURLRequestContext, base::Unretained(profile->GetRequestContext())), - base::BindOnce(&ContentBrowserClientQt::AddNetworkHintsMessageFilter, base::Unretained(this), id)); + if (profile->GetRequestContext()) { + base::PostTaskWithTraitsAndReplyWithResult( + FROM_HERE, {content::BrowserThread::IO}, + base::BindOnce(&net::URLRequestContextGetter::GetURLRequestContext, base::Unretained(profile->GetRequestContext())), + base::BindOnce(&ContentBrowserClientQt::AddNetworkHintsMessageFilter, base::Unretained(this), id)); + } + + // Allow requesting custom schemes. + const auto policy = content::ChildProcessSecurityPolicy::GetInstance(); + const auto profileQt = static_cast<ProfileQt *>(host->GetBrowserContext()); + const auto profileAdapter = profileQt->profileAdapter(); + for (const QByteArray &scheme : profileAdapter->customUrlSchemes()) + policy->GrantRequestScheme(id, scheme.toStdString()); // FIXME: Add a settings variable to enable/disable the file scheme. - content::ChildProcessSecurityPolicy::GetInstance()->GrantRequestScheme(id, url::kFileScheme); - static_cast<ProfileQt*>(host->GetBrowserContext())->m_profileAdapter->userResourceController()->renderProcessStartedWithHost(host); + policy->GrantRequestScheme(id, url::kFileScheme); + profileAdapter->userResourceController()->renderProcessStartedWithHost(host); host->AddFilter(new BrowserMessageFilterQt(id, profile)); #if QT_CONFIG(webengine_printing_and_pdf) host->AddFilter(new PrintingMessageFilterQt(host->GetID())); @@ -313,11 +342,7 @@ void ContentBrowserClientQt::RenderProcessWillLaunch(content::RenderProcessHost* void ContentBrowserClientQt::ResourceDispatcherHostCreated() { -#if BUILDFLAG(ENABLE_EXTENSIONS) - m_resourceDispatcherHostDelegate.reset(new ResourceDispatcherHostDelegateQt); -#else m_resourceDispatcherHostDelegate.reset(new content::ResourceDispatcherHostDelegate); -#endif content::ResourceDispatcherHost::Get()->SetDelegate(m_resourceDispatcherHostDelegate.get()); } @@ -459,12 +484,20 @@ void ContentBrowserClientQt::AppendExtraCommandLineSwitches(base::CommandLine* c void ContentBrowserClientQt::GetAdditionalWebUISchemes(std::vector<std::string>* additional_schemes) { + ContentBrowserClient::GetAdditionalWebUISchemes(additional_schemes); additional_schemes->push_back(content::kChromeDevToolsScheme); } void ContentBrowserClientQt::GetAdditionalViewSourceSchemes(std::vector<std::string>* additional_schemes) { - additional_schemes->push_back(content::kChromeDevToolsScheme); + ContentBrowserClient::GetAdditionalViewSourceSchemes(additional_schemes); +} + +void ContentBrowserClientQt::GetAdditionalAllowedSchemesForFileSystem(std::vector<std::string>* additional_schemes) +{ + ContentBrowserClient::GetAdditionalAllowedSchemesForFileSystem(additional_schemes); + additional_schemes->push_back(content::kChromeDevToolsScheme); + additional_schemes->push_back(content::kChromeUIScheme); } #if defined(Q_OS_LINUX) @@ -687,13 +720,27 @@ bool ContentBrowserClientQt::WillCreateRestrictedCookieManager(network::mojom::R int routing_id, network::mojom::RestrictedCookieManagerRequest *request) { - base::PostTaskWithTraits( - FROM_HERE, {content::BrowserThread::IO}, - base::BindOnce(&ProfileIODataQt::CreateRestrictedCookieManager, - ProfileIODataQt::FromBrowserContext(browser_context)->getWeakPtrOnUIThread(), - std::move(*request), - role, origin, is_service_worker, process_id, routing_id)); - return true; + if (Profile::FromBrowserContext(browser_context)->GetRequestContext()) { + base::PostTaskWithTraits( + FROM_HERE, {content::BrowserThread::IO}, + base::BindOnce(&ProfileIODataQt::CreateRestrictedCookieManager, + ProfileIODataQt::FromBrowserContext(browser_context)->getWeakPtrOnUIThread(), + std::move(*request), + role, origin, is_service_worker, process_id, routing_id)); + return true; + } + + network::mojom::RestrictedCookieManagerRequest orig_request = std::move(*request); + network::mojom::RestrictedCookieManagerPtrInfo target_rcm_info; + *request = mojo::MakeRequest(&target_rcm_info); + + ProxyingRestrictedCookieManagerQt::CreateAndBind( + ProfileIODataQt::FromBrowserContext(browser_context), + std::move(target_rcm_info), + is_service_worker, process_id, routing_id, + std::move(orig_request)); + + return false; // only made a proxy, still need the actual impl to be made. } bool ContentBrowserClientQt::AllowAppCacheOnIO(const GURL &manifest_url, @@ -751,6 +798,14 @@ static void LaunchURL(const GURL& url, content::WebContents* webContents = web_contents_getter.Run(); if (!webContents) return; + + ProtocolHandlerRegistry* protocolHandlerRegistry = + ProtocolHandlerRegistryFactory::GetForBrowserContext( + webContents->GetBrowserContext()); + if (protocolHandlerRegistry && + protocolHandlerRegistry->IsHandledProtocol(url.scheme())) + return; + WebContentsDelegateQt *contentsDelegate = static_cast<WebContentsDelegateQt*>(webContents->GetDelegate()); contentsDelegate->launchExternalURL(toQt(url), page_transition, is_main_frame, has_user_gesture); } @@ -766,7 +821,7 @@ bool ContentBrowserClientQt::HandleExternalProtocol( bool has_user_gesture, network::mojom::URLLoaderFactoryPtr *out_factory) { - Q_ASSERT(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); +// Q_ASSERT(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); Q_UNUSED(child_id); Q_UNUSED(navigation_data); Q_UNUSED(out_factory); @@ -822,30 +877,102 @@ private: std::vector<std::unique_ptr<content::URLLoaderThrottle>> ContentBrowserClientQt::CreateURLLoaderThrottlesOnIO( - const network::ResourceRequest & /*request*/, content::ResourceContext *resource_context, + const network::ResourceRequest &request, content::ResourceContext *resource_context, const base::RepeatingCallback<content::WebContents *()> & /*wc_getter*/, - content::NavigationUIData * /*navigation_ui_data*/, int /*frame_tree_node_id*/) + content::NavigationUIData * /*navigation_ui_data*/, int frame_tree_node_id) { std::vector<std::unique_ptr<content::URLLoaderThrottle>> result; ProfileIODataQt *ioData = ProfileIODataQt::FromResourceContext(resource_context); result.push_back(std::make_unique<ProtocolHandlerThrottle< scoped_refptr<ProtocolHandlerRegistry::IOThreadDelegate>>>( - ioData->protocolHandlerRegistryIOThreadDelegate())); + ioData->protocolHandlerRegistryIOThreadDelegate())); +#if BUILDFLAG(ENABLE_EXTENSIONS) + result.push_back(std::make_unique<PluginResponseInterceptorURLLoaderThrottle>( + resource_context, request.resource_type, frame_tree_node_id)); +#endif return result; } std::vector<std::unique_ptr<content::URLLoaderThrottle>> ContentBrowserClientQt::CreateURLLoaderThrottles( const network::ResourceRequest &request, content::BrowserContext *browser_context, - const base::RepeatingCallback<content::WebContents *()> &wc_getter, - content::NavigationUIData *navigation_ui_data, int frame_tree_node_id) + const base::RepeatingCallback<content::WebContents *()> & /*wc_getter*/, + content::NavigationUIData * /*navigation_ui_data*/, int frame_tree_node_id) { std::vector<std::unique_ptr<content::URLLoaderThrottle>> result; result.push_back(std::make_unique<ProtocolHandlerThrottle<ProtocolHandlerRegistry *>>( - ProtocolHandlerRegistryFactory::GetForBrowserContext(browser_context))); + ProtocolHandlerRegistryFactory::GetForBrowserContext(browser_context))); +#if BUILDFLAG(ENABLE_EXTENSIONS) + result.push_back(std::make_unique<PluginResponseInterceptorURLLoaderThrottle>( + browser_context, request.resource_type, frame_tree_node_id)); +#endif return result; } +extern WebContentsAdapterClient::NavigationType pageTransitionToNavigationType(ui::PageTransition transition); + +static bool navigationThrottleCallback(content::WebContents *source, + const navigation_interception::NavigationParams ¶ms) +{ + // We call navigationRequested later in launchExternalUrl for external protocols. + // The is_external_protocol parameter here is not fully accurate though, + // and doesn't know about profile specific custom URL schemes. + ProfileQt *profile = static_cast<ProfileQt *>(source->GetBrowserContext()); + if (params.is_external_protocol() && !profile->profileAdapter()->urlSchemeHandler(toQByteArray(params.url().scheme()))) + return false; + int navigationRequestAction = WebContentsAdapterClient::AcceptRequest; + WebContentsDelegateQt *delegate = static_cast<WebContentsDelegateQt *>(source->GetDelegate()); + WebContentsAdapterClient *client = delegate->adapterClient(); + client->navigationRequested(pageTransitionToNavigationType(params.transition_type()), + toQt(params.url()), + navigationRequestAction, + params.is_main_frame()); + return navigationRequestAction == static_cast<int>(WebContentsAdapterClient::IgnoreRequest); +} + +std::vector<std::unique_ptr<content::NavigationThrottle>> ContentBrowserClientQt::CreateThrottlesForNavigation( + content::NavigationHandle *navigation_handle) +{ + std::vector<std::unique_ptr<content::NavigationThrottle>> throttles; + throttles.push_back(std::make_unique<navigation_interception::InterceptNavigationThrottle>( + navigation_handle, + base::BindRepeating(&navigationThrottleCallback), + navigation_interception::SynchronyMode::kSync)); + return throttles; +} + +bool ContentBrowserClientQt::IsHandledURL(const GURL &url) +{ + static const char *const kProtocolList[] = { + url::kFileScheme, + content::kChromeDevToolsScheme, +#if BUILDFLAG(ENABLE_EXTENSIONS) + extensions::kExtensionScheme, +#endif + content::kChromeUIScheme, + url::kDataScheme, + url::kAboutScheme, +#if !BUILDFLAG(DISABLE_FTP_SUPPORT) + url::kFtpScheme, +#endif // !BUILDFLAG(DISABLE_FTP_SUPPORT) + url::kBlobScheme, + url::kFileSystemScheme, + url::kQrcScheme, + }; + + // We don't check url.IsCustom() here because we don't + // know if the registered protocol is installed in the + // profile that will be used to load the URL. + + const std::string scheme = url.scheme(); + + for (const char *protocol : kProtocolList) { + if (scheme == protocol) + return true; + } + return net::URLRequest::IsHandledProtocol(scheme); +} + std::unique_ptr<content::LoginDelegate> ContentBrowserClientQt::CreateLoginDelegate( const net::AuthChallengeInfo &authInfo, content::WebContents *web_contents, @@ -875,6 +1002,16 @@ bool ContentBrowserClientQt::ShouldUseProcessPerSite(content::BrowserContext* br return ContentBrowserClient::ShouldUseProcessPerSite(browser_context, effective_url); } +bool ContentBrowserClientQt::DoesSiteRequireDedicatedProcess(content::BrowserOrResourceContext browser_or_resource_context, + const GURL &effective_site_url) +{ +#if BUILDFLAG(ENABLE_EXTENSIONS) + if (effective_site_url.SchemeIs(extensions::kExtensionScheme)) + return true; +#endif + return ContentBrowserClient::DoesSiteRequireDedicatedProcess(browser_or_resource_context, effective_site_url); +} + std::string ContentBrowserClientQt::getUserAgent() { // Mention the Chromium version we're based on to get passed stupid UA-string-based feature detection (several WebRTC demos need this) @@ -887,4 +1024,194 @@ std::string ContentBrowserClientQt::GetProduct() return productName.toStdString(); } +scoped_refptr<network::SharedURLLoaderFactory> ContentBrowserClientQt::GetSystemSharedURLLoaderFactory() +{ + if (!SystemNetworkContextManager::GetInstance()) + return nullptr; + return SystemNetworkContextManager::GetInstance()->GetSharedURLLoaderFactory(); +} + +network::mojom::NetworkContext *ContentBrowserClientQt::GetSystemNetworkContext() +{ + if (!SystemNetworkContextManager::GetInstance()) + return nullptr; + return SystemNetworkContextManager::GetInstance()->GetContext(); +} + +void ContentBrowserClientQt::OnNetworkServiceCreated(network::mojom::NetworkService *network_service) +{ + if (!base::FeatureList::IsEnabled(network::features::kNetworkService)) + return; + + if (!SystemNetworkContextManager::GetInstance()) + SystemNetworkContextManager::CreateInstance(); + + // Need to set up global NetworkService state before anything else uses it. + SystemNetworkContextManager::GetInstance()->OnNetworkServiceCreated(network_service); +} + +network::mojom::NetworkContextPtr ContentBrowserClientQt::CreateNetworkContext(content::BrowserContext *context, + bool in_memory, + const base::FilePath &relative_partition_path) +{ + if (!base::FeatureList::IsEnabled(network::features::kNetworkService)) + return nullptr; + + network::mojom::NetworkContextPtr network_context; + // ### do we need to pass in_memory and relative_partition_path to ProfileIODataQt::CreateNetworkContextParams() ? + network::mojom::NetworkContextParamsPtr context_params = ProfileIODataQt::FromBrowserContext(context)->CreateNetworkContextParams(); + content::GetNetworkService()->CreateNetworkContext(mojo::MakeRequest(&network_context), std::move(context_params)); + + network::mojom::CookieManagerPtrInfo cookie_manager_info; + network_context->GetCookieManager(mojo::MakeRequest(&cookie_manager_info)); + ProfileIODataQt::FromBrowserContext(context)->cookieDelegate()->setMojoCookieManager(std::move(cookie_manager_info)); + + return network_context; +} + +std::vector<base::FilePath> ContentBrowserClientQt::GetNetworkContextsParentDirectory() +{ + return { + toFilePath(QStandardPaths::writableLocation(QStandardPaths::DataLocation)), + toFilePath(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)) }; +} + +void ContentBrowserClientQt::RegisterNonNetworkNavigationURLLoaderFactories(int frame_tree_node_id, + NonNetworkURLLoaderFactoryMap *factories) +{ + DCHECK(base::FeatureList::IsEnabled(network::features::kNetworkService)); + content::WebContents *web_contents = content::WebContents::FromFrameTreeNodeId(frame_tree_node_id); + Profile *profile = Profile::FromBrowserContext(web_contents->GetBrowserContext()); + ProfileAdapter *profileAdapter = static_cast<ProfileQt *>(profile)->profileAdapter(); + + for (const QByteArray &scheme : profileAdapter->customUrlSchemes()) + factories->emplace(scheme.toStdString(), CreateCustomURLLoaderFactory(profileAdapter)); + +#if BUILDFLAG(ENABLE_EXTENSIONS) + factories->emplace( + extensions::kExtensionScheme, + extensions::CreateExtensionNavigationURLLoaderFactory(profile, + !!extensions::WebViewGuest::FromWebContents(web_contents))); +#endif +} + +#if BUILDFLAG(ENABLE_EXTENSIONS) +namespace { +// The FileURLLoaderFactory provided to the extension background pages. +// Checks with the ChildProcessSecurityPolicy to validate the file access. +class FileURLLoaderFactory : public network::mojom::URLLoaderFactory +{ +public: + explicit FileURLLoaderFactory(int child_id) : child_id_(child_id) {} + +private: + // network::mojom::URLLoaderFactory: + void CreateLoaderAndStart(network::mojom::URLLoaderRequest loader, + int32_t routing_id, + int32_t request_id, + uint32_t options, + const network::ResourceRequest& request, + network::mojom::URLLoaderClientPtr client, + const net::MutableNetworkTrafficAnnotationTag &traffic_annotation) override + { + if (!content::ChildProcessSecurityPolicy::GetInstance()->CanRequestURL(child_id_, request.url)) { + client->OnComplete(network::URLLoaderCompletionStatus(net::ERR_ACCESS_DENIED)); + return; + } + content::CreateFileURLLoader(request, std::move(loader), std::move(client), + /* observer */ nullptr, + /* allow_directory_listing */ false); + } + + void Clone(network::mojom::URLLoaderFactoryRequest loader) override + { + bindings_.AddBinding(this, std::move(loader)); + } + + int child_id_; + mojo::BindingSet<network::mojom::URLLoaderFactory> bindings_; + DISALLOW_COPY_AND_ASSIGN(FileURLLoaderFactory); +}; +} // namespace +#endif + +void ContentBrowserClientQt::RegisterNonNetworkSubresourceURLLoaderFactories(int render_process_id, int render_frame_id, + NonNetworkURLLoaderFactoryMap *factories) +{ + if (!base::FeatureList::IsEnabled(network::features::kNetworkService)) + return; + content::RenderProcessHost *process_host = content::RenderProcessHost::FromID(render_process_id); + Profile *profile = Profile::FromBrowserContext(process_host->GetBrowserContext()); + ProfileAdapter *profileAdapter = static_cast<ProfileQt *>(profile)->profileAdapter(); + + for (const QByteArray &scheme : profileAdapter->customUrlSchemes()) + factories->emplace(scheme.toStdString(), CreateCustomURLLoaderFactory(profileAdapter)); + +#if BUILDFLAG(ENABLE_EXTENSIONS) + auto factory = extensions::CreateExtensionURLLoaderFactory(render_process_id, render_frame_id); + if (factory) + factories->emplace(extensions::kExtensionScheme, std::move(factory)); + + content::RenderFrameHost *frame_host = content::RenderFrameHost::FromID(render_process_id, render_frame_id); + content::WebContents *web_contents = content::WebContents::FromRenderFrameHost(frame_host); + if (!web_contents) + return; + + extensions::ExtensionWebContentsObserverQt *web_observer = + extensions::ExtensionWebContentsObserverQt::FromWebContents(web_contents); + if (!web_observer) + return; + + const extensions::Extension *extension = web_observer->GetExtensionFromFrame(frame_host, false); + if (!extension) + return; + + std::vector<std::string> allowed_webui_hosts; + // Support for chrome:// scheme if appropriate. + if ((extension->is_extension() || extension->is_platform_app()) && + extensions::Manifest::IsComponentLocation(extension->location())) { + // Components of chrome that are implemented as extensions or platform apps + // are allowed to use chrome://resources/ and chrome://theme/ URLs. + allowed_webui_hosts.emplace_back(content::kChromeUIResourcesHost); + } + if (!allowed_webui_hosts.empty()) { + factories->emplace(content::kChromeUIScheme, + content::CreateWebUIURLLoader(frame_host, + content::kChromeUIScheme, + std::move(allowed_webui_hosts))); + } + // Support for file:// scheme when approved by ChildProcessSecurityPolicy. + // FIXME: Not needed after switching to using transferable url loaders and guest views. + factories->emplace(url::kFileScheme, std::make_unique<FileURLLoaderFactory>(render_process_id)); +#endif +} + +bool ContentBrowserClientQt::WillCreateURLLoaderFactory( + content::BrowserContext *browser_context, + content::RenderFrameHost *frame, + int render_process_id, + bool is_navigation, + bool is_download, + const url::Origin &request_initiator, + mojo::PendingReceiver<network::mojom::URLLoaderFactory> *factory_receiver, + network::mojom::TrustedURLLoaderHeaderClientPtrInfo *header_client, + bool *bypass_redirect_checks) +{ + if (!base::FeatureList::IsEnabled(network::features::kNetworkService)) + return false; + + auto proxied_receiver = std::move(*factory_receiver); + network::mojom::URLLoaderFactoryPtrInfo target_factory_info; + *factory_receiver = mojo::MakeRequest(&target_factory_info); + int process_id = is_navigation ? 0 : render_process_id; + + base::PostTaskWithTraits( + FROM_HERE, {content::BrowserThread::IO}, + base::BindOnce(&ProxyingURLLoaderFactoryQt::CreateProxy, process_id, + browser_context->GetResourceContext(), + std::move(proxied_receiver), + std::move(target_factory_info))); + return true; +} + } // namespace QtWebEngineCore diff --git a/src/core/content_browser_client_qt.h b/src/core/content_browser_client_qt.h index f5b03a8d9..c6a252562 100644 --- a/src/core/content_browser_client_qt.h +++ b/src/core/content_browser_client_qt.h @@ -116,6 +116,7 @@ public: void GetAdditionalViewSourceSchemes(std::vector<std::string>* additional_schemes) override; void GetAdditionalWebUISchemes(std::vector<std::string>* additional_schemes) override; + void GetAdditionalAllowedSchemesForFileSystem(std::vector<std::string>* additional_schemes) override; void BindInterfaceRequestFromFrame(content::RenderFrameHost* render_frame_host, const std::string& interface_name, @@ -177,6 +178,8 @@ public: #endif bool ShouldIsolateErrorPage(bool in_main_frame) override; bool ShouldUseProcessPerSite(content::BrowserContext* browser_context, const GURL& effective_url) override; + bool DoesSiteRequireDedicatedProcess(content::BrowserOrResourceContext browser_or_resource_contexts, + const GURL &effective_site_url) override; #if defined(Q_OS_LINUX) void GetAdditionalMappedFilesForChildProcess(const base::CommandLine& command_line, int child_process_id, content::PosixFileDescriptorInfo* mappings) override; @@ -216,6 +219,31 @@ public: const base::RepeatingCallback<content::WebContents *()> &wc_getter, content::NavigationUIData *navigation_ui_data, int frame_tree_node_id) override; + std::vector<std::unique_ptr<content::NavigationThrottle>> CreateThrottlesForNavigation( + content::NavigationHandle *navigation_handle) override; + + bool IsHandledURL(const GURL &url) override; + + bool WillCreateURLLoaderFactory(content::BrowserContext *browser_context, + content::RenderFrameHost *frame, + int render_process_id, + bool is_navigation, + bool is_download, + const url::Origin &request_initiator, + mojo::PendingReceiver<network::mojom::URLLoaderFactory> *factory_receiver, + network::mojom::TrustedURLLoaderHeaderClientPtrInfo *header_client, + bool *bypass_redirect_checks) override; + scoped_refptr<network::SharedURLLoaderFactory> GetSystemSharedURLLoaderFactory() override; + network::mojom::NetworkContext *GetSystemNetworkContext() override; + void OnNetworkServiceCreated(network::mojom::NetworkService *network_service) override; + network::mojom::NetworkContextPtr CreateNetworkContext(content::BrowserContext *context, + bool in_memory, + const base::FilePath &relative_partition_path) override; + std::vector<base::FilePath> GetNetworkContextsParentDirectory() override; + void RegisterNonNetworkNavigationURLLoaderFactories(int frame_tree_node_id, NonNetworkURLLoaderFactoryMap *factories) override; + void RegisterNonNetworkSubresourceURLLoaderFactories(int render_process_id, int render_frame_id, + NonNetworkURLLoaderFactoryMap* factories) override; + static std::string getUserAgent(); std::string GetUserAgent() override { return getUserAgent(); } diff --git a/src/core/core_chromium.pri b/src/core/core_chromium.pri index 9eab15456..000cffa50 100644 --- a/src/core/core_chromium.pri +++ b/src/core/core_chromium.pri @@ -83,11 +83,16 @@ SOURCES = \ net/client_cert_store_data.cpp \ net/cookie_monster_delegate_qt.cpp \ net/custom_protocol_handler.cpp \ + net/custom_url_loader_factory.cpp \ net/network_delegate_qt.cpp \ + net/proxy_config_monitor.cpp \ net/proxy_config_service_qt.cpp \ + net/proxying_url_loader_factory_qt.cpp \ + net/proxying_restricted_cookie_manager_qt.cpp \ net/qrc_url_scheme_handler.cpp \ net/restricted_cookie_manager_qt.cpp \ net/ssl_host_state_delegate_qt.cpp \ + net/system_network_context_manager.cpp \ net/url_request_context_getter_qt.cpp \ net/url_request_custom_job.cpp \ net/url_request_custom_job_delegate.cpp \ @@ -194,10 +199,14 @@ HEADERS = \ net/client_cert_store_data.h \ net/cookie_monster_delegate_qt.h \ net/custom_protocol_handler.h \ + net/custom_url_loader_factory.h \ net/network_delegate_qt.h \ + net/proxying_url_loader_factory_qt.h \ + net/proxying_restricted_cookie_manager_qt.h \ net/qrc_url_scheme_handler.h \ net/restricted_cookie_manager_qt.h \ net/ssl_host_state_delegate_qt.h \ + net/system_network_context_manager.h \ net/url_request_context_getter_qt.h \ net/url_request_custom_job.h \ net/url_request_custom_job_delegate.h \ @@ -219,6 +228,7 @@ HEADERS = \ profile_adapter_client.h \ profile_qt.h \ profile_io_data_qt.h \ + proxy_config_monitor.h \ proxy_config_service_qt.h \ quota_permission_context_qt.h \ quota_request_controller.h \ @@ -339,11 +349,11 @@ qtConfig(webengine-extensions) { extensions/extensions_browser_api_provider_qt.cpp \ extensions/extensions_browser_client_qt.cpp \ extensions/mime_handler_view_guest_delegate_qt.cpp \ + net/plugin_response_interceptor_url_loader_throttle.cpp \ renderer/extensions/extensions_dispatcher_delegate_qt.cpp \ renderer/extensions/extensions_renderer_client_qt.cpp \ renderer/extensions/renderer_permissions_policy_delegate_qt.cpp \ - renderer/extensions/resource_request_policy_qt.cpp \ - renderer_host/resource_dispatcher_host_delegate_qt.cpp + renderer/extensions/resource_request_policy_qt.cpp HEADERS += \ common/extensions/extensions_api_provider_qt.h \ @@ -356,9 +366,9 @@ qtConfig(webengine-extensions) { extensions/extensions_browser_api_provider_qt.h \ extensions/extensions_browser_client_qt.h \ extensions/mime_handler_view_guest_delegate_qt.h \ + net/plugin_response_interceptor_url_loader_throttle.h \ renderer/extensions/extensions_dispatcher_delegate_qt.h \ renderer/extensions/extensions_renderer_client_qt.h \ renderer/extensions/renderer_permissions_policy_delegate_qt.h \ - renderer/extensions/resource_request_policy_qt.h \ - renderer_host/resource_dispatcher_host_delegate_qt.h + renderer/extensions/resource_request_policy_qt.h } diff --git a/src/core/core_gn_config.pri b/src/core/core_gn_config.pri index a089eecd0..2b8f2e18f 100644 --- a/src/core/core_gn_config.pri +++ b/src/core/core_gn_config.pri @@ -11,6 +11,7 @@ qtConfig (webengine-extensions) { } GN_CORE_INCLUDE_DIRS = $$PWD/service GN_CREATE_PRI = true +GN_PRECOMPILED_HEADERS = true QMAKE_INTERNAL_INCLUDED_FILES = $$GN_IMPORTS $$GN_INCLUDES $$GN_FILE diff --git a/src/core/extensions/extension_web_contents_observer_qt.cpp b/src/core/extensions/extension_web_contents_observer_qt.cpp index 365f04e46..5b1514bb4 100644 --- a/src/core/extensions/extension_web_contents_observer_qt.cpp +++ b/src/core/extensions/extension_web_contents_observer_qt.cpp @@ -70,42 +70,6 @@ void ExtensionWebContentsObserverQt::CreateForWebContents(content::WebContents * FromWebContents(web_contents)->Initialize(); } -std::string ExtensionWebContentsObserverQt::GetExtensionIdFromFrame(content::RenderFrameHost *render_frame_host) const -{ - const GURL &site = render_frame_host->GetSiteInstance()->GetSiteURL(); - if (!site.SchemeIs(kExtensionScheme)) - return std::string(); - - return site.host(); -} - -const Extension *ExtensionWebContentsObserverQt::GetExtensionFromFrame(content::RenderFrameHost *render_frame_host, bool verify_url) const -{ - std::string extension_id = GetExtensionIdFromFrame(render_frame_host); - if (extension_id.empty()) - return nullptr; - - content::BrowserContext *browser_context = - render_frame_host->GetProcess()->GetBrowserContext(); - const Extension *extension = ExtensionRegistry::Get(browser_context) - ->enabled_extensions() - .GetByID(extension_id); - if (!extension) - return nullptr; - - if (verify_url) { - const url::Origin &origin(render_frame_host->GetLastCommittedOrigin()); - // Without site isolation, this check is needed to eliminate non-extension - // schemes. With site isolation, this is still needed to exclude sandboxed - // extension frames with a unique origin. - const GURL site_url(render_frame_host->GetSiteInstance()->GetSiteURL()); - if (origin.opaque() || site_url != content::SiteInstance::GetSiteForURL(browser_context, origin.GetURL())) - return nullptr; - } - - return extension; -} - void ExtensionWebContentsObserverQt::RenderFrameCreated(content::RenderFrameHost *render_frame_host) { ExtensionWebContentsObserver::RenderFrameCreated(render_frame_host); diff --git a/src/core/extensions/extension_web_contents_observer_qt.h b/src/core/extensions/extension_web_contents_observer_qt.h index a528b3856..658966b31 100644 --- a/src/core/extensions/extension_web_contents_observer_qt.h +++ b/src/core/extensions/extension_web_contents_observer_qt.h @@ -59,9 +59,6 @@ public: static void CreateForWebContents(content::WebContents *web_contents); - std::string GetExtensionIdFromFrame(content::RenderFrameHost *) const; - const Extension *GetExtensionFromFrame(content::RenderFrameHost *, bool) const; - // content::WebContentsObserver overrides. void RenderFrameCreated(content::RenderFrameHost *render_frame_host) override; diff --git a/src/core/extensions/extensions_browser_client_qt.cpp b/src/core/extensions/extensions_browser_client_qt.cpp index 59c15d2f5..d974155c5 100644 --- a/src/core/extensions/extensions_browser_client_qt.cpp +++ b/src/core/extensions/extensions_browser_client_qt.cpp @@ -66,10 +66,12 @@ #include "extensions/browser/mojo/interface_registration.h" #include "extensions/browser/url_request_util.h" #include "extensions/common/file_util.h" +#include "mojo/public/cpp/bindings/strong_binding.h" #include "net/base/completion_once_callback.h" #include "net/base/mime_util.h" #include "net/url_request/url_request_simple_job.h" #include "services/network/public/mojom/url_loader.mojom.h" +#include "third_party/zlib/google/compression_utils.h" #include "ui/base/resource/resource_bundle.h" #include "component_extension_resource_manager_qt.h" @@ -165,6 +167,163 @@ private: mutable base::WeakPtrFactory<URLRequestResourceBundleJob> weak_factory_; }; +scoped_refptr<base::RefCountedMemory> GetResource(int resource_id, const std::string &extension_id) +{ + const ui::ResourceBundle &rb = ui::ResourceBundle::GetSharedInstance(); + scoped_refptr<base::RefCountedMemory> bytes = rb.LoadDataResourceBytes(resource_id); + auto *replacements = extensions::ExtensionsBrowserClient::Get()->GetComponentExtensionResourceManager() + ? extensions::ExtensionsBrowserClient::Get()->GetComponentExtensionResourceManager()->GetTemplateReplacementsForExtension( + extension_id) + : nullptr; + + bool is_gzipped = rb.IsGzipped(resource_id); + if (!bytes->size() || (!replacements && !is_gzipped)) { + return bytes; + } + + base::StringPiece input(reinterpret_cast<const char *>(bytes->front()), bytes->size()); + + std::string temp_str; + + base::StringPiece source = input; + if (is_gzipped) { + temp_str.resize(compression::GetUncompressedSize(input)); + source = temp_str; + CHECK(compression::GzipUncompress(input, source)); + } + + if (replacements) { + temp_str = ui::ReplaceTemplateExpressions(source, *replacements); + } + + DCHECK(!temp_str.empty()); + + return base::RefCountedString::TakeString(&temp_str); +} + +// Loads an extension resource in a Chrome .pak file. These are used by +// component extensions. +class ResourceBundleFileLoader : public network::mojom::URLLoader +{ +public: + static void CreateAndStart(const network::ResourceRequest &request, network::mojom::URLLoaderRequest loader, + network::mojom::URLLoaderClientPtrInfo client_info, const base::FilePath &filename, + int resource_id, const std::string &content_security_policy, bool send_cors_header) + { + // Owns itself. Will live as long as its URLLoader and URLLoaderClientPtr + // bindings are alive - essentially until either the client gives up or all + // file data has been sent to it. + auto *bundle_loader = new ResourceBundleFileLoader(content_security_policy, send_cors_header); + bundle_loader->Start(request, std::move(loader), std::move(client_info), filename, resource_id); + } + + // mojom::URLLoader implementation: + void FollowRedirect(const std::vector<std::string> &removed_headers, + const net::HttpRequestHeaders &modified_headers, const base::Optional<GURL> &new_url) override + { + NOTREACHED() << "No redirects for local file loads."; + } + // Current implementation reads all resource data at start of resource + // load, so priority, and pausing is not currently implemented. + void SetPriority(net::RequestPriority priority, int32_t intra_priority_value) override {} + void PauseReadingBodyFromNet() override {} + void ResumeReadingBodyFromNet() override {} + void ProceedWithResponse() override {} + +private: + ResourceBundleFileLoader(const std::string &content_security_policy, bool send_cors_header) : binding_(this) + { + response_headers_ = extensions::BuildHttpHeaders(content_security_policy, send_cors_header, base::Time()); + } + ~ResourceBundleFileLoader() override = default; + + void Start(const network::ResourceRequest &request, network::mojom::URLLoaderRequest loader, + network::mojom::URLLoaderClientPtrInfo client_info, const base::FilePath &filename, int resource_id) + { + client_.Bind(std::move(client_info)); + binding_.Bind(std::move(loader)); + binding_.set_connection_error_handler( + base::BindOnce(&ResourceBundleFileLoader::OnBindingError, base::Unretained(this))); + client_.set_connection_error_handler( + base::BindOnce(&ResourceBundleFileLoader::OnConnectionError, base::Unretained(this))); + auto data = GetResource(resource_id, request.url.host()); + + std::string *read_mime_type = new std::string; + base::PostTaskWithTraitsAndReplyWithResult( + FROM_HERE, { base::MayBlock() }, + base::BindOnce(&net::GetMimeTypeFromFile, filename, base::Unretained(read_mime_type)), + base::BindOnce(&ResourceBundleFileLoader::OnMimeTypeRead, weak_factory_.GetWeakPtr(), std::move(data), + base::Owned(read_mime_type))); + } + + void OnMimeTypeRead(scoped_refptr<base::RefCountedMemory> data, std::string *read_mime_type, bool read_result) + { + network::ResourceResponseHead head; + head.request_start = base::TimeTicks::Now(); + head.response_start = base::TimeTicks::Now(); + head.content_length = data->size(); + head.mime_type = *read_mime_type; + DetermineCharset(head.mime_type, data.get(), &head.charset); + mojo::DataPipe pipe(data->size()); + if (!pipe.consumer_handle.is_valid()) { + client_->OnComplete(network::URLLoaderCompletionStatus(net::ERR_FAILED)); + client_.reset(); + MaybeDeleteSelf(); + return; + } + head.headers = response_headers_; + head.headers->AddHeader(base::StringPrintf("%s: %s", net::HttpRequestHeaders::kContentLength, + base::NumberToString(head.content_length).c_str())); + if (!head.mime_type.empty()) { + head.headers->AddHeader( + base::StringPrintf("%s: %s", net::HttpRequestHeaders::kContentType, head.mime_type.c_str())); + } + client_->OnReceiveResponse(head); + client_->OnStartLoadingResponseBody(std::move(pipe.consumer_handle)); + + uint32_t write_size = data->size(); + MojoResult result = pipe.producer_handle->WriteData(data->front(), &write_size, MOJO_WRITE_DATA_FLAG_NONE); + OnFileWritten(result); + } + + void OnConnectionError() + { + client_.reset(); + MaybeDeleteSelf(); + } + + void OnBindingError() + { + binding_.Close(); + MaybeDeleteSelf(); + } + + void MaybeDeleteSelf() + { + if (!binding_.is_bound() && !client_.is_bound()) + delete this; + } + + void OnFileWritten(MojoResult result) + { + // All the data has been written now. The consumer will be notified that + // there will be no more data to read from now. + if (result == MOJO_RESULT_OK) + client_->OnComplete(network::URLLoaderCompletionStatus(net::OK)); + else + client_->OnComplete(network::URLLoaderCompletionStatus(net::ERR_FAILED)); + client_.reset(); + MaybeDeleteSelf(); + } + + mojo::Binding<network::mojom::URLLoader> binding_; + network::mojom::URLLoaderClientPtr client_; + scoped_refptr<net::HttpResponseHeaders> response_headers_; + base::WeakPtrFactory<ResourceBundleFileLoader> weak_factory_{ this }; + + DISALLOW_COPY_AND_ASSIGN(ResourceBundleFileLoader); +}; + } // namespace namespace extensions { @@ -307,7 +466,8 @@ void ExtensionsBrowserClientQt::LoadResourceFromResourceBundle(const network::Re network::mojom::URLLoaderClientPtr client, bool send_cors_header) { - NOTIMPLEMENTED(); + ResourceBundleFileLoader::CreateAndStart(request, std::move(loader), client.PassInterface(), resource_relative_path, + resource_id, content_security_policy, send_cors_header); } diff --git a/src/core/login_delegate_qt.cpp b/src/core/login_delegate_qt.cpp index f63252112..80e2d9102 100644 --- a/src/core/login_delegate_qt.cpp +++ b/src/core/login_delegate_qt.cpp @@ -51,6 +51,8 @@ #include "content/public/browser/resource_dispatcher_host.h" #include "content/public/browser/resource_request_info.h" #include "extensions/buildflags/buildflags.h" +#include "services/network/public/cpp/features.h" + #if BUILDFLAG(ENABLE_EXTENSIONS) #include "extensions/browser/info_map.h" #include "extensions/common/extension.h" @@ -153,7 +155,9 @@ void LoginDelegateQt::sendAuthToRequester(bool success, const QString &user, con std::move(m_auth_required_callback).Run(base::nullopt); } - destroy(); + // With network service the auth callback has already deleted us. + if (!base::FeatureList::IsEnabled(network::features::kNetworkService)) + destroy(); } void LoginDelegateQt::destroy() diff --git a/src/core/net/cookie_monster_delegate_qt.cpp b/src/core/net/cookie_monster_delegate_qt.cpp index dad9a8db5..e0fa60be7 100644 --- a/src/core/net/cookie_monster_delegate_qt.cpp +++ b/src/core/net/cookie_monster_delegate_qt.cpp @@ -44,7 +44,9 @@ #include "base/task/post_task.h" #include "content/public/browser/browser_task_traits.h" #include "content/public/browser/browser_thread.h" +#include "mojo/public/cpp/bindings/binding.h" #include "net/cookies/cookie_util.h" +#include "services/network/public/mojom/cookie_manager.mojom.h" #include "api/qwebenginecookiestore.h" #include "api/qwebenginecookiestore_p.h" @@ -52,6 +54,25 @@ namespace QtWebEngineCore { +class CookieChangeListener : public network::mojom::CookieChangeListener +{ +public: + CookieChangeListener(CookieMonsterDelegateQt *delegate) : m_delegate(delegate) { } + ~CookieChangeListener() override = default; + + // network::mojom::CookieChangeListener: + void OnCookieChange(const net::CanonicalCookie &canonical_cookie, + network::mojom::CookieChangeCause cause) override + { + m_delegate->OnCookieChanged(canonical_cookie, net::CookieChangeCause(cause)); + } + +private: + CookieMonsterDelegateQt *m_delegate; + + DISALLOW_COPY_AND_ASSIGN(CookieChangeListener); +}; + static GURL sourceUrlForCookie(const QNetworkCookie &cookie) { QString urlFragment = QStringLiteral("%1%2").arg(cookie.domain()).arg(cookie.path()); @@ -61,6 +82,8 @@ static GURL sourceUrlForCookie(const QNetworkCookie &cookie) CookieMonsterDelegateQt::CookieMonsterDelegateQt() : m_client(0) , m_cookieMonster(nullptr) + , m_listener(new CookieChangeListener(this)) + , m_binding(m_listener.get()) { } @@ -81,20 +104,24 @@ void CookieMonsterDelegateQt::AddStore(net::CookieStore *store) bool CookieMonsterDelegateQt::hasCookieMonster() { - return m_cookieMonster; + return m_cookieMonster || m_mojoCookieManager.is_bound(); } void CookieMonsterDelegateQt::getAllCookies(quint64 callbackId) { - net::CookieMonster::GetCookieListCallback callback = + if (m_mojoCookieManager.is_bound()) { + m_mojoCookieManager->GetAllCookies(base::BindOnce(&CookieMonsterDelegateQt::GetAllCookiesCallbackOnUIThread, this, callbackId)); + } else { + net::CookieMonster::GetCookieListCallback callback = base::BindOnce(&CookieMonsterDelegateQt::GetAllCookiesCallbackOnIOThread, this, callbackId); - - base::PostTaskWithTraits(FROM_HERE, {content::BrowserThread::IO}, - base::BindOnce(&CookieMonsterDelegateQt::GetAllCookiesOnIOThread, this, std::move(callback))); + base::PostTaskWithTraits(FROM_HERE, {content::BrowserThread::IO}, + base::BindOnce(&CookieMonsterDelegateQt::GetAllCookiesOnIOThread, this, std::move(callback))); + } } void CookieMonsterDelegateQt::GetAllCookiesOnIOThread(net::CookieMonster::GetCookieListCallback callback) { + DCHECK_CURRENTLY_ON(content::BrowserThread::IO); if (m_cookieMonster) m_cookieMonster->GetAllCookiesAsync(std::move(callback)); } @@ -105,19 +132,30 @@ void CookieMonsterDelegateQt::setCookie(quint64 callbackId, const QNetworkCookie Q_ASSERT(m_client); net::CookieStore::SetCookiesCallback callback; - if (callbackId != CallbackDirectory::NoCallbackId) - callback = base::BindOnce(&CookieMonsterDelegateQt::SetCookieCallbackOnIOThread, this, callbackId); GURL gurl = origin.isEmpty() ? sourceUrlForCookie(cookie) : toGurl(origin); - - base::PostTaskWithTraits(FROM_HERE, {content::BrowserThread::IO}, - base::BindOnce(&CookieMonsterDelegateQt::SetCookieOnIOThread, this, - gurl, cookie.toRawForm().toStdString(), std::move(callback))); + std::string cookie_line = cookie.toRawForm().toStdString(); + + if (m_mojoCookieManager.is_bound()) { + if (callbackId != CallbackDirectory::NoCallbackId) + callback = base::BindOnce(&CookieMonsterDelegateQt::SetCookieCallbackOnUIThread, this, callbackId); + net::CookieOptions options; + options.set_include_httponly(); + auto cookie = net::CanonicalCookie::Create(gurl, cookie_line, base::Time::Now(), options); + m_mojoCookieManager->SetCanonicalCookie(*cookie.get(), gurl.scheme(), options, std::move(callback)); + } else { + if (callbackId != CallbackDirectory::NoCallbackId) + callback = base::BindOnce(&CookieMonsterDelegateQt::SetCookieCallbackOnIOThread, this, callbackId); + base::PostTaskWithTraits(FROM_HERE, {content::BrowserThread::IO}, + base::BindOnce(&CookieMonsterDelegateQt::SetCookieOnIOThread, this, + gurl, std::move(cookie_line), std::move(callback))); + } } void CookieMonsterDelegateQt::SetCookieOnIOThread(const GURL &url, const std::string &cookie_line, net::CookieMonster::SetCookiesCallback callback) { + DCHECK_CURRENTLY_ON(content::BrowserThread::IO); net::CookieOptions options; options.set_include_httponly(); @@ -131,14 +169,22 @@ void CookieMonsterDelegateQt::deleteCookie(const QNetworkCookie &cookie, const Q Q_ASSERT(m_client); GURL gurl = origin.isEmpty() ? sourceUrlForCookie(cookie) : toGurl(origin); - - base::PostTaskWithTraits(FROM_HERE, {content::BrowserThread::IO}, - base::BindOnce(&CookieMonsterDelegateQt::DeleteCookieOnIOThread, this, - gurl, cookie.name().toStdString())); + std::string cookie_name = cookie.name().toStdString(); + if (m_mojoCookieManager.is_bound()) { + auto filter = network::mojom::CookieDeletionFilter::New(); + filter->url = gurl; + filter->cookie_name = cookie_name; + m_mojoCookieManager->DeleteCookies(std::move(filter), network::mojom::CookieManager::DeleteCookiesCallback()); + } else { + base::PostTaskWithTraits(FROM_HERE, {content::BrowserThread::IO}, + base::BindOnce(&CookieMonsterDelegateQt::DeleteCookieOnIOThread, this, + gurl, cookie_name)); + } } void CookieMonsterDelegateQt::DeleteCookieOnIOThread(const GURL &url, const std::string &cookie_name) { + DCHECK_CURRENTLY_ON(content::BrowserThread::IO); if (m_cookieMonster) { net::CookieMonster::GetCookieListCallback callback = base::BindOnce(&CookieMonsterDelegateQt::GetCookiesToDeleteCallback, this, cookie_name); @@ -168,14 +214,23 @@ void CookieMonsterDelegateQt::deleteSessionCookies(quint64 callbackId) Q_ASSERT(hasCookieMonster()); Q_ASSERT(m_client); - net::CookieMonster::DeleteCallback callback = - base::BindOnce(&CookieMonsterDelegateQt::DeleteCookiesCallbackOnIOThread, this, callbackId); - base::PostTaskWithTraits(FROM_HERE, {content::BrowserThread::IO}, - base::BindOnce(&CookieMonsterDelegateQt::DeleteSessionCookiesOnIOThread, this, std::move(callback))); + if (m_mojoCookieManager.is_bound()) { + net::CookieMonster::DeleteCallback callback = + base::BindOnce(&CookieMonsterDelegateQt::DeleteCookiesCallbackOnUIThread, this, callbackId); + auto filter = network::mojom::CookieDeletionFilter::New(); + filter->session_control = network::mojom::CookieDeletionSessionControl::SESSION_COOKIES; + m_mojoCookieManager->DeleteCookies(std::move(filter), std::move(callback)); + } else { + net::CookieMonster::DeleteCallback callback = + base::BindOnce(&CookieMonsterDelegateQt::DeleteCookiesCallbackOnIOThread, this, callbackId); + base::PostTaskWithTraits(FROM_HERE, {content::BrowserThread::IO}, + base::BindOnce(&CookieMonsterDelegateQt::DeleteSessionCookiesOnIOThread, this, std::move(callback))); + } } void CookieMonsterDelegateQt::DeleteSessionCookiesOnIOThread(net::CookieMonster::DeleteCallback callback) { + DCHECK_CURRENTLY_ON(content::BrowserThread::IO); if (m_cookieMonster) m_cookieMonster->DeleteSessionCookiesAsync(std::move(callback)); } @@ -185,14 +240,22 @@ void CookieMonsterDelegateQt::deleteAllCookies(quint64 callbackId) Q_ASSERT(hasCookieMonster()); Q_ASSERT(m_client); - net::CookieMonster::DeleteCallback callback = - base::BindOnce(&CookieMonsterDelegateQt::DeleteCookiesCallbackOnIOThread, this, callbackId); - base::PostTaskWithTraits(FROM_HERE, {content::BrowserThread::IO}, - base::BindOnce(&CookieMonsterDelegateQt::DeleteAllOnIOThread, this, std::move(callback))); + if (m_mojoCookieManager.is_bound()) { + net::CookieMonster::DeleteCallback callback = + base::BindOnce(&CookieMonsterDelegateQt::DeleteCookiesCallbackOnUIThread, this, callbackId); + auto filter = network::mojom::CookieDeletionFilter::New(); + m_mojoCookieManager->DeleteCookies(std::move(filter), std::move(callback)); + } else { + net::CookieMonster::DeleteCallback callback = + base::BindOnce(&CookieMonsterDelegateQt::DeleteCookiesCallbackOnIOThread, this, callbackId); + base::PostTaskWithTraits(FROM_HERE, {content::BrowserThread::IO}, + base::BindOnce(&CookieMonsterDelegateQt::DeleteAllOnIOThread, this, std::move(callback))); + } } void CookieMonsterDelegateQt::DeleteAllOnIOThread(net::CookieMonster::DeleteCallback callback) { + DCHECK_CURRENTLY_ON(content::BrowserThread::IO); if (m_cookieMonster) m_cookieMonster->DeleteAllAsync(std::move(callback)); } @@ -217,6 +280,26 @@ void CookieMonsterDelegateQt::setCookieMonster(net::CookieMonster *monster) m_client->d_func()->rejectPendingUserCookies(); } +void CookieMonsterDelegateQt::setMojoCookieManager(network::mojom::CookieManagerPtrInfo cookie_manager_info) +{ +// DCHECK_CURRENTLY_ON(content::BrowserThread::IO); + m_mojoCookieManager.Bind(std::move(cookie_manager_info)); + + network::mojom::CookieChangeListenerPtr listener_ptr; + m_binding.Bind(mojo::MakeRequest(&listener_ptr)); + + m_mojoCookieManager->AddGlobalChangeListener(std::move(listener_ptr)); + + if (m_client) + m_client->d_func()->processPendingUserCookies(); +} + +void CookieMonsterDelegateQt::unsetMojoCookieManager() +{ + m_binding.Close(); + m_mojoCookieManager.reset(); +} + void CookieMonsterDelegateQt::setClient(QWebEngineCookieStore *client) { m_client = client; @@ -255,17 +338,29 @@ void CookieMonsterDelegateQt::OnCookieChanged(const net::CanonicalCookie &cookie void CookieMonsterDelegateQt::GetAllCookiesCallbackOnIOThread(qint64 callbackId, const net::CookieList &cookies, const net::CookieStatusList &statusList) { + DCHECK_CURRENTLY_ON(content::BrowserThread::IO); QByteArray rawCookies; for (auto &&cookie : cookies) rawCookies += toQt(cookie).toRawForm() % QByteArrayLiteral("\n"); base::PostTaskWithTraits( FROM_HERE, {content::BrowserThread::UI}, - base::BindOnce(&CookieMonsterDelegateQt::GetAllCookiesCallbackOnUIThread, this, callbackId, rawCookies)); + base::BindOnce(&CookieMonsterDelegateQt::GetAllCookiesResultOnUIThread, this, callbackId, rawCookies)); +} + +void CookieMonsterDelegateQt::GetAllCookiesCallbackOnUIThread(qint64 callbackId, const std::vector<net::CanonicalCookie> &cookies) +{ + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + QByteArray rawCookies; + for (auto &&cookie : cookies) + rawCookies += toQt(cookie).toRawForm() % QByteArrayLiteral("\n"); + + GetAllCookiesResultOnUIThread(callbackId, rawCookies); } void CookieMonsterDelegateQt::SetCookieCallbackOnIOThread(qint64 callbackId, net::CanonicalCookie::CookieInclusionStatus status) { + DCHECK_CURRENTLY_ON(content::BrowserThread::IO); base::PostTaskWithTraits( FROM_HERE, {content::BrowserThread::UI}, base::BindOnce(&CookieMonsterDelegateQt::SetCookieCallbackOnUIThread, this, callbackId, status)); @@ -273,12 +368,13 @@ void CookieMonsterDelegateQt::SetCookieCallbackOnIOThread(qint64 callbackId, net void CookieMonsterDelegateQt::DeleteCookiesCallbackOnIOThread(qint64 callbackId, uint numCookies) { + DCHECK_CURRENTLY_ON(content::BrowserThread::IO); base::PostTaskWithTraits( FROM_HERE, {content::BrowserThread::UI}, base::BindOnce(&CookieMonsterDelegateQt::DeleteCookiesCallbackOnUIThread, this, callbackId, numCookies)); } -void CookieMonsterDelegateQt::GetAllCookiesCallbackOnUIThread(qint64 callbackId, const QByteArray &cookies) +void CookieMonsterDelegateQt::GetAllCookiesResultOnUIThread(qint64 callbackId, const QByteArray &cookies) { if (m_client) m_client->d_func()->onGetAllCallbackResult(callbackId, cookies); diff --git a/src/core/net/cookie_monster_delegate_qt.h b/src/core/net/cookie_monster_delegate_qt.h index 23b803790..02c84e061 100644 --- a/src/core/net/cookie_monster_delegate_qt.h +++ b/src/core/net/cookie_monster_delegate_qt.h @@ -57,8 +57,21 @@ QT_WARNING_PUSH // For some reason adding -Wno-unused-parameter to QMAKE_CXXFLAGS has no // effect with clang, so use a pragma for these dirty chromium headers QT_WARNING_DISABLE_CLANG("-Wunused-parameter") + +// We need to work around Chromium using 'signals' as a variable name in headers: +#ifdef signals +#define StAsH_signals signals +#undef signals +#endif #include "base/memory/ref_counted.h" +#include "mojo/public/cpp/bindings/binding.h" #include "net/cookies/cookie_monster.h" +#include "services/network/public/mojom/cookie_manager.mojom-forward.h" +#include "services/network/public/mojom/cookie_manager.mojom.h" +#ifdef StAsH_signals +#define signals StAsH_signals +#undef StAsH_signals +#endif QT_WARNING_POP #include <QNetworkCookie> @@ -74,12 +87,17 @@ namespace QtWebEngineCore { // to correctly handle file:// cookies, qrc:// should only be used for testing. static const char *const kCookieableSchemes[] = { "http", "https", "qrc", "ws", "wss" }; +class CookieMonsterDelegateQtPrivate; + class Q_WEBENGINECORE_PRIVATE_EXPORT CookieMonsterDelegateQt : public base::RefCountedThreadSafe<CookieMonsterDelegateQt> { QPointer<QWebEngineCookieStore> m_client; net::CookieMonster *m_cookieMonster; std::vector<std::unique_ptr<net::CookieChangeSubscription>> m_subscriptions; + network::mojom::CookieManagerPtr m_mojoCookieManager; + std::unique_ptr<network::mojom::CookieChangeListener> m_listener; + mojo::Binding<network::mojom::CookieChangeListener> m_binding; public: CookieMonsterDelegateQt(); ~CookieMonsterDelegateQt(); @@ -94,6 +112,8 @@ public: void setCookieMonster(net::CookieMonster *monster); void setClient(QWebEngineCookieStore *client); + void setMojoCookieManager(network::mojom::CookieManagerPtrInfo cookie_manager_info); + void unsetMojoCookieManager(); bool canSetCookie(const QUrl &firstPartyUrl, const QByteArray &cookieLine, const QUrl &url) const; bool canGetCookies(const QUrl &firstPartyUrl, const QUrl &url) const; @@ -116,7 +136,8 @@ private: void SetCookieCallbackOnIOThread(qint64 callbackId, net::CanonicalCookie::CookieInclusionStatus status); void DeleteCookiesCallbackOnIOThread(qint64 callbackId, uint numCookies); - void GetAllCookiesCallbackOnUIThread(qint64 callbackId, const QByteArray &cookies); + void GetAllCookiesCallbackOnUIThread(qint64 callbackId, const std::vector<net::CanonicalCookie> &cookies); + void GetAllCookiesResultOnUIThread(qint64 callbackId, const QByteArray &cookies); void SetCookieCallbackOnUIThread(qint64 callbackId, net::CanonicalCookie::CookieInclusionStatus status); void DeleteCookiesCallbackOnUIThread(qint64 callbackId, uint numCookies); }; diff --git a/src/core/net/custom_url_loader_factory.cpp b/src/core/net/custom_url_loader_factory.cpp new file mode 100644 index 000000000..f6485c56b --- /dev/null +++ b/src/core/net/custom_url_loader_factory.cpp @@ -0,0 +1,412 @@ +/**************************************************************************** +** +** Copyright (C) 2019 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 "custom_url_loader_factory.h" + +#include "base/strings/stringprintf.h" +#include "base/task/post_task.h" +#include "content/public/browser/browser_task_traits.h" +#include "content/public/browser/browser_thread.h" +#include "mojo/public/cpp/bindings/binding_set.h" +#include "mojo/public/cpp/system/data_pipe.h" +#include "mojo/public/cpp/system/data_pipe_producer.h" +#include "net/base/net_errors.h" +#include "net/http/http_status_code.h" +#include "net/http/http_util.h" +#include "services/network/public/mojom/url_loader.mojom.h" +#include "services/network/public/mojom/url_loader_factory.mojom.h" + +#include "api/qwebengineurlscheme.h" +#include "net/url_request_custom_job_proxy.h" +#include "profile_adapter.h" +#include "type_conversion.h" + +#include <QtCore/qbytearray.h> +#include <QtCore/qfile.h> +#include <QtCore/qfileinfo.h> +#include <QtCore/qiodevice.h> +#include <QtCore/qmimedatabase.h> +#include <QtCore/qmimedata.h> +#include <QtCore/qurl.h> + +namespace QtWebEngineCore { + +namespace { + +class CustomURLLoader : public network::mojom::URLLoader + , private URLRequestCustomJobProxy::Client +{ +public: + static void CreateAndStart(const network::ResourceRequest &request, + network::mojom::URLLoaderRequest loader, + network::mojom::URLLoaderClientPtrInfo client_info, + QPointer<ProfileAdapter> profileAdapter) + { + // CustomURLLoader will handle its own life-cycle, and delete when + // the client lets go. + auto *customUrlLoader = new CustomURLLoader(request, std::move(loader), std::move(client_info), profileAdapter); + customUrlLoader->Start(); + } + + // network::mojom::URLLoader: + void FollowRedirect(const std::vector<std::string> &removed_headers, + const net::HttpRequestHeaders &modified_headers, + const base::Optional<GURL> &new_url) override + { + // We can be asked for follow our own redirect + scoped_refptr<URLRequestCustomJobProxy> proxy = new URLRequestCustomJobProxy(this, m_proxy->m_scheme, m_proxy->m_profileAdapter); + m_proxy->m_client = nullptr; +// m_taskRunner->PostTask(FROM_HERE, base::BindOnce(&URLRequestCustomJobProxy::release, m_proxy)); + base::PostTaskWithTraits(FROM_HERE, { content::BrowserThread::UI }, + base::BindOnce(&URLRequestCustomJobProxy::release, m_proxy)); + m_proxy = std::move(proxy); + if (new_url) + m_request.url = *new_url; + else + m_request.url = m_redirect; + // ### remove and modify headers? + m_redirect = GURL(); + Start(); + } + void SetPriority(net::RequestPriority priority, int32_t intra_priority_value) override { } + void PauseReadingBodyFromNet() override { } + void ResumeReadingBodyFromNet() override { } + void ProceedWithResponse() override { } + +private: + CustomURLLoader(const network::ResourceRequest &request, + network::mojom::URLLoaderRequest loader, + network::mojom::URLLoaderClientPtrInfo client_info, + QPointer<ProfileAdapter> profileAdapter) + // ### We can opt to run the url-loader on the UI thread instead + : m_taskRunner(base::CreateSingleThreadTaskRunner({ content::BrowserThread::IO })) + , m_proxy(new URLRequestCustomJobProxy(this, request.url.scheme(), profileAdapter)) + , m_binding(this, std::move(loader)) + , m_client(std::move(client_info)) + , m_request(request) + { + m_binding.set_connection_error_handler( + base::BindOnce(&CustomURLLoader::OnConnectionError, base::Unretained(this))); + m_firstBytePosition = 0; + m_device = nullptr; + m_error = 0; + QWebEngineUrlScheme scheme = QWebEngineUrlScheme::schemeByName(QByteArray::fromStdString(request.url.scheme())); + m_corsEnabled = scheme.flags().testFlag(QWebEngineUrlScheme::CorsEnabled); + } + + ~CustomURLLoader() override = default; + + void Start() + { + m_head.request_start = base::TimeTicks::Now(); + + if (!m_pipe.consumer_handle.is_valid()) + return CompleteWithFailure(net::ERR_FAILED); + + std::map<std::string, std::string> headers; + net::HttpRequestHeaders::Iterator it(m_request.headers); + while (it.GetNext()) + headers.emplace(it.name(), it.value()); + if (!m_request.referrer.is_empty()) + headers.emplace("Referer", m_request.referrer.spec()); + + std::string rangeHeader; + if (m_request.headers.GetHeader(net::HttpRequestHeaders::kRange, &rangeHeader)) { + std::vector<net::HttpByteRange> ranges; + if (net::HttpUtil::ParseRangeHeader(rangeHeader, &ranges)) { + // Chromium doesn't support multiple range requests in one single URL request. + if (ranges.size() == 1) + m_firstBytePosition = ranges[0].first_byte_position(); + } + } + +// m_taskRunner->PostTask(FROM_HERE, + base::PostTaskWithTraits(FROM_HERE, { content::BrowserThread::UI }, + base::BindOnce(&URLRequestCustomJobProxy::initialize, m_proxy, + m_request.url, m_request.method, m_request.request_initiator, std::move(headers))); + } + + void CompleteWithFailure(net::Error net_error) + { + m_client->OnComplete(network::URLLoaderCompletionStatus(net_error)); + ClearProxyAndClient(false); + } + + void OnConnectionError() + { + m_binding.Close(); + if (m_client.is_bound()) + ClearProxyAndClient(false); + else + delete this; + } + + void OnTransferComplete(MojoResult result) + { + if (result == MOJO_RESULT_OK) { + network::URLLoaderCompletionStatus status(net::OK); + status.encoded_data_length = m_totalBytesRead + m_head.headers->raw_headers().length(); + status.encoded_body_length = m_totalBytesRead; + status.decoded_body_length = m_totalBytesRead; + m_client->OnComplete(status); + } else { + m_client->OnComplete(network::URLLoaderCompletionStatus(net::ERR_FAILED)); + } + ClearProxyAndClient(false /* result == MOJO_RESULT_OK */); + } + + void ClearProxyAndClient(bool wait_for_loader_error = false) + { + m_proxy->m_client = nullptr; + m_client.reset(); + if (m_device && m_device->isOpen()) + m_device->close(); + m_device = nullptr; +// m_taskRunner->PostTask(FROM_HERE, base::BindOnce(&URLRequestCustomJobProxy::release, m_proxy)); + base::PostTaskWithTraits(FROM_HERE, { content::BrowserThread::UI }, + base::BindOnce(&URLRequestCustomJobProxy::release, m_proxy)); + if (!wait_for_loader_error || !m_binding.is_bound()) + delete this; + } + + // URLRequestCustomJobProxy::Client: + void notifyExpectedContentSize(qint64 size) override + { + m_head.content_length = size; + } + void notifyHeadersComplete() override + { + m_taskRunner->PostTask(FROM_HERE, + base::BindOnce(&CustomURLLoader::reportHeadersComplete, base::Unretained(this))); + } + void reportHeadersComplete() + { + DCHECK(!m_error); + m_head.response_start = base::TimeTicks::Now(); + + std::string headers; + if (!m_redirect.is_empty()) { + headers += "HTTP/1.1 303 See Other\n"; + headers += base::StringPrintf("Location: %s\n", m_redirect.spec().c_str()); + } else { + headers += "HTTP/1.1 200 OK\n"; + if (m_mimeType.size() > 0) { + headers += base::StringPrintf("Content-Type: %s", m_mimeType.c_str()); + if (m_charset.size() > 0) + headers += base::StringPrintf("; charset=%s", m_charset.c_str()); + headers += "\n"; + } + } + if (m_corsEnabled) { + std::string origin; + if (m_request.headers.GetHeader("Origin", &origin)) { + headers += base::StringPrintf("Access-Control-Allow-Origin: %s\n", origin.c_str()); + headers += "Access-Control-Allow-Credentials: true\n"; + } + } + m_head.headers = base::MakeRefCounted<net::HttpResponseHeaders>(net::HttpUtil::AssembleRawHeaders(headers)); + m_head.encoded_data_length = m_head.headers->raw_headers().length(); + + if (!m_redirect.is_empty()) { + m_head.content_length = m_head.encoded_body_length = -1; + net::URLRequest::FirstPartyURLPolicy first_party_url_policy = + m_request.update_first_party_url_on_redirect ? net::URLRequest::UPDATE_FIRST_PARTY_URL_ON_REDIRECT + : net::URLRequest::NEVER_CHANGE_FIRST_PARTY_URL; + net::RedirectInfo redirectInfo = net::RedirectInfo::ComputeRedirectInfo( + m_request.method, m_request.url, + m_request.site_for_cookies, m_request.top_frame_origin, + first_party_url_policy, m_request.referrer_policy, + m_request.referrer.spec(), net::HTTP_SEE_OTHER, + m_redirect, base::nullopt, false /*insecure_scheme_was_upgraded*/); + m_client->OnReceiveRedirect(redirectInfo, m_head); + // ### should m_request be updated with RedirectInfo? (see FollowRedirect) + return; + } + DCHECK(m_device); + m_head.mime_type = m_mimeType; + m_head.charset = m_charset; + m_client->OnReceiveResponse(m_head); + m_client->OnStartLoadingResponseBody(std::move(m_pipe.consumer_handle)); + + readAvailableData(); + } + void notifyCanceled() override + { + OnTransferComplete(MOJO_RESULT_CANCELLED); + } + void notifyAborted() override + { + notifyStartFailure(net::ERR_ABORTED); + } + void notifyStartFailure(int error) override + { + m_head.response_start = base::TimeTicks::Now(); + std::string headers; + switch (error) { + case net::ERR_INVALID_URL: + headers = "HTTP/1.1 400 Bad Request\n"; + break; + case net::ERR_FILE_NOT_FOUND: + headers = "HTTP/1.1 404 Not Found\n"; + break; + case net::ERR_ABORTED: + headers = "HTTP/1.1 503 Request Aborted\n"; + break; + case net::ERR_ACCESS_DENIED: + headers = "HTTP/1.1 403 Forbidden\n"; + break; + case net::ERR_FAILED: + headers = "HTTP/1.1 400 Request Failed\n"; + break; + default: + headers = "HTTP/1.1 500 Internal Error\n"; + break; + } + m_head.headers = base::MakeRefCounted<net::HttpResponseHeaders>(net::HttpUtil::AssembleRawHeaders(headers)); + m_head.encoded_data_length = m_head.headers->raw_headers().length(); + m_head.content_length = m_head.encoded_body_length = -1; + m_client->OnReceiveResponse(m_head); + CompleteWithFailure(net::Error(error)); + } + void notifyReadyRead() override + { + m_taskRunner->PostTask(FROM_HERE, + base::BindOnce(&CustomURLLoader::readAvailableData, base::Unretained(this))); + } + void readAvailableData() + { + if (m_error) { + CompleteWithFailure(net::Error(m_error)); + return; + } + if (!m_device) { + CompleteWithFailure(net::ERR_FAILED); + return; + } + char buffer[2048]; + do { + int read_size = m_device->read(buffer, 2048); + if (m_error) { + CompleteWithFailure(net::Error(m_error)); + return; + } + if (read_size > 0) { + uint32_t read_bytes = read_size; + m_pipe.producer_handle->WriteData(buffer, &read_bytes, MOJO_WRITE_DATA_FLAG_NONE); + m_totalBytesRead += read_bytes; + } else if (read_size < 0 && !m_device->atEnd()) { + CompleteWithFailure(net::ERR_FAILED); + return; + } + } while (m_device->bytesAvailable()); + m_client->OnTransferSizeUpdated(m_totalBytesRead); + if (m_device->atEnd()) + OnTransferComplete(MOJO_RESULT_OK); + } + base::TaskRunner *taskRunner() override + { + return m_taskRunner.get(); + } + + scoped_refptr<base::TaskRunner> m_taskRunner; + scoped_refptr<URLRequestCustomJobProxy> m_proxy; + + mojo::Binding<network::mojom::URLLoader> m_binding; + network::mojom::URLLoaderClientPtr m_client; + mojo::DataPipe m_pipe; + + network::ResourceRequest m_request; + network::ResourceResponseHead m_head; + qint64 m_totalBytesRead = 0; + bool m_corsEnabled; + + DISALLOW_COPY_AND_ASSIGN(CustomURLLoader); +}; + +class CustomURLLoaderFactory : public network::mojom::URLLoaderFactory { +public: + CustomURLLoaderFactory(ProfileAdapter *profileAdapter) + : m_taskRunner(base::CreateSequencedTaskRunner({ content::BrowserThread::IO })) + , m_profileAdapter(profileAdapter) + { + } + ~CustomURLLoaderFactory() override = default; + + // network::mojom::URLLoaderFactory: + void CreateLoaderAndStart(network::mojom::URLLoaderRequest loader, + int32_t routing_id, + int32_t request_id, + uint32_t options, + const network::ResourceRequest &request, + network::mojom::URLLoaderClientPtr client, + const net::MutableNetworkTrafficAnnotationTag &traffic_annotation) override + { + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + Q_UNUSED(routing_id); + Q_UNUSED(request_id); + Q_UNUSED(options); + Q_UNUSED(traffic_annotation); + + m_taskRunner->PostTask(FROM_HERE, + base::BindOnce(&CustomURLLoader::CreateAndStart, request, + std::move(loader), client.PassInterface(), + m_profileAdapter)); + + } + + void Clone(network::mojom::URLLoaderFactoryRequest request) override + { + m_bindings.AddBinding(this, std::move(request)); + } + + const scoped_refptr<base::SequencedTaskRunner> m_taskRunner; + mojo::BindingSet<network::mojom::URLLoaderFactory> m_bindings; + QPointer<ProfileAdapter> m_profileAdapter; + DISALLOW_COPY_AND_ASSIGN(CustomURLLoaderFactory); +}; + +} // namespace + +std::unique_ptr<network::mojom::URLLoaderFactory> CreateCustomURLLoaderFactory(ProfileAdapter *profileAdapter) +{ + return std::make_unique<CustomURLLoaderFactory>(profileAdapter); +} + +} // namespace QtWebEngineCore + diff --git a/src/core/net/custom_url_loader_factory.h b/src/core/net/custom_url_loader_factory.h new file mode 100644 index 000000000..58adf4b79 --- /dev/null +++ b/src/core/net/custom_url_loader_factory.h @@ -0,0 +1,69 @@ +/**************************************************************************** +** +** Copyright (C) 2019 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$ +** +****************************************************************************/ + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#ifndef CUSTOM_URL_LOADER_FACTORY_H_ +#define CUSTOM_URL_LOADER_FACTORY_H_ + +#include <memory> + +namespace network { +namespace mojom { +class URLLoaderFactory; +} // namespace mojom +} // namespace network + +namespace QtWebEngineCore { +class ProfileAdapter; + +std::unique_ptr<network::mojom::URLLoaderFactory> CreateCustomURLLoaderFactory(ProfileAdapter *profileAdapter); + +} // namespace QtWebEngineCore + +#endif // CUSTOM_URL_LOADER_FACTORY_H_ diff --git a/src/core/net/network_delegate_qt.cpp b/src/core/net/network_delegate_qt.cpp index 7d3801ffe..684558abb 100644 --- a/src/core/net/network_delegate_qt.cpp +++ b/src/core/net/network_delegate_qt.cpp @@ -178,8 +178,7 @@ int NetworkDelegateQt::OnBeforeURLRequest(net::URLRequest *request, net::Complet return net::OK; // try to bail out - if (!m_profileIOData->hasPageInterceptors() && (!m_profileIOData->requestInterceptor() || m_profileIOData->isInterceptorDeprecated()) && - !content::IsResourceTypeFrame(resourceType)) + if (!m_profileIOData->hasPageInterceptors() && (!m_profileIOData->requestInterceptor() || m_profileIOData->isInterceptorDeprecated())) return net::OK; auto webContentsGetter = resourceInfo->GetWebContentsGetterForRequest(); diff --git a/src/core/net/plugin_response_interceptor_url_loader_throttle.cpp b/src/core/net/plugin_response_interceptor_url_loader_throttle.cpp new file mode 100644 index 000000000..cfd011ade --- /dev/null +++ b/src/core/net/plugin_response_interceptor_url_loader_throttle.cpp @@ -0,0 +1,135 @@ +/**************************************************************************** +** +** Copyright (C) 2019 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 "plugin_response_interceptor_url_loader_throttle.h" + +#include "base/bind.h" +#include "base/task/post_task.h" +#include "content/public/browser/browser_task_traits.h" +#include "content/public/browser/browser_thread.h" +#include "content/public/browser/download_manager.h" +#include "content/public/browser/download_request_utils.h" +#include "content/public/browser/download_utils.h" +#include "content/public/common/resource_type.h" +#include "extensions/common/constants.h" +#include "extensions/common/extension.h" + +#include "extensions/extension_system_qt.h" +#include "profile_adapter.h" +#include "profile_io_data_qt.h" +#include "profile_qt.h" +#include "web_contents_delegate_qt.h" + +#include <string> + +namespace QtWebEngineCore { + +void onPdfStreamIntercepted(const GURL &original_url, std::string extension_id, int frame_tree_node_id) +{ + content::WebContents *web_contents = content::WebContents::FromFrameTreeNodeId(frame_tree_node_id); + if (!web_contents) + return; + + WebContentsDelegateQt *contentsDelegate = static_cast<WebContentsDelegateQt *>(web_contents->GetDelegate()); + if (!contentsDelegate) + return; + + WebEngineSettings *settings = contentsDelegate->webEngineSettings(); + if (!settings->testAttribute(WebEngineSettings::PdfViewerEnabled) + || !settings->testAttribute(WebEngineSettings::PluginsEnabled)) { + // If the applications has been set up to always download PDF files to open them in an + // external viewer, trigger the download. + std::unique_ptr<download::DownloadUrlParameters> params( + content::DownloadRequestUtils::CreateDownloadForWebContentsMainFrame(web_contents, original_url, + MISSING_TRAFFIC_ANNOTATION)); + content::BrowserContext::GetDownloadManager(web_contents->GetBrowserContext())->DownloadUrl(std::move(params)); + return; + } + + // The URL passes the original pdf resource url, that will be requested + // by the pdf viewer extension page. + content::NavigationController::LoadURLParams params( + GURL(base::StringPrintf("%s://%s/index.html?%s", extensions::kExtensionScheme, + extension_id.c_str(), original_url.spec().c_str()))); + + params.frame_tree_node_id = frame_tree_node_id; + web_contents->GetController().LoadURLWithParams(params); +} + + +PluginResponseInterceptorURLLoaderThrottle::PluginResponseInterceptorURLLoaderThrottle( + content::ResourceContext *resource_context, int resource_type, int frame_tree_node_id) + : m_resource_context(resource_context), m_resource_type(resource_type), m_frame_tree_node_id(frame_tree_node_id) +{} + +PluginResponseInterceptorURLLoaderThrottle::PluginResponseInterceptorURLLoaderThrottle( + content::BrowserContext *browser_context, int resource_type, int frame_tree_node_id) + : m_browser_context(browser_context), m_resource_type(resource_type), m_frame_tree_node_id(frame_tree_node_id) +{} + +void PluginResponseInterceptorURLLoaderThrottle::WillProcessResponse(const GURL &response_url, + network::ResourceResponseHead *response_head, + bool *defer) +{ + Q_UNUSED(defer); + if (content::download_utils::MustDownload(response_url, response_head->headers.get(), response_head->mime_type)) + return; + + if (m_resource_context) { + DCHECK_CURRENTLY_ON(content::BrowserThread::IO); + } else { + DCHECK(m_browser_context); + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + } + + std::string extension_id; + // FIXME: We should use extensions::InfoMap in the future: + if (response_head->mime_type == "application/pdf") + extension_id = extension_misc::kPdfExtensionId; + if (extension_id.empty()) + return; + + base::PostTaskWithTraits(FROM_HERE, {content::BrowserThread::UI}, + base::BindOnce(&onPdfStreamIntercepted, + response_url, + extension_id, + m_frame_tree_node_id)); +} + +} // namespace QtWebEngineCore diff --git a/src/core/renderer_host/resource_dispatcher_host_delegate_qt.h b/src/core/net/plugin_response_interceptor_url_loader_throttle.h index 3039fd03e..82384e11e 100644 --- a/src/core/renderer_host/resource_dispatcher_host_delegate_qt.h +++ b/src/core/net/plugin_response_interceptor_url_loader_throttle.h @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2018 The Qt Company Ltd. +** Copyright (C) 2019 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the QtWebEngine module of the Qt Toolkit. @@ -37,41 +37,40 @@ ** ****************************************************************************/ -#ifndef RESOURCE_DISPATCHER_HOST_DELEGATE_QT_H -#define RESOURCE_DISPATCHER_HOST_DELEGATE_QT_H +#ifndef PLUGIN_RESPONSE_INTERCEPTOR_URL_LOADER_THROTTLE_H_ +#define PLUGIN_RESPONSE_INTERCEPTOR_URL_LOADER_THROTTLE_H_ -#include "content/public/browser/resource_dispatcher_host_delegate.h" -#include "extensions/buildflags/buildflags.h" +#include "base/macros.h" +#include "content/public/common/url_loader_throttle.h" -#include "web_contents_adapter_client.h" +namespace content { +class BrowserContext; +class ResourceContext; +} namespace QtWebEngineCore { -class ResourceDispatcherHostDelegateQt : public content::ResourceDispatcherHostDelegate { +class PluginResponseInterceptorURLLoaderThrottle : public content::URLLoaderThrottle +{ public: - // If the stream will be rendered in a BrowserPlugin, |payload| will contain - // the data that should be given to the old ResourceHandler to forward to the - // renderer process. - bool ShouldInterceptResourceAsStream(net::URLRequest *request, - const std::string &mime_type, - GURL *origin, - std::string *payload) override; + PluginResponseInterceptorURLLoaderThrottle(content::ResourceContext *resource_context, + int resource_type, int frame_tree_node_id); + PluginResponseInterceptorURLLoaderThrottle(content::BrowserContext *browser_context, + int resource_type, int frame_tree_node_id); + ~PluginResponseInterceptorURLLoaderThrottle() override = default; - // Informs the delegate that a Stream was created. The Stream can be read from - // the blob URL of the Stream, but can only be read once. - void OnStreamCreated(net::URLRequest *request, - std::unique_ptr<content::StreamInfo> stream) override; private: -#if BUILDFLAG(ENABLE_EXTENSIONS) - struct StreamTargetInfo { - std::string extension_id; - std::string view_id; - }; - std::map<net::URLRequest *, StreamTargetInfo> stream_target_info_; -#endif + // content::URLLoaderThrottle overrides; + void WillProcessResponse(const GURL &response_url, network::ResourceResponseHead *response_head, bool *defer) override; + content::ResourceContext *m_resource_context = nullptr; + content::BrowserContext *m_browser_context = nullptr; + const int m_resource_type; + const int m_frame_tree_node_id; + + DISALLOW_COPY_AND_ASSIGN(PluginResponseInterceptorURLLoaderThrottle); }; } // namespace QtWebEngineCore -#endif // RESOURCE_DISPATCHER_HOST_DELEGATE_QT_H +#endif // PLUGIN_RESPONSE_INTERCEPTOR_URL_LOADER_THROTTLE_H_ diff --git a/src/core/net/proxy_config_monitor.cpp b/src/core/net/proxy_config_monitor.cpp new file mode 100644 index 000000000..818b6cb7f --- /dev/null +++ b/src/core/net/proxy_config_monitor.cpp @@ -0,0 +1,125 @@ +/**************************************************************************** +** +** Copyright (C) 2019 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$ +** +****************************************************************************/ + +// originally based on chrome/browser/net/proxy_config_monitor.cc +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "profile_qt.h" +#include "proxy_config_monitor.h" +#include "proxy_config_service_qt.h" + +#include "base/strings/utf_string_conversions.h" +#include "base/task/post_task.h" +#include "build/build_config.h" +#include "components/proxy_config/pref_proxy_config_tracker_impl.h" +#include "content/public/browser/browser_task_traits.h" +#include "content/public/browser/browser_thread.h" +#include "mojo/public/cpp/bindings/associated_interface_ptr.h" +#include "mojo/public/cpp/bindings/pending_remote.h" +#include "net/proxy_resolution/proxy_resolution_service.h" +#include "services/network/public/mojom/network_context.mojom.h" + +#include <utility> + +using content::BrowserThread; + +ProxyConfigMonitor::ProxyConfigMonitor(PrefService *prefs) +{ + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + + proxy_config_service_.reset( + new ProxyConfigServiceQt( + prefs, base::CreateSingleThreadTaskRunnerWithTraits({ BrowserThread::UI }))); + + proxy_config_service_->AddObserver(this); +} + +ProxyConfigMonitor::~ProxyConfigMonitor() +{ + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI) + || !BrowserThread::IsThreadInitialized(BrowserThread::UI)); + proxy_config_service_->RemoveObserver(this); +} + +void ProxyConfigMonitor::AddToNetworkContextParams( + network::mojom::NetworkContextParams *network_context_params) +{ + network::mojom::ProxyConfigClientPtr proxy_config_client; + network_context_params->proxy_config_client_request = mojo::MakeRequest(&proxy_config_client); + proxy_config_client_set_.AddPtr(std::move(proxy_config_client)); + + poller_binding_set_.AddBinding( + this, mojo::MakeRequest(&network_context_params->proxy_config_poller_client)); + + net::ProxyConfigWithAnnotation proxy_config; + net::ProxyConfigService::ConfigAvailability availability = + proxy_config_service_->GetLatestProxyConfig(&proxy_config); + if (availability != net::ProxyConfigService::CONFIG_PENDING) + network_context_params->initial_proxy_config = proxy_config; +} + +void ProxyConfigMonitor::OnProxyConfigChanged( + const net::ProxyConfigWithAnnotation &config, + net::ProxyConfigService::ConfigAvailability availability) +{ + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI) + || !BrowserThread::IsThreadInitialized(BrowserThread::UI)); + proxy_config_client_set_.ForAllPtrs( + [config, availability](network::mojom::ProxyConfigClient *proxy_config_client) { + switch (availability) { + case net::ProxyConfigService::CONFIG_VALID: + proxy_config_client->OnProxyConfigUpdated(config); + break; + case net::ProxyConfigService::CONFIG_UNSET: + proxy_config_client->OnProxyConfigUpdated( + net::ProxyConfigWithAnnotation::CreateDirect()); + break; + case net::ProxyConfigService::CONFIG_PENDING: + NOTREACHED(); + break; + } + }); +} + +void ProxyConfigMonitor::OnLazyProxyConfigPoll() +{ + proxy_config_service_->OnLazyPoll(); +} diff --git a/src/core/net/proxy_config_monitor.h b/src/core/net/proxy_config_monitor.h new file mode 100644 index 000000000..23f073a84 --- /dev/null +++ b/src/core/net/proxy_config_monitor.h @@ -0,0 +1,104 @@ +/**************************************************************************** +** +** Copyright (C) 2019 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$ +** +****************************************************************************/ + +// originally based on chrome/browser/net/proxy_config_monitor.h +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef PROXY_CONFIG_MONITOR_H +#define PROXY_CONFIG_MONITOR_H + +#include <memory> +#include <string> + +#include "base/macros.h" +#include "build/buildflag.h" +#include "extensions/buildflags/buildflags.h" +#include "mojo/public/cpp/bindings/binding_set.h" +#include "mojo/public/cpp/bindings/interface_ptr_set.h" +#include "net/proxy_resolution/proxy_config_service.h" +#include "services/network/public/mojom/network_context.mojom-forward.h" +#include "services/network/public/mojom/network_service.mojom-forward.h" +#include "services/network/public/mojom/proxy_config.mojom-forward.h" +#include "services/network/public/mojom/proxy_config_with_annotation.mojom.h" + +namespace net { +class ProxyConfigWithAnnotation; +} + +class PrefService; +class ProxyConfigServiceQt; + +// Tracks the ProxyConfig to use, and passes any updates to a NetworkContext's +// ProxyConfigClient. This also responds to errors related to proxy settings +// from the NetworkContext, and forwards them any listening Chrome extensions +// associated with the profile. +class ProxyConfigMonitor : public net::ProxyConfigService::Observer, + public network::mojom::ProxyConfigPollerClient +{ +public: + explicit ProxyConfigMonitor(PrefService *prefs = nullptr); + + ~ProxyConfigMonitor() override; + + // Populates proxy-related fields of |network_context_params|. Updated + // ProxyConfigs will be sent to a NetworkContext created with those params + // whenever the configuration changes. Can be called more than once to inform + // multiple NetworkContexts of proxy changes. + void AddToNetworkContextParams(network::mojom::NetworkContextParams *network_context_params); + +private: + // net::ProxyConfigService::Observer implementation: + void OnProxyConfigChanged(const net::ProxyConfigWithAnnotation &config, + net::ProxyConfigService::ConfigAvailability availability) override; + + // network::mojom::ProxyConfigPollerClient implementation: + void OnLazyProxyConfigPoll() override; + + std::unique_ptr<ProxyConfigServiceQt> proxy_config_service_; + + mojo::BindingSet<network::mojom::ProxyConfigPollerClient> poller_binding_set_; + + mojo::InterfacePtrSet<network::mojom::ProxyConfigClient> proxy_config_client_set_; + + DISALLOW_COPY_AND_ASSIGN(ProxyConfigMonitor); +}; + +#endif // !PROXY_CONFIG_MONITOR_H diff --git a/src/core/net/proxy_config_service_qt.cpp b/src/core/net/proxy_config_service_qt.cpp index 8016c7e83..59884961d 100644 --- a/src/core/net/proxy_config_service_qt.cpp +++ b/src/core/net/proxy_config_service_qt.cpp @@ -46,8 +46,9 @@ #include "proxy_config_service_qt.h" #include "base/bind.h" -#include "content/public/browser/browser_thread.h" #include "components/proxy_config/pref_proxy_config_tracker_impl.h" +#include "content/public/browser/browser_thread.h" +#include "net/proxy_resolution/proxy_resolution_service.h" using content::BrowserThread; @@ -69,34 +70,40 @@ net::ProxyServer ProxyConfigServiceQt::fromQNetworkProxy(const QNetworkProxy &qt } } -ProxyConfigServiceQt::ProxyConfigServiceQt(std::unique_ptr<ProxyConfigService> baseService, - const net::ProxyConfigWithAnnotation &initialConfig, - ProxyPrefs::ConfigState initialState) - : m_baseService(baseService.release()) +ProxyConfigServiceQt::ProxyConfigServiceQt(PrefService *prefService, + const scoped_refptr<base::SingleThreadTaskRunner> &taskRunner) + : m_baseService(net::ProxyResolutionService::CreateSystemProxyConfigService(taskRunner)) , m_usesSystemConfiguration(false) , m_registeredObserver(false) - , m_prefConfig(initialConfig) - , m_perfState(initialState) -{} + , m_prefState(prefService + ? PrefProxyConfigTrackerImpl::ReadPrefConfig(prefService, &m_prefConfig) + : ProxyPrefs::CONFIG_UNSET) +{ + DETACH_FROM_SEQUENCE(m_sequenceChecker); +} ProxyConfigServiceQt::~ProxyConfigServiceQt() { + DCHECK_CALLED_ON_VALID_SEQUENCE(m_sequenceChecker); if (m_registeredObserver && m_baseService.get()) m_baseService->RemoveObserver(this); } void ProxyConfigServiceQt::AddObserver(net::ProxyConfigService::Observer *observer) { + DCHECK_CALLED_ON_VALID_SEQUENCE(m_sequenceChecker); m_observers.AddObserver(observer); } void ProxyConfigServiceQt::RemoveObserver(net::ProxyConfigService::Observer *observer) { + DCHECK_CALLED_ON_VALID_SEQUENCE(m_sequenceChecker); m_observers.RemoveObserver(observer); } net::ProxyConfigService::ConfigAvailability ProxyConfigServiceQt::GetLatestProxyConfig(net::ProxyConfigWithAnnotation *config) { + DCHECK_CALLED_ON_VALID_SEQUENCE(m_sequenceChecker); m_usesSystemConfiguration = QNetworkProxyFactory::usesSystemConfiguration(); if (m_usesSystemConfiguration) { // Use Chromium's base service to retrieve system settings @@ -106,7 +113,7 @@ net::ProxyConfigService::ConfigAvailability ProxyConfigServiceQt::GetLatestProxy systemAvailability = m_baseService->GetLatestProxyConfig(&systemConfig); ProxyPrefs::ConfigState configState; systemAvailability = PrefProxyConfigTrackerImpl::GetEffectiveProxyConfig( - m_perfState, m_prefConfig, systemAvailability, systemConfig, + m_prefState, m_prefConfig, systemAvailability, systemConfig, false, &configState, config); RegisterObserver(); return systemAvailability; @@ -151,7 +158,7 @@ net::ProxyConfigService::ConfigAvailability ProxyConfigServiceQt::GetLatestProxy void ProxyConfigServiceQt::OnLazyPoll() { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + DCHECK_CALLED_ON_VALID_SEQUENCE(m_sequenceChecker); // We need to update if // - setUseSystemConfiguration() was called in between @@ -168,7 +175,7 @@ void ProxyConfigServiceQt::OnLazyPoll() // Called when the base service changed void ProxyConfigServiceQt::OnProxyConfigChanged(const net::ProxyConfigWithAnnotation &config, ConfigAvailability availability) { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + DCHECK_CALLED_ON_VALID_SEQUENCE(m_sequenceChecker); Q_UNUSED(config); if (!m_usesSystemConfiguration) @@ -180,6 +187,7 @@ void ProxyConfigServiceQt::OnProxyConfigChanged(const net::ProxyConfigWithAnnota // Update our observers void ProxyConfigServiceQt::Update() { + DCHECK_CALLED_ON_VALID_SEQUENCE(m_sequenceChecker); net::ProxyConfigWithAnnotation actual_config; ConfigAvailability availability = GetLatestProxyConfig(&actual_config); if (availability == CONFIG_PENDING) @@ -193,7 +201,7 @@ void ProxyConfigServiceQt::Update() // in the constructor. void ProxyConfigServiceQt::RegisterObserver() { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + DCHECK_CALLED_ON_VALID_SEQUENCE(m_sequenceChecker); if (!m_registeredObserver && m_baseService.get()) { m_baseService->AddObserver(this); m_registeredObserver = true; diff --git a/src/core/net/proxy_config_service_qt.h b/src/core/net/proxy_config_service_qt.h index 4d8619055..c0928bc03 100644 --- a/src/core/net/proxy_config_service_qt.h +++ b/src/core/net/proxy_config_service_qt.h @@ -42,6 +42,7 @@ #include "base/memory/ref_counted.h" #include "base/observer_list.h" +#include "base/single_thread_task_runner.h" #include "net/proxy_resolution/proxy_config.h" #include "net/proxy_resolution/proxy_config_service.h" @@ -50,6 +51,8 @@ #include <QNetworkProxy> +class PrefService; + class ProxyConfigServiceQt : public net::ProxyConfigService , public net::ProxyConfigService::Observer @@ -57,9 +60,8 @@ class ProxyConfigServiceQt public: static net::ProxyServer fromQNetworkProxy(const QNetworkProxy &); - explicit ProxyConfigServiceQt(std::unique_ptr<ProxyConfigService> baseService, - const net::ProxyConfigWithAnnotation &initialConfig, - ProxyPrefs::ConfigState initialState); + explicit ProxyConfigServiceQt(PrefService *prefService, + const scoped_refptr<base::SingleThreadTaskRunner> &taskRunner); ~ProxyConfigServiceQt() override; // ProxyConfigService implementation: @@ -92,7 +94,9 @@ private: // Configuration as defined by prefs. net::ProxyConfigWithAnnotation m_prefConfig; - ProxyPrefs::ConfigState m_perfState; + ProxyPrefs::ConfigState m_prefState; + + SEQUENCE_CHECKER(m_sequenceChecker); DISALLOW_COPY_AND_ASSIGN(ProxyConfigServiceQt); }; diff --git a/src/core/net/proxying_restricted_cookie_manager_qt.cpp b/src/core/net/proxying_restricted_cookie_manager_qt.cpp new file mode 100644 index 000000000..b6abeb567 --- /dev/null +++ b/src/core/net/proxying_restricted_cookie_manager_qt.cpp @@ -0,0 +1,231 @@ +/**************************************************************************** +** +** Copyright (C) 2019 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$ +** +****************************************************************************/ + +// originally based on android_webview/browser/network_service/aw_proxying_restricted_cookie_manager.cc: +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "proxying_restricted_cookie_manager_qt.h" + +#include "api/qwebenginecookiestore.h" +#include "api/qwebenginecookiestore_p.h" +#include "profile_adapter.h" +#include "profile_qt.h" +#include "type_conversion.h" + +#include "base/memory/ptr_util.h" +#include "base/task/post_task.h" +#include "content/public/browser/browser_task_traits.h" +#include "content/public/browser/browser_thread.h" +#include "mojo/public/cpp/bindings/strong_binding.h" + +namespace QtWebEngineCore { + +class ProxyingRestrictedCookieManagerListenerQt : public network::mojom::CookieChangeListener { +public: + ProxyingRestrictedCookieManagerListenerQt(const GURL &url, + const GURL &site_for_cookies, + base::WeakPtr<ProxyingRestrictedCookieManagerQt> restricted_cookie_manager, + network::mojom::CookieChangeListenerPtr client_listener) + : url_(url) + , site_for_cookies_(site_for_cookies) + , restricted_cookie_manager_(restricted_cookie_manager) + , client_listener_(std::move(client_listener)) + {} + + void OnCookieChange(const net::CanonicalCookie &cookie, network::mojom::CookieChangeCause cause) override + { + if (restricted_cookie_manager_ && restricted_cookie_manager_->allowCookies(url_, site_for_cookies_)) + client_listener_->OnCookieChange(cookie, cause); + } + +private: + const GURL url_; + const GURL site_for_cookies_; + base::WeakPtr<ProxyingRestrictedCookieManagerQt> restricted_cookie_manager_; + network::mojom::CookieChangeListenerPtr client_listener_; +}; + +// static +void ProxyingRestrictedCookieManagerQt::CreateAndBind(ProfileIODataQt *profileIoData, + network::mojom::RestrictedCookieManagerPtrInfo underlying_rcm, + bool is_service_worker, + int process_id, + int frame_id, + network::mojom::RestrictedCookieManagerRequest request) +{ + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + + base::PostTaskWithTraits(FROM_HERE, {content::BrowserThread::IO}, + base::BindOnce(&ProxyingRestrictedCookieManagerQt::CreateAndBindOnIoThread, + profileIoData, + std::move(underlying_rcm), + is_service_worker, process_id, frame_id, + std::move(request))); +} + + +// static +void ProxyingRestrictedCookieManagerQt::CreateAndBindOnIoThread(ProfileIODataQt *profileIoData, + network::mojom::RestrictedCookieManagerPtrInfo underlying_rcm, + bool is_service_worker, + int process_id, + int frame_id, + network::mojom::RestrictedCookieManagerRequest request) +{ + DCHECK_CURRENTLY_ON(content::BrowserThread::IO); + + auto wrapper = base::WrapUnique(new ProxyingRestrictedCookieManagerQt( + profileIoData->getWeakPtrOnIOThread(), + network::mojom::RestrictedCookieManagerPtr(std::move(underlying_rcm)), + is_service_worker, process_id, frame_id)); + mojo::MakeStrongBinding(std::move(wrapper), std::move(request)); +} + +ProxyingRestrictedCookieManagerQt::ProxyingRestrictedCookieManagerQt(base::WeakPtr<ProfileIODataQt> profileIoData, + network::mojom::RestrictedCookieManagerPtr underlyingRestrictedCookieManager, + bool is_service_worker, + int32_t process_id, + int32_t frame_id) + : m_profileIoData(std::move(profileIoData)) + , underlying_restricted_cookie_manager_(std::move(underlyingRestrictedCookieManager)) + , is_service_worker_(is_service_worker) + , process_id_(process_id) + , frame_id_(frame_id) + , weak_factory_(this) +{ + DCHECK_CURRENTLY_ON(content::BrowserThread::IO); +} + +ProxyingRestrictedCookieManagerQt::~ProxyingRestrictedCookieManagerQt() +{ + DCHECK_CURRENTLY_ON(content::BrowserThread::IO); +} + +void ProxyingRestrictedCookieManagerQt::GetAllForUrl(const GURL &url, + const GURL &site_for_cookies, + network::mojom::CookieManagerGetOptionsPtr options, + GetAllForUrlCallback callback) +{ + DCHECK_CURRENTLY_ON(content::BrowserThread::IO); + + if (allowCookies(url, site_for_cookies)) { + underlying_restricted_cookie_manager_->GetAllForUrl(url, site_for_cookies, std::move(options), std::move(callback)); + } else { + std::move(callback).Run(std::vector<net::CanonicalCookie>()); + } +} + +void ProxyingRestrictedCookieManagerQt::SetCanonicalCookie(const net::CanonicalCookie &cookie, + const GURL &url, + const GURL &site_for_cookies, + SetCanonicalCookieCallback callback) +{ + DCHECK_CURRENTLY_ON(content::BrowserThread::IO); + + if (allowCookies(url, site_for_cookies)) { + underlying_restricted_cookie_manager_->SetCanonicalCookie(cookie, url, site_for_cookies, std::move(callback)); + } else { + std::move(callback).Run(false); + } +} + +void ProxyingRestrictedCookieManagerQt::AddChangeListener(const GURL &url, + const GURL &site_for_cookies, + network::mojom::CookieChangeListenerPtr listener, + AddChangeListenerCallback callback) +{ + DCHECK_CURRENTLY_ON(content::BrowserThread::IO); + + network::mojom::CookieChangeListenerPtr proxy_listener_ptr; + auto proxy_listener = + std::make_unique<ProxyingRestrictedCookieManagerListenerQt>( + url, site_for_cookies, weak_factory_.GetWeakPtr(), + std::move(listener)); + + mojo::MakeStrongBinding(std::move(proxy_listener), + mojo::MakeRequest(&proxy_listener_ptr)); + + underlying_restricted_cookie_manager_->AddChangeListener(url, site_for_cookies, std::move(proxy_listener_ptr), std::move(callback)); +} + +void ProxyingRestrictedCookieManagerQt::SetCookieFromString(const GURL &url, + const GURL &site_for_cookies, + const std::string &cookie, + SetCookieFromStringCallback callback) +{ + DCHECK_CURRENTLY_ON(content::BrowserThread::IO); + + if (allowCookies(url, site_for_cookies)) { + underlying_restricted_cookie_manager_->SetCookieFromString(url, site_for_cookies, cookie, std::move(callback)); + } else { + std::move(callback).Run(); + } +} + +void ProxyingRestrictedCookieManagerQt::GetCookiesString(const GURL &url, + const GURL &site_for_cookies, + GetCookiesStringCallback callback) +{ + DCHECK_CURRENTLY_ON(content::BrowserThread::IO); + + if (allowCookies(url, site_for_cookies)) { + underlying_restricted_cookie_manager_->GetCookiesString(url, site_for_cookies, std::move(callback)); + } else { + std::move(callback).Run(""); + } +} + +void ProxyingRestrictedCookieManagerQt::CookiesEnabledFor(const GURL &url, + const GURL &site_for_cookies, + CookiesEnabledForCallback callback) +{ + DCHECK_CURRENTLY_ON(content::BrowserThread::IO); + std::move(callback).Run(allowCookies(url, site_for_cookies)); +} + +bool ProxyingRestrictedCookieManagerQt::allowCookies(const GURL &url, const GURL &site_for_cookies) const +{ + if (!m_profileIoData) + return false; + return m_profileIoData->canGetCookies(toQt(site_for_cookies), toQt(url)); +} + +} // namespace QtWebEngineCore diff --git a/src/core/net/proxying_restricted_cookie_manager_qt.h b/src/core/net/proxying_restricted_cookie_manager_qt.h new file mode 100644 index 000000000..7a9f0e3ab --- /dev/null +++ b/src/core/net/proxying_restricted_cookie_manager_qt.h @@ -0,0 +1,121 @@ +/**************************************************************************** +** +** Copyright (C) 2019 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 PROXYING_RESTRICTED_COOKIE_MANAGER_QT_H +#define PROXYING_RESTRICTED_COOKIE_MANAGER_QT_H + +#include "base/macros.h" +#include "base/memory/weak_ptr.h" +#include "services/network/public/mojom/restricted_cookie_manager.mojom.h" +#include "url/gurl.h" + +namespace QtWebEngineCore { + +class ProfileIODataQt; + +class ProxyingRestrictedCookieManagerQt : public network::mojom::RestrictedCookieManager +{ +public: + // Expects to be called on the UI thread. + static void CreateAndBind(ProfileIODataQt *profileIoData, + network::mojom::RestrictedCookieManagerPtrInfo underlying_rcm, + bool is_service_worker, + int process_id, + int frame_id, + network::mojom::RestrictedCookieManagerRequest request); + + ~ProxyingRestrictedCookieManagerQt() override; + + // network::mojom::RestrictedCookieManager interface: + void GetAllForUrl(const GURL &url, + const GURL &site_for_cookies, + network::mojom::CookieManagerGetOptionsPtr options, + GetAllForUrlCallback callback) override; + void SetCanonicalCookie(const net::CanonicalCookie& cookie, + const GURL &url, + const GURL &site_for_cookies, + SetCanonicalCookieCallback callback) override; + void AddChangeListener(const GURL &url, + const GURL &site_for_cookies, + network::mojom::CookieChangeListenerPtr listener, + AddChangeListenerCallback callback) override; + void SetCookieFromString(const GURL &url, + const GURL &site_for_cookies, + const std::string &cookie, + SetCookieFromStringCallback callback) override; + void GetCookiesString(const GURL &url, + const GURL &site_for_cookies, + GetCookiesStringCallback callback) override; + + void CookiesEnabledFor(const GURL &url, + const GURL &site_for_cookies, + CookiesEnabledForCallback callback) override; + + // Internal: + bool allowCookies(const GURL &url, const GURL &site_for_cookies) const; + +private: + ProxyingRestrictedCookieManagerQt(base::WeakPtr<ProfileIODataQt> profileIoData, + network::mojom::RestrictedCookieManagerPtr underlyingRestrictedCookieManager, + bool is_service_worker, + int32_t process_id, + int32_t frame_id); + + static void CreateAndBindOnIoThread(ProfileIODataQt *profileIoData, + network::mojom::RestrictedCookieManagerPtrInfo underlying_rcm, + bool is_service_worker, + int process_id, + int frame_id, + network::mojom::RestrictedCookieManagerRequest request); + + base::WeakPtr<ProfileIODataQt> m_profileIoData; + + network::mojom::RestrictedCookieManagerPtr underlying_restricted_cookie_manager_; + bool is_service_worker_; + int process_id_; + int frame_id_; + + base::WeakPtrFactory<ProxyingRestrictedCookieManagerQt> weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(ProxyingRestrictedCookieManagerQt); +}; + +} // namespace QtWebEngineCore + +#endif // PROXYING_RESTRICTED_COOKIE_MANAGER_QT_H diff --git a/src/core/net/proxying_url_loader_factory_qt.cpp b/src/core/net/proxying_url_loader_factory_qt.cpp new file mode 100644 index 000000000..173e19eeb --- /dev/null +++ b/src/core/net/proxying_url_loader_factory_qt.cpp @@ -0,0 +1,562 @@ +/**************************************************************************** +** +** Copyright (C) 2019 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 "proxying_url_loader_factory_qt.h" + +#include <utility> + +#include "base/bind.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/stringprintf.h" +#include "base/task/post_task.h" +#include "components/safe_browsing/common/safebrowsing_constants.h" +#include "content/browser/web_contents/web_contents_impl.h" +#include "content/public/browser/browser_task_traits.h" +#include "content/public/browser/browser_thread.h" +#include "content/public/browser/global_request_id.h" +#include "content/public/browser/render_process_host.h" +#include "content/public/browser/resource_request_info.h" +#include "content/public/browser/web_contents.h" +#include "content/public/common/content_constants.h" +#include "content/public/common/url_utils.h" +#include "net/base/load_flags.h" +#include "net/http/http_status_code.h" +#include "net/http/http_util.h" + +#include "api/qwebengineurlrequestinfo_p.h" +#include "net/url_request_custom_job_proxy.h" +#include "net/url_request_notification.h" +#include "profile_io_data_qt.h" +#include "type_conversion.h" +#include "web_contents_adapter_client.h" +#include "web_contents_view_qt.h" +#include <QVariant> + +// originally based on aw_proxying_url_loader_factory.cc: +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +namespace QtWebEngineCore { + +extern WebContentsAdapterClient::NavigationType pageTransitionToNavigationType(ui::PageTransition transition); + +static QWebEngineUrlRequestInfo::ResourceType toQt(content::ResourceType resourceType) +{ + if (resourceType >= content::ResourceType::kMainFrame && resourceType <= content::ResourceType::kMaxValue) + return static_cast<QWebEngineUrlRequestInfo::ResourceType>(resourceType); + return QWebEngineUrlRequestInfo::ResourceTypeUnknown; +} + +static QWebEngineUrlRequestInfo::NavigationType toQt(WebContentsAdapterClient::NavigationType navigationType) +{ + return static_cast<QWebEngineUrlRequestInfo::NavigationType>(navigationType); +} + +// Handles intercepted, in-progress requests/responses, so that they can be +// controlled and modified accordingly. +class InterceptedRequest : public network::mojom::URLLoader + , public network::mojom::URLLoaderClient +{ +public: + InterceptedRequest(int process_id, uint64_t request_id, int32_t routing_id, uint32_t options, + const network::ResourceRequest &request, + const net::MutableNetworkTrafficAnnotationTag &traffic_annotation, + ProfileIODataQt *profileData, + network::mojom::URLLoaderRequest loader_request, network::mojom::URLLoaderClientPtr client, + network::mojom::URLLoaderFactoryPtr target_factory); + ~InterceptedRequest() override; + + void Restart(); + void InterceptOnUIThread(); + + // network::mojom::URLLoaderClient + void OnReceiveResponse(const network::ResourceResponseHead &head) override; + void OnReceiveRedirect(const net::RedirectInfo &redirect_info, const network::ResourceResponseHead &head) override; + void OnUploadProgress(int64_t current_position, int64_t total_size, OnUploadProgressCallback callback) override; + void OnReceiveCachedMetadata(mojo_base::BigBuffer data) override; + void OnTransferSizeUpdated(int32_t transfer_size_diff) override; + void OnStartLoadingResponseBody(mojo::ScopedDataPipeConsumerHandle body) override; + void OnComplete(const network::URLLoaderCompletionStatus &status) override; + + // network::mojom::URLLoader + void FollowRedirect(const std::vector<std::string> &removed_headers, + const net::HttpRequestHeaders &modified_headers, const base::Optional<GURL> &new_url) override; + void ProceedWithResponse() override; + void SetPriority(net::RequestPriority priority, int32_t intra_priority_value) override; + void PauseReadingBodyFromNet() override; + void ResumeReadingBodyFromNet() override; + + void ContinueAfterIntercept(); + +private: + // This is called when the original URLLoaderClient has a connection error. + void OnURLLoaderClientError(); + + // This is called when the original URLLoader has a connection error. + void OnURLLoaderError(uint32_t custom_reason, const std::string &description); + + // Call OnComplete on |target_client_|. If |wait_for_loader_error| is true + // then this object will wait for |proxied_loader_binding_| to have a + // connection error before destructing. + void CallOnComplete(const network::URLLoaderCompletionStatus &status, bool wait_for_loader_error); + + void SendErrorAndCompleteImmediately(int error_code); + + const int process_id_; + const uint64_t request_id_; + const int32_t routing_id_; + const uint32_t options_; + bool input_stream_previously_failed_ = false; + bool request_was_redirected_ = false; + + // If the |target_loader_| called OnComplete with an error this stores it. + // That way the destructor can send it to OnReceivedError if safe browsing + // error didn't occur. + int error_status_ = net::OK; + GURL m_originalUrl; + + network::ResourceRequest request_; + network::ResourceResponseHead current_response_; + + const net::MutableNetworkTrafficAnnotationTag traffic_annotation_; + + ProfileIODataQt *m_profileData; + mojo::Binding<network::mojom::URLLoader> proxied_loader_binding_; + network::mojom::URLLoaderClientPtr target_client_; + + mojo::Binding<network::mojom::URLLoaderClient> proxied_client_binding_; + network::mojom::URLLoaderPtr target_loader_; + network::mojom::URLLoaderFactoryPtr target_factory_; + + base::WeakPtrFactory<InterceptedRequest> m_weakFactory; + base::WeakPtr<InterceptedRequest> m_weakPtr; + DISALLOW_COPY_AND_ASSIGN(InterceptedRequest); +}; + +InterceptedRequest::InterceptedRequest(int process_id, uint64_t request_id, int32_t routing_id, uint32_t options, + const network::ResourceRequest &request, + const net::MutableNetworkTrafficAnnotationTag &traffic_annotation, + ProfileIODataQt *profileData, + network::mojom::URLLoaderRequest loader_request, + network::mojom::URLLoaderClientPtr client, + network::mojom::URLLoaderFactoryPtr target_factory) + : process_id_(process_id) + , request_id_(request_id) + , routing_id_(routing_id) + , options_(options) + , request_(request) + , traffic_annotation_(traffic_annotation) + , m_profileData(profileData) + , proxied_loader_binding_(this, std::move(loader_request)) + , target_client_(std::move(client)) + , proxied_client_binding_(this) + , target_factory_(std::move(target_factory)) + , m_weakFactory(this) + , m_weakPtr(m_weakFactory.GetWeakPtr()) +{ + // If there is a client error, clean up the request. + target_client_.set_connection_error_handler( + base::BindOnce(&InterceptedRequest::OnURLLoaderClientError, m_weakFactory.GetWeakPtr())); + proxied_loader_binding_.set_connection_error_with_reason_handler( + base::BindOnce(&InterceptedRequest::OnURLLoaderError, m_weakFactory.GetWeakPtr())); +} + +InterceptedRequest::~InterceptedRequest() +{ + m_weakFactory.InvalidateWeakPtrs(); +} + +void InterceptedRequest::Restart() +{ + // FIXME: Support deprecated interceptors here + + // FIXME: unretained post? + base::PostTaskWithTraits( + FROM_HERE, {content::BrowserThread::UI}, + base::BindOnce(&InterceptedRequest::InterceptOnUIThread, base::Unretained(this))); +} + +void InterceptedRequest::InterceptOnUIThread() +{ + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + + content::ResourceType resourceType = content::ResourceType(request_.resource_type); + WebContentsAdapterClient::NavigationType navigationType = + pageTransitionToNavigationType(ui::PageTransition(request_.transition_type)); + + m_originalUrl = request_.url; + const QUrl qUrl = toQt(request_.url); + + const QUrl initiator = request_.request_initiator.has_value() ? toQt(request_.request_initiator->GetURL()) : QUrl(); + + QUrl firstPartyUrl; + if (resourceType == content::ResourceType::kSubFrame) + firstPartyUrl = toQt(request_.first_party_url); + else + firstPartyUrl = toQt(request_.site_for_cookies); + + QWebEngineUrlRequestInfoPrivate *infoPrivate = new QWebEngineUrlRequestInfoPrivate(toQt(resourceType), + toQt(navigationType), + qUrl, + firstPartyUrl, + initiator, + QByteArray::fromStdString(request_.method)); + QWebEngineUrlRequestInfo requestInfo(infoPrivate); + + content::WebContents *webContents = nullptr; + if (process_id_) { + content::RenderFrameHost *frameHost = content::RenderFrameHost::FromID(process_id_, request_.render_frame_id); + webContents = content::WebContents::FromRenderFrameHost(frameHost); + } else + webContents = content::WebContents::FromFrameTreeNodeId(request_.render_frame_id); + + if (webContents) { + int result = net::OK; + if (m_profileData) { + QWebEngineUrlRequestInterceptor *interceptor = m_profileData->requestInterceptor(); + if (interceptor && !interceptor->property("deprecated").toBool()) + interceptor->interceptRequest(requestInfo); + } + + WebContentsAdapterClient *client = + WebContentsViewQt::from(static_cast<content::WebContentsImpl*>(webContents)->GetView())->client(); + + if (!requestInfo.changed()) { + client->interceptRequest(requestInfo); + } + + if (requestInfo.changed()) { + result = requestInfo.d_ptr->shouldBlockRequest ? net::ERR_BLOCKED_BY_CLIENT : net::OK; + // We handle the rest of the changes later when we are back in I/O thread + } + + if (result != net::OK) { + base::PostTaskWithTraits( + FROM_HERE, {content::BrowserThread::IO}, + base::BindOnce(&InterceptedRequest::SendErrorAndCompleteImmediately, m_weakPtr, result)); + return; + } + if (requestInfo.changed()) { + if (requestInfo.requestUrl() != qUrl) { + net::URLRequest::FirstPartyURLPolicy first_party_url_policy = + request_.update_first_party_url_on_redirect ? net::URLRequest::UPDATE_FIRST_PARTY_URL_ON_REDIRECT + : net::URLRequest::NEVER_CHANGE_FIRST_PARTY_URL; + net::RedirectInfo redirectInfo = net::RedirectInfo::ComputeRedirectInfo(request_.method, request_.url, + request_.site_for_cookies, request_.top_frame_origin, + first_party_url_policy, request_.referrer_policy, + request_.referrer.spec(), net::HTTP_TEMPORARY_REDIRECT, + toGurl(requestInfo.requestUrl()), base::nullopt, + false /*insecure_scheme_was_upgraded*/); + + // FIXME: Should probably create a new header. + current_response_.encoded_data_length = 0; + // FIXME: unretained post. + base::PostTaskWithTraits( + FROM_HERE, {content::BrowserThread::IO}, + base::BindOnce(&network::mojom::URLLoaderClientProxy::OnReceiveRedirect, base::Unretained(&(*target_client_)), redirectInfo, current_response_)); + request_.method = redirectInfo.new_method; + request_.url = redirectInfo.new_url; + request_.site_for_cookies = redirectInfo.new_site_for_cookies; + request_.referrer = GURL(redirectInfo.new_referrer); + request_.referrer_policy = redirectInfo.new_referrer_policy; + if (request_.method == net::HttpRequestHeaders::kGetMethod) + request_.request_body = nullptr; + return; + } + + if (!requestInfo.d_ptr->extraHeaders.isEmpty()) { + auto end = requestInfo.d_ptr->extraHeaders.constEnd(); + for (auto header = requestInfo.d_ptr->extraHeaders.constBegin(); header != end; ++header) { + std::string h = header.key().toStdString(); + if (base::LowerCaseEqualsASCII(h, "referer")) { + request_.referrer = GURL(header.value().toStdString()); + } else { + request_.headers.SetHeader(h, header.value().toStdString()); + } + } + } + } + } + base::PostTaskWithTraits( + FROM_HERE, {content::BrowserThread::IO}, + base::BindOnce(&InterceptedRequest::ContinueAfterIntercept, m_weakPtr)); +} + +void InterceptedRequest::ContinueAfterIntercept() +{ + DCHECK_CURRENTLY_ON(content::BrowserThread::IO); + if (!target_loader_ && target_factory_) { + network::mojom::URLLoaderClientPtr proxied_client; + proxied_client_binding_.Bind(mojo::MakeRequest(&proxied_client)); + target_factory_->CreateLoaderAndStart(mojo::MakeRequest(&target_loader_), routing_id_, request_id_, options_, + request_, std::move(proxied_client), traffic_annotation_); + } +} + +// URLLoaderClient methods. + +void InterceptedRequest::OnReceiveResponse(const network::ResourceResponseHead &head) +{ + current_response_ = head; + + target_client_->OnReceiveResponse(head); +} + +void InterceptedRequest::OnReceiveRedirect(const net::RedirectInfo &redirect_info, const network::ResourceResponseHead &head) +{ + // TODO(timvolodine): handle redirect override. + request_was_redirected_ = true; + current_response_ = head; + target_client_->OnReceiveRedirect(redirect_info, head); + request_.url = redirect_info.new_url; + request_.method = redirect_info.new_method; + request_.site_for_cookies = redirect_info.new_site_for_cookies; + request_.referrer = GURL(redirect_info.new_referrer); + request_.referrer_policy = redirect_info.new_referrer_policy; +} + +void InterceptedRequest::OnUploadProgress(int64_t current_position, int64_t total_size, OnUploadProgressCallback callback) +{ + target_client_->OnUploadProgress(current_position, total_size, std::move(callback)); +} + +void InterceptedRequest::OnReceiveCachedMetadata(mojo_base::BigBuffer data) +{ + target_client_->OnReceiveCachedMetadata(std::move(data)); +} + +void InterceptedRequest::OnTransferSizeUpdated(int32_t transfer_size_diff) +{ + target_client_->OnTransferSizeUpdated(transfer_size_diff); +} + +void InterceptedRequest::OnStartLoadingResponseBody(mojo::ScopedDataPipeConsumerHandle body) +{ + target_client_->OnStartLoadingResponseBody(std::move(body)); +} + +void InterceptedRequest::OnComplete(const network::URLLoaderCompletionStatus &status) +{ + // Only wait for the original loader to possibly have a custom error if the + // target loader succeeded. If the target loader failed, then it was a race as + // to whether that error or the safe browsing error would be reported. + CallOnComplete(status, status.error_code == net::OK); +} + +// URLLoader methods. + +void InterceptedRequest::FollowRedirect(const std::vector<std::string> &removed_headers, + const net::HttpRequestHeaders &modified_headers, + const base::Optional<GURL> &new_url) +{ + if (target_loader_) + target_loader_->FollowRedirect(removed_headers, modified_headers, new_url); + + // If |OnURLLoaderClientError| was called then we're just waiting for the + // connection error handler of |proxied_loader_binding_|. Don't restart the + // job since that'll create another URLLoader + if (!target_client_) + return; + + Restart(); +} + +void InterceptedRequest::ProceedWithResponse() +{ + if (target_loader_) + target_loader_->ProceedWithResponse(); +} + +void InterceptedRequest::SetPriority(net::RequestPriority priority, int32_t intra_priority_value) +{ + if (target_loader_) + target_loader_->SetPriority(priority, intra_priority_value); +} + +void InterceptedRequest::PauseReadingBodyFromNet() +{ + if (target_loader_) + target_loader_->PauseReadingBodyFromNet(); +} + +void InterceptedRequest::ResumeReadingBodyFromNet() +{ + if (target_loader_) + target_loader_->ResumeReadingBodyFromNet(); +} + +void InterceptedRequest::OnURLLoaderClientError() +{ + // We set |wait_for_loader_error| to true because if the loader did have a + // custom_reason error then the client would be reset as well and it would be + // a race as to which connection error we saw first. + CallOnComplete(network::URLLoaderCompletionStatus(net::ERR_ABORTED), true /* wait_for_loader_error */); +} + +void InterceptedRequest::OnURLLoaderError(uint32_t custom_reason, const std::string &description) +{ + // If CallOnComplete was already called, then this object is ready to be deleted. + if (!target_client_) + delete this; +} + +void InterceptedRequest::CallOnComplete(const network::URLLoaderCompletionStatus &status, bool wait_for_loader_error) +{ + DCHECK_CURRENTLY_ON(content::BrowserThread::IO); + // Save an error status so that we call onReceiveError at destruction if there + // was no safe browsing error. + if (status.error_code != net::OK) + error_status_ = status.error_code; + + if (target_client_) + target_client_->OnComplete(status); + + if (proxied_loader_binding_ && wait_for_loader_error) { + // Don't delete |this| yet, in case the |proxied_loader_binding_|'s + // error_handler is called with a reason to indicate an error which we want + // to send to the client bridge. Also reset |target_client_| so we don't + // get its error_handler called and then delete |this|. + target_client_.reset(); + + // Since the original client is gone no need to continue loading the + // request. + proxied_client_binding_.Close(); + target_loader_.reset(); + + // In case there are pending checks as to whether this request should be + // intercepted, we don't want that causing |target_client_| to be used + // later. + m_weakFactory.InvalidateWeakPtrs(); + } else { + delete this; + } +} + +void InterceptedRequest::SendErrorAndCompleteImmediately(int error_code) +{ + DCHECK_CURRENTLY_ON(content::BrowserThread::IO); + auto status = network::URLLoaderCompletionStatus(error_code); + target_client_->OnComplete(status); + delete this; +} + +ProxyingURLLoaderFactoryQt::ProxyingURLLoaderFactoryQt(int process_id, + content::ResourceContext *resourceContext, + network::mojom::URLLoaderFactoryRequest loader_request, + network::mojom::URLLoaderFactoryPtrInfo target_factory_info) + : m_processId(process_id), m_resourceContext(resourceContext), m_weakFactory(this) +{ + DCHECK_CURRENTLY_ON(content::BrowserThread::IO); + if (target_factory_info) { + m_targetFactory.Bind(std::move(target_factory_info)); + m_targetFactory.set_connection_error_handler( + base::BindOnce(&ProxyingURLLoaderFactoryQt::OnTargetFactoryError, m_weakFactory.GetWeakPtr())); + } + m_proxyBindings.AddBinding(this, std::move(loader_request)); + m_proxyBindings.set_connection_error_handler( + base::BindRepeating(&ProxyingURLLoaderFactoryQt::OnProxyBindingError, m_weakFactory.GetWeakPtr())); +} + +ProxyingURLLoaderFactoryQt::~ProxyingURLLoaderFactoryQt() +{ + m_weakFactory.InvalidateWeakPtrs(); +} + +// static +void ProxyingURLLoaderFactoryQt::CreateProxy(int process_id, + content::ResourceContext *resourceContext, + network::mojom::URLLoaderFactoryRequest loader_request, + network::mojom::URLLoaderFactoryPtrInfo target_factory_info) +{ + DCHECK_CURRENTLY_ON(content::BrowserThread::IO); + + // Will manage its own lifetime + new ProxyingURLLoaderFactoryQt(process_id, resourceContext, std::move(loader_request), std::move(target_factory_info)); +} + +void ProxyingURLLoaderFactoryQt::CreateLoaderAndStart(network::mojom::URLLoaderRequest loader, int32_t routing_id, + int32_t request_id, uint32_t options, + const network::ResourceRequest &request, + network::mojom::URLLoaderClientPtr client, + const net::MutableNetworkTrafficAnnotationTag &traffic_annotation) +{ + DCHECK_CURRENTLY_ON(content::BrowserThread::IO); + + ProfileIODataQt *profileIOData = ProfileIODataQt::FromResourceContext(m_resourceContext); + + QWebEngineUrlRequestInterceptor *profileInterceptor = profileIOData ? profileIOData->requestInterceptor() : nullptr; + if (!profileIOData || !(profileInterceptor || profileIOData->hasPageInterceptors())) { + m_targetFactory->CreateLoaderAndStart( + std::move(loader), routing_id, request_id, options, request, + std::move(client), traffic_annotation); + return; + } + + network::mojom::URLLoaderFactoryPtr target_factory_clone; + if (m_targetFactory) + m_targetFactory->Clone(mojo::MakeRequest(&target_factory_clone)); + + + // Will manage its own lifetime + InterceptedRequest *req = new InterceptedRequest(m_processId, request_id, routing_id, options, request, + traffic_annotation, profileIOData, + std::move(loader), std::move(client), + std::move(target_factory_clone)); + req->Restart(); +} + +void ProxyingURLLoaderFactoryQt::OnTargetFactoryError() +{ + delete this; +} + +void ProxyingURLLoaderFactoryQt::OnProxyBindingError() +{ + if (m_proxyBindings.empty()) + delete this; +} + +void ProxyingURLLoaderFactoryQt::Clone(network::mojom::URLLoaderFactoryRequest loader_request) +{ + DCHECK_CURRENTLY_ON(content::BrowserThread::IO); + m_proxyBindings.AddBinding(this, std::move(loader_request)); +} + +} // namespace QtWebEngineCore diff --git a/src/core/net/proxying_url_loader_factory_qt.h b/src/core/net/proxying_url_loader_factory_qt.h new file mode 100644 index 000000000..a2f175885 --- /dev/null +++ b/src/core/net/proxying_url_loader_factory_qt.h @@ -0,0 +1,107 @@ +/**************************************************************************** +** +** Copyright (C) 2019 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 PROXYING_URL_LOADER_FACTORY_QT_H_ +#define PROXYING_URL_LOADER_FACTORY_QT_H_ + +#include "base/callback.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/memory/ref_counted_delete_on_sequence.h" +#include "base/memory/weak_ptr.h" +#include "base/optional.h" +#include "mojo/public/cpp/bindings/binding.h" +#include "mojo/public/cpp/bindings/binding_set.h" +#include "net/traffic_annotation/network_traffic_annotation.h" +#include "services/network/public/cpp/resource_request.h" +#include "services/network/public/cpp/resource_response.h" +#include "services/network/public/mojom/url_loader.mojom.h" +#include "services/network/public/mojom/url_loader_factory.mojom.h" +#include "url/gurl.h" + +// based on aw_proxying_url_loader_factory.h: +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +namespace content { +class ResourceContext; +} + +namespace QtWebEngineCore { + +class ProxyingURLLoaderFactoryQt : public network::mojom::URLLoaderFactory +{ +public: + ProxyingURLLoaderFactoryQt(int process_id, content::ResourceContext *resourceContext, + network::mojom::URLLoaderFactoryRequest loader_request, + network::mojom::URLLoaderFactoryPtrInfo target_factory_info); + + ~ProxyingURLLoaderFactoryQt() override; + + // static + static void CreateProxy(int process_id, content::ResourceContext *resourceContext, + network::mojom::URLLoaderFactoryRequest loader, + network::mojom::URLLoaderFactoryPtrInfo target_factory_info); + + void CreateLoaderAndStart(network::mojom::URLLoaderRequest loader, int32_t routing_id, int32_t request_id, + uint32_t options, const network::ResourceRequest &request, + network::mojom::URLLoaderClientPtr client, + const net::MutableNetworkTrafficAnnotationTag &traffic_annotation) override; + + void Clone(network::mojom::URLLoaderFactoryRequest loader_request) override; + +private: + void OnTargetFactoryError(); + void OnProxyBindingError(); + + const int m_processId; + mojo::BindingSet<network::mojom::URLLoaderFactory> m_proxyBindings; + network::mojom::URLLoaderFactoryPtr m_targetFactory; + + content::ResourceContext *m_resourceContext; + + base::WeakPtrFactory<ProxyingURLLoaderFactoryQt> m_weakFactory; + + DISALLOW_COPY_AND_ASSIGN(ProxyingURLLoaderFactoryQt); +}; + +} // namespace QtWebEngineCore + +#endif // PROXYING_URL_LOADER_FACTORY_QT_H_ diff --git a/src/core/net/restricted_cookie_manager_qt.h b/src/core/net/restricted_cookie_manager_qt.h index 9154f671d..c135a1795 100644 --- a/src/core/net/restricted_cookie_manager_qt.h +++ b/src/core/net/restricted_cookie_manager_qt.h @@ -62,7 +62,7 @@ public: int32_t frame_id); ~RestrictedCookieManagerQt() override; - // network::mojom::RestrictedCookieManager interface: + // network::RestrictedCookieManager: void GetAllForUrl(const GURL &url, const GURL &site_for_cookies, network::mojom::CookieManagerGetOptionsPtr options, diff --git a/src/core/net/system_network_context_manager.cpp b/src/core/net/system_network_context_manager.cpp new file mode 100644 index 000000000..cf84b840b --- /dev/null +++ b/src/core/net/system_network_context_manager.cpp @@ -0,0 +1,351 @@ +/**************************************************************************** +** +** Copyright (C) 2019 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$ +** +****************************************************************************/ + +// based on chrome/browser/net/system_network_context_manager.cc: +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/system_network_context_manager.h" + +#include <set> +#include <unordered_map> +#include <utility> + +#include "base/bind.h" +#include "base/command_line.h" +#include "base/feature_list.h" +#include "base/logging.h" +#include "base/sequence_checker.h" +#include "base/strings/string_split.h" +#include "base/task/post_task.h" +#include "base/values.h" +#include "build/build_config.h" +#include "chrome/browser/net/chrome_mojo_proxy_resolver_factory.h" +#include "chrome/common/chrome_switches.h" +#include "components/certificate_transparency/ct_known_logs.h" +#include "components/network_session_configurator/common/network_features.h" +#include "content/public/browser/browser_task_traits.h" +#include "content/public/browser/browser_thread.h" +#include "content/public/browser/cors_exempt_headers.h" +#include "content/public/browser/network_service_instance.h" +#include "content/public/common/content_features.h" +#include "content/public/common/content_switches.h" +#include "content/public/common/service_names.mojom.h" +#include "content/public/common/user_agent.h" +#include "mojo/public/cpp/bindings/associated_interface_ptr.h" +#include "net/dns/public/util.h" +#include "net/net_buildflags.h" +#include "net/third_party/uri_template/uri_template.h" +#include "services/network/network_service.h" +#include "services/network/public/cpp/cross_thread_shared_url_loader_factory_info.h" +#include "services/network/public/cpp/features.h" +#include "services/network/public/cpp/shared_url_loader_factory.h" +#include "services/network/public/mojom/host_resolver.mojom.h" +#include "services/proxy_resolver/public/mojom/proxy_resolver.mojom.h" +#include "url/gurl.h" + +namespace { + +// The global instance of the SystemNetworkContextmanager. +SystemNetworkContextManager *g_system_network_context_manager = nullptr; + +network::mojom::HttpAuthStaticParamsPtr CreateHttpAuthStaticParams() +{ + network::mojom::HttpAuthStaticParamsPtr auth_static_params = network::mojom::HttpAuthStaticParams::New(); + + auth_static_params->supported_schemes = { "basic", "digest", "ntlm", "negotiate" }; + + return auth_static_params; +} + +network::mojom::HttpAuthDynamicParamsPtr CreateHttpAuthDynamicParams() +{ + network::mojom::HttpAuthDynamicParamsPtr auth_dynamic_params = network::mojom::HttpAuthDynamicParams::New(); + + auto *command_line = base::CommandLine::ForCurrentProcess(); + auth_dynamic_params->server_whitelist = command_line->GetSwitchValueASCII(switches::kAuthServerWhitelist); +// auth_dynamic_params->delegate_whitelist = command_line->GetSwitchValueASCII(switches::kAuthNegotiateDelegateWhitelist); +// auth_dynamic_params->enable_negotiate_port = command_line->HasSwitch(switches::kEnableAuthNegotiatePort); + + return auth_dynamic_params; +} + +} // namespace + +// SharedURLLoaderFactory backed by a SystemNetworkContextManager and its +// network context. Transparently handles crashes. +class SystemNetworkContextManager::URLLoaderFactoryForSystem : public network::SharedURLLoaderFactory +{ +public: + explicit URLLoaderFactoryForSystem(SystemNetworkContextManager *manager) : manager_(manager) + { + DETACH_FROM_SEQUENCE(sequence_checker_); + } + + // mojom::URLLoaderFactory implementation: + + void CreateLoaderAndStart(network::mojom::URLLoaderRequest request, int32_t routing_id, int32_t request_id, + uint32_t options, const network::ResourceRequest &url_request, + network::mojom::URLLoaderClientPtr client, + const net::MutableNetworkTrafficAnnotationTag &traffic_annotation) override + { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + if (!manager_) + return; + manager_->GetURLLoaderFactory()->CreateLoaderAndStart(std::move(request), routing_id, request_id, options, + url_request, std::move(client), traffic_annotation); + } + + void Clone(network::mojom::URLLoaderFactoryRequest request) override + { + if (!manager_) + return; + manager_->GetURLLoaderFactory()->Clone(std::move(request)); + } + + // SharedURLLoaderFactory implementation: + std::unique_ptr<network::SharedURLLoaderFactoryInfo> Clone() override + { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + return std::make_unique<network::CrossThreadSharedURLLoaderFactoryInfo>(this); + } + + void Shutdown() { manager_ = nullptr; } + +private: + friend class base::RefCounted<URLLoaderFactoryForSystem>; + ~URLLoaderFactoryForSystem() override {} + + SEQUENCE_CHECKER(sequence_checker_); + SystemNetworkContextManager *manager_; + + DISALLOW_COPY_AND_ASSIGN(URLLoaderFactoryForSystem); +}; + +network::mojom::NetworkContext *SystemNetworkContextManager::GetContext() +{ + if (!base::FeatureList::IsEnabled(network::features::kNetworkService)) { + // SetUp should already have been called. + DCHECK(io_thread_network_context_); + return io_thread_network_context_.get(); + } + + if (!network_service_network_context_ || network_service_network_context_.encountered_error()) { + // This should call into OnNetworkServiceCreated(), which will re-create + // the network service, if needed. There's a chance that it won't be + // invoked, if the NetworkContext has encountered an error but the + // NetworkService has not yet noticed its pipe was closed. In that case, + // trying to create a new NetworkContext would fail, anyways, and hopefully + // a new NetworkContext will be created on the next GetContext() call. + content::GetNetworkService(); + DCHECK(network_service_network_context_); + } + return network_service_network_context_.get(); +} + +network::mojom::URLLoaderFactory *SystemNetworkContextManager::GetURLLoaderFactory() +{ + // Create the URLLoaderFactory as needed. + if (url_loader_factory_ && !url_loader_factory_.encountered_error()) { + return url_loader_factory_.get(); + } + + network::mojom::URLLoaderFactoryParamsPtr params = network::mojom::URLLoaderFactoryParams::New(); + params->process_id = network::mojom::kBrowserProcessId; + params->is_corb_enabled = false; + GetContext()->CreateURLLoaderFactory(mojo::MakeRequest(&url_loader_factory_), std::move(params)); + return url_loader_factory_.get(); +} + +scoped_refptr<network::SharedURLLoaderFactory> SystemNetworkContextManager::GetSharedURLLoaderFactory() +{ + return shared_url_loader_factory_; +} + +void SystemNetworkContextManager::SetUp( + network::mojom::NetworkContextRequest *network_context_request, + network::mojom::NetworkContextParamsPtr *network_context_params, bool *stub_resolver_enabled, + base::Optional<std::vector<network::mojom::DnsOverHttpsServerPtr>> *dns_over_https_servers, + network::mojom::HttpAuthStaticParamsPtr *http_auth_static_params, + network::mojom::HttpAuthDynamicParamsPtr *http_auth_dynamic_params, bool *is_quic_allowed) +{ + if (!base::FeatureList::IsEnabled(network::features::kNetworkService)) { + *network_context_request = mojo::MakeRequest(&io_thread_network_context_); + *network_context_params = CreateNetworkContextParams(); + } + *is_quic_allowed = false; + *http_auth_static_params = CreateHttpAuthStaticParams(); + *http_auth_dynamic_params = CreateHttpAuthDynamicParams(); + // GetStubResolverConfig(local_state_, stub_resolver_enabled, dns_over_https_servers); +} + +// static +SystemNetworkContextManager *SystemNetworkContextManager::CreateInstance() +{ + DCHECK(!g_system_network_context_manager); + g_system_network_context_manager = new SystemNetworkContextManager(); + return g_system_network_context_manager; +} + +// static +SystemNetworkContextManager *SystemNetworkContextManager::GetInstance() +{ + return g_system_network_context_manager; +} + +// static +void SystemNetworkContextManager::DeleteInstance() +{ + DCHECK(g_system_network_context_manager); + delete g_system_network_context_manager; +} + +SystemNetworkContextManager::SystemNetworkContextManager() +{ + shared_url_loader_factory_ = new URLLoaderFactoryForSystem(this); +} + +SystemNetworkContextManager::~SystemNetworkContextManager() +{ + shared_url_loader_factory_->Shutdown(); +} + +void SystemNetworkContextManager::OnNetworkServiceCreated(network::mojom::NetworkService *network_service) +{ + if (!base::FeatureList::IsEnabled(network::features::kNetworkService)) + return; + // Disable QUIC globally + network_service->DisableQuic(); + + network_service->SetUpHttpAuth(CreateHttpAuthStaticParams()); + network_service->ConfigureHttpAuthPrefs(CreateHttpAuthDynamicParams()); + + // The system NetworkContext must be created first, since it sets + // |primary_network_context| to true. + network_service->CreateNetworkContext(MakeRequest(&network_service_network_context_), CreateNetworkContextParams()); + + // Configure the stub resolver. This must be done after the system + // NetworkContext is created, but before anything has the chance to use it. + // bool stub_resolver_enabled; + // base::Optional<std::vector<network::mojom::DnsOverHttpsServerPtr>> dns_over_https_servers; + // GetStubResolverConfig(local_state_, &stub_resolver_enabled, &dns_over_https_servers); + // content::GetNetworkService()->ConfigureStubHostResolver(stub_resolver_enabled, std::move(dns_over_https_servers)); +} + +void SystemNetworkContextManager::AddSSLConfigToNetworkContextParams(network::mojom::NetworkContextParams *network_context_params) +{ + network_context_params->initial_ssl_config = network::mojom::SSLConfig::New(); + network_context_params->initial_ssl_config->rev_checking_enabled = true; + network_context_params->initial_ssl_config->symantec_enforcement_disabled = true; +} + +network::mojom::NetworkContextParamsPtr SystemNetworkContextManager::CreateDefaultNetworkContextParams() +{ + network::mojom::NetworkContextParamsPtr network_context_params = network::mojom::NetworkContextParams::New(); + content::UpdateCorsExemptHeader(network_context_params.get()); + + network_context_params->enable_brotli = true; + + // network_context_params->user_agent = GetUserAgent(); + + // Disable referrers by default. Any consumer that enables referrers should + // respect prefs::kEnableReferrers from the appropriate pref store. + network_context_params->enable_referrers = false; + + // const base::CommandLine& command_line = + // *base::CommandLine::ForCurrentProcess(); + + // // TODO(eroman): Figure out why this doesn't work in single-process mode, + // // or if it does work, now. + // // Should be possible now that a private isolate is used. + // // http://crbug.com/474654 + // if (!command_line.HasSwitch(switches::kWinHttpProxyResolver)) { + // if (command_line.HasSwitch(switches::kSingleProcess)) { + // LOG(ERROR) << "Cannot use V8 Proxy resolver in single process mode."; + // } else { + network_context_params->proxy_resolver_factory = ChromeMojoProxyResolverFactory::CreateWithSelfOwnedReceiver(); + // } + // } + + // network_context_params->pac_quick_check_enabled = local_state_->GetBoolean(prefs::kQuickCheckEnabled); + + // Use the SystemNetworkContextManager to populate and update SSL + // configuration. The SystemNetworkContextManager is owned by the + // BrowserProcess itself, so will only be destroyed on shutdown, at which + // point, all NetworkContexts will be destroyed as well. + AddSSLConfigToNetworkContextParams(network_context_params.get()); + + // CT is only enabled on Desktop platforms for now. + network_context_params->enforce_chrome_ct_policy = true; + for (const auto &ct_log : certificate_transparency::GetKnownLogs()) { + // TODO(rsleevi): https://crbug.com/702062 - Remove this duplication. + network::mojom::CTLogInfoPtr log_info = network::mojom::CTLogInfo::New(); + log_info->public_key = std::string(ct_log.log_key, ct_log.log_key_length); + log_info->name = ct_log.log_name; + network_context_params->ct_logs.push_back(std::move(log_info)); + } + + network_context_params->http_09_on_non_default_ports_enabled = false; + + return network_context_params; +} + +network::mojom::NetworkContextParamsPtr SystemNetworkContextManager::CreateNetworkContextParams() +{ + // TODO(mmenke): Set up parameters here (in memory cookie store, etc). + network::mojom::NetworkContextParamsPtr network_context_params = CreateDefaultNetworkContextParams(); + + network_context_params->context_name = std::string("system"); + + network_context_params->enable_referrers = false; + + network_context_params->http_cache_enabled = false; + + // These are needed for PAC scripts that use FTP URLs. +#if !BUILDFLAG(DISABLE_FTP_SUPPORT) + network_context_params->enable_ftp_url_support = true; +#endif + + network_context_params->primary_network_context = true; + + proxy_config_monitor_.AddToNetworkContextParams(network_context_params.get()); + + return network_context_params; +} diff --git a/src/core/net/system_network_context_manager.h b/src/core/net/system_network_context_manager.h new file mode 100644 index 000000000..288af5195 --- /dev/null +++ b/src/core/net/system_network_context_manager.h @@ -0,0 +1,187 @@ +/**************************************************************************** +** +** Copyright (C) 2019 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$ +** +****************************************************************************/ + +// based on chrome/browser/net/system_network_context_manager.h: +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SYSTEM_NETWORK_CONTEXT_MANAGER_H_ +#define SYSTEM_NETWORK_CONTEXT_MANAGER_H_ + +#include <memory> +#include <string> +#include <vector> + +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/optional.h" +#include "services/network/public/mojom/host_resolver.mojom-forward.h" +#include "services/network/public/mojom/network_context.mojom.h" +#include "services/network/public/mojom/network_service.mojom-forward.h" +#include "services/network/public/mojom/ssl_config.mojom-forward.h" +#include "services/network/public/mojom/url_loader_factory.mojom-forward.h" + +#include "net/proxy_config_monitor.h" + +namespace network { +namespace mojom { +class URLLoaderFactory; +} +class SharedURLLoaderFactory; +} // namespace network + +namespace net_log { +class NetExportFileWriter; +} + +// Responsible for creating and managing access to the system NetworkContext. +// Lives on the UI thread. The NetworkContext this owns is intended for requests +// not associated with a profile. It stores no data on disk, and has no HTTP +// cache, but it does have ephemeral cookie and channel ID stores. It also does +// not have access to HTTP proxy auth information the user has entered or that +// comes from extensions, and similarly, has no extension-provided per-profile +// proxy configuration information. +// +// This class is also responsible for configuring global NetworkService state. +// +// The "system" NetworkContext will either share a URLRequestContext with +// IOThread's SystemURLRequestContext and be part of IOThread's NetworkService +// (If the network service is disabled) or be an independent NetworkContext +// using the actual network service. +// +// This class is intended to eventually replace IOThread. Handling the two cases +// differently allows this to be used in production without breaking anything or +// requiring two separate paths, while IOThread consumers slowly transition over +// to being compatible with the network service. +class SystemNetworkContextManager +{ +public: + ~SystemNetworkContextManager(); + + // Creates the global instance of SystemNetworkContextManager. If an + // instance already exists, this will cause a DCHECK failure. + static SystemNetworkContextManager *CreateInstance(); + + // Gets the global SystemNetworkContextManager instance. + static SystemNetworkContextManager *GetInstance(); + + // Destroys the global SystemNetworkContextManager instance. + static void DeleteInstance(); + + // If the network service is disabled, |network_context_request| will be for + // the NetworkContext used by the SystemNetworkContextManager and + // |network_context_params| as needed to set up a system NetworkContext. + // Otherwise, this method can still be used to help set up the IOThread's + // in-process URLRequestContext. + // + // Must be called before the system NetworkContext is first used. + // + // |stub_resolver_enabled|, |dns_over_https_servers|, + // |http_auth_static_params|, |http_auth_dynamic_params|, and + // |is_quic_allowed| are used to pass initial NetworkService state to the + // caller, so the NetworkService can be configured appropriately. Using + // NetworkService's Mojo interface to set those options would lead to races + // with other UI->IO thread network-related tasks, since Mojo doesn't preserve + // execution order relative to PostTasks. + void SetUp(network::mojom::NetworkContextRequest *network_context_request, + network::mojom::NetworkContextParamsPtr *network_context_params, bool *stub_resolver_enabled, + base::Optional<std::vector<network::mojom::DnsOverHttpsServerPtr>> *dns_over_https_servers, + network::mojom::HttpAuthStaticParamsPtr *http_auth_static_params, + network::mojom::HttpAuthDynamicParamsPtr *http_auth_dynamic_params, bool *is_quic_allowed); + + // Returns the System NetworkContext. May only be called after SetUp(). Does + // any initialization of the NetworkService that may be needed when first + // called. + network::mojom::NetworkContext *GetContext(); + + // Returns a URLLoaderFactory owned by the SystemNetworkContextManager that is + // backed by the SystemNetworkContext. Allows sharing of the URLLoaderFactory. + // Prefer this to creating a new one. Call Clone() on the value returned by + // this method to get a URLLoaderFactory that can be used on other threads. + network::mojom::URLLoaderFactory *GetURLLoaderFactory(); + + // Returns a SharedURLLoaderFactory owned by the SystemNetworkContextManager + // that is backed by the SystemNetworkContext. + scoped_refptr<network::SharedURLLoaderFactory> GetSharedURLLoaderFactory(); + + // Called when content creates a NetworkService. Creates the + // SystemNetworkContext, if the network service is enabled. + void OnNetworkServiceCreated(network::mojom::NetworkService *network_service); + + // Populates |initial_ssl_config| and |ssl_config_client_request| members of + // |network_context_params|. As long as the SystemNetworkContextManager + // exists, any NetworkContext created with the params will continue to get + // SSL configuration updates. + void AddSSLConfigToNetworkContextParams(network::mojom::NetworkContextParams *network_context_params); + + // Returns default set of parameters for configuring the network service. + network::mojom::NetworkContextParamsPtr CreateDefaultNetworkContextParams(); + +private: + class URLLoaderFactoryForSystem; + + explicit SystemNetworkContextManager(); + + // Creates parameters for the NetworkContext. May only be called once, since + // it initializes some class members. + network::mojom::NetworkContextParamsPtr CreateNetworkContextParams(); + + // ProxyConfigMonitor proxy_config_monitor_; + + // NetworkContext using the network service, if the network service is + // enabled. nullptr, otherwise. + network::mojom::NetworkContextPtr network_service_network_context_; + + // This is a NetworkContext that wraps the IOThread's SystemURLRequestContext. + // Always initialized in SetUp, but it's only returned by Context() when the + // network service is disabled. + network::mojom::NetworkContextPtr io_thread_network_context_; + + // URLLoaderFactory backed by the NetworkContext returned by GetContext(), so + // consumers don't all need to create their own factory. + scoped_refptr<URLLoaderFactoryForSystem> shared_url_loader_factory_; + network::mojom::URLLoaderFactoryPtr url_loader_factory_; + + ProxyConfigMonitor proxy_config_monitor_; + + DISALLOW_COPY_AND_ASSIGN(SystemNetworkContextManager); +}; + +#endif // SYSTEM_NETWORK_CONTEXT_MANAGER_H_ diff --git a/src/core/net/url_request_custom_job.cpp b/src/core/net/url_request_custom_job.cpp index 0d4ac620f..1f6e6abf7 100644 --- a/src/core/net/url_request_custom_job.cpp +++ b/src/core/net/url_request_custom_job.cpp @@ -60,21 +60,22 @@ URLRequestCustomJob::URLRequestCustomJob(URLRequest *request, const std::string &scheme, QPointer<ProfileAdapter> profileAdapter) : URLRequestJob(request, networkDelegate) + , m_taskRunner(base::CreateSingleThreadTaskRunner({ content::BrowserThread::IO })) , m_proxy(new URLRequestCustomJobProxy(this, scheme, profileAdapter)) - , m_device(nullptr) - , m_firstBytePosition(0) - , m_error(0) , m_pendingReadSize(0) , m_pendingReadPos(0) , m_pendingReadBuffer(nullptr) , m_corsEnabled(QWebEngineUrlScheme::schemeByName(QByteArray::fromStdString(scheme)) .flags().testFlag(QWebEngineUrlScheme::CorsEnabled)) { + m_device = nullptr; + m_firstBytePosition = 0; + m_error = 0; } URLRequestCustomJob::~URLRequestCustomJob() { - m_proxy->m_job = nullptr; + m_proxy->m_client = nullptr; if (m_device && m_device->isOpen()) m_device->close(); m_device = nullptr; @@ -107,7 +108,7 @@ void URLRequestCustomJob::Start() void URLRequestCustomJob::Kill() { DCHECK_CURRENTLY_ON(content::BrowserThread::IO); - m_proxy->m_job = nullptr; + m_proxy->m_client = nullptr; if (m_device && m_device->isOpen()) m_device->close(); if (m_pendingReadBuffer) { @@ -228,6 +229,31 @@ int URLRequestCustomJob::ReadRawData(IOBuffer *buf, int bufSize) } } +void URLRequestCustomJob::notifyExpectedContentSize(qint64 size) +{ + set_expected_content_size(size); +} + +void URLRequestCustomJob::notifyHeadersComplete() +{ + NotifyHeadersComplete(); +} + +void URLRequestCustomJob::notifyCanceled() +{ + NotifyCanceled(); +} + +void URLRequestCustomJob::notifyAborted() +{ + NotifyStartError(URLRequestStatus(URLRequestStatus::CANCELED, net::ERR_ABORTED)); +} + +void URLRequestCustomJob::notifyStartFailure(int error) +{ + NotifyStartError(URLRequestStatus::FromError(error)); +} + void URLRequestCustomJob::notifyReadyRead() { DCHECK_CURRENTLY_ON(content::BrowserThread::IO); @@ -264,4 +290,9 @@ void URLRequestCustomJob::notifyReadyRead() buf->Release(); } +base::TaskRunner *URLRequestCustomJob::taskRunner() +{ + return m_taskRunner.get(); +} + } // namespace diff --git a/src/core/net/url_request_custom_job.h b/src/core/net/url_request_custom_job.h index db40b52bb..517f68c3f 100644 --- a/src/core/net/url_request_custom_job.h +++ b/src/core/net/url_request_custom_job.h @@ -42,6 +42,9 @@ #include "net/url_request/url_request_job.h" #include "url/gurl.h" + +#include "url_request_custom_job_proxy.h" + #include <QtCore/QPointer> QT_FORWARD_DECLARE_CLASS(QIODevice) @@ -53,13 +56,14 @@ class URLRequestCustomJobDelegate; class URLRequestCustomJobProxy; // A request job that handles reading custom URL schemes -class URLRequestCustomJob : public net::URLRequestJob +class URLRequestCustomJob : public net::URLRequestJob, private URLRequestCustomJobProxy::Client { public: URLRequestCustomJob(net::URLRequest *request, net::NetworkDelegate *networkDelegate, const std::string &scheme, QPointer<ProfileAdapter> profileAdapter); + // net::URLRequestJob: void Start() override; void Kill() override; int ReadRawData(net::IOBuffer *buf, int buf_size) override; @@ -67,20 +71,23 @@ public: bool GetCharset(std::string *charset) override; void GetResponseInfo(net::HttpResponseInfo *info) override; bool IsRedirectResponse(GURL *location, int *http_status_code, bool *insecure_scheme_was_upgraded) override; - void SetExtraRequestHeaders(const net::HttpRequestHeaders &headers); + void SetExtraRequestHeaders(const net::HttpRequestHeaders &headers) override; protected: virtual ~URLRequestCustomJob(); private: - void notifyReadyRead(); + // URLRequestCustomJobProxy::Client: + void notifyExpectedContentSize(qint64 size) override; + void notifyHeadersComplete() override; + void notifyCanceled() override; + void notifyAborted() override; + void notifyStartFailure(int error) override; + void notifyReadyRead() override; + base::TaskRunner *taskRunner() override; + + scoped_refptr<base::TaskRunner> m_taskRunner; scoped_refptr<URLRequestCustomJobProxy> m_proxy; - std::string m_mimeType; - std::string m_charset; - GURL m_redirect; - QIODevice *m_device; - int64_t m_firstBytePosition; - int m_error; int m_pendingReadSize; int m_pendingReadPos; net::IOBuffer *m_pendingReadBuffer; diff --git a/src/core/net/url_request_custom_job_delegate.cpp b/src/core/net/url_request_custom_job_delegate.cpp index f73296cf0..ff307bede 100644 --- a/src/core/net/url_request_custom_job_delegate.cpp +++ b/src/core/net/url_request_custom_job_delegate.cpp @@ -92,27 +92,27 @@ void URLRequestCustomJobDelegate::reply(const QByteArray &contentType, QIODevice { if (device) QObject::connect(device, &QIODevice::readyRead, this, &URLRequestCustomJobDelegate::slotReadyRead); - base::PostTaskWithTraits(FROM_HERE, {content::BrowserThread::IO}, - base::BindOnce(&URLRequestCustomJobProxy::reply, - m_proxy,contentType.toStdString(),device)); + m_proxy->m_ioTaskRunner->PostTask(FROM_HERE, + base::BindOnce(&URLRequestCustomJobProxy::reply, + m_proxy, contentType.toStdString(),device)); } void URLRequestCustomJobDelegate::slotReadyRead() { - base::PostTaskWithTraits(FROM_HERE, {content::BrowserThread::IO}, - base::BindOnce(&URLRequestCustomJobProxy::readyRead, m_proxy)); + m_proxy->m_ioTaskRunner->PostTask(FROM_HERE, + base::BindOnce(&URLRequestCustomJobProxy::readyRead, m_proxy)); } void URLRequestCustomJobDelegate::abort() { - base::PostTaskWithTraits(FROM_HERE, {content::BrowserThread::IO}, - base::BindOnce(&URLRequestCustomJobProxy::abort, m_proxy)); + m_proxy->m_ioTaskRunner->PostTask(FROM_HERE, + base::BindOnce(&URLRequestCustomJobProxy::abort, m_proxy)); } void URLRequestCustomJobDelegate::redirect(const QUrl &url) { - base::PostTaskWithTraits(FROM_HERE, {content::BrowserThread::IO}, - base::BindOnce(&URLRequestCustomJobProxy::redirect, m_proxy, toGurl(url))); + m_proxy->m_ioTaskRunner->PostTask(FROM_HERE, + base::BindOnce(&URLRequestCustomJobProxy::redirect, m_proxy, toGurl(url))); } void URLRequestCustomJobDelegate::fail(Error error) @@ -138,8 +138,8 @@ void URLRequestCustomJobDelegate::fail(Error error) break; } if (net_error) { - base::PostTaskWithTraits(FROM_HERE, {content::BrowserThread::IO}, - base::BindOnce(&URLRequestCustomJobProxy::fail, m_proxy, net_error)); + m_proxy->m_ioTaskRunner->PostTask(FROM_HERE, + base::BindOnce(&URLRequestCustomJobProxy::fail, m_proxy, net_error)); } } diff --git a/src/core/net/url_request_custom_job_proxy.cpp b/src/core/net/url_request_custom_job_proxy.cpp index 27fed7bf2..d2df64f2f 100644 --- a/src/core/net/url_request_custom_job_proxy.cpp +++ b/src/core/net/url_request_custom_job_proxy.cpp @@ -38,28 +38,29 @@ ****************************************************************************/ #include "url_request_custom_job_proxy.h" -#include "url_request_custom_job.h" #include "url_request_custom_job_delegate.h" + +#include "content/public/browser/browser_thread.h" +#include "net/base/net_errors.h" + #include "api/qwebengineurlrequestjob.h" #include "profile_adapter.h" #include "type_conversion.h" -#include "content/public/browser/browser_thread.h" #include "web_engine_context.h" -using namespace net; - namespace QtWebEngineCore { -URLRequestCustomJobProxy::URLRequestCustomJobProxy(URLRequestCustomJob *job, +URLRequestCustomJobProxy::URLRequestCustomJobProxy(URLRequestCustomJobProxy::Client *client, const std::string &scheme, QPointer<ProfileAdapter> profileAdapter) - : m_job(job) + : m_client(client) , m_started(false) , m_scheme(scheme) , m_delegate(nullptr) , m_profileAdapter(profileAdapter) + , m_ioTaskRunner(m_client->taskRunner()) { - DCHECK_CURRENTLY_ON(content::BrowserThread::IO); + DCHECK(m_ioTaskRunner && m_ioTaskRunner->RunsTasksInCurrentSequence()); } URLRequestCustomJobProxy::~URLRequestCustomJobProxy() @@ -87,74 +88,74 @@ void URLRequestCustomJobProxy::setReplyCharset(const std::string &charset) */ void URLRequestCustomJobProxy::reply(std::string mimeType, QIODevice *device) { - DCHECK_CURRENTLY_ON(content::BrowserThread::IO); - if (!m_job) + if (!m_client) return; - m_job->m_mimeType = mimeType; - m_job->m_device = device; - if (m_job->m_device && !m_job->m_device->isReadable()) - m_job->m_device->open(QIODevice::ReadOnly); + DCHECK (!m_ioTaskRunner || m_ioTaskRunner->RunsTasksInCurrentSequence()); + m_client->m_mimeType = mimeType; + m_client->m_device = device; + if (m_client->m_device && !m_client->m_device->isReadable()) + m_client->m_device->open(QIODevice::ReadOnly); - if (m_job->m_firstBytePosition > 0) - m_job->m_device->seek(m_job->m_firstBytePosition); + if (m_client->m_firstBytePosition > 0) + m_client->m_device->seek(m_client->m_firstBytePosition); - qint64 deviceSize = m_job->m_device ? m_job->m_device->size() : -1; - qint64 remainingBytes = deviceSize - m_job->m_firstBytePosition; + qint64 deviceSize = m_client->m_device ? m_client->m_device->size() : -1; + qint64 remainingBytes = deviceSize - m_client->m_firstBytePosition; if (remainingBytes > 0) - m_job->set_expected_content_size(remainingBytes); + m_client->notifyExpectedContentSize(remainingBytes); - if (m_job->m_device && m_job->m_device->isReadable()) { + if (m_client->m_device && m_client->m_device->isReadable()) { m_started = true; - m_job->NotifyHeadersComplete(); + m_client->notifyHeadersComplete(); } else { - fail(ERR_INVALID_URL); + fail(net::ERR_INVALID_URL); } } void URLRequestCustomJobProxy::redirect(GURL url) { - DCHECK_CURRENTLY_ON(content::BrowserThread::IO); - if (!m_job) + if (!m_client) return; - if (m_job->m_device || m_job->m_error) + DCHECK (!m_ioTaskRunner || m_ioTaskRunner->RunsTasksInCurrentSequence()); + if (m_client->m_device || m_client->m_error) return; - m_job->m_redirect = url; + m_client->m_redirect = url; m_started = true; - m_job->NotifyHeadersComplete(); + m_client->notifyHeadersComplete(); } void URLRequestCustomJobProxy::abort() { - DCHECK_CURRENTLY_ON(content::BrowserThread::IO); - if (!m_job) + if (!m_client) return; - if (m_job->m_device && m_job->m_device->isOpen()) - m_job->m_device->close(); - m_job->m_device = nullptr; + DCHECK (!m_ioTaskRunner || m_ioTaskRunner->RunsTasksInCurrentSequence()); + if (m_client->m_device && m_client->m_device->isOpen()) + m_client->m_device->close(); + m_client->m_device = nullptr; if (m_started) - m_job->NotifyCanceled(); + m_client->notifyCanceled(); else - m_job->NotifyStartError(URLRequestStatus(URLRequestStatus::CANCELED, ERR_ABORTED)); + m_client->notifyAborted(); } void URLRequestCustomJobProxy::fail(int error) { - DCHECK_CURRENTLY_ON(content::BrowserThread::IO); - if (!m_job) + if (!m_client) return; - m_job->m_error = error; - if (m_job->m_device) - m_job->m_device->close(); + DCHECK (m_ioTaskRunner->RunsTasksInCurrentSequence()); + m_client->m_error = error; + if (m_client->m_device) + m_client->m_device->close(); if (!m_started) - m_job->NotifyStartError(URLRequestStatus::FromError(error)); + m_client->notifyStartFailure(error); // else we fail on the next read, or the read that might already be in progress } void URLRequestCustomJobProxy::readyRead() { - DCHECK_CURRENTLY_ON(content::BrowserThread::IO); - if (m_job) - m_job->notifyReadyRead(); + DCHECK (m_ioTaskRunner->RunsTasksInCurrentSequence()); + if (m_client) + m_client->notifyReadyRead(); } void URLRequestCustomJobProxy::initialize(GURL url, std::string method, diff --git a/src/core/net/url_request_custom_job_proxy.h b/src/core/net/url_request_custom_job_proxy.h index d4cd7e208..db38083dd 100644 --- a/src/core/net/url_request_custom_job_proxy.h +++ b/src/core/net/url_request_custom_job_proxy.h @@ -42,6 +42,7 @@ #include "base/memory/weak_ptr.h" #include "base/optional.h" +#include "base/task_runner.h" #include "url/gurl.h" #include "url/origin.h" #include <QtCore/QPointer> @@ -60,7 +61,24 @@ class URLRequestCustomJobProxy : public base::RefCountedThreadSafe<URLRequestCus { public: - URLRequestCustomJobProxy(URLRequestCustomJob *job, + class Client { + public: + std::string m_mimeType; + std::string m_charset; + GURL m_redirect; + QIODevice *m_device; + int64_t m_firstBytePosition; + int m_error; + virtual void notifyExpectedContentSize(qint64 size) = 0; + virtual void notifyHeadersComplete() = 0; + virtual void notifyCanceled() = 0; + virtual void notifyAborted() = 0; + virtual void notifyStartFailure(int) = 0; + virtual void notifyReadyRead() = 0; + virtual base::TaskRunner *taskRunner() = 0; + }; + + URLRequestCustomJobProxy(Client *client, const std::string &scheme, QPointer<ProfileAdapter> profileAdapter); ~URLRequestCustomJobProxy(); @@ -76,13 +94,14 @@ public: void readyRead(); // IO thread owned: - URLRequestCustomJob *m_job; + Client *m_client; bool m_started; // UI thread owned: std::string m_scheme; URLRequestCustomJobDelegate *m_delegate; QPointer<ProfileAdapter> m_profileAdapter; + scoped_refptr<base::TaskRunner> m_ioTaskRunner; }; } // namespace QtWebEngineCore diff --git a/src/core/net/url_request_notification.cpp b/src/core/net/url_request_notification.cpp index 279bd5077..74c56254e 100644 --- a/src/core/net/url_request_notification.cpp +++ b/src/core/net/url_request_notification.cpp @@ -44,11 +44,13 @@ #include "content/browser/web_contents/web_contents_impl.h" #include "content/public/browser/browser_thread.h" #include "net/url_request/url_request.h" -#include "web_contents_adapter_client.h" -#include "web_contents_view_qt.h" + +#include "api/qwebengineurlrequestinfo_p.h" #include "profile_io_data_qt.h" -#include "qwebengineurlrequestinfo_p.h" #include "type_conversion.h" +#include "web_contents_adapter_client.h" +#include "web_contents_view_qt.h" + #include <QVariant> namespace QtWebEngineCore { @@ -67,11 +69,6 @@ private: const char UserData::key[] = "QtWebEngineCore::URLRequestNotification"; -static content::ResourceType fromQt(QWebEngineUrlRequestInfo::ResourceType resourceType) -{ - return static_cast<content::ResourceType>(resourceType); -} - URLRequestNotification::URLRequestNotification(net::URLRequest *request, bool isMainFrameRequest, GURL *newUrl, QWebEngineUrlRequestInfo &&requestInfo, content::ResourceRequestInfo::WebContentsGetter webContentsGetter, @@ -106,7 +103,6 @@ void URLRequestNotification::notify() content::WebContents *webContents = m_webContentsGetter.Run(); if (webContents) { - if (m_profileAdapter && m_profileAdapter->requestInterceptor()) { QWebEngineUrlRequestInterceptor *interceptor = m_profileAdapter->requestInterceptor(); if (!interceptor->property("deprecated").toBool()) @@ -124,25 +120,6 @@ void URLRequestNotification::notify() result = m_requestInfo.d_ptr->shouldBlockRequest ? net::ERR_BLOCKED_BY_CLIENT : net::OK; // We handle the rest of the changes later when we are back in I/O thread } - - // Only do navigationRequested on MAIN_FRAME and SUB_FRAME resources - if (result == net::OK && content::IsResourceTypeFrame(fromQt(m_requestInfo.resourceType()))) { - int navigationRequestAction = WebContentsAdapterClient::AcceptRequest; - client->navigationRequested(m_requestInfo.navigationType(), - m_requestInfo.requestUrl(), - navigationRequestAction, - m_isMainFrameRequest); - result = net::ERR_FAILED; - switch (static_cast<WebContentsAdapterClient::NavigationRequestAction>(navigationRequestAction)) { - case WebContentsAdapterClient::AcceptRequest: - result = net::OK; - break; - case WebContentsAdapterClient::IgnoreRequest: - result = net::ERR_ABORTED; - break; - } - DCHECK(result != net::ERR_FAILED); - } } // Run the callback on the IO thread. diff --git a/src/core/profile_adapter.cpp b/src/core/profile_adapter.cpp index bccdf1ada..ba2f97475 100644 --- a/src/core/profile_adapter.cpp +++ b/src/core/profile_adapter.cpp @@ -193,8 +193,7 @@ void ProfileAdapter::setRequestInterceptor(QWebEngineUrlRequestInterceptor *inte Q_ASSERT(!m_profile->m_profileIOData->requestInterceptor()); }); - if (m_profile->m_urlRequestContextGetter.get()) - m_profile->m_profileIOData->updateRequestInterceptor(); + m_profile->m_profileIOData->updateRequestInterceptor(); } void ProfileAdapter::addClient(ProfileAdapterClient *adapterClient) @@ -210,16 +209,14 @@ void ProfileAdapter::removeClient(ProfileAdapterClient *adapterClient) void ProfileAdapter::addPageRequestInterceptor() { ++m_pageRequestInterceptors; - if (m_profile->m_urlRequestContextGetter.get()) - m_profile->m_profileIOData->updateRequestInterceptor(); + m_profile->m_profileIOData->updateRequestInterceptor(); } void ProfileAdapter::removePageRequestInterceptor() { Q_ASSERT(m_pageRequestInterceptors > 0); --m_pageRequestInterceptors; - if (m_profile->m_urlRequestContextGetter.get()) - m_profile->m_profileIOData->updateRequestInterceptor(); + m_profile->m_profileIOData->updateRequestInterceptor(); } diff --git a/src/core/profile_io_data_qt.cpp b/src/core/profile_io_data_qt.cpp index 68be09ad2..aaaeb277b 100644 --- a/src/core/profile_io_data_qt.cpp +++ b/src/core/profile_io_data_qt.cpp @@ -40,12 +40,14 @@ #include "profile_io_data_qt.h" #include "base/task/post_task.h" +#include "chrome/common/chrome_constants.h" #include "components/certificate_transparency/ct_known_logs.h" #include "components/network_session_configurator/common/network_features.h" #include "content/public/browser/browser_task_traits.h" #include "content/public/browser/browser_thread.h" #include "content/public/browser/browsing_data_remover.h" #include "content/public/browser/cookie_store_factory.h" +#include "content/public/browser/shared_cors_origin_access_list.h" #include "content/public/common/content_features.h" #include "chrome/browser/custom_handlers/protocol_handler_registry_factory.h" #include "chrome/browser/net/chrome_mojo_proxy_resolver_factory.h" @@ -80,6 +82,8 @@ #include "services/file/user_id_map.h" #include "services/network/proxy_service_mojo.h" #include "services/network/restricted_cookie_manager.h" +#include "services/network/public/cpp/features.h" +#include "services/network/public/cpp/cors/origin_access_list.h" #include "net/client_cert_override.h" #include "net/client_cert_store_data.h" @@ -88,6 +92,7 @@ #include "net/network_delegate_qt.h" #include "net/proxy_config_service_qt.h" #include "net/restricted_cookie_manager_qt.h" +#include "net/system_network_context_manager.h" #include "net/url_request_context_getter_qt.h" #include "profile_qt.h" #include "resource_context_qt.h" @@ -103,6 +108,7 @@ #endif #include <mutex> +#include <QVariant> namespace QtWebEngineCore { @@ -218,6 +224,9 @@ void ProfileIODataQt::shutdownOnUIThread() delete m_clientCertificateStoreData; m_clientCertificateStoreData = nullptr; #endif + if (m_cookieDelegate) + m_cookieDelegate->unsetMojoCookieManager(); + m_proxyConfigMonitor.reset(); bool posted = content::BrowserThread::DeleteSoon(content::BrowserThread::IO, FROM_HERE, this); if (!posted) { qWarning() << "Could not delete ProfileIODataQt on io thread !"; @@ -248,9 +257,16 @@ extensions::ExtensionSystemQt* ProfileIODataQt::GetExtensionSystem() base::WeakPtr<ProfileIODataQt> ProfileIODataQt::getWeakPtrOnUIThread() { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + DCHECK(m_initialized); return m_weakPtr; } +base::WeakPtr<ProfileIODataQt> ProfileIODataQt::getWeakPtrOnIOThread() +{ + DCHECK_CURRENTLY_ON(content::BrowserThread::IO); + return m_weakPtrFactory.GetWeakPtr(); +} + void ProfileIODataQt::initializeOnIOThread() { m_networkDelegate.reset(new NetworkDelegateQt(this)); @@ -279,7 +295,10 @@ void ProfileIODataQt::initializeOnUIThread() m_protocolHandlerRegistryIOThreadDelegate = protocolHandlerRegistry->io_thread_delegate(); m_cookieDelegate = new CookieMonsterDelegateQt(); m_cookieDelegate->setClient(m_profile->profileAdapter()->cookieStore()); - createProxyConfig(); + if (base::FeatureList::IsEnabled(network::features::kNetworkService)) + m_proxyConfigMonitor.reset(new ProxyConfigMonitor(m_profile->GetPrefs())); + else + createProxyConfig(); } void ProfileIODataQt::cancelAllUrlRequests() @@ -407,6 +426,11 @@ void ProfileIODataQt::generateCookieStore() const std::lock_guard<QRecursiveMutex> lock(m_mutex); // FIXME: Add code to remove the old channel-id database. + // TODO(nharper): Remove the following when no longer needed - see + // crbug.com/903642. +// base::PostTaskWithTraits( +// FROM_HERE, {base::TaskPriority::BEST_EFFORT, base::MayBlock(), base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN}, +// base::BindOnce(DeleteChannelIDFiles, path.Append(chrome::kChannelIDFilename))); std::unique_ptr<net::CookieStore> cookieStore; switch (m_persistentCookiesPolicy) { @@ -630,7 +654,8 @@ void ProfileIODataQt::requestStorageGeneration() { const std::lock_guard<QRecursiveMutex> lock(m_mutex); if (m_initialized && !m_updateAllStorage) { m_updateAllStorage = true; - createProxyConfig(); + if (!base::FeatureList::IsEnabled(network::features::kNetworkService)) + createProxyConfig(); base::PostTaskWithTraits(FROM_HERE, {content::BrowserThread::IO}, base::BindOnce(&ProfileIODataQt::generateAllStorage, m_weakPtr)); } @@ -639,20 +664,17 @@ void ProfileIODataQt::requestStorageGeneration() { // TODO(miklocek): mojofy ProxyConfigServiceQt void ProfileIODataQt::createProxyConfig() { + Q_ASSERT(!base::FeatureList::IsEnabled(network::features::kNetworkService)); Q_ASSERT(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); const std::lock_guard<QRecursiveMutex> lock(m_mutex); // We must create the proxy config service on the UI loop on Linux because it // must synchronously run on the glib message loop. This will be passed to // the URLRequestContextStorage on the IO thread in GetURLRequestContext(). Q_ASSERT(m_proxyConfigService == 0); - net::ProxyConfigWithAnnotation initialConfig; - ProxyPrefs::ConfigState initialConfigState = PrefProxyConfigTrackerImpl::ReadPrefConfig( - m_profileAdapter->profile()->GetPrefs(), &initialConfig); m_proxyConfigService = new ProxyConfigServiceQt( - net::ProxyResolutionService::CreateSystemProxyConfigService( - base::CreateSingleThreadTaskRunnerWithTraits({content::BrowserThread::IO})), - initialConfig, initialConfigState); + m_profileAdapter->profile()->GetPrefs(), + base::CreateSingleThreadTaskRunnerWithTraits({content::BrowserThread::IO})); //pass interface to io thread m_proxyResolverFactoryInterface = ChromeMojoProxyResolverFactory::CreateWithSelfOwnedReceiver(); } @@ -751,6 +773,7 @@ bool ProfileIODataQt::isInterceptorDeprecated() const QWebEngineUrlRequestInterceptor *ProfileIODataQt::acquireInterceptor() { + Q_ASSERT(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); m_mutex.lock(); return m_requestInterceptor; } @@ -769,6 +792,7 @@ bool ProfileIODataQt::hasPageInterceptors() void ProfileIODataQt::releaseInterceptor() { + Q_ASSERT(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); m_mutex.unlock(); } @@ -828,6 +852,58 @@ void ProfileIODataQt::CreateRestrictedCookieManager(network::mojom::RestrictedCo std::move(request)); } +network::mojom::NetworkContextParamsPtr ProfileIODataQt::CreateNetworkContextParams() +{ + updateStorageSettings(); + + network::mojom::NetworkContextParamsPtr network_context_params = + SystemNetworkContextManager::GetInstance()->CreateDefaultNetworkContextParams(); + + network_context_params->context_name = m_profile->profileAdapter()->storageName().toStdString(); + network_context_params->accept_language = m_httpAcceptLanguage.toStdString(); + + network_context_params->enable_referrers = true; + network_context_params->enable_encrypted_cookies = false; // ??? +// network_context_params->proxy_resolver_factory = std::move(m_proxyResolverFactoryInterface); + + network_context_params->http_cache_enabled = m_httpCacheType != ProfileAdapter::NoCache; + network_context_params->http_cache_max_size = m_httpCacheMaxSize; + if (m_httpCacheType == ProfileAdapter::DiskHttpCache) + network_context_params->http_cache_path = toFilePath(m_httpCachePath); + + if (m_persistentCookiesPolicy != ProfileAdapter::NoPersistentCookies) { + base::FilePath cookie_path = toFilePath(m_dataPath); + cookie_path = cookie_path.AppendASCII("Cookies"); + network_context_params->cookie_path = cookie_path; + + network_context_params->restore_old_session_cookies = false; + network_context_params->persist_session_cookies = m_persistentCookiesPolicy == ProfileAdapter::ForcePersistentCookies; + } + if (!m_dataPath.isEmpty()) { + network_context_params->http_server_properties_path = toFilePath(m_dataPath).AppendASCII("Network Persistent State"); + network_context_params->transport_security_persister_path = toFilePath(m_dataPath); + } + +#if !BUILDFLAG(DISABLE_FTP_SUPPORT) + network_context_params->enable_ftp_url_support = true; +#endif // !BUILDFLAG(DISABLE_FTP_SUPPORT) + +// network_context_params->enable_certificate_reporting = true; +// network_context_params->enable_expect_ct_reporting = true; + network_context_params->enforce_chrome_ct_policy = false; + network_context_params->primary_network_context = m_useForGlobalCertificateVerification; + + if (base::FeatureList::IsEnabled(network::features::kNetworkService)) { + // Should be initialized with existing per-profile CORS access lists. + network_context_params->cors_origin_access_list = + m_profile->GetSharedCorsOriginAccessList()->GetOriginAccessList().CreateCorsOriginAccessPatternsList(); + } + + m_proxyConfigMonitor->AddToNetworkContextParams(network_context_params.get()); + + return network_context_params; +} + // static ProfileIODataQt *ProfileIODataQt::FromBrowserContext(content::BrowserContext *browser_context) { diff --git a/src/core/profile_io_data_qt.h b/src/core/profile_io_data_qt.h index ec0a3dac9..882602012 100644 --- a/src/core/profile_io_data_qt.h +++ b/src/core/profile_io_data_qt.h @@ -47,7 +47,9 @@ #include "chrome/browser/custom_handlers/protocol_handler_registry.h" #include "extensions/buildflags/buildflags.h" #include "mojo/public/cpp/bindings/strong_binding_set.h" +#include "net/proxy_config_monitor.h" #include "services/network/cookie_settings.h" +#include "services/network/public/mojom/network_context.mojom.h" #include "services/network/public/mojom/restricted_cookie_manager.mojom.h" #include "services/proxy_resolver/public/mojom/proxy_resolver.mojom.h" @@ -156,6 +158,8 @@ public: int32_t process_id, int32_t routing_id); + network::mojom::NetworkContextParamsPtr CreateNetworkContextParams(); + #if QT_CONFIG(ssl) ClientCertificateStoreData *clientCertificateStoreData(); #endif @@ -163,8 +167,11 @@ public: static ProfileIODataQt *FromBrowserContext(content::BrowserContext *browser_context); static ProfileIODataQt *FromResourceContext(content::ResourceContext *resource_context); + base::WeakPtr<ProfileIODataQt> getWeakPtrOnIOThread(); base::WeakPtr<ProfileIODataQt> getWeakPtrOnUIThread(); + CookieMonsterDelegateQt *cookieDelegate() const { return m_cookieDelegate.get(); } + private: void removeBrowsingDataRemoverObserver(); @@ -191,6 +198,7 @@ private: QPointer<ProfileAdapter> m_profileAdapter; // never dereferenced in IO thread and it is passed by qpointer ProfileAdapter::PersistentCookiesPolicy m_persistentCookiesPolicy; mojo::StrongBindingSet<network::mojom::RestrictedCookieManager> m_restrictedCookieManagerBindings; + std::unique_ptr<ProxyConfigMonitor> m_proxyConfigMonitor; #if QT_CONFIG(ssl) ClientCertificateStoreData *m_clientCertificateStoreData; diff --git a/src/core/qtwebengine.gni b/src/core/qtwebengine.gni index 9106e4d56..f17f28495 100644 --- a/src/core/qtwebengine.gni +++ b/src/core/qtwebengine.gni @@ -20,6 +20,7 @@ deps = [ "//components/cdm/renderer", "//components/error_page/common", "//components/keyed_service/content", + "//components/navigation_interception", "//components/network_hints/browser", "//components/network_hints/common", "//components/network_hints/renderer", diff --git a/src/core/renderer/content_renderer_client_qt.cpp b/src/core/renderer/content_renderer_client_qt.cpp index b9b199087..0201e5de4 100644 --- a/src/core/renderer/content_renderer_client_qt.cpp +++ b/src/core/renderer/content_renderer_client_qt.cpp @@ -113,8 +113,7 @@ namespace QtWebEngineCore { static const char kHttpErrorDomain[] = "http"; -ContentRendererClientQt::ContentRendererClientQt() - : m_serviceBinding(this) +ContentRendererClientQt::ContentRendererClientQt() : m_serviceBinding(this) { #if BUILDFLAG(ENABLE_EXTENSIONS) extensions::ExtensionsClient::Set(extensions::ExtensionsClientQt::GetInstance()); @@ -122,9 +121,7 @@ ContentRendererClientQt::ContentRendererClientQt() #endif } -ContentRendererClientQt::~ContentRendererClientQt() -{ -} +ContentRendererClientQt::~ContentRendererClientQt() {} void ContentRendererClientQt::RenderThreadStarted() { @@ -137,10 +134,9 @@ void ContentRendererClientQt::RenderThreadStarted() m_prescientNetworkingDispatcher.reset(new network_hints::PrescientNetworkingDispatcher()); auto registry = std::make_unique<service_manager::BinderRegistry>(); - registry->AddInterface(m_visitedLinkSlave->GetBindCallback(), - base::ThreadTaskRunnerHandle::Get()); + registry->AddInterface(m_visitedLinkSlave->GetBindCallback(), base::ThreadTaskRunnerHandle::Get()); content::ChildThread::Get()->GetServiceManagerConnection()->AddConnectionFilter( - std::make_unique<content::SimpleConnectionFilter>(std::move(registry))); + std::make_unique<content::SimpleConnectionFilter>(std::move(registry))); renderThread->AddObserver(m_renderThreadObserver.data()); renderThread->AddObserver(UserResourceController::instance()); @@ -153,32 +149,32 @@ void ContentRendererClientQt::RenderThreadStarted() // Allow XMLHttpRequests from qrc to file. blink::WebURL qrc(blink::KURL("qrc:")); blink::WebString file(blink::WebString::FromASCII("file")); - blink::WebSecurityPolicy::AddOriginAccessAllowListEntry(qrc, file, blink::WebString(), 0, - network::mojom::CorsDomainMatchMode::kAllowSubdomains, - network::mojom::CorsPortMatchMode::kAllowAnyPort, - network::mojom::CorsOriginAccessMatchPriority::kDefaultPriority); + blink::WebSecurityPolicy::AddOriginAccessAllowListEntry( + qrc, file, blink::WebString(), 0, network::mojom::CorsDomainMatchMode::kAllowSubdomains, + network::mojom::CorsPortMatchMode::kAllowAnyPort, + network::mojom::CorsOriginAccessMatchPriority::kDefaultPriority); #if BUILDFLAG(ENABLE_EXTENSIONS) // Allow the pdf viewer extension to access chrome resources blink::WebURL pdfViewerExtension(blink::KURL("chrome-extension://mhjfbmdgcfjbbpaeojofohoefgiehjai")); blink::WebString chromeResources(blink::WebString::FromASCII("chrome")); - blink::WebSecurityPolicy::AddOriginAccessAllowListEntry(pdfViewerExtension, chromeResources, blink::WebString(), 0, - network::mojom::CorsDomainMatchMode::kAllowSubdomains, - network::mojom::CorsPortMatchMode::kAllowAnyPort, - network::mojom::CorsOriginAccessMatchPriority::kDefaultPriority); + blink::WebSecurityPolicy::AddOriginAccessAllowListEntry( + pdfViewerExtension, chromeResources, blink::WebString(), 0, + network::mojom::CorsDomainMatchMode::kAllowSubdomains, network::mojom::CorsPortMatchMode::kAllowAnyPort, + network::mojom::CorsOriginAccessMatchPriority::kDefaultPriority); ExtensionsRendererClientQt::GetInstance()->RenderThreadStarted(); #endif } -void ContentRendererClientQt::RenderViewCreated(content::RenderView* render_view) +void ContentRendererClientQt::RenderViewCreated(content::RenderView *render_view) { // RenderViewObservers destroy themselves with their RenderView. new RenderViewObserverQt(render_view); UserResourceController::instance()->renderViewCreated(render_view); } -void ContentRendererClientQt::RenderFrameCreated(content::RenderFrame* render_frame) +void ContentRendererClientQt::RenderFrameCreated(content::RenderFrame *render_frame) { QtWebEngineCore::RenderFrameObserverQt *render_frame_observer = new QtWebEngineCore::RenderFrameObserverQt(render_frame, m_webCacheImpl.data()); @@ -195,9 +191,7 @@ void ContentRendererClientQt::RenderFrameCreated(content::RenderFrame* render_fr new SpellCheckProvider(render_frame, m_spellCheck.data(), this); #endif #if QT_CONFIG(webengine_printing_and_pdf) - new printing::PrintRenderFrameHelper( - render_frame, - base::WrapUnique(new PrintWebViewHelperDelegateQt())); + new printing::PrintRenderFrameHelper(render_frame, base::WrapUnique(new PrintWebViewHelperDelegateQt())); #endif // QT_CONFIG(webengine_printing_and_pdf) #if BUILDFLAG(ENABLE_EXTENSIONS) auto registry = std::make_unique<service_manager::BinderRegistry>(); @@ -259,9 +253,9 @@ void ContentRendererClientQt::PrepareErrorPage(content::RenderFrame *renderFrame std::string *errorHtml) { Q_UNUSED(ignoring_cache); - GetNavigationErrorStringsInternal(renderFrame, httpMethod, - error_page::Error::NetError(web_error.url(), web_error.reason(), web_error.has_copy_in_cache()), - errorHtml); + GetNavigationErrorStringsInternal( + renderFrame, httpMethod, + error_page::Error::NetError(web_error.url(), web_error.reason(), web_error.has_copy_in_cache()), errorHtml); } void ContentRendererClientQt::PrepareErrorPageForHttpStatusError(content::RenderFrame *renderFrame, @@ -277,7 +271,10 @@ void ContentRendererClientQt::PrepareErrorPageForHttpStatusError(content::Render errorHtml); } -void ContentRendererClientQt::GetNavigationErrorStringsInternal(content::RenderFrame *renderFrame, const std::string &httpMethod, const error_page::Error &error, std::string *errorHtml) +void ContentRendererClientQt::GetNavigationErrorStringsInternal(content::RenderFrame *renderFrame, + const std::string &httpMethod, + const error_page::Error &error, + std::string *errorHtml) { Q_UNUSED(renderFrame) const bool isPost = QByteArray::fromStdString(httpMethod) == QByteArrayLiteral("POST"); @@ -322,9 +319,9 @@ blink::WebPrescientNetworking *ContentRendererClientQt::GetPrescientNetworking() return m_prescientNetworkingDispatcher.get(); } -bool ContentRendererClientQt::OverrideCreatePlugin( - content::RenderFrame* render_frame, - const blink::WebPluginParams& params, blink::WebPlugin** plugin) +bool ContentRendererClientQt::OverrideCreatePlugin(content::RenderFrame *render_frame, + const blink::WebPluginParams ¶ms, + blink::WebPlugin **plugin) { #if BUILDFLAG(ENABLE_EXTENSIONS) if (!ExtensionsRendererClientQt::GetInstance()->OverrideCreatePlugin(render_frame, params)) @@ -333,20 +330,21 @@ bool ContentRendererClientQt::OverrideCreatePlugin( return content::ContentRendererClient::OverrideCreatePlugin(render_frame, params, plugin); } -content::BrowserPluginDelegate* ContentRendererClientQt::CreateBrowserPluginDelegate(content::RenderFrame *render_frame, +content::BrowserPluginDelegate *ContentRendererClientQt::CreateBrowserPluginDelegate(content::RenderFrame *render_frame, const content::WebPluginInfo &info, const std::string &mime_type, const GURL &original_url) { #if BUILDFLAG(ENABLE_EXTENSIONS) - return ExtensionsRendererClientQt::GetInstance()->CreateBrowserPluginDelegate(render_frame, info, mime_type, original_url); + return ExtensionsRendererClientQt::GetInstance()->CreateBrowserPluginDelegate(render_frame, info, mime_type, + original_url); #else return nullptr; #endif } void ContentRendererClientQt::OnBindInterface(const service_manager::BindSourceInfo &remote_info, - const std::string& name, + const std::string &name, mojo::ScopedMessagePipeHandle handle) { Q_UNUSED(remote_info); @@ -355,9 +353,8 @@ void ContentRendererClientQt::OnBindInterface(const service_manager::BindSourceI void ContentRendererClientQt::GetInterface(const std::string &interface_name, mojo::ScopedMessagePipeHandle interface_pipe) { - m_serviceBinding.GetConnector()->BindInterface( - service_manager::ServiceFilter::ByName("qtwebengine"), - interface_name, std::move(interface_pipe)); + m_serviceBinding.GetConnector()->BindInterface(service_manager::ServiceFilter::ByName("qtwebengine"), + interface_name, std::move(interface_pipe)); } // The following is based on chrome/renderer/media/chrome_key_systems.cc: @@ -367,7 +364,7 @@ void ContentRendererClientQt::GetInterface(const std::string &interface_name, mo #if BUILDFLAG(ENABLE_LIBRARY_CDMS) // External Clear Key (used for testing). -static void AddExternalClearKey(std::vector<std::unique_ptr<media::KeySystemProperties>>* concrete_key_systems) +static void AddExternalClearKey(std::vector<std::unique_ptr<media::KeySystemProperties>> *concrete_key_systems) { // TODO(xhwang): Move these into an array so we can use a for loop to add // supported key systems below. @@ -403,57 +400,58 @@ static void AddExternalClearKey(std::vector<std::unique_ptr<media::KeySystemProp } concrete_key_systems->emplace_back( - new cdm::ExternalClearKeyProperties(kExternalClearKeyKeySystem)); + new cdm::ExternalClearKeyProperties(kExternalClearKeyKeySystem)); // Add support of decrypt-only mode in ClearKeyCdm. - concrete_key_systems->emplace_back(new cdm::ExternalClearKeyProperties( - kExternalClearKeyDecryptOnlyKeySystem)); + concrete_key_systems->emplace_back( + new cdm::ExternalClearKeyProperties(kExternalClearKeyDecryptOnlyKeySystem)); // A key system that triggers various types of messages in ClearKeyCdm. - concrete_key_systems->emplace_back(new cdm::ExternalClearKeyProperties( - kExternalClearKeyMessageTypeTestKeySystem)); + concrete_key_systems->emplace_back( + new cdm::ExternalClearKeyProperties(kExternalClearKeyMessageTypeTestKeySystem)); // A key system that triggers the FileIO test in ClearKeyCdm. - concrete_key_systems->emplace_back(new cdm::ExternalClearKeyProperties( - kExternalClearKeyFileIOTestKeySystem)); + concrete_key_systems->emplace_back( + new cdm::ExternalClearKeyProperties(kExternalClearKeyFileIOTestKeySystem)); // A key system that triggers the output protection test in ClearKeyCdm. - concrete_key_systems->emplace_back(new cdm::ExternalClearKeyProperties( - kExternalClearKeyOutputProtectionTestKeySystem)); + concrete_key_systems->emplace_back( + new cdm::ExternalClearKeyProperties(kExternalClearKeyOutputProtectionTestKeySystem)); // A key system that triggers the platform verification test in ClearKeyCdm. - concrete_key_systems->emplace_back(new cdm::ExternalClearKeyProperties( - kExternalClearKeyPlatformVerificationTestKeySystem)); + concrete_key_systems->emplace_back( + new cdm::ExternalClearKeyProperties(kExternalClearKeyPlatformVerificationTestKeySystem)); // A key system that Chrome thinks is supported by ClearKeyCdm, but actually // will be refused by ClearKeyCdm. This is to test the CDM initialization // failure case. - concrete_key_systems->emplace_back(new cdm::ExternalClearKeyProperties( - kExternalClearKeyInitializeFailKeySystem)); + concrete_key_systems->emplace_back( + new cdm::ExternalClearKeyProperties(kExternalClearKeyInitializeFailKeySystem)); // A key system that triggers a crash in ClearKeyCdm. - concrete_key_systems->emplace_back(new cdm::ExternalClearKeyProperties( - kExternalClearKeyCrashKeySystem)); + concrete_key_systems->emplace_back( + new cdm::ExternalClearKeyProperties(kExternalClearKeyCrashKeySystem)); // A key system that triggers the verify host files test in ClearKeyCdm. - concrete_key_systems->emplace_back(new cdm::ExternalClearKeyProperties( - kExternalClearKeyVerifyCdmHostTestKeySystem)); + concrete_key_systems->emplace_back( + new cdm::ExternalClearKeyProperties(kExternalClearKeyVerifyCdmHostTestKeySystem)); // A key system that fetches the Storage ID in ClearKeyCdm. - concrete_key_systems->emplace_back(new cdm::ExternalClearKeyProperties( - kExternalClearKeyStorageIdTestKeySystem)); + concrete_key_systems->emplace_back( + new cdm::ExternalClearKeyProperties(kExternalClearKeyStorageIdTestKeySystem)); // A key system that is registered with a different CDM GUID. - concrete_key_systems->emplace_back(new cdm::ExternalClearKeyProperties( - kExternalClearKeyDifferentGuidTestKeySystem)); + concrete_key_systems->emplace_back( + new cdm::ExternalClearKeyProperties(kExternalClearKeyDifferentGuidTestKeySystem)); // A key system that triggers CDM Proxy test in ClearKeyCdm. - concrete_key_systems->emplace_back(new cdm::ExternalClearKeyProperties( - kExternalClearKeyCdmProxyTestKeySystem)); + concrete_key_systems->emplace_back( + new cdm::ExternalClearKeyProperties(kExternalClearKeyCdmProxyTestKeySystem)); } #if BUILDFLAG(ENABLE_WIDEVINE) -static media::SupportedCodecs GetSupportedCodecs(const std::vector<media::VideoCodec> &supported_video_codecs, bool is_secure) +static media::SupportedCodecs GetSupportedCodecs(const std::vector<media::VideoCodec> &supported_video_codecs, + bool is_secure) { media::SupportedCodecs supported_codecs = media::EME_CODEC_NONE; @@ -469,7 +467,7 @@ static media::SupportedCodecs GetSupportedCodecs(const std::vector<media::VideoC supported_codecs |= media::EME_CODEC_FLAC; #if BUILDFLAG(USE_PROPRIETARY_CODECS) supported_codecs |= media::EME_CODEC_AAC; -#endif // BUILDFLAG(USE_PROPRIETARY_CODECS) +#endif // BUILDFLAG(USE_PROPRIETARY_CODECS) } // Video codecs are determined by what was registered for the CDM. @@ -486,7 +484,7 @@ static media::SupportedCodecs GetSupportedCodecs(const std::vector<media::VideoC case media::VideoCodec::kCodecH264: supported_codecs |= media::EME_CODEC_AVC1; break; -#endif // BUILDFLAG(USE_PROPRIETARY_CODECS) +#endif // BUILDFLAG(USE_PROPRIETARY_CODECS) default: DVLOG(1) << "Unexpected supported codec: " << GetCodecName(codec); break; @@ -505,13 +503,11 @@ static void AddWidevine(std::vector<std::unique_ptr<media::KeySystemProperties>> } // Codecs and encryption schemes. - auto codecs = - GetSupportedCodecs(capability->video_codecs, /*is_secure=*/false); - const auto& encryption_schemes = capability->encryption_schemes; + auto codecs = GetSupportedCodecs(capability->video_codecs, /*is_secure=*/false); + const auto &encryption_schemes = capability->encryption_schemes; auto hw_secure_codecs = GetSupportedCodecs(capability->hw_secure_video_codecs, /*is_secure=*/true); - const auto& hw_secure_encryption_schemes = - capability->hw_secure_encryption_schemes; + const auto &hw_secure_encryption_schemes = capability->hw_secure_encryption_schemes; // Robustness. using Robustness = cdm::WidevineKeySystemProperties::Robustness; @@ -574,11 +570,13 @@ void ContentRendererClientQt::WillSendRequest(blink::WebLocalFrame *frame, bool *attach_same_site_cookies) { #if BUILDFLAG(ENABLE_EXTENSIONS) - ExtensionsRendererClientQt::GetInstance()->WillSendRequest(frame, transition_type, url, initiator_origin, new_url, attach_same_site_cookies); + ExtensionsRendererClientQt::GetInstance()->WillSendRequest(frame, transition_type, url, initiator_origin, new_url, + attach_same_site_cookies); if (!new_url->is_empty()) return; #endif - content::ContentRendererClient::WillSendRequest(frame, transition_type, url, initiator_origin, new_url, attach_same_site_cookies); + content::ContentRendererClient::WillSendRequest(frame, transition_type, url, initiator_origin, new_url, + attach_same_site_cookies); } void ContentRendererClientQt::CreateRendererService(service_manager::mojom::ServiceRequest service_request) @@ -587,7 +585,7 @@ void ContentRendererClientQt::CreateRendererService(service_manager::mojom::Serv m_serviceBinding.Bind(std::move(service_request)); } -service_manager::Connector* ContentRendererClientQt::GetConnector() +service_manager::Connector *ContentRendererClientQt::GetConnector() { return m_serviceBinding.GetConnector(); } diff --git a/src/core/renderer/content_renderer_client_qt.h b/src/core/renderer/content_renderer_client_qt.h index a13d16b5c..4b394bd3e 100644 --- a/src/core/renderer/content_renderer_client_qt.h +++ b/src/core/renderer/content_renderer_client_qt.h @@ -74,9 +74,10 @@ namespace QtWebEngineCore { class RenderThreadObserverQt; -class ContentRendererClientQt : public content::ContentRendererClient - , public service_manager::Service - , public service_manager::LocalInterfaceProvider +class ContentRendererClientQt + : public content::ContentRendererClient + , public service_manager::Service + , public service_manager::LocalInterfaceProvider { public: ContentRendererClientQt(); @@ -85,7 +86,7 @@ public: // content::ContentRendererClient: void RenderThreadStarted() override; void RenderViewCreated(content::RenderView *render_view) override; - void RenderFrameCreated(content::RenderFrame* render_frame) override; + void RenderFrameCreated(content::RenderFrame *render_frame) override; bool ShouldSuppressErrorPage(content::RenderFrame *, const GURL &) override; bool HasErrorPage(int http_status_code) override; @@ -103,16 +104,19 @@ public: uint64_t VisitedLinkHash(const char *canonical_url, size_t length) override; bool IsLinkVisited(uint64_t linkHash) override; - blink::WebPrescientNetworking* GetPrescientNetworking() override; - void AddSupportedKeySystems(std::vector<std::unique_ptr<media::KeySystemProperties>>* key_systems) override; + blink::WebPrescientNetworking *GetPrescientNetworking() override; + void AddSupportedKeySystems(std::vector<std::unique_ptr<media::KeySystemProperties>> *key_systems) override; void RunScriptsAtDocumentStart(content::RenderFrame *render_frame) override; void RunScriptsAtDocumentEnd(content::RenderFrame *render_frame) override; void RunScriptsAtDocumentIdle(content::RenderFrame *render_frame) override; - bool OverrideCreatePlugin(content::RenderFrame* render_frame, - const blink::WebPluginParams& params, blink::WebPlugin** plugin) override; - content::BrowserPluginDelegate* CreateBrowserPluginDelegate(content::RenderFrame* render_frame, - const content::WebPluginInfo& info, const std::string& mime_type, const GURL& original_url) override; + bool OverrideCreatePlugin(content::RenderFrame *render_frame, + const blink::WebPluginParams ¶ms, + blink::WebPlugin **plugin) override; + content::BrowserPluginDelegate *CreateBrowserPluginDelegate(content::RenderFrame *render_frame, + const content::WebPluginInfo &info, + const std::string &mime_type, + const GURL &original_url) override; void WillSendRequest(blink::WebLocalFrame *frame, ui::PageTransition transition_type, @@ -135,10 +139,10 @@ private: mojo::ScopedMessagePipeHandle handle) override; // service_manager::LocalInterfaceProvider: - void GetInterface(const std::string& name, mojo::ScopedMessagePipeHandle request_handle) override; + void GetInterface(const std::string &name, mojo::ScopedMessagePipeHandle request_handle) override; - void GetNavigationErrorStringsInternal(content::RenderFrame* renderFrame, const std::string &httpMethod, - const error_page::Error& error, std::string* errorHtml); + void GetNavigationErrorStringsInternal(content::RenderFrame *renderFrame, const std::string &httpMethod, + const error_page::Error &error, std::string *errorHtml); QScopedPointer<RenderThreadObserverQt> m_renderThreadObserver; QScopedPointer<visitedlink::VisitedLinkSlave> m_visitedLinkSlave; diff --git a/src/core/renderer/content_settings_observer_qt.cpp b/src/core/renderer/content_settings_observer_qt.cpp index a9e89dfee..fc7019367 100644 --- a/src/core/renderer/content_settings_observer_qt.cpp +++ b/src/core/renderer/content_settings_observer_qt.cpp @@ -63,40 +63,37 @@ bool IsUniqueFrame(blink::WebFrame *frame) frame->Top()->GetSecurityOrigin().IsUnique(); } -} // namespace +} // namespace namespace QtWebEngineCore { ContentSettingsObserverQt::ContentSettingsObserverQt(content::RenderFrame *render_frame) - : content::RenderFrameObserver(render_frame) - , content::RenderFrameObserverTracker<ContentSettingsObserverQt>(render_frame) - , m_currentRequestId(0) + : content::RenderFrameObserver(render_frame) + , content::RenderFrameObserverTracker<ContentSettingsObserverQt>(render_frame) + , m_currentRequestId(0) { ClearBlockedContentSettings(); render_frame->GetWebFrame()->SetContentSettingsClient(this); } -ContentSettingsObserverQt::~ContentSettingsObserverQt() { -} +ContentSettingsObserverQt::~ContentSettingsObserverQt() {} -bool ContentSettingsObserverQt::OnMessageReceived(const IPC::Message& message) +bool ContentSettingsObserverQt::OnMessageReceived(const IPC::Message &message) { bool handled = true; IPC_BEGIN_MESSAGE_MAP(ContentSettingsObserverQt, message) - IPC_MESSAGE_HANDLER(QtWebEngineMsg_RequestFileSystemAccessAsyncResponse, - OnRequestFileSystemAccessAsyncResponse) - IPC_MESSAGE_UNHANDLED(handled = false) + IPC_MESSAGE_HANDLER(QtWebEngineMsg_RequestFileSystemAccessAsyncResponse, OnRequestFileSystemAccessAsyncResponse) + IPC_MESSAGE_UNHANDLED(handled = false) IPC_END_MESSAGE_MAP() return handled; } -void ContentSettingsObserverQt::DidCommitProvisionalLoad(bool is_same_document_navigation, - ui::PageTransition /*transition*/) +void ContentSettingsObserverQt::DidCommitProvisionalLoad(bool is_same_document_navigation, ui::PageTransition /*transition*/) { - blink::WebLocalFrame* frame = render_frame()->GetWebFrame(); + blink::WebLocalFrame *frame = render_frame()->GetWebFrame(); if (frame->Parent()) - return; // Not a top-level navigation. + return; // Not a top-level navigation. if (!is_same_document_navigation) ClearBlockedContentSettings(); @@ -104,8 +101,7 @@ void ContentSettingsObserverQt::DidCommitProvisionalLoad(bool is_same_document_n GURL url = frame->GetDocument().Url(); // If we start failing this DCHECK, please makes sure we don't regress // this bug: http://code.google.com/p/chromium/issues/detail?id=79304 - DCHECK(frame->GetDocument().GetSecurityOrigin().ToString() == "null" || - !url.SchemeIs(url::kDataScheme)); + DCHECK(frame->GetDocument().GetSecurityOrigin().ToString() == "null" || !url.SchemeIs(url::kDataScheme)); } void ContentSettingsObserverQt::OnDestruct() @@ -120,10 +116,8 @@ bool ContentSettingsObserverQt::AllowDatabase() return false; bool result = false; - Send(new QtWebEngineHostMsg_AllowDatabase( - routing_id(), url::Origin(frame->GetSecurityOrigin()).GetURL(), - url::Origin(frame->Top()->GetSecurityOrigin()).GetURL(), - &result)); + Send(new QtWebEngineHostMsg_AllowDatabase(routing_id(), url::Origin(frame->GetSecurityOrigin()).GetURL(), + url::Origin(frame->Top()->GetSecurityOrigin()).GetURL(), &result)); return result; } @@ -140,10 +134,9 @@ void ContentSettingsObserverQt::RequestFileSystemAccessAsync(base::OnceCallback< // Verify there are no duplicate insertions. DCHECK(inserted); - Send(new QtWebEngineHostMsg_RequestFileSystemAccessAsync( - routing_id(), m_currentRequestId, - url::Origin(frame->GetSecurityOrigin()).GetURL(), - url::Origin(frame->Top()->GetSecurityOrigin()).GetURL())); + Send(new QtWebEngineHostMsg_RequestFileSystemAccessAsync(routing_id(), m_currentRequestId, + url::Origin(frame->GetSecurityOrigin()).GetURL(), + url::Origin(frame->Top()->GetSecurityOrigin()).GetURL())); } bool ContentSettingsObserverQt::AllowIndexedDB(const WebSecurityOrigin &origin) @@ -153,10 +146,8 @@ bool ContentSettingsObserverQt::AllowIndexedDB(const WebSecurityOrigin &origin) return false; bool result = false; - Send(new QtWebEngineHostMsg_AllowIndexedDB( - routing_id(), url::Origin(origin).GetURL(), - url::Origin(frame->Top()->GetSecurityOrigin()).GetURL(), - &result)); + Send(new QtWebEngineHostMsg_AllowIndexedDB(routing_id(), url::Origin(origin).GetURL(), + url::Origin(frame->Top()->GetSecurityOrigin()).GetURL(), &result)); return result; } @@ -172,9 +163,8 @@ bool ContentSettingsObserverQt::AllowStorage(bool local) return permissions->second; bool result = false; - Send(new QtWebEngineHostMsg_AllowDOMStorage( - routing_id(), url::Origin(frame->GetSecurityOrigin()).GetURL(), - url::Origin(frame->Top()->GetSecurityOrigin()).GetURL(), local, &result)); + Send(new QtWebEngineHostMsg_AllowDOMStorage(routing_id(), url::Origin(frame->GetSecurityOrigin()).GetURL(), + url::Origin(frame->Top()->GetSecurityOrigin()).GetURL(), local, &result)); m_cachedStoragePermissions[key] = result; return result; } diff --git a/src/core/renderer/content_settings_observer_qt.h b/src/core/renderer/content_settings_observer_qt.h index 9c071aa3c..71e1fbca5 100644 --- a/src/core/renderer/content_settings_observer_qt.h +++ b/src/core/renderer/content_settings_observer_qt.h @@ -58,9 +58,9 @@ namespace QtWebEngineCore { // Handles blocking content per content settings for each RenderFrame. class ContentSettingsObserverQt - : public content::RenderFrameObserver - , public content::RenderFrameObserverTracker<ContentSettingsObserverQt> - , public blink::WebContentSettingsClient + : public content::RenderFrameObserver + , public content::RenderFrameObserverTracker<ContentSettingsObserverQt> + , public blink::WebContentSettingsClient { public: ContentSettingsObserverQt(content::RenderFrame *render_frame); @@ -73,11 +73,9 @@ public: bool AllowStorage(bool local) override; private: - // RenderFrameObserver implementation: bool OnMessageReceived(const IPC::Message &message) override; - void DidCommitProvisionalLoad(bool is_same_document_navigation, - ui::PageTransition transition) override; + void DidCommitProvisionalLoad(bool is_same_document_navigation, ui::PageTransition transition) override; void OnDestruct() override; // Message handlers. @@ -98,4 +96,4 @@ private: } // namespace QtWebEngineCore -#endif // RENDERER_CONTENT_SETTINGS_OBSERVER_QT_H +#endif // RENDERER_CONTENT_SETTINGS_OBSERVER_QT_H diff --git a/src/core/renderer/print_web_view_helper_delegate_qt.cpp b/src/core/renderer/print_web_view_helper_delegate_qt.cpp index 67cdd6b66..b85ff7249 100644 --- a/src/core/renderer/print_web_view_helper_delegate_qt.cpp +++ b/src/core/renderer/print_web_view_helper_delegate_qt.cpp @@ -52,21 +52,17 @@ #include "web_engine_library_info.h" namespace QtWebEngineCore { -PrintWebViewHelperDelegateQt::~PrintWebViewHelperDelegateQt() -{ - -} +PrintWebViewHelperDelegateQt::~PrintWebViewHelperDelegateQt() {} bool PrintWebViewHelperDelegateQt::CancelPrerender(content::RenderFrame *) { return false; } -blink::WebElement PrintWebViewHelperDelegateQt::GetPdfElement(blink::WebLocalFrame* frame) +blink::WebElement PrintWebViewHelperDelegateQt::GetPdfElement(blink::WebLocalFrame *frame) { GURL url = frame->GetDocument().Url(); - if (url.SchemeIs(extensions::kExtensionScheme) && url.host() == extension_misc::kPdfExtensionId) - { + if (url.SchemeIs(extensions::kExtensionScheme) && url.host() == extension_misc::kPdfExtensionId) { // <object> with id="plugin" is created in // chrome/browser/resources/pdf/pdf.js. auto plugin_element = frame->GetDocument().GetElementById("plugin"); @@ -81,7 +77,7 @@ bool PrintWebViewHelperDelegateQt::IsPrintPreviewEnabled() return true; } -bool PrintWebViewHelperDelegateQt::OverridePrint(blink::WebLocalFrame* frame) +bool PrintWebViewHelperDelegateQt::OverridePrint(blink::WebLocalFrame *frame) { return false; } diff --git a/src/core/renderer/print_web_view_helper_delegate_qt.h b/src/core/renderer/print_web_view_helper_delegate_qt.h index e3020922f..f02580b91 100644 --- a/src/core/renderer/print_web_view_helper_delegate_qt.h +++ b/src/core/renderer/print_web_view_helper_delegate_qt.h @@ -57,15 +57,14 @@ class PrintWebViewHelperDelegateQt : public printing::PrintRenderFrameHelper::De public: ~PrintWebViewHelperDelegateQt() override; - bool CancelPrerender(content::RenderFrame* render_frame) override; + bool CancelPrerender(content::RenderFrame *render_frame) override; - blink::WebElement GetPdfElement(blink::WebLocalFrame* frame) override; + blink::WebElement GetPdfElement(blink::WebLocalFrame *frame) override; bool IsPrintPreviewEnabled() override; - bool OverridePrint(blink::WebLocalFrame* frame) override; -}; // class PrintWebViewHelperDelegateQt + bool OverridePrint(blink::WebLocalFrame *frame) override; +}; // class PrintWebViewHelperDelegateQt } #endif // PRINT_WEB_VIEW_HELPER_DELEGATE_QT_H - diff --git a/src/core/renderer/render_frame_observer_qt.cpp b/src/core/renderer/render_frame_observer_qt.cpp index c48ef3b5c..cc01acec6 100644 --- a/src/core/renderer/render_frame_observer_qt.cpp +++ b/src/core/renderer/render_frame_observer_qt.cpp @@ -55,30 +55,25 @@ namespace QtWebEngineCore { -RenderFrameObserverQt::RenderFrameObserverQt(content::RenderFrame* render_frame, - web_cache::WebCacheImpl* web_cache_impl) +RenderFrameObserverQt::RenderFrameObserverQt(content::RenderFrame *render_frame, web_cache::WebCacheImpl *web_cache_impl) : RenderFrameObserver(render_frame) , RenderFrameObserverTracker<RenderFrameObserverQt>(render_frame) , m_isFrameDetached(false) , m_web_cache_impl(web_cache_impl) -{ -} +{} -RenderFrameObserverQt::~RenderFrameObserverQt() -{ -} +RenderFrameObserverQt::~RenderFrameObserverQt() {} -void RenderFrameObserverQt::OnDestruct() { +void RenderFrameObserverQt::OnDestruct() +{ delete this; } #if QT_CONFIG(webengine_pepper_plugins) -void RenderFrameObserverQt::DidCreatePepperPlugin(content::RendererPpapiHost* host) +void RenderFrameObserverQt::DidCreatePepperPlugin(content::RendererPpapiHost *host) { - host->GetPpapiHost()->AddHostFactoryFilter( - base::WrapUnique(new PepperRendererHostFactoryQt(host))); - host->GetPpapiHost()->AddInstanceMessageFilter( - base::WrapUnique(new PepperSharedMemoryMessageFilter(host))); + host->GetPpapiHost()->AddHostFactoryFilter(base::WrapUnique(new PepperRendererHostFactoryQt(host))); + host->GetPpapiHost()->AddInstanceMessageFilter(base::WrapUnique(new PepperSharedMemoryMessageFilter(host))); } #endif diff --git a/src/core/renderer/render_frame_observer_qt.h b/src/core/renderer/render_frame_observer_qt.h index fb9fd3869..6bdf4ad23 100644 --- a/src/core/renderer/render_frame_observer_qt.h +++ b/src/core/renderer/render_frame_observer_qt.h @@ -57,23 +57,22 @@ class WebCacheImpl; namespace QtWebEngineCore { class RenderFrameObserverQt - : public content::RenderFrameObserver - , public content::RenderFrameObserverTracker<RenderFrameObserverQt> + : public content::RenderFrameObserver + , public content::RenderFrameObserverTracker<RenderFrameObserverQt> { public: - explicit RenderFrameObserverQt(content::RenderFrame* render_frame, - web_cache::WebCacheImpl* web_cache_impl); + explicit RenderFrameObserverQt(content::RenderFrame *render_frame, web_cache::WebCacheImpl *web_cache_impl); ~RenderFrameObserverQt(); #if QT_CONFIG(webengine_pepper_plugins) - void DidCreatePepperPlugin(content::RendererPpapiHost* host) override; + void DidCreatePepperPlugin(content::RendererPpapiHost *host) override; #endif void OnDestruct() override; void FrameDetached() override; bool isFrameDetached() const; - service_manager::BinderRegistry* registry() { return ®istry_; } + service_manager::BinderRegistry *registry() { return ®istry_; } private: DISALLOW_COPY_AND_ASSIGN(RenderFrameObserverQt); diff --git a/src/core/renderer/render_thread_observer_qt.cpp b/src/core/renderer/render_thread_observer_qt.cpp index 64b9fd961..6012099b8 100644 --- a/src/core/renderer/render_thread_observer_qt.cpp +++ b/src/core/renderer/render_thread_observer_qt.cpp @@ -52,7 +52,8 @@ bool RenderThreadObserverQt::m_isIncognitoProcess = false; void RenderThreadObserverQt::RegisterMojoInterfaces(blink::AssociatedInterfaceRegistry *associated_interfaces) { - associated_interfaces->AddInterface(base::Bind(&RenderThreadObserverQt::OnRendererConfigurationAssociatedRequest, base::Unretained(this))); + associated_interfaces->AddInterface( + base::Bind(&RenderThreadObserverQt::OnRendererConfigurationAssociatedRequest, base::Unretained(this))); } void RenderThreadObserverQt::UnregisterMojoInterfaces(blink::AssociatedInterfaceRegistry *associated_interfaces) @@ -65,7 +66,8 @@ void RenderThreadObserverQt::SetInitialConfiguration(bool is_incognito_process) m_isIncognitoProcess = is_incognito_process; } -void RenderThreadObserverQt::OnRendererConfigurationAssociatedRequest(qtwebengine::mojom::RendererConfigurationAssociatedRequest request) +void RenderThreadObserverQt::OnRendererConfigurationAssociatedRequest( + qtwebengine::mojom::RendererConfigurationAssociatedRequest request) { m_rendererConfigurationBindings.AddBinding(this, std::move(request)); } diff --git a/src/core/renderer/render_thread_observer_qt.h b/src/core/renderer/render_thread_observer_qt.h index 29b842ab4..21acc6f13 100644 --- a/src/core/renderer/render_thread_observer_qt.h +++ b/src/core/renderer/render_thread_observer_qt.h @@ -46,10 +46,11 @@ namespace QtWebEngineCore { -class RenderThreadObserverQt : public content::RenderThreadObserver, - public qtwebengine::mojom::RendererConfiguration { +class RenderThreadObserverQt + : public content::RenderThreadObserver + , public qtwebengine::mojom::RendererConfiguration +{ public: - RenderThreadObserverQt() = default; ~RenderThreadObserverQt() override = default; diff --git a/src/core/renderer/render_view_observer_qt.cpp b/src/core/renderer/render_view_observer_qt.cpp index 7e7c7bdf8..731d8b97d 100644 --- a/src/core/renderer/render_view_observer_qt.cpp +++ b/src/core/renderer/render_view_observer_qt.cpp @@ -50,18 +50,15 @@ #include "third_party/blink/public/web/web_local_frame.h" #include "third_party/blink/public/web/web_view.h" -RenderViewObserverQt::RenderViewObserverQt( - content::RenderView* render_view) - : content::RenderViewObserver(render_view) -{ -} +RenderViewObserverQt::RenderViewObserverQt(content::RenderView *render_view) : content::RenderViewObserver(render_view) +{} void RenderViewObserverQt::onFetchDocumentMarkup(quint64 requestId) { blink::WebString markup; if (render_view()->GetWebView()->MainFrame()->IsWebLocalFrame()) markup = blink::WebFrameContentDumper::DumpAsMarkup( - static_cast<blink::WebLocalFrame*>(render_view()->GetWebView()->MainFrame())); + static_cast<blink::WebLocalFrame *>(render_view()->GetWebView()->MainFrame())); Send(new RenderViewObserverHostQt_DidFetchDocumentMarkup(routing_id(), requestId, markup.Utf16())); } @@ -69,9 +66,8 @@ void RenderViewObserverQt::onFetchDocumentInnerText(quint64 requestId) { blink::WebString text; if (render_view()->GetWebView()->MainFrame()->IsWebLocalFrame()) - text = blink::WebFrameContentDumper::DumpWebViewAsText( - render_view()->GetWebView(), - std::numeric_limits<std::size_t>::max()); + text = blink::WebFrameContentDumper::DumpWebViewAsText(render_view()->GetWebView(), + std::numeric_limits<std::size_t>::max()); Send(new RenderViewObserverHostQt_DidFetchDocumentInnerText(routing_id(), requestId, text.Utf16())); } @@ -85,7 +81,7 @@ void RenderViewObserverQt::OnDestruct() delete this; } -bool RenderViewObserverQt::OnMessageReceived(const IPC::Message& message) +bool RenderViewObserverQt::OnMessageReceived(const IPC::Message &message) { bool handled = true; IPC_BEGIN_MESSAGE_MAP(RenderViewObserverQt, message) diff --git a/src/core/renderer/render_view_observer_qt.h b/src/core/renderer/render_view_observer_qt.h index a878eebe8..5c555b222 100644 --- a/src/core/renderer/render_view_observer_qt.h +++ b/src/core/renderer/render_view_observer_qt.h @@ -43,9 +43,10 @@ #include <QtGlobal> -class RenderViewObserverQt : public content::RenderViewObserver { +class RenderViewObserverQt : public content::RenderViewObserver +{ public: - RenderViewObserverQt(content::RenderView* render_view); + RenderViewObserverQt(content::RenderView *render_view); private: void onFetchDocumentMarkup(quint64 requestId); @@ -54,7 +55,7 @@ private: void OnDestruct() override; - bool OnMessageReceived(const IPC::Message& message) override; + bool OnMessageReceived(const IPC::Message &message) override; DISALLOW_COPY_AND_ASSIGN(RenderViewObserverQt); }; diff --git a/src/core/renderer/user_resource_controller.cpp b/src/core/renderer/user_resource_controller.cpp index 2613d262e..5c1bd301f 100644 --- a/src/core/renderer/user_resource_controller.cpp +++ b/src/core/renderer/user_resource_controller.cpp @@ -64,7 +64,7 @@ Q_GLOBAL_STATIC(UserResourceController, qt_webengine_userResourceController) -static content::RenderView * const globalScriptsIndex = 0; +static content::RenderView *const globalScriptsIndex = nullptr; // Scripts meant to run after the load event will be run 500ms after DOMContentLoaded if the load event doesn't come within that delay. static const int afterLoadTimeout = 500; @@ -74,7 +74,8 @@ static int validUserScriptSchemes() return URLPattern::SCHEME_HTTP | URLPattern::SCHEME_HTTPS | URLPattern::SCHEME_FILE | URLPattern::SCHEME_QRC; } -static bool regexMatchesURL(const std::string &pat, const GURL &url) { +static bool regexMatchesURL(const std::string &pat, const GURL &url) +{ QRegularExpression qre(QtWebEngineCore::toQt(pat)); qre.setPatternOptions(QRegularExpression::CaseInsensitiveOption); if (!qre.isValid()) @@ -96,7 +97,8 @@ static bool includeRuleMatchesURL(const std::string &pat, const GURL &url) return false; } -static bool scriptMatchesURL(const UserScriptData &scriptData, const GURL &url) { +static bool scriptMatchesURL(const UserScriptData &scriptData, const GURL &url) +{ // Logic taken from Chromium (extensions/common/user_script.cc) bool matchFound; if (!scriptData.urlPatterns.empty()) { @@ -133,7 +135,7 @@ static bool scriptMatchesURL(const UserScriptData &scriptData, const GURL &url) class UserResourceController::RenderFrameObserverHelper : public content::RenderFrameObserver { public: - RenderFrameObserverHelper(content::RenderFrame* render_frame); + RenderFrameObserverHelper(content::RenderFrame *render_frame); private: // RenderFrameObserver implementation. @@ -142,7 +144,7 @@ private: void DidFinishLoad() override; void FrameDetached() override; void OnDestruct() override; - bool OnMessageReceived(const IPC::Message& message) override; + bool OnMessageReceived(const IPC::Message &message) override; void onUserScriptAdded(const UserScriptData &); void onUserScriptRemoved(const UserScriptData &); @@ -154,12 +156,10 @@ private: // Helper class to create WeakPtrs so the AfterLoad tasks can be canceled and to // avoid running scripts more than once per injection point. -class UserResourceController::RenderFrameObserverHelper::Runner : public base::SupportsWeakPtr<Runner> { +class UserResourceController::RenderFrameObserverHelper::Runner : public base::SupportsWeakPtr<Runner> +{ public: - explicit Runner(blink::WebLocalFrame *frame) - : m_frame(frame) - { - } + explicit Runner(blink::WebLocalFrame *frame) : m_frame(frame) {} void run(UserScriptData::InjectionPoint p) { @@ -179,7 +179,8 @@ private: class UserResourceController::RenderViewObserverHelper : public content::RenderViewObserver { public: - RenderViewObserverHelper(content::RenderView* render_view); + RenderViewObserverHelper(content::RenderView *render_view); + private: // RenderViewObserver implementation. void OnDestruct() override; @@ -201,8 +202,7 @@ void UserResourceController::runScripts(UserScriptData::InjectionPoint p, blink: for (uint64_t id : qAsConst(scriptsToRun)) { const UserScriptData &script = m_scripts.value(id); - if (script.injectionPoint != p - || (!script.injectForSubframes && !isMainFrame)) + if (script.injectionPoint != p || (!script.injectForSubframes && !isMainFrame)) continue; if (!scriptMatchesURL(script, frame->GetDocument().Url())) continue; @@ -221,13 +221,11 @@ void UserResourceController::RunScriptsAtDocumentEnd(content::RenderFrame *rende UserResourceController::RenderFrameObserverHelper::RenderFrameObserverHelper(content::RenderFrame *render_frame) : content::RenderFrameObserver(render_frame) -{ -} +{} UserResourceController::RenderViewObserverHelper::RenderViewObserverHelper(content::RenderView *render_view) : content::RenderViewObserver(render_view) -{ -} +{} void UserResourceController::RenderFrameObserverHelper::DidCommitProvisionalLoad(bool is_same_document_navigation, ui::PageTransition /*transitionbool*/) @@ -242,8 +240,7 @@ void UserResourceController::RenderFrameObserverHelper::DidCommitProvisionalLoad m_runner.reset(new Runner(render_frame()->GetWebFrame())); base::ThreadTaskRunnerHandle::Get()->PostTask( - FROM_HERE, - base::BindOnce(&Runner::run, m_runner->AsWeakPtr(), UserScriptData::DocumentElementCreation)); + FROM_HERE, base::BindOnce(&Runner::run, m_runner->AsWeakPtr(), UserScriptData::DocumentElementCreation)); } void UserResourceController::RenderFrameObserverHelper::DidFinishDocumentLoad() @@ -252,18 +249,15 @@ void UserResourceController::RenderFrameObserverHelper::DidFinishDocumentLoad() // called instead of DidCommitProvisionalLoad). if (m_runner) base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( - FROM_HERE, - base::BindOnce(&Runner::run, m_runner->AsWeakPtr(), UserScriptData::AfterLoad), - base::TimeDelta::FromMilliseconds(afterLoadTimeout)); - + FROM_HERE, base::BindOnce(&Runner::run, m_runner->AsWeakPtr(), UserScriptData::AfterLoad), + base::TimeDelta::FromMilliseconds(afterLoadTimeout)); } void UserResourceController::RenderFrameObserverHelper::DidFinishLoad() { if (m_runner) base::ThreadTaskRunnerHandle::Get()->PostTask( - FROM_HERE, - base::BindOnce(&Runner::run, m_runner->AsWeakPtr(), UserScriptData::AfterLoad)); + FROM_HERE, base::BindOnce(&Runner::run, m_runner->AsWeakPtr(), UserScriptData::AfterLoad)); } void UserResourceController::RenderFrameObserverHelper::FrameDetached() @@ -293,7 +287,7 @@ bool UserResourceController::RenderFrameObserverHelper::OnMessageReceived(const IPC_MESSAGE_HANDLER(RenderFrameObserverHelper_ClearScripts, onScriptsCleared) IPC_MESSAGE_UNHANDLED(handled = false) IPC_END_MESSAGE_MAP() - return handled; + return handled; } void UserResourceController::RenderFrameObserverHelper::onUserScriptAdded(const UserScriptData &script) @@ -411,4 +405,3 @@ void UserResourceController::onClearScripts() { clearScriptsForView(globalScriptsIndex); } - diff --git a/src/core/renderer/user_resource_controller.h b/src/core/renderer/user_resource_controller.h index 0b5e0a0c6..3a493b9b7 100644 --- a/src/core/renderer/user_resource_controller.h +++ b/src/core/renderer/user_resource_controller.h @@ -56,7 +56,8 @@ class RenderFrame; class RenderView; } -class UserResourceController : public content::RenderThreadObserver { +class UserResourceController : public content::RenderThreadObserver +{ public: static UserResourceController *instance(); diff --git a/src/core/renderer/web_channel_ipc_transport.cpp b/src/core/renderer/web_channel_ipc_transport.cpp index 108686068..8e961566c 100644 --- a/src/core/renderer/web_channel_ipc_transport.cpp +++ b/src/core/renderer/web_channel_ipc_transport.cpp @@ -61,11 +61,13 @@ namespace QtWebEngineCore { -class WebChannelTransport : public gin::Wrappable<WebChannelTransport> { +class WebChannelTransport : public gin::Wrappable<WebChannelTransport> +{ public: static gin::WrapperInfo kWrapperInfo; static void Install(blink::WebLocalFrame *frame, uint worldId); static void Uninstall(blink::WebLocalFrame *frame, uint worldId); + private: WebChannelTransport() {} void NativeQtSendMessage(gin::Arguments *args); @@ -152,9 +154,7 @@ void WebChannelTransport::NativeQtSendMessage(gin::Arguments *args) v8::Local<v8::String> jsonString = v8::Local<v8::String>::Cast(jsonValue); QByteArray json(jsonString->Utf8Length(isolate), 0); - jsonString->WriteUtf8(isolate, - json.data(), json.size(), - nullptr, v8::String::REPLACE_INVALID_UTF8); + jsonString->WriteUtf8(isolate, json.data(), json.size(), nullptr, v8::String::REPLACE_INVALID_UTF8); QJsonParseError error; QJsonDocument doc = QJsonDocument::fromJson(json, &error); @@ -172,21 +172,19 @@ void WebChannelTransport::NativeQtSendMessage(gin::Arguments *args) gin::ObjectTemplateBuilder WebChannelTransport::GetObjectTemplateBuilder(v8::Isolate *isolate) { - return gin::Wrappable<WebChannelTransport>::GetObjectTemplateBuilder(isolate) - .SetMethod("send", &WebChannelTransport::NativeQtSendMessage); + return gin::Wrappable<WebChannelTransport>::GetObjectTemplateBuilder(isolate).SetMethod( + "send", &WebChannelTransport::NativeQtSendMessage); } WebChannelIPCTransport::WebChannelIPCTransport(content::RenderFrame *renderFrame) - : content::RenderFrameObserver(renderFrame) - , m_worldId(0) - , m_worldInitialized(false) + : content::RenderFrameObserver(renderFrame), m_worldId(0), m_worldInitialized(false) { renderFrame->GetAssociatedInterfaceRegistry()->AddInterface( - base::Bind(&WebChannelIPCTransport::BindRequest, base::Unretained(this))); + base::Bind(&WebChannelIPCTransport::BindRequest, base::Unretained(this))); } -void WebChannelIPCTransport::BindRequest( - qtwebchannel::mojom::WebChannelTransportRenderAssociatedRequest request) { +void WebChannelIPCTransport::BindRequest(qtwebchannel::mojom::WebChannelTransportRenderAssociatedRequest request) +{ m_binding.AddBinding(this, std::move(request)); } @@ -208,7 +206,7 @@ void WebChannelIPCTransport::SetWorldId(uint32_t worldId) void WebChannelIPCTransport::ResetWorldId() { - if (m_worldInitialized && m_canUseContext) + if (m_worldInitialized && m_canUseContext) WebChannelTransport::Uninstall(render_frame()->GetWebFrame(), m_worldId); m_worldInitialized = false; @@ -222,8 +220,8 @@ void WebChannelIPCTransport::DispatchWebChannelMessage(const std::vector<uint8_t if (!m_canUseContext) return; - QJsonDocument doc = QJsonDocument::fromRawData(reinterpret_cast<const char *>(binaryJson.data()), - binaryJson.size(), QJsonDocument::BypassValidation); + QJsonDocument doc = QJsonDocument::fromRawData(reinterpret_cast<const char *>(binaryJson.data()), binaryJson.size(), + QJsonDocument::BypassValidation); DCHECK(doc.isObject()); QByteArray json = doc.toJson(QJsonDocument::Compact); @@ -242,22 +240,23 @@ void WebChannelIPCTransport::DispatchWebChannelMessage(const std::vector<uint8_t if (qtObjectValue.IsEmpty() || !qtObjectValue.ToLocalChecked()->IsObject()) return; v8::Local<v8::Object> qtObject = v8::Local<v8::Object>::Cast(qtObjectValue.ToLocalChecked()); - v8::MaybeLocal<v8::Value> webChannelObjectValue(qtObject->Get(context, gin::StringToV8(isolate, "webChannelTransport"))); + v8::MaybeLocal<v8::Value> webChannelObjectValue( + qtObject->Get(context, gin::StringToV8(isolate, "webChannelTransport"))); if (webChannelObjectValue.IsEmpty() || !webChannelObjectValue.ToLocalChecked()->IsObject()) return; v8::Local<v8::Object> webChannelObject = v8::Local<v8::Object>::Cast(webChannelObjectValue.ToLocalChecked()); v8::MaybeLocal<v8::Value> callbackValue(webChannelObject->Get(context, gin::StringToV8(isolate, "onmessage"))); if (callbackValue.IsEmpty() || !callbackValue.ToLocalChecked()->IsFunction()) { - LOG(WARNING) << "onmessage is not a callable property of qt.webChannelTransport. Some things might not work as expected."; + LOG(WARNING) << "onmessage is not a callable property of qt.webChannelTransport. Some things might not work as " + "expected."; return; } v8::Local<v8::Object> messageObject(v8::Object::New(isolate)); v8::Maybe<bool> wasSet = messageObject->DefineOwnProperty( - context, - v8::String::NewFromUtf8(isolate, "data").ToLocalChecked(), - v8::String::NewFromUtf8(isolate, json.constData(), v8::NewStringType::kNormal, json.size()).ToLocalChecked(), - v8::PropertyAttribute(v8::ReadOnly | v8::DontDelete)); + context, v8::String::NewFromUtf8(isolate, "data").ToLocalChecked(), + v8::String::NewFromUtf8(isolate, json.constData(), v8::NewStringType::kNormal, json.size()).ToLocalChecked(), + v8::PropertyAttribute(v8::ReadOnly | v8::DontDelete)); DCHECK(!wasSet.IsNothing() && wasSet.FromJust()); v8::Local<v8::Function> callback = v8::Local<v8::Function>::Cast(callbackValue.ToLocalChecked()); diff --git a/src/core/renderer/web_channel_ipc_transport.h b/src/core/renderer/web_channel_ipc_transport.h index 178c20ed1..a80eecf24 100644 --- a/src/core/renderer/web_channel_ipc_transport.h +++ b/src/core/renderer/web_channel_ipc_transport.h @@ -49,8 +49,10 @@ namespace QtWebEngineCore { -class WebChannelIPCTransport: private content::RenderFrameObserver, - public qtwebchannel::mojom::WebChannelTransportRender { +class WebChannelIPCTransport + : private content::RenderFrameObserver + , public qtwebchannel::mojom::WebChannelTransportRender +{ public: WebChannelIPCTransport(content::RenderFrame *); @@ -58,7 +60,7 @@ private: // qtwebchannel::mojom::WebChannelTransportRender void SetWorldId(uint32_t worldId) override; void ResetWorldId() override; - void DispatchWebChannelMessage(const std::vector<uint8_t>& binaryJson, uint32_t worldId) override; + void DispatchWebChannelMessage(const std::vector<uint8_t> &binaryJson, uint32_t worldId) override; // RenderFrameObserver void WillReleaseScriptContext(v8::Local<v8::Context> context, int worldId) override; diff --git a/src/core/renderer_host/render_view_observer_host_qt.cpp b/src/core/renderer_host/render_view_observer_host_qt.cpp index c097e102d..165a9d86a 100644 --- a/src/core/renderer_host/render_view_observer_host_qt.cpp +++ b/src/core/renderer_host/render_view_observer_host_qt.cpp @@ -69,7 +69,7 @@ void RenderViewObserverHostQt::fetchDocumentInnerText(quint64 requestId) web_contents()->GetRenderViewHost()->GetRoutingID(), requestId)); } -bool RenderViewObserverHostQt::OnMessageReceived(const IPC::Message& message) +bool RenderViewObserverHostQt::OnMessageReceived(const IPC::Message &message) { bool handled = true; IPC_BEGIN_MESSAGE_MAP(RenderViewObserverHostQt, message) @@ -80,15 +80,14 @@ bool RenderViewObserverHostQt::OnMessageReceived(const IPC::Message& message) IPC_MESSAGE_UNHANDLED(handled = false) IPC_END_MESSAGE_MAP() return handled; - } -void RenderViewObserverHostQt::onDidFetchDocumentMarkup(quint64 requestId, const base::string16& markup) +void RenderViewObserverHostQt::onDidFetchDocumentMarkup(quint64 requestId, const base::string16 &markup) { m_adapterClient->didFetchDocumentMarkup(requestId, toQt(markup)); } -void RenderViewObserverHostQt::onDidFetchDocumentInnerText(quint64 requestId, const base::string16& innerText) +void RenderViewObserverHostQt::onDidFetchDocumentInnerText(quint64 requestId, const base::string16 &innerText) { m_adapterClient->didFetchDocumentInnerText(requestId, toQt(innerText)); } diff --git a/src/core/renderer_host/render_view_observer_host_qt.h b/src/core/renderer_host/render_view_observer_host_qt.h index a08263e07..8590ecbc5 100644 --- a/src/core/renderer_host/render_view_observer_host_qt.h +++ b/src/core/renderer_host/render_view_observer_host_qt.h @@ -45,7 +45,7 @@ #include <QtGlobal> namespace content { - class WebContents; +class WebContents; } namespace QtWebEngineCore { @@ -55,14 +55,14 @@ class WebContentsAdapterClient; class RenderViewObserverHostQt : public content::WebContentsObserver { public: - RenderViewObserverHostQt(content::WebContents*, WebContentsAdapterClient *adapterClient); + RenderViewObserverHostQt(content::WebContents *, WebContentsAdapterClient *adapterClient); void fetchDocumentMarkup(quint64 requestId); void fetchDocumentInnerText(quint64 requestId); private: - bool OnMessageReceived(const IPC::Message& message) override; - void onDidFetchDocumentMarkup(quint64 requestId, const base::string16& markup); - void onDidFetchDocumentInnerText(quint64 requestId, const base::string16& innerText); + bool OnMessageReceived(const IPC::Message &message) override; + void onDidFetchDocumentMarkup(quint64 requestId, const base::string16 &markup); + void onDidFetchDocumentInnerText(quint64 requestId, const base::string16 &innerText); WebContentsAdapterClient *m_adapterClient; }; diff --git a/src/core/renderer_host/resource_dispatcher_host_delegate_qt.cpp b/src/core/renderer_host/resource_dispatcher_host_delegate_qt.cpp deleted file mode 100644 index a08e2cf88..000000000 --- a/src/core/renderer_host/resource_dispatcher_host_delegate_qt.cpp +++ /dev/null @@ -1,186 +0,0 @@ -/**************************************************************************** -** -** 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$ -** -****************************************************************************/ - -// Copyright 2013 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE.Chromium file. - -#include "resource_dispatcher_host_delegate_qt.h" - -#include "base/bind.h" -#include "base/guid.h" -#include "base/strings/stringprintf.h" -#include "base/task/post_task.h" -#include "content/public/browser/browser_context.h" -#include "content/public/browser/browser_task_traits.h" -#include "content/public/browser/browser_thread.h" -#include "content/public/browser/download_manager.h" -#include "content/public/browser/download_request_utils.h" -#include "content/public/browser/navigation_controller.h" - -#include "content/public/browser/render_frame_host.h" -#include "content/public/browser/render_view_host.h" -#include "content/public/browser/resource_dispatcher_host.h" -#include "content/public/browser/resource_request_info.h" -#include "content/public/browser/stream_info.h" -#include "content/public/browser/web_contents.h" - -#include "extensions/extension_system_qt.h" -#include "extensions/browser/info_map.h" -#include "extensions/common/constants.h" -#include "extensions/common/extension.h" -#include "extensions/common/manifest_handlers/mime_types_handler.h" - -#include "net/base/escape.h" -#include "net/url_request/url_request.h" - -#include "profile_io_data_qt.h" -#include "type_conversion.h" -#include "web_contents_delegate_qt.h" -#include "web_engine_settings.h" - -namespace QtWebEngineCore { - -void OnPdfStreamIntercepted( - const GURL& original_url, - std::string extension_id, - int frame_tree_node_id, - const content::ResourceRequestInfo::WebContentsGetter& - web_contents_getter) { - content::WebContents* web_contents = web_contents_getter.Run(); - if (!web_contents) - return; - - WebContentsDelegateQt *contentsDelegate = static_cast<WebContentsDelegateQt*>(web_contents->GetDelegate()); - if (!contentsDelegate) - return; - - WebEngineSettings *settings = contentsDelegate->webEngineSettings(); - if (!settings->testAttribute(WebEngineSettings::PdfViewerEnabled) - || !settings->testAttribute(WebEngineSettings::PluginsEnabled)) { - // If the applications has been set up to always download PDF files to open them in an - // external viewer, trigger the download. - std::unique_ptr<download::DownloadUrlParameters> params( - content::DownloadRequestUtils::CreateDownloadForWebContentsMainFrame( - web_contents, original_url, MISSING_TRAFFIC_ANNOTATION)); - content::BrowserContext::GetDownloadManager(web_contents->GetBrowserContext()) - ->DownloadUrl(std::move(params)); - return; - } - - // The URL passes the original pdf resource url, that will be requested - // by the pdf viewer extension page. - content::NavigationController::LoadURLParams params( - GURL(base::StringPrintf("%s://%s/index.html?%s", extensions::kExtensionScheme, - extension_id.c_str(), - original_url.spec().c_str()))); - - params.frame_tree_node_id = frame_tree_node_id; - web_contents->GetController().LoadURLWithParams(params); -} - -bool ResourceDispatcherHostDelegateQt::ShouldInterceptResourceAsStream(net::URLRequest *request, - const std::string &mime_type, - GURL *origin, - std::string *payload) -{ - content::ResourceRequestInfo* info = - content::ResourceRequestInfo::ForRequest(request); - - int render_process_host_id = -1; - int render_frame_id = -1; - if (!content::ResourceRequestInfo::GetRenderFrameForRequest(request, &render_process_host_id, &render_frame_id)) - return false; - - std::vector<std::string> whitelist = MimeTypesHandler::GetMIMETypeWhitelist(); - - extensions::ExtensionSystemQt *extensionSystem = ProfileIODataQt::FromResourceContext(info->GetContext())->GetExtensionSystem(); - if (!extensionSystem) - return false; - - const scoped_refptr<const extensions::InfoMap> extension_info_map(extensionSystem->info_map()); - - for (const std::string &extension_id : whitelist) { - const extensions::Extension *extension = extension_info_map->extensions().GetByID(extension_id); - if (!extension) - continue; - - MimeTypesHandler* handler = MimeTypesHandler::GetHandler(extension); - if (!handler) - continue; - if (handler->CanHandleMIMEType(mime_type)) { - StreamTargetInfo target_info; - *origin = extensions::Extension::GetBaseURLFromExtensionId(extension_id); - target_info.extension_id = extension_id; - target_info.view_id = base::GenerateGUID(); - *payload = target_info.view_id; - stream_target_info_[request] = target_info; - return true; - } - } - return false; -} - -// Informs the delegate that a Stream was created. The Stream can be read from -// the blob URL of the Stream, but can only be read once. -void ResourceDispatcherHostDelegateQt::OnStreamCreated(net::URLRequest *request, - std::unique_ptr<content::StreamInfo> stream) -{ - content::ResourceRequestInfo *info = content::ResourceRequestInfo::ForRequest(request); - std::map<net::URLRequest *, StreamTargetInfo>::iterator ix = stream_target_info_.find(request); - CHECK(ix != stream_target_info_.end()); - int render_frame_id = -1; - int render_process_id = -1; - if (!content::ResourceRequestInfo::GetRenderFrameForRequest(request, &render_process_id, &render_frame_id)) { - stream_target_info_.erase(request); - request->Cancel(); - return; - } - - base::PostTaskWithTraits( - FROM_HERE, {content::BrowserThread::UI}, - base::BindOnce(&OnPdfStreamIntercepted, - request->url(), ix->second.extension_id, - info->GetFrameTreeNodeId(), info->GetWebContentsGetterForRequest() - ) - ); - stream_target_info_.erase(request); -} - -} // namespace QtWebEngineCore diff --git a/src/core/renderer_host/user_resource_controller_host.cpp b/src/core/renderer_host/user_resource_controller_host.cpp index ed4d736f2..96625c13f 100644 --- a/src/core/renderer_host/user_resource_controller_host.cpp +++ b/src/core/renderer_host/user_resource_controller_host.cpp @@ -51,39 +51,37 @@ namespace QtWebEngineCore { -class UserResourceControllerHost::WebContentsObserverHelper : public content::WebContentsObserver { +class UserResourceControllerHost::WebContentsObserverHelper : public content::WebContentsObserver +{ public: WebContentsObserverHelper(UserResourceControllerHost *, content::WebContents *); // WebContentsObserver overrides: void RenderFrameCreated(content::RenderFrameHost *renderFrameHost) override; - void RenderFrameHostChanged(content::RenderFrameHost *oldHost, - content::RenderFrameHost *newHost) override; + void RenderFrameHostChanged(content::RenderFrameHost *oldHost, content::RenderFrameHost *newHost) override; void WebContentsDestroyed() override; private: UserResourceControllerHost *m_controllerHost; }; -UserResourceControllerHost::WebContentsObserverHelper::WebContentsObserverHelper(UserResourceControllerHost *controller, content::WebContents *contents) - : content::WebContentsObserver(contents) - , m_controllerHost(controller) +UserResourceControllerHost::WebContentsObserverHelper::WebContentsObserverHelper(UserResourceControllerHost *controller, + content::WebContents *contents) + : content::WebContentsObserver(contents) + , m_controllerHost(controller) { } -void UserResourceControllerHost::WebContentsObserverHelper::RenderFrameCreated( - content::RenderFrameHost *renderFrameHost) +void UserResourceControllerHost::WebContentsObserverHelper::RenderFrameCreated(content::RenderFrameHost *renderFrameHost) { content::WebContents *contents = web_contents(); const QList<UserScript> scripts = m_controllerHost->m_perContentsScripts.value(contents); for (const UserScript &script : scripts) - renderFrameHost->Send(new RenderFrameObserverHelper_AddScript( - renderFrameHost->GetRoutingID(), script.data())); + renderFrameHost->Send(new RenderFrameObserverHelper_AddScript(renderFrameHost->GetRoutingID(), script.data())); } -void UserResourceControllerHost::WebContentsObserverHelper::RenderFrameHostChanged( - content::RenderFrameHost *oldHost, - content::RenderFrameHost *newHost) +void UserResourceControllerHost::WebContentsObserverHelper::RenderFrameHostChanged(content::RenderFrameHost *oldHost, + content::RenderFrameHost *newHost) { if (oldHost) oldHost->Send(new RenderFrameObserverHelper_ClearScripts(oldHost->GetRoutingID())); @@ -95,10 +93,12 @@ void UserResourceControllerHost::WebContentsObserverHelper::WebContentsDestroyed delete this; } -class UserResourceControllerHost::RenderProcessObserverHelper : public content::RenderProcessHostObserver { +class UserResourceControllerHost::RenderProcessObserverHelper : public content::RenderProcessHostObserver +{ public: RenderProcessObserverHelper(UserResourceControllerHost *); void RenderProcessHostDestroyed(content::RenderProcessHost *) override; + private: UserResourceControllerHost *m_controllerHost; }; @@ -142,9 +142,9 @@ void UserResourceControllerHost::addUserScript(const UserScript &script, WebCont } } contents->GetRenderViewHost()->Send( - new RenderFrameObserverHelper_AddScript( - contents->GetRenderViewHost()->GetMainFrame()->GetRoutingID(), - script.data())); + new RenderFrameObserverHelper_AddScript( + contents->GetRenderViewHost()->GetMainFrame()->GetRoutingID(), + script.data())); } } @@ -165,8 +165,7 @@ bool UserResourceControllerHost::removeUserScript(const UserScript &script, WebC return false; const bool isProfileWideScript = !adapter; if (isProfileWideScript) { - QList<UserScript>::iterator it - = std::find(m_profileWideScripts.begin(), m_profileWideScripts.end(), script); + QList<UserScript>::iterator it = std::find(m_profileWideScripts.begin(), m_profileWideScripts.end(), script); if (it == m_profileWideScripts.end()) return false; for (content::RenderProcessHost *renderer : qAsConst(m_observedProcesses)) @@ -181,9 +180,7 @@ bool UserResourceControllerHost::removeUserScript(const UserScript &script, WebC if (it == list.end()) return false; contents->GetRenderViewHost()->Send( - new RenderFrameObserverHelper_RemoveScript( - contents->GetMainFrame()->GetRoutingID(), - (*it).data())); + new RenderFrameObserverHelper_RemoveScript(contents->GetMainFrame()->GetRoutingID(), (*it).data())); list.erase(it); } return true; @@ -200,7 +197,7 @@ void UserResourceControllerHost::clearAllScripts(WebContentsAdapter *adapter) content::WebContents *contents = adapter->webContents(); m_perContentsScripts.remove(contents); contents->GetRenderViewHost()->Send( - new RenderFrameObserverHelper_ClearScripts(contents->GetMainFrame()->GetRoutingID())); + new RenderFrameObserverHelper_ClearScripts(contents->GetMainFrame()->GetRoutingID())); } } diff --git a/src/core/renderer_host/user_resource_controller_host.h b/src/core/renderer_host/user_resource_controller_host.h index 16a73f5fb..9d828feb6 100644 --- a/src/core/renderer_host/user_resource_controller_host.h +++ b/src/core/renderer_host/user_resource_controller_host.h @@ -66,7 +66,8 @@ namespace QtWebEngineCore { class WebContentsAdapter; -class Q_WEBENGINECORE_PRIVATE_EXPORT UserResourceControllerHost { +class Q_WEBENGINECORE_PRIVATE_EXPORT UserResourceControllerHost +{ public: UserResourceControllerHost(); diff --git a/src/core/renderer_host/web_channel_ipc_transport_host.cpp b/src/core/renderer_host/web_channel_ipc_transport_host.cpp index b1aab00a1..6dbf2a370 100644 --- a/src/core/renderer_host/web_channel_ipc_transport_host.cpp +++ b/src/core/renderer_host/web_channel_ipc_transport_host.cpp @@ -52,8 +52,6 @@ #include <QJsonObject> #include <QLoggingCategory> -#include <QtCore/private/qjson_p.h> - namespace QtWebEngineCore { Q_LOGGING_CATEGORY(log, "qt.webengine.webchanneltransport") @@ -63,7 +61,7 @@ inline QDebug operator<<(QDebug stream, content::RenderFrameHost *frame) return stream << "frame " << frame->GetRoutingID() << " in process " << frame->GetProcess()->GetID(); } -template <class T> +template<class T> inline QDebug operator<<(QDebug stream, const base::Optional<T> &opt) { if (opt) @@ -142,12 +140,7 @@ void WebChannelIPCTransportHost::DispatchWebChannelMessage(const std::vector<uin return; } - QJsonDocument doc; - // QJsonDocument::fromRawData does not check the length before it starts - // parsing the QJsonPrivate::Header and QJsonPrivate::Base structures. - if (binaryJson.size() >= sizeof(QJsonPrivate::Header) + sizeof(QJsonPrivate::Base)) - doc = QJsonDocument::fromRawData(reinterpret_cast<const char *>(binaryJson.data()), - binaryJson.size()); + QJsonDocument doc = QJsonDocument::fromRawData(reinterpret_cast<const char *>(binaryJson.data()), binaryJson.size()); if (!doc.isObject()) { qCCritical(log).nospace() << "received invalid webchannel message from " << frame; diff --git a/src/core/renderer_host/web_channel_ipc_transport_host.h b/src/core/renderer_host/web_channel_ipc_transport_host.h index 94891f25f..d53b24b6b 100644 --- a/src/core/renderer_host/web_channel_ipc_transport_host.h +++ b/src/core/renderer_host/web_channel_ipc_transport_host.h @@ -53,9 +53,11 @@ QT_FORWARD_DECLARE_CLASS(QString) namespace QtWebEngineCore { -class WebChannelIPCTransportHost : public QWebChannelAbstractTransport +class WebChannelIPCTransportHost + : public QWebChannelAbstractTransport , private content::WebContentsObserver - , qtwebchannel::mojom::WebChannelTransportHost { + , qtwebchannel::mojom::WebChannelTransportHost +{ public: WebChannelIPCTransportHost(content::WebContents *webContents, uint32_t worldId = 0, QObject *parent = nullptr); ~WebChannelIPCTransportHost() override; diff --git a/src/core/web_contents_adapter.cpp b/src/core/web_contents_adapter.cpp index 8cc8179cf..6d763faa5 100644 --- a/src/core/web_contents_adapter.cpp +++ b/src/core/web_contents_adapter.cpp @@ -1139,6 +1139,17 @@ bool WebContentsAdapter::recentlyAudible() const return m_webContents->IsCurrentlyAudible(); } +qint64 WebContentsAdapter::renderProcessPid() const +{ + CHECK_INITIALIZED(0); + + content::RenderProcessHost *renderProcessHost = m_webContents->GetMainFrame()->GetProcess(); + const base::Process &process = renderProcessHost->GetProcess(); + if (!process.IsValid()) + return 0; + return process.Pid(); +} + void WebContentsAdapter::copyImageAt(const QPoint &location) { CHECK_INITIALIZED(); diff --git a/src/core/web_contents_adapter.h b/src/core/web_contents_adapter.h index 11f8f9cb1..1afcdc01a 100644 --- a/src/core/web_contents_adapter.h +++ b/src/core/web_contents_adapter.h @@ -166,6 +166,7 @@ public: bool isAudioMuted() const; void setAudioMuted(bool mute); bool recentlyAudible() const; + qint64 renderProcessPid() const; // Must match blink::WebMediaPlayerAction::Type. enum MediaPlayerAction { diff --git a/src/core/web_contents_adapter_client.h b/src/core/web_contents_adapter_client.h index e2f667358..3a75185f8 100644 --- a/src/core/web_contents_adapter_client.h +++ b/src/core/web_contents_adapter_client.h @@ -458,6 +458,7 @@ public: virtual void didUpdateTargetURL(const QUrl&) = 0; virtual void selectionChanged() = 0; virtual void recentlyAudibleChanged(bool recentlyAudible) = 0; + virtual void renderProcessPidChanged(qint64 pid) = 0; virtual QRectF viewportRect() const = 0; virtual QColor backgroundColor() const = 0; virtual void loadStarted(const QUrl &provisionalUrl, bool isErrorPage = false) = 0; diff --git a/src/core/web_contents_delegate_qt.cpp b/src/core/web_contents_delegate_qt.cpp index c1cf1f659..271b87736 100644 --- a/src/core/web_contents_delegate_qt.cpp +++ b/src/core/web_contents_delegate_qt.cpp @@ -313,6 +313,14 @@ void WebContentsDelegateQt::RenderFrameHostChanged(content::RenderFrameHost *old if (new_host) { content::FrameTreeNode *new_node = static_cast<content::RenderFrameHostImpl *>(new_host)->frame_tree_node(); m_frameFocusedObserver.addNode(new_node); + + // Is this a main frame? + if (new_host->GetFrameOwnerElementType() == blink::FrameOwnerElementType::kNone) { + content::RenderProcessHost *renderProcessHost = new_host->GetProcess(); + const base::Process &process = renderProcessHost->GetProcess(); + if (process.IsValid()) + m_viewClient->renderProcessPidChanged(process.Pid()); + } } } diff --git a/src/core/web_engine_context.cpp b/src/core/web_engine_context.cpp index c34d3c612..7ccd8ce8d 100644 --- a/src/core/web_engine_context.cpp +++ b/src/core/web_engine_context.cpp @@ -73,6 +73,7 @@ #include "content/public/common/content_paths.h" #include "content/public/common/content_switches.h" #include "content/public/common/main_function_params.h" +#include "content/public/common/network_service_util.h" #include "gpu/command_buffer/service/gpu_switches.h" #include "gpu/command_buffer/service/sync_point_manager.h" #include "media/audio/audio_manager.h" @@ -539,8 +540,12 @@ WebEngineContext::WebEngineContext() // The video-capture service is not functioning at this moment (since 69) appendToFeatureList(disableFeatures, features::kMojoVideoCapture.name); - // We do not yet support the network-service, but it is enabled by default since 75. - appendToFeatureList(disableFeatures, network::features::kNetworkService.name); + // We do not yet fully support the network-service, but it has been enabled by default since 75. + bool enableNetworkService = !parsedCommandLine->HasSwitch("disable-network-service"); + parsedCommandLine->RemoveSwitch("disable-network-service"); + if (!enableNetworkService) + appendToFeatureList(disableFeatures, network::features::kNetworkService.name); + // BlinkGenPropertyTrees is enabled by default in 75, but causes regressions. appendToFeatureList(disableFeatures, blink::features::kBlinkGenPropertyTrees.name); @@ -690,6 +695,9 @@ WebEngineContext::WebEngineContext() m_serviceManagerEnvironment = std::make_unique<content::ServiceManagerEnvironment>(content::BrowserTaskExecutor::CreateIOThread()); m_startupData = m_serviceManagerEnvironment->CreateBrowserStartupData(); + if (base::FeatureList::IsEnabled(network::features::kNetworkService)) + content::ForceInProcessNetworkService(true); + // Once the MessageLoop has been created, attach a top-level RunLoop. m_runLoop.reset(new base::RunLoop); m_runLoop->BeforeRun(); diff --git a/src/pdf/api/qpdfbookmarkmodel.h b/src/pdf/api/qpdfbookmarkmodel.h new file mode 100644 index 000000000..503a29100 --- /dev/null +++ b/src/pdf/api/qpdfbookmarkmodel.h @@ -0,0 +1,98 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtPDF module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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.LGPLv3 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.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 later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPDFBOOKMARKMODEL_H +#define QPDFBOOKMARKMODEL_H + +#include <QtPdf/qtpdfglobal.h> +#include <QAbstractItemModel> + +QT_BEGIN_NAMESPACE + +class QPdfDocument; +class QPdfBookmarkModelPrivate; + +class Q_PDF_EXPORT QPdfBookmarkModel : public QAbstractItemModel +{ + Q_OBJECT + + Q_PROPERTY(QPdfDocument* document READ document WRITE setDocument NOTIFY documentChanged) + Q_PROPERTY(StructureMode structureMode READ structureMode WRITE setStructureMode NOTIFY structureModeChanged) + +public: + enum StructureMode + { + TreeMode, + ListMode + }; + Q_ENUM(StructureMode) + + enum Role + { + TitleRole = Qt::DisplayRole, + LevelRole = Qt::UserRole, + PageNumberRole + }; + Q_ENUM(Role) + + explicit QPdfBookmarkModel(QObject *parent = nullptr); + + QPdfDocument* document() const; + void setDocument(QPdfDocument *document); + + StructureMode structureMode() const; + void setStructureMode(StructureMode mode); + + QVariant data(const QModelIndex &index, int role) const override; + QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; + QModelIndex parent(const QModelIndex &index) const override; + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + int columnCount(const QModelIndex &parent = QModelIndex()) const override; + QHash<int, QByteArray> roleNames() const override; + +Q_SIGNALS: + void documentChanged(QPdfDocument *document); + void structureModeChanged(QPdfBookmarkModel::StructureMode structureMode); + +private: + Q_DECLARE_PRIVATE(QPdfBookmarkModel) + + Q_PRIVATE_SLOT(d_func(), void _q_documentStatusChanged()) +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/pdf/api/qpdfdocument.h b/src/pdf/api/qpdfdocument.h new file mode 100644 index 000000000..1252fa6d2 --- /dev/null +++ b/src/pdf/api/qpdfdocument.h @@ -0,0 +1,131 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtPDF module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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.LGPLv3 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.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 later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPDFDOCUMENT_H +#define QPDFDOCUMENT_H + +#include <QtPdf/qtpdfglobal.h> +#include <QImage> +#include <QObject> +#include <QtPdf/QPdfDocumentRenderOptions> + +QT_BEGIN_NAMESPACE + +class QPdfDocumentPrivate; +class QNetworkReply; + +class Q_PDF_EXPORT QPdfDocument : public QObject +{ + Q_OBJECT + + Q_PROPERTY(int pageCount READ pageCount NOTIFY pageCountChanged FINAL) + Q_PROPERTY(QString password READ password WRITE setPassword NOTIFY passwordChanged FINAL) + Q_PROPERTY(Status status READ status NOTIFY statusChanged FINAL) + +public: + enum Status { + Null, + Loading, + Ready, + Unloading, + Error + }; + Q_ENUM(Status) + + enum DocumentError { + NoError, + UnknownError, + DataNotYetAvailableError, + FileNotFoundError, + InvalidFileFormatError, + IncorrectPasswordError, + UnsupportedSecuritySchemeError + }; + Q_ENUM(DocumentError) + + enum MetaDataField { + Title, + Subject, + Author, + Keywords, + Producer, + Creator, + CreationDate, + ModificationDate + }; + Q_ENUM(MetaDataField) + + explicit QPdfDocument(QObject *parent = nullptr); + ~QPdfDocument(); + + DocumentError load(const QString &fileName); + + Status status() const; + + void load(QIODevice *device); + void setPassword(const QString &password); + QString password() const; + + QVariant metaData(MetaDataField field) const; + + DocumentError error() const; + + void close(); + + int pageCount() const; + + QSizeF pageSize(int page) const; + + QImage render(int page, QSize imageSize, QPdfDocumentRenderOptions options = QPdfDocumentRenderOptions()); + +Q_SIGNALS: + void passwordChanged(); + void passwordRequired(); + void statusChanged(QPdfDocument::Status status); + void pageCountChanged(int pageCount); + +private: + friend class QPdfBookmarkModelPrivate; + friend class QPdfSearchModel; + + Q_PRIVATE_SLOT(d, void _q_tryLoadingWithSizeFromContentHeader()) + Q_PRIVATE_SLOT(d, void _q_copyFromSequentialSourceDevice()) + QScopedPointer<QPdfDocumentPrivate> d; +}; + +QT_END_NAMESPACE + +#endif // QPDFDOCUMENT_H diff --git a/src/pdf/api/qpdfdocument_p.h b/src/pdf/api/qpdfdocument_p.h new file mode 100644 index 000000000..928754210 --- /dev/null +++ b/src/pdf/api/qpdfdocument_p.h @@ -0,0 +1,111 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtPDF module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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.LGPLv3 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.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 later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPDFDOCUMENT_P_H +#define QPDFDOCUMENT_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qpdfdocument.h" + +#include "third_party/pdfium/public/fpdfview.h" +#include "third_party/pdfium/public/fpdf_dataavail.h" + +#include <qbuffer.h> +#include <qmutex.h> +#include <qnetworkreply.h> +#include <qpointer.h> + +QT_BEGIN_NAMESPACE + +class QPdfMutexLocker : public QMutexLocker +{ +public: + QPdfMutexLocker(); +}; + +class QPdfDocumentPrivate: public FPDF_FILEACCESS, public FX_FILEAVAIL, public FX_DOWNLOADHINTS +{ +public: + QPdfDocumentPrivate(); + ~QPdfDocumentPrivate(); + + QPdfDocument *q; + + FPDF_AVAIL avail; + FPDF_DOCUMENT doc; + bool loadComplete; + + QPointer<QIODevice> device; + QScopedPointer<QIODevice> ownDevice; + QBuffer asyncBuffer; + QPointer<QIODevice> sequentialSourceDevice; + QByteArray password; + + QPdfDocument::Status status; + QPdfDocument::DocumentError lastError; + int pageCount; + + void clear(); + + void load(QIODevice *device, bool ownDevice); + void loadAsync(QIODevice *device); + + void _q_tryLoadingWithSizeFromContentHeader(); + void initiateAsyncLoadWithTotalSizeKnown(quint64 totalSize); + void _q_copyFromSequentialSourceDevice(); + void tryLoadDocument(); + void checkComplete(); + void setStatus(QPdfDocument::Status status); + + static FPDF_BOOL fpdf_IsDataAvail(struct _FX_FILEAVAIL* pThis, size_t offset, size_t size); + static int fpdf_GetBlock(void* param, unsigned long position, unsigned char* pBuf, unsigned long size); + static void fpdf_AddSegment(struct _FX_DOWNLOADHINTS* pThis, size_t offset, size_t size); + void updateLastError(); +}; + +QT_END_NAMESPACE + +#endif // QPDFDOCUMENT_P_H diff --git a/src/pdf/api/qpdfdocumentrenderoptions.h b/src/pdf/api/qpdfdocumentrenderoptions.h new file mode 100644 index 000000000..99a5db2e3 --- /dev/null +++ b/src/pdf/api/qpdfdocumentrenderoptions.h @@ -0,0 +1,89 @@ +/**************************************************************************** +** +** Copyright (C) 2017 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Tobias König <tobias.koenig@kdab.com> +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtPDF module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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.LGPLv3 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.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 later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPDFDOCUMENTRENDEROPTIONS_H +#define QPDFDOCUMENTRENDEROPTIONS_H + +#include <QtPdf/qpdfnamespace.h> +#include <QtCore/QObject> + +QT_BEGIN_NAMESPACE + +class QPdfDocumentRenderOptions +{ +public: + Q_DECL_CONSTEXPR QPdfDocumentRenderOptions() Q_DECL_NOTHROW : data(0) {} + + Q_DECL_CONSTEXPR QPdf::Rotation rotation() const Q_DECL_NOTHROW { return static_cast<QPdf::Rotation>(bits.rotation); } + Q_DECL_RELAXED_CONSTEXPR void setRotation(QPdf::Rotation _rotation) Q_DECL_NOTHROW { bits.rotation = _rotation; } + + Q_DECL_CONSTEXPR QPdf::RenderFlags renderFlags() const Q_DECL_NOTHROW { return static_cast<QPdf::RenderFlags>(bits.renderFlags); } + Q_DECL_RELAXED_CONSTEXPR void setRenderFlags(QPdf::RenderFlags _renderFlags) Q_DECL_NOTHROW { bits.renderFlags = _renderFlags; } + +private: + friend Q_DECL_CONSTEXPR inline bool operator==(QPdfDocumentRenderOptions lhs, QPdfDocumentRenderOptions rhs) Q_DECL_NOTHROW; + + + struct Bits { + quint32 renderFlags : 8; + quint32 rotation : 3; + quint32 reserved : 21; + quint32 reserved2 : 32; + }; + + union { + Bits bits; + quint64 data; + }; +}; + +Q_DECLARE_TYPEINFO(QPdfDocumentRenderOptions, Q_PRIMITIVE_TYPE); + +Q_DECL_CONSTEXPR inline bool operator==(QPdfDocumentRenderOptions lhs, QPdfDocumentRenderOptions rhs) Q_DECL_NOTHROW +{ + return lhs.data == rhs.data; +} + +Q_DECL_CONSTEXPR inline bool operator!=(QPdfDocumentRenderOptions lhs, QPdfDocumentRenderOptions rhs) Q_DECL_NOTHROW +{ + return !operator==(lhs, rhs); +} + +QT_END_NAMESPACE + +Q_DECLARE_METATYPE(QPdfDocumentRenderOptions) + +#endif // QPDFDOCUMENTRENDEROPTIONS_H diff --git a/src/pdf/api/qpdfnamespace.h b/src/pdf/api/qpdfnamespace.h new file mode 100644 index 000000000..c8fd8a580 --- /dev/null +++ b/src/pdf/api/qpdfnamespace.h @@ -0,0 +1,72 @@ +/**************************************************************************** +** +** Copyright (C) 2017 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Tobias König <tobias.koenig@kdab.com> +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtPDF module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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.LGPLv3 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.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 later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPDFNAMESPACE_H +#define QPDFNAMESPACE_H + +#include <QObject> + +QT_BEGIN_NAMESPACE + +namespace QPdf { + Q_NAMESPACE + + enum Rotation { + Rotate0, + Rotate90, + Rotate180, + Rotate270 + }; + Q_ENUM_NS(Rotation) + + enum RenderFlag { + NoRenderFlags = 0x000, + RenderAnnotations = 0x001, + RenderOptimizedForLcd = 0x002, + RenderGrayscale = 0x004, + RenderForceHalftone = 0x008, + RenderTextAliased = 0x010, + RenderImageAliased = 0x020, + RenderPathAliased = 0x040 + }; + Q_FLAG_NS(RenderFlag) + Q_DECLARE_FLAGS(RenderFlags, RenderFlag) +} + +Q_DECLARE_OPERATORS_FOR_FLAGS(QPdf::RenderFlags) + +QT_END_NAMESPACE +#endif diff --git a/src/pdf/api/qpdfpagenavigation.h b/src/pdf/api/qpdfpagenavigation.h new file mode 100644 index 000000000..8da809646 --- /dev/null +++ b/src/pdf/api/qpdfpagenavigation.h @@ -0,0 +1,91 @@ +/**************************************************************************** +** +** Copyright (C) 2017 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Tobias König <tobias.koenig@kdab.com> +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtPDF module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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.LGPLv3 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.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 later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPDFPAGENAVIGATION_H +#define QPDFPAGENAVIGATION_H + +#include <QtPdf/qtpdfglobal.h> +#include <QObject> + +QT_BEGIN_NAMESPACE + +class QPdfDocument; +class QPdfPageNavigationPrivate; + +class Q_PDF_EXPORT QPdfPageNavigation : public QObject +{ + Q_OBJECT + + Q_PROPERTY(QPdfDocument* document READ document WRITE setDocument NOTIFY documentChanged) + + Q_PROPERTY(int currentPage READ currentPage WRITE setCurrentPage NOTIFY currentPageChanged) + Q_PROPERTY(int pageCount READ pageCount NOTIFY pageCountChanged) + Q_PROPERTY(bool canGoToPreviousPage READ canGoToPreviousPage NOTIFY canGoToPreviousPageChanged) + Q_PROPERTY(bool canGoToNextPage READ canGoToNextPage NOTIFY canGoToNextPageChanged) + +public: + explicit QPdfPageNavigation(QObject *parent = nullptr); + ~QPdfPageNavigation(); + + QPdfDocument* document() const; + void setDocument(QPdfDocument *document); + + int currentPage() const; + void setCurrentPage(int currentPage); + + int pageCount() const; + + bool canGoToPreviousPage() const; + bool canGoToNextPage() const; + +public Q_SLOTS: + void goToPreviousPage(); + void goToNextPage(); + +Q_SIGNALS: + void documentChanged(QPdfDocument *document); + void currentPageChanged(int currentPage); + void pageCountChanged(int pageCount); + void canGoToPreviousPageChanged(bool canGo); + void canGoToNextPageChanged(bool canGo); + +private: + Q_DECLARE_PRIVATE(QPdfPageNavigation) +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/pdf/api/qpdfpagerenderer.h b/src/pdf/api/qpdfpagerenderer.h new file mode 100644 index 000000000..de6704480 --- /dev/null +++ b/src/pdf/api/qpdfpagerenderer.h @@ -0,0 +1,90 @@ +/**************************************************************************** +** +** Copyright (C) 2017 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Tobias König <tobias.koenig@kdab.com> +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtPDF module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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.LGPLv3 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.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 later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPDFPAGERENDERER_H +#define QPDFPAGERENDERER_H + +#include <QtPdf/qtpdfglobal.h> +#include <QObject> +#include <QtPdf/QPdfDocumentRenderOptions> +#include <QSize> + +QT_BEGIN_NAMESPACE + +class QPdfDocument; +class QPdfPageRendererPrivate; + +class Q_PDF_EXPORT QPdfPageRenderer : public QObject +{ + Q_OBJECT + + Q_PROPERTY(QPdfDocument* document READ document WRITE setDocument NOTIFY documentChanged) + Q_PROPERTY(RenderMode renderMode READ renderMode WRITE setRenderMode NOTIFY renderModeChanged) + +public: + enum RenderMode + { + MultiThreadedRenderMode, + SingleThreadedRenderMode + }; + Q_ENUM(RenderMode) + + explicit QPdfPageRenderer(QObject *parent = nullptr); + ~QPdfPageRenderer(); + + RenderMode renderMode() const; + void setRenderMode(RenderMode mode); + + QPdfDocument* document() const; + void setDocument(QPdfDocument *document); + + quint64 requestPage(int pageNumber, QSize imageSize, + QPdfDocumentRenderOptions options = QPdfDocumentRenderOptions()); + +Q_SIGNALS: + void documentChanged(QPdfDocument *document); + void renderModeChanged(RenderMode renderMode); + + void pageRendered(int pageNumber, QSize imageSize, const QImage &image, + QPdfDocumentRenderOptions options, quint64 requestId); + +private: + Q_DECLARE_PRIVATE(QPdfPageRenderer) +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/pdf/api/qpdfsearchmodel.h b/src/pdf/api/qpdfsearchmodel.h new file mode 100644 index 000000000..02d2a20d5 --- /dev/null +++ b/src/pdf/api/qpdfsearchmodel.h @@ -0,0 +1,74 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtPDF module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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.LGPLv3 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.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 later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPDFSEARCHMODEL_H +#define QPDFSEARCHMODEL_H + +#include "qtpdfglobal.h" +#include "qpdfdocument.h" + +#include <QObject> + +QT_BEGIN_NAMESPACE + +class QPdfSearchModelPrivate; + +class Q_PDF_EXPORT QPdfSearchModel : public QObject // TODO QAIM? +{ + Q_OBJECT + Q_PROPERTY(QPdfDocument *document READ document WRITE setDocument NOTIFY documentChanged) + +public: + explicit QPdfSearchModel(QObject *parent = nullptr); + ~QPdfSearchModel(); + + QVector<QRectF> matches(int page, const QString &searchString); + + QPdfDocument *document() const; + +public Q_SLOTS: + void setDocument(QPdfDocument *document); + +Q_SIGNALS: + void documentChanged(); + +private: + QScopedPointer<QPdfSearchModelPrivate> d; +}; + +QT_END_NAMESPACE + +#endif // QPDFSEARCHMODEL_H diff --git a/src/pdf/api/qtpdfglobal.h b/src/pdf/api/qtpdfglobal.h new file mode 100644 index 000000000..34cfc46d9 --- /dev/null +++ b/src/pdf/api/qtpdfglobal.h @@ -0,0 +1,59 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtPDF module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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.LGPLv3 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.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 later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QTPDFGLOBAL_H +#define QTPDFGLOBAL_H + +#include <QtCore/qglobal.h> + +QT_BEGIN_NAMESPACE + +#ifndef Q_PDF_EXPORT +# ifndef QT_STATIC +# if defined(QT_BUILD_PDF_LIB) +# define Q_PDF_EXPORT Q_DECL_EXPORT +# else +# define Q_PDF_EXPORT Q_DECL_IMPORT +# endif +# else +# define Q_PDF_EXPORT +# endif +#endif + +QT_END_NAMESPACE + +#endif // QTPDFGLOBAL_H + diff --git a/src/pdf/configure.json b/src/pdf/configure.json new file mode 100644 index 000000000..1f21183a2 --- /dev/null +++ b/src/pdf/configure.json @@ -0,0 +1,64 @@ +{ + "module": "pdf", + "depends" : [ "buildtools-private" ], + "testDir": "../../config.tests", + "condition": "features.build-qtpdf", + "libraries": { + }, + "tests": { + }, + "features": { + "pdf-v8": { + "label": "Support V8", + "purpose": "Enables javascript support.", + "autoDetect": "false", + "condition": "!config.ios", + "output": ["privateFeature" ] + }, + "pdf-xfa": { + "label": "Support XFA", + "purpose": "Enables XFA support.", + "condition": "features.pdf-v8", + "output": ["privateFeature" ] + }, + "pdf-xfa-bmp": { + "label": "Support XFA-BMP", + "purpose": "Enables XFA-BMP support.", + "condition": "features.pdf-xfa", + "output": ["privateFeature" ] + }, + "pdf-xfa-gif": { + "label": "Support XFA-GIF", + "purpose": "Enables XFA-GIF support.", + "condition": "features.pdf-xfa", + "output": ["privateFeature" ] + }, + "pdf-xfa-png": { + "label": "Support XFA-PNG", + "purpose": "Enables XFA-PNG support.", + "condition": "features.pdf-xfa", + "output": ["privateFeature" ] + }, + "pdf-xfa-tiff": { + "label": "Support XFA-TIFF", + "purpose": "Enables XFA-TIFF support.", + "condition": "features.pdf-xfa", + "output": ["privateFeature" ] + } + }, + "report": [ + ], + "summary": [ + { + "section": "Qt PDF", + "entries": [ + "pdf-v8", + "pdf-xfa", + "pdf-xfa-bmp", + "pdf-xfa-gif", + "pdf-xfa-png", + "pdf-xfa-tiff" + ] + } + ] +} diff --git a/src/pdf/doc/qtpdf.qdocconf b/src/pdf/doc/qtpdf.qdocconf new file mode 100644 index 000000000..b55b25327 --- /dev/null +++ b/src/pdf/doc/qtpdf.qdocconf @@ -0,0 +1,55 @@ +include($QT_INSTALL_DOCS/global/qt-module-defaults.qdocconf) + +project = QtPdf +description = Qt Pdf Reference Documentation +version = $QT_VERSION + +qhp.projects = QtPdf + +qhp.QtPdf.file = qtpdf.qhp +qhp.QtPdf.namespace = org.qt-project.qtpdf.$QT_VERSION_TAG +qhp.QtPdf.virtualFolder = qtpdf +qhp.QtPdf.indexTitle = Qt PDF +qhp.QtPdf.indexRoot = + +qhp.QtPdf.filterAttributes = qtpdf $QT_VERSION qtrefdoc +qhp.QtPdf.customFilters.Qt.name = QtPdf $QT_VERSION +qhp.QtPdf.customFilters.Qt.filterAttributes = qtpdf $QT_VERSION + +qhp.QtPdf.subprojects = classes qmltypes examples + +qhp.QtPdf.subprojects.classes.title = C++ Classes +qhp.QtPdf.subprojects.classes.indexTitle = Qt PDF C++ Classes +qhp.QtPdf.subprojects.classes.selectors = class fake:headerfile +qhp.QtPdf.subprojects.classes.sortPages = true + +qhp.QtPdf.subprojects.qmltypes.title = QML Types +qhp.QtPdf.subprojects.qmltypes.indexTitle = Qt Quick PDF QML Types +qhp.QtPdf.subprojects.qmltypes.selectors = qmltype +qhp.QtPdf.subprojects.qmltypes.sortPages = true + +qhp.QtPdf.subprojects.examples.title = Examples +qhp.QtPdf.subprojects.examples.indexTitle = Qt PDF Examples +qhp.QtPdf.subprojects.examples.selectors = doc:example +qhp.QtPdf.subprojects.examples.sortPages = true + +depends += qtcore \ + qtwidgets \ + qtgui \ + qtdoc \ + qmake + +headerdirs += ../api \ + ../quick + +sourcedirs += .. \ + ../quick + +exampledirs += ../../../examples/pdfwidgets \ + snippets/ + +imagedirs += images + +navigation.landingpage = "Qt PDF" +navigation.cppclassespage = "Qt PDF C++ Classes" +navigation.qmltypespage = "Qt WebEngine QML Types" diff --git a/src/pdf/doc/snippets/qtpdf_build_snippet.qdoc b/src/pdf/doc/snippets/qtpdf_build_snippet.qdoc new file mode 100644 index 000000000..25593b1ee --- /dev/null +++ b/src/pdf/doc/snippets/qtpdf_build_snippet.qdoc @@ -0,0 +1,35 @@ +/**************************************************************************** +** +** Copyright (C) 2019 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$ +** +****************************************************************************/ + +//! [0] +QT += pdf +//! [0] + + +//! [1] +#include <QtPdf> +//! [1] diff --git a/src/pdf/doc/src/qtpdf-index.qdoc b/src/pdf/doc/src/qtpdf-index.qdoc new file mode 100644 index 000000000..6893272ac --- /dev/null +++ b/src/pdf/doc/src/qtpdf-index.qdoc @@ -0,0 +1,65 @@ +/**************************************************************************** +** +** Copyright (C) 2019 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$ +** +****************************************************************************/ + +/*! + \page qtpdf-index.html + \title Qt PDF + + \brief Renders pages from PDF documents. + + The Qt PDF module contains classes and functions for rendering + PDF documents. The \l QPdfDocument class loads a PDF document + and renders pages from it according to the options provided by + the \l QPdfDocumentRenderOptions class. The \l QPdfPageRenderer + class manages a queue that collects all render requests. The + \l QPdfPageNavigation class handles the navigation through a + PDF document. + + \section1 Getting Started + + To include the definitions of the module's classes, use the + following directive: + + \snippet qtpdf_build_snippet.qdoc 1 + + To link against the module, add this line to your qmake project file: + + \snippet qtpdf_build_snippet.qdoc 0 + + \section1 Articles and Guides + + \list + \li \l{Qt PDF Overview} + \endlist + + \section1 API Reference + + \list + \li \l{Qt PDF C++ Classes} + \li \l{Qt Quick PDF QML Types} + \endlist +*/ diff --git a/src/pdf/doc/src/qtpdf-module.qdoc b/src/pdf/doc/src/qtpdf-module.qdoc new file mode 100644 index 000000000..4170deb38 --- /dev/null +++ b/src/pdf/doc/src/qtpdf-module.qdoc @@ -0,0 +1,50 @@ +/**************************************************************************** +** +** Copyright (C) 2019 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$ +** +****************************************************************************/ + + +/*! + \module QtPdf + \title Qt PDF C++ Classes + \brief Renders pages from PDF documents. + \since 5.14 + \ingroup qtwebengine-modules + \ingroup modules + + The Qt PDF module contains classes and functions for rendering + PDF documents. + + To include the definitions of the module's classes, use the + following directive: + + \snippet qtpdf_build_snippet.qdoc 1 + + \if !defined(qtforpython) + To link against the module, add this line to your qmake project file: + + \snippet qtpdf_build_snippet.qdoc 0 + \endif +*/ diff --git a/src/pdf/gn_run.pro b/src/pdf/gn_run.pro new file mode 100644 index 000000000..7fd4d4ffc --- /dev/null +++ b/src/pdf/gn_run.pro @@ -0,0 +1,70 @@ +include($$QTWEBENGINE_OUT_ROOT/src/buildtools/qtbuildtools-config.pri) +QT_FOR_CONFIG += buildtools-private + +TEMPLATE = aux + +qtConfig(debug_and_release): CONFIG += debug_and_release build_all + +qtConfig(webengine-system-ninja) { + QT_TOOL.ninja.binary = ninja +} else { + QT_TOOL.ninja.binary = $$shell_quote($$shell_path($$ninjaPath())) +} + +win32 { + # Add the gnuwin32/bin subdir of qt5.git to PATH. Needed for calling bison and friends. + gnuwin32path.name = PATH + gnuwin32path.value = $$shell_path($$clean_path($$QTWEBENGINE_ROOT/../gnuwin32/bin)) + gnuwin32path.CONFIG += prepend + exists($$gnuwin32path.value): QT_TOOL_ENV = gnuwin32path +} + +qtPrepareTool(NINJA, ninja) +QT_TOOL_ENV = + +build_pass|!debug_and_release { + gn_binary = gn + + runninja.target = run_ninja + + # fixme: refine args + gn_args = $$gnArgs() + + include($$QTWEBENGINE_ROOT/src/buildtools/config/pdf.pri) + + # fixme: qtwebengine_target + gn_args += "qtwebengine_target=\"$$system_path($$OUT_PWD/$$getConfigDir()):QtPdf\"" + + # fixme: + !qtConfig(webengine-system-gn) { + gn_binary = $$system_quote($$system_path($$gnPath())) + } + + gn_args = $$system_quote($$gn_args) + gn_src_root = $$system_quote($$system_path($$QTWEBENGINE_ROOT/$$getChromiumSrcDir())) + gn_build_root = $$system_quote($$system_path($$OUT_PWD/$$getConfigDir())) + gn_python = "--script-executable=$$pythonPathForSystem()" + gn_run = $$gn_binary gen $$gn_build_root $$gn_python --args=$$gn_args --root=$$gn_src_root + + message("Running: $$gn_run ") + !system($$gn_run) { + error("GN run error!") + } + + ninjaflags = $$(NINJAFLAGS) + isEmpty(ninjaflags):!silent: ninjaflags = "-v" + + runninja.commands = $$NINJA $$ninjaflags -C $$gn_build_root QtPdf + QMAKE_EXTRA_TARGETS += runninja + + build_pass:build_all: default_target.target = all + else: default_target.target = first + default_target.depends = runninja + QMAKE_EXTRA_TARGETS += default_target +} + +!build_pass:debug_and_release { + # Special GNU make target for the meta Makefile that ensures that our debug and release Makefiles won't both run ninja in parallel. + notParallel.target = .NOTPARALLEL + QMAKE_EXTRA_TARGETS += notParallel +} diff --git a/src/pdf/jsbridge.cpp b/src/pdf/jsbridge.cpp new file mode 100644 index 000000000..95b813929 --- /dev/null +++ b/src/pdf/jsbridge.cpp @@ -0,0 +1,38 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtPDF module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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.LGPLv3 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.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 later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "fpdfsdk/javascript/JS_Runtime_Stub.cpp" + diff --git a/src/pdf/pdf.pro b/src/pdf/pdf.pro new file mode 100644 index 000000000..c5513ce93 --- /dev/null +++ b/src/pdf/pdf.pro @@ -0,0 +1,16 @@ +TEMPLATE = subdirs +pdfcore.file = pdfcore.pro +pdfcore_generator.file = pdfcore_generator.pro +gn_run.file = gn_run.pro + +gn_run.depends = pdfcore_generator +pdfcore.depends = gn_run +quick.depends = pdfcore + +SUBDIRS += \ + pdfcore_generator \ + gn_run \ + pdfcore \ + quick + + diff --git a/src/pdf/pdfcore.pro b/src/pdf/pdfcore.pro new file mode 100644 index 000000000..812b7fc14 --- /dev/null +++ b/src/pdf/pdfcore.pro @@ -0,0 +1,81 @@ +TARGET = QtPdf +MODULE = pdf + +QT += gui core core-private +QT_PRIVATE += network + +TEMPLATE = lib + +INCLUDEPATH += $$QTWEBENGINE_ROOT/src/pdf +CHROMIUM_SRC_DIR = $$QTWEBENGINE_ROOT/$$getChromiumSrcDir() +CHROMIUM_GEN_DIR = $$OUT_PWD/../$$getConfigDir()/gen +INCLUDEPATH += $$QTWEBENGINE_ROOT/src/pdf \ + $$CHROMIUM_GEN_DIR \ + $$CHROMIUM_SRC_DIR \ + api + +DEFINES += QT_BUILD_PDF_LIB +win32: DEFINES += NOMINMAX + +linking_pri = $$OUT_PWD/$$getConfigDir()/$${TARGET}.pri +!include($$linking_pri) { + error("Could not find the linking information that gn should have generated.") +} + +isEmpty(NINJA_OBJECTS): error("Missing object files from QtPdf linking pri.") +isEmpty(NINJA_LFLAGS): error("Missing linker flags from QtPdf linking pri") +isEmpty(NINJA_LIBS): error("Missing library files from QtPdf linking pri") + +NINJA_OBJECTS = $$eval($$list($$NINJA_OBJECTS)) +RSP_FILE = $$OUT_PWD/$$getConfigDir()/$${TARGET}.rsp +for(object, NINJA_OBJECTS): RSP_CONTENT += $$object +write_file($$RSP_FILE, RSP_CONTENT) + +macos:LIBS_PRIVATE += -Wl,-filelist,$$shell_quote($$RSP_FILE) +linux:LIBS_PRIVATE += @$$RSP_FILE + +# QTBUG-58710 add main rsp file on windows +win32:QMAKE_LFLAGS += @$$RSP_FILE + +!isEmpty(NINJA_ARCHIVES) { + linux: LIBS_PRIVATE += -Wl,--start-group $$NINJA_ARCHIVES -Wl,--end-group + else: LIBS_PRIVATE += $$NINJA_ARCHIVES +} + +LIBS_PRIVATE += $$NINJA_LIB_DIRS $$NINJA_LIBS + +QMAKE_DOCS = $$PWD/doc/qtpdf.qdocconf + +gcc { + QMAKE_CXXFLAGS_WARN_ON += -Wno-unused-parameter +} + +msvc { + QMAKE_CXXFLAGS_WARN_ON += -wd"4100" +} + +ios: OBJECTS += $$NINJA_OBJECTS + +SOURCES += \ + qpdfbookmarkmodel.cpp \ + qpdfdocument.cpp \ + qpdfpagenavigation.cpp \ + qpdfpagerenderer.cpp \ + qpdfsearchmodel.cpp \ + +# all "public" headers must be in "api" for sync script and to hide auto generated headers +# by Chromium in case of in-source build + +HEADERS += \ + api/qpdfbookmarkmodel.h \ + api/qpdfdocument.h \ + api/qpdfdocument_p.h \ + api/qpdfdocumentrenderoptions.h \ + api/qtpdfglobal.h \ + api/qpdfnamespace.h \ + api/qpdfpagenavigation.h \ + api/qpdfpagerenderer.h \ + api/qpdfsearchmodel.h \ + qpdfsearchmodel_p.h \ + +load(qt_module) diff --git a/src/pdf/pdfcore_generator.pro b/src/pdf/pdfcore_generator.pro new file mode 100644 index 000000000..c8eb13b81 --- /dev/null +++ b/src/pdf/pdfcore_generator.pro @@ -0,0 +1,15 @@ +qtConfig(debug_and_release): CONFIG += debug_and_release + +TARGET = QtPdf +TEMPLATE = lib +CONFIG = gn_generator $$CONFIG +ios: CONFIG -=static # note we still do static on ios when linking +GN_SRC_DIR = $$PWD +GN_FILE = $$OUT_PWD/$$getConfigDir()/BUILD.gn +GN_FIND_MOCABLES_SCRIPT = $$shell_path($$QTWEBENGINE_ROOT/tools/scripts/gn_find_mocables.py) +GN_RUN_BINARY_SCRIPT = $$shell_path($$QTWEBENGINE_ROOT/tools/scripts/gn_run_binary.py) +GN_IMPORTS = $$PWD/qtpdf.gni +GN_CREATE_PRI = true +QMAKE_INTERNAL_INCLUDED_FILES = $$GN_IMPORTS $$GN_INCLUDES $$GN_FILE + + diff --git a/src/pdf/qpdfbookmarkmodel.cpp b/src/pdf/qpdfbookmarkmodel.cpp new file mode 100644 index 000000000..e648657ba --- /dev/null +++ b/src/pdf/qpdfbookmarkmodel.cpp @@ -0,0 +1,363 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtPDF module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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.LGPLv3 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.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 later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qpdfbookmarkmodel.h" + +#include "qpdfdocument.h" +#include "qpdfdocument_p.h" + +#include "third_party/pdfium/public/fpdf_doc.h" +#include "third_party/pdfium/public/fpdfview.h" + +#include <QPointer> +#include <QScopedPointer> +#include <private/qabstractitemmodel_p.h> + +QT_BEGIN_NAMESPACE + +class BookmarkNode +{ +public: + explicit BookmarkNode(BookmarkNode *parentNode = nullptr) + : m_parentNode(parentNode) + , m_level(0) + , m_pageNumber(0) + { + } + + ~BookmarkNode() + { + clear(); + } + + void clear() + { + qDeleteAll(m_childNodes); + m_childNodes.clear(); + } + + void appendChild(BookmarkNode *child) + { + m_childNodes.append(child); + } + + BookmarkNode *child(int row) const + { + return m_childNodes.at(row); + } + + int childCount() const + { + return m_childNodes.count(); + } + + int row() const + { + if (m_parentNode) + return m_parentNode->m_childNodes.indexOf(const_cast<BookmarkNode*>(this)); + + return 0; + } + + BookmarkNode *parentNode() const + { + return m_parentNode; + } + + QString title() const + { + return m_title; + } + + void setTitle(const QString &title) + { + m_title = title; + } + + int level() const + { + return m_level; + } + + void setLevel(int level) + { + m_level = level; + } + + int pageNumber() const + { + return m_pageNumber; + } + + void setPageNumber(int pageNumber) + { + m_pageNumber = pageNumber; + } + +private: + QVector<BookmarkNode*> m_childNodes; + BookmarkNode *m_parentNode; + + QString m_title; + int m_level; + int m_pageNumber; +}; + + +class QPdfBookmarkModelPrivate : public QAbstractItemModelPrivate +{ +public: + QPdfBookmarkModelPrivate() + : QAbstractItemModelPrivate() + , m_rootNode(new BookmarkNode(nullptr)) + , m_document(nullptr) + , m_structureMode(QPdfBookmarkModel::TreeMode) + { + } + + void rebuild() + { + Q_Q(QPdfBookmarkModel); + + const bool documentAvailable = (m_document && m_document->status() == QPdfDocument::Ready); + + if (documentAvailable) { + q->beginResetModel(); + m_rootNode->clear(); + QPdfMutexLocker lock; + appendChildNode(m_rootNode.data(), nullptr, 0, m_document->d->doc); + lock.unlock(); + q->endResetModel(); + } else { + if (m_rootNode->childCount() == 0) { + return; + } else { + q->beginResetModel(); + m_rootNode->clear(); + q->endResetModel(); + } + } + } + + void appendChildNode(BookmarkNode *parentBookmarkNode, FPDF_BOOKMARK parentBookmark, int level, FPDF_DOCUMENT document) + { + FPDF_BOOKMARK bookmark = FPDFBookmark_GetFirstChild(document, parentBookmark); + + while (bookmark) { + BookmarkNode *childBookmarkNode = nullptr; + + if (m_structureMode == QPdfBookmarkModel::TreeMode) { + childBookmarkNode = new BookmarkNode(parentBookmarkNode); + parentBookmarkNode->appendChild(childBookmarkNode); + } else if (m_structureMode == QPdfBookmarkModel::ListMode) { + childBookmarkNode = new BookmarkNode(m_rootNode.data()); + m_rootNode->appendChild(childBookmarkNode); + } + + const int titleLength = int(FPDFBookmark_GetTitle(bookmark, nullptr, 0)); + + QVector<ushort> titleBuffer(titleLength); + FPDFBookmark_GetTitle(bookmark, titleBuffer.data(), quint32(titleBuffer.length())); + + const FPDF_DEST dest = FPDFBookmark_GetDest(document, bookmark); + const int pageNumber = FPDFDest_GetDestPageIndex(document, dest); + + childBookmarkNode->setTitle(QString::fromUtf16(titleBuffer.data())); + childBookmarkNode->setLevel(level); + childBookmarkNode->setPageNumber(pageNumber); + + // recurse down + appendChildNode(childBookmarkNode, bookmark, level + 1, document); + + bookmark = FPDFBookmark_GetNextSibling(document, bookmark); + } + } + + void _q_documentStatusChanged() + { + rebuild(); + } + + Q_DECLARE_PUBLIC(QPdfBookmarkModel) + + QScopedPointer<BookmarkNode> m_rootNode; + QPointer<QPdfDocument> m_document; + QPdfBookmarkModel::StructureMode m_structureMode; +}; + + +QPdfBookmarkModel::QPdfBookmarkModel(QObject *parent) + : QAbstractItemModel(*new QPdfBookmarkModelPrivate, parent) +{ +} + +QPdfDocument* QPdfBookmarkModel::document() const +{ + Q_D(const QPdfBookmarkModel); + + return d->m_document; +} + +void QPdfBookmarkModel::setDocument(QPdfDocument *document) +{ + Q_D(QPdfBookmarkModel); + + if (d->m_document == document) + return; + + if (d->m_document) + disconnect(d->m_document, SIGNAL(statusChanged(QPdfDocument::Status)), this, SLOT(_q_documentStatusChanged())); + + d->m_document = document; + emit documentChanged(d->m_document); + + if (d->m_document) + connect(d->m_document, SIGNAL(statusChanged(QPdfDocument::Status)), this, SLOT(_q_documentStatusChanged())); + + d->rebuild(); +} + +QPdfBookmarkModel::StructureMode QPdfBookmarkModel::structureMode() const +{ + Q_D(const QPdfBookmarkModel); + + return d->m_structureMode; +} + +void QPdfBookmarkModel::setStructureMode(StructureMode mode) +{ + Q_D(QPdfBookmarkModel); + + if (d->m_structureMode == mode) + return; + + d->m_structureMode = mode; + emit structureModeChanged(d->m_structureMode); + + d->rebuild(); +} + +int QPdfBookmarkModel::columnCount(const QModelIndex &parent) const +{ + return 1; +} + +QHash<int, QByteArray> QPdfBookmarkModel::roleNames() const +{ + QHash<int, QByteArray> names; + + names[TitleRole] = "title"; + names[LevelRole] = "level"; + names[PageNumberRole] = "pageNumber"; + + return names; +} + +QVariant QPdfBookmarkModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) + return QVariant(); + + const BookmarkNode *node = static_cast<BookmarkNode*>(index.internalPointer()); + switch (role) { + case TitleRole: + return node->title(); + case LevelRole: + return node->level(); + case PageNumberRole: + return node->pageNumber(); + default: + return QVariant(); + } +} + +QModelIndex QPdfBookmarkModel::index(int row, int column, const QModelIndex &parent) const +{ + Q_D(const QPdfBookmarkModel); + + if (!hasIndex(row, column, parent)) + return QModelIndex(); + + BookmarkNode *parentNode; + + if (!parent.isValid()) + parentNode = d->m_rootNode.data(); + else + parentNode = static_cast<BookmarkNode*>(parent.internalPointer()); + + BookmarkNode *childNode = parentNode->child(row); + if (childNode) + return createIndex(row, column, childNode); + else + return QModelIndex(); +} + +QModelIndex QPdfBookmarkModel::parent(const QModelIndex &index) const +{ + Q_D(const QPdfBookmarkModel); + + if (!index.isValid()) + return QModelIndex(); + + const BookmarkNode *childNode = static_cast<BookmarkNode*>(index.internalPointer()); + BookmarkNode *parentNode = childNode->parentNode(); + + if (parentNode == d->m_rootNode.data()) + return QModelIndex(); + + return createIndex(parentNode->row(), 0, parentNode); +} + +int QPdfBookmarkModel::rowCount(const QModelIndex &parent) const +{ + Q_D(const QPdfBookmarkModel); + + if (parent.column() > 0) + return 0; + + BookmarkNode *parentNode = nullptr; + + if (!parent.isValid()) + parentNode = d->m_rootNode.data(); + else + parentNode = static_cast<BookmarkNode*>(parent.internalPointer()); + + return parentNode->childCount(); +} + +QT_END_NAMESPACE + +#include "moc_qpdfbookmarkmodel.cpp" diff --git a/src/pdf/qpdfdocument.cpp b/src/pdf/qpdfdocument.cpp new file mode 100644 index 000000000..690953691 --- /dev/null +++ b/src/pdf/qpdfdocument.cpp @@ -0,0 +1,644 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtPDF module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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.LGPLv3 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.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 later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qpdfdocument.h" +#include "qpdfdocument_p.h" + +#include "third_party/pdfium/public/fpdf_doc.h" + +#include <QDateTime> +#include <QDebug> +#include <QElapsedTimer> +#include <QFile> +#include <QHash> +#include <QLoggingCategory> +#include <QMutex> + +QT_BEGIN_NAMESPACE + +// The library is not thread-safe at all, it has a lot of global variables. +Q_GLOBAL_STATIC_WITH_ARGS(QMutex, pdfMutex, (QMutex::Recursive)); +static int libraryRefCount; +Q_LOGGING_CATEGORY(qLcDoc, "qt.pdf.document") + +QPdfMutexLocker::QPdfMutexLocker() + : QMutexLocker(pdfMutex()) +{ +} + +QPdfDocumentPrivate::QPdfDocumentPrivate() + : avail(nullptr) + , doc(nullptr) + , loadComplete(false) + , status(QPdfDocument::Null) + , lastError(QPdfDocument::NoError) + , pageCount(0) +{ + asyncBuffer.setData(QByteArray()); + asyncBuffer.open(QIODevice::ReadWrite); + + const QPdfMutexLocker lock; + + if (libraryRefCount == 0) + FPDF_InitLibrary(); + ++libraryRefCount; + + // FPDF_FILEACCESS setup + m_Param = this; + m_GetBlock = fpdf_GetBlock; + + // FX_FILEAVAIL setup + FX_FILEAVAIL::version = 1; + IsDataAvail = fpdf_IsDataAvail; + + // FX_DOWNLOADHINTS setup + FX_DOWNLOADHINTS::version = 1; + AddSegment = fpdf_AddSegment; +} + +QPdfDocumentPrivate::~QPdfDocumentPrivate() +{ + q->close(); + + const QPdfMutexLocker lock; + + if (!--libraryRefCount) + FPDF_DestroyLibrary(); +} + +void QPdfDocumentPrivate::clear() +{ + QPdfMutexLocker lock; + + if (doc) + FPDF_CloseDocument(doc); + doc = nullptr; + + if (avail) + FPDFAvail_Destroy(avail); + avail = nullptr; + lock.unlock(); + + if (pageCount != 0) { + pageCount = 0; + emit q->pageCountChanged(pageCount); + } + + loadComplete = false; + + asyncBuffer.close(); + asyncBuffer.setData(QByteArray()); + asyncBuffer.open(QIODevice::ReadWrite); + + if (sequentialSourceDevice) + sequentialSourceDevice->disconnect(q); +} + +void QPdfDocumentPrivate::updateLastError() +{ + if (doc) { + lastError = QPdfDocument::NoError; + return; + } + + QPdfMutexLocker lock; + const unsigned long error = FPDF_GetLastError(); + lock.unlock(); + + switch (error) { + case FPDF_ERR_SUCCESS: lastError = QPdfDocument::NoError; break; + case FPDF_ERR_UNKNOWN: lastError = QPdfDocument::UnknownError; break; + case FPDF_ERR_FILE: lastError = QPdfDocument::FileNotFoundError; break; + case FPDF_ERR_FORMAT: lastError = QPdfDocument::InvalidFileFormatError; break; + case FPDF_ERR_PASSWORD: lastError = QPdfDocument::IncorrectPasswordError; break; + case FPDF_ERR_SECURITY: lastError = QPdfDocument::UnsupportedSecuritySchemeError; break; + default: + Q_UNREACHABLE(); + } +} + +void QPdfDocumentPrivate::load(QIODevice *newDevice, bool transferDeviceOwnership) +{ + if (transferDeviceOwnership) + ownDevice.reset(newDevice); + else + ownDevice.reset(); + + if (newDevice->isSequential()) { + sequentialSourceDevice = newDevice; + device = &asyncBuffer; + QNetworkReply *reply = qobject_cast<QNetworkReply*>(sequentialSourceDevice); + + if (!reply) { + setStatus(QPdfDocument::Error); + qWarning() << "QPdfDocument: Loading from sequential devices only supported with QNetworkAccessManager."; + return; + } + + if (reply->isFinished() && reply->error() != QNetworkReply::NoError) { + setStatus(QPdfDocument::Error); + return; + } + + QObject::connect(reply, &QNetworkReply::finished, q, [this, reply](){ + if (reply->error() != QNetworkReply::NoError || reply->bytesAvailable() == 0) { + this->setStatus(QPdfDocument::Error); + } + }); + + if (reply->header(QNetworkRequest::ContentLengthHeader).isValid()) + _q_tryLoadingWithSizeFromContentHeader(); + else + QObject::connect(reply, SIGNAL(metaDataChanged()), q, SLOT(_q_tryLoadingWithSizeFromContentHeader())); + } else { + device = newDevice; + initiateAsyncLoadWithTotalSizeKnown(device->size()); + checkComplete(); + } +} + +void QPdfDocumentPrivate::_q_tryLoadingWithSizeFromContentHeader() +{ + if (avail) + return; + + const QNetworkReply *networkReply = qobject_cast<QNetworkReply*>(sequentialSourceDevice); + if (!networkReply) { + setStatus(QPdfDocument::Error); + return; + } + + const QVariant contentLength = networkReply->header(QNetworkRequest::ContentLengthHeader); + if (!contentLength.isValid()) { + setStatus(QPdfDocument::Error); + return; + } + + QObject::connect(sequentialSourceDevice, SIGNAL(readyRead()), q, SLOT(_q_copyFromSequentialSourceDevice())); + + initiateAsyncLoadWithTotalSizeKnown(contentLength.toULongLong()); + + if (sequentialSourceDevice->bytesAvailable()) + _q_copyFromSequentialSourceDevice(); +} + +void QPdfDocumentPrivate::initiateAsyncLoadWithTotalSizeKnown(quint64 totalSize) +{ + // FPDF_FILEACCESS setup + m_FileLen = totalSize; + + const QPdfMutexLocker lock; + + avail = FPDFAvail_Create(this, this); +} + +void QPdfDocumentPrivate::_q_copyFromSequentialSourceDevice() +{ + if (loadComplete) + return; + + const QByteArray data = sequentialSourceDevice->read(sequentialSourceDevice->bytesAvailable()); + if (data.isEmpty()) + return; + + asyncBuffer.seek(asyncBuffer.size()); + asyncBuffer.write(data); + + checkComplete(); +} + +void QPdfDocumentPrivate::tryLoadDocument() +{ + QPdfMutexLocker lock; + switch (FPDFAvail_IsDocAvail(avail, this)) { + case PDF_DATA_ERROR: + qCDebug(qLcDoc) << "error loading"; + break; + case PDF_DATA_NOTAVAIL: + qCDebug(qLcDoc) << "data not yet available"; + lastError = QPdfDocument::DataNotYetAvailableError; + setStatus(QPdfDocument::Error); + break; + case PDF_DATA_AVAIL: + // all good + break; + } + + Q_ASSERT(!doc); + + doc = FPDFAvail_GetDocument(avail, password); + lock.unlock(); + + updateLastError(); + + if (lastError == QPdfDocument::IncorrectPasswordError) { + FPDF_CloseDocument(doc); + doc = nullptr; + + setStatus(QPdfDocument::Error); + emit q->passwordRequired(); + } +} + +void QPdfDocumentPrivate::checkComplete() +{ + if (!avail || loadComplete) + return; + + if (!doc) + tryLoadDocument(); + + if (!doc) + return; + + loadComplete = true; + + QPdfMutexLocker lock; + + const int newPageCount = FPDF_GetPageCount(doc); + for (int i = 0; i < newPageCount; ++i) { + int result = PDF_DATA_NOTAVAIL; + while (result == PDF_DATA_NOTAVAIL) { + result = FPDFAvail_IsPageAvail(avail, i, this); + } + + if (result == PDF_DATA_ERROR) + loadComplete = false; + } + + lock.unlock(); + + if (loadComplete) { + if (newPageCount != pageCount) { + pageCount = newPageCount; + emit q->pageCountChanged(pageCount); + } + + setStatus(QPdfDocument::Ready); + } +} + +void QPdfDocumentPrivate::setStatus(QPdfDocument::Status documentStatus) +{ + if (status == documentStatus) + return; + + status = documentStatus; + emit q->statusChanged(status); +} + +FPDF_BOOL QPdfDocumentPrivate::fpdf_IsDataAvail(_FX_FILEAVAIL *pThis, size_t offset, size_t size) +{ + QPdfDocumentPrivate *d = static_cast<QPdfDocumentPrivate*>(pThis); + return offset + size <= static_cast<quint64>(d->device->size()); +} + +int QPdfDocumentPrivate::fpdf_GetBlock(void *param, unsigned long position, unsigned char *pBuf, unsigned long size) +{ + QPdfDocumentPrivate *d = static_cast<QPdfDocumentPrivate*>(reinterpret_cast<FPDF_FILEACCESS*>(param)); + d->device->seek(position); + return qMax(qint64(0), d->device->read(reinterpret_cast<char *>(pBuf), size)); + +} + +void QPdfDocumentPrivate::fpdf_AddSegment(_FX_DOWNLOADHINTS *pThis, size_t offset, size_t size) +{ + Q_UNUSED(pThis); + Q_UNUSED(offset); + Q_UNUSED(size); +} + +/*! + \class QPdfDocument + \since 5.10 + \inmodule QtPdf + + \brief The QPdfDocument class loads a PDF document and renders pages from it. +*/ + +/*! + Constructs a new document with parent object \a parent. +*/ +QPdfDocument::QPdfDocument(QObject *parent) + : QObject(parent) + , d(new QPdfDocumentPrivate) +{ + d->q = this; +} + +/*! + Destroys the document. +*/ +QPdfDocument::~QPdfDocument() +{ +} + +QPdfDocument::DocumentError QPdfDocument::load(const QString &fileName) +{ + close(); + + d->setStatus(QPdfDocument::Loading); + + QScopedPointer<QFile> f(new QFile(fileName)); + if (!f->open(QIODevice::ReadOnly)) { + d->lastError = FileNotFoundError; + d->setStatus(QPdfDocument::Error); + } else { + d->load(f.take(), /*transfer ownership*/true); + } + return d->lastError; +} + +/*! + \enum QPdfDocument::Status + + This enum describes the current status of the document. + + \value Null The initial status after the document has been created or after it has been closed. + \value Loading The status after load() has been called and before the document is fully loaded. + \value Ready The status when the document is fully loaded and its data can be accessed. + \value Unloading The status after close() has been called on an open document. + At this point the document is still valid and all its data can be accessed. + \value Error The status after Loading, if loading has failed. + + \sa QPdfDocument::status() +*/ + +/*! + Returns the current status of the document. +*/ +QPdfDocument::Status QPdfDocument::status() const +{ + return d->status; +} + +void QPdfDocument::load(QIODevice *device) +{ + close(); + + d->setStatus(QPdfDocument::Loading); + + d->load(device, /*transfer ownership*/false); +} + +void QPdfDocument::setPassword(const QString &password) +{ + const QByteArray newPassword = password.toUtf8(); + + if (d->password == newPassword) + return; + + d->password = newPassword; + emit passwordChanged(); +} + +QString QPdfDocument::password() const +{ + return QString::fromUtf8(d->password); +} + +/*! + \enum QPdfDocument::MetaDataField + + This enum describes the available fields of meta data. + + \value Title The document's title as QString. + \value Author The name of the person who created the document as QString. + \value Subject The subject of the document as QString. + \value Keywords Keywords associated with the document as QString. + \value Creator If the document was converted to PDF from another format, + the name of the conforming product that created the original document + from which it was converted as QString. + \value Producer If the document was converted to PDF from another format, + the name of the conforming product that converted it to PDF as QString. + \value CreationDate The date and time the document was created as QDateTime. + \value ModificationDate The date and time the document was most recently modified as QDateTime. + + \sa QPdfDocument::metaData() +*/ + +/*! + Returns the meta data of the document for the given \a field. +*/ +QVariant QPdfDocument::metaData(MetaDataField field) const +{ + if (!d->doc) + return QString(); + + QByteArray fieldName; + switch (field) { + case Title: + fieldName = "Title"; + break; + case Subject: + fieldName = "Subject"; + break; + case Author: + fieldName = "Author"; + break; + case Keywords: + fieldName = "Keywords"; + break; + case Producer: + fieldName = "Producer"; + break; + case Creator: + fieldName = "Creator"; + break; + case CreationDate: + fieldName = "CreationDate"; + break; + case ModificationDate: + fieldName = "ModDate"; + break; + } + + QPdfMutexLocker lock; + const unsigned long len = FPDF_GetMetaText(d->doc, fieldName.constData(), nullptr, 0); + + QVector<ushort> buf(len); + FPDF_GetMetaText(d->doc, fieldName.constData(), buf.data(), buf.length()); + lock.unlock(); + + QString text = QString::fromUtf16(buf.data()); + + switch (field) { + case Title: // fall through + case Subject: + case Author: + case Keywords: + case Producer: + case Creator: + return text; + case CreationDate: // fall through + case ModificationDate: + // convert a "D:YYYYMMDDHHmmSSOHH'mm'" into "YYYY-MM-DDTHH:mm:ss+HH:mm" + if (text.startsWith(QLatin1String("D:"))) + text = text.mid(2); + text.insert(4, QLatin1Char('-')); + text.insert(7, QLatin1Char('-')); + text.insert(10, QLatin1Char('T')); + text.insert(13, QLatin1Char(':')); + text.insert(16, QLatin1Char(':')); + text.replace(QLatin1Char('\''), QLatin1Char(':')); + if (text.endsWith(QLatin1Char(':'))) + text.chop(1); + + return QDateTime::fromString(text, Qt::ISODate); + } + + return QVariant(); +} + +QPdfDocument::DocumentError QPdfDocument::error() const +{ + return d->lastError; +} + +/*! + Closes the document. +*/ +void QPdfDocument::close() +{ + if (!d->doc) + return; + + d->setStatus(Unloading); + + d->clear(); + + if (!d->password.isEmpty()) { + d->password.clear(); + emit passwordChanged(); + } + + d->setStatus(Null); +} + +/*! + Returns the amount of pages for the loaded document or \c 0 if + no document is loaded. +*/ +int QPdfDocument::pageCount() const +{ + return d->pageCount; +} + +QSizeF QPdfDocument::pageSize(int page) const +{ + QSizeF result; + if (!d->doc) + return result; + + const QPdfMutexLocker lock; + + FPDF_GetPageSizeByIndex(d->doc, page, &result.rwidth(), &result.rheight()); + return result; +} + +/*! + Renders the \a page into a QImage of size \a imageSize according to the + provided \a renderOptions. + + Returns the rendered page or an empty image in case of an error. + + Note: If the \a imageSize does not match the aspect ratio of the page in the + PDF document, the page is rendered scaled, so that it covers the + complete \a imageSize. +*/ +QImage QPdfDocument::render(int page, QSize imageSize, QPdfDocumentRenderOptions renderOptions) +{ + if (!d->doc) + return QImage(); + + const QPdfMutexLocker lock; + + QElapsedTimer timer; + if (Q_UNLIKELY(qLcDoc().isDebugEnabled())) + timer.start(); + FPDF_PAGE pdfPage = FPDF_LoadPage(d->doc, page); + if (!pdfPage) + return QImage(); + + QImage result(imageSize, QImage::Format_ARGB32); + result.fill(Qt::transparent); + FPDF_BITMAP bitmap = FPDFBitmap_CreateEx(result.width(), result.height(), FPDFBitmap_BGRA, result.bits(), result.bytesPerLine()); + + int rotation = 0; + switch (renderOptions.rotation()) { + case QPdf::Rotate0: + rotation = 0; + break; + case QPdf::Rotate90: + rotation = 1; + break; + case QPdf::Rotate180: + rotation = 2; + break; + case QPdf::Rotate270: + rotation = 3; + break; + } + + const QPdf::RenderFlags renderFlags = renderOptions.renderFlags(); + int flags = 0; + if (renderFlags & QPdf::RenderAnnotations) + flags |= FPDF_ANNOT; + if (renderFlags & QPdf::RenderOptimizedForLcd) + flags |= FPDF_LCD_TEXT; + if (renderFlags & QPdf::RenderGrayscale) + flags |= FPDF_GRAYSCALE; + if (renderFlags & QPdf::RenderForceHalftone) + flags |= FPDF_RENDER_FORCEHALFTONE; + if (renderFlags & QPdf::RenderTextAliased) + flags |= FPDF_RENDER_NO_SMOOTHTEXT; + if (renderFlags & QPdf::RenderImageAliased) + flags |= FPDF_RENDER_NO_SMOOTHIMAGE; + if (renderFlags & QPdf::RenderPathAliased) + flags |= FPDF_RENDER_NO_SMOOTHPATH; + + FPDF_RenderPageBitmap(bitmap, pdfPage, 0, 0, result.width(), result.height(), rotation, flags); + + FPDFBitmap_Destroy(bitmap); + + FPDF_ClosePage(pdfPage); + qCDebug(qLcDoc) << "page" << page << imageSize << "took" << timer.elapsed() << "ms"; + return result; +} + +QT_END_NAMESPACE + +#include "moc_qpdfdocument.cpp" diff --git a/src/pdf/qpdfdocumentrenderoptions.qdoc b/src/pdf/qpdfdocumentrenderoptions.qdoc new file mode 100644 index 000000000..cafb0afc1 --- /dev/null +++ b/src/pdf/qpdfdocumentrenderoptions.qdoc @@ -0,0 +1,105 @@ +/**************************************************************************** +** +** Copyright (C) 2017 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Tobias König <tobias.koenig@kdab.com> +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtPDF module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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.LGPLv3 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.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 later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qpdfdocumentrenderoptions.h" + +QT_BEGIN_NAMESPACE + +/*! + \class QPdfDocumentRenderOptions + \since 5.10 + \inmodule QtPdf + + \brief The QPdfDocumentRenderOptions class holds the options to render a page from a PDF document. + + \sa QPdfDocument +*/ + +/*! + \fn QPdfDocumentRenderOptions::QPdfDocumentRenderOptions() + + Constructs a QPdfDocumentRenderOptions object. +*/ + +/*! + \fn QPdf::Rotation QPdfDocumentRenderOptions::rotation() const + + Returns the rotation used for rendering a page from a PDF document. + + \sa setRotation() +*/ + +/*! + \fn void QPdfDocumentRenderOptions::setRotation(QPdf::Rotation rotation) + + Sets the \a rotation used for rendering a page from a PDF document. + + \sa rotation() +*/ + +/*! + \fn QPdf::RenderFlags QPdfDocumentRenderOptions::renderFlags() const + + Returns the special flags used for rendering a page from a PDF document. + + \sa setRenderFlags() +*/ + +/*! + \fn void QPdfDocumentRenderOptions::setRenderFlags(QPdf::RenderFlags flags) + + Sets the special \a flags used for rendering a page from a PDF document. + + \sa renderFlags() +*/ + +/*! + \fn bool operator!=(QPdfDocumentRenderOptions lhs, QPdfDocumentRenderOptions rhs) + \relates QPdfDocumentRenderOptions + + Returns \c true if the options \a lhs and \a rhs are different, otherwise + returns \c false. +*/ + +/*! + \fn bool operator==(QPdfDocumentRenderOptions lhs, QPdfDocumentRenderOptions rhs) + \relates QPdfDocumentRenderOptions + + Returns \c true if the options \a lhs and \a rhs are equal, + otherwise returns \c false. +*/ + +QT_END_NAMESPACE diff --git a/src/pdf/qpdfnamespace.qdoc b/src/pdf/qpdfnamespace.qdoc new file mode 100644 index 000000000..96bb090e9 --- /dev/null +++ b/src/pdf/qpdfnamespace.qdoc @@ -0,0 +1,74 @@ +/**************************************************************************** +** +** Copyright (C) 2017 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Tobias König <tobias.koenig@kdab.com> +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtPDF module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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.LGPLv3 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.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 later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/*! + \namespace QPdf + \inmodule QtPdf + \keyword QPdf Namespace + + \brief The QPdf namespace contains miscellaneous identifiers + used throughout the QtPdf module. +*/ + +/*! + \enum QPdf::Rotation + + This enum describes the rotation of the page for rendering. + + \value Rotate0 Do not rotate (the default) + \value Rotate90 Rotate 90 degrees clockwise + \value Rotate180 Rotate 180 degrees + \value Rotate270 Rotate 270 degrees clockwise + + \sa QPdfDocument::render() +*/ +/*! + \enum QPdf::RenderFlag + + This enum is used to describe how a page should be rendered. + + \value NoRenderFlags The default value, representing no flags. + \value RenderAnnotations The page is rendered with annotations. + \value RenderOptimizedForLcd The text of the page is rendered optimized for LCD display. + \value RenderGrayscale The page is rendered grayscale. + \value RenderForceHalftone Always use halftones for rendering if the output image is stretched. + \value RenderTextAliased Anti-aliasing is disabled for rendering text. + \value RenderImageAliased Anti-aliasing is disabled for rendering images. + \value RenderPathAliased Anti-aliasing is disabled for rendering paths. + + \sa QPdfDocument::render() +*/ + diff --git a/src/pdf/qpdfpagenavigation.cpp b/src/pdf/qpdfpagenavigation.cpp new file mode 100644 index 000000000..497c1c2eb --- /dev/null +++ b/src/pdf/qpdfpagenavigation.cpp @@ -0,0 +1,314 @@ +/**************************************************************************** +** +** Copyright (C) 2017 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Tobias König <tobias.koenig@kdab.com> +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtPDF module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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.LGPLv3 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.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 later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qpdfpagenavigation.h" + +#include "qpdfdocument.h" + +#include <private/qobject_p.h> + +#include <QPointer> + +QT_BEGIN_NAMESPACE + +class QPdfPageNavigationPrivate : public QObjectPrivate +{ +public: + QPdfPageNavigationPrivate() + : QObjectPrivate() + { + } + + void update() + { + Q_Q(QPdfPageNavigation); + + const bool documentAvailable = m_document && m_document->status() == QPdfDocument::Ready; + + if (documentAvailable) { + const int newPageCount = m_document->pageCount(); + if (m_pageCount != newPageCount) { + m_pageCount = newPageCount; + emit q->pageCountChanged(m_pageCount); + } + } else { + if (m_pageCount != 0) { + m_pageCount = 0; + emit q->pageCountChanged(m_pageCount); + } + } + + if (m_currentPage != 0) { + m_currentPage = 0; + emit q->currentPageChanged(m_currentPage); + } + + updatePrevNext(); + } + + void updatePrevNext() + { + Q_Q(QPdfPageNavigation); + + const bool hasPreviousPage = m_currentPage > 0; + const bool hasNextPage = m_currentPage < (m_pageCount - 1); + + if (m_canGoToPreviousPage != hasPreviousPage) { + m_canGoToPreviousPage = hasPreviousPage; + emit q->canGoToPreviousPageChanged(m_canGoToPreviousPage); + } + + if (m_canGoToNextPage != hasNextPage) { + m_canGoToNextPage = hasNextPage; + emit q->canGoToNextPageChanged(m_canGoToNextPage); + } + } + + void documentStatusChanged() + { + update(); + } + + Q_DECLARE_PUBLIC(QPdfPageNavigation) + + QPointer<QPdfDocument> m_document = nullptr; + int m_currentPage = 0; + int m_pageCount = 0; + bool m_canGoToPreviousPage = false; + bool m_canGoToNextPage = false; + + QMetaObject::Connection m_documentStatusChangedConnection; +}; + +/*! + \class QPdfPageNavigation + \since 5.10 + \inmodule QtPdf + + \brief The QPdfPageNavigation class handles the navigation through a PDF document. + + \sa QPdfDocument +*/ + + +/*! + Constructs a page navigation object with parent object \a parent. +*/ +QPdfPageNavigation::QPdfPageNavigation(QObject *parent) + : QObject(*new QPdfPageNavigationPrivate, parent) +{ +} + +/*! + Destroys the page navigation object. +*/ +QPdfPageNavigation::~QPdfPageNavigation() +{ +} + +/*! + \property QPdfPageNavigation::document + \brief The document instance on which this object navigates. + + By default, this property is \c nullptr. + + \sa document(), setDocument(), QPdfDocument +*/ + +/*! + Returns the document on which this object navigates, or a \c nullptr + if none has set before. + + \sa QPdfDocument +*/ +QPdfDocument* QPdfPageNavigation::document() const +{ + Q_D(const QPdfPageNavigation); + + return d->m_document; +} + +/*! + Sets the \a document this object navigates on. + + After a new document has been set, the currentPage will be \c 0. + + \sa QPdfDocument +*/ +void QPdfPageNavigation::setDocument(QPdfDocument *document) +{ + Q_D(QPdfPageNavigation); + + if (d->m_document == document) + return; + + if (d->m_document) + disconnect(d->m_documentStatusChangedConnection); + + d->m_document = document; + emit documentChanged(d->m_document); + + if (d->m_document) + d->m_documentStatusChangedConnection = connect(d->m_document.data(), &QPdfDocument::statusChanged, this, [d](){ d->documentStatusChanged(); }); + + d->update(); +} + +/*! + \property QPdfPageNavigation::currentPage + \brief The current page number in the document. + + \sa currentPage(), setCurrentPage() +*/ + +/*! + Returns the current page number or \c 0 if there is no document set. + + After a document has been loaded, the currentPage will always be \c 0. +*/ +int QPdfPageNavigation::currentPage() const +{ + Q_D(const QPdfPageNavigation); + + return d->m_currentPage; +} + +/*! + \fn void QPdfPageNavigation::setCurrentPage(int page) + + Sets the current \a page number. +*/ +void QPdfPageNavigation::setCurrentPage(int newPage) +{ + Q_D(QPdfPageNavigation); + + if (newPage < 0 || newPage >= d->m_pageCount) + return; + + if (d->m_currentPage == newPage) + return; + + d->m_currentPage = newPage; + emit currentPageChanged(d->m_currentPage); + + d->updatePrevNext(); +} + +/*! + \property QPdfPageNavigation::pageCount + \brief The number of pages in the document. + + \sa pageCount() +*/ + +/*! + Returns the number of pages in the document or \c 0 if there + is no document set. +*/ +int QPdfPageNavigation::pageCount() const +{ + Q_D(const QPdfPageNavigation); + + return d->m_pageCount; +} + +/*! + \property QPdfPageNavigation::canGoToPreviousPage + \brief Indicates whether there is a page before the current page. + + \sa canGoToPreviousPage(), goToPreviousPage() +*/ + +/*! + Returns whether there is a page before the current one. +*/ +bool QPdfPageNavigation::canGoToPreviousPage() const +{ + Q_D(const QPdfPageNavigation); + + return d->m_canGoToPreviousPage; +} + +/*! + \property QPdfPageNavigation::canGoToNextPage + \brief Indicates whether there is a page after the current page. + + \sa canGoToNextPage(), goToNextPage() +*/ + +/*! + Returns whether there is a page after the current one. +*/ +bool QPdfPageNavigation::canGoToNextPage() const +{ + Q_D(const QPdfPageNavigation); + + return d->m_canGoToNextPage; +} + +/*! + Changes the current page to the previous page. + + If there is no previous page in the document, nothing happens. + + \sa canGoToPreviousPage +*/ +void QPdfPageNavigation::goToPreviousPage() +{ + Q_D(QPdfPageNavigation); + + if (d->m_currentPage > 0) + setCurrentPage(d->m_currentPage - 1); +} + +/*! + Changes the current page to the next page. + + If there is no next page in the document, nothing happens. + + \sa canGoToNextPage +*/ +void QPdfPageNavigation::goToNextPage() +{ + Q_D(QPdfPageNavigation); + + if (d->m_currentPage < d->m_pageCount - 1) + setCurrentPage(d->m_currentPage + 1); +} + +QT_END_NAMESPACE + +#include "moc_qpdfpagenavigation.cpp" diff --git a/src/pdf/qpdfpagerenderer.cpp b/src/pdf/qpdfpagerenderer.cpp new file mode 100644 index 000000000..e90f66700 --- /dev/null +++ b/src/pdf/qpdfpagerenderer.cpp @@ -0,0 +1,359 @@ +/**************************************************************************** +** +** Copyright (C) 2017 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Tobias König <tobias.koenig@kdab.com> +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtPDF module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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.LGPLv3 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.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 later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qpdfpagerenderer.h" + +#include <private/qobject_p.h> +#include <QMutex> +#include <QPdfDocument> +#include <QPointer> +#include <QThread> + +QT_BEGIN_NAMESPACE + +class RenderWorker : public QObject +{ + Q_OBJECT + +public: + RenderWorker(); + ~RenderWorker(); + + void setDocument(QPdfDocument *document); + +public Q_SLOTS: + void requestPage(quint64 requestId, int page, QSize imageSize, + QPdfDocumentRenderOptions options); + +Q_SIGNALS: + void pageRendered(int page, QSize imageSize, const QImage &image, + QPdfDocumentRenderOptions options, quint64 requestId); + +private: + QPointer<QPdfDocument> m_document; + QMutex m_mutex; +}; + +class QPdfPageRendererPrivate : public QObjectPrivate +{ + Q_DECLARE_PUBLIC(QPdfPageRenderer) + +public: + QPdfPageRendererPrivate(); + ~QPdfPageRendererPrivate(); + + void handleNextRequest(); + void requestFinished(int page, QSize imageSize, const QImage &image, + QPdfDocumentRenderOptions options, quint64 requestId); + + QPdfPageRenderer::RenderMode m_renderMode = QPdfPageRenderer::SingleThreadedRenderMode; + QPointer<QPdfDocument> m_document; + + struct PageRequest + { + quint64 id; + int pageNumber; + QSize imageSize; + QPdfDocumentRenderOptions options; + }; + + QVector<PageRequest> m_requests; + QVector<PageRequest> m_pendingRequests; + quint64 m_requestIdCounter = 1; + + QThread *m_renderThread = nullptr; + QScopedPointer<RenderWorker> m_renderWorker; +}; + +Q_DECLARE_TYPEINFO(QPdfPageRendererPrivate::PageRequest, Q_PRIMITIVE_TYPE); + + +RenderWorker::RenderWorker() + : m_document(nullptr) +{ +} + +RenderWorker::~RenderWorker() +{ +} + +void RenderWorker::setDocument(QPdfDocument *document) +{ + const QMutexLocker locker(&m_mutex); + + if (m_document == document) + return; + + m_document = document; +} + +void RenderWorker::requestPage(quint64 requestId, int pageNumber, QSize imageSize, + QPdfDocumentRenderOptions options) +{ + const QMutexLocker locker(&m_mutex); + + if (!m_document || m_document->status() != QPdfDocument::Ready) + return; + + const QImage image = m_document->render(pageNumber, imageSize, options); + + emit pageRendered(pageNumber, imageSize, image, options, requestId); +} + + +QPdfPageRendererPrivate::QPdfPageRendererPrivate() + : QObjectPrivate() + , m_renderWorker(new RenderWorker) +{ +} + +QPdfPageRendererPrivate::~QPdfPageRendererPrivate() +{ + if (m_renderThread) { + m_renderThread->quit(); + m_renderThread->wait(); + } +} + +void QPdfPageRendererPrivate::handleNextRequest() +{ + if (m_requests.isEmpty()) + return; + + const PageRequest request = m_requests.takeFirst(); + m_pendingRequests.append(request); + + QMetaObject::invokeMethod(m_renderWorker.data(), "requestPage", Qt::QueuedConnection, + Q_ARG(quint64, request.id), Q_ARG(int, request.pageNumber), + Q_ARG(QSize, request.imageSize), Q_ARG(QPdfDocumentRenderOptions, + request.options)); +} + +void QPdfPageRendererPrivate::requestFinished(int page, QSize imageSize, const QImage &image, QPdfDocumentRenderOptions options, quint64 requestId) +{ + const auto it = std::find_if(m_pendingRequests.begin(), m_pendingRequests.end(), + [page, imageSize, options](const PageRequest &request){ return request.pageNumber == page && request.imageSize == imageSize && request.options == options; }); + + if (it != m_pendingRequests.end()) + m_pendingRequests.erase(it); +} + +/*! + \class QPdfPageRenderer + \since 5.11 + \inmodule QtPdf + + \brief The QPdfPageRenderer class encapsulates the rendering of pages of a PDF document. + + The QPdfPageRenderer contains a queue that collects all render requests that are invoked through + requestPage(). Depending on the configured RenderMode the QPdfPageRenderer processes this queue + in the main UI thread on next event loop invocation (SingleThreadedRenderMode) or in a separate worker thread + (MultiThreadedRenderMode) and emits the result through the pageRendered() signal for each request once + the rendering is done. + + \sa QPdfDocument +*/ + + +/*! + Constructs a page renderer object with parent object \a parent. +*/ +QPdfPageRenderer::QPdfPageRenderer(QObject *parent) + : QObject(*new QPdfPageRendererPrivate(), parent) +{ + Q_D(QPdfPageRenderer); + + qRegisterMetaType<QPdfDocumentRenderOptions>(); + + connect(d->m_renderWorker.data(), &RenderWorker::pageRendered, this, + [this,d](int page, QSize imageSize, const QImage &image, QPdfDocumentRenderOptions options, quint64 requestId) { + d->requestFinished(page, imageSize, image, options, requestId); + emit pageRendered(page, imageSize, image, options, requestId); + d->handleNextRequest(); + }); +} + +/*! + Destroys the page renderer object. +*/ +QPdfPageRenderer::~QPdfPageRenderer() +{ +} + +/*! + \enum QPdfPageRenderer::RenderMode + + This enum describes how the pages are rendered. + + \value MultiThreadedRenderMode All pages are rendered in a separate worker thread. + \value SingleThreadedRenderMode All pages are rendered in the main UI thread (default). + + \sa renderMode(), setRenderMode() +*/ + +/*! + \property QPdfPageRenderer::renderMode + \brief The mode the renderer uses to render the pages. + + By default, this property is \c QPdfPageRenderer::SingleThreaded. + + \sa setRenderMode(), RenderMode +*/ + +/*! + Returns the mode of how the pages are rendered. + + \sa RenderMode +*/ +QPdfPageRenderer::RenderMode QPdfPageRenderer::renderMode() const +{ + Q_D(const QPdfPageRenderer); + + return d->m_renderMode; +} + +/*! + Sets the mode of how the pages are rendered to \a mode. + + \sa RenderMode +*/ +void QPdfPageRenderer::setRenderMode(RenderMode mode) +{ + Q_D(QPdfPageRenderer); + + if (d->m_renderMode == mode) + return; + + d->m_renderMode = mode; + emit renderModeChanged(d->m_renderMode); + + if (d->m_renderMode == MultiThreadedRenderMode) { + d->m_renderThread = new QThread; + d->m_renderWorker->moveToThread(d->m_renderThread); + d->m_renderThread->start(); + } else { + d->m_renderThread->quit(); + d->m_renderThread->wait(); + delete d->m_renderThread; + d->m_renderThread = nullptr; + + // pulling the object from another thread should be fine, once that thread is deleted + d->m_renderWorker->moveToThread(this->thread()); + } +} + +/*! + \property QPdfPageRenderer::document + \brief The document instance this object renders the pages from. + + By default, this property is \c nullptr. + + \sa document(), setDocument(), QPdfDocument +*/ + +/*! + Returns the document this objects renders the pages from, or a \c nullptr + if none has been set before. + + \sa QPdfDocument +*/ +QPdfDocument* QPdfPageRenderer::document() const +{ + Q_D(const QPdfPageRenderer); + + return d->m_document; +} + +/*! + Sets the \a document this object renders the pages from. + + \sa QPdfDocument +*/ +void QPdfPageRenderer::setDocument(QPdfDocument *document) +{ + Q_D(QPdfPageRenderer); + + if (d->m_document == document) + return; + + d->m_document = document; + emit documentChanged(d->m_document); + + d->m_renderWorker->setDocument(d->m_document); +} + +/*! + Requests the renderer to render the page \a pageNumber into a QImage of size \a imageSize + according to the provided \a options. + + Once the rendering is done the pageRendered() signal is emitted with the result as parameters. + + The return value is an ID that uniquely identifies the render request. If a request with the + same parameters is still in the queue, the ID of that queued request is returned. +*/ +quint64 QPdfPageRenderer::requestPage(int pageNumber, QSize imageSize, + QPdfDocumentRenderOptions options) +{ + Q_D(QPdfPageRenderer); + + if (!d->m_document || d->m_document->status() != QPdfDocument::Ready) + return 0; + + for (const auto request : qAsConst(d->m_pendingRequests)) { + if (request.pageNumber == pageNumber + && request.imageSize == imageSize + && request.options == options) + return request.id; + } + + const auto id = d->m_requestIdCounter++; + + QPdfPageRendererPrivate::PageRequest request; + request.id = id; + request.pageNumber = pageNumber; + request.imageSize = imageSize; + request.options = options; + + d->m_requests.append(request); + + d->handleNextRequest(); + + return id; +} + +QT_END_NAMESPACE + +#include "qpdfpagerenderer.moc" diff --git a/src/pdf/qpdfsearchmodel.cpp b/src/pdf/qpdfsearchmodel.cpp new file mode 100644 index 000000000..9010d76d3 --- /dev/null +++ b/src/pdf/qpdfsearchmodel.cpp @@ -0,0 +1,115 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtPDF module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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.LGPLv3 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.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 later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qpdfsearchmodel.h" +#include "qpdfsearchmodel_p.h" +#include "qpdfdocument_p.h" + +#include "third_party/pdfium/public/fpdf_doc.h" +#include "third_party/pdfium/public/fpdf_text.h" + +#include <QLoggingCategory> + +QT_BEGIN_NAMESPACE + +Q_LOGGING_CATEGORY(qLcS, "qt.pdf.search") + +QPdfSearchModel::QPdfSearchModel(QObject *parent) + : QObject(parent), + d(new QPdfSearchModelPrivate()) +{ +} + +QPdfSearchModel::~QPdfSearchModel() {} + +QVector<QRectF> QPdfSearchModel::matches(int page, const QString &searchString) +{ + const QPdfMutexLocker lock; + FPDF_PAGE pdfPage = FPDF_LoadPage(d->document->d->doc, page); + if (!pdfPage) { + qWarning() << "failed to load page" << page; + return {}; + } + double pageHeight = FPDF_GetPageHeight(pdfPage); + FPDF_TEXTPAGE textPage = FPDFText_LoadPage(pdfPage); + if (!textPage) { + qWarning() << "failed to load text of page" << page; + FPDF_ClosePage(pdfPage); + return {}; + } + QVector<QRectF> ret; + if (searchString.isEmpty()) + return ret; + FPDF_SCHHANDLE sh = FPDFText_FindStart(textPage, searchString.utf16(), 0, 0); + while (FPDFText_FindNext(sh)) { + int idx = FPDFText_GetSchResultIndex(sh); + int count = FPDFText_GetSchCount(sh); + int rectCount = FPDFText_CountRects(textPage, idx, count); + qCDebug(qLcS) << searchString << ": matched" << count << "chars @" << idx << "across" << rectCount << "rects"; + for (int r = 0; r < rectCount; ++r) { + double left, top, right, bottom; + FPDFText_GetRect(textPage, r, &left, &top, &right, &bottom); + ret << QRectF(left, pageHeight - top, right - left, top - bottom); + qCDebug(qLcS) << ret.last(); + } + } + FPDFText_FindClose(sh); + FPDFText_ClosePage(textPage); + FPDF_ClosePage(pdfPage); + + return ret; +} + +QPdfDocument *QPdfSearchModel::document() const +{ + return d->document; +} + +void QPdfSearchModel::setDocument(QPdfDocument *document) +{ + if (d->document == document) + return; + d->document = document; + emit documentChanged(); +} + +QPdfSearchModelPrivate::QPdfSearchModelPrivate() +{ +} + +QT_END_NAMESPACE + +#include "moc_qpdfsearchmodel.cpp" diff --git a/src/pdf/qpdfsearchmodel_p.h b/src/pdf/qpdfsearchmodel_p.h new file mode 100644 index 000000000..90490d8e5 --- /dev/null +++ b/src/pdf/qpdfsearchmodel_p.h @@ -0,0 +1,56 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtPDF module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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.LGPLv3 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.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 later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPDFSEARCHMODEL_P_H +#define QPDFSEARCHMODEL_P_H + +#include "qpdfsearchmodel.h" + +#include "third_party/pdfium/public/fpdfview.h" + +QT_BEGIN_NAMESPACE + +class QPdfSearchModelPrivate +{ +public: + QPdfSearchModelPrivate(); + + QPdfDocument *document = nullptr; +}; + +QT_END_NAMESPACE + +#endif // QPDFSEARCHMODEL_P_H diff --git a/src/pdf/qtpdf.gni b/src/pdf/qtpdf.gni new file mode 100644 index 000000000..c31f3e9a0 --- /dev/null +++ b/src/pdf/qtpdf.gni @@ -0,0 +1,7 @@ +include_dirs = [ +] + +deps = [ + "//third_party/pdfium" +] + diff --git a/src/pdf/quick/plugin.cpp b/src/pdf/quick/plugin.cpp new file mode 100644 index 000000000..664ba51ab --- /dev/null +++ b/src/pdf/quick/plugin.cpp @@ -0,0 +1,91 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtPDF module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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.LGPLv3 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.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 later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtQml/qqml.h> +#include <QtQml/qqmlcomponent.h> +#include <QtQml/qqmlengine.h> +#include <QtQml/qqmlextensionplugin.h> +#include "qquickpdfdocument_p.h" +#include "qquickpdfsearchmodel_p.h" + +QT_BEGIN_NAMESPACE + +/*! + \qmlmodule QtQuick.Pdf 5.15 + \title Qt Quick PDF QML Types + \ingroup qmlmodules + \brief Provides QML types for handling PDF documents. + + This QML module contains types for handling PDF documents. + + To use the types in this module, import the module with the following line: + + \code + import QtQuick.Pdf 5.15 + \endcode +*/ + +class QtQuick2PdfPlugin : public QQmlExtensionPlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID QQmlExtensionInterface_iid) + +public: + QtQuick2PdfPlugin() : QQmlExtensionPlugin() { } + + void initializeEngine(QQmlEngine *engine, const char *uri) override { + Q_UNUSED(uri); +#ifndef QT_STATIC + engine->addImportPath(QStringLiteral("qrc:/")); +#endif + } + + void registerTypes(const char *uri) override { + Q_ASSERT(QLatin1String(uri) == QLatin1String("QtQuick.Pdf")); + + // Register the latest version, even if there are no new types or new revisions for existing types yet. + qmlRegisterModule(uri, 2, QT_VERSION_MINOR); + + qmlRegisterType<QQuickPdfDocument>(uri, 5, 15, "PdfDocument"); + qmlRegisterType<QQuickPdfSearchModel>(uri, 5, 15, "PdfSearchModel"); + + qmlRegisterType(QUrl("qrc:/qt-project.org/qtpdf/qml/PdfPageView.qml"), uri, 5, 15, "PdfPageView"); + } +}; + +QT_END_NAMESPACE + +#include "plugin.moc" diff --git a/src/pdf/quick/plugins.qmltypes b/src/pdf/quick/plugins.qmltypes new file mode 100644 index 000000000..a30361d33 --- /dev/null +++ b/src/pdf/quick/plugins.qmltypes @@ -0,0 +1,52 @@ +import QtQuick.tooling 1.2 + +// This file describes the plugin-supplied types contained in the library. +// It is used for QML tooling purposes only. +// +// This file was auto-generated by: +// 'qmlplugindump -nonrelocatable QtQuick.Pdf 5.14' + +Module { + dependencies: [ + "QtGraphicalEffects 1.12", + "QtQuick 2.14", + "QtQuick.Controls 2.14", + "QtQuick.Controls.Fusion 2.14", + "QtQuick.Controls.Fusion.impl 2.14", + "QtQuick.Controls.Imagine 2.14", + "QtQuick.Controls.Imagine.impl 2.14", + "QtQuick.Controls.Material 2.14", + "QtQuick.Controls.Material.impl 2.14", + "QtQuick.Controls.Universal 2.14", + "QtQuick.Controls.Universal.impl 2.12", + "QtQuick.Controls.impl 2.14", + "QtQuick.Shapes 1.14", + "QtQuick.Templates 2.14", + "QtQuick.Window 2.2" + ] + Component { + name: "QQuickPdfDocument" + prototype: "QObject" + exports: ["QtQuick.Pdf/PdfDocument 5.14"] + exportMetaObjectRevisions: [0] + Property { name: "source"; type: "QUrl" } + Property { name: "pageCount"; type: "int"; isReadonly: true } + Property { name: "password"; type: "string" } + Property { name: "status"; type: "QPdfDocument::Status"; isReadonly: true } + Property { name: "title"; type: "string"; isReadonly: true } + Property { name: "subject"; type: "string"; isReadonly: true } + Property { name: "author"; type: "string"; isReadonly: true } + Property { name: "keywords"; type: "string"; isReadonly: true } + Property { name: "producer"; type: "string"; isReadonly: true } + Property { name: "creator"; type: "string"; isReadonly: true } + Property { name: "creationDate"; type: "QDateTime"; isReadonly: true } + Property { name: "modificationDate"; type: "QDateTime"; isReadonly: true } + Signal { name: "passwordRequired" } + Signal { name: "metaDataLoaded" } + Method { + name: "pagePointSize" + type: "QSizeF" + Parameter { name: "page"; type: "int" } + } + } +} diff --git a/src/pdf/quick/qml/PdfPageView.qml b/src/pdf/quick/qml/PdfPageView.qml new file mode 100644 index 000000000..b7f75f4c2 --- /dev/null +++ b/src/pdf/quick/qml/PdfPageView.qml @@ -0,0 +1,119 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtPDF module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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.LGPLv3 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.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 later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Pdf 5.15 +import QtQuick.Shapes 1.15 + +Rectangle { + id: paper + width: image.width + height: image.height + + // public API + // TODO 5.15: required property + property var document: null + property real renderScale: 1 + property alias sourceSize: image.sourceSize + property alias currentPage: image.currentFrame + property alias pageCount: image.frameCount + property alias searchString: searchModel.searchString + property alias status: image.status + + property real __pageScale: image.paintedWidth / document.pagePointSize(image.currentFrame).width + + PdfSearchModel { + id: searchModel + document: paper.document + page: image.currentFrame + } + + Image { + id: image + source: document.status === PdfDocument.Ready ? document.source : "" + asynchronous: true + fillMode: Image.PreserveAspectFit + } + function reRenderIfNecessary() { + var newSourceWidth = image.sourceSize.width * paper.scale + var ratio = newSourceWidth / image.sourceSize.width + if (ratio > 1.1 || ratio < 0.9) { + image.sourceSize.width = newSourceWidth + image.sourceSize.height = 1 + paper.scale = 1 + } + } + onRenderScaleChanged: { + image.sourceSize.width = document.pagePointSize(image.currentFrame).width * renderScale + image.sourceSize.height = 0 + paper.scale = 1 + } + + Shape { + anchors.fill: parent + opacity: 0.25 + visible: image.status === Image.Ready + ShapePath { + strokeWidth: 1 + strokeColor: "blue" + fillColor: "cyan" + scale: Qt.size(paper.__pageScale, paper.__pageScale) + PathMultiline { + id: searchResultBoundaries + paths: searchModel.matchGeometry + } + } + } + PinchHandler { + id: pinch + minimumScale: 0.1 + maximumScale: 10 + minimumRotation: 0 + maximumRotation: 0 + onActiveChanged: if (!active) paper.reRenderIfNecessary() + grabPermissions: PinchHandler.TakeOverForbidden // don't allow takeover if pinch has started + } + DragHandler { + id: pageMovingTouchDrag + acceptedDevices: PointerDevice.TouchScreen + } + DragHandler { + id: pageMovingMiddleMouseDrag + acceptedDevices: PointerDevice.Mouse | PointerDevice.Stylus + acceptedButtons: Qt.MiddleButton + snapMode: DragHandler.NoSnap + } +} diff --git a/src/pdf/quick/qmldir b/src/pdf/quick/qmldir new file mode 100644 index 000000000..65fa95cda --- /dev/null +++ b/src/pdf/quick/qmldir @@ -0,0 +1,4 @@ +module QtQuick.Pdf +plugin pdfplugin +classname QtQuick2PdfPlugin +typeinfo plugins.qmltypes diff --git a/src/pdf/quick/qquickpdfdocument.cpp b/src/pdf/quick/qquickpdfdocument.cpp new file mode 100644 index 000000000..73b3d4537 --- /dev/null +++ b/src/pdf/quick/qquickpdfdocument.cpp @@ -0,0 +1,237 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtPDF module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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.LGPLv3 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.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 later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qquickpdfdocument_p.h" +#include <QQuickItem> +#include <QQmlEngine> +#include <QStandardPaths> +#include <private/qguiapplication_p.h> + +QT_BEGIN_NAMESPACE + +/*! + \qmltype Document + \instantiates QQuickPdfDocument + \inqmlmodule QtQuick.Pdf + \ingroup pdf + \brief A representation of a PDF document. + \since 5.15 + + A Document provides access to PDF document meta-information. + It is not necessary for rendering, as it is enough to use an + \l Image with source set to the URL of the PDF. +*/ + +/*! + Constructs a PDF document. +*/ +QQuickPdfDocument::QQuickPdfDocument(QObject *parent) + : QObject(parent) +{ + connect(&m_doc, &QPdfDocument::passwordChanged, this, &QQuickPdfDocument::passwordChanged); + connect(&m_doc, &QPdfDocument::passwordRequired, this, &QQuickPdfDocument::passwordRequired); + connect(&m_doc, &QPdfDocument::statusChanged, [=] (QPdfDocument::Status status) { + emit statusChanged(); + if (status == QPdfDocument::Ready) + emit metaDataChanged(); + }); + connect(&m_doc, &QPdfDocument::pageCountChanged, this, &QQuickPdfDocument::pageCountChanged); +} + +void QQuickPdfDocument::componentComplete() +{ + if (m_doc.error() == QPdfDocument::IncorrectPasswordError) + emit passwordRequired(); +} + +/*! + \qmlproperty url Document::source + + This property holds a URL pointing to the PDF file to be loaded. + + \note At this time, only local filesystem URLs are supported. +*/ +void QQuickPdfDocument::setSource(QUrl source) +{ + if (m_source == source) + return; + + m_source = source; + emit sourceChanged(); + m_doc.load(source.path()); +} + +/*! + \qmlproperty string Document::error + + This property holds a translated string representation of the current + error, if any. + + \sa status +*/ +QString QQuickPdfDocument::error() const +{ + switch (m_doc.error()) { + case QPdfDocument::NoError: + return tr("no error"); + break; + case QPdfDocument::UnknownError: + break; + case QPdfDocument::DataNotYetAvailableError: + return tr("data not yet available"); + break; + case QPdfDocument::FileNotFoundError: + return tr("file not found"); + break; + case QPdfDocument::InvalidFileFormatError: + return tr("invalid file format"); + break; + case QPdfDocument::IncorrectPasswordError: + return tr("incorrect password"); + break; + case QPdfDocument::UnsupportedSecuritySchemeError: + return tr("unsupported security scheme"); + break; + } + return tr("unknown error"); +} + +/*! + \qmlproperty bool Document::password + + This property holds the document password. If the passwordRequired() + signal is emitted, the UI should prompt the user and then set this + property so that document opening can continue. +*/ +void QQuickPdfDocument::setPassword(const QString &password) +{ + if (m_doc.password() == password) + return; + m_doc.setPassword(password); + if (source().isValid() && source().isLocalFile()) + m_doc.load(source().path()); +} + +/*! + \qmlproperty int Document::pageCount + + This property holds the number of pages the PDF contains. +*/ + +/*! + \qmlsignal Document::passwordRequired() + + This signal is emitted when the PDF requires a password in order to open. + The UI in a typical PDF viewer should prompt the user for the password + and then set the password property when the user has provided it. +*/ + +/*! + \qmlmethod size Document::pagePointSize(int page) + + Returns the size of the given \a page in points. +*/ +QSizeF QQuickPdfDocument::pagePointSize(int page) const +{ + return m_doc.pageSize(page); +} + +/*! + \qmlproperty string Document::title + + This property holds the document's title. A typical viewer UI can bind this + to the \c Window.title property. +*/ + +/*! + \qmlproperty string Document::author + + This property holds the name of the person who created the document. +*/ + +/*! + \qmlproperty string Document::subject + + This property holds the subject of the document. +*/ + +/*! + \qmlproperty string Document::keywords + + This property holds the keywords associated with the document. +*/ + +/*! + \qmlproperty string Document::creator + + If the document was converted to PDF from another format, this property + holds the name of the software that created the original document. +*/ + +/*! + \qmlproperty string Document::producer + + If the document was converted to PDF from another format, this property + holds the name of the software that converted it to PDF. +*/ + +/*! + \qmlproperty string Document::creationDate + + This property holds the date and time the document was created. +*/ + +/*! + \qmlproperty string Document::modificationDate + + This property holds the date and time the document was most recently + modified. +*/ + +/*! + \qmlproperty enum Document::status + + This property tells the current status of the document. The possible values are: + + \value PdfDocument.Null The initial status after the document has been created or after it has been closed. + \value PdfDocument.Loading The status after load() has been called and before the document is fully loaded. + \value PdfDocument.Ready The status when the document is fully loaded and its data can be accessed. + \value PdfDocument.Unloading The status after close() has been called on an open document. + At this point the document is still valid and all its data can be accessed. + \value PdfDocument.Error The status after Loading, if loading has failed. +*/ + +QT_END_NAMESPACE diff --git a/src/pdf/quick/qquickpdfdocument_p.h b/src/pdf/quick/qquickpdfdocument_p.h new file mode 100644 index 000000000..1ec7edb1a --- /dev/null +++ b/src/pdf/quick/qquickpdfdocument_p.h @@ -0,0 +1,129 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtPDF module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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.LGPLv3 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.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 later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKPDFDOCUMENT_P_H +#define QQUICKPDFDOCUMENT_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtPdf/QPdfDocument> +#include <QDateTime> +#include <QJSValue> +#include <QQmlParserStatus> +#include <QUrl> +#include <QVariant> + +QT_BEGIN_NAMESPACE + +class QQuickPdfDocument : public QObject, public QQmlParserStatus +{ + Q_OBJECT + Q_PROPERTY(QUrl source READ source WRITE setSource NOTIFY sourceChanged) + Q_PROPERTY(int pageCount READ pageCount NOTIFY pageCountChanged FINAL) + Q_PROPERTY(QString password READ password WRITE setPassword NOTIFY passwordChanged FINAL) + Q_PROPERTY(QPdfDocument::Status status READ status NOTIFY statusChanged FINAL) + Q_PROPERTY(QString error READ error NOTIFY statusChanged FINAL) + + Q_PROPERTY(QString title READ title NOTIFY metaDataChanged) + Q_PROPERTY(QString subject READ subject NOTIFY metaDataChanged) + Q_PROPERTY(QString author READ author NOTIFY metaDataChanged) + Q_PROPERTY(QString keywords READ keywords NOTIFY metaDataChanged) + Q_PROPERTY(QString producer READ producer NOTIFY metaDataChanged) + Q_PROPERTY(QString creator READ creator NOTIFY metaDataChanged) + Q_PROPERTY(QDateTime creationDate READ creationDate NOTIFY metaDataChanged) + Q_PROPERTY(QDateTime modificationDate READ modificationDate NOTIFY metaDataChanged) + +public: + explicit QQuickPdfDocument(QObject *parent = nullptr); + + void classBegin() override {} + void componentComplete() override; + + QUrl source() const { return m_source; } + void setSource(QUrl source); + + int pageCount() const { return m_doc.pageCount(); } + QPdfDocument::Status status() const { return m_doc.status(); } + + QString error() const; + + QString password() const { return m_doc.password(); } + void setPassword(const QString &password); + + QString title() { return m_doc.metaData(QPdfDocument::Title).toString(); } + QString author() { return m_doc.metaData(QPdfDocument::Author).toString(); } + QString subject() { return m_doc.metaData(QPdfDocument::Subject).toString(); } + QString keywords() { return m_doc.metaData(QPdfDocument::Keywords).toString(); } + QString producer() { return m_doc.metaData(QPdfDocument::Producer).toString(); } + QString creator() { return m_doc.metaData(QPdfDocument::Creator).toString(); } + QDateTime creationDate() { return m_doc.metaData(QPdfDocument::CreationDate).toDateTime(); } + QDateTime modificationDate() { return m_doc.metaData(QPdfDocument::ModificationDate).toDateTime(); } + + Q_INVOKABLE QSizeF pagePointSize(int page) const; + +Q_SIGNALS: + void sourceChanged(); + void passwordChanged(); + void passwordRequired(); + void statusChanged(); + void pageCountChanged(); + void metaDataChanged(); + +private: + QPdfDocument &document() { return m_doc; } + +private: + QUrl m_source; + QPdfDocument m_doc; + + friend class QQuickPdfSearchModel; + friend class QQuickPdfSelection; + + Q_DISABLE_COPY(QQuickPdfDocument) +}; + +QT_END_NAMESPACE + +#endif // QQUICKPDFDOCUMENT_P_H diff --git a/src/pdf/quick/qquickpdfsearchmodel.cpp b/src/pdf/quick/qquickpdfsearchmodel.cpp new file mode 100644 index 000000000..8b0e88673 --- /dev/null +++ b/src/pdf/quick/qquickpdfsearchmodel.cpp @@ -0,0 +1,165 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtPDF module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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.LGPLv3 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.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 later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qquickpdfsearchmodel_p.h" +#include <QQuickItem> +#include <QQmlEngine> +#include <QStandardPaths> +#include <private/qguiapplication_p.h> + +QT_BEGIN_NAMESPACE + +/*! + \qmltype PdfSearchModel + \instantiates QQuickPdfSearchModel + \inqmlmodule QtQuick.Pdf + \ingroup pdf + \brief A representation of text search results within a PDF Document. + \since 5.15 + + PdfSearchModel provides the ability to search for text strings within a + document and get the geometric locations of matches on each page. +*/ + +QQuickPdfSearchModel::QQuickPdfSearchModel(QObject *parent) + : QPdfSearchModel(parent) +{ +} + +QQuickPdfDocument *QQuickPdfSearchModel::document() const +{ + return m_quickDocument; +} + +void QQuickPdfSearchModel::setDocument(QQuickPdfDocument *document) +{ + if (document == m_quickDocument) + return; + m_quickDocument = document; + QPdfSearchModel::setDocument(&document->m_doc); +} + +/*! + \qmlproperty list<list<point>> PdfSearchModel::matchGeometry + + A set of paths in a form that can be bound to the \c paths property of a + \l {QtQuick::PathMultiline}{PathMultiline} instance to render a batch of + rectangles around all the locations where search results are found: + + \qml + PdfDocument { + id: doc + } + PdfSearchModel { + id: searchModel + document: doc + page: doc.currentPage + } + Shape { + ShapePath { + PathMultiline { + paths: searchModel.matchGeometry + } + } + } + \endqml + + \sa PathMultiline +*/ +QVector<QPolygonF> QQuickPdfSearchModel::matchGeometry() const +{ + return m_matchGeometry; +} + +/*! + \qmlproperty string PdfSearchModel::searchString + + The string to search for. +*/ +QString QQuickPdfSearchModel::searchString() const +{ + return m_searchString; +} + +void QQuickPdfSearchModel::setSearchString(QString searchString) +{ + if (m_searchString == searchString) + return; + + m_searchString = searchString; + emit searchStringChanged(); + updateResults(); +} + +/*! + \qmlproperty int PdfSearchModel::page + + The page number on which to search. + + \sa QtQuick::Image::currentFrame +*/ +int QQuickPdfSearchModel::page() const +{ + return m_page; +} + +void QQuickPdfSearchModel::setPage(int page) +{ + if (m_page == page) + return; + + m_page = page; + emit pageChanged(); + updateResults(); +} + +void QQuickPdfSearchModel::updateResults() +{ + if (!document() || (m_searchString.isEmpty() && !m_matchGeometry.isEmpty()) || m_page < 0 || m_page > document()->pageCount()) { + m_matchGeometry.clear(); + emit matchGeometryChanged(); + } + QVector<QRectF> m = QPdfSearchModel::matches(m_page, m_searchString); + QVector<QPolygonF> matches; + for (QRectF r : m) + matches << QPolygonF(r); + if (matches != m_matchGeometry) { + m_matchGeometry = matches; + emit matchGeometryChanged(); + } +} + +QT_END_NAMESPACE diff --git a/src/pdf/quick/qquickpdfsearchmodel_p.h b/src/pdf/quick/qquickpdfsearchmodel_p.h new file mode 100644 index 000000000..799ef825f --- /dev/null +++ b/src/pdf/quick/qquickpdfsearchmodel_p.h @@ -0,0 +1,103 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtPDF module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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.LGPLv3 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.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 later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKPDFSEARCHMODEL_P_H +#define QQUICKPDFSEARCHMODEL_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qquickpdfdocument_p.h" +#include "../api/qpdfsearchmodel.h" + +#include <QVariant> +#include <QtQml/qqml.h> + +QT_BEGIN_NAMESPACE + +class QQuickPdfSearchModel : public QPdfSearchModel +{ + Q_OBJECT + Q_PROPERTY(QQuickPdfDocument *document READ document WRITE setDocument NOTIFY documentChanged) + Q_PROPERTY(int page READ page WRITE setPage NOTIFY pageChanged) + Q_PROPERTY(QString searchString READ searchString WRITE setSearchString NOTIFY searchStringChanged) + Q_PROPERTY(QVector<QPolygonF> matchGeometry READ matchGeometry NOTIFY matchGeometryChanged) + +public: + explicit QQuickPdfSearchModel(QObject *parent = nullptr); + + QQuickPdfDocument *document() const; + void setDocument(QQuickPdfDocument * document); + + int page() const; + void setPage(int page); + + QString searchString() const; + void setSearchString(QString searchString); + + QVector<QPolygonF> matchGeometry() const; + +signals: + void documentChanged(); + void pageChanged(); + void searchStringChanged(); + void matchGeometryChanged(); + +private: + void updateResults(); + +private: + QQuickPdfDocument *m_quickDocument = nullptr; + QString m_searchString; + QVector<QPolygonF> m_matchGeometry; + int m_page; + + Q_DISABLE_COPY(QQuickPdfSearchModel) +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QQuickPdfSearchModel) + +#endif // QQUICKPDFSEARCHMODEL_P_H diff --git a/src/pdf/quick/quick.pro b/src/pdf/quick/quick.pro new file mode 100644 index 000000000..cda768369 --- /dev/null +++ b/src/pdf/quick/quick.pro @@ -0,0 +1,26 @@ +CXX_MODULE = qml +TARGET = pdfplugin +TARGETPATH = QtQuick/Pdf +IMPORT_VERSION = 1.0 + +#QMAKE_DOCS = $$PWD/doc/qtquickpdf.qdocconf + +PDF_QML_FILES = \ + qml/PdfPageView.qml \ + +QML_FILES += $$PDF_QML_FILES qmldir + +RESOURCES += resources.qrc + +SOURCES += \ + plugin.cpp \ + qquickpdfdocument.cpp \ + qquickpdfsearchmodel.cpp \ + +HEADERS += \ + qquickpdfdocument_p.h \ + qquickpdfsearchmodel_p.h \ + +QT += pdf quick-private gui gui-private core core-private qml qml-private + +load(qml_plugin) diff --git a/src/pdf/quick/resources.qrc b/src/pdf/quick/resources.qrc new file mode 100644 index 000000000..a3f34189c --- /dev/null +++ b/src/pdf/quick/resources.qrc @@ -0,0 +1,5 @@ +<RCC> + <qresource prefix="/qt-project.org/qtpdf"> + <file>qml/PdfPageView.qml</file> + </qresource> +</RCC> diff --git a/src/pdfwidgets/configure.json b/src/pdfwidgets/configure.json new file mode 100644 index 000000000..b33f08555 --- /dev/null +++ b/src/pdfwidgets/configure.json @@ -0,0 +1,28 @@ +{ + "module": "pdfwidgets", + "condition": "module.pdf && features.pdf-widgets", + "depends": [ + "pdf-private" + ], + "commandline": { + "options": { + "pdf-widgets": "boolean" + } + }, + "features": { + "pdf-widgets": { + "label": "Support Qt PDF Widgets", + "purpose": "Provides Qt PDF Widgets support.", + "condition": "module.widgets", + "output": [ "privateFeature" ] + } + }, + "summary": [ + { + "section": "Qt PDF Widgets", + "entries": [ + "pdf-widgets" + ] + } + ] +} diff --git a/src/pdfwidgets/pdfwidgets.pro b/src/pdfwidgets/pdfwidgets.pro new file mode 100644 index 000000000..cf221be03 --- /dev/null +++ b/src/pdfwidgets/pdfwidgets.pro @@ -0,0 +1,12 @@ +TARGET = QtPdfWidgets +QT = core gui widgets widgets-private pdf + +SOURCES += \ + qpdfview.cpp + +HEADERS += \ + qpdfview.h \ + qpdfview_p.h \ + qtpdfwidgetsglobal.h + +load(qt_module) diff --git a/src/pdfwidgets/qpdfview.cpp b/src/pdfwidgets/qpdfview.cpp new file mode 100644 index 000000000..19e5b719a --- /dev/null +++ b/src/pdfwidgets/qpdfview.cpp @@ -0,0 +1,493 @@ +/**************************************************************************** +** +** Copyright (C) 2017 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Tobias König <tobias.koenig@kdab.com> +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtPDF module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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.LGPLv3 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.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 later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qpdfview.h" +#include "qpdfview_p.h" + +#include "qpdfpagerenderer.h" + +#include <QGuiApplication> +#include <QPdfDocument> +#include <QPdfPageNavigation> +#include <QScreen> +#include <QScrollBar> +#include <QScroller> + +QT_BEGIN_NAMESPACE + +QPdfViewPrivate::QPdfViewPrivate() + : QAbstractScrollAreaPrivate() + , m_document(nullptr) + , m_pageNavigation(nullptr) + , m_pageRenderer(nullptr) + , m_pageMode(QPdfView::SinglePage) + , m_zoomMode(QPdfView::CustomZoom) + , m_zoomFactor(1.0) + , m_pageSpacing(3) + , m_documentMargins(6, 6, 6, 6) + , m_blockPageScrolling(false) + , m_pageCacheLimit(20) + , m_screenResolution(QGuiApplication::primaryScreen()->logicalDotsPerInch() / 72.0) +{ +} + +void QPdfViewPrivate::init() +{ + Q_Q(QPdfView); + + m_pageNavigation = new QPdfPageNavigation(q); + m_pageRenderer = new QPdfPageRenderer(q); + m_pageRenderer->setRenderMode(QPdfPageRenderer::MultiThreadedRenderMode); +} + +void QPdfViewPrivate::documentStatusChanged() +{ + updateDocumentLayout(); + invalidatePageCache(); +} + +void QPdfViewPrivate::currentPageChanged(int currentPage) +{ + Q_Q(QPdfView); + + if (m_blockPageScrolling) + return; + + q->verticalScrollBar()->setValue(yPositionForPage(currentPage)); + + if (m_pageMode == QPdfView::SinglePage) + invalidateDocumentLayout(); +} + +void QPdfViewPrivate::calculateViewport() +{ + Q_Q(QPdfView); + + const int x = q->horizontalScrollBar()->value(); + const int y = q->verticalScrollBar()->value(); + const int width = q->viewport()->width(); + const int height = q->viewport()->height(); + + setViewport(QRect(x, y, width, height)); +} + +void QPdfViewPrivate::setViewport(QRect viewport) +{ + if (m_viewport == viewport) + return; + + const QSize oldSize = m_viewport.size(); + + m_viewport = viewport; + + if (oldSize != m_viewport.size()) { + updateDocumentLayout(); + + if (m_zoomMode != QPdfView::CustomZoom) { + invalidatePageCache(); + } + } + + if (m_pageMode == QPdfView::MultiPage) { + // An imaginary, 2px height line at the upper half of the viewport, which is used to + // determine which page is currently located there -> we propagate that as 'current' page + // to the QPdfPageNavigation object + const QRect currentPageLine(m_viewport.x(), m_viewport.y() + m_viewport.height() * 0.4, m_viewport.width(), 2); + + int currentPage = 0; + for (auto it = m_documentLayout.pageGeometries.cbegin(); it != m_documentLayout.pageGeometries.cend(); ++it) { + const QRect pageGeometry = it.value(); + if (pageGeometry.intersects(currentPageLine)) { + currentPage = it.key(); + break; + } + } + + if (currentPage != m_pageNavigation->currentPage()) { + m_blockPageScrolling = true; + m_pageNavigation->setCurrentPage(currentPage); + m_blockPageScrolling = false; + } + } +} + +void QPdfViewPrivate::updateScrollBars() +{ + Q_Q(QPdfView); + + const QSize p = q->viewport()->size(); + const QSize v = m_documentLayout.documentSize; + + q->horizontalScrollBar()->setRange(0, v.width() - p.width()); + q->horizontalScrollBar()->setPageStep(p.width()); + q->verticalScrollBar()->setRange(0, v.height() - p.height()); + q->verticalScrollBar()->setPageStep(p.height()); +} + +void QPdfViewPrivate::pageRendered(int pageNumber, QSize imageSize, const QImage &image, quint64 requestId) +{ + Q_Q(QPdfView); + + Q_UNUSED(imageSize) + Q_UNUSED(requestId) + + if (!m_cachedPagesLRU.contains(pageNumber)) { + if (m_cachedPagesLRU.length() > m_pageCacheLimit) + m_pageCache.remove(m_cachedPagesLRU.takeFirst()); + + m_cachedPagesLRU.append(pageNumber); + } + + m_pageCache.insert(pageNumber, image); + + q->viewport()->update(); +} + +void QPdfViewPrivate::invalidateDocumentLayout() +{ + updateDocumentLayout(); + invalidatePageCache(); +} + +void QPdfViewPrivate::invalidatePageCache() +{ + Q_Q(QPdfView); + + m_pageCache.clear(); + q->viewport()->update(); +} + +QPdfViewPrivate::DocumentLayout QPdfViewPrivate::calculateDocumentLayout() const +{ + // The DocumentLayout describes a virtual layout where all pages are positioned inside + // - For SinglePage mode, this is just an area as large as the current page surrounded + // by the m_documentMargins. + // - For MultiPage mode, this is the area that is covered by all pages which are placed + // below each other, with m_pageSpacing inbetween and surrounded by m_documentMargins + + DocumentLayout documentLayout; + + if (!m_document || m_document->status() != QPdfDocument::Ready) + return documentLayout; + + QHash<int, QRect> pageGeometries; + + const int pageCount = m_document->pageCount(); + + int totalWidth = 0; + + const int startPage = (m_pageMode == QPdfView::SinglePage ? m_pageNavigation->currentPage() : 0); + const int endPage = (m_pageMode == QPdfView::SinglePage ? m_pageNavigation->currentPage() + 1 : pageCount); + + // calculate page sizes + for (int page = startPage; page < endPage; ++page) { + QSize pageSize; + if (m_zoomMode == QPdfView::CustomZoom) { + pageSize = QSizeF(m_document->pageSize(page) * m_screenResolution * m_zoomFactor).toSize(); + } else if (m_zoomMode == QPdfView::FitToWidth) { + pageSize = QSizeF(m_document->pageSize(page) * m_screenResolution).toSize(); + const qreal factor = (qreal(m_viewport.width() - m_documentMargins.left() - m_documentMargins.right()) / qreal(pageSize.width())); + pageSize *= factor; + } else if (m_zoomMode == QPdfView::FitInView) { + const QSize viewportSize(m_viewport.size() + QSize(-m_documentMargins.left() - m_documentMargins.right(), -m_pageSpacing)); + + pageSize = QSizeF(m_document->pageSize(page) * m_screenResolution).toSize(); + pageSize = pageSize.scaled(viewportSize, Qt::KeepAspectRatio); + } + + totalWidth = qMax(totalWidth, pageSize.width()); + + pageGeometries[page] = QRect(QPoint(0, 0), pageSize); + } + + totalWidth += m_documentMargins.left() + m_documentMargins.right(); + + int pageY = m_documentMargins.top(); + + // calculate page positions + for (int page = startPage; page < endPage; ++page) { + const QSize pageSize = pageGeometries[page].size(); + + // center horizontal inside the viewport + const int pageX = (qMax(totalWidth, m_viewport.width()) - pageSize.width()) / 2; + + pageGeometries[page].moveTopLeft(QPoint(pageX, pageY)); + + pageY += pageSize.height() + m_pageSpacing; + } + + pageY += m_documentMargins.bottom(); + + documentLayout.pageGeometries = pageGeometries; + + // calculate overall document size + documentLayout.documentSize = QSize(totalWidth, pageY); + + return documentLayout; +} + +qreal QPdfViewPrivate::yPositionForPage(int pageNumber) const +{ + const auto it = m_documentLayout.pageGeometries.constFind(pageNumber); + if (it == m_documentLayout.pageGeometries.cend()) + return 0.0; + + return (*it).y(); +} + +void QPdfViewPrivate::updateDocumentLayout() +{ + m_documentLayout = calculateDocumentLayout(); + + updateScrollBars(); +} + + +QPdfView::QPdfView(QWidget *parent) + : QAbstractScrollArea(*new QPdfViewPrivate(), parent) +{ + Q_D(QPdfView); + + d->init(); + + connect(d->m_pageNavigation, &QPdfPageNavigation::currentPageChanged, this, [d](int page){ d->currentPageChanged(page); }); + + connect(d->m_pageRenderer, &QPdfPageRenderer::pageRendered, + this, [d](int pageNumber, QSize imageSize, const QImage &image, QPdfDocumentRenderOptions, quint64 requestId){ d->pageRendered(pageNumber, imageSize, image, requestId); }); + + verticalScrollBar()->setSingleStep(20); + horizontalScrollBar()->setSingleStep(20); + + QScroller::grabGesture(this); + + d->calculateViewport(); +} + +/*! + \internal +*/ +QPdfView::QPdfView(QPdfViewPrivate &dd, QWidget *parent) + : QAbstractScrollArea(dd, parent) +{ +} + +QPdfView::~QPdfView() +{ +} + +void QPdfView::setDocument(QPdfDocument *document) +{ + Q_D(QPdfView); + + if (d->m_document == document) + return; + + if (d->m_document) + disconnect(d->m_documentStatusChangedConnection); + + d->m_document = document; + emit documentChanged(d->m_document); + + if (d->m_document) + d->m_documentStatusChangedConnection = connect(d->m_document.data(), &QPdfDocument::statusChanged, this, [d](){ d->documentStatusChanged(); }); + + d->m_pageNavigation->setDocument(d->m_document); + d->m_pageRenderer->setDocument(d->m_document); + + d->documentStatusChanged(); +} + +QPdfDocument *QPdfView::document() const +{ + Q_D(const QPdfView); + + return d->m_document; +} + +QPdfPageNavigation *QPdfView::pageNavigation() const +{ + Q_D(const QPdfView); + + return d->m_pageNavigation; +} + +QPdfView::PageMode QPdfView::pageMode() const +{ + Q_D(const QPdfView); + + return d->m_pageMode; +} + +void QPdfView::setPageMode(PageMode mode) +{ + Q_D(QPdfView); + + if (d->m_pageMode == mode) + return; + + d->m_pageMode = mode; + d->invalidateDocumentLayout(); + + emit pageModeChanged(d->m_pageMode); +} + +QPdfView::ZoomMode QPdfView::zoomMode() const +{ + Q_D(const QPdfView); + + return d->m_zoomMode; +} + +void QPdfView::setZoomMode(ZoomMode mode) +{ + Q_D(QPdfView); + + if (d->m_zoomMode == mode) + return; + + d->m_zoomMode = mode; + d->invalidateDocumentLayout(); + + emit zoomModeChanged(d->m_zoomMode); +} + +qreal QPdfView::zoomFactor() const +{ + Q_D(const QPdfView); + + return d->m_zoomFactor; +} + +void QPdfView::setZoomFactor(qreal factor) +{ + Q_D(QPdfView); + + if (d->m_zoomFactor == factor) + return; + + d->m_zoomFactor = factor; + d->invalidateDocumentLayout(); + + emit zoomFactorChanged(d->m_zoomFactor); +} + +int QPdfView::pageSpacing() const +{ + Q_D(const QPdfView); + + return d->m_pageSpacing; +} + +void QPdfView::setPageSpacing(int spacing) +{ + Q_D(QPdfView); + + if (d->m_pageSpacing == spacing) + return; + + d->m_pageSpacing = spacing; + d->invalidateDocumentLayout(); + + emit pageSpacingChanged(d->m_pageSpacing); +} + +QMargins QPdfView::documentMargins() const +{ + Q_D(const QPdfView); + + return d->m_documentMargins; +} + +void QPdfView::setDocumentMargins(QMargins margins) +{ + Q_D(QPdfView); + + if (d->m_documentMargins == margins) + return; + + d->m_documentMargins = margins; + d->invalidateDocumentLayout(); + + emit documentMarginsChanged(d->m_documentMargins); +} + +void QPdfView::paintEvent(QPaintEvent *event) +{ + Q_D(QPdfView); + + QPainter painter(viewport()); + painter.fillRect(event->rect(), palette().brush(QPalette::Dark)); + painter.translate(-d->m_viewport.x(), -d->m_viewport.y()); + + for (auto it = d->m_documentLayout.pageGeometries.cbegin(); it != d->m_documentLayout.pageGeometries.cend(); ++it) { + const QRect pageGeometry = it.value(); + if (pageGeometry.intersects(d->m_viewport)) { // page needs to be painted + painter.fillRect(pageGeometry, Qt::white); + + const int page = it.key(); + const auto pageIt = d->m_pageCache.constFind(page); + if (pageIt != d->m_pageCache.cend()) { + const QImage &img = pageIt.value(); + painter.drawImage(pageGeometry.topLeft(), img); + } else { + d->m_pageRenderer->requestPage(page, pageGeometry.size()); + } + } + } +} + +void QPdfView::resizeEvent(QResizeEvent *event) +{ + Q_D(QPdfView); + + QAbstractScrollArea::resizeEvent(event); + + d->updateScrollBars(); + d->calculateViewport(); +} + +void QPdfView::scrollContentsBy(int dx, int dy) +{ + Q_D(QPdfView); + + QAbstractScrollArea::scrollContentsBy(dx, dy); + + d->calculateViewport(); +} + +QT_END_NAMESPACE + +#include "moc_qpdfview.cpp" diff --git a/src/pdfwidgets/qpdfview.h b/src/pdfwidgets/qpdfview.h new file mode 100644 index 000000000..cee1cb64c --- /dev/null +++ b/src/pdfwidgets/qpdfview.h @@ -0,0 +1,122 @@ +/**************************************************************************** +** +** Copyright (C) 2017 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Tobias König <tobias.koenig@kdab.com> +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtPDF module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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.LGPLv3 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.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 later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPDFVIEW_H +#define QPDFVIEW_H + +#include <QtPdfWidgets/qtpdfwidgetsglobal.h> +#include <QtWidgets/qabstractscrollarea.h> + +QT_BEGIN_NAMESPACE + +class QPdfDocument; +class QPdfPageNavigation; +class QPdfViewPrivate; + +class Q_PDF_WIDGETS_EXPORT QPdfView : public QAbstractScrollArea +{ + Q_OBJECT + + Q_PROPERTY(QPdfDocument* document READ document WRITE setDocument NOTIFY documentChanged) + + Q_PROPERTY(PageMode pageMode READ pageMode WRITE setPageMode NOTIFY pageModeChanged) + Q_PROPERTY(ZoomMode zoomMode READ zoomMode WRITE setZoomMode NOTIFY zoomModeChanged) + Q_PROPERTY(qreal zoomFactor READ zoomFactor WRITE setZoomFactor NOTIFY zoomFactorChanged) + + Q_PROPERTY(int pageSpacing READ pageSpacing WRITE setPageSpacing NOTIFY pageSpacingChanged) + Q_PROPERTY(QMargins documentMargins READ documentMargins WRITE setDocumentMargins NOTIFY documentMarginsChanged) + +public: + enum PageMode + { + SinglePage, + MultiPage + }; + Q_ENUM(PageMode) + + enum ZoomMode + { + CustomZoom, + FitToWidth, + FitInView + }; + Q_ENUM(ZoomMode) + + explicit QPdfView(QWidget *parent = nullptr); + ~QPdfView(); + + void setDocument(QPdfDocument *document); + QPdfDocument *document() const; + + QPdfPageNavigation *pageNavigation() const; + + PageMode pageMode() const; + ZoomMode zoomMode() const; + qreal zoomFactor() const; + + int pageSpacing() const; + void setPageSpacing(int spacing); + + QMargins documentMargins() const; + void setDocumentMargins(QMargins margins); + +public Q_SLOTS: + void setPageMode(PageMode mode); + void setZoomMode(ZoomMode mode); + void setZoomFactor(qreal factor); + +Q_SIGNALS: + void documentChanged(QPdfDocument *document); + void pageModeChanged(PageMode pageMode); + void zoomModeChanged(ZoomMode zoomMode); + void zoomFactorChanged(qreal zoomFactor); + void pageSpacingChanged(int pageSpacing); + void documentMarginsChanged(QMargins documentMargins); + +protected: + explicit QPdfView(QPdfViewPrivate &, QWidget *); + + void paintEvent(QPaintEvent *event) override; + void resizeEvent(QResizeEvent *event) override; + void scrollContentsBy(int dx, int dy) override; + +private: + Q_DECLARE_PRIVATE(QPdfView) +}; + +QT_END_NAMESPACE + +#endif // QPDFVIEW_H diff --git a/src/pdfwidgets/qpdfview_p.h b/src/pdfwidgets/qpdfview_p.h new file mode 100644 index 000000000..9fd54b4b6 --- /dev/null +++ b/src/pdfwidgets/qpdfview_p.h @@ -0,0 +1,119 @@ +/**************************************************************************** +** +** Copyright (C) 2017 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Tobias König <tobias.koenig@kdab.com> +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtPDF module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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.LGPLv3 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.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 later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPDFVIEW_P_H +#define QPDFVIEW_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qpdfview.h" + +#include <QPointer> +#include <QtWidgets/private/qabstractscrollarea_p.h> + +QT_BEGIN_NAMESPACE + +class QPdfPageRenderer; + +class QPdfViewPrivate : public QAbstractScrollAreaPrivate +{ + Q_DECLARE_PUBLIC(QPdfView) + +public: + QPdfViewPrivate(); + void init(); + + void documentStatusChanged(); + void currentPageChanged(int currentPage); + void calculateViewport(); + void setViewport(QRect viewport); + void updateScrollBars(); + + void pageRendered(int pageNumber, QSize imageSize, const QImage &image, quint64 requestId); + void invalidateDocumentLayout(); + void invalidatePageCache(); + + qreal yPositionForPage(int page) const; + + struct DocumentLayout + { + QSize documentSize; + QHash<int, QRect> pageGeometries; + }; + + DocumentLayout calculateDocumentLayout() const; + void updateDocumentLayout(); + + QPointer<QPdfDocument> m_document; + QPdfPageNavigation* m_pageNavigation; + QPdfPageRenderer *m_pageRenderer; + + QPdfView::PageMode m_pageMode; + QPdfView::ZoomMode m_zoomMode; + qreal m_zoomFactor; + + int m_pageSpacing; + QMargins m_documentMargins; + + bool m_blockPageScrolling; + + QMetaObject::Connection m_documentStatusChangedConnection; + + QRect m_viewport; + + QHash<int, QImage> m_pageCache; + QVector<int> m_cachedPagesLRU; + int m_pageCacheLimit; + + DocumentLayout m_documentLayout; + + qreal m_screenResolution; // pixels per point +}; + +Q_DECLARE_TYPEINFO(QPdfViewPrivate::DocumentLayout, Q_MOVABLE_TYPE); + +QT_END_NAMESPACE + +#endif // QPDFVIEW_P_H diff --git a/src/pdfwidgets/qtpdfwidgetsglobal.h b/src/pdfwidgets/qtpdfwidgetsglobal.h new file mode 100644 index 000000000..6c73a34f6 --- /dev/null +++ b/src/pdfwidgets/qtpdfwidgetsglobal.h @@ -0,0 +1,59 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtPDF module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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.LGPLv3 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.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 later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QTPDFWIDGETSGLOBAL_H +#define QTPDFWIDGETSGLOBAL_H + +#include <QtCore/qglobal.h> + +QT_BEGIN_NAMESPACE + +#ifndef Q_PDF_WIDGETS_EXPORT +# ifndef QT_STATIC +# if defined(QT_BUILD_PDFWIDGETS_LIB) +# define Q_PDF_WIDGETS_EXPORT Q_DECL_EXPORT +# else +# define Q_PDF_WIDGETS_EXPORT Q_DECL_IMPORT +# endif +# else +# define Q_PDF_WIDGETS_EXPORT +# endif +#endif + +QT_END_NAMESPACE + +#endif // QTPDFWIDGETSGLOBAL_H + diff --git a/src/plugins/imageformats/imageformats.pro b/src/plugins/imageformats/imageformats.pro new file mode 100644 index 000000000..c3b9cb3a4 --- /dev/null +++ b/src/plugins/imageformats/imageformats.pro @@ -0,0 +1,2 @@ +TEMPLATE = subdirs +SUBDIRS += pdf diff --git a/src/plugins/imageformats/pdf/main.cpp b/src/plugins/imageformats/pdf/main.cpp new file mode 100644 index 000000000..b4d59353c --- /dev/null +++ b/src/plugins/imageformats/pdf/main.cpp @@ -0,0 +1,74 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtPDF module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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.LGPLv3 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.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 later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qpdfiohandler_p.h" + +QT_BEGIN_NAMESPACE + +class QPdfPlugin : public QImageIOPlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID QImageIOHandlerFactoryInterface_iid FILE "pdf.json") + +public: + Capabilities capabilities(QIODevice *device, const QByteArray &format) const override; + QImageIOHandler *create(QIODevice *device, const QByteArray &format = QByteArray()) const override; +}; + +QImageIOPlugin::Capabilities QPdfPlugin::capabilities(QIODevice *device, const QByteArray &format) const +{ + if (format == "pdf") + return Capabilities(CanRead); + if (!format.isEmpty()) + return {}; + + Capabilities cap; + if (device->isReadable() && QPdfIOHandler::canRead(device)) + cap |= CanRead; + return cap; +} + +QImageIOHandler *QPdfPlugin::create(QIODevice *device, const QByteArray &format) const +{ + QPdfIOHandler *hand = new QPdfIOHandler(); + hand->setDevice(device); + hand->setFormat(format); + return hand; +} + +QT_END_NAMESPACE + +#include "main.moc" diff --git a/src/plugins/imageformats/pdf/pdf.json b/src/plugins/imageformats/pdf/pdf.json new file mode 100644 index 000000000..1f5268ca1 --- /dev/null +++ b/src/plugins/imageformats/pdf/pdf.json @@ -0,0 +1,4 @@ +{ + "Keys": [ "pdf" ], + "MimeTypes": [ "application/pdf" ] +} diff --git a/src/plugins/imageformats/pdf/pdf.pro b/src/plugins/imageformats/pdf/pdf.pro new file mode 100644 index 000000000..b820ae7d5 --- /dev/null +++ b/src/plugins/imageformats/pdf/pdf.pro @@ -0,0 +1,11 @@ +TARGET = qpdf + +PLUGIN_TYPE = imageformats +PLUGIN_EXTENDS = pdf +PLUGIN_CLASS_NAME = QPdfPlugin +load(qt_plugin) + +HEADERS += qpdfiohandler_p.h +SOURCES += main.cpp \ + qpdfiohandler.cpp +QT += pdf diff --git a/src/plugins/imageformats/pdf/qpdfiohandler.cpp b/src/plugins/imageformats/pdf/qpdfiohandler.cpp new file mode 100644 index 000000000..9df85cf08 --- /dev/null +++ b/src/plugins/imageformats/pdf/qpdfiohandler.cpp @@ -0,0 +1,238 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtPDF module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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.LGPLv3 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.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 later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qpdfiohandler_p.h" +#include <QLoggingCategory> +#include <QPainter> + +QT_BEGIN_NAMESPACE + +Q_LOGGING_CATEGORY(qLcPdf, "qt.imageformat.pdf") + +QPdfIOHandler::QPdfIOHandler() +{ +} + +bool QPdfIOHandler::canRead() const +{ + if (!device()) + return false; + if (m_loaded) + return true; + if (QPdfIOHandler::canRead(device())) { + setFormat("pdf"); + return true; + } + return false; +} + +bool QPdfIOHandler::canRead(QIODevice *device) +{ + char buf[6]; + device->peek(buf, 6); + return (!qstrncmp(buf, "%PDF-", 5) || Q_UNLIKELY(!qstrncmp(buf, "\012%PDF-", 6))); +} + +int QPdfIOHandler::currentImageNumber() const +{ + return m_page; +} + +QRect QPdfIOHandler::currentImageRect() const +{ + return QRect(QPoint(0, 0), m_doc.pageSize(m_page).toSize()); +} + +int QPdfIOHandler::imageCount() const +{ + int ret = 0; + if (const_cast<QPdfIOHandler *>(this)->load(device())) + ret = m_doc.pageCount(); + qCDebug(qLcPdf) << "imageCount" << ret; + return ret; +} + +QByteArray QPdfIOHandler::name() const +{ + return m_doc.metaData(QPdfDocument::MetaDataField::Title).toString().toLocal8Bit(); +} + +bool QPdfIOHandler::read(QImage *image) +{ + if (load(device())) { + if (m_page >= m_doc.pageCount()) + return false; + if (m_page < 0) + m_page = 0; + const bool xform = (m_clipRect.isValid() || m_scaledSize.isValid() || m_scaledClipRect.isValid()); + QSize finalSize = m_doc.pageSize(m_page).toSize(); + QRectF bounds; + if (xform && !finalSize.isEmpty()) { + bounds = QRectF(QPointF(0,0), QSizeF(finalSize)); + QPoint tr1, tr2; + QSizeF sc(1, 1); + if (m_clipRect.isValid()) { + tr1 = -m_clipRect.topLeft(); + finalSize = m_clipRect.size(); + } + if (m_scaledSize.isValid()) { + sc = QSizeF(qreal(m_scaledSize.width()) / finalSize.width(), + qreal(m_scaledSize.height()) / finalSize.height()); + finalSize = m_scaledSize; + } + if (m_scaledClipRect.isValid()) { + tr2 = -m_scaledClipRect.topLeft(); + finalSize = m_scaledClipRect.size(); + } + QTransform t; + t.translate(tr2.x(), tr2.y()); + t.scale(sc.width(), sc.height()); + t.translate(tr1.x(), tr1.y()); + bounds = t.mapRect(bounds); + } + qCDebug(qLcPdf) << Q_FUNC_INFO << m_page << finalSize; + if (image->size() != finalSize || !image->reinterpretAsFormat(QImage::Format_ARGB32_Premultiplied)) { + *image = QImage(finalSize, QImage::Format_ARGB32_Premultiplied); + if (!finalSize.isEmpty() && image->isNull()) { + // avoid QTBUG-68229 + qWarning("QPdfIOHandler: QImage allocation failed (size %i x %i)", finalSize.width(), finalSize.height()); + return false; + } + } + if (!finalSize.isEmpty()) { + image->fill(m_backColor.rgba()); + QPainter p(image); + QImage pageImage = m_doc.render(m_page, finalSize); + p.drawImage(0, 0, pageImage); + p.end(); + } + return true; + } + + return false; +} + +QVariant QPdfIOHandler::option(ImageOption option) const +{ + switch (option) { + case ImageFormat: + return QImage::Format_ARGB32_Premultiplied; + case Size: + const_cast<QPdfIOHandler *>(this)->load(device()); + return m_doc.pageSize(qMax(0, m_page)); + case ClipRect: + return m_clipRect; + case ScaledSize: + return m_scaledSize; + case ScaledClipRect: + return m_scaledClipRect; + case BackgroundColor: + return m_backColor; + case Name: + return m_doc.metaData(QPdfDocument::Title); + default: + break; + } + return QVariant(); +} + +void QPdfIOHandler::setOption(ImageOption option, const QVariant & value) +{ + switch (option) { + case ClipRect: + m_clipRect = value.toRect(); + break; + case ScaledSize: + m_scaledSize = value.toSize(); + break; + case ScaledClipRect: + m_scaledClipRect = value.toRect(); + break; + case BackgroundColor: + m_backColor = value.value<QColor>(); + break; + default: + break; + } +} + +bool QPdfIOHandler::supportsOption(ImageOption option) const +{ + switch (option) + { + case ImageFormat: + case Size: + case ClipRect: + case ScaledSize: + case ScaledClipRect: + case BackgroundColor: + case Name: + return true; + default: + break; + } + return false; +} + +bool QPdfIOHandler::jumpToImage(int frame) +{ + qCDebug(qLcPdf) << Q_FUNC_INFO << frame; + if (frame < 0 || frame >= m_doc.pageCount()) + return false; + m_page = frame; + return true; +} + +bool QPdfIOHandler::jumpToNextImage() +{ + return jumpToImage(m_page + 1); +} + +bool QPdfIOHandler::load(QIODevice *device) +{ + if (m_loaded) + return true; + if (format().isEmpty()) + if (!canRead()) + return false; + + m_doc.load(device); + m_loaded = (m_doc.error() == QPdfDocument::DocumentError::NoError); + + return m_loaded; +} + +QT_END_NAMESPACE diff --git a/src/plugins/imageformats/pdf/qpdfiohandler_p.h b/src/plugins/imageformats/pdf/qpdfiohandler_p.h new file mode 100644 index 000000000..ca0a27581 --- /dev/null +++ b/src/plugins/imageformats/pdf/qpdfiohandler_p.h @@ -0,0 +1,89 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtPDF module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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.LGPLv3 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.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 later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPDFIOHANDLER_H +#define QPDFIOHANDLER_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtGui/qimageiohandler.h> +#include <QtPdf/QPdfDocument> + +QT_BEGIN_NAMESPACE + +class QPdfIOHandler : public QImageIOHandler +{ +public: + QPdfIOHandler(); + bool canRead() const override; + static bool canRead(QIODevice *device); + int currentImageNumber() const override; + QRect currentImageRect() const override; + int imageCount() const override; + QByteArray name() const override; + bool read(QImage *image) override; + QVariant option(ImageOption option) const override; + void setOption(ImageOption option, const QVariant & value) override; + bool supportsOption(ImageOption option) const override; + bool jumpToImage(int frame) override; + bool jumpToNextImage() override; + +private: + bool load(QIODevice *device); + +private: + QPdfDocument m_doc; + int m_page = -1; + + QRect m_clipRect; + QSize m_scaledSize; + QRect m_scaledClipRect; + QColor m_backColor = Qt::transparent; + bool m_loaded = false; +}; + +QT_END_NAMESPACE + +#endif // QPDFIOHANDLER_H diff --git a/src/plugins/plugins.pro b/src/plugins/plugins.pro index 6698a9736..71c24e6c2 100644 --- a/src/plugins/plugins.pro +++ b/src/plugins/plugins.pro @@ -1,2 +1,3 @@ TEMPLATE = subdirs -qtHaveModule(designer): SUBDIRS += qwebengineview +qtHaveModule(webengine-widgets): qtHaveModule(designer): SUBDIRS += qwebengineview +qtHaveModule(pdf): qtConfig(imageformatplugin): SUBDIRS += imageformats diff --git a/src/process/Entitlements_mac.plist b/src/process/QtWebEngineProcess.entitlements index f2fbabddb..f2fbabddb 100644 --- a/src/process/Entitlements_mac.plist +++ b/src/process/QtWebEngineProcess.entitlements diff --git a/src/process/process.pro b/src/process/process.pro index e861af182..72aad52a5 100644 --- a/src/process/process.pro +++ b/src/process/process.pro @@ -46,6 +46,11 @@ qtConfig(build_all): CONFIG += build_all qtConfig(framework) { # Deploy the QtWebEngineProcess app bundle into the QtWebEngineCore framework. DESTDIR = $$MODULE_BASE_OUTDIR/lib/QtWebEngineCore.framework/Versions/5/Helpers + + # Deploy the entitlements file so macdeployqt can use it. + entitlements.files = QtWebEngineProcess.entitlements + entitlements.path = Contents/Resources/ + QMAKE_BUNDLE_DATA += entitlements } else { CONFIG -= app_bundle win32: DESTDIR = $$MODULE_BASE_OUTDIR/bin diff --git a/src/src.pro b/src/src.pro index de88878a6..6b45ac84a 100644 --- a/src/src.pro +++ b/src/src.pro @@ -4,9 +4,11 @@ include($$QTWEBENGINE_OUT_ROOT/src/buildtools/qtbuildtools-config.pri) include($$QTWEBENGINE_OUT_ROOT/src/core/qtwebenginecore-config.pri) include($$QTWEBENGINE_OUT_ROOT/src/webengine/qtwebengine-config.pri) include($$QTWEBENGINE_OUT_ROOT/src/webenginewidgets/qtwebenginewidgets-config.pri) +include($$QTWEBENGINE_OUT_ROOT/src/pdf/qtpdf-config.pri) +include($$QTWEBENGINE_OUT_ROOT/src/pdfwidgets/qtpdfwidgets-config.pri) QT_FOR_CONFIG += buildtools-private webenginecore webenginecore-private webengine-private \ - webenginewidgets-private + webenginewidgets-private pdf-private pdfwidgets-private TEMPLATE = subdirs @@ -38,7 +40,18 @@ qtConfig(build-qtwebengine-core):qtConfig(webengine-core-support) { } } -!qtConfig(webengine-core-support): qtConfig(build-qtwebengine-core) { +qtConfig(build-qtpdf):qtConfig(webengine-qtpdf-support) { + pdf.depends = buildtools + SUBDIRS += pdf + !contains(SUBDIRS, buildtools): SUBDIRS += buildtools + !contains(SUBDIRS, plugins): SUBDIRS += plugins + qtConfig(pdf-widgets) { + pdfwidgets.depends = pdf + SUBDIRS += pdfwidgets + } +} + +!qtConfig(webengine-core-support):if(qtConfig(build-qtwebengine-core)|qtConfig(build-qtpdf)) { !qtwebengine_makeCheckError():!isEmpty(skipBuildReason):!build_pass { errorbuild.commands = @echo Modules will not be built. $${skipBuildReason} errorbuild.CONFIG = phony diff --git a/src/tools/qwebengine_convert_dict/main.cpp b/src/tools/qwebengine_convert_dict/main.cpp index 1694dbcef..0ebba96b1 100644 --- a/src/tools/qwebengine_convert_dict/main.cpp +++ b/src/tools/qwebengine_convert_dict/main.cpp @@ -84,7 +84,7 @@ inline bool VerifyWords(const convert_dict::DicReader::WordList& org_words, hunspell::BDictReader reader; if (!reader.Init(reinterpret_cast<const unsigned char*>(serialized.data()), serialized.size())) { - out << "BDict is invalid" << endl; + out << "BDict is invalid" << Qt::endl; return false; } hunspell::WordIterator iter = reader.GetAllWordIterator(); @@ -96,7 +96,7 @@ inline bool VerifyWords(const convert_dict::DicReader::WordList& org_words, for (size_t i = 0; i < org_words.size(); i++) { int affix_matches = iter.Advance(buf, buf_size, affix_ids); if (affix_matches == 0) { - out << "Found the end before we expected" << endl; + out << "Found the end before we expected" << Qt::endl; return false; } @@ -104,7 +104,7 @@ inline bool VerifyWords(const convert_dict::DicReader::WordList& org_words, out << "Word does not match!\n" << " Index: " << i << "\n" << " Expected: " << QString::fromStdString(org_words[i].first) << "\n" - << " Actual: " << QString::fromUtf8(buf) << endl; + << " Actual: " << QString::fromUtf8(buf) << Qt::endl; return false; } @@ -118,7 +118,7 @@ inline bool VerifyWords(const convert_dict::DicReader::WordList& org_words, << " Index: " << i << "\n" << " Word: " << QString::fromUtf8(buf) << "\n" << " Expected: " << expectedAffixes << "\n" - << " Actual: " << actualAffixes << endl; + << " Actual: " << actualAffixes << Qt::endl; return false; } } @@ -148,7 +148,7 @@ int main(int argc, char *argv[]) out << "Usage: qwebengine_convert_dict <dic file> <bdic file>\n\nExample:\n" "qwebengine_convert_dict ./en-US.dic ./en-US.bdic\nwill read en-US.dic, " "en-US.dic_delta, and en-US.aff from the current directory and generate " - "en-US.bdic\n" << endl; + "en-US.bdic\n" << Qt::endl; return 1; } @@ -184,7 +184,7 @@ int main(int argc, char *argv[]) out << "Couldn't find ICU data directory. Please check that the following path exists: " << icuDataDir << "\nAlternatively provide the directory path via the QT_WEBENGINE_ICU_DAT_DIR " - "environment variable.\n" << endl; + "environment variable.\n" << Qt::endl; return 1; } @@ -196,21 +196,21 @@ int main(int argc, char *argv[]) base::FilePath file_out_path = toFilePath(argv[2]); base::FilePath aff_path = file_in_path.ReplaceExtension(FILE_PATH_LITERAL(".aff")); - out << "Reading " << toQt(aff_path.value()) << endl; + out << "Reading " << toQt(aff_path.value()) << Qt::endl; convert_dict::AffReader aff_reader(aff_path); if (!aff_reader.Read()) { - out << "Unable to read the aff file." << endl; + out << "Unable to read the aff file." << Qt::endl; return 1; } base::FilePath dic_path = file_in_path.ReplaceExtension(FILE_PATH_LITERAL(".dic")); - out << "Reading " << toQt(dic_path.value()) << endl; + out << "Reading " << toQt(dic_path.value()) << Qt::endl; // DicReader will also read the .dic_delta file. convert_dict::DicReader dic_reader(dic_path); if (!dic_reader.Read(&aff_reader)) { - out << "Unable to read the dic file." << endl; + out << "Unable to read the dic file." << Qt::endl; return 1; } @@ -222,27 +222,27 @@ int main(int argc, char *argv[]) writer.SetOtherCommands(aff_reader.other_commands()); writer.SetWords(dic_reader.words()); - out << "Serializing..." << endl; + out << "Serializing..." << Qt::endl; std::string serialized = writer.GetBDict(); - out << "Verifying..." << endl; + out << "Verifying..." << Qt::endl; if (!VerifyWords(dic_reader.words(), serialized, out)) { - out << "ERROR converting, the dictionary does not check out OK." << endl; + out << "ERROR converting, the dictionary does not check out OK." << Qt::endl; return 1; } - out << "Writing " << toQt(file_out_path.value()) << endl; + out << "Writing " << toQt(file_out_path.value()) << Qt::endl; FILE *out_file = base::OpenFile(file_out_path, "wb"); if (!out_file) { - out << "ERROR writing file" << endl; + out << "ERROR writing file" << Qt::endl; return 1; } size_t written = fwrite(&serialized[0], 1, serialized.size(), out_file); Q_ASSERT(written == serialized.size()); base::CloseFile(out_file); - out << "Success. Dictionary converted." << endl; + out << "Success. Dictionary converted." << Qt::endl; return 0; } diff --git a/src/webengine/api/qquickwebenginehistory.cpp b/src/webengine/api/qquickwebenginehistory.cpp index d3c4a0026..e77974a0f 100644 --- a/src/webengine/api/qquickwebenginehistory.cpp +++ b/src/webengine/api/qquickwebenginehistory.cpp @@ -327,6 +327,20 @@ QQuickWebEngineHistoryListModel *QQuickWebEngineHistory::forwardItems() const return d->m_forwardNavigationModel.data(); } +/*! + \qmlmethod void WebEngineHistory::clear() + \since QtWebEngine 1.11 + + Clears the history. +*/ +void QQuickWebEngineHistory::clear() +{ + Q_D(QQuickWebEngineHistory); + d->m_view->adapter->clearNavigationHistory(); + d->m_view->updateNavigationActions(); + reset(); +} + void QQuickWebEngineHistory::reset() { Q_D(QQuickWebEngineHistory); diff --git a/src/webengine/api/qquickwebenginehistory_p.h b/src/webengine/api/qquickwebenginehistory_p.h index bf049b2a6..5d4783e96 100644 --- a/src/webengine/api/qquickwebenginehistory_p.h +++ b/src/webengine/api/qquickwebenginehistory_p.h @@ -107,6 +107,7 @@ public: QQuickWebEngineHistoryListModel *items() const; QQuickWebEngineHistoryListModel *backItems() const; QQuickWebEngineHistoryListModel *forwardItems() const; + Q_REVISION(1) Q_INVOKABLE void clear(); void reset(); diff --git a/src/webengine/api/qquickwebenginetestsupport.cpp b/src/webengine/api/qquickwebenginetestsupport.cpp index b7b863125..bef87160e 100644 --- a/src/webengine/api/qquickwebenginetestsupport.cpp +++ b/src/webengine/api/qquickwebenginetestsupport.cpp @@ -157,7 +157,7 @@ void QQuickWebEngineTestEvent::mouseEvent(QEvent::Type type, QWindow *window, QO if (sgitem) pos = sgitem->mapToScene(_pos).toPoint(); - QMouseEvent me(type, pos, window->mapFromGlobal(pos), Qt::LeftButton, Qt::LeftButton, 0); + QMouseEvent me(type, pos, window->mapFromGlobal(pos), Qt::LeftButton, Qt::LeftButton, {}); me.setTimestamp(++QTest::lastMouseTimestamp); QSpontaneKeyEvent::setSpontaneous(&me); diff --git a/src/webengine/api/qquickwebengineview.cpp b/src/webengine/api/qquickwebengineview.cpp index 83ada3c11..67d4142b9 100644 --- a/src/webengine/api/qquickwebengineview.cpp +++ b/src/webengine/api/qquickwebengineview.cpp @@ -428,6 +428,12 @@ void QQuickWebEngineViewPrivate::recentlyAudibleChanged(bool recentlyAudible) Q_EMIT q->recentlyAudibleChanged(recentlyAudible); } +void QQuickWebEngineViewPrivate::renderProcessPidChanged(qint64 pid) +{ + Q_Q(QQuickWebEngineView); + Q_EMIT q->renderProcessPidChanged(pid); +} + QRectF QQuickWebEngineViewPrivate::viewportRect() const { Q_Q(const QQuickWebEngineView); @@ -1403,6 +1409,12 @@ bool QQuickWebEngineView::recentlyAudible() const return d->adapter->recentlyAudible(); } +qint64 QQuickWebEngineView::renderProcessPid() const +{ + const Q_D(QQuickWebEngineView); + return d->adapter->renderProcessPid(); +} + void QQuickWebEngineView::printToPdf(const QString& filePath, PrintedPageSizeId pageSizeId, PrintedPageOrientation orientation) { #if QT_CONFIG(webengine_printing_and_pdf) diff --git a/src/webengine/api/qquickwebengineview_p.h b/src/webengine/api/qquickwebengineview_p.h index 618f9407e..ab84b2600 100644 --- a/src/webengine/api/qquickwebengineview_p.h +++ b/src/webengine/api/qquickwebengineview_p.h @@ -142,6 +142,8 @@ class Q_WEBENGINE_PRIVATE_EXPORT QQuickWebEngineView : public QQuickItem { Q_PROPERTY(LifecycleState lifecycleState READ lifecycleState WRITE setLifecycleState NOTIFY lifecycleStateChanged REVISION 10 FINAL) Q_PROPERTY(LifecycleState recommendedState READ recommendedState NOTIFY recommendedStateChanged REVISION 10 FINAL) + Q_PROPERTY(qint64 renderProcessPid READ renderProcessPid NOTIFY renderProcessPidChanged FINAL REVISION 11) + public: QQuickWebEngineView(QQuickItem *parent = 0); ~QQuickWebEngineView(); @@ -493,6 +495,8 @@ public: void setAudioMuted(bool muted); bool recentlyAudible() const; + qint64 renderProcessPid() const; + #if QT_CONFIG(webengine_testsupport) QQuickWebEngineTestSupport *testSupport() const; void setTestSupport(QQuickWebEngineTestSupport *testSupport); @@ -520,7 +524,7 @@ public Q_SLOTS: void reload(); Q_REVISION(1) void reloadAndBypassCache(); void stop(); - Q_REVISION(1) void findText(const QString &subString, FindFlags options = 0, const QJSValue &callback = QJSValue()); + Q_REVISION(1) void findText(const QString &subString, FindFlags options = { }, const QJSValue &callback = QJSValue()); Q_REVISION(1) void fullScreenCancelled(); Q_REVISION(1) void grantFeaturePermission(const QUrl &securityOrigin, Feature, bool granted); Q_REVISION(2) void setActiveFocusOnPress(bool arg); @@ -576,6 +580,7 @@ Q_SIGNALS: Q_REVISION(10) void lifecycleStateChanged(LifecycleState state); Q_REVISION(10) void recommendedStateChanged(LifecycleState state); Q_REVISION(10) void findTextFinished(const QWebEngineFindTextResult &result); + Q_REVISION(11) void renderProcessPidChanged(qint64 pid); #if QT_CONFIG(webengine_testsupport) void testSupportChanged(); diff --git a/src/webengine/api/qquickwebengineview_p_p.h b/src/webengine/api/qquickwebengineview_p_p.h index a2ae86f91..8557a8db5 100644 --- a/src/webengine/api/qquickwebengineview_p_p.h +++ b/src/webengine/api/qquickwebengineview_p_p.h @@ -112,6 +112,7 @@ public: void didUpdateTargetURL(const QUrl&) override; void selectionChanged() override; void recentlyAudibleChanged(bool recentlyAudible) override; + void renderProcessPidChanged(qint64 pid) override; QRectF viewportRect() const override; QColor backgroundColor() const override; void loadStarted(const QUrl &provisionalUrl, bool isErrorPage = false) override; diff --git a/src/webengine/doc/src/webengineview_lgpl.qdoc b/src/webengine/doc/src/webengineview_lgpl.qdoc index 6e349dfb3..0cd8441cf 100644 --- a/src/webengine/doc/src/webengineview_lgpl.qdoc +++ b/src/webengine/doc/src/webengineview_lgpl.qdoc @@ -1614,3 +1614,27 @@ \sa findText(), FindTextResult */ + +/*! + \qmlproperty qint64 WebEngineView::renderProcessPid + \brief Returns the process ID (PID) of the render process assigned to the + current page's main frame. + \since QtWebEngine 1.11 + \readonly + + If no render process is available yet, \c 0 is returned. + + \sa renderProcessPidChanged +*/ + +/*! + \qmlsignal WebEngineView::renderProcessPidChanged(qint64 pid) + \since QtWebEngine 1.11 + \readonly + + If no render process is available yet, \c 0 is returned. + This signal is emitted when the PID (process ID) of the page's underlying + render process changed. + + \sa renderProcessPid +*/ diff --git a/src/webengine/plugin/plugin.cpp b/src/webengine/plugin/plugin.cpp index 3acf7d058..a74373b23 100644 --- a/src/webengine/plugin/plugin.cpp +++ b/src/webengine/plugin/plugin.cpp @@ -97,6 +97,7 @@ public: qmlRegisterType<QQuickWebEngineView, 8>(uri, 1, 8, "WebEngineView"); qmlRegisterType<QQuickWebEngineView, 9>(uri, 1, 9, "WebEngineView"); qmlRegisterType<QQuickWebEngineView, 10>(uri, 1, 10, "WebEngineView"); + qmlRegisterType<QQuickWebEngineView, 11>(uri, 1, 11, "WebEngineView"); qmlRegisterType<QQuickWebEngineProfile>(uri, 1, 1, "WebEngineProfile"); qmlRegisterType<QQuickWebEngineProfile, 1>(uri, 1, 2, "WebEngineProfile"); qmlRegisterType<QQuickWebEngineProfile, 2>(uri, 1, 3, "WebEngineProfile"); @@ -137,6 +138,7 @@ public: qmlRegisterSingletonType<QQuickWebEngineSingleton>(uri, 1, 1, "WebEngine", webEngineSingletonProvider); qmlRegisterUncreatableType<QQuickWebEngineHistory>(uri, 1, 1, "NavigationHistory", msgUncreatableType("NavigationHistory")); + qmlRegisterUncreatableType<QQuickWebEngineHistory, 1>(uri, 1, 11, "NavigationHistory", msgUncreatableType("NavigationHistory")); qmlRegisterUncreatableType<QQuickWebEngineHistoryListModel>(uri, 1, 1, "NavigationHistoryListModel", msgUncreatableType("NavigationHistory")); qmlRegisterUncreatableType<QQuickWebEngineFullScreenRequest>(uri, 1, 1, "FullScreenRequest", diff --git a/src/webengine/plugin/plugins.qmltypes b/src/webengine/plugin/plugins.qmltypes index a23d1c3c0..24b073290 100644 --- a/src/webengine/plugin/plugins.qmltypes +++ b/src/webengine/plugin/plugins.qmltypes @@ -1348,6 +1348,7 @@ Module { Property { name: "devToolsView"; revision: 7; type: "QQuickWebEngineView"; isPointer: true } Property { name: "lifecycleState"; revision: 10; type: "LifecycleState" } Property { name: "recommendedState"; revision: 10; type: "LifecycleState"; isReadonly: true } + Property { name: "renderProcessId"; revision: 11; type: "qint64"; isReadonly: true } Signal { name: "loadingChanged" Parameter { name: "loadRequest"; type: "QQuickWebEngineLoadRequest"; isPointer: true } @@ -1526,6 +1527,11 @@ Module { revision: 10 Parameter { name: "result"; type: "QWebEngineFindTextResult" } } + Signal { + name: "renderProcessPidChanged" + revision: 11 + Parameter { name: "pid"; type: "qint64" } + } Method { name: "runJavaScript" Parameter { type: "string" } diff --git a/src/webenginewidgets/api/qwebenginepage.cpp b/src/webenginewidgets/api/qwebenginepage.cpp index 33a7721e7..54e4dd9af 100644 --- a/src/webenginewidgets/api/qwebenginepage.cpp +++ b/src/webenginewidgets/api/qwebenginepage.cpp @@ -277,6 +277,12 @@ void QWebEnginePagePrivate::recentlyAudibleChanged(bool recentlyAudible) Q_EMIT q->recentlyAudibleChanged(recentlyAudible); } +void QWebEnginePagePrivate::renderProcessPidChanged(qint64 pid) +{ + Q_Q(QWebEnginePage); + Q_EMIT q->renderProcessPidChanged(pid); +} + QRectF QWebEnginePagePrivate::viewportRect() const { return view ? view->rect() : QRectF(); @@ -937,6 +943,13 @@ QWebEnginePage::QWebEnginePage(QObject* parent) */ /*! + \fn void QWebEnginePage::renderProcessPidChanged(qint64 pid); + \since 5.15 + + This signal is emitted when the underlying render process PID, \a renderProcessPid, changes. +*/ + +/*! \fn void QWebEnginePage::iconUrlChanged(const QUrl &url) This signal is emitted when the URL of the icon ("favicon") associated with the @@ -1149,6 +1162,20 @@ bool QWebEnginePage::recentlyAudible() const return d->adapter->isInitialized() && d->adapter->recentlyAudible(); } +/*! + \property QWebEnginePage::renderProcessPid + \brief The process ID (PID) of the render process assigned to the current + page's main frame. + \since 5.15 + + If no render process is available yet, \c 0 is returned. +*/ +qint64 QWebEnginePage::renderProcessPid() const +{ + Q_D(const QWebEnginePage); + return d->adapter->renderProcessPid(); +} + void QWebEnginePage::setView(QWidget *newViewBase) { QWebEnginePagePrivate::bindPageAndView(this, qobject_cast<QWebEngineView *>(newViewBase)); @@ -2313,7 +2340,7 @@ QStringList QWebEnginePage::chooseFiles(FileSelectionMode mode, const QStringLis QString str; switch (static_cast<FilePickerController::FileChooserMode>(mode)) { case FilePickerController::OpenMultiple: - ret = QFileDialog::getOpenFileNames(view(), QString(), QString(), filter.join(";;"), nullptr, QFileDialog::HideNameFilterDetails); + ret = QFileDialog::getOpenFileNames(view(), QString(), QString(), filter.join(QStringLiteral(";;")), nullptr, QFileDialog::HideNameFilterDetails); break; // Chromium extension, not exposed as part of the public API for now. case FilePickerController::UploadFolder: @@ -2327,7 +2354,7 @@ QStringList QWebEnginePage::chooseFiles(FileSelectionMode mode, const QStringLis ret << str; break; case FilePickerController::Open: - str = QFileDialog::getOpenFileName(view(), QString(), oldFiles.first(), filter.join(";;"), nullptr, QFileDialog::HideNameFilterDetails); + str = QFileDialog::getOpenFileName(view(), QString(), oldFiles.first(), filter.join(QStringLiteral(";;")), nullptr, QFileDialog::HideNameFilterDetails); if (!str.isNull()) ret << str; break; diff --git a/src/webenginewidgets/api/qwebenginepage.h b/src/webenginewidgets/api/qwebenginepage.h index cd012ea70..3cf6a9f8b 100644 --- a/src/webenginewidgets/api/qwebenginepage.h +++ b/src/webenginewidgets/api/qwebenginepage.h @@ -92,6 +92,7 @@ class QWEBENGINEWIDGETS_EXPORT QWebEnginePage : public QObject { Q_PROPERTY(bool visible READ isVisible WRITE setVisible NOTIFY visibleChanged) Q_PROPERTY(LifecycleState lifecycleState READ lifecycleState WRITE setLifecycleState NOTIFY lifecycleStateChanged) Q_PROPERTY(LifecycleState recommendedState READ recommendedState NOTIFY recommendedStateChanged) + Q_PROPERTY(qint64 renderProcessPid READ renderProcessPid NOTIFY renderProcessPidChanged) public: enum WebAction { @@ -305,6 +306,7 @@ public: bool isAudioMuted() const; void setAudioMuted(bool muted); bool recentlyAudible() const; + qint64 renderProcessPid() const; void printToPdf(const QString &filePath, const QPageLayout &layout = QPageLayout(QPageSize(QPageSize::A4), QPageLayout::Portrait, QMarginsF())); void printToPdf(const QWebEngineCallback<const QByteArray&> &resultCallback, const QPageLayout &layout = QPageLayout(QPageSize(QPageSize::A4), QPageLayout::Portrait, QMarginsF())); @@ -361,6 +363,7 @@ Q_SIGNALS: void contentsSizeChanged(const QSizeF &size); void audioMutedChanged(bool muted); void recentlyAudibleChanged(bool recentlyAudible); + void renderProcessPidChanged(qint64 pid); void pdfPrintingFinished(const QString &filePath, bool success); void printRequested(); diff --git a/src/webenginewidgets/api/qwebenginepage_p.h b/src/webenginewidgets/api/qwebenginepage_p.h index e78b0f926..7fdc811e2 100644 --- a/src/webenginewidgets/api/qwebenginepage_p.h +++ b/src/webenginewidgets/api/qwebenginepage_p.h @@ -103,6 +103,7 @@ public: void didUpdateTargetURL(const QUrl&) override; void selectionChanged() override; void recentlyAudibleChanged(bool recentlyAudible) override; + void renderProcessPidChanged(qint64 pid) override; QRectF viewportRect() const override; QColor backgroundColor() const override; void loadStarted(const QUrl &provisionalUrl, bool isErrorPage = false) override; diff --git a/sync.profile b/sync.profile index 4cc549106..a1e3c8ded 100644 --- a/sync.profile +++ b/sync.profile @@ -2,11 +2,14 @@ "QtWebEngine" => "$basedir/src/webengine", "QtWebEngineWidgets" => "$basedir/src/webenginewidgets", "QtWebEngineCore" => "$basedir/src/core", + "QtPdf" => "$basedir/src/pdf", + "QtPdfWidgets" => "$basedir/src/pdfwidgets", ); %moduleheaders = ( # restrict the module headers to those found in relative path "QtWebEngine" => "api", "QtWebEngineWidgets" => "api", "QtWebEngineCore" => "api", + "QtPdf" => "api" ); %classnames = ( ); diff --git a/tests/auto/auto.pro b/tests/auto/auto.pro index 59bcd5aef..9b71e1183 100644 --- a/tests/auto/auto.pro +++ b/tests/auto/auto.pro @@ -7,3 +7,8 @@ qtHaveModule(webengine) { qtHaveModule(webenginewidgets) { SUBDIRS += core widgets } + +qtHaveModule(pdf) { + SUBDIRS += pdf +} + diff --git a/tests/auto/core/qwebengineurlrequestinterceptor/tst_qwebengineurlrequestinterceptor.cpp b/tests/auto/core/qwebengineurlrequestinterceptor/tst_qwebengineurlrequestinterceptor.cpp index c0762aa14..76a061a8f 100644 --- a/tests/auto/core/qwebengineurlrequestinterceptor/tst_qwebengineurlrequestinterceptor.cpp +++ b/tests/auto/core/qwebengineurlrequestinterceptor/tst_qwebengineurlrequestinterceptor.cpp @@ -290,18 +290,21 @@ void tst_QWebEngineUrlRequestInterceptor::requestedUrl() page.setUrl(QUrl("qrc:///resources/__placeholder__")); QVERIFY(spy.wait()); QTRY_COMPARE(spy.count(), 1); + QVERIFY(interceptor.requestInfos.count() >= 1); QCOMPARE(interceptor.requestInfos.at(0).requestUrl, QUrl("qrc:///resources/content.html")); QCOMPARE(page.requestedUrl(), QUrl("qrc:///resources/__placeholder__")); QCOMPARE(page.url(), QUrl("qrc:///resources/content.html")); page.setUrl(QUrl("qrc:/non-existent.html")); QTRY_COMPARE(spy.count(), 2); + QVERIFY(interceptor.requestInfos.count() >= 3); QCOMPARE(interceptor.requestInfos.at(2).requestUrl, QUrl("qrc:/non-existent.html")); QCOMPARE(page.requestedUrl(), QUrl("qrc:///resources/__placeholder__")); QCOMPARE(page.url(), QUrl("qrc:///resources/content.html")); page.setUrl(QUrl("http://abcdef.abcdef")); QTRY_COMPARE_WITH_TIMEOUT(spy.count(), 3, 15000); + QVERIFY(interceptor.requestInfos.count() >= 4); QCOMPARE(interceptor.requestInfos.at(3).requestUrl, QUrl("http://abcdef.abcdef/")); QCOMPARE(page.requestedUrl(), QUrl("qrc:///resources/__placeholder__")); QCOMPARE(page.url(), QUrl("qrc:///resources/content.html")); @@ -359,6 +362,7 @@ void tst_QWebEngineUrlRequestInterceptor::firstPartyUrl() page.setUrl(QUrl("qrc:///resources/firstparty.html")); QVERIFY(spy.wait()); + QVERIFY(interceptor.requestInfos.count() >= 2); QCOMPARE(interceptor.requestInfos.at(0).requestUrl, QUrl("qrc:///resources/firstparty.html")); QCOMPARE(interceptor.requestInfos.at(1).requestUrl, QUrl("qrc:///resources/content.html")); QCOMPARE(interceptor.requestInfos.at(0).firstPartyUrl, QUrl("qrc:///resources/firstparty.html")); @@ -393,16 +397,19 @@ void tst_QWebEngineUrlRequestInterceptor::firstPartyUrlNestedIframes() page.setUrl(requestUrl); QTRY_COMPARE(loadSpy.count(), 1); + QVERIFY(interceptor.requestInfos.count() >= 1); RequestInfo info = interceptor.requestInfos.at(0); QCOMPARE(info.requestUrl, requestUrl); QCOMPARE(info.firstPartyUrl, requestUrl); QCOMPARE(info.resourceType, QWebEngineUrlRequestInfo::ResourceTypeMainFrame); + QVERIFY(interceptor.requestInfos.count() >= 2); info = interceptor.requestInfos.at(1); QCOMPARE(info.requestUrl, QUrl(adjustedUrl + "iframe2.html")); QCOMPARE(info.firstPartyUrl, requestUrl); QCOMPARE(info.resourceType, QWebEngineUrlRequestInfo::ResourceTypeSubFrame); + QVERIFY(interceptor.requestInfos.count() >= 3); info = interceptor.requestInfos.at(2); QCOMPARE(info.requestUrl, QUrl(adjustedUrl + "iframe3.html")); QCOMPARE(info.firstPartyUrl, requestUrl); @@ -456,6 +463,7 @@ void tst_QWebEngineUrlRequestInterceptor::requestInterceptorByResourceType() QTRY_COMPARE(interceptor.getUrlRequestForType(static_cast<QWebEngineUrlRequestInfo::ResourceType>(resourceType)).count(), 1); QList<RequestInfo> infos = interceptor.getUrlRequestForType(static_cast<QWebEngineUrlRequestInfo::ResourceType>(resourceType)); + QVERIFY(infos.count() >= 1); QCOMPARE(infos.at(0).requestUrl, requestUrl); QCOMPARE(infos.at(0).firstPartyUrl, firstPartyUrl); QCOMPARE(infos.at(0).resourceType, resourceType); diff --git a/tests/auto/pdf/pdf.pro b/tests/auto/pdf/pdf.pro new file mode 100644 index 000000000..a2b3fcff2 --- /dev/null +++ b/tests/auto/pdf/pdf.pro @@ -0,0 +1,8 @@ +TEMPLATE = subdirs + +SUBDIRS = \ + qpdfbookmarkmodel \ + qpdfpagenavigation \ + qpdfpagerenderer + +qtHaveModule(printsupport): SUBDIRS += qpdfdocument diff --git a/tests/auto/pdf/qpdfbookmarkmodel/pdf-sample.bookmarks.pdf b/tests/auto/pdf/qpdfbookmarkmodel/pdf-sample.bookmarks.pdf Binary files differnew file mode 100644 index 000000000..bd27c18b6 --- /dev/null +++ b/tests/auto/pdf/qpdfbookmarkmodel/pdf-sample.bookmarks.pdf diff --git a/tests/auto/pdf/qpdfbookmarkmodel/pdf-sample.bookmarks_pages.pdf b/tests/auto/pdf/qpdfbookmarkmodel/pdf-sample.bookmarks_pages.pdf Binary files differnew file mode 100644 index 000000000..c4e1aa36e --- /dev/null +++ b/tests/auto/pdf/qpdfbookmarkmodel/pdf-sample.bookmarks_pages.pdf diff --git a/tests/auto/pdf/qpdfbookmarkmodel/qpdfbookmarkmodel.pro b/tests/auto/pdf/qpdfbookmarkmodel/qpdfbookmarkmodel.pro new file mode 100644 index 000000000..11a010637 --- /dev/null +++ b/tests/auto/pdf/qpdfbookmarkmodel/qpdfbookmarkmodel.pro @@ -0,0 +1,5 @@ +CONFIG += testcase +TARGET = tst_qpdfbookmarkmodel +QT += pdf testlib network +macos:CONFIG -= app_bundle +SOURCES += tst_qpdfbookmarkmodel.cpp diff --git a/tests/auto/pdf/qpdfbookmarkmodel/tst_qpdfbookmarkmodel.cpp b/tests/auto/pdf/qpdfbookmarkmodel/tst_qpdfbookmarkmodel.cpp new file mode 100644 index 000000000..fddc98011 --- /dev/null +++ b/tests/auto/pdf/qpdfbookmarkmodel/tst_qpdfbookmarkmodel.cpp @@ -0,0 +1,287 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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.LGPLv3 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.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 later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +#include <QtTest/QtTest> + +#include <QPdfDocument> +#include <QPdfBookmarkModel> + +class tst_QPdfBookmarkModel: public QObject +{ + Q_OBJECT + +public: + tst_QPdfBookmarkModel() + { + qRegisterMetaType<QPdfDocument::Status>(); + } + +private slots: + void emptyModel(); + void setEmptyDocument(); + void setEmptyDocumentAndLoad(); + void setLoadedDocument(); + void unloadDocument(); + void testTreeStructure(); + void testListStructure(); + void testPageNumberRole(); +}; + +void tst_QPdfBookmarkModel::emptyModel() +{ + QPdfBookmarkModel model; + + QVERIFY(!model.document()); + QCOMPARE(model.structureMode(), QPdfBookmarkModel::TreeMode); + QCOMPARE(model.rowCount(), 0); + QCOMPARE(model.columnCount(), 1); + QCOMPARE(model.index(0, 0).isValid(), false); +} + +void tst_QPdfBookmarkModel::setEmptyDocument() +{ + QPdfDocument document; + QPdfBookmarkModel model; + + model.setDocument(&document); + + QCOMPARE(model.document(), &document); + QCOMPARE(model.structureMode(), QPdfBookmarkModel::TreeMode); + QCOMPARE(model.rowCount(), 0); + QCOMPARE(model.columnCount(), 1); + QCOMPARE(model.index(0, 0).isValid(), false); +} + +void tst_QPdfBookmarkModel::setEmptyDocumentAndLoad() +{ + QPdfDocument document; + QPdfBookmarkModel model; + + model.setDocument(&document); + + QSignalSpy modelAboutToBeResetSpy(&model, SIGNAL(modelAboutToBeReset())); + QSignalSpy modelResetSpy(&model, SIGNAL(modelReset())); + + QCOMPARE(document.load(QFINDTESTDATA("pdf-sample.bookmarks.pdf")), QPdfDocument::NoError); + + QCOMPARE(modelAboutToBeResetSpy.count(), 1); + QCOMPARE(modelResetSpy.count(), 1); + + QCOMPARE(model.rowCount(), 3); +} + +void tst_QPdfBookmarkModel::setLoadedDocument() +{ + QPdfDocument document; + QCOMPARE(document.load(QFINDTESTDATA("pdf-sample.bookmarks.pdf")), QPdfDocument::NoError); + + QPdfBookmarkModel model; + + QSignalSpy modelAboutToBeResetSpy(&model, SIGNAL(modelAboutToBeReset())); + QSignalSpy modelResetSpy(&model, SIGNAL(modelReset())); + + model.setDocument(&document); + + QCOMPARE(modelAboutToBeResetSpy.count(), 1); + QCOMPARE(modelResetSpy.count(), 1); + + QCOMPARE(model.rowCount(), 3); +} + +void tst_QPdfBookmarkModel::unloadDocument() +{ + QPdfDocument document; + QCOMPARE(document.load(QFINDTESTDATA("pdf-sample.bookmarks.pdf")), QPdfDocument::NoError); + + QPdfBookmarkModel model; + model.setDocument(&document); + + QCOMPARE(model.rowCount(), 3); + + QSignalSpy modelAboutToBeResetSpy(&model, SIGNAL(modelAboutToBeReset())); + QSignalSpy modelResetSpy(&model, SIGNAL(modelReset())); + + document.close(); + + QCOMPARE(modelAboutToBeResetSpy.count(), 1); + QCOMPARE(modelResetSpy.count(), 1); + + QCOMPARE(model.rowCount(), 0); +} + +void tst_QPdfBookmarkModel::testTreeStructure() +{ + QPdfDocument document; + QCOMPARE(document.load(QFINDTESTDATA("pdf-sample.bookmarks.pdf")), QPdfDocument::NoError); + + QPdfBookmarkModel model; + model.setDocument(&document); + + QCOMPARE(model.rowCount(), 3); + + const QModelIndex index1 = model.index(0, 0); + QCOMPARE(index1.data(QPdfBookmarkModel::TitleRole).toString(), QLatin1String("Section 1")); + QCOMPARE(index1.data(QPdfBookmarkModel::LevelRole).toInt(), 0); + QCOMPARE(model.rowCount(index1), 2); + + const QModelIndex index1_1 = model.index(0, 0, index1); + QCOMPARE(index1_1.data(QPdfBookmarkModel::TitleRole).toString(), QLatin1String("Section 1.1")); + QCOMPARE(index1_1.data(QPdfBookmarkModel::LevelRole).toInt(), 1); + QCOMPARE(model.rowCount(index1_1), 0); + + const QModelIndex index1_2 = model.index(1, 0, index1); + QCOMPARE(index1_2.data(QPdfBookmarkModel::TitleRole).toString(), QLatin1String("Section 1.2")); + QCOMPARE(index1_2.data(QPdfBookmarkModel::LevelRole).toInt(), 1); + QCOMPARE(model.rowCount(index1_2), 0); + + const QModelIndex index2 = model.index(1, 0); + QCOMPARE(index2.data(QPdfBookmarkModel::TitleRole).toString(), QLatin1String("Section 2")); + QCOMPARE(index2.data(QPdfBookmarkModel::LevelRole).toInt(), 0); + QCOMPARE(model.rowCount(index2), 2); + + const QModelIndex index2_1 = model.index(0, 0, index2); + QCOMPARE(index2_1.data(QPdfBookmarkModel::TitleRole).toString(), QLatin1String("Section 2.1")); + QCOMPARE(index2_1.data(QPdfBookmarkModel::LevelRole).toInt(), 1); + QCOMPARE(model.rowCount(index2_1), 1); + + const QModelIndex index2_1_1 = model.index(0, 0, index2_1); + QCOMPARE(index2_1_1.data(QPdfBookmarkModel::TitleRole).toString(), QLatin1String("Section 2.1.1")); + QCOMPARE(index2_1_1.data(QPdfBookmarkModel::LevelRole).toInt(), 2); + QCOMPARE(model.rowCount(index2_1_1), 0); + + const QModelIndex index2_2 = model.index(1, 0, index2); + QCOMPARE(index2_2.data(QPdfBookmarkModel::TitleRole).toString(), QLatin1String("Section 2.2")); + QCOMPARE(index2_2.data(QPdfBookmarkModel::LevelRole).toInt(), 1); + QCOMPARE(model.rowCount(index2_2), 0); + + const QModelIndex index3 = model.index(2, 0); + QCOMPARE(index3.data(QPdfBookmarkModel::TitleRole).toString(), QLatin1String("Section 3")); + QCOMPARE(index3.data(QPdfBookmarkModel::LevelRole).toInt(), 0); + QCOMPARE(model.rowCount(index3), 0); + + const QModelIndex index4 = model.index(3, 0); + QCOMPARE(index4, QModelIndex()); +} + +void tst_QPdfBookmarkModel::testListStructure() +{ + QPdfDocument document; + QCOMPARE(document.load(QFINDTESTDATA("pdf-sample.bookmarks.pdf")), QPdfDocument::NoError); + + QPdfBookmarkModel model; + model.setDocument(&document); + + QSignalSpy modelAboutToBeResetSpy(&model, SIGNAL(modelAboutToBeReset())); + QSignalSpy modelResetSpy(&model, SIGNAL(modelReset())); + + model.setStructureMode(QPdfBookmarkModel::ListMode); + + QCOMPARE(modelAboutToBeResetSpy.count(), 1); + QCOMPARE(modelResetSpy.count(), 1); + + QCOMPARE(model.rowCount(), 8); + + const QModelIndex index1 = model.index(0, 0); + QCOMPARE(index1.data(QPdfBookmarkModel::TitleRole).toString(), QLatin1String("Section 1")); + QCOMPARE(index1.data(QPdfBookmarkModel::LevelRole).toInt(), 0); + QCOMPARE(model.rowCount(index1), 0); + + const QModelIndex index1_1 = model.index(1, 0); + QCOMPARE(index1_1.data(QPdfBookmarkModel::TitleRole).toString(), QLatin1String("Section 1.1")); + QCOMPARE(index1_1.data(QPdfBookmarkModel::LevelRole).toInt(), 1); + QCOMPARE(model.rowCount(index1_1), 0); + + const QModelIndex index1_2 = model.index(2, 0); + QCOMPARE(index1_2.data(QPdfBookmarkModel::TitleRole).toString(), QLatin1String("Section 1.2")); + QCOMPARE(index1_2.data(QPdfBookmarkModel::LevelRole).toInt(), 1); + QCOMPARE(model.rowCount(index1_2), 0); + + const QModelIndex index2 = model.index(3, 0); + QCOMPARE(index2.data(QPdfBookmarkModel::TitleRole).toString(), QLatin1String("Section 2")); + QCOMPARE(index2.data(QPdfBookmarkModel::LevelRole).toInt(), 0); + QCOMPARE(model.rowCount(index2), 0); + + const QModelIndex index2_1 = model.index(4, 0); + QCOMPARE(index2_1.data(QPdfBookmarkModel::TitleRole).toString(), QLatin1String("Section 2.1")); + QCOMPARE(index2_1.data(QPdfBookmarkModel::LevelRole).toInt(), 1); + QCOMPARE(model.rowCount(index2_1), 0); + + const QModelIndex index2_1_1 = model.index(5, 0); + QCOMPARE(index2_1_1.data(QPdfBookmarkModel::TitleRole).toString(), QLatin1String("Section 2.1.1")); + QCOMPARE(index2_1_1.data(QPdfBookmarkModel::LevelRole).toInt(), 2); + QCOMPARE(model.rowCount(index2_1_1), 0); + + const QModelIndex index2_2 = model.index(6, 0); + QCOMPARE(index2_2.data(QPdfBookmarkModel::TitleRole).toString(), QLatin1String("Section 2.2")); + QCOMPARE(index2_2.data(QPdfBookmarkModel::LevelRole).toInt(), 1); + QCOMPARE(model.rowCount(index2_2), 0); + + const QModelIndex index3 = model.index(7, 0); + QCOMPARE(index3.data(QPdfBookmarkModel::TitleRole).toString(), QLatin1String("Section 3")); + QCOMPARE(index3.data(QPdfBookmarkModel::LevelRole).toInt(), 0); + QCOMPARE(model.rowCount(index3), 0); + + const QModelIndex index4 = model.index(8, 0); + QCOMPARE(index4, QModelIndex()); +} + +void tst_QPdfBookmarkModel::testPageNumberRole() +{ + QPdfDocument document; + QCOMPARE(document.load(QFINDTESTDATA("pdf-sample.bookmarks_pages.pdf")), QPdfDocument::NoError); + + QPdfBookmarkModel model; + model.setDocument(&document); + + QCOMPARE(model.rowCount(), 3); + + const QModelIndex index1 = model.index(0, 0); + QCOMPARE(index1.data(QPdfBookmarkModel::PageNumberRole).toInt(), 0); + + const QModelIndex index2 = model.index(1, 0); + QCOMPARE(index2.data(QPdfBookmarkModel::PageNumberRole).toInt(), 1); + + const QModelIndex index2_1 = model.index(0, 0, index2); + QCOMPARE(index2_1.data(QPdfBookmarkModel::PageNumberRole).toInt(), 1); + + const QModelIndex index3 = model.index(2, 0); + QCOMPARE(index3.data(QPdfBookmarkModel::PageNumberRole).toInt(), 2); +} + +QTEST_MAIN(tst_QPdfBookmarkModel) + +#include "tst_qpdfbookmarkmodel.moc" diff --git a/tests/auto/pdf/qpdfdocument/BLACKLIST b/tests/auto/pdf/qpdfdocument/BLACKLIST new file mode 100644 index 000000000..b8db556d6 --- /dev/null +++ b/tests/auto/pdf/qpdfdocument/BLACKLIST @@ -0,0 +1,6 @@ +[password] +* + +[passwordClearedOnClose] +* + diff --git a/tests/auto/pdf/qpdfdocument/pdf-sample.metadata.pdf b/tests/auto/pdf/qpdfdocument/pdf-sample.metadata.pdf Binary files differnew file mode 100644 index 000000000..c3350ba5f --- /dev/null +++ b/tests/auto/pdf/qpdfdocument/pdf-sample.metadata.pdf diff --git a/tests/auto/pdf/qpdfdocument/pdf-sample.protected.pdf b/tests/auto/pdf/qpdfdocument/pdf-sample.protected.pdf Binary files differnew file mode 100644 index 000000000..d76fdd1a6 --- /dev/null +++ b/tests/auto/pdf/qpdfdocument/pdf-sample.protected.pdf diff --git a/tests/auto/pdf/qpdfdocument/qpdfdocument.pro b/tests/auto/pdf/qpdfdocument/qpdfdocument.pro new file mode 100644 index 000000000..8382a25e3 --- /dev/null +++ b/tests/auto/pdf/qpdfdocument/qpdfdocument.pro @@ -0,0 +1,6 @@ +CONFIG += testcase +TARGET = tst_qpdfdocument +QT += pdf printsupport testlib network +macx:CONFIG -= app_bundle +SOURCES += tst_qpdfdocument.cpp + diff --git a/tests/auto/pdf/qpdfdocument/tst_qpdfdocument.cpp b/tests/auto/pdf/qpdfdocument/tst_qpdfdocument.cpp new file mode 100644 index 000000000..29b85fc89 --- /dev/null +++ b/tests/auto/pdf/qpdfdocument/tst_qpdfdocument.cpp @@ -0,0 +1,386 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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.LGPLv3 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.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 later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +#include <QtTest/QtTest> + +#include <QPainter> +#include <QPdfDocument> +#include <QPrinter> +#include <QTemporaryFile> +#include <QNetworkAccessManager> +#include <QNetworkRequest> +#include <QNetworkReply> + +class tst_QPdfDocument: public QObject +{ + Q_OBJECT + +public: + tst_QPdfDocument() + { + qRegisterMetaType<QPdfDocument::Status>(); + } + +private slots: + void pageCount(); + void loadFromIODevice(); + void loadAsync(); + void password(); + void close(); + void loadAfterClose(); + void closeOnDestroy(); + void status(); + void passwordClearedOnClose(); + void metaData(); +}; + +struct TemporaryPdf: public QTemporaryFile +{ + TemporaryPdf(); + QPageLayout pageLayout; +}; + + +TemporaryPdf::TemporaryPdf() +{ + open(); + pageLayout = QPageLayout(QPageSize(QPageSize::A4), QPageLayout::Portrait, QMarginsF()); + + { + QPrinter printer; + printer.setOutputFormat(QPrinter::PdfFormat); + printer.setOutputFileName(fileName()); + printer.setPageLayout(pageLayout); + + { + QPainter painter(&printer); + painter.drawText(100, 100, QStringLiteral("Hello Page 1")); + printer.newPage(); + painter.drawText(100, 100, QStringLiteral("Hello Page 2")); + } + } + + seek(0); +} + +void tst_QPdfDocument::pageCount() +{ + TemporaryPdf tempPdf; + + QPdfDocument doc; + QSignalSpy pageCountChangedSpy(&doc, SIGNAL(pageCountChanged(int))); + + QCOMPARE(doc.pageCount(), 0); + QCOMPARE(doc.load(tempPdf.fileName()), QPdfDocument::NoError); + QCOMPARE(doc.pageCount(), 2); + QCOMPARE(pageCountChangedSpy.count(), 1); + QCOMPARE(pageCountChangedSpy[0][0].toInt(), doc.pageCount()); + + QCOMPARE(doc.pageSize(0).toSize(), tempPdf.pageLayout.fullRectPoints().size()); +} + +void tst_QPdfDocument::loadFromIODevice() +{ + TemporaryPdf tempPdf; + QPdfDocument doc; + QSignalSpy statusChangedSpy(&doc, SIGNAL(statusChanged(QPdfDocument::Status))); + QSignalSpy pageCountChangedSpy(&doc, SIGNAL(pageCountChanged(int))); + doc.load(&tempPdf); + QCOMPARE(statusChangedSpy.count(), 2); + QCOMPARE(statusChangedSpy[0][0].value<QPdfDocument::Status>(), QPdfDocument::Loading); + QCOMPARE(statusChangedSpy[1][0].value<QPdfDocument::Status>(), QPdfDocument::Ready); + QCOMPARE(doc.error(), QPdfDocument::NoError); + QCOMPARE(doc.pageCount(), 2); + QCOMPARE(pageCountChangedSpy.count(), 1); + QCOMPARE(pageCountChangedSpy[0][0].toInt(), doc.pageCount()); +} + +void tst_QPdfDocument::loadAsync() +{ + TemporaryPdf tempPdf; + + QNetworkAccessManager nam; + + QUrl url = QUrl::fromLocalFile(tempPdf.fileName()); + QScopedPointer<QNetworkReply> reply(nam.get(QNetworkRequest(url))); + + QPdfDocument doc; + QSignalSpy statusChangedSpy(&doc, SIGNAL(statusChanged(QPdfDocument::Status))); + QSignalSpy pageCountChangedSpy(&doc, SIGNAL(pageCountChanged(int))); + + doc.load(reply.data()); + + QCOMPARE(statusChangedSpy.count(), 2); + QCOMPARE(statusChangedSpy[0][0].value<QPdfDocument::Status>(), QPdfDocument::Loading); + QCOMPARE(statusChangedSpy[1][0].value<QPdfDocument::Status>(), QPdfDocument::Ready); + QCOMPARE(doc.pageCount(), 2); + QCOMPARE(pageCountChangedSpy.count(), 1); + QCOMPARE(pageCountChangedSpy[0][0].toInt(), doc.pageCount()); +} + +void tst_QPdfDocument::password() +{ + QPdfDocument doc; + QSignalSpy passwordChangedSpy(&doc, SIGNAL(passwordChanged())); + + QCOMPARE(doc.pageCount(), 0); + QCOMPARE(doc.load(QFINDTESTDATA("pdf-sample.protected.pdf")), QPdfDocument::IncorrectPasswordError); + QCOMPARE(passwordChangedSpy.count(), 0); + doc.setPassword(QStringLiteral("WrongPassword")); + QCOMPARE(passwordChangedSpy.count(), 1); + QCOMPARE(doc.load(QFINDTESTDATA("pdf-sample.protected.pdf")), QPdfDocument::IncorrectPasswordError); + QCOMPARE(doc.status(), QPdfDocument::Error); + doc.setPassword(QStringLiteral("Qt")); + QCOMPARE(passwordChangedSpy.count(), 2); + QCOMPARE(doc.load(QFINDTESTDATA("pdf-sample.protected.pdf")), QPdfDocument::NoError); + QCOMPARE(doc.pageCount(), 1); +} + +void tst_QPdfDocument::close() +{ + TemporaryPdf tempPdf; + QPdfDocument doc; + + QSignalSpy statusChangedSpy(&doc, SIGNAL(statusChanged(QPdfDocument::Status))); + QSignalSpy pageCountChangedSpy(&doc, SIGNAL(pageCountChanged(int))); + + doc.load(&tempPdf); + + QCOMPARE(statusChangedSpy.count(), 2); + QCOMPARE(statusChangedSpy[0][0].value<QPdfDocument::Status>(), QPdfDocument::Loading); + QCOMPARE(statusChangedSpy[1][0].value<QPdfDocument::Status>(), QPdfDocument::Ready); + QCOMPARE(pageCountChangedSpy.count(), 1); + QCOMPARE(pageCountChangedSpy[0][0].toInt(), doc.pageCount()); + + statusChangedSpy.clear(); + pageCountChangedSpy.clear(); + + doc.close(); + QCOMPARE(statusChangedSpy.count(), 2); + QCOMPARE(statusChangedSpy[0][0].value<QPdfDocument::Status>(), QPdfDocument::Unloading); + QCOMPARE(statusChangedSpy[1][0].value<QPdfDocument::Status>(), QPdfDocument::Null); + QCOMPARE(doc.pageCount(), 0); + QCOMPARE(pageCountChangedSpy.count(), 1); + QCOMPARE(pageCountChangedSpy[0][0].toInt(), doc.pageCount()); +} + +void tst_QPdfDocument::loadAfterClose() +{ + TemporaryPdf tempPdf; + QPdfDocument doc; + + QSignalSpy statusChangedSpy(&doc, SIGNAL(statusChanged(QPdfDocument::Status))); + QSignalSpy pageCountChangedSpy(&doc, SIGNAL(pageCountChanged(int))); + + doc.load(&tempPdf); + QCOMPARE(statusChangedSpy.count(), 2); + QCOMPARE(statusChangedSpy[0][0].value<QPdfDocument::Status>(), QPdfDocument::Loading); + QCOMPARE(statusChangedSpy[1][0].value<QPdfDocument::Status>(), QPdfDocument::Ready); + QCOMPARE(pageCountChangedSpy.count(), 1); + QCOMPARE(pageCountChangedSpy[0][0].toInt(), doc.pageCount()); + statusChangedSpy.clear(); + pageCountChangedSpy.clear(); + + doc.close(); + QCOMPARE(statusChangedSpy.count(), 2); + QCOMPARE(statusChangedSpy[0][0].value<QPdfDocument::Status>(), QPdfDocument::Unloading); + QCOMPARE(statusChangedSpy[1][0].value<QPdfDocument::Status>(), QPdfDocument::Null); + QCOMPARE(pageCountChangedSpy.count(), 1); + QCOMPARE(pageCountChangedSpy[0][0].toInt(), doc.pageCount()); + statusChangedSpy.clear(); + pageCountChangedSpy.clear(); + + doc.load(&tempPdf); + QCOMPARE(statusChangedSpy.count(), 2); + QCOMPARE(statusChangedSpy[0][0].value<QPdfDocument::Status>(), QPdfDocument::Loading); + QCOMPARE(statusChangedSpy[1][0].value<QPdfDocument::Status>(), QPdfDocument::Ready); + QCOMPARE(doc.error(), QPdfDocument::NoError); + QCOMPARE(doc.pageCount(), 2); + QCOMPARE(pageCountChangedSpy.count(), 1); + QCOMPARE(pageCountChangedSpy[0][0].toInt(), doc.pageCount()); +} + +void tst_QPdfDocument::closeOnDestroy() +{ + TemporaryPdf tempPdf; + + // deleting an open document should automatically close it + { + QPdfDocument *doc = new QPdfDocument; + + doc->load(&tempPdf); + + QSignalSpy statusChangedSpy(doc, SIGNAL(statusChanged(QPdfDocument::Status))); + QSignalSpy pageCountChangedSpy(doc, SIGNAL(pageCountChanged(int))); + + delete doc; + + QCOMPARE(statusChangedSpy.count(), 2); + QCOMPARE(statusChangedSpy[0][0].value<QPdfDocument::Status>(), QPdfDocument::Unloading); + QCOMPARE(statusChangedSpy[1][0].value<QPdfDocument::Status>(), QPdfDocument::Null); + QCOMPARE(pageCountChangedSpy.count(), 1); + QCOMPARE(pageCountChangedSpy[0][0].toInt(), 0); + } + + // deleting a closed document should not emit any signal + { + QPdfDocument *doc = new QPdfDocument; + doc->load(&tempPdf); + doc->close(); + + QSignalSpy statusChangedSpy(doc, SIGNAL(statusChanged(QPdfDocument::Status))); + QSignalSpy pageCountChangedSpy(doc, SIGNAL(pageCountChanged(int))); + + delete doc; + + QCOMPARE(statusChangedSpy.count(), 0); + QCOMPARE(pageCountChangedSpy.count(), 0); + } +} + +void tst_QPdfDocument::status() +{ + TemporaryPdf tempPdf; + + QPdfDocument doc; + QCOMPARE(doc.status(), QPdfDocument::Null); + + QSignalSpy statusChangedSpy(&doc, SIGNAL(statusChanged(QPdfDocument::Status))); + + // open existing document + doc.load(&tempPdf); + QCOMPARE(statusChangedSpy.count(), 2); + QCOMPARE(statusChangedSpy[0][0].value<QPdfDocument::Status>(), QPdfDocument::Loading); + QCOMPARE(statusChangedSpy[1][0].value<QPdfDocument::Status>(), QPdfDocument::Ready); + statusChangedSpy.clear(); + + QCOMPARE(doc.status(), QPdfDocument::Ready); + + // close document + doc.close(); + + QCOMPARE(statusChangedSpy.count(), 2); + QCOMPARE(statusChangedSpy[0][0].value<QPdfDocument::Status>(), QPdfDocument::Unloading); + QCOMPARE(statusChangedSpy[1][0].value<QPdfDocument::Status>(), QPdfDocument::Null); + statusChangedSpy.clear(); + + QCOMPARE(doc.status(), QPdfDocument::Null); + + // try to open non-existing document + doc.load(QFINDTESTDATA("does-not-exist.pdf")); + QCOMPARE(statusChangedSpy.count(), 2); + QCOMPARE(statusChangedSpy[0][0].value<QPdfDocument::Status>(), QPdfDocument::Loading); + QCOMPARE(statusChangedSpy[1][0].value<QPdfDocument::Status>(), QPdfDocument::Error); + QCOMPARE(doc.status(), QPdfDocument::Error); + statusChangedSpy.clear(); + + // try to open non-existing document asynchronously + QNetworkAccessManager accessManager; + + const QUrl url("http://doesnotexist.qt.io"); + QScopedPointer<QNetworkReply> reply(accessManager.get(QNetworkRequest(url))); + + doc.load(reply.data()); + + QElapsedTimer stopWatch; + stopWatch.start(); + forever { + QCoreApplication::instance()->processEvents(); + if (statusChangedSpy.count() == 2) + break; + if (stopWatch.elapsed() >= 30000) + break; + } + + QCOMPARE(statusChangedSpy.count(), 2); + QCOMPARE(statusChangedSpy[0][0].value<QPdfDocument::Status>(), QPdfDocument::Loading); + QCOMPARE(statusChangedSpy[1][0].value<QPdfDocument::Status>(), QPdfDocument::Error); + statusChangedSpy.clear(); +} + +void tst_QPdfDocument::passwordClearedOnClose() +{ + TemporaryPdf tempPdf; + QPdfDocument doc; + + QSignalSpy passwordChangedSpy(&doc, SIGNAL(passwordChanged())); + + doc.setPassword(QStringLiteral("Qt")); + QCOMPARE(passwordChangedSpy.count(), 1); + QCOMPARE(doc.load(QFINDTESTDATA("pdf-sample.protected.pdf")), QPdfDocument::NoError); + passwordChangedSpy.clear(); + + doc.close(); // password is cleared on close + QCOMPARE(passwordChangedSpy.count(), 1); + passwordChangedSpy.clear(); + + doc.load(&tempPdf); + doc.close(); // signal is not emitted if password didn't change + QCOMPARE(passwordChangedSpy.count(), 0); +} + +void tst_QPdfDocument::metaData() +{ + QPdfDocument doc; + + // a closed document does not return any meta data + QCOMPARE(doc.metaData(QPdfDocument::Title).toString(), QString()); + QCOMPARE(doc.metaData(QPdfDocument::Subject).toString(), QString()); + QCOMPARE(doc.metaData(QPdfDocument::Author).toString(), QString()); + QCOMPARE(doc.metaData(QPdfDocument::Keywords).toString(), QString()); + QCOMPARE(doc.metaData(QPdfDocument::Producer).toString(), QString()); + QCOMPARE(doc.metaData(QPdfDocument::Creator).toString(), QString()); + QCOMPARE(doc.metaData(QPdfDocument::CreationDate).toDateTime(), QDateTime()); + QCOMPARE(doc.metaData(QPdfDocument::ModificationDate).toDateTime(), QDateTime()); + + QCOMPARE(doc.load(QFINDTESTDATA("pdf-sample.metadata.pdf")), QPdfDocument::NoError); + + // check for proper meta data from sample document + QCOMPARE(doc.metaData(QPdfDocument::Title).toString(), QString::fromLatin1("Qt PDF Unit Test Document")); + QCOMPARE(doc.metaData(QPdfDocument::Subject).toString(), QString::fromLatin1("A test for meta data access")); + QCOMPARE(doc.metaData(QPdfDocument::Author).toString(), QString::fromLatin1("John Doe")); + QCOMPARE(doc.metaData(QPdfDocument::Keywords).toString(), QString::fromLatin1("meta data keywords")); + QCOMPARE(doc.metaData(QPdfDocument::Producer).toString(), QString::fromLatin1("LibreOffice 5.1")); + QCOMPARE(doc.metaData(QPdfDocument::Creator).toString(), QString::fromLatin1("Writer")); + QCOMPARE(doc.metaData(QPdfDocument::CreationDate).toDateTime(), QDateTime(QDate(2016, 8, 7), QTime(7, 3, 6), Qt::UTC)); + QCOMPARE(doc.metaData(QPdfDocument::ModificationDate).toDateTime(), QDateTime(QDate(2016, 8, 8), QTime(8, 3, 6), Qt::UTC)); +} + +QTEST_MAIN(tst_QPdfDocument) + +#include "tst_qpdfdocument.moc" + diff --git a/tests/auto/pdf/qpdfpagenavigation/pdf-sample.pagenavigation.pdf b/tests/auto/pdf/qpdfpagenavigation/pdf-sample.pagenavigation.pdf Binary files differnew file mode 100644 index 000000000..c4e1aa36e --- /dev/null +++ b/tests/auto/pdf/qpdfpagenavigation/pdf-sample.pagenavigation.pdf diff --git a/tests/auto/pdf/qpdfpagenavigation/qpdfpagenavigation.pro b/tests/auto/pdf/qpdfpagenavigation/qpdfpagenavigation.pro new file mode 100644 index 000000000..8de99543f --- /dev/null +++ b/tests/auto/pdf/qpdfpagenavigation/qpdfpagenavigation.pro @@ -0,0 +1,5 @@ +CONFIG += testcase +TARGET = tst_qpdfpagenavigation +QT += pdf testlib network +macos:CONFIG -= app_bundle +SOURCES += tst_qpdfpagenavigation.cpp diff --git a/tests/auto/pdf/qpdfpagenavigation/tst_qpdfpagenavigation.cpp b/tests/auto/pdf/qpdfpagenavigation/tst_qpdfpagenavigation.cpp new file mode 100644 index 000000000..ff6a02750 --- /dev/null +++ b/tests/auto/pdf/qpdfpagenavigation/tst_qpdfpagenavigation.cpp @@ -0,0 +1,200 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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.LGPLv3 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.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 later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +#include <QtTest/QtTest> + +#include <QPdfDocument> +#include <QPdfPageNavigation> + +class tst_QPdfPageNavigation: public QObject +{ + Q_OBJECT + +private slots: + void defaultValues(); + void setEmptyDocument(); + void setEmptyDocumentAndLoad(); + void setLoadedDocument(); + void unloadDocument(); + void navigate(); +}; + +void tst_QPdfPageNavigation::defaultValues() +{ + QPdfPageNavigation pageNavigation; + + QCOMPARE(pageNavigation.document(), nullptr); + QCOMPARE(pageNavigation.currentPage(), 0); + QCOMPARE(pageNavigation.pageCount(), 0); + QCOMPARE(pageNavigation.canGoToPreviousPage(), false); + QCOMPARE(pageNavigation.canGoToNextPage(), false); +} + +void tst_QPdfPageNavigation::setEmptyDocument() +{ + QPdfDocument document; + QPdfPageNavigation pageNavigation; + + pageNavigation.setDocument(&document); + + QCOMPARE(pageNavigation.document(), &document); + QCOMPARE(pageNavigation.currentPage(), 0); + QCOMPARE(pageNavigation.pageCount(), 0); + QCOMPARE(pageNavigation.canGoToPreviousPage(), false); + QCOMPARE(pageNavigation.canGoToNextPage(), false); +} + +void tst_QPdfPageNavigation::setEmptyDocumentAndLoad() +{ + QPdfDocument document; + QPdfPageNavigation pageNavigation; + + pageNavigation.setDocument(&document); + + QSignalSpy currentPageChangedSpy(&pageNavigation, &QPdfPageNavigation::currentPageChanged); + QSignalSpy pageCountChangedSpy(&pageNavigation, &QPdfPageNavigation::pageCountChanged); + QSignalSpy canGoToPreviousPageChangedSpy(&pageNavigation, &QPdfPageNavigation::canGoToPreviousPageChanged); + QSignalSpy canGoToNextPageChangedSpy(&pageNavigation, &QPdfPageNavigation::canGoToNextPageChanged); + + QCOMPARE(document.load(QFINDTESTDATA("pdf-sample.pagenavigation.pdf")), QPdfDocument::NoError); + + QCOMPARE(currentPageChangedSpy.count(), 0); // current page stays '0' + QCOMPARE(pageCountChangedSpy.count(), 1); + QCOMPARE(pageCountChangedSpy[0][0].toInt(), 3); + QCOMPARE(canGoToPreviousPageChangedSpy.count(), 0); // still no previous page available + QCOMPARE(canGoToNextPageChangedSpy.count(), 1); + QCOMPARE(canGoToNextPageChangedSpy[0][0].toBool(), true); +} + +void tst_QPdfPageNavigation::setLoadedDocument() +{ + QPdfDocument document; + QCOMPARE(document.load(QFINDTESTDATA("pdf-sample.pagenavigation.pdf")), QPdfDocument::NoError); + + QPdfPageNavigation pageNavigation; + + QSignalSpy currentPageChangedSpy(&pageNavigation, &QPdfPageNavigation::currentPageChanged); + QSignalSpy pageCountChangedSpy(&pageNavigation, &QPdfPageNavigation::pageCountChanged); + QSignalSpy canGoToPreviousPageChangedSpy(&pageNavigation, &QPdfPageNavigation::canGoToPreviousPageChanged); + QSignalSpy canGoToNextPageChangedSpy(&pageNavigation, &QPdfPageNavigation::canGoToNextPageChanged); + + pageNavigation.setDocument(&document); + + QCOMPARE(currentPageChangedSpy.count(), 0); // current page stays '0' + QCOMPARE(pageCountChangedSpy.count(), 1); + QCOMPARE(pageCountChangedSpy[0][0].toInt(), 3); + QCOMPARE(canGoToPreviousPageChangedSpy.count(), 0); // still no previous page available + QCOMPARE(canGoToNextPageChangedSpy.count(), 1); + QCOMPARE(canGoToNextPageChangedSpy[0][0].toBool(), true); +} + +void tst_QPdfPageNavigation::unloadDocument() +{ + QPdfDocument document; + QCOMPARE(document.load(QFINDTESTDATA("pdf-sample.pagenavigation.pdf")), QPdfDocument::NoError); + + QPdfPageNavigation pageNavigation; + pageNavigation.setDocument(&document); + + QSignalSpy currentPageChangedSpy(&pageNavigation, &QPdfPageNavigation::currentPageChanged); + QSignalSpy pageCountChangedSpy(&pageNavigation, &QPdfPageNavigation::pageCountChanged); + QSignalSpy canGoToPreviousPageChangedSpy(&pageNavigation, &QPdfPageNavigation::canGoToPreviousPageChanged); + QSignalSpy canGoToNextPageChangedSpy(&pageNavigation, &QPdfPageNavigation::canGoToNextPageChanged); + + document.close(); + + QCOMPARE(currentPageChangedSpy.count(), 0); // current page stays '0' + QCOMPARE(pageCountChangedSpy.count(), 1); + QCOMPARE(pageCountChangedSpy[0][0].toInt(), 0); + QCOMPARE(canGoToPreviousPageChangedSpy.count(), 0); // still no previous page available + QCOMPARE(canGoToNextPageChangedSpy.count(), 1); + QCOMPARE(canGoToNextPageChangedSpy[0][0].toBool(), false); +} + +void tst_QPdfPageNavigation::navigate() +{ + QPdfDocument document; + QCOMPARE(document.load(QFINDTESTDATA("pdf-sample.pagenavigation.pdf")), QPdfDocument::NoError); + + QPdfPageNavigation pageNavigation; + pageNavigation.setDocument(&document); + + QSignalSpy currentPageChangedSpy(&pageNavigation, &QPdfPageNavigation::currentPageChanged); + QSignalSpy canGoToPreviousPageChangedSpy(&pageNavigation, &QPdfPageNavigation::canGoToPreviousPageChanged); + QSignalSpy canGoToNextPageChangedSpy(&pageNavigation, &QPdfPageNavigation::canGoToNextPageChanged); + + QCOMPARE(pageNavigation.currentPage(), 0); + + // try to go to previous page while there is none + QCOMPARE(pageNavigation.canGoToPreviousPage(), false); + pageNavigation.goToPreviousPage(); + QCOMPARE(canGoToPreviousPageChangedSpy.count(), 0); + QCOMPARE(pageNavigation.currentPage(), 0); + QCOMPARE(pageNavigation.canGoToPreviousPage(), false); + + // try to go to next page + QCOMPARE(pageNavigation.canGoToNextPage(), true); + pageNavigation.goToNextPage(); + QCOMPARE(canGoToPreviousPageChangedSpy.count(), 1); + QCOMPARE(canGoToNextPageChangedSpy.count(), 0); + QCOMPARE(currentPageChangedSpy.count(), 1); + QCOMPARE(pageNavigation.currentPage(), 1); + QCOMPARE(pageNavigation.canGoToPreviousPage(), true); + + currentPageChangedSpy.clear(); + canGoToPreviousPageChangedSpy.clear(); + canGoToNextPageChangedSpy.clear(); + + // try to go to last page + pageNavigation.setCurrentPage(2); + QCOMPARE(canGoToPreviousPageChangedSpy.count(), 0); + QCOMPARE(canGoToNextPageChangedSpy.count(), 1); + QCOMPARE(currentPageChangedSpy.count(), 1); + QCOMPARE(pageNavigation.currentPage(), 2); + QCOMPARE(pageNavigation.canGoToNextPage(), false); + + // check that invalid requests are ignored + pageNavigation.setCurrentPage(-1); + QCOMPARE(pageNavigation.currentPage(), 2); + + pageNavigation.setCurrentPage(3); + QCOMPARE(pageNavigation.currentPage(), 2); +} + +QTEST_MAIN(tst_QPdfPageNavigation) + +#include "tst_qpdfpagenavigation.moc" diff --git a/tests/auto/pdf/qpdfpagerenderer/pdf-sample.pagerenderer.pdf b/tests/auto/pdf/qpdfpagerenderer/pdf-sample.pagerenderer.pdf Binary files differnew file mode 100644 index 000000000..c4e1aa36e --- /dev/null +++ b/tests/auto/pdf/qpdfpagerenderer/pdf-sample.pagerenderer.pdf diff --git a/tests/auto/pdf/qpdfpagerenderer/qpdfpagerenderer.pro b/tests/auto/pdf/qpdfpagerenderer/qpdfpagerenderer.pro new file mode 100644 index 000000000..9ccb4e82c --- /dev/null +++ b/tests/auto/pdf/qpdfpagerenderer/qpdfpagerenderer.pro @@ -0,0 +1,5 @@ +CONFIG += testcase +TARGET = tst_qpdfpagerenderer +QT += pdf testlib network +macos:CONFIG -= app_bundle +SOURCES += tst_qpdfpagerenderer.cpp diff --git a/tests/auto/pdf/qpdfpagerenderer/tst_qpdfpagerenderer.cpp b/tests/auto/pdf/qpdfpagerenderer/tst_qpdfpagerenderer.cpp new file mode 100644 index 000000000..8eaef7c6e --- /dev/null +++ b/tests/auto/pdf/qpdfpagerenderer/tst_qpdfpagerenderer.cpp @@ -0,0 +1,184 @@ +/**************************************************************************** +** +** Copyright (C) 2017 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Tobias König <tobias.koenig@kdab.com> +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtPDF module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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.LGPLv3 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.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 later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QPdfDocument> +#include <QPdfPageRenderer> + +#include <QtTest/QtTest> + +class tst_QPdfPageRenderer: public QObject +{ + Q_OBJECT + +private slots: + void defaultValues(); + void withNoDocument(); + void withEmptyDocument(); + void withLoadedDocumentSingleThreaded(); + void withLoadedDocumentMultiThreaded(); + void switchingRenderMode(); +}; + +void tst_QPdfPageRenderer::defaultValues() +{ + QPdfPageRenderer pageRenderer; + + QCOMPARE(pageRenderer.document(), nullptr); + QCOMPARE(pageRenderer.renderMode(), QPdfPageRenderer::SingleThreadedRenderMode); +} + +void tst_QPdfPageRenderer::withNoDocument() +{ + QPdfPageRenderer pageRenderer; + + const QSize imageSize(100, 100); + const quint64 requestId = pageRenderer.requestPage(0, imageSize); + + QCOMPARE(requestId, quint64(0)); +} + +void tst_QPdfPageRenderer::withEmptyDocument() +{ + QPdfDocument document; + QPdfPageRenderer pageRenderer; + + pageRenderer.setDocument(&document); + + const QSize imageSize(100, 100); + const quint64 requestId = pageRenderer.requestPage(0, imageSize); + + QCOMPARE(requestId, quint64(0)); +} + +void tst_QPdfPageRenderer::withLoadedDocumentSingleThreaded() +{ + QPdfDocument document; + QPdfPageRenderer pageRenderer; + pageRenderer.setDocument(&document); + + QCOMPARE(document.load(QFINDTESTDATA("pdf-sample.pagerenderer.pdf")), QPdfDocument::NoError); + + QSignalSpy pageRenderedSpy(&pageRenderer, &QPdfPageRenderer::pageRendered); + + const QSize imageSize(100, 100); + const quint64 requestId = pageRenderer.requestPage(0, imageSize); + + QCOMPARE(requestId, quint64(1)); + QTRY_COMPARE(pageRenderedSpy.count(), 1); + QCOMPARE(pageRenderedSpy[0][0].toInt(), 0); + QCOMPARE(pageRenderedSpy[0][1].toSize(), imageSize); + QCOMPARE(pageRenderedSpy[0][2].value<QImage>().size(), imageSize); + QCOMPARE(pageRenderedSpy[0][4].toULongLong(), requestId); +} + +void tst_QPdfPageRenderer::withLoadedDocumentMultiThreaded() +{ + QPdfDocument document; + + QPdfPageRenderer pageRenderer; + pageRenderer.setDocument(&document); + pageRenderer.setRenderMode(QPdfPageRenderer::MultiThreadedRenderMode); + + QCOMPARE(document.load(QFINDTESTDATA("pdf-sample.pagerenderer.pdf")), QPdfDocument::NoError); + + QSignalSpy pageRenderedSpy(&pageRenderer, &QPdfPageRenderer::pageRendered); + + const QSize imageSize(100, 100); + const quint64 requestId = pageRenderer.requestPage(0, imageSize); + + QCOMPARE(requestId, quint64(1)); + QTRY_COMPARE(pageRenderedSpy.count(), 1); + QCOMPARE(pageRenderedSpy[0][0].toInt(), 0); + QCOMPARE(pageRenderedSpy[0][1].toSize(), imageSize); + QCOMPARE(pageRenderedSpy[0][2].value<QImage>().size(), imageSize); + QCOMPARE(pageRenderedSpy[0][4].toULongLong(), requestId); +} + +void tst_QPdfPageRenderer::switchingRenderMode() +{ + QPdfDocument document; + QPdfPageRenderer pageRenderer; + pageRenderer.setDocument(&document); + + QCOMPARE(document.load(QFINDTESTDATA("pdf-sample.pagerenderer.pdf")), QPdfDocument::NoError); + + QSignalSpy pageRenderedSpy(&pageRenderer, &QPdfPageRenderer::pageRendered); + + // render single threaded + const QSize imageSize(100, 100); + const quint64 firstRequestId = pageRenderer.requestPage(0, imageSize); + + QTRY_COMPARE(pageRenderedSpy.count(), 1); + QCOMPARE(pageRenderedSpy[0][0].toInt(), 0); + QCOMPARE(pageRenderedSpy[0][1].toSize(), imageSize); + QCOMPARE(pageRenderedSpy[0][2].value<QImage>().size(), imageSize); + QCOMPARE(pageRenderedSpy[0][4].toULongLong(), firstRequestId); + + const QImage image = pageRenderedSpy[0][2].value<QImage>(); + + pageRenderedSpy.clear(); + + // switch to multi threaded + pageRenderer.setRenderMode(QPdfPageRenderer::MultiThreadedRenderMode); + + const quint64 secondRequestId = pageRenderer.requestPage(0, imageSize); + + QVERIFY(firstRequestId != secondRequestId); + QTRY_COMPARE(pageRenderedSpy.count(), 1); + QCOMPARE(pageRenderedSpy[0][0].toInt(), 0); + QCOMPARE(pageRenderedSpy[0][1].toSize(), imageSize); + QCOMPARE(pageRenderedSpy[0][2].value<QImage>(), image); + QCOMPARE(pageRenderedSpy[0][2].value<QImage>().size(), imageSize); + QCOMPARE(pageRenderedSpy[0][4].toULongLong(), secondRequestId); + + pageRenderedSpy.clear(); + + // switch back to single threaded + pageRenderer.setRenderMode(QPdfPageRenderer::SingleThreadedRenderMode); + + const quint64 thirdRequestId = pageRenderer.requestPage(0, imageSize); + + QTRY_COMPARE(pageRenderedSpy.count(), 1); + QCOMPARE(pageRenderedSpy[0][0].toInt(), 0); + QCOMPARE(pageRenderedSpy[0][1].toSize(), imageSize); + QCOMPARE(pageRenderedSpy[0][2].value<QImage>(), image); + QCOMPARE(pageRenderedSpy[0][2].value<QImage>().size(), imageSize); + QCOMPARE(pageRenderedSpy[0][4].toULongLong(), thirdRequestId); +} + +QTEST_MAIN(tst_QPdfPageRenderer) + +#include "tst_qpdfpagerenderer.moc" diff --git a/tests/auto/pdf/qpdfsearchmodel/qpdfsearchmodel.pro b/tests/auto/pdf/qpdfsearchmodel/qpdfsearchmodel.pro new file mode 100644 index 000000000..205fef175 --- /dev/null +++ b/tests/auto/pdf/qpdfsearchmodel/qpdfsearchmodel.pro @@ -0,0 +1,5 @@ +CONFIG += testcase +TARGET = tst_qpdfsearchmodel +QT += pdf testlib network +macos:CONFIG -= app_bundle +SOURCES += tst_qpdfsearchmodel.cpp diff --git a/tests/auto/pdf/qpdfsearchmodel/test.pdf b/tests/auto/pdf/qpdfsearchmodel/test.pdf Binary files differnew file mode 100644 index 000000000..a9dc1bc29 --- /dev/null +++ b/tests/auto/pdf/qpdfsearchmodel/test.pdf diff --git a/tests/auto/pdf/qpdfsearchmodel/tst_qpdfsearchmodel.cpp b/tests/auto/pdf/qpdfsearchmodel/tst_qpdfsearchmodel.cpp new file mode 100644 index 000000000..e690bfc11 --- /dev/null +++ b/tests/auto/pdf/qpdfsearchmodel/tst_qpdfsearchmodel.cpp @@ -0,0 +1,69 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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.LGPLv3 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.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 later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +#include <QtTest/QtTest> + +#include <QPdfDocument> +#include <QPdfSearchModel> + +class tst_QPdfSearchModel: public QObject +{ + Q_OBJECT + +public: + tst_QPdfSearchModel() {} + +private slots: + void findText(); +}; + +void tst_QPdfSearchModel::findText() +{ + QPdfDocument document; + QCOMPARE(document.load(QFINDTESTDATA("test.pdf")), QPdfDocument::NoError); + + QPdfSearchModel model; + model.setDocument(&document); + QVector<QRectF> matches = model.matches(1, "ai"); + + qDebug() << matches; + QCOMPARE(matches.count(), 3); +} + +QTEST_MAIN(tst_QPdfSearchModel) + +#include "tst_qpdfsearchmodel.moc" diff --git a/tests/auto/quick/publicapi/tst_publicapi.cpp b/tests/auto/quick/publicapi/tst_publicapi.cpp index 321972057..aaca7997b 100644 --- a/tests/auto/quick/publicapi/tst_publicapi.cpp +++ b/tests/auto/quick/publicapi/tst_publicapi.cpp @@ -300,6 +300,7 @@ static const QStringList expectedAPI = QStringList() << "QQuickWebEngineFullScreenRequest.reject() --> void" << "QQuickWebEngineFullScreenRequest.toggleOn --> bool" << "QQuickWebEngineHistory.backItems --> QQuickWebEngineHistoryListModel*" + << "QQuickWebEngineHistory.clear() --> void" << "QQuickWebEngineHistory.forwardItems --> QQuickWebEngineHistoryListModel*" << "QQuickWebEngineHistory.items --> QQuickWebEngineHistoryListModel*" << "QQuickWebEngineJavaScriptDialogRequest.DialogTypeAlert --> DialogType" @@ -740,6 +741,8 @@ static const QStringList expectedAPI = QStringList() << "QQuickWebEngineView.quotaRequested(QWebEngineQuotaRequest) --> void" << "QQuickWebEngineView.recentlyAudible --> bool" << "QQuickWebEngineView.recentlyAudibleChanged(bool) --> void" + << "QQuickWebEngineView.renderProcessPid --> qlonglong" + << "QQuickWebEngineView.renderProcessPidChanged(qlonglong) --> void" << "QQuickWebEngineView.recommendedState --> LifecycleState" << "QQuickWebEngineView.recommendedStateChanged(LifecycleState) --> void" << "QQuickWebEngineView.registerProtocolHandlerRequested(QWebEngineRegisterProtocolHandlerRequest) --> void" diff --git a/tests/auto/quick/qmltests/data/tst_navigationHistory.qml b/tests/auto/quick/qmltests/data/tst_navigationHistory.qml index 77664e645..6ed232589 100644 --- a/tests/auto/quick/qmltests/data/tst_navigationHistory.qml +++ b/tests/auto/quick/qmltests/data/tst_navigationHistory.qml @@ -134,6 +134,13 @@ TestWebEngineView { compare(forwardItemsList.count, 1) compare(backItemsList.currentItem.text, Qt.resolvedUrl("test1.html")) compare(forwardItemsList.currentItem.text, Qt.resolvedUrl("javascript.html")) + + webEngineView.navigationHistory.clear() + compare(webEngineView.url, Qt.resolvedUrl("test2.html")) + compare(webEngineView.canGoBack, false) + compare(webEngineView.canGoForward, false) + compare(backItemsList.count, 0) + compare(forwardItemsList.count, 0) } } } diff --git a/tests/auto/quick/qquickwebengineview/tst_qquickwebengineview.cpp b/tests/auto/quick/qquickwebengineview/tst_qquickwebengineview.cpp index 5572515a1..5a7879993 100644 --- a/tests/auto/quick/qquickwebengineview/tst_qquickwebengineview.cpp +++ b/tests/auto/quick/qquickwebengineview/tst_qquickwebengineview.cpp @@ -582,7 +582,7 @@ void tst_QQuickWebEngineView::interruptImeTextComposition() QFETCH(QString, eventType); if (eventType == "MouseButton") { QPoint textInputCenter = elementCenter(view, QStringLiteral("input2")); - QTest::mouseClick(view->window(), Qt::LeftButton, 0, textInputCenter); + QTest::mouseClick(view->window(), Qt::LeftButton, {}, textInputCenter); } else if (eventType == "Touch") { QPoint textInputCenter = elementCenter(view, QStringLiteral("input2")); QTouchDevice *touchDevice = QTest::createTouchDevice(); @@ -619,7 +619,7 @@ void tst_QQuickWebEngineView::inputContextQueryInput() // Set focus on an input field. QPoint textInputCenter = elementCenter(view, "input1"); - QTest::mouseClick(view->window(), Qt::LeftButton, 0, textInputCenter); + QTest::mouseClick(view->window(), Qt::LeftButton, {}, textInputCenter); QTRY_COMPARE(testContext.infos.count(), 2); QCOMPARE(evaluateJavaScriptSync(view, "document.activeElement.id").toString(), QStringLiteral("input1")); foreach (const InputMethodInfo &info, testContext.infos) { diff --git a/tests/auto/widgets/origins/BLACKLIST b/tests/auto/widgets/origins/BLACKLIST new file mode 100644 index 000000000..db858f11e --- /dev/null +++ b/tests/auto/widgets/origins/BLACKLIST @@ -0,0 +1,3 @@ +# QTBUG-81556 +[mixedXHR] +* diff --git a/tests/auto/widgets/origins/tst_origins.cpp b/tests/auto/widgets/origins/tst_origins.cpp index c63f4d690..e3927f763 100644 --- a/tests/auto/widgets/origins/tst_origins.cpp +++ b/tests/auto/widgets/origins/tst_origins.cpp @@ -481,6 +481,8 @@ void tst_Origins::subdirWithoutAccess() { ScopedAttribute sa(m_page->settings(), QWebEngineSettings::LocalContentCanAccessFileUrls, false); + QTest::ignoreMessage(QtSystemMsg, QRegularExpression(QSL("Uncaught SecurityError"))); + QTest::ignoreMessage(QtSystemMsg, QRegularExpression(QSL("Uncaught SecurityError"))); QVERIFY(verifyLoad(QSL("file:" THIS_DIR "resources/subdir/index.html"))); QCOMPARE(eval(QSL("msg[0]")), QVariant()); QCOMPARE(eval(QSL("msg[1]")), QVariant()); @@ -507,22 +509,28 @@ void tst_Origins::mixedSchemes() QVERIFY(verifyLoad(QSL("file:" THIS_DIR "resources/mixedSchemes.html"))); eval(QSL("setIFrameUrl('file:" THIS_DIR "resources/mixedSchemes_frame.html')")); QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadAndAccess"))); + QTest::ignoreMessage(QtSystemMsg, QRegularExpression(QSL("Uncaught SecurityError"))); eval(QSL("setIFrameUrl('qrc:/resources/mixedSchemes_frame.html')")); QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadButNotAccess"))); + QTest::ignoreMessage(QtSystemMsg, QRegularExpression(QSL("Uncaught SecurityError"))); eval(QSL("setIFrameUrl('tst:/resources/mixedSchemes_frame.html')")); QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadButNotAccess"))); QVERIFY(verifyLoad(QSL("qrc:/resources/mixedSchemes.html"))); + QTest::ignoreMessage(QtSystemMsg, QRegularExpression(QSL("Uncaught SecurityError"))); eval(QSL("setIFrameUrl('file:" THIS_DIR "resources/mixedSchemes_frame.html')")); QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadButNotAccess"))); eval(QSL("setIFrameUrl('qrc:/resources/mixedSchemes_frame.html')")); QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadAndAccess"))); + QTest::ignoreMessage(QtSystemMsg, QRegularExpression(QSL("Uncaught SecurityError"))); eval(QSL("setIFrameUrl('tst:/resources/mixedSchemes_frame.html')")); QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadButNotAccess"))); QVERIFY(verifyLoad(QSL("tst:/resources/mixedSchemes.html"))); + QTest::ignoreMessage(QtSystemMsg, QRegularExpression(QSL("Not allowed to load local resource"))); eval(QSL("setIFrameUrl('file:" THIS_DIR "resources/mixedSchemes_frame.html')")); QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("cannotLoad"))); + QTest::ignoreMessage(QtSystemMsg, QRegularExpression(QSL("Uncaught SecurityError"))); eval(QSL("setIFrameUrl('qrc:/resources/mixedSchemes_frame.html')")); QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadButNotAccess"))); eval(QSL("setIFrameUrl('tst:/resources/mixedSchemes_frame.html')")); @@ -531,36 +539,47 @@ void tst_Origins::mixedSchemes() QVERIFY(verifyLoad(QSL("PathSyntax:/resources/mixedSchemes.html"))); eval(QSL("setIFrameUrl('PathSyntax:/resources/mixedSchemes_frame.html')")); QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadAndAccess"))); + QTest::ignoreMessage(QtSystemMsg, QRegularExpression(QSL("Not allowed to load local resource"))); eval(QSL("setIFrameUrl('PathSyntax-Local:/resources/mixedSchemes_frame.html')")); QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("cannotLoad"))); eval(QSL("setIFrameUrl('PathSyntax-LocalAccessAllowed:/resources/mixedSchemes_frame.html')")); + QTest::ignoreMessage(QtSystemMsg, QRegularExpression(QSL("Uncaught SecurityError"))); QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadButNotAccess"))); + QTest::ignoreMessage(QtSystemMsg, QRegularExpression(QSL("Uncaught SecurityError"))); eval(QSL("setIFrameUrl('PathSyntax-NoAccessAllowed:/resources/mixedSchemes_frame.html')")); QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadButNotAccess"))); QVERIFY(verifyLoad(QSL("PathSyntax-LocalAccessAllowed:/resources/mixedSchemes.html"))); + QTest::ignoreMessage(QtSystemMsg, QRegularExpression(QSL("Uncaught SecurityError"))); eval(QSL("setIFrameUrl('PathSyntax:/resources/mixedSchemes_frame.html')")); QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadButNotAccess"))); + QTest::ignoreMessage(QtSystemMsg, QRegularExpression(QSL("Uncaught SecurityError"))); eval(QSL("setIFrameUrl('PathSyntax-Local:/resources/mixedSchemes_frame.html')")); QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadButNotAccess"))); eval(QSL("setIFrameUrl('PathSyntax-LocalAccessAllowed:/resources/mixedSchemes_frame.html')")); QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadAndAccess"))); + QTest::ignoreMessage(QtSystemMsg, QRegularExpression(QSL("Uncaught SecurityError"))); eval(QSL("setIFrameUrl('PathSyntax-NoAccessAllowed:/resources/mixedSchemes_frame.html')")); QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadButNotAccess"))); QVERIFY(verifyLoad(QSL("PathSyntax-NoAccessAllowed:/resources/mixedSchemes.html"))); + QTest::ignoreMessage(QtSystemMsg, QRegularExpression(QSL("Uncaught SecurityError"))); eval(QSL("setIFrameUrl('PathSyntax:/resources/mixedSchemes_frame.html')")); QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadButNotAccess"))); + QTest::ignoreMessage(QtSystemMsg, QRegularExpression(QSL("Not allowed to load local resource"))); eval(QSL("setIFrameUrl('PathSyntax-Local:/resources/mixedSchemes_frame.html')")); QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("cannotLoad"))); + QTest::ignoreMessage(QtSystemMsg, QRegularExpression(QSL("Uncaught SecurityError"))); eval(QSL("setIFrameUrl('PathSyntax-LocalAccessAllowed:/resources/mixedSchemes_frame.html')")); QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadButNotAccess"))); + QTest::ignoreMessage(QtSystemMsg, QRegularExpression(QSL("Uncaught SecurityError"))); eval(QSL("setIFrameUrl('PathSyntax-NoAccessAllowed:/resources/mixedSchemes_frame.html')")); QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadButNotAccess"))); QVERIFY(verifyLoad(QSL("HostSyntax://a/resources/mixedSchemes.html"))); eval(QSL("setIFrameUrl('HostSyntax://a/resources/mixedSchemes_frame.html')")); QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadAndAccess"))); + QTest::ignoreMessage(QtSystemMsg, QRegularExpression(QSL("Uncaught SecurityError"))); eval(QSL("setIFrameUrl('HostSyntax://b/resources/mixedSchemes_frame.html')")); QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadButNotAccess"))); } @@ -569,14 +588,17 @@ void tst_Origins::mixedSchemes() void tst_Origins::mixedSchemesWithCsp() { QVERIFY(verifyLoad(QSL("HostSyntax://a/resources/mixedSchemesWithCsp.html"))); + QTest::ignoreMessage(QtSystemMsg, QRegularExpression(QSL("violates the following Content Security Policy"))); eval(QSL("setIFrameUrl('HostSyntax://a/resources/mixedSchemes_frame.html')")); QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadButNotAccess"))); + QTest::ignoreMessage(QtSystemMsg, QRegularExpression(QSL("violates the following Content Security Policy"))); eval(QSL("setIFrameUrl('HostSyntax://b/resources/mixedSchemes_frame.html')")); QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadButNotAccess"))); QVERIFY(verifyLoad(QSL("HostSyntax-ContentSecurityPolicyIgnored://a/resources/mixedSchemesWithCsp.html"))); eval(QSL("setIFrameUrl('HostSyntax-ContentSecurityPolicyIgnored://a/resources/mixedSchemes_frame.html')")); QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadAndAccess"))); + QTest::ignoreMessage(QtSystemMsg, QRegularExpression(QSL("Uncaught SecurityError"))); eval(QSL("setIFrameUrl('HostSyntax-ContentSecurityPolicyIgnored://b/resources/mixedSchemes_frame.html')")); QTRY_COMPARE(eval(QSL("result")), QVariant(QSL("canLoadButNotAccess"))); } diff --git a/tests/auto/widgets/proxypac/BLACKLIST b/tests/auto/widgets/proxypac/BLACKLIST new file mode 100644 index 000000000..42e9f8934 --- /dev/null +++ b/tests/auto/widgets/proxypac/BLACKLIST @@ -0,0 +1,3 @@ +# QTBUG-81557 +[proxypac] +* diff --git a/tests/auto/widgets/qwebenginepage/BLACKLIST b/tests/auto/widgets/qwebenginepage/BLACKLIST index 2498ed765..086b39fb1 100644 --- a/tests/auto/widgets/qwebenginepage/BLACKLIST +++ b/tests/auto/widgets/qwebenginepage/BLACKLIST @@ -4,3 +4,7 @@ osx [mouseMovementProperties] windows macos # Can't move cursor (QTBUG-76312) + +# QTBUG-81614 +[setHtmlWithBaseURL] +* diff --git a/tests/auto/widgets/qwebenginepage/tst_qwebenginepage.cpp b/tests/auto/widgets/qwebenginepage/tst_qwebenginepage.cpp index 07fb2eab9..9f4e422f8 100644 --- a/tests/auto/widgets/qwebenginepage/tst_qwebenginepage.cpp +++ b/tests/auto/widgets/qwebenginepage/tst_qwebenginepage.cpp @@ -226,6 +226,7 @@ private Q_SLOTS: void customUserAgentInNewTab(); void renderProcessCrashed(); + void renderProcessPid(); private: static QPoint elementCenter(QWebEnginePage *page, const QString &id); @@ -970,7 +971,7 @@ void tst_QWebEnginePage::findText() { CallbackSpy<bool> callbackSpy; QSignalSpy signalSpy(m_view->page(), &QWebEnginePage::findTextFinished); - m_view->findText("", 0, callbackSpy.ref()); + m_view->findText("", {}, callbackSpy.ref()); QVERIFY(callbackSpy.wasCalled()); QCOMPARE(signalSpy.count(), 1); QTRY_COMPARE(m_view->selectedText(), QString("foo bar")); @@ -981,7 +982,7 @@ void tst_QWebEnginePage::findText() { CallbackSpy<bool> callbackSpy; QSignalSpy signalSpy(m_view->page(), &QWebEnginePage::findTextFinished); - m_view->findText("Will not be found", 0, callbackSpy.ref()); + m_view->findText("Will not be found", {}, callbackSpy.ref()); QCOMPARE(callbackSpy.waitForResult(), false); QTRY_COMPARE(signalSpy.count(), 1); auto result = signalSpy.takeFirst().value(0).value<QWebEngineFindTextResult>(); @@ -998,7 +999,7 @@ void tst_QWebEnginePage::findText() { CallbackSpy<bool> callbackSpy; QSignalSpy signalSpy(m_view->page(), &QWebEnginePage::findTextFinished); - m_view->findText("foo", 0, callbackSpy.ref()); + m_view->findText("foo", {}, callbackSpy.ref()); QVERIFY(callbackSpy.waitForResult()); QTRY_COMPARE(signalSpy.count(), 1); QTRY_VERIFY(m_view->selectedText().isEmpty()); @@ -1009,7 +1010,7 @@ void tst_QWebEnginePage::findText() { CallbackSpy<bool> callbackSpy; QSignalSpy signalSpy(m_view->page(), &QWebEnginePage::findTextFinished); - m_view->findText("", 0, callbackSpy.ref()); + m_view->findText("", {}, callbackSpy.ref()); QTRY_VERIFY(callbackSpy.wasCalled()); QTRY_COMPARE(signalSpy.count(), 1); QTRY_COMPARE(m_view->selectedText(), QString("foo")); @@ -1019,8 +1020,8 @@ void tst_QWebEnginePage::findText() // should interrupt the first one. { QSignalSpy signalSpy(m_view->page(), &QWebEnginePage::findTextFinished); - m_view->findText("foo", 0); - m_view->findText("foo", 0); + m_view->findText("foo", {}); + m_view->findText("foo", {}); QTRY_COMPARE(signalSpy.count(), 2); QTRY_VERIFY(m_view->selectedText().isEmpty()); @@ -1075,11 +1076,11 @@ void tst_QWebEnginePage::findTextSuccessiveShouldCallAllCallbacks() QSignalSpy loadSpy(m_view, SIGNAL(loadFinished(bool))); m_view->setHtml(QString("<html><head></head><body><div>abcdefg abcdefg abcdefg abcdefg abcdefg</div></body></html>")); QTRY_COMPARE_WITH_TIMEOUT(loadSpy.count(), 1, 20000); - m_page->findText("abcde", 0, spy1.ref()); - m_page->findText("abcd", 0, spy2.ref()); - m_page->findText("abc", 0, spy3.ref()); - m_page->findText("ab", 0, spy4.ref()); - m_page->findText("a", 0, spy5.ref()); + m_page->findText("abcde", {}, spy1.ref()); + m_page->findText("abcd", {}, spy2.ref()); + m_page->findText("abc", {}, spy3.ref()); + m_page->findText("ab", {}, spy4.ref()); + m_page->findText("a", {}, spy5.ref()); spy5.waitForResult(); QVERIFY(spy1.wasCalled()); QVERIFY(spy2.wasCalled()); @@ -1100,10 +1101,10 @@ void tst_QWebEnginePage::findTextCalledOnMatch() // CALLBACK bool callbackCalled = false; - m_view->page()->findText("foo", 0, [this, &callbackCalled](bool found) { + m_view->page()->findText("foo", {}, [this, &callbackCalled](bool found) { QVERIFY(found); - m_view->page()->findText("bar", 0, [&callbackCalled](bool found) { + m_view->page()->findText("bar", {}, [&callbackCalled](bool found) { QVERIFY(found); callbackCalled = true; }); @@ -1137,7 +1138,7 @@ void tst_QWebEnginePage::findTextActiveMatchOrdinal() // Iterate over all "foo" matches. for (int i = 1; i <= 3; ++i) { - m_view->page()->findText("foo", 0); + m_view->page()->findText("foo", {}); QTRY_COMPARE(findTextSpy.count(), 1); result = findTextSpy.takeFirst().value(0).value<QWebEngineFindTextResult>(); QCOMPARE(result.numberOfMatches(), 3); @@ -1145,7 +1146,7 @@ void tst_QWebEnginePage::findTextActiveMatchOrdinal() } // The last match is followed by the fist one. - m_view->page()->findText("foo", 0); + m_view->page()->findText("foo", {}); QTRY_COMPARE(findTextSpy.count(), 1); result = findTextSpy.takeFirst().value(0).value<QWebEngineFindTextResult>(); QCOMPARE(result.numberOfMatches(), 3); @@ -1159,14 +1160,14 @@ void tst_QWebEnginePage::findTextActiveMatchOrdinal() QCOMPARE(result.activeMatch(), 3); // Finding another word resets the activeMatch. - m_view->page()->findText("bar", 0); + m_view->page()->findText("bar", {}); QTRY_COMPARE(findTextSpy.count(), 1); result = findTextSpy.takeFirst().value(0).value<QWebEngineFindTextResult>(); QCOMPARE(result.numberOfMatches(), 2); QCOMPARE(result.activeMatch(), 1); // If no match activeMatch is 0. - m_view->page()->findText("bla", 0); + m_view->page()->findText("bla", {}); QTRY_COMPARE(findTextSpy.count(), 1); result = findTextSpy.takeFirst().value(0).value<QWebEngineFindTextResult>(); QCOMPARE(result.numberOfMatches(), 0); @@ -2014,7 +2015,7 @@ private Q_SLOTS: void continueError() { - emit error(this->error()); + emit error(this->networkError()); emit finished(); } }; @@ -3280,7 +3281,7 @@ void tst_QWebEnginePage::dataURLFragment() QTRY_COMPARE(loadFinishedSpy.count(), 1); QSignalSpy urlChangedSpy(m_page, SIGNAL(urlChanged(QUrl))); - QTest::mouseClick(m_view->focusProxy(), Qt::LeftButton, 0, elementCenter(m_page, "link")); + QTest::mouseClick(m_view->focusProxy(), Qt::LeftButton, {}, elementCenter(m_page, "link")); QVERIFY(urlChangedSpy.wait()); QCOMPARE(m_page->url().fragment(), QStringLiteral("anchor")); @@ -3290,7 +3291,7 @@ void tst_QWebEnginePage::dataURLFragment() "</body></html>", QUrl("http://test.qt.io/mytest.html")); QTRY_COMPARE(loadFinishedSpy.count(), 2); - QTest::mouseClick(m_view->focusProxy(), Qt::LeftButton, 0, elementCenter(m_page, "link")); + QTest::mouseClick(m_view->focusProxy(), Qt::LeftButton, {}, elementCenter(m_page, "link")); QVERIFY(urlChangedSpy.wait()); QCOMPARE(m_page->url(), QUrl("http://test.qt.io/mytest.html#anchor")); } @@ -3360,7 +3361,7 @@ void tst_QWebEnginePage::openLinkInDifferentProfile() QTRY_COMPARE(spy1.count(), 1); QVERIFY(spy1.takeFirst().value(0).toBool()); page1.targetPage = &page2; - QTest::mouseClick(view.focusProxy(), Qt::MiddleButton, 0, elementCenter(&page1, "link")); + QTest::mouseClick(view.focusProxy(), Qt::MiddleButton, {}, elementCenter(&page1, "link")); QTRY_COMPARE(spy2.count(), 1); QVERIFY(spy2.takeFirst().value(0).toBool()); } @@ -4317,7 +4318,7 @@ void tst_QWebEnginePage::customUserAgentInNewTab() QTRY_COMPARE(spy.count(), 1); QVERIFY(spy.takeFirst().value(0).toBool()); QCOMPARE(evaluateJavaScriptSync(&page, QStringLiteral("navigator.userAgent")).toString(), profile1.httpUserAgent()); - QTest::mouseClick(view.focusProxy(), Qt::LeftButton, 0, elementCenter(&page, "link")); + QTest::mouseClick(view.focusProxy(), Qt::LeftButton, {}, elementCenter(&page, "link")); QTRY_VERIFY(page.newPage); QTRY_VERIFY(!lastUserAgent.isEmpty()); QCOMPARE(lastUserAgent, profile1.httpUserAgent().toUtf8()); @@ -4332,7 +4333,7 @@ void tst_QWebEnginePage::customUserAgentInNewTab() QString("'>link</a></body></html>")); QTRY_COMPARE(spy.count(), 1); QVERIFY(spy.takeFirst().value(0).toBool()); - QTest::mouseClick(view.focusProxy(), Qt::LeftButton, 0, elementCenter(&page, "link")); + QTest::mouseClick(view.focusProxy(), Qt::LeftButton, {}, elementCenter(&page, "link")); QTRY_VERIFY(page.newPage); QTRY_VERIFY(!lastUserAgent.isEmpty()); QCOMPARE(lastUserAgent, profile2.httpUserAgent().toUtf8()); @@ -4358,6 +4359,24 @@ void tst_QWebEnginePage::renderProcessCrashed() status == QWebEnginePage::AbnormalTerminationStatus); } +void tst_QWebEnginePage::renderProcessPid() +{ + QCOMPARE(m_page->renderProcessPid(), 0); + + m_page->load(QUrl("about:blank")); + QSignalSpy spyFinished(m_page, &QWebEnginePage::loadFinished); + QVERIFY(spyFinished.wait()); + + QVERIFY(m_page->renderProcessPid() > 1); + + bool crashed = false; + connect(m_page, &QWebEnginePage::renderProcessTerminated, [&]() { crashed = true; }); + m_page->load(QUrl("chrome://crash")); + QTRY_VERIFY_WITH_TIMEOUT(crashed, 20000); + + QCOMPARE(m_page->renderProcessPid(), 0); +} + static QByteArrayList params = {QByteArrayLiteral("--use-fake-device-for-media-stream")}; W_QTEST_MAIN(tst_QWebEnginePage, params) diff --git a/tests/auto/widgets/qwebengineprofile/tst_qwebengineprofile.cpp b/tests/auto/widgets/qwebengineprofile/tst_qwebengineprofile.cpp index a7a5ba62a..eed9c071a 100644 --- a/tests/auto/widgets/qwebengineprofile/tst_qwebengineprofile.cpp +++ b/tests/auto/widgets/qwebengineprofile/tst_qwebengineprofile.cpp @@ -153,17 +153,17 @@ void tst_QWebEngineProfile::testProfile() void tst_QWebEngineProfile::clearDataFromCache() { - QWebEnginePage page; - QDir cacheDir("./tst_QWebEngineProfile_cacheDir"); cacheDir.makeAbsolute(); if (cacheDir.exists()) cacheDir.removeRecursively(); cacheDir.mkpath(cacheDir.path()); - QWebEngineProfile *profile = page.profile(); - profile->setCachePath(cacheDir.path()); - profile->setHttpCacheType(QWebEngineProfile::DiskHttpCache); + QWebEngineProfile profile(QStringLiteral("Test")); + profile.setCachePath(cacheDir.path()); + profile.setHttpCacheType(QWebEngineProfile::DiskHttpCache); + + QWebEnginePage page(&profile, nullptr); QSignalSpy loadFinishedSpy(&page, SIGNAL(loadFinished(bool))); page.load(QUrl("http://qt-project.org")); @@ -180,7 +180,7 @@ void tst_QWebEngineProfile::clearDataFromCache() QSignalSpy directoryChangedSpy(&fileSystemWatcher, SIGNAL(directoryChanged(const QString &))); // It deletes most of the files, but not all of them. - profile->clearHttpCache(); + profile.clearHttpCache(); QTest::qWait(1000); QTRY_VERIFY(directoryChangedSpy.count() > 0); @@ -815,26 +815,32 @@ void tst_QWebEngineProfile::initiator() InitiatorSpy handler; QWebEngineProfile profile; profile.installUrlSchemeHandler("foo", &handler); - QWebEnginePage page(&profile); + QWebEnginePage page(&profile, nullptr); QSignalSpy loadFinishedSpy(&page, SIGNAL(loadFinished(bool))); + page.load(QUrl("about:blank")); + QTRY_COMPARE(loadFinishedSpy.count(), 1); + loadFinishedSpy.clear(); // about:blank has a unique origin, so initiator should be QUrl("null") evaluateJavaScriptSync(&page, "window.location = 'foo:bar'"); - QVERIFY(loadFinishedSpy.wait()); + QTRY_COMPARE(loadFinishedSpy.count(), 1); + loadFinishedSpy.clear(); QCOMPARE(handler.initiator, QUrl("null")); page.setHtml("", QUrl("http://test:123/foo%20bar")); - QVERIFY(loadFinishedSpy.wait()); + QTRY_COMPARE(loadFinishedSpy.count(), 1); + loadFinishedSpy.clear(); // baseUrl determines the origin, so QUrl("http://test:123") evaluateJavaScriptSync(&page, "window.location = 'foo:bar'"); - QVERIFY(loadFinishedSpy.wait()); + QTRY_COMPARE(loadFinishedSpy.count(), 1); + loadFinishedSpy.clear(); 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()); + QTRY_COMPARE(loadFinishedSpy.count(), 1); QCOMPARE(handler.initiator, QUrl()); } diff --git a/tests/auto/widgets/qwebengineview/tst_qwebengineview.cpp b/tests/auto/widgets/qwebengineview/tst_qwebengineview.cpp index b5f038bba..9c9a32917 100644 --- a/tests/auto/widgets/qwebengineview/tst_qwebengineview.cpp +++ b/tests/auto/widgets/qwebengineview/tst_qwebengineview.cpp @@ -537,7 +537,7 @@ void tst_QWebEngineView::focusInputTypes() // 'text' field QPoint textInputCenter = elementCenter(webView.page(), "textInput"); - QTest::mouseClick(webView.focusProxy(), Qt::LeftButton, 0, textInputCenter); + QTest::mouseClick(webView.focusProxy(), Qt::LeftButton, {}, textInputCenter); QTRY_COMPARE(evaluateJavaScriptSync(webView.page(), "document.activeElement.id").toString(), QStringLiteral("textInput")); VERIFY_INPUTMETHOD_HINTS(webView.focusProxy()->inputMethodHints(), Qt::ImhPreferLowercase); QVERIFY(webView.focusProxy()->testAttribute(Qt::WA_InputMethodEnabled)); @@ -545,7 +545,7 @@ void tst_QWebEngineView::focusInputTypes() // 'password' field QPoint passwordInputCenter = elementCenter(webView.page(), "passwordInput"); - QTest::mouseClick(webView.focusProxy(), Qt::LeftButton, 0, passwordInputCenter); + QTest::mouseClick(webView.focusProxy(), Qt::LeftButton, {}, passwordInputCenter); QTRY_COMPARE(evaluateJavaScriptSync(webView.page(), "document.activeElement.id").toString(), QStringLiteral("passwordInput")); VERIFY_INPUTMETHOD_HINTS(webView.focusProxy()->inputMethodHints(), (Qt::ImhSensitiveData | Qt::ImhNoPredictiveText | Qt::ImhNoAutoUppercase | Qt::ImhHiddenText)); QVERIFY(!webView.focusProxy()->testAttribute(Qt::WA_InputMethodEnabled)); @@ -553,7 +553,7 @@ void tst_QWebEngineView::focusInputTypes() // 'tel' field QPoint telInputCenter = elementCenter(webView.page(), "telInput"); - QTest::mouseClick(webView.focusProxy(), Qt::LeftButton, 0, telInputCenter); + QTest::mouseClick(webView.focusProxy(), Qt::LeftButton, {}, telInputCenter); QTRY_COMPARE(evaluateJavaScriptSync(webView.page(), "document.activeElement.id").toString(), QStringLiteral("telInput")); VERIFY_INPUTMETHOD_HINTS(webView.focusProxy()->inputMethodHints(), Qt::ImhDialableCharactersOnly); QVERIFY(webView.focusProxy()->testAttribute(Qt::WA_InputMethodEnabled)); @@ -561,7 +561,7 @@ void tst_QWebEngineView::focusInputTypes() // 'number' field QPoint numberInputCenter = elementCenter(webView.page(), "numberInput"); - QTest::mouseClick(webView.focusProxy(), Qt::LeftButton, 0, numberInputCenter); + QTest::mouseClick(webView.focusProxy(), Qt::LeftButton, {}, numberInputCenter); QTRY_COMPARE(evaluateJavaScriptSync(webView.page(), "document.activeElement.id").toString(), QStringLiteral("numberInput")); VERIFY_INPUTMETHOD_HINTS(webView.focusProxy()->inputMethodHints(), Qt::ImhFormattedNumbersOnly); QVERIFY(webView.focusProxy()->testAttribute(Qt::WA_InputMethodEnabled)); @@ -569,7 +569,7 @@ void tst_QWebEngineView::focusInputTypes() // 'email' field QPoint emailInputCenter = elementCenter(webView.page(), "emailInput"); - QTest::mouseClick(webView.focusProxy(), Qt::LeftButton, 0, emailInputCenter); + QTest::mouseClick(webView.focusProxy(), Qt::LeftButton, {}, emailInputCenter); QTRY_COMPARE(evaluateJavaScriptSync(webView.page(), "document.activeElement.id").toString(), QStringLiteral("emailInput")); VERIFY_INPUTMETHOD_HINTS(webView.focusProxy()->inputMethodHints(), Qt::ImhEmailCharactersOnly); QVERIFY(webView.focusProxy()->testAttribute(Qt::WA_InputMethodEnabled)); @@ -577,28 +577,28 @@ void tst_QWebEngineView::focusInputTypes() // 'url' field QPoint urlInputCenter = elementCenter(webView.page(), "urlInput"); - QTest::mouseClick(webView.focusProxy(), Qt::LeftButton, 0, urlInputCenter); + QTest::mouseClick(webView.focusProxy(), Qt::LeftButton, {}, urlInputCenter); QTRY_COMPARE(evaluateJavaScriptSync(webView.page(), "document.activeElement.id").toString(), QStringLiteral("urlInput")); VERIFY_INPUTMETHOD_HINTS(webView.focusProxy()->inputMethodHints(), (Qt::ImhUrlCharactersOnly | Qt::ImhNoPredictiveText | Qt::ImhNoAutoUppercase)); QVERIFY(webView.focusProxy()->testAttribute(Qt::WA_InputMethodEnabled)); QTRY_VERIFY(inputMethodQuery(Qt::ImEnabled).toBool()); // 'password' field - QTest::mouseClick(webView.focusProxy(), Qt::LeftButton, 0, passwordInputCenter); + QTest::mouseClick(webView.focusProxy(), Qt::LeftButton, {}, passwordInputCenter); QTRY_COMPARE(evaluateJavaScriptSync(webView.page(), "document.activeElement.id").toString(), QStringLiteral("passwordInput")); VERIFY_INPUTMETHOD_HINTS(webView.focusProxy()->inputMethodHints(), (Qt::ImhSensitiveData | Qt::ImhNoPredictiveText | Qt::ImhNoAutoUppercase | Qt::ImhHiddenText)); QVERIFY(!webView.focusProxy()->testAttribute(Qt::WA_InputMethodEnabled)); QTRY_COMPARE(inputMethodQuery(Qt::ImEnabled).toBool(), imeHasHiddenTextCapability); // 'text' type - QTest::mouseClick(webView.focusProxy(), Qt::LeftButton, 0, textInputCenter); + QTest::mouseClick(webView.focusProxy(), Qt::LeftButton, {}, textInputCenter); QTRY_COMPARE(evaluateJavaScriptSync(webView.page(), "document.activeElement.id").toString(), QStringLiteral("textInput")); VERIFY_INPUTMETHOD_HINTS(webView.focusProxy()->inputMethodHints(), Qt::ImhPreferLowercase); QVERIFY(webView.focusProxy()->testAttribute(Qt::WA_InputMethodEnabled)); QTRY_VERIFY(inputMethodQuery(Qt::ImEnabled).toBool()); // 'password' field - QTest::mouseClick(webView.focusProxy(), Qt::LeftButton, 0, passwordInputCenter); + QTest::mouseClick(webView.focusProxy(), Qt::LeftButton, {}, passwordInputCenter); QTRY_COMPARE(evaluateJavaScriptSync(webView.page(), "document.activeElement.id").toString(), QStringLiteral("passwordInput")); VERIFY_INPUTMETHOD_HINTS(webView.focusProxy()->inputMethodHints(), (Qt::ImhSensitiveData | Qt::ImhNoPredictiveText | Qt::ImhNoAutoUppercase | Qt::ImhHiddenText)); QVERIFY(!webView.focusProxy()->testAttribute(Qt::WA_InputMethodEnabled)); @@ -606,7 +606,7 @@ void tst_QWebEngineView::focusInputTypes() // 'text area' field QPoint textAreaCenter = elementCenter(webView.page(), "textArea"); - QTest::mouseClick(webView.focusProxy(), Qt::LeftButton, 0, textAreaCenter); + QTest::mouseClick(webView.focusProxy(), Qt::LeftButton, {}, textAreaCenter); QTRY_COMPARE(evaluateJavaScriptSync(webView.page(), "document.activeElement.id").toString(), QStringLiteral("textArea")); VERIFY_INPUTMETHOD_HINTS(webView.focusProxy()->inputMethodHints(), (Qt::ImhMultiLine | Qt::ImhPreferLowercase)); QVERIFY(webView.focusProxy()->testAttribute(Qt::WA_InputMethodEnabled)); @@ -695,12 +695,12 @@ void tst_QWebEngineView::horizontalScrollbarTest() QSignalSpy scrollSpy(view.page(), SIGNAL(scrollPositionChanged(QPointF))); // Note: The test below assumes that the layout direction is Qt::LeftToRight. - QTest::mouseClick(view.focusProxy(), Qt::LeftButton, 0, QPoint(550, 595)); + QTest::mouseClick(view.focusProxy(), Qt::LeftButton, {}, QPoint(550, 595)); scrollSpy.wait(); QVERIFY(view.page()->scrollPosition().x() > 0); // Note: The test below assumes that the layout direction is Qt::LeftToRight. - QTest::mouseClick(view.focusProxy(), Qt::LeftButton, 0, QPoint(20, 595)); + QTest::mouseClick(view.focusProxy(), Qt::LeftButton, {}, QPoint(20, 595)); scrollSpy.wait(); QVERIFY(view.page()->scrollPosition() == QPoint(0, 0)); } @@ -1471,7 +1471,7 @@ void tst_QWebEngineView::mouseClick() QVERIFY(evaluateJavaScriptSync(view.page(), "document.activeElement.id").toString().isEmpty()); textInputCenter = elementCenter(view.page(), "input"); - QTest::mouseClick(view.focusProxy(), Qt::LeftButton, 0, textInputCenter); + QTest::mouseClick(view.focusProxy(), Qt::LeftButton, {}, textInputCenter); QTRY_COMPARE(evaluateJavaScriptSync(view.page(), "document.activeElement.id").toString(), QStringLiteral("input")); QCOMPARE(selectionChangedSpy.count(), 0); QVERIFY(view.focusProxy()->inputMethodQuery(Qt::ImCurrentSelection).toString().isEmpty()); @@ -1492,7 +1492,7 @@ void tst_QWebEngineView::mouseClick() QCOMPARE(evaluateJavaScriptSync(view.page(), "document.activeElement.id").toString(), QStringLiteral("input")); QCOMPARE(view.focusProxy()->inputMethodQuery(Qt::ImCurrentSelection).toString(), QStringLiteral("Company")); - QTest::mouseClick(view.focusProxy(), Qt::LeftButton, 0, textInputCenter); + QTest::mouseClick(view.focusProxy(), Qt::LeftButton, {}, textInputCenter); QVERIFY(selectionChangedSpy.wait()); QCOMPARE(selectionChangedSpy.count(), 2); QVERIFY(view.focusProxy()->inputMethodQuery(Qt::ImCurrentSelection).toString().isEmpty()); @@ -1513,7 +1513,7 @@ void tst_QWebEngineView::mouseClick() QCOMPARE(evaluateJavaScriptSync(view.page(), "document.activeElement.id").toString(), QStringLiteral("input")); QCOMPARE(view.focusProxy()->inputMethodQuery(Qt::ImCurrentSelection).toString(), QStringLiteral("The Qt Company")); - QTest::mouseClick(view.focusProxy(), Qt::LeftButton, 0, textInputCenter); + QTest::mouseClick(view.focusProxy(), Qt::LeftButton, {}, textInputCenter); QVERIFY(selectionChangedSpy.wait()); QCOMPARE(selectionChangedSpy.count(), 3); QVERIFY(view.focusProxy()->inputMethodQuery(Qt::ImCurrentSelection).toString().isEmpty()); @@ -1990,7 +1990,7 @@ void tst_QWebEngineView::softwareInputPanel() QVERIFY(loadFinishedSpy.wait()); QPoint textInputCenter = elementCenter(view.page(), "input1"); - QTest::mouseClick(view.focusProxy(), Qt::LeftButton, 0, textInputCenter); + QTest::mouseClick(view.focusProxy(), Qt::LeftButton, {}, textInputCenter); QTRY_COMPARE(evaluateJavaScriptSync(view.page(), "document.activeElement.id").toString(), QStringLiteral("input1")); // This part of the test checks if the SIP (Software Input Panel) is triggered, @@ -2009,7 +2009,7 @@ void tst_QWebEngineView::softwareInputPanel() QTRY_VERIFY(!testContext.isInputPanelVisible()); testContext.hideInputPanel(); - QTest::mouseClick(view.focusProxy(), Qt::LeftButton, 0, textInputCenter); + QTest::mouseClick(view.focusProxy(), Qt::LeftButton, {}, textInputCenter); QTRY_VERIFY(testContext.isInputPanelVisible()); view.setHtml("<html><body><p id='para'>nothing to input here</p></body></html>"); @@ -2017,7 +2017,7 @@ void tst_QWebEngineView::softwareInputPanel() testContext.hideInputPanel(); QPoint paraCenter = elementCenter(view.page(), "para"); - QTest::mouseClick(view.focusProxy(), Qt::LeftButton, 0, paraCenter); + QTest::mouseClick(view.focusProxy(), Qt::LeftButton, {}, paraCenter); QVERIFY(!testContext.isInputPanelVisible()); @@ -2029,7 +2029,7 @@ void tst_QWebEngineView::softwareInputPanel() QVERIFY(loadFinishedSpy.wait()); QPoint btnDivCenter = elementCenter(view.page(), "btnDiv"); - QTest::mouseClick(view.focusProxy(), Qt::LeftButton, 0, btnDivCenter); + QTest::mouseClick(view.focusProxy(), Qt::LeftButton, {}, btnDivCenter); QVERIFY(!testContext.isInputPanelVisible()); } @@ -2054,7 +2054,7 @@ void tst_QWebEngineView::inputContextQueryInput() // Set focus on an input field. QPoint textInputCenter = elementCenter(view.page(), "input1"); - QTest::mouseClick(view.focusProxy(), Qt::LeftButton, 0, textInputCenter); + QTest::mouseClick(view.focusProxy(), Qt::LeftButton, {}, textInputCenter); QTRY_COMPARE(testContext.infos.count(), 2); QCOMPARE(evaluateJavaScriptSync(view.page(), "document.activeElement.id").toString(), QStringLiteral("input1")); foreach (const InputMethodInfo &info, testContext.infos) { @@ -2203,7 +2203,7 @@ void tst_QWebEngineView::inputMethods() QTRY_COMPARE(loadFinishedSpy.size(), 1); QPoint textInputCenter = elementCenter(view.page(), "input1"); - QTest::mouseClick(view.focusProxy(), Qt::LeftButton, 0, textInputCenter); + QTest::mouseClick(view.focusProxy(), Qt::LeftButton, {}, textInputCenter); QTRY_COMPARE(evaluateJavaScriptSync(view.page(), "document.activeElement.id").toString(), QStringLiteral("input1")); // ImCursorRectangle @@ -2304,7 +2304,7 @@ void tst_QWebEngineView::textSelectionInInputField() // LEFT to RIGHT selection // Mouse click event moves the current cursor to the end of the text QPoint textInputCenter = elementCenter(view.page(), "input1"); - QTest::mouseClick(view.focusProxy(), Qt::LeftButton, 0, textInputCenter); + QTest::mouseClick(view.focusProxy(), Qt::LeftButton, {}, textInputCenter); QTRY_COMPARE(evaluateJavaScriptSync(view.page(), "document.activeElement.id").toString(), QStringLiteral("input1")); QTRY_COMPARE(view.focusProxy()->inputMethodQuery(Qt::ImCursorPosition).toInt(), 11); QTRY_COMPARE(view.focusProxy()->inputMethodQuery(Qt::ImAnchorPosition).toInt(), 11); @@ -2346,7 +2346,7 @@ void tst_QWebEngineView::textSelectionInInputField() // RIGHT to LEFT selection // Deselect the selection (this moves the current cursor to the end of the text) - QTest::mouseClick(view.focusProxy(), Qt::LeftButton, 0, textInputCenter); + QTest::mouseClick(view.focusProxy(), Qt::LeftButton, {}, textInputCenter); QVERIFY(selectionChangedSpy.wait()); QCOMPARE(selectionChangedSpy.count(), 2); @@ -2386,7 +2386,7 @@ void tst_QWebEngineView::textSelectionOutOfInputField() QVERIFY(view.page()->selectedText().isEmpty()); // Simple click should not update text selection, however it updates selection bounds in Chromium - QTest::mouseClick(view.focusProxy(), Qt::LeftButton, 0, view.geometry().center()); + QTest::mouseClick(view.focusProxy(), Qt::LeftButton, {}, view.geometry().center()); QCOMPARE(selectionChangedSpy.count(), 0); QVERIFY(!view.hasSelection()); QVERIFY(view.page()->selectedText().isEmpty()); @@ -2399,7 +2399,7 @@ void tst_QWebEngineView::textSelectionOutOfInputField() QCOMPARE(view.page()->selectedText(), QString("This is a text")); // Deselect text by mouse click - QTest::mouseClick(view.focusProxy(), Qt::LeftButton, 0, view.geometry().center()); + QTest::mouseClick(view.focusProxy(), Qt::LeftButton, {}, view.geometry().center()); QVERIFY(selectionChangedSpy.wait()); QCOMPARE(selectionChangedSpy.count(), 2); QVERIFY(!view.hasSelection()); @@ -2446,7 +2446,7 @@ void tst_QWebEngineView::textSelectionOutOfInputField() // Remove selection by clicking into an input field QPoint textInputCenter = elementCenter(view.page(), "input1"); - QTest::mouseClick(view.focusProxy(), Qt::LeftButton, 0, textInputCenter); + QTest::mouseClick(view.focusProxy(), Qt::LeftButton, {}, textInputCenter); QVERIFY(selectionChangedSpy.wait()); QCOMPARE(evaluateJavaScriptSync(view.page(), "document.activeElement.id").toString(), QStringLiteral("input1")); QCOMPARE(selectionChangedSpy.count(), 2); @@ -2461,7 +2461,7 @@ void tst_QWebEngineView::textSelectionOutOfInputField() QCOMPARE(view.page()->selectedText(), QString("QtWebEngine")); // Deselect input field's text by mouse click - QTest::mouseClick(view.focusProxy(), Qt::LeftButton, 0, view.geometry().center()); + QTest::mouseClick(view.focusProxy(), Qt::LeftButton, {}, view.geometry().center()); QVERIFY(selectionChangedSpy.wait()); QCOMPARE(selectionChangedSpy.count(), 4); QVERIFY(!view.hasSelection()); @@ -2482,14 +2482,14 @@ void tst_QWebEngineView::hiddenText() QVERIFY(loadFinishedSpy.wait()); QPoint passwordInputCenter = elementCenter(view.page(), "password1"); - QTest::mouseClick(view.focusProxy(), Qt::LeftButton, 0, passwordInputCenter); + QTest::mouseClick(view.focusProxy(), Qt::LeftButton, {}, passwordInputCenter); QTRY_COMPARE(evaluateJavaScriptSync(view.page(), "document.activeElement.id").toString(), QStringLiteral("password1")); QVERIFY(!view.focusProxy()->testAttribute(Qt::WA_InputMethodEnabled)); QVERIFY(view.focusProxy()->inputMethodHints() & Qt::ImhHiddenText); QPoint textInputCenter = elementCenter(view.page(), "input1"); - QTest::mouseClick(view.focusProxy(), Qt::LeftButton, 0, textInputCenter); + QTest::mouseClick(view.focusProxy(), Qt::LeftButton, {}, textInputCenter); QTRY_COMPARE(evaluateJavaScriptSync(view.page(), "document.activeElement.id").toString(), QStringLiteral("input1")); QVERIFY(!(view.focusProxy()->inputMethodHints() & Qt::ImhHiddenText)); } @@ -3107,7 +3107,7 @@ void tst_QWebEngineView::globalMouseSelection() // Deselect the selection (this moves the current cursor to the end of the text) QPoint textInputCenter = elementCenter(view.page(), "input1"); - QTest::mouseClick(view.focusProxy(), Qt::LeftButton, 0, textInputCenter); + QTest::mouseClick(view.focusProxy(), Qt::LeftButton, {}, textInputCenter); QVERIFY(selectionChangedSpy.wait()); QCOMPARE(selectionChangedSpy.count(), 2); QVERIFY(QApplication::clipboard()->text(QClipboard::Selection).isEmpty()); diff --git a/tests/auto/widgets/schemes/tst_schemes.cpp b/tests/auto/widgets/schemes/tst_schemes.cpp index 1b6093571..a4a0e34ff 100644 --- a/tests/auto/widgets/schemes/tst_schemes.cpp +++ b/tests/auto/widgets/schemes/tst_schemes.cpp @@ -38,6 +38,7 @@ class tst_Schemes : public QObject Q_OBJECT private Q_SLOTS: + void unknownUrlSchemePolicy_data(); void unknownUrlSchemePolicy(); }; @@ -58,8 +59,27 @@ public: } }; +Q_DECLARE_METATYPE(QWebEngineSettings::UnknownUrlSchemePolicy) + +void tst_Schemes::unknownUrlSchemePolicy_data() +{ + QTest::addColumn<QWebEngineSettings::UnknownUrlSchemePolicy>("policy"); + QTest::addColumn<bool>("userAction"); + QTest::newRow("DisallowUnknownUrlSchemes, script") << QWebEngineSettings::DisallowUnknownUrlSchemes << false; + QTest::newRow("DisallowUnknownUrlSchemes, user") << QWebEngineSettings::DisallowUnknownUrlSchemes << true; + QTest::newRow("AllowUnknownUrlSchemesFromUserInteraction, script") << QWebEngineSettings::AllowUnknownUrlSchemesFromUserInteraction << false; + QTest::newRow("AllowUnknownUrlSchemesFromUserInteraction, user") << QWebEngineSettings::AllowUnknownUrlSchemesFromUserInteraction << true; + QTest::newRow("AllowAllUnknownUrlSchemes, script") << QWebEngineSettings::AllowAllUnknownUrlSchemes << false; + QTest::newRow("AllowAllUnknownUrlSchemes, user") << QWebEngineSettings::AllowAllUnknownUrlSchemes << true; + QTest::newRow("default UnknownUrlSchemePolicy, script") << QWebEngineSettings::UnknownUrlSchemePolicy(0) << false; + QTest::newRow("default UnknownUrlSchemePolicy, user") << QWebEngineSettings::UnknownUrlSchemePolicy(0) << true; +} + void tst_Schemes::unknownUrlSchemePolicy() { + QFETCH(QWebEngineSettings::UnknownUrlSchemePolicy, policy); + QFETCH(bool, userAction); + QWebEngineView view; AcceptNavigationRequestHandler page; QSignalSpy loadFinishedSpy(&page, &QWebEnginePage::loadFinished); @@ -71,41 +91,31 @@ void tst_Schemes::unknownUrlSchemePolicy() settings->setAttribute(QWebEngineSettings::ErrorPageEnabled, true); settings->setAttribute(QWebEngineSettings::FocusOnNavigationEnabled, true); - QWebEngineSettings::UnknownUrlSchemePolicy policies[6] = {QWebEngineSettings::DisallowUnknownUrlSchemes, - QWebEngineSettings::DisallowUnknownUrlSchemes, - QWebEngineSettings::AllowUnknownUrlSchemesFromUserInteraction, - QWebEngineSettings::AllowUnknownUrlSchemesFromUserInteraction, - QWebEngineSettings::AllowAllUnknownUrlSchemes, - QWebEngineSettings::AllowAllUnknownUrlSchemes}; - // even iterations are for navigation-requests from javascript, - // odd iterations are for navigations-requests from user-interaction - for (int i = 0; i < 8; i++) { - if (i <= 5) - settings->setUnknownUrlSchemePolicy(policies[i]); - else - settings->resetUnknownUrlSchemePolicy(); - loadFinishedSpy.clear(); - page.acceptNavigationRequestCalls = 0; - bool shouldAccept; - - if (i % 2 == 0) { // navigation request coming from javascript - shouldAccept = (4 <= i && i <= 5); // only case AllowAllUnknownUrlSchemes - view.setHtml("<html><script>setTimeout(function(){ window.location.href='nonexistentscheme://somewhere'; }, 10);</script><body>testing...</body></html>"); - } else { // navigation request coming from user interaction - shouldAccept = (2 <= i); // all cases except DisallowUnknownUrlSchemes - view.setHtml("<html><body><a id='nonexlink' href='nonexistentscheme://somewhere'>nonexistentscheme://somewhere</a></body></html>"); - QTRY_COMPARE_WITH_TIMEOUT(loadFinishedSpy.size(), 1, 15000); - // focus and trigger the link - view.page()->runJavaScript("document.getElementById('nonexlink').focus();", [&view](const QVariant &result) { - Q_UNUSED(result); - QTest::sendKeyEvent(QTest::Press, view.focusProxy(), Qt::Key_Return, QString("\r"), Qt::NoModifier); - QTest::sendKeyEvent(QTest::Release, view.focusProxy(), Qt::Key_Return, QString("\r"), Qt::NoModifier); - }); - } + if (policy > 0) + settings->setUnknownUrlSchemePolicy(policy); + else + settings->resetUnknownUrlSchemePolicy(); + loadFinishedSpy.clear(); + page.acceptNavigationRequestCalls = 0; + bool shouldAccept; - QTRY_COMPARE_WITH_TIMEOUT(loadFinishedSpy.size(), 2, 60000); - QCOMPARE(page.acceptNavigationRequestCalls, shouldAccept ? 1 : 0); + if (!userAction) { // navigation request coming from javascript + shouldAccept = (policy == QWebEngineSettings::AllowAllUnknownUrlSchemes); + view.setHtml("<html><script>setTimeout(function(){ window.location.href='nonexistentscheme://somewhere'; }, 10);</script><body>testing...</body></html>"); + } else { // navigation request coming from user interaction + shouldAccept = (policy != QWebEngineSettings::DisallowUnknownUrlSchemes); + view.setHtml("<html><body><a id='nonexlink' href='nonexistentscheme://somewhere'>nonexistentscheme://somewhere</a></body></html>"); + QTRY_COMPARE_WITH_TIMEOUT(loadFinishedSpy.size(), 1, 15000); + // focus and trigger the link + view.page()->runJavaScript("document.getElementById('nonexlink').focus();", [&view](const QVariant &result) { + Q_UNUSED(result); + QTest::sendKeyEvent(QTest::Press, view.focusProxy(), Qt::Key_Return, QString("\r"), Qt::NoModifier); + QTest::sendKeyEvent(QTest::Release, view.focusProxy(), Qt::Key_Return, QString("\r"), Qt::NoModifier); + }); } + + QTRY_COMPARE_WITH_TIMEOUT(loadFinishedSpy.size(), 2, 60000); + QCOMPARE(page.acceptNavigationRequestCalls, shouldAccept ? 1 : 0); } QTEST_MAIN(tst_Schemes) diff --git a/tests/auto/widgets/spellchecking/tst_spellchecking.cpp b/tests/auto/widgets/spellchecking/tst_spellchecking.cpp index 64df05d89..801e2a76c 100644 --- a/tests/auto/widgets/spellchecking/tst_spellchecking.cpp +++ b/tests/auto/widgets/spellchecking/tst_spellchecking.cpp @@ -41,10 +41,10 @@ public: void activateMenu(QWidget *widget, const QPoint &position) { QTest::mouseMove(widget, position); - QTest::mousePress(widget, Qt::RightButton, 0, position); + QTest::mousePress(widget, Qt::RightButton, {}, position); QContextMenuEvent evcont(QContextMenuEvent::Mouse, position, mapToGlobal(position)); event(&evcont); - QTest::mouseRelease(widget, Qt::RightButton, 0, position); + QTest::mouseRelease(widget, Qt::RightButton, {}, position); } const QWebEngineContextMenuData& data() @@ -175,8 +175,8 @@ void tst_Spellchecking::spellcheck() //type text, spellchecker needs time QTest::mouseMove(m_view->focusWidget(), QPoint(20,20)); - QTest::mousePress(m_view->focusWidget(), Qt::LeftButton, 0, QPoint(20,20)); - QTest::mouseRelease(m_view->focusWidget(), Qt::LeftButton, 0, QPoint(20,20)); + QTest::mousePress(m_view->focusWidget(), Qt::LeftButton, {}, QPoint(20,20)); + QTest::mouseRelease(m_view->focusWidget(), Qt::LeftButton, {}, QPoint(20,20)); QString text("I lowe Qt ...."); for (int i = 0; i < text.length(); i++) { QTest::keyClicks(m_view->focusWidget(), text.at(i)); diff --git a/tests/auto/widgets/util.h b/tests/auto/widgets/util.h index eba974f33..20241be8b 100644 --- a/tests/auto/widgets/util.h +++ b/tests/auto/widgets/util.h @@ -132,7 +132,7 @@ static inline QString toHtmlSync(QWebEnginePage *page) static inline bool findTextSync(QWebEnginePage *page, const QString &subString) { CallbackSpy<bool> spy; - page->findText(subString, 0, spy.ref()); + page->findText(subString, {}, spy.ref()); return spy.waitForResult(); } diff --git a/tests/manual/quick/pdf/listview.qml b/tests/manual/quick/pdf/listview.qml new file mode 100644 index 000000000..361ae7d89 --- /dev/null +++ b/tests/manual/quick/pdf/listview.qml @@ -0,0 +1,95 @@ +/**************************************************************************** +** +** Copyright (C) 2020 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$ +** +****************************************************************************/ +import QtQuick 2.14 +import QtQuick.Window 2.14 +import QtQuick.Pdf 5.15 + +Window { + width: 600 + height: 440 + color: "lightgrey" + title: doc.source + visible: true + + PdfDocument { + id: doc + source: "test.pdf" + } + + ListView { + id: listView + anchors.fill: parent + model: doc.pageCount + spacing: 6 + delegate: Column { + Rectangle { + id: paper + width: image.width + height: image.height + Image { + id: image + objectName: "PDF page " + index + source: doc.source + currentFrame: index + asynchronous: true + } + } + Text { + text: "Page " + (image.currentFrame + 1) + } + } + } + + Text { + anchors.bottom: parent.bottom + anchors.right: parent.right + text: "page " + Math.max(1, (listView.indexAt(0, listView.contentY) + 1)) + " of " + doc.pageCount + } +} diff --git a/tests/manual/quick/pdf/pessimizedListView.qml b/tests/manual/quick/pdf/pessimizedListView.qml new file mode 100644 index 000000000..4ae0edabe --- /dev/null +++ b/tests/manual/quick/pdf/pessimizedListView.qml @@ -0,0 +1,174 @@ +/**************************************************************************** +** +** Copyright (C) 2020 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$ +** +****************************************************************************/ +import QtQuick 2.14 +import QtQuick.Controls 2.14 +import QtQuick.Layouts 1.14 +import QtQuick.Pdf 5.15 +import Qt.labs.platform 1.1 as P + +ApplicationWindow { + width: 900 + height: 1000 + color: "lightgrey" + title: doc.source + " scale " + imageScale.toFixed(2) + visible: true + property real imageScale: 1 + + header: ToolBar { + RowLayout { + anchors.fill: parent + anchors.rightMargin: 6 + ToolButton { + action: Action { + text: "🗁" + shortcut: StandardKey.Open + onTriggered: fileDialog.open() + } + } + ToolButton { + action: Action { + text: "⊕" + shortcut: StandardKey.ZoomIn + onTriggered: imageScale *= Math.sqrt(2) + } + } + ToolButton { + action: Action { + text: "⊖" + shortcut: StandardKey.ZoomOut + onTriggered: imageScale /= Math.sqrt(2) + } + } + ToolButton { + action: Action { + text: "1x" + shortcut: "Ctrl+0" + onTriggered: imageScale = 1 + } + } + + Label { + text: "Pixels/point:" + } + SpinBox { + id: oversamplingSB + from: 1; to: 8; value: 4 + } + + Label { + text: "cacheBuffer:" + } + SpinBox { + id: cacheBufferSB + from: 0; to: 1000; stepSize: 50; value: 100 + } + + CheckBox { + id: asyncCB + text: "async" + } + + CheckBox { + id: cacheCB + text: "cache" + } + } + } + + PdfDocument { + id: doc + source: "test.pdf" + } + + P.FileDialog { + id: fileDialog + title: "Open a PDF file" + nameFilters: [ "PDF files (*.pdf)" ] + onAccepted: doc.source = file + } + + ListView { + id: listView + anchors.fill: parent + anchors.margins: 10 + model: doc.pageCount + spacing: 6 + cacheBuffer: cacheBufferSB.value + ScrollBar.vertical: ScrollBar { } + delegate: Rectangle { + id: paper + width: Math.max(60, image.width) * imageScale + height: 100 * imageScale + BusyIndicator { + anchors.centerIn: parent + running: image.status === Image.Loading + } + Image { + id: image + scale: imageScale + anchors.centerIn: parent + sourceSize.width: doc.pagePointSize(index).width * oversamplingSB.value + height: 100 + fillMode: Image.PreserveAspectFit + objectName: "PDF page " + index + source: doc.source + currentFrame: index + asynchronous: asyncCB.checked + cache: cacheCB.checked + } + } + } + + Text { + anchors.bottom: parent.bottom + anchors.right: parent.right + text: "page " + Math.max(1, (listView.indexAt(0, listView.contentY) + 1)) + " of " + doc.pageCount + } +} diff --git a/tests/manual/quick/pdf/simplest.qml b/tests/manual/quick/pdf/simplest.qml new file mode 100644 index 000000000..0571493af --- /dev/null +++ b/tests/manual/quick/pdf/simplest.qml @@ -0,0 +1,66 @@ +/**************************************************************************** +** +** Copyright (C) 2020 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$ +** +****************************************************************************/ +import QtQuick 2.14 + +Image { + id: image + source: "test.pdf" + fillMode: Image.PreserveAspectFit + Shortcut { + sequence: StandardKey.MoveToNextPage + enabled: image.currentFrame < image.frameCount - 1 + onActivated: image.currentFrame++ + } + Shortcut { + sequence: StandardKey.MoveToPreviousPage + enabled: image.currentFrame > 0 + onActivated: image.currentFrame-- + } +} diff --git a/tests/manual/quick/pdf/test.pdf b/tests/manual/quick/pdf/test.pdf Binary files differnew file mode 100644 index 000000000..a9dc1bc29 --- /dev/null +++ b/tests/manual/quick/pdf/test.pdf diff --git a/tests/manual/quick/pdf/withdoc.qml b/tests/manual/quick/pdf/withdoc.qml new file mode 100644 index 000000000..0fed5b16e --- /dev/null +++ b/tests/manual/quick/pdf/withdoc.qml @@ -0,0 +1,159 @@ +/**************************************************************************** +** +** Copyright (C) 2020 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$ +** +****************************************************************************/ +import QtQuick 2.14 +import QtQuick.Controls 2.14 +import Qt.labs.platform 1.1 as Platform +import QtQuick.Pdf 5.15 +import QtQuick.Window 2.14 + +Window { + width: 800 + height: 940 + color: "lightgrey" + title: doc.source + visible: true + + PdfDocument { + id: doc + source: "test.pdf" + } + + Platform.FileDialog { + id: fileDialog + title: "Open a PDF file" + nameFilters: [ "PDF files (*.pdf)" ] + onAccepted: doc.source = file + } + + Column { + id: column + anchors.fill: parent + anchors.margins: 6 + spacing: 6 + Text { text: "title: " + doc.title; visible: doc.title !== "" } + Text { text: "author: " + doc.author; visible: doc.author !== "" } + Text { text: "subject: " + doc.subject; visible: doc.subject !== "" } + Text { text: "keywords: " + doc.keywords; visible: doc.keywords !== "" } + Text { text: "producer: " + doc.producer; visible: doc.producer !== "" } + Text { text: "creator: " + doc.creator; visible: doc.creator !== "" } + Text { text: "creationDate: " + doc.creationDate; visible: doc.creationDate !== "" } + Text { text: "modificationDate: " + doc.modificationDate; visible: doc.modificationDate !== "" } + Text { text: "implicit size: " + image.implicitWidth + "x" + image.implicitHeight } + Text { text: "source size: " + image.sourceSize.width + "x" + image.sourceSize.height } + Text { text: "painted size: " + image.paintedWidth + "x" + image.paintedHeight } + + Flickable { + width: column.width + height: width + contentWidth: paper.width + contentHeight: paper.height + z: -1 + Rectangle { + id: paper + width: image.width + height: image.height + Image { + id: image + source: doc.status === PdfDocument.Ready ? doc.source : "" + + property real zoomFactor: Math.sqrt(2) + + DragHandler { + id: dragHandler + target: null + } + + Shortcut { + sequence: StandardKey.MoveToNextPage + enabled: image.currentFrame < image.frameCount - 1 + onActivated: image.currentFrame++ + } + Shortcut { + sequence: StandardKey.MoveToPreviousPage + enabled: image.currentFrame > 0 + onActivated: image.currentFrame-- + } + Shortcut { + sequence: StandardKey.ZoomIn + enabled: image.sourceSize.width < 5000 + onActivated: { + image.sourceSize.width = image.implicitWidth * image.zoomFactor + image.sourceSize.height = image.implicitHeight * image.zoomFactor + } + } + Shortcut { + sequence: StandardKey.ZoomOut + enabled: image.width > 50 + onActivated: { + image.sourceSize.width = image.implicitWidth / image.zoomFactor + image.sourceSize.height = image.implicitHeight / image.zoomFactor + } + } + Shortcut { + sequence: "Ctrl+0" + onActivated: image.sourceSize = undefined + } + Shortcut { + sequence: StandardKey.Open + onActivated: fileDialog.open() + } + Shortcut { + sequence: StandardKey.Quit + onActivated: Qt.quit() + } + } + } + } + } + Text { + anchors.bottom: parent.bottom + text: "page " + (image.currentFrame + 1) + " of " + doc.pageCount + } +} diff --git a/tools/scripts/take_snapshot.py b/tools/scripts/take_snapshot.py index 741979a9e..b7c2c4bd9 100755 --- a/tools/scripts/take_snapshot.py +++ b/tools/scripts/take_snapshot.py @@ -179,7 +179,6 @@ def isInChromiumBlacklist(file_path): or file_path.startswith('third_party/icu/android') or file_path.startswith('third_party/icu/cast') or file_path.startswith('third_party/icu/chromeos') - or file_path.startswith('third_party/icu/ios') or file_path.startswith('third_party/instrumented_libraries') or file_path.startswith('third_party/jsr-305') or file_path.startswith('third_party/junit') |